Skip to content

Commit f10eadd

Browse files
authored
feat: SemVerProxy 0.1.0 (#1)
* chore: forge init * forge install: forge-std v1.9.7 * feat(lib): `@oz` with `--no-git` * feat: `.gitignore` * feat: add libs * feat: `SemVerProxy` * test: libs & SemVerProxy * test: storage collision & invariant * clean: polish comments * clean(SemVerProxy): polish comments * feat: update README.md
1 parent 6639bd6 commit f10eadd

File tree

876 files changed

+137970
-1
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

876 files changed

+137970
-1
lines changed

.github/workflows/test.yml

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
pull_request:
6+
workflow_dispatch:
7+
8+
env:
9+
FOUNDRY_PROFILE: ci
10+
11+
jobs:
12+
check:
13+
strategy:
14+
fail-fast: true
15+
16+
name: Foundry project
17+
runs-on: ubuntu-latest
18+
steps:
19+
- uses: actions/checkout@v4
20+
with:
21+
submodules: recursive
22+
23+
- name: Install Foundry
24+
uses: foundry-rs/foundry-toolchain@v1
25+
26+
- name: Show Forge version
27+
run: |
28+
forge --version
29+
30+
- name: Run Forge fmt
31+
run: |
32+
forge fmt --check
33+
id: fmt
34+
35+
- name: Run Forge build
36+
run: |
37+
forge build --sizes
38+
id: build
39+
40+
- name: Run Forge tests
41+
run: |
42+
forge test -vvv
43+
id: test

.gitignore

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# Compiler files
2+
cache/
3+
out/
4+
5+
*.org
6+
.env
7+
.DS_Store

.gitmodules

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
[submodule "lib/forge-std"]
2+
path = lib/forge-std
3+
url = https://github.com/foundry-rs/forge-std

.prettierrc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"plugins": ["prettier-plugin-solidity"]
3+
}

README.md

