cfx_executor/state/state_object/
collateral.rs

1use super::{State, Substate};
2use crate::{
3    executive_observer::TracerTrait, internal_contract::storage_point_prop,
4    return_if, try_loaded,
5};
6use cfx_parameters::{
7    consensus_internal::CIP107_STORAGE_POINT_PROP_INIT,
8    staking::DRIPS_PER_STORAGE_COLLATERAL_UNIT,
9};
10use cfx_statedb::{global_params::*, Result as DbResult};
11use cfx_types::{address_util::AddressUtil, Address, AddressSpaceUtil, U256};
12use cfx_vm_types::{self as vm, Spec};
13
14impl State {
15    pub fn collateral_for_storage(&self, address: &Address) -> DbResult<U256> {
16        let acc = try_loaded!(self.read_native_account_lock(address));
17        Ok(acc.collateral_for_storage())
18    }
19
20    pub fn token_collateral_for_storage(
21        &self, address: &Address,
22    ) -> DbResult<U256> {
23        let acc = try_loaded!(self.read_native_account_lock(address));
24        Ok(acc.token_collateral_for_storage())
25    }
26
27    pub fn available_storage_points_for_collateral(
28        &self, address: &Address,
29    ) -> DbResult<U256> {
30        let acc = try_loaded!(self.read_native_account_lock(address));
31        Ok(acc
32            .sponsor_info()
33            .storage_points
34            .as_ref()
35            .map(|points| points.unused)
36            .unwrap_or_default())
37    }
38
39    /// Caller should make sure that staking_balance for this account is
40    /// sufficient enough.
41    pub fn add_collateral_for_storage(
42        &mut self, address: &Address, by: &U256,
43    ) -> DbResult<U256> {
44        return_if!(by.is_zero());
45
46        let storage_points_used = self
47            .write_native_account_lock(&address)?
48            .add_collateral_for_storage(by);
49        *self.global_stat.val::<TotalStorage>() += *by - storage_points_used;
50        *self.global_stat.val::<UsedStoragePoints>() += storage_points_used;
51        Ok(storage_points_used)
52    }
53
54    pub fn sub_collateral_for_storage(
55        &mut self, address: &Address, by: &U256,
56    ) -> DbResult<U256> {
57        return_if!(by.is_zero());
58
59        let collateral = self.token_collateral_for_storage(address)?;
60        let refundable = if by > &collateral { &collateral } else { by };
61        let burnt = *by - *refundable;
62        let storage_points_refund = if !refundable.is_zero() {
63            self.write_account_or_new_lock(&address.with_native_space())?
64                .sub_collateral_for_storage(refundable)
65        } else {
66            U256::zero()
67        };
68
69        *self.global_stat.val::<TotalStorage>() -= *by - storage_points_refund;
70        *self.global_stat.val::<UsedStoragePoints>() -= storage_points_refund;
71        self.sub_total_issued(burnt);
72
73        Ok(storage_points_refund)
74    }
75
76    pub fn check_storage_limit(
77        &self, original_sender: &Address, storage_limit: &U256, dry_run: bool,
78    ) -> DbResult<CollateralCheckResult> {
79        let collateral_for_storage =
80            self.collateral_for_storage(original_sender)?;
81        Ok(if collateral_for_storage > *storage_limit && !dry_run {
82            Err(CollateralCheckError::ExceedStorageLimit {
83                limit: *storage_limit,
84                required: collateral_for_storage,
85            })
86        } else {
87            Ok(())
88        })
89    }
90
91    pub fn storage_point_prop(&self) -> DbResult<U256> {
92        self.get_system_storage(&storage_point_prop())
93    }
94
95    fn initialize_cip107(
96        &mut self, address: &Address,
97    ) -> DbResult<(U256, U256)> {
98        debug!("Check initialize CIP-107");
99
100        let prop: U256 = self.storage_point_prop()?;
101        let mut account =
102            self.write_account_or_new_lock(&address.with_native_space())?;
103        return_if!(!account.is_contract());
104        return_if!(account.is_cip_107_initialized());
105
106        let (from_balance, from_collateral) = account.initialize_cip107(prop);
107        std::mem::drop(account);
108
109        self.add_converted_storage_point(from_balance, from_collateral);
110        Ok((from_balance, from_collateral))
111    }
112}
113
114impl State {
115    // TODO: This function can only be called after VM execution. There are some
116    // test cases breaks this assumption, which will be fixed in a separated PR.
117    #[cfg(test)]
118    pub fn settle_collateral_and_check(
119        &mut self, storage_owner: &Address, storage_limit: &U256,
120        substate: &mut Substate, tracer: &mut dyn TracerTrait, spec: &Spec,
121        dry_run: bool,
122    ) -> DbResult<CollateralCheckResult> {
123        let res =
124            settle_collateral_for_all(self, substate, tracer, spec, dry_run)?;
125        Ok(if res.is_ok() {
126            self.check_storage_limit(storage_owner, storage_limit, dry_run)?
127        } else {
128            res
129        })
130    }
131
132    #[cfg(test)]
133    pub fn settle_collateral_and_assert(
134        &mut self, storage_owner: &Address, substate: &mut Substate,
135        should_success: bool,
136    ) -> DbResult<()> {
137        let res = self.settle_collateral_and_check(
138            storage_owner,
139            &U256::MAX,
140            substate,
141            &mut (),
142            &Spec::new_spec_for_test(),
143            false,
144        )?;
145
146        if should_success {
147            res.unwrap();
148        } else {
149            res.unwrap_err();
150        }
151
152        Ok(())
153    }
154}
155
156/// Charges or refund storage collateral and update `total_storage_tokens`.
157fn settle_collateral_for_address(
158    state: &mut State, addr: &Address, substate: &Substate,
159    tracer: &mut dyn TracerTrait, spec: &Spec, dry_run: bool,
160) -> DbResult<CollateralCheckResult> {
161    let addr_with_space = addr.with_native_space();
162    let (inc_collaterals, sub_collaterals) =
163        substate.get_collateral_change(addr);
164    let (inc, sub) = (
165        *DRIPS_PER_STORAGE_COLLATERAL_UNIT * inc_collaterals,
166        *DRIPS_PER_STORAGE_COLLATERAL_UNIT * sub_collaterals,
167    );
168
169    let is_contract = state.is_contract_with_code(&addr_with_space)?;
170
171    // Initialize CIP-107
172    if spec.cip107
173        && addr.is_contract_address()
174        && (!sub.is_zero() || !inc.is_zero())
175    {
176        let (from_balance, from_collateral) = state.initialize_cip107(addr)?;
177        tracer.trace_convert_storage_points(
178            *addr,
179            from_balance,
180            from_collateral,
181        );
182    }
183
184    if !sub.is_zero() {
185        let storage_points_refund =
186            state.sub_collateral_for_storage(addr, &sub)?;
187        tracer.trace_refund_collateral(*addr, sub - storage_points_refund);
188    }
189    if !inc.is_zero() && !dry_run {
190        let balance = if is_contract {
191            state.sponsor_balance_for_collateral(addr)?
192                + state.available_storage_points_for_collateral(addr)?
193        } else {
194            state.balance(&addr_with_space)?
195        };
196        // sponsor_balance is not enough to cover storage incremental.
197        if inc > balance {
198            return Ok(Err(CollateralCheckError::NotEnoughBalance {
199                required: inc,
200                got: balance,
201            }));
202        }
203
204        let storage_points_used =
205            state.add_collateral_for_storage(addr, &inc)?;
206        tracer.trace_occupy_collateral(*addr, inc - storage_points_used);
207    }
208    Ok(Ok(()))
209}
210
211/// Charge and refund all the storage collaterals.
212/// The suicided addresses are skimmed because their collateral have been
213/// checked out. This function should only be called in post-processing
214/// of a transaction.
215pub fn settle_collateral_for_all(
216    state: &mut State, substate: &Substate, tracer: &mut dyn TracerTrait,
217    spec: &Spec, dry_run: bool,
218) -> DbResult<CollateralCheckResult> {
219    for address in substate.keys_for_collateral_changed().iter() {
220        let res = settle_collateral_for_address(
221            state, &address, substate, tracer, spec, dry_run,
222        )?;
223        if res.is_err() {
224            return Ok(res);
225        }
226    }
227    Ok(Ok(()))
228}
229
230/// Initialize CIP-107 for the whole system.
231pub fn initialize_cip107(state: &mut State) -> DbResult<()> {
232    debug!(
233        "set storage_point_prop to {}",
234        CIP107_STORAGE_POINT_PROP_INIT
235    );
236    state.set_system_storage(
237        storage_point_prop().to_vec(),
238        CIP107_STORAGE_POINT_PROP_INIT.into(),
239    )
240}
241
242pub type CollateralCheckResult = std::result::Result<(), CollateralCheckError>;
243
244#[derive(Copy, Clone, PartialEq, Debug)]
245pub enum CollateralCheckError {
246    ExceedStorageLimit { limit: U256, required: U256 },
247    NotEnoughBalance { required: U256, got: U256 },
248}
249
250impl CollateralCheckError {
251    pub fn into_vm_error(self) -> vm::Error {
252        match self {
253            CollateralCheckError::ExceedStorageLimit { .. } => {
254                vm::Error::ExceedStorageLimit
255            }
256            CollateralCheckError::NotEnoughBalance { required, got } => {
257                vm::Error::NotEnoughBalanceForStorage { required, got }
258            }
259        }
260    }
261}