Skip to content

Commit

Permalink
Merge pull request #44 from MorpheusAIs/feature/delegation-internal
Browse files Browse the repository at this point in the history
Audited delegation feature
  • Loading branch information
FedokDL authored Jan 6, 2025
2 parents 55ecaa8 + de1a976 commit 2515fb6
Show file tree
Hide file tree
Showing 23 changed files with 1,698 additions and 31 deletions.
109 changes: 109 additions & 0 deletions smart-contracts/contracts/delegate/DelegateFactory.sol
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 smart-contracts/contracts/delegate/ProvidersDelegate.sol
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;
}
}
Loading

0 comments on commit 2515fb6

Please sign in to comment.