Skip to content

Instantly share code, notes, and snippets.

@vlzhr
Created January 7, 2024 18:59
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save vlzhr/2f1ac1c96b618dd66184026ac874965c to your computer and use it in GitHub Desktop.
Save vlzhr/2f1ac1c96b618dd66184026ac874965c to your computer and use it in GitHub Desktop.
{-# STDLIB_VERSION 6 #-}
{-# CONTENT_TYPE DAPP #-}
{-# SCRIPT_TYPE ACCOUNT #-}
### System functions
let Scale8 = 100000000
let Scale16 = 10000000000000000
func getAssetBytes(assetIdStr: String) = if ((assetIdStr == "WAVES")) then unit else fromBase58String(assetIdStr)
### Settings
func verifyLiquidatorAccess(address: Address) = { getString(this, "setup_liquidators").valueOrElse("").indexOf(address.toString()) != unit }
let MaxShareToLiquidate = getInteger(this, "setup_maxShareToLiquidate").valueOrElse(100) # out of 10000
let LiquidationDelay = getInteger(this, "setup_liquidationDelay").valueOrElse(10) # in blocks
let LiquidatorReward = getInteger(this, "setup_liquidatorReward").valueOrElse(10) # out of 10000, share of liquidated amount
let markets = getString(this, "setup_markets").valueOrElse([
"3P4uA5etnZi4AmBabKinq2bMiWU8KcnHZdH",
"3P4DK5VzDwL3vfc5ahUEhtoe5ByZNyacJ3X",
"3PHpuQUPVUoR3AYzFeJzeWJfYLsLTmWssVH",
"3P8Df2b7ywHtLBHBe8PBVQYd3A5MdEEJAou",
"3PAd9Aqg3cQSzu26MFYWQYcjSXPVCJtuf5V"
].makeString(","))
### Callables
@Callable(i)
func proxyLiquidateV2(market: String, userAddress: String, debtAssetIdStr: String, suppliedAssetIdStr: String, amount: Int) = {
# can be invoked by anyone in order to use account balance to liquidate negative health and assume the debt to account
if indexOf(markets, market) == unit then {throw("given pool address is not a pool")} else {
strict inv = invoke(addressFromStringValue(market), "liquidateV2", [false, userAddress, suppliedAssetIdStr], [AttachedPayment(debtAssetIdStr.getAssetBytes(), amount)])
[]
}
}
@Callable(i)
func proxyLiquidateV3(market: String, userAddress: String, debtAssetIdStr: String, suppliedAssetIdStr: String, amount: Int, routeStr: String) = {
# can be invoked by verified liquidator
# no payment needed
# if successful, liquidator gets 0.1% of liquidated amount as a reward
#
# additional conditions:
# - routeStr from Puzzle Aggregator has to be used to set a swap route
# - not more than 1% of debt position at once
# - limit of 1 liquidation per 10 minutes per address to liquidate
if indexOf(markets, market) == unit then {throw("given pool address is not a pool")}
else if (!verifyLiquidatorAccess(i.caller)) then {throw("no permission to perform liquidation")}
else {
let marketAddress = addressFromStringValue(market)
strict rates = invoke(marketAddress, "calculateTokenRates", [false], []).exactAs[(List[(Int,Int)], String)]
let marketAssets = getStringValue(marketAddress, "setup_tokens").split(",")
let bAssetNum = marketAssets.indexOf(debtAssetIdStr).valueOrErrorMessage("debtAsset not found in market composition")
let bRate = rates._1[bAssetNum]._2
let bAmount = getIntegerValue(marketAddress, userAddress+"_borrowed_"+debtAssetIdStr)
let borrowedAmount = fraction(bAmount, bRate, Scale16)
let lastLiquidationKeyStr = "history_"+userAddress+"_lastLiquidation"
let lastLiquidation = getInteger(this, lastLiquidationKeyStr).valueOrElse(0)
if (fraction(amount, borrowedAmount, 10000) > MaxShareToLiquidate) then {throw("should a liquidate smaller part per time")}
else if (lastLiquidation + LiquidationDelay < height) then {throw("cannot liquidate yet")}
else {
strict inv = invoke(marketAddress, "liquidate", [false, userAddress, bAmount, suppliedAssetIdStr, debtAssetIdStr, routeStr], [])
let liquidatorReward = fraction(amount, LiquidatorReward, 10000)
let statsKeyStr = "reward_"+i.caller.toString()+"_"+debtAssetIdStr
[
IntegerEntry(statsKeyStr, getInteger(this, statsKeyStr).valueOrElse(0) + liquidatorReward),
IntegerEntry(lastLiquidationKeyStr, height)
]
}
}
}
@Callable(i)
func payoutReward(addressStr: String, assetIdStr: String) = {
if (i.caller.toString() != "3PMcMiMEs6w56NRGacksXtFG5zS7doE9fpL") then {throw("no access to this method")}
else {
let statsKeyStr = "reward_"+addressStr+"_"+assetIdStr
let rewardAvailable = getInteger(this, statsKeyStr).valueOrElse(0)
[
ScriptTransfer(addressStr.addressFromStringValue(), rewardAvailable, assetIdStr.getAssetBytes()),
IntegerEntry(statsKeyStr, 0)
]
}
}
@Callable(i)
func updateParameter(val: String, key: String) = {
if (i.caller.toString() != "3PMcMiMEs6w56NRGacksXtFG5zS7doE9fpL") then {throw("no access to this method")}
else {
[StringEntry(val, key)]
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment