evm_precompiles/
lib.rs

1// Copyright 2019-2021 PureStake Inc.
2// This file is part of Moonbeam.
3
4// Moonbeam is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8
9// Moonbeam is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12// GNU General Public License for more details.
13
14// You should have received a copy of the GNU General Public License
15// along with Moonbeam. If not, see <http://www.gnu.org/licenses/>.
16
17mod data;
18
19use ethereum::Log;
20use evm::{executor::stack::PrecompileFailure, ExitError};
21use primitive_types::{H160, H256};
22
23pub use data::{Address, EvmData, EvmDataReader, EvmDataWriter};
24pub use evm_precompiles_derive::generate_function_selector;
25
26/// Alias for Result returning an EVM precompile error.
27pub type EvmResult<T = ()> = Result<T, PrecompileFailure>;
28
29#[macro_export(crate)]
30macro_rules! err {
31    ($e: expr) => {
32        PrecompileFailure::Error { exit_status: $e }
33    };
34}
35
36/// Return an error with provided (static) text.
37pub fn error<T: Into<std::borrow::Cow<'static, str>>>(text: T) -> PrecompileFailure {
38    err!(ExitError::Other(text.into()))
39}
40
41/// Builder for PrecompileOutput.
42#[derive(Clone, Debug)]
43pub struct LogsBuilder {
44    address: H160,
45    logs: Vec<Log>,
46}
47
48impl LogsBuilder {
49    /// Create a new builder with no logs.
50    /// Takes the address of the precompile (usualy `context.address`).
51    pub fn new(address: H160) -> Self {
52        Self {
53            logs: vec![],
54            address,
55        }
56    }
57
58    /// Returns the logs array.
59    pub fn build(self) -> Vec<Log> {
60        self.logs
61    }
62
63    /// Add a 0-topic log.
64    pub fn log0<D>(mut self, data: D) -> Self
65    where
66        D: Into<Vec<u8>>,
67    {
68        self.logs.push(Log {
69            address: self.address,
70            data: data.into(),
71            topics: vec![],
72        });
73        self
74    }
75
76    /// Add a 1-topic log.
77    pub fn log1<D, T0>(mut self, topic0: T0, data: D) -> Self
78    where
79        D: Into<Vec<u8>>,
80        T0: Into<H256>,
81    {
82        self.logs.push(Log {
83            address: self.address,
84            data: data.into(),
85            topics: vec![topic0.into()],
86        });
87        self
88    }
89
90    /// Add a 2-topics log.
91    pub fn log2<D, T0, T1>(mut self, topic0: T0, topic1: T1, data: D) -> Self
92    where
93        D: Into<Vec<u8>>,
94        T0: Into<H256>,
95        T1: Into<H256>,
96    {
97        self.logs.push(Log {
98            address: self.address,
99            data: data.into(),
100            topics: vec![topic0.into(), topic1.into()],
101        });
102        self
103    }
104
105    /// Add a 3-topics log.
106    pub fn log3<D, T0, T1, T2>(mut self, topic0: T0, topic1: T1, topic2: T2, data: D) -> Self
107    where
108        D: Into<Vec<u8>>,
109        T0: Into<H256>,
110        T1: Into<H256>,
111        T2: Into<H256>,
112    {
113        self.logs.push(Log {
114            address: self.address,
115            data: data.into(),
116            topics: vec![topic0.into(), topic1.into(), topic2.into()],
117        });
118        self
119    }
120
121    /// Add a 4-topics log.
122    pub fn log4<D, T0, T1, T2, T3>(
123        mut self,
124        topic0: T0,
125        topic1: T1,
126        topic2: T2,
127        topic3: T3,
128        data: D,
129    ) -> Self
130    where
131        D: Into<Vec<u8>>,
132        T0: Into<H256>,
133        T1: Into<H256>,
134        T2: Into<H256>,
135        T3: Into<H256>,
136    {
137        self.logs.push(Log {
138            address: self.address,
139            data: data.into(),
140            topics: vec![topic0.into(), topic1.into(), topic2.into(), topic3.into()],
141        });
142        self
143    }
144}
145
146/// Custom Gasometer to record costs in precompiles.
147/// It is advised to record known costs as early as possible to
148/// avoid unecessary computations if there is an Out of Gas.
149#[derive(Clone, Copy, Debug)]
150pub struct Gasometer {
151    target_gas: Option<u64>,
152    used_gas: u64,
153}
154
155impl Gasometer {
156    /// Create a new Gasometer with provided gas limit.
157    /// None is no limit.
158    pub fn new(target_gas: Option<u64>) -> Self {
159        Self {
160            target_gas,
161            used_gas: 0,
162        }
163    }
164
165    /// Get used gas.
166    pub fn used_gas(&self) -> u64 {
167        self.used_gas
168    }
169
170    /// Record cost, and return error if it goes out of gas.
171    pub fn record_cost(&mut self, cost: u64) -> EvmResult {
172        self.used_gas = self
173            .used_gas
174            .checked_add(cost)
175            .ok_or(err!(ExitError::OutOfGas))?;
176
177        match self.target_gas {
178            Some(gas_limit) if self.used_gas > gas_limit => Err(err!(ExitError::OutOfGas)),
179            _ => Ok(()),
180        }
181    }
182
183    /// Record cost of a log manualy.
184    /// This can be useful to record log costs early when their content have static size.
185    pub fn record_log_costs_manual(&mut self, topics: usize, data_len: usize) -> EvmResult {
186        // Cost calculation is copied from EVM code that is not publicly exposed by the crates.
187        // https://github.com/rust-blockchain/evm/blob/master/gasometer/src/costs.rs#L148
188
189        const G_LOG: u64 = 375;
190        const G_LOGDATA: u64 = 8;
191        const G_LOGTOPIC: u64 = 375;
192
193        let topic_cost = G_LOGTOPIC
194            .checked_mul(topics as u64)
195            .ok_or(err!(ExitError::OutOfGas))?;
196
197        let data_cost = G_LOGDATA
198            .checked_mul(data_len as u64)
199            .ok_or(err!(ExitError::OutOfGas))?;
200
201        self.record_cost(G_LOG)?;
202        self.record_cost(topic_cost)?;
203        self.record_cost(data_cost)?;
204
205        Ok(())
206    }
207
208    /// Record cost of logs.
209    pub fn record_log_costs(&mut self, logs: &[Log]) -> EvmResult {
210        for log in logs {
211            self.record_log_costs_manual(log.topics.len(), log.data.len())?;
212        }
213
214        Ok(())
215    }
216
217    /// Compute remaining gas.
218    /// Returns error if out of gas.
219    /// Returns None if no gas limit.
220    pub fn remaining_gas(&self) -> EvmResult<Option<u64>> {
221        Ok(match self.target_gas {
222            None => None,
223            Some(gas_limit) => Some(
224                gas_limit
225                    .checked_sub(self.used_gas)
226                    .ok_or(err!(ExitError::OutOfGas))?,
227            ),
228        })
229    }
230}