Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add events based on casper-event-standard crate. #1

Open
wants to merge 4 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@ test: setup-test
cd tests && cargo test

clippy:
cd contract && cargo clippy --all-targets -- -D warnings
cd contract && cargo clippy --target wasm32-unknown-unknown --bins -- -D warnings
cd contract && cargo clippy --no-default-features --lib -- -D warnings
cd tests && cargo clippy --all-targets -- -D warnings

check-lint: clippy
Expand Down
32 changes: 32 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,14 @@ This modality provides three options:

The `MetadataMutability` option of `Mutable` cannot be used in conjunction with `NFTIdentifierMode` modality of `Hash`.

#### EventsMode

The `EventsMode` modality allows the deployers of the contract to decide on schemas for recording events during the operation of the contract where changes to the tokens happen.

0. `NoEvents`: No events will be recorded during the operation, this is the default mode.
1. `CEP47`: The event schema from the CEP47 contract has been implemented as a possibility.
2. `CES` : Events will be recorded during the operation of the contract using an event schema in compliance with the Casper Event Schema. Refer to section [Casper Event Standard](#casper-event-standard) for more information.

### Usage

#### Installing the contract.
Expand Down Expand Up @@ -651,6 +659,30 @@ If the `NFTIdentifierMode` is set to `Ordinal`, this number corresponds directly

If it is set to `Hash`, you will need to reference the `HASH_BY_INDEX` dictionary to determine the mapping of token numbers to token hashes.

## Casper Event Standard

When the `CES` events mode is enabled during contract installation, the operations that make changes on the tokens are recorded in the `__events` dictionary. Such event entries can be observed via a node's Server Side Events stream or querying the dictionary at any time using the RPC interface.

The emitted events are encoded according to the [Casper Event Standard](https://github.com/make-software/casper-event-standard), and the schema can be known by an observer reading the `__events_schema` contract named key.

For this CEP-78 reference implementation in particular, the events schema is the following:

| Event name | Included values and type |
|-----------------|----------------------------------------------------------------------|
| Mint | recipient (Key), token_id (Any), data (String) |
| Transfer | owner (Key), operator (Option<Key>), recipient (Key), token_id (Any) |
| Burn | owner (Key), token_id (Any) |
| Approval | owner (Key), operator (Option<Key>), token_id (Any) |
| ApprovalForAll | owner (Key), operator (Option<Key>), token_ids (List<Any>) |
| MetadataUpdated | token_id (Any), data (String) |
| Migration | - |
| VariablesSet | - |

Token identifiers are stored under the `CLType` `Any` and the encoding depends on `NFTIdentifierMode`:.

* `NFTIdentifierMode::Ordinal`: the `token id` is encoded as a byte `0x00` followed by a `u64` number.
* `NFTIdentifierMode::Hash`: the `token_id` is encoded as a byte `0x01` followed by a `String`.

## Error Codes

|Code |Error |
Expand Down
11 changes: 9 additions & 2 deletions contract/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@ version = "1.1.1"
edition = "2018"

[dependencies]
casper-contract = {version = "1.4.3", features = ["test-support"]}
casper-contract = { version = "1.4.3", features = ["test-support"], optional = true }
casper-types = "1.4.5"
serde = { version = "1", features = ["derive", "alloc"], default-features = false }
base16 = { version = "0.2", default-features = false, features = ["alloc"] }
casper-serde-json-wasm = { git = "https://github.com/darthsiroftardis/casper-serde-json-wasm", branch = "casper-no-std"}
casper-serde-json-wasm = { git = "https://github.com/darthsiroftardis/casper-serde-json-wasm", branch = "casper-no-std", optional = true }
casper-event-standard = "0.1.1"

[[bin]]
name = "contract"
Expand All @@ -21,3 +22,9 @@ test = false
codegen-units = 1
lto = true

[features]
default = ["contract-support"]
contract-support = [
"dep:casper-contract",
"dep:casper-serde-json-wasm"
]
122 changes: 122 additions & 0 deletions contract/src/events.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
use alloc::{string::String, vec::Vec};
use casper_event_standard::Event;
use casper_types::Key;

use crate::modalities::TokenIdentifier;

#[derive(Event, Debug, PartialEq, Eq)]
pub struct Mint {
recipient: Key,
token_id: TokenIdentifier,
data: String,
}

impl Mint {
pub fn new(recipient: Key, token_id: TokenIdentifier, data: String) -> Self {
Self {
recipient,
token_id,
data,
}
}
}

#[derive(Event, Debug, PartialEq, Eq)]
pub struct Burn {
owner: Key,
token_id: TokenIdentifier,
}

impl Burn {
pub fn new(owner: Key, token_id: TokenIdentifier) -> Self {
Self { owner, token_id }
}
}

#[derive(Event, Debug, PartialEq, Eq)]
pub struct Approval {
owner: Key,
operator: Key,
token_id: TokenIdentifier,
}

impl Approval {
pub fn new(owner: Key, operator: Key, token_id: TokenIdentifier) -> Self {
Self {
owner,
operator,
token_id,
}
}
}

#[derive(Event, Debug, PartialEq, Eq)]
pub struct ApprovalForAll {
owner: Key,
operator: Option<Key>,
token_ids: Vec<TokenIdentifier>,
}

impl ApprovalForAll {
pub fn new(owner: Key, operator: Option<Key>, token_ids: Vec<TokenIdentifier>) -> Self {
Self {
owner,
operator,
token_ids,
}
}
}

#[derive(Event, Debug, PartialEq, Eq)]
pub struct Transfer {
owner: Key,
operator: Option<Key>,
recipient: Key,
token_id: TokenIdentifier,
}

impl Transfer {
pub fn new(
owner: Key,
operator: Option<Key>,
recipient: Key,
token_id: TokenIdentifier,
) -> Self {
Self {
owner,
operator,
recipient,
token_id,
}
}
}

#[derive(Event, Debug, PartialEq, Eq)]
pub struct MetadataUpdated {
token_id: TokenIdentifier,
data: String,
}

impl MetadataUpdated {
pub fn new(token_id: TokenIdentifier, data: String) -> Self {
Self { token_id, data }
}
}

#[derive(Event, Debug, PartialEq, Eq, Default)]
pub struct VariablesSet {}

impl VariablesSet {
pub fn new() -> Self {
Self {}
}
}

#[derive(Event, Debug, PartialEq, Eq, Default)]
pub struct Migration {}

impl Migration {
pub fn new() -> Self {
Self {}
}
}
7 changes: 7 additions & 0 deletions contract/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#![no_std]

extern crate alloc;

pub mod error;
pub mod events;
pub mod modalities;
60 changes: 55 additions & 5 deletions contract/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ compile_error!("target arch should be wasm32: compile with '--target wasm32-unkn

mod constants;
mod error;
mod events;
mod metadata;
mod modalities;
mod utils;
Expand All @@ -20,6 +21,9 @@ use alloc::{
vec::Vec,
};
use core::convert::TryInto;
use events::{
Approval, ApprovalForAll, Burn, MetadataUpdated, Migration, Mint, Transfer, VariablesSet,
};

use casper_types::{
contracts::NamedKeys, runtime_args, CLType, CLValue, ContractHash, ContractPackageHash,
Expand Down Expand Up @@ -348,6 +352,9 @@ pub extern "C" fn init() {
runtime::put_key(PAGE_LIMIT, storage::new_uref(page_table_width).into());
}
runtime::put_key(MIGRATION_FLAG, storage::new_uref(true).into());

// Initialize events structures.
utils::init_events();
}

// set_variables allows the user to set any variable or any combination of variables simultaneously.
Expand Down Expand Up @@ -402,6 +409,9 @@ pub extern "C" fn set_variables() {
WhitelistMode::Locked => runtime::revert(NFTCoreError::InvalidWhitelistMode),
}
}

// Emit VariablesSet event.
casper_event_standard::emit(VariablesSet::new());
}

// Mints a new token. Minting will fail if allow_minting is set to false.
Expand Down Expand Up @@ -539,7 +549,7 @@ pub extern "C" fn mint() {
utils::upsert_dictionary_value_from_key(
&metadata::get_metadata_dictionary_name(&metadata_kind),
&token_identifier.get_dictionary_item_key(),
metadata,
metadata.clone(),
);

let owned_tokens_item_key = utils::get_owned_tokens_dictionary_item_key(token_owner_key);
Expand Down Expand Up @@ -569,6 +579,13 @@ pub extern "C" fn mint() {
);
storage::write(number_of_minted_tokens_uref, minted_tokens_count + 1u64);

// Emit Mint event.
casper_event_standard::emit(Mint::new(
token_owner_key,
token_identifier.clone(),
metadata,
));

if let OwnerReverseLookupMode::Complete = utils::get_reporting_mode() {
if (NFTIdentifierMode::Hash == identifier_mode)
&& utils::should_migrate_token_hashes(token_owner_key)
Expand Down Expand Up @@ -696,6 +713,9 @@ pub extern "C" fn burn() {
};

utils::upsert_dictionary_value_from_key(TOKEN_COUNTS, &owned_tokens_item_key, updated_balance);

// Emit Burn event.
casper_event_standard::emit(Burn::new(token_owner, token_identifier));
}

// approve marks a token as approved for transfer by an account
Expand Down Expand Up @@ -778,6 +798,9 @@ pub extern "C" fn approve() {
&token_identifier_dictionary_key,
Some(operator),
);

// Emit Approval event.
casper_event_standard::emit(Approval::new(token_owner_key, operator, token_identifier));
}

// This is an extremely gas intensive operation. DO NOT invoke this
Expand All @@ -801,6 +824,8 @@ pub extern "C" fn set_approval_for_all() {
)
.unwrap_or_revert();

let token_owner: Key = utils::get_verified_caller().unwrap_or_revert();

let operator = utils::get_named_arg_with_user_errors::<Key>(
ARG_OPERATOR,
NFTCoreError::MissingOperator,
Expand All @@ -820,14 +845,17 @@ pub extern "C" fn set_approval_for_all() {
};

// Depending on approve_all we either approve all or disapprove all.
for token_id in owned_tokens {
let operator = if approve_all { Some(operator) } else { None };
for token_id in &owned_tokens {
// We assume a burned token cannot be approved
if utils::is_token_burned(&token_id) {
if utils::is_token_burned(token_id) {
runtime::revert(NFTCoreError::PreviouslyBurntToken)
}
let operator = if approve_all { Some(operator) } else { None };
storage::dictionary_put(operator_uref, &token_id.get_dictionary_item_key(), operator);
}

// Emit ApprovalForAll event.
casper_event_standard::emit(ApprovalForAll::new(token_owner, operator, owned_tokens));
}

// Transfers token from token_owner to specified account. Transfer will go through if caller is
Expand Down Expand Up @@ -976,6 +1004,19 @@ pub extern "C" fn transfer() {
Option::<Key>::None,
);

// Emit Transfer event.
let operator = if caller == token_owner_key {
None
} else {
Some(caller)
};
casper_event_standard::emit(Transfer::new(
token_owner_key,
operator,
target_owner_key,
token_identifier.clone(),
));

if let OwnerReverseLookupMode::Complete = utils::get_reporting_mode() {
// Update to_account owned_tokens. Revert if owned_tokens list is not found
let token_number = utils::get_token_index(&token_identifier);
Expand Down Expand Up @@ -1252,8 +1293,11 @@ pub extern "C" fn set_token_metadata() {
utils::upsert_dictionary_value_from_key(
&metadata::get_metadata_dictionary_name(&metadata_kind),
&token_identifier.get_dictionary_item_key(),
updated_metadata,
updated_metadata.clone(),
);

// Emit MetadataUpdate event.
casper_event_standard::emit(MetadataUpdated::new(token_identifier, updated_metadata));
}

#[no_mangle]
Expand Down Expand Up @@ -1340,6 +1384,12 @@ pub extern "C" fn migrate() {
);
}
}

// Initialize events structures.
utils::init_events();

// Emit Migration event.
casper_event_standard::emit(Migration::new());
}

#[no_mangle]
Expand Down
Loading