Canto v2 contest - Picodes's results

Execution layer for original work.

General Information

Platform: Code4rena

Start Date: 28/06/2022

Pot Size: $25,000 USDC

Total HM: 14

Participants: 50

Period: 4 days

Judge: GalloDaSballo

Total Solo HM: 7

Id: 141

League: ETH

Canto

Findings Distribution

Researcher Performance

Rank: 1/50

Findings: 5

Award: $3,313.85

🌟 Selected for report: 3

🚀 Solo Findings: 2

Findings Information

🌟 Selected for report: Picodes

Labels

bug
3 (High Risk)
sponsor acknowledged

Awards

2386.7697 USDC - $2,386.77

External Links

Lines of code

https://github.com/Plex-Engineer/lending-market-v2/blob/ea5840de72eab58bec837bb51986ac73712fcfde/contracts/ERC20.sol#L33 https://github.com/Plex-Engineer/lending-market/blob/ab31a612be354e252d72faead63d86b844172761/contracts/ERC20.sol#L95

Vulnerability details

Impact

_totalSupply can be initialized to something different than 0, which would lead to an inaccurate total supply, and could easily break integrations, computations of market cap, etc.

Proof of Concept

If the constructor is called with _initialSupply = 1000, the totalSupply will be initialized to 1000.

As all the others computations are correct, there will be for ever a discrepancy of 1000 between the real total supply and the one of the contract.

Remove _initialSupply.

#0 - GalloDaSballo

2022-08-14T23:35:51Z

Same bug as from Canto V1. Recommend the sponsor to just set to 0 and remove the assignment from the constructor

#1 - GalloDaSballo

2022-08-18T19:39:39Z

#2 - Shungy

2022-09-03T02:02:21Z

In the provided contracts, v2 repo is included: https://github.com/code-423n4/2022-06-canto-v2

However, in this submission, the second line of code provided links to the v1 repo. The described issue only exists in v1 version. In v2 version the issue does not exist because msg.sender balance is updated along with the total supply: https://github.com/Plex-Engineer/lending-market-v2/blob/ea5840de72eab58bec837bb51986ac73712fcfde/contracts/ERC20.sol#L34

Therefore this finding seems invalid.

#3 - GalloDaSballo

2022-09-03T14:36:59Z

In the provided contracts, v2 repo is included: https://github.com/code-423n4/2022-06-canto-v2

However, in this submission, the second line of code provided links to the v1 repo. The described issue only exists in v1 version. In v2 version the issue does not exist because msg.sender balance is updated along with the total supply: https://github.com/Plex-Engineer/lending-market-v2/blob/ea5840de72eab58bec837bb51986ac73712fcfde/contracts/ERC20.sol#L34

Therefore this finding seems invalid.

You're right, I must have missed the line with the mitigation.

The current code will update the _totalSupply and will give the balance to the deployer.

This is a mistake on my part and the finding should have been closed as invalid as it was mitigated in the V2 code in scope.

#4 - GalloDaSballo

2022-09-03T14:41:23Z

While a nitpick I'd recommend changing the code to use _mint as it the code in scope will not emit an event which may cause issues if you're tracking via theGraph or similar

Either way I made a mistake here, sorry about that

Findings Information

🌟 Selected for report: Picodes

Also found by: Soosh, cccz, ladboy233

Labels

bug
2 (Med Risk)
sponsor acknowledged

Awards

70.4682 USDC - $70.47

External Links

Lines of code

https://github.com/Plex-Engineer/lending-market-v2/blob/ea5840de72eab58bec837bb51986ac73712fcfde/contracts/Stableswap/BaseV1-periphery.sol#L86

Vulnerability details

Impact

The ensure modifier is commented, so deadlines will not work when passing orders, breaking this functionnality.

#0 - GalloDaSballo

2022-08-13T23:16:27Z

The warden has shown how, due to a comment, the modifier deadline doesn't work.

Because Front-running is a key aspect of AMM design, deadline is a useful tool to ensure that your tx cannot be "saved for later"

Due to the removal of the check, it may be more profitable for a miner to deny the transaction from being mined until the transaction incurs the maximum amount of slippage.

The lack of deadline means that the tx can be withheld indefinitely at the advantage of the miner

For those reasons I agree with Medium Severity

Findings Information

🌟 Selected for report: Picodes

Labels

bug
2 (Med Risk)
sponsor acknowledged

Awards

716.0309 USDC - $716.03

External Links

Lines of code

https://github.com/Plex-Engineer/lending-market-v2/blob/ea5840de72eab58bec837bb51986ac73712fcfde/contracts/CNote.sol#L33 https://github.com/Plex-Engineer/lending-market-v2/blob/ea5840de72eab58bec837bb51986ac73712fcfde/contracts/Note.sol#L14

Vulnerability details

Impact

The fact that there is no cap on the amount of Note that can be borrowed makes the Oracle Extractable Value unlimited. But as you intend to rely on TWAP, you need to make sure the cost of oracle manipulation is lower than the Oracle Extractable Value.

Proof of Concept

By manipulating the TWAPs of the designated proxy used for Note (USDC ?) and its relative price to a given collateral(which would be highly costly), an attacker could borrow Note without limit, and empty all pools related to Note and all Note-related value, instantly killing the stablecoin.

The value extractable by Oracle Manipulations is usually easily computable as it is the size of the lending market, but here, it’s more difficult to evaluate as it could potentially be any value linked to Note. This makes risk management harder and increase significantly the risk of attack.

Therefore a cap on how many Notes can be borrowed needs to be added to mitigate this risk.

The attack would be:

  • Manipulate the USDC / Collateral TWAP to be able to borrow Note with less than 1$ of collateral, which would be costly.
  • Extract all the value possible linked to Note, for example: - by buying all the tokens from pools Note / token at a discount - by supplying Notes to the lending platform and borrow collaterals for which the Note price is still at 1$ - etc

Essentially as you have no cap on the amount of Note that could be borrowed in such a scenario, you cannot be sure that the potential attack profits are lower than the attack cost.

The governance needs to set a limit on how much Note can be borrowed to mitigate risks, or add for example an “hourly” borrowing limit.

Easiest way to do this would be able to mint / burn from the accountant

#0 - GalloDaSballo

2022-08-16T16:49:26Z

I don't think you can manipulate the price of cNOTE per this code

https://github.com/Plex-Engineer/lending-market-v2/blob/443a8c0fed3c5018e95f3881a31b81a555c42b2d/contracts/Stableswap/BaseV1-periphery.sol#L499-L502

        //set price statically to 1 when the Comptroller is retrieving Price
        else if (compareStrings(ctoken.symbol(), "cNOTE") && msg.sender == Comptroller) {
            return 1; // Note price is fixed to 1
        }

However, you can manipulate the price of another token against USDC

https://github.com/Plex-Engineer/lending-market-v2/blob/443a8c0fed3c5018e95f3881a31b81a555c42b2d/contracts/Stableswap/BaseV1-periphery.sol#L529-L533

        else {
            stablePair = (stable == 0) ? false : true;
            pair = IBaseV1Pair(pairFor(USDC, underlying, stablePair)); //get the pair for the USDC/underlying pool
            price = pair.quote(underlying, 1, 8); //how much USDC is this token redeemable for
        }

The attack outlined by the warden would require an imbalance in the price of an asset against the given above code.

It would also require oracle manipulation, which requires no external arbitrage nor intervention It would require some value to be extractable from the system

For those reasons, I think Medium Severity is more appropriate

Awards

104.8725 USDC - $104.87

Labels

bug
QA (Quality Assurance)

External Links

[Low - 01] - Make sure to also initialize implementations when using upgradeable contracts

When using an upgradeable proxy over an implementation it is important to ensure the underlying implementation is initialised in addition to the proxied contract.

Consider the following example where we have a Proxy as contractA and a Implementation as contractB . As a parameter to the constructor of contractA the data is provided to make a delegate call to contractB.initialize() which updates the storage as required in contractA .

Thus, contractB has not been initialized, only contractA has had its state updated. As a result any user is able to call initialize() on contractB , giving themselves full permissions over the contract. There are two reasons why it is not desirable for malicious users to have full control over a contract:

  1. Any delegate calls to external contracts can call selfdestruct which would delete the implementation contract temporarily making the proxy unusable (until it can be updated with a new implementation); This is not the case for your implementations from what I’ve seen (otherwise this would have been a high issue).
  2. Scammers and malicious users may use implementation contracts, and you’d rather avoid this and keep control over them..

Recommendations

Ensure that the initializer modifier is called on all underlying implementations during deployment. This could easily be done by adding in the implementation:

// @custom:oz-upgrades-unsafe-allow constructor
constructor() initializer {}

[Low - 02] - Use storage gaps instead of separating the code from the storage

Instead of the old-fashioned, Compound way of coding upgradeable contracts (with XXStorageV1 that you then use as a child for XXStorageV2), it’d be cleaner to use the OpenZeppelin way, were the storage is were it belongs and you just add storage gaps to contracts.

This holds for TreasuryDelegator and AccountantDelegator. Reference: https://medium.com/nomic-foundation-blog/malicious-backdoors-in-ethereum-proxies-62629adf3357

[Low - 03] - Proxies are subject to Proxy selector clashing

This holds for TreasuryDelegator, AccountantDelegator and the others Delegators. Reference: https://medium.com/nomic-foundation-blog/malicious-backdoors-in-ethereum-proxies-62629adf3357

[NC - 01] - Use type(uint256).max

