Skip to content

Commit

Permalink
feat: add recharging cycles impl
Browse files Browse the repository at this point in the history
  • Loading branch information
EmperorOrokuSaki committed Jul 17, 2024
1 parent a6f5405 commit 85c6d6c
Show file tree
Hide file tree
Showing 7 changed files with 316 additions and 75 deletions.
1 change: 0 additions & 1 deletion ir_manager/src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ use std::str::FromStr;
use alloy_primitives::U256;
use ic_exports::ic_kit::ic::time;

use crate::gas::estimate_transaction_fees;
use crate::process::LiquityProcess;
use crate::types::*;
use crate::utils::{lock, unlock};
Expand Down
10 changes: 5 additions & 5 deletions ir_manager/src/canister.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
use crate::{
evm_rpc::Service,
state::*,
types::{DerivationPath, InitArgs, StrategyData, StrategyQueryData},
charger::check_threshold, evm_rpc::Service, state::*, types::{DerivationPath, InitArgs, ManagerError, StrategyData, StrategyQueryData, SwapResponse}, utils::{fetch_cketh_balance, fetch_ether_cycles_rate}
};
use alloy_primitives::U256;
use ic_canister::{generate_idl, init, query, update, Canister, Idl, PreUpdate};
use ic_exports::{candid::Principal, ic_kit::ic::time};
use ic_exports::{candid::Principal, ic_cdk::{api::call::msg_cycles_available, caller}, ic_kit::ic::time};
use std::{collections::HashMap, str::FromStr};

#[derive(Canister)]
Expand Down Expand Up @@ -75,8 +73,10 @@ impl IrManager {
}

