Coder Social home page Coder Social logo

sirenmarkets / core Goto Github PK

View Code? Open in Web Editor NEW
42.0 6.0 12.0 7.35 MB

SIREN Core Smart Contracts

Home Page: https://sirenmarkets.com/

License: GNU General Public License v3.0

Solidity 24.51% JavaScript 0.65% Shell 0.05% TypeScript 74.79%
ethereum smart-contracts dapp defi options blockchain derivatives

core's Introduction

SIREN logo

Siren Markets Core Smart Contracts

This repository contains the source code for the Siren Markets core smart contracts.

SIREN CI Coverage Status GitHub contributors GitHub commit activity GitHub Stars GitHub repo size GitHub

Website sirenmarkets.com Blog Docs Governance Twitter SirenProtocol

GitHub pull requests by-label GitHub Issues

Mainnet Contract List

Build and test

$ npm install
$ npm test

Design Goals

  • Build a fully unit tested system with 100% code coverage, including error scenarios and event logging
  • Allow the Siren system to be upgradeable over time by the governance system to include new functionality not included in the initial launch, without requiring migration to a new token (e.g. Augur)
  • Minimize gas by deploying proxy contracts whenever possible instead of full logic contracts
  • Utilize Open Zeppelin contracts whenever possible instead of rolling our own version
  • Fully comment the codebase so new developers can quickly grok the protocol and contribute

Protocol Overview

See the technical documentation for more details on the protocol

Series Lifecycle Example

Below is one of the unit tests showing the flow for, minting options, exercising an option, and claiming the remaining series' collateral.

it("Allows claiming after expiration with full redemptions", async () => {
  // Amount we will be minting
  const MINT_AMOUNT = 100

  // Give Alice 100 tokens
  await collateralToken.mint(aliceAccount, MINT_AMOUNT)

  // Save off the tokens
  const bTokenIndex = await deployedSeriesController.bTokenIndex(seriesId)

  // approve the amount and mint alice some options - wBTC collateral will be locked into series contract
  await collateralToken.approve(deployedSeriesController.address, MINT_AMOUNT, {
    from: aliceAccount,
  })
  await deployedSeriesController.mintOptions(seriesId, MINT_AMOUNT, {
    from: aliceAccount,
  })

  // Send the bTokens from alice to Bob - simulates alice selling option
  await deployedERC1155Controller.safeTransferFrom(
    aliceAccount,
    bobAccount,
    bTokenIndex,
    MINT_AMOUNT,
    "0x0",
    { from: aliceAccount },
  )

  // Move the block time into the future so the contract is expired
  await time.increaseTo(expiration + ONE_DAY)

  // Bob exercises
  await deployedERC1155Controller.setApprovalForAll(
    deployedSeriesController.address,
    true,
    { from: bobAccount },
  )
  await deployedSeriesController.exerciseOption(seriesId, MINT_AMOUNT, true, {
    from: bobAccount,
  })

  // Should succeed from Alice claiming leftover collateral
  await deployedERC1155Controller.setApprovalForAll(
    deployedSeriesController.address,
    true,
    { from: aliceAccount },
  )
  await deployedSeriesController.claimCollateral(seriesId, MINT_AMOUNT, {
    from: aliceAccount,
  })

  // Bob should own his share of collateral tokens
  assertBNEq(
    await collateralToken.balanceOf(bobAccount),
    "17",
    "bob should have his collateral",
  )

  // Alice should own her share of collateral tokens
  assertBNEq(
    await collateralToken.balanceOf(aliceAccount),
    "83",
    "alice should have her collateral",
  )
})

Development

This repo will generate TS clients for the contracts on install. When updating the contracts, the TS definitions can be manually updated by:

  1. Running npm run compile
  2. Running npm run build

The compiled JSON ABI files should be commited after deployment so that the deployment metadata is available in the repo.

core's People

Contributors

dependabot[bot] avatar lukaskiss222 avatar mysticdakra avatar pooleja avatar seafi avatar siren-tamer avatar theros-master-of-waves avatar zareth-san avatar

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

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

core's Issues

