BCDV 1013 - Advanced Smart Contracts
Accessing storage, Exception handling and Debugging
Dhruvin Parikh, July 2021
Topics
- Ethereum Smart Contract Storage
- Debugging Ethereum transactions
try/catch
in Solidity
- Custom errors in Solidity
Smart contract Storage
- This is where state variables are stored
- It is persistent between function calls and transactions
- It is a key-value mapping of 2256 keys; each value is 32 bytes long
State Variables Storage Layout
- One Astronomically large array
- Solidity generates code that saves variable values in their declaration order.
- Variable first was declared first, it’s stored in slot 0
- Items that need less than 32 bytes are packed together. See Rules
contract Sample {
uint first;
uint second;
}
Astronomically large array mental model
Fixed-Sized Variables storage layout
contract StorageTest {
uint256 a;
uint256[2] b;
struct Entry {
uint256 id;
uint256 value;
}
Entry c;
}
Dynamic-Sized Variables storage layout
contract StorageTest {
uint256 a;
uint256[2] b;
struct Entry {
uint256 id;
uint256 value;
}
Entry c;
Entry[] d;
}
Dynamic-Sized Variables storage layout
Mapping storage layout
contract StorageTest {
uint256 a;
uint256[2] b;
struct Entry {
uint256 id;
uint256 value;
}
Entry c;
Entry[] d;
mapping(uint256 => uint256) e;
mapping(uint256 => uint256) f;
}
Complex types storage layout
contract StorageTest {
uint256 a;
uint256[2] b;
struct Entry {
uint256 id;
uint256 value;
}
Entry c;
Entry[] d;
mapping(uint256 => uint256) e;
mapping(uint256 => uint256) f;
mapping(uint256 => uint256[]) g;
mapping(uint256 => uint256)[] h;
}
Complex types storage layout
- To find
g[123][0]
:function mapLocation(uint256 slot, uint256 key)
public pure returns (uint256) {
return uint256(keccak256(key, slot));
}
function arrLocation(uint256 slot, uint256 index, uint256 elementSize)
public pure returns (uint256) {
return uint256(keccak256(slot)) + (index * elementSize);
}
arrLoc = mapLocation(8, 123);
itemLoc = arrLocation(arrLoc, 0, 1);
Complex types storage layout
- To find
h[2][456]
:function mapLocation(uint256 slot, uint256 key)
public pure returns (uint256) {
return uint256(keccak256(key, slot));
}
function arrLocation(uint256 slot, uint256 index, uint256 elementSize)
public pure returns (uint256) {
return uint256(keccak256(slot)) + (index * elementSize);
}
mapLoc = arrLocation(9, 2, 1);
itemLoc = mapLocation(mapLoc, 456);
Packed Storage

Old way to try/catch
function execute(uint256 amount) external {
(bool success, bytes memory returnData) = address(this).call(
abi.encodeWithSignature("onlyEven(uint256)",amount)
);
if (success) { }
else { }
}
function onlyEven(uint256 a) public {
require(a % 2 == 0, "Ups! Reverting");
}
- Changes applied before/after the call are not rolled back here.
try/catch
(1)
- Handle external call failures
- React on failed external calls and contract creation.
- Cannot be used for internal function calls
- Call public function with
this
to make it external.
- Eventual
out of gas
error is caught by the low-level catch
clause.
out of gas
error is not caught if transaction executing code runs out of gas.
try/catch
(4)
- External calls
contract CalledContract {
function getTwo() public returns (uint256) {
return 2;
}
}
contract TryCatcher {
CalledContract public externalContract;
function execute() public returns (uint256, bool) {
try externalContract.getTwo() returns (uint256 v) {
uint256 newValue = v + 2;
return (newValue, true);
} catch {
emit CatchEvent();
}
}
}
try/catch
(Panic codes)
0x01
: assert evaluates to false.
0x11
: underflow/overflow.
0x12
: e.g. 5/0
or 23%0
.
0x21
: Converting a value that is too big or negative into an enum type.
0x31
: Call .pop()
on an empty array.
0x32
: bytesN or an array slice at an out-of-bounds or negative index (i.e. x[i] where i >= x.length or i < 0).
0x41
: too much memory or create large array.
0x51
: zero-initialized variable of internal function type.
Debugging Error (An Infinite loop)
function set(uint x) public {
while(true) {
myVariable = x;
}
}
Error: VM Exception while processing transaction: out of gas
Debugging Error (An Infinite Error check)
function set(uint x) public {
assert(x == 0);
myVariable = x;
}
Error: VM Exception while processing transaction: invalid opcode
Debugging Error (A function isn't operating as desired)
event Odd();
event Even();
function set(uint x) public {
myVariable = x;
if (x % 2 == 0) {
emit Odd();
} else {
emit Even();
}
}
Custom errors in solidity
- Older version :
revert("Insufficient funds.")
- Cons
- Expensive deploy cost
- Difficult to use dynamic information
error
(v0.8.4
& above) statement can be declared inside/outside of smart contracts
- Issue in
try/catch
for custom error
Custom errors in solidity : Example 1
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.4;
error Unauthorized();
contract VendingMachine {
address payable owner = payable(msg.sender);
function withdraw() public {
if (msg.sender != owner)
revert Unauthorized();
owner.transfer(address(this).balance);
}
}
Custom errors in solidity : Example 2
pragma solidity ^0.8.4;
error InsufficientBalance(uint256 available, uint256 required);
contract TestToken {
mapping(address => uint) balance;
function transfer(address to, uint256 amount) public {
if (amount > balance[msg.sender])
revert InsufficientBalance({
available: balance[msg.sender],
required: amount
});
balance[msg.sender] -= amount;
balance[to] += amount;
}
}
BCDV 1013 - Advanced Smart Contracts Accessing storage, Exception handling and Debugging Dhruvin Parikh, July 2021