Maybe you heard about upgrading smart contracts. If you didn't probably you would like: Wait... I thought smart contracts are immutable. And you are right. Smart contracts are immutable. But still, we can change the flow.
What does it mean Immutable
Immutable means: unchanging over time or unable to be changed. And that is how smart contracts work.
What does it mean Upgrade
Upgrade means: raise (something) to a higher standard, in particular improve (equipment or machinery) by adding or replacing components. So upgradeable things are mutable, right? Not so much.
How to upgrade a smart contract?
Okay, let's dive into it directly. In solidity, there is an opcode named delegatecall. This opcode calls another smart contract with its own state. Here is a real-world example of this: Think that your car is broken and you call the technical service worker for your car's company. Technical staff come in your place and repair your car, right? Not his own car, your car. That is how delegatecall works. Your car is your state, and the technical person's car is his state. You requested a functional job from the element and he worked on your state, not his own state.
That is how delegatecall is works. You can read more about it from here
So we have two contracts like that:
interface Logic {
function withdraw() external;
}
contract ImmutableContract {
mapping(address => uint) public balances;
address public implementation;
address owner;
constructor(address _impl) {
implementation = _impl;
owner = msg.sender;
}
function deposit() external payable {
balances[msg.sender] += balances;
}
function withdraw() external {
Logic(implementation).withdraw();
}
function setImplementation(address _impl) {
require(msg.sender == owner, "You are not the owner");
implementation = _impl;
}
}
contract NotSoMuchImmutable {
mapping(address => uint) balances;
address public implementation;
function withdraw() external {
payable(msg.sender).send(balances[msg.sender]);
}
}
What we got here:
- An interface named Logic. So, as you can understand we have another contract that implements the logic.
- Deposit and withdraw functions. That means it is a bank, do not oppose it, it is a bank.
- And our logic contract.
- Lastly, a function for changing implementation's address.
What we expect:
- When we deposit our money, our balance will increase as much as the money we invest.
- When we withdraw, we will get the all money in our account.
Yeah, you read the smart contract of my bank and you invested your money. Because I am a trustable man and my contract is too. So, you invested your life savings in my bank, because you don't want to spend it.
And, you are retired. You are an old person. You wanted to take your money and go to the coastal city to take a breath. You asked your grandson to withdraw your money with the withdraw function.
He said: You don't have any money in that bank. You took him from the computer and you ran the withdraw function and you got the same result. But how?
When I got too much money in my bank I change the implementation address. My new implementation looks like this:
contract CryBitches {
mapping(address => uint) balances;
address public implementation;
address owner;
function withdraw() external {
payable(owner).send(balances[msg.sender]);
}
}
So when you tried to withdraw your money, you send your money to me by your hands, well, actually with your grandson's hands.
Conclusion
We inspect the mutable contracts. As you can see contracts are not mutable, but variables are. We are trying to build a trustless world with blockchain and smart contracts. But people build upgradeable contracts.
This is a serious discussion. Because the people who are using upgradeable contracts have a reason. They say what if there is a bug in the contract? Actually from my perspective, if there is a bug, you lose the game. We have to test as we can do before we deploy it.
You decide to use or not use those contracts. Have a good day.