Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Wind staking feature #34

Open
wants to merge 5 commits into
base: release/sc24.04.1
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 13 additions & 34 deletions script/control.ride
Original file line number Diff line number Diff line change
Expand Up @@ -40,34 +40,19 @@ let IdxControlCfgGnsbtControllerDapp = 11
let IdxControlCfgRestV2Dapp = 12
let IdxControlCfgGovernanceDapp = 13
let IdxControlCfgPegProviderDapp = 14
let IdxControlCfgFacadeDapp = 15
let IdxControlCfgWindProxyDapp = 16
let ContractsListSize = IdxControlCfgWindProxyDapp

func keyControlConfig() = "%s__controlConfig"
func keyPriceByAsset(assetIdStr: String) = ["%s%s%s__common__priceByAsset", assetIdStr].makeString(SEP)
func keyBlackSwanThreshold() = "%s%s__controlConfig__blackSwanThreshold"

func dataControlCfg(neutrinoContract: String, auctionContract: String, rpdContract: String, mathContract: String,
liquidationContract: String, restContract: String, nodeRegistryContract: String, nsbtStakingContract: String,
mediatorContract: String, surfStakingContract: String, gnsbtControllerContract: String, restV2Contract: String,
governanceContract: String, doraContract: String, poolsFacadeContract: String) = {
makeString_2C(
["%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s",
neutrinoContract, #1
auctionContract, #2
rpdContract, #3
mathContract, #4
liquidationContract, #5
restContract, #6
nodeRegistryContract, #7
nsbtStakingContract, #8
mediatorContract, #9
surfStakingContract, #10
gnsbtControllerContract, #11
restV2Contract, #12
governanceContract, #13
doraContract, #14
poolsFacadeContract #15
],
SEP)
func dataControlCfg(contractsList: List[String]) = {
let formatPrefix = "%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s"
if (formatPrefix.size() != ContractsListSize * 2) then throw("Size of format specifier does not match") else

makeString_2C(formatPrefix :: contractsList, SEP)
}

#-------------------Functions----------------------
Expand Down Expand Up @@ -347,17 +332,11 @@ func checkPrices(newWavesPrice: Int, newWxPrice: Int, newViresPrice: Int) = {
}

@Callable(i)
func constructorV1(neutrinoContract: String, auctionContract: String, rpdContract: String, mathContract: String,
liquidationContract: String, restContract: String, nodeRegistryContract: String,
nsbtStakingContract: String, mediatorContract: String, surfStakingContract: String, gnsbtControllerContract: String,
restV2Contract: String, governanceContract: String, doraContract: String, poolsFacadeContract: String) = {
if (i.caller != this) then throw("permissions denied") else

[StringEntry(keyControlConfig(), dataControlCfg(
neutrinoContract, auctionContract, rpdContract, mathContract, liquidationContract,
restContract, nodeRegistryContract, nsbtStakingContract, mediatorContract,
surfStakingContract, gnsbtControllerContract, restV2Contract, governanceContract, doraContract, poolsFacadeContract
))]
func constructorV2(contractsList: List[String]) = {
if (i.caller != this) then throw("permissions denied") else
if (contractsList.size() != ContractsListSize) then throw("Wrong number of contracts in the list") else

[StringEntry(keyControlConfig(), dataControlCfg(contractsList))]
}

#-------------------Callable----------------------
Expand Down
11 changes: 10 additions & 1 deletion script/math.ride
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ func keyPriceAdjArbRegulator() = "%s%s__priceAdj__arbRegulator"
func keyNeutrinoAssetId() = "neutrino_asset_id"
func keyNsbtAssetId() = "bond_asset_id"
func keySurfAssetId() = "surf_asset_id"
func keyWindAssetId() = "wind_asset_id"
func swapsTimeframeKEY() = "swaps_timeframe"
func keyUserLastQuickSwapHeight(userAddress: String) = ["%s%s", "userLastQuickSwapHeight", userAddress].makeString(SEP)
func keyQuickSwapUserSpentInPeriod(userAddress: String) = ["%s%s", "quickSwapUserSpentInPeriod", userAddress].makeString(SEP)
Expand Down Expand Up @@ -122,6 +123,7 @@ let IdxControlCfgGnsbtControllerDapp = 11
let IdxControlCfgRestV2Dapp = 12
let IdxControlCfgGovernanceDapp = 13
let IdxControlCfgPegProviderDapp = 14
let IdxControlCfgWindProxyDapp = 16

func keyControlAddress() = "%s%s__config__controlAddress"
func keyControlCfg() = "%s__controlConfig"
Expand All @@ -138,11 +140,14 @@ let auctionContract = controlCfg.getContractAddressOrFail(IdxControlCfgAuctionDa
let liquidationContract = controlCfg.getContractAddressOrFail(IdxControlCfgLiquidationDapp)
let usdnStakingContract = controlCfg.getContractAddressOrFail(IdxControlCfgRpdDapp)
let pegProviderContract = controlCfg.getContractAddressOrFail(IdxControlCfgPegProviderDapp)
let windProxyContract = controlCfg.getContractAddressOrFail(IdxControlCfgWindProxyDapp)

let neutrinoAssetIdStr = neutrinoContract.getStringOrFail(keyNeutrinoAssetId())
let neutrinoAssetId = fromBase58String(neutrinoAssetIdStr)
let nsbtAssetId = fromBase58String(neutrinoContract.getStringValue(keyNsbtAssetId()))
let surfAssetId = fromBase58String(auctionContract.getStringValue(keySurfAssetId()))
let windAssetIdStr = neutrinoContract.getStringOrFail(keyWindAssetId())
let windAssetId = fromBase58String(windAssetIdStr)

func keyBalanceLocked() = "balance_lock_"
func totalLockedKEY(swapType: String, assetId: String) = makeString(["%s%s%s", "balanceLock", swapType, assetId], SEP)
Expand Down Expand Up @@ -190,6 +195,10 @@ func reserve(assetId: String) = {
let tempAssetId = assetId.fromBase58String()
if (tempAssetId == WAVESID) then
wavesBalance(neutrinoContract).regular - wavesLockedBalance # reserves in WAVES to cover neutrino supply
else if (tempAssetId == windAssetId) then {
neutrinoContract.assetBalance(tempAssetId) # possible non-staked WIND amount on neutrino contract
+ windProxyContract.invoke("balanceREADONLY", [], []).asInt()
}
else neutrinoContract.assetBalance(tempAssetId) - # reserves in other basket tokens
neutrinoContract.getInteger(keyTokenLockedBalance(assetId)).valueOrElse(0) # locked balance from new basketToken -> neutrino swaps
}
Expand Down Expand Up @@ -218,7 +227,7 @@ func getBR() = {
let BR = if (neutrinoSupply == 0) then 0 else getBR()

func getBasketInfo() = {
let basketAssets = neutrinoContract.getString(basketAssetsKey()).value() # WAVES__wxId__viresId__swopId__eggId__westId
let basketAssets = neutrinoContract.getString(basketAssetsKey()).value() # WAVES__wxId__viresId__windId
let basket = basketAssets.split(SEP)

# 1st pass: calculate total reserves
Expand Down
81 changes: 41 additions & 40 deletions script/neutrino.ride
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ let IdxControlCfgGnsbtControllerDapp = 11
let IdxControlCfgRestV2Dapp = 12
let IdxControlCfgGovernanceDapp = 13
let IdxControlCfgFacadeDapp = 15
let IdxControlCfgWindProxyDapp = 16

func keyControlAddress() = "%s%s__config__controlAddress"
func keyControlCfg() = "%s__controlConfig"
Expand All @@ -156,10 +157,13 @@ let auctionContract = controlCfg.getContractAddressOrFail(IdxControlCfgAuctionDa
let nodeRegistryContract = controlCfg.getContractAddressOrFail(IdxControlCfgNodeRegistryDapp)
let govContract = controlCfg.getContractAddressOrFail(IdxControlCfgGovernanceDapp)
let poolsFacadeContract = controlCfg.getContractAddressOrFail(IdxControlCfgFacadeDapp)
let windProxyContract = controlCfg.getContractAddressOrFail(IdxControlCfgWindProxyDapp)

#-------------------Constructor-------------------------
let NeutrinoAssetIdKey = "neutrino_asset_id"
let BondAssetIdKey = "bond_asset_id"
let WindAssetIdKey = "wind_asset_id"

let AuctionContractKey = "auction_contract"
let NsbtStakingContractKey = "nsbtStakingContract"
let LiquidationContractKey = "liquidation_contract"
Expand Down Expand Up @@ -224,6 +228,20 @@ func basketWeightByAssetKey(assetId: String) = "%s%s%s__common__we
func keyPriceByAsset(assetId: String) = "%s%s%s__common__priceByAsset__" + assetId
func maxFeeKey() = "%s%s__common__maxAllowedFee"

#-------------------Global vars-------------------------

let neutrinoAssetId = getStringByKey(NeutrinoAssetIdKey).fromBase58String()
let windAssetIdStr = getStringByKey(WindAssetIdKey)
let windAssetId = fromBase58String(windAssetIdStr)
let priceIndex = getNumberByAddressAndKey(controlContract, PriceIndexKey) # Last price history iterator from control.ride
let isBlocked = getBoolByAddressAndKey(controlContract, IsBlockedKey) # Checks for contract locks that might happen after attacks. The var is read from control contract
let isDisabledIssueXtn = getBoolByAddressAndKey(controlContract, IsDisabledIssueKey)
let nodeOracleProviderPubKey = fromBase58String(getStringByKey(NodeOracleProviderPubKeyKey))
let bondAssetId = fromBase58String("6nSpVyNH7yM69eg446wrQR94ipbbcmZMU1ENPwanC97g") # NSBT with 6 decimals as USDN does
let deprecatedBondAssetId = fromBase58String("975akZBfnMj513U7MZaHKzQrmsEx5aE3wdWKTrHBhbjF") # USDNB with 0 decimals

let neutrinoContract = this

#-------------------State Reading functions-------------------
func multiOutFeeREAD() = getInteger(BasketOutFeePartKey).valueOrElse(getInteger(WavesOutFeePartKey).valueOrElse(DEFAULTBURNFEE))
func singleOutFeeREAD() = getInteger(NeutrinoOutFeePartKey).valueOrElse(DEFAULTSWAPFEE)
Expand Down Expand Up @@ -281,18 +299,20 @@ func convertNeutrinoToBasket(amount: Int, basketInfoStr: String, neutrinoSupply:
let d6 = getInteger(basketDKey()).value()
let outPart = MULT6 - fraction(k6, d6, MULT6) # for outMulti swaps, k*D part stays in reserves, and (1 - k*D) after applying fee goes to user
# basket item: assetIdStr:share6:price6:reserve8
func conv(acc: (List[(String, Int, Int, Int)], Int, Int), item: String) = {
func conv(acc: (List[(String, Int, Int, Int)], Int, Int, Int), item: String) = {
let parts = item.split(LISTSEP)
let assetId = parts[0]
let share = parts[1].parseIntValue()
let price = parts[2].parseIntValue()
let reserv = parts[3].parseIntValue()
if (neutrinoSupply == 0) then throw("Attempt to burn neutrino at zero supply") else
let outAmount = fraction(fraction(amount, reserv, neutrinoSupply), outPart, MULT6) # amount * (1 - k*D)
let wavesTuple = if (assetId == "WAVES") then (outAmount, price) else (acc._2, acc._3)
(acc._1 :+ (assetId, outAmount, price, share), wavesTuple._1, wavesTuple._2)
let outTuple = if (assetId == "WAVES") then (outAmount, price, acc._4)
else if (assetId == windAssetIdStr) then (acc._2, acc._3, outAmount)
else (acc._2, acc._3, acc._4)
(acc._1 :+ (assetId, outAmount, price, share), outTuple._1, outTuple._2, outTuple._3)
}
FOLD<10>(basketInfo, ([], 0, 0), conv) # (List[(assetId, outAmount, price, share)], wavesOutAmount, wavesPrice)
FOLD<10>(basketInfo, ([], 0, 0, 0), conv) # (List[(assetId, outAmount, price, share)], wavesOutAmount, wavesPrice, windOutAmount)
}

#-------------------Failures-------------------
Expand All @@ -306,17 +326,6 @@ func priceIndexFAIL(index: Int, priceIndex: Int, indexHeight: Int, unlockHeight:
+ " unlockHeight=" + toString(unlockHeight)
+ " prevIndexHeight=" + toString(prevIndexHeight))

#-------------------Global vars-------------------------

let neutrinoAssetId = getStringByKey(NeutrinoAssetIdKey).fromBase58String()
let priceIndex = getNumberByAddressAndKey(controlContract, PriceIndexKey) # Last price history iterator from control.ride
let isBlocked = getBoolByAddressAndKey(controlContract, IsBlockedKey) # Checks for contract locks that might happen after attacks. The var is read from control contract
let isDisabledIssueXtn = getBoolByAddressAndKey(controlContract, IsDisabledIssueKey)
let nodeOracleProviderPubKey = fromBase58String(getStringByKey(NodeOracleProviderPubKeyKey))
let bondAssetId = fromBase58String("6nSpVyNH7yM69eg446wrQR94ipbbcmZMU1ENPwanC97g") # NSBT with 6 decimals as USDN does
let deprecatedBondAssetId = fromBase58String("975akZBfnMj513U7MZaHKzQrmsEx5aE3wdWKTrHBhbjF") # USDNB with 0 decimals

let neutrinoContract = this
#-------------------Global vars deficit, locked & supply -------------------------
let currentPrice = getNumberByAddressAndKey(controlContract, PriceKey) # The value from control.ride

Expand Down Expand Up @@ -572,7 +581,8 @@ func calcWithdrawT2U(inAmount: Int, inAsset: String, basketInfo: String, weighte
0, # debug - part of inAmount that is used BEFORE reaching BR protection level
0, # debug - part of inAmount that is used AFTER reaching BR protection level
[], # dummy value
[outAmt[1], outAmt[2]] # [netAmount, feeAmount]
[outAmt[1], outAmt[2]], # [netAmount, feeAmount]
0 # WIND amount to unstake
)
}

Expand All @@ -592,7 +602,8 @@ func calcWithdrawU2Basket(usdnIn: Int, basketInfo: String, neutrinoSupply: Int)
usdnIn, # debug - part of inAmount that is used BEFORE reaching BR protection level
0, # debug - part of inAmount that is used AFTER reaching BR protection level
outAmtGross._1, # List[(assetId, outAmount, price, share)]
[] # dummy value
[], # dummy value
outAmtGross._4 # WIND amount to unstake
)
}

Expand Down Expand Up @@ -620,7 +631,8 @@ func calcWithdrawU2T(usdnIn: Int, basketInfo: String, neutrinoSupply: Int, outAs
usdnIn, # debug - part of inAmount that is used BEFORE reaching BR protection level
0, # debug - part of inAmount that is used AFTER reaching BR protection level
[s], # singleton List[(assetId, outAmount, price, share)]
[] # dummy value
[], # dummy value
outAmtGross._4 # WIND amount to unstake
)
}

Expand All @@ -630,7 +642,7 @@ func calcWithdraw(swapType: String, inAmount: Int, price: Int, neutrinoMetrics:
let weightedPrice = neutrinoMetrics[nMetricWeightedPrice].asInt()
let outDataTuple =
if (swapType == "outNeutrino") then calcWithdrawT2U(inAmount, inAsset, basketInfo, weightedPrice) else
if (swapType == "outMulti") then
if (swapType == "outMulti") then
if (outAssetOpt != "") then calcWithdrawU2T(inAmount, basketInfo, neutrinoSupply, outAssetOpt, isVirtual)
else calcWithdrawU2Basket(inAmount, basketInfo, neutrinoSupply)
else throw("Unsupported swap type " + swapType)
Expand All @@ -643,6 +655,7 @@ func calcWithdraw(swapType: String, inAmount: Int, price: Int, neutrinoMetrics:
let withdrawPrice = outDataTuple._6 # weightedPrice for outNeutrino
let basketData = outDataTuple._11 # List[(assetId, outAmount, price, share)] for outMulti
let netFeeData = outDataTuple._12 # [netAmount, feeAmount] for outNeutrino
let windOutAmt = outDataTuple._13 # WIND amount to unstake

let outNetAmt = if (swapType == "outNeutrino") then netFeeData[0] else 0
let outFeeAmt = if (swapType == "outNeutrino") then netFeeData[1] else 0
Expand All @@ -654,8 +667,8 @@ func calcWithdraw(swapType: String, inAmount: Int, price: Int, neutrinoMetrics:
let withdrawBasket = if (swapType == "outMulti") then applyFeesBasket(basketData, feePart) else "" # assetId:price:netAmount:feeAmount:share_ ...

# WARNING: if u modify then need to check RestV2
# 1 2 3 4 5 6 7 8 9
(outNetAmt, outAssetId, outSurfAmt, inAmtToSurfPart, unleaseAmt, outFeeAmt, outAmtGross, withdrawPrice, withdrawBasket)
# 1 2 3 4 5 6 7 8 9 10
(outNetAmt, outAssetId, outSurfAmt, inAmtToSurfPart, unleaseAmt, outFeeAmt, outAmtGross, withdrawPrice, withdrawBasket, windOutAmt)
}

func validateAndGenerateWavesLimitActions(swapType: String, outAssetOpt: String, withdrawBasket: String) = {
Expand Down Expand Up @@ -844,7 +857,7 @@ func swapTokenToNeutrino() = {
if (i.payments.size() != 1) then throw("swapTokenToNeutrino require only one payment") else
let pmt = i.payments[0].value()
# check allowed assets
let basketAssets = getString(basketAssetsKey()).value() # WAVES__wxId__viresId__swopId__eggId__westId
let basketAssets = getString(basketAssetsKey()).value() # WAVES__wxId__viresId__windId
let pmtAssetIdStr = pmt.assetId.valueOrElse(WAVESID).toBase58String()
if (!basketAssets.contains(pmtAssetIdStr)) then throw(pmtAssetIdStr + " is not one of basket tokens:" + basketAssets) else

Expand All @@ -867,16 +880,6 @@ func swapNeutrinoToBasket() = {
swapNeutrinoInternal(i, "")
}

@Callable(i)
func swapNeutrinoToSingleBasketToken(assetIdOut: String) = {
let basketAssets = getString(basketAssetsKey()).value().split(SEP) # WAVES__wxId__viresId
if (!basketAssets.containsElement(assetIdOut)) then throw("unknown assetIdOut, should be one of the basket") else
if (assetIdOut != "WAVES") then throw("currently only WAVES supported as target asset") else
let healthy = poolsFacadeContract.invoke("wxPoolHealthyREADONLY", [], []).asBool()
if (!healthy) then throw("WX pools down, cannot swap to single token") else
swapNeutrinoInternal(i, assetIdOut)
}

@Callable(i)
func internalSaveInteger(key: String, amount: Int) = {
if (i.caller != this) then throw("internalSaveInteger is not public method") else
Expand Down Expand Up @@ -931,6 +934,7 @@ func withdraw(account: String, index: Int, swapTxId: String) = {
let outAmtGross = withdrawTuple._7 # for outNeutrino
let withdrawPrice = withdrawTuple._8 # weightedPrice for outNeutrino
let withdrawBasket = withdrawTuple._9 # assetId:price:netAmount:feeAmount:share_ ...
let windOutAmount = withdrawTuple._10 # WIND amount to unstake

if (swapType == "outNeutrino" && outAmtGross <= 0) then throw("balance equals zero") else

Expand All @@ -941,6 +945,11 @@ func withdraw(account: String, index: Int, swapTxId: String) = {
0
} else 0

strict windUnstakeOpt = if (swapType == "outMulti" && windOutAmount > 0) then {
strict unstakeWind = windProxyContract.invoke("unstakeIndex", [windOutAmount], [])
0
} else 0

strict unleaseInvOrEmpty = this.invoke("internalUnleaseAndLease", [unleaseAmt], [])
let gnsbtData = gnsbtControllerContract.invoke("gnsbtInfoSYSREADONLY", ["", 0, 0], []).asAnyList()
let gnsbtAmtTotal = gnsbtData[1].asInt()
Expand Down Expand Up @@ -1001,14 +1010,6 @@ func internalUnleaseAndLease(unleaseAmount: Int) = {
prepareUnleaseAndLease(unleaseAmount)
}

# Callback for auction contract to transfer USDN to user
@Callable(i)
func transferUsdnToUser(amount: Int, addr: String) = {
if (i.caller != auctionContract) then throw("Only auction contract is authorized") else

[ScriptTransfer(addressFromStringValue(addr), amount, neutrinoAssetId)]
}

# Accept waves from auction after buyNsbt/buySurf to lease them immediately
# also from governance after creating new voting
@Callable(i)
Expand Down
Loading