-
Notifications
You must be signed in to change notification settings - Fork 0
/
staking.ride
152 lines (132 loc) · 7.57 KB
/
staking.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
{-# STDLIB_VERSION 5 #-}
{-# CONTENT_TYPE DAPP #-}
{-# SCRIPT_TYPE ACCOUNT #-}
#
# Smart Contract Implementing Staking by Users of DEFO Protocol
#
#-------------------Base functions----------------------
func getNumberByKey(key: String) = {
getInteger(this, key).valueOrElse(0)
}
func getStringByKey(key: String) = {
getString(this, key).valueOrElse("")
}
func getBoolByKey(key: String) = {
getBoolean(this, key).valueOrElse(false)
}
func getNumberByAddressAndKey(address: String, key: String) = {
getInteger(addressFromStringValue(address), key).valueOrElse(0)
}
func getStringByAddressAndKey(address: Address, key: String) = {
getString(address, key).valueOrElse("")
}
let separator = "__"
#-------------------Constructor-------------------------
let factoryContractAddressKey = "%s" + separator + "factory"
#-------------------Keys---------------------------------
let keyAssetStatus = 3
func getStakingAssetBalanceKey(assetId: String) = "%s%s" + separator + "stakingBalance" + separator + assetId
func getUserBalanceKey(owner: String, assetId: String) = "%s%s%s"+ separator + "stakingBalance" + separator + assetId + separator + owner
func getAssetConfigKey(assetAddress: String) = "%s%s%s" + separator + "defoAsset" + separator + assetAddress + "__config"
#-------------------Split protocol keys-------------------------------
func getRewardsConfigKey(owner : String, share: Int, receiver: String) = "%s%s%s%s" + separator + "stakingConfig" + separator + owner + separator + toString(share) + separator + receiver
func getCurrentRewardsConfigKey(owner : String) = "%s%s" + separator + "stakingConfigCurrent" + separator + owner
func getRewardsConfigStartKey(configKey : String, isStart: Boolean) = configKey + (if isStart then separator + "start" else separator + "end")
func getRewardConfigInitialShare(owner : String) = "%s%s" + separator + owner + separator + "initialShare"
func getAssetAddressByAssetIdKey(assetId: String) = "%s%s%s" + separator + "defoAsset" + separator + assetId + separator + "addressByAssetId"
#-------------------Convert functions-------------------
func convertJsonArrayToList(jsonArray: String, splitSymbol: String) = {
jsonArray.split(splitSymbol)
}
#-------------------Global vars-------------------------
let factoryContract = addressFromStringValue(getString(this,factoryContractAddressKey).valueOrErrorMessage("No factory contract specified."))
#-------------------Get functions----------------------
func getUserBalance(owner : String, assetId: String) = getNumberByKey(getUserBalanceKey(owner, assetId))
func getStakingAssetBalance(assetId: String) = getNumberByKey(getStakingAssetBalanceKey(assetId))
func getSyntheticContractByAssetId(assetId: String) = getString(factoryContract,getAssetAddressByAssetIdKey(assetId))
func getAssetConfigArray(assetAddress: String) = convertJsonArrayToList(
getStringByAddressAndKey(factoryContract,getAssetConfigKey(assetAddress)).valueOrErrorMessage("No DEFO Asset " + assetAddress + "config present"),
separator)
func internallock(i: Invocation, receiver: String, share: Int) = {
let pmt = i.payments[0].valueOrErrorMessage("No asset pased in payment.")
let assetIdString = toBase58String(value(pmt.assetId))
let syntheticAssetContract = getSyntheticContractByAssetId(assetIdString).valueOrErrorMessage("Passed payment asset is not valid.")
let syntheticAssetConfig = getAssetConfigArray(syntheticAssetContract)
# 1. find asser's contract by asset_id
if (!isDefined(addressFromString(receiver)))
then throw("Invalid address format " + receiver)
else if (share > 100)
then throw("staking rewards share cannot be higher than 100%")
else if (share < 1)
then throw("staking rewards share cannot be lower than 1%")
else if (getElement(syntheticAssetConfig,keyAssetStatus) != "ISSUED")
then throw("Synthetic asset should be with ISSUED status")
else {
let account = toString(i.caller)
let currentConfig = getStringByKey(getCurrentRewardsConfigKey(account))
let correctData = if (currentConfig != "") then {
# staking has been submitted before - need to do additional validations
let currentConfigData = currentConfig.split(separator)
let currShare = currentConfigData[3].parseIntValue()
let currReceiver = currentConfigData[4]
# can be zero because of old records
let notMigratedInitialShare = getNumberByKey(getRewardConfigInitialShare(account))
let actualInitialShare = if (notMigratedInitialShare == 0) then currShare else notMigratedInitialShare
#if (currReceiver != receiver) then throw("Impossible to setup new referral address for existed staking") else
#if (share < actualInitialShare) then throw("Impossible to setup new share less then initial share for existed staking") else
let newShare = if (share < actualInitialShare || currReceiver != receiver) then actualInitialShare else share
[actualInitialShare.toString(), newShare.toString(), currReceiver]
} else {
[share.toString(), share.toString(), receiver]
}
let correctInitialShare = correctData[0].parseIntValue()
let correctShare = correctData[1].parseIntValue()
let correctReceiver = correctData[2]
let newCurrentConfig = getRewardsConfigKey(account, correctShare, correctReceiver)
let isNewConfig = !(currentConfig == newCurrentConfig)
let end = if isNewConfig then height else 0
let start = if isNewConfig then height else getNumberByKey(getRewardsConfigStartKey(newCurrentConfig, true))
[
IntegerEntry(getStakingAssetBalanceKey(assetIdString), getStakingAssetBalance(assetIdString) + pmt.amount),
IntegerEntry(getUserBalanceKey(account, assetIdString), getUserBalance(account, assetIdString) + pmt.amount),
IntegerEntry(getRewardsConfigStartKey(currentConfig, false), end), #if there is no current config it leads to dead state key
IntegerEntry(getRewardsConfigStartKey(newCurrentConfig, true), start),
IntegerEntry(getRewardConfigInitialShare(account), correctInitialShare),
StringEntry(getCurrentRewardsConfigKey(account), newCurrentConfig)
]
}
}
#-------------------Callable----------------------
# start neutrino staking
# [called by user]
@Callable(i)
func startStakingSP(receiver: String, share: Int) = {
internallock(i, receiver, share)
}
@Callable(i)
func startStaking() = {
internallock(i, toString(i.caller), 100)
}
# cancel neutrino staking
# [called by user]
@Callable(i)
func stopStaking(unlockAmount: Int, assetIdString: String) = {
let account = toString(i.caller)
let assetId = fromBase58String(assetIdString)
let syntheticAssetContract = getSyntheticContractByAssetId(assetIdString).valueOrErrorMessage("Passed payment asset is not valid.")
let syntheticAssetConfig = getAssetConfigArray(syntheticAssetContract)
let balance = getUserBalance(account, assetIdString) - unlockAmount
if (balance < 0)
then throw("invalid amount")
else if (getElement(syntheticAssetConfig,keyAssetStatus) != "ISSUED")
then throw("Synthetic asset should be with ISSUED status")
else {
[
IntegerEntry(getStakingAssetBalanceKey(assetIdString), getStakingAssetBalance(assetIdString) - unlockAmount),
IntegerEntry(getUserBalanceKey(account, assetIdString), balance),
ScriptTransfer(addressFromStringValue(account), unlockAmount, assetId)
]
}
}
@Verifier(tx)
func verify() = sigVerify_128Kb(tx.bodyBytes, tx.proofs[0], fromBase58String("Ce5zjCMEm3ZxvKznGGm8BhwaDiRNd8kADwMNgriVg4tD"))