diff --git a/EREBRUS_NFT/Move.toml b/EREBRUS_NFT/Move.toml index 4fe8092..3ba7edb 100644 --- a/EREBRUS_NFT/Move.toml +++ b/EREBRUS_NFT/Move.toml @@ -1,7 +1,6 @@ [package] -name = "init" +name = "erberus" version = "1.0.0" -authors = [] [dependencies.AptosFramework] git = 'https://github.com/aptos-labs/aptos-core.git' @@ -14,7 +13,7 @@ rev = 'main' subdir = 'aptos-move/framework/aptos-token-objects' [addresses] -admin = "" +admin = "0x6299daec2e0c0e0f8c0f824bd91290286af7d709375b4c9593858d49a2227a40" VSEED = "" wv1 = "" std = "0x1" diff --git a/EREBRUS_SEOUL_NFT/Move.toml b/EREBRUS_SEOUL_NFT/Move.toml new file mode 100644 index 0000000..2b1c3f4 --- /dev/null +++ b/EREBRUS_SEOUL_NFT/Move.toml @@ -0,0 +1,20 @@ +[package] +name = "erberus_seoul" +version = "1.0.0" + +[dependencies.AptosFramework] +git = 'https://github.com/aptos-labs/aptos-core.git' +rev = 'main' +subdir = 'aptos-move/framework/aptos-framework' + +[dependencies.AptosTokenObjects] +git = 'https://github.com/aptos-labs/aptos-core.git' +rev = 'main' +subdir = 'aptos-move/framework/aptos-token-objects' + +[addresses] +admin = "" +VSEED = "" +wv1 = "" +std = "0x1" +aptos_token_objects ="0x4" \ No newline at end of file diff --git a/EREBRUS_SEOUL_NFT/README.md b/EREBRUS_SEOUL_NFT/README.md new file mode 100644 index 0000000..cd36f26 --- /dev/null +++ b/EREBRUS_SEOUL_NFT/README.md @@ -0,0 +1,58 @@ +# EREBRUS +Smart Contract for the EREBRUS NFT with supply of 111. + +Where decentralization meets VPN for ultimate internet security. +Anonymous Virtual Private Network for accessing internet in stealth mode bypassing filewalls and filters + +To be deployed to testnet under contract address: + +Entry Functions: +- user_mint +- delegate_mint + +View Functions: +- total_minted_NFTs: Total NFTs minted so far +- owner_of: Accepts tokenId to tell who is its owner + +Events: +- NftMintedEvent + + +## Usage + +To use this smart contract, follow these steps: + +1. Compile the Move code: + ``` + aptos init + ``` +3. Compile the Move code: + ``` + aptos move compile + ``` + +2. Publish the compiled package: + ``` + aptos move publish + ``` + +### Customizing the EREBRUS Token + +To modify the EREBRUS token properties, you need to edit the `erebrus.move` file. Look for the following constants and update them as needed: + +- `COLLECTION_NAME` +- `COLLECTION_DESCRIPTION` +- `COLLECTION_URI` +- `TOKEN_DESCRIPTION` +- `TOKEN_URI` + +Make sure to recompile and republish the contract after making any changes. + + + + +## Revisions: +### 1.0.1: +### 1.0.2: +### 1.0.3: +- Deployed on mainnet under contract address: \ No newline at end of file diff --git a/EREBRUS_SEOUL_NFT/sources/erebrus_seoul.move b/EREBRUS_SEOUL_NFT/sources/erebrus_seoul.move new file mode 100644 index 0000000..6b3aeb5 --- /dev/null +++ b/EREBRUS_SEOUL_NFT/sources/erebrus_seoul.move @@ -0,0 +1,553 @@ +module admin::erebrus_seoul{ + //============================================================================================== + // Dependencies + //============================================================================================== + use std::object; + use std::signer; + use aptos_token_objects::token; + use aptos_framework::event::{Self, EventHandle}; + use aptos_framework::account::{Self, SignerCapability}; + use std::string; + use aptos_token_objects::collection; + use aptos_framework::timestamp; + use aptos_framework::option; + use std::string_utils; + use std::vector; + use aptos_token_objects::royalty; + use std::bcs; + + + + //============================================================================================== + // Errors + //============================================================================================== + + const ERROR_SIGNER_NOT_ADMIN: u64 = 0; + const ERROR_SUPPLY_EXCEEDED: u64 = 1; + const ERROR_SIGNER_NOT_OPERATOR: u64 = 2; + const ERROR_USER_MINT_EXCEEDED: u64 = 3; + const ERROR_OTHERS: u64 = 4; + const ERROR_INSUFFICIENT_BALANCE: u64 = 5; + const ERROR_ALREADY_OPERATOR: u64 = 6; + + //============================================================================================== + // Constants + //============================================================================================== + + // Contract Version + const VERSION: u64 = 1; + // Supply limit + const SUPPLY: u64 = 55; + + // NFT collection information + const COLLECTION_NAME: vector = b"EREBRUS"; + const COLLECTION_DESCRIPTION: vector = b"Erebrus, by NetSepio. 111 innovative utility NFT that offers you access to a blockchain-backed, distributed Anonymous VPN."; + const COLLECTION_URI: vector = b"ipfs://bafybeidvpe6xdwribm5qjywrvucqys34rte26uukp6hqq3lolefd7ddjoq/erebrus_collection_uri.gif"; + + // Token information + const TOKEN_DESCRIPTION: vector = b"Erebrus NFT"; + const TOKEN_URI: vector = b"ipfs://bafybeieocwztsh2aqhb4vuwlpduw2blamfgsegthyjb3kbbwatqnj6t4hy/"; + + //============================================================================================== + // Module Structs + //============================================================================================== + + struct ErebrusToken has key { + // Used for editing the token data + mutator_ref: token::MutatorRef, + // Used for burning the token + burn_ref: token::BurnRef, + // Used for transferring the token + transfer_ref: object::TransferRef + } + + struct State has key { + // signer cap of the module's resource account + signer_cap: SignerCapability, + // NFT count + minted: u64, + operator: vector
, + //minter + minter: vector
, + // minted_nft_obj_add + nft_list: vector
, + // Events + nft_minted_events: EventHandle + } + //============================================================================================== + // Event structs + //============================================================================================== + + struct NftMintedEvent has store, drop { + // minter + user: address, + // nft # + nft_no: u64, + // nft object address + obj_add: address, + // timestamp + timestamp: u64 + } + + //============================================================================================== + // Functions + //============================================================================================== + + /* + Initializes the module by creating a resource account, registering with AptosCoin, creating + the token collectiions, and setting up the State resource. + @param account - signer representing the module publisher + */ + fun init_module(admin: &signer) { + assert_admin(signer::address_of(admin)); + // Seed for resource account creation + let seed = bcs::to_bytes(&@VSEED); + let (resource_signer, resource_cap) = account::create_resource_account(admin, seed); + + let royalty = royalty::create(5,100,@wv1); + + // Create an NFT collection with an unlimied supply and the following aspects: + collection::create_fixed_collection( + &resource_signer, + string::utf8(COLLECTION_DESCRIPTION), + SUPPLY, + string::utf8(COLLECTION_NAME), + option::some(royalty), + string::utf8(COLLECTION_URI) + ); + + // Create the State global resource and move it to the admin account + let state = State{ + signer_cap: resource_cap, + minted: 0, + operator: vector::empty(), + minter: vector::empty(), + nft_list: vector::empty(), + nft_minted_events: account::new_event_handle(&resource_signer) + }; + move_to(admin, state); + } + + public entry fun delegate_mint(operator: &signer, minter: address) acquires State{ + assert_operator(signer::address_of(operator)); + mint_internal(minter); + } + + /* + Grants operator roles + @param admin - admin signer + @param user - user address + */ + public entry fun grant_role( + admin: &signer, + user: address + ) acquires State { + assert_user_does_not_have_role(user); + assert_admin(signer::address_of(admin)); + let state = borrow_global_mut(@admin); + vector::push_back(&mut state.operator, user); + } + + /* + Revoke operator roles + @param admin - admin signer + @param user - user address + */ + public entry fun revoke_role( + admin: &signer, + user: address + ) acquires State { + assert_operator(user); + assert_admin(signer::address_of(admin)); + let state = borrow_global_mut(@admin); + vector::remove_value(&mut state.operator, &user); + } + + //============================================================================================== + // Helper functions + //============================================================================================== + + inline fun mint_internal(user: address){ + let state = borrow_global_mut(@admin); + assert_supply_not_exceeded(state.minted); + + + let current_nft = state.minted + 1; + let res_signer = account::create_signer_with_capability(&state.signer_cap); + + let royalty = royalty::create(5,100,@wv1); + let uri = string::utf8(TOKEN_URI); + string::append(&mut uri, string_utils::format1(&b"{}.json", current_nft)); + // Create a new named token: + let token_const_ref = token::create_named_token( + &res_signer, + string::utf8(COLLECTION_NAME), + string::utf8(TOKEN_DESCRIPTION), + string_utils::format1(&b"Erebrus #{}", current_nft), + option::some(royalty), + uri + ); + + let obj_signer = object::generate_signer(&token_const_ref); + let obj_add = object::address_from_constructor_ref(&token_const_ref); + + // Transfer the token to the user account + object::transfer_raw(&res_signer, obj_add, user); + + // Create the ErebrusToken object and move it to the new token object signer + let new_nft_token = ErebrusToken { + mutator_ref: token::generate_mutator_ref(&token_const_ref), + burn_ref: token::generate_burn_ref(&token_const_ref), + transfer_ref: object::generate_transfer_ref(&token_const_ref), + }; + + move_to(&obj_signer, new_nft_token); + + state.minted = current_nft; + vector::push_back(&mut state.minter, user); + vector::push_back(&mut state.nft_list, obj_add); + + // Emit a new NftMintedEvent + event::emit_event( + &mut state.nft_minted_events, + NftMintedEvent { + user, + nft_no: current_nft, + obj_add, + timestamp: timestamp::now_seconds() + }); + } + + //============================================================================================== + // View functions + //============================================================================================== + + #[view] + public fun total_minted_NFTs(): u64 acquires State { + let state = borrow_global(@admin); + state.minted + } + + #[view] + public fun owner_of(tokenId: u64): address acquires State { + let state = borrow_global(@admin); + object::owner(object::address_to_object(*vector::borrow(&state.nft_list, tokenId-1))) + } + + //============================================================================================== + // Validation functions + //============================================================================================== + + inline fun assert_admin(admin: address) { + assert!(admin == @admin, ERROR_SIGNER_NOT_ADMIN); + } + + inline fun assert_operator(user: address) acquires State { + let state = borrow_global(@admin); + assert!(vector::contains(&state.operator,&user), ERROR_SIGNER_NOT_OPERATOR); + } + + + inline fun assert_supply_not_exceeded(minted: u64) { + assert!(minted <= SUPPLY, ERROR_SUPPLY_EXCEEDED); + } + + inline fun assert_user_does_not_have_role(user: address) acquires State { + let state = borrow_global(@admin); + assert!(!vector::contains(&state.operator,&user), ERROR_ALREADY_OPERATOR); + } + + //============================================================================================== + // Test functions + //============================================================================================== + + #[test(admin = @admin)] + fun test_init_module_success( + admin: &signer + ) acquires State { + let admin_address = signer::address_of(admin); + account::create_account_for_test(admin_address); + + let aptos_framework = account::create_account_for_test(@aptos_framework); + timestamp::set_time_has_started_for_testing(&aptos_framework); + + init_module(admin); + + let seed = bcs::to_bytes(&@VSEED); + let expected_resource_account_address = account::create_resource_address(&admin_address, seed); + assert!(account::exists_at(expected_resource_account_address), 0); + + let state = borrow_global(admin_address); + assert!( + account::get_signer_capability_address(&state.signer_cap) == expected_resource_account_address, + 0 + ); + + let expected_collection_address = collection::create_collection_address( + &expected_resource_account_address, + &string::utf8(COLLECTION_NAME) + ); + let collection_object = object::address_to_object(expected_collection_address); + assert!( + collection::creator(collection_object) == expected_resource_account_address, + 4 + ); + assert!( + collection::name(collection_object) == string::utf8(COLLECTION_NAME), + 4 + ); + assert!( + collection::description(collection_object) == string::utf8(COLLECTION_DESCRIPTION), + 4 + ); + assert!( + collection::uri(collection_object) == string::utf8(COLLECTION_URI), + 4 + ); + + assert!(event::counter(&state.nft_minted_events) == 0, 4); + } + + #[test(admin = @admin, user = @0xA, bank = @wv1)] + fun test_mint_success( + admin: &signer, + user: &signer, + bank: &signer + ) acquires State { + let admin_address = signer::address_of(admin); + let user_address = signer::address_of(user); + let bank_address = signer::address_of(bank); + account::create_account_for_test(admin_address); + account::create_account_for_test(user_address); + account::create_account_for_test(bank_address); + + let aptos_framework = account::create_account_for_test(@aptos_framework); + timestamp::set_time_has_started_for_testing(&aptos_framework); + let (burn_cap, mint_cap) = + aptos_coin::initialize_for_test(&aptos_framework); + coin::register(user); + coin::register(bank); + init_module(admin); + aptos_coin::mint(&aptos_framework, user_address, MINT_PRICE); + + let image_uri = string::utf8(TOKEN_URI); + string::append(&mut image_uri, string_utils::format1(&b"{}.json",1)); + + let seed = bcs::to_bytes(&@VSEED); + let resource_account_address = account::create_resource_address(&@admin, seed); + + user_mint(user); + + let state = borrow_global(admin_address); + + let expected_nft_token_address = token::create_token_address( + &resource_account_address, + &string::utf8(COLLECTION_NAME), + &string_utils::format1(&b"nft#{}", state.minted) + ); + let nft_token_object = object::address_to_object(expected_nft_token_address); + assert!( + object::is_owner(nft_token_object, user_address) == true, + 1 + ); + assert!( + token::creator(nft_token_object) == resource_account_address, + 4 + ); + assert!( + token::name(nft_token_object) == string_utils::format1(&b"nft#{}", state.minted), + 4 + ); + assert!( + token::description(nft_token_object) == string::utf8(TOKEN_DESCRIPTION), + 4 + ); + assert!( + token::uri(nft_token_object) == image_uri, + 4 + ); + assert!( + option::is_some(&token::royalty(nft_token_object)), + 4 + ); + assert!( + vector::contains(&state.nft_list, &expected_nft_token_address), + 4 + ); + + coin::destroy_burn_cap(burn_cap); + coin::destroy_mint_cap(mint_cap); + + assert!(event::counter(&state.nft_minted_events) == 1, 4); + + } + + #[test(admin = @admin, user = @0xA, bank = @wv1)] + #[expected_failure(abort_code = ERROR_SUPPLY_EXCEEDED)] + fun test_mint_failed_supply_exceeded( + admin: &signer, + user: &signer, + bank: &signer + ) acquires State { + let admin_address = signer::address_of(admin); + let user_address = signer::address_of(user); + let bank_address = signer::address_of(bank); + account::create_account_for_test(admin_address); + account::create_account_for_test(user_address); + account::create_account_for_test(bank_address); + + let aptos_framework = account::create_account_for_test(@aptos_framework); + timestamp::set_time_has_started_for_testing(&aptos_framework); + + let (burn_cap, mint_cap) = + aptos_coin::initialize_for_test(&aptos_framework); + coin::register(user); + coin::register(bank); + + init_module(admin); + aptos_coin::mint(&aptos_framework, user_address, MINT_PRICE); + + { + let state = borrow_global_mut(admin_address); + state.minted = 500; + }; + + user_mint(user); + + coin::destroy_burn_cap(burn_cap); + coin::destroy_mint_cap(mint_cap); + } + + #[test(admin = @admin, user = @0xA, bank = @wv1)] + #[expected_failure(abort_code = ERROR_USER_MINT_EXCEEDED)] + fun test_mint_failed_minter_minted( + admin: &signer, + user: &signer, + bank: &signer + ) acquires State { + let admin_address = signer::address_of(admin); + let user_address = signer::address_of(user); + let bank_address = signer::address_of(bank); + account::create_account_for_test(admin_address); + account::create_account_for_test(user_address); + account::create_account_for_test(bank_address); + + let aptos_framework = account::create_account_for_test(@aptos_framework); + timestamp::set_time_has_started_for_testing(&aptos_framework); + + let (burn_cap, mint_cap) = + aptos_coin::initialize_for_test(&aptos_framework); + coin::register(user); + coin::register(bank); + + init_module(admin); + aptos_coin::mint(&aptos_framework, user_address, MINT_PRICE); + + user_mint(user); + user_mint(user); + + coin::destroy_burn_cap(burn_cap); + coin::destroy_mint_cap(mint_cap); + } + + #[test(admin = @admin, user = @0xA, operator = @0xB)] + fun test_delegate_mint_success( + admin: &signer, + operator: &signer, + user: &signer + ) acquires State { + let admin_address = signer::address_of(admin); + let user_address = signer::address_of(user); + account::create_account_for_test(admin_address); + account::create_account_for_test(user_address); + + let aptos_framework = account::create_account_for_test(@aptos_framework); + timestamp::set_time_has_started_for_testing(&aptos_framework); + + admin::reviews::init_module_for_test(admin); + + let (burn_cap, mint_cap) = + aptos_coin::initialize_for_test(&aptos_framework); + coin::register(user); + init_module(admin); + aptos_coin::mint(&aptos_framework, user_address, MINT_PRICE); + + admin::reviews::grant_role( + admin, + signer::address_of(operator), + string::utf8(b"operator") + ); + + let image_uri = string::utf8(TOKEN_URI); + string::append(&mut image_uri, string_utils::format1(&b"{}.json",1)); + + let seed = bcs::to_bytes(&@VSEED); + let resource_account_address = account::create_resource_address(&@admin, seed); + + delegate_mint(operator, user_address); + + let state = borrow_global(admin_address); + + let expected_nft_token_address = token::create_token_address( + &resource_account_address, + &string::utf8(COLLECTION_NAME), + &string_utils::format1(&b"nft#{}", state.minted) + ); + let nft_token_object = object::address_to_object(expected_nft_token_address); + assert!( + object::is_owner(nft_token_object, user_address) == true, + 1 + ); + assert!( + token::creator(nft_token_object) == resource_account_address, + 4 + ); + assert!( + token::name(nft_token_object) == string_utils::format1(&b"nft#{}", state.minted), + 4 + ); + assert!( + token::description(nft_token_object) == string::utf8(TOKEN_DESCRIPTION), + 4 + ); + assert!( + token::uri(nft_token_object) == image_uri, + 4 + ); + assert!( + option::is_some(&token::royalty(nft_token_object)), + 4 + ); + assert!( + vector::contains(&state.nft_list, &expected_nft_token_address), + 4 + ); + + coin::destroy_burn_cap(burn_cap); + coin::destroy_mint_cap(mint_cap); + + assert!(event::counter(&state.nft_minted_events) == 1, 4); + } + + #[test(admin = @admin, user = @0xA, operator = @0xB)] + #[expected_failure(abort_code = ERROR_SIGNER_NOT_OPERATOR)] + fun test_delegate_mint_failure_not_operator( + admin: &signer, + operator: &signer, + user: &signer + ) acquires State { + let admin_address = signer::address_of(admin); + let user_address = signer::address_of(user); + account::create_account_for_test(admin_address); + account::create_account_for_test(user_address); + + let aptos_framework = account::create_account_for_test(@aptos_framework); + timestamp::set_time_has_started_for_testing(&aptos_framework); + + admin::reviews::init_module_for_test(admin); + init_module(admin); + + delegate_mint(operator, user_address); + } + +} \ No newline at end of file