Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
26 changes: 18 additions & 8 deletions protocol-contracts/staking/contracts/ProtocolStaking.sol
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ interface IERC20Mintable is IERC20 {

/**
* @dev Staking contract that distributes newly minted tokens to eligible accounts at a configurable flow rate.
*
*
* NOTE: This staking contract does not support non-standard ERC-20 tokens such as fee-on-transfer or rebasing tokens.
* @custom:security-contact [email protected]
*/
Expand Down Expand Up @@ -91,7 +91,8 @@ contract ProtocolStaking is AccessControlDefaultAdminRulesUpgradeable, ERC20Vote
address governor,
address upgrader,
address manager,
uint48 initialUnstakeCooldownPeriod
uint48 initialUnstakeCooldownPeriod,
uint256 initialRewardRate
) public initializer {
__AccessControlDefaultAdminRules_init(0, governor);
_grantRole(UPGRADER_ROLE, upgrader);
Expand All @@ -101,6 +102,7 @@ contract ProtocolStaking is AccessControlDefaultAdminRulesUpgradeable, ERC20Vote
__EIP712_init(name, version);
_getProtocolStakingStorage()._stakingToken = stakingToken_;
_setUnstakeCooldownPeriod(initialUnstakeCooldownPeriod);
_setRewardRate(initialRewardRate);
}

/**
Expand Down Expand Up @@ -173,12 +175,7 @@ contract ProtocolStaking is AccessControlDefaultAdminRulesUpgradeable, ERC20Vote
* @param rewardRate_ The new reward rate in tokens per second.
*/
function setRewardRate(uint256 rewardRate_) public onlyRole(MANAGER_ROLE) {
ProtocolStakingStorage storage $ = _getProtocolStakingStorage();
$._lastUpdateReward = _historicalReward();
$._lastUpdateTimestamp = Time.timestamp();
$._rewardRate = rewardRate_;

emit RewardRateSet(rewardRate_);
_setRewardRate(rewardRate_);
}

/**
Expand Down Expand Up @@ -322,6 +319,19 @@ contract ProtocolStaking is AccessControlDefaultAdminRulesUpgradeable, ERC20Vote
emit UnstakeCooldownPeriodSet(unstakeCooldownPeriod_);
}

/**
* @dev Sets the reward rate in tokens per second.
* @param rewardRate_ The new reward rate in tokens per second.
*/
function _setRewardRate(uint256 rewardRate_) internal {
ProtocolStakingStorage storage $ = _getProtocolStakingStorage();
$._lastUpdateReward = _historicalReward();
$._lastUpdateTimestamp = Time.timestamp();
$._rewardRate = rewardRate_;

emit RewardRateSet(rewardRate_);
}

