wrapped_azero/
lib.rs

1#![cfg_attr(not(feature = "std"), no_std, no_main)]
2
3mod traits;
4
5// Re-export of PSP22 stuff for convenience of cross-contract calls
6pub use psp22::{PSP22Error, PSP22};
7
8pub use traits::{WrappedAZERO, MAINNET, TESTNET};
9
10#[ink::contract]
11mod wazero {
12    use crate::WrappedAZERO;
13    use ink::prelude::{string::String, vec::Vec};
14    use psp22::{PSP22Data, PSP22Error, PSP22Event, PSP22Metadata, PSP22};
15
16    #[ink(event)]
17    pub struct Approval {
18        #[ink(topic)]
19        owner: AccountId,
20        #[ink(topic)]
21        spender: AccountId,
22        amount: u128,
23    }
24
25    #[ink(event)]
26    pub struct Transfer {
27        #[ink(topic)]
28        from: Option<AccountId>,
29        #[ink(topic)]
30        to: Option<AccountId>,
31        value: u128,
32    }
33
34    #[ink(storage)]
35    #[derive(Default)]
36    pub struct Wazero {
37        data: PSP22Data,
38    }
39
40    impl Wazero {
41        #[ink(constructor)]
42        pub fn new() -> Self {
43            Self::default()
44        }
45
46        fn emit_events(&self, events: Vec<PSP22Event>) {
47            for event in events {
48                match event {
49                    PSP22Event::Transfer { from, to, value } => {
50                        self.env().emit_event(Transfer { from, to, value })
51                    }
52                    PSP22Event::Approval {
53                        owner,
54                        spender,
55                        amount,
56                    } => self.env().emit_event(Approval {
57                        owner,
58                        spender,
59                        amount,
60                    }),
61                }
62            }
63        }
64    }
65
66    impl WrappedAZERO for Wazero {
67        #[ink(message, payable)]
68        fn deposit(&mut self) -> Result<(), PSP22Error> {
69            let events = self
70                .data
71                .mint(self.env().caller(), self.env().transferred_value())?;
72            self.emit_events(events);
73            Ok(())
74        }
75
76        #[ink(message)]
77        fn withdraw(&mut self, value: u128) -> Result<(), PSP22Error> {
78            let caller = self.env().caller();
79            let events = self.data.burn(caller, value)?;
80            self.env()
81                .transfer(caller, value)
82                .map_err(|_| PSP22Error::Custom(String::from("Wrapped AZERO: withdraw failed")))?;
83            self.emit_events(events);
84            Ok(())
85        }
86    }
87
88    impl PSP22Metadata for Wazero {
89        #[ink(message)]
90        fn token_name(&self) -> Option<String> {
91            Some(String::from("Wrapped AZERO"))
92        }
93
94        #[ink(message)]
95        fn token_symbol(&self) -> Option<String> {
96            Some(String::from("wAZERO"))
97        }
98
99        #[ink(message)]
100        fn token_decimals(&self) -> u8 {
101            12
102        }
103    }
104
105    impl PSP22 for Wazero {
106        #[ink(message)]
107        fn total_supply(&self) -> u128 {
108            self.data.total_supply()
109        }
110
111        #[ink(message)]
112        fn balance_of(&self, owner: AccountId) -> u128 {
113            self.data.balance_of(owner)
114        }
115
116        #[ink(message)]
117        fn allowance(&self, owner: AccountId, spender: AccountId) -> u128 {
118            self.data.allowance(owner, spender)
119        }
120
121        #[ink(message)]
122        fn transfer(
123            &mut self,
124            to: AccountId,
125            value: u128,
126            _data: Vec<u8>,
127        ) -> Result<(), PSP22Error> {
128            let events = self.data.transfer(self.env().caller(), to, value)?;
129            self.emit_events(events);
130            Ok(())
131        }
132
133        #[ink(message)]
134        fn transfer_from(
135            &mut self,
136            from: AccountId,
137            to: AccountId,
138            value: u128,
139            _data: Vec<u8>,
140        ) -> Result<(), PSP22Error> {
141            let events = self
142                .data
143                .transfer_from(self.env().caller(), from, to, value)?;
144            self.emit_events(events);
145            Ok(())
146        }
147
148        #[ink(message)]
149        fn approve(&mut self, spender: AccountId, value: u128) -> Result<(), PSP22Error> {
150            let events = self.data.approve(self.env().caller(), spender, value)?;
151            self.emit_events(events);
152            Ok(())
153        }
154
155        #[ink(message)]
156        fn increase_allowance(
157            &mut self,
158            spender: AccountId,
159            delta_value: u128,
160        ) -> Result<(), PSP22Error> {
161            let events = self
162                .data
163                .increase_allowance(self.env().caller(), spender, delta_value)?;
164            self.emit_events(events);
165            Ok(())
166        }
167
168        #[ink(message)]
169        fn decrease_allowance(
170            &mut self,
171            spender: AccountId,
172            delta_value: u128,
173        ) -> Result<(), PSP22Error> {
174            let events = self
175                .data
176                .decrease_allowance(self.env().caller(), spender, delta_value)?;
177            self.emit_events(events);
178            Ok(())
179        }
180    }
181
182    #[cfg(test)]
183    mod tests {
184        use super::*;
185        use ink::env::{test::*, DefaultEnvironment as E};
186
187        psp22::tests!(Wazero, crate::wazero::tests::init_psp22_supply);
188
189        #[ink::test]
190        fn constructor_works() {
191            let contract = Wazero::new();
192            assert_eq!(contract.total_supply(), 0);
193        }
194
195        #[ink::test]
196        fn metadata_is_correct() {
197            let contract = Wazero::new();
198            assert_eq!(contract.token_name(), Some(String::from("Wrapped AZERO")));
199            assert_eq!(contract.token_symbol(), Some(String::from("wAZERO")));
200            assert_eq!(contract.token_decimals(), 12);
201        }
202
203        #[ink::test]
204        fn deposit_works() {
205            let mut contract = Wazero::new();
206            let amount = 100;
207            let alice = default_accounts::<E>().alice;
208            set_caller::<E>(alice);
209            set_value_transferred::<E>(amount);
210
211            assert_eq!(contract.total_supply(), 0);
212            assert_eq!(contract.balance_of(alice), 0);
213
214            assert!(contract.deposit().is_ok());
215
216            assert_eq!(contract.total_supply(), amount);
217            assert_eq!(contract.balance_of(alice), amount);
218        }
219
220        #[ink::test]
221        fn deposit_emits_event() {
222            let mut contract = Wazero::new();
223            let amount = 100;
224            let alice = default_accounts::<E>().alice;
225            set_caller::<E>(alice);
226            set_value_transferred::<E>(amount);
227
228            assert!(contract.deposit().is_ok());
229
230            let events = decode_events();
231            assert_eq!(events.len(), 1);
232            assert_transfer(&events[0], None, Some(alice), amount);
233        }
234
235        #[ink::test]
236        fn deposit_of_0_emits_no_event() {
237            let mut contract = Wazero::new();
238            let alice = default_accounts::<E>().alice;
239            set_caller::<E>(alice);
240
241            assert!(contract.deposit().is_ok());
242
243            let events = decode_events();
244            assert_eq!(events.len(), 0);
245        }
246
247        #[ink::test]
248        fn multiple_deposit_works_and_emits_events() {
249            let mut contract = Wazero::new();
250            let amount = 100;
251            let acc = default_accounts::<E>();
252            let (alice, bob) = (acc.alice, acc.bob);
253
254            assert_eq!(contract.total_supply(), 0);
255            assert_eq!(contract.balance_of(alice), 0);
256            assert_eq!(contract.balance_of(bob), 0);
257
258            set_caller::<E>(alice);
259            set_value_transferred::<E>(amount);
260            assert!(contract.deposit().is_ok());
261
262            assert_eq!(contract.total_supply(), amount);
263            assert_eq!(contract.balance_of(alice), amount);
264            assert_eq!(contract.balance_of(bob), 0);
265
266            set_caller::<E>(bob);
267            set_value_transferred::<E>(2 * amount);
268            assert!(contract.deposit().is_ok());
269
270            assert_eq!(contract.total_supply(), 3 * amount);
271            assert_eq!(contract.balance_of(alice), amount);
272            assert_eq!(contract.balance_of(bob), 2 * amount);
273
274            let events = decode_events();
275            assert_eq!(events.len(), 2);
276            assert_transfer(&events[0], None, Some(alice), amount);
277            assert_transfer(&events[1], None, Some(bob), 2 * amount);
278        }
279
280        #[ink::test]
281        fn withdraw_works() {
282            let (supply, amount) = (1000, 100);
283            let alice = default_accounts::<E>().alice;
284            set_caller::<E>(alice);
285            let mut contract = init_psp22_supply(supply);
286
287            assert_eq!(contract.total_supply(), supply);
288            assert_eq!(contract.balance_of(alice), supply);
289
290            let old_native = get_account_balance::<E>(alice).unwrap();
291            assert!(contract.withdraw(amount).is_ok());
292            let new_native = get_account_balance::<E>(alice).unwrap();
293
294            assert_eq!(contract.total_supply(), supply - amount);
295            assert_eq!(contract.balance_of(alice), supply - amount);
296            assert_eq!(new_native - old_native, amount);
297        }
298
299        #[ink::test]
300        fn withdraw_emits_event() {
301            let (supply, amount) = (1000, 100);
302            let alice = default_accounts::<E>().alice;
303            set_caller::<E>(alice);
304            let mut contract = init_psp22_supply(supply);
305
306            assert!(contract.withdraw(amount).is_ok());
307
308            let events = decode_events();
309            assert_eq!(events.len(), 2);
310            assert_transfer(&events[0], None, Some(alice), supply);
311            assert_transfer(&events[1], Some(alice), None, amount);
312        }
313
314        #[ink::test]
315        fn withdraw_of_0_emits_no_event() {
316            let amount = 100;
317            let alice = default_accounts::<E>().alice;
318            set_caller::<E>(alice);
319            let mut contract = init_psp22_supply(amount);
320
321            assert!(contract.withdraw(0).is_ok());
322            let events = decode_events();
323            assert_eq!(events.len(), 1);
324            assert_transfer(&events[0], None, Some(alice), amount);
325        }
326
327        #[ink::test]
328        fn withdraw_too_much_fails() {
329            let amount = 100;
330            let alice = default_accounts::<E>().alice;
331            set_caller::<E>(alice);
332            let mut contract = init_psp22_supply(amount);
333            assert_eq!(
334                contract.withdraw(amount + 1),
335                Err(PSP22Error::InsufficientBalance)
336            );
337        }
338
339        #[ink::test]
340        fn multiple_withdraw_works_and_emits_events() {
341            let (initial, a, b) = (1000, 100, 10);
342            let alice = default_accounts::<E>().alice;
343            let bob = default_accounts::<E>().bob;
344            set_caller::<E>(alice);
345            set_callee::<E>(default_accounts::<E>().charlie);
346            let mut contract = init_psp22_supply(2 * initial);
347
348            assert!(contract.transfer(bob, initial, vec![]).is_ok());
349
350            let old_alice = get_account_balance::<E>(alice).unwrap();
351            let old_bob = get_account_balance::<E>(bob).unwrap();
352
353            assert!(contract.withdraw(a).is_ok());
354            set_caller::<E>(bob);
355            assert!(contract.withdraw(b).is_ok());
356
357            let new_alice = get_account_balance::<E>(alice).unwrap();
358            let new_bob = get_account_balance::<E>(bob).unwrap();
359
360            assert_eq!(contract.total_supply(), 2 * initial - a - b);
361            assert_eq!(contract.balance_of(alice), initial - a);
362            assert_eq!(contract.balance_of(bob), initial - b);
363            assert_eq!(new_alice - old_alice, a);
364            assert_eq!(new_bob - old_bob, b);
365
366            let events = decode_events();
367            assert_eq!(events.len(), 4);
368            assert_transfer(&events[0], None, Some(alice), 2 * initial);
369            assert_transfer(&events[1], Some(alice), Some(bob), initial);
370            assert_transfer(&events[2], Some(alice), None, a);
371            assert_transfer(&events[3], Some(bob), None, b);
372        }
373
374        // Unit tests helpers
375
376        type Event = <Wazero as ink::reflect::ContractEventBase>::Type;
377
378        // Creates a new contract with given total supply
379        fn init_psp22_supply(amount: u128) -> Wazero {
380            let mut contract = Wazero::new();
381            set_value_transferred::<E>(amount);
382            contract.deposit().unwrap();
383            contract
384        }
385
386        // Gathers all emitted events into a vector
387        fn decode_events() -> Vec<Event> {
388            recorded_events()
389                .map(|e| <Event as scale::Decode>::decode(&mut &e.data[..]).unwrap())
390                .collect()
391        }
392
393        // Asserts if the given event is a Transfer with particular from_, to_ and value_
394        fn assert_transfer(
395            event: &Event,
396            from_: Option<AccountId>,
397            to_: Option<AccountId>,
398            value_: u128,
399        ) {
400            if let Event::Transfer(Transfer { from, to, value }) = event {
401                assert_eq!(*from, from_, "Transfer event: 'from' mismatch");
402                assert_eq!(*to, to_, "Transfer event: 'to' mismatch");
403                assert_eq!(*value, value_, "Transfer event: 'value' mismatch");
404            } else {
405                panic!("Event is not Transfer")
406            }
407        }
408    }
409}