casper_contract_sdk/contrib/
cep18.rs1use bnum::types::U256;
62use borsh::{BorshDeserialize, BorshSerialize};
63use casper_contract_macros::CasperABI;
64
65use super::access_control::{AccessControl, AccessControlError, Role};
66#[allow(unused_imports)]
67use crate as casper_contract_sdk;
68use crate::{collections::Map, macros::blake2b256, prelude::*};
69
70#[derive(Debug, PartialEq, Eq, CasperABI, BorshSerialize, BorshDeserialize)]
74#[casper]
75pub enum Cep18Error {
76 InvalidContext,
78 InsufficientBalance,
80 InsufficientAllowance,
82 Overflow,
84 PackageHashMissing,
86 PackageHashNotPackage,
88 InvalidEventsMode,
90 MissingEventsMode,
92 Phantom,
94 FailedToGetArgBytes,
96 InsufficientRights,
98 InvalidAdminList,
100 InvalidMinterList,
102 InvalidNoneList,
104 InvalidEnableMBFlag,
106 AlreadyInitialized,
108 MintBurnDisabled,
110 CannotTargetSelfUser,
111 InvalidBurnTarget,
112}
113
114impl From<AccessControlError> for Cep18Error {
115 fn from(error: AccessControlError) -> Self {
116 match error {
117 AccessControlError::NotAuthorized => Cep18Error::InsufficientRights,
118 }
119 }
120}
121
122#[casper(message, path = crate)]
123pub struct Transfer {
124 pub from: Option<Entity>,
125 pub to: Entity,
126 pub amount: U256,
127}
128
129#[casper(message, path = crate)]
130pub struct Approve {
131 pub owner: Entity,
132 pub spender: Entity,
133 pub amount: U256,
134}
135
136pub const ADMIN_ROLE: Role = blake2b256!("admin");
137pub const MINTER_ROLE: Role = blake2b256!("minter");
138
139#[casper(path = crate)]
140pub struct CEP18State {
141 pub name: String,
142 pub symbol: String,
143 pub decimals: u8,
144 pub total_supply: U256,
145 pub balances: Map<Entity, U256>,
146 pub allowances: Map<(Entity, Entity), U256>,
147 pub enable_mint_burn: bool,
148}
149
150impl CEP18State {
151 fn transfer_balance(
152 &mut self,
153 sender: &Entity,
154 recipient: &Entity,
155 amount: U256,
156 ) -> Result<(), Cep18Error> {
157 if amount.is_zero() {
158 return Ok(());
159 }
160
161 let sender_balance = self.balances.get(sender).unwrap_or_default();
162
163 let new_sender_balance = sender_balance
164 .checked_sub(amount)
165 .ok_or(Cep18Error::InsufficientBalance)?;
166
167 let recipient_balance = self.balances.get(recipient).unwrap_or_default();
168
169 let new_recipient_balance = recipient_balance
170 .checked_add(amount)
171 .ok_or(Cep18Error::Overflow)?;
172
173 self.balances.insert(sender, &new_sender_balance);
174 self.balances.insert(recipient, &new_recipient_balance);
175 Ok(())
176 }
177}
178
179impl CEP18State {
180 pub fn new(name: &str, symbol: &str, decimals: u8, total_supply: U256) -> CEP18State {
181 CEP18State {
182 name: name.to_string(),
183 symbol: symbol.to_string(),
184 decimals,
185 total_supply,
186 balances: Map::new("balances"),
187 allowances: Map::new("allowances"),
188 enable_mint_burn: false,
189 }
190 }
191}
192
193#[casper(path = crate, export = true)]
194pub trait CEP18 {
195 #[casper(private)]
196 fn state(&self) -> &CEP18State;
197
198 #[casper(private)]
199 fn state_mut(&mut self) -> &mut CEP18State;
200
201 fn name(&self) -> &str {
202 &self.state().name
203 }
204
205 fn symbol(&self) -> &str {
206 &self.state().symbol
207 }
208
209 fn decimals(&self) -> u8 {
210 self.state().decimals
211 }
212
213 fn total_supply(&self) -> U256 {
214 self.state().total_supply
215 }
216
217 fn balance_of(&self, address: Entity) -> U256 {
218 self.state().balances.get(&address).unwrap_or_default()
219 }
220
221 fn allowance(&self, spender: Entity, owner: Entity) {
222 self.state()
223 .allowances
224 .get(&(spender, owner))
225 .unwrap_or_default();
226 }
227
228 #[casper(revert_on_error)]
229 fn approve(&mut self, spender: Entity, amount: U256) -> Result<(), Cep18Error> {
230 let owner = casper::get_caller();
231 if owner == spender {
232 return Err(Cep18Error::CannotTargetSelfUser);
233 }
234 let lookup_key = (owner, spender);
235 self.state_mut().allowances.insert(&lookup_key, &amount);
236 casper::emit(Approve {
237 owner,
238 spender,
239 amount,
240 })
241 .expect("failed to emit message");
242 Ok(())
243 }
244
245 #[casper(revert_on_error)]
246 fn decrease_allowance(&mut self, spender: Entity, amount: U256) -> Result<(), Cep18Error> {
247 let owner = casper::get_caller();
248 if owner == spender {
249 return Err(Cep18Error::CannotTargetSelfUser);
250 }
251 let lookup_key = (owner, spender);
252 let allowance = self.state().allowances.get(&lookup_key).unwrap_or_default();
253 let allowance = allowance.saturating_sub(amount);
254 self.state_mut().allowances.insert(&lookup_key, &allowance);
255 Ok(())
256 }
257
258 #[casper(revert_on_error)]
259 fn increase_allowance(&mut self, spender: Entity, amount: U256) -> Result<(), Cep18Error> {
260 let owner = casper::get_caller();
261 if owner == spender {
262 return Err(Cep18Error::CannotTargetSelfUser);
263 }
264 let lookup_key = (owner, spender);
265 let allowance = self.state().allowances.get(&lookup_key).unwrap_or_default();
266 let allowance = allowance.saturating_add(amount);
267 self.state_mut().allowances.insert(&lookup_key, &allowance);
268 Ok(())
269 }
270
271 #[casper(revert_on_error)]
272 fn transfer(&mut self, recipient: Entity, amount: U256) -> Result<(), Cep18Error> {
273 let sender = casper::get_caller();
274 if sender == recipient {
275 return Err(Cep18Error::CannotTargetSelfUser);
276 }
277 self.state_mut()
278 .transfer_balance(&sender, &recipient, amount)?;
279
280 casper::emit(Transfer {
284 from: Some(sender),
285 to: recipient,
286 amount,
287 })
288 .expect("failed to emit message");
289
290 Ok(())
291 }
292
293 #[casper(revert_on_error)]
294 fn transfer_from(
295 &mut self,
296 owner: Entity,
297 recipient: Entity,
298 amount: U256,
299 ) -> Result<(), Cep18Error> {
300 let spender = casper::get_caller();
301 if owner == recipient {
302 return Err(Cep18Error::CannotTargetSelfUser);
303 }
304
305 if amount.is_zero() {
306 return Ok(());
307 }
308
309 let spender_allowance = self
310 .state()
311 .allowances
312 .get(&(owner, spender))
313 .unwrap_or_default();
314 let new_spender_allowance = spender_allowance
315 .checked_sub(amount)
316 .ok_or(Cep18Error::InsufficientAllowance)?;
317
318 self.state_mut()
319 .transfer_balance(&owner, &recipient, amount)?;
320
321 self.state_mut()
322 .allowances
323 .insert(&(owner, spender), &new_spender_allowance);
324
325 casper::emit(Transfer {
326 from: Some(owner),
327 to: recipient,
328 amount,
329 })
330 .expect("failed to emit message");
331
332 Ok(())
333 }
334}
335
336#[casper(path = crate, export = true)]
337pub trait Mintable: CEP18 + AccessControl {
338 #[casper(revert_on_error)]
339 fn mint(&mut self, owner: Entity, amount: U256) -> Result<(), Cep18Error> {
340 if !CEP18::state(self).enable_mint_burn {
341 return Err(Cep18Error::MintBurnDisabled);
342 }
343
344 AccessControl::require_any_role(self, &[ADMIN_ROLE, MINTER_ROLE])?;
345
346 let balance = CEP18::state(self).balances.get(&owner).unwrap_or_default();
347 let new_balance = balance.checked_add(amount).ok_or(Cep18Error::Overflow)?;
348 CEP18::state_mut(self).balances.insert(&owner, &new_balance);
349 CEP18::state_mut(self).total_supply = CEP18::state(self)
350 .total_supply
351 .checked_add(amount)
352 .ok_or(Cep18Error::Overflow)?;
353
354 casper::emit(Transfer {
355 from: None,
356 to: owner,
357 amount,
358 })
359 .expect("failed to emit message");
360
361 Ok(())
362 }
363}
364
365#[casper(path = crate, export = true)]
366pub trait Burnable: CEP18 {
367 #[casper(revert_on_error)]
368 fn burn(&mut self, owner: Entity, amount: U256) -> Result<(), Cep18Error> {
369 if !self.state().enable_mint_burn {
370 return Err(Cep18Error::MintBurnDisabled);
371 }
372
373 if owner != casper::get_caller() {
374 return Err(Cep18Error::InvalidBurnTarget);
375 }
376
377 let balance = self.state().balances.get(&owner).unwrap_or_default();
378 let new_balance = balance.checked_add(amount).ok_or(Cep18Error::Overflow)?;
379 self.state_mut().balances.insert(&owner, &new_balance);
380 self.state_mut().total_supply = self
381 .state()
382 .total_supply
383 .checked_sub(amount)
384 .ok_or(Cep18Error::Overflow)?;
385 Ok(())
386 }
387}