diff --git a/Cargo.toml b/Cargo.toml index 33139f2..b9fe012 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,8 +18,9 @@ alloy = { version = "0.2", features = [ "rpc-types", "transports", "signers", + "transport-http", "signer-local", - "sol-types" + "sol-types", ]} log = "0.4" thiserror = "1.0.63" diff --git a/build.rs b/build.rs index 6723015..4feb335 100644 --- a/build.rs +++ b/build.rs @@ -77,6 +77,7 @@ const ERC6492_FILE: &str = "forge/out/Erc6492.sol/ValidateSigOffchain.json"; const ERC6492_BYTECODE_FILE: &str = "forge/out/Erc6492.sol/ValidateSigOffchain.bytecode"; const ERC1271_MOCK_FILE: &str = "forge/out/Erc1271Mock.sol/Erc1271Mock.json"; const ERC1271_MOCK_BYTECODE_FILE: &str = "forge/out/Erc1271Mock.sol/Erc1271Mock.bytecode"; + fn extract_bytecodes() { extract_bytecode( &format_foundry_dir(ERC6492_FILE), diff --git a/contracts/DeployedSmartWalletAbi.json b/contracts/DeployedSmartWalletAbi.json new file mode 100644 index 0000000..4d351ab --- /dev/null +++ b/contracts/DeployedSmartWalletAbi.json @@ -0,0 +1,758 @@ +[{ + "inputs": [], + "stateMutability": "nonpayable", + "type": "constructor" + }, { + "inputs": [ + { + "internalType": "bytes", + "name": "owner", + "type": "bytes" + } + ], + "name": "AlreadyOwner", + "type": "error" + }, { + "inputs": [], + "name": "Initialized", + "type": "error" + }, { + "inputs": [ + { + "internalType": "bytes", + "name": "owner", + "type": "bytes" + } + ], + "name": "InvalidEthereumAddressOwner", + "type": "error" + }, { + "inputs": [ + { + "internalType": "uint256", + "name": "key", + "type": "uint256" + } + ], + "name": "InvalidNonceKey", + "type": "error" + }, { + "inputs": [ + { + "internalType": "bytes", + "name": "owner", + "type": "bytes" + } + ], + "name": "InvalidOwnerBytesLength", + "type": "error" + }, { + "inputs": [], + "name": "LastOwner", + "type": "error" + }, { + "inputs": [ + { + "internalType": "uint256", + "name": "index", + "type": "uint256" + } + ], + "name": "NoOwnerAtIndex", + "type": "error" + }, { + "inputs": [ + { + "internalType": "uint256", + "name": "ownersRemaining", + "type": "uint256" + } + ], + "name": "NotLastOwner", + "type": "error" + }, { + "inputs": [ + { + "internalType": "bytes4", + "name": "selector", + "type": "bytes4" + } + ], + "name": "SelectorNotAllowed", + "type": "error" + }, { + "inputs": [], + "name": "Unauthorized", + "type": "error" + }, { + "inputs": [], + "name": "UnauthorizedCallContext", + "type": "error" + }, { + "inputs": [], + "name": "UpgradeFailed", + "type": "error" + }, { + "inputs": [ + { + "internalType": "uint256", + "name": "index", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "expectedOwner", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "actualOwner", + "type": "bytes" + } + ], + "name": "WrongOwnerAtIndex", + "type": "error" + }, { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "index", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "owner", + "type": "bytes" + } + ], + "name": "AddOwner", + "type": "event" + }, { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "index", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "owner", + "type": "bytes" + } + ], + "name": "RemoveOwner", + "type": "event" + }, { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "implementation", + "type": "address" + } + ], + "name": "Upgraded", + "type": "event" + }, { + "stateMutability": "payable", + "type": "fallback" + }, { + "inputs": [], + "name": "REPLAYABLE_NONCE_KEY", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "addOwnerAddress", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, { + "inputs": [ + { + "internalType": "bytes32", + "name": "x", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "y", + "type": "bytes32" + } + ], + "name": "addOwnerPublicKey", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, { + "inputs": [ + { + "internalType": "bytes4", + "name": "functionSelector", + "type": "bytes4" + } + ], + "name": "canSkipChainIdValidation", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "pure", + "type": "function" + }, { + "inputs": [], + "name": "domainSeparator", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, { + "inputs": [], + "name": "eip712Domain", + "outputs": [ + { + "internalType": "bytes1", + "name": "fields", + "type": "bytes1" + }, + { + "internalType": "string", + "name": "name", + "type": "string" + }, + { + "internalType": "string", + "name": "version", + "type": "string" + }, + { + "internalType": "uint256", + "name": "chainId", + "type": "uint256" + }, + { + "internalType": "address", + "name": "verifyingContract", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "salt", + "type": "bytes32" + }, + { + "internalType": "uint256[]", + "name": "extensions", + "type": "uint256[]" + } + ], + "stateMutability": "view", + "type": "function" + }, { + "inputs": [], + "name": "entryPoint", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, { + "inputs": [ + { + "internalType": "address", + "name": "target", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "execute", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, { + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "target", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "internalType": "struct CoinbaseSmartWallet.Call[]", + "name": "calls", + "type": "tuple[]" + } + ], + "name": "executeBatch", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, { + "inputs": [ + { + "internalType": "bytes[]", + "name": "calls", + "type": "bytes[]" + } + ], + "name": "executeWithoutChainIdValidation", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, { + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "nonce", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "initCode", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "callData", + "type": "bytes" + }, + { + "internalType": "uint256", + "name": "callGasLimit", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "verificationGasLimit", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "preVerificationGas", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "maxFeePerGas", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "maxPriorityFeePerGas", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "paymasterAndData", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "signature", + "type": "bytes" + } + ], + "internalType": "struct UserOperation", + "name": "userOp", + "type": "tuple" + } + ], + "name": "getUserOpHashWithoutChainId", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, { + "inputs": [], + "name": "implementation", + "outputs": [ + { + "internalType": "address", + "name": "$", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, { + "inputs": [ + { + "internalType": "bytes[]", + "name": "owners", + "type": "bytes[]" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "isOwnerAddress", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, { + "inputs": [ + { + "internalType": "bytes", + "name": "account", + "type": "bytes" + } + ], + "name": "isOwnerBytes", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, { + "inputs": [ + { + "internalType": "bytes32", + "name": "x", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "y", + "type": "bytes32" + } + ], + "name": "isOwnerPublicKey", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, { + "inputs": [ + { + "internalType": "bytes32", + "name": "hash", + "type": "bytes32" + }, + { + "internalType": "bytes", + "name": "signature", + "type": "bytes" + } + ], + "name": "isValidSignature", + "outputs": [ + { + "internalType": "bytes4", + "name": "result", + "type": "bytes4" + } + ], + "stateMutability": "view", + "type": "function" + }, { + "inputs": [], + "name": "nextOwnerIndex", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, { + "inputs": [ + { + "internalType": "uint256", + "name": "index", + "type": "uint256" + } + ], + "name": "ownerAtIndex", + "outputs": [ + { + "internalType": "bytes", + "name": "", + "type": "bytes" + } + ], + "stateMutability": "view", + "type": "function" + }, { + "inputs": [], + "name": "ownerCount", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, { + "inputs": [], + "name": "proxiableUUID", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, { + "inputs": [ + { + "internalType": "uint256", + "name": "index", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "owner", + "type": "bytes" + } + ], + "name": "removeLastOwner", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, { + "inputs": [ + { + "internalType": "uint256", + "name": "index", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "owner", + "type": "bytes" + } + ], + "name": "removeOwnerAtIndex", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, { + "inputs": [], + "name": "removedOwnersCount", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, { + "inputs": [ + { + "internalType": "bytes32", + "name": "hash", + "type": "bytes32" + } + ], + "name": "replaySafeHash", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, { + "inputs": [ + { + "internalType": "address", + "name": "newImplementation", + "type": "address" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "upgradeToAndCall", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, { + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "nonce", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "initCode", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "callData", + "type": "bytes" + }, + { + "internalType": "uint256", + "name": "callGasLimit", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "verificationGasLimit", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "preVerificationGas", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "maxFeePerGas", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "maxPriorityFeePerGas", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "paymasterAndData", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "signature", + "type": "bytes" + } + ], + "internalType": "struct UserOperation", + "name": "userOp", + "type": "tuple" + }, + { + "internalType": "bytes32", + "name": "userOpHash", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "missingAccountFunds", + "type": "uint256" + } + ], + "name": "validateUserOp", + "outputs": [ + { + "internalType": "uint256", + "name": "validationData", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, { + "stateMutability": "payable", + "type": "receive" + }] \ No newline at end of file diff --git a/contracts/Erc1271Mock.sol b/contracts/Erc1271Mock.sol index a79f0dc..b8cc90b 100644 --- a/contracts/Erc1271Mock.sol +++ b/contracts/Erc1271Mock.sol @@ -78,4 +78,4 @@ contract Erc1271Mock { return signer; } -} +} \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 033220e..2e90314 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,7 +5,7 @@ use { rpc::types::{TransactionInput, TransactionRequest}, signers::k256::ecdsa::{RecoveryId, Signature, VerifyingKey}, sol, - sol_types::SolConstructor, + sol_types::{SolCall, SolConstructor}, transports::{Transport, TransportErrorKind}, }, log::error, @@ -14,7 +14,6 @@ use { /// The expected result for a successful signature verification. const SUCCESS_RESULT: u8 = 0x01; -/// The magic bytes used to detect ERC-6492 signatures. const ERC6492_DETECTION_SUFFIX: [u8; 32] = [ 0x64, 0x92, 0x64, 0x92, 0x64, 0x92, 0x64, 0x92, 0x64, 0x92, 0x64, 0x92, 0x64, 0x92, 0x64, 0x92, 0x64, 0x92, 0x64, 0x92, 0x64, 0x92, 0x64, 0x92, 0x64, 0x92, 0x64, 0x92, 0x64, 0x92, 0x64, 0x92, @@ -25,6 +24,11 @@ const VALIDATE_SIG_OFFCHAIN_BYTECODE: &[u8] = include_bytes!(concat!( "/../../../../.foundry/forge/out/Erc6492.sol/ValidateSigOffchain.bytecode" )); +const ERC1271_MOCK_BYTECODE: &[u8] = include_bytes!(concat!( + env!("OUT_DIR"), + "/../../../../.foundry/forge/out/Erc1271Mock.sol/Erc1271Mock.bytecode" +)); + sol! { contract ValidateSigOffchain { constructor (address _signer, bytes32 _hash, bytes memory _signature); @@ -42,6 +46,11 @@ sol! { contract Create2 { function computeAddress(bytes32 salt, bytes32 bytecodeHash) external view returns (address) {} } + + contract Erc1271Mock { + function isValidSignature(bytes32 _hash, bytes calldata _signature) external view returns (bytes4) {} + } + } sol! { @@ -171,6 +180,7 @@ where if signature.len() < 20 { return Err(SignatureError::InvalidSignature); } + Ok(Address::from_slice(&signature[0..20])) } } @@ -201,21 +211,69 @@ fn ecrecover_address(message: B256, signature: &[u8]) -> Result( +pub async fn verify_signature_auto( signature: S, message: FixedBytes<32>, provider: P, ) -> Result where S: Into + Clone, - // M: AsRef<[u8]> + Clone, P: Provider, T: Transport + Clone, { let address = extract_address(signature.clone(), message, &provider) .await .unwrap(); - verify_signature(signature, address, message, provider).await + + let code_at_address = provider + .get_code_at(address) + .await + .map_err(SignatureError::ProviderError) + .unwrap(); + + if code_at_address.is_empty() { + verify_signature(signature, address, message, provider).await + } else { + let call = Erc1271Mock::isValidSignatureCall { + _hash: message, + _signature: signature.into(), + }; + let bytes = ERC1271_MOCK_BYTECODE + .iter() + .cloned() + .chain(call.abi_encode()) + .collect::>(); + + let transaction_request = + TransactionRequest::default().input(TransactionInput::new(bytes.into())); + + let result = provider.call(&transaction_request).await; + + match result { + Err(e) => { + if let Some(error_response) = e.as_error_resp() { + if error_response.message.starts_with("execution reverted") { + Ok(Verification::Invalid) + } else { + Err(e) + } + } else { + Err(e) + } + } + Ok(result) => { + if let Some(result) = result.first() { + if result == &SUCCESS_RESULT { + Ok(Verification::Valid) + } else { + Ok(Verification::Invalid) + } + } else { + Ok(Verification::Invalid) + } + } + } + } } /// Recovers the signer's address from an ECDSA signature. @@ -252,6 +310,18 @@ fn ecrecover(hash: B256, v: u8, r: U256, s: U256) -> Result, T: Transport + Clone, { - let call = ValidateSigOffchain::constructorCall { - _signer: address, - _hash: message, - _signature: signature.into(), - }; - let bytes = VALIDATE_SIG_OFFCHAIN_BYTECODE - .iter() - .cloned() - .chain(call.abi_encode()) - .collect::>(); - let transaction_request = - TransactionRequest::default().input(TransactionInput::new(bytes.into())); - - let result = provider.call(&transaction_request).await; - - match result { - Err(e) => { - if let Some(error_response) = e.as_error_resp() { - if error_response.message.starts_with("execution reverted") { - Ok(Verification::Invalid) - } else { - Err(e) + let code_exists = provider + .get_code_at(address) + .await + .map_err(SignatureError::ProviderError) + .map(|code| !code.is_empty()) + .unwrap(); + if code_exists { + let call_request = TransactionRequest::default() + .to(address) + .input(TransactionInput::new( + Erc1271Mock::isValidSignatureCall { + _hash: message, + _signature: signature.into(), } + .abi_encode() + .into(), + )); + + let result = provider.call(&call_request).await; + + let result = result.unwrap(); + let magic = result.get(..4); + if let Some(magic) = magic { + if magic == MAGIC_VALUE.to_be_bytes().to_vec() { + Ok(Verification::Valid) } else { - Err(e) + Ok(Verification::Invalid) } + } else { + Ok(Verification::Invalid) } - Ok(result) => { - if let Some(result) = result.first() { - if result == &SUCCESS_RESULT { - Ok(Verification::Valid) + } else { + let call = ValidateSigOffchain::constructorCall { + _signer: address, + _hash: message, + _signature: signature.into(), + }; + let bytes = VALIDATE_SIG_OFFCHAIN_BYTECODE + .iter() + .cloned() + .chain(call.abi_encode()) + .collect::>(); + let transaction_request = + TransactionRequest::default().input(TransactionInput::new(bytes.into())); + + let result = provider.call(&transaction_request).await; + match result { + Err(e) => { + if let Some(error_response) = e.as_error_resp() { + if error_response.message.starts_with("execution reverted") { + Ok(Verification::Invalid) + } else { + Err(e) + } + } else { + Err(e) + } + } + Ok(result) => { + if let Some(result) = result.first() { + if result == &SUCCESS_RESULT { + Ok(Verification::Valid) + } else { + Ok(Verification::Invalid) + } } else { Ok(Verification::Invalid) } - } else { - Ok(Verification::Invalid) } } } @@ -326,8 +428,11 @@ mod test { super::*, alloy::{ dyn_abi::TypedData, - primitives::{address, b256, bytes, keccak256, Address, Uint, B256, I256, U256}, - providers::{network::Ethereum, ReqwestProvider}, + primitives::{ + address, b256, bytes, eip191_hash_message, keccak256, Address, Uint, B256, I256, + U256, + }, + providers::{network::Ethereum, Provider, ReqwestProvider}, signers::{k256::ecdsa::SigningKey, local::PrivateKeySigner, Signer, SignerSync}, sol, sol_types::{eip712_domain, SolCall, SolValue}, @@ -339,32 +444,9 @@ mod test { }, }; - // Manual test. Paste address, signature, message, and project ID to verify - // function - #[tokio::test] - #[ignore] - async fn manual() { - let address = address!("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"); - let message = "xxx"; - let signature = bytes!("aaaa"); - - let provider = ReqwestProvider::::new_http( - "https://rpc.walletconnect.com/v1?chainId=eip155:1&projectId=xxx" - .parse() - .unwrap(), - ); - let message_bytes = message_str_to_bytes(message); - assert!( - verify_signature(signature, address, message_bytes, provider) - .await - .unwrap() - .is_valid() - ); - } - #[tokio::test] async fn test_extract_address_eoa() { - let (_anvil, _rpc_url, provider, private_key) = spawn_anvil(); + let (_anvil, _rpc_url, provider, private_key) = spawn_anvil(None); let message = "test message"; let message_bytes = alloy::primitives::eip191_hash_message(message.as_bytes()); let signature = sign_message_eip191(message, &private_key); @@ -377,7 +459,7 @@ mod test { #[tokio::test] async fn test_extract_address_eip721() { - let (_anvil, _rpc_url, provider, _private_key) = spawn_anvil(); + let (_anvil, _rpc_url, provider, _private_key) = spawn_anvil(None); sol! { #[derive(Debug, Serialize)] struct FooBar { @@ -425,7 +507,7 @@ mod test { #[tokio::test] async fn test_extract_address_erc1271() { - let (_anvil, rpc_url, provider, private_key) = spawn_anvil(); + let (_anvil, rpc_url, provider, private_key) = spawn_anvil(None); let contract_address = deploy_contract( &rpc_url, &private_key, @@ -446,9 +528,7 @@ mod test { #[tokio::test] async fn test_extract_address_erc6492() { - env_logger::init(); - - let (_anvil, rpc_url, provider, private_key) = spawn_anvil(); + let (_anvil, rpc_url, provider, private_key) = spawn_anvil(None); let create2_factory_address = deploy_contract(&rpc_url, &private_key, CREATE2_CONTRACT, None).await; let message = "test message"; @@ -501,7 +581,7 @@ mod test { } #[tokio::test] async fn eoa_pass() { - let (_anvil, _rpc_url, provider, _private_key) = spawn_anvil(); + let (_anvil, _rpc_url, provider, _private_key) = spawn_anvil(None); let private_key = SigningKey::random(&mut rand::thread_rng()); let message = "xxx"; @@ -520,7 +600,7 @@ mod test { #[tokio::test] async fn eoa_wrong_signature() { - let (_anvil, _rpc_url, provider, _private_key) = spawn_anvil(); + let (_anvil, _rpc_url, provider, _private_key) = spawn_anvil(None); let private_key = SigningKey::random(&mut rand::thread_rng()); let message = "xxx"; @@ -538,7 +618,7 @@ mod test { #[tokio::test] async fn typed_data() { - let (_anvil, _rpc_url, provider, _private_key) = spawn_anvil(); + let (_anvil, _rpc_url, provider, _private_key) = spawn_anvil(None); sol! { #[derive(Debug, Serialize)] @@ -603,7 +683,7 @@ mod test { #[tokio::test] async fn eoa_wrong_address() { - let (_anvil, _rpc_url, provider, _private_key) = spawn_anvil(); + let (_anvil, _rpc_url, provider, _private_key) = spawn_anvil(None); let private_key = SigningKey::random(&mut rand::thread_rng()); let message = "xxx"; @@ -621,7 +701,7 @@ mod test { #[tokio::test] async fn eoa_wrong_message() { - let (_anvil, _rpc_url, provider, _private_key) = spawn_anvil(); + let (_anvil, _rpc_url, provider, _private_key) = spawn_anvil(None); let private_key = SigningKey::random(&mut rand::thread_rng()); let message = "xxx"; @@ -639,7 +719,7 @@ mod test { #[tokio::test] async fn erc1271_pass() { - let (_anvil, rpc_url, provider, private_key) = spawn_anvil(); + let (_anvil, rpc_url, provider, private_key) = spawn_anvil(None); let contract_address = deploy_contract( &rpc_url, &private_key, @@ -663,7 +743,7 @@ mod test { #[tokio::test] async fn erc1271_wrong_signature() { - let (_anvil, rpc_url, provider, private_key) = spawn_anvil(); + let (_anvil, rpc_url, provider, private_key) = spawn_anvil(None); let contract_address = deploy_contract( &rpc_url, &private_key, @@ -688,7 +768,7 @@ mod test { #[tokio::test] async fn erc1271_wrong_signer() { - let (anvil, rpc_url, provider, private_key) = spawn_anvil(); + let (anvil, rpc_url, provider, private_key) = spawn_anvil(None); let contract_address = deploy_contract( &rpc_url, &private_key, @@ -714,7 +794,7 @@ mod test { #[tokio::test] async fn erc1271_wrong_contract_address() { - let (_anvil, rpc_url, provider, private_key) = spawn_anvil(); + let (_anvil, rpc_url, provider, private_key) = spawn_anvil(None); let mut contract_address = deploy_contract( &rpc_url, &private_key, @@ -740,7 +820,7 @@ mod test { #[tokio::test] async fn erc1271_wrong_message() { - let (_anvil, rpc_url, provider, private_key) = spawn_anvil(); + let (_anvil, rpc_url, provider, private_key) = spawn_anvil(None); let contract_address = deploy_contract( &rpc_url, &private_key, @@ -766,10 +846,12 @@ mod test { env!("OUT_DIR"), "/../../../../.foundry/forge/out/Erc1271Mock.sol/Erc1271Mock.bytecode" )); + const ERC6492_MAGIC_BYTES: [u16; 16] = [ 0x6492, 0x6492, 0x6492, 0x6492, 0x6492, 0x6492, 0x6492, 0x6492, 0x6492, 0x6492, 0x6492, 0x6492, 0x6492, 0x6492, 0x6492, 0x6492, ]; + sol! { contract Erc1271Mock { address owner_eoa; @@ -824,7 +906,7 @@ mod test { #[tokio::test] async fn erc6492_pass() { - let (_anvil, rpc_url, provider, private_key) = spawn_anvil(); + let (_anvil, rpc_url, provider, private_key) = spawn_anvil(None); let create2_factory_address = deploy_contract(&rpc_url, &private_key, CREATE2_CONTRACT, None).await; @@ -846,7 +928,7 @@ mod test { #[tokio::test] async fn erc6492_wrong_signature() { - let (_anvil, rpc_url, provider, private_key) = spawn_anvil(); + let (_anvil, rpc_url, provider, private_key) = spawn_anvil(None); let create2_factory_address = deploy_contract(&rpc_url, &private_key, CREATE2_CONTRACT, None).await; @@ -870,7 +952,7 @@ mod test { #[tokio::test] async fn erc6492_wrong_signer() { - let (anvil, rpc_url, provider, private_key) = spawn_anvil(); + let (anvil, rpc_url, provider, private_key) = spawn_anvil(None); let create2_factory_address = deploy_contract(&rpc_url, &private_key, CREATE2_CONTRACT, None).await; @@ -896,7 +978,7 @@ mod test { #[tokio::test] async fn erc6492_wrong_contract_address() { - let (_anvil, rpc_url, provider, private_key) = spawn_anvil(); + let (_anvil, rpc_url, provider, private_key) = spawn_anvil(None); let create2_factory_address = deploy_contract(&rpc_url, &private_key, CREATE2_CONTRACT, None).await; @@ -922,7 +1004,7 @@ mod test { #[tokio::test] async fn erc6492_wrong_message() { - let (_anvil, rpc_url, provider, private_key) = spawn_anvil(); + let (_anvil, rpc_url, provider, private_key) = spawn_anvil(None); let create2_factory_address = deploy_contract(&rpc_url, &private_key, CREATE2_CONTRACT, None).await; @@ -943,9 +1025,10 @@ mod test { .is_valid(), ); } + #[tokio::test] async fn test_erc6492_signature_with_invalid_factory() { - let (_anvil, rpc_url, provider, private_key) = spawn_anvil(); + let (_anvil, rpc_url, provider, private_key) = spawn_anvil(None); let create2_factory_address = deploy_contract(&rpc_url, &private_key, CREATE2_CONTRACT, None).await; let message = "test message"; @@ -970,7 +1053,7 @@ mod test { #[tokio::test] async fn test_erc6492_signature_with_empty_factory_calldata() { - let (_anvil, rpc_url, provider, private_key) = spawn_anvil(); + let (_anvil, rpc_url, provider, private_key) = spawn_anvil(None); let create2_factory_address = deploy_contract(&rpc_url, &private_key, CREATE2_CONTRACT, None).await; let message = "test message"; @@ -993,7 +1076,7 @@ mod test { #[tokio::test] async fn test_erc6492_signature_with_mismatched_lengths() { - let (_anvil, rpc_url, provider, private_key) = spawn_anvil(); + let (_anvil, rpc_url, provider, private_key) = spawn_anvil(None); let create2_factory_address = deploy_contract(&rpc_url, &private_key, CREATE2_CONTRACT, None).await; let message = "test message"; @@ -1018,7 +1101,7 @@ mod test { #[tokio::test] async fn test_erc6492_signature_with_invalid_magic_bytes() { - let (_anvil, rpc_url, provider, private_key) = spawn_anvil(); + let (_anvil, rpc_url, provider, private_key) = spawn_anvil(None); let create2_factory_address = deploy_contract(&rpc_url, &private_key, CREATE2_CONTRACT, None).await; let message = "test message"; @@ -1042,10 +1125,57 @@ mod test { assert!(matches!(result, Ok(addr) if addr != predeploy_address)); } + #[tokio::test] + #[cfg(any(test, feature = "reqwest"))] + async fn erc6492_deployed_smart_wallet() { + // contract deployed at 0x3e5608baE5e195F7EB12792f0Ba1aEf911F364E9 on base sepolia + let (_anvil, _rpc_url, provider, _private_key) = + spawn_anvil(Some("https://sepolia.base.org")); + + let message = "Example Message"; + let hash_message: FixedBytes<32> = eip191_hash_message(message.as_bytes()); + let signature:Bytes = alloy::hex::decode("0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000017000000000000000000000000000000000000000000000000000000000000000111d8c5e6edcd92aeac9ee215372ebf3264300a429ae6dd79f93d71781d1a397d747b83a603182db717314be83bf9ae0d18d0652294a09d8d472a0cdd6f4a11c80000000000000000000000000000000000000000000000000000000000000025f9351d6932d2b1c9aca4fee013c4ff2672f712f28e9c37330bac4fa19292b8351d00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008f7b2274797065223a22776562617574686e2e676574222c226368616c6c656e6765223a2274337659526847564b6258504a476c506e51676d79495950573079442d356b497675536e497a494d485a4d222c226f726967696e223a2268747470733a2f2f6b6579732d626574612e636f696e626173652e636f6d222c2263726f73734f726967696e223a66616c73657d0000000000000000000000000000000000").unwrap().into(); + + // Bytes::from(signature.to_vec()); + let signer = address!("3e5608baE5e195F7EB12792f0Ba1aEf911F364E9"); + + let result = verify_signature(signature, signer, hash_message, provider).await; + + println!("{:?}", result); + assert!(result.unwrap().is_valid()); + } + + #[tokio::test] + async fn test_erc6492_signature_with_large_factory_calldata() { + let (_anvil, rpc_url, provider, private_key) = spawn_anvil(None); + let create2_factory_address = + deploy_contract(&rpc_url, &private_key, CREATE2_CONTRACT, None).await; + let message = "test message"; + let message_bytes = message_str_to_bytes(message); + let signature = sign_message_eip191(message, &private_key); + + let (_predeploy_address, erc6492_signature) = predeploy_signature( + Address::from_private_key(&private_key), + create2_factory_address, + signature, + ); + + // Increase the size of factory calldata + let factory_calldata_offset = bytes_to_usize(&erc6492_signature[32..64]); + let large_calldata = vec![0; 1_000_000]; // 1MB of data + let mut new_signature = erc6492_signature[..factory_calldata_offset + 32].to_vec(); + new_signature.extend_from_slice(&U256::from(large_calldata.len()).to_be_bytes::<32>()); + new_signature.extend_from_slice(&large_calldata); + new_signature.extend_from_slice(&erc6492_signature[factory_calldata_offset + 32..]); + + let result = extract_address(new_signature, message_bytes, provider).await; + assert!(result.is_err()); + } + #[tokio::test] #[ignore] async fn test_erc6492_signature_with_deployed_contract() { - let (_anvil, rpc_url, provider, private_key) = spawn_anvil(); + let (_anvil, rpc_url, provider, private_key) = spawn_anvil(None); let create2_factory_address = deploy_contract(&rpc_url, &private_key, CREATE2_CONTRACT, None).await; let message = "test message"; @@ -1068,10 +1198,11 @@ mod test { let result = extract_address(erc6492_signature, message_bytes, provider).await; assert_eq!(result.unwrap(), address); } + #[tokio::test] #[ignore] async fn test_erc6492_signature_with_different_message() { - let (_anvil, rpc_url, provider, private_key) = spawn_anvil(); + let (_anvil, rpc_url, provider, private_key) = spawn_anvil(None); let create2_factory_address = deploy_contract(&rpc_url, &private_key, CREATE2_CONTRACT, None).await; @@ -1163,30 +1294,26 @@ mod test { println!("Test completed successfully"); } + // Manual test. Paste address, signature, message, and project ID to verify + // function #[tokio::test] - async fn test_erc6492_signature_with_large_factory_calldata() { - let (_anvil, rpc_url, provider, private_key) = spawn_anvil(); - let create2_factory_address = - deploy_contract(&rpc_url, &private_key, CREATE2_CONTRACT, None).await; - let message = "test message"; - let message_bytes = message_str_to_bytes(message); - let signature = sign_message_eip191(message, &private_key); - - let (_predeploy_address, erc6492_signature) = predeploy_signature( - Address::from_private_key(&private_key), - create2_factory_address, - signature, - ); + #[ignore] + async fn manual() { + let (_anvil, _rpc_url, _provider, _private_key) = spawn_anvil(None); - // Increase the size of factory calldata - let factory_calldata_offset = bytes_to_usize(&erc6492_signature[32..64]); - let large_calldata = vec![0; 1_000_000]; // 1MB of data - let mut new_signature = erc6492_signature[..factory_calldata_offset + 32].to_vec(); - new_signature.extend_from_slice(&U256::from(large_calldata.len()).to_be_bytes::<32>()); - new_signature.extend_from_slice(&large_calldata); - new_signature.extend_from_slice(&erc6492_signature[factory_calldata_offset + 32..]); + let address = address!("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"); + let message = "xxx"; + let message_hash = eip191_hash_message(message.as_bytes()); + let signature = bytes!("aaaa"); - let result = extract_address(new_signature, message_bytes, provider).await; - assert!(result.is_err()); + let provider = ReqwestProvider::::new_http( + "https://rpc.walletconnect.com/v1?chainId=eip155:1&projectId=xxx" + .parse() + .unwrap(), + ); + assert!(verify_signature(signature, address, message_hash, provider) + .await + .unwrap() + .is_valid()); } } diff --git a/src/test_helpers.rs b/src/test_helpers.rs index 2229c3e..cbe29d0 100644 --- a/src/test_helpers.rs +++ b/src/test_helpers.rs @@ -21,13 +21,20 @@ fn format_foundry_dir(path: &str) -> String { ) } -pub fn spawn_anvil() -> (AnvilInstance, String, ReqwestProvider, SigningKey) { - let anvil = Anvil::at(format_foundry_dir("bin/anvil")).spawn(); - let rpc_url = anvil.endpoint(); - let provider = ReqwestProvider::::new_http(anvil.endpoint_url()); - let private_key = anvil.keys().first().unwrap().clone(); +pub fn spawn_anvil(fork_url: Option<&str>) -> (AnvilInstance, String, ReqwestProvider, SigningKey) { + let mut anvil = Anvil::at(format_foundry_dir("bin/anvil")); + + if let Some(fork_url) = fork_url { + anvil = anvil.fork(fork_url); + } + + let anvil_instance = anvil.spawn(); + + let rpc_url = anvil_instance.endpoint(); + let provider = ReqwestProvider::::new_http(anvil_instance.endpoint_url()); + let private_key = anvil_instance.keys().first().unwrap().clone(); ( - anvil, + anvil_instance, rpc_url, provider, SigningKey::from_bytes(&private_key.to_bytes()).unwrap(),