Search code examples
pythonsolidityweb3py

How to create event filter for contract created by contract in web3py


I'm trying to create a filter for an event raised by a contract created by my desired contract. However, I can't figure it out. Here's what I have right now.

MergeModule.sol

pragma solidity ^0.4.23;

contract MergeModule {
    event MergeEvent(uint prID);

    function MergeModule(){

    }

    function merge(uint prID) public {
        emit MergeEvent(prID);
    }
}

Handler.sol

pragma solidity ^0.4.23;

import "./merge_module.sol";

contract Handler {

    // a getter for this will automatically be made by the compiler
    address public mergeModule;

    function Handler() public {
        mergeModule = new MergeModule();
    }

    function execute() public {
        mergeModule.merge();
    }
}

test_handler.py

from web3 import Web3, EthereumTesterProvider
import unittest
import os
from eth_tester.exceptions import TransactionFailed
import tests.utils.utils as utils
from web3.utils.filters import Filter


class TestHandler(unittest.TestCase):
    PROJECT_ROOT = os.path.dirname(os.path.dirname(__file__))
    CONTRACT_ROOT = os.path.join(PROJECT_ROOT, "contracts")
    TEST_CONTRACT_ROOT = os.path.join(CONTRACT_ROOT, "test_contracts")

    def setUp(self):
        handler_contract_path = os.path.join(self.CONTRACT_ROOT, "handler.sol")

        # web3.py instance
        self.w3 = Web3(EthereumTesterProvider())

        self.contract, self.contract_address, self.contract_instance = utils.create_contract(self.CONTRACT_ROOT,
                                                                                             handler_contract_path,
                                                                                             "Handler", self.w3)


    def test_event_emitted(self):
        # this prints something different from self.contract_address
        print(self.contract_instance.mergeModule())
        
        # this creates a reference to the Handler contract. I know this because when I inspect it with the debugger I see `execute` as one of the functions
        merge_contract = self.w3.eth.contract(self.contract_instance.mergeModule())
        merge_event_filter: Filter = merge_contract.events.MergeEvent.createFilter(fromBlock=0)

        # do stuff here with the filter

utils.create_contract more or less does what's shown in the quickstart for web3py with some modifications to handle compiling multiple files. I suspect that I need to pass the abi of the mergeModule when executing merge_contract = self.w3.eth.contract(self.contract_instance.mergeModule()) but I'm not sure.

The error that I get when I run this is:

AttributeError: 'ContractEvents' object has no attribute 'MergeEvent'

which makes sense because merge_contract is a Handler contract and not a MergeModule contract.


Solution

  • Looks like I was right. Passing the abi for the MergeModule was the key. Here's a hacky function I wrote to get a reference to the merge module.

        def get_merge_module(self):
            from solc import compile_source, compile_files
    
            # compile all of my contract files, one of which is the solidity file with the merge module
            compiled_sol = compile_files(utils.get_contract_file_paths(self.CONTRACT_ROOT))
    
    
            contract_interface = compiled_sol[f'{os.path.join(self.CONTRACT_ROOT, "merge_module.sol")}:MergeModule']
            abi = contract_interface["abi"]
    
            merge_contract = self.w3.eth.contract(self.contract_instance.mergeModule(), abi=abi)
    
            return merge_contract