contract: allow connecting to many clients/addresses

This commit is contained in:
Georgios Konstantopoulos 2020-06-02 13:36:02 +03:00
parent 6bd3c41bd0
commit e051cffe47
No known key found for this signature in database
GPG Key ID: FA607837CD26EDBC
8 changed files with 97 additions and 48 deletions

View File

@ -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)?;

View File

@ -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

View File

@ -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)]

View File

@ -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");
}

View File

@ -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;
}
}

View File

@ -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>;

View File

@ -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)))

View File

@ -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;