Skip to content

Commit

Permalink
feat: add lido rewards distribution
Browse files Browse the repository at this point in the history
  • Loading branch information
rkolpakov committed Dec 16, 2022
1 parent a449467 commit 6559556
Show file tree
Hide file tree
Showing 2 changed files with 165 additions and 10 deletions.
36 changes: 26 additions & 10 deletions contracts/0.4.24/Lido.sol
Original file line number Diff line number Diff line change
Expand Up @@ -744,20 +744,36 @@ contract Lido is ILido, StETH, AragonApp {
// token shares.

address stakingRouterAddress = getStakingRouter();
(
address[] memory moduleAddresses,
uint256[] memory moduleShares,
uint256[] memory moduleFees,
uint256[] memory moduleTreasuryFees
) = IStakingRouter(stakingRouterAddress).getSharesTable();

address treasury = getTreasury();
uint256 rewards2mint = 0;
uint256[] memory moduleRewards = new uint256[](moduleAddresses.length);

for (uint256 i = 0; i < moduleAddresses.length; i++) {
uint256 moduleShare = _totalRewards * moduleShares[i] / TOTAL_BASIS_POINTS;

moduleRewards[i] = moduleShare * moduleFees[i] / TOTAL_BASIS_POINTS;
rewards2mint += moduleShare * moduleTreasuryFees[i] / TOTAL_BASIS_POINTS + moduleRewards[i];
}

uint256 shares2mint = rewards2mint.mul(_getTotalShares()).div(_getTotalPooledEther().sub(rewards2mint));

// address modulefee treasuryfee

// (uint256 shares2mint, uint256 totalKeys, uint256[] memory moduleKeys) = IStakingRouter(stakingRouterAddress).calculateShares2Mint(
// _totalRewards
// );
_mintShares(address(this), shares2mint);

// // Mint the calculated amount of shares to this contract address. This will reduce the
// // balances of the holders, as if the fee was taken in parts from each of them.
// _mintShares(stakingRouterAddress, shares2mint);
for (uint256 j = 0; j < moduleAddresses.length; j++) {
uint256 moduleRewardInShares = getSharesByPooledEth(moduleRewards[j]);
shares2mint -= moduleRewardInShares;

// //distribute shares
// IStakingRouter(stakingRouterAddress).distributeShares(shares2mint, totalKeys, moduleKeys);
_transferShares(address(this), moduleAddresses[j], moduleRewardInShares);
}

_transferShares(address(this), treasury, shares2mint);
}

