Posted on Leave a comment

S-Contract Access Modifiers

Upcoming Changes to Solidity and How They Function

GUARD FUNCTIONS in Solidity : Revert(), Assert(), and Require() , and the New REVERT Opcode in the EVM.

The release of Solidity version 0.4.10 introduced the functions assert(), require()and revert() . Confusion has reigned ever since.

The assert() and require() “guard” functions improve the readability of contract code, but differentiating between them can be quite confounding.

In this article, I’ll:

  1. explain the problem these functions solve.
  2. discuss how the Solidity compiler handles the new assert(), require()and revert()functions
  3. Give some rules of thumb for deciding how and when to use each one.

For convenience, I’ve created a simple contract using each of these features which you can test out in remix here.

If you really just want a TLDR version, this answer on the ethereum stackexchange should suffice.

Patterns for error handling in Solidity

re: obsolete keyword “Throw”

Supplanted by new functions assert(), require(), and revert() 

These provide the same functionality, with a much cleaner syntax.

Illustration: The old throw pattern way: 

throw and then if …

Let’s say your contract has a few special functions that are designed to be callable only by a particular address. Let’s say the address is designated as owner.

Prior to Solidity 0.4.10 (and for a while thereafter), the following was a common pattern for enforcing permissions:

contract HasAnOwner {
address owner;

function useSuperPowers(){
if (msg.sender != owner) { throw; }
// do something only the owner should be allowed to do
}
}

If the useSuperPowers() function is called by anyone other than owner, the function will throw. The result is that it would thereby return an error:invalid opcode , undo all state changes, and use up all remaining gas (see this article for more on gas and fees in ethereum).

Happily, the throw keyword is now being deprecated, and eventually will be removed altogether. Fortunately, the new functions assert(), require(), and revert() provide the same functionality, with a much cleaner syntax.

Life after “throw”

Let’s look at the new way to update that if .. throw pattern with our new guard functions.

The line:

if(msg.sender != owner) { throw; }

succinctly substitutes for all the following:

  • if(msg.sender != owner) { revert(); }
  • assert(msg.sender == owner);
  • require(msg.sender == owner);

Note that in the assert() and require() examples, the conditional statement is an inversion of the if block’s condition, switching the comparison operator !=to ==.

Differentiating between assert() and require()

First, to help separate these ‘guard’ functions in your mind, imagine assert()as an overly assertive bully, who steals all your gas.Then imagine require()as a polite managerial type, who calls out your errors, but is more forgiving.

With that mnemonic handy, what’s the real difference between these two functions?

Prior to the Byzantium network upgrade, require() and assert() actually behave identically, but their bytecode output is slightly different.

  1. assert() uses the 0xfe opcode to cause an error condition
  2. require() uses the 0xfd opcode to cause an error condition

If you look up either of those opcodes in the yellow paper, you won’t find them. This is why you see the invalid opcode error, because there’s no specification for how a client should handle them.

That will change however after Byzantium, and the implemention of EIP-140: REVERT instruction in the Ethereum Virtual Machine . Then the0xfd opcode will be mapped to theREVERT instruction.

This is what I find really fascinating:

Many contracts have been deployed since version 0.4.10, which include a new opcode lying dormant, until it’s no longer invalid. At the appointed time, it will wake up, and become REVERT!

Note: throw andrevert() also use 0xfd. Prior to 0.4.10. throw used 0xfe.

What the REVERT opcode will do

REVERT will still undo all state changes, but it will be handled differently than an “invalid opcode” in two ways:

  1. It will allow you to return a value.
  2. It will refund any remaining gas to the caller.

1. It will allow you to return a value

Most smart contract developers are quite familiar with the notoriously unhelpful invalid opcode error. Fortunately, we’ll soon be able to return an error message, or a number corresponding to an error type.

That will look something like this:

revert(‘Something bad happened’);

or

require(condition, ‘Something bad happened’);

Note: solidity doesn’t support this return value argument yet, but you can watch this issue for that update.

2. Refund the remaining gas to the caller

Currently, when your contract throws it uses up any remaining gas. This can result in a very generous donation to miners, and often ends up costing users a lot of money.

Once REVERT is implemented in the EVM, it will be plain old bad manners not to use it to refund the excess gas.

Choosing between revert(), assert() and require()

So, ifrevert() and require() both refund any left over gas, AND allow you to return a value, why would want to burn up gas using assert()?

The difference lies in the bytecode output, and for this I’ll quote from the docs(emphasis mine):

The require function should be used to ensure valid conditions, such as inputs, or contract state variables are met, or to validate return values from calls to external contracts. If used properly, analysis tools can evaluate your contract to identify the conditions and function calls which will reach a failing assert.Properly functioning code should never reach a failing assert statement; if this happens there is a bug in your contract which you should fix.

To clarify that somewhat: it should be considered a normal and healthy occurrence for a require() statement to fail (same with revert()). When an assert() statement fails, something very wrong and unexpected has happened, and you need to fix your code.

By following this guidance, static analysis and formal verification tools will be able to examine your contracts to find and prove the conditions which could break your contract, or to prove that your contract operates as designed without flaws.

In practice, I use a few heuristics to help me decide which is appropriate.

Use require()to:

  • Validate user inputs ie. require(input<20);
  • Validate the response from an external contract ie. require(external.send(amount));
  • Validate state conditions prior to execution, ie. require(block.number > SOME_BLOCK_NUMBER) or require(balance[msg.sender]>=amount)
  • Generally, you should use require most often
  • Generally, it will be used towards the beginning of a function

