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