Skip to content

Commit

Permalink
2024 Move Migration Syntax (#74)
Browse files Browse the repository at this point in the history
* reference update

* test changes

* test function

* update

* coupons.move finished

* coupons migrated

* remove comments

* update small syntax

* debug and update test

* coupon tests

* setup

* denylist

* discounts

* managed_names

* registration

* renewal

* subdomains

* subdomain proxy

* utils

* part 1 suins

* move part 2

* migration tests update

* remaining tests

* dependency updates

* update coupons

* more syntax conversion

* borrow syntax additional

* final borrow changes

* cleanup use

* more cleanup

* minor

* nesting added

* formatting

* update

* coupon update

* rules update

* update

* discounts
  • Loading branch information
leecchh authored Apr 16, 2024
1 parent 8bc0d30 commit 0ca48e7
Show file tree
Hide file tree
Showing 48 changed files with 1,317 additions and 1,501 deletions.
65 changes: 26 additions & 39 deletions packages/coupons/sources/coupons.move
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,12 @@
/// Each coupon is validated towards a list of rules. View `rules` module for explanation.
/// The app is authorized on `SuiNS` to be able to claim names and add earnings to the registry.
module coupons::coupons {
use std::string::{Self, String};
use std::string::String;

use sui::table::{Self, Table};
use sui::tx_context::{TxContext, sender};
use sui::object::{Self, UID};
use sui::transfer;
use sui::dynamic_field::{Self as df};
use sui::clock::Clock;
use sui::sui::SUI;
use sui::coin::{Self, Coin};
use sui::{table::{Self, Table}, dynamic_field::{Self as df}, clock::Clock, sui::SUI, coin::Coin};

use coupons::rules::{Self, CouponRules};
use coupons::constants;
use coupons::{rules::{Self, CouponRules}, constants};
use suins::{domain, suins::{Self, AdminCap, SuiNS}, suins_registration::SuinsRegistration, config::Self, registry::Registry};

/// Coupon already exists
const ECouponAlreadyExists: u64 = 0;
Expand All @@ -41,12 +34,6 @@ module coupons::coupons {
/// Our versioning of the coupons package.
const VERSION: u8 = 1;

// use suins::config;
use suins::domain;
use suins::suins::{Self, AdminCap, SuiNS}; // re-use AdminCap for creating new coupons.
use suins::suins_registration::SuinsRegistration;
use suins::config::{Self, Config};
use suins::registry::{Self, Registry};

// Authorization for the Coupons on SuiNS, to be able to register names on the app.
public struct CouponsApp has drop {}
Expand Down Expand Up @@ -99,65 +86,65 @@ module coupons::coupons {
clock: &Clock,
ctx: &mut TxContext
): SuinsRegistration {
assert_version_is_valid(self);
self.assert_version_is_valid();
// Validate that specified coupon is valid.
assert!(table::contains(&mut self.data.coupons, coupon_code), ECouponNotExists);
assert!(self.data.coupons.contains(coupon_code), ECouponNotExists);

// Verify coupon house is authorized to buy names.
suins::assert_app_is_authorized<CouponsApp>(suins);
suins.assert_app_is_authorized<CouponsApp>();

// Validate registration years are in [0,5] range.
assert!(no_years > 0 && no_years <= 5, EInvalidYearsArgument);

let config = suins::get_config<Config>(suins);
let config = suins.get_config();
let domain = domain::new(domain_name);
let label = domain::sld(&domain);
let label = domain.sld();

let domain_length = (string::length(label) as u8);
let domain_length = (label.length() as u8);

// Borrow coupon from the table.
let coupon = table::borrow_mut(&mut self.data.coupons, coupon_code);
let coupon = &mut self.data.coupons[coupon_code];

// We need to do a total of 5 checks, based on `CouponRules`
// Our checks work with `AND`, all of the conditions must pass for a coupon to be used.
// 1. Validate domain size.
rules::assert_coupon_valid_for_domain_size(&coupon.rules, domain_length);
coupon.rules.assert_coupon_valid_for_domain_size(domain_length);
// 2. Decrease available claims. Will ABORT if the coupon doesn't have enough available claims.
rules::decrease_available_claims(&mut coupon.rules);
coupon.rules.decrease_available_claims();
// 3. Validate the coupon is valid for the specified user.
rules::assert_coupon_valid_for_address(&coupon.rules, sender(ctx));
coupon.rules.assert_coupon_valid_for_address(ctx.sender());
// 4. Validate the coupon hasn't expired (Based on clock)
rules::assert_coupon_is_not_expired(&coupon.rules, clock);
coupon.rules.assert_coupon_is_not_expired(clock);
// 5. Validate years are valid for the coupon.
rules::assert_coupon_valid_for_domain_years(&coupon.rules, no_years);
coupon.rules.assert_coupon_valid_for_domain_years(no_years);

// Validate name can be registered (is main domain (no subdomain) and length is valid)
config::assert_valid_user_registerable_domain(&domain);

let original_price = config::calculate_price(config, domain_length, no_years);
let sale_price = internal_calculate_sale_price(original_price, coupon);

assert!(coin::value(&payment) == sale_price, EIncorrectAmount);
suins::app_add_balance(CouponsApp {}, suins, coin::into_balance(payment));
assert!(payment.value() == sale_price, EIncorrectAmount);
suins::app_add_balance(CouponsApp {}, suins, payment.into_balance());

// Clean up our registry by removing the coupon if no more available claims!
if(!rules::has_available_claims(&coupon.rules)){
if(!coupon.rules.has_available_claims()){
// remove the coupon, since it's no longer usable.
internal_remove_coupon(&mut self.data, coupon_code);
self.data.internal_remove_coupon(coupon_code);
};

let registry = suins::app_registry_mut<CouponsApp, Registry>(CouponsApp {}, suins);
registry::add_record(registry, domain, no_years, clock, ctx)
registry.add_record(domain, no_years, clock, ctx)
}

// A convenient helper to calculate the price in a PTB.
// Important: This function doesn't check the validity of the coupon (Whether the user can indeed use it)
// Nor does it calculate the original price. This is part of the Frontend anyways.
public fun calculate_sale_price(self: &mut CouponHouse, price: u64, coupon_code: String): u64 {
// Validate that specified coupon is valid.
assert!(table::contains(&mut self.data.coupons, coupon_code), ECouponNotExists);
assert!(self.data.coupons.contains(coupon_code), ECouponNotExists);
// Borrow coupon from the table.
let coupon = table::borrow_mut(&mut self.data.coupons, coupon_code);
let coupon = &mut self.data.coupons[coupon_code];
internal_calculate_sale_price(price, coupon)
}

Expand Down Expand Up @@ -262,8 +249,8 @@ module coupons::coupons {
code: String,
coupon: Coupon
) {
assert!(!table::contains(&mut self.coupons, code), ECouponAlreadyExists);
table::add(&mut self.coupons, code, coupon);
assert!(!self.coupons.contains(code), ECouponAlreadyExists);
self.coupons.add(code, coupon);
}

/// An internal function to create a coupon object.
Expand All @@ -282,7 +269,7 @@ module coupons::coupons {

// A function to remove a coupon from the system.
fun internal_remove_coupon(self: &mut Data, code: String) {
table::remove(&mut self.coupons, code);
self.coupons.remove(code);
}

// test only functions.
Expand Down
6 changes: 2 additions & 4 deletions packages/coupons/sources/range.move
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@
/// A module to introduce `range` checks for the rules.
module coupons::range {

use std::vector;

/// Invalid [from, to] setup in the range!
/// `to` parameter has to be >= `from`
const EInvalidRange: u64 = 0;
Expand All @@ -31,12 +29,12 @@ module coupons::range {

/// Get floor limit for the range.
public fun from(range: &Range): u8 {
*vector::borrow(&range.vec, 0)
range.vec[0]
}

/// Get upper limit for the range.
public fun to(range: &Range): u8 {
*vector::borrow(&range.vec, 1)
range.vec[1]
}

}
51 changes: 23 additions & 28 deletions packages/coupons/sources/rules.move
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,9 @@
// A module with a couple of helpers for validation of coupons
// validation of names etc.
module coupons::rules {
use sui::clock::Clock;

use std::vector;
use std::option::{Self, Option};

use sui::clock::{Self, Clock};

use coupons::constants;
use coupons::range::{Self, Range};
use coupons::{constants, range::Range};

use suins::constants::{Self as suins_constants};
// Errors
Expand Down Expand Up @@ -64,7 +59,7 @@ module coupons::rules {
): CouponRules {
assert!(is_valid_years_range(&years), EInvalidYears);
assert!(is_valid_length_range(&length), EInvalidLengthRule);
assert!(option::is_none(&available_claims) || (*option::borrow(&available_claims) > 0), EInvalidAvailableClaims);
assert!(available_claims.is_none() || (*available_claims.borrow() > 0), EInvalidAvailableClaims);
CouponRules {
length, available_claims, user, expiration, years
}
Expand All @@ -87,19 +82,19 @@ module coupons::rules {
/// We shouldn't get here ever, as we're checking this on the coupon creation, but
/// keeping it as a sanity check (e.g. created a coupon with 0 available claims).
public fun decrease_available_claims(rules: &mut CouponRules) {
if(option::is_some(&rules.available_claims)){
if(rules.available_claims.is_some()){
assert!(has_available_claims(rules), ENoMoreAvailableClaims);
// Decrease available claims by 1.
let available_claims = *option::borrow(&rules.available_claims);
option::swap(&mut rules.available_claims, available_claims - 1);
let available_claims = *rules.available_claims.borrow();
rules.available_claims.swap(available_claims - 1);
}
}

// Checks whether a coupon has available claims.
// Returns true if the rule is not set OR it has used all the available claims.
public fun has_available_claims(rules: &CouponRules): bool {
if(option::is_none(&rules.available_claims)) return true;
*option::borrow(&rules.available_claims) > 0
if(rules.available_claims.is_none()) return true;
*rules.available_claims.borrow() > 0
}

// Assertion helper for the validity of years.
Expand All @@ -113,13 +108,13 @@ module coupons::rules {
// 1. Exact years (e.g. 2 years, by passing [2,2])
// 2. Range of years (e.g. [1,3])
public fun is_coupon_valid_for_domain_years(rules: &CouponRules, target: u8): bool {
if(option::is_none(&rules.years)) return true;
if(rules.years.is_none()) return true;

range::is_in_range(option::borrow(&rules.years), target)
rules.years.borrow().is_in_range(target)
}

public fun assert_is_valid_discount_type(`type`: u8) {
assert!(vector::contains(&constants::discount_rule_types(), &`type`), EInvalidType);
assert!(constants::discount_rule_types().contains(&`type`), EInvalidType);
}

// verify that we are creating the coupons correctly (based on amount & type).
Expand All @@ -139,9 +134,9 @@ module coupons::rules {
/// We check the length of the name based on the domain length rule
public fun is_coupon_valid_for_domain_size(rules: &CouponRules, length: u8): bool {
// If the vec is not set, we pass this rule test.
if(option::is_none(&rules.length)) return true;
if(rules.length.is_none()) return true;

range::is_in_range(option::borrow(&rules.length), length)
rules.length.borrow().is_in_range(length)
}


Expand All @@ -152,8 +147,8 @@ module coupons::rules {
}
/// Check that the domain is valid for the specified address
public fun is_coupon_valid_for_address(rules: &CouponRules, user: address): bool {
if(option::is_none(&rules.user)) return true;
*option::borrow(&rules.user) == user
if(rules.user.is_none()) return true;
rules.user.borrow() == user
}

/// Simple assertion for the coupon expiration.
Expand All @@ -164,23 +159,23 @@ module coupons::rules {

/// Check whether a coupon has expired
public fun is_coupon_expired(rules: &CouponRules, clock: &Clock): bool {
if(option::is_none(&rules.expiration)){
if(rules.expiration.is_none()){
return false
};

clock::timestamp_ms(clock) > *option::borrow(&rules.expiration)
clock.timestamp_ms() > *rules.expiration.borrow()
}


fun is_valid_years_range(range: &Option<Range>): bool {
if(option::is_none(range)) return true;
let range = option::borrow(range);
range::from(range) >= 1 && range::to(range) <= 5
if(range.is_none()) return true;
let range = range.borrow();
range.from() >= 1 && range.to() <= 5
}

fun is_valid_length_range(range: &Option<Range>): bool {
if(option::is_none(range)) return true;
let range = option::borrow(range);
range::from(range) >= suins_constants::min_domain_length() && range::to(range) <= suins_constants::max_domain_length()
if(range.is_none()) return true;
let range = range.borrow();
range.from() >= suins_constants::min_domain_length() && range.to() <= suins_constants::max_domain_length()
}
}
60 changes: 34 additions & 26 deletions packages/coupons/tests/authorization_tests.move
Original file line number Diff line number Diff line change
Expand Up @@ -5,61 +5,69 @@
#[test_only]
module coupons::app_authorization_tests {

// use std::string::{utf8, String};
use sui::test_scenario::{Self};
use sui::test_scenario::{return_shared, return_to_sender, end};

use coupons::coupons::{Self, CouponHouse};
use suins::suins::{AdminCap};
use coupons::setup::{Self, UnauthorizedTestApp, TestApp, admin, user};
use coupons::{
coupons::{app_data_mut, deauthorize_app},
setup::{
Self,
UnauthorizedTestApp,
TestApp,
admin,
user,
test_init,
unauthorized_test_app
}
};

#[test]
fun admin_get_app_success() {
let mut scenario_val = setup::test_init();
let mut scenario_val = test_init();
let scenario = &mut scenario_val;
// auth style as authorized app
{
test_scenario::next_tx(scenario, user());
let mut coupon_house = test_scenario::take_shared<CouponHouse>(scenario);
coupons::app_data_mut<TestApp>(setup::test_app(), &mut coupon_house);
test_scenario::return_shared(coupon_house);
scenario.next_tx(user());
let mut coupon_house = scenario.take_shared();
app_data_mut<TestApp>(setup::test_app(), &mut coupon_house);
return_shared(coupon_house);
};

test_scenario::end(scenario_val);
end(scenario_val);
}

#[test]
fun authorized_app_get_app_success(){
let mut scenario_val = setup::test_init();
let mut scenario_val = test_init();
let scenario = &mut scenario_val;
{
test_scenario::next_tx(scenario, admin());
scenario.next_tx(admin());

let mut coupon_house = test_scenario::take_shared<CouponHouse>(scenario);
let admin_cap = test_scenario::take_from_sender<AdminCap>(scenario);
let mut coupon_house = scenario.take_shared();
let admin_cap = scenario.take_from_sender();

// test app deauthorization.
coupons::deauthorize_app<TestApp>(&admin_cap, &mut coupon_house);
deauthorize_app<TestApp>(&admin_cap, &mut coupon_house);

// test that the app is indeed non authorized
assert!(!coupons::is_app_authorized<TestApp>(&coupon_house), 0);
assert!(!coupon_house.is_app_authorized<TestApp>(), 0);

test_scenario::return_to_sender(scenario, admin_cap);
test_scenario::return_shared(coupon_house);
return_to_sender(scenario, admin_cap);
return_shared(coupon_house);
};
test_scenario::end(scenario_val);
end(scenario_val);
}

#[test, expected_failure(abort_code=::coupons::coupons::EAppNotAuthorized)]
fun unauthorized_app_failure() {
let mut scenario_val = setup::test_init();
let mut scenario_val = test_init();
let scenario = &mut scenario_val;
{
test_scenario::next_tx(scenario, user());
let mut coupon_house = test_scenario::take_shared<CouponHouse>(scenario);
coupons::app_data_mut<UnauthorizedTestApp>(setup::unauthorized_test_app(), &mut coupon_house);
test_scenario::return_shared(coupon_house);
scenario.next_tx(user());
let mut coupon_house = scenario.take_shared();
app_data_mut<UnauthorizedTestApp>(unauthorized_test_app(), &mut coupon_house);
return_shared(coupon_house);
};
test_scenario::end(scenario_val);
end(scenario_val);
}

}
Loading

0 comments on commit 0ca48e7

Please sign in to comment.