diff --git a/package.json b/package.json index 438738366..22eb4af6b 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,9 @@ "test-lp-staking-v2": "mocha --parallel --require test/lp_staking_v2/_hooks.mjs test/lp_staking_v2", "test-lp": "mocha --parallel --require test/lp/_hooks.mjs test/lp", "test-lp-stable": "mocha --parallel --require test/lp_stable/_hooks.mjs test/lp_stable", + "test-lp-stable-new-pools-with-different-decimals": "mocha --parallel --require test/lp_stable/new_pools_with_different_decimals/_hooks.mjs test/lp_stable/new_pools_with_different_decimals", + "test-lp-stable-decimals-migration": "mocha --parallel --require test/lp_stable/lp_stable_decimals_migration/_hooks.mjs test/lp_stable/lp_stable_decimals_migration", + "test-lp-to-lp-stable-migration": "mocha --parallel --require test/lp_stable/lp_to_lp_stable_migration/_hooks.mjs test/lp_stable/lp_to_lp_stable_migration", "test-referral": "mocha --parallel --require test/referral/_hooks.mjs test/referral", "ci-test": "concurrently --max-processes 8 --timings npm:test-*", "test": "API_NODE_URL=http://localhost:6869 npm run ci-test", diff --git a/ride/lp_stable.ride b/ride/lp_stable.ride index f11595ef4..faa0df4ba 100644 --- a/ride/lp_stable.ride +++ b/ride/lp_stable.ride @@ -1,4 +1,4 @@ -{-# STDLIB_VERSION 5 #-} +{-# STDLIB_VERSION 6 #-} {-# CONTENT_TYPE DAPP #-} {-# SCRIPT_TYPE ACCOUNT #-} @@ -36,9 +36,12 @@ #----------------- # GLOBAL VARIABLES #----------------- +let defaultDecimals = 1_000_000 + let scale8 = 100_000_000 let scale8BigInt = 100_000_000.toBigInt() let scale18 = 1_000_000_000_000_000_000.toBigInt() +let thousand = 1000 let zeroBigInt = 0.toBigInt() let oneBigInt = 1.toBigInt() let slippage4D = (scale8 - scale8 * 1 / scale8).toBigInt() # 9999999 or error of 0.0000001 @@ -53,76 +56,70 @@ let PoolPutDis = 2 # PUT DISABLED, pool with put operation disab let PoolMatcherDis = 3 # MATCHER DISABLED, pool with matcher operations disabled let PoolShutdown = 4 # SHUTDOWN, pool operations halted # data indexes from pool config stored in factory -let idxPoolAddress = 1 -let idxPoolSt = 2 -let idxLPAsId = 3 -let idxAmAsId = 4 -let idxPrAsId = 5 -let idxAmtAsDcm = 6 -let idxPriceAsDcm = 7 -let idxIAmtAsId = 8 -let idxIPriceAsId = 9 +let idxPoolAddress = 1 +let idxPoolSt = 2 +let idxLPAsId = 3 +let idxAmAsId = 4 +let idxPrAsId = 5 +let idxAmtAsDcm = 6 +let idxPriceAsDcm = 7 +let idxIAmtAsId = 8 +let idxIPriceAsId = 9 # data indexes from factory config let idxFactStakCntr = 1 let idxFactSlippCntr = 7 let idxFactGwxRewCntr = 10 -let delay = "%s__delay" +let feePermilleDefault = 0 #------------------------- # WX COMMON LIBRARY #------------------------- -func t1(origVal: Int, origScaleMult: Int) = fraction(origVal.toBigInt(), scale18, origScaleMult.toBigInt()) -func f1(val: BigInt, resultScaleMult: Int) = fraction(val, resultScaleMult.toBigInt(), scale18).toInt() +func toX18(origVal: Int, origScaleMult: Int) = fraction(origVal.toBigInt(), scale18, origScaleMult.toBigInt()) +func fromX18(val: BigInt, resultScaleMult: Int) = fraction(val, resultScaleMult.toBigInt(), scale18).toInt() # cast passed amount to specified 'resScale' scale value from 'curScale' scale value -func ts(amt: Int, resScale: Int, curScale: Int) = fraction(amt, resScale, curScale) +func ts(amt: Int, resScale: Int, curScale: Int) = fraction(amt, resScale, curScale) func abs(val: BigInt) = if (val < zeroBigInt) then -val else val #------------------------- # KEYS ON CURRENT CONTRACT #------------------------- -func fc() = {"%s__factoryContract"} -func mpk() = {"%s__managerPublicKey"} -func pmpk() = {"%s__pendingManagerPublicKey"} -func pl() = {"%s%s__price__last"} -func ph(h: Int, t: Int) = {makeString(["%s%s%d%d__price__history", h.toString(), t.toString()], SEP)} -# keyPutActionByUser -func pau(ua: String, txId: String) = "%s%s%s__P__" + ua + "__" + txId -# keyGetActionByUser -func gau(ua: String, txId: String) = "%s%s%s__G__" + ua + "__" + txId -func aa() = {"%s__amountAsset"} -func pa() = {"%s__priceAsset"} -# keyAmplificator -func amp() = {"%s__amp"} -func ada() = "%s__addonAddr" - -# lastGetOneTknCall -func lgotc(caller: String) = {makeString(["%s%s__lastGetOneTknCall", caller], SEP)} -# lastSetOneTknCall -func lsotc(caller: String) = {makeString(["%s%s__lastPutOneTknCall", caller], SEP)} +func keyFactoryContact() = {"%s__factoryContract"} +func keyManagerPublicKey() = {"%s__managerPublicKey"} +func keyPendingManagerPublicKey() = {"%s__pendingManagerPublicKey"} +func keyPriceLast() = {"%s%s__price__last"} +func keyPriceHistory(h: Int, t: Int) = {makeString(["%s%s%d%d__price__history", h.toString(), t.toString()], SEP)} +func keyPutActionByUser(ua: String, txId: String) = "%s%s%s__P__" + ua + "__" + txId +func keyGetActionByUser(ua: String, txId: String) = "%s%s%s__G__" + ua + "__" + txId +func keyAmountAsset() = {"%s__amountAsset"} +func keyPriceAsset() = {"%s__priceAsset"} +func keyAmplificator() = {"%s__amp"} +func keyAddonAddress() = "%s__addonAddr" + +let keyFeePermille = "%s__feePermille" +let feePermille = this.getInteger(keyFeePermille).valueOrElse(feePermilleDefault) #------------------------ # KEYS ON OTHER CONTRACTS #------------------------ -# from factory -# keyFactoryConfig -func fcfg() = {"%s__factoryConfig"} -# keyMatcherPub -func mtpk() = "%s%s__matcher__publicKey" -# keyPoolConfig -func pc(iAmtAs: String, iPrAs: String) = {"%d%d%s__" + iAmtAs + "__" + iPrAs + "__config"} -# keyMappingsBaseAsset2internalId -func mba(bAStr: String) = {"%s%s%s__mappings__baseAsset2internalId__" + bAStr} +func keyFactoryConfig() = {"%s__factoryConfig"} +func keyMatcherPub() = "%s%s__matcher__publicKey" +func keyPoolConfig(iAmtAs: String, iPrAs: String) = {"%d%d%s__" + iAmtAs + "__" + iPrAs + "__config"} +func keyMappingsBaseAsset2internalId(bAStr: String) = {"%s%s%s__mappings__baseAsset2internalId__" + bAStr} # keyAllPoolsShutdown -func aps() = {"%s__shutdown"} +func keyAllPoolsShutdown() = {"%s__shutdown"} #------------------------ # FAILURES #------------------------ -# throwOrderError -func toe(orV: Boolean, sendrV: Boolean, matchV: Boolean) = { - throw("Failed: ordValid=" + orV.toString() + " sndrValid=" + sendrV.toString() + " mtchrValid=" + matchV.toString()) +func throwErr(msg: String) = ["lp_stable.ride:", msg].makeString(" ").throw() +# func valueOrErr( +# valueFrom: String|Int|Unit, +# msg: String +# ) = valueFrom.valueOrErrorMessage(["lp_stable.ride:", msg].makeString(" ").throw()) +func throwOrderError(orV: Boolean, sendrV: Boolean, matchV: Boolean) = { + throwErr("Failed: ordValid=" + orV.toString() + " sndrValid=" + sendrV.toString() + " mtchrValid=" + matchV.toString()) } #------------------------ @@ -132,44 +129,44 @@ func toe(orV: Boolean, sendrV: Boolean, matchV: Boolean) = { func str(val: Any) = { match val { case valStr: String => valStr - case _ => throw("fail cast to String") + case _ => throwErr("fail cast to String") } } -# getStringOrFail -func strf(addr: Address, key: String) = addr.getString(key).valueOrErrorMessage(makeString(["mandatory ", addr.toString(), ".", key, " not defined"], "")) -# getIntOrFail -func intf(addr: Address, key: String) = addr.getInteger(key).valueOrErrorMessage(makeString(["mandatory ", addr.toString(), ".", key, " not defined"], "")) +func getStringOrFail( + addr: Address, + key: String +) = addr.getString(key).valueOrErrorMessage(makeString(["mandatory ", addr.toString(), ".", key, " not defined"], "")) +func getIntOrFail( + addr: Address, + key: String +) = addr.getInteger(key).valueOrErrorMessage(makeString(["mandatory ", addr.toString(), ".", key, " not defined"], "")) -# factoryContract -let fca = addressFromStringValue(strf(this, fc())) +let factoryContract = addressFromStringValue(getStringOrFail(this, keyFactoryContact())) -let A = strf(this, amp()) +let amplificator = getStringOrFail(this, keyAmplificator()) -# isGlobalShutdown # check that global shutdown is take place -func igs() = { - fca.getBoolean(aps()).valueOrElse(false) +func isGlobalShutdown() = { + factoryContract.getBoolean(keyAllPoolsShutdown()).valueOrElse(false) } - -# getMatcherPubOrFail -func mp() = { - fca.strf(mtpk()).fromBase58String() +func getMatcherPubOrFail() = { + factoryContract.getStringOrFail(keyMatcherPub()).fromBase58String() } -# getPoolConfig +let matcherAddress = getMatcherPubOrFail().addressFromPublicKey() + # function used to gather all pool data from factory -func gpc() = { - let amtAs = strf(this, aa()) - let priceAs = strf(this, pa()) - let iPriceAs = intf(fca, mba(priceAs)) - let iAmtAs = intf(fca, mba(amtAs)) - strf(fca, pc(iAmtAs.toString(), iPriceAs.toString())).split(SEP) +func getPoolConfig() = { + let amtAs = getStringOrFail(this, keyAmountAsset()) + let priceAs = getStringOrFail(this, keyPriceAsset()) + let iPriceAs = getIntOrFail(factoryContract, keyMappingsBaseAsset2internalId(priceAs)) + let iAmtAs = getIntOrFail(factoryContract, keyMappingsBaseAsset2internalId(amtAs)) + getStringOrFail(factoryContract, keyPoolConfig(iAmtAs.toString(), iPriceAs.toString())).split(SEP) } -# getFactoryConfig -func gfc() = { - strf(fca, fcfg()).split(SEP) +func getFactoryConfig() = { + getStringOrFail(factoryContract, keyFactoryConfig()).split(SEP) } func dataPutActionInfo(inAmtAssetAmt: Int, inPriceAssetAmt: Int, outLpAmt: Int, price: Int, slipByUser: Int, slippageReal: Int, txHeight: Int, txTimestamp: Int, slipageAmAmt: Int, slipagePrAmt: Int) = { @@ -184,171 +181,175 @@ func getAccBalance(assetId: String) = { if(assetId == "WAVES") then wavesBalance(this).available else assetBalance(this, fromBase58String(assetId)) } -# calcPriceBigInt -func cpbi(prAmtX18: BigInt, amAmtX18: BigInt) = { +func calcPriceBigInt(prAmtX18: BigInt, amAmtX18: BigInt) = { fraction(prAmtX18, scale18, amAmtX18) } -# validateAbsDiff -func vad(A1: BigInt, A2: BigInt, slippage: BigInt) = { - let diff = fraction(A1 - A2, scale8BigInt, A2) +func validateAbsDiff(estimateLP: BigInt, outLp: BigInt, slippage: BigInt) = { + let diff = fraction(estimateLP - outLp, scale8BigInt, outLp) let pass = (slippage - abs(diff)) > zeroBigInt - if (!pass) then throw("Big slpg: " + diff.toString()) else - (pass, min([A1, A2])) + if (!pass) then throwErr("Big slippage: " + diff.toString()) else + (pass, min([estimateLP, outLp])) } -# validateD -func vd(D1: BigInt, D0: BigInt, slpg: BigInt) = { +func validateD(D1: BigInt, D0: BigInt, slippage: BigInt) = { let diff = fraction(D0, scale8BigInt, D1) - let fail = diff < slpg - if (fail || D1 < D0) then throw(D0.toString() + " " + D1.toString() + " " + diff.toString() + " " + slpg.toString()) else + let fail = diff < slippage + if (fail || D1 < D0) then throwErr(D0.toString() + " " + D1.toString() + " " + diff.toString() + " " + slippage.toString()) else fail } -# privateCalcPrice # cast assets and calc price -func pcp(amAssetDcm: Int, prAssetDcm: Int, amAmt: Int, prAmt: Int) = { - let amtAsAmtX18 = amAmt.t1(amAssetDcm) - let prAsAmtX18 = prAmt.t1(prAssetDcm) - cpbi(prAsAmtX18, amtAsAmtX18) +func privateCalcPrice(amAssetDcm: Int, prAssetDcm: Int, amAmt: Int, prAmt: Int) = { + let amtAsAmtX18 = amAmt.toX18(amAssetDcm) + let prAsAmtX18 = prAmt.toX18(prAssetDcm) + calcPriceBigInt(prAsAmtX18, amtAsAmtX18) } # used only in stats endpoint, so result values are in scale8 as required func calcPrices(amAmt: Int, prAmt: Int, lpAmt: Int) = { - let cfg = gpc() + let cfg = getPoolConfig() let amtAsDcm = cfg[idxAmtAsDcm].parseIntValue() let prAsDcm = cfg[idxPriceAsDcm].parseIntValue() - let priceX18 = pcp(amtAsDcm, prAsDcm, amAmt, prAmt) + let priceX18 = privateCalcPrice(amtAsDcm, prAsDcm, amAmt, prAmt) - let amAmtX18 = amAmt.t1(amtAsDcm) - let prAmtX18 = prAmt.t1(prAsDcm) - let lpAmtX18 = lpAmt.t1(scale8) + let amAmtX18 = amAmt.toX18(amtAsDcm) + let prAmtX18 = prAmt.toX18(prAsDcm) + let lpAmtX18 = lpAmt.toX18(scale8) - let lpPrInAmAsX18 = cpbi(amAmtX18, lpAmtX18) - let lpPrInPrAsX18 = cpbi(prAmtX18, lpAmtX18) + let lpPrInAmAsX18 = calcPriceBigInt(amAmtX18, lpAmtX18) + let lpPrInPrAsX18 = calcPriceBigInt(prAmtX18, lpAmtX18) [priceX18, lpPrInAmAsX18, lpPrInPrAsX18] } # public API which is used by backend func calculatePrices(amAmt: Int, prAmt: Int, lpAmt: Int) = { - let p = calcPrices(amAmt, prAmt, lpAmt) - [p[0].f1(scale8), - p[1].f1(scale8), - p[2].f1(scale8)] + let p = calcPrices(amAmt, prAmt, lpAmt) + [ + p[0].fromX18(scale8), + p[1].fromX18(scale8), + p[2].fromX18(scale8) + ] } -# estimateGetOperation -func ego(txId58: String, pmtAssetId: String, pmtLpAmt: Int, userAddress: Address) = { +func estimateGetOperation(txId58: String, paymentAssetId: String, paymentLpAmount: Int, userAddress: Address) = { # data from pool config - let cfg = gpc() - let lpId = cfg[idxLPAsId] - let amId = cfg[idxAmAsId] - let prId = cfg[idxPrAsId] - let amDcm = cfg[idxAmtAsDcm].parseIntValue() - let prDcm = cfg[idxPriceAsDcm].parseIntValue() - let sts = cfg[idxPoolSt] + let cfg = getPoolConfig() + let lpAssetId = cfg[idxLPAsId] + let amountId = cfg[idxAmAsId] + let priceId = cfg[idxPrAsId] + let amountDecimals = cfg[idxAmtAsDcm].parseIntValue() + let priceDecimals = cfg[idxPriceAsDcm].parseIntValue() + let sts = cfg[idxPoolSt] - let lpEmiss = assetInfo(lpId.fromBase58String()).valueOrErrorMessage("Wrong LP id").quantity + let lpEmission = assetInfo(lpAssetId.fromBase58String()).valueOrErrorMessage("Wrong LP id").quantity - # validation block - if (lpId != pmtAssetId) then throw("Wrong pmt asset") else + strict validationBlock = lpAssetId == paymentAssetId || throwErr("Wrong payment asset") + let amountBalance = getAccBalance(amountId) + let priceBalance = getAccBalance(priceId) - let amBalance = getAccBalance(amId) - let amBalanceX18 = amBalance.t1(amDcm) + let amountBalanceDefaultDecimals = fraction(amountBalance, defaultDecimals, amountDecimals) + let priceBalanceDefaultDecimals = fraction(priceBalance, defaultDecimals, priceDecimals) - let prBalance = getAccBalance(prId) - let prBalanceX18 = prBalance.t1(prDcm) + let amountBalanceX18 = amountBalanceDefaultDecimals.toX18(defaultDecimals) + let priceBalanceX18 = priceBalanceDefaultDecimals.toX18(defaultDecimals) - let curPriceX18 = cpbi(prBalanceX18, amBalanceX18) - let curPrice = curPriceX18.f1(scale8) + let currentPriceX18 = calcPriceBigInt(priceBalanceX18, amountBalanceX18) + let curPrice = currentPriceX18.fromX18(scale8) + + let paymentLpAmountX18 = paymentLpAmount.toX18(scale8) + let lpEmissionX18 = lpEmission.toX18(scale8) - let pmtLpAmtX18 = pmtLpAmt.t1(scale8) - let lpEmissX18 = lpEmiss.t1(scale8) # calculations - let outAmAmtX18 = fraction(amBalanceX18, pmtLpAmtX18, lpEmissX18) - let outPrAmtX18 = fraction(prBalanceX18, pmtLpAmtX18, lpEmissX18) + let outAmountAmountX18 = fraction(amountBalanceX18, paymentLpAmountX18, lpEmissionX18) + let outPriceAmountX18 = fraction(priceBalanceX18, paymentLpAmountX18, lpEmissionX18) # cast amounts to asset decimals - let outAmAmt = outAmAmtX18.f1(amDcm) - let outPrAmt = outPrAmtX18.f1(prDcm) + let outAmountAmountDefaultDecimals = outAmountAmountX18.fromX18(defaultDecimals) + let outPriceAmountDefaultDecimals = outPriceAmountX18.fromX18(defaultDecimals) + + let outAmountAmount = fraction(outAmountAmountDefaultDecimals, amountDecimals, defaultDecimals) + let outPriceAmount = fraction(outPriceAmountDefaultDecimals, priceDecimals, defaultDecimals) let state = if (txId58 == "") then [] else [ - ScriptTransfer(userAddress, outAmAmt, if (amId == "WAVES") then unit else amId.fromBase58String()), - ScriptTransfer(userAddress, outPrAmt, if (prId == "WAVES") then unit else prId.fromBase58String()), - + ScriptTransfer(userAddress, outAmountAmount, if (amountId == "WAVES") then unit else amountId.fromBase58String()), + ScriptTransfer(userAddress, outPriceAmount, if (priceId == "WAVES") then unit else priceId.fromBase58String()), StringEntry( - gau(userAddress.toString(), txId58), - dataGetActionInfo(outAmAmt, outPrAmt, pmtLpAmt, curPrice, height, lastBlock.timestamp)), - IntegerEntry(pl(), curPrice), - IntegerEntry(ph(height, lastBlock.timestamp), curPrice) + keyGetActionByUser(userAddress.toString(), txId58), + dataGetActionInfo(outAmountAmount, outPriceAmount, paymentLpAmount, curPrice, height, lastBlock.timestamp) + ), + IntegerEntry(keyPriceLast(), curPrice), + IntegerEntry(keyPriceHistory(height, lastBlock.timestamp), curPrice) ] - ( outAmAmt, # 1 - outPrAmt, # 2 - amId, # 3 - prId, # 4 - amBalance, # 5 - prBalance, # 6 - lpEmiss, # 7 - curPriceX18, # 8 - sts, # 9 - state # 10 + ( + outAmountAmount, # 1 + outPriceAmount, # 2 + amountId, # 3 + priceId, # 4 + amountBalance, # 5 + priceBalance, # 6 + lpEmission, # 7 + currentPriceX18, # 8 + sts, # 9 + state # 10 ) } -# estimatePutOperation -func epo(txId58: String, - slippage: Int, - inAmAmt: Int, - inAmId: ByteVector|Unit, - inPrAmt: Int, - inPrId: ByteVector|Unit, - userAddress: String, - isEval: Boolean, - emitLp: Boolean, - isOneAsset: Boolean, - pmtAmt: Int, - pmtId: String) = { +func estimatePutOperation( + txId58: String, + slippageTolerance: Int, + inAmountAssetAmount: Int, + inAmountAssetId: ByteVector|Unit, + inPriceAssetAmount: Int, + inPriceAssetId: ByteVector|Unit, + userAddress: String, + isEvaluate: Boolean, + emitLp: Boolean, + isOneAsset: Boolean, + paymentAmount: Int, + paymentId: String +) = { # data from pool config - let cfg = gpc() - let lpId = cfg[idxLPAsId].fromBase58String() - let amIdStr = cfg[idxAmAsId] - let prIdStr = cfg[idxPrAsId] - let inAmIdStr = cfg[idxIAmtAsId] - let inPrIdStr = cfg[idxIPriceAsId] - let amtDcm = cfg[idxAmtAsDcm].parseIntValue() - let priceDcm = cfg[idxPriceAsDcm].parseIntValue() - let sts = cfg[idxPoolSt] - - let lpEm = assetInfo(lpId).valueOrErrorMessage("Wr lp as").quantity - - # get current balances from acc - let amBalance = if(isEval) then getAccBalance(amIdStr) else - if(isOneAsset && pmtId == amIdStr) then getAccBalance(amIdStr) - pmtAmt else - if(isOneAsset) then getAccBalance(amIdStr) else getAccBalance(amIdStr) - inAmAmt - let prBalance = if(isEval) then getAccBalance(prIdStr) else - if(isOneAsset && pmtId == prIdStr) then getAccBalance(prIdStr) - pmtAmt else - if(isOneAsset) then getAccBalance(prIdStr) else getAccBalance(prIdStr) - inPrAmt - - #if(true) then throw("lpEmission = "+ lpEmission.toString() + - # " amBalance = " + amBalance.toString() + - # " prBalance = " + prBalance.toString() + - # " getAccBalance(amIdStr) = " + getAccBalance(amIdStr).toString() + - # " etAccBalance(prIdStr) = " + getAccBalance(prIdStr).toString()) else + let cfg = getPoolConfig() + let lpAssetId = cfg[idxLPAsId].fromBase58String() + let amountIdStr = cfg[idxAmAsId] + let priceIdStr = cfg[idxPrAsId] + let inAmountIdStr = cfg[idxIAmtAsId] + let inPriceIdStr = cfg[idxIPriceAsId] + let amountDecimals = cfg[idxAmtAsDcm].parseIntValue() + let priceDecimals = cfg[idxPriceAsDcm].parseIntValue() + let sts = cfg[idxPoolSt] + + let lpEmission = assetInfo(lpAssetId).valueOrErrorMessage("Wrong lp asset").quantity + + # get current balances from account + let amountBalance = if(isEvaluate) then getAccBalance(amountIdStr) else + if(isOneAsset && paymentId == amountIdStr) then getAccBalance(amountIdStr) - paymentAmount else + if(isOneAsset) then getAccBalance(amountIdStr) else getAccBalance(amountIdStr) - inAmountAssetAmount + + let priceBalance = if(isEvaluate) then getAccBalance(priceIdStr) else + if(isOneAsset && paymentId == priceIdStr) then getAccBalance(priceIdStr) - paymentAmount else + if(isOneAsset) then getAccBalance(priceIdStr) else getAccBalance(priceIdStr) - inPriceAssetAmount + + let amountBalanceDefaultDecimals = fraction(amountBalance, defaultDecimals, amountDecimals) + let priceBalanceDefaultDecimals = fraction(priceBalance, defaultDecimals, priceDecimals) + + let inAmountAssetAmountDefaultDecimals = fraction(inAmountAssetAmount, defaultDecimals, amountDecimals) + let inPriceAssetAmountDefaultDecimals = fraction(inPriceAssetAmount, defaultDecimals, priceDecimals) # cast amounts to the lp decimals - let inAmAssetAmtX18 = inAmAmt.t1(amtDcm) - let inPrAssetAmtX18 = inPrAmt.t1(priceDcm) + let inAmountAssetAmountX18 = inAmountAssetAmountDefaultDecimals.toX18(defaultDecimals) + let inPriceAssetAmountX18 = inPriceAssetAmountDefaultDecimals.toX18(defaultDecimals) # calc user expected price - let userPriceX18 = cpbi(inPrAssetAmtX18, inAmAssetAmtX18) + let userPriceX18 = calcPriceBigInt(inPriceAssetAmountX18, inAmountAssetAmountX18) - # calc pool price - let amBalanceX18 = amBalance.t1(amtDcm) - let prBalanceX18 = prBalance.t1(priceDcm) + let amountBalanceX18 = amountBalanceDefaultDecimals.toX18(defaultDecimals) + let priceBalanceX18 = priceBalanceDefaultDecimals.toX18(defaultDecimals) # case of the initial or first deposit # result is a tuple containing the following: @@ -356,94 +357,106 @@ func epo(txId58: String, # 2. amtAsset amount that goes to Pool liquidity # 3. priceAsset amount that goes to Pool liquidity # 4. pool price after PUT operation - let r = if(lpEm == 0) then { - let curPriceX18 = zeroBigInt - let slippageX18 = zeroBigInt - # calc initial deposit by geometric mean - let lpAmtX18 = pow(inAmAssetAmtX18 * inPrAssetAmtX18, 0, 5.toBigInt(), 1, 0, DOWN) - ( - lpAmtX18.f1(scale8), - inAmAssetAmtX18.f1(amtDcm), - inPrAssetAmtX18.f1(priceDcm), - cpbi(prBalanceX18 + inPrAssetAmtX18, amBalanceX18 + inAmAssetAmtX18), - slippageX18 - ) + let r = if(lpEmission == 0) then { + let currentPriceX18 = zeroBigInt + let slippageX18 = zeroBigInt + # calc initial deposit by geometric mean + let lpAmountX18 = pow(inAmountAssetAmountX18 * inPriceAssetAmountX18, 0, 5.toBigInt(), 1, 0, DOWN) + ( + lpAmountX18.fromX18(scale8), + inAmountAssetAmountX18.fromX18(defaultDecimals), + inPriceAssetAmountX18.fromX18(defaultDecimals), + calcPriceBigInt(priceBalanceX18 + inPriceAssetAmountX18, amountBalanceX18 + inAmountAssetAmountX18), + slippageX18 + ) } else { - let curPriceX18 = cpbi(prBalanceX18, amBalanceX18) - let slippageRealX18 = fraction(abs(curPriceX18 - userPriceX18), scale18, curPriceX18) - let slippageX18 = slippage.t1(scale8) - # validate slippage - if (curPriceX18 != zeroBigInt && slippageRealX18 > slippageX18) then throw("Price slippage " + slippageRealX18.toString() + " > " + slippageX18.toString()) else - - let lpEmissionX18 = lpEm.t1(scale8) - # calculate amount of price asset needed to deposit pool by current price and user's amountAsset amount - let prViaAmX18 = fraction(inAmAssetAmtX18, curPriceX18, scale18) - let amViaPrX18 = fraction(inPrAssetAmtX18, scale18, curPriceX18) - - # calculate amount and price assets to perform pool deposit in proportion to current pool price - let expectedAmts= if (prViaAmX18 > inPrAssetAmtX18) - then (amViaPrX18, inPrAssetAmtX18) - else (inAmAssetAmtX18, prViaAmX18) - - let expAmtAssetAmtX18 = expectedAmts._1 - let expPriceAssetAmtX18 = expectedAmts._2 - # calculate LP amount that user - let lpAmtX18 = fraction(lpEmissionX18, expPriceAssetAmtX18, prBalanceX18) - ( - lpAmtX18.f1(scale8), - expAmtAssetAmtX18.f1(amtDcm), - expPriceAssetAmtX18.f1(priceDcm), - curPriceX18, - slippageX18 - ) - } - - let calcLpAmt = r._1 - let calcAmAssetPmt = r._2 - let calcPrAssetPmt = r._3 - let curPrice = r._4.f1(scale8) - let slippageCalc = r._5.f1(scale8) - - if(calcLpAmt <= 0) then throw("LP <= 0") else - - let emitLpAmt = if (!emitLp) then 0 else calcLpAmt - let amDiff = inAmAmt - calcAmAssetPmt - let prDiff = inPrAmt - calcPrAssetPmt - - let (writeAmAmt, writePrAmt) = if(isOneAsset && pmtId == amIdStr) - then (pmtAmt, 0) - else if(isOneAsset && pmtId == prIdStr) - then (0, pmtAmt) - else (calcAmAssetPmt, calcPrAssetPmt) + let currentPriceX18 = calcPriceBigInt(priceBalanceX18, amountBalanceX18) + let slippageRealX18 = fraction(abs(currentPriceX18 - userPriceX18), scale18, currentPriceX18) + let slippageX18 = slippageTolerance.toX18(scale8) + + strict validateSlippage = (currentPriceX18 != zeroBigInt && slippageRealX18 > slippageX18) == false || + throwErr("Price slippage " + slippageRealX18.toString() + " > " + slippageX18.toString()) + + let lpEmissionX18 = lpEmission.toX18(scale8) + # calculate amount of price asset needed to deposit pool by current price and user's amountAsset amount + let prViaAmX18 = fraction(inAmountAssetAmountX18, currentPriceX18, scale18) + let amViaPrX18 = fraction(inPriceAssetAmountX18, scale18, currentPriceX18) + + # calculate amount and price assets to perform pool deposit in proportion to current pool price + let expectedAmounts = if (prViaAmX18 > inPriceAssetAmountX18) + then (amViaPrX18, inAmountAssetAmountX18) + else (inAmountAssetAmountX18, prViaAmX18) + + let expectedAmountAssetAmountX18 = expectedAmounts._1 + let expectedPriceAssetAmountX18 = expectedAmounts._2 + # calculate LP amount that user + let lpAmountX18 = fraction(lpEmissionX18, expectedPriceAssetAmountX18, priceBalanceX18) + ( + lpAmountX18.fromX18(scale8), + expectedAmountAssetAmountX18.fromX18(defaultDecimals), + expectedPriceAssetAmountX18.fromX18(defaultDecimals), + currentPriceX18, + slippageX18 + ) + } + + let calculateLpAmount = r._1 + let calculateAmountAssetPayment = fraction(r._2, amountDecimals, defaultDecimals) + let calculatePriceAssetPayment = fraction(r._3, priceDecimals, defaultDecimals) + let currentPrice = r._4.fromX18(scale8) + let slippageCalculate = r._5.fromX18(scale8) + + strict checkCalcLpAmount = calculateLpAmount > 0 || throwErr("LP <= 0") + + let emitLpAmount = if (!emitLp) then 0 else calculateLpAmount + let amountDiff = inAmountAssetAmount - calculateAmountAssetPayment + let priceDiff = inPriceAssetAmount - calculatePriceAssetPayment + + let (writeAmAmt, writePrAmt) = if(isOneAsset && paymentId == amountIdStr) + then (paymentAmount, 0) + else if(isOneAsset && paymentId == priceIdStr) + then (0, paymentAmount) + else (calculateAmountAssetPayment, calculatePriceAssetPayment) let commonState = [ - IntegerEntry(pl(), curPrice), - IntegerEntry(ph(height, lastBlock.timestamp), curPrice), + IntegerEntry(keyPriceLast(), currentPrice), + IntegerEntry(keyPriceHistory(height, lastBlock.timestamp), currentPrice), StringEntry( - pau(userAddress, txId58), - dataPutActionInfo(writeAmAmt, writePrAmt, emitLpAmt, curPrice, slippage, slippageCalc, height, lastBlock.timestamp, amDiff, prDiff)) + keyPutActionByUser(userAddress, txId58), + dataPutActionInfo( + writeAmAmt, + writePrAmt, + emitLpAmount, + currentPrice, + slippageTolerance, + slippageCalculate, + height, + lastBlock.timestamp, + amountDiff, + priceDiff + ) + ) ] ( - calcLpAmt, # 1. - emitLpAmt, # 2. - curPrice, # 3. - amBalance, # 4. - prBalance, # 5. - lpEm, # 6. - lpId, # 7. - sts, # 8. - commonState, # 9. - amDiff, # 10. - prDiff, # 11. - inAmId, # 12 - inPrId # 13 + calculateLpAmount, # 1. + emitLpAmount, # 2. + currentPrice, # 3. + amountBalance, # 4. + priceBalance, # 5. + lpEmission, # 6. + lpAssetId, # 7. + sts, # 8. + commonState, # 9. + amountDiff, # 10. + priceDiff, # 11. + inAmountAssetId, # 12. + inPriceAssetId # 13. ) } -# validateMatcherOrderAllowed -func moa(order: Order) = { - let cfg = gpc() +func validateMatcherOrderAllowed(order: Order) = { + let cfg = getPoolConfig() let amtAsId = cfg[idxAmAsId] let prAsId = cfg[idxPrAsId] let sts = cfg[idxPoolSt].parseIntValue() @@ -454,132 +467,138 @@ func moa(order: Order) = { let accAmtAsBalance = getAccBalance(amtAsId) let accPrAsBalance = getAccBalance(prAsId) - let curPriceX18 = if(order.orderType == Buy) - then pcp(amtAsDcm, prAsDcm, accAmtAsBalance + order.amount, accPrAsBalance) - else pcp(amtAsDcm, prAsDcm, accAmtAsBalance - order.amount, accPrAsBalance) - let curPrice = curPriceX18.f1(scale8) + let currentPriceX18 = if(order.orderType == Buy) + then privateCalcPrice(amtAsDcm, prAsDcm, accAmtAsBalance + order.amount, accPrAsBalance) + else privateCalcPrice(amtAsDcm, prAsDcm, accAmtAsBalance - order.amount, accPrAsBalance) + let curPrice = currentPriceX18.fromX18(scale8) # validate status - if (igs() || sts == PoolMatcherDis || sts == PoolShutdown) then throw("Admin blocked") else + if (isGlobalShutdown() || sts == PoolMatcherDis || sts == PoolShutdown) then throwErr("Admin blocked") else # validate pairs let orAmtAsset = order.assetPair.amountAsset let orAmtAsStr = if( orAmtAsset == unit) then "WAVES" else toBase58String(orAmtAsset.value()) let orPrAsset = order.assetPair.priceAsset let orPrAsStr = if( orPrAsset == unit) then "WAVES" else toBase58String(orPrAsset.value()) - if(orAmtAsStr != amtAsId || orPrAsStr != prAsId) then throw("Wr assets") else + if(orAmtAsStr != amtAsId || orPrAsStr != prAsId) then throwErr("Wr assets") else let orderPrice = order.price #priceDecimals = 8 + priceAssetDcm - amtAssetDcm - let priceDcm = fraction(scale8, prAsDcm, amtAsDcm) - let castOrderPrice = orderPrice.ts(scale8, priceDcm) + let priceDecimals = fraction(scale8, prAsDcm, amtAsDcm) + let castOrderPrice = orderPrice.ts(scale8, priceDecimals) let isOrderPriceValid = if(order.orderType == Buy) then castOrderPrice <= curPrice else castOrderPrice >= curPrice #if(!isOrderPriceValid) then throw("Order price leads to K decrease. castedOrderPrice="+castedOrderPrice.toString() + " curPrice="+curPrice.toString()) else true } -# commonGet -func cg(i: Invocation) = { - if (i.payments.size() != 1) then throw("1 pmnt exp") else +func commonGet(i: Invocation) = { + strict checkPayments = i.payments.size() == 1 || throwErr("1 payment expected") - let pmt = i.payments[0].value() - let pmtAssetId = pmt.assetId.value() - let pmtAmt = pmt.amount + let payment = i.payments[0].value() + let paymentAssetId = payment.assetId.value() + let paymentAmount = payment.amount - let r = ego(i.transactionId.toBase58String(), pmtAssetId.toBase58String(), pmtAmt, i.caller) - let outAmAmt = r._1 - let outPrAmt = r._2 - let sts = r._9.parseIntValue() + let r = estimateGetOperation( + i.transactionId.toBase58String(), + paymentAssetId.toBase58String(), + paymentAmount, + i.caller + ) + let outAmountAmount = r._1 + let outPriceAmount = r._2 + let sts = r._9.parseIntValue() let state = r._10 - if(igs() || sts == PoolShutdown) then throw("Admin blocked: " + sts.toString()) else + if(isGlobalShutdown() || sts == PoolShutdown) then throwErr("Admin blocked: " + sts.toString()) else - (outAmAmt, - outPrAmt, - pmtAmt, - pmtAssetId, + (outAmountAmount, + outPriceAmount, + paymentAmount, + paymentAssetId, state ) } -# commonPut -func cp(caller: String, - txId: String, - amAsPmt: AttachedPayment, - prAsPmt: AttachedPayment, - slippage: Int, - emitLp: Boolean, - isOneAsset: Boolean, - pmtAmt: Int, - pmtId: String) = { - - let r = epo(txId, # i.transactionId.toBase58String() - slippage, - amAsPmt.value().amount, - amAsPmt.value().assetId, - prAsPmt.value().amount, - prAsPmt.value().assetId, - caller, #i.caller.toString() - false, - emitLp, - isOneAsset, - pmtAmt, - pmtId) - - let sts = r._8.parseIntValue() - if(igs() || sts == PoolPutDis || sts == PoolShutdown) then throw("Blocked:" + sts.toString()) else - r +func commonPut( + caller: String, + txId: String, + amountAssetPayment: AttachedPayment, + priceAssetPayment: AttachedPayment, + slippage: Int, + emitLp: Boolean, + isOneAsset: Boolean, + paymentAmount: Int, + paymentId: String +) = { + let r = estimatePutOperation( + txId, + slippage, + amountAssetPayment.value().amount, + amountAssetPayment.value().assetId, + priceAssetPayment.value().amount, + priceAssetPayment.value().assetId, + caller, + false, + emitLp, + isOneAsset, + paymentAmount, + paymentId + ) + + let sts = r._8.parseIntValue() + if(isGlobalShutdown() || sts == PoolPutDis || sts == PoolShutdown) then throwErr("Blocked:" + sts.toString()) else + r +} + +func takeFee(amount: Int) = { + let fee = fraction(amount, feePermille, thousand) + (amount - fee, fee) } -# managerPublicKeyOrUnit -func m() = match mpk().getString() { +func managerPublicKeyOrUnit() = match keyManagerPublicKey().getString() { case s: String => s.fromBase58String() case _: Unit => unit } -# pendingManagerPublicKeyOrUnit -func pm() = match pmpk().getString() { +func pendingManagerPublicKeyOrUnit() = match keyPendingManagerPublicKey().getString() { case s: String => s.fromBase58String() case _: Unit => unit } -let pd = "Permission denied".throw() +let pd = "Permission denied".throwErr() -# mustManager -func mm(i: Invocation) = { - match m() { +func mustManager(i: Invocation) = { + match managerPublicKeyOrUnit() { case pk: ByteVector => i.callerPublicKey == pk || pd case _: Unit => i.caller == this || pd } } @Callable(i) -# ido, team, emission, staking, locking (boosting), rest -# fc - factoryContract -func constructor(fc: String) = { - strict c = i.mm() +func constructor(factoryContract: String) = { + strict checkCaller = i.mustManager() - [StringEntry(fc(), fc)] + [StringEntry(keyFactoryContact(), factoryContract)] } @Callable(i) func setManager(pendingManagerPublicKey: String) = { - strict c = i.mm() - strict cm = pendingManagerPublicKey.fromBase58String() + strict checkCaller = i.mustManager() + strict checkManagerPublicKey = pendingManagerPublicKey.fromBase58String() - [StringEntry(pmpk(), pendingManagerPublicKey)] + [StringEntry(keyPendingManagerPublicKey(), pendingManagerPublicKey)] } @Callable(i) func confirmManager() = { - let p = pm() - strict hpm = p.isDefined() || throw("No pending manager") - strict cpm = i.callerPublicKey == p.value() || throw("You are not pending manager") + let pm = pendingManagerPublicKeyOrUnit() + strict hasPM = pm.isDefined() || throwErr("No pending manager") + strict checkPM = i.callerPublicKey == pm.value() || throwErr("You are not pending manager") [ - StringEntry(mpk(), p.value().toBase58String()), - DeleteEntry(pmpk()) + StringEntry(keyManagerPublicKey(), pm.value().toBase58String()), + DeleteEntry(keyPendingManagerPublicKey()) ] } @@ -599,153 +618,173 @@ func confirmManager() = { # return: # transfer LP tokens based on deposit share @Callable(i) -func put(slip: Int, autoStake: Boolean) = { - let factCfg = gfc() - let stakingCntr = addressFromString(factCfg[idxFactStakCntr]).valueOrErrorMessage("Wr st addr") - let slipCntr = addressFromString(factCfg[idxFactSlippCntr]).valueOrErrorMessage("Wr sl addr") - if(slip < 0) then throw("Wrong slippage") else - if (i.payments.size() != 2) then throw("2 pmnts expd") else - # estPut - let e = cp(i.caller.toString(), - i.transactionId.toBase58String(), - AttachedPayment(i.payments[0].value().assetId, i.payments[0].value().amount), - i.payments[1], - slip, - true, - false, - 0, - "") - - let emitLpAmt = e._2 - let lpAssetId = e._7 - let state = e._9 - let amDiff = e._10 - let prDiff = e._11 - let amId = e._12 - let prId = e._13 +func put(slippage: Int, autoStake: Boolean) = { + let factCfg = getFactoryConfig() + let stakingContract = addressFromString(factCfg[idxFactStakCntr]).valueOrErrorMessage("Wrong staking contract") + let slippageContract = addressFromString(factCfg[idxFactSlippCntr]).valueOrErrorMessage("Wrogn slippage contract") + strict slippageCheck = slippage >= 0 || throwErr("wrong slippage") + strict paymentsCheck = i.payments.size() == 2 || throwErr("2 payments expected") + + let estimatePut = commonPut( + i.caller.toString(), + i.transactionId.toBase58String(), + AttachedPayment(i.payments[0].value().assetId, i.payments[0].value().amount), + i.payments[1], + slippage, + true, + false, + 0, + "" + ) + + let emitLpAmount = estimatePut._2 + let lpAssetId = estimatePut._7 + let state = estimatePut._9 + let amountDiff = estimatePut._10 + let priceDiff = estimatePut._11 + let amountId = estimatePut._12 + let priceId = estimatePut._13 # emit lp on factory - strict r = fca.invoke("emit", [emitLpAmt], []) + strict r = factoryContract.invoke("emit", [emitLpAmount], []) # if the lp instance address is in the legacy list then the legacy factory address will be returned from the factory strict el = match (r) { - case legacy: Address => legacy.invoke("emit", [emitLpAmt], []) + case legacy: Address => legacy.invoke("emit", [emitLpAmount], []) case _ => unit } - strict sa = if(amDiff > 0) then slipCntr.invoke("put",[],[AttachedPayment(amId, amDiff)]) else [] - strict sp = if(prDiff > 0) then slipCntr.invoke("put",[],[AttachedPayment(prId, prDiff)]) else [] - - let lpTrnsfr = - if(autoStake) then strict ss = stakingCntr.invoke("stake",[],[AttachedPayment(lpAssetId, emitLpAmt)]); [] - else [ScriptTransfer(i.caller, emitLpAmt, lpAssetId)] - - state++lpTrnsfr + strict sa = if(amountDiff > 0) then { + slippageContract.invoke("put",[],[AttachedPayment(amountId, amountDiff)]) + } else [] + strict sp = if(priceDiff > 0) then { + slippageContract.invoke("put",[],[AttachedPayment(priceId, priceDiff)]) + } else [] + + let lpTrasfer = + if(autoStake) then strict ss = stakingContract.invoke("stake",[],[AttachedPayment(lpAssetId, emitLpAmount)]); [] + else [ScriptTransfer(i.caller, emitLpAmount, lpAssetId)] + + state++lpTrasfer } # slippage in scale8 @Callable(i) -func putOneTkn(amAssetPart: Int, prAssetPart: Int, outLp: Int, slippage: Int, autoStake: Boolean) = { - let cfg = gfc() +func putOneTkn(amountAssetPart: Int, priceAssetPart: Int, outLp: Int, slippage: Int, autoStake: Boolean) = { + let cfg = getFactoryConfig() #if(true) then throw("off") else - let stakingCntr = addressFromString(cfg[idxFactStakCntr]).valueOrErrorMessage("Wr st addr") - let slipCntr = addressFromString(cfg[idxFactSlippCntr]).valueOrErrorMessage("Wr sl addr") - let gwxCntr = addressFromString(cfg[idxFactGwxRewCntr]).valueOrErrorMessage("Wr gwx addr") - let poolCfg = gpc() - let amId = poolCfg[idxAmAsId] - let prId = poolCfg[idxPrAsId] - let amDcm = poolCfg[idxAmtAsDcm].parseIntValue() - let prDcm = poolCfg[idxPriceAsDcm].parseIntValue() - - let addon = this.getString(ada()).valueOrElse("") + let stakingContract = addressFromString(cfg[idxFactStakCntr]).valueOrErrorMessage("Wrong staking contract") + let slippageContract = addressFromString(cfg[idxFactSlippCntr]).valueOrErrorMessage("Wrong slippage contract") + let gwxRewardContract = addressFromString(cfg[idxFactGwxRewCntr]).valueOrErrorMessage("Wrong gwx reward contract") + let poolCfg = getPoolConfig() + let amountId = poolCfg[idxAmAsId] + let priceId = poolCfg[idxPrAsId] + let amountDecimals = poolCfg[idxAmtAsDcm].parseIntValue() + let priceDecimals = poolCfg[idxPriceAsDcm].parseIntValue() + + let addon = this.getString(keyAddonAddress()).valueOrElse("") let userAddress = if(addon == i.caller.toString()) then i.originCaller else i.caller - let addonContract = addressFromString( - ada().getString().valueOrErrorMessage("no addons") - ).valueOrErrorMessage("addon address in not valid") - - strict check = addonContract.reentrantInvoke("ensureCanPutOneTkn", [userAddress.toString()], []); + if(slippage <= 0 || amountAssetPart<=0 || priceAssetPart<=0 || outLp <= 0) then throwErr("Wrong params") else + if (i.payments.size() != 1) then throwErr("1 payment expected") else - if(slippage <= 0 || amAssetPart<=0 || prAssetPart<=0 || outLp <= 0) then throw("Wrong params") else - if (i.payments.size() != 1) then throw("1 pmnt expd") else + let payment = i.payments[0].value() + let paymentAssetId = payment.assetId.value().toBase58String() + let paymentAmountRaw = payment.amount + let (paymentAmount, feeAmount) = paymentAmountRaw.takeFee() - let pmt = i.payments[0].value() - let pmtAssetId = pmt.assetId.value().toBase58String() - let pmtAmt = pmt.amount - if(pmtAmt < amAssetPart || pmtAmt < prAssetPart || pmtAmt < 10_000_000) then throw("Wrong pmt amt") else + if (paymentAmount < amountAssetPart || paymentAmount < priceAssetPart || paymentAmount < 10_000_000) then { + throwErr("wrong payment amount") + } else - let amBalance = getAccBalance(amId) - let prBalance = getAccBalance(prId) + let amountBalance = getAccBalance(amountId) + let priceBalance = getAccBalance(priceId) - let (amBalanceNow, prBalanceNow, virtSwapInAm, virtSwapOutPr, virtSwapInPr, virtSwapOutAm) = - if (pmtAssetId == amId) then { - (amBalance-pmtAmt, prBalance, pmtAmt - amAssetPart, prAssetPart, 0, 0) - } else if (pmtAssetId == prId) then { - (amBalance, prBalance-pmtAmt, 0, 0, pmtAmt - prAssetPart, amAssetPart) - } else throw("wrong pmtAssetId") + let (amountBalanceNow, priceBalanceNow, virtSwapInAm, virtSwapOutPr, virtSwapInPr, virtSwapOutAm) = + if (paymentAssetId == amountId) then { + (amountBalance-paymentAmount, priceBalance, paymentAmount - amountAssetPart, priceAssetPart, 0, 0) + } else if (paymentAssetId == priceId) then { + (amountBalance, priceBalance-paymentAmount, 0, 0, paymentAmount - priceAssetPart, amountAssetPart) + } else throwErr("wrong paymentAssetId") - let D0 = gwxCntr.invoke("calcD", [ - amBalanceNow.toString(), - prBalanceNow.toString(), - A, Amult, Dconv],[]) + let D0 = gwxRewardContract.invoke("calcD", [ + amountBalanceNow.toString(), + priceBalanceNow.toString(), + amplificator, Amult, Dconv],[] + ) - let D1 = gwxCntr.invoke("calcD", [ - (amBalanceNow + virtSwapInAm - virtSwapOutAm).toBigInt().toString(), - (prBalanceNow + virtSwapInPr - virtSwapOutPr).toBigInt().toString(), - A, Amult, Dconv],[]) + let D1 = gwxRewardContract.invoke("calcD", [ + (amountBalanceNow + virtSwapInAm - virtSwapOutAm).toBigInt().toString(), + (priceBalanceNow + virtSwapInPr - virtSwapOutPr).toBigInt().toString(), + amplificator, Amult, Dconv],[] + ) - strict D0vsD1 = vd(D1.str().parseBigIntValue(), D0.str().parseBigIntValue(), slippage4D) + strict D0vsD1 = validateD(D1.str().parseBigIntValue(), D0.str().parseBigIntValue(), slippage4D) - let estPut = cp(i.caller.toString(), - i.transactionId.toBase58String(), - AttachedPayment(amId.fromBase58String(), amAssetPart), - AttachedPayment(prId.fromBase58String(), prAssetPart), - slippage, - true, - true, - pmtAmt, - pmtAssetId) + let estimatePut = commonPut( + i.caller.toString(), + i.transactionId.toBase58String(), + AttachedPayment(amountId.fromBase58String(), amountAssetPart), + AttachedPayment(priceId.fromBase58String(), priceAssetPart), + slippage, + true, + true, + paymentAmount, + paymentAssetId + ) - let estimLP = estPut._2 - let lpAssetId = estPut._7 - let state = estPut._9 - let amDiff = estPut._10 - let prDiff = estPut._11 + let estimateLP = estimatePut._2 + let lpAssetId = estimatePut._7 + let state = estimatePut._9 + let amountDiff = estimatePut._10 + let priceDiff = estimatePut._11 - let lpCalcRes = vad(estimLP.toBigInt(), outLp.toBigInt(), slippage.toBigInt()) + let lpCalcRes = validateAbsDiff(estimateLP.toBigInt(), outLp.toBigInt(), slippage.toBigInt()) - let emitLpAmt = lpCalcRes._2.toInt() + let emitLpAmount = lpCalcRes._2.toInt() # emit lp on factory - strict e = fca.invoke("emit", [emitLpAmt], []) + strict e = factoryContract.invoke("emit", [emitLpAmount], []) # if the lp instance address is in the legacy list then the legacy factory address will be returned from the factory strict el = match (e) { - case legacy: Address => legacy.invoke("emit", [emitLpAmt], []) + case legacy: Address => legacy.invoke("emit", [emitLpAmount], []) case _ => unit } - strict sa = if(amDiff > 0) then slipCntr.invoke("put",[],[AttachedPayment(amId.fromBase58String(), amDiff)]) else [] - strict sp = if(prDiff > 0) then slipCntr.invoke("put",[],[AttachedPayment(prId.fromBase58String(), prDiff)]) else [] + strict sa = if (amountDiff > 0) then { + slippageContract.invoke("put",[],[AttachedPayment(amountId.fromBase58String(), amountDiff)]) + } else [] + strict sp = if (priceDiff > 0) then { + slippageContract.invoke("put",[],[AttachedPayment(priceId.fromBase58String(), priceDiff)]) + } else [] + + let lpTrasfer = if (autoStake) then { + strict ss = stakingContract.invoke("stake",[],[AttachedPayment(lpAssetId, emitLpAmount)]); [] + } else { + [ScriptTransfer(i.caller, emitLpAmount, lpAssetId)] + } - let lpTrnsfr = - if(autoStake) then strict ss = stakingCntr.invoke("stake",[],[AttachedPayment(lpAssetId, emitLpAmt)]); [] - else [ScriptTransfer(i.caller, emitLpAmt, lpAssetId)] + let sendFeeToMatcher = if (feeAmount > 0) then { + [ScriptTransfer(matcherAddress, feeAmount, paymentAssetId.fromBase58String())] + } else [] - state ++ lpTrnsfr + state ++ lpTrasfer ++ sendFeeToMatcher } # Put without LP emission @Callable(i) -func putForFree(maxSlpg: Int) = { - if(maxSlpg < 0) then throw("Wrong slpg") else - if (i.payments.size() != 2) then throw("2 pmnts expd") else - let estPut = cp(i.caller.toString(), - i.transactionId.toBase58String(), - AttachedPayment(i.payments[0].value().assetId, i.payments[0].value().amount), - i.payments[1], - maxSlpg, - false, - false, - 0, - "") - estPut._9 +func putForFree(maxslippage: Int) = { + if (maxslippage < 0) then throwErr("wrong slippage") else + if (i.payments.size() != 2) then throwErr("2 payments expected") else + let estimatePut = commonPut( + i.caller.toString(), + i.transactionId.toBase58String(), + AttachedPayment(i.payments[0].value().assetId, i.payments[0].value().amount), + i.payments[1], + maxslippage, + false, + false, + 0, + "" + ) + estimatePut._9 } # Called by: LP @@ -761,143 +800,174 @@ func putForFree(maxSlpg: Int) = { # transfer to user his share of pool tokens base on passed lp token amount @Callable(i) func get() = { - let r = cg(i) - let outAmtAmt = r._1 - let outPrAmt = r._2 - let pmtAmt = r._3 - let pmtAssetId = r._4 - let state = r._5 - strict b = invoke(fca, - "burn", - [pmtAmt], - [AttachedPayment(pmtAssetId, pmtAmt)]) + let r = commonGet(i) + let outAmtAmt = r._1 + let outPriceAmount = r._2 + let paymentAmount = r._3 + let paymentAssetId = r._4 + let state = r._5 + strict b = invoke( + factoryContract, + "burn", + [paymentAmount], + [AttachedPayment(paymentAssetId, paymentAmount)] + ) state } @Callable(i) func getOneTkn(exchResult: Int, notUsed: Int, outAmount: Int, outAssetId: String, slippage: Int) = { - if (i.payments.size() != 1) then throw("1 pmnt expd") else + if (i.payments.size() != 1) then throwErr("1 payment expected") else #if(true) then throw("off") else - let cfg = gpc() - let lpId = cfg[idxLPAsId] - let amId = cfg[idxAmAsId] - let prId = cfg[idxPrAsId] - let amDcm = cfg[idxAmtAsDcm].parseIntValue() - let prDcm = cfg[idxPriceAsDcm].parseIntValue() - let sts = cfg[idxPoolSt] - - let factCfg = gfc() - let gwxCntr = addressFromString(factCfg[idxFactGwxRewCntr]).valueOrErrorMessage("Wr sl addr") - - let pmt = i.payments[0].value() - - # todo: addon is a separate contract with unstake and get on tkn operation, should me merged with this contract once max script size is increased - let addon = this.getString(ada()).valueOrElse("") - let userAddress = if(addon == i.caller.toString()) then i.originCaller else i.caller - let txId58 = i.transactionId.toBase58String() - let pmtAssetId = pmt.assetId.value() - let pmtAmt = pmt.amount - - let addonContract = addressFromString( - ada().getString().valueOrErrorMessage("no addons") - ).valueOrErrorMessage("addon address in not valid") - - strict check = addonContract.reentrantInvoke("ensureCanGetOneTkn", [userAddress.toString()], []); + let cfg = getPoolConfig() + let lpAssetId = cfg[idxLPAsId] + let amountId = cfg[idxAmAsId] + let priceId = cfg[idxPrAsId] + let amountDecimals = cfg[idxAmtAsDcm].parseIntValue() + let priceDecimals = cfg[idxPriceAsDcm].parseIntValue() + let sts = cfg[idxPoolSt] + + let factCfg = getFactoryConfig() + let gwxRewardContract = addressFromString( + factCfg[idxFactGwxRewCntr] + ).valueOrErrorMessage("Wrong gwxRewardContract address") + let payment = i.payments[0].value() + + # todo: addon is a separate contract with unstake and get on tkn operation, + # should me merged with this contract once max script size is increased + let addon = this.getString(keyAddonAddress()).valueOrElse("") + let userAddress = if(addon == i.caller.toString()) then i.originCaller else i.caller + let txId58 = i.transactionId.toBase58String() + let paymentAssetId = payment.assetId.value() + let paymentAmount = payment.amount + + if(paymentAmount < 10_00_000_000) then throwErr("Min payment 10 LP") else + + if(slippage < 0 || exchResult < 0 || outAmount < 0) then throwErr("Wrong params") else + if (lpAssetId != paymentAssetId.toBase58String()) then throwErr("Wrong LP") else + + let r = estimateGetOperation( + i.transactionId.toBase58String(), + paymentAssetId.toBase58String(), + paymentAmount, + i.caller + ) + let estimAmAmt = r._1 + let estimPrAmt = r._2 - if(pmtAmt < 10_00_000_000) then throw("Min pmt 10 LP") else + let amountBalance = getAccBalance(amountId) + let priceBalance = getAccBalance(priceId) - if(slippage < 0 || exchResult < 0 || outAmount < 0) then throw("Wrong params") else - if (lpId != pmtAssetId.toBase58String()) then throw("Wrong LP") else + let (amountBalanceNow, priceBalanceNow, virtSwapInAm, virtSwapOutPr, virtSwapInPr, virtSwapOutAm, totalGet) = + if (outAssetId == amountId) then { + (amountBalance - estimAmAmt, priceBalance - estimPrAmt, exchResult, estimPrAmt, 0, 0, estimAmAmt + exchResult) + } else if (outAssetId == priceId) then { + (amountBalance - estimAmAmt, priceBalance - estimPrAmt, 0, 0, exchResult, estimAmAmt, estimPrAmt + exchResult) + } else throwErr("wrong outAssetId") - let r = ego(i.transactionId.toBase58String(), pmtAssetId.toBase58String(), pmtAmt, i.caller) - let estimAmAmt = r._1 - let estimPrAmt = r._2 + if(virtSwapInAm < 0 || virtSwapInPr < 0) then throwErr("Wrong calc") else - let amBalance = getAccBalance(amId) - let prBalance = getAccBalance(prId) + let D0 = gwxRewardContract.invoke("calcD", [ + amountBalanceNow.toString(), + priceBalanceNow.toString(), + amplificator, + Amult, + Dconv + ],[]); - let (amBalanceNow, prBalanceNow, virtSwapInAm, virtSwapOutPr, virtSwapInPr, virtSwapOutAm, totalGet) = - if (outAssetId == amId) then { - (amBalance - estimAmAmt, prBalance - estimPrAmt, exchResult, estimPrAmt, 0, 0, estimAmAmt + exchResult) - } else if (outAssetId == prId) then { - (amBalance - estimAmAmt, prBalance - estimPrAmt, 0, 0, exchResult, estimAmAmt, estimPrAmt + exchResult) - } else throw("wrong outAssetId") + let D1 = gwxRewardContract.invoke("calcD", [ + (amountBalanceNow - virtSwapInAm + virtSwapOutAm).toString(), + (priceBalanceNow + virtSwapOutPr - virtSwapInPr).toString(), + amplificator, + Amult, + Dconv + ],[]); - if(virtSwapInAm < 0 || virtSwapInPr < 0) then throw("Wrong calc") else + strict D0vsD1 = validateD(D1.str().parseBigIntValue(), D0.str().parseBigIntValue(), slippage4D) - let D0 = gwxCntr.invoke("calcD", [ - amBalanceNow.toString(), - prBalanceNow.toString(), A, Amult, Dconv],[]); + strict finalRes = validateAbsDiff(totalGet.toBigInt(), outAmount.toBigInt(), slippage.toBigInt()) - let D1 = gwxCntr.invoke("calcD", [ - (amBalanceNow - virtSwapInAm + virtSwapOutAm).toString(), - (prBalanceNow + virtSwapOutPr - virtSwapInPr).toString(), A, Amult, Dconv],[]); + let (outAm, outPr) = if(outAssetId == amountId) then (finalRes._2.toInt(), 0) else (0, finalRes._2.toInt()) + let totalAmountRaw = outAm + outPr + let (totalAmount, feeAmount) = totalAmountRaw.takeFee() - strict D0vsD1 = vd(D1.str().parseBigIntValue(), D0.str().parseBigIntValue(), slippage4D) + let outAssetIdOrWaves = if (outAssetId == "WAVES") then unit else outAssetId.fromBase58String() - strict finalRes = vad(totalGet.toBigInt(), outAmount.toBigInt(), slippage.toBigInt()) + let sendFeeToMatcher = if (feeAmount > 0) then [ScriptTransfer(matcherAddress, feeAmount, outAssetIdOrWaves)] else [] - let (outAm, outPr) = if(outAssetId == amId) then (finalRes._2.toInt(), 0) else (0, finalRes._2.toInt()) + strict decimals = if amountDecimals >= priceDecimals then { + amountDecimals + } else { + priceDecimals + } - let curPrX18 = cpbi(prBalance.t1(prDcm), amBalance.t1(amDcm)) - let curPr = curPrX18.f1(scale8) + let curPrX18 = calcPriceBigInt(priceBalance.toX18(decimals), amountBalance.toX18(decimals)) + let curPr = curPrX18.fromX18(scale8) strict state = [ - ScriptTransfer(userAddress, outAm + outPr, if (outAssetId == "WAVES") then unit else outAssetId.fromBase58String()), + ScriptTransfer(userAddress, totalAmount, outAssetIdOrWaves), StringEntry( - gau(userAddress.toString(), txId58), - dataGetActionInfo(outAm, outPr, pmtAmt, curPr, height, lastBlock.timestamp)), - IntegerEntry(pl(), curPr), - IntegerEntry(ph(height, lastBlock.timestamp), curPr) + keyGetActionByUser(userAddress.toString(), txId58), + dataGetActionInfo(outAm, outPr, paymentAmount, curPr, height, lastBlock.timestamp) + ), + IntegerEntry(keyPriceLast(), curPr), + IntegerEntry(keyPriceHistory(height, lastBlock.timestamp), curPr) ] - strict burn = invoke(fca, - "burn", - [pmtAmt], - [AttachedPayment(pmtAssetId, pmtAmt)]) - state + strict burn = invoke( + factoryContract, + "burn", + [paymentAmount], + [AttachedPayment(paymentAssetId, paymentAmount)]) + state ++ sendFeeToMatcher } @Callable(i) func getNoLess(noLessThenAmtAsset: Int, noLessThenPriceAsset: Int) = { - let r = cg(i) - let outAmAmt = r._1 - let outPrAmt = r._2 - let pmtAmt = r._3 - let pmtAssetId = r._4 - let state = r._5 - if (outAmAmt < noLessThenAmtAsset) then throw("Failed: " + outAmAmt.toString() + " < " + noLessThenAmtAsset.toString()) else - if (outPrAmt < noLessThenPriceAsset) then throw("Failed: " + outPrAmt.toString() + " < " + noLessThenPriceAsset.toString()) else - - strict burnLPAssetOnFactory = invoke(fca, - "burn", - [pmtAmt], - [AttachedPayment(pmtAssetId, pmtAmt)]) + let r = commonGet(i) + let outAmountAmount = r._1 + let outPriceAmount = r._2 + let paymentAmount = r._3 + let paymentAssetId = r._4 + let state = r._5 + strict checkOutAmountAmount = outAmountAmount >= noLessThenAmtAsset || throwErr( + "Failed: " + outAmountAmount.toString() + " < " + noLessThenAmtAsset.toString() + ) + strict checkOutPriceAmount = outPriceAmount >= noLessThenPriceAsset || throwErr( + "Failed: " + outPriceAmount.toString() + " < " + noLessThenPriceAsset.toString() + ) + + strict burnLPAssetOnFactory = invoke( + factoryContract, + "burn", + [paymentAmount], + [AttachedPayment(paymentAssetId, paymentAmount)] + ) state } # Unstake LP tokens and exit from pool @Callable(i) func unstakeAndGet(amount: Int) = { - strict checkPayments = if (i.payments.size() != 0) then throw("No pmnts expd") else true + strict checkPayments = if (i.payments.size() != 0) then throwErr("no payments expected") else true - let cfg = gpc() - let factoryCfg = gfc() + let cfg = getPoolConfig() + let factoryCfg = getFactoryConfig() let lpAssetId = cfg[idxLPAsId].fromBase58String() - let staking = factoryCfg[idxFactStakCntr].addressFromString().valueOrErrorMessage("Wr st addr") + let staking = factoryCfg[idxFactStakCntr].addressFromString().valueOrErrorMessage("wrong") # negative amount will not pass strict unstakeInv = staking.invoke("unstake", [lpAssetId.toBase58String(), amount], []) - let r = ego(i.transactionId.toBase58String(), lpAssetId.toBase58String(), amount, i.caller) + let r = estimateGetOperation(i.transactionId.toBase58String(), lpAssetId.toBase58String(), amount, i.caller) let sts = r._9.parseIntValue() let state = r._10 - strict v = if (igs() || sts == PoolShutdown) then throw("Blocked: " + sts.toString()) else true + strict v = if (isGlobalShutdown() || sts == PoolShutdown) then throwErr("Blocked: " + sts.toString()) else true - strict burnA = invoke(fca, "burn", [amount], [AttachedPayment(lpAssetId, amount)]) + strict burnA = invoke(factoryContract, "burn", [amount], [AttachedPayment(lpAssetId, amount)]) state } @@ -913,19 +983,21 @@ func unstakeAndGet(amount: Int) = { # return: @Callable(i) func activate(amtAsStr: String, prAsStr: String) = { - if (i.caller.toString() != fca.toString()) then throw("denied") else { - ([ - StringEntry(aa(),amtAsStr), - StringEntry(pa(),prAsStr) - ], - "success") - } + if (i.caller.toString() != factoryContract.toString()) then throwErr("denied") else { + ( + [ + StringEntry(keyAmountAsset(),amtAsStr), + StringEntry(keyPriceAsset(),prAsStr) + ], + "success" + ) + } } # Set string from addon @Callable(i) func setS(k: String, v: String) = { - if (i.caller.toString() != this.strf(ada())) then pd else + if (i.caller.toString() != this.getStringOrFail(keyAddonAddress())) then pd else [ StringEntry(k, v) @@ -935,7 +1007,7 @@ func setS(k: String, v: String) = { # Set integer from addon @Callable(i) func setI(k: String, v: Int) = { - if (i.caller.toString() != this.strf(ada())) then pd else + if (i.caller.toString() != this.getStringOrFail(keyAddonAddress())) then pd else [ IntegerEntry(k, v) @@ -947,7 +1019,7 @@ func setI(k: String, v: Int) = { func getPoolConfigWrapperREADONLY() = { ( [], - gpc() + getPoolConfig() ) } @@ -976,7 +1048,7 @@ func calcPricesWrapperREADONLY(amAmt: Int, prAmt: Int, lpAmt: Int) = { func fromX18WrapperREADONLY(val: String, resScaleMult: Int) = { ( [], - f1(val.parseBigIntValue(), resScaleMult) + fromX18(val.parseBigIntValue(), resScaleMult) ) } @@ -984,7 +1056,7 @@ func fromX18WrapperREADONLY(val: String, resScaleMult: Int) = { func toX18WrapperREADONLY(origVal: Int, origScaleMult: Int) = { ( [], - t1(origVal, origScaleMult).toString() + toX18(origVal, origScaleMult).toString() ) } @@ -992,7 +1064,7 @@ func toX18WrapperREADONLY(origVal: Int, origScaleMult: Int) = { func calcPriceBigIntWrapperREADONLY(prAmtX18: String, amAmtX18: String) = { ( [], - cpbi(prAmtX18.parseBigIntValue(), amAmtX18.parseBigIntValue()).toString() + calcPriceBigInt(prAmtX18.parseBigIntValue(), amAmtX18.parseBigIntValue()).toString() ) } @@ -1000,25 +1072,25 @@ func calcPriceBigIntWrapperREADONLY(prAmtX18: String, amAmtX18: String) = { func estimatePutOperationWrapperREADONLY( txId58: String, slippage: Int, - inAmAmt: Int, + inAmountAssetAmount: Int, inAmId: ByteVector, - inPrAmt: Int, + inPriceAssetAmount: Int, inPrId: ByteVector, usrAddr: String, - isEval: Boolean, + isEvaluate: Boolean, emitLp: Boolean ) = { ( [], - epo( + estimatePutOperation( txId58, slippage, - inAmAmt, + inAmountAssetAmount, inAmId, - inPrAmt, + inPriceAssetAmount, inPrId, usrAddr, - isEval, + isEvaluate, emitLp, false, 0, @@ -1028,11 +1100,11 @@ func estimatePutOperationWrapperREADONLY( } @Callable(i) -func estimateGetOperationWrapperREADONLY(txId58: String, pmtAsId: String, pmtLpAmt: Int, usrAddr: String) = { - let r = ego( +func estimateGetOperationWrapperREADONLY(txId58: String, paymentAsId: String, paymentLpAmount: Int, usrAddr: String) = { + let r = estimateGetOperation( txId58, - pmtAsId, - pmtLpAmt, + paymentAsId, + paymentLpAmount, usrAddr.addressFromStringValue() ) ( @@ -1044,20 +1116,19 @@ func estimateGetOperationWrapperREADONLY(txId58: String, pmtAsId: String, pmtLpA @Verifier(tx) func verify() = { match tx { - case order: Order => - let mtchPub = mp() - let orV = moa(order) - let sndrV = sigVerify(order.bodyBytes, order.proofs[0], order.senderPublicKey) - let mtchV = sigVerify(order.bodyBytes, order.proofs[1], mtchPub) - - (orV && sndrV && mtchV) - || toe(orV, sndrV, mtchV) - case _ => { - let targetPublicKey = match m() { - case pk: ByteVector => pk - case _: Unit => tx.senderPublicKey - } - sigVerify(tx.bodyBytes, tx.proofs[0], targetPublicKey) - } + case order: Order => + let mtchPub = getMatcherPubOrFail() + let orV = validateMatcherOrderAllowed(order) + let sndrV = sigVerify(order.bodyBytes, order.proofs[0], order.senderPublicKey) + let mtchV = sigVerify(order.bodyBytes, order.proofs[1], mtchPub) + + (orV && sndrV && mtchV) || throwOrderError(orV, sndrV, mtchV) + case _ => { + let targetPublicKey = match managerPublicKeyOrUnit() { + case pk: ByteVector => pk + case _: Unit => tx.senderPublicKey + } + sigVerify(tx.bodyBytes, tx.proofs[0], targetPublicKey) } + } } diff --git a/ride/lp_stable_addon.ride b/ride/lp_stable_addon.ride index f1e857908..e20ab4116 100644 --- a/ride/lp_stable_addon.ride +++ b/ride/lp_stable_addon.ride @@ -12,8 +12,6 @@ let idxPrAsId = 5 # data indexes from factory config let idxFactStakCntr = 1 -let delay = "%s__delay" - #------------------------- # KEYS ON CURRENT CONTRACT #------------------------- @@ -28,8 +26,6 @@ func keyAdminPubKeys() = "%s__adminPubKeys" func keyAmp() = "%s__amp" func keyAmpHistory(height: Int) = "%s%d__amp__" + height.toString() -func lastGetOneTknCall(caller: String) = {makeString(["%s%s__lastGetOneTknCall", caller], SEP)} -func lastPutOneTknCall(caller: String) = {makeString(["%s%s__lastPutOneTknCall", caller], SEP)} #------------------------ # KEYS ON OTHER CONTRACTS #------------------------ @@ -141,7 +137,7 @@ func unstakeAndGetOneTkn(amount: Int, exchResult: Int, notUsed: Int, outAmount: # negative amount will not pass strict unstakeInv = staking.invoke("unstake", [lpAssetId.toBase58String(), amount], []) - strict getOneTkn = poolContract.reentrantInvoke("getOneTkn", [exchResult, notUsed, outAmount, outAssetId, slippage],[AttachedPayment(lpAssetId, amount)] ) + strict getOneTkn = poolContract.invoke("getOneTkn", [exchResult, notUsed, outAmount, outAssetId, slippage],[AttachedPayment(lpAssetId, amount)] ) [] } @@ -155,64 +151,6 @@ func setAmp(amp: String) = { ([], (res1, res2)) } -@Callable(i) -func ensureCanGetOneTkn(caller: String) = { - strict checkCaller = i.mustPool() - - strict ensureCanPut = match poolContract.getInteger(lastPutOneTknCall(caller)) { - case int: Int => { - let permittedHeight = int + poolContract.getInteger(delay).value() - let isReadyforPutOneTkn = height >= permittedHeight - let needBlocks = permittedHeight - height - isReadyforPutOneTkn || (["you should wait", needBlocks.toString(), "blocks more to perform the action"]).makeString(" ").throw() - } - case _ => true - } - - strict ensureCanGet = match poolContract.getInteger(lastGetOneTknCall(caller)) { - case int: Int => { - let permittedHeight = int + poolContract.getInteger(delay).value() - let isReadyforGetOneTkn = height >= permittedHeight - let needBlocks = permittedHeight - height - isReadyforGetOneTkn || (["you should wait", needBlocks.toString(), "blocks more to perform the action"]).makeString(" ").throw() - } - case _ => true - } - - strict setI = poolContract.invoke("setI", [lastPutOneTknCall(caller), height], []) - - [] -} - -@Callable(i) -func ensureCanPutOneTkn(caller: String) = { - strict checkCaller = i.mustPool() - - strict ensureCanPut = match poolContract.getInteger(lastPutOneTknCall(caller)) { - case int: Int => { - let permittedHeight = int + poolContract.getInteger(delay).value() - let isReadyforPutOneTkn = height >= permittedHeight - let needBlocks = permittedHeight - height - isReadyforPutOneTkn || (["you should wait", needBlocks.toString(), "blocks more to perform the action"]).makeString(" ").throw() - } - case _ => true - } - - strict ensureCanGet = match poolContract.getInteger(lastGetOneTknCall(caller)) { - case int: Int => { - let permittedHeight = int + poolContract.getInteger(delay).value() - let isReadyforGetOneTkn = height >= permittedHeight - let needBlocks = permittedHeight - height - isReadyforGetOneTkn || (["you should wait", needBlocks.toString(), "blocks more to perform the action"]).makeString(" ").throw() - } - case _ => true - } - - strict setI = poolContract.invoke("setI", [lastPutOneTknCall(caller), height], []) - - [] -} - @Verifier(tx) func verify() = { let targetPublicKey = match managerPublicKeyOrUnit() { diff --git a/test/lp_stable/getOneTkn.mjs b/test/lp_stable/getOneTkn.mjs index 17661b929..e195643c0 100644 --- a/test/lp_stable/getOneTkn.mjs +++ b/test/lp_stable/getOneTkn.mjs @@ -22,7 +22,6 @@ describe('lp_stable: getOneTkn.mjs', /** @this {MochaSuiteModified} */() => { const usdtAmount = 1e8; const exchResult = 0; const notUsed = 0; - const delay = 2; const expectedOutAmAmt = 1e8; const expectedOutPrAmt = 0; @@ -47,9 +46,7 @@ describe('lp_stable: getOneTkn.mjs', /** @this {MochaSuiteModified} */() => { chainId, }, this.accounts.user1); await api.transactions.broadcast(putOneTkn, {}); - const { height } = await ni.waitForTx(putOneTkn.id, { apiBase }); - - await ni.waitForHeight(height + delay, { apiBase }); + await ni.waitForTx(putOneTkn.id, { apiBase }); const getOneTkn = invokeScript({ dApp: lpStable, @@ -100,7 +97,6 @@ describe('lp_stable: getOneTkn.mjs', /** @this {MochaSuiteModified} */() => { expect(stateChanges.invokes.map((item) => [item.dApp, item.call.function])) .to.deep.include.members([ - [address(this.accounts.lpStableAddon, chainId), 'ensureCanGetOneTkn'], [address(this.accounts.gwxReward, chainId), 'calcD'], [address(this.accounts.gwxReward, chainId), 'calcD'], [address(this.accounts.factoryV2, chainId), 'burn'], diff --git a/test/lp_stable/lp_stable_decimals_migration/_hooks.mjs b/test/lp_stable/lp_stable_decimals_migration/_hooks.mjs new file mode 100644 index 000000000..a7a5e7136 --- /dev/null +++ b/test/lp_stable/lp_stable_decimals_migration/_hooks.mjs @@ -0,0 +1,294 @@ +import { address, publicKey, randomSeed } from '@waves/ts-lib-crypto'; +import { + data, + invokeScript, + issue, + massTransfer, + nodeInteraction, +} from '@waves/waves-transactions'; +import { create } from '@waves/node-api-js'; +import { format, join } from 'path'; +import { setScriptFromFile } from '../../utils.mjs'; + +const { waitForTx } = nodeInteraction; +const apiBase = process.env.API_NODE_URL; +const seed = 'waves private node seed with waves tokens'; +const chainId = 'R'; +const api = create(apiBase); +const seedWordsCount = 5; +const ridePath = 'ride'; +const ridePathForTest = join('test', 'lp_stable', 'lp_stable_decimals_migration', 'ride'); +const mockRidePath = join('test', 'lp_stable', 'lp_stable_decimals_migration', 'mock'); +const lpStablePath = format({ dir: ridePathForTest, base: 'lp_stable_old.ride' }); +const lpStableAddonPath = format({ dir: ridePathForTest, base: 'lp_stable_addon_old.ride' }); +const factoryV2Path = format({ dir: ridePath, base: 'factory_v2.ride' }); +const stakingPath = format({ dir: mockRidePath, base: 'staking.mock.ride' }); +const slippagePath = format({ dir: mockRidePath, base: 'slippage.mock.ride' }); +const assetsStorePath = format({ dir: mockRidePath, base: 'assets_store.mock.ride' }); +const gwxRewardPath = format({ dir: mockRidePath, base: 'gwx_reward.mock.ride' }); + +export const mochaHooks = { + async beforeAll() { + const names = [ + 'lpStable', + 'lpStableAddon', + 'factoryV2', + 'staking', + 'slippage', + 'gwxReward', + 'manager', + 'store', + 'user1', + ]; + this.accounts = Object.fromEntries(names.map((item) => [item, randomSeed(seedWordsCount)])); + const seeds = Object.values(this.accounts); + const amount = 10e10; + const massTransferTx = massTransfer({ + transfers: seeds.map((item) => ({ recipient: address(item, chainId), amount })), + chainId, + }, seed); + await api.transactions.broadcast(massTransferTx, {}); + await waitForTx(massTransferTx.id, { apiBase }); + + await setScriptFromFile(lpStablePath, this.accounts.lpStable); + await setScriptFromFile(lpStableAddonPath, this.accounts.lpStableAddon); + await setScriptFromFile(factoryV2Path, this.accounts.factoryV2); + await setScriptFromFile(stakingPath, this.accounts.staking); + await setScriptFromFile(slippagePath, this.accounts.slippage); + await setScriptFromFile(assetsStorePath, this.accounts.store); + await setScriptFromFile(gwxRewardPath, this.accounts.gwxReward); + + const usdnIssueTx = issue({ + name: 'USDN', + description: '', + quantity: 10e8, + decimals: 6, + chainId, + }, seed); + await api.transactions.broadcast(usdnIssueTx, {}); + await waitForTx(usdnIssueTx.id, { apiBase }); + this.usdnAssetId = usdnIssueTx.id; + + const usdnAmount = 10e8; + const massTransferTxUSDN = massTransfer({ + transfers: names.slice(-1).map((name) => ({ + recipient: address(this.accounts[name], chainId), amount: usdnAmount, + })), + assetId: this.usdnAssetId, + chainId, + }, seed); + await api.transactions.broadcast(massTransferTxUSDN, {}); + await waitForTx(massTransferTxUSDN.id, { apiBase }); + + const usdtIssueTx = issue({ + name: 'USDT', + description: '', + quantity: 10e8, + decimals: 6, + chainId, + }, seed); + await api.transactions.broadcast(usdtIssueTx, {}); + await waitForTx(usdtIssueTx.id, { apiBase }); + this.usdtAssetId = usdtIssueTx.id; + + const usdtAmount = 10e8; + const massTransferTxUSDT = massTransfer({ + transfers: names.slice(-1).map((name) => ({ + recipient: address(this.accounts[name], chainId), amount: usdtAmount, + })), + assetId: this.usdtAssetId, + chainId, + }, seed); + await api.transactions.broadcast(massTransferTxUSDT, {}); + await waitForTx(massTransferTxUSDT.id, { apiBase }); + + const constructorFactoryV2InvokeTx = invokeScript({ + dApp: address(this.accounts.factoryV2, chainId), + additionalFee: 4e5, + call: { + function: 'constructor', + args: [ + { type: 'string', value: address(this.accounts.staking, chainId) }, + { type: 'string', value: '' }, + { type: 'string', value: '' }, + { type: 'string', value: '' }, + { type: 'string', value: '' }, + { type: 'string', value: '' }, + { type: 'string', value: address(this.accounts.slippage, chainId) }, + { type: 'integer', value: 8 }, + ], + }, + chainId, + }, this.accounts.factoryV2); + await api.transactions.broadcast(constructorFactoryV2InvokeTx, {}); + await waitForTx(constructorFactoryV2InvokeTx.id, { apiBase }); + + const constructorV2FactoryV2InvokeTx = invokeScript({ + dApp: address(this.accounts.factoryV2, chainId), + additionalFee: 4e5, + call: { + function: 'constructorV2', + args: [ + { type: 'string', value: '' }, + ], + }, + chainId, + }, this.accounts.factoryV2); + await api.transactions.broadcast(constructorV2FactoryV2InvokeTx, {}); + await waitForTx(constructorV2FactoryV2InvokeTx.id, { apiBase }); + + const constructorV3FactoryV2InvokeTx = invokeScript({ + dApp: address(this.accounts.factoryV2, chainId), + additionalFee: 4e5, + call: { + function: 'constructorV3', + args: [ + { type: 'string', value: '' }, + { type: 'string', value: '' }, + { type: 'string', value: address(this.accounts.gwxReward, chainId) }, + { type: 'string', value: '' }, + ], + }, + chainId, + }, this.accounts.factoryV2); + await api.transactions.broadcast(constructorV3FactoryV2InvokeTx, {}); + await waitForTx(constructorV3FactoryV2InvokeTx.id, { apiBase }); + + const constructorV4FactoryV2InvokeTx = invokeScript({ + dApp: address(this.accounts.factoryV2, chainId), + additionalFee: 4e5, + call: { + function: 'constructorV4', + args: [ + { type: 'string', value: '' }, + { type: 'list', value: [{ type: 'string', value: '' }] }, + ], + }, + chainId, + }, this.accounts.factoryV2); + await api.transactions.broadcast(constructorV4FactoryV2InvokeTx, {}); + await waitForTx(constructorV4FactoryV2InvokeTx.id, { apiBase }); + + const constructorV5FactoryV2InvokeTx = invokeScript({ + dApp: address(this.accounts.factoryV2, chainId), + additionalFee: 4e5, + call: { + function: 'constructorV5', + args: [ + { type: 'string', value: address(this.accounts.store, chainId) }, + ], + }, + chainId, + }, this.accounts.factoryV2); + await api.transactions.broadcast(constructorV5FactoryV2InvokeTx, {}); + await waitForTx(constructorV5FactoryV2InvokeTx.id, { apiBase }); + + const setManagerFactoryV2Tx = data({ + additionalFee: 4e5, + data: [{ + key: '%s__managerPublicKey', + type: 'string', + value: publicKey(this.accounts.manager), + }], + chainId, + }, this.accounts.factoryV2); + await api.transactions.broadcast(setManagerFactoryV2Tx, {}); + await waitForTx(setManagerFactoryV2Tx.id, { apiBase }); + + const constructorLpStableInvokeTx = invokeScript({ + dApp: address(this.accounts.lpStable, chainId), + additionalFee: 4e5, + call: { + function: 'constructor', + args: [ + { type: 'string', value: address(this.accounts.factoryV2, chainId) }, + ], + }, + chainId, + }, this.accounts.lpStable); + await api.transactions.broadcast(constructorLpStableInvokeTx, {}); + await waitForTx(constructorLpStableInvokeTx.id, { apiBase }); + + const activateNewPoolTx = invokeScript({ + dApp: address(this.accounts.factoryV2, chainId), + fee: 100500000, + call: { + function: 'activateNewPool', + args: [ + { type: 'string', value: address(this.accounts.lpStable, chainId) }, + { type: 'string', value: this.usdtAssetId }, + { type: 'string', value: this.usdnAssetId }, + { type: 'string', value: 'USDTUSDNLP' }, + { type: 'string', value: 'WX USDT/USDN pool liquidity provider token' }, + { type: 'integer', value: 0 }, + { type: 'string', value: '' }, + { type: 'string', value: '' }, + ], + }, + chainId, + }, this.accounts.manager); + await api.transactions.broadcast(activateNewPoolTx, {}); + const { stateChanges } = await waitForTx(activateNewPoolTx.id, { apiBase }); + this.lpStableAssetId = stateChanges.issues[0].assetId; + + const setLpStableAddonTx = data({ + additionalFee: 4e5, + data: [{ + key: '%s__addonAddr', + type: 'string', + value: address(this.accounts.lpStableAddon, chainId), + }], + chainId, + }, this.accounts.lpStable); + await api.transactions.broadcast(setLpStableAddonTx, {}); + await waitForTx(setLpStableAddonTx.id, { apiBase }); + + const setLpStableTx = data({ + additionalFee: 4e5, + data: [{ + key: '%s__poolAddress', + type: 'string', + value: address(this.accounts.lpStable, chainId), + }], + chainId, + }, this.accounts.lpStableAddon); + await api.transactions.broadcast(setLpStableTx, {}); + await waitForTx(setLpStableTx.id, { apiBase }); + + const setDelayTx = data({ + additionalFee: 4e5, + data: [{ + key: '%s__delay', + type: 'integer', + value: 2, + }], + chainId, + }, this.accounts.lpStable); + await api.transactions.broadcast(setDelayTx, {}); + await waitForTx(setDelayTx.id, { apiBase }); + + const setAmpTx = data({ + additionalFee: 4e5, + data: [{ + key: '%s__amp', + type: 'string', + value: '1000', + }], + chainId, + }, this.accounts.lpStable); + await api.transactions.broadcast(setAmpTx, {}); + await waitForTx(setAmpTx.id, { apiBase }); + + const setManagerLpStableTx = data({ + additionalFee: 4e5, + data: [{ + key: '%s__managerPublicKey', + type: 'string', + value: publicKey(this.accounts.manager), + }], + chainId, + }, this.accounts.lpStable); + await api.transactions.broadcast(setManagerLpStableTx, {}); + await waitForTx(setManagerLpStableTx.id, { apiBase }); + }, +}; diff --git a/test/lp_stable/lp_stable_decimals_migration/get.mjs b/test/lp_stable/lp_stable_decimals_migration/get.mjs new file mode 100644 index 000000000..c9a0f0820 --- /dev/null +++ b/test/lp_stable/lp_stable_decimals_migration/get.mjs @@ -0,0 +1,313 @@ +import chai from 'chai'; +import chaiAsPromised from 'chai-as-promised'; +import { address, publicKey } from '@waves/ts-lib-crypto'; +import { + invokeScript, nodeInteraction as ni, setScript, +} from '@waves/waves-transactions'; +import { create } from '@waves/node-api-js'; +import { format } from 'path'; +import ride from '@waves/ride-js'; +import { readFile } from 'fs/promises'; + +chai.use(chaiAsPromised); +const { expect } = chai; + +const apiBase = process.env.API_NODE_URL; +const chainId = 'R'; + +const api = create(apiBase); + +describe('lp_stable_decimals_migration: get.mjs', /** @this {MochaSuiteModified} */() => { + it('should successfully change the state in the same way after changing the script from lp_stable_old.ride to lp_stable.ride when executing the put and get method', async function () { + const usdtAmount = 1e6; + const usdnAmount = 1e6; + const shouldAutoStake = false; + + const expectedInAmtAssetAmt = 1e6; + const expectedInPriceAssetAmt = 1e6; + const expectedOutLpAmt = 1e8; + const expectedPrice = 1e8; + const expectedSlipByUser = 0; + const expectedSlippageReal = 0; + const expectedSlipageAmAmt = 0; + const expectedSlipagePrAmt = 0; + const expectedPriceLast = 1e8; + const expectedPriceHistory = 1e8; + const expectedInvokesCount = 1; + + const lpStable = address(this.accounts.lpStable, chainId); + + // putFirst + // -------------------------------------------------------------------------------------------- + const putFirst = invokeScript({ + dApp: lpStable, + payment: [ + { assetId: this.usdtAssetId, amount: usdtAmount }, + { assetId: this.usdnAssetId, amount: usdnAmount }, + ], + call: { + function: 'put', + args: [ + { type: 'integer', value: 0 }, + { type: 'boolean', value: shouldAutoStake }, + ], + }, + chainId, + }, this.accounts.user1); + await api.transactions.broadcast(putFirst, {}); + await ni.waitForTx(putFirst.id, { apiBase }); + + // getAfterPutFirst + // -------------------------------------------------------------------------------------------- + const getAfterPutFirst = invokeScript({ + dApp: lpStable, + payment: [ + { assetId: this.lpStableAssetId, amount: expectedOutLpAmt }, + ], + call: { + function: 'get', + args: [], + }, + chainId, + }, this.accounts.user1); + await api.transactions.broadcast(getAfterPutFirst, {}); + const { + height: heightGetAfterPutFirst, + stateChanges: stateChangesGetAfterPutFirst, + id: idGetAfterPutFirst, + } = await ni.waitForTx(getAfterPutFirst.id, { apiBase }); + + const { + timestamp: timestampGetAfterPutFirst, + } = await api.blocks.fetchHeadersAt(heightGetAfterPutFirst); + const keyPriceHistoryGetAfterPutFirst = `%s%s%d%d__price__history__${heightGetAfterPutFirst}__${timestampGetAfterPutFirst}`; + + // check getAfterPutFirst + // -------------------------------------------------------------------------------------------- + expect(stateChangesGetAfterPutFirst.data).to.eql([{ + key: `%s%s%s__G__${address(this.accounts.user1, chainId)}__${idGetAfterPutFirst}`, + type: 'string', + value: `%d%d%d%d%d%d__${usdtAmount}__${usdnAmount}__${expectedOutLpAmt}__${expectedPriceLast}__${heightGetAfterPutFirst}__${timestampGetAfterPutFirst}`, + }, { + key: '%s%s__price__last', + type: 'integer', + value: expectedPriceLast, + }, { + key: keyPriceHistoryGetAfterPutFirst, + type: 'integer', + value: expectedPriceHistory, + }]); + + expect(stateChangesGetAfterPutFirst.transfers).to.eql([{ + address: address(this.accounts.user1, chainId), + asset: this.usdtAssetId, + amount: usdtAmount, + }, { + address: address(this.accounts.user1, chainId), + asset: this.usdnAssetId, + amount: usdnAmount, + }]); + + const { invokes: invokesGetAfterPutFirst } = stateChangesGetAfterPutFirst; + expect(invokesGetAfterPutFirst.length).to.eql(expectedInvokesCount); + + expect(invokesGetAfterPutFirst[0].dApp).to.eql(address(this.accounts.factoryV2, chainId)); + expect(invokesGetAfterPutFirst[0].call.function).to.eql('burn'); + expect(invokesGetAfterPutFirst[0].call.args).to.eql([ + { + type: 'Int', + value: expectedOutLpAmt, + }]); + expect(invokesGetAfterPutFirst[0].stateChanges.burns).to.eql([{ + assetId: this.lpStableAssetId, + quantity: expectedOutLpAmt, + }]); + + // putSecond + // -------------------------------------------------------------------------------------------- + const putSecond = invokeScript({ + dApp: lpStable, + payment: [ + { assetId: this.usdtAssetId, amount: usdtAmount }, + { assetId: this.usdnAssetId, amount: usdnAmount }, + ], + call: { + function: 'put', + args: [ + { type: 'integer', value: 0 }, + { type: 'boolean', value: shouldAutoStake }, + ], + }, + chainId, + }, this.accounts.user1); + await api.transactions.broadcast(putSecond, {}); + await ni.waitForTx(putSecond.id, { apiBase }); + + // setScript + // -------------------------------------------------------------------------------------------- + const ridePath = 'ride'; + const lpStableV2Path = format({ dir: ridePath, base: 'lp_stable.ride' }); + const lpStableAddonV2Path = format({ dir: ridePath, base: 'lp_stable_addon.ride' }); + + const { base64: base64LpStableV2 } = ride.compile( + (await readFile(lpStableV2Path, { encoding: 'utf-8' })), + ).result; + const ssTxLpStableV2 = setScript({ + script: base64LpStableV2, + chainId, + fee: 34e5, + senderPublicKey: publicKey(this.accounts.lpStable), + }, this.accounts.manager); + await api.transactions.broadcast(ssTxLpStableV2, {}); + await ni.waitForTx(ssTxLpStableV2.id, { apiBase }); + + const { base64: base64LpStableAddonV2 } = ride.compile( + (await readFile(lpStableAddonV2Path, { encoding: 'utf-8' })), + ).result; + const ssTxLpStableAddonV2 = setScript({ + script: base64LpStableAddonV2, + chainId, + fee: 14e5, + }, this.accounts.lpStableAddon); + await api.transactions.broadcast(ssTxLpStableAddonV2, {}); + await ni.waitForTx(ssTxLpStableAddonV2.id, { apiBase }); + + // putAfterSetScript + // -------------------------------------------------------------------------------------------- + const putAfterSetScript = invokeScript({ + dApp: lpStable, + payment: [ + { assetId: this.usdtAssetId, amount: usdtAmount }, + { assetId: this.usdnAssetId, amount: usdnAmount }, + ], + call: { + function: 'put', + args: [ + { type: 'integer', value: 0 }, + { type: 'boolean', value: shouldAutoStake }, + ], + }, + chainId, + }, this.accounts.user1); + await api.transactions.broadcast(putAfterSetScript, {}); + const { + height: heightPutAfterSetScript, + stateChanges: stateChangesPutAfterSetScript, + id: idPutAfterSetScript, + } = await ni.waitForTx(putAfterSetScript.id, { apiBase }); + + const { + timestamp: timestampPutAfterSetScript, + } = await api.blocks.fetchHeadersAt(heightPutAfterSetScript); + const keyPriceHistoryAfterSetScript = `%s%s%d%d__price__history__${heightPutAfterSetScript}__${timestampPutAfterSetScript}`; + + // check putAfterSetScript + // -------------------------------------------------------------------------------------------- + expect(stateChangesPutAfterSetScript.data).to.eql([{ + key: '%s%s__price__last', + type: 'integer', + value: expectedPriceLast, + }, { + key: keyPriceHistoryAfterSetScript, + type: 'integer', + value: expectedPriceHistory, + }, { + key: `%s%s%s__P__${address(this.accounts.user1, chainId)}__${idPutAfterSetScript}`, + type: 'string', + value: `%d%d%d%d%d%d%d%d%d%d__${expectedInAmtAssetAmt}__${expectedInPriceAssetAmt}__${expectedOutLpAmt}__${expectedPrice}__${expectedSlipByUser}__${expectedSlippageReal}__${heightPutAfterSetScript}__${timestampPutAfterSetScript}__${expectedSlipageAmAmt}__${expectedSlipagePrAmt}`, + }]); + + expect(stateChangesPutAfterSetScript.transfers).to.eql([{ + address: address(this.accounts.user1, chainId), + asset: this.lpStableAssetId, + amount: expectedOutLpAmt, + }]); + + const { invokes: invokesPutAfterSetScript } = stateChangesPutAfterSetScript; + expect(invokesPutAfterSetScript.length).to.eql(expectedInvokesCount); + + expect(invokesPutAfterSetScript[0].dApp).to.eql(address(this.accounts.factoryV2, chainId)); + expect(invokesPutAfterSetScript[0].call.function).to.eql('emit'); + expect(invokesPutAfterSetScript[0].call.args).to.eql([ + { + type: 'Int', + value: expectedOutLpAmt, + }]); + expect(invokesPutAfterSetScript[0].stateChanges.transfers).to.eql([{ + address: address(this.accounts.lpStable, chainId), + asset: this.lpStableAssetId, + amount: expectedOutLpAmt, + }]); + expect(invokesPutAfterSetScript[0].stateChanges.reissues).to.eql([{ + assetId: this.lpStableAssetId, + isReissuable: true, + quantity: expectedOutLpAmt, + }]); + + // getAfterSetScript + // -------------------------------------------------------------------------------------------- + const getAfterSetScript = invokeScript({ + dApp: lpStable, + payment: [ + { assetId: this.lpStableAssetId, amount: expectedOutLpAmt }, + ], + call: { + function: 'get', + args: [], + }, + chainId, + }, this.accounts.user1); + await api.transactions.broadcast(getAfterSetScript, {}); + const { + height: heightGetAfterSetScript, + stateChanges: stateChangesGetAfterSetScript, + id: idGetAfterSetScript, + } = await ni.waitForTx(getAfterSetScript.id, { apiBase }); + + const { + timestamp: timestampGetAfterSetScript, + } = await api.blocks.fetchHeadersAt(heightGetAfterSetScript); + const keyPriceHistoryGetAfterSetScript = `%s%s%d%d__price__history__${heightGetAfterSetScript}__${timestampGetAfterSetScript}`; + + // check getAfterSetScript + // -------------------------------------------------------------------------------------------- + expect(stateChangesGetAfterSetScript.data).to.eql([{ + key: `%s%s%s__G__${address(this.accounts.user1, chainId)}__${idGetAfterSetScript}`, + type: 'string', + value: `%d%d%d%d%d%d__${usdtAmount}__${usdnAmount}__${expectedOutLpAmt}__${expectedPriceLast}__${heightGetAfterSetScript}__${timestampGetAfterSetScript}`, + }, { + key: '%s%s__price__last', + type: 'integer', + value: expectedPriceLast, + }, { + key: keyPriceHistoryGetAfterSetScript, + type: 'integer', + value: expectedPriceHistory, + }]); + + expect(stateChangesGetAfterSetScript.transfers).to.eql([{ + address: address(this.accounts.user1, chainId), + asset: this.usdtAssetId, + amount: usdtAmount, + }, { + address: address(this.accounts.user1, chainId), + asset: this.usdnAssetId, + amount: usdnAmount, + }]); + + const { invokes: invokesGetAfterSetScript } = stateChangesGetAfterSetScript; + expect(invokesGetAfterSetScript.length).to.eql(expectedInvokesCount); + + expect(invokesGetAfterSetScript[0].dApp).to.eql(address(this.accounts.factoryV2, chainId)); + expect(invokesGetAfterSetScript[0].call.function).to.eql('burn'); + expect(invokesGetAfterSetScript[0].call.args).to.eql([ + { + type: 'Int', + value: expectedOutLpAmt, + }]); + expect(invokesGetAfterSetScript[0].stateChanges.burns).to.eql([{ + assetId: this.lpStableAssetId, + quantity: expectedOutLpAmt, + }]); + }); +}); diff --git a/test/lp_stable/lp_stable_decimals_migration/getOneTkn.mjs b/test/lp_stable/lp_stable_decimals_migration/getOneTkn.mjs new file mode 100644 index 000000000..a97436e34 --- /dev/null +++ b/test/lp_stable/lp_stable_decimals_migration/getOneTkn.mjs @@ -0,0 +1,250 @@ +import chai from 'chai'; +import chaiAsPromised from 'chai-as-promised'; +import { address, publicKey } from '@waves/ts-lib-crypto'; +import { + invokeScript, nodeInteraction as ni, setScript, +} from '@waves/waves-transactions'; +import { create } from '@waves/node-api-js'; +import { format } from 'path'; +import ride from '@waves/ride-js'; +import { readFile } from 'fs/promises'; + +chai.use(chaiAsPromised); +const { expect } = chai; + +const apiBase = process.env.API_NODE_URL; +const chainId = 'R'; + +const api = create(apiBase); + +describe('lp_stable_decimals_migration: getOneTkn.mjs', /** @this {MochaSuiteModified} */() => { + it('should successfully change the state in the same way after changing the script from lp_stable_old.ride to lp_stable.ride when executing the putOneTkn and get method', async function () { + const amAssetPart = 1e8; + const prAssetPart = 1e8; + const outLp = 1e10; + const slippage = 1e3; + const autoStake = false; + const usdtAmount = 1e8; + const usdnAmount = 1e8; + const exchResult = 0; + const notUsed = 0; + + const expectedOutLpAmt = 1e10; + const expectedPriceLast = 5e7; + const expectedPriceHistory = 5e7; + const expectedInvokesCountOldScript = 4; + const expectedInvokesCountNewScript = 3; + + const lpStable = address(this.accounts.lpStable, chainId); + + // put (need liquidity) + // -------------------------------------------------------------------------------------------- + const put = invokeScript({ + dApp: lpStable, + payment: [ + { assetId: this.usdtAssetId, amount: usdtAmount }, + { assetId: this.usdnAssetId, amount: usdnAmount }, + ], + call: { + function: 'put', + args: [ + { type: 'integer', value: 0 }, + { type: 'boolean', value: autoStake }, + ], + }, + chainId, + }, this.accounts.user1); + await api.transactions.broadcast(put, {}); + await ni.waitForTx(put.id, { apiBase }); + + // putOneTknFirst + // -------------------------------------------------------------------------------------------- + + const putOneTknFirst = invokeScript({ + dApp: lpStable, + payment: [ + { assetId: this.usdtAssetId, amount: usdtAmount }, + ], + call: { + function: 'putOneTkn', + args: [ + { type: 'integer', value: amAssetPart }, + { type: 'integer', value: prAssetPart }, + { type: 'integer', value: outLp }, + { type: 'integer', value: slippage }, + { type: 'boolean', value: autoStake }, + ], + }, + chainId, + }, this.accounts.user1); + await api.transactions.broadcast(putOneTknFirst, {}); + await ni.waitForTx(putOneTknFirst.id, { apiBase }); + + // getOneTknAfterPutOneTknFirst + // -------------------------------------------------------------------------------------------- + const getOneTknAfterPutOneTknFirst = invokeScript({ + dApp: lpStable, + payment: [ + { assetId: this.lpStableAssetId, amount: outLp }, + ], + call: { + function: 'getOneTkn', + args: [ + { type: 'integer', value: exchResult }, + { type: 'integer', value: notUsed }, + { type: 'integer', value: usdtAmount }, + { type: 'string', value: this.usdtAssetId }, + { type: 'integer', value: slippage }, + ], + }, + chainId, + }, this.accounts.user1); + await api.transactions.broadcast(getOneTknAfterPutOneTknFirst, {}); + const { + height: heightGetOneTknAfterPutOneTknFirst, + stateChanges: stateChangesGetOneTknAfterPutOneTknFirst, + id: idGetOneTknAfterPutOneTknFirst, + } = await ni.waitForTx(getOneTknAfterPutOneTknFirst.id, { apiBase }); + + const { + timestamp: timestampGetOneTknAfterPutOneTknFirst, + } = await api.blocks.fetchHeadersAt(heightGetOneTknAfterPutOneTknFirst); + const keyPriceHistoryGetOneTknAfterPutOneTknFirst = `%s%s%d%d__price__history__${heightGetOneTknAfterPutOneTknFirst}__${timestampGetOneTknAfterPutOneTknFirst}`; + + // check getOneTknAfterPutOneTknFirst + // -------------------------------------------------------------------------------------------- + expect(stateChangesGetOneTknAfterPutOneTknFirst.data).to.eql([{ + key: `%s%s%s__G__${address(this.accounts.user1, chainId)}__${idGetOneTknAfterPutOneTknFirst}`, + type: 'string', + value: `%d%d%d%d%d%d__${usdtAmount}__${notUsed}__${expectedOutLpAmt}__${expectedPriceLast}__${heightGetOneTknAfterPutOneTknFirst}__${timestampGetOneTknAfterPutOneTknFirst}`, + }, { + key: '%s%s__price__last', + type: 'integer', + value: expectedPriceLast, + }, { + key: keyPriceHistoryGetOneTknAfterPutOneTknFirst, + type: 'integer', + value: expectedPriceHistory, + }]); + + expect(stateChangesGetOneTknAfterPutOneTknFirst.transfers).to.eql([{ + address: address(this.accounts.user1, chainId), + asset: this.usdtAssetId, + amount: usdtAmount, + }]); + + const { + invokes: invokesGetOneTknAfterPutOneTknFirst, + } = stateChangesGetOneTknAfterPutOneTknFirst; + expect(invokesGetOneTknAfterPutOneTknFirst.length).to.eql(expectedInvokesCountOldScript); + + // putOneTknSecond + // -------------------------------------------------------------------------------------------- + + const putOneTknSecond = invokeScript({ + dApp: lpStable, + payment: [ + { assetId: this.usdtAssetId, amount: usdtAmount }, + ], + call: { + function: 'putOneTkn', + args: [ + { type: 'integer', value: amAssetPart }, + { type: 'integer', value: prAssetPart }, + { type: 'integer', value: outLp }, + { type: 'integer', value: slippage }, + { type: 'boolean', value: autoStake }, + ], + }, + chainId, + }, this.accounts.user1); + await api.transactions.broadcast(putOneTknSecond, {}); + await ni.waitForTx(putOneTknSecond.id, { apiBase }); + + // setScript + // -------------------------------------------------------------------------------------------- + const ridePath = 'ride'; + const lpStableV2Path = format({ dir: ridePath, base: 'lp_stable.ride' }); + const lpStableAddonV2Path = format({ dir: ridePath, base: 'lp_stable_addon.ride' }); + + const { base64: base64LpStableV2 } = ride.compile( + (await readFile(lpStableV2Path, { encoding: 'utf-8' })), + ).result; + const ssTxLpStableV2 = setScript({ + script: base64LpStableV2, + chainId, + fee: 100e5, + senderPublicKey: publicKey(this.accounts.lpStable), + }, this.accounts.manager); + await api.transactions.broadcast(ssTxLpStableV2, {}); + await ni.waitForTx(ssTxLpStableV2.id, { apiBase }); + + const { base64: base64LpStableAddonV2 } = ride.compile( + (await readFile(lpStableAddonV2Path, { encoding: 'utf-8' })), + ).result; + const ssTxLpStableAddonV2 = setScript({ + script: base64LpStableAddonV2, + chainId, + fee: 14e5, + }, this.accounts.lpStableAddon); + await api.transactions.broadcast(ssTxLpStableAddonV2, {}); + await ni.waitForTx(ssTxLpStableAddonV2.id, { apiBase }); + + // getOneTknAfterSetScript + // -------------------------------------------------------------------------------------------- + + const getOneTknAfterSetScript = invokeScript({ + dApp: lpStable, + payment: [ + { assetId: this.lpStableAssetId, amount: outLp }, + ], + call: { + function: 'getOneTkn', + args: [ + { type: 'integer', value: exchResult }, + { type: 'integer', value: notUsed }, + { type: 'integer', value: usdtAmount }, + { type: 'string', value: this.usdtAssetId }, + { type: 'integer', value: slippage }, + ], + }, + chainId, + }, this.accounts.user1); + await api.transactions.broadcast(getOneTknAfterSetScript, {}); + const { + height: heightGetOneTknAfterSetScript, + stateChanges: stateChangesGetOneTknAfterSetScript, + id: idGetOneTknAfterSetScript, + } = await ni.waitForTx(getOneTknAfterSetScript.id, { apiBase }); + + const { + timestamp: timestampGetOneTknAfterSetScript, + } = await api.blocks.fetchHeadersAt(heightGetOneTknAfterSetScript); + const keyPriceHistoryGetOneTknAfterSetScript = `%s%s%d%d__price__history__${heightGetOneTknAfterSetScript}__${timestampGetOneTknAfterSetScript}`; + + // check getOneTknAfterSetScript + // -------------------------------------------------------------------------------------------- + expect(stateChangesGetOneTknAfterSetScript.data).to.eql([{ + key: `%s%s%s__G__${address(this.accounts.user1, chainId)}__${idGetOneTknAfterSetScript}`, + type: 'string', + value: `%d%d%d%d%d%d__${usdtAmount}__${notUsed}__${expectedOutLpAmt}__${expectedPriceLast}__${heightGetOneTknAfterSetScript}__${timestampGetOneTknAfterSetScript}`, + }, { + key: '%s%s__price__last', + type: 'integer', + value: expectedPriceLast, + }, { + key: keyPriceHistoryGetOneTknAfterSetScript, + type: 'integer', + value: expectedPriceHistory, + }]); + + expect(stateChangesGetOneTknAfterSetScript.transfers).to.eql([{ + address: address(this.accounts.user1, chainId), + asset: this.usdtAssetId, + amount: usdtAmount, + }]); + + const { invokes: invokesGetOneTknAfterSetScript } = stateChangesGetOneTknAfterSetScript; + expect(invokesGetOneTknAfterSetScript.length).to.eql(expectedInvokesCountNewScript); + }); +}); diff --git a/test/lp_stable/lp_stable_decimals_migration/mock/assets_store.mock.ride b/test/lp_stable/lp_stable_decimals_migration/mock/assets_store.mock.ride new file mode 100644 index 000000000..42014d193 --- /dev/null +++ b/test/lp_stable/lp_stable_decimals_migration/mock/assets_store.mock.ride @@ -0,0 +1,50 @@ +{-# STDLIB_VERSION 5 #-} +{-# CONTENT_TYPE DAPP #-} +{-# SCRIPT_TYPE ACCOUNT #-} + +let SEP = "__" + +let statusVerified = 2 +let statusUnverified = 0 + +func keyStatus(assetId: String) = "status_<"+assetId+">" + +func isVerified(assetId: String) = { + assetId.keyStatus().getInteger().valueOrElse(statusUnverified) == statusVerified +} + +@Callable(i) +func createOrUpdate(assetId: String, logo: String, verified: Boolean) = { + (nil, unit) +} + +@Callable(i) +func addLabel(assetId: String, label: String) = { + (nil, unit) +} + +@Callable(i) +func addAssetsLink(amountAsset: String, priceAsset: String, lpAsset: String) = { + (nil, unit) +} + +@Callable(i) +func increaseAssetPoolsNumber(assetId: String) = { + (nil, unit) +} + +@Callable(i) +func setVerified(assetId: String, verified: Boolean) = { + (nil, unit) +} + +@Callable(i) +func setLogo(assetId: String, logo: String) = { + (nil, unit) +} + +@Callable(i) +func isVerifiedREADONLY(assetId: String) = { + # (nil, assetId.getBoolean().valueOrElse(false)) + (nil, assetId.isVerified()) +} diff --git a/test/lp_stable/lp_stable_decimals_migration/mock/gwx_reward.mock.ride b/test/lp_stable/lp_stable_decimals_migration/mock/gwx_reward.mock.ride new file mode 100644 index 000000000..edf4dae45 --- /dev/null +++ b/test/lp_stable/lp_stable_decimals_migration/mock/gwx_reward.mock.ride @@ -0,0 +1,14 @@ +{-# STDLIB_VERSION 5 #-} +{-# CONTENT_TYPE DAPP #-} +{-# SCRIPT_TYPE ACCOUNT #-} + +@Callable(i) +func calcD( + x1BigIntStr: String, + x2BigIntStr: String, + ampBigIntStr: String, + aPrecisionBigIntStr: String, + targetPrecisionBigIntStr: String +) = { + ([], 1000.toString()) +} diff --git a/test/lp_stable/lp_stable_decimals_migration/mock/slippage.mock.ride b/test/lp_stable/lp_stable_decimals_migration/mock/slippage.mock.ride new file mode 100644 index 000000000..cdeb0d811 --- /dev/null +++ b/test/lp_stable/lp_stable_decimals_migration/mock/slippage.mock.ride @@ -0,0 +1,8 @@ +{-# STDLIB_VERSION 5 #-} +{-# CONTENT_TYPE DAPP #-} +{-# SCRIPT_TYPE ACCOUNT #-} + +@Callable(i) +func put() = { + (nil, unit) +} diff --git a/test/lp_stable/lp_stable_decimals_migration/mock/staking.mock.ride b/test/lp_stable/lp_stable_decimals_migration/mock/staking.mock.ride new file mode 100644 index 000000000..4df35f321 --- /dev/null +++ b/test/lp_stable/lp_stable_decimals_migration/mock/staking.mock.ride @@ -0,0 +1,19 @@ +{-# STDLIB_VERSION 5 #-} +{-# CONTENT_TYPE DAPP #-} +{-# SCRIPT_TYPE ACCOUNT #-} + +@Callable(i) +func stake() = { + (nil, unit) +} + +@Callable(i) +func unstake(lpAssetIdStr: String, amount: Int) = { + let lpAssetId = lpAssetIdStr.fromBase58String() + ( + [ + ScriptTransfer(i.caller, amount, lpAssetId) + ], + unit + ) +} diff --git a/test/lp_stable/lp_stable_decimals_migration/put.mjs b/test/lp_stable/lp_stable_decimals_migration/put.mjs new file mode 100644 index 000000000..6270c9a0e --- /dev/null +++ b/test/lp_stable/lp_stable_decimals_migration/put.mjs @@ -0,0 +1,235 @@ +import chai from 'chai'; +import chaiAsPromised from 'chai-as-promised'; +import { address, publicKey } from '@waves/ts-lib-crypto'; +import { + invokeScript, nodeInteraction as ni, setScript, +} from '@waves/waves-transactions'; +import { create } from '@waves/node-api-js'; +import { format } from 'path'; +import ride from '@waves/ride-js'; +import { readFile } from 'fs/promises'; + +chai.use(chaiAsPromised); +const { expect } = chai; + +const apiBase = process.env.API_NODE_URL; +const chainId = 'R'; + +const api = create(apiBase); + +describe('lp_stable_decimals_migration: put.mjs', /** @this {MochaSuiteModified} */() => { + it('should successfully change the state in the same way after changing the script from lp_stable_old.ride to lp_stable.ride when executing the put and get method', async function () { + const usdtAmount = 1e6; + const usdnAmount = 1e6; + const shouldAutoStake = false; + + const expectedOutLpAmt = 1e8; + const expectedPriceLast = 1e8; + const expectedPriceHistory = 1e8; + const expectedInvokesCount = 1; + + const lpStable = address(this.accounts.lpStable, chainId); + + // putFirst + // -------------------------------------------------------------------------------------------- + const putFirst = invokeScript({ + dApp: lpStable, + payment: [ + { assetId: this.usdtAssetId, amount: usdtAmount }, + { assetId: this.usdnAssetId, amount: usdnAmount }, + ], + call: { + function: 'put', + args: [ + { type: 'integer', value: 0 }, + { type: 'boolean', value: shouldAutoStake }, + ], + }, + chainId, + }, this.accounts.user1); + await api.transactions.broadcast(putFirst, {}); + await ni.waitForTx(putFirst.id, { apiBase }); + + // setScript + // -------------------------------------------------------------------------------------------- + const ridePath = 'ride'; + const lpStableV2Path = format({ dir: ridePath, base: 'lp_stable.ride' }); + const lpStableAddonV2Path = format({ dir: ridePath, base: 'lp_stable_addon.ride' }); + + const { base64: base64LpStableV2 } = ride.compile( + (await readFile(lpStableV2Path, { encoding: 'utf-8' })), + ).result; + const ssTxLpStableV2 = setScript({ + script: base64LpStableV2, + chainId, + fee: 34e5, + senderPublicKey: publicKey(this.accounts.lpStable), + }, this.accounts.manager); + await api.transactions.broadcast(ssTxLpStableV2, {}); + await ni.waitForTx(ssTxLpStableV2.id, { apiBase }); + + const { base64: base64LpStableAddonV2 } = ride.compile( + (await readFile(lpStableAddonV2Path, { encoding: 'utf-8' })), + ).result; + const ssTxLpStableAddonV2 = setScript({ + script: base64LpStableAddonV2, + chainId, + fee: 14e5, + }, this.accounts.lpStableAddon); + await api.transactions.broadcast(ssTxLpStableAddonV2, {}); + await ni.waitForTx(ssTxLpStableAddonV2.id, { apiBase }); + + // putAfterSetScript + // -------------------------------------------------------------------------------------------- + const putAfterSetScript = invokeScript({ + dApp: lpStable, + payment: [ + { assetId: this.usdtAssetId, amount: usdtAmount }, + { assetId: this.usdnAssetId, amount: usdnAmount }, + ], + call: { + function: 'put', + args: [ + { type: 'integer', value: 0 }, + { type: 'boolean', value: shouldAutoStake }, + ], + }, + chainId, + }, this.accounts.user1); + await api.transactions.broadcast(putAfterSetScript, {}); + await ni.waitForTx(putAfterSetScript.id, { apiBase }); + + // getAfterPut + // -------------------------------------------------------------------------------------------- + const getAfterPut = invokeScript({ + dApp: lpStable, + payment: [ + { assetId: this.lpStableAssetId, amount: expectedOutLpAmt }, + ], + call: { + function: 'get', + args: [], + }, + chainId, + }, this.accounts.user1); + await api.transactions.broadcast(getAfterPut, {}); + const { + height: heightGetAfterPut, + stateChanges: stateChangesGetAfterPut, + id: idGetAfterPut, + } = await ni.waitForTx(getAfterPut.id, { apiBase }); + + const { + timestamp: timestampGetAfterPut, + } = await api.blocks.fetchHeadersAt(heightGetAfterPut); + const keyPriceHistoryGetAfterPut = `%s%s%d%d__price__history__${heightGetAfterPut}__${timestampGetAfterPut}`; + + // check getAfterPut + // -------------------------------------------------------------------------------------------- + expect(stateChangesGetAfterPut.data).to.eql([{ + key: `%s%s%s__G__${address(this.accounts.user1, chainId)}__${idGetAfterPut}`, + type: 'string', + value: `%d%d%d%d%d%d__${usdtAmount}__${usdnAmount}__${expectedOutLpAmt}__${expectedPriceLast}__${heightGetAfterPut}__${timestampGetAfterPut}`, + }, { + key: '%s%s__price__last', + type: 'integer', + value: expectedPriceLast, + }, { + key: keyPriceHistoryGetAfterPut, + type: 'integer', + value: expectedPriceHistory, + }]); + + expect(stateChangesGetAfterPut.transfers).to.eql([{ + address: address(this.accounts.user1, chainId), + asset: this.usdtAssetId, + amount: usdtAmount, + }, { + address: address(this.accounts.user1, chainId), + asset: this.usdnAssetId, + amount: usdnAmount, + }]); + + const { invokes: invokesGetAfterPut } = stateChangesGetAfterPut; + expect(invokesGetAfterPut.length).to.eql(expectedInvokesCount); + + expect(invokesGetAfterPut[0].dApp).to.eql(address(this.accounts.factoryV2, chainId)); + expect(invokesGetAfterPut[0].call.function).to.eql('burn'); + expect(invokesGetAfterPut[0].call.args).to.eql([ + { + type: 'Int', + value: expectedOutLpAmt, + }]); + expect(invokesGetAfterPut[0].stateChanges.burns).to.eql([{ + assetId: this.lpStableAssetId, + quantity: expectedOutLpAmt, + }]); + + // getSecondAfterPut + // -------------------------------------------------------------------------------------------- + const getSecondAfterPut = invokeScript({ + dApp: lpStable, + payment: [ + { assetId: this.lpStableAssetId, amount: expectedOutLpAmt }, + ], + call: { + function: 'get', + args: [], + }, + chainId, + }, this.accounts.user1); + await api.transactions.broadcast(getSecondAfterPut, {}); + const { + height: heightGetSecondAfterPut, + stateChanges: stateChangesGetSecondAfterPut, + id: idGetSecondAfterPut, + } = await ni.waitForTx(getSecondAfterPut.id, { apiBase }); + + const { + timestamp: timestampGetSecondAfterPut, + } = await api.blocks.fetchHeadersAt(heightGetSecondAfterPut); + const keyPriceHistoryGetSecondAfterPut = `%s%s%d%d__price__history__${heightGetSecondAfterPut}__${timestampGetSecondAfterPut}`; + + // check getSecondAfterPut + // -------------------------------------------------------------------------------------------- + expect(stateChangesGetSecondAfterPut.data).to.eql([{ + key: `%s%s%s__G__${address(this.accounts.user1, chainId)}__${idGetSecondAfterPut}`, + type: 'string', + value: `%d%d%d%d%d%d__${usdtAmount}__${usdnAmount}__${expectedOutLpAmt}__${expectedPriceLast}__${heightGetSecondAfterPut}__${timestampGetSecondAfterPut}`, + }, { + key: '%s%s__price__last', + type: 'integer', + value: expectedPriceLast, + }, { + key: keyPriceHistoryGetSecondAfterPut, + type: 'integer', + value: expectedPriceHistory, + }]); + + expect(stateChangesGetSecondAfterPut.transfers).to.eql([{ + address: address(this.accounts.user1, chainId), + asset: this.usdtAssetId, + amount: usdtAmount, + }, { + address: address(this.accounts.user1, chainId), + asset: this.usdnAssetId, + amount: usdnAmount, + }]); + + const { invokes: invokesGetAfterSecondPut } = stateChangesGetSecondAfterPut; + expect(invokesGetAfterSecondPut.length).to.eql(expectedInvokesCount); + + expect(invokesGetAfterSecondPut[0].dApp) + .to.eql(address(this.accounts.factoryV2, chainId)); + expect(invokesGetAfterSecondPut[0].call.function).to.eql('burn'); + expect(invokesGetAfterSecondPut[0].call.args).to.eql([ + { + type: 'Int', + value: expectedOutLpAmt, + }]); + expect(invokesGetAfterSecondPut[0].stateChanges.burns).to.eql([{ + assetId: this.lpStableAssetId, + quantity: expectedOutLpAmt, + }]); + }); +}); diff --git a/test/lp_stable/lp_stable_decimals_migration/putOneTkn.mjs b/test/lp_stable/lp_stable_decimals_migration/putOneTkn.mjs new file mode 100644 index 000000000..cfa9a31db --- /dev/null +++ b/test/lp_stable/lp_stable_decimals_migration/putOneTkn.mjs @@ -0,0 +1,259 @@ +import chai from 'chai'; +import chaiAsPromised from 'chai-as-promised'; +import { address, publicKey } from '@waves/ts-lib-crypto'; +import { + invokeScript, nodeInteraction as ni, setScript, +} from '@waves/waves-transactions'; +import { create } from '@waves/node-api-js'; +import { format } from 'path'; +import ride from '@waves/ride-js'; +import { readFile } from 'fs/promises'; + +chai.use(chaiAsPromised); +const { expect } = chai; + +const apiBase = process.env.API_NODE_URL; +const chainId = 'R'; + +const api = create(apiBase); + +describe('lp_stable_decimals_migration: putOneTkn.mjs', /** @this {MochaSuiteModified} */() => { + it('should successfully change the state in the same way after changing the script from lp_stable_old.ride to lp_stable.ride when executing the putOneTkn and get method', async function () { + const amAssetPart = 1e8; + const prAssetPart = 1e8; + const outLp = 1e10; + const slippage = 1e8; + const autoStake = false; + const usdtAmount = 1e8; + const usdnAmount = 1e8; + const exchResult = 0; + const notUsed = 0; + + const expectedOutLpAmt = 1e10; + + // putOnetkn getOnetkn setScript putOnetkn getOnetkn + // const expectedPriceLast = 5e7; + // const expectedPriceHistory = 5e7; + // const expectedInvokesCountOldScript = 4; + // const expectedInvokesCountNewScript = 3; + + // putOnetkn setScript putOnetkn getOnetkn getOnetkn + const expectedAfterFirstPutOneTknPriceLast = 16666666; + const expectedAfterFirstPutOneTknPriceHistory = 16666666; + const expectedAfterSecondPutOneTknPriceLast = 25000000; + const expectedAfterSecondPutOneTknPriceHistory = 25000000; + const expectedInvokesCountScript = 3; + + const lpStable = address(this.accounts.lpStable, chainId); + + // put (need liquidity) + // -------------------------------------------------------------------------------------------- + const put = invokeScript({ + dApp: lpStable, + payment: [ + { assetId: this.usdtAssetId, amount: usdtAmount }, + { assetId: this.usdnAssetId, amount: usdnAmount }, + ], + call: { + function: 'put', + args: [ + { type: 'integer', value: 0 }, + { type: 'boolean', value: autoStake }, + ], + }, + chainId, + }, this.accounts.user1); + await api.transactions.broadcast(put, {}); + await ni.waitForTx(put.id, { apiBase }); + + // putOneTknFirst + // -------------------------------------------------------------------------------------------- + const putOneTknFirst = invokeScript({ + dApp: lpStable, + payment: [ + { assetId: this.usdtAssetId, amount: usdtAmount }, + ], + call: { + function: 'putOneTkn', + args: [ + { type: 'integer', value: amAssetPart }, + { type: 'integer', value: prAssetPart }, + { type: 'integer', value: outLp }, + { type: 'integer', value: slippage }, + { type: 'boolean', value: autoStake }, + ], + }, + chainId, + }, this.accounts.user1); + await api.transactions.broadcast(putOneTknFirst, {}); + await ni.waitForTx(putOneTknFirst.id, { apiBase }); + + // setScript + // -------------------------------------------------------------------------------------------- + const ridePath = 'ride'; + const lpStableV2Path = format({ dir: ridePath, base: 'lp_stable.ride' }); + const lpStableAddonV2Path = format({ dir: ridePath, base: 'lp_stable_addon.ride' }); + + const { base64: base64LpStableV2 } = ride.compile( + (await readFile(lpStableV2Path, { encoding: 'utf-8' })), + ).result; + const ssTxLpStableV2 = setScript({ + script: base64LpStableV2, + chainId, + fee: 100e5, + senderPublicKey: publicKey(this.accounts.lpStable), + }, this.accounts.manager); + await api.transactions.broadcast(ssTxLpStableV2, {}); + await ni.waitForTx(ssTxLpStableV2.id, { apiBase }); + + const { base64: base64LpStableAddonV2 } = ride.compile( + (await readFile(lpStableAddonV2Path, { encoding: 'utf-8' })), + ).result; + const ssTxLpStableAddonV2 = setScript({ + script: base64LpStableAddonV2, + chainId, + fee: 14e5, + }, this.accounts.lpStableAddon); + await api.transactions.broadcast(ssTxLpStableAddonV2, {}); + await ni.waitForTx(ssTxLpStableAddonV2.id, { apiBase }); + + // putOneTknSecond + // -------------------------------------------------------------------------------------------- + const putOneTknSecond = invokeScript({ + dApp: lpStable, + payment: [ + { assetId: this.usdtAssetId, amount: usdtAmount }, + ], + call: { + function: 'putOneTkn', + args: [ + { type: 'integer', value: amAssetPart }, + { type: 'integer', value: prAssetPart }, + { type: 'integer', value: outLp }, + { type: 'integer', value: slippage }, + { type: 'boolean', value: autoStake }, + ], + }, + chainId, + }, this.accounts.user1); + await api.transactions.broadcast(putOneTknSecond, {}); + await ni.waitForTx(putOneTknSecond.id, { apiBase }); + + // getOneTknAfterPutOneTknFirst + // -------------------------------------------------------------------------------------------- + const getOneTknAfterPutOneTknFirst = invokeScript({ + dApp: lpStable, + payment: [ + { assetId: this.lpStableAssetId, amount: outLp }, + ], + call: { + function: 'getOneTkn', + args: [ + { type: 'integer', value: exchResult }, + { type: 'integer', value: notUsed }, + { type: 'integer', value: usdtAmount }, + { type: 'string', value: this.usdtAssetId }, + { type: 'integer', value: slippage }, + ], + }, + chainId, + }, this.accounts.user1); + await api.transactions.broadcast(getOneTknAfterPutOneTknFirst, {}); + const { + height: heightGetOneTknAfterPutOneTknFirst, + stateChanges: stateChangesGetOneTknAfterPutOneTknFirst, + id: idGetOneTknAfterPutOneTknFirst, + } = await ni.waitForTx(getOneTknAfterPutOneTknFirst.id, { apiBase }); + + const { + timestamp: timestampGetOneTknAfterPutOneTknFirst, + } = await api.blocks.fetchHeadersAt(heightGetOneTknAfterPutOneTknFirst); + const keyPriceHistoryGetOneTknAfterPutOneTknFirst = `%s%s%d%d__price__history__${heightGetOneTknAfterPutOneTknFirst}__${timestampGetOneTknAfterPutOneTknFirst}`; + + // check getOneTknAfterPutOneTknFirst + // -------------------------------------------------------------------------------------------- + expect(stateChangesGetOneTknAfterPutOneTknFirst.data).to.eql([{ + key: `%s%s%s__G__${address(this.accounts.user1, chainId)}__${idGetOneTknAfterPutOneTknFirst}`, + type: 'string', + value: `%d%d%d%d%d%d__${usdtAmount}__${notUsed}__${expectedOutLpAmt}__${expectedAfterFirstPutOneTknPriceLast}__${heightGetOneTknAfterPutOneTknFirst}__${timestampGetOneTknAfterPutOneTknFirst}`, + }, { + key: '%s%s__price__last', + type: 'integer', + value: expectedAfterFirstPutOneTknPriceLast, + }, { + key: keyPriceHistoryGetOneTknAfterPutOneTknFirst, + type: 'integer', + value: expectedAfterFirstPutOneTknPriceHistory, + }]); + + expect(stateChangesGetOneTknAfterPutOneTknFirst.transfers).to.eql([{ + address: address(this.accounts.user1, chainId), + asset: this.usdtAssetId, + amount: usdtAmount, + }]); + + const { + invokes: invokesGetOneTknAfterPutOneTknFirst, + } = stateChangesGetOneTknAfterPutOneTknFirst; + expect(invokesGetOneTknAfterPutOneTknFirst.length).to.eql(expectedInvokesCountScript); + + // getOneTknAfterPutOneTknSecond + // -------------------------------------------------------------------------------------------- + + const getOneTknAfterPutOneTknSecond = invokeScript({ + dApp: lpStable, + payment: [ + { assetId: this.lpStableAssetId, amount: outLp }, + ], + call: { + function: 'getOneTkn', + args: [ + { type: 'integer', value: exchResult }, + { type: 'integer', value: notUsed }, + { type: 'integer', value: usdtAmount }, + { type: 'string', value: this.usdtAssetId }, + { type: 'integer', value: slippage }, + ], + }, + chainId, + }, this.accounts.user1); + await api.transactions.broadcast(getOneTknAfterPutOneTknSecond, {}); + const { + height: heightGetOneTknAfterPutOneTknSecond, + stateChanges: stateChangesGetOneTknAfterPutOneTknSecond, + id: idGetOneTknAfterPutOneTknSecond, + } = await ni.waitForTx(getOneTknAfterPutOneTknSecond.id, { apiBase }); + + const { + timestamp: timestampGetOneTknAfterPutOneTknSecond, + } = await api.blocks.fetchHeadersAt(heightGetOneTknAfterPutOneTknSecond); + const keyPriceHistoryGetOneTknAfterPutOneTknSecond = `%s%s%d%d__price__history__${heightGetOneTknAfterPutOneTknSecond}__${timestampGetOneTknAfterPutOneTknSecond}`; + + // check getOneTknAfterPutOneTknSecond + // -------------------------------------------------------------------------------------------- + expect(stateChangesGetOneTknAfterPutOneTknSecond.data).to.eql([{ + key: `%s%s%s__G__${address(this.accounts.user1, chainId)}__${idGetOneTknAfterPutOneTknSecond}`, + type: 'string', + value: `%d%d%d%d%d%d__${usdtAmount}__${notUsed}__${expectedOutLpAmt}__${expectedAfterSecondPutOneTknPriceLast}__${heightGetOneTknAfterPutOneTknSecond}__${timestampGetOneTknAfterPutOneTknSecond}`, + }, { + key: '%s%s__price__last', + type: 'integer', + value: expectedAfterSecondPutOneTknPriceLast, + }, { + key: keyPriceHistoryGetOneTknAfterPutOneTknSecond, + type: 'integer', + value: expectedAfterSecondPutOneTknPriceHistory, + }]); + + expect(stateChangesGetOneTknAfterPutOneTknSecond.transfers).to.eql([{ + address: address(this.accounts.user1, chainId), + asset: this.usdtAssetId, + amount: usdtAmount, + }]); + + const { + invokes: invokesGetOneTknAfterPutOneTknSecond, + } = stateChangesGetOneTknAfterPutOneTknSecond; + expect(invokesGetOneTknAfterPutOneTknSecond.length).to.eql(expectedInvokesCountScript); + }); +}); diff --git a/test/lp_stable/lp_stable_decimals_migration/ride/lp_stable_addon_old.ride b/test/lp_stable/lp_stable_decimals_migration/ride/lp_stable_addon_old.ride new file mode 100644 index 000000000..0e95d4284 --- /dev/null +++ b/test/lp_stable/lp_stable_decimals_migration/ride/lp_stable_addon_old.ride @@ -0,0 +1,181 @@ +{-# STDLIB_VERSION 5 #-} +{-# CONTENT_TYPE DAPP #-} +{-# SCRIPT_TYPE ACCOUNT #-} + +let SEP = "__" +let EMPTY = "" +# data indexes from pool config stored in factory +let idxPoolAddress = 1 +let idxLPAsId = 3 +let idxAmAsId = 4 +let idxPrAsId = 5 +# data indexes from factory config +let idxFactStakCntr = 1 + +let delay = "%s__delay" + +#------------------------- +# KEYS ON CURRENT CONTRACT +#------------------------- +func keyFactCntr() = {"%s__factoryContract"} +func keyManagerPublicKey() = {"%s__managerPublicKey"} +func keyPendingManagerPublicKey() = {"%s__pendingManagerPublicKey"} +func keyPoolAddr() = {"%s__poolAddress"} +func keyAmtAsset() = {"%s__amountAsset"} +func keyPriceAsset() = {"%s__priceAsset"} +func keyAdminPubKeys() = "%s__adminPubKeys" + +func keyAmp() = "%s__amp" +func keyAmpHistory(height: Int) = "%s%d__amp__" + height.toString() + +func lastGetOneTknCall(caller: String) = {makeString(["%s%s__lastGetOneTknCall", caller], SEP)} +func lastPutOneTknCall(caller: String) = {makeString(["%s%s__lastPutOneTknCall", caller], SEP)} +#------------------------ +# KEYS ON OTHER CONTRACTS +#------------------------ +# from factory +func keyFactoryConfig() = {"%s__factoryConfig"} +func keyPoolConfig(iAmtAs: String, iPrAs: String) = {"%d%d%s__" + iAmtAs + "__" + iPrAs + "__config"} +func keyMappingsBaseAsset2internalId(bAStr: String) = {"%s%s%s__mappings__baseAsset2internalId__" + bAStr} + +#------------------------ +# GLOBAL FUNCTIONS +#------------------------ +func getStringOrFail(addr: Address, key: String) = addr.getString(key).valueOrErrorMessage(makeString(["mandatory ", addr.toString(), ".", key, " not defined"], "")) +func getIntOrFail(addr: Address, key: String) = addr.getInteger(key).valueOrErrorMessage(makeString(["mandatory ", addr.toString(), ".", key, " not defined"], "")) + +let poolContract = addressFromStringValue(getStringOrFail(this, keyPoolAddr())) +let factoryContract = addressFromStringValue(getStringOrFail(poolContract, keyFactCntr())) + +# function used to gather all pool data from factory +func getPoolConfig() = { + let amtAs = getStringOrFail(poolContract, keyAmtAsset()) + let priceAs = getStringOrFail(poolContract, keyPriceAsset()) + let iPriceAs = getIntOrFail(factoryContract, keyMappingsBaseAsset2internalId(priceAs)) + let iAmtAs = getIntOrFail(factoryContract, keyMappingsBaseAsset2internalId(amtAs)) + getStringOrFail(factoryContract, keyPoolConfig(iAmtAs.toString(), iPriceAs.toString())).split(SEP) +} + +func getFactoryConfig() = { + getStringOrFail(factoryContract, keyFactoryConfig()).split(SEP) +} + +func managerPublicKeyOrUnit() = match keyManagerPublicKey().getString() { + case s: String => s.fromBase58String() + case _: Unit => unit +} + +func pendingManagerPublicKeyOrUnit() = match keyPendingManagerPublicKey().getString() { + case s: String => s.fromBase58String() + case _: Unit => unit +} + +func mustManager(i: Invocation) = { + let pd = "Permission denied".throw() + + match managerPublicKeyOrUnit() { + case pk: ByteVector => i.callerPublicKey == pk || pd + case _: Unit => i.caller == this || pd + } +} + +func getAdmins() = { + match keyAdminPubKeys().getString() { + case s: String => if (s.size() == 0) then nil else s.split(SEP) + case _ => nil + } +} + +func mustAdmin(i: Invocation) = { + getAdmins().containsElement(i.callerPublicKey.toBase58String()) || i.mustManager() +} + +func mustPool(i: Invocation) = { + i.caller == poolContract || "caller must be the pool".throw() +} + +@Callable(i) +func constructor(poolAddress: String) = { + strict checkCaller = i.mustManager() + + [StringEntry(keyPoolAddr(), poolAddress)] +} + +@Callable(i) +func setManager(pendingManagerPublicKey: String) = { + strict checkCaller = i.mustManager() + strict checkManagerPublicKey = pendingManagerPublicKey.fromBase58String() + + [StringEntry(keyPendingManagerPublicKey(), pendingManagerPublicKey)] +} + +@Callable(i) +func confirmManager() = { + let pm = pendingManagerPublicKeyOrUnit() + strict hasPM = pm.isDefined() || throw("No pending manager") + strict checkPM = i.callerPublicKey == pm.value() || throw("You are not pending manager") + + [ + StringEntry(keyManagerPublicKey(), pm.value().toBase58String()), + DeleteEntry(keyPendingManagerPublicKey()) + ] +} + +@Callable(i) +func setAdmins(adminPubKeys: List[String]) = { + strict checkCaller = i.mustManager() + + [StringEntry(keyAdminPubKeys(), adminPubKeys.makeString(SEP))] +} + +# Unstake LP tokens and exit from pool +@Callable(i) +func unstakeAndGetOneTkn(amount: Int, exchResult: Int, notUsed: Int, outAmount: Int, outAssetId: String, slippage: Int) = { + strict checkPayments = if (i.payments.size() != 0) then throw("No pmnts expd") else true + + let cfg = getPoolConfig() + let factoryCfg = getFactoryConfig() + + let lpAssetId = cfg[idxLPAsId].fromBase58String() + let staking = factoryCfg[idxFactStakCntr].addressFromString().valueOrErrorMessage("Wr st addr") + + # negative amount will not pass + strict unstakeInv = staking.invoke("unstake", [lpAssetId.toBase58String(), amount], []) + strict getOneTkn = poolContract.reentrantInvoke("getOneTkn", [exchResult, notUsed, outAmount, outAssetId, slippage],[AttachedPayment(lpAssetId, amount)] ) + [] +} + +@Callable(i) +func setAmp(amp: String) = { + strict checkCaller = i.mustAdmin() + + let res1 = poolContract.invoke("setS", [keyAmp(), amp], []) + let res2 = poolContract.invoke("setS", [height.keyAmpHistory(), amp], []) + + ([], (res1, res2)) +} + +@Callable(i) +func ensureCanGetOneTkn(caller: String) = { + strict checkCaller = i.mustPool() + strict setI = poolContract.invoke("setI", [lastPutOneTknCall(caller), height], []) + + [] +} + +@Callable(i) +func ensureCanPutOneTkn(caller: String) = { + strict checkCaller = i.mustPool() + strict setI = poolContract.invoke("setI", [lastPutOneTknCall(caller), height], []) + + [] +} + +@Verifier(tx) +func verify() = { + let targetPublicKey = match managerPublicKeyOrUnit() { + case pk: ByteVector => pk + case _: Unit => tx.senderPublicKey + } + sigVerify(tx.bodyBytes, tx.proofs[0], targetPublicKey) +} diff --git a/test/lp_stable/lp_stable_decimals_migration/ride/lp_stable_old.ride b/test/lp_stable/lp_stable_decimals_migration/ride/lp_stable_old.ride new file mode 100644 index 000000000..f11595ef4 --- /dev/null +++ b/test/lp_stable/lp_stable_decimals_migration/ride/lp_stable_old.ride @@ -0,0 +1,1063 @@ +{-# STDLIB_VERSION 5 #-} +{-# CONTENT_TYPE DAPP #-} +{-# SCRIPT_TYPE ACCOUNT #-} + +# TODO: +# +# add callable to remove existing pool +# add restriction on min deposit amount +# AMU: move "WAVES" into spec variable +# AMU: add safe cast method instead of copy/past: inAmAssetId.valueOrElse("WAVES".fromBase58String()).toBase58String() + +# Description: +# Contract represents single liquidity pool for specific asset pair, e.g. BTC-USDN +# +# Actors: +# 1. LP +# 2. Factory +# 3. Matcher +# +# Actor LP could do do the following: +# 1. Enter the pool +# 2. Exit the pool +# +# Factory LP could do do the following: +# 1. Activate the pool +# 2. Halt pool operations partially of completelly +# +# Matcher LP could do do the following: +# 1. Perform exchange operations with pool assets +# +# New Pool deployment flow: +# 0. Factory contract has BLAKE2b-256 hash of the Pool contract. +# 1. Pool contract is deployed to the blockchain (factory address is injected into it) +# 2. Factory calls 'activate' callable and activate pool in case all prerequisites passed (assset pairs are not registered, contract hash matches actual) + +#----------------- +# GLOBAL VARIABLES +#----------------- +let scale8 = 100_000_000 +let scale8BigInt = 100_000_000.toBigInt() +let scale18 = 1_000_000_000_000_000_000.toBigInt() +let zeroBigInt = 0.toBigInt() +let oneBigInt = 1.toBigInt() +let slippage4D = (scale8 - scale8 * 1 / scale8).toBigInt() # 9999999 or error of 0.0000001 + +let Amult = "100" +let Dconv = "1" # D convergence + +let SEP = "__" +let EMPTY = "" +let PoolActive = 1 # ACTIVE, pool without restrictions +let PoolPutDis = 2 # PUT DISABLED, pool with put operation disabled +let PoolMatcherDis = 3 # MATCHER DISABLED, pool with matcher operations disabled +let PoolShutdown = 4 # SHUTDOWN, pool operations halted +# data indexes from pool config stored in factory +let idxPoolAddress = 1 +let idxPoolSt = 2 +let idxLPAsId = 3 +let idxAmAsId = 4 +let idxPrAsId = 5 +let idxAmtAsDcm = 6 +let idxPriceAsDcm = 7 +let idxIAmtAsId = 8 +let idxIPriceAsId = 9 +# data indexes from factory config +let idxFactStakCntr = 1 +let idxFactSlippCntr = 7 +let idxFactGwxRewCntr = 10 + +let delay = "%s__delay" +#------------------------- +# WX COMMON LIBRARY +#------------------------- +func t1(origVal: Int, origScaleMult: Int) = fraction(origVal.toBigInt(), scale18, origScaleMult.toBigInt()) +func f1(val: BigInt, resultScaleMult: Int) = fraction(val, resultScaleMult.toBigInt(), scale18).toInt() + +# cast passed amount to specified 'resScale' scale value from 'curScale' scale value +func ts(amt: Int, resScale: Int, curScale: Int) = fraction(amt, resScale, curScale) + +func abs(val: BigInt) = if (val < zeroBigInt) then -val else val + +#------------------------- +# KEYS ON CURRENT CONTRACT +#------------------------- +func fc() = {"%s__factoryContract"} +func mpk() = {"%s__managerPublicKey"} +func pmpk() = {"%s__pendingManagerPublicKey"} +func pl() = {"%s%s__price__last"} +func ph(h: Int, t: Int) = {makeString(["%s%s%d%d__price__history", h.toString(), t.toString()], SEP)} +# keyPutActionByUser +func pau(ua: String, txId: String) = "%s%s%s__P__" + ua + "__" + txId +# keyGetActionByUser +func gau(ua: String, txId: String) = "%s%s%s__G__" + ua + "__" + txId +func aa() = {"%s__amountAsset"} +func pa() = {"%s__priceAsset"} +# keyAmplificator +func amp() = {"%s__amp"} +func ada() = "%s__addonAddr" + +# lastGetOneTknCall +func lgotc(caller: String) = {makeString(["%s%s__lastGetOneTknCall", caller], SEP)} +# lastSetOneTknCall +func lsotc(caller: String) = {makeString(["%s%s__lastPutOneTknCall", caller], SEP)} + +#------------------------ +# KEYS ON OTHER CONTRACTS +#------------------------ +# from factory +# keyFactoryConfig +func fcfg() = {"%s__factoryConfig"} +# keyMatcherPub +func mtpk() = "%s%s__matcher__publicKey" +# keyPoolConfig +func pc(iAmtAs: String, iPrAs: String) = {"%d%d%s__" + iAmtAs + "__" + iPrAs + "__config"} +# keyMappingsBaseAsset2internalId +func mba(bAStr: String) = {"%s%s%s__mappings__baseAsset2internalId__" + bAStr} +# keyAllPoolsShutdown +func aps() = {"%s__shutdown"} + +#------------------------ +# FAILURES +#------------------------ +# throwOrderError +func toe(orV: Boolean, sendrV: Boolean, matchV: Boolean) = { + throw("Failed: ordValid=" + orV.toString() + " sndrValid=" + sendrV.toString() + " mtchrValid=" + matchV.toString()) +} + +#------------------------ +# GLOBAL FUNCTIONS +#------------------------ + +func str(val: Any) = { + match val { + case valStr: String => valStr + case _ => throw("fail cast to String") + } +} + +# getStringOrFail +func strf(addr: Address, key: String) = addr.getString(key).valueOrErrorMessage(makeString(["mandatory ", addr.toString(), ".", key, " not defined"], "")) +# getIntOrFail +func intf(addr: Address, key: String) = addr.getInteger(key).valueOrErrorMessage(makeString(["mandatory ", addr.toString(), ".", key, " not defined"], "")) + +# factoryContract +let fca = addressFromStringValue(strf(this, fc())) + +let A = strf(this, amp()) + +# isGlobalShutdown +# check that global shutdown is take place +func igs() = { + fca.getBoolean(aps()).valueOrElse(false) +} + +# getMatcherPubOrFail +func mp() = { + fca.strf(mtpk()).fromBase58String() +} + +# getPoolConfig +# function used to gather all pool data from factory +func gpc() = { + let amtAs = strf(this, aa()) + let priceAs = strf(this, pa()) + let iPriceAs = intf(fca, mba(priceAs)) + let iAmtAs = intf(fca, mba(amtAs)) + strf(fca, pc(iAmtAs.toString(), iPriceAs.toString())).split(SEP) +} + +# getFactoryConfig +func gfc() = { + strf(fca, fcfg()).split(SEP) +} + +func dataPutActionInfo(inAmtAssetAmt: Int, inPriceAssetAmt: Int, outLpAmt: Int, price: Int, slipByUser: Int, slippageReal: Int, txHeight: Int, txTimestamp: Int, slipageAmAmt: Int, slipagePrAmt: Int) = { + makeString(["%d%d%d%d%d%d%d%d%d%d", inAmtAssetAmt.toString(), inPriceAssetAmt.toString(), outLpAmt.toString(), price.toString(), slipByUser.toString(), slippageReal.toString(), txHeight.toString(), txTimestamp.toString(), slipageAmAmt.toString(), slipagePrAmt.toString()], SEP) +} + +func dataGetActionInfo(outAmtAssetAmt: Int, outPriceAssetAmt: Int, inLpAmt: Int, price: Int, txHeight: Int, txTimestamp: Int) = { + makeString( ["%d%d%d%d%d%d", outAmtAssetAmt.toString(), outPriceAssetAmt.toString(), inLpAmt.toString(), price.toString(), txHeight.toString(), txTimestamp.toString()], SEP) +} + +func getAccBalance(assetId: String) = { + if(assetId == "WAVES") then wavesBalance(this).available else assetBalance(this, fromBase58String(assetId)) +} + +# calcPriceBigInt +func cpbi(prAmtX18: BigInt, amAmtX18: BigInt) = { + fraction(prAmtX18, scale18, amAmtX18) +} + +# validateAbsDiff +func vad(A1: BigInt, A2: BigInt, slippage: BigInt) = { + let diff = fraction(A1 - A2, scale8BigInt, A2) + let pass = (slippage - abs(diff)) > zeroBigInt + if (!pass) then throw("Big slpg: " + diff.toString()) else + (pass, min([A1, A2])) +} + +# validateD +func vd(D1: BigInt, D0: BigInt, slpg: BigInt) = { + let diff = fraction(D0, scale8BigInt, D1) + let fail = diff < slpg + if (fail || D1 < D0) then throw(D0.toString() + " " + D1.toString() + " " + diff.toString() + " " + slpg.toString()) else + fail +} + +# privateCalcPrice +# cast assets and calc price +func pcp(amAssetDcm: Int, prAssetDcm: Int, amAmt: Int, prAmt: Int) = { + let amtAsAmtX18 = amAmt.t1(amAssetDcm) + let prAsAmtX18 = prAmt.t1(prAssetDcm) + cpbi(prAsAmtX18, amtAsAmtX18) +} + +# used only in stats endpoint, so result values are in scale8 as required +func calcPrices(amAmt: Int, prAmt: Int, lpAmt: Int) = { + let cfg = gpc() + let amtAsDcm = cfg[idxAmtAsDcm].parseIntValue() + let prAsDcm = cfg[idxPriceAsDcm].parseIntValue() + + let priceX18 = pcp(amtAsDcm, prAsDcm, amAmt, prAmt) + + let amAmtX18 = amAmt.t1(amtAsDcm) + let prAmtX18 = prAmt.t1(prAsDcm) + let lpAmtX18 = lpAmt.t1(scale8) + + let lpPrInAmAsX18 = cpbi(amAmtX18, lpAmtX18) + let lpPrInPrAsX18 = cpbi(prAmtX18, lpAmtX18) + + [priceX18, lpPrInAmAsX18, lpPrInPrAsX18] +} + +# public API which is used by backend +func calculatePrices(amAmt: Int, prAmt: Int, lpAmt: Int) = { + let p = calcPrices(amAmt, prAmt, lpAmt) + [p[0].f1(scale8), + p[1].f1(scale8), + p[2].f1(scale8)] +} + +# estimateGetOperation +func ego(txId58: String, pmtAssetId: String, pmtLpAmt: Int, userAddress: Address) = { + # data from pool config + let cfg = gpc() + let lpId = cfg[idxLPAsId] + let amId = cfg[idxAmAsId] + let prId = cfg[idxPrAsId] + let amDcm = cfg[idxAmtAsDcm].parseIntValue() + let prDcm = cfg[idxPriceAsDcm].parseIntValue() + let sts = cfg[idxPoolSt] + + let lpEmiss = assetInfo(lpId.fromBase58String()).valueOrErrorMessage("Wrong LP id").quantity + + # validation block + if (lpId != pmtAssetId) then throw("Wrong pmt asset") else + + let amBalance = getAccBalance(amId) + let amBalanceX18 = amBalance.t1(amDcm) + + let prBalance = getAccBalance(prId) + let prBalanceX18 = prBalance.t1(prDcm) + + let curPriceX18 = cpbi(prBalanceX18, amBalanceX18) + let curPrice = curPriceX18.f1(scale8) + + let pmtLpAmtX18 = pmtLpAmt.t1(scale8) + let lpEmissX18 = lpEmiss.t1(scale8) + # calculations + let outAmAmtX18 = fraction(amBalanceX18, pmtLpAmtX18, lpEmissX18) + let outPrAmtX18 = fraction(prBalanceX18, pmtLpAmtX18, lpEmissX18) + + # cast amounts to asset decimals + let outAmAmt = outAmAmtX18.f1(amDcm) + let outPrAmt = outPrAmtX18.f1(prDcm) + + let state = if (txId58 == "") then [] else + [ + ScriptTransfer(userAddress, outAmAmt, if (amId == "WAVES") then unit else amId.fromBase58String()), + ScriptTransfer(userAddress, outPrAmt, if (prId == "WAVES") then unit else prId.fromBase58String()), + + StringEntry( + gau(userAddress.toString(), txId58), + dataGetActionInfo(outAmAmt, outPrAmt, pmtLpAmt, curPrice, height, lastBlock.timestamp)), + IntegerEntry(pl(), curPrice), + IntegerEntry(ph(height, lastBlock.timestamp), curPrice) + ] + + ( outAmAmt, # 1 + outPrAmt, # 2 + amId, # 3 + prId, # 4 + amBalance, # 5 + prBalance, # 6 + lpEmiss, # 7 + curPriceX18, # 8 + sts, # 9 + state # 10 + ) +} + +# estimatePutOperation +func epo(txId58: String, + slippage: Int, + inAmAmt: Int, + inAmId: ByteVector|Unit, + inPrAmt: Int, + inPrId: ByteVector|Unit, + userAddress: String, + isEval: Boolean, + emitLp: Boolean, + isOneAsset: Boolean, + pmtAmt: Int, + pmtId: String) = { + # data from pool config + let cfg = gpc() + let lpId = cfg[idxLPAsId].fromBase58String() + let amIdStr = cfg[idxAmAsId] + let prIdStr = cfg[idxPrAsId] + let inAmIdStr = cfg[idxIAmtAsId] + let inPrIdStr = cfg[idxIPriceAsId] + let amtDcm = cfg[idxAmtAsDcm].parseIntValue() + let priceDcm = cfg[idxPriceAsDcm].parseIntValue() + let sts = cfg[idxPoolSt] + + let lpEm = assetInfo(lpId).valueOrErrorMessage("Wr lp as").quantity + + # get current balances from acc + let amBalance = if(isEval) then getAccBalance(amIdStr) else + if(isOneAsset && pmtId == amIdStr) then getAccBalance(amIdStr) - pmtAmt else + if(isOneAsset) then getAccBalance(amIdStr) else getAccBalance(amIdStr) - inAmAmt + let prBalance = if(isEval) then getAccBalance(prIdStr) else + if(isOneAsset && pmtId == prIdStr) then getAccBalance(prIdStr) - pmtAmt else + if(isOneAsset) then getAccBalance(prIdStr) else getAccBalance(prIdStr) - inPrAmt + + #if(true) then throw("lpEmission = "+ lpEmission.toString() + + # " amBalance = " + amBalance.toString() + + # " prBalance = " + prBalance.toString() + + # " getAccBalance(amIdStr) = " + getAccBalance(amIdStr).toString() + + # " etAccBalance(prIdStr) = " + getAccBalance(prIdStr).toString()) else + + # cast amounts to the lp decimals + let inAmAssetAmtX18 = inAmAmt.t1(amtDcm) + let inPrAssetAmtX18 = inPrAmt.t1(priceDcm) + + # calc user expected price + let userPriceX18 = cpbi(inPrAssetAmtX18, inAmAssetAmtX18) + + # calc pool price + let amBalanceX18 = amBalance.t1(amtDcm) + let prBalanceX18 = prBalance.t1(priceDcm) + + # case of the initial or first deposit + # result is a tuple containing the following: + # 1. lp amount that user got + # 2. amtAsset amount that goes to Pool liquidity + # 3. priceAsset amount that goes to Pool liquidity + # 4. pool price after PUT operation + let r = if(lpEm == 0) then { + let curPriceX18 = zeroBigInt + let slippageX18 = zeroBigInt + # calc initial deposit by geometric mean + let lpAmtX18 = pow(inAmAssetAmtX18 * inPrAssetAmtX18, 0, 5.toBigInt(), 1, 0, DOWN) + ( + lpAmtX18.f1(scale8), + inAmAssetAmtX18.f1(amtDcm), + inPrAssetAmtX18.f1(priceDcm), + cpbi(prBalanceX18 + inPrAssetAmtX18, amBalanceX18 + inAmAssetAmtX18), + slippageX18 + ) + } else { + let curPriceX18 = cpbi(prBalanceX18, amBalanceX18) + let slippageRealX18 = fraction(abs(curPriceX18 - userPriceX18), scale18, curPriceX18) + let slippageX18 = slippage.t1(scale8) + # validate slippage + if (curPriceX18 != zeroBigInt && slippageRealX18 > slippageX18) then throw("Price slippage " + slippageRealX18.toString() + " > " + slippageX18.toString()) else + + let lpEmissionX18 = lpEm.t1(scale8) + # calculate amount of price asset needed to deposit pool by current price and user's amountAsset amount + let prViaAmX18 = fraction(inAmAssetAmtX18, curPriceX18, scale18) + let amViaPrX18 = fraction(inPrAssetAmtX18, scale18, curPriceX18) + + # calculate amount and price assets to perform pool deposit in proportion to current pool price + let expectedAmts= if (prViaAmX18 > inPrAssetAmtX18) + then (amViaPrX18, inPrAssetAmtX18) + else (inAmAssetAmtX18, prViaAmX18) + + let expAmtAssetAmtX18 = expectedAmts._1 + let expPriceAssetAmtX18 = expectedAmts._2 + # calculate LP amount that user + let lpAmtX18 = fraction(lpEmissionX18, expPriceAssetAmtX18, prBalanceX18) + ( + lpAmtX18.f1(scale8), + expAmtAssetAmtX18.f1(amtDcm), + expPriceAssetAmtX18.f1(priceDcm), + curPriceX18, + slippageX18 + ) + } + + let calcLpAmt = r._1 + let calcAmAssetPmt = r._2 + let calcPrAssetPmt = r._3 + let curPrice = r._4.f1(scale8) + let slippageCalc = r._5.f1(scale8) + + if(calcLpAmt <= 0) then throw("LP <= 0") else + + let emitLpAmt = if (!emitLp) then 0 else calcLpAmt + let amDiff = inAmAmt - calcAmAssetPmt + let prDiff = inPrAmt - calcPrAssetPmt + + let (writeAmAmt, writePrAmt) = if(isOneAsset && pmtId == amIdStr) + then (pmtAmt, 0) + else if(isOneAsset && pmtId == prIdStr) + then (0, pmtAmt) + else (calcAmAssetPmt, calcPrAssetPmt) + + let commonState = [ + IntegerEntry(pl(), curPrice), + IntegerEntry(ph(height, lastBlock.timestamp), curPrice), + StringEntry( + pau(userAddress, txId58), + dataPutActionInfo(writeAmAmt, writePrAmt, emitLpAmt, curPrice, slippage, slippageCalc, height, lastBlock.timestamp, amDiff, prDiff)) + ] + + ( + calcLpAmt, # 1. + emitLpAmt, # 2. + curPrice, # 3. + amBalance, # 4. + prBalance, # 5. + lpEm, # 6. + lpId, # 7. + sts, # 8. + commonState, # 9. + amDiff, # 10. + prDiff, # 11. + inAmId, # 12 + inPrId # 13 + ) +} + +# validateMatcherOrderAllowed +func moa(order: Order) = { + let cfg = gpc() + let amtAsId = cfg[idxAmAsId] + let prAsId = cfg[idxPrAsId] + let sts = cfg[idxPoolSt].parseIntValue() + let amtAsDcm = cfg[idxAmtAsDcm].parseIntValue() + let prAsDcm = cfg[idxPriceAsDcm].parseIntValue() + + # get current balances from acc + let accAmtAsBalance = getAccBalance(amtAsId) + let accPrAsBalance = getAccBalance(prAsId) + + let curPriceX18 = if(order.orderType == Buy) + then pcp(amtAsDcm, prAsDcm, accAmtAsBalance + order.amount, accPrAsBalance) + else pcp(amtAsDcm, prAsDcm, accAmtAsBalance - order.amount, accPrAsBalance) + let curPrice = curPriceX18.f1(scale8) + + # validate status + if (igs() || sts == PoolMatcherDis || sts == PoolShutdown) then throw("Admin blocked") else + + # validate pairs + let orAmtAsset = order.assetPair.amountAsset + let orAmtAsStr = if( orAmtAsset == unit) then "WAVES" else toBase58String(orAmtAsset.value()) + let orPrAsset = order.assetPair.priceAsset + let orPrAsStr = if( orPrAsset == unit) then "WAVES" else toBase58String(orPrAsset.value()) + if(orAmtAsStr != amtAsId || orPrAsStr != prAsId) then throw("Wr assets") else + + let orderPrice = order.price + #priceDecimals = 8 + priceAssetDcm - amtAssetDcm + let priceDcm = fraction(scale8, prAsDcm, amtAsDcm) + let castOrderPrice = orderPrice.ts(scale8, priceDcm) + + let isOrderPriceValid = if(order.orderType == Buy) then castOrderPrice <= curPrice else castOrderPrice >= curPrice + #if(!isOrderPriceValid) then throw("Order price leads to K decrease. castedOrderPrice="+castedOrderPrice.toString() + " curPrice="+curPrice.toString()) else + true +} + +# commonGet +func cg(i: Invocation) = { + if (i.payments.size() != 1) then throw("1 pmnt exp") else + + let pmt = i.payments[0].value() + let pmtAssetId = pmt.assetId.value() + let pmtAmt = pmt.amount + + let r = ego(i.transactionId.toBase58String(), pmtAssetId.toBase58String(), pmtAmt, i.caller) + let outAmAmt = r._1 + let outPrAmt = r._2 + let sts = r._9.parseIntValue() + let state = r._10 + + if(igs() || sts == PoolShutdown) then throw("Admin blocked: " + sts.toString()) else + + (outAmAmt, + outPrAmt, + pmtAmt, + pmtAssetId, + state + ) +} + +# commonPut +func cp(caller: String, + txId: String, + amAsPmt: AttachedPayment, + prAsPmt: AttachedPayment, + slippage: Int, + emitLp: Boolean, + isOneAsset: Boolean, + pmtAmt: Int, + pmtId: String) = { + + let r = epo(txId, # i.transactionId.toBase58String() + slippage, + amAsPmt.value().amount, + amAsPmt.value().assetId, + prAsPmt.value().amount, + prAsPmt.value().assetId, + caller, #i.caller.toString() + false, + emitLp, + isOneAsset, + pmtAmt, + pmtId) + + let sts = r._8.parseIntValue() + if(igs() || sts == PoolPutDis || sts == PoolShutdown) then throw("Blocked:" + sts.toString()) else + r +} + +# managerPublicKeyOrUnit +func m() = match mpk().getString() { + case s: String => s.fromBase58String() + case _: Unit => unit +} + +# pendingManagerPublicKeyOrUnit +func pm() = match pmpk().getString() { + case s: String => s.fromBase58String() + case _: Unit => unit +} + +let pd = "Permission denied".throw() + +# mustManager +func mm(i: Invocation) = { + match m() { + case pk: ByteVector => i.callerPublicKey == pk || pd + case _: Unit => i.caller == this || pd + } +} + +@Callable(i) +# ido, team, emission, staking, locking (boosting), rest +# fc - factoryContract +func constructor(fc: String) = { + strict c = i.mm() + + [StringEntry(fc(), fc)] +} + +@Callable(i) +func setManager(pendingManagerPublicKey: String) = { + strict c = i.mm() + strict cm = pendingManagerPublicKey.fromBase58String() + + [StringEntry(pmpk(), pendingManagerPublicKey)] +} + +@Callable(i) +func confirmManager() = { + let p = pm() + strict hpm = p.isDefined() || throw("No pending manager") + strict cpm = i.callerPublicKey == p.value() || throw("You are not pending manager") + + [ + StringEntry(mpk(), p.value().toBase58String()), + DeleteEntry(pmpk()) + ] +} + +# called by: LP +# +# purpose: +# function used for entering the pool +# actions: +# validate list: +# 1. tokens ratio is in correct range +# 2. slipage is not bigger that current tokens ratio +# arguments: +# slippageTolerance max allowed slippage +# shouldAutoStake perform LP staking immediatelly in case true otherwise transfer LP to user) +# attach: +# attached should be two valid tokens from the available pools. +# return: +# transfer LP tokens based on deposit share +@Callable(i) +func put(slip: Int, autoStake: Boolean) = { + let factCfg = gfc() + let stakingCntr = addressFromString(factCfg[idxFactStakCntr]).valueOrErrorMessage("Wr st addr") + let slipCntr = addressFromString(factCfg[idxFactSlippCntr]).valueOrErrorMessage("Wr sl addr") + if(slip < 0) then throw("Wrong slippage") else + if (i.payments.size() != 2) then throw("2 pmnts expd") else + # estPut + let e = cp(i.caller.toString(), + i.transactionId.toBase58String(), + AttachedPayment(i.payments[0].value().assetId, i.payments[0].value().amount), + i.payments[1], + slip, + true, + false, + 0, + "") + + let emitLpAmt = e._2 + let lpAssetId = e._7 + let state = e._9 + let amDiff = e._10 + let prDiff = e._11 + let amId = e._12 + let prId = e._13 + + # emit lp on factory + strict r = fca.invoke("emit", [emitLpAmt], []) + # if the lp instance address is in the legacy list then the legacy factory address will be returned from the factory + strict el = match (r) { + case legacy: Address => legacy.invoke("emit", [emitLpAmt], []) + case _ => unit + } + strict sa = if(amDiff > 0) then slipCntr.invoke("put",[],[AttachedPayment(amId, amDiff)]) else [] + strict sp = if(prDiff > 0) then slipCntr.invoke("put",[],[AttachedPayment(prId, prDiff)]) else [] + + let lpTrnsfr = + if(autoStake) then strict ss = stakingCntr.invoke("stake",[],[AttachedPayment(lpAssetId, emitLpAmt)]); [] + else [ScriptTransfer(i.caller, emitLpAmt, lpAssetId)] + + state++lpTrnsfr +} + +# slippage in scale8 +@Callable(i) +func putOneTkn(amAssetPart: Int, prAssetPart: Int, outLp: Int, slippage: Int, autoStake: Boolean) = { + let cfg = gfc() + #if(true) then throw("off") else + let stakingCntr = addressFromString(cfg[idxFactStakCntr]).valueOrErrorMessage("Wr st addr") + let slipCntr = addressFromString(cfg[idxFactSlippCntr]).valueOrErrorMessage("Wr sl addr") + let gwxCntr = addressFromString(cfg[idxFactGwxRewCntr]).valueOrErrorMessage("Wr gwx addr") + let poolCfg = gpc() + let amId = poolCfg[idxAmAsId] + let prId = poolCfg[idxPrAsId] + let amDcm = poolCfg[idxAmtAsDcm].parseIntValue() + let prDcm = poolCfg[idxPriceAsDcm].parseIntValue() + + let addon = this.getString(ada()).valueOrElse("") + let userAddress = if(addon == i.caller.toString()) then i.originCaller else i.caller + + let addonContract = addressFromString( + ada().getString().valueOrErrorMessage("no addons") + ).valueOrErrorMessage("addon address in not valid") + + strict check = addonContract.reentrantInvoke("ensureCanPutOneTkn", [userAddress.toString()], []); + + if(slippage <= 0 || amAssetPart<=0 || prAssetPart<=0 || outLp <= 0) then throw("Wrong params") else + if (i.payments.size() != 1) then throw("1 pmnt expd") else + + let pmt = i.payments[0].value() + let pmtAssetId = pmt.assetId.value().toBase58String() + let pmtAmt = pmt.amount + if(pmtAmt < amAssetPart || pmtAmt < prAssetPart || pmtAmt < 10_000_000) then throw("Wrong pmt amt") else + + let amBalance = getAccBalance(amId) + let prBalance = getAccBalance(prId) + + let (amBalanceNow, prBalanceNow, virtSwapInAm, virtSwapOutPr, virtSwapInPr, virtSwapOutAm) = + if (pmtAssetId == amId) then { + (amBalance-pmtAmt, prBalance, pmtAmt - amAssetPart, prAssetPart, 0, 0) + } else if (pmtAssetId == prId) then { + (amBalance, prBalance-pmtAmt, 0, 0, pmtAmt - prAssetPart, amAssetPart) + } else throw("wrong pmtAssetId") + + let D0 = gwxCntr.invoke("calcD", [ + amBalanceNow.toString(), + prBalanceNow.toString(), + A, Amult, Dconv],[]) + + let D1 = gwxCntr.invoke("calcD", [ + (amBalanceNow + virtSwapInAm - virtSwapOutAm).toBigInt().toString(), + (prBalanceNow + virtSwapInPr - virtSwapOutPr).toBigInt().toString(), + A, Amult, Dconv],[]) + + strict D0vsD1 = vd(D1.str().parseBigIntValue(), D0.str().parseBigIntValue(), slippage4D) + + let estPut = cp(i.caller.toString(), + i.transactionId.toBase58String(), + AttachedPayment(amId.fromBase58String(), amAssetPart), + AttachedPayment(prId.fromBase58String(), prAssetPart), + slippage, + true, + true, + pmtAmt, + pmtAssetId) + + let estimLP = estPut._2 + let lpAssetId = estPut._7 + let state = estPut._9 + let amDiff = estPut._10 + let prDiff = estPut._11 + + let lpCalcRes = vad(estimLP.toBigInt(), outLp.toBigInt(), slippage.toBigInt()) + + let emitLpAmt = lpCalcRes._2.toInt() + + # emit lp on factory + strict e = fca.invoke("emit", [emitLpAmt], []) + # if the lp instance address is in the legacy list then the legacy factory address will be returned from the factory + strict el = match (e) { + case legacy: Address => legacy.invoke("emit", [emitLpAmt], []) + case _ => unit + } + strict sa = if(amDiff > 0) then slipCntr.invoke("put",[],[AttachedPayment(amId.fromBase58String(), amDiff)]) else [] + strict sp = if(prDiff > 0) then slipCntr.invoke("put",[],[AttachedPayment(prId.fromBase58String(), prDiff)]) else [] + + let lpTrnsfr = + if(autoStake) then strict ss = stakingCntr.invoke("stake",[],[AttachedPayment(lpAssetId, emitLpAmt)]); [] + else [ScriptTransfer(i.caller, emitLpAmt, lpAssetId)] + + state ++ lpTrnsfr +} + +# Put without LP emission +@Callable(i) +func putForFree(maxSlpg: Int) = { + if(maxSlpg < 0) then throw("Wrong slpg") else + if (i.payments.size() != 2) then throw("2 pmnts expd") else + let estPut = cp(i.caller.toString(), + i.transactionId.toBase58String(), + AttachedPayment(i.payments[0].value().assetId, i.payments[0].value().amount), + i.payments[1], + maxSlpg, + false, + false, + 0, + "") + estPut._9 +} + +# Called by: LP +# +# purpose: +# function used for exit from pool partially or fully +# actions: +# arguments: +# attach: +# attached should be corresponding pool LP token +# validate list: +# return: +# transfer to user his share of pool tokens base on passed lp token amount +@Callable(i) +func get() = { + let r = cg(i) + let outAmtAmt = r._1 + let outPrAmt = r._2 + let pmtAmt = r._3 + let pmtAssetId = r._4 + let state = r._5 + strict b = invoke(fca, + "burn", + [pmtAmt], + [AttachedPayment(pmtAssetId, pmtAmt)]) + state +} + +@Callable(i) +func getOneTkn(exchResult: Int, notUsed: Int, outAmount: Int, outAssetId: String, slippage: Int) = { + if (i.payments.size() != 1) then throw("1 pmnt expd") else + #if(true) then throw("off") else + let cfg = gpc() + let lpId = cfg[idxLPAsId] + let amId = cfg[idxAmAsId] + let prId = cfg[idxPrAsId] + let amDcm = cfg[idxAmtAsDcm].parseIntValue() + let prDcm = cfg[idxPriceAsDcm].parseIntValue() + let sts = cfg[idxPoolSt] + + let factCfg = gfc() + let gwxCntr = addressFromString(factCfg[idxFactGwxRewCntr]).valueOrErrorMessage("Wr sl addr") + + let pmt = i.payments[0].value() + + # todo: addon is a separate contract with unstake and get on tkn operation, should me merged with this contract once max script size is increased + let addon = this.getString(ada()).valueOrElse("") + let userAddress = if(addon == i.caller.toString()) then i.originCaller else i.caller + let txId58 = i.transactionId.toBase58String() + let pmtAssetId = pmt.assetId.value() + let pmtAmt = pmt.amount + + let addonContract = addressFromString( + ada().getString().valueOrErrorMessage("no addons") + ).valueOrErrorMessage("addon address in not valid") + + strict check = addonContract.reentrantInvoke("ensureCanGetOneTkn", [userAddress.toString()], []); + + if(pmtAmt < 10_00_000_000) then throw("Min pmt 10 LP") else + + if(slippage < 0 || exchResult < 0 || outAmount < 0) then throw("Wrong params") else + if (lpId != pmtAssetId.toBase58String()) then throw("Wrong LP") else + + let r = ego(i.transactionId.toBase58String(), pmtAssetId.toBase58String(), pmtAmt, i.caller) + let estimAmAmt = r._1 + let estimPrAmt = r._2 + + let amBalance = getAccBalance(amId) + let prBalance = getAccBalance(prId) + + let (amBalanceNow, prBalanceNow, virtSwapInAm, virtSwapOutPr, virtSwapInPr, virtSwapOutAm, totalGet) = + if (outAssetId == amId) then { + (amBalance - estimAmAmt, prBalance - estimPrAmt, exchResult, estimPrAmt, 0, 0, estimAmAmt + exchResult) + } else if (outAssetId == prId) then { + (amBalance - estimAmAmt, prBalance - estimPrAmt, 0, 0, exchResult, estimAmAmt, estimPrAmt + exchResult) + } else throw("wrong outAssetId") + + if(virtSwapInAm < 0 || virtSwapInPr < 0) then throw("Wrong calc") else + + let D0 = gwxCntr.invoke("calcD", [ + amBalanceNow.toString(), + prBalanceNow.toString(), A, Amult, Dconv],[]); + + let D1 = gwxCntr.invoke("calcD", [ + (amBalanceNow - virtSwapInAm + virtSwapOutAm).toString(), + (prBalanceNow + virtSwapOutPr - virtSwapInPr).toString(), A, Amult, Dconv],[]); + + strict D0vsD1 = vd(D1.str().parseBigIntValue(), D0.str().parseBigIntValue(), slippage4D) + + strict finalRes = vad(totalGet.toBigInt(), outAmount.toBigInt(), slippage.toBigInt()) + + let (outAm, outPr) = if(outAssetId == amId) then (finalRes._2.toInt(), 0) else (0, finalRes._2.toInt()) + + let curPrX18 = cpbi(prBalance.t1(prDcm), amBalance.t1(amDcm)) + let curPr = curPrX18.f1(scale8) + + strict state = + [ + ScriptTransfer(userAddress, outAm + outPr, if (outAssetId == "WAVES") then unit else outAssetId.fromBase58String()), + StringEntry( + gau(userAddress.toString(), txId58), + dataGetActionInfo(outAm, outPr, pmtAmt, curPr, height, lastBlock.timestamp)), + IntegerEntry(pl(), curPr), + IntegerEntry(ph(height, lastBlock.timestamp), curPr) + ] + + strict burn = invoke(fca, + "burn", + [pmtAmt], + [AttachedPayment(pmtAssetId, pmtAmt)]) + state +} + +@Callable(i) +func getNoLess(noLessThenAmtAsset: Int, noLessThenPriceAsset: Int) = { + let r = cg(i) + let outAmAmt = r._1 + let outPrAmt = r._2 + let pmtAmt = r._3 + let pmtAssetId = r._4 + let state = r._5 + if (outAmAmt < noLessThenAmtAsset) then throw("Failed: " + outAmAmt.toString() + " < " + noLessThenAmtAsset.toString()) else + if (outPrAmt < noLessThenPriceAsset) then throw("Failed: " + outPrAmt.toString() + " < " + noLessThenPriceAsset.toString()) else + + strict burnLPAssetOnFactory = invoke(fca, + "burn", + [pmtAmt], + [AttachedPayment(pmtAssetId, pmtAmt)]) + state +} + +# Unstake LP tokens and exit from pool +@Callable(i) +func unstakeAndGet(amount: Int) = { + strict checkPayments = if (i.payments.size() != 0) then throw("No pmnts expd") else true + + let cfg = gpc() + let factoryCfg = gfc() + + let lpAssetId = cfg[idxLPAsId].fromBase58String() + let staking = factoryCfg[idxFactStakCntr].addressFromString().valueOrErrorMessage("Wr st addr") + + # negative amount will not pass + strict unstakeInv = staking.invoke("unstake", [lpAssetId.toBase58String(), amount], []) + + let r = ego(i.transactionId.toBase58String(), lpAssetId.toBase58String(), amount, i.caller) + let sts = r._9.parseIntValue() + let state = r._10 + + strict v = if (igs() || sts == PoolShutdown) then throw("Blocked: " + sts.toString()) else true + + strict burnA = invoke(fca, "burn", [amount], [AttachedPayment(lpAssetId, amount)]) + + state +} + +# purpose: +# used BY FACTORY for activating new LP pool. Validate it was called only once. +# actions: +# 1. issue new LP token and save data in state +# 2. burn LP token +# 3. write initial price, that is used for first deposit +# arguments: +# attach: +# return: +@Callable(i) +func activate(amtAsStr: String, prAsStr: String) = { + if (i.caller.toString() != fca.toString()) then throw("denied") else { + ([ + StringEntry(aa(),amtAsStr), + StringEntry(pa(),prAsStr) + ], + "success") + } +} + +# Set string from addon +@Callable(i) +func setS(k: String, v: String) = { + if (i.caller.toString() != this.strf(ada())) then pd else + + [ + StringEntry(k, v) + ] +} + +# Set integer from addon +@Callable(i) +func setI(k: String, v: Int) = { + if (i.caller.toString() != this.strf(ada())) then pd else + + [ + IntegerEntry(k, v) + ] +} + +# API wrappers +@Callable(i) +func getPoolConfigWrapperREADONLY() = { + ( + [], + gpc() + ) +} + +@Callable(i) +func getAccBalanceWrapperREADONLY(assetId: String) = { + ( + [], + assetId.getAccBalance() + ) +} + +@Callable(i) +func calcPricesWrapperREADONLY(amAmt: Int, prAmt: Int, lpAmt: Int) = { + let pr = calcPrices(amAmt, prAmt, lpAmt) + ( + [], + [ + pr[0].toString(), + pr[1].toString(), + pr[2].toString() + ] + ) +} + +@Callable(i) +func fromX18WrapperREADONLY(val: String, resScaleMult: Int) = { + ( + [], + f1(val.parseBigIntValue(), resScaleMult) + ) +} + +@Callable(i) +func toX18WrapperREADONLY(origVal: Int, origScaleMult: Int) = { + ( + [], + t1(origVal, origScaleMult).toString() + ) +} + +@Callable(i) +func calcPriceBigIntWrapperREADONLY(prAmtX18: String, amAmtX18: String) = { + ( + [], + cpbi(prAmtX18.parseBigIntValue(), amAmtX18.parseBigIntValue()).toString() + ) +} + +@Callable(i) +func estimatePutOperationWrapperREADONLY( + txId58: String, + slippage: Int, + inAmAmt: Int, + inAmId: ByteVector, + inPrAmt: Int, + inPrId: ByteVector, + usrAddr: String, + isEval: Boolean, + emitLp: Boolean +) = { + ( + [], + epo( + txId58, + slippage, + inAmAmt, + inAmId, + inPrAmt, + inPrId, + usrAddr, + isEval, + emitLp, + false, + 0, + "" + ) + ) +} + +@Callable(i) +func estimateGetOperationWrapperREADONLY(txId58: String, pmtAsId: String, pmtLpAmt: Int, usrAddr: String) = { + let r = ego( + txId58, + pmtAsId, + pmtLpAmt, + usrAddr.addressFromStringValue() + ) + ( + [], + (r._1, r._2, r._3, r._4, r._5, r._6, r._7, r._8.toString(), r._9, r._10) + ) +} + +@Verifier(tx) +func verify() = { + match tx { + case order: Order => + let mtchPub = mp() + let orV = moa(order) + let sndrV = sigVerify(order.bodyBytes, order.proofs[0], order.senderPublicKey) + let mtchV = sigVerify(order.bodyBytes, order.proofs[1], mtchPub) + + (orV && sndrV && mtchV) + || toe(orV, sndrV, mtchV) + case _ => { + let targetPublicKey = match m() { + case pk: ByteVector => pk + case _: Unit => tx.senderPublicKey + } + sigVerify(tx.bodyBytes, tx.proofs[0], targetPublicKey) + } + } +} diff --git a/test/lp_stable/lp_to_lp_stable_migration/_hooks.mjs b/test/lp_stable/lp_to_lp_stable_migration/_hooks.mjs new file mode 100644 index 000000000..2e3b47257 --- /dev/null +++ b/test/lp_stable/lp_to_lp_stable_migration/_hooks.mjs @@ -0,0 +1,240 @@ +import { address, publicKey, randomSeed } from '@waves/ts-lib-crypto'; +import { + data, + invokeScript, + issue, + massTransfer, + nodeInteraction, +} from '@waves/waves-transactions'; +import { create } from '@waves/node-api-js'; +import { format, join } from 'path'; +import { setScriptFromFile } from '../../utils.mjs'; + +const { waitForTx } = nodeInteraction; +const apiBase = process.env.API_NODE_URL; +const seed = 'waves private node seed with waves tokens'; +const chainId = 'R'; +const api = create(apiBase); +const seedWordsCount = 5; +const ridePath = 'ride'; +const mockRidePath = join('test', 'lp', 'mock'); +const lpPath = format({ dir: ridePath, base: 'lp.ride' }); +const factoryV2Path = format({ dir: ridePath, base: 'factory_v2.ride' }); +const stakingPath = format({ dir: mockRidePath, base: 'staking.mock.ride' }); +const slippagePath = format({ dir: mockRidePath, base: 'slippage.mock.ride' }); +const assetsStorePath = format({ dir: mockRidePath, base: 'assets_store.mock.ride' }); + +export const mochaHooks = { + async beforeAll() { + const names = [ + 'lp', + 'lpStableV2Addon', + 'factoryV2', + 'staking', + 'slippage', + 'manager', + 'store', + 'user1', + ]; + this.accounts = Object.fromEntries(names.map((item) => [item, randomSeed(seedWordsCount)])); + const seeds = Object.values(this.accounts); + const amount = 10e10; + const massTransferTx = massTransfer({ + transfers: seeds.map((item) => ({ recipient: address(item, chainId), amount })), + chainId, + }, seed); + await api.transactions.broadcast(massTransferTx, {}); + await waitForTx(massTransferTx.id, { apiBase }); + + await setScriptFromFile(lpPath, this.accounts.lp); + await setScriptFromFile(factoryV2Path, this.accounts.factoryV2); + await setScriptFromFile(stakingPath, this.accounts.staking); + await setScriptFromFile(slippagePath, this.accounts.slippage); + await setScriptFromFile(assetsStorePath, this.accounts.store); + + const usdnIssueTx = issue({ + name: 'USDN', + description: '', + quantity: 10e6, + decimals: 6, + chainId, + }, seed); + await api.transactions.broadcast(usdnIssueTx, {}); + await waitForTx(usdnIssueTx.id, { apiBase }); + this.usdnAssetId = usdnIssueTx.id; + + const usdnAmount = 10e6; + const massTransferTxUSDN = massTransfer({ + transfers: names.slice(-1).map((name) => ({ + recipient: address(this.accounts[name], chainId), amount: usdnAmount, + })), + assetId: this.usdnAssetId, + chainId, + }, seed); + await api.transactions.broadcast(massTransferTxUSDN, {}); + await waitForTx(massTransferTxUSDN.id, { apiBase }); + + const eastIssueTx = issue({ + name: 'EAST', + description: '', + quantity: 10e8, + decimals: 8, + chainId, + }, seed); + await api.transactions.broadcast(eastIssueTx, {}); + await waitForTx(eastIssueTx.id, { apiBase }); + this.eastAssetId = eastIssueTx.id; + + const eastAmount = 10e8; + const massTransferTxEAST = massTransfer({ + transfers: names.slice(-1).map((name) => ({ + recipient: address(this.accounts[name], chainId), amount: eastAmount, + })), + assetId: this.eastAssetId, + chainId, + }, seed); + await api.transactions.broadcast(massTransferTxEAST, {}); + await waitForTx(massTransferTxEAST.id, { apiBase }); + + const constructorFactoryV2InvokeTx = invokeScript({ + dApp: address(this.accounts.factoryV2, chainId), + additionalFee: 4e5, + call: { + function: 'constructor', + args: [ + { type: 'string', value: address(this.accounts.staking, chainId) }, + { type: 'string', value: '' }, + { type: 'string', value: '' }, + { type: 'string', value: '' }, + { type: 'string', value: '' }, + { type: 'string', value: '' }, + { type: 'string', value: address(this.accounts.slippage, chainId) }, + { type: 'integer', value: 8 }, + ], + }, + chainId, + }, this.accounts.factoryV2); + await api.transactions.broadcast(constructorFactoryV2InvokeTx, {}); + await waitForTx(constructorFactoryV2InvokeTx.id, { apiBase }); + + const constructorV2FactoryV2InvokeTx = invokeScript({ + dApp: address(this.accounts.factoryV2, chainId), + additionalFee: 4e5, + call: { + function: 'constructorV2', + args: [ + { type: 'string', value: '' }, + ], + }, + chainId, + }, this.accounts.factoryV2); + await api.transactions.broadcast(constructorV2FactoryV2InvokeTx, {}); + await waitForTx(constructorV2FactoryV2InvokeTx.id, { apiBase }); + + const constructorV3FactoryV2InvokeTx = invokeScript({ + dApp: address(this.accounts.factoryV2, chainId), + additionalFee: 4e5, + call: { + function: 'constructorV3', + args: [ + { type: 'string', value: '' }, + { type: 'string', value: '' }, + { type: 'string', value: '' }, + { type: 'string', value: '' }, + ], + }, + chainId, + }, this.accounts.factoryV2); + await api.transactions.broadcast(constructorV3FactoryV2InvokeTx, {}); + await waitForTx(constructorV3FactoryV2InvokeTx.id, { apiBase }); + + const constructorV4FactoryV2InvokeTx = invokeScript({ + dApp: address(this.accounts.factoryV2, chainId), + additionalFee: 4e5, + call: { + function: 'constructorV4', + args: [ + { type: 'string', value: '' }, + { type: 'list', value: [{ type: 'string', value: '' }] }, + ], + }, + chainId, + }, this.accounts.factoryV2); + await api.transactions.broadcast(constructorV4FactoryV2InvokeTx, {}); + await waitForTx(constructorV4FactoryV2InvokeTx.id, { apiBase }); + + const constructorV5FactoryV2InvokeTx = invokeScript({ + dApp: address(this.accounts.factoryV2, chainId), + additionalFee: 4e5, + call: { + function: 'constructorV5', + args: [ + { type: 'string', value: address(this.accounts.store, chainId) }, + ], + }, + chainId, + }, this.accounts.factoryV2); + await api.transactions.broadcast(constructorV5FactoryV2InvokeTx, {}); + await waitForTx(constructorV5FactoryV2InvokeTx.id, { apiBase }); + + const setManagerFactoryV2Tx = data({ + additionalFee: 4e5, + data: [{ + key: '%s__managerPublicKey', + type: 'string', + value: publicKey(this.accounts.manager), + }], + chainId, + }, this.accounts.factoryV2); + await api.transactions.broadcast(setManagerFactoryV2Tx, {}); + await waitForTx(setManagerFactoryV2Tx.id, { apiBase }); + + const constructorLpInvokeTx = invokeScript({ + dApp: address(this.accounts.lp, chainId), + additionalFee: 4e5, + call: { + function: 'constructor', + args: [ + { type: 'string', value: address(this.accounts.factoryV2, chainId) }, + ], + }, + chainId, + }, this.accounts.lp); + await api.transactions.broadcast(constructorLpInvokeTx, {}); + await waitForTx(constructorLpInvokeTx.id, { apiBase }); + + const activateNewPoolTx = invokeScript({ + dApp: address(this.accounts.factoryV2, chainId), + fee: 100500000, + call: { + function: 'activateNewPool', + args: [ + { type: 'string', value: address(this.accounts.lp, chainId) }, + { type: 'string', value: this.eastAssetId }, + { type: 'string', value: this.usdnAssetId }, + { type: 'string', value: 'EASTUSDNLP' }, + { type: 'string', value: 'WX EAST/USDN pool liquidity provider token' }, + { type: 'integer', value: 0 }, + { type: 'string', value: '' }, + { type: 'string', value: '' }, + ], + }, + chainId, + }, this.accounts.manager); + await api.transactions.broadcast(activateNewPoolTx, {}); + const { stateChanges } = await waitForTx(activateNewPoolTx.id, { apiBase }); + this.lpAssetId = stateChanges.issues[0].assetId; + + const setManagerLpTx = data({ + additionalFee: 4e5, + data: [{ + key: '%s__managerPublicKey', + type: 'string', + value: publicKey(this.accounts.manager), + }], + chainId, + }, this.accounts.lp); + await api.transactions.broadcast(setManagerLpTx, {}); + await waitForTx(setManagerLpTx.id, { apiBase }); + }, +}; diff --git a/test/lp_stable/lp_to_lp_stable_migration/get.mjs b/test/lp_stable/lp_to_lp_stable_migration/get.mjs new file mode 100644 index 000000000..d1a4eb0cc --- /dev/null +++ b/test/lp_stable/lp_to_lp_stable_migration/get.mjs @@ -0,0 +1,234 @@ +import chai from 'chai'; +import chaiAsPromised from 'chai-as-promised'; +import { address, publicKey } from '@waves/ts-lib-crypto'; +import { + invokeScript, nodeInteraction as ni, setScript, +} from '@waves/waves-transactions'; +import { create } from '@waves/node-api-js'; +import { format } from 'path'; +import ride from '@waves/ride-js'; +import { readFile } from 'fs/promises'; + +chai.use(chaiAsPromised); +const { expect } = chai; + +const apiBase = process.env.API_NODE_URL; +const chainId = 'R'; + +const api = create(apiBase); + +describe('lp_to_lp_stable_migration: get.mjs', /** @this {MochaSuiteModified} */() => { + it('should successfully change the state in the same way after changing the script from lp.ride to lp_stable.ride when executing the put and get method', async function () { + const usdnAmount = 1e6; + const eastAmount = 1e8; + const shouldAutoStake = false; + + const expectedOutLpAmt = 1e8; + const expectedPriceLast = 1e8; + const expectedPriceHistory = 1e8; + const expectedInvokesCount = 1; + + const lp = address(this.accounts.lp, chainId); + + // putFirst + // -------------------------------------------------------------------------------------------- + const putFirst = invokeScript({ + dApp: lp, + payment: [ + { assetId: this.eastAssetId, amount: eastAmount }, + { assetId: this.usdnAssetId, amount: usdnAmount }, + ], + call: { + function: 'put', + args: [ + { type: 'integer', value: 0 }, + { type: 'boolean', value: shouldAutoStake }, + ], + }, + chainId, + }, this.accounts.user1); + await api.transactions.broadcast(putFirst, {}); + await ni.waitForTx(putFirst.id, { apiBase }); + + // getAfterPutFirst + // -------------------------------------------------------------------------------------------- + const getAfterPutFirst = invokeScript({ + dApp: lp, + payment: [ + { assetId: this.lpAssetId, amount: expectedOutLpAmt }, + ], + call: { + function: 'get', + args: [], + }, + chainId, + }, this.accounts.user1); + await api.transactions.broadcast(getAfterPutFirst, {}); + const { + height: heightGetAfterPutFirst, + stateChanges: stateChangesGetAfterPutFirst, + id: idGetAfterPutFirst, + } = await ni.waitForTx(getAfterPutFirst.id, { apiBase }); + + const { + timestamp: timestampGetAfterPutFirst, + } = await api.blocks.fetchHeadersAt(heightGetAfterPutFirst); + const keyPriceHistoryGetAfterPutFirst = `%s%s%d%d__price__history__${heightGetAfterPutFirst}__${timestampGetAfterPutFirst}`; + + // check getAfterPutFirst + // -------------------------------------------------------------------------------------------- + expect(stateChangesGetAfterPutFirst.data).to.eql([{ + key: `%s%s%s__G__${address(this.accounts.user1, chainId)}__${idGetAfterPutFirst}`, + type: 'string', + value: `%d%d%d%d%d%d__${eastAmount}__${usdnAmount}__${expectedOutLpAmt}__${expectedPriceLast}__${heightGetAfterPutFirst}__${timestampGetAfterPutFirst}`, + }, { + key: '%s%s__price__last', + type: 'integer', + value: expectedPriceLast, + }, { + key: keyPriceHistoryGetAfterPutFirst, + type: 'integer', + value: expectedPriceHistory, + }]); + + expect(stateChangesGetAfterPutFirst.transfers).to.eql([{ + address: address(this.accounts.user1, chainId), + asset: this.eastAssetId, + amount: eastAmount, + }, { + address: address(this.accounts.user1, chainId), + asset: this.usdnAssetId, + amount: usdnAmount, + }]); + + const { invokes: invokesGetAfterPutFirst } = stateChangesGetAfterPutFirst; + expect(invokesGetAfterPutFirst.length).to.eql(expectedInvokesCount); + + expect(invokesGetAfterPutFirst[0].dApp).to.eql(address(this.accounts.factoryV2, chainId)); + expect(invokesGetAfterPutFirst[0].call.function).to.eql('burn'); + expect(invokesGetAfterPutFirst[0].call.args).to.eql([ + { + type: 'Int', + value: expectedOutLpAmt, + }]); + expect(invokesGetAfterPutFirst[0].stateChanges.burns).to.eql([{ + assetId: this.lpAssetId, + quantity: expectedOutLpAmt, + }]); + + // putSecond + // -------------------------------------------------------------------------------------------- + const putSecond = invokeScript({ + dApp: lp, + payment: [ + { assetId: this.eastAssetId, amount: eastAmount }, + { assetId: this.usdnAssetId, amount: usdnAmount }, + ], + call: { + function: 'put', + args: [ + { type: 'integer', value: 0 }, + { type: 'boolean', value: shouldAutoStake }, + ], + }, + chainId, + }, this.accounts.user1); + await api.transactions.broadcast(putSecond, {}); + await ni.waitForTx(putSecond.id, { apiBase }); + + // setScript + // -------------------------------------------------------------------------------------------- + const ridePath = 'ride'; + const lpStableV2Path = format({ dir: ridePath, base: 'lp_stable.ride' }); + const lpStableAddonV2Path = format({ dir: ridePath, base: 'lp_stable_addon.ride' }); + + const { base64: base64LpStableV2 } = ride.compile( + (await readFile(lpStableV2Path, { encoding: 'utf-8' })), + ).result; + const ssTxLpStableV2 = setScript({ + script: base64LpStableV2, + chainId, + fee: 38e5, + senderPublicKey: publicKey(this.accounts.lp), + }, this.accounts.manager); + await api.transactions.broadcast(ssTxLpStableV2, {}); + await ni.waitForTx(ssTxLpStableV2.id, { apiBase }); + + const { base64: base64LpStableAddonV2 } = ride.compile( + (await readFile(lpStableAddonV2Path, { encoding: 'utf-8' })), + ).result; + const ssTxLpStableAddonV2 = setScript({ + script: base64LpStableAddonV2, + chainId, + fee: 10e5, + }, this.accounts.lpStableV2Addon); + await api.transactions.broadcast(ssTxLpStableAddonV2, {}); + await ni.waitForTx(ssTxLpStableAddonV2.id, { apiBase }); + + // getAfterSetScript + // -------------------------------------------------------------------------------------------- + const getAfterSetScript = invokeScript({ + dApp: lp, + payment: [ + { assetId: this.lpAssetId, amount: expectedOutLpAmt }, + ], + call: { + function: 'get', + args: [], + }, + chainId, + }, this.accounts.user1); + await api.transactions.broadcast(getAfterSetScript, {}); + const { + height: heightGetAfterSetScript, + stateChanges: stateChangesGetAfterSetScript, + id: idGetAfterSetScript, + } = await ni.waitForTx(getAfterSetScript.id, { apiBase }); + + const { + timestamp: timestampGetAfterSetScript, + } = await api.blocks.fetchHeadersAt(heightGetAfterSetScript); + const keyPriceHistoryGetAfterSetScript = `%s%s%d%d__price__history__${heightGetAfterSetScript}__${timestampGetAfterSetScript}`; + + // check getAfterSetScript + // -------------------------------------------------------------------------------------------- + expect(stateChangesGetAfterSetScript.data).to.eql([{ + key: `%s%s%s__G__${address(this.accounts.user1, chainId)}__${idGetAfterSetScript}`, + type: 'string', + value: `%d%d%d%d%d%d__${eastAmount}__${usdnAmount}__${expectedOutLpAmt}__${expectedPriceLast}__${heightGetAfterSetScript}__${timestampGetAfterSetScript}`, + }, { + key: '%s%s__price__last', + type: 'integer', + value: expectedPriceLast, + }, { + key: keyPriceHistoryGetAfterSetScript, + type: 'integer', + value: expectedPriceHistory, + }]); + + expect(stateChangesGetAfterSetScript.transfers).to.eql([{ + address: address(this.accounts.user1, chainId), + asset: this.eastAssetId, + amount: eastAmount, + }, { + address: address(this.accounts.user1, chainId), + asset: this.usdnAssetId, + amount: usdnAmount, + }]); + + const { invokes: invokesGetAfterSetScript } = stateChangesGetAfterSetScript; + expect(invokesGetAfterSetScript.length).to.eql(expectedInvokesCount); + + expect(invokesGetAfterSetScript[0].dApp).to.eql(address(this.accounts.factoryV2, chainId)); + expect(invokesGetAfterSetScript[0].call.function).to.eql('burn'); + expect(invokesGetAfterSetScript[0].call.args).to.eql([ + { + type: 'Int', + value: expectedOutLpAmt, + }]); + expect(invokesGetAfterSetScript[0].stateChanges.burns).to.eql([{ + assetId: this.lpAssetId, + quantity: expectedOutLpAmt, + }]); + }); +}); diff --git a/test/lp_stable/lp_to_lp_stable_migration/put.mjs b/test/lp_stable/lp_to_lp_stable_migration/put.mjs new file mode 100644 index 000000000..8d9150aee --- /dev/null +++ b/test/lp_stable/lp_to_lp_stable_migration/put.mjs @@ -0,0 +1,234 @@ +import chai from 'chai'; +import chaiAsPromised from 'chai-as-promised'; +import { address, publicKey } from '@waves/ts-lib-crypto'; +import { + invokeScript, nodeInteraction as ni, setScript, +} from '@waves/waves-transactions'; +import { create } from '@waves/node-api-js'; +import { format } from 'path'; +import ride from '@waves/ride-js'; +import { readFile } from 'fs/promises'; + +chai.use(chaiAsPromised); +const { expect } = chai; + +const apiBase = process.env.API_NODE_URL; +const chainId = 'R'; + +const api = create(apiBase); + +describe('lp_to_lp_stable_migration: put.mjs', /** @this {MochaSuiteModified} */() => { + it('should successfully change the state in the same way after changing the script from lp.ride to lp_stable.ride when executing the put and get method', async function () { + const usdnAmount = 1e6; + const eastAmount = 1e8; + const shouldAutoStake = false; + + const expectedOutLpAmt = 1e8; + const expectedPriceLast = 1e8; + const expectedPriceHistory = 1e8; + const expectedInvokesCount = 1; + + const lp = address(this.accounts.lp, chainId); + + // putFirst + // -------------------------------------------------------------------------------------------- + const putFirst = invokeScript({ + dApp: lp, + payment: [ + { assetId: this.eastAssetId, amount: eastAmount }, + { assetId: this.usdnAssetId, amount: usdnAmount }, + ], + call: { + function: 'put', + args: [ + { type: 'integer', value: 0 }, + { type: 'boolean', value: shouldAutoStake }, + ], + }, + chainId, + }, this.accounts.user1); + await api.transactions.broadcast(putFirst, {}); + await ni.waitForTx(putFirst.id, { apiBase }); + + // setScript + // -------------------------------------------------------------------------------------------- + const ridePath = 'ride'; + const lpStableV2Path = format({ dir: ridePath, base: 'lp_stable.ride' }); + const lpStableAddonV2Path = format({ dir: ridePath, base: 'lp_stable_addon.ride' }); + + const { base64: base64LpStableV2 } = ride.compile( + (await readFile(lpStableV2Path, { encoding: 'utf-8' })), + ).result; + const ssTxLpStableV2 = setScript({ + script: base64LpStableV2, + chainId, + fee: 38e5, + senderPublicKey: publicKey(this.accounts.lp), + }, this.accounts.manager); + await api.transactions.broadcast(ssTxLpStableV2, {}); + await ni.waitForTx(ssTxLpStableV2.id, { apiBase }); + + const { base64: base64LpStableAddonV2 } = ride.compile( + (await readFile(lpStableAddonV2Path, { encoding: 'utf-8' })), + ).result; + const ssTxLpStableAddonV2 = setScript({ + script: base64LpStableAddonV2, + chainId, + fee: 10e5, + }, this.accounts.lpStableV2Addon); + await api.transactions.broadcast(ssTxLpStableAddonV2, {}); + await ni.waitForTx(ssTxLpStableAddonV2.id, { apiBase }); + + // putSecond + // -------------------------------------------------------------------------------------------- + const putSecond = invokeScript({ + dApp: lp, + payment: [ + { assetId: this.eastAssetId, amount: eastAmount }, + { assetId: this.usdnAssetId, amount: usdnAmount }, + ], + call: { + function: 'put', + args: [ + { type: 'integer', value: 0 }, + { type: 'boolean', value: shouldAutoStake }, + ], + }, + chainId, + }, this.accounts.user1); + await api.transactions.broadcast(putSecond, {}); + await ni.waitForTx(putSecond.id, { apiBase }); + + // getFirstAfterPut + // -------------------------------------------------------------------------------------------- + const getFirstAfterPut = invokeScript({ + dApp: lp, + payment: [ + { assetId: this.lpAssetId, amount: expectedOutLpAmt }, + ], + call: { + function: 'get', + args: [], + }, + chainId, + }, this.accounts.user1); + await api.transactions.broadcast(getFirstAfterPut, {}); + const { + height: heightGetFirstAfterPut, + stateChanges: stateChangesGetFirstAfterPut, + id: idGetFirstAfterPut, + } = await ni.waitForTx(getFirstAfterPut.id, { apiBase }); + + const { + timestamp: timestampGetFirstAfterPut, + } = await api.blocks.fetchHeadersAt(heightGetFirstAfterPut); + const keyPriceHistoryGetFirstAfterPut = `%s%s%d%d__price__history__${heightGetFirstAfterPut}__${timestampGetFirstAfterPut}`; + + // check getFirstAfterPut + // -------------------------------------------------------------------------------------------- + expect(stateChangesGetFirstAfterPut.data).to.eql([{ + key: `%s%s%s__G__${address(this.accounts.user1, chainId)}__${idGetFirstAfterPut}`, + type: 'string', + value: `%d%d%d%d%d%d__${eastAmount}__${usdnAmount}__${expectedOutLpAmt}__${expectedPriceLast}__${heightGetFirstAfterPut}__${timestampGetFirstAfterPut}`, + }, { + key: '%s%s__price__last', + type: 'integer', + value: expectedPriceLast, + }, { + key: keyPriceHistoryGetFirstAfterPut, + type: 'integer', + value: expectedPriceHistory, + }]); + + expect(stateChangesGetFirstAfterPut.transfers).to.eql([{ + address: address(this.accounts.user1, chainId), + asset: this.eastAssetId, + amount: eastAmount, + }, { + address: address(this.accounts.user1, chainId), + asset: this.usdnAssetId, + amount: usdnAmount, + }]); + + const { invokes: invokesGetFirstAfterPut } = stateChangesGetFirstAfterPut; + expect(invokesGetFirstAfterPut.length).to.eql(expectedInvokesCount); + + expect(invokesGetFirstAfterPut[0].dApp).to.eql(address(this.accounts.factoryV2, chainId)); + expect(invokesGetFirstAfterPut[0].call.function).to.eql('burn'); + expect(invokesGetFirstAfterPut[0].call.args).to.eql([ + { + type: 'Int', + value: expectedOutLpAmt, + }]); + expect(invokesGetFirstAfterPut[0].stateChanges.burns).to.eql([{ + assetId: this.lpAssetId, + quantity: expectedOutLpAmt, + }]); + + // getSecondAfterPut + // -------------------------------------------------------------------------------------------- + const getSecondAfterPut = invokeScript({ + dApp: lp, + payment: [ + { assetId: this.lpAssetId, amount: expectedOutLpAmt }, + ], + call: { + function: 'get', + args: [], + }, + chainId, + }, this.accounts.user1); + await api.transactions.broadcast(getSecondAfterPut, {}); + const { + height: heightGetSecondAfterPut, + stateChanges: stateChangesGetSecondAfterPut, + id: idGetSecondAfterPut, + } = await ni.waitForTx(getFirstAfterPut.id, { apiBase }); + + const { + timestamp: timestampGetSecondAfterPut, + } = await api.blocks.fetchHeadersAt(heightGetSecondAfterPut); + const keyPriceHistoryGetSecondAfterPut = `%s%s%d%d__price__history__${heightGetSecondAfterPut}__${timestampGetSecondAfterPut}`; + + // check getSecondAfterPut + // -------------------------------------------------------------------------------------------- + expect(stateChangesGetSecondAfterPut.data).to.eql([{ + key: `%s%s%s__G__${address(this.accounts.user1, chainId)}__${idGetSecondAfterPut}`, + type: 'string', + value: `%d%d%d%d%d%d__${eastAmount}__${usdnAmount}__${expectedOutLpAmt}__${expectedPriceLast}__${heightGetSecondAfterPut}__${timestampGetSecondAfterPut}`, + }, { + key: '%s%s__price__last', + type: 'integer', + value: expectedPriceLast, + }, { + key: keyPriceHistoryGetSecondAfterPut, + type: 'integer', + value: expectedPriceHistory, + }]); + + expect(stateChangesGetSecondAfterPut.transfers).to.eql([{ + address: address(this.accounts.user1, chainId), + asset: this.eastAssetId, + amount: eastAmount, + }, { + address: address(this.accounts.user1, chainId), + asset: this.usdnAssetId, + amount: usdnAmount, + }]); + + const { invokes: invokesGetSecondAfterPut } = stateChangesGetSecondAfterPut; + expect(invokesGetSecondAfterPut.length).to.eql(expectedInvokesCount); + + expect(invokesGetSecondAfterPut[0].dApp).to.eql(address(this.accounts.factoryV2, chainId)); + expect(invokesGetSecondAfterPut[0].call.function).to.eql('burn'); + expect(invokesGetSecondAfterPut[0].call.args).to.eql([ + { + type: 'Int', + value: expectedOutLpAmt, + }]); + expect(invokesGetSecondAfterPut[0].stateChanges.burns).to.eql([{ + assetId: this.lpAssetId, + quantity: expectedOutLpAmt, + }]); + }); +}); diff --git a/test/lp_stable/new_pools_with_different_decimals/_hooks.mjs b/test/lp_stable/new_pools_with_different_decimals/_hooks.mjs new file mode 100644 index 000000000..e40166aed --- /dev/null +++ b/test/lp_stable/new_pools_with_different_decimals/_hooks.mjs @@ -0,0 +1,283 @@ +import { address, publicKey, randomSeed } from '@waves/ts-lib-crypto'; +import { + data, + invokeScript, + issue, + massTransfer, + nodeInteraction, +} from '@waves/waves-transactions'; +import { create } from '@waves/node-api-js'; +import { format } from 'path'; +import { setScriptFromFile } from '../../utils.mjs'; + +const { waitForTx } = nodeInteraction; +const apiBase = process.env.API_NODE_URL; +const seed = 'waves private node seed with waves tokens'; +const chainId = 'R'; +const api = create(apiBase); +const seedWordsCount = 5; +const ridePath = 'ride'; +const mockRidePath = 'test/lp_stable/mock'; +const lpStablePath = format({ dir: ridePath, base: 'lp_stable.ride' }); +const lpStableAddonPath = format({ dir: ridePath, base: 'lp_stable_addon.ride' }); +const factoryV2Path = format({ dir: ridePath, base: 'factory_v2.ride' }); +const stakingPath = format({ dir: mockRidePath, base: 'staking.mock.ride' }); +const slippagePath = format({ dir: mockRidePath, base: 'slippage.mock.ride' }); +const assetsStorePath = format({ dir: mockRidePath, base: 'assets_store.mock.ride' }); +const gwxRewardPath = format({ dir: mockRidePath, base: 'gwx_reward.mock.ride' }); + +export const mochaHooks = { + async beforeAll() { + const names = ['lpStable', 'lpStableAddon', 'factoryV2', 'staking', 'slippage', 'gwxReward', 'manager', 'store', 'user1']; + this.accounts = Object.fromEntries(names.map((item) => [item, randomSeed(seedWordsCount)])); + const seeds = Object.values(this.accounts); + const amount = 1e10; + const massTransferTx = massTransfer({ + transfers: seeds.map((item) => ({ recipient: address(item, chainId), amount })), + chainId, + }, seed); + await api.transactions.broadcast(massTransferTx, {}); + await waitForTx(massTransferTx.id, { apiBase }); + + await setScriptFromFile(lpStablePath, this.accounts.lpStable); + await setScriptFromFile(lpStableAddonPath, this.accounts.lpStableAddon); + await setScriptFromFile(factoryV2Path, this.accounts.factoryV2); + await setScriptFromFile(stakingPath, this.accounts.staking); + await setScriptFromFile(slippagePath, this.accounts.slippage); + await setScriptFromFile(assetsStorePath, this.accounts.store); + await setScriptFromFile(gwxRewardPath, this.accounts.gwxReward); + + const usdnIssueTx = issue({ + name: 'USDN', + description: '', + quantity: 10e16, + decimals: 6, + chainId, + }, seed); + await api.transactions.broadcast(usdnIssueTx, {}); + await waitForTx(usdnIssueTx.id, { apiBase }); + this.usdnAssetId = usdnIssueTx.id; + + const usdnAmount = 1e16; + const massTransferTxUSDN = massTransfer({ + transfers: names.slice(-1).map((name) => ({ + recipient: address(this.accounts[name], chainId), amount: usdnAmount, + })), + assetId: this.usdnAssetId, + chainId, + }, seed); + await api.transactions.broadcast(massTransferTxUSDN, {}); + await waitForTx(massTransferTxUSDN.id, { apiBase }); + + const usdtIssueTx = issue({ + name: 'USDT', + description: '', + quantity: 1e10, + decimals: 8, + chainId, + }, seed); + await api.transactions.broadcast(usdtIssueTx, {}); + await waitForTx(usdtIssueTx.id, { apiBase }); + this.usdtAssetId = usdtIssueTx.id; + + const usdtAmount = 1e10; + const massTransferTxUSDT = massTransfer({ + transfers: names.slice(-1).map((name) => ({ + recipient: address(this.accounts[name], chainId), amount: usdtAmount, + })), + assetId: this.usdtAssetId, + chainId, + }, seed); + await api.transactions.broadcast(massTransferTxUSDT, {}); + await waitForTx(massTransferTxUSDT.id, { apiBase }); + + const constructorFactoryV2InvokeTx = invokeScript({ + dApp: address(this.accounts.factoryV2, chainId), + additionalFee: 4e5, + call: { + function: 'constructor', + args: [ + { type: 'string', value: address(this.accounts.staking, chainId) }, + { type: 'string', value: '' }, + { type: 'string', value: '' }, + { type: 'string', value: '' }, + { type: 'string', value: '' }, + { type: 'string', value: '' }, + { type: 'string', value: address(this.accounts.slippage, chainId) }, + { type: 'integer', value: 8 }, + ], + }, + chainId, + }, this.accounts.factoryV2); + await api.transactions.broadcast(constructorFactoryV2InvokeTx, {}); + await waitForTx(constructorFactoryV2InvokeTx.id, { apiBase }); + + const constructorV2FactoryV2InvokeTx = invokeScript({ + dApp: address(this.accounts.factoryV2, chainId), + additionalFee: 4e5, + call: { + function: 'constructorV2', + args: [ + { type: 'string', value: '' }, + ], + }, + chainId, + }, this.accounts.factoryV2); + await api.transactions.broadcast(constructorV2FactoryV2InvokeTx, {}); + await waitForTx(constructorV2FactoryV2InvokeTx.id, { apiBase }); + + const constructorV3FactoryV2InvokeTx = invokeScript({ + dApp: address(this.accounts.factoryV2, chainId), + additionalFee: 4e5, + call: { + function: 'constructorV3', + args: [ + { type: 'string', value: '' }, + { type: 'string', value: '' }, + { type: 'string', value: address(this.accounts.gwxReward, chainId) }, + { type: 'string', value: '' }, + ], + }, + chainId, + }, this.accounts.factoryV2); + await api.transactions.broadcast(constructorV3FactoryV2InvokeTx, {}); + await waitForTx(constructorV3FactoryV2InvokeTx.id, { apiBase }); + + const constructorV4FactoryV2InvokeTx = invokeScript({ + dApp: address(this.accounts.factoryV2, chainId), + additionalFee: 4e5, + call: { + function: 'constructorV4', + args: [ + { type: 'string', value: '' }, + { type: 'list', value: [{ type: 'string', value: '' }] }, + ], + }, + chainId, + }, this.accounts.factoryV2); + await api.transactions.broadcast(constructorV4FactoryV2InvokeTx, {}); + await waitForTx(constructorV4FactoryV2InvokeTx.id, { apiBase }); + + const constructorV5FactoryV2InvokeTx = invokeScript({ + dApp: address(this.accounts.factoryV2, chainId), + additionalFee: 4e5, + call: { + function: 'constructorV5', + args: [ + { type: 'string', value: address(this.accounts.store, chainId) }, + ], + }, + chainId, + }, this.accounts.factoryV2); + await api.transactions.broadcast(constructorV5FactoryV2InvokeTx, {}); + await waitForTx(constructorV5FactoryV2InvokeTx.id, { apiBase }); + + const setManagerFactoryV2Tx = data({ + additionalFee: 4e5, + data: [{ + key: '%s__managerPublicKey', + type: 'string', + value: publicKey(this.accounts.manager), + }], + chainId, + }, this.accounts.factoryV2); + await api.transactions.broadcast(setManagerFactoryV2Tx, {}); + await waitForTx(setManagerFactoryV2Tx.id, { apiBase }); + + const constructorLpStableInvokeTx = invokeScript({ + dApp: address(this.accounts.lpStable, chainId), + additionalFee: 4e5, + call: { + function: 'constructor', + args: [ + { type: 'string', value: address(this.accounts.factoryV2, chainId) }, + ], + }, + chainId, + }, this.accounts.lpStable); + await api.transactions.broadcast(constructorLpStableInvokeTx, {}); + await waitForTx(constructorLpStableInvokeTx.id, { apiBase }); + + const activateNewPoolTx = invokeScript({ + dApp: address(this.accounts.factoryV2, chainId), + fee: 100500000, + call: { + function: 'activateNewPool', + args: [ + { type: 'string', value: address(this.accounts.lpStable, chainId) }, + { type: 'string', value: this.usdtAssetId }, + { type: 'string', value: this.usdnAssetId }, + { type: 'string', value: 'USDTUSDNLP' }, + { type: 'string', value: 'WX USDT/USDN pool liquidity provider token' }, + { type: 'integer', value: 0 }, + { type: 'string', value: '' }, + { type: 'string', value: '' }, + ], + }, + chainId, + }, this.accounts.manager); + await api.transactions.broadcast(activateNewPoolTx, {}); + const { stateChanges } = await waitForTx(activateNewPoolTx.id, { apiBase }); + this.lpStableAssetId = stateChanges.issues[0].assetId; + + const setLpStableAddonTx = data({ + additionalFee: 4e5, + data: [{ + key: '%s__addonAddr', + type: 'string', + value: address(this.accounts.lpStableAddon, chainId), + }], + chainId, + }, this.accounts.lpStable); + await api.transactions.broadcast(setLpStableAddonTx, {}); + await waitForTx(setLpStableAddonTx.id, { apiBase }); + + const setLpStableTx = data({ + additionalFee: 4e5, + data: [{ + key: '%s__poolAddress', + type: 'string', + value: address(this.accounts.lpStable, chainId), + }], + chainId, + }, this.accounts.lpStableAddon); + await api.transactions.broadcast(setLpStableTx, {}); + await waitForTx(setLpStableTx.id, { apiBase }); + + const setDelayTx = data({ + additionalFee: 4e5, + data: [{ + key: '%s__delay', + type: 'integer', + value: 2, + }], + chainId, + }, this.accounts.lpStable); + await api.transactions.broadcast(setDelayTx, {}); + await waitForTx(setDelayTx.id, { apiBase }); + + const setAmpTx = data({ + additionalFee: 4e5, + data: [{ + key: '%s__amp', + type: 'string', + value: '1000', + }], + chainId, + }, this.accounts.lpStable); + await api.transactions.broadcast(setAmpTx, {}); + await waitForTx(setAmpTx.id, { apiBase }); + + const setManagerLpStableTx = data({ + additionalFee: 4e5, + data: [{ + key: '%s__managerPublicKey', + type: 'string', + value: publicKey(this.accounts.manager), + }], + chainId, + }, this.accounts.lpStable); + await api.transactions.broadcast(setManagerLpStableTx, {}); + await waitForTx(setManagerLpStableTx.id, { apiBase }); + }, +}; diff --git a/test/lp_stable/new_pools_with_different_decimals/estimatePutOperationWrapperREADONLY.mjs b/test/lp_stable/new_pools_with_different_decimals/estimatePutOperationWrapperREADONLY.mjs new file mode 100644 index 000000000..85ea1a079 --- /dev/null +++ b/test/lp_stable/new_pools_with_different_decimals/estimatePutOperationWrapperREADONLY.mjs @@ -0,0 +1,76 @@ +import chai from 'chai'; +import chaiAsPromised from 'chai-as-promised'; + +import { address } from '@waves/ts-lib-crypto'; +import { create } from '@waves/node-api-js'; + +chai.use(chaiAsPromised); +const { expect } = chai; + +const apiBase = process.env.API_NODE_URL; +const chainId = 'R'; +const api = create(apiBase); + +describe( + 'lp_stable: estimatePutOperationWrapperREADONLY.mjs', + /** @this {MochaSuiteModified} */() => { + it('should successfully estimatePutOperationWrapperREADONLY', async function () { + const txId58 = '\"\"'; /* eslint-disable-line */ + const slippage = 500000; + const inAmAmt = 1e8; + const inAmId = this.usdtAssetId; + const inPrAmt = 1e8; + const inPrId = this.usdnAssetId; + const usrAddr = '\"\"'; /* eslint-disable-line */ + const isEval = true; + const emitLp = false; + + const expected1 = { type: 'Int', value: 1000000000 }; // outLp + const expected2 = { type: 'Int', value: 0 }; // emitLpAmount + const expected3 = { type: 'Int', value: 10000000000 }; // currentPrice + const expected4 = { type: 'Int', value: 0 }; // amountBalance + const expected5 = { type: 'Int', value: 0 }; // priceBalance + const expected6 = { type: 'Int', value: 0 }; // lpEmission + const expected7 = { type: 'ByteVector', value: this.lpStableAssetId }; // lpAssetId + const expected8 = { type: 'String', value: '1' }; // sts + const expected9 = { + type: 'IntegerEntry', + value: { + key: { + type: 'String', + value: '%s%s__price__last', + }, + value: { + type: 'Int', + value: 10000000000, + }, + }, + }; + + const expected10 = { type: 'Int', value: 0 }; // amountDiff + const expected11 = { type: 'Int', value: 0 }; // priceDiff + const expected12 = { type: 'String', value: this.usdtAssetId }; // inAmountAssetId + const expected13 = { type: 'String', value: this.usdnAssetId }; // inPriceAssetId + + const lpStable = address(this.accounts.lpStable, chainId); + + const expr = `estimatePutOperationWrapperREADONLY(${txId58}, ${slippage}, ${inAmAmt}, \"${inAmId}\", ${inPrAmt}, \"${inPrId}\", ${usrAddr}, ${isEval}, ${emitLp})`; /* eslint-disable-line */ + const response = await api.utils.fetchEvaluate(lpStable, expr); + const checkData = response.result.value._2.value; /* eslint-disable-line */ + + expect(checkData._1).to.eql(expected1); /* eslint-disable-line */ + expect(checkData._2).to.eql(expected2); /* eslint-disable-line */ + expect(checkData._3).to.eql(expected3); /* eslint-disable-line */ + expect(checkData._4).to.eql(expected4); /* eslint-disable-line */ + expect(checkData._5).to.eql(expected5); /* eslint-disable-line */ + expect(checkData._6).to.eql(expected6); /* eslint-disable-line */ + expect(checkData._7).to.eql(expected7); /* eslint-disable-line */ + expect(checkData._8).to.eql(expected8); /* eslint-disable-line */ + expect(checkData._9.value[0]).to.eql(expected9); /* eslint-disable-line */ + expect(checkData._10).to.eql(expected10); /* eslint-disable-line */ + expect(checkData._11).to.eql(expected11); /* eslint-disable-line */ + expect(checkData._12).to.eql(expected12); /* eslint-disable-line */ + expect(checkData._13).to.eql(expected13); /* eslint-disable-line */ + }); + }, +); diff --git a/test/lp_stable/new_pools_with_different_decimals/get.mjs b/test/lp_stable/new_pools_with_different_decimals/get.mjs new file mode 100644 index 000000000..84316e671 --- /dev/null +++ b/test/lp_stable/new_pools_with_different_decimals/get.mjs @@ -0,0 +1,93 @@ +import chai from 'chai'; +import chaiAsPromised from 'chai-as-promised'; +import { address } from '@waves/ts-lib-crypto'; +import { invokeScript, nodeInteraction as ni } from '@waves/waves-transactions'; +import { create } from '@waves/node-api-js'; + +chai.use(chaiAsPromised); +const { expect } = chai; + +const apiBase = process.env.API_NODE_URL; +const chainId = 'R'; + +const api = create(apiBase); + +describe('lp_stable: get.mjs', /** @this {MochaSuiteModified} */() => { + it( + 'should successfully get. The put method uses the shouldAutoStake argument with a value of false', + async function () { + const usdnAmount = 1e16 / 10; + const usdtAmount = 1e8 / 10; + const lpStableAmount = 1e11; + const shouldAutoStake = false; + const priceLast = 1e18; + const priceHistory = 1e18; + + const lpStable = address(this.accounts.lpStable, chainId); + + const put = invokeScript({ + dApp: lpStable, + payment: [ + { assetId: this.usdtAssetId, amount: usdtAmount }, + { assetId: this.usdnAssetId, amount: usdnAmount }, + ], + call: { + function: 'put', + args: [ + { type: 'integer', value: 0 }, + { type: 'boolean', value: shouldAutoStake }, + ], + }, + chainId, + }, this.accounts.user1); + await api.transactions.broadcast(put, {}); + await ni.waitForTx(put.id, { apiBase }); + + const get = invokeScript({ + dApp: lpStable, + payment: [ + { assetId: this.lpStableAssetId, amount: lpStableAmount }, + ], + call: { + function: 'get', + args: [], + }, + chainId, + }, this.accounts.user1); + await api.transactions.broadcast(get, {}); + const { height, stateChanges, id } = await ni.waitForTx(get.id, { apiBase }); + + const { timestamp } = await api.blocks.fetchHeadersAt(height); + const keyPriceHistory = `%s%s%d%d__price__history__${height}__${timestamp}`; + + expect(stateChanges.data).to.eql([{ + key: `%s%s%s__G__${address(this.accounts.user1, chainId)}__${id}`, + type: 'string', + value: `%d%d%d%d%d%d__${usdtAmount / 10}__${usdnAmount / 10}__${lpStableAmount}__${priceLast}__${height}__${timestamp}`, + }, { + key: '%s%s__price__last', + type: 'integer', + value: priceLast.toString(), + }, { + key: keyPriceHistory, + type: 'integer', + value: priceHistory.toString(), + }]); + + expect(stateChanges.transfers).to.eql([{ + address: address(this.accounts.user1, chainId), + asset: this.usdtAssetId, + amount: usdtAmount / 10, + }, { + address: address(this.accounts.user1, chainId), + asset: this.usdnAssetId, + amount: (usdnAmount / 10).toString(), + }]); + + expect(stateChanges.invokes.map((item) => [item.dApp, item.call.function])) + .to.deep.include.members([ + [address(this.accounts.factoryV2, chainId), 'burn'], + ]); + }, + ); +}); diff --git a/test/lp_stable/new_pools_with_different_decimals/getOneTkn.mjs b/test/lp_stable/new_pools_with_different_decimals/getOneTkn.mjs new file mode 100644 index 000000000..427ae7e85 --- /dev/null +++ b/test/lp_stable/new_pools_with_different_decimals/getOneTkn.mjs @@ -0,0 +1,106 @@ +import chai from 'chai'; +import chaiAsPromised from 'chai-as-promised'; +import { address } from '@waves/ts-lib-crypto'; +import { invokeScript, nodeInteraction as ni } from '@waves/waves-transactions'; +import { create } from '@waves/node-api-js'; + +chai.use(chaiAsPromised); +const { expect } = chai; + +const apiBase = process.env.API_NODE_URL; +const chainId = 'R'; + +const api = create(apiBase); + +describe('lp_stable: getOneTkn.mjs', /** @this {MochaSuiteModified} */() => { + it('should successfully getOneTkn.', async function () { + const amAssetPart = 1e10; + const prAssetPart = 1e10; + const outLp = 1e11; + const slippage = 1e3; + const autoStake = false; + const usdtAmount = 1e10; + + const exchResult = 0; + const notUsed = 0; + + const expectedOutAmAmt = 1e10; + const expectedOutPrAmt = 0; + + const lpStable = address(this.accounts.lpStable, chainId); + + const putOneTkn = invokeScript({ + dApp: lpStable, + payment: [ + { assetId: this.usdtAssetId, amount: usdtAmount }, + ], + call: { + function: 'putOneTkn', + args: [ + { type: 'integer', value: amAssetPart }, + { type: 'integer', value: prAssetPart }, + { type: 'integer', value: outLp }, + { type: 'integer', value: slippage }, + { type: 'boolean', value: autoStake }, + ], + }, + chainId, + }, this.accounts.user1); + await api.transactions.broadcast(putOneTkn, {}); + await ni.waitForTx(putOneTkn.id, { apiBase }); + + const getOneTkn = invokeScript({ + dApp: lpStable, + payment: [ + { assetId: this.lpStableAssetId, amount: outLp }, + ], + call: { + function: 'getOneTkn', + args: [ + { type: 'integer', value: exchResult }, + { type: 'integer', value: notUsed }, + { type: 'integer', value: usdtAmount }, + { type: 'string', value: this.usdtAssetId }, + { type: 'integer', value: slippage }, + ], + }, + chainId, + }, this.accounts.user1); + await api.transactions.broadcast(getOneTkn, {}); + const { + height: heightAfterGetOneTkn, + stateChanges, + id, + } = await ni.waitForTx(getOneTkn.id, { apiBase }); + + const { timestamp } = await api.blocks.fetchHeadersAt(heightAfterGetOneTkn); + const keyPriceHistory = `%s%s%d%d__price__history__${heightAfterGetOneTkn}__${timestamp}`; + + expect(stateChanges.data).to.eql([{ + key: `%s%s%s__G__${address(this.accounts.user1, chainId)}__${id}`, + type: 'string', + value: `%d%d%d%d%d%d__${expectedOutAmAmt}__${expectedOutPrAmt}__${outLp}__0__${heightAfterGetOneTkn}__${timestamp}`, + }, { + key: '%s%s__price__last', + type: 'integer', + value: 0, + }, { + key: keyPriceHistory, + type: 'integer', + value: 0, + }]); + + expect(stateChanges.transfers).to.eql([{ + address: address(this.accounts.user1, chainId), + asset: this.usdtAssetId, + amount: usdtAmount, + }]); + + expect(stateChanges.invokes.map((item) => [item.dApp, item.call.function])) + .to.deep.include.members([ + [address(this.accounts.gwxReward, chainId), 'calcD'], + [address(this.accounts.gwxReward, chainId), 'calcD'], + [address(this.accounts.factoryV2, chainId), 'burn'], + ]); + }); +}); diff --git a/test/lp_stable/new_pools_with_different_decimals/put.mjs b/test/lp_stable/new_pools_with_different_decimals/put.mjs new file mode 100644 index 000000000..42d4dc0b5 --- /dev/null +++ b/test/lp_stable/new_pools_with_different_decimals/put.mjs @@ -0,0 +1,72 @@ +import chai from 'chai'; +import chaiAsPromised from 'chai-as-promised'; +import { address } from '@waves/ts-lib-crypto'; +import { invokeScript, nodeInteraction as ni } from '@waves/waves-transactions'; +import { create } from '@waves/node-api-js'; + +chai.use(chaiAsPromised); +const { expect } = chai; + +const apiBase = process.env.API_NODE_URL; +const chainId = 'R'; + +const api = create(apiBase); + +describe('lp_stable: put.mjs', /** @this {MochaSuiteModified} */() => { + it('should successfully put with shouldAutoStake false', async function () { + const usdnAmount = 1e16 / 10; + const usdtAmount = 1e8 / 10; + const expectedLpAmount = 1e12; + const shouldAutoStake = false; + const priceLast = 1e18; + const priceHistory = 1e18; + + const lpStable = address(this.accounts.lpStable, chainId); + + const put = invokeScript({ + dApp: lpStable, + payment: [ + { assetId: this.usdtAssetId, amount: usdtAmount }, + { assetId: this.usdnAssetId, amount: usdnAmount }, + ], + call: { + function: 'put', + args: [ + { type: 'integer', value: 0 }, + { type: 'boolean', value: shouldAutoStake }, + ], + }, + chainId, + }, this.accounts.user1); + await api.transactions.broadcast(put, {}); + const { height, stateChanges, id } = await ni.waitForTx(put.id, { apiBase }); + + const { timestamp } = await api.blocks.fetchHeadersAt(height); + const keyPriceHistory = `%s%s%d%d__price__history__${height}__${timestamp}`; + + expect(stateChanges.data).to.eql([{ + key: '%s%s__price__last', + type: 'integer', + value: priceLast.toString(), + }, { + key: keyPriceHistory, + type: 'integer', + value: priceHistory.toString(), + }, { + key: `%s%s%s__P__${address(this.accounts.user1, chainId)}__${id}`, + type: 'string', + value: `%d%d%d%d%d%d%d%d%d%d__${usdtAmount}__${usdnAmount}__${expectedLpAmount}__${priceLast}__0__0__${height}__${timestamp}__0__0`, + }]); + + expect(stateChanges.transfers).to.eql([{ + address: address(this.accounts.user1, chainId), + asset: this.lpStableAssetId, + amount: expectedLpAmount, + }]); + + expect(stateChanges.invokes.map((item) => [item.dApp, item.call.function])) + .to.deep.include.members([ + [address(this.accounts.factoryV2, chainId), 'emit'], + ]); + }); +}); diff --git a/test/lp_stable/new_pools_with_different_decimals/putOneTkn.mjs b/test/lp_stable/new_pools_with_different_decimals/putOneTkn.mjs new file mode 100644 index 000000000..5cf89731e --- /dev/null +++ b/test/lp_stable/new_pools_with_different_decimals/putOneTkn.mjs @@ -0,0 +1,85 @@ +import chai from 'chai'; +import chaiAsPromised from 'chai-as-promised'; +import { address } from '@waves/ts-lib-crypto'; +import { invokeScript, nodeInteraction as ni } from '@waves/waves-transactions'; +import { create } from '@waves/node-api-js'; + +chai.use(chaiAsPromised); +const { expect } = chai; + +const apiBase = process.env.API_NODE_URL; +const chainId = 'R'; + +const api = create(apiBase); + +describe('lp_stable: putOneTkn.mjs', /** @this {MochaSuiteModified} */() => { + it('should successfully putOneTkn with autoStake false', async function () { + const amAssetPart = 1e10; + const prAssetPart = 1e10; + const outLp = 1e11; + const slippage = 1e3; + const autoStake = false; + const usdtAmount = 1e10; + + const expectedPriceLast = 1e10; + const expectedPriceHistory = 1e10; + const expectedWriteAmAmt = 1e10; + const expectedWritePrAmt = 0; + const expectedEmitLpAmt = 1e11; + const expectedslippageCalc = 0; + const expectedAmDiff = 0; + const expectedPrDiff = 0; + + const lpStable = address(this.accounts.lpStable, chainId); + + const putOneTkn = invokeScript({ + dApp: lpStable, + payment: [ + { assetId: this.usdtAssetId, amount: usdtAmount }, + ], + call: { + function: 'putOneTkn', + args: [ + { type: 'integer', value: amAssetPart }, + { type: 'integer', value: prAssetPart }, + { type: 'integer', value: outLp }, + { type: 'integer', value: slippage }, + { type: 'boolean', value: autoStake }, + ], + }, + chainId, + }, this.accounts.user1); + await api.transactions.broadcast(putOneTkn, {}); + const { height, stateChanges, id } = await ni.waitForTx(putOneTkn.id, { apiBase }); + + const { timestamp } = await api.blocks.fetchHeadersAt(height); + const keyPriceHistory = `%s%s%d%d__price__history__${height}__${timestamp}`; + + expect(stateChanges.data).to.eql([{ + key: '%s%s__price__last', + type: 'integer', + value: expectedPriceLast, + }, { + key: keyPriceHistory, + type: 'integer', + value: expectedPriceHistory, + }, { + key: `%s%s%s__P__${address(this.accounts.user1, chainId)}__${id}`, + type: 'string', + value: `%d%d%d%d%d%d%d%d%d%d__${expectedWriteAmAmt}__${expectedWritePrAmt}__${expectedEmitLpAmt}__${expectedPriceLast}__${slippage}__${expectedslippageCalc}__${height}__${timestamp}__${expectedAmDiff}__${expectedPrDiff}`, + }]); + + expect(stateChanges.transfers).to.eql([{ + address: address(this.accounts.user1, chainId), + asset: this.lpStableAssetId, + amount: outLp, + }]); + + expect(stateChanges.invokes.map((item) => [item.dApp, item.call.function])) + .to.deep.include.members([ + [address(this.accounts.gwxReward, chainId), 'calcD'], + [address(this.accounts.gwxReward, chainId), 'calcD'], + [address(this.accounts.factoryV2, chainId), 'emit'], + ]); + }); +}); diff --git a/test/lp_stable/putOneTkn.mjs b/test/lp_stable/putOneTkn.mjs index e82ae47cb..633b7827d 100644 --- a/test/lp_stable/putOneTkn.mjs +++ b/test/lp_stable/putOneTkn.mjs @@ -77,7 +77,6 @@ describe('lp_stable: putOneTkn.mjs', /** @this {MochaSuiteModified} */() => { expect(stateChanges.invokes.map((item) => [item.dApp, item.call.function])) .to.deep.include.members([ - [address(this.accounts.lpStableAddon, chainId), 'ensureCanPutOneTkn'], [address(this.accounts.gwxReward, chainId), 'calcD'], [address(this.accounts.gwxReward, chainId), 'calcD'], [address(this.accounts.factoryV2, chainId), 'emit'], diff --git a/test/lp_stable/putOneTknAutoStake.mjs b/test/lp_stable/putOneTknAutoStake.mjs index 8130603c3..975c70498 100644 --- a/test/lp_stable/putOneTknAutoStake.mjs +++ b/test/lp_stable/putOneTknAutoStake.mjs @@ -71,7 +71,6 @@ describe('lp_stable: putOneTkn.mjs', /** @this {MochaSuiteModified} */() => { expect(stateChanges.invokes.map((item) => [item.dApp, item.call.function])) .to.deep.include.members([ - [address(this.accounts.lpStableAddon, chainId), 'ensureCanPutOneTkn'], [address(this.accounts.gwxReward, chainId), 'calcD'], [address(this.accounts.gwxReward, chainId), 'calcD'], [address(this.accounts.factoryV2, chainId), 'emit'], diff --git a/test/lp_stable/putRejectSlippageTolerance.mjs b/test/lp_stable/putRejectSlippageTolerance.mjs index f22c58857..79d82bd51 100644 --- a/test/lp_stable/putRejectSlippageTolerance.mjs +++ b/test/lp_stable/putRejectSlippageTolerance.mjs @@ -35,7 +35,7 @@ describe('lpStable: putRejectSlippageTolerance.mjs', /** @this {MochaSuiteModifi }, this.accounts.user1); // await api.transactions.broadcast(put, {}); await expect(api.transactions.broadcast(put, {})).to.be.rejectedWith( - /^Error while executing account-script: Wrong slippage$/, + /^Error while executing account-script: lp_stable.ride: wrong slippage$/, ); }); }); diff --git a/test/lp_stable/unstakeAndGetRejectIsGlobalShutdown.mjs b/test/lp_stable/unstakeAndGetRejectIsGlobalShutdown.mjs index 163970aa9..69530d0d5 100644 --- a/test/lp_stable/unstakeAndGetRejectIsGlobalShutdown.mjs +++ b/test/lp_stable/unstakeAndGetRejectIsGlobalShutdown.mjs @@ -65,7 +65,7 @@ describe('lpStable: unstakeAndGetRejectIsGlobalShutdown.mjs', /** @this {MochaSu }, this.accounts.user1); await expect(api.transactions.broadcast(unstakeAndGet, {})).to.be.rejectedWith( - /^Error while executing account-script: Blocked: 1$/, + /^Error while executing account-script: lp_stable.ride: Blocked: 1$/, ); }, ); diff --git a/test/lp_stable/unstakeAndGetRejectNoPayments.mjs b/test/lp_stable/unstakeAndGetRejectNoPayments.mjs index bb8c88afc..d62dd2589 100644 --- a/test/lp_stable/unstakeAndGetRejectNoPayments.mjs +++ b/test/lp_stable/unstakeAndGetRejectNoPayments.mjs @@ -55,7 +55,7 @@ describe('lpStable: unstakeAndGetRejectNoPayments.mjs', /** @this {MochaSuiteMod }, this.accounts.user1); await expect(api.transactions.broadcast(unstakeAndGet, {})).to.be.rejectedWith( - /^Error while executing account-script: No pmnts expd$/, + /^Error while executing account-script: lp_stable.ride: no payments expected$/, ); }, ); diff --git a/test/lp_stable/unstakeAndGetRejectPoolShutdown.mjs b/test/lp_stable/unstakeAndGetRejectPoolShutdown.mjs index 83b967e65..e9c73e726 100644 --- a/test/lp_stable/unstakeAndGetRejectPoolShutdown.mjs +++ b/test/lp_stable/unstakeAndGetRejectPoolShutdown.mjs @@ -66,7 +66,7 @@ describe('lpStable: unstakeAndGetRejectPoolShutdown.mjs', /** @this {MochaSuiteM }, this.accounts.user1); await expect(api.transactions.broadcast(unstakeAndGet, {})).to.be.rejectedWith( - /^Error while executing account-script: Blocked: 4$/, + /^Error while executing account-script: lp_stable.ride: Blocked: 4$/, ); }, );