Moremoney is a decentralized borrowing protocol that lets you take on Interest Free loans using both liquid and illiquid tokens as collateral, while still earning farm reward and/or interest on your collateral. Loans are issued out in the protocol's dollar pegged stablecoin. Base tokens like USDT, ETH, AVAX as well as LPT and other form of ibTKNs are supported as collateral.
Install dependencies:
yarn install
Place a private key file in your home folder ~/.moremoney-secret
. If you want it to match up with your wallet like MetaMask, create the account in your wallet, copy the private key and paste it into the file.
- Get a snowtrace / etherscan API key and put it in
.etherscan-keys.json
(in project root folder), formatted like this{ "avalanche": "YOURSECRETKEYGOESHERE" }
- copy a current addresses.json file into
build/addresses.json
- Run
yarn deploy avalanche
(In order to replace an already deployed contract add it like thisyarn deploy avalanche FooContract
- The deploy script will export a new
addresses.json
to the frontend repo in the same parent folder
If you want a completely fresh deploy, without relying on the forked existing protocol, run:
yarn dev
To run a local fork of the protocol, as deployed on avalanche, run:
yarn start
To deploy any new contracts to that fork as well as additional tokens and strategy activations, run the following in a separate shell session:
yarn local-deploy
If you want to deploy any changes you made to existing contracts, you have to name those contracts, like so:
yarn local-deploy DirectFlashLiquidation WrapNativeIsolatedLending
This is alpha software, demonstrating functionality and proficiency, which has not yet been reviewed and tested rigorously.
- User Deposits collateral and Borrows the protocol's stablecoin
- Collateral is forwarded to partner protocols. (E.g. YieldYak, TraderJoe or Pangolin) to earn interest and/or farm reward
- Reward is compounded or used to pay back the loan
Every supported asset has a yield generation strategy assigned to it that features:
Compounding
: Yield earned by underlying collateral is compounded thereby increasing vault cRatio and borrowing powerAutorepayment
: Yield earned by underlying collateral is converted to USDm and used to wipe out users debt position
Both options reduce the likelihood of a vault falling below the liquidation threshold.
Stablecoin
: The mintable stablecoin (also provides ERC3156 flash loans)IsolatedLendingTranche
andTranche
: The user-facing contract ERC721 contract representing a tranche of tokens against which a user can borrow.Strategy
: The abstract parent class of all the strategies in thestrategies
subfolder, to which theTranche
contracts forward their assets. There are auto-repaying strategies (YieldConversionStrategy
and its descendants) and compounding strategies (e.g.YieldYakStrategy
), as well asSimpleHoldingStrategy
which does not generate yield.oracles/*
: A selection of fine oracles. Liquidity pools are tracked jointly with general TWAP oracles. There are means to proxy from one ground-truth oracle to derived oracles (e.g. via TWAP).Roles
: Contracts declare which roles they play and which other roles they depend on.DependencyController
keeps track of all this andExecutor
/controller-actions
are ways to effect changes to the roles system. Theroles
subfolder shoehorns the solidity type system into providing some typechecking support of role dependencies.Vault
: Currently not deployed, part of future protocol plans for cross-asset lending
Note: Since these contracts are not targeted at ETH mainnet at this time, less heed was given to gas optimization.
In order to reduce the attack surface, ownership and control of parameters and other roles is concentrated and managed in one contract: Roles.sol
(with significant assistance by DependencyController.sol
). Roles.sol
is kept very simple on purpose and has been previously used successfully as basis for the Marginswap protocol. The currentExecutor
variable in DependencyController
is set and unset only once in the code base.
Ownership will be held in a multi-signature wallet for a brief beta period, during which limiting parameters will be strict and risks are discouraged, while the functional viability of the system is tested, with the ability to quickly react. After this the community will add a timelock and transfer ownership to Compound-style governance.
Contracts within the deployed system are intrinsically immutable and loosely coupled, i.e. individual parts of the system may be replaced and new contracts with similar roles/powers as existing contracts can be added. This is useful for centrally and consistently managing minting/burning privileges, responding to changing liquidation and harvesting needs etc. In order to keep gas costs manageable, roles assignments are cached locally per contract (cf. DependentContract.sol
and RoleAware.sol
) and updated by DependencyController.sol
, since contracts can broadcast their dependence on being notified of role updates for any specific role. Thereby finding a balance between a system responsive to changing needs and contract immutability.
'Roles' fall into two categories:
- Main characters: One unique contract performing a unique role within the entire system. (E.g. strategy registry or the stablecoin) Used for service discovery.
- Roles: Multiple contracts can be marked as holding a role. Used for access control checking (E.g. minter / burner, fund transferer on behalf of users)
Tranche contracts (of which IsolatedLending
is the only deployed instance) forward collateral assets to other contracts, strategies, which in turn have the logic for tracking and interacting with other yield-bearing systems.
Stages of auto-repayment:
- Reward tokens for a specific asset have been transfered to the strategy, either because the user triggered a deposit or withdraw, or because they called
harvestPartially
to induce the yield generating contract to distribute reward. tallyReward(address token)
assigns that excess reward balance to an underlying asset within the contract's internal accounting.convertReward2Stable
can be called by anyone in order to convert accumulated reward tokens into our stablecoin, at their current USD price.tallyHarvestBalance(address token)
finally makes this harvested stable accrue to accounts as loan repayment
The AMMYieldConverter
contract offers a way to do this entire process in one transaction, including unwinding reward tokens on AMMs.
- Every time yield gets converted from reward token to our stable, and the harvest balance is tallied, we create a yield checkpoint, reflecting how much yield has accrued up to that point.
- A configurable percentage of yield gets diverted to protocol funds upon conversion.
- Accounts keep track of a yield checkpoint index, indicating when in this list of checkpoints they started earning.
- The time period between checkpoints is a yield phase.
- Newly created accounts start accruing in the next phase after the next checkpoint is created.
- Exiting accounts are removed from the current yield phase collateral total.
- Total proceeds from a yield phase are divided by the collateral total for the current phase and stored in the checkpoint.
- Yield for an individual user is
collateral * (cumulativeYieldAtCurrentCheckpt - cumulativeYieldAtInitialCheckpt)
(cf.Strategy._viewYield(account, tokenMeta, currency)
for the formula in action).
MasterChef
-style contracts often transfer reward upon every interaction, sotallyReward
is called at each withdraw and deposit.- The contract houses a variety of assets lodged in one
MasterChef
contract under one roof, checking thepid
for each token upon token initialization.
- Maintains a mapping from staking token to staking contract, checking integrity of mapping at initializtion.
- Reward contracts offer a direct way to harvest reward.
- We use YieldYak for auto-compounding yield
- Deposited shares are stored along with deposited collateral amount
- At account update time, we calculate a new collateral amount taking the amount corresponding to deposited shares and subtracting a fee percentage from the delta compared to the deposited amount(cf
YieldYakStrategy._applyCompounding
) - Thereby fees on yield are assessed by discounting the compounding
An attacker can potentially manipulate the deposit tokens per share ratio in the YieldYak strategy, within one transaction, by donating tokens to it. If that attacker were able to gain more from our protocol than it costs effect this manipulation, they would have a valid attack vector. We take the following measures:
- Deposit limits are set per-strategy, per-asset.
- The value adjustment by share price is local to the YieldYak strategy and does not infect price oracles in the rest of our protocol.
- Users cannot take a leveraged collateral position in this strategy while the yield generating strategy is sound (and tokens cannot be drained from it) and the strategy guarded against reentrancy a.
- If the yield generating YieldYak strategy were compromised, prudent deposit limits can guard against runaway position-building.
- Absent extreme upstream vulnerabilities, any action to donate funds to a compounding strategy simply results in additional yield to be distributed.
The strategies system offers a gradiated range of responses to strategy or token failure and/or deprecation.
- To deprecate a specific token in a specific strategy while leaving existing accounts intact:
setDepositLimit
to zero - To deprecate an entire strategy: Repeat
setDepositLimit
for all the tokens in that strategy - To deactivate and replace a strategy immediately: execute
migrateAllTo(address destination)
on the strategy and all tranches for all assets managed by the strategy will see their assets withdrawn and housed temporarily until users interact with them again. - To rescue stranded funds:
rescueCollateral
,rescueStrandedTokens
andrescueNative
.
With one call to migrateStrategy
users can send their own assets in their tranche to a different yield-bearing strategy which supports that asset.
Liquidation occurs in IsolatedLendingLiquidation.sol
.
- Would-be liquidators bid a rebalancing amount in stablecoin with which to repay some debt of the liquidatable tranche.
- Liquidators can request a collateral amount by which they may be compensated, the value of which may not exceed their liquidation bid, plus a per-asset liquidation fee.
- After depositing the rebalancing amount of stablecoin and withdrawing the requested collateral to the liquidator, the tranche must be above the minimum collateralization threshold.
- The protocol additionally asseses a fee, as a portion of the requested collateral value, which can be set per-asset.
NOTE: In case a tranche goes underwater we reserve liquidation for governance and whitelisted addresses, in order to guard against oracle vulnerabilities.
We will also provide unprivileged convenience contracts to organize complete unwinding of positions using AMMs and stablecoin flash loans.
The protocol provides a central point for governance to register oracles in OracleRegistry
. Oracles generally provide a way to convert amounts in one token into another and are specifically used to convert amounts in a variety of accepted collateral tokens into amounts in our USD-pegged stablecoin (often represented by converting into USD or other USD-pegged tokens).
Oracle calls are offered as view functions or state-updating.
Chainlink provides a strong stable of USD price feeds on a variety of networks. The main issues to bear in mind are oracle freshness and correct decimal conversion. In order to guard against stale chainlink pricefeeds, our oracles also maintain a fallback TWAP oracle, using another reputable stablecoin as stand-in for USD price.
UniswapV2-style AMM pairs maintain a time-weighted cumulative price, which our protocol relies on by choosing suitable AMM pairs. Important issues here are available liquidity, which is held in a dispersed way or locked up (to guard against price manipulations and sudden withdrawals), as well as overflows & properly scaled fixed point math, as the cumulative price numbers are returned as 2 ** 112
fixed point numbers. The oracle maintains ongoing state on the pair, which can be re-used for LPT oracles.
The scaled oracle converts from one token to the other by multiplication by a constant factor. Useful for converting trusted pegged stables.
The proxy oracle converts from token A to token B via token C, by looking up oracles between A and C, then B and C and chaining their token amount conversions.
UniswapV2-style AMM pairs mint LP tokens which our protocol accepts as collateral. Oracles for this asset class have widely been implemented according to the "fair LPT oracle" method, which does leave room for some potential vulnerabilities.
According to the amount of burn
function in UniswapV2Pair
, one unit of LPT liquidity
corresponds to a proportional share of both underlying asset reserves:
amount0 = liquidity.mul(balance0) / _totalSupply; // using balances ensures pro-rata distribution
amount1 = liquidity.mul(balance1) / _totalSupply; // using balances ensures pro-rata distribution
The algorithm proposed by Alpha Homora goes as follows:
This oracle can potentially make a lending protocol vulnerable:
- An attacker might manipulate the reserves by dumping in more balance and triggering an update inflating
k = r0 * r1
, while leaving thetotalSupply
the same. - If an attacker were able to a acquire a (potentially leveraged) position with gains from the price manipulation in excess of the cost of manipulating the price, they could extract value from the protocol.
Acquiring such an extreme position in a stablecoin lending protocol against an ostensibly illiquid asset may not be as readily possible as in a p2p lending system, nevertheless caution is indicated.
The measures we take to combat attacks are as follows:
- We do not use the current values for
k
andtotalSupply
within a block in which they are updated and we space updates by a reasonable interval, such as 5 minutes. - Deposits are capped to a fraction of the total supply (again not the current-block value)
- In some instances we can smooth updates to these core parameters and / or only let updates be performed by whitelisted addresses (to be implemented as necessary)
- We add an additional time-weighted component to our price calculations (see below)
These measures also apply to other synthetic assets where prices or conversion factors are subject to within-block changes.
In order to better re-use our existing oracle infrastructure, we adapt the LPT oracle formula in the following way:
- Instead of computing the oracle price solely on the basis of the most recent
k
value, we impute reserves based onk
in conjunction with the cumulative price (seecontracts/oracles/TwapOracle.sol
, functionprice0FP2Reserves
for the conversion formula derivation). - We then compute the price for total supply and
k
of last update, in a straightforward way based on the sum of USD values of both reserves.