soroban_env_host/builtin_contracts/
account_contract.rs1use crate::{
6 auth::{AuthorizedFunction, AuthorizedInvocation},
7 builtin_contracts::{
8 base_types::{Address, BytesN, Vec as HostVec},
9 common_types::ContractExecutable,
10 contract_error::ContractError,
11 },
12 err,
13 host::{
14 frame::{CallParams, ContractReentryMode},
15 Host,
16 },
17 xdr::{
18 self, AccountId, ContractId, ContractIdPreimage, ScErrorCode, ScErrorType,
19 ThresholdIndexes, Uint256,
20 },
21 Env, EnvBase, ErrorHandler, HostError, Symbol, TryFromVal, TryIntoVal, Val,
22};
23use core::cmp::Ordering;
24
25const MAX_ACCOUNT_SIGNATURES: u32 = 20;
26
27use soroban_builtin_sdk_macros::contracttype;
28
29pub const ACCOUNT_CONTRACT_CHECK_AUTH_FN_NAME: &str = "__check_auth";
30
31#[derive(Clone)]
32#[contracttype]
33pub(crate) struct ContractAuthorizationContext {
34 pub(crate) contract: Address,
35 pub(crate) fn_name: Symbol,
36 pub(crate) args: HostVec,
37}
38
39#[derive(Clone)]
40#[contracttype]
41pub(crate) struct CreateContractHostFnContext {
42 pub(crate) executable: ContractExecutable,
43 pub(crate) salt: BytesN<32>,
44}
45
46#[derive(Clone)]
47#[contracttype]
48pub(crate) struct CreateContractWithConstructorHostFnContext {
49 pub(crate) executable: ContractExecutable,
50 pub(crate) salt: BytesN<32>,
51 pub(crate) constructor_args: HostVec,
52}
53
54#[derive(Clone)]
55#[contracttype]
56pub(crate) enum AuthorizationContext {
57 Contract(ContractAuthorizationContext),
58 CreateContractHostFn(CreateContractHostFnContext),
59 CreateContractWithCtorHostFn(CreateContractWithConstructorHostFnContext),
60}
61
62#[derive(Clone)]
63#[contracttype]
64pub(crate) struct AccountEd25519Signature {
65 pub(crate) public_key: BytesN<32>,
66 pub(crate) signature: BytesN<64>,
67}
68
69impl AuthorizationContext {
70 fn from_authorized_fn(host: &Host, function: &AuthorizedFunction) -> Result<Self, HostError> {
71 match function {
72 AuthorizedFunction::ContractFn(contract_fn) => {
73 let args = HostVec::try_from_val(host, &contract_fn.args)?;
74 Ok(AuthorizationContext::Contract(
75 ContractAuthorizationContext {
76 contract: contract_fn.contract_address.try_into_val(host)?,
77 fn_name: contract_fn.function_name,
78 args,
79 },
80 ))
81 }
82 AuthorizedFunction::CreateContractHostFn(args) => {
83 let wasm_hash = match &args.executable {
84 xdr::ContractExecutable::Wasm(wasm_hash) => {
85 BytesN::<32>::from_slice(host, wasm_hash.as_slice())?
86 }
87 xdr::ContractExecutable::StellarAsset => return Err(host.err(
88 ScErrorType::Auth,
89 ScErrorCode::InvalidInput,
90 "StellarAsset executable is not allowed when authorizing create_contract host fn",
91 &[],
92 )),
93 };
94 let salt = match &args.contract_id_preimage {
95 ContractIdPreimage::Address(id_from_addr) => {
96 BytesN::<32>::from_slice(host, id_from_addr.salt.as_slice())?
97 }
98 ContractIdPreimage::Asset(_) => return Err(host.err(
99 ScErrorType::Auth,
100 ScErrorCode::InvalidInput,
101 "asset preimage is not allowed when authorizing create_contract host fn",
102 &[],
103 )),
104 };
105 if args.constructor_args.is_empty() {
106 Ok(AuthorizationContext::CreateContractHostFn(
107 CreateContractHostFnContext {
108 executable: ContractExecutable::Wasm(wasm_hash),
109 salt,
110 },
111 ))
112 } else {
113 let args_vec = host.scvals_to_val_vec(&args.constructor_args.as_slice())?;
114 Ok(AuthorizationContext::CreateContractWithCtorHostFn(
115 CreateContractWithConstructorHostFnContext {
116 executable: ContractExecutable::Wasm(wasm_hash),
117 salt,
118 constructor_args: args_vec.try_into_val(host)?,
119 },
120 ))
121 }
122 }
123 }
124 }
125}
126
127fn invocation_tree_to_auth_contexts(
129 host: &Host,
130 invocation: &AuthorizedInvocation,
131 out_contexts: &mut HostVec,
132) -> Result<(), HostError> {
133 out_contexts.push(&AuthorizationContext::from_authorized_fn(
134 host,
135 &invocation.function,
136 )?)?;
137 for sub_invocation in &invocation.sub_invocations {
138 invocation_tree_to_auth_contexts(host, sub_invocation, out_contexts)?;
139 }
140 Ok(())
141}
142
143pub(crate) fn check_account_contract_auth(
145 host: &Host,
146 account_contract: &ContractId,
147 signature_payload: &[u8; 32],
148 signature: Val,
149 invocation: &AuthorizedInvocation,
150) -> Result<(), HostError> {
151 let payload_obj = host.bytes_new_from_slice(signature_payload)?;
152 let mut auth_context_vec = HostVec::new(host)?;
153 invocation_tree_to_auth_contexts(host, invocation, &mut auth_context_vec)?;
154 Ok(host
155 .call_n_internal(
156 account_contract,
157 ACCOUNT_CONTRACT_CHECK_AUTH_FN_NAME.try_into_val(host)?,
158 &[payload_obj.into(), signature, auth_context_vec.into()],
159 CallParams {
160 reentry_mode: ContractReentryMode::SelfAllowed,
163 internal_host_call: true,
164 treat_missing_function_as_noop: false,
165 },
166 )?
167 .try_into()?)
168}
169
170pub(crate) fn check_account_authentication(
172 host: &Host,
173 account_id: AccountId,
174 payload: &[u8],
175 signature: Val,
176) -> Result<(), HostError> {
177 let signatures: HostVec = signature.try_into_val(host)?;
178 let len = signatures.len()?;
181 if len > MAX_ACCOUNT_SIGNATURES {
182 return Err(err!(
183 host,
184 ContractError::AuthenticationError,
185 "too many account signers",
186 len
187 ));
188 }
189 if len == 0 {
190 return Err(host.error(
191 ContractError::AuthenticationError.into(),
192 "no account signatures found",
193 &[],
194 ));
195 }
196 let payload_obj = host.bytes_new_from_slice(payload)?;
197 let account = host.load_account(account_id)?;
198 let mut prev_pk: Option<BytesN<32>> = None;
199 let mut weight = 0u32;
200 for i in 0..len {
201 let sig: AccountEd25519Signature = signatures.get(i)?;
202 if let Some(prev) = prev_pk {
204 if prev.compare(&sig.public_key)? != Ordering::Less {
205 return Err(err!(
206 host,
207 ContractError::AuthenticationError,
208 "public keys are not ordered",
209 prev,
210 sig.public_key
211 ));
212 }
213 }
214
215 host.verify_sig_ed25519(
216 sig.public_key.clone().into(),
217 payload_obj,
218 sig.signature.into(),
219 )?;
220
221 let signer_weight =
222 host.get_signer_weight_from_account(Uint256(sig.public_key.to_array()?), &account)?;
223 if signer_weight == 0 {
227 return Err(err!(
228 host,
229 ContractError::AuthenticationError,
230 "signer does not belong to account",
231 sig.public_key
232 ));
233 }
234 weight = weight.saturating_add(signer_weight as u32);
238 prev_pk = Some(sig.public_key);
239 }
240 let Some(threshold) = account.thresholds.0.get(ThresholdIndexes::Med as usize) else {
243 return Err(host.error(
244 (ScErrorType::Auth, ScErrorCode::InternalError).into(),
245 "unexpected thresholds-array size",
246 &[],
247 ));
248 };
249 if weight < *threshold as u32 {
250 Err(err!(
251 host,
252 ContractError::AuthenticationError,
253 "signature weight is lower than threshold",
254 weight,
255 *threshold as u32
256 ))
257 } else {
258 Ok(())
259 }
260}