odra_modules/
wrapped_native.rs

1//! Wrapped CSPR token implementation
2use 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/// The WrappedNativeToken module.
9#[odra::module(events = [Deposit, Withdrawal])]
10pub struct WrappedNativeToken {
11    erc20: SubModule<Erc20>
12}
13
14/// The WrappedNativeToken module implementation.
15#[odra::module]
16impl WrappedNativeToken {
17    /// Initializes the contract with the metadata.
18    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    /// Deposits native tokens into the contract.
25    #[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    /// Withdraws native tokens from the contract.
41    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    /// Sets the allowance for `spender` to spend `amount` of the caller's tokens.
54    pub fn allowance(&self, owner: &Address, spender: &Address) -> U256 {
55        self.erc20.allowance(owner, spender)
56    }
57
58    /// Returns the balance of `address`.
59    pub fn balance_of(&self, address: &Address) -> U256 {
60        self.erc20.balance_of(address)
61    }
62
63    /// Returns the total supply of the token.
64    pub fn total_supply(&self) -> U256 {
65        self.erc20.total_supply()
66    }
67
68    /// Returns the number of decimals used by the token.
69    pub fn decimals(&self) -> u8 {
70        self.erc20.decimals()
71    }
72
73    /// Returns the symbol of the token.
74    pub fn symbol(&self) -> String {
75        self.erc20.symbol()
76    }
77
78    /// Returns the name of the token.
79    pub fn name(&self) -> String {
80        self.erc20.name()
81    }
82
83    /// Approves `spender` to spend `amount` of the caller's tokens.
84    pub fn approve(&mut self, spender: &Address, amount: &U256) {
85        self.erc20.approve(spender, amount)
86    }
87
88    /// Transfers `amount` of the owners tokens to `recipient` using allowance.
89    pub fn transfer_from(&mut self, owner: &Address, recipient: &Address, amount: &U256) {
90        self.erc20.transfer_from(owner, recipient, amount)
91    }
92
93    /// Transfers `amount` of the caller's tokens to `recipient`.
94    pub fn transfer(&mut self, recipient: &Address, amount: &U256) {
95        self.erc20.transfer(recipient, amount)
96    }
97}
98
99/// Events emitted by the WrappedNativeToken module.
100pub mod events {
101    use odra::casper_event_standard;
102    use odra::casper_types::U256;
103    use odra::prelude::*;
104
105    /// Event emitted when native tokens are deposited into the contract.
106    #[odra::event]
107    pub struct Deposit {
108        /// An Address of the account that deposited the tokens.
109        pub account: Address,
110        /// The amount of tokens deposited.
111        pub value: U256
112    }
113
114    /// Event emitted when native tokens are withdrawn from the contract.
115    #[odra::event]
116    pub struct Withdrawal {
117        /// An Address of the account that withdrew the tokens.
118        pub account: Address,
119        /// The amount of tokens withdrawn.
120        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        // When deploy a contract.
167        let (_, token, _, _, _, _) = setup();
168
169        // Then the contract has correct metadata.
170        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        // Given a fresh contract.
179        let (env, token, account, account_balance, _, _) = setup();
180
181        // When deposit tokens.
182        let deposit_amount = 1_000u32;
183        token.with_tokens(deposit_amount.into()).deposit();
184
185        // Then native tokens are correctly deducted.
186        assert_eq!(account_balance - deposit_amount, env.balance_of(&account));
187
188        // Then the contract balance is updated.
189        assert_eq!(token.balance_of(&account), deposit_amount.into());
190
191        // The events were emitted.
192        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        // Given a fresh contract.
213        let (env, token, account_1, _, account_2, _) = setup();
214
215        // When two users deposit some tokens.
216        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        // Then the total supply in the sum of deposits.
225        assert_eq!(
226            token.total_supply(),
227            (deposit_amount + deposit_amount).to_u256().unwrap()
228        );
229        // Then events were emitted.
230        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        // Given a new contract.
244        let (_, token, _, balance, _, _) = setup();
245        // When the deposit amount exceeds the user's balance
246        // Then an error occurs.
247        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        // Deposit some tokens in the contract.
256        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        // When withdraw some tokens.
262        let withdrawal_amount: U256 = 1_000.into();
263        token.withdraw(&withdrawal_amount);
264
265        // Then the user has the withdrawn tokens back.
266        assert_eq!(
267            account_balance + withdrawal_amount.to_u512(),
268            env.balance_of(&account)
269        );
270        // Then the balance in the contract is deducted.
271        assert_eq!(
272            token.balance_of(&account),
273            deposit_amount.to_u256().unwrap() - withdrawal_amount
274        );
275
276        // Then events were emitted.
277        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        // Given a new contract.
297        let (_, mut token, _, _, _, _) = setup();
298        // When the user withdraws amount exceeds the user's deposit
299        // Then an error occurs.
300        assert_eq!(
301            token.try_withdraw(&U256::one()),
302            Err(InsufficientBalance.into())
303        );
304    }
305}