EVM probibits writes to state in static context. One cannot write storage, send Ether, create/destruct contracts, log, etc. In this article we show that it's still possible write data to the state even the static context. ### Write state in static context The idea is to leverage the `accessed_storage_keys` set. When a storage key is accessed it is added to this set (exception being EIP-2930). If the key is accessed it is "warm". Accessing warm keys is cheaper than the "cold" ones. We'll leverage this gas price discrepancy to store information. To write information to state we just `SLOAD` storage slots. We can define an encoding which translates the access to different storage keys to different semantics. Example: touch the storage key `1234567` to store the information that a contract was called by the address `0xdeadbeef`. To read the information we again `SLOAD` the storage slots and see how much gas we were charged. If we were charged with cold access price then we know the slot wasn't touched. Example: read the `1234567` key - if the gas charged was under `GAS_COLD_SLOAD` (which is 2100) then we know that we encoded the information that the contract was called by `0xdeadbeef`. ### Implementation We implement a counter which counts in the `static` context how many times the contract was called. The caller calls the callee 10 times in the `static` context. The goal for the callee is to count how many times it was called: ```vyper= @external def call_calle() -> uint256: for i: uint256 in range(10): u: uint256 = staticcall self.callee.foo() return staticcall self.callee.get_num_of_accesses() ``` In `foo` we then store the call information via: ```vyper= @view def get_counter() -> uint256: gas_original: uint256 = msg.gas tmp: uint256 = 0 delta: uint256 = 0 for i: uint256 in range(LEN): tmp = self.counter[i] delta = gas_original - msg.gas if delta > GAS_COLD_SLOAD: return i else: gas_original = msg.gas return max_value(uint256) @view @external def foo() -> uint256: tmp: uint256 = self.get_counter() return 0 ``` The `get_counter` method loops until the `delta` in charged gas at least `GAS_COLD_SLOAD + 1`. In such case we know that we read a cold key. In the first iteration we will read `counter[0]` and return. In the second `counter[0]` and `counter[1]`. Other iterations follow the same pattern. To retrieve the information later (this works only in the **same** transaction) we call `get_counter` again: ```vyper= @view @external def get_num_of_accesses() -> uint256: return self.get_counter() ``` Run `pytest` to test that the `get_num_of_accesses()` really returns 10. Full source available at: https://github.com/cyberthirst/evm-static-context-write/tree/main ### Conclusion We showed that at the transaction level it is possible to write infromation to state even in the static execution context. The idea was to leverage the fact that accesses to storage are priced differently if the keys are warm/cold.