There are many examples of require() in use for such things in our Smart Contract Best Practices.

Use revert()to:

  • Handle the same type of situations as require(), but with more complex logic.

If you have some complex nested if/else logic flow, you may find that it makes sense to use revert() instead of require(). Keep in mind though, complex logic is a code smell.

Use assert() to:

  • Check for overflow/underflow, ie. c = a+b; assert(c > b)
  • Check invariants, ie. assert(this.balance >= totalSupply);
  • Validate state after making changes
  • Prevent conditions which should never, ever be possible
  • Generally, you will probably use assert less often
  • Generally, it will be used towards the end of a function.

Basically, require() should be your go to function for checking conditions, assert() is just there to prevent anything really bad from happening, but it shouldn’t be possible for the condition to evaluate to false.

Also: “you should not use assert blindly for overflow checking but only if you think that previous checks (either using ifor require) would make an overflow impossible”. — comment from @chriseth

Conclusion

These functions are very powerful tools for your security toolbox. Knowing how and when to use them will not only help prevent vulnerabilities, but also make your code more user friendly, and future proof against upcoming changes.

Access Modifiers & Applications
1:01/7:30 On completion of this lesson, you will be able to explain usage of function modifiers, explain the use of the “require” clause for input validation, illustrate the “assert” declaration for post-condition checking, discuss “reverting” a transaction and the “reward” function. The main intent of smart contract transaction is to execute a function. However, smart contracts often require control over who or what can execute the function, at what time a function needs to be executed, and what are the preconditions to be met before getting access to the function.
It’s a good habit to validate input values to the function so as to avoid unnecessary execution and waste of gas.

If there are any errors found during the input validation, these have to be handled appropriately. At the end of the function execution, you may want to assert that certain conditions have been met to ensure the integrity of the smart contract. Let’s begin with an important feature of Solidity: modifiers.They can address some of the concerns just mentioned.
Modifiers can change the behavior of a function. That’s why this feature is referred to as a modifier. It is also known as a function modifier since it is specified at the entry to a function and executed before the execution of the function begins.
You can think of a modifier as a gatekeeper protecting a function. A modifier typically checks a condition using a “require” so that if the condition failed, the transaction that called the function can be reverted. This is achieved with the “revert” function. This will completely reject the transaction and revert all its state changes. It will not be recorded on the blockchain.
Let’s understand the modifier and the require clauses using the functions in the Ballot Smart Contract. To the “register” function we’ll add a modifier “onlyBy(chairperson)”. Will do that via the following steps.
Define modifier for the clause onlyBy(chairperson). Add a special notation underscore add a semicolon to the modifier definition that includes the function.
Using the modifier clause in the function header. Step 1 is a definition of the modifier “onlyBy”,
only the chairperson who created the smart contract is authorized to register any new orders.
Step 2, shows how to make the modifier include a place holder for any function.
Step 3, use the modifier clause in the header of the function definition.
Now, let’s change the existing if statement of the Ballot smart contract such that it reverses the transaction on input validation failure. Why? Because we don’t need to waste blockchain resources for a failed transaction. Next, we’ll illustrate “assert” using a function payoff that computes and pays off bets. It is preferable that the assert not fail and that it requires checking the balance after each payoff in the above case.
We are making sure bank has a balance of $10,000 after all payoffs. Under normal circumstances, assert should not fail. This is more for handling an anomalies,flaws or malicious events. In such exceptional cases, the condition would be that the bank balance would somehow dip below the required reserves.
The following picture summarizes all the features we discussed. Modifiers and error handlers, and where they are typically used. The rules, laws, policies, and governance conditions are coded as modifiers. You can use the modifiers as gatekeepers for the functions. If your transaction to invoke the function does not meet the condition specified at the header of the function, your transaction will be reverted. It will not be recorded onto the block chain.
Modifiers can also be used to validate rules external to the function. For example, rules described at the opening of the lesson, who has access to the function, at what time, and what condition, etc.
Once the screening conditions are satisfied, input validation can be carried out inside the function using require declaration statement. In this case, also the transaction will be reverted on failed validation. This is done before the execution of the function. There may be anomalies, flaws or malicious code that could result in unexpected outcomes and create
exceptions. These can be caught, usually at the end of the function, or sometimes within the function using assert declarative statement, and the entire transaction including the function execution and the state change will be reverted.
Here is an example of an online digital media “Bazaar”.
Input condition as specified by a modifier is atLeast5Sellers. This enforces the condition that there should be atLeast5Sellers with their products before a buyer initiates a buy message through a transaction.
This is a global condition for all buyers. Therefore, it is appropriate to install it using a modifier declarative statement.
Once there are enough sellers, a buyer can buy. Inside the function, we verify if the buyer has sufficient funds to buy the specific item requested. If not, the transaction is reverted. Once the verification is satisfied, money is transferred to the seller, and item is transferred to the buyer.
We have a simple assert declaration at the end that the bought item should have been transferred. After all, it’s a digital item. This point is reached only if the item has not been transferred or could not be transferred due to exceptional circumstances. This was a simplified example of concepts learned in this lesson.
In summary, function modifiers along with state reverting functions of revert, require, and assert, collectively support and lend a robust error handling-approach for a Smart Contract.
These declarative features can be used to perform formal verification and static analysis of a Smart Contract to make sure it implements the intent of its creator.