B008: A Map to the Dark Forest
One of the many reasons Ethereum has come to be as successful as it is today is that it seamlessly allows smart contracts within its ecosystem to interact between them, enforcing a set of abstractions that allow a strongly typed system to invoke arbitrary functions and thus contracts.
While powerful, this feature exposes contracts to a whole slew of new potential attack vectors and vulnerabilities, such as incorrect encoding of data, re-entrancy attacks, and in general misbehavior that can arise from an external contract that is completely unrelated to the original contract’s code.
In this article, I will investigate some common contracts that are being interfaced to and how to properly handle their integration as well as outputs to ensure that they have been interfaced to correctly. Vulnerabilities in those integrations usually arise from improper implementation rather than be entirely the fault of the offending external contract.
Uniswap V2: Liquidity Provision
With the increase in forks, we have seen multiple projects utilize Uniswap as a means to introduce novel liquidity-based tokens and systems that attempt to incentivize or disincentivize users in the liquid market dynamics. A prime example of this are SafeMoon forks that provide a way for their smart contract to re-circulate accumulated fees as liquidity provided within the Uniswap pool.
Uniswap is what is known as a constant produce market, balancing the price ratio of either token within the pair based on a basic set of mathematical principles. In turn, this means that the reserves within the pair will rarely be in equilibrium.
Given that transactions on a blockchain aren’t immediately executed, the liquidity provision mechanisms of Uniswap accept the “upper bound” of the values that the user desires to supply as liquidity.
The SafeMoon implementation simply swaps half the token amount to ETH and attempts to supply liquidity using the resulting values which will almost always result in a remainder. This remainder always remains in the contract and currently, a total of roughly 3,420 in BNB (Around 1$ million) will remain locked forever.
This could have been avoided by conforming to the Uniswap V2 documentation and adding proper handling of the return values of the
addLiquidityETH function, the same applying to its sister function
Furthermore, in the current implementation slippage is set as “unavoidable”. This is untrue given that either a TWAP-based solution can be utilized for the price or an external price oracle, topics which we will investigate in another article.
Chainlink: Latest Price Retrieval
One other relatively unknown integration instruction that is supplied slightly obscurely in the Chainlink documentation is validating a reported price’s stale-ness, which may be unsuitable depending on the type of smart contract application one is building.
The usage of Chainlink has skyrocketed in the past months given that it is ultimately a free-to-use and flash-loan-resistant (not flash-loan-immune) solution to acquiring the “true” price of an asset. As a result, it has seen widespread adoption and can be seen interfaced to by multiple DeFi systems.
The most commonly utilized function, in particular, is the
latestRoundData function documented here. It enables the retrieval of the latest reported price within the Chainlink network. A caveat of this is that it represents the latest “reported” price, not necessarily the most up-to-date one. While not explicitly defined in the documentation of the function itself, another section of the Chainlink documentation does warn against price staleness.
As shown above, even the example implementation by Chainlink does not validate against data staleness. In short, one needs to simply check the round deviation between
roundID (representing the current round) and
answeredInRound (representing the round the price was reported) ensuring that it is within an acceptable threshold.
If data freshness is of absolute importance, the threshold can also be set to
0 to ensure the reported price was just submitted. This particular validation is important as other, smart-contract unrelated incidents can affect price staleness such as the Chainlink network being under DDoS, containing a malfunction, or even being affected by force majeure.
The Ethereum network has been oft parallelized to a dark forest; if one is careful enough it is, however, relatively easy to traverse. The code execution paths a smart contract’s code accepts as “sane” are entirely defined by it regardless of the external interactions it performs.
With this in mind, smart contract developers should aim to build self-sufficient zero-assumption systems rather than rely on the “good behavior” of external contracts for their own system’s security.
If there are any integrations in particular that you would like to see explained, kindly let me know as it provides me with material to continue my daily articles!