Skip to content

Instantly share code, notes, and snippets.

@shobhitic
Last active April 18, 2024 04:51
Show Gist options
  • Star 7 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save shobhitic/a51b1577482aaf27220b89371c2f07a6 to your computer and use it in GitHub Desktop.
Save shobhitic/a51b1577482aaf27220b89371c2f07a6 to your computer and use it in GitHub Desktop.
UUPS Upgradable Smart Contract - https://youtu.be/bPOOvyVpI9U

Link to video - https://youtu.be/bPOOvyVpI9U

First deploy the logic contract (MyContract). Then deploy the Proxy contract. In the arguments for Proxy contract's constructor, send the calldata and address of MyContract. To compute the calldata, run this in the remix console -

web3.utils.sha3('initialize()').substr(0, 10)

If the initialization function is different for your contract, change the initialize() to your contract's function name.

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Connect to Proxy</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
<script src="https://cdn.jsdelivr.net/npm/web3@latest/dist/web3.min.js"></script>
</head>
<body>
<div class="container mt-5">
<div class="row">
<div class="col-6 offset-3">
<div id="beforeconnect">
<button class="btn btn-primary mt-3" id="connect">connect</button>
</div>
<div id="afterconnect">
<div>
<button class="btn btn-primary mt-3" id="increment">increment</button>
</div>
<div>
<button class="btn btn-primary mt-3" id="current">Get Current Value</button>
<h3>Current Value: <span id="currentVal"></span></h3>
</div>
</div>
</div>
</div>
</div>
<script type="text/javascript">
document.getElementById('afterconnect').hidden = true
var account = null;
var contract = null;
const connect = async () => {
if (window.ethereum) {
await window.ethereum.send('eth_requestAccounts');
window.web3 = new Web3(window.ethereum);
var accounts = await web3.eth.getAccounts();
account = accounts[0];
contract = new web3.eth.Contract(ABI, ADDRESS);
document.getElementById('beforeconnect').hidden = true
document.getElementById('afterconnect').hidden = false
document.getElementById('current').onclick = current;
document.getElementById('increment').onclick = increment;
await current()
} else {
alert('Metamask not detected')
}
}
const increment = async () => {
if (contract) {
await contract.methods.increment().send({ from: account });
await current()
}
}
const current = async () => {
if (contract) {
var count = await contract.methods.count().call()
document.getElementById('currentVal').innerHTML = count;
}
}
document.getElementById('connect').onclick = connect;
var ADDRESS = '0xf54B9DccD560E20b89CfF60B92ce339c9E3d866D'
var ABI = [{"inputs":[],"name":"count","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"decrement","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"increment","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"initalized","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"proxiableUUID","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"newCode","type":"address"}],"name":"updateCode","outputs":[],"stateMutability":"nonpayable","type":"function"}]
</script>
<style type="text/css">
</style>
</body>
</html>
//SPDX-License-Identifier: MIT
pragma solidity ^0.8.1;
import "./Proxiable.sol";
contract MyContract is Proxiable {
address public owner;
uint public count;
bool public initalized = false;
function initialize() public {
require(owner == address(0), "Already initalized");
require(!initalized, "Already initalized");
owner = msg.sender;
initalized = true;
}
function increment() public {
count++;
}
function updateCode(address newCode) onlyOwner public {
updateCodeAddress(newCode);
}
modifier onlyOwner() {
require(msg.sender == owner, "Only owner is allowed to perform this action");
_;
}
}
//SPDX-License-Identifier: MIT
pragma solidity ^0.8.1;
import "./Proxiable.sol";
contract MyContract2 is Proxiable {
address public owner;
uint public count;
bool public initalized = false;
function initialize() public {
require(owner == address(0), "Already initalized");
require(!initalized, "Already initalized");
owner = msg.sender;
initalized = true;
}
function increment() public {
count++;
}
function decrement() public {
count--;
}
function updateCode(address newCode) onlyOwner public {
updateCodeAddress(newCode);
}
modifier onlyOwner() {
require(msg.sender == owner, "Only owner is allowed to perform this action");
_;
}
}
//SPDX-License-Identifier: MIT
pragma solidity ^0.8.1;
contract Proxiable {
// Code position in storage is keccak256("PROXIABLE") = "0xc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7"
function updateCodeAddress(address newAddress) internal {
require(
bytes32(0xc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7) == Proxiable(newAddress).proxiableUUID(),
"Not compatible"
);
assembly { // solium-disable-line
sstore(0xc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7, newAddress)
}
}
function proxiableUUID() public pure returns (bytes32) {
return 0xc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7;
}
}
//SPDX-License-Identifier: MIT
pragma solidity ^0.8.1;
contract Proxy {
// Code position in storage is keccak256("PROXIABLE") = "0xc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7"
constructor(bytes memory constructData, address contractLogic) {
// save the code address
assembly { // solium-disable-line
sstore(0xc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7, contractLogic)
}
(bool success, bytes memory result ) = contractLogic.delegatecall(constructData); // solium-disable-line
require(success, "Construction failed");
}
fallback() external payable {
assembly { // solium-disable-line
let contractLogic := sload(0xc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7)
calldatacopy(0x0, 0x0, calldatasize())
let success := delegatecall(sub(gas(), 10000), contractLogic, 0x0, calldatasize(), 0, 0)
let retSz := returndatasize()
returndatacopy(0, 0, retSz)
switch success
case 0 {
revert(0, retSz)
}
default {
return(0, retSz)
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment