Security in Smart Contracts. Ethernet N0: Web3, ABI

19/01/2022




Security in Smart Contracts is a topic that I find passionate and that is very important in the blockchain ecosystem, at the time of writing this first entry on my blog, 80 million dollars have just been stolen from the Qubit protocol.

In this series of posts, we are going to be solving the security challenges in smart contracts posed in the Ethernaut game, developed by the Open Zepellin team, a world-renowned company in this area.

The idea of this series is to understand the concepts that each level requires us to understand in order to pass them correctly and not to simply decide what code to write to pass the level.


How we interact with the challenge contract

Something to keep in mind is that the code of all smart contracts is compiled by the Ethereum Virtual Machine (EVM) in two different formats:

  • Aplication Binary Interface (ABI): It is a layer in JSON format that gives us the way in which we will communicate with the contract (written in Solidity) with another language (Javascript or Python for example).
  • Bytecode: Low-level binary code that is interpreted by the EVM.


When we create a new instance of the level, what is happening is that Ethernaut (Open Zeppelin) deploys the contract belonging to that level to a new address within the Ropsten testnet.

By having Metamask installed in our browser, by default we can use the Web3 library. What this does is allow us to interact with the ABI of the instance of the level that has just been deployed, allowing us to call functions and variables of the contract (as long as the visibility and access parameters of these allow us) from our console.


Overcoming the challenge

To pass this level, we are required to guess the password and be able to call the authenticate function correctly so that the boolean variable "clared" becomes true.

The password state variable is defined in the constructor (the constructor is a function that is executed only once at the time the contract is deployed):

constructor(string memory _password) public {

  password = _password;

}


This state variable is stored as a public string:

string public password;

For all public variables, Solidity automatically generates the getter function with the same name ("nameVariable()"), that is, we can access the value of this variable inside and outside the contract.
In order to see its value, what we can do is call this getter function of the variable within our console:

await contract.password();

We use await because this operation returns a promise.

Now that we know the value of the password variable, what we do is call the authenticate function, passing this value as an argument.

await contract.authenticate("password-value");

Note: Since we are changing the status of the contract with this function, we need to pay a commission (gas) for the transaction.

To be sure that the variable "clared" returned true, we cannot call the getter function of that variable, i.e. "contract.clared()", since the variable is declared private (later we will see that although the variables are private within the contract we can also know their value within the blockchain). To check that we passed the level we can call the getCleared function which returns the value of this variable.

await contract.getClared();