ab_system_contract_simple_wallet_base/
lib.rs1#![feature(maybe_uninit_as_bytes, ptr_as_ref_unchecked, slice_ptr_get, try_blocks)]
16#![no_std]
17
18pub mod payload;
19pub mod seal;
20pub mod utils;
21
22use crate::payload::{TransactionMethodContext, TransactionPayloadDecoder};
23use crate::seal::hash_and_verify;
24use ab_contracts_common::env::{Env, MethodContext};
25use ab_contracts_common::{ContractError, MAX_TOTAL_METHOD_ARGS};
26use ab_contracts_macros::contract;
27use ab_contracts_standards::tx_handler::{TxHandlerPayload, TxHandlerSeal, TxHandlerSlots};
28use ab_core_primitives::transaction::TransactionHeader;
29use ab_io_type::trivial_type::TrivialType;
30use core::ffi::c_void;
31use core::mem::MaybeUninit;
32use core::ptr;
33use schnorrkel::PublicKey;
34
35pub const SIGNING_CONTEXT: &[u8] = b"system-simple-wallet";
41pub const EXTERNAL_ARGS_BUFFER_SIZE: usize = MAX_TOTAL_METHOD_ARGS as usize
49 * (size_of::<*mut c_void>() + size_of::<u32>() * 2)
50 / size_of::<*mut c_void>();
51pub const OUTPUT_BUFFER_SIZE: usize = 32 * 1024 / size_of::<u128>();
61pub const OUTPUT_BUFFER_OFFSETS_SIZE: usize = 16;
71
72#[derive(Debug, Copy, Clone, TrivialType)]
76#[repr(C)]
77pub struct Seal {
78 pub signature: [u8; 64],
79 pub nonce: u64,
80}
81
82#[derive(Debug, Copy, Clone, Eq, PartialEq, TrivialType)]
86#[repr(C)]
87pub struct WalletState {
88 pub public_key: [u8; 32],
89 pub nonce: u64,
90}
91
92#[derive(Debug, Copy, Clone, TrivialType)]
96#[repr(C)]
97pub struct SimpleWalletBase;
98
99#[contract]
100impl SimpleWalletBase {
101 #[view]
103 #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
104 pub fn initialize(#[input] &public_key: &[u8; 32]) -> Result<WalletState, ContractError> {
105 PublicKey::from_bytes(&public_key).map_err(|_error| ContractError::BadInput)?;
109
110 Ok(WalletState {
111 public_key,
112 nonce: 0,
113 })
114 }
115
116 #[view]
118 #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
119 pub fn authorize(
120 #[input] state: &WalletState,
121 #[input] header: &TransactionHeader,
122 #[input] read_slots: &TxHandlerSlots,
123 #[input] write_slots: &TxHandlerSlots,
124 #[input] payload: &TxHandlerPayload,
125 #[input] seal: &TxHandlerSeal,
126 ) -> Result<(), ContractError> {
127 let Some(seal) = seal.read_trivial_type::<Seal>() else {
128 return Err(ContractError::BadInput);
129 };
130
131 let expected_nonce = state.nonce;
132 if expected_nonce.checked_add(1).is_none() {
134 return Err(ContractError::Forbidden);
135 };
136
137 let public_key = PublicKey::from_bytes(state.public_key.as_ref())
138 .expect("Guaranteed by constructor; qed");
139 hash_and_verify(
140 &public_key,
141 expected_nonce,
142 header,
143 read_slots.get_initialized(),
144 write_slots.get_initialized(),
145 payload.get_initialized(),
146 &seal,
147 )
148 }
149
150 #[update]
161 #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
162 pub fn execute(
163 #[env] env: &mut Env<'_>,
164 #[input] header: &TransactionHeader,
165 #[input] read_slots: &TxHandlerSlots,
166 #[input] write_slots: &TxHandlerSlots,
167 #[input] payload: &TxHandlerPayload,
168 #[input] seal: &TxHandlerSeal,
169 ) -> Result<(), ContractError> {
170 let _ = header;
171 let _ = read_slots;
172 let _ = write_slots;
173 let _ = seal;
174
175 if env.caller() != env.context() {
177 return Err(ContractError::Forbidden);
178 }
179
180 let mut external_args_buffer = [ptr::null_mut(); EXTERNAL_ARGS_BUFFER_SIZE];
181 let mut output_buffer = [MaybeUninit::uninit(); OUTPUT_BUFFER_SIZE];
182 let mut output_buffer_details = [MaybeUninit::uninit(); OUTPUT_BUFFER_OFFSETS_SIZE];
183
184 let mut payload_decoder = TransactionPayloadDecoder::new(
185 payload.get_initialized(),
186 &mut external_args_buffer,
187 &mut output_buffer,
188 &mut output_buffer_details,
189 |method_context| match method_context {
190 TransactionMethodContext::Null => MethodContext::Reset,
191 TransactionMethodContext::Wallet => MethodContext::Keep,
192 },
193 );
194
195 while let Some(prepared_method) = payload_decoder
196 .decode_next_method()
197 .map_err(|_error| ContractError::BadInput)?
198 {
199 env.call_prepared(prepared_method)?;
200 }
201
202 Ok(())
203 }
204
205 #[view]
207 #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
208 pub fn increase_nonce(#[input] state: &WalletState) -> Result<WalletState, ContractError> {
209 let nonce = state.nonce.checked_add(1).ok_or(ContractError::Forbidden)?;
210
211 Ok(WalletState {
212 public_key: state.public_key,
213 nonce,
214 })
215 }
216
217 #[view]
219 #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
220 pub fn change_public_key(
221 #[input] state: &WalletState,
222 #[input] &public_key: &[u8; 32],
223 ) -> Result<WalletState, ContractError> {
224 PublicKey::from_bytes(&public_key).map_err(|_error| ContractError::BadInput)?;
226
227 Ok(WalletState {
228 public_key,
229 nonce: state.nonce,
230 })
231 }
232}