mirror of
https://github.com/tig-foundation/tig-monorepo.git
synced 2026-02-21 10:27:49 +08:00
Add support for TokenLocker. Remove submit-topup and submit-deposit.
This commit is contained in:
parent
a91414b7f3
commit
a8c5fda873
@ -1,110 +1,8 @@
|
||||
use crate::context::*;
|
||||
use anyhow::{anyhow, Result};
|
||||
use logging_timer::time;
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
time::{SystemTime, UNIX_EPOCH},
|
||||
};
|
||||
use std::collections::HashMap;
|
||||
use tig_structs::core::*;
|
||||
use tig_utils::*;
|
||||
|
||||
#[time]
|
||||
pub async fn submit_topup<T: Context>(
|
||||
ctx: &T,
|
||||
player_id: String,
|
||||
tx_hash: String,
|
||||
log_idx: Option<usize>,
|
||||
) -> Result<String> {
|
||||
let config = ctx.get_config().await;
|
||||
|
||||
let transfer = get_transfer(&config.erc20.rpc_url, &tx_hash, log_idx.clone())
|
||||
.await
|
||||
.map_err(|_| anyhow!("Invalid transaction: {}", tx_hash))?;
|
||||
if transfer.erc20 != config.erc20.token_address {
|
||||
return Err(anyhow!("Transfer asset must be TIG token"));
|
||||
}
|
||||
if transfer.sender != player_id {
|
||||
return Err(anyhow!("Transfer must be from player"));
|
||||
}
|
||||
if transfer.receiver != config.topups.topup_address {
|
||||
return Err(anyhow!("Transfer must send to topup_address"));
|
||||
}
|
||||
if transfer.amount < config.topups.min_topup_amount {
|
||||
return Err(anyhow!("Transfer must be at least min_topup_amount"));
|
||||
}
|
||||
let topup_id = ctx
|
||||
.add_topup_to_mempool(TopUpDetails {
|
||||
player_id,
|
||||
tx_hash,
|
||||
amount: transfer.amount,
|
||||
log_idx: transfer.log_idx,
|
||||
})
|
||||
.await?;
|
||||
Ok(topup_id)
|
||||
}
|
||||
|
||||
#[time]
|
||||
pub async fn submit_deposit<T: Context>(
|
||||
ctx: &T,
|
||||
player_id: String,
|
||||
tx_hash: String,
|
||||
log_idx: Option<usize>,
|
||||
) -> Result<String> {
|
||||
let config = ctx.get_config().await;
|
||||
|
||||
let linear_lock = get_linear_lock(&config.erc20.rpc_url, &tx_hash, log_idx.clone())
|
||||
.await
|
||||
.map_err(|_| anyhow!("Invalid transaction: {}", tx_hash))?;
|
||||
if linear_lock.locker != config.deposits.lock_address {
|
||||
return Err(anyhow!("Deposit must be LinearLock of TIG token"));
|
||||
}
|
||||
if linear_lock.erc20 != config.erc20.token_address {
|
||||
return Err(anyhow!("LinearLock asset must be TIG token"));
|
||||
}
|
||||
if linear_lock.owner != player_id {
|
||||
return Err(anyhow!("LinearLock must be owned by player"));
|
||||
}
|
||||
if linear_lock.can_cancel {
|
||||
return Err(anyhow!("LinearLock must not be cancelable"));
|
||||
}
|
||||
if linear_lock.can_transfer {
|
||||
return Err(anyhow!("LinearLock with transferrable not supported"));
|
||||
}
|
||||
if linear_lock.cliff_timestamp != 0 {
|
||||
return Err(anyhow!("LinearLock with cliff not supported"));
|
||||
}
|
||||
if linear_lock.amount < config.deposits.min_lock_amount {
|
||||
return Err(anyhow!("LinearLock must be at least min_lock_amount"));
|
||||
}
|
||||
let now = SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_secs();
|
||||
if linear_lock.end_timestamp <= now {
|
||||
return Err(anyhow!("LinearLock is already expired"));
|
||||
}
|
||||
|
||||
let min_duration = config.deposits.min_lock_period
|
||||
* config.rounds.blocks_per_round
|
||||
* config.rounds.seconds_between_blocks;
|
||||
if linear_lock.end_timestamp - linear_lock.start_timestamp < min_duration as u64 {
|
||||
return Err(anyhow!(
|
||||
"LinearLock must be at least {} round",
|
||||
config.deposits.min_lock_period
|
||||
));
|
||||
}
|
||||
let deposit_id = ctx
|
||||
.add_deposit_to_mempool(DepositDetails {
|
||||
player_id,
|
||||
tx_hash,
|
||||
amount: linear_lock.amount,
|
||||
log_idx: linear_lock.log_idx,
|
||||
start_timestamp: linear_lock.start_timestamp,
|
||||
end_timestamp: linear_lock.end_timestamp,
|
||||
})
|
||||
.await?;
|
||||
Ok(deposit_id)
|
||||
}
|
||||
|
||||
#[time]
|
||||
pub async fn set_delegatees<T: Context>(
|
||||
@ -271,33 +169,48 @@ pub(crate) async fn update(cache: &mut AddBlockCache) {
|
||||
}
|
||||
|
||||
for deposit in active_deposit_details.values() {
|
||||
let total_time = PreciseNumber::from(deposit.end_timestamp - deposit.start_timestamp);
|
||||
for i in 0..lock_period_cap {
|
||||
if round_timestamps[i + 1] <= deposit.start_timestamp {
|
||||
continue;
|
||||
match &deposit.r#type {
|
||||
DepositType::Linear {
|
||||
start_timestamp,
|
||||
end_timestamp,
|
||||
} => {
|
||||
let total_time = PreciseNumber::from(end_timestamp - start_timestamp);
|
||||
for i in 0..lock_period_cap {
|
||||
if round_timestamps[i + 1] <= *start_timestamp {
|
||||
continue;
|
||||
}
|
||||
if round_timestamps[i] >= *end_timestamp {
|
||||
break;
|
||||
}
|
||||
let start = if round_timestamps[i] <= *start_timestamp {
|
||||
*start_timestamp
|
||||
} else {
|
||||
round_timestamps[i]
|
||||
};
|
||||
// all deposits above max_lock_period_rounds get the same max weight
|
||||
let end =
|
||||
if round_timestamps[i + 1] >= *end_timestamp || i + 1 == lock_period_cap {
|
||||
*end_timestamp
|
||||
} else {
|
||||
round_timestamps[i + 1]
|
||||
};
|
||||
let amount = deposit.amount * PreciseNumber::from(end - start) / total_time;
|
||||
let weight = PreciseNumber::from(i + 1);
|
||||
let player_data = active_players_block_data
|
||||
.get_mut(&deposit.player_id)
|
||||
.unwrap();
|
||||
*player_data.deposit_by_locked_period.get_mut(i).unwrap() += amount;
|
||||
player_data.weighted_deposit += amount * weight;
|
||||
}
|
||||
}
|
||||
if round_timestamps[i] >= deposit.end_timestamp {
|
||||
break;
|
||||
DepositType::Lock { .. } => {
|
||||
let weight = PreciseNumber::from(4);
|
||||
let player_data = active_players_block_data
|
||||
.get_mut(&deposit.player_id)
|
||||
.unwrap();
|
||||
*player_data.deposit_by_locked_period.get_mut(3).unwrap() += deposit.amount;
|
||||
player_data.weighted_deposit += deposit.amount * weight;
|
||||
}
|
||||
let start = if round_timestamps[i] <= deposit.start_timestamp {
|
||||
deposit.start_timestamp
|
||||
} else {
|
||||
round_timestamps[i]
|
||||
};
|
||||
// all deposits above max_lock_period_rounds get the same max weight
|
||||
let end =
|
||||
if round_timestamps[i + 1] >= deposit.end_timestamp || i + 1 == lock_period_cap {
|
||||
deposit.end_timestamp
|
||||
} else {
|
||||
round_timestamps[i + 1]
|
||||
};
|
||||
let amount = deposit.amount * PreciseNumber::from(end - start) / total_time;
|
||||
let weight = PreciseNumber::from(i + 1);
|
||||
let player_data = active_players_block_data
|
||||
.get_mut(&deposit.player_id)
|
||||
.unwrap();
|
||||
*player_data.deposit_by_locked_period.get_mut(i).unwrap() += amount;
|
||||
player_data.weighted_deposit += amount * weight;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -5,7 +5,7 @@ use context::*;
|
||||
pub use contracts::{
|
||||
algorithms::{submit_algorithm, submit_binary, submit_breakthrough},
|
||||
benchmarks::{submit_benchmark, submit_fraud, submit_precommit, submit_proof},
|
||||
players::{set_delegatees, set_reward_share, set_vote, submit_deposit, submit_topup},
|
||||
players::{set_delegatees, set_reward_share, set_vote},
|
||||
};
|
||||
|
||||
pub async fn add_block<T: Context>(ctx: &T) {
|
||||
|
||||
@ -3,7 +3,7 @@ use serde::{Deserialize, Serialize};
|
||||
use serde_json::{Map, Value};
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use tig_utils::{jsonify, u64s_from_str, u8s_from_str};
|
||||
pub use tig_utils::{Frontier, MerkleBranch, MerkleHash, Point, PreciseNumber, Transfer, U256};
|
||||
pub use tig_utils::{Frontier, MerkleBranch, MerkleHash, Point, PreciseNumber, U256};
|
||||
|
||||
serializable_struct_with_getters! {
|
||||
Algorithm {
|
||||
@ -312,14 +312,24 @@ serializable_struct_with_getters! {
|
||||
}
|
||||
|
||||
// Deposit child structs
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum DepositType {
|
||||
Linear {
|
||||
start_timestamp: u64,
|
||||
end_timestamp: u64,
|
||||
},
|
||||
Lock {
|
||||
eth_block_num: u64,
|
||||
},
|
||||
}
|
||||
serializable_struct_with_getters! {
|
||||
DepositDetails {
|
||||
player_id: String,
|
||||
tx_hash: String,
|
||||
log_idx: usize,
|
||||
amount: PreciseNumber,
|
||||
start_timestamp: u64,
|
||||
end_timestamp: u64,
|
||||
r#type: DepositType,
|
||||
}
|
||||
}
|
||||
serializable_struct_with_getters! {
|
||||
|
||||
@ -1,284 +1,122 @@
|
||||
use crate::number::PreciseNumber;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::str::FromStr;
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
||||
pub struct Transfer {
|
||||
pub erc20: String,
|
||||
pub sender: String,
|
||||
pub receiver: String,
|
||||
pub amount: PreciseNumber,
|
||||
pub log_idx: usize,
|
||||
use anyhow::{anyhow, Result};
|
||||
use hex::{self, ToHex};
|
||||
use web3::{contract::*, signing::*, types::*};
|
||||
|
||||
pub fn recover_address_from_msg_and_sig(msg: &str, sig: &str) -> Result<String> {
|
||||
let hash_msg = hash_message(msg.as_bytes());
|
||||
let recovery = Recovery::from_raw_signature(
|
||||
hash_msg,
|
||||
hex::decode(sig.trim_start_matches("0x"))
|
||||
.map_err(|e| web3::Error::InvalidResponse(e.to_string()))?,
|
||||
)?;
|
||||
let (signature, recovery_id) = recovery.as_signature().unwrap();
|
||||
let address = recover(hash_msg.as_bytes(), &signature, recovery_id)?;
|
||||
Ok(format!("0x{}", address.encode_hex::<String>()))
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
||||
pub struct LinearLock {
|
||||
pub locker: String,
|
||||
pub erc20: String,
|
||||
pub owner: String,
|
||||
pub can_cancel: bool,
|
||||
pub can_transfer: bool,
|
||||
pub start_timestamp: u64,
|
||||
pub cliff_timestamp: u64,
|
||||
pub end_timestamp: u64,
|
||||
pub amount: PreciseNumber,
|
||||
pub log_idx: usize,
|
||||
}
|
||||
|
||||
#[cfg(feature = "web3")]
|
||||
mod web3_feature {
|
||||
use std::str::FromStr;
|
||||
|
||||
use crate::PreciseNumber;
|
||||
use anyhow::{anyhow, Result};
|
||||
use hex::{self, ToHex};
|
||||
use web3::{contract::*, signing::*, types::*};
|
||||
|
||||
pub fn recover_address_from_msg_and_sig(msg: &str, sig: &str) -> Result<String> {
|
||||
let hash_msg = hash_message(msg.as_bytes());
|
||||
let recovery = Recovery::from_raw_signature(
|
||||
hash_msg,
|
||||
hex::decode(sig.trim_start_matches("0x"))
|
||||
.map_err(|e| web3::Error::InvalidResponse(e.to_string()))?,
|
||||
)?;
|
||||
let (signature, recovery_id) = recovery.as_signature().unwrap();
|
||||
let address = recover(hash_msg.as_bytes(), &signature, recovery_id)?;
|
||||
Ok(format!("0x{}", address.encode_hex::<String>()))
|
||||
pub const GNOSIS_SAFE_ABI: &str = r#"[
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"name": "_dataHash",
|
||||
"type": "bytes32"
|
||||
},
|
||||
{
|
||||
"name": "_signature",
|
||||
"type": "bytes"
|
||||
}
|
||||
],
|
||||
"name": "isValidSignature",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "bytes4"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
}
|
||||
]"#;
|
||||
|
||||
pub const ERC20_TRANSFER_TOPIC: &str =
|
||||
"ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef";
|
||||
pub async fn is_valid_gnosis_safe_sig(
|
||||
rpc_url: &str,
|
||||
address: &str,
|
||||
msg: &str,
|
||||
sig: &str,
|
||||
) -> Result<()> {
|
||||
let transport = web3::transports::Http::new(rpc_url)?;
|
||||
let eth = web3::Web3::new(transport).eth();
|
||||
|
||||
pub async fn get_transfer(
|
||||
rpc_url: &str,
|
||||
tx_hash: &str,
|
||||
log_idx: Option<usize>,
|
||||
) -> Result<super::Transfer> {
|
||||
let transport = web3::transports::Http::new(rpc_url)?;
|
||||
let eth = web3::Web3::new(transport).eth();
|
||||
|
||||
let tx_hash = H256::from_slice(hex::decode(tx_hash.trim_start_matches("0x"))?.as_slice());
|
||||
let receipt = eth
|
||||
.transaction_receipt(tx_hash)
|
||||
.await?
|
||||
.ok_or_else(|| anyhow!("Receipt for transaction {} not found", tx_hash))?;
|
||||
|
||||
if !receipt.status.is_some_and(|x| x.as_u64() == 1) {
|
||||
return Err(anyhow!("Transaction not confirmed"));
|
||||
}
|
||||
|
||||
// Find the Transfer event log
|
||||
let transfer_topic = H256::from_slice(&hex::decode(ERC20_TRANSFER_TOPIC)?);
|
||||
let (log_idx, transfer_log) = receipt
|
||||
.logs
|
||||
.iter()
|
||||
.enumerate()
|
||||
.find(|(idx, log)| {
|
||||
(log_idx.is_none() || log_idx.is_some_and(|i| i == *idx))
|
||||
&& !log.topics.is_empty()
|
||||
&& log.topics[0] == transfer_topic
|
||||
})
|
||||
.ok_or_else(|| anyhow!("No ERC20 transfer event found"))?;
|
||||
|
||||
if transfer_log.topics.len() != 3 {
|
||||
return Err(anyhow!("Invalid Transfer event format"));
|
||||
}
|
||||
|
||||
// Extract transfer details from the event
|
||||
let erc20 = format!("0x{}", hex::encode(&transfer_log.address.as_bytes()));
|
||||
let sender = format!(
|
||||
"0x{}",
|
||||
hex::encode(&transfer_log.topics[1].as_bytes()[12..])
|
||||
);
|
||||
let receiver = format!(
|
||||
"0x{}",
|
||||
hex::encode(&transfer_log.topics[2].as_bytes()[12..])
|
||||
);
|
||||
let amount = PreciseNumber::from_hex_str(&hex::encode(&transfer_log.data.0))?;
|
||||
|
||||
Ok(super::Transfer {
|
||||
erc20,
|
||||
sender,
|
||||
receiver,
|
||||
amount,
|
||||
log_idx,
|
||||
})
|
||||
}
|
||||
|
||||
pub const SABLIERV2_CREATELOCKUPLINEARSTREAM_TOPIC: &str =
|
||||
"44cb432df42caa86b7ec73644ab8aec922bc44c71c98fc330addc75b88adbc7c";
|
||||
|
||||
pub async fn get_linear_lock(
|
||||
rpc_url: &str,
|
||||
tx_hash: &str,
|
||||
log_idx: Option<usize>,
|
||||
) -> Result<super::LinearLock> {
|
||||
let transport = web3::transports::Http::new(rpc_url)?;
|
||||
let eth = web3::Web3::new(transport).eth();
|
||||
|
||||
let tx_hash = H256::from_slice(hex::decode(tx_hash.trim_start_matches("0x"))?.as_slice());
|
||||
let receipt = eth
|
||||
.transaction_receipt(tx_hash)
|
||||
.await?
|
||||
.ok_or_else(|| anyhow!("Receipt for transaction {} not found", tx_hash))?;
|
||||
|
||||
if !receipt.status.is_some_and(|x| x.as_u64() == 1) {
|
||||
return Err(anyhow!("Transaction not confirmed"));
|
||||
}
|
||||
|
||||
// Find the Transfer event log
|
||||
let linear_lock_topic =
|
||||
H256::from_slice(&hex::decode(SABLIERV2_CREATELOCKUPLINEARSTREAM_TOPIC)?);
|
||||
let (log_idx, transfer_log) = receipt
|
||||
.logs
|
||||
.iter()
|
||||
.enumerate()
|
||||
.find(|(idx, log)| {
|
||||
(log_idx.is_none() || log_idx.is_some_and(|i| i == *idx))
|
||||
&& !log.topics.is_empty()
|
||||
&& log.topics[0] == linear_lock_topic
|
||||
})
|
||||
.ok_or_else(|| anyhow!("No ERC20 transfer event found"))?;
|
||||
|
||||
if transfer_log.topics.len() != 4 {
|
||||
return Err(anyhow!("Invalid CreateLinearLockStream event format"));
|
||||
}
|
||||
|
||||
// Extract transfer details from the event
|
||||
let locker = format!("0x{}", hex::encode(&transfer_log.address.as_bytes()));
|
||||
let owner = format!(
|
||||
"0x{}",
|
||||
hex::encode(&transfer_log.topics[2].as_bytes()[12..])
|
||||
);
|
||||
let erc20 = format!(
|
||||
"0x{}",
|
||||
hex::encode(&transfer_log.topics[3].as_bytes()[12..])
|
||||
);
|
||||
let amount = PreciseNumber::from_hex_str(&hex::encode(&transfer_log.data.0[64..96]))?;
|
||||
let can_cancel = transfer_log.data.0[159] == 1;
|
||||
let can_transfer = transfer_log.data.0[191] == 1;
|
||||
let start_timestamp = u64::from_be_bytes(transfer_log.data.0[216..224].try_into().unwrap());
|
||||
let cliff_timestamp = u64::from_be_bytes(transfer_log.data.0[248..256].try_into().unwrap());
|
||||
let end_timestamp = u64::from_be_bytes(transfer_log.data.0[280..288].try_into().unwrap());
|
||||
|
||||
Ok(super::LinearLock {
|
||||
locker,
|
||||
erc20,
|
||||
owner,
|
||||
amount,
|
||||
can_cancel,
|
||||
can_transfer,
|
||||
start_timestamp,
|
||||
cliff_timestamp,
|
||||
end_timestamp,
|
||||
log_idx,
|
||||
})
|
||||
}
|
||||
|
||||
pub const GNOSIS_SAFE_ABI: &str = r#"[
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"name": "_dataHash",
|
||||
"type": "bytes32"
|
||||
},
|
||||
{
|
||||
"name": "_signature",
|
||||
"type": "bytes"
|
||||
}
|
||||
],
|
||||
"name": "isValidSignature",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "bytes4"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
}
|
||||
]"#;
|
||||
|
||||
pub async fn is_valid_gnosis_safe_sig(
|
||||
rpc_url: &str,
|
||||
address: &str,
|
||||
msg: &str,
|
||||
sig: &str,
|
||||
) -> Result<()> {
|
||||
let transport = web3::transports::Http::new(rpc_url)?;
|
||||
let eth = web3::Web3::new(transport).eth();
|
||||
|
||||
let gnosis_safe = Contract::from_json(
|
||||
eth.clone(),
|
||||
H160::from_str(&address)?,
|
||||
GNOSIS_SAFE_ABI.as_bytes(),
|
||||
let gnosis_safe = Contract::from_json(
|
||||
eth.clone(),
|
||||
H160::from_str(&address)?,
|
||||
GNOSIS_SAFE_ABI.as_bytes(),
|
||||
)
|
||||
.unwrap();
|
||||
let result: Result<Vec<u8>, _> = gnosis_safe
|
||||
.query(
|
||||
"isValidSignature",
|
||||
(
|
||||
hash_message(msg.as_bytes()),
|
||||
hex::decode(sig.trim_start_matches("0x"))?,
|
||||
),
|
||||
None,
|
||||
Options::default(),
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
let result: Result<Vec<u8>, _> = gnosis_safe
|
||||
.query(
|
||||
"isValidSignature",
|
||||
(
|
||||
hash_message(msg.as_bytes()),
|
||||
hex::decode(sig.trim_start_matches("0x"))?,
|
||||
),
|
||||
None,
|
||||
Options::default(),
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
match result {
|
||||
Ok(_) => Ok(()),
|
||||
Err(e) => Err(anyhow!("Failed query isValidSignature: {:?}", e)),
|
||||
}
|
||||
}
|
||||
|
||||
pub const ENS_REVERSE_RECORDS_ADDRESS: &str = "0x3671aE578E63FdF66ad4F3E12CC0c0d71Ac7510C";
|
||||
pub const ENS_REVERSE_RECORDS_ABI: &str = r#"[
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "address[]",
|
||||
"name": "addresses",
|
||||
"type": "address[]"
|
||||
}
|
||||
],
|
||||
"name": "getNames",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "string[]",
|
||||
"name": "r",
|
||||
"type": "string[]"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
}
|
||||
]"#;
|
||||
|
||||
pub async fn lookup_ens_name(rpc_url: &str, address: &str) -> Result<Option<String>> {
|
||||
let transport = web3::transports::Http::new(rpc_url)?;
|
||||
let eth = web3::Web3::new(transport).eth();
|
||||
|
||||
let reverse_records = Contract::from_json(
|
||||
eth.clone(),
|
||||
H160::from_str(ENS_REVERSE_RECORDS_ADDRESS)?,
|
||||
ENS_REVERSE_RECORDS_ABI.as_bytes(),
|
||||
)?;
|
||||
|
||||
let addresses = vec![H160::from_str(address.trim_start_matches("0x"))?];
|
||||
|
||||
let names: Vec<String> = reverse_records
|
||||
.query("getNames", (addresses,), None, Options::default(), None)
|
||||
.await?;
|
||||
|
||||
// Return first name or none if empty
|
||||
Ok(if !names[0].is_empty() {
|
||||
Some(names[0].clone())
|
||||
} else {
|
||||
None
|
||||
})
|
||||
.await;
|
||||
match result {
|
||||
Ok(_) => Ok(()),
|
||||
Err(e) => Err(anyhow!("Failed query isValidSignature: {:?}", e)),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "web3")]
|
||||
pub use web3_feature::*;
|
||||
pub const ENS_REVERSE_RECORDS_ADDRESS: &str = "0x3671aE578E63FdF66ad4F3E12CC0c0d71Ac7510C";
|
||||
pub const ENS_REVERSE_RECORDS_ABI: &str = r#"[
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "address[]",
|
||||
"name": "addresses",
|
||||
"type": "address[]"
|
||||
}
|
||||
],
|
||||
"name": "getNames",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "string[]",
|
||||
"name": "r",
|
||||
"type": "string[]"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
}
|
||||
]"#;
|
||||
|
||||
pub async fn lookup_ens_name(rpc_url: &str, address: &str) -> Result<Option<String>> {
|
||||
let transport = web3::transports::Http::new(rpc_url)?;
|
||||
let eth = web3::Web3::new(transport).eth();
|
||||
|
||||
let reverse_records = Contract::from_json(
|
||||
eth.clone(),
|
||||
H160::from_str(ENS_REVERSE_RECORDS_ADDRESS)?,
|
||||
ENS_REVERSE_RECORDS_ABI.as_bytes(),
|
||||
)?;
|
||||
|
||||
let addresses = vec![H160::from_str(address.trim_start_matches("0x"))?];
|
||||
|
||||
let names: Vec<String> = reverse_records
|
||||
.query("getNames", (addresses,), None, Options::default(), None)
|
||||
.await?;
|
||||
|
||||
// Return first name or none if empty
|
||||
Ok(if !names[0].is_empty() {
|
||||
Some(names[0].clone())
|
||||
} else {
|
||||
None
|
||||
})
|
||||
}
|
||||
|
||||
@ -1,4 +1,6 @@
|
||||
#[cfg(feature = "web3")]
|
||||
mod eth;
|
||||
#[cfg(feature = "web3")]
|
||||
pub use eth::*;
|
||||
mod hash;
|
||||
pub use hash::*;
|
||||
|
||||
@ -55,92 +55,6 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_get_transfer() {
|
||||
assert_eq!(
|
||||
tig_utils::get_transfer(
|
||||
"https://mainnet.base.org",
|
||||
"0xb02b7c2a4bf72f7b5e96dcfa64d42cf75b765657998c9168beddcf06811b3701",
|
||||
None
|
||||
)
|
||||
.await
|
||||
.unwrap(),
|
||||
tig_utils::Transfer {
|
||||
erc20: "0x0c03ce270b4826ec62e7dd007f0b716068639f7b".to_string(),
|
||||
sender: "0x691108d12348ef0a153896492d96ff92bce90fe8".to_string(),
|
||||
receiver: "0x4e14297b4a5f7ab2e3c32ba262df2a2f8e367111".to_string(),
|
||||
amount: tig_utils::PreciseNumber::from_hex_str(
|
||||
"00000000000000000000000000000000000000000000000178f3b8cd09d1681e"
|
||||
)
|
||||
.unwrap(),
|
||||
log_idx: 0
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
tig_utils::get_transfer(
|
||||
"https://mainnet.base.org",
|
||||
"0xf68188c3913a45236a9435ac7b947448d607a2e7894c6dc205d45a3e3475dd9b",
|
||||
Some(5),
|
||||
)
|
||||
.await
|
||||
.unwrap(),
|
||||
tig_utils::Transfer {
|
||||
erc20: "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913".to_string(),
|
||||
sender: "0x6cdcb1c4a4d1c3c6d054b27ac5b77e89eafb971d".to_string(),
|
||||
receiver: "0x042c37762d1d126bc61eac2f5ceb7a96318f5db9".to_string(),
|
||||
amount: tig_utils::PreciseNumber::from_hex_str(
|
||||
"00000000000000000000000000000000000000000000000000000000000dfec2"
|
||||
)
|
||||
.unwrap(),
|
||||
log_idx: 5
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
tig_utils::get_transfer(
|
||||
"https://sepolia.base.org",
|
||||
"0x093aa07701f2cb1ef62f0efcf101588898d6d2869edf66b8efc23969a15c218f",
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.unwrap(),
|
||||
tig_utils::Transfer {
|
||||
erc20: "0x3366feee9bbe5b830df9e1fa743828732b13959a".to_string(),
|
||||
sender: "0x26979f7282fc78cc83a74c5aeb317e7c13d33235".to_string(),
|
||||
receiver: "0xc30edf0147c46d0a5f79bfe9b15ce9de9b8879be".to_string(),
|
||||
amount: tig_utils::PreciseNumber::from(123),
|
||||
log_idx: 0,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_get_linearlock() {
|
||||
assert_eq!(
|
||||
tig_utils::get_linear_lock(
|
||||
"https://mainnet.base.org",
|
||||
"0xcb183d3a0335eb71739dc58b926c68c0382d806cab9503b5bdeeb267eb961ea1",
|
||||
None
|
||||
)
|
||||
.await
|
||||
.unwrap(),
|
||||
tig_utils::LinearLock {
|
||||
locker: "0x4cb16d4153123a74bc724d161050959754f378d8".to_string(),
|
||||
erc20: "0x0c03ce270b4826ec62e7dd007f0b716068639f7b".to_string(),
|
||||
owner: "0xe716caba1aa085ad6f96d9027be4722a37a4e98a".to_string(),
|
||||
amount: tig_utils::PreciseNumber::from_hex_str(
|
||||
"0000000000000000000000000000000000000000000008890c5abfd643a573f8"
|
||||
)
|
||||
.unwrap(),
|
||||
can_cancel: false,
|
||||
can_transfer: true,
|
||||
start_timestamp: 1728860015,
|
||||
cliff_timestamp: 0,
|
||||
end_timestamp: 1729464815,
|
||||
log_idx: 10
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_lookup_ens_name() {
|
||||
assert_eq!(
|
||||
|
||||
Loading…
Reference in New Issue
Block a user