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(),