cfx_executor/internal_contract/components/
function.rs

1// Copyright 2020 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 cfx_statedb::Result as DbResult;
6use cfx_types::U256;
7use cfx_vm_types::{self as vm, ActionParams, CallType, GasLeft};
8use solidity_abi::{ABIDecodable, ABIEncodable};
9
10use super::{InternalRefContext, InternalTrapResult, IsActive};
11use InternalTrapResult::*;
12
13/// Native implementation of a solidity-interface function.
14pub trait SolidityFunctionTrait: Send + Sync + IsActive {
15    fn execute(
16        &self, input: &[u8], params: &ActionParams,
17        context: &mut InternalRefContext,
18    ) -> InternalTrapResult<GasLeft>;
19
20    /// The string for function sig
21    fn name(&self) -> &'static str;
22
23    /// The function sig for this function
24    fn function_sig(&self) -> [u8; 4];
25}
26
27pub trait SolidityFunctionConfigTrait:
28    InterfaceTrait + PreExecCheckTrait + UpfrontPaymentTrait
29{
30}
31
32impl<T> SolidityFunctionConfigTrait for T where T: InterfaceTrait + PreExecCheckTrait + UpfrontPaymentTrait
33{}
34
35/// The standard implementation of the solidity function trait. The developer of
36/// new functions should implement the following traits.
37///
38/// The `InterfaceTrait` is implemented when constructing a new struct with
39/// macro `make_solidity_function`.
40///
41/// The `PreExecCheckTrait` and `UpfrontPaymentTrait` trait can be implemented
42/// by macro `set_default_config`. By default, the contract with be set non
43/// payable and forbid static. Sometimes we need to implement
44/// `UpfrontPaymentTrait` manually if the gas required is not a constant value.
45///
46/// You always needs to implement `ExecutionTrait`, which is the core of the
47/// function execution.
48impl<T: SolidityFunctionConfigTrait + ExecutionTrait + IsActive>
49    SolidityFunctionTrait for T
50{
51    fn execute(
52        &self, input: &[u8], params: &ActionParams,
53        context: &mut InternalRefContext,
54    ) -> InternalTrapResult<GasLeft> {
55        let (solidity_params, cost) =
56            match preprocessing(self, input, params, context) {
57                Ok(res) => res,
58                Err(err) => {
59                    return Return(Err(err));
60                }
61            };
62
63        let gas_left = params.gas - cost;
64
65        ExecutionTrait::execute_inner(
66            self,
67            solidity_params,
68            params,
69            gas_left,
70            context,
71        )
72        .map_return(|output| {
73            GasLeft::NeedsReturn {
74                gas_left,
75                data: output.abi_encode().into(),
76                apply_state: true,
77            }
78            .charge_return_data_gas(context.spec)
79        })
80    }
81
82    fn name(&self) -> &'static str { return Self::NAME_AND_PARAMS; }
83
84    fn function_sig(&self) -> [u8; 4] { return Self::FUNC_SIG; }
85}
86
87fn preprocessing<T: SolidityFunctionConfigTrait>(
88    sol_fn: &T, input: &[u8], params: &ActionParams,
89    context: &InternalRefContext,
90) -> vm::Result<(T::Input, U256)> {
91    sol_fn.pre_execution_check(params, context)?;
92    let solidity_params = <T::Input as ABIDecodable>::abi_decode(&input)?;
93    let cost = sol_fn.upfront_gas_payment(&solidity_params, params, context)?;
94    if cost > params.gas {
95        return Err(vm::Error::OutOfGas);
96    }
97    Ok((solidity_params, cost))
98}
99
100pub trait InterfaceTrait {
101    type Input: ABIDecodable;
102    type Output: ABIEncodable;
103    const NAME_AND_PARAMS: &'static str;
104    const FUNC_SIG: [u8; 4];
105}
106
107pub trait PreExecCheckTrait: Send + Sync {
108    fn pre_execution_check(
109        &self, params: &ActionParams, context: &InternalRefContext,
110    ) -> vm::Result<()>;
111}
112
113pub trait ExecutionTrait: Send + Sync + InterfaceTrait {
114    fn execute_inner(
115        &self, input: Self::Input, params: &ActionParams, gas_left: U256,
116        context: &mut InternalRefContext,
117    ) -> InternalTrapResult<<Self as InterfaceTrait>::Output>;
118}
119
120/// The Execution trait without sub-call and sub-create.
121pub trait SimpleExecutionTrait: Send + Sync + InterfaceTrait {
122    fn execute_inner(
123        &self, input: Self::Input, params: &ActionParams,
124        context: &mut InternalRefContext,
125    ) -> vm::Result<<Self as InterfaceTrait>::Output>;
126}
127
128impl<T> ExecutionTrait for T
129where T: SimpleExecutionTrait
130{
131    fn execute_inner(
132        &self, input: Self::Input, params: &ActionParams, _gas_left: U256,
133        context: &mut InternalRefContext,
134    ) -> InternalTrapResult<<Self as InterfaceTrait>::Output> {
135        Return(SimpleExecutionTrait::execute_inner(
136            self, input, params, context,
137        ))
138    }
139}
140
141pub trait UpfrontPaymentTrait: Send + Sync + InterfaceTrait {
142    fn upfront_gas_payment(
143        &self, input: &Self::Input, params: &ActionParams,
144        context: &InternalRefContext,
145    ) -> DbResult<U256>;
146}
147
148pub trait PreExecCheckConfTrait: Send + Sync {
149    /// Whether such internal function is payable.
150    const PAYABLE: bool;
151    /// Whether such internal function has write operation.
152    const HAS_WRITE_OP: bool;
153}
154
155impl<T: PreExecCheckConfTrait> PreExecCheckTrait for T {
156    fn pre_execution_check(
157        &self, params: &ActionParams, context: &InternalRefContext,
158    ) -> vm::Result<()> {
159        if !Self::PAYABLE && !params.value.value().is_zero() {
160            return Err(vm::Error::InternalContract(
161                "should not transfer balance to non-payable function".into(),
162            ));
163        }
164
165        let spec = context.spec;
166        // Check static context before CIP-132
167        let mut static_context = context.callstack.in_reentrancy(spec)
168            || params.call_type == CallType::StaticCall;
169        // Add the lost constraint after CIP-132
170        static_context |= spec.cip132 && context.static_flag;
171
172        if Self::HAS_WRITE_OP && static_context {
173            return Err(vm::Error::MutableCallInStaticContext);
174        }
175
176        Ok(())
177    }
178}
179
180#[macro_export]
181/// Make a solidity interface function, it requires three parameters
182/// 1. The type of input parameters.
183/// 2. The string to compute interface signature.
184/// 3. The type of output parameters.
185///
186/// For example, in order to make a function with interface
187/// get_whitelist(address user, address contract) public returns bool, you
188/// should use
189/// ```
190/// use cfx_executor::{make_solidity_function, internal_contract::InterfaceTrait};
191/// use cfx_types::{Address,U256};
192/// use sha3_macro::keccak;
193///
194/// make_solidity_function!{
195///     struct WhateverStructName((Address, Address), "get_whitelist(address,address)", bool);
196/// }
197/// ```
198/// If the function has no return value, the third parameter can be omitted.
199macro_rules! make_solidity_function {
200    ( $(#[$attr:meta])* $visibility:vis struct $name:ident ($input:ty, $interface:expr ); ) => {
201        $crate::make_solidity_function! {
202            $(#[$attr])* $visibility struct $name ($input, $interface, () );
203        }
204    };
205    ( $(#[$attr:meta])* $visibility:vis struct $name:ident ($input:ty, $interface:expr, $output:ty ); ) => {
206        $(#[$attr])*
207        #[derive(Copy, Clone)]
208        $visibility struct $name {
209        }
210
211        impl $name {
212            pub fn instance() -> Self {
213                Self {}
214            }
215        }
216
217        impl InterfaceTrait for $name {
218            type Input = $input;
219            type Output = $output;
220            const NAME_AND_PARAMS: &'static str = $interface;
221            const FUNC_SIG: [u8; 4] = {
222                let x = keccak!($interface);
223                [x[0],x[1],x[2],x[3]]
224            };
225        }
226    };
227}
228
229#[macro_export]
230macro_rules! impl_function_type {
231    ( $name:ident, "non_payable_write" $(, gas: $gas:expr)? ) => {
232        $crate::impl_function_type!(@inner, $name, false, true $(, $gas)?);
233    };
234    ( $name:ident, "payable_write" $(, gas: $gas:expr)? ) => {
235        $crate::impl_function_type!(@inner, $name, true, true $(, $gas)?);
236    };
237    ( $name:ident, "query" $(, gas: $gas:expr)? ) => {
238        $crate::impl_function_type!(@inner, $name, false, false $(, $gas)?);
239    };
240    ( @inner, $name:ident, $payable:expr, $has_write_op:expr $(, $gas:expr)? ) => {
241        impl PreExecCheckConfTrait for $name {
242            const PAYABLE: bool = $payable;
243            const HAS_WRITE_OP: bool = $has_write_op;
244        }
245        $(
246            impl UpfrontPaymentTrait for $name {
247                fn upfront_gas_payment(
248                    &self, _input: &Self::Input, _params: &ActionParams, context: &InternalRefContext,
249                ) -> DbResult<U256> {
250                    Ok(U256::from($gas(context.spec)))
251                }
252            }
253        )?
254    };
255    ( $name:ident, "query_with_default_gas" ) => {
256        impl PreExecCheckConfTrait for $name {
257            const PAYABLE: bool = false ;
258            const HAS_WRITE_OP: bool = false;
259        }
260
261        impl UpfrontPaymentTrait for $name {
262            fn upfront_gas_payment(
263                &self, _input: &Self::Input, _params: &ActionParams, context: &InternalRefContext,
264            ) -> DbResult<U256> {
265                Ok(U256::from(context.spec.balance_gas))
266            }
267        }
268    };
269}