1#![cfg(any(feature = "std", feature = "runtime-benchmarks", test))]
26
27use crate::{metering::TransactionLimits, OriginFor};
28use alloy_core::{
29 primitives::{Address, U256 as EU256},
30 sol_types::*,
31};
32use frame_support::{
33 traits::{
34 tokens::{
35 fungible, fungibles, DepositConsequence, Fortitude, Precision, Preservation,
36 Provenance, WithdrawConsequence,
37 },
38 OriginTrait,
39 },
40 PalletId,
41};
42use sp_core::{H160, U256};
43use sp_runtime::{traits::AccountIdConversion, DispatchError};
44
45use super::{address::AddressMapper, pallet, Config, ContractResult, ExecConfig, Pallet, Weight};
46use ethereum_standards::IERC20;
47
48const WEIGHT_LIMIT: Weight = Weight::from_parts(10_000_000_000, 100_000);
49
50impl<T: Config> Pallet<T> {
51 pub fn checking_account() -> <T as frame_system::Config>::AccountId {
55 PalletId(*b"py/revch").into_account_truncating()
56 }
57}
58
59impl<T: Config> fungibles::Inspect<<T as frame_system::Config>::AccountId> for Pallet<T> {
60 type AssetId = H160;
62 type Balance = u128;
64
65 fn total_issuance(asset_id: Self::AssetId) -> Self::Balance {
67 let data = IERC20::totalSupplyCall {}.abi_encode();
68 let ContractResult { result, .. } = Self::bare_call(
69 OriginFor::<T>::signed(Self::checking_account()),
70 asset_id,
71 U256::zero(),
72 TransactionLimits::WeightAndDeposit {
73 weight_limit: WEIGHT_LIMIT,
74 deposit_limit:
75 <<T as pallet::Config>::Currency as fungible::Inspect<_>>::total_issuance(),
76 },
77 data,
78 ExecConfig::new_substrate_tx(),
79 );
80 if let Ok(return_value) = result {
81 if let Ok(eu256) = EU256::abi_decode_validate(&return_value.data) {
82 eu256.to::<u128>()
83 } else {
84 0
85 }
86 } else {
87 0
88 }
89 }
90
91 fn minimum_balance(_: Self::AssetId) -> Self::Balance {
92 1
94 }
95
96 fn total_balance(asset_id: Self::AssetId, account_id: &T::AccountId) -> Self::Balance {
97 Self::balance(asset_id, account_id)
100 }
101
102 fn balance(asset_id: Self::AssetId, account_id: &T::AccountId) -> Self::Balance {
103 let eth_address = T::AddressMapper::to_address(account_id);
104 let address = Address::from(Into::<[u8; 20]>::into(eth_address));
105 let data = IERC20::balanceOfCall { account: address }.abi_encode();
106 let ContractResult { result, .. } = Self::bare_call(
107 OriginFor::<T>::signed(account_id.clone()),
108 asset_id,
109 U256::zero(),
110 TransactionLimits::WeightAndDeposit {
111 weight_limit: WEIGHT_LIMIT,
112 deposit_limit:
113 <<T as pallet::Config>::Currency as fungible::Inspect<_>>::total_issuance(),
114 },
115 data,
116 ExecConfig::new_substrate_tx(),
117 );
118 if let Ok(return_value) = result {
119 if let Ok(eu256) = EU256::abi_decode_validate(&return_value.data) {
120 eu256.to::<u128>()
121 } else {
122 0
123 }
124 } else {
125 0
126 }
127 }
128
129 fn reducible_balance(
130 asset_id: Self::AssetId,
131 account_id: &T::AccountId,
132 _: Preservation,
133 _: Fortitude,
134 ) -> Self::Balance {
135 Self::balance(asset_id, account_id)
138 }
139
140 fn can_deposit(
141 _: Self::AssetId,
142 _: &T::AccountId,
143 _: Self::Balance,
144 _: Provenance,
145 ) -> DepositConsequence {
146 DepositConsequence::Success
147 }
148
149 fn can_withdraw(
150 _: Self::AssetId,
151 _: &T::AccountId,
152 _: Self::Balance,
153 ) -> WithdrawConsequence<Self::Balance> {
154 WithdrawConsequence::Success
155 }
156
157 fn asset_exists(_: Self::AssetId) -> bool {
158 false
159 }
160}
161
162impl<T: Config> fungibles::Mutate<<T as frame_system::Config>::AccountId> for Pallet<T> {
166 fn burn_from(
167 asset_id: Self::AssetId,
168 who: &T::AccountId,
169 amount: Self::Balance,
170 _: Preservation,
171 _: Precision,
172 _: Fortitude,
173 ) -> Result<Self::Balance, DispatchError> {
174 let checking_account_eth = T::AddressMapper::to_address(&Self::checking_account());
175 let checking_address = Address::from(Into::<[u8; 20]>::into(checking_account_eth));
176 let data =
177 IERC20::transferCall { to: checking_address, value: EU256::from(amount) }.abi_encode();
178 let ContractResult { result, weight_consumed, .. } = Self::bare_call(
179 OriginFor::<T>::signed(who.clone()),
180 asset_id,
181 U256::zero(),
182 TransactionLimits::WeightAndDeposit {
183 weight_limit: WEIGHT_LIMIT,
184 deposit_limit:
185 <<T as pallet::Config>::Currency as fungible::Inspect<_>>::total_issuance(),
186 },
187 data,
188 ExecConfig::new_substrate_tx(),
189 );
190 log::trace!(target: "whatiwant", "{weight_consumed}");
191 if let Ok(return_value) = result {
192 if return_value.did_revert() {
193 Err("Contract reverted".into())
194 } else {
195 let is_success =
196 bool::abi_decode_validate(&return_value.data).expect("Failed to ABI decode");
197 if is_success {
198 let balance = <Self as fungibles::Inspect<_>>::balance(asset_id, who);
199 Ok(balance)
200 } else {
201 Err("Contract transfer failed".into())
202 }
203 }
204 } else {
205 Err("Contract out of gas".into())
206 }
207 }
208
209 fn mint_into(
210 asset_id: Self::AssetId,
211 who: &T::AccountId,
212 amount: Self::Balance,
213 ) -> Result<Self::Balance, DispatchError> {
214 let eth_address = T::AddressMapper::to_address(who);
215 let address = Address::from(Into::<[u8; 20]>::into(eth_address));
216 let data = IERC20::transferCall { to: address, value: EU256::from(amount) }.abi_encode();
217 let ContractResult { result, .. } = Self::bare_call(
218 OriginFor::<T>::signed(Self::checking_account()),
219 asset_id,
220 U256::zero(),
221 TransactionLimits::WeightAndDeposit {
222 weight_limit: WEIGHT_LIMIT,
223 deposit_limit:
224 <<T as pallet::Config>::Currency as fungible::Inspect<_>>::total_issuance(),
225 },
226 data,
227 ExecConfig::new_substrate_tx(),
228 );
229 if let Ok(return_value) = result {
230 if return_value.did_revert() {
231 Err("Contract reverted".into())
232 } else {
233 let is_success =
234 bool::abi_decode_validate(&return_value.data).expect("Failed to ABI decode");
235 if is_success {
236 let balance = <Self as fungibles::Inspect<_>>::balance(asset_id, who);
237 Ok(balance)
238 } else {
239 Err("Contract transfer failed".into())
240 }
241 }
242 } else {
243 Err("Contract out of gas".into())
244 }
245 }
246}
247
248impl<T: Config> fungibles::Unbalanced<<T as frame_system::Config>::AccountId> for Pallet<T> {
253 fn handle_raw_dust(_: Self::AssetId, _: Self::Balance) {}
254 fn handle_dust(_: fungibles::Dust<T::AccountId, Self>) {}
255 fn write_balance(
256 _: Self::AssetId,
257 _: &T::AccountId,
258 _: Self::Balance,
259 ) -> Result<Option<Self::Balance>, DispatchError> {
260 Err(DispatchError::Unavailable)
261 }
262 fn set_total_issuance(_id: Self::AssetId, _amount: Self::Balance) {
263 }
265
266 fn decrease_balance(
267 _: Self::AssetId,
268 _: &T::AccountId,
269 _: Self::Balance,
270 _: Precision,
271 _: Preservation,
272 _: Fortitude,
273 ) -> Result<Self::Balance, DispatchError> {
274 Err(DispatchError::Unavailable)
275 }
276
277 fn increase_balance(
278 _: Self::AssetId,
279 _: &T::AccountId,
280 _: Self::Balance,
281 _: Precision,
282 ) -> Result<Self::Balance, DispatchError> {
283 Err(DispatchError::Unavailable)
284 }
285}
286
287#[cfg(test)]
288mod tests {
289 use super::*;
290 use crate::{
291 test_utils::{builder::*, ALICE},
292 tests::{Contracts, ExtBuilder, RuntimeOrigin, Test},
293 AccountInfoOf, Code,
294 };
295 use frame_support::assert_ok;
296 const ERC20_PVM_CODE: &[u8] = include_bytes!("../fixtures/erc20/erc20.polkavm");
297
298 #[test]
299 fn call_erc20_contract() {
300 ExtBuilder::default().existential_deposit(1).build().execute_with(|| {
301 let _ =
302 <<Test as Config>::Currency as fungible::Mutate<_>>::set_balance(&ALICE, 1_000_000);
303 let code = ERC20_PVM_CODE.to_vec();
304 let amount = EU256::from(1000);
305 let constructor_data = sol_data::Uint::<256>::abi_encode(&amount);
306 let Contract { addr, .. } = BareInstantiateBuilder::<Test>::bare_instantiate(
307 RuntimeOrigin::signed(ALICE),
308 Code::Upload(code),
309 )
310 .data(constructor_data)
311 .build_and_unwrap_contract();
312 let result = BareCallBuilder::<Test>::bare_call(RuntimeOrigin::signed(ALICE), addr)
313 .data(IERC20::totalSupplyCall {}.abi_encode())
314 .build_and_unwrap_result();
315 let balance =
316 EU256::abi_decode_validate(&result.data).expect("Failed to decode ABI response");
317 assert_eq!(balance, EU256::from(amount));
318 assert_eq!(AccountInfoOf::<Test>::contains_key(&addr), true);
320 });
321 }
322
323 #[test]
324 fn total_issuance_works() {
325 ExtBuilder::default().existential_deposit(1).build().execute_with(|| {
326 let _ =
327 <<Test as Config>::Currency as fungible::Mutate<_>>::set_balance(&ALICE, 1_000_000);
328 let code = ERC20_PVM_CODE.to_vec();
329 let amount = 1000;
330 let constructor_data = sol_data::Uint::<256>::abi_encode(&EU256::from(amount));
331 let Contract { addr, .. } = BareInstantiateBuilder::<Test>::bare_instantiate(
332 RuntimeOrigin::signed(ALICE),
333 Code::Upload(code),
334 )
335 .data(constructor_data)
336 .build_and_unwrap_contract();
337
338 let total_issuance = <Contracts as fungibles::Inspect<_>>::total_issuance(addr);
339 assert_eq!(total_issuance, amount);
340 });
341 }
342
343 #[test]
344 fn get_balance_of_erc20() {
345 ExtBuilder::default().existential_deposit(1).build().execute_with(|| {
346 let _ =
347 <<Test as Config>::Currency as fungible::Mutate<_>>::set_balance(&ALICE, 1_000_000);
348 let code = ERC20_PVM_CODE.to_vec();
349 let amount = 1000;
350 let constructor_data = sol_data::Uint::<256>::abi_encode(&EU256::from(amount));
351 let Contract { addr, .. } = BareInstantiateBuilder::<Test>::bare_instantiate(
352 RuntimeOrigin::signed(ALICE),
353 Code::Upload(code),
354 )
355 .data(constructor_data)
356 .build_and_unwrap_contract();
357 assert_eq!(<Contracts as fungibles::Inspect<_>>::balance(addr, &ALICE), amount);
358 });
359 }
360
361 #[test]
362 fn burn_from_impl_works() {
363 ExtBuilder::default().existential_deposit(1).build().execute_with(|| {
364 let _ =
365 <<Test as Config>::Currency as fungible::Mutate<_>>::set_balance(&ALICE, 1_000_000);
366 let code = ERC20_PVM_CODE.to_vec();
367 let amount = 1000;
368 let constructor_data = sol_data::Uint::<256>::abi_encode(&(EU256::from(amount * 2)));
369 let Contract { addr, .. } = BareInstantiateBuilder::<Test>::bare_instantiate(
370 RuntimeOrigin::signed(ALICE),
371 Code::Upload(code),
372 )
373 .data(constructor_data)
374 .build_and_unwrap_contract();
375 let _ = BareCallBuilder::<Test>::bare_call(RuntimeOrigin::signed(ALICE), addr)
376 .build_and_unwrap_result();
377 assert_eq!(<Contracts as fungibles::Inspect<_>>::balance(addr, &ALICE), amount * 2);
378
379 assert_ok!(<Contracts as fungibles::Mutate<_>>::burn_from(
381 addr,
382 &ALICE,
383 amount,
384 Preservation::Expendable,
385 Precision::Exact,
386 Fortitude::Polite
387 ));
388 assert_eq!(<Contracts as fungibles::Inspect<_>>::balance(addr, &ALICE), amount);
389 });
390 }
391
392 #[test]
393 fn mint_into_impl_works() {
394 ExtBuilder::default().existential_deposit(1).build().execute_with(|| {
395 let checking_account = Pallet::<Test>::checking_account();
396 let _ =
397 <<Test as Config>::Currency as fungible::Mutate<_>>::set_balance(&ALICE, 1_000_000);
398 let _ = <<Test as Config>::Currency as fungible::Mutate<_>>::set_balance(
399 &checking_account,
400 1_000_000,
401 );
402 let code = ERC20_PVM_CODE.to_vec();
403 let amount = 1000;
404 let constructor_data = sol_data::Uint::<256>::abi_encode(&EU256::from(amount));
405 let Contract { addr, .. } = BareInstantiateBuilder::<Test>::bare_instantiate(
407 RuntimeOrigin::signed(checking_account.clone()),
408 Code::Upload(code),
409 )
410 .transaction_limits(TransactionLimits::WeightAndDeposit {
411 weight_limit: WEIGHT_LIMIT,
412 deposit_limit: 1_000_000_000_000,
413 })
414 .data(constructor_data)
415 .build_and_unwrap_contract();
416 assert_eq!(
417 <Contracts as fungibles::Inspect<_>>::balance(addr, &checking_account),
418 amount
419 );
420 assert_eq!(<Contracts as fungibles::Inspect<_>>::balance(addr, &ALICE), 0);
421
422 assert_ok!(<Contracts as fungibles::Mutate<_>>::mint_into(addr, &ALICE, amount));
424 assert_eq!(<Contracts as fungibles::Inspect<_>>::balance(addr, &checking_account), 0);
426 assert_eq!(<Contracts as fungibles::Inspect<_>>::balance(addr, &ALICE), amount);
427 });
428 }
429}