Skip to content

Commit

Permalink
Add support for gnosis safe.
Browse files Browse the repository at this point in the history
  • Loading branch information
FiveMovesAhead committed May 8, 2024
1 parent 46b0919 commit 116b544
Show file tree
Hide file tree
Showing 5 changed files with 96 additions and 4 deletions.
1 change: 1 addition & 0 deletions tig-protocol/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ pub trait Context {
wasm_vm_config: &WasmVMConfig,
) -> ContextResult<anyhow::Result<SolutionData>>;
async fn get_transaction(&mut self, tx_hash: &String) -> ContextResult<Transaction>;
async fn get_multisig_owners(&mut self, address: &String) -> ContextResult<Vec<String>>;

// Mempool
async fn add_block(
Expand Down
13 changes: 12 additions & 1 deletion tig-protocol/src/submit_algorithm.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use std::collections::HashSet;

use crate::{context::*, error::*};
use tig_structs::core::*;
use tig_utils::*;
Expand Down Expand Up @@ -61,13 +63,22 @@ async fn verify_submission_fee<T: Context>(
tx_hash: details.tx_hash.clone(),
});
}
let mut valid_senders = HashSet::<String>::new();
valid_senders.insert(player.id.clone());
if player.details.is_multisig {
let multisig_owners = ctx
.get_multisig_owners(&player.id)
.await
.unwrap_or_else(|e| panic!("get_multisig_owners error: {:?}", e));
valid_senders.extend(multisig_owners.into_iter());
}

let transaction = ctx.get_transaction(&details.tx_hash).await.map_err(|_| {
ProtocolError::InvalidTransaction {
tx_hash: details.tx_hash.clone(),
}
})?;
if transaction.sender != player.id {
if !valid_senders.contains(&transaction.sender) {
return Err(ProtocolError::InvalidSubmissionFeeSender {
tx_hash: details.tx_hash.clone(),
expected_sender: player.id.clone(),
Expand Down
1 change: 1 addition & 0 deletions tig-structs/src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ serializable_struct_with_getters! {
RequestApiKeyReq {
signature: String,
address: String,
gnosis_safe_setup_tx_hash: Option<String>,
}
}

Expand Down
1 change: 1 addition & 0 deletions tig-structs/src/core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,7 @@ serializable_struct_with_getters! {
serializable_struct_with_getters! {
PlayerDetails {
name: String,
is_multisig: bool,
}
}
serializable_struct_with_getters! {
Expand Down
84 changes: 81 additions & 3 deletions tig-utils/src/eth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,12 @@ pub struct Transaction {

#[cfg(feature = "web3")]
mod web3_feature {
use std::str::FromStr;

use crate::PreciseNumber;
use anyhow::{anyhow, Result};
use hex::{self, ToHex};
use web3::{signing::*, types::*};
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());
Expand All @@ -36,16 +38,92 @@ mod web3_feature {
.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"));
}
let tx = eth
.transaction(TransactionId::Hash(tx_hash))
.await?
.ok_or_else(|| anyhow!("Transaction {} not found", tx_hash))?;
Ok(super::Transaction {
sender: format!("{:?}", tx.from.unwrap()),
receiver: format!("{:?}", receipt.to.unwrap()),
sender: format!("{:?}", tx.from.ok_or_else(|| anyhow!("Sender not found"))?),
receiver: format!(
"{:?}",
receipt.to.ok_or_else(|| anyhow!("Receiver not found"))?
),
amount: PreciseNumber::from_dec_str(&tx.value.to_string())?,
})
}

pub const PROXY_CREATION_TOPIC_ID: &str =
"0x4f51faf6c4561ff95f067657e43439f0f856d97c04d9ec9070a6199ad418e235";
pub const GNOSIS_SAFE_PROXY_CONTRACT_ADDRESS: &str =
"0xc22834581ebc8527d974f8a1c97e1bea4ef910bc";
pub const GNOSIS_SAFE_ABI: &str = r#"[
{
"inputs":[],
"name":"getOwners",
"outputs":[
{
"internalType":"address[]",
"name":"",
"type":"address[]"
}
],
"stateMutability":"view",
"type":"function"
}
]"#;

pub async fn get_gnosis_safe_owners(rpc_url: &str, address: &str) -> Result<Vec<String>> {
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(&gnosis_safe_address)?,
GNOSIS_SAFE_ABI.as_bytes(),
)
.unwrap();
let owners: Vec<Address> = gnosis_safe
.query("getOwners", (), None, Options::default(), None)
.await
.map_err(|e| anyhow!("Failed query getOwners: {:?}", e))?;
Ok(owners.iter().map(|x| format!("{:?}", x)).collect())
}

pub async fn get_gnosis_safe_address(rpc_url: &str, tx_hash: &str) -> Result<String> {
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"));
}
if !receipt
.to
.is_some_and(|x| format!("{:?}", x) == GNOSIS_SAFE_PROXY_CONTRACT_ADDRESS)
{
return Err(anyhow!("Not a Create Gnosis Safe transaction"));
}
match receipt
.logs
.iter()
.find(|log| {
log.topics
.iter()
.any(|topic| format!("{:?}", topic) == PROXY_CREATION_TOPIC_ID)
})
.map(|log| format!("0x{}", hex::encode(&log.data.0.as_slice()[12..32])))
{
None => Err(anyhow!("No ProxyCreation event found")),
Some(gnosis_safe_address) => Ok(gnosis_safe_address),
}
}
}

#[cfg(feature = "web3")]
Expand Down

0 comments on commit 116b544

Please sign in to comment.