From 7e55693598e9dee98d23447d80f7e649833295f4 Mon Sep 17 00:00:00 2001 From: alnoki <43892045+alnoki@users.noreply.github.com> Date: Thu, 31 Oct 2024 17:25:50 -0700 Subject: [PATCH 01/22] Add stub for access code --- .../emojicoin_dot_fun_access_code.move | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 src/move/rewards/sources/emojicoin_dot_fun_access_code.move diff --git a/src/move/rewards/sources/emojicoin_dot_fun_access_code.move b/src/move/rewards/sources/emojicoin_dot_fun_access_code.move new file mode 100644 index 000000000..fe1abe8d0 --- /dev/null +++ b/src/move/rewards/sources/emojicoin_dot_fun_access_code.move @@ -0,0 +1,22 @@ +module rewards::emojicoin_dot_fun_access_code { + + use aptos_framework::account::{Self, SignerCapability}; + use aptos_framework::event; + + /// Resource account address seed for the access code vault, uses a different seed from base + /// rewards to prevent a seed collision. + const VAULT: vector = b"AccessCodeVault"; + + const OCTAS_PER_APT: u64 = 100_000_000; + const APT_PER_REDEMPTION: u64 = 5; + + struct Vault has key { + signer_capability: SignerCapability, + } + + #[event] + struct EmojicoinDotFunAccessCodeRedemption has copy, drop, store { + claimant: address, + } + +} \ No newline at end of file From 86cd6f6d16dd251d0b47727ebd835246fae66b12 Mon Sep 17 00:00:00 2001 From: alnoki <43892045+alnoki@users.noreply.github.com> Date: Thu, 31 Oct 2024 17:27:52 -0700 Subject: [PATCH 02/22] Format --- .../emojicoin_dot_fun_access_code.move | 7 +- .../sources/emojicoin_dot_fun_rewards.move | 169 ++++++++++-------- 2 files changed, 93 insertions(+), 83 deletions(-) diff --git a/src/move/rewards/sources/emojicoin_dot_fun_access_code.move b/src/move/rewards/sources/emojicoin_dot_fun_access_code.move index fe1abe8d0..fc3d50d3d 100644 --- a/src/move/rewards/sources/emojicoin_dot_fun_access_code.move +++ b/src/move/rewards/sources/emojicoin_dot_fun_access_code.move @@ -11,12 +11,11 @@ module rewards::emojicoin_dot_fun_access_code { const APT_PER_REDEMPTION: u64 = 5; struct Vault has key { - signer_capability: SignerCapability, + signer_capability: SignerCapability } #[event] struct EmojicoinDotFunAccessCodeRedemption has copy, drop, store { - claimant: address, + claimant: address } - -} \ No newline at end of file +} diff --git a/src/move/rewards/sources/emojicoin_dot_fun_rewards.move b/src/move/rewards/sources/emojicoin_dot_fun_rewards.move index b2cb660a1..4334c1e9e 100644 --- a/src/move/rewards/sources/emojicoin_dot_fun_rewards.move +++ b/src/move/rewards/sources/emojicoin_dot_fun_rewards.move @@ -44,41 +44,27 @@ module rewards::emojicoin_dot_fun_rewards { const E_APT_REWARD_AMOUNTS_DECREASING: u64 = 4; /// Expected number of rewards disbursed per tier per `APT_NOMINAL_VOLUME`. - const NOMINAL_N_REWARDS_PER_TIER: vector = vector[ - 1_500, - 500, - 200, - 50, - 5, - 1, - ]; + const NOMINAL_N_REWARDS_PER_TIER: vector = vector[1_500, 500, 200, 50, 5, 1]; /// Reward value in APT per reward per tier. - const APT_REWARD_AMOUNTS_PER_TIER: vector = vector[ - 1, - 2, - 5, - 10, - 100, - 500, - ]; + const APT_REWARD_AMOUNTS_PER_TIER: vector = vector[1, 2, 5, 10, 100, 500]; struct RewardTier has drop, store { apt_amount_per_reward: u64, n_rewards_disbursed: u64, n_rewards_remaining: u64, - reward_probability_per_octa_of_swap_fees_paid_q64: u128, + reward_probability_per_octa_of_swap_fees_paid_q64: u128 } struct Vault has key { signer_capability: SignerCapability, - reward_tiers: vector, + reward_tiers: vector } #[event] struct EmojicoinDotFunRewards has copy, drop, store { swap: Swap, - octas_reward_amount: u64, + octas_reward_amount: u64 } #[randomness] @@ -87,22 +73,22 @@ module rewards::emojicoin_dot_fun_rewards { market_address: address, input_amount: u64, is_sell: bool, - min_output_amount: u64, + min_output_amount: u64 ) acquires Vault { // Simulate swap to get integrator fee, then execute swap. let swapper_address = signer::address_of(swapper); - let swap = emojicoin_dot_fun::simulate_swap( - swapper_address, - market_address, - input_amount, - is_sell, - @integrator, - INTEGRATOR_FEE_RATE_BPS, - ); - let ( - _, _, _, _, _, _, _, _, _, _, _, _, integrator_fee_in_octas, _, _, _, _, _, - ) = emojicoin_dot_fun::unpack_swap(swap); + let swap = + emojicoin_dot_fun::simulate_swap( + swapper_address, + market_address, + input_amount, + is_sell, + @integrator, + INTEGRATOR_FEE_RATE_BPS + ); + let (_, _, _, _, _, _, _, _, _, _, _, _, integrator_fee_in_octas, _, _, _, _, _) = + emojicoin_dot_fun::unpack_swap(swap); emojicoin_dot_fun::swap( swapper, market_address, @@ -110,7 +96,7 @@ module rewards::emojicoin_dot_fun_rewards { is_sell, @integrator, INTEGRATOR_FEE_RATE_BPS, - min_output_amount, + min_output_amount ); // Get vault balance, returning early if empty. @@ -138,7 +124,8 @@ module rewards::emojicoin_dot_fun_rewards { // `probability_per_octa_q64` is less than 1 in nominal probability, hence less than // `MAX_U64` when represented as a Q64-style `u128`. Since `integrator_fee_in_octas` is // also significantly less than `MAX_U64`, the product is less than `MAX_U128`. - let reward_threshold_q64 = probability_per_octa_q64 * (integrator_fee_in_octas as u128); + let reward_threshold_q64 = + probability_per_octa_q64 * (integrator_fee_in_octas as u128); // Check if user is eligible for reward at current tier by checking if tier still has // rewards remaining, then compare random number to probability threshold. Since the @@ -154,37 +141,46 @@ module rewards::emojicoin_dot_fun_rewards { // disbursement effectively requires an additional GOTO statement (`BrFalse`) in bytecode // for the false branch. if (!option::is_none(&highest_winning_tier_index)) { - let highest_winning_tier_index = option::destroy_some(highest_winning_tier_index); - let tier_ref_mut = vector::borrow_mut(tiers_ref_mut, highest_winning_tier_index); + let highest_winning_tier_index = + option::destroy_some(highest_winning_tier_index); + let tier_ref_mut = vector::borrow_mut( + tiers_ref_mut, highest_winning_tier_index + ); tier_ref_mut.n_rewards_remaining = tier_ref_mut.n_rewards_remaining - 1; tier_ref_mut.n_rewards_disbursed = tier_ref_mut.n_rewards_disbursed + 1; - let octas_reward_amount = tier_ref_mut.apt_amount_per_reward * OCTAS_PER_APT; - let vault_signer = account::create_signer_with_capability(vault_signer_cap_ref); - event::emit(EmojicoinDotFunRewards{ swap, octas_reward_amount }); + let octas_reward_amount = tier_ref_mut.apt_amount_per_reward + * OCTAS_PER_APT; + let vault_signer = + account::create_signer_with_capability(vault_signer_cap_ref); + event::emit(EmojicoinDotFunRewards { swap, octas_reward_amount }); aptos_account::transfer(&vault_signer, swapper_address, octas_reward_amount); } } /// To fund 10 rewards in the first tier and 5 rewards in the second tier, pass /// `n_rewards_to_fund_per_tier` as `vector[10, 5, ...]`. - public entry fun fund_tiers(funder: &signer, n_rewards_to_fund_per_tier: vector) - acquires Vault - { + public entry fun fund_tiers( + funder: &signer, n_rewards_to_fund_per_tier: vector + ) acquires Vault { // Check tiers. let vault_ref_mut = borrow_global_mut(@rewards); let n_tiers = vector::length(&NOMINAL_N_REWARDS_PER_TIER); let tiers_ref_mut = &mut vault_ref_mut.reward_tiers; - assert!(vector::length(&n_rewards_to_fund_per_tier) == n_tiers, E_N_TIERS_MISMATCH); + assert!( + vector::length(&n_rewards_to_fund_per_tier) == n_tiers, E_N_TIERS_MISMATCH + ); // Calculate total amount to fund, updating remaining rewards for each tier. let octas_to_fund = 0; - for (i in 0..n_tiers) { + for (i in 0..n_tiers) { let tier_ref_mut = vector::borrow_mut(tiers_ref_mut, i); - let n_rewards_to_fund_this_tier = *vector::borrow(&n_rewards_to_fund_per_tier, i); - octas_to_fund = octas_to_fund + - n_rewards_to_fund_this_tier * tier_ref_mut.apt_amount_per_reward * OCTAS_PER_APT; - tier_ref_mut.n_rewards_remaining = - tier_ref_mut.n_rewards_remaining + n_rewards_to_fund_this_tier; + let n_rewards_to_fund_this_tier = + *vector::borrow(&n_rewards_to_fund_per_tier, i); + octas_to_fund = octas_to_fund + + n_rewards_to_fund_this_tier * tier_ref_mut.apt_amount_per_reward + * OCTAS_PER_APT; + tier_ref_mut.n_rewards_remaining = tier_ref_mut.n_rewards_remaining + + n_rewards_to_fund_this_tier; }; // Transfer rewards to vault. @@ -194,35 +190,43 @@ module rewards::emojicoin_dot_fun_rewards { } fun init_module(rewards: &signer) { - let (vault_signer, signer_capability) = account::create_resource_account(rewards, VAULT); - move_to(rewards, Vault { - signer_capability, - reward_tiers: reward_tiers(), - }); + let (vault_signer, signer_capability) = + account::create_resource_account(rewards, VAULT); + move_to( + rewards, + Vault { signer_capability, reward_tiers: reward_tiers() } + ); coin::register(&vault_signer); } fun reward_tiers(): vector { // Check tier count. let n_tiers = vector::length(&NOMINAL_N_REWARDS_PER_TIER); - assert!(vector::length(&APT_REWARD_AMOUNTS_PER_TIER) == n_tiers, E_N_TIERS_MISMATCH); + assert!( + vector::length(&APT_REWARD_AMOUNTS_PER_TIER) == n_tiers, E_N_TIERS_MISMATCH + ); // Check number of rewards, and reward amount in APT: total, and for each tier. let n_rewards_total = 0; let apt_total_reward_amount = 0; let n_rewards_per_tier_last = *vector::borrow(&NOMINAL_N_REWARDS_PER_TIER, 0); - let apt_reward_amount_last_tier = *vector::borrow(&APT_REWARD_AMOUNTS_PER_TIER, 0); - for (i in 0..n_tiers) { + let apt_reward_amount_last_tier = *vector::borrow( + &APT_REWARD_AMOUNTS_PER_TIER, 0 + ); + for (i in 0..n_tiers) { let n_rewards_this_tier = *vector::borrow(&NOMINAL_N_REWARDS_PER_TIER, i); - assert!(n_rewards_this_tier <= n_rewards_per_tier_last, E_N_REWARDS_INCREASING); + assert!( + n_rewards_this_tier <= n_rewards_per_tier_last, E_N_REWARDS_INCREASING + ); n_rewards_total = n_rewards_total + n_rewards_this_tier; - let apt_reward_amount_this_tier = *vector::borrow(&APT_REWARD_AMOUNTS_PER_TIER, i); + let apt_reward_amount_this_tier = + *vector::borrow(&APT_REWARD_AMOUNTS_PER_TIER, i); assert!( apt_reward_amount_this_tier >= apt_reward_amount_last_tier, E_APT_REWARD_AMOUNTS_DECREASING ); - apt_total_reward_amount = apt_total_reward_amount + - apt_reward_amount_this_tier * n_rewards_this_tier; + apt_total_reward_amount = apt_total_reward_amount + + apt_reward_amount_this_tier * n_rewards_this_tier; n_rewards_per_tier_last = n_rewards_this_tier; apt_reward_amount_last_tier = apt_reward_amount_this_tier; }; @@ -230,35 +234,42 @@ module rewards::emojicoin_dot_fun_rewards { // Check expected value of rewards against nominal fees paid. let octas_nominal_volume = APT_NOMINAL_VOLUME * OCTAS_PER_APT; - let octas_nominal_fees = ( + let octas_nominal_fees = ( - (octas_nominal_volume as u128) * (INTEGRATOR_FEE_RATE_BPS as u128) / - (BASIS_POINTS_PER_UNIT as u128) - ) as u64 - ); + ((octas_nominal_volume as u128) * (INTEGRATOR_FEE_RATE_BPS as u128) + / (BASIS_POINTS_PER_UNIT as u128)) as u64 + ); let octas_total_reward_amount = apt_total_reward_amount * OCTAS_PER_APT; assert!(octas_total_reward_amount <= octas_nominal_fees, E_EXPECTED_VALUE); // Construct tiers. let reward_tiers = vector[]; for (i in 0..n_tiers) { - vector::push_back(&mut reward_tiers, RewardTier { - apt_amount_per_reward: *vector::borrow(&APT_REWARD_AMOUNTS_PER_TIER, i), - n_rewards_disbursed: 0, - n_rewards_remaining: 0, - // Once `octas_nominal_fees` have been paid, `NOMINAL_N_REWARDS_PER_TIER[i]` rewards - // will have been disbursed, so the probability of winning a reward for an octa of - // fees paid is the ratio of the number of rewards in this tier to the total number - // of nominal fees paid after all rewards for this tier have been disbursed. - reward_probability_per_octa_of_swap_fees_paid_q64: - ( - ((*vector::borrow(&NOMINAL_N_REWARDS_PER_TIER, i) as u128) << SHIFT_Q64) / - (octas_nominal_fees as u128) + vector::push_back( + &mut reward_tiers, + RewardTier { + apt_amount_per_reward: *vector::borrow( + &APT_REWARD_AMOUNTS_PER_TIER, i + ), + n_rewards_disbursed: 0, + n_rewards_remaining: 0, + // Once `octas_nominal_fees` have been paid, `NOMINAL_N_REWARDS_PER_TIER[i]` + // rewards will have been disbursed, so the probability of winning a reward for + // an octa of fees paid is the ratio of the number of rewards in this tier to + // the total number of nominal fees paid after all rewards for this tier have + // been disbursed. + reward_probability_per_octa_of_swap_fees_paid_q64: ( + ((*vector::borrow(&NOMINAL_N_REWARDS_PER_TIER, i) as u128) + << SHIFT_Q64) / (octas_nominal_fees as u128) ) - }); + } + ); }; reward_tiers } - #[test] fun test_reward_tiers() { reward_tiers(); } + #[test] + fun test_reward_tiers() { + reward_tiers(); + } } From 0c6010112aea0c57d7809d52f54ed51f49f2191a Mon Sep 17 00:00:00 2001 From: alnoki <43892045+alnoki@users.noreply.github.com> Date: Thu, 31 Oct 2024 18:31:48 -0700 Subject: [PATCH 03/22] Add access codes module --- .../emojicoin_dot_fun_access_code.move | 202 +++++++++++++++++- 1 file changed, 195 insertions(+), 7 deletions(-) diff --git a/src/move/rewards/sources/emojicoin_dot_fun_access_code.move b/src/move/rewards/sources/emojicoin_dot_fun_access_code.move index fc3d50d3d..0b1a22804 100644 --- a/src/move/rewards/sources/emojicoin_dot_fun_access_code.move +++ b/src/move/rewards/sources/emojicoin_dot_fun_access_code.move @@ -1,21 +1,209 @@ module rewards::emojicoin_dot_fun_access_code { use aptos_framework::account::{Self, SignerCapability}; + use aptos_framework::aptos_account; + use aptos_framework::aptos_coin::AptosCoin; + use aptos_framework::coin; use aptos_framework::event; + use aptos_std::simple_map::SimpleMap; + use aptos_std::smart_table::{Self, SmartTable}; + use emojicoin_dot_fun::emojicoin_dot_fun::{Self, Swap}; + use std::option::Option; + use std::signer; + use std::hash; - /// Resource account address seed for the access code vault, uses a different seed from base - /// rewards to prevent a seed collision. - const VAULT: vector = b"AccessCodeVault"; - - const OCTAS_PER_APT: u64 = 100_000_000; const APT_PER_REDEMPTION: u64 = 5; + const INTEGRATOR_FEE_RATE_BPS: u8 = 100; + const NIL: address = @0x0; + const OCTAS_PER_APT: u64 = 100_000_000; + const VAULT: vector = b"Access code vault"; + + /// Signer does not correspond to admin. + const E_NOT_ADMIN: u64 = 0; + /// Admin to remove address does not correspond to admin. + const E_ADMIN_TO_REMOVE_IS_NOT_ADMIN: u64 = 1; + /// Access code is not in manifest. + const E_INVALID_ACCESS_CODE: u64 = 2; + /// Access code has already been redeemed. + const E_ACCESS_CODE_ALREADY_REDEEMED: u64 = 3; + /// Vault has insufficient funds. + const E_VAULT_INSUFFICIENT_FUNDS: u64 = 4; struct Vault has key { - signer_capability: SignerCapability + signer_capability: SignerCapability, + /// Map from SHA3-256 hash of access code to address of claimaint, `NIL` if unclaimed. + manifest: SmartTable, address>, + /// Addresses of signers who can mutate the manifest. + admins: vector
} #[event] struct EmojicoinDotFunAccessCodeRedemption has copy, drop, store { - claimant: address + claimant: address, + octas_claim_amount: u64, + swap: Swap + } + + #[view] + public fun access_code_hash_claimant_entry( + access_code_hash: vector + ): address acquires Vault { + *Vault[@rewards].manifest.borrow(access_code_hash) + } + + #[view] + public fun access_code_hashes(): vector> acquires Vault { + Vault[@rewards].manifest.keys() + } + + #[view] + public fun access_code_hashes_paginated( + starting_bucket_index: u64, starting_vector_index: u64, num_keys_to_get: u64 + ): (vector>, Option, Option) acquires Vault { + Vault[@rewards].manifest.keys_paginated( + starting_bucket_index, starting_vector_index, num_keys_to_get + ) + } + + #[view] + public fun is_access_code_hash_in_manifest( + access_code_hash: vector + ): bool acquires Vault { + Vault[@rewards].manifest.contains(access_code_hash) + } + + #[view] + public fun manifest_to_simple_map(): SimpleMap, address> acquires Vault { + Vault[@rewards].manifest.to_simple_map() + } + + #[view] + public fun vault_balance(): u64 acquires Vault { + coin::balance( + account::get_signer_capability_address(&Vault[@rewards].signer_capability) + ) + } + + public entry fun add_access_code_hashes( + admin: &signer, access_code_hashes: vector> + ) acquires Vault { + let manifest_ref_mut = &mut borrow_vault_mut_checked(admin).manifest; + access_code_hashes.for_each(|access_code_hash| { + manifest_ref_mut.add(access_code_hash, NIL); + }); + } + + public entry fun add_admin(admin: &signer, new_admin: address) acquires Vault { + let admins_ref_mut = &mut borrow_vault_mut_checked(admin).admins; + admins_ref_mut.push_back(new_admin); + } + + public entry fun fund_vault(funder: &signer, amount: u64) acquires Vault { + aptos_account::transfer( + funder, + account::get_signer_capability_address(&Vault[@rewards].signer_capability), + amount + ); + } + + public entry fun redeem( + claimant: &signer, + access_code: vector, + market_address: address, + min_output_amount: u64 + ) acquires Vault { + + // Verify access code. + let access_code_hash = hash::sha3_256(access_code); + let vault_ref_mut = &mut Vault[@rewards]; + let manifest_ref_mut = &mut vault_ref_mut.manifest; + assert!(manifest_ref_mut.contains(access_code_hash), E_INVALID_ACCESS_CODE); + assert!( + *manifest_ref_mut.borrow(access_code_hash) == NIL, + E_ACCESS_CODE_ALREADY_REDEEMED + ); + + // Check vault balance. + let vault_signer_cap_ref = &vault_ref_mut.signer_capability; + let vault_address = account::get_signer_capability_address(vault_signer_cap_ref); + let octas_claim_amount = APT_PER_REDEMPTION * OCTAS_PER_APT; + assert!( + coin::balance(vault_address) >= octas_claim_amount, + E_VAULT_INSUFFICIENT_FUNDS + ); + + // Update manifest, transfer APT to claimaint. + let claimant_address = signer::address_of(claimant); + *manifest_ref_mut.borrow_mut(access_code_hash) = claimant_address; + let vault_signer = account::create_signer_with_capability(vault_signer_cap_ref); + aptos_account::transfer(&vault_signer, claimant_address, octas_claim_amount); + + // Invoke swap, emit event. + let swap_event = + emojicoin_dot_fun::simulate_swap( + claimant_address, + market_address, + octas_claim_amount, + false, + @integrator, + INTEGRATOR_FEE_RATE_BPS + ); + emojicoin_dot_fun::swap( + claimant, + market_address, + octas_claim_amount, + false, + @integrator, + INTEGRATOR_FEE_RATE_BPS, + min_output_amount + ); + event::emit( + EmojicoinDotFunAccessCodeRedemption { + claimant: claimant_address, + octas_claim_amount, + swap: swap_event + } + ); + + } + + public entry fun remove_access_code_hashes( + admin: &signer, access_code_hashes: vector> + ) acquires Vault { + let manifest_ref_mut = &mut borrow_vault_mut_checked(admin).manifest; + access_code_hashes.for_each(|access_code_hash| { + if (manifest_ref_mut.contains(access_code_hash) + && *manifest_ref_mut.borrow(access_code_hash) == NIL) { + manifest_ref_mut.remove(access_code_hash); + } + }); + } + + public entry fun remove_admin(admin: &signer, admin_to_remove: address) acquires Vault { + let admins_ref_mut = &mut borrow_vault_mut_checked(admin).admins; + let (admin_to_remove_is_admin, admin_to_remove_index) = + admins_ref_mut.index_of(&admin_to_remove); + assert!(admin_to_remove_is_admin, E_ADMIN_TO_REMOVE_IS_NOT_ADMIN); + admins_ref_mut.remove(admin_to_remove_index); + } + + fun init_module(rewards: &signer) { + let (vault_signer, signer_capability) = + account::create_resource_account(rewards, VAULT); + move_to( + rewards, + Vault { + signer_capability, + manifest: smart_table::new(), + admins: vector[signer::address_of(rewards)] + } + ); + coin::register(&vault_signer); + } + + inline fun borrow_vault_mut_checked(admin: &signer): &mut Vault { + let vault_ref_mut = &mut Vault[@rewards]; + assert!(vault_ref_mut.admins.contains(&signer::address_of(admin)), E_NOT_ADMIN); + vault_ref_mut } } From b4a7ddd4e063a765d2843e8be62ec476e64b598d Mon Sep 17 00:00:00 2001 From: alnoki <43892045+alnoki@users.noreply.github.com> Date: Thu, 31 Oct 2024 18:46:04 -0700 Subject: [PATCH 04/22] Address spellchecker --- src/move/rewards/sources/emojicoin_dot_fun_access_code.move | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/move/rewards/sources/emojicoin_dot_fun_access_code.move b/src/move/rewards/sources/emojicoin_dot_fun_access_code.move index 0b1a22804..2df5d17b9 100644 --- a/src/move/rewards/sources/emojicoin_dot_fun_access_code.move +++ b/src/move/rewards/sources/emojicoin_dot_fun_access_code.move @@ -1,3 +1,4 @@ +// cspell:word funder module rewards::emojicoin_dot_fun_access_code { use aptos_framework::account::{Self, SignerCapability}; @@ -31,7 +32,7 @@ module rewards::emojicoin_dot_fun_access_code { struct Vault has key { signer_capability: SignerCapability, - /// Map from SHA3-256 hash of access code to address of claimaint, `NIL` if unclaimed. + /// Map from SHA3-256 hash of access code to address of claimant, `NIL` if unclaimed. manifest: SmartTable, address>, /// Addresses of signers who can mutate the manifest. admins: vector
@@ -132,7 +133,7 @@ module rewards::emojicoin_dot_fun_access_code { E_VAULT_INSUFFICIENT_FUNDS ); - // Update manifest, transfer APT to claimaint. + // Update manifest, transfer APT to claimant. let claimant_address = signer::address_of(claimant); *manifest_ref_mut.borrow_mut(access_code_hash) = claimant_address; let vault_signer = account::create_signer_with_capability(vault_signer_cap_ref); From 80bc925598fea1c38046dfdfc37a45e15c9ffb1c Mon Sep 17 00:00:00 2001 From: alnoki <43892045+alnoki@users.noreply.github.com> Date: Fri, 1 Nov 2024 10:31:10 -0700 Subject: [PATCH 05/22] Add challenge prototyping --- src/move/rewards/sources/emojicoin_dot_fun_access_code.move | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/move/rewards/sources/emojicoin_dot_fun_access_code.move b/src/move/rewards/sources/emojicoin_dot_fun_access_code.move index 2df5d17b9..4dfa82ee7 100644 --- a/src/move/rewards/sources/emojicoin_dot_fun_access_code.move +++ b/src/move/rewards/sources/emojicoin_dot_fun_access_code.move @@ -32,7 +32,8 @@ module rewards::emojicoin_dot_fun_access_code { struct Vault has key { signer_capability: SignerCapability, - /// Map from SHA3-256 hash of access code to address of claimant, `NIL` if unclaimed. + /// Map from public key of an Aptos Connect escrow account the to address of claimant, + /// `NIL` if unclaimed. manifest: SmartTable, address>, /// Addresses of signers who can mutate the manifest. admins: vector
@@ -109,7 +110,7 @@ module rewards::emojicoin_dot_fun_access_code { public entry fun redeem( claimant: &signer, - access_code: vector, + claimant_address_bytes_signed_by_aptos_connect_escrow_account_private_key: vector, market_address: address, min_output_amount: u64 ) acquires Vault { From 8637879d81fb09bc29d710743abe9b29f13b7938 Mon Sep 17 00:00:00 2001 From: alnoki <43892045+alnoki@users.noreply.github.com> Date: Fri, 1 Nov 2024 15:04:48 -0700 Subject: [PATCH 06/22] Rename to qr_code module --- ..._dot_fun_access_code.move => emojicoin_dot_fun_qr_code.move} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename src/move/rewards/sources/{emojicoin_dot_fun_access_code.move => emojicoin_dot_fun_qr_code.move} (99%) diff --git a/src/move/rewards/sources/emojicoin_dot_fun_access_code.move b/src/move/rewards/sources/emojicoin_dot_fun_qr_code.move similarity index 99% rename from src/move/rewards/sources/emojicoin_dot_fun_access_code.move rename to src/move/rewards/sources/emojicoin_dot_fun_qr_code.move index 4dfa82ee7..2eb910c62 100644 --- a/src/move/rewards/sources/emojicoin_dot_fun_access_code.move +++ b/src/move/rewards/sources/emojicoin_dot_fun_qr_code.move @@ -1,5 +1,5 @@ // cspell:word funder -module rewards::emojicoin_dot_fun_access_code { +module rewards::emojicoin_dot_fun_qr_code { use aptos_framework::account::{Self, SignerCapability}; use aptos_framework::aptos_account; From ff654e736334810216265b8b6866c28509b03b0a Mon Sep 17 00:00:00 2001 From: alnoki <43892045+alnoki@users.noreply.github.com> Date: Fri, 1 Nov 2024 15:24:32 -0700 Subject: [PATCH 07/22] Refactor QR code to compile --- .../sources/emojicoin_dot_fun_qr_code.move | 296 +++++++++--------- 1 file changed, 152 insertions(+), 144 deletions(-) diff --git a/src/move/rewards/sources/emojicoin_dot_fun_qr_code.move b/src/move/rewards/sources/emojicoin_dot_fun_qr_code.move index 2eb910c62..5bcac6af1 100644 --- a/src/move/rewards/sources/emojicoin_dot_fun_qr_code.move +++ b/src/move/rewards/sources/emojicoin_dot_fun_qr_code.move @@ -6,6 +6,7 @@ module rewards::emojicoin_dot_fun_qr_code { use aptos_framework::aptos_coin::AptosCoin; use aptos_framework::coin; use aptos_framework::event; + use aptos_std::ed25519::{Self, ValidatedPublicKey}; use aptos_std::simple_map::SimpleMap; use aptos_std::smart_table::{Self, SmartTable}; use emojicoin_dot_fun::emojicoin_dot_fun::{Self, Swap}; @@ -13,28 +14,26 @@ module rewards::emojicoin_dot_fun_qr_code { use std::signer; use std::hash; - const APT_PER_REDEMPTION: u64 = 5; const INTEGRATOR_FEE_RATE_BPS: u8 = 100; const NIL: address = @0x0; - const OCTAS_PER_APT: u64 = 100_000_000; - const VAULT: vector = b"Access code vault"; + const OCTAS_PER_REDEMPTION: u64 = 10_000_000; + const VAULT: vector = b"QR code vault"; /// Signer does not correspond to admin. const E_NOT_ADMIN: u64 = 0; /// Admin to remove address does not correspond to admin. const E_ADMIN_TO_REMOVE_IS_NOT_ADMIN: u64 = 1; - /// Access code is not in manifest. - const E_INVALID_ACCESS_CODE: u64 = 2; - /// Access code has already been redeemed. - const E_ACCESS_CODE_ALREADY_REDEEMED: u64 = 3; + /// Public key of QR code private key is not in manifest. + const E_INVALID_QR_CODE: u64 = 2; + /// QR code has already been redeemed. + const E_QR_CODE_ALREADY_REDEEMED: u64 = 3; /// Vault has insufficient funds. const E_VAULT_INSUFFICIENT_FUNDS: u64 = 4; struct Vault has key { signer_capability: SignerCapability, - /// Map from public key of an Aptos Connect escrow account the to address of claimant, - /// `NIL` if unclaimed. - manifest: SmartTable, address>, + /// Map from public key of QR code private key to address of claimant, `NIL` if unclaimed. + manifest: SmartTable, /// Addresses of signers who can mutate the manifest. admins: vector
} @@ -46,141 +45,145 @@ module rewards::emojicoin_dot_fun_qr_code { swap: Swap } - #[view] - public fun access_code_hash_claimant_entry( - access_code_hash: vector - ): address acquires Vault { - *Vault[@rewards].manifest.borrow(access_code_hash) - } - - #[view] - public fun access_code_hashes(): vector> acquires Vault { - Vault[@rewards].manifest.keys() - } - - #[view] - public fun access_code_hashes_paginated( - starting_bucket_index: u64, starting_vector_index: u64, num_keys_to_get: u64 - ): (vector>, Option, Option) acquires Vault { - Vault[@rewards].manifest.keys_paginated( - starting_bucket_index, starting_vector_index, num_keys_to_get - ) - } - - #[view] - public fun is_access_code_hash_in_manifest( - access_code_hash: vector - ): bool acquires Vault { - Vault[@rewards].manifest.contains(access_code_hash) - } - - #[view] - public fun manifest_to_simple_map(): SimpleMap, address> acquires Vault { - Vault[@rewards].manifest.to_simple_map() - } - - #[view] - public fun vault_balance(): u64 acquires Vault { - coin::balance( - account::get_signer_capability_address(&Vault[@rewards].signer_capability) - ) - } - - public entry fun add_access_code_hashes( - admin: &signer, access_code_hashes: vector> - ) acquires Vault { - let manifest_ref_mut = &mut borrow_vault_mut_checked(admin).manifest; - access_code_hashes.for_each(|access_code_hash| { - manifest_ref_mut.add(access_code_hash, NIL); - }); - } + /* + #[view] + public fun access_code_hash_claimant_entry( + access_code_hash: vector + ): address acquires Vault { + *Vault[@rewards].manifest.borrow(access_code_hash) + } + + #[view] + public fun access_code_hashes(): vector> acquires Vault { + Vault[@rewards].manifest.keys() + } + + #[view] + public fun access_code_hashes_paginated( + starting_bucket_index: u64, starting_vector_index: u64, num_keys_to_get: u64 + ): (vector>, Option, Option) acquires Vault { + Vault[@rewards].manifest.keys_paginated( + starting_bucket_index, starting_vector_index, num_keys_to_get + ) + } + + #[view] + public fun is_access_code_hash_in_manifest( + access_code_hash: vector + ): bool acquires Vault { + Vault[@rewards].manifest.contains(access_code_hash) + } + + #[view] + public fun manifest_to_simple_map(): SimpleMap, address> acquires Vault { + Vault[@rewards].manifest.to_simple_map() + } + + #[view] + public fun vault_balance(): u64 acquires Vault { + coin::balance( + account::get_signer_capability_address(&Vault[@rewards].signer_capability) + ) + } + + public entry fun add_access_code_hashes( + admin: &signer, access_code_hashes: vector> + ) acquires Vault { + let manifest_ref_mut = &mut borrow_vault_mut_checked(admin).manifest; + access_code_hashes.for_each(|access_code_hash| { + manifest_ref_mut.add(access_code_hash, NIL); + }); + } + */ public entry fun add_admin(admin: &signer, new_admin: address) acquires Vault { let admins_ref_mut = &mut borrow_vault_mut_checked(admin).admins; admins_ref_mut.push_back(new_admin); } - public entry fun fund_vault(funder: &signer, amount: u64) acquires Vault { - aptos_account::transfer( - funder, - account::get_signer_capability_address(&Vault[@rewards].signer_capability), - amount - ); - } + /* + public entry fun fund_vault(funder: &signer, amount: u64) acquires Vault { + aptos_account::transfer( + funder, + account::get_signer_capability_address(&Vault[@rewards].signer_capability), + amount + ); + } + + public entry fun redeem( + claimant: &signer, + claimant_address_bytes_signed_by_aptos_connect_escrow_account_private_key: vector, + market_address: address, + min_output_amount: u64 + ) acquires Vault { + + // Verify access code. + let access_code_hash = hash::sha3_256(access_code); + let vault_ref_mut = &mut Vault[@rewards]; + let manifest_ref_mut = &mut vault_ref_mut.manifest; + assert!(manifest_ref_mut.contains(access_code_hash), E_INVALID_ACCESS_CODE); + assert!( + *manifest_ref_mut.borrow(access_code_hash) == NIL, + E_ACCESS_CODE_ALREADY_REDEEMED + ); - public entry fun redeem( - claimant: &signer, - claimant_address_bytes_signed_by_aptos_connect_escrow_account_private_key: vector, - market_address: address, - min_output_amount: u64 - ) acquires Vault { + // Check vault balance. + let vault_signer_cap_ref = &vault_ref_mut.signer_capability; + let vault_address = account::get_signer_capability_address(vault_signer_cap_ref); + let octas_claim_amount = APT_PER_REDEMPTION * OCTAS_PER_APT; + assert!( + coin::balance(vault_address) >= octas_claim_amount, + E_VAULT_INSUFFICIENT_FUNDS + ); - // Verify access code. - let access_code_hash = hash::sha3_256(access_code); - let vault_ref_mut = &mut Vault[@rewards]; - let manifest_ref_mut = &mut vault_ref_mut.manifest; - assert!(manifest_ref_mut.contains(access_code_hash), E_INVALID_ACCESS_CODE); - assert!( - *manifest_ref_mut.borrow(access_code_hash) == NIL, - E_ACCESS_CODE_ALREADY_REDEEMED - ); - - // Check vault balance. - let vault_signer_cap_ref = &vault_ref_mut.signer_capability; - let vault_address = account::get_signer_capability_address(vault_signer_cap_ref); - let octas_claim_amount = APT_PER_REDEMPTION * OCTAS_PER_APT; - assert!( - coin::balance(vault_address) >= octas_claim_amount, - E_VAULT_INSUFFICIENT_FUNDS - ); - - // Update manifest, transfer APT to claimant. - let claimant_address = signer::address_of(claimant); - *manifest_ref_mut.borrow_mut(access_code_hash) = claimant_address; - let vault_signer = account::create_signer_with_capability(vault_signer_cap_ref); - aptos_account::transfer(&vault_signer, claimant_address, octas_claim_amount); - - // Invoke swap, emit event. - let swap_event = - emojicoin_dot_fun::simulate_swap( - claimant_address, + // Update manifest, transfer APT to claimant. + let claimant_address = signer::address_of(claimant); + *manifest_ref_mut.borrow_mut(access_code_hash) = claimant_address; + let vault_signer = account::create_signer_with_capability(vault_signer_cap_ref); + aptos_account::transfer(&vault_signer, claimant_address, octas_claim_amount); + + // Invoke swap, emit event. + let swap_event = + emojicoin_dot_fun::simulate_swap( + claimant_address, + market_address, + octas_claim_amount, + false, + @integrator, + INTEGRATOR_FEE_RATE_BPS + ); + emojicoin_dot_fun::swap( + claimant, market_address, octas_claim_amount, false, @integrator, - INTEGRATOR_FEE_RATE_BPS + INTEGRATOR_FEE_RATE_BPS, + min_output_amount + ); + event::emit( + EmojicoinDotFunAccessCodeRedemption { + claimant: claimant_address, + octas_claim_amount, + swap: swap_event + } ); - emojicoin_dot_fun::swap( - claimant, - market_address, - octas_claim_amount, - false, - @integrator, - INTEGRATOR_FEE_RATE_BPS, - min_output_amount - ); - event::emit( - EmojicoinDotFunAccessCodeRedemption { - claimant: claimant_address, - octas_claim_amount, - swap: swap_event - } - ); - - } - - public entry fun remove_access_code_hashes( - admin: &signer, access_code_hashes: vector> - ) acquires Vault { - let manifest_ref_mut = &mut borrow_vault_mut_checked(admin).manifest; - access_code_hashes.for_each(|access_code_hash| { - if (manifest_ref_mut.contains(access_code_hash) - && *manifest_ref_mut.borrow(access_code_hash) == NIL) { - manifest_ref_mut.remove(access_code_hash); - } - }); - } + } + + public entry fun remove_access_code_hashes( + admin: &signer, access_code_hashes: vector> + ) acquires Vault { + let manifest_ref_mut = &mut borrow_vault_mut_checked(admin).manifest; + access_code_hashes.for_each(|access_code_hash| { + if (manifest_ref_mut.contains(access_code_hash) + && *manifest_ref_mut.borrow(access_code_hash) == NIL) { + manifest_ref_mut.remove(access_code_hash); + } + }); + } + + */ public entry fun remove_admin(admin: &signer, admin_to_remove: address) acquires Vault { let admins_ref_mut = &mut borrow_vault_mut_checked(admin).admins; let (admin_to_remove_is_admin, admin_to_remove_index) = @@ -189,19 +192,24 @@ module rewards::emojicoin_dot_fun_qr_code { admins_ref_mut.remove(admin_to_remove_index); } - fun init_module(rewards: &signer) { - let (vault_signer, signer_capability) = - account::create_resource_account(rewards, VAULT); - move_to( - rewards, - Vault { - signer_capability, - manifest: smart_table::new(), - admins: vector[signer::address_of(rewards)] - } - ); - coin::register(&vault_signer); - } + /* + + + fun init_module(rewards: &signer) { + let (vault_signer, signer_capability) = + account::create_resource_account(rewards, VAULT); + move_to( + rewards, + Vault { + signer_capability, + manifest: smart_table::new(), + admins: vector[signer::address_of(rewards)] + } + ); + coin::register(&vault_signer); + } + + */ inline fun borrow_vault_mut_checked(admin: &signer): &mut Vault { let vault_ref_mut = &mut Vault[@rewards]; From b68ffc8f10933a276cbc6e7db99cf78909dcd332 Mon Sep 17 00:00:00 2001 From: alnoki <43892045+alnoki@users.noreply.github.com> Date: Fri, 1 Nov 2024 15:56:10 -0700 Subject: [PATCH 08/22] Add public key validation --- .../sources/emojicoin_dot_fun_qr_code.move | 57 ++++++++++--------- 1 file changed, 29 insertions(+), 28 deletions(-) diff --git a/src/move/rewards/sources/emojicoin_dot_fun_qr_code.move b/src/move/rewards/sources/emojicoin_dot_fun_qr_code.move index 5bcac6af1..9d3fe6e62 100644 --- a/src/move/rewards/sources/emojicoin_dot_fun_qr_code.move +++ b/src/move/rewards/sources/emojicoin_dot_fun_qr_code.move @@ -10,7 +10,7 @@ module rewards::emojicoin_dot_fun_qr_code { use aptos_std::simple_map::SimpleMap; use aptos_std::smart_table::{Self, SmartTable}; use emojicoin_dot_fun::emojicoin_dot_fun::{Self, Swap}; - use std::option::Option; + use std::option::{Self, Option}; use std::signer; use std::hash; @@ -29,6 +29,8 @@ module rewards::emojicoin_dot_fun_qr_code { const E_QR_CODE_ALREADY_REDEEMED: u64 = 3; /// Vault has insufficient funds. const E_VAULT_INSUFFICIENT_FUNDS: u64 = 4; + /// Public key does not pass Ed25519 validation. + const E_INVALID_PUBLIC_KEY: u64 = 5; struct Vault has key { signer_capability: SignerCapability, @@ -39,7 +41,7 @@ module rewards::emojicoin_dot_fun_qr_code { } #[event] - struct EmojicoinDotFunAccessCodeRedemption has copy, drop, store { + struct EmojicoinDotFunQRcodeRedemption has copy, drop, store { claimant: address, octas_claim_amount: u64, swap: Swap @@ -86,16 +88,20 @@ module rewards::emojicoin_dot_fun_qr_code { ) } - public entry fun add_access_code_hashes( - admin: &signer, access_code_hashes: vector> - ) acquires Vault { - let manifest_ref_mut = &mut borrow_vault_mut_checked(admin).manifest; - access_code_hashes.for_each(|access_code_hash| { - manifest_ref_mut.add(access_code_hash, NIL); - }); - } */ + public entry fun add_public_keys( + admin: &signer, public_keys: vector> + ) acquires Vault { + let manifest_ref_mut = &mut borrow_vault_mut_checked(admin).manifest; + public_keys.for_each(|public_key_bytes| { + let validated_public_key_option = + ed25519::new_validated_public_key_from_bytes(public_key_bytes); + assert!(option::is_some(&validated_public_key_option), E_INVALID_PUBLIC_KEY); + manifest_ref_mut.add(option::destroy_some(validated_public_key_option), NIL); + }); + } + public entry fun add_admin(admin: &signer, new_admin: address) acquires Vault { let admins_ref_mut = &mut borrow_vault_mut_checked(admin).admins; admins_ref_mut.push_back(new_admin); @@ -192,24 +198,19 @@ module rewards::emojicoin_dot_fun_qr_code { admins_ref_mut.remove(admin_to_remove_index); } - /* - - - fun init_module(rewards: &signer) { - let (vault_signer, signer_capability) = - account::create_resource_account(rewards, VAULT); - move_to( - rewards, - Vault { - signer_capability, - manifest: smart_table::new(), - admins: vector[signer::address_of(rewards)] - } - ); - coin::register(&vault_signer); - } - - */ + fun init_module(rewards: &signer) { + let (vault_signer, signer_capability) = + account::create_resource_account(rewards, VAULT); + move_to( + rewards, + Vault { + signer_capability, + manifest: smart_table::new(), + admins: vector[signer::address_of(rewards)] + } + ); + coin::register(&vault_signer); + } inline fun borrow_vault_mut_checked(admin: &signer): &mut Vault { let vault_ref_mut = &mut Vault[@rewards]; From 6410dcdf762fd95e9159c18ea573e5071e385b47 Mon Sep 17 00:00:00 2001 From: alnoki <43892045+alnoki@users.noreply.github.com> Date: Fri, 1 Nov 2024 16:54:05 -0700 Subject: [PATCH 09/22] Add QR code redemption schema --- .../sources/emojicoin_dot_fun_qr_code.move | 197 ++++++++++-------- 1 file changed, 108 insertions(+), 89 deletions(-) diff --git a/src/move/rewards/sources/emojicoin_dot_fun_qr_code.move b/src/move/rewards/sources/emojicoin_dot_fun_qr_code.move index 9d3fe6e62..48316abd8 100644 --- a/src/move/rewards/sources/emojicoin_dot_fun_qr_code.move +++ b/src/move/rewards/sources/emojicoin_dot_fun_qr_code.move @@ -11,12 +11,12 @@ module rewards::emojicoin_dot_fun_qr_code { use aptos_std::smart_table::{Self, SmartTable}; use emojicoin_dot_fun::emojicoin_dot_fun::{Self, Swap}; use std::option::{Self, Option}; + use std::bcs; use std::signer; - use std::hash; const INTEGRATOR_FEE_RATE_BPS: u8 = 100; const NIL: address = @0x0; - const OCTAS_PER_REDEMPTION: u64 = 10_000_000; + const DEFAULT_CLAIM_AMOUNT: u64 = 10_000_000; const VAULT: vector = b"QR code vault"; /// Signer does not correspond to admin. @@ -31,19 +31,22 @@ module rewards::emojicoin_dot_fun_qr_code { const E_VAULT_INSUFFICIENT_FUNDS: u64 = 4; /// Public key does not pass Ed25519 validation. const E_INVALID_PUBLIC_KEY: u64 = 5; + /// Signature does not pass Ed25519 validation. + const E_INVALID_SIGNATURE: u64 = 6; struct Vault has key { - signer_capability: SignerCapability, + /// Addresses of signers who can mutate the manifest. + admins: vector
, + claim_amount: u64, /// Map from public key of QR code private key to address of claimant, `NIL` if unclaimed. manifest: SmartTable, - /// Addresses of signers who can mutate the manifest. - admins: vector
+ signer_capability: SignerCapability } #[event] - struct EmojicoinDotFunQRcodeRedemption has copy, drop, store { + struct EmojicoinDotFunQRCodeRedemption has copy, drop, store { claimant: address, - octas_claim_amount: u64, + claim_amount: u64, swap: Swap } @@ -91,105 +94,100 @@ module rewards::emojicoin_dot_fun_qr_code { */ public entry fun add_public_keys( - admin: &signer, public_keys: vector> + admin: &signer, public_keys_as_bytes: vector> ) acquires Vault { let manifest_ref_mut = &mut borrow_vault_mut_checked(admin).manifest; - public_keys.for_each(|public_key_bytes| { - let validated_public_key_option = - ed25519::new_validated_public_key_from_bytes(public_key_bytes); - assert!(option::is_some(&validated_public_key_option), E_INVALID_PUBLIC_KEY); - manifest_ref_mut.add(option::destroy_some(validated_public_key_option), NIL); + public_keys_as_bytes.for_each(|public_key_bytes| { + manifest_ref_mut.add(validate_public_key_bytes(public_key_bytes), NIL); }); } + public entry fun fund_vault(funder: &signer, n_claims: u64) acquires Vault { + let amount = n_claims * Vault[@rewards].claim_amount; + aptos_account::transfer( + funder, + account::get_signer_capability_address(&Vault[@rewards].signer_capability), + amount + ); + } + public entry fun add_admin(admin: &signer, new_admin: address) acquires Vault { let admins_ref_mut = &mut borrow_vault_mut_checked(admin).admins; admins_ref_mut.push_back(new_admin); } - /* - public entry fun fund_vault(funder: &signer, amount: u64) acquires Vault { - aptos_account::transfer( - funder, - account::get_signer_capability_address(&Vault[@rewards].signer_capability), - amount - ); - } + public entry fun redeem( + claimant: &signer, + signature_bytes: vector, + public_key_bytes: vector, + market_address: address, + min_output_amount: u64 + ) acquires Vault { - public entry fun redeem( - claimant: &signer, - claimant_address_bytes_signed_by_aptos_connect_escrow_account_private_key: vector, - market_address: address, - min_output_amount: u64 - ) acquires Vault { - - // Verify access code. - let access_code_hash = hash::sha3_256(access_code); - let vault_ref_mut = &mut Vault[@rewards]; - let manifest_ref_mut = &mut vault_ref_mut.manifest; - assert!(manifest_ref_mut.contains(access_code_hash), E_INVALID_ACCESS_CODE); - assert!( - *manifest_ref_mut.borrow(access_code_hash) == NIL, - E_ACCESS_CODE_ALREADY_REDEEMED - ); + // Verify signature. + let validated_public_key = validate_public_key_bytes(public_key_bytes); + let claimant_address = signer::address_of(claimant); + assert!( + ed25519::signature_verify_strict( + &ed25519::new_signature_from_bytes(signature_bytes), + &ed25519::public_key_to_unvalidated(&validated_public_key), + bcs::to_bytes(&claimant_address) + ), + E_INVALID_SIGNATURE + ); - // Check vault balance. - let vault_signer_cap_ref = &vault_ref_mut.signer_capability; - let vault_address = account::get_signer_capability_address(vault_signer_cap_ref); - let octas_claim_amount = APT_PER_REDEMPTION * OCTAS_PER_APT; - assert!( - coin::balance(vault_address) >= octas_claim_amount, - E_VAULT_INSUFFICIENT_FUNDS - ); + // Verify public key is eligible for claim. + let vault_ref_mut = &mut Vault[@rewards]; + let manifest_ref_mut = &mut vault_ref_mut.manifest; + assert!(manifest_ref_mut.contains(validated_public_key), E_INVALID_QR_CODE); + assert!( + *manifest_ref_mut.borrow(validated_public_key) == NIL, + E_QR_CODE_ALREADY_REDEEMED + ); + + // Check vault balance. + let vault_signer_cap_ref = &vault_ref_mut.signer_capability; + let vault_address = account::get_signer_capability_address(vault_signer_cap_ref); + let claim_amount = vault_ref_mut.claim_amount; + assert!( + coin::balance(vault_address) >= claim_amount, + E_VAULT_INSUFFICIENT_FUNDS + ); + + // Update manifest, transfer APT to claimant. + *manifest_ref_mut.borrow_mut(validated_public_key) = claimant_address; + let vault_signer = account::create_signer_with_capability(vault_signer_cap_ref); + aptos_account::transfer(&vault_signer, claimant_address, claim_amount); - // Update manifest, transfer APT to claimant. - let claimant_address = signer::address_of(claimant); - *manifest_ref_mut.borrow_mut(access_code_hash) = claimant_address; - let vault_signer = account::create_signer_with_capability(vault_signer_cap_ref); - aptos_account::transfer(&vault_signer, claimant_address, octas_claim_amount); - - // Invoke swap, emit event. - let swap_event = - emojicoin_dot_fun::simulate_swap( - claimant_address, - market_address, - octas_claim_amount, - false, - @integrator, - INTEGRATOR_FEE_RATE_BPS - ); - emojicoin_dot_fun::swap( - claimant, + // Invoke swap, emit event. + let swap_event = + emojicoin_dot_fun::simulate_swap( + claimant_address, market_address, - octas_claim_amount, + claim_amount, false, @integrator, - INTEGRATOR_FEE_RATE_BPS, - min_output_amount + INTEGRATOR_FEE_RATE_BPS ); - event::emit( - EmojicoinDotFunAccessCodeRedemption { - claimant: claimant_address, - octas_claim_amount, - swap: swap_event - } - ); - - } + emojicoin_dot_fun::swap( + claimant, + market_address, + claim_amount, + false, + @integrator, + INTEGRATOR_FEE_RATE_BPS, + min_output_amount + ); + event::emit( + EmojicoinDotFunQRCodeRedemption { + claimant: claimant_address, + claim_amount, + swap: swap_event + } + ); - public entry fun remove_access_code_hashes( - admin: &signer, access_code_hashes: vector> - ) acquires Vault { - let manifest_ref_mut = &mut borrow_vault_mut_checked(admin).manifest; - access_code_hashes.for_each(|access_code_hash| { - if (manifest_ref_mut.contains(access_code_hash) - && *manifest_ref_mut.borrow(access_code_hash) == NIL) { - manifest_ref_mut.remove(access_code_hash); - } - }); - } + } - */ public entry fun remove_admin(admin: &signer, admin_to_remove: address) acquires Vault { let admins_ref_mut = &mut borrow_vault_mut_checked(admin).admins; let (admin_to_remove_is_admin, admin_to_remove_index) = @@ -198,15 +196,29 @@ module rewards::emojicoin_dot_fun_qr_code { admins_ref_mut.remove(admin_to_remove_index); } + public entry fun remove_public_keys( + admin: &signer, public_keys_as_bytes: vector> + ) acquires Vault { + let manifest_ref_mut = &mut borrow_vault_mut_checked(admin).manifest; + public_keys_as_bytes.for_each(|public_key_bytes| { + let validated_public_key = validate_public_key_bytes(public_key_bytes); + if (manifest_ref_mut.contains(validated_public_key) + && *manifest_ref_mut.borrow(validated_public_key) == NIL) { + manifest_ref_mut.remove(validated_public_key); + } + }); + } + fun init_module(rewards: &signer) { let (vault_signer, signer_capability) = account::create_resource_account(rewards, VAULT); move_to( rewards, Vault { - signer_capability, + admins: vector[signer::address_of(rewards)], + claim_amount: DEFAULT_CLAIM_AMOUNT, manifest: smart_table::new(), - admins: vector[signer::address_of(rewards)] + signer_capability } ); coin::register(&vault_signer); @@ -217,4 +229,11 @@ module rewards::emojicoin_dot_fun_qr_code { assert!(vault_ref_mut.admins.contains(&signer::address_of(admin)), E_NOT_ADMIN); vault_ref_mut } + + inline fun validate_public_key_bytes(public_key_bytes: vector): ValidatedPublicKey { + let validated_public_key_option = + ed25519::new_validated_public_key_from_bytes(public_key_bytes); + assert!(option::is_some(&validated_public_key_option), E_INVALID_PUBLIC_KEY); + option::destroy_some(validated_public_key_option) + } } From 0b5b2dddf4d3dda03771ff0a715530f4d9f6ec04 Mon Sep 17 00:00:00 2001 From: alnoki <43892045+alnoki@users.noreply.github.com> Date: Fri, 1 Nov 2024 17:14:27 -0700 Subject: [PATCH 10/22] Update redemption process, add view funcs --- .../sources/emojicoin_dot_fun_qr_code.move | 120 +++++++++++------- 1 file changed, 73 insertions(+), 47 deletions(-) diff --git a/src/move/rewards/sources/emojicoin_dot_fun_qr_code.move b/src/move/rewards/sources/emojicoin_dot_fun_qr_code.move index 48316abd8..b73a9fc32 100644 --- a/src/move/rewards/sources/emojicoin_dot_fun_qr_code.move +++ b/src/move/rewards/sources/emojicoin_dot_fun_qr_code.move @@ -37,9 +37,11 @@ module rewards::emojicoin_dot_fun_qr_code { struct Vault has key { /// Addresses of signers who can mutate the manifest. admins: vector
, + /// In octas. claim_amount: u64, /// Map from public key of QR code private key to address of claimant, `NIL` if unclaimed. manifest: SmartTable, + /// Approves transfers from the vault. signer_capability: SignerCapability } @@ -50,48 +52,61 @@ module rewards::emojicoin_dot_fun_qr_code { swap: Swap } - /* - #[view] - public fun access_code_hash_claimant_entry( - access_code_hash: vector - ): address acquires Vault { - *Vault[@rewards].manifest.borrow(access_code_hash) - } - - #[view] - public fun access_code_hashes(): vector> acquires Vault { - Vault[@rewards].manifest.keys() - } - - #[view] - public fun access_code_hashes_paginated( - starting_bucket_index: u64, starting_vector_index: u64, num_keys_to_get: u64 - ): (vector>, Option, Option) acquires Vault { - Vault[@rewards].manifest.keys_paginated( - starting_bucket_index, starting_vector_index, num_keys_to_get - ) - } - - #[view] - public fun is_access_code_hash_in_manifest( - access_code_hash: vector - ): bool acquires Vault { - Vault[@rewards].manifest.contains(access_code_hash) - } - - #[view] - public fun manifest_to_simple_map(): SimpleMap, address> acquires Vault { - Vault[@rewards].manifest.to_simple_map() - } - - #[view] - public fun vault_balance(): u64 acquires Vault { - coin::balance( - account::get_signer_capability_address(&Vault[@rewards].signer_capability) - ) - } - - */ + #[view] + public fun admins(): vector
acquires Vault { + Vault[@rewards].admins + } + + #[view] + public fun claim_amount(): u64 acquires Vault { + Vault[@rewards].claim_amount + } + + #[view] + public fun public_key_is_in_manifest(public_key_bytes: vector): bool acquires Vault { + Vault[@rewards].manifest.contains(validate_public_key_bytes(public_key_bytes)) + } + + #[view] + public fun public_key_claimant(public_key_bytes: vector): address acquires Vault { + *Vault[@rewards].manifest.borrow(validate_public_key_bytes(public_key_bytes)) + } + + #[view] + public fun public_keys(): vector acquires Vault { + Vault[@rewards].manifest.keys() + } + + #[view] + public fun public_keys_paginated( + starting_bucket_index: u64, starting_vector_index: u64, num_public_keys_to_get: u64 + ): (vector, Option, Option) acquires Vault { + Vault[@rewards].manifest.keys_paginated( + starting_bucket_index, starting_vector_index, num_public_keys_to_get + ) + } + + #[view] + public fun manifest_to_simple_map(): SimpleMap acquires Vault { + Vault[@rewards].manifest.to_simple_map() + } + + #[view] + public fun vault_balance(): u64 acquires Vault { + coin::balance( + account::get_signer_capability_address(&Vault[@rewards].signer_capability) + ) + } + + #[view] + public fun vault_signer_address(): address acquires Vault { + account::get_signer_capability_address(&Vault[@rewards].signer_capability) + } + + public entry fun add_admin(admin: &signer, new_admin: address) acquires Vault { + let admins_ref_mut = &mut borrow_vault_mut_checked(admin).admins; + admins_ref_mut.push_back(new_admin); + } public entry fun add_public_keys( admin: &signer, public_keys_as_bytes: vector> @@ -111,11 +126,8 @@ module rewards::emojicoin_dot_fun_qr_code { ); } - public entry fun add_admin(admin: &signer, new_admin: address) acquires Vault { - let admins_ref_mut = &mut borrow_vault_mut_checked(admin).admins; - admins_ref_mut.push_back(new_admin); - } - + /// `signature_bytes` is generated by signing the claimant's address with the QR code private + /// key, and can be verified by `public_key_bytes`, the corresponding QR code public key. public entry fun redeem( claimant: &signer, signature_bytes: vector, @@ -209,6 +221,20 @@ module rewards::emojicoin_dot_fun_qr_code { }); } + public entry fun set_claim_amount(admin: &signer, claim_amount: u64) acquires Vault { + borrow_vault_mut_checked(admin).claim_amount = claim_amount; + } + + public entry fun withdraw_from_vault(admin: &signer, amount: u64) acquires Vault { + aptos_account::transfer( + &account::create_signer_with_capability( + &borrow_vault_mut_checked(admin).signer_capability + ), + signer::address_of(admin), + amount + ); + } + fun init_module(rewards: &signer) { let (vault_signer, signer_capability) = account::create_resource_account(rewards, VAULT); From f131733a49b9e6c89b7770a0e95a9f919570acd6 Mon Sep 17 00:00:00 2001 From: alnoki <43892045+alnoki@users.noreply.github.com> Date: Fri, 1 Nov 2024 17:38:48 -0700 Subject: [PATCH 11/22] Rename to claim link --- .../sources/emojicoin_dot_fun_claim_link.move | 279 ++++++++++++++++++ 1 file changed, 279 insertions(+) create mode 100644 src/move/rewards/sources/emojicoin_dot_fun_claim_link.move diff --git a/src/move/rewards/sources/emojicoin_dot_fun_claim_link.move b/src/move/rewards/sources/emojicoin_dot_fun_claim_link.move new file mode 100644 index 000000000..3e23c4674 --- /dev/null +++ b/src/move/rewards/sources/emojicoin_dot_fun_claim_link.move @@ -0,0 +1,279 @@ +// cspell:word funder +module rewards::emojicoin_dot_fun_claim_link { + + use aptos_framework::account::{Self, SignerCapability}; + use aptos_framework::aptos_account; + use aptos_framework::aptos_coin::AptosCoin; + use aptos_framework::coin; + use aptos_framework::event; + use aptos_std::ed25519::{Self, ValidatedPublicKey}; + use aptos_std::simple_map::SimpleMap; + use aptos_std::smart_table::{Self, SmartTable}; + use emojicoin_dot_fun::emojicoin_dot_fun::{Self, Swap}; + use std::option::{Self, Option}; + use std::bcs; + use std::signer; + + const INTEGRATOR_FEE_RATE_BPS: u8 = 100; + const NIL: address = @0x0; + const DEFAULT_CLAIM_AMOUNT: u64 = 10_000_000; + const VAULT: vector = b"Claim link vault"; + + /// Signer does not correspond to admin. + const E_NOT_ADMIN: u64 = 0; + /// Admin to remove address does not correspond to admin. + const E_ADMIN_TO_REMOVE_IS_NOT_ADMIN: u64 = 1; + /// Public key of claim link private key is not in manifest. + const E_INVALID_CLAIM_LINK: u64 = 2; + /// Claim link has already been claimed. + const E_CLAIM_LINK_ALREADY_CLAIMED: u64 = 3; + /// Vault has insufficient funds. + const E_VAULT_INSUFFICIENT_FUNDS: u64 = 4; + /// Public key does not pass Ed25519 validation. + const E_INVALID_PUBLIC_KEY: u64 = 5; + /// Signature does not pass Ed25519 validation. + const E_INVALID_SIGNATURE: u64 = 6; + + struct Vault has key { + /// Addresses of signers who can mutate the manifest. + admins: vector
, + /// In octas. + claim_amount: u64, + /// Map from claim link public key to address of claimant, `NIL` if unclaimed. + manifest: SmartTable, + /// Approves transfers from the vault. + signer_capability: SignerCapability + } + + #[event] + struct EmojicoinDotFunClaimLinkRedemption has copy, drop, store { + claimant: address, + claim_amount: u64, + swap: Swap + } + + #[view] + public fun admins(): vector
acquires Vault { + Vault[@rewards].admins + } + + #[view] + public fun claim_amount(): u64 acquires Vault { + Vault[@rewards].claim_amount + } + + #[view] + public fun public_key_is_in_manifest(public_key_bytes: vector): bool acquires Vault { + Vault[@rewards].manifest.contains(validate_public_key_bytes(public_key_bytes)) + } + + #[view] + public fun public_key_claimant(public_key_bytes: vector): address acquires Vault { + *Vault[@rewards].manifest.borrow(validate_public_key_bytes(public_key_bytes)) + } + + #[view] + public fun public_keys(): vector acquires Vault { + Vault[@rewards].manifest.keys() + } + + #[view] + public fun public_keys_paginated( + starting_bucket_index: u64, starting_vector_index: u64, num_public_keys_to_get: u64 + ): (vector, Option, Option) acquires Vault { + Vault[@rewards].manifest.keys_paginated( + starting_bucket_index, starting_vector_index, num_public_keys_to_get + ) + } + + #[view] + public fun manifest_to_simple_map(): SimpleMap acquires Vault { + Vault[@rewards].manifest.to_simple_map() + } + + #[view] + public fun vault_balance(): u64 acquires Vault { + coin::balance( + account::get_signer_capability_address(&Vault[@rewards].signer_capability) + ) + } + + #[view] + public fun vault_signer_address(): address acquires Vault { + account::get_signer_capability_address(&Vault[@rewards].signer_capability) + } + + public entry fun add_admin(admin: &signer, new_admin: address) acquires Vault { + let admins_ref_mut = &mut borrow_vault_mut_checked(admin).admins; + admins_ref_mut.push_back(new_admin); + } + + public entry fun add_public_keys( + admin: &signer, public_keys_as_bytes: vector> + ) acquires Vault { + let manifest_ref_mut = &mut borrow_vault_mut_checked(admin).manifest; + public_keys_as_bytes.for_each(|public_key_bytes| { + manifest_ref_mut.add(validate_public_key_bytes(public_key_bytes), NIL); + }); + } + + public entry fun fund_vault(funder: &signer, n_claims: u64) acquires Vault { + let amount = n_claims * Vault[@rewards].claim_amount; + aptos_account::transfer( + funder, + account::get_signer_capability_address(&Vault[@rewards].signer_capability), + amount + ); + } + + /// `signature_bytes` is generated by signing the claimant's address with the claim link private + /// key, and can be verified by `public_key_bytes`, the corresponding claim link public key. + public entry fun redeem( + claimant: &signer, + signature_bytes: vector, + public_key_bytes: vector, + market_address: address, + min_output_amount: u64 + ) acquires Vault { + + // Verify signature. + let validated_public_key = validate_public_key_bytes(public_key_bytes); + let claimant_address = signer::address_of(claimant); + assert!( + ed25519::signature_verify_strict( + &ed25519::new_signature_from_bytes(signature_bytes), + &ed25519::public_key_to_unvalidated(&validated_public_key), + bcs::to_bytes(&claimant_address) + ), + E_INVALID_SIGNATURE + ); + + // Verify public key is eligible for claim. + let vault_ref_mut = &mut Vault[@rewards]; + let manifest_ref_mut = &mut vault_ref_mut.manifest; + assert!(manifest_ref_mut.contains(validated_public_key), E_INVALID_CLAIM_LINK); + assert!( + *manifest_ref_mut.borrow(validated_public_key) == NIL, + E_CLAIM_LINK_ALREADY_CLAIMED + ); + + // Check vault balance. + let vault_signer_cap_ref = &vault_ref_mut.signer_capability; + let vault_address = account::get_signer_capability_address(vault_signer_cap_ref); + let claim_amount = vault_ref_mut.claim_amount; + assert!( + coin::balance(vault_address) >= claim_amount, + E_VAULT_INSUFFICIENT_FUNDS + ); + + // Update manifest, transfer APT to claimant. + *manifest_ref_mut.borrow_mut(validated_public_key) = claimant_address; + let vault_signer = account::create_signer_with_capability(vault_signer_cap_ref); + aptos_account::transfer(&vault_signer, claimant_address, claim_amount); + + // Invoke swap, emit event. + let swap_event = + emojicoin_dot_fun::simulate_swap( + claimant_address, + market_address, + claim_amount, + false, + @integrator, + INTEGRATOR_FEE_RATE_BPS + ); + emojicoin_dot_fun::swap( + claimant, + market_address, + claim_amount, + false, + @integrator, + INTEGRATOR_FEE_RATE_BPS, + min_output_amount + ); + event::emit( + EmojicoinDotFunClaimLinkRedemption { + claimant: claimant_address, + claim_amount, + swap: swap_event + } + ); + + } + + public entry fun remove_admin(admin: &signer, admin_to_remove: address) acquires Vault { + let admins_ref_mut = &mut borrow_vault_mut_checked(admin).admins; + let (admin_to_remove_is_admin, admin_to_remove_index) = + admins_ref_mut.index_of(&admin_to_remove); + assert!(admin_to_remove_is_admin, E_ADMIN_TO_REMOVE_IS_NOT_ADMIN); + admins_ref_mut.remove(admin_to_remove_index); + } + + public entry fun remove_public_keys( + admin: &signer, public_keys_as_bytes: vector> + ) acquires Vault { + let manifest_ref_mut = &mut borrow_vault_mut_checked(admin).manifest; + public_keys_as_bytes.for_each(|public_key_bytes| { + let validated_public_key = validate_public_key_bytes(public_key_bytes); + if (manifest_ref_mut.contains(validated_public_key) + && *manifest_ref_mut.borrow(validated_public_key) == NIL) { + manifest_ref_mut.remove(validated_public_key); + } + }); + } + + public entry fun set_claim_amount(admin: &signer, claim_amount: u64) acquires Vault { + borrow_vault_mut_checked(admin).claim_amount = claim_amount; + } + + public entry fun withdraw_from_vault(admin: &signer, amount: u64) acquires Vault { + aptos_account::transfer( + &account::create_signer_with_capability( + &borrow_vault_mut_checked(admin).signer_capability + ), + signer::address_of(admin), + amount + ); + } + + fun init_module(rewards: &signer) { + let (vault_signer, signer_capability) = + account::create_resource_account(rewards, VAULT); + move_to( + rewards, + Vault { + admins: vector[signer::address_of(rewards)], + claim_amount: DEFAULT_CLAIM_AMOUNT, + manifest: smart_table::new(), + signer_capability + } + ); + coin::register(&vault_signer); + } + + inline fun borrow_vault_mut_checked(admin: &signer): &mut Vault { + let vault_ref_mut = &mut Vault[@rewards]; + assert!(vault_ref_mut.admins.contains(&signer::address_of(admin)), E_NOT_ADMIN); + vault_ref_mut + } + + inline fun validate_public_key_bytes(public_key_bytes: vector): ValidatedPublicKey { + let validated_public_key_option = + ed25519::new_validated_public_key_from_bytes(public_key_bytes); + assert!(option::is_some(&validated_public_key_option), E_INVALID_PUBLIC_KEY); + option::destroy_some(validated_public_key_option) + } + + #[test_only] + use black_heart_market::coin_factory::{ + Emojicoin as BlackHeartEmojicoin, + EmojicoinLP as BlackHeartEmojicoinLP + }; + + #[test] + fun test_core_flow() { + // Initialize black cat market, have it undergo state transition. + emojicoin_dot_fun::tests::init_package_then_exact_transition(); + + // Get claim link private, public keys. + } +} From 90ab8291843f94f599c60f1b0a304c064bd0f827 Mon Sep 17 00:00:00 2001 From: alnoki <43892045+alnoki@users.noreply.github.com> Date: Fri, 1 Nov 2024 17:59:06 -0700 Subject: [PATCH 12/22] Add general flow test --- src/move/rewards/Move.toml | 3 + .../sources/emojicoin_dot_fun_claim_link.move | 64 ++++- .../sources/emojicoin_dot_fun_qr_code.move | 265 ------------------ 3 files changed, 63 insertions(+), 269 deletions(-) delete mode 100644 src/move/rewards/sources/emojicoin_dot_fun_qr_code.move diff --git a/src/move/rewards/Move.toml b/src/move/rewards/Move.toml index 998e5be3e..9d37aa13e 100644 --- a/src/move/rewards/Move.toml +++ b/src/move/rewards/Move.toml @@ -11,6 +11,9 @@ emojicoin_dot_fun = "0xc0de" integrator = "0xccc" rewards = "0xddd" +[dev-dependencies.BlackHeartCoinFactory] +local = "../test_coin_factories/black_heart" + [package] authors = ["Econia Labs (developers@econialabs.com)"] name = "EmojicoinDotFunRewards" diff --git a/src/move/rewards/sources/emojicoin_dot_fun_claim_link.move b/src/move/rewards/sources/emojicoin_dot_fun_claim_link.move index 3e23c4674..106a7854c 100644 --- a/src/move/rewards/sources/emojicoin_dot_fun_claim_link.move +++ b/src/move/rewards/sources/emojicoin_dot_fun_claim_link.move @@ -264,16 +264,72 @@ module rewards::emojicoin_dot_fun_claim_link { } #[test_only] - use black_heart_market::coin_factory::{ - Emojicoin as BlackHeartEmojicoin, - EmojicoinLP as BlackHeartEmojicoinLP + use aptos_framework::account::{create_signer_for_test as get_signer}; + #[test_only] + use black_cat_market::coin_factory::{ + Emojicoin as BlackCatEmojicoin, + EmojicoinLP as BlackCatEmojicoinLP }; + #[test_only] + const CLAIMANT: address = @0x1111; + #[test] - fun test_core_flow() { + fun test_general_flow() acquires Vault { // Initialize black cat market, have it undergo state transition. emojicoin_dot_fun::tests::init_package_then_exact_transition(); // Get claim link private, public keys. + let (claim_link_private_key, claim_link_validated_public_key) = + ed25519::generate_keys(); + let claim_link_validated_public_key_bytes = + ed25519::validated_public_key_to_bytes(&claim_link_validated_public_key); + + // Initialize module, manifest, vault. + let rewards_signer = get_signer(@rewards); + init_module(&rewards_signer); + add_public_keys( + &rewards_signer, + vector[claim_link_validated_public_key_bytes] + ); + emojicoin_dot_fun::test_acquisitions::mint_aptos_coin_to( + @rewards, DEFAULT_CLAIM_AMOUNT + ); + fund_vault(&rewards_signer, 1); + + // Get expected proceeds from swap. + let swap_event = + emojicoin_dot_fun::simulate_swap( + CLAIMANT, + @black_cat_market, + DEFAULT_CLAIM_AMOUNT, + false, + @integrator, + INTEGRATOR_FEE_RATE_BPS + ); + let (_, _, _, _, _, _, _, _, net_proceeds, _, _, _, _, _, _, _, _, _) = + emojicoin_dot_fun::unpack_swap(swap_event); + assert!(net_proceeds > 0); + + // Redeem a claim link. + redeem( + &get_signer(CLAIMANT), + ed25519::signature_to_bytes( + &ed25519::sign_arbitrary_bytes( + &claim_link_private_key, bcs::to_bytes(&CLAIMANT) + ) + ), + claim_link_validated_public_key_bytes, + @black_cat_market, + 1 + ); + + // Verify claimaint's emojicoin balance. + assert!(coin::balance(CLAIMANT) == net_proceeds); + + // Check vault balance, manifest. + assert!(vault_balance() == 0); + assert!(public_key_claimant(claim_link_validated_public_key_bytes) == CLAIMANT); + } } diff --git a/src/move/rewards/sources/emojicoin_dot_fun_qr_code.move b/src/move/rewards/sources/emojicoin_dot_fun_qr_code.move deleted file mode 100644 index b73a9fc32..000000000 --- a/src/move/rewards/sources/emojicoin_dot_fun_qr_code.move +++ /dev/null @@ -1,265 +0,0 @@ -// cspell:word funder -module rewards::emojicoin_dot_fun_qr_code { - - use aptos_framework::account::{Self, SignerCapability}; - use aptos_framework::aptos_account; - use aptos_framework::aptos_coin::AptosCoin; - use aptos_framework::coin; - use aptos_framework::event; - use aptos_std::ed25519::{Self, ValidatedPublicKey}; - use aptos_std::simple_map::SimpleMap; - use aptos_std::smart_table::{Self, SmartTable}; - use emojicoin_dot_fun::emojicoin_dot_fun::{Self, Swap}; - use std::option::{Self, Option}; - use std::bcs; - use std::signer; - - const INTEGRATOR_FEE_RATE_BPS: u8 = 100; - const NIL: address = @0x0; - const DEFAULT_CLAIM_AMOUNT: u64 = 10_000_000; - const VAULT: vector = b"QR code vault"; - - /// Signer does not correspond to admin. - const E_NOT_ADMIN: u64 = 0; - /// Admin to remove address does not correspond to admin. - const E_ADMIN_TO_REMOVE_IS_NOT_ADMIN: u64 = 1; - /// Public key of QR code private key is not in manifest. - const E_INVALID_QR_CODE: u64 = 2; - /// QR code has already been redeemed. - const E_QR_CODE_ALREADY_REDEEMED: u64 = 3; - /// Vault has insufficient funds. - const E_VAULT_INSUFFICIENT_FUNDS: u64 = 4; - /// Public key does not pass Ed25519 validation. - const E_INVALID_PUBLIC_KEY: u64 = 5; - /// Signature does not pass Ed25519 validation. - const E_INVALID_SIGNATURE: u64 = 6; - - struct Vault has key { - /// Addresses of signers who can mutate the manifest. - admins: vector
, - /// In octas. - claim_amount: u64, - /// Map from public key of QR code private key to address of claimant, `NIL` if unclaimed. - manifest: SmartTable, - /// Approves transfers from the vault. - signer_capability: SignerCapability - } - - #[event] - struct EmojicoinDotFunQRCodeRedemption has copy, drop, store { - claimant: address, - claim_amount: u64, - swap: Swap - } - - #[view] - public fun admins(): vector
acquires Vault { - Vault[@rewards].admins - } - - #[view] - public fun claim_amount(): u64 acquires Vault { - Vault[@rewards].claim_amount - } - - #[view] - public fun public_key_is_in_manifest(public_key_bytes: vector): bool acquires Vault { - Vault[@rewards].manifest.contains(validate_public_key_bytes(public_key_bytes)) - } - - #[view] - public fun public_key_claimant(public_key_bytes: vector): address acquires Vault { - *Vault[@rewards].manifest.borrow(validate_public_key_bytes(public_key_bytes)) - } - - #[view] - public fun public_keys(): vector acquires Vault { - Vault[@rewards].manifest.keys() - } - - #[view] - public fun public_keys_paginated( - starting_bucket_index: u64, starting_vector_index: u64, num_public_keys_to_get: u64 - ): (vector, Option, Option) acquires Vault { - Vault[@rewards].manifest.keys_paginated( - starting_bucket_index, starting_vector_index, num_public_keys_to_get - ) - } - - #[view] - public fun manifest_to_simple_map(): SimpleMap acquires Vault { - Vault[@rewards].manifest.to_simple_map() - } - - #[view] - public fun vault_balance(): u64 acquires Vault { - coin::balance( - account::get_signer_capability_address(&Vault[@rewards].signer_capability) - ) - } - - #[view] - public fun vault_signer_address(): address acquires Vault { - account::get_signer_capability_address(&Vault[@rewards].signer_capability) - } - - public entry fun add_admin(admin: &signer, new_admin: address) acquires Vault { - let admins_ref_mut = &mut borrow_vault_mut_checked(admin).admins; - admins_ref_mut.push_back(new_admin); - } - - public entry fun add_public_keys( - admin: &signer, public_keys_as_bytes: vector> - ) acquires Vault { - let manifest_ref_mut = &mut borrow_vault_mut_checked(admin).manifest; - public_keys_as_bytes.for_each(|public_key_bytes| { - manifest_ref_mut.add(validate_public_key_bytes(public_key_bytes), NIL); - }); - } - - public entry fun fund_vault(funder: &signer, n_claims: u64) acquires Vault { - let amount = n_claims * Vault[@rewards].claim_amount; - aptos_account::transfer( - funder, - account::get_signer_capability_address(&Vault[@rewards].signer_capability), - amount - ); - } - - /// `signature_bytes` is generated by signing the claimant's address with the QR code private - /// key, and can be verified by `public_key_bytes`, the corresponding QR code public key. - public entry fun redeem( - claimant: &signer, - signature_bytes: vector, - public_key_bytes: vector, - market_address: address, - min_output_amount: u64 - ) acquires Vault { - - // Verify signature. - let validated_public_key = validate_public_key_bytes(public_key_bytes); - let claimant_address = signer::address_of(claimant); - assert!( - ed25519::signature_verify_strict( - &ed25519::new_signature_from_bytes(signature_bytes), - &ed25519::public_key_to_unvalidated(&validated_public_key), - bcs::to_bytes(&claimant_address) - ), - E_INVALID_SIGNATURE - ); - - // Verify public key is eligible for claim. - let vault_ref_mut = &mut Vault[@rewards]; - let manifest_ref_mut = &mut vault_ref_mut.manifest; - assert!(manifest_ref_mut.contains(validated_public_key), E_INVALID_QR_CODE); - assert!( - *manifest_ref_mut.borrow(validated_public_key) == NIL, - E_QR_CODE_ALREADY_REDEEMED - ); - - // Check vault balance. - let vault_signer_cap_ref = &vault_ref_mut.signer_capability; - let vault_address = account::get_signer_capability_address(vault_signer_cap_ref); - let claim_amount = vault_ref_mut.claim_amount; - assert!( - coin::balance(vault_address) >= claim_amount, - E_VAULT_INSUFFICIENT_FUNDS - ); - - // Update manifest, transfer APT to claimant. - *manifest_ref_mut.borrow_mut(validated_public_key) = claimant_address; - let vault_signer = account::create_signer_with_capability(vault_signer_cap_ref); - aptos_account::transfer(&vault_signer, claimant_address, claim_amount); - - // Invoke swap, emit event. - let swap_event = - emojicoin_dot_fun::simulate_swap( - claimant_address, - market_address, - claim_amount, - false, - @integrator, - INTEGRATOR_FEE_RATE_BPS - ); - emojicoin_dot_fun::swap( - claimant, - market_address, - claim_amount, - false, - @integrator, - INTEGRATOR_FEE_RATE_BPS, - min_output_amount - ); - event::emit( - EmojicoinDotFunQRCodeRedemption { - claimant: claimant_address, - claim_amount, - swap: swap_event - } - ); - - } - - public entry fun remove_admin(admin: &signer, admin_to_remove: address) acquires Vault { - let admins_ref_mut = &mut borrow_vault_mut_checked(admin).admins; - let (admin_to_remove_is_admin, admin_to_remove_index) = - admins_ref_mut.index_of(&admin_to_remove); - assert!(admin_to_remove_is_admin, E_ADMIN_TO_REMOVE_IS_NOT_ADMIN); - admins_ref_mut.remove(admin_to_remove_index); - } - - public entry fun remove_public_keys( - admin: &signer, public_keys_as_bytes: vector> - ) acquires Vault { - let manifest_ref_mut = &mut borrow_vault_mut_checked(admin).manifest; - public_keys_as_bytes.for_each(|public_key_bytes| { - let validated_public_key = validate_public_key_bytes(public_key_bytes); - if (manifest_ref_mut.contains(validated_public_key) - && *manifest_ref_mut.borrow(validated_public_key) == NIL) { - manifest_ref_mut.remove(validated_public_key); - } - }); - } - - public entry fun set_claim_amount(admin: &signer, claim_amount: u64) acquires Vault { - borrow_vault_mut_checked(admin).claim_amount = claim_amount; - } - - public entry fun withdraw_from_vault(admin: &signer, amount: u64) acquires Vault { - aptos_account::transfer( - &account::create_signer_with_capability( - &borrow_vault_mut_checked(admin).signer_capability - ), - signer::address_of(admin), - amount - ); - } - - fun init_module(rewards: &signer) { - let (vault_signer, signer_capability) = - account::create_resource_account(rewards, VAULT); - move_to( - rewards, - Vault { - admins: vector[signer::address_of(rewards)], - claim_amount: DEFAULT_CLAIM_AMOUNT, - manifest: smart_table::new(), - signer_capability - } - ); - coin::register(&vault_signer); - } - - inline fun borrow_vault_mut_checked(admin: &signer): &mut Vault { - let vault_ref_mut = &mut Vault[@rewards]; - assert!(vault_ref_mut.admins.contains(&signer::address_of(admin)), E_NOT_ADMIN); - vault_ref_mut - } - - inline fun validate_public_key_bytes(public_key_bytes: vector): ValidatedPublicKey { - let validated_public_key_option = - ed25519::new_validated_public_key_from_bytes(public_key_bytes); - assert!(option::is_some(&validated_public_key_option), E_INVALID_PUBLIC_KEY); - option::destroy_some(validated_public_key_option) - } -} From aea123933db09ac4b979e53f7f9fd9d8aba10c7f Mon Sep 17 00:00:00 2001 From: alnoki <43892045+alnoki@users.noreply.github.com> Date: Fri, 1 Nov 2024 18:00:50 -0700 Subject: [PATCH 13/22] Name variable for more readability --- src/move/rewards/sources/emojicoin_dot_fun_claim_link.move | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/move/rewards/sources/emojicoin_dot_fun_claim_link.move b/src/move/rewards/sources/emojicoin_dot_fun_claim_link.move index 106a7854c..829a5e240 100644 --- a/src/move/rewards/sources/emojicoin_dot_fun_claim_link.move +++ b/src/move/rewards/sources/emojicoin_dot_fun_claim_link.move @@ -295,7 +295,8 @@ module rewards::emojicoin_dot_fun_claim_link { emojicoin_dot_fun::test_acquisitions::mint_aptos_coin_to( @rewards, DEFAULT_CLAIM_AMOUNT ); - fund_vault(&rewards_signer, 1); + let n_redemptions = 1; + fund_vault(&rewards_signer, n_redemptions); // Get expected proceeds from swap. let swap_event = From f29dfec9da46451794e9f270d07f2e37e94d9b1f Mon Sep 17 00:00:00 2001 From: alnoki <43892045+alnoki@users.noreply.github.com> Date: Fri, 1 Nov 2024 18:05:04 -0700 Subject: [PATCH 14/22] Update README --- src/move/rewards/README.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/move/rewards/README.md b/src/move/rewards/README.md index aef2f8758..95d7de08e 100644 --- a/src/move/rewards/README.md +++ b/src/move/rewards/README.md @@ -39,11 +39,12 @@ aptos move publish \ --profile $PROFILE ``` -## Fund the vault +## Fund the vaults ```sh REWARDS=0xaaa... PROFILE=my-profile +N_CLAIM_LINK_REDEMPTIONS_TO_FUND=10 N_REWARDS_TO_FUND_PER_TIER="u64:[1500,500,200,50,5,1]" ``` @@ -53,3 +54,10 @@ aptos move run \ --function-id $REWARDS::emojicoin_dot_fun_rewards::fund_tiers \ --profile $PROFILE ``` + +```sh +aptos move run \ + --args $N_CLAIM_LINK_REDEMPTIONS_TO_FUND \ + --function-id $REWARDS::emojicoin_dot_fun_claim_link::fund_vault \ + --profile $PROFILE +``` From 89f7295718d523e1a9439af358c5f22a4644ebb0 Mon Sep 17 00:00:00 2001 From: alnoki <43892045+alnoki@users.noreply.github.com> Date: Fri, 1 Nov 2024 18:06:41 -0700 Subject: [PATCH 15/22] Bump version minor --- src/move/rewards/Move.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/move/rewards/Move.toml b/src/move/rewards/Move.toml index 9d37aa13e..08e52d06e 100644 --- a/src/move/rewards/Move.toml +++ b/src/move/rewards/Move.toml @@ -18,4 +18,4 @@ local = "../test_coin_factories/black_heart" authors = ["Econia Labs (developers@econialabs.com)"] name = "EmojicoinDotFunRewards" upgrade_policy = "compatible" -version = "1.0.1" +version = "1.1.0" From e8c05aa012c75ad35dbaa14653d0fa3250977d92 Mon Sep 17 00:00:00 2001 From: alnoki <43892045+alnoki@users.noreply.github.com> Date: Wed, 6 Nov 2024 11:18:38 -0800 Subject: [PATCH 16/22] Update default claim amount to 1 APT --- src/move/rewards/sources/emojicoin_dot_fun_claim_link.move | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/move/rewards/sources/emojicoin_dot_fun_claim_link.move b/src/move/rewards/sources/emojicoin_dot_fun_claim_link.move index 829a5e240..e43392af8 100644 --- a/src/move/rewards/sources/emojicoin_dot_fun_claim_link.move +++ b/src/move/rewards/sources/emojicoin_dot_fun_claim_link.move @@ -16,7 +16,7 @@ module rewards::emojicoin_dot_fun_claim_link { const INTEGRATOR_FEE_RATE_BPS: u8 = 100; const NIL: address = @0x0; - const DEFAULT_CLAIM_AMOUNT: u64 = 10_000_000; + const DEFAULT_CLAIM_AMOUNT: u64 = 100_000_000; const VAULT: vector = b"Claim link vault"; /// Signer does not correspond to admin. From c7def51f341cb8f9bbd10ecb74aba5feefa9db1a Mon Sep 17 00:00:00 2001 From: alnoki <43892045+alnoki@users.noreply.github.com> Date: Thu, 14 Nov 2024 16:57:00 -0800 Subject: [PATCH 17/22] Update checks, testing --- .../sources/emojicoin_dot_fun_claim_link.move | 193 +++++++++++++++++- 1 file changed, 191 insertions(+), 2 deletions(-) diff --git a/src/move/rewards/sources/emojicoin_dot_fun_claim_link.move b/src/move/rewards/sources/emojicoin_dot_fun_claim_link.move index e43392af8..2e2d2f188 100644 --- a/src/move/rewards/sources/emojicoin_dot_fun_claim_link.move +++ b/src/move/rewards/sources/emojicoin_dot_fun_claim_link.move @@ -33,6 +33,10 @@ module rewards::emojicoin_dot_fun_claim_link { const E_INVALID_PUBLIC_KEY: u64 = 5; /// Signature does not pass Ed25519 validation. const E_INVALID_SIGNATURE: u64 = 6; + /// Admin to remove is rewards publisher. + const E_ADMIN_TO_REMOVE_IS_REWARDS_PUBLISHER: u64 = 7; + /// Admin is already an admin. + const E_ALREADY_ADMIN: u64 = 8; struct Vault has key { /// Addresses of signers who can mutate the manifest. @@ -105,6 +109,7 @@ module rewards::emojicoin_dot_fun_claim_link { public entry fun add_admin(admin: &signer, new_admin: address) acquires Vault { let admins_ref_mut = &mut borrow_vault_mut_checked(admin).admins; + assert!(!admins_ref_mut.contains(&new_admin), E_ALREADY_ADMIN); admins_ref_mut.push_back(new_admin); } @@ -204,6 +209,7 @@ module rewards::emojicoin_dot_fun_claim_link { let admins_ref_mut = &mut borrow_vault_mut_checked(admin).admins; let (admin_to_remove_is_admin, admin_to_remove_index) = admins_ref_mut.index_of(&admin_to_remove); + assert!(admin_to_remove != @rewards, E_ADMIN_TO_REMOVE_IS_REWARDS_PUBLISHER); assert!(admin_to_remove_is_admin, E_ADMIN_TO_REMOVE_IS_NOT_ADMIN); admins_ref_mut.remove(admin_to_remove_index); } @@ -285,9 +291,30 @@ module rewards::emojicoin_dot_fun_claim_link { let claim_link_validated_public_key_bytes = ed25519::validated_public_key_to_bytes(&claim_link_validated_public_key); - // Initialize module, manifest, vault. + // Initialize module. let rewards_signer = get_signer(@rewards); init_module(&rewards_signer); + + // Check initial state. + assert!(admins() == vector[@rewards]); + assert!(claim_amount() == DEFAULT_CLAIM_AMOUNT); + assert!(!public_key_is_in_manifest(claim_link_validated_public_key_bytes)); + assert!(public_keys().is_empty()); + let (keys, starting_bucket_index, starting_vector_index) = + public_keys_paginated(0, 0, 1); + assert!(keys == vector[]); + assert!(starting_bucket_index == option::none()); + assert!(starting_vector_index == option::none()); + assert!(manifest_to_simple_map().length() == 0); + assert!(vault_balance() == 0); + assert!( + vault_signer_address() + == account::get_signer_capability_address( + &Vault[@rewards].signer_capability + ) + ); + + // Add an admin, public key, mint APT, fund vault. add_public_keys( &rewards_signer, vector[claim_link_validated_public_key_bytes] @@ -297,13 +324,65 @@ module rewards::emojicoin_dot_fun_claim_link { ); let n_redemptions = 1; fund_vault(&rewards_signer, n_redemptions); + let new_admin = @0x2222; + add_admin(&rewards_signer, new_admin); + + // Check new state. + assert!(admins() == vector[@rewards, new_admin]); + assert!(claim_amount() == DEFAULT_CLAIM_AMOUNT); + assert!(public_key_is_in_manifest(claim_link_validated_public_key_bytes)); + assert!( + manifest_to_simple_map().keys() == vector[claim_link_validated_public_key] + ); + assert!(public_key_claimant(claim_link_validated_public_key_bytes) == NIL); + (keys, starting_bucket_index, starting_vector_index) = public_keys_paginated( + 0, 0, 1 + ); + assert!(keys == vector[claim_link_validated_public_key]); + assert!(starting_bucket_index == option::none()); + assert!(starting_vector_index == option::none()); + assert!( + manifest_to_simple_map().keys() == vector[claim_link_validated_public_key] + ); + assert!(vault_balance() == DEFAULT_CLAIM_AMOUNT); + + // Fund another reward, double claim amount, fund another reward, remove admin, withdraw. + emojicoin_dot_fun::test_acquisitions::mint_aptos_coin_to( + @rewards, 3 * DEFAULT_CLAIM_AMOUNT + ); + fund_vault(&rewards_signer, 1); + set_claim_amount(&rewards_signer, 2 * DEFAULT_CLAIM_AMOUNT); + fund_vault(&rewards_signer, 1); + remove_admin(&rewards_signer, new_admin); + withdraw_from_vault(&rewards_signer, 2 * DEFAULT_CLAIM_AMOUNT); + + // Verify new state. + assert!(admins() == vector[@rewards]); + assert!(claim_amount() == 2 * DEFAULT_CLAIM_AMOUNT); + assert!(vault_balance() == 2 * DEFAULT_CLAIM_AMOUNT); + assert!( + coin::balance(@rewards) == 2 * DEFAULT_CLAIM_AMOUNT + ); + + // Verify that public key can be removed and re-added. + remove_public_keys( + &rewards_signer, + vector[claim_link_validated_public_key_bytes] + ); + assert!(!public_key_is_in_manifest(claim_link_validated_public_key_bytes)); + add_public_keys( + &rewards_signer, + vector[claim_link_validated_public_key_bytes] + ); + assert!(public_key_is_in_manifest(claim_link_validated_public_key_bytes)); + assert!(public_key_claimant(claim_link_validated_public_key_bytes) == NIL); // Get expected proceeds from swap. let swap_event = emojicoin_dot_fun::simulate_swap( CLAIMANT, @black_cat_market, - DEFAULT_CLAIM_AMOUNT, + 2 * DEFAULT_CLAIM_AMOUNT, false, @integrator, INTEGRATOR_FEE_RATE_BPS @@ -332,5 +411,115 @@ module rewards::emojicoin_dot_fun_claim_link { assert!(vault_balance() == 0); assert!(public_key_claimant(claim_link_validated_public_key_bytes) == CLAIMANT); + // Verify that public key entry can no longer be removed. + remove_public_keys( + &rewards_signer, + vector[claim_link_validated_public_key_bytes] + ); + assert!(public_key_is_in_manifest(claim_link_validated_public_key_bytes)); + + } + + #[test, expected_failure(abort_code = E_ALREADY_ADMIN)] + fun test_add_admin_already_admin() acquires Vault { + emojicoin_dot_fun::tests::init_package(); + let rewards_signer = get_signer(@rewards); + init_module(&rewards_signer); + add_admin(&rewards_signer, @rewards); + } + + #[test, expected_failure(abort_code = E_NOT_ADMIN)] + fun test_add_admin_not_admin() acquires Vault { + emojicoin_dot_fun::tests::init_package(); + let rewards_signer = get_signer(@rewards); + init_module(&rewards_signer); + let not_admin = @0x2222; + let not_admin_signer = get_signer(not_admin); + assert!(&rewards_signer != ¬_admin_signer); + add_admin(¬_admin_signer, not_admin); + } + + #[test, expected_failure(abort_code = E_INVALID_PUBLIC_KEY)] + fun test_add_public_keys_invalid_public_key() acquires Vault { + emojicoin_dot_fun::tests::init_package(); + let rewards_signer = get_signer(@rewards); + init_module(&rewards_signer); + add_public_keys(&rewards_signer, vector[vector[0x0]]); + } + + #[test, expected_failure(abort_code = E_NOT_ADMIN)] + fun test_add_public_keys_not_admin() acquires Vault { + emojicoin_dot_fun::tests::init_package(); + let rewards_signer = get_signer(@rewards); + init_module(&rewards_signer); + let not_admin_signer = get_signer(@0x2222); + assert!(&rewards_signer != ¬_admin_signer); + add_public_keys(¬_admin_signer, vector[]); + } + + #[test, expected_failure(abort_code = E_NOT_ADMIN)] + fun test_remove_admin_not_admin() acquires Vault { + emojicoin_dot_fun::tests::init_package(); + let rewards_signer = get_signer(@rewards); + init_module(&rewards_signer); + let not_admin = @0x2222; + assert!(not_admin != @rewards); + remove_admin(&get_signer(not_admin), @rewards); + } + + #[test, expected_failure(abort_code = E_ADMIN_TO_REMOVE_IS_NOT_ADMIN)] + fun test_remove_admin_admin_to_remove_is_not_admin() acquires Vault { + emojicoin_dot_fun::tests::init_package(); + let rewards_signer = get_signer(@rewards); + init_module(&rewards_signer); + let not_admin = @0x2222; + assert!(not_admin != @rewards); + remove_admin(&rewards_signer, not_admin); + } + + #[test, expected_failure(abort_code = E_ADMIN_TO_REMOVE_IS_REWARDS_PUBLISHER)] + fun test_remove_admin_admin_to_remove_is_rewards_publisher() acquires Vault { + emojicoin_dot_fun::tests::init_package(); + let rewards_signer = get_signer(@rewards); + init_module(&rewards_signer); + remove_admin(&rewards_signer, @rewards); + } + + #[test, expected_failure(abort_code = E_INVALID_PUBLIC_KEY)] + fun test_remove_public_keys_invalid_public_key() acquires Vault { + emojicoin_dot_fun::tests::init_package(); + let rewards_signer = get_signer(@rewards); + init_module(&rewards_signer); + remove_public_keys(&rewards_signer, vector[vector[0x0]]); + } + + #[test, expected_failure(abort_code = E_NOT_ADMIN)] + fun test_remove_public_keys_not_admin() acquires Vault { + emojicoin_dot_fun::tests::init_package(); + let rewards_signer = get_signer(@rewards); + init_module(&rewards_signer); + let not_admin_signer = get_signer(@0x2222); + assert!(&rewards_signer != ¬_admin_signer); + remove_public_keys(¬_admin_signer, vector[]); + } + + #[test, expected_failure(abort_code = E_NOT_ADMIN)] + fun test_set_claim_amount_not_admin() acquires Vault { + emojicoin_dot_fun::tests::init_package(); + let rewards_signer = get_signer(@rewards); + init_module(&rewards_signer); + let not_admin_signer = get_signer(@0x2222); + assert!(¬_admin_signer != &rewards_signer); + set_claim_amount(¬_admin_signer, 1); + } + + #[test, expected_failure(abort_code = E_NOT_ADMIN)] + fun withdraw_from_vault_not_admin() acquires Vault { + emojicoin_dot_fun::tests::init_package(); + let rewards_signer = get_signer(@rewards); + init_module(&rewards_signer); + let not_admin_signer = get_signer(@0x2222); + assert!(¬_admin_signer != &rewards_signer); + withdraw_from_vault(¬_admin_signer, 1); } } From beb4a8ae738bfc44c06930ddd42b6f391e14049e Mon Sep 17 00:00:00 2001 From: alnoki <43892045+alnoki@users.noreply.github.com> Date: Thu, 14 Nov 2024 17:12:09 -0800 Subject: [PATCH 18/22] Add invalid signature test --- .../sources/emojicoin_dot_fun_claim_link.move | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/src/move/rewards/sources/emojicoin_dot_fun_claim_link.move b/src/move/rewards/sources/emojicoin_dot_fun_claim_link.move index 2e2d2f188..464905d85 100644 --- a/src/move/rewards/sources/emojicoin_dot_fun_claim_link.move +++ b/src/move/rewards/sources/emojicoin_dot_fun_claim_link.move @@ -280,6 +280,37 @@ module rewards::emojicoin_dot_fun_claim_link { #[test_only] const CLAIMANT: address = @0x1111; + #[test_only] + fun prepare_for_redemption(): (vector, vector) acquires Vault { + // Init package, execute exact transition swap, fund vault. + emojicoin_dot_fun::tests::init_package_then_exact_transition(); + let rewards_signer = get_signer(@rewards); + init_module(&rewards_signer); + emojicoin_dot_fun::test_acquisitions::mint_aptos_coin_to( + @rewards, DEFAULT_CLAIM_AMOUNT + ); + fund_vault(&rewards_signer, 1); + + // Generate private, public keys. + let (claim_link_private_key, claim_link_validated_public_key) = + ed25519::generate_keys(); + let claim_link_validated_public_key_bytes = + ed25519::validated_public_key_to_bytes(&claim_link_validated_public_key); + let signature_bytes = + ed25519::signature_to_bytes( + &ed25519::sign_arbitrary_bytes( + &claim_link_private_key, bcs::to_bytes(&CLAIMANT) + ) + ); + add_public_keys( + &rewards_signer, + vector[claim_link_validated_public_key_bytes] + ); + + // Return valid signature against claimaint's address, claim link public key bytes. + (signature_bytes, claim_link_validated_public_key_bytes) + } + #[test] fun test_general_flow() acquires Vault { // Initialize black cat market, have it undergo state transition. @@ -457,6 +488,20 @@ module rewards::emojicoin_dot_fun_claim_link { add_public_keys(¬_admin_signer, vector[]); } + #[test, expected_failure(abort_code = E_INVALID_SIGNATURE)] + fun test_redeem_invalid_signature() acquires Vault { + let (signature_bytes, claim_link_validated_public_key_bytes) = + prepare_for_redemption(); + signature_bytes[0] = signature_bytes[0] ^ 0xff; + redeem( + &get_signer(CLAIMANT), + signature_bytes, + claim_link_validated_public_key_bytes, + @black_cat_market, + 1 + ); + } + #[test, expected_failure(abort_code = E_NOT_ADMIN)] fun test_remove_admin_not_admin() acquires Vault { emojicoin_dot_fun::tests::init_package(); From f1c938f73c1bb46cf3495550e63e69691cf16ce0 Mon Sep 17 00:00:00 2001 From: alnoki <43892045+alnoki@users.noreply.github.com> Date: Thu, 14 Nov 2024 17:23:47 -0800 Subject: [PATCH 19/22] Add more expected failure tests --- .../sources/emojicoin_dot_fun_claim_link.move | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/src/move/rewards/sources/emojicoin_dot_fun_claim_link.move b/src/move/rewards/sources/emojicoin_dot_fun_claim_link.move index 464905d85..ab0727924 100644 --- a/src/move/rewards/sources/emojicoin_dot_fun_claim_link.move +++ b/src/move/rewards/sources/emojicoin_dot_fun_claim_link.move @@ -488,6 +488,43 @@ module rewards::emojicoin_dot_fun_claim_link { add_public_keys(¬_admin_signer, vector[]); } + #[test, expected_failure(abort_code = E_CLAIM_LINK_ALREADY_CLAIMED)] + fun test_redeem_claim_link_already_claimed() acquires Vault { + let (signature_bytes, claim_link_validated_public_key_bytes) = + prepare_for_redemption(); + redeem( + &get_signer(CLAIMANT), + signature_bytes, + claim_link_validated_public_key_bytes, + @black_cat_market, + 1 + ); + redeem( + &get_signer(CLAIMANT), + signature_bytes, + claim_link_validated_public_key_bytes, + @black_cat_market, + 1 + ); + } + + #[test, expected_failure(abort_code = E_INVALID_CLAIM_LINK)] + fun test_redeem_invalid_claim_link() acquires Vault { + let (signature_bytes, claim_link_validated_public_key_bytes) = + prepare_for_redemption(); + remove_public_keys( + &get_signer(@rewards), + vector[claim_link_validated_public_key_bytes] + ); + redeem( + &get_signer(CLAIMANT), + signature_bytes, + claim_link_validated_public_key_bytes, + @black_cat_market, + 1 + ); + } + #[test, expected_failure(abort_code = E_INVALID_SIGNATURE)] fun test_redeem_invalid_signature() acquires Vault { let (signature_bytes, claim_link_validated_public_key_bytes) = @@ -502,6 +539,20 @@ module rewards::emojicoin_dot_fun_claim_link { ); } + #[test, expected_failure(abort_code = E_VAULT_INSUFFICIENT_FUNDS)] + fun test_redeem_vault_insufficient_funds() acquires Vault { + let (signature_bytes, claim_link_validated_public_key_bytes) = + prepare_for_redemption(); + withdraw_from_vault(&get_signer(@rewards), DEFAULT_CLAIM_AMOUNT); + redeem( + &get_signer(CLAIMANT), + signature_bytes, + claim_link_validated_public_key_bytes, + @black_cat_market, + 1 + ); + } + #[test, expected_failure(abort_code = E_NOT_ADMIN)] fun test_remove_admin_not_admin() acquires Vault { emojicoin_dot_fun::tests::init_package(); From db613fbfb3fa9b7e616f157354a70d6c029ffa6e Mon Sep 17 00:00:00 2001 From: alnoki <43892045+alnoki@users.noreply.github.com> Date: Thu, 14 Nov 2024 17:37:13 -0800 Subject: [PATCH 20/22] Add more tests --- .../sources/emojicoin_dot_fun_claim_link.move | 109 +++++++++++------- 1 file changed, 69 insertions(+), 40 deletions(-) diff --git a/src/move/rewards/sources/emojicoin_dot_fun_claim_link.move b/src/move/rewards/sources/emojicoin_dot_fun_claim_link.move index ab0727924..3b0dd135a 100644 --- a/src/move/rewards/sources/emojicoin_dot_fun_claim_link.move +++ b/src/move/rewards/sources/emojicoin_dot_fun_claim_link.move @@ -311,6 +311,43 @@ module rewards::emojicoin_dot_fun_claim_link { (signature_bytes, claim_link_validated_public_key_bytes) } + #[test, expected_failure(abort_code = E_ALREADY_ADMIN)] + fun test_add_admin_already_admin() acquires Vault { + emojicoin_dot_fun::tests::init_package(); + let rewards_signer = get_signer(@rewards); + init_module(&rewards_signer); + add_admin(&rewards_signer, @rewards); + } + + #[test, expected_failure(abort_code = E_NOT_ADMIN)] + fun test_add_admin_not_admin() acquires Vault { + emojicoin_dot_fun::tests::init_package(); + let rewards_signer = get_signer(@rewards); + init_module(&rewards_signer); + let not_admin = @0x2222; + let not_admin_signer = get_signer(not_admin); + assert!(&rewards_signer != ¬_admin_signer); + add_admin(¬_admin_signer, not_admin); + } + + #[test, expected_failure(abort_code = E_INVALID_PUBLIC_KEY)] + fun test_add_public_keys_invalid_public_key() acquires Vault { + emojicoin_dot_fun::tests::init_package(); + let rewards_signer = get_signer(@rewards); + init_module(&rewards_signer); + add_public_keys(&rewards_signer, vector[vector[0x0]]); + } + + #[test, expected_failure(abort_code = E_NOT_ADMIN)] + fun test_add_public_keys_not_admin() acquires Vault { + emojicoin_dot_fun::tests::init_package(); + let rewards_signer = get_signer(@rewards); + init_module(&rewards_signer); + let not_admin_signer = get_signer(@0x2222); + assert!(&rewards_signer != ¬_admin_signer); + add_public_keys(¬_admin_signer, vector[]); + } + #[test] fun test_general_flow() acquires Vault { // Initialize black cat market, have it undergo state transition. @@ -449,49 +486,31 @@ module rewards::emojicoin_dot_fun_claim_link { ); assert!(public_key_is_in_manifest(claim_link_validated_public_key_bytes)); - } - - #[test, expected_failure(abort_code = E_ALREADY_ADMIN)] - fun test_add_admin_already_admin() acquires Vault { - emojicoin_dot_fun::tests::init_package(); - let rewards_signer = get_signer(@rewards); - init_module(&rewards_signer); - add_admin(&rewards_signer, @rewards); - } - - #[test, expected_failure(abort_code = E_NOT_ADMIN)] - fun test_add_admin_not_admin() acquires Vault { - emojicoin_dot_fun::tests::init_package(); - let rewards_signer = get_signer(@rewards); - init_module(&rewards_signer); - let not_admin = @0x2222; - let not_admin_signer = get_signer(not_admin); - assert!(&rewards_signer != ¬_admin_signer); - add_admin(¬_admin_signer, not_admin); + // Verify silent return for trying to remove public key not in manifest. + let (_, new_public_key) = ed25519::generate_keys(); + remove_public_keys( + &rewards_signer, + vector[ed25519::validated_public_key_to_bytes(&new_public_key)] + ); } #[test, expected_failure(abort_code = E_INVALID_PUBLIC_KEY)] - fun test_add_public_keys_invalid_public_key() acquires Vault { - emojicoin_dot_fun::tests::init_package(); - let rewards_signer = get_signer(@rewards); - init_module(&rewards_signer); - add_public_keys(&rewards_signer, vector[vector[0x0]]); + fun test_public_key_claimaint_invalid_public_key() acquires Vault { + let (_, claim_link_validated_public_key_bytes) = prepare_for_redemption(); + claim_link_validated_public_key_bytes.push_back(0); + public_key_claimant(claim_link_validated_public_key_bytes); } - #[test, expected_failure(abort_code = E_NOT_ADMIN)] - fun test_add_public_keys_not_admin() acquires Vault { - emojicoin_dot_fun::tests::init_package(); - let rewards_signer = get_signer(@rewards); - init_module(&rewards_signer); - let not_admin_signer = get_signer(@0x2222); - assert!(&rewards_signer != ¬_admin_signer); - add_public_keys(¬_admin_signer, vector[]); + #[test, expected_failure(abort_code = E_INVALID_PUBLIC_KEY)] + fun test_public_key_is_in_manifest_invalid_public_key() acquires Vault { + let (_, claim_link_validated_public_key_bytes) = prepare_for_redemption(); + claim_link_validated_public_key_bytes.push_back(0); + public_key_is_in_manifest(claim_link_validated_public_key_bytes); } #[test, expected_failure(abort_code = E_CLAIM_LINK_ALREADY_CLAIMED)] fun test_redeem_claim_link_already_claimed() acquires Vault { - let (signature_bytes, claim_link_validated_public_key_bytes) = - prepare_for_redemption(); + let (signature_bytes, claim_link_validated_public_key_bytes) = prepare_for_redemption(); redeem( &get_signer(CLAIMANT), signature_bytes, @@ -510,8 +529,7 @@ module rewards::emojicoin_dot_fun_claim_link { #[test, expected_failure(abort_code = E_INVALID_CLAIM_LINK)] fun test_redeem_invalid_claim_link() acquires Vault { - let (signature_bytes, claim_link_validated_public_key_bytes) = - prepare_for_redemption(); + let (signature_bytes, claim_link_validated_public_key_bytes) = prepare_for_redemption(); remove_public_keys( &get_signer(@rewards), vector[claim_link_validated_public_key_bytes] @@ -525,10 +543,22 @@ module rewards::emojicoin_dot_fun_claim_link { ); } + #[test, expected_failure(abort_code = E_INVALID_PUBLIC_KEY)] + fun test_redeem_invalid_public_key() acquires Vault { + let (signature_bytes, claim_link_validated_public_key_bytes) = prepare_for_redemption(); + claim_link_validated_public_key_bytes.push_back(0); + redeem( + &get_signer(CLAIMANT), + signature_bytes, + claim_link_validated_public_key_bytes, + @black_cat_market, + 1 + ); + } + #[test, expected_failure(abort_code = E_INVALID_SIGNATURE)] fun test_redeem_invalid_signature() acquires Vault { - let (signature_bytes, claim_link_validated_public_key_bytes) = - prepare_for_redemption(); + let (signature_bytes, claim_link_validated_public_key_bytes) = prepare_for_redemption(); signature_bytes[0] = signature_bytes[0] ^ 0xff; redeem( &get_signer(CLAIMANT), @@ -541,8 +571,7 @@ module rewards::emojicoin_dot_fun_claim_link { #[test, expected_failure(abort_code = E_VAULT_INSUFFICIENT_FUNDS)] fun test_redeem_vault_insufficient_funds() acquires Vault { - let (signature_bytes, claim_link_validated_public_key_bytes) = - prepare_for_redemption(); + let (signature_bytes, claim_link_validated_public_key_bytes) = prepare_for_redemption(); withdraw_from_vault(&get_signer(@rewards), DEFAULT_CLAIM_AMOUNT); redeem( &get_signer(CLAIMANT), From cf91314398bcae3e9b6263eb93be33693e65b5f5 Mon Sep 17 00:00:00 2001 From: alnoki <43892045+alnoki@users.noreply.github.com> Date: Thu, 14 Nov 2024 17:40:56 -0800 Subject: [PATCH 21/22] Format --- .../sources/emojicoin_dot_fun_claim_link.move | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/move/rewards/sources/emojicoin_dot_fun_claim_link.move b/src/move/rewards/sources/emojicoin_dot_fun_claim_link.move index 3b0dd135a..8e362bfc0 100644 --- a/src/move/rewards/sources/emojicoin_dot_fun_claim_link.move +++ b/src/move/rewards/sources/emojicoin_dot_fun_claim_link.move @@ -510,7 +510,8 @@ module rewards::emojicoin_dot_fun_claim_link { #[test, expected_failure(abort_code = E_CLAIM_LINK_ALREADY_CLAIMED)] fun test_redeem_claim_link_already_claimed() acquires Vault { - let (signature_bytes, claim_link_validated_public_key_bytes) = prepare_for_redemption(); + let (signature_bytes, claim_link_validated_public_key_bytes) = + prepare_for_redemption(); redeem( &get_signer(CLAIMANT), signature_bytes, @@ -529,7 +530,8 @@ module rewards::emojicoin_dot_fun_claim_link { #[test, expected_failure(abort_code = E_INVALID_CLAIM_LINK)] fun test_redeem_invalid_claim_link() acquires Vault { - let (signature_bytes, claim_link_validated_public_key_bytes) = prepare_for_redemption(); + let (signature_bytes, claim_link_validated_public_key_bytes) = + prepare_for_redemption(); remove_public_keys( &get_signer(@rewards), vector[claim_link_validated_public_key_bytes] @@ -545,7 +547,8 @@ module rewards::emojicoin_dot_fun_claim_link { #[test, expected_failure(abort_code = E_INVALID_PUBLIC_KEY)] fun test_redeem_invalid_public_key() acquires Vault { - let (signature_bytes, claim_link_validated_public_key_bytes) = prepare_for_redemption(); + let (signature_bytes, claim_link_validated_public_key_bytes) = + prepare_for_redemption(); claim_link_validated_public_key_bytes.push_back(0); redeem( &get_signer(CLAIMANT), @@ -558,7 +561,8 @@ module rewards::emojicoin_dot_fun_claim_link { #[test, expected_failure(abort_code = E_INVALID_SIGNATURE)] fun test_redeem_invalid_signature() acquires Vault { - let (signature_bytes, claim_link_validated_public_key_bytes) = prepare_for_redemption(); + let (signature_bytes, claim_link_validated_public_key_bytes) = + prepare_for_redemption(); signature_bytes[0] = signature_bytes[0] ^ 0xff; redeem( &get_signer(CLAIMANT), @@ -571,7 +575,8 @@ module rewards::emojicoin_dot_fun_claim_link { #[test, expected_failure(abort_code = E_VAULT_INSUFFICIENT_FUNDS)] fun test_redeem_vault_insufficient_funds() acquires Vault { - let (signature_bytes, claim_link_validated_public_key_bytes) = prepare_for_redemption(); + let (signature_bytes, claim_link_validated_public_key_bytes) = + prepare_for_redemption(); withdraw_from_vault(&get_signer(@rewards), DEFAULT_CLAIM_AMOUNT); redeem( &get_signer(CLAIMANT), From 98fbfb67c2ec284f335090957a85f4afb89b84d3 Mon Sep 17 00:00:00 2001 From: alnoki <43892045+alnoki@users.noreply.github.com> Date: Thu, 14 Nov 2024 17:42:33 -0800 Subject: [PATCH 22/22] Address pre-commit --- src/move/rewards/sources/emojicoin_dot_fun_claim_link.move | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/move/rewards/sources/emojicoin_dot_fun_claim_link.move b/src/move/rewards/sources/emojicoin_dot_fun_claim_link.move index 8e362bfc0..031187360 100644 --- a/src/move/rewards/sources/emojicoin_dot_fun_claim_link.move +++ b/src/move/rewards/sources/emojicoin_dot_fun_claim_link.move @@ -1,4 +1,5 @@ // cspell:word funder +// cspell:word unvalidated module rewards::emojicoin_dot_fun_claim_link { use aptos_framework::account::{Self, SignerCapability}; @@ -307,7 +308,7 @@ module rewards::emojicoin_dot_fun_claim_link { vector[claim_link_validated_public_key_bytes] ); - // Return valid signature against claimaint's address, claim link public key bytes. + // Return valid signature against claimant's address, claim link public key bytes. (signature_bytes, claim_link_validated_public_key_bytes) } @@ -472,7 +473,7 @@ module rewards::emojicoin_dot_fun_claim_link { 1 ); - // Verify claimaint's emojicoin balance. + // Verify claimant's emojicoin balance. assert!(coin::balance(CLAIMANT) == net_proceeds); // Check vault balance, manifest. @@ -495,7 +496,7 @@ module rewards::emojicoin_dot_fun_claim_link { } #[test, expected_failure(abort_code = E_INVALID_PUBLIC_KEY)] - fun test_public_key_claimaint_invalid_public_key() acquires Vault { + fun test_public_key_claimant_invalid_public_key() acquires Vault { let (_, claim_link_validated_public_key_bytes) = prepare_for_redemption(); claim_link_validated_public_key_bytes.push_back(0); public_key_claimant(claim_link_validated_public_key_bytes);