erc1155_wasm/
lib.rs

1use std::collections::{HashMap, HashSet};
2use wasm_bindgen::prelude::*;
3use web_sys::console;
4
5/// A highly optimized ERC1155 implementation in Rust for WebAssembly (WASM).
6#[wasm_bindgen]
7pub struct ERC1155 {
8    access_control: AccessControl,
9    balances: HashMap<(String, u32), u64>,  // (User, TokenID) -> Balance
10    approvals: HashMap<String, HashMap<String, bool>>, // User -> (Approved User -> Approval Status)
11    reentrancy_guard: ReentrancyGuard,
12}
13
14/// Reentrancy guard to prevent reentrancy attacks.
15pub struct ReentrancyGuard {
16    is_locked: bool,
17}
18
19impl ReentrancyGuard {
20    /// Creates a new reentrancy guard.
21    pub fn new() -> Self {
22        Self { is_locked: false }
23    }
24
25    /// Locks the guard, preventing reentrant calls.
26    pub fn enter(&mut self) -> Result<(), String> {
27        if self.is_locked {
28            return Err("Reentrancy detected.".into());
29        }
30        self.is_locked = true;
31        Ok(())
32    }
33
34    /// Unlocks the guard, allowing further function execution.
35    pub fn exit(&mut self) {
36        self.is_locked = false;
37    }
38}
39
40/// Structure for managing access control (owner and admin rights).
41pub struct AccessControl {
42    owner: String,
43    admins: HashSet<String>,
44}
45
46impl AccessControl {
47    /// Initializes a new access control structure with the contract owner.
48    pub fn new(owner: String) -> Self {
49        Self {
50            owner,
51            admins: HashSet::new(),
52        }
53    }
54
55    /// Checks if the caller is the owner.
56    pub fn is_owner(&self, caller: &str) -> bool {
57        self.owner == caller
58    }
59
60    /// Checks if the caller is an admin.
61    pub fn is_admin(&self, caller: &str) -> bool {
62        self.admins.contains(caller)
63    }
64
65    /// Adds a new admin to the contract (only the owner can add admins).
66    pub fn add_admin(&mut self, caller: &str, new_admin: &str) -> Result<(), String> {
67        if !self.is_owner(caller) {
68            return Err("Only the owner can add admins.".into());
69        }
70        self.admins.insert(new_admin.to_string());
71        Ok(())
72    }
73}
74
75#[wasm_bindgen]
76impl ERC1155 {
77    /// Initializes a new ERC1155 contract with the owner.
78    /// # Parameters
79    /// - `owner`: The initial owner of the contract.
80    #[wasm_bindgen(constructor)]
81    pub fn new(owner: &str) -> ERC1155 {
82        console::log_1(&format!("ERC1155 initialized with owner: {}", owner).into());
83        ERC1155 {
84            access_control: AccessControl::new(owner.to_string()),
85            balances: HashMap::new(),
86            approvals: HashMap::new(),
87            reentrancy_guard: ReentrancyGuard::new(),
88        }
89    }
90
91    /// Mints new tokens for a given `token_id` (only admins can mint).
92    /// # Parameters
93    /// - `caller`: The address calling the function (must be an admin).
94    /// - `token_id`: The ID of the token to mint.
95    /// - `amount`: The number of tokens to mint.
96    pub fn mint(&mut self, caller: &str, token_id: u32, amount: u64) -> Result<(), String> {
97        if !self.access_control.is_admin(caller) {
98            console::log_1(&format!("Mint failed: {} is not an admin", caller).into());
99            return Err("Caller is not authorized to mint tokens.".into());
100        }
101
102        self.reentrancy_guard.enter()?; // Reentrancy protection
103
104        let balance = self.balances.entry((caller.to_string(), token_id)).or_insert(0);
105        *balance += amount;
106
107        console::log_1(&format!("Minted {} tokens of ID {} to {}", amount, token_id, caller).into());
108        self.reentrancy_guard.exit(); // Reentrancy protection exit
109
110        Ok(())
111    }
112
113    /// Transfers tokens to another user.
114    /// # Parameters
115    /// - `caller`: The address initiating the transfer (must be owner or approved).
116    /// - `to`: The recipient of the tokens.
117    /// - `token_id`: The ID of the token being transferred.
118    /// - `amount`: The number of tokens to transfer.
119    pub fn transfer(&mut self, caller: &str, to: &str, token_id: u32, amount: u64) -> Result<(), String> {
120        // Check if the caller is the owner or approved to transfer
121        if !self.is_approved(caller, token_id) && !self.access_control.is_owner(caller) {
122            console::log_1(&format!("Transfer failed: {} is not approved or the owner.", caller).into());
123            return Err("Caller is not authorized to transfer.".into());
124        }
125
126        // Transfer logic
127        let balance = self.balances.entry((caller.to_string(), token_id)).or_insert(0);
128        if *balance < amount {
129            return Err("Insufficient balance.".into());
130        }
131        *balance -= amount;
132        let recipient_balance = self.balances.entry((to.to_string(), token_id)).or_insert(0);
133        *recipient_balance += amount;
134
135        console::log_1(&format!("Transferred {} tokens of ID {} from {} to {}", amount, token_id, caller, to).into());
136        Ok(())
137    }
138
139    /// Approves another user to transfer tokens on behalf of the caller.
140    pub fn approve(&mut self, caller: &str, approved: &str, token_id: u32) -> Result<(), String> {
141        let approval_entry = self.approvals.entry(caller.to_string()).or_insert_with(HashMap::new);
142        approval_entry.insert(approved.to_string(), true);
143
144        console::log_1(&format!("Approval set for {} to transfer token ID {} by {}", approved, token_id, caller).into());
145        Ok(())
146    }
147
148    /// Returns the balance of tokens for a specific user and token ID.
149    pub fn balance_of(&self, owner: &str, token_id: u32) -> u64 {
150        *self.balances.get(&(owner.to_string(), token_id)).unwrap_or(&0)
151    }
152
153    /// Adds a new admin to the contract (only the owner can add admins).
154    pub fn add_admin(&mut self, caller: &str, new_admin: &str) -> Result<(), String> {
155        self.access_control.add_admin(caller, new_admin)
156    }
157
158    /// Transfers ownership of the contract (only the current owner can transfer).
159    pub fn transfer_ownership(&mut self, caller: &str, new_owner: &str) -> Result<(), String> {
160        if self.access_control.is_owner(caller) {
161            self.access_control = AccessControl::new(new_owner.to_string());
162            console::log_1(&format!("Ownership transferred to {}", new_owner).into());
163            Ok(())
164        } else {
165            Err("Caller is not authorized to transfer ownership.".into())
166        }
167    }
168
169    /// Internal function to check if the caller is approved to transfer a token.
170    fn is_approved(&self, caller: &str, token_id: u32) -> bool {
171        if let Some(approval_map) = self.approvals.get(caller) {
172            return *approval_map.get(&token_id.to_string()).unwrap_or(&false);
173        }
174        false
175    }
176}