From b4ffdcc2359e90861b1e66c30eea4bd371885dac Mon Sep 17 00:00:00 2001 From: Zaphod Beeblebrox Date: Wed, 10 Apr 2024 14:00:09 +0300 Subject: [PATCH 1/5] support of WIND staking --- script/control.ride | 47 ++++++++++++-------------------------------- script/math.ride | 12 ++++++++++- script/neutrino.ride | 40 ++++++++++++++++++++++++++----------- 3 files changed, 53 insertions(+), 46 deletions(-) diff --git a/script/control.ride b/script/control.ride index 5d9374b..bd04c62 100644 --- a/script/control.ride +++ b/script/control.ride @@ -40,34 +40,19 @@ let IdxControlCfgGnsbtControllerDapp = 11 let IdxControlCfgRestV2Dapp = 12 let IdxControlCfgGovernanceDapp = 13 let IdxControlCfgPegProviderDapp = 14 +let IdxControlCfgFacadeDapp = 15 +let IdxControlCfgWindPoolDapp = 16 +let ContractsListSize = IdxControlCfgWindPoolDapp 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---------------------- @@ -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---------------------- diff --git a/script/math.ride b/script/math.ride index 3af33f9..d647113 100644 --- a/script/math.ride +++ b/script/math.ride @@ -122,6 +122,7 @@ let IdxControlCfgGnsbtControllerDapp = 11 let IdxControlCfgRestV2Dapp = 12 let IdxControlCfgGovernanceDapp = 13 let IdxControlCfgPegProviderDapp = 14 +let IdxControlCfgWindPoolDapp = 16 func keyControlAddress() = "%s%s__config__controlAddress" func keyControlCfg() = "%s__controlConfig" @@ -138,12 +139,18 @@ let auctionContract = controlCfg.getContractAddressOrFail(IdxControlCfgAuctionDa let liquidationContract = controlCfg.getContractAddressOrFail(IdxControlCfgLiquidationDapp) let usdnStakingContract = controlCfg.getContractAddressOrFail(IdxControlCfgRpdDapp) let pegProviderContract = controlCfg.getContractAddressOrFail(IdxControlCfgPegProviderDapp) +let windContract = controlCfg.getContractAddressOrFail(IdxControlCfgWindPoolDapp) let neutrinoAssetIdStr = neutrinoContract.getStringOrFail(keyNeutrinoAssetId()) let neutrinoAssetId = fromBase58String(neutrinoAssetIdStr) let nsbtAssetId = fromBase58String(neutrinoContract.getStringValue(keyNsbtAssetId())) let surfAssetId = fromBase58String(auctionContract.getStringValue(keySurfAssetId())) +let windIdx = 3 # IMPORTANT! WIND token should have index 3 in the basket: WAVES__wxId__viresId__windId +let windAssetIdStr = neutrinoContract.getStringOrFail(basketAssetsKey()).split(SEP)[windIdx] +let windAssetId = fromBase58String(windAssetIdStr) +func keyWindStaked(stakerAddrStr: String) = stakerAddrStr + "_indexStaked" + func keyBalanceLocked() = "balance_lock_" func totalLockedKEY(swapType: String, assetId: String) = makeString(["%s%s%s", "balanceLock", swapType, assetId], SEP) func keyTokenLockedBalance(assetId: String) = totalLockedKEY("outNeutrino", assetId) @@ -190,6 +197,9 @@ 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 + windContract.getInteger(keyWindStaked(neutrinoContract.toString())).valueOrElse(0) # staked in WIND pool else neutrinoContract.assetBalance(tempAssetId) - # reserves in other basket tokens neutrinoContract.getInteger(keyTokenLockedBalance(assetId)).valueOrElse(0) # locked balance from new basketToken -> neutrino swaps } @@ -218,7 +228,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 diff --git a/script/neutrino.ride b/script/neutrino.ride index 75cdfdf..e0fa882 100644 --- a/script/neutrino.ride +++ b/script/neutrino.ride @@ -137,6 +137,7 @@ let IdxControlCfgGnsbtControllerDapp = 11 let IdxControlCfgRestV2Dapp = 12 let IdxControlCfgGovernanceDapp = 13 let IdxControlCfgFacadeDapp = 15 +let IdxControlCfgWindPoolDapp = 16 func keyControlAddress() = "%s%s__config__controlAddress" func keyControlCfg() = "%s__controlConfig" @@ -156,6 +157,7 @@ let auctionContract = controlCfg.getContractAddressOrFail(IdxControlCfgAuctionDa let nodeRegistryContract = controlCfg.getContractAddressOrFail(IdxControlCfgNodeRegistryDapp) let govContract = controlCfg.getContractAddressOrFail(IdxControlCfgGovernanceDapp) let poolsFacadeContract = controlCfg.getContractAddressOrFail(IdxControlCfgFacadeDapp) +let windContract = controlCfg.getContractAddressOrFail(IdxControlCfgWindPoolDapp) #-------------------Constructor------------------------- let NeutrinoAssetIdKey = "neutrino_asset_id" @@ -224,6 +226,10 @@ 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" +let windIdx = 3 # IMPORTANT! WIND token should have index 3 in the basket: WAVES__wxId__viresId__windId +let windAssetIdStr = this.getStringOrFail(basketAssetsKey()).split(SEP)[windIdx] +let windAssetId = fromBase58String(windAssetIdStr) + #-------------------State Reading functions------------------- func multiOutFeeREAD() = getInteger(BasketOutFeePartKey).valueOrElse(getInteger(WavesOutFeePartKey).valueOrElse(DEFAULTBURNFEE)) func singleOutFeeREAD() = getInteger(NeutrinoOutFeePartKey).valueOrElse(DEFAULTSWAPFEE) @@ -281,7 +287,7 @@ 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() @@ -289,10 +295,12 @@ func convertNeutrinoToBasket(amount: Int, basketInfoStr: String, neutrinoSupply: 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------------------- @@ -572,7 +580,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 ) } @@ -592,7 +601,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 ) } @@ -620,7 +630,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 ) } @@ -643,6 +654,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 @@ -654,8 +666,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) = { @@ -844,7 +856,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 @@ -869,7 +881,7 @@ func swapNeutrinoToBasket() = { @Callable(i) func swapNeutrinoToSingleBasketToken(assetIdOut: String) = { - let basketAssets = getString(basketAssetsKey()).value().split(SEP) # WAVES__wxId__viresId + let basketAssets = getString(basketAssetsKey()).value().split(SEP) # WAVES__wxId__viresId__windId 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() @@ -931,6 +943,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 @@ -941,6 +954,11 @@ func withdraw(account: String, index: Int, swapTxId: String) = { 0 } else 0 + strict windUnstakeOpt = if (swapType == "outMulti" && windOutAmount > 0) then { + strict unstakeWind = windContract.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() From 835e203f72736eb0dd7126a8825618be9b678cf2 Mon Sep 17 00:00:00 2001 From: Zaphod Beeblebrox Date: Tue, 16 Apr 2024 21:35:48 +0300 Subject: [PATCH 2/5] WIND staking with proxy contract --- script/control.ride | 4 +-- script/math.ride | 12 ++++++--- script/neutrino.ride | 6 ++--- script/wind_proxy.ride | 60 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 73 insertions(+), 9 deletions(-) create mode 100644 script/wind_proxy.ride diff --git a/script/control.ride b/script/control.ride index bd04c62..d56ebd0 100644 --- a/script/control.ride +++ b/script/control.ride @@ -41,8 +41,8 @@ let IdxControlCfgRestV2Dapp = 12 let IdxControlCfgGovernanceDapp = 13 let IdxControlCfgPegProviderDapp = 14 let IdxControlCfgFacadeDapp = 15 -let IdxControlCfgWindPoolDapp = 16 -let ContractsListSize = IdxControlCfgWindPoolDapp +let IdxControlCfgWindProxyDapp = 16 +let ContractsListSize = IdxControlCfgWindProxyDapp func keyControlConfig() = "%s__controlConfig" func keyPriceByAsset(assetIdStr: String) = ["%s%s%s__common__priceByAsset", assetIdStr].makeString(SEP) diff --git a/script/math.ride b/script/math.ride index d647113..757a912 100644 --- a/script/math.ride +++ b/script/math.ride @@ -122,7 +122,7 @@ let IdxControlCfgGnsbtControllerDapp = 11 let IdxControlCfgRestV2Dapp = 12 let IdxControlCfgGovernanceDapp = 13 let IdxControlCfgPegProviderDapp = 14 -let IdxControlCfgWindPoolDapp = 16 +let IdxControlCfgWindProxyDapp = 16 func keyControlAddress() = "%s%s__config__controlAddress" func keyControlCfg() = "%s__controlConfig" @@ -139,13 +139,14 @@ let auctionContract = controlCfg.getContractAddressOrFail(IdxControlCfgAuctionDa let liquidationContract = controlCfg.getContractAddressOrFail(IdxControlCfgLiquidationDapp) let usdnStakingContract = controlCfg.getContractAddressOrFail(IdxControlCfgRpdDapp) let pegProviderContract = controlCfg.getContractAddressOrFail(IdxControlCfgPegProviderDapp) -let windContract = controlCfg.getContractAddressOrFail(IdxControlCfgWindPoolDapp) +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 windContractKey = "%s__windContract" let windIdx = 3 # IMPORTANT! WIND token should have index 3 in the basket: WAVES__wxId__viresId__windId let windAssetIdStr = neutrinoContract.getStringOrFail(basketAssetsKey()).split(SEP)[windIdx] let windAssetId = fromBase58String(windAssetIdStr) @@ -197,9 +198,12 @@ 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 + else if (tempAssetId == windAssetId) then { + let windContract = windProxyContract.getString(windContractKey).valueOrElse("3P37uv8V2CMNMtKtwRRNnYPjNqPdGGszytW").addressFromStringValue() + windProxyContract.assetBalance(tempAssetId) + # possible non-staked WIND amount on wind proxy contract + neutrinoContract.assetBalance(tempAssetId) + # possible non-staked WIND amount on neutrino contract windContract.getInteger(keyWindStaked(neutrinoContract.toString())).valueOrElse(0) # staked in WIND pool + } else neutrinoContract.assetBalance(tempAssetId) - # reserves in other basket tokens neutrinoContract.getInteger(keyTokenLockedBalance(assetId)).valueOrElse(0) # locked balance from new basketToken -> neutrino swaps } diff --git a/script/neutrino.ride b/script/neutrino.ride index e0fa882..deeb3db 100644 --- a/script/neutrino.ride +++ b/script/neutrino.ride @@ -137,7 +137,7 @@ let IdxControlCfgGnsbtControllerDapp = 11 let IdxControlCfgRestV2Dapp = 12 let IdxControlCfgGovernanceDapp = 13 let IdxControlCfgFacadeDapp = 15 -let IdxControlCfgWindPoolDapp = 16 +let IdxControlCfgWindProxyDapp = 16 func keyControlAddress() = "%s%s__config__controlAddress" func keyControlCfg() = "%s__controlConfig" @@ -157,7 +157,7 @@ let auctionContract = controlCfg.getContractAddressOrFail(IdxControlCfgAuctionDa let nodeRegistryContract = controlCfg.getContractAddressOrFail(IdxControlCfgNodeRegistryDapp) let govContract = controlCfg.getContractAddressOrFail(IdxControlCfgGovernanceDapp) let poolsFacadeContract = controlCfg.getContractAddressOrFail(IdxControlCfgFacadeDapp) -let windContract = controlCfg.getContractAddressOrFail(IdxControlCfgWindPoolDapp) +let windProxyContract = controlCfg.getContractAddressOrFail(IdxControlCfgWindProxyDapp) #-------------------Constructor------------------------- let NeutrinoAssetIdKey = "neutrino_asset_id" @@ -955,7 +955,7 @@ func withdraw(account: String, index: Int, swapTxId: String) = { } else 0 strict windUnstakeOpt = if (swapType == "outMulti" && windOutAmount > 0) then { - strict unstakeWind = windContract.invoke("unstakeIndex", [windOutAmount], []) + strict unstakeWind = windProxyContract.invoke("unstakeIndex", [windOutAmount], []) 0 } else 0 diff --git a/script/wind_proxy.ride b/script/wind_proxy.ride new file mode 100644 index 0000000..5555cf8 --- /dev/null +++ b/script/wind_proxy.ride @@ -0,0 +1,60 @@ +{-# STDLIB_VERSION 6 #-} +{-# CONTENT_TYPE DAPP #-} +{-# SCRIPT_TYPE ACCOUNT #-} + +let beneficiaryAddressKey = "%s__beneficiaryAddress" +let windContractKey = "%s__windContract" +let neutrinoContractKey = "%s__neutrinoContract" +# WIND pool contract on Puzzle +let windContract = this.getString(windContractKey).valueOrElse("3P37uv8V2CMNMtKtwRRNnYPjNqPdGGszytW").addressFromStringValue() +let neutrinoContract = this.getString(neutrinoContractKey).valueOrElse("3PC9BfRwJWWiw9AREE2B3eWzCks3CYtg4yo").addressFromStringValue() +let windAssetId = base58'6TXFMpr6rG4tr2CuPmVRq1NsjgPLJ59s2VMVnL1ZLtpR' + +# address to receive rewards +# currently beneficiary is ignored, because Puzzle wind megapool does not pay rewards in WIND token +@Callable(i) +func setBeneficiary(beneficiary: String) = { + if (i.caller != this) then throw("Permission denied") else + [StringEntry(beneficiaryAddressKey, beneficiary)] +} + +# usually rewards are being claimed on each stakeIndex/unstakeIndex operation +# but can be called explicitly +# 10 pool tokens will be collected on this proxy contract +# can be called by anyone, on behalf of this proxy contract +@Callable(i) +func claimIndexRewards() = { + strict claim = windContract.invoke("claimIndexRewards", [], []) + ([], 0) +} + +# claimIndexRewards will be called inside +# can be called by anyone, on behalf of this proxy contract +@Callable(i) +func stakeIndex() = { + if (i.payments[0].assetId.value() != windAssetId) then throw("WIND asset should be attached") else + strict stake = windContract.invoke("stakeIndexFor", [this.toString()], i.payments) + ([], 0) +} + +# claimIndexRewards will be called inside +@Callable(i) +func unstakeIndex(indexAmount: Int) = { + if (i.caller != this && i.caller != neutrinoContract) then throw("Permission denied") else + if (indexAmount < 0) then throw("WIND amount can't be negative") else + if (indexAmount > 0) then { + strict stake = windContract.invoke("unstakeIndex", [indexAmount], []) + if (i.caller == neutrinoContract) then { + ([ScriptTransfer(neutrinoContract, indexAmount, windAssetId)], 0) + } else { + ([], 0) + } + } else { # indexAmount == 0, do nothing + ([], 0) + } +} + +# These calls probably better to do manually: +# generateIndex(true) with 10 payments attached (convert 10 pool assets to WIND) +# generateIndexAndStake with 10 payments attached (convert 10 pool assets to WIND and stake) +# generateIndexWithOneTokenAndStake with payment in WAVES (convert WAVES to WIND and stake) \ No newline at end of file From 3d2cf1516e76957512b58b24076889fc903d2de6 Mon Sep 17 00:00:00 2001 From: Zaphod Beeblebrox Date: Mon, 22 Apr 2024 13:51:47 +0300 Subject: [PATCH 3/5] WIND staking with proxy contract (refactored) --- script/wind_proxy.ride | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/script/wind_proxy.ride b/script/wind_proxy.ride index 5555cf8..2ad1cce 100644 --- a/script/wind_proxy.ride +++ b/script/wind_proxy.ride @@ -2,12 +2,28 @@ {-# CONTENT_TYPE DAPP #-} {-# SCRIPT_TYPE ACCOUNT #-} +let SEP = "__" +func getStringOrFail(address: Address, key: String) = address.getString(key) + .valueOrErrorMessage(makeString(["mandatory ", address.toString(), ".", key, " is not defined"], "")) + +# data index from controlConfig +let IdxControlCfgNeutrinoDapp = 1 + +let controlAddressKey = "%s%s__config__controlAddress" +let controlCfgKey = "%s__controlConfig" + +func readControlCfgOrFail(control: Address) = split_4C(control.getStringOrFail(controlCfgKey), SEP) +func getContractAddressOrFail(controlCfg: List[String], idx: Int) = controlCfg[idx].addressFromString() + .valueOrErrorMessage("Control cfg doesn't contain address at index " + idx.toString()) + +let controlContract = this.getString(controlAddressKey).valueOrElse("3P5Bfd58PPfNvBM2Hy8QfbcDqMeNtzg7KfP").addressFromStringValue() +let controlCfg = controlContract.readControlCfgOrFail() +let neutrinoContract = controlCfg.getContractAddressOrFail(IdxControlCfgNeutrinoDapp) + let beneficiaryAddressKey = "%s__beneficiaryAddress" let windContractKey = "%s__windContract" -let neutrinoContractKey = "%s__neutrinoContract" # WIND pool contract on Puzzle let windContract = this.getString(windContractKey).valueOrElse("3P37uv8V2CMNMtKtwRRNnYPjNqPdGGszytW").addressFromStringValue() -let neutrinoContract = this.getString(neutrinoContractKey).valueOrElse("3PC9BfRwJWWiw9AREE2B3eWzCks3CYtg4yo").addressFromStringValue() let windAssetId = base58'6TXFMpr6rG4tr2CuPmVRq1NsjgPLJ59s2VMVnL1ZLtpR' # address to receive rewards From 10589cd3ecdb638ea38d04dc16ad36765707bcf7 Mon Sep 17 00:00:00 2001 From: "neutrino.admin" Date: Wed, 24 Apr 2024 14:01:42 +0400 Subject: [PATCH 4/5] wind staking - code review --- script/math.ride | 16 +++++-------- script/neutrino.ride | 51 ++++++++++++++---------------------------- script/wind_proxy.ride | 40 +++++++++++++++------------------ 3 files changed, 41 insertions(+), 66 deletions(-) diff --git a/script/math.ride b/script/math.ride index 757a912..499abcb 100644 --- a/script/math.ride +++ b/script/math.ride @@ -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) @@ -145,12 +146,8 @@ let neutrinoAssetIdStr = neutrinoContract.getStringOrFail(keyNeutrinoAssetId( let neutrinoAssetId = fromBase58String(neutrinoAssetIdStr) let nsbtAssetId = fromBase58String(neutrinoContract.getStringValue(keyNsbtAssetId())) let surfAssetId = fromBase58String(auctionContract.getStringValue(keySurfAssetId())) - -let windContractKey = "%s__windContract" -let windIdx = 3 # IMPORTANT! WIND token should have index 3 in the basket: WAVES__wxId__viresId__windId -let windAssetIdStr = neutrinoContract.getStringOrFail(basketAssetsKey()).split(SEP)[windIdx] -let windAssetId = fromBase58String(windAssetIdStr) -func keyWindStaked(stakerAddrStr: String) = stakerAddrStr + "_indexStaked" +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) @@ -199,10 +196,9 @@ func reserve(assetId: String) = { if (tempAssetId == WAVESID) then wavesBalance(neutrinoContract).regular - wavesLockedBalance # reserves in WAVES to cover neutrino supply else if (tempAssetId == windAssetId) then { - let windContract = windProxyContract.getString(windContractKey).valueOrElse("3P37uv8V2CMNMtKtwRRNnYPjNqPdGGszytW").addressFromStringValue() - windProxyContract.assetBalance(tempAssetId) + # possible non-staked WIND amount on wind proxy contract - neutrinoContract.assetBalance(tempAssetId) + # possible non-staked WIND amount on neutrino contract - windContract.getInteger(keyWindStaked(neutrinoContract.toString())).valueOrElse(0) # staked in WIND pool + windProxyContract.assetBalance(tempAssetId) # possible non-staked WIND amount on wind proxy contract + + 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 diff --git a/script/neutrino.ride b/script/neutrino.ride index deeb3db..88505c5 100644 --- a/script/neutrino.ride +++ b/script/neutrino.ride @@ -162,6 +162,8 @@ let windProxyContract = controlCfg.getContractAddressOrFail(IdxControlCfgWindPro #-------------------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" @@ -226,9 +228,19 @@ 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" -let windIdx = 3 # IMPORTANT! WIND token should have index 3 in the basket: WAVES__wxId__viresId__windId -let windAssetIdStr = this.getStringOrFail(basketAssetsKey()).split(SEP)[windIdx] +#-------------------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)) @@ -295,8 +307,8 @@ func convertNeutrinoToBasket(amount: Int, basketInfoStr: String, neutrinoSupply: 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 outTuple = if (assetId == "WAVES") then (outAmount, price, acc._4) - else if (assetId == windAssetIdStr) then (acc._2, acc._3, outAmount) + 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) } @@ -314,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 @@ -641,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) @@ -879,16 +880,6 @@ func swapNeutrinoToBasket() = { swapNeutrinoInternal(i, "") } -@Callable(i) -func swapNeutrinoToSingleBasketToken(assetIdOut: String) = { - let basketAssets = getString(basketAssetsKey()).value().split(SEP) # WAVES__wxId__viresId__windId - 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 @@ -1019,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) diff --git a/script/wind_proxy.ride b/script/wind_proxy.ride index 2ad1cce..a4ec094 100644 --- a/script/wind_proxy.ride +++ b/script/wind_proxy.ride @@ -16,23 +16,20 @@ func readControlCfgOrFail(control: Address) = split_4C(control.getStringOrFail(c func getContractAddressOrFail(controlCfg: List[String], idx: Int) = controlCfg[idx].addressFromString() .valueOrErrorMessage("Control cfg doesn't contain address at index " + idx.toString()) +#-------neutrino keys------ +func keyWindAssetId() = "wind_asset_id" + let controlContract = this.getString(controlAddressKey).valueOrElse("3P5Bfd58PPfNvBM2Hy8QfbcDqMeNtzg7KfP").addressFromStringValue() let controlCfg = controlContract.readControlCfgOrFail() let neutrinoContract = controlCfg.getContractAddressOrFail(IdxControlCfgNeutrinoDapp) -let beneficiaryAddressKey = "%s__beneficiaryAddress" -let windContractKey = "%s__windContract" # WIND pool contract on Puzzle -let windContract = this.getString(windContractKey).valueOrElse("3P37uv8V2CMNMtKtwRRNnYPjNqPdGGszytW").addressFromStringValue() -let windAssetId = base58'6TXFMpr6rG4tr2CuPmVRq1NsjgPLJ59s2VMVnL1ZLtpR' +let windContractKey = "%s__windContract" +let windContract = this.getString(windContractKey).valueOrElse("3P37uv8V2CMNMtKtwRRNnYPjNqPdGGszytW").addressFromStringValue() +let windAssetIdStr = neutrinoContract.getStringOrFail(keyWindAssetId()) +let windAssetId = fromBase58String(windAssetIdStr) -# address to receive rewards -# currently beneficiary is ignored, because Puzzle wind megapool does not pay rewards in WIND token -@Callable(i) -func setBeneficiary(beneficiary: String) = { - if (i.caller != this) then throw("Permission denied") else - [StringEntry(beneficiaryAddressKey, beneficiary)] -} +func keyWindStaked(stakerAddrStr: String) = stakerAddrStr + "_indexStaked" # usually rewards are being claimed on each stakeIndex/unstakeIndex operation # but can be called explicitly @@ -53,21 +50,20 @@ func stakeIndex() = { ([], 0) } +@Callable(i) +func balanceREADONLY() = { + let stakedAmt = windContract.getInteger(keyWindStaked(this.toString())).valueOrElse(0) # staked in WIND pool + ([], stakedAmt) +} + # claimIndexRewards will be called inside @Callable(i) func unstakeIndex(indexAmount: Int) = { if (i.caller != this && i.caller != neutrinoContract) then throw("Permission denied") else - if (indexAmount < 0) then throw("WIND amount can't be negative") else - if (indexAmount > 0) then { - strict stake = windContract.invoke("unstakeIndex", [indexAmount], []) - if (i.caller == neutrinoContract) then { - ([ScriptTransfer(neutrinoContract, indexAmount, windAssetId)], 0) - } else { - ([], 0) - } - } else { # indexAmount == 0, do nothing - ([], 0) - } + if (indexAmount <= 0) then throw("WIND amount can't be negative or zero") else + + strict stake = windContract.invoke("unstakeIndex", [indexAmount], []) + ([ScriptTransfer(neutrinoContract, indexAmount, windAssetId)], 0) } # These calls probably better to do manually: From ed33b14e5378248a16fe4e47dc0e31e0672de5d3 Mon Sep 17 00:00:00 2001 From: "neutrino.admin" Date: Wed, 24 Apr 2024 14:08:40 +0400 Subject: [PATCH 5/5] wind staking - code review 2 --- script/math.ride | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/script/math.ride b/script/math.ride index 499abcb..005a695 100644 --- a/script/math.ride +++ b/script/math.ride @@ -196,8 +196,7 @@ func reserve(assetId: String) = { if (tempAssetId == WAVESID) then wavesBalance(neutrinoContract).regular - wavesLockedBalance # reserves in WAVES to cover neutrino supply else if (tempAssetId == windAssetId) then { - windProxyContract.assetBalance(tempAssetId) # possible non-staked WIND amount on wind proxy contract - + neutrinoContract.assetBalance(tempAssetId) # possible non-staked WIND amount on neutrino contract + neutrinoContract.assetBalance(tempAssetId) # possible non-staked WIND amount on neutrino contract + windProxyContract.invoke("balanceREADONLY", [], []).asInt() } else neutrinoContract.assetBalance(tempAssetId) - # reserves in other basket tokens