-
Notifications
You must be signed in to change notification settings - Fork 45
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Generate a Mocked Version of the Contract. #83
Comments
My initial thought was that we could mock contracts on the transport level, living generated code as is. I'm looking into it right now. There's also a proposal to make generated contract bindings generic over transport implementation (see #249). I wonder if that is a path we want to choose for mock implementation? I.e. make contracts generic over method builders and things like that. Also @fleupold proposed exporting a trait with all contract methods, and providing a default implementation for it (see #424). This approach could help us in the future if we ever want to support contract interfaces ( |
So far mocking on transport level seems like the least intrusive and most flexible solution. There are two downsides. First, if user wants to use mocked contracts, they'll have to use dynamic transport, even if we implement #249. Second, this will require a lot of infrastructure around mocked transport. We'll need methods to deploy mocked contracts, to define expected behaviours, and much more. Which leads me to another question. Do we want to support other RPC calls in mocked transport? How often do we use contracts to call their methods without using transport to do other interactions with node? If the answer is "yes, we want other RPC calls", then this task looks like "mocking the entire node". While it is extremely interesting, I'm not sure we want to spend time implementing it, at least not now =) |
Providing traits for contracts does not seem feasible without major changes to method and event builders. Right now, we create method builders by using appropriate contract methods, then work with concrete method builder instances. Contract traits will have to return dynamic method builders that implement method builder traits, so the whole code becomes dynamic. The same thing happens with using generics: we'll end up with a contract that's generic over transport, method builder, and event builder. Either of the above two scenarios include too much dynamic dispatch or generics for my taste. That's a subjective judgement based on taste, though, as I can't provide any prediction on performance, code size and compilation times impact. I'll be very happy to discuss these approaches. |
So, here's a concept of mock interface: // contains a web3 instance with a mock transport
let mock = create_mock();
// set up for dynamic contract
let mocked_instance = mock.deploy(abi);
mocked_instance
.expect_call([/* signature */])
.with_params(/* params spec */)
.returning(/* return spec */);
// mocked_instance.instance() returns Instance
// that you can pass to your code
run_code(mocked_instance.instance());
// set up for generated contract
let mocked_erc20 = MockERC20::deploy(&mock);
mocked_erc20
.expect_transfer()
.with_params(/* params spec */)
.returning(/* return spec */);
// mocked_erc20.contract() returns ERC20
// that you can pass to your code
run_code(mocked_erc20.contract()); Some questions:
|
If we want to depend on |
I would also be OK to have this enabled by a feature. I don't really have a preference on which way is better - but I do agree it shouldn't be included unconditionally. |
I see a bit of an issue here. Contract bindings may be exported as a separate crate (see I see three possible solutions:
|
If generic associated types were stable, we could've solved it by doing something like this. In ethcontract: /// This is a lightweight interface that can get info about
/// contract's function and do whatever it wants with it.
/// In this example, implementation of this trait will use
/// the info to add expectation to the mocked contract.
trait MethodInfoConsumer {
type Return<T, R>;
fn consume<T, R>(&self, signature: H160) -> Self::Return<T, R>;
} In generated code: /// This struct simply provides info about ERC20's methods
/// and relies it to `MethodInfoConsumer`.
struct ERC20MethodInfo<C: MethodInfoConsumer>(C);
impl ERC20MethodInfo {
fn transfer(&self) -> C::Return<(Address, U256), bool> {
self.0.consume([/* signature */])
}
fn approve(&self) -> C::Return<(Address, U256), bool> {
self.0.consume([/* signature */])
}
} Then // returns `MockedContract<ERC20MethodInfo<MockMethodFactory>>`
let mocked_erc20 = MockERC20::deploy(&mock);
mocked_erc20
.expect() // Returns `ERC20MethodInfo<MockMethodFactory>`.
.transfer() // User indicates that they expect a call to `transfer`.
// `ERC20MethodInfo` relies this info to `MockMethodFactory`
// which creates a new expectation for the mocked contract
// and returns a handle that allows further set up.
.returning(/* return spec */); // Set up as usual. I can't figure out anything that doesn't require generic associated types... |
BTW associated generic types could land this year, see rust-lang/rust#44265. |
While it would be nice to generate type safe method mock signatures (like in your last code-snippet), I think it would be already a valuable "first step" to just create a let mocked_transport = MockableTransport::new()
mocked_transport.expect_eth_call("balanceOf").returns(100)
let contract = ERC20::deploy(Web3::new(mocked_transport)); But of course if there are more fancy solutions easily possible, that would be great. The goal of this should be to allow us to somehow mock components that rely on contracts extracting the contract functions into its own trait just for the purpose of local mocking (cf. gnosis/gp-v2-services@9815507#diff-b53cd20554b63eaba3f6a63b21e2e992dd354d6bab38156b5d0e566bbedc3bc4R62) |
For unit testing. Still need to iron out exactly what this means, and how it will be done.
The text was updated successfully, but these errors were encountered: