-
Notifications
You must be signed in to change notification settings - Fork 5
Vagner - depositFromNotional
function is payable, which means that it should accept Ether, but in reality will revert 100% when msg.value > 0
#51
Comments
Trade Handler is executed in a delegate call context, I don't really follow this issue. |
depositFromNotional
function is payable, which means that it should accept Ether, but in reality will revert 100% when msg.value > 0depositFromNotional
function is payable, which means that it should accept Ether, but in reality will revert 100% when msg.value > 0
Escalate File: core\vm\contract.go
134: func (c *Contract) AsDelegate() *Contract {
135: // NOTE: caller must, at all times be a contract. It should never happen
136: // that caller is something other than a Contract.
137: parent := c.caller.(*Contract)
138: c.CallerAddress = parent.CallerAddress
139: c.value = parent.value
140:
141: return c
142: } basically what this means is that, anytime when you have a function that is payable, and implicitly has |
You've created a valid escalation! To remove the escalation from consideration: Delete your comment. You may delete or edit your escalation comment anytime before the 48-hour escalation window closes. After that, the escalation becomes final. |
LoL, how is #8 a low? Because i didn't use the same exact words you used when describing your issue? Its vulnerability detail: tradingModule.executeTrade() and TradingModule.executeTradeWithDynamicSlippage() won't be able to receive ETH (Whenever ETH is sell token) because they lack the payable keyword. This can cause reverts in some of the key functions of the vaults like: depositFromNotional() And its impact: vaults will be unable to execute trades on external exchanges via the trading module whenever ETH is the sell Token` How are they different? |
Hey, I didn't say that it was a low personally, I said that it is not a duplicate of this, and should be considered as the duplicate rule stated, if it is kept as a duplicate. The big difference is that the report failed to find the real problem of this whole issue, the |
The Msg.value will always be > 0 whenever the token being transferred is ETH |
Hi @VagnerAndrei26 @AuditorPraise can any of you provide me a coded test file proving the issue? Imo if it is 100% going to revert, should be easy enough to proof it, appreciate your assistance. |
Yeah I can, I can show a simpler implementation of 2 contracts doing some delegatecalls, I am 100% sure on this issue, because I got paid for it multiple times already and it got confirmed in other contest too. Will provide the simple example later today, after I get home. |
Hello bro @nevillehuang, i just wrote a quick implementation of 2 contracts doing some delegatecalls on remix. // SPDX-License-Identifier: GPL-3.0
pragma solidity =0.8.18;
/**
* @hypothesis
* @dev just for research.
*/
import "hardhat/console.sol";
contract ContractA {
receive() external payable {}
address public contractBAddress;
event EtherTransferred(address indexed from, address indexed to, uint256 amount);
constructor(address _contractBAddress) {
contractBAddress = _contractBAddress;
}
function delegateCallToContractB(uint256 amount) payable external {
// Use delegatecall to execute the transferEther function in ContractB
(bool success, ) = payable(contractBAddress).delegatecall(
abi.encodeWithSignature("transferEther(address,uint256)", msg.sender, amount)
);
require(success, "Delegate call to ContractB failed");
console.log("trf was successful");
}
}
contract ContractB {
receive() external payable {}
function transferEther(address recipient, uint256 amount)
external {
//@audit-issue the delegate call from contract A fails without the payable keyword here
}
} So this is supposed to show the issue we are talking about. The code above should revert, then add the Here's an article that may be of help with sending Ether on remix How do you send Ether as a function to a contract using Remix? |
The 2 reports are duplicates IMO, as the root issues is stated in both of them, and their mitigations solves the issues well. |
@jeffywu Is there a foundry test revolving this issue that shows it will not revert if u pass in a msg.value? I am really surprised if this is true that a test would not have caught this. Since the PoC provided is not protocol specific, I will have to double check before making any final comment. |
The example provided from @AuditorPraise is good, it is pretty similar to what I wanted to present. The bases of this issue is simple, if you have a |
Awaiting a protocol-specific PoC @VagnerAndrei26 and a comment from @jeffywu. |
Hey @Czar102 it was kinda hard to do it, because of the complex codebase and test, but here is the protocol specific POC. function enterVaultBypass(
address account,
uint256 depositAmount,
uint256 maturity,
bytes memory data
) internal virtual returns (uint256 vaultShares) {
vm.prank(address(NOTIONAL));
deal(address(NOTIONAL), depositAmount);
vaultShares = vault.depositFromNotional{value : depositAmount}(account, depositAmount, maturity, data);
totalVaultShares[maturity] += vaultShares;
totalVaultSharesAllMaturities += vaultShares;
} and here is the trace logs, as you can see it revert at executeTrade, as I expected and explained in the report
and here is the case where the ETH is transferred to the vault itself, and then function enterVaultBypass(
address account,
uint256 depositAmount,
uint256 maturity,
bytes memory data
) internal virtual returns (uint256 vaultShares) {
vm.prank(address(NOTIONAL));
deal(address(vault), depositAmount);
vaultShares = vault.depositFromNotional(account, depositAmount, maturity, data);
totalVaultShares[maturity] += vaultShares;
totalVaultSharesAllMaturities += vaultShares;
} and here is the trace call, where the call succeded
The reason that they didn't find that in the test case is because they were doing the tests in the second way, by transferring the ETH to the Vault with |
Interesting, thank you @VagnerAndrei26. What is the loss of funds in this scenario? |
This does not fall under loss of funds here, but more towards the contract rendered useless, since the main functionality, the deposit one, would not work, especially for the pools that have ETH, like the one I provided in the test file. |
@VagnerAndrei26 appreciate the follow up on this issue, I agree it is indeed valid. Good find. I was able to reproduce this below and put a fix in: |
It seems to me that the same is possible using another way. Also, there is no loss of funds, only loss of functionality. This is a really good find, but per Sherlock rules, I don't think I can make it a medium. Planning to leave it a low severity issue. Appreciate the escalation. |
Hi @Czar102 based on this comment by @VagnerAndrei26 I think this qualifies for a medium on grounds on a permanent DoS on a desired functionality no?
What is the possible other way you are referring to? |
Hey @Czar102 , there is not really other ways of doing it. The only way of doing it is passing |
Hello boss @Czar102, There's no other way to send ETH other than having msg.value > 0. IMO i think this qualifies as a MED due to permanent Dos on a desired functionality. |
Hi, that's right, sorry for my misunderstanding and the confusion because of it. I see how the test didn't fully check the functionality. It's a great find. Planning to accept the escalation and make the issue a valid medium. |
Result: |
Escalations have been resolved successfully! Escalation status:
|
Fixed in PR 80 |
Vagner
high
depositFromNotional
function is payable, which means that it should accept Ether, but in reality will revert 100% when msg.value > 0Summary
The function
depositFromNotional
used inBaseStrategyVault.sol
is payable, which means that it should accept Ether, but in reality it will revert every time when msg.value is > than 0 in any of existing strategy.Vulnerability Detail
depositFromNotional
is a function used inBaseStrategyVault.sol
for every strategy, to deposit from notional to a specific strategy. As you can see this function has thepayable
keywordhttps://github.com/sherlock-audit/2023-10-notional/blob/main/leveraged-vaults/contracts/vaults/common/BaseStrategyVault.sol#L166-L173
which means that it is expected to be used along with msg.value being > than 0. This function would call
_depositFromNotional
which is different on any strategy used, but let's take the most simple case, since all of them will be the same in the end, the case ofCrossCurrencyVault.sol
. InCrossCurrencyVault.sol
,_depositFromNotional
would later call_executeTrade
https://github.com/sherlock-audit/2023-10-notional/blob/main/leveraged-vaults/contracts/vaults/CrossCurrencyVault.sol#L184
which would use the
TradeHandler
library as can be seen herehttps://github.com/sherlock-audit/2023-10-notional/blob/main/leveraged-vaults/contracts/vaults/common/BaseStrategyVault.sol#L124
If we look into the
TradeHandler
library,_executeTrade
would delegatecall into the implementation ofTradingModule.sol
toexecuteTrade
function, as can be seen herehttps://github.com/sherlock-audit/2023-10-notional/blob/main/leveraged-vaults/contracts/trading/TradeHandler.sol#L41-L42
If we look into the
executeTrade
function inTradingModule.sol
we can see that this function does not have the payable, keyword, which mean that it will not accept msg.value > than 0https://github.com/sherlock-audit/2023-10-notional/blob/main/leveraged-vaults/contracts/trading/TradingModule.sol#L169-L193
The big and important thing to know about delegate call is that, msg.sender and msg.value will always be kept when you delegate call, so the problem that arise here is the fact that , the calls made to
depositFromNotional
with msg.value > 0 , would always revert when it gets to this delegatecall, in every strategy, since the function that it delegates to , doesn't have thepayable
keyword, and since msg.value is always kept trough the delegate calls, the call would just revert. This would be the case for all the strategies since they all uses_executeTrade
or_executeTradeWithDynamicSlippage
in a way or another, so every payable function that would use any of these 2 functions from theTradeHandler.sol
library would revert all the time, if msg.value is > 0.Impact
Impact is a high one, since strategy using pools that need to interact with Ether would be useless and the whole functionality would be broken.
Code Snippet
https://github.com/sherlock-audit/2023-10-notional/blob/main/leveraged-vaults/contracts/trading/TradeHandler.sol#L18-L45
Tool used
Manual Review
Recommendation
There are multiple solution to this, the easiest one is to make the function
executeTrade
inTradingModule.sol
payable, so the delegate call would not revert when msg.value is greater than 0, or if you don't intend to use ether withdepositFromNotional
, remove thepayable
keyword. It is important to take special care in those delegate calls since they are used multiple times in the codebase, and could mess up functionalities when msg.value is intended to be used.The text was updated successfully, but these errors were encountered: