From 3c0ba2a9a1be24c7dcd04976f9df8ab53b5fc87b Mon Sep 17 00:00:00 2001 From: hopeyen Date: Fri, 5 Jan 2024 09:29:35 -0600 Subject: [PATCH] feat: send on-chain allocate tx and read get_allocation x --- subfile-exchange/src/subfile_client/mod.rs | 1 - .../src/transaction_manager/mod.rs | 12 +- .../src/transaction_manager/staking.rs | 260 +++++++++++------- subfile-exchange/src/util.rs | 36 +-- 4 files changed, 160 insertions(+), 149 deletions(-) diff --git a/subfile-exchange/src/subfile_client/mod.rs b/subfile-exchange/src/subfile_client/mod.rs index 98d3d0c..753d53a 100644 --- a/subfile-exchange/src/subfile_client/mod.rs +++ b/subfile-exchange/src/subfile_client/mod.rs @@ -73,7 +73,6 @@ impl SubfileDownloader { ) .await; - SubfileDownloader { http_client: reqwest::Client::new(), ipfs_hash: args.ipfs_hash, diff --git a/subfile-exchange/src/transaction_manager/mod.rs b/subfile-exchange/src/transaction_manager/mod.rs index 4e3ed9c..ba3d906 100644 --- a/subfile-exchange/src/transaction_manager/mod.rs +++ b/subfile-exchange/src/transaction_manager/mod.rs @@ -1,4 +1,3 @@ -use ethers::contract::Contract; use ethers::prelude::*; use ethers_core::k256::ecdsa::SigningKey; @@ -11,8 +10,7 @@ use std::sync::Arc; pub mod staking; /// Contracts: (contract name, contract object) -pub type NetworkContracts = - HashMap; +pub type NetworkContracts = HashMap; /// Contracts: (contract name, contract address) pub type ContractAddresses = HashMap; /// Client with provider endpoint and a wallet @@ -33,6 +31,7 @@ impl TransactionManager { ) -> Result { let provider = Provider::::try_from(provider_url)?; let chain_id = provider.get_chainid().await?; + let wallet = wallet.with_chain_id(provider.get_chainid().await.unwrap().as_u64()); let client = Arc::new(SignerMiddleware::new(provider, wallet)); // Access contracts for the specified chain_id @@ -42,10 +41,9 @@ impl TransactionManager { // Initiate contract instances let contracts = NetworkContracts::new(); // Test reading the function - let value = staking::controller(&client, *contract_addresses.get("L2Staking").unwrap()).await?; - println!("controller value: {:#?}", value); - let value = staking::allocate(&client, *contract_addresses.get("L2Staking").unwrap()).await?; - println!("allocate value: {:#?}", value); + let value = + staking::controller(&client, *contract_addresses.get("L2Staking").unwrap()).await?; + tracing::debug!("test read - controller value: {:#?}", value); Ok(TransactionManager { client, contracts }) } } diff --git a/subfile-exchange/src/transaction_manager/staking.rs b/subfile-exchange/src/transaction_manager/staking.rs index 5c8b356..fd36a31 100644 --- a/subfile-exchange/src/transaction_manager/staking.rs +++ b/subfile-exchange/src/transaction_manager/staking.rs @@ -2,10 +2,10 @@ use ethers::contract::{abigen, Contract}; use ethers::middleware::SignerMiddleware; use ethers::providers::{Http, Provider}; use ethers::signers::coins_bip39::English; -use ethers::signers::{Wallet, Signer}; -use ethers_core::types::{H160, U256, H256, Address, Bytes}; -use ethers_core::utils::{keccak256, hash_message}; -use ethers_core::{k256::ecdsa::SigningKey, utils::to_checksum}; +use ethers::signers::{Signer, Wallet}; +use ethers_core::k256::ecdsa::SigningKey; +use ethers_core::types::{Bytes, H160, U256}; +use ethers_core::utils::keccak256; use hdwallet::{DefaultKeyChain, ExtendedPrivKey}; use std::collections::HashMap; @@ -14,7 +14,7 @@ use std::sync::Arc; use crate::errors::Error; use crate::transaction_manager::coins_bip39::Mnemonic; -use crate::util::{derive_key_pair, UDecimal18, build_wallet}; +use crate::util::{build_wallet, derive_key_pair}; pub type NetworkContracts = HashMap, Wallet>>>; @@ -42,129 +42,144 @@ pub async fn controller(client: &ContractClient, contract_addr: H160) -> Result< } /// call staking contract allocate function -// pub async fn allocate(client: &ContractClient, contract_addr: H160, deployment_hash: String) -> Result<(), Error> { -pub async fn allocate(client: &ContractClient, contract_addr: H160) -> Result { - tracing::info!("in allocate"); - let contract = L2Staking::new(contract_addr, Arc::new(client.clone())); - let value = controller(client, contract_addr).await; - tracing::info!(value = tracing::field::debug(&value), "controller helper"); - println!("allocate value"); - +pub async fn allocate( + client: &ContractClient, + contract_addr: H160, + deployment: &str, +) -> Result { //TODO: Start with hardcoding, later add field indexer address to TX manager, tokens to fn params + let contract = L2Staking::new(contract_addr, Arc::new(client.clone())); let mnemonic = "culture alcohol unfair success pupil economy stomach dignity beyond absurd client latin"; - let epoch: u64 = 1016; - let deployment = "QmeaPp764FjQjPB66M9ijmQKmLhwBpHQhA7dEbH2FA1j3v"; + let epoch: u64 = 1030; let existing_ids: Vec = vec![]; - - let (allocation_signer, allocation_id) = unique_allocation_id(mnemonic, epoch, deployment, &existing_ids)?; - println!("allocation signer and id {:#?}, {:#?}", allocation_signer, allocation_id); - let decoded_bytes = bs58::decode(deployment) - .into_vec() - .map_err(|e| Error::InvalidConfig(format!("Failed to decode Qm Hash to bytes: {}", e.to_string())))?; - // Truncate or pad the decoded bytes to fit into 32 bytes - let mut deployment_byte32 = [0u8; 32]; - let len = std::cmp::min(decoded_bytes.len(), 32); - deployment_byte32[..len].copy_from_slice(&decoded_bytes[..len]); - - let tokens = U256::from(1); + let tokens = U256::from(100000); let metadata: [u8; 32] = [0; 32]; -println!("allocate metadata"); + + let (allocation_signer, allocation_id) = + unique_allocation_id(mnemonic, epoch, deployment, &existing_ids)?; + let deployment_byte32 = ipfs_hash_to_bytes(deployment)?; let indexer_address = build_wallet(mnemonic)?.address(); let proof = allocation_id_proof(&allocation_signer, indexer_address, allocation_id).await?; -println!("allocate proof: {:#?}", proof); - let value = contract - .allocate(deployment_byte32, tokens, allocation_id, metadata, proof) - .call() - .await; - // .map_err(|e| Error::ContractError(e.to_string()))?; - println!("allocate call result"); - tracing::info!(value = tracing::field::debug(&value), "allocate call value"); -println!("allocate result: {:#?}", value); - - let value = - get_allocation(&client, contract_addr, allocation_id) + + tracing::info!( + dep_bytes = tracing::field::debug(&deployment_byte32), + tokens = tracing::field::debug(&tokens), + allocation_id = tracing::field::debug(&allocation_id), + metadata = tracing::field::debug(&metadata), + proof = tracing::field::debug(&proof), + "allocate params", + ); + + let populated_tx = contract.allocate(deployment_byte32, tokens, allocation_id, metadata, proof); + let estimated_gas = populated_tx + .estimate_gas() .await .map_err(|e| Error::ContractError(e.to_string()))?; - println!("get allocate: {:#?}", value); - tracing::info!(value = tracing::field::debug(&value), "allocate call value"); + tracing::debug!( + estimated_gas = tracing::field::debug(&estimated_gas), + "estimate gas" + ); - Ok(allocation_id) -} + // Attempt to send the populated tx with estimated gas, can later add a slippage + let tx_result = populated_tx + .gas(estimated_gas) + .send() + .await + .map_err(|e| { + // let encoded_error = &e.to_string()[2..]; + // let error_message_hex = &encoded_error[8 + 64..]; + // let bytes = hex::decode(error_message_hex).unwrap(); + // let message = String::from_utf8(bytes).unwrap(); + + Error::ContractError(e.to_string()) + })? + .await + .map_err(|e| Error::ContractError(e.to_string()))?; + // .map_err(|e| Error::ContractError(e.to_string()))?; + tracing::debug!( + value = tracing::field::debug(&tx_result), + "allocate call result" + ); + // Can call to double check but probably not necessary + let value = get_allocation(client, contract_addr, allocation_id) + .await + .map_err(|e| Error::ContractError(e.to_string()))?; + tracing::trace!( + value = tracing::field::debug(&value), + "get_allocation call result" + ); + + Ok(value) +} /// call staking contract allocate function -// pub async fn allocate(client: &ContractClient, contract_addr: H160, deployment_hash: String) -> Result<(), Error> { -pub async fn get_allocation(client: &ContractClient, contract_addr: H160, allocation_id: H160) -> Result { - println!("contract addr: {:#?}", contract_addr); +pub async fn get_allocation( + client: &ContractClient, + contract_addr: H160, + allocation_id: H160, +) -> Result { let contract = L2Staking::new(contract_addr, Arc::new(client.clone())); let value = contract .get_allocation(allocation_id) .call() .await .map_err(|e| Error::ContractError(e.to_string()))?; - println!("get allocate: {:#?}", value); tracing::info!(value = tracing::field::debug(&value), "allocate call value"); Ok(value) } -/// create proof for allocation id +/// create packed keccak hash for allocation id as proof pub async fn allocation_id_proof( signer: &Wallet, indexer_address: H160, - allocation_id: H160 , + allocation_id: H160, ) -> Result { - // Convert addresses to their checksum format (EIP-55) - let indexer_address = to_checksum(&Address::from(indexer_address), None); - let allocation_id = to_checksum(&Address::from(allocation_id), None); - - // Hash the addresses using Keccak-256 - let message_hash = keccak256(format!("{}{}", indexer_address, allocation_id)); - + // Convert the raw bytes to hex and concatenate + let combined_hex = format!( + "{}{}", + hex::encode(indexer_address.as_bytes()), + hex::encode(allocation_id.as_bytes()) + ); + let bytes = + hex::decode(combined_hex.clone()).map_err(|e| Error::InvalidConfig(e.to_string()))?; + // Compute the Keccak-256 hash of the bytes + let message_hash = keccak256(bytes); // Sign the message hash - // let signature = signer.sign_hash(H256::from(&hash_message(&message_hash))).map_err(|e| Error::WalletError(e))?; - - let signature = signer.sign_hash(hash_message(&message_hash)).map_err(|e| Error::WalletError(e))?; - - - - - + let signature = signer + .sign_message(message_hash) + .await + .map_err(Error::WalletError)?; - // // Wrap the message in Ethereum's specific message format - // let eth_message_hash = hash_message(&message_hash); + Ok(Bytes::from(signature.to_vec())) +} - // Sign the message hash - // let signature = signer.sign_hash(H256::from(eth_message_hash), true).await?; +/// Convert IPFS hash to byte representation +fn ipfs_hash_to_bytes(deployment: &str) -> Result<[u8; 32], Error> { + let decoded_bytes = &bs58::decode(deployment) + .into_vec() + .map_err(|e| Error::InvalidConfig(format!("Failed to decode Qm Hash to bytes: {}", e)))? + [2..]; + let mut deployment_byte32 = [0u8; 32]; + let len = std::cmp::min(decoded_bytes.len(), 32); + deployment_byte32[..len].copy_from_slice(&decoded_bytes[..len]); + let _hex_string = format!("0x{}", hex::encode(deployment_byte32)); + let decoded_bytes = bs58::decode(deployment) + .into_vec() + .map_err(|e| Error::InvalidConfig(format!("Failed to decode Qm Hash to bytes: {}", e)))?; + if decoded_bytes.len() - 2 != 32 { + return Err(Error::InvalidConfig( + "Decoded bytes (minus the first two) are not 32 bytes long".into(), + )); + } - Ok(Bytes::from(signature.to_vec())) + Ok(deployment_byte32) } - -// export const allocationIdProof = ( -// signer: Signer, -// indexerAddress: string, -// allocationId: string, -// ): Promise => { -// const messageHash = utils.solidityKeccak256( -// ['address', 'address'], -// [indexerAddress, allocationId], -// ) -// const messageHashBytes = utils.arrayify(messageHash) -// return signer.signMessage(messageHashBytes) -// } -// // logger.debug('Obtain a unique Allocation ID') -// // const { allocationSigner, allocationId } = uniqueAllocationID( -// // this.network.transactionManager.wallet.mnemonic.phrase, -// // context.currentEpoch.toNumber(), -// // deployment, -// // context.activeAllocations.map((allocation) => allocation.id), -// // ) - - -// Function to find a unique allocation ID +// Find a unique allocation ID for the indexer, epoch, and deployment // take wallet mnemonic and derive address // take epoch and deployment for a child signer // filter from existing ids to ensure uniqueness @@ -208,7 +223,7 @@ mod tests { let deployment = "QmeaPp764FjQjPB66M9ijmQKmLhwBpHQhA7dEbH2FA1j3v"; let mut existing_ids: Vec = vec![]; - for i in 0..100 { + for _i in 0..100 { let (_, allocation_id) = unique_allocation_id(indexer_mnemonic, epoch, deployment, &existing_ids).unwrap(); existing_ids.push(allocation_id); @@ -220,30 +235,63 @@ mod tests { assert!(uniquesness.is_err()); } + #[test] + fn test_subgraph_deployment_repr() { + let ipfs_hash = "QmWAsLViTdCbs9zbejzmRndpZpNXU97CzeLJwdZKuvCUdF"; + let expected = "745bf7153ea3c7d2cf7985042813945cd7797afabfbcf432eb0718a9dbead00a"; + assert!(hex::encode(ipfs_hash_to_bytes(ipfs_hash).unwrap()) == expected); + } + #[tokio::test] - async fn test_allocate() { + async fn test_allocation_id_proof() { + let mnemonic = "sheriff obscure trick beauty army fat wink legal flee leader section suit"; + let epoch: u64 = 1024; + let deployment = "QmWAsLViTdCbs9zbejzmRndpZpNXU97CzeLJwdZKuvCUdF"; + + let indexer_address = build_wallet(mnemonic).unwrap().address(); + let existing_ids: Vec = vec![]; + let (allocation_signer, allocation_id) = + unique_allocation_id(mnemonic, epoch, deployment, &existing_ids).unwrap(); + + let id = format!("{:#?}", allocation_id); + assert_eq!(id, "0x1cf8e1a42860cf19606da7e358f23d265b9ba6aa"); + + let proof = allocation_id_proof(&allocation_signer, indexer_address, allocation_id) + .await + .unwrap(); + assert!(proof.to_string() == "0x1457a0a1a6a0531181bc31e8ed4c1dc9129f4dbeb8866b4213045ca275d14a757639ee5886c6778555d0b516c5776bbb919efc9cfbafd6f1017253e48b3d76301c") + //With a secret mnemonic + // assert_eq!(id, "0x6a39615c9f35ef68b4a99584c0566a1fcdd67d0a"); + // assert!(proof.to_string() == "0x7f47ee3a4e614c43352f8920e81371b0aa2298bb99ed6fc8eb4ed54f0bb1954c1463abd7a86a21185671f9a827c5ecaa1f509320cf9ee53f77338475018d1d931c") + } + #[tokio::test] + #[ignore] + async fn test_allocate() { let indexer_mnemonic = - "sheriff obscure trick beauty army fat wink legal flee leader section suit"; + "sheriff obscure trick beauty army fat wink legal flee leader section suit"; + let _deployment = "QmWAsLViTdCbs9zbejzmRndpZpNXU97CzeLJwdZKuvCUdF"; println!("start"); - let provider = Provider::::try_from("https://arbitrum-goerli.infura.io/v3/dc1a550f824a4c6aa428a3376f983145").unwrap(); - println!("start provider"); + let provider = Provider::::try_from( + "https://arbitrum-goerli.infura.io/v3/dc1a550f824a4c6aa428a3376f983145", + ) + .unwrap(); + println!("start provider"); let wallet = build_wallet(indexer_mnemonic).expect("Mnemonic build wallet"); - println!("start wallet"); + println!("start wallet"); let client = SignerMiddleware::new(provider, wallet); - println!("start client"); + println!("start client"); // Access contracts for the specified chain_id - let contract_addresses = - network_contract_addresses("../addresses.json", &"421614".to_string()).unwrap(); + let contract_addresses = network_contract_addresses("../addresses.json", "421614").unwrap(); println!("start contract address"); let deployment = "QmeaPp764FjQjPB66M9ijmQKmLhwBpHQhA7dEbH2FA1j3v"; - let staking_addr = contract_addresses.get("L2Staking").unwrap(); - println!("start l2 sataking address"); + let staking_addr = contract_addresses.get("L2Staking").unwrap(); + println!("start l2 sataking address"); - let res = allocate(&client, *staking_addr).await; - println!("finish: {:#?}", res); + let res = allocate(&client, *staking_addr, deployment).await; + println!("finish: {:#?}", res); assert!(res.is_ok()) } diff --git a/subfile-exchange/src/util.rs b/subfile-exchange/src/util.rs index 839cb05..36ca567 100644 --- a/subfile-exchange/src/util.rs +++ b/subfile-exchange/src/util.rs @@ -1,7 +1,6 @@ use alloy_primitives::U256; use ethers::signers::{coins_bip39::English, LocalWallet, MnemonicBuilder, Signer, Wallet}; -use ethers_core::utils::keccak256; -use ethers_core::{k256::ecdsa::SigningKey, utils::to_checksum}; +use ethers_core::k256::ecdsa::SigningKey; use ethers_core::types::H160; use hdwallet::{ChainPath, DefaultKeyChain, KeyChain}; use std::{fmt, iter, str}; @@ -58,39 +57,6 @@ pub fn derive_key_pair( Ok((private_key, wallet.address())) } -// /// create proof for allocation id -// pub async fn allocation_id_proof( -// signer: &Wallet, -// indexer_address: &str, -// allocation_id: &str, -// ) -> Result> { -// // Convert addresses to their checksum format (EIP-55) -// let indexer_address = to_checksum(&Address::from_str(indexer_address)?, None); -// let allocation_id = to_checksum(&Address::from_str(allocation_id)?, None); - -// // Hash the addresses using Keccak-256 -// let message_hash = keccak256(format!("{}{}", indexer_address, allocation_id)); - -// // Sign the message hash -// let signature = signer.sign_hash(H256::from_slice(&message_hash), true).await?; - -// Ok(signature.to_string()) -// } - - -// export const allocationIdProof = ( -// signer: Signer, -// indexerAddress: string, -// allocationId: string, -// ): Promise => { -// const messageHash = utils.solidityKeccak256( -// ['address', 'address'], -// [indexerAddress, allocationId], -// ) -// const messageHashBytes = utils.arrayify(messageHash) -// return signer.signMessage(messageHashBytes) -// } - /* Token unit and formatting */ const ONE_18: u128 = 1_000_000_000_000_000_000;