Add `transaction` field to AmmTokenEvent

We want to see the recent transactions that interacted with the AMM, and an AmmTokenEvent entity exists in the subgraph for every transaction on a particular AMM. Currently it's missing a transaction field, so we need to add that to the entity

add onlyOwner function to MarketsRegistry for pruning the markets and marketsByAssets arrays

Background
There are several functions in the Siren contracts which loops through the MarketsRegistry.markets and MarketsRegistry.marketsByAssets mapping and array. The logic assumes that no individual contract calls in a given loop will fail, but this might occur for unforeseen reasons. To manually prevent getting into this terminal endstate, we should add onlyOwner functions on the MarketsRegistry which will remove Markets from these collections, and in the array's case it will move all subsequence Markets one position backward in the array to retain the invariant that all marketsByAssets' elements are contiguous.

Goal
Implement these functions, and add tests for the following scenarios:

  • remove 1 element and make sure getTotalPoolValue can still be called
  • remove all elements and make sure getTotalPoolValue can still be called
  • remove 1 element from front, back, and middle and ensure all elements remain contiguous, and in the front test case ensure the 0th index contains a Market
  • try to remove by non-admin user and fail

deploy staking contracts to mainnet

Once we've deployed the staking contracts and tested them in rinkeby, we need to deploy them to mainnet and transfer ownership to the multisig contract

add tests for the all staking contracts

depends on #19

Some tests we need to add:

  • single round LPP with single staker
  • multiple round LPP with single staker
  • multiple round LPP with multiple stakers in each round
  • 12 month vesting period with multiple stakers withdrawing throughout the 12 months
  • validate rounding errors do not block users from withdrawing their rewards from the VestingVault. Try to withdraw within the last day of the vesting schedule
  • validate that existing rewards from a previous round do not interfere with calculations for rewards in the current round
  • validate that an LP's rewards can only be sent to themselves
  • validate that there's no way to get SI locked into the rewards distribution contract or the staking contract

Track bToken cost basis in subgraph

In order to be able to show PnL for each trade we need to track the average cost of bTokens position. One way of doing it is adding costBasis field to ERC1155AccountBalance entity. This entity tracks bToken and wToken balances for each account/seriesId combination.

In order for this to work properly we need to add the following logic when handling events:

  1. BTokensBought handler. Here we need to find ERC1155AccountBalance record based on id in the format '--'. ERC1155 index is the token id which for bToken is seriesId * 2 + 1. Once the record is found we populate/update the costBasis value taking into account any previous cost bases (using the weighted average cost calculation)

  2. erc1155Controller handleTransferSingle and handleTransferBatch the only change here is when creating a new record for recipient of tokens we should copy/update the costBasis value to the recipient record. If the recipient already has a balance of this bToken the cost basis will be non-0, so we need to use the weighted average cost formula to update the costBasis field.

An alternative solution would be to create a separate entity just to track cost basis, but that seems more complicated.

Create SDK for Paradigm

For the integration with Paradigm we need to create an SDK that we will provide to them so they can accomplish calls off chain between our protocol and relay.

The spec below outlines what all will have to be done this task will focus on the SDK piece of this.

https://github.com/sirenmarkets/core/files/8806317/Paradigm_DeFi_Integration_Specification.2.pdf

There is already an SDK that is outlined for us here
https://github.com/tradeparadigm/sdks

Follow the folder using ribon to create a basic SDK for them to use.

Contracts for Paradigm
https://github.com/sirenmarkets/core-private/blob/paradigm-auction/contracts/auction/ISwap.sol

Make the MinterAmm an ERC20 token and use it as the LPToken

Currently we deploy a separate ERC20 token for each AMM pool to track LP ownership. Meanwhile, Uniswap simply makes the pool an ERC20 token, and uses that as the pool token. We should change the AMM to do this as well since it will save on gas (no contract creation, no cross-contract ERC20 calls) and it will make the subgraph mapping simpler.

Auto-exercise of ITM options

Currently if an ITM option is not exercised before expiration it would expire worthless even though it has significant value. In TradFi all platforms auto-exercise options.

Possible solutions:

  • We run an oracle that goes over all expiring contracts and exercises them using a special oracleExercise function. The oracle doesn't have to supply the price, the price can be taken on-chain. (pros: no action from users, cons: requires us to run an oracle)

  • Allow both buyer and seller to trigger the claim function that uses current on-chain underlying price to perform an exercise and exchange tokens into payment tokens automatically (pros: no oracle is needed, cons: requires user action)

The auto-exerciser oracle could use a uniswap flash loan to pay for exercising. i.e.:

  1. Borrow exercise amount in USDC from uniswap
  2. Exercise, receive WBTC
  3. Repay Uniswap using WBTC
  4. Send the rest to bToken holder

add deployment scripts (testnet + mainnet) for staking-related contracts

For the LPP we need to deploy and transfer ownership correctly for the 3 staking-related contracts we'll be using. These are StakingRewards.sol, RewardsDistribution.sol, and VestingVault.sol.

Below is the number of each contract we'll need to deploy

  • 2 StakingRewards (one for WBTC and one for USDC)
  • 1 RewardsDistribution (a single RewardsDistribution can add reward disbursements for both AMM’s)
  • 1 VestingVault contract (a single VestingVault can vest SI gained from both StakingRewards contracts

after the contracts are deployed and initialized correctly, their ownership should be transferred to the multisig wallet. The multisig wallet will need to call Owned.acceptOwnership for ownership to actually be transferred

Improve PriceOracle setSettlementPrice reliability by storing all token pairs in an iterable collection

Our process for setting the settlement price for Series which have just expired has several moving parts, and if we could reduce the number of moving parts to as few as possible, then our protocol as a whole would be more reliable. Specifically, right now when a Series expires the settlement price bot gets called immediately after Friday 8am and sets the settlement price for a specific token pair. This requires multiple onchain transaction calls, all of which must succeed in order for the protocol to run correctly.

Wouldn't it be more reliable if all of the lambda function had to do was call a single function, and all of the necessary settlement prices were to be set? We could do this if we use OZ's EnumerableSet to store all of the token pairs, and then we have a setAllSettlementPrices function which iterates through the token pairs and sets their prices.

We would still want an individual setSettlementPrice function in case setAllSettlementPrices takes too much gas and we need to call them individually.

Upgrade rinkeby AMM contracts to use new global TVL limit logic

In #17 we added a global limit, which we'll raise throughout the LPP with each new round to allow additional liquidity to be added. We need to upgrade our contracts on the rinkeby network and test existing functionality to make sure the AMM still works as expected

Update Defi Pulse Endpoint

Is your feature request related to a problem? Please describe.
Now that we are using V2 on polygon we need to add our polygon endpoint to aggregate our TVL from mainnet and polygon network

Describe the solution you'd like
Add the polygon subgraph url so we can then aggregate our TVL from both networks.

Update our DeFiLlama Endpoint

Is your feature request related to a problem? Please describe.
Now that we are using V2 on polygon we need to add our polygon endpoint to aggregate our TVL from mainnet and polygon network

Describe the solution you'd like
Add the polygon subgraph url so we can then aggregate our TVL from both networks.

Change SeriesName to use "-" instead of "." and add in the ability to have decimals instead of only whole numbers on SeriesNames

Is your feature request related to a problem? Please describe.
The problem we want to fix is the confusion where we have a series name like "WMATIC.USDC.20210827.C.1.WMATIC"

The issue is the strike price of mattic is "1.6" making the series name confusing since we can have multiple series with the same name.

Describe the solution you'd like

Change "WMATIC.USDC.20210827.C.1.WMATIC" to a more descriptive name like "WMATIC-USDC-20210827-C-1.6-WMATIC"
This would make it so we can include the decimal strike price of the series name.

Describe alternatives you've considered

We can leave it as it is because we dont rely on the series name for displaying data however I think it would be useful for when we query the graph so we dont need to call multiple properties to be able to differentiate between them.

Additional context

The screen show below shows what the subgraph returns without any other context except for series name showing why it may be confusing since we have multiple seriesnames with the same value.
image

Uniswap TWAP on-chain price oracle

Instead of using chainlink as price oracle, we can try to use uniswap v3 pools as price oracle using TWAP.
(Based on this whitepaper https://uniswap.org/whitepaper-v3.pdf, chapter 5)
(https://docs.uniswap.org/protocol/concepts/V3-overview/oracle)

Idea:
Tick Accumulator -> The tick accumulator stores the cumulative sum of the active tick at the time of the observation. The tick accumulator value increases monotonically and grows by the value of the current tick - per second.

To derive the arithmetic mean tick over an interval, the caller needs to retrieve two observations, one after the other, take the delta of the two values, and divide by the time elapsed between them. Calculating a TWAP from the tick accumulator is also covered in the whitepaper. Note that using an arithmetic mean tick to derive a price corresponds to a geometric mean price.

We can use: https://github.com/Uniswap/v3-periphery/blob/main/contracts/libraries/OracleLibrary.sol
especially consult function.

Allow rewards to be claimed on behalf of other addresses

Background

The current staking rewards may only be claimed by an address that directly interacts with StakingRewards.sol by calling getReward(). The rewards are always sent to msg.sender. This issue adds logic for rewards to be claimed on behalf of another account. The advantage is one of flexibility for the future, e.g. to more easily enable keepers for yield aggregators.

Scope

Functionality

getRewardFor(address account) uses the same functionality as getReward(), replacing msg.sender with account

Optionally, getReward() could call getRewardFor(msg.sender) to avoid code duplication.

Design SirenExchange Interface

function bTokenBuy(
        uint64 seriesId,
        uint256 bTokenAmount,
        uint256 tokenInAddress,
        uint256 tokenOutAddress,
        uint256 tokenAmountInMaximum,
        address routerAddress,
        address[] calldata path,
        address sirenAmmAddress,
    ) external {
        // Uniswap v2 logic below for example, but this will work for quickswap or sushiswap

        // amountOut
        uint256 amountOut = AMM.bTokenGetCollateralIn(seriesId, bTokenAmount);

        // Transfer the specified `amountInMaximum` to this contract.
        TransferHelper.safeTransferFrom(tokenInAddress, msg.sender, address(this), amountInMaximum);
        // Approve the router to spend  `amountInMaximum`.
        TransferHelper.safeApprove(tokenInAddress, address(swapRouter), amountInMaximum);

        // Executes the swap, returning the amountIn actually spent.
        uint256 amountIn = swapRouter.swapTokensForExactTokens(
           amountOut,
           tokenAmountInMaximum,
           path,
           address(this),
           block.timestamp
         );

        // If the swap did not require the full amountInMaximum to achieve the exact amountOut then we refund msg.sender and approve the router to spend 0.
        if (amountIn < amountInMaximum) {
            TransferHelper.safeApprove(tokenInAddress, address(swapRouter), 0);
            TransferHelper.safeTransferFrom(tokenInAddress, address(this), msg.sender, amountInMaximum - amountIn);
        } 

        // Approve AMM to transfer the token
        TransferHelper.safeApprove(tokenOutAddress, sirenAmmAddress, amountOut);

        // buy bToken
        AMM.bTokenBuy(seriesId, bTokenAmount, amountOut);

        // Transfer bToken to the buyer ....
}

    function bTokenSell(
        uint64 seriesId,
        uint256 bTokenAmount,
        uint256 tokenInAddress, // token that AMM is giving in return for bToken, this token goes into Router
        uint256 tokenOutAddress, // token that user gets in the end
        uint256 tokenAmountOutMinimum,
        address routerAddress,
        address[] calldata path,
        address sirenAmmAddress,
    ) external {
    ....

    function wTokenSell(
        uint64 seriesId,
        uint256 wTokenAmount,
        uint256 tokenInAddress, // token that AMM is giving in return for wToken, this token goes into Router
        uint256 tokenOutAddress, // token that user gets in the end
        uint256 tokenAmountOutMinimum,
        address routerAddress,
        address[] calldata path,
        address sirenAmmAddress,
    ) external {
    ....

Implement Black-Scholes in the AMM for better pricing

One immediate improvement we can do on the AMM even before we figure out dynamic pricing is to start using full black-scholes. We can totally do it on Polygon / L2. Here are solidity implementation examples:

https://github.com/pods-finance/contracts/blob/develop/contracts/amm/BlackScholes.sol
https://github.com/keep3r-network/keep3r.network/blob/master/contracts/Keep3rV1Volatility.sol

Some rough steps to take for completing this task:

  1. Decide whether we want to use these contracts as inspiration to write our own from scratch, or do we treat these more or less as a black box and copy + paste them into our protocol in place of our current MinterAmm.calcPrice? For now it'd probably be better to treat it as a black box, but learn enough about these implementations to know what the arguments are used for
  2. Create a PR with tests which replaces calcPrice with the necessary contracts here
  3. Update the deploy_singleton.ts scripts to correctly deploy the Black-Scholes contracts.

Refactor the MinterAmm to store a reference to its OPEN Markets

Background:
Right now the MinterAmm fetches the Markets relevant to it by using its assetPair to get an array of Markets from the MarketsRegistry (see MinterAmm.getMarkets). Since the list of markets stored on the MarketsRegistry only appends Markets and does not remove them, this means MinterAmm methods that need to loop over all of the MinterAmm's Markets (e.g. MinterAmm.getTotalPoolValue, MinterAmm._sellOrWithdrawActiveTokens, and MinterAmm.claimAllExpiredTokens) will increase in gas cost over time. Eventually these gas costs will discourage traders and LP's from using the protocol at all.

The list of OPEN Markets stays relatively constant, while the number of EXPIRED Markets only grows over time. We need to decouple the MinterAmm from the MarketsRegistry's list of EXPIRED Markets, so that the MinterAmm only needs to store and loop over its OPEN Markets.

Goal:
Update the MinterAmm so it can store its own array of OPEN Markets, and not need to reference MarketsRegistry's ever-growing list of Markets. The MinterAmm should be able to remove a Market once its expired and all of its wTokens have been claimed (i.e. after a call to MinterAmm.claimAllExpiredTokens). This will optimize the AMM so it only needs to keep OPEN Markets in its collection of Markets.

Relevant Contract Methods and Lines of Code:

  • MarketsRegistry.createMarket: This method will likely need a way to tell a MinterAmm that a new Market has been added to the AMM
  • (New) MinterAmm.addMarket: Adds a new Market to its collection
  • (New) MinterAmm.removeMarket: Removes an EXPIRED market from its collection

Testing:
Please write your tests in a new file, and do not add to an existing test file. This will make it easier to merge into the v2 contract changes we're making.

Add Tenderly monitoring to v2 contracts

We use Tenderly for debugging failed transactions and simulating transactions before we call them on Mainnet, but we need to add our new contracts on Polygon.

We should add

AmmFactory
SeriesController
SeriesVault
ERC1155Controller
PriceOracle
All of the AMMs

Add require check on PriceOracle.addTokenPair for non-zero price

Describe the bug
If a newly added token pair's oracle returns a price of 0 inside of PriceOracle.addTokenPair, then that token pair will never be useable going forward.

To Reproduce
Steps to reproduce the behavior:

  1. Call PriceOracle.addTokenPair using an oracle that returns a price of 0.

Expected behavior
I expect any call to PriceOracle.addTokenPair will not render that token pair useless

Additional context
To fix this, we should require a check in PriceOracle.addTokenPair to make sure we never set a price of 0, or else we'll never be able to use that token pair again.

Able to sell bTokens to AMM (not minted in AMM)

Right now, the amm support bTokenSell only for bTokens minted from the Amm.

The other projects allow traders to sell options, which have not been minted from the amm.
We can also incorporate this feature to our Amm.

The benefits will be:

  • higher trading volume
  • price efficiency

The negatives:

  • LP providers do not earn any more theta (earning will. need to come from fees)

How to do it:

  • We will need to update bTokenSell (sell bTokens not from AMM)
  • maybe we will need bTokenVault as we have wTokenVault or combine them
  • update the withdraw function to incorporate the bTokens

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.