multiversx_sc_modules/token_merge/
mod.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
multiversx_sc::imports!();
multiversx_sc::derive_imports!();

pub mod custom_merged_token_attributes;
pub mod merged_token_instances;
pub mod merged_token_setup;

use merged_token_instances::{MergedTokenInstances, MAX_MERGED_TOKENS};

use self::custom_merged_token_attributes::MergedTokenAttributesCreator;

static SC_DOES_NOT_OWN_NFT_PARTS_ERR_MSG: &[u8] = b"NFT parts belong to another merging SC";

const MIN_MERGE_PAYMENTS: usize = 2;

#[multiversx_sc::module]
pub trait TokenMergeModule:
    merged_token_setup::MergedTokenSetupModule
    + crate::default_issue_callbacks::DefaultIssueCallbacksModule
    + crate::pause::PauseModule
{
    fn merge_tokens<AttributesCreator: MergedTokenAttributesCreator<ScType = Self>>(
        &self,
        payments: &ManagedVec<EsdtTokenPayment>,
        attr_creator: &AttributesCreator,
    ) -> EsdtTokenPayment {
        self.require_not_paused();
        require!(
            payments.len() >= MIN_MERGE_PAYMENTS,
            "Must send at least 2 tokens"
        );

        let merged_token_id = self.merged_token().get_token_id();
        let sc_address = self.blockchain().get_sc_address();
        let token_whitelist = self.mergeable_tokens_whitelist();

        let mut already_merged_tokens = ArrayVec::<_, MAX_MERGED_TOKENS>::new();
        let mut single_tokens = ArrayVec::<_, MAX_MERGED_TOKENS>::new();
        for token in payments {
            if token.token_identifier == merged_token_id {
                let token_data = self.blockchain().get_esdt_token_data(
                    &sc_address,
                    &token.token_identifier,
                    token.token_nonce,
                );

                require!(
                    token_data.creator == sc_address,
                    SC_DOES_NOT_OWN_NFT_PARTS_ERR_MSG
                );

                let merged_instances =
                    MergedTokenInstances::decode_from_first_uri(&token_data.uris);
                already_merged_tokens.push(merged_instances);
            } else {
                require!(
                    token_whitelist.contains(&token.token_identifier),
                    "Token {} cannot be merged",
                    (token.token_identifier)
                );

                single_tokens.push(token);
            }
        }

        let mut all_merged_instances = MergedTokenInstances::new();
        for already_merged in already_merged_tokens {
            all_merged_instances.merge_with_other(already_merged);
        }

        for single_token_instance in single_tokens {
            all_merged_instances.add_or_update_instance(single_token_instance.clone());
        }

        let merged_token_payment =
            self.create_merged_token(merged_token_id, &all_merged_instances, attr_creator);

        self.tx()
            .to(ToCaller)
            .payment(&merged_token_payment)
            .transfer_if_not_empty();

        merged_token_payment
    }

    fn split_tokens(
        &self,
        payments: &ManagedVec<EsdtTokenPayment>,
    ) -> ManagedVec<EsdtTokenPayment> {
        self.require_not_paused();
        require!(!payments.is_empty(), "No payments");

        let merged_token_id = self.merged_token().get_token_id();
        let sc_address = self.blockchain().get_sc_address();

        let mut output_payments = ManagedVec::new();
        for token in payments {
            require!(
                token.token_identifier == merged_token_id,
                "Invalid token to split"
            );

            let token_data = self.blockchain().get_esdt_token_data(
                &sc_address,
                &token.token_identifier,
                token.token_nonce,
            );
            require!(
                token_data.creator == sc_address,
                SC_DOES_NOT_OWN_NFT_PARTS_ERR_MSG
            );

            let previously_merged_instance_attributes =
                MergedTokenInstances::decode_from_first_uri(&token_data.uris);
            for inst in previously_merged_instance_attributes.into_instances() {
                output_payments.push(inst);
            }

            self.send()
                .esdt_local_burn(&token.token_identifier, token.token_nonce, &token.amount);
        }

        self.tx().to(ToCaller).payment(&output_payments).transfer();

        output_payments
    }

    fn split_token_partial<AttributesCreator: MergedTokenAttributesCreator<ScType = Self>>(
        &self,
        merged_token: EsdtTokenPayment,
        mut tokens_to_remove: ManagedVec<EsdtTokenPayment>,
        attr_creator: &AttributesCreator,
    ) -> ManagedVec<EsdtTokenPayment> {
        self.require_not_paused();
        self.merged_token()
            .require_same_token(&merged_token.token_identifier);

        let sc_address = self.blockchain().get_sc_address();
        let merged_token_data = self.blockchain().get_esdt_token_data(
            &sc_address,
            &merged_token.token_identifier,
            merged_token.token_nonce,
        );
        require!(
            merged_token_data.creator == sc_address,
            SC_DOES_NOT_OWN_NFT_PARTS_ERR_MSG
        );

        let mut merged_attributes =
            MergedTokenInstances::decode_from_first_uri(&merged_token_data.uris);
        for token in &tokens_to_remove {
            merged_attributes.deduct_balance_for_instance(&token);
        }

        self.send().esdt_local_burn(
            &merged_token.token_identifier,
            merged_token.token_nonce,
            &merged_token.amount,
        );

        // all removed tokens get sent to user, so we can re-use this as output payments
        let new_merged_token = self.create_merged_token(
            merged_token.token_identifier,
            &merged_attributes,
            attr_creator,
        );
        tokens_to_remove.push(new_merged_token);

        self.tx().to(ToCaller).payment(&tokens_to_remove).transfer();

        tokens_to_remove
    }
}