Skip to content

Commit

Permalink
v1.1: Events, Better Oracle Setup, ETHx/DAIx Support (#4)
Browse files Browse the repository at this point in the history
* sushiswap oracle, tellor fallback, events

* update tests and readme

* eth->dai exchange testing
  • Loading branch information
mikeghen authored Jul 21, 2021
1 parent 02b2608 commit 194af54
Show file tree
Hide file tree
Showing 7 changed files with 116 additions and 202 deletions.
29 changes: 14 additions & 15 deletions 01-Contracts/contracts/StreamExchange.sol
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,7 @@ contract StreamExchange is Ownable, SuperAppBase, UsingTellor {
using StreamExchangeStorage for StreamExchangeStorage.StreamExchange;
StreamExchangeStorage.StreamExchange internal _exchange;

// TODO: Emit these events where appropriate
// event StartedInboundStream(address from, uint96 rate);
// event EndedInboundStream(address from, uint96 rate);
// event Distribution(uint256 totalAmount, uint256 feeCollected);

event UpdatedStream(address from, int96 newRate, int96 totalInflow);

constructor(
ISuperfluid host,
Expand Down Expand Up @@ -81,6 +77,7 @@ contract StreamExchange is Ownable, SuperAppBase, UsingTellor {
_exchange.oracle = ITellor(oracle);
_exchange.requestId = requestId;
_exchange.feeRate = 20000;
_exchange.rateTolerance = 10000;
_exchange.subsidyIndexId = 1;
_exchange.subsidyRate = 4e17; // 0.4 tokens/second ~ 1,000,000 tokens in a month
_exchange.owner = msg.sender;
Expand Down Expand Up @@ -131,19 +128,13 @@ contract StreamExchange is Ownable, SuperAppBase, UsingTellor {

_exchange.streams[requester].rate = _exchange.streams[requester].rate + changeInFlowRate;

// if (_exchange.streams[requester].rate == 0) {
// // Delete the subscription
// console.log("Deleting subscription");
// newCtx = _exchange._deleteSubscriptionWithContext(newCtx, address(this), _exchange.outputIndexId, requester, _exchange.outputToken);
// newCtx = _exchange._deleteSubscriptionWithContext(newCtx, address(this), _exchange.subsidyIndexId, requester, _exchange.subsidyToken);
// } else {
// Update the subscription
newCtx = _exchange._updateSubscriptionWithContext(newCtx, _exchange.outputIndexId, requester, uint128(uint(int(_exchange.streams[requester].rate)))/100, _exchange.outputToken);
newCtx = _exchange._updateSubscriptionWithContext(newCtx, _exchange.subsidyIndexId, requester, uint128(uint(int(_exchange.streams[requester].rate)))/100, _exchange.subsidyToken);
// }
newCtx = _exchange._updateSubscriptionWithContext(newCtx, _exchange.outputIndexId, requester, uint128(uint(int(_exchange.streams[requester].rate))), _exchange.outputToken);
newCtx = _exchange._updateSubscriptionWithContext(newCtx, _exchange.subsidyIndexId, requester, uint128(uint(int(_exchange.streams[requester].rate))), _exchange.subsidyToken);

_exchange.totalInflow = _exchange.totalInflow + changeInFlowRate;

emit UpdatedStream(requester, _exchange.streams[requester].rate, _exchange.totalInflow);

}


Expand All @@ -159,6 +150,10 @@ contract StreamExchange is Ownable, SuperAppBase, UsingTellor {
_exchange.feeRate = feeRate;
}

function setRateTolerance(uint128 rateTolerance) external onlyOwner {
_exchange.rateTolerance = rateTolerance;
}

function setOracle(address oracle) external onlyOwner {
_exchange.oracle = ITellor(oracle);
}
Expand Down Expand Up @@ -223,6 +218,10 @@ contract StreamExchange is Ownable, SuperAppBase, UsingTellor {
return _exchange.feeRate;
}

function getRateTolerance() external view returns (uint256) {
return _exchange.rateTolerance;
}

function getStreamRate(address streamer) external view returns (int96) {
return _exchange.streams[streamer].rate;
}
Expand Down
81 changes: 52 additions & 29 deletions 01-Contracts/contracts/StreamExchangeHelper.sol
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ library StreamExchangeHelper {

using SafeERC20 for ERC20;

// TODO: Emit these events where appropriate
event Distribution(uint256 totalAmount, uint256 feeCollected, address token);


function _getCurrentValue(
StreamExchangeStorage.StreamExchange storage self,
uint256 _requestId
Expand Down Expand Up @@ -88,56 +92,75 @@ library StreamExchangeHelper {
// Confirm the app has enough to distribute
require(self.outputToken.balanceOf(address(this)) >= actualAmount, "!enough");

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

// Distribute a subsidy if possible
if(self.subsidyToken.balanceOf(address(this)) >= subsidyAmount) {
newCtx = _idaDistribute(self, self.subsidyIndexId, uint128(subsidyAmount), self.subsidyToken, newCtx);
emit Distribution(subsidyAmount, 0, address(self.subsidyToken));
}

self.lastDistributionAt = block.timestamp;

// Take the fee
ISuperToken(self.outputToken).transfer(self.owner, feeCollected);

require(ISuperToken(self.inputToken).balanceOf(address(this)) == 0, "!sellAllInput");


return newCtx;

}

function _swap(
StreamExchangeStorage.StreamExchange storage self,
uint256 amount,
uint256 amount, // Assumes this is outputToken.balanceOf(address(this))
uint256 exchangeRate,
uint256 deadline
) public returns(uint) {

uint256 minOutput = amount * 1e18 / exchangeRate / 1e12;

self.inputToken.downgrade(amount);
address inputToken = self.inputToken.getUnderlyingToken();
address outputToken = self.outputToken.getUnderlyingToken();
address[] memory path = new address[](2);
path[0] = inputToken;
path[1] = outputToken;

// approve the router to spend
ERC20(inputToken).safeIncreaseAllowance(address(self.sushiRouter), amount);

uint[] memory amounts = self.sushiRouter.swapExactTokensForTokens(
amount,
minOutput,
path,
address(this),
deadline
);

ERC20(outputToken).safeIncreaseAllowance(address(self.outputToken), amounts[1]);
self.outputToken.upgrade(amounts[1]);

// TODO: Take a small fee

return amounts[1];
}
address inputToken; // The underlying input token address
address outputToken; // The underlying output token address
address[] memory path; // The path to take
uint256 minOutput; // The minimum amount of output tokens based on Tellor
uint256 outputAmount; // The balance before the swap

console.log("Amount to swap", amount);
// TODO: This needs to be "invertable"
// minOutput = amount * 1e18 / exchangeRate / 1e12;
minOutput = amount * exchangeRate / 1e6;
console.log("minOutput", minOutput);
minOutput = minOutput * (1e6 - self.rateTolerance) / 1e6;
console.log("minOutput", minOutput);

self.inputToken.downgrade(amount);
inputToken = self.inputToken.getUnderlyingToken();
outputToken = self.outputToken.getUnderlyingToken();
path = new address[](2);
path[0] = inputToken;
path[1] = outputToken;

// Swap on Sushiswap
ERC20(inputToken).safeIncreaseAllowance(address(self.sushiRouter), amount);
self.sushiRouter.swapExactTokensForTokens(
amount,
0, // Accept any amount but fail if we're too far from the oracle price
path,
address(this),
deadline
);
// Assumes `amount` was outputToken.balanceOf(address(this))
outputAmount = ERC20(outputToken).balanceOf(address(this));
console.log("outputAmount", outputAmount);
require(outputAmount >= minOutput, "BAD_EXCHANGE_RATE: Try again later");

// Convert the outputToken back to its supertoken version
ERC20(outputToken).safeIncreaseAllowance(address(self.outputToken), outputAmount);
self.outputToken.upgrade(outputAmount);

return outputAmount;
}


function _initalizeLiquidityMining(StreamExchangeStorage.StreamExchange storage self) internal {
Expand Down
1 change: 1 addition & 0 deletions 01-Contracts/contracts/StreamExchangeStorage.sol
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ library StreamExchangeStorage {
uint256 requestId; // The id of the tellor request that has input/output exchange rate
uint128 feeRate; // The fee taken as a % with 6 decimals
address owner; // The owner of the exchange
uint256 rateTolerance; // The percentage to deviate from the oracle scaled to 1e6
}

}
17 changes: 4 additions & 13 deletions 01-Contracts/scripts/distribute.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,8 @@
async function main() {

const [keeper] = await ethers.getSigners();
const [owner] = await ethers.getSigners();

// Update the oracle
const TellorPlayground = await ethers.getContractFactory("TellorPlayground");
const tp = await TellorPlayground.attach("0xC79255821DA1edf8E1a8870ED5cED9099bf2eAAA");

let o = await tp.submitValue(1, 1000000);
console.log("submitValue:", o);

const StreamExchangeHelper = await ethers.getContractFactory("StreamExchangeHelper")
const seh = await StreamExchangeHelper.attach("0x0942570634A80bcd096873afC9b112A900492fd7")
console.log("Deployed StreamExchangeHelper ")

const StreamExchange = await ethers.getContractFactory("StreamExchange", {
libraries: {
Expand All @@ -20,11 +11,11 @@ async function main() {
});
const rickoshea = await StreamExchange.attach("0x7E2E5f06e36da0BA58B08940a72Fd6b68FbDfD61")

// console.log("getOuputToken", await rickoshea.getOuputToken())
// console.log("getInputToken", await rickoshea.getInputToken())
console.log("getOuputToken", await rickoshea.getOuputToken())
console.log("getInputToken", await rickoshea.getInputToken())


let dr = await rickoshea.distribute();
// let dr = await rickoshea.distribute();

console.log("Distribute:", dr);

Expand Down
29 changes: 29 additions & 0 deletions 01-Contracts/scripts/set-oracle-address.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
async function main() {

const [keeper] = await ethers.getSigners();
const TELLOR_CONTRACT_ADDRESS = "0xC79255821DA1edf8E1a8870ED5cED9099bf2eAAA"
const STREAM_EXCHANGE_HELPER_ADDRESS = "0x0C7776292AB9E95c54282fD74e47d73338c457D8"
const RICOCHET_CONTRACT_ADDRESS = "0x387af38C133056a0744FB6e823CdB459AE3c5a1f"

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 rickoshea = await StreamExchange.attach(RICOCHET_CONTRACT_ADDRESS)

console.log("getTellorOracle", await rickoshea.getTellorOracle())
console.log("setOracle", TELLOR_CONTRACT_ADDRESS, await rickoshea.setOracle(TELLOR_CONTRACT_ADDRESS))
// console.log("getTellorOracle", await rickoshea.getTellorOracle())

}

main()
.then(() => process.exit(0))
.catch(error => {
console.error(error);
process.exit(1);
});
Loading

0 comments on commit 194af54

Please sign in to comment.