Skip to content

Commit

Permalink
v1.2: Fix IDA and fee calculation issues (#6)
Browse files Browse the repository at this point in the history
* sushiswap oracle, tellor fallback, events

* update tests and readme

* eth->dai exchange testing

* check IDA shares before distribute

* misc. fixes for deploy

* Added notes for v2 spec

* calculate fee and distribute the actualAmount

* tests for fee and v1.2 deploy config

* script for setting rate tolerance

* support verification
  • Loading branch information
mikeghen authored Jul 31, 2021
1 parent 194af54 commit 7d35473
Show file tree
Hide file tree
Showing 9 changed files with 147 additions and 23 deletions.
34 changes: 34 additions & 0 deletions 00-Meta/Specification.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Ricochet Exchange v2 (Draft)

## Overview
* Ricochet is a stream exchange that uses Superfluid
* Each `RicochetExchange` contract supports two-way streaming swaps
* Streamers can stream either `tokenA` or `tokenB` at any rate and RicochetExchange keepers will trigger swaps periodically
* `RicochetExchange` will perform the following algorithm to swap:
* Determine the `surplusToken`, which of tokenA or tokenB there's an excess amount of the other token available to swap with
* Example: consider there is $100 DAI and $50 ETH that's been streamed into the contract
* ETH is the `surplusToken` because there's _more_ than enough DAI to make the swap
* Set the other token as the `deficitToken`
* Consider the above example, DAI is the deficitToken because there's not enough ETH to swap for DAI
* Next, perform the internal swap, swap the `surplusToken` for the `deficitToken` at the current `exchangeRate`
* Then, take the remaining amount of the `deficitToken` and swap on Sushiswap
* Finally, distribute to `tokenA` and `tokenB` their tokens

## Protocol Speciciations

### Structures
* `Oracle`
* `ITellor oracle` - Address of deployed simple oracle for input//output token
* `uint256 requestId` - The id of the tellor request that has input/output exchange rate
* `uint256 rateTolerance` - The percentage to deviate from the oracle scaled to 1e6

* `Exchange`
* `ISuperfluid host` - Superfluid host contract
* `IConstantFlowAgreementV1 cfa` - The stored constant flow agreement class address
* `IInstantDistributionAgreementV1 ida` - The stored instant dist. agreement class address
* `ISuperToken tokenA` - One of the tokens supported for streaming
* `ISuperToken tokenB` - The other one of the tokens supported for streaming
* `int96 totalInflow` - The fee taken as a % with 6 decimals
* `uint128 feeRate` - The fee taken as a % with 6 decimals
* `IUniswapV2Router02 sushiRouter` - Address of sushsiwap router to use for swapping
* `Oracle oracle` - The oracle to use for the exchange
25 changes: 25 additions & 0 deletions 01-Contracts/arguments.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@

// Polygon Mainnet
const HOST_ADDRESS = "0x3E14dC1b13c488a8d5D310918780c983bD5982E7";
const CFA_ADDRESS = "0x6EeE6060f715257b970700bc2656De21dEdF074C";
const IDA_ADDRESS = "0xB0aABBA4B2783A72C52956CDEF62d438ecA2d7a1";
const DAIX_ADDRESS = "0x1305F6B6Df9Dc47159D12Eb7aC2804d4A33173c2";
const ETHX_ADDRESS = "0x27e1e4E6BC79D93032abef01025811B7E4727e85";
const SUSHISWAP_ROUTER_ADDRESS = "0x1b02dA8Cb0d097eB8D57A175b88c7D8b47997506";
const TELLOR_ORACLE_ADDRESS = "0xC79255821DA1edf8E1a8870ED5cED9099bf2eAAA";
const RIC_CONTRACT_ADDRESS = "0x263026e7e53dbfdce5ae55ade22493f828922965";
const TELLOR_REQUEST_ID = 1;


module.exports = [
HOST_ADDRESS,
CFA_ADDRESS,
IDA_ADDRESS,
process.env.INPUT_TOKEN_ADDRESS,
process.env.OUTPUT_TOKEN_ADDRESS,
RIC_CONTRACT_ADDRESS,
SUSHISWAP_ROUTER_ADDRESS,
TELLOR_ORACLE_ADDRESS,
TELLOR_REQUEST_ID,
process.env.SF_REG_KEY
];
8 changes: 6 additions & 2 deletions 01-Contracts/contracts/StreamExchange.sol
Original file line number Diff line number Diff line change
Expand Up @@ -118,8 +118,12 @@ contract StreamExchange is Ownable, SuperAppBase, UsingTellor {

newCtx = ctx;

// NOTE: Trigger a distribution if there's any inputToken
if (ISuperToken(_exchange.inputToken).balanceOf(address(this)) > 0 && doDistributeFirst) {
(, , uint128 totalUnitsApproved, uint128 totalUnitsPending) = _exchange.ida.getIndex(
_exchange.outputToken,
address(this),
_exchange.outputIndexId);

if (doDistributeFirst && totalUnitsApproved + totalUnitsPending > 0 && ISuperToken(_exchange.inputToken).balanceOf(address(this)) > 0) {
newCtx = _exchange._distribute(newCtx);
}

Expand Down
8 changes: 4 additions & 4 deletions 01-Contracts/contracts/StreamExchangeHelper.sol
Original file line number Diff line number Diff line change
Expand Up @@ -82,16 +82,16 @@ library StreamExchangeHelper {
if (actualAmount == 0) { return newCtx; }

// Calculate the fee for making the distribution
uint256 feeCollected = outputBalance * self.feeRate / 1e6;
uint256 distAmount = outputBalance - feeCollected;
uint256 feeCollected = actualAmount * self.feeRate / 1e6;
uint256 distAmount = actualAmount - feeCollected;


// Calculate subside
uint256 subsidyAmount = (block.timestamp - self.lastDistributionAt) * self.subsidyRate;

// Confirm the app has enough to distribute
require(self.outputToken.balanceOf(address(this)) >= actualAmount, "!enough");

console.log("distAmount", distAmount);
newCtx = _idaDistribute(self, self.outputIndexId, uint128(distAmount), self.outputToken, newCtx);
emit Distribution(distAmount, feeCollected, address(self.outputToken));

Expand Down Expand Up @@ -132,7 +132,7 @@ library StreamExchangeHelper {
minOutput = amount * exchangeRate / 1e6;
console.log("minOutput", minOutput);
minOutput = minOutput * (1e6 - self.rateTolerance) / 1e6;
console.log("minOutput", minOutput);
console.log("minOutput after rate tolerance", minOutput);

self.inputToken.downgrade(amount);
inputToken = self.inputToken.getUnderlyingToken();
Expand Down
20 changes: 13 additions & 7 deletions 01-Contracts/hardhat.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ require("@nomiclabs/hardhat-web3");
require('@nomiclabs/hardhat-ethers');
require('@openzeppelin/hardhat-upgrades');
require('hardhat-contract-sizer');
require("@nomiclabs/hardhat-etherscan");

// This is a sample Hardhat task. To learn how to create your own go to
// https://hardhat.org/guides/create-task.html
Expand All @@ -26,12 +27,12 @@ module.exports = {
timeout: 100000
},
networks: {
// polygon: {
// url: process.env.POLYGON_QUIKNODE_URL,
// accounts: [process.env.PRIVATE_KEY],
// gas: 2000000,
// gasPrice: 2000000000
// },
polygon: {
url: "https://polygon-mainnet.infura.io/v3/" + process.env.INFURA_KEY,
accounts: [process.env.MATIC_PRIVATE_KEY],
gas: 2000000,
gasPrice: 20000000000
},
rinkeby: {
url: "https://rinkeby.infura.io/v3/" + process.env.INFURA_KEY,
accounts: [process.env.PRIVATE_KEY],
Expand All @@ -44,11 +45,16 @@ module.exports = {
// },
hardhat: {
forking: {
url: "https://rinkeby.infura.io/v3/" + process.env.INFURA_KEY,
url: process.env.QUICK_NODE_URL,
accounts: [process.env.PRIVATE_KEY_ADMIN, process.env.PRIVATE_KEY_ALICE, process.env.PRIVATE_KEY_BOB],
}
}
},
etherscan: {
// Your API key for Etherscan
// Obtain one at https://etherscan.io/
apiKey: process.env.POLYSCAN_API_KEY
},
contractSizer: {
alphaSort: true,
runOnCompile: true,
Expand Down
3 changes: 2 additions & 1 deletion 01-Contracts/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,11 @@
"license": "ISC",
"devDependencies": {
"@nomiclabs/hardhat-ethers": "^2.0.2",
"@nomiclabs/hardhat-etherscan": "^2.1.4",
"@nomiclabs/hardhat-waffle": "^2.0.1",
"@nomiclabs/hardhat-web3": "^2.0.0",
"@openzeppelin/contracts": "^4.0.0",
"@openzeppelin/hardhat-upgrades": "^1.9.0",
"@superfluid-finance/ethereum-contracts": "^1.0.0-rc.5",
"@truffle/contract": "^4.3.23",
"axios": "^0.21.1",
Expand All @@ -26,7 +28,6 @@
},
"dependencies": {
"@openzeppelin/contracts-upgradeable": "^4.1.0",
"@openzeppelin/hardhat-upgrades": "^1.8.2",
"@uniswap/v2-core": "^1.0.1",
"@uniswap/v2-periphery": "^1.1.0-beta.0",
"hardhat-contract-sizer": "^2.0.3",
Expand Down
13 changes: 8 additions & 5 deletions 01-Contracts/scripts/deploy-polygon.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,21 +46,24 @@ async function main() {
console.log("\tHOST_ADDRESS", HOST_ADDRESS)
console.log("\tCFA_ADDRESS", CFA_ADDRESS)
console.log("\tIDA_ADDRESS", IDA_ADDRESS)
console.log("\tDAIX_ADDRESS",DAIX_ADDRESS)
console.log("\tETHX_ADDRESS", ETHX_ADDRESS)
console.log("\tINPUT_TOKEN", process.env.INPUT_TOKEN_ADDRESS)
console.log("\tOUTPUT_TOKEN", process.env.OUTPUT_TOKEN_ADDRESS)
console.log("\tSUSHISWAP_ROUTER_ADDRESS", SUSHISWAP_ROUTER_ADDRESS)
console.log("\tTELLOR_ORACLE_ADDRESS", TELLOR_ORACLE_ADDRESS)
console.log("\tTELLOR_REQUEST_ID", TELLOR_REQUEST_ID)



const streamExchange = await StreamExchange.deploy( HOST_ADDRESS,
CFA_ADDRESS,
IDA_ADDRESS,
DAIX_ADDRESS,
ETHX_ADDRESS,
process.env.INPUT_TOKEN_ADDRESS,
process.env.OUTPUT_TOKEN_ADDRESS,
RIC_CONTRACT_ADDRESS,
SUSHISWAP_ROUTER_ADDRESS,
TELLOR_ORACLE_ADDRESS,
TELLOR_REQUEST_ID,
"ricochet23" );
process.env.SF_REG_KEY );
await streamExchange.deployed();
console.log("Deployed StreamExchange at address:", streamExchange.address);
}
Expand Down
28 changes: 28 additions & 0 deletions 01-Contracts/scripts/set-rate-tolerance.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
async function main() {

const [keeper] = await ethers.getSigners();
const RATE_TOLERANCE = "20000"
const STREAM_EXCHANGE_HELPER_ADDRESS = "0x0C7776292AB9E95c54282fD74e47d73338c457D8"
const RICOCHET_CONTRACT_ADDRESS = "0xe0B7907FA4B759FA4cB201F0E02E16374Bc523fd"

const StreamExchangeHelper = await ethers.getContractFactory("StreamExchangeHelper")
const seh = await StreamExchangeHelper.attach(STREAM_EXCHANGE_HELPER_ADDRESS)

const StreamExchange = await ethers.getContractFactory("StreamExchange", {
libraries: {
StreamExchangeHelper: seh.address,
},
});
const ricochet = await StreamExchange.attach(RICOCHET_CONTRACT_ADDRESS)

console.log("rateTolerance", await ricochet.getRateTolerance())
console.log("setRateTolerance", RATE_TOLERANCE, await ricochet.setRateTolerance(RATE_TOLERANCE))

}

main()
.then(() => process.exit(0))
.catch(error => {
console.error(error);
process.exit(1);
});
31 changes: 27 additions & 4 deletions 01-Contracts/test/SteamExchange.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ describe("StreamExchange", () => {
before(async function () {
//process.env.RESET_SUPERFLUID_FRAMEWORK = 1;
let response = await axios.get('https://api.coingecko.com/api/v3/simple/price?ids=ethereum&vs_currencies=usd')
oraclePrice = parseInt(response.data.ethereum.usd * 1.002 * 1000000).toString()
oraclePrice = parseInt(response.data.ethereum.usd * 1.005 * 1000000).toString()
console.log("oraclePrice", oraclePrice)
});

Expand Down Expand Up @@ -389,28 +389,43 @@ describe("StreamExchange", () => {
expect(await app.getFeeRate()).to.equal(20000)

await app.connect(owner).setFeeRate(20000);
await app.connect(owner).setRateTolerance(50000);
await app.connect(owner).setSubsidyRate("500000000000000000")

expect(await app.getSubsidyRate()).to.equal("500000000000000000")
expect(await app.getFeeRate()).to.equal(20000)
expect(await app.getRateTolerance()).to.equal(50000)
console.log("Getters and setters correct")

const inflowRate = toWad(0.0000004000);
const inflowRate = toWad(0.00000004000);

await ethx.transfer(u.bob.address, "100000000000000000", {from: u.admin.address});
await ethx.transfer(u.alice.address, "100000000000000000", {from: u.admin.address});
console.log("Transfer bob")
await ethx.transfer(u.bob.address, "7000000000000000", {from: u.admin.address});
console.log("Transfer aliuce")
await ethx.transfer(u.alice.address, "7000000000000000", {from: u.admin.address});
console.log("Done")

await tp.submitValue(1, oraclePrice);

await takeMeasurements();

// Test owner start/stop stream
await u.admin.flow({ flowRate: inflowRate, recipient: u.app });
await traveler.advanceTimeAndBlock(60*60*3);
await tp.submitValue(1, oraclePrice);
await app.distribute()
await u.admin.flow({ flowRate: "0", recipient: u.app });



await u.bob.flow({ flowRate: inflowRate, recipient: u.app });
await traveler.advanceTimeAndBlock(60*60*3);
await tp.submitValue(1, oraclePrice);
await app.distribute()
await takeMeasurements();
await delta("Bob", bobBalances)
await delta("Alice", aliceBalances)
await delta("Owner", ownerBalances)

// Round 2
await u.alice.flow({ flowRate: inflowRate, recipient: u.app });
Expand All @@ -420,6 +435,8 @@ describe("StreamExchange", () => {
await takeMeasurements()
await delta("Bob", bobBalances)
await delta("Alice", aliceBalances)
await delta("Owner", ownerBalances)


// Round 3
await traveler.advanceTimeAndBlock(60*60*2);
Expand All @@ -428,6 +445,8 @@ describe("StreamExchange", () => {
await takeMeasurements()
await delta("Bob", bobBalances)
await delta("Alice", aliceBalances)
await delta("Owner", ownerBalances)



// Round 4
Expand All @@ -438,6 +457,8 @@ describe("StreamExchange", () => {
await takeMeasurements()
await delta("Bob", bobBalances)
await delta("Alice", aliceBalances)
await delta("Owner", ownerBalances)


// Round 5
await traveler.advanceTimeAndBlock(60*60*2);
Expand All @@ -446,6 +467,8 @@ describe("StreamExchange", () => {
await takeMeasurements()
await delta("Bob", bobBalances)
await delta("Alice", aliceBalances)
await delta("Owner", ownerBalances)



});
Expand Down

0 comments on commit 7d35473

Please sign in to comment.