https://github.com/Plex-Engineer/lending-market-v2/blob/ea5840de72eab58bec837bb51986ac73712fcfde/contracts/ERC20.sol#L18

For readability you can use type(uint256).max instead of defining uint256 MAX_INT = 2**256-1;

[NC - 02] - ERC20: make decimals virtual

According to your comments decimals is intended to be potentially overloaded. Therefore make it virtual.

https://github.com/Plex-Engineer/lending-market-v2/blob/ea5840de72eab58bec837bb51986ac73712fcfde/contracts/ERC20.sol#L65

[NC - 03] - Correct indentation

https://github.com/Plex-Engineer/lending-market-v2/blob/ea5840de72eab58bec837bb51986ac73712fcfde/contracts/ERC20.sol#L92

[NC - 04] - Useless variable

https://github.com/Plex-Engineer/lending-market-v2/blob/ea5840de72eab58bec837bb51986ac73712fcfde/contracts/ERC20.sol#L13

_initialSupply is not used anywhere so could be removed.

[NC - 05] - Import of hardhat.console

https://github.com/Plex-Engineer/lending-market-v2/blob/ea5840de72eab58bec837bb51986ac73712fcfde/contracts/Accountant/AccountantDelegate.sol#L7 https://github.com/Plex-Engineer/lending-market-v2/blob/ea5840de72eab58bec837bb51986ac73712fcfde/contracts/CErc20.sol#L5 https://github.com/Plex-Engineer/lending-market-v2/blob/ea5840de72eab58bec837bb51986ac73712fcfde/contracts/Stableswap/BaseV1-core.sol#L4

[NC - 06] - Remove payable in fallback

Instead of using a custom require, why not just removing the payable modifier in the following fallbacks ?

https://github.com/Plex-Engineer/lending-market-v2/blob/ea5840de72eab58bec837bb51986ac73712fcfde/contracts/Accountant/AccountantDelegator.sol#L123 https://github.com/Plex-Engineer/lending-market-v2/blob/ea5840de72eab58bec837bb51986ac73712fcfde/contracts/Treasury/TreasuryDelegator.sol#L121

[NC - 07] - In Delegator contracts, a lot of functions are useless and could just be remove: the fallback is enough

Examples: https://github.com/Plex-Engineer/lending-market-v2/blob/ea5840de72eab58bec837bb51986ac73712fcfde/contracts/Treasury/TreasuryDelegator.sol#L63 https://github.com/Plex-Engineer/lending-market-v2/blob/ea5840de72eab58bec837bb51986ac73712fcfde/contracts/Treasury/TreasuryDelegator.sol#L67

[NC - 08] - Typo: doesn"t -> doesn’t

https://github.com/Plex-Engineer/lending-market-v2/blob/ea5840de72eab58bec837bb51986ac73712fcfde/contracts/Stableswap/BaseV1-periphery.sol#L230

#0 - GalloDaSballo

2022-08-16T21:00:43Z

[Low - 01] - Make sure to also initialize implementations when using upgradeable contracts

I don't believe this applies to the codebase as the pattern: -> Base Contract with Offset -> DelegateCall -> Implementation Contract with Offset means that the storage is actually kept in BaseContract, and the Offset is used to ensure that the Implementation will not break the BaseContract (which can still happen if done improperly)

However I don't think them implementation needs initialization "in general"

[Low - 02] - Use storage gaps instead of separating the code from the storage

Similar reasoning here, I think Refactoring is more appropriate and No Gaps are necessary for the Implementations as long as they follow the Offset Pattern R

[Low - 03] - Proxies are subject to Proxy selector clashing

This is a valid finding in that a clashing implementation will never be executable as the BaseContract selector will match

L

[NC - 01] - Use type(uint256).max

R

## [NC - 02] - ERC20: make decimals virtual Nice Catch, valid R

[NC - 03] - Correct indentation

R

[NC - 04] - Useless variable

NC

[NC - 05] - Import of hardhat.console

R

[NC - 06] - Remove payable in fallback

R

[NC - 07] - In Delegator contracts, a lot of functions are useless and could just be remove: the fallback is enough

Because of the clash mentioned above, I'm not counting this as valid, I'd assume the idea is to avoid the clashes as mentioned above

[NC - 08] - Typo: doesn"t -> doesn’t

NC

#1 - GalloDaSballo

2022-08-16T21:01:10Z

Nice report, a couple copy pasta but rest is pretty unique

#2 - GalloDaSballo

2022-08-16T21:02:04Z

1L 6R 2NC

Awards

35.707 USDC - $35.71

Labels

bug
G (Gas Optimization)

External Links

#0 - GalloDaSballo

2022-08-14T21:02:56Z

[Gas - 03] Using address to test equality would be more efficient

100 gas from the CALL also another 2.1k here, another 200 for the second CALL + SLOAD. Rest is negligible

I recommend focusing on immutables to keep it short and get additional savings

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