use alloy_evm::Database;
use alloy_primitives::{address, Address};
use revm::{database::State, state::EvmState};
use crate::{MegaHardforks, SystemContractSpec};
pub use crate::sandbox::{
calculate_keyless_deploy_address, decode_keyless_tx, encode_error_result, recover_signer,
KeylessDeployError,
};
pub const KEYLESS_DEPLOY_ADDRESS: Address = address!("0x6342000000000000000000000000000000000003");
pub use mega_system_contracts::keyless_deploy::V1_0_0_CODE as KEYLESS_DEPLOY_CODE;
pub use mega_system_contracts::keyless_deploy::V1_0_0_CODE_HASH as KEYLESS_DEPLOY_CODE_HASH;
pub use mega_system_contracts::keyless_deploy::IKeylessDeploy;
pub fn transact_deploy_keyless_deploy_contract<DB: Database>(
hardforks: impl MegaHardforks,
block_timestamp: u64,
db: &mut State<DB>,
) -> Result<Option<EvmState>, DB::Error> {
keyless_deploy_spec(&hardforks, block_timestamp)
.map(|s| crate::transact_deploy(db, &s))
.transpose()
}
pub(crate) fn keyless_deploy_spec(
hardforks: &impl MegaHardforks,
block_timestamp: u64,
) -> Option<SystemContractSpec> {
hardforks.is_rex_2_active_at_timestamp(block_timestamp).then(|| {
SystemContractSpec::new(
KEYLESS_DEPLOY_ADDRESS,
KEYLESS_DEPLOY_CODE,
KEYLESS_DEPLOY_CODE_HASH,
)
})
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{MegaHardfork, MegaHardforkConfig};
use revm::{
database::InMemoryDB,
state::{AccountInfo, Bytecode},
};
#[test]
fn test_deploy_keyless_deploy_contract_on_fresh_db() {
let mut db = InMemoryDB::default();
let mut state = State::builder().with_database(&mut db).build();
let hardforks = MegaHardforkConfig::default().with_all_activated();
let result = transact_deploy_keyless_deploy_contract(&hardforks, 0, &mut state)
.expect("Deployment should succeed")
.expect("Should return state");
let account = result.get(&KEYLESS_DEPLOY_ADDRESS).expect("Account should exist");
assert!(account.is_touched());
assert!(account.is_created(), "fresh deploy must mark account as created");
assert_eq!(account.info.code_hash, KEYLESS_DEPLOY_CODE_HASH);
}
#[test]
fn test_deploy_keyless_deploy_contract_existing_account_not_marked_created() {
let wrong_code = alloy_primitives::bytes!("0x6000");
let wrong_code_hash = alloy_primitives::keccak256(&wrong_code);
let mut db = InMemoryDB::default();
db.insert_account_info(
KEYLESS_DEPLOY_ADDRESS,
AccountInfo {
balance: revm::primitives::U256::ZERO,
nonce: 0,
code_hash: wrong_code_hash,
code: Some(Bytecode::new_raw(wrong_code)),
},
);
let mut state = State::builder().with_database(&mut db).build();
let hardforks = MegaHardforkConfig::default().with_all_activated();
let result = transact_deploy_keyless_deploy_contract(&hardforks, 0, &mut state)
.expect("Deployment should succeed")
.expect("Should return state");
let account = result.get(&KEYLESS_DEPLOY_ADDRESS).expect("Account should exist");
assert_eq!(account.info.code_hash, KEYLESS_DEPLOY_CODE_HASH);
assert!(account.is_touched());
assert!(
!account.is_created(),
"in-place bytecode update of an existing account must not mark it as created"
);
}
#[test]
fn test_deploy_keyless_deploy_contract_requires_rex2() {
let mut db = InMemoryDB::default();
let mut state = State::builder().with_database(&mut db).build();
let hardforks =
MegaHardforkConfig::default().with_all_activated().without(MegaHardfork::Rex2);
let result = transact_deploy_keyless_deploy_contract(&hardforks, 0, &mut state)
.expect("Should succeed");
assert_eq!(result, None, "should not deploy before Rex2 activation");
}
}