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