1

I would like to implement a process similar to what Nick Johnson wrote about some years ago.

https://weka.medium.com/dividend-bearing-tokens-on-ethereum-42d01c710657

I'm using 0.8.19 and changing the version is not feasible.

In earlier compilers, it was possible to wrap state-changing operations in a view function, e.g. by attaching a modifier. The state would not be updated in static calls, but the return values would reflect the result of the state changes that would have occurred. If the same code was executed in a transaction, then the changes would stick.

One could, for example, attend to housekeeping or garbage collection in modifiers applied to both view and mutating functions, which was very useful for avoiding repetition. Having used it, I know it worked at that time.

Modern compilers are very strict about this, which means that concise code needs considerable duplication and decomposition in order that view functions and updating functions to reach the same conclusion (I realize it's possible).

staticcall reverts if the target changes the state. We expect that to happen every time and it's okay if it's forgotten until next time. It will stick when there is a real transaction and someone pays the gas.

I've looked at https://github.com/gnosis/util-contracts.git, which looks promising. Either I can't get it to work or it has the same problem. Reverts if the target of the "simulation" modifies state. I haven't ruled out that the error is in my contract that tries to use it.

Is this even possible? I expect the EVM can do this unless a fork is explicitly blocking what used to work.

Pseudo:

pragma solidity 0.8.19;

...

function simulateUpdate() external view returns (uint256 nextUp) {
   nextUp = externalContract.mutatingFunction();
}

  • It's not feasible to change the contract we want to inspect on this "what if" basis.
  • It's not feasible to drop view from the function that wants to look (too disruptive).
  • It's acceptable if the changes (if any) stick.
  • It's acceptable if the changes (if any) revert.

In case it is illustrative, this compiles:

pragma solidity 0.4.17;

contract Target {

    uint256 x;

    function foo(uint256 y) external returns (uint256) {
        x = x+y;
        return x;
    }
}


contract Simulate {

    Target public target;
    uint256 public foo; // because a modifier can't return a value

    modifier getFoo (uint256 a) {
        foo = target.foo(a);
        _;
    }

    function Simulate() public {
        target = new Target();
    }

    // mutating activities in the view function
    function bar(uint256 y) external view getFoo(y) returns (uint256) {
        return foo;
    }
}
2
  • 1
    Starting with Solidity 0.5.0, the compiler began enforcing strict rules to ensure that view and pure functions do not modify the state of the contract. And I am not sure there is any trick to bypass this. Even with use of lower level assembly to implement the functionality. If you want this in order to simulate a transaction, Tenderly might be a total useful for such a thing. Though it too has its limitations.
    – Sky
    Jul 13, 2023 at 18:52
  • Some "view-like" functions that implement delayed updates. All works. UI uses static call. All good. The challenge is external contracts that want to use the information and strongly prefer that we use a view function so they don't have to make major changes. I'm finding there may not be any way around the rules but I don't want to rule it out without asking. Maybe a kind soul knows a magic trick? Jul 13, 2023 at 20:31

2 Answers 2

1

Considering you mentioned trying to avoid code duplication, It might be possible to approach this by separating the view vs storage elements of the function.

Example:

pragma solidity ^0.8.19;

contract Example {
    uint256 public data;

    // A view function to check the new state
    function seeNewData(uint256 input) public view returns (uint256) {
        return data + input;
    }

    // Another function to actually change the state
    function changeData(uint256 input) public {
        data = seeNewData(input);
    }
}

It's not a perfect solution,and it might be demanding to implement onto more complex logic. But this is the best "trick" I was able to come up with.

1
  • 1
    Yup. No other way I can see. There's a lot of storage manipulation using arrays that needs to be replicated by other means. Cant push and pop, for example, on memory arrays. Would be better, IMHO, if the compiler trusted the developer a little more. Why I'm asking if anyone knows a magic trick. Not looking good, lol. Jul 16, 2023 at 2:26
0

Thanks to @Sky for the strong hint.

It looks like it's prevented "on the EVM level", by the compiler, starting with 0.5.0, so I don't think there's a way around this, even with assembly.

Solidity 0.5.0 Release notes: https://docs.soliditylang.org/en/v0.5.0/

"Pure and view functions are now called using the opcode STATICCALL instead of CALL if the EVM version is Byzantium or later. This disallows state changes on the EVM level."

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.