From a8c5fda873943b4d2f8246cf20e6853b6071e0c9 Mon Sep 17 00:00:00 2001 From: FiveMovesAhead Date: Wed, 15 Jan 2025 00:29:37 +0000 Subject: [PATCH] Add support for TokenLocker. Remove submit-topup and submit-deposit. --- tig-protocol/src/contracts/players.rs | 169 +++-------- tig-protocol/src/lib.rs | 2 +- tig-structs/src/core.rs | 16 +- tig-utils/src/eth.rs | 386 ++++++++------------------ tig-utils/src/lib.rs | 2 + tig-utils/tests/eth.rs | 86 ------ 6 files changed, 169 insertions(+), 492 deletions(-) diff --git a/tig-protocol/src/contracts/players.rs b/tig-protocol/src/contracts/players.rs index 7991a8c0..19e2822d 100644 --- a/tig-protocol/src/contracts/players.rs +++ b/tig-protocol/src/contracts/players.rs @@ -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( - ctx: &T, - player_id: String, - tx_hash: String, - log_idx: Option, -) -> Result { - 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( - ctx: &T, - player_id: String, - tx_hash: String, - log_idx: Option, -) -> Result { - 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( @@ -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; } } } diff --git a/tig-protocol/src/lib.rs b/tig-protocol/src/lib.rs index accceaf6..43ed7b72 100644 --- a/tig-protocol/src/lib.rs +++ b/tig-protocol/src/lib.rs @@ -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(ctx: &T) { diff --git a/tig-structs/src/core.rs b/tig-structs/src/core.rs index 553972c1..a5fb7bdc 100644 --- a/tig-structs/src/core.rs +++ b/tig-structs/src/core.rs @@ -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! { diff --git a/tig-utils/src/eth.rs b/tig-utils/src/eth.rs index 3542f674..4e1c833e 100644 --- a/tig-utils/src/eth.rs +++ b/tig-utils/src/eth.rs @@ -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 { + 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::())) } -#[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 { - 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::())) +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, - ) -> Result { - 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, - ) -> Result { - 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, _> = gnosis_safe + .query( + "isValidSignature", + ( + hash_message(msg.as_bytes()), + hex::decode(sig.trim_start_matches("0x"))?, + ), + None, + Options::default(), + None, ) - .unwrap(); - let result: Result, _> = 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> { - 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 = 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> { + 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 = 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 + }) +} diff --git a/tig-utils/src/lib.rs b/tig-utils/src/lib.rs index ff95f599..d62ac379 100644 --- a/tig-utils/src/lib.rs +++ b/tig-utils/src/lib.rs @@ -1,4 +1,6 @@ +#[cfg(feature = "web3")] mod eth; +#[cfg(feature = "web3")] pub use eth::*; mod hash; pub use hash::*; diff --git a/tig-utils/tests/eth.rs b/tig-utils/tests/eth.rs index 07f184ae..53f1b560 100644 --- a/tig-utils/tests/eth.rs +++ b/tig-utils/tests/eth.rs @@ -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!(