forked from lidofinance/core
-
Notifications
You must be signed in to change notification settings - Fork 0
/
lido_deposit_iteration_limit.js
159 lines (117 loc) · 6.38 KB
/
lido_deposit_iteration_limit.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
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 { pad, ETH, hexConcat } = require('../helpers/utils')
const { deployDaoAndPool } = require('./helpers/deploy')
const NodeOperatorsRegistry = artifacts.require('NodeOperatorsRegistry')
contract('Lido: deposit loop iteration limit', (addresses) => {
const [
// the root account which deployed the DAO
appManager,
// the address which we use to simulate the voting DAO application
voting,
// node operators
nodeOperator,
// users who deposit Ether to the pool
user1,
user2,
// an unrelated address
nobody
] = addresses
// Limits the number of validators assigned in a single transaction, regardless the amount
// of Ether submitted to/buffered in the contract and the number of spare validator keys.
// This is needed to prevent the deposit loop from failing due to it using more gas than
// available in a single block and to protect from possible attacks exploiting this.
const depositIterationLimit = 5
let pool, nodeOperatorRegistry, depositContractMock
it('DAO, node operators registry, token, and pool are deployed and initialized', async () => {
const deployed = await deployDaoAndPool(appManager, voting)
// contracts/Lido.sol
pool = deployed.pool
// contracts/nos/NodeOperatorsRegistry.sol
nodeOperatorRegistry = deployed.nodeOperatorRegistry
// mocks
depositContractMock = deployed.depositContractMock
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 })
})
it('voting adds a node operator with 16 signing keys', async () => {
const validatorsLimit = 1000
const numKeys = 21
const txn = await nodeOperatorRegistry.addNodeOperator('operator_1', nodeOperator, validatorsLimit, { from: voting })
// Some Truffle versions fail to decode logs here, so we're decoding them explicitly using a helper
const nodeOperatorId = getEventArgument(txn, 'NodeOperatorAdded', 'id', { decodeForAbi: NodeOperatorsRegistry._json.abi })
assertBn(await nodeOperatorRegistry.getNodeOperatorsCount(), 1, 'total node operators')
const data = Array.from({ length: numKeys }, (_, i) => {
const n = 1 + 10 * i
return {
key: pad(`0x${n.toString(16)}`, 48),
sig: pad(`0x${n.toString(16)}`, 96)
}
})
const keys = hexConcat(...data.map((v) => v.key))
const sigs = hexConcat(...data.map((v) => v.sig))
await nodeOperatorRegistry.addSigningKeysOperatorBH(nodeOperatorId, numKeys, keys, sigs, { from: nodeOperator })
const totalKeys = await nodeOperatorRegistry.getTotalSigningKeyCount(nodeOperatorId, { from: nobody })
assertBn(totalKeys, numKeys, 'total signing keys')
})
it('a user submits 20 * 32 ETH', async () => {
const referral = ZERO_ADDRESS
await pool.submit(referral, { from: user1, value: ETH(20 * 32) })
assertBn(await pool.getTotalPooledEther(), ETH(20 * 32), 'total controlled ether')
// at this point, no deposit assignments were made and all ether is buffered
assertBn(await pool.getBufferedEther(), ETH(20 * 32), 'buffered ether')
const ether2Stat = await pool.getBeaconStat()
assertBn(ether2Stat.depositedValidators, 0, 'deposited validators')
})
it('one can assign the buffered ether to validators by calling depositBufferedEther() and passing deposit iteration limit', async () => {
const depositIterationLimit = 5
await pool.depositBufferedEther(depositIterationLimit)
// 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')
// 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 () => {
const depositIterationLimit = 10
await pool.depositBufferedEther(depositIterationLimit)
assertBn(await depositContractMock.totalCalls(), 15, 'total validators assigned')
const ether2Stat = await pool.getBeaconStat()
assertBn(ether2Stat.depositedValidators, 15, 'deposited validators')
assertBn(await pool.getBufferedEther(), ETH(5 * 32), 'buffered ether')
})
it('the number of assigned validators is limited by the remaining ether', async () => {
const depositIterationLimit = 10
await pool.depositBufferedEther(depositIterationLimit)
assertBn(await depositContractMock.totalCalls(), 20)
const ether2Stat = await pool.getBeaconStat()
assertBn(ether2Stat.depositedValidators, 20, 'deposited validators')
// the is no ether left buffered in the pool
assertBn(await pool.getBufferedEther(), ETH(0), 'buffered ether')
})
it('a user submits 2 * 32 ETH', async () => {
const referral = ZERO_ADDRESS
await pool.submit(referral, { from: user1, value: ETH(2 * 32) })
assertBn(await pool.getTotalPooledEther(), ETH(22 * 32), 'total controlled ether')
assertBn(await pool.getBufferedEther(), ETH(2 * 32), 'buffered ether')
})
it('the number of assigned validators is still limited by the number of available validator keys', async () => {
const depositIterationLimit = 10
await pool.depositBufferedEther(depositIterationLimit)
assertBn(await depositContractMock.totalCalls(), 21)
const ether2Stat = await pool.getBeaconStat()
assertBn(ether2Stat.depositedValidators, 21, 'deposited validators')
// the rest of the received Ether is still buffered in the pool
assertBn(await pool.getBufferedEther(), ETH(1 * 32), 'buffered ether')
})
it('depositBufferedEther is a nop if there are no signing keys available', async () => {
const depositIterationLimit = 10
await pool.depositBufferedEther(depositIterationLimit)
assertBn(await depositContractMock.totalCalls(), 21, 'total validators assigned')
// the rest of the received Ether is still buffered in the pool
assertBn(await pool.getBufferedEther(), ETH(1 * 32), 'buffered ether')
})
})