(Part 3) Ethereum Solidity Development - Contract Mutability, DelegateCall And Calling Functions By Address(PT 3)

in #utopian-io6 years ago

Repository

https://github.com/igormuba/MutableSolidityContractEthereum

What Will I Learn?

  • How to achieve mutability WITHOUT using assembly
  • How to properly use the delegatecall
  • Change a function (using mutability)

Requirements

  • Internet connection
  • Code editor
  • Browser

Difficulty

  • Intermediate

Tutorial Contents

Introduction

Wow, this one has been the hardest tutorial so far, I have been working on this since yesterday because I had to test many mutability techniques.

Ethereum contracts are designed to be immutable, so you are not supposed to change them. But you can "hack it", the theory behind making contracts mutable relies on assembly code, when you use the delegate function call the EVM compiles an empty address that will be filled later.

I have tested many ways of doing this, but most of them were either old (do not work on Solidity 5 onwards) or need understanding of assembly.
To give you a taste, here is how the assembly version of the call would look like

note, I suck at machine language, so I have copied this assembly code from here
https://medium.com/quillhash/how-to-write-upgradable-smart-contracts-in-solidity-d8f1b95a0e9a

assembly {
      let result := delegatecall(gas, _impl, add(data, 0x20), mload(data), 0, 0)//this builds the payload
      let size := returndatasize //we need to know the payload size
      let ptr := mload(0x40) //the "injected" payload goes here
      returndatacopy(ptr, 0, size)
      switch result
      case 0 { revert(ptr, size) }
      default { return(ptr, size) }
    }

Yes, scary, but we don't have to use assembly, I am show you how below, this is very exciting because you can actually make mutable contracts using a relatively higher level syntax (we have functions to do lower level stuff for us!)

The design

We have 3 contracts, first we will build the main contract, the one that will not change, the way we achieve mutability is by putting the functions we want to change in another contract and give the address to the main one, so when we want to change the code we build a new contract and change the address we gave to the main to the new.

Sounds difficult, but this way you can understand better:

third contract: receives 2 numbers and sum them together
second contract: receives 2 numbers and multiply them
first contract: receives 2 numbers and do something, but we can change this something by telling it to send to the second or third contract

The bones

The three contracts must have the same variables to avoid compilation errors, you will see later, so let us set them up on Remix

The below is the main contract, the one the users will call without worrying about the code

pragma solidity ^0.5.0;

contract firstOne{
address linkedContract; //this is where we will put the real functions, as this is a variable we can change!!
uint256 total; //result of the operation
}

The below is the second contract, this one will do multiplication

contract secondOne{
    //the variable total will not be used, but it needs to be here
    //to avoid compilation error
   //because the compiler does not know this one is called from outside!
uint256 public total;
}

The third is the same as the second but it will do a sum

contract secondOne{
uint256 public total;
}

Changing the "mutable" part on the first contract

As you could see on the last section, the first step to achieve mutability is to have a "pointer" to tell the main contract where to find the real code, so the function below sets the linked contract, do it on the first, the one that will call the second and third

function setLinkedContract(address _newContract) public{ //received a contract address as argument
        linkedContract = _newContract; //stored on the variable contract
    }

Remember, after you deploy a contract you CAN NOT CREATE NEW VARIABLES, but you can change them! We exploit this to achieve mutability on something that is designed to be immutable

Doing a "generic calculation" on the first (main contract) by calling another one

This is the exciting part that got me stuck for 2 days, first I will show you the finished code, this code goes into the first and main contract, the generic one that delegates the calculation

function delegatedCalculation(uint firstNumber, uint secondNumber) public{ //receives the 2 numbers to do something
        bool status; //all delegatecall functions returns a bool if it was successfull or not
        bytes memory result; //we will have this variable on the second too
        (status, result) = linkedContract.delegatecall(abi.encodePacked(bytes4(keccak256("calculate(uint256,uint256)")), firstNumber, secondNumber)); //this is the hardcore part, most tutorials online use
//assembly, but you don't need to as solidity has functions, this basically created the bytecode
//for the payload that will be inserted into the empty bytecode, refer to the
//assembly code and the comments in it on the beginning of the tutorial
    }

The interesting part of the code is

linkedContract.delegatecall(abi.encodePacked(bytes4(keccak256("calculate(uint256,uint256)")), firstNumber, secondNumber))

Notice that the function and argument types it receive should be a string "calculate(uint256,uint256)"

the linked contract is the address of the contract with the real code
the delegatecall function receives a bytecode payload to send to the address that hosts it, you could manually generate it using a tool to generate the bytecode, there is a cool tool for that
https://abi.hashex.org/
But you can also use Solidity syntax to achieve the same (more expensive though!)

Finishing the first contract

The last thing left to do on the first contract is to just create a function to get the total to see if it worked (we still have to make the other contracts though!!)

function getTotal() public view returns (uint256){
        return total;
    }

Creating the contract that hosts the REAL code

