sonicapi/
builders.rs

1// SONIC: Standard library for formally-verifiable distributed contracts
2//
3// SPDX-License-Identifier: Apache-2.0
4//
5// Designed in 2019-2025 by Dr Maxim Orlovsky <orlovsky@ubideco.org>
6// Written in 2024-2025 by Dr Maxim Orlovsky <orlovsky@ubideco.org>
7//
8// Copyright (C) 2019-2024 LNP/BP Standards Association, Switzerland.
9// Copyright (C) 2024-2025 Laboratories for Ubiquitous Deterministic Computing (UBIDECO),
10//                         Institute for Distributed and Cognitive Systems (InDCS), Switzerland.
11// Copyright (C) 2019-2025 Dr Maxim Orlovsky.
12// All rights under the above copyrights are reserved.
13//
14// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
15// in compliance with the License. You may obtain a copy of the License at
16//
17//        http://www.apache.org/licenses/LICENSE-2.0
18//
19// Unless required by applicable law or agreed to in writing, software distributed under the License
20// is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
21// or implied. See the License for the specific language governing permissions and limitations under
22// the License.
23
24use aluvm::LibSite;
25use amplify::confinement::SmallVec;
26use amplify::num::u256;
27use chrono::{DateTime, Utc};
28use strict_encoding::TypeName;
29use strict_types::{StrictVal, TypeSystem};
30use ultrasonic::{
31    fe256, AuthToken, CallId, CellAddr, CodexId, Consensus, ContractId, ContractMeta, ContractName, Genesis, Identity,
32    Input, Issue, Operation, StateCell, StateData, StateValue,
33};
34
35use crate::{Api, Articles, DataCell, MethodName, Schema, StateAtom, StateName};
36
37#[derive(Clone, PartialEq, Eq, Debug)]
38#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
39pub struct NamedState<T> {
40    pub name: StateName,
41    #[cfg_attr(feature = "serde", serde(flatten))]
42    pub state: T,
43}
44
45#[derive(Clone, PartialEq, Eq, Debug)]
46#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
47pub struct CoreParams {
48    pub method: MethodName,
49    pub global: Vec<NamedState<StateAtom>>,
50    pub owned: Vec<NamedState<DataCell>>,
51}
52
53#[derive(Clone, PartialEq, Eq, Debug)]
54#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
55pub struct IssueParams {
56    pub name: TypeName,
57    pub consensus: Consensus,
58    pub testnet: bool,
59    pub timestamp: Option<DateTime<Utc>>,
60    #[cfg_attr(feature = "serde", serde(flatten))]
61    pub core: CoreParams,
62}
63
64impl Schema {
65    pub fn start_issue(self, method: impl Into<MethodName>, consensus: Consensus, testnet: bool) -> IssueBuilder {
66        let builder = Builder::new(self.call_id(method));
67        IssueBuilder { builder, schema: self, testnet, consensus }
68    }
69
70    pub fn start_issue_mainnet(self, method: impl Into<MethodName>, consensus: Consensus) -> IssueBuilder {
71        self.start_issue(method, consensus, false)
72    }
73    pub fn start_issue_testnet(self, method: impl Into<MethodName>, consensus: Consensus) -> IssueBuilder {
74        self.start_issue(method, consensus, true)
75    }
76
77    pub fn issue(self, params: IssueParams) -> Articles {
78        let mut builder = self.start_issue(params.core.method, params.consensus, params.testnet);
79
80        for NamedState { name, state } in params.core.global {
81            builder = builder.append(name, state.verified, state.unverified)
82        }
83        for NamedState { name, state } in params.core.owned {
84            builder = builder.assign(name, state.auth, state.data, state.lock)
85        }
86
87        let timestamp = params.timestamp.unwrap_or_else(Utc::now).timestamp();
88        builder.finish(params.name, timestamp)
89    }
90}
91
92#[derive(Clone, Debug)]
93pub struct IssueBuilder {
94    builder: Builder,
95    schema: Schema,
96    testnet: bool,
97    consensus: Consensus,
98}
99
100impl IssueBuilder {
101    pub fn append(mut self, name: impl Into<StateName>, data: StrictVal, raw: Option<StrictVal>) -> Self {
102        self.builder = self
103            .builder
104            .add_immutable(name, data, raw, &self.schema.default_api, &self.schema.types);
105        self
106    }
107
108    pub fn assign(
109        mut self,
110        name: impl Into<StateName>,
111        auth: AuthToken,
112        data: StrictVal,
113        lock: Option<LibSite>,
114    ) -> Self {
115        self.builder =
116            self.builder
117                .add_destructible(name, auth, data, lock, &self.schema.default_api, &self.schema.types);
118        self
119    }
120
121    pub fn finish(self, name: impl Into<TypeName>, timestamp: i64) -> Articles {
122        let meta = ContractMeta {
123            consensus: self.consensus,
124            testnet: self.testnet,
125            reserved: zero!(),
126            timestamp,
127            name: ContractName::Named(name.into()),
128            issuer: Identity::default(),
129        };
130        let genesis = self.builder.issue_genesis(self.schema.codex.codex_id());
131        let issue = Issue {
132            version: default!(),
133            meta,
134            codex: self.schema.codex.clone(),
135            genesis,
136        };
137        Articles { issue, contract_sigs: none!(), schema: self.schema }
138    }
139}
140
141#[derive(Clone, Debug)]
142pub struct Builder {
143    call_id: CallId,
144    destructible: SmallVec<StateCell>,
145    immutable: SmallVec<StateData>,
146}
147
148impl Builder {
149    pub fn new(call_id: CallId) -> Self { Builder { call_id, destructible: none!(), immutable: none!() } }
150
151    pub fn add_immutable(
152        mut self,
153        name: impl Into<StateName>,
154        data: StrictVal,
155        raw: Option<StrictVal>,
156        api: &Api,
157        sys: &TypeSystem,
158    ) -> Self {
159        let data = api.build_immutable(name, data, raw, sys);
160        self.immutable.push(data).expect("too many state elements");
161        self
162    }
163
164    pub fn add_destructible(
165        mut self,
166        name: impl Into<StateName>,
167        auth: AuthToken,
168        data: StrictVal,
169        lock: Option<LibSite>,
170        api: &Api,
171        sys: &TypeSystem,
172    ) -> Self {
173        let data = api.build_destructible(name, data, sys);
174        let cell = StateCell { data, auth, lock };
175        self.destructible
176            .push(cell)
177            .expect("too many state elements");
178        self
179    }
180
181    pub fn issue_genesis(self, codex_id: CodexId) -> Genesis {
182        Genesis {
183            codex_id,
184            call_id: self.call_id,
185            nonce: fe256::from(u256::ZERO),
186            blank1: zero!(),
187            blank2: zero!(),
188            destructible: self.destructible,
189            immutable: self.immutable,
190            reserved: zero!(),
191        }
192    }
193}
194
195#[derive(Clone, Debug)]
196pub struct BuilderRef<'c> {
197    type_system: &'c TypeSystem,
198    api: &'c Api,
199    inner: Builder,
200}
201
202impl<'c> BuilderRef<'c> {
203    pub fn new(api: &'c Api, call_id: CallId, sys: &'c TypeSystem) -> Self {
204        BuilderRef { type_system: sys, api, inner: Builder::new(call_id) }
205    }
206
207    pub fn add_immutable(mut self, name: impl Into<StateName>, data: StrictVal, raw: Option<StrictVal>) -> Self {
208        self.inner = self
209            .inner
210            .add_immutable(name, data, raw, self.api, self.type_system);
211        self
212    }
213
214    pub fn add_destructible(
215        mut self,
216        name: impl Into<StateName>,
217        auth: AuthToken,
218        data: StrictVal,
219        lock: Option<LibSite>,
220    ) -> Self {
221        self.inner = self
222            .inner
223            .add_destructible(name, auth, data, lock, self.api, self.type_system);
224        self
225    }
226
227    pub fn issue_genesis(self, codex_id: CodexId) -> Genesis { self.inner.issue_genesis(codex_id) }
228}
229
230#[derive(Clone, Debug)]
231pub struct OpBuilder {
232    contract_id: ContractId,
233    destroying: SmallVec<Input>,
234    reading: SmallVec<CellAddr>,
235    inner: Builder,
236}
237
238impl OpBuilder {
239    pub fn new(contract_id: ContractId, call_id: CallId) -> Self {
240        let inner = Builder::new(call_id);
241        Self { contract_id, destroying: none!(), reading: none!(), inner }
242    }
243
244    pub fn add_immutable(
245        mut self,
246        name: impl Into<StateName>,
247        data: StrictVal,
248        raw: Option<StrictVal>,
249        api: &Api,
250        sys: &TypeSystem,
251    ) -> Self {
252        self.inner = self.inner.add_immutable(name, data, raw, api, sys);
253        self
254    }
255
256    pub fn add_destructible(
257        mut self,
258        name: impl Into<StateName>,
259        auth: AuthToken,
260        data: StrictVal,
261        lock: Option<LibSite>,
262        api: &Api,
263        sys: &TypeSystem,
264    ) -> Self {
265        self.inner = self
266            .inner
267            .add_destructible(name, auth, data, lock, api, sys);
268        self
269    }
270
271    pub fn access(mut self, addr: CellAddr) -> Self {
272        self.reading
273            .push(addr)
274            .expect("number of read memory cells exceeds 64k limit");
275        self
276    }
277
278    pub fn destroy(mut self, addr: CellAddr, _witness: StrictVal) -> Self {
279        // TODO: Convert witness
280        let input = Input { addr, witness: StateValue::None };
281        self.destroying
282            .push(input)
283            .expect("number of inputs exceeds 64k limit");
284        self
285    }
286
287    pub fn finalize(self) -> Operation {
288        Operation {
289            contract_id: self.contract_id,
290            call_id: self.inner.call_id,
291            nonce: fe256::from(u256::ZERO),
292            destroying: self.destroying,
293            reading: self.reading,
294            destructible: self.inner.destructible,
295            immutable: self.inner.immutable,
296            reserved: zero!(),
297        }
298    }
299}
300
301#[derive(Clone, Debug)]
302pub struct OpBuilderRef<'c> {
303    type_system: &'c TypeSystem,
304    api: &'c Api,
305    inner: OpBuilder,
306}
307
308impl<'c> OpBuilderRef<'c> {
309    pub fn new(api: &'c Api, contract_id: ContractId, call_id: CallId, sys: &'c TypeSystem) -> Self {
310        let inner = OpBuilder::new(contract_id, call_id);
311        Self { api, type_system: sys, inner }
312    }
313
314    pub fn add_immutable(mut self, name: impl Into<StateName>, data: StrictVal, raw: Option<StrictVal>) -> Self {
315        self.inner = self
316            .inner
317            .add_immutable(name, data, raw, self.api, self.type_system);
318        self
319    }
320
321    pub fn add_destructible(
322        mut self,
323        name: impl Into<StateName>,
324        auth: AuthToken,
325        data: StrictVal,
326        lock: Option<LibSite>,
327    ) -> Self {
328        self.inner = self
329            .inner
330            .add_destructible(name, auth, data, lock, self.api, self.type_system);
331        self
332    }
333
334    pub fn access(mut self, addr: CellAddr) -> Self {
335        self.inner = self.inner.access(addr);
336        self
337    }
338
339    pub fn destroy(mut self, addr: CellAddr, witness: StrictVal) -> Self {
340        self.inner = self.inner.destroy(addr, witness);
341        self
342    }
343
344    pub fn finalize(self) -> Operation { self.inner.finalize() }
345}