Skip to content

Commit 2eef3f5

Browse files
author
TSxo
committed
feat: UUPS
1 parent e60da73 commit 2eef3f5

9 files changed

Lines changed: 582 additions & 4 deletions

File tree

.github/workflows/test.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ env:
1010

1111
jobs:
1212
check:
13-
name: Foundry project
13+
name: Build and Test
1414
runs-on: ubuntu-latest
1515
steps:
1616
- uses: actions/checkout@v4

LICENSE

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
Copyright 2025 TSxo
2+
3+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4+
5+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6+
7+
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

README.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
# libsol
22

3+
[![CI](https://github.com/TSxo/libsol/actions/workflows/test.yml/badge.svg)](https://github.com/TSxo/libsol/actions/workflows/test.yml)
4+
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg?style=flat-square)](https://opensource.org/licenses/MIT)
5+
36
A suite of opinionated, optimized smart contract modules.
47

58
## Contracts
69

710
The smart contracts are located in the `src` directory.
811

9-
```ml
12+
```
1013
.
1114
├── auth
1215
│   ├── IOwned.sol
@@ -21,7 +24,12 @@ The smart contracts are located in the `src` directory.
2124
│   ├── CallContext.sol
2225
│   └── Mutex.sol
2326
└── proxy
27+
├── ERC1967
28+
│   └── ERC1967Logic.sol
2429
├── Proxy.sol
30+
└── UUPS
31+
├── UUPSImplementation.sol
32+
└── UUPSProxy.sol
2533
```
2634

2735
## Purpose & License

src/auth/managed/AuthManager.sol

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -171,8 +171,8 @@ contract AuthManager is IAuthority, IAuthManager {
171171

172172
let success := call(gas(), target, 0x00, 0x00, 0x24, 0x00, 0x00)
173173

174-
if iszero(eq(returndatasize(), 0x00)) { revert(0x00, 0x00) }
175174
if iszero(success) { revert(0x00, 0x00) }
175+
if iszero(eq(returndatasize(), 0x00)) { revert(0x00, 0x00) }
176176

177177
log3(0x00, 0x00, AUTHORITY_UPDATED, target, newAuthority)
178178
}
@@ -385,8 +385,8 @@ contract AuthManager is IAuthority, IAuthManager {
385385
mstore(0x00, 0x8da5cb5b) // `owner()`
386386
let success := staticcall(gas(), address(), 0x1c, 0x04, 0x00, 0x20)
387387

388-
if iszero(eq(returndatasize(), 0x20)) { revert(0x00, 0x00) }
389388
if iszero(success) { revert(0x00, 0x00) }
389+
if iszero(eq(returndatasize(), 0x20)) { revert(0x00, 0x00) }
390390

391391
if iszero(eq(caller(), mload(0x00))) {
392392
mstore(0x0, 0x336eb95b) // `AuthManager__Unauthorized()`
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.20;
3+
4+
import { UUPSImplementation } from "@tsxo/libsol/proxy/UUPS/UUPSImplementation.sol";
5+
6+
contract UUPSCounterMock is UUPSImplementation {
7+
uint256 private _count;
8+
address private _owner;
9+
10+
event Count(uint256 n);
11+
event Received(uint256 val);
12+
13+
constructor() {
14+
_owner = msg.sender;
15+
}
16+
17+
receive() external payable {
18+
emit Received(msg.value);
19+
}
20+
21+
function initialize() external {
22+
require(_owner == address(0), "Already initialized");
23+
_owner = msg.sender;
24+
}
25+
26+
function count() external view returns (uint256) {
27+
return _count;
28+
}
29+
30+
function increment() external {
31+
_count += 1;
32+
emit Count(_count);
33+
}
34+
35+
function decrement() external {
36+
_count -= 1;
37+
emit Count(_count);
38+
}
39+
40+
function setCount(uint256 newCount) external {
41+
_count = newCount;
42+
emit Count(_count);
43+
}
44+
45+
function fundMe() external payable {
46+
emit Received(msg.value);
47+
}
48+
49+
function owner() external view returns (address) {
50+
return _owner;
51+
}
52+
53+
function _authorizeUpgrade(address) internal override {
54+
require(msg.sender == _owner, "Not authorized");
55+
}
56+
}
57+
58+
contract UUPSCounterV2Mock is UUPSImplementation {
59+
uint256 private _count;
60+
address private _owner;
61+
bool public upgraded;
62+
63+
event Count(uint256 n);
64+
event Received(uint256 val);
65+
event UpgradedV2();
66+
67+
receive() external payable {
68+
emit Received(msg.value);
69+
}
70+
71+
function count() external view returns (uint256) {
72+
return _count;
73+
}
74+
75+
function increment() external {
76+
_count += 1;
77+
emit Count(_count);
78+
}
79+
80+
function decrement() external {
81+
_count -= 1;
82+
emit Count(_count);
83+
}
84+
85+
function setCount(uint256 newCount) external {
86+
_count = newCount;
87+
emit Count(_count);
88+
}
89+
90+
function markAsUpgraded() external {
91+
upgraded = true;
92+
emit UpgradedV2();
93+
}
94+
95+
function fundMe() external payable {
96+
emit Received(msg.value);
97+
}
98+
99+
function _authorizeUpgrade(address) internal override {
100+
require(msg.sender == _owner, "Not authorized");
101+
}
102+
}

src/proxy/ERC1967/ERC1967Logic.sol

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
// SPDX-License-Identifier: MIT
2+
3+
pragma solidity ^0.8.20;
4+
5+
/// @title ERC1967Logic
6+
///
7+
/// @author TSxo
8+
///
9+
/// @dev This abstract contract provides the core components of ERC-1967 that are
10+
/// specific to logic (implementation) contracts. It exposes the standardized
11+
/// storage slot used by proxies to reference the implementation address, as well
12+
/// as the event and error definitions needed to support upgradeability.
13+
///
14+
/// See: https://eips.ethereum.org/EIPS/eip-1967
15+
abstract contract ERC1967Logic {
16+
// -------------------------------------------------------------------------
17+
// State
18+
19+
/// @dev bytes32(uint256(keccak256('eip1967.proxy.implementation')) - 1)
20+
bytes32 internal constant IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
21+
22+
/// @dev keccak256(bytes("Upgraded(address)"))
23+
bytes32 internal constant UPGRADED = 0xbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b;
24+
25+
// -------------------------------------------------------------------------
26+
// Events
27+
28+
/// @notice Emitted when the implementation address is updated.
29+
///
30+
/// @param implementation The new implementation address.
31+
event Upgraded(address indexed implementation);
32+
33+
// -------------------------------------------------------------------------
34+
// Errors
35+
36+
/// @notice Raised when updating the implementation address fails.
37+
error ERC1967Logic__UpgradeFailed();
38+
}
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
// SPDX-License-Identifier: MIT
2+
3+
pragma solidity ^0.8.20;
4+
5+
import { CallContext } from "../../mixins/CallContext.sol";
6+
import { ERC1967Logic } from "../ERC1967/ERC1967Logic.sol";
7+
8+
/// @title UUPSImplementation
9+
///
10+
/// @author TSxo
11+
///
12+
/// @dev An ERC-1822 and ERC-1967 compliant UUPS implementation contract. Designed
13+
/// to be used with an ERC-1967 proxy.
14+
///
15+
/// # Acknowledgements
16+
///
17+
/// Heavy inspiration is taken from:
18+
/// - OpenZeppelin;
19+
/// - Solmate; and
20+
/// - Solady.
21+
///
22+
/// Thank you.
23+
abstract contract UUPSImplementation is CallContext, ERC1967Logic {
24+
// -------------------------------------------------------------------------
25+
// Functions - External
26+
27+
/// @notice Returns the slot at which the implementation address is stored.
28+
///
29+
/// @dev Requirements:
30+
/// - Not callable through a proxy. This prevents upgrades to a proxy contract.
31+
///
32+
/// See https://eips.ethereum.org/EIPS/eip-1822
33+
function proxiableUUID() external view virtual notDelegated returns (bytes32) {
34+
return IMPLEMENTATION_SLOT;
35+
}
36+
37+
// -------------------------------------------------------------------------
38+
// Functions - Public
39+
40+
/// @notice Upgrades the implementation of the proxy to `newImplementation`
41+
/// and executes the function call, if any, encoded in `data`.
42+
///
43+
/// @dev Requirements:
44+
/// - Only callable through a proxy.
45+
/// - The caller must be authorized to perform the upgrade.
46+
/// - The `newImplementation` must be ERC-1822 compliant.
47+
///
48+
/// Emits an `Upgraded` event.
49+
function upgradeToAndCall(address newImplementation, bytes calldata data) public payable virtual onlyProxy {
50+
_authorizeUpgrade(newImplementation);
51+
52+
assembly ("memory-safe") {
53+
mstore(0x00, 0x52d1902d) // `proxiableUUID()`
54+
55+
let success := staticcall(gas(), newImplementation, 0x1c, 0x04, 0x00, 0x20)
56+
57+
if iszero(success) { revert(0x00, 0x00) }
58+
if iszero(eq(returndatasize(), 0x20)) { revert(0x00, 0x00) }
59+
60+
if iszero(eq(mload(0x00), IMPLEMENTATION_SLOT)) {
61+
mstore(0x00, 0x5f73959e) // `ERC1967Logic__UpgradeFailed()`
62+
revert(0x1c, 0x04)
63+
}
64+
65+
log2(0x00, 0x00, UPGRADED, newImplementation)
66+
sstore(IMPLEMENTATION_SLOT, newImplementation)
67+
68+
if data.length {
69+
let ptr := mload(0x40)
70+
calldatacopy(ptr, data.offset, data.length)
71+
72+
success := delegatecall(gas(), newImplementation, ptr, data.length, 0x00, 0x00)
73+
if iszero(success) {
74+
returndatacopy(ptr, 0x00, returndatasize())
75+
revert(ptr, returndatasize())
76+
}
77+
}
78+
}
79+
}
80+
81+
// -------------------------------------------------------------------------
82+
// Functions - Internal
83+
84+
/// @notice Called by `upgradeToAndCall` to check whether the `msg.sender`
85+
/// is authorized to perform the upgrade.
86+
///
87+
/// @dev Override this function with your preferred access check. Example:
88+
///
89+
/// ```solidity
90+
/// function _authorizeUpgrade(address) internal override onlyOwner {}
91+
/// ```
92+
function _authorizeUpgrade(address newImplementation) internal virtual;
93+
}

src/proxy/UUPS/UUPSProxy.sol

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
// SPDX-License-Identifier: MIT
2+
3+
pragma solidity ^0.8.20;
4+
5+
import { ERC1967Logic } from "../ERC1967/ERC1967Logic.sol";
6+
import { Proxy } from "../Proxy.sol";
7+
8+
/// @title UUPSProxy
9+
///
10+
/// @author TSxo
11+
///
12+
/// @dev An ERC-1967 proxy designed for use with ERC-1822 UUPS implementations.
13+
///
14+
/// # Acknowledgements
15+
///
16+
/// Heavy inspiration is taken from:
17+
/// - OpenZeppelin;
18+
/// - Solmate; and
19+
/// - Solady.
20+
///
21+
/// Thank you.
22+
contract UUPSProxy is Proxy, ERC1967Logic {
23+
// -------------------------------------------------------------------------
24+
// Functions - Constructor
25+
26+
/// @notice Initializes the contract with an implementation (logic) address.
27+
///
28+
/// @dev Requirements:
29+
/// - The implementation contract must be ERC-1822 compliant.
30+
///
31+
/// If `data` is provided, it will be used to execute a delegatecall to the
32+
/// implementation contract.
33+
///
34+
/// Emits an `Upgraded` event.
35+
constructor(address newImplementation, bytes memory data) payable {
36+
assembly ("memory-safe") {
37+
mstore(0x00, 0x52d1902d) // `proxiableUUID()`
38+
39+
let success := staticcall(gas(), newImplementation, 0x1c, 0x04, 0x00, 0x20)
40+
41+
if iszero(success) { revert(0x00, 0x00) }
42+
if iszero(eq(returndatasize(), 0x20)) { revert(0x00, 0x00) }
43+
44+
if iszero(eq(mload(0x00), IMPLEMENTATION_SLOT)) {
45+
mstore(0x00, 0x5f73959e) // `ERC1967Logic__UpgradeFailed()`
46+
revert(0x1c, 0x04)
47+
}
48+
49+
log2(0x00, 0x00, UPGRADED, newImplementation)
50+
sstore(IMPLEMENTATION_SLOT, newImplementation)
51+
52+
let len := mload(data)
53+
if len {
54+
let ptr := add(data, 0x20)
55+
56+
success := delegatecall(gas(), newImplementation, ptr, len, 0x00, 0x00)
57+
returndatacopy(0x00, 0x00, returndatasize())
58+
59+
if iszero(success) { revert(0x00, returndatasize()) }
60+
}
61+
}
62+
}
63+
64+
// -------------------------------------------------------------------------
65+
// Functions - Internal
66+
67+
/// @notice Returns the current implementation address.
68+
///
69+
/// @return result The current implementation address.
70+
function _implementation() internal view override returns (address result) {
71+
assembly ("memory-safe") {
72+
result := sload(IMPLEMENTATION_SLOT)
73+
}
74+
}
75+
}

0 commit comments

Comments
 (0)