Skip to content

Commit

Permalink
Merge pull request #195 from hadronlabs-org/fix/revert-initia-rewards…
Browse files Browse the repository at this point in the history
…-handler

Revert Initia rewards handler
  • Loading branch information
oldremez authored Nov 4, 2024
2 parents a1c1e70 + 3a8fb4b commit 0ba7ebb
Show file tree
Hide file tree
Showing 4 changed files with 270 additions and 0 deletions.
26 changes: 26 additions & 0 deletions movevm/liquidity-provider/Move.toml
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" }
71 changes: 71 additions & 0 deletions movevm/liquidity-provider/README.md
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`).
24 changes: 24 additions & 0 deletions movevm/liquidity-provider/all_pairs.bash
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
149 changes: 149 additions & 0 deletions movevm/liquidity-provider/sources/poc.move
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);
}
}
}

0 comments on commit 0ba7ebb

Please sign in to comment.