1use crate::erc20::Erc20;
3use crate::wrapped_native::events::{Deposit, Withdrawal};
4use odra::casper_types::U256;
5use odra::prelude::*;
6use odra::uints::{ToU256, ToU512};
7
8#[odra::module(events = [Deposit, Withdrawal])]
10pub struct WrappedNativeToken {
11 erc20: SubModule<Erc20>
12}
13
14#[odra::module]
16impl WrappedNativeToken {
17 pub fn init(&mut self) {
19 let symbol = "WCSPR".to_string();
20 let name = "Wrapped CSPR".to_string();
21 self.erc20.init(symbol, name, 9, None);
22 }
23
24 #[odra(payable)]
26 pub fn deposit(&mut self) {
27 let caller = self.env().caller();
28
29 let amount = self.env().attached_value();
30
31 let amount = amount.to_u256().unwrap_or_revert(self);
32 self.erc20.mint(&caller, &amount);
33
34 self.env().emit_event(Deposit {
35 account: caller,
36 value: amount
37 });
38 }
39
40 pub fn withdraw(&mut self, amount: &U256) {
42 let caller = self.env().caller();
43
44 self.erc20.burn(&caller, amount);
45 self.env().transfer_tokens(&caller, &amount.to_u512());
46
47 self.env().emit_event(Withdrawal {
48 account: caller,
49 value: *amount
50 });
51 }
52
53 pub fn allowance(&self, owner: &Address, spender: &Address) -> U256 {
55 self.erc20.allowance(owner, spender)
56 }
57
58 pub fn balance_of(&self, address: &Address) -> U256 {
60 self.erc20.balance_of(address)
61 }
62
63 pub fn total_supply(&self) -> U256 {
65 self.erc20.total_supply()
66 }
67
68 pub fn decimals(&self) -> u8 {
70 self.erc20.decimals()
71 }
72
73 pub fn symbol(&self) -> String {
75 self.erc20.symbol()
76 }
77
78 pub fn name(&self) -> String {
80 self.erc20.name()
81 }
82
83 pub fn approve(&mut self, spender: &Address, amount: &U256) {
85 self.erc20.approve(spender, amount)
86 }
87
88 pub fn transfer_from(&mut self, owner: &Address, recipient: &Address, amount: &U256) {
90 self.erc20.transfer_from(owner, recipient, amount)
91 }
92
93 pub fn transfer(&mut self, recipient: &Address, amount: &U256) {
95 self.erc20.transfer(recipient, amount)
96 }
97}
98
99pub mod events {
101 use odra::casper_event_standard;
102 use odra::casper_types::U256;
103 use odra::prelude::*;
104
105 #[odra::event]
107 pub struct Deposit {
108 pub account: Address,
110 pub value: U256
112 }
113
114 #[odra::event]
116 pub struct Withdrawal {
117 pub account: Address,
119 pub value: U256
121 }
122}
123
124#[cfg(test)]
125mod tests {
126 use crate::erc20::errors::Error::InsufficientBalance;
127 use crate::erc20::events::Transfer;
128 use crate::wrapped_native::events::{Deposit, Withdrawal};
129 use crate::wrapped_native::WrappedNativeTokenHostRef;
130 use odra::casper_event_standard::EventInstance;
131 use odra::casper_types::{U256, U512};
132 use odra::host::{Deployer, HostEnv, HostRef, NoArgs};
133 use odra::prelude::*;
134 use odra::uints::{ToU256, ToU512};
135 use odra::VmError::BalanceExceeded;
136
137 use super::WrappedNativeToken;
138
139 fn setup() -> (
140 HostEnv,
141 WrappedNativeTokenHostRef,
142 Address,
143 U512,
144 Address,
145 U512
146 ) {
147 let env = odra_test::env();
148 let token = WrappedNativeToken::deploy(&env, NoArgs);
149 let account_1 = env.get_account(0);
150 let account_1_balance = env.balance_of(&account_1);
151 let account_2 = env.get_account(1);
152 let account_2_balance = env.balance_of(&account_2);
153
154 (
155 env,
156 token,
157 account_1,
158 account_1_balance,
159 account_2,
160 account_2_balance
161 )
162 }
163
164 #[test]
165 fn test_init() {
166 let (_, token, _, _, _, _) = setup();
168
169 assert_eq!(token.total_supply(), U256::zero());
171 assert_eq!(token.name(), "Wrapped CSPR".to_string());
172 assert_eq!(token.symbol(), "WCSPR".to_string());
173 assert_eq!(token.decimals(), 9);
174 }
175
176 #[test]
177 fn test_deposit() {
178 let (env, token, account, account_balance, _, _) = setup();
180
181 let deposit_amount = 1_000u32;
183 token.with_tokens(deposit_amount.into()).deposit();
184
185 assert_eq!(account_balance - deposit_amount, env.balance_of(&account));
187
188 assert_eq!(token.balance_of(&account), deposit_amount.into());
190
191 assert!(env.emitted_event(
193 token.address(),
194 &Transfer {
195 from: None,
196 to: Some(account),
197 amount: deposit_amount.into()
198 }
199 ));
200
201 assert!(env.emitted_event(
202 token.address(),
203 &Deposit {
204 account,
205 value: deposit_amount.into()
206 }
207 ));
208 }
209
210 #[test]
211 fn test_minting() {
212 let (env, token, account_1, _, account_2, _) = setup();
214
215 let deposit_amount = 1_000.into();
217
218 env.set_caller(account_1);
219 token.with_tokens(deposit_amount).deposit();
220
221 env.set_caller(account_2);
222 token.with_tokens(deposit_amount).deposit();
223
224 assert_eq!(
226 token.total_supply(),
227 (deposit_amount + deposit_amount).to_u256().unwrap()
228 );
229 assert!(env.event_names(token.address()).ends_with(
231 vec![
232 Transfer::name(),
233 Deposit::name(),
234 Transfer::name(),
235 Deposit::name()
236 ]
237 .as_slice()
238 ));
239 }
240
241 #[test]
242 fn test_deposit_amount_exceeding_account_balance() {
243 let (_, token, _, balance, _, _) = setup();
245 assert_eq!(
248 token.with_tokens(balance + U512::one()).try_deposit(),
249 Err(OdraError::VmError(BalanceExceeded))
250 );
251 }
252
253 #[test]
254 fn test_withdrawal() {
255 let (env, mut token, account, _, _, _) = setup();
257 let deposit_amount: U512 = 3_000.into();
258 token.with_tokens(deposit_amount).deposit();
259 let account_balance = env.balance_of(&account);
260
261 let withdrawal_amount: U256 = 1_000.into();
263 token.withdraw(&withdrawal_amount);
264
265 assert_eq!(
267 account_balance + withdrawal_amount.to_u512(),
268 env.balance_of(&account)
269 );
270 assert_eq!(
272 token.balance_of(&account),
273 deposit_amount.to_u256().unwrap() - withdrawal_amount
274 );
275
276 assert!(env.emitted_event(
278 token.address(),
279 &Transfer {
280 from: Some(account),
281 to: None,
282 amount: withdrawal_amount
283 }
284 ));
285 assert!(env.emitted_event(
286 token.address(),
287 &Withdrawal {
288 account,
289 value: withdrawal_amount
290 }
291 ));
292 }
293
294 #[test]
295 fn test_withdrawal_amount_exceeding_account_deposit() {
296 let (_, mut token, _, _, _, _) = setup();
298 assert_eq!(
301 token.try_withdraw(&U256::one()),
302 Err(InsufficientBalance.into())
303 );
304 }
305}