mirror of
https://github.com/autistic-symposium/web3-starter-sol.git
synced 2025-07-05 12:04:55 -04:00
236 lines
6.5 KiB
Solidity
236 lines
6.5 KiB
Solidity
// SPDX-License-Identifier: MIT
|
|
pragma solidity ^0.8.17;
|
|
|
|
// Transparent upgradeable proxy pattern
|
|
|
|
contract CounterV1 {
|
|
uint public count;
|
|
|
|
function inc() external {
|
|
count += 1;
|
|
}
|
|
}
|
|
|
|
contract CounterV2 {
|
|
uint public count;
|
|
|
|
function inc() external {
|
|
count += 1;
|
|
}
|
|
|
|
function dec() external {
|
|
count -= 1;
|
|
}
|
|
}
|
|
|
|
contract BuggyProxy {
|
|
address public implementation;
|
|
address public admin;
|
|
|
|
constructor() {
|
|
admin = msg.sender;
|
|
}
|
|
|
|
function _delegate() private {
|
|
(bool ok, ) = implementation.delegatecall(msg.data);
|
|
require(ok, "delegatecall failed");
|
|
}
|
|
|
|
fallback() external payable {
|
|
_delegate();
|
|
}
|
|
|
|
receive() external payable {
|
|
_delegate();
|
|
}
|
|
|
|
function upgradeTo(address _implementation) external {
|
|
require(msg.sender == admin, "not authorized");
|
|
implementation = _implementation;
|
|
}
|
|
}
|
|
|
|
contract Dev {
|
|
function selectors() external view returns (bytes4, bytes4, bytes4) {
|
|
return (
|
|
Proxy.admin.selector,
|
|
Proxy.implementation.selector,
|
|
Proxy.upgradeTo.selector
|
|
);
|
|
}
|
|
}
|
|
|
|
contract Proxy {
|
|
// All functions / variables should be private, forward all calls to fallback
|
|
|
|
// -1 for unknown preimage
|
|
// 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc
|
|
bytes32 private constant IMPLEMENTATION_SLOT =
|
|
bytes32(uint(keccak256("eip1967.proxy.implementation")) - 1);
|
|
// 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103
|
|
bytes32 private constant ADMIN_SLOT =
|
|
bytes32(uint(keccak256("eip1967.proxy.admin")) - 1);
|
|
|
|
constructor() {
|
|
_setAdmin(msg.sender);
|
|
}
|
|
|
|
modifier ifAdmin() {
|
|
if (msg.sender == _getAdmin()) {
|
|
_;
|
|
} else {
|
|
_fallback();
|
|
}
|
|
}
|
|
|
|
function _getAdmin() private view returns (address) {
|
|
return StorageSlot.getAddressSlot(ADMIN_SLOT).value;
|
|
}
|
|
|
|
function _setAdmin(address _admin) private {
|
|
require(_admin != address(0), "admin = zero address");
|
|
StorageSlot.getAddressSlot(ADMIN_SLOT).value = _admin;
|
|
}
|
|
|
|
function _getImplementation() private view returns (address) {
|
|
return StorageSlot.getAddressSlot(IMPLEMENTATION_SLOT).value;
|
|
}
|
|
|
|
function _setImplementation(address _implementation) private {
|
|
require(_implementation.code.length > 0, "implementation is not contract");
|
|
StorageSlot.getAddressSlot(IMPLEMENTATION_SLOT).value = _implementation;
|
|
}
|
|
|
|
// Admin interface //
|
|
function changeAdmin(address _admin) external ifAdmin {
|
|
_setAdmin(_admin);
|
|
}
|
|
|
|
// 0x3659cfe6
|
|
function upgradeTo(address _implementation) external ifAdmin {
|
|
_setImplementation(_implementation);
|
|
}
|
|
|
|
// 0xf851a440
|
|
function admin() external ifAdmin returns (address) {
|
|
return _getAdmin();
|
|
}
|
|
|
|
// 0x5c60da1b
|
|
function implementation() external ifAdmin returns (address) {
|
|
return _getImplementation();
|
|
}
|
|
|
|
// User interface //
|
|
function _delegate(address _implementation) internal virtual {
|
|
assembly {
|
|
// Copy msg.data. We take full control of memory in this inline assembly
|
|
// block because it will not return to Solidity code. We overwrite the
|
|
// Solidity scratch pad at memory position 0.
|
|
|
|
// calldatacopy(t, f, s) - copy s bytes from calldata at position f to mem at position t
|
|
// calldatasize() - size of call data in bytes
|
|
calldatacopy(0, 0, calldatasize())
|
|
|
|
// Call the implementation.
|
|
// out and outsize are 0 because we don't know the size yet.
|
|
|
|
// delegatecall(g, a, in, insize, out, outsize) -
|
|
// - call contract at address a
|
|
// - with input mem[in…(in+insize))
|
|
// - providing g gas
|
|
// - and output area mem[out…(out+outsize))
|
|
// - returning 0 on error (eg. out of gas) and 1 on success
|
|
let result := delegatecall(gas(), _implementation, 0, calldatasize(), 0, 0)
|
|
|
|
// Copy the returned data.
|
|
// returndatacopy(t, f, s) - copy s bytes from returndata at position f to mem at position t
|
|
// returndatasize() - size of the last returndata
|
|
returndatacopy(0, 0, returndatasize())
|
|
|
|
switch result
|
|
// delegatecall returns 0 on error.
|
|
case 0 {
|
|
// revert(p, s) - end execution, revert state changes, return data mem[p…(p+s))
|
|
revert(0, returndatasize())
|
|
}
|
|
default {
|
|
// return(p, s) - end execution, return data mem[p…(p+s))
|
|
return(0, returndatasize())
|
|
}
|
|
}
|
|
}
|
|
|
|
function _fallback() private {
|
|
_delegate(_getImplementation());
|
|
}
|
|
|
|
fallback() external payable {
|
|
_fallback();
|
|
}
|
|
|
|
receive() external payable {
|
|
_fallback();
|
|
}
|
|
}
|
|
|
|
contract ProxyAdmin {
|
|
address public owner;
|
|
|
|
constructor() {
|
|
owner = msg.sender;
|
|
}
|
|
|
|
modifier onlyOwner() {
|
|
require(msg.sender == owner, "not owner");
|
|
_;
|
|
}
|
|
|
|
function getProxyAdmin(address proxy) external view returns (address) {
|
|
(bool ok, bytes memory res) = proxy.staticcall(abi.encodeCall(Proxy.admin, ()));
|
|
require(ok, "call failed");
|
|
return abi.decode(res, (address));
|
|
}
|
|
|
|
function getProxyImplementation(address proxy) external view returns (address) {
|
|
(bool ok, bytes memory res) = proxy.staticcall(
|
|
abi.encodeCall(Proxy.implementation, ())
|
|
);
|
|
require(ok, "call failed");
|
|
return abi.decode(res, (address));
|
|
}
|
|
|
|
function changeProxyAdmin(address payable proxy, address admin) external onlyOwner {
|
|
Proxy(proxy).changeAdmin(admin);
|
|
}
|
|
|
|
function upgrade(address payable proxy, address implementation) external onlyOwner {
|
|
Proxy(proxy).upgradeTo(implementation);
|
|
}
|
|
}
|
|
|
|
library StorageSlot {
|
|
struct AddressSlot {
|
|
address value;
|
|
}
|
|
|
|
function getAddressSlot(
|
|
bytes32 slot
|
|
) internal pure returns (AddressSlot storage r) {
|
|
assembly {
|
|
r.slot := slot
|
|
}
|
|
}
|
|
}
|
|
|
|
contract TestSlot {
|
|
bytes32 public constant slot = keccak256("TEST_SLOT");
|
|
|
|
function getSlot() external view returns (address) {
|
|
return StorageSlot.getAddressSlot(slot).value;
|
|
}
|
|
|
|
function writeSlot(address _addr) external {
|
|
StorageSlot.getAddressSlot(slot).value = _addr;
|
|
}
|
|
}
|