ab_system_contract_simple_wallet_base/
lib.rs1#![feature(non_null_from_ref, ptr_as_ref_unchecked, try_blocks, unchecked_shifts)]
16#![no_std]
17
18pub mod payload;
19pub mod seal;
20
21use crate::payload::{TransactionMethodContext, TransactionPayloadDecoder};
22use crate::seal::hash_and_verify;
23use ab_contracts_common::ContractError;
24use ab_contracts_common::env::{Env, MethodContext, TransactionHeader};
25use ab_contracts_io_type::trivial_type::TrivialType;
26use ab_contracts_io_type::variable_bytes::VariableBytes;
27use ab_contracts_macros::contract;
28use ab_contracts_standards::tx_handler::{TxHandlerPayload, TxHandlerSeal, TxHandlerSlots};
29use core::mem::MaybeUninit;
30use core::ptr;
31use schnorrkel::PublicKey;
32
33pub const SIGNING_CONTEXT: &[u8] = b"system-simple-wallet";
39pub const EXTERNAL_ARGS_BUFFER_SIZE: usize = 32 * 1024;
47pub const OUTPUT_BUFFER_SIZE: usize = 32 * 1024;
57pub const OUTPUT_BUFFER_OFFSETS_SIZE: usize = 16;
67
68#[derive(Debug, Copy, Clone, TrivialType)]
72#[repr(C)]
73pub struct Seal {
74 pub signature: [u8; 64],
75 pub nonce: u64,
76}
77
78#[derive(Debug, Copy, Clone, TrivialType)]
82#[repr(C)]
83pub struct WalletState {
84 pub public_key: [u8; 32],
85 pub nonce: u64,
86}
87
88#[derive(Debug, Copy, Clone, TrivialType)]
92#[repr(C)]
93pub struct SimpleWalletBase;
94
95#[contract]
96impl SimpleWalletBase {
97 #[view]
99 pub fn initialize(
100 #[input] &public_key: &[u8; 32],
101 #[output] state: &mut VariableBytes,
102 ) -> Result<(), ContractError> {
103 PublicKey::from_bytes(&public_key).map_err(|_error| ContractError::BadInput)?;
107
108 if !state.copy_from(&WalletState {
109 public_key,
110 nonce: 0,
111 }) {
112 return Err(ContractError::BadInput);
113 }
114
115 Ok(())
116 }
117
118 #[view]
120 pub fn authorize(
121 #[input] state: &VariableBytes,
122 #[input] header: &TransactionHeader,
123 #[input] read_slots: &TxHandlerSlots,
124 #[input] write_slots: &TxHandlerSlots,
125 #[input] payload: &TxHandlerPayload,
126 #[input] seal: &TxHandlerSeal,
127 ) -> Result<(), ContractError> {
128 let Some(state) = state.read_trivial_type::<WalletState>() else {
129 return Err(ContractError::BadInput);
130 };
131 let Some(seal) = seal.read_trivial_type::<Seal>() else {
132 return Err(ContractError::BadInput);
133 };
134
135 let expected_nonce = state.nonce;
136 if expected_nonce.checked_add(1).is_none() {
138 return Err(ContractError::Forbidden);
139 };
140
141 let public_key = PublicKey::from_bytes(state.public_key.as_ref())
142 .expect("Guaranteed by constructor; qed");
143 hash_and_verify(
144 &public_key,
145 expected_nonce,
146 header,
147 read_slots.get_initialized(),
148 write_slots.get_initialized(),
149 payload.get_initialized(),
150 &seal,
151 )
152 }
153
154 #[update]
165 pub fn execute(
166 #[env] env: &mut Env<'_>,
167 #[input] header: &TransactionHeader,
168 #[input] read_slots: &TxHandlerSlots,
169 #[input] write_slots: &TxHandlerSlots,
170 #[input] payload: &TxHandlerPayload,
171 #[input] seal: &TxHandlerSeal,
172 ) -> Result<(), ContractError> {
173 let _ = header;
174 let _ = read_slots;
175 let _ = write_slots;
176 let _ = seal;
177
178 if env.caller() != env.context() {
180 return Err(ContractError::Forbidden);
181 }
182
183 let mut external_args_buffer = [ptr::null_mut(); EXTERNAL_ARGS_BUFFER_SIZE];
184 let mut output_buffer = [MaybeUninit::uninit(); OUTPUT_BUFFER_SIZE];
185 let mut output_buffer_offsets = [MaybeUninit::uninit(); OUTPUT_BUFFER_OFFSETS_SIZE];
186
187 let mut payload_decoder = TransactionPayloadDecoder::new(
188 payload.get_initialized(),
189 &mut external_args_buffer,
190 &mut output_buffer,
191 &mut output_buffer_offsets,
192 |method_context| match method_context {
193 TransactionMethodContext::Null => MethodContext::Reset,
194 TransactionMethodContext::Wallet => MethodContext::Keep,
195 },
196 );
197
198 while let Some(prepared_method) = payload_decoder
199 .decode_next_method()
200 .map_err(|_error| ContractError::BadInput)?
201 {
202 env.call_prepared(prepared_method)?;
203 }
204
205 Ok(())
206 }
207
208 #[view]
210 pub fn increase_nonce(
211 #[input] state: &VariableBytes,
212 #[input] seal: &TxHandlerSeal,
213 #[output] new_state: &mut VariableBytes,
214 ) -> Result<(), ContractError> {
215 let _ = seal;
216
217 let Some(mut state) = state.read_trivial_type::<WalletState>() else {
218 return Err(ContractError::BadInput);
219 };
220
221 state.nonce = state.nonce.checked_add(1).ok_or(ContractError::Forbidden)?;
222
223 if !new_state.copy_from(&state) {
224 return Err(ContractError::BadInput);
225 }
226
227 Ok(())
228 }
229
230 #[view]
232 pub fn change_public_key(
233 #[input] state: &VariableBytes,
234 #[input] &public_key: &[u8; 32],
235 #[output] new_state: &mut VariableBytes,
236 ) -> Result<(), ContractError> {
237 let Some(mut state) = state.read_trivial_type::<WalletState>() else {
238 return Err(ContractError::BadInput);
239 };
240 PublicKey::from_bytes(&public_key).map_err(|_error| ContractError::BadInput)?;
242
243 state.public_key = public_key;
244
245 if !new_state.copy_from(&state) {
246 return Err(ContractError::BadInput);
247 }
248
249 Ok(())
250 }
251}