The proxy pattern helps you upgrade smart contracts without losing data or changing the contract address. It separates logic from data, so you can fix bugs or add features easily.
Proxy pattern (upgradeable contracts) in Blockchain / Solidity
Start learning this pattern below
Jump into concepts and practice - no test required
contract Proxy {
address implementation;
fallback() external payable {
(bool success, ) = implementation.delegatecall(msg.data);
require(success);
}
function upgradeTo(address newImplementation) external {
implementation = newImplementation;
}
}The fallback function forwards calls to the logic contract using delegatecall.
The upgradeTo function changes the logic contract address to upgrade functionality.
contract Proxy {
address implementation;
fallback() external payable {
(bool success, ) = implementation.delegatecall(msg.data);
require(success);
}
function upgradeTo(address newImplementation) external {
implementation = newImplementation;
}
}contract LogicV1 {
uint public x;
function setX(uint _x) public {
x = _x;
}
}contract LogicV2 {
uint public x;
function setX(uint _x) public {
x = _x * 2;
}
}This example shows a proxy contract that forwards calls to a logic contract using delegatecall. The owner can upgrade the logic contract address. LogicV1 sets a value directly, LogicV2 doubles the value before setting it.
pragma solidity ^0.8.0; contract Proxy { address public implementation; address public owner; constructor(address _implementation) { implementation = _implementation; owner = msg.sender; } fallback() external payable { address impl = implementation; assembly { calldatacopy(0, 0, calldatasize()) let result := delegatecall(gas(), impl, 0, calldatasize(), 0, 0) let size := returndatasize() returndatacopy(0, 0, size) switch result case 0 { revert(0, size) } default { return(0, size) } } } function upgradeTo(address newImplementation) external { require(msg.sender == owner, "Only owner can upgrade"); implementation = newImplementation; } } contract LogicV1 { uint public x; function setX(uint _x) public { x = _x; } } contract LogicV2 { uint public x; function setX(uint _x) public { x = _x * 2; } }
Storage layout must be the same between logic versions to avoid corrupting data.
Only the proxy holds the data; logic contracts are stateless.
Use access control on upgrade functions to prevent unauthorized upgrades.
The proxy pattern separates data and logic to allow contract upgrades.
Calls to the proxy are forwarded to the current logic contract using delegatecall.
Upgrading means changing the logic contract address without changing the proxy address.
Practice
What is the main purpose of using the Proxy pattern in smart contracts?
Solution
Step 1: Understand the Proxy pattern role
The Proxy pattern allows a contract to forward calls to another contract, enabling upgrades.Step 2: Identify the main benefit
This forwarding lets you change the logic contract without changing the proxy's address.Final Answer:
To upgrade contract logic without changing the contract address -> Option AQuick Check:
Proxy pattern = Upgrade logic without address change [OK]
- Thinking proxy reduces gas fees
- Believing proxy creates contract copies
- Assuming proxy prevents all changes
Which Solidity keyword is used inside a proxy contract to forward calls to the implementation contract?
Solution
Step 1: Recall Solidity call types
Solidity has several low-level calls: call, delegatecall, send, transfer.Step 2: Identify forwarding call for proxy
Proxy contracts usedelegatecallto run implementation code in proxy's context.Final Answer:
delegatecall -> Option AQuick Check:
Proxy forwarding uses delegatecall [OK]
- Confusing call with delegatecall
- Using transfer or send which are for Ether
- Not knowing delegatecall preserves storage
Consider this simplified proxy contract snippet in Solidity:
contract Proxy {
address implementation;
fallback() external payable {
(bool success, ) = implementation.delegatecall(msg.data);
require(success);
}
}What happens if implementation address is zero?
Solution
Step 1: Understand delegatecall to zero address
Calling delegatecall on address zero means no code to execute.Step 2: Effect of delegatecall failure
delegatecall returns false on failure; require(success) then reverts transaction.Final Answer:
The call will fail and revert the transaction -> Option DQuick Check:
delegatecall to zero address = revert [OK]
- Assuming call succeeds silently
- Thinking fallback is skipped
- Believing contract self-destructs
Identify the bug in this proxy upgrade function:
function upgradeTo(address newImplementation) public {
implementation = newImplementation;
}What is the main issue?
Solution
Step 1: Check function access control
The function is public, so anyone can call it and change implementation.Step 2: Understand security risk
Without restricting access, attackers can hijack the contract logic.Final Answer:
No access control, anyone can upgrade implementation -> Option CQuick Check:
Upgrade function needs access control [OK]
- Ignoring access control importance
- Focusing only on event emission
- Thinking public vs external affects security
You want to upgrade a proxy contract to a new implementation that adds a new state variable. What must you ensure to avoid breaking storage layout?
Solution
Step 1: Understand storage layout importance
Proxy pattern requires storage layout consistency between implementations.Step 2: Correct way to add variables
New variables must be appended to avoid overwriting existing storage slots.Final Answer:
Add new variables only at the end of existing storage variables -> Option BQuick Check:
Storage layout consistency = append variables [OK]
- Rearranging variables breaks storage
- Removing old variables causes data loss
- Changing types shifts storage slots
