-
Notifications
You must be signed in to change notification settings - Fork 12
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #195 from hadronlabs-org/fix/revert-initia-rewards…
…-handler Revert Initia rewards handler
- Loading branch information
Showing
4 changed files
with
270 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
[package] | ||
name = "liquidity-provider" | ||
version = "1.0.0" | ||
authors = [] | ||
|
||
|
||
[addresses] | ||
# Do not change | ||
initia_std = "0x1" | ||
std = "0x1" | ||
|
||
# Address of uinit:uusdc pool | ||
pair = "0xdbf06c48af3984ec6d9ae8a9aa7dbb0bb1e784aa9b8c4a5681af660cf8558d7d" | ||
|
||
# Address of uinit token | ||
asset = "0x8e4733bdabcf7d4afc3d14f0dd46c9bf52fb0fce9e4b996c939e195b8bc891d9" | ||
|
||
# Contract owner | ||
me = "_" | ||
|
||
# Receives LP tokens | ||
recipient = "_" | ||
|
||
|
||
[dependencies] | ||
InitiaStdlib = { git = "https://github.com/initia-labs/movevm", subdir = "precompile/modules/initia_stdlib", rev = "main" } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
# Initia MoveVM "provide liquidity and send LP" module | ||
|
||
#### 1. Prepare an empty address with INIT tokens | ||
|
||
You might need to use a [faucet](https://faucet.testnet.initia.xyz/) for that. | ||
This way, you should have: | ||
|
||
- initiad binary; | ||
- mnemonic with some INIT tokens on initiation-2 network. | ||
|
||
#### 2. Configure deployment | ||
|
||
Navigate to `Move.toml` and open it in your editor of choice. You are interested in the | ||
section `[addresses]`. `me` and `recipient` are filled with placeholder (`_`) addresses, | ||
so you will have to fill them up. You can use this easy snippet to generate hexadecimal | ||
addresses from keys stored in your initiad keychain: | ||
|
||
```bash | ||
NAME="<name of your key>"; echo "0x$(initiad keys parse "$(initiad keys show "$NAME" --output json | jq -r '.address')" --output json | jq -r '.bytes' | tr '[:upper:]' '[:lower:]')" | ||
``` | ||
|
||
First, fill `me` to the address of your own account. For `recipient`, you can create a new | ||
empty account and use it's address. | ||
|
||
#### 3. Build module | ||
|
||
It is as easy as `initiad move build`. | ||
|
||
#### 4. Deploy module | ||
|
||
It is also easy, sign your normal Cosmos SDK transaction: | ||
|
||
```bash | ||
initiad move deploy --path "$(pwd)" --upgrade-policy COMPATIBLE --from <name of your key> --gas auto --gas-adjustment 1.5 --gas-prices 0.025uinit --node https://rpc.initiation-2.initia.xyz:443 --chain-id initiation-2 | ||
``` | ||
|
||
#### 5. Determine address of module object | ||
|
||
Open module upload transaction in block explorer, for example take a look at | ||
[this one](https://scan.testnet.initia.xyz/initiation-1/txs/7B408B00337E840D0AF2BB89615CEEFFD73A28458D1BD31185418909FAF37BDB). | ||
Look for event log with `type_tag` equal to `0x1::object::CreateEvent`. | ||
Inside this event there is a JSON, containing a field `object` with the | ||
address of our new module object. This address is where INIT tokens are expected | ||
to be deposited to. | ||
|
||
#### 6. Send INIT tokens to the module object | ||
|
||
Should be as easy as a normal Cosmos SDK bank transfer, with a single caveat. | ||
You have object address in HEX format, but you need bech32. Let's convert it: | ||
suppose you have address `0x8a6fc188562db0e6008896b4e7a5ec027fe3461cb4169adc5165d1b58732d720`. | ||
Run `initiad keys parse 8a6fc188562db0e6008896b4e7a5ec027fe3461cb4169adc5165d1b58732d720`, | ||
take the first output with `init1` prefix: `init13fhurzzk9kcwvqygj66w0f0vqfl7x3sukstf4hz3vhgmtpej6usqzqa0mq`, | ||
this would be the address to send funds to: | ||
|
||
```bash | ||
initiad tx bank send <name of your key> init13fhurzzk9kcwvqygj66w0f0vqfl7x3sukstf4hz3vhgmtpej6usqzqa0mq 4242uinit --gas auto --gas-adjustment 1.5 --gas-prices 0.025uinit --chain-id initiation-2 --node https://rpc.initiation-2.initia.xyz:443 | ||
``` | ||
|
||
#### 7. Execute contract | ||
|
||
```bash | ||
initiad tx move execute <address of @me from Move.toml> liquidity_provider provide --from testnet --gas auto --gas-adjustment 1.5 --gas-prices 0.025uinit --node https://rpc.initiation-2.initia.xyz:443 --chain-id initiation-2 | ||
``` | ||
|
||
#### 8. Validate | ||
|
||
Use block explorer to validate that: | ||
|
||
- @me address doesn't have any INIT tokens anymore; | ||
- @recipient address has some LP tokens (denom is | ||
`move/dbf06c48af3984ec6d9ae8a9aa7dbb0bb1e784aa9b8c4a5681af660cf8558d7d`). |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
#!/usr/bin/env bash | ||
|
||
# Fetch all DEX pairs on initiation-2 and print their info | ||
|
||
set -euo pipefail | ||
IFS=$'\n\t' | ||
|
||
declare -a q=( | ||
"--output" "json" | ||
"--node" "https://rpc.initiation-2.initia.xyz:443" | ||
) | ||
|
||
all="$(initiad query move view 0x1 dex get_all_pairs --args '["option<address>:null", "option<address>:null", "option<address>:null", "u8:255"]' "${q[@]}" | jq -r '.data')" | ||
for pair in $(echo "$all" | jq -rc '.[]'); do | ||
lp="$(echo "$pair" | jq -r '.liquidity_token')" | ||
coin_a="$(echo "$pair" | jq -r '.coin_a')" | ||
coin_b="$(echo "$pair" | jq -r '.coin_b')" | ||
metadata="$(initiad query move resource "$lp" 0x1::fungible_asset::Metadata "${q[@]}")" | ||
name="$(echo "$metadata" | jq -r '.resource.move_resource' | jq '.data.name')" | ||
symbol="$(echo "$metadata" | jq -r '.resource.move_resource' | jq '.data.symbol')" | ||
supply="$(initiad query move resource "$lp" 0x1::fungible_asset::Supply "${q[@]}")" | ||
supply="$(echo "$supply" | jq -r '.resource.move_resource' | jq '.data.current')" | ||
echo "LP: $lp, name: $name, symbol: $symbol, supply: $supply, coin_a: $coin_a, coin_b: $coin_b" | ||
done |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,149 @@ | ||
module me::liquidity_provider { | ||
use initia_std::dex; | ||
use initia_std::object::{Self, Object, ExtendRef}; | ||
use initia_std::coin; | ||
use initia_std::json; | ||
use initia_std::cosmos; | ||
use initia_std::oracle; | ||
use initia_std::bigdecimal; | ||
use initia_std::math128; | ||
use initia_std::block; | ||
use initia_std::fungible_asset::Metadata; | ||
use std::option; | ||
use std::signer; | ||
use std::address; | ||
use std::string::{Self, String}; | ||
use std::error; | ||
|
||
const ENOT_OWNER: u64 = 1; | ||
|
||
struct ModuleStore has key { | ||
extend_ref: ExtendRef, | ||
price: u256, | ||
ts: u64, | ||
decimals: u64, | ||
} | ||
|
||
struct MsgExecuteJSON has drop { | ||
_type_: String, | ||
sender: String, | ||
module_address: String, | ||
module_name: String, | ||
function_name: String, | ||
type_args: vector<String>, | ||
args: vector<String>, | ||
} | ||
|
||
fun init_module(creator: &signer) { | ||
let constructor_ref = object::create_object(@me, false); | ||
let extend_ref = object::generate_extend_ref(&constructor_ref); | ||
move_to(creator, ModuleStore { extend_ref, price: 0, ts: 0, decimals: 0, }); | ||
} | ||
|
||
// emits stargate message which: | ||
// 1. calls @me::liquidity_provider::store() | ||
// 2.1. then calls @me::liquidity_provider(1, false) if store() fails | ||
// 2.2. then calls @me::liquidity_provider(1, true) if store() succeeds | ||
public entry fun provide() acquires ModuleStore { | ||
let store = borrow_global<ModuleStore>(@me); | ||
let signer = object::generate_signer_for_extending(&store.extend_ref); | ||
let addr = signer::address_of(&signer); | ||
|
||
let msg = MsgExecuteJSON { | ||
_type_: string::utf8(b"/initia.move.v1.MsgExecuteJSON"), | ||
sender: address::to_sdk(addr), | ||
module_address: address::to_sdk(@me), | ||
module_name: string::utf8(b"liquidity_provider"), | ||
function_name: string::utf8(b"store"), | ||
type_args: vector[], | ||
args: vector[], | ||
}; | ||
|
||
let fid = address::to_string(@me); | ||
string::append(&mut fid, string::utf8(b"::liquidity_provider::callback")); | ||
|
||
cosmos::stargate_with_options( | ||
&signer, | ||
json::marshal(&msg), | ||
cosmos::allow_failure_with_callback(1, fid), | ||
); | ||
} | ||
|
||
// Last resort function only available for module admin to withdraw all funds of `coin` denomination | ||
// from module's object address in case if there is an unrecoverable bug somewhere | ||
public entry fun backup(account: &signer, coin: Object<Metadata>) acquires ModuleStore { | ||
assert!( | ||
signer::address_of(account) == @me, | ||
error::permission_denied(ENOT_OWNER), | ||
); | ||
|
||
let store = borrow_global<ModuleStore>(@me); | ||
let ref_signer = object::generate_signer_for_extending(&store.extend_ref); | ||
let balance = coin::balance(signer::address_of(&ref_signer), coin); | ||
coin::transfer(&ref_signer, signer::address_of(account), coin, balance); | ||
} | ||
|
||
// 1. provide liquidity | ||
// 2. sweep all received LP tokens to @recipient | ||
fun provide_liquidity(account: &signer) { | ||
let addr = signer::address_of(account); | ||
|
||
let metadata_in = object::address_to_object(@asset); | ||
let amount_in = coin::balance(addr, metadata_in); | ||
dex::single_asset_provide_liquidity_script( | ||
account, | ||
object::address_to_object(@pair), | ||
metadata_in, | ||
amount_in, | ||
option::none(), | ||
); | ||
|
||
let metadata_out = object::address_to_object(@pair); | ||
let amount_out = coin::balance(addr, metadata_out); | ||
coin::transfer(account, @recipient, metadata_out, amount_out); | ||
} | ||
|
||
// Read INIT price from slinky | ||
entry fun store() acquires ModuleStore { | ||
let (price, ts, decimals) = oracle::get_price(string::utf8(b"INIT/USD")); | ||
let store = borrow_global_mut<ModuleStore>(@me); | ||
store.price = price; | ||
store.ts = ts; | ||
store.decimals = decimals; | ||
} | ||
|
||
// MEV protection, only provide liquidity if pool price is up to date with off-chain price feed | ||
entry fun callback(_id: u64, success: bool) acquires ModuleStore { | ||
let store = borrow_global<ModuleStore>(@me); | ||
let signer = object::generate_signer_for_extending(&store.extend_ref); | ||
|
||
if (success) { | ||
let slinky_price = bigdecimal::from_ratio_u256( | ||
store.price, | ||
(math128::pow(10, (store.decimals as u128)) as u256), | ||
); | ||
let pool_price = dex::get_spot_price( | ||
object::address_to_object(@pair), | ||
object::address_to_object(@asset), | ||
); | ||
let ratio = if (bigdecimal::gt(slinky_price, pool_price)) { | ||
bigdecimal::div(slinky_price, pool_price) | ||
} else { | ||
bigdecimal::div(pool_price, slinky_price) | ||
}; | ||
let block_ts = block::get_current_block_timestamp(); | ||
if ( | ||
// slinky price is up to date | ||
store.ts == block_ts | ||
&& | ||
// pool price is not more than 1% off | ||
bigdecimal::le(ratio, bigdecimal::from_ratio_u256(101, 1)) | ||
) { | ||
provide_liquidity(&signer); | ||
} | ||
} else { | ||
// slinky doesn't know about INIT yet, skip any validation and go full YOLO | ||
provide_liquidity(&signer); | ||
} | ||
} | ||
} |