odra_modules/
wrapped_native.rs

1//! Wrapped CSPR token implementation
2use 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/// An event emitted when native tokens are deposited into the contract.
9#[odra::event]
10pub struct OnCsprDeposit {
11    /// Address of the account that deposited the tokens.
12    pub account: Address,
13    /// The amount of tokens deposited.
14    pub value: U512
15}
16
17/// The CsprDeposit contract.
18#[odra::module]
19pub struct CsprDeposit {}
20
21/// The CsprDeposit contract implementation.
22#[odra::module]
23impl CsprDeposit {
24    /// Deposits native tokens into the contract.
25    #[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/// The WrappedNativeToken module.
35#[odra::module(events = [Deposit, Withdrawal])]
36pub struct WrappedNativeToken {
37    token: SubModule<Cep18>
38}
39
40/// The WrappedNativeToken module implementation.
41#[odra::module]
42impl WrappedNativeToken {
43    /// Initializes the contract with the metadata.
44    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    /// Deposits native tokens into the contract.
51    #[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    /// Withdraws native tokens from the contract.
67    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    /// Sets the allowance for `spender` to spend `amount` of the caller's tokens.
86    pub fn allowance(&self, owner: &Address, spender: &Address) -> U256 {
87        self.token.allowance(owner, spender)
88    }
89
90    /// Returns the balance of `address`.
91    pub fn balance_of(&self, address: &Address) -> U256 {
92        self.token.balance_of(address)
93    }
94
95    /// Returns the total supply of the token.
96    pub fn total_supply(&self) -> U256 {
97        self.token.total_supply()
98    }
99
100    /// Returns the number of decimals used by the token.
101    pub fn decimals(&self) -> u8 {
102        self.token.decimals()
103    }
104
105    /// Returns the symbol of the token.
106    pub fn symbol(&self) -> String {
107        self.token.symbol()
108    }
109
110    /// Returns the name of the token.
111    pub fn name(&self) -> String {
112        self.token.name()
113    }
114
115    /// Approves `spender` to spend `amount` of the caller's tokens.
116    pub fn approve(&mut self, spender: &Address, amount: &U256) {
117        self.token.approve(spender, amount)
118    }
119
120    /// Transfers `amount` of the owners tokens to `recipient` using allowance.
121    pub fn transfer_from(&mut self, owner: &Address, recipient: &Address, amount: &U256) {
122        self.token.transfer_from(owner, recipient, amount)
123    }
124
125    /// Transfers `amount` of the caller's tokens to `recipient`.
126    pub fn transfer(&mut self, recipient: &Address, amount: &U256) {
127        self.token.transfer(recipient, amount)
128    }
129}
130
131/// Events emitted by the WrappedNativeToken module.
132pub mod events {
133    use odra::casper_event_standard;
134    use odra::casper_types::U256;
135    use odra::prelude::*;
136
137    /// Event emitted when native tokens are deposited into the contract.
138    #[odra::event]
139    pub struct Deposit {
140        /// An Address of the account that deposited the tokens.
141        pub account: Address,
142        /// The amount of tokens deposited.
143        pub value: U256
144    }
145
146    /// Event emitted when native tokens are withdrawn from the contract.
147    #[odra::event]
148    pub struct Withdrawal {
149        /// An Address of the account that withdrew the tokens.
150        pub account: Address,
151        /// The amount of tokens withdrawn.
152        pub value: U256
153    }
154}
155
156#[cfg(test)]
157mod tests {
158    use crate::cep18::errors::Error::InsufficientBalance;
159    use crate::cep18::events::{Burn, Mint};
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        // When deploy a contract.
199        let (_, token, _, _, _, _) = setup();
200
201        // Then the contract has correct metadata.
202        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        // Given a fresh contract.
211        let (env, token, account, account_balance, _, _) = setup();
212
213        // When deposit tokens.
214        let deposit_amount = 1_000u32;
215        token.with_tokens(deposit_amount.into()).deposit();
216
217        // Then native tokens are correctly deducted.
218        assert_eq!(account_balance - deposit_amount, env.balance_of(&account));
219
220        // Then the contract balance is updated.
221        assert_eq!(token.balance_of(&account), deposit_amount.into());
222
223        // The events were emitted.
224        assert!(env.emitted_event(
225            &token,
226            Mint {
227                recipient: account,
228                amount: deposit_amount.into()
229            }
230        ));
231
232        assert!(env.emitted_event(
233            &token,
234            Deposit {
235                account,
236                value: deposit_amount.into()
237            }
238        ));
239    }
240
241    #[test]
242    fn test_minting() {
243        // Given a fresh contract.
244        let (env, token, account_1, _, account_2, _) = setup();
245
246        // When two users deposit some tokens.
247        let deposit_amount = 1_000.into();
248
249        env.set_caller(account_1);
250        token.with_tokens(deposit_amount).deposit();
251
252        env.set_caller(account_2);
253        token.with_tokens(deposit_amount).deposit();
254
255        // Then the total supply in the sum of deposits.
256        assert_eq!(
257            token.total_supply(),
258            (deposit_amount + deposit_amount)
259                .to_u256()
260                .expect("Valid U256")
261        );
262        // Then events were emitted.
263        assert!(env.event_names(&token).ends_with(
264            vec![Mint::name(), Deposit::name(), Mint::name(), Deposit::name()].as_slice()
265        ));
266    }
267
268    #[test]
269    fn test_deposit_amount_exceeding_account_balance() {
270        // Given a new contract.
271        let (_, token, _, balance, _, _) = setup();
272        // When the deposit amount exceeds the user's balance
273        // Then an error occurs.
274        assert_eq!(
275            token.with_tokens(balance + U512::one()).try_deposit(),
276            Err(OdraError::VmError(BalanceExceeded))
277        );
278    }
279
280    #[test]
281    fn test_withdrawal() {
282        // Deposit some tokens in the contract.
283        let (env, mut token, account, _, _, _) = setup();
284        let deposit_amount: U512 = 3_000.into();
285        token.with_tokens(deposit_amount).deposit();
286        let account_balance = env.balance_of(&account);
287
288        // When withdraw some tokens.
289        let withdrawal_amount: U256 = 1_000.into();
290        token.withdraw(&withdrawal_amount);
291
292        // Then the user has the withdrawn tokens back.
293        assert_eq!(
294            account_balance + withdrawal_amount.to_u512(),
295            env.balance_of(&account)
296        );
297        // Then the balance in the contract is deducted.
298        assert_eq!(
299            token.balance_of(&account),
300            deposit_amount.to_u256().expect("Valid U256") - withdrawal_amount
301        );
302
303        // Then events were emitted.
304        assert!(env.emitted_event(
305            &token,
306            Burn {
307                owner: account,
308                amount: withdrawal_amount
309            }
310        ));
311        assert!(env.emitted_event(
312            &token,
313            Withdrawal {
314                account,
315                value: withdrawal_amount
316            }
317        ));
318    }
319
320    #[test]
321    fn test_withdrawal_amount_exceeding_account_deposit() {
322        // Given a new contract.
323        let (_, mut token, _, _, _, _) = setup();
324        // When the user withdraws amount exceeds the user's deposit
325        // Then an error occurs.
326        assert_eq!(
327            token.try_withdraw(&U256::one()),
328            Err(InsufficientBalance.into())
329        );
330    }
331}