stylus_tools/core/
activation.rs1use alloy::{
10 primitives::{utils::parse_ether, Address, Bytes, U256},
11 providers::{Provider, WalletProvider},
12 rpc::types::{
13 state::{AccountOverride, StateOverride},
14 TransactionReceipt,
15 },
16};
17
18use crate::{
19 precompiles,
20 utils::{
21 bump_data_fee,
22 color::{DebugColor, GREY, LAVENDER},
23 format_data_fee,
24 },
25};
26
27#[derive(Debug)]
28pub struct ActivationConfig {
29 pub data_fee_bump_percent: u64,
30}
31
32impl Default for ActivationConfig {
33 fn default() -> Self {
34 Self {
35 data_fee_bump_percent: 20,
36 }
37 }
38}
39
40#[derive(Debug, thiserror::Error)]
41pub enum ActivationError {
42 #[error("{0}")]
43 Contract(alloy::contract::Error),
44 #[error("{0}")]
45 PendingTransaction(#[from] alloy::providers::PendingTransactionError),
46 #[error("{0}")]
47 Rpc(#[from] alloy::transports::RpcError<alloy::transports::TransportErrorKind>),
48
49 #[error(
50 "Contract could not be activated as it is missing an entrypoint. \
51 Please ensure that your contract has an #[entrypoint] defined on your main struct"
52 )]
53 MissingEntrypoint,
54}
55
56impl From<alloy::contract::Error> for ActivationError {
57 fn from(err: alloy::contract::Error) -> Self {
58 if err.to_string().contains("pay_for_memory_grow") {
59 Self::MissingEntrypoint
60 } else {
61 Self::Contract(err)
62 }
63 }
64}
65
66pub async fn activate_contract(
68 address: Address,
69 config: &ActivationConfig,
70 provider: &(impl Provider + WalletProvider),
71) -> Result<TransactionReceipt, ActivationError> {
72 let code = provider.get_code_at(address).await?;
73 let from_address = provider.default_signer_address();
74 let data_fee = data_fee(code, address, config, &provider).await?;
75
76 let receipt = precompiles::arb_wasm(&provider)
77 .activateProgram(address)
78 .from(from_address)
79 .value(data_fee)
80 .send()
81 .await?
82 .get_receipt()
83 .await?;
84
85 info!(@grey,
86 "successfully activated contract 0x{} with tx {}",
87 hex::encode(address),
88 hex::encode(receipt.transaction_hash).debug_lavender()
89 );
90
91 Ok(receipt)
92}
93
94pub async fn data_fee(
96 code: impl Into<Bytes>,
97 address: Address,
98 config: &ActivationConfig,
99 provider: &impl Provider,
100) -> Result<U256, ActivationError> {
101 let arbwasm = precompiles::arb_wasm(provider);
102 let random_sender_addr = Address::random();
103 let spoofed_sender_account = AccountOverride::default().with_balance(U256::MAX);
104 let spoofed_code = AccountOverride::default().with_code(code);
105 let state_override = StateOverride::from_iter([
106 (address, spoofed_code),
107 (random_sender_addr, spoofed_sender_account),
108 ]);
109
110 let result = arbwasm
111 .activateProgram(address)
112 .state(state_override)
113 .from(random_sender_addr)
114 .value(parse_ether("1").unwrap())
115 .call()
116 .await?;
117
118 let data_fee = result.dataFee;
119 let bump = config.data_fee_bump_percent;
120 let adjusted = bump_data_fee(data_fee, bump);
121 info!(@grey,
122 "wasm data fee: {} {GREY}(originally {}{GREY} with {LAVENDER}{bump}%{GREY} bump)",
123 format_data_fee(adjusted),
124 format_data_fee(data_fee)
125 );
126
127 Ok(adjusted)
128}
129
130pub async fn estimate_gas(
132 address: Address,
133 config: &ActivationConfig,
134 provider: &(impl Provider + WalletProvider),
135) -> Result<u64, ActivationError> {
136 let code = provider.get_code_at(address).await?;
137 let from_address = provider.default_signer_address();
138 let data_fee = data_fee(code, address, config, &provider).await?;
139
140 let gas = precompiles::arb_wasm(&provider)
141 .activateProgram(address)
142 .from(from_address)
143 .value(data_fee)
144 .estimate_gas()
145 .await?;
146
147 Ok(gas)
148}