ab_system_contract_simple_wallet_base/
lib.rs1#![feature(
16 maybe_uninit_as_bytes,
17 maybe_uninit_slice,
18 non_null_from_ref,
19 ptr_as_ref_unchecked,
20 slice_as_array,
21 try_blocks,
22 unchecked_shifts
23)]
24#![no_std]
25
26pub mod payload;
27pub mod seal;
28pub mod utils;
29
30use crate::payload::{TransactionMethodContext, TransactionPayloadDecoder};
31use crate::seal::hash_and_verify;
32use ab_contracts_common::env::{Env, MethodContext};
33use ab_contracts_common::{ContractError, MAX_TOTAL_METHOD_ARGS};
34use ab_contracts_io_type::trivial_type::TrivialType;
35use ab_contracts_macros::contract;
36use ab_contracts_standards::tx_handler::{TxHandlerPayload, TxHandlerSeal, TxHandlerSlots};
37use ab_transaction::TransactionHeader;
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 = 3 * MAX_TOTAL_METHOD_ARGS as usize;
56pub const OUTPUT_BUFFER_SIZE: usize = 32 * 1024 / size_of::<u128>();
66pub const OUTPUT_BUFFER_OFFSETS_SIZE: usize = 16;
76
77#[derive(Debug, Copy, Clone, TrivialType)]
81#[repr(C)]
82pub struct Seal {
83 pub signature: [u8; 64],
84 pub nonce: u64,
85}
86
87#[derive(Debug, Copy, Clone, Eq, PartialEq, TrivialType)]
91#[repr(C)]
92pub struct WalletState {
93 pub public_key: [u8; 32],
94 pub nonce: u64,
95}
96
97#[derive(Debug, Copy, Clone, TrivialType)]
101#[repr(C)]
102pub struct SimpleWalletBase;
103
104#[contract]
105impl SimpleWalletBase {
106 #[view]
108 pub fn initialize(#[input] &public_key: &[u8; 32]) -> Result<WalletState, ContractError> {
109 PublicKey::from_bytes(&public_key).map_err(|_error| ContractError::BadInput)?;
113
114 Ok(WalletState {
115 public_key,
116 nonce: 0,
117 })
118 }
119
120 #[view]
122 pub fn authorize(
123 #[input] state: &WalletState,
124 #[input] header: &TransactionHeader,
125 #[input] read_slots: &TxHandlerSlots,
126 #[input] write_slots: &TxHandlerSlots,
127 #[input] payload: &TxHandlerPayload,
128 #[input] seal: &TxHandlerSeal,
129 ) -> Result<(), ContractError> {
130 let Some(seal) = seal.read_trivial_type::<Seal>() else {
131 return Err(ContractError::BadInput);
132 };
133
134 let expected_nonce = state.nonce;
135 if expected_nonce.checked_add(1).is_none() {
137 return Err(ContractError::Forbidden);
138 };
139
140 let public_key = PublicKey::from_bytes(state.public_key.as_ref())
141 .expect("Guaranteed by constructor; qed");
142 hash_and_verify(
143 &public_key,
144 expected_nonce,
145 header,
146 read_slots.get_initialized(),
147 write_slots.get_initialized(),
148 payload.get_initialized(),
149 &seal,
150 )
151 }
152
153 #[update]
164 pub fn execute(
165 #[env] env: &mut Env<'_>,
166 #[input] header: &TransactionHeader,
167 #[input] read_slots: &TxHandlerSlots,
168 #[input] write_slots: &TxHandlerSlots,
169 #[input] payload: &TxHandlerPayload,
170 #[input] seal: &TxHandlerSeal,
171 ) -> Result<(), ContractError> {
172 let _ = header;
173 let _ = read_slots;
174 let _ = write_slots;
175 let _ = seal;
176
177 if env.caller() != env.context() {
179 return Err(ContractError::Forbidden);
180 }
181
182 let mut external_args_buffer = [ptr::null_mut(); EXTERNAL_ARGS_BUFFER_SIZE];
183 let mut output_buffer = [MaybeUninit::uninit(); OUTPUT_BUFFER_SIZE];
184 let mut output_buffer_offsets = [MaybeUninit::uninit(); OUTPUT_BUFFER_OFFSETS_SIZE];
185
186 let mut payload_decoder = TransactionPayloadDecoder::new(
187 payload.get_initialized(),
188 &mut external_args_buffer,
189 &mut output_buffer,
190 &mut output_buffer_offsets,
191 |method_context| match method_context {
192 TransactionMethodContext::Null => MethodContext::Reset,
193 TransactionMethodContext::Wallet => MethodContext::Keep,
194 },
195 );
196
197 while let Some(prepared_method) = payload_decoder
198 .decode_next_method()
199 .map_err(|_error| ContractError::BadInput)?
200 {
201 env.call_prepared(prepared_method)?;
202 }
203
204 Ok(())
205 }
206
207 #[view]
209 pub fn increase_nonce(#[input] state: &WalletState) -> Result<WalletState, ContractError> {
210 let nonce = state.nonce.checked_add(1).ok_or(ContractError::Forbidden)?;
211
212 Ok(WalletState {
213 public_key: state.public_key,
214 nonce,
215 })
216 }
217
218 #[view]
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}