On the second and third contract we need to make a function called calculate, so in both we do

function calculate(uint firstNumber, uint secondNumber) public returns (uint256){
        result = firstNumber*secondNumber; //on the second I put this a multiplication
//but on the third contract it is a sum
        total = result; //THIS CHANGES THE TOTAL IN THE MAIN CONTRACT, NOT ON THE SECOND
        return result;
    }

The code above is the same on the second and third, except that on the second it is a multiplication and on the third a sum, please, make those changes

Also, notice that the total=result; changes the variable total on the main contract! The delegatecall executes a foreigner function as if it was a function from the main one, so all variables you want to add need to be inside the function on the second contract as it ignores all the context outside the function inside the second contract!!!
Also, this is why they have to have the same variables outside, because the second one on compile time will throw an error because it does not know it is just gonna be used by being "delegatedcalled"

How to change and execute the mutable contract

By now we should have the 3 contracts ready, the main one that will call the function from the others, the second that does a multiplication and the third that does a sum

Note: the order below does not matter, but I am doing in order to be more organized when changing the main contract

First, compile and deploy (on testing environment) the main contract on remix

Now, deploy the second contract so we can get it's address

Copy the second contract (the multiplication) on Remix

Input the address on the setLinkedContract field and click it's pink button to execute the transaction

Now choose the numbers to do the multiplication

And get the total!

It works!
But it is not impressive yet, this is a mutable contract, so let us change it!

Deploy the third contract, copy it's address, just like you did with the second, and input the third contract address


Now, do the calculation again by inputting the same numbers as before and clicking the pink button and get the total, aaaaaaaaand.....

CONGRATULATIONS, YOU HAVE ACHIEVE MUTABILITY ON CONTRACTS, WE OFFICIALLY CAN NOT EVEN TRUST THE BLOCKCHAIN ANYMORE HAHA

Jokes aside, that is true, mutability defeats the purpouse of the blockchain being mutable, but some things are for good, because of mutability we can avoid exploits, contracts in the past have been hacked, and will keep being hacked as long as evil people exist
https://medium.com/new-alchemy/a-short-history-of-smart-contract-hacks-on-ethereum-1a30020b5fd

But by using mutability wisely you can do hot fixes for newly discovered bugs. No code is hackerproof, but we can make hackers life more difficult.

How to use mutability fairly

Yes, there is a discussion of whether mutability tricks (there are many techniques to achieve this, I have shown only one) are morally right or not. While we have no answer for that, we can make things more fair by saving a history of previous linked contracts! This way people can see what contracts were being used before and how many times have you changed the mutable contract. One way to do that is

Create the following variables to inform people

uint256 timesThisContractChanged; // straight forward a counter of how many changes has this contract had
address[] historyOfPreviousContracts;//list of old versions of the linked contract

And implement functions to retrieve that information

function getTimesThisContractChanged() view public returns(uint256){
        return timesThisContractChanged;
    }
    
    function getHistoryOfPreviousContracts() view public returns(address[] memory){
        return historyOfPreviousContracts;
    }

Now, after compiling and deploying all 3 contracts again, and after changing the linked contract to the second, and then to the third, we can see that it properly shows the information to keep track of contract changes, and the good thing is that they can trust this information, as this is the immutable part of the contract!

Curriculum

I AM A STEEM WITNESS

To vote for me, simply use the SteemConnect link below
https://steemconnect.com/sign/account-witness-vote?witness=igormuba&approve=1

or go to https://steemit.com/~witnesses

(note, it is /~witnesses, not /witness)
Scroll all the way down to "If you would like to vote for a witness outside of the top 100, enter the account name below to cast a vote."

type igormuba and click vote

Sort:  

Thank you for your contribution @igormuba.
After reviewing your tutorial we only have one suggestion:

  • We suggest that you be more careful about writing your text. Some small mistakes. Before publishing your post make sure the text is well formatted and without errors.

We are waiting for your next tutorial.

Your contribution has been evaluated according to Utopian policies and guidelines, as well as a predefined set of questions pertaining to the category.

To view those questions and the relevant answers related to your post, click here.


Need help? Write a ticket on https://support.utopian.io/.
Chat with us on Discord.
[utopian-moderator]

Thank you for remembering me, this one took me a bit more work than usual to figure out how to create the data payload properly and indeed I forgot to review before publishing

Posted using Partiko Android

Thank you for your review, @portugalcoin! Keep up the good work!

Hey, @igormuba!

Thanks for contributing on Utopian.
We’re already looking forward to your next contribution!

Get higher incentives and support Utopian.io!
Simply set @utopian.pay as a 5% (or higher) payout beneficiary on your contribution post (via SteemPlus or Steeditor).

Want to chat? Join us on Discord https://discord.gg/h52nFrV.

Vote for Utopian Witness!

Coin Marketplace

STEEM 0.18
TRX 0.15
JST 0.029
BTC 60775.02
ETH 2656.41
USDT 1.00
SBD 2.42