keep3r-v2
Version:
The Keep3r Network is a decentralized network for projects that need external devops, and for external teams to find keeper jobs
47 lines • 258 kB
JSON
{
"language": "Solidity",
"sources": {
"solidity/contracts/Keep3r.sol": {
"content": "// SPDX-License-Identifier: MIT\n\n/*\n\nCoded for The Keep3r Network with ♥ by\n\n██████╗░███████╗███████╗██╗ ░██╗░░░░░░░██╗░█████╗░███╗░░██╗██████╗░███████╗██████╗░██╗░░░░░░█████╗░███╗░░██╗██████╗░\n██╔══██╗██╔════╝██╔════╝██║ ░██║░░██╗░░██║██╔══██╗████╗░██║██╔══██╗██╔════╝██╔══██╗██║░░░░░██╔══██╗████╗░██║██╔══██╗\n██║░░██║█████╗░░█████╗░░██║ ░╚██╗████╗██╔╝██║░░██║██╔██╗██║██║░░██║█████╗░░██████╔╝██║░░░░░███████║██╔██╗██║██║░░██║\n██║░░██║██╔══╝░░██╔══╝░░██║ ░░████╔═████║░██║░░██║██║╚████║██║░░██║██╔══╝░░██╔══██╗██║░░░░░██╔══██║██║╚████║██║░░██║\n██████╔╝███████╗██║░░░░░██║ ░░╚██╔╝░╚██╔╝░╚█████╔╝██║░╚███║██████╔╝███████╗██║░░██║███████╗██║░░██║██║░╚███║██████╔╝\n╚═════╝░╚══════╝╚═╝░░░░░╚═╝ ░░░╚═╝░░░╚═╝░░░╚════╝░╚═╝░░╚══╝╚═════╝░╚══════╝╚═╝░░╚═╝╚══════╝╚═╝░░╚═╝╚═╝░░╚══╝╚═════╝░\n\nhttps://defi.sucks\n\n*/\n\npragma solidity >=0.8.4 <0.9.0;\n\nimport './peripherals/jobs/Keep3rJobs.sol';\nimport './peripherals/keepers/Keep3rKeepers.sol';\nimport './peripherals/Keep3rAccountance.sol';\nimport './peripherals/Keep3rRoles.sol';\nimport './peripherals/Keep3rParameters.sol';\nimport './peripherals/DustCollector.sol';\n\ncontract Keep3r is DustCollector, Keep3rJobs, Keep3rKeepers {\n constructor(\n address _governance,\n address _keep3rHelper,\n address _keep3rV1,\n address _keep3rV1Proxy,\n address _kp3rWethPool\n ) Keep3rParameters(_keep3rHelper, _keep3rV1, _keep3rV1Proxy, _kp3rWethPool) Keep3rRoles(_governance) DustCollector() {}\n}\n"
},
"solidity/contracts/peripherals/jobs/Keep3rJobs.sol": {
"content": "// SPDX-License-Identifier: MIT\npragma solidity >=0.8.4 <0.9.0;\n\nimport './Keep3rJobDisputable.sol';\nimport './Keep3rJobWorkable.sol';\nimport './Keep3rJobManager.sol';\n\nabstract contract Keep3rJobs is Keep3rJobDisputable, Keep3rJobManager, Keep3rJobWorkable {}\n"
},
"solidity/contracts/peripherals/keepers/Keep3rKeepers.sol": {
"content": "// SPDX-License-Identifier: MIT\npragma solidity >=0.8.4 <0.9.0;\n\nimport './Keep3rKeeperDisputable.sol';\n\nabstract contract Keep3rKeepers is Keep3rKeeperDisputable {}\n"
},
"solidity/contracts/peripherals/Keep3rAccountance.sol": {
"content": "// SPDX-License-Identifier: MIT\npragma solidity >=0.8.4 <0.9.0;\n\nimport '@openzeppelin/contracts/utils/structs/EnumerableSet.sol';\nimport '../../interfaces/peripherals/IKeep3rAccountance.sol';\n\nabstract contract Keep3rAccountance is IKeep3rAccountance {\n using EnumerableSet for EnumerableSet.AddressSet;\n\n /// @notice List of all enabled keepers\n EnumerableSet.AddressSet internal _keepers;\n\n /// @inheritdoc IKeep3rAccountance\n mapping(address => uint256) public override workCompleted;\n\n /// @inheritdoc IKeep3rAccountance\n mapping(address => uint256) public override firstSeen;\n\n /// @inheritdoc IKeep3rAccountance\n mapping(address => bool) public override disputes;\n\n /// @inheritdoc IKeep3rAccountance\n /// @notice Mapping (job => bonding => amount)\n mapping(address => mapping(address => uint256)) public override bonds;\n\n /// @inheritdoc IKeep3rAccountance\n mapping(address => mapping(address => uint256)) public override jobTokenCredits;\n\n /// @notice The current liquidity credits available for a job\n mapping(address => uint256) internal _jobLiquidityCredits;\n\n /// @notice Map the address of a job to its correspondent periodCredits\n mapping(address => uint256) internal _jobPeriodCredits;\n\n /// @notice Enumerable array of Job Tokens for Credits\n mapping(address => EnumerableSet.AddressSet) internal _jobTokens;\n\n /// @notice List of liquidities that a job has (job => liquidities)\n mapping(address => EnumerableSet.AddressSet) internal _jobLiquidities;\n\n /// @notice Liquidity pool to observe\n mapping(address => address) internal _liquidityPool;\n\n /// @notice Tracks if a pool has KP3R as token0\n mapping(address => bool) internal _isKP3RToken0;\n\n /// @inheritdoc IKeep3rAccountance\n mapping(address => mapping(address => uint256)) public override pendingBonds;\n\n /// @inheritdoc IKeep3rAccountance\n mapping(address => mapping(address => uint256)) public override canActivateAfter;\n\n /// @inheritdoc IKeep3rAccountance\n mapping(address => mapping(address => uint256)) public override canWithdrawAfter;\n\n /// @inheritdoc IKeep3rAccountance\n mapping(address => mapping(address => uint256)) public override pendingUnbonds;\n\n /// @inheritdoc IKeep3rAccountance\n mapping(address => bool) public override hasBonded;\n\n /// @notice List of all enabled jobs\n EnumerableSet.AddressSet internal _jobs;\n\n /// @inheritdoc IKeep3rAccountance\n function jobs() external view override returns (address[] memory _list) {\n _list = _jobs.values();\n }\n\n /// @inheritdoc IKeep3rAccountance\n function keepers() external view override returns (address[] memory _list) {\n _list = _keepers.values();\n }\n}\n"
},
"solidity/contracts/peripherals/Keep3rRoles.sol": {
"content": "// SPDX-License-Identifier: MIT\npragma solidity >=0.8.4 <0.9.0;\n\nimport '../../interfaces/peripherals/IKeep3rRoles.sol';\nimport '@openzeppelin/contracts/utils/structs/EnumerableSet.sol';\nimport './Governable.sol';\n\ncontract Keep3rRoles is IKeep3rRoles, Governable {\n /// @inheritdoc IKeep3rRoles\n mapping(address => bool) public override slashers;\n\n /// @inheritdoc IKeep3rRoles\n mapping(address => bool) public override disputers;\n\n constructor(address _governance) Governable(_governance) {}\n\n /// @inheritdoc IKeep3rRoles\n function addSlasher(address _slasher) external override onlyGovernance {\n if (slashers[_slasher]) revert SlasherExistent();\n slashers[_slasher] = true;\n emit SlasherAdded(_slasher);\n }\n\n /// @inheritdoc IKeep3rRoles\n function removeSlasher(address _slasher) external override onlyGovernance {\n if (!slashers[_slasher]) revert SlasherUnexistent();\n delete slashers[_slasher];\n emit SlasherRemoved(_slasher);\n }\n\n /// @inheritdoc IKeep3rRoles\n function addDisputer(address _disputer) external override onlyGovernance {\n if (disputers[_disputer]) revert DisputerExistent();\n disputers[_disputer] = true;\n emit DisputerAdded(_disputer);\n }\n\n /// @inheritdoc IKeep3rRoles\n function removeDisputer(address _disputer) external override onlyGovernance {\n if (!disputers[_disputer]) revert DisputerUnexistent();\n delete disputers[_disputer];\n emit DisputerRemoved(_disputer);\n }\n\n /// @notice Functions with this modifier can only be called by either a slasher or governance\n modifier onlySlasher {\n if (!slashers[msg.sender]) revert OnlySlasher();\n _;\n }\n\n /// @notice Functions with this modifier can only be called by either a disputer or governance\n modifier onlyDisputer {\n if (!disputers[msg.sender]) revert OnlyDisputer();\n _;\n }\n}\n"
},
"solidity/contracts/peripherals/Keep3rParameters.sol": {
"content": "// SPDX-License-Identifier: MIT\npragma solidity >=0.8.4 <0.9.0;\n\nimport '../../interfaces/IKeep3rHelper.sol';\nimport '../../interfaces/peripherals/IKeep3rParameters.sol';\nimport './Keep3rAccountance.sol';\nimport './Keep3rRoles.sol';\n\nabstract contract Keep3rParameters is IKeep3rParameters, Keep3rAccountance, Keep3rRoles {\n /// @inheritdoc IKeep3rParameters\n address public override keep3rV1;\n\n /// @inheritdoc IKeep3rParameters\n address public override keep3rV1Proxy;\n\n /// @inheritdoc IKeep3rParameters\n address public override keep3rHelper;\n\n /// @inheritdoc IKeep3rParameters\n address public override kp3rWethPool;\n\n /// @inheritdoc IKeep3rParameters\n uint256 public override bondTime = 3 days;\n\n /// @inheritdoc IKeep3rParameters\n uint256 public override unbondTime = 14 days;\n\n /// @inheritdoc IKeep3rParameters\n uint256 public override liquidityMinimum = 3 ether;\n\n /// @inheritdoc IKeep3rParameters\n uint256 public override rewardPeriodTime = 5 days;\n\n /// @inheritdoc IKeep3rParameters\n uint256 public override inflationPeriod = 34 days;\n\n /// @inheritdoc IKeep3rParameters\n uint256 public override fee = 30;\n\n /// @inheritdoc IKeep3rParameters\n uint256 public constant override BASE = 10000;\n\n /// @inheritdoc IKeep3rParameters\n uint256 public constant override MIN_REWARD_PERIOD_TIME = 1 days;\n\n constructor(\n address _keep3rHelper,\n address _keep3rV1,\n address _keep3rV1Proxy,\n address _kp3rWethPool\n ) {\n keep3rHelper = _keep3rHelper;\n keep3rV1 = _keep3rV1;\n keep3rV1Proxy = _keep3rV1Proxy;\n kp3rWethPool = _kp3rWethPool;\n _liquidityPool[kp3rWethPool] = kp3rWethPool;\n _isKP3RToken0[_kp3rWethPool] = IKeep3rHelper(keep3rHelper).isKP3RToken0(kp3rWethPool);\n }\n\n /// @inheritdoc IKeep3rParameters\n function setKeep3rHelper(address _keep3rHelper) external override onlyGovernance {\n if (_keep3rHelper == address(0)) revert ZeroAddress();\n keep3rHelper = _keep3rHelper;\n emit Keep3rHelperChange(_keep3rHelper);\n }\n\n /// @inheritdoc IKeep3rParameters\n function setKeep3rV1(address _keep3rV1) external override onlyGovernance {\n if (_keep3rV1 == address(0)) revert ZeroAddress();\n keep3rV1 = _keep3rV1;\n emit Keep3rV1Change(_keep3rV1);\n }\n\n /// @inheritdoc IKeep3rParameters\n function setKeep3rV1Proxy(address _keep3rV1Proxy) external override onlyGovernance {\n if (_keep3rV1Proxy == address(0)) revert ZeroAddress();\n keep3rV1Proxy = _keep3rV1Proxy;\n emit Keep3rV1ProxyChange(_keep3rV1Proxy);\n }\n\n /// @inheritdoc IKeep3rParameters\n function setKp3rWethPool(address _kp3rWethPool) external override onlyGovernance {\n if (_kp3rWethPool == address(0)) revert ZeroAddress();\n kp3rWethPool = _kp3rWethPool;\n _liquidityPool[kp3rWethPool] = kp3rWethPool;\n _isKP3RToken0[_kp3rWethPool] = IKeep3rHelper(keep3rHelper).isKP3RToken0(_kp3rWethPool);\n emit Kp3rWethPoolChange(_kp3rWethPool);\n }\n\n /// @inheritdoc IKeep3rParameters\n function setBondTime(uint256 _bondTime) external override onlyGovernance {\n bondTime = _bondTime;\n emit BondTimeChange(_bondTime);\n }\n\n /// @inheritdoc IKeep3rParameters\n function setUnbondTime(uint256 _unbondTime) external override onlyGovernance {\n unbondTime = _unbondTime;\n emit UnbondTimeChange(_unbondTime);\n }\n\n /// @inheritdoc IKeep3rParameters\n function setLiquidityMinimum(uint256 _liquidityMinimum) external override onlyGovernance {\n liquidityMinimum = _liquidityMinimum;\n emit LiquidityMinimumChange(_liquidityMinimum);\n }\n\n /// @inheritdoc IKeep3rParameters\n function setRewardPeriodTime(uint256 _rewardPeriodTime) external override onlyGovernance {\n if (_rewardPeriodTime < MIN_REWARD_PERIOD_TIME) revert MinRewardPeriod();\n rewardPeriodTime = _rewardPeriodTime;\n emit RewardPeriodTimeChange(_rewardPeriodTime);\n }\n\n /// @inheritdoc IKeep3rParameters\n function setInflationPeriod(uint256 _inflationPeriod) external override onlyGovernance {\n inflationPeriod = _inflationPeriod;\n emit InflationPeriodChange(_inflationPeriod);\n }\n\n /// @inheritdoc IKeep3rParameters\n function setFee(uint256 _fee) external override onlyGovernance {\n fee = _fee;\n emit FeeChange(_fee);\n }\n}\n"
},
"solidity/contracts/peripherals/DustCollector.sol": {
"content": "// SPDX-License-Identifier: MIT\n\npragma solidity >=0.8.4 <0.9.0;\n\nimport '@openzeppelin/contracts/token/ERC20/IERC20.sol';\nimport '@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol';\nimport '../../contracts/peripherals/Governable.sol';\nimport '../../interfaces/peripherals/IDustCollector.sol';\n\nabstract contract DustCollector is IDustCollector, Governable {\n using SafeERC20 for IERC20;\n\n address public constant ETH_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;\n\n function sendDust(\n address _token,\n uint256 _amount,\n address _to\n ) external override onlyGovernance {\n if (_to == address(0)) revert ZeroAddress();\n if (_token == ETH_ADDRESS) {\n payable(_to).transfer(_amount);\n } else {\n IERC20(_token).safeTransfer(_to, _amount);\n }\n emit DustSent(_token, _amount, _to);\n }\n}\n"
},
"solidity/contracts/peripherals/jobs/Keep3rJobDisputable.sol": {
"content": "// SPDX-License-Identifier: MIT\npragma solidity >=0.8.4 <0.9.0;\n\nimport './Keep3rJobFundableCredits.sol';\nimport './Keep3rJobFundableLiquidity.sol';\nimport '../Keep3rDisputable.sol';\n\nabstract contract Keep3rJobDisputable is IKeep3rJobDisputable, Keep3rDisputable, Keep3rJobFundableCredits, Keep3rJobFundableLiquidity {\n using EnumerableSet for EnumerableSet.AddressSet;\n using SafeERC20 for IERC20;\n\n /// @inheritdoc IKeep3rJobDisputable\n function slashTokenFromJob(\n address _job,\n address _token,\n uint256 _amount\n ) external override onlySlasher {\n if (!disputes[_job]) revert NotDisputed();\n if (!_jobTokens[_job].contains(_token)) revert JobTokenUnexistent();\n if (jobTokenCredits[_job][_token] < _amount) revert JobTokenInsufficient();\n\n try IERC20(_token).transfer(governance, _amount) {} catch {}\n jobTokenCredits[_job][_token] -= _amount;\n if (jobTokenCredits[_job][_token] == 0) {\n _jobTokens[_job].remove(_token);\n }\n\n // emit event\n emit JobSlashToken(_job, _token, msg.sender, _amount);\n }\n\n /// @inheritdoc IKeep3rJobDisputable\n function slashLiquidityFromJob(\n address _job,\n address _liquidity,\n uint256 _amount\n ) external override onlySlasher {\n if (!disputes[_job]) revert NotDisputed();\n\n _unbondLiquidityFromJob(_job, _liquidity, _amount);\n try IERC20(_liquidity).transfer(governance, _amount) {} catch {}\n emit JobSlashLiquidity(_job, _liquidity, msg.sender, _amount);\n }\n}\n"
},
"solidity/contracts/peripherals/jobs/Keep3rJobWorkable.sol": {
"content": "// SPDX-License-Identifier: MIT\npragma solidity >=0.8.4 <0.9.0;\n\nimport './Keep3rJobMigration.sol';\nimport '../../../interfaces/IKeep3rHelper.sol';\nimport '../../../interfaces/peripherals/IKeep3rJobs.sol';\n\nimport '@openzeppelin/contracts/token/ERC20/IERC20.sol';\nimport '@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol';\n\nabstract contract Keep3rJobWorkable is IKeep3rJobWorkable, Keep3rJobMigration {\n using EnumerableSet for EnumerableSet.AddressSet;\n using SafeERC20 for IERC20;\n\n uint256 internal _initialGas;\n\n /// @inheritdoc IKeep3rJobWorkable\n function isKeeper(address _keeper) external override returns (bool _isKeeper) {\n _initialGas = gasleft();\n if (_keepers.contains(_keeper)) {\n emit KeeperValidation(gasleft());\n return true;\n }\n }\n\n /// @inheritdoc IKeep3rJobWorkable\n function isBondedKeeper(\n address _keeper,\n address _bond,\n uint256 _minBond,\n uint256 _earned,\n uint256 _age\n ) public override returns (bool _isBondedKeeper) {\n _initialGas = gasleft();\n if (\n _keepers.contains(_keeper) &&\n bonds[_keeper][_bond] >= _minBond &&\n workCompleted[_keeper] >= _earned &&\n block.timestamp - firstSeen[_keeper] >= _age\n ) {\n emit KeeperValidation(gasleft());\n return true;\n }\n }\n\n /// @inheritdoc IKeep3rJobWorkable\n function worked(address _keeper) external override {\n address _job = msg.sender;\n if (disputes[_job]) revert JobDisputed();\n if (!_jobs.contains(_job)) revert JobUnapproved();\n\n if (_updateJobCreditsIfNeeded(_job)) {\n emit LiquidityCreditsReward(_job, rewardedAt[_job], _jobLiquidityCredits[_job], _jobPeriodCredits[_job]);\n }\n\n uint256 _gasRecord = gasleft();\n uint256 _boost = IKeep3rHelper(keep3rHelper).getRewardBoostFor(bonds[_keeper][keep3rV1]);\n\n uint256 _payment = (_quoteLiquidity(_initialGas - _gasRecord, kp3rWethPool) * _boost) / BASE;\n\n if (_payment > _jobLiquidityCredits[_job]) {\n _rewardJobCredits(_job);\n emit LiquidityCreditsReward(_job, rewardedAt[_job], _jobLiquidityCredits[_job], _jobPeriodCredits[_job]);\n }\n\n uint256 _gasUsed = _initialGas - gasleft();\n _payment = (_gasUsed * _payment) / (_initialGas - _gasRecord);\n\n _bondedPayment(_job, _keeper, _payment);\n emit KeeperWork(keep3rV1, _job, _keeper, _payment, gasleft());\n }\n\n /// @inheritdoc IKeep3rJobWorkable\n function bondedPayment(address _keeper, uint256 _payment) public override {\n address _job = msg.sender;\n\n if (disputes[_job]) revert JobDisputed();\n if (!_jobs.contains(_job)) revert JobUnapproved();\n\n if (_updateJobCreditsIfNeeded(_job)) {\n emit LiquidityCreditsReward(_job, rewardedAt[_job], _jobLiquidityCredits[_job], _jobPeriodCredits[_job]);\n }\n\n if (_payment > _jobLiquidityCredits[_job]) {\n _rewardJobCredits(_job);\n emit LiquidityCreditsReward(_job, rewardedAt[_job], _jobLiquidityCredits[_job], _jobPeriodCredits[_job]);\n }\n\n _bondedPayment(_job, _keeper, _payment);\n emit KeeperWork(keep3rV1, _job, _keeper, _payment, gasleft());\n }\n\n function _bondedPayment(\n address _job,\n address _keeper,\n uint256 _payment\n ) internal {\n if (_payment > _jobLiquidityCredits[_job]) revert InsufficientFunds();\n\n workedAt[_job] = block.timestamp;\n _jobLiquidityCredits[_job] -= _payment;\n bonds[_keeper][keep3rV1] += _payment;\n workCompleted[_keeper] += _payment;\n }\n\n /// @inheritdoc IKeep3rJobWorkable\n function directTokenPayment(\n address _token,\n address _keeper,\n uint256 _amount\n ) external override {\n address _job = msg.sender;\n\n if (disputes[_job]) revert JobDisputed();\n if (disputes[_keeper]) revert Disputed();\n if (!_jobs.contains(_job)) revert JobUnapproved();\n if (jobTokenCredits[_job][_token] < _amount) revert InsufficientFunds();\n jobTokenCredits[_job][_token] -= _amount;\n IERC20(_token).safeTransfer(_keeper, _amount);\n emit KeeperWork(_token, _job, _keeper, _amount, gasleft());\n }\n}\n"
},
"solidity/contracts/peripherals/jobs/Keep3rJobManager.sol": {
"content": "// SPDX-License-Identifier: MIT\npragma solidity >=0.8.4 <0.9.0;\n\nimport './Keep3rJobOwnership.sol';\nimport '../Keep3rRoles.sol';\nimport '../Keep3rParameters.sol';\nimport '../../../interfaces/peripherals/IKeep3rJobs.sol';\n\nabstract contract Keep3rJobManager is IKeep3rJobManager, Keep3rJobOwnership, Keep3rRoles, Keep3rParameters {\n using EnumerableSet for EnumerableSet.AddressSet;\n\n /// @inheritdoc IKeep3rJobManager\n function addJob(address _job) external override {\n if (_jobs.contains(_job)) revert JobAlreadyAdded();\n if (hasBonded[_job]) revert AlreadyAKeeper();\n _jobs.add(_job);\n jobOwner[_job] = msg.sender;\n emit JobAddition(msg.sender, _job);\n }\n}\n"
},
"solidity/contracts/peripherals/jobs/Keep3rJobFundableCredits.sol": {
"content": "// SPDX-License-Identifier: MIT\npragma solidity >=0.8.4 <0.9.0;\n\nimport './Keep3rJobOwnership.sol';\nimport '../Keep3rAccountance.sol';\nimport '../Keep3rParameters.sol';\nimport '../../../interfaces/peripherals/IKeep3rJobs.sol';\n\nimport '@openzeppelin/contracts/security/ReentrancyGuard.sol';\nimport '@openzeppelin/contracts/token/ERC20/IERC20.sol';\nimport '@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol';\nimport '@openzeppelin/contracts/utils/math/Math.sol';\n\nabstract contract Keep3rJobFundableCredits is IKeep3rJobFundableCredits, ReentrancyGuard, Keep3rJobOwnership, Keep3rParameters {\n using EnumerableSet for EnumerableSet.AddressSet;\n using SafeERC20 for IERC20;\n\n /// @notice Cooldown between withdrawals\n uint256 internal constant _WITHDRAW_TOKENS_COOLDOWN = 1 minutes;\n\n /// @inheritdoc IKeep3rJobFundableCredits\n mapping(address => mapping(address => uint256)) public override jobTokenCreditsAddedAt;\n\n /// @inheritdoc IKeep3rJobFundableCredits\n function addTokenCreditsToJob(\n address _job,\n address _token,\n uint256 _amount\n ) external override nonReentrant {\n if (!_jobs.contains(_job)) revert JobUnavailable();\n // KP3R shouldn't be used for direct token payments\n if (_token == keep3rV1) revert TokenUnallowed();\n uint256 _before = IERC20(_token).balanceOf(address(this));\n IERC20(_token).safeTransferFrom(msg.sender, address(this), _amount);\n uint256 _received = IERC20(_token).balanceOf(address(this)) - _before;\n uint256 _tokenFee = (_received * fee) / BASE;\n jobTokenCredits[_job][_token] += _received - _tokenFee;\n jobTokenCreditsAddedAt[_job][_token] = block.timestamp;\n IERC20(_token).safeTransfer(governance, _tokenFee);\n _jobTokens[_job].add(_token);\n\n emit TokenCreditAddition(_job, _token, msg.sender, _received);\n }\n\n /// @inheritdoc IKeep3rJobFundableCredits\n function withdrawTokenCreditsFromJob(\n address _job,\n address _token,\n uint256 _amount,\n address _receiver\n ) external override nonReentrant onlyJobOwner(_job) {\n if (block.timestamp <= jobTokenCreditsAddedAt[_job][_token] + _WITHDRAW_TOKENS_COOLDOWN) revert JobTokenCreditsLocked();\n if (jobTokenCredits[_job][_token] < _amount) revert InsufficientJobTokenCredits();\n if (disputes[_job]) revert JobDisputed();\n\n jobTokenCredits[_job][_token] -= _amount;\n IERC20(_token).safeTransfer(_receiver, _amount);\n\n if (jobTokenCredits[_job][_token] == 0) {\n _jobTokens[_job].remove(_token);\n }\n\n emit TokenCreditWithdrawal(_job, _token, _receiver, _amount);\n }\n}\n"
},
"solidity/contracts/peripherals/jobs/Keep3rJobFundableLiquidity.sol": {
"content": "// SPDX-License-Identifier: MIT\npragma solidity >=0.8.4 <0.9.0;\n\nimport './Keep3rJobOwnership.sol';\nimport '../Keep3rAccountance.sol';\nimport '../Keep3rParameters.sol';\nimport '../../../interfaces/IPairManager.sol';\nimport '../../../interfaces/peripherals/IKeep3rJobs.sol';\n\nimport '../../libraries/FullMath.sol';\n\nimport '@openzeppelin/contracts/security/ReentrancyGuard.sol';\nimport '@openzeppelin/contracts/token/ERC20/IERC20.sol';\nimport '@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol';\nimport '@openzeppelin/contracts/utils/math/Math.sol';\n\nabstract contract Keep3rJobFundableLiquidity is IKeep3rJobFundableLiquidity, ReentrancyGuard, Keep3rJobOwnership, Keep3rParameters {\n using EnumerableSet for EnumerableSet.AddressSet;\n using SafeERC20 for IERC20;\n\n /// @notice List of liquidities that are accepted in the system\n EnumerableSet.AddressSet internal _approvedLiquidities;\n\n /// @inheritdoc IKeep3rJobFundableLiquidity\n mapping(address => mapping(address => uint256)) public override liquidityAmount;\n\n /// @inheritdoc IKeep3rJobFundableLiquidity\n mapping(address => uint256) public override rewardedAt;\n\n /// @inheritdoc IKeep3rJobFundableLiquidity\n mapping(address => uint256) public override workedAt;\n\n /// @notice Tracks an address and returns its TickCache\n mapping(address => TickCache) internal _tick;\n\n // Views\n\n /// @inheritdoc IKeep3rJobFundableLiquidity\n function approvedLiquidities() external view override returns (address[] memory _list) {\n _list = _approvedLiquidities.values();\n }\n\n /// @inheritdoc IKeep3rJobFundableLiquidity\n function jobPeriodCredits(address _job) public view override returns (uint256 _periodCredits) {\n for (uint256 i; i < _jobLiquidities[_job].length(); i++) {\n address _liquidity = _jobLiquidities[_job].at(i);\n if (_approvedLiquidities.contains(_liquidity)) {\n TickCache memory _tickCache = observeLiquidity(_liquidity);\n if (_tickCache.period != 0) {\n int56 _tickDifference = _isKP3RToken0[_liquidity] ? _tickCache.difference : -_tickCache.difference;\n _periodCredits += _getReward(\n IKeep3rHelper(keep3rHelper).getKP3RsAtTick(liquidityAmount[_job][_liquidity], _tickDifference, rewardPeriodTime)\n );\n }\n }\n }\n }\n\n /// @inheritdoc IKeep3rJobFundableLiquidity\n function jobLiquidityCredits(address _job) public view override returns (uint256 _liquidityCredits) {\n uint256 _periodCredits = jobPeriodCredits(_job);\n\n // A job can have liquidityCredits without periodCredits (forced by Governance)\n if (rewardedAt[_job] > _period(block.timestamp - rewardPeriodTime)) {\n // Will calculate job credits only if it was rewarded later than last period\n if ((block.timestamp - rewardedAt[_job]) >= rewardPeriodTime) {\n // Will return a full period if job was rewarded more than a period ago\n _liquidityCredits = _periodCredits;\n } else {\n // Will update minted job credits (not forced) to new twaps if credits are outdated\n _liquidityCredits = _periodCredits > 0\n ? (_jobLiquidityCredits[_job] * _periodCredits) / _jobPeriodCredits[_job]\n : _jobLiquidityCredits[_job];\n }\n } else {\n // Will return a full period if job credits are expired\n _liquidityCredits = _periodCredits;\n }\n }\n\n /// @inheritdoc IKeep3rJobFundableLiquidity\n function totalJobCredits(address _job) external view override returns (uint256 _credits) {\n uint256 _periodCredits = jobPeriodCredits(_job);\n uint256 _cooldown;\n\n if ((rewardedAt[_job] > _period(block.timestamp - rewardPeriodTime))) {\n // Will calculate cooldown if it outdated\n if ((block.timestamp - rewardedAt[_job]) >= rewardPeriodTime) {\n // Will calculate cooldown from last reward reference in this period\n _cooldown = block.timestamp - (rewardedAt[_job] + rewardPeriodTime);\n } else {\n // Will calculate cooldown from last reward timestamp\n _cooldown = block.timestamp - rewardedAt[_job];\n }\n } else {\n // Will calculate cooldown from period start if expired\n _cooldown = block.timestamp - _period(block.timestamp);\n }\n _credits = jobLiquidityCredits(_job) + _phase(_cooldown, _periodCredits);\n }\n\n /// @inheritdoc IKeep3rJobFundableLiquidity\n function quoteLiquidity(address _liquidity, uint256 _amount) external view override returns (uint256 _periodCredits) {\n if (_approvedLiquidities.contains(_liquidity)) {\n TickCache memory _tickCache = observeLiquidity(_liquidity);\n if (_tickCache.period != 0) {\n int56 _tickDifference = _isKP3RToken0[_liquidity] ? _tickCache.difference : -_tickCache.difference;\n return _getReward(IKeep3rHelper(keep3rHelper).getKP3RsAtTick(_amount, _tickDifference, rewardPeriodTime));\n }\n }\n }\n\n /// @inheritdoc IKeep3rJobFundableLiquidity\n function observeLiquidity(address _liquidity) public view override returns (TickCache memory _tickCache) {\n if (_tick[_liquidity].period == _period(block.timestamp)) {\n // Will return cached twaps if liquidity is updated\n _tickCache = _tick[_liquidity];\n } else {\n bool success;\n uint256 lastPeriod = _period(block.timestamp - rewardPeriodTime);\n\n if (_tick[_liquidity].period == lastPeriod) {\n // Will only ask for current period accumulator if liquidity is outdated\n uint32[] memory _secondsAgo = new uint32[](1);\n int56 previousTick = _tick[_liquidity].current;\n\n _secondsAgo[0] = uint32(block.timestamp - _period(block.timestamp));\n\n (_tickCache.current, , success) = IKeep3rHelper(keep3rHelper).observe(_liquidityPool[_liquidity], _secondsAgo);\n\n _tickCache.difference = _tickCache.current - previousTick;\n } else if (_tick[_liquidity].period < lastPeriod) {\n // Will ask for 2 accumulators if liquidity is expired\n uint32[] memory _secondsAgo = new uint32[](2);\n\n _secondsAgo[0] = uint32(block.timestamp - _period(block.timestamp));\n _secondsAgo[1] = uint32(block.timestamp - _period(block.timestamp) + rewardPeriodTime);\n\n int56 _tickCumulative2;\n (_tickCache.current, _tickCumulative2, success) = IKeep3rHelper(keep3rHelper).observe(_liquidityPool[_liquidity], _secondsAgo);\n\n _tickCache.difference = _tickCache.current - _tickCumulative2;\n }\n if (success) {\n _tickCache.period = _period(block.timestamp);\n } else {\n _tickCache.period = 0;\n }\n }\n }\n\n // Methods\n\n /// @inheritdoc IKeep3rJobFundableLiquidity\n function forceLiquidityCreditsToJob(address _job, uint256 _amount) external override onlyGovernance {\n if (!_jobs.contains(_job)) revert JobUnavailable();\n _settleJobAccountance(_job);\n _jobLiquidityCredits[_job] += _amount;\n emit LiquidityCreditsForced(_job, rewardedAt[_job], _jobLiquidityCredits[_job]);\n }\n\n /// @inheritdoc IKeep3rJobFundableLiquidity\n function approveLiquidity(address _liquidity) external override onlyGovernance {\n if (!_approvedLiquidities.add(_liquidity)) revert LiquidityPairApproved();\n _liquidityPool[_liquidity] = IPairManager(_liquidity).pool();\n _isKP3RToken0[_liquidity] = IKeep3rHelper(keep3rHelper).isKP3RToken0(_liquidityPool[_liquidity]);\n _tick[_liquidity] = observeLiquidity(_liquidity);\n emit LiquidityApproval(_liquidity);\n }\n\n /// @inheritdoc IKeep3rJobFundableLiquidity\n function revokeLiquidity(address _liquidity) external override onlyGovernance {\n if (!_approvedLiquidities.remove(_liquidity)) revert LiquidityPairUnexistent();\n emit LiquidityRevocation(_liquidity);\n }\n\n /// @inheritdoc IKeep3rJobFundableLiquidity\n function addLiquidityToJob(\n address _job,\n address _liquidity,\n uint256 _amount\n ) external override nonReentrant {\n if (!_approvedLiquidities.contains(_liquidity)) revert LiquidityPairUnapproved();\n if (!_jobs.contains(_job)) revert JobUnavailable();\n\n _jobLiquidities[_job].add(_liquidity);\n\n _settleJobAccountance(_job);\n\n if (_quoteLiquidity(liquidityAmount[_job][_liquidity] + _amount, _liquidity) < liquidityMinimum) revert JobLiquidityLessThanMin();\n\n emit LiquidityCreditsReward(_job, rewardedAt[_job], _jobLiquidityCredits[_job], _jobPeriodCredits[_job]);\n\n IERC20(_liquidity).safeTransferFrom(msg.sender, address(this), _amount);\n liquidityAmount[_job][_liquidity] += _amount;\n _jobPeriodCredits[_job] += _getReward(_quoteLiquidity(_amount, _liquidity));\n emit LiquidityAddition(_job, _liquidity, msg.sender, _amount);\n }\n\n /// @inheritdoc IKeep3rJobFundableLiquidity\n function unbondLiquidityFromJob(\n address _job,\n address _liquidity,\n uint256 _amount\n ) external override onlyJobOwner(_job) {\n canWithdrawAfter[_job][_liquidity] = block.timestamp + unbondTime;\n pendingUnbonds[_job][_liquidity] += _amount;\n _unbondLiquidityFromJob(_job, _liquidity, _amount);\n\n uint256 _remainingLiquidity = liquidityAmount[_job][_liquidity];\n if (_remainingLiquidity > 0 && _quoteLiquidity(_remainingLiquidity, _liquidity) < liquidityMinimum) revert JobLiquidityLessThanMin();\n\n emit Unbonding(_job, _liquidity, _amount);\n }\n\n /// @inheritdoc IKeep3rJobFundableLiquidity\n function withdrawLiquidityFromJob(\n address _job,\n address _liquidity,\n address _receiver\n ) external override onlyJobOwner(_job) {\n if (_receiver == address(0)) revert ZeroAddress();\n if (canWithdrawAfter[_job][_liquidity] == 0) revert UnbondsUnexistent();\n if (canWithdrawAfter[_job][_liquidity] >= block.timestamp) revert UnbondsLocked();\n if (disputes[_job]) revert Disputed();\n\n uint256 _amount = pendingUnbonds[_job][_liquidity];\n IERC20(_liquidity).safeTransfer(_receiver, _amount);\n emit LiquidityWithdrawal(_job, _liquidity, _receiver, _amount);\n\n pendingUnbonds[_job][_liquidity] = 0;\n }\n\n // Internal functions\n\n /// @notice Updates or rewards job liquidity credits depending on time since last job reward\n function _updateJobCreditsIfNeeded(address _job) internal returns (bool _rewarded) {\n if (rewardedAt[_job] < _period(block.timestamp)) {\n // Will exit function if job has been rewarded in current period\n if (rewardedAt[_job] <= _period(block.timestamp - rewardPeriodTime)) {\n // Will reset job to period syncronicity if a full period passed without rewards\n _updateJobPeriod(_job);\n _jobLiquidityCredits[_job] = _jobPeriodCredits[_job];\n rewardedAt[_job] = _period(block.timestamp);\n _rewarded = true;\n } else if ((block.timestamp - rewardedAt[_job]) >= rewardPeriodTime) {\n // Will reset job's syncronicity if last reward was more than epoch ago\n _updateJobPeriod(_job);\n _jobLiquidityCredits[_job] = _jobPeriodCredits[_job];\n rewardedAt[_job] += rewardPeriodTime;\n _rewarded = true;\n } else if (workedAt[_job] < _period(block.timestamp)) {\n // First keeper on period has to update job accountance to current twaps\n uint256 previousPeriodCredits = _jobPeriodCredits[_job];\n _updateJobPeriod(_job);\n _jobLiquidityCredits[_job] = (_jobLiquidityCredits[_job] * _jobPeriodCredits[_job]) / previousPeriodCredits;\n // Updating job accountance does not reward job\n }\n }\n }\n\n /// @notice Only called if _jobLiquidityCredits < payment\n function _rewardJobCredits(address _job) internal {\n /// @notice Only way to += jobLiquidityCredits is when keeper rewarding (cannot pay work)\n /* WARNING: this allows to top up _jobLiquidityCredits to a max of 1.99 but have to spend at least 1 */\n _jobLiquidityCredits[_job] += _phase(block.timestamp - rewardedAt[_job], _jobPeriodCredits[_job]);\n rewardedAt[_job] = block.timestamp;\n }\n\n /// @notice Updates accountance for _jobPeriodCredits\n function _updateJobPeriod(address _job) internal {\n _jobPeriodCredits[_job] = _calculateJobPeriodCredits(_job);\n }\n\n /// @notice Quotes the outdated job liquidities and calculates _periodCredits\n /// @dev This function is also responsible for keeping the KP3R/WETH quote updated\n function _calculateJobPeriodCredits(address _job) internal returns (uint256 _periodCredits) {\n if (_tick[kp3rWethPool].period != _period(block.timestamp)) {\n // Updates KP3R/WETH quote if needed\n _tick[kp3rWethPool] = observeLiquidity(kp3rWethPool);\n }\n\n for (uint256 i; i < _jobLiquidities[_job].length(); i++) {\n address _liquidity = _jobLiquidities[_job].at(i);\n if (_approvedLiquidities.contains(_liquidity)) {\n if (_tick[_liquidity].period != _period(block.timestamp)) {\n // Updates liquidity cache only if needed\n _tick[_liquidity] = observeLiquidity(_liquidity);\n }\n _periodCredits += _getReward(_quoteLiquidity(liquidityAmount[_job][_liquidity], _liquidity));\n }\n }\n }\n\n /// @notice Updates job accountance calculating the impact of the unbonded liquidity amount\n function _unbondLiquidityFromJob(\n address _job,\n address _liquidity,\n uint256 _amount\n ) internal nonReentrant {\n if (!_jobLiquidities[_job].contains(_liquidity)) revert JobLiquidityUnexistent();\n if (liquidityAmount[_job][_liquidity] < _amount) revert JobLiquidityInsufficient();\n\n // Ensures current twaps in job liquidities\n _updateJobPeriod(_job);\n uint256 _periodCreditsToRemove = _getReward(_quoteLiquidity(_amount, _liquidity));\n\n // A liquidity can be revoked causing a job to have 0 periodCredits\n if (_jobPeriodCredits[_job] > 0) {\n // Removes a % correspondant to a full rewardPeriodTime for the liquidity withdrawn vs all of the liquidities\n _jobLiquidityCredits[_job] -= (_jobLiquidityCredits[_job] * _periodCreditsToRemove) / _jobPeriodCredits[_job];\n _jobPeriodCredits[_job] -= _periodCreditsToRemove;\n }\n\n liquidityAmount[_job][_liquidity] -= _amount;\n if (liquidityAmount[_job][_liquidity] == 0) {\n _jobLiquidities[_job].remove(_liquidity);\n }\n }\n\n /// @notice Returns a fraction of the multiplier or the whole multiplier if equal or more than a rewardPeriodTime has passed\n function _phase(uint256 _timePassed, uint256 _multiplier) internal view returns (uint256 _result) {\n if (_timePassed < rewardPeriodTime) {\n _result = (_timePassed * _multiplier) / rewardPeriodTime;\n } else _result = _multiplier;\n }\n\n /// @notice Returns the start of the period of the provided timestamp\n function _period(uint256 _timestamp) internal view returns (uint256 _periodTimestamp) {\n return _timestamp - (_timestamp % rewardPeriodTime);\n }\n\n /// @notice Calculates relation between rewardPeriod and inflationPeriod\n function _getReward(uint256 _baseAmount) internal view returns (uint256 _credits) {\n return FullMath.mulDiv(_baseAmount, rewardPeriodTime, inflationPeriod);\n }\n\n /// @notice Returns underlying KP3R amount for a given liquidity amount\n function _quoteLiquidity(uint256 _amount, address _liquidity) internal view returns (uint256 _quote) {\n if (_tick[_liquidity].period != 0) {\n int56 _tickDifference = _isKP3RToken0[_liquidity] ? _tick[_liquidity].difference : -_tick[_liquidity].difference;\n _quote = IKeep3rHelper(keep3rHelper).getKP3RsAtTick(_amount, _tickDifference, rewardPeriodTime);\n }\n }\n\n /// @notice Updates job credits to current quotes and rewards job's pending minted credits\n /// @dev Ensures a maximum of 1 period of credits\n function _settleJobAccountance(address _job) internal virtual {\n _updateJobCreditsIfNeeded(_job);\n _rewardJobCredits(_job);\n _jobLiquidityCredits[_job] = Math.min(_jobLiquidityCredits[_job], _jobPeriodCredits[_job]);\n }\n}\n"
},
"solidity/contracts/peripherals/Keep3rDisputable.sol": {
"content": "// SPDX-License-Identifier: MIT\npragma solidity >=0.8.4 <0.9.0;\n\nimport './Keep3rParameters.sol';\nimport './Keep3rRoles.sol';\nimport '../../interfaces/peripherals/IKeep3rDisputable.sol';\n\nabstract contract Keep3rDisputable is IKeep3rDisputable, Keep3rAccountance, Keep3rRoles {\n /// @inheritdoc IKeep3rDisputable\n function dispute(address _jobOrKeeper) external override onlyDisputer {\n if (disputes[_jobOrKeeper]) revert AlreadyDisputed();\n disputes[_jobOrKeeper] = true;\n emit Dispute(_jobOrKeeper, msg.sender);\n }\n\n /// @inheritdoc IKeep3rDisputable\n function resolve(address _jobOrKeeper) external override onlyDisputer {\n if (!disputes[_jobOrKeeper]) revert NotDisputed();\n disputes[_jobOrKeeper] = false;\n emit Resolve(_jobOrKeeper, msg.sender);\n }\n}\n"
},
"solidity/contracts/peripherals/jobs/Keep3rJobOwnership.sol": {
"content": "// SPDX-License-Identifier: MIT\npragma solidity >=0.8.4 <0.9.0;\n\nimport '../../../interfaces/peripherals/IKeep3rJobs.sol';\n\nabstract contract Keep3rJobOwnership is IKeep3rJobOwnership {\n /// @inheritdoc IKeep3rJobOwnership\n mapping(address => address) public override jobOwner;\n\n /// @inheritdoc IKeep3rJobOwnership\n mapping(address => address) public override jobPendingOwner;\n\n /// @inheritdoc IKeep3rJobOwnership\n function changeJobOwnership(address _job, address _newOwner) external override onlyJobOwner(_job) {\n jobPendingOwner[_job] = _newOwner;\n emit JobOwnershipChange(_job, jobOwner[_job], _newOwner);\n }\n\n /// @inheritdoc IKeep3rJobOwnership\n function acceptJobOwnership(address _job) external override onlyPendingJobOwner(_job) {\n address _previousOwner = jobOwner[_job];\n\n jobOwner[_job] = jobPendingOwner[_job];\n delete jobPendingOwner[_job];\n\n emit JobOwnershipAssent(msg.sender, _job, _previousOwner);\n }\n\n modifier onlyJobOwner(address _job) {\n if (msg.sender != jobOwner[_job]) revert OnlyJobOwner();\n _;\n }\n\n modifier onlyPendingJobOwner(address _job) {\n if (msg.sender != jobPendingOwner[_job]) revert OnlyPendingJobOwner();\n _;\n }\n}\n"
},
"solidity/interfaces/peripherals/IKeep3rJobs.sol": {
"content": "// SPDX-License-Identifier: MIT\npragma solidity >=0.8.4 <0.9.0;\n\n/// @title Keep3rJobFundableCredits contract\n/// @notice Handles the addition and withdrawal of credits from a job\ninterface IKeep3rJobFundableCredits {\n // Events\n\n /// @notice Emitted when Keep3rJobFundableCredits#addTokenCreditsToJob is called\n /// @param _job The address of the job being credited\n /// @param _token The address of the token being provided\n /// @param _provider The user that calls the function\n /// @param _amount The amount of credit being added to the job\n event TokenCreditAddition(address indexed _job, address indexed _token, address indexed _provider, uint256 _amount);\n\n /// @notice Emitted when Keep3rJobFundableCredits#withdrawTokenCreditsFromJob is called\n /// @param _job The address of the job from which the credits are withdrawn\n /// @param _token The credit being withdrawn from the job\n /// @param _receiver The user that receives the tokens\n /// @param _amount The amount of credit withdrawn\n event TokenCreditWithdrawal(address indexed _job, address indexed _token, address indexed _receiver, uint256 _amount);\n\n // Errors\n\n /// @notice Throws when the token is KP3R, as it should not be used for direct token payments\n error TokenUnallowed();\n\n /// @notice Throws when the token withdraw cooldown has not yet passed\n error JobTokenCreditsLocked();\n\n /// @notice Throws when the user tries to withdraw more tokens than it has\n error InsufficientJobTokenCredits();\n\n // Variables\n\n /// @notice Last block where tokens were added to the job [job => token => timestamp]\n /// @return _timestamp The last block where tokens were added to the job\n function jobTokenCreditsAddedAt(address _job, address _token) external view returns (uint256 _timestamp);\n\n // Methods\n\n /// @notice Add credit to a job to be paid out for work\n /// @param _job The address of the job being credited\n /// @param _token The address of the token being credited\n /// @param _amount The amount of credit being added\n function addTokenCreditsToJob(\n address _job,\n address _token,\n uint256 _amount\n ) external;\n\n /// @notice Withdraw credit from a job\n /// @param _job The address of the job from which the credits are withdrawn\n /// @param _token The address of the token being withdrawn\n /// @param _amount The amount of token to be withdrawn\n /// @param _receiver The user that will receive tokens\n function withdrawTokenCreditsFromJob(\n address _job,\n address _token,\n uint256 _amount,\n address _receiver\n ) external;\n}\n\n/// @title Keep3rJobFundableLiquidity contract\n/// @notice Handles the funding of jobs through specific liquidity pairs\ninterface IKeep3rJobFundableLiquidity {\n // Events\n\n /// @notice Emitted when Keep3rJobFundableLiquidity#approveLiquidity function is called\n /// @param _liquidity The address of the liquidity pair being approved\n event LiquidityApproval(address _liquidity);\n\n /// @notice Emitted when Keep3rJobFundableLiquidity#revokeLiquidity function is called\n /// @param _liquidity The address of the liquidity pair being revoked\n event LiquidityRevocation(address _liquidity);\n\n /// @notice Emitted when IKeep3rJobFundableLiquidity#addLiquidityToJob function is called\n /// @param _job The address of the job to which liquidity will be added\n /// @param _liquidity The address of the liquidity being added\n /// @param _provider The user that calls the function\n /// @param _amount The amount of liquidity being added\n event LiquidityAddition(address indexed _job, address indexed _liquidity, address indexed _provider, uint256 _amount);\n\n /// @notice Emitted when IKeep3rJobFundableLiquidity#withdrawLiquidityFromJob function is called\n /// @param _job The address of the job of which liquidity will be withdrawn from\n /// @param _liquidity The address of the liquidity being withdrawn\n /// @param _receiver The receiver of the liquidity tokens\n /// @param _amount The amount of liquidity being withdrawn from the job\n event LiquidityWithdrawal(address indexed _job, address indexed _liquidity, address indexed _receiver, uint256 _amount);\n\n /// @notice Emitted when Keep3rJobFundableLiquidity#addLiquidityToJob function is called\n /// @param _job The address of the job whose credits will be updated\n /// @param _rewardedAt The time at which the job was last rewarded\n /// @param _currentCredits The current credits of the job\n /// @param _periodCredits The credits of the job for the current period\n event LiquidityCreditsReward(address indexed _job, uint256 _rewardedAt, uint256 _currentCredits, uint256 _periodCredits);\n\n /// @notice Emitted when Keep3rJobFundableLiquidity#forceLiquidityCreditsToJob function is called\n /// @param _job The address of the job whose credits will be updated\n /// @param _rewardedAt The time at which the job was last rewarded\n /// @param _currentCredits The current credits of the job\n event LiquidityCreditsForced(address indexed _job, uint256 _rewardedAt, uint256 _currentCredits);\n\n // Errors\n\n /// @notice Throws when the liquidity being approved has already been approved\n error LiquidityPairApproved();\n\n /// @notice Throws when the liquidity being removed has not been approved\n error LiquidityPairUnexistent();\n\n /// @notice Throws when trying to add liquidity to an unapproved pool\n error LiquidityPairUnapproved();\n\n /// @notice Throws when the job doesn't have the requested liquidity\n error JobLiquidityUnexistent();\n\n /// @notice Throws when trying to remove more liquidity than the job has\n error JobLiquidityInsufficient();\n\n /// @notice Throws when trying to add less liquidity than the minimum liquidity required\n error JobLiquidityLessThanMin();\n\n // Structs\n\n /// @notice Stores the tick information of the different liquidity pairs\n struct TickCache {\n int56 current; // Tracks the current tick\n int56 difference; // Stores the difference between the current tick and the last tick\n uint256 period; // Stores the period at which the last observation was made\n }\n\n // Variables\n\n /// @notice Lists liquidity pairs\n /// @return _list An array of addresses with all the approved liquidity pairs\n function approvedLiquidities() external view returns (address[] memory _list);\n\n /// @notice Amount of liquidity in a specified job\n /// @param _job The address of the job being checked\n /// @param _liquidity The address of the liquidity we are checking\n /// @return _amount Amount of liquidity in the specified job\n function liquidityAmount(address _job, address _liquidity) external view returns (uint256 _amount);\n\n /// @notice Last time the job was rewarded liquidity credits\n /// @param _job The address of the job being checked\n /// @return _timestamp Timestamp of the last time the job was rewarded liquidity credits\n function rewardedAt(address _job) external view returns (uint256 _timestamp);\n\n /// @notice Last time the job was worked\n /// @param _job The address of the job being checked\n /// @return _timestamp Timestamp of the last time the job was worked\n function workedAt(address _job) external view returns (uint256 _timestamp);\n\n // Methods\n\n /// @notice Returns the liquidity credits of a given job\n /// @param _job The address of the job of which we want to know the liquidity credits\n /// @return _amount The liquidity credits of a given job\n function jobLiquidityCredits(address _job) external view returns (uint256 _amount);\n\n /// @notice Returns the credits of a given job for the current period\n /// @param _job The address of the job of which we want to know the period credits\n /// @return _amount The credits the given job has at the current period\n function jobPeriodCredits(address _job) external view returns (uint256 _amount);\n\n /// @notice Calculates the total credits of a given job\n /// @param _job The address of the job of which we want to know the total credits\n /// @return _amount The total credits of the given job\n function totalJobCredits(address _job) external view returns (uint256 _amount);\n\n /// @notice Calculates how many credits should be rewarded periodically for a given liquidity amount\n /// @dev _periodCredits = underlying KP3Rs for given liquidity amount * rewardPeriod / inflationPeriod\n /// @param _liquidity The liquidity to provide\n /// @param _amount The amount of liquidity to provide\n /// @return _periodCredits The amount of KP3R periodically minted for the given liquidity\n function quoteLiquidity(address _liquidity, uint256 _amount) external view returns (uint256 _periodCredits);\n\n /// @notice Observes the current state of the liquidity pair being observed and updates TickCache with the information\n /// @param _liquidity The liquidity pair being observed\n /// @return _tickCache The updated TickCache\n function observeLiquidity(address _liquidity) external view returns (TickCache memory _tickCache);\n\n /// @notice Gifts liquidity credits to the specified job\n /// @param _job The address of the job being credited\n /// @param _amount The amount of liquidity credits to gift\n function forceLiquidityCreditsToJob(address _job, uint256 _amount) external;\n\n /// @notice Approve a liquidity pair for being accepted in future\n /// @param _liquidity The address of the liquidity accepted\n function approveLiquidity(address _liquidity) external;\n\n /// @notice Revoke a liquidity pair from being accepted in future\n /// @param _liquidity The liquidity no longer accepted\n function revokeLiquidity(address _liquidity) external;\n\n /// @notice Allows anyone to fund a job with liquidity\n /// @param _job The address of the job to assign liquidity to\n /// @param _liquidity The liquidity being added\n /// @param _amount The amount of liquidity tokens to add\n function addLiqui