datahaven/operator/precompiles/proxy/src/tests.rs
Ahmad Kaouk 55e973b8f0
fix: change pallet_evm alias to EVM to fix eth_getCode (#213)
## Summary
- rename the FRAME alias for `pallet_evm` from `Evm` to `EVM` across the
mainnet, stagenet, and testnet runtimes
- adjust benchmarks, configuration modules, genesis builders, and
runtime tests to rely on the new alias
- keep precompile genesis setup and proxy/precompile tests aligned with
the updated names

  ## Context
Frontier’s `StorageOverrideHandler` (see
`fc_storage::StorageQuerier::account_code`) reads contract bytecode from
`pallet_evm::AccountCodes` using the constant `PALLET_EVM = b"EVM"` to
build the storage key:
  `twox_128("EVM") ++ twox_128("AccountCodes") ++ …`

Our runtimes exported `pallet_evm` as `Evm`, so substrate stored
bytecode under the *camel-cased* prefix (`twox_128("Evm")`). Every call
that ultimately hits the storage override—including `eth_getCode`,
`eth_call`, and state queries during replay—therefore failed to locate
code for *any* account (deployed contracts and precompiles alike).
Renaming the alias to `EVM` realigns the storage prefix with Frontier’s
  expectations so the override layers can pull bytecode correctly.

  ## Testing
  - `cargo check -p datahaven-node`
  - `cargo build --release -p datahaven-node`
- `eth_getCode 0x0000000000000000000000000000000000000802` → returns
`0x60006000fd`
  
## Storage Migration
Renaming a pallet alias changes the storage prefix for all pallet data.
Without migration, existing EVM data (smart contracts, account codes,
storage) would become inaccessible.

  **Migration details:**

  - **Type**: Multi-Block Migration (MBM)
- **Storage migrated**: `AccountCodes`, `AccountCodesMetadata`,
`AccountStorages`
  - **Migration ID**: `datahaven-evm-mbm` (version 0 → 1)

  **Testing the migration:**

  ```bash
  # Build runtime with try-runtime
cargo build --release --features try-runtime -p
datahaven-stagenet-runtime

  # Test against stagenet
try-runtime \
--runtime
./target/release/wbuild/datahaven-stagenet-runtime/datahaven_stagenet_runtime.wasm
\
      on-runtime-upgrade \
      --blocktime 6000 \
      --checks all \
      --disable-spec-version-check \
      live --uri wss://dh-validator-0.datahaven-kt.xyz
```

  Test results from stagenet:
  -  Migration completes in 1 block
  -  PoV size: ~5.3 KB
  -  Weight consumption: <0.1% of block capacity
  -  All 39 keys successfully migrated

  ## ⚠️ Breaking Changes ⚠️
If you are manually computing storage keys for the EVM pallet (e.g., directly querying chain state), you must update your code to use the new storage prefix:
  - Old prefix: twox128("Evm") = 0x8b90cb...
  - New prefix: twox128("EVM") = 0x6a5e91...

  All EVM-facing interfaces remain unchanged.
2025-10-10 17:48:52 +00:00

862 lines
29 KiB
Rust

// Copyright 2019-2025 PureStake Inc.
// This file is part of Moonbeam.
// Moonbeam is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Moonbeam is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Moonbeam. If not, see <http://www.gnu.org/licenses/>.
use crate::{
assert_event_emitted, assert_event_not_emitted,
mock::{
AccountId, ExtBuilder, PCall, PrecompilesValue, ProxyType, Runtime, RuntimeCall,
RuntimeEvent, RuntimeOrigin,
},
};
use frame_support::assert_ok;
use pallet_evm::Call as EvmCall;
use pallet_proxy::{
Call as ProxyCall, Event as ProxyEvent, Pallet as ProxyPallet, ProxyDefinition,
};
use precompile_utils::{precompile_set::AddressU64, prelude::*, testing::*};
use sp_core::{Get, H160, H256, U256};
use sp_runtime::traits::Dispatchable;
use std::cell::Cell;
use std::rc::Rc;
use std::str::from_utf8;
#[test]
fn test_selector_less_than_four_bytes_reverts() {
ExtBuilder::default().build().execute_with(|| {
PrecompilesValue::get()
.prepare_test(Alice, Precompile1, vec![1u8, 2, 3])
.execute_reverts(|output| output == b"Tried to read selector out of bounds");
});
}
#[test]
fn test_unimplemented_selector_reverts() {
ExtBuilder::default().build().execute_with(|| {
PrecompilesValue::get()
.prepare_test(Alice, Precompile1, vec![1u8, 2, 3, 4])
.execute_reverts(|output| output == b"Unknown selector");
});
}
#[test]
fn selectors() {
assert!(PCall::add_proxy_selectors().contains(&0x74a34dd3));
assert!(PCall::remove_proxy_selectors().contains(&0xfef3f708));
assert!(PCall::remove_proxies_selectors().contains(&0x14a5b5fa));
assert!(PCall::proxy_selectors().contains(&0x0d3cff86));
assert!(PCall::proxy_force_type_selectors().contains(&0x4a36b2cd));
assert!(PCall::is_proxy_selectors().contains(&0xe26d38ed));
}
#[test]
fn modifiers() {
ExtBuilder::default().build().execute_with(|| {
let mut tester =
PrecompilesModifierTester::new(PrecompilesValue::get(), Alice, Precompile1);
tester.test_default_modifier(PCall::add_proxy_selectors());
tester.test_default_modifier(PCall::remove_proxy_selectors());
tester.test_default_modifier(PCall::remove_proxies_selectors());
tester.test_payable_modifier(PCall::proxy_selectors());
tester.test_payable_modifier(PCall::proxy_force_type_selectors());
tester.test_view_modifier(PCall::is_proxy_selectors());
});
}
#[test]
fn test_add_proxy_fails_if_invalid_value_for_proxy_type() {
ExtBuilder::default()
.with_balances(vec![(Alice.into(), 1000), (Bob.into(), 1000)])
.build()
.execute_with(|| {
PrecompilesValue::get()
.prepare_test(
Alice,
Precompile1,
PCall::add_proxy {
delegate: Address(Bob.into()),
proxy_type: 10,
delay: 0,
},
)
.execute_reverts(|o| o == b"proxyType: Failed decoding value to ProxyType");
})
}
#[test]
fn test_add_proxy_fails_if_duplicate_proxy() {
ExtBuilder::default()
.with_balances(vec![(Alice.into(), 1000), (Bob.into(), 1000)])
.build()
.execute_with(|| {
assert_ok!(RuntimeCall::Proxy(ProxyCall::add_proxy {
delegate: Bob.into(),
proxy_type: ProxyType::Something,
delay: 0,
})
.dispatch(RuntimeOrigin::signed(Alice.into())));
PrecompilesValue::get()
.prepare_test(
Alice,
Precompile1,
PCall::add_proxy {
delegate: Address(Bob.into()),
proxy_type: ProxyType::Something as u8,
delay: 0,
},
)
.execute_reverts(|o| o == b"Cannot add more than one proxy");
})
}
#[test]
fn test_add_proxy_fails_if_less_permissive_proxy() {
ExtBuilder::default()
.with_balances(vec![(Alice.into(), 1000), (Bob.into(), 1000)])
.build()
.execute_with(|| {
assert_ok!(RuntimeCall::Proxy(ProxyCall::add_proxy {
delegate: Bob.into(),
proxy_type: ProxyType::Something,
delay: 0,
})
.dispatch(RuntimeOrigin::signed(Alice.into())));
PrecompilesValue::get()
.prepare_test(
Alice,
Precompile1,
PCall::add_proxy {
delegate: Address(Bob.into()),
proxy_type: ProxyType::Nothing as u8,
delay: 0,
},
)
.execute_reverts(|o| o == b"Cannot add more than one proxy");
})
}
#[test]
fn test_add_proxy_fails_if_more_permissive_proxy() {
ExtBuilder::default()
.with_balances(vec![(Alice.into(), 1000), (Bob.into(), 1000)])
.build()
.execute_with(|| {
assert_ok!(RuntimeCall::Proxy(ProxyCall::add_proxy {
delegate: Bob.into(),
proxy_type: ProxyType::Something,
delay: 0,
})
.dispatch(RuntimeOrigin::signed(Alice.into())));
PrecompilesValue::get()
.prepare_test(
Alice,
Precompile1,
PCall::add_proxy {
delegate: Address(Bob.into()),
proxy_type: ProxyType::Any as u8,
delay: 0,
},
)
.execute_reverts(|o| o == b"Cannot add more than one proxy");
})
}
#[test]
fn test_add_proxy_succeeds() {
ExtBuilder::default()
.with_balances(vec![(Alice.into(), 1000), (Bob.into(), 1000)])
.build()
.execute_with(|| {
PrecompilesValue::get()
.prepare_test(
Alice,
Precompile1,
PCall::add_proxy {
delegate: Address(Bob.into()),
proxy_type: ProxyType::Something as u8,
delay: 1,
},
)
.execute_returns(());
assert_event_emitted!(RuntimeEvent::Proxy(ProxyEvent::ProxyAdded {
delegator: Alice.into(),
delegatee: Bob.into(),
proxy_type: ProxyType::Something,
delay: 1,
}));
let proxies = <ProxyPallet<Runtime>>::proxies(AccountId::from(Alice)).0;
assert_eq!(
proxies,
vec![ProxyDefinition {
delegate: Bob.into(),
proxy_type: ProxyType::Something,
delay: 1,
}],
)
})
}
#[test]
fn test_remove_proxy_fails_if_invalid_value_for_proxy_type() {
ExtBuilder::default()
.with_balances(vec![(Alice.into(), 1000), (Bob.into(), 1000)])
.build()
.execute_with(|| {
assert_ok!(RuntimeCall::Proxy(ProxyCall::add_proxy {
delegate: Bob.into(),
proxy_type: ProxyType::Something,
delay: 0,
})
.dispatch(RuntimeOrigin::signed(Alice.into())));
PrecompilesValue::get()
.prepare_test(
Alice,
Precompile1,
PCall::remove_proxy {
delegate: Address(Bob.into()),
proxy_type: 10,
delay: 0,
},
)
.execute_reverts(|o| o == b"proxyType: Failed decoding value to ProxyType");
})
}
#[test]
fn test_remove_proxy_fails_if_proxy_not_exist() {
ExtBuilder::default()
.with_balances(vec![(Alice.into(), 1000), (Bob.into(), 1000)])
.build()
.execute_with(|| {
PrecompilesValue::get()
.prepare_test(
Alice,
Precompile1,
PCall::remove_proxy {
delegate: Address(Bob.into()),
proxy_type: ProxyType::Something as u8,
delay: 0,
},
)
.execute_reverts(|output| from_utf8(&output).unwrap().contains("NotFound"));
})
}
#[test]
fn test_remove_proxy_succeeds() {
ExtBuilder::default()
.with_balances(vec![(Alice.into(), 1000), (Bob.into(), 1000)])
.build()
.execute_with(|| {
assert_ok!(RuntimeCall::Proxy(ProxyCall::add_proxy {
delegate: Bob.into(),
proxy_type: ProxyType::Something,
delay: 0,
})
.dispatch(RuntimeOrigin::signed(Alice.into())));
PrecompilesValue::get()
.prepare_test(
Alice,
Precompile1,
PCall::remove_proxy {
delegate: Address(Bob.into()),
proxy_type: ProxyType::Something as u8,
delay: 0,
},
)
.execute_returns(());
assert_event_emitted!(RuntimeEvent::Proxy(ProxyEvent::ProxyRemoved {
delegator: Alice.into(),
delegatee: Bob.into(),
proxy_type: ProxyType::Something,
delay: 0,
}));
let proxies = <ProxyPallet<Runtime>>::proxies(AccountId::from(Alice)).0;
assert_eq!(proxies, vec![])
})
}
#[test]
fn test_remove_proxies_succeeds() {
ExtBuilder::default()
.with_balances(vec![(Alice.into(), 1000), (Bob.into(), 1000)])
.build()
.execute_with(|| {
assert_ok!(RuntimeCall::Proxy(ProxyCall::add_proxy {
delegate: Bob.into(),
proxy_type: ProxyType::Something,
delay: 0,
})
.dispatch(RuntimeOrigin::signed(Alice.into())));
assert_ok!(RuntimeCall::Proxy(ProxyCall::add_proxy {
delegate: Charlie.into(),
proxy_type: ProxyType::Any,
delay: 0,
})
.dispatch(RuntimeOrigin::signed(Alice.into())));
PrecompilesValue::get()
.prepare_test(Alice, Precompile1, PCall::remove_proxies {})
.execute_returns(());
let proxies = <ProxyPallet<Runtime>>::proxies(AccountId::from(Alice)).0;
assert_eq!(proxies, vec![])
})
}
#[test]
fn test_remove_proxies_succeeds_when_no_proxy_exists() {
ExtBuilder::default()
.with_balances(vec![(Alice.into(), 1000), (Bob.into(), 1000)])
.build()
.execute_with(|| {
PrecompilesValue::get()
.prepare_test(Alice, Precompile1, PCall::remove_proxies {})
.execute_returns(());
let proxies = <ProxyPallet<Runtime>>::proxies(AccountId::from(Alice)).0;
assert_eq!(proxies, vec![])
})
}
#[test]
fn test_proxy_force_type_fails_if_invalid_value_for_force_proxy_type() {
ExtBuilder::default()
.with_balances(vec![(Alice.into(), 1000), (Bob.into(), 1000)])
.build()
.execute_with(|| {
PrecompilesValue::get()
.prepare_test(
Alice,
Precompile1,
PCall::proxy_force_type {
real: Address(Bob.into()),
force_proxy_type: 10,
call_to: Address(Alice.into()),
call_data: BoundedBytes::from([]),
},
)
.execute_reverts(|o| o == b"forceProxyType: Failed decoding value to ProxyType");
})
}
#[test]
fn test_proxy_fails_if_not_proxy() {
ExtBuilder::default()
.with_balances(vec![(Alice.into(), 1000), (Bob.into(), 1000)])
.build()
.execute_with(|| {
PrecompilesValue::get()
.prepare_test(
Alice,
Precompile1,
PCall::proxy {
real: Address(Bob.into()),
call_to: Address(Alice.into()),
call_data: BoundedBytes::from([]),
},
)
.execute_reverts(|o| o == b"Not proxy");
})
}
#[test]
fn test_proxy_fails_if_call_filtered() {
ExtBuilder::default()
.with_balances(vec![(Alice.into(), 1000), (Bob.into(), 1000)])
.build()
.execute_with(|| {
// add delayed proxy
PrecompilesValue::get()
.prepare_test(
Alice,
Precompile1,
PCall::add_proxy {
delegate: Address(Bob.into()),
proxy_type: 2,
delay: 0,
},
)
.execute_returns(());
// Trying to use delayed proxy without any announcement
PrecompilesValue::get()
.prepare_test(
Bob,
Precompile1,
PCall::proxy {
real: Address(Alice.into()),
call_to: Address(Bob.into()),
call_data: BoundedBytes::from([]),
},
)
.execute_reverts(|o| o == b"CallFiltered");
})
}
#[test]
fn test_is_proxy_returns_false_if_not_proxy() {
ExtBuilder::default()
.with_balances(vec![(Alice.into(), 1000), (Bob.into(), 1000)])
.build()
.execute_with(|| {
PrecompilesValue::get()
.prepare_test(
Alice,
Precompile1,
PCall::is_proxy {
real: Address(Alice.into()),
delegate: Address(Bob.into()),
proxy_type: ProxyType::Something as u8,
delay: 0,
},
)
.execute_returns(false);
})
}
#[test]
fn test_is_proxy_returns_false_if_proxy_type_incorrect() {
ExtBuilder::default()
.with_balances(vec![(Alice.into(), 1000), (Bob.into(), 1000)])
.build()
.execute_with(|| {
assert_ok!(RuntimeCall::Proxy(ProxyCall::add_proxy {
delegate: Bob.into(),
proxy_type: ProxyType::Something,
delay: 0,
})
.dispatch(RuntimeOrigin::signed(Alice.into())));
PrecompilesValue::get()
.prepare_test(
Alice,
Precompile1,
PCall::is_proxy {
real: Address(Alice.into()),
delegate: Address(Bob.into()),
proxy_type: ProxyType::Any as u8,
delay: 0,
},
)
.execute_returns(false);
})
}
#[test]
fn test_is_proxy_returns_false_if_proxy_delay_incorrect() {
ExtBuilder::default()
.with_balances(vec![(Alice.into(), 1000), (Bob.into(), 1000)])
.build()
.execute_with(|| {
assert_ok!(RuntimeCall::Proxy(ProxyCall::add_proxy {
delegate: Bob.into(),
proxy_type: ProxyType::Something,
delay: 1,
})
.dispatch(RuntimeOrigin::signed(Alice.into())));
PrecompilesValue::get()
.prepare_test(
Alice,
Precompile1,
PCall::is_proxy {
real: Address(Alice.into()),
delegate: Address(Bob.into()),
proxy_type: ProxyType::Any as u8,
delay: 0,
},
)
.execute_returns(false);
})
}
#[test]
fn test_is_proxy_returns_true_if_proxy() {
ExtBuilder::default()
.with_balances(vec![(Alice.into(), 1000), (Bob.into(), 1000)])
.build()
.execute_with(|| {
assert_ok!(RuntimeCall::Proxy(ProxyCall::add_proxy {
delegate: Bob.into(),
proxy_type: ProxyType::Something,
delay: 1,
})
.dispatch(RuntimeOrigin::signed(Alice.into())));
PrecompilesValue::get()
.prepare_test(
Alice,
Precompile1,
PCall::is_proxy {
real: Address(Alice.into()),
delegate: Address(Bob.into()),
proxy_type: ProxyType::Something as u8,
delay: 1,
},
)
.execute_returns(true);
})
}
#[test]
fn test_nested_evm_bypass_proxy_should_allow_elevating_proxy_type() {
ExtBuilder::default()
.with_balances(vec![(Alice.into(), 100000000), (Bob.into(), 100000000)])
.build()
.execute_with(|| {
// make Bob a ProxyType::Something for Alice
assert_ok!(RuntimeCall::Proxy(ProxyCall::add_proxy {
delegate: Bob.into(),
proxy_type: ProxyType::Something,
delay: 0,
})
.dispatch(RuntimeOrigin::signed(Alice.into())));
// construct the call wrapping the add_proxy precompile to escalate to ProxyType::Any
let add_proxy_precompile = PCall::add_proxy {
delegate: Address(Bob.into()),
proxy_type: ProxyType::Any as u8,
delay: 0,
}
.into();
let evm_call = RuntimeCall::EVM(EvmCall::call {
source: Alice.into(),
target: Precompile1.into(),
input: add_proxy_precompile,
value: U256::zero(),
gas_limit: u64::max_value(),
max_fee_per_gas: 0.into(),
max_priority_fee_per_gas: Some(U256::zero()),
nonce: None,
access_list: Vec::new(),
});
// call the evm call in a proxy call
assert_ok!(<ProxyPallet<Runtime>>::proxy(
RuntimeOrigin::signed(Bob.into()),
Alice.into(),
None,
Box::new(evm_call)
));
// assert Bob was not assigned ProxyType::Any
assert_event_not_emitted!(RuntimeEvent::Proxy(ProxyEvent::ProxyAdded {
delegator: Alice.into(),
delegatee: Bob.into(),
proxy_type: ProxyType::Any,
delay: 0,
}));
})
}
#[test]
fn fails_if_called_by_smart_contract() {
ExtBuilder::default()
.with_balances(vec![(Alice.into(), 1000), (Bob.into(), 1000)])
.build()
.execute_with(|| {
// Set code to Alice address as it if was a smart contract.
pallet_evm::AccountCodes::<Runtime>::insert(H160::from(Alice), vec![10u8]);
pallet_evm::AccountCodesMetadata::<Runtime>::insert(
H160::from(Alice),
pallet_evm::CodeMetadata {
size: 10,
hash: H256::default(),
},
);
PrecompilesValue::get()
.prepare_test(
Alice,
Precompile1,
PCall::add_proxy {
delegate: Address(Bob.into()),
proxy_type: ProxyType::Something as u8,
delay: 1,
},
)
.execute_reverts(|output| output == b"Function not callable by smart contracts");
})
}
#[test]
fn succeed_if_called_by_precompile() {
ExtBuilder::default()
.with_balances(vec![(Alice.into(), 1000), (Bob.into(), 1000)])
.build()
.execute_with(|| {
// Set dummy code to Alice address as it if was a precompile.
pallet_evm::AccountCodes::<Runtime>::insert(
H160::from(Alice),
vec![0x60, 0x00, 0x60, 0x00, 0xfd],
);
PrecompilesValue::get()
.prepare_test(
Alice,
Precompile1,
PCall::add_proxy {
delegate: Address(Bob.into()),
proxy_type: ProxyType::Something as u8,
delay: 1,
},
)
.execute_returns(());
})
}
#[test]
fn succeed_if_is_proxy_called_by_smart_contract() {
ExtBuilder::default()
.with_balances(vec![(Alice.into(), 1000), (Bob.into(), 1000)])
.build()
.execute_with(|| {
// Set code to Alice address as it if was a smart contract.
pallet_evm::AccountCodes::<Runtime>::insert(H160::from(Alice), vec![10u8]);
PrecompilesValue::get()
.prepare_test(
Alice,
Precompile1,
PCall::is_proxy {
real: Address(Alice.into()),
delegate: Address(Bob.into()),
proxy_type: ProxyType::Something as u8,
delay: 1,
},
)
.execute_returns(false);
})
}
#[test]
fn proxy_proxy_should_fail_if_called_by_precompile() {
ExtBuilder::default()
.with_balances(vec![
(AddressU64::<1>::get().into(), 1000),
(Bob.into(), 1000),
])
.build()
.execute_with(|| {
PrecompilesValue::get()
.prepare_test(
AddressU64::<1>::get(),
Precompile1,
PCall::proxy {
real: Address(Alice.into()),
call_to: Address(Bob.into()),
call_data: BoundedBytes::from([]),
},
)
.execute_reverts(|output| output == b"Function not callable by precompiles");
})
}
#[test]
fn proxy_proxy_should_succeed_if_called_by_allowed_precompile() {
// "Not proxy" means that the security filter has passed, so the call to proxy.proxy would work
// if we had done a proxy.add_proxy before.
ExtBuilder::default()
.with_balances(vec![
(AddressU64::<1>::get().into(), 1000),
(Bob.into(), 1000),
])
.build()
.execute_with(|| {
PrecompilesValue::get()
.prepare_test(
// Address<2> allowed in mock.rs
AddressU64::<2>::get(),
Precompile1,
PCall::proxy {
real: Address(Alice.into()),
call_to: Address(Bob.into()),
call_data: BoundedBytes::from([]),
},
)
.execute_reverts(|output| output == b"Not proxy");
})
}
#[test]
fn proxy_proxy_should_succeed_if_called_by_smart_contract() {
ExtBuilder::default()
.with_balances(vec![
(AddressU64::<1>::get().into(), 1000),
(Bob.into(), 1000),
])
.build()
.execute_with(|| {
// Set code to Alice address as it if was a smart contract.
pallet_evm::AccountCodes::<Runtime>::insert(H160::from(Alice), vec![10u8]);
// Bob allows Alice to make calls on his behalf
assert_ok!(RuntimeCall::Proxy(ProxyCall::add_proxy {
delegate: Alice.into(),
proxy_type: ProxyType::Any,
delay: 0,
})
.dispatch(RuntimeOrigin::signed(Bob.into())));
let inside = Rc::new(Cell::new(false));
let inside2 = inside.clone();
// The smart contract calls proxy.proxy to call address Charlie as if it was Bob
PrecompilesValue::get()
.prepare_test(
Alice,
Precompile1,
PCall::proxy {
real: Address(Bob.into()),
call_to: Address(Charlie.into()),
call_data: BoundedBytes::from([1]),
},
)
.with_subcall_handle(move |subcall| {
let Subcall {
address,
transfer,
input,
target_gas: _,
is_static,
context,
} = subcall;
assert_eq!(context.caller, Bob.into());
assert_eq!(address, Charlie.into());
assert_eq!(is_static, false);
assert!(transfer.is_none());
assert_eq!(context.address, Charlie.into());
assert_eq!(context.apparent_value, 0u8.into());
assert_eq!(&input, &[1]);
inside2.set(true);
SubcallOutput {
output: b"TEST".to_vec(),
cost: 13,
logs: vec![log1(Bob, H256::repeat_byte(0x11), vec![])],
..SubcallOutput::succeed()
}
})
.execute_returns(());
// Ensure that the subcall was actually called.
// proxy.proxy does not propagate the return value, so we cannot check for the return
// value "TEST"
assert!(inside.get(), "subcall not called");
})
}
#[test]
fn proxy_proxy_should_fail_if_called_by_smart_contract_for_a_non_eoa_account() {
ExtBuilder::default()
.with_balances(vec![
(AddressU64::<1>::get().into(), 1000),
(Bob.into(), 1000),
])
.build()
.execute_with(|| {
// Set code to Alice & Bob addresses as if they are smart contracts.
pallet_evm::AccountCodes::<Runtime>::insert(H160::from(Alice), vec![10u8]);
pallet_evm::AccountCodesMetadata::<Runtime>::insert(
H160::from(Alice),
pallet_evm::CodeMetadata {
size: 10,
hash: H256::default(),
},
);
pallet_evm::AccountCodes::<Runtime>::insert(H160::from(Bob), vec![10u8]);
pallet_evm::AccountCodesMetadata::<Runtime>::insert(
H160::from(Bob),
pallet_evm::CodeMetadata {
size: 10,
hash: H256::default(),
},
);
// Bob allows Alice to make calls on his behalf
assert_ok!(RuntimeCall::Proxy(ProxyCall::add_proxy {
delegate: Alice.into(),
proxy_type: ProxyType::Any,
delay: 0,
})
.dispatch(RuntimeOrigin::signed(Bob.into())));
let inside = Rc::new(Cell::new(false));
let inside2 = inside.clone();
// The smart contract calls proxy.proxy to call address Charlie as if it was Bob
PrecompilesValue::get()
.prepare_test(
Alice,
Precompile1,
PCall::proxy {
real: Address(Bob.into()),
call_to: Address(Charlie.into()),
call_data: BoundedBytes::from([1]),
},
)
.with_subcall_handle(move |subcall| {
let Subcall {
address,
transfer,
input,
target_gas: _,
is_static,
context,
} = subcall;
assert_eq!(context.caller, Bob.into());
assert_eq!(address, Charlie.into());
assert_eq!(is_static, false);
assert!(transfer.is_none());
assert_eq!(context.address, Charlie.into());
assert_eq!(context.apparent_value, 0u8.into());
assert_eq!(&input, &[1]);
inside2.set(true);
SubcallOutput {
output: b"TEST".to_vec(),
cost: 13,
logs: vec![log1(Bob, H256::repeat_byte(0x11), vec![])],
..SubcallOutput::succeed()
}
})
.execute_reverts(|output| output == b"real address must be EOA");
})
}
#[test]
fn test_solidity_interface_has_all_function_selectors_documented_and_implemented() {
check_precompile_implements_solidity_interfaces(&["Proxy.sol"], PCall::supports_selector)
}