Time decaying voting weight

I have a voting token which is this:

contract TokenVP is
    ERC20,
    ERC20Burnable,
    RoleController,
    ERC20Permit,
    ERC20Votes
{

I also have another ERC20 token which is a regular everyday token. Lets call it MyERC20 I give some amount of TokenVP to the users based on this simple formula: (Locked amount of MyERC20) * (Locked amount of days)

Example for the above equation: I have 100 MyERC20 tokens. I lock for 10 days. I get: (100*10)=1000 TokenVP.

But the amount of TokenVP will stay as it is (1000) all the time. I want TokenVP to decay over time. For simplicity, everyday it will decay like this: Say 1 day has passed. Then the amount of TokenVP I should have: (100*9)=900 TokenVP. How can I achieve this behavior so that this is reflected to the voting calculation as well?

What I've tried?

From what I see, the voting weight is calculated when _castVote in Governer.sol is called. Which gets the uint256 weight from GovernerVotes.sol via _getVotes function. Then it goes somewhere else then it ends up in the Votes.sol where the getPastVotes is called. This retrieves _delegateCheckpoints which takes a record only during the delegate calls. This checkpoint is not enough to keep track of decaying voting power since only 1 delegate call is enough to vote during the lock period. I'm stuck...

Why don't you just inherit this contract and override that function in it?

I can but OpenZeppelin's contracts are audited, commented, analyzed etc. Also, there could be an implemented solution which I'm not aware of.

So how is that in conflict with my suggestion?

You don't need to implement your own version of this contract, you just need to inherit it exactly as is, and then override that one specific function which you want to behave in a different manner.

In fact, you need to do that anyway, since this contract is abstract, as it doesn't implement all the functions in the IGovernor interface.

Most likely, you can achieve your goal by implementing it in those "missing" functions (and it might also "relieve" you from the need to override the already implemented function _getVotes).

Hey @DoDZilla,

Indeed, you can override the _getVotes function to add the decaying voting weight logic based on the locked time. However, I don't know exactly how that lock time effectively works so I can't provide a recommendation, but I'd be something like:

abstract contract GovernorDecayingVotes is GovernorVotes {
    function _getVotes(
        address account,
        uint256 timepoint,
        bytes memory /*params*/
    ) internal view virtual override returns (uint256) {
        return token.getPastVotes(account, timepoint) * _multiplier(timepoint);
    }

    function _multiplier(uint256 timepoint) internal view returns (uint256) {
        return 1; // Add your decaying logic here
    }
}

Still, any modification to the voting accounting system should be carefully reviewed since it can have unexpected consequences. For example, make sure both delegated and self-owned votes are counted as you expect.

1 Like

And the decaying-logic implementation should probably be something like:

uint256 private _voteEndTime;

function startVote() external onlyOwner {
    require(_voteEndTime < block.timestamp, "a vote is already in progress");
    _voteEndTime = block.timestamp + 10 days;
}

function _multiplier() private view returns (uint256) {
    return _voteEndTime - block.timestamp;
}