Repository: ethereum/web3.py

Issue:
Add API to iterate through all events in a contract
### What was wrong?

No easy way to get all events for a contract (while still parsing the results). See [this StackExchange question](https://ethereum.stackexchange.com/questions/54473/how-to-read-allevents-using-python-web3-theres-capability-in-web3-js). One option is to iterate over all the events, but it's a bit awkward right now. I think the easiest way is:

```py
    from web3.contract import ContractEvent
    
    filters = [
      event.createFilter(fromBlock='latest')
      for event in myContract.events
      if isinstance(event, ContractEvent)
    ]
```

### How can it be fixed?

Some options: 
- Implement `__iter__` on `Contract.events` to iterate through all events in the ABI (my favorite option, except that it's inconsistent with `contract.functions`, which is doing the wrong thing IMO)
- Add a new `Contract.all_events()` equivalent to `Contract.all_functions()`

Then the example changes to:

```py
    filters = [
      event.createFilter(fromBlock='latest')
      for event in myContract.events
    ]
```

---

Of course, we could also implement `contract.create_filter()` like web3.js's `contract.events.allEvents`. I kind of like that the filters are event specific right now, though. I don't think it's too big a deal to require callers to write a filter loop on events.

Discussion:
reedsa: Committed a change to also sort the events by `blockNumber`, I think that makes more sense than just sorting by `logIndex`.

Patch:
diff --git a/docs/web3.contract.rst b/docs/web3.contract.rst
index ee836ca3..f5082e29 100644
--- a/docs/web3.contract.rst
+++ b/docs/web3.contract.rst
@@ -944,6 +944,8 @@ For example:
 
    Fetches all logs for a given event within the specified block range or block hash.
 
+   Returns a list of decoded event logs sorted by ``logIndex``.
+
     ``argument_filters`` is an optional dictionary argument that can be used to filter
     for logs where the event's argument values match the values provided in the
     dictionary. The keys must match the event argument names as they exist in the ABI.
diff --git a/3192.bugfix.rst b/newsfragments/3192.bugfix.rst
similarity index 100%
rename from 3192.bugfix.rst
rename to newsfragments/3192.bugfix.rst
diff --git a/newsfragments/3228.feature.rst b/newsfragments/3228.feature.rst
new file mode 100644
index 00000000..8a9b0a98
--- /dev/null
+++ b/newsfragments/3228.feature.rst
@@ -0,0 +1,1 @@
+Contract event ``get_logs`` results sorted by each ``ContractEvent`` ``logIndex``.
\ No newline at end of file
diff --git a/web3/contract/contract.py b/web3/contract/contract.py
index 93b5afe9..e6c1ab17 100644
--- a/web3/contract/contract.py
+++ b/web3/contract/contract.py
@@ -192,7 +192,9 @@ class ContractEvent(BaseContractEvent):
             all_event_logs,
             argument_filters,
         )
-        return filtered_logs
+        sorted_logs = sorted(filtered_logs, key=lambda e: e["logIndex"])
+        sorted_logs = sorted(sorted_logs, key=lambda e: e["blockNumber"])
+        return sorted_logs
 
     @combomethod
     def create_filter(


Test patch:
diff --git a/tests/conftest.py b/tests/conftest.py
index 6a4b5028..730ddb00 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -46,9 +46,10 @@ def emitter_contract_data():
     return EMITTER_CONTRACT_DATA
 
 
+# This class defines events for the EmitterContract and are used to construct
+# a fixture for contract event logs. Parameterized tests that utilize an `emitter`
+# contract fixture will use this data.
 class LogFunctions:
-    # These appear to be for a very specific test and this doesn't need to be updated
-    # for every event in the emitter contract. That ends up breaking that test.
     LogAnonymous = 0
     LogNoArguments = 1
     LogSingleArg = 2
@@ -74,6 +75,9 @@ def emitter_contract_event_ids():
     return LogFunctions
 
 
+# This class defines topics for the EmitterContract and are used to construct
+# a fixture for contract event log topics. Parameterized tests that utilize
+# an `emitter` contract fixture will use this data.
 class LogTopics:
     LogAnonymous = event_signature_to_log_topic("LogAnonymous()")
     LogNoArguments = event_signature_to_log_topic("LogNoArguments()")
diff --git a/tests/core/contracts/test_extracting_event_data.py b/tests/core/contracts/test_extracting_event_data.py
index 07e113db..630a2c04 100644
--- a/tests/core/contracts/test_extracting_event_data.py
+++ b/tests/core/contracts/test_extracting_event_data.py
@@ -331,6 +331,78 @@ def test_argument_extraction_strict_bytes_types(
     assert event_data["event"] == "LogListArgs"
 
 
+def test_contract_event_get_logs_sorted_by_log_index(w3, emitter, request_mocker):
+    get_logs_response = [
+        {
+            "type": "mined",
+            "logIndex": 10,
+            "transactionIndex": 0,
+            "transactionHash": "0xaef7f312d863780b861d8c38984b2a33f77e9508810735e2b042143f7f189f83",  # noqa: E501
+            "blockHash": "0x2200ec3324fdaca4ee2f4629489d2d06fb28108dae61b63b84ef39702e2b64e7",  # noqa: E501
+            "blockNumber": 3,
+            "address": "0xF2E246BB76DF876Cef8b38ae84130F4F55De395b",
+            "data": "0x",
+            "topics": [
+                "0x1e86022f78f8d04f8e3dfd13a2bdb280403e6632877c0dbee5e4eeb259908a5c"
+            ],
+        },
+        {
+            "type": "mined",
+            "logIndex": 0,
+            "transactionIndex": 0,
+            "transactionHash": "0x61e57bb1b5af14ca1b0964a84fb640bf39927961f26311a6450475a749e00cbb",  # noqa: E501
+            "blockHash": "0x73dd9a3b0f581689ebd67adea0debe05672a334c723379dc506fb71a666c1754",  # noqa: E501
+            "blockNumber": 4,
+            "address": "0xF2E246BB76DF876Cef8b38ae84130F4F55De395b",
+            "data": "0x",
+            "topics": [
+                "0x1e86022f78f8d04f8e3dfd13a2bdb280403e6632877c0dbee5e4eeb259908a5c"
+            ],
+        },
+        {
+            "type": "mined",
+            "logIndex": 123,
+            "transactionIndex": 0,
+            "transactionHash": "0x61e57bb1b5af14ca1b0964a84fb640bf39927961f26311a6450475a749e00cbb",  # noqa: E501
+            "blockHash": "0x73dd9a3b0f581689ebd67adea0debe05672a334c723379dc506fb71a666c1754",  # noqa: E501
+            "blockNumber": 1,
+            "address": "0xF2E246BB76DF876Cef8b38ae84130F4F55De395b",
+            "data": "0x",
+            "topics": [
+                "0x1e86022f78f8d04f8e3dfd13a2bdb280403e6632877c0dbee5e4eeb259908a5c"
+            ],
+        },
+        {
+            "type": "mined",
+            "logIndex": 54,
+            "transactionIndex": 0,
+            "transactionHash": "0x61e57bb1b5af14ca1b0964a84fb640bf39927961f26311a6450475a749e00cbb",  # noqa: E501
+            "blockHash": "0x73dd9a3b0f581689ebd67adea0debe05672a334c723379dc506fb71a666c1754",  # noqa: E501
+            "blockNumber": 1,
+            "address": "0xF2E246BB76DF876Cef8b38ae84130F4F55De395b",
+            "data": "0x",
+            "topics": [
+                "0x1e86022f78f8d04f8e3dfd13a2bdb280403e6632877c0dbee5e4eeb259908a5c"
+            ],
+        },
+    ]
+
+    with request_mocker(w3, mock_results={"eth_getLogs": get_logs_response}):
+        logs = emitter.events.LogNoArguments().get_logs()
+
+        sorted_logs = sorted(
+            emitter.events.LogNoArguments().get_logs(),
+            key=lambda l: l["logIndex"],
+        )
+        sorted_logs = sorted(
+            emitter.events.LogNoArguments().get_logs(),
+            key=lambda l: l["blockNumber"],
+        )
+
+    assert len(logs) == 4
+    assert logs == sorted_logs
+
+
 @pytest.mark.parametrize(
     "contract_fn,event_name,call_args,expected_args,warning_msg,process_receipt",
     (