1use crate::cep18_token::Cep18;
3use crate::wrapped_native::events::{Deposit, Withdrawal};
4use odra::casper_types::{U256, U512};
5use odra::uints::{ToU256, ToU512};
6use odra::{prelude::*, ContractRef};
7
8#[odra::event]
10pub struct OnCsprDeposit {
11 pub account: Address,
13 pub value: U512
15}
16
17#[odra::module]
19pub struct CsprDeposit {}
20
21#[odra::module]
23impl CsprDeposit {
24 #[odra(payable)]
26 pub fn deposit(&self) {
27 self.env().emit_event(OnCsprDeposit {
28 account: self.env().caller(),
29 value: self.env().attached_value()
30 });
31 }
32}
33
34#[odra::module(events = [Deposit, Withdrawal])]
36pub struct WrappedNativeToken {
37 token: SubModule<Cep18>
38}
39
40#[odra::module]
42impl WrappedNativeToken {
43 pub fn init(&mut self) {
45 let symbol = "WCSPR".to_string();
46 let name = "Wrapped CSPR".to_string();
47 self.token.init(symbol, name, 9, U256::zero());
48 }
49
50 #[odra(payable)]
52 pub fn deposit(&mut self) {
53 let caller = self.env().caller();
54
55 let amount = self.env().attached_value();
56
57 let amount = amount.to_u256().unwrap_or_revert(self);
58 self.token.raw_mint(&caller, &amount);
59
60 self.env().emit_event(Deposit {
61 account: caller,
62 value: amount
63 });
64 }
65
66 pub fn withdraw(&mut self, amount: &U256) {
68 let caller = self.env().caller();
69
70 self.token.raw_burn(&caller, amount);
71 if caller.is_contract() {
72 CsprDepositContractRef::new(self.env(), caller)
73 .with_tokens(amount.to_u512())
74 .deposit();
75 } else {
76 self.env().transfer_tokens(&caller, &amount.to_u512());
77 }
78
79 self.env().emit_event(Withdrawal {
80 account: caller,
81 value: *amount
82 });
83 }
84
85 pub fn withdraw_to(&mut self, recipient: &Address, amount: &U256) {
89 let caller = self.env().caller();
90
91 self.token.raw_burn(&caller, amount);
93
94 if recipient.is_contract() {
96 CsprDepositContractRef::new(self.env(), *recipient)
97 .with_tokens(amount.to_u512())
98 .deposit();
99 } else {
100 self.env().transfer_tokens(recipient, &amount.to_u512());
101 }
102
103 self.env().emit_event(Withdrawal {
104 account: caller,
105 value: *amount
106 });
107 }
108
109 pub fn allowance(&self, owner: &Address, spender: &Address) -> U256 {
111 self.token.allowance(owner, spender)
112 }
113
114 pub fn balance_of(&self, address: &Address) -> U256 {
116 self.token.balance_of(address)
117 }
118
119 pub fn total_supply(&self) -> U256 {
121 self.token.total_supply()
122 }
123
124 pub fn decimals(&self) -> u8 {
126 self.token.decimals()
127 }
128
129 pub fn symbol(&self) -> String {
131 self.token.symbol()
132 }
133
134 pub fn name(&self) -> String {
136 self.token.name()
137 }
138
139 pub fn approve(&mut self, spender: &Address, amount: &U256) {
141 self.token.approve(spender, amount)
142 }
143
144 pub fn transfer_from(&mut self, owner: &Address, recipient: &Address, amount: &U256) {
146 self.token.transfer_from(owner, recipient, amount)
147 }
148
149 pub fn transfer(&mut self, recipient: &Address, amount: &U256) {
151 self.token.transfer(recipient, amount)
152 }
153}
154
155pub mod events {
157 use odra::casper_event_standard;
158 use odra::casper_types::U256;
159 use odra::prelude::*;
160
161 #[odra::event]
163 pub struct Deposit {
164 pub account: Address,
166 pub value: U256
168 }
169
170 #[odra::event]
172 pub struct Withdrawal {
173 pub account: Address,
175 pub value: U256
177 }
178}
179
180#[cfg(test)]
181mod tests {
182 use crate::cep18::errors::Error::InsufficientBalance;
183 use crate::cep18::events::{Burn, Mint};
184 use crate::wrapped_native::events::{Deposit, Withdrawal};
185 use crate::wrapped_native::WrappedNativeTokenHostRef;
186 use odra::casper_event_standard::EventInstance;
187 use odra::casper_types::{U256, U512};
188 use odra::host::{Deployer, HostEnv, HostRef, NoArgs};
189 use odra::prelude::*;
190 use odra::uints::{ToU256, ToU512};
191 use odra::VmError::BalanceExceeded;
192
193 use super::WrappedNativeToken;
194
195 fn setup() -> (
196 HostEnv,
197 WrappedNativeTokenHostRef,
198 Address,
199 U512,
200 Address,
201 U512
202 ) {
203 let env = odra_test::env();
204 let token = WrappedNativeToken::deploy(&env, NoArgs);
205 let account_1 = env.get_account(0);
206 let account_1_balance = env.balance_of(&account_1);
207 let account_2 = env.get_account(1);
208 let account_2_balance = env.balance_of(&account_2);
209
210 (
211 env,
212 token,
213 account_1,
214 account_1_balance,
215 account_2,
216 account_2_balance
217 )
218 }
219
220 #[test]
221 fn test_init() {
222 let (_, token, _, _, _, _) = setup();
224
225 assert_eq!(token.total_supply(), U256::zero());
227 assert_eq!(token.name(), "Wrapped CSPR".to_string());
228 assert_eq!(token.symbol(), "WCSPR".to_string());
229 assert_eq!(token.decimals(), 9);
230 }
231
232 #[test]
233 fn test_deposit() {
234 let (env, token, account, account_balance, _, _) = setup();
236
237 let deposit_amount = 1_000u32;
239 token.with_tokens(deposit_amount.into()).deposit();
240
241 assert_eq!(account_balance - deposit_amount, env.balance_of(&account));
243
244 assert_eq!(token.balance_of(&account), deposit_amount.into());
246
247 assert!(env.emitted_event(
249 &token,
250 Mint {
251 recipient: account,
252 amount: deposit_amount.into()
253 }
254 ));
255
256 assert!(env.emitted_event(
257 &token,
258 Deposit {
259 account,
260 value: deposit_amount.into()
261 }
262 ));
263 }
264
265 #[test]
266 fn test_minting() {
267 let (env, token, account_1, _, account_2, _) = setup();
269
270 let deposit_amount = 1_000.into();
272
273 env.set_caller(account_1);
274 token.with_tokens(deposit_amount).deposit();
275
276 env.set_caller(account_2);
277 token.with_tokens(deposit_amount).deposit();
278
279 assert_eq!(
281 token.total_supply(),
282 (deposit_amount + deposit_amount)
283 .to_u256()
284 .expect("Valid U256")
285 );
286 assert!(env.event_names(&token).ends_with(
288 vec![Mint::name(), Deposit::name(), Mint::name(), Deposit::name()].as_slice()
289 ));
290 }
291
292 #[test]
293 fn test_deposit_amount_exceeding_account_balance() {
294 let (_, token, _, balance, _, _) = setup();
296 assert_eq!(
299 token.with_tokens(balance + U512::one()).try_deposit(),
300 Err(OdraError::VmError(BalanceExceeded))
301 );
302 }
303
304 #[test]
305 fn test_withdrawal() {
306 let (env, mut token, account, _, _, _) = setup();
308 let deposit_amount: U512 = 3_000.into();
309 token.with_tokens(deposit_amount).deposit();
310 let account_balance = env.balance_of(&account);
311
312 let withdrawal_amount: U256 = 1_000.into();
314 token.withdraw(&withdrawal_amount);
315
316 assert_eq!(
318 account_balance + withdrawal_amount.to_u512(),
319 env.balance_of(&account)
320 );
321 assert_eq!(
323 token.balance_of(&account),
324 deposit_amount.to_u256().expect("Valid U256") - withdrawal_amount
325 );
326
327 assert!(env.emitted_event(
329 &token,
330 Burn {
331 owner: account,
332 amount: withdrawal_amount
333 }
334 ));
335 assert!(env.emitted_event(
336 &token,
337 Withdrawal {
338 account,
339 value: withdrawal_amount
340 }
341 ));
342 }
343
344 #[test]
345 fn test_withdrawal_amount_exceeding_account_deposit() {
346 let (_, mut token, _, _, _, _) = setup();
348 assert_eq!(
351 token.try_withdraw(&U256::one()),
352 Err(InsufficientBalance.into())
353 );
354 }
355}