Proxy Contract Example Code: Bank (Solidity)
- ✅
Proxy: Delegates calls to another logic contract (upgradeable). - 🏦
Bank: The original logic contract with deposit/withdraw. - 🔁
Bank2: The upgraded version of the Bank with an added transfer function.
🧠 Goal: Upgradeable Smart Contracts Using Proxy Pattern
This structure allows you to:
- Deploy one permanent proxy that users interact with.
- Swap the underlying logic contract (Bank → Bank2) over time.
- Keep all user balances and contract state persistent via
delegatecall.
📁 1. Proxy Contract
contract Proxy {
address public implementation;
address public admin;
constructor(address _implementation) {
implementation = _implementation;
admin = msg.sender;
}
function upgrade(address newImplementation) public {
implementation = newImplementation;
}
fallback() external payable {
address impl = implementation;
assembly {
calldatacopy(0, 0, calldatasize())
let result := delegatecall(gas(), impl, 0, calldatasize(), 0, 0)
returndatacopy(0, 0, returndatasize())
switch result
case 0 { revert(0, returndatasize()) }
default { return(0, returndatasize()) }
}
}
receive() external payable {}
}
🔍 What It Does:
- Stores the address of the logic contract in
implementation. - Lets the
adminupgrade to a new logic contract usingupgrade(). - Forwards any calls it doesn’t recognize to
implementationusingdelegatecall.
delegatecall runs the logic contract in the context of the proxy, meaning the proxy’s storage is used — not the logic contract’s.
🏦 2. Bank (Initial Logic Contract)
contract Bank {
mapping(address => uint) public balances;
function deposit() public payable {
balances[msg.sender] += msg.value;
}
function withdraw(uint amount) public payable {
require(balances[msg.sender] >= amount, "Insufficient balances to withdraw");
balances[msg.sender] -= amount;
payable(msg.sender).transfer(amount);
}
}
💡 What It Does:
- Users can deposit Ether and it updates their balance.
- Users can withdraw Ether from their balance.
- Data is stored in the
balancesmapping.
When called through the proxy, the balances are stored in the proxy’s storage — this is crucial for upgradability.
🔁 3. Bank2 (Upgraded Logic Contract)
contract Bank2 {
mapping(address => uint) public balances;
function deposit(uint amount) public payable {
balances[msg.sender] += amount;
}
function withdraw(uint256 amount) public payable {
require(balances[msg.sender] >= amount, "Insufficient balances");
balances[msg.sender] -= amount;
}
function transfer(address recipient, uint256 amount) public {
require(balances[msg.sender] >= amount, "Insufficient balance to send");
balances[msg.sender] -= amount;
balances[recipient] += amount;
}
}
🆕 What’s New:
- Same
balanceslayout — this is crucial so storage mapping works with the proxy. - Adds a
transfer()function so users can send balance directly to others. withdraw()doesn’t transfer Ether here — possibly a placeholder or error.
Always ensure the storage layout matches when upgrading logic contracts to avoid storage corruption.
🧪 Example Usage Flow
🔧 Deployment Flow
- Deploy
Bankcontract. - Deploy
Proxy, passingBank’s address to constructor. - Interact with proxy:
Proxy.deposit()delegates toBank.deposit().- User balance stored in proxy contract.
🔁 Upgrade to Bank2
- Call
Proxy.upgrade(Bank2 address)(ideally restricted to admin). - Now, all calls to proxy are delegated to
Bank2.
📤 Transfer Funds
User calls:
Proxy.transfer(address recipient, amount)
- Executes
Bank2.transfer()via delegatecall. - Balances still stored in proxy, but logic has changed.
🔒 Important Considerations
| Concern | Solution |
|---|---|
| 💥 Anyone can upgrade | Add require(msg.sender == admin) check |
| 🧠 Storage mismatch on upgrade | Maintain exact layout across logic versions |
⚠️ Missing initialize logic |
Add init function for first-time setup |
| 🔍 Debugging delegatecall | Use proxy logs or events in logic contract |
✅ Final Thoughts
Proxy contracts are essential in any mature smart contract ecosystem because:
- They allow upgrades without redeployment.
- They separate logic from data, making your architecture modular.
- They offer gas savings via contract reuse.
🛠 Summary Table
| Contract | Role |
|---|---|
| Proxy | Stores state & delegates calls |
| Bank | Initial logic (deposit/withdraw) |
| Bank2 | Upgraded logic (adds transfer) |