Skip to content

Commit 194af54

Browse files
authored
v1.1: Events, Better Oracle Setup, ETHx/DAIx Support (#4)
* sushiswap oracle, tellor fallback, events * update tests and readme * eth->dai exchange testing
1 parent 02b2608 commit 194af54

File tree

7 files changed

+116
-202
lines changed

7 files changed

+116
-202
lines changed

01-Contracts/contracts/StreamExchange.sol

Lines changed: 14 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -46,11 +46,7 @@ contract StreamExchange is Ownable, SuperAppBase, UsingTellor {
4646
using StreamExchangeStorage for StreamExchangeStorage.StreamExchange;
4747
StreamExchangeStorage.StreamExchange internal _exchange;
4848

49-
// TODO: Emit these events where appropriate
50-
// event StartedInboundStream(address from, uint96 rate);
51-
// event EndedInboundStream(address from, uint96 rate);
52-
// event Distribution(uint256 totalAmount, uint256 feeCollected);
53-
49+
event UpdatedStream(address from, int96 newRate, int96 totalInflow);
5450

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

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

134-
// if (_exchange.streams[requester].rate == 0) {
135-
// // Delete the subscription
136-
// console.log("Deleting subscription");
137-
// newCtx = _exchange._deleteSubscriptionWithContext(newCtx, address(this), _exchange.outputIndexId, requester, _exchange.outputToken);
138-
// newCtx = _exchange._deleteSubscriptionWithContext(newCtx, address(this), _exchange.subsidyIndexId, requester, _exchange.subsidyToken);
139-
// } else {
140-
// Update the subscription
141-
newCtx = _exchange._updateSubscriptionWithContext(newCtx, _exchange.outputIndexId, requester, uint128(uint(int(_exchange.streams[requester].rate)))/100, _exchange.outputToken);
142-
newCtx = _exchange._updateSubscriptionWithContext(newCtx, _exchange.subsidyIndexId, requester, uint128(uint(int(_exchange.streams[requester].rate)))/100, _exchange.subsidyToken);
143-
// }
131+
newCtx = _exchange._updateSubscriptionWithContext(newCtx, _exchange.outputIndexId, requester, uint128(uint(int(_exchange.streams[requester].rate))), _exchange.outputToken);
132+
newCtx = _exchange._updateSubscriptionWithContext(newCtx, _exchange.subsidyIndexId, requester, uint128(uint(int(_exchange.streams[requester].rate))), _exchange.subsidyToken);
144133

145134
_exchange.totalInflow = _exchange.totalInflow + changeInFlowRate;
146135

136+
emit UpdatedStream(requester, _exchange.streams[requester].rate, _exchange.totalInflow);
137+
147138
}
148139

149140

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

153+
function setRateTolerance(uint128 rateTolerance) external onlyOwner {
154+
_exchange.rateTolerance = rateTolerance;
155+
}
156+
162157
function setOracle(address oracle) external onlyOwner {
163158
_exchange.oracle = ITellor(oracle);
164159
}
@@ -223,6 +218,10 @@ contract StreamExchange is Ownable, SuperAppBase, UsingTellor {
223218
return _exchange.feeRate;
224219
}
225220

221+
function getRateTolerance() external view returns (uint256) {
222+
return _exchange.rateTolerance;
223+
}
224+
226225
function getStreamRate(address streamer) external view returns (int96) {
227226
return _exchange.streams[streamer].rate;
228227
}

01-Contracts/contracts/StreamExchangeHelper.sol

Lines changed: 52 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@ library StreamExchangeHelper {
2020

2121
using SafeERC20 for ERC20;
2222

23+
// TODO: Emit these events where appropriate
24+
event Distribution(uint256 totalAmount, uint256 feeCollected, address token);
25+
26+
2327
function _getCurrentValue(
2428
StreamExchangeStorage.StreamExchange storage self,
2529
uint256 _requestId
@@ -88,56 +92,75 @@ library StreamExchangeHelper {
8892
// Confirm the app has enough to distribute
8993
require(self.outputToken.balanceOf(address(this)) >= actualAmount, "!enough");
9094

91-
newCtx = _idaDistribute(self, self.outputIndexId, uint128(actualAmount), self.outputToken, newCtx);
95+
newCtx = _idaDistribute(self, self.outputIndexId, uint128(distAmount), self.outputToken, newCtx);
96+
emit Distribution(distAmount, feeCollected, address(self.outputToken));
9297

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

98104
self.lastDistributionAt = block.timestamp;
99105

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

109+
require(ISuperToken(self.inputToken).balanceOf(address(this)) == 0, "!sellAllInput");
110+
111+
103112
return newCtx;
104113

105114
}
106115

107116
function _swap(
108117
StreamExchangeStorage.StreamExchange storage self,
109-
uint256 amount,
118+
uint256 amount, // Assumes this is outputToken.balanceOf(address(this))
110119
uint256 exchangeRate,
111120
uint256 deadline
112121
) public returns(uint) {
113122

114-
uint256 minOutput = amount * 1e18 / exchangeRate / 1e12;
115-
116-
self.inputToken.downgrade(amount);
117-
address inputToken = self.inputToken.getUnderlyingToken();
118-
address outputToken = self.outputToken.getUnderlyingToken();
119-
address[] memory path = new address[](2);
120-
path[0] = inputToken;
121-
path[1] = outputToken;
122-
123-
// approve the router to spend
124-
ERC20(inputToken).safeIncreaseAllowance(address(self.sushiRouter), amount);
125-
126-
uint[] memory amounts = self.sushiRouter.swapExactTokensForTokens(
127-
amount,
128-
minOutput,
129-
path,
130-
address(this),
131-
deadline
132-
);
133-
134-
ERC20(outputToken).safeIncreaseAllowance(address(self.outputToken), amounts[1]);
135-
self.outputToken.upgrade(amounts[1]);
136-
137-
// TODO: Take a small fee
138-
139-
return amounts[1];
140-
}
123+
address inputToken; // The underlying input token address
124+
address outputToken; // The underlying output token address
125+
address[] memory path; // The path to take
126+
uint256 minOutput; // The minimum amount of output tokens based on Tellor
127+
uint256 outputAmount; // The balance before the swap
128+
129+
console.log("Amount to swap", amount);
130+
// TODO: This needs to be "invertable"
131+
// minOutput = amount * 1e18 / exchangeRate / 1e12;
132+
minOutput = amount * exchangeRate / 1e6;
133+
console.log("minOutput", minOutput);
134+
minOutput = minOutput * (1e6 - self.rateTolerance) / 1e6;
135+
console.log("minOutput", minOutput);
136+
137+
self.inputToken.downgrade(amount);
138+
inputToken = self.inputToken.getUnderlyingToken();
139+
outputToken = self.outputToken.getUnderlyingToken();
140+
path = new address[](2);
141+
path[0] = inputToken;
142+
path[1] = outputToken;
143+
144+
// Swap on Sushiswap
145+
ERC20(inputToken).safeIncreaseAllowance(address(self.sushiRouter), amount);
146+
self.sushiRouter.swapExactTokensForTokens(
147+
amount,
148+
0, // Accept any amount but fail if we're too far from the oracle price
149+
path,
150+
address(this),
151+
deadline
152+
);
153+
// Assumes `amount` was outputToken.balanceOf(address(this))
154+
outputAmount = ERC20(outputToken).balanceOf(address(this));
155+
console.log("outputAmount", outputAmount);
156+
require(outputAmount >= minOutput, "BAD_EXCHANGE_RATE: Try again later");
157+
158+
// Convert the outputToken back to its supertoken version
159+
ERC20(outputToken).safeIncreaseAllowance(address(self.outputToken), outputAmount);
160+
self.outputToken.upgrade(outputAmount);
161+
162+
return outputAmount;
163+
}
141164

142165

143166
function _initalizeLiquidityMining(StreamExchangeStorage.StreamExchange storage self) internal {

01-Contracts/contracts/StreamExchangeStorage.sol

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ library StreamExchangeStorage {
4444
uint256 requestId; // The id of the tellor request that has input/output exchange rate
4545
uint128 feeRate; // The fee taken as a % with 6 decimals
4646
address owner; // The owner of the exchange
47+
uint256 rateTolerance; // The percentage to deviate from the oracle scaled to 1e6
4748
}
4849

4950
}

01-Contracts/scripts/distribute.js

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,8 @@
11
async function main() {
22

3-
const [keeper] = await ethers.getSigners();
3+
const [owner] = await ethers.getSigners();
44

5-
// Update the oracle
6-
const TellorPlayground = await ethers.getContractFactory("TellorPlayground");
7-
const tp = await TellorPlayground.attach("0xC79255821DA1edf8E1a8870ED5cED9099bf2eAAA");
85

9-
let o = await tp.submitValue(1, 1000000);
10-
console.log("submitValue:", o);
11-
12-
const StreamExchangeHelper = await ethers.getContractFactory("StreamExchangeHelper")
13-
const seh = await StreamExchangeHelper.attach("0x0942570634A80bcd096873afC9b112A900492fd7")
14-
console.log("Deployed StreamExchangeHelper ")
156

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

23-
// console.log("getOuputToken", await rickoshea.getOuputToken())
24-
// console.log("getInputToken", await rickoshea.getInputToken())
14+
console.log("getOuputToken", await rickoshea.getOuputToken())
15+
console.log("getInputToken", await rickoshea.getInputToken())
2516

2617

27-
let dr = await rickoshea.distribute();
18+
// let dr = await rickoshea.distribute();
2819

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

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
async function main() {
2+
3+
const [keeper] = await ethers.getSigners();
4+
const TELLOR_CONTRACT_ADDRESS = "0xC79255821DA1edf8E1a8870ED5cED9099bf2eAAA"
5+
const STREAM_EXCHANGE_HELPER_ADDRESS = "0x0C7776292AB9E95c54282fD74e47d73338c457D8"
6+
const RICOCHET_CONTRACT_ADDRESS = "0x387af38C133056a0744FB6e823CdB459AE3c5a1f"
7+
8+
const StreamExchangeHelper = await ethers.getContractFactory("StreamExchangeHelper")
9+
const seh = await StreamExchangeHelper.attach(STREAM_EXCHANGE_HELPER_ADDRESS)
10+
11+
const StreamExchange = await ethers.getContractFactory("StreamExchange", {
12+
libraries: {
13+
StreamExchangeHelper: seh.address,
14+
},
15+
});
16+
const rickoshea = await StreamExchange.attach(RICOCHET_CONTRACT_ADDRESS)
17+
18+
console.log("getTellorOracle", await rickoshea.getTellorOracle())
19+
console.log("setOracle", TELLOR_CONTRACT_ADDRESS, await rickoshea.setOracle(TELLOR_CONTRACT_ADDRESS))
20+
// console.log("getTellorOracle", await rickoshea.getTellorOracle())
21+
22+
}
23+
24+
main()
25+
.then(() => process.exit(0))
26+
.catch(error => {
27+
console.error(error);
28+
process.exit(1);
29+
});

0 commit comments

Comments
 (0)