0

From the solidity docs:

Use the Checks-Effects-Interactions Pattern Most functions will first perform some checks (who called the function, are the arguments in range, did they send enough Ether, does the person have tokens, etc.). These checks should be done first.

As the second step, if all checks passed, effects to the state variables of the current contract should be made. Interaction with other contracts should be the very last step in any function.

Early contracts delayed some effects and waited for external function calls to return in a non-error state. This is often a serious mistake because of the re-entrancy problem explained above.

Note that, also, calls to known contracts might in turn cause calls to unknown contracts, so it is probably better to just always apply this pattern.

Does the statement "Interaction with other contracts should be the very last step in any function" also apply to "constant interactions", i.e., when calling a pure or view function in another contract?

I think that the answer is naturally No, however, the docs do emphasize any function.

Isn't this restriction a little too harsh? Or am I wrong?

Thank you!

2

Isn't this restriction a little too harsh?

It's not a "restriction." It's a pattern to help you avoid a certain class of bugs. There are certainly times when it's safe to violate this pattern, but it's a good rule of thumb.

Does the statement "Interaction with other contracts should be the very last step in any function" also apply to "constant interactions", i.e., when calling a pure or view function in another contract?

How do you know that the function you're calling is pure or view? If you're always calling into the same contract and you've audited its code, then you could probably safely ignore the Checks-Effects-Interactions pattern. But if you're calling into a contract that's determined at runtime, you have no idea if it's going to change state or not.

  • I don't understand what you mean by "the contract you're calling is pure or view". Interacting with a contract, as far as I understand this term, means calling a contract's function. When I say pure or view, I refer to that function, which I obviously know during compilation-time, regardless of whether or not I know the contract's address during compilation-time (how else would I call this function without knowing its prototype?). – goodvibration Aug 20 '18 at 15:16
  • 1
    Sorry, I should have said "function that you're calling" (edited now). The function signature doesn't exist at runtime. Calling a view or pure function looks just the same at runtime as calling a function that doesn't have one of those modifiers. – user19510 Aug 20 '18 at 16:04
2

Isn't this restriction a little too harsh?

Not really. It's just a counter-intuitive order of events. You can get used to using it in all cases. Eventually, code will look highly suspect to you if it doesn't follow the safe pattern.

Intuitive approach:

  1. Interact with a contract
  2. Check the result.
  3. Do something with the result.

Safe way:

  1. Optimistically deal with state changes (accounting) - assume success.
  2. Interact with the contract.
  3. Revert changes from step 1 if needed (.transfer()) does it automatically. revert() is an option, or manually return all values to their previous values in the unlikely event that you want to continue.

It may help to separate contracts into two categories: Those you trust and those you can't be certain about.

There is nothing wrong with, and you often can't avoid calling view and pure functions in your own system (trusted) and there is nothing wrong with that. You may also want to call view functions in other people's contracts that may be suspect - they have made it upgradable, for example, so there is no way ensure the interaction will always be safe even if you see the code today.

Given that those untrusted contracts may themselves inspect the state of your contract, there is a possibility of re-entrant class of exploits. A solution to that problem is to ensure that your state is completely in order before you transfer flow control to the untrusted contract. In other words, don't give them a chance to inspect a half-baked, incomplete transaction in progress because they invoke other functions in your contract or might return with an unexpected result your contract isn't prepared for.

In essence, put your guards up front and gather all the info you will need (checks). Record the complete update to your own state, including the expected results of the final steps, e.g. zero out a balance (effects). Lastly, state-changing operations in other "untrusted" contracts such as send, transfer or call. Notice if it failed and revert your state changes in that case.

Hope it helps.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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