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

Closed
88 changes: 88 additions & 0 deletions smart-contracts/contracts/delegate/DelegatorFactory.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
// 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 {IOwnable} from "../interfaces/utils/IOwnable.sol";

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

event ProxyDeployed(address indexed proxyAddress);
event ImplementationUpdated(address indexed newImplementation);

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_
) 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(address(proxy_));

IProvidersDelegator(proxy_).ProvidersDelegator_init(lumerinDiamond, feeTreasury_, fee_, name_, endpoint_);
IOwnable(proxy_).transferOwnership(_msgSender());

emit ProxyDeployed(address(proxy_));

return address(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);

emit ImplementationUpdated(_newImplementation);
FedokDL marked this conversation as resolved.
Show resolved Hide resolved
}

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

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();
}

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;
}
}
21 changes: 17 additions & 4 deletions smart-contracts/contracts/diamond/facets/SessionRouter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
pragma solidity ^0.8.24;

import {Math} from "@openzeppelin/contracts/utils/math/Math.sol";
import {Address} from "@openzeppelin/contracts/utils/Address.sol";

import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
import {SafeERC20, IERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";

Expand Down Expand Up @@ -32,6 +34,7 @@ contract SessionRouter is
using LibSD for LibSD.SD;
using SafeERC20 for IERC20;
using EnumerableSet for EnumerableSet.Bytes32Set;
using Address for address;

function __SessionRouter_init(
address fundingAccount_,
Expand Down Expand Up @@ -187,7 +190,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 +537,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_.isContract()) {
(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;
}
}
Loading