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$/,
);
},
);