#[update]
pub async fn swap_cketh(&self) {
pub async fn swap_cketh(&self) -> Result<SwapResponse, ManagerError> {
// lock / unlock based on the current cycles balance of the canister
check_threshold().await?;
transfer_cketh(caller()).await?
}

pub fn idl() -> Idl {
Expand Down
159 changes: 98 additions & 61 deletions ir_manager/src/charger.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,77 +2,59 @@ use std::{str::FromStr, time::Duration};

use alloy_primitives::{Bytes, FixedBytes, U256};
use alloy_sol_types::SolCall;
use candid::Principal;
use ic_exports::{
candid::Nat,
ic_cdk::{
api::{self, call},
api::{
self,
call::{self, msg_cycles_accept, msg_cycles_available},
canister_balance, canister_balance128,
},
call,
},
ic_cdk_timers::set_timer,
ic_kit::ic::{self, id},
ic_kit::{
ic::{self, id},
CallResult,
},
};
use icrc_ledger_types::icrc1::{
account::Account,
transfer::{TransferArg, TransferError},
};
use icrc_ledger_types::icrc1::account::Account;
use serde_json::json;

use crate::{
evm_rpc::{RpcService, Service},
signer::get_canister_public_key,
state::{
CKETH_HELPER, CKETH_LEDGER, ETHER_RECHARGE_VALUE, RPC_CANISTER, RPC_URL, STRATEGY_DATA,
CKETH_HELPER, CKETH_LEDGER, CKETH_THRESHOLD, CYCLES_THRESHOLD, ETHER_RECHARGE_VALUE,
RPC_CANISTER, RPC_URL, STRATEGY_DATA,
},
types::{depositCall, depositReturn, DerivationPath, ManagerError, StrategyData, SwapResponse},
utils::{
decode_request_response, decode_response, fetch_cketh_balance, fetch_ether_cycles_rate,
rpc_provider, send_raw_transaction,
},
types::{depositCall, depositReturn, DerivationPath, ManagerError, StrategyData},
utils::{decode_request_response, rpc_provider, send_raw_transaction},
};

pub async fn recharge() {
// The canister cycles balance has fallen below threshold

// Deposit ether from one of the EOAs that has enough balance
ether_deposit().await;

// Set a one-off timer for the next 20 minutes (the time cketh takes to load balance on the ic side)
set_timer(Duration::from_secs(1200), || {
ic::spawn(async {
let _ = resume_recharging().await;
})
});
// Burn cketh for cycles and recharge
}

async fn resume_recharging() {
let cketh_ledger = CKETH_LEDGER.with(|ledger| ledger.borrow().clone());
let account = Account {
owner: id(),
subaccount: None,
};

let (balance,): (Nat,) = call(cketh_ledger, "icrc1_balance_of", (account,))
.await
.unwrap();

// Todo: check if the balance matches the deposit value minus fee
pub async fn check_threshold() -> Result<(), ManagerError> {
let threshold = CYCLES_THRESHOLD.get();
if canister_balance() <= threshold {
return Ok(());
}
Err(ManagerError::CyclesBalanceAboveRechargingThreshold)
}

async fn fetch_balance(rpc_canister: &Service, rpc_url: &str, pk: String) -> U256 {
let rpc: RpcService = rpc_provider(rpc_url);
let json_args = json!({
"id": 1,
"jsonrpc": "2.0",
"params": [
pk,
"latest"
],
"method": "eth_getBalance"
})
.to_string();
let request_response = rpc_canister.request(rpc, json_args, 50000, 10000000).await;

let decoded_hex = decode_request_response(request_response).unwrap();
let mut padded = [0u8; 32];
let start = 32 - decoded_hex.len();
padded[start..].copy_from_slice(&decoded_hex);

U256::from_be_bytes(padded)
pub async fn recharge_cketh() -> Result<(), ManagerError> {
let current_balance = fetch_cketh_balance().await?;
let cketh_threshold = CKETH_THRESHOLD.with(|threshold| threshold.borrow().clone());
if current_balance < cketh_threshold {
// Deposit ether from one of the EOAs that has enough balance
return ether_deposit().await;
}
Ok(())
}

async fn ether_deposit() -> Result<(), ManagerError> {
Expand Down Expand Up @@ -103,7 +85,7 @@ async fn ether_deposit() -> Result<(), ManagerError> {
let transaction_data = deposit_call.abi_encode();

// todo: fetch the cycles with estimation
let submission_result = send_raw_transaction(
send_raw_transaction(
cketh_helper,
transaction_data,
ether_value,
Expand All @@ -113,13 +95,68 @@ async fn ether_deposit() -> Result<(), ManagerError> {
&rpc_url,
100000000,
)
.await;
.await
.map(|_| Ok(()))
.unwrap_or_else(|e| Err(e))
}

let rpc_canister_response = rpc_canister
.request(rpc, json_data, 500000, 10_000_000_000)
.await;
async fn fetch_balance(rpc_canister: &Service, rpc_url: &str, pk: String) -> U256 {
let rpc: RpcService = rpc_provider(rpc_url);
let json_args = json!({
"id": 1,
"jsonrpc": "2.0",
"params": [
pk,
"latest"
],
"method": "eth_getBalance"
})
.to_string();
let request_response = rpc_canister.request(rpc, json_args, 50000, 10000000).await;

let decoded_hex = decode_request_response(request_response).unwrap();
let mut padded = [0u8; 32];
let start = 32 - decoded_hex.len();
padded[start..].copy_from_slice(&decoded_hex);

U256::from_be_bytes(padded)
}

decode_response::<depositReturn, depositCall>(rpc_canister_response)
.map(|data| Ok(data))
.unwrap_or_else(|e| Err(e))
pub async fn transfer_cketh(receiver: Principal) -> Result<SwapResponse, ManagerError> {
// todo: account for the fee
let rate = fetch_ether_cycles_rate().await?;
let attached_cycles = msg_cycles_available();
let maximum_returned_ether_amount = attached_cycles * rate;

// first check if the balance permits the max transfer amount
let balance = fetch_cketh_balance().await?;
// second calculate the amount to transfer and accept cycles first
let (transfer_amount, cycles_to_accept) = if balance > maximum_returned_ether_amount {
(maximum_returned_ether_amount, attached_cycles)
} else {
(balance, balance / rate)
};
msg_cycles_accept(cycles_to_accept);
// third send the cketh to the user
let ledger_principal = CKETH_LEDGER.with(|ledger| ledger.borrow().clone());

let args = TransferArg {
from_subaccount: None,
to: receiver.into(),
fee: todo!(),
created_at_time: None,
memo: None,
amount: transfer_amount,
};

let call_response: CallResult<(Result<Nat, TransferError>,)> =
call(ledger_principal, "icrc1_transfer", (args,)).await;

match call_response {
Ok(response) => Ok(SwapResponse {
accepted_cycles: cycles_to_accept,
returning_ether: transfer_amount,
}),
Err(err) => Err(ManagerError::Custom(err.1)),
}
}
5 changes: 4 additions & 1 deletion ir_manager/src/state.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
use std::{
cell::{Cell, RefCell},
collections::HashMap,
collections::HashMap, str::FromStr,
};

use alloy_primitives::U256;
use candid::Nat;
use ic_exports::candid::Principal;

use crate::{evm_rpc::Service, types::StrategyData};
Expand All @@ -22,5 +23,7 @@ thread_local! {
pub static CKETH_HELPER: RefCell<String> = RefCell::new("0x7574eB42cA208A4f6960ECCAfDF186D627dCC175".to_string());
pub static CKETH_LEDGER: RefCell<Principal> = RefCell::new(Principal::from_text("ss2fx-dyaaa-aaaar-qacoq-cai").unwrap());
pub static ETHER_RECHARGE_VALUE: RefCell<U256> = RefCell::new(U256::from(30000000000000000)); // 0.03 ETH in WEI
pub static CKETH_THRESHOLD: RefCell<Nat> = RefCell::new(Nat::from_str("30000000000000000").unwrap()); // 0.03 ETH in WEI
pub static MAX_RETRY_ATTEMPTS: Cell<u64> = Cell::new(3);
pub static EXCHANGE_RATE_CANISTER: RefCell<Principal> = RefCell::new(Principal::from_text("uf6dk-hyaaa-aaaaq-qaaaq-cai").unwrap());
}
22 changes: 18 additions & 4 deletions ir_manager/src/timers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,7 @@ use ic_exports::{
};

use crate::{
api::execute_strategy,
state::{MAX_RETRY_ATTEMPTS, STRATEGY_DATA},
types::StrategyData,
utils::{retry, set_public_keys},
api::execute_strategy, charger::recharge_cketh, state::{MAX_RETRY_ATTEMPTS, STRATEGY_DATA}, types::StrategyData, utils::{retry, set_public_keys}
};

pub fn start_timers() {
Expand All @@ -24,6 +21,7 @@ pub fn start_timers() {

let max_retry_attempts = MAX_RETRY_ATTEMPTS.with(|max_value| max_value.get());

// STRATEGY TIMER | EVERY 1 HOUR
for (key, strategy) in strategies {
set_timer_interval(Duration::from_secs(3600), move || {
spawn(async {
Expand All @@ -40,4 +38,20 @@ pub fn start_timers() {
});
});
}

// CKETH RECHARGER | EVERY 24 HOURS
set_timer_interval(Duration::from_secs(86_400), move || {
spawn(async {
for _ in 0..=max_retry_attempts {
let result = match recharge_cketh().await {
Ok(()) => Ok(()),
Err(error) => recharge_cketh().await,
};

if result.is_ok() {
break;
}
}
});
});
}
Loading

0 comments on commit 85c6d6c

Please sign in to comment.