top of page

The Basics of Smart Contracts 5: Fallback Methods, Modifiers and Events

  • andy1265
  • Jul 18, 2022
  • 6 min read

In the previous installment of this series we covered Contract constructors and Solidity's version of destructors, the selfdestruct method. In this article we will be looking at fallback functions and function modifiers. In this installment we will be using the contract from the second installment of this series as a base and as such all instructions for setting up the environment can be found in that article.


In order to execute a fallback function we are also going to look at how to call a contracts method from Javascript without an ABI or potentially an incorrect ABI.


Fallback Function?

Fallback functions are a type of function in Solidity executed when a call is made to a smart contract that either doesn't fit any of the available functions or has no function identifier. Fallback functions are also executed when a contract is sent Ether with no other data supplied (i.e. the data field of the tx is empty).


Fallback functions are different from standard functions in a number of ways. The main differences can be seen below:

  1. defined with the fallback keyword.

  2. Are always external.

  3. Has no name.

  4. Either take no arguments and returns no parameters.

  5. fallback functions can be payable and can only consume up to 2300 gas. Which is not a great deal.

  6. There can only be one fallback function per smart contract.

Function Modifiers

The main use case of modifiers is for automatically checking a condition, prior to executing a function. If the function does not meet the modifier requirement, an exception is thrown, and the function execution stops.


To begin with lets look at the modifier we will be using in our smart contract below:

modifier passPhrase(string memory _magicWord) {
        require(keccak256(bytes(magicWord)) ==                keccak256(bytes(_magicWord)), "Invalid Passphrase!");
        _;
  }

This does a simple comparison of two hashes. The keccak256 of magicWord and _magicWord, two variables we will see is our contract later.


These modifiers are declared in the function definition and simply check something before the function runs. Should the check pass the functions code will be executed and if it fails a value can be returned, error thrown etc.


Events

Events in Solidity are used to track the execution of any call sent to a contract. You can subscribe to these events using a web3 JSON-RPC interface as we have done in the example below.


Events are inheritable members of contracts which store the arguments passed in the transaction logs when emitted. Generally, events are used to inform the calling application about the current state of the contract, with the help of the logging facility of EVM. Events notify the applications about the change made to the contracts and applications which can be used to execute the dependent logic.


Events are defined within the contracts as global and called within their functions. Events can take a parameter list and can be given a set of instructions to execute much like a normal function. The information and values are saved as part of the transactions inside the block.


Setting up the Environment

Environment setup is the same as in the 2nd installment of this series and can be found here. The steps are exactly the same for this installment. All that will need to be changed is the contract code and javascript solution, everything else should work out of the box.


Solidity Contract Code

The following code is the smart contract we will be using in this installment. It has an event which we will look at briefly here and then in more detail in the next installment. It also has a modifier, a constructor, 2 view functions and a fallback function.

// SPDX-License-Identifier: MIT
pragma solidity >= 0.8.11;

contract greeting {
  string private magicWord;
  event fallbackActivated(string);

  modifier passPhrase(string memory _magicWord) {
        require(keccak256(bytes(magicWord)) == keccak256(bytes(_magicWord)), "Invalid Passphrase!");
        _;
  }

  constructor () {
    magicWord = "superSecretPassPhrase";
  }

  function checkPassPhrase(string memory _magicWord) public view passPhrase(_magicWord) returns (string memory){
    return "The magic words matched!";
  }

  function getPassPhrase() public view returns (string memory) {
    return magicWord;
  }

  fallback() external {
    emit fallbackActivated("The fallback method has been activated!");
  }
}

Starting from the top let's go over all the things we have not seen before in this contract. That has us starting with our event. Events in solidity are used to track the execution of a transaction that is sent to a contract.

event fallbackActivated(string);

We will catch our event in our Javascript and print the output so we can see that our fallback function has activated.


Next we have our modifier which we looked at previously. We shall continue where we left off above.

modifier passPhrase(string memory _magicWord) {
        require(keccak256(bytes(magicWord)) ==                keccak256(bytes(_magicWord)), "Invalid Passphrase!");
        _;
  }

Following the comparison we discussed above we have the _; symbol. This is called a merge wildcard. It merges the function code with the modifier code where the _; is placed.


In other terms, the body of the function (to which the modifier is attached to) will be inserted where the special symbol _; appears in the modifier’s definition. It is a requirement that modifiers have this in their body.


