Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add base implementation #40

Open
wants to merge 9 commits into
base: fix/session-approval-delegation
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 6 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
94 changes: 94 additions & 0 deletions smart-contracts/contracts/delegate/DelegatorFactory.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
// 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 {IProvidersDelegator} from "../interfaces/delegate/IProvidersDelegator.sol";
import {IDelegatorFactory} from "../interfaces/delegate/IDelegatorFactory.sol";
import {IOwnable} from "../interfaces/utils/IOwnable.sol";

contract DelegatorFactory is IDelegatorFactory, OwnableUpgradeable, PausableUpgradeable, UUPSUpgradeable {
address public lumerinDiamond;
address public beacon;
mapping(address => address[]) public proxies;
RuslanProgrammer marked this conversation as resolved.
Show resolved Hide resolved

constructor() {
_disableInitializers();
}

function DelegatorFactory_init(address lumerinDiamond_, address implementation_) external initializer {
__Pausable_init();
__Ownable_init();
__UUPSUpgradeable_init();

lumerinDiamond = lumerinDiamond_;

beacon = address(new UpgradeableBeacon(implementation_));
}

function pause() external onlyOwner {
_pause();
}

function unpause() external onlyOwner {
_unpause();
}

function deployProxy(
address feeTreasury_,
uint256 fee_,
string memory name_,
string memory endpoint_,
uint128 deregistrationTimeout_,
uint128 deregistrationNonFeePeriod_
) external whenNotPaused returns (address) {
bytes32 salt_ = _calculatePoolSalt(_msgSender());
address proxy_ = address(new BeaconProxy{salt: salt_}(beacon, bytes("")));
FedokDL marked this conversation as resolved.
Show resolved Hide resolved

proxies[_msgSender()].push(proxy_);

IProvidersDelegator(proxy_).ProvidersDelegator_init(
lumerinDiamond,
feeTreasury_,
fee_,
name_,
endpoint_,
deregistrationTimeout_,
deregistrationNonFeePeriod_
);
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 {}
}
286 changes: 286 additions & 0 deletions smart-contracts/contracts/delegate/ProvidersDelegator.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,286 @@
// 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 {IProvidersDelegator} from "../interfaces/delegate/IProvidersDelegator.sol";
import {IBidStorage} from "../interfaces/storage/IBidStorage.sol";
import {IProviderRegistry} from "../interfaces/facets/IProviderRegistry.sol";
import {IMarketplace} from "../interfaces/facets/IMarketplace.sol";

contract ProvidersDelegator is IProvidersDelegator, OwnableUpgradeable {
FedokDL marked this conversation as resolved.
Show resolved Hide resolved
using SafeERC20 for IERC20;
using Math for uint256;

// Deps
address public lumerinDiamond;
address public token;

// Fee
address public feeTreasury;
uint256 public fee;

// Metadata
string public name;
string public endpoint;

// Main calculation storage
uint256 public totalStaked;
uint256 public totalRate;
uint256 public lastContractBalance;
bool public isStakeClosed;
mapping(address => Staker) public stakers;

// Deregistration limits
uint128 public deregistrationOpenAt;
RuslanProgrammer marked this conversation as resolved.
Show resolved Hide resolved
uint128 public deregistrationTimeout;
uint128 public deregistrationNonFeeOpened;
uint128 public deregistrationNonFeePeriod;

constructor() {
_disableInitializers();
}

function ProvidersDelegator_init(
address lumerinDiamond_,
address feeTreasury_,
uint256 fee_,
string memory name_,
string memory endpoint_,
uint128 deregistrationTimeout_,
uint128 deregistrationNonFeePeriod_
) 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_;

IERC20(token).approve(lumerinDiamond_, type(uint256).max);

deregistrationTimeout = deregistrationTimeout_;
deregistrationOpenAt = uint128(block.timestamp) + deregistrationTimeout_;
deregistrationNonFeePeriod = deregistrationNonFeePeriod_;
}

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 {
if (isStakeClosed) {
revert StakeClosed();
}

if (amount_ == 0) {
revert InsufficientAmount();
}

address user_ = _msgSender();
Staker storage staker = stakers[user_];

(uint256 currentRate_, uint256 contractBalance_) = getCurrentRate();
uint256 pendingRewards_ = _getCurrentStakerRewards(currentRate_, staker);

IERC20(token).safeTransferFrom(user_, address(this), amount_);

totalRate = currentRate_;
totalStaked += amount_;

lastContractBalance = contractBalance_;
FedokDL marked this conversation as resolved.
Show resolved Hide resolved

staker.rate = currentRate_;
staker.staked += amount_;
staker.pendingRewards = pendingRewards_;

IProviderRegistry(lumerinDiamond).providerRegister(address(this), amount_, endpoint);

emit Staked(user_, staker.staked, staker.pendingRewards, staker.rate);
}

function restake(address staker_, uint256 amount_) external {
if (_msgSender() != staker_ && _msgSender() != owner()) {
revert RestakeInvalidCaller(_msgSender(), staker_);
}

Staker storage staker = stakers[staker_];
if (staker.isRestakeDisabled) {
revert RestakeDisabled(staker_);
}

(uint256 currentRate_, uint256 contractBalance_) = getCurrentRate();
uint256 pendingRewards_ = _getCurrentStakerRewards(currentRate_, staker);

amount_ = amount_.min(contractBalance_).min(pendingRewards_);
if (amount_ == 0) {
revert InsufficientAmount();
}

uint256 feeAmount_ = (amount_ * fee) / PRECISION;
uint256 amountWithFee_ = amount_ - feeAmount_;
FedokDL marked this conversation as resolved.
Show resolved Hide resolved
if (feeAmount_ != 0) {
IERC20(token).safeTransfer(feeTreasury, feeAmount_);

emit FeeClaimed(feeTreasury, feeAmount_);
}

IProviderRegistry(lumerinDiamond).providerRegister(address(this), amountWithFee_, endpoint);

totalRate = currentRate_;
totalStaked += amountWithFee_;

lastContractBalance = contractBalance_ - amount_;

staker.rate = currentRate_;
staker.staked += amountWithFee_;
staker.claimed += amount_;
staker.pendingRewards = pendingRewards_ - amount_;

emit Restaked(staker_, staker.staked, staker.pendingRewards, staker.rate);
}

function claim(address staker_, uint256 amount_) external {
FedokDL marked this conversation as resolved.
Show resolved Hide resolved
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 && block.timestamp > deregistrationNonFeeOpened + deregistrationNonFeePeriod) {
IERC20(token).safeTransfer(feeTreasury, feeAmount_);

amount_ -= feeAmount_;

emit FeeClaimed(feeTreasury, feeAmount_);
}

IERC20(token).safeTransfer(staker_, amount_);

emit Claimed(staker_, staker.staked, staker.pendingRewards, staker.rate);
}

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 (block.timestamp < deregistrationOpenAt) {
_checkOwner();
} else {
deregistrationOpenAt = uint128(block.timestamp) + deregistrationTimeout;
}

_deleteModelBids(bidIds_);
IProviderRegistry(lumerinDiamond).providerDeregister(address(this));

deregistrationNonFeeOpened = uint128(block.timestamp);
}

function postModelBid(bytes32 modelId_, uint256 pricePerSecond_) external onlyOwner returns (bytes32) {
return IMarketplace(lumerinDiamond).postModelBid(address(this), modelId_, pricePerSecond_);
}

function deleteModelBids(bytes32[] calldata bidIds_) external {
if (block.timestamp < deregistrationOpenAt) {
_checkOwner();
}

_deleteModelBids(bidIds_);
}

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