contract: allow connecting to many clients/addresses
This commit is contained in:
parent
6bd3c41bd0
commit
e051cffe47
|
@ -30,6 +30,7 @@ pub enum ContractError {
|
|||
ContractNotDeployed,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ContractCall<'a, P, S, D> {
|
||||
pub(crate) tx: TransactionRequest,
|
||||
pub(crate) function: Function,
|
||||
|
@ -85,8 +86,8 @@ where
|
|||
/// and return the return type of the transaction without mutating the state
|
||||
///
|
||||
/// Note: this function _does not_ send a transaction from your account
|
||||
pub async fn call(self) -> Result<D, ContractError> {
|
||||
let bytes = self.client.call(self.tx, self.block).await?;
|
||||
pub async fn call(&self) -> Result<D, ContractError> {
|
||||
let bytes = self.client.call(&self.tx, self.block).await?;
|
||||
|
||||
let tokens = self.function.decode_output(&bytes.0)?;
|
||||
|
||||
|
|
|
@ -113,13 +113,31 @@ where
|
|||
})
|
||||
}
|
||||
|
||||
pub fn address(&self) -> &Address {
|
||||
&self.address
|
||||
pub fn address(&self) -> Address {
|
||||
self.address
|
||||
}
|
||||
|
||||
pub fn abi(&self) -> &Abi {
|
||||
&self.abi
|
||||
}
|
||||
|
||||
/// Returns a new contract instance at `address`.
|
||||
///
|
||||
/// Clones `self` internally
|
||||
pub fn at<T: Into<Address>>(&self, address: T) -> Self {
|
||||
let mut this = self.clone();
|
||||
this.address = address.into();
|
||||
this
|
||||
}
|
||||
|
||||
/// Returns a new contract instance using the provided client
|
||||
///
|
||||
/// Clones `self` internally
|
||||
pub fn connect(&self, client: &'a Client<P, S>) -> Self {
|
||||
let mut this = self.clone();
|
||||
this.client = client;
|
||||
this
|
||||
}
|
||||
}
|
||||
|
||||
/// Utility function for creating a mapping between a unique signature and a
|
||||
|
|
|
@ -17,7 +17,7 @@ const POLL_INTERVAL: u64 = 7000;
|
|||
#[derive(Debug, Clone)]
|
||||
pub struct Deployer<'a, P, S> {
|
||||
client: &'a Client<P, S>,
|
||||
abi: &'a Abi,
|
||||
pub abi: &'a Abi,
|
||||
tx: TransactionRequest,
|
||||
confs: usize,
|
||||
poll_interval: Duration,
|
||||
|
@ -57,6 +57,14 @@ where
|
|||
let contract = Contract::new(address, self.abi, self.client);
|
||||
Ok(contract)
|
||||
}
|
||||
|
||||
pub fn abi(&self) -> &Abi {
|
||||
&self.abi
|
||||
}
|
||||
|
||||
pub fn client(&self) -> &Client<P, S> {
|
||||
&self.client
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use ethers_contract::{Contract, ContractFactory};
|
||||
use ethers_contract::ContractFactory;
|
||||
use ethers_core::{
|
||||
types::H256,
|
||||
types::{Address, H256},
|
||||
utils::{GanacheBuilder, Solc},
|
||||
};
|
||||
use ethers_providers::{Http, Provider};
|
||||
|
@ -9,70 +9,90 @@ use std::convert::TryFrom;
|
|||
|
||||
#[tokio::test]
|
||||
async fn deploy_and_call_contract() {
|
||||
// 1. compile the contract
|
||||
// compile the contract
|
||||
let compiled = Solc::new("./tests/contract.sol").build().unwrap();
|
||||
let contract = compiled
|
||||
.get("SimpleStorage")
|
||||
.expect("could not find contract");
|
||||
|
||||
// 2. launch ganache
|
||||
// launch ganache
|
||||
let port = 8546u64;
|
||||
let url = format!("http://localhost:{}", port).to_string();
|
||||
let _ganache = GanacheBuilder::new().port(port)
|
||||
.mnemonic("abstract vacuum mammal awkward pudding scene penalty purchase dinner depart evoke puzzle")
|
||||
.spawn();
|
||||
|
||||
// 3. instantiate our wallet
|
||||
let wallet = "380eb0f3d505f087e438eca80bc4df9a7faa24f868e69fc0440261a0fc0567dc"
|
||||
.parse::<Wallet>()
|
||||
.unwrap();
|
||||
|
||||
// 4. connect to the network
|
||||
// connect to the network
|
||||
let provider = Provider::<Http>::try_from(url.as_str()).unwrap();
|
||||
|
||||
// 5. instantiate the client with the wallet
|
||||
let client = wallet.connect(provider);
|
||||
// instantiate our wallets
|
||||
let [wallet1, wallet2]: [Wallet; 2] = [
|
||||
"380eb0f3d505f087e438eca80bc4df9a7faa24f868e69fc0440261a0fc0567dc"
|
||||
.parse()
|
||||
.unwrap(),
|
||||
"cc96601bc52293b53c4736a12af9130abf347669b3813f9ec4cafdf6991b087e"
|
||||
.parse()
|
||||
.unwrap(),
|
||||
];
|
||||
|
||||
// 6. create a factory which will be used to deploy instances of the contract
|
||||
// Instantiate the clients. We assume that clients consume the provider and the wallet
|
||||
// (which makes sense), so for multi-client tests, you must clone the provider.
|
||||
let client = wallet1.connect(provider.clone());
|
||||
let client2 = wallet2.connect(provider);
|
||||
|
||||
// create a factory which will be used to deploy instances of the contract
|
||||
let factory = ContractFactory::new(&client, &contract.abi, &contract.bytecode);
|
||||
|
||||
// 7. deploy it with the constructor arguments
|
||||
let contract = factory
|
||||
.deploy("initial value".to_string())
|
||||
.unwrap()
|
||||
.send()
|
||||
.await
|
||||
.unwrap();
|
||||
// `send` consumes the deployer so it must be cloned for later re-use
|
||||
// (practically it's not expected that you'll need to deploy multiple instances of
|
||||
// the _same_ deployer, so it's fine to clone here from a dev UX vs perf tradeoff)
|
||||
let deployer = factory.deploy("initial value".to_string()).unwrap();
|
||||
let contract = deployer.clone().send().await.unwrap();
|
||||
|
||||
// 8. get the contract's address
|
||||
let addr = contract.address();
|
||||
let get_value = contract.method::<_, String>("getValue", ()).unwrap();
|
||||
let last_sender = contract.method::<_, Address>("lastSender", ()).unwrap();
|
||||
|
||||
// 9. instantiate the contract
|
||||
let contract = Contract::new(*addr, contract.abi(), &client);
|
||||
|
||||
// 10. the initial value must be the one set in the constructor
|
||||
let value: String = contract
|
||||
.method("getValue", ())
|
||||
.unwrap()
|
||||
.call()
|
||||
.await
|
||||
.unwrap();
|
||||
// the initial value must be the one set in the constructor
|
||||
let value = get_value.clone().call().await.unwrap();
|
||||
assert_eq!(value, "initial value");
|
||||
|
||||
// 11. call the `setValue` method (ugly API here)
|
||||
// make a call with `client2`
|
||||
let _tx_hash = contract
|
||||
.connect(&client2)
|
||||
.method::<_, H256>("setValue", "hi".to_owned())
|
||||
.unwrap()
|
||||
.send()
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(last_sender.clone().call().await.unwrap(), client2.address());
|
||||
assert_eq!(get_value.clone().call().await.unwrap(), "hi");
|
||||
|
||||
// 12. get the new value
|
||||
let value: String = contract
|
||||
.method("getValue", ())
|
||||
// we can also call contract methods at other addresses with the `at` call
|
||||
// (useful when interacting with multiple ERC20s for example)
|
||||
let contract2_addr = deployer.clone().send().await.unwrap().address();
|
||||
let contract2 = contract.at(contract2_addr);
|
||||
let init_value: String = contract2
|
||||
.method::<_, String>("getValue", ())
|
||||
.unwrap()
|
||||
.call()
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(value, "hi");
|
||||
let init_address = contract2
|
||||
.method::<_, Address>("lastSender", ())
|
||||
.unwrap()
|
||||
.call()
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(init_address, Address::zero());
|
||||
assert_eq!(init_value, "initial value");
|
||||
|
||||
// we can still interact with the old contract instance
|
||||
let _tx_hash = contract
|
||||
.method::<_, H256>("setValue", "hi2".to_owned())
|
||||
.unwrap()
|
||||
.send()
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(last_sender.clone().call().await.unwrap(), client.address());
|
||||
assert_eq!(get_value.clone().call().await.unwrap(), "hi2");
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ contract SimpleStorage {
|
|||
|
||||
event ValueChanged(address indexed author, string oldValue, string newValue);
|
||||
|
||||
address public lastSender;
|
||||
string _value;
|
||||
|
||||
constructor(string memory value) public {
|
||||
|
@ -18,5 +19,6 @@ contract SimpleStorage {
|
|||
function setValue(string memory value) public {
|
||||
emit ValueChanged(msg.sender, _value, value);
|
||||
_value = value;
|
||||
lastSender = msg.sender;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@ pub type HttpProvider = Provider<Http>;
|
|||
#[async_trait]
|
||||
/// Trait which must be implemented by data transports to be used with the Ethereum
|
||||
/// JSON-RPC provider.
|
||||
pub trait JsonRpcClient: Debug {
|
||||
pub trait JsonRpcClient: Debug + Clone {
|
||||
/// A JSON-RPC Error
|
||||
type Error: Error + Into<ProviderError>;
|
||||
|
||||
|
|
|
@ -186,10 +186,10 @@ impl<P: JsonRpcClient> Provider<P> {
|
|||
/// This is free, since it does not change any state on the blockchain.
|
||||
pub async fn call(
|
||||
&self,
|
||||
tx: TransactionRequest,
|
||||
tx: &TransactionRequest,
|
||||
block: Option<BlockNumber>,
|
||||
) -> Result<Bytes, ProviderError> {
|
||||
let tx = utils::serialize(&tx);
|
||||
let tx = utils::serialize(tx);
|
||||
let block = utils::serialize(&block.unwrap_or(BlockNumber::Latest));
|
||||
Ok(self
|
||||
.0
|
||||
|
@ -311,7 +311,7 @@ impl<P: JsonRpcClient> Provider<P> {
|
|||
// first get the resolver responsible for this name
|
||||
// the call will return a Bytes array which we convert to an address
|
||||
let data = self
|
||||
.call(ens::get_resolver(ens_addr, ens_name), None)
|
||||
.call(&ens::get_resolver(ens_addr, ens_name), None)
|
||||
.await?;
|
||||
|
||||
let resolver_address: Address = decode_bytes(ParamType::Address, data);
|
||||
|
@ -321,7 +321,7 @@ impl<P: JsonRpcClient> Provider<P> {
|
|||
|
||||
// resolve
|
||||
let data = self
|
||||
.call(ens::resolve(resolver_address, selector, ens_name), None)
|
||||
.call(&ens::resolve(resolver_address, selector, ens_name), None)
|
||||
.await?;
|
||||
|
||||
Ok(Some(decode_bytes(param, data)))
|
||||
|
|
|
@ -12,7 +12,7 @@ use std::error::Error;
|
|||
///
|
||||
/// Implement this trait to support different signing modes, e.g. Ledger, hosted etc.
|
||||
// TODO: We might need a `SignerAsync` trait for HSM use cases?
|
||||
pub trait Signer {
|
||||
pub trait Signer: Clone {
|
||||
type Error: Error + Into<ClientError>;
|
||||
/// Signs the hash of the provided message after prefixing it
|
||||
fn sign_message<S: AsRef<[u8]>>(&self, message: S) -> Signature;
|
||||
|
|
Loading…
Reference in New Issue