From 8c69bfabe53e085ea17d6c27f586e92ee5411352 Mon Sep 17 00:00:00 2001 From: Oleksandr Date: Fri, 13 Dec 2024 13:31:41 +0200 Subject: [PATCH] review fixes, add the deregister process --- .../contracts/delegate/DelegatorFactory.sol | 42 +++--- .../contracts/delegate/ProvidersDelegator.sol | 85 +++++++----- .../interfaces/delegate/IDelegatorFactory.sol | 59 +++++++++ .../delegate/IProvidersDelegator.sol | 100 ++++++++++++-- .../test/delegate/DelegatorFactory.test.ts | 28 ++-- .../test/delegate/ProviderDelegator.test.ts | 124 ++++++++++++------ .../deployers/delegate/providers-delegator.ts | 12 +- 7 files changed, 327 insertions(+), 123 deletions(-) create mode 100644 smart-contracts/contracts/interfaces/delegate/IDelegatorFactory.sol diff --git a/smart-contracts/contracts/delegate/DelegatorFactory.sol b/smart-contracts/contracts/delegate/DelegatorFactory.sol index 4f149bab..1f524e4b 100644 --- a/smart-contracts/contracts/delegate/DelegatorFactory.sol +++ b/smart-contracts/contracts/delegate/DelegatorFactory.sol @@ -9,28 +9,26 @@ import {UpgradeableBeacon} from "@openzeppelin/contracts/proxy/beacon/Upgradeabl 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 OwnableUpgradeable, PausableUpgradeable, UUPSUpgradeable { +contract DelegatorFactory is IDelegatorFactory, OwnableUpgradeable, PausableUpgradeable, UUPSUpgradeable { address public lumerinDiamond; address public beacon; mapping(address => address[]) public proxies; - event ProxyDeployed(address indexed proxyAddress); - event ImplementationUpdated(address indexed newImplementation); - constructor() { _disableInitializers(); } - function DelegatorFactory_init(address _lumerinDiamond, address _implementation) external initializer { + function DelegatorFactory_init(address lumerinDiamond_, address implementation_) external initializer { __Pausable_init(); __Ownable_init(); __UUPSUpgradeable_init(); - lumerinDiamond = _lumerinDiamond; + lumerinDiamond = lumerinDiamond_; - beacon = address(new UpgradeableBeacon(_implementation)); + beacon = address(new UpgradeableBeacon(implementation_)); } function pause() external onlyOwner { @@ -45,23 +43,33 @@ contract DelegatorFactory is OwnableUpgradeable, PausableUpgradeable, UUPSUpgrad address feeTreasury_, uint256 fee_, string memory name_, - string memory endpoint_ + string memory endpoint_, + uint128 deregistrationTimeout_, + uint128 deregistrationNonFeePeriod_ ) external whenNotPaused returns (address) { bytes32 salt_ = _calculatePoolSalt(_msgSender()); address proxy_ = address(new BeaconProxy{salt: salt_}(beacon, bytes(""))); - proxies[_msgSender()].push(address(proxy_)); + proxies[_msgSender()].push(proxy_); - IProvidersDelegator(proxy_).ProvidersDelegator_init(lumerinDiamond, feeTreasury_, fee_, name_, endpoint_); + IProvidersDelegator(proxy_).ProvidersDelegator_init( + lumerinDiamond, + feeTreasury_, + fee_, + name_, + endpoint_, + deregistrationTimeout_, + deregistrationNonFeePeriod_ + ); IOwnable(proxy_).transferOwnership(_msgSender()); - emit ProxyDeployed(address(proxy_)); + emit ProxyDeployed(proxy_); - return address(proxy_); + return proxy_; } - function predictProxyAddress(address _deployer) external view returns (address) { - bytes32 salt_ = _calculatePoolSalt(_deployer); + 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(""))) @@ -70,10 +78,8 @@ contract DelegatorFactory is OwnableUpgradeable, PausableUpgradeable, UUPSUpgrad return Create2.computeAddress(salt_, bytecodeHash_); } - function updateImplementation(address _newImplementation) external onlyOwner { - UpgradeableBeacon(beacon).upgradeTo(_newImplementation); - - emit ImplementationUpdated(_newImplementation); + function updateImplementation(address newImplementation_) external onlyOwner { + UpgradeableBeacon(beacon).upgradeTo(newImplementation_); } function version() external pure returns (uint256) { diff --git a/smart-contracts/contracts/delegate/ProvidersDelegator.sol b/smart-contracts/contracts/delegate/ProvidersDelegator.sol index 6f09a9e7..5fde9a14 100644 --- a/smart-contracts/contracts/delegate/ProvidersDelegator.sol +++ b/smart-contracts/contracts/delegate/ProvidersDelegator.sol @@ -16,34 +16,26 @@ contract ProvidersDelegator is IProvidersDelegator, OwnableUpgradeable { 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; + uint128 public deregistrationOpenAt; + uint128 public deregistrationTimeout; + uint128 public deregistrationNonFeeOpened; + uint128 public deregistrationNonFeePeriod; + constructor() { _disableInitializers(); } @@ -53,7 +45,9 @@ contract ProvidersDelegator is IProvidersDelegator, OwnableUpgradeable { address feeTreasury_, uint256 fee_, string memory name_, - string memory endpoint_ + string memory endpoint_, + uint128 deregistrationTimeout_, + uint128 deregistrationNonFeePeriod_ ) external initializer { __Ownable_init(); @@ -62,9 +56,18 @@ contract ProvidersDelegator is IProvidersDelegator, OwnableUpgradeable { setName(name_); setEndpoint(endpoint_); - setFee(feeTreasury_, fee_); + 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 { @@ -87,18 +90,14 @@ contract ProvidersDelegator is IProvidersDelegator, OwnableUpgradeable { emit EndpointUpdated(endpoint_); } - function setFee(address feeTreasury_, uint256 fee_) public onlyOwner { + function setFeeTreasury(address feeTreasury_) public onlyOwner { if (feeTreasury_ == address(0)) { revert InvalidFeeTreasuryAddress(); } - if (fee_ > PRECISION) { - revert InvalidFee(fee_, PRECISION); - } - fee = fee_; feeTreasury = feeTreasury_; - emit FeeUpdated(fee_, feeTreasury_); + emit FeeTreasuryUpdated(feeTreasury_); } function setIsStakeClosed(bool isStakeClosed_) public onlyOwner { @@ -117,10 +116,11 @@ contract ProvidersDelegator is IProvidersDelegator, OwnableUpgradeable { if (isStakeClosed) { revert StakeClosed(); } + if (amount_ == 0) { revert InsufficientAmount(); } - + address user_ = _msgSender(); Staker storage staker = stakers[user_]; @@ -168,6 +168,7 @@ contract ProvidersDelegator is IProvidersDelegator, OwnableUpgradeable { staker.rate = currentRate_; staker.staked += amount_; + staker.claimed += amount_; staker.pendingRewards = pendingRewards_ - amount_; IProviderRegistry(lumerinDiamond).providerRegister(address(this), amount_, endpoint); @@ -192,9 +193,10 @@ contract ProvidersDelegator is IProvidersDelegator, OwnableUpgradeable { staker.rate = currentRate_; staker.pendingRewards = pendingRewards_ - amount_; + staker.claimed += amount_; uint256 feeAmount_ = (amount_ * fee) / PRECISION; - if (feeAmount_ != 0) { + if (feeAmount_ != 0 && block.timestamp > deregistrationNonFeeOpened + deregistrationNonFeePeriod) { IERC20(token).safeTransfer(feeTreasury, feeAmount_); amount_ -= feeAmount_; @@ -221,25 +223,42 @@ contract ProvidersDelegator is IProvidersDelegator, OwnableUpgradeable { } function getCurrentStakerRewards(address staker_) public view returns (uint256) { - Staker memory staker = stakers[staker_]; - (uint256 currentRate_,) = getCurrentRate(); + (uint256 currentRate_, ) = getCurrentRate(); - return _getCurrentStakerRewards(currentRate_, staker); + return _getCurrentStakerRewards(currentRate_, stakers[staker_]); } - function providerDeregister() external onlyOwner { + 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) { + 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 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) { diff --git a/smart-contracts/contracts/interfaces/delegate/IDelegatorFactory.sol b/smart-contracts/contracts/interfaces/delegate/IDelegatorFactory.sol new file mode 100644 index 00000000..c95d24dc --- /dev/null +++ b/smart-contracts/contracts/interfaces/delegate/IDelegatorFactory.sol @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +interface IDelegatorFactory { + event ProxyDeployed(address indexed proxyAddress); + event ImplementationUpdated(address indexed newImplementation); + + /** + * The function to initialize the contract. + * @param lumerinDiamond_ The Lumerin protocol address. + * @param implementation_ The implementation address. + */ + function DelegatorFactory_init(address lumerinDiamond_, address implementation_) external; + + /** + * Triggers stopped state. + */ + function pause() external; + + /** + * Returns to normal state. + */ + function unpause() external; + + /** + * The function to deploy the new proxy contract. + * @param feeTreasury_ The subnet fee treasury. + * @param fee_ The fee percent where 100% = 10^25. + * @param name_ The Subnet name. + * @param endpoint_ The subnet endpoint. + * @param deregistrationTimeout_ Provider deregistration will be available after this timeout. + * @param deregistrationNonFeePeriod_ Period after deregistration when Stakers can claim rewards without fee. + */ + function deployProxy( + address feeTreasury_, + uint256 fee_, + string memory name_, + string memory endpoint_, + uint128 deregistrationTimeout_, + uint128 deregistrationNonFeePeriod_ + ) external returns (address); + + /** + * The function to predict new proxy address. + * @param _deployer The deployer address. + */ + function predictProxyAddress(address _deployer) external view returns (address); + + /** + * The function to upgrade the implementation. + * @param _newImplementation The new implementation address. + */ + function updateImplementation(address _newImplementation) external; + + /** + * The function to get contract version. + */ + function version() external pure returns (uint256); +} diff --git a/smart-contracts/contracts/interfaces/delegate/IProvidersDelegator.sol b/smart-contracts/contracts/interfaces/delegate/IProvidersDelegator.sol index 5843a4ec..84bd7fe8 100644 --- a/smart-contracts/contracts/interfaces/delegate/IProvidersDelegator.sol +++ b/smart-contracts/contracts/interfaces/delegate/IProvidersDelegator.sol @@ -4,7 +4,7 @@ pragma solidity ^0.8.24; interface IProvidersDelegator { event NameUpdated(string name); event EndpointUpdated(string endpoint); - event FeeUpdated(uint256 fee, address feeTreasury); + event FeeTreasuryUpdated(address feeTreasury); event IsStakeClosedUpdated(bool isStakeClosed); event IsRestakeDisabledUpdated(address staker, bool isRestakeDisabled); event Staked(address staker, uint256 staked, uint256 pendingRewards, uint256 rate); @@ -12,8 +12,6 @@ interface IProvidersDelegator { event Claimed(address staker, uint256 staked, uint256 pendingRewards, uint256 rate); event FeeClaimed(address feeTreasury, uint256 feeAmount); - - error InvalidNameLength(); error InvalidEndpointLength(); error InvalidFeeTreasuryAddress(); @@ -24,49 +22,123 @@ interface IProvidersDelegator { error RestakeInvalidCaller(address caller, address staker); error ClaimAmountIsZero(); + /** + * @param staked Staked amount. + * @param claimed Claimed amount. + * @param rate The user internal rate. + * @param pendingRewards Pending rewards for claim. + * @param isRestakeDisabled If true, restake isn't available. + */ struct Staker { uint256 staked; + uint256 claimed; uint256 rate; uint256 pendingRewards; bool isRestakeDisabled; } + /** + * The function to initialize the contract. + * @param lumerinDiamond_ The Lumerin protocol address. + * @param feeTreasury_ The subnet fee treasury. + * @param fee_ The fee percent where 100% = 10^25. + * @param name_ The Subnet name. + * @param endpoint_ The subnet endpoint. + * @param deregistrationTimeout_ Provider deregistration will be available after this timeout. + * @param deregistrationNonFeePeriod_ Period after deregistration when Stakers can claim rewards without fee. + */ function ProvidersDelegator_init( address lumerinDiamond_, address feeTreasury_, uint256 fee_, string memory name_, - string memory endpoint_ + string memory endpoint_, + uint128 deregistrationTimeout_, + uint128 deregistrationNonFeePeriod_ ) external; + /** + * The function to set the Subnet name. + * @param name_ New name. + */ function setName(string memory name_) external; + /** + * The function to set the new endpoint. + * @param endpoint_ New endpoint. + */ function setEndpoint(string memory endpoint_) external; - function setFee(address feeTreasury_, uint256 fee_) external; + /** + * The function to set fee treasury address. + * @param feeTreasury_ New address + */ + function setFeeTreasury(address feeTreasury_) external; + /** + * The function close or open possibility to stake new tokens. + * @param isStakeClosed_ True or False. + */ function setIsStakeClosed(bool isStakeClosed_) external; + /** + * The function to disabled possibility for restake. + * @param isRestakeDisabled_ True or False. + */ function setIsRestakeDisabled(bool isRestakeDisabled_) external; + /** + * The function to stake tokens. + * @param amount_ Amount to stake. + */ function stake(uint256 amount_) external; + /** + * The function to restake rewards. + * @param staker_ Staker address. + * @param amount_ Amount to stake. + */ function restake(address staker_, uint256 amount_) external; + /** + * The function to claim rewards. + * @param staker_ Staker address. + * @param amount_ Amount to stake. + */ function claim(address staker_, uint256 amount_) external; + /** + * The function to return the current rate. + */ function getCurrentRate() external view returns (uint256, uint256); + /** + * The function to get amount of the Staker rewards. + * @param staker_ Staker address. + */ 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; - + /** + * The function to deregister the provider. + * @param bidIds_ Bid IDs. + */ + function providerDeregister(bytes32[] calldata bidIds_) external; + + /** + * The function create the model bid. + * @param modelId_ Model ID. + * @param pricePerSecond_ Price per second. + */ + function postModelBid(bytes32 modelId_, uint256 pricePerSecond_) external returns (bytes32); + + /** + * The function to delete model bids. + * @param bidIds_ Bid IDs. + */ + function deleteModelBids(bytes32[] calldata bidIds_) external; + + /** + * The function to get contract version. + */ function version() external pure returns (uint256); } diff --git a/smart-contracts/test/delegate/DelegatorFactory.test.ts b/smart-contracts/test/delegate/DelegatorFactory.test.ts index 92728733..2715750d 100644 --- a/smart-contracts/test/delegate/DelegatorFactory.test.ts +++ b/smart-contracts/test/delegate/DelegatorFactory.test.ts @@ -87,7 +87,7 @@ describe('DelegatorFactory', () => { }); it('should deploy a new proxy', async () => { - await delegatorFactory.connect(SHEV).deployProxy(KYLE, wei(0.1, 25), 'name', 'endpoint'); + await delegatorFactory.connect(SHEV).deployProxy(KYLE, wei(0.1, 25), 'name', 'endpoint', 1, 2); const proxy = providersDelegatorFactory.attach(await delegatorFactory.proxies(SHEV, 0)) as ProvidersDelegator; @@ -98,9 +98,9 @@ describe('DelegatorFactory', () => { expect(await proxy.endpoint()).to.eq('endpoint'); }); it('should deploy new proxies', async () => { - await delegatorFactory.connect(SHEV).deployProxy(KYLE, wei(0.1, 25), 'name1', 'endpoint1'); - await delegatorFactory.connect(SHEV).deployProxy(SHEV, wei(0.2, 25), 'name2', 'endpoint2'); - await delegatorFactory.connect(KYLE).deployProxy(SHEV, wei(0.3, 25), 'name3', 'endpoint3'); + await delegatorFactory.connect(SHEV).deployProxy(KYLE, wei(0.1, 25), 'name1', 'endpoint1', 1, 2); + await delegatorFactory.connect(SHEV).deployProxy(SHEV, wei(0.2, 25), 'name2', 'endpoint2', 1, 2); + await delegatorFactory.connect(KYLE).deployProxy(SHEV, wei(0.3, 25), 'name3', 'endpoint3', 1, 2); let proxy = providersDelegatorFactory.attach(await delegatorFactory.proxies(SHEV, 1)) as ProvidersDelegator; expect(await proxy.owner()).to.eq(SHEV); @@ -120,11 +120,11 @@ describe('DelegatorFactory', () => { it('should revert when paused and not after the unpause', async () => { await delegatorFactory.pause(); await expect( - delegatorFactory.connect(SHEV).deployProxy(KYLE, wei(0.1, 25), 'name1', 'endpoint1'), + delegatorFactory.connect(SHEV).deployProxy(KYLE, wei(0.1, 25), 'name1', 'endpoint1', 1, 2), ).to.be.rejectedWith('Pausable: paused'); await delegatorFactory.unpause(); - await delegatorFactory.connect(SHEV).deployProxy(KYLE, wei(0.1, 25), 'name1', 'endpoint1'); + await delegatorFactory.connect(SHEV).deployProxy(KYLE, wei(0.1, 25), 'name1', 'endpoint1', 1, 2); }); it('should throw error when caller is not an owner', async () => { await expect(delegatorFactory.connect(KYLE).pause()).to.be.revertedWith('Ownable: caller is not the owner'); @@ -138,7 +138,7 @@ describe('DelegatorFactory', () => { describe('#predictProxyAddress', () => { it('should predict a proxy address', async () => { const predictedProxyAddress = await delegatorFactory.predictProxyAddress(SHEV); - await delegatorFactory.connect(SHEV).deployProxy(KYLE, wei(0.1, 25), 'name', 'endpoint'); + await delegatorFactory.connect(SHEV).deployProxy(KYLE, wei(0.1, 25), 'name', 'endpoint', 1, 2); const proxyAddress = await delegatorFactory.proxies(SHEV, 0); @@ -146,28 +146,28 @@ describe('DelegatorFactory', () => { }); it('should predict proxy addresses', async () => { let predictedProxyAddress = await delegatorFactory.predictProxyAddress(SHEV); - await delegatorFactory.connect(SHEV).deployProxy(KYLE, wei(0.1, 25), 'name', 'endpoint'); + await delegatorFactory.connect(SHEV).deployProxy(KYLE, wei(0.1, 25), 'name', 'endpoint', 1, 2); expect(await delegatorFactory.proxies(SHEV, 0)).to.eq(predictedProxyAddress); predictedProxyAddress = await delegatorFactory.predictProxyAddress(SHEV); - await delegatorFactory.connect(SHEV).deployProxy(KYLE, wei(0.1, 25), 'name', 'endpoint'); + await delegatorFactory.connect(SHEV).deployProxy(KYLE, wei(0.1, 25), 'name', 'endpoint', 1, 2); expect(await delegatorFactory.proxies(SHEV, 1)).to.eq(predictedProxyAddress); predictedProxyAddress = await delegatorFactory.predictProxyAddress(KYLE); - await delegatorFactory.connect(KYLE).deployProxy(KYLE, wei(0.1, 25), 'name', 'endpoint'); + await delegatorFactory.connect(KYLE).deployProxy(KYLE, wei(0.1, 25), 'name', 'endpoint', 1, 2); expect(await delegatorFactory.proxies(KYLE, 0)).to.eq(predictedProxyAddress); predictedProxyAddress = await delegatorFactory.predictProxyAddress(SHEV); - await delegatorFactory.connect(SHEV).deployProxy(KYLE, wei(0.1, 25), 'name', 'endpoint'); + await delegatorFactory.connect(SHEV).deployProxy(KYLE, wei(0.1, 25), 'name', 'endpoint', 1, 2); expect(await delegatorFactory.proxies(SHEV, 2)).to.eq(predictedProxyAddress); }); }); describe('#updateImplementation', () => { it('should update proxies implementation', async () => { - await delegatorFactory.connect(SHEV).deployProxy(KYLE, wei(0.1, 25), 'name', 'endpoint'); - await delegatorFactory.connect(SHEV).deployProxy(KYLE, wei(0.1, 25), 'name', 'endpoint'); - await delegatorFactory.connect(KYLE).deployProxy(KYLE, wei(0.1, 25), 'name', 'endpoint'); + await delegatorFactory.connect(SHEV).deployProxy(KYLE, wei(0.1, 25), 'name', 'endpoint', 1, 2); + await delegatorFactory.connect(SHEV).deployProxy(KYLE, wei(0.1, 25), 'name', 'endpoint', 1, 2); + await delegatorFactory.connect(KYLE).deployProxy(KYLE, wei(0.1, 25), 'name', 'endpoint', 1, 2); const factory = await ethers.getContractFactory('UUPSMock'); const newImpl = await factory.deploy(); diff --git a/smart-contracts/test/delegate/ProviderDelegator.test.ts b/smart-contracts/test/delegate/ProviderDelegator.test.ts index 2a191b63..993d26b4 100644 --- a/smart-contracts/test/delegate/ProviderDelegator.test.ts +++ b/smart-contracts/test/delegate/ProviderDelegator.test.ts @@ -53,6 +53,7 @@ describe('ProvidersDelegator', () => { let delegateRegistry: DelegateRegistry; before(async () => { + // await setTime(5000); [OWNER, DELEGATOR, TREASURY, KYLE, SHEV, ALAN] = await ethers.getSigners(); [diamond, token, delegateRegistry] = await Promise.all([ @@ -75,6 +76,8 @@ describe('ProvidersDelegator', () => { wei(0.2, 25), 'DLNAME', 'ENDPOINT', + 3600, + 300, ); await token.transfer(KYLE, wei(1000)); @@ -86,6 +89,7 @@ describe('ProvidersDelegator', () => { await token.connect(ALAN).approve(sessionRouter, wei(1000)); await token.connect(KYLE).approve(providersDelegator, wei(1000)); await token.connect(SHEV).approve(providersDelegator, wei(1000)); + await token.connect(ALAN).approve(providersDelegator, wei(1000)); await token.connect(DELEGATOR).approve(modelRegistry, wei(1000)); await reverter.snapshot(); @@ -96,9 +100,14 @@ describe('ProvidersDelegator', () => { describe('#ProvidersDelegator_init', () => { it('should revert if try to call init function twice', async () => { await expect( - providersDelegator.ProvidersDelegator_init(OWNER, await TREASURY.getAddress(), 1, '', ''), + providersDelegator.ProvidersDelegator_init(OWNER, await TREASURY.getAddress(), 1, '', '', 0, 0), ).to.be.rejectedWith('Initializable: contract is already initialized'); }); + it('should throw error when fee is invalid', async () => { + await expect( + deployProvidersDelegator(diamond, await TREASURY.getAddress(), wei(1.1, 25), 'DLNAME', 'ENDPOINT', 3600, 300), + ).to.be.revertedWithCustomError(providersDelegator, 'InvalidFee'); + }); }); describe('#setName', () => { @@ -137,27 +146,20 @@ describe('ProvidersDelegator', () => { }); }); - describe('#setFee', () => { + describe('#setFeeTreasuryTreasury', () => { it('should set the provider fee', async () => { - await providersDelegator.setFee(KYLE, wei(0.1, 25)); + await providersDelegator.setFeeTreasury(KYLE); - expect(await providersDelegator.fee()).eq(wei(0.1, 25)); expect(await providersDelegator.feeTreasury()).eq(KYLE); }); it('should throw error when fee treasury is invalid', async () => { - await expect(providersDelegator.setFee(ZERO_ADDR, wei(0.1, 25))).to.be.revertedWithCustomError( + await expect(providersDelegator.setFeeTreasury(ZERO_ADDR)).to.be.revertedWithCustomError( providersDelegator, 'InvalidFeeTreasuryAddress', ); }); - it('should throw error when fee is invalid', async () => { - await expect(providersDelegator.setFee(KYLE, wei(1.01, 25))).to.be.revertedWithCustomError( - providersDelegator, - 'InvalidFee', - ); - }); it('should throw error when caller is not an owner', async () => { - await expect(providersDelegator.connect(KYLE).setFee(KYLE, wei(1.01, 25))).to.be.revertedWith( + await expect(providersDelegator.connect(KYLE).setFeeTreasury(KYLE)).to.be.revertedWith( 'Ownable: caller is not the owner', ); }); @@ -229,14 +231,15 @@ describe('ProvidersDelegator', () => { describe('#claim', () => { beforeEach(async () => { - await providersDelegator.setFee(TREASURY, wei(0.2, 25)); + await setTime(5000); }); - it('should correctly claim, one staker, full claim', async () => { await providersDelegator.connect(KYLE).stake(wei(100)); await token.transfer(providersDelegator, wei(10)); + expect(await providersDelegator.getCurrentStakerRewards(KYLE)).to.eq(wei(10)); + await providersDelegator.connect(KYLE).claim(KYLE, wei(9999)); expect(await token.balanceOf(KYLE)).to.eq(wei(908)); expect(await token.balanceOf(TREASURY)).to.eq(wei(2)); @@ -303,19 +306,6 @@ describe('ProvidersDelegator', () => { expect(await token.balanceOf(SHEV)).to.eq(wei(784)); expect(await token.balanceOf(TREASURY)).to.eq(wei(4 + 4 + 4 + 5 + 13)); }); - it('should correctly claim, full amount without fee', async () => { - await providersDelegator.setFee(TREASURY, wei(0, 25)); - - await providersDelegator.connect(KYLE).stake(wei(100)); - expect(await providersDelegator.getCurrentStakerRewards(KYLE)).to.eq(wei(0)); - - await token.transfer(providersDelegator, wei(10)); - expect(await providersDelegator.getCurrentStakerRewards(KYLE)).to.eq(wei(10)); - - await providersDelegator.connect(KYLE).claim(KYLE, wei(9999)); - expect(await token.balanceOf(KYLE)).to.eq(wei(910)); - expect(await token.balanceOf(TREASURY)).to.eq(wei(0)); - }); it('should throw error when nothing to claim', async () => { await expect(providersDelegator.connect(KYLE).claim(KYLE, wei(999))).to.be.revertedWithCustomError( providersDelegator, @@ -326,9 +316,8 @@ describe('ProvidersDelegator', () => { describe('#restake', () => { beforeEach(async () => { - await providersDelegator.setFee(TREASURY, wei(0.2, 25)); + await setTime(5000); }); - it('should correctly restake, two stakers, full restake', async () => { await providersDelegator.connect(KYLE).stake(wei(100)); await providersDelegator.connect(SHEV).stake(wei(300)); @@ -397,26 +386,22 @@ describe('ProvidersDelegator', () => { }); describe('#providerDeregister', () => { - beforeEach(async () => { - await providersDelegator.setFee(TREASURY, wei(0.2, 25)); - }); - it('should deregister the provider', async () => { await providersDelegator.connect(KYLE).stake(wei(100)); - await providersDelegator.providerDeregister(); + await providersDelegator.providerDeregister([]); await providersDelegator.connect(KYLE).claim(KYLE, wei(9999)); - expect(await token.balanceOf(KYLE)).to.eq(wei(980)); - expect(await token.balanceOf(TREASURY)).to.eq(wei(20)); + expect(await token.balanceOf(KYLE)).to.eq(wei(1000)); + expect(await token.balanceOf(TREASURY)).to.eq(wei(0)); }); it('should throw error when caller is not an owner', async () => { - await expect(providersDelegator.connect(KYLE).providerDeregister()).to.be.revertedWith( + await expect(providersDelegator.connect(KYLE).providerDeregister([])).to.be.revertedWith( 'Ownable: caller is not the owner', ); }); }); - describe('#postModelBid, #deleteModelBid', () => { + describe('#postModelBid, #deleteModelBids', () => { const baseModelId = getHex(Buffer.from('1')); it('should deregister the model bid and delete it', async () => { @@ -433,9 +418,16 @@ describe('ProvidersDelegator', () => { // Register bid await providersDelegator.postModelBid(modelId, wei(0.0001)); - const bidId = await marketplace.getBidId(await providersDelegator.getAddress(), modelId, 0); + let bidId = await marketplace.getBidId(await providersDelegator.getAddress(), modelId, 0); + + await providersDelegator.deleteModelBids([bidId]); + + // Register bid again and deregister not from OWNER + await providersDelegator.postModelBid(modelId, wei(0.0001)); + bidId = await marketplace.getBidId(await providersDelegator.getAddress(), modelId, 1); - await providersDelegator.deleteModelBid(bidId); + await setTime(10000); + await providersDelegator.connect(ALAN).deleteModelBids([bidId]); }); it('should throw error when caller is not an owner', async () => { await expect(providersDelegator.connect(KYLE).postModelBid(baseModelId, wei(0.0001))).to.be.revertedWith( @@ -443,7 +435,7 @@ describe('ProvidersDelegator', () => { ); }); it('should throw error when caller is not an owner', async () => { - await expect(providersDelegator.connect(KYLE).deleteModelBid(baseModelId)).to.be.revertedWith( + await expect(providersDelegator.connect(KYLE).deleteModelBids([baseModelId])).to.be.revertedWith( 'Ownable: caller is not the owner', ); }); @@ -458,9 +450,8 @@ describe('ProvidersDelegator', () => { describe('full flow', () => { const baseModelId = getHex(Buffer.from('1')); - it('should deregister the provider', async () => { + it('should claim correct reward amount', async () => { // Register provider - await providersDelegator.setFee(TREASURY, wei(0.2, 25)); await providersDelegator.connect(KYLE).stake(wei(100)); await providersDelegator.connect(SHEV).stake(wei(300)); @@ -497,6 +488,53 @@ describe('ProvidersDelegator', () => { expect(await token.balanceOf(SHEV)).to.eq(wei(700) + BigInt(Number(reward.toString()) * 0.75 * 0.8)); expect(await token.balanceOf(TREASURY)).to.eq(BigInt(Number(reward.toString()) * 0.2)); }); + + it('should correctly deregister provider without fees', async () => { + await setTime(payoutStart + 1 * DAY); + + // Register provider + await providersDelegator.connect(KYLE).stake(wei(100)); + await providersDelegator.connect(SHEV).stake(wei(300)); + + // Register model + await modelRegistry + .connect(DELEGATOR) + .modelRegister(DELEGATOR, baseModelId, getHex(Buffer.from('ipfs://ipfsaddress')), 0, wei(100), 'name', [ + 'tag_1', + ]); + const modelId = await modelRegistry.getModelId(DELEGATOR, baseModelId); + + // Register bid + await providersDelegator.postModelBid(modelId, wei(0.0001)); + const bidId = await marketplace.getBidId(await providersDelegator.getAddress(), modelId, 0); + + // Open session + await setTime(payoutStart + 10 * DAY); + const { msg, signature } = await getProviderApproval(OWNER, ALAN, bidId); + await sessionRouter.connect(ALAN).openSession(ALAN, wei(50), false, msg, signature); + const sessionId = await sessionRouter.getSessionId(ALAN, providersDelegator, bidId, 0); + + // Close session + await setTime(payoutStart + 15 * DAY); + const { msg: receiptMsg } = await getReceipt(OWNER, sessionId, 0, 0); + const { signature: receiptSig } = await getReceipt(OWNER, sessionId, 0, 0); + await sessionRouter.connect(ALAN).closeSession(receiptMsg, receiptSig); + + // Add the new Staker + await providersDelegator.connect(ALAN).stake(wei(1000)); + + // Deregister the providers + await providersDelegator.connect(KYLE).providerDeregister([bidId]); + + // Claim rewards + await providersDelegator.claim(KYLE, wei(9999)); + await providersDelegator.claim(SHEV, wei(9999)); + await providersDelegator.claim(ALAN, wei(9999)); + expect(await token.balanceOf(KYLE)).to.closeTo(wei(1000), wei(0.1)); + expect(await token.balanceOf(SHEV)).to.closeTo(wei(1000), wei(0.1)); + expect(await token.balanceOf(ALAN)).to.closeTo(wei(1000), wei(0.2)); + expect(await token.balanceOf(TREASURY)).to.eq(wei(0)); + }); }); }); diff --git a/smart-contracts/test/helpers/deployers/delegate/providers-delegator.ts b/smart-contracts/test/helpers/deployers/delegate/providers-delegator.ts index d2d6ce60..41d64de5 100644 --- a/smart-contracts/test/helpers/deployers/delegate/providers-delegator.ts +++ b/smart-contracts/test/helpers/deployers/delegate/providers-delegator.ts @@ -9,6 +9,8 @@ export const deployProvidersDelegator = async ( fee: BigNumberish, name: string, endpoint: string, + deregistrationTimeout: number, + deregistrationNonFeePeriod: number, ): Promise => { const [implFactory, proxyFactory] = await Promise.all([ ethers.getContractFactory('ProvidersDelegator'), @@ -19,7 +21,15 @@ export const deployProvidersDelegator = async ( const proxy = await proxyFactory.deploy(impl, '0x'); const contract = implFactory.attach(proxy) as ProvidersDelegator; - await contract.ProvidersDelegator_init(diamond, feeTreasury, fee, name, endpoint); + await contract.ProvidersDelegator_init( + diamond, + feeTreasury, + fee, + name, + endpoint, + deregistrationTimeout, + deregistrationNonFeePeriod, + ); return contract; };