Skip to content

Commit

Permalink
[FIL-227] Automatic allocator (#225)
Browse files Browse the repository at this point in the history
---------

Co-authored-by: Filip Lelek <[email protected]>
  • Loading branch information
Filip-L and Filip Lelek authored Sep 19, 2024
1 parent f3a74f1 commit d7ac445
Show file tree
Hide file tree
Showing 19 changed files with 1,186 additions and 841 deletions.
1,618 changes: 814 additions & 804 deletions Cargo.lock

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM rust:1.76 AS builder
FROM rust:1.81 AS builder
COPY ./fplus-lib /fplus-lib
COPY ./fplus-http-server/Cargo.toml /fplus-http-server/Cargo.toml
COPY ./Cargo.lock /fplus-http-server/Cargo.lock
Expand Down
2 changes: 2 additions & 0 deletions fplus-database/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,6 @@ serde = { version = "1.0.164", features = ["derive", "std",
serial_test = "3.0.0"
sha1 = "0.10.6"
serde_json = "1.0.96"
alloy = { version = "0.3.2", features = ["signers"] }
sea-orm-newtype = "0.0.1"
urlencoding = "2.1.3"
11 changes: 11 additions & 0 deletions fplus-database/src/database/applications.rs
Original file line number Diff line number Diff line change
Expand Up @@ -446,3 +446,14 @@ pub async fn delete_application(
application.delete(&conn).await?;
Ok(())
}

pub async fn get_applications_by_client_id(
id: &String,
) -> Result<Vec<ApplicationModel>, sea_orm::DbErr> {
let conn = get_database_connection().await?;
let result = Application::find()
.filter(Column::Id.eq(id))
.all(&conn)
.await?;
Ok(result)
}
57 changes: 57 additions & 0 deletions fplus-database/src/database/autoallocations.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
use crate::get_database_connection;
use crate::models::autoallocations::AddressWrapper;
use crate::models::autoallocations::{
Column, Entity as Autoallocations, Model as AutoallocationModel,
};
use alloy::primitives::Address;
use chrono::{DateTime, FixedOffset};
use sea_orm::{entity::*, query::*, DbBackend, DbErr};

pub async fn get_last_client_autoallocation(
client_evm_address: impl Into<AddressWrapper>,
) -> Result<Option<DateTime<FixedOffset>>, DbErr> {
let response = get_autoallocation(client_evm_address.into()).await?;
Ok(response.map(|allocation| allocation.last_allocation))
}

pub async fn create_or_update_autoallocation(
client_evm_address: &Address,
days_to_next_autoallocation: &i64,
) -> Result<u64, sea_orm::DbErr> {
let conn = get_database_connection().await?;
let client_address = client_evm_address.to_checksum(None);

let exec_res = conn
.execute(Statement::from_sql_and_values(
DbBackend::Postgres,
"INSERT INTO autoallocations (evm_wallet_address, last_allocation)
VALUES ($1, NOW())
ON CONFLICT (evm_wallet_address)
DO UPDATE SET last_allocation = NOW()
WHERE autoallocations.last_allocation <= NOW() - (INTERVAL '1 day' * $2::int);",
[client_address.into(), (*days_to_next_autoallocation).into()],
))
.await?;
Ok(exec_res.rows_affected())
}

pub async fn get_autoallocation(
client_evm_address: impl Into<AddressWrapper>,
) -> Result<Option<AutoallocationModel>, DbErr> {
let conn = get_database_connection().await?;
let response = Autoallocations::find()
.filter(Column::EvmWalletAddress.contains(client_evm_address.into()))
.one(&conn)
.await?;
Ok(response)
}

pub async fn delete_autoallocation(
client_evm_address: impl Into<AddressWrapper>,
) -> Result<(), sea_orm::DbErr> {
let conn = get_database_connection().await?;
Autoallocations::delete_by_id(client_evm_address.into())
.exec(&conn)
.await?;
Ok(())
}
1 change: 1 addition & 0 deletions fplus-database/src/database/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
pub mod allocation_amounts;
pub mod allocators;
pub mod applications;
pub mod autoallocations;
41 changes: 41 additions & 0 deletions fplus-database/src/models/autoallocations.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
use alloy::primitives::{Address, AddressError};
use chrono::{DateTime, FixedOffset};
use sea_orm::entity::prelude::*;
use sea_orm_newtype::DeriveNewType;
use serde::{Deserialize, Serialize};

#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
#[sea_orm(table_name = "autoallocations")]
pub struct Model {
#[sea_orm(primary_key, auto_increment = false)]
pub evm_wallet_address: AddressWrapper,
pub last_allocation: DateTime<FixedOffset>,
}

#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {}

impl ActiveModelBehavior for ActiveModel {}

#[derive(Clone, Debug, PartialEq, DeriveNewType, Eq, Serialize, Deserialize)]
#[sea_orm_newtype(try_from_into = "String", primary_key)]
pub struct AddressWrapper(pub Address);

impl TryFrom<String> for AddressWrapper {
type Error = AddressError;
fn try_from(value: String) -> Result<Self, Self::Error> {
Ok(AddressWrapper(Address::parse_checksummed(value, None)?))
}
}

impl From<AddressWrapper> for String {
fn from(value: AddressWrapper) -> Self {
value.0.to_checksum(None)
}
}

impl From<Address> for AddressWrapper {
fn from(value: Address) -> Self {
AddressWrapper(value)
}
}
1 change: 1 addition & 0 deletions fplus-database/src/models/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ pub mod prelude;
pub mod allocation_amounts;
pub mod allocators;
pub mod applications;
pub mod autoallocations;
3 changes: 0 additions & 3 deletions fplus-http-server/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,3 @@ async-trait = "0.1.73"
uuidv4 = "1.0.0"
log = "0.4.20"
cron = "0.12.1"
alloy = { git = "https://github.com/alloy-rs/alloy", version = "0.1.0", features = [
"signers",
] }
2 changes: 2 additions & 0 deletions fplus-http-server/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,8 @@ async fn main() -> std::io::Result<()> {
.service(router::allocator::delete)
.service(router::allocator::create_allocator_from_json)
.service(router::allocator::update_allocator_force)
.service(router::autoallocator::last_client_allocation)
.service(router::autoallocator::trigger_autoallocation)
// .service(router::allocator::get_installation_ids)
})
.bind(("0.0.0.0", 8080))?
Expand Down
23 changes: 23 additions & 0 deletions fplus-http-server/src/router/autoallocator.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
use actix_web::{get, post, web, HttpResponse, Responder};
use fplus_database::database::autoallocations as autoallocations_db;
use fplus_lib::core::autoallocator;
use fplus_lib::core::{LastAutoallocationQueryParams, TriggerAutoallocationInfo};
#[get("/autoallocator/last_client_allocation")]
pub async fn last_client_allocation(
query: web::Query<LastAutoallocationQueryParams>,
) -> impl Responder {
match autoallocations_db::get_last_client_autoallocation(query.evm_wallet_address).await {
Ok(last_client_allocation) => {
HttpResponse::Ok().body(serde_json::to_string_pretty(&last_client_allocation).unwrap())
}
Err(e) => HttpResponse::BadRequest().body(e.to_string()),
}
}

#[post("autoallocator/trigger_autoallocation")]
pub async fn trigger_autoallocation(info: web::Json<TriggerAutoallocationInfo>) -> impl Responder {
match autoallocator::trigger_autoallocation(&info.into_inner()).await {
Ok(()) => HttpResponse::Ok().body(serde_json::to_string_pretty("Success").unwrap()),
Err(e) => HttpResponse::BadRequest().body(e.to_string()),
}
}
1 change: 1 addition & 0 deletions fplus-http-server/src/router/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use actix_web::{get, HttpResponse, Responder};

pub mod allocator;
pub mod application;
pub mod autoallocator;
pub mod blockchain;
pub mod verifier;

Expand Down
14 changes: 2 additions & 12 deletions fplus-lib/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,20 +34,10 @@ fplus-database = { path = "../fplus-database", version = "2.0.2" }
pem = "1.0"
anyhow = "1.0"
regex = "1.0"
alloy = { git = "https://github.com/alloy-rs/alloy", version = "0.1.0", features = [
"signers",
"providers",
"node-bindings",
"sol-types",
"json",
"network",
"rpc-types-eth",
"provider-http",
"dyn-abi",
"eip712",
] }
tempfile = "3.10.1"
size = "0.5.0-preview2"
alloy = { version = "0.3.2", features = ["full"] }
fvm_shared = "4.4.0"

[dev-dependencies]
actix-rt = "2.9.0"
Expand Down
6 changes: 6 additions & 0 deletions fplus-lib/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,12 @@ pub fn default_env_vars() -> &'static HashMap<&'static str, &'static str> {
m.insert("RPC_URL", "https://mainnet.optimism.io");
m.insert("DMOB_API_URL", "https://api.datacapstats.io/public/api");
m.insert("DMOB_API_KEY", "5c993a17-7b18-4ead-a8a8-89dad981d87e");
m.insert("DAYS_TO_NEXT_AUTOALLOCATION", "14");
m.insert(
"ALLOCATOR_CONTRACT_ADDRESS",
"0x640bD4be149f40714D95aBcD414338bc7CfF39a3",
);
m.insert("AUTOALLOCATION_AMOUNT", "68719476736"); // 68719476736 B == 64 GB
m
})
}
Expand Down
37 changes: 35 additions & 2 deletions fplus-lib/src/core/application/gitcoin_interaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@ use crate::config::get_env_var_or_default;
use crate::error::LDNError;
use anyhow::Result;

