Add support for TokenLocker. Remove submit-topup and submit-deposit.

This commit is contained in:
FiveMovesAhead 2025-01-15 00:29:37 +00:00
parent a91414b7f3
commit a8c5fda873
6 changed files with 169 additions and 492 deletions

View File

@ -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,23 +169,28 @@ 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);
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] <= deposit.start_timestamp {
if round_timestamps[i + 1] <= *start_timestamp {
continue;
}
if round_timestamps[i] >= deposit.end_timestamp {
if round_timestamps[i] >= *end_timestamp {
break;
}
let start = if round_timestamps[i] <= deposit.start_timestamp {
deposit.start_timestamp
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] >= deposit.end_timestamp || i + 1 == lock_period_cap {
deposit.end_timestamp
if round_timestamps[i + 1] >= *end_timestamp || i + 1 == lock_period_cap {
*end_timestamp
} else {
round_timestamps[i + 1]
};
@ -300,4 +203,14 @@ pub(crate) async fn update(cache: &mut AddBlockCache) {
player_data.weighted_deposit += amount * weight;
}
}
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;
}
}
}
}

View File

@ -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) {

View File

@ -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! {

View File

@ -1,39 +1,10 @@
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::*};
#[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> {
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,
@ -43,138 +14,9 @@ mod web3_feature {
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 ERC20_TRANSFER_TOPIC: &str =
"ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef";
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#"[
pub const GNOSIS_SAFE_ABI: &str = r#"[
{
"inputs": [
{
@ -197,14 +39,14 @@ mod web3_feature {
"stateMutability": "view",
"type": "function"
}
]"#;
]"#;
pub async fn is_valid_gnosis_safe_sig(
pub async fn is_valid_gnosis_safe_sig(
rpc_url: &str,
address: &str,
msg: &str,
sig: &str,
) -> Result<()> {
) -> Result<()> {
let transport = web3::transports::Http::new(rpc_url)?;
let eth = web3::Web3::new(transport).eth();
@ -230,10 +72,10 @@ mod web3_feature {
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#"[
pub const ENS_REVERSE_RECORDS_ADDRESS: &str = "0x3671aE578E63FdF66ad4F3E12CC0c0d71Ac7510C";
pub const ENS_REVERSE_RECORDS_ABI: &str = r#"[
{
"inputs": [
{
@ -253,9 +95,9 @@ mod web3_feature {
"stateMutability": "view",
"type": "function"
}
]"#;
]"#;
pub async fn lookup_ens_name(rpc_url: &str, address: &str) -> Result<Option<String>> {
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();
@ -277,8 +119,4 @@ mod web3_feature {
} else {
None
})
}
}
#[cfg(feature = "web3")]
pub use web3_feature::*;

View File

@ -1,4 +1,6 @@
#[cfg(feature = "web3")]
mod eth;
#[cfg(feature = "web3")]
pub use eth::*;
mod hash;
pub use hash::*;

View File

@ -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!(