ab_system_contract_simple_wallet_base/
lib.rs1#![feature(
16 const_block_items,
17 const_convert,
18 const_trait_impl,
19 maybe_uninit_as_bytes,
20 slice_ptr_get,
21 try_blocks
22)]
23#![no_std]
24
25pub mod payload;
26pub mod seal;
27pub mod utils;
28
29use crate::payload::{TransactionMethodContext, TransactionPayloadDecoder};
30use crate::seal::hash_and_verify;
31use ab_contracts_common::env::{Env, MethodContext};
32use ab_contracts_common::{ContractError, MAX_TOTAL_METHOD_ARGS};
33use ab_contracts_macros::contract;
34use ab_contracts_standards::tx_handler::{TxHandlerPayload, TxHandlerSeal, TxHandlerSlots};
35use ab_core_primitives::transaction::TransactionHeader;
36use ab_io_type::trivial_type::TrivialType;
37use core::ffi::c_void;
38use core::mem::MaybeUninit;
39use core::ptr;
40use schnorrkel::PublicKey;
41
42pub const SIGNING_CONTEXT: &[u8] = b"system-simple-wallet";
48pub const EXTERNAL_ARGS_BUFFER_SIZE: usize = MAX_TOTAL_METHOD_ARGS as usize
56 * (size_of::<*mut c_void>() + size_of::<u32>() * 2)
57 / size_of::<*mut c_void>();
58pub const OUTPUT_BUFFER_SIZE: usize = 32 * 1024 / size_of::<u128>();
68pub const OUTPUT_BUFFER_OFFSETS_SIZE: usize = 16;
78
79#[derive(Debug, Copy, Clone, TrivialType)]
83#[repr(C)]
84pub struct Seal {
85 pub signature: [u8; 64],
86 pub nonce: u64,
87}
88
89#[derive(Debug, Copy, Clone, Eq, PartialEq, TrivialType)]
93#[repr(C)]
94pub struct WalletState {
95 pub public_key: [u8; 32],
96 pub nonce: u64,
97}
98
99#[derive(Debug, Copy, Clone, TrivialType)]
103#[repr(C)]
104pub struct SimpleWalletBase;
105
106#[contract]
107impl SimpleWalletBase {
108 #[view]
110 #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
111 pub fn initialize(#[input] &public_key: &[u8; 32]) -> Result<WalletState, ContractError> {
112 PublicKey::from_bytes(&public_key).map_err(|_error| ContractError::BadInput)?;
116
117 Ok(WalletState {
118 public_key,
119 nonce: 0,
120 })
121 }
122
123 #[view]
125 #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
126 pub fn authorize(
127 #[input] state: &WalletState,
128 #[input] header: &TransactionHeader,
129 #[input] read_slots: &TxHandlerSlots,
130 #[input] write_slots: &TxHandlerSlots,
131 #[input] payload: &TxHandlerPayload,
132 #[input] seal: &TxHandlerSeal,
133 ) -> Result<(), ContractError> {
134 let Some(seal) = seal.read_trivial_type::<Seal>() else {
135 return Err(ContractError::BadInput);
136 };
137
138 let expected_nonce = state.nonce;
139 if expected_nonce.checked_add(1).is_none() {
141 return Err(ContractError::Forbidden);
142 };
143
144 let public_key = PublicKey::from_bytes(state.public_key.as_ref())
145 .expect("Guaranteed by constructor; qed");
146 hash_and_verify(
147 &public_key,
148 expected_nonce,
149 header,
150 read_slots.get_initialized(),
151 write_slots.get_initialized(),
152 payload.get_initialized(),
153 &seal,
154 )
155 }
156
157 #[update]
168 #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
169 pub fn execute(
170 #[env] env: &mut Env<'_>,
171 #[input] header: &TransactionHeader,
172 #[input] read_slots: &TxHandlerSlots,
173 #[input] write_slots: &TxHandlerSlots,
174 #[input] payload: &TxHandlerPayload,
175 #[input] seal: &TxHandlerSeal,
176 ) -> Result<(), ContractError> {
177 let _ = header;
178 let _ = read_slots;
179 let _ = write_slots;
180 let _ = seal;
181
182 if env.caller() != env.context() {
184 return Err(ContractError::Forbidden);
185 }
186
187 let mut external_args_buffer = [ptr::null_mut(); EXTERNAL_ARGS_BUFFER_SIZE];
188 let mut output_buffer = [MaybeUninit::uninit(); OUTPUT_BUFFER_SIZE];
189 let mut output_buffer_details = [MaybeUninit::uninit(); OUTPUT_BUFFER_OFFSETS_SIZE];
190
191 let mut payload_decoder = TransactionPayloadDecoder::new(
192 payload.get_initialized(),
193 &mut external_args_buffer,
194 &mut output_buffer,
195 &mut output_buffer_details,
196 |method_context| match method_context {
197 TransactionMethodContext::Null => MethodContext::Reset,
198 TransactionMethodContext::Wallet => MethodContext::Keep,
199 },
200 );
201
202 while let Some(prepared_method) = payload_decoder
203 .decode_next_method()
204 .map_err(|_error| ContractError::BadInput)?
205 {
206 env.call_prepared(prepared_method)?;
207 }
208
209 Ok(())
210 }
211
212 #[view]
214 #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
215 pub fn increase_nonce(#[input] state: &WalletState) -> Result<WalletState, ContractError> {
216 let nonce = state.nonce.checked_add(1).ok_or(ContractError::Forbidden)?;
217
218 Ok(WalletState {
219 public_key: state.public_key,
220 nonce,
221 })
222 }
223
224 #[view]
226 #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
227 pub fn change_public_key(
228 #[input] state: &WalletState,
229 #[input] &public_key: &[u8; 32],
230 ) -> Result<WalletState, ContractError> {
231 PublicKey::from_bytes(&public_key).map_err(|_error| ContractError::BadInput)?;
233
234 Ok(WalletState {
235 public_key,
236 nonce: state.nonce,
237 })
238 }
239}