
Introducing DEXES
The Web3 backbone for business processes
🥳 Welcome to Degengineering! 🥳
Welcome to Degengineering! At Degengineering, we are comitted to take a deep dive into Web3 technologies and use them to build the next generation of decentralized business applications.
<100 subscribers

Introducing DEXES
The Web3 backbone for business processes
🥳 Welcome to Degengineering! 🥳
Welcome to Degengineering! At Degengineering, we are comitted to take a deep dive into Web3 technologies and use them to build the next generation of decentralized business applications.
Share Dialog
Share Dialog


VersaProxy is a proxy contract that supports multiple versions of a logic contract and routes calls to the correct version using a given version number.

The proxy can route calls to the correct logic contract using a given uint256 version number. This allows users and developers to manage migrations more smoothly.
Thanks to ERC-7201 (Namespaced Storage Layout), developers can choose to have multiple versions of the logic contract working on the same storage slot or reset the state by changing the storage slot used by the new version of the logic contract.
By convention, all functions called through VersaProxy must have <uint256 version> as their first parameter, even if the version number is not used by the logic contract.
Libraries cannot be used as-is and must be rewritten to include the version number as the first parameter of proxiable functions.
Smart contracts are immutable—once deployed, they cannot be updated to fix bugs or add features. Until now, when you wanted to change the logic of your decentralized application, you basically had two options:
Deploy a new version of your smart contracts and update all frontends to use the new version, or juggle between different contract versions.
The two major drawbacks of this approach are:
a. All frontends must be updated at once, which can be a major issue in a truly decentralized environment.
b. Migrating the state/storage of a smart contract is neither easy nor free.
Use OpenZeppelin upgradeable contracts, where a proxy contract delegates calls to a logic contract. When developers deploy a new version of the logic contract, they call a special function on the proxy contract to update the logic contract being used.
A key benefit of this pattern is that the contract state and storage are preserved, as they belong to the proxy. However, the major drawback is the lack of soft migration:
If the second version introduces breaking changes, users who haven't upgraded in time may find their clients broken.
With both approaches, users are forced to migrate immediately when the developer pushes an update. However, in some cases, this is not feasible, as not all users can update their clients simultaneously.
VersaProxy was developed to provide an alternative way to manage smart contract upgrades with a smooth transition between versions. Developers have more flexibility to upgrade their smart contracts while allowing users to migrate at their own pace without breaking their clients.
Not everything will be possible, and not every option will be the right one.
Be cautious.
VersaProxy follows only two conventions:
Use ERC-7201 Namespaced Storage Layout in your logic contract. This gives you fine-grained control over whether different logic contract versions share the same storage slot or not.
All functions of the logic contract that are called through the proxy must include a uint256 parameter as the target version number, even if the parameter is not used by the logic contract.
The fallback function of VersaProxy scans the calldata to automatically extract and use the target version number. It does this by:
Skipping the function selector.
Reading a 32-byte word that, by convention, represents the target version number.
The rest functions similarly to OpenZeppelin's proxy contract.
/**
* @dev Fallback function that proxies calls to the target implementation by using a version number
* provided in the first 4 bytes of the calldata. The calldata will be forwarded to the implementation contract
* withtout the first 4 bytes.
*/
fallback() external payable {
// Get the target version number as a uint256 from the first parameter in calldata.
//
// The convention is all proxied functions in the implementation contracts MUST have the version number as the first parameter.
// In order to achieve this, we need to skip the function selector (4 bytes) and read the first parameter as a uint256.
uint256 version;
assembly {
// Extract the first parameter (version as uint256) from calldata
version := calldataload(4)
}
// Get the implementation address for the provided version
require(version < _nextVersion, "Unsupported version");
require(_enabledVersions[version], "Version not enabled");
address implementation = _implementations[version];
//console.log("Target version is:", version);
//console.log("Target implementation is:", implementation);
// Regular delegatecall to the implementation contract as per Proxy pattern
assembly {
// Copy calldata to memory
calldatacopy(0, 0, calldatasize())
// Delegatecall to the implementation contract
let result := delegatecall(gas(), implementation, 0, calldatasize(), 0, 0)
// Copy returndata to memory
returndatacopy(0, 0, returndatasize())
// Handle success or failure
switch result
case 0 { revert(0, returndatasize()) }
default { return(0, returndatasize()) }
}
}Below is an example of a smart contract function that has been rewritten to be used through VersaProxy (see source code: VersaOwnableV0.sol).
/**
* @param version Not used here. The version number use dby VersaProxy to route the call (convention)
* @dev Returns the address of the current owner.
*/
function owner(uint256 version) public view virtual returns (address) {
VersaOwnableV0Storage storage $ = _getVersaOwnableV0Storage();
return $._owner;
}The next steps will be to create a public code repository providing the source code of VersaProxy, its tests, rewritten contract libraries, and examples.
VersaProxy is a proxy contract that supports multiple versions of a logic contract and routes calls to the correct version using a given version number.

