-
Notifications
You must be signed in to change notification settings - Fork 0
/
factory.ride
578 lines (503 loc) · 33.4 KB
/
factory.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
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
{-# STDLIB_VERSION 4 #-}
{-# CONTENT_TYPE DAPP #-}
{-# SCRIPT_TYPE ACCOUNT #-}
#-------------------Private functions----------------------
func getBoolByKey(key: String) = {
getBoolean(this, key).valueOrElse(false)
}
let SEP = "__"
#------------------ Variables -----------------------
let keyAssetCode = 1
let keyAssetId = 2
let keyAssetStatus = 3
let keyAssetPriceDecimals = 4
let keyAssetBakingAsset = 5
let keyOvercollateralPercentage = 6
let keyMinPool = 7
let keyPriceOracleAddress = 8
let keyMinBuyPayment = 9
let keyMinSellPayment = 10
let keyBuyLockInterval = 11
let keySellLockInterval = 12
let keyBuyFee = 13
let keySellFee = 14
let keyPoolRedemptionTimeout = 15
let keyWeekendsPriceChangeCoefficient = 16
let keyAssetAccountPublicStr = 17
let keyAssetStakingReferralPublicStr = 18
let keyCrossExchangeCoefficient = 19
let keyStakingMinAmtFromSellCoefficient = 20
let keyPoolStabilityFee = 21
let keyProtocolFee = 22
let adminPublicKey = "$ADMINPK"
#------------------- Constructor -------------------------
let neutrinoContractAddressKey = "%s%s__commonConfig__neutrinoContractAddress"
let neutrinoStakingAddressKey = "%s%s__commonConfig__neutrinoStakingAddress"
let minimunPoolFundingAmountKey = "%s%s__commonConfig__minimumPoolFundingAmount"
let isInitializedKey = "%s%s__commonConfig__initialized"
let debtTokenIdKey = "%s%s__commonConfig__debtAssetId"
let debtTokenEtalonBalanceKey = "%s%s__commonConfig__debtAssetEtalonBalance"
let defoStakingAddressKey = "%s%s__commonConfig__defoStakingAddress"
let defoStakingPacemakerPubKey = "%s%s__commonConfig__defoStakingPacemakerPub"
#------------------- Keys on other contracts ---------------------------------
let neutrinoAssetIdKey = "neutrino_asset_id" # from Neutrino contract
let nsbtAssetIdKey = "bond_asset_id" # from Neutrino contract
let neutrinoRpdKey = "rpd_contract" # from Neutrino contract
let defoAssetIdKey = "%s__defoAssetId" # key on synthetic asset contract that represents issued asset id
let setAssetScriptTxIdKey = "%s__assetScriptTxId" # key on synthetic asset contract that represents issued asset transaction id
#---------------------- Keys on current contract --------------------------
# main config of an asset
func getAssetConfigKey(assetAddress: String) = "%s%s%s__defoAsset__" + assetAddress + "__config"
let IdxPreInitializedAssetPublic = 0
let IdxPreInitializedAssetStakingReferralPublic = 1
let IdxPreInitializedAssetDescription = 2
func getPreInitializedAssetDataKey(assetCode: String) = "%s%s__preInitializedAssetData__" + assetCode
# dynamic asset parameters
func getAssetPriceKey(assetCode: String) = "%s%s__price__" + assetCode
func getAssetInitHeightKey(assetAddress: String) = "%s%s%s__defoAsset__" + assetAddress + "__initHeight"
func getAssetActivateHeightKey(assetAddress: String) = "%s%s%s__defoAsset__" + assetAddress + "__activateHeight"
func getAssetCurrrentPoolAmountKey(assetAddress: String) = "%s%s%s__defoAsset__" + assetAddress + "__currentPool"
func getAssetMaxPoolAmountKey(assetAddress: String) = "%s%s%s__defoAsset__" + assetAddress + "__maxPool"
func getAssetMetaKey(assetAddress: String) = "%s%s%s__defoAsset__" + assetAddress + "__meta"
func getAssetAddressByCodeKey(assetCode: String) = "%s%s%s__defoAsset__" + assetCode + "__addressByAssetCode"
func getAssetAddressByAssetIdKey(assetId: String) = "%s%s%s__defoAsset__" + assetId + "__addressByAssetId" # key on factory contract that links issued asset id with synthetic asset contract
func getPoolMakerParticipationAmountKey(assetAddress: String, poolMakerAddress: String) = makeString(["%s%s%s%s__pool", assetAddress, poolMakerAddress, "amount"], SEP)
func getPoolMakerParticipationMaxAmountKey(assetAddress: String, poolMakerAddress: String) = makeString(["%s%s%s%s__pool", assetAddress, poolMakerAddress, "maxAmount"], SEP)
func getPoolMakerLockedAmountKey(assetAddress: String, poolMakerAddress: String) = makeString(["%s%s%s%s__pool", assetAddress, poolMakerAddress, "lockedAmount"], SEP)
func getPoolMakerUnlockHeightKey(assetAddress: String, poolMakerAddress: String) = makeString(["%s%s%s%s__pool", assetAddress, poolMakerAddress, "unlockHeight"], SEP)
func getPoolMakerLiquidityRequestKey(assetAddress: String, poolMakerAddress: String, h: Int) = makeString(["%s%s%s%d%s__pool", assetAddress, poolMakerAddress, h.toString(), "liquidityRequest"], SEP)
func getPoolTotalLockedAmountKey(assetAddress: String) = makeString(["%s%s%s__pool", assetAddress, "totalLockedAmount"], SEP)
func getPoolMinInvestAmountKey(assetAddress: String) = makeString(["%s%s%s__pool", assetAddress, "minInvestAmount"], SEP)
func getPoolMinRedeemAmountKey(assetAddress: String) = makeString(["%s%s%s__pool", assetAddress, "minRedeemAmount"], SEP)
func getAssetConfigArray(assetAddress: String) = this.getString(getAssetConfigKey(assetAddress))
.valueOrErrorMessage("No DEFO Asset config present for " + assetAddress)
.split(SEP)
#-------------------Global variables-------------------------
let multiplicator = 1000000
let neutrinoContract = addressFromStringValue(getString(this,neutrinoContractAddressKey).valueOrErrorMessage("No neutrino contract specified."))
let neutrinoAssetIdStr = getString(neutrinoContract, neutrinoAssetIdKey).valueOrErrorMessage("No USDN asset id found.")
let neutrinoAssetId = fromBase58String(neutrinoAssetIdStr)
let nsbtAssetId = fromBase58String(getString(neutrinoContract, nsbtAssetIdKey).valueOrErrorMessage("No NSBT asset id found"))
let minimumPoolFunding = getInteger(this, minimunPoolFundingAmountKey).valueOrErrorMessage("Minimum pool funding amount is not specified.")
let ASSEtPROPOSED = "PROPOSED"
let ASSEtREADY = "READY"
let ASSEtISSUED = "ISSUED"
let assetStatus = (ASSEtPROPOSED,ASSEtREADY,ASSEtISSUED)
let debtToken = fromBase58String(getString(this,debtTokenIdKey).valueOrErrorMessage("No debt token found"))
let debtTokenReissueAmount = getInteger(this, debtTokenEtalonBalanceKey).valueOrElse(0)
#-------------------Neutrino RPD API-----------------------
let neutrinoRpdContract = neutrinoContract.getStringValue(neutrinoRpdKey).addressFromStringValue()
func getNeutrinoRpdBalance(userAddress: String) = {
let userNeutrinoRpdBalance = makeString(["rpd_balance", neutrinoAssetIdStr, userAddress], "_")
neutrinoRpdContract.getInteger(userNeutrinoRpdBalance).valueOrElse(0)
}
#-------------------Get functions-----------------------
func getAssetAddressByCode(assetSymbol: String) = getString(this, getAssetAddressByCodeKey(assetSymbol)).valueOrElse("")
func getAssetAddressByCodeOrFail(assetSymbol: String) = getString(this, getAssetAddressByCodeKey(assetSymbol)).valueOrErrorMessage("Cannot find asset address by " + assetSymbol + " code")
func getAssetCode(assetConfig: List[String]) = assetConfig[keyAssetCode]
func getAssetID(assetConfig: List[String]) = assetConfig[keyAssetId]
func getAssetStatus(assetConfig: List[String]) = assetConfig[keyAssetStatus]
func getAssetPriceDecimals(assetConfig: List[String]) = parseIntValue(assetConfig[keyAssetPriceDecimals])
func getAssetBakingAssetId(assetConfig: List[String]) = assetConfig[keyAssetBakingAsset]
func getAssetOverCollateralPercent(assetConfig: List[String]) = parseIntValue(assetConfig[keyOvercollateralPercentage])
func getAssetMinRequiredPoolAmount(assetConfig: List[String]) = parseIntValue(assetConfig[keyMinPool])
func getAssetPriceOracleAddress(assetConfig: List[String]) = assetConfig[keyPriceOracleAddress]
func getMinBuyPaymentPerAsset(assetConfig: List[String]) = parseIntValue(assetConfig[keyMinBuyPayment]) # in usdn
func getMinSellPaymentPerAsset(assetConfig: List[String]) = parseIntValue(assetConfig[keyMinSellPayment])
func getBuyLockIntervalPerAsset(assetConfig: List[String]) = parseIntValue(assetConfig[keyBuyLockInterval])
func getSellLockIntervalPerAsset(assetConfig: List[String]) = parseIntValue(assetConfig[keySellLockInterval])
func getBuyFeePerAsset(assetConfig: List[String]) = parseIntValue(assetConfig[keyBuyFee])
func getSellFeePerAsset(assetConfig: List[String]) = parseIntValue(assetConfig[keySellFee])
func getRedemptionTimeoutBlocks(assetConfig: List[String]) = parseIntValue(assetConfig[keyPoolRedemptionTimeout])
func getWeekendsPriceChangeCoefficient(assetConfig: List[String]) = parseIntValue(assetConfig[keyWeekendsPriceChangeCoefficient])
func getProtocolFeePerAsset(assetConfig: List[String]) = parseIntValue(assetConfig[keyProtocolFee])
func getAssetInitHeight(assetAddress: String) = getInteger(this,getAssetInitHeightKey(assetAddress)).valueOrElse(0)
func getAssetActivateHeight(assetAddress: String) = getInteger(this,getAssetActivateHeightKey(assetAddress)).valueOrElse(0)
func getAssetCurrentPoolAmount(assetAddress: String) = getInteger(this,getAssetCurrrentPoolAmountKey(assetAddress)).valueOrElse(0)
func getAssetMaxPoolAmount(assetAddress: String) = getInteger(this,getAssetMaxPoolAmountKey(assetAddress)).valueOrElse(0)
func getAssetMeta(assetAddress: String) = getString(this,getAssetMetaKey(assetAddress)).valueOrElse("")
func getPoolMakerParticipationAmount(assetAddress: String, poolMakerAddress: String) = this.getInteger(getPoolMakerParticipationAmountKey(assetAddress, poolMakerAddress)).valueOrElse(0)
func getPoolMakerParticipationMaxAmount(assetAddress: String, poolMakerAddress: String) = this.getInteger(getPoolMakerParticipationMaxAmountKey(assetAddress, poolMakerAddress)).valueOrElse(0)
func getPoolMakerLockedAmount(assetAddress: String, poolMakerAddress: String) = this.getInteger(getPoolMakerLockedAmountKey(assetAddress, poolMakerAddress)).valueOrElse(0)
func getPoolMakerUnlockHeight(assetAddress: String, poolMakerAddress: String) = this.getInteger(getPoolMakerUnlockHeightKey(assetAddress, poolMakerAddress)).valueOrElse(0)
func getAssetPrice(priceOracleAddress: Address, assetCode: String) = priceOracleAddress.getInteger(getAssetPriceKey(assetCode)).valueOrErrorMessage("No Asset price found")
func getPoolTotalLockedAmount(assetAddress: String) = this.getInteger(getPoolTotalLockedAmountKey(assetAddress)).valueOrElse(0)
func composeAssetConfigurationString(assetCode: String, assetId: String, assetStatus: String, priceDecimals: Int, backingAssetId: String, overCollateralPercent: Int, minPool: Int, priceOracleAddress: String,
minBuyPayment: Int, minSellPayment: Int, buyLockInterval: Int, sellLockInterval: Int, buyFee: Int, sellFee: Int, poolRedemptionTimeout: Int,
weekendsPriceChangeCoefficient: Int, assetAccountPublicStr: String, assetStakingReferralPublicStr: String,
crossExchangeCoefficientStr: String, stakingMinAmtFromSellCoefficientStr: String, poolStabilityFeeStr: String, protocolFeeStr: String) = {
makeString([
"%s%s%s%d%s%d%d%s%d%d%d%d%d%d%d%d%s%s%d%d%d",
assetCode ,
assetId ,
assetStatus ,
toString(priceDecimals) ,
backingAssetId ,
toString(overCollateralPercent) ,
toString(minPool) ,
priceOracleAddress ,
toString(minBuyPayment) ,
toString(minSellPayment) ,
toString(buyLockInterval) ,
toString(sellLockInterval) ,
toString(buyFee) ,
toString(sellFee) ,
toString(poolRedemptionTimeout) ,
toString(weekendsPriceChangeCoefficient),
assetAccountPublicStr,
assetStakingReferralPublicStr,
crossExchangeCoefficientStr,
stakingMinAmtFromSellCoefficientStr,
poolStabilityFeeStr,
protocolFeeStr], "__")
}
func updateAssetCfgStatus(newStatus: String, assetConfigurationList: List[String]) = {
let aCode = getAssetCode(assetConfigurationList)
let aID = getAssetID(assetConfigurationList)
let aPriceDecimals = getAssetPriceDecimals(assetConfigurationList)
let aBakingAssetId = getAssetBakingAssetId(assetConfigurationList)
let aOverCollateralPercent = getAssetOverCollateralPercent(assetConfigurationList)
let aMinRequiredPool = getAssetMinRequiredPoolAmount(assetConfigurationList)
let aPriceOracleAddress = getAssetPriceOracleAddress(assetConfigurationList)
let aMinBuyPayment = getMinBuyPaymentPerAsset(assetConfigurationList)
let aMinSellPayment = getMinSellPaymentPerAsset(assetConfigurationList)
let aBuyLockInterval = getBuyLockIntervalPerAsset(assetConfigurationList)
let aSellLockInterval = getSellLockIntervalPerAsset(assetConfigurationList)
let aBuyFee = getBuyFeePerAsset(assetConfigurationList)
let aSellFee = getSellFeePerAsset(assetConfigurationList)
let aRedemptionTimeoutBlocks = getRedemptionTimeoutBlocks(assetConfigurationList)
let weekendsPriceChangeCoefficient = getWeekendsPriceChangeCoefficient(assetConfigurationList)
composeAssetConfigurationString(
aCode,
aID,
newStatus,
aPriceDecimals,
aBakingAssetId,
aOverCollateralPercent,
aMinRequiredPool,
aPriceOracleAddress,
aMinBuyPayment,
aMinSellPayment,
aBuyLockInterval,
aSellLockInterval,
aBuyFee,
aSellFee,
aRedemptionTimeoutBlocks,
weekendsPriceChangeCoefficient,
assetConfigurationList[keyAssetAccountPublicStr],
assetConfigurationList[keyAssetStakingReferralPublicStr],
assetConfigurationList[keyCrossExchangeCoefficient],
assetConfigurationList[keyStakingMinAmtFromSellCoefficient],
assetConfigurationList[keyPoolStabilityFee],
assetConfigurationList[keyProtocolFee])
}
func calcAllowedAmountToRedeem(assetCode: String, poolMakerAddress: String) = {
let assetAddressStr = getAssetAddressByCodeOrFail(assetCode)
let assetAddress = assetAddressStr.addressFromStringValue()
let assetConfigurationList = getAssetConfigArray(assetAddressStr)
let assetIdStr = getAssetID(assetConfigurationList)
let assetId = assetIdStr.fromBase58String()
let poolActualSize = this.getInteger(getAssetCurrrentPoolAmountKey(assetAddressStr)).valueOrElse(0)
let poolMaxSize = this.getInteger(getAssetMaxPoolAmountKey(assetAddressStr)).valueOrElse(0)
let poolTotalLockedAmountKey = getPoolTotalLockedAmountKey(assetAddressStr)
let poolTotalLocked = this.getInteger(poolTotalLockedAmountKey).valueOrElse(0)
let assetUsdnStakedAmount = getNeutrinoRpdBalance(assetAddressStr)
let assetUsdnBalanceAmount = assetAddress.assetBalance(neutrinoAssetId)
# if positive then this acc is creditor
# if negative then this acc is debtor
# todo sync with asset math
let lendedOrDebtAmount = debtTokenReissueAmount - assetAddress.assetBalance(debtToken)
let assetTotalUsdnAmount = assetUsdnStakedAmount + assetUsdnBalanceAmount - poolTotalLocked + lendedOrDebtAmount
let ucollateral = assetTotalUsdnAmount - poolActualSize
let defoAssetBalance = assetAddress.assetBalance(assetId) # can be accumulated as
let assetEmissionDirty = assetInfo(assetId).valueOrErrorMessage("Cannot find asset by id " + assetIdStr).quantity
let assetEmission = assetEmissionDirty - defoAssetBalance
let priceOracleAddress = addressFromStringValue(getAssetPriceOracleAddress(assetConfigurationList))
let currentPrice = getAssetPrice(priceOracleAddress, assetCode)
let assetEmissionOvercollateralRate = fraction(multiplicator + getAssetOverCollateralPercent(assetConfigurationList), assetEmission, multiplicator)
let usdnNeededToCoverEmission = fraction(assetEmissionOvercollateralRate, multiplicator, currentPrice)
let poolMakerMaxAmount = getPoolMakerParticipationMaxAmount(assetAddressStr, poolMakerAddress)
let poolMakerParticipationAmountKey = getPoolMakerParticipationAmountKey(assetAddressStr, poolMakerAddress)
let poolMakerActualAmount = this.getInteger(poolMakerParticipationAmountKey).valueOrElse(0)
let poolMakerLockedAmount = getPoolMakerLockedAmount(assetAddressStr, poolMakerAddress)
let surplus = ucollateral - usdnNeededToCoverEmission
let percentViaActual = fraction(poolMakerActualAmount, multiplicator, poolActualSize)
let percentViaMax = fraction(poolMakerMaxAmount, multiplicator, poolMaxSize)
let frozenPart = if (surplus >= 0) then 0 else max([percentViaActual, percentViaMax])
let allowedAmountToRedeem = if (surplus >= 0)
then poolMakerActualAmount
else fraction(frozenPart, surplus, multiplicator) + poolMakerActualAmount
let withdrawHeight = height + getRedemptionTimeoutBlocks(assetConfigurationList)
# 1 2 3 4 5 6 7
(assetAddressStr, allowedAmountToRedeem, poolTotalLocked, poolActualSize, poolMakerActualAmount, withdrawHeight, poolMakerLockedAmount,
# 8
makeString([
# 0, 1, 2, 3
assetAddressStr, assetIdStr, poolMakerAddress, assetUsdnStakedAmount.toString(),
# 4, 5, 6, 7
assetUsdnBalanceAmount.toString(), assetTotalUsdnAmount.toString(), poolActualSize.toString(), poolMaxSize.toString(),
# 8, 9, 10, 11
ucollateral.toString(), assetEmission.toString(), currentPrice.toString(), assetEmissionOvercollateralRate.toString(),
# 12, 13, 14, 15
poolMakerMaxAmount.toString(), poolMakerActualAmount.toString(), poolMakerLockedAmount.toString(), surplus.toString(),
# 16, 17, 18, 19
percentViaActual.toString(), percentViaMax.toString(), frozenPart.toString(), allowedAmountToRedeem.toString()],
SEP)
)
}
@Callable(i)
func init(neutrinoContractAddress: String, neutrinoStakingAddress: String, minimunPoolFundingAmount: Int,
debtEthalonBalance: Int, defoStakingAddress: String,
defoStakingPacemakerPub: String) = {
if (toBase58String(i.callerPublicKey) != adminPublicKey) then
throw("Only Admin can perform contract operation.")
else if(getBoolByKey(isInitializedKey) == true)
then throw("Contract was already initialized.")
else {
let issueDebt = Issue("DEFO_DEBT", "DEFO Debt Token.", 1, 6, true)
let debtTokenId = calculateAssetId(issueDebt)
[
StringEntry(neutrinoContractAddressKey, neutrinoContractAddress),
StringEntry(neutrinoStakingAddressKey, neutrinoStakingAddress),
IntegerEntry(minimunPoolFundingAmountKey, minimunPoolFundingAmount),
IntegerEntry(debtTokenEtalonBalanceKey, debtEthalonBalance),
StringEntry(debtTokenIdKey, toBase58String(debtTokenId)),
StringEntry(defoStakingAddressKey, defoStakingAddress),
StringEntry(defoStakingPacemakerPubKey, defoStakingPacemakerPub),
BooleanEntry(isInitializedKey, true),
issueDebt,
Burn(debtTokenId, 1)
]
}
}
# Propose new synthetic asset
# [called by user]
# TODO: add payment to initiate pool by firts pool maker
@Callable(i)
func proposeAsset(assetCode: String, minPool: Int, priceOracleAddress: String, minSellPayment: Int) = {
let backingAssetId = neutrinoAssetIdStr
let priceDecimals = multiplicator
let buyLockInterval = 0
let sellLockInterval = 0
let minBuyPayment = 1 * multiplicator
let poolRedemptionTimeout = 0
let buyFee = 2750 # same as 0.00275 or 0.275%
let sellFee = 2750 # same as 0.002275 or 0.275%
let weekendsPriceChangeCoefficient = 15000 # same as 0.015 or 1.5%
let overCollateralPercent = 0
let crossExchangeCoefficient = 500000 # same as 0.5 TODO - save to state and migrate asset contract
let stakingMinAmtFromSellCoefficient = 1 * multiplicator
let minPoolInvestAmount = 10 * multiplicator
let minPoolRedeemAmount = 1 * multiplicator
let poolStabilityFee = 500 # same as 0.0005 or 0.05%
let protocolFee = 2750 # same as 0.00275 or 0.275%
if (toBase58String(i.callerPublicKey) != adminPublicKey) then
throw("Only Admin can perform contract operation.")
else if(getBoolByKey(isInitializedKey) == false)
then throw("Contract is not yet ready.")
else if(fromBase58String(backingAssetId) != neutrinoAssetId)
then throw("Baking asset id could be USDN only.")
else if (getAssetAddressByCode(assetCode) != "")
then throw("Asset with passed code " + assetCode + " has already been proposed.")
else if(minPool < minimumPoolFunding)
then throw("Minimum pool amount should be greater or equals to " + toString(minimumPoolFunding))
else if(overCollateralPercent < 0 || minBuyPayment < 0 || minSellPayment < 0 || buyLockInterval < 0 || sellLockInterval < 0
|| buyFee < 0 || sellFee < 0)
then throw("One of the passed parameters is invalid.")
else {
let priceOracleAddressValid = addressFromStringValue(priceOracleAddress).valueOrErrorMessage("Passed Price Oracle Address is not valid.")
let preInitDataStr = this.getString(getPreInitializedAssetDataKey(assetCode)).valueOrErrorMessage("Unsupported currency - there is no preinitialized data for " + assetCode)
let preInitDataArray = preInitDataStr.split(SEP)
let preInitAssetPublicStr = preInitDataArray[IdxPreInitializedAssetPublic]
let preInitStakingReferralPublicStr = preInitDataArray[IdxPreInitializedAssetStakingReferralPublic]
let preInitDescription = preInitDataArray[IdxPreInitializedAssetDescription]
let assetAddress = addressFromPublicKey(preInitAssetPublicStr.fromBase58String())
let assetAddressStr = assetAddress.toString()
# TODO consider to refactor and use two steps assetId generation
let defoAssetId = assetAddress.getString(defoAssetIdKey).valueOrErrorMessage("No defo asset ID found.")
[
StringEntry(
getAssetConfigKey(assetAddressStr),
composeAssetConfigurationString(
assetCode,
defoAssetId,
assetStatus._1,
priceDecimals,
backingAssetId,
overCollateralPercent,
minPool,
priceOracleAddress,
minBuyPayment,
minSellPayment,
buyLockInterval,
sellLockInterval,
buyFee,
sellFee,
poolRedemptionTimeout,
weekendsPriceChangeCoefficient,
preInitAssetPublicStr,
preInitStakingReferralPublicStr,
crossExchangeCoefficient.toString(),
stakingMinAmtFromSellCoefficient.toString(),
poolStabilityFee.toString(),
protocolFee.toString())
),
StringEntry(getAssetAddressByCodeKey(assetCode), assetAddressStr),
StringEntry(getAssetMetaKey(assetAddressStr), preInitDescription),
IntegerEntry(getAssetInitHeightKey(assetAddressStr), height),
StringEntry(getAssetAddressByAssetIdKey(defoAssetId), assetAddressStr),
IntegerEntry(getPoolMinInvestAmountKey(assetAddressStr), minPoolInvestAmount),
IntegerEntry(getPoolMinRedeemAmountKey(assetAddressStr), minPoolRedeemAmount)
]
}
}
# Fund specified synthetic asset liquidity pool
# [called by user]
@Callable(i)
func fundLiquidityPool(assetCode: String) = {
let assetAddressStr = getAssetAddressByCodeOrFail(assetCode)
let assetConfigurationList = getAssetConfigArray(assetAddressStr)
let attachedPayment = i.payments[0].value()
let poolMakerAddress = i.caller.toString()
let currentPoolMakerBalance = getPoolMakerParticipationAmount(assetAddressStr, poolMakerAddress)
let minPoolInvestAmount = this.getInteger(getPoolMinInvestAmountKey(assetAddressStr)).valueOrElse(10 * multiplicator)
if(getBoolByKey(isInitializedKey) == false) then throw("Contract is not yet ready.") else
if(attachedPayment.assetId != neutrinoAssetId) then throw("Liquidity pool could be funded with USDN only.") else
if(currentPoolMakerBalance < minPoolInvestAmount && attachedPayment.amount < minPoolInvestAmount) then throw("Attached payment amount is less tha min allowed: " + minPoolInvestAmount.toString()) else
let newCurrentPoolAmount = attachedPayment.amount + getAssetCurrentPoolAmount(assetAddressStr)
let maxPoolAmount = getAssetMaxPoolAmount(assetAddressStr)
let maxPoolMakerBalance = getPoolMakerParticipationMaxAmount(assetAddressStr, poolMakerAddress)
let currentAssetStatus = getAssetStatus(assetConfigurationList)
let newAssetStatus = if(currentAssetStatus == ASSEtPROPOSED && newCurrentPoolAmount >= getAssetMinRequiredPoolAmount(assetConfigurationList))
then ASSEtREADY
else currentAssetStatus
let newPoolMakerBalance = currentPoolMakerBalance + attachedPayment.amount
# in case synthetic asset is in ISSUED status, immediatelly transfer amount to asset contract
let immediateTransferIfIssued = if(currentAssetStatus == ASSEtISSUED)
then [ScriptTransfer(addressFromStringValue(assetAddressStr), attachedPayment.amount, neutrinoAssetId)]
else []
[
IntegerEntry(getPoolMakerParticipationAmountKey(assetAddressStr, poolMakerAddress), newPoolMakerBalance),
IntegerEntry(getPoolMakerParticipationMaxAmountKey(assetAddressStr, poolMakerAddress), max([newPoolMakerBalance, maxPoolMakerBalance])),
# check that liquidity pool size is enough to set READY status
IntegerEntry(getAssetCurrrentPoolAmountKey(assetAddressStr), newCurrentPoolAmount),
IntegerEntry(getAssetMaxPoolAmountKey(assetAddressStr), max([maxPoolAmount, newCurrentPoolAmount])),
StringEntry(getAssetConfigKey(assetAddressStr), updateAssetCfgStatus(newAssetStatus, assetConfigurationList))
] ++ immediateTransferIfIssued
}
# Issue new synthetic asset
# [called by pacemaker]
@Callable(i)
func activateAsset(assetCode: String) = {
let assetAddress = getAssetAddressByCode(assetCode)
let assetConfigurationList = getAssetConfigArray(assetAddress)
let assetCurrentPoolAmount = getAssetCurrentPoolAmount(assetAddress)
#let setAssetScriptTXID = getString(addressFromStringValue(assetAddress),setAssetScriptTxIdKey).valueOrErrorMessage("Asset is not ready to be activated. Script TX ID is absent.")
let aCurrentStatus = getAssetStatus(assetConfigurationList)
if (toBase58String(i.callerPublicKey) != adminPublicKey) then
throw("Only Admin can perform contract operation.")
else if(getBoolByKey(isInitializedKey) == false)
then throw("Contract is not yet ready.")
else if(aCurrentStatus != assetStatus._2)
then throw("Asset is not in READY status. Current status is " + aCurrentStatus + ".")
else if(assetAddress == "")
then throw("Cannot find asset " + assetCode + ".")
else if(assetCurrentPoolAmount < minimumPoolFunding)
then throw("Cannot issue asset as liquidity pool amount (" + toString(assetCurrentPoolAmount) + ") is less than required (" + toString(minimumPoolFunding) + ")")
else {
#let setAssetScriptHeight = transactionHeightById(fromBase58String(setAssetScriptTXID)).valueOrErrorMessage("Asset is not ready to be activated. Set script TX is not applied yet.")
let issueDebt = Reissue(debtToken, debtTokenReissueAmount, true)
[
IntegerEntry(getAssetActivateHeightKey(assetAddress), height),
StringEntry(getAssetConfigKey(assetAddress),
updateAssetCfgStatus(assetStatus._3, assetConfigurationList)),
# reissue new portion of Debt for new asset
issueDebt,
# transfer asset pool to asset address
ScriptTransfer(addressFromStringValue(assetAddress), assetCurrentPoolAmount, neutrinoAssetId),
# transfer debt tokens to asset address
ScriptTransfer(addressFromStringValue(assetAddress), debtTokenReissueAmount, debtToken)
]
}
}
# Create an application to redeem specified amount from pool.
# It is a delayed operation, as necessary amount have to be unstaked and further transfered from asset contract to factory contract.
# [called by pool maker]
@Callable(i)
func redeemFromPool(assetCode: String, amount: Int) = {
let callerAddress = i.caller.toString()
let redeemTuple = calcAllowedAmountToRedeem(assetCode, callerAddress)
let assetAddressStr = redeemTuple._1
let allowedAmountToRedeem = redeemTuple._2
let poolTotalLocked = redeemTuple._3
let poolActualSize = redeemTuple._4
let poolMakerActualAmount = redeemTuple._5
let withdrawHeight = redeemTuple._6
let poolMakerLockedAmount = redeemTuple._7
let traceData = redeemTuple._8
let poolMakerLiquidityRequestKey = getPoolMakerLiquidityRequestKey(assetAddressStr, callerAddress, withdrawHeight)
let minPoolRedeemAmount = this.getInteger(getPoolMinRedeemAmountKey(assetAddressStr)).valueOrElse(1 * multiplicator)
if(getBoolByKey(isInitializedKey) == false) then throw("Contract is not yet ready.") else
# validate that amount is correct and less or equals to total funds belongs to this caller and validate that caller has participated in pool
if (amount <= 0) then throw("Cannot find pool maker by address " + callerAddress + " or amount passed is not correct.") else
if (poolMakerActualAmount <= 0) then throw("Pool maker has no funds") else
if (allowedAmountToRedeem <= 0) then throw("All pool maker funds are locked") else
if (allowedAmountToRedeem < amount) then throw("Allowed amount to redeem is less then requested: allowedAmountToRedeem=" + allowedAmountToRedeem.toString() + " requested=" + amount.toString()) else
if (amount < minPoolRedeemAmount) then throw("Redeem amount is less then min allowed") else
# validate that caller doesn't have any funds locked for withdraw
if(poolMakerLockedAmount > 0) then throw("Pool maker already has initiated redemption that is not withdrawn yet.") else
if (this.getInteger(poolMakerLiquidityRequestKey).isDefined()) then throw("Pool maker has already initiated redemption for " + height.toString() + " height") else
[
IntegerEntry(getPoolTotalLockedAmountKey(assetAddressStr), poolTotalLocked + amount),
IntegerEntry(getAssetCurrrentPoolAmountKey(assetAddressStr), poolActualSize - amount),
IntegerEntry(getPoolMakerParticipationAmountKey(assetAddressStr, callerAddress), poolMakerActualAmount - amount),
IntegerEntry(poolMakerLiquidityRequestKey, amount),
IntegerEntry(getPoolMakerLockedAmountKey(assetAddressStr, callerAddress), amount),
IntegerEntry(getPoolMakerUnlockHeightKey(assetAddressStr, callerAddress), withdrawHeight),
StringEntry("%s%s_trace_redeemFromPool", traceData)
]
}
@Callable(i)
func getMaxAllowedAmountToRedeem(assetCode: String, poolMakerAddress: String) = {
let redeemTuple = calcAllowedAmountToRedeem(assetCode, poolMakerAddress)
let assetAddressStr = redeemTuple._1
let allowedAmountToRedeem = redeemTuple._2
let poolTotalLocked = redeemTuple._3
let poolActualSize = redeemTuple._4
let poolMakerActualAmount = redeemTuple._5
let withdrawHeight = redeemTuple._6
let poolMakerLockedAmount = redeemTuple._7
let traceData = redeemTuple._8
throw("{"
+ "allowedAmountToRedeem: " + allowedAmountToRedeem.toString()
+ ", assetCode: " + assetCode
+ ", poolMaker: " + poolMakerAddress
+ ", trace: " + traceData
+ "}")
}
# [called by pool maker]
@Callable(i)
func withdraw(assetCode: String, poolMakerAddress: String) = {
let assetAddressStr = getAssetAddressByCodeOrFail(assetCode)
let assetAddress = assetAddressStr.addressFromStringValue()
let poolMakerLockedAmountKey = getPoolMakerLockedAmountKey(assetAddressStr, poolMakerAddress)
let registeredAmount4Withdraw = this.getInteger(poolMakerLockedAmountKey).valueOrElse(0)
let poolMakerUnlockHeightKey = getPoolMakerUnlockHeightKey(assetAddressStr, poolMakerAddress)
let withdrawHeight = this.getInteger(poolMakerUnlockHeightKey).valueOrElse(0)
let poolTotalLockedAmountKey = getPoolTotalLockedAmountKey(assetAddressStr)
let poolTotalLocked = this.getInteger(poolTotalLockedAmountKey).valueOrElse(0)
let poolMakerLiquidityRequestKey = getPoolMakerLiquidityRequestKey(assetAddressStr, poolMakerAddress, withdrawHeight)
if(getBoolByKey(isInitializedKey) == false) then throw("Contract is not yet ready.") else
if (!assetAddress.getInteger(poolMakerLiquidityRequestKey).isDefined()) then throw("Need to transfer liquidity from asset account first") else
# it works like a status
if(registeredAmount4Withdraw <= 0) then throw("Cannot find pool maker by address " + poolMakerAddress + " or no locked amount.") else
if(withdrawHeight > height) then throw("Please wait " + withdrawHeight.toString() + " before withdraw.") else
[
IntegerEntry(poolMakerLockedAmountKey, 0),
IntegerEntry(poolTotalLockedAmountKey, poolTotalLocked - registeredAmount4Withdraw),
IntegerEntry(poolMakerUnlockHeightKey, 0),
ScriptTransfer(addressFromStringValue(poolMakerAddress), registeredAmount4Withdraw, neutrinoAssetId)
]
}
@Verifier(tx)
func verify() = sigVerify_128Kb(tx.bodyBytes, tx.proofs[0], fromBase58String("Ce5zjCMEm3ZxvKznGGm8BhwaDiRNd8kADwMNgriVg4tD"))