“One of the deeper features of Solidity are the low-level functions [call()],
[callcode()],
[delegatecall()], and
[send()]. Their behavior in accounting for errors is quite different from other Solidity functions, as they will not propagate (or bubble up) and will not lead to a total reversion of the current execution. Instead, they will return a boolean value set to false, and the code will continue to run. This can surprise developers and, if the return value of such low-level calls are not checked, can lead to fail-opens and other unwanted outcomes.” – DASPExample: King of Ether and EtherPot are given by DASP as examples of this vulnerability and while they didn’t result in large amounts of money lost, forgetting to check the return value of
[send()] caused the contracts to misbehave.
Clarity: Clarity addresses this in its type system. All publicly-callable functions in Clarity
must return a
[(response a b)] type, which encodes either an “everything is fine” type
[(ok a)] or a “something went wrong” type
[(err b)]. This is enforced by the consensus rules — you cannot create a public function that does
not do this. At the same time, the function’s caller must explicitly handle the
[ok] or
[err] cases in order to get the returned value of the function — their code won’t be instantiated if it doesn’t (so you can’t just ignore errors). In addition, if the top-level public function returns an
[(err b)] response, the transaction aborts (so unhandled errors will cause the transaction to abort). While this doesn’t prevent buggy error handling, it does prevent accidental omission of it.