The proxy can route calls to the correct logic contract using a given uint256 version number. This allows users and developers to manage migrations more smoothly.
Thanks to ERC-7201 (Namespaced Storage Layout), developers can choose to have multiple versions of the logic contract working on the same storage slot or reset the state by changing the storage slot used by the new version of the logic contract.
By convention, all functions called through VersaProxy must have <uint256 version> as their first parameter, even if the version number is not used by the logic contract.
Libraries cannot be used as-is and must be rewritten to include the version number as the first parameter of proxiable functions.
Smart contracts are immutable—once deployed, they cannot be updated to fix bugs or add features. Until now, when you wanted to change the logic of your decentralized application, you basically had two options:
Deploy a new version of your smart contracts and update all frontends to use the new version, or juggle between different contract versions.
The two major drawbacks of this approach are:
a. All frontends must be updated at once, which can be a major issue in a truly decentralized environment.
b. Migrating the state/storage of a smart contract is neither easy nor free.
Use OpenZeppelin upgradeable contracts, where a proxy contract delegates calls to a logic contract. When developers deploy a new version of the logic contract, they call a special function on the proxy contract to update the logic contract being used.
A key benefit of this pattern is that the contract state and storage are preserved, as they belong to the proxy. However, the major drawback is the lack of soft migration:
If the second version introduces breaking changes, users who haven't upgraded in time may find their clients broken.
With both approaches, users are forced to migrate immediately when the developer pushes an update. However, in some cases, this is not feasible, as not all users can update their clients simultaneously.
VersaProxy was developed to provide an alternative way to manage smart contract upgrades with a smooth transition between versions. Developers have more flexibility to upgrade their smart contracts while allowing users to migrate at their own pace without breaking their clients.
Not everything will be possible, and not every option will be the right one.
Be cautious.
VersaProxy follows only two conventions:
Use ERC-7201 Namespaced Storage Layout in your logic contract. This gives you fine-grained control over whether different logic contract versions share the same storage slot or not.
All functions of the logic contract that are called through the proxy must include a uint256 parameter as the target version number, even if the parameter is not used by the logic contract.
The fallback function of VersaProxy scans the calldata to automatically extract and use the target version number. It does this by:
Skipping the function selector.
Reading a 32-byte word that, by convention, represents the target version number.
The rest functions similarly to OpenZeppelin's proxy contract.
/**
* @dev Fallback function that proxies calls to the target implementation by using a version number
* provided in the first 4 bytes of the calldata. The calldata will be forwarded to the implementation contract
* withtout the first 4 bytes.
*/
fallback() external payable {
// Get the target version number as a uint256 from the first parameter in calldata.
//
// The convention is all proxied functions in the implementation contracts MUST have the version number as the first parameter.
// In order to achieve this, we need to skip the function selector (4 bytes) and read the first parameter as a uint256.
uint256 version;
assembly {
// Extract the first parameter (version as uint256) from calldata
version := calldataload(4)
}
// Get the implementation address for the provided version
require(version < _nextVersion, "Unsupported version");
require(_enabledVersions[version], "Version not enabled");
address implementation = _implementations[version];
//console.log("Target version is:", version);
//console.log("Target implementation is:", implementation);
// Regular delegatecall to the implementation contract as per Proxy pattern
assembly {
// Copy calldata to memory
calldatacopy(0, 0, calldatasize())
// Delegatecall to the implementation contract
let result := delegatecall(gas(), implementation, 0, calldatasize(), 0, 0)
// Copy returndata to memory
returndatacopy(0, 0, returndatasize())
// Handle success or failure
switch result
case 0 { revert(0, returndatasize()) }
default { return(0, returndatasize()) }
}
}Below is an example of a smart contract function that has been rewritten to be used through VersaProxy (see source code: VersaOwnableV0.sol).
/**
* @param version Not used here. The version number use dby VersaProxy to route the call (convention)
* @dev Returns the address of the current owner.
*/
function owner(uint256 version) public view virtual returns (address) {
VersaOwnableV0Storage storage $ = _getVersaOwnableV0Storage();
return $._owner;
}The next steps will be to create a public code repository providing the source code of VersaProxy, its tests, rewritten contract libraries, and examples.
// SPDX-License-Identifier: MIT
import "./lib/openzeppelin/access/Ownable.sol";
// Import Hardhat's console
//import "hardhat/console.sol";
/**
* @title VersaProxy
* @author @degengineering.ink
* @notice This contract acts as a proxy dispatcher that routes calls to different versions of an implementation contract.
*
* @dev VersaProxy allows multiple live versions of an implementation contract to co-exist.
*
* The goals of VersaProxy are to:
* - Allow multiple releases of the implementation contract to coexist by the the time users migrate to the latest release. VersaProxy will preserve the storage and balance.
* - Allow users to choose which version of the implementation contract will be called by the proxy by providing a uint256 version number as the first parameter of the calldata.
* - Allow the owner to add new releases of an implementation contract.
* - Allow the owner to update an existing version inplace.
* - Allow the owner to enable/disable versions.
*/
contract VersaProxy is Ownable {
/*************************************************************************************************************************************/
/* STORAGE */
/*************************************************************************************************************************************/
/**
* @notice Array which contains the addresses of the implementation contracts for each version
*/
address[] private _implementations;
// Array of bool which indicates whether the version is enabled or not
bool[] private _enabledVersions;
// Next version number, starts at 0.
uint256 private _nextVersion;
/*************************************************************************************************************************************/
/* CONSTRUCTOR */
/*************************************************************************************************************************************/
constructor() Ownable(msg.sender) {
// Initialize the next version to 0
_nextVersion = 0;
}
/*************************************************************************************************************************************/
/* EVENTS */
/*************************************************************************************************************************************/
/**
* @notice Emitted when a new version of the implementation contract is released.
* @param version The version number of the release.
* @param implementation The address of the implementation contract.
*/
event Release(uint256 version, address implementation);
/**
* @notice Emitted when an existing release is updated inplace.
* @param version The version number.
* @param old The old address of the implementation contract.
* @param current The new address of the implementation contract.
*/
event Update(uint256 version, address old, address current);
/**
* @notice Emitted when a version is enabled.
* @param version The number of the version that has been enabled.
*/
event Enabled(uint256 version);
/**
* @notice Emitted when a version is disabled.
* @param version The number of the version that has been disabled.
*/
event Disabled(uint256 version);
/*************************************************************************************************************************************/
/* RELEASE MMANAGEMENT */
/*************************************************************************************************************************************/
/**
* Add a new release to this proxy and enable it.
* @param implementation The address of the implementation contract.
* @dev This function will assign the next version number to the provided release unless no release has been done yet (0).
* Only the owner can call this function.
*/
function release(address implementation) public onlyOwner {
// Create the new release
uint256 current = _release(implementation);
// Enable the new release
_enable(current);
}
/**
* Enable a specific version of the implementation contract.
* @param version The version number to enable.
* @dev Only the owner can call this function.
*/
function enable(uint256 version) public onlyOwner {
require(version < _nextVersion, "Unsupported version");
require(!_enabledVersions[version], "Version already enabled");
_enable(version);
}
/**
* Disable a specific version of the implementation contract.
* @param version The version number to disable.
* @dev Only the owner can call this function.
*/
function disable(uint256 version) public onlyOwner {
require(version < _nextVersion, "Unsupported version");
require(_enabledVersions[version], "Version already disabled");
_disable(version);
}
/**
* Update an existing release inplace and enable it.
* @param version The version number of the release to update.
* @param implementation the address of the new implementation contract.
*/
function update(uint256 version, address implementation) public onlyOwner {
require(version < _nextVersion, "Unsupported version");
_update(version, implementation);
_enable(version);
}
/**
* Get the latest version number.
*/
function latestVersion() public view returns (uint256) {
return _nextVersion == 0 ? 0 : _nextVersion - 1;
}
/**
* @dev Gets the implementation address for a specific version.
* @param version The version number.
* @return The address of the implementation contract.
*/
function getImplementation(uint256 version) public view returns (address) {
return _implementations[version];
}
/**
* Add the provided address as new release to this proxy.
* @param implementation The address of the implementation contract.
* @dev This function will assign the next version number to the provided release unless no release has been done yet (0).
*/
function _release(address implementation) internal returns (uint256) {
// Add the implementation address to the implementations array
_implementations.push(implementation);
_enabledVersions.push(false);
// Emit a Release event
emit Release(_nextVersion, implementation);
// Increment the next version number
_nextVersion++;
// Return the version number assigned to the release
return latestVersion();
}
/**
* Enable the provided version.
* @param version The version number to enable.
*/
function _enable(uint256 version) internal {
// Set the version to enabled
_enabledVersions[version] = true;
// Emit an Enabled event
emit Enabled(version);
}
/**
* Disable the provided version.
* @param version The version number to disable.
*/
function _disable(uint256 version) internal {
// Set the version to disabled
_enabledVersions[version] = false;
// Emit a Disabled event
emit Disabled(version);
}
/**
* Update the implementation address for a specific version.
* @param version The version number.
* @param implementation The address of the implementation contract.
*/
function _update(uint256 version, address implementation) internal {
// Get the old implementation address
address old = _implementations[version];
// Update the implementation address
_implementations[version] = implementation;
// Emit an Update event
emit Update(version, old, implementation);
}
/**
* @dev Fallback function that proxies calls to the target implementation by using a version number
* provided in the first 4 bytes of the calldata. The calldata will be forwarded to the implementation contract
* withtout the first 4 bytes.
*/
fallback() external payable {
// Get the target version number as a uint256 from the first parameter in calldata.
//
// The convention is all proxied functions in the implementation contracts MUST have the version number as the first parameter.
// In order to achieve this, we need to skip the function selector (4 bytes) and read the first parameter as a uint256.
uint256 version;
assembly {
// Extract the first parameter (version as uint256) from calldata
version := calldataload(4)
}
// Get the implementation address for the provided version
require(version < _nextVersion, "Unsupported version");
require(_enabledVersions[version], "Version not enabled");
address implementation = _implementations[version];
//console.log("Target version is:", version);
//console.log("Target implementation is:", implementation);
// Regular delegatecall to the implementation contract as per Proxy pattern
assembly {
// Copy calldata to memory
calldatacopy(0, 0, calldatasize())
// Delegatecall to the implementation contract
let result := delegatecall(gas(), implementation, 0, calldatasize(), 0, 0)
// Copy returndata to memory
returndatacopy(0, 0, returndatasize())
// Handle success or failure
switch result
case 0 { revert(0, returndatasize()) }
default { return(0, returndatasize()) }
}
}
// Receive Ether when msg.data is empty
receive() external payable {
// Revert with a message: no donnations accepted
revert("No donations accepted");
}
}
// SPDX-License-Identifier: UNLICENSED
import {ContextUpgradeable} from "./lib/openzeppelin/utils/ContextUpgradeable.sol";
import {Initializable} from "./lib/openzeppelin/proxy/utils/Initializable.sol";
/**
* @dev Contract module which provides a basic access control mechanism, where
* there is an account (an owner) that can be granted exclusive access to
* specific functions.
*
* The initial owner is set to the address provided by the deployer. This can
* later be changed with {transferOwnership}.
*
* This module is used through inheritance. It will make available the modifier
* `onlyOwner`, which can be applied to your functions to restrict their use to
* the owner.
*
* The only difference with OwnableUpgradeable is that all functions of the contract
* which should be callable through the proxy include a uint256 as first parameter by
* convention. This uint256 is a version number that is used by VersaProxy to route
* calls to the right live version of the implementation contract.
*/
abstract contract VersaOwnableV0 is Initializable, ContextUpgradeable {
/// @custom:storage-location erc7201:openzeppelin.storage.VersaOwnableV0
struct VersaOwnableV0Storage {
address _owner;
}
// keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.VersaOwnableV0")) - 1)) & ~bytes32(uint256(0xff))
bytes32 private constant VersaOwnableV0StorageLocation = 0x0f67f515d92eae41c3e9af6441886123951ac11f3028c192bb6c729582a00700;
function _getVersaOwnableV0Storage() private pure returns (VersaOwnableV0Storage storage $) {
assembly {
$.slot := VersaOwnableV0StorageLocation
}
}
/**
* @dev The caller account is not authorized to perform an operation.
*/
error OwnableUnauthorizedAccount(address account);
/**
* @dev The owner is not a valid owner account. (eg. `address(0)`)
*/
error OwnableInvalidOwner(address owner);
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/**
* @dev Initializes the contract setting the address provided by the deployer as the initial owner.
*/
function __Ownable_init(address initialOwner) internal onlyInitializing {
__Ownable_init_unchained(initialOwner);
}
function __Ownable_init_unchained(address initialOwner) internal onlyInitializing {
if (initialOwner == address(0)) {
revert OwnableInvalidOwner(address(0));
}
_transferOwnership(initialOwner);
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
_checkOwner();
_;
}
/**
* @param version Not used here. The version number use dby VersaProxy to route the call (convention)
* @dev Returns the address of the current owner.
*/
function owner(uint256 version) public view virtual returns (address) {
VersaOwnableV0Storage storage $ = _getVersaOwnableV0Storage();
return $._owner;
}
/**
* @dev Throws if the sender is not the owner.
*/
function _checkOwner() internal view virtual {
if (owner(0) != _msgSender()) {
revert OwnableUnauthorizedAccount(_msgSender());
}
}
/**
* @param version Not used here. The version number use dby VersaProxy to route the call (convention)
* @dev Leaves the contract without owner. It will not be possible to call
* `onlyOwner` functions. Can only be called by the current owner.
*
* NOTE: Renouncing ownership will leave the contract without an owner,
* thereby disabling any functionality that is only available to the owner.
*/
function renounceOwnership(uint256 version) public virtual onlyOwner {
_transferOwnership(address(0));
}
/**
* @param version Not used here. The version number use dby VersaProxy to route the call (convention)
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Can only be called by the current owner.
*/
function transferOwnership(uint256 version, address newOwner) public virtual onlyOwner {
if (newOwner == address(0)) {
revert OwnableInvalidOwner(address(0));
}
_transferOwnership(newOwner);
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Internal function without access restriction.
*/
function _transferOwnership(address newOwner) internal virtual {
address oldOwner = owner(0); // Remember: version number is not used internally
VersaOwnableV0Storage storage $ = _getVersaOwnableV0Storage();
$._owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
}// SPDX-License-Identifier: MIT
import "./lib/openzeppelin/access/Ownable.sol";
// Import Hardhat's console
//import "hardhat/console.sol";
/**
* @title VersaProxy
* @author @degengineering.ink
* @notice This contract acts as a proxy dispatcher that routes calls to different versions of an implementation contract.
*
* @dev VersaProxy allows multiple live versions of an implementation contract to co-exist.
*
* The goals of VersaProxy are to:
* - Allow multiple releases of the implementation contract to coexist by the the time users migrate to the latest release. VersaProxy will preserve the storage and balance.
* - Allow users to choose which version of the implementation contract will be called by the proxy by providing a uint256 version number as the first parameter of the calldata.
* - Allow the owner to add new releases of an implementation contract.
* - Allow the owner to update an existing version inplace.
* - Allow the owner to enable/disable versions.
*/
contract VersaProxy is Ownable {
/*************************************************************************************************************************************/
/* STORAGE */
/*************************************************************************************************************************************/
/**
* @notice Array which contains the addresses of the implementation contracts for each version
*/
address[] private _implementations;
// Array of bool which indicates whether the version is enabled or not
bool[] private _enabledVersions;
// Next version number, starts at 0.
uint256 private _nextVersion;
/*************************************************************************************************************************************/
/* CONSTRUCTOR */
/*************************************************************************************************************************************/
constructor() Ownable(msg.sender) {
// Initialize the next version to 0
_nextVersion = 0;
}
/*************************************************************************************************************************************/
/* EVENTS */
/*************************************************************************************************************************************/
/**
* @notice Emitted when a new version of the implementation contract is released.
* @param version The version number of the release.
* @param implementation The address of the implementation contract.
*/
event Release(uint256 version, address implementation);
/**
* @notice Emitted when an existing release is updated inplace.
* @param version The version number.
* @param old The old address of the implementation contract.
* @param current The new address of the implementation contract.
*/
event Update(uint256 version, address old, address current);
/**
* @notice Emitted when a version is enabled.
* @param version The number of the version that has been enabled.
*/
event Enabled(uint256 version);
/**
* @notice Emitted when a version is disabled.
* @param version The number of the version that has been disabled.
*/
event Disabled(uint256 version);
/*************************************************************************************************************************************/
/* RELEASE MMANAGEMENT */
/*************************************************************************************************************************************/
/**
* Add a new release to this proxy and enable it.
* @param implementation The address of the implementation contract.
* @dev This function will assign the next version number to the provided release unless no release has been done yet (0).
* Only the owner can call this function.
*/
function release(address implementation) public onlyOwner {
// Create the new release
uint256 current = _release(implementation);
// Enable the new release
_enable(current);
}
/**
* Enable a specific version of the implementation contract.
* @param version The version number to enable.
* @dev Only the owner can call this function.
*/
function enable(uint256 version) public onlyOwner {
require(version < _nextVersion, "Unsupported version");
require(!_enabledVersions[version], "Version already enabled");
_enable(version);
}
/**
* Disable a specific version of the implementation contract.
* @param version The version number to disable.
* @dev Only the owner can call this function.
*/
function disable(uint256 version) public onlyOwner {
require(version < _nextVersion, "Unsupported version");
require(_enabledVersions[version], "Version already disabled");
_disable(version);
}
/**
* Update an existing release inplace and enable it.
* @param version The version number of the release to update.
* @param implementation the address of the new implementation contract.
*/
function update(uint256 version, address implementation) public onlyOwner {
require(version < _nextVersion, "Unsupported version");
_update(version, implementation);
_enable(version);
}
/**
* Get the latest version number.
*/
function latestVersion() public view returns (uint256) {
return _nextVersion == 0 ? 0 : _nextVersion - 1;
}
/**
* @dev Gets the implementation address for a specific version.
* @param version The version number.
* @return The address of the implementation contract.
*/
function getImplementation(uint256 version) public view returns (address) {
return _implementations[version];
}
/**
* Add the provided address as new release to this proxy.
* @param implementation The address of the implementation contract.
* @dev This function will assign the next version number to the provided release unless no release has been done yet (0).
*/
function _release(address implementation) internal returns (uint256) {
// Add the implementation address to the implementations array
_implementations.push(implementation);
_enabledVersions.push(false);
// Emit a Release event
emit Release(_nextVersion, implementation);
// Increment the next version number
_nextVersion++;
// Return the version number assigned to the release
return latestVersion();
}
/**
* Enable the provided version.
* @param version The version number to enable.
*/
function _enable(uint256 version) internal {
// Set the version to enabled
_enabledVersions[version] = true;
// Emit an Enabled event
emit Enabled(version);
}
/**
* Disable the provided version.
* @param version The version number to disable.
*/
function _disable(uint256 version) internal {
// Set the version to disabled
_enabledVersions[version] = false;
// Emit a Disabled event
emit Disabled(version);
}
/**
* Update the implementation address for a specific version.
* @param version The version number.
* @param implementation The address of the implementation contract.
*/
function _update(uint256 version, address implementation) internal {
// Get the old implementation address
address old = _implementations[version];
// Update the implementation address
_implementations[version] = implementation;
// Emit an Update event
emit Update(version, old, implementation);
}
/**
* @dev Fallback function that proxies calls to the target implementation by using a version number
* provided in the first 4 bytes of the calldata. The calldata will be forwarded to the implementation contract
* withtout the first 4 bytes.
*/
fallback() external payable {
// Get the target version number as a uint256 from the first parameter in calldata.
//
// The convention is all proxied functions in the implementation contracts MUST have the version number as the first parameter.
// In order to achieve this, we need to skip the function selector (4 bytes) and read the first parameter as a uint256.
uint256 version;
assembly {
// Extract the first parameter (version as uint256) from calldata
version := calldataload(4)
}
// Get the implementation address for the provided version
require(version < _nextVersion, "Unsupported version");
require(_enabledVersions[version], "Version not enabled");
address implementation = _implementations[version];
//console.log("Target version is:", version);
//console.log("Target implementation is:", implementation);
// Regular delegatecall to the implementation contract as per Proxy pattern
assembly {
// Copy calldata to memory
calldatacopy(0, 0, calldatasize())
// Delegatecall to the implementation contract
let result := delegatecall(gas(), implementation, 0, calldatasize(), 0, 0)
// Copy returndata to memory
returndatacopy(0, 0, returndatasize())
// Handle success or failure
switch result
case 0 { revert(0, returndatasize()) }
default { return(0, returndatasize()) }
}
}
// Receive Ether when msg.data is empty
receive() external payable {
// Revert with a message: no donnations accepted
revert("No donations accepted");
}
}
// SPDX-License-Identifier: UNLICENSED
import {ContextUpgradeable} from "./lib/openzeppelin/utils/ContextUpgradeable.sol";
import {Initializable} from "./lib/openzeppelin/proxy/utils/Initializable.sol";
/**
* @dev Contract module which provides a basic access control mechanism, where
* there is an account (an owner) that can be granted exclusive access to
* specific functions.
*
* The initial owner is set to the address provided by the deployer. This can
* later be changed with {transferOwnership}.
*
* This module is used through inheritance. It will make available the modifier
* `onlyOwner`, which can be applied to your functions to restrict their use to
* the owner.
*
* The only difference with OwnableUpgradeable is that all functions of the contract
* which should be callable through the proxy include a uint256 as first parameter by
* convention. This uint256 is a version number that is used by VersaProxy to route
* calls to the right live version of the implementation contract.
*/
abstract contract VersaOwnableV0 is Initializable, ContextUpgradeable {
/// @custom:storage-location erc7201:openzeppelin.storage.VersaOwnableV0
struct VersaOwnableV0Storage {
address _owner;
}
// keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.VersaOwnableV0")) - 1)) & ~bytes32(uint256(0xff))
bytes32 private constant VersaOwnableV0StorageLocation = 0x0f67f515d92eae41c3e9af6441886123951ac11f3028c192bb6c729582a00700;
function _getVersaOwnableV0Storage() private pure returns (VersaOwnableV0Storage storage $) {
assembly {
$.slot := VersaOwnableV0StorageLocation
}
}
/**
* @dev The caller account is not authorized to perform an operation.
*/
error OwnableUnauthorizedAccount(address account);
/**
* @dev The owner is not a valid owner account. (eg. `address(0)`)
*/
error OwnableInvalidOwner(address owner);
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/**
* @dev Initializes the contract setting the address provided by the deployer as the initial owner.
*/
function __Ownable_init(address initialOwner) internal onlyInitializing {
__Ownable_init_unchained(initialOwner);
}
function __Ownable_init_unchained(address initialOwner) internal onlyInitializing {
if (initialOwner == address(0)) {
revert OwnableInvalidOwner(address(0));
}
_transferOwnership(initialOwner);
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
_checkOwner();
_;
}
/**
* @param version Not used here. The version number use dby VersaProxy to route the call (convention)
* @dev Returns the address of the current owner.
*/
function owner(uint256 version) public view virtual returns (address) {
VersaOwnableV0Storage storage $ = _getVersaOwnableV0Storage();
return $._owner;
}
/**
* @dev Throws if the sender is not the owner.
*/
function _checkOwner() internal view virtual {
if (owner(0) != _msgSender()) {
revert OwnableUnauthorizedAccount(_msgSender());
}
}
/**
* @param version Not used here. The version number use dby VersaProxy to route the call (convention)
* @dev Leaves the contract without owner. It will not be possible to call
* `onlyOwner` functions. Can only be called by the current owner.
*
* NOTE: Renouncing ownership will leave the contract without an owner,
* thereby disabling any functionality that is only available to the owner.
*/
function renounceOwnership(uint256 version) public virtual onlyOwner {
_transferOwnership(address(0));
}
/**
* @param version Not used here. The version number use dby VersaProxy to route the call (convention)
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Can only be called by the current owner.
*/
function transferOwnership(uint256 version, address newOwner) public virtual onlyOwner {
if (newOwner == address(0)) {
revert OwnableInvalidOwner(address(0));
}
_transferOwnership(newOwner);
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Internal function without access restriction.
*/
function _transferOwnership(address newOwner) internal virtual {
address oldOwner = owner(0); // Remember: version number is not used internally
VersaOwnableV0Storage storage $ = _getVersaOwnableV0Storage();
$._owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
}
13 comments
Share your creation & win 25 USDC! Quote cast or comment below something you worked on last week (art, code, writing...) and we will reward the top 10 tomorrow!
Love it! Boosted :) I wrote this last week https://warpcast.com/chicbangs.eth/0x59f2e8c9
Here is my entry ;) https://warpcast.com/disky.eth/0x0932c7e1
Last week was the close of my collection: https://www.tinyrainboot.com/even-the-devil-once-had-wings/ 11 3d models that i sculpted, minted on Base. <3
https://warpcast.com/darrylyeo/0x8f67c68b
I was at ETH Denver: - gave a talk - created content
I wrote about scammers on Farcaster and how to spot them. I hope to flesh this out into a bigger article this week. https://warpcast.com/pichi/0xfe1af460
I created a full collection on base, the /degentleladies, which is a tribute to the OF /degentlemen and it's ready to launch on @opensea on March 8th, International woman's day. https://opensea.io/collection/degentleladies
all three in one interactive ethereum explainer explain.worldcomputer.art
Prediction markets that aren’t just yes or no https://www.onit.fun/m/0x3bf364a5cf7ce58bc8032ea08777ddebdfe78951
I made thisssssss art printed out into t-shirt https://warpcast.com/zeedan/0xa8ae225d
Right on schedule. Read the article about VersaProxy on @paragraph https://paragraph.xyz/@degengineering.ink/intoducing-versaproxy-smart-contract
Right on time to keep the streak going 😅 Find our first article about VersaProxy, a new way to manage smart contract upgrades 🥳 https://paragraph.xyz/@degengineering.ink/intoducing-versaproxy-smart-contract