generated from peersky/bootstrap_solidity
-
-
Notifications
You must be signed in to change notification settings - Fork 1
16 distributor upgrades handler #53
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
peersky
wants to merge
7
commits into
dev
Choose a base branch
from
16-distributor-upgrades-handler
base: dev
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
bada3e1
wip
peersky 879f1bc
Merge branch 'dev' into 16-distributor-upgrades-handler
peersky bbd0127
adjusting for ERC7744 for ERC7702
peersky f2eacf1
reduce optimisation heaviness
peersky ec051fc
initial tests pass
peersky 289ae11
docs, small tunings in TDD mode
peersky acb00db
Add mock distribution contracts and update VSCode settings
peersky File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
.env | ||
.secrets/ |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
module.exports = { | ||
skipFiles: ["mocks/", "erc7744/"] | ||
}; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,3 @@ | ||
{ | ||
"cSpell.words": ["mstore", "retval", "strg"] | ||
"cSpell.words": ["continous", "extcodecopy", "mstore", "retval", "strg"] | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.19; | ||
|
||
import "../../src/interfaces/IDistribution.sol"; | ||
import {ShortStrings, ShortString} from "@openzeppelin/contracts/utils/ShortStrings.sol"; | ||
|
||
contract MockFailingDistribution is IDistribution { | ||
ShortString private immutable distributionName; | ||
uint256 private constant DISTRIBUTION_VERSION = 1; | ||
|
||
constructor() { | ||
distributionName = ShortStrings.toShortString("MockFailingDistribution"); | ||
} | ||
|
||
function instantiate(bytes memory args) external override returns (address[] memory instances, bytes32 name, uint256 version) { | ||
// Check if the args contain a specific "FAIL" string to trigger a revert | ||
if (args.length > 0 && keccak256(args) == keccak256(abi.encode(bytes32("FAIL")))) { | ||
revert("Intentional instantiation failure"); | ||
} | ||
|
||
// Normal instantiation | ||
instances = new address[](1); | ||
instances[0] = address(this); | ||
emit Distributed(msg.sender, instances); | ||
name = ShortString.unwrap(distributionName); | ||
version = DISTRIBUTION_VERSION; | ||
} | ||
|
||
function get() external view override returns (address[] memory sources, bytes32 name, uint256 version) { | ||
sources = new address[](1); | ||
sources[0] = address(this); | ||
name = ShortString.unwrap(distributionName); | ||
version = DISTRIBUTION_VERSION; | ||
} | ||
|
||
function contractURI() external pure returns (string memory) { | ||
return "ipfs://mockFailingDistribution"; | ||
} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.19; | ||
|
||
import "../../src/interfaces/IDistribution.sol"; | ||
import {ShortStrings, ShortString} from "@openzeppelin/contracts/utils/ShortStrings.sol"; | ||
|
||
contract MockLowLevelDistribution is IDistribution { | ||
ShortString private immutable distributionName; | ||
uint256 private constant DISTRIBUTION_VERSION = 1; | ||
|
||
constructor() { | ||
distributionName = ShortStrings.toShortString("MockLowLevelDistribution"); | ||
} | ||
|
||
function instantiate(bytes memory) external override returns (address[] memory instances, bytes32 name, uint256 version) { | ||
// This will be a low-level call error (CALL opcode that fails) | ||
// solhint-disable-next-line avoid-low-level-calls | ||
(bool success, ) = address(0).call(abi.encodeWithSignature("nonExistentFunction()")); | ||
require(success, "MockLowLevelDistribution: low level call failed"); | ||
|
||
// This code is unreachable | ||
instances = new address[](1); | ||
instances[0] = address(this); | ||
name = ShortString.unwrap(distributionName); | ||
version = DISTRIBUTION_VERSION; | ||
} | ||
|
||
function get() external view override returns (address[] memory sources, bytes32 name, uint256 version) { | ||
sources = new address[](1); | ||
sources[0] = address(this); | ||
name = ShortString.unwrap(distributionName); | ||
version = DISTRIBUTION_VERSION; | ||
} | ||
|
||
function contractURI() external pure returns (string memory) { | ||
return "ipfs://mockLowLevelDistribution"; | ||
} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity >=0.8.0 <0.9.0; | ||
|
||
import "../../src/interfaces/IMigration.sol"; | ||
import "../../src/interfaces/IRepository.sol"; | ||
import "../../src/versioning/LibSemver.sol"; | ||
import "@openzeppelin/contracts/utils/introspection/ERC165.sol"; | ||
|
||
contract MockMigration is IMigration, ERC165 { | ||
event MigrationExecuted( | ||
address[] instances, | ||
uint256 oldVersion, | ||
uint256 newVersion, | ||
bytes distributorCalldata, | ||
bytes userCalldata | ||
); | ||
|
||
function migrate( | ||
address[] memory instances, | ||
LibSemver.Version memory oldVersion, | ||
LibSemver.Version memory newVersion, | ||
IRepository repository, | ||
bytes calldata distributorCalldata, | ||
bytes calldata userCalldata | ||
) external override { | ||
// Emit an event with migration details | ||
emit MigrationExecuted( | ||
instances, | ||
LibSemver.toUint256(oldVersion), | ||
LibSemver.toUint256(newVersion), | ||
distributorCalldata, | ||
userCalldata | ||
); | ||
|
||
// Don't do anything else - this is just a mock for testing | ||
} | ||
|
||
function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165) returns (bool) { | ||
return | ||
interfaceId == type(IMigration).interfaceId || | ||
super.supportsInterface(interfaceId); | ||
} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.19; | ||
|
||
import "../../src/interfaces/IDistribution.sol"; | ||
import {ShortStrings, ShortString} from "@openzeppelin/contracts/utils/ShortStrings.sol"; | ||
|
||
contract MockPanicDistribution is IDistribution { | ||
ShortString private immutable distributionName; | ||
uint256 private constant DISTRIBUTION_VERSION = 1; | ||
|
||
constructor() { | ||
distributionName = ShortStrings.toShortString("MockPanicDistribution"); | ||
} | ||
|
||
function instantiate(bytes memory) external override returns (address[] memory instances, bytes32 name, uint256 version) { | ||
// This will cause a panic | ||
assert(false); | ||
|
||
// This code is unreachable but needed to compile | ||
instances = new address[](0); | ||
name = ShortString.unwrap(distributionName); | ||
version = DISTRIBUTION_VERSION; | ||
} | ||
|
||
function get() external view override returns (address[] memory sources, bytes32 name, uint256 version) { | ||
sources = new address[](1); | ||
sources[0] = address(this); | ||
name = ShortString.unwrap(distributionName); | ||
version = DISTRIBUTION_VERSION; | ||
} | ||
|
||
function contractURI() external pure returns (string memory) { | ||
return "ipfs://MockPanicDistribution"; | ||
} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity >=0.8.0 <0.9.0; | ||
|
||
import "../../src/interfaces/IRepository.sol"; | ||
import "../../src/versioning/LibSemver.sol"; | ||
import "../../src/erc7744/LibERC7744.sol"; | ||
|
||
contract MockRepository is IRepository { | ||
using LibERC7744 for bytes32; | ||
using LibSemver for LibSemver.Version; | ||
using LibSemver for LibSemver.VersionRequirement; | ||
|
||
mapping(uint256 => bytes32) private sources; | ||
mapping(uint256 => bytes) private sourceMetadata; | ||
mapping(uint64 => bytes32) private migrationScripts; | ||
bytes32 private _repositoryName = bytes32("MockRepository"); | ||
|
||
function addSource(LibSemver.Version memory version, bytes32 sourceId, bytes memory metadata) external { | ||
sources[version.toUint256()] = sourceId; | ||
sourceMetadata[version.toUint256()] = metadata; | ||
} | ||
|
||
function addMigrationScript(uint64 majorVersion, bytes32 migrationHash) external { | ||
migrationScripts[majorVersion] = migrationHash; | ||
} | ||
|
||
function resolveVersion(LibSemver.VersionRequirement calldata requirement) external view override returns (uint256) { | ||
// For simplicity, just return the exact version number from the requirement | ||
return requirement.version.toUint256(); | ||
} | ||
|
||
function get(LibSemver.VersionRequirement calldata requirement) external view override returns (Source memory) { | ||
uint256 version = requirement.version.toUint256(); | ||
bytes32 sourceId = sources[version]; | ||
bytes memory metadata = sourceMetadata[version]; | ||
if (sourceId == bytes32(0)) { | ||
// If no specific source is set, return a default | ||
sourceId = bytes32(uint256(uint160(address(this)))); | ||
} | ||
return Source(requirement.version, sourceId, metadata); | ||
} | ||
|
||
function getLatest() external view override returns (Source memory) { | ||
// Just return a placeholder value | ||
LibSemver.Version memory latestVersion = LibSemver.parse(1); | ||
bytes32 sourceId = bytes32(uint256(uint160(address(this)))); | ||
return Source(latestVersion, sourceId, ""); | ||
} | ||
|
||
function getMigrationScript(uint64 majorVersion) external view override returns (bytes32) { | ||
return migrationScripts[majorVersion]; | ||
} | ||
|
||
function supportsInterface(bytes4 interfaceId) external pure override returns (bool) { | ||
return | ||
interfaceId == type(IRepository).interfaceId || | ||
interfaceId == 0x01ffc9a7; // ERC165 | ||
} | ||
|
||
function repositoryName() external view override returns (bytes32) { | ||
return _repositoryName; | ||
} | ||
|
||
function contractURI() external pure override returns (string memory) { | ||
return "ipfs://mockContract"; | ||
} | ||
|
||
function updateReleaseMetadata(LibSemver.Version memory version, bytes calldata releaseMetadata) external override { | ||
sourceMetadata[version.toUint256()] = releaseMetadata; | ||
emit ReleaseMetadataUpdated(version.toUint256(), releaseMetadata); | ||
} | ||
|
||
function newRelease(bytes32 sourceId, bytes memory metadata, LibSemver.Version memory version, bytes32 migrationHash) external override { | ||
uint256 versionUint = version.toUint256(); | ||
sources[versionUint] = sourceId; | ||
sourceMetadata[versionUint] = metadata; | ||
migrationScripts[version.major] = migrationHash; | ||
emit VersionAdded(versionUint, sourceId, metadata); | ||
emit MigrationScriptAdded(version.major, migrationHash); | ||
} | ||
|
||
function changeMigrationScript(uint64 majorVersion, bytes32 migrationHash) external override { | ||
migrationScripts[majorVersion] = migrationHash; | ||
emit MigrationScriptAdded(majorVersion, migrationHash); | ||
} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
# Distributions | ||
|
||
Distributions are the main entities in EDS. They are used to distribute and upgrade contracts. | ||
|
||
Distributions are enshrined to be developed in state-less manner, this allows easier auditing and secure portability of code cross-chain. | ||
|
||
> [!WARNING] | ||
> If you deploy stateless distributions they will may likely be problematic to adapt by developers because of bytecode hash referencing by [Indexer](./Indexer.md). This is done intentionally to enshrine more secure development best practices. | ||
|
||
## Stateful distributions | ||
|
||
Stateful distributions are distributions that are not stateless. They are used to distribute and upgrade contracts that are not stateless. | ||
|
||
If you need to deploy such stateful distributions, we suggest using [Distributor](./Distributors.md) instead that will manage state of your distribution and is designed with features for that in mind. | ||
|
||
## Creating a distribution | ||
|
||
In order to create a distribution, first deploy your contract on-chain or copy address you want to use and then index contract code using [Indexer](./Indexer.md) | ||
|
||
Once indexed, you can create a distribution using one of available [distribution contracts](../src/distributions) | ||
|
||
When extending distributions, you must implement `sources()` virtual function. For every source you return there, distribution abstracts will create one way or another a proxy that will deploy and link every source to proxy. | ||
|
||
Here is simple example of stateless distribution using [CloneDistribution](../src/distributions/CloneDistribution.sol): | ||
|
||
```solidity | ||
import "@openzeppelin/contracts/utils/Strings.sol"; | ||
import "@peeramid-labs/eds/versioning/LibSemver.sol"; | ||
import "@peeramid-labs/eds/distributions/CloneDistribution.sol"; | ||
contract MyDistribution is CloneDistribution { | ||
|
||
ShortString private immutable distributionName; | ||
uint256 private immutable distributionVersion; | ||
|
||
|
||
constructor(LibSemver.Version memory version) { | ||
distributionName = ShortStrings.toShortString("MyDistribution"); | ||
distributionVersion = version.toUint256(); | ||
} | ||
using LibSemver for LibSemver.Version; | ||
function sources() public pure override returns (address[] memory sources, bytes32 name, uint256 version) { | ||
sources = new address[](1); | ||
sources[0] = 0x1234567890123456789012345678901234567890; | ||
name = ShortString.unwrap(distributionName); | ||
version = distributionVersion; | ||
} | ||
} | ||
``` | ||
|
||
### Creating upgradable repository managed distribution | ||
|
||
In EDS [Upgradability](./Upgradability.md) is complex multi-party trust process. It enables pattern where distributor & user must agree on upgrade before it can be executed. | ||
|
||
In order to enable this process, standard upgradable proxies cannot be used. Instead, we must use proxies that have [ERC7746 Hooks](./Hooks.md) implemented within immutable part of the contract. | ||
This hooks must be implemented by developer of the distribution in such way, that only [Distributor](./Distributors.md) can upgrade the distribution, but the Installer consent is checked in runtime. | ||
|
||
Example of such upgradable distribution is [WrappedTransparentUpgradeableProxy](../src/proxies/WrappedTransparentUpgradeableProxy.sol). | ||
|
||
Management of the upgrades & migrations on developer side is done via [Repository](./Repositories.md) contract. | ||
Distributors are free to implement their own logic of migration of the state, wrapping or completley bypassing Developer packages as they need to. | ||
|
||
For more details refer to [Upgradability](./Upgradability.md) documentation. | ||
|
||
## Creating Distributions from CLI | ||
|
||
> [!NOTE] | ||
> The CLI provides utilities to create and manage distributions. | ||
|
||
```bash | ||
# Deploy a new distribution with source addresses | ||
eds distribution deploy <source-addresses> --proxy-type <Upgradable|Clonable> --name <distribution-name> --version <version> [--uri <uri>] | ||
|
||
# Deploy a distribution with source code hashes | ||
eds distribution deploy-with-hashes <source-hashes> --proxy-type <Upgradable|Clonable> --name <distribution-name> --version <version> [--uri <uri>] | ||
|
||
# Get information about a distribution | ||
eds distribution <address> info | ||
|
||
# Instantiate a distribution | ||
eds distribution <address> instantiate [--data <hex-data>] | ||
|
||
# Verify a distribution's source code matches code hashes | ||
eds distribution <address> verify | ||
|
||
# Common options available for all commands | ||
--rpc-url <url> # RPC endpoint | ||
--private-key <key> # Private key for transactions | ||
--gas-limit <limit> # Optional gas limit | ||
--gas-price <price> # Optional gas price | ||
``` | ||
|
||
|
||
|
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Verification agent
🧩 Analysis chain
Consider standardizing mock distribution implementations.
This mock implements a similar functionality to
src/mocks/MockLowLevelDistribution.sol
but uses a different approach. Consider standardizing these implementations or clarifying the purpose of having two different versions.Run the following to understand the usage differences between the two implementations:
🏁 Script executed:
Length of output: 378
🏁 Script executed:
Length of output: 3570
🏁 Script executed:
Length of output: 2933
Standardize MockLowLevelDistribution Implementations
It looks like you now have two separate
MockLowLevelDistribution
contracts that both force a low-level revert but via different mechanisms and with slightly different APIs. This duplication can lead to ambiguous artifact resolution and extra maintenance.Please consolidate or clearly differentiate them:
•
src/mocks/MockLowLevelDistribution.sol
– inline assembly
invalid()
to trigger the error– emits
Distributed
event– uses
bytes32("MockLowLevelDistribution")
for the name•
contracts/mocks/MockLowLevelDistribution.sol
– OpenZeppelin
ShortStrings.toShortString
+ low-level call to address(0) +require
– no event emission
– unwraps a
ShortString
tobytes32
Action items: