Upgradeability Is a Bug

Steve Marx
Feb 5, 2019 · 5 min read
Image for post
Image for post

tl;dr

Why do we need smart contracts?

contract InvestmentOpportunity {
address public investor;
uint256 public payday;

constructor() public payable {}
function invest() external payable {
require(investor == address(0), "Someone beat you to it!");
require(msg.value == address(this).balance / 2,
"You must match the contract balance.");

investor = msg.sender;
payday = now + 24 hours;
}

function withdraw() external {
require(msg.sender == investor,
"Only the investor can withdraw.");
require(now >= payday,
"You must wait until the payday time.");
msg.sender.transfer(address(this).balance);
}
}

The importance of immutability

Upgradeability = mutability

contract Base {
// proxy state
address owner;
address implementation;
// implementation state
address public investor;
uint256 public payday;
}
contract Implementation is Base {
function invest() external payable {
require(investor == address(0), "Someone beat you to it!");
require(msg.value == address(this).balance / 2,
"You must match the contract balance.");

investor = msg.sender;
payday = now + 24 hours;
}

function withdraw() external {
require(msg.sender == investor,
"Only the investor can withdraw.");
require(now >= payday,
"You must wait until the payday time.");

msg.sender.transfer(address(this).balance);
}
}
contract Proxy is Base {
constructor(address _implementation) public payable {
owner = msg.sender;
implementation = _implementation;
}
function setImplementation(address _implementation) external {
require(msg.sender == owner);
implementation = _implementation;
}

// adapted from https://github.com/zeppelinos/labs/blob/master/upgradeability_using_eternal_storage/contracts/Proxy.sol
function() external payable {
address impl = implementation;
assembly {
let ptr := mload(0x40)
calldatacopy(ptr, 0, calldatasize)
let result := delegatecall(gas, impl, ptr,
calldatasize, 0, 0)
let size := returndatasize
returndatacopy(ptr, 0, size)

switch result
case 0 { revert(ptr, size) }
default { return(ptr, size) }
}
}
}
contract ExitScam is Base {
function exit() external {
require(msg.sender == owner);
selfdestruct(msg.sender);
}
}

Mitigations

Just say no

Limit mutability

Use parameters

Migration, not mutation

Final thoughts

ConsenSys Diligence

ConsenSys Diligence has the mission of solving Ethereum…

Sign up for Smart Contract Security Newsletter

By ConsenSys Diligence

The goal of this newsletter is to help you keep up with, (and understand) the latest attacks, threats and defenses, and security best practices in the blockchain and smart contract security. Take a look

Create a free Medium account to get Smart Contract Security Newsletter in your inbox.

Thanks to Maurelian, Bernhard Mueller, and Gerhard Wagner

Steve Marx

Written by

Working on Ethereum smart contract security at @ConsenSys. Co-creator of https://www.site44.com and https://programtheblockchain.com .

ConsenSys Diligence

ConsenSys Diligence has the mission of solving Ethereum smart contract security. Contact us for an audit at diligence@consensys.net.

Steve Marx

Written by

Working on Ethereum smart contract security at @ConsenSys. Co-creator of https://www.site44.com and https://programtheblockchain.com .

ConsenSys Diligence

ConsenSys Diligence has the mission of solving Ethereum smart contract security. Contact us for an audit at diligence@consensys.net.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch

Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore

Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store