Skip to content

Commit

Permalink
Create voucher with , VoucherHub address, Set account as owner, expir…
Browse files Browse the repository at this point in the history
…ation and deal with Authorization (#6)
  • Loading branch information
gfournieriExec authored Apr 4, 2024
2 parents 5fda8d1 + 2c764ec commit aaa0327
Show file tree
Hide file tree
Showing 10 changed files with 529 additions and 182 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Changelog

## vNEXT
- Create voucher from VoucherHub with : type, expiration, authorize list. (#6)
- Create vouchers with create2. (#5)
- Create upgradeable voucher contract. (#4)
- Add voucher type structure, duration, description and asset eligible. (#3)
Expand Down
1 change: 1 addition & 0 deletions Jenkinsfile
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

node {
stage('Clone') {
cleanWs()
checkoutInfo = checkout(scm)
echo "git checkout: ${checkoutInfo}"
}
Expand Down
35 changes: 31 additions & 4 deletions contracts/IVoucherHub.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,43 @@
pragma solidity ^0.8.20;

interface IVoucherHub {
event VoucherCreated(address indexed voucher, address owner, uint256 expiration);
struct VoucherType {
string description;
uint256 duration;
}
event VoucherCreated(
address indexed voucher,
address owner,
uint256 expiration,
uint256 voucherType
);
event VoucherTypeCreated(uint256 indexed id, string description, uint256 duration);
event VoucherTypeDescriptionUpdated(uint256 indexed id, string description);
event VoucherTypeDurationUpdated(uint256 indexed id, uint256 duration);
event EligibleAssetAdded(uint256 indexed id, address asset);
event EligibleAssetRemoved(uint256 indexed id, address asset);

function createVoucher(
address account,
uint256 expiration
address owner,
uint256 voucherType
) external returns (address voucherAddress);
function getVoucher(address account) external view returns (address voucherAddress);

function getVoucher(address account) external view returns (address);

function getVoucherBeacon() external view returns (address);

function getIexecPoco() external view returns (address);

function getVoucherTypeCount() external view returns (uint256);

function getVoucherType(uint256 id) external view returns (VoucherType memory);

function addEligibleAsset(uint256 voucherTypeId, address asset) external;

function removeEligibleAsset(uint256 voucherTypeId, address asset) external;

function isAssetEligibleToMatchOrdersSponsoring(
uint256 voucherTypeId,
address asset
) external view returns (bool);
}
61 changes: 41 additions & 20 deletions contracts/VoucherHub.sol
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// SPDX-FileCopyrightText: 2024 IEXEC BLOCKCHAIN TECH <[email protected]>
// SPDX-License-Identifier: Apache-2.0

pragma solidity ^0.8.20;

import {Create2} from "@openzeppelin/contracts/utils/Create2.sol";
import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
Expand All @@ -9,13 +11,7 @@ import {Voucher} from "./beacon/Voucher.sol";
import {VoucherProxy} from "./beacon/VoucherProxy.sol";
import {IVoucherHub} from "./IVoucherHub.sol";

pragma solidity ^0.8.20;

contract VoucherHub is OwnableUpgradeable, UUPSUpgradeable, IVoucherHub {
struct VoucherType {
string description;
uint256 duration;
}
/// @custom:storage-location erc7201:iexec.voucher.storage.VoucherHub
struct VoucherHubStorage {
address _iexecPoco;
Expand Down Expand Up @@ -86,33 +82,49 @@ contract VoucherHub is OwnableUpgradeable, UUPSUpgradeable, IVoucherHub {
emit VoucherTypeDurationUpdated(id, duration);
}

/**
* Get the voucher type details by ID.
*/
function getVoucherType(
uint256 id
) public view whenVoucherTypeExists(id) returns (VoucherType memory) {
VoucherHubStorage storage $ = _getVoucherHubStorage();
return $.voucherTypes[id];
}

/**
* Get voucher types count.
*/
function getVoucherTypeCount() public view returns (uint256) {
VoucherHubStorage storage $ = _getVoucherHubStorage();
return $.voucherTypes.length;
}

/**
* Add an eligible asset to a voucher type.
* @param voucherTypeId The ID of the voucher type.
* @param asset The address of the asset to add.
*/
function addEligibleAsset(uint256 voucherTypeId, address asset) external onlyOwner {
_setAssetEligibility(voucherTypeId, asset, true);
emit EligibleAssetAdded(voucherTypeId, asset);
}

/**
* Remove an eligible asset to a voucher type.
* @param voucherTypeId The ID of the voucher type.
* @param asset The address of the asset to remove.
*/
function removeEligibleAsset(uint256 voucherTypeId, address asset) external onlyOwner {
_setAssetEligibility(voucherTypeId, asset, false);
emit EligibleAssetRemoved(voucherTypeId, asset);
}

function _setAssetEligibility(uint256 voucherTypeId, address asset, bool isEligible) private {
VoucherHubStorage storage $ = _getVoucherHubStorage();
$.matchOrdersEligibility[voucherTypeId][asset] = isEligible;
}

/**
* Check if an asset is eligible to match orders sponsoring.
* @param voucherTypeId The ID of the voucher type.
* @param asset The address of the asset to check.
*/
function isAssetEligibleToMatchOrdersSponsoring(
uint256 voucherTypeId,
address asset
Expand All @@ -121,6 +133,9 @@ contract VoucherHub is OwnableUpgradeable, UUPSUpgradeable, IVoucherHub {
return $.matchOrdersEligibility[voucherTypeId][asset];
}

/**
* Get iExec Poco address used by vouchers.
*/
function getIexecPoco() public view returns (address) {
VoucherHubStorage storage $ = _getVoucherHubStorage();
return $._iexecPoco;
Expand All @@ -143,31 +158,32 @@ contract VoucherHub is OwnableUpgradeable, UUPSUpgradeable, IVoucherHub {
* @dev Note: the same account could have 2 voucher instances if the "beaconAddress"
* changes, but this should not happen since the beacon is upgradeable, hence the
* address should never be changed.
*
* @param owner voucher owner
* @param expiration voucher expiration
* @param owner The address of the voucher owner.
* @param voucherType The ID of the voucher type.
* @return voucherAddress The address of the created voucher contract.
*/
function createVoucher(
address owner,
uint256 expiration
) external override onlyOwner returns (address voucherAddress) {
uint256 voucherType
) external onlyOwner returns (address voucherAddress) {
VoucherHubStorage storage $ = _getVoucherHubStorage();
uint256 voucherExpiration = block.timestamp + getVoucherType(voucherType).duration;
voucherAddress = address(new VoucherProxy{salt: _getCreate2Salt(owner)}($._voucherBeacon));
// Initialize the created proxy contract.
// The proxy contract does a delegatecall to its implementation.
// Re-Entrancy safe because the target contract is controlled.
Voucher(voucherAddress).initialize(owner, expiration);
emit VoucherCreated(voucherAddress, owner, expiration);
Voucher(voucherAddress).initialize(owner, address(this), voucherExpiration, voucherType);
emit VoucherCreated(voucherAddress, owner, voucherExpiration, voucherType);
}

/**
* TODO return Voucher structure.
*
* Get voucher address of a given account.
* Returns address(0) if voucher is not found.
* @param account owner address.
* @param account voucher's owner address.
*/
function getVoucher(address account) public view override returns (address voucherAddress) {
function getVoucher(address account) public view returns (address voucherAddress) {
VoucherHubStorage storage $ = _getVoucherHubStorage();
voucherAddress = Create2.computeAddress(
_getCreate2Salt(account), // salt
Expand All @@ -178,6 +194,11 @@ contract VoucherHub is OwnableUpgradeable, UUPSUpgradeable, IVoucherHub {

function _authorizeUpgrade(address newImplementation) internal override onlyOwner {}

function _setAssetEligibility(uint256 voucherTypeId, address asset, bool isEligible) private {
VoucherHubStorage storage $ = _getVoucherHubStorage();
$.matchOrdersEligibility[voucherTypeId][asset] = isEligible;
}

function _getCreate2Salt(address account) private pure returns (bytes32) {
return bytes32(uint256(uint160(account)));
}
Expand Down
12 changes: 8 additions & 4 deletions contracts/beacon/IVoucher.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,15 @@

pragma solidity ^0.8.20;

/**
* @title Interface of the voucher contract.
*/
interface IVoucher {
event ExpirationUpdated(uint256 newExpiration);
event AccountAuthorized(address indexed account);
event AccountUnauthorized(address indexed account);

function getVoucherHub() external view returns (address);
function getExpiration() external view returns (uint256);
function getType() external view returns (uint256);

function authorizeAccount(address account) external;
function unauthorizeAccount(address account) external;
function isAccountAuthorized(address account) external view returns (bool);
}
92 changes: 83 additions & 9 deletions contracts/beacon/Voucher.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,43 +8,117 @@ import {IVoucher} from "./IVoucher.sol";

/**
* @title Implementation of the voucher contract.
* @notice Deployed along the Beacon contract using "Upgrades" plugin of OZ.
* Deployed along the Beacon contract using "Upgrades" plugin of OZ.
*/
contract Voucher is OwnableUpgradeable, IVoucher {
/// @custom:storage-location erc7201:iexec.voucher.storage.Voucher
struct VoucherStorage {
address _voucherHub;
uint256 _expiration;
uint256 _type;
mapping(address => bool) _authorizedAccounts;
}

// keccak256(abi.encode(uint256(keccak256("iexec.voucher.storage.Voucher")) - 1))
// & ~bytes32(uint256(0xff));
bytes32 private constant VOUCHER_STORAGE_LOCATION =
0xc2e244293dc04d6c7fa946e063317ff8e6770fd48cbaff411a60f1efc8a7e800;

function _getVoucherStorage() private pure returns (VoucherStorage storage $) {
assembly {
$.slot := VOUCHER_STORAGE_LOCATION
}
}

/// @custom:oz-upgrades-unsafe-allow constructor
constructor() {
_disableInitializers();
}

/**
* Initialize implementation contract.
* @param expiration initial expiration.
* @param owner The owner of the contract.
* @param voucherTypeId The type Id of the voucher.
* @param expiration The expiration timestamp of the voucher.
* @param voucherHub The address of the voucher hub.
*/
function initialize(address owner, uint256 expiration) external initializer {
function initialize(
address owner,
address voucherHub,
uint256 expiration,
uint256 voucherTypeId
) external initializer {
__Ownable_init(owner);
VoucherStorage storage $ = _getVoucherStorage();
$._voucherHub = voucherHub;
$._expiration = expiration;
emit ExpirationUpdated(expiration);
$._type = voucherTypeId;
// TODO: deposit sRLC.
}

function getExpiration() external view override returns (uint256) {
/**
* Retrieve the address of the voucher hub associated with the voucher.
* @return voucherHubAddress The address of the voucher hub.
*/
function getVoucherHub() external view returns (address) {
VoucherStorage storage $ = _getVoucherStorage();
return $._voucherHub;
}

/**
* Retrieve the expiration timestamp of the voucher.
* @return expirationTimestamp The expiration timestamp.
*/
function getExpiration() external view returns (uint256) {
VoucherStorage storage $ = _getVoucherStorage();
return $._expiration;
}

function _getVoucherStorage() private pure returns (VoucherStorage storage $) {
assembly {
$.slot := VOUCHER_STORAGE_LOCATION
}
/**
* Retrieve the type of the voucher.
* @return voucherType The type of the voucher.
*/
function getType() external view returns (uint256) {
VoucherStorage storage $ = _getVoucherStorage();
return $._type;
}

/**
* Sets authorization for an account.
* @param account The account to authorize.
*/
function authorizeAccount(address account) external onlyOwner {
_setAccountAuthorization(account, true);
emit AccountAuthorized(account);
}

/**
* Unsets authorization for an account.
* @param account The account to remove authorization from.
*/
function unauthorizeAccount(address account) external onlyOwner {
_setAccountAuthorization(account, false);
emit AccountUnauthorized(account);
}

/**
* Checks if an account is authorized for.
* @param account The account to check.
* @return isAuthorized True if the account is authorized, false otherwise.
*/
function isAccountAuthorized(address account) external view returns (bool) {
VoucherStorage storage $ = _getVoucherStorage();
return account == owner() || $._authorizedAccounts[account];
}

/**
* Internal function to set authorization for an account.
* @param account The account to set authorization for.
* @param isAuthorized Whether to authorize or unauthorize the account.
*/
function _setAccountAuthorization(address account, bool isAuthorized) private {
require(account != owner(), "Voucher: owner is already authorized.");
VoucherStorage storage $ = _getVoucherStorage();
$._authorizedAccounts[account] = isAuthorized;
}
}
10 changes: 6 additions & 4 deletions contracts/mocks/VoucherV2Mock.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@
pragma solidity ^0.8.20;

import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import {IVoucher} from "../beacon/IVoucher.sol";

contract VoucherV2Mock is OwnableUpgradeable, IVoucher {
contract VoucherV2Mock is OwnableUpgradeable {
/// @custom:storage-location erc7201:iexec.voucher.storage.Voucher
struct VoucherStorage {
address _voucherHub;
uint256 _expiration;
uint256 _type;
mapping(address => bool) _authorizedAccounts;
uint256 _newStateVariable;
}

Expand All @@ -27,12 +29,12 @@ contract VoucherV2Mock is OwnableUpgradeable, IVoucher {
* Initialize new implementation contract.
* @param newStateVariable test variable.
*/
function initialize(uint256 newStateVariable) external reinitializer(2) {
function initializeV2(uint256 newStateVariable) external reinitializer(2) {
VoucherStorage storage $ = _getVoucherStorage();
$._newStateVariable = newStateVariable;
}

function getExpiration() external view override returns (uint256) {
function getExpiration() external view returns (uint256) {
VoucherStorage storage $ = _getVoucherStorage();
return $._expiration;
}
Expand Down
Loading

0 comments on commit aaa0327

Please sign in to comment.