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:
Rohit Narurkar 2021-01-28 12:21:53 +05:30 committed by GitHub
parent b69f68f089
commit 4c8d3c81e7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 71 additions and 1 deletions

View File

@ -173,6 +173,7 @@ impl FromStr for Signature {
type Err = SignatureError; type Err = SignatureError;
fn from_str(s: &str) -> Result<Self, Self::Err> { fn from_str(s: &str) -> Result<Self, Self::Err> {
let s = s.strip_prefix("0x").unwrap_or(s);
let bytes = hex::decode(s)?; let bytes = hex::decode(s)?;
Signature::try_from(&bytes[..]) Signature::try_from(&bytes[..])
} }
@ -260,4 +261,17 @@ mod tests {
Address::from_str("2c7536E3605D9C16a7a3D7b1898e529396a65c23").unwrap() 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);
}
} }

View File

@ -235,6 +235,11 @@ where
&self.inner &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 /// 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 /// gas cost and nonce calculations take it into account. For simple transactions this can be
/// left to `None`. /// left to `None`.

View File

@ -306,6 +306,13 @@ pub trait Middleware: Sync + Send + Debug {
.map_err(FromErr::from) .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>( async fn sign<T: Into<Bytes> + Send + Sync>(
&self, &self,
data: T, data: T,

View File

@ -79,6 +79,9 @@ pub enum ProviderError {
#[error(transparent)] #[error(transparent)]
HexError(#[from] hex::FromHexError), HexError(#[from] hex::FromHexError),
#[error("custom error: {0}")]
CustomError(String),
} }
/// Types of filters supported by the JSON-RPC. /// 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())) 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. /// Signs data using a specific account. This account needs to be unlocked.
async fn sign<T: Into<Bytes> + Send + Sync>( async fn sign<T: Into<Bytes> + Send + Sync>(
&self, &self,
@ -324,7 +336,15 @@ impl<P: JsonRpcClient> Middleware for Provider<P> {
) -> Result<Signature, ProviderError> { ) -> Result<Signature, ProviderError> {
let data = utils::serialize(&data.into()); let data = utils::serialize(&data.into());
let from = utils::serialize(from); 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 ////// 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 // 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 // eth_pendingTransactions, https://github.com/trufflesuite/ganache-core/issues/405
// example command: `geth --dev --rpc --dev.period 1` // example command: `geth --dev --rpc --dev.period 1`