Eip 8: Contract Bugfixes

This upgrade fixes some relatively minor but important bugs that have been uncovered during operation of the Euler protocol:

NOTE: The DToken and Markets diffs above contain changes to the shared BaseLogic library since these module hasn’t been updated since. None of the changed code is used, except for an optimisation tounpackTrailingParams.

Accounts can get themselves into borrow-isolation

Normally there is no need to perform a liquidity check for an account after performing a deposit, since this operation cannot decrease a user’s health score. However, with self-collateralisation a deposit could put an account into isolation violation in the case where an account has two (cross-tier) borrows and then does a deposit for one of these assets. This will result in a self-collateralised loan, which should be isolated, but isn’t because of the additional existing liability.

The consequence for a user who does this is that their account will experience borrow-isolation violation errors until they withdraw the deposited amount or repay one (or both) of the loans. Fortunately, this doesn’t pose a risk to the protocol, since the self-collateralised isolation is enforced to provide some bounds on liquidations and to simplify the computation of max mining multipliers etc, and not for protocol security. However, getting your account into borrow-isolation violation is a sub-optimal user experience that we’d like to prevent.

The fix implemented in this eIP is to also check liquidity whenever a user’s balance is increased. To minimise the gas cost, this is skipped when the user has no borrows on the asset. Since the borrowed amount is stored in the same storage slot as the balance, this only adds about 200 gas more to deposits and swaps.

Uniform decimals and logs in DTokens

Our original design for DTokens was to expose balances normalised to 18 decimal places, similar to ETokens. So, an underlying that has fewer decimals would have its balances “padded” out to 18 decimals with extra 0s if necessary. This is how amounts are stored internally in Euler, which simplifies many internal calculations since decimal conversions only need to happen at the periphery.

During development it was decided that the external interfaces such as balanceOf, totalSupply, transfer, etc would work with values that use the underlying’s decimals. This is possible and natural because DTokens values are in the exact same units as underlying, and require no exchange-rate conversion, unlike ETokens. Unfortunately, due to an oversight, the decimals interface and the logs emitted by DTokens were preserved at the 18-decimal place normalised values. We take full responsibility for this, and sincerely apologise.

The consequence for this is that to properly interpret the balances of DTokens, you must use the underlying’s value for decimals, not the value returned from the decimals() method (which is always 18). However, the logs are correct when interpreted with decimals of 18 (but not the underlying’s decimals).

This issue escaped our notice because we don’t use the Transfer/Approval events on DTokens (or ETokens) for any of our own processing. It is much more convenient to use the corresponding logs emitted by the core Euler contract. The DToken/EToken logs are only implemented for ERC-20 conformance. However, they are useful as standardised format to be displayed in transaction inspectors such as etherscan.

This eIP addresses this by making all externally visible values conform to the decimals of the underlying asset. This includes the approveDebt() function which now accepts values in the underlying decimals (not normalised). Technically this is a breaking change, but we believe it will have little to no user impact.

Unfortunately, it does mean that historical logs for assets with non-18 decimal place tokens such as dUSDC and dWBTC will be inaccurate if interpreted with the new value returned by decimals(). This is unfortunate, but will have no impact on the contract functionality or any account histories viewed via Euler interfaces. It may result in inaccurate historical data on etherscan and/or other interfaces that solely use the DToken logs.

underlyingAsset accessors

An underlyingAsset() accessor method that retrieves the underlying asset’s address has been added to the EToken and DToken modules. This is a convenience feature so that you don’t need to use the Markets module to retrieve this value.

Natspec comment updates

Some of the natspec comments in the DToken module weren’t as clear as they should be, so they have been improved.

Prevent creation of nested PTokens

UPDATE: This change description was posted after this eIP was deployed because it is security-sensitive (called eIP 8.1).

If a PToken is created of a PToken (ie ppWETH), then this asset will cause a failure in RiskManager when a price was queried for this asset. If a user entered into this market, then liquidations would begin to fail for this user, potentially allowing them to accrue bad debt on the pool.

This eIP upgrades the Markets module to prevent creation of “nested” PTokens, and we have verified that no nested PTokens have been created on mainnet.

This bug was reported via Immunefi, and we are paying the reporter a bounty with severity level of High.