Finally we have our fallback function. This simply emits the event we discussed above and returns as such this fallback function is not payable however normally the fallback function would be payable as a common design pattern is to use the fallback function to catch calls which send Ether incorrectly and would normally fail.

fallback() external {
    emit fallbackActivated("The fallback method has been activated!");
  }

As such if we correctly catch our event after our fallback function is activated we should recieve the string "The fallback method has been activated!".


Calling the contract with Javascript

This again is very similar to last time however with some differences we will note later in our javascript code. As with last time first grab your ABI:

solcjs --abi path/to/your/greeting.sol

This will create a file with a JSON string which will look something like the following:

[{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"string","name":"","type":"string"}],"name":"fallbackActivated","type":"event"},{"stateMutability":"nonpayable","type":"fallback"},{"inputs":[{"internalType":"string","name":"_magicWord","type":"string"}],"name":"checkPassPhrase","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"a","type":"string"},{"internalType":"string","name":"b","type":"string"}],"name":"compareStrings","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"getPassPhrase","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"}, {"inputs":[],"name":"notAMethod","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"}]

Now that we have these two we are ready to start writing our JavaScript. Take the Javascript below and paste it into the file that was just created.

const ethers = require('ethers');
const Web3 = require('web3');

const web3 = new Web3('ws://localhost:8545');

const signer = new ethers.Wallet("0xf214f2b2cd398c806f84e317254e0f0b801d0643303237d97a22a48e01628897", ethers.getDefaultProvider("http://localhost:8545"));
const contract_abi = [{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"string","name":"","type":"string"}],"name":"fallbackActivated","type":"event"},{"stateMutability":"nonpayable","type":"fallback"},{"inputs":[{"internalType":"string","name":"_magicWord","type":"string"}],"name":"checkPassPhrase","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"a","type":"string"},{"internalType":"string","name":"b","type":"string"}],"name":"compareStrings","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"getPassPhrase","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"}, {"inputs":[],"name":"notAMethod","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"}]
const contract_address = "0x5FbDB2315678afecb367f032d93F642f64180aa3";
var Contract = new web3.eth.Contract(contract_abi, contract_address);

async function contractCall(){
  Contract.events.fallbackActivated({})
    .on('data', async function(event){
        console.log(event.returnValues);
        return process.exit(1);
    })
    .on('error', console.error);
  console.log(await Contract.methods.checkPassPhrase("superSecretPassPhrase").call());
  var tx = await Contract.methods.notAMethod().send({from: signer.address, gas: 30000000});
}

contractCall();

There are 3 things we need to look at in this javascript code that we have not covered previously. That is the new Web3 provider, the event listener and the call to the fallback function. As such let's begin with the new Web3 provider.

const web3 = new Web3('ws://localhost:8545');

Previously we had been using a HttpProvider however HttpProviders can not listen for events. Instead we need to use a web socket provider. Following on from that we will now look at the event listener code:

Contract.events.fallbackActivated({})
    .on('data', async function(event){
        console.log(event.returnValues);
        return process.exit(1);
    })
    .on('error', console.error);

The above code listens for an event named "fallbackActivated" which is defined in the ABI and upon receiving the event notification prints the return value to stdout using console.log then exits the process. This is done as the event is the last thing to happen in our process execution sequence.


Next we get to the Javascript to call a method not in the ABI we will add a section to our ABI detailing a function that does not exist. There are many ways to go about getting a fallback method to execute such as sending ether to the contract without calling a specific function. This one simulates a user using an incorrect ABI:

{"inputs":[],"name":"notAMethod","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"}]

The code below executes a function of our contract named notAFunction which is not in our contract and as such activates our fallback function which emits the "fallbackActivated" event.

var tx = await Contract.methods.notAMethod().send({from: signer.address, gas: 30000000});

Now our JavaScript caller is complete we can test it out and look at the output. Simply run the following and take a look at the output.

node ContractCall.js

You should see output that looks something like the following:

The magic words matched!
Result { '0': 'The fallback method has been activated!' }

Excellent, we now have seen how we set function modifiers, activated fallback functions, listen for events and call functions not defined in our contract.


Challenges

  1. Implement the smart contract and javascript solution in this article on a private blockchain.

  2. What happens if you define more than 1 fallback method?

  3. What happens if you emit the _ from a modifier?

  4. Call the fallback method in a different way.

  5. How can you check an event by just parsing the transaction receipt of the call to notAMethod?










 
 
 

Recent Posts

See All

Comments


bottom of page