Preventing Re-Entrance in Solidity

How do we prevent individuals from re-entering themselves into a smart contract?

By Ron Gierlach

I recently was tasked with writing a smart contract that would equally distribute sent funds between several recipients, all of whom could publicly add themselves to the contract.

Simple enough it would seem; hold a list of recipient addresses and add a sender's address when they call the public becomeRecipient function.

contract FundDistributor {
  address   public owner;
  address[] public recipients;

  function FundDistributor () {
    owner = msg.sender;
  }

  function becomeRecipient () public {
    recipients.push(msg.sender);
  }

  function distributeFunds () payable {
    /* ... */
  }
}

It is pretty obvious that in the contract's current form, a recipient could repeatedly re-enter themselves and receive a larger share of the distributed funds.

Given the contract's existing state variables, checking if a sender's address is in the list of recipients would require a linear search. Not very fancy coding!

Okay fine, let's use a mapping instead. It should store keys as addresses and values as booleans.

contract FundDistributor {
  address   public owner;
  mapping(address => bool) public recipients;

  modifier notRecipient () {
    require(!recipients[msg.sender]);
    _;
  }

  function FundDistributor () {
    owner = msg.sender;
  }

  function becomeRecipient () notRecipient() public {
    recipients[msg.sender] = true;
  }

  function distributeFunds () payable {
    /* ... */
  }
}

Booleans will default to false. So anytime someone calls becomeRecipient we can update the mapping with a key/value pair so that when the recipient address is looked up, it returns true.

This makes writing a modifier to prevent re-entrance pretty straight forward! Simply lookup the sender's address in the recipients mapping and throw if the value is true.

However, mappings in Solidity are not iterable. This poses an issue if we'd like to somehow display our recipients in our UI or even send funds to each recipient. There are library contracts that will provide this functionality for us, but I think the solution in our case is pretty simple.

Use both an array and a mapping! The array to hold recipient addresses and the mapping to hold a recipient's index on the list.

contract FundDistributor {
  address   public owner;
  address[] public recipients;
  mapping(address => uint) public recipientIndexes;

  modifier notRecipient () {
    if (recipients.length > 0) {
      uint index = recipientIndexes[msg.sender];
      address recipient = recipients[index];
      require(msg.sender != recipients[index]);        
    }
    _;
  }

  function FundDistributor () {
    owner = msg.sender;
  }

  function becomeRecipient () notRecipient() public {
    // push returns the new length of the list
    recipientIndexes[msg.sender] = recipients.push(msg.sender) - 1;
  }

  function distributeFunds () payable {
    /* ... */
  }
}

By updating the contract we've secured the ability to look up previous recipients and keep our array of addresses in tact!