pub trait ExpirableSolStruct: SolStruct {
fn get_expires_at(&self) -> &str;
fn get_issued_at(&self) -> &str;
}

sol! {
#[allow(missing_docs)]
function getScore(address user) view returns (uint256);
Expand All @@ -28,6 +33,34 @@ sol! {
string issued_at;
string expires_at;
}

#[derive(Deserialize)]
struct KycAutoallocationApproval {
string message;
string client_fil_address;
string issued_at;
string expires_at;
}
}

impl ExpirableSolStruct for KycApproval {
fn get_expires_at(&self) -> &str {
&self.expires_at
}

fn get_issued_at(&self) -> &str {
&self.issued_at
}
}

impl ExpirableSolStruct for KycAutoallocationApproval {
fn get_expires_at(&self) -> &str {
&self.expires_at
}

fn get_issued_at(&self) -> &str {
&self.issued_at
}
}

pub async fn verify_on_gitcoin(address_from_signature: &Address) -> Result<f64, LDNError> {
Expand Down Expand Up @@ -73,8 +106,8 @@ fn calculate_score(response: Bytes) -> f64 {
score as f64 / 10000.0
}

pub fn get_address_from_signature(
message: &KycApproval,
pub fn get_address_from_signature<T: SolStruct>(
message: &T,
signature: &str,
) -> Result<Address, LDNError> {
let domain = eip712_domain! {
Expand Down
83 changes: 83 additions & 0 deletions fplus-lib/src/core/autoallocator/metaallocator_interaction.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
use std::str::FromStr;

use crate::config::get_env_var_or_default;
use crate::error::LDNError;
use alloy::{
network::{EthereumWallet, TransactionBuilder},
primitives::{Address, Bytes, U256},
providers::{Provider, ProviderBuilder},
rpc::types::eth::TransactionRequest,
signers::local::PrivateKeySigner,
sol,
sol_types::SolCall,
};
use anyhow::Result;
use fplus_database::config::get_env_or_throw;
use fvm_shared::address::{set_current_network, Address as FilecoinAddress, Network};
sol! {
#[allow(missing_docs)]
function addVerifiedClient(bytes calldata clientAddress, uint256 amount);
}

async fn get_provider() -> Result<impl Provider, LDNError> {
let private_key = get_env_or_throw("AUTOALLOCATOR_PRIVATE_KEY");
let signer: PrivateKeySigner = private_key.parse().expect("Should parse private key");
let wallet = EthereumWallet::from(signer);
let rpc_url = get_env_var_or_default("GLIF_NODE_URL");
let provider = ProviderBuilder::new()
.with_recommended_fillers()
.wallet(wallet)
.on_builtin(&rpc_url)
.await
.map_err(|e| LDNError::New(format!("Building provider failed: {}", e)))?;
Ok(provider)
}

pub async fn add_verified_client(address: &str, amount: &u64) -> Result<(), LDNError> {
let provider = get_provider().await?;
let fil_address = decode_filecoin_address(address)?;
let amount = U256::try_from(*amount)
.map_err(|e| LDNError::New(format!("Failed to prase amount to U256 /// {}", e)))?;
let call = addVerifiedClientCall {
clientAddress: fil_address.into(),
amount,
}
.abi_encode();
let allocator_contract =
Address::parse_checksummed(get_env_var_or_default("ALLOCATOR_CONTRACT_ADDRESS"), None)
.map_err(|e| {
LDNError::New(format!(
"Parse ALLOCATOR_CONTRACT_ADDRESS to Address failed: {}",
e
))
})?;
let input = Bytes::from(call);

let tx = TransactionRequest::default()
.with_to(allocator_contract)
.with_input(input)
.with_gas_limit(45_000_000);

provider
.send_transaction(tx)
.await
.map_err(|e| LDNError::New(format!("RPC error: {}", e)))?
.watch()
.await
.map_err(|e| LDNError::New(format!("Transaction failed: {}", e)))?;
Ok(())
}

fn decode_filecoin_address(address: &str) -> Result<Vec<u8>, LDNError> {
let address_prefix = address.get(0..1);
if let Some(address_prefix) = address_prefix {
if address_prefix.eq("f") {
set_current_network(Network::Mainnet);
} else if address_prefix.eq("t") {
set_current_network(Network::Testnet);
}
}
let fil_address = FilecoinAddress::from_str(address)
.map_err(|e| LDNError::New(format!("Failed to prase address from string /// {}", e)))?;
Ok(fil_address.to_bytes())
}
Loading

0 comments on commit d7ac445

Please sign in to comment.