Lines changed: 95 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,95 @@
1-
# semver-proxy
1+
# SemVer Proxy
2+
Proxy contract that allows dispatching calls to multiple implementations, based on [Semantic Versioning](https://semver.org/). Automatically handles incrementation of `major.minor.patch` parts of a version during upgrades, but it *doesn't enforce proper adherence to Semantic Versioning by developers on a smart-contract level* - it's considered to be a responsibility of a smart-contract developer to properly adhere to Semantic Versioning.
3+
4+
## Quick Start
5+
```sh
6+
git clone https://github.com/daoio/semver-proxy
7+
8+
## Install deps (note, oz contracts are already installed locally with --no-git)
9+
forge install
10+
## Run tests
11+
forge test
12+
```
13+
14+
## Storage layout of `SemVerProxy`
15+
`SemVerProxy` reserves storage slots **0 to 99** for implementation contracts by declaring a fixed-size array that occupies these slots:
16+
```solidity
17+
uint256[100] private __gap; // Reserves slots 0-99 for implementations
18+
```
19+
So, the storage layout of a proxy and an arbitrary implementation contract can be represented as follows:
20+
```text
21+
Proxy storage layout:
22+
┌─────────────┐
23+
│ Slot 0-99 │ ← `__gap` placeholder (reserved for implementations)
24+
├─────────────┤
25+
│ Slot 100 │ ← `_latestVersion`
26+
│ Slot 101 │ ← Other state variables
27+
│ ... │
28+
└─────────────┘
29+
30+
Implementation storage example:
31+
┌─────────────┐
32+
│ Slot 0 │ ← Implementation's first state variable
33+
│ Slot 1 │ ← Implementation's second state variable
34+
│ ... │
35+
│ Slot 99 │ ← Last available slot for implementation
36+
├─────────────┤
37+
| Slot 100 | ← ⚠️ This will collide with proxy's storage
38+
└─────────────┘
39+
```
40+
41+
## Releases and Versioning
42+
A **release** refers to a versioned implementation contract stored in the proxy. Unlike standard proxies, `SemVerProxy` maintains multiple implementations simultaneously, each accessible through its semantic version.
43+
44+
- **Latest Release**: Stored in the [ERC-1967 implementation slot](https://eips.ethereum.org/EIPS/eip-1967#logic-contract-address)
45+
(Updating this overwrites the ERC-1967 slot)
46+
- **Historical Versions**: Preserved in the `_releases` mapping
47+
(All versions remain accessible to clients by their `major.minor.patch` identifier)
48+
- **Initial Version**: `0.1.0` per [SemVer initial development guidelines](https://semver.org/#how-should-i-deal-with-revisions-in-the-0yz-initial-development-phase)
49+
50+
### Version Upgrade
51+
Admins can release new version of implementation contract via the following function calls (effectively upgrading proxy to use new implemenation, though clients might still use previous versions of the implementation contract):
52+
53+
| Function | Behavior | Example |
54+
| ------------- | ------------- | -- |
55+
| `releasePatch` | Increments patch | `1.2.3``1.2.4` |
56+
| `releaseMinor` | Increments minor, resets patch | `1.2.3``1.3.0` |
57+
| `releaseMajor` | Increments major, resets minor and patch | `1.2.3``2.0.0` |
58+
59+
## Subscribing to Specific Versions
60+
By default, all non-admin calls that end up in `fallback()` function are dispatched to `delegatecall` to the latest release (stored in ERC-1967 slot). But, users can "subscribe" to use the implementation version they need, by calling:
61+
```solidity
62+
function subscribeToVersion(Version memory version) external;
63+
64+
// Where {Version} is:
65+
struct Version {
66+
uint64 major;
67+
uint64 minor;
68+
uint128 patch;
69+
}
70+
```
71+
After this action all calls of a subscribed user will be dispatched to the `major.minor.patch` version they have specified for subscription. All subscriptions are stored inside `SemVerProxy._subscribedClients` mapping
72+
```mermaid
73+
flowchart TD
74+
A(["subscribed to 1.0.1"]) --> n4["SemVerProxy"]
75+
n1(["subscribed to 0.1.0"]) --> n4
76+
n2(["not subscribed"]) --> n4
77+
n4 --> C["Release v 1.0.1"] & D["Release v 0.1.0"] & n3["Latest Release v 2.0.0"]
78+
n4@{ shape: diam}
79+
n3@{ shape: rect}
80+
linkStyle 0 stroke:#D50000,fill:none
81+
linkStyle 1 stroke:#2962FF,fill:none
82+
linkStyle 2 stroke:#00C853,fill:none
83+
linkStyle 3 stroke:#D50000,fill:none
84+
linkStyle 4 stroke:#2962FF,fill:none
85+
linkStyle 5 stroke:#00C853
86+
```
87+
88+
Users can unsubscribe from using specific version and use the latest one by calling:
89+
```solidity
90+
function unsubscribeFromVersioning() external;
91+
```
92+
93+
## Security Considerations
94+
- Since `SemVerProxy` only reserves slots from 0 to 99, any 100+ slot of the implementation will collide with proxy.
95+
- `SemVerProxy` has externally accessable function, therefore there's a possibiliy of function selector clash (i.e., if implementation defines functions that have the same signature as external functions of `SemVerProxy`).

foundry.toml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
[profile.default]
2+
src = "src"
3+
out = "out"
4+
libs = ["lib"]
5+
6+
[invariant]
7+
runs = 1000
8+
depth = 300
9+
10+
# See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options

lib/forge-std

Submodule forge-std added at 77041d2
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'openzeppelin-solidity': patch
3+
---
4+
5+
Add constructors to the different signers.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'openzeppelin-solidity': minor
3+
---
4+
5+
`AccountERC7579`: Extension of `Account` that implements support for ERC-7579 modules of type executor, validator, and fallback handler.
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"$schema": "https://unpkg.com/@changesets/config@2.3.0/schema.json",
3+
"changelog": [
4+
"@changesets/changelog-github",
5+
{
6+
"repo": "OpenZeppelin/openzeppelin-contracts"
7+
}
8+
],
9+
"commit": false,
10+
"access": "public",
11+
"baseBranch": "master"
12+
}

0 commit comments

Comments
 (0)