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 1 commit
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
253 changes: 253 additions & 0 deletions smart-contracts/contracts/delegate/ProvidersDelegator.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,253 @@
// 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;

/**
* @dev The Lumerin protocol data
*/
address public lumerinDiamond;
address public token;

/**
* @dev The fee data
*/
address public feeTreasury;
uint256 public fee;

/**
* @dev Provider metadata
*/
string public name;
string public endpoint;


/**
* @dev The main contract logic data
*/
uint256 public totalStaked;
uint256 public totalRate;
uint256 public lastContractBalance;
bool public isStakeClosed;
mapping(address => Staker) public stakers;

constructor() {
_disableInitializers();
}

function ProvidersDelegator_init(
address lumerinDiamond_,
address feeTreasury_,
uint256 fee_,
string memory name_,
string memory endpoint_
) external initializer {
__Ownable_init();

lumerinDiamond = lumerinDiamond_;
token = IBidStorage(lumerinDiamond).getToken();

setName(name_);
setEndpoint(endpoint_);
setFee(feeTreasury_, fee_);

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 setFee(address feeTreasury_, uint256 fee_) public onlyOwner {
if (feeTreasury_ == address(0)) {
revert InvalidFeeTreasuryAddress();
}
if (fee_ > PRECISION) {
revert InvalidFee(fee_, PRECISION);
}

fee = fee_;
feeTreasury = feeTreasury_;

emit FeeUpdated(fee_, 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);

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_;

IERC20(token).safeTransferFrom(user_, address(this), amount_);
FedokDL marked this conversation as resolved.
Show resolved Hide resolved
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();
}

totalRate = currentRate_;
totalStaked += amount_;

lastContractBalance = contractBalance_ - amount_;

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

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

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_;

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.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) {
Staker memory staker = stakers[staker_];
FedokDL marked this conversation as resolved.
Show resolved Hide resolved
(uint256 currentRate_,) = getCurrentRate();

return _getCurrentStakerRewards(currentRate_, staker);
}

function providerDeregister() external onlyOwner {
FedokDL marked this conversation as resolved.
Show resolved Hide resolved
IProviderRegistry(lumerinDiamond).providerDeregister(address(this));
}

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

function deleteModelBid(bytes32 bidId_) external onlyOwner {
return IMarketplace(lumerinDiamond).deleteModelBid(bidId_);
}

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;
}
}
20 changes: 16 additions & 4 deletions smart-contracts/contracts/diamond/facets/SessionRouter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ import {LibSD} from "../../libs/LibSD.sol";

import {ISessionRouter} from "../../interfaces/facets/ISessionRouter.sol";

import "hardhat/console.sol";
FedokDL marked this conversation as resolved.
Show resolved Hide resolved

contract SessionRouter is
ISessionRouter,
OwnableDiamondStorage,
Expand Down Expand Up @@ -187,7 +189,7 @@ contract SessionRouter is
}

function _extractProviderApproval(bytes calldata providerApproval_) private view returns (bytes32) {
(bytes32 bidId_, uint256 chainId_,, uint128 timestamp_) = abi.decode(
(bytes32 bidId_, uint256 chainId_, , uint128 timestamp_) = abi.decode(
providerApproval_,
(bytes32, uint256, address, uint128)
);
Expand Down Expand Up @@ -534,10 +536,20 @@ contract SessionRouter is
bytes32 receiptHash_ = ECDSA.toEthSignedMessageHash(keccak256(receipt_));
address user_ = ECDSA.recover(receiptHash_, signature_);

if (user_ != provider_ && !isRightsDelegated(user_, provider_, DELEGATION_RULES_PROVIDER)) {
return false;
if (user_ == provider_ || isRightsDelegated(user_, provider_, DELEGATION_RULES_PROVIDER)) {
return true;
}

if (provider_.code.length > 0) {
FedokDL marked this conversation as resolved.
Show resolved Hide resolved
(bool success, bytes memory result) = provider_.staticcall(abi.encodeWithSignature("owner()"));
if (success && result.length == 32) {
address owner_ = abi.decode(result, (address));
if (user_ == owner_) {
return true;
}
}
}

return true;
return false;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

interface IProvidersDelegator {
event NameUpdated(string name);
event EndpointUpdated(string endpoint);
event FeeUpdated(uint256 fee, address feeTreasury);
event IsStakeClosedUpdated(bool isStakeClosed);
event IsRestakeDisabledUpdated(address staker, bool isRestakeDisabled);
event Staked(address staker, uint256 staked, uint256 pendingRewards, uint256 rate);
event Restaked(address staker, uint256 staked, uint256 pendingRewards, uint256 rate);
event Claimed(address staker, uint256 staked, uint256 pendingRewards, uint256 rate);
event FeeClaimed(address feeTreasury, uint256 feeAmount);



error InvalidNameLength();
error InvalidEndpointLength();
error InvalidFeeTreasuryAddress();
error InvalidFee(uint256 current, uint256 max);
error StakeClosed();
error InsufficientAmount();
error RestakeDisabled(address staker);
error RestakeInvalidCaller(address caller, address staker);
error ClaimAmountIsZero();

struct Staker {
uint256 staked;
uint256 rate;
uint256 pendingRewards;
bool isRestakeDisabled;
}

function ProvidersDelegator_init(
address lumerinDiamond_,
address feeTreasury_,
uint256 fee_,
string memory name_,
string memory endpoint_
) external;

function setName(string memory name_) external;

function setEndpoint(string memory endpoint_) external;

function setFee(address feeTreasury_, uint256 fee_) external;

function setIsStakeClosed(bool isStakeClosed_) external;

function setIsRestakeDisabled(bool isRestakeDisabled_) external;

function stake(uint256 amount_) external;

function restake(address staker_, uint256 amount_) external;

function claim(address staker_, uint256 amount_) external;

function getCurrentRate() external view returns (uint256, uint256);

function getCurrentStakerRewards(address staker_) external view returns (uint256);

function providerDeregister() external;

function postModelBid(
bytes32 modelId_,
uint256 pricePerSecond_
) external returns (bytes32);

function deleteModelBid(bytes32 bidId_) external;

function version() external pure returns (uint256);
}
2 changes: 2 additions & 0 deletions smart-contracts/contracts/mock/tokens/MorpheusToken.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ pragma solidity ^0.8.24;

import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";

import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";

contract MorpheusToken is ERC20 {
// set the initial supply to 42 million like in whitepaper
uint256 public constant INITIAL_SUPPLUY = 42_000_000 ether;
Expand Down
Loading