Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(cosigner): removing sending user operation to the bundler, adding call to the sendUserOp endpoint #754

Merged
merged 1 commit into from
Aug 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 39 additions & 56 deletions src/handlers/sessions/cosign.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ use {
state::AppState,
storage::irn::OperationType,
utils::crypto::{
abi_encode_two_bytes_arrays, call_get_signature, call_get_user_op_hash,
disassemble_caip10, pack_signature, send_user_operation_to_bundler, to_eip191_message,
CaipNamespaces, ChainId,
abi_encode_two_bytes_arrays, call_get_user_op_hash, disassemble_caip10,
is_address_valid, pack_signature, to_eip191_message, CaipNamespaces, ChainId,
UserOperation,
},
},
axum::{
Expand All @@ -24,11 +24,11 @@ use {
},
serde::{Deserialize, Serialize},
std::{sync::Arc, time::SystemTime},
tracing::info,
wc::future::FutureExt,
};

const ENTRY_POINT_V07_CONTRACT_ADDRESS: &str = "0x0000000071727De22E5E9d8BAf0edAc6f37da032";
const SEND_USER_OP_ENDPOINT: &str = "https://react-wallet.walletconnect.com/api/sendUserOp";

/// Co-sign response schema
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
Expand All @@ -37,6 +37,20 @@ pub struct CoSignResponse {
user_operation_tx_hash: String,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SendUserOpRequest {
pub chain_id: usize,
pub user_op: UserOperation,
pub permissions_context: Option<String>,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SendUserOpResponse {
pub receipt: String,
}

pub async fn handler(
state: State<Arc<AppState>>,
address: Path<String>,
Expand All @@ -58,6 +72,9 @@ async fn handler_internal(
if namespace != CaipNamespaces::Eip155 {
return Err(RpcError::UnsupportedNamespace(namespace));
}
if !is_address_valid(&address, &namespace) {
return Err(RpcError::InvalidAddress);
}

// ChainID validation
let chain_id_uint = chain_id
Expand All @@ -67,9 +84,6 @@ async fn handler_internal(
return Err(RpcError::UnsupportedChain(chain_id.clone()));
}

let h160_address = address
.parse::<H160>()
.map_err(|_| RpcError::InvalidAddress)?;
let chain_id_caip2 = format!("{}:{}", namespace, chain_id);
let mut user_op = request_payload.user_op.clone();

Expand Down Expand Up @@ -117,20 +131,7 @@ async fn handler_internal(
.context
.clone()
.ok_or_else(|| RpcError::PermissionContextNotUpdated(request_payload.pci.clone()))?;
let permission_context = hex::decode(
permission_context_item
.context
.permissions_context
.clone()
.trim_start_matches("0x"),
)
.map_err(|e| {
RpcError::WrongHexFormat(format!(
"error:{:?} permission_context:{}",
e.to_string(),
permission_context_item.context.permissions_context
))
})?;
let permission_context = permission_context_item.context.permissions_context.clone();

// Sign the userOp hash with the permission signing key
let signing_key_bytes = BASE64_STANDARD
Expand All @@ -152,39 +153,21 @@ async fn handler_internal(
// Update the userOp with the signature
user_op.signature = concatenated_signature;

// Get the Signature from the UserOpBuilder
let user_op_builder_contract_address = permission_context_item
.context
.signer_data
.user_op_builder
.parse::<H160>()
.map_err(|_| RpcError::InvalidAddress)?;
let get_signature_result = call_get_signature(
rpc_project_id,
&chain_id_caip2,
user_op_builder_contract_address,
h160_address,
user_op.clone(),
permission_context.into(),
)
.await?;

// Todo: remove this debug line before production stage
info!("UserOpPacked final JSON: {:?}", serde_json::json!(user_op));

// Update the userOp with the signature,
// send the userOperation to the bundler and get the receipt
user_op.signature = get_signature_result;
let user_operation_tx_hash = send_user_operation_to_bundler(
&user_op,
&chain_id,
ENTRY_POINT_V07_CONTRACT_ADDRESS,
state.providers.bundler_ops_provider.as_ref(),
)
.await?;

Ok(Json(CoSignResponse {
user_operation_tx_hash,
})
.into_response())
// Make a POST request to the sendUserOp endpoint
let send_user_op_request = SendUserOpRequest {
chain_id: chain_id_uint as usize,
user_op: user_op.clone(),
permissions_context: Some(permission_context),
};
let http_client = state.http_client.clone();
let send_user_op_call_result = http_client
.post(SEND_USER_OP_ENDPOINT)
.json(&send_user_op_request)
.send()
.await?;
let result = send_user_op_call_result
.json::<SendUserOpResponse>()
.await?;

Ok(Json(result).into_response())
}
156 changes: 1 addition & 155 deletions src/utils/crypto.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
use {
crate::{
analytics::MessageSource,
error::RpcError,
providers::{BundlerOpsProvider, SupportedBundlerOps},
},
crate::{analytics::MessageSource, error::RpcError},
alloy_primitives::Address,
base64::prelude::*,
bs58,
Expand Down Expand Up @@ -464,59 +460,6 @@ pub async fn call_get_user_op_hash(
Ok(hash)
}

/// Call getSignature on ERC-7579 userOperationBuilder contract
#[tracing::instrument(level = "debug")]
pub async fn call_get_signature(
rpc_project_id: &str,
chain_id: &str,
contract_address: H160,
smart_account_address: H160,
user_operation: UserOperation,
context: Bytes,
) -> Result<Bytes, CryptoUitlsError> {
abigen!(
Safe7579UserOperationBuilder,
r#"[
struct v07UserOperation { address sender; uint256 nonce; bytes initCode; bytes callData; bytes32 accountGasLimits; uint256 preVerificationGas; bytes32 gasFees; bytes paymasterAndData; bytes signature}
function formatSignature(address smartAccount, v07UserOperation calldata userOperation, bytes calldata context) public view returns (bytes)
]"#,
);

let provider = Provider::<Http>::try_from(format!(
"https://rpc.walletconnect.com/v1?chainId={}&projectId={}",
chain_id, rpc_project_id
))
.map_err(|e| CryptoUitlsError::RpcUrlParseError(format!("Failed to parse RPC url: {}", e)))?;
let provider = Arc::new(provider);
let contract = Safe7579UserOperationBuilder::new(contract_address, provider);

let packed_user_op = user_operation.get_packed();
let user_op = v07UserOperation {
sender: packed_user_op.sender,
nonce: packed_user_op.nonce,
init_code: packed_user_op.init_code,
call_data: packed_user_op.call_data,
account_gas_limits: packed_user_op.account_gas_limits.into(),
pre_verification_gas: packed_user_op.pre_verification_gas,
gas_fees: packed_user_op.gas_fees.into(),
paymaster_and_data: packed_user_op.paymaster_and_data,
signature: packed_user_op.signature,
};

let signature = contract
.format_signature(smart_account_address, user_op, context)
.call()
.await
.map_err(|e| {
CryptoUitlsError::ContractCallError(format!(
"Failed to call formatSignature in Safe7579UserOperationBuilder contract: {}",
e
))
})?;

Ok(signature)
}

/// Convert EVM chain ID to coin type ENSIP-11
#[tracing::instrument(level = "debug")]
pub fn convert_evm_chain_id_to_coin_type(chain_id: u32) -> u32 {
Expand Down Expand Up @@ -759,38 +702,6 @@ pub fn convert_token_amount_to_value(balance: U256, price: f64, decimals: u32) -
balance_f64 * price
}

/// Function to send UserOperation to the bundler and return the user operation tx hash
#[tracing::instrument(skip(bundler), level = "debug")]
pub async fn send_user_operation_to_bundler(
user_op: &UserOperation,
chain_id: &str,
entry_point: &str,
bundler: &dyn BundlerOpsProvider,
) -> Result<String, RpcError> {
// Send the UserOperation to the bundler
let response = bundler
.bundler_rpc_call(
chain_id,
1,
&JSON_RPC_VERSION[..],
&SupportedBundlerOps::EthSendUserOperation,
serde_json::json!([user_op.clone(), entry_point]),
)
.await?;

// Get the transaction hash from the response
let tx_hash = response
.get("result")
.ok_or(CryptoUitlsError::NoResultInRpcResponse)?;

let tx_hash_string = tx_hash
.as_str()
.ok_or(CryptoUitlsError::BundlerRpcResponseError(
"Error converting tx hash result into string".to_string(),
))?;

Ok(tx_hash_string.into())
}
#[cfg(test)]
mod tests {
use {
Expand Down Expand Up @@ -1022,69 +933,4 @@ mod tests {
"a5e787e98d421a0e62b2457e525bc8a4b1bde14cc71d48c0cf139b0b1fadb1cc"
);
}

/// Creating a dummy context for the UserOperationBuilder contract
fn create_dummy_context_for_op_builder() -> Bytes {
let validator_address = hex::decode("1234567890123456789012345678901234567890").unwrap();
// Assuming mode is SmartSessionMode.USE (1)
let mode = vec![1];
let signer_id =
hex::decode("abcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdef")
.unwrap();

let mut context = Vec::new();
context.extend_from_slice(&validator_address);
context.extend_from_slice(&mode);
context.extend_from_slice(&signer_id);

Bytes::from(context)
}

// Ignoring this test until the RPC project ID is provided by the CI workflow
// The test can be run manually by providing the project ID
#[ignore]
#[tokio::test]
async fn test_call_get_signature() {
let rpc_project_id = ""; // Fill the project ID
let chain_id = "eip155:11155111";
// UserOpBuilder contract address
let contract_address = "0xCd67aCD5d31969e2c368d6A1cfE1911932C744b1"
.parse::<H160>()
.unwrap();
// Dummy smart account address
let sa_address = "0x1234567890123456789012345678901234567890"
.parse::<H160>()
.unwrap();
let user_operation = UserOperation {
sender: sa_address,
nonce: U256::zero(),
call_data: Bytes::from(vec![0x04, 0x05, 0x06]),
call_gas_limit: U128::zero(),
verification_gas_limit: U128::zero(),
pre_verification_gas: U256::zero(),
max_fee_per_gas: U128::zero(),
max_priority_fee_per_gas: U128::zero(),
signature: Bytes::from(vec![0x0a, 0x0b, 0x0c]),
factory: None,
factory_data: None,
paymaster: None,
paymaster_data: None,
paymaster_post_op_gas_limit: None,
paymaster_verification_gas_limit: None,
};

let result = call_get_signature(
rpc_project_id,
chain_id,
contract_address,
sa_address,
user_operation,
create_dummy_context_for_op_builder(),
)
.await
.unwrap();

// Expect an empty signature because of dummy parameters
assert_eq!(hex::encode(result), "");
}
}
Loading