/**
Expand Down
139 changes: 139 additions & 0 deletions test/0.4.24/lido.rewards-distribution.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
const { assert } = require('chai')
const { newDao, newApp } = require('./helpers/dao')
const { assertBn, assertRevert, assertEvent } = require('@aragon/contract-helpers-test/src/asserts')
const { ZERO_ADDRESS, bn } = require('@aragon/contract-helpers-test')

const NodeOperatorsRegistry = artifacts.require('NodeOperatorsRegistryMock')

const Lido = artifacts.require('LidoMock.sol')
const OracleMock = artifacts.require('OracleMock.sol')
const DepositContractMock = artifacts.require('DepositContractMock.sol')
const StakingRouter = artifacts.require('StakingRouter.sol')
const ModuleSolo = artifacts.require('ModuleSolo.sol')

const TOTAL_BASIS_POINTS = 10000
const ETH = (value) => web3.utils.toWei(value + '', 'ether')

contract('Lido', ([appManager, voting, user2, depositor]) => {
let appBase, nodeOperatorsRegistryBase, app, oracle, depositContract, curatedModule, stakingRouter, soloModule
let treasuryAddr
let dao, acl

before('deploy base app', async () => {
// Deploy the app's base contract.
appBase = await Lido.new()
oracle = await OracleMock.new()
depositContract = await DepositContractMock.new()
nodeOperatorsRegistryBase = await NodeOperatorsRegistry.new()
})

beforeEach('deploy dao and app', async () => {
;({ dao, acl } = await newDao(appManager))

// Instantiate a proxy for the app, using the base contract as its logic implementation.
let proxyAddress = await newApp(dao, 'lido', appBase.address, appManager)
app = await Lido.at(proxyAddress)

// NodeOperatorsRegistry
proxyAddress = await newApp(dao, 'node-operators-registry', nodeOperatorsRegistryBase.address, appManager)
curatedModule = await NodeOperatorsRegistry.at(proxyAddress)
await curatedModule.initialize(app.address)

// Set up the app's permissions.
await acl.createPermission(voting, app.address, await app.PAUSE_ROLE(), appManager, { from: appManager })
await acl.createPermission(voting, app.address, await app.RESUME_ROLE(), appManager, { from: appManager })
await acl.createPermission(voting, app.address, await app.MANAGE_FEE(), appManager, { from: appManager })
await acl.createPermission(voting, app.address, await app.MANAGE_WITHDRAWAL_KEY(), appManager, { from: appManager })
await acl.createPermission(voting, app.address, await app.BURN_ROLE(), appManager, { from: appManager })
await acl.createPermission(voting, app.address, await app.MANAGE_PROTOCOL_CONTRACTS_ROLE(), appManager, { from: appManager })
await acl.createPermission(voting, app.address, await app.SET_EL_REWARDS_VAULT_ROLE(), appManager, { from: appManager })
await acl.createPermission(voting, app.address, await app.SET_EL_REWARDS_WITHDRAWAL_LIMIT_ROLE(), appManager, {
from: appManager
})
await acl.createPermission(voting, app.address, await app.STAKING_PAUSE_ROLE(), appManager, { from: appManager })
await acl.createPermission(voting, app.address, await app.STAKING_CONTROL_ROLE(), appManager, { from: appManager })

await acl.createPermission(voting, curatedModule.address, await curatedModule.MANAGE_SIGNING_KEYS(), appManager, { from: appManager })
await acl.createPermission(voting, curatedModule.address, await curatedModule.ADD_NODE_OPERATOR_ROLE(), appManager, {
from: appManager
})
await acl.createPermission(voting, curatedModule.address, await curatedModule.SET_NODE_OPERATOR_ACTIVE_ROLE(), appManager, {
from: appManager
})
await acl.createPermission(voting, curatedModule.address, await curatedModule.SET_NODE_OPERATOR_NAME_ROLE(), appManager, {
from: appManager
})
await acl.createPermission(voting, curatedModule.address, await curatedModule.SET_NODE_OPERATOR_ADDRESS_ROLE(), appManager, {
from: appManager
})
await acl.createPermission(voting, curatedModule.address, await curatedModule.SET_NODE_OPERATOR_LIMIT_ROLE(), appManager, {
from: appManager
})
await acl.createPermission(voting, curatedModule.address, await curatedModule.REPORT_STOPPED_VALIDATORS_ROLE(), appManager, {
from: appManager
})
await acl.createPermission(depositor, app.address, await app.DEPOSIT_ROLE(), appManager, { from: appManager })

// Initialize the app's proxy.
await app.initialize(depositContract.address, oracle.address, curatedModule.address)

assert((await app.isStakingPaused()) === true)
assert((await app.isStopped()) === true)
await app.resume({ from: voting })
assert((await app.isStakingPaused()) === false)
assert((await app.isStopped()) === false)

treasuryAddr = await app.getTreasury()

await oracle.setPool(app.address)
await depositContract.reset()
})

beforeEach('setup staking router', async () => {
stakingRouter = await StakingRouter.new(app.address, depositContract.address)
await app.setStakingRouter(stakingRouter.address)

soloModule = await ModuleSolo.new(1, app.address, 500, { from: appManager })

await stakingRouter.addModule('Curated', curatedModule.address, 0, 500, { from: appManager })
await curatedModule.setTotalKeys(100, { from: appManager })
await curatedModule.setTotalUsedKeys(50, { from: appManager })
await curatedModule.setTotalStoppedKeys(100, { from: appManager })

await stakingRouter.addModule('Solo', soloModule.address, 0, 500, { from: appManager })
await soloModule.setTotalKeys(100, { from: appManager })
await soloModule.setTotalUsedKeys(50, { from: appManager })
await soloModule.setTotalStoppedKeys(100, { from: appManager })

stakingModules = [curatedModule, soloModule]
})

it('Rewards distribution fills treasury', async () => {
const depositAmount = ETH(1)
const treasuryRewards = (depositAmount * 500) / TOTAL_BASIS_POINTS

await app.submit(ZERO_ADDRESS, { from: user2, value: ETH(32) })

const treasuryBalanceBefore = await app.balanceOf(treasuryAddr)
await oracle.reportBeacon(100, 0, depositAmount, { from: appManager })

const treasuryBalanceAfter = await app.balanceOf(treasuryAddr)
assertBn(treasuryBalanceBefore.add(bn(treasuryRewards)).sub(bn(1)), treasuryBalanceAfter)
})

it('Rewards distribution fills modules', async () => {
const depositAmount = ETH(1)
const { modulesShares } = await stakingRouter.getSharesTable()
const moduleFee = (depositAmount * modulesShares[0]) / TOTAL_BASIS_POINTS
const rewards = (moduleFee * (await soloModule.getFee())) / TOTAL_BASIS_POINTS

await app.submit(ZERO_ADDRESS, { from: user2, value: ETH(32) })

const moduleBalanceBefore = await app.balanceOf(soloModule.address)

await oracle.reportBeacon(100, 0, depositAmount, { from: appManager })

const moduleBalanceAfter = await app.balanceOf(soloModule.address)
assertBn(moduleBalanceBefore.add(bn(rewards).sub(bn(1))), moduleBalanceAfter)
})
})

0 comments on commit 6559556

Please sign in to comment.