function _updateRewards(address user, uint256 weightBefore, uint256 weightAfter) internal {
ProtocolStakingStorage storage $ = _getProtocolStakingStorage();
uint256 oldTotalWeight = $._totalEligibleStakedWeight;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ async function deployProtocolStaking(
symbol: string,
version: string,
cooldown: number,
rewardRate: bigint,
hre: HardhatRuntimeEnvironment,
) {
const { getNamedAccounts, ethers, deployments, upgrades, network } = hre;
Expand All @@ -39,7 +40,7 @@ async function deployProtocolStaking(
const protocolStakingFactory = await ethers.getContractFactory(PROTOCOL_STAKING_CONTRACT_NAME, deployerSigner);
const proxy = await upgrades.deployProxy(
protocolStakingFactory,
[tokenName, symbol, version, zamaTokenAddress, deployer, daoAddress, deployer, cooldown],
[tokenName, symbol, version, zamaTokenAddress, deployer, daoAddress, deployer, cooldown, rewardRate],
{ kind: 'uups', initializer: 'initialize' },
);
await proxy.waitForDeployment();
Expand Down Expand Up @@ -74,9 +75,10 @@ task('task:deployProtocolStakingCopro').setAction(async function (_, hre) {
const coproTokenSymbol = getRequiredEnvVar('PROTOCOL_STAKING_COPRO_TOKEN_SYMBOL');
const coproVersion = getRequiredEnvVar('PROTOCOL_STAKING_COPRO_VERSION');
const coproCooldown = parseInt(getRequiredEnvVar('PROTOCOL_STAKING_COPRO_COOLDOWN_PERIOD'));
const coproRewardRate = BigInt(parseInt(getRequiredEnvVar('PROTOCOL_STAKING_COPRO_REWARD_RATE')));

// Deploy the coprocessor protocol staking contract
await deployProtocolStaking(coproTokenName, coproTokenSymbol, coproVersion, coproCooldown, hre);
await deployProtocolStaking(coproTokenName, coproTokenSymbol, coproVersion, coproCooldown, coproRewardRate, hre);
});

// Deploy the KMS ProtocolStaking contracts
Expand All @@ -90,9 +92,10 @@ task('task:deployProtocolStakingKMS').setAction(async function (_, hre) {
const kmsTokenSymbol = getRequiredEnvVar('PROTOCOL_STAKING_KMS_TOKEN_SYMBOL');
const kmsVersion = getRequiredEnvVar('PROTOCOL_STAKING_KMS_VERSION');
const kmsCooldown = parseInt(getRequiredEnvVar('PROTOCOL_STAKING_KMS_COOLDOWN_PERIOD'));
const kmsRewardRate = BigInt(parseInt(getRequiredEnvVar('PROTOCOL_STAKING_KMS_REWARD_RATE')));

// Deploy the KMS protocol staking contract
await deployProtocolStaking(kmsTokenName, kmsTokenSymbol, kmsVersion, kmsCooldown, hre);
await deployProtocolStaking(kmsTokenName, kmsTokenSymbol, kmsVersion, kmsCooldown, kmsRewardRate, hre);
});

// Deploy the ProtocolStaking contracts
Expand Down
28 changes: 13 additions & 15 deletions protocol-contracts/staking/test/OperatorRewarder.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,20 +12,19 @@ describe('OperatorRewarder', function () {
const [staker1, staker2, admin, anyone, ...accounts] = await ethers.getSigners();

const token = await ethers.deployContract('$ERC20Mock', ['StakingToken', 'ST', 18]);
const protocolStaking = await ethers
.getContractFactory('ProtocolStakingSlashingMock')
.then(factory =>
upgrades.deployProxy(factory, [
'StakedToken',
'SST',
'1',
token.target,
admin.address,
admin.address,
admin.address,
60 /* 1 min */,
]),
);
const protocolStaking = await ethers.getContractFactory('ProtocolStakingSlashingMock').then(factory =>
upgrades.deployProxy(factory, [
'StakedToken',
'SST',
'1',
token.target,
admin.address,
admin.address,
admin.address,
60 /* 1 min */, // unstake cooldown period
ethers.parseEther('0.5'), // reward rate
]),
);
const operatorStaking = await ethers.deployContract('$OperatorStaking', [
'OPStake',
'OP',
Expand All @@ -43,7 +42,6 @@ describe('OperatorRewarder', function () {
);

await protocolStaking.connect(admin).addEligibleAccount(operatorStaking);
await protocolStaking.connect(admin).setRewardRate(ethers.parseEther('0.5'));

Object.assign(this, { staker1, staker2, admin, anyone, accounts, token, operatorStaking, protocolStaking, mock });
});
Expand Down
27 changes: 13 additions & 14 deletions protocol-contracts/staking/test/OperatorStaking.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,20 +11,19 @@ describe('OperatorStaking', function () {
const [staker1, staker2, admin, ...accounts] = await ethers.getSigners();

const token = await ethers.deployContract('$ERC20Mock', ['StakingToken', 'ST', 18]);
const protocolStaking = await ethers
.getContractFactory('ProtocolStakingSlashingMock')
.then(factory =>
upgrades.deployProxy(factory, [
'StakedToken',
'SST',
'1',
token.target,
admin.address,
admin.address,
admin.address,
60 /* 1 min */,
]),
);
const protocolStaking = await ethers.getContractFactory('ProtocolStakingSlashingMock').then(factory =>
upgrades.deployProxy(factory, [
'StakedToken',
'SST',
'1',
token.target,
admin.address,
admin.address,
admin.address,
60 /* 1 min */, // unstake cooldown period
0n, // reward rate
]),
);
const mock = await ethers.deployContract('$OperatorStaking', ['OPStake', 'OP', protocolStaking, admin.address]);

await Promise.all(
Expand Down
29 changes: 14 additions & 15 deletions protocol-contracts/staking/test/ProtocolStaking.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { anyValue } from '@nomicfoundation/hardhat-chai-matchers/withArgs';
import { mine, time } from '@nomicfoundation/hardhat-network-helpers';
import { time } from '@nomicfoundation/hardhat-network-helpers';
import { expect } from 'chai';
import { ethers, upgrades } from 'hardhat';

Expand All @@ -12,20 +12,19 @@ describe('Protocol Staking', function () {
beforeEach(async function () {
const [staker1, staker2, admin, upgrader, manager, anyone, ...accounts] = await ethers.getSigners();
const token = await ethers.deployContract('$ERC20Mock', ['StakingToken', 'ST', 18]);
const mock = await ethers
.getContractFactory('ProtocolStaking')
.then(factory =>
upgrades.deployProxy(factory, [
'StakedToken',
'SST',
'1',
token.target,
admin.address,
upgrader.address,
manager.address,
1,
]),
);
const mock = await ethers.getContractFactory('ProtocolStaking').then(factory =>
upgrades.deployProxy(factory, [
'StakedToken',
'SST',
'1',
token.target,
admin.address,
upgrader.address,
manager.address,
1, // unstake cooldown period
0n, // reward rate
]),
);

await Promise.all(
[staker1, staker2].flatMap(account => [
Expand Down