Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: sim-bootstrap boilerplate #1031

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
16 changes: 16 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ members = [
"lana/ids",
"lana/dashboard",

"lana/sim-bootstrap",

"core/user",
"core/governance",
"core/money",
Expand Down
3 changes: 3 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@ reset-deps: reset-tf-state clean-deps start-deps setup-db run-tf
run-server:
cargo run --bin lana-cli --features sim-time -- --config ./bats/lana-sim-time.yml | tee .e2e-logs

run-server-with-bootstrap:
cargo run --bin lana-cli --all-features -- --config ./bats/lana-sim-time.yml | tee .e2e-logs

check-code: sdl
git diff --exit-code lana/admin-server/src/graphql/schema.graphql
SQLX_OFFLINE=true cargo fmt --check --all
Expand Down
3 changes: 2 additions & 1 deletion bats/lana-sim-time.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ app:
time:
realtime: false
sim_time:
start_at: "2021-01-01T00:00:00Z"
start_at: "2024-01-01T00:00:00Z"
tick_interval_ms: 1
tick_duration_secs: 1000
transform_to_realtime: false
Empty file added bootstrap.yml
Empty file.
8 changes: 6 additions & 2 deletions lana/app/src/app/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ pub struct LanaApp {
report: Reports,
terms_templates: TermsTemplates,
documents: Documents,
_outbox: Outbox,
outbox: Outbox,
governance: Governance,
dashboard: Dashboard,
}
Expand Down Expand Up @@ -117,12 +117,16 @@ impl LanaApp {
credit_facilities,
terms_templates,
documents,
_outbox: outbox,
outbox,
governance,
dashboard,
})
}

pub fn outbox(&self) -> &Outbox {
&self.outbox
}

pub fn dashboard(&self) -> &Dashboard {
&self.dashboard
}
Expand Down
4 changes: 4 additions & 0 deletions lana/app/src/credit_facility/jobs/cvl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use crate::{
credit_facility::{repo::*, CreditFacilitiesByCollateralizationRatioCursor},
job::*,
price::Price,
primitives::CreditFacilityStatus,
terms::CVLPct,
};

Expand Down Expand Up @@ -104,6 +105,9 @@ impl JobRunner for CreditFacilityProcessingJobRunner {
let mut at_least_one = false;

for facility in credit_facilities.entities.iter_mut() {
if facility.status() == CreditFacilityStatus::Closed {
continue;
}
if facility
.maybe_update_collateralization(
price,
Expand Down
8 changes: 8 additions & 0 deletions lana/app/src/credit_facility/publisher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,12 +86,20 @@ impl CreditFacilityPublisher {
};

Some(CreditEvent::FacilityCollateralUpdated {
id: entity.id,
new_amount: *total_collateral,
abs_diff: *abs_diff,
action,
recorded_at: *recorded_in_ledger_at,
})
}
InterestAccrualConcluded {
amount, accrued_at, ..
} => Some(CreditEvent::AccrualExecuted {
id: entity.id,
amount: *amount,
accrued_at: *accrued_at,
}),

_ => None,
})
Expand Down
2 changes: 2 additions & 0 deletions lana/cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@ edition = "2021"

[features]
sim-time = ["dep:sim-time"]
sim-bootstrap = ["dep:sim-bootstrap"]

fail-on-warnings = []

[dependencies]
lana-app = { path = "../app" }
admin-server = { path = "../admin-server" }
sim-bootstrap = { path = "../sim-bootstrap", optional = true }

