neo_devpack_solidity/solidity/
upgrade.rs1use once_cell::sync::Lazy;
2use regex::Regex;
3
4#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize)]
5#[serde(rename_all = "snake_case")]
6pub enum UpgradeSeverity {
7 Info,
8 Warning,
9 Error,
10}
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize)]
13#[serde(rename_all = "snake_case")]
14pub enum UpgradeCategory {
15 AutoCompatible,
16 ManualMigration,
17 ManifestReview,
18}
19
20#[derive(Debug, Clone, serde::Serialize)]
21#[serde(rename_all = "camelCase")]
22pub struct UpgradeFinding {
23 pub phase: String,
24 pub severity: UpgradeSeverity,
25 pub category: UpgradeCategory,
26 pub code: Option<String>,
27 pub contract: Option<String>,
28 pub message: String,
29 pub suggestion: Option<String>,
30}
31
32impl UpgradeFinding {
33 #[allow(clippy::too_many_arguments)]
34 pub fn new(
35 phase: impl Into<String>,
36 severity: UpgradeSeverity,
37 category: UpgradeCategory,
38 code: Option<impl Into<String>>,
39 contract: Option<impl Into<String>>,
40 message: impl Into<String>,
41 suggestion: Option<impl Into<String>>,
42 ) -> Self {
43 Self {
44 phase: phase.into(),
45 severity,
46 category,
47 code: code.map(Into::into),
48 contract: contract.map(Into::into),
49 message: message.into(),
50 suggestion: suggestion.map(Into::into),
51 }
52 }
53}
54
55pub fn analyze_upgrade_patterns(source: &str) -> Vec<UpgradeFinding> {
56 static BLOCKHASH_RE: Lazy<Regex> = Lazy::new(|| Regex::new(r"\bblockhash\s*\(").unwrap());
57 static SELFDESTRUCT_RE: Lazy<Regex> = Lazy::new(|| Regex::new(r"\bselfdestruct\s*\(").unwrap());
58 static CODEHASH_RE: Lazy<Regex> = Lazy::new(|| Regex::new(r"\.codehash\b").unwrap());
59 static TX_ORIGIN_RE: Lazy<Regex> = Lazy::new(|| Regex::new(r"\btx\.origin\b").unwrap());
60 static MSG_SIG_RE: Lazy<Regex> = Lazy::new(|| Regex::new(r"\bmsg\.sig\b").unwrap());
61 static DELEGATECALL_RE: Lazy<Regex> =
62 Lazy::new(|| Regex::new(r"(?:\.|\b)delegatecall\s*\(").unwrap());
63 static STATICCALL_RE: Lazy<Regex> =
64 Lazy::new(|| Regex::new(r"(?:\.|\b)staticcall\s*\(").unwrap());
65 static LOW_LEVEL_CALL_RE: Lazy<Regex> =
66 Lazy::new(|| Regex::new(r"\.\s*call\s*(?:\(|\{)").unwrap());
67 static ETHER_UNIT_RE: Lazy<Regex> = Lazy::new(|| {
75 Regex::new(r"(?:\d|\))\s*(?:wei|gwei|szabo|finney|ether)\b").unwrap()
76 });
77 static SUPPORTS_INTERFACE_RE: Lazy<Regex> =
78 Lazy::new(|| Regex::new(r"\bsupportsInterface\s*\(").unwrap());
79
80 let mut findings = Vec::new();
81
82 if BLOCKHASH_RE.is_match(source) {
83 findings.push(UpgradeFinding::new(
84 "source_scan",
85 UpgradeSeverity::Warning,
86 UpgradeCategory::AutoCompatible,
87 Some("SCAN_BLOCKHASH_AUTO"),
88 None::<String>,
89 "blockhash() is compiler-compatible on Neo N3 and is auto-mapped to Ledger.getBlockHash().",
90 Some("Prefer explicit Ledger.getBlockHash(height) when authoring Neo-native Solidity."),
91 ));
92 }
93
94 if SELFDESTRUCT_RE.is_match(source) {
95 findings.push(UpgradeFinding::new(
96 "source_scan",
97 UpgradeSeverity::Warning,
98 UpgradeCategory::AutoCompatible,
99 Some("SCAN_SELFDESTRUCT_AUTO"),
100 None::<String>,
101 "selfdestruct() is auto-mapped to ContractManagement.destroy() during Neo compilation.",
102 Some(
103 "Review destruction and upgrade semantics before depending on EVM selfdestruct behavior.",
104 ),
105 ));
106 }
107
108 if CODEHASH_RE.is_match(source) {
109 findings.push(UpgradeFinding::new(
110 "source_scan",
111 UpgradeSeverity::Warning,
112 UpgradeCategory::AutoCompatible,
113 Some("SCAN_CODEHASH_AUTO"),
114 None::<String>,
115 "address.codehash is compiler-compatible on Neo N3 and resolves to the script hash identity.",
116 Some("Prefer explicit script-hash based checks in Neo-native code."),
117 ));
118 }
119
120 if TX_ORIGIN_RE.is_match(source) {
121 findings.push(UpgradeFinding::new(
122 "source_scan",
123 UpgradeSeverity::Warning,
124 UpgradeCategory::ManualMigration,
125 Some("SCAN_TX_ORIGIN"),
126 None::<String>,
127 "tx.origin keeps compiling on Neo N3, but its authorization semantics differ from EVM.",
128 Some("Use Runtime.checkWitness(...) or msg.sender-oriented authorization instead."),
129 ));
130 }
131
132 if MSG_SIG_RE.is_match(source) {
133 findings.push(UpgradeFinding::new(
134 "source_scan",
135 UpgradeSeverity::Warning,
136 UpgradeCategory::ManualMigration,
137 Some("SCAN_MSG_SIG"),
138 None::<String>,
139 "msg.sig compiles on Neo N3 as the current function selector, but this differs from EVM semantics across internal calls.",
140 Some("Use explicit method names or interface IDs when you need dispatch identity that survives internal calls."),
141 ));
142 }
143
144 if DELEGATECALL_RE.is_match(source) {
145 findings.push(UpgradeFinding::new(
146 "source_scan",
147 UpgradeSeverity::Error,
148 UpgradeCategory::ManualMigration,
149 Some("SCAN_DELEGATECALL"),
150 None::<String>,
151 "delegatecall-style upgrade and proxy flows do not exist on Neo N3.",
152 Some(
153 "Replace delegatecall proxies with ContractManagement.update() or explicit cross-contract calls.",
154 ),
155 ));
156 }
157
158 if STATICCALL_RE.is_match(source) {
159 findings.push(UpgradeFinding::new(
160 "source_scan",
161 UpgradeSeverity::Warning,
162 UpgradeCategory::ManualMigration,
163 Some("SCAN_STATICCALL"),
164 None::<String>,
165 "staticcall has no direct EVM-equivalent execution mode on Neo N3.",
166 Some("Prefer typed view/pure calls or Syscalls.contractCallWithFlags(..., ReadOnly)."),
167 ));
168 }
169
170 if LOW_LEVEL_CALL_RE.is_match(source) {
171 findings.push(UpgradeFinding::new(
172 "source_scan",
173 UpgradeSeverity::Warning,
174 UpgradeCategory::ManifestReview,
175 Some("SCAN_LOW_LEVEL_CALL"),
176 None::<String>,
177 "Low-level address.call(...) patterns can force broader Neo manifest permissions.",
178 Some(
179 "Prefer typed interfaces or fixed-target wrappers so manifest permissions stay narrow.",
180 ),
181 ));
182 }
183
184 if ETHER_UNIT_RE.is_match(source) {
185 findings.push(UpgradeFinding::new(
186 "source_scan",
187 UpgradeSeverity::Error,
188 UpgradeCategory::ManualMigration,
189 Some("SCAN_ETHER_UNITS"),
190 None::<String>,
191 "Ether-denominated value units are EVM-specific and do not map directly to Neo N3 execution.",
192 Some("Use integer GAS fractions (10^8) or NEP-17 payment flows instead."),
193 ));
194 }
195
196 if SUPPORTS_INTERFACE_RE.is_match(source) {
197 findings.push(UpgradeFinding::new(
198 "source_scan",
199 UpgradeSeverity::Warning,
200 UpgradeCategory::ManualMigration,
201 Some("SCAN_SUPPORTS_INTERFACE"),
202 None::<String>,
203 "supportsInterface(bytes4) is usually unnecessary on Neo N3 because interfaces are advertised in the manifest.",
204 Some(
205 "Prefer manifest.supportedstandards instead of EIP-165 style runtime checks.",
206 ),
207 ));
208 }
209
210 findings
211}