pallet_assets_precompiles/
lib.rs1#![cfg_attr(not(feature = "std"), no_std)]
20
21extern crate alloc;
22
23use alloc::vec::Vec;
24use core::marker::PhantomData;
25use ethereum_standards::{
26 IERC20,
27 IERC20::{IERC20Calls, IERC20Events},
28};
29use pallet_assets::{weights::WeightInfo, Call, Config, TransferFlags};
30use pallet_revive::precompiles::{
31 alloy::{
32 self,
33 primitives::IntoLogData,
34 sol_types::{Revert, SolCall},
35 },
36 AddressMapper, AddressMatcher, Error, Ext, Precompile, RuntimeCosts, H160, H256,
37};
38
39#[cfg(test)]
40mod mock;
41#[cfg(test)]
42mod tests;
43
44pub trait AssetIdExtractor {
46 type AssetId;
47 fn asset_id_from_address(address: &[u8; 20]) -> Result<Self::AssetId, Error>;
49}
50
51pub trait AssetPrecompileConfig {
53 const MATCHER: AddressMatcher;
55
56 type AssetIdExtractor: AssetIdExtractor;
58}
59
60pub struct InlineAssetIdExtractor;
62
63impl AssetIdExtractor for InlineAssetIdExtractor {
64 type AssetId = u32;
65 fn asset_id_from_address(addr: &[u8; 20]) -> Result<Self::AssetId, Error> {
66 let bytes: [u8; 4] = addr[0..4].try_into().expect("slice is 4 bytes; qed");
67 let index = u32::from_be_bytes(bytes);
68 return Ok(index.into());
69 }
70}
71
72pub struct InlineIdConfig<const PREFIX: u16>;
74
75impl<const P: u16> AssetPrecompileConfig for InlineIdConfig<P> {
76 const MATCHER: AddressMatcher = AddressMatcher::Prefix(core::num::NonZero::new(P).unwrap());
77 type AssetIdExtractor = InlineAssetIdExtractor;
78}
79
80pub struct ERC20<Runtime, PrecompileConfig, Instance = ()> {
82 _phantom: PhantomData<(Runtime, PrecompileConfig, Instance)>,
83}
84
85impl<Runtime, PrecompileConfig, Instance: 'static> Precompile
86 for ERC20<Runtime, PrecompileConfig, Instance>
87where
88 PrecompileConfig: AssetPrecompileConfig,
89 Runtime: crate::Config<Instance> + pallet_revive::Config,
90 <<PrecompileConfig as AssetPrecompileConfig>::AssetIdExtractor as AssetIdExtractor>::AssetId:
91 Into<<Runtime as Config<Instance>>::AssetId>,
92 Call<Runtime, Instance>: Into<<Runtime as pallet_revive::Config>::RuntimeCall>,
93 alloy::primitives::U256: TryInto<<Runtime as Config<Instance>>::Balance>,
94
95 alloy::primitives::U256: TryFrom<<Runtime as Config<Instance>>::Balance>,
97{
98 type T = Runtime;
99 type Interface = IERC20::IERC20Calls;
100 const MATCHER: AddressMatcher = PrecompileConfig::MATCHER;
101 const HAS_CONTRACT_INFO: bool = false;
102
103 fn call(
104 address: &[u8; 20],
105 input: &Self::Interface,
106 env: &mut impl Ext<T = Self::T>,
107 ) -> Result<Vec<u8>, Error> {
108 let asset_id = PrecompileConfig::AssetIdExtractor::asset_id_from_address(address)?.into();
109
110 match input {
111 IERC20Calls::transfer(_) | IERC20Calls::approve(_) | IERC20Calls::transferFrom(_)
112 if env.is_read_only() =>
113 Err(Error::Error(pallet_revive::Error::<Self::T>::StateChangeDenied.into())),
114
115 IERC20Calls::transfer(call) => Self::transfer(asset_id, call, env),
116 IERC20Calls::totalSupply(_) => Self::total_supply(asset_id, env),
117 IERC20Calls::balanceOf(call) => Self::balance_of(asset_id, call, env),
118 IERC20Calls::allowance(call) => Self::allowance(asset_id, call, env),
119 IERC20Calls::approve(call) => Self::approve(asset_id, call, env),
120 IERC20Calls::transferFrom(call) => Self::transfer_from(asset_id, call, env),
121 }
122 }
123}
124
125const ERR_INVALID_CALLER: &str = "Invalid caller";
126const ERR_BALANCE_CONVERSION_FAILED: &str = "Balance conversion failed";
127
128impl<Runtime, PrecompileConfig, Instance: 'static> ERC20<Runtime, PrecompileConfig, Instance>
129where
130 PrecompileConfig: AssetPrecompileConfig,
131 Runtime: crate::Config<Instance> + pallet_revive::Config,
132 <<PrecompileConfig as AssetPrecompileConfig>::AssetIdExtractor as AssetIdExtractor>::AssetId:
133 Into<<Runtime as Config<Instance>>::AssetId>,
134 Call<Runtime, Instance>: Into<<Runtime as pallet_revive::Config>::RuntimeCall>,
135 alloy::primitives::U256: TryInto<<Runtime as Config<Instance>>::Balance>,
136
137 alloy::primitives::U256: TryFrom<<Runtime as Config<Instance>>::Balance>,
139{
140 fn caller(env: &mut impl Ext<T = Runtime>) -> Result<H160, Error> {
142 env.caller()
143 .account_id()
144 .map(<Runtime as pallet_revive::Config>::AddressMapper::to_address)
145 .map_err(|_| Error::Revert(Revert { reason: ERR_INVALID_CALLER.into() }))
146 }
147
148 fn to_balance(
150 value: alloy::primitives::U256,
151 ) -> Result<<Runtime as Config<Instance>>::Balance, Error> {
152 value
153 .try_into()
154 .map_err(|_| Error::Revert(Revert { reason: ERR_BALANCE_CONVERSION_FAILED.into() }))
155 }
156
157 fn to_u256(
160 value: <Runtime as Config<Instance>>::Balance,
161 ) -> Result<alloy::primitives::U256, Error> {
162 alloy::primitives::U256::try_from(value)
163 .map_err(|_| Error::Revert(Revert { reason: ERR_BALANCE_CONVERSION_FAILED.into() }))
164 }
165
166 fn deposit_event(env: &mut impl Ext<T = Runtime>, event: IERC20Events) -> Result<(), Error> {
168 let (topics, data) = event.into_log_data().split();
169 let topics = topics.into_iter().map(|v| H256(v.0)).collect::<Vec<_>>();
170 env.frame_meter_mut().charge_weight_token(RuntimeCosts::DepositEvent {
171 num_topic: topics.len() as u32,
172 len: topics.len() as u32,
173 })?;
174 env.deposit_event(topics, data.to_vec());
175 Ok(())
176 }
177
178 fn transfer(
180 asset_id: <Runtime as Config<Instance>>::AssetId,
181 call: &IERC20::transferCall,
182 env: &mut impl Ext<T = Runtime>,
183 ) -> Result<Vec<u8>, Error> {
184 env.charge(<Runtime as Config<Instance>>::WeightInfo::transfer())?;
185
186 let from = Self::caller(env)?;
187 let dest = <Runtime as pallet_revive::Config>::AddressMapper::to_account_id(
188 &call.to.into_array().into(),
189 );
190
191 let f = TransferFlags { keep_alive: false, best_effort: false, burn_dust: false };
192 pallet_assets::Pallet::<Runtime, Instance>::do_transfer(
193 asset_id,
194 &<Runtime as pallet_revive::Config>::AddressMapper::to_account_id(&from),
195 &dest,
196 Self::to_balance(call.value)?,
197 None,
198 f,
199 )?;
200
201 Self::deposit_event(
202 env,
203 IERC20Events::Transfer(IERC20::Transfer {
204 from: from.0.into(),
205 to: call.to,
206 value: call.value,
207 }),
208 )?;
209
210 return Ok(IERC20::transferCall::abi_encode_returns(&true));
211 }
212
213 fn total_supply(
215 asset_id: <Runtime as Config<Instance>>::AssetId,
216 env: &mut impl Ext<T = Runtime>,
217 ) -> Result<Vec<u8>, Error> {
218 use frame_support::traits::fungibles::Inspect;
219 env.charge(<Runtime as Config<Instance>>::WeightInfo::total_issuance())?;
220
221 let value =
222 Self::to_u256(pallet_assets::Pallet::<Runtime, Instance>::total_issuance(asset_id))?;
223 return Ok(IERC20::totalSupplyCall::abi_encode_returns(&value));
224 }
225
226 fn balance_of(
228 asset_id: <Runtime as Config<Instance>>::AssetId,
229 call: &IERC20::balanceOfCall,
230 env: &mut impl Ext<T = Runtime>,
231 ) -> Result<Vec<u8>, Error> {
232 env.charge(<Runtime as Config<Instance>>::WeightInfo::balance())?;
233 let account = call.account.into_array().into();
234 let account = <Runtime as pallet_revive::Config>::AddressMapper::to_account_id(&account);
235 let value =
236 Self::to_u256(pallet_assets::Pallet::<Runtime, Instance>::balance(asset_id, account))?;
237 return Ok(IERC20::balanceOfCall::abi_encode_returns(&value));
238 }
239
240 fn allowance(
242 asset_id: <Runtime as Config<Instance>>::AssetId,
243 call: &IERC20::allowanceCall,
244 env: &mut impl Ext<T = Runtime>,
245 ) -> Result<Vec<u8>, Error> {
246 env.charge(<Runtime as Config<Instance>>::WeightInfo::allowance())?;
247 use frame_support::traits::fungibles::approvals::Inspect;
248 let owner = call.owner.into_array().into();
249 let owner = <Runtime as pallet_revive::Config>::AddressMapper::to_account_id(&owner);
250
251 let spender = call.spender.into_array().into();
252 let spender = <Runtime as pallet_revive::Config>::AddressMapper::to_account_id(&spender);
253 let value = Self::to_u256(pallet_assets::Pallet::<Runtime, Instance>::allowance(
254 asset_id, &owner, &spender,
255 ))?;
256
257 return Ok(IERC20::balanceOfCall::abi_encode_returns(&value));
258 }
259
260 fn approve(
262 asset_id: <Runtime as Config<Instance>>::AssetId,
263 call: &IERC20::approveCall,
264 env: &mut impl Ext<T = Runtime>,
265 ) -> Result<Vec<u8>, Error> {
266 env.charge(<Runtime as Config<Instance>>::WeightInfo::approve_transfer())?;
267 let owner = Self::caller(env)?;
268 let spender = call.spender.into_array().into();
269 let spender = <Runtime as pallet_revive::Config>::AddressMapper::to_account_id(&spender);
270
271 pallet_assets::Pallet::<Runtime, Instance>::do_approve_transfer(
272 asset_id,
273 &<Runtime as pallet_revive::Config>::AddressMapper::to_account_id(&owner),
274 &spender,
275 Self::to_balance(call.value)?,
276 )?;
277
278 Self::deposit_event(
279 env,
280 IERC20Events::Approval(IERC20::Approval {
281 owner: owner.0.into(),
282 spender: call.spender,
283 value: call.value,
284 }),
285 )?;
286
287 return Ok(IERC20::approveCall::abi_encode_returns(&true));
288 }
289
290 fn transfer_from(
292 asset_id: <Runtime as Config<Instance>>::AssetId,
293 call: &IERC20::transferFromCall,
294 env: &mut impl Ext<T = Runtime>,
295 ) -> Result<Vec<u8>, Error> {
296 env.charge(<Runtime as Config<Instance>>::WeightInfo::transfer_approved())?;
297 let spender = Self::caller(env)?;
298 let spender = <Runtime as pallet_revive::Config>::AddressMapper::to_account_id(&spender);
299
300 let from = call.from.into_array().into();
301 let from = <Runtime as pallet_revive::Config>::AddressMapper::to_account_id(&from);
302
303 let to = call.to.into_array().into();
304 let to = <Runtime as pallet_revive::Config>::AddressMapper::to_account_id(&to);
305
306 pallet_assets::Pallet::<Runtime, Instance>::do_transfer_approved(
307 asset_id,
308 &from,
309 &spender,
310 &to,
311 Self::to_balance(call.value)?,
312 )?;
313
314 Self::deposit_event(
315 env,
316 IERC20Events::Transfer(IERC20::Transfer {
317 from: call.from,
318 to: call.to,
319 value: call.value,
320 }),
321 )?;
322
323 return Ok(IERC20::transferFromCall::abi_encode_returns(&true));
324 }
325}