cfx_executor/internal_contract/impls/
params_control.rs

1// Copyright 2019 Conflux Foundation. All rights reserved.
2// Conflux is free software and distributed under GNU General Public License.
3// See http://www.gnu.org/licenses/
4
5use std::convert::TryInto;
6
7use cfx_math::power_two_fractional;
8use cfx_parameters::consensus_internal::DAO_MIN_VOTE_PERCENTAGE;
9use cfx_statedb::Result as DbResult;
10use cfx_types::{Address, U256, U512};
11use cfx_vm_types::{self as vm, ActionParams, Spec};
12use lazy_static::lazy_static;
13
14use super::super::{
15    components::{InternalRefContext, SolidityEventTrait},
16    contracts::params_control::*,
17    impls::staking::get_vote_power,
18};
19use crate::{internal_bail, state::State};
20
21use self::system_storage_key::{
22    current_pos_staking_for_votes, settled_pos_staking_for_votes,
23};
24pub use system_storage_key::storage_point_prop;
25
26pub fn cast_vote(
27    address: Address, version: u64, votes: Vec<Vote>, params: &ActionParams,
28    context: &mut InternalRefContext,
29) -> vm::Result<()> {
30    // If this is called, `env.number` must be larger than the activation
31    // number. And version starts from 1 to tell if an account has ever voted in
32    // the first version.
33    let current_voting_version = (context.env.number
34        - context.spec.cip94_activation_block_number)
35        / context.spec.params_dao_vote_period
36        + 1;
37    if version != current_voting_version {
38        internal_bail!(
39            "vote version unmatch: current={} voted={}",
40            current_voting_version,
41            version
42        );
43    }
44    let old_version =
45        context.storage_at(params, &storage_key::versions(&address))?;
46    let is_new_vote = old_version.as_u64() != version;
47
48    let mut vote_counts = [None; PARAMETER_INDEX_MAX];
49    for vote in votes {
50        if vote.index >= params_index_max(context.spec) as u16 {
51            internal_bail!("invalid vote index or opt_index");
52        }
53        let entry = &mut vote_counts[vote.index as usize];
54        match entry {
55            None => {
56                *entry = Some(vote.votes);
57            }
58            Some(_) => {
59                internal_bail!(
60                    "Parameter voted twice: vote.index={}",
61                    vote.index
62                );
63            }
64        }
65    }
66    if is_new_vote {
67        // If this is the first vote in the version, even for the not-voted
68        // parameters, we still reset the account's votes from previous
69        // versions.
70        for index in 0..params_index_max(context.spec) {
71            if vote_counts[index].is_none() {
72                vote_counts[index] = Some([U256::zero(); OPTION_INDEX_MAX]);
73            }
74        }
75    }
76
77    let vote_power = get_vote_power(
78        address,
79        U256::from(context.env.number),
80        context.env.number,
81        context.state,
82    )?;
83    debug!("vote_power:{}", vote_power);
84    for index in 0..params_index_max(context.spec) {
85        if vote_counts[index].is_none() {
86            continue;
87        }
88        let param_vote = vote_counts[index].unwrap();
89        let total_counts = param_vote[0]
90            .saturating_add(param_vote[1])
91            .saturating_add(param_vote[2]);
92        if total_counts > vote_power {
93            internal_bail!(
94                "not enough vote power: power={} votes={}",
95                vote_power,
96                total_counts
97            );
98        }
99        let mut old_votes = [U256::zero(); 3];
100        for opt_index in 0..OPTION_INDEX_MAX {
101            let vote_slot = storage_key::votes(&address, index, opt_index);
102            let old_vote = if is_new_vote {
103                U256::zero()
104            } else {
105                context.storage_at(params, &vote_slot)?
106            };
107            old_votes[opt_index] = old_vote;
108            debug!(
109                "index:{}, opt_index{}, old_vote: {}, new_vote: {}",
110                index, opt_index, old_vote, param_vote[opt_index]
111            );
112
113            // Update the global votes if the account vote changes.
114            if param_vote[opt_index] != old_vote {
115                let old_total_votes = context.state.get_system_storage(
116                    &CURRENT_VOTES_ENTRIES[index][opt_index],
117                )?;
118                // Should not overflow since we checked the efficiency of voting
119                // power.
120                let new_total_votes =
121                    old_total_votes + param_vote[opt_index] - old_vote;
122
123                debug!(
124                    "old_total_vote: {}, new_total_vote:{}",
125                    old_total_votes, new_total_votes
126                );
127                context.state.set_system_storage(
128                    CURRENT_VOTES_ENTRIES[index][opt_index].to_vec(),
129                    new_total_votes,
130                )?;
131            }
132
133            // Overwrite the account vote entry.
134            context.set_storage(
135                params,
136                vote_slot.to_vec(),
137                param_vote[opt_index],
138            )?;
139        }
140        if !is_new_vote {
141            RevokeEvent::log(
142                &(version, address, index as u16),
143                &old_votes,
144                params,
145                context,
146            )?;
147        }
148        VoteEvent::log(
149            &(version, address, index as u16),
150            &vote_counts[index].as_ref().unwrap(),
151            params,
152            context,
153        )?;
154    }
155    if is_new_vote {
156        context.set_storage(
157            params,
158            storage_key::versions(&address).to_vec(),
159            U256::from(version),
160        )?;
161    }
162    Ok(())
163}
164
165pub fn cast_vote_gas(length: usize, spec: &Spec) -> usize {
166    let version_gas =
167        2 * spec.cold_sload_gas + spec.sha3_gas + spec.sstore_reset_gas;
168
169    let io_gas_per_topic = 3
170        * (2 * spec.cold_sload_gas
171            + 2 * spec.sstore_reset_gas
172            + 2 * spec.sha3_gas);
173
174    let log_gas_per_topic = 2 * spec.log_gas
175        + 8 * spec.log_topic_gas
176        + 32 * 3 * 2 * spec.log_data_gas;
177
178    version_gas + length * (io_gas_per_topic + log_gas_per_topic)
179}
180
181pub fn read_vote(
182    address: Address, params: &ActionParams, context: &mut InternalRefContext,
183) -> vm::Result<Vec<Vote>> {
184    let current_voting_version = (context.env.number
185        - context.spec.cip94_activation_block_number)
186        / context.spec.params_dao_vote_period
187        + 1;
188    let version = context
189        .storage_at(params, &storage_key::versions(&address))?
190        .as_u64();
191    let deprecated_vote = version != current_voting_version;
192
193    let mut votes_list = Vec::new();
194    for index in 0..params_index_max(context.spec) {
195        let mut param_vote = [U256::zero(); OPTION_INDEX_MAX];
196        if !deprecated_vote {
197            for opt_index in 0..OPTION_INDEX_MAX {
198                let votes = context.storage_at(
199                    params,
200                    &storage_key::votes(&address, index, opt_index),
201                )?;
202                param_vote[opt_index] = votes;
203            }
204        }
205        votes_list.push(Vote {
206            index: index as u16,
207            votes: param_vote,
208        })
209    }
210    Ok(votes_list)
211}
212
213pub fn total_votes(
214    version: u64, context: &mut InternalRefContext,
215) -> vm::Result<Vec<Vote>> {
216    let current_voting_version = (context.env.number
217        - context.spec.cip94_activation_block_number)
218        / context.spec.params_dao_vote_period
219        + 1;
220
221    let state = &context.state;
222
223    let votes_entries = if version + 1 == current_voting_version {
224        SETTLED_VOTES_ENTRIES.as_ref()
225    } else if version == current_voting_version {
226        CURRENT_VOTES_ENTRIES.as_ref()
227    } else {
228        internal_bail!(
229            "Unsupport version {} (current {})",
230            version,
231            current_voting_version
232        );
233    };
234
235    let mut answer = vec![];
236    for x in 0..params_index_max(context.spec) {
237        let slot_entry = &votes_entries[x];
238        answer.push(Vote {
239            index: x as u16,
240            votes: [
241                state.get_system_storage(
242                    slot_entry[OPTION_UNCHANGE_INDEX as usize].as_ref(),
243                )?,
244                state.get_system_storage(
245                    slot_entry[OPTION_INCREASE_INDEX as usize].as_ref(),
246                )?,
247                state.get_system_storage(
248                    slot_entry[OPTION_DECREASE_INDEX as usize].as_ref(),
249                )?,
250            ],
251        });
252    }
253
254    Ok(answer)
255}
256
257pub fn pos_stake_for_votes(
258    version: u64, context: &mut InternalRefContext,
259) -> vm::Result<U256> {
260    let current_voting_version = (context.env.number
261        - context.spec.cip94_activation_block_number)
262        / context.spec.params_dao_vote_period
263        + 1;
264
265    let state = &context.state;
266    let pos_stake_entry = if version + 1 == current_voting_version {
267        settled_pos_staking_for_votes()
268    } else if version == current_voting_version {
269        current_pos_staking_for_votes()
270    } else {
271        internal_bail!(
272            "Unsupport version {} (current {})",
273            version,
274            current_voting_version
275        );
276    };
277    Ok(state.get_system_storage(&pos_stake_entry)?)
278}
279
280lazy_static! {
281    static ref CURRENT_VOTES_ENTRIES: [[[u8; 32]; OPTION_INDEX_MAX]; PARAMETER_INDEX_MAX] = {
282        let mut answer: [[[u8; 32]; OPTION_INDEX_MAX]; PARAMETER_INDEX_MAX] =
283            Default::default();
284        for index in 0..PARAMETER_INDEX_MAX {
285            for opt_index in 0..OPTION_INDEX_MAX {
286                answer[index][opt_index] =
287                    system_storage_key::current_votes(index, opt_index);
288            }
289        }
290        answer
291    };
292    static ref SETTLED_VOTES_ENTRIES: [[[u8; 32]; OPTION_INDEX_MAX]; PARAMETER_INDEX_MAX] = {
293        let mut answer: [[[u8; 32]; OPTION_INDEX_MAX]; PARAMETER_INDEX_MAX] =
294            Default::default();
295        for index in 0..PARAMETER_INDEX_MAX {
296            for opt_index in 0..OPTION_INDEX_MAX {
297                answer[index][opt_index] =
298                    system_storage_key::settled_votes(index, opt_index);
299            }
300        }
301        answer
302    };
303}
304
305#[derive(Clone, Copy, Debug, Default)]
306pub struct ParamVoteCount {
307    unchange: U256,
308    increase: U256,
309    decrease: U256,
310}
311
312impl ParamVoteCount {
313    pub fn new(unchange: U256, increase: U256, decrease: U256) -> Self {
314        Self {
315            unchange,
316            increase,
317            decrease,
318        }
319    }
320
321    pub fn from_state<U: AsRef<[u8]>>(
322        state: &State, slot_entry: &[U; 3],
323    ) -> DbResult<Self> {
324        Ok(ParamVoteCount {
325            unchange: state.get_system_storage(
326                slot_entry[OPTION_UNCHANGE_INDEX as usize].as_ref(),
327            )?,
328            increase: state.get_system_storage(
329                &slot_entry[OPTION_INCREASE_INDEX as usize].as_ref(),
330            )?,
331            decrease: state.get_system_storage(
332                &slot_entry[OPTION_DECREASE_INDEX as usize].as_ref(),
333            )?,
334        })
335    }
336
337    pub fn compute_next_params(
338        &self, old_value: U256, pos_staking_for_votes: U256,
339    ) -> U256 {
340        if self.should_update(pos_staking_for_votes) {
341            let answer = self.compute_next_params_inner(old_value);
342            // The return value should be in `[2^8, 2^192]`
343            let min_value = U256::from(256u64);
344            let max_value = U256::one() << 192usize;
345            if answer < min_value {
346                min_value
347            } else if answer > max_value {
348                max_value
349            } else {
350                answer
351            }
352        } else {
353            debug!("params unchanged with pos token {}", pos_staking_for_votes);
354            old_value
355        }
356    }
357
358    fn compute_next_params_inner(&self, old_value: U256) -> U256 {
359        // `VoteCount` only counts valid votes, so this will not overflow.
360        let total = self.unchange + self.increase + self.decrease;
361
362        if total == U256::zero() || self.increase == self.decrease {
363            // If no one votes, we just keep the value unchanged.
364            return old_value;
365        } else if self.increase == total {
366            return old_value * 2u64;
367        } else if self.decrease == total {
368            return old_value / 2u64;
369        };
370
371        let weight = if self.increase > self.decrease {
372            self.increase - self.decrease
373        } else {
374            self.decrease - self.increase
375        };
376        let increase = self.increase > self.decrease;
377
378        let frac_power = (U512::from(weight) << 64u64) / U512::from(total);
379        assert!(frac_power < (U512::one() << 64u64));
380        let frac_power = frac_power.as_u64();
381
382        let ratio = power_two_fractional(frac_power, increase, 96);
383        let new_value = (U512::from(old_value) * U512::from(ratio)) >> 96u64;
384
385        if new_value > (U512::one() << 192u64) {
386            return U256::one() << 192u64;
387        } else {
388            return new_value.try_into().unwrap();
389        }
390    }
391
392    fn should_update(&self, pos_staking_for_votes: U256) -> bool {
393        (self.decrease + self.increase + self.unchange)
394            >= pos_staking_for_votes * DAO_MIN_VOTE_PERCENTAGE / 100
395    }
396}
397
398#[derive(Clone, Copy, Debug, Default)]
399pub struct AllParamsVoteCount {
400    pub pow_base_reward: ParamVoteCount,
401    pub pos_reward_interest: ParamVoteCount,
402    pub storage_point_prop: ParamVoteCount,
403    pub base_fee_prop: ParamVoteCount,
404}
405
406/// If the vote counts are not initialized, all counts will be zero, and the
407/// parameters will be unchanged.
408pub fn get_settled_param_vote_count(
409    state: &State,
410) -> DbResult<AllParamsVoteCount> {
411    let pow_base_reward = ParamVoteCount::from_state(
412        state,
413        &SETTLED_VOTES_ENTRIES[POW_BASE_REWARD_INDEX as usize],
414    )?;
415    let pos_reward_interest = ParamVoteCount::from_state(
416        state,
417        &SETTLED_VOTES_ENTRIES[POS_REWARD_INTEREST_RATE_INDEX as usize],
418    )?;
419    let storage_point_prop = ParamVoteCount::from_state(
420        state,
421        &SETTLED_VOTES_ENTRIES[STORAGE_POINT_PROP_INDEX as usize],
422    )?;
423    let base_fee_prop = ParamVoteCount::from_state(
424        state,
425        &SETTLED_VOTES_ENTRIES[BASEFEE_PROP_INDEX as usize],
426    )?;
427    Ok(AllParamsVoteCount {
428        pow_base_reward,
429        pos_reward_interest,
430        storage_point_prop,
431        base_fee_prop,
432    })
433}
434
435pub fn get_settled_pos_staking_for_votes(state: &State) -> DbResult<U256> {
436    state.get_system_storage(&settled_pos_staking_for_votes())
437}
438
439/// Move the next vote counts into settled and reset the counts.
440/// `set_pos_staking` is for compatibility with the Testnet.
441pub fn settle_current_votes(state: &mut State, cip105: bool) -> DbResult<()> {
442    // Here using `PARAMETER_INDEX_MAX` without knowing the block_number is okay
443    // because if the new parameters have not been enabled, their votes will
444    // be zero and setting them will be no-op.
445    for index in 0..PARAMETER_INDEX_MAX {
446        for opt_index in 0..OPTION_INDEX_MAX {
447            let vote_count = state
448                .get_system_storage(&CURRENT_VOTES_ENTRIES[index][opt_index])?;
449            state.set_system_storage(
450                SETTLED_VOTES_ENTRIES[index][opt_index].to_vec(),
451                vote_count,
452            )?;
453            state.set_system_storage(
454                CURRENT_VOTES_ENTRIES[index][opt_index].to_vec(),
455                U256::zero(),
456            )?;
457        }
458    }
459    if cip105 {
460        let pos_staking =
461            state.get_system_storage(&current_pos_staking_for_votes())?;
462        state.set_system_storage(
463            settled_pos_staking_for_votes().to_vec(),
464            pos_staking,
465        )?;
466        state.set_system_storage(
467            current_pos_staking_for_votes().to_vec(),
468            state.total_pos_staking_tokens(),
469        )?;
470    }
471    Ok(())
472}
473
474pub fn params_index_max(spec: &Spec) -> usize {
475    let mut max = PARAMETER_INDEX_MAX;
476    if !spec.cip1559 {
477        max -= 1;
478    }
479    if !spec.cip107 {
480        max -= 1;
481    }
482    max
483}
484
485/// Solidity variable sequences.
486/// ```solidity
487/// struct VoteInfo {
488///     uint version,
489///     uint[3] pow_base_reward dynamic,
490///     uint[3] pos_interest_rate dynamic,
491/// }
492/// mapping(address => VoteInfo) votes;
493/// ```
494mod storage_key {
495    use cfx_types::{Address, BigEndianHash, H256, U256};
496
497    use super::super::super::components::storage_layout::*;
498
499    const VOTES_SLOT: usize = 0;
500
501    // TODO: add cache to avoid duplicated hash computing
502    pub fn versions(address: &Address) -> [u8; 32] {
503        // Position of `votes`
504        let base = U256::from(VOTES_SLOT);
505
506        // Position of `votes[address]`
507        let address_slot = mapping_slot(base, H256::from(*address).into_uint());
508
509        // Position of `votes[address].version`
510        let version_slot = address_slot;
511
512        return u256_to_array(version_slot);
513    }
514
515    pub fn votes(
516        address: &Address, index: usize, opt_index: usize,
517    ) -> [u8; 32] {
518        const TOPIC_OFFSET: [usize; 4] = [1, 2, 3, 4];
519
520        // Position of `votes`
521        let base = U256::from(VOTES_SLOT);
522
523        // Position of `votes[address]`
524        let address_slot = mapping_slot(base, H256::from(*address).into_uint());
525
526        // Position of `votes[address].<topic>` (static slot)
527        let topic_slot = address_slot + TOPIC_OFFSET[index];
528
529        // Position of `votes[address].<topic>` (dynamic slot)
530        let topic_slot = dynamic_slot(topic_slot);
531
532        // Position of `votes[address].<topic>[opt_index]`
533        let opt_slot = array_slot(topic_slot, opt_index, 1);
534
535        return u256_to_array(opt_slot);
536    }
537}
538
539/// Solidity variable sequences.
540/// ```solidity
541/// struct VoteStats {
542///     uint[3] pow_base_reward dynamic,
543///     uint[3] pos_interest_rate dynamic,
544/// }
545/// VoteStats current_votes dynamic;
546/// VoteStats settled_votes dynamic;
547/// uint current_pos_staking;
548/// uint settled_pos_staking;
549/// ```
550mod system_storage_key {
551    use cfx_parameters::internal_contract_addresses::PARAMS_CONTROL_CONTRACT_ADDRESS;
552    use cfx_types::U256;
553
554    use super::super::super::{
555        components::storage_layout::*, contracts::system_storage::base_slot,
556    };
557
558    const CURRENT_VOTES_SLOT: usize = 0;
559    const SETTLED_VOTES_SLOT: usize = 1;
560    const CURRENT_POS_STAKING_SLOT: usize = 2;
561    const SETTLED_POS_STAKING_SLOT: usize = 3;
562    const STORAGE_POINT_PROP_SLOT: usize = 4;
563
564    fn vote_stats(base: U256, index: usize, opt_index: usize) -> U256 {
565        // Position of `.<topic>` (static slot)
566        let topic_slot = base + index;
567
568        // Position of `.<topic>` (dynamic slot)
569        let topic_slot = dynamic_slot(topic_slot);
570
571        // Position of `.<topic>[opt_index]`
572        return array_slot(topic_slot, opt_index, 1);
573    }
574
575    pub(super) fn current_votes(index: usize, opt_index: usize) -> [u8; 32] {
576        // Position of `current_votes` (static slot)
577        let base = base_slot(PARAMS_CONTROL_CONTRACT_ADDRESS)
578            + U256::from(CURRENT_VOTES_SLOT);
579
580        // Position of `current_votes` (dynamic slot)
581        let base = dynamic_slot(base);
582
583        u256_to_array(vote_stats(base, index, opt_index))
584    }
585
586    pub(super) fn settled_votes(index: usize, opt_index: usize) -> [u8; 32] {
587        // Position of `settled_votes` (static slot)
588        let base = base_slot(PARAMS_CONTROL_CONTRACT_ADDRESS)
589            + U256::from(SETTLED_VOTES_SLOT);
590
591        // Position of `settled_votes` (dynamic slot)
592        let base = dynamic_slot(base);
593
594        u256_to_array(vote_stats(base, index, opt_index))
595    }
596
597    pub(super) fn current_pos_staking_for_votes() -> [u8; 32] {
598        // Position of `current_pos_staking` (static slot)
599        let base = base_slot(PARAMS_CONTROL_CONTRACT_ADDRESS)
600            + U256::from(CURRENT_POS_STAKING_SLOT);
601        u256_to_array(base)
602    }
603
604    pub(super) fn settled_pos_staking_for_votes() -> [u8; 32] {
605        // Position of `settled_pos_staking` (static slot)
606        let base = base_slot(PARAMS_CONTROL_CONTRACT_ADDRESS)
607            + U256::from(SETTLED_POS_STAKING_SLOT);
608        u256_to_array(base)
609    }
610
611    pub fn storage_point_prop() -> [u8; 32] {
612        // Position of `storage_point_prop` (static slot)
613        let base = base_slot(PARAMS_CONTROL_CONTRACT_ADDRESS)
614            + U256::from(STORAGE_POINT_PROP_SLOT);
615        u256_to_array(base)
616    }
617}