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

Updated the upgradability of nameservice.cairo #223

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
200 changes: 187 additions & 13 deletions onchain/cairo/src/afk_id/nameservice.cairo
Original file line number Diff line number Diff line change
@@ -1,39 +1,115 @@
pub mod UserNameClaimErrors {
pub const USERNAME_CLAIMED: felt252 = 'Username already claimed';
pub const USER_HAS_USERNAME: felt252 = 'User already has a username';
pub const USER_DOESNT_HAVE_USERNAME: felt252 = 'User does not have a username';
pub const USER_DOESNT_HAVE_USERNAME: felt252 = 'User does not have username';
pub const SUBSCRIPTION_EXPIRED: felt252 = 'Subscription has expired';
pub const INVALID_PAYMENT: felt252 = 'Invalid payment amount';
pub const INVALID_PRICE: felt252 = 'Invalid price setting';
pub const INVALID_USERNAME: felt252 = 'Invalid username format';
pub const INVALID_DOMAIN_SUFFIX: felt252 = 'Domain must end with .afk';

}

const ADMIN_ROLE: felt252 = selector!("ADMIN_ROLE");


#[starknet::contract]
pub mod Nameservice {
use afk::interfaces::erc20::{IERC20Dispatcher, IERC20DispatcherTrait};
use afk::interfaces::username_store::IUsernameStore;
use openzeppelin_access::accesscontrol::AccessControlComponent;
use openzeppelin_introspection::src5::SRC5Component;
use openzeppelin::access::ownable::OwnableComponent;
use openzeppelin_token::erc20::{ERC20Component, ERC20HooksEmptyImpl};

use openzeppelin::upgrades::UpgradeableComponent;
use openzeppelin::upgrades::interface::IUpgradeable;
use starknet::storage::{StoragePointerWriteAccess, StoragePathEntry, Map};
use starknet::{
ContractAddress, contract_address_const, get_caller_address, get_block_timestamp
ContractAddress, contract_address_const, get_caller_address, get_block_timestamp,
get_contract_address, ClassHash
};
use super::UserNameClaimErrors;
use super::ADMIN_ROLE;

const YEAR_IN_SECONDS: u64 = 31536000_u64; // 365 days in seconds

// Components
component!(path: OwnableComponent, storage: ownable, event: OwnableEvent);
component!(path: UpgradeableComponent, storage: upgradeable, event: UpgradeableEvent);
component!(path: AccessControlComponent, storage: accesscontrol, event: AccessControlEvent);
component!(path: SRC5Component, storage: src5, event: SRC5Event);
// component!(path: ERC20Component, storage: erc20, event: ERC20Event);

/// Ownable
#[abi(embed_v0)]
impl OwnableImpl = OwnableComponent::OwnableImpl<ContractState>;
impl OwnableInternalImpl = OwnableComponent::InternalImpl<ContractState>;

/// Upgradeable
impl UpgradeableInternalImpl = UpgradeableComponent::InternalImpl<ContractState>;

// AccessControl
#[abi(embed_v0)]
impl AccessControlImpl = AccessControlComponent::AccessControlImpl<ContractState>;
impl AccessControlInternalImpl = AccessControlComponent::InternalImpl<ContractState>;

// SRC5
#[abi(embed_v0)]
impl SRC5Impl = SRC5Component::SRC5Impl<ContractState>;

// // ERC20
// #[abi(embed_v0)]
// impl ERC20Impl = ERC20Component::ERC20Impl<ContractState>;
// #[abi(embed_v0)]
// impl ERC20MetadataImpl = ERC20Component::ERC20MetadataImpl<ContractState>;
// impl ERC20InternalImpl = ERC20Component::InternalImpl<ContractState>;

#[storage]
struct Storage {
usernames: Map::<felt252, ContractAddress>,
user_to_username: Map::<ContractAddress, felt252>,
subscription_expiry: Map::<ContractAddress, u64>,
subscription_price: u256,
token_quote: ContractAddress
token_quote: ContractAddress,
#[substorage(v0)]
upgradeable: UpgradeableComponent::Storage,
#[substorage(v0)]
ownable: OwnableComponent::Storage,
#[substorage(v0)]
accesscontrol: AccessControlComponent::Storage,
#[substorage(v0)]
src5: SRC5Component::Storage
// #[substorage(v0)]
// erc20: ERC20Component::Storage

}

#[event]
#[derive(Drop, starknet::Event)]
enum Event {
pub enum Event {
UserNameClaimed: UserNameClaimed,
UserNameChanged: UserNameChanged
UserNameChanged: UserNameChanged,
SubscriptionRenewed: SubscriptionRenewed,
PriceUpdated: PriceUpdated,
#[flat]
AccessControlEvent: AccessControlComponent::Event,
#[flat]
SRC5Event: SRC5Component::Event,
// #[flat]
// ERC20Event: ERC20Component::Event,
#[flat]
OwnableEvent: OwnableComponent::Event,
#[flat]
UpgradeableEvent: UpgradeableComponent::Event,
}

#[derive(Drop, starknet::Event)]
struct UserNameClaimed {
#[key]
address: ContractAddress,
username: felt252,
timestamp: u64
expiry: u64
}

#[derive(Drop, starknet::Event)]
Expand All @@ -44,45 +120,91 @@ pub mod Nameservice {
new_username: felt252
}

#[derive(Drop, starknet::Event)]
struct SubscriptionRenewed {
#[key]
address: ContractAddress,
expiry: u64
}

#[derive(Drop, starknet::Event)]
struct PriceUpdated {
old_price: u256,
new_price: u256
}

#[constructor]
fn constructor(ref self: ContractState,
owner: ContractAddress,
admin: ContractAddress
) {
self.ownable.initializer(owner);

self.accesscontrol.initializer();
self.accesscontrol._grant_role(ADMIN_ROLE, admin);
}

// External interfaces implementation
// #[external(v0)]
// impl AccessControlImpl = AccessControlComponent::AccessControlImpl<ContractState>;

#[external(v0)]
impl UpgradeableImpl of IUpgradeable<ContractState> {
fn upgrade(ref self: ContractState, new_class_hash: ClassHash) {
// This function can only be called by the owner
self.ownable.assert_only_owner();
//TODO: Replace class hash
// self.upgradeable._upgrade(new_class_hash);
}
}

#[abi(embed_v0)]
impl Nameservice of IUsernameStore<ContractState> {
fn claim_username(ref self: ContractState, key: felt252) {
let caller_address = get_caller_address();
let current_time = get_block_timestamp();

// TODO add yearly timestamp subscription
// Check for user having username
assert(
self.user_to_username.read(caller_address) == 0,
UserNameClaimErrors::USER_HAS_USERNAME
);

// Check for availability
let username_address = self.usernames.read(key);
assert(
username_address == contract_address_const::<0>(),
UserNameClaimErrors::USERNAME_CLAIMED
);

// Payment
let price = self.subscription_price.read();
let payment_token = IERC20Dispatcher { contract_address: self.token_quote.read() };
payment_token.transfer_from(caller_address, get_contract_address(), price);

let expiry = current_time + YEAR_IN_SECONDS;
self.usernames.entry(key).write(caller_address);
self.user_to_username.entry(caller_address).write(key);
self.subscription_expiry.entry(caller_address).write(expiry);

self
.emit(
UserNameClaimed {
username: key, address: caller_address, timestamp: get_block_timestamp()
}
);
self.emit(UserNameClaimed { username: key, address: caller_address, expiry });
}

fn change_username(ref self: ContractState, new_username: felt252) {
let caller_address = get_caller_address();
let current_time = get_block_timestamp();
let old_username = self.user_to_username.read(caller_address);
assert(old_username != 0, UserNameClaimErrors::USER_DOESNT_HAVE_USERNAME);
let expiry = self.subscription_expiry.read(caller_address);
assert(current_time < expiry, UserNameClaimErrors::SUBSCRIPTION_EXPIRED);

let new_username_address = self.usernames.read(new_username);
assert(
new_username_address == contract_address_const::<0>(),
UserNameClaimErrors::USERNAME_CLAIMED
);

// Update username mappings
self.usernames.entry(old_username).write(contract_address_const::<0>());
self.usernames.entry(new_username).write(caller_address);
self.user_to_username.entry(caller_address).write(new_username);
Expand All @@ -104,5 +226,57 @@ pub mod Nameservice {
fn get_username_address(self: @ContractState, key: felt252) -> ContractAddress {
self.usernames.read(key)
}

fn renew_subscription(ref self: ContractState) {
let caller = get_caller_address();
let current_time = get_block_timestamp();
let current_expiry = self.subscription_expiry.read(caller);

// Payment
let price = self.subscription_price.read();
let payment_token = IERC20Dispatcher { contract_address: self.token_quote.read() };
payment_token.transfer_from(caller, get_contract_address(), price);

let new_expiry = max(current_time, current_expiry) + YEAR_IN_SECONDS;
self.subscription_expiry.write(caller, new_expiry);

self.emit(SubscriptionRenewed { address: caller, expiry: new_expiry });
}


fn withdraw_fees(ref self: ContractState, amount: u256) {
self.accesscontrol.assert_only_role(ADMIN_ROLE);
let token = IERC20Dispatcher { contract_address: self.token_quote.read() };
token.transfer(self.ownable.owner(), amount);
}
}

// Admin functions
#[external(v0)]
fn update_subscription_price(ref self: ContractState, new_price: u256) {
self.accesscontrol.assert_only_role(ADMIN_ROLE);
assert(new_price > 0, UserNameClaimErrors::INVALID_PRICE);

let old_price = self.subscription_price.read();
self.subscription_price.write(new_price);

self.emit(PriceUpdated { old_price, new_price });
}

#[external(v0)]
fn set_token_quote(ref self: ContractState, token_quote: ContractAddress) {
self.accesscontrol.assert_only_role(ADMIN_ROLE);
self.token_quote.write(token_quote);
}


//Internal function to check the maximum of two
#[generate_trait]
fn max(a: u64, b: u64) -> u64 {
if a > b {
a
} else {
b
}
}
}
4 changes: 4 additions & 0 deletions onchain/cairo/src/afk_id/username_store.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,10 @@ pub mod UsernameStore {
);
}

fn withdraw_fees(ref self: ContractState, amount: u256) {}

fn renew_subscription(ref self: ContractState) {}

fn get_username(self: @ContractState, address: ContractAddress) -> felt252 {
self.user_to_username.read(address)
}
Expand Down
8 changes: 8 additions & 0 deletions onchain/cairo/src/interfaces/nameservice.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,12 @@ pub trait INameservice<TContractState> {
fn change_username(ref self: TContractState, new_username: felt252);
fn get_username(self: @TContractState, address: ContractAddress) -> felt252;
fn get_username_address(self: @TContractState, key: felt252) -> ContractAddress;
fn renew_subscription(ref self: TContractState);
fn get_subscription_expiry(self: @TContractState, address: ContractAddress) -> u64;
fn get_subscription_price(self: @TContractState) -> u256;
fn withdraw_fees(ref self: TContractState, amount: u256);

// Add these functions
fn set_token_quote(ref self: TContractState, token_quote: ContractAddress);
fn update_subscription_price(ref self: TContractState, new_price: u256);
}
2 changes: 2 additions & 0 deletions onchain/cairo/src/interfaces/username_store.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,6 @@ pub trait IUsernameStore<TContractState> {
fn change_username(ref self: TContractState, new_username: felt252);
fn get_username(self: @TContractState, address: ContractAddress) -> felt252;
fn get_username_address(self: @TContractState, key: felt252) -> ContractAddress;
fn withdraw_fees(ref self: TContractState, amount: u256);
fn renew_subscription(ref self: TContractState);
}
Loading
Loading