hats-finance / Paladin-0x1610bfde27e57b068af7f38aec3d2a7b1d146989

Smart contract for the Vote-Flywheel part of Paladin Tokenomics
Other
1 stars 1 forks source link

Unable to clear expired power that has already been used for free. #42

Open hats-bug-reporter[bot] opened 8 months ago

hats-bug-reporter[bot] commented 8 months ago

Github username: -- Twitter username: -- Submission hash (on-chain): 0xbeb4a0ecba733f733ff048f2b43cb4c12355e39f4290a20399985fadc3c46e20 Severity: medium

Description: Description\ Some of the user's votes are for proxied voters, while there are also available free votes that the user can utilize. All votes have expiration dates. If the user wishes to clear expired free votes, there is a unique method to cast 0 free votes to that gauge. This affects future voting.

Attack Scenario\ Suppose User A has 20% of their votes as free votes, while the remaining 80% are for proxied voters.

  1. Revised Code File (Optional)

    Add a function to clear expired free votes.

    function clearExpiredUsedFreePower(address user, address gauge)  external {
        VotedSlope memory oldSlope = voteUserSlopes[user][gauge];
    
        if (oldSlope.caller == user && oldSlope.end < block.timestamp) {
            usedFreePower[user] -= oldSlope.power;
            delete voteUserSlopes[user][gauge];
        }
    }
Kogaroshi commented 8 months ago

I think this is invalid, as you only think that to replace a vote a new vote with userPower == 0 is needed. But the user, once the lock is expired, and the cooldown to vote for that gauge is through, the user can vote with a new userPower (lower than the one there is to free a part of the votes if needed, and higher if the user has free voting power available too). The internal calculations will account for the votes being expired, and now to 0, but in practice the usedPower is still 20%, simply 20% of 0 is 0.

GTH1235 commented 8 months ago

@Kogaroshi , Thanks for your comment.

Let me try to break down the example.

If the user decides not to cast free votes to G1, does that mean that none of the proxied voters will cast votes for 10 days?

I think the suggested solution is straightforward and easy to implement.

Further comments would be greatly appreciated.

Kogaroshi commented 8 months ago

The reason I said I thought it was invalid is because the scenario you described is normal. Even in the case no votes expire, the proxies can't vote on gauges the user voted for, which is indeed makes the flow for votes a bit harder to manage for the users and their proxie(s), but also required so the proxy system is not bypassed (by having the user remove proxy casted votes, or offering a possibility to override the cooldown that could then be used to bypass it when it should not be).

GTH1235 commented 8 months ago

Using the suggested solution helps us avoid an undesired situation. By clearing the expired free votes instead of casting votes to G1, we can freely utilize the cleared free power for G2, and proxied voters can still vote for G1 as their choice. I believe this can be marked as at least low priority, but I will respect your decision.

Thanks, @Kogaroshi

Kogaroshi commented 8 months ago

I understood the solution you proposed, but it also introduces a system where users make the lowest duration of locks, and use the new system for reset their votes for free, without any consideration for the cooldown, for them (or their proxie(s) to cast any desired votes without caring about the flow all other users that Lock long time and change their votes when they need go through. Imo that introduces an unfair system, that will also incentivize to make small duration locks rather than long term locks, which means create unfairness towards less-aligned users in the system, which does not make sense to us in the context of tokenomics.

GTH1235 commented 8 months ago

What about this? Of course, I also agree that we should not permit clearing previous free votes before the cooldown time, even though those votes have already expired.

function clearExpiredUsedFreePower(address user, address gauge)  external {
        VotedSlope memory oldSlope = voteUserSlopes[user][gauge];

        if (oldSlope.caller == user && oldSlope.end < block.timestamp && block.timestamp >= lastUserVote[user][gauge] + VOTE_COOLDOWN) {
            usedFreePower[user] -= oldSlope.power;
            delete voteUserSlopes[user][gauge];
        }
}
Kogaroshi commented 8 months ago

Even for proxy votes, it stays the same flaw, as I can just proxy all my vote to my 2nd address for the whole duration of my small locks, and use that feature to bypass the cooldown.

GTH1235 commented 8 months ago

Seems like I am misunderstanding your point.

Let me think more.