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

feat: add lido rewards distribution #466

Merged
merged 1 commit into from
Dec 16, 2022
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
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)
})
})