Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 0 additions & 4 deletions .eslintignore

This file was deleted.

1 change: 0 additions & 1 deletion .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
"ecmaVersion": 12
},
"rules": {
// Auto-sort imports
"sort-imports": "off",
"import/order": "off",
"simple-import-sort/imports": "error",
Expand Down
20 changes: 0 additions & 20 deletions .husky/post-checkout

This file was deleted.

3 changes: 0 additions & 3 deletions .husky/pre-commit
100755 → 100644
Original file line number Diff line number Diff line change
@@ -1,4 +1 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"

yarn lint-staged
4 changes: 0 additions & 4 deletions .husky/pre-push

This file was deleted.

5 changes: 0 additions & 5 deletions .prettierignore

This file was deleted.

18 changes: 10 additions & 8 deletions contracts/QuestChain.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,16 @@

pragma solidity ^0.8.26;

import "@openzeppelin/contracts/access/AccessControl.sol";
import "@openzeppelin/contracts/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts/security/Pausable.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "@openzeppelin/contracts/utils/Context.sol";

import "./interfaces/IQuestChain.sol";
import "./interfaces/ILimiter.sol";
import {AccessControl} from "@openzeppelin/contracts/access/AccessControl.sol";
import {Initializable} from "@openzeppelin/contracts/proxy/utils/Initializable.sol";
import {Pausable} from "@openzeppelin/contracts/utils/Pausable.sol";
import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";

import {IQuestChainFactory} from "./interfaces/IQuestChainFactory.sol";
import {IQuestChainToken} from "./interfaces/IQuestChainToken.sol";
import {IQuestChain, QuestDetails, QuestStatus} from "./interfaces/IQuestChain.sol";
import {ILimiter} from "./interfaces/ILimiter.sol";
import {QuestChainCommons} from "./libraries/QuestChainCommons.sol";

/// @title QuestChain Contract
/// @notice Manages quests within a quest chain, including creation, editing, submission, and review of quests.
Expand Down
163 changes: 60 additions & 103 deletions contracts/QuestChainFactory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,114 +6,101 @@ pragma solidity ^0.8.26;
// ║═╬╗│ │├┤ └─┐ │ ║ ├─┤├─┤││││└─┐
// ╚═╝╚└─┘└─┘└─┘ ┴ ╚═╝┴ ┴┴ ┴┴┘└┘└─┘

import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "@openzeppelin/contracts/proxy/Clones.sol";

import "./interfaces/IQuestChain.sol";
import "./interfaces/IQuestChainFactory.sol";
import "./QuestChainToken.sol";

// author: @dan13ram

/* solhint-disable not-rely-on-time */

import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import {Clones} from "@openzeppelin/contracts/proxy/Clones.sol";

import {IQuestChain} from "./interfaces/IQuestChain.sol";
import {IQuestChainFactory} from "./interfaces/IQuestChainFactory.sol";
import {IQuestChainToken} from "./interfaces/IQuestChainToken.sol";
import {QuestChainToken} from "./QuestChainToken.sol";
import {QuestChainCommons} from "./libraries/QuestChainCommons.sol";

/// @title QuestChainFactory
/// @notice Factory contract for creating and managing quest chains.
/// @dev This contract deploys new quest chain contracts and manages access control.
contract QuestChainFactory is IQuestChainFactory, ReentrancyGuard {
using SafeERC20 for IERC20Token;

/********************************
STATE VARIABLES
*******************************/

// immutable contract address for quest chain ERC1155 tokens
uint256 private constant ONE_DAY = 86400;

/// @notice Immutable contract address for quest chain ERC1155 tokens.
// solhint-disable-next-line style-guide-casing,immutable-vars-naming
IQuestChainToken public immutable questChainToken;
// immutable template contract address for quest chain

/// @notice Immutable template contract address for quest chains.
// solhint-disable-next-line style-guide-casing,immutable-vars-naming
address public immutable questChainTemplate;

// counter for all quest chains
/// @notice Counter for all quest chains.
uint256 public questChainCount = 0;

// access control role
/// @notice Admin address with access control privileges.
address public admin;
// proposed admin address

/// @notice Proposed admin address awaiting approval.
address public proposedAdmin;
// timestamp of last admin proposal
uint256 public adminProposalTimestamp;

uint256 private constant ONE_DAY = 86400;
/// @notice Timestamp of the last admin proposal.
uint256 public adminProposalTimestamp;

/********************************
MAPPING STRUCTS EVENTS MODIFIER
*******************************/

// mapping from quest chain counter to deployed quest chains
/// @notice Mapping from quest chain counter to deployed quest chains.
mapping(uint256 => address) private _questChains;

/**
* @dev Access control modifier for functions callable by admin only
*/
/// @dev Access control modifier for functions callable by admin only.
modifier onlyAdmin() {
require(admin == msg.sender, "QCFactory: not admin");
if (admin != msg.sender) revert NotAdmin();
_;
}

/**
* @dev Modifier enforces non zero address
*/
/// @dev Modifier enforces non-zero address.
modifier nonZeroAddr(address _address) {
require(_address != address(0), "QCFactory: 0 address");
if (_address == address(0)) revert ZeroAddress();
_;
}

/**
* @dev Modifier enforces two addresses are different
*/
/// @dev Modifier enforces two addresses are different.
modifier mustChangeAddr(address _oldAddress, address _newAddress) {
require(_oldAddress != _newAddress, "QCFactory: no change");
if (_oldAddress == _newAddress) revert NoAddressChange();
_;
}

/**
* @dev Modifier enforces two integers are different
*/
/// @dev Modifier enforces two integers are different.
modifier mustChangeUint(uint256 _oldUint, uint256 _newUint) {
require(_oldUint != _newUint, "QCFactory: no change");
if (_oldUint == _newUint) revert NoUintChange();
_;
}

/**
* @dev Modifier enforces timestamps be atleast a day ago
*/
/// @dev Modifier enforces timestamps to be at least a day ago.
modifier onlyAfterDelay(uint256 _timestamp) {
require(block.timestamp >= _timestamp + ONE_DAY, "QCFactory: too soon");
if (block.timestamp < _timestamp + ONE_DAY) revert TooSoon();
_;
}

/// @notice Constructor to set up the QuestChainFactory.
/// @param _template The address of the quest chain template.
/// @param _admin The address of the initial admin.
constructor(
address _template,
address _admin
) nonZeroAddr(_template) nonZeroAddr(_admin) {
// deploy the Quest Chain Token and store it's address
questChainToken = new QuestChainToken();

// set the quest chain template contract
questChainTemplate = _template;

// set the admin address
admin = _admin;

// log constructor data
emit FactorySetup();
}

/*************************
ACCESS CONTROL FUNCTIONS
*************************/

/**
* @dev Proposes a new admin address
* @param _admin the address of the new admin
*/
/// @notice Proposes a new admin address.
/// @param _admin The address of the new admin.
function proposeAdminReplace(
address _admin
)
Expand All @@ -122,106 +109,76 @@ contract QuestChainFactory is IQuestChainFactory, ReentrancyGuard {
nonZeroAddr(_admin)
mustChangeAddr(proposedAdmin, _admin)
{
// set proposed admin address
proposedAdmin = _admin;
adminProposalTimestamp = block.timestamp;

// log proposedAdmin change data
emit AdminReplaceProposed(_admin);
}

/**
* @dev Executes the proposed admin replacement
*/
/// @notice Executes the proposed admin replacement.
function executeAdminReplace()
external
nonZeroAddr(proposedAdmin)
onlyAfterDelay(adminProposalTimestamp)
mustChangeAddr(proposedAdmin, admin)
{
require(proposedAdmin == msg.sender, "QCFactory: !proposedAdmin");

// replace admin
if (proposedAdmin != msg.sender) revert NotProposedAdmin();
admin = proposedAdmin;

delete proposedAdmin;
delete adminProposalTimestamp;

// log admin change data
emit AdminReplaced(admin);
}

/**
* @dev Deploys a new quest chain minimal proxy
* @param _info the initialization data struct for our new clone
* @param _salt an arbitrary source of entropy
*/
/// @notice Deploys a new quest chain minimal proxy.
/// @param _info The initialization data struct for the new clone.
/// @param _salt An arbitrary source of entropy.
/// @return The address of the created quest chain.
function create(
QuestChainCommons.QuestChainInfo calldata _info,
bytes32 _salt
) external returns (address) {
// deploy new quest chain minimal proxy
return _create(_info, _salt);
}

/**
* @dev Returns the address of a deployed quest chain proxy
* @param _index the quest chain contract index
*/
/// @notice Returns the address of a deployed quest chain proxy.
/// @param _index The quest chain contract index.
/// @return The address of the quest chain at the given index.
function getQuestChainAddress(
uint256 _index
) external view returns (address) {
return _questChains[_index];
}

/**
* @dev Internal function deploys and initializes a new quest chain minimal proxy
* @param _info the initialization data struct for our new clone
* @param _salt an arbitrary source of entropy
*/
/// @dev Internal function deploys and initializes a new quest chain minimal proxy.
/// @param _info The initialization data struct for the new clone.
/// @param _salt An arbitrary source of entropy.
/// @return The address of the created quest chain.
function _create(
QuestChainCommons.QuestChainInfo calldata _info,
bytes32 _salt
) internal returns (address) {
// deploy a new quest chain clone
address questChainAddress = _newClone(_salt);

// initialize the new quest chain clone
_setupQuestChain(questChainAddress, _info);

return questChainAddress;
}

/**
* @dev Internal function deploys a new quest chain minimal proxy
* @param _salt an arbitrary source of entropy
*/
/// @dev Internal function deploys a new quest chain minimal proxy.
/// @param _salt An arbitrary source of entropy.
/// @return The address of the created clone.
function _newClone(bytes32 _salt) internal returns (address) {
return Clones.cloneDeterministic(questChainTemplate, _salt);
}

/**
* @dev Internal function initializes a new quest chain minimal proxy
* @param _questChainAddress the new minimal proxy's address
* @param _info the initialization parameters
*/
/// @dev Internal function initializes a new quest chain minimal proxy.
/// @param _questChainAddress The new minimal proxy's address.
/// @param _info The initialization parameters.
function _setupQuestChain(
address _questChainAddress,
QuestChainCommons.QuestChainInfo calldata _info
) internal {
// assign the quest chain token owner
questChainToken.setTokenOwner(questChainCount, _questChainAddress);

// initialize the quest chain proxy
IQuestChain(_questChainAddress).init(_info);

// store the new proxy's address in the quest chain registry
_questChains[questChainCount] = _questChainAddress;

// log quest chain creation data
emit QuestChainCreated(questChainCount, _questChainAddress);

// increment quest chain counter
questChainCount++;
}
}
Loading