Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ env:

jobs:
check:
name: Foundry project
name: Build and Test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
Expand Down
7 changes: 7 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Copyright 2025 TSxo

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:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

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.
10 changes: 9 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
# libsol

[![CI](https://github.com/TSxo/libsol/actions/workflows/test.yml/badge.svg)](https://github.com/TSxo/libsol/actions/workflows/test.yml)
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg?style=flat-square)](https://opensource.org/licenses/MIT)

A suite of opinionated, optimized smart contract modules.

## Contracts

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

```ml
```
.
├── auth
│   ├── IOwned.sol
Expand All @@ -21,7 +24,12 @@ The smart contracts are located in the `src` directory.
│   ├── CallContext.sol
│   └── Mutex.sol
└── proxy
├── ERC1967
│   └── ERC1967Logic.sol
├── Proxy.sol
└── UUPS
├── UUPSImplementation.sol
└── UUPSProxy.sol
```

## Purpose & License
Expand Down
4 changes: 2 additions & 2 deletions src/auth/managed/AuthManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -171,8 +171,8 @@ contract AuthManager is IAuthority, IAuthManager {

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

if iszero(eq(returndatasize(), 0x00)) { revert(0x00, 0x00) }
if iszero(success) { revert(0x00, 0x00) }
if iszero(eq(returndatasize(), 0x00)) { revert(0x00, 0x00) }

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

if iszero(eq(returndatasize(), 0x20)) { revert(0x00, 0x00) }
if iszero(success) { revert(0x00, 0x00) }
if iszero(eq(returndatasize(), 0x20)) { revert(0x00, 0x00) }

if iszero(eq(caller(), mload(0x00))) {
mstore(0x0, 0x336eb95b) // `AuthManager__Unauthorized()`
Expand Down
102 changes: 102 additions & 0 deletions src/mocks/proxy/UUPS/UUPSImplementationMock.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import { UUPSImplementation } from "@tsxo/libsol/proxy/UUPS/UUPSImplementation.sol";

contract UUPSCounterMock is UUPSImplementation {
uint256 private _count;
address private _owner;

event Count(uint256 n);
event Received(uint256 val);

constructor() {
_owner = msg.sender;
}

receive() external payable {
emit Received(msg.value);
}

function initialize() external {
require(_owner == address(0), "Already initialized");
_owner = msg.sender;
}

function count() external view returns (uint256) {
return _count;
}

function increment() external {
_count += 1;
emit Count(_count);
}

function decrement() external {
_count -= 1;
emit Count(_count);
}

function setCount(uint256 newCount) external {
_count = newCount;
emit Count(_count);
}

function fundMe() external payable {
emit Received(msg.value);
}

function owner() external view returns (address) {
return _owner;
}

function _authorizeUpgrade(address) internal override {
require(msg.sender == _owner, "Not authorized");
}
}

contract UUPSCounterV2Mock is UUPSImplementation {
uint256 private _count;
address private _owner;
bool public upgraded;

event Count(uint256 n);
event Received(uint256 val);
event UpgradedV2();

receive() external payable {
emit Received(msg.value);
}

function count() external view returns (uint256) {
return _count;
}

function increment() external {
_count += 1;
emit Count(_count);
}

function decrement() external {
_count -= 1;
emit Count(_count);
}

function setCount(uint256 newCount) external {
_count = newCount;
emit Count(_count);
}

function markAsUpgraded() external {
upgraded = true;
emit UpgradedV2();
}

function fundMe() external payable {
emit Received(msg.value);
}

function _authorizeUpgrade(address) internal override {
require(msg.sender == _owner, "Not authorized");
}
}
38 changes: 38 additions & 0 deletions src/proxy/ERC1967/ERC1967Logic.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.20;

/// @title ERC1967Logic
///
/// @author TSxo
///
/// @dev This abstract contract provides the core components of ERC-1967 that are
/// specific to logic (implementation) contracts. It exposes the standardized
/// storage slot used by proxies to reference the implementation address, as well
/// as the event and error definitions needed to support upgradeability.
///
/// See: https://eips.ethereum.org/EIPS/eip-1967
abstract contract ERC1967Logic {
// -------------------------------------------------------------------------
// State

/// @dev bytes32(uint256(keccak256('eip1967.proxy.implementation')) - 1)
bytes32 internal constant IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;

/// @dev keccak256(bytes("Upgraded(address)"))
bytes32 internal constant UPGRADED = 0xbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b;

// -------------------------------------------------------------------------
// Events

/// @notice Emitted when the implementation address is updated.
///
/// @param implementation The new implementation address.
event Upgraded(address indexed implementation);

// -------------------------------------------------------------------------
// Errors

/// @notice Raised when updating the implementation address fails.
error ERC1967Logic__UpgradeFailed();
}
93 changes: 93 additions & 0 deletions src/proxy/UUPS/UUPSImplementation.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.20;

import { CallContext } from "../../mixins/CallContext.sol";
import { ERC1967Logic } from "../ERC1967/ERC1967Logic.sol";

/// @title UUPSImplementation
///
/// @author TSxo
///
/// @dev An ERC-1822 and ERC-1967 compliant UUPS implementation contract. Designed
/// to be used with an ERC-1967 proxy.
///
/// # Acknowledgements
///
/// Heavy inspiration is taken from:
/// - OpenZeppelin;
/// - Solmate; and
/// - Solady.
///
/// Thank you.
abstract contract UUPSImplementation is CallContext, ERC1967Logic {
// -------------------------------------------------------------------------
// Functions - External

/// @notice Returns the slot at which the implementation address is stored.
///
/// @dev Requirements:
/// - Not callable through a proxy. This prevents upgrades to a proxy contract.
///
/// See https://eips.ethereum.org/EIPS/eip-1822
function proxiableUUID() external view virtual notDelegated returns (bytes32) {
return IMPLEMENTATION_SLOT;
}

// -------------------------------------------------------------------------
// Functions - Public

/// @notice Upgrades the implementation of the proxy to `newImplementation`
/// and executes the function call, if any, encoded in `data`.
///
/// @dev Requirements:
/// - Only callable through a proxy.
/// - The caller must be authorized to perform the upgrade.
/// - The `newImplementation` must be ERC-1822 compliant.
///
/// Emits an `Upgraded` event.
function upgradeToAndCall(address newImplementation, bytes calldata data) public payable virtual onlyProxy {
_authorizeUpgrade(newImplementation);

assembly ("memory-safe") {
mstore(0x00, 0x52d1902d) // `proxiableUUID()`

let success := staticcall(gas(), newImplementation, 0x1c, 0x04, 0x00, 0x20)

if iszero(success) { revert(0x00, 0x00) }
if iszero(eq(returndatasize(), 0x20)) { revert(0x00, 0x00) }

if iszero(eq(mload(0x00), IMPLEMENTATION_SLOT)) {
mstore(0x00, 0x5f73959e) // `ERC1967Logic__UpgradeFailed()`
revert(0x1c, 0x04)
}

log2(0x00, 0x00, UPGRADED, newImplementation)
sstore(IMPLEMENTATION_SLOT, newImplementation)

if data.length {
let ptr := mload(0x40)
calldatacopy(ptr, data.offset, data.length)

success := delegatecall(gas(), newImplementation, ptr, data.length, 0x00, 0x00)
if iszero(success) {
returndatacopy(ptr, 0x00, returndatasize())
revert(ptr, returndatasize())
}
}
}
}

// -------------------------------------------------------------------------
// Functions - Internal

/// @notice Called by `upgradeToAndCall` to check whether the `msg.sender`
/// is authorized to perform the upgrade.
///
/// @dev Override this function with your preferred access check. Example:
///
/// ```solidity
/// function _authorizeUpgrade(address) internal override onlyOwner {}
/// ```
function _authorizeUpgrade(address newImplementation) internal virtual;
}
75 changes: 75 additions & 0 deletions src/proxy/UUPS/UUPSProxy.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.20;

import { ERC1967Logic } from "../ERC1967/ERC1967Logic.sol";
import { Proxy } from "../Proxy.sol";

/// @title UUPSProxy
///
/// @author TSxo
///
/// @dev An ERC-1967 proxy designed for use with ERC-1822 UUPS implementations.
///
/// # Acknowledgements
///
/// Heavy inspiration is taken from:
/// - OpenZeppelin;
/// - Solmate; and
/// - Solady.
///
/// Thank you.
contract UUPSProxy is Proxy, ERC1967Logic {
// -------------------------------------------------------------------------
// Functions - Constructor

/// @notice Initializes the contract with an implementation (logic) address.
///
/// @dev Requirements:
/// - The implementation contract must be ERC-1822 compliant.
///
/// If `data` is provided, it will be used to execute a delegatecall to the
/// implementation contract.
///
/// Emits an `Upgraded` event.
constructor(address newImplementation, bytes memory data) payable {
assembly ("memory-safe") {
mstore(0x00, 0x52d1902d) // `proxiableUUID()`

let success := staticcall(gas(), newImplementation, 0x1c, 0x04, 0x00, 0x20)

if iszero(success) { revert(0x00, 0x00) }
if iszero(eq(returndatasize(), 0x20)) { revert(0x00, 0x00) }

if iszero(eq(mload(0x00), IMPLEMENTATION_SLOT)) {
mstore(0x00, 0x5f73959e) // `ERC1967Logic__UpgradeFailed()`
revert(0x1c, 0x04)
}

log2(0x00, 0x00, UPGRADED, newImplementation)
sstore(IMPLEMENTATION_SLOT, newImplementation)

let len := mload(data)
if len {
let ptr := add(data, 0x20)

success := delegatecall(gas(), newImplementation, ptr, len, 0x00, 0x00)
returndatacopy(0x00, 0x00, returndatasize())

if iszero(success) { revert(0x00, returndatasize()) }
}
}
}

// -------------------------------------------------------------------------
// Functions - Internal

/// @notice Returns the current implementation address.
///
/// @return result The current implementation address.
function _implementation() internal view override returns (address result) {
assembly ("memory-safe") {
result := sload(IMPLEMENTATION_SLOT)
}
}
}
Loading