-
Notifications
You must be signed in to change notification settings - Fork 25
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Use AdminUpgadablilityProxy * add oz application dir to arc * improve test covers * lint * more tests
- Loading branch information
1 parent
012d1bc
commit dc45775
Showing
12 changed files
with
541 additions
and
25 deletions.
There are no files selected for viewing
This file contains 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,120 @@ | ||
pragma solidity 0.5.17; | ||
|
||
import "./ImplementationProvider.sol"; | ||
import "./Package.sol"; | ||
import "@openzeppelin/upgrades/contracts/upgradeability/AdminUpgradeabilityProxy.sol"; | ||
import "@openzeppelin/upgrades/contracts/ownership/Ownable.sol"; | ||
|
||
|
||
/** | ||
* @title App | ||
* @dev Contract for upgradeable applications. | ||
* It handles the creation of proxies. | ||
*/ | ||
contract App is OpenZeppelinUpgradesOwnable { | ||
/** | ||
* @dev Emitted when a new proxy is created. | ||
* @param proxy Address of the created proxy. | ||
*/ | ||
event ProxyCreated(address proxy); | ||
|
||
/** | ||
* @dev Emitted when a package dependency is changed in the application. | ||
* @param providerName Name of the package that changed. | ||
* @param package Address of the package associated to the name. | ||
* @param version Version of the package in use. | ||
*/ | ||
event PackageChanged(string providerName, address package, uint64[3] version); | ||
|
||
/** | ||
* @dev Tracks a package in a particular version, used for retrieving implementations | ||
*/ | ||
struct ProviderInfo { | ||
Package package; | ||
uint64[3] version; | ||
} | ||
|
||
/** | ||
* @dev Maps from dependency name to a tuple of package and version | ||
*/ | ||
mapping(string => ProviderInfo) internal providers; | ||
|
||
/** | ||
* @dev Sets a package in a specific version as a dependency for this application. | ||
* Requires the version to be present in the package. | ||
* @param packageName Name of the package to set or overwrite. | ||
* @param package Address of the package to register. | ||
* @param version Version of the package to use in this application. | ||
*/ | ||
function setPackage(string memory packageName, Package package, uint64[3] memory version) public onlyOwner { | ||
require(package.hasVersion(version), "The requested version must be registered in the given package"); | ||
providers[packageName] = ProviderInfo(package, version); | ||
emit PackageChanged(packageName, address(package), version); | ||
} | ||
|
||
/** | ||
* @dev Unsets a package given its name. | ||
* Reverts if the package is not set in the application. | ||
* @param packageName Name of the package to remove. | ||
*/ | ||
function unsetPackage(string memory packageName) public onlyOwner { | ||
require(address(providers[packageName].package) != address(0), "Package to unset not found"); | ||
delete providers[packageName]; | ||
emit PackageChanged(packageName, address(0), [uint64(0), uint64(0), uint64(0)]); | ||
} | ||
|
||
/** | ||
* @dev Creates a new proxy for the given contract and forwards a function call to it. | ||
* This is useful to initialize the proxied contract. | ||
* @param packageName Name of the package where the contract is contained. | ||
* @param contractName Name of the contract. | ||
* @param admin Address of the proxy administrator. | ||
* @param data Data to send as msg.data to the corresponding implementation to initialize the proxied contract. | ||
* It should include the signature and the parameters of the function to be called, as described in | ||
* https://solidity.readthedocs.io/en/v0.4.24/abi-spec.html#function-selector-and-argument-encoding. | ||
* This parameter is optional, if no data is given the initialization call to proxied contract will be skipped. | ||
* @return Address of the new proxy. | ||
*/ | ||
function create(string memory packageName, string memory contractName, address admin, bytes memory data) | ||
public | ||
payable | ||
returns (AdminUpgradeabilityProxy) { | ||
address implementation = getImplementation(packageName, contractName); | ||
AdminUpgradeabilityProxy proxy = (new AdminUpgradeabilityProxy).value(msg.value)(implementation, admin, data); | ||
emit ProxyCreated(address(proxy)); | ||
return proxy; | ||
} | ||
|
||
/** | ||
* @dev Returns the implementation address for a given contract name, provided by the `ImplementationProvider`. | ||
* @param packageName Name of the package where the contract is contained. | ||
* @param contractName Name of the contract. | ||
* @return Address where the contract is implemented. | ||
*/ | ||
function getImplementation(string memory packageName, string memory contractName) public view returns (address) { | ||
ImplementationProvider provider = getProvider(packageName); | ||
if (address(provider) == address(0)) return address(0); | ||
return provider.getImplementation(contractName); | ||
} | ||
|
||
/** | ||
* @dev Returns the provider for a given package name, or zero if not set. | ||
* @param packageName Name of the package to be retrieved. | ||
* @return The provider. | ||
*/ | ||
function getProvider(string memory packageName) public view returns (ImplementationProvider provider) { | ||
ProviderInfo storage info = providers[packageName]; | ||
if (address(info.package) == address(0)) return ImplementationProvider(0); | ||
return ImplementationProvider(info.package.getContract(info.version)); | ||
} | ||
|
||
/** | ||
* @dev Returns information on a package given its name. | ||
* @param packageName Name of the package to be queried. | ||
* @return A tuple with the package address and pinned version given a package name, or zero if not set | ||
*/ | ||
function getPackage(string memory packageName) public view returns (Package, uint64[3] memory) { | ||
ProviderInfo storage info = providers[packageName]; | ||
return (info.package, info.version); | ||
} | ||
} |
This file contains 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,77 @@ | ||
pragma solidity 0.5.17; | ||
|
||
import "./ImplementationProvider.sol"; | ||
import "@openzeppelin/upgrades/contracts/ownership/Ownable.sol"; | ||
import "@openzeppelin/upgrades/contracts/utils/Address.sol"; | ||
|
||
|
||
/** | ||
* @title ImplementationDirectory | ||
* @dev Implementation provider that stores contract implementations in a mapping. | ||
*/ | ||
contract ImplementationDirectory is ImplementationProvider, OpenZeppelinUpgradesOwnable { | ||
/** | ||
* @dev Emitted when the implementation of a contract is changed. | ||
* @param contractName Name of the contract. | ||
* @param implementation Address of the added implementation. | ||
*/ | ||
event ImplementationChanged(string contractName, address indexed implementation); | ||
|
||
/** | ||
* @dev Emitted when the implementation directory is frozen. | ||
*/ | ||
event Frozen(); | ||
|
||
/// @dev Mapping where the addresses of the implementations are stored. | ||
mapping (string => address) internal implementations; | ||
|
||
/// @dev Mutability state of the directory. | ||
bool public frozen; | ||
|
||
/** | ||
* @dev Modifier that allows functions to be called only before the contract is frozen. | ||
*/ | ||
modifier whenNotFrozen() { | ||
require(!frozen, "Cannot perform action for a frozen implementation directory"); | ||
_; | ||
} | ||
|
||
/** | ||
* @dev Makes the directory irreversibly immutable. | ||
* It can only be called once, by the owner. | ||
*/ | ||
function freeze() public onlyOwner whenNotFrozen { | ||
frozen = true; | ||
emit Frozen(); | ||
} | ||
|
||
/** | ||
* @dev Sets the address of the implementation of a contract in the directory. | ||
* @param contractName Name of the contract. | ||
* @param implementation Address of the implementation. | ||
*/ | ||
function setImplementation(string memory contractName, address implementation) public onlyOwner whenNotFrozen { | ||
require(OpenZeppelinUpgradesAddress.isContract(implementation), | ||
"Cannot set implementation in directory with a non-contract address"); | ||
implementations[contractName] = implementation; | ||
emit ImplementationChanged(contractName, implementation); | ||
} | ||
|
||
/** | ||
* @dev Removes the address of a contract implementation from the directory. | ||
* @param contractName Name of the contract. | ||
*/ | ||
function unsetImplementation(string memory contractName) public onlyOwner whenNotFrozen { | ||
implementations[contractName] = address(0); | ||
emit ImplementationChanged(contractName, address(0)); | ||
} | ||
|
||
/** | ||
* @dev Returns the implementation address of a contract. | ||
* @param contractName Name of the contract. | ||
* @return Address of the implementation. | ||
*/ | ||
function getImplementation(string memory contractName) public view returns (address) { | ||
return implementations[contractName]; | ||
} | ||
} |
This file contains 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,15 @@ | ||
pragma solidity 0.5.17; | ||
|
||
|
||
/** | ||
* @title ImplementationProvider | ||
* @dev Abstract contract for providing implementation addresses for other contracts by name. | ||
*/ | ||
contract ImplementationProvider { | ||
/** | ||
* @dev Abstract function to return the implementation address of a contract. | ||
* @param contractName Name of the contract. | ||
* @return Implementation address of the contract. | ||
*/ | ||
function getImplementation(string memory contractName) public view returns (address); | ||
} |
This file contains 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,141 @@ | ||
pragma solidity 0.5.17; | ||
|
||
import "@openzeppelin/upgrades/contracts/ownership/Ownable.sol"; | ||
|
||
|
||
/** | ||
* @title Package | ||
* @dev A package is composed by a set of versions, identified via semantic versioning, | ||
* where each version has a contract address that refers to a reusable implementation, | ||
* plus an optional content URI with metadata. Note that the semver identifier is restricted | ||
* to major, minor, and patch, as prerelease tags are not supported. | ||
*/ | ||
contract Package is OpenZeppelinUpgradesOwnable { | ||
/** | ||
* @dev Emitted when a version is added to the package. | ||
* @param semanticVersion Name of the added version. | ||
* @param contractAddress Contract associated with the version. | ||
* @param contentURI Optional content URI with metadata of the version. | ||
*/ | ||
event VersionAdded(uint64[3] semanticVersion, address contractAddress, bytes contentURI); | ||
|
||
struct Version { | ||
uint64[3] semanticVersion; | ||
address contractAddress; | ||
bytes contentURI; | ||
} | ||
|
||
mapping (bytes32 => Version) internal versions; | ||
mapping (uint64 => bytes32) internal majorToLatestVersion; | ||
uint64 internal latestMajor; | ||
|
||
/** | ||
* @dev Adds a new version to the package. Only the Owner can add new versions. | ||
* Reverts if the specified semver identifier already exists. | ||
* Emits a `VersionAdded` event if successful. | ||
* @param semanticVersion Semver identifier of the version. | ||
* @param contractAddress Contract address for the version, must be non-zero. | ||
* @param contentURI Optional content URI for the version. | ||
*/ | ||
function addVersion(uint64[3] memory semanticVersion, address contractAddress, bytes memory contentURI) | ||
public | ||
onlyOwner { | ||
require(contractAddress != address(0), "Contract address is required"); | ||
require(!hasVersion(semanticVersion), "Given version is already registered in package"); | ||
require(!semanticVersionIsZero(semanticVersion), "Version must be non zero"); | ||
|
||
// Register version | ||
bytes32 versionId = semanticVersionHash(semanticVersion); | ||
versions[versionId] = Version(semanticVersion, contractAddress, contentURI); | ||
|
||
// Update latest major | ||
uint64 major = semanticVersion[0]; | ||
if (major > latestMajor) { | ||
latestMajor = semanticVersion[0]; | ||
} | ||
|
||
// Update latest version for this major | ||
uint64 minor = semanticVersion[1]; | ||
uint64 patch = semanticVersion[2]; | ||
uint64[3] storage latestVersionForMajor = versions[majorToLatestVersion[major]].semanticVersion; | ||
if (semanticVersionIsZero(latestVersionForMajor) // No latest was set for this major | ||
|| (minor > latestVersionForMajor[1]) // Or current minor is greater | ||
|| (minor == latestVersionForMajor[1] && patch > latestVersionForMajor[2]) // Or current patch is greater | ||
) { | ||
majorToLatestVersion[major] = versionId; | ||
} | ||
|
||
emit VersionAdded(semanticVersion, contractAddress, contentURI); | ||
} | ||
|
||
/** | ||
* @dev Checks whether a version is present in the package. | ||
* @param semanticVersion Semver identifier of the version. | ||
* @return true if the version is registered in this package, false otherwise. | ||
*/ | ||
function hasVersion(uint64[3] memory semanticVersion) public view returns (bool) { | ||
Version storage version = versions[semanticVersionHash(semanticVersion)]; | ||
return address(version.contractAddress) != address(0); | ||
} | ||
|
||
/** | ||
* @dev Returns the version with the highest semver identifier registered in the package. | ||
* For instance, if `1.2.0`, `1.3.0`, and `2.0.0` are present, will always return `2.0.0`, regardless | ||
* of the order in which they were registered. Returns zero if no versions are registered. | ||
* @return Semver identifier, contract address, and content URI for the version, or zero if not exists. | ||
*/ | ||
function getLatest() | ||
public | ||
view | ||
returns (uint64[3] memory semanticVersion, address contractAddress, bytes memory contentURI) { | ||
return getLatestByMajor(latestMajor); | ||
} | ||
|
||
/** | ||
* @dev Returns the version with the highest semver identifier for the given major. | ||
* For instance, if `1.2.0`, `1.3.0`, and `2.0.0` are present, will return `1.3.0` for major `1`, | ||
* regardless of the order in which they were registered. Returns zero if no versions are registered | ||
* for the specified major. | ||
* @param major Major identifier to query | ||
* @return Semver identifier, contract address, and content URI for the version, or zero if not exists. | ||
*/ | ||
function getLatestByMajor(uint64 major) | ||
public | ||
view | ||
returns (uint64[3] memory semanticVersion, address contractAddress, bytes memory contentURI) { | ||
Version storage version = versions[majorToLatestVersion[major]]; | ||
return (version.semanticVersion, version.contractAddress, version.contentURI); | ||
} | ||
|
||
/** | ||
* @dev Returns a version given its semver identifier. | ||
* @param semanticVersion Semver identifier of the version. | ||
* @return Contract address and content URI for the version, or zero if not exists. | ||
*/ | ||
function getVersion(uint64[3] memory semanticVersion) | ||
public | ||
view | ||
returns (address contractAddress, bytes memory contentURI) { | ||
Version storage version = versions[semanticVersionHash(semanticVersion)]; | ||
return (version.contractAddress, version.contentURI); | ||
} | ||
|
||
/** | ||
* @dev Returns a contract for a version given its semver identifier. | ||
* This method is equivalent to `getVersion`, but returns only the contract address. | ||
* @param semanticVersion Semver identifier of the version. | ||
* @return Contract address for the version, or zero if not exists. | ||
*/ | ||
function getContract(uint64[3] memory semanticVersion) public view returns (address contractAddress) { | ||
Version storage version = versions[semanticVersionHash(semanticVersion)]; | ||
return version.contractAddress; | ||
} | ||
|
||
function semanticVersionHash(uint64[3] memory version) internal pure returns (bytes32) { | ||
return keccak256(abi.encodePacked(version[0], version[1], version[2])); | ||
} | ||
|
||
function semanticVersionIsZero(uint64[3] memory version) internal pure returns (bool) { | ||
return version[0] == 0 && version[1] == 0 && version[2] == 0; | ||
} | ||
} |
This file contains 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
Oops, something went wrong.