1use crate::erc20::Erc20;
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 erc20: SubModule<Erc20>
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.erc20.init(symbol, name, 9, None);
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.erc20.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.erc20.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 allowance(&self, owner: &Address, spender: &Address) -> U256 {
87 self.erc20.allowance(owner, spender)
88 }
89
90 pub fn balance_of(&self, address: &Address) -> U256 {
92 self.erc20.balance_of(address)
93 }
94
95 pub fn total_supply(&self) -> U256 {
97 self.erc20.total_supply()
98 }
99
100 pub fn decimals(&self) -> u8 {
102 self.erc20.decimals()
103 }
104
105 pub fn symbol(&self) -> String {
107 self.erc20.symbol()
108 }
109
110 pub fn name(&self) -> String {
112 self.erc20.name()
113 }
114
115 pub fn approve(&mut self, spender: &Address, amount: &U256) {
117 self.erc20.approve(spender, amount)
118 }
119
120 pub fn transfer_from(&mut self, owner: &Address, recipient: &Address, amount: &U256) {
122 self.erc20.transfer_from(owner, recipient, amount)
123 }
124
125 pub fn transfer(&mut self, recipient: &Address, amount: &U256) {
127 self.erc20.transfer(recipient, amount)
128 }
129}
130
131pub mod events {
133 use odra::casper_event_standard;
134 use odra::casper_types::U256;
135 use odra::prelude::*;
136
137 #[odra::event]
139 pub struct Deposit {
140 pub account: Address,
142 pub value: U256
144 }
145
146 #[odra::event]
148 pub struct Withdrawal {
149 pub account: Address,
151 pub value: U256
153 }
154}
155
156#[cfg(test)]
157mod tests {
158 use crate::erc20::errors::Error::InsufficientBalance;
159 use crate::erc20::events::Transfer;
160 use crate::wrapped_native::events::{Deposit, Withdrawal};
161 use crate::wrapped_native::WrappedNativeTokenHostRef;
162 use odra::casper_event_standard::EventInstance;
163 use odra::casper_types::{U256, U512};
164 use odra::host::{Deployer, HostEnv, HostRef, NoArgs};
165 use odra::prelude::*;
166 use odra::uints::{ToU256, ToU512};
167 use odra::VmError::BalanceExceeded;
168
169 use super::WrappedNativeToken;
170
171 fn setup() -> (
172 HostEnv,
173 WrappedNativeTokenHostRef,
174 Address,
175 U512,
176 Address,
177 U512
178 ) {
179 let env = odra_test::env();
180 let token = WrappedNativeToken::deploy(&env, NoArgs);
181 let account_1 = env.get_account(0);
182 let account_1_balance = env.balance_of(&account_1);
183 let account_2 = env.get_account(1);
184 let account_2_balance = env.balance_of(&account_2);
185
186 (
187 env,
188 token,
189 account_1,
190 account_1_balance,
191 account_2,
192 account_2_balance
193 )
194 }
195
196 #[test]
197 fn test_init() {
198 let (_, token, _, _, _, _) = setup();
200
201 assert_eq!(token.total_supply(), U256::zero());
203 assert_eq!(token.name(), "Wrapped CSPR".to_string());
204 assert_eq!(token.symbol(), "WCSPR".to_string());
205 assert_eq!(token.decimals(), 9);
206 }
207
208 #[test]
209 fn test_deposit() {
210 let (env, token, account, account_balance, _, _) = setup();
212
213 let deposit_amount = 1_000u32;
215 token.with_tokens(deposit_amount.into()).deposit();
216
217 assert_eq!(account_balance - deposit_amount, env.balance_of(&account));
219
220 assert_eq!(token.balance_of(&account), deposit_amount.into());
222
223 assert!(env.emitted_event(
225 token.address(),
226 &Transfer {
227 from: None,
228 to: Some(account),
229 amount: deposit_amount.into()
230 }
231 ));
232
233 assert!(env.emitted_event(
234 token.address(),
235 &Deposit {
236 account,
237 value: deposit_amount.into()
238 }
239 ));
240 }
241
242 #[test]
243 fn test_minting() {
244 let (env, token, account_1, _, account_2, _) = setup();
246
247 let deposit_amount = 1_000.into();
249
250 env.set_caller(account_1);
251 token.with_tokens(deposit_amount).deposit();
252
253 env.set_caller(account_2);
254 token.with_tokens(deposit_amount).deposit();
255
256 assert_eq!(
258 token.total_supply(),
259 (deposit_amount + deposit_amount).to_u256().unwrap()
260 );
261 assert!(env.event_names(token.address()).ends_with(
263 vec![
264 Transfer::name(),
265 Deposit::name(),
266 Transfer::name(),
267 Deposit::name()
268 ]
269 .as_slice()
270 ));
271 }
272
273 #[test]
274 fn test_deposit_amount_exceeding_account_balance() {
275 let (_, token, _, balance, _, _) = setup();
277 assert_eq!(
280 token.with_tokens(balance + U512::one()).try_deposit(),
281 Err(OdraError::VmError(BalanceExceeded))
282 );
283 }
284
285 #[test]
286 fn test_withdrawal() {
287 let (env, mut token, account, _, _, _) = setup();
289 let deposit_amount: U512 = 3_000.into();
290 token.with_tokens(deposit_amount).deposit();
291 let account_balance = env.balance_of(&account);
292
293 let withdrawal_amount: U256 = 1_000.into();
295 token.withdraw(&withdrawal_amount);
296
297 assert_eq!(
299 account_balance + withdrawal_amount.to_u512(),
300 env.balance_of(&account)
301 );
302 assert_eq!(
304 token.balance_of(&account),
305 deposit_amount.to_u256().unwrap() - withdrawal_amount
306 );
307
308 assert!(env.emitted_event(
310 token.address(),
311 &Transfer {
312 from: Some(account),
313 to: None,
314 amount: withdrawal_amount
315 }
316 ));
317 assert!(env.emitted_event(
318 token.address(),
319 &Withdrawal {
320 account,
321 value: withdrawal_amount
322 }
323 ));
324 }
325
326 #[test]
327 fn test_withdrawal_amount_exceeding_account_deposit() {
328 let (_, mut token, _, _, _, _) = setup();
330 assert_eq!(
333 token.try_withdraw(&U256::one()),
334 Err(InsufficientBalance.into())
335 );
336 }
337}