ghost-node/pallets/slow-clap/src/tests.rs
Uncle Stretch 66719626bb
inital commit, which is clearly not initial
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2024-10-03 15:38:52 +03:00

697 lines
25 KiB
Rust

#![cfg(test)]
use super::*;
use crate::mock::*;
use frame_support::{assert_err, assert_ok};
use sp_core::offchain::{
testing,
testing::TestOffchainExt, OffchainWorkerExt,
// OffchainDbExt, TransactionPoolExt,
};
// use sp_runtime::testing::UintAuthorityId;
fn prepare_networks() -> (u32, u32) {
let network = NetworkData {
chain_name: "Ethereum".into(),
default_endpoint: get_rpc_endpoint(),
finality_delay: Some(69),
release_delay: Some(69),
network_type: ghost_networks::NetworkType::Evm,
gatekeeper: get_gatekeeper(),
topic_name: get_topic_name(),
incoming_fee: 0,
outgoing_fee: 0,
};
assert_ok!(Networks::register_network(
RuntimeOrigin::root(),
get_network_id(),
network));
(1, 69)
}
fn prepare_companion(amount: u64) -> Companion<NetworkIdOf<Runtime>, BalanceOf<Runtime>> {
Companion {
network_id: 1,
receiver: H160::from_low_u64_ne(1337),
fee: H256::from_low_u64_ne(40_000),
amount: amount,
}
}
#[test]
fn test_throttling_slash_function() {
let dummy_offence = ThrottlingOffence {
session_index: 0,
validator_set_count: 50,
offenders: vec![()],
};
assert_eq!(dummy_offence.slash_fraction(1), Perbill::zero());
assert_eq!(dummy_offence.slash_fraction(5), Perbill::zero());
assert_eq!(dummy_offence.slash_fraction(7), Perbill::from_parts(4200000));
assert_eq!(dummy_offence.slash_fraction(17), Perbill::from_parts(46200000));
}
#[test]
fn propose_companion_works_as_expected() {
new_test_ext().execute_with(|| {
let actor = alice_account_id();
let companion_id = SlowClap::current_companion_id();
let (valid_network_id, _) = prepare_networks();
let companion = prepare_companion(100);
assert_eq!(Balances::balance(&actor), 100);
assert_eq!(Balances::total_issuance(), 100);
assert_eq!(SlowClap::companions(valid_network_id, companion_id), None);
assert_eq!(SlowClap::companion_details(companion_id), None);
assert_eq!(SlowClap::current_companion_id(), companion_id);
assert_ok!(SlowClap::propose_companion(RuntimeOrigin::signed(actor), valid_network_id, companion.clone()));
assert_eq!(Balances::balance(&actor), 0);
assert_eq!(Balances::total_issuance(), 0);
assert_eq!(SlowClap::companions(valid_network_id, companion_id), Some(actor));
assert_eq!(SlowClap::companion_details(companion_id), Some(companion));
assert_eq!(SlowClap::current_companion_id(), companion_id + 1);
});
}
#[test]
fn propose_companion_emits_event() {
new_test_ext().execute_with(|| {
let actor = alice_account_id();
let companion_id = SlowClap::current_companion_id();
let (valid_network_id, _) = prepare_networks();
let companion = prepare_companion(100);
System::reset_events();
assert_ok!(SlowClap::propose_companion(
RuntimeOrigin::signed(actor),
valid_network_id, companion.clone()));
System::assert_has_event(RuntimeEvent::SlowClap(
crate::Event::CompanionCreated {
companion_id,
owner: actor,
companion,
}));
});
}
#[test]
fn could_not_propose_if_not_enough_funds() {
new_test_ext().execute_with(|| {
let actor = alice_account_id();
let (valid_network_id, _) = prepare_networks();
let companion = prepare_companion(1_000);
assert_err!(SlowClap::propose_companion(
RuntimeOrigin::signed(actor),
valid_network_id, companion.clone()),
sp_runtime::TokenError::FundsUnavailable);
let companion = prepare_companion(100 / 2);
let prev_balance = Balances::balance(&actor);
assert_ok!(SlowClap::propose_companion(
RuntimeOrigin::signed(actor),
valid_network_id, companion.clone()));
assert_eq!(Balances::balance(&actor), prev_balance / 2);
});
}
#[test]
fn companion_amount_should_be_existent() {
new_test_ext().execute_with(|| {
let actor = alice_account_id();
let (valid_network_id, _) = prepare_networks();
let companion = prepare_companion(1);
assert_err!(SlowClap::propose_companion(
RuntimeOrigin::signed(actor),
valid_network_id, companion.clone()),
crate::Error::<Runtime>::CompanionAmountNotExistent);
});
}
#[test]
fn could_not_register_companion_if_network_not_registered() {
new_test_ext().execute_with(|| {
let actor = alice_account_id();
let (_, invalid_network_id) = prepare_networks();
let companion = prepare_companion(100);
assert_err!(SlowClap::propose_companion(
RuntimeOrigin::signed(actor),
invalid_network_id,
companion.clone()),
crate::Error::<Runtime>::NonRegisteredNetwork);
});
}
#[test]
fn release_companion_works() {
new_test_ext().execute_with(|| {
let actor = alice_account_id();
let companion_id = SlowClap::current_companion_id();
let (valid_network_id, _) = prepare_networks();
let companion = prepare_companion(50);
assert_ok!(SlowClap::propose_companion(
RuntimeOrigin::signed(actor),
valid_network_id,
companion.clone()));
assert_eq!(SlowClap::release_blocks(companion_id), 0);
assert_ok!(SlowClap::release_companion(
RuntimeOrigin::signed(actor),
valid_network_id, companion_id));
assert_eq!(SlowClap::release_blocks(companion_id), 69 + 1);
});
}
#[test]
fn release_companion_emits_event() {
new_test_ext().execute_with(|| {
let actor = alice_account_id();
let companion_id = SlowClap::current_companion_id();
let (valid_network_id, _) = prepare_networks();
let companion = prepare_companion(50);
assert_ok!(SlowClap::propose_companion(
RuntimeOrigin::signed(actor),
valid_network_id, companion.clone()));
System::reset_events();
assert_ok!(SlowClap::release_companion(
RuntimeOrigin::signed(actor),
valid_network_id, companion_id));
System::assert_has_event(RuntimeEvent::SlowClap(
crate::Event::CompanionReleased {
companion_id,
network_id: valid_network_id,
who: actor,
release_block: 69 + 1 }));
});
}
#[test]
fn could_not_release_from_random_account() {
new_test_ext().execute_with(|| {
let actor = alice_account_id();
let companion_id = SlowClap::current_companion_id();
let (valid_network_id, _) = prepare_networks();
let companion = prepare_companion(50);
assert_ok!(SlowClap::propose_companion(
RuntimeOrigin::signed(actor),
valid_network_id, companion.clone()));
assert_err!(SlowClap::release_companion(
RuntimeOrigin::signed(eve_account_id()),
valid_network_id, companion_id),
crate::Error::<Runtime>::NotValidCompanion);
});
}
#[test]
fn kill_companion_works() {
new_test_ext().execute_with(|| {
let actor = alice_account_id();
let companion_id = SlowClap::current_companion_id();
let (valid_network_id, _) = prepare_networks();
let companion = prepare_companion(50);
assert_ok!(SlowClap::propose_companion(
RuntimeOrigin::signed(actor),
valid_network_id, companion.clone()));
assert_ok!(SlowClap::release_companion(
RuntimeOrigin::signed(actor),
valid_network_id, companion_id));
let release_block = ReleaseBlocks::<Runtime>::get(companion_id);
assert_eq!(SlowClap::companions(valid_network_id, companion_id), Some(actor));
assert_eq!(SlowClap::companion_details(companion_id), Some(companion));
assert_eq!(Balances::balance(&actor), 50);
assert_eq!(Balances::total_issuance(), 50);
System::set_block_number(release_block + 1);
assert_ok!(SlowClap::kill_companion(
RuntimeOrigin::signed(actor),
valid_network_id, companion_id));
assert_eq!(SlowClap::companions(valid_network_id, companion_id), None);
assert_eq!(SlowClap::companion_details(companion_id), None);
assert_eq!(Balances::balance(&actor), 100);
assert_eq!(Balances::total_issuance(), 100);
});
}
#[test]
fn kill_companion_emits_event() {
new_test_ext().execute_with(|| {
let actor = alice_account_id();
let companion_id = SlowClap::current_companion_id();
let (valid_network_id, _) = prepare_networks();
let companion = prepare_companion(50);
assert_ok!(SlowClap::propose_companion(
RuntimeOrigin::signed(actor),
valid_network_id, companion.clone()));
assert_ok!(SlowClap::release_companion(
RuntimeOrigin::signed(actor),
valid_network_id, companion_id));
System::set_block_number(
ReleaseBlocks::<Runtime>::get(companion_id) + 1);
System::reset_events();
assert_ok!(SlowClap::kill_companion(
RuntimeOrigin::signed(actor),
valid_network_id, companion_id));
System::assert_has_event(RuntimeEvent::SlowClap(
crate::Event::CompanionKilled {
network_id: valid_network_id,
who: actor,
companion_id,
freed_balance: 50,
}));
});
}
#[test]
fn could_not_kill_companion_that_was_not_released() {
new_test_ext().execute_with(|| {
let actor = alice_account_id();
let companion_id = SlowClap::current_companion_id();
let (valid_network_id, _) = prepare_networks();
let companion = prepare_companion(50);
assert_ok!(SlowClap::propose_companion(
RuntimeOrigin::signed(actor),
valid_network_id, companion.clone()));
assert_ok!(SlowClap::release_companion(
RuntimeOrigin::signed(actor),
valid_network_id, companion_id));
let release_block = ReleaseBlocks::<Runtime>::get(companion_id);
assert_eq!(SlowClap::companions(valid_network_id, companion_id), Some(actor));
assert_eq!(SlowClap::companion_details(companion_id), Some(companion.clone()));
assert_eq!(Balances::balance(&actor), 50);
assert_eq!(Balances::total_issuance(), 50);
System::set_block_number(release_block / 2);
assert_err!(SlowClap::kill_companion(
RuntimeOrigin::signed(actor),
valid_network_id, companion_id),
crate::Error::<Runtime>::ReleaseTimeHasNotCome);
assert_eq!(SlowClap::companions(valid_network_id, companion_id), Some(actor));
assert_eq!(SlowClap::companion_details(companion_id), Some(companion));
assert_eq!(Balances::balance(&actor), 50);
assert_eq!(Balances::total_issuance(), 50);
});
}
#[test]
fn could_not_kill_companion_from_random_account() {
new_test_ext().execute_with(|| {
let actor = alice_account_id();
let companion_id = SlowClap::current_companion_id();
let (valid_network_id, _) = prepare_networks();
let companion = prepare_companion(50);
assert_ok!(SlowClap::propose_companion(
RuntimeOrigin::signed(actor),
valid_network_id, companion.clone()));
assert_ok!(SlowClap::release_companion(
RuntimeOrigin::signed(actor),
valid_network_id, companion_id));
let release_block = ReleaseBlocks::<Runtime>::get(companion_id);
assert_eq!(SlowClap::companions(valid_network_id, companion_id), Some(actor));
assert_eq!(SlowClap::companion_details(companion_id), Some(companion.clone()));
assert_eq!(Balances::balance(&actor), 50);
assert_eq!(Balances::total_issuance(), 50);
System::set_block_number(release_block + 1);
assert_err!(SlowClap::kill_companion(
RuntimeOrigin::signed(eve_account_id()),
valid_network_id, companion_id),
crate::Error::<Runtime>::NotValidCompanion);
assert_eq!(SlowClap::companions(valid_network_id, companion_id), Some(actor));
assert_eq!(SlowClap::companion_details(companion_id), Some(companion));
assert_eq!(Balances::balance(&actor), 50);
assert_eq!(Balances::total_issuance(), 50);
});
}
#[test]
fn request_body_is_correct_for_get_block_number() {
let (offchain, _) = TestOffchainExt::new();
let mut t = sp_io::TestExternalities::default();
t.register_extension(OffchainWorkerExt::new(offchain));
t.execute_with(|| {
let request_body = SlowClap::prepare_request_body(
None, 0, Vec::new(), Vec::new());
assert_eq!(core::str::from_utf8(&request_body).unwrap(),
r#"{"id":0,"jsonrpc":"2.0","method":"eth_blockNumber"}"#);
});
}
#[test]
fn request_body_is_correct_for_get_logs() {
let (offchain, _) = TestOffchainExt::new();
let mut t = sp_io::TestExternalities::default();
t.register_extension(OffchainWorkerExt::new(offchain));
t.execute_with(|| {
let request_body = SlowClap::prepare_request_body(
Some(1337), 69, get_gatekeeper(), get_topic_name());
assert_eq!(
core::str::from_utf8(&request_body).unwrap(),
r#"{"id":0,"jsonrpc":"2.0","method":"eth_getLogs","params":[{"fromBlock":1268,"toBlock":1337,"address":"0x4d224452801ACEd8B2F0aebE155379bb5D594381","topics":["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"]}]}"#,
);
});
}
#[test]
fn should_make_http_call_for_block_number() {
let (offchain, state) = TestOffchainExt::new();
let mut t = sp_io::TestExternalities::default();
t.register_extension(OffchainWorkerExt::new(offchain));
evm_block_response(&mut state.write());
t.execute_with(|| {
let request_body = SlowClap::prepare_request_body(
None, 0, Vec::new(), Vec::new());
let raw_response = SlowClap::fetch_from_remote(
get_rpc_endpoint(), request_body).unwrap();
assert_eq!(raw_response.len(), 45usize); // precalculated
});
}
#[test]
fn should_make_http_call_for_logs() {
let (offchain, state) = TestOffchainExt::new();
let mut t = sp_io::TestExternalities::default();
t.register_extension(OffchainWorkerExt::new(offchain));
evm_logs_response(&mut state.write());
t.execute_with(|| {
let request_body = SlowClap::prepare_request_body(
Some(20335857), 84, get_gatekeeper(), get_topic_name());
let raw_response = SlowClap::fetch_from_remote(
get_rpc_endpoint(), request_body).unwrap();
assert_eq!(raw_response.len(), 1805); // precalculated
});
}
#[test]
fn should_make_http_call_and_parse_block_number() {
let (offchain, state) = TestOffchainExt::new();
let mut t = sp_io::TestExternalities::default();
t.register_extension(OffchainWorkerExt::new(offchain));
evm_block_response(&mut state.write());
t.execute_with(|| {
let evm_response = SlowClap::fetch_and_parse(
None, 0, Vec::<u8>::new(),
Vec::<u8>::new(), get_rpc_endpoint()).unwrap();
let evm_block_number = match evm_response {
EvmResponseType::BlockNumber(evm_block) => Some(evm_block),
_ => None,
};
assert_eq!(evm_block_number.unwrap_or_default(), 20335745);
});
}
#[test]
fn should_make_http_call_and_parse_logs() {
let (offchain, state) = TestOffchainExt::new();
let mut t = sp_io::TestExternalities::default();
t.register_extension(OffchainWorkerExt::new(offchain));
evm_logs_response(&mut state.write());
let expected_logs = get_expected_logs();
t.execute_with(|| {
let evm_response = SlowClap::fetch_and_parse(
Some(20335857), 84, get_gatekeeper(),
get_topic_name(), get_rpc_endpoint()
).unwrap();
let evm_logs = match evm_response {
EvmResponseType::TransactionLogs(logs) => Some(logs),
_ => None,
};
assert!(evm_logs.is_some());
let evm_logs = evm_logs.unwrap();
assert_eq!(evm_logs, expected_logs);
});
}
#[test]
fn should_catch_error_in_json_response() {
let (offchain, state) = TestOffchainExt::new();
let mut t = sp_io::TestExternalities::default();
t.register_extension(OffchainWorkerExt::new(offchain));
evm_error_response(&mut state.write());
t.execute_with(|| {
assert_err!(SlowClap::fetch_and_parse(
None, 0, Vec::<u8>::new(),
Vec::<u8>::new(), get_rpc_endpoint()),
OffchainErr::ErrorInEvmResponse);
});
}
// #[test]
// fn conversion_to_claps_is_correct() {
// todo!();
// }
//
// #[test]
// fn evm_block_storage_is_empty_by_default() {
// todo!();
// }
//
// #[test]
// fn evm_block_is_stored_locally_after_first_response() {
// todo!();
// }
//
// #[test]
// fn evm_block_storage_is_none_after_logs() {
// todo!();
// }
//
// #[test]
// fn send_evm_usigned_transaction_from_authority() {
// todo!();
// }
// #[test]
// fn should_report_offline_validators() {
// new_test_ext().execute_with(|| {
// let block = 1;
// System::set_block_number(block);
// advance_session();
// let validators = vec![1, 2, 3, 4, 5, 6];
// Validators::mutate(|l| *l = Some(validators.clone()));
// advance_session();
//
// advance_session();
//
// let offences = Offences::take();
// assert_eq!(
// offences,
// vec![(
// vec![],
// ThrottlingOffence {
// session_index: 2,
// validator_set_count: 3,
// offenders: vec![(1, 1), (2, 2), (3, 3)],
// }
// )]
// );
//
// for (idx, v) in validators.into_iter().take(4).enumerate() {
// let _ = clap();
// }
//
// advance_session();
//
// let offences = Offences::take();
// assert_eq!(
// offences,
// vec![(
// vec![],
// ThrottlingOffence {
// session_index: 3,
// validator_set_count: 6,
// offenders: vec![(5, 5), (6, 6)],
// }
// )]
// );
// });
// }
//
// #[test]
// fn should_increase_actions_of_validators_when_clap_is_received() {
// }
//
// #[test]
// fn same_claps_should_not_increase_actions() {
// }
//
// #[test]
// fn should_cleanup_received_claps_on_session_end() {
// }
//
// #[test]
// fn should_mark_validator_if_disabled() {
// }
fn get_rpc_endpoint() -> Vec<u8> {
b"https://nd-422-757-666.p2pify.com/0a9d79d93fb2f4a4b1e04695da2b77a7/".to_vec()
}
fn get_gatekeeper() -> Vec<u8> {
b"0x4d224452801ACEd8B2F0aebE155379bb5D594381".to_vec()
}
fn get_topic_name() -> Vec<u8> {
b"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef".to_vec()
}
fn get_network_id() -> u32 {
// let mut network_id: NetworkIdOf<Runtime> = Default::default();
// network_id.saturating_inc();
// network_id
1u32
}
fn evm_block_response(state: &mut testing::OffchainState) {
let expected_body = br#"{"id":0,"jsonrpc":"2.0","method":"eth_blockNumber"}"#.to_vec();
state.expect_request(testing::PendingRequest {
method: "POST".into(),
uri: "https://nd-422-757-666.p2pify.com/0a9d79d93fb2f4a4b1e04695da2b77a7/".into(),
headers: vec![
("ACCEPT".to_string(), "APPLICATION/JSON".to_string()),
("CONTENT-TYPE".to_string(), "APPLICATION/JSON".to_string()),
],
response: Some(b"{\"id\":0,\"jsonrpc\":\"2.0\",\"result\":\"0x1364c81\"}".to_vec()),
body: expected_body,
sent: true,
..Default::default()
});
}
fn evm_error_response(state: &mut testing::OffchainState) {
let expected_body = br#"{"id":0,"jsonrpc":"2.0","method":"eth_blockNumber"}"#.to_vec();
state.expect_request(testing::PendingRequest {
method: "POST".into(),
uri: "https://nd-422-757-666.p2pify.com/0a9d79d93fb2f4a4b1e04695da2b77a7/".into(),
headers: vec![
("ACCEPT".to_string(), "APPLICATION/JSON".to_string()),
("CONTENT-TYPE".to_string(), "APPLICATION/JSON".to_string()),
],
response: Some(b"{\"id\":0,\"jsonrpc\":\"2.0\",\"error\":\"some bad error occures :-(\"}".to_vec()),
body: expected_body,
sent: true,
..Default::default()
});
}
fn evm_logs_response(state: &mut testing::OffchainState) {
let expected_body = br#"{"id":0,"jsonrpc":"2.0","method":"eth_getLogs","params":[{"fromBlock":20335773,"toBlock":20335857,"address":"0x4d224452801ACEd8B2F0aebE155379bb5D594381","topics":["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"]}]}"#.to_vec();
let expected_response = br#"{
"jsonrpc":"2.0",
"id":0,
"result":[
{
"address":"0x4d224452801aced8b2f0aebe155379bb5d594381",
"topics":[
"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
"0x000000000000000000000000d91efec7e42f80156d1d9f660a69847188950747",
"0x0000000000000000000000005e611dfbe71b988cf2a3e5fe4191b5e3d4c4212a"
],
"data":"0x000000000000000000000000000000000000000000000046f0d522a71fdac000",
"blockNumber":"0x1364c9d",
"transactionHash":"0xa82f2fe87f4ba01ab3bd2cd4d0fe75a26f0e9a37e2badc004a0e38f9d088a758",
"transactionIndex":"0x11",
"blockHash":"0x4b08f82d5a948c0bc5c23c8ddce55e5b4536d7454be53668bbd985ef13dca04a",
"logIndex":"0x92",
"removed":false
},
{
"address":"0x4d224452801aced8b2f0aebe155379bb5d594381",
"topics":[
"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
"0x0000000000000000000000005954ab967bc958940b7eb73ee84797dc8a2afbb9",
"0x000000000000000000000000465165be7cacdbd2cbc8334f549fab9783ad6e7a"
],
"data":"0x0000000000000000000000000000000000000000000000027c790defec959e75",
"blockNumber":"0x1364cf1",
"transactionHash":"0xd9f02b79a90db7536b0afb5e24bbc1f4319dc3d8c57af7c39941acd249ec053a",
"transactionIndex":"0x2c",
"blockHash":"0x6876b18f5081b5d24d5a73107a667a7713256f6e51edbbe52bf8df24e340b0f7",
"logIndex":"0x14b",
"removed":false
}
]
}"#.to_vec();
state.expect_request(testing::PendingRequest {
method: "POST".into(),
uri: "https://nd-422-757-666.p2pify.com/0a9d79d93fb2f4a4b1e04695da2b77a7/".into(),
headers: vec![
("ACCEPT".to_string(), "APPLICATION/JSON".to_string()),
("CONTENT-TYPE".to_string(), "APPLICATION/JSON".to_string()),
],
body: expected_body,
response: Some(expected_response),
sent: true,
..Default::default()
});
}
fn get_expected_logs() -> Vec<Log> {
let byte_converter = |s: &str| -> Vec<u8> {
(2..s.len())
.step_by(2)
.map(|i| u8::from_str_radix(&s[i..i+2], 16).expect("valid u8 symbol; qed"))
.collect()
};
vec![
Log {
transaction_hash: Some(H256::from_slice(&byte_converter("0xa82f2fe87f4ba01ab3bd2cd4d0fe75a26f0e9a37e2badc004a0e38f9d088a758"))),
block_number: Some(20335773),
topics: vec![
byte_converter("0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"),
byte_converter("0x000000000000000000000000d91efec7e42f80156d1d9f660a69847188950747"),
byte_converter("0x0000000000000000000000005e611dfbe71b988cf2a3e5fe4191b5e3d4c4212a"),
],
address: Some(b"0x4d224452801aced8b2f0aebe155379bb5d594381".to_vec()),
data: sp_std::collections::btree_map::BTreeMap::from([(0, 1308625900000000000000)]),
removed: false,
},
Log {
transaction_hash: Some(H256::from_slice(&byte_converter("0xd9f02b79a90db7536b0afb5e24bbc1f4319dc3d8c57af7c39941acd249ec053a"))),
block_number: Some(20335857),
topics: vec![
byte_converter("0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"),
byte_converter("0x0000000000000000000000005954ab967bc958940b7eb73ee84797dc8a2afbb9"),
byte_converter("0x000000000000000000000000465165be7cacdbd2cbc8334f549fab9783ad6e7a"),
],
address: Some(b"0x4d224452801aced8b2f0aebe155379bb5d594381".to_vec()),
data: sp_std::collections::btree_map::BTreeMap::from([(0, 45862703604421729909)]),
removed: false,
},
]
}