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;
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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`.
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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`
|
||||||
|
|
Loading…
Reference in New Issue