cowprotocol/ethcontract-rs

Export some kind of general Contract struct

bh2smith opened this issue · 2 comments

Not exactly sure how to describe this, but I suppose it would be analogous to the Contract object what we know and love from ethers which can be instantiated as an interface from an ABI or as an instance when also provided with an address and web3Provider.

This would allow us to make generalizations for arbitrary contracts some common functionalities.

One particular place where this could come in handy is when implementing an Event Listener in place of the generic EventRetrieving

pub struct EventHandler<C: EventRetrieving, S: EventStoring<C::Event>> {
    contract: C,
    store: S,
    last_handled_block: Option<u64>,
}

Where

pub trait EventRetrieving {
    type Event: ParseLog;
    fn get_events(&self) -> AllEventsBuilder<DynTransport, Self::Event>;
    fn web3(&self) -> Web3<DynTransport>;
}

At the moment we need to implement EventRetrieving in a very boiler plate kind of way for each contract we want to listen to.

impl EventRetrieving for GPv2SettlementContract {
    type Event = ContractEvent;
    fn get_events(&self) -> AllEventsBuilder<DynTransport, Self::Event> {
        self.0.all_events()
    }

    fn web3(&self) -> Web3<DynTransport> {
        self.0.raw_instance().web3()
    }
}

So, I think there is a large change we can make to the ethcontract binding generation. Specifically we can generate:

struct MyContractAbi;

implement Abi for MyContractAbi {
  type Function: MyContractFunction;
  type Event: MyContractEvent;
}

enum MyContractFunction {
  Foo {
    value: U256,
    owner: Address,
  },
  Bar,
}

enum MyContractEvent {
  // same as now.
}

We would then be able to create instances of type:

let instance: Contract<MyContractAbi>;

Some other advantages of this separation:

  1. We can define functions generic on some contract type:
fn do_something_to_contract<C: Abi>(instance: Contract<C>) { /* ... */ };
fn call_some_function<C: Abi>(instance: Contract<C>, function: C::Function) { /* ... */ };
  1. Have a better way forward for mock contracts:
let instance: MockContract<MyContractAbi>;
  1. Be able to encode function data without needing a contract instance:
let calldata = MyContractEvent::encode(MyContractFunction::Foo { value, owner });

Trying to make this general deployment block fetcher

async fn get_deployment_block<T: Transport>(contract: &ethcontract::contract::Instance<T>) -> Option<u64> {
    match contract.deployment_information() {
        Some(DeploymentInformation::BlockNumber(block_number)) => Some(block_number),
        Some(DeploymentInformation::TransactionHash(hash)) => Some(
            contract
                .raw_instance()
                .web3()
                .block_number_from_tx_hash(hash)
                .await?,
        ),
        None => None,
    }
}

But when I give it a generated contract instance I get this:

error[E0308]: mismatched types
   --> shared/src/balancer/event_handler.rs:286:71
    |
286 |         let deployment_block_two_token_factory = get_deployment_block(&two_token_pool_factory).await;
    |                                                                       ^^^^^^^^^^^^^^^^^^^^^^^ expected struct `Instance`, found struct `BalancerV2WeightedPool2TokensFactory`
    |
    = note: expected reference `&Instance<_>`
               found reference `&BalancerV2WeightedPool2TokensFactory`

Managed to work around it by passing the DeploymentInformation instead of the contract like so

async fn get_deployment_block(
    deployment_info: Option<DeploymentInformation>,
    web3: &DynWeb3,
) -> Option<u64> {
    match deployment_info {
        Some(DeploymentInformation::BlockNumber(block_number)) => Some(block_number),
        Some(DeploymentInformation::TransactionHash(hash)) => {
            Some(web3.block_number_from_tx_hash(hash).await.ok()?)
        }
        None => None,
    }
}