Skip to content

Commit

Permalink
feat: auto graph token spender approval, price and average fix
Browse files Browse the repository at this point in the history
  • Loading branch information
hopeyen committed Feb 28, 2024
1 parent 8de095a commit 9f35275
Show file tree
Hide file tree
Showing 5 changed files with 74 additions and 18 deletions.
6 changes: 4 additions & 2 deletions docs/client_guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,9 @@ $ file-exchange downloader \
--verifier 0xfC24cE7a4428A6B89B52645243662A02BA734ECF \
--provider "arbitrum-sepolia-rpc-endpoint" \
--network-subgraph https://api.thegraph.com/subgraphs/name/graphprotocol/graph-network-arbitrum-sepolia \
--escrow-subgraph https://api.thegraph.com/subgraphs/name/graphprotocol/scalar-tap-arbitrum-sepolia
--escrow-subgraph https://api.thegraph.com/subgraphs/name/graphprotocol/scalar-tap-arbitrum-sepolia \
--provider-concurrency 2 \
--max-auto-deposit 500
```

### Requirements
Expand All @@ -51,7 +53,7 @@ To use the client effectively, you will need:
1. Download and install the source code.
2. Gather configurations: Identify the CID of the desired Bundle, registered indexer endpoints, a local path for storing the downloaded files, private key (or mnemonics) of a wallet valid for Escrow payments, (optional) Obtain a free query auth token for limited access, the preference to concurrent providers for downloading.
3. Use the CLI commands to download files.
4. Before downloading, the client will check the status and price of the providers. If the download can be achived by availablility and price at the time of initiation, then download will proceed. If there is no availability, the client will suggest alternative bundles that overlaps with the target bundle and the corresponding providers. If there is not enough balance, the client will suggest Escrow top-up amounts for the Escrow accounts.
4. Before downloading, the client will check the status and price of the providers. If the download can be achived by availablility and price at the time of initiation, then download will proceed. If there is no availability, the client will suggest alternative bundles that overlaps with the target bundle and the corresponding providers. If there is not enough balance, the client will suggest Escrow top-up amounts for the Escrow accounts. With a configured on-chain deposit, the downloader might send GraphToken approval transaction to approve Escrow spending and deposit required amounts to the providers.

Enjoy seamless access to a vast world of data!

Expand Down
57 changes: 47 additions & 10 deletions file-exchange/src/download_client/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use secp256k1::SecretKey;
use std::collections::{HashMap, HashSet};
use std::fs::File;

use std::ops::Sub;
use std::path::Path;
use std::str::FromStr;
use std::sync::{Arc, Mutex as StdMutex};
Expand Down Expand Up @@ -442,10 +443,11 @@ impl Downloader {
.iter()
.map(|f| f.file_manifest.total_bytes)
.sum::<u64>();
let multiplier =
(total_bytes as f64) / (self.provider_concurrency as f64) * fail_tolerance;

let endpoints = self.indexer_urls.lock().unwrap().clone();
let multiplier = (total_bytes as f64)
/ ((self.provider_concurrency).min(endpoints.len().try_into().unwrap()) as f64)
* fail_tolerance;
let mut insufficient_balances = vec![];
for endpoint in endpoints {
tracing::trace!(
Expand Down Expand Up @@ -476,26 +478,51 @@ impl Downloader {
}
Some(a) => {
total_buying_power_in_bytes += a / endpoint.price_per_byte;
tracing::trace!("Balance is enough for this account");
tracing::trace!(
balance = a,
price_per_byte = endpoint.price_per_byte,
total_bytes,
buying_power_in_bytes = a / endpoint.price_per_byte,
"Balance is enough for this account"
);
0.0
}
};
insufficient_balances.push((receiver.clone(), missing));
}

let total_deficit = insufficient_balances.iter().map(|(_, v)| v).sum::<f64>();
if (total_buying_power_in_bytes as u64) >= total_bytes {
tracing::info!("Balance is enough to purchase the file, go ahead to download");
} else if insufficient_balances.iter().map(|(_, v)| v).sum::<f64>()
<= self.max_auto_deposit
{
} else if total_deficit <= self.max_auto_deposit {
tracing::info!("Downloader is allowed to automatically deposit sufficient balance for complete download, now depositing");
let escrow_allowance = f64::from_str(
&on_chain
.transaction_manager
.escrow_allowance()
.await?
.to_string(),
)
.map_err(|e| Error::ContractError(e.to_string()))?;
if total_deficit.gt(&escrow_allowance) {
let missing_allowance = U256::from_dec_str(
&total_deficit
.sub(total_deficit.sub(escrow_allowance))
.to_string(),
)
.map_err(|e| Error::ContractError(e.to_string()))?;
let _ = on_chain
.transaction_manager
.approve_escrow(&missing_allowance)
.await?;
};

let deficits: Vec<(String, f64)> = insufficient_balances
.into_iter()
.filter(|&(_, amount)| amount != 0.0)
.collect();
let (receivers, amounts): (Vec<String>, Vec<f64>) = deficits.into_iter().unzip();
let _ = on_chain
let tx_res = on_chain
.transaction_manager
.deposit_many(
receivers
Expand All @@ -505,14 +532,24 @@ impl Downloader {
amounts
.into_iter()
.map(|f| {
U256::from_dec_str(&f.to_string()).expect("Amount not parseable")
tracing::info!(
amount = &f.ceil().to_string().to_string(),
"amount"
);
U256::from_dec_str(&f.ceil().to_string())
.expect("Amount not parseable")
})
.collect(),
)
.await;
// Downloader might handle approval as well?
if let Err(e) = tx_res {
tracing::warn!(error = e.to_string(), "Failed to submit Escrow deposit, might need to approve Escrow contract as a GRT spender");

return Err(Error::ContractError(e.to_string()));
};
tracing::info!("Finished Escrow deposit, okay to initiate download");
} else {
let msg = format!("Balance is not enough to purchase {} bytes, look at the error message to see top-up recommendations for each account", total_buying_power_in_bytes);
let msg = format!("Balance is not enough to purchase {} bytes, look at the error message to see top-up recommendations for each account, or configure maximum automatic deposit threshold to be greater than the deficit amount of {}", total_bytes, total_deficit);
return Err(Error::PricingError(msg));
}
};
Expand Down
21 changes: 19 additions & 2 deletions file-exchange/src/transaction_manager/graph_token.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use ethers::contract::abigen;
use std::str::FromStr;

use ethers::contract::abigen;
use ethers_core::types::{TransactionReceipt, H160, U256};

use crate::errors::Error;
Expand All @@ -26,8 +27,24 @@ impl TransactionManager {
Ok(value)
}

/// Query wallet owner's allowance of GRT tokens to Escrow contract
pub async fn escrow_allowance(&self) -> Result<U256, Error> {
let owner = H160::from_str(&self.public_address().expect("Wallet address"))
.map_err(|e| Error::ContractError(e.to_string()))?;
let spender = self
.contract_addresses
.get("Escrow")
.expect("Escrow address");
let value = self
.token_contract
.allowance(owner, *spender)
.call()
.await
.map_err(|e| Error::ContractError(e.to_string()))?;
Ok(value)
}

// Approve spender and amount
/// call staking contract allocate function
pub async fn approve_escrow(
&self,
amount: &U256,
Expand Down
2 changes: 1 addition & 1 deletion file-service/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ pub struct ServerArgs {
env = "PRICE_PER_BYTE",
help = "Price per byte in GRT"
)]
pub price_per_byte: f32,
pub price_per_byte: f64,
}

#[derive(clap::ValueEnum, Clone, Debug, Serialize, Deserialize, Default)]
Expand Down
6 changes: 3 additions & 3 deletions file-service/src/file_server/cost.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use crate::file_server::ServerContext;
#[derive(Clone, Debug, Serialize, Deserialize, SimpleObject)]
pub struct GraphQlCostModel {
pub deployment: String,
pub price_per_byte: f32,
pub price_per_byte: f64,
}

#[derive(Default)]
Expand All @@ -23,7 +23,7 @@ impl Query {
ctx: &Context<'_>,
deployments: Vec<String>,
) -> Result<Vec<GraphQlCostModel>, anyhow::Error> {
let price: f32 = ctx
let price: f64 = ctx
.data_unchecked::<ServerContext>()
.state
.config
Expand Down Expand Up @@ -54,7 +54,7 @@ impl Query {
.get(&deployment)
.cloned();
let res = bundle.map(|_b| {
let price: f32 = ctx
let price: f64 = ctx
.data_unchecked::<ServerContext>()
.state
.config
Expand Down

0 comments on commit 9f35275

Please sign in to comment.