ππ I present a way to set up rate limits that are automatically updated, dynamically increase over time, and protect your protocol from big hacks.
You can find π a reference implementation and the thinking behind all of this.
Recently I've been thinking about limiting the damage hacks do. And I've had chats with a few founders trying to solve this issue with very different approaches.
It's nearly impossible to ensure code is completely safe, but there might be ways to limit the amount of damage done
Many protocols, especially in the beginning, limit the number of funds users can deposit. This choice is usually done to prevent the amount of lost user funds in case a hack happens or the protocol malfunctions.
ποΈ However, these limits are set by the governance. There are a few problems with this approach.
1οΈβ£ Setting the limit through governance is expensive and gives power to a very limited number of people.
2οΈβ£ It takes a lot of time since the vote needs to pass.
3οΈβ£ The proposer needs to pick the right amount, and we're stuck with that until the gov decides to update it.
π§πΌβπ» My approach implements a few ways of setting limits at the protocol level.
And I can explain each approach and its advantages and disadvantages. I also add handwritten notes explaining how this works in practice with concrete numbers.
The first and simplest way to implement a limit is a
1οΈβ£ Limit per action
This type of rate limiting blocks any amount higher than the specified limit.
You can achieve this by defining a modifier that receives the argument `amount` and checks if it's above the specified limit.
πͺ‘ This isn't too difficult to implement but doesn't help much. An attacker could still extract infinite funds, but not all at once.
They need to split their action into multiple actions. Eventually, they withdraw as many funds as they want.
The second way is a bit better
2οΈβ£ Rate limit per second
You can define a rate limit per second enforced since the limit was set. This does limit the number of moved tokens but has other associated risks.
If the protocol was used a relatively long time ago but not so much recently, there is a lot of unused rate limit which can be used up all at once.
I hope my poorly hand-drawn graph shows this case.
A different way of doing this is implementing a
3οΈβ£ dynamically increasing bucket system.
You can think about this as having an empty bucket available for the actions you want to limit. If the bucket fills up, you need a larger bucket for the next time interval.
I like this system much more because even though the buckets increase over time, they increase only if the protocol increases in usage.
If the protocol is not used anymore, the limit falls to the initially set limit.
It does not require an expensive governance vote to update the limit to new highs, the cost is offloaded to the users interacting with the protocol.
Of course, the implementation can be improved to
- β½οΈ use less gas
- π¦ have more features
- π€ be more readable
Also, there might be other problems in the implementation that we don't know about. The point of this implementation is for educational purposes and to have fun.
github.com/cleanunicorn/rate-limit
If you got this far, we can start discussing different approaches to this problem. And I am curious to hear what your ideas are.
Let me start.
There are a few off-chain approaches one could do to protect their protocol.
π Analyze the trends and progressively move the limit higher (or lower).
This either requires direct access to the protocol or an ordinary governance vote. However, it centralizes power if it's done directly, or it's expensive and slow if it goes through governance.
π Monitor the mem pool and try to front-run the hacking transaction.
This is even more difficult to do in practice because it's really hard to know in real-time what the hacks are; one also needs relationships with block producers for higher certainty that it will be included.