-
Notifications
You must be signed in to change notification settings - Fork 0
/
control.ride
385 lines (328 loc) · 19 KB
/
control.ride
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
{-# STDLIB_VERSION 4 #-}
{-# CONTENT_TYPE DAPP #-}
{-# SCRIPT_TYPE ACCOUNT #-}
#
# Smart Contract for Price Oracles and Emergency Oracles of Defo Neutrino Protocol
#
#-------------------Constants---------------------------
#-------------------Constructor-------------------------
#-------------------Keys---------------------------------
let SEP = "__"
let minPricesCount = 3
let percentPriceOffset = 5
func keyFactory() = {"%s__factory"}
func keyPrice(currencyCode: String) = "%s%s__price__" + currencyCode
func keyPriceFailure(currencyCode: String) = makeString(["%s%s%d__finalizationFailure", currencyCode, height.toString()], SEP)
func keyLastHeight(currencyCode: String) = "%s%s__lastHeight__" + currencyCode
func keyPriceByHeight(currencyCode: String, h: Int) = makeString(["%s%s%d__priceByHeight", currencyCode, h.toString()], SEP)
func keyIdx(currencyCode: String) = "%s%s__idxCurrent__" + currencyCode
func keyIdx2Height(currencyCode: String, idx: Int) = makeString(["%s%s%d__idx2Height", currencyCode, idx.toString()], SEP)
func keyHeight2Idx(currencyCode: String, h: Int) = makeString(["%s%s%d__height2Idx", currencyCode, h.toString()], SEP)
func keyGroupFinStatus(groupNum: String, finHeightStr: String) = makeString(["%s%d%d__groupFinStatus", groupNum, finHeightStr], SEP)
func keyIsMarketOpened(currencyCode: String) = "%s%s__isMarketOpened__" + currencyCode
func keyGroupData(groupNum: String) = "%s%d__group__" + groupNum
func keyEmptyPriceMsg(position: Int) = makeString(["%s%d%d__emptyPriceMsg", height.toString(), position.toString()], SEP)
func keyIsBlocked() = "%s__isBlocked"
func keyIsBlockedSender() = "%s%s__isBlocked__sender"
func keyIsBlockedReason() = "%s%s__isBlocked__reason"
func keyOracles() = "%s%s__config__oracles"
func keyBlackSwarmPrice(h: Int) = "%s%s%d__isBlocked__blackSwarmPrice__" + h.toString()
### Factory API - legacy implenetation
# @Legacy
let factoryAcc = this.getString(keyFactory()).valueOrErrorMessage("No config at this=" + this.toString() + " for key=" + keyFactory())
.addressFromStringValue()
# @Legacy
func keyFactoryAssetCfg(assetAddressStr: String) = {"%s%s%s__defoAsset__" + assetAddressStr + "__config"}
# @Legacy
func keyFactoryDefoAddressByAssetCode(assetCode: String) = {"%s%s%s__defoAsset__" + assetCode + "__addressByAssetCode"}
let IdxDefoAssetCode = 1
let IdxDefoAssetId = 2
let IdxDefoAssetStatus = 3
let IdxPriceDecimals = 4
let IdxBaseAssetId = 5
let IdxOverCollateralPercent = 6
let IdxMinInitPool = 7
let IdxPriceOracleAddress = 8
let IdxMinBuyPayment = 9
let IdxMinSellPayment = 10
let IdxBuyLockInterval = 11
let IdxSellLockInterval = 12
let IdxBuyFeePercent = 13
let IdxSellFeePercent = 14
# @Legacy
func factoryReadAssetCfgByAddress(assetAddressStr: String) = {
factoryAcc.getString(keyFactoryAssetCfg(assetAddressStr))
.valueOrErrorMessage("No config at factory=" + factoryAcc.toString() + " for key=" + keyFactoryAssetCfg(assetAddressStr))
.split("__")
}
# @Legacy
func factoryReadAssetCfgByCode(assetCode: String) = {
let assetAddressStr = factoryAcc.getString(keyFactoryDefoAddressByAssetCode(assetCode))
.valueOrErrorMessage("No config at factory=" + factoryAcc.toString() + " for key=" + keyFactoryDefoAddressByAssetCode(assetCode))
(assetAddressStr, factoryReadAssetCfgByAddress(assetAddressStr))
}
# @Legacy
func formattingPriceMsg(currencyCode: String, price: Int) = {
toBytes("WAVESDEFOPREFIX_" + currencyCode + "_" + toString(height) + "_" + toString(price))
}
# @Legacy
func findPricesInRange(prices: List[Int]) = {
let minPercentBound = 90
let maxPercentBound = 110
let p0 = prices[0]
let check0 = if (prices[0] <= 0) then [0] else {
let p01 = prices[1] * 100 / p0
let p02 = prices[2] * 100 / p0
let p03 = prices[3] * 100 / p0
let p04 = prices[4] * 100 / p0
let array1 = if(p01 < maxPercentBound && p01 > minPercentBound) then 1::[0] else [0]
let array2 = if(p02 < maxPercentBound && p02 > minPercentBound) then 2::array1 else array1
let array3 = if(p03 < maxPercentBound && p03 > minPercentBound) then 3::array2 else array2
if(p04 < maxPercentBound && p04 > minPercentBound) then 4::array3 else array3
}
let check1 = if (check0.size() >= minPricesCount) then check0 else {
let p1 = prices[1]
if (p1 <= 0) then [1] else {
let p10 = prices[0] * 100 / p1
let p12 = prices[2] * 100 / p1
let p13 = prices[3] * 100 / p1
let p14 = prices[4] * 100 / p1
let array1 = if(p10 < maxPercentBound && p10 > minPercentBound) then 0::[1] else [1]
let array2 = if(p12 < maxPercentBound && p12 > minPercentBound) then 2::array1 else array1
let array3 = if(p13 < maxPercentBound && p13 > minPercentBound) then 3::array2 else array2
if(p14 < maxPercentBound && p14 > minPercentBound) then 4::array3 else array3
}
}
let check2 = if (check1.size() >= minPricesCount) then check1 else {
let p2 = prices[2]
if (p2 <= 0) then [2] else {
let p20 = prices[0] * 100 / p2
let p21 = prices[1] * 100 / p2
let p23 = prices[3] * 100 / p2
let p24 = prices[4] * 100 / p2
let array1 = if (p20 < maxPercentBound && p20 > minPercentBound) then 0::[2] else [2]
let array2 = if(p21 < maxPercentBound && p21 > minPercentBound) then 1::array1 else array1
let array3 = if(p23 < maxPercentBound && p23 > minPercentBound) then 3::array2 else array2
if(p24 < maxPercentBound && p24 > minPercentBound) then 4::array3 else array3
}
}
let check3 = if (check2.size() >= minPricesCount) then check2 else {
let p3 = prices[3]
if (p3 <= 0) then [3] else {
let p30 = prices[0] * 100 / p3
let p31 = prices[1] * 100 / p3
let p32 = prices[2] * 100 / p3
let p34 = prices[4] * 100 / p3
let array1 = if (p30 < maxPercentBound && p30 > minPercentBound) then 0::[3] else [3]
let array2 = if(p31 < maxPercentBound && p31 > minPercentBound) then 1::array1 else array1
let array3 = if(p32 < maxPercentBound && p32 > minPercentBound) then 2::array2 else array2
if(p34 < maxPercentBound && p34 > minPercentBound) then 4::array3 else array3
}
}
if (check3.size() >= minPricesCount) then check3 else {
let p4 = prices[4]
if (p4 <= 0) then [4] else {
let p40 = prices[0] * 100 / p4
let p41 = prices[1] * 100 / p4
let p42 = prices[2] * 100 / p4
let p43 = prices[3] * 100 / p4
let array1 = if (p40 < maxPercentBound && p40 > minPercentBound) then 0::[4] else [4]
let array2 = if(p41 < maxPercentBound && p41 > minPercentBound) then 1::array1 else array1
let array3 = if(p42 < maxPercentBound && p42 > minPercentBound) then 2::array2 else array2
if(p43 < maxPercentBound && p43 > minPercentBound) then 3::array3 else array3
}
}
}
# @Legacy
func readPriceByHeight(currencyCode: String, h: Int) = {
this.getInteger(keyPriceByHeight(currencyCode, h)).valueOrElse(0)
}
### this API
func isGroupFinalaized(groupNum: String, finHeightStr: String) = {this.getBoolean(keyGroupFinStatus(groupNum, finHeightStr)).isDefined()}
# just to debug different complexities
func readGroupDataOrFail(groupNum: String) = {
let k = keyGroupData(groupNum)
let groupDataOpt = this.getString(k)
if (groupDataOpt.isDefined()) then groupDataOpt.value() else throw("empty group data for key=" + k)
}
#-------------------Global vars-------------------------
let isBlocked = this.getBoolean(keyIsBlocked()).valueOrElse(false)
let pubKeyOraclesList = this.getStringValue(keyOracles()).split(",")
let oracleCount = pubKeyOraclesList.size()
#-------------------Failures----------------------------
func throwInvalidFinalizationHeight(finHeightStr: String) = {
throw("invalid finalization height: height=" + height.toString() + " finalizationHeight=" + finHeightStr)
}
func throwGroupAlreadyFinalized(groupNum: String, finHeightStr: String) = {
throw("prices for groupNum=" + groupNum + " at " + finHeightStr + " height have been already finalized")
}
func throwInvalidSignsParamLength(num: Int, param: ByteVector) = {
throw("invalid signs" + num.toString() + " parameter: actual.size=" + param.size().toString() + " base58Val=" + toBase58String(param))
}
func throwOutOfTurnFinalization() = {
throw("Out of turn finalization: " + height.toString() + " block should be finalize by " + pubKeyOraclesList[height % oracleCount])
}
func throwBlockedError() = {
throw("contract is blocked by EMERGENCY SHUTDOWN action")
}
func finalizePriceV2Common(position: Int, groupNum: String, groupDataStr: String, msgArray: List[String], signs: ByteVector) = {
# msgArray[0] - data0 - curency code
# msgArray[1] - data0 - price
# msgArray[2] - data0 - isMarketOpened
# size=3
# --------------------------------
# msgArray[3] - data1 -
# msgArray[4] - data1 -
# msgArray[5] - data1 -
# size=6
# --------------------------------
let msgOffset = position * 3
if (msgOffset >= msgArray.size()) then [StringEntry(keyEmptyPriceMsg(position), "price data is empty")] else
let currencyCode = msgArray[msgOffset+0]
let newPriceStr = msgArray[msgOffset+1]
let isMarketOpenedStr = msgArray[msgOffset+2]
let newPriceOpt = newPriceStr.parseInt()
let isMarketOpenedOpt = isMarketOpenedStr.parseInt()
# 20 20 20 20 20
# if num == 0 then drop 0 or 0*20
# if num == 0 then drop 20 or 1*20
# if num == 0 then drop 40 or 2*20
# if num == 0 then drop 60 or 3*20
# if num == 0 then drop 80 or 4*20
let sig0 = signs.take(64)
let sig1 = signs.drop(64).take(64)
let sig2 = signs.drop(128).take(64)
let sig3 = signs.drop(192).take(64)
let sig4 = signs.takeRight(64)
if (groupDataStr.contains(currencyCode)) then {
if (newPriceOpt.isDefined() && isMarketOpenedOpt.isDefined()) then {
let newPrice = newPriceOpt.value()
let isMarketOpened = if (isMarketOpenedOpt.value() == 1) then true else false
let priceMsgStr = makeString(["WAVES:DEFO:PROTOCOL:PRICE:V002", groupNum, height.toString(), currencyCode, newPriceStr, isMarketOpenedStr], "_")
let priceMsg = priceMsgStr.toBytes()
let verificationsCount =
(if (sigVerify_8Kb(priceMsg, sig0, fromBase58String(pubKeyOraclesList[0]))) then 1 else 0)
+ (if (sigVerify_8Kb(priceMsg, sig1, fromBase58String(pubKeyOraclesList[1]))) then 1 else 0)
+ (if (sigVerify_8Kb(priceMsg, sig2, fromBase58String(pubKeyOraclesList[2]))) then 1 else 0)
+ (if (sigVerify_8Kb(priceMsg, sig3, fromBase58String(pubKeyOraclesList[3]))) then 1 else 0)
+ (if (sigVerify_8Kb(priceMsg, sig4, fromBase58String(pubKeyOraclesList[4]))) then 1 else 0)
if (verificationsCount >= minPricesCount) then {
let price = this.getInteger(keyPrice(currencyCode)).valueOrElse(0)
let idx = this.getInteger(keyIdx(currencyCode)).valueOrElse(0)
if(price != 0 && (newPrice >= (price + price*percentPriceOffset/100) || newPrice <= (price - price*percentPriceOffset/100))) then {
let reason = "automatic emergency shutdown because of large price variability"
[BooleanEntry(keyIsBlocked(), true),
StringEntry(keyIsBlockedSender(), toString(this)),
StringEntry(keyIsBlockedReason(), reason),
IntegerEntry(keyBlackSwarmPrice(height), newPrice)]
} else {
let newIdx = idx + 1
[IntegerEntry(keyPrice(currencyCode), newPrice),
IntegerEntry(keyLastHeight(currencyCode), height),
IntegerEntry(keyPriceByHeight(currencyCode, height), newPrice),
IntegerEntry(keyIdx(currencyCode), newIdx),
IntegerEntry(keyIdx2Height(currencyCode, newIdx), height),
IntegerEntry(keyHeight2Idx(currencyCode, height), newIdx),
BooleanEntry(keyIsMarketOpened(currencyCode), isMarketOpened)]
}
} else [StringEntry(keyPriceFailure(currencyCode),
"verificationsCount = " + verificationsCount.toString()
+ "signs0.length=" + signs.size().toString()
+ " msg0 = " + priceMsgStr
+ " sig0 = " + toBase58String(sig0)
+ " key0 = " + pubKeyOraclesList[0])]
} else [StringEntry(keyPriceFailure(currencyCode),
"data parsing error: newPrice=" + newPriceStr + " isMarketOpened=" + isMarketOpenedStr)]
} else [StringEntry(keyPriceFailure(currencyCode),
currencyCode + " doesn't exist in group: groupDataStr=" + groupDataStr)]
}
# TODO New currency can be registered via special invocation call
# @header
# ${prefix}_${groupNum}_${height}
# @msg
# ${curCode0}_${price0}_${isMarketOpened0}_${curCode1}_${price1}_${isMarketOpened1}... up to 4
# @signs0
# ${sig0}${sig1}${sig2}${sig3} - signs vector for different prices which are provided by single price oracle
@Callable(i)
func finalizeCurrentPriceV2(header: String, msg: String, signs0: ByteVector, signs1: ByteVector, signs2: ByteVector, signs3: ByteVector) = {
let headerArray = header.split("_")
let groupNum = headerArray[1]
let finHeightStr = headerArray[2]
let finHeight = finHeightStr.parseIntValue()
let signsLength = 64*oracleCount
if (isBlocked) then throwBlockedError() else
if (height != finHeight) then throwInvalidFinalizationHeight(finHeightStr) else
if (isGroupFinalaized(groupNum, finHeightStr)) then throwGroupAlreadyFinalized(groupNum, finHeightStr) else
if (pubKeyOraclesList[height % oracleCount] != i.callerPublicKey.toBase58String()) then throwOutOfTurnFinalization() else
if (signs0.size() != signsLength) then throwInvalidSignsParamLength(0, signs0) else
if (signs1.size() != signsLength) then throwInvalidSignsParamLength(1, signs1) else
if (signs2.size() != signsLength) then throwInvalidSignsParamLength(2, signs2) else
if (signs3.size() != signsLength) then throwInvalidSignsParamLength(3, signs3) else
#let groupDataStr = this.getStringValue(keyGroupData(groupNum))
#let groupDataStr = readGroupDataOrFail(groupNum)
let groupDataKey = keyGroupData(groupNum)
let groupDataStr = this.getString(groupDataKey).valueOrErrorMessage("empty group data for key=" + groupDataKey)
let msgArray = msg.split("_")
if (msgArray.size() % 3 != 0) then throw("msg parameters count must be multiple of 3: msgArray.size=" + msg.size().toString()) else
finalizePriceV2Common(0, groupNum, groupDataStr, msgArray, signs0)
++finalizePriceV2Common(1, groupNum, groupDataStr, msgArray, signs1)
++finalizePriceV2Common(2, groupNum, groupDataStr, msgArray, signs2)
++finalizePriceV2Common(3, groupNum, groupDataStr, msgArray, signs3)
:+BooleanEntry(keyGroupFinStatus(groupNum, finHeightStr), true)
}
# @Legacy
@Callable(i)
func finalizeCurrentPrice(currencyCode: String, price1: Int, sign1: ByteVector, price2: Int, sign2: ByteVector, price3: Int, sign3: ByteVector, price4: Int, sign4: ByteVector, price5: Int, sign5: ByteVector) = {
let price = this.getInteger(keyPrice(currencyCode)).valueOrElse(0)
let idx = this.getInteger(keyIdx(currencyCode)).valueOrElse(0)
let currencyCfg = factoryReadAssetCfgByCode(currencyCode)._2
if (isBlocked) then throw("contract is blocked by EMERGENCY SHUTDOWN actions untill reactivation by emergency oracles") else
if (currencyCfg[IdxDefoAssetStatus] != "ISSUED") then throw(currencyCode + " has not been issued yet: status=" + currencyCfg[IdxDefoAssetStatus]) else
if (readPriceByHeight(currencyCode, height) != 0) then throw("wait next block") else
if (pubKeyOraclesList[height % 5] != toBase58String(i.callerPublicKey)) then throw("Out of turn finalization: "
+ toString(height) + " block should be finalize by " + pubKeyOraclesList[height % 5]) else
let prices = [
if sigVerify_8Kb(formattingPriceMsg(currencyCode, price1), sign1, fromBase58String(pubKeyOraclesList[0])) then price1 else 0,
if sigVerify_8Kb(formattingPriceMsg(currencyCode, price2), sign2, fromBase58String(pubKeyOraclesList[1])) then price2 else 0,
if sigVerify_8Kb(formattingPriceMsg(currencyCode, price3), sign3, fromBase58String(pubKeyOraclesList[2])) then price3 else 0,
if sigVerify_8Kb(formattingPriceMsg(currencyCode, price4), sign4, fromBase58String(pubKeyOraclesList[3])) then price4 else 0,
if sigVerify_8Kb(formattingPriceMsg(currencyCode, price5), sign5, fromBase58String(pubKeyOraclesList[4])) then price5 else 0
]
let zeroPriceCount = (if (prices[0] == 0) then 1 else 0)
+ (if (prices[1] == 0) then 1 else 0)
+ (if (prices[2] == 0) then 1 else 0)
+ (if (prices[3] == 0) then 1 else 0)
+ (if (prices[4] == 0) then 1 else 0)
if (zeroPriceCount >= minPricesCount) then throw(minPricesCount.toString() + " prices or more are equals to 0") else
let pricesInRange = findPricesInRange(prices)
let priceProvidingCount = pricesInRange.size();
if (priceProvidingCount < minPricesCount) then throw("Could not finalize price because of big variation: height=" + toString(height)
+ "\n" + pubKeyOraclesList[0] + "=" + toString(prices[0])
+ "\n" + pubKeyOraclesList[1] + "=" + toString(prices[1])
+ "\n" + pubKeyOraclesList[2] + "=" + toString(prices[2])
+ "\n" + pubKeyOraclesList[3] + "=" + toString(prices[3])
+ "\n" + pubKeyOraclesList[4] + "=" + toString(prices[4])) else
let sum1 = prices[pricesInRange[0]] + prices[pricesInRange[1]] + prices[pricesInRange[2]]
let sum2 = if (priceProvidingCount >= 4) then sum1 + prices[pricesInRange[3]] else sum1
let priceSum = if (priceProvidingCount >= 5) then sum2 + prices[pricesInRange[4]] else sum2
if (priceProvidingCount >= 6) then throw("Invalid pricesInRange creation") else
let newPrice = priceSum/priceProvidingCount
if(price != 0 && (newPrice >= (price + price*percentPriceOffset/100) || newPrice <= (price - price*percentPriceOffset/100))) then {
let reason = "automatic emergency shutdown because of large price variability"
[BooleanEntry(keyIsBlocked(), true),
StringEntry(keyIsBlockedSender(), toString(this)),
StringEntry(keyIsBlockedReason(), reason),
IntegerEntry(keyBlackSwarmPrice(height), newPrice)]
} else {
let newIdx = idx + 1
[IntegerEntry(keyPrice(currencyCode), newPrice),
IntegerEntry(keyLastHeight(currencyCode), height),
IntegerEntry(keyPriceByHeight(currencyCode, height), newPrice),
IntegerEntry(keyIdx(currencyCode), newIdx),
IntegerEntry(keyIdx2Height(currencyCode, newIdx), height),
IntegerEntry(keyHeight2Idx(currencyCode, height), newIdx)
]
}
}
@Verifier(tx)
func verify() = sigVerify_128Kb(tx.bodyBytes, tx.proofs[0], fromBase58String("Ce5zjCMEm3ZxvKznGGm8BhwaDiRNd8kADwMNgriVg4tD"))