tracing-utils = { path = "../../lib/tracing-utils", features = ["http"] }
sim-time = { path = "../../lib/sim-time", optional = true }
Expand Down
8 changes: 8 additions & 0 deletions lana/cli/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,16 @@ async fn run_cmd(lana_home: &str, config: Config) -> anyhow::Result<()> {
let (send, mut receive) = tokio::sync::mpsc::channel(1);
let mut handles = Vec::new();
let pool = db::init_pool(&config.db).await?;
#[cfg(feature = "sim-bootstrap")]
let superuser_email = &config.app.user.superuser_email.clone().expect("super user");

let admin_app = lana_app::app::LanaApp::run(pool.clone(), config.app).await?;

#[cfg(feature = "sim-bootstrap")]
{
sim_bootstrap::run(superuser_email.to_string(), admin_app.clone()).await?;
}

let admin_send = send.clone();

handles.push(tokio::spawn(async move {
Expand Down
6 changes: 6 additions & 0 deletions lana/events/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,17 @@ pub enum CreditEvent {
recorded_at: DateTime<Utc>,
},
FacilityCollateralUpdated {
id: CreditFacilityId,
new_amount: Satoshis,
abs_diff: Satoshis,
action: FacilityCollateralUpdateAction,
recorded_at: DateTime<Utc>,
},
AccrualExecuted {
id: CreditFacilityId,
amount: UsdCents,
accrued_at: DateTime<Utc>,
},
}

macro_rules! impl_event_marker {
Expand Down
21 changes: 21 additions & 0 deletions lana/sim-bootstrap/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
[package]
name = "sim-bootstrap"
version = "0.1.0"
edition = "2021"

[features]

fail-on-warnings = []

[dependencies]
lana-app = { path = "../app" }
lana-events = { path = "../events" }

sim-time = { path = "../../lib/sim-time" }

anyhow = { workspace = true }
rust_decimal = { workspace = true }
rust_decimal_macros = { workspace = true }
tokio = { workspace = true }
futures = { workspace = true }
async-trait = { workspace = true }
163 changes: 163 additions & 0 deletions lana/sim-bootstrap/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
#![cfg_attr(feature = "fail-on-warnings", deny(warnings))]
#![cfg_attr(feature = "fail-on-warnings", deny(clippy::all))]

use futures::StreamExt;
use rust_decimal_macros::dec;

use lana_app::{
app::LanaApp,
primitives::{CreditFacilityId, Satoshis, Subject, UsdCents},
terms::{Duration, InterestInterval, TermValues},
};
use lana_events::*;

pub async fn run(superuser_email: String, app: LanaApp) -> anyhow::Result<()> {
let sub = superuser_subject(&superuser_email, app.clone()).await?;
initial_setup(&sub, app.clone()).await?;
let id = bootstrap_credit_facility(&sub, app.clone()).await?;

let _handle = tokio::spawn(async move {
let _ = process_repayment(sub, id, app).await;
});
// add accrual events to CreditEvent public events
// spawn tokio task that waits for public events -> reacts by executing a repayment
//
// once that is working genericise to be able to create N credit facilities
// inject config for N credit facilities
//
println!("waiting for real time");
sim_time::wait_until_realtime().await;
println!("done");
Ok(())
}

async fn process_repayment(sub: Subject, id: CreditFacilityId, app: LanaApp) -> anyhow::Result<()> {
let mut stream = app.outbox().listen_persisted(None).await?;

while let Some(msg) = stream.next().await {
match &msg.payload {
Some(LanaEvent::Credit(CreditEvent::AccrualExecuted {
id: cf_id, amount, ..
})) if *cf_id == id && amount > &UsdCents::ZERO => {
let _ = app
.credit_facilities()
.record_payment(&sub, id, *amount)
.await;
let mut cf = app
.credit_facilities()
.find_by_id(&sub, id)
.await?
.expect("cf exists");
if cf.interest_accrual_in_progress().is_none() {
app.credit_facilities()
.record_payment(&sub, id, cf.outstanding().total())
.await?;
app.credit_facilities().complete_facility(&sub, id).await?;
}
}
Some(LanaEvent::Credit(CreditEvent::FacilityCompleted { id: cf_id, .. }))
if *cf_id == id =>
{
break;
}
_ => {}
}
}

Ok(())
}

pub async fn initial_setup(sub: &Subject, app: LanaApp) -> anyhow::Result<()> {
let values = std_terms();
let _ = app
.terms_templates()
.create_terms_template(sub, "bootstrap".to_string(), values)
.await;

let _ = app
.customers()
.create(
sub,
"[email protected]".to_string(),
"bootstrap-telegram".to_string(),
)
.await;
let _ = app
.customers()
.create(
sub,
"[email protected]".to_string(),
"bootstrap-whale".to_string(),
)
.await;
let customer = app
.customers()
.find_by_email(sub, "[email protected]".to_string())
.await?
.expect("Customer not found");

let _ = app
.deposits()
.record(
sub,
customer.id,
UsdCents::try_from_usd(dec!(1_000_000))?,
None,
)
.await?;
Ok(())
}

pub async fn superuser_subject(superuser_email: &String, app: LanaApp) -> anyhow::Result<Subject> {
let superuser = app
.users()
.find_by_email(None, superuser_email)
.await?
.expect("Superuser not found");
Ok(Subject::from(superuser.id))
}

pub async fn bootstrap_credit_facility(
sub: &Subject,
app: LanaApp,
) -> anyhow::Result<CreditFacilityId> {
let customer_email = "[email protected]".to_string();
let customer = app
.customers()
.find_by_email(sub, customer_email)
.await?
.expect("Superuser not found");
let terms = std_terms();
let cf = app
.credit_facilities()
.initiate(
sub,
customer.id,
UsdCents::try_from_usd(dec!(10_000_000))?,
terms,
)
.await?;
let id = cf.id;
tokio::time::sleep(std::time::Duration::from_millis(500)).await;
app.credit_facilities()
.update_collateral(sub, id, Satoshis::try_from_btc(dec!(230))?)
.await?;
tokio::time::sleep(std::time::Duration::from_millis(500)).await;
app.credit_facilities()
.initiate_disbursal(sub, id, UsdCents::try_from_usd(dec!(1_000_000))?)
.await?;
Ok(id)
}

fn std_terms() -> TermValues {
TermValues::builder()
.annual_rate(dec!(12))
.initial_cvl(dec!(140))
.margin_call_cvl(dec!(125))
.liquidation_cvl(dec!(105))
.duration(Duration::Months(3))
.incurrence_interval(InterestInterval::EndOfDay)
.accrual_interval(InterestInterval::EndOfMonth)
.build()
.unwrap()
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading