├── .dockerignore ├── .github ├── ISSUE_TEMPLATE │ └── security_report.md ├── scripts │ ├── generate_matrix.rb │ └── run_tests.rb └── workflows │ ├── release.yml │ ├── run_test.yml │ └── tlin_check.yml ├── .gitignore ├── Dockerfile ├── Gemfile ├── LICENSE ├── Makefile ├── README.md ├── SECURITY.md ├── audits └── [BigInteger] Gnoswap Audit Report (ENG)_Final.pdf ├── changelog.md ├── contract ├── gnowork.toml ├── p │ └── gnoswap │ │ ├── fuzz │ │ ├── fuzz.gno │ │ ├── fuzz_test.gno │ │ ├── gnomod.toml │ │ └── seed.gno │ │ ├── gnsmath │ │ ├── bit_math.gno │ │ ├── bit_math_test.gno │ │ ├── doc.gno │ │ ├── errors.gno │ │ ├── gnomod.toml │ │ ├── sqrt_price_math.gno │ │ ├── sqrt_price_math_test.gno │ │ ├── swap_math.gno │ │ └── swap_math_test.gno │ │ ├── int256 │ │ ├── LICENSE │ │ ├── README.md │ │ ├── absolute.gno │ │ ├── absolute_test.gno │ │ ├── arithmetic.gno │ │ ├── arithmetic_test.gno │ │ ├── bitwise.gno │ │ ├── bitwise_test.gno │ │ ├── cmp.gno │ │ ├── cmp_test.gno │ │ ├── conversion.gno │ │ ├── conversion_test.gno │ │ ├── doc.gno │ │ ├── gnomod.toml │ │ ├── int256.gno │ │ └── int256_test.gno │ │ ├── rbac │ │ ├── README.md │ │ ├── doc.gno │ │ ├── errors.gno │ │ ├── gnomod.toml │ │ ├── ownable.gno │ │ ├── ownable_test.gno │ │ ├── rbac.gno │ │ ├── rbac_test.gno │ │ ├── role.gno │ │ └── types.gno │ │ └── uint256 │ │ ├── LICENSE │ │ ├── README.md │ │ ├── _helper_test.gno │ │ ├── arithmetic.gno │ │ ├── arithmetic_test.gno │ │ ├── bits_table.gno │ │ ├── bitwise.gno │ │ ├── bitwise_test.gno │ │ ├── cmp.gno │ │ ├── cmp_test.gno │ │ ├── conversion.gno │ │ ├── conversion_test.gno │ │ ├── doc.gno │ │ ├── error.gno │ │ ├── fullmath.gno │ │ ├── fullmath_test.gno │ │ ├── gnomod.toml │ │ ├── gs_pointer.gno │ │ ├── mod.gno │ │ ├── uint256.gno │ │ ├── uint256_test.gno │ │ └── utils.gno └── r │ ├── gnoswap │ ├── access │ │ ├── README.md │ │ ├── access.gno │ │ ├── access_test.gno │ │ ├── assert.gno │ │ ├── assert_test.gno │ │ ├── consts.gno │ │ ├── errors.gno │ │ ├── gnomod.toml │ │ ├── swap_whitelist.gno │ │ └── swap_whitelist_test.gno │ ├── emission │ │ ├── README.md │ │ ├── _helper_test.gno │ │ ├── assert.gno │ │ ├── distribution.gno │ │ ├── distribution_test.gno │ │ ├── emission.gno │ │ ├── emission_test.gno │ │ ├── errors.gno │ │ ├── gnomod.toml │ │ ├── leftamount_test.gno │ │ ├── security_test.gno │ │ └── utils.gno │ ├── gns │ │ ├── README.md │ │ ├── _helper_test.gno │ │ ├── assert.gno │ │ ├── consts.gno │ │ ├── emission_distribution_test.gno │ │ ├── emission_state.gno │ │ ├── emission_test.gno │ │ ├── errors.gno │ │ ├── getter.gno │ │ ├── gnomod.toml │ │ ├── gns.gno │ │ ├── gns_emission.gno │ │ ├── gns_test.gno │ │ ├── halving.gno │ │ ├── halving_emission_scenario_test.gno │ │ ├── halving_test.gno │ │ ├── testutils.gno │ │ └── utils.gno │ ├── halt │ │ ├── README.md │ │ ├── assert.gno │ │ ├── config.gno │ │ ├── config_test.gno │ │ ├── doc.gno │ │ ├── errors.gno │ │ ├── getters.gno │ │ ├── gnomod.toml │ │ ├── halt.gno │ │ ├── halt_state.gno │ │ ├── halt_test.gno │ │ ├── types.gno │ │ └── types_test.gno │ ├── rbac │ │ ├── README.md │ │ ├── assert.gno │ │ ├── assert_test.gno │ │ ├── consts.gno │ │ ├── emit.gno │ │ ├── errors.gno │ │ ├── gnomod.toml │ │ ├── ownership.gno │ │ ├── ownership_test.gno │ │ ├── rbac.gno │ │ ├── rbac_test.gno │ │ ├── role.gno │ │ └── role_test.gno │ ├── referral │ │ ├── README.md │ │ ├── additional_test.gno │ │ ├── doc.gno │ │ ├── errors.gno │ │ ├── global_keeper.gno │ │ ├── global_keeper_test.gno │ │ ├── gnomod.toml │ │ ├── keeper.gno │ │ ├── keeper_test.gno │ │ ├── keeper_unit_test.gno │ │ ├── rate_limit_test.gno │ │ ├── referral.gno │ │ ├── referral_unit_test.gno │ │ ├── security_test.gno │ │ ├── type.gno │ │ ├── utility_test.gno │ │ ├── utils.gno │ │ └── validation_test.gno │ ├── test_token │ │ ├── bar │ │ │ ├── bar.gno │ │ │ └── gnomod.toml │ │ ├── baz │ │ │ ├── baz.gno │ │ │ └── gnomod.toml │ │ ├── foo │ │ │ ├── foo.gno │ │ │ └── gnomod.toml │ │ ├── obl │ │ │ ├── gnomod.toml │ │ │ └── obl.gno │ │ ├── qux │ │ │ ├── gnomod.toml │ │ │ └── qux.gno │ │ └── usdc │ │ │ ├── gnomod.toml │ │ │ └── usdc.gno │ └── v1 │ │ ├── common │ │ ├── assert.gno │ │ ├── coins.gno │ │ ├── consts.gno │ │ ├── doc.gno │ │ ├── errors.gno │ │ ├── gnomod.toml │ │ ├── grc20reg_helper.gno │ │ ├── grc20reg_helper_test.gno │ │ ├── liquidity_amounts.gno │ │ ├── liquidity_amounts_test.gno │ │ ├── tick_math.gno │ │ └── tick_math_test.gno │ │ ├── community_pool │ │ ├── README.md │ │ ├── community_pool.gno │ │ ├── community_pool_test.gno │ │ ├── doc.gno │ │ ├── errors.gno │ │ └── gnomod.toml │ │ ├── gnft │ │ ├── assert.gno │ │ ├── errors.gno │ │ ├── gnft.gno │ │ ├── gnft_test.gno │ │ ├── gnomod.toml │ │ ├── svg_generator.gno │ │ ├── svg_generator_test.gno │ │ ├── testutils.gno │ │ └── utils.gno │ │ ├── gov │ │ ├── README.md │ │ ├── doc.gno │ │ ├── gnomod.toml │ │ ├── governance │ │ │ ├── _helper_test.gno │ │ │ ├── api.gno │ │ │ ├── assert.gno │ │ │ ├── config.gno │ │ │ ├── config_test.gno │ │ │ ├── consts.gno │ │ │ ├── counter.gno │ │ │ ├── counter_test.gno │ │ │ ├── doc.gno │ │ │ ├── errors.gno │ │ │ ├── getter_proposal.gno │ │ │ ├── getter_proposal_test.gno │ │ │ ├── getter_vote.gno │ │ │ ├── gnomod.toml │ │ │ ├── governance_execute.gno │ │ │ ├── governance_execute_test.gno │ │ │ ├── governance_propose.gno │ │ │ ├── governance_propose_test.gno │ │ │ ├── governance_vote.gno │ │ │ ├── governance_vote_test.gno │ │ │ ├── parameter_registry.gno │ │ │ ├── parameter_registry_handler.gno │ │ │ ├── parameter_registry_handler_test.gno │ │ │ ├── parameter_registry_test.gno │ │ │ ├── proposal.gno │ │ │ ├── proposal_action_status.gno │ │ │ ├── proposal_action_status_test.gno │ │ │ ├── proposal_data.gno │ │ │ ├── proposal_data_test.gno │ │ │ ├── proposal_manager.gno │ │ │ ├── proposal_manager_test.gno │ │ │ ├── proposal_schedule_status.gno │ │ │ ├── proposal_schedule_status_test.gno │ │ │ ├── proposal_status.gno │ │ │ ├── proposal_status_test.gno │ │ │ ├── proposal_test.gno │ │ │ ├── proposal_vote_status.gno │ │ │ ├── proposal_vote_status_test.gno │ │ │ ├── state.gno │ │ │ ├── utils.gno │ │ │ ├── utils_test.gno │ │ │ ├── voting_info.gno │ │ │ └── voting_info_test.gno │ │ ├── staker │ │ │ ├── _helper_test.gno │ │ │ ├── api_delegation.gno │ │ │ ├── api_staker.gno │ │ │ ├── assert.gno │ │ │ ├── assert_test.gno │ │ │ ├── consts.gno │ │ │ ├── counter.gno │ │ │ ├── counter_test.gno │ │ │ ├── delegation.gno │ │ │ ├── delegation_history.gno │ │ │ ├── delegation_history_test.gno │ │ │ ├── delegation_manager_test.gno │ │ │ ├── delegation_mananger.gno │ │ │ ├── delegation_record.gno │ │ │ ├── delegation_record_test.gno │ │ │ ├── delegation_snapshot.gno │ │ │ ├── delegation_snapshot_test.gno │ │ │ ├── delegation_test.gno │ │ │ ├── delegation_withdraw.gno │ │ │ ├── delegation_withdraw_test.gno │ │ │ ├── doc.gno │ │ │ ├── emission_reward_manager.gno │ │ │ ├── emission_reward_manager_test.gno │ │ │ ├── emission_reward_state.gno │ │ │ ├── emission_reward_state_test.gno │ │ │ ├── errors.gno │ │ │ ├── getter_delegation_snapshot.gno │ │ │ ├── getter_delegation_snapshot_test.gno │ │ │ ├── gnomod.toml │ │ │ ├── protocol_fee_reward_manager.gno │ │ │ ├── protocol_fee_reward_manager_test.gno │ │ │ ├── protocol_fee_reward_state.gno │ │ │ ├── protocol_fee_reward_state_test.gno │ │ │ ├── staker_delegate.gno │ │ │ ├── staker_delegate_test.gno │ │ │ ├── staker_delegation_snapshot.gno │ │ │ ├── staker_delegation_snapshot_test.gno │ │ │ ├── staker_reward.gno │ │ │ ├── staker_reward_test.gno │ │ │ ├── state.gno │ │ │ ├── state_test.gno │ │ │ ├── util.gno │ │ │ └── util_test.gno │ │ └── xgns │ │ │ ├── doc.gno │ │ │ ├── errors.gno │ │ │ ├── gnomod.toml │ │ │ ├── xgns.gno │ │ │ └── xgns_test.gno │ │ ├── launchpad │ │ ├── README.md │ │ ├── api_deposit.gno │ │ ├── api_project.gno │ │ ├── api_reward.gno │ │ ├── assert.gno │ │ ├── consts.gno │ │ ├── counter.gno │ │ ├── counter_test.gno │ │ ├── deposit.gno │ │ ├── deposit_test.gno │ │ ├── errors.gno │ │ ├── gnomod.toml │ │ ├── json_builder.gno │ │ ├── launchpad_deposit.gno │ │ ├── launchpad_deposit_gns_test.gno │ │ ├── launchpad_deposit_test.gno │ │ ├── launchpad_project.gno │ │ ├── launchpad_project_test.gno │ │ ├── launchpad_protocol_fee.gno │ │ ├── launchpad_reward.gno │ │ ├── launchpad_reward_test.gno │ │ ├── launchpad_withdraw.gno │ │ ├── launchpad_withdraw_test.gno │ │ ├── project.gno │ │ ├── project_condition.gno │ │ ├── project_condition_test.gno │ │ ├── project_test.gno │ │ ├── project_tier.gno │ │ ├── project_tier_test.gno │ │ ├── reward_manager.gno │ │ ├── reward_manager_test.gno │ │ ├── reward_state.gno │ │ ├── reward_state_test.gno │ │ ├── state.gno │ │ ├── tier_allocation_test.gno │ │ ├── tier_duration_test.gno │ │ └── utils.gno │ │ ├── pool │ │ ├── README.md │ │ ├── _helper_test.gno │ │ ├── api.gno │ │ ├── api_test.gno │ │ ├── assert.gno │ │ ├── doc.gno │ │ ├── dsl_test.gno │ │ ├── errors.gno │ │ ├── factory_param.gno │ │ ├── factory_param_test.gno │ │ ├── fuzz_test.gno │ │ ├── getter.gno │ │ ├── getter_test.gno │ │ ├── gnomod.toml │ │ ├── json.gno │ │ ├── liquidity_math.gno │ │ ├── liquidity_math_test.gno │ │ ├── manager.gno │ │ ├── manager_test.gno │ │ ├── oracle.gno │ │ ├── oracle_integration_test.gno │ │ ├── oracle_test.gno │ │ ├── pool.gno │ │ ├── pool_test.gno │ │ ├── pool_type.gno │ │ ├── position.gno │ │ ├── position_test.gno │ │ ├── protocol_fee.gno │ │ ├── protocol_fee_test.gno │ │ ├── swap.gno │ │ ├── swap_paid_test.gno │ │ ├── swap_test.gno │ │ ├── testutils.gno │ │ ├── tick.gno │ │ ├── tick_bitmap.gno │ │ ├── tick_bitmap_test.gno │ │ ├── tick_test.gno │ │ ├── transfer.gno │ │ ├── transfer_test.gno │ │ ├── type.gno │ │ ├── utils.gno │ │ └── utils_test.gno │ │ ├── position │ │ ├── README.md │ │ ├── _helper_test.gno │ │ ├── api.gno │ │ ├── api_test.gno │ │ ├── assert.gno │ │ ├── burn.gno │ │ ├── doc.gno │ │ ├── errors.gno │ │ ├── getter.gno │ │ ├── getter_test.gno │ │ ├── gnomod.toml │ │ ├── json.gno │ │ ├── liquidity_management.gno │ │ ├── liquidity_management_test.gno │ │ ├── manager.gno │ │ ├── mint.gno │ │ ├── mint_test.gno │ │ ├── native_token.gno │ │ ├── native_token_test.gno │ │ ├── position.gno │ │ ├── position_test.gno │ │ ├── reposition.gno │ │ ├── testutils.gno │ │ ├── type.gno │ │ ├── utils.gno │ │ └── utils_test.gno │ │ ├── protocol_fee │ │ ├── README.md │ │ ├── api.gno │ │ ├── api_test.gno │ │ ├── assert.gno │ │ ├── consts.gno │ │ ├── doc.gno │ │ ├── errors.gno │ │ ├── getter.gno │ │ ├── gnomod.toml │ │ ├── protocol_fee.gno │ │ ├── protocol_fee_test.gno │ │ ├── state.gno │ │ └── state_test.gno │ │ ├── router │ │ ├── README.md │ │ ├── _helper_test.gno │ │ ├── assert.gno │ │ ├── base.gno │ │ ├── base_test.gno │ │ ├── consts.gno │ │ ├── doc.gno │ │ ├── dsl_test.gno │ │ ├── errors.gno │ │ ├── exact_in.gno │ │ ├── exact_in_test.gno │ │ ├── exact_out.gno │ │ ├── exact_out_test.gno │ │ ├── fuzz_test.gno │ │ ├── gnomod.toml │ │ ├── protocol_fee_swap.gno │ │ ├── protocol_fee_swap_test.gno │ │ ├── router.gno │ │ ├── router_dry.gno │ │ ├── router_dry_test.gno │ │ ├── router_test.gno │ │ ├── swap_callback.gno │ │ ├── swap_inner.gno │ │ ├── swap_inner_test.gno │ │ ├── swap_multi.gno │ │ ├── swap_multi_test.gno │ │ ├── swap_single.gno │ │ ├── swap_single_test.gno │ │ ├── type.gno │ │ ├── type_test.gno │ │ ├── utils.gno │ │ ├── utils_test.gno │ │ └── wrap_unwrap.gno │ │ └── staker │ │ ├── README.md │ │ ├── _helper_test.gno │ │ ├── api.gno │ │ ├── assert.gno │ │ ├── calculate_pool_position_reward.gno │ │ ├── consts.gno │ │ ├── counter.gno │ │ ├── counter_test.gno │ │ ├── doc.gno │ │ ├── errors.gno │ │ ├── external_deposit_fee.gno │ │ ├── external_deposit_fee_test.gno │ │ ├── external_incentive.gno │ │ ├── external_incentive_test.gno │ │ ├── external_token_list.gno │ │ ├── external_token_list_test.gno │ │ ├── getter.gno │ │ ├── gnomod.toml │ │ ├── incentive_id.gno │ │ ├── json.gno │ │ ├── manage_pool_tier_and_warmup.gno │ │ ├── mint_stake.gno │ │ ├── pool_incentive_validation_test.gno │ │ ├── protocol_fee_unstaking.gno │ │ ├── protocol_fee_unstaking_test.gno │ │ ├── query.gno │ │ ├── query_test.gno │ │ ├── reward_calculation.gno │ │ ├── reward_calculation_canonical_env_test.gno │ │ ├── reward_calculation_canonical_test.gno │ │ ├── reward_calculation_incentives.gno │ │ ├── reward_calculation_incentives_test.gno │ │ ├── reward_calculation_pool.gno │ │ ├── reward_calculation_pool_tier.gno │ │ ├── reward_calculation_pool_tier_test.gno │ │ ├── reward_calculation_tick.gno │ │ ├── reward_calculation_tick_test.gno │ │ ├── reward_calculation_types.gno │ │ ├── reward_calculation_types_test.gno │ │ ├── reward_calculation_warmup.gno │ │ ├── reward_calculation_warmup_test.gno │ │ ├── staker.gno │ │ ├── staker_test.gno │ │ ├── type.gno │ │ ├── utils.gno │ │ ├── utils_test.gno │ │ ├── warp_unwrap_test.gno │ │ └── wrap_unwrap.gno │ └── scenario │ ├── gns │ ├── changed_halving_years_by_change_block_time_filetest.gno │ ├── distribution_start_block_height_filetest.gno │ ├── gnomod.toml │ ├── mint_gns_by_skipping_blocks_filetest.gno │ ├── mint_gns_during_all_halving_years_filetest.gno │ ├── override_distribution_height_filetest.gno │ └── verify_minted_gns_remaining_amount_filetest.gno │ ├── gov │ ├── governance │ │ ├── gnomod.toml │ │ ├── governance_proposal_community_pool_spend_filetest.gno │ │ ├── governance_proposal_execute_blocktime_filetest.gno │ │ ├── governance_proposal_execute_emission_filetest.gno │ │ ├── governance_proposal_execute_pool_fee_filetest.gno │ │ ├── governance_proposal_execute_reconfigure_filetest.gno │ │ ├── governance_proposal_multi_execute_filetest.gno │ │ ├── governance_proposal_status_update_filetest.gno │ │ ├── governance_proposal_text_filetest.gno │ │ ├── governance_vote_gov_delegated_filetest.gno │ │ ├── governance_vote_gov_delegated_undelegated_after_propose_before_vote_filetest.gno │ │ ├── governance_vote_gov_delegated_undelegated_before_propose_filetest.gno │ │ └── governance_vote_with_launchpad_xgns_filetest.gno │ └── staker │ │ ├── complex_protocol_fee_reward_filetest.gno │ │ ├── delegate_and_redelegate_with_emission_reward_filetest.gno │ │ ├── delegate_with_emission_reward_filetest.gno │ │ ├── gnomod.toml │ │ ├── launchpad_protocol_fee_reward_filetest.gno │ │ └── staker_protocol_fee_reward_filetest.gno │ ├── launchpad │ ├── gnomod.toml │ ├── launchpad_create_project_by_different_condition_token_count_filetest.gno │ ├── launchpad_create_project_by_duplicate_condition_token_filetest.gno │ ├── launchpad_create_project_by_empty_condition_token_filetest.gno │ ├── launchpad_create_project_by_insufficient_reward_token_filetest.gno │ ├── launchpad_create_project_by_invalid_condition_token_filetest.gno │ ├── launchpad_create_project_by_invalid_recipient_filetest.gno │ ├── launchpad_create_project_by_non_admin_filetest.gno │ ├── launchpad_create_project_by_unregisterd_condition_token_filetest.gno │ ├── launchpad_create_project_by_zero_reward_amount_filetest.gno │ ├── launchpad_create_project_filetest.gno │ ├── launchpad_deposit_project_multi_recipient_filetest.gno │ ├── launchpad_deposit_project_single_recipient_filetest.gno │ └── launchpad_refund_ended_project_no_deposit_filetest.gno │ ├── pool │ ├── burn0_filetest.gno │ ├── burn1_filetest.gno │ ├── burn2_filetest.gno │ ├── burn3_filetest.gno │ ├── burn4_filetest.gno │ ├── dryswap0_filetest.gno │ ├── dryswap1_filetest.gno │ ├── dryswap2_filetest.gno │ ├── dryswap3_filetest.gno │ ├── dryswap4_filetest.gno │ ├── gnomod.toml │ ├── integrated_filetest.gno │ ├── mint_filetest.gno │ ├── multi_token_filetest.gno │ ├── native_swap_filetest.gno │ ├── pool_filetest.gno │ ├── pool_init_filetest.gno │ ├── pool_limit_filetest.gno │ ├── pool_limit_order_negative_filetest.gno │ ├── pool_limit_with_protocol_fee_fail_filetest.gno │ ├── pool_limit_with_protocol_fee_filetest.gno │ ├── single_lp_filetest.gno │ ├── swap_dryswap_filetest.gno │ ├── tick_bitmap_filetest.gno │ └── tick_transaction_filetest.gno │ ├── position │ ├── basic_position_management_filetest.gno │ ├── capital_efficiency_boundaries_test.gno │ ├── edge_case_fee_tier_boundary_ticks_filetest.gno │ ├── edge_case_mint_burn_roundtrip_invariant_filetest.gno │ ├── edge_case_monotonicity_filetest.gno │ ├── edge_case_overflow_protection_extreme_filetest.gno │ ├── edge_case_rounding_consistency_filetest.gno │ ├── edge_case_ultra_tight_range_filetest.gno │ ├── gnomod.toml │ ├── liquidity_management_filetest.gno │ ├── native_token_pool_out_of_range_mint_position_filetest.gno │ ├── position_collect_fee_filetest.gno │ ├── position_collect_fee_with_protocol_fee_filetest.gno │ ├── position_full_with_emission_filetest.gno │ ├── position_increase_decrease_filetest.gno │ ├── position_increase_with_native_coin_filetest.gno │ ├── position_increase_with_zero_native_coin_filetest.gno │ ├── position_mint_and_balance_check_filetest.gno │ ├── position_mint_gnot_grc20_range_filetest.gno │ ├── position_mint_swap_burn_filetest.gno │ ├── position_minting_filetest.gno │ ├── position_multi_user_fee_filetest.gno │ ├── position_multi_user_same_key_filetest.gno │ ├── position_native_token_filetest.gno │ ├── position_reposition_filetest.gno │ ├── position_same_user_fee_distribution_filetest.gno │ ├── position_swap_fee_distribution_filetest.gno │ ├── position_unclaimed_fee_filetest.gno │ ├── verify_symmetric_liquidity_filetest.gno │ ├── wugnot_gns_100_pool_burn_position_with_narrow_range_filetest.gno │ └── wugnot_gns_100_pool_mint_position_filetest.gno │ ├── rbac │ ├── gnomod.toml │ ├── rbac_common_usage_filetest.gno │ └── rbac_full_scenario_filetest.gno │ ├── router │ ├── basic_wrap_unwrap_operations_filetest.gno │ ├── dryswap_swap_consistency_filetest.gno │ ├── exact_in_single_swap_route_filetest.gno │ ├── exact_in_single_swap_route_partial_filetest.gno │ ├── exact_in_swap_route_with_input_native_token_filetest.gno │ ├── exact_in_swap_route_with_native_token_filetest.gno │ ├── exact_in_swap_route_with_protocol_fee_filetest.gno │ ├── exact_in_swap_route_with_wrapped_native_output_token_filetest.gno │ ├── exact_out_gnot_swap_route_with_single_route_filetest.gno │ ├── exact_out_single_swap_route_filetest.gno │ ├── exact_out_single_swap_route_native_partial_filetest.gno │ ├── exact_out_swap_route_with_multi_route_filetest.gno │ ├── exact_out_swap_route_with_protocol_fee_filetest.gno │ ├── exact_out_swap_route_with_single_route_filetest.gno │ ├── exact_out_swap_route_with_wrapped_native_output_token_filetest.gno │ ├── fee_calculation_accuracy_filetest.gno │ ├── gnomod.toml │ ├── position_lifecycle_with_native_token_filetest.gno │ ├── position_router_swaps_multi_position_filetest.gno │ ├── price_impact_analysis_filetest.gno │ ├── router_all_2_route_2_hop_filetest.gno │ ├── router_spec_#1_ExactIn_filetest.gno │ ├── router_spec_#2_ExactIn_filetest.gno │ ├── router_spec_#3_ExactIn_filetest.gno │ ├── router_spec_#4_ExactIn_filetest.gno │ ├── router_spec_#5_ExactOut_filetest.gno │ ├── router_spec_#6_ExactOut_filetest.gno │ ├── router_spec_#7_ExactOut_filetest.gno │ ├── router_spec_#8_ExactOut_filetest.gno │ ├── router_swap_native_token_integration_filetest.gno │ ├── router_swap_route_1route_1hop_all_liquidity_exact_in_filetest.gno │ ├── router_swap_route_1route_1hop_all_liquidity_exact_out_filetest.gno │ ├── router_swap_route_1route_1hop_filetest.gno │ ├── router_swap_route_1route_1hop_out_range_filetest.gno │ ├── router_swap_route_1route_1hop_wrapped_native_in_out_filetest.gno │ ├── router_swap_route_1route_2hop_wrapped_native_in_out_filetest.gno │ ├── router_swap_route_1route_3hop_wrapped_native_middle_filetest.gno │ ├── router_swap_route_2route_2hop_filetest.gno │ ├── router_swap_validate_balance_filetest.gno │ └── swap_route_with_gnot_wugnot_filetest.gno │ ├── staker │ ├── average_block_time_change_from_gns_filetest.gno │ ├── external_incentive_complete_distribution_filetest.gno │ ├── external_incentive_drain_filetest.gno │ ├── external_incentive_dust_refund_filetest.gno │ ├── external_incentive_refund_gnot_filetest.gno │ ├── external_incentive_refund_wugnot_filetest.gno │ ├── full_internal_external_filetest.gno │ ├── gnomod.toml │ ├── insufficient_external_reward_filetest.gno │ ├── mint_and_staker_position_filetest.gno │ ├── more_01_single_position_for_each_warmup_tier_total_4_position_internal_only_filetest.gno │ ├── more_02_single_position_for_each_warmup_tier_total_4_position_two_external_filetest.gno │ ├── more_04_positions_with_different_liquidity_and_in_range_change_by_swap_filetest.gno │ ├── no_position_to_give_reward_filetest.gno │ ├── partial_incentive_failure_filetest.gno │ ├── pool_add_to_tier2_and_change_to_tier3_internal_filetest.gno │ ├── pool_add_to_tier2_and_removed_internal_filetest.gno │ ├── pool_multi_position_tier_removal_filetest.gno │ ├── pool_partial_collect_tier_removal_filetest.gno │ ├── pool_tier_change_reward_filetest.gno │ ├── pool_tier_removal_readd_check_rewards_filetest.gno │ ├── pool_tier_removal_readd_filetest.gno │ ├── position_inrange_change_by_swap_external_filetest.gno │ ├── position_inrange_change_by_swap_internal_filetest.gno │ ├── reward_for_user_collect_change_by_collecting_reward_external_filetest.gno │ ├── reward_for_user_collect_change_by_collecting_reward_internal_filetest.gno │ ├── short_warmup_internal_gnot_gns_3000_filetest.gno │ ├── single_gns_external_ends_filetest.gno │ ├── single_position_stake_unstake_restake_filetest.gno │ ├── single_position_stake_unstake_same_block_filetest.gno │ ├── staked_liquidity_change_by_staking_unstaking_external_filetest.gno │ ├── staked_liquidity_change_by_staking_unstaking_internal_filetest.gno │ ├── staker_collect_reward_vulnerability_filetest.gno │ ├── staker_detier_unstake_filetest.gno │ ├── staker_double_unstake_prevention_filetest.gno │ ├── staker_emission_cache_sync_issue_filetest.gno │ ├── staker_external_incentive_end_unstake_filetest.gno │ ├── staker_external_native_coin_filetest.gno │ ├── staker_manage_pool_tiers_filetest.gno │ ├── staker_mint_and_stake_filetest.gno │ ├── staker_native_create_collect_unstake_filetest.gno │ ├── staker_native_create_collect_unstake_with_finish_filetest.gno │ ├── staker_nft_transfer_01_filetest.gno │ ├── staker_nft_transfer_02_filetest.gno │ ├── staker_nft_transfer_03_filetest.gno │ ├── staker_short_warmup_period_calculate_pool_position_reward_apis_filetest.gno │ ├── staker_short_warmup_period_external_10_filetest.gno │ ├── staker_short_warmup_period_external_12_filetest.gno │ ├── staker_short_warmup_period_external_13_gns_external_filetest.gno │ ├── staker_short_warmup_period_external_14_position_in_out_range_changed_by_swap_filetest.gno │ ├── staker_short_warmup_period_external_15_90d_filetest.gno │ ├── staker_short_warmup_period_external_16_180d_filetest.gno │ ├── staker_short_warmup_period_external_17_365d_filetest.gno │ ├── staker_short_warmup_period_internal_01_filetest.gno │ ├── staker_short_warmup_period_internal_02_small_liq_filetest.gno │ ├── staker_short_warmup_period_internal_03_change_tier_filetest.gno │ ├── staker_short_warmup_period_internal_04_remove_tier_filetest.gno │ ├── staker_short_warmup_period_internal_05_position_in_out_range_changed_by_swap_filetest.gno │ ├── staker_short_warmup_period_internal_gns_and_external_gns_90_filetest.gno │ ├── token_uri_in_same_block_filetest.gno │ ├── tracking_unclaimed_period_validation_filetest.gno │ ├── tracking_unclaimed_period_validation_with_multi_position_filetest.gno │ └── unstake_after_tier_removal_filetest.gno │ └── upgrade │ ├── change_gns_distribution_target_filetest.gno │ ├── gnomod.toml │ ├── upgrade_with_withdraw_and_halt_filetest.gno │ └── upgrade_with_withdraw_filetest.gno ├── docker-compose.yml ├── go.mod ├── go.sum ├── remove-test.sh ├── scripts ├── test.sh ├── test_values.mk └── test_values.sh ├── sonar-project.properties └── tests ├── _info.mk ├── collect_fee_test.mk ├── deploy.mk ├── deploy ├── codegen.go └── generate │ ├── main.go │ └── main_test.go ├── integration ├── doc.go ├── node_testing.go ├── pkgloader.go ├── process.go ├── process │ └── main.go ├── process_test.go ├── testdata │ ├── deploy.txtar │ ├── public_tx.txtar │ └── upgrade_contracts.txtar ├── testdata_test.go ├── testscript_gnoland.go ├── testscript_testing.go ├── utils.go └── utils_test.go └── test.mk /.dockerignore: -------------------------------------------------------------------------------- 1 | # Version control 2 | .git 3 | .gitignore 4 | 5 | # Environment variables 6 | .env 7 | .env.* 8 | 9 | # Python 10 | __pycache__/ 11 | *.py[cod] 12 | *$py.class 13 | *.so 14 | .Python 15 | .pytest_cache/ 16 | .coverage 17 | htmlcov/ 18 | 19 | # Logs and databases 20 | *.log 21 | *.sqlite3 22 | 23 | # IDE specific files 24 | .idea/ 25 | .vscode/ 26 | *.swp 27 | *.swo 28 | 29 | # OS specific files 30 | .DS_Store 31 | Thumbs.db 32 | 33 | # Build and distribution 34 | dist/ 35 | build/ 36 | *.egg-info/ 37 | 38 | # Virtual environment 39 | .venv/ 40 | venv/ 41 | ENV/ 42 | 43 | # Docker 44 | Dockerfile 45 | .dockerignore -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/security_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 🛡 Security Report 3 | about: Report a security vulnerability privately 4 | labels: security 5 | --- 6 | 7 | 11 | 12 | **Describe the vulnerability** 13 | A clear and concise description… 14 | 15 | **Steps to reproduce** 16 | 1. … 17 | 2. … 18 | 19 | **Impact** 20 | What happens and how serious… 21 | 22 | **Expected behaviour** 23 | Tell us what should happen 24 | 25 | **Actual behaviour** 26 | Tell us what happens instead 27 | 28 | **Logs** 29 | Please paste any logs here that demonstrate the issue, if they exist 30 | 31 | **Proposed solution** 32 | If you have an idea of how to fix this issue, please write it down here, so we can begin discussing it -------------------------------------------------------------------------------- /.github/scripts/run_tests.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require 'optparse' 3 | require 'pathname' 4 | require 'fileutils' 5 | require 'open3' 6 | 7 | class TestRunner 8 | def initialize(folder, root_dir = nil) 9 | @folder = folder 10 | @root_dir = root_dir || "/home/runner/work/gnoswap/gnoswap/gno" 11 | end 12 | 13 | def run_command(command) 14 | puts "> #{command}" 15 | stdout, stderr, status = Open3.capture3(command) 16 | puts stdout unless stdout.empty? 17 | puts stderr unless stderr.empty? 18 | 19 | unless status.success? 20 | puts "Error: Command failed with status #{status.exitstatus}" 21 | exit status.exitstatus 22 | end 23 | end 24 | 25 | def run_unit_tests 26 | puts "Running unit tests for #{@folder}" 27 | 28 | # With gnowork.toml, we can run tests directly from each directory 29 | # No need to search for workspace root anymore 30 | test_dir = File.expand_path(@folder) 31 | 32 | unless File.directory?(test_dir) 33 | puts "Error: Directory #{test_dir} does not exist" 34 | exit 1 35 | end 36 | 37 | # Change to the test directory 38 | Dir.chdir(test_dir) do 39 | puts "Running tests in: #{Dir.pwd}" 40 | 41 | # Run gno test -v . to execute all tests in the current directory 42 | run_command("gno test -v . -root-dir #{@root_dir}") 43 | end 44 | end 45 | 46 | 47 | def run_all 48 | run_unit_tests 49 | end 50 | end 51 | 52 | if __FILE__ == $0 53 | options = {} 54 | OptionParser.new do |opts| 55 | opts.banner = "Usage: run_tests.rb [options]" 56 | 57 | opts.on("-f", "--folder FOLDER", "Test folder path") do |f| 58 | options[:folder] = f 59 | end 60 | 61 | opts.on("-r", "--root-dir DIR", "Root directory") do |r| 62 | options[:root_dir] = r 63 | end 64 | 65 | opts.on("-h", "--help", "Show this help message") do 66 | puts opts 67 | exit 68 | end 69 | end.parse! 70 | 71 | if options[:folder].nil? 72 | puts "Error: Please provide a folder path with -f or --folder" 73 | exit 1 74 | end 75 | 76 | runner = TestRunner.new(options[:folder], options[:root_dir]) 77 | runner.run_all 78 | end 79 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release Management 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | paths: 8 | - 'CHANGELOG.md' 9 | 10 | permissions: 11 | contents: write 12 | pull-requests: write 13 | 14 | jobs: 15 | create-release: 16 | runs-on: ubuntu-latest 17 | steps: 18 | - name: Checkout code 19 | uses: actions/checkout@v4 20 | with: 21 | fetch-depth: 0 22 | 23 | - name: Get version from CHANGELOG 24 | id: changelog 25 | run: | 26 | VERSION=$(grep -m 1 "## \[.*\]" CHANGELOG.md | grep -o "[0-9]\+\.[0-9]\+\.[0-9]\+") 27 | echo "version=$VERSION" >> $GITHUB_OUTPUT 28 | 29 | - name: Create Release 30 | if: steps.changelog.outputs.version != '' 31 | env: 32 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 33 | VERSION: ${{ steps.changelog.outputs.version }} 34 | run: | 35 | if ! git tag | grep -q "^v$VERSION$"; then 36 | # Extract changelog content for this version 37 | CHANGELOG_CONTENTS=$(awk "/## \[$VERSION\]/,/## \[/{ if (!/## \[$VERSION\]/ && !/## \[/) print }" CHANGELOG.md) 38 | 39 | # Create and push tag 40 | git config --local user.email "action@github.com" 41 | git config --local user.name "GitHub Action" 42 | git tag -a "v$VERSION" -m "Release version $VERSION" 43 | git push origin "v$VERSION" 44 | 45 | # Create GitHub release 46 | gh release create "v$VERSION" \ 47 | --title "Release v$VERSION" \ 48 | --notes "$CHANGELOG_CONTENTS" \ 49 | --draft=false \ 50 | --prerelease=false 51 | fi 52 | -------------------------------------------------------------------------------- /.github/workflows/run_test.yml: -------------------------------------------------------------------------------- 1 | name: run-test 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | generate-matrix: 10 | runs-on: ubuntu-latest 11 | outputs: 12 | matrix: ${{ steps.set-matrix.outputs.matrix }} 13 | 14 | steps: 15 | - name: Check out repository 16 | uses: actions/checkout@v4 17 | 18 | - name: Set up Ruby 19 | uses: ruby/setup-ruby@v1 20 | with: 21 | ruby-version: 3.4 22 | 23 | - name: Install bundler 24 | run: gem install bundler 25 | 26 | - name: Install ruby gems 27 | run: bundle install 28 | 29 | - name: Generate test matrix 30 | id: set-matrix 31 | run: | 32 | chmod +x .github/scripts/generate_matrix.rb 33 | echo "matrix=$(ruby .github/scripts/generate_matrix.rb contract tests/scenario)" >> $GITHUB_OUTPUT 34 | 35 | test-gnoswap: 36 | needs: generate-matrix 37 | runs-on: ubuntu-latest 38 | strategy: 39 | fail-fast: false 40 | matrix: ${{fromJson(needs.generate-matrix.outputs.matrix)}} 41 | 42 | steps: 43 | - name: Check out gnoswap repo 44 | uses: actions/checkout@v4 45 | 46 | - name: Check out gno(master - before change std prefix) 47 | uses: actions/checkout@v4 48 | with: 49 | repository: gnoswap-labs/gno 50 | ref: master 51 | path: ./gno 52 | 53 | - name: Set up Go 54 | uses: actions/setup-go@v5 55 | with: 56 | go-version: "1.22" 57 | 58 | - name: Install gno 59 | run: | 60 | cd gno 61 | make install.gno 62 | 63 | - name: "Run tests for ${{ matrix.name }}" 64 | run: | 65 | chmod +x .github/scripts/run_tests.rb 66 | ruby .github/scripts/run_tests.rb \ 67 | -f "${{ matrix.folder }}" \ 68 | -r "/home/runner/work/gnoswap/gnoswap/gno" 69 | -------------------------------------------------------------------------------- /.github/workflows/tlin_check.yml: -------------------------------------------------------------------------------- 1 | name: tlin-check 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | tlin-check: 10 | strategy: 11 | fail-fast: false 12 | 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - name: checkout code 17 | uses: actions/checkout@v4 18 | with: 19 | ref: ${{ github.event.pull_request.head.ref }} 20 | 21 | - name: checkout tlin 22 | uses: actions/checkout@v4 23 | with: 24 | repository: gnoverse/tlin 25 | ref: main 26 | path: ./tlin 27 | 28 | - name: setup go 29 | uses: actions/setup-go@v5 30 | with: 31 | go-version: 1.22 32 | 33 | - name: changed files 34 | id: changed_files 35 | uses: tj-actions/changed-files@v45 36 | with: 37 | files: | 38 | *.gno 39 | **.gno 40 | 41 | - name: install tlin 42 | run: | 43 | cd tlin 44 | go install ./cmd/tlin 45 | 46 | - name: tlin check 47 | run: | 48 | for file in ${{ steps.changed_files.outputs.all_changed_files }}; do 49 | echo "checking ${file} ..." 50 | tlin ${file} 51 | done 52 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.11-slim 2 | 3 | # Create a non-root user 4 | RUN groupadd -r appuser && useradd -r -g appuser -m appuser 5 | 6 | RUN pip install uv 7 | 8 | WORKDIR /app 9 | 10 | COPY requirements.txt . 11 | 12 | # install dependencies 13 | RUN uv venv && \ 14 | . .venv/bin/activate && \ 15 | uv pip install -r requirements.txt 16 | 17 | # Copy only necessary files 18 | COPY setup.py . 19 | COPY scripts/ scripts/ 20 | COPY contract/ contract/ 21 | COPY mapping.yml . 22 | 23 | # Change ownership of the application files 24 | RUN chown -R appuser:appuser /app 25 | 26 | # Switch to non-root user 27 | USER appuser 28 | 29 | CMD ["python3", "setup.py"] -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | gem 'toml' 3 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Reporting a Vulnerability 4 | If you've identified a vulnerability, please **DO NOT** open a new public issue. Instead, report it through one of the following venues: 5 | 6 | * Submit an advisory through GitHub: https://github.com/gnoswap-labs/gnoswap/security/advisories/new 7 | * Email info [at-symbol] gnoswap [dot] io. If you are concerned about confidentiality e.g. because of a high-severity issue, you may email us for PGP or Signal contact details. If you’ve found multiple vulnerabilities, please submit one per email. 8 | 9 | We will respond within 3 business days to all received reports. 10 | 11 | Thank you for helping to keep our ecosystem safe! -------------------------------------------------------------------------------- /audits/[BigInteger] Gnoswap Audit Report (ENG)_Final.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gnoswap-labs/gnoswap/5a61b832073696fbc5e7d6adeadc468f9b3be4ad/audits/[BigInteger] Gnoswap Audit Report (ENG)_Final.pdf -------------------------------------------------------------------------------- /changelog.md: -------------------------------------------------------------------------------- 1 | # CHANGELOG 2 | 3 | 24 | 25 | All notable changes to this project will be documented in this file. 26 | 27 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), 28 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 29 | 30 | ## [Unreleased] 31 | 32 | ### Added 33 | - 34 | 35 | ### Changed 36 | - 37 | 38 | ### Deprecated 39 | - 40 | 41 | ### Removed 42 | - 43 | 44 | ### Fixed 45 | - 46 | 47 | ### Security 48 | - 49 | 50 | ## [1.0.0] - YYYY-MM-DD 51 | ### Added 52 | - First stable release 53 | - [Feature description] 54 | 55 | ### Changed 56 | - [Change description] 57 | 58 | ### Fixed 59 | - [Fix description] 60 | 61 | 83 | 84 | [Unreleased]: https://github.com/gnoswap-labs/gnoswap/compare/v1.0.0...HEAD 85 | [1.0.0]: https://github.com/gnoswap-labs/gnoswap/releases/tag/v1.0.0 86 | -------------------------------------------------------------------------------- /contract/gnowork.toml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gnoswap-labs/gnoswap/5a61b832073696fbc5e7d6adeadc468f9b3be4ad/contract/gnowork.toml -------------------------------------------------------------------------------- /contract/p/gnoswap/fuzz/gnomod.toml: -------------------------------------------------------------------------------- 1 | module = "gno.land/p/gnoswap/fuzz" 2 | gno = "0.9" 3 | -------------------------------------------------------------------------------- /contract/p/gnoswap/fuzz/seed.gno: -------------------------------------------------------------------------------- 1 | package fuzz 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | // SeedManager manages unique seed generation for fuzz testing. 8 | // Each instance maintains its own seed sequence, avoiding global state issues 9 | // and ensuring diverse random sequences across different generators. 10 | type SeedManager struct { 11 | baseSeed uint64 12 | counter uint64 13 | } 14 | 15 | // NewSeedManager creates a new seed manager with time-based initial seed. 16 | func NewSeedManager() *SeedManager { 17 | return &SeedManager{ 18 | baseSeed: uint64(time.Now().UnixNano()), 19 | counter: 0, 20 | } 21 | } 22 | 23 | // NewSeedManagerWithSeed creates a seed manager with the specified base seed for reproducible tests. 24 | func NewSeedManagerWithSeed(seed uint64) *SeedManager { 25 | return &SeedManager{ 26 | baseSeed: seed, 27 | counter: 0, 28 | } 29 | } 30 | 31 | // NextSeed returns the next unique seed value using prime multiplier 2654435761 for good distribution. 32 | func (sm *SeedManager) NextSeed() uint64 { 33 | sm.counter++ 34 | return sm.baseSeed + sm.counter*2654435761 35 | } 36 | 37 | // CreateIntGenerator creates an IntGenerator with a unique seed. 38 | func (sm *SeedManager) CreateIntGenerator(min, max int) Generator { 39 | return IntRangeWithSeed(min, max, sm.NextSeed()) 40 | } 41 | 42 | // CreateUint32Generator creates a Uint32Generator with a unique seed. 43 | func (sm *SeedManager) CreateUint32Generator(min, max uint32) Generator { 44 | return Uint32RangeWithSeed(min, max, sm.NextSeed()) 45 | } 46 | 47 | // CreateBoolGenerator creates a BoolGenerator with a unique seed. 48 | func (sm *SeedManager) CreateBoolGenerator() Generator { 49 | return BoolWithSeed(sm.NextSeed()) 50 | } 51 | 52 | // CreateStringGenerator creates a StringGenerator with a unique seed. 53 | func (sm *SeedManager) CreateStringGenerator(minLen, maxLen int) Generator { 54 | return StringWithSeed(minLen, maxLen, sm.NextSeed()) 55 | } 56 | -------------------------------------------------------------------------------- /contract/p/gnoswap/gnsmath/bit_math.gno: -------------------------------------------------------------------------------- 1 | package gnsmath 2 | 3 | import ( 4 | u256 "gno.land/p/gnoswap/uint256" 5 | ) 6 | 7 | var ( 8 | msbShifts = []bitShift{ 9 | {u256.Zero().Lsh(u256.One(), 128), 128}, // 2^128 10 | {u256.Zero().Lsh(u256.One(), 64), 64}, // 2^64 11 | {u256.Zero().Lsh(u256.One(), 32), 32}, // 2^32 12 | {u256.Zero().Lsh(u256.One(), 16), 16}, // 2^16 13 | {u256.Zero().Lsh(u256.One(), 8), 8}, // 2^8 14 | {u256.Zero().Lsh(u256.One(), 4), 4}, // 2^4 15 | {u256.Zero().Lsh(u256.One(), 2), 2}, // 2^2 16 | {u256.Zero().Lsh(u256.One(), 1), 1}, // 2^1 17 | } 18 | 19 | lsbShifts = []bitShift{ 20 | {u256.Zero().Sub(u256.Zero().Lsh(u256.One(), 128), u256.One()), 128}, // 2^128 - 1 21 | {u256.Zero().Sub(u256.Zero().Lsh(u256.One(), 64), u256.One()), 64}, // 2^64 - 1 22 | {u256.Zero().Sub(u256.Zero().Lsh(u256.One(), 32), u256.One()), 32}, // 2^32 - 1 23 | {u256.Zero().Sub(u256.Zero().Lsh(u256.One(), 16), u256.One()), 16}, // 2^16 - 1 24 | {u256.Zero().Sub(u256.Zero().Lsh(u256.One(), 8), u256.One()), 8}, // 2^8 - 1 25 | {u256.NewUint(0xf), 4}, // 2^4 - 1 = 15 26 | {u256.NewUint(0x3), 2}, // 2^2 - 1 = 3 27 | {u256.NewUint(0x1), 1}, // 2^1 - 1 = 1 28 | } 29 | ) 30 | 31 | // bitShift represents a bit pattern and corresponding shift amount for bit manipulation. 32 | type bitShift struct { 33 | bitPattern *u256.Uint 34 | shift uint 35 | } 36 | 37 | // BitMathMostSignificantBit returns the 0-based position of the most significant bit in x. 38 | // This function is essential for AMM calculations involving price ranges and tick boundaries. 39 | // Panics if x is zero. 40 | func BitMathMostSignificantBit(x *u256.Uint) uint8 { 41 | if x.IsZero() { 42 | panic(errMSBZeroInput) 43 | } 44 | 45 | temp := x.Clone() 46 | r := uint8(0) 47 | 48 | for _, s := range msbShifts { 49 | if temp.Gte(s.bitPattern) { 50 | temp = temp.Rsh(temp, s.shift) 51 | r += uint8(s.shift) 52 | } 53 | } 54 | 55 | return r 56 | } 57 | 58 | // BitMathLeastSignificantBit returns the 0-based position of the least significant bit in x. 59 | // This function is used in AMM calculations for efficient bit manipulation and range queries. 60 | // Panics if x is zero. 61 | func BitMathLeastSignificantBit(x *u256.Uint) uint8 { 62 | if x.IsZero() { 63 | panic(errLSBZeroInput) 64 | } 65 | 66 | temp := x.Clone() 67 | hasSetBits := u256.Zero() 68 | r := uint8(255) 69 | 70 | for _, s := range lsbShifts { 71 | hasSetBits = hasSetBits.And(temp, s.bitPattern) 72 | if !hasSetBits.IsZero() { 73 | r -= uint8(s.shift) 74 | } else { 75 | temp = temp.Rsh(temp, s.shift) 76 | } 77 | } 78 | 79 | return r 80 | } 81 | -------------------------------------------------------------------------------- /contract/p/gnoswap/gnsmath/doc.gno: -------------------------------------------------------------------------------- 1 | // Package gnsmath implements core mathematical operations for GnoSwap's AMM. 2 | // 3 | // This package provides the fundamental calculations for concentrated liquidity, 4 | // including sqrt price math, swap calculations, and bit manipulation utilities. 5 | // All operations use Q96 and Q160 fixed-point arithmetic for precision. 6 | // 7 | // The implementation follows Uniswap V3's mathematical model, ensuring 8 | // compatibility and correctness for cross-chain liquidity operations. 9 | package gnsmath 10 | -------------------------------------------------------------------------------- /contract/p/gnoswap/gnsmath/errors.gno: -------------------------------------------------------------------------------- 1 | package gnsmath 2 | 3 | import ( 4 | "errors" 5 | ) 6 | 7 | var ( 8 | errInvalidPoolSqrtPrice = errors.New("invalid pool sqrt price calculation: product/amount != sqrtPX96 or numerator1 <= product") 9 | errSqrtPriceExceedsQuotient = errors.New("sqrt price exceeds calculated quotient") 10 | errSqrtPriceZero = errors.New("sqrtPX96 should not be zero") 11 | errLiquidityZero = errors.New("liquidity should not be zero") 12 | errSqrtRatioAX96Zero = errors.New("sqrtRatioAX96 must be greater than zero") 13 | errAmount0DeltaOverflow = errors.New("GetAmount0Delta: overflow") 14 | errAmount1DeltaOverflow = errors.New("GetAmount1Delta: overflow") 15 | errMSBZeroInput = errors.New("input for MSB calculation should not be zero") 16 | errLSBZeroInput = errors.New("input for LSB calculation should not be zero") 17 | errGetAmount0DeltaNilInput = errors.New("GetAmount0Delta: input parameters cannot be nil") 18 | errGetAmount1DeltaNilInput = errors.New("GetAmount1Delta: input parameters cannot be nil") 19 | ) 20 | -------------------------------------------------------------------------------- /contract/p/gnoswap/gnsmath/gnomod.toml: -------------------------------------------------------------------------------- 1 | module = "gno.land/p/gnoswap/gnsmath" 2 | gno = "0.9" 3 | -------------------------------------------------------------------------------- /contract/p/gnoswap/int256/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Trịnh Đức Bảo Linh(Kevin) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /contract/p/gnoswap/int256/README.md: -------------------------------------------------------------------------------- 1 | # int256 2 | 3 | 256-bit signed integer arithmetic for GnoSwap. 4 | 5 | ## Overview 6 | 7 | Fixed-size 256-bit signed integer library optimized for AMM calculations with overflow detection. 8 | 9 | ## Features 10 | 11 | - Fixed 256-bit size (predictable gas costs) 12 | - Two's complement representation 13 | - Overflow detection on all operations 14 | - AMM-optimized functions 15 | - Range: -(2^255) to 2^255-1 16 | 17 | ## Usage 18 | 19 | ```go 20 | import i256 "gno.land/p/gnoswap/int256" 21 | 22 | // Create values 23 | a := i256.NewInt(100) 24 | b := i256.MustFromDecimal("-1000") 25 | 26 | // Arithmetic with overflow detection 27 | result, overflow := new(i256.Int).AddOverflow(a, b) 28 | if overflow { 29 | // Handle overflow 30 | } 31 | ``` 32 | 33 | ## Implementation 34 | 35 | Built on [uint256](../uint256) for underlying arithmetic. -------------------------------------------------------------------------------- /contract/p/gnoswap/int256/absolute.gno: -------------------------------------------------------------------------------- 1 | package int256 2 | 3 | import ( 4 | "gno.land/p/gnoswap/uint256" 5 | ) 6 | 7 | // Abs returns the absolute value of z. 8 | func (z *Int) Abs() *uint256.Uint { 9 | return z.abs.Clone() 10 | } 11 | 12 | // AbsGt returns true if the absolute value of z is greater than x. 13 | func (z *Int) AbsGt(x *uint256.Uint) bool { 14 | return z.abs.Gt(x) 15 | } 16 | 17 | // AbsLt returns true if the absolute value of z is less than x. 18 | func (z *Int) AbsLt(x *uint256.Uint) bool { 19 | return z.abs.Lt(x) 20 | } 21 | 22 | // AbsOverflow sets z to the absolute value of x and returns z and whether overflow occurred. 23 | // Overflow occurs when x is the minimum int256 value (-2^255), as its absolute value (2^255) 24 | // cannot be represented in a signed 256-bit integer. 25 | func (z *Int) AbsOverflow(x *Int) (*Int, bool) { 26 | z = z.initiateAbs() 27 | 28 | // overflow can be happen when negating a minimum of int256 value 29 | if x.neg && x.abs.Eq(MinInt256().abs) { 30 | z.Set(x) // keep the original value 31 | return z, true 32 | } 33 | 34 | z.abs.Set(x.abs) 35 | z.neg = false 36 | 37 | return z, false 38 | } 39 | -------------------------------------------------------------------------------- /contract/p/gnoswap/int256/doc.gno: -------------------------------------------------------------------------------- 1 | // Package int256 implements 256-bit signed integer arithmetic for GnoSwap. 2 | // 3 | // This package provides an Int type that represents a 256-bit signed integer 4 | // using two's complement representation. It supports the full range from 5 | // -(2^255) to 2^255-1, with arithmetic operations that detect overflow. 6 | // 7 | // The implementation follows Ethereum's int256 semantics, ensuring compatibility 8 | // for cross-chain DeFi protocols. Operations are optimized for common AMM 9 | // calculations including tick math and price computations. 10 | // 11 | // Critical operations like Add, Sub, and Mul return overflow flags, enabling 12 | // safe handling of edge cases in financial calculations. 13 | package int256 14 | -------------------------------------------------------------------------------- /contract/p/gnoswap/int256/gnomod.toml: -------------------------------------------------------------------------------- 1 | module = "gno.land/p/gnoswap/int256" 2 | gno = "0.9" 3 | -------------------------------------------------------------------------------- /contract/p/gnoswap/rbac/README.md: -------------------------------------------------------------------------------- 1 | # RBAC 2 | 3 | Role-Based Access Control package for Gno smart contracts. 4 | 5 | ## Overview 6 | 7 | Flexible RBAC system enabling dynamic role and permission management without contract redeployment. 8 | 9 | ## Features 10 | 11 | - Dynamic role registration 12 | - Multiple permissions per role 13 | - Declarative role definition 14 | - Custom permission logic 15 | - Runtime updates 16 | 17 | ## Core API 18 | 19 | ```go 20 | // Create RBAC manager 21 | func New() *RBAC 22 | 23 | // Role management 24 | func (rb *RBAC) RegisterRole(roleName string) error 25 | func (rb *RBAC) DeclareRole(roleName string, opts ...RoleOption) error 26 | 27 | // Permission management 28 | func (rb *RBAC) RegisterPermission(roleName, permissionName string, checker PermissionChecker) error 29 | func (rb *RBAC) UpdatePermission(roleName, permissionName string, newChecker PermissionChecker) error 30 | func (rb *RBAC) RemovePermission(roleName, permissionName string) error 31 | 32 | // Access control 33 | func (rb *RBAC) CheckPermission(roleName, permissionName string, caller Address) error 34 | 35 | // Permission checker type 36 | type PermissionChecker func(caller std.Address) error 37 | ``` 38 | 39 | ## Usage 40 | 41 | ```go 42 | // Create manager 43 | manager := rbac.New() 44 | 45 | // Register role 46 | manager.RegisterRole("admin") 47 | 48 | // Add permission 49 | adminChecker := func(caller std.Address) error { 50 | if caller != adminAddr { 51 | return errors.New("not admin") 52 | } 53 | return nil 54 | } 55 | manager.RegisterPermission("admin", "access", adminChecker) 56 | 57 | // Declarative role setup 58 | manager.DeclareRole("editor", 59 | rbac.WithPermission("edit", editorChecker)) 60 | 61 | // Check access 62 | err := manager.CheckPermission("admin", "access", caller) 63 | ``` 64 | 65 | ## Architecture 66 | 67 | ``` 68 | Client → RBAC → Role → PermissionChecker 69 | ↓ ↓ ↓ 70 | Manager Storage Validation 71 | ``` 72 | 73 | ## Security 74 | 75 | - No direct address-to-role mapping 76 | - Custom validation logic per permission 77 | - Runtime permission updates 78 | - Isolated permission checks -------------------------------------------------------------------------------- /contract/p/gnoswap/rbac/errors.gno: -------------------------------------------------------------------------------- 1 | package rbac 2 | 3 | import ( 4 | "errors" 5 | ) 6 | 7 | var ( 8 | ErrNoPendingOwner = errors.New("no pending owner") 9 | ErrUnauthorized = errors.New("caller is not owner") 10 | ErrPendingUnauthorized = errors.New("caller is not pending owner") 11 | ErrInvalidAddress = errors.New("invalid address") 12 | 13 | ErrInvalidRoleName = errors.New("invalid role name") 14 | ErrRoleDoesNotExist = errors.New("role does not exist") 15 | ErrRoleAlreadyExists = errors.New("role already exists") 16 | ErrCannotRegisterSystemRole = errors.New("cannot register system role") 17 | ErrCannotRemoveSystemRole = errors.New("cannot remove system role") 18 | ErrVersionDoesNotExist = errors.New("version does not exist") 19 | ErrCannotUpdateVersion = errors.New("cannot update version") 20 | ) 21 | -------------------------------------------------------------------------------- /contract/p/gnoswap/rbac/gnomod.toml: -------------------------------------------------------------------------------- 1 | module = "gno.land/p/gnoswap/rbac" 2 | gno = "0.9" 3 | -------------------------------------------------------------------------------- /contract/p/gnoswap/rbac/rbac_test.gno: -------------------------------------------------------------------------------- 1 | package rbac 2 | 3 | import ( 4 | "testing" 5 | 6 | "gno.land/p/nt/testutils" 7 | "gno.land/p/nt/uassert" 8 | "gno.land/p/nt/ufmt" 9 | ) 10 | 11 | var ( 12 | adminRole = "admin" 13 | editorRole = "editor" 14 | 15 | adminAddr = testutils.TestAddress(adminRole) 16 | userAddr = testutils.TestAddress("user") 17 | editorAddr = testutils.TestAddress(editorRole) 18 | ) 19 | 20 | func adminChecker(caller address) error { 21 | if caller != adminAddr { 22 | return ufmt.Errorf("caller is not admin") 23 | } 24 | return nil 25 | } 26 | 27 | func editorChecker(caller address) error { 28 | if caller != editorAddr { 29 | return ufmt.Errorf("caller is not editor") 30 | } 31 | return nil 32 | } 33 | 34 | func TestRegisterRole(t *testing.T) { 35 | manager := New() 36 | 37 | err := manager.RegisterRole(adminRole, adminAddr) 38 | uassert.NoError(t, err) 39 | 40 | err = manager.RegisterRole(adminRole, adminAddr) 41 | uassert.Error(t, err) 42 | } 43 | 44 | func TestRemoveRole(t *testing.T) { 45 | manager := New() 46 | 47 | t.Run("success remove non-system role", func(t *testing.T) { 48 | // First register role and permission 49 | err := manager.RegisterRole(editorRole, editorAddr) 50 | uassert.NoError(t, err) 51 | 52 | // Remove role 53 | err = manager.RemoveRole(editorRole) 54 | uassert.NoError(t, err) 55 | }) 56 | 57 | t.Run("fail to remove non-existent role", func(t *testing.T) { 58 | err := manager.RemoveRole("non_existent_role") 59 | uassert.Error(t, err) 60 | }) 61 | 62 | t.Run("fail to remove system role", func(t *testing.T) { 63 | // Register system role 64 | err := manager.RegisterRole(adminRole, adminAddr) 65 | uassert.NoError(t, err) 66 | 67 | // Try to remove system role 68 | err = manager.RemoveRole(adminRole) 69 | uassert.Error(t, err) 70 | }) 71 | } 72 | -------------------------------------------------------------------------------- /contract/p/gnoswap/rbac/role.gno: -------------------------------------------------------------------------------- 1 | package rbac 2 | 3 | const maxVersionSize = int(9223372036854775807) 4 | 5 | var zeroAddress = address("") 6 | 7 | // Role represents a role with a name and an assigned address. 8 | type Role struct { 9 | // name represents the role's identifier 10 | name string 11 | addresses map[int]string 12 | latestVersion int 13 | } 14 | 15 | // NewRole creates a new Role instance with roleName. 16 | func NewRole(roleName string, addr address) *Role { 17 | return &Role{ 18 | name: roleName, 19 | addresses: map[int]string{1: addr.String()}, 20 | latestVersion: 1, 21 | } 22 | } 23 | 24 | // Name returns the role's name. 25 | func (r *Role) Name() string { return r.name } 26 | 27 | // Address returns the address assigned to this role. Returns empty address if no address is assigned. 28 | func (r *Role) Address(version int) address { 29 | addr, exists := r.getAddress(version) 30 | if !exists { 31 | return zeroAddress 32 | } 33 | 34 | return address(addr) 35 | } 36 | 37 | func (r *Role) CurrentAddress() address { 38 | addr, exists := r.getAddress(r.latestVersion) 39 | if !exists { 40 | return zeroAddress 41 | } 42 | 43 | return address(addr) 44 | } 45 | 46 | // IsEmpty returns true if no address is assigned to this role. 47 | func (r *Role) IsEmpty() bool { 48 | return r.CurrentAddress() == "" 49 | } 50 | 51 | // IsAuthorized returns true if addr matches the role's assigned address. 52 | func (r *Role) IsAuthorized(addr address) bool { 53 | return r.CurrentAddress().String() == addr.String() 54 | } 55 | 56 | func (r *Role) getAddress(version int) (string, bool) { 57 | addr, exists := r.addresses[version] 58 | if exists { 59 | return addr, true 60 | } 61 | 62 | return "", false 63 | } 64 | 65 | // setAddress assigns addr to this role. 66 | func (r *Role) setAddress(version int, addr address) error { 67 | _, exists := r.getAddress(version) 68 | if !exists { 69 | return ErrVersionDoesNotExist 70 | } 71 | 72 | r.addresses[version] = addr.String() 73 | 74 | return nil 75 | } 76 | 77 | func (r *Role) nextVersionAddress(addr address) error { 78 | latestVersion := r.latestVersion + 1 79 | if latestVersion >= maxVersionSize || latestVersion < 1 { 80 | return ErrCannotUpdateVersion 81 | } 82 | 83 | r.latestVersion = latestVersion 84 | r.addresses[r.latestVersion] = addr.String() 85 | 86 | return nil 87 | } 88 | -------------------------------------------------------------------------------- /contract/p/gnoswap/rbac/types.gno: -------------------------------------------------------------------------------- 1 | package rbac 2 | 3 | // SystemRole represents a predefined system role that cannot be removed. 4 | type SystemRole string 5 | 6 | var ( 7 | ROLE_ADMIN SystemRole = "admin" 8 | ROLE_DEVOPS SystemRole = "devops" 9 | ROLE_COMMUNITY_POOL SystemRole = "community_pool" 10 | ROLE_GOVERNANCE SystemRole = "governance" 11 | ROLE_GOV_STAKER SystemRole = "gov_staker" 12 | ROLE_XGNS SystemRole = "xgns" 13 | ROLE_POOL SystemRole = "pool" 14 | ROLE_POSITION SystemRole = "position" 15 | ROLE_ROUTER SystemRole = "router" 16 | ROLE_STAKER SystemRole = "staker" 17 | ROLE_EMISSION SystemRole = "emission" 18 | ROLE_LAUNCHPAD SystemRole = "launchpad" 19 | ROLE_PROTOCOL_FEE SystemRole = "protocol_fee" 20 | ) 21 | 22 | var systemRoleNames = map[string]SystemRole{ 23 | "admin": ROLE_ADMIN, 24 | "devops": ROLE_DEVOPS, 25 | "community_pool": ROLE_COMMUNITY_POOL, 26 | "governance": ROLE_GOVERNANCE, 27 | "gov_staker": ROLE_GOV_STAKER, 28 | "xgns": ROLE_XGNS, 29 | "pool": ROLE_POOL, 30 | "position": ROLE_POSITION, 31 | "router": ROLE_ROUTER, 32 | "staker": ROLE_STAKER, 33 | "emission": ROLE_EMISSION, 34 | "launchpad": ROLE_LAUNCHPAD, 35 | "protocol_fee": ROLE_PROTOCOL_FEE, 36 | } 37 | 38 | // String returns the string representation of the SystemRole. 39 | // Returns "Unknown" if the role is not a valid system role. 40 | func (r SystemRole) String() string { 41 | roleName := string(r) 42 | if _, ok := systemRoleNames[roleName]; !ok { 43 | return "Unknown" 44 | } 45 | 46 | return roleName 47 | } 48 | 49 | // IsSystemRole returns true if roleName is a system role. 50 | func IsSystemRole(roleName string) bool { 51 | _, ok := systemRoleNames[roleName] 52 | 53 | return ok 54 | } 55 | -------------------------------------------------------------------------------- /contract/p/gnoswap/uint256/LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright 2020 uint256 Authors 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | 1. Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 11 | 2. Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | 3. Neither the name of the copyright holder nor the names of its 16 | contributors may be used to endorse or promote products derived from 17 | this software without specific prior written permission. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 23 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 25 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 26 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 27 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | -------------------------------------------------------------------------------- /contract/p/gnoswap/uint256/README.md: -------------------------------------------------------------------------------- 1 | # uint256 2 | 3 | 256-bit unsigned integer arithmetic for GnoSwap. 4 | 5 | ## Overview 6 | 7 | Fixed-size 256-bit unsigned integer library optimized for AMM calculations with overflow detection and precise MulDiv operations. 8 | 9 | ## Features 10 | 11 | - Fixed 256-bit size (4 uint64 values) 12 | - Overflow detection on all operations 13 | - Optimized MulDiv for precise calculations 14 | - String conversion (decimal, hex, binary) 15 | - Range: 0 to 2^256-1 16 | 17 | ## Usage 18 | 19 | ```go 20 | import u256 "gno.land/p/gnoswap/uint256" 21 | 22 | // Create values 23 | a := u256.NewUint(1000) 24 | b := u256.MustFromDecimal("1000000000000000000") 25 | 26 | // Arithmetic with overflow detection 27 | result, overflow := new(u256.Uint).AddOverflow(a, b) 28 | if overflow { 29 | // Handle overflow 30 | } 31 | 32 | // Precise MulDiv (a * b / c) 33 | result := u256.MulDiv(a, b, c) 34 | ``` 35 | 36 | ## Credits 37 | 38 | Ported from [holiman/uint256](https://github.com/holiman/uint256) -------------------------------------------------------------------------------- /contract/p/gnoswap/uint256/_helper_test.gno: -------------------------------------------------------------------------------- 1 | package uint256 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func shouldEQ(t *testing.T, got, expected any) { 8 | if got != expected { 9 | t.Errorf("got %v, expected %v", got, expected) 10 | } 11 | } 12 | 13 | func shouldNEQ(t *testing.T, got, expected any) { 14 | if got == expected { 15 | t.Errorf("got %v, didn't expected %v", got, expected) 16 | } 17 | } 18 | 19 | func shouldPanic(t *testing.T, f func()) { 20 | defer func() { 21 | if r := recover(); r == nil { 22 | t.Errorf("expected panic") 23 | } 24 | }() 25 | f() 26 | } 27 | 28 | func shouldPanicWithMsg(t *testing.T, f func(), msg string) { 29 | defer func() { 30 | if r := recover(); r == nil { 31 | t.Errorf("The code did not panic") 32 | } else { 33 | if r != msg { 34 | t.Errorf("excepted panic(%v), got(%v)", msg, r) 35 | } 36 | } 37 | }() 38 | f() 39 | } 40 | 41 | // for original tests 42 | func parseUint(s string) *Uint { 43 | if len(s) >= 2 && s[:2] == "0x" { 44 | return MustFromHex(s) 45 | } 46 | return MustFromDecimal(s) 47 | } 48 | 49 | // for testing.T 50 | func parseUintT(t *testing.T, s string) *Uint { 51 | return parseUint(s) 52 | } 53 | -------------------------------------------------------------------------------- /contract/p/gnoswap/uint256/conversion_test.gno: -------------------------------------------------------------------------------- 1 | package uint256 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestIsUint64(t *testing.T) { 8 | tests := []struct { 9 | x string 10 | want bool 11 | }{ 12 | {"0x0", true}, 13 | {"0x1", true}, 14 | {"0x10", true}, 15 | {"0xffffffffffffffff", true}, 16 | {"0x10000000000000000", false}, 17 | } 18 | 19 | for _, tc := range tests { 20 | x := MustFromHex(tc.x) 21 | got := x.IsUint64() 22 | 23 | if got != tc.want { 24 | t.Errorf("IsUint64(%s) = %v, want %v", tc.x, got, tc.want) 25 | } 26 | } 27 | } 28 | 29 | func TestDec(t *testing.T) { 30 | testCases := []struct { 31 | name string 32 | z Uint 33 | want string 34 | }{ 35 | { 36 | name: "zero", 37 | z: Uint{arr: [4]uint64{0, 0, 0, 0}}, 38 | want: "0", 39 | }, 40 | { 41 | name: "less than 20 digits", 42 | z: Uint{arr: [4]uint64{1234567890, 0, 0, 0}}, 43 | want: "1234567890", 44 | }, 45 | { 46 | name: "max possible value", 47 | z: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}}, 48 | want: "115792089237316195423570985008687907853269984665640564039457584007913129639935", 49 | }, 50 | } 51 | 52 | for _, tc := range testCases { 53 | t.Run(tc.name, func(t *testing.T) { 54 | result := tc.z.Dec() 55 | if result != tc.want { 56 | t.Errorf("Dec(%v) = %s, want %s", tc.z, result, tc.want) 57 | } 58 | }) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /contract/p/gnoswap/uint256/doc.gno: -------------------------------------------------------------------------------- 1 | // Package uint256 implements 256-bit unsigned integer arithmetic for GnoSwap. 2 | // 3 | // This package provides a Uint type that represents a 256-bit unsigned integer, 4 | // stored as four uint64 values in little-endian order. It includes arithmetic 5 | // operations with overflow detection, which are essential for safe token 6 | // calculations in DeFi protocols. 7 | // 8 | // The implementation is optimized for gas efficiency while maintaining 9 | // compatibility with Ethereum's uint256 semantics, ensuring consistent 10 | // behavior across different blockchain environments. 11 | // 12 | // All operations that may overflow return both the result and an overflow flag, 13 | // allowing calling code to handle overflow conditions appropriately. 14 | package uint256 15 | -------------------------------------------------------------------------------- /contract/p/gnoswap/uint256/error.gno: -------------------------------------------------------------------------------- 1 | package uint256 2 | 3 | import ( 4 | "errors" 5 | ) 6 | 7 | var ( 8 | ErrEmptyString = errors.New("empty hex string") 9 | ErrSyntax = errors.New("invalid hex string") 10 | ErrRange = errors.New("number out of range") 11 | ErrMissingPrefix = errors.New("hex string without 0x prefix") 12 | ErrEmptyNumber = errors.New("hex string \"0x\"") 13 | ErrLeadingZero = errors.New("hex number with leading zero digits") 14 | ErrBig256Range = errors.New("hex number > 256 bits") 15 | ErrBadBufferLength = errors.New("bad ssz buffer length") 16 | ErrBadEncodedLength = errors.New("bad ssz encoded length") 17 | ErrInvalidBase = errors.New("invalid base") 18 | ErrInvalidBitSize = errors.New("invalid bit size") 19 | ) 20 | 21 | type u256Error struct { 22 | fn string // function name 23 | input string 24 | err error 25 | } 26 | 27 | func (e *u256Error) Error() string { 28 | return e.fn + ": " + e.input + ": " + e.err.Error() 29 | } 30 | 31 | func (e *u256Error) Unwrap() error { 32 | return e.err 33 | } 34 | 35 | func errEmptyString(fn, input string) error { 36 | return &u256Error{fn: fn, input: input, err: ErrEmptyString} 37 | } 38 | 39 | func errSyntax(fn, input string) error { 40 | return &u256Error{fn: fn, input: input, err: ErrSyntax} 41 | } 42 | 43 | func errMissingPrefix(fn, input string) error { 44 | return &u256Error{fn: fn, input: input, err: ErrMissingPrefix} 45 | } 46 | 47 | func errEmptyNumber(fn, input string) error { 48 | return &u256Error{fn: fn, input: input, err: ErrEmptyNumber} 49 | } 50 | 51 | func errLeadingZero(fn, input string) error { 52 | return &u256Error{fn: fn, input: input, err: ErrLeadingZero} 53 | } 54 | 55 | func errRange(fn, input string) error { 56 | return &u256Error{fn: fn, input: input, err: ErrRange} 57 | } 58 | 59 | func errBig256Range(fn, input string) error { 60 | return &u256Error{fn: fn, input: input, err: ErrBig256Range} 61 | } 62 | 63 | func errBadBufferLength(fn, input string) error { 64 | return &u256Error{fn: fn, input: input, err: ErrBadBufferLength} 65 | } 66 | 67 | func errInvalidBase(fn string, base int) error { 68 | return &u256Error{fn: fn, input: string(base), err: ErrInvalidBase} 69 | } 70 | 71 | func errInvalidBitSize(fn string, bitSize int) error { 72 | return &u256Error{fn: fn, input: string(bitSize), err: ErrInvalidBitSize} 73 | } 74 | -------------------------------------------------------------------------------- /contract/p/gnoswap/uint256/gnomod.toml: -------------------------------------------------------------------------------- 1 | module = "gno.land/p/gnoswap/uint256" 2 | gno = "0.9" 3 | -------------------------------------------------------------------------------- /contract/p/gnoswap/uint256/gs_pointer.gno: -------------------------------------------------------------------------------- 1 | package uint256 2 | 3 | func (z *Uint) NilToZero() *Uint { 4 | if z == nil { 5 | z = NewUint(0) 6 | } 7 | return z 8 | } 9 | -------------------------------------------------------------------------------- /contract/p/gnoswap/uint256/utils.gno: -------------------------------------------------------------------------------- 1 | package uint256 2 | 3 | // checkNumberS validates that input represents a valid hexadecimal number. 4 | // Requires "0x" or "0X" prefix and disallows leading zeros after the prefix. 5 | func checkNumberS(input string) error { 6 | const fn = "UnmarshalText" 7 | l := len(input) 8 | if l == 0 { 9 | return errEmptyString(fn, input) 10 | } 11 | if l < 2 || input[0] != '0' || 12 | (input[1] != 'x' && input[1] != 'X') { 13 | return errMissingPrefix(fn, input) 14 | } 15 | if l == 2 { 16 | return errEmptyNumber(fn, input) 17 | } 18 | if len(input) > 3 && input[2] == '0' { 19 | return errLeadingZero(fn, input) 20 | } 21 | return nil 22 | } 23 | -------------------------------------------------------------------------------- /contract/r/gnoswap/access/README.md: -------------------------------------------------------------------------------- 1 | # Access 2 | 3 | Role-based access control for GnoSwap contracts. 4 | 5 | ## Overview 6 | 7 | Access control system manages permissions across all protocol contracts using role-based authorization. 8 | 9 | ## Roles 10 | 11 | - **admin**: Protocol administrator 12 | - **governance**: Governance contract 13 | - **router**: Swap router 14 | - **pool**: Pool management 15 | - **position**: Position NFT 16 | - **staker**: Liquidity staking 17 | - **emission**: GNS emission 18 | - **protocol_fee**: Fee collector 19 | - **launchpad**: Token launchpad 20 | - **gov_staker**: Governance staking 21 | - **gov_xgns**: xGNS token 22 | 23 | ## Key Functions 24 | 25 | ### `GetAddress` 26 | Returns address for a role. 27 | 28 | ### `SetRoleAddresses` 29 | Updates all role addresses (RBAC only). 30 | 31 | ### `IsAuthorized` 32 | Checks if address has role. 33 | 34 | ### Assert Functions 35 | - `AssertIsAdmin` - Require admin role 36 | - `AssertIsGovernance` - Require governance 37 | - `AssertIsAdminOrGovernance` - Admin or governance 38 | - `AssertIsRouter`, `AssertIsPool`, etc. 39 | 40 | ### Swap Whitelist 41 | - `UpdateSwapWhiteList` - Add to whitelist 42 | - `RemoveFromSwapWhiteList` - Remove from whitelist 43 | - `IsSwapWhitelisted` - Check whitelist status 44 | 45 | ## Usage 46 | 47 | ```go 48 | // Check permission 49 | if !access.IsAuthorized("admin", caller) { 50 | panic("unauthorized") 51 | } 52 | 53 | // Assert permission (panics if unauthorized) 54 | access.AssertIsAdminOrGovernance(caller) 55 | 56 | // Get role address 57 | addr, exists := access.GetAddress("router") 58 | 59 | // Manage whitelist 60 | access.UpdateSwapWhiteList(routerAddr) 61 | ``` 62 | 63 | ## Security 64 | 65 | - Centralized permission management 66 | - Role-based authorization 67 | - Swap whitelist for approved routers 68 | - RBAC-only role updates -------------------------------------------------------------------------------- /contract/r/gnoswap/access/consts.gno: -------------------------------------------------------------------------------- 1 | package access 2 | 3 | const ( 4 | rbacPackagePath = "gno.land/r/gnoswap/rbac" 5 | ) 6 | -------------------------------------------------------------------------------- /contract/r/gnoswap/access/errors.gno: -------------------------------------------------------------------------------- 1 | package access 2 | 3 | const ( 4 | errConfigNil = "config cannot be nil" 5 | errNotInitialized = "access control not initialized" 6 | errEmptyRole = "role name cannot be empty" 7 | errRoleExists = "role %s already exists" 8 | errDeclareRole = "failed to declare role %s: %v" 9 | errUnauthorized = "caller(%s) is not authorized for role %s" 10 | ) 11 | -------------------------------------------------------------------------------- /contract/r/gnoswap/access/gnomod.toml: -------------------------------------------------------------------------------- 1 | module = "gno.land/r/gnoswap/access" 2 | gno = "0.9" 3 | -------------------------------------------------------------------------------- /contract/r/gnoswap/access/swap_whitelist.gno: -------------------------------------------------------------------------------- 1 | package access 2 | 3 | import ( 4 | "chain/runtime" 5 | 6 | prbac "gno.land/p/gnoswap/rbac" 7 | "gno.land/p/nt/ufmt" 8 | ) 9 | 10 | // Router whitelist storage 11 | var swapWhitelist map[address]bool 12 | 13 | func init() { 14 | swapWhitelist = make(map[address]bool) 15 | } 16 | 17 | // UpdateSwapWhiteList adds a router address to the swap whitelist. 18 | // Panics if router address is invalid. 19 | // Only admin or governance can call this function. 20 | func UpdateSwapWhiteList(cur realm, router address) { 21 | caller := runtime.PreviousRealm().Address() 22 | AssertIsAdminOrGovernance(caller) 23 | 24 | if !router.IsValid() { 25 | panic(ufmt.Errorf("invalid router address: %s", router)) 26 | } 27 | 28 | // Add or update the router in the whitelist 29 | swapWhitelist[router] = true 30 | } 31 | 32 | // RemoveFromSwapWhiteList removes a router address from the swap whitelist. 33 | // Does nothing if the router is not in the whitelist. 34 | // Only admin or governance can call this function. 35 | func RemoveFromSwapWhiteList(cur realm, router address) { 36 | caller := runtime.PreviousRealm().Address() 37 | AssertIsAdminOrGovernance(caller) 38 | 39 | delete(swapWhitelist, router) 40 | } 41 | 42 | // IsSwapWhitelisted returns true if the address is either the official router 43 | // or is in the swap whitelist. Returns false otherwise. 44 | func IsSwapWhitelisted(addr address) bool { 45 | // Check if it's the official router first 46 | // 47 | // Note: While it's a common pattern to store the router's address 48 | // in a global variable to prevent unnecessary function calls, 49 | // this function is called infrequently and retrieves the address internally 50 | // to respond to address changes. 51 | officialRouter, ok := GetAddress(prbac.ROLE_ROUTER.String()) 52 | if ok && addr == officialRouter { 53 | return true 54 | } 55 | 56 | // Then check whitelist 57 | return swapWhitelist[addr] 58 | } 59 | 60 | // GetWhitelistedSwaps returns all whitelisted router addresses including 61 | // the official router address if it exists. 62 | func GetWhitelistedSwaps() []address { 63 | routers := make([]address, 0, len(swapWhitelist)+1) 64 | 65 | // Include official router 66 | officialRouter, ok := GetAddress(prbac.ROLE_ROUTER.String()) 67 | if ok { 68 | routers = append(routers, officialRouter) 69 | } 70 | 71 | // Add whitelisted routers 72 | for router := range swapWhitelist { 73 | routers = append(routers, router) 74 | } 75 | 76 | return routers 77 | } 78 | -------------------------------------------------------------------------------- /contract/r/gnoswap/emission/_helper_test.gno: -------------------------------------------------------------------------------- 1 | package emission 2 | 3 | import ( 4 | "strconv" 5 | "testing" 6 | 7 | "gno.land/p/nt/avl" 8 | 9 | prbac "gno.land/p/gnoswap/rbac" 10 | "gno.land/r/gnoswap/access" 11 | _ "gno.land/r/gnoswap/rbac" // initialize rbac package 12 | ) 13 | 14 | var ( 15 | adminAddr, _ = access.GetAddress(prbac.ROLE_ADMIN.String()) 16 | stakerAddr, _ = access.GetAddress(prbac.ROLE_STAKER.String()) 17 | govAddr, _ = access.GetAddress(prbac.ROLE_GOVERNANCE.String()) 18 | govStakerAddr, _ = access.GetAddress(prbac.ROLE_GOV_STAKER.String()) 19 | devOpsAddr, _ = access.GetAddress(prbac.ROLE_DEVOPS.String()) 20 | communityPoolAddr, _ = access.GetAddress(prbac.ROLE_COMMUNITY_POOL.String()) 21 | 22 | adminRealm = testing.NewUserRealm(adminAddr) 23 | stakerRealm = testing.NewUserRealm(stakerAddr) 24 | govRealm = testing.NewUserRealm(govAddr) 25 | govStakerRealm = testing.NewUserRealm(govStakerAddr) 26 | ) 27 | 28 | func resetObject(t *testing.T) { 29 | t.Helper() 30 | 31 | distributionBpsPct = avl.NewTree() 32 | distributionBpsPct.Set(strconv.Itoa(LIQUIDITY_STAKER), int64(7500)) 33 | distributionBpsPct.Set(strconv.Itoa(DEVOPS), int64(2000)) 34 | distributionBpsPct.Set(strconv.Itoa(COMMUNITY_POOL), int64(500)) 35 | distributionBpsPct.Set(strconv.Itoa(GOV_STAKER), int64(0)) 36 | 37 | distributedToStaker = 0 38 | distributedToDevOps = 0 39 | distributedToCommunityPool = 0 40 | distributedToGovStaker = 0 41 | accuDistributedToStaker = 0 42 | accuDistributedToDevOps = 0 43 | accuDistributedToCommunityPool = 0 44 | accuDistributedToGovStaker = 0 45 | 46 | // Reset emission-specific variables 47 | leftGNSAmount = 0 48 | lastExecutedTimestamp = 0 49 | distributionStartTimestamp = 0 50 | } 51 | -------------------------------------------------------------------------------- /contract/r/gnoswap/emission/assert.gno: -------------------------------------------------------------------------------- 1 | package emission 2 | 3 | import ( 4 | "gno.land/p/nt/ufmt" 5 | ) 6 | 7 | // assertValidDistributionTargets panics if any of the four distribution targets is invalid 8 | // or if there are duplicate targets. All four distribution targets must be unique and valid. 9 | func assertValidDistributionTargets(target01, target02, target03, target04 int) { 10 | validTargets := map[int]bool{ 11 | LIQUIDITY_STAKER: false, 12 | DEVOPS: false, 13 | COMMUNITY_POOL: false, 14 | GOV_STAKER: false, 15 | } 16 | 17 | currentTargets := []int{target01, target02, target03, target04} 18 | 19 | for _, target := range currentTargets { 20 | if _, ok := validTargets[target]; !ok { 21 | panic(makeErrorWithDetails( 22 | errInvalidEmissionTarget, 23 | ufmt.Sprintf("invalid target(%d)", target), 24 | )) 25 | } 26 | 27 | validTargets[target] = true 28 | } 29 | 30 | for _, valid := range validTargets { 31 | if !valid { 32 | panic(errDuplicateTarget) 33 | } 34 | } 35 | } 36 | 37 | // assertValidDistributionTarget panics if the given distribution target is invalid. 38 | func assertValidDistributionTarget(target int) { 39 | validTargets := map[int]bool{ 40 | LIQUIDITY_STAKER: false, 41 | DEVOPS: false, 42 | COMMUNITY_POOL: false, 43 | GOV_STAKER: false, 44 | } 45 | 46 | if _, ok := validTargets[target]; !ok { 47 | panic(makeErrorWithDetails( 48 | errInvalidEmissionTarget, 49 | ufmt.Sprintf("invalid target(%d)", target), 50 | )) 51 | } 52 | } 53 | 54 | // assertValidDistributionPct ensures the sum of all distribution percentages equals 10000 (100%). 55 | // Panics if the sum does not equal exactly 10000 basis points. 56 | func assertValidDistributionPct(pct01, pct02, pct03, pct04 int64) { 57 | // Validate individual percentages are non-negative and reasonable 58 | percentages := []int64{pct01, pct02, pct03, pct04} 59 | for i, pct := range percentages { 60 | if pct < 0 { 61 | panic(makeErrorWithDetails( 62 | errInvalidEmissionPct, 63 | ufmt.Sprintf("percentage %d cannot be negative: %d", i+1, pct), 64 | )) 65 | } 66 | 67 | if pct > 10000 { 68 | panic(makeErrorWithDetails( 69 | errInvalidEmissionPct, 70 | ufmt.Sprintf("percentage %d cannot exceed 100%%: %d", i+1, pct), 71 | )) 72 | } 73 | } 74 | 75 | sum := pct01 + pct02 + pct03 + pct04 76 | if sum != 10000 { 77 | panic(makeErrorWithDetails( 78 | errInvalidEmissionPct, 79 | ufmt.Sprintf("sum of percentages must be 10000, got %d", sum), 80 | )) 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /contract/r/gnoswap/emission/errors.gno: -------------------------------------------------------------------------------- 1 | package emission 2 | 3 | import ( 4 | "errors" 5 | 6 | "gno.land/p/nt/ufmt" 7 | ) 8 | 9 | var ( 10 | errCallbackIsNil = errors.New("[GNOSWAP-EMISSION-001] callback func is nil") 11 | errInvalidEmissionTarget = errors.New("[GNOSWAP-EMISSION-002] invalid emission target") 12 | errInvalidEmissionPct = errors.New("[GNOSWAP-EMISSION-003] invalid emission percentage") 13 | errDuplicateTarget = errors.New("[GNOSWAP-EMISSION-004] duplicate emission target") 14 | errEmissionAddressNotFound = errors.New("[GNOSWAP-EMISSION-005] emission address not found") 15 | errDistributionAddressNotFound = errors.New("[GNOSWAP-EMISSION-006] distribution address not found") 16 | ) 17 | 18 | // makeErrorWithDetails creates a new error by combining a base error with additional details. 19 | func makeErrorWithDetails(err error, detail string) error { 20 | return ufmt.Errorf("%s || %s", err.Error(), detail) 21 | } 22 | -------------------------------------------------------------------------------- /contract/r/gnoswap/emission/gnomod.toml: -------------------------------------------------------------------------------- 1 | module = "gno.land/r/gnoswap/emission" 2 | gno = "0.9" 3 | -------------------------------------------------------------------------------- /contract/r/gnoswap/emission/utils.gno: -------------------------------------------------------------------------------- 1 | package emission 2 | 3 | import ( 4 | "strconv" 5 | 6 | "gno.land/p/nt/ufmt" 7 | ) 8 | 9 | // formatUint converts various unsigned integer types to string representation. 10 | // Panics if the type is not supported. 11 | func formatUint(v any) string { 12 | switch v := v.(type) { 13 | case uint8: 14 | return strconv.FormatUint(uint64(v), 10) 15 | case uint32: 16 | return strconv.FormatUint(uint64(v), 10) 17 | case uint64: 18 | return strconv.FormatUint(v, 10) 19 | default: 20 | panic(ufmt.Sprintf("invalid type: %T", v)) 21 | } 22 | } 23 | 24 | // formatInt converts various signed integer types to string representation. 25 | // Panics if the type is not supported. 26 | func formatInt(v any) string { 27 | switch v := v.(type) { 28 | case int32: 29 | return strconv.FormatInt(int64(v), 10) 30 | case int64: 31 | return strconv.FormatInt(v, 10) 32 | case int: 33 | return strconv.Itoa(v) 34 | default: 35 | panic(ufmt.Sprintf("invalid type: %T", v)) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /contract/r/gnoswap/gns/README.md: -------------------------------------------------------------------------------- 1 | # GNS 2 | 3 | GnoSwap governance and utility token. 4 | 5 | ## Overview 6 | 7 | GNS is the native governance token of GnoSwap, featuring a deflationary emission schedule with halvings every 2 years over 12 years total. 8 | 9 | ## Token Economics 10 | 11 | - **Symbol**: GNS 12 | - **Decimals**: 6 13 | - **Max Supply**: 1,000,000,000 GNS 14 | - **Initial Mint**: 100,000,000 GNS 15 | - **Total Emission**: 900,000,000 GNS over 12 years 16 | 17 | ## Emission Schedule 18 | 19 | | Years | Annual Emission | Rate | 20 | |-------|----------------|------| 21 | | 1-2 | 225,000,000 | 100% | 22 | | 3-4 | 112,500,000 | 50% | 23 | | 5-6 | 56,250,000 | 25% | 24 | | 7-8 | 28,125,000 | 12.5%| 25 | | 9-12 | 14,062,500 | 6.25%| 26 | 27 | ## Core Functions 28 | 29 | ### `Transfer` 30 | Transfers tokens between addresses. 31 | 32 | ### `TransferFrom` 33 | Transfers with allowance. 34 | 35 | ### `Approve` 36 | Approves spending allowance. 37 | 38 | ### `MintGns` 39 | Mints new tokens per emission schedule. 40 | 41 | ### `Burn` 42 | Burns tokens from supply. 43 | 44 | ## Usage 45 | 46 | ```go 47 | // Transfer tokens 48 | Transfer(to, amount) 49 | 50 | // Approve and transfer 51 | Approve(spender, amount) 52 | TransferFrom(from, to, amount) 53 | 54 | // Mint per emission schedule 55 | MintGns() 56 | ``` 57 | 58 | ## Distribution 59 | 60 | See [emission contract](../emission) for distribution details. -------------------------------------------------------------------------------- /contract/r/gnoswap/gns/_helper_test.gno: -------------------------------------------------------------------------------- 1 | package gns 2 | 3 | import ( 4 | "chain/runtime" 5 | "testing" 6 | "time" 7 | 8 | "gno.land/p/demo/tokens/grc20" 9 | "gno.land/p/nt/ownable" 10 | 11 | prabc "gno.land/p/gnoswap/rbac" 12 | 13 | "gno.land/r/gnoswap/access" 14 | 15 | _ "gno.land/r/gnoswap/rbac" // initialize rbac package 16 | ) 17 | 18 | func resetObject(t *testing.T) { 19 | t.Helper() 20 | 21 | resetGnsTokenObject(t) 22 | resetEmissionState(t) 23 | } 24 | 25 | func resetGnsTokenObject(t *testing.T) { 26 | t.Helper() 27 | 28 | token, privateLedger = grc20.NewToken("Gnoswap", "GNS", 6) 29 | 30 | adminAddr, _ := access.GetAddress(prabc.ROLE_ADMIN.String()) 31 | owner = ownable.NewWithAddress(adminAddr) 32 | 33 | privateLedger.Mint(owner.Owner(), INITIAL_MINT_AMOUNT) 34 | } 35 | 36 | // resetEmissionState resets the emission state to a clean state for testing 37 | func resetEmissionState(t *testing.T) { 38 | t.Helper() 39 | 40 | blockHeight := runtime.ChainHeight() 41 | startTimestamp := time.Now().Unix() 42 | emissionState = NewEmissionState(blockHeight, startTimestamp) 43 | } 44 | -------------------------------------------------------------------------------- /contract/r/gnoswap/gns/assert.gno: -------------------------------------------------------------------------------- 1 | package gns 2 | 3 | import ( 4 | "chain/runtime" 5 | 6 | "gno.land/p/nt/ufmt" 7 | ) 8 | 9 | func assertAddressIsPreviousRealm(addr address) { 10 | previousRealm := runtime.PreviousRealm() 11 | if addr != previousRealm.Address() { 12 | panic(ufmt.Errorf("address(%s) is not previous realm", addr.String())) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /contract/r/gnoswap/gns/consts.gno: -------------------------------------------------------------------------------- 1 | package gns 2 | 3 | const ( 4 | DAY_PER_YEAR = 365 5 | SECONDS_PER_DAY = 86400 6 | SECONDS_IN_YEAR = 31536000 7 | 8 | HALVING_START_YEAR = int64(1) 9 | HALVING_END_YEAR = int64(12) 10 | 11 | // Maximum allowed block time in milliseconds (1 second) 12 | MAX_BLOCK_TIME_MS = 1e9 13 | ) 14 | 15 | // Annual halving amount - maximum issuance per year 16 | var halvingAmountsPerYear = [HALVING_END_YEAR]int64{ 17 | 18_750_000_000_000 * 12, // Year 1: 225000000000000 18 | 18_750_000_000_000 * 12, // Year 2: 225000000000000 19 | 9_375_000_000_000 * 12, // Year 3: 112500000000000 20 | 9_375_000_000_000 * 12, // Year 4: 112500000000000 21 | 4_687_500_000_000 * 12, // Year 5: 56250000000000 22 | 4_687_500_000_000 * 12, // Year 6: 56250000000000 23 | 2_343_750_000_000 * 12, // Year 7: 28125000000000 24 | 2_343_750_000_000 * 12, // Year 8: 28125000000000 25 | 1_171_875_000_000 * 12, // Year 9: 14062500000000 26 | 1_171_875_000_000 * 12, // Year 10: 14062500000000 27 | 1_171_875_000_000 * 12, // Year 11: 14062500000000 28 | 1_171_875_000_000 * 12, // Year 12: 14062500000000 29 | } 30 | -------------------------------------------------------------------------------- /contract/r/gnoswap/gns/errors.gno: -------------------------------------------------------------------------------- 1 | package gns 2 | 3 | import ( 4 | "errors" 5 | 6 | "gno.land/p/nt/ufmt" 7 | ) 8 | 9 | var ( 10 | errInvalidYear = errors.New("[GNOSWAP-GNS-001] invalid year") 11 | errTooManyEmission = errors.New("[GNOSWAP-GNS-002] too many emission reward") 12 | errEmissionChangeIsNilCallback = errors.New("[GNOSWAP-GNS-003] callback emission change is nil") 13 | errInvalidAvgBlockTimeInMs = errors.New("[GNOSWAP-GNS-004] invalid avg block time in ms") 14 | ) 15 | 16 | func makeErrorWithDetails(err error, details string) error { 17 | return ufmt.Errorf("%s || %s", err.Error(), details) 18 | } 19 | -------------------------------------------------------------------------------- /contract/r/gnoswap/gns/gnomod.toml: -------------------------------------------------------------------------------- 1 | module = "gno.land/r/gnoswap/gns" 2 | gno = "0.9" 3 | -------------------------------------------------------------------------------- /contract/r/gnoswap/gns/gns_emission.gno: -------------------------------------------------------------------------------- 1 | package gns 2 | 3 | import ( 4 | "chain" 5 | "chain/runtime" 6 | 7 | "gno.land/r/gnoswap/access" 8 | ) 9 | 10 | // InitEmissionState initializes emission schedule with start timestamp. 11 | // Only callable by emission contract. Sets up 12-year emission schedule 12 | // with halving every 2 years. Panics if caller is not emission contract. 13 | func InitEmissionState(cur realm, height int64, timestamp int64) { 14 | caller := runtime.PreviousRealm().Address() 15 | access.AssertIsEmission(caller) 16 | 17 | emissionState = NewEmissionState(height, timestamp) 18 | 19 | previousRealm := runtime.PreviousRealm() 20 | chain.Emit( 21 | "InitEmissionState", 22 | "prevAddr", previousRealm.Address().String(), 23 | "prevRealm", previousRealm.PkgPath(), 24 | "height", formatInt(height), 25 | "timestamp", formatInt(timestamp), 26 | "startTimestamp", formatInt(emissionState.getStartTimestamp()), 27 | "endTimestamp", formatInt(emissionState.getEndTimestamp()), 28 | ) 29 | } 30 | -------------------------------------------------------------------------------- /contract/r/gnoswap/gns/testutils.gno: -------------------------------------------------------------------------------- 1 | package gns 2 | 3 | import ( 4 | "chain/runtime" 5 | "testing" 6 | "time" 7 | ) 8 | 9 | // InitGnsTest initializes GNS state for testing by resetting emission amounts and state. 10 | func InitGnsTest(t *testing.T) { 11 | t.Helper() 12 | 13 | setLeftEmissionAmount(MAX_EMISSION_AMOUNT) 14 | setMintedEmissionAmount(0) 15 | setLastMintedTimestamp(time.Now().Unix()) 16 | burnAmount = 0 17 | 18 | emissionState = NewEmissionState(runtime.ChainHeight(), time.Now().Unix()) 19 | } 20 | -------------------------------------------------------------------------------- /contract/r/gnoswap/gns/utils.gno: -------------------------------------------------------------------------------- 1 | package gns 2 | 3 | import ( 4 | "strconv" 5 | 6 | "gno.land/p/nt/ufmt" 7 | 8 | prabc "gno.land/p/gnoswap/rbac" 9 | 10 | "gno.land/r/gnoswap/access" 11 | "gno.land/r/gnoswap/rbac" 12 | ) 13 | 14 | // validBlockTime validates that block time is within acceptable range. 15 | // Returns error if block time is <= 0 or >= 1e9. 16 | func validBlockTime(blockTime int64) error { 17 | if blockTime <= 0 || blockTime >= 1e9 { 18 | return errInvalidAvgBlockTimeInMs 19 | } 20 | 21 | return nil 22 | } 23 | 24 | // validYear validates that year is within halving period range (1-12). 25 | // Returns error if year is outside the valid range. 26 | func validYear(year int64) error { 27 | if year < HALVING_START_YEAR || year > HALVING_END_YEAR { 28 | return makeErrorWithDetails(errInvalidYear, ufmt.Sprintf("year: %d", year)) 29 | } 30 | 31 | return nil 32 | } 33 | 34 | // validEmissionAmount validates that the emission amount does not exceed maximum. 35 | // Returns error if minting the amount would exceed MAX_EMISSION_AMOUNT. 36 | func validEmissionAmount(amount int64) error { 37 | if (amount + MintedEmissionAmount()) > MAX_EMISSION_AMOUNT { 38 | return ufmt.Errorf("too many emission amount: %d", amount) 39 | } 40 | 41 | return nil 42 | } 43 | 44 | // getAdminAddress returns the admin address from access control or default role address. 45 | func getAdminAddress() address { 46 | addr, exists := access.GetAddress(prabc.ROLE_ADMIN.String()) 47 | if !exists { 48 | return rbac.DefaultRoleAddresses[prabc.ROLE_ADMIN] 49 | } 50 | 51 | return addr 52 | } 53 | 54 | // i64Min returns the smaller of two int64 values. 55 | func i64Min(x, y int64) int64 { 56 | if x < y { 57 | return x 58 | } 59 | return y 60 | } 61 | 62 | // formatUint formats unsigned integer types to string. 63 | // Supports uint8, uint32, and uint64. Panics for unsupported types. 64 | func formatUint(v any) string { 65 | switch v := v.(type) { 66 | case uint8: 67 | return strconv.FormatUint(uint64(v), 10) 68 | case uint32: 69 | return strconv.FormatUint(uint64(v), 10) 70 | case uint64: 71 | return strconv.FormatUint(v, 10) 72 | default: 73 | panic(ufmt.Sprintf("invalid type: %T", v)) 74 | } 75 | } 76 | 77 | // formatInt formats signed integer types to string. 78 | // Supports int32, int64, and int. Panics for unsupported types. 79 | func formatInt(v any) string { 80 | switch v := v.(type) { 81 | case int32: 82 | return strconv.FormatInt(int64(v), 10) 83 | case int64: 84 | return strconv.FormatInt(v, 10) 85 | case int: 86 | return strconv.Itoa(v) 87 | default: 88 | panic(ufmt.Sprintf("invalid type: %T", v)) 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /contract/r/gnoswap/halt/doc.gno: -------------------------------------------------------------------------------- 1 | // Package halt provides functionality for managing protocol halt levels and operations. 2 | package halt 3 | -------------------------------------------------------------------------------- /contract/r/gnoswap/halt/errors.gno: -------------------------------------------------------------------------------- 1 | package halt 2 | 3 | import ( 4 | "errors" 5 | 6 | "gno.land/p/nt/ufmt" 7 | ) 8 | 9 | var ( 10 | errHalted = errors.New("halted") 11 | errInvalidOpType = errors.New("invalid operation type") 12 | errInvalidHaltLevel = errors.New("invalid halt level") 13 | errCannotUpdateVersion = errors.New("cannot update version") 14 | ) 15 | 16 | // makeErrorWithDetails creates an error with additional details appended to the base error message. 17 | func makeErrorWithDetails(err error, details string) error { 18 | return ufmt.Errorf("%s: %s", err.Error(), details) 19 | } 20 | -------------------------------------------------------------------------------- /contract/r/gnoswap/halt/gnomod.toml: -------------------------------------------------------------------------------- 1 | module = "gno.land/r/gnoswap/halt" 2 | gno = "0.9" 3 | -------------------------------------------------------------------------------- /contract/r/gnoswap/rbac/assert.gno: -------------------------------------------------------------------------------- 1 | package rbac 2 | 3 | import ( 4 | "gno.land/p/nt/ufmt" 5 | 6 | prbac "gno.land/p/gnoswap/rbac" 7 | ) 8 | 9 | // assertIsOwner panics if addr is not the current owner. 10 | func assertIsOwner(addr address) { 11 | if manager.Owner() != addr { 12 | panic(makeErrorWithDetails( 13 | errCallerIsNotOwner, 14 | ufmt.Sprintf("caller: %s", addr.String()), 15 | )) 16 | } 17 | } 18 | 19 | // assertIsPendingOwner panics if addr is not the pending owner. 20 | func assertIsPendingOwner(addr address) { 21 | if manager.PendingOwner() != addr { 22 | panic(makeErrorWithDetails( 23 | errCallerIsNotPendingOwner, 24 | ufmt.Sprintf("caller: %s", addr.String()), 25 | )) 26 | } 27 | } 28 | 29 | // assertIsAdmin panics if addr is not authorized for admin role. 30 | func assertIsAdmin(addr address) { 31 | if !manager.IsAuthorized(prbac.ROLE_ADMIN.String(), addr) { 32 | panic( 33 | makeErrorWithDetails( 34 | errCallerIsNotAdmin, 35 | ufmt.Sprintf("caller: %s", addr.String()), 36 | ), 37 | ) 38 | } 39 | } 40 | 41 | // assertIsValidRoleName panics if roleName is invalid (empty). 42 | func assertIsValidRoleName(roleName string) { 43 | if roleName == "" { 44 | panic(makeErrorWithDetails( 45 | errInvalidRoleName, 46 | ufmt.Sprintf("role name: %s", roleName), 47 | )) 48 | } 49 | } 50 | 51 | func assertIsValidAddress(addr address) { 52 | if addr == "" || !addr.IsValid() { 53 | panic(makeErrorWithDetails( 54 | errInvalidAddress, 55 | ufmt.Sprintf("address: %s", addr.String()), 56 | )) 57 | } 58 | } 59 | 60 | func assertIsAdminOrGovernance(addr address) { 61 | if manager.IsAuthorized(prbac.ROLE_ADMIN.String(), addr) || manager.IsAuthorized(prbac.ROLE_GOVERNANCE.String(), addr) { 62 | return 63 | } 64 | 65 | panic(makeErrorWithDetails( 66 | errCallerIsNotAdminOrGovernance, 67 | ufmt.Sprintf("caller: %s", addr.String()), 68 | )) 69 | } 70 | -------------------------------------------------------------------------------- /contract/r/gnoswap/rbac/consts.gno: -------------------------------------------------------------------------------- 1 | package rbac 2 | 3 | import "chain" 4 | 5 | // Initial addresses for protocol roles. 6 | const ( 7 | // ADMIN is the initial admin address for RBAC management. 8 | ADMIN address = "g17290cwvmrapvp869xfnhhawa8sm9edpufzat7d" 9 | // DEV_OPS is the initial DevOps address for operational tasks. 10 | DEV_OPS address = "g1mjvd83nnjee3z2g7683er55me9f09688pd4mj9" 11 | ) 12 | 13 | // Derived addresses for GnoSwap protocol packages. 14 | var ( 15 | // GNS_ADDR is the derived address for the GNS token package. 16 | GNS_ADDR address = chain.PackageAddress("gno.land/r/gnoswap/gns") 17 | // EMISSION_ADDR is the derived address for the emission package. 18 | EMISSION_ADDR address = chain.PackageAddress("gno.land/r/gnoswap/emission") 19 | 20 | // POOL_ADDR is the derived address for the pool package. 21 | POOL_ADDR address = chain.PackageAddress("gno.land/r/gnoswap/v1/pool") 22 | // POSITION_ADDR is the derived address for the position package. 23 | POSITION_ADDR address = chain.PackageAddress("gno.land/r/gnoswap/v1/position") 24 | // ROUTER_ADDR is the derived address for the router package. 25 | ROUTER_ADDR address = chain.PackageAddress("gno.land/r/gnoswap/v1/router") 26 | // STAKER_ADDR is the derived address for the staker package. 27 | STAKER_ADDR address = chain.PackageAddress("gno.land/r/gnoswap/v1/staker") 28 | // PROTOCOL_FEE_ADDR is the derived address for the protocol fee package. 29 | PROTOCOL_FEE_ADDR address = chain.PackageAddress("gno.land/r/gnoswap/v1/protocol_fee") 30 | // COMMUNITY_POOL_ADDR is the derived address for the community pool package. 31 | COMMUNITY_POOL_ADDR address = chain.PackageAddress("gno.land/r/gnoswap/v1/community_pool") 32 | // GOV_GOVERNANCE_ADDR is the derived address for the governance package. 33 | GOV_GOVERNANCE_ADDR address = chain.PackageAddress("gno.land/r/gnoswap/v1/gov/governance") 34 | // GOV_STAKER_ADDR is the derived address for the governance staker package. 35 | GOV_STAKER_ADDR address = chain.PackageAddress("gno.land/r/gnoswap/v1/gov/staker") 36 | // GOV_XGNS_ADDR is the derived address for the xGNS governance package. 37 | GOV_XGNS_ADDR address = chain.PackageAddress("gno.land/r/gnoswap/v1/gov/xgns") 38 | // LAUNCHPAD_ADDR is the derived address for the launchpad package. 39 | LAUNCHPAD_ADDR address = chain.PackageAddress("gno.land/r/gnoswap/v1/launchpad") 40 | ) 41 | -------------------------------------------------------------------------------- /contract/r/gnoswap/rbac/emit.gno: -------------------------------------------------------------------------------- 1 | package rbac 2 | 3 | import ( 4 | "chain" 5 | "chain/runtime" 6 | 7 | "gno.land/p/nt/ufmt" 8 | ) 9 | 10 | // emitRegisterRoleEvent emits a RegisterRole event with roleName and address information. 11 | func emitRegisterRoleEvent(roleName string, version int, roleAddress address) { 12 | versionStr := ufmt.Sprintf("%d", version) 13 | 14 | prevRealm := runtime.PreviousRealm() 15 | chain.Emit( 16 | "RegisterRole", 17 | "prevAddr", prevRealm.Address().String(), 18 | "prevRealm", prevRealm.PkgPath(), 19 | "roleName", roleName, 20 | "roleVersion", versionStr, 21 | "roleAddress", roleAddress.String(), 22 | ) 23 | } 24 | 25 | // emitRemoveRoleEvent emits a RemoveRole event with roleName information. 26 | func emitRemoveRoleEvent(roleName string) { 27 | prevRealm := runtime.PreviousRealm() 28 | chain.Emit( 29 | "RemoveRole", 30 | "prevAddr", prevRealm.Address().String(), 31 | "prevRealm", prevRealm.PkgPath(), 32 | "roleName", roleName, 33 | "roleAddress", "", 34 | ) 35 | } 36 | 37 | // emitUpdateRoleAddressEvent emits an UpdateRoleAddress event with roleName and address information. 38 | func emitUpdateRoleAddressEvent(roleName string, version int, address address) { 39 | versionStr := ufmt.Sprintf("%d", version) 40 | 41 | prevRealm := runtime.PreviousRealm() 42 | chain.Emit( 43 | "UpdateRoleAddress", 44 | "prevAddr", prevRealm.Address().String(), 45 | "prevRealm", prevRealm.PkgPath(), 46 | "roleName", roleName, 47 | "roleVersion", versionStr, 48 | "roleAddress", address.String(), 49 | ) 50 | } 51 | -------------------------------------------------------------------------------- /contract/r/gnoswap/rbac/errors.gno: -------------------------------------------------------------------------------- 1 | package rbac 2 | 3 | import ( 4 | "errors" 5 | 6 | "gno.land/p/nt/ufmt" 7 | ) 8 | 9 | var ( 10 | errCallerIsNotOwner = errors.New("caller is not owner") 11 | errCallerIsNotAdmin = errors.New("caller is not admin") 12 | errCallerIsNotAdminOrGovernance = errors.New("caller is not admin or governance") 13 | errCallerIsNotPendingOwner = errors.New("caller is not pending owner") 14 | errInvalidAddress = errors.New("invalid address") 15 | errInvalidRoleName = errors.New("invalid role name") 16 | ) 17 | 18 | // makeErrorWithDetails combines an error with additional details. 19 | func makeErrorWithDetails(err error, details string) error { 20 | return ufmt.Errorf("%s || %s", err.Error(), details) 21 | } 22 | -------------------------------------------------------------------------------- /contract/r/gnoswap/rbac/gnomod.toml: -------------------------------------------------------------------------------- 1 | module = "gno.land/r/gnoswap/rbac" 2 | gno = "0.9" 3 | -------------------------------------------------------------------------------- /contract/r/gnoswap/rbac/ownership.gno: -------------------------------------------------------------------------------- 1 | package rbac 2 | 3 | import "chain/runtime" 4 | 5 | // IsOwner returns true if addr is the current owner. 6 | func IsOwner(addr address) bool { 7 | return manager.Owner() == addr 8 | } 9 | 10 | // IsPendingOwner returns true if addr is the pending owner. 11 | func IsPendingOwner(addr address) bool { 12 | return manager.PendingOwner() == addr 13 | } 14 | 15 | // GetOwner returns the current owner address. 16 | func GetOwner() address { 17 | return manager.Owner() 18 | } 19 | 20 | // GetPendingOwner returns the pending owner address. 21 | func GetPendingOwner() address { 22 | return manager.PendingOwner() 23 | } 24 | 25 | // AcceptOwnership completes the ownership transfer process. 26 | // Only callable by pending owner. 27 | func AcceptOwnership(cur realm) { 28 | caller := runtime.PreviousRealm().Address() 29 | assertIsPendingOwner(caller) 30 | 31 | err := manager.AcceptOwnershipBy(caller) 32 | if err != nil { 33 | panic(err) 34 | } 35 | } 36 | 37 | // TransferOwnership initiates the ownership transfer process. 38 | // 39 | // Parameters: 40 | // - addr: address to transfer ownership to 41 | // 42 | // Only callable by current owner. 43 | func TransferOwnership(cur realm, addr address) { 44 | caller := runtime.PreviousRealm().Address() 45 | assertIsOwner(caller) 46 | assertIsValidAddress(addr) 47 | 48 | err := manager.TransferOwnershipBy(addr, caller) 49 | if err != nil { 50 | panic(err) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /contract/r/gnoswap/rbac/role.gno: -------------------------------------------------------------------------------- 1 | package rbac 2 | 3 | import prbac "gno.land/p/gnoswap/rbac" 4 | 5 | // DefaultRoleAddresses maps system roles to their default addresses. 6 | // Used during RBAC initialization to set up the protocol role structure. 7 | var DefaultRoleAddresses = map[prbac.SystemRole]address{ 8 | prbac.ROLE_ADMIN: ADMIN, 9 | prbac.ROLE_DEVOPS: DEV_OPS, 10 | prbac.ROLE_COMMUNITY_POOL: COMMUNITY_POOL_ADDR, 11 | prbac.ROLE_GOVERNANCE: GOV_GOVERNANCE_ADDR, 12 | prbac.ROLE_GOV_STAKER: GOV_STAKER_ADDR, 13 | prbac.ROLE_XGNS: GOV_XGNS_ADDR, 14 | prbac.ROLE_POOL: POOL_ADDR, 15 | prbac.ROLE_POSITION: POSITION_ADDR, 16 | prbac.ROLE_ROUTER: ROUTER_ADDR, 17 | prbac.ROLE_STAKER: STAKER_ADDR, 18 | prbac.ROLE_EMISSION: EMISSION_ADDR, 19 | prbac.ROLE_LAUNCHPAD: LAUNCHPAD_ADDR, 20 | prbac.ROLE_PROTOCOL_FEE: PROTOCOL_FEE_ADDR, 21 | } 22 | -------------------------------------------------------------------------------- /contract/r/gnoswap/referral/README.md: -------------------------------------------------------------------------------- 1 | # Referral 2 | 3 | Referral system for tracking user relationships. 4 | 5 | ## Overview 6 | 7 | Manages referral relationships between users with cooldown periods to prevent gaming. 8 | 9 | ## Key Functions 10 | 11 | ### `TryRegister` 12 | Attempts to register referral relationship. 13 | 14 | ### `Register` 15 | Registers new referral (panics if exists). 16 | 17 | ### `UpdateReferral` 18 | Changes referral address after cooldown. 19 | 20 | ### `DeleteReferral` 21 | Removes referral relationship. 22 | 23 | ### `GetReferral` 24 | Returns referral for address. 25 | 26 | ## Usage 27 | 28 | ```go 29 | // Register referral 30 | success := TryRegister(user, referrer) 31 | 32 | // Update after cooldown 33 | UpdateReferral(newReferrer) 34 | 35 | // Query referral 36 | referrer := GetReferral(userAddress) 37 | 38 | // Remove referral 39 | DeleteReferral() 40 | ``` 41 | 42 | ## Security 43 | 44 | - One referral per address 45 | - 24-hour change cooldown 46 | - No self-referrals 47 | - Immutable during cooldown -------------------------------------------------------------------------------- /contract/r/gnoswap/referral/errors.gno: -------------------------------------------------------------------------------- 1 | package referral 2 | 3 | import ( 4 | "errors" 5 | ) 6 | 7 | var ( 8 | ErrInvalidAddress = errors.New("invalid address format") 9 | ErrZeroAddress = errors.New("zero address is not allowed") 10 | ErrSelfReferral = errors.New("self referral is not allowed") 11 | ErrUnauthorized = errors.New("unauthorized caller") 12 | ErrInvalidCaller = errors.New("invalid caller") 13 | ErrCyclicReference = errors.New("cyclic reference is not allowed") 14 | ErrTooManyRequests = errors.New("too many requests: operations allowed once per 24 hours for each address") 15 | ErrNotFound = errors.New("referral not found") 16 | ) 17 | -------------------------------------------------------------------------------- /contract/r/gnoswap/referral/global_keeper.gno: -------------------------------------------------------------------------------- 1 | package referral 2 | 3 | import "chain" 4 | 5 | var gReferralKeeper ReferralKeeper 6 | 7 | const ( 8 | EventReferralInvalid = "ReferralInvalid" 9 | EventRegisterFailed = "ReferralRegistrationFailed" 10 | EventRegisterSuccess = "ReferralRegistrationSuccess" 11 | ) 12 | 13 | func init() { 14 | if gReferralKeeper == nil { 15 | gReferralKeeper = NewKeeper() 16 | } 17 | } 18 | 19 | // getKeeper returns the global referral keeper instance. 20 | func getKeeper() ReferralKeeper { 21 | return gReferralKeeper 22 | } 23 | 24 | // GetReferral returns the referral address for the given address. 25 | func GetReferral(addr string) string { 26 | referral, err := gReferralKeeper.get(address(addr)) 27 | if err != nil { 28 | return "" 29 | } 30 | return referral.String() 31 | } 32 | 33 | // HasReferral returns true if the given address has a referral. 34 | func HasReferral(addr string) bool { 35 | referral, err := gReferralKeeper.get(address(addr)) 36 | if err != nil { 37 | return false 38 | } 39 | return referral != zeroAddress 40 | } 41 | 42 | // IsEmpty returns true if no referrals exist in the system. 43 | func IsEmpty() bool { 44 | return gReferralKeeper.isEmpty() 45 | } 46 | 47 | // TryRegister attempts to register a new referral. 48 | // 49 | // Parameters: 50 | // - addr: address to register 51 | // - referral: referral address string 52 | // 53 | // Returns true on success, false on failure. 54 | func TryRegister(cur realm, addr address, referral string) bool { 55 | refAddr := address(referral) 56 | err := gReferralKeeper.register(addr, refAddr) 57 | if err != nil { 58 | chain.Emit( 59 | EventRegisterFailed, 60 | "address", addr.String(), 61 | "error", err.Error(), 62 | ) 63 | return false 64 | } 65 | 66 | chain.Emit( 67 | EventRegisterSuccess, 68 | "address", addr.String(), 69 | "referral", referral, 70 | ) 71 | return true 72 | } 73 | -------------------------------------------------------------------------------- /contract/r/gnoswap/referral/gnomod.toml: -------------------------------------------------------------------------------- 1 | module = "gno.land/r/gnoswap/referral" 2 | gno = "0.9" 3 | -------------------------------------------------------------------------------- /contract/r/gnoswap/referral/referral.gno: -------------------------------------------------------------------------------- 1 | package referral 2 | 3 | // Referral manages referral relationships between addresses. 4 | type Referral struct { 5 | keeper ReferralKeeper 6 | } 7 | 8 | // NewReferral creates a new Referral instance. 9 | func NewReferral() *Referral { 10 | if gReferralKeeper == nil { 11 | gReferralKeeper = NewKeeper() 12 | } 13 | return &Referral{ 14 | keeper: gReferralKeeper, 15 | } 16 | } 17 | 18 | // Register creates a new referral relationship. 19 | // 20 | // Parameters: 21 | // - addr: address to register 22 | // - refAddr: referral address 23 | func (r *Referral) Register(addr, refAddr address) error { 24 | return r.keeper.register(addr, refAddr) 25 | } 26 | 27 | // Update modifies an existing referral relationship. 28 | // 29 | // Parameters: 30 | // - addr: address to update 31 | // - newAddr: new referral address 32 | func (r *Referral) Update(addr, newAddr address) error { 33 | return r.keeper.update(addr, newAddr) 34 | } 35 | 36 | // Remove deletes a referral relationship. 37 | // 38 | // Parameters: 39 | // - addr: address to remove 40 | func (r *Referral) Remove(addr address) error { 41 | return r.keeper.remove(addr) 42 | } 43 | 44 | // Has returns true if a referral exists for the given address. 45 | func (r *Referral) Has(addr address) bool { 46 | return r.keeper.has(addr) 47 | } 48 | 49 | // Get retrieves the referral address for the given address. 50 | func (r *Referral) Get(addr address) (address, error) { 51 | return r.keeper.get(addr) 52 | } 53 | -------------------------------------------------------------------------------- /contract/r/gnoswap/referral/type.gno: -------------------------------------------------------------------------------- 1 | package referral 2 | 3 | var zeroAddress = address("") 4 | 5 | // Event types for referral actions. 6 | const ( 7 | EventTypeRegister = "RegisterReferral" 8 | EventTypeUpdate = "UpdateReferral" 9 | EventTypeRemove = "RemoveReferral" 10 | ) 11 | 12 | // ReferralKeeper defines the interface for managing referral relationships. 13 | type ReferralKeeper interface { 14 | // register creates a new referral relationship between addresses. 15 | register(addr, refAddr address) error 16 | 17 | // update modifies an existing referral address. 18 | update(addr, newRefAddr address) error 19 | 20 | // remove deletes a referral relationship. 21 | remove(addr address) error 22 | 23 | // has returns true if a referral exists for the given address. 24 | has(addr address) bool 25 | 26 | // get retrieves the referral address for a given address. 27 | get(addr address) (address, error) 28 | 29 | // isEmpty returns true if no referrals exist in the system. 30 | isEmpty() bool 31 | } 32 | -------------------------------------------------------------------------------- /contract/r/gnoswap/referral/utils.gno: -------------------------------------------------------------------------------- 1 | package referral 2 | 3 | import ( 4 | "gno.land/p/nt/ufmt" 5 | 6 | prabc "gno.land/p/gnoswap/rbac" 7 | 8 | "gno.land/r/gnoswap/access" 9 | _ "gno.land/r/gnoswap/rbac" 10 | ) 11 | 12 | // validCallerRoles is a list of roles that are authorized to modify referral data. 13 | // This includes governance contracts, router, position manager, and staker contracts. 14 | var validCallerRoles = []string{ 15 | prabc.ROLE_GOVERNANCE.String(), 16 | prabc.ROLE_GOV_STAKER.String(), 17 | prabc.ROLE_ROUTER.String(), 18 | prabc.ROLE_POSITION.String(), 19 | prabc.ROLE_STAKER.String(), 20 | prabc.ROLE_LAUNCHPAD.String(), 21 | } 22 | 23 | // isValidCaller checks if the caller address has permission to modify referral data. 24 | // Only addresses with specific roles defined in validCallerRoles are authorized. 25 | // Returns an error if the caller is not authorized. 26 | func isValidCaller(caller address) error { 27 | for _, role := range validCallerRoles { 28 | if access.IsAuthorized(role, caller) { 29 | return nil 30 | } 31 | } 32 | 33 | return ufmt.Errorf("unauthorized caller: %s", caller) 34 | } 35 | -------------------------------------------------------------------------------- /contract/r/gnoswap/test_token/bar/bar.gno: -------------------------------------------------------------------------------- 1 | package bar 2 | 3 | import ( 4 | "strings" 5 | 6 | "gno.land/p/demo/tokens/grc20" 7 | "gno.land/p/nt/ownable" 8 | "gno.land/p/nt/ufmt" 9 | 10 | "gno.land/r/demo/defi/grc20reg" 11 | ) 12 | 13 | var ( 14 | token, privateLedger = grc20.NewToken("Bar", "BAR", 6) 15 | owner = ownable.NewWithAddress("g17290cwvmrapvp869xfnhhawa8sm9edpufzat7d") // ADMIN 16 | ) 17 | 18 | func init() { 19 | privateLedger.Mint(owner.Owner(), 100_000_000_000_000) 20 | grc20reg.Register(cross, token, "") 21 | } 22 | 23 | func TotalSupply() int64 { 24 | userTeller := token.CallerTeller() 25 | return userTeller.TotalSupply() 26 | } 27 | 28 | func BalanceOf(owner address) int64 { 29 | userTeller := token.CallerTeller() 30 | return userTeller.BalanceOf(owner) 31 | } 32 | 33 | func Allowance(owner, spender address) int64 { 34 | userTeller := token.CallerTeller() 35 | return userTeller.Allowance(owner, spender) 36 | } 37 | 38 | func Transfer(cur realm, to address, amount int64) { 39 | userTeller := token.CallerTeller() 40 | checkErr(userTeller.Transfer(to, amount)) 41 | } 42 | 43 | func Approve(cur realm, spender address, amount int64) { 44 | userTeller := token.CallerTeller() 45 | checkErr(userTeller.Approve(spender, amount)) 46 | } 47 | 48 | func TransferFrom(cur realm, from, to address, amount int64) { 49 | userTeller := token.CallerTeller() 50 | checkErr(userTeller.TransferFrom(from, to, amount)) 51 | } 52 | 53 | func Burn(cur realm, from address, amount int64) { 54 | owner.AssertOwnedByPrevious() 55 | checkErr(privateLedger.Burn(from, amount)) 56 | } 57 | 58 | func Render(path string) string { 59 | parts := strings.Split(path, "/") 60 | c := len(parts) 61 | 62 | switch { 63 | case path == "": 64 | return token.RenderHome() 65 | case c == 2 && parts[0] == "balance": 66 | owner := address(parts[1]) 67 | userTeller := token.CallerTeller() 68 | balance := userTeller.BalanceOf(owner) 69 | return ufmt.Sprintf("%d\n", balance) 70 | default: 71 | return "404\n" 72 | } 73 | } 74 | 75 | func checkErr(err error) { 76 | if err != nil { 77 | panic(err) 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /contract/r/gnoswap/test_token/bar/gnomod.toml: -------------------------------------------------------------------------------- 1 | module = "gno.land/r/onbloc/bar" 2 | gno = "0.9" 3 | -------------------------------------------------------------------------------- /contract/r/gnoswap/test_token/baz/baz.gno: -------------------------------------------------------------------------------- 1 | package baz 2 | 3 | import ( 4 | "strings" 5 | 6 | "gno.land/p/demo/tokens/grc20" 7 | "gno.land/p/nt/ownable" 8 | "gno.land/p/nt/ufmt" 9 | 10 | "gno.land/r/demo/defi/grc20reg" 11 | ) 12 | 13 | var ( 14 | token, privateLedger = grc20.NewToken("Baz", "BAZ", 6) 15 | owner = ownable.NewWithAddress("g17290cwvmrapvp869xfnhhawa8sm9edpufzat7d") // ADMIN 16 | ) 17 | 18 | func init() { 19 | privateLedger.Mint(owner.Owner(), 100_000_000_000_000) 20 | grc20reg.Register(cross, token, "") 21 | } 22 | 23 | func TotalSupply() int64 { 24 | userTeller := token.CallerTeller() 25 | return userTeller.TotalSupply() 26 | } 27 | 28 | func BalanceOf(owner address) int64 { 29 | userTeller := token.CallerTeller() 30 | return userTeller.BalanceOf(owner) 31 | } 32 | 33 | func Allowance(owner, spender address) int64 { 34 | userTeller := token.CallerTeller() 35 | return userTeller.Allowance(owner, spender) 36 | } 37 | 38 | func Transfer(cur realm, to address, amount int64) { 39 | userTeller := token.CallerTeller() 40 | checkErr(userTeller.Transfer(to, amount)) 41 | } 42 | 43 | func Approve(cur realm, spender address, amount int64) { 44 | userTeller := token.CallerTeller() 45 | checkErr(userTeller.Approve(spender, amount)) 46 | } 47 | 48 | func TransferFrom(cur realm, from, to address, amount int64) { 49 | userTeller := token.CallerTeller() 50 | checkErr(userTeller.TransferFrom(from, to, amount)) 51 | } 52 | 53 | func Burn(cur realm, from address, amount int64) { 54 | owner.AssertOwnedByPrevious() 55 | checkErr(privateLedger.Burn(from, amount)) 56 | } 57 | 58 | func Render(path string) string { 59 | parts := strings.Split(path, "/") 60 | c := len(parts) 61 | 62 | switch { 63 | case path == "": 64 | return token.RenderHome() 65 | case c == 2 && parts[0] == "balance": 66 | owner := address(parts[1]) 67 | userTeller := token.CallerTeller() 68 | balance := userTeller.BalanceOf(owner) 69 | return ufmt.Sprintf("%d\n", balance) 70 | default: 71 | return "404\n" 72 | } 73 | } 74 | 75 | func checkErr(err error) { 76 | if err != nil { 77 | panic(err) 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /contract/r/gnoswap/test_token/baz/gnomod.toml: -------------------------------------------------------------------------------- 1 | module = "gno.land/r/onbloc/baz" 2 | gno = "0.9" 3 | -------------------------------------------------------------------------------- /contract/r/gnoswap/test_token/foo/foo.gno: -------------------------------------------------------------------------------- 1 | package foo 2 | 3 | import ( 4 | "strings" 5 | 6 | "gno.land/p/demo/tokens/grc20" 7 | "gno.land/p/nt/ownable" 8 | "gno.land/p/nt/ufmt" 9 | 10 | "gno.land/r/demo/defi/grc20reg" 11 | ) 12 | 13 | var ( 14 | token, privateLedger = grc20.NewToken("Foo", "FOO", 6) 15 | owner = ownable.NewWithAddress("g17290cwvmrapvp869xfnhhawa8sm9edpufzat7d") // ADMIN 16 | ) 17 | 18 | func init() { 19 | privateLedger.Mint(owner.Owner(), 100_000_000_000_000) 20 | grc20reg.Register(cross, token, "") 21 | } 22 | 23 | func TotalSupply() int64 { 24 | userTeller := token.CallerTeller() 25 | return userTeller.TotalSupply() 26 | } 27 | 28 | func BalanceOf(owner address) int64 { 29 | userTeller := token.CallerTeller() 30 | return userTeller.BalanceOf(owner) 31 | } 32 | 33 | func Allowance(owner, spender address) int64 { 34 | userTeller := token.CallerTeller() 35 | return userTeller.Allowance(owner, spender) 36 | } 37 | 38 | func Transfer(cur realm, to address, amount int64) { 39 | userTeller := token.CallerTeller() 40 | checkErr(userTeller.Transfer(to, amount)) 41 | } 42 | 43 | func Approve(cur realm, spender address, amount int64) { 44 | userTeller := token.CallerTeller() 45 | checkErr(userTeller.Approve(spender, amount)) 46 | } 47 | 48 | func TransferFrom(cur realm, from, to address, amount int64) { 49 | userTeller := token.CallerTeller() 50 | checkErr(userTeller.TransferFrom(from, to, amount)) 51 | } 52 | 53 | func Burn(cur realm, from address, amount int64) { 54 | owner.AssertOwnedByPrevious() 55 | checkErr(privateLedger.Burn(from, amount)) 56 | } 57 | 58 | func Render(path string) string { 59 | parts := strings.Split(path, "/") 60 | c := len(parts) 61 | 62 | switch { 63 | case path == "": 64 | return token.RenderHome() 65 | case c == 2 && parts[0] == "balance": 66 | owner := address(parts[1]) 67 | userTeller := token.CallerTeller() 68 | balance := userTeller.BalanceOf(owner) 69 | return ufmt.Sprintf("%d\n", balance) 70 | default: 71 | return "404\n" 72 | } 73 | } 74 | 75 | func checkErr(err error) { 76 | if err != nil { 77 | panic(err) 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /contract/r/gnoswap/test_token/foo/gnomod.toml: -------------------------------------------------------------------------------- 1 | module = "gno.land/r/onbloc/foo" 2 | gno = "0.9" 3 | -------------------------------------------------------------------------------- /contract/r/gnoswap/test_token/obl/gnomod.toml: -------------------------------------------------------------------------------- 1 | module = "gno.land/r/onbloc/obl" 2 | gno = "0.9" 3 | -------------------------------------------------------------------------------- /contract/r/gnoswap/test_token/obl/obl.gno: -------------------------------------------------------------------------------- 1 | package obl 2 | 3 | import ( 4 | "strings" 5 | 6 | "gno.land/p/demo/tokens/grc20" 7 | "gno.land/p/nt/ownable" 8 | "gno.land/p/nt/ufmt" 9 | 10 | "gno.land/r/demo/defi/grc20reg" 11 | ) 12 | 13 | var ( 14 | token, privateLedger = grc20.NewToken("Obl", "OBL", 6) 15 | owner = ownable.NewWithAddress("g17290cwvmrapvp869xfnhhawa8sm9edpufzat7d") // ADMIN 16 | ) 17 | 18 | func init() { 19 | privateLedger.Mint(owner.Owner(), 100_000_000_000_000) 20 | grc20reg.Register(cross, token, "") 21 | } 22 | 23 | func TotalSupply() int64 { 24 | userTeller := token.CallerTeller() 25 | return userTeller.TotalSupply() 26 | } 27 | 28 | func BalanceOf(owner address) int64 { 29 | userTeller := token.CallerTeller() 30 | return userTeller.BalanceOf(owner) 31 | } 32 | 33 | func Allowance(owner, spender address) int64 { 34 | userTeller := token.CallerTeller() 35 | return userTeller.Allowance(owner, spender) 36 | } 37 | 38 | func Transfer(cur realm, to address, amount int64) { 39 | userTeller := token.CallerTeller() 40 | checkErr(userTeller.Transfer(to, amount)) 41 | } 42 | 43 | func Approve(cur realm, spender address, amount int64) { 44 | userTeller := token.CallerTeller() 45 | checkErr(userTeller.Approve(spender, amount)) 46 | } 47 | 48 | func TransferFrom(cur realm, from, to address, amount int64) { 49 | userTeller := token.CallerTeller() 50 | checkErr(userTeller.TransferFrom(from, to, amount)) 51 | } 52 | 53 | func Burn(cur realm, from address, amount int64) { 54 | owner.AssertOwnedByPrevious() 55 | checkErr(privateLedger.Burn(from, amount)) 56 | } 57 | 58 | func Render(path string) string { 59 | parts := strings.Split(path, "/") 60 | c := len(parts) 61 | 62 | switch { 63 | case path == "": 64 | return token.RenderHome() 65 | case c == 2 && parts[0] == "balance": 66 | owner := address(parts[1]) 67 | userTeller := token.CallerTeller() 68 | balance := userTeller.BalanceOf(owner) 69 | return ufmt.Sprintf("%d\n", balance) 70 | default: 71 | return "404\n" 72 | } 73 | } 74 | 75 | func checkErr(err error) { 76 | if err != nil { 77 | panic(err) 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /contract/r/gnoswap/test_token/qux/gnomod.toml: -------------------------------------------------------------------------------- 1 | module = "gno.land/r/onbloc/qux" 2 | gno = "0.9" 3 | -------------------------------------------------------------------------------- /contract/r/gnoswap/test_token/qux/qux.gno: -------------------------------------------------------------------------------- 1 | package qux 2 | 3 | import ( 4 | "strings" 5 | 6 | "gno.land/p/demo/tokens/grc20" 7 | "gno.land/p/nt/ownable" 8 | "gno.land/p/nt/ufmt" 9 | 10 | "gno.land/r/demo/defi/grc20reg" 11 | ) 12 | 13 | var ( 14 | token, privateLedger = grc20.NewToken("Qux", "QUX", 6) 15 | owner = ownable.NewWithAddress("g17290cwvmrapvp869xfnhhawa8sm9edpufzat7d") // ADMIN 16 | ) 17 | 18 | func init() { 19 | privateLedger.Mint(owner.Owner(), 100_000_000_000_000) 20 | grc20reg.Register(cross, token, "") 21 | } 22 | 23 | func TotalSupply() int64 { 24 | userTeller := token.CallerTeller() 25 | return userTeller.TotalSupply() 26 | } 27 | 28 | func BalanceOf(owner address) int64 { 29 | userTeller := token.CallerTeller() 30 | return userTeller.BalanceOf(owner) 31 | } 32 | 33 | func Allowance(owner, spender address) int64 { 34 | userTeller := token.CallerTeller() 35 | return userTeller.Allowance(owner, spender) 36 | } 37 | 38 | func Transfer(cur realm, to address, amount int64) { 39 | userTeller := token.CallerTeller() 40 | checkErr(userTeller.Transfer(to, amount)) 41 | } 42 | 43 | func Approve(cur realm, spender address, amount int64) { 44 | userTeller := token.CallerTeller() 45 | checkErr(userTeller.Approve(spender, amount)) 46 | } 47 | 48 | func TransferFrom(cur realm, from, to address, amount int64) { 49 | userTeller := token.CallerTeller() 50 | checkErr(userTeller.TransferFrom(from, to, amount)) 51 | } 52 | 53 | func Burn(cur realm, from address, amount int64) { 54 | owner.AssertOwnedByPrevious() 55 | checkErr(privateLedger.Burn(from, amount)) 56 | } 57 | 58 | func Render(path string) string { 59 | parts := strings.Split(path, "/") 60 | c := len(parts) 61 | 62 | switch { 63 | case path == "": 64 | return token.RenderHome() 65 | case c == 2 && parts[0] == "balance": 66 | owner := address(parts[1]) 67 | userTeller := token.CallerTeller() 68 | balance := userTeller.BalanceOf(owner) 69 | return ufmt.Sprintf("%d\n", balance) 70 | default: 71 | return "404\n" 72 | } 73 | } 74 | 75 | func checkErr(err error) { 76 | if err != nil { 77 | panic(err) 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /contract/r/gnoswap/test_token/usdc/gnomod.toml: -------------------------------------------------------------------------------- 1 | module = "gno.land/r/onbloc/usdc" 2 | gno = "0.9" 3 | -------------------------------------------------------------------------------- /contract/r/gnoswap/test_token/usdc/usdc.gno: -------------------------------------------------------------------------------- 1 | package usdc 2 | 3 | import ( 4 | "strings" 5 | 6 | "gno.land/p/demo/tokens/grc20" 7 | "gno.land/p/nt/ownable" 8 | "gno.land/p/nt/ufmt" 9 | 10 | "gno.land/r/demo/defi/grc20reg" 11 | ) 12 | 13 | var ( 14 | token, privateLedger = grc20.NewToken("Usd Coin", "USDC", 6) 15 | owner = ownable.NewWithAddress("g17290cwvmrapvp869xfnhhawa8sm9edpufzat7d") // ADMIN 16 | ) 17 | 18 | func init() { 19 | privateLedger.Mint(owner.Owner(), 100_000_000_000_000) 20 | grc20reg.Register(cross, token, "") 21 | } 22 | 23 | func TotalSupply() int64 { 24 | userTeller := token.CallerTeller() 25 | return userTeller.TotalSupply() 26 | } 27 | 28 | func BalanceOf(owner address) int64 { 29 | userTeller := token.CallerTeller() 30 | return userTeller.BalanceOf(owner) 31 | } 32 | 33 | func Allowance(owner, spender address) int64 { 34 | userTeller := token.CallerTeller() 35 | return userTeller.Allowance(owner, spender) 36 | } 37 | 38 | func Transfer(cur realm, to address, amount int64) { 39 | userTeller := token.CallerTeller() 40 | checkErr(userTeller.Transfer(to, amount)) 41 | } 42 | 43 | func Approve(cur realm, spender address, amount int64) { 44 | userTeller := token.CallerTeller() 45 | checkErr(userTeller.Approve(spender, amount)) 46 | } 47 | 48 | func TransferFrom(cur realm, from, to address, amount int64) { 49 | userTeller := token.CallerTeller() 50 | checkErr(userTeller.TransferFrom(from, to, amount)) 51 | } 52 | 53 | func Burn(cur realm, from address, amount int64) { 54 | owner.AssertOwnedByPrevious() 55 | checkErr(privateLedger.Burn(from, amount)) 56 | } 57 | 58 | func Render(path string) string { 59 | parts := strings.Split(path, "/") 60 | c := len(parts) 61 | 62 | switch { 63 | case path == "": 64 | return token.RenderHome() 65 | case c == 2 && parts[0] == "balance": 66 | owner := address(parts[1]) 67 | userTeller := token.CallerTeller() 68 | balance := userTeller.BalanceOf(owner) 69 | return ufmt.Sprintf("%d\n", balance) 70 | default: 71 | return "404\n" 72 | } 73 | } 74 | 75 | func checkErr(err error) { 76 | if err != nil { 77 | panic(err) 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /contract/r/gnoswap/v1/common/assert.gno: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | func AssertIsUserSendGNOTAmount(amount int64) { 4 | if hasNotSupportedCoins() { 5 | panic(errNotSupportedCoins) 6 | } 7 | 8 | if !isUserSendGNOTAmount(amount) && amount > 0 { 9 | panic(errInvalidGNOTAmount) 10 | } 11 | } 12 | 13 | func AssertIsNotHandleNativeCoin() { 14 | if ExistsUserSendCoins() { 15 | panic(errNotHandleNativeCoin) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /contract/r/gnoswap/v1/common/coins.gno: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import "chain/banker" 4 | 5 | // IsGNOTPath checks if the given path is either native gnot or wrapped ugnot path 6 | func IsGNOTPath(path string) bool { 7 | return path == GNOT_PATH || path == WUGNOT_PATH 8 | } 9 | 10 | // IsGNOTNativePath checks if the given path is the native gnot path 11 | func IsGNOTNativePath(path string) bool { 12 | return path == GNOT_PATH 13 | } 14 | 15 | // IsGNOTWrappedPath checks if the given path is the wrapped ugnot path 16 | func IsGNOTWrappedPath(path string) bool { 17 | return path == WUGNOT_PATH 18 | } 19 | 20 | // ExistsUserSendCoins checks if the user has sent any coins with the transaction 21 | func ExistsUserSendCoins() bool { 22 | return len(getUserSendCoins()) > 0 23 | } 24 | 25 | // isUserSendGNOTAmount validates if the user sent the expected gnot amount 26 | // Returns true if no gnot was sent and amount is non-zero, or if sent amount matches expected amount 27 | func isUserSendGNOTAmount(amount int64) bool { 28 | sendCoins := getUserSendCoins() 29 | 30 | gnotAmount, exists := sendCoins[GNOT_DENOM] 31 | if !exists { 32 | return false 33 | } 34 | 35 | return gnotAmount == amount 36 | } 37 | 38 | // hasNotSupportedCoins checks if the user sent unsupported coins 39 | // Returns true if non-gnot coins were sent or if no coins were sent at all 40 | func hasNotSupportedCoins() bool { 41 | sendCoins := getUserSendCoins() 42 | coinsCount := len(sendCoins) 43 | 44 | if _, exists := sendCoins[GNOT_DENOM]; !exists { 45 | return coinsCount == 1 46 | } 47 | 48 | return coinsCount == 0 49 | } 50 | 51 | // getUserSendCoins retrieves and aggregates all coins sent by the user in the current transaction 52 | // Returns a map of denomination to total amount, filtering out zero or negative amounts 53 | func getUserSendCoins() map[string]int64 { 54 | coinsMap := make(map[string]int64) 55 | 56 | for _, coin := range banker.OriginSend() { 57 | if coin.Amount <= 0 { 58 | continue 59 | } 60 | 61 | if _, exists := coinsMap[coin.Denom]; exists { 62 | coinsMap[coin.Denom] += coin.Amount 63 | } else { 64 | coinsMap[coin.Denom] = coin.Amount 65 | } 66 | } 67 | 68 | return coinsMap 69 | } 70 | -------------------------------------------------------------------------------- /contract/r/gnoswap/v1/common/consts.gno: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | // Tick bounds. 4 | const ( 5 | minTick = -887272 6 | maxTick = 887272 7 | 8 | GNOT_DENOM = "ugnot" 9 | GNOT_PATH = "gnot" 10 | WUGNOT_PATH = "gno.land/r/gnoland/wugnot" 11 | ) 12 | -------------------------------------------------------------------------------- /contract/r/gnoswap/v1/common/doc.gno: -------------------------------------------------------------------------------- 1 | // Package common provides shared utilities for GnoSwap v1 contracts. 2 | // 3 | // This package contains core mathematical functions and helpers used across 4 | // the protocol, including tick math for price calculations, liquidity math 5 | // for position management, and GRC20 registry integration. 6 | package common 7 | -------------------------------------------------------------------------------- /contract/r/gnoswap/v1/common/errors.gno: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "errors" 5 | 6 | "gno.land/p/nt/ufmt" 7 | ) 8 | 9 | var ( 10 | errNoPermission = errors.New("[GNOSWAP-COMMON-001] caller has no permission") 11 | errHalted = errors.New("[GNOSWAP-COMMON-002] halted") 12 | errOutOfRange = errors.New("[GNOSWAP-COMMON-003] value out of range") 13 | errNotRegistered = errors.New("[GNOSWAP-COMMON-004] token is not registered") 14 | errInvalidAddr = errors.New("[GNOSWAP-COMMON-005] invalid address") 15 | errOverflow = errors.New("[GNOSWAP-COMMON-006] overflow") 16 | errInvalidPositionId = errors.New("[GNOSWAP-COMMON-007] invalid positionId") 17 | errInvalidInput = errors.New("[GNOSWAP-COMMON-008] invalid input data") 18 | errOverFlow = errors.New("[GNOSWAP-COMMON-009] overflow") 19 | errIdenticalTicks = errors.New("[GNOSWAP-COMMON-010] identical ticks") 20 | errNotSupportedCoins = errors.New("[GNOSWAP-COMMON-011] user send coins contains not supported coins") 21 | errInvalidGNOTAmount = errors.New("[GNOSWAP-COMMON-012] user send gnot amount is not equal to specified amount") 22 | errNotHandleNativeCoin = errors.New("[GNOSWAP-COMMON-013] handle native coin is not allowed") 23 | ) 24 | 25 | // newErrorWithDetail creates an error message with additional context in format " || ". 26 | func newErrorWithDetail(err error, detail string) string { 27 | return ufmt.Errorf("%s || %s", err.Error(), detail).Error() 28 | } 29 | -------------------------------------------------------------------------------- /contract/r/gnoswap/v1/common/gnomod.toml: -------------------------------------------------------------------------------- 1 | module = "gno.land/r/gnoswap/v1/common" 2 | gno = "0.9" 3 | -------------------------------------------------------------------------------- /contract/r/gnoswap/v1/community_pool/README.md: -------------------------------------------------------------------------------- 1 | # Community Pool 2 | 3 | GnoSwap community treasury for ecosystem development. 4 | 5 | ## Overview 6 | 7 | Community-governed treasury that receives protocol emissions and fees for ecosystem growth initiatives. Also collects unclaimed internal staking rewards from warmup periods. 8 | 9 | ## Configuration 10 | 11 | - **Emission Allocation**: 5% of GNS emissions (default) 12 | - **Governance Control**: All disbursements require proposal 13 | - **Fund Sources**: GNS emissions, unclaimed rewards (internal reward only), protocol fees 14 | 15 | ## Governance Process 16 | 17 | - **Proposal Creation**: Submit funding request with justification 18 | - **Voting Period**: Token holders vote on proposal 19 | - **Execution**: Approved transfers execute automatically 20 | - **Transparency**: All operations emit events 21 | 22 | ## Key Functions 23 | 24 | ### `TransferToken` 25 | Transfers tokens to specified address (governance only). 26 | 27 | ## Usage 28 | 29 | ```go 30 | // Transfer via governance proposal 31 | TransferToken( 32 | "gno.land/r/demo/usdc", 33 | recipientAddr, 34 | 1000000, 35 | ) 36 | ``` 37 | 38 | ## Security 39 | 40 | - Governance-only transfers 41 | - No emergency withdrawals 42 | - Event emission for transparency 43 | - Multi-token support -------------------------------------------------------------------------------- /contract/r/gnoswap/v1/community_pool/community_pool.gno: -------------------------------------------------------------------------------- 1 | package community_pool 2 | 3 | import ( 4 | "chain" 5 | "chain/runtime" 6 | "strconv" 7 | 8 | "gno.land/r/gnoswap/access" 9 | "gno.land/r/gnoswap/halt" 10 | "gno.land/r/gnoswap/v1/common" 11 | ) 12 | 13 | // TransferToken transfers tokens from the community pool. 14 | // 15 | // Parameters: 16 | // - tokenPath: token contract path 17 | // - to: recipient address 18 | // - amount: transfer amount 19 | // 20 | // Only callable by admin or governance. 21 | func TransferToken(cur realm, tokenPath string, to address, amount int64) { 22 | halt.AssertIsNotHaltedCommunityPoolWithVersion(1) 23 | halt.AssertIsNotHaltedWithdrawWithVersion(1) 24 | 25 | caller := runtime.PreviousRealm().Address() 26 | access.AssertIsAdminOrGovernance(caller) 27 | 28 | err := transferToken(tokenPath, to, amount) 29 | if err != nil { 30 | panic(err) 31 | } 32 | } 33 | 34 | // transferToken performs actual token transfer. 35 | func transferToken(tokenPath string, to address, amount int64) error { 36 | common.SafeGRC20Transfer(cross, tokenPath, to, amount) 37 | 38 | prevRealm := runtime.PreviousRealm() 39 | chain.Emit( 40 | "TransferToken", 41 | "prevAddr", prevRealm.Address().String(), 42 | "prevRealm", prevRealm.PkgPath(), 43 | "tokenPath", tokenPath, 44 | "to", to.String(), 45 | "amount", strconv.FormatInt(amount, 10), 46 | ) 47 | 48 | return nil 49 | } 50 | -------------------------------------------------------------------------------- /contract/r/gnoswap/v1/community_pool/doc.gno: -------------------------------------------------------------------------------- 1 | // Package community_pool manages the GnoSwap community treasury. 2 | // 3 | // This contract holds protocol-owned assets that can be allocated through 4 | // governance proposals. It receives a portion of GNS emissions and can be 5 | // used for ecosystem development, grants, and protocol improvements. 6 | package community_pool 7 | -------------------------------------------------------------------------------- /contract/r/gnoswap/v1/community_pool/errors.gno: -------------------------------------------------------------------------------- 1 | package community_pool 2 | 3 | import ( 4 | "errors" 5 | ) 6 | 7 | var ( 8 | errNoPermission = errors.New("[GNOSWAP-COMMUNITY_POOL-001] caller has no permission") 9 | errNotRegistered = errors.New("[GNOSWAP-COMMUNITY_POOL-002] not registered") 10 | errAlreadyRegistered = errors.New("[GNOSWAP-COMMUNITY_POOL-003] already registered") 11 | errLocked = errors.New("[GNOSWAP-COMMUNITY_POOL-004] can't transfer token while locked") 12 | errHalted = errors.New("[GNOSWAP-COMMUNITY_POOL-005] halted") 13 | ) 14 | -------------------------------------------------------------------------------- /contract/r/gnoswap/v1/community_pool/gnomod.toml: -------------------------------------------------------------------------------- 1 | module = "gno.land/r/gnoswap/v1/community_pool" 2 | gno = "0.9" 3 | -------------------------------------------------------------------------------- /contract/r/gnoswap/v1/gnft/assert.gno: -------------------------------------------------------------------------------- 1 | package gnft 2 | 3 | import ( 4 | "gno.land/p/demo/tokens/grc721" 5 | "gno.land/p/nt/ufmt" 6 | ) 7 | 8 | // assertIsValidTokenURI panics if the token already has a URI set. 9 | func assertIsValidTokenURI(tid grc721.TokenID) { 10 | uri, _ := nft.TokenURI(tid) 11 | if string(uri) != "" { 12 | panic(makeErrorWithDetails(errCannotSetURI, ufmt.Sprintf("token id (%s) has already set URI", string(tid)))) 13 | } 14 | } 15 | 16 | // assertIsValidAddress panics if the address is invalid. 17 | func assertIsValidAddress(addr address) { 18 | if !addr.IsValid() { 19 | panic(makeErrorWithDetails(errInvalidAddress, ufmt.Sprintf("address (%s)", addr.String()))) 20 | } 21 | } 22 | 23 | // assertFromIsValidAddress panics if the from address is invalid. 24 | func assertFromIsValidAddress(from address) { 25 | if !from.IsValid() { 26 | panic(makeErrorWithDetails(errInvalidAddress, ufmt.Sprintf("from address (%s)", from.String()))) 27 | } 28 | } 29 | 30 | // assertToIsValidAddress panics if the to address is invalid. 31 | func assertToIsValidAddress(to address) { 32 | if !to.IsValid() { 33 | panic(makeErrorWithDetails(errInvalidAddress, ufmt.Sprintf("to address (%s)", to.String()))) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /contract/r/gnoswap/v1/gnft/errors.gno: -------------------------------------------------------------------------------- 1 | package gnft 2 | 3 | import ( 4 | "errors" 5 | 6 | "gno.land/p/nt/ufmt" 7 | ) 8 | 9 | var ( 10 | errNoPermission = errors.New("[GNOSWAP-GNFT-001] caller has no permission") 11 | errNotTokenOwner = errors.New("[GNOSWAP-GNFT-001] caller is not token owner") 12 | 13 | errCannotSetURI = errors.New("[GNOSWAP-GNFT-002] cannot set URI") 14 | errTokenDoesNotExist = errors.New("[GNOSWAP-GNFT-002] cannot set URI || token does not exist") 15 | errTokenBurned = errors.New("[GNOSWAP-GNFT-002] cannot set URI || token has been burned") 16 | 17 | errNoTokenForCaller = errors.New("[GNOSWAP-GNFT-003] no token for caller") 18 | errInvalidAddress = errors.New("[GNOSWAP-GNFT-004] invalid addresss") 19 | errInvalidTokenID = errors.New("[GNOSWAP-GNFT-005] invalid token ID") 20 | 21 | // Transfer errors 22 | errNotOwnerOrApproved = errors.New("[GNOSWAP-GNFT-006] caller is not token owner or approved") 23 | errTokenNotExists = errors.New("[GNOSWAP-GNFT-007] token does not exist") 24 | errTransferToSelf = errors.New("[GNOSWAP-GNFT-008] cannot transfer to self") 25 | errNotApprovedForAll = errors.New("[GNOSWAP-GNFT-009] not approved for all tokens") 26 | ) 27 | 28 | // makeErrorWithDetails creates an error with additional context. 29 | func makeErrorWithDetails(err error, details string) error { 30 | return ufmt.Errorf("%s || %s", err.Error(), details) 31 | } 32 | -------------------------------------------------------------------------------- /contract/r/gnoswap/v1/gnft/gnomod.toml: -------------------------------------------------------------------------------- 1 | module = "gno.land/r/gnoswap/v1/gnft" 2 | gno = "0.9" 3 | -------------------------------------------------------------------------------- /contract/r/gnoswap/v1/gnft/testutils.gno: -------------------------------------------------------------------------------- 1 | package gnft 2 | 3 | import ( 4 | "testing" 5 | 6 | "gno.land/p/demo/tokens/grc721" 7 | ) 8 | 9 | func InitGNFTTest(t *testing.T) { 10 | t.Helper() 11 | 12 | func(cur realm) { 13 | nft = grc721.NewBasicNFT("GNOSWAP NFT", "GNFT") 14 | }(cross) 15 | } 16 | -------------------------------------------------------------------------------- /contract/r/gnoswap/v1/gov/doc.gno: -------------------------------------------------------------------------------- 1 | // Package gov provides Gnoswap's governance system through three packages: 2 | // 1. governance: Handles proposal creation, voting, and execution 3 | // 2. staker: Manages GNS staking, delegation, and reward distribution 4 | // 3. xgns: Implements the xGNS token representing staked GNS 5 | package gov 6 | -------------------------------------------------------------------------------- /contract/r/gnoswap/v1/gov/gnomod.toml: -------------------------------------------------------------------------------- 1 | module = "gno.land/r/xxx_myrealm_xxx/xxx_fixme_xxx" 2 | gno = "0.0" 3 | -------------------------------------------------------------------------------- /contract/r/gnoswap/v1/gov/governance/_helper_test.gno: -------------------------------------------------------------------------------- 1 | package governance 2 | 3 | import ( 4 | "chain/banker" 5 | "testing" 6 | 7 | "gno.land/p/onbloc/json" 8 | 9 | prbac "gno.land/p/gnoswap/rbac" 10 | "gno.land/r/gnoswap/access" 11 | ) 12 | 13 | var ( 14 | admin, _ = access.GetAddress(prbac.ROLE_ADMIN.String()) 15 | 16 | fooPath string = "gno.land/r/onbloc/foo" 17 | barPath string = "gno.land/r/onbloc/bar" 18 | bazPath string = "gno.land/r/onbloc/baz" 19 | quxPath string = "gno.land/r/onbloc/qux" 20 | 21 | oblPath string = "gno.land/r/onbloc/obl" 22 | 23 | fee100 uint32 = 100 24 | fee500 uint32 = 500 25 | fee3000 uint32 = 3000 26 | 27 | max_timeout int64 = 9999999999 28 | 29 | wugnotAddr address = "g15vj5q08amlvyd0nx6zjgcvwq2d0gt9fcchrvum" 30 | ) 31 | 32 | // Realms to mock frames 33 | var ( 34 | adminRealm = testing.NewUserRealm(admin) 35 | 36 | posPath = "gno.land/r/gnoswap/v1/position" 37 | posRealm = testing.NewCodeRealm(posPath) 38 | 39 | routerPath = "gno.land/r/gnoswap/v1/router" 40 | rouRealm = testing.NewCodeRealm(routerPath) 41 | 42 | stakerPath = "gno.land/r/gnoswap/v1/staker" 43 | stkRealm = testing.NewCodeRealm(stakerPath) 44 | 45 | govPath = "gno.land/r/gnoswap/v1/gov/governance" 46 | govRealm = testing.NewCodeRealm(govPath) 47 | 48 | govStakerAddr, _ = access.GetAddress(prbac.ROLE_GOV_STAKER.String()) 49 | communityPoolAddr, _ = access.GetAddress(prbac.ROLE_COMMUNITY_POOL.String()) 50 | launchpadAddr, _ = access.GetAddress(prbac.ROLE_LAUNCHPAD.String()) 51 | ) 52 | 53 | /* HELPER */ 54 | func ugnotBalanceOf(addr address) uint64 { 55 | testBanker := banker.NewBanker(banker.BankerTypeRealmIssue) 56 | 57 | coins := testBanker.GetCoins(addr) 58 | if len(coins) == 0 { 59 | return 0 60 | } 61 | 62 | return uint64(coins.AmountOf("ugnot")) 63 | } 64 | 65 | func unmarshal(data string) *json.Node { 66 | return json.Must(json.Unmarshal([]byte(data))) 67 | } 68 | -------------------------------------------------------------------------------- /contract/r/gnoswap/v1/gov/governance/assert.gno: -------------------------------------------------------------------------------- 1 | package governance 2 | 3 | // assertCallerIsProposer panics if the caller is not the proposer of the given proposal. 4 | func assertCallerIsProposer(proposalID int64, caller address) { 5 | proposal, exists := getProposal(proposalID) 6 | if !exists { 7 | panic(errProposalNotFound) 8 | } 9 | 10 | if !proposal.IsProposer(caller) { 11 | panic(errNotProposer) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /contract/r/gnoswap/v1/gov/governance/consts.gno: -------------------------------------------------------------------------------- 1 | package governance 2 | 3 | const ( 4 | // Governance can execute multiple messages in a single proposal 5 | // each message is a string with the following format: 6 | // *EXE**EXE* 7 | // To execute a message, we need to parse the message and call the corresponding function 8 | // with the given parameters 9 | parameterSeparator = "*EXE*" 10 | 11 | messageSeparator = "*GOV*" 12 | 13 | maxTitleLength = 255 14 | maxDescriptionLength = 10_000 15 | maxNumberOfExecution = 10 16 | ) 17 | -------------------------------------------------------------------------------- /contract/r/gnoswap/v1/gov/governance/counter.gno: -------------------------------------------------------------------------------- 1 | package governance 2 | 3 | // Counter manages unique incrementing IDs. 4 | type Counter struct { 5 | id int64 6 | } 7 | 8 | // NewCounter creates a new Counter starting at 0. 9 | func NewCounter() *Counter { 10 | return &Counter{ 11 | id: 0, 12 | } 13 | } 14 | 15 | // next increments and returns the next ID. 16 | func (c *Counter) next() int64 { 17 | c.id++ 18 | 19 | return c.id 20 | } 21 | 22 | // Get returns the current ID without incrementing. 23 | func (c *Counter) Get() int64 { 24 | return c.id 25 | } 26 | -------------------------------------------------------------------------------- /contract/r/gnoswap/v1/gov/governance/counter_test.gno: -------------------------------------------------------------------------------- 1 | package governance 2 | 3 | import ( 4 | "testing" 5 | 6 | "gno.land/p/nt/uassert" 7 | ) 8 | 9 | // Test for NewCounter, next, and Get methods. 10 | func TestCounter_Behavior(t *testing.T) { 11 | tests := []struct { 12 | name string 13 | initValue int64 14 | callNextTimes int 15 | expectedId int64 16 | }{ 17 | { 18 | name: "Initial value is zero", 19 | initValue: 0, 20 | callNextTimes: 0, 21 | expectedId: 0, 22 | }, 23 | { 24 | name: "Call next once", 25 | initValue: 0, 26 | callNextTimes: 1, 27 | expectedId: 1, 28 | }, 29 | { 30 | name: "Call next multiple times", 31 | initValue: 0, 32 | callNextTimes: 5, 33 | expectedId: 5, 34 | }, 35 | { 36 | name: "Start from non-zero, call next", 37 | initValue: 10, 38 | callNextTimes: 3, 39 | expectedId: 13, 40 | }, 41 | } 42 | 43 | for _, tc := range tests { 44 | t.Run(tc.name, func(t *testing.T) { 45 | // given: create counter and set initial value if needed 46 | c := NewCounter() 47 | if tc.initValue > 0 { 48 | c.id = tc.initValue 49 | } 50 | 51 | // when: call next() as many times as needed 52 | for i := 0; i < tc.callNextTimes; i++ { 53 | c.next() 54 | } 55 | 56 | // then: check id value 57 | uassert.Equal(t, c.Get(), tc.expectedId) 58 | }) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /contract/r/gnoswap/v1/gov/governance/doc.gno: -------------------------------------------------------------------------------- 1 | // Package governance implements proposal lifecycle management and voting. 2 | // It supports text proposals, parameter changes, and community pool spending. 3 | // Proposals go through creation, voting, and execution phases with configurable 4 | // parameters for voting delays, periods, and thresholds. 5 | package governance 6 | -------------------------------------------------------------------------------- /contract/r/gnoswap/v1/gov/governance/getter_proposal.gno: -------------------------------------------------------------------------------- 1 | package governance 2 | 3 | import "time" 4 | 5 | func mustGetProposal(proposalId int64) *Proposal { 6 | proposal, ok := getProposal(proposalId) 7 | if !ok { 8 | panic(errDataNotFound) 9 | } 10 | 11 | return proposal 12 | } 13 | 14 | func GetProposerByProposalId(proposalId int64) string { 15 | return mustGetProposal(proposalId).proposer.String() 16 | } 17 | 18 | func GetProposalTypeByProposalId(proposalId int64) string { 19 | return mustGetProposal(proposalId).data.proposalType.String() 20 | } 21 | 22 | func GetYeaByProposalId(proposalId int64) int64 { 23 | return mustGetProposal(proposalId).status.YesWeight() 24 | } 25 | 26 | func GetNayByProposalId(proposalId int64) int64 { 27 | return mustGetProposal(proposalId).status.NoWeight() 28 | } 29 | 30 | func GetConfigVersionByProposalId(proposalId int64) int64 { 31 | return mustGetProposal(proposalId).configVersion 32 | } 33 | 34 | func GetQuorumAmountByProposalId(proposalId int64) int64 { 35 | return mustGetProposal(proposalId).status.voteStatus.quorumAmount 36 | } 37 | 38 | func GetTitleByProposalId(proposalId int64) string { 39 | return mustGetProposal(proposalId).metadata.title 40 | } 41 | 42 | func GetDescriptionByProposalId(proposalId int64) string { 43 | return mustGetProposal(proposalId).metadata.description 44 | } 45 | 46 | // GetExecutionStateByProposalId is deprecated. Use GetProposalStatusById instead. 47 | // This function is kept for backward compatibility. 48 | func GetExecutionStateByProposalId(proposalId int64) string { 49 | currentAt := time.Now().Unix() 50 | proposal := mustGetProposal(proposalId) 51 | 52 | return proposal.Status(currentAt) 53 | } 54 | 55 | func GetLatestConfig() Config { 56 | config, ok := getCurrentConfig() 57 | if !ok { 58 | panic(errDataNotFound) 59 | } 60 | 61 | return config 62 | } 63 | 64 | func GetConfig(configVersion int64) Config { 65 | config, ok := getConfig(configVersion) 66 | if !ok { 67 | panic(errDataNotFound) 68 | } 69 | 70 | return config 71 | } 72 | -------------------------------------------------------------------------------- /contract/r/gnoswap/v1/gov/governance/getter_vote.gno: -------------------------------------------------------------------------------- 1 | package governance 2 | 3 | func GetVoteWeight(proposalID int64, address address) int64 { 4 | proposalUserVotingInfo, ok := getProposalUserVotingInfo(proposalID, address) 5 | if !ok { 6 | panic(errDataNotFound) 7 | } 8 | 9 | return proposalUserVotingInfo.VotedWeight() 10 | } 11 | 12 | func GetVotedHeight(proposalID int64, address address) int64 { 13 | proposalUserVotingInfo, ok := getProposalUserVotingInfo(proposalID, address) 14 | if !ok { 15 | panic(errDataNotFound) 16 | } 17 | 18 | return proposalUserVotingInfo.votedHeight 19 | } 20 | 21 | func GetVotedAt(proposalID int64, address address) int64 { 22 | proposalUserVotingInfo, ok := getProposalUserVotingInfo(proposalID, address) 23 | if !ok { 24 | panic(errDataNotFound) 25 | } 26 | 27 | return proposalUserVotingInfo.votedAt 28 | } 29 | -------------------------------------------------------------------------------- /contract/r/gnoswap/v1/gov/governance/gnomod.toml: -------------------------------------------------------------------------------- 1 | module = "gno.land/r/gnoswap/v1/gov/governance" 2 | gno = "0.9" 3 | -------------------------------------------------------------------------------- /contract/r/gnoswap/v1/gov/governance/parameter_registry_handler_test.gno: -------------------------------------------------------------------------------- 1 | package governance 2 | 3 | import ( 4 | "testing" 5 | 6 | "gno.land/p/nt/uassert" 7 | "gno.land/p/nt/ufmt" 8 | ) 9 | 10 | // TestRegistryHandler_Execute tests the execution functionality of registry handler 11 | func TestRegistryHandler_Execute(t *testing.T) { 12 | tests := []struct { 13 | name string 14 | params []string 15 | handlerFn ParameterHandler 16 | expectedError bool 17 | expectedErrMsg string 18 | }{ 19 | { 20 | name: "Success - Execute with valid params", 21 | params: []string{"param1", "param2"}, 22 | handlerFn: &ParameterHandlerOptions{ 23 | pkgPath: "test/pkg", 24 | function: "testFunc", 25 | paramCount: 2, 26 | handlerFunc: func([]string) error { 27 | return nil 28 | }, 29 | }, 30 | expectedError: false, 31 | }, 32 | { 33 | name: "Failure - Different param count", 34 | params: []string{"param1"}, 35 | handlerFn: &ParameterHandlerOptions{ 36 | pkgPath: "test/pkg", 37 | function: "testFunc", 38 | paramCount: 2, 39 | handlerFunc: func([]string) error { 40 | return ufmt.Errorf("test error") 41 | }, 42 | }, 43 | expectedError: true, 44 | expectedErrMsg: "expected 2 parameters, got 1", 45 | }, 46 | { 47 | name: "Failure - Execute with error", 48 | params: []string{"param1"}, 49 | handlerFn: &ParameterHandlerOptions{ 50 | pkgPath: "test/pkg", 51 | function: "testFunc", 52 | paramCount: 1, 53 | handlerFunc: func([]string) error { 54 | return ufmt.Errorf("test error") 55 | }, 56 | }, 57 | expectedError: true, 58 | expectedErrMsg: "test error", 59 | }, 60 | } 61 | 62 | for _, tc := range tests { 63 | t.Run(tc.name, func(t *testing.T) { 64 | // given 65 | handler := tc.handlerFn 66 | 67 | // when 68 | err := handler.Execute(tc.params) 69 | 70 | // then 71 | if tc.expectedError { 72 | uassert.NotNil(t, err) 73 | uassert.Equal(t, err.Error(), tc.expectedErrMsg) 74 | } else { 75 | uassert.Nil(t, err) 76 | } 77 | }) 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /contract/r/gnoswap/v1/gov/staker/api_delegation.gno: -------------------------------------------------------------------------------- 1 | package staker 2 | 3 | import ( 4 | "gno.land/r/gnoswap/v1/gov/xgns" 5 | ) 6 | 7 | // GetTotalxGnsSupply returns the total amount of xGNS supply. 8 | func GetTotalxGnsSupply() int64 { 9 | return xgns.TotalSupply() 10 | } 11 | 12 | // GetTotalVoteWeight returns the total amount of xGNS used for voting. 13 | func GetTotalVoteWeight() int64 { 14 | return xgns.VotingSupply() 15 | } 16 | 17 | // GetTotalDelegated returns the total amount of xGNS delegated. 18 | func GetTotalDelegated() int64 { 19 | return totalDelegatedAmount 20 | } 21 | 22 | // GetTotalLockedAmount returns the total amount of locked GNS. 23 | func GetTotalLockedAmount() int64 { 24 | return totalLockedAmount 25 | } 26 | -------------------------------------------------------------------------------- /contract/r/gnoswap/v1/gov/staker/api_staker.gno: -------------------------------------------------------------------------------- 1 | package staker 2 | 3 | import ( 4 | "chain/runtime" 5 | "time" 6 | 7 | "gno.land/p/nt/ufmt" 8 | "gno.land/p/onbloc/json" 9 | 10 | "gno.land/r/gnoswap/emission" 11 | "gno.land/r/gnoswap/v1/protocol_fee" 12 | ) 13 | 14 | // GetLockedAmount returns total locked GNS. 15 | func GetLockedAmount() int64 { 16 | lockedAmount := int64(0) 17 | 18 | delegations.Iterate("", "", func(key string, value any) bool { 19 | delegation, ok := value.(*Delegation) 20 | if !ok { 21 | panic(ufmt.Sprintf("failed to cast delegations's element to *Delegation: %T", value)) 22 | } 23 | lockedAmount += delegation.DelegatedAmount() 24 | return false 25 | }) 26 | 27 | return lockedAmount 28 | } 29 | 30 | // GetClaimableRewardByAddress returns claimable reward for address. 31 | func GetClaimableRewardByAddress(addr address) string { 32 | return GetClaimableRewardByRewardID(addr.String()) 33 | } 34 | 35 | // GetClaimableRewardByLaunchpad returns claimable reward for launchpad. 36 | func GetClaimableRewardByLaunchpad(addr address) string { 37 | return GetClaimableRewardByRewardID(makeLaunchpadRewardID(addr.String())) 38 | } 39 | 40 | // GetClaimableRewardByRewardID returns claimable reward by ID. 41 | func GetClaimableRewardByRewardID(rewardID string) string { 42 | func(cur realm) { 43 | emission.MintAndDistributeGns(cross) 44 | protocol_fee.DistributeProtocolFee(cross) 45 | }(cross) 46 | 47 | emissionDistributedAmount := emission.GetAccuDistributedToGovStaker() 48 | emissionReward, _ := emissionRewardManager.GetClaimableRewardAmount(emissionDistributedAmount, rewardID, time.Now().Unix()) 49 | 50 | protocolFeeDistributedAmounts := getDistributedProtocolFees() 51 | protocolFeeRewards, _ := protocolFeeRewardManager.GetClaimableRewardAmounts(protocolFeeDistributedAmounts, rewardID, time.Now().Unix()) 52 | 53 | if emissionReward == 0 && len(protocolFeeRewards) == 0 { 54 | return "" 55 | } 56 | 57 | data := json.Builder(). 58 | WriteString("height", formatInt(runtime.ChainHeight())). 59 | WriteString("now", formatInt(time.Now().Unix())). 60 | WriteString("emissionReward", formatInt(emissionReward)). 61 | Node() 62 | 63 | // Always include protocolFees array, even if empty 64 | pfArr := json.ArrayNode("", nil) 65 | for tokenPath, protocolFeeReward := range protocolFeeRewards { 66 | if protocolFeeReward > 0 { 67 | pfObj := json.Builder(). 68 | WriteString("tokenPath", tokenPath). 69 | WriteString("amount", formatInt(protocolFeeReward)). 70 | Node() 71 | pfArr.AppendArray(pfObj) 72 | } 73 | } 74 | data.AppendObject("protocolFees", pfArr) 75 | 76 | return marshal(data) 77 | } 78 | -------------------------------------------------------------------------------- /contract/r/gnoswap/v1/gov/staker/assert.gno: -------------------------------------------------------------------------------- 1 | package staker 2 | 3 | import "gno.land/p/nt/ufmt" 4 | 5 | // assertIsValidDelegateAmount validates that the delegation amount meets system requirements. 6 | // This function checks minimum amount and multiple requirements. 7 | // 8 | // Parameters: 9 | // - amount: amount to validate 10 | // 11 | // Returns: 12 | // - error: nil if valid, error describing validation failure 13 | func assertIsValidDelegateAmount(amount int64) { 14 | if amount < minimumAmount { 15 | panic(makeErrorWithDetails( 16 | errLessThanMinimum, 17 | ufmt.Sprintf("minimum amount to delegate is %d (requested:%d)", minimumAmount, amount), 18 | )) 19 | } 20 | 21 | if amount%minimumAmount != 0 { 22 | panic(makeErrorWithDetails( 23 | errInvalidAmount, 24 | ufmt.Sprintf("amount must be multiple of %d", minimumAmount), 25 | )) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /contract/r/gnoswap/v1/gov/staker/assert_test.gno: -------------------------------------------------------------------------------- 1 | package staker 2 | 3 | import ( 4 | "testing" 5 | 6 | "gno.land/p/nt/uassert" 7 | ) 8 | 9 | // Test validateDelegateAmount function 10 | func TestStakerDelegate_validateDelegateAmount(t *testing.T) { 11 | tests := []struct { 12 | name string 13 | amount int64 14 | expectError bool 15 | expectedMsg string 16 | }{ 17 | { 18 | name: "Valid minimum amount", 19 | amount: minimumAmount, 20 | expectError: false, 21 | }, 22 | { 23 | name: "Valid multiple of minimum amount", 24 | amount: minimumAmount * 2, 25 | expectError: false, 26 | }, 27 | { 28 | name: "Amount less than minimum", 29 | amount: minimumAmount - 1, 30 | expectError: true, 31 | expectedMsg: "[GNOSWAP-GOV_STAKER-011] can not delegate less than minimum amount || minimum amount to delegate is 1000000 (requested:999999)", 32 | }, 33 | { 34 | name: "Amount not multiple of minimum", 35 | amount: minimumAmount + 1, 36 | expectError: true, 37 | expectedMsg: "[GNOSWAP-GOV_STAKER-004] invalid amount || amount must be multiple of 1000000", 38 | }, 39 | { 40 | name: "Zero amount", 41 | amount: 0, 42 | expectError: true, 43 | expectedMsg: "[GNOSWAP-GOV_STAKER-011] can not delegate less than minimum amount || minimum amount to delegate is 1000000 (requested:0)", 44 | }, 45 | { 46 | name: "Negative amount", 47 | amount: -100, 48 | expectError: true, 49 | expectedMsg: "[GNOSWAP-GOV_STAKER-011] can not delegate less than minimum amount || minimum amount to delegate is 1000000 (requested:-100)", 50 | }, 51 | } 52 | 53 | for _, tt := range tests { 54 | t.Run(tt.name, func(t *testing.T) { 55 | // Then: Check result 56 | if tt.expectError { 57 | uassert.PanicsWithMessage(t, tt.expectedMsg, func() { 58 | assertIsValidDelegateAmount(tt.amount) 59 | }) 60 | } else { 61 | uassert.NotPanics(t, func() { 62 | assertIsValidDelegateAmount(tt.amount) 63 | }) 64 | } 65 | }) 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /contract/r/gnoswap/v1/gov/staker/consts.gno: -------------------------------------------------------------------------------- 1 | package staker 2 | 3 | import ( 4 | u256 "gno.land/p/gnoswap/uint256" 5 | ) 6 | 7 | const ( 8 | GNOT string = "gnot" 9 | minimumAmount = 1_000_000 // 1 GNS 10 | ) 11 | 12 | var q128 = u256.MustFromDecimal("340282366920938463463374607431768211456") 13 | -------------------------------------------------------------------------------- /contract/r/gnoswap/v1/gov/staker/counter.gno: -------------------------------------------------------------------------------- 1 | package staker 2 | 3 | type Counter struct { 4 | id int64 5 | } 6 | 7 | func NewCounter() *Counter { return &Counter{id: 0} } 8 | func (c *Counter) Get() int64 { return c.id } 9 | 10 | func (c *Counter) next() int64 { 11 | c.id++ 12 | return c.id 13 | } 14 | -------------------------------------------------------------------------------- /contract/r/gnoswap/v1/gov/staker/counter_test.gno: -------------------------------------------------------------------------------- 1 | package staker 2 | 3 | import ( 4 | "testing" 5 | 6 | "gno.land/p/nt/uassert" 7 | ) 8 | 9 | // TestCounter_NewCounter tests the creation of new Counter instances 10 | func TestCounter_NewCounter(t *testing.T) { 11 | tests := []struct { 12 | name string 13 | expectedID int64 14 | }{ 15 | { 16 | name: "Create new counter", 17 | expectedID: 0, 18 | }, 19 | } 20 | 21 | for _, tc := range tests { 22 | t.Run(tc.name, func(t *testing.T) { 23 | // when 24 | counter := NewCounter() 25 | 26 | // then 27 | uassert.NotNil(t, counter) 28 | uassert.Equal(t, counter.Get(), tc.expectedID) 29 | }) 30 | } 31 | } 32 | 33 | // TestCounter_Next tests the next functionality 34 | func TestCounter_Next(t *testing.T) { 35 | tests := []struct { 36 | name string 37 | callCount int 38 | expectedLast int64 39 | }{ 40 | { 41 | name: "Single next call", 42 | callCount: 1, 43 | expectedLast: 1, 44 | }, 45 | { 46 | name: "Multiple next calls", 47 | callCount: 5, 48 | expectedLast: 5, 49 | }, 50 | { 51 | name: "Ten next calls", 52 | callCount: 10, 53 | expectedLast: 10, 54 | }, 55 | } 56 | 57 | for _, tc := range tests { 58 | t.Run(tc.name, func(t *testing.T) { 59 | // given 60 | counter := NewCounter() 61 | 62 | // when 63 | var lastID int64 64 | for i := 0; i < tc.callCount; i++ { 65 | lastID = counter.next() 66 | } 67 | 68 | // then 69 | uassert.Equal(t, lastID, tc.expectedLast) 70 | uassert.Equal(t, counter.Get(), tc.expectedLast) 71 | }) 72 | } 73 | } 74 | 75 | // TestCounter_Get tests the get functionality 76 | func TestCounter_Get(t *testing.T) { 77 | tests := []struct { 78 | name string 79 | nextCalls int 80 | expectedValue int64 81 | }{ 82 | { 83 | name: "Get initial value", 84 | nextCalls: 0, 85 | expectedValue: 0, 86 | }, 87 | { 88 | name: "Get after one increment", 89 | nextCalls: 1, 90 | expectedValue: 1, 91 | }, 92 | { 93 | name: "Get after multiple increments", 94 | nextCalls: 3, 95 | expectedValue: 3, 96 | }, 97 | } 98 | 99 | for _, tc := range tests { 100 | t.Run(tc.name, func(t *testing.T) { 101 | // given 102 | counter := NewCounter() 103 | for i := 0; i < tc.nextCalls; i++ { 104 | counter.next() 105 | } 106 | 107 | // when 108 | value := counter.Get() 109 | 110 | // then 111 | uassert.Equal(t, value, tc.expectedValue) 112 | }) 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /contract/r/gnoswap/v1/gov/staker/delegation_history.gno: -------------------------------------------------------------------------------- 1 | package staker 2 | 3 | // DelegationHistory represents a chronological list of delegation records 4 | // used to track delegation changes over time for snapshot calculations 5 | type DelegationHistory []*DelegationRecord 6 | 7 | // getRecordsBy retrieves delegation records that occurred at or after the specified snapshot time. 8 | // This method is used to filter historical records for calculating delegation snapshots at specific points in time. 9 | // 10 | // Parameters: 11 | // - snapshotTime: timestamp to filter records from (inclusive) 12 | // 13 | // Returns: 14 | // - DelegationHistory: filtered records occurring at or after snapshotTime 15 | func (dh DelegationHistory) getRecordsBy(snapshotTime int64) DelegationHistory { 16 | records := make(DelegationHistory, 0) 17 | 18 | historyIndex := -1 19 | 20 | // Find the first record at or after the snapshot time 21 | for index, record := range dh { 22 | if record.CreatedAt() >= snapshotTime { 23 | historyIndex = index 24 | break 25 | } 26 | } 27 | 28 | // If no records found at or after snapshot time, return empty slice 29 | if historyIndex == -1 { 30 | return records 31 | } 32 | 33 | // Return all records from the found index onwards 34 | records = append(records, dh[historyIndex:]...) 35 | 36 | return records 37 | } 38 | 39 | // addRecord appends a new delegation record to the history. 40 | // This method maintains the chronological order of delegation events. 41 | // 42 | // Parameters: 43 | // - delegationRecord: the delegation record to add to history 44 | // 45 | // Returns: 46 | // - DelegationHistory: updated history with the new record appended 47 | func (dh DelegationHistory) addRecord(delegationRecord *DelegationRecord) DelegationHistory { 48 | return append(dh, delegationRecord) 49 | } 50 | 51 | // removeRecordsBy removes historical records that occurred before the specified time. 52 | // This method is used for cleanup operations to remove old historical data. 53 | // 54 | // Parameters: 55 | // - previousTime: cutoff timestamp for record removal 56 | // 57 | // Returns: 58 | // - DelegationHistory: filtered history containing only records at or after previousTime 59 | func (dh DelegationHistory) removeRecordsBy(previousTime int64) DelegationHistory { 60 | return dh.getRecordsBy(previousTime) 61 | } 62 | -------------------------------------------------------------------------------- /contract/r/gnoswap/v1/gov/staker/doc.gno: -------------------------------------------------------------------------------- 1 | // Package staker manages GNS token staking and delegation functionality. 2 | // It handles delegation of voting power, distribution of protocol rewards, 3 | // and implements a 7-day lockup period for unstaking operations. 4 | package staker 5 | -------------------------------------------------------------------------------- /contract/r/gnoswap/v1/gov/staker/errors.gno: -------------------------------------------------------------------------------- 1 | package staker 2 | 3 | import ( 4 | "errors" 5 | 6 | "gno.land/p/nt/ufmt" 7 | ) 8 | 9 | var ( 10 | errNoPermission = errors.New("[GNOSWAP-GOV_STAKER-001] caller has no permission") 11 | errDataNotFound = errors.New("[GNOSWAP-GOV_STAKER-002] requested data not found") 12 | errTransferFailed = errors.New("[GNOSWAP-GOV_STAKER-003] transfer failed") 13 | errInvalidAmount = errors.New("[GNOSWAP-GOV_STAKER-004] invalid amount") 14 | errNoDelegatedAmount = errors.New("[GNOSWAP-GOV_STAKER-005] zero delegated amount") 15 | errNoDelegatedTarget = errors.New("[GNOSWAP-GOV_STAKER-006] did not delegated to that address") 16 | errNotEnoughDelegated = errors.New("[GNOSWAP-GOV_STAKER-007] not enough delegated") 17 | errInvalidAddress = errors.New("[GNOSWAP-GOV_STAKER-008] invalid address") 18 | errFutureTime = errors.New("[GNOSWAP-GOV_STAKER-009] can not use future time") 19 | errNotEnoughBalance = errors.New("[GNOSWAP-GOV_STAKER-010] not enough balance") 20 | errLessThanMinimum = errors.New("[GNOSWAP-GOV_STAKER-011] can not delegate less than minimum amount") 21 | ) 22 | 23 | func makeErrorWithDetails(err error, detail string) error { 24 | return ufmt.Errorf("%s || %s", err.Error(), detail) 25 | } 26 | 27 | // checkTransferError checks transfer error. 28 | func checkTransferError(err error) { 29 | if err != nil { 30 | panic(makeErrorWithDetails(errTransferFailed, err.Error())) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /contract/r/gnoswap/v1/gov/staker/getter_delegation_snapshot.gno: -------------------------------------------------------------------------------- 1 | package staker 2 | 3 | // GetDelegationSnapshots retrieves the delegation snapshot at a specific point in time. 4 | // This function reconstructs historical delegation states by taking the current snapshot 5 | // and reversing the effects of delegation records that occurred after the specified time. 6 | // 7 | // The algorithm works by: 8 | // 1. Cloning the current delegation snapshot 9 | // 2. Getting all delegation records that occurred at or after the snapshot time 10 | // 3. Subtracting each record in reverse chronological order to restore the historical state 11 | // 12 | // Parameters: 13 | // - snapshotTime: timestamp to retrieve the snapshot for 14 | // 15 | // Returns: 16 | // - DelegationSnapshot: delegation state at the specified time 17 | // - bool: true if snapshot was successfully calculated, false otherwise 18 | func GetDelegationSnapshots(snapshotTime int64) (DelegationSnapshot, bool) { 19 | // Get current delegation snapshots and create a working copy 20 | delegationSnapshots := getDelegationSnapshots() 21 | currentDelegationSnapshot := delegationSnapshots.clone() 22 | 23 | // Get delegation history and filter for records after snapshot time 24 | delegationHistory := getDelegationHistory() 25 | historyRecords := delegationHistory.getRecordsBy(snapshotTime) 26 | 27 | // Apply records in reverse order to reconstruct historical state 28 | // This effectively "undoes" all delegation changes that happened after snapshotTime 29 | for i := len(historyRecords) - 1; i >= 0; i-- { 30 | currentDelegationSnapshot = currentDelegationSnapshot.subRecord(historyRecords[i]) 31 | } 32 | 33 | return currentDelegationSnapshot, true 34 | } 35 | -------------------------------------------------------------------------------- /contract/r/gnoswap/v1/gov/staker/gnomod.toml: -------------------------------------------------------------------------------- 1 | module = "gno.land/r/gnoswap/v1/gov/staker" 2 | gno = "0.9" 3 | -------------------------------------------------------------------------------- /contract/r/gnoswap/v1/gov/staker/util_test.gno: -------------------------------------------------------------------------------- 1 | package staker 2 | 3 | import ( 4 | "testing" 5 | 6 | "gno.land/p/nt/uassert" 7 | ) 8 | 9 | // TestFormatUint tests uint64 formatting 10 | func TestFormatUint(t *testing.T) { 11 | tests := []struct { 12 | name string 13 | input uint64 14 | expected string 15 | }{ 16 | { 17 | name: "Format zero", 18 | input: 0, 19 | expected: "0", 20 | }, 21 | { 22 | name: "Format positive number", 23 | input: 12345, 24 | expected: "12345", 25 | }, 26 | { 27 | name: "Format large number", 28 | input: 18446744073709551615, // max uint64 29 | expected: "18446744073709551615", 30 | }, 31 | } 32 | 33 | for _, tc := range tests { 34 | t.Run(tc.name, func(t *testing.T) { 35 | // when 36 | result := formatUint(tc.input) 37 | 38 | // then 39 | uassert.Equal(t, result, tc.expected) 40 | }) 41 | } 42 | } 43 | 44 | // TestFormatInt tests int64 formatting 45 | func TestFormatInt(t *testing.T) { 46 | tests := []struct { 47 | name string 48 | input int64 49 | expected string 50 | }{ 51 | { 52 | name: "Format zero", 53 | input: 0, 54 | expected: "0", 55 | }, 56 | { 57 | name: "Format positive number", 58 | input: 12345, 59 | expected: "12345", 60 | }, 61 | { 62 | name: "Format negative number", 63 | input: -12345, 64 | expected: "-12345", 65 | }, 66 | { 67 | name: "Format max int64", 68 | input: 9223372036854775807, 69 | expected: "9223372036854775807", 70 | }, 71 | } 72 | 73 | for _, tc := range tests { 74 | t.Run(tc.name, func(t *testing.T) { 75 | // when 76 | result := formatInt(tc.input) 77 | 78 | // then 79 | uassert.Equal(t, result, tc.expected) 80 | }) 81 | } 82 | } 83 | 84 | // TestB64Encode tests base64 encoding 85 | func TestB64Encode(t *testing.T) { 86 | tests := []struct { 87 | name string 88 | input string 89 | expected string 90 | }{ 91 | { 92 | name: "Encode simple string", 93 | input: "hello", 94 | expected: "aGVsbG8=", 95 | }, 96 | { 97 | name: "Encode empty string", 98 | input: "", 99 | expected: "", 100 | }, 101 | { 102 | name: "Encode complex string", 103 | input: "Hello, World!", 104 | expected: "SGVsbG8sIFdvcmxkIQ==", 105 | }, 106 | } 107 | 108 | for _, tc := range tests { 109 | t.Run(tc.name, func(t *testing.T) { 110 | // when 111 | result := b64Encode(tc.input) 112 | 113 | // then 114 | uassert.Equal(t, result, tc.expected) 115 | }) 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /contract/r/gnoswap/v1/gov/xgns/doc.gno: -------------------------------------------------------------------------------- 1 | // Package xgns implements the GRC20-compliant xGNS token that represents 2 | // staked GNS tokens. It manages minting/burning operations and tracks 3 | // voting power for governance. 4 | package xgns 5 | -------------------------------------------------------------------------------- /contract/r/gnoswap/v1/gov/xgns/errors.gno: -------------------------------------------------------------------------------- 1 | package xgns 2 | 3 | import ( 4 | "errors" 5 | ) 6 | 7 | var errNoPermission = errors.New("[GNOSWAP-XGNS-001] caller has no permission") 8 | 9 | // checkErr panics if an error occurs. 10 | func checkErr(err error) { 11 | if err != nil { 12 | panic(err.Error()) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /contract/r/gnoswap/v1/gov/xgns/gnomod.toml: -------------------------------------------------------------------------------- 1 | module = "gno.land/r/gnoswap/v1/gov/xgns" 2 | gno = "0.9" 3 | -------------------------------------------------------------------------------- /contract/r/gnoswap/v1/launchpad/README.md: -------------------------------------------------------------------------------- 1 | # Launchpad 2 | 3 | Token distribution platform for early-stage projects. 4 | 5 | ## Overview 6 | 7 | Launchpad enables new projects to distribute tokens to GNS stakers with tiered lock periods and pro-rata reward distribution. For more details about the concept, check out [docs](https://docs.gnoswap.io/core-concepts/launchpad). 8 | 9 | ## Configuration 10 | 11 | - **Pool Tiers**: 30, 90, 180 days 12 | - **Minimum Start Delay**: 7 days 13 | - **Auto-delegation**: Staked GNS converts to xGNS 14 | - **Tier Allocation**: Customizable per project 15 | 16 | ## Core Features 17 | 18 | - GNS staking for project token rewards 19 | - Multiple tier durations with different rewards 20 | - Automatic xGNS delegation for governance 21 | - Pro-rata distribution based on stake size 22 | - Conditional participation requirements 23 | 24 | ## Key Functions 25 | 26 | ### `CreateProject` 27 | Creates new token distribution project. 28 | 29 | ### `DepositGns` 30 | Stakes GNS to earn project tokens. 31 | 32 | ### `CollectRewardByDepositId` 33 | Claims earned project tokens. 34 | 35 | ### `CollectDepositGns` 36 | Withdraws GNS after lock period. 37 | 38 | ### `TransferLeftFromProjectByAdmin` 39 | Refunds unclaimed rewards to project. 40 | 41 | ## Usage 42 | 43 | ```go 44 | // Create project 45 | projectId := CreateProject( 46 | name, tokenPath, recipient, amount, 47 | conditionTokens, conditionAmounts, 48 | tier30Ratio, tier90Ratio, tier180Ratio, 49 | startTime 50 | ) 51 | 52 | // Stake GNS 53 | depositId := DepositGns(projectTierId, amount, referrer) 54 | 55 | // Collect rewards 56 | CollectRewardByDepositId(depositId) 57 | 58 | // Withdraw after lock period 59 | CollectDepositGns(depositId) 60 | ``` 61 | 62 | ## Security 63 | 64 | - GNS locked until tier period ends 65 | - Automatic governance delegation 66 | - Conditional requirements prevent abuse -------------------------------------------------------------------------------- /contract/r/gnoswap/v1/launchpad/api_deposit.gno: -------------------------------------------------------------------------------- 1 | package launchpad 2 | 3 | // ApiGetDepositByDepositId retrieves deposit information by deposit ID. 4 | func ApiGetDepositByDepositId(depositId string) string { 5 | deposit, exist := deposits.Get(depositId) 6 | if !exist { 7 | return "" 8 | } 9 | 10 | builder := metaBuilder() 11 | d, ok := deposit.(*Deposit) 12 | if !ok { 13 | panic("failed to cast deposit to *Deposit") 14 | } 15 | depositBuilder(builder, d) 16 | 17 | return marshal(builder.Node()) 18 | } 19 | -------------------------------------------------------------------------------- /contract/r/gnoswap/v1/launchpad/api_reward.gno: -------------------------------------------------------------------------------- 1 | package launchpad 2 | 3 | import gs "gno.land/r/gnoswap/v1/gov/staker" 4 | 5 | // ApiGetProjectRecipientRewardByProjectId retrieves the claimable reward for a project recipient by project ID. 6 | func ApiGetProjectRecipientRewardByProjectId(projectId string) string { 7 | project, exist := projects.Get(projectId) 8 | if !exist { 9 | return "0" 10 | } 11 | 12 | return gs.GetClaimableRewardByAddress(project.(*Project).recipient) 13 | } 14 | 15 | // ApiGetProjectRecipientRewardByAddress retrieves the claimable reward for a recipient by address. 16 | func ApiGetProjectRecipientRewardByAddress(address address) string { 17 | if !address.IsValid() { 18 | return "0" 19 | } 20 | 21 | return gs.GetClaimableRewardByAddress(address) 22 | } 23 | -------------------------------------------------------------------------------- /contract/r/gnoswap/v1/launchpad/assert.gno: -------------------------------------------------------------------------------- 1 | package launchpad 2 | 3 | import "gno.land/p/nt/ufmt" 4 | 5 | // assertIsDepositOwner asserts that the caller is the owner of the deposit. 6 | // Panics if the caller is not the owner of the deposit. 7 | func assertIsDepositOwner(depositID string, caller address) { 8 | deposit, err := getDeposit(depositID) 9 | if err != nil { 10 | panic(err.Error()) 11 | } 12 | 13 | if !deposit.IsOwner(caller) { 14 | panic(makeErrorWithDetails(errInvalidOwner, ufmt.Sprintf("(%s)", caller.String())).Error()) 15 | } 16 | } 17 | 18 | // assertIsValidAmount panics if the amount is zero. 19 | func assertIsValidAmount(amount int64) { 20 | if amount < minimumDepositAmount { 21 | panic(makeErrorWithDetails( 22 | errInvalidAmount, 23 | ufmt.Sprintf("amount(%d) should greater than minimum deposit amount(%d)", amount, minimumDepositAmount), 24 | )) 25 | } 26 | 27 | if (amount % minimumDepositAmount) != 0 { 28 | panic(makeErrorWithDetails( 29 | errInvalidAmount, 30 | ufmt.Sprintf("amount(%d) must be a multiple of 1_000_000", amount), 31 | )) 32 | } 33 | } 34 | 35 | // assertHasProject asserts that the caller is the owner of at least one project. 36 | // Panics if the caller is not the owner of any project. 37 | func assertHasProject(caller address) { 38 | hasProject := false 39 | 40 | projects.Iterate("", "", func(key string, value interface{}) bool { 41 | project, ok := value.(*Project) 42 | if !ok { 43 | panic(ufmt.Sprintf("failed to cast projects's element to *Project: %T", value)) 44 | } 45 | 46 | hasProject = project.IsOwner(caller) 47 | 48 | // if true, break the loop 49 | return hasProject 50 | }) 51 | 52 | if !hasProject { 53 | panic(makeErrorWithDetails( 54 | errInvalidOwner, 55 | ufmt.Sprintf("caller %s is not the owner of any project", caller.String()), 56 | )) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /contract/r/gnoswap/v1/launchpad/consts.gno: -------------------------------------------------------------------------------- 1 | package launchpad 2 | 3 | import ( 4 | u256 "gno.land/p/gnoswap/uint256" 5 | ) 6 | 7 | const ( 8 | projectTier30 = int64(30) 9 | projectTier90 = int64(90) 10 | projectTier180 = int64(180) 11 | 12 | dayTime = int64(24 * 60 * 60) // 86400 13 | 14 | minimumDepositAmount = int64(1_000_000) 15 | 16 | stringSplitterPad = "*PAD*" 17 | 18 | projectMinimumStartDelayTime = int64(60 * 60) // 1 hour 19 | ) 20 | 21 | // contract paths 22 | const ( 23 | GOV_XGNS_PATH string = "gno.land/r/gnoswap/v1/gov/xgns" 24 | ) 25 | 26 | var projectTierDurations = []int64{ 27 | projectTier30, 28 | projectTier90, 29 | projectTier180, 30 | } 31 | 32 | var projectTierDurationTimes = map[int64]int64{ 33 | projectTier30: dayTime * projectTier30, // 30 days 34 | projectTier90: dayTime * projectTier90, // 90 days 35 | projectTier180: dayTime * projectTier180, // 180 days 36 | } 37 | 38 | var projectTierRewardCollectableDuration = map[int64]int64{ 39 | projectTier30: dayTime * 3, // 3 days 40 | projectTier90: dayTime * 7, // 7 days 41 | projectTier180: dayTime * 14, // 14 days 42 | } 43 | 44 | var q128 = u256.MustFromDecimal("340282366920938463463374607431768211456") 45 | -------------------------------------------------------------------------------- /contract/r/gnoswap/v1/launchpad/counter.gno: -------------------------------------------------------------------------------- 1 | package launchpad 2 | 3 | // Counter manages unique incrementing IDs. 4 | type Counter struct { 5 | id int64 6 | } 7 | 8 | // NewCounter creates a new Counter starting at 0. 9 | func NewCounter() *Counter { 10 | return &Counter{ 11 | id: 0, 12 | } 13 | } 14 | 15 | // next increments the counter and returns the next ID. 16 | func (c *Counter) next() int64 { 17 | c.id++ 18 | 19 | return c.id 20 | } 21 | 22 | // Get returns the current ID without incrementing. 23 | func (c *Counter) Get() int64 { 24 | return c.id 25 | } 26 | -------------------------------------------------------------------------------- /contract/r/gnoswap/v1/launchpad/counter_test.gno: -------------------------------------------------------------------------------- 1 | package launchpad 2 | 3 | import ( 4 | "testing" 5 | 6 | "gno.land/p/nt/uassert" 7 | ) 8 | 9 | // Test for NewCounter, next, and Get methods. 10 | func TestCounter_Behavior(t *testing.T) { 11 | tests := []struct { 12 | name string 13 | initValue int64 14 | callNextTimes int 15 | expectedId int64 16 | }{ 17 | { 18 | name: "Initial value is zero", 19 | initValue: 0, 20 | callNextTimes: 0, 21 | expectedId: 0, 22 | }, 23 | { 24 | name: "Call next once", 25 | initValue: 0, 26 | callNextTimes: 1, 27 | expectedId: 1, 28 | }, 29 | { 30 | name: "Call next multiple times", 31 | initValue: 0, 32 | callNextTimes: 5, 33 | expectedId: 5, 34 | }, 35 | { 36 | name: "Start from non-zero, call next", 37 | initValue: 10, 38 | callNextTimes: 3, 39 | expectedId: 13, 40 | }, 41 | } 42 | 43 | for _, tc := range tests { 44 | t.Run(tc.name, func(t *testing.T) { 45 | // given: create counter and set initial value if needed 46 | c := NewCounter() 47 | if tc.initValue > 0 { 48 | c.id = tc.initValue 49 | } 50 | 51 | // when: call next() as many times as needed 52 | for i := 0; i < tc.callNextTimes; i++ { 53 | c.next() 54 | } 55 | 56 | // then: check id value 57 | uassert.Equal(t, c.Get(), tc.expectedId) 58 | }) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /contract/r/gnoswap/v1/launchpad/gnomod.toml: -------------------------------------------------------------------------------- 1 | module = "gno.land/r/gnoswap/v1/launchpad" 2 | gno = "0.9" 3 | -------------------------------------------------------------------------------- /contract/r/gnoswap/v1/launchpad/launchpad_protocol_fee.gno: -------------------------------------------------------------------------------- 1 | package launchpad 2 | 3 | import ( 4 | "chain/runtime" 5 | 6 | "gno.land/r/gnoswap/access" 7 | "gno.land/r/gnoswap/halt" 8 | gov_staker "gno.land/r/gnoswap/v1/gov/staker" 9 | ) 10 | 11 | // CollectProtocolFee collects protocol fee from gov/staker for project recipient wallets. 12 | // Only users can call this function. 13 | func CollectProtocolFee(cur realm) { 14 | halt.AssertIsNotHaltedLaunchpadWithVersion(1) 15 | halt.AssertIsNotHaltedWithdrawWithVersion(1) 16 | 17 | previousRealm := runtime.PreviousRealm() 18 | access.AssertIsUser(previousRealm) 19 | 20 | caller := previousRealm.Address() 21 | assertHasProject(caller) 22 | 23 | gov_staker.CollectRewardFromLaunchPad(cross, caller) 24 | } 25 | -------------------------------------------------------------------------------- /contract/r/gnoswap/v1/launchpad/launchpad_reward.gno: -------------------------------------------------------------------------------- 1 | package launchpad 2 | 3 | import ( 4 | "chain" 5 | "chain/runtime" 6 | "time" 7 | 8 | "gno.land/r/gnoswap/access" 9 | "gno.land/r/gnoswap/halt" 10 | ) 11 | 12 | // CollectRewardByDepositId collects reward from a specific deposit. 13 | // 14 | // Parameters: 15 | // - depositID: ID of the deposit to collect from 16 | // 17 | // Returns amount of reward collected. 18 | // Only callable by deposit owner. 19 | func CollectRewardByDepositId(cur realm, depositID string) int64 { 20 | halt.AssertIsNotHaltedLaunchpadWithVersion(1) 21 | halt.AssertIsNotHaltedWithdrawWithVersion(1) 22 | 23 | previousRealm := runtime.PreviousRealm() 24 | access.AssertIsUser(previousRealm) 25 | 26 | caller := previousRealm.Address() 27 | assertIsDepositOwner(depositID, caller) 28 | 29 | deposit := mustGetDeposit(depositID) 30 | currentHeight := runtime.ChainHeight() 31 | currentTime := time.Now().Unix() 32 | rewardAmount, err := collectDepositReward(deposit, currentHeight, currentTime) 33 | if err != nil { 34 | panic(err) 35 | } 36 | 37 | chain.Emit( 38 | "CollectRewardByDepositId", 39 | "prevAddr", previousRealm.Address().String(), 40 | "prevRealm", previousRealm.PkgPath(), 41 | "depositId", depositID, 42 | "amount", formatInt(rewardAmount), 43 | ) 44 | 45 | return rewardAmount 46 | } 47 | 48 | // collectDepositReward calculates and collects the reward for a deposit. 49 | func collectDepositReward(deposit *Deposit, currentHeight, currentTime int64) (int64, error) { 50 | if currentTime <= 0 { 51 | return 0, makeErrorWithDetails(errInvalidTime, "currentTime must be positive") 52 | } 53 | 54 | // Get project tier and reward manager 55 | projectTier, err := getProjectTier(deposit.ProjectID(), deposit.Tier()) 56 | if err != nil { 57 | return 0, err 58 | } 59 | 60 | rewardManager, err := getProjectTierRewardManager(projectTier.ID()) 61 | if err != nil { 62 | return 0, err 63 | } 64 | 65 | // Update reward state before collection 66 | err = rewardManager.updateRewardPerDepositX128(projectTier.CurrentDepositAmount(), currentHeight, currentTime) 67 | if err != nil { 68 | return 0, err 69 | } 70 | 71 | // Collect reward 72 | rewardAmount, err := rewardManager.collectReward(deposit.ID(), currentTime) 73 | if err != nil { 74 | return 0, err 75 | } 76 | 77 | return rewardAmount, nil 78 | } 79 | -------------------------------------------------------------------------------- /contract/r/gnoswap/v1/launchpad/utils.gno: -------------------------------------------------------------------------------- 1 | package launchpad 2 | 3 | import ( 4 | "strconv" 5 | "strings" 6 | 7 | "gno.land/p/nt/ufmt" 8 | 9 | u256 "gno.land/p/gnoswap/uint256" 10 | ) 11 | 12 | // formatInt returns the string representation of the int64 value. 13 | func formatInt(value int64) string { 14 | return strconv.FormatInt(value, 10) 15 | } 16 | 17 | // parseProjectTierID parses a project tier ID into its project ID and duration. 18 | // Returns the project ID {tokenPath}:{createdHeight} and the duration of the project tier (30, 90, 180). 19 | func parseProjectTierID(projectTierID string) (string, int64) { 20 | parts := strings.Split(projectTierID, ":") 21 | if len(parts) != 3 { 22 | panic(makeErrorWithDetails( 23 | errInvalidData, 24 | ufmt.Sprintf("(%s)", projectTierID), 25 | )) 26 | } 27 | 28 | projectID := ufmt.Sprintf("%s:%s", parts[0], parts[1]) 29 | 30 | tierDuration, err := strconv.ParseInt(parts[2], 10, 64) 31 | if err != nil { 32 | panic(makeErrorWithDetails( 33 | errInvalidData, 34 | ufmt.Sprintf("(%s)", projectTierID), 35 | )) 36 | } 37 | 38 | // Validate tier duration 39 | if tierDuration != projectTier30 && tierDuration != projectTier90 && tierDuration != projectTier180 { 40 | panic(makeErrorWithDetails( 41 | errInvalidTier, 42 | ufmt.Sprintf("pool type(%d) is not available", tierDuration), 43 | )) 44 | } 45 | 46 | return projectID, tierDuration 47 | } 48 | 49 | // safeConvertToInt64 safely converts a *u256.Uint value to an int64, ensuring no overflow. 50 | // 51 | // This function attempts to convert the given *u256.Uint value to an int64. If the value exceeds 52 | // the maximum allowable range for int64 (`2^63 - 1`), it triggers a panic with a descriptive error message. 53 | // 54 | // Parameters: 55 | // - value (*u256.Uint): The unsigned 256-bit integer to be converted. 56 | // 57 | // Returns: 58 | // - int64: The converted value if it falls within the int64 range. 59 | // 60 | // Panics: 61 | // - If the `value` exceeds the range of int64, the function will panic with an error indicating 62 | // the overflow and the original value. 63 | func safeConvertToInt64(value *u256.Uint) int64 { 64 | const INT64_MAX = 9223372036854775807 65 | const MAX_INT64 = "9223372036854775807" 66 | 67 | res, overflow := value.Uint64WithOverflow() 68 | if overflow || res > uint64(INT64_MAX) { 69 | panic(ufmt.Sprintf( 70 | "amount(%s) overflows int64 range (max %s)", 71 | value.ToString(), 72 | MAX_INT64, 73 | )) 74 | } 75 | return int64(res) 76 | } 77 | -------------------------------------------------------------------------------- /contract/r/gnoswap/v1/pool/api_test.gno: -------------------------------------------------------------------------------- 1 | package pool 2 | 3 | import ( 4 | "testing" 5 | 6 | "gno.land/p/nt/uassert" 7 | ) 8 | 9 | func TestMakeStatNode(t *testing.T) { 10 | t.Run("default block height and timestamp", func(t *testing.T) { 11 | statNode := newStatNode().JSON() 12 | 13 | statHeight, _ := statNode.GetKey("height") 14 | uassert.Equal(t, statHeight.String(), "123") 15 | 16 | statTimestamp, _ := statNode.GetKey("timestamp") 17 | uassert.Equal(t, statTimestamp.String(), "1234567890") 18 | }) 19 | 20 | t.Run("increase block height and timestamp", func(t *testing.T) { 21 | testing.SkipHeights(1) 22 | 23 | statNode := newStatNode().JSON() 24 | 25 | statHeight, _ := statNode.GetKey("height") 26 | uassert.Equal(t, statHeight.String(), "124") 27 | }) 28 | } 29 | -------------------------------------------------------------------------------- /contract/r/gnoswap/v1/pool/doc.gno: -------------------------------------------------------------------------------- 1 | // Package pool implements GnoSwap's concentrated liquidity pools based on Uniswap V3. 2 | // It manages liquidity positions, executes swaps, and maintains pool state including 3 | // price, liquidity, and fee calculations. 4 | // 5 | // The pool contract is the core of the GnoSwap AMM, supporting: 6 | // - Concentrated liquidity within custom price ranges 7 | // - Multiple fee tiers (0.01%, 0.05%, 0.3%, 1%) 8 | // - Single-tick and cross-tick swaps 9 | // - Protocol fee collection 10 | // - Tick bitmap optimization for gas efficiency 11 | package pool 12 | -------------------------------------------------------------------------------- /contract/r/gnoswap/v1/pool/gnomod.toml: -------------------------------------------------------------------------------- 1 | module = "gno.land/r/gnoswap/v1/pool" 2 | gno = "0.9" 3 | -------------------------------------------------------------------------------- /contract/r/gnoswap/v1/pool/liquidity_math.gno: -------------------------------------------------------------------------------- 1 | package pool 2 | 3 | import ( 4 | i256 "gno.land/p/gnoswap/int256" 5 | u256 "gno.land/p/gnoswap/uint256" 6 | 7 | "gno.land/p/nt/ufmt" 8 | ) 9 | 10 | // liquidityMathAddDelta calculates the new liquidity by applying the delta liquidity to the current liquidity. 11 | // If delta liquidity is negative, it subtracts the absolute value of delta liquidity from the current liquidity. 12 | // If delta liquidity is positive, it adds the absolute value of delta liquidity to the current liquidity. 13 | // Returns the new liquidity as a uint256 value. 14 | func liquidityMathAddDelta(x *u256.Uint, y *i256.Int) *u256.Uint { 15 | if x == nil || y == nil { 16 | panic(newErrorWithDetail( 17 | errInvalidInput, 18 | "x or y is nil", 19 | )) 20 | } 21 | 22 | absDelta := y.Abs() 23 | 24 | // Subtract or add based on the sign of y 25 | if y.IsNeg() { 26 | z := u256.Zero().Sub(x, absDelta) 27 | if z.Gte(x) { 28 | panic(newErrorWithDetail( 29 | errLiquidityCalculation, 30 | ufmt.Sprintf("Condition failed: (z must be < x) (x: %s, y: %s, z:%s)", x.ToString(), y.ToString(), z.ToString()), 31 | )) 32 | } 33 | return z 34 | } 35 | z := u256.Zero().Add(x, absDelta) 36 | if z.Lt(x) { 37 | panic(newErrorWithDetail( 38 | errLiquidityCalculation, 39 | ufmt.Sprintf("Condition failed: (z must be >= x) (x: %s, y: %s, z:%s)", x.ToString(), y.ToString(), z.ToString()), 40 | )) 41 | } 42 | return z 43 | } 44 | -------------------------------------------------------------------------------- /contract/r/gnoswap/v1/pool/liquidity_math_test.gno: -------------------------------------------------------------------------------- 1 | package pool 2 | 3 | import ( 4 | "testing" 5 | 6 | "gno.land/p/nt/ufmt" 7 | 8 | i256 "gno.land/p/gnoswap/int256" 9 | u256 "gno.land/p/gnoswap/uint256" 10 | ) 11 | 12 | func TestLiquidityMathAddDelta(t *testing.T) { 13 | tests := []struct { 14 | name string 15 | fn func() 16 | wantPanic string 17 | }{ 18 | { 19 | name: "x is nil", 20 | fn: func() { 21 | var y *i256.Int 22 | y = i256.MustFromDecimal("100") 23 | liquidityMathAddDelta(nil, y) 24 | }, 25 | wantPanic: newErrorWithDetail(errInvalidInput, "x or y is nil"), 26 | }, 27 | { 28 | name: "y is nil", 29 | fn: func() { 30 | var x *u256.Uint 31 | x = u256.MustFromDecimal("100") 32 | liquidityMathAddDelta(x, nil) 33 | }, 34 | wantPanic: newErrorWithDetail(errInvalidInput, "x or y is nil"), 35 | }, 36 | { 37 | name: "underflow panic with sub delta", 38 | fn: func() { 39 | x := u256.NewUint(0) 40 | y := i256.MustFromDecimal("-100") 41 | liquidityMathAddDelta(x, y) 42 | }, 43 | wantPanic: newErrorWithDetail( 44 | errLiquidityCalculation, 45 | ufmt.Sprintf("Condition failed: (z must be < x) (x: 0, y: -100, z:115792089237316195423570985008687907853269984665640564039457584007913129639836)")), 46 | }, 47 | { 48 | name: "overflow panic with add delta", 49 | fn: func() { 50 | x := u256.MustFromDecimal("115792089237316195423570985008687907853269984665640564039457584007913129639935") // 2^256 - 1 51 | y := i256.MustFromDecimal("100") 52 | liquidityMathAddDelta(x, y) 53 | }, 54 | wantPanic: newErrorWithDetail( 55 | errLiquidityCalculation, 56 | ufmt.Sprintf("Condition failed: (z must be >= x) (x: 115792089237316195423570985008687907853269984665640564039457584007913129639935, y: 100, z:99)")), 57 | }, 58 | } 59 | 60 | for _, tt := range tests { 61 | t.Run(tt.name, func(t *testing.T) { 62 | defer func() { 63 | r := recover() 64 | if r == nil { 65 | t.Errorf("%s: expected panic but fot none", tt.name) 66 | return 67 | } 68 | if r.(string) != tt.wantPanic { 69 | t.Errorf("%s: got panic %v, want %v", tt.name, r, tt.wantPanic) 70 | } 71 | }() 72 | 73 | tt.fn() 74 | }) 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /contract/r/gnoswap/v1/pool/testutils.gno: -------------------------------------------------------------------------------- 1 | package pool 2 | 3 | import ( 4 | "testing" 5 | 6 | "gno.land/p/nt/avl" 7 | ) 8 | 9 | func InitPoolTest(t *testing.T) { 10 | t.Helper() 11 | 12 | func(cur realm) { 13 | pools = avl.NewTree() 14 | slot0FeeProtocol = 0 15 | poolCreationFee = 100_000_000 16 | withdrawalFeeBPS = 100 17 | }(cross) 18 | } 19 | -------------------------------------------------------------------------------- /contract/r/gnoswap/v1/position/doc.gno: -------------------------------------------------------------------------------- 1 | // Package position manages liquidity positions as NFTs in GnoSwap pools. 2 | // It provides functionality for minting, burning, and managing concentrated liquidity positions, 3 | // handling position ownership through GNFT tokens, and managing wrapped/unwrapped native tokens. 4 | // Each position represents liquidity provided within a specific price range in a pool. 5 | package position 6 | -------------------------------------------------------------------------------- /contract/r/gnoswap/v1/position/errors.gno: -------------------------------------------------------------------------------- 1 | package position 2 | 3 | import ( 4 | "errors" 5 | 6 | "gno.land/p/nt/ufmt" 7 | ) 8 | 9 | var ( 10 | errNoPermission = errors.New("[GNOSWAP-POSITION-001] caller has no permission") 11 | errSlippage = errors.New("[GNOSWAP-POSITION-002] slippage failed") 12 | errWrapUnwrap = errors.New("[GNOSWAP-POSITION-003] wrap, unwrap failed") 13 | errZeroWrappedAmount = errors.New("[GNOSWAP-POSITION-004] zero wrapped amount") 14 | errInvalidInput = errors.New("[GNOSWAP-POSITION-005] invalid input data") 15 | errDataNotFound = errors.New("[GNOSWAP-POSITION-006] requested data not found") 16 | errExpired = errors.New("[GNOSWAP-POSITION-007] transaction expired") 17 | errWugnotMinimum = errors.New("[GNOSWAP-POSITION-008] can not wrap less than minimum amount") 18 | errNotClear = errors.New("[GNOSWAP-POSITION-009] position is not clear") 19 | errZeroLiquidity = errors.New("[GNOSWAP-POSITION-010] zero liquidity") 20 | errPositionExist = errors.New("[GNOSWAP-POSITION-011] position with same positionId already exists") 21 | errInvalidAddress = errors.New("[GNOSWAP-POSITION-012] invalid address") 22 | errPositionDoesNotExist = errors.New("[GNOSWAP-POSITION-013] position does not exist") 23 | errZeroUGNOT = errors.New("[GNOSWAP-POSITION-014] No UGNOTs were sent") 24 | errInsufficientUGNOT = errors.New("[GNOSWAP-POSITION-015] Insufficient UGNOT provided") 25 | errInvalidTokenPath = errors.New("[GNOSWAP-POSITION-016] invalid token address") 26 | errInvalidLiquidityRatio = errors.New("[GNOSWAP-POSITION-017] invalid liquidity ratio") 27 | errUnderflow = errors.New("[GNOSWAP-POSITION-018] underflow") 28 | errOverflow = errors.New("[GNOSWAP-POSITION-019] overflow") 29 | errInvalidLiquidity = errors.New("[GNOSWAP-POSITION-019] invalid liquidity") 30 | ) 31 | 32 | // newErrorWithDetail appends additional context or details to an existing error message. 33 | func newErrorWithDetail(err error, detail string) string { 34 | return ufmt.Errorf("%s || %s", err.Error(), detail).Error() 35 | } 36 | 37 | // makeErrorWithDetails creates an error with additional context. 38 | func makeErrorWithDetails(err error, details string) error { 39 | return ufmt.Errorf("%s || %s", err.Error(), details) 40 | } 41 | -------------------------------------------------------------------------------- /contract/r/gnoswap/v1/position/gnomod.toml: -------------------------------------------------------------------------------- 1 | module = "gno.land/r/gnoswap/v1/position" 2 | gno = "0.9" 3 | -------------------------------------------------------------------------------- /contract/r/gnoswap/v1/position/liquidity_management.gno: -------------------------------------------------------------------------------- 1 | package position 2 | 3 | import ( 4 | u256 "gno.land/p/gnoswap/uint256" 5 | "gno.land/p/nt/ufmt" 6 | 7 | "gno.land/r/gnoswap/v1/common" 8 | pl "gno.land/r/gnoswap/v1/pool" 9 | ) 10 | 11 | type AddLiquidityParams struct { 12 | poolKey string // poolPath of the pool which has the position 13 | tickLower int32 // lower end of the tick range for the position 14 | tickUpper int32 // upper end of the tick range for the position 15 | amount0Desired *u256.Uint // desired amount of token0 to be minted 16 | amount1Desired *u256.Uint // desired amount of token1 to be minted 17 | amount0Min *u256.Uint // minimum amount of token0 to be minted 18 | amount1Min *u256.Uint // minimum amount of token1 to be minted 19 | caller address // address to call the function 20 | } 21 | 22 | // addLiquidity calculates liquidity amounts and mints position tokens to a pool. 23 | func addLiquidity(params AddLiquidityParams) (*u256.Uint, *u256.Uint, *u256.Uint) { 24 | sqrtPriceX96 := pl.GetSlot0SqrtPriceX96(params.poolKey) 25 | sqrtRatioAX96 := common.TickMathGetSqrtRatioAtTick(params.tickLower) 26 | sqrtRatioBX96 := common.TickMathGetSqrtRatioAtTick(params.tickUpper) 27 | 28 | liquidity := common.GetLiquidityForAmounts( 29 | sqrtPriceX96, 30 | sqrtRatioAX96, 31 | sqrtRatioBX96, 32 | params.amount0Desired, 33 | params.amount1Desired, 34 | ) 35 | 36 | token0, token1, fee := splitOf(params.poolKey) 37 | amount0Str, amount1Str := pl.Mint( 38 | cross, 39 | token0, 40 | token1, 41 | fee, 42 | params.tickLower, 43 | params.tickUpper, 44 | liquidity.ToString(), 45 | params.caller, 46 | ) 47 | 48 | amount0 := u256.MustFromDecimal(amount0Str) 49 | amount1 := u256.MustFromDecimal(amount1Str) 50 | 51 | amount0Cond := amount0.Gte(params.amount0Min) 52 | amount1Cond := amount1.Gte(params.amount1Min) 53 | 54 | if !(amount0Cond && amount1Cond) { 55 | panic(newErrorWithDetail( 56 | errSlippage, 57 | ufmt.Sprintf( 58 | "Price Slippage Check(amount0(%s) >= amount0Min(%s), amount1(%s) >= amount1Min(%s))", 59 | amount0Str, params.amount0Min.ToString(), amount1Str, params.amount1Min.ToString()), 60 | )) 61 | } 62 | 63 | return liquidity, amount0, amount1 64 | } 65 | -------------------------------------------------------------------------------- /contract/r/gnoswap/v1/position/testutils.gno: -------------------------------------------------------------------------------- 1 | package position 2 | 3 | import ( 4 | "testing" 5 | 6 | "gno.land/p/nt/avl" 7 | 8 | "gno.land/r/gnoswap/v1/gnft" 9 | "gno.land/r/gnoswap/v1/pool" 10 | ) 11 | 12 | func InitPositionTest(t *testing.T) { 13 | t.Helper() 14 | 15 | func(cur realm) { 16 | positions = avl.NewTree() 17 | nextId = 1 18 | }(cross) 19 | } 20 | 21 | func initPositionTest(t *testing.T) { 22 | t.Helper() 23 | 24 | InitPositionTest(t) 25 | pool.InitPoolTest(t) 26 | gnft.InitGNFTTest(t) 27 | } 28 | -------------------------------------------------------------------------------- /contract/r/gnoswap/v1/protocol_fee/README.md: -------------------------------------------------------------------------------- 1 | # Protocol Fee 2 | 3 | Fee collection and distribution for protocol operations. 4 | 5 | ## Overview 6 | 7 | Protocol Fee contract collects fees from various protocol operations and distributes them to xGNS holders and DevOps. 8 | 9 | ## Configuration 10 | 11 | - **Router Fee**: 0.15% of swap amount 12 | - **Pool Creation Fee**: 100 GNS 13 | - **Withdrawal Fee**: 1% of LP fees claimed 14 | - **Unstaking Fee**: 1% of staking rewards 15 | - **Distribution**: 100% to xGNS holders (default) 16 | 17 | ## Fee Sources 18 | 19 | 1. **Swaps**: 0.15% fee on all trades 20 | 2. **Pool Creation**: 100 GNS per new pool 21 | 3. **LP Withdrawals**: 1% of collected fees 22 | 4. **Staking Claims**: 1% of rewards 23 | 24 | ## Key Functions 25 | 26 | ### `DistributeProtocolFee` 27 | Distributes accumulated fees to recipients. 28 | 29 | ### `SetDevOpsPct` 30 | Sets DevOps funding percentage. 31 | 32 | ### `SetGovStakerPct` 33 | Sets xGNS holder percentage. 34 | 35 | ### `AddToProtocolFee` 36 | Adds fees to distribution queue. 37 | 38 | ## Usage 39 | 40 | ```go 41 | // Distribute accumulated fees 42 | tokenAmounts := DistributeProtocolFee() 43 | 44 | // Configure distribution 45 | SetDevOpsPct(2000) // 20% to DevOps 46 | SetGovStakerPct(8000) // 80% to xGNS holders 47 | 48 | // View accumulated fees 49 | GetProtocolFee(tokenPath) 50 | ``` 51 | 52 | ## Security 53 | 54 | - Admin-only configuration changes 55 | - Automatic fee accumulation 56 | - Multi-token support 57 | - Transparent distribution tracking -------------------------------------------------------------------------------- /contract/r/gnoswap/v1/protocol_fee/assert.gno: -------------------------------------------------------------------------------- 1 | package protocol_fee 2 | 3 | import ( 4 | prbac "gno.land/p/gnoswap/rbac" 5 | "gno.land/p/nt/ufmt" 6 | 7 | "gno.land/r/gnoswap/access" 8 | ) 9 | 10 | // assertIsPoolOrPositionOrRouterOrStaker panics if the caller is not the pool, router, or staker contract. 11 | func assertIsPoolOrPositionOrRouterOrStaker(caller address) { 12 | if access.IsAuthorizedWithVersion(prbac.ROLE_POOL.String(), 1, caller) || 13 | access.IsAuthorizedWithVersion(prbac.ROLE_POSITION.String(), 1, caller) || 14 | access.IsAuthorizedWithVersion(prbac.ROLE_ROUTER.String(), 1, caller) || 15 | access.IsAuthorizedWithVersion(prbac.ROLE_STAKER.String(), 1, caller) { 16 | return 17 | } 18 | 19 | panic(ufmt.Errorf("unauthorized: caller %s is not pool or position or router or staker", caller)) 20 | } 21 | 22 | // assertIsAdminOrGovStaker panics if the caller is not admin or gov/staker. 23 | func assertIsAdminOrGovStaker(caller address) { 24 | if access.IsAuthorized(prbac.ROLE_ADMIN.String(), caller) { 25 | return 26 | } 27 | 28 | if access.IsAuthorizedWithVersion(prbac.ROLE_GOV_STAKER.String(), 1, caller) { 29 | return 30 | } 31 | 32 | panic(ufmt.Errorf("unauthorized: caller %s is not admin or gov/staker", caller)) 33 | } 34 | 35 | // assertIsValidPercent panics if the percentage is invalid (not between 0-10000). 36 | func assertIsValidPercent(pct int64) { 37 | if pct > 10000 { 38 | panic(makeErrorWithDetail( 39 | errInvalidPct, 40 | ufmt.Sprintf("pct(%d) should not be bigger than 10000", pct), 41 | )) 42 | } 43 | 44 | if pct < 0 { 45 | panic(makeErrorWithDetail( 46 | errInvalidPct, 47 | ufmt.Sprintf("pct(%d) should not be smaller than 0", pct), 48 | )) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /contract/r/gnoswap/v1/protocol_fee/consts.gno: -------------------------------------------------------------------------------- 1 | package protocol_fee 2 | 3 | import ( 4 | prabc "gno.land/p/gnoswap/rbac" 5 | "gno.land/r/gnoswap/access" 6 | ) 7 | 8 | var ( 9 | protocolFeeAddr = access.MustGetAddressWithVersion(prabc.ROLE_PROTOCOL_FEE.String(), 1) 10 | govStakerAddr = access.MustGetAddressWithVersion(prabc.ROLE_GOV_STAKER.String(), 1) 11 | devOpsAddr = access.MustGetAddressWithVersion(prabc.ROLE_DEVOPS.String(), 1) 12 | ) 13 | -------------------------------------------------------------------------------- /contract/r/gnoswap/v1/protocol_fee/doc.gno: -------------------------------------------------------------------------------- 1 | // Package protocol_fee collects and distributes protocol fees from swaps. 2 | // 3 | // This contract accumulates fees generated from trading activity and 4 | // distributes them to devOps and governance stakers according to 5 | // configurable percentages set through governance. 6 | package protocol_fee 7 | -------------------------------------------------------------------------------- /contract/r/gnoswap/v1/protocol_fee/errors.gno: -------------------------------------------------------------------------------- 1 | package protocol_fee 2 | 3 | import ( 4 | "errors" 5 | 6 | "gno.land/p/nt/ufmt" 7 | ) 8 | 9 | var ( 10 | errNoPermission = errors.New("[GNOSWAP-PROTOCOL_FEE-001] caller has no permission") 11 | errInvalidPct = errors.New("[GNOSWAP-PROTOCOL_FEE-002] invalid percentage") 12 | errInvalidAmount = errors.New("[GNOSWAP-PROTOCOL_FEE-003] invalid amount") 13 | ) 14 | 15 | // makeErrorWithDetail creates an error with additional context. 16 | func makeErrorWithDetail(err error, detail string) error { 17 | return ufmt.Errorf("%s || %s", err.Error(), detail) 18 | } 19 | -------------------------------------------------------------------------------- /contract/r/gnoswap/v1/protocol_fee/gnomod.toml: -------------------------------------------------------------------------------- 1 | module = "gno.land/r/gnoswap/v1/protocol_fee" 2 | gno = "0.9" 3 | -------------------------------------------------------------------------------- /contract/r/gnoswap/v1/router/assert.gno: -------------------------------------------------------------------------------- 1 | package router 2 | 3 | import ( 4 | "strconv" 5 | "strings" 6 | "time" 7 | 8 | u256 "gno.land/p/gnoswap/uint256" 9 | "gno.land/p/nt/ufmt" 10 | "gno.land/r/gnoswap/v1/common" 11 | ) 12 | 13 | // assertIsNotExpired ensures the transaction deadline has not passed. 14 | func assertIsNotExpired(deadline int64) { 15 | now := time.Now().Unix() 16 | 17 | if now > deadline { 18 | panic(makeErrorWithDetails( 19 | errExpired, 20 | ufmt.Sprintf("transaction too old, now(%d) > deadline(%d)", now, deadline), 21 | )) 22 | } 23 | } 24 | 25 | // assertIsValidUserCoinSend asserts that the user has sent the correct amount of native coin. 26 | func assertIsValidUserCoinSend(tokenPath, amount string) { 27 | amountInt, err := strconv.ParseInt(amount, 10, 64) 28 | if err != nil { 29 | panic(err) 30 | } 31 | 32 | if common.IsGNOTNativePath(tokenPath) { 33 | common.AssertIsUserSendGNOTAmount(amountInt) 34 | } else { 35 | common.AssertIsNotHandleNativeCoin() 36 | } 37 | } 38 | 39 | func assertIsValidSqrtPriceLimitX96(sqrtPriceLimitX96 string) { 40 | if sqrtPriceLimitX96 == "" { 41 | panic(makeErrorWithDetails( 42 | errInvalidInput, 43 | ufmt.Sprintf("invalid sqrtPriceLimitX96: %s", sqrtPriceLimitX96), 44 | )) 45 | } 46 | 47 | _, err := u256.FromDecimal(sqrtPriceLimitX96) 48 | if err != nil { 49 | panic(makeErrorWithDetails( 50 | errInvalidInput, 51 | ufmt.Sprintf("invalid sqrtPriceLimitX96: %s", sqrtPriceLimitX96), 52 | )) 53 | } 54 | } 55 | 56 | func assertIsValidSingleSwapRouteArrPath(routePaths, inputToken, outputToken string) { 57 | if routePaths == "" { 58 | panic(makeErrorWithDetails( 59 | errInvalidInput, 60 | ufmt.Sprintf("invalid route: %s", routePaths), 61 | )) 62 | } 63 | 64 | if strings.Count(routePaths, ",") > 0 { 65 | panic(makeErrorWithDetails( 66 | errInvalidInput, 67 | ufmt.Sprintf("invalid routePaths: %s", routePaths), 68 | )) 69 | } 70 | 71 | if strings.Count(routePaths, POOL_SEPARATOR) > 0 { 72 | panic(makeErrorWithDetails( 73 | errInvalidInput, 74 | ufmt.Sprintf("invalid routePaths: %s", routePaths), 75 | )) 76 | } 77 | 78 | assertIsValidRoutePaths(routePaths, inputToken, outputToken) 79 | } 80 | 81 | func assertIsValidRoutePaths(routePaths, inputToken, outputToken string) { 82 | err := validateRoutePaths(routePaths, inputToken, outputToken) 83 | if err != nil { 84 | panic(err) 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /contract/r/gnoswap/v1/router/consts.gno: -------------------------------------------------------------------------------- 1 | package router 2 | 3 | import ( 4 | prabc "gno.land/p/gnoswap/rbac" 5 | 6 | "gno.land/r/gnoswap/access" 7 | ) 8 | 9 | const ( 10 | maxApprove = int64(9223372036854775807) 11 | 12 | zeroString = "0" 13 | maxPercentageString = "100" 14 | ) 15 | 16 | var ( 17 | poolAddr = access.MustGetAddressWithVersion(prabc.ROLE_POOL.String(), 1) 18 | routerAddr = access.MustGetAddressWithVersion(prabc.ROLE_ROUTER.String(), 1) 19 | positionAddr = access.MustGetAddressWithVersion(prabc.ROLE_POSITION.String(), 1) 20 | protocolFeeAddr = access.MustGetAddressWithVersion(prabc.ROLE_PROTOCOL_FEE.String(), 1) 21 | ) 22 | -------------------------------------------------------------------------------- /contract/r/gnoswap/v1/router/doc.gno: -------------------------------------------------------------------------------- 1 | // Package router handles token swaps through GnoSwap liquidity pools. 2 | // 3 | // The router provides user-facing swap functions with slippage protection, 4 | // multi-hop routing, and automatic GNOT wrapping/unwrapping. It supports 5 | // both exact input and exact output swap modes. 6 | // 7 | // All swap functions include deadline checks and minimum output validation 8 | // to protect users from unfavorable price movements. 9 | package router 10 | -------------------------------------------------------------------------------- /contract/r/gnoswap/v1/router/errors.gno: -------------------------------------------------------------------------------- 1 | package router 2 | 3 | import ( 4 | "errors" 5 | 6 | "gno.land/p/nt/ufmt" 7 | ) 8 | 9 | var ( 10 | errNoPermission = errors.New("[GNOSWAP-ROUTER-001] caller has no permission") 11 | errSlippage = errors.New("[GNOSWAP-ROUTER-002] slippage check failed") 12 | errInvalidRoutesAndQuotes = errors.New("[GNOSWAP-ROUTER-003] invalid routes and quotes") 13 | errExpired = errors.New("[GNOSWAP-ROUTER-004] transaction expired") 14 | errInvalidInput = errors.New("[GNOSWAP-ROUTER-005] invalid input data") 15 | errInvalidPoolFeeTier = errors.New("[GNOSWAP-ROUTER-006] invalid pool fee tier") 16 | errInvalidSwapFee = errors.New("[GNOSWAP-ROUTER-007] invalid swap fee") 17 | errInvalidSwapType = errors.New("[GNOSWAP-ROUTER-008] invalid swap type") 18 | errInvalidPoolPath = errors.New("[GNOSWAP-ROUTER-009] invalid pool path") 19 | errWrapUnwrap = errors.New("[GNOSWAP-ROUTER-010] wrap, unwrap failed") 20 | errWugnotMinimum = errors.New("[GNOSWAP-ROUTER-011] less than minimum amount ") 21 | errQuoteParser = errors.New("[GNOSWAP-ROUTER-012] quote parse failed") 22 | errHopsOutOfRange = errors.New("[GNOSWAP-ROUTER-013] number of hops must be 1~3") 23 | errSameTokenSwap = errors.New("[GNOSWAP-ROUTER-014] cannot swap same token") 24 | errProtocolFeeOverflow = errors.New("[GNOSWAP-ROUTER-015] protocol fee calculation overflow") 25 | errOverFlow = errors.New("[GNOSWAP-ROUTER-016] overflow") 26 | errInvalidRoutePath = errors.New("[GNOSWAP-ROUTER-017] invalid route path") 27 | errInvalidRouteFirstToken = errors.New("[GNOSWAP-ROUTER-018] invalid route first token") 28 | errInvalidRouteLastToken = errors.New("[GNOSWAP-ROUTER-019] invalid route last token") 29 | errUnderFlowDeductRouterFee = errors.New("[GNOSWAP-ROUTER-020] underflow in deduct router fee") 30 | ) 31 | 32 | // addDetailToError adds detail to an error message. 33 | func addDetailToError(err error, detail string) string { 34 | finalErr := ufmt.Errorf("%s || %s", err.Error(), detail) 35 | return finalErr.Error() 36 | } 37 | 38 | // makeErrorWithDetails creates an error with additional context. 39 | func makeErrorWithDetails(err error, detail string) error { 40 | return ufmt.Errorf("%s || %s", err.Error(), detail) 41 | } 42 | -------------------------------------------------------------------------------- /contract/r/gnoswap/v1/router/gnomod.toml: -------------------------------------------------------------------------------- 1 | module = "gno.land/r/gnoswap/v1/router" 2 | gno = "0.9" 3 | -------------------------------------------------------------------------------- /contract/r/gnoswap/v1/router/protocol_fee_swap_test.gno: -------------------------------------------------------------------------------- 1 | package router 2 | 3 | import ( 4 | "testing" 5 | 6 | u256 "gno.land/p/gnoswap/uint256" 7 | "gno.land/r/onbloc/bar" 8 | ) 9 | 10 | func TestHandleSwapFee(t *testing.T) { 11 | tests := []struct { 12 | name string 13 | amount *u256.Uint 14 | swapFeeValue uint64 15 | expectedAmount *u256.Uint 16 | }{ 17 | { 18 | name: "zero swap fee", 19 | amount: u256.NewUint(1000), 20 | swapFeeValue: 0, 21 | expectedAmount: u256.NewUint(1000), 22 | }, 23 | { 24 | name: "normal swap fee calculation (0.15%)", 25 | amount: u256.NewUint(10000), 26 | swapFeeValue: 15, 27 | expectedAmount: u256.NewUint(9985), // 10000 - (10000 * 0.15%) 28 | }, 29 | { 30 | name: "Dry Run test", 31 | amount: u256.NewUint(10000), 32 | swapFeeValue: 15, 33 | expectedAmount: u256.NewUint(9985), 34 | }, 35 | { 36 | name: "large amount swap fee calculation", 37 | amount: u256.NewUint(1000000), 38 | swapFeeValue: 15, 39 | expectedAmount: u256.NewUint(998500), // 1000000 - (1000000 * 0.15%) 40 | }, 41 | } 42 | 43 | for _, tt := range tests { 44 | t.Run(tt.name, func(t *testing.T) { 45 | originalSwapFee := swapFee 46 | swapFee = tt.swapFeeValue 47 | defer func() { 48 | swapFee = originalSwapFee 49 | }() 50 | 51 | testing.SetRealm(testing.NewUserRealm(adminAddr)) 52 | bar.Transfer(cross, routerAddr, tt.amount.Int64()) 53 | 54 | testing.SetRealm(routerRealm) 55 | 56 | func(cur realm) { 57 | result := handleSwapFee(barPath, tt.amount) 58 | 59 | if !result.Eq(tt.expectedAmount) { 60 | t.Errorf("handleSwapFee() = %v, want %v", result, tt.expectedAmount) 61 | } 62 | }(cross) 63 | }) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /contract/r/gnoswap/v1/router/swap_callback.gno: -------------------------------------------------------------------------------- 1 | package router 2 | 3 | import ( 4 | i256 "gno.land/p/gnoswap/int256" 5 | u256 "gno.land/p/gnoswap/uint256" 6 | 7 | "gno.land/r/gnoswap/v1/common" 8 | ) 9 | 10 | // swapCallback implements the pool's SwapCallback interface. 11 | // This is called by the pool after it has sent output tokens to the recipient. 12 | // The router must transfer the required input tokens to the pool. 13 | // 14 | // This callback pattern enables: 15 | // 1. Flash swaps (receive tokens before paying) 16 | // 2. Just-in-time token transfers 17 | // 3. Complex multi-hop swaps without intermediate transfers 18 | func swapCallback( 19 | token0Path, token1Path string, 20 | amount0Delta, amount1Delta string, 21 | payer address, 22 | ) error { 23 | var tokenToPay string 24 | 25 | amountToPay := i256.Zero() 26 | 27 | amount0DeltaI256 := i256.MustFromDecimal(amount0Delta) 28 | amount1DeltaI256 := i256.MustFromDecimal(amount1Delta) 29 | 30 | // amount0Delta > 0 means pool wants token0 31 | // amount1Delta > 0 means pool wants token1 32 | if amount0DeltaI256.Gt(i256.Zero()) { 33 | amountToPay = amount0DeltaI256 34 | tokenToPay = token0Path 35 | // Token0 is needed - this will be determined from context 36 | } else if amount1DeltaI256.Gt(i256.Zero()) { 37 | amountToPay = amount1DeltaI256 38 | tokenToPay = token1Path 39 | } else { 40 | return nil 41 | } 42 | 43 | // Transfer tokens from router to pool 44 | // The router should already have the tokens from the user 45 | transferToPool(tokenToPay, amountToPay.Abs(), payer) 46 | 47 | return nil 48 | } 49 | 50 | // transferToPool transfers tokens from router to pool 51 | func transferToPool(token string, amount *u256.Uint, payer address) { 52 | isTransferByRouter := payer == routerAddr 53 | 54 | balance := common.BalanceOf(token, payer) 55 | 56 | if u256.NewUintFromInt64(balance).Lt(amount) { 57 | panic("insufficient balance in router for callback") 58 | } 59 | 60 | if isTransferByRouter { 61 | common.SafeGRC20Transfer(cross, token, poolAddr, amount.Int64()) 62 | } else { 63 | common.SafeGRC20TransferFrom(cross, token, payer, poolAddr, amount.Int64()) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /contract/r/gnoswap/v1/router/swap_single.gno: -------------------------------------------------------------------------------- 1 | package router 2 | 3 | import ( 4 | u256 "gno.land/p/gnoswap/uint256" 5 | "gno.land/r/gnoswap/v1/common" 6 | ) 7 | 8 | var zero = u256.Zero() 9 | 10 | // SwapExecutor defines the interface for executing swaps. 11 | type SwapExecutor interface { 12 | // execute performs the swap operation. 13 | execute(p *SingleSwapParams) (amountIn, amountOut *u256.Uint) 14 | } 15 | 16 | // executeSwap is the common logic for both real and dry swaps. 17 | func executeSwap(executor SwapExecutor, p *SingleSwapParams) (amountIn, amountOut *u256.Uint) { 18 | if p.tokenIn == p.tokenOut { 19 | panic(errSameTokenSwap) 20 | } 21 | 22 | common.MustRegistered(p.tokenIn, p.tokenOut) 23 | 24 | return executor.execute(p) 25 | } 26 | 27 | var ( 28 | _ SwapExecutor = (*RealSwapExecutor)(nil) 29 | _ SwapExecutor = (*DrySwapExecutor)(nil) 30 | ) 31 | 32 | // singleSwap executes a swap within a single pool using the provided parameters. 33 | // It processes a token swap within two assets using a specific fee tier and 34 | // automatically sets the recipient to the caller's address. 35 | func singleSwap(p *SingleSwapParams) (amountIn, amountOut *u256.Uint) { 36 | return executeSwap(&RealSwapExecutor{}, p) 37 | } 38 | 39 | // singleDrySwap simulates a single-token swap operation without executing it. 40 | // It performs a dry run simulation and does not alter the state. 41 | func singleDrySwap(p *SingleSwapParams) (amountIn, amountOut *u256.Uint) { 42 | return executeSwap(&DrySwapExecutor{}, p) 43 | } 44 | -------------------------------------------------------------------------------- /contract/r/gnoswap/v1/router/type_test.gno: -------------------------------------------------------------------------------- 1 | package router 2 | 3 | import ( 4 | "testing" 5 | 6 | "gno.land/p/nt/uassert" 7 | ) 8 | 9 | func TestTrySwapTypeFromStr(t *testing.T) { 10 | tests := []struct { 11 | name string 12 | input string 13 | want SwapType 14 | wantErr bool 15 | }{ 16 | { 17 | name: "valid EXACT_IN", 18 | input: rawExactIn, 19 | want: ExactIn, 20 | wantErr: false, 21 | }, 22 | { 23 | name: "valid EXACT_OUT", 24 | input: rawExactOut, 25 | want: ExactOut, 26 | wantErr: false, 27 | }, 28 | { 29 | name: "invalid empty string", 30 | input: "", 31 | want: "", 32 | wantErr: true, 33 | }, 34 | { 35 | name: "invalid swap type", 36 | input: "INVALID_TYPE", 37 | want: "", 38 | wantErr: true, 39 | }, 40 | } 41 | 42 | for _, tt := range tests { 43 | t.Run(tt.name, func(t *testing.T) { 44 | got, err := trySwapTypeFromStr(tt.input) 45 | 46 | if !tt.wantErr { 47 | uassert.NoError(t, err) 48 | } 49 | 50 | if got != tt.want { 51 | t.Errorf("trySwapTypeFromStr() = %v, want %v", got, tt.want) 52 | } 53 | }) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /contract/r/gnoswap/v1/staker/consts.gno: -------------------------------------------------------------------------------- 1 | package staker 2 | 3 | // WRAP & UNWRAP 4 | const ( 5 | GNOT string = "gnot" 6 | GNOT_DENOM string = "ugnot" 7 | 8 | // ref: https://github.com/gnolang/gno/blob/81a88a2976ba9f2f9127ebbe7fb7d1e1f7fa4bd4/examples/gno.land/r/gnoland/wugnot/wugnot.gno#L19 9 | UGNOT_MIN_DEPOSIT_TO_WRAP int64 = 1000 10 | 11 | GNS_PATH string = "gno.land/r/gnoswap/gns" 12 | WUGNOT_PATH string = "gno.land/r/gnoland/wugnot" 13 | ) 14 | -------------------------------------------------------------------------------- /contract/r/gnoswap/v1/staker/counter.gno: -------------------------------------------------------------------------------- 1 | package staker 2 | 3 | type Counter struct { 4 | id int64 5 | } 6 | 7 | func NewCounter() *Counter { 8 | return &Counter{ 9 | id: 0, 10 | } 11 | } 12 | 13 | func (c *Counter) next() int64 { 14 | c.id++ 15 | 16 | return c.id 17 | } 18 | 19 | func (c *Counter) Get() int64 { 20 | return c.id 21 | } 22 | -------------------------------------------------------------------------------- /contract/r/gnoswap/v1/staker/counter_test.gno: -------------------------------------------------------------------------------- 1 | package staker 2 | 3 | import ( 4 | "testing" 5 | 6 | "gno.land/p/nt/uassert" 7 | ) 8 | 9 | // Test for NewCounter, next, and Get methods. 10 | func TestCounter_Behavior(t *testing.T) { 11 | tests := []struct { 12 | name string 13 | initValue int64 14 | callNextTimes int 15 | expectedId int64 16 | }{ 17 | { 18 | name: "Initial value is zero", 19 | initValue: 0, 20 | callNextTimes: 0, 21 | expectedId: 0, 22 | }, 23 | { 24 | name: "Call next once", 25 | initValue: 0, 26 | callNextTimes: 1, 27 | expectedId: 1, 28 | }, 29 | { 30 | name: "Call next multiple times", 31 | initValue: 0, 32 | callNextTimes: 5, 33 | expectedId: 5, 34 | }, 35 | { 36 | name: "Start from non-zero, call next", 37 | initValue: 10, 38 | callNextTimes: 3, 39 | expectedId: 13, 40 | }, 41 | } 42 | 43 | for _, tc := range tests { 44 | t.Run(tc.name, func(t *testing.T) { 45 | // given: create counter and set initial value if needed 46 | c := NewCounter() 47 | if tc.initValue > 0 { 48 | c.id = tc.initValue 49 | } 50 | 51 | // when: call next() as many times as needed 52 | for i := 0; i < tc.callNextTimes; i++ { 53 | c.next() 54 | } 55 | 56 | // then: check id value 57 | uassert.Equal(t, c.Get(), tc.expectedId) 58 | }) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /contract/r/gnoswap/v1/staker/doc.gno: -------------------------------------------------------------------------------- 1 | // Package staker manages liquidity mining rewards for GnoSwap positions. 2 | // 3 | // The staker distributes GNS emissions and external incentives to liquidity 4 | // providers based on their position size, price range, and staking duration. 5 | // It supports both internal GNS rewards and external token incentives. 6 | // 7 | // Rewards are calculated per-tick and accumulate over time, with automatic 8 | // compounding and fee collection integration. 9 | package staker 10 | -------------------------------------------------------------------------------- /contract/r/gnoswap/v1/staker/external_incentive_test.gno: -------------------------------------------------------------------------------- 1 | package staker 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | 7 | "gno.land/p/nt/testutils" 8 | "gno.land/p/nt/ufmt" 9 | ) 10 | 11 | func TestCreateExternalIncentiveWithAllowedTokens(t *testing.T) { 12 | poolPath := "test_pool" 13 | account1 := testutils.TestAddress("account1") 14 | account2 := testutils.TestAddress("account2") 15 | 16 | // create pool 17 | pool := NewPool(poolPath, 100) 18 | pools.set(poolPath, pool) 19 | 20 | currentTime := time.Now().Unix() 21 | startTime := currentTime + 86400 // 1 day later 22 | endTime := startTime + (86400 * 7) // 7 days after start 23 | 24 | // Define allowed tokens and test scenarios 25 | allowedTokens := []string{GNS_PATH, WUGNOT_PATH, "token0_path", "token1_path"} 26 | accounts := []address{account1, account2} 27 | amounts := []int64{1000, 2000, 1500, 2500, 3000, 3500} 28 | 29 | // Test various combinations 30 | tests := []struct { 31 | accountIdx int 32 | tokenIdx int 33 | amountIdx int 34 | }{ 35 | {0, 0, 0}, // account1, GNS, 1000 36 | {0, 1, 1}, // account1, WUGNOT, 2000 37 | {1, 2, 2}, // account2, token0_path, 1500 38 | {1, 3, 3}, // account2, token1_path, 2500 39 | {0, 2, 4}, // account1, token0_path, 3000 40 | {1, 0, 5}, // account2, GNS, 3500 41 | } 42 | 43 | for i, tc := range tests { 44 | creator := accounts[tc.accountIdx] 45 | token := allowedTokens[tc.tokenIdx] 46 | amount := amounts[tc.amountIdx] 47 | 48 | incentiveId := ufmt.Sprintf("%d", i+1) 49 | name := ufmt.Sprintf("account_%d_token_%s_amount_%d", tc.accountIdx+1, token, amount) 50 | 51 | t.Run(name, func(t *testing.T) { 52 | // create incentive 53 | incentive := &ExternalIncentive{ 54 | incentiveId: incentiveId, 55 | startTimestamp: startTime, 56 | endTimestamp: endTime, 57 | rewardToken: token, 58 | rewardAmount: amount, 59 | refundee: creator, 60 | } 61 | 62 | // add incentive 63 | pool.incentives.create(creator, incentive) 64 | 65 | // verify incentive was created 66 | ictv, exists := pool.incentives.Get(incentive.incentiveId) 67 | if !exists { 68 | t.Fatal("incentive was not created") 69 | } 70 | 71 | if ictv.rewardAmount != amount { 72 | t.Errorf("expected reward amount %d, got %d", amount, ictv.rewardAmount) 73 | } 74 | 75 | if ictv.rewardToken != token { 76 | t.Errorf("expected reward token %s, got %s", token, ictv.rewardToken) 77 | } 78 | 79 | if ictv.refundee != creator { 80 | t.Errorf("expected refundee %s, got %s", creator, ictv.refundee) 81 | } 82 | }) 83 | } 84 | 85 | // clean up after test 86 | pools.tree.Remove(poolPath) 87 | } 88 | -------------------------------------------------------------------------------- /contract/r/gnoswap/v1/staker/gnomod.toml: -------------------------------------------------------------------------------- 1 | module = "gno.land/r/gnoswap/v1/staker" 2 | gno = "0.9" 3 | -------------------------------------------------------------------------------- /contract/r/gnoswap/v1/staker/incentive_id.gno: -------------------------------------------------------------------------------- 1 | package staker 2 | 3 | import "gno.land/p/nt/ufmt" 4 | 5 | // Counter for generating unique incentive IDs 6 | var incentiveCounter = NewCounter() 7 | 8 | // nextIncentiveID generates a new unique incentive ID using creator address, timestamp and counter 9 | func nextIncentiveID(creator address, timestamp int64) string { 10 | return makeIncentiveID(creator, timestamp, incentiveCounter.next()) 11 | } 12 | 13 | // makeIncentiveID formats an incentive ID string from the given components 14 | // incentive id format: creator:timestamp:index 15 | func makeIncentiveID(creator address, timestamp int64, index int64) string { 16 | return ufmt.Sprintf("%s:%d:%d", creator.String(), timestamp, index) 17 | } 18 | -------------------------------------------------------------------------------- /contract/r/gnoswap/v1/staker/pool_incentive_validation_test.gno: -------------------------------------------------------------------------------- 1 | package staker 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | 7 | "gno.land/p/nt/testutils" 8 | ) 9 | 10 | // TestStakeToken_WithEndedIncentives tests that StakeToken incorrectly allows 11 | // staking in pools that only have ended incentives due to the bug in IsExternallyIncentivizedPool 12 | func TestStakeToken_WithEndedIncentives(t *testing.T) { 13 | poolPath := "test_pool_stake_validation" 14 | creator := testutils.TestAddress("creator") 15 | 16 | testing.SetRealm(adminRealm) 17 | pool := NewPool(poolPath, 100) 18 | pools.set(poolPath, pool) 19 | 20 | // Add an ended incentive 21 | currentTime := time.Now().Unix() 22 | endedIncentive := &ExternalIncentive{ 23 | incentiveId: "ended_incentive", 24 | targetPoolPath: poolPath, 25 | startTimestamp: currentTime - 7200, // Started 2 hours ago 26 | endTimestamp: currentTime - 3600, // Ended 1 hour ago 27 | rewardToken: GNS_PATH, 28 | rewardAmount: 1000, 29 | refundee: creator, 30 | depositGnsAmount: 100, 31 | } 32 | pool.incentives.create(creator, endedIncentive) 33 | 34 | // After fix: pool should NOT show as incentivized with only ended incentives 35 | if pool.IsExternallyIncentivizedPool() { 36 | t.Fatal("pool should NOT show as incentivized with only ended incentives") 37 | } 38 | 39 | // Test poolHasIncentives validation 40 | // This would normally be called within StakeToken 41 | // Since poolTiers might not be initialized in test, we'll check external incentives only 42 | hasExternalIncentives := pool.IsExternallyIncentivizedPool() 43 | 44 | if hasExternalIncentives { 45 | t.Fatal("pool should NOT have external incentives when only ended incentives exist") 46 | } 47 | 48 | // Verify that there are actually no active incentives 49 | activeIncentives := pool.incentives.GetAllInTimestamps(currentTime, currentTime+3600) 50 | if len(activeIncentives) != 0 { 51 | t.Fatalf("expected 0 active incentives, got %d", len(activeIncentives)) 52 | } 53 | 54 | // Clean up 55 | pools.tree.Remove(poolPath) 56 | } 57 | -------------------------------------------------------------------------------- /contract/r/gnoswap/v1/staker/protocol_fee_unstaking.gno: -------------------------------------------------------------------------------- 1 | package staker 2 | 3 | import ( 4 | "chain" 5 | "chain/runtime" 6 | 7 | "gno.land/p/nt/ufmt" 8 | 9 | "gno.land/r/gnoswap/access" 10 | "gno.land/r/gnoswap/halt" 11 | "gno.land/r/gnoswap/v1/common" 12 | 13 | pf "gno.land/r/gnoswap/v1/protocol_fee" 14 | ) 15 | 16 | const FEE_PRECISION = 10000 17 | 18 | // unstakingFee is the fee charged when unstaking positions. 19 | // This parameter can be modified through governance. 20 | var unstakingFee = int64(100) // 1% 21 | 22 | // GetUnstakingFee returns the current unstaking fee rate in basis points. 23 | func GetUnstakingFee() int64 { return unstakingFee } 24 | 25 | // handleStakingRewardFee calculates and applies the unstaking fee. 26 | func handleStakingRewardFee( 27 | tokenPath string, 28 | amount int64, 29 | internal bool, 30 | ) (int64, int64, error) { 31 | if unstakingFee == 0 { 32 | return amount, 0, nil 33 | } 34 | 35 | // Do not change the order of the operation. 36 | feeAmount := (amount * unstakingFee) / FEE_PRECISION 37 | if feeAmount < 0 { 38 | return 0, 0, ufmt.Errorf("fee amount cannot be negative") 39 | } 40 | 41 | if feeAmount == 0 { 42 | return amount, 0, nil 43 | } 44 | 45 | if internal { 46 | tokenPath = GNS_PATH 47 | } 48 | 49 | // external contract has fee 50 | common.SafeGRC20Transfer(cross, tokenPath, protocolFeeAddr, feeAmount) 51 | pf.AddToProtocolFee(cross, tokenPath, feeAmount) 52 | 53 | return amount - feeAmount, feeAmount, nil 54 | } 55 | 56 | // SetUnStakingFee sets the unstaking fee rate in basis points. 57 | // Only admin or governance can call this function. 58 | func SetUnStakingFee(cur realm, fee int64) { 59 | halt.AssertIsNotHaltedStakerWithVersion(1) 60 | 61 | caller := runtime.PreviousRealm().Address() 62 | access.AssertIsAdminOrGovernanceWithVersion(1, caller) 63 | 64 | assertIsValidFeeRate(fee) 65 | 66 | prevUnStakingFee := GetUnstakingFee() 67 | 68 | setUnStakingFee(fee) 69 | 70 | previousRealm := runtime.PreviousRealm() 71 | chain.Emit( 72 | "SetUnStakingFee", 73 | "prevAddr", previousRealm.Address().String(), 74 | "prevRealm", previousRealm.PkgPath(), 75 | "prevFee", formatAnyInt(prevUnStakingFee), 76 | "newFee", formatAnyInt(fee), 77 | ) 78 | } 79 | 80 | // setUnStakingFee internally updates the unstaking fee. 81 | func setUnStakingFee(fee int64) { 82 | unstakingFee = fee 83 | } 84 | -------------------------------------------------------------------------------- /contract/r/gnoswap/v1/staker/reward_calculation.gno: -------------------------------------------------------------------------------- 1 | package staker 2 | 3 | import ( 4 | "gno.land/p/nt/ufmt" 5 | 6 | i256 "gno.land/p/gnoswap/int256" 7 | u256 "gno.land/p/gnoswap/uint256" 8 | ) 9 | 10 | // liquidityMathAddDelta calculates the new liquidity by applying the delta liquidity to the current liquidity. 11 | // If delta liquidity is negative, it subtracts the absolute value of delta liquidity from the current liquidity. 12 | // If delta liquidity is positive, it adds the absolute value of delta liquidity to the current liquidity. 13 | // 14 | // Parameters: 15 | // - x: The current liquidity as a uint256 value. 16 | // - y: The delta liquidity as a signed int256 value. 17 | // 18 | // Returns: 19 | // - The new liquidity as a uint256 value. 20 | // 21 | // Notes: 22 | // - If `x` or `y` is nil, the function panics with an appropriate error message. 23 | // - If `y` is negative, its absolute value is subtracted from `x`. 24 | // - The result must be less than `x`. Otherwise, the function panics to prevent underflow. 25 | // 26 | // - If `y` is positive, it is added to `x`. 27 | // - The result must be greater than or equal to `x`. Otherwise, the function panics to prevent overflow. 28 | // 29 | // - The function ensures correctness by validating the results of the arithmetic operations. 30 | func liquidityMathAddDelta(x *u256.Uint, y *i256.Int) *u256.Uint { 31 | if x == nil || y == nil { 32 | panic(makeErrorWithDetails( 33 | errInvalidInput, 34 | "x or y is nil", 35 | )) 36 | } 37 | 38 | var z *u256.Uint 39 | 40 | // Subtract or add based on the sign of y 41 | if y.Lt(i256.Zero()) { 42 | absDelta := y.Abs() 43 | z = u256.Zero().Sub(x, absDelta) 44 | if z.Gte(x) { 45 | panic(makeErrorWithDetails( 46 | errCalculationError, 47 | ufmt.Sprintf("Condition failed: (z must be < x) (x: %s, y: %s, z:%s)", x.ToString(), y.ToString(), z.ToString()), 48 | )) 49 | } 50 | } else { 51 | z = u256.Zero().Add(x, y.Abs()) 52 | if z.Lt(x) { 53 | panic(makeErrorWithDetails( 54 | errCalculationError, 55 | ufmt.Sprintf("Condition failed: (z must be >= x) (x: %s, y: %s, z:%s)", x.ToString(), y.ToString(), z.ToString()), 56 | )) 57 | } 58 | } 59 | 60 | return z 61 | } 62 | -------------------------------------------------------------------------------- /contract/r/scenario/gns/gnomod.toml: -------------------------------------------------------------------------------- 1 | module = "gno.land/r/gnoswap/scenario/gns" 2 | gno = "0.9" 3 | -------------------------------------------------------------------------------- /contract/r/scenario/gns/mint_gns_by_skipping_blocks_filetest.gno: -------------------------------------------------------------------------------- 1 | // mint gns by skipping blocks 2 | package main 3 | 4 | import ( 5 | "testing" 6 | "time" 7 | 8 | "gno.land/p/nt/testutils" 9 | "gno.land/p/nt/ufmt" 10 | 11 | prbac "gno.land/p/gnoswap/rbac" 12 | "gno.land/r/gnoswap/emission" 13 | "gno.land/r/gnoswap/gns" 14 | 15 | _ "gno.land/r/gnoswap/v1/staker" 16 | 17 | "gno.land/r/gnoswap/access" 18 | ) 19 | 20 | var t *testing.T 21 | 22 | var ( 23 | user01Addr = testutils.TestAddress("user01Addr") 24 | user01Realm = testing.NewUserRealm(user01Addr) 25 | 26 | adminAddr, _ = access.GetAddress(prbac.ROLE_ADMIN.String()) 27 | ) 28 | 29 | func main() { 30 | ufmt.Println("[SCENARIO] 1. Initialize emission callback") 31 | initEmissionCallback() 32 | println() 33 | 34 | ufmt.Println("[SCENARIO] 2. Mint GNS when initialized") 35 | mintGNS() 36 | println() 37 | 38 | ufmt.Println("[SCENARIO] 3. Skip 50 blocks") 39 | testing.SkipHeights(50) 40 | println() 41 | 42 | ufmt.Println("[SCENARIO] 4. Mint GNS when skipped 50 blocks") 43 | mintGNS() 44 | } 45 | 46 | func initEmissionCallback() { 47 | testing.SetOriginCaller(adminAddr) 48 | emission.SetDistributionStartTime(cross, time.Now().Unix()+1) 49 | testing.SkipHeights(1) 50 | } 51 | 52 | func mintGNS() { 53 | testing.SetRealm(testing.NewCodeRealm("gno.land/r/gnoswap/emission")) 54 | 55 | mintedAmount := gns.MintGns(cross, user01Addr) 56 | 57 | ufmt.Printf("[EXPECTED] minted amount should be %d\n", mintedAmount) 58 | } 59 | 60 | // Output: 61 | // [SCENARIO] 1. Initialize emission callback 62 | // 63 | // [SCENARIO] 2. Mint GNS when initialized 64 | // [EXPECTED] minted amount should be 35673515 65 | // 66 | // [SCENARIO] 3. Skip 50 blocks 67 | // 68 | // [SCENARIO] 4. Mint GNS when skipped 50 blocks 69 | // [EXPECTED] minted amount should be 1783675750 70 | -------------------------------------------------------------------------------- /contract/r/scenario/gov/governance/gnomod.toml: -------------------------------------------------------------------------------- 1 | module = "gno.land/r/gnoswap/scenario/gov/governance" 2 | gno = "0.9" 3 | -------------------------------------------------------------------------------- /contract/r/scenario/gov/staker/gnomod.toml: -------------------------------------------------------------------------------- 1 | module = "gno.land/r/gnoswap/scenario/gov/staker" 2 | gno = "0.9" 3 | -------------------------------------------------------------------------------- /contract/r/scenario/launchpad/gnomod.toml: -------------------------------------------------------------------------------- 1 | module = "gno.land/r/gnoswap/scenario/launchpad" 2 | gno = "0.9" 3 | -------------------------------------------------------------------------------- /contract/r/scenario/pool/burn3_filetest.gno: -------------------------------------------------------------------------------- 1 | // PKGPATH: gno.land/r/demo/main 2 | 3 | package main 4 | 5 | import ( 6 | "testing" 7 | 8 | prbac "gno.land/p/gnoswap/rbac" 9 | "gno.land/p/nt/uassert" 10 | 11 | "gno.land/r/gnoswap/access" 12 | 13 | "gno.land/r/gnoswap/v1/pool" 14 | ) 15 | 16 | var t *testing.T 17 | 18 | // Constants for fee tiers 19 | const ( 20 | FeeTier100 uint32 = 100 21 | FeeTier500 uint32 = 500 22 | ) 23 | 24 | // Variables for test addresses and realms 25 | var ( 26 | adminAddr, _ = access.GetAddress(prbac.ROLE_ADMIN.String()) 27 | positionAddr, _ = access.GetAddress(prbac.ROLE_POSITION.String()) 28 | posRealm = testing.NewUserRealm(positionAddr) 29 | ) 30 | 31 | func main() { 32 | testNonExistentPoolBurn() 33 | } 34 | 35 | // testNonExistentPoolBurn tests burning from non-existent pool (should abort) 36 | func testNonExistentPoolBurn() { 37 | // Given: No pool exists 38 | barTokenPath := "gno.land/r/onbloc/bar" 39 | bazTokenPath := "gno.land/r/onbloc/baz" 40 | fee := FeeTier500 41 | tickLower := int32(-100) 42 | tickUpper := int32(100) 43 | burnAmount := "1000000" 44 | 45 | println("[INFO] Attempting to burn from non-existent pool") 46 | println("[INFO] Token0:", barTokenPath) 47 | println("[INFO] Token1:", bazTokenPath) 48 | println("[INFO] Fee tier:", fee) 49 | println("[INFO] Tick range:", tickLower, "to", tickUpper) 50 | println("[INFO] Burn amount:", burnAmount) 51 | 52 | // When: Try to burn from non-existent pool 53 | testing.SetRealm(posRealm) 54 | 55 | // Then: Should abort with specific error message 56 | expectedAbortMsg := "[GNOSWAP-POOL-008] requested data not found || expected poolPath(gno.land/r/onbloc/bar:gno.land/r/onbloc/baz:500) to exist" 57 | println("[EXPECTED] Should abort with message:", expectedAbortMsg) 58 | 59 | uassert.AbortsWithMessage(t, expectedAbortMsg, func() { 60 | pool.Burn( 61 | cross, 62 | barTokenPath, 63 | bazTokenPath, 64 | fee, 65 | tickLower, 66 | tickUpper, 67 | burnAmount, 68 | adminAddr, 69 | ) 70 | }) 71 | 72 | println("[SCENARIO] 4. Non-existent Pool Burn - PASSED") 73 | } 74 | 75 | // Output: 76 | // [INFO] Attempting to burn from non-existent pool 77 | // [INFO] Token0: gno.land/r/onbloc/bar 78 | // [INFO] Token1: gno.land/r/onbloc/baz 79 | // [INFO] Fee tier: 500 80 | // [INFO] Tick range: -100 to 100 81 | // [INFO] Burn amount: 1000000 82 | // [EXPECTED] Should abort with message: [GNOSWAP-POOL-008] requested data not found || expected poolPath(gno.land/r/onbloc/bar:gno.land/r/onbloc/baz:500) to exist 83 | // [SCENARIO] 4. Non-existent Pool Burn - PASSED 84 | -------------------------------------------------------------------------------- /contract/r/scenario/pool/gnomod.toml: -------------------------------------------------------------------------------- 1 | module = "gno.land/r/gnoswap/scenario/pool" 2 | gno = "0.9" 3 | -------------------------------------------------------------------------------- /contract/r/scenario/position/gnomod.toml: -------------------------------------------------------------------------------- 1 | module = "gno.land/r/gnoswap/scenario/position" 2 | gno = "0.9" 3 | -------------------------------------------------------------------------------- /contract/r/scenario/rbac/gnomod.toml: -------------------------------------------------------------------------------- 1 | module = "gno.land/r/gnoswap/scenario/rbac" 2 | gno = "0.9" 3 | -------------------------------------------------------------------------------- /contract/r/scenario/router/gnomod.toml: -------------------------------------------------------------------------------- 1 | module = "gno.land/r/gnoswap/scenario/router" 2 | gno = "0.9" 3 | -------------------------------------------------------------------------------- /contract/r/scenario/staker/gnomod.toml: -------------------------------------------------------------------------------- 1 | module = "gno.land/r/gnoswap/scenario/staker" 2 | gno = "0.9" 3 | -------------------------------------------------------------------------------- /contract/r/scenario/upgrade/gnomod.toml: -------------------------------------------------------------------------------- 1 | module = "gno.land/r/gnoswap/scenario/upgrade" 2 | gno = "0.9" 3 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | app: 3 | build: . 4 | volumes: 5 | - .:/app 6 | environment: 7 | - PYTHONUNBUFFERED=1 8 | command: python3 setup.py 9 | -------------------------------------------------------------------------------- /remove-test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Script to remove all *_test.gno and testutils.gno files from the project 4 | 5 | echo "Finding all *_test.gno and testutils.gno files..." 6 | TEST_FILES=$(find . -name "*_test.gno" -o -name "testutils.gno" -type f) 7 | 8 | if [ -z "$TEST_FILES" ]; then 9 | echo "No test files found." 10 | exit 0 11 | fi 12 | 13 | echo "Found the following test files:" 14 | echo "$TEST_FILES" 15 | echo 16 | echo "Total count: $(echo "$TEST_FILES" | wc -l) files" 17 | echo 18 | 19 | read -p "Are you sure you want to delete all these files? (y/N): " -n 1 -r 20 | echo 21 | if [[ $REPLY =~ ^[Yy]$ ]]; then 22 | echo "Removing test files..." 23 | echo "$TEST_FILES" | while IFS= read -r file; do 24 | rm -f "$file" 25 | echo "Removed: $file" 26 | done 27 | echo "All test files and testutils.gno files have been removed." 28 | else 29 | echo "Operation cancelled." 30 | fi 31 | -------------------------------------------------------------------------------- /scripts/test_values.mk: -------------------------------------------------------------------------------- 1 | # Matrix configurations 2 | TEST_KEYS:=p/uint256 \ 3 | p/int256 \ 4 | p/gnsmath \ 5 | r/common \ 6 | r/gns \ 7 | r/gnft \ 8 | r/gov/xgns \ 9 | r/emission \ 10 | r/protocol_fee \ 11 | r/pool \ 12 | r/position \ 13 | r/router \ 14 | r/staker \ 15 | r/community_pool \ 16 | r/gov/staker \ 17 | r/gov/governance \ 18 | r/launchpad 19 | 20 | TEST_VALUES:=gno/examples/gno.land/p/gnoswap/uint256 \ 21 | gno/examples/gno.land/p/gnoswap/int256 \ 22 | gno/examples/gno.land/p/gnoswap/gnsmath \ 23 | gno/examples/gno.land/r/gnoswap/v1/common \ 24 | gno/examples/gno.land/r/gnoswap/gns \ 25 | gno/examples/gno.land/r/gnoswap/v1/gnft \ 26 | gno/examples/gno.land/r/gnoswap/v1/gov/xgns \ 27 | gno/examples/gno.land/r/gnoswap/emission \ 28 | gno/examples/gno.land/r/gnoswap/v1/protocol_fee \ 29 | gno/examples/gno.land/r/gnoswap/v1/pool \ 30 | gno/examples/gno.land/r/gnoswap/v1/position \ 31 | gno/examples/gno.land/r/gnoswap/v1/router \ 32 | gno/examples/gno.land/r/gnoswap/v1/staker \ 33 | gno/examples/gno.land/r/gnoswap/v1/community_pool \ 34 | gno/examples/gno.land/r/gnoswap/v1/gov/staker \ 35 | gno/examples/gno.land/r/gnoswap/v1/gov/governance \ 36 | gno/examples/gno.land/r/gnoswap/v1/launchpad 37 | -------------------------------------------------------------------------------- /scripts/test_values.sh: -------------------------------------------------------------------------------- 1 | # Matrix configurations 2 | TEST_KEYS=("p/uint256" "p/int256" "p/gnsmath" "r/common" "r/gns" "r/gnft" "r/gov/xgns" 3 | "r/emission" "r/protocol_fee" "r/pool" "r/position" "r/router" "r/staker" 4 | "r/community_pool" "r/gov/staker" "r/gov/governance" "r/launchpad") 5 | 6 | TEST_VALUES=( 7 | "gno/examples/gno.land/p/gnoswap/uint256" 8 | "gno/examples/gno.land/p/gnoswap/int256" 9 | "gno/examples/gno.land/p/gnoswap/gnsmath" 10 | "gno/examples/gno.land/r/gnoswap/v1/common" 11 | "gno/examples/gno.land/r/gnoswap/gns" 12 | "gno/examples/gno.land/r/gnoswap/v1/gnft" 13 | "gno/examples/gno.land/r/gnoswap/v1/gov/xgns" 14 | "gno/examples/gno.land/r/gnoswap/emission" 15 | "gno/examples/gno.land/r/gnoswap/v1/protocol_fee" 16 | "gno/examples/gno.land/r/gnoswap/v1/pool" 17 | "gno/examples/gno.land/r/gnoswap/v1/position" 18 | "gno/examples/gno.land/r/gnoswap/v1/router" 19 | "gno/examples/gno.land/r/gnoswap/v1/staker" 20 | "gno/examples/gno.land/r/gnoswap/v1/community_pool" 21 | "gno/examples/gno.land/r/gnoswap/v1/gov/staker" 22 | "gno/examples/gno.land/r/gnoswap/v1/gov/governance" 23 | "gno/examples/gno.land/r/gnoswap/v1/launchpad" 24 | ) -------------------------------------------------------------------------------- /sonar-project.properties: -------------------------------------------------------------------------------- 1 | sonar.projectKey=gnoswap-labs_gnoswap-swap 2 | sonar.organization=gnoswap-labs 3 | sonar.exclusions=**/*_test.gno 4 | 5 | # This is the name and version displayed in the SonarCloud UI. 6 | #sonar.projectName=gnoswap-batch-service 7 | #sonar.projectVersion=1.0 8 | 9 | 10 | # Path is relative to the sonar-project.properties file. Replace "\" by "/" on Windows. 11 | #sonar.sources=. 12 | 13 | # Encoding of the source code. Default is default system encoding 14 | #sonar.sourceEncoding=UTF-8 15 | 16 | -------------------------------------------------------------------------------- /tests/_info.mk: -------------------------------------------------------------------------------- 1 | # r/demo/wugnot from gno 2 | ADDR_WUGNOT := g15vj5q08amlvyd0nx6zjgcvwq2d0gt9fcchrvum 3 | 4 | # based on v1 5 | ADDR_POOL := g148tjamj80yyrm309z7rk690an22thd2l3z8ank 6 | ADDR_POSITION := g1q646ctzhvn60v492x8ucvyqnrj2w30cwh6efk5 7 | ADDR_ROUTER := g1lm2l7tf49h3mykesct7rhfml30yx8dw5xrval7 8 | ADDR_STAKER := g1cceshmzzlmrh7rr3z30j2t5mrvsq9yccysw9nu 9 | ADDR_PROTOCOL_FEE := g1f7wpek7q67tkns27sw495u5yuu3a5wwjxw5l6l 10 | ADDR_GOV_STAKER := g17e3ykyqk9jmqe2y9wxe9zhep3p7cw56davjqwa 11 | ADR_GOV_GOV := g17s8w2ve7k85fwfnrk59lmlhthkjdted8whvqxd 12 | ADDR_LAUNCHPAD := g122mau2lp2rc0scs8d27pkkuys4w54mdy2tuer3 13 | ADDR_GNS := g13ffa5r3mqfxu3s7ejl02scq9536wt6c2t789dm 14 | ADDR_GNFT := g1wxv2rdfn53qc84nt3nn646f9yh3nly8lm7j89t 15 | 16 | # username address 17 | ADDR_GNOSWAP := 18 | ADDR_ADMIN := 19 | ADDR_TEST := 20 | 21 | # INCENTIVE_START 22 | TOMORROW_MIDNIGHT := $(shell (gdate -ud 'tomorrow 00:00:00' +%s)) 23 | INCENTIVE_END := $(shell expr $(TOMORROW_MIDNIGHT) + 7776000) # 7776000 SECONDS = 90 DAY 24 | 25 | # MAX_UINT64 := 18446744073709551615 26 | MAX_APPROVE := 9223372036854775806 27 | TX_EXPIRE := 9999999999 28 | 29 | MAKEFILE := $(shell realpath $(firstword $(MAKEFILE_LIST))) 30 | ROOT_DIR:=$(shell dirname $(MAKEFILE))/../ 31 | 32 | 33 | # TODO: change below 2 values based on which chain to deploy 34 | GNOLAND_RPC_URL ?= localhost:26657 35 | CHAINID ?= dev 36 | -------------------------------------------------------------------------------- /tests/deploy/codegen.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | //go:generate go run generate/main.go 4 | -------------------------------------------------------------------------------- /tests/integration/process/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "os" 7 | "time" 8 | 9 | "github.com/gnolang/gno/gno.land/pkg/integration" 10 | ) 11 | 12 | func main() { 13 | ctx, cancel := context.WithTimeout(context.Background(), time.Second*30) 14 | defer cancel() 15 | 16 | if err := integration.RunMain(ctx, os.Stdin, os.Stdout, os.Stderr); err != nil { 17 | fmt.Fprintln(os.Stderr, err.Error()) 18 | os.Exit(1) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /tests/integration/testdata_test.go: -------------------------------------------------------------------------------- 1 | package integration 2 | 3 | import ( 4 | "os" 5 | "strconv" 6 | "testing" 7 | 8 | gno_integration "github.com/gnolang/gno/gnovm/pkg/integration" 9 | "github.com/rogpeppe/go-internal/testscript" 10 | "github.com/stretchr/testify/require" 11 | ) 12 | 13 | func TestTestdata(t *testing.T) { 14 | t.Parallel() 15 | 16 | flagInMemoryTS, _ := strconv.ParseBool(os.Getenv("INMEMORY_TS")) 17 | flagNoSeqTS, _ := strconv.ParseBool(os.Getenv("NO_SEQ_TS")) 18 | 19 | p := gno_integration.NewTestingParams(t, "testdata") 20 | 21 | if coverdir, ok := gno_integration.ResolveCoverageDir(); ok { 22 | err := gno_integration.SetupTestscriptsCoverage(&p, coverdir) 23 | require.NoError(t, err) 24 | } 25 | 26 | // Set up gnoland for testscript 27 | err := SetupGnolandTestscript(t, &p) 28 | require.NoError(t, err) 29 | 30 | mode := commandKindTesting 31 | if flagInMemoryTS { 32 | mode = commandKindInMemory 33 | } 34 | 35 | origSetup := p.Setup 36 | p.Setup = func(env *testscript.Env) error { 37 | env.Values[envKeyExecCommand] = mode 38 | if origSetup != nil { 39 | if err := origSetup(env); err != nil { 40 | return err 41 | } 42 | } 43 | 44 | return nil 45 | } 46 | 47 | if flagInMemoryTS && !flagNoSeqTS { 48 | testscript.RunT(tSeqShim{t}, p) 49 | } else { 50 | testscript.Run(t, p) 51 | } 52 | } 53 | 54 | type tSeqShim struct{ *testing.T } 55 | 56 | // noop Parallel method allow us to run test sequentially 57 | func (tSeqShim) Parallel() {} 58 | 59 | func (t tSeqShim) Run(name string, f func(testscript.T)) { 60 | t.T.Run(name, func(t *testing.T) { 61 | f(tSeqShim{t}) 62 | }) 63 | } 64 | 65 | func (t tSeqShim) Verbose() bool { 66 | return testing.Verbose() 67 | } 68 | -------------------------------------------------------------------------------- /tests/integration/testscript_testing.go: -------------------------------------------------------------------------------- 1 | package integration 2 | 3 | import ( 4 | "errors" 5 | "testing" 6 | 7 | "github.com/rogpeppe/go-internal/testscript" 8 | "github.com/stretchr/testify/assert" 9 | "github.com/stretchr/testify/require" 10 | ) 11 | 12 | // This error is from testscript.Fatalf and is needed to correctly 13 | // handle the FailNow method. 14 | // see: https://github.com/rogpeppe/go-internal/blob/32ae33786eccde1672d4ba373c80e1bc282bfbf6/testscript/testscript.go#L799-L812 15 | var errFailNow = errors.New("fail now!") //nolint:stylecheck 16 | 17 | var ( 18 | _ require.TestingT = (*testingTS)(nil) 19 | _ assert.TestingT = (*testingTS)(nil) 20 | _ TestingTS = &testing.T{} 21 | ) 22 | 23 | type TestingTS = require.TestingT 24 | 25 | type testingTS struct { 26 | *testscript.TestScript 27 | } 28 | 29 | func TSTestingT(ts *testscript.TestScript) TestingTS { 30 | return &testingTS{ts} 31 | } 32 | 33 | func (t *testingTS) Errorf(format string, args ...any) { 34 | defer recover() // we can ignore recover result, we just want to catch it up 35 | t.Fatalf(format, args...) 36 | } 37 | 38 | func (t *testingTS) FailNow() { 39 | // unfortunately we can't access underlying `t.t.FailNow` method 40 | panic(errFailNow) 41 | } 42 | -------------------------------------------------------------------------------- /tests/integration/utils.go: -------------------------------------------------------------------------------- 1 | package integration 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "strconv" 7 | "strings" 8 | ) 9 | 10 | // `unquote` takes a slice of strings, resulting from splitting a string block by spaces, and 11 | // processes them. The function handles quoted phrases and escape characters within these strings. 12 | func unquote(args []string) ([]string, error) { 13 | const quote = '"' 14 | 15 | parts := []string{} 16 | var inQuote bool 17 | 18 | var part strings.Builder 19 | for _, arg := range args { 20 | var escaped bool 21 | for _, c := range arg { 22 | if escaped { 23 | // If the character is meant to be escaped, it is processed with Unquote. 24 | // We use `Unquote` here for two main reasons: 25 | // 1. It will validate that the escape sequence is correct 26 | // 2. It converts the escaped string to its corresponding raw character. 27 | // For example, "\\t" becomes '\t'. 28 | uc, err := strconv.Unquote(`"\` + string(c) + `"`) 29 | if err != nil { 30 | return nil, fmt.Errorf("unhandled escape sequence `\\%c`: %w", c, err) 31 | } 32 | 33 | part.WriteString(uc) 34 | escaped = false 35 | continue 36 | } 37 | 38 | // If we are inside a quoted string and encounter an escape character, 39 | // flag the next character as `escaped` 40 | if inQuote && c == '\\' { 41 | escaped = true 42 | continue 43 | } 44 | 45 | // Detect quote and toggle inQuote state 46 | if c == quote { 47 | inQuote = !inQuote 48 | continue 49 | } 50 | 51 | // Handle regular character 52 | part.WriteRune(c) 53 | } 54 | 55 | // If we're inside a quote, add a single space. 56 | // It reflects one or multiple spaces between args in the original string. 57 | if inQuote { 58 | part.WriteRune(' ') 59 | continue 60 | } 61 | 62 | // Finalize part, add to parts, and reset for next part 63 | parts = append(parts, part.String()) 64 | part.Reset() 65 | } 66 | 67 | // Check if a quote is left open 68 | if inQuote { 69 | return nil, errors.New("unfinished quote") 70 | } 71 | 72 | return parts, nil 73 | } 74 | -------------------------------------------------------------------------------- /tests/integration/utils_test.go: -------------------------------------------------------------------------------- 1 | package integration 2 | 3 | import ( 4 | "strings" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | "github.com/stretchr/testify/require" 9 | ) 10 | 11 | func TestUnquote(t *testing.T) { 12 | t.Parallel() 13 | 14 | cases := []struct { 15 | Input string 16 | Expected []string 17 | ShouldFail bool 18 | }{ 19 | {"", []string{""}, false}, 20 | {"g", []string{"g"}, false}, 21 | {"Hello Gno", []string{"Hello", "Gno"}, false}, 22 | {`"Hello" "Gno"`, []string{"Hello", "Gno"}, false}, 23 | {`"Hel lo" "Gno"`, []string{"Hel lo", "Gno"}, false}, 24 | {`"H e l l o\n" \nGno`, []string{"H e l l o\n", "\\nGno"}, false}, 25 | {`"Hel\n"\nlo " ""G"n"o"`, []string{"Hel\n\\nlo", " Gno"}, false}, 26 | {`"He said, \"Hello\"" "Gno"`, []string{`He said, "Hello"`, "Gno"}, false}, 27 | {`"\n \t" \n\t`, []string{"\n \t", "\\n\\t"}, false}, 28 | {`"Hel\\n"\t\\nlo " ""\\nGno"`, []string{"Hel\\n\\t\\\\nlo", " \\nGno"}, false}, 29 | // errors: 30 | {`"Hello Gno`, []string{}, true}, // unfinished quote 31 | {`"Hello\e Gno"`, []string{}, true}, // unhandled escape sequence 32 | } 33 | 34 | for _, tc := range cases { 35 | tc := tc 36 | t.Run(tc.Input, func(t *testing.T) { 37 | t.Parallel() 38 | 39 | // split by whitespace to simulate command-line arguments 40 | args := strings.Split(tc.Input, " ") 41 | unquotedArgs, err := unquote(args) 42 | if tc.ShouldFail { 43 | require.Error(t, err) 44 | return 45 | } 46 | 47 | require.NoError(t, err) 48 | assert.Equal(t, tc.Expected, unquotedArgs) 49 | }) 50 | } 51 | } 52 | --------------------------------------------------------------------------------