-
Notifications
You must be signed in to change notification settings - Fork 13
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #44 from MorpheusAIs/feature/delegation-internal
Audited delegation feature
- Loading branch information
Showing
23 changed files
with
1,698 additions
and
31 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,109 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.20; | ||
|
||
import {Create2} from "@openzeppelin/contracts/utils/Create2.sol"; | ||
import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; | ||
import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; | ||
import {PausableUpgradeable} from "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol"; | ||
import {UpgradeableBeacon} from "@openzeppelin/contracts/proxy/beacon/UpgradeableBeacon.sol"; | ||
import {BeaconProxy} from "@openzeppelin/contracts/proxy/beacon/BeaconProxy.sol"; | ||
|
||
import {IProvidersDelegate} from "../interfaces/delegate/IProvidersDelegate.sol"; | ||
import {IDelegateFactory} from "../interfaces/delegate/IDelegateFactory.sol"; | ||
import {IOwnable} from "../interfaces/utils/IOwnable.sol"; | ||
|
||
contract DelegateFactory is IDelegateFactory, OwnableUpgradeable, PausableUpgradeable, UUPSUpgradeable { | ||
address public lumerinDiamond; | ||
address public beacon; | ||
|
||
mapping(address => address[]) public proxies; | ||
uint128 public minDeregistrationTimeout; | ||
|
||
constructor() { | ||
_disableInitializers(); | ||
} | ||
|
||
function DelegateFactory_init( | ||
address lumerinDiamond_, | ||
address implementation_, | ||
uint128 minDeregistrationTimeout_ | ||
) external initializer { | ||
__Pausable_init(); | ||
__Ownable_init(); | ||
__UUPSUpgradeable_init(); | ||
|
||
setMinDeregistrationTimeout(minDeregistrationTimeout_); | ||
lumerinDiamond = lumerinDiamond_; | ||
|
||
beacon = address(new UpgradeableBeacon(implementation_)); | ||
} | ||
|
||
function pause() external onlyOwner { | ||
_pause(); | ||
} | ||
|
||
function unpause() external onlyOwner { | ||
_unpause(); | ||
} | ||
|
||
function setMinDeregistrationTimeout(uint128 minDeregistrationTimeout_) public onlyOwner { | ||
minDeregistrationTimeout = minDeregistrationTimeout_; | ||
|
||
emit MinDeregistrationTimeoutUpdated(minDeregistrationTimeout_); | ||
} | ||
|
||
function deployProxy( | ||
address feeTreasury_, | ||
uint256 fee_, | ||
string memory name_, | ||
string memory endpoint_, | ||
uint128 deregistrationOpensAt_ | ||
) external whenNotPaused returns (address) { | ||
if (deregistrationOpensAt_ <= block.timestamp + minDeregistrationTimeout) { | ||
revert InvalidDeregistrationOpenAt(deregistrationOpensAt_, uint128(block.timestamp + minDeregistrationTimeout)); | ||
} | ||
|
||
bytes32 salt_ = _calculatePoolSalt(_msgSender()); | ||
address proxy_ = address(new BeaconProxy{salt: salt_}(beacon, bytes(""))); | ||
|
||
proxies[_msgSender()].push(proxy_); | ||
|
||
IProvidersDelegate(proxy_).ProvidersDelegate_init( | ||
lumerinDiamond, | ||
feeTreasury_, | ||
fee_, | ||
name_, | ||
endpoint_, | ||
deregistrationOpensAt_ | ||
); | ||
IOwnable(proxy_).transferOwnership(_msgSender()); | ||
|
||
emit ProxyDeployed(proxy_); | ||
|
||
return proxy_; | ||
} | ||
|
||
function predictProxyAddress(address deployer_) external view returns (address) { | ||
bytes32 salt_ = _calculatePoolSalt(deployer_); | ||
|
||
bytes32 bytecodeHash_ = keccak256( | ||
abi.encodePacked(type(BeaconProxy).creationCode, abi.encode(address(beacon), bytes(""))) | ||
); | ||
|
||
return Create2.computeAddress(salt_, bytecodeHash_); | ||
} | ||
|
||
function updateImplementation(address newImplementation_) external onlyOwner { | ||
UpgradeableBeacon(beacon).upgradeTo(newImplementation_); | ||
} | ||
|
||
function version() external pure returns (uint256) { | ||
return 1; | ||
} | ||
|
||
function _calculatePoolSalt(address sender_) internal view returns (bytes32) { | ||
return keccak256(abi.encodePacked(sender_, proxies[sender_].length)); | ||
} | ||
|
||
function _authorizeUpgrade(address) internal view override onlyOwner {} | ||
} |
275 changes: 275 additions & 0 deletions
275
smart-contracts/contracts/delegate/ProvidersDelegate.sol
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,275 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.24; | ||
|
||
import {SafeERC20, IERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; | ||
import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; | ||
import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; | ||
|
||
import {PRECISION} from "@solarity/solidity-lib/utils/Globals.sol"; | ||
|
||
import {IProvidersDelegate} from "../interfaces/delegate/IProvidersDelegate.sol"; | ||
import {IBidStorage} from "../interfaces/storage/IBidStorage.sol"; | ||
import {ISessionRouter} from "../interfaces/facets/ISessionRouter.sol"; | ||
import {IProviderRegistry} from "../interfaces/facets/IProviderRegistry.sol"; | ||
import {IMarketplace} from "../interfaces/facets/IMarketplace.sol"; | ||
|
||
contract ProvidersDelegate is IProvidersDelegate, OwnableUpgradeable { | ||
using SafeERC20 for IERC20; | ||
using Math for uint256; | ||
|
||
// The contract deps | ||
address public lumerinDiamond; | ||
address public token; | ||
|
||
// The owner fee | ||
address public feeTreasury; | ||
uint256 public fee; | ||
|
||
// The contract metadata | ||
string public name; | ||
string public endpoint; | ||
|
||
// The main calculation storage | ||
uint256 public totalStaked; | ||
uint256 public totalRate; | ||
uint256 public lastContractBalance; | ||
|
||
// The Staker data | ||
bool public isStakeClosed; | ||
mapping(address => Staker) public stakers; | ||
|
||
// Deregistration limits | ||
uint128 public deregistrationOpensAt; | ||
|
||
constructor() { | ||
_disableInitializers(); | ||
} | ||
|
||
function ProvidersDelegate_init( | ||
address lumerinDiamond_, | ||
address feeTreasury_, | ||
uint256 fee_, | ||
string memory name_, | ||
string memory endpoint_, | ||
uint128 deregistrationOpensAt_ | ||
) external initializer { | ||
__Ownable_init(); | ||
|
||
lumerinDiamond = lumerinDiamond_; | ||
token = IBidStorage(lumerinDiamond).getToken(); | ||
|
||
setName(name_); | ||
setEndpoint(endpoint_); | ||
setFeeTreasury(feeTreasury_); | ||
|
||
if (fee_ > PRECISION) { | ||
revert InvalidFee(fee_, PRECISION); | ||
} | ||
|
||
fee = fee_; | ||
deregistrationOpensAt = deregistrationOpensAt_; | ||
|
||
IERC20(token).approve(lumerinDiamond_, type(uint256).max); | ||
} | ||
|
||
function setName(string memory name_) public onlyOwner { | ||
if (bytes(name_).length == 0) { | ||
revert InvalidNameLength(); | ||
} | ||
|
||
name = name_; | ||
|
||
emit NameUpdated(name_); | ||
} | ||
|
||
function setEndpoint(string memory endpoint_) public onlyOwner { | ||
if (bytes(endpoint_).length == 0) { | ||
revert InvalidEndpointLength(); | ||
} | ||
|
||
endpoint = endpoint_; | ||
|
||
emit EndpointUpdated(endpoint_); | ||
} | ||
|
||
function setFeeTreasury(address feeTreasury_) public onlyOwner { | ||
if (feeTreasury_ == address(0)) { | ||
revert InvalidFeeTreasuryAddress(); | ||
} | ||
|
||
feeTreasury = feeTreasury_; | ||
|
||
emit FeeTreasuryUpdated(feeTreasury_); | ||
} | ||
|
||
function setIsStakeClosed(bool isStakeClosed_) public onlyOwner { | ||
isStakeClosed = isStakeClosed_; | ||
|
||
emit IsStakeClosedUpdated(isStakeClosed_); | ||
} | ||
|
||
function setIsRestakeDisabled(bool isRestakeDisabled_) external { | ||
stakers[_msgSender()].isRestakeDisabled = isRestakeDisabled_; | ||
|
||
emit IsRestakeDisabledUpdated(_msgSender(), isRestakeDisabled_); | ||
} | ||
|
||
function stake(uint256 amount_) external { | ||
_stake(_msgSender(), amount_); | ||
} | ||
|
||
function _stake(address staker_, uint256 amount_) private { | ||
if (isStakeClosed && !isStakeAfterDeregisterAvailable()) { | ||
revert StakeClosed(); | ||
} | ||
if (amount_ == 0) { | ||
revert InsufficientAmount(); | ||
} | ||
|
||
Staker storage staker = stakers[staker_]; | ||
|
||
(uint256 currentRate_, uint256 contractBalance_) = getCurrentRate(); | ||
uint256 pendingRewards_ = _getCurrentStakerRewards(currentRate_, staker); | ||
|
||
IERC20(token).safeTransferFrom(staker_, address(this), amount_); | ||
|
||
totalRate = currentRate_; | ||
totalStaked += amount_; | ||
|
||
lastContractBalance = contractBalance_; | ||
|
||
staker.rate = currentRate_; | ||
staker.staked += amount_; | ||
staker.pendingRewards = pendingRewards_; | ||
|
||
IProviderRegistry(lumerinDiamond).providerRegister(address(this), amount_, endpoint); | ||
|
||
emit Staked(staker_, staker.staked, totalStaked, staker.rate); | ||
} | ||
|
||
function restake(address staker_, uint256 amount_) external { | ||
if (_msgSender() != staker_ && _msgSender() != owner()) { | ||
revert RestakeInvalidCaller(_msgSender(), staker_); | ||
} | ||
if (_msgSender() == owner() && stakers[staker_].isRestakeDisabled) { | ||
revert RestakeDisabled(staker_); | ||
} | ||
|
||
amount_ = claim(staker_, amount_); | ||
_stake(staker_, amount_); | ||
} | ||
|
||
function claim(address staker_, uint256 amount_) public returns (uint256) { | ||
Staker storage staker = stakers[staker_]; | ||
|
||
(uint256 currentRate_, uint256 contractBalance_) = getCurrentRate(); | ||
uint256 pendingRewards_ = _getCurrentStakerRewards(currentRate_, staker); | ||
|
||
amount_ = amount_.min(contractBalance_).min(pendingRewards_); | ||
if (amount_ == 0) { | ||
revert ClaimAmountIsZero(); | ||
} | ||
|
||
totalRate = currentRate_; | ||
|
||
lastContractBalance = contractBalance_ - amount_; | ||
|
||
staker.rate = currentRate_; | ||
staker.pendingRewards = pendingRewards_ - amount_; | ||
staker.claimed += amount_; | ||
|
||
uint256 feeAmount_ = (amount_ * fee) / PRECISION; | ||
if (feeAmount_ != 0) { | ||
IERC20(token).safeTransfer(feeTreasury, feeAmount_); | ||
|
||
amount_ -= feeAmount_; | ||
|
||
emit FeeClaimed(feeTreasury, feeAmount_); | ||
} | ||
|
||
IERC20(token).safeTransfer(staker_, amount_); | ||
|
||
emit Claimed(staker_, staker.claimed, staker.rate); | ||
|
||
return amount_; | ||
} | ||
|
||
function getCurrentRate() public view returns (uint256, uint256) { | ||
uint256 contractBalance_ = IERC20(token).balanceOf(address(this)); | ||
|
||
if (totalStaked == 0) { | ||
return (totalRate, contractBalance_); | ||
} | ||
|
||
uint256 reward_ = contractBalance_ - lastContractBalance; | ||
uint256 rate_ = totalRate + (reward_ * PRECISION) / totalStaked; | ||
|
||
return (rate_, contractBalance_); | ||
} | ||
|
||
function getCurrentStakerRewards(address staker_) public view returns (uint256) { | ||
(uint256 currentRate_, ) = getCurrentRate(); | ||
|
||
return _getCurrentStakerRewards(currentRate_, stakers[staker_]); | ||
} | ||
|
||
function providerDeregister(bytes32[] calldata bidIds_) external { | ||
if (!isDeregisterAvailable()) { | ||
_checkOwner(); | ||
} | ||
|
||
_deleteModelBids(bidIds_); | ||
IProviderRegistry(lumerinDiamond).providerDeregister(address(this)); | ||
|
||
fee = 0; | ||
} | ||
|
||
function postModelBid(bytes32 modelId_, uint256 pricePerSecond_) external onlyOwner returns (bytes32) { | ||
if (isDeregisterAvailable()) { | ||
revert BidCannotBeCreatedDuringThisPeriod(); | ||
} | ||
|
||
IERC20(token).safeTransferFrom(_msgSender(), address(this), IMarketplace(lumerinDiamond).getBidFee()); | ||
|
||
return IMarketplace(lumerinDiamond).postModelBid(address(this), modelId_, pricePerSecond_); | ||
} | ||
|
||
function deleteModelBids(bytes32[] calldata bidIds_) external { | ||
if (!isDeregisterAvailable()) { | ||
_checkOwner(); | ||
} | ||
|
||
_deleteModelBids(bidIds_); | ||
} | ||
|
||
function claimForProvider(bytes32 sessionId_) external { | ||
ISessionRouter(lumerinDiamond).claimForProvider(sessionId_); | ||
} | ||
|
||
function isDeregisterAvailable() public view returns (bool) { | ||
return block.timestamp >= deregistrationOpensAt; | ||
} | ||
|
||
function isStakeAfterDeregisterAvailable() public view returns (bool) { | ||
IProviderRegistry.Provider memory provider_ = IProviderRegistry(lumerinDiamond).getProvider(address(this)); | ||
return isDeregisterAvailable() && provider_.stake > 0 && provider_.isDeleted; | ||
} | ||
|
||
function _deleteModelBids(bytes32[] calldata bidIds_) private { | ||
address lumerinDiamond_ = lumerinDiamond; | ||
|
||
for (uint256 i = 0; i < bidIds_.length; i++) { | ||
IMarketplace(lumerinDiamond_).deleteModelBid(bidIds_[i]); | ||
} | ||
} | ||
|
||
function _getCurrentStakerRewards(uint256 delegatorRate_, Staker memory staker_) private pure returns (uint256) { | ||
uint256 newRewards_ = ((delegatorRate_ - staker_.rate) * staker_.staked) / PRECISION; | ||
|
||
return staker_.pendingRewards + newRewards_; | ||
} | ||
|
||
function version() external pure returns (uint256) { | ||
return 1; | ||
} | ||
} |
Oops, something went wrong.