The investment app where friends invest and build strategies for each other.
Platform: Code4rena
Start Date: 05/05/2022
End Date: 18/05/2022
Period: 14 days
Status: Awarded
Pot Size: $125,000 USDT
Participants: 63
Id: 121
League: ETH
leastwood | 1/63 | $23,426.89 | 11 | 5 | 3 | 6 | 5 | 0 | 0 | 0 |
TrustTeldKeng | 2/63 | $17,792.90 | 8 | 3 | 3 | 3 | 3 | - | - | 0 |
0x1f8b | 3/63 | $12,034.67 | 7 | 2 | 2 | 3 | 2 | - | - | 0 |
berndartmueller | 4/63 | $9,224.02 | 6 | 1 | 1 | 4 | 3 | - | 0 | 0 |
WatchPug | 5/63 | $8,775.02 | 6 | 2 | 1 | 2 | 2 | - | - | 0 |
0x52 | 6/63 | $5,838.51 | 2 | 1 | 1 | 1 | 1 | 0 | 0 | 0 |
sorrynotsorry | 7/63 | $5,264.32 | 4 | 2 | 1 | 1 | 0 | - | 0 | 0 |
GimelSec | 8/63 | $4,688.85 | 7 | 0 | 0 | 5 | 3 | - | - | 0 |
IllIllI | 9/63 | $3,427.58 | 5 | 0 | 0 | 3 | 0 | - | - | 0 |
CertoraInc | 10/63 | $2,267.63 | 4 | 1 | 0 | 1 | 1 | - | - | 0 |
Auditor per page
All solidity files are included in scope, apart from contracts/test.
Contract | Dir | Loc | Purpose |
---|---|---|---|
PlatformProxyAdmin | contracts/ | 101 | ProxyAdmin managing upgradeability of StrategyController and StrategyProxyFactory. |
Strategy | ^^ | 869 | Comprised of positions along with their data, global thresholds and fees. Is StrategyToken. |
StrategyController | ^^ | 784 | Powerhouse contract giving main entrypoint for users to deposit into and withdraw against strategies, for managers to rebalance and restructure their strategies as well as other admin actions. |
StrategyControllerStorage | ^^ | 784 | Storage for StrategyController. |
StrategyProxyAdmin | ^^ | 70 | Enables managers of strategies to upgrade their strategy to the latest implementation held by StrategyProxyFactory. |
StrategyProxyFactory | ^^ | 297 | Entrypoint for users to create strategies. Exposes administrative functions to update base strategy implementation, add items and estimators to registry, and other admin actions. |
StrategyProxyFactoryStorage | ^^ | 18 | Storage for StrategyProxyFactory |
StrategyToken | ^^ | 322 | ERC20 token representing share of positions in Strategy. |
StrategyTokenStorage | ^^ | 40 | Storage for StrategyToken. |
Whitelist | ^^ | 23 | Maintains ledger of accounts approved for actions in the StrategyController and Strategy. Accounts referenced are typically routers or adapters. |
BaseAdapter | contracts/adapters/ | 21 | Abstract contract defining virtual swap function for decendants to override. |
AaveV2DebtAdapter | adapters/borrow/ | 66 | Borrows and repays assets from AaveV2. |
BalancerAdapter | /contracts/adapters/exchanges/ | 239 | Exchanges tokenIn for tokenOut across Balancer V1. |
CurveAdapter | ^^ | 239 | Exchanges tokenIn for tokenOut across a Curve pool. |
KyberSwapAdapter | ^^ | 62 | Exchanges tokenIn for tokenOut across KyberSwap. |
SynthetixAdapter | ^^ | 58 | Exchanges tokenIn for tokenOut across Synthetix. |
UniswapV2Adapter | ^^ | 79 | Exchanges tokenIn for tokenOut across a UniswapV2Pair. |
UniswapV3Adapter | ^^ | 54 | Exchanges tokenIn for tokenOut for a registered fee across UniswapV3. |
AaveV2Adapter | contracts/adapters/lending/ | 79 | Lends and recovers assets through AaveV2. |
CompoundAdapter | contracts/adapters/lending/ | 83 | Lends and recovers assets through Compound. |
CurveLPAdapter | contracts/adapters/lending/ | 156 | Deposits and withdraws into Curve liquidity pools for liquidity token. |
MetaStrategyAdapter | contracts/adapters/liquidity/ | 61 | Deposits and withdraws assets in exchange for stategy token. |
CurveGuageAdapter | contracts/adapters/vaults/ | 60 | Deposits and withdraws into CurveGuage vaults. |
YEarnV2Adapter | ^^ | 71 | Deposits and withdraws into YEarnV2 vaults. |
AddressUtils | contracts/helpers/ | 10 | .. |
GasCostProvider | ^^ | 17 | .. |
Multicall | ^^ | 32 | .. |
RevertDebug | ^^ | 26 | .. |
StringUtils | ^^ | 17 | .. |
StrategyControllerPaused | contracts/implementations/recovery | 294 | Emergency implementation of StrategyController to be put in place as a pausing mechanism. |
InterfacesFolder | contracts/interfaces/ | 1254 | |
Math | contracts/libraries/ | 74 | Math library exposing sqrt and nuanced arithmetic functions. |
SafeERC20 | ^^ | 82 | Fork of OpenZeppelin's SafeERC20 library, providing "safe" wrappers to standard ERC20 functions. |
StrategyLibrary | ^^ | 117 | Library exposing common calculations and validations such as getExpectedTokenValue , and checkBalance . |
UniswapV2Library | ^^ | 132 | Consolidating some common UniswapV2 library functions such as sortTokens , pairFor , getAmountIn , getAmountOut , etc. |
EnsoOracle | contracts/oracles | 90 | Core oracle estimating strategies' value as the sum of specific oracle estimates on its contituents. |
AaveV2DebtEstimator | contracts/oracles/estimators | 23 | Oracle estimating a debt token by estimating a balance of its underlying asset. |
AaveV2Estimator | contracts/oracles/estimators | 23 | Oracle estimating a debt token by estimating a balance of its underlying asset. |
BasicEstimator | ^^ | 27 | Oracle estimating a token using a set protocol oracle. |
CompoundEstimator | ^^ | 27 | Oracle estimating a debt token by estimating a balance of its underlying asset. |
CurveGaugeEstimator | ^^ | 23 | Oracle estimating a token by estimating a balance of its lp token. |
CurveLPEstimator | ^^ | 93 | Oracle estimating an lp token by estimating a balance of its pool tokens. |
EmergencyEstimator | ^^ | 31 | Oracle giving coarse estimate as a stop-gap when other estimators are unavailable. |
StrategyEstimator | ^^ | 29 | Oracle estimating strategy token by considering the estimates of the strategy's positions. |
YEarnV2Estimator | ^^ | 27 | Oracle estimating a vault by estimating share in its token. |
ChainlinkOracle | contracts/oracles/protocols/ | 27 | Oracle consulting the chainlink price feed. |
ProtocolOracle | ^^ | 36 | Base contract for protocol oracles to provide consultations on the estimate of a token. |
UniswapV3Oracle | ^^ | 54 | A protocol oracle consulting quotes from uniswap v3. |
ChainlinkRegistry | contracts/oracles/registries | 49 | Registry of tokens to be estimated by the ChainlinkOracle. |
CurveDepositZapRegistry | ^^ | 24 | .. |
TokenRegistry | ^^ | 42 | Registry of tokens with their item and estimator categories dictating how they'll be estimated. |
UniswapV3Registry | ^^ | 96 | Registry of tokens to be estimated by the UniswapV3Oracle. |
BatchDepositRouter | contracts/routers/ | 59 | Router enabling batching of deposit and withdraw into positions, but does not support rebalance or restructure . |
FullRouter | ^^ | 757 | Router extending functionality of LoopRouter to include routing through debt items. |
LoopRouter | ^^ | 226 | Router implementing deposit , withdraw , rebalance , and restructure of strategy items. |
MulticallRouter | ^^ | 125 | Generic Router enabling powerful custom routing into positions. |
StrategyRouter | ^^ | 182 | Base router defining _buyPath and _sellPath as well as virtual functions for router decendants for routing into asset positions. |
Social trading application whereby anyone
can create a strategy, and can have others invest into this strategy. The creator manages the strategy, and if social
is enabled then others can invest into this strategy. If restructure
enabled then manager can change the structure:
timelock
when the creator wants to change the structure they propose the structure, and then will need to wait x
hours timelock
until they can execute on the structure - this is to prevent against deploying own token then restructuring and misusing investor funds.
Difference choices of exchanges:
Users can nest multiple calls together, e.g.
Users of these contracts can be divided into three groups: owner, managers, and users.
The contract owner has the ability to upgrade the StrategyController and StrategyProxyFactory. The owner also has the ability register the tokens in TokenRegistry, UniswapV3Registry, ChainlinkRegistry, and CurveDepositZapRegistry. These registries are used the Oracles and Adapters to estimate values or facilitate swaps. Finally, the owner is able to update the contract addresses that are stored on the StrategyProxyFactory such as oracle, whitelist, or strategy implementation.
A manager controls many of the core functions for a strategy. They are able to rebalance or restructure a strategy. They can update values like the rebalance threshold, rebalance/restructure slippage, and timelock. However, restructuring or updating values requires them to wait out the timelock period before they can finalize their changes, this is to give users time to exit if they are dissatisfied with proposed changes.
While the owner of the contracts is able to deploy a new Strategy implementation on the platform, only the manager is able to upgrade their strategy to the new version.
A user may invest in a strategy. They have the ability to deposit into a strategy or withdraw (either by trading their tokens for ETH/WETH or withdrawing the tokens directly) from a strategy. Since all strategies are ERC-20 compatible, the user is free to transfer their strategy tokens to whomever they like.
The Strategy contract is an ERC-20 token that stores other ERC-20 tokens and holds in its state any data related to the strategy composition. It has several functions that can only be called by the StrategyController, such as approving tokens held by the Strategy to be used by other contracts.
This contract has special privileges over the Strategy contracts. Most functions are only available to the manager of the strategy that is being called. With the StrategyController, a manager may rebalance or restructure a strategy by trading tokens via a Router contract. A Router is given temporary approval over a strategy's tokens. At the end of a transaction, the EnsoOracle is consulted to ensure there is no value loss to the strategy.
The routers are used to handle all trading logic for strategies. They are given approval over the strategy tokens and can deposit, withdraw, and swap tokens in order to achieve the expected outcome of the function being called. Routers will use the current estimated values of a strategy's tokens and compare it to a strategy's expected values based on each token's percentage stored in the Strategy contract. When swapping tokens, the router does a delegate call to the swap
function of an adapter for the particular exchange or protocol. The router relies on tradeData
that is stored in the Strategy contract to determine which adapters to use for swapping. Some trades can use multiple adapters and tokens to get into the correct position.
Adapters are used to give a common interface to all exchanges or protocols that are supported by Enso. They implement the swap
function which gets called by a router. The swap
function only supports exchanging one token for another token.
The oracle is used to estimate the value of a strategy. It does this by querying the strategy for the tokens that it holds and estimating each token balance in ETH. It relies on the TokenRegistry to determine which protocol a token belongs to and using a protocol-specific estimator to determine the token value. For basic tokens that are not part of a DeFi protocol, we rely on our UniswapV3Oracle or ChainlinkOracle to determine the token value.
The oracle guards against slippage and strategy value manipulation. As such, it needs to be secure against attacks that manipulate the estimated price of tokens. A major area of concern is the use of Uniswap V3 as the oracle for many of the tokens. Is there any possibility of manipulating the price via a sandwich attack using flash loans? Our main focus has been protecting against MEV and within-block price manipulation, but what about manipulation across multiple blocks? There are many markets that may not be as efficient as we like and a price could be manipulated over several blocks, or several minutes or hours. How much liquidity does a market needs in order to make such attacks untenable? What is the optimal time period the TWAP should use to determine the average price?
The FullRouter provides much of the trading logic for swapping into and out of basic tokens, Synthetix tokens, and Aave V2 debt tokens. However, it does rely on data provided by the manager during strategy creation. In particular, in order for tokens to be put into a leveraged position, the manager must set which tokens are leveraged inside the debt token's tradeData.cache
which holds bytes
data that gets interpreted inside the FullRouter. This bytes
data contains a tuple array where each tuple holds the leveraged item's token address and the percentage of the expected value that is bought with debt. The nature of these debt arrangements can be very complex and it is worth looking at where the FullRouter might fail to trade into the proper positions. Furthermore, if a manager has misconfigured the tradeData
is there a scenario where a user who attempts to deposit into such a strategy risks losing funds or does such a deposit simply revert.
The MulticallRouter allows users to do all the normal operations (deposit, withdraw, rebalance, restructure), but by passing an array of arbitrary calls. This allows a user to use strategy funds as they please, as long as the transaction doesn't cause the strategy to get imbalanced or lose value. However, since it is a shared contract, there could be additional risks to using this contract. For example, giving an ERC20 approval to this contract would allow anyone calling this contract to transfer funds from you. While we account for this risk with how the StrategyController interacts with the router, there may be additional risks that we are unaware of.
Adapters are the main point of interaction between Enso strategies and the wider DeFI ecosystem. Incorrect implementation of an adapter could potentially lead to loss of user or strategy funds. Our routers are designed to call the adapters via a delegate call. This can make reasoning around adapters difficult as all calls within a swap
function are being made by the router. Of concern is if an adapter does not utilize the entire amount
that is passed in it's parameters. In such a scenario, left over funds could be trapped in the router.
yarn install
yarn build
should update .env_example for tests to run from mainnet fork before testing
yarn test