Giter Site home page Giter Site logo

usmfum / usm Goto Github PK

View Code? Open in Web Editor NEW
133.0 13.0 24.0 1.88 MB

Minimalist USD - A minimalist, collateralized stablecoin built on Ethereum.

License: GNU General Public License v3.0

JavaScript 35.81% Solidity 63.86% Shell 0.33%
ethereum chainlink uniswap stablecoin defi erc20

usm's People

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

usm's Issues

Combine mintBurnAdjustment and fundDefundAdjustment into a single buySellAdjustment

This is an idea I've had in the back of my mind, to fix some unpleasant cases where the system charges both buyers and sellers an excessive fee. I dove in this week and looks like it's working - it's a significant change (different formulas in usmFromMint() and co), but a worthwhile one I believe. Doesn't change the public API at all. I'm putting some time into revamping the tests before I submit a PR - aiming for sometime tomorrow. Then I can get back to oracle work!

Off-chain signatures in UI

For better UX, the UI should make use of off-chain signatures and use them to interact with ERC20Permit.

Constants in USM.sol or IUSM.sol?

This is total nitpicking but just for my understanding - we have the enum Side {Buy, Sell} in IUSM, but the constants like WAD and MAX_DEBT_RATIO, and the struct TimedValue definition, in USM. Any rhyme or reason to that or does it just not matter much...

Fund/Defund using USM

Implement in Proxy by calling:

function fundWithUSM(...) {
    proxy.burn(...);
    proxy.fund(...);
}

function defundToUSM(...) {
    proxy.defund(...);
    proxy.mint(...);
}

Note that we are calling the Proxy functions, meaning that limit orders are included.

Does IUSM need usmTotalSupply()?

USMView needs to call usm.totalSupply(), but its usm is of type IUSM, and I couldn't figure out how make IUSM include a totalSupply() fn; because then USM would be inheriting it from both IUSM and ERC20Permit, and I couldn't get that to work. So I gave the IUSM interface a usmTotalSupply() fn, where USM.usmTotalSupply() just returns totalSupply().

This works fine! But the redundant usmTotalSupply() is ugly. Ideally USMView would just call usm.totalSupply().

Maybe we could make IUSM inherit from IERC20, so that IUSM users like USMView can count on it implementing totalSupply()?

Otherwise this may just be one of those "It ain't beautiful but it's fine stop worrying about it" things.

fund limit buy orders

fund limit buy orders (aka: specify a max FUM price when doing a fund op, so that the ETH/USM is stored for later execution if the current FUM price is too high)

Is this project active?

Can USM be deployed on ETH compatible chains? Is the project team open to working on commercial collaboration?

fumPrice shoots to an impractical high price if the fum total supply is very low

In the current fumPrice function,

price = (buffer <= 0 ? 0 : uint(buffer).wadDiv(fumSupply, roundUp));

if the fumSupply is very low and buffer is very high the price returned is so high that it can be considered impractical. Moreover, at such high price both fund and defund functions throw error when calculating fumOut or ethOut in fund/defund respectively.

fumOut = ethIn.wadDivDown(fumBuyPrice0);

Change OurUniswapV2TWAPOracle.latestPrice() to return cached price, not freshly calculated one

This will help incentivize anyone using the TWAP to call refreshPrice() (rather than latestPrice()) in order to get a fresh price, which is beneficial for the other users of the TWAP (like us!). Even better for us would be to remove latestPrice() from the TWAP oracle entirely, but having no view function to get a price seems a little harsh. I think "You can call our view function (latestPrice()) rather than our transactional fn (refreshPrice()), but it won't give you as fresh a price" is a reasonable compromise.

