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

Happy path rework #364

Merged
merged 2 commits into from
Oct 14, 2021
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
9 changes: 9 additions & 0 deletions contracts/0.4.24/test_helpers/DepositContractMock.sol
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ contract DepositContractMock is IDepositContract {
}

Call[] public calls;
bytes32 internal depositRoot;

function deposit(
bytes /* 48 */ pubkey,
Expand All @@ -40,4 +41,12 @@ contract DepositContractMock is IDepositContract {
function reset() external {
calls.length = 0;
}

function get_deposit_root() external view returns (bytes32) {
return depositRoot;
}

function set_deposit_root(bytes32 _newRoot) external {
depositRoot = _newRoot;
}
}
12 changes: 12 additions & 0 deletions test/helpers/blockchain.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
async function waitBlocks(numBlocksToMine) {
let block
for (let i = 0; i < numBlocksToMine; ++i) {
await network.provider.send('evm_mine')
block = await web3.eth.getBlock('latest')
}
return block
}

module.exports = {
waitBlocks
}
39 changes: 36 additions & 3 deletions test/scenario/helpers/deploy.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,27 @@ const Lido = artifacts.require('LidoMock.sol')
const NodeOperatorsRegistry = artifacts.require('NodeOperatorsRegistry')
const OracleMock = artifacts.require('OracleMock.sol')
const DepositContractMock = artifacts.require('DepositContractMock.sol')
const DepositSecurityModule = artifacts.require('DepositSecurityModule.sol')

module.exports = {
deployDaoAndPool
}

async function deployDaoAndPool(appManager, voting, depositor) {
const NETWORK_ID = 1000
const MAX_DEPOSITS_PER_BLOCK = 100
const MIN_DEPOSIT_BLOCK_DISTANCE = 20
const PAUSE_INTENT_VALIDITY_PERIOD_BLOCKS = 10
const GUARDIAN1 = '0x5Fc0E75BF6502009943590492B02A1d08EAc9C43'
const GUARDIAN2 = '0x8516Cbb5ABe73D775bfc0d21Af226e229F7181A3'
const GUARDIAN3 = '0xdaEAd0E0194abd565d28c1013399801d79627c14'
const GUARDIAN_PRIVATE_KEYS = {
[GUARDIAN1]: '0x3578665169e03e05a26bd5c565ffd12c81a1e0df7d0679f8aee4153110a83c8c',
[GUARDIAN2]: '0x88868f0fb667cfe50261bb385be8987e0ce62faee934af33c3026cf65f25f09e',
[GUARDIAN3]: '0x75e6f508b637327debc90962cd38943ddb9cfc1fc4a8572fc5e3d0984e1261de'
}
const DEPOSIT_ROOT = '0xd151867719c94ad8458feaf491809f9bc8096c702a72747403ecaac30c179137'

async function deployDaoAndPool(appManager, voting) {
// Deploy the DAO, oracle and deposit contract mocks, and base contracts for
// Lido (the pool) and NodeOperatorsRegistry (the Node Operators registry)

Expand All @@ -35,6 +50,18 @@ async function deployDaoAndPool(appManager, voting, depositor) {
NodeOperatorsRegistry.at(nodeOperatorRegistryProxyAddress)
])

const depositSecurityModule = await DepositSecurityModule.new(
pool.address,
depositContractMock.address,
nodeOperatorRegistry.address,
NETWORK_ID,
MAX_DEPOSITS_PER_BLOCK,
MIN_DEPOSIT_BLOCK_DISTANCE,
PAUSE_INTENT_VALIDITY_PERIOD_BLOCKS,
{ from: appManager }
)
await depositSecurityModule.addGuardians([GUARDIAN3, GUARDIAN1, GUARDIAN2], 2, { from: appManager })

// Initialize the node operators registry and the pool
await nodeOperatorRegistry.initialize(pool.address)

Expand Down Expand Up @@ -74,7 +101,7 @@ async function deployDaoAndPool(appManager, voting, depositor) {
acl.createPermission(voting, pool.address, POOL_BURN_ROLE, appManager, { from: appManager }),

// Allow depositor to deposit buffered ether
acl.createPermission(depositor, pool.address, DEPOSIT_ROLE, appManager, { from: appManager }),
acl.createPermission(depositSecurityModule.address, pool.address, DEPOSIT_ROLE, appManager, { from: appManager }),

// Allow voting to manage node operators registry
acl.createPermission(voting, nodeOperatorRegistry.address, NODE_OPERATOR_REGISTRY_MANAGE_SIGNING_KEYS, appManager, {
Expand Down Expand Up @@ -104,6 +131,7 @@ async function deployDaoAndPool(appManager, voting, depositor) {

await oracleMock.setPool(pool.address)
await depositContractMock.reset()
await depositContractMock.set_deposit_root(DEPOSIT_ROOT)

const [treasuryAddr, insuranceAddr] = await Promise.all([pool.getTreasury(), pool.getInsuranceFund()])

Expand All @@ -116,6 +144,11 @@ async function deployDaoAndPool(appManager, voting, depositor) {
pool,
nodeOperatorRegistry,
treasuryAddr,
insuranceAddr
insuranceAddr,
depositSecurityModule,
guardians: {
privateKeys: GUARDIAN_PRIVATE_KEYS,
addresses: [GUARDIAN1, GUARDIAN2, GUARDIAN3]
}
}
}
162 changes: 146 additions & 16 deletions test/scenario/lido_deposit_iteration_limit.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
const { assert } = require('chai')
const { assertBn, assertRevert } = require('@aragon/contract-helpers-test/src/asserts')
const { getEvents, getEventArgument, ZERO_ADDRESS } = require('@aragon/contract-helpers-test')
const { assertBn } = require('@aragon/contract-helpers-test/src/asserts')
const { getEventArgument, ZERO_ADDRESS } = require('@aragon/contract-helpers-test')

const { pad, ETH, hexConcat } = require('../helpers/utils')
const { deployDaoAndPool } = require('./helpers/deploy')
const { newDao } = require('@aragon/toolkit/dist/dao')
const { signDepositData } = require('../0.8.9/helpers/signatures')
const { waitBlocks } = require('../helpers/blockchain')

const NodeOperatorsRegistry = artifacts.require('NodeOperatorsRegistry')

Expand All @@ -18,10 +18,8 @@ contract('Lido: deposit loop iteration limit', (addresses) => {
nodeOperator,
// users who deposit Ether to the pool
user1,
user2,
// an unrelated address
nobody,
depositor
nobody
] = addresses

// Limits the number of validators assigned in a single transaction, regardless the amount
Expand All @@ -31,9 +29,10 @@ contract('Lido: deposit loop iteration limit', (addresses) => {
const depositIterationLimit = 5

let pool, nodeOperatorRegistry, depositContractMock
let depositSecurityModule, depositRoot, guardians

it('DAO, node operators registry, token, and pool are deployed and initialized', async () => {
const deployed = await deployDaoAndPool(appManager, voting, depositor)
it('DAO, node operators registry, token, pool and deposit security module are deployed and initialized', async () => {
const deployed = await deployDaoAndPool(appManager, voting)

// contracts/Lido.sol
pool = deployed.pool
Expand All @@ -44,6 +43,10 @@ contract('Lido: deposit loop iteration limit', (addresses) => {
// mocks
depositContractMock = deployed.depositContractMock

depositSecurityModule = deployed.depositSecurityModule
guardians = deployed.guardians
depositRoot = await depositContractMock.get_deposit_root()

await pool.setFee(0.01 * 10000, { from: voting })
await pool.setFeeDistribution(0.3 * 10000, 0.2 * 10000, 0.5 * 10000, { from: voting })
await pool.setWithdrawalCredentials(pad('0x0202', 32), { from: voting })
Expand Down Expand Up @@ -90,23 +93,90 @@ contract('Lido: deposit loop iteration limit', (addresses) => {
assertBn(ether2Stat.depositedValidators, 0, 'deposited validators')
})

it('one can assign the buffered ether to validators by calling depositBufferedEther() and passing deposit iteration limit', async () => {
it('guardians can assign the buffered ether to validators by calling depositBufferedEther() and passing deposit iteration limit', async () => {
const depositIterationLimit = 5
await pool.depositBufferedEther(depositIterationLimit, { from: depositor })
let bufferedEther = await pool.getBufferedEther()
console.log('Buffered Ether:', bufferedEther.toString())

const block = await web3.eth.getBlock('latest')
const keysOpIndex = await nodeOperatorRegistry.getKeysOpIndex()
const signatures = [
signDepositData(
await depositSecurityModule.ATTEST_MESSAGE_PREFIX(),
depositRoot,
keysOpIndex,
block.number,
block.hash,
guardians.privateKeys[guardians.addresses[0]]
),
signDepositData(
await depositSecurityModule.ATTEST_MESSAGE_PREFIX(),
depositRoot,
keysOpIndex,
block.number,
block.hash,
guardians.privateKeys[guardians.addresses[1]]
)
]
await depositSecurityModule.depositBufferedEther(depositIterationLimit, depositRoot, keysOpIndex, block.number, block.hash, signatures)

// no more than depositIterationLimit validators are assigned in a single transaction
assertBn(await depositContractMock.totalCalls(), 5, 'total validators assigned')

const ether2Stat = await pool.getBeaconStat()
assertBn(ether2Stat.depositedValidators, 5, 'deposited validators')

bufferedEther = await pool.getBufferedEther()

// the rest of the received Ether is still buffered in the pool
assertBn(await pool.getBufferedEther(), ETH(15 * 32), 'buffered ether')
})

it('one can advance the deposit loop further by calling depositBufferedEther() once again', async () => {
it('guardians can advance the deposit loop further by calling depositBufferedEther() once again', async () => {
const depositIterationLimit = 10
await pool.depositBufferedEther(depositIterationLimit, { from: depositor })

let block = await waitBlocks(await depositSecurityModule.getMinDepositBlockDistance())
let keysOpIndex = await nodeOperatorRegistry.getKeysOpIndex()
let signatures = [
signDepositData(
await depositSecurityModule.ATTEST_MESSAGE_PREFIX(),
depositRoot,
keysOpIndex,
block.number,
block.hash,
guardians.privateKeys[guardians.addresses[0]]
),
signDepositData(
await depositSecurityModule.ATTEST_MESSAGE_PREFIX(),
depositRoot,
keysOpIndex,
block.number,
block.hash,
guardians.privateKeys[guardians.addresses[1]]
)
]
await depositSecurityModule.depositBufferedEther(depositIterationLimit, depositRoot, keysOpIndex, block.number, block.hash, signatures)

block = await waitBlocks(await depositSecurityModule.getMinDepositBlockDistance())
keysOpIndex = await nodeOperatorRegistry.getKeysOpIndex()
signatures = [
signDepositData(
await depositSecurityModule.ATTEST_MESSAGE_PREFIX(),
depositRoot,
keysOpIndex,
block.number,
block.hash,
guardians.privateKeys[guardians.addresses[0]]
),
signDepositData(
await depositSecurityModule.ATTEST_MESSAGE_PREFIX(),
depositRoot,
keysOpIndex,
block.number,
block.hash,
guardians.privateKeys[guardians.addresses[1]]
)
]

assertBn(await depositContractMock.totalCalls(), 15, 'total validators assigned')

Expand All @@ -118,7 +188,27 @@ contract('Lido: deposit loop iteration limit', (addresses) => {

it('the number of assigned validators is limited by the remaining ether', async () => {
const depositIterationLimit = 10
await pool.depositBufferedEther(depositIterationLimit, { from: depositor })
const block = await waitBlocks(await depositSecurityModule.getMinDepositBlockDistance())
const keysOpIndex = await nodeOperatorRegistry.getKeysOpIndex()
const signatures = [
signDepositData(
await depositSecurityModule.ATTEST_MESSAGE_PREFIX(),
depositRoot,
keysOpIndex,
block.number,
block.hash,
guardians.privateKeys[guardians.addresses[0]]
),
signDepositData(
await depositSecurityModule.ATTEST_MESSAGE_PREFIX(),
depositRoot,
keysOpIndex,
block.number,
block.hash,
guardians.privateKeys[guardians.addresses[1]]
)
]
await depositSecurityModule.depositBufferedEther(depositIterationLimit, depositRoot, keysOpIndex, block.number, block.hash, signatures)

assertBn(await depositContractMock.totalCalls(), 20)

Expand All @@ -139,7 +229,27 @@ contract('Lido: deposit loop iteration limit', (addresses) => {

it('the number of assigned validators is still limited by the number of available validator keys', async () => {
const depositIterationLimit = 10
await pool.depositBufferedEther(depositIterationLimit, { from: depositor })
const block = await waitBlocks(await depositSecurityModule.getMinDepositBlockDistance())
const keysOpIndex = await nodeOperatorRegistry.getKeysOpIndex()
const signatures = [
signDepositData(
await depositSecurityModule.ATTEST_MESSAGE_PREFIX(),
depositRoot,
keysOpIndex,
block.number,
block.hash,
guardians.privateKeys[guardians.addresses[0]]
),
signDepositData(
await depositSecurityModule.ATTEST_MESSAGE_PREFIX(),
depositRoot,
keysOpIndex,
block.number,
block.hash,
guardians.privateKeys[guardians.addresses[1]]
)
]
await depositSecurityModule.depositBufferedEther(depositIterationLimit, depositRoot, keysOpIndex, block.number, block.hash, signatures)

assertBn(await depositContractMock.totalCalls(), 21)

Expand All @@ -152,7 +262,27 @@ contract('Lido: deposit loop iteration limit', (addresses) => {

it('depositBufferedEther is a nop if there are no signing keys available', async () => {
const depositIterationLimit = 10
await pool.depositBufferedEther(depositIterationLimit, { from: depositor })
const block = await waitBlocks(await depositSecurityModule.getMinDepositBlockDistance())
const keysOpIndex = await nodeOperatorRegistry.getKeysOpIndex()
const signatures = [
signDepositData(
await depositSecurityModule.ATTEST_MESSAGE_PREFIX(),
depositRoot,
keysOpIndex,
block.number,
block.hash,
guardians.privateKeys[guardians.addresses[0]]
),
signDepositData(
await depositSecurityModule.ATTEST_MESSAGE_PREFIX(),
depositRoot,
keysOpIndex,
block.number,
block.hash,
guardians.privateKeys[guardians.addresses[1]]
)
]
await depositSecurityModule.depositBufferedEther(depositIterationLimit, depositRoot, keysOpIndex, block.number, block.hash, signatures)

assertBn(await depositContractMock.totalCalls(), 21, 'total validators assigned')

Expand Down
Loading