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