-
Notifications
You must be signed in to change notification settings - Fork 37
/
Copy pathlib.rs
1057 lines (953 loc) · 35.7 KB
/
lib.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
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
//! Upgradable wCCD smart contract (Concordium's canonical wCCD
//! implementation following the CIS-2 standard)
//!
//! # Description
//! The token in this contract is a wrapped CCD (wCCD), meaning it holds a one
//! to one correspondence with the CCD. This smart contract can be
//! paused/unpaused and upgraded by an admin address.
//! Note: The word 'address' refers to either an account address or a
//! contract address.
//!
//! As follows from the CIS-2 specification, the contract has a `transfer`
//! function for transferring an amount of a specific token type from one
//! address to another address. An address can enable and disable one or more
//! addresses as operators with the `updateOperator` function. An operator of
//! some token owner address is allowed to transfer or unwrap any tokens of the
//! owner.
//!
//! Besides the contract functions required by the CIS-2 standard, this contract
//! implements a function `wrap` for converting CCD into wCCD tokens. It accepts
//! an amount of CCD and mints this amount of wCCD tokens. The function requires
//! a receiving address as an input parameter that receives the minted wCCD
//! tokens.
//!
//! The contract also implements a contract function `unwrap` for converting
//! wCCD back into CCD. The function takes the amount of tokens to unwrap, the
//! address owning these wCCD and a receiver for the CCD. If the sender is the
//! owner or an operator of the owner, the wCCD tokens are burned and the amount
//! of CCD is sent to the receiver.
//!
//! There is a corresponding tutorial for this smart contract available here:
//! https://developer.concordium.software/en/mainnet/smart-contracts/tutorials/wCCD/index.html
//!
//! The admin address can pause/unpause the protocol, set implementors, transfer
//! the admin address to a new address, and update the metadata URL.
#![cfg_attr(not(feature = "std"), no_std)]
use concordium_cis2::{Cis2Event, *};
use concordium_std::*;
/// The id of the wCCD token in this contract.
pub const TOKEN_ID_WCCD: ContractTokenId = TokenIdUnit();
/// Tag for the NewAdmin event.
pub const NEW_ADMIN_EVENT_TAG: u8 = 0;
/// List of supported standards by this contract address.
pub const SUPPORTS_STANDARDS: [StandardIdentifier<'static>; 2] =
[CIS0_STANDARD_IDENTIFIER, CIS2_STANDARD_IDENTIFIER];
/// Sha256 digest
pub type Sha256 = [u8; 32];
// Types
/// Contract token ID type.
/// Since this contract will only ever contain this one token type, we use the
/// smallest possible token ID.
pub type ContractTokenId = TokenIdUnit;
/// Contract token amount type.
/// Since this contract is wrapping the CCD and the CCD can be represented as a
/// u64, we can specialize the token amount to u64 reducing module size and cost
/// of arithmetics.
pub type ContractTokenAmount = TokenAmountU64;
/// The state tracked for each address.
#[derive(Serial, DeserialWithState, Deletable)]
#[concordium(state_parameter = "S")]
pub struct AddressState<S = StateApi> {
/// The number of tokens owned by this address.
pub balance: ContractTokenAmount,
/// The address which are currently enabled as operators for this token and
/// this address.
pub operators: StateSet<Address, S>,
}
/// The contract state,
#[derive(Serial, DeserialWithState)]
#[concordium(state_parameter = "S")]
struct State<S: HasStateApi = StateApi> {
/// The admin address can upgrade the contract, pause and unpause the
/// contract, transfer the admin address to a new address, set
/// implementors, and update the metadata URL in the contract.
admin: Address,
/// Contract is paused if `paused = true` and unpaused if `paused = false`.
paused: bool,
/// Map specifying the `AddressState` (balance and operators) for every
/// address.
token: StateMap<Address, AddressState<S>, S>,
/// Map with contract addresses providing implementations of additional
/// standards.
implementors: StateMap<StandardIdentifierOwned, Vec<ContractAddress>, S>,
/// The MetadataUrl of the token.
/// `StateBox` allows for lazy loading data. This is helpful
/// in the situations when one wants to do a partial update not touching
/// this field, which can be large.
metadata_url: StateBox<MetadataUrl, S>,
}
/// The parameter type for the contract function `unwrap`.
/// Takes an amount of tokens and unwraps the CCD and sends it to a receiver.
#[derive(Serialize, SchemaType)]
pub struct UnwrapParams {
/// The amount of tokens to unwrap.
pub amount: ContractTokenAmount,
/// The owner of the tokens.
pub owner: Address,
/// The address to receive these unwrapped CCD.
pub receiver: Receiver,
/// If the `Receiver` is a contract the unwrapped CCD together with these
/// additional data bytes are sent to the function entrypoint specified in
/// the `Receiver`.
pub data: AdditionalData,
}
/// The parameter type for the contract function `wrap`.
/// It includes a receiver for receiving the wrapped CCD tokens.
#[derive(Serialize, SchemaType, Debug)]
pub struct WrapParams {
/// The address to receive these tokens.
/// If the receiver is the sender of the message wrapping the tokens, it
/// will not log a transfer event.
pub to: Receiver,
/// Some additional data bytes are used in the `OnReceivingCis2` hook. Only
/// if the `Receiver` is a contract and the `Receiver` is not
/// the invoker of the wrap function the receive hook function is
/// executed. The `OnReceivingCis2` hook invokes the function entrypoint
/// specified in the `Receiver` with these additional data bytes as
/// part of the input parameters. This action allows the receiving smart
/// contract to react to the credited wCCD amount.
pub data: AdditionalData,
}
/// The parameter type for the contract function `setImplementors`.
/// Takes a standard identifier and list of contract addresses providing
/// implementations of this standard.
#[derive(Debug, Serialize, SchemaType, PartialEq, Eq)]
pub struct SetImplementorsParams {
/// The identifier for the standard.
pub id: StandardIdentifierOwned,
/// The addresses of the implementors of the standard.
pub implementors: Vec<ContractAddress>,
}
/// The parameter type for the contract function `upgrade`.
/// Takes the new module and optionally an entrypoint to call in the new module
/// after triggering the upgrade. The upgrade is reverted if the entrypoint
/// fails. This is useful for doing migration in the same transaction triggering
/// the upgrade.
#[derive(Debug, Serialize, SchemaType, PartialEq, Eq)]
pub struct UpgradeParams {
/// The new module reference.
pub module: ModuleReference,
/// Optional entrypoint to call in the new module after upgrade.
pub migrate: Option<(OwnedEntrypointName, OwnedParameter)>,
}
/// The return type for the contract function `view`.
#[derive(Serialize, SchemaType, PartialEq, Eq, Debug)]
pub struct ReturnBasicState {
/// The admin address can upgrade the contract, pause and unpause the
/// contract, transfer the admin address to a new address, set
/// implementors, and update the metadata URL in the contract.
pub admin: Address,
/// Contract is paused if `paused = true` and unpaused if `paused = false`.
pub paused: bool,
/// The metadata URL of the token.
pub metadata_url: MetadataUrl,
}
/// The parameter type for the contract function `setMetadataUrl`.
#[derive(Serialize, SchemaType, Clone, PartialEq, Eq, Debug)]
pub struct SetMetadataUrlParams {
/// The URL following the specification RFC1738.
pub url: String,
/// The hash of the document stored at the above URL.
pub hash: Option<Sha256>,
}
/// The parameter type for the contract function `setPaused`.
#[derive(Serialize, SchemaType, PartialEq, Eq, Debug)]
#[repr(transparent)]
pub struct SetPausedParams {
/// Contract is paused if `paused = true` and unpaused if `paused = false`.
pub paused: bool,
}
/// A NewAdminEvent introduced by this smart contract.
#[derive(Serialize, SchemaType, PartialEq, Eq, Debug)]
#[repr(transparent)]
#[concordium(transparent)]
pub struct NewAdminEvent {
/// New admin address.
pub new_admin: Address,
}
/// Tagged events to be serialized for the event log.
#[derive(SchemaType, Serialize, PartialEq, Eq, Debug)]
#[concordium(repr(u8))]
pub enum WccdEvent {
NewAdmin {
new_admin: NewAdminEvent,
},
#[concordium(forward = cis2_events)]
Cis2Event(Cis2Event<ContractTokenId, ContractTokenAmount>),
}
/// The different errors the contract can produce.
#[derive(Serialize, Debug, PartialEq, Eq, Reject, SchemaType)]
pub enum CustomContractError {
/// Failed parsing the parameter.
#[from(ParseError)]
ParseParams,
/// Failed logging: Log is full.
LogFull,
/// Failed logging: Log is malformed.
LogMalformed,
/// Contract is paused.
ContractPaused,
/// Failed to invoke a contract.
InvokeContractError,
/// Failed to invoke a transfer.
InvokeTransferError,
/// Upgrade failed because the new module does not exist.
FailedUpgradeMissingModule,
/// Upgrade failed because the new module does not contain a contract with a
/// matching name.
FailedUpgradeMissingContract,
/// Upgrade failed because the smart contract version of the module is not
/// supported.
FailedUpgradeUnsupportedModuleVersion,
}
pub type ContractError = Cis2Error<CustomContractError>;
pub type ContractResult<A> = Result<A, ContractError>;
/// Mapping the logging errors to ContractError.
impl From<LogError> for CustomContractError {
fn from(le: LogError) -> Self {
match le {
LogError::Full => Self::LogFull,
LogError::Malformed => Self::LogMalformed,
}
}
}
/// Mapping errors related to contract invocations to CustomContractError.
impl<T> From<CallContractError<T>> for CustomContractError {
fn from(_cce: CallContractError<T>) -> Self { Self::InvokeContractError }
}
/// Mapping errors related to contract invocations to CustomContractError.
impl From<TransferError> for CustomContractError {
fn from(_te: TransferError) -> Self { Self::InvokeTransferError }
}
/// Mapping errors related to contract upgrades to CustomContractError.
impl From<UpgradeError> for CustomContractError {
#[inline(always)]
fn from(ue: UpgradeError) -> Self {
match ue {
UpgradeError::MissingModule => Self::FailedUpgradeMissingModule,
UpgradeError::MissingContract => Self::FailedUpgradeMissingContract,
UpgradeError::UnsupportedModuleVersion => Self::FailedUpgradeUnsupportedModuleVersion,
}
}
}
/// Mapping CustomContractError to ContractError
impl From<CustomContractError> for ContractError {
fn from(c: CustomContractError) -> Self { Cis2Error::Custom(c) }
}
impl State {
/// Creates a new state with no one owning any tokens by default.
fn new(state_builder: &mut StateBuilder, admin: Address, metadata_url: MetadataUrl) -> Self {
State {
admin,
paused: false,
token: state_builder.new_map(),
implementors: state_builder.new_map(),
metadata_url: state_builder.new_box(metadata_url),
}
}
/// Get the current balance of a given token id for a given address.
/// Results in an error if the token id does not exist in the state.
fn balance(
&self,
token_id: &ContractTokenId,
address: &Address,
) -> ContractResult<ContractTokenAmount> {
ensure_eq!(token_id, &TOKEN_ID_WCCD, ContractError::InvalidTokenId);
Ok(self.token.get(address).map(|s| s.balance).unwrap_or_else(|| 0u64.into()))
}
/// Check if an address is an operator of a specific owner address.
fn is_operator(&self, address: &Address, owner: &Address) -> bool {
self.token
.get(owner)
.map(|address_state| address_state.operators.contains(address))
.unwrap_or(false)
}
/// Update the state with a transfer.
/// Results in an error if the token id does not exist in the state or if
/// the from address has insufficient tokens to do the transfer.
fn transfer(
&mut self,
token_id: &ContractTokenId,
amount: ContractTokenAmount,
from: &Address,
to: &Address,
state_builder: &mut StateBuilder,
) -> ContractResult<()> {
ensure_eq!(token_id, &TOKEN_ID_WCCD, ContractError::InvalidTokenId);
if amount == 0u64.into() {
return Ok(());
}
{
let mut from_state =
self.token.get_mut(from).ok_or(ContractError::InsufficientFunds)?;
ensure!(from_state.balance >= amount, ContractError::InsufficientFunds);
from_state.balance -= amount;
}
let mut to_state = self.token.entry(*to).or_insert_with(|| AddressState {
balance: 0u64.into(),
operators: state_builder.new_set(),
});
to_state.balance += amount;
Ok(())
}
/// Update the state adding a new operator for a given token id and address.
/// Succeeds even if the `operator` is already an operator for this
/// `token_id` and `address`.
fn add_operator(
&mut self,
owner: &Address,
operator: &Address,
state_builder: &mut StateBuilder,
) {
let mut owner_state = self.token.entry(*owner).or_insert_with(|| AddressState {
balance: 0u64.into(),
operators: state_builder.new_set(),
});
owner_state.operators.insert(*operator);
}
/// Update the state removing an operator for a given token id and address.
/// Succeeds even if the `operator` is not an operator for this `token_id`
/// and `address`.
fn remove_operator(&mut self, owner: &Address, operator: &Address) {
self.token.entry(*owner).and_modify(|address_state| {
address_state.operators.remove(operator);
});
}
/// Mint an amount of wCCD tokens.
/// Results in an error if the token id does not exist in the state.
fn mint(
&mut self,
token_id: &ContractTokenId,
amount: ContractTokenAmount,
owner: &Address,
state_builder: &mut StateBuilder,
) -> ContractResult<()> {
ensure_eq!(token_id, &TOKEN_ID_WCCD, ContractError::InvalidTokenId);
let mut owner_state = self.token.entry(*owner).or_insert_with(|| AddressState {
balance: 0u64.into(),
operators: state_builder.new_set(),
});
owner_state.balance += amount;
Ok(())
}
/// Burn an amount of wCCD tokens.
/// Results in an error if the token id does not exist in the state or if
/// the owner address has insufficient tokens to do the burn.
fn burn(
&mut self,
token_id: &ContractTokenId,
amount: ContractTokenAmount,
owner: &Address,
) -> ContractResult<()> {
ensure_eq!(token_id, &TOKEN_ID_WCCD, ContractError::InvalidTokenId);
if amount == 0u64.into() {
return Ok(());
}
let mut from_state = self.token.get_mut(owner).ok_or(ContractError::InsufficientFunds)?;
ensure!(from_state.balance >= amount, ContractError::InsufficientFunds);
from_state.balance -= amount;
Ok(())
}
/// Check if state contains any implementors for a given standard.
fn have_implementors(&self, std_id: &StandardIdentifierOwned) -> SupportResult {
if let Some(addresses) = self.implementors.get(std_id) {
SupportResult::SupportBy(addresses.to_vec())
} else {
SupportResult::NoSupport
}
}
/// Set implementors for a given standard.
fn set_implementors(
&mut self,
std_id: StandardIdentifierOwned,
implementors: Vec<ContractAddress>,
) {
let _ = self.implementors.insert(std_id, implementors);
}
}
// Contract functions
/// Initialize contract instance with no initial tokens.
/// Logs a `Mint` event for the single token id with no amounts.
/// Logs a `TokenMetadata` event with the metadata url and hash.
/// Logs a `NewAdmin` event with the invoker as admin.
#[init(
contract = "cis2_wCCD",
enable_logger,
parameter = "SetMetadataUrlParams",
event = "WccdEvent"
)]
fn contract_init(
ctx: &InitContext,
state_builder: &mut StateBuilder,
logger: &mut impl HasLogger,
) -> InitResult<State> {
// Parse the parameter.
let params: SetMetadataUrlParams = ctx.parameter_cursor().get()?;
// Get the instantiator of this contract instance to be used as the initial
// admin.
let invoker = Address::Account(ctx.init_origin());
// Create the metadata_url
let metadata_url = MetadataUrl {
url: params.url.clone(),
hash: params.hash,
};
// Construct the initial contract state.
let state = State::new(state_builder, invoker, metadata_url.clone());
// Log event for the newly minted token.
logger.log(&WccdEvent::Cis2Event(Cis2Event::Mint(MintEvent {
token_id: TOKEN_ID_WCCD,
amount: ContractTokenAmount::from(0u64),
owner: invoker,
})))?;
// Log event for where to find metadata for the token
logger.log(&WccdEvent::Cis2Event(Cis2Event::TokenMetadata::<_, ContractTokenAmount>(
TokenMetadataEvent {
token_id: TOKEN_ID_WCCD,
metadata_url,
},
)))?;
// Log event for the new admin.
logger.log(&WccdEvent::NewAdmin {
new_admin: NewAdminEvent {
new_admin: invoker,
},
})?;
Ok(state)
}
/// Wrap an amount of CCD into wCCD tokens and transfer the tokens if the sender
/// is not the receiver.
#[receive(
contract = "cis2_wCCD",
name = "wrap",
parameter = "WrapParams",
error = "ContractError",
enable_logger,
mutable,
payable
)]
fn contract_wrap(
ctx: &ReceiveContext,
host: &mut Host<State>,
amount: Amount,
logger: &mut impl HasLogger,
) -> ContractResult<()> {
// Check that contract is not paused.
ensure!(!host.state().paused, ContractError::Custom(CustomContractError::ContractPaused));
// Parse the parameter.
let params: WrapParams = ctx.parameter_cursor().get()?;
// Get the sender who invoked this contract function.
let sender = ctx.sender();
let receive_address = params.to.address();
let (state, state_builder) = host.state_and_builder();
// Update the state.
state.mint(&TOKEN_ID_WCCD, amount.micro_ccd.into(), &receive_address, state_builder)?;
// Log the newly minted tokens.
logger.log(&WccdEvent::Cis2Event(Cis2Event::Mint(MintEvent {
token_id: TOKEN_ID_WCCD,
amount: ContractTokenAmount::from(amount.micro_ccd),
owner: sender,
})))?;
// Only logs a transfer event if the receiver is not the sender.
// Only executes the `OnReceivingCis2` hook if the receiver is not the sender
// and the receiver is a contract.
if sender != receive_address {
logger.log(&WccdEvent::Cis2Event(Cis2Event::Transfer(TransferEvent {
token_id: TOKEN_ID_WCCD,
amount: ContractTokenAmount::from(amount.micro_ccd),
from: sender,
to: receive_address,
})))?;
// If the receiver is a contract: invoke the receive hook function.
if let Receiver::Contract(address, function) = params.to {
let parameter = OnReceivingCis2Params {
token_id: TOKEN_ID_WCCD,
amount: ContractTokenAmount::from(amount.micro_ccd),
from: sender,
data: params.data,
};
host.invoke_contract(
&address,
¶meter,
function.as_entrypoint_name(),
Amount::zero(),
)?;
}
}
Ok(())
}
/// Unwrap an amount of wCCD tokens into CCD
#[receive(
contract = "cis2_wCCD",
name = "unwrap",
parameter = "UnwrapParams",
error = "ContractError",
enable_logger,
mutable
)]
fn contract_unwrap(
ctx: &ReceiveContext,
host: &mut Host<State>,
logger: &mut impl HasLogger,
) -> ContractResult<()> {
// Check that contract is not paused.
ensure!(!host.state().paused, ContractError::Custom(CustomContractError::ContractPaused));
// Parse the parameter.
let params: UnwrapParams = ctx.parameter_cursor().get()?;
// Get the sender who invoked this contract function.
let sender = ctx.sender();
let state = host.state_mut();
// Authenticate the sender
ensure!(
sender == params.owner || state.is_operator(&sender, ¶ms.owner),
ContractError::Unauthorized
);
// Update the state.
state.burn(&TOKEN_ID_WCCD, params.amount, ¶ms.owner)?;
// Log the burning of tokens.
logger.log(&WccdEvent::Cis2Event(Cis2Event::Burn(BurnEvent {
token_id: TOKEN_ID_WCCD,
amount: params.amount,
owner: params.owner,
})))?;
let unwrapped_amount = Amount::from_micro_ccd(params.amount.into());
// Transfer the CCD to the receiver
match params.receiver {
Receiver::Account(address) => host.invoke_transfer(&address, unwrapped_amount)?,
Receiver::Contract(address, function) => {
host.invoke_contract(
&address,
¶ms.data,
function.as_entrypoint_name(),
unwrapped_amount,
)?;
}
}
Ok(())
}
/// Transfer the admin address to a new admin address.
///
/// It rejects if:
/// - Sender is not the current admin of the contract instance.
/// - It fails to parse the parameter.
#[receive(
contract = "cis2_wCCD",
name = "updateAdmin",
parameter = "Address",
error = "ContractError",
enable_logger,
mutable
)]
fn contract_update_admin(
ctx: &ReceiveContext,
host: &mut Host<State>,
logger: &mut impl HasLogger,
) -> ContractResult<()> {
// Check that only the current admin is authorized to update the admin address.
ensure_eq!(ctx.sender(), host.state().admin, ContractError::Unauthorized);
// Parse the parameter.
let new_admin = ctx.parameter_cursor().get()?;
// Update the admin variable.
host.state_mut().admin = new_admin;
// Log a new admin event.
logger.log(&WccdEvent::NewAdmin {
new_admin: NewAdminEvent {
new_admin,
},
})?;
Ok(())
}
/// Pause/Unpause this smart contract instance by the admin. All non-admin
/// state-mutative functions (wrap, unwrap, transfer, updateOperator) cannot be
/// executed when the contract is paused.
///
/// It rejects if:
/// - Sender is not the admin of the contract instance.
/// - It fails to parse the parameter.
#[receive(
contract = "cis2_wCCD",
name = "setPaused",
parameter = "SetPausedParams",
error = "ContractError",
mutable
)]
fn contract_set_paused(ctx: &ReceiveContext, host: &mut Host<State>) -> ContractResult<()> {
// Check that only the admin is authorized to pause/unpause the contract.
ensure_eq!(ctx.sender(), host.state().admin, ContractError::Unauthorized);
// Parse the parameter.
let params: SetPausedParams = ctx.parameter_cursor().get()?;
// Update the paused variable.
host.state_mut().paused = params.paused;
Ok(())
}
/// Update the metadata URL in this smart contract instance.
///
/// It rejects if:
/// - Sender is not the admin of the contract instance.
/// - It fails to parse the parameter.
#[receive(
contract = "cis2_wCCD",
name = "setMetadataUrl",
parameter = "SetMetadataUrlParams",
error = "ContractError",
enable_logger,
mutable
)]
fn contract_state_set_metadata_url(
ctx: &ReceiveContext,
host: &mut Host<State>,
logger: &mut impl HasLogger,
) -> ContractResult<()> {
// Check that only the admin is authorized to update the URL.
ensure_eq!(ctx.sender(), host.state().admin, ContractError::Unauthorized);
// Parse the parameter.
let params: SetMetadataUrlParams = ctx.parameter_cursor().get()?;
// Create the metadata_url
let metadata_url = MetadataUrl {
url: params.url.clone(),
hash: params.hash,
};
// Update the hash variable.
*host.state_mut().metadata_url = metadata_url.clone();
// Log an event including the new metadata for this token.
logger.log(&WccdEvent::Cis2Event(Cis2Event::TokenMetadata::<_, ContractTokenAmount>(
TokenMetadataEvent {
token_id: TOKEN_ID_WCCD,
metadata_url,
},
)))?;
Ok(())
}
// Contract functions required by the CIS-2 standard
type TransferParameter = TransferParams<ContractTokenId, ContractTokenAmount>;
/// Execute a list of token transfers, in the order of the list.
///
/// Logs a `Transfer` event and invokes a receive hook function for every
/// transfer in the list.
///
/// It rejects if:
/// - It fails to parse the parameter.
/// - Any of the transfers fail to be executed, which could be if:
/// - The `token_id` does not exist.
/// - The sender is not the owner of the token, or an operator for this
/// specific `token_id` and `from` address.
/// - The token is not owned by the `from`.
/// - Fails to log event.
/// - Any of the receive hook function calls rejects.
#[receive(
contract = "cis2_wCCD",
name = "transfer",
parameter = "TransferParameter",
error = "ContractError",
enable_logger,
mutable
)]
fn contract_transfer(
ctx: &ReceiveContext,
host: &mut Host<State>,
logger: &mut impl HasLogger,
) -> ContractResult<()> {
// Check that contract is not paused.
ensure!(!host.state().paused, ContractError::Custom(CustomContractError::ContractPaused));
// Parse the parameter.
let TransferParams(transfers): TransferParameter = ctx.parameter_cursor().get()?;
// Get the sender who invoked this contract function.
let sender = ctx.sender();
for Transfer {
token_id,
amount,
from,
to,
data,
} in transfers
{
let (state, builder) = host.state_and_builder();
// Authenticate the sender for this transfer
ensure!(from == sender || state.is_operator(&sender, &from), ContractError::Unauthorized);
let to_address = to.address();
// Update the contract state
state.transfer(&token_id, amount, &from, &to_address, builder)?;
// Log transfer event
logger.log(&WccdEvent::Cis2Event(Cis2Event::Transfer(TransferEvent {
token_id,
amount,
from,
to: to_address,
})))?;
// If the receiver is a contract: invoke the receive hook function.
if let Receiver::Contract(address, function) = to {
let parameter = OnReceivingCis2Params {
token_id,
amount,
from,
data,
};
host.invoke_contract(
&address,
¶meter,
function.as_entrypoint_name(),
Amount::zero(),
)?;
}
}
Ok(())
}
/// Enable or disable addresses as operators of the sender address.
/// Logs an `UpdateOperator` event.
///
/// It rejects if:
/// - It fails to parse the parameter.
/// - Fails to log event.
#[receive(
contract = "cis2_wCCD",
name = "updateOperator",
parameter = "UpdateOperatorParams",
error = "ContractError",
enable_logger,
mutable
)]
fn contract_update_operator(
ctx: &ReceiveContext,
host: &mut Host<State>,
logger: &mut impl HasLogger,
) -> ContractResult<()> {
// Check that contract is not paused.
ensure!(!host.state().paused, ContractError::Custom(CustomContractError::ContractPaused));
// Parse the parameter.
let UpdateOperatorParams(params) = ctx.parameter_cursor().get()?;
// Get the sender who invoked this contract function.
let sender = ctx.sender();
let (state, state_builder) = host.state_and_builder();
for param in params {
// Update the operator in the state.
match param.update {
OperatorUpdate::Add => state.add_operator(&sender, ¶m.operator, state_builder),
OperatorUpdate::Remove => state.remove_operator(&sender, ¶m.operator),
}
// Log the appropriate event
logger.log(&WccdEvent::Cis2Event(
Cis2Event::<ContractTokenId, ContractTokenAmount>::UpdateOperator(
UpdateOperatorEvent {
owner: sender,
operator: param.operator,
update: param.update,
},
),
))?;
}
Ok(())
}
/// Parameter type for the CIS-2 function `balanceOf` specialized to the subset
/// of TokenIDs used by this contract.
pub type ContractBalanceOfQueryParams = BalanceOfQueryParams<ContractTokenId>;
pub type ContractBalanceOfQueryResponse = BalanceOfQueryResponse<ContractTokenAmount>;
/// Get the balance of given token IDs and addresses.
///
/// It rejects if:
/// - It fails to parse the parameter.
/// - Any of the queried `token_id` does not exist.
#[receive(
contract = "cis2_wCCD",
name = "balanceOf",
parameter = "ContractBalanceOfQueryParams",
return_value = "ContractBalanceOfQueryResponse",
error = "ContractError"
)]
fn contract_balance_of(
ctx: &ReceiveContext,
host: &Host<State>,
) -> ContractResult<ContractBalanceOfQueryResponse> {
// Parse the parameter.
let params: ContractBalanceOfQueryParams = ctx.parameter_cursor().get()?;
// Build the response.
let mut response = Vec::with_capacity(params.queries.len());
for query in params.queries {
// Query the state for balance.
let amount = host.state().balance(&query.token_id, &query.address)?;
response.push(amount);
}
let result = ContractBalanceOfQueryResponse::from(response);
Ok(result)
}
/// Takes a list of queries. Each query contains an owner address and some
/// address that will be checked if it is an operator to the owner address.
///
/// It rejects if:
/// - It fails to parse the parameter.
#[receive(
contract = "cis2_wCCD",
name = "operatorOf",
parameter = "OperatorOfQueryParams",
return_value = "OperatorOfQueryResponse",
error = "ContractError"
)]
fn contract_operator_of(
ctx: &ReceiveContext,
host: &Host<State>,
) -> ContractResult<OperatorOfQueryResponse> {
// Parse the parameter.
let params: OperatorOfQueryParams = ctx.parameter_cursor().get()?;
// Build the response.
let mut response = Vec::with_capacity(params.queries.len());
for query in params.queries {
// Query the state if the `address` being an `operator` of `owner`.
let is_operator = host.state().is_operator(&query.address, &query.owner);
response.push(is_operator);
}
let result = OperatorOfQueryResponse::from(response);
Ok(result)
}
/// Parameter type for the CIS-2 function `tokenMetadata` specialized to the
/// subset of TokenIDs used by this contract.
// This type is pub to silence the dead_code warning, as this type is only used
// for when generating the schema.
pub type ContractTokenMetadataQueryParams = TokenMetadataQueryParams<ContractTokenId>;
/// Get the token metadata URLs and checksums given a list of token IDs.
///
/// It rejects if:
/// - It fails to parse the parameter.
/// - Any of the queried `token_id` does not exist.
#[receive(
contract = "cis2_wCCD",
name = "tokenMetadata",
parameter = "ContractTokenMetadataQueryParams",
return_value = "TokenMetadataQueryResponse",
error = "ContractError"
)]
fn contract_token_metadata(
ctx: &ReceiveContext,
host: &Host<State>,
) -> ContractResult<TokenMetadataQueryResponse> {
// Parse the parameter.
let params: ContractTokenMetadataQueryParams = ctx.parameter_cursor().get()?;
// Build the response.
let mut response = Vec::with_capacity(params.queries.len());
for token_id in params.queries {
// Check the token exists.
ensure_eq!(token_id, TOKEN_ID_WCCD, ContractError::InvalidTokenId);
response.push(host.state().metadata_url.clone());
}
let result = TokenMetadataQueryResponse::from(response);
Ok(result)
}
/// Function to view the basic state of the contract.
#[receive(
contract = "cis2_wCCD",
name = "view",
return_value = "ReturnBasicState",
error = "ContractError"
)]
fn contract_view(_ctx: &ReceiveContext, host: &Host<State>) -> ContractResult<ReturnBasicState> {
let state = ReturnBasicState {
admin: host.state().admin,
paused: host.state().paused,
metadata_url: host.state().metadata_url.clone(),
};
Ok(state)
}
/// Get the supported standards or addresses for a implementation given list of
/// standard identifiers.
///
/// It rejects if:
/// - It fails to parse the parameter.
#[receive(
contract = "cis2_wCCD",
name = "supports",
parameter = "SupportsQueryParams",
return_value = "SupportsQueryResponse",
error = "ContractError"
)]
fn contract_supports(
ctx: &ReceiveContext,
host: &Host<State>,
) -> ContractResult<SupportsQueryResponse> {
// Parse the parameter.
let params: SupportsQueryParams = ctx.parameter_cursor().get()?;
// Build the response.
let mut response = Vec::with_capacity(params.queries.len());
for std_id in params.queries {
if SUPPORTS_STANDARDS.contains(&std_id.as_standard_identifier()) {
response.push(SupportResult::Support);
} else {
response.push(host.state().have_implementors(&std_id));
}
}
let result = SupportsQueryResponse::from(response);
Ok(result)
}
/// Set the addresses for an implementation given a standard identifier and a
/// list of contract addresses.
///
/// It rejects if:
/// - Sender is not the admin of the contract instance.
/// - It fails to parse the parameter.
#[receive(
contract = "cis2_wCCD",
name = "setImplementors",
parameter = "SetImplementorsParams",
error = "ContractError",
mutable