Skip to content
This repository has been archived by the owner on Dec 22, 2024. It is now read-only.

brakeless - When actual executionFee is greater than expected executionFee, lossFee is calculated incorrectly and keepers are not fairly compensated #276

Closed
sherlock-admin4 opened this issue Jun 20, 2024 · 0 comments
Labels
Duplicate A valid issue that is a duplicate of an issue with `Has Duplicates` label Medium A valid Medium severity issue Reward A payout will be made for this issue Sponsor Disputed The sponsor disputed this issue's validity Won't Fix The sponsor confirmed this issue will not be fixed

Comments

@sherlock-admin4
Copy link

sherlock-admin4 commented Jun 20, 2024

brakeless

Medium

When actual executionFee is greater than expected executionFee, lossFee is calculated incorrectly and keepers are not fairly compensated

Summary

In the GasProcess::processExecutionFee() method, when executionFee > cache.userExecutionFee, there are two issues.

First, the lossFee calculation always results in 0, which will prevent the system from incrementing totalLossExecutionFee correctly.

Second, keepers are not compensated fairly for calling the transaction and will lose fee costs, as the cost to execute the transaction will be greater than the fees that they receive.

Vulnerability Detail

// GasProcess::processExecutionFee() (L17 - L41)
function processExecutionFee(PayExecutionFeeParams memory cache) external {
    uint256 usedGas = cache.startGas - gasleft();
    uint256 executionFee = usedGas * tx.gasprice;   // @audit executionFee calculated
    uint256 refundFee;
    uint256 lossFee;
    if (executionFee > cache.userExecutionFee) {
        executionFee = cache.userExecutionFee;    // @audit executionFee overwritten
        lossFee = executionFee - cache.userExecutionFee;  // @audit shouldn't this step occur prior to caching?
    } else {
        refundFee = cache.userExecutionFee - executionFee;
    }
    VaultProcess.transferOut(
        cache.from,
        AppConfig.getChainConfig().wrapperToken,
        address(this),
        cache.userExecutionFee
    );
    VaultProcess.withdrawEther(cache.keeper, executionFee);   // @audit cache.userExecutionFee < executionFee
    if (refundFee > 0) {
        VaultProcess.withdrawEther(cache.account, refundFee);
    }
    if (lossFee > 0) {   // @audit never triggered
        CommonData.addLossExecutionFee(lossFee);
    }
}
// CommonData::addLossExecutionFee() (L132 - L136)
function addLossExecutionFee(uint256 amount) external {
    Props storage self = load();
    self.totalLossExecutionFee += amount;
    emit LossExecutionFeeUpdateEvent(self.totalLossExecutionFee - amount, self.totalLossExecutionFee);
}

Incorrect lossFee calculation

GasProcess::processExecutionFee() is called by two-phase commit functions that are executed by a keeper in OrderFacet, PositionFacet, and StakeFacet .

This method is intended to pay out the keeper execution fee.

This method also pays either a refund to the account’s owner if the actual execution fee is less than the expected fee, or tracks additional amounts as a lossFee in case execution costs more than it should.

In the code snippet above, the executionFee is initially calculated based on how much gas was consumed in the transaction.

If the actual executionFee > expected executionFee, the executionFee memory variable is overwritten with the expected executionFee (cache.executionFee), which is passed in as input.

Then, the lossFee is calculated as executionFee - cache.userExecutionFee = cache.userExecutionFee - cache.userExecutionFee = 0.

As a result, the conditional at the bottom of the function body is never executed and totalLossExecutionFee is never incremented.

Keepers are not fairly compensated

In addition, keepers will be paid cache.executionFee, which is less than the actual executionFee amount.

Protocols that utilize keepers often top-up and reserve additional funds within the system for keeper fees.

Elfi expects that users will pay for the executionFee most of the time, which is provided when creating an order or updating a position.

However, if the executionFee costs more than what is expected, keepers will pay more to execute the transaction than what they receive in fees, which may discourage keepers from doing work for the protocol.

Impact

totalLossExecutionFee is never updated or modified, as intended.

As a result, totalLossExecutionFee will remain 0 for the lifetime of the system.

Keepers will also lose fee costs when the execution costs more than expected.

Code Snippet

https://github.com/sherlock-audit/2024-05-elfi-protocol/blob/main/elfi-perp-contracts/contracts/process/GasProcess.sol#L17-L41

Tool used

Manual Review

Recommendation

The keeper should receive the actual executionFee every time.

Also, removing the caching in the first conditional block will allow lossFee to be calculated correctly and CommonData::addLossExecutionFee() to be executed as intended.

function processExecutionFee(PayExecutionFeeParams memory cache) external {
    uint256 usedGas = cache.startGas - gasleft();
    uint256 executionFee = usedGas * tx.gasprice;   
    uint256 refundFee;
    uint256 lossFee;
    if (executionFee > cache.userExecutionFee) {
-       executionFee = cache.userExecutionFee;
        lossFee = executionFee - cache.userExecutionFee;  
        
+       VaultProcess.transferOut(
+           cache.from,
+           AppConfig.getChainConfig().wrapperToken,
+           address(this),
+           executionFee
+       );
    } else {
        refundFee = cache.userExecutionFee - executionFee;
    
+       VaultProcess.transferOut(
+           cache.from,
+           AppConfig.getChainConfig().wrapperToken,
+           address(this),
+           cache.userExecutionFee  // cache.userExecutionFee = executionFee + refundFee
+       );
    }

    VaultProcess.withdrawEther(cache.keeper, executionFee);  // keeper always receives actual executionFee
    if (refundFee > 0) {
        VaultProcess.withdrawEther(cache.account, refundFee);
    }
    if (lossFee > 0) {   
        CommonData.addLossExecutionFee(lossFee);
    }
}

Duplicate of #108

@sherlock-admin3 sherlock-admin3 added Won't Fix The sponsor confirmed this issue will not be fixed and removed Won't Fix The sponsor confirmed this issue will not be fixed labels Jun 21, 2024
@github-actions github-actions bot added Medium A valid Medium severity issue Duplicate A valid issue that is a duplicate of an issue with `Has Duplicates` label labels Jun 27, 2024
@sherlock-admin3 sherlock-admin3 added Sponsor Disputed The sponsor disputed this issue's validity Won't Fix The sponsor confirmed this issue will not be fixed labels Jul 3, 2024
@sherlock-admin2 sherlock-admin2 changed the title Tangy Ocean Ape - When actual executionFee is greater than expected executionFee, lossFee is calculated incorrectly and keepers are not fairly compensated brakeless - When actual executionFee is greater than expected executionFee, lossFee is calculated incorrectly and keepers are not fairly compensated Jul 3, 2024
@sherlock-admin2 sherlock-admin2 added the Reward A payout will be made for this issue label Jul 3, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Duplicate A valid issue that is a duplicate of an issue with `Has Duplicates` label Medium A valid Medium severity issue Reward A payout will be made for this issue Sponsor Disputed The sponsor disputed this issue's validity Won't Fix The sponsor confirmed this issue will not be fixed
Projects
None yet
Development

No branches or pull requests

3 participants