The Graph is a decentralized indexing protocol. Developers can access and query data across different blockchain using web2 APIs. Many projects, use this for UIs but also for backend services. It falls into the blockchain infrastructure category. To pay for using the service, there is a token. This is where the TWO bugs are at.
A subgraph API is the curator of creator of the content. To have this be created, a user needs to stake tokens. Alongside this, they pay a 1% curation tax in GRT tokens to the platform. When calling mint() the amount of tokens paid is rounded down. If the tax was 1%, then sending in 99 tokens would result in 0 tokens, instead of 1, being sent in.
To me, this feels fairly insignificant because the cost of gas would be much higher. However, the article claims that since this was deployed on Avalanche (low gas cost EVM chain). To be honest, this felt sorta hand-wavy but I'll live with it. By batching 99 tokens per call in a contract, the cost is cheaper than the cost of gas. This steals revenue from the protocol, which is bad.
When a node operator for the Graph wants to provide services to earn rewards, they stake their GRT tokens for some period of time. When unstaking, there is a thawing period in order to ensure that bad indexers are penalized for their actions.
When calling unstake() there's a calculation error that allows a user to bypass the lock duration. There's a weighted average function for unstaking. It takes into consideration the total unstaked tokens and the amount of tokens newly being unstaked. Given this information, it will return a time where the tokens can be unstaked.
This function is vulnerable to a rounding issue when the currentUnstakedTokens is 201600 larger than newUnstakedTokens. When this happens, the newLockedUntil function will return the previous time! Using the same strategy as before, an attacker can unstake small batches of tokens at a time to avoid the locking period.
Rounding bugs are very unintuitive issues for me. The first one makes sense but required a specific blockchain to make viable. The second one I stared at the function for a while and played with some numbers until I understood it. It seems that with small numbers, the rounding is bad!
I had two big takeaways. First, if the supported chain for the protocol is on a cheap fee blockchain, then the small rounding errors become a bigger deal. Second, is a heuristic for checking these in my head. Initially, checking if the rounding direction if good or bad for the protocol. If it's bad, then review the impact then play with the numbers some to understand the impact. Good review!