(This is based on a suggestion from @albertocuestacanada last month. I already incorporated it into USM/USMView, but I realized tonight I didn't incorporate it into OurUniswapV2TWAPOracle.)

Note that this issue has no direct impact on USM, which only ever calls refreshPrice() anyway. But it does indirectly affect USM in that the more often other users of our oracle call refreshPrice(), the more up-to-date the numbers it gives us will be.

Think about how to choose constant params like BUY_SELL_ADJUSTMENTS_HALF_LIFE

  1. Perhaps also revisit MIN_FUM_BUY_PRICE_HALF_LIFE and MAX_DEBT_RATIO (and maybe more?), but especially BUY_SELL_ADJUSTMENTS_HALF_LIFE. Right now the latter is 1 minute: I've been pondering whether we should consider making it longer - maybe even much longer, like 30-60 minutes. Basic tradeoff:
  • A short (eg ≤ 1 min) half-life means that, after eg a long-ETH operation (burn/fund) pushes up the price (the "sliding price" effect), the price drops back towards the raw oracle price faster. This saves the next buyer fees, but means that when the oracle price is slow/off, traders get more chances to exploit it.
  • A long (eg ≥ 10 min) half-life means that the sliding price takes longer to gravitate back to the oracle price after trades push/pull it away. This may inconvenience traders (higher fees), but also protects the system from arbitrage during periods where the oracle price is out of whack.

    We probably want our v1 to prioritize safety of the system over user fees: anyway patient users can wait to do their ops during low-activity periods, during which the adjustments will be slight and price will be close to the raw oracle price. So, we should probably extend the half-life to eg 10 minutes, or something.
  1. An added wrinkle here is that we have two adjustments, one for mint/burn, another for fund/defund. So if, eg, there have been more mints than burns recently, that will push down the sell price used for mints and defunds; and if there have been more funds than defunds, that will push up the buy price used for burns and funds. If both of these have happened recently (more mints than burns and more funds than defunds), then fees will effectively be high for all operations.

    I did this, rather than have a single adjustment based on the total net of recent long-ETH vs short-ETH operations, because it's not trivial to net eg a short-ETH mint against a long-ETH fund. If 10 ETH has been passed to mint and 5 ETH to fund, that could add up to net long ETH in total or net short, since fund is effectively a leveraged buy - it depends on the current debt ratio, etc...

    Perhaps in the future we can save users fees by calculating this offset carefully and boiling it down to a single long-or-short adjustment, but for now I did the safe thing and kept the two adjustments separate: long-ETH ops (burn/fund) will be more expensive if either more burns than mints have been done recently, or more funds than defunds.

Prevent users from losing funds by eg sending v2 USM to the v1 contract

As discussed on Telegram - this is a predictable (serious) future UX problem we should think through now, since the most natural fix is to make (eg) USMv1._transfer() reject sends if the recipient is USMv2, and making that work requires figuring out how USMv1 will be able to know USMv2's address once we release the latter.

Autoexecutor idea

Related to #61, as discussed on Telegram - a generic "autoexecutor" smart contract that you can call addTask(task) on, with task.isExecutable() and task.execute() callbacks, where each addTask() call also executes up to two previously-scheduled-and-currently-executable tasks. Might be workable might not

Implement a basic UI

Create a basic UI so users can interact with testnet USM using their preferred web3 provider.

Should we implement `buy` functions?

I'm noticing, though, that all the buy functions are missing from the system. We can use a precise amount of ETH to fund for an unknown amount of FUM. What if the user wants to mint exactly 1 FUM?

Same with all others. Would we just reverse the logic in the frontend? Code it in the smart contracts? Not implement it?

Simplify minFumBuyPrice - we don't need to store it

Working on the fees math made me realize we don't actually need to store the minFumBuyPrice when debt ratio > max (eg 80%): there's a clean way to infer it from other stored values. This has been bugging me for a long time - it's actually what I spent most of my first week on USM in July banging my head against!

In brief: the way it works now is, if the system's debt ratio goes above 80%, it records the FUM price in ETH terms as of the moment it crossed 80%, and charges at least that much (in ETH terms) until debt ratio goes back below 80%.

  • Example: pool contains 50 ETH @ $1,200 = $60,000, 40,000 USM outstanding, debt ratio 66.67%, 2,000 FUM @ $10 (0.008333 ETH)
  • Price drops to $500. So now: 50 ETH @ $500 = $25,000, 40,000 USM outstanding, debt ratio 160%, 2,000 FUM @ theoretical -$7.50 (-0.015 ETH)
  • But the system stores the theoretical FUM price as of the point the 80% debt ratio was crossed: 0.005 ETH. So regardless of the theoretical low/negative FUM price, fund callers will always be charged ≥ 0.005 ETH per FUM, until debt ratio gets back below 80%.

The new way is, when debt ratio > 80%, instead of using the actual USM quantity in the FUM price calculation, we use a lower "effective USM" quantity equating to an 80% debt ratio. Basically, we pretend any USM that pushes us above 80% doesn't exist.

  • Revisiting the example above: once the price drops to $500, the theoretical FUM price is ($25,000 - 40,000) / 2,000 = -$7.50 (-0.015 ETH). But we pretend there's 80% * $25,000 = 20,000 USM outstanding, rather than 40,000. This gives a min FUM buy price of: ($25,000 - 20,000) / 2,000 = $2.50 (0.005 ETH).
  • So we get the same mfbp as in our previous storage-based approach - 0.005 ETH - without needing to store it.

I found this because the storage-based method doesn't work for the new fumFromFund() math - I needed to back it out this way instead. Then I realized then we don't need to store it at all.

I believe the new way always gives the same number as before, except it avoids the ugliest part of the old way: the fact that in some cases the FUM buy price would discontinuously drop once debt ratio got back below 80%. (FUM price plunging after people buy FUM = bad incentives.) Now it instead declines smoothly as debt ratio approaches 80%, which seems strictly more correct to me.

Note we do still want to store a value related to mfbp, because of the separate feature (Elliot's) where we make mfbp decline over time. But now we can just store the time to start declining from, rather than a price value.

0-fee upgrade op

0-fee upgrade op, to save users money if/when we need to push a fresh contract

Consider removing MINT_FEE/BURN_FEE

Just scrapping all the MINT_FEE/BURN_FEE stuff if it makes life simpler (probably), because I think the const-product fees are likely to be all we use in the end.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.