Feat/is middleware signer (#182)
* feat: signature from_str can handle 0x-prefixed strings * feat: add is_signer method to the middleware trait * fix: eth_sign Signature deserialisation * chore: refactor for cleaner decoding of Signature
This commit is contained in:
parent
b69f68f089
commit
4c8d3c81e7
|
@ -173,6 +173,7 @@ impl FromStr for Signature {
|
|||
type Err = SignatureError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let s = s.strip_prefix("0x").unwrap_or(s);
|
||||
let bytes = hex::decode(s)?;
|
||||
Signature::try_from(&bytes[..])
|
||||
}
|
||||
|
@ -260,4 +261,17 @@ mod tests {
|
|||
Address::from_str("2c7536E3605D9C16a7a3D7b1898e529396a65c23").unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn signature_from_str() {
|
||||
let s1 = Signature::from_str(
|
||||
"0xaa231fbe0ed2b5418e6ba7c19bee2522852955ec50996c02a2fe3e71d30ddaf1645baf4823fea7cb4fcc7150842493847cfb6a6d63ab93e8ee928ee3f61f503500"
|
||||
).expect("could not parse 0x-prefixed signature");
|
||||
|
||||
let s2 = Signature::from_str(
|
||||
"aa231fbe0ed2b5418e6ba7c19bee2522852955ec50996c02a2fe3e71d30ddaf1645baf4823fea7cb4fcc7150842493847cfb6a6d63ab93e8ee928ee3f61f503500"
|
||||
).expect("could not parse non-prefixed signature");
|
||||
|
||||
assert_eq!(s1, s2);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -235,6 +235,11 @@ where
|
|||
&self.inner
|
||||
}
|
||||
|
||||
/// `SignerMiddleware` is instantiated with a signer.
|
||||
async fn is_signer(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
/// Signs and broadcasts the transaction. The optional parameter `block` can be passed so that
|
||||
/// gas cost and nonce calculations take it into account. For simple transactions this can be
|
||||
/// left to `None`.
|
||||
|
|
|
@ -306,6 +306,13 @@ pub trait Middleware: Sync + Send + Debug {
|
|||
.map_err(FromErr::from)
|
||||
}
|
||||
|
||||
/// This returns true if either the middleware stack contains a `SignerMiddleware`, or the
|
||||
/// JSON-RPC provider has an unlocked key that can sign using the `eth_sign` call. If none of
|
||||
/// the above conditions are met, then the middleware stack is not capable of signing data.
|
||||
async fn is_signer(&self) -> bool {
|
||||
self.inner().is_signer().await
|
||||
}
|
||||
|
||||
async fn sign<T: Into<Bytes> + Send + Sync>(
|
||||
&self,
|
||||
data: T,
|
||||
|
|
|
@ -79,6 +79,9 @@ pub enum ProviderError {
|
|||
|
||||
#[error(transparent)]
|
||||
HexError(#[from] hex::FromHexError),
|
||||
|
||||
#[error("custom error: {0}")]
|
||||
CustomError(String),
|
||||
}
|
||||
|
||||
/// Types of filters supported by the JSON-RPC.
|
||||
|
@ -316,6 +319,15 @@ impl<P: JsonRpcClient> Middleware for Provider<P> {
|
|||
Ok(PendingTransaction::new(tx_hash, self).interval(self.get_interval()))
|
||||
}
|
||||
|
||||
/// The JSON-RPC provider is at the bottom-most position in the middleware stack. Here we check
|
||||
/// if it has the key for the sender address unlocked, as well as supports the `eth_sign` call.
|
||||
async fn is_signer(&self) -> bool {
|
||||
match self.3 {
|
||||
Some(sender) => self.sign(vec![], &sender).await.is_ok(),
|
||||
None => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Signs data using a specific account. This account needs to be unlocked.
|
||||
async fn sign<T: Into<Bytes> + Send + Sync>(
|
||||
&self,
|
||||
|
@ -324,7 +336,15 @@ impl<P: JsonRpcClient> Middleware for Provider<P> {
|
|||
) -> Result<Signature, ProviderError> {
|
||||
let data = utils::serialize(&data.into());
|
||||
let from = utils::serialize(from);
|
||||
self.request("eth_sign", [from, data]).await
|
||||
|
||||
// get the response from `eth_sign` call and trim the 0x-prefix if present.
|
||||
let sig: String = self.request("eth_sign", [from, data]).await?;
|
||||
let sig = sig.strip_prefix("0x").unwrap_or(&sig);
|
||||
|
||||
// decode the signature.
|
||||
let sig = hex::decode(sig)?;
|
||||
Ok(Signature::try_from(sig.as_slice())
|
||||
.map_err(|e| ProviderError::CustomError(e.to_string()))?)
|
||||
}
|
||||
|
||||
////// Contract state
|
||||
|
@ -847,6 +867,30 @@ mod tests {
|
|||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_is_signer() {
|
||||
use ethers_core::utils::Ganache;
|
||||
use std::str::FromStr;
|
||||
|
||||
let ganache = Ganache::new().spawn();
|
||||
let provider = Provider::<Http>::try_from(ganache.endpoint())
|
||||
.unwrap()
|
||||
.with_sender(ganache.addresses()[0]);
|
||||
assert_eq!(provider.is_signer().await, true);
|
||||
|
||||
let provider = Provider::<Http>::try_from(ganache.endpoint()).unwrap();
|
||||
assert_eq!(provider.is_signer().await, false);
|
||||
|
||||
let sender = Address::from_str("635B4764D1939DfAcD3a8014726159abC277BecC")
|
||||
.expect("should be able to parse hex address");
|
||||
let provider = Provider::<Http>::try_from(
|
||||
"https://ropsten.infura.io/v3/fd8b88b56aa84f6da87b60f5441d6778",
|
||||
)
|
||||
.unwrap()
|
||||
.with_sender(sender);
|
||||
assert_eq!(provider.is_signer().await, false);
|
||||
}
|
||||
|
||||
// this must be run with geth or parity since ganache-core still does not support
|
||||
// eth_pendingTransactions, https://github.com/trufflesuite/ganache-core/issues/405
|
||||
// example command: `geth --dev --rpc --dev.period 1`
|
||||
|
|
Loading…
Reference in New Issue