| 
 | 1 | +pragma solidity 0.5.17;  | 
 | 2 | + | 
 | 3 | +import "@openzeppelin/upgrades/contracts/ownership/Ownable.sol";  | 
 | 4 | + | 
 | 5 | + | 
 | 6 | +/**  | 
 | 7 | + * @title Package  | 
 | 8 | + * @dev A package is composed by a set of versions, identified via semantic versioning,  | 
 | 9 | + * where each version has a contract address that refers to a reusable implementation,  | 
 | 10 | + * plus an optional content URI with metadata. Note that the semver identifier is restricted  | 
 | 11 | + * to major, minor, and patch, as prerelease tags are not supported.  | 
 | 12 | + */  | 
 | 13 | +contract Package is OpenZeppelinUpgradesOwnable {  | 
 | 14 | +  /**  | 
 | 15 | +   * @dev Emitted when a version is added to the package.  | 
 | 16 | +   * @param semanticVersion Name of the added version.  | 
 | 17 | +   * @param contractAddress Contract associated with the version.  | 
 | 18 | +   * @param contentURI Optional content URI with metadata of the version.  | 
 | 19 | +   */  | 
 | 20 | +    event VersionAdded(uint64[3] semanticVersion, address contractAddress, bytes contentURI);  | 
 | 21 | + | 
 | 22 | +    struct Version {  | 
 | 23 | +        uint64[3] semanticVersion;  | 
 | 24 | +        address contractAddress;  | 
 | 25 | +        bytes contentURI;  | 
 | 26 | +    }  | 
 | 27 | + | 
 | 28 | +    mapping (bytes32 => Version) internal versions;  | 
 | 29 | +    mapping (uint64 => bytes32) internal majorToLatestVersion;  | 
 | 30 | +    uint64 internal latestMajor;  | 
 | 31 | + | 
 | 32 | +  /**  | 
 | 33 | +   * @dev Adds a new version to the package. Only the Owner can add new versions.  | 
 | 34 | +   * Reverts if the specified semver identifier already exists.  | 
 | 35 | +   * Emits a `VersionAdded` event if successful.  | 
 | 36 | +   * @param semanticVersion Semver identifier of the version.  | 
 | 37 | +   * @param contractAddress Contract address for the version, must be non-zero.  | 
 | 38 | +   * @param contentURI Optional content URI for the version.  | 
 | 39 | +   */  | 
 | 40 | +    function addVersion(uint64[3] memory semanticVersion, address contractAddress, bytes memory contentURI)  | 
 | 41 | +    public  | 
 | 42 | +    onlyOwner {  | 
 | 43 | +        require(contractAddress != address(0), "Contract address is required");  | 
 | 44 | +        require(!hasVersion(semanticVersion), "Given version is already registered in package");  | 
 | 45 | +        require(!semanticVersionIsZero(semanticVersion), "Version must be non zero");  | 
 | 46 | + | 
 | 47 | +        // Register version  | 
 | 48 | +        bytes32 versionId = semanticVersionHash(semanticVersion);  | 
 | 49 | +        versions[versionId] = Version(semanticVersion, contractAddress, contentURI);  | 
 | 50 | + | 
 | 51 | +        // Update latest major  | 
 | 52 | +        uint64 major = semanticVersion[0];  | 
 | 53 | +        if (major > latestMajor) {  | 
 | 54 | +            latestMajor = semanticVersion[0];  | 
 | 55 | +        }  | 
 | 56 | + | 
 | 57 | +        // Update latest version for this major  | 
 | 58 | +        uint64 minor = semanticVersion[1];  | 
 | 59 | +        uint64 patch = semanticVersion[2];  | 
 | 60 | +        uint64[3] storage latestVersionForMajor = versions[majorToLatestVersion[major]].semanticVersion;  | 
 | 61 | +        if (semanticVersionIsZero(latestVersionForMajor) // No latest was set for this major  | 
 | 62 | +            || (minor > latestVersionForMajor[1]) // Or current minor is greater  | 
 | 63 | +            || (minor == latestVersionForMajor[1] && patch > latestVersionForMajor[2]) // Or current patch is greater  | 
 | 64 | +        ) {  | 
 | 65 | +            majorToLatestVersion[major] = versionId;  | 
 | 66 | +        }  | 
 | 67 | + | 
 | 68 | +        emit VersionAdded(semanticVersion, contractAddress, contentURI);  | 
 | 69 | +    }  | 
 | 70 | + | 
 | 71 | +  /**  | 
 | 72 | +   * @dev Checks whether a version is present in the package.  | 
 | 73 | +   * @param semanticVersion Semver identifier of the version.  | 
 | 74 | +   * @return true if the version is registered in this package, false otherwise.  | 
 | 75 | +   */  | 
 | 76 | +    function hasVersion(uint64[3] memory semanticVersion) public view returns (bool) {  | 
 | 77 | +        Version storage version = versions[semanticVersionHash(semanticVersion)];  | 
 | 78 | +        return address(version.contractAddress) != address(0);  | 
 | 79 | +    }  | 
 | 80 | + | 
 | 81 | +  /**  | 
 | 82 | +   * @dev Returns the version with the highest semver identifier registered in the package.  | 
 | 83 | +   * For instance, if `1.2.0`, `1.3.0`, and `2.0.0` are present, will always return `2.0.0`, regardless  | 
 | 84 | +   * of the order in which they were registered. Returns zero if no versions are registered.  | 
 | 85 | +   * @return Semver identifier, contract address, and content URI for the version, or zero if not exists.  | 
 | 86 | +   */  | 
 | 87 | +    function getLatest()  | 
 | 88 | +    public  | 
 | 89 | +    view  | 
 | 90 | +    returns (uint64[3] memory semanticVersion, address contractAddress, bytes memory contentURI) {  | 
 | 91 | +        return getLatestByMajor(latestMajor);  | 
 | 92 | +    }  | 
 | 93 | + | 
 | 94 | +  /**  | 
 | 95 | +   * @dev Returns the version with the highest semver identifier for the given major.  | 
 | 96 | +   * For instance, if `1.2.0`, `1.3.0`, and `2.0.0` are present, will return `1.3.0` for major `1`,  | 
 | 97 | +   * regardless of the order in which they were registered. Returns zero if no versions are registered  | 
 | 98 | +   * for the specified major.  | 
 | 99 | +   * @param major Major identifier to query  | 
 | 100 | +   * @return Semver identifier, contract address, and content URI for the version, or zero if not exists.  | 
 | 101 | +   */  | 
 | 102 | +    function getLatestByMajor(uint64 major)  | 
 | 103 | +    public  | 
 | 104 | +    view  | 
 | 105 | +    returns (uint64[3] memory semanticVersion, address contractAddress, bytes memory contentURI) {  | 
 | 106 | +        Version storage version = versions[majorToLatestVersion[major]];  | 
 | 107 | +        return (version.semanticVersion, version.contractAddress, version.contentURI);  | 
 | 108 | +    }  | 
 | 109 | + | 
 | 110 | +    /**  | 
 | 111 | +     * @dev Returns a version given its semver identifier.  | 
 | 112 | +     * @param semanticVersion Semver identifier of the version.  | 
 | 113 | +     * @return Contract address and content URI for the version, or zero if not exists.  | 
 | 114 | +     */  | 
 | 115 | +    function getVersion(uint64[3] memory semanticVersion)  | 
 | 116 | +    public  | 
 | 117 | +    view  | 
 | 118 | +    returns (address contractAddress, bytes memory contentURI) {  | 
 | 119 | +        Version storage version = versions[semanticVersionHash(semanticVersion)];  | 
 | 120 | +        return (version.contractAddress, version.contentURI);  | 
 | 121 | +    }  | 
 | 122 | + | 
 | 123 | +  /**  | 
 | 124 | +   * @dev Returns a contract for a version given its semver identifier.  | 
 | 125 | +   * This method is equivalent to `getVersion`, but returns only the contract address.  | 
 | 126 | +   * @param semanticVersion Semver identifier of the version.  | 
 | 127 | +   * @return Contract address for the version, or zero if not exists.  | 
 | 128 | +   */  | 
 | 129 | +    function getContract(uint64[3] memory semanticVersion) public view returns (address contractAddress) {  | 
 | 130 | +        Version storage version = versions[semanticVersionHash(semanticVersion)];  | 
 | 131 | +        return version.contractAddress;  | 
 | 132 | +    }  | 
 | 133 | + | 
 | 134 | +    function semanticVersionHash(uint64[3] memory version) internal pure returns (bytes32) {  | 
 | 135 | +        return keccak256(abi.encodePacked(version[0], version[1], version[2]));  | 
 | 136 | +    }  | 
 | 137 | + | 
 | 138 | +    function semanticVersionIsZero(uint64[3] memory version) internal pure returns (bool) {  | 
 | 139 | +        return version[0] == 0 && version[1] == 0 && version[2] == 0;  | 
 | 140 | +    }  | 
 | 141 | +}  | 
0 commit comments