• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

stacks-network / stacks-core / 26453101490-2

26 May 2026 02:04PM UTC coverage: 47.615% (-38.3%) from 85.959%
26453101490-2

Pull #7239

github

5801ac
web-flow
Merge 2c09eb011 into 30629d416
Pull Request #7239: Fix/pox5 contract reentrancy

105014 of 220546 relevant lines covered (47.62%)

12959152.48 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

67.46
/stackslib/src/net/api/getcontractsrc.rs
1
// Copyright (C) 2013-2020 Blockstack PBC, a public benefit corporation
2
// Copyright (C) 2020-2023 Stacks Open Internet Foundation
3
//
4
// This program is free software: you can redistribute it and/or modify
5
// it under the terms of the GNU General Public License as published by
6
// the Free Software Foundation, either version 3 of the License, or
7
// (at your option) any later version.
8
//
9
// This program is distributed in the hope that it will be useful,
10
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
// GNU General Public License for more details.
13
//
14
// You should have received a copy of the GNU General Public License
15
// along with this program.  If not, see <http://www.gnu.org/licenses/>.
16

17
use clarity::vm::clarity::ClarityConnection;
18
use clarity::vm::database::clarity_store::{make_contract_hash_key, ContractCommitment};
19
use clarity::vm::representations::{CONTRACT_NAME_REGEX_STRING, STANDARD_PRINCIPAL_REGEX_STRING};
20
use clarity::vm::types::QualifiedContractIdentifier;
21
use clarity::vm::ContractName;
22
use regex::{Captures, Regex};
23
use stacks_common::types::chainstate::StacksAddress;
24
use stacks_common::types::net::PeerHost;
25
use stacks_common::util::hash::to_hex;
26

27
use crate::net::http::{
28
    parse_json, Error, HttpNotFound, HttpRequest, HttpRequestContents, HttpRequestPreamble,
29
    HttpResponse, HttpResponseContents, HttpResponsePayload, HttpResponsePreamble,
30
};
31
use crate::net::httpcore::{
32
    request, HttpRequestContentsExtensions as _, RPCRequestHandler, StacksHttpRequest,
33
    StacksHttpResponse,
34
};
35
use crate::net::{Error as NetError, StacksNodeState, TipRequest};
36

37
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
38
pub struct ContractSrcResponse {
39
    pub source: String,
40
    pub publish_height: u32,
41
    #[serde(rename = "proof")]
42
    #[serde(default)]
43
    #[serde(skip_serializing_if = "Option::is_none")]
44
    pub marf_proof: Option<String>,
45
}
46

47
#[derive(Clone)]
48
pub struct RPCGetContractSrcRequestHandler {
49
    pub contract_identifier: Option<QualifiedContractIdentifier>,
50
}
51

52
impl RPCGetContractSrcRequestHandler {
53
    pub fn new() -> Self {
1,079,027✔
54
        Self {
1,079,027✔
55
            contract_identifier: None,
1,079,027✔
56
        }
1,079,027✔
57
    }
1,079,027✔
58
}
59

60
/// Decode the HTTP request
61
impl HttpRequest for RPCGetContractSrcRequestHandler {
62
    fn verb(&self) -> &'static str {
1,079,027✔
63
        "GET"
1,079,027✔
64
    }
1,079,027✔
65

66
    fn path_regex(&self) -> Regex {
2,158,054✔
67
        Regex::new(&format!(
2,158,054✔
68
            "^/v2/contracts/source/(?P<address>{})/(?P<contract>{})$",
2,158,054✔
69
            *STANDARD_PRINCIPAL_REGEX_STRING, *CONTRACT_NAME_REGEX_STRING
2,158,054✔
70
        ))
2,158,054✔
71
        .unwrap()
2,158,054✔
72
    }
2,158,054✔
73

74
    fn metrics_identifier(&self) -> &str {
144✔
75
        "/v2/contracts/source/:principal/:contract_name"
144✔
76
    }
144✔
77

78
    /// Try to decode this request.
79
    /// There's nothing to load here, so just make sure the request is well-formed.
80
    fn try_parse_request(
144✔
81
        &mut self,
144✔
82
        preamble: &HttpRequestPreamble,
144✔
83
        captures: &Captures,
144✔
84
        query: Option<&str>,
144✔
85
        _body: &[u8],
144✔
86
    ) -> Result<HttpRequestContents, Error> {
144✔
87
        if preamble.get_content_length() != 0 {
144✔
88
            return Err(Error::DecodeError(
×
89
                "Invalid Http request: expected 0-length body".to_string(),
×
90
            ));
×
91
        }
144✔
92

93
        let contract_identifier = request::get_contract_address(captures, "address", "contract")?;
144✔
94
        self.contract_identifier = Some(contract_identifier);
144✔
95

96
        let contents = HttpRequestContents::new().query_string(query);
144✔
97
        Ok(contents)
144✔
98
    }
144✔
99
}
100

101
/// Handle the HTTP request
102
impl RPCRequestHandler for RPCGetContractSrcRequestHandler {
103
    /// Reset internal state
104
    fn restart(&mut self) {
144✔
105
        self.contract_identifier = None;
144✔
106
    }
144✔
107

108
    /// Make the response
109
    fn try_handle_request(
144✔
110
        &mut self,
144✔
111
        preamble: HttpRequestPreamble,
144✔
112
        contents: HttpRequestContents,
144✔
113
        node: &mut StacksNodeState,
144✔
114
    ) -> Result<(HttpResponsePreamble, HttpResponseContents), NetError> {
144✔
115
        let contract_identifier = self.contract_identifier.take().ok_or(NetError::SendError(
144✔
116
            "`contract_identifier` not set".to_string(),
144✔
117
        ))?;
144✔
118
        let tip = match node.load_stacks_chain_tip(&preamble, &contents) {
144✔
119
            Ok(tip) => tip,
144✔
120
            Err(error_resp) => {
×
121
                return error_resp.try_into_contents().map_err(NetError::from);
×
122
            }
123
        };
124
        let with_proof = contents.get_with_proof();
144✔
125

126
        let data_resp =
144✔
127
            node.with_node_state(|_network, sortdb, chainstate, _mempool, _rpc_args| {
144✔
128
                chainstate.maybe_read_only_clarity_tx(
144✔
129
                    &sortdb.index_handle_at_block(chainstate, &tip)?,
144✔
130
                    &tip,
144✔
131
                    |clarity_tx| {
144✔
132
                        clarity_tx.with_clarity_db_readonly(|db| {
144✔
133
                            let source = db.get_contract_src(&contract_identifier)?;
144✔
134
                            let contract_commit_key = make_contract_hash_key(&contract_identifier);
99✔
135
                            let (contract_commit, proof) = if with_proof {
99✔
136
                                db.get_data_with_proof::<ContractCommitment>(&contract_commit_key)
27✔
137
                                    .ok()
27✔
138
                                    .flatten()
27✔
139
                                    .map(|(a, b)| (a, Some(format!("0x{}", to_hex(&b)))))?
27✔
140
                            } else {
141
                                db.get_data::<ContractCommitment>(&contract_commit_key)
72✔
142
                                    .ok()
72✔
143
                                    .flatten()
72✔
144
                                    .map(|a| (a, None))?
72✔
145
                            };
146

147
                            let publish_height = contract_commit.block_height;
99✔
148
                            Some(ContractSrcResponse {
99✔
149
                                source,
99✔
150
                                publish_height,
99✔
151
                                marf_proof: proof,
99✔
152
                            })
99✔
153
                        })
144✔
154
                    },
144✔
155
                )
156
            });
144✔
157

158
        let data_resp = match data_resp {
144✔
159
            Ok(Some(Some(data))) => data,
99✔
160
            Ok(Some(None)) => {
161
                return StacksHttpResponse::new_error(
45✔
162
                    &preamble,
45✔
163
                    &HttpNotFound::new("No contract source data found".to_string()),
45✔
164
                )
165
                .try_into_contents()
45✔
166
                .map_err(NetError::from);
45✔
167
            }
168
            Ok(None) | Err(_) => {
169
                return StacksHttpResponse::new_error(
×
170
                    &preamble,
×
171
                    &HttpNotFound::new("Chain tip not found".to_string()),
×
172
                )
173
                .try_into_contents()
×
174
                .map_err(NetError::from);
×
175
            }
176
        };
177

178
        let preamble = HttpResponsePreamble::ok_json(&preamble);
99✔
179
        let body = HttpResponseContents::try_from_json(&data_resp)?;
99✔
180
        Ok((preamble, body))
99✔
181
    }
144✔
182
}
183

184
/// Decode the HTTP response
185
impl HttpResponse for RPCGetContractSrcRequestHandler {
186
    fn try_parse_response(
×
187
        &self,
×
188
        preamble: &HttpResponsePreamble,
×
189
        body: &[u8],
×
190
    ) -> Result<HttpResponsePayload, Error> {
×
191
        let contract_src: ContractSrcResponse = parse_json(preamble, body)?;
×
192
        Ok(HttpResponsePayload::try_from_json(contract_src)?)
×
193
    }
×
194
}
195

196
impl StacksHttpRequest {
197
    /// Make a new request for a contract's source code
198
    pub fn new_getcontractsrc(
×
199
        host: PeerHost,
×
200
        contract_addr: StacksAddress,
×
201
        contract_name: ContractName,
×
202
        tip_req: TipRequest,
×
203
        with_proof: bool,
×
204
    ) -> StacksHttpRequest {
×
205
        StacksHttpRequest::new_for_peer(
×
206
            host,
×
207
            "GET".into(),
×
208
            format!("/v2/contracts/source/{}/{}", &contract_addr, &contract_name),
×
209
            HttpRequestContents::new()
×
210
                .for_tip(tip_req)
×
211
                .query_arg("proof".into(), if with_proof { "1" } else { "0" }.into()),
×
212
        )
213
        .expect("FATAL: failed to construct request from infallible data")
×
214
    }
×
215
}
216

217
impl StacksHttpResponse {
218
    pub fn decode_contract_src_response(self) -> Result<ContractSrcResponse, NetError> {
×
219
        let contents = self.get_http_payload_ok()?;
×
220
        let contents_json: serde_json::Value = contents.try_into()?;
×
221
        let resp: ContractSrcResponse = serde_json::from_value(contents_json)
×
222
            .map_err(|_e| NetError::DeserializeError("Failed to load from JSON".to_string()))?;
×
223
        Ok(resp)
×
224
    }
×
225
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc