Best way to calculate total fees that vary over time?

I’m writing a smart contract that allows a user to deposit money to pay for a service, and they are charged a fee out of that balance for every block that elapses. I’m not actually modifying their underlying balance to reflect these fees, and instead I’m calculating their effective balance on demand whenever it is needed.

The problem is that the fees are going to be variable over time because governance determines the amount that is charged per block in fees. If I simply charged the current fee rate multiplied by the number of blocks that elapsed, they would be charged that rate for the entire duration instead of just when the fee rate was modified.

To get around this I created a dynamic array that stores a checkpoint. It stores the block number where the fee was changed and what it was changed from. To calculate the fees, I loop over this array and calculates the fee incrementally between each checkpoint.

The problem is that this array could get really big over time if people vote to change the fee a lot. It also seems like my code to incrementally count up the fees is missing some edge cases, but I’m not sure. Is there a proven way to do this kind of calculation that I should look at to help improve my code? How do I tackle the problem of this dynamic array getting too big over time?

I have considered writing a function that consolidates the array by averaging the fees over a few checkpoints and combines them into one checkpoint, but this means that some people will pay a bit more or less in fees for that consolidated block than they are supposed to.

Here’s my code for the function that calculates this. Is there a better way to do it?

 function _calculateFees(uint _lastDeposit) private view returns (uint) {
    uint fees = 0;
    //TODO Safemath
    for(uint i = 0; i < costPerBlockCheckpoints.length; i++) {
        if (_lastDeposit >= costPerBlockCheckpoints[i].block) {
            continue;
        }
        // Add the fees for the partial checkpoint where the _lastDeposit took place
        if (fees == 0) {
            fees += (costPerBlockCheckpoints[i].block - _lastDeposit) * costPerBlockCheckpoints[i-1].value;
            continue;
        }
        // Add the fees for a full checkpoint
        fees += (costPerBlockCheckpoints[i].block - costPerBlockCheckpoints[i-1].block) * costPerBlockCheckpoints[i-1].value;
    }
    
    uint lastCheckpoint = costPerBlockCheckpoints[costPerBlockCheckpoints.length-1].block;
    // Add the fees between the current block and the last checkpoint or the last deposit.
    if (_lastDeposit < lastCheckpoint) {
        fees += (block.number - lastCheckpoint) * costPerBlock;
    }
    else {
        fees += (block.number - _lastDeposit) * costPerBlock;
    }
    
    return fees;
}

Hi branover, welcome to the forums!

My approach would be to assign a mapping that holds the current fee that has been calculated from the previous iterations.

Then when you need to update it, all it needs is the most recent fee from the mapping, and how much to adjust it by. Then add a new index to the mapping with your new total fee amount.

1 Like