From 12c46a8acf87b40271e82828ef4292b8f5ecd98d Mon Sep 17 00:00:00 2001 From: moul <94029+moul@users.noreply.github.com> Date: Sat, 18 Jan 2025 11:51:20 +0100 Subject: [PATCH 01/20] xxx: reimport the whole codebase as a single commit for PR review Signed-off-by: moul <94029+moul@users.noreply.github.com> --- .github/workflows/release.yml | 51 + .github/workflows/tlin_check.yml | 51 + .gitignore | 354 ++++ LICENSE | 661 ++++++++ README.md | 212 +++ .../register_gnodev/gno.mod | 1 + .../register_gnodev/register_gnodev.gno | 233 +++ .../grc20reg/approve_transferfrom.txtar | 53 + __local/grc20_tokens/onbloc/bar/bar.gno | 85 + __local/grc20_tokens/onbloc/bar/gno.mod | 1 + __local/grc20_tokens/onbloc/baz/baz.gno | 85 + __local/grc20_tokens/onbloc/baz/gno.mod | 1 + __local/grc20_tokens/onbloc/foo/foo.gno | 85 + __local/grc20_tokens/onbloc/foo/gno.mod | 1 + __local/grc20_tokens/onbloc/obl/gno.mod | 1 + __local/grc20_tokens/onbloc/obl/obl.gno | 85 + __local/grc20_tokens/onbloc/qux/gno.mod | 1 + __local/grc20_tokens/onbloc/qux/qux.gno | 85 + __local/grc20_tokens/onbloc/usdc/gno.mod | 1 + __local/grc20_tokens/onbloc/usdc/usdc.gno | 85 + __local/test/all_test_data.mk | 713 +++++++++ __local/test/emission_test.mk | 344 ++++ __local/test/gov_proposal.mk | 404 +++++ __local/test/launchpad_test.mk | 503 ++++++ _deploy/p/gnoswap/int256/LICENSE | 21 + _deploy/p/gnoswap/int256/README.md | 6 + _deploy/p/gnoswap/int256/absolute.gno | 20 + _deploy/p/gnoswap/int256/absolute_test.gno | 105 ++ _deploy/p/gnoswap/int256/arithmetic.gno | 203 +++ _deploy/p/gnoswap/int256/arithmetic_test.gno | 571 +++++++ _deploy/p/gnoswap/int256/bitwise.gno | 94 ++ _deploy/p/gnoswap/int256/bitwise_test.gno | 201 +++ _deploy/p/gnoswap/int256/cmp.gno | 101 ++ _deploy/p/gnoswap/int256/cmp_test.gno | 317 ++++ _deploy/p/gnoswap/int256/conversion.gno | 88 + _deploy/p/gnoswap/int256/conversion_test.gno | 234 +++ _deploy/p/gnoswap/int256/gno.mod | 3 + _deploy/p/gnoswap/int256/int256.gno | 126 ++ _deploy/p/gnoswap/int256/int256_test.gno | 25 + ...__TEST_0_INIT_VARIABLE_AND_HELPER_test.gno | 39 + .../p/gnoswap/pool/__TEST_bit_math_test.gnoA | 71 + _deploy/p/gnoswap/pool/bit_math.gno | 65 + _deploy/p/gnoswap/pool/consts.gno | 16 + _deploy/p/gnoswap/pool/gno.mod | 1 + _deploy/p/gnoswap/pool/sqrt_price_math.gno | 398 +++++ .../p/gnoswap/pool/sqrt_price_math_test.gno | 643 ++++++++ _deploy/p/gnoswap/pool/swap_math.gno | 143 ++ _deploy/p/gnoswap/pool/swap_math_test.gno | 273 ++++ _deploy/p/gnoswap/uint256/LICENSE | 28 + _deploy/p/gnoswap/uint256/README.md | 5 + ...__TEST_0_INIT_VARIABLE_AND_HELPER_test.gno | 39 + .../p/gnoswap/uint256/__TEST_u256_test.gno | 321 ++++ _deploy/p/gnoswap/uint256/arithmetic.gno | 476 ++++++ _deploy/p/gnoswap/uint256/arithmetic_test.gno | 369 +++++ _deploy/p/gnoswap/uint256/bits_table.gno | 79 + _deploy/p/gnoswap/uint256/bitwise.gno | 264 +++ _deploy/p/gnoswap/uint256/bitwise_test.gno | 346 ++++ _deploy/p/gnoswap/uint256/cmp.gno | 125 ++ _deploy/p/gnoswap/uint256/cmp_test.gno | 163 ++ _deploy/p/gnoswap/uint256/conversion.gno | 570 +++++++ _deploy/p/gnoswap/uint256/conversion_test.gno | 60 + _deploy/p/gnoswap/uint256/error.gno | 73 + _deploy/p/gnoswap/uint256/fullmath.gno | 151 ++ _deploy/p/gnoswap/uint256/fullmath_test.gno | 256 +++ _deploy/p/gnoswap/uint256/gno.mod | 3 + _deploy/p/gnoswap/uint256/gs_pointer.gno | 8 + _deploy/p/gnoswap/uint256/mod.gno | 605 +++++++ _deploy/p/gnoswap/uint256/uint256.gno | 291 ++++ _deploy/p/gnoswap/uint256/utils.gno | 20 + _deploy/r/gnoswap/common/access.gno | 108 ++ _deploy/r/gnoswap/common/access_test.gno | 136 ++ .../r/gnoswap/common/address_and_username.gno | 29 + .../common/address_and_username_test.gno | 115 ++ _deploy/r/gnoswap/common/errors.gno | 32 + _deploy/r/gnoswap/common/gno.mod | 1 + _deploy/r/gnoswap/common/grc20reg_helper.gno | 100 ++ .../r/gnoswap/common/grc20reg_helper_test.gno | 261 +++ _deploy/r/gnoswap/common/grc721_token_id.gno | 41 + .../r/gnoswap/common/grc721_token_id_test.gno | 64 + _deploy/r/gnoswap/common/halt.gno | 99 ++ _deploy/r/gnoswap/common/halt_test.gno | 81 + _deploy/r/gnoswap/common/limit_caller.gno | 52 + .../r/gnoswap/common/limit_caller_test.gno | 28 + .../r/gnoswap/common/liquidity_amounts.gno | 314 ++++ .../gnoswap/common/liquidity_amounts_test.gno | 506 ++++++ _deploy/r/gnoswap/common/math.gno | 100 ++ _deploy/r/gnoswap/common/math_test.gno | 234 +++ _deploy/r/gnoswap/common/strings.gno | 59 + _deploy/r/gnoswap/common/tick_math.gno | 378 +++++ _deploy/r/gnoswap/common/tick_math_test.gno | 209 +++ _deploy/r/gnoswap/common/util.gno | 33 + _deploy/r/gnoswap/consts/consts.gno | 152 ++ _deploy/r/gnoswap/consts/gno.mod | 1 + _deploy/r/gnoswap/gnft/errors.gno | 19 + _deploy/r/gnoswap/gnft/gnft.gno | 507 ++++++ _deploy/r/gnoswap/gnft/gnft_test.gno | 529 ++++++ _deploy/r/gnoswap/gnft/gno.mod | 1 + _deploy/r/gnoswap/gnft/svg_generator.gno | 67 + _deploy/r/gnoswap/gnft/svg_generator_test.gno | 25 + _deploy/r/gnoswap/gnft/utils.gno | 59 + _deploy/r/gnoswap/gns/_helper_test.gno | 43 + _deploy/r/gnoswap/gns/errors.gno | 17 + _deploy/r/gnoswap/gns/gno.mod | 1 + _deploy/r/gnoswap/gns/gns.gno | 344 ++++ _deploy/r/gnoswap/gns/gns_test.gno | 400 +++++ _deploy/r/gnoswap/gns/halving.gno | 586 +++++++ _deploy/r/gnoswap/gns/halving_test.gno | 191 +++ .../tests/gns_calculate_and_mint_test.gnoA | 106 ++ .../minted_and_left_emission_amount_test.gnoA | 98 ++ _deploy/r/gnoswap/gns/tests/z1_filetest.gno | 113 ++ _deploy/r/gnoswap/gns/tests/z2_filetest.gno | 136 ++ _deploy/r/gnoswap/gns/tests/z3_filetest.gno | 108 ++ _deploy/r/gnoswap/gns/tests/z4_filetest.gno | 93 ++ _deploy/r/gnoswap/gns/utils.gno | 66 + ...eger] Gnoswap Audit Report (ENG)_Final.pdf | Bin 0 -> 622618 bytes changelog.md | 85 + community_pool/community_pool.gno | 81 + community_pool/community_pool_test.gno | 219 +++ community_pool/errors.gno | 19 + community_pool/gno.mod | 1 + .../__TEST_0_INIT_TOKEN_REGISTER_test.gno | 143 ++ ...__TEST_0_INIT_VARIABLE_AND_HELPER_test.gno | 31 + emission/_test_helper.gno | 44 + emission/distribution.gno | 356 +++++ emission/distribution_test.gno | 457 ++++++ emission/emission.gno | 84 + emission/emission_test.gno | 59 + emission/errors.gno | 18 + emission/gno.mod | 1 + emission/utils.gno | 92 ++ gov/doc.gno | 2 + gov/governance/api.gno | 260 +++ gov/governance/api_test.gno | 186 +++ gov/governance/config.gno | 164 ++ gov/governance/config_test.gno | 219 +++ gov/governance/errors.gno | 31 + gov/governance/execute.gno | 317 ++++ gov/governance/execute_test.gno | 368 +++++ gov/governance/fn_registry.gno | 193 +++ gov/governance/fn_registry_test.gno | 227 +++ gov/governance/getter_proposal.gno | 37 + gov/governance/getter_vote.gno | 40 + gov/governance/gno.mod | 1 + gov/governance/proposal.gno | 386 +++++ gov/governance/proposal_test.gno | 361 +++++ .../__TEST_0_INIT_TOKEN_REGISTER_test.gno | 158 ++ ...__TEST_0_INIT_VARIABLE_AND_HELPER_test.gno | 46 + ...overnance_proposal_MULTI_execute_test.gnoA | 97 ++ ...ce_proposal_community_pool_spend_test.gnoA | 213 +++ ...TEST_governance_proposal_execute_test.gnoA | 309 ++++ ...overnance_proposal_status_update_test.gnoA | 452 ++++++ .../__TEST_governance_proposal_text_test.gnoA | 238 +++ ...ST_governance_vote_gov_delegated_test.gnoA | 176 ++ ...egated_after_propose_before_vote_test.gnoA | 168 ++ ...gated_undelegated_before_propose_test.gnoA | 178 +++ ...ernance_vote_with_launchpad_xgns_test.gnoA | 200 +++ gov/governance/type.gno | 139 ++ gov/governance/utils.gno | 151 ++ gov/governance/vote.gno | 250 +++ gov/governance/vote_test.gno | 511 ++++++ gov/staker/api_delegation.gno | 68 + gov/staker/api_history.gno | 65 + gov/staker/api_staker.gno | 90 ++ gov/staker/clean_delegation_stat_history.gno | 72 + .../clean_delegation_stat_history_test.gno | 84 + gov/staker/delegate_undelegate.gno | 180 +++ gov/staker/delegate_undelegate_test.gno | 110 ++ gov/staker/errors.gno | 36 + gov/staker/gno.mod | 1 + gov/staker/history.gno | 72 + gov/staker/history_test.gno | 103 ++ gov/staker/reward_calculation.gno | 407 +++++ gov/staker/reward_calculation_test.gno | 107 ++ gov/staker/staker.gno | 461 ++++++ gov/staker/staker_test.gno | 624 ++++++++ .../__TEST_0_INIT_TOKEN_REGISTER_test.gnoA | 185 +++ ..._TEST_0_INIT_VARIABLE_AND_HELPER_test.gnoA | 35 + gov/staker/tests/__TEST_api_test.gnoA | 174 ++ .../tests/__TEST_emission_gns_mint_test.gnoA | 74 + ..._TEST_governance_reward_emission_test.gnoA | 152 ++ ...T_governance_reward_protocol_fee_test.gnoA | 238 +++ ...ward_protocol_fee_with_launchpad_test.gnoA | 204 +++ .../clean_delegation_stat_history_test.gnoA | 85 + gov/staker/tests/history_test.gnoA | 81 + gov/staker/util.gno | 140 ++ gov/staker/util_test.gno | 268 ++++ gov/xgns/errors.gno | 22 + gov/xgns/gno.mod | 1 + gov/xgns/xgns.gno | 195 +++ gov/xgns/xgns_test.gno | 76 + launchpad/_RPC_api_deposit.gno | 176 ++ launchpad/_RPC_api_project.gno | 193 +++ launchpad/_RPC_api_reward.gno | 57 + launchpad/consts.gno | 16 + launchpad/doc.gno | 3 + launchpad/dummy_test.gno | 11 + launchpad/errors.gno | 31 + launchpad/gno.mod | 1 + launchpad/launchpad_deposit.gno | 557 +++++++ launchpad/launchpad_init.gno | 369 +++++ launchpad/launchpad_reward.gno | 363 +++++ launchpad/reward_calculation.gno | 208 +++ .../__TEST_0_INIT_TOKEN_REGISTER_test.gno | 203 +++ ...__TEST_0_INIT_VARIABLE_AND_HELPER_test.gno | 45 + launchpad/tests/__TEST_RPC_api_test.gnoA | 258 +++ .../__TEST_launchpad_create_project_test.gnoA | 84 + ...deposit_project_single_recipient_test.gnoA | 151 ++ ...ad_deposit_project_two_recipient_test.gnoA | 192 +++ ..._refund_ended_proejct_no_deposit_test.gnoA | 96 ++ ...refund_ended_proejct_with_tier30_test.gnoA | 333 ++++ ...unchpad_reward_and_gov_reward_01_test.gnoA | 130 ++ ...unchpad_reward_and_gov_reward_02_test.gnoA | 249 +++ ...unchpad_reward_and_gov_reward_03_test.gnoA | 425 +++++ ...single_deposit_reward_by_proejct_test.gnoA | 114 ++ ...e_deposit_reward_by_proejct_tier_test.gnoA | 121 ++ ...e_deposit_01_deposit_collect_gns_test.gnoA | 255 +++ ...sit_02_deposit_reward_by_proejct_test.gnoA | 122 ++ ...sit_03_deposit_reward_by_deposit_test.gnoA | 116 ++ ...osit_reward_by_deposit_endheight_test.gnoA | 99 ++ ...30_two_deposit_reward_by_project_test.gnoA | 141 ++ ...e_deposit_reward_by_proejct_tier_test.gnoA | 121 ++ ...le_deposit_reward_by_proejct_tier_test.gno | 143 ++ launchpad/type.gno | 96 ++ launchpad/util.gno | 80 + pool/_helper_test.gno | 615 +++++++ pool/api.gno | 325 ++++ pool/api_test.gno | 172 ++ pool/doc.gno | 2 + pool/errors.gno | 52 + pool/getter.gno | 128 ++ pool/getter_test.gno | 491 ++++++ pool/gno.mod | 1 + pool/liquidity_math.gno | 61 + pool/liquidity_math_test.gno | 76 + pool/pool.gno | 518 ++++++ pool/pool_manager.gno | 349 ++++ pool/pool_manager_test.gno | 239 +++ pool/pool_test.gno | 165 ++ pool/pool_transfer.gno | 203 +++ pool/pool_transfer_test.gno | 298 ++++ pool/position.gno | 183 +++ pool/position_modify.gno | 119 ++ pool/position_modify_test.gno | 292 ++++ pool/position_test.gno | 176 ++ pool/position_update.gno | 99 ++ pool/position_update_test.gno | 82 + pool/protocol_fee_pool_creation.gno | 70 + pool/protocol_fee_pool_creation_test.gno | 182 +++ pool/protocol_fee_withdrawal.gno | 165 ++ pool/protocol_fee_withdrawal_test.gno | 335 ++++ pool/swap.gno | 528 ++++++ pool/swap_test.gno | 953 +++++++++++ pool/tests/__TEST_pool_burn_test.gnoA | 225 +++ pool/tests/__TEST_pool_create_pool_test.gnoA | 40 + .../__TEST_pool_dryswap_and_swap_test.gnoA | 86 + pool/tests/__TEST_pool_fee_protocol_test.gnoA | 88 + pool/tests/__TEST_pool_init_test.gnoA | 146 ++ pool/tests/__TEST_pool_limit_test.gnoA | 156 ++ ...EST_pool_limit_with_protocol_fee_test.gnoA | 170 ++ pool/tests/__TEST_pool_mint_test.gnoA | 100 ++ pool/tests/__TEST_pool_multi_token_test.gnoA | 364 +++++ pool/tests/__TEST_pool_native_swap_test.gnoA | 134 ++ pool/tests/__TEST_pool_single_lp_test.gnoA | 381 +++++ pool/tests/__TEST_pool_spec_#1_test.gnoA | 301 ++++ pool/tests/__TEST_pool_spec_#2_test.gnoA | 323 ++++ pool/tests/__TEST_pool_spec_#3_test.gnoA | 159 ++ pool/tests/__TEST_pool_spec_#4_test.gnoA | 178 +++ pool/tests/__TEST_pool_spec_#5_test.gnoA | 167 ++ pool/tests/__TEST_pool_spec_#6_test.gnoA | 143 ++ pool/tests/__TEST_pool_test.gnoA | 182 +++ pool/tests/__TEST_pool_tick_test.gnoA | 910 +++++++++++ .../__TEST_pool_tick_transaction_test.gnoA | 96 ++ pool/tests/__TEST_tick_bitmap_test.gnoA | 590 +++++++ pool/tick.gno | 407 +++++ pool/tick_bitmap.gno | 174 ++ pool/tick_bitmap_test.gno | 161 ++ pool/tick_test.gno | 730 +++++++++ pool/type.gno | 463 ++++++ pool/utils.gno | 277 ++++ pool/utils_test.gno | 453 ++++++ position/_helper_test.gno | 823 ++++++++++ position/api.gno | 529 ++++++ position/api_test.gno | 456 ++++++ position/doc.gno | 2 + position/errors.gno | 41 + position/getter.gno | 118 ++ position/getter_test.gno | 181 +++ position/gno.mod | 1 + position/liquidity_management.gno | 118 ++ position/liquidity_management_test.gno | 110 ++ position/native_token.gno | 171 ++ position/native_token_test.gno | 431 +++++ position/position.gno | 1174 ++++++++++++++ position/position_test.gno | 1422 +++++++++++++++++ ...__TEST_fee_collect_with_two_user_test.gnoA | 159 ++ position/tests/__TEST_position_api_test.gnoA | 217 +++ position/tests/__TEST_position_full_test.gnoA | 88 + ...TEST_position_full_with_emission_test.gnoA | 151 ++ ...osition_increase_burned_position_test.gnoA | 155 ++ ..._TEST_position_increase_decrease_test.gnoA | 214 +++ ...nt_gnot_grc20_in-range_out-range_test.gnoA | 211 +++ ...EST_position_mint_swap_burn_left_test.gnoA | 292 ++++ ...osition_native_increase_decrease_test.gnoA | 223 +++ ...T_position_native_mint_swap_burn_test.gnoA | 214 +++ ...ST_position_reposition_gnot_pair_test.gnoA | 311 ++++ ...T_position_reposition_grc20_pair_test.gnoA | 335 ++++ ..._reposition_grc20_pair_with_swap_test.gnoA | 391 +++++ ...iff_range_diff_position_swap_fee_test.gnoA | 196 +++ ...ame_range_diff_position_swap_fee_test.gnoA | 200 +++ ...st_two_position_used_single_swap_test.gnoA | 179 +++ ...owed_left_grc20_pair_more_action_test.gnoA | 344 ++++ ...owed_left_pair_more_action_exact_test.gnoA | 440 +++++ .../__TEST_position_unclaimed_fee_test.gnoA | 286 ++++ position/type.gno | 123 ++ position/utils.gno | 369 +++++ position/utils_test.gno | 975 +++++++++++ protocol_fee/errors.gno | 21 + protocol_fee/gno.mod | 1 + protocol_fee/protocol_fee.gno | 148 ++ protocol_fee/protocol_fee_test.gno | 79 + .../__TEST_0_INIT_TOKEN_REGISTER_test.gno | 144 ++ ...__TEST_0_INIT_VARIABLE_AND_HELPER_test.gno | 47 + protocol_fee/utils.gno | 14 + router/_helper_test.gno | 489 ++++++ router/base.gno | 155 ++ router/base_test.gno | 211 +++ router/doc.gno | 2 + router/errors.gno | 28 + router/exact_in.gno | 139 ++ router/exact_in_test.gno | 167 ++ router/exact_out.gno | 131 ++ router/gno.mod | 1 + router/protocol_fee_swap.gno | 115 ++ router/protocol_fee_swap_test.gno | 57 + router/router.gno | 197 +++ router/router_dry.gno | 170 ++ router/router_dry_test.gno | 69 + router/router_test.gno | 132 ++ router/swap_inner.gno | 309 ++++ router/swap_inner_test.gno | 132 ++ router/swap_multi.gno | 208 +++ router/swap_multi_test.gno | 137 ++ router/swap_single.gno | 77 + router/swap_single_test.gno | 84 + .../__TEST_0_INIT_TOKEN_REGISTER_test.gnoA | 155 ++ .../__TEST_0_INIT_VARS_HELPERS_test.gnoA | 66 + .../__TEST_router_all_2_route_2_hop_test.gnoA | 179 +++ ..._all_2_route_2_hop_with_emission_test.gnoA | 256 +++ ..._router_native_swap_amount_check_test.gnoA | 67 + .../__TEST_router_spec_#1_ExactIn_test.gnoA | 97 ++ .../__TEST_router_spec_#2_ExactIn_test.gnoA | 74 + .../__TEST_router_spec_#3_ExactIn_test.gnoA | 78 + .../__TEST_router_spec_#4_ExactIn_test.gnoA | 78 + .../__TEST_router_spec_#5_ExactOut_test.gnoA | 107 ++ .../__TEST_router_spec_#6_ExactOut_test.gnoA | 67 + .../__TEST_router_spec_#7_ExactOut_test.gnoA | 76 + .../__TEST_router_spec_#8_ExactOut_test.gnoA | 77 + ...oute_1hop_all_liquidity_exact_in_test.gnoA | 82 + ...ute_1hop_all_liquidity_exact_out_test.gnoA | 78 + ...1hop_native_in_out_test_exact_in_test.gnoA | 183 +++ ...swap_route_1route_1hop_out_range_test.gnoA | 94 ++ ...ST_router_swap_route_1route_1hop_test.gnoA | 138 ++ ...route_1hop_wrapped_native_in_out_test.gnoA | 76 + ...route_2hop_wrapped_native_in_out_test.gnoA | 142 ++ ...route_3hop_wrapped_native_middle_test.gnoA | 113 ++ ...ST_router_swap_route_2route_2hop_test.gnoA | 136 ++ router/type.gno | 152 ++ router/type_test.gno | 55 + router/utils.gno | 217 +++ router/utils_test.gno | 205 +++ router/wrap_unwrap.gno | 51 + setup.py | 71 + sonar-project.properties | 15 + ...r_total_4_position_internal_only_test.gnoA | 269 ++++ ...er_total_4_position_two_external_test.gnoA | 473 ++++++ ...idity_and_in_range_chane_by_swap_test.gnoA | 268 ++++ ...rt_wramup_internal_gnot_gns_3000_test.gnoA | 185 +++ .../__TEST_staker_NFT_transfer_01_test.gnoA | 182 +++ .../__TEST_staker_NFT_transfer_02_test.gnoA | 118 ++ .../__TEST_staker_NFT_transfer_03_test.gnoA | 194 +++ ..._emission_and_external_incentive_test.gnoA | 617 +++++++ ...TEST_staker_external_native_coin_test.gnoA | 142 ++ ...__TEST_staker_full_with_emission_test.gnoA | 296 ++++ .../__TEST_staker_manage_pool_tiers_test.gnoA | 122 ++ staker/__TEST_staker_mint_and_stake_test.gnoA | 125 ++ ...er_native_create_collect_unstake_test.gnoA | 308 ++++ ...ort-warmup_period_collect_reward_test.gnoA | 308 ++++ ...ate_pool_position_reward_GETTER_test.gnoXX | 556 +++++++ ..._short_warmup_period_external_10_test.gnoA | 238 +++ ..._short_warmup_period_external_12_test.gnoA | 184 +++ ..._period_external_13_gns_external_test.gnoA | 187 +++ ...ion_in_out_range_changed_by_swap_test.gnoA | 277 ++++ ...rt_warmup_period_external_15_90d_test.gnoA | 153 ++ ...t_warmup_period_external_16_180d_test.gnoA | 153 ++ ...t_warmup_period_external_17_365d_test.gnoA | 147 ++ ..._short_warmup_period_internal_01_test.gnoA | 190 +++ ...mup_period_internal_02_small_liq_test.gnoA | 192 +++ ...p_period_internal_03_change_tier_test.gnoA | 236 +++ ...p_period_internal_04_remove_tier_test.gnoA | 240 +++ ...riod_internal_05_collect_rewards_test.gnoA | 288 ++++ ...ion_in_out_range_changed_by_swap_test.gnoA | 198 +++ ...rmup_period_internal_external_90_test.gnoA | 223 +++ ...rmup_period_internal_external_91_test.gnoA | 260 +++ ...rt_wramup_period_staker_external_test.gnoA | 233 +++ .../__TEST_token_uri_in_same_block_test.gnoA | 69 + staker/_helper_test.gno | 750 +++++++++ staker/api.gno | 1350 ++++++++++++++++ staker/calculate_pool_position_reward.gno | 158 ++ staker/doc.gno | 3 + staker/errors.gno | 47 + staker/external_deposit_fee.gno | 75 + staker/external_deposit_fee_test.gno | 114 ++ staker/external_token_list.gno | 77 + staker/external_token_list_test.gno | 131 ++ ...e_block_time_change_from_gns_filetest.gnoA | 196 +++ ...z_no_position_to_give_reward_filetest.gnoA | 175 ++ ...and_change_to_tier3_internal_filetest.gnoA | 237 +++ ...o_tier2_and_removed_internal_filetest.gnoA | 260 +++ ...ange_change_by_swap_external_filetest.gnoA | 307 ++++ ...ange_change_by_swap_internal_filetest.gnoA | 284 ++++ ...ternal_filetest.gnoXX_ApiGetReward_missing | 250 +++ ...ternal_filetest.gnoXX_ApiGetReward_missing | 207 +++ ...ing_reward_at_sameblock_differnet_position | 282 ++++ ...sition_stake_unstake_restake_filetest.gnoA | 224 +++ ...ion_stake_unstake_same_block_filetest.gnoA | 166 ++ ...ty_change_by_staking_external_filetest.gno | 278 ++++ ...staking_external_filetest.gnoXX_RewardZero | 275 ++++ ...y_change_by_staking_internal_filetest.gnoA | 245 +++ ...oXXX_CollectReward_DiffPosition_SameHeight | 252 +++ ...change_by_unstaking_internal_filetest.gnoA | 218 +++ ...as_gns_filetest.gnoXX_ApiGetReward_missing | 299 ++++ ...rnal_02_position_range_change_filetest.gno | 318 ++++ ...al_filetest.gnoXX_RefundLeftExternalAmount | 262 +++ staker/getter.gno | 763 +++++++++ staker/gno.mod | 1 + staker/incentive_id.gno | 115 ++ staker/incentive_id_test.gno | 56 + staker/manage_pool_tier_and_warmup.gno | 174 ++ staker/mint_stake.gno | 74 + staker/protocol_fee_unstaking.gno | 152 ++ staker/protocol_fee_unstaking_test.gno | 80 + staker/query.gno | 125 ++ staker/query_test.gno | 82 + staker/reward_calculation.gno | 61 + .../reward_calculation_canonical_env_test.gno | 603 +++++++ staker/reward_calculation_canonical_test.gnoA | 1171 ++++++++++++++ staker/reward_calculation_incentives.gno | 182 +++ staker/reward_calculation_pool.gno | 545 +++++++ staker/reward_calculation_pool_tier.gno | 288 ++++ staker/reward_calculation_pool_tier_test.gno | 137 ++ staker/reward_calculation_tick.gno | 377 +++++ staker/reward_calculation_tick_test.gno | 283 ++++ staker/reward_calculation_types.gno | 181 +++ staker/reward_calculation_types_test.gno | 176 ++ staker/reward_calculation_warmup.gno | 110 ++ staker/reward_calculation_warmup_test.gno | 419 +++++ staker/reward_pool_store.gno | 186 +++ staker/reward_pool_store_test.gno | 78 + staker/staker.gno | 700 ++++++++ staker/staker_external_incentive.gno | 294 ++++ staker/type.gno | 146 ++ staker/utils.gno | 188 +++ staker/utils_test.gno | 299 ++++ staker/warp_unwrap_test.gno | 161 ++ staker/wrap_unwrap.gno | 64 + 465 files changed, 92615 insertions(+) create mode 100644 .github/workflows/release.yml create mode 100644 .github/workflows/tlin_check.yml create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 __local/grc20_tokens/g1er355fkjksqpdtwmhf5penwa82p0rhqxkkyhk5/register_gnodev/gno.mod create mode 100644 __local/grc20_tokens/g1er355fkjksqpdtwmhf5penwa82p0rhqxkkyhk5/register_gnodev/register_gnodev.gno create mode 100644 __local/grc20_tokens/grc20reg/approve_transferfrom.txtar create mode 100644 __local/grc20_tokens/onbloc/bar/bar.gno create mode 100644 __local/grc20_tokens/onbloc/bar/gno.mod create mode 100644 __local/grc20_tokens/onbloc/baz/baz.gno create mode 100644 __local/grc20_tokens/onbloc/baz/gno.mod create mode 100644 __local/grc20_tokens/onbloc/foo/foo.gno create mode 100644 __local/grc20_tokens/onbloc/foo/gno.mod create mode 100644 __local/grc20_tokens/onbloc/obl/gno.mod create mode 100644 __local/grc20_tokens/onbloc/obl/obl.gno create mode 100644 __local/grc20_tokens/onbloc/qux/gno.mod create mode 100644 __local/grc20_tokens/onbloc/qux/qux.gno create mode 100644 __local/grc20_tokens/onbloc/usdc/gno.mod create mode 100644 __local/grc20_tokens/onbloc/usdc/usdc.gno create mode 100644 __local/test/all_test_data.mk create mode 100644 __local/test/emission_test.mk create mode 100644 __local/test/gov_proposal.mk create mode 100644 __local/test/launchpad_test.mk create mode 100644 _deploy/p/gnoswap/int256/LICENSE create mode 100644 _deploy/p/gnoswap/int256/README.md create mode 100644 _deploy/p/gnoswap/int256/absolute.gno create mode 100644 _deploy/p/gnoswap/int256/absolute_test.gno create mode 100644 _deploy/p/gnoswap/int256/arithmetic.gno create mode 100644 _deploy/p/gnoswap/int256/arithmetic_test.gno create mode 100644 _deploy/p/gnoswap/int256/bitwise.gno create mode 100644 _deploy/p/gnoswap/int256/bitwise_test.gno create mode 100644 _deploy/p/gnoswap/int256/cmp.gno create mode 100644 _deploy/p/gnoswap/int256/cmp_test.gno create mode 100644 _deploy/p/gnoswap/int256/conversion.gno create mode 100644 _deploy/p/gnoswap/int256/conversion_test.gno create mode 100644 _deploy/p/gnoswap/int256/gno.mod create mode 100644 _deploy/p/gnoswap/int256/int256.gno create mode 100644 _deploy/p/gnoswap/int256/int256_test.gno create mode 100644 _deploy/p/gnoswap/pool/__TEST_0_INIT_VARIABLE_AND_HELPER_test.gno create mode 100644 _deploy/p/gnoswap/pool/__TEST_bit_math_test.gnoA create mode 100644 _deploy/p/gnoswap/pool/bit_math.gno create mode 100644 _deploy/p/gnoswap/pool/consts.gno create mode 100644 _deploy/p/gnoswap/pool/gno.mod create mode 100644 _deploy/p/gnoswap/pool/sqrt_price_math.gno create mode 100644 _deploy/p/gnoswap/pool/sqrt_price_math_test.gno create mode 100644 _deploy/p/gnoswap/pool/swap_math.gno create mode 100644 _deploy/p/gnoswap/pool/swap_math_test.gno create mode 100644 _deploy/p/gnoswap/uint256/LICENSE create mode 100644 _deploy/p/gnoswap/uint256/README.md create mode 100644 _deploy/p/gnoswap/uint256/__TEST_0_INIT_VARIABLE_AND_HELPER_test.gno create mode 100644 _deploy/p/gnoswap/uint256/__TEST_u256_test.gno create mode 100644 _deploy/p/gnoswap/uint256/arithmetic.gno create mode 100644 _deploy/p/gnoswap/uint256/arithmetic_test.gno create mode 100644 _deploy/p/gnoswap/uint256/bits_table.gno create mode 100644 _deploy/p/gnoswap/uint256/bitwise.gno create mode 100644 _deploy/p/gnoswap/uint256/bitwise_test.gno create mode 100644 _deploy/p/gnoswap/uint256/cmp.gno create mode 100644 _deploy/p/gnoswap/uint256/cmp_test.gno create mode 100644 _deploy/p/gnoswap/uint256/conversion.gno create mode 100644 _deploy/p/gnoswap/uint256/conversion_test.gno create mode 100644 _deploy/p/gnoswap/uint256/error.gno create mode 100644 _deploy/p/gnoswap/uint256/fullmath.gno create mode 100644 _deploy/p/gnoswap/uint256/fullmath_test.gno create mode 100644 _deploy/p/gnoswap/uint256/gno.mod create mode 100644 _deploy/p/gnoswap/uint256/gs_pointer.gno create mode 100644 _deploy/p/gnoswap/uint256/mod.gno create mode 100644 _deploy/p/gnoswap/uint256/uint256.gno create mode 100644 _deploy/p/gnoswap/uint256/utils.gno create mode 100644 _deploy/r/gnoswap/common/access.gno create mode 100644 _deploy/r/gnoswap/common/access_test.gno create mode 100644 _deploy/r/gnoswap/common/address_and_username.gno create mode 100644 _deploy/r/gnoswap/common/address_and_username_test.gno create mode 100644 _deploy/r/gnoswap/common/errors.gno create mode 100644 _deploy/r/gnoswap/common/gno.mod create mode 100644 _deploy/r/gnoswap/common/grc20reg_helper.gno create mode 100644 _deploy/r/gnoswap/common/grc20reg_helper_test.gno create mode 100644 _deploy/r/gnoswap/common/grc721_token_id.gno create mode 100644 _deploy/r/gnoswap/common/grc721_token_id_test.gno create mode 100644 _deploy/r/gnoswap/common/halt.gno create mode 100644 _deploy/r/gnoswap/common/halt_test.gno create mode 100644 _deploy/r/gnoswap/common/limit_caller.gno create mode 100644 _deploy/r/gnoswap/common/limit_caller_test.gno create mode 100644 _deploy/r/gnoswap/common/liquidity_amounts.gno create mode 100644 _deploy/r/gnoswap/common/liquidity_amounts_test.gno create mode 100644 _deploy/r/gnoswap/common/math.gno create mode 100644 _deploy/r/gnoswap/common/math_test.gno create mode 100644 _deploy/r/gnoswap/common/strings.gno create mode 100644 _deploy/r/gnoswap/common/tick_math.gno create mode 100644 _deploy/r/gnoswap/common/tick_math_test.gno create mode 100644 _deploy/r/gnoswap/common/util.gno create mode 100644 _deploy/r/gnoswap/consts/consts.gno create mode 100644 _deploy/r/gnoswap/consts/gno.mod create mode 100644 _deploy/r/gnoswap/gnft/errors.gno create mode 100644 _deploy/r/gnoswap/gnft/gnft.gno create mode 100644 _deploy/r/gnoswap/gnft/gnft_test.gno create mode 100644 _deploy/r/gnoswap/gnft/gno.mod create mode 100644 _deploy/r/gnoswap/gnft/svg_generator.gno create mode 100644 _deploy/r/gnoswap/gnft/svg_generator_test.gno create mode 100644 _deploy/r/gnoswap/gnft/utils.gno create mode 100644 _deploy/r/gnoswap/gns/_helper_test.gno create mode 100644 _deploy/r/gnoswap/gns/errors.gno create mode 100644 _deploy/r/gnoswap/gns/gno.mod create mode 100644 _deploy/r/gnoswap/gns/gns.gno create mode 100644 _deploy/r/gnoswap/gns/gns_test.gno create mode 100644 _deploy/r/gnoswap/gns/halving.gno create mode 100644 _deploy/r/gnoswap/gns/halving_test.gno create mode 100644 _deploy/r/gnoswap/gns/tests/gns_calculate_and_mint_test.gnoA create mode 100644 _deploy/r/gnoswap/gns/tests/minted_and_left_emission_amount_test.gnoA create mode 100644 _deploy/r/gnoswap/gns/tests/z1_filetest.gno create mode 100644 _deploy/r/gnoswap/gns/tests/z2_filetest.gno create mode 100644 _deploy/r/gnoswap/gns/tests/z3_filetest.gno create mode 100644 _deploy/r/gnoswap/gns/tests/z4_filetest.gno create mode 100644 _deploy/r/gnoswap/gns/utils.gno create mode 100644 audits/[BigInteger] Gnoswap Audit Report (ENG)_Final.pdf create mode 100644 changelog.md create mode 100644 community_pool/community_pool.gno create mode 100644 community_pool/community_pool_test.gno create mode 100644 community_pool/errors.gno create mode 100644 community_pool/gno.mod create mode 100644 community_pool/tests/__TEST_0_INIT_TOKEN_REGISTER_test.gno create mode 100644 community_pool/tests/__TEST_0_INIT_VARIABLE_AND_HELPER_test.gno create mode 100644 emission/_test_helper.gno create mode 100644 emission/distribution.gno create mode 100644 emission/distribution_test.gno create mode 100644 emission/emission.gno create mode 100644 emission/emission_test.gno create mode 100644 emission/errors.gno create mode 100644 emission/gno.mod create mode 100644 emission/utils.gno create mode 100644 gov/doc.gno create mode 100644 gov/governance/api.gno create mode 100644 gov/governance/api_test.gno create mode 100644 gov/governance/config.gno create mode 100644 gov/governance/config_test.gno create mode 100644 gov/governance/errors.gno create mode 100644 gov/governance/execute.gno create mode 100644 gov/governance/execute_test.gno create mode 100644 gov/governance/fn_registry.gno create mode 100644 gov/governance/fn_registry_test.gno create mode 100644 gov/governance/getter_proposal.gno create mode 100644 gov/governance/getter_vote.gno create mode 100644 gov/governance/gno.mod create mode 100644 gov/governance/proposal.gno create mode 100644 gov/governance/proposal_test.gno create mode 100644 gov/governance/tests/__TEST_0_INIT_TOKEN_REGISTER_test.gno create mode 100644 gov/governance/tests/__TEST_0_INIT_VARIABLE_AND_HELPER_test.gno create mode 100644 gov/governance/tests/__TEST_governance_proposal_MULTI_execute_test.gnoA create mode 100644 gov/governance/tests/__TEST_governance_proposal_community_pool_spend_test.gnoA create mode 100644 gov/governance/tests/__TEST_governance_proposal_execute_test.gnoA create mode 100644 gov/governance/tests/__TEST_governance_proposal_status_update_test.gnoA create mode 100644 gov/governance/tests/__TEST_governance_proposal_text_test.gnoA create mode 100644 gov/governance/tests/__TEST_governance_vote_gov_delegated_test.gnoA create mode 100644 gov/governance/tests/__TEST_governance_vote_gov_delegated_undelegated_after_propose_before_vote_test.gnoA create mode 100644 gov/governance/tests/__TEST_governance_vote_gov_delegated_undelegated_before_propose_test.gnoA create mode 100644 gov/governance/tests/__TEST_governance_vote_with_launchpad_xgns_test.gnoA create mode 100644 gov/governance/type.gno create mode 100644 gov/governance/utils.gno create mode 100644 gov/governance/vote.gno create mode 100644 gov/governance/vote_test.gno create mode 100644 gov/staker/api_delegation.gno create mode 100644 gov/staker/api_history.gno create mode 100644 gov/staker/api_staker.gno create mode 100644 gov/staker/clean_delegation_stat_history.gno create mode 100644 gov/staker/clean_delegation_stat_history_test.gno create mode 100644 gov/staker/delegate_undelegate.gno create mode 100644 gov/staker/delegate_undelegate_test.gno create mode 100644 gov/staker/errors.gno create mode 100644 gov/staker/gno.mod create mode 100644 gov/staker/history.gno create mode 100644 gov/staker/history_test.gno create mode 100644 gov/staker/reward_calculation.gno create mode 100644 gov/staker/reward_calculation_test.gno create mode 100644 gov/staker/staker.gno create mode 100644 gov/staker/staker_test.gno create mode 100644 gov/staker/tests/__TEST_0_INIT_TOKEN_REGISTER_test.gnoA create mode 100644 gov/staker/tests/__TEST_0_INIT_VARIABLE_AND_HELPER_test.gnoA create mode 100644 gov/staker/tests/__TEST_api_test.gnoA create mode 100644 gov/staker/tests/__TEST_emission_gns_mint_test.gnoA create mode 100644 gov/staker/tests/__TEST_governance_reward_emission_test.gnoA create mode 100644 gov/staker/tests/__TEST_governance_reward_protocol_fee_test.gnoA create mode 100644 gov/staker/tests/__TEST_governance_reward_protocol_fee_with_launchpad_test.gnoA create mode 100644 gov/staker/tests/clean_delegation_stat_history_test.gnoA create mode 100644 gov/staker/tests/history_test.gnoA create mode 100644 gov/staker/util.gno create mode 100644 gov/staker/util_test.gno create mode 100644 gov/xgns/errors.gno create mode 100644 gov/xgns/gno.mod create mode 100644 gov/xgns/xgns.gno create mode 100644 gov/xgns/xgns_test.gno create mode 100644 launchpad/_RPC_api_deposit.gno create mode 100644 launchpad/_RPC_api_project.gno create mode 100644 launchpad/_RPC_api_reward.gno create mode 100644 launchpad/consts.gno create mode 100644 launchpad/doc.gno create mode 100644 launchpad/dummy_test.gno create mode 100644 launchpad/errors.gno create mode 100644 launchpad/gno.mod create mode 100644 launchpad/launchpad_deposit.gno create mode 100644 launchpad/launchpad_init.gno create mode 100644 launchpad/launchpad_reward.gno create mode 100644 launchpad/reward_calculation.gno create mode 100644 launchpad/tests/__TEST_0_INIT_TOKEN_REGISTER_test.gno create mode 100644 launchpad/tests/__TEST_0_INIT_VARIABLE_AND_HELPER_test.gno create mode 100644 launchpad/tests/__TEST_RPC_api_test.gnoA create mode 100644 launchpad/tests/__TEST_launchpad_create_project_test.gnoA create mode 100644 launchpad/tests/__TEST_launchpad_deposit_project_single_recipient_test.gnoA create mode 100644 launchpad/tests/__TEST_launchpad_deposit_project_two_recipient_test.gnoA create mode 100644 launchpad/tests/__TEST_launchpad_refund_ended_proejct_no_deposit_test.gnoA create mode 100644 launchpad/tests/__TEST_launchpad_refund_ended_proejct_with_tier30_test.gnoA create mode 100644 launchpad/tests/__TEST_launchpad_reward_and_gov_reward_01_test.gnoA create mode 100644 launchpad/tests/__TEST_launchpad_reward_and_gov_reward_02_test.gnoA create mode 100644 launchpad/tests/__TEST_launchpad_reward_and_gov_reward_03_test.gnoA create mode 100644 launchpad/tests/__TEST_launchpad_tier180_single_deposit_reward_by_proejct_test.gnoA create mode 100644 launchpad/tests/__TEST_launchpad_tier180_single_deposit_reward_by_proejct_tier_test.gnoA create mode 100644 launchpad/tests/__TEST_launchpad_tier30_single_deposit_01_deposit_collect_gns_test.gnoA create mode 100644 launchpad/tests/__TEST_launchpad_tier30_single_deposit_02_deposit_reward_by_proejct_test.gnoA create mode 100644 launchpad/tests/__TEST_launchpad_tier30_single_deposit_03_deposit_reward_by_deposit_test.gnoA create mode 100644 launchpad/tests/__TEST_launchpad_tier30_single_deposit_04_deposit_reward_by_deposit_endheight_test.gnoA create mode 100644 launchpad/tests/__TEST_launchpad_tier30_two_deposit_reward_by_project_test.gnoA create mode 100644 launchpad/tests/__TEST_launchpad_tier90_single_deposit_reward_by_proejct_tier_test.gnoA create mode 100644 launchpad/tests/__TEST_launchpad_tier_all_single_deposit_reward_by_proejct_tier_test.gno create mode 100644 launchpad/type.gno create mode 100644 launchpad/util.gno create mode 100644 pool/_helper_test.gno create mode 100644 pool/api.gno create mode 100644 pool/api_test.gno create mode 100644 pool/doc.gno create mode 100644 pool/errors.gno create mode 100644 pool/getter.gno create mode 100644 pool/getter_test.gno create mode 100644 pool/gno.mod create mode 100644 pool/liquidity_math.gno create mode 100644 pool/liquidity_math_test.gno create mode 100644 pool/pool.gno create mode 100644 pool/pool_manager.gno create mode 100644 pool/pool_manager_test.gno create mode 100644 pool/pool_test.gno create mode 100644 pool/pool_transfer.gno create mode 100644 pool/pool_transfer_test.gno create mode 100644 pool/position.gno create mode 100644 pool/position_modify.gno create mode 100644 pool/position_modify_test.gno create mode 100644 pool/position_test.gno create mode 100644 pool/position_update.gno create mode 100644 pool/position_update_test.gno create mode 100644 pool/protocol_fee_pool_creation.gno create mode 100644 pool/protocol_fee_pool_creation_test.gno create mode 100644 pool/protocol_fee_withdrawal.gno create mode 100644 pool/protocol_fee_withdrawal_test.gno create mode 100644 pool/swap.gno create mode 100644 pool/swap_test.gno create mode 100644 pool/tests/__TEST_pool_burn_test.gnoA create mode 100644 pool/tests/__TEST_pool_create_pool_test.gnoA create mode 100644 pool/tests/__TEST_pool_dryswap_and_swap_test.gnoA create mode 100644 pool/tests/__TEST_pool_fee_protocol_test.gnoA create mode 100644 pool/tests/__TEST_pool_init_test.gnoA create mode 100644 pool/tests/__TEST_pool_limit_test.gnoA create mode 100644 pool/tests/__TEST_pool_limit_with_protocol_fee_test.gnoA create mode 100644 pool/tests/__TEST_pool_mint_test.gnoA create mode 100644 pool/tests/__TEST_pool_multi_token_test.gnoA create mode 100644 pool/tests/__TEST_pool_native_swap_test.gnoA create mode 100644 pool/tests/__TEST_pool_single_lp_test.gnoA create mode 100644 pool/tests/__TEST_pool_spec_#1_test.gnoA create mode 100644 pool/tests/__TEST_pool_spec_#2_test.gnoA create mode 100644 pool/tests/__TEST_pool_spec_#3_test.gnoA create mode 100644 pool/tests/__TEST_pool_spec_#4_test.gnoA create mode 100644 pool/tests/__TEST_pool_spec_#5_test.gnoA create mode 100644 pool/tests/__TEST_pool_spec_#6_test.gnoA create mode 100644 pool/tests/__TEST_pool_test.gnoA create mode 100644 pool/tests/__TEST_pool_tick_test.gnoA create mode 100644 pool/tests/__TEST_pool_tick_transaction_test.gnoA create mode 100644 pool/tests/__TEST_tick_bitmap_test.gnoA create mode 100644 pool/tick.gno create mode 100644 pool/tick_bitmap.gno create mode 100644 pool/tick_bitmap_test.gno create mode 100644 pool/tick_test.gno create mode 100644 pool/type.gno create mode 100644 pool/utils.gno create mode 100644 pool/utils_test.gno create mode 100644 position/_helper_test.gno create mode 100644 position/api.gno create mode 100644 position/api_test.gno create mode 100644 position/doc.gno create mode 100644 position/errors.gno create mode 100644 position/getter.gno create mode 100644 position/getter_test.gno create mode 100644 position/gno.mod create mode 100644 position/liquidity_management.gno create mode 100644 position/liquidity_management_test.gno create mode 100644 position/native_token.gno create mode 100644 position/native_token_test.gno create mode 100644 position/position.gno create mode 100644 position/position_test.gno create mode 100644 position/tests/__TEST_fee_collect_with_two_user_test.gnoA create mode 100644 position/tests/__TEST_position_api_test.gnoA create mode 100644 position/tests/__TEST_position_full_test.gnoA create mode 100644 position/tests/__TEST_position_full_with_emission_test.gnoA create mode 100644 position/tests/__TEST_position_increase_burned_position_test.gnoA create mode 100644 position/tests/__TEST_position_increase_decrease_test.gnoA create mode 100644 position/tests/__TEST_position_mint_gnot_grc20_in-range_out-range_test.gnoA create mode 100644 position/tests/__TEST_position_mint_swap_burn_left_test.gnoA create mode 100644 position/tests/__TEST_position_native_increase_decrease_test.gnoA create mode 100644 position/tests/__TEST_position_native_mint_swap_burn_test.gnoA create mode 100644 position/tests/__TEST_position_reposition_gnot_pair_test.gnoA create mode 100644 position/tests/__TEST_position_reposition_grc20_pair_test.gnoA create mode 100644 position/tests/__TEST_position_reposition_grc20_pair_with_swap_test.gnoA create mode 100644 position/tests/__TEST_position_same_user_same_pool_diff_range_diff_position_swap_fee_test.gnoA create mode 100644 position/tests/__TEST_position_same_user_same_pool_same_range_diff_position_swap_fee_test.gnoA create mode 100644 position/tests/__TEST_position_test_two_position_used_single_swap_test.gnoA create mode 100644 position/tests/__TEST_position_tokens_owed_left_grc20_pair_more_action_test.gnoA create mode 100644 position/tests/__TEST_position_tokens_owed_left_pair_more_action_exact_test.gnoA create mode 100644 position/tests/__TEST_position_unclaimed_fee_test.gnoA create mode 100644 position/type.gno create mode 100644 position/utils.gno create mode 100644 position/utils_test.gno create mode 100644 protocol_fee/errors.gno create mode 100644 protocol_fee/gno.mod create mode 100644 protocol_fee/protocol_fee.gno create mode 100644 protocol_fee/protocol_fee_test.gno create mode 100644 protocol_fee/tests/__TEST_0_INIT_TOKEN_REGISTER_test.gno create mode 100644 protocol_fee/tests/__TEST_0_INIT_VARIABLE_AND_HELPER_test.gno create mode 100644 protocol_fee/utils.gno create mode 100644 router/_helper_test.gno create mode 100644 router/base.gno create mode 100644 router/base_test.gno create mode 100644 router/doc.gno create mode 100644 router/errors.gno create mode 100644 router/exact_in.gno create mode 100644 router/exact_in_test.gno create mode 100644 router/exact_out.gno create mode 100644 router/gno.mod create mode 100644 router/protocol_fee_swap.gno create mode 100644 router/protocol_fee_swap_test.gno create mode 100644 router/router.gno create mode 100644 router/router_dry.gno create mode 100644 router/router_dry_test.gno create mode 100644 router/router_test.gno create mode 100644 router/swap_inner.gno create mode 100644 router/swap_inner_test.gno create mode 100644 router/swap_multi.gno create mode 100644 router/swap_multi_test.gno create mode 100644 router/swap_single.gno create mode 100644 router/swap_single_test.gno create mode 100644 router/tests/__TEST_0_INIT_TOKEN_REGISTER_test.gnoA create mode 100644 router/tests/__TEST_0_INIT_VARS_HELPERS_test.gnoA create mode 100644 router/tests/__TEST_router_all_2_route_2_hop_test.gnoA create mode 100644 router/tests/__TEST_router_all_2_route_2_hop_with_emission_test.gnoA create mode 100644 router/tests/__TEST_router_native_swap_amount_check_test.gnoA create mode 100644 router/tests/__TEST_router_spec_#1_ExactIn_test.gnoA create mode 100644 router/tests/__TEST_router_spec_#2_ExactIn_test.gnoA create mode 100644 router/tests/__TEST_router_spec_#3_ExactIn_test.gnoA create mode 100644 router/tests/__TEST_router_spec_#4_ExactIn_test.gnoA create mode 100644 router/tests/__TEST_router_spec_#5_ExactOut_test.gnoA create mode 100644 router/tests/__TEST_router_spec_#6_ExactOut_test.gnoA create mode 100644 router/tests/__TEST_router_spec_#7_ExactOut_test.gnoA create mode 100644 router/tests/__TEST_router_spec_#8_ExactOut_test.gnoA create mode 100644 router/tests/__TEST_router_swap_route_1route_1hop_all_liquidity_exact_in_test.gnoA create mode 100644 router/tests/__TEST_router_swap_route_1route_1hop_all_liquidity_exact_out_test.gnoA create mode 100644 router/tests/__TEST_router_swap_route_1route_1hop_native_in_out_test_exact_in_test.gnoA create mode 100644 router/tests/__TEST_router_swap_route_1route_1hop_out_range_test.gnoA create mode 100644 router/tests/__TEST_router_swap_route_1route_1hop_test.gnoA create mode 100644 router/tests/__TEST_router_swap_route_1route_1hop_wrapped_native_in_out_test.gnoA create mode 100644 router/tests/__TEST_router_swap_route_1route_2hop_wrapped_native_in_out_test.gnoA create mode 100644 router/tests/__TEST_router_swap_route_1route_3hop_wrapped_native_middle_test.gnoA create mode 100644 router/tests/__TEST_router_swap_route_2route_2hop_test.gnoA create mode 100644 router/type.gno create mode 100644 router/type_test.gno create mode 100644 router/utils.gno create mode 100644 router/utils_test.gno create mode 100644 router/wrap_unwrap.gno create mode 100644 setup.py create mode 100644 sonar-project.properties create mode 100644 staker/__TEST_more_01_single_position_for_each_warmup_tier_total_4_position_internal_only_test.gnoA create mode 100644 staker/__TEST_more_02_single_position_for_each_warmup_tier_total_4_position_two_external_test.gnoA create mode 100644 staker/__TEST_more_04_positions_with_different_liquidity_and_in_range_chane_by_swap_test.gnoA create mode 100644 staker/__TEST_short_wramup_internal_gnot_gns_3000_test.gnoA create mode 100644 staker/__TEST_staker_NFT_transfer_01_test.gnoA create mode 100644 staker/__TEST_staker_NFT_transfer_02_test.gnoA create mode 100644 staker/__TEST_staker_NFT_transfer_03_test.gnoA create mode 100644 staker/__TEST_staker_emission_and_external_incentive_test.gnoA create mode 100644 staker/__TEST_staker_external_native_coin_test.gnoA create mode 100644 staker/__TEST_staker_full_with_emission_test.gnoA create mode 100644 staker/__TEST_staker_manage_pool_tiers_test.gnoA create mode 100644 staker/__TEST_staker_mint_and_stake_test.gnoA create mode 100644 staker/__TEST_staker_native_create_collect_unstake_test.gnoA create mode 100644 staker/__TEST_staker_short-warmup_period_collect_reward_test.gnoA create mode 100644 staker/__TEST_staker_short_warmup_period_calculate_pool_position_reward_GETTER_test.gnoXX create mode 100644 staker/__TEST_staker_short_warmup_period_external_10_test.gnoA create mode 100644 staker/__TEST_staker_short_warmup_period_external_12_test.gnoA create mode 100644 staker/__TEST_staker_short_warmup_period_external_13_gns_external_test.gnoA create mode 100644 staker/__TEST_staker_short_warmup_period_external_14_position_in_out_range_changed_by_swap_test.gnoA create mode 100644 staker/__TEST_staker_short_warmup_period_external_15_90d_test.gnoA create mode 100644 staker/__TEST_staker_short_warmup_period_external_16_180d_test.gnoA create mode 100644 staker/__TEST_staker_short_warmup_period_external_17_365d_test.gnoA create mode 100644 staker/__TEST_staker_short_warmup_period_internal_01_test.gnoA create mode 100644 staker/__TEST_staker_short_warmup_period_internal_02_small_liq_test.gnoA create mode 100644 staker/__TEST_staker_short_warmup_period_internal_03_change_tier_test.gnoA create mode 100644 staker/__TEST_staker_short_warmup_period_internal_04_remove_tier_test.gnoA create mode 100644 staker/__TEST_staker_short_warmup_period_internal_05_collect_rewards_test.gnoA create mode 100644 staker/__TEST_staker_short_warmup_period_internal_06_position_in_out_range_changed_by_swap_test.gnoA create mode 100644 staker/__TEST_staker_short_warmup_period_internal_external_90_test.gnoA create mode 100644 staker/__TEST_staker_short_warmup_period_internal_external_91_test.gnoA create mode 100644 staker/__TEST_staker_short_wramup_period_staker_external_test.gnoA create mode 100644 staker/__TEST_token_uri_in_same_block_test.gnoA create mode 100644 staker/_helper_test.gno create mode 100644 staker/api.gno create mode 100644 staker/calculate_pool_position_reward.gno create mode 100644 staker/doc.gno create mode 100644 staker/errors.gno create mode 100644 staker/external_deposit_fee.gno create mode 100644 staker/external_deposit_fee_test.gno create mode 100644 staker/external_token_list.gno create mode 100644 staker/external_token_list_test.gno create mode 100644 staker/filetests/z_average_block_time_change_from_gns_filetest.gnoA create mode 100644 staker/filetests/z_no_position_to_give_reward_filetest.gnoA create mode 100644 staker/filetests/z_pool_add_to_tier2_and_change_to_tier3_internal_filetest.gnoA create mode 100644 staker/filetests/z_pool_add_to_tier2_and_removed_internal_filetest.gnoA create mode 100644 staker/filetests/z_position_inrange_change_by_swap_external_filetest.gnoA create mode 100644 staker/filetests/z_position_inrange_change_by_swap_internal_filetest.gnoA create mode 100644 staker/filetests/z_reward_for_user_collect_change_by_collecting_reward_external_filetest.gnoXX_ApiGetReward_missing create mode 100644 staker/filetests/z_reward_for_user_collect_change_by_collecting_reward_internal_filetest.gnoXX_ApiGetReward_missing create mode 100644 staker/filetests/z_single_gns_external_ends_filetest.gnXX_collecting_reward_at_sameblock_differnet_position create mode 100644 staker/filetests/z_single_position_stake_unstake_restake_filetest.gnoA create mode 100644 staker/filetests/z_single_position_stake_unstake_same_block_filetest.gnoA create mode 100644 staker/filetests/z_staked_liquidity_change_by_staking_external_filetest.gno create mode 100644 staker/filetests/z_staked_liquidity_change_by_staking_external_filetest.gnoXX_RewardZero create mode 100644 staker/filetests/z_staked_liquidity_change_by_staking_internal_filetest.gnoA create mode 100644 staker/filetests/z_staked_liquidity_change_by_unstaking_external_filetest.gnoXXX_CollectReward_DiffPosition_SameHeight create mode 100644 staker/filetests/z_staked_liquidity_change_by_unstaking_internal_filetest.gnoA create mode 100644 staker/filetests/z_staker_internal_external_01_both_has_gns_filetest.gnoXX_ApiGetReward_missing create mode 100644 staker/filetests/z_staker_internal_external_02_position_range_change_filetest.gno create mode 100644 staker/filetests/z_two_external_incentive_one_ends_external_filetest.gnoXX_RefundLeftExternalAmount create mode 100644 staker/getter.gno create mode 100644 staker/gno.mod create mode 100644 staker/incentive_id.gno create mode 100644 staker/incentive_id_test.gno create mode 100644 staker/manage_pool_tier_and_warmup.gno create mode 100644 staker/mint_stake.gno create mode 100644 staker/protocol_fee_unstaking.gno create mode 100644 staker/protocol_fee_unstaking_test.gno create mode 100644 staker/query.gno create mode 100644 staker/query_test.gno create mode 100644 staker/reward_calculation.gno create mode 100644 staker/reward_calculation_canonical_env_test.gno create mode 100644 staker/reward_calculation_canonical_test.gnoA create mode 100644 staker/reward_calculation_incentives.gno create mode 100644 staker/reward_calculation_pool.gno create mode 100644 staker/reward_calculation_pool_tier.gno create mode 100644 staker/reward_calculation_pool_tier_test.gno create mode 100644 staker/reward_calculation_tick.gno create mode 100644 staker/reward_calculation_tick_test.gno create mode 100644 staker/reward_calculation_types.gno create mode 100644 staker/reward_calculation_types_test.gno create mode 100644 staker/reward_calculation_warmup.gno create mode 100644 staker/reward_calculation_warmup_test.gno create mode 100644 staker/reward_pool_store.gno create mode 100644 staker/reward_pool_store_test.gno create mode 100644 staker/staker.gno create mode 100644 staker/staker_external_incentive.gno create mode 100644 staker/type.gno create mode 100644 staker/utils.gno create mode 100644 staker/utils_test.gno create mode 100644 staker/warp_unwrap_test.gno create mode 100644 staker/wrap_unwrap.gno diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 000000000..7ad1bb85a --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,51 @@ +name: Release Management + +on: + push: + branches: + - main + paths: + - 'CHANGELOG.md' + +permissions: + contents: write + pull-requests: write + +jobs: + create-release: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Get version from CHANGELOG + id: changelog + run: | + VERSION=$(grep -m 1 "## \[.*\]" CHANGELOG.md | grep -o "[0-9]\+\.[0-9]\+\.[0-9]\+") + echo "version=$VERSION" >> $GITHUB_OUTPUT + + - name: Create Release + if: steps.changelog.outputs.version != '' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + VERSION: ${{ steps.changelog.outputs.version }} + run: | + if ! git tag | grep -q "^v$VERSION$"; then + # Extract changelog content for this version + CHANGELOG_CONTENTS=$(awk "/## \[$VERSION\]/,/## \[/{ if (!/## \[$VERSION\]/ && !/## \[/) print }" CHANGELOG.md) + + # Create and push tag + git config --local user.email "action@github.com" + git config --local user.name "GitHub Action" + git tag -a "v$VERSION" -m "Release version $VERSION" + git push origin "v$VERSION" + + # Create GitHub release + gh release create "v$VERSION" \ + --title "Release v$VERSION" \ + --notes "$CHANGELOG_CONTENTS" \ + --draft=false \ + --prerelease=false + fi diff --git a/.github/workflows/tlin_check.yml b/.github/workflows/tlin_check.yml new file mode 100644 index 000000000..a66ec53d9 --- /dev/null +++ b/.github/workflows/tlin_check.yml @@ -0,0 +1,51 @@ +name: tlin-check + +on: + pull_request: + branches: + - main + +jobs: + tlin-check: + strategy: + fail-fast: false + + runs-on: ubuntu-latest + + steps: + - name: checkout code + uses: actions/checkout@v4 + with: + ref: ${{ github.event.pull_request.head.ref }} + + - name: checkout tlin + uses: actions/checkout@v4 + with: + repository: gnoverse/tlin + ref: main + path: ./tlin + + - name: setup go + uses: actions/setup-go@v5 + with: + go-version: 1.22 + + - name: changed files + id: changed_files + uses: tj-actions/changed-files@v45 + with: + files: | + *.gno + **.gno + + - name: install tlin + run: | + cd tlin + go install ./cmd/tlin + + - name: tlin check + run: | + for file in ${{ steps.changed_files.outputs.all_changed_files }}; do + echo "checking ${file} ..." + tlin ${file} + done diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..48acd8abf --- /dev/null +++ b/.gitignore @@ -0,0 +1,354 @@ +# Created by https://www.toptal.com/developers/gitignore/api/macos,go,goland+all,visualstudiocode,dotenv,python,jupyternotebooks +# Edit at https://www.toptal.com/developers/gitignore?templates=macos,go,goland+all,visualstudiocode,dotenv,python,jupyternotebooks + +### dotenv ### +.env + +### Go ### +# If you prefer the allow list template instead of the deny list, see community template: +# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore +# +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, built with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Dependency directories (remove the comment below to include it) +# vendor/ + +# Go workspace file +go.work + +### GoLand+all ### +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# AWS User-specific +.idea/**/aws.xml + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +# .idea/artifacts +# .idea/compiler.xml +# .idea/jarRepositories.xml +# .idea/modules.xml +# .idea/*.iml +# .idea/modules +# *.iml +# *.ipr + +# CMake +cmake-build-*/ + +# Mongo Explorer plugin +.idea/**/mongoSettings.xml + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# SonarLint plugin +.idea/sonarlint/ + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Editor-based Rest Client +.idea/httpRequests + +# Android studio 3.1+ serialized cache file +.idea/caches/build_file_checksums.ser + +### GoLand+all Patch ### +# Ignore everything but code style settings and run configurations +# that are supposed to be shared within teams. + +.idea/* + +!.idea/codeStyles +!.idea/runConfigurations + +### JupyterNotebooks ### +# gitignore template for Jupyter Notebooks +# website: http://jupyter.org/ + +.ipynb_checkpoints +*/.ipynb_checkpoints/* + +# IPython +profile_default/ +ipython_config.py + +# Remove previous ipynb_checkpoints +# git rm -r .ipynb_checkpoints/ + +### macOS ### +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### macOS Patch ### +# iCloud generated files +*.icloud + +### Python ### +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook + +# IPython + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/#use-with-ide +.pdm.toml + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ + +### Python Patch ### +# Poetry local configuration file - https://python-poetry.org/docs/configuration/#local-configuration +poetry.toml + +# ruff +.ruff_cache/ + +# LSP config files +pyrightconfig.json + +### VisualStudioCode ### +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +!.vscode/*.code-snippets + +# Local History for Visual Studio Code +.history/ + +# Built Visual Studio Code Extensions +*.vsix + +### VisualStudioCode Patch ### +# Ignore all local history of files +.history +.ionide + +# End of https://www.toptal.com/developers/gitignore/api/macos,go,goland+all,visualstudiocode,dotenv,python,jupyternotebooks + +.scannerwork + diff --git a/LICENSE b/LICENSE new file mode 100644 index 000000000..0ad25db4b --- /dev/null +++ b/LICENSE @@ -0,0 +1,661 @@ + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU Affero General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU Affero General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU Affero General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU Affero General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU AGPL, see +. diff --git a/README.md b/README.md index f55bb4278..18ef8db65 100644 --- a/README.md +++ b/README.md @@ -1 +1,213 @@ +<<<<<<< HEAD # Gnoswap Contract +======= +# GnoSwap Contracts + +This repository contains smart contracts (realms) for GnoSwap. + +## Index + +- [Setting Up and Testing GnoSwap Contracts](#setting-up-and-testing-gnoswap-contracts) + - [Prerequisites](#prerequisites) + - [Setting Up GnoSwap Contracts](#setting-up-gnoswap-contracts) + - [Running Tests](#running-tests) +- [Realms](#realms) + - [Core Realms Deployed on Testnet4](#core-realms-deployed-on-testnet4) + - [Pool](#pool) + - [Position](#position) + - [Router](#router) + - [Staker](#staker) + +## Setting Up and Testing GnoSwap Contracts + +There are two ways to set up GnoSwap contracts: using the provided setup script or manually following the steps below. + +### Prerequisites + +- GNU Make 3.81 or higher +- Latest version of [gno.land](https://github.com/gnolang/gno) +- Python 3.12 or higher + +### Using the Setup Script + +> Note: If you're using the script, you don't need to manually perform the steps listed in the next section. + +For convenience, we provide a Python script that automates the setup process. This script can clone the repository, copy contracts, and move test files as needed. + +- To set up in your home directory without cloning the repository: + + ```bash + python3 setup.py + ``` + +- To set up in a specific directory without cloning: + + ```bash + python3 setup.py -w /path/to/workdir + ``` + +- To clone the repository and set up in your home directory: + + ```bash + python3 setup.py -c + ``` + +- To clone the repository and set up in a specific directory: + + ```bash + python3 setup.py -w /path/to/workdir -c + ``` + +Options: + +- `-w` or `--workdir`: Specify the working directory (default is your home directory) +- `-c` or `--clone`: Clone the gnoswap repository before setting up + +The script will perform all necessary steps to set up the GnoSwap contracts in the specified directory. + +### Setting Up GnoSwap Contracts Manually + +This section guides you through the process of setting up GnoSwap contracts. The process involves three main steps: cloning the `gnoswap` repository, copying the contracts to the `gno` directory, and moving test cases to their respective directories. + +To set up GnoSwap contracts in Gno Core, follow these steps: + +1. Clone the `gnoswap` contracts repository: + + > Tip: If `$WORKDIR` is home directory, then `$WORKDIR` is `~/`. + + ```bash + cd $WORKDIR + git clone https://github.com/gnoswap-labs/gnoswap.git + cd gnoswap + ``` + +2. Copy the `gnoswap` contracts into the cloned `gno` repository: + + ```bash + # make some directory + mkdir -p $WORKDIR/gno/examples/gno.land/r/gnoswap/v1 + mkdir -p $WORKDIR/gno/examples/gno.land/p/gnoswap + + # copy grc20 tokens + cp -R __local/grc20_tokens/* $WORKDIR/gno/examples/gno.land/r/ + + # copy gnoswap base packages ( includes uint256, int256 and bit of pool calculation ) + cp -R _deploy/p/gnoswap/* $WORKDIR/gno/examples/gno.land/p/gnoswap + + # copy gnoswap base realms ( includes common logic, variables and consts ) + cp -R _deploy/r/gnoswap/* $WORKDIR/gno/examples/gno.land/r/gnoswap/v1 + + # copy gnoswap realms + cp -R community_pool emission pool position protocol_fee router staker $WORKDIR/gno/examples/gno.land/r/gnoswap/v1 + ``` + +3. Move all test cases into its own directory: + +Move the test cases for each contract to their respective directories. It's not necessary to move all tests; you can selectively move only the tests you need. However, files containing `VARS_HELPERS` in their name must be moved. + +```bash +cd $WORKDIR/gno/examples/gno.land/r/gnoswap/v1/{name} +mv tests/* . +``` + +For example, to move all tests for the `pool` realm: + +```bash +cd $WORKDIR/gno/examples/gno.land/r/gnoswap/v1/pool +mv tests/* . +``` + +Other realms can be moved in a similar way. + +### Running Tests + +While it's possible to run tests in the cloned `gno` directory (where the above setup process was completed), it's recommended to run them in the `gnoswap` directory to avoid confusion due to the large number of changed files. + +First, navigate to the `gno/examples` directory: + +```bash +cd $WORKDIR/gno/examples +``` + +Next, move to the Realm directory you want to test (such as `pool`, `staker`, etc.), then run the tests using the `gno test` command: + +```bash +gno test -root-dir $WORKDIR/gno -v=false ./gno.land/r/gnoswap/v1/pool +``` + +## Realms + +This section provides information about the core realms of GnoSwap that have been deployed. + +### Core Realms Deployed on Testnet4 + +- pool: [gno.land/r/gnoswap/v1/pool](https://gnoscan.io/realms/details?path=gno.land%2Fr%2Fgnoswap%2Fv2%2Fpool) +- position: [gno.land/r/gnoswap/v1/position](https://gnoscan.io/realms/details?path=gno.land%2Fr%2Fgnoswap%2Fv2%2Fposition) +- router: [gno.land/r/gnoswap/v1/router](https://gnoscan.io/realms/details?path=gno.land%2Fr%2Fgnoswap%2Fv2%2Frouter) +- staker: [gno.land/r/gnoswap/v1/staker](https://gnoscan.io/realms/details?path=gno.land%2Fr%2Fgnoswap%2Fv2%2Fstaker) + +### Pool + +Pool is a core component of GnoSwap, a smart contract that facilitates liquidity provision and trading between two GRC20 tokens. Each pool has a unique token pair, fee tier, and customizable liquidity range, leveraging Uniswap V3's concentrated liquidity mechanism. + +Key features: + +- Composed of two GRC20 tokens. +- Allows liquidity provision within a user-defined, customizable price range. +- Supports various fee tiers, suitable for different trading strategies. +- Dynamically adjusts liquidity according to price fluctuations. + +### Position + +Position is a GRC721 NFT (non-fungible token) representing the liquidity provider's (LP's) unique liquidity position. Each position has the following key functions and properties: + +1. Minting: Users can create a new position by providing liquidity within a specific price range. + +2. Liquidity Increase/Decrease: The liquidity of an existing position can be increased or decreased. + +3. Fee Collection: Trading fees generated by the position can be collected. + +4. Repositioning: The price range of an existing position can be adjusted. + +Each position has unique characteristics, making it non-fungible. Positions store information such as the owner's address, price range (upper and lower ticks), amount of liquidity and accumulated fees. + +Within the same pool, the liquidity of position with overlapping price range is merged within the same tick. This structure allows for more precise control of liquidity provision and better capital efficiency. + +### Router + +The Router in GnoSwap is responsible for executing token swaps and managing swap routes. It provides the following key functionalities: + +1. `SwapRoute`: Executes token swaps based on specified routes and swap types (`EXACT_IN` or `EXACT_OUT`). It handles single and multi-hop swaps, supporting up to 3~7 routes. + +2. `DrySwapRoute`: Simulates swap routes without executing the actual swap. useful for estimating swap outcomes. + +3. Fee Management: Implements a protocol fee for swaps, which can be adjusted by admin or governance. + +4. Support for both native `GNOT` and wrapped `WUGNOT` tokens. + +The Router plays a role by efficiently routing trades and ensuring optimal execution of swaps across various liquidity pools. + +### Staker + +The `Staker` manages LP token staking and reward distribution. It offers the following key functionalities: + +1. Stake Token: Allows users to stake LP tokens, enabling them to earn rewards. + +2. Collect Reward: Enables stakers to collect accumulated rewards from their staked positions. + +3. Unstake Token: Allows users to withdraw their staked LP tokens and collect all accumulated rewards. + +4. Create External Incentive: Permits users to create additional reward incentives for specific liquidity pools. + +5. End External Incentive: Allows the creator or admin to terminate an external incentive and refund remaining rewards. + +Key features of the Staker include: + +- Support for both internal (protocol-native) and external (user-created) incentives. +- Flexible reward distribution mechanisms for different incentive types. +- Safety checks to ensure proper staking, unstaking, and reward collection processes. +- Management of GNS token emissions for internal rewards. +- Handling of native GNOT and wrapped WUGNOT tokens for rewards. + +The staker is a crucial components for GnoSwap's tokenomics by incentivizing liquidity provision and allowing for community-driven reward programs. It enhances the overall ecosystem by promoting long-term liquidity. +>>>>>>> 7fb1cc4 (xxx: reimport the whole codebase as a single commit for PR review) diff --git a/__local/grc20_tokens/g1er355fkjksqpdtwmhf5penwa82p0rhqxkkyhk5/register_gnodev/gno.mod b/__local/grc20_tokens/g1er355fkjksqpdtwmhf5penwa82p0rhqxkkyhk5/register_gnodev/gno.mod new file mode 100644 index 000000000..62b3faf3f --- /dev/null +++ b/__local/grc20_tokens/g1er355fkjksqpdtwmhf5penwa82p0rhqxkkyhk5/register_gnodev/gno.mod @@ -0,0 +1 @@ +module gno.land/r/g1er355fkjksqpdtwmhf5penwa82p0rhqxkkyhk5/v2/register_gnodev \ No newline at end of file diff --git a/__local/grc20_tokens/g1er355fkjksqpdtwmhf5penwa82p0rhqxkkyhk5/register_gnodev/register_gnodev.gno b/__local/grc20_tokens/g1er355fkjksqpdtwmhf5penwa82p0rhqxkkyhk5/register_gnodev/register_gnodev.gno new file mode 100644 index 000000000..1746a401c --- /dev/null +++ b/__local/grc20_tokens/g1er355fkjksqpdtwmhf5penwa82p0rhqxkkyhk5/register_gnodev/register_gnodev.gno @@ -0,0 +1,233 @@ +package register_gnodev + +import ( + pusers "gno.land/p/demo/users" + + "gno.land/r/onbloc/bar" + "gno.land/r/onbloc/baz" + "gno.land/r/onbloc/foo" + + "gno.land/r/onbloc/obl" + "gno.land/r/onbloc/qux" + "gno.land/r/onbloc/usdc" + + "gno.land/r/demo/foo20" + "gno.land/r/demo/wugnot" + + "gno.land/r/gnoswap/v1/gns" + + cp "gno.land/r/gnoswap/v1/community_pool" + gs "gno.land/r/gnoswap/v1/gov/staker" + lp "gno.land/r/gnoswap/v1/launchpad" + pl "gno.land/r/gnoswap/v1/pool" + pf "gno.land/r/gnoswap/v1/protocol_fee" + rr "gno.land/r/gnoswap/v1/router" + sr "gno.land/r/gnoswap/v1/staker" +) + +type FooToken struct{} + +func (FooToken) Transfer() func(to pusers.AddressOrName, amount uint64) { + return foo.Transfer +} +func (FooToken) TransferFrom() func(from, to pusers.AddressOrName, amount uint64) { + return foo.TransferFrom +} +func (FooToken) BalanceOf() func(owner pusers.AddressOrName) uint64 { + return foo.BalanceOf +} +func (FooToken) Approve() func(spender pusers.AddressOrName, amount uint64) { + return foo.Approve +} + +type BarToken struct{} + +func (BarToken) Transfer() func(to pusers.AddressOrName, amount uint64) { + return bar.Transfer +} +func (BarToken) TransferFrom() func(from, to pusers.AddressOrName, amount uint64) { + return bar.TransferFrom +} +func (BarToken) BalanceOf() func(owner pusers.AddressOrName) uint64 { + return bar.BalanceOf +} +func (BarToken) Approve() func(spender pusers.AddressOrName, amount uint64) { + return bar.Approve +} + +type BazToken struct{} + +func (BazToken) Transfer() func(to pusers.AddressOrName, amount uint64) { + return baz.Transfer +} +func (BazToken) TransferFrom() func(from, to pusers.AddressOrName, amount uint64) { + return baz.TransferFrom +} +func (BazToken) BalanceOf() func(owner pusers.AddressOrName) uint64 { + return baz.BalanceOf +} +func (BazToken) Approve() func(spender pusers.AddressOrName, amount uint64) { + return baz.Approve +} + +type QuxToken struct{} + +func (QuxToken) Transfer() func(to pusers.AddressOrName, amount uint64) { + return qux.Transfer +} +func (QuxToken) TransferFrom() func(from, to pusers.AddressOrName, amount uint64) { + return qux.TransferFrom +} +func (QuxToken) BalanceOf() func(owner pusers.AddressOrName) uint64 { + return qux.BalanceOf +} +func (QuxToken) Approve() func(spender pusers.AddressOrName, amount uint64) { + return qux.Approve +} + +type GnsToken struct{} + +func (GnsToken) Transfer() func(to pusers.AddressOrName, amount uint64) { + return gns.Transfer +} +func (GnsToken) TransferFrom() func(from, to pusers.AddressOrName, amount uint64) { + return gns.TransferFrom +} +func (GnsToken) BalanceOf() func(owner pusers.AddressOrName) uint64 { + return gns.BalanceOf +} +func (GnsToken) Approve() func(spender pusers.AddressOrName, amount uint64) { + return gns.Approve +} + +type OblToken struct{} + +func (OblToken) Transfer() func(to pusers.AddressOrName, amount uint64) { + return obl.Transfer +} +func (OblToken) TransferFrom() func(from, to pusers.AddressOrName, amount uint64) { + return obl.TransferFrom +} +func (OblToken) BalanceOf() func(owner pusers.AddressOrName) uint64 { + return obl.BalanceOf +} +func (OblToken) Approve() func(spender pusers.AddressOrName, amount uint64) { + return obl.Approve +} + +type Foo20Token struct{} + +func (Foo20Token) Transfer() func(to pusers.AddressOrName, amount uint64) { + return foo20.Transfer +} +func (Foo20Token) TransferFrom() func(from, to pusers.AddressOrName, amount uint64) { + return foo20.TransferFrom +} +func (Foo20Token) BalanceOf() func(owner pusers.AddressOrName) uint64 { + return foo20.BalanceOf +} +func (Foo20Token) Approve() func(spender pusers.AddressOrName, amount uint64) { + return foo20.Approve +} + +type WugnotToken struct{} + +func (WugnotToken) Transfer() func(to pusers.AddressOrName, amount uint64) { + return wugnot.Transfer +} +func (WugnotToken) TransferFrom() func(from, to pusers.AddressOrName, amount uint64) { + return wugnot.TransferFrom +} +func (WugnotToken) BalanceOf() func(owner pusers.AddressOrName) uint64 { + return wugnot.BalanceOf +} +func (WugnotToken) Approve() func(spender pusers.AddressOrName, amount uint64) { + return wugnot.Approve +} + +type UsdcToken struct{} + +func (UsdcToken) Transfer() func(to pusers.AddressOrName, amount uint64) { + return usdc.Transfer +} +func (UsdcToken) TransferFrom() func(from, to pusers.AddressOrName, amount uint64) { + return usdc.TransferFrom +} +func (UsdcToken) BalanceOf() func(owner pusers.AddressOrName) uint64 { + return usdc.BalanceOf +} +func (UsdcToken) Approve() func(spender pusers.AddressOrName, amount uint64) { + return usdc.Approve +} + +func init() { + pl.RegisterGRC20Interface("gno.land/r/demo/wugnot", WugnotToken{}) + pl.RegisterGRC20Interface("gno.land/r/demo/foo20", Foo20Token{}) + pl.RegisterGRC20Interface("gno.land/r/onbloc/foo", FooToken{}) + pl.RegisterGRC20Interface("gno.land/r/onbloc/bar", BarToken{}) + pl.RegisterGRC20Interface("gno.land/r/onbloc/baz", BazToken{}) + pl.RegisterGRC20Interface("gno.land/r/onbloc/qux", QuxToken{}) + pl.RegisterGRC20Interface("gno.land/r/onbloc/obl", OblToken{}) + pl.RegisterGRC20Interface("gno.land/r/onbloc/usdc", UsdcToken{}) + pl.RegisterGRC20Interface("gno.land/r/gnoswap/v1/gns", GnsToken{}) + + sr.RegisterGRC20Interface("gno.land/r/demo/wugnot", WugnotToken{}) + sr.RegisterGRC20Interface("gno.land/r/demo/foo20", Foo20Token{}) + sr.RegisterGRC20Interface("gno.land/r/onbloc/foo", FooToken{}) + sr.RegisterGRC20Interface("gno.land/r/onbloc/bar", BarToken{}) + sr.RegisterGRC20Interface("gno.land/r/onbloc/baz", BazToken{}) + sr.RegisterGRC20Interface("gno.land/r/onbloc/qux", QuxToken{}) + sr.RegisterGRC20Interface("gno.land/r/onbloc/obl", OblToken{}) + sr.RegisterGRC20Interface("gno.land/r/onbloc/usdc", UsdcToken{}) + sr.RegisterGRC20Interface("gno.land/r/gnoswap/v1/gns", GnsToken{}) + + rr.RegisterGRC20Interface("gno.land/r/demo/wugnot", WugnotToken{}) + rr.RegisterGRC20Interface("gno.land/r/demo/foo20", Foo20Token{}) + rr.RegisterGRC20Interface("gno.land/r/onbloc/foo", FooToken{}) + rr.RegisterGRC20Interface("gno.land/r/onbloc/bar", BarToken{}) + rr.RegisterGRC20Interface("gno.land/r/onbloc/baz", BazToken{}) + rr.RegisterGRC20Interface("gno.land/r/onbloc/qux", QuxToken{}) + rr.RegisterGRC20Interface("gno.land/r/onbloc/obl", OblToken{}) + rr.RegisterGRC20Interface("gno.land/r/onbloc/usdc", UsdcToken{}) + rr.RegisterGRC20Interface("gno.land/r/gnoswap/v1/gns", GnsToken{}) + + pf.RegisterGRC20Interface("gno.land/r/demo/wugnot", WugnotToken{}) + pf.RegisterGRC20Interface("gno.land/r/demo/foo20", Foo20Token{}) + pf.RegisterGRC20Interface("gno.land/r/onbloc/foo", FooToken{}) + pf.RegisterGRC20Interface("gno.land/r/onbloc/bar", BarToken{}) + pf.RegisterGRC20Interface("gno.land/r/onbloc/baz", BazToken{}) + pf.RegisterGRC20Interface("gno.land/r/onbloc/qux", QuxToken{}) + pf.RegisterGRC20Interface("gno.land/r/onbloc/obl", OblToken{}) + pf.RegisterGRC20Interface("gno.land/r/onbloc/usdc", UsdcToken{}) + pf.RegisterGRC20Interface("gno.land/r/gnoswap/v1/gns", GnsToken{}) + + cp.RegisterGRC20Interface("gno.land/r/demo/wugnot", WugnotToken{}) + cp.RegisterGRC20Interface("gno.land/r/demo/foo20", Foo20Token{}) + cp.RegisterGRC20Interface("gno.land/r/onbloc/foo", FooToken{}) + cp.RegisterGRC20Interface("gno.land/r/onbloc/bar", BarToken{}) + cp.RegisterGRC20Interface("gno.land/r/onbloc/baz", BazToken{}) + cp.RegisterGRC20Interface("gno.land/r/onbloc/qux", QuxToken{}) + cp.RegisterGRC20Interface("gno.land/r/onbloc/obl", OblToken{}) + cp.RegisterGRC20Interface("gno.land/r/onbloc/usdc", UsdcToken{}) + cp.RegisterGRC20Interface("gno.land/r/gnoswap/v1/gns", GnsToken{}) + + gs.RegisterGRC20Interface("gno.land/r/demo/wugnot", WugnotToken{}) + gs.RegisterGRC20Interface("gno.land/r/demo/foo20", Foo20Token{}) + gs.RegisterGRC20Interface("gno.land/r/onbloc/foo", FooToken{}) + gs.RegisterGRC20Interface("gno.land/r/onbloc/bar", BarToken{}) + gs.RegisterGRC20Interface("gno.land/r/onbloc/baz", BazToken{}) + gs.RegisterGRC20Interface("gno.land/r/onbloc/qux", QuxToken{}) + gs.RegisterGRC20Interface("gno.land/r/onbloc/obl", OblToken{}) + gs.RegisterGRC20Interface("gno.land/r/onbloc/usdc", UsdcToken{}) + gs.RegisterGRC20Interface("gno.land/r/gnoswap/v1/gns", GnsToken{}) + + lp.RegisterGRC20Interface("gno.land/r/demo/wugnot", WugnotToken{}) + lp.RegisterGRC20Interface("gno.land/r/demo/foo20", Foo20Token{}) + lp.RegisterGRC20Interface("gno.land/r/onbloc/foo", FooToken{}) + lp.RegisterGRC20Interface("gno.land/r/onbloc/bar", BarToken{}) + lp.RegisterGRC20Interface("gno.land/r/onbloc/baz", BazToken{}) + lp.RegisterGRC20Interface("gno.land/r/onbloc/qux", QuxToken{}) + lp.RegisterGRC20Interface("gno.land/r/onbloc/obl", OblToken{}) + lp.RegisterGRC20Interface("gno.land/r/onbloc/usdc", UsdcToken{}) + lp.RegisterGRC20Interface("gno.land/r/gnoswap/v1/gns", GnsToken{}) +} diff --git a/__local/grc20_tokens/grc20reg/approve_transferfrom.txtar b/__local/grc20_tokens/grc20reg/approve_transferfrom.txtar new file mode 100644 index 000000000..ff43ac448 --- /dev/null +++ b/__local/grc20_tokens/grc20reg/approve_transferfrom.txtar @@ -0,0 +1,53 @@ +loadpkg gno.land/p/demo/users + +loadpkg gno.land/r/demo/foo20 +loadpkg gno.land/r/demo/grc20reg + +loadpkg gno.land/r/demo/reg $WORK/reg + +## start a new node +gnoland start + +## faucet +# gnokey maketx call -pkgpath gno.land/r/demo/foo20 -func Faucet -gas-fee 1ugnot -gas-wanted 4000000 -broadcast -chainid=tendermint_test test1 + +## print reg addr +gnokey maketx call -pkgpath gno.land/r/demo/reg -func RelamAddr -gas-fee 1ugnot -gas-wanted 4000000 -broadcast -chainid=tendermint_test test1 +stdout 'g19tlskvga928es8ug2empargp0teul03apzjud9' + +## approve +gnokey maketx call -pkgpath gno.land/r/demo/foo20 -func Approve -args 'g19tlskvga928es8ug2empargp0teul03apzjud9' -args '100' -gas-fee 1ugnot -gas-wanted 4000000 -broadcast -chainid=tendermint_test test1 + +## transfer from +gnokey maketx call -pkgpath gno.land/r/demo/reg -func TransferFromWithReg -gas-fee 1ugnot -gas-wanted 4000000 -broadcast -chainid=tendermint_test test1 +stdout '' + +-- reg/reg.gno -- +package reg + +import ( + "std" + + "gno.land/r/demo/grc20reg" +) + +func RelamAddr() string { + addr := std.CurrentRealm().Addr().String() + return addr +} + +func TransferFromWithReg() { + caller := std.PrevRealm().Addr() + curr := std.CurrentRealm().Addr() + + + // using import + // foo20.TransferFrom(uCaller, uCurr, uint64(100)) + + // using grc20reg + fooTokenGetter := grc20reg.Get("gno.land/r/demo/foo20") + fooToken := fooTokenGetter() + userTeller := fooToken.CallerTeller() + + userTeller.TransferFrom(caller, curr, uint64(100)) +} \ No newline at end of file diff --git a/__local/grc20_tokens/onbloc/bar/bar.gno b/__local/grc20_tokens/onbloc/bar/bar.gno new file mode 100644 index 000000000..d3d998e8a --- /dev/null +++ b/__local/grc20_tokens/onbloc/bar/bar.gno @@ -0,0 +1,85 @@ +package bar + +import ( + "strings" + + "gno.land/p/demo/grc/grc20" + "gno.land/p/demo/ownable" + "gno.land/p/demo/ufmt" + pusers "gno.land/p/demo/users" + + "gno.land/r/demo/grc20reg" + "gno.land/r/demo/users" +) + +var ( + Token, privateLedger = grc20.NewToken("Bar", "BAR", 6) + UserTeller = Token.CallerTeller() + owner = ownable.NewWithAddress("g17290cwvmrapvp869xfnhhawa8sm9edpufzat7d") // ADMIN +) + +func init() { + privateLedger.Mint(owner.Owner(), 100_000_000_000_000) + getter := func() *grc20.Token { return Token } + grc20reg.Register(getter, "") +} + +func TotalSupply() uint64 { + return UserTeller.TotalSupply() +} + +func BalanceOf(owner pusers.AddressOrName) uint64 { + ownerAddr := users.Resolve(owner) + return UserTeller.BalanceOf(ownerAddr) +} + +func Allowance(owner, spender pusers.AddressOrName) uint64 { + ownerAddr := users.Resolve(owner) + spenderAddr := users.Resolve(spender) + return UserTeller.Allowance(ownerAddr, spenderAddr) +} + +func Transfer(to pusers.AddressOrName, amount uint64) { + toAddr := users.Resolve(to) + checkErr(UserTeller.Transfer(toAddr, amount)) +} + +func Approve(spender pusers.AddressOrName, amount uint64) { + spenderAddr := users.Resolve(spender) + checkErr(UserTeller.Approve(spenderAddr, amount)) +} + +func TransferFrom(from, to pusers.AddressOrName, amount uint64) { + fromAddr := users.Resolve(from) + toAddr := users.Resolve(to) + checkErr(UserTeller.TransferFrom(fromAddr, toAddr, amount)) +} + +func Burn(from pusers.AddressOrName, amount uint64) { + owner.AssertCallerIsOwner() + fromAddr := users.Resolve(from) + checkErr(privateLedger.Burn(fromAddr, amount)) +} + +func Render(path string) string { + parts := strings.Split(path, "/") + c := len(parts) + + switch { + case path == "": + return Token.RenderHome() + case c == 2 && parts[0] == "balance": + owner := pusers.AddressOrName(parts[1]) + ownerAddr := users.Resolve(owner) + balance := UserTeller.BalanceOf(ownerAddr) + return ufmt.Sprintf("%d\n", balance) + default: + return "404\n" + } +} + +func checkErr(err error) { + if err != nil { + panic(err) + } +} diff --git a/__local/grc20_tokens/onbloc/bar/gno.mod b/__local/grc20_tokens/onbloc/bar/gno.mod new file mode 100644 index 000000000..3cb44e9bd --- /dev/null +++ b/__local/grc20_tokens/onbloc/bar/gno.mod @@ -0,0 +1 @@ +module gno.land/r/onbloc/bar \ No newline at end of file diff --git a/__local/grc20_tokens/onbloc/baz/baz.gno b/__local/grc20_tokens/onbloc/baz/baz.gno new file mode 100644 index 000000000..c1dbdca1e --- /dev/null +++ b/__local/grc20_tokens/onbloc/baz/baz.gno @@ -0,0 +1,85 @@ +package baz + +import ( + "strings" + + "gno.land/p/demo/grc/grc20" + "gno.land/p/demo/ownable" + "gno.land/p/demo/ufmt" + pusers "gno.land/p/demo/users" + + "gno.land/r/demo/grc20reg" + "gno.land/r/demo/users" +) + +var ( + Token, privateLedger = grc20.NewToken("Baz", "BAZ", 6) + UserTeller = Token.CallerTeller() + owner = ownable.NewWithAddress("g17290cwvmrapvp869xfnhhawa8sm9edpufzat7d") // ADMIN +) + +func init() { + privateLedger.Mint(owner.Owner(), 100_000_000_000_000) + getter := func() *grc20.Token { return Token } + grc20reg.Register(getter, "") +} + +func TotalSupply() uint64 { + return UserTeller.TotalSupply() +} + +func BalanceOf(owner pusers.AddressOrName) uint64 { + ownerAddr := users.Resolve(owner) + return UserTeller.BalanceOf(ownerAddr) +} + +func Allowance(owner, spender pusers.AddressOrName) uint64 { + ownerAddr := users.Resolve(owner) + spenderAddr := users.Resolve(spender) + return UserTeller.Allowance(ownerAddr, spenderAddr) +} + +func Transfer(to pusers.AddressOrName, amount uint64) { + toAddr := users.Resolve(to) + checkErr(UserTeller.Transfer(toAddr, amount)) +} + +func Approve(spender pusers.AddressOrName, amount uint64) { + spenderAddr := users.Resolve(spender) + checkErr(UserTeller.Approve(spenderAddr, amount)) +} + +func TransferFrom(from, to pusers.AddressOrName, amount uint64) { + fromAddr := users.Resolve(from) + toAddr := users.Resolve(to) + checkErr(UserTeller.TransferFrom(fromAddr, toAddr, amount)) +} + +func Burn(from pusers.AddressOrName, amount uint64) { + owner.AssertCallerIsOwner() + fromAddr := users.Resolve(from) + checkErr(privateLedger.Burn(fromAddr, amount)) +} + +func Render(path string) string { + parts := strings.Split(path, "/") + c := len(parts) + + switch { + case path == "": + return Token.RenderHome() + case c == 2 && parts[0] == "balance": + owner := pusers.AddressOrName(parts[1]) + ownerAddr := users.Resolve(owner) + balance := UserTeller.BalanceOf(ownerAddr) + return ufmt.Sprintf("%d\n", balance) + default: + return "404\n" + } +} + +func checkErr(err error) { + if err != nil { + panic(err) + } +} diff --git a/__local/grc20_tokens/onbloc/baz/gno.mod b/__local/grc20_tokens/onbloc/baz/gno.mod new file mode 100644 index 000000000..52e2bfcff --- /dev/null +++ b/__local/grc20_tokens/onbloc/baz/gno.mod @@ -0,0 +1 @@ +module gno.land/r/onbloc/baz \ No newline at end of file diff --git a/__local/grc20_tokens/onbloc/foo/foo.gno b/__local/grc20_tokens/onbloc/foo/foo.gno new file mode 100644 index 000000000..e99ee1d95 --- /dev/null +++ b/__local/grc20_tokens/onbloc/foo/foo.gno @@ -0,0 +1,85 @@ +package foo + +import ( + "strings" + + "gno.land/p/demo/grc/grc20" + "gno.land/p/demo/ownable" + "gno.land/p/demo/ufmt" + pusers "gno.land/p/demo/users" + + "gno.land/r/demo/grc20reg" + "gno.land/r/demo/users" +) + +var ( + Token, privateLedger = grc20.NewToken("Baz", "BAZ", 6) + UserTeller = Token.CallerTeller() + owner = ownable.NewWithAddress("g17290cwvmrapvp869xfnhhawa8sm9edpufzat7d") // ADMIN +) + +func init() { + privateLedger.Mint(owner.Owner(), 100_000_000_000_000) + getter := func() *grc20.Token { return Token } + grc20reg.Register(getter, "") +} + +func TotalSupply() uint64 { + return UserTeller.TotalSupply() +} + +func BalanceOf(owner pusers.AddressOrName) uint64 { + ownerAddr := users.Resolve(owner) + return UserTeller.BalanceOf(ownerAddr) +} + +func Allowance(owner, spender pusers.AddressOrName) uint64 { + ownerAddr := users.Resolve(owner) + spenderAddr := users.Resolve(spender) + return UserTeller.Allowance(ownerAddr, spenderAddr) +} + +func Transfer(to pusers.AddressOrName, amount uint64) { + toAddr := users.Resolve(to) + checkErr(UserTeller.Transfer(toAddr, amount)) +} + +func Approve(spender pusers.AddressOrName, amount uint64) { + spenderAddr := users.Resolve(spender) + checkErr(UserTeller.Approve(spenderAddr, amount)) +} + +func TransferFrom(from, to pusers.AddressOrName, amount uint64) { + fromAddr := users.Resolve(from) + toAddr := users.Resolve(to) + checkErr(UserTeller.TransferFrom(fromAddr, toAddr, amount)) +} + +func Burn(from pusers.AddressOrName, amount uint64) { + owner.AssertCallerIsOwner() + fromAddr := users.Resolve(from) + checkErr(privateLedger.Burn(fromAddr, amount)) +} + +func Render(path string) string { + parts := strings.Split(path, "/") + c := len(parts) + + switch { + case path == "": + return Token.RenderHome() + case c == 2 && parts[0] == "balance": + owner := pusers.AddressOrName(parts[1]) + ownerAddr := users.Resolve(owner) + balance := UserTeller.BalanceOf(ownerAddr) + return ufmt.Sprintf("%d\n", balance) + default: + return "404\n" + } +} + +func checkErr(err error) { + if err != nil { + panic(err) + } +} diff --git a/__local/grc20_tokens/onbloc/foo/gno.mod b/__local/grc20_tokens/onbloc/foo/gno.mod new file mode 100644 index 000000000..d0c856160 --- /dev/null +++ b/__local/grc20_tokens/onbloc/foo/gno.mod @@ -0,0 +1 @@ +module gno.land/r/onbloc/foo \ No newline at end of file diff --git a/__local/grc20_tokens/onbloc/obl/gno.mod b/__local/grc20_tokens/onbloc/obl/gno.mod new file mode 100644 index 000000000..1799581b2 --- /dev/null +++ b/__local/grc20_tokens/onbloc/obl/gno.mod @@ -0,0 +1 @@ +module gno.land/r/onbloc/obl \ No newline at end of file diff --git a/__local/grc20_tokens/onbloc/obl/obl.gno b/__local/grc20_tokens/onbloc/obl/obl.gno new file mode 100644 index 000000000..976cc088a --- /dev/null +++ b/__local/grc20_tokens/onbloc/obl/obl.gno @@ -0,0 +1,85 @@ +package obl + +import ( + "strings" + + "gno.land/p/demo/grc/grc20" + "gno.land/p/demo/ownable" + "gno.land/p/demo/ufmt" + pusers "gno.land/p/demo/users" + + "gno.land/r/demo/grc20reg" + "gno.land/r/demo/users" +) + +var ( + Token, privateLedger = grc20.NewToken("Obl", "OBL", 6) + UserTeller = Token.CallerTeller() + owner = ownable.NewWithAddress("g17290cwvmrapvp869xfnhhawa8sm9edpufzat7d") // ADMIN +) + +func init() { + privateLedger.Mint(owner.Owner(), 100_000_000_000_000) + getter := func() *grc20.Token { return Token } + grc20reg.Register(getter, "") +} + +func TotalSupply() uint64 { + return UserTeller.TotalSupply() +} + +func BalanceOf(owner pusers.AddressOrName) uint64 { + ownerAddr := users.Resolve(owner) + return UserTeller.BalanceOf(ownerAddr) +} + +func Allowance(owner, spender pusers.AddressOrName) uint64 { + ownerAddr := users.Resolve(owner) + spenderAddr := users.Resolve(spender) + return UserTeller.Allowance(ownerAddr, spenderAddr) +} + +func Transfer(to pusers.AddressOrName, amount uint64) { + toAddr := users.Resolve(to) + checkErr(UserTeller.Transfer(toAddr, amount)) +} + +func Approve(spender pusers.AddressOrName, amount uint64) { + spenderAddr := users.Resolve(spender) + checkErr(UserTeller.Approve(spenderAddr, amount)) +} + +func TransferFrom(from, to pusers.AddressOrName, amount uint64) { + fromAddr := users.Resolve(from) + toAddr := users.Resolve(to) + checkErr(UserTeller.TransferFrom(fromAddr, toAddr, amount)) +} + +func Burn(from pusers.AddressOrName, amount uint64) { + owner.AssertCallerIsOwner() + fromAddr := users.Resolve(from) + checkErr(privateLedger.Burn(fromAddr, amount)) +} + +func Render(path string) string { + parts := strings.Split(path, "/") + c := len(parts) + + switch { + case path == "": + return Token.RenderHome() + case c == 2 && parts[0] == "balance": + owner := pusers.AddressOrName(parts[1]) + ownerAddr := users.Resolve(owner) + balance := UserTeller.BalanceOf(ownerAddr) + return ufmt.Sprintf("%d\n", balance) + default: + return "404\n" + } +} + +func checkErr(err error) { + if err != nil { + panic(err) + } +} diff --git a/__local/grc20_tokens/onbloc/qux/gno.mod b/__local/grc20_tokens/onbloc/qux/gno.mod new file mode 100644 index 000000000..3671f03d4 --- /dev/null +++ b/__local/grc20_tokens/onbloc/qux/gno.mod @@ -0,0 +1 @@ +module gno.land/r/onbloc/qux \ No newline at end of file diff --git a/__local/grc20_tokens/onbloc/qux/qux.gno b/__local/grc20_tokens/onbloc/qux/qux.gno new file mode 100644 index 000000000..29aaec1b6 --- /dev/null +++ b/__local/grc20_tokens/onbloc/qux/qux.gno @@ -0,0 +1,85 @@ +package qux + +import ( + "strings" + + "gno.land/p/demo/grc/grc20" + "gno.land/p/demo/ownable" + "gno.land/p/demo/ufmt" + pusers "gno.land/p/demo/users" + + "gno.land/r/demo/grc20reg" + "gno.land/r/demo/users" +) + +var ( + Token, privateLedger = grc20.NewToken("Qux", "QUX", 6) + UserTeller = Token.CallerTeller() + owner = ownable.NewWithAddress("g17290cwvmrapvp869xfnhhawa8sm9edpufzat7d") // ADMIN +) + +func init() { + privateLedger.Mint(owner.Owner(), 100_000_000_000_000) + getter := func() *grc20.Token { return Token } + grc20reg.Register(getter, "") +} + +func TotalSupply() uint64 { + return UserTeller.TotalSupply() +} + +func BalanceOf(owner pusers.AddressOrName) uint64 { + ownerAddr := users.Resolve(owner) + return UserTeller.BalanceOf(ownerAddr) +} + +func Allowance(owner, spender pusers.AddressOrName) uint64 { + ownerAddr := users.Resolve(owner) + spenderAddr := users.Resolve(spender) + return UserTeller.Allowance(ownerAddr, spenderAddr) +} + +func Transfer(to pusers.AddressOrName, amount uint64) { + toAddr := users.Resolve(to) + checkErr(UserTeller.Transfer(toAddr, amount)) +} + +func Approve(spender pusers.AddressOrName, amount uint64) { + spenderAddr := users.Resolve(spender) + checkErr(UserTeller.Approve(spenderAddr, amount)) +} + +func TransferFrom(from, to pusers.AddressOrName, amount uint64) { + fromAddr := users.Resolve(from) + toAddr := users.Resolve(to) + checkErr(UserTeller.TransferFrom(fromAddr, toAddr, amount)) +} + +func Burn(from pusers.AddressOrName, amount uint64) { + owner.AssertCallerIsOwner() + fromAddr := users.Resolve(from) + checkErr(privateLedger.Burn(fromAddr, amount)) +} + +func Render(path string) string { + parts := strings.Split(path, "/") + c := len(parts) + + switch { + case path == "": + return Token.RenderHome() + case c == 2 && parts[0] == "balance": + owner := pusers.AddressOrName(parts[1]) + ownerAddr := users.Resolve(owner) + balance := UserTeller.BalanceOf(ownerAddr) + return ufmt.Sprintf("%d\n", balance) + default: + return "404\n" + } +} + +func checkErr(err error) { + if err != nil { + panic(err) + } +} diff --git a/__local/grc20_tokens/onbloc/usdc/gno.mod b/__local/grc20_tokens/onbloc/usdc/gno.mod new file mode 100644 index 000000000..3d7ccb60c --- /dev/null +++ b/__local/grc20_tokens/onbloc/usdc/gno.mod @@ -0,0 +1 @@ +module gno.land/r/onbloc/usdc \ No newline at end of file diff --git a/__local/grc20_tokens/onbloc/usdc/usdc.gno b/__local/grc20_tokens/onbloc/usdc/usdc.gno new file mode 100644 index 000000000..c0469bd1f --- /dev/null +++ b/__local/grc20_tokens/onbloc/usdc/usdc.gno @@ -0,0 +1,85 @@ +package usdc + +import ( + "strings" + + "gno.land/p/demo/grc/grc20" + "gno.land/p/demo/ownable" + "gno.land/p/demo/ufmt" + pusers "gno.land/p/demo/users" + + "gno.land/r/demo/grc20reg" + "gno.land/r/demo/users" +) + +var ( + Token, privateLedger = grc20.NewToken("Usd Coin", "USDC", 6) + UserTeller = Token.CallerTeller() + owner = ownable.NewWithAddress("g17290cwvmrapvp869xfnhhawa8sm9edpufzat7d") // ADMIN +) + +func init() { + privateLedger.Mint(owner.Owner(), 100_000_000_000_000) + getter := func() *grc20.Token { return Token } + grc20reg.Register(getter, "") +} + +func TotalSupply() uint64 { + return UserTeller.TotalSupply() +} + +func BalanceOf(owner pusers.AddressOrName) uint64 { + ownerAddr := users.Resolve(owner) + return UserTeller.BalanceOf(ownerAddr) +} + +func Allowance(owner, spender pusers.AddressOrName) uint64 { + ownerAddr := users.Resolve(owner) + spenderAddr := users.Resolve(spender) + return UserTeller.Allowance(ownerAddr, spenderAddr) +} + +func Transfer(to pusers.AddressOrName, amount uint64) { + toAddr := users.Resolve(to) + checkErr(UserTeller.Transfer(toAddr, amount)) +} + +func Approve(spender pusers.AddressOrName, amount uint64) { + spenderAddr := users.Resolve(spender) + checkErr(UserTeller.Approve(spenderAddr, amount)) +} + +func TransferFrom(from, to pusers.AddressOrName, amount uint64) { + fromAddr := users.Resolve(from) + toAddr := users.Resolve(to) + checkErr(UserTeller.TransferFrom(fromAddr, toAddr, amount)) +} + +func Burn(from pusers.AddressOrName, amount uint64) { + owner.AssertCallerIsOwner() + fromAddr := users.Resolve(from) + checkErr(privateLedger.Burn(fromAddr, amount)) +} + +func Render(path string) string { + parts := strings.Split(path, "/") + c := len(parts) + + switch { + case path == "": + return Token.RenderHome() + case c == 2 && parts[0] == "balance": + owner := pusers.AddressOrName(parts[1]) + ownerAddr := users.Resolve(owner) + balance := UserTeller.BalanceOf(ownerAddr) + return ufmt.Sprintf("%d\n", balance) + default: + return "404\n" + } +} + +func checkErr(err error) { + if err != nil { + panic(err) + } +} diff --git a/__local/test/all_test_data.mk b/__local/test/all_test_data.mk new file mode 100644 index 000000000..5090b81e4 --- /dev/null +++ b/__local/test/all_test_data.mk @@ -0,0 +1,713 @@ +# make -f __local/test/test_data.mk init init-test + +ADDR_GSA := g17290cwvmrapvp869xfnhhawa8sm9edpufzat7d + +ADDR_LP01 := g1qf5863trkaq447zr2xdmql83g0twzl37dm9qqt +ADDR_LP02 := g1ta0w7j4f586kwqu584z5h5sjurzywz3na7qg0a +ADDR_TR01 := g14m6fj3t8005u77ku6zyzazq9vd9hwhl00ppt8j + +ADDR_REGISTER := g1er355fkjksqpdtwmhf5penwa82p0rhqxkkyhk5 + + +ADDR_POOL := g148tjamj80yyrm309z7rk690an22thd2l3z8ank +ADDR_POSITION := g1q646ctzhvn60v492x8ucvyqnrj2w30cwh6efk5 +ADDR_ROUTER := g1lm2l7tf49h3mykesct7rhfml30yx8dw5xrval7 +ADDR_STAKER := g1cceshmzzlmrh7rr3z30j2t5mrvsq9yccysw9nu +ADDR_PROTOCOL_FEE := g1f7wpek7q67tkns27sw495u5yuu3a5wwjxw5l6l + +ADDR_GNS := g1jgqwaa2le3yr63d533fj785qkjspumzv22ys5m +ADDR_GNFT := g1wxv2rdfn53qc84nt3nn646f9yh3nly8lm7j89t + +ADDR_WUGNOT := g1pf6dv9fjk3rn0m4jjcne306ga4he3mzmupfjl6 + + +MAX_UINT64 := 18446744073709551615 +TX_EXPIRE := 9999999999 + +# INCENTIVE_START +# TOMORROW_MIDNIGHT := $(shell date +%s) # DEV +TOMORROW_MIDNIGHT := $(shell (gdate -ud 'tomorrow 00:00:00' +%s)) +INCENTIVE_END := $(shell expr $(TOMORROW_MIDNIGHT) + 7776000) # 7776000 SECONDS = 90 DAY + + +MAKEFILE := $(shell realpath $(firstword $(MAKEFILE_LIST))) + +GNOLAND_RPC_URL ?= http://localhost:26657 +CHAINID ?= dev + +# GNOLAND_RPC_URL ?= https://dev.rpc.gnoswap.io:443 +# CHAINID ?= dev.gnoswap + +ROOT_DIR:=$(shell dirname $(MAKEFILE))/../../ + + +## INIT +.PHONY: init +init: wait send-ugnot-must deploy-libraries deploy-base-tokens deploy-gnoswap-realms deploy-test-tokens register-token pool-create-gns-wugnot-default + +.PHONY: deploy-libraries +deploy-libraries: deploy-uint256 deploy-int256 deploy-consts deploy-package-pool deploy-common + +.PHONY: deploy-base-tokens +deploy-base-tokens: deploy-gns deploy-usdc deploy-gnft + +.PHONY: deploy-test-tokens +deploy-test-tokens: deploy-foo deploy-bar deploy-baz deploy-qux deploy-obl + +.PHONY: deploy-gnoswap-realms +deploy-gnoswap-realms: deploy-xgns deploy-emission deploy-pool deploy-position deploy-staker deploy-router deploy-community_pool deploy-protocol_fee deploy-gov-staker deploy-gov-governance + + +### TEST AFTER INIT +.PHONY: init-test +init-test: test-send-ugnot test-grc20-transfer test-pool-create test-position-mint test-increase-decrease test-create-external-incentive test-stake-token test-swap # test-collect-fee test-unstake-token test-burn-position + +.PHONY: test-grc20-transfer +test-grc20-transfer: transfer-foo transfer-bar transfer-baz transfer-qux transfer-obl + +.PHONY: test-pool-create +test-pool-create: pool-create-bar-baz pool-create-baz-qux pool-create-qux-foo pool-create-foo-gns pool-create-gns-wugnot + +.PHONY: test-position-mint +test-position-mint: mint-bar-baz mint-baz-qux mint-qux-foo mint-gns-gnot mint-gns-foo + +.PHONY: test-increase-decrease +test-increase-decrease: increase-liquidity-position-01 decrease-liquidity-position-01 + +.PHONY: test-create-external +test-create-external-incentive: create-external-incentive create-external-incentive-2 create-external-incentive-3 + +.PHONY: test-stake-token +test-stake-token: stake-token-8 stake-token-9 mint-and-stake + +.PHONY: test-swap +test-swap: swap-exact-in-single-bar-to-baz swap-exact-in-single-baz-to-bar + + +# .PHONY: test-collect-fee +# test-collect-fee: collect-fee-position-1 + +# .PHONY: test-unstake-token +# test-unstake-token: unstake-token-1-5 unstake-token-6 unstake-token-7 unstake-token-8 + +# .PHONY: test-burn-position +# test-burn-position: burn-position-1 burn-position-2 burn-position-6 burn-position-7 + + + +# wait chain to start +wait: + $(info ************ [ETC] wait 1 seconds for chain to start ************) + $(shell sleep 1) + @echo + + +# send ugnot to necessary accounts +send-ugnot-must: + $(info ************ send ugnot to necessary accounts ************) + @echo "" | gnokey maketx send -send 10000000000ugnot -to $(ADDR_GSA) -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" test1 > /dev/null + @echo "" | gnokey maketx send -send 10000000000ugnot -to $(ADDR_PROTOCOL_FEE) -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" test1 > /dev/null + @echo "" | gnokey maketx send -send 10000000000ugnot -to $(ADDR_REGISTER) -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" test1 > /dev/null + @echo + + +# send ugnot to test accounts +test-send-ugnot: + $(info ************ send ugnot to test accounts ************) + @echo "" | gnokey maketx send -send 10000000000ugnot -to $(ADDR_LP01) -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" test1 > /dev/null + @echo "" | gnokey maketx send -send 10000000000ugnot -to $(ADDR_LP02) -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" test1 > /dev/null + @echo "" | gnokey maketx send -send 10000000000ugnot -to $(ADDR_TR01) -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" test1 > /dev/null + @echo + + +# deploy test grc20 tokens +deploy-foo: + $(info ************ deploy foo ************) + @echo "" | gnokey maketx addpkg -pkgdir $(ROOT_DIR)/__local/grc20_tokens/onbloc/foo -pkgpath gno.land/r/onbloc/foo -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin > /dev/null + @echo + +deploy-bar: + $(info ************ deploy bar ************) + @echo "" | gnokey maketx addpkg -pkgdir $(ROOT_DIR)/__local/grc20_tokens/onbloc/bar -pkgpath gno.land/r/onbloc/bar -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin > /dev/null + @echo + +deploy-baz: + $(info ************ deploy baz ************) + @echo "" | gnokey maketx addpkg -pkgdir $(ROOT_DIR)/__local/grc20_tokens/onbloc/baz -pkgpath gno.land/r/onbloc/baz -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin > /dev/null + @echo + +deploy-qux: + $(info ************ deploy qux ************) + @echo "" | gnokey maketx addpkg -pkgdir $(ROOT_DIR)/__local/grc20_tokens/onbloc/qux -pkgpath gno.land/r/onbloc/qux -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin > /dev/null + @echo + +deploy-obl: + $(info ************ deploy obl ************) + @echo "" | gnokey maketx addpkg -pkgdir $(ROOT_DIR)/__local/grc20_tokens/onbloc/obl -pkgpath gno.land/r/onbloc/obl -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin > /dev/null + @echo + + +# deploy base tokens +deploy-gns: + $(info ************ deploy gns ************) + @echo "" | gnokey maketx addpkg -pkgdir $(ROOT_DIR)/_deploy/r/gnoswap/gns -pkgpath gno.land/r/gnoswap/v1/gns -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin > /dev/null + @echo + +deploy-gnft: + $(info ************ deploy gnft ************) + @echo "" | gnokey maketx addpkg -pkgdir $(ROOT_DIR)/_deploy/r/gnoswap/gnft -pkgpath gno.land/r/gnoswap/v1/gnft -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin > /dev/null + @echo + +deploy-usdc: + $(info ************ deploy usdc ************) + @echo "" | gnokey maketx addpkg -pkgdir $(ROOT_DIR)/__local/grc20_tokens/onbloc/usdc -pkgpath gno.land/r/onbloc/usdc -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin > /dev/null + @echo + + +# deploy packages +deploy-uint256: + $(info ************ deploy uint256 ************) + @echo "" | gnokey maketx addpkg -pkgdir $(ROOT_DIR)/_deploy/p/gnoswap/uint256 -pkgpath gno.land/p/gnoswap/uint256 -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin > /dev/null + @echo + +deploy-int256: + $(info ************ deploy int256 ************) + @echo "" | gnokey maketx addpkg -pkgdir $(ROOT_DIR)/_deploy/p/gnoswap/int256 -pkgpath gno.land/p/gnoswap/int256 -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin > /dev/null + @echo + +deploy-package-pool: + $(info ************ deploy package pool ************) + @echo "" | gnokey maketx addpkg -pkgdir $(ROOT_DIR)/_deploy/p/gnoswap/pool -pkgpath gno.land/p/gnoswap/pool -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin > /dev/null + @echo + + +# deploy common realms +deploy-consts: + $(info ************ deploy consts ************) + @echo "" | gnokey maketx addpkg -pkgdir $(ROOT_DIR)/_deploy/r/gnoswap/consts -pkgpath gno.land/r/gnoswap/v1/consts -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin > /dev/null + @echo + +deploy-common: + $(info ************ deploy common ************) + @echo "" | gnokey maketx addpkg -pkgdir $(ROOT_DIR)/_deploy/r/gnoswap/common -pkgpath gno.land/r/gnoswap/v1/common -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin > /dev/null + @echo + + +# deploy gnoswap realms +deploy-xgns: + $(info ************ deploy xgns ************) + @echo "" | gnokey maketx addpkg -pkgdir $(ROOT_DIR)/gov/xgns -pkgpath gno.land/r/gnoswap/v1/gov/xgns -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin > /dev/null + @echo + +deploy-emission: + $(info ************ deploy emission ************) + @echo "" | gnokey maketx addpkg -pkgdir $(ROOT_DIR)/emission -pkgpath gno.land/r/gnoswap/v1/emission -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin > /dev/null + @echo + +deploy-pool: + $(info ************ deploy pool ************) + @echo "" | gnokey maketx addpkg -pkgdir $(ROOT_DIR)/pool -pkgpath gno.land/r/gnoswap/v1/pool -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin > /dev/null + @echo + +deploy-position: + $(info ************ deploy position ************) + @echo "" | gnokey maketx addpkg -pkgdir $(ROOT_DIR)/position -pkgpath gno.land/r/gnoswap/v1/position -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin > /dev/null + @echo + +deploy-router: + $(info ************ deploy router ************) + @echo "" | gnokey maketx addpkg -pkgdir $(ROOT_DIR)/router -pkgpath gno.land/r/gnoswap/v1/router -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin > /dev/null + @echo + +deploy-staker: + $(info ************ deploy staker ************) + @echo "" | gnokey maketx addpkg -pkgdir $(ROOT_DIR)/staker -pkgpath gno.land/r/gnoswap/v1/staker -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin > /dev/null + @echo + +deploy-community_pool: + $(info ************ deploy community_pool ************) + @echo "" | gnokey maketx addpkg -pkgdir $(ROOT_DIR)/community_pool -pkgpath gno.land/r/gnoswap/v1/community_pool -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin > /dev/null + @echo + +deploy-protocol_fee: + $(info ************ deploy protocol_fee ************) + @echo "" | gnokey maketx addpkg -pkgdir $(ROOT_DIR)/protocol_fee -pkgpath gno.land/r/gnoswap/v1/protocol_fee -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin > /dev/null + @echo + +deploy-gov-staker: + $(info ************ deploy gov/staker ************) + @echo "" | gnokey maketx addpkg -pkgdir $(ROOT_DIR)/gov/staker -pkgpath gno.land/r/gnoswap/v1/gov/staker -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin > /dev/null + @echo + +deploy-gov-governance: + $(info ************ deploy gov/governance ************) + @echo "" | gnokey maketx addpkg -pkgdir $(ROOT_DIR)/gov/governance -pkgpath gno.land/r/gnoswap/v1/gov/governance -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin > /dev/null + @echo + + +# Register +register-token: + $(info ************ deploy register_gnodev ************) + @echo "" | gnokey maketx addpkg -pkgdir $(ROOT_DIR)/__local/grc20_tokens/g1er355fkjksqpdtwmhf5penwa82p0rhqxkkyhk5/register_gnodev -pkgpath gno.land/r/g1er355fkjksqpdtwmhf5penwa82p0rhqxkkyhk5/v2/register_gnodev -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" register > /dev/null + @echo + + +# transfer grc20s +transfer-foo: + $(info ************ transfer foo ************) + @echo "" | gnokey maketx call -pkgpath gno.land/r/onbloc/foo -func Transfer -args $(ADDR_LP01) -args "1000000000" -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin > /dev/null + @echo "" | gnokey maketx call -pkgpath gno.land/r/onbloc/foo -func Transfer -args $(ADDR_LP02) -args "1000000000" -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin > /dev/null + @echo "" | gnokey maketx call -pkgpath gno.land/r/onbloc/foo -func Transfer -args $(ADDR_TR01) -args "1000000000" -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin > /dev/null + @echo + +transfer-bar: + $(info ************ transfer bar ************) + @echo "" | gnokey maketx call -pkgpath gno.land/r/onbloc/bar -func Transfer -args $(ADDR_LP01) -args "1000000000" -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin > /dev/null + @echo "" | gnokey maketx call -pkgpath gno.land/r/onbloc/bar -func Transfer -args $(ADDR_LP02) -args "1000000000" -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin > /dev/null + @echo "" | gnokey maketx call -pkgpath gno.land/r/onbloc/bar -func Transfer -args $(ADDR_TR01) -args "1000000000" -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin > /dev/null + @echo + +transfer-baz: + $(info ************ transfer baz ************) + @echo "" | gnokey maketx call -pkgpath gno.land/r/onbloc/baz -func Transfer -args $(ADDR_LP01) -args "1000000000" -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin > /dev/null + @echo "" | gnokey maketx call -pkgpath gno.land/r/onbloc/baz -func Transfer -args $(ADDR_LP02) -args "1000000000" -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin > /dev/null + @echo "" | gnokey maketx call -pkgpath gno.land/r/onbloc/baz -func Transfer -args $(ADDR_TR01) -args "1000000000" -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin > /dev/null + @echo + +transfer-qux: + $(info ************ transfer qux ************) + @echo "" | gnokey maketx call -pkgpath gno.land/r/onbloc/qux -func Transfer -args $(ADDR_LP01) -args "1000000000" -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin > /dev/null + @echo "" | gnokey maketx call -pkgpath gno.land/r/onbloc/qux -func Transfer -args $(ADDR_LP02) -args "1000000000" -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin > /dev/null + @echo "" | gnokey maketx call -pkgpath gno.land/r/onbloc/qux -func Transfer -args $(ADDR_TR01) -args "1000000000" -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin > /dev/null + @echo + +transfer-obl: + $(info ************ transfer obl ************) + @echo "" | gnokey maketx call -pkgpath gno.land/r/onbloc/obl -func Transfer -args $(ADDR_LP01) -args "1000000000" -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin > /dev/null + @echo "" | gnokey maketx call -pkgpath gno.land/r/onbloc/obl -func Transfer -args $(ADDR_LP02) -args "1000000000" -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin > /dev/null + @echo "" | gnokey maketx call -pkgpath gno.land/r/onbloc/obl -func Transfer -args $(ADDR_TR01) -args "1000000000" -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin > /dev/null + @echo + + +# default pool create +pool-create-gns-wugnot-default: + $(info ************ set pool creation fee to 0uGNS for testing ************) + @echo "" | gnokey maketx call -pkgpath gno.land/r/gnoswap/v1/pool -func SetPoolCreationFee -args 0 -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin > /dev/null + @echo + + + $(info ************ create default pool (GNS:WUGNOT:0.03%) ************) + # tick 0 ≈ x1 ≈ 79228162514264337593543950337 + @echo "" | gnokey maketx call -pkgpath gno.land/r/gnoswap/v1/pool -func CreatePool -args "gno.land/r/demo/wugnot" -args "gno.land/r/gnoswap/v1/gns" -args 3000 -args 79228162514264337593543950337 -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin > /dev/null + @echo + + +# test pool create +pool-create-bar-baz: + $(info ************ create pool bar:baz ************) + # tick -10 ≈ x0.99900054978007157835406815138412639498710632324219 ≈ 79188560314459151373725315960 + @echo "" | gnokey maketx call -pkgpath gno.land/r/gnoswap/v1/pool -func CreatePool -args "gno.land/r/onbloc/bar" -args "gno.land/r/onbloc/baz" -args 100 -args 79188560314459151373725315960 -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin > /dev/null + + # tick +10 ≈ x1.00100045012002092370551054045790806412696838378906 ≈ 79267784519130042428790663799 + @echo "" | gnokey maketx call -pkgpath gno.land/r/gnoswap/v1/pool -func CreatePool -args "gno.land/r/onbloc/bar" -args "gno.land/r/onbloc/baz" -args 500 -args 79267784519130042428790663799 -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin > /dev/null + + # tick 46055 ≈ x100.00995593181238518809550441801548004150390625000000 ≈ 792321063670230269303669868814 + @echo "" | gnokey maketx call -pkgpath gno.land/r/gnoswap/v1/pool -func CreatePool -args "gno.land/r/onbloc/bar" -args "gno.land/r/onbloc/baz" -args 3000 -args 792321063670230269303669868814 -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin > /dev/null + @echo + +pool-create-baz-qux: + $(info ************ create pool baz:qux ************) + # tick 23028 ≈ x10.00099779659037757539863378042355179786682128906250 ≈ 250553947533412109193337304115 + @echo "" | gnokey maketx call -pkgpath gno.land/r/gnoswap/v1/pool -func CreatePool -args "gno.land/r/onbloc/baz" -args "gno.land/r/onbloc/qux" -args 500 -args 250553947533412109193337304115 -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin > /dev/null + @echo + +pool-create-qux-foo: + $(info ************ create pool qux:foo ************) + # tick 6932 ≈ x2.00003632383094753777186269871890544891357421875000 ≈ 112046559425783515914356180039 + # tick -6932 ≈ x0.49999091920718774506582349204109050333499908447266 ≈ 56022262241300288188759753413 + @echo "" | gnokey maketx call -pkgpath gno.land/r/gnoswap/v1/pool -func CreatePool -args "gno.land/r/onbloc/foo" -args "gno.land/r/onbloc/qux" -args 500 -args 56022262241300288188759753413 -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin > /dev/null + @echo + +pool-create-foo-gns: + $(info ************ create pool foo:gns ************) + # tick 0 ≈ x1 ≈ 79228162514264337593543950337 + @echo "" | gnokey maketx call -pkgpath gno.land/r/gnoswap/v1/pool -func CreatePool -args "gno.land/r/gnoswap/v1/gns" -args "gno.land/r/onbloc/foo" -args 500 -args 79228162514264337593543950337 -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin > /dev/null + @echo + +pool-create-gns-wugnot: + $(info ************ create pool gns:wugnot ************) + # tick 10 ≈ x1.00100045012002092370551054045790806412696838378906 ≈ 79267784519130042428790663799 + # tick -10 ≈ x0.99900054978007157835406815138412639498710632324219 ≈ 79188560314459151373725315960 + @echo "" | gnokey maketx call -pkgpath gno.land/r/gnoswap/v1/pool -func CreatePool -args "gnot" -args "gno.land/r/gnoswap/v1/gns" -args 100 -args 79267784519130042428790663799 -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin > /dev/null + @echo + + +## test mint position +mint-bar-baz: + $(info ************ mint positions(1~5) to bar:baz // gnoswap_lp01 ************) + + # APPROVE FISRT + @echo "" | gnokey maketx call -pkgpath gno.land/r/onbloc/bar -func Approve -args $(ADDR_POOL) -args $(MAX_UINT64) -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_lp01 > /dev/null + @echo "" | gnokey maketx call -pkgpath gno.land/r/onbloc/baz -func Approve -args $(ADDR_POOL) -args $(MAX_UINT64) -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_lp01 > /dev/null + @echo + + # THEN MINT + @echo "" | gnokey maketx call -pkgpath gno.land/r/gnoswap/v1/position -func Mint -args "gno.land/r/onbloc/bar" -args "gno.land/r/onbloc/baz" -args 100 -args "-20" -args 0 -args 20000000 -args 20000000 -args 0 -args 0 -args $(TX_EXPIRE) -args $(ADDR_LP01) -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_lp01 > /dev/null + @echo "" | gnokey maketx call -pkgpath gno.land/r/gnoswap/v1/position -func Mint -args "gno.land/r/onbloc/bar" -args "gno.land/r/onbloc/baz" -args 100 -args 0 -args 10 -args 20000000 -args 20000000 -args 0 -args 0 -args $(TX_EXPIRE) -args $(ADDR_LP01) -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_lp01 > /dev/null + @echo "" | gnokey maketx call -pkgpath gno.land/r/gnoswap/v1/position -func Mint -args "gno.land/r/onbloc/bar" -args "gno.land/r/onbloc/baz" -args 100 -args "-30" -args "-20" -args 20000000 -args 20000000 -args 0 -args 0 -args $(TX_EXPIRE) -args $(ADDR_LP01) -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_lp01 > /dev/null + + @echo "" | gnokey maketx call -pkgpath gno.land/r/gnoswap/v1/position -func Mint -args "gno.land/r/onbloc/bar" -args "gno.land/r/onbloc/baz" -args 500 -args 0 -args 20 -args 20000000 -args 20000000 -args 0 -args 0 -args $(TX_EXPIRE) -args $(ADDR_LP01) -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_lp01 > /dev/null + + @echo "" | gnokey maketx call -pkgpath gno.land/r/gnoswap/v1/position -func Mint -args "gno.land/r/onbloc/bar" -args "gno.land/r/onbloc/baz" -args 3000 -args 36060 -args 56040 -args 100 -args 100 -args 0 -args 0 -args $(TX_EXPIRE) -args $(ADDR_LP01) -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_lp01 > /dev/null + @echo + + @echo "" | gnokey maketx call -pkgpath gno.land/r/gnoswap/v1/gnft -func SetTokenURILast -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_lp01 > /dev/null + @echo + +mint-baz-qux: + $(info ************ mint position(6) to baz:qux // gnoswap_lp02 ************) + + # APPROVE FISRT + @echo "" | gnokey maketx call -pkgpath gno.land/r/onbloc/baz -func Approve -args $(ADDR_POOL) -args $(MAX_UINT64) -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_lp02 > /dev/null + @echo "" | gnokey maketx call -pkgpath gno.land/r/onbloc/qux -func Approve -args $(ADDR_POOL) -args $(MAX_UINT64) -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_lp02 > /dev/null + @echo + + # THEN MINT + @echo "" | gnokey maketx call -pkgpath gno.land/r/gnoswap/v1/position -func Mint -args "gno.land/r/onbloc/baz" -args "gno.land/r/onbloc/qux" -args 500 -args 13030 -args 33030 -args 20000000 -args 20000000 -args 0 -args 0 -args $(TX_EXPIRE) -args $(ADDR_LP02) -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_lp02 > /dev/null + @echo + + @echo "" | gnokey maketx call -pkgpath gno.land/r/gnoswap/v1/gnft -func SetTokenURILast -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_lp02 > /dev/null + @echo + +mint-qux-foo: + $(info ************ mint position(7) to qux:foo // gnoswap_lp01 ************) + + # APPROVE FISRT + @echo "" | gnokey maketx call -pkgpath gno.land/r/onbloc/qux -func Approve -args $(ADDR_POOL) -args $(MAX_UINT64) -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_lp01 > /dev/null + @echo "" | gnokey maketx call -pkgpath gno.land/r/onbloc/foo -func Approve -args $(ADDR_POOL) -args $(MAX_UINT64) -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_lp01 > /dev/null + @echo + + # THEN MINT + @echo "" | gnokey maketx call -pkgpath gno.land/r/gnoswap/v1/position -func Mint -args "gno.land/r/onbloc/qux" -args "gno.land/r/onbloc/foo" -args 500 -args 5930 -args 7930 -args 20000000 -args 20000000 -args 0 -args 0 -args $(TX_EXPIRE) -args $(ADDR_LP01) -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_lp01 > /dev/null + @echo + + @echo "" | gnokey maketx call -pkgpath gno.land/r/gnoswap/v1/gnft -func SetTokenURILast -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_lp01 > /dev/null + @echo + + +mint-gns-gnot: + $(info ************ mint position(8) to gns:wugnot // gnoswap_admin ************) + # APPROVE FISRT + @echo "" | gnokey maketx call -pkgpath gno.land/r/gnoswap/v1/gns -func Approve -args $(ADDR_POOL) -args $(MAX_UINT64) -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin > /dev/null + @echo "" | gnokey maketx call -pkgpath gno.land/r/demo/wugnot -func Approve -args $(ADDR_POOL) -args $(MAX_UINT64) -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin > /dev/null + @echo + + # APPROVE WUGNOT TO POSITION, to get refund wugnot left after wrap -> mint + @echo "" | gnokey maketx call -pkgpath gno.land/r/demo/wugnot -func Approve -args $(ADDR_POSITION) -args $(MAX_UINT64) -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin > /dev/null + + # THEN MINT + @echo "" | gnokey maketx call -pkgpath gno.land/r/gnoswap/v1/position -func Mint -send "20000000ugnot" -args "gno.land/r/gnoswap/v1/gns" -args "gnot" -args 3000 -args "-49980" -args "49980" -args 20000000 -args 20000000 -args 1 -args 1 -args $(TX_EXPIRE) -args $(ADDR_GSA) -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin > /dev/null + @echo + + @echo "" | gnokey maketx call -pkgpath gno.land/r/gnoswap/v1/gnft -func SetTokenURILast -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin > /dev/null + @echo + + +mint-gns-foo: + $(info ************ mint position(9) to gns:foo // gnoswap_admin ************) + # APPROVE FISRT + @echo "" | gnokey maketx call -pkgpath gno.land/r/gnoswap/v1/gns -func Approve -args $(ADDR_POOL) -args $(MAX_UINT64) -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin > /dev/null + @echo "" | gnokey maketx call -pkgpath gno.land/r/onbloc/foo -func Approve -args $(ADDR_POOL) -args $(MAX_UINT64) -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin > /dev/null + @echo + + # THEN MINT + @echo "" | gnokey maketx call -pkgpath gno.land/r/gnoswap/v1/position -func Mint -args "gno.land/r/gnoswap/v1/gns" -args "gno.land/r/onbloc/foo" -args 500 -args -6000 -args 6000 -args 20000000 -args 20000000 -args 0 -args 0 -args $(TX_EXPIRE) -args $(ADDR_GSA) -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin > /dev/null + @echo + + @echo "" | gnokey maketx call -pkgpath gno.land/r/gnoswap/v1/gnft -func SetTokenURILast -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin > /dev/null + @echo + + + +# test increase - decrease +increase-liquidity-position-01: + $(info ************ increase position(1) liquidity bar:baz:100 // gnoswap_lp01 ************) + @echo "" | gnokey maketx call -pkgpath gno.land/r/gnoswap/v1/position -func IncreaseLiquidity -args 1 -args 20000000 -args 20000000 -args 1 -args 1 -args $(TX_EXPIRE) -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_lp01 > /dev/null + @echo + +decrease-liquidity-position-01: + $(info ************ decrease position(1) liquidity bar:baz:100 // gnoswap_lp01 ************) + @echo "" | gnokey maketx call -pkgpath gno.land/r/gnoswap/v1/position -func DecreaseLiquidity -args 1 -args 10 -args 0 -args 0 -args $(TX_EXPIRE) -args "false" -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 20000000 -memo "" gnoswap_lp01 > /dev/null + @echo + + +## test create external incentive +create-external-incentive: + $(info ************ create external incentive [foo] => gns:foo:500 // gnoswap_admin ************) + # APPROVE REWARD + @echo "" | gnokey maketx call -pkgpath gno.land/r/onbloc/foo -func Approve -args $(ADDR_STAKER) -args $(MAX_UINT64) -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 9000000 -memo "" gnoswap_admin > /dev/null + @echo + + ## APROVE GNS (DEPOSIT) + @echo "" | gnokey maketx call -pkgpath gno.land/r/gnoswap/v1/gns -func Approve -args $(ADDR_STAKER) -args $(MAX_UINT64) -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 9000000 -memo "" gnoswap_admin > /dev/null + @echo + + # THEN CREATE EXTERNAL INCENTIVE + @echo "" | gnokey maketx call -pkgpath gno.land/r/gnoswap/v1/staker -func CreateExternalIncentive -args "gno.land/r/gnoswap/v1/gns:gno.land/r/onbloc/foo:500" -args "gno.land/r/onbloc/foo" -args 1000000000 -args $(TOMORROW_MIDNIGHT) -args $(INCENTIVE_END) -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin > /dev/null + @echo + + +create-external-incentive-2: + $(info ************ create external incentive [gns] => gns:foo:500 // gnoswap_admin ************) + # APPROVE REWARD ( and DEPOSIT ) + @echo "" | gnokey maketx call -pkgpath gno.land/r/gnoswap/v1/gns -func Approve -args $(ADDR_STAKER) -args $(MAX_UINT64) -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 9000000 -memo "" gnoswap_admin > /dev/null + @echo + + # THEN CREATE EXTERNAL INCENTIVE + @echo "" | gnokey maketx call -pkgpath gno.land/r/gnoswap/v1/staker -func CreateExternalIncentive -args "gno.land/r/gnoswap/v1/gns:gno.land/r/onbloc/foo:500" -args "gno.land/r/gnoswap/v1/gns" -args 1000000000 -args $(TOMORROW_MIDNIGHT) -args $(INCENTIVE_END) -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin > /dev/null + @echo + + +create-external-incentive-3: + $(info ************ create external incentive [foo] => gns:foo:500 // gnoswap_lp01 ************) + # APPROVE REWARD + @echo "" | gnokey maketx call -pkgpath gno.land/r/onbloc/foo -func Approve -args $(ADDR_STAKER) -args $(MAX_UINT64) -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 9000000 -memo "" gnoswap_lp01 > /dev/null + @echo + + ## TRANSFER GNS FROM admin to lp01 + @echo "" | gnokey maketx call -pkgpath gno.land/r/gnoswap/v1/gns -func Transfer -args $(ADDR_LP01) -args "1000000000" -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin > /dev/null + + ## APROVE GNS (DEPOSIT) + @echo "" | gnokey maketx call -pkgpath gno.land/r/gnoswap/v1/gns -func Approve -args $(ADDR_STAKER) -args $(MAX_UINT64) -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 9000000 -memo "" gnoswap_lp01 > /dev/null + @echo + + # THEN CREATE EXTERNAL INCENTIVE + @echo "" | gnokey maketx call -pkgpath gno.land/r/gnoswap/v1/staker -func CreateExternalIncentive -args "gno.land/r/gnoswap/v1/gns:gno.land/r/onbloc/foo:500" -args "gno.land/r/onbloc/foo" -args 500000000 -args $(TOMORROW_MIDNIGHT) -args $(INCENTIVE_END) -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_lp01 > /dev/null + @echo + + +## test stake-token + mint_and_stake +stake-token-8: + $(info ************ stake token 8 // gnoswap_admin ************) + @echo "" | gnokey maketx call -pkgpath gno.land/r/gnoswap/v1/gnft -func Approve -args $(ADDR_STAKER) -args 8 -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin > /dev/null + @echo "" | gnokey maketx call -pkgpath gno.land/r/gnoswap/v1/staker -func StakeToken -args 8 -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin > /dev/null + @echo + +stake-token-9: + $(info ************ stake token 9 // gnoswap_admin ************) + @echo "" | gnokey maketx call -pkgpath gno.land/r/gnoswap/v1/gnft -func Approve -args $(ADDR_STAKER) -args 9 -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin > /dev/null + @echo "" | gnokey maketx call -pkgpath gno.land/r/gnoswap/v1/staker -func StakeToken -args 9 -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin > /dev/null + @echo + +mint-and-stake: + $(info ************ mint and stake(10), to same position with lpTokenId 8 (gns:gnot:3000) // gnoswap_admin ************) + # APPROVE FISRT + @echo "" | gnokey maketx call -pkgpath gno.land/r/gnoswap/v1/gns -func Approve -args $(ADDR_POOL) -args $(MAX_UINT64) -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin > /dev/null + @echo "" | gnokey maketx call -pkgpath gno.land/r/demo/wugnot -func Approve -args $(ADDR_POOL) -args $(MAX_UINT64) -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin > /dev/null + @echo + + # THEN MINT + @echo "" | gnokey maketx call -pkgpath gno.land/r/gnoswap/v1/staker -func MintAndStake -send "20000000ugnot" -args "gno.land/r/gnoswap/v1/gns" -args "gnot" -args 3000 -args "-49980" -args "49980" -args 20000000 -args 20000000 -args 0 -args 0 -args $(TX_EXPIRE) -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin > /dev/null + @echo + +## test swap +swap-gns-to-gnot: + @$(MAKE) -f $(MAKEFILE) print-fee-collector + + $(info ************ swap gns -> gnot, exact_in // gnoswap_admin ************) + + # approve INPUT TOKEN to POOL + @echo "" | gnokey maketx call -pkgpath gno.land/r/gnoswap/v1/gns -func Approve -args $(ADDR_POOL) -args $(MAX_UINT64) -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin > /dev/null + + # approve OUTPUT TOKEN to ROUTER ( as 0.15% fee ) + @echo "" | gnokey maketx call -pkgpath gno.land/r/demo/wugnot -func Approve -args $(ADDR_ROUTER) -args $(MAX_UINT64) -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin > /dev/null + + @echo "" | gnokey maketx call -pkgpath gno.land/r/gnoswap/v1/router -func SwapRoute -args "gno.land/r/gnoswap/v1/gns" -args "gno.land/r/demo/wugnot" -args 50000 -args "EXACT_IN" -args "gno.land/r/gnoswap/v1/gns:gno.land/r/demo/wugnot:3000" -args "100" -args "1" -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin > /dev/null + @echo + + @$(MAKE) -f $(MAKEFILE) print-fee-collector + @echo + +swap-gnot-to-gns: + @$(MAKE) -f $(MAKEFILE) print-fee-collector + + $(info ************ swap gnot -> gns, exact_in // gnoswap_admin ************) + + # approve INPUT TOKEN to POOL + @echo "" | gnokey maketx call -pkgpath gno.land/r/demo/wugnot -func Approve -args $(ADDR_POOL) -args $(MAX_UINT64) -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin > /dev/null + + # approve OUTPUT TOKEN to ROUTER ( as 0.15% fee ) + @echo "" | gnokey maketx call -pkgpath gno.land/r/gnoswap/v1/gns -func Approve -args $(ADDR_ROUTER) -args $(MAX_UINT64) -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin > /dev/null + + @echo "" | gnokey maketx call -pkgpath gno.land/r/gnoswap/v1/router -func SwapRoute -args "gno.land/r/demo/wugnot" -args "gno.land/r/gnoswap/v1/gns" -args 50000 -args "EXACT_IN" -args "gno.land/r/demo/wugnot:gno.land/r/gnoswap/v1/gns:3000" -args "100" -args "1" -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin > /dev/null + @echo + + @$(MAKE) -f $(MAKEFILE) print-fee-collector + @echo + +swap-exact-in-single-bar-to-baz: + @$(MAKE) -f $(MAKEFILE) print-fee-collector + + $(info ************ swap bar -> baz, exact_in // gnoswap_tr01 ************) + + # approve INPUT TOKEN to POOL + @echo "" | gnokey maketx call -pkgpath gno.land/r/onbloc/bar -func Approve -args $(ADDR_POOL) -args $(MAX_UINT64) -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_tr01 > /dev/null + + # approve OUTPUT TOKEN to ROUTER ( as 0.15% fee ) + @echo "" | gnokey maketx call -pkgpath gno.land/r/onbloc/baz -func Approve -args $(ADDR_ROUTER) -args $(MAX_UINT64) -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_tr01 > /dev/null + + @echo "" | gnokey maketx call -pkgpath gno.land/r/gnoswap/v1/router -func SwapRoute -args "gno.land/r/onbloc/bar" -args "gno.land/r/onbloc/baz" -args 50000 -args "EXACT_IN" -args "gno.land/r/onbloc/bar:gno.land/r/onbloc/baz:100" -args "100" -args "1" -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_tr01 > /dev/null + @echo + + @$(MAKE) -f $(MAKEFILE) print-fee-collector + @echo + +swap-exact-in-single-baz-to-bar: + $(info ************ swap baz -> bar, exact_in // gnoswap_tr01 ************) + + # approve INPUT TOKEN to POOL + @echo "" | gnokey maketx call -pkgpath gno.land/r/onbloc/baz -func Approve -args $(ADDR_POOL) -args $(MAX_UINT64) -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_tr01 > /dev/null + + # approve OUTPUT TOKEN to ROUTER ( as 0.15% fee ) + @echo "" | gnokey maketx call -pkgpath gno.land/r/onbloc/bar -func Approve -args $(ADDR_ROUTER) -args $(MAX_UINT64) -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_tr01 > /dev/null + + @echo "" | gnokey maketx call -pkgpath gno.land/r/gnoswap/v1/router -func SwapRoute -args "gno.land/r/onbloc/baz" -args "gno.land/r/onbloc/bar" -args 50000 -args "EXACT_IN" -args "gno.land/r/onbloc/baz:gno.land/r/onbloc/bar:100" -args "100" -args "1" -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_tr01 > /dev/null + @echo + + @$(MAKE) -f $(MAKEFILE) print-fee-collector + @echo + +swap-exact-in-single-foo-to-gns: + $(info ************ swap foo -> gns, exact_in // gnoswap_tr01 ************) + + # approve INPUT TOKEN to POOL + @echo "" | gnokey maketx call -pkgpath gno.land/r/onbloc/foo -func Approve -args $(ADDR_POOL) -args $(MAX_UINT64) -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_tr01 > /dev/null + + # approve OUTPUT TOKEN to ROUTER ( as 0.15% fee ) + @echo "" | gnokey maketx call -pkgpath gno.land/r/gnoswap/v1/gns -func Approve -args $(ADDR_ROUTER) -args $(MAX_UINT64) -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_tr01 > /dev/null + + @echo "" | gnokey maketx call -pkgpath gno.land/r/gnoswap/v1/router -func SwapRoute -args "gno.land/r/onbloc/foo" -args "gno.land/r/gnoswap/v1/gns" -args 50000 -args "EXACT_IN" -args "gno.land/r/onbloc/foo:gno.land/r/gnoswap/v1/gns:500" -args "100" -args "1" -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_tr01 > /dev/null + @echo + + @$(MAKE) -f $(MAKEFILE) print-fee-collector + @echo + +swap-exact-out-single-foo-to-gns: + $(info ************ swap foo -> gns, exact_out // gnoswap_tr01 ************) + + # approve INPUT TOKEN to POOL + @echo "" | gnokey maketx call -pkgpath gno.land/r/onbloc/foo -func Approve -args $(ADDR_POOL) -args $(MAX_UINT64) -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_tr01 > /dev/null + + # approve OUTPUT TOKEN to ROUTER ( as 0.15% fee ) + @echo "" | gnokey maketx call -pkgpath gno.land/r/gnoswap/v1/gns -func Approve -args $(ADDR_ROUTER) -args $(MAX_UINT64) -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_tr01 > /dev/null + + @echo "" | gnokey maketx call -pkgpath gno.land/r/gnoswap/v1/router -func SwapRoute -args "gno.land/r/onbloc/foo" -args "gno.land/r/gnoswap/v1/gns" -args 50000 -args "EXACT_OUT" -args "gno.land/r/onbloc/foo:gno.land/r/gnoswap/v1/gns:500" -args "100" -args "50000" -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 100ugnot -gas-wanted 100000000 -memo "" gnoswap_tr01 > /dev/null + @echo + + @$(MAKE) -f $(MAKEFILE) print-fee-collector + @echo + +swap-exact-in-multi-foo-to-gns-to-wugnot: + @$(MAKE) -f $(MAKEFILE) print-fee-collector + + $(info ************ swap foo -> gns -> wugnot, exact_in // gnoswap_tr01 ************) + + # approve INPUT TOKEN to POOL + @echo "" | gnokey maketx call -pkgpath gno.land/r/onbloc/foo -func Approve -args $(ADDR_POOL) -args $(MAX_UINT64) -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_tr01 > /dev/null + + # approve OUTPUT TOKEN to ROUTER ( as 0.15% fee ) + @echo "" | gnokey maketx call -pkgpath gno.land/r/demo/wugnot -func Approve -args $(ADDR_ROUTER) -args $(MAX_UINT64) -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_tr01 > /dev/null + + @echo "" | gnokey maketx call -pkgpath gno.land/r/gnoswap/v1/router -func SwapRoute -args "gno.land/r/onbloc/foo" -args "gno.land/r/demo/wugnot" -args 50000 -args "EXACT_IN" -args "gno.land/r/onbloc/foo:gno.land/r/gnoswap/v1/gns:500*POOL*gno.land/r/gnoswap/v1/gns:gno.land/r/demo/wugnot:3000" -args "100" -args "1" -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 20000000 -memo "" gnoswap_tr01 > /dev/null + @echo + + @$(MAKE) -f $(MAKEFILE) print-fee-collector + @echo + + +set-pool-creation-fee: + $(info ************ set pool creation fee to 100_000_000 uGNS ************) + @echo "" | gnokey maketx call -pkgpath gno.land/r/gnoswap/v1/pool -func SetPoolCreationFee -args 100000000 -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin > /dev/null + + +## ETC +print-fee-collector: + $(info ************ print fee collector balance ************) + @printf "BAR " + @curl -s '$(GNOLAND_RPC_URL)/abci_query?path="vm/qeval"&data="gno.land/r/onbloc/bar.BalanceOf(\"$(ADDR_PROTOCOL_FEE)\")"' | jq -r '.result.response.ResponseBase.Data' | base64 -d + @echo + + @printf "BAZ " + @curl -s '$(GNOLAND_RPC_URL)/abci_query?path="vm/qeval"&data="gno.land/r/onbloc/baz.BalanceOf(\"$(ADDR_PROTOCOL_FEE)\")"' | jq -r '.result.response.ResponseBase.Data' | base64 -d + @echo + + @printf "QUX " + @curl -s '$(GNOLAND_RPC_URL)/abci_query?path="vm/qeval"&data="gno.land/r/onbloc/qux.BalanceOf(\"$(ADDR_PROTOCOL_FEE)\")"' | jq -r '.result.response.ResponseBase.Data' | base64 -d + @echo + + @printf "FOO " + @curl -s '$(GNOLAND_RPC_URL)/abci_query?path="vm/qeval"&data="gno.land/r/onbloc/foo.BalanceOf(\"$(ADDR_PROTOCOL_FEE)\")"' | jq -r '.result.response.ResponseBase.Data' | base64 -d + @echo + + @printf "GNS " + @curl -s '$(GNOLAND_RPC_URL)/abci_query?path="vm/qeval"&data="gno.land/r/gnoswap/v1/gns.BalanceOf(\"$(ADDR_PROTOCOL_FEE)\")"' | jq -r '.result.response.ResponseBase.Data' | base64 -d + @echo + + @printf "OBL " + @curl -s '$(GNOLAND_RPC_URL)/abci_query?path="vm/qeval"&data="gno.land/r/onbloc/obl.BalanceOf(\"$(ADDR_PROTOCOL_FEE)\")"' | jq -r '.result.response.ResponseBase.Data' | base64 -d + @echo + + @printf "USDC " + @curl -s '$(GNOLAND_RPC_URL)/abci_query?path="vm/qeval"&data="gno.land/r/onbloc/usdc.BalanceOf(\"$(ADDR_PROTOCOL_FEE)\")"' | jq -r '.result.response.ResponseBase.Data' | base64 -d + @echo + + @printf "WUGNOT " + @curl -s '$(GNOLAND_RPC_URL)/abci_query?path="vm/qeval"&data="gno.land/r/demo/wugnot.BalanceOf(\"$(ADDR_PROTOCOL_FEE)\")"' | jq -r '.result.response.ResponseBase.Data' | base64 -d + @echo + + @printf "UGNOT " + @curl -s '$(GNOLAND_RPC_URL)/abci_query?path="bank/balances/$(ADDR_PROTOCOL_FEE)"' | jq -r '.result.response.ResponseBase.Data' | base64 -d + @echo + + +## ETC +emission-debug: + $(info ************ emission debug ************) + @curl -s '$(GNOLAND_RPC_URL)/abci_query?path="vm/qeval"&data="gno.land/r/gnoswap/v1/staker.GetPrintInfo()"' | jq -r '.result.response.ResponseBase.Data' | base64 -d + + +## TEST +collect-reward-9: + $(info ************ collect reward 9 // gnoswap_admin (3 externals) ************) + # approve reward token(foo, gns) to staker + @echo "" | gnokey maketx call -pkgpath gno.land/r/gnoswap/v1/gns -func Approve -args $(ADDR_STAKER) -args $(MAX_UINT64) -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin > /dev/null + @echo "" | gnokey maketx call -pkgpath gno.land/r/onbloc/foo -func Approve -args $(ADDR_STAKER) -args $(MAX_UINT64) -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin > /dev/null + + @echo "" | gnokey maketx call -pkgpath gno.land/r/gnoswap/v1/staker -func CollectReward -args 9 -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin > /dev/null + @echo + + +## TRANSFER FOR QA +ADDR_ROH := g16a7etgm9z2r653ucl36rj0l2yqcxgrz2jyegzx +transfer-roh: + $(info ************ TO roh account // transfer COIN(ugnot), GRC20(bar, baz, foo, obl, qux, usdc) ************) + @echo "" | gnokey maketx send -send 10000000000ugnot -to $(ADDR_ROH) -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" test1 > /dev/null + @echo "" | gnokey maketx call -pkgpath gno.land/r/onbloc/bar -func Transfer -args $(ADDR_ROH) -args "1000000000" -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin > /dev/null + @echo "" | gnokey maketx call -pkgpath gno.land/r/onbloc/baz -func Transfer -args $(ADDR_ROH) -args "1000000000" -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin > /dev/null + @echo "" | gnokey maketx call -pkgpath gno.land/r/onbloc/foo -func Transfer -args $(ADDR_ROH) -args "1000000000" -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin > /dev/null + @echo "" | gnokey maketx call -pkgpath gno.land/r/onbloc/obl -func Transfer -args $(ADDR_ROH) -args "1000000000" -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin > /dev/null + @echo "" | gnokey maketx call -pkgpath gno.land/r/onbloc/qux -func Transfer -args $(ADDR_ROH) -args "1000000000" -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin > /dev/null + @echo "" | gnokey maketx call -pkgpath gno.land/r/onbloc/usdc -func Transfer -args $(ADDR_ROH) -args "1000000000" -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin > /dev/null + @echo "" + +ADDR_TEST1 := g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5 +transfer-test1: + $(info ************ TO test1 account // transfer COIN(ugnot), GRC20(bar, baz, foo, obl, qux, usdc) ************) + @echo "" | gnokey maketx send -send 10000000000ugnot -to $(ADDR_TEST1) -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" test1 > /dev/null + @echo "" | gnokey maketx call -pkgpath gno.land/r/onbloc/bar -func Transfer -args $(ADDR_TEST1) -args "1000000000" -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin > /dev/null + @echo "" | gnokey maketx call -pkgpath gno.land/r/onbloc/baz -func Transfer -args $(ADDR_TEST1) -args "1000000000" -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin > /dev/null + @echo "" | gnokey maketx call -pkgpath gno.land/r/onbloc/foo -func Transfer -args $(ADDR_TEST1) -args "1000000000" -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin > /dev/null + @echo "" | gnokey maketx call -pkgpath gno.land/r/onbloc/obl -func Transfer -args $(ADDR_TEST1) -args "1000000000" -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin > /dev/null + @echo "" | gnokey maketx call -pkgpath gno.land/r/onbloc/qux -func Transfer -args $(ADDR_TEST1) -args "1000000000" -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin > /dev/null + @echo "" | gnokey maketx call -pkgpath gno.land/r/onbloc/usdc -func Transfer -args $(ADDR_TEST1) -args "1000000000" -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin > /dev/null + @echo "" \ No newline at end of file diff --git a/__local/test/emission_test.mk b/__local/test/emission_test.mk new file mode 100644 index 000000000..d2075fea3 --- /dev/null +++ b/__local/test/emission_test.mk @@ -0,0 +1,344 @@ +# make -f __local/test/emission_test.mk init init-test + +ADDR_GSA := g17290cwvmrapvp869xfnhhawa8sm9edpufzat7d +ADDR_REGISTER := g1er355fkjksqpdtwmhf5penwa82p0rhqxkkyhk5 + +ADDR_GOV := g1cu4dufdlzu0l9pekkqhw6mjnrxkp3z7ykst09d +ADDR_POOL := g148tjamj80yyrm309z7rk690an22thd2l3z8ank +ADDR_POSITION := g1q646ctzhvn60v492x8ucvyqnrj2w30cwh6efk5 +ADDR_ROUTER := g1lm2l7tf49h3mykesct7rhfml30yx8dw5xrval7 +ADDR_STAKER := g1cceshmzzlmrh7rr3z30j2t5mrvsq9yccysw9nu +ADDR_PROTOCOL_FEE := g1f7wpek7q67tkns27sw495u5yuu3a5wwjxw5l6l + +ADDR_GNS := g1jgqwaa2le3yr63d533fj785qkjspumzv22ys5m +ADDR_GNFT := g1wxv2rdfn53qc84nt3nn646f9yh3nly8lm7j89t + +ADDR_WUGNOT := g1pf6dv9fjk3rn0m4jjcne306ga4he3mzmupfjl6 + + +MAX_UINT64 := 18446744073709551615 +TX_EXPIRE := 9999999999 + +MAKEFILE := $(shell realpath $(firstword $(MAKEFILE_LIST))) + +GNOLAND_RPC_URL ?= http://localhost:26657 +CHAINID ?= dev + +# GNOLAND_RPC_URL ?= https://dev.rpc.gnoswap.io:443 +# CHAINID ?= dev.gnoswap + +ROOT_DIR:=$(shell dirname $(MAKEFILE))/../../ + + +## INIT +.PHONY: init +init: wait send-ugnot-must deploy-libraries deploy-base-tokens deploy-gnoswap-realms deploy-test-tokens register-token pool-create-gns-wugnot-default + +.PHONY: deploy-libraries +deploy-libraries: deploy-uint256 deploy-int256 deploy-consts deploy-package-pool deploy-common + +.PHONY: deploy-base-tokens +deploy-base-tokens: deploy-gns deploy-usdc deploy-gnft + +.PHONY: deploy-test-tokens +deploy-test-tokens: deploy-foo deploy-bar deploy-baz deploy-qux deploy-obl + +.PHONY: deploy-gnoswap-realms +deploy-gnoswap-realms: deploy-xgns deploy-emission deploy-pool deploy-position deploy-staker deploy-router deploy-community_pool deploy-protocol_fee deploy-gov-staker deploy-gov-governance + +### TEST AFTER INIT +.PHONY: init-test +init-test: test-pool-create test-position-mint test-stake-token + +.PHONY: test-pool-create +test-pool-create: pool-create-bar-baz pool-create-foo-qux + +.PHONY: test-position-mint +test-position-mint: mint-gns-gnot + +.PHONY: test-stake-token +test-stake-token: stake-token-1 + +.PHONY: test-collect-reward +test-collect-reward: collect-reward-1 + +.PHONY: test-unstake-token +test-unstake-token: unstake-token-1 + +.PHONY: test-burn-position +test-burn-position: burn-position-1 + + + +# wait chain to start +wait: + $(info ************ [ETC] wait 1 seconds for chain to start ************) + $(shell sleep 1) + @echo + + +# send ugnot to necessary accounts +send-ugnot-must: + $(info ************ send ugnot to necessary accounts ************) + @echo "" | gnokey maketx send -send 10000000000ugnot -to $(ADDR_GSA) -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" test1 > /dev/null + @echo "" | gnokey maketx send -send 10000000000ugnot -to $(ADDR_PROTOCOL_FEE) -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" test1 > /dev/null + @echo "" | gnokey maketx send -send 10000000000ugnot -to $(ADDR_REGISTER) -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" test1 > /dev/null + @echo + + +# deploy test grc20 tokens +deploy-foo: + $(info ************ deploy foo ************) + @echo "" | gnokey maketx addpkg -pkgdir $(ROOT_DIR)/__local/grc20_tokens/onbloc/foo -pkgpath gno.land/r/onbloc/foo -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin > /dev/null + @echo + +deploy-bar: + $(info ************ deploy bar ************) + @echo "" | gnokey maketx addpkg -pkgdir $(ROOT_DIR)/__local/grc20_tokens/onbloc/bar -pkgpath gno.land/r/onbloc/bar -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin > /dev/null + @echo + +deploy-baz: + $(info ************ deploy baz ************) + @echo "" | gnokey maketx addpkg -pkgdir $(ROOT_DIR)/__local/grc20_tokens/onbloc/baz -pkgpath gno.land/r/onbloc/baz -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin > /dev/null + @echo + +deploy-qux: + $(info ************ deploy qux ************) + @echo "" | gnokey maketx addpkg -pkgdir $(ROOT_DIR)/__local/grc20_tokens/onbloc/qux -pkgpath gno.land/r/onbloc/qux -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin > /dev/null + @echo + +deploy-obl: + $(info ************ deploy obl ************) + @echo "" | gnokey maketx addpkg -pkgdir $(ROOT_DIR)/__local/grc20_tokens/onbloc/obl -pkgpath gno.land/r/onbloc/obl -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin > /dev/null + @echo + + +# deploy base tokens +deploy-gns: + $(info ************ deploy gns ************) + @echo "" | gnokey maketx addpkg -pkgdir $(ROOT_DIR)/_deploy/r/gnoswap/gns -pkgpath gno.land/r/gnoswap/v1/gns -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin > /dev/null + @echo + +deploy-gnft: + $(info ************ deploy gnft ************) + @echo "" | gnokey maketx addpkg -pkgdir $(ROOT_DIR)/_deploy/r/gnoswap/gnft -pkgpath gno.land/r/gnoswap/v1/gnft -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin > /dev/null + @echo + +deploy-usdc: + $(info ************ deploy usdc ************) + @echo "" | gnokey maketx addpkg -pkgdir $(ROOT_DIR)/__local/grc20_tokens/onbloc/usdc -pkgpath gno.land/r/onbloc/usdc -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin > /dev/null + @echo + + +# deploy packages +deploy-uint256: + $(info ************ deploy uint256 ************) + @echo "" | gnokey maketx addpkg -pkgdir $(ROOT_DIR)/_deploy/p/gnoswap/uint256 -pkgpath gno.land/p/gnoswap/uint256 -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin > /dev/null + @echo + +deploy-int256: + $(info ************ deploy int256 ************) + @echo "" | gnokey maketx addpkg -pkgdir $(ROOT_DIR)/_deploy/p/gnoswap/int256 -pkgpath gno.land/p/gnoswap/int256 -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin > /dev/null + @echo + +deploy-package-pool: + $(info ************ deploy package pool ************) + @echo "" | gnokey maketx addpkg -pkgdir $(ROOT_DIR)/_deploy/p/gnoswap/pool -pkgpath gno.land/p/gnoswap/pool -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin > /dev/null + @echo + + +# deploy common realms +deploy-consts: + $(info ************ deploy consts ************) + @echo "" | gnokey maketx addpkg -pkgdir $(ROOT_DIR)/_deploy/r/gnoswap/consts -pkgpath gno.land/r/gnoswap/v1/consts -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin > /dev/null + @echo + +deploy-common: + $(info ************ deploy common ************) + @echo "" | gnokey maketx addpkg -pkgdir $(ROOT_DIR)/_deploy/r/gnoswap/common -pkgpath gno.land/r/gnoswap/v1/common -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin > /dev/null + @echo + + +# deploy gnoswap realms +deploy-xgns: + $(info ************ deploy xgns ************) + @echo "" | gnokey maketx addpkg -pkgdir $(ROOT_DIR)/gov/xgns -pkgpath gno.land/r/gnoswap/v1/gov/xgns -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin > /dev/null + @echo + +deploy-emission: + $(info ************ deploy emission ************) + @echo "" | gnokey maketx addpkg -pkgdir $(ROOT_DIR)/emission -pkgpath gno.land/r/gnoswap/v1/emission -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin > /dev/null + @echo + +deploy-pool: + $(info ************ deploy pool ************) + @echo "" | gnokey maketx addpkg -pkgdir $(ROOT_DIR)/pool -pkgpath gno.land/r/gnoswap/v1/pool -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin > /dev/null + @echo + +deploy-position: + $(info ************ deploy position ************) + @echo "" | gnokey maketx addpkg -pkgdir $(ROOT_DIR)/position -pkgpath gno.land/r/gnoswap/v1/position -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin > /dev/null + @echo + +deploy-router: + $(info ************ deploy router ************) + @echo "" | gnokey maketx addpkg -pkgdir $(ROOT_DIR)/router -pkgpath gno.land/r/gnoswap/v1/router -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin > /dev/null + @echo + +deploy-staker: + $(info ************ deploy staker ************) + @echo "" | gnokey maketx addpkg -pkgdir $(ROOT_DIR)/staker -pkgpath gno.land/r/gnoswap/v1/staker -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin > /dev/null + @echo + +deploy-community_pool: + $(info ************ deploy community_pool ************) + @echo "" | gnokey maketx addpkg -pkgdir $(ROOT_DIR)/community_pool -pkgpath gno.land/r/gnoswap/v1/community_pool -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin > /dev/null + @echo + +deploy-protocol_fee: + $(info ************ deploy protocol_fee ************) + @echo "" | gnokey maketx addpkg -pkgdir $(ROOT_DIR)/protocol_fee -pkgpath gno.land/r/gnoswap/v1/protocol_fee -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin > /dev/null + @echo + +deploy-gov-staker: + $(info ************ deploy gov/staker ************) + @echo "" | gnokey maketx addpkg -pkgdir $(ROOT_DIR)/gov/staker -pkgpath gno.land/r/gnoswap/v1/gov/staker -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin > /dev/null + @echo + +deploy-gov-governance: + $(info ************ deploy gov/governance ************) + @echo "" | gnokey maketx addpkg -pkgdir $(ROOT_DIR)/gov/governance -pkgpath gno.land/r/gnoswap/v1/gov/governance -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin > /dev/null + @echo + +# Register +register-token: + $(info ************ deploy register_gnodev ************) + @echo "" | gnokey maketx addpkg -pkgdir $(ROOT_DIR)/__local/grc20_tokens/g1er355fkjksqpdtwmhf5penwa82p0rhqxkkyhk5/register_gnodev -pkgpath gno.land/r/g1er355fkjksqpdtwmhf5penwa82p0rhqxkkyhk5/v2/register_gnodev -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" register > /dev/null + @echo + +# default pool create +pool-create-gns-wugnot-default: + $(info ************ set pool creation fee to 0uGNS for testing ************) + @echo "" | gnokey maketx call -pkgpath gno.land/r/gnoswap/v1/pool -func SetPoolCreationFee -args 0 -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin > /dev/null + @echo + + + $(info ************ create default pool (GNS:WUGNOT:0.03%) ************) + # tick 0 ≈ x1 ≈ 79228162514264337593543950337 + @echo "" | gnokey maketx call -pkgpath gno.land/r/gnoswap/v1/pool -func CreatePool -args "gno.land/r/demo/wugnot" -args "gno.land/r/gnoswap/v1/gns" -args 3000 -args 79228162514264337593543950337 -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin > /dev/null + @echo + +# test pool create +pool-create-bar-baz: + $(info ************ create pool bar:baz ************) + # tick -10 ≈ x0.99900054978007157835406815138412639498710632324219 ≈ 79188560314459151373725315960 + @echo "" | gnokey maketx call -pkgpath gno.land/r/gnoswap/v1/pool -func CreatePool -args "gno.land/r/onbloc/bar" -args "gno.land/r/onbloc/baz" -args 100 -args 79188560314459151373725315960 -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin > /dev/null + @echo + +pool-create-foo-qux: + $(info ************ create pool foo:qux ************) + # tick -10 ≈ x0.99900054978007157835406815138412639498710632324219 ≈ 79188560314459151373725315960 + @echo "" | gnokey maketx call -pkgpath gno.land/r/gnoswap/v1/pool -func CreatePool -args "gno.land/r/onbloc/foo" -args "gno.land/r/onbloc/qux" -args 100 -args 79188560314459151373725315960 -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin > /dev/null + @echo + +mint-gns-gnot: + $(info ************ mint position(1) to gns:wugnot // gnoswap_admin ************) + # APPROVE FISRT + @echo "" | gnokey maketx call -pkgpath gno.land/r/gnoswap/v1/gns -func Approve -args $(ADDR_POOL) -args $(MAX_UINT64) -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin > /dev/null + @echo "" | gnokey maketx call -pkgpath gno.land/r/demo/wugnot -func Approve -args $(ADDR_POOL) -args $(MAX_UINT64) -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin > /dev/null + @echo + + # APPROVE WUGNOT TO POSITION, to get refund wugnot left after wrap -> mint + @echo "" | gnokey maketx call -pkgpath gno.land/r/demo/wugnot -func Approve -args $(ADDR_POSITION) -args $(MAX_UINT64) -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin > /dev/null + + # THEN MINT + @echo "" | gnokey maketx call -pkgpath gno.land/r/gnoswap/v1/position -func Mint -send "20000000ugnot" -args "gno.land/r/gnoswap/v1/gns" -args "gnot" -args 3000 -args "-49980" -args "49980" -args 20000000 -args 20000000 -args 1 -args 1 -args $(TX_EXPIRE) -args $(ADDR_GSA) -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin > /dev/null + @echo + + # Set-Token-Uri + @echo "" | gnokey maketx call -pkgpath gno.land/r/gnoswap/v1/gnft -func SetTokenURILast -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin > /dev/null + @echo + +stake-token-1: + $(info ************ stake token 1 // gnoswap_admin ************) + @echo "" | gnokey maketx call -pkgpath gno.land/r/gnoswap/v1/gnft -func Approve -args $(ADDR_STAKER) -args 1 -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin > /dev/null + @echo "" | gnokey maketx call -pkgpath gno.land/r/gnoswap/v1/staker -func StakeToken -args 1 -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin > /dev/null + @echo + +## test collect reward +collect-reward-1: + $(info ************ collect reward 1 // gnoswap_admin (internal) ************) + # approve reward token(gns) to STAKER + @echo "" | gnokey maketx call -pkgpath gno.land/r/gnoswap/v1/gns -func Approve -args $(ADDR_STAKER) -args $(MAX_UINT64) -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin > /dev/null + + @echo "" | gnokey maketx call -pkgpath gno.land/r/gnoswap/v1/staker -func CollectReward -args 1 -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin > /dev/null + @echo + +## test unstake token +unstake-token-1: + $(info ************ unstake token 1 // gnoswap_admin ************) + @echo "" | gnokey maketx call -pkgpath gno.land/r/gnoswap/v1/staker -func UnstakeToken -args 1 -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin > /dev/null + @echo + +## test position burn +burn-position-1: + $(info ************ decrease entire liquidity(==burn) from position 1 // gnoswap_admin ************) + @echo "" | gnokey maketx call -pkgpath gno.land/r/gnoswap/v1/position -func DecreaseLiquidity -args 1 -args 100 -args 0 -args 0 -args $(TX_EXPIRE) -args "false" -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin > /dev/null + @echo + + +set-pool-tier-1-bar-baz: + $(info ************ set pool tier 2 bar:baz // gnoswap_admin ************) + @echo "" | gnokey maketx call -pkgpath gno.land/r/gnoswap/v1/staker -func SetPoolTier -args "gno.land/r/onbloc/bar:gno.land/r/onbloc/baz:100" -args 1 -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin > /dev/null + @echo + +stake-token-2: + $(info ************ stake token 2 // gnoswap_admin ************) + @echo "" | gnokey maketx call -pkgpath gno.land/r/gnoswap/v1/gnft -func Approve -args $(ADDR_STAKER) -args 2 -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin > /dev/null + @echo "" | gnokey maketx call -pkgpath gno.land/r/gnoswap/v1/staker -func StakeToken -args 2 -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin > /dev/null + @echo + +set-pool-tier-3-foo-qux: + $(info ************ set pool tier 3 foo:qux // gnoswap_admin ************) + @echo "" | gnokey maketx call -pkgpath gno.land/r/gnoswap/v1/staker -func SetPoolTier -args "gno.land/r/onbloc/foo:gno.land/r/onbloc/qux:100" -args 3 -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin > /dev/null + @echo + +stake-token-3: + $(info ************ stake token 3 // gnoswap_admin ************) + @echo "" | gnokey maketx call -pkgpath gno.land/r/gnoswap/v1/gnft -func Approve -args $(ADDR_STAKER) -args 3 -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin > /dev/null + @echo "" | gnokey maketx call -pkgpath gno.land/r/gnoswap/v1/staker -func StakeToken -args 3 -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin > /dev/null + @echo + + +# CREATE EXTERNAL INCENTIVE DEPOSIT +external-deposit-approve: + $(info ************ approve gns to staker // gnoswap_admin ************) + @echo "" | gnokey maketx call -pkgpath gno.land/r/gnoswap/v1/gns -func Approve -args $(ADDR_STAKER) -args $(MAX_UINT64) -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin > /dev/null + + + + +## TRANSFER FOR QA +ADDR_ROH := g16a7etgm9z2r653ucl36rj0l2yqcxgrz2jyegzx +transfer-roh: + $(info ************ TO roh account // transfer COIN(ugnot), GRC20(bar, baz, foo, obl, qux, usdc) ************) + @echo "" | gnokey maketx send -send 10000000000ugnot -to $(ADDR_ROH) -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" test1 > /dev/null + @echo "" | gnokey maketx call -pkgpath gno.land/r/onbloc/bar -func Transfer -args $(ADDR_ROH) -args "1000000000" -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin > /dev/null + @echo "" | gnokey maketx call -pkgpath gno.land/r/onbloc/baz -func Transfer -args $(ADDR_ROH) -args "1000000000" -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin > /dev/null + @echo "" | gnokey maketx call -pkgpath gno.land/r/onbloc/foo -func Transfer -args $(ADDR_ROH) -args "1000000000" -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin > /dev/null + @echo "" | gnokey maketx call -pkgpath gno.land/r/onbloc/obl -func Transfer -args $(ADDR_ROH) -args "1000000000" -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin > /dev/null + @echo "" | gnokey maketx call -pkgpath gno.land/r/onbloc/qux -func Transfer -args $(ADDR_ROH) -args "1000000000" -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin > /dev/null + @echo "" | gnokey maketx call -pkgpath gno.land/r/onbloc/usdc -func Transfer -args $(ADDR_ROH) -args "1000000000" -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin > /dev/null + @echo "" + +ADDR_TEST1 := g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5 +transfer-test1: + $(info ************ TO test1 account // transfer COIN(ugnot), GRC20(bar, baz, foo, obl, qux, usdc) ************) + @echo "" | gnokey maketx send -send 10000000000ugnot -to $(ADDR_TEST1) -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" test1 > /dev/null + @echo "" | gnokey maketx call -pkgpath gno.land/r/onbloc/bar -func Transfer -args $(ADDR_TEST1) -args "1000000000" -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin > /dev/null + @echo "" | gnokey maketx call -pkgpath gno.land/r/onbloc/baz -func Transfer -args $(ADDR_TEST1) -args "1000000000" -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin > /dev/null + @echo "" | gnokey maketx call -pkgpath gno.land/r/onbloc/foo -func Transfer -args $(ADDR_TEST1) -args "1000000000" -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin > /dev/null + @echo "" | gnokey maketx call -pkgpath gno.land/r/onbloc/obl -func Transfer -args $(ADDR_TEST1) -args "1000000000" -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin > /dev/null + @echo "" | gnokey maketx call -pkgpath gno.land/r/onbloc/qux -func Transfer -args $(ADDR_TEST1) -args "1000000000" -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin > /dev/null + @echo "" | gnokey maketx call -pkgpath gno.land/r/onbloc/usdc -func Transfer -args $(ADDR_TEST1) -args "1000000000" -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin > /dev/null + @echo "" \ No newline at end of file diff --git a/__local/test/gov_proposal.mk b/__local/test/gov_proposal.mk new file mode 100644 index 000000000..7575ca9ce --- /dev/null +++ b/__local/test/gov_proposal.mk @@ -0,0 +1,404 @@ +# make -f __local/test/gov_proposal.mk init init-test gov-test + +ADDR_GSA := g17290cwvmrapvp869xfnhhawa8sm9edpufzat7d +ADDR_REGISTER := g1er355fkjksqpdtwmhf5penwa82p0rhqxkkyhk5 + +ADDR_LP01 := g1qf5863trkaq447zr2xdmql83g0twzl37dm9qqt +ADDR_LP02 := g1ta0w7j4f586kwqu584z5h5sjurzywz3na7qg0a +ADDR_TR01 := g14m6fj3t8005u77ku6zyzazq9vd9hwhl00ppt8j + +ADDR_POOL := g148tjamj80yyrm309z7rk690an22thd2l3z8ank +ADDR_POSITION := g1q646ctzhvn60v492x8ucvyqnrj2w30cwh6efk5 +ADDR_ROUTER := g1lm2l7tf49h3mykesct7rhfml30yx8dw5xrval7 +ADDR_STAKER := g1cceshmzzlmrh7rr3z30j2t5mrvsq9yccysw9nu +ADDR_PROTOCOL_FEE := g1f7wpek7q67tkns27sw495u5yuu3a5wwjxw5l6l + +ADDR_GOV_STAKER := g17e3ykyqk9jmqe2y9wxe9zhep3p7cw56davjqwa +AADR_GOV_GOV := g17s8w2ve7k85fwfnrk59lmlhthkjdted8whvqxd + +ADDR_GNS := g1jgqwaa2le3yr63d533fj785qkjspumzv22ys5m +ADDR_GNFT := g1wxv2rdfn53qc84nt3nn646f9yh3nly8lm7j89t + +ADDR_WUGNOT := g1pf6dv9fjk3rn0m4jjcne306ga4he3mzmupfjl6 + + +MAX_UINT64 := 18446744073709551615 +TX_EXPIRE := 9999999999 + +MAKEFILE := $(shell realpath $(firstword $(MAKEFILE_LIST))) + +GNOLAND_RPC_URL ?= http://localhost:26657 +CHAINID ?= dev + +# GNOLAND_RPC_URL ?= https://dev.rpc.gnoswap.io:443 +# CHAINID ?= dev.gnoswap + +ROOT_DIR:=$(shell dirname $(MAKEFILE))/../../ + + +## INIT +.PHONY: init +init: wait send-ugnot-must deploy-libraries deploy-base-tokens deploy-gnoswap-realms deploy-test-tokens register-token pool-create-gns-wugnot-default + +.PHONY: deploy-libraries +deploy-libraries: deploy-uint256 deploy-int256 deploy-consts deploy-package-pool deploy-common + +.PHONY: deploy-base-tokens +deploy-base-tokens: deploy-gns deploy-usdc deploy-gnft + +.PHONY: deploy-test-tokens +deploy-test-tokens: deploy-foo deploy-bar deploy-baz deploy-qux deploy-obl + +.PHONY: deploy-gnoswap-realms +deploy-gnoswap-realms: deploy-xgns deploy-emission deploy-pool deploy-position deploy-staker deploy-router deploy-community_pool deploy-protocol_fee deploy-gov-staker deploy-gov-governance + +### TEST AFTER INIT +.PHONY: init-test +init-test: test-pool-create test-position-mint test-stake-token + +.PHONY: test-pool-create +test-pool-create: pool-create-bar-baz pool-create-foo-qux + +.PHONY: test-position-mint +test-position-mint: mint-gns-gnot + +.PHONY: test-stake-token +test-stake-token: stake-token-1 + +## TEST GOV +.PHONY: gov-test +gov-test: gov-staker gov-propose-proposals gov-propose-cancel # gov-vote gov-execute + +.PHONY: gov-staker +gov-staker: delegate-1 delegate-2 redelegate undelegate collect-undelegated collect-gov-reward + +.PHONY: gov-propose-proposals +gov-propose-proposals: propose-text propose-community propose-param + +.PHONY: gov-propose-cancel +gov-propose-cancel: cancel-text + +.PHONY: gov-vote +gov-toe: vote-community vote-param + +.PHONY: gov-execute +gov-execute: execute-community execute-param + + +# wait chain to start +wait: + $(info ************ [ETC] wait 1 seconds for chain to start ************) + $(shell sleep 1) + @echo + + +# send ugnot to necessary accounts +send-ugnot-must: + $(info ************ send ugnot to necessary accounts ************) + @echo "" | gnokey maketx send -send 10000000000ugnot -to $(ADDR_GSA) -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" test1 + @echo "" | gnokey maketx send -send 10000000000ugnot -to $(ADDR_PROTOCOL_FEE) -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" test1 + @echo "" | gnokey maketx send -send 10000000000ugnot -to $(ADDR_REGISTER) -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" test1 + @echo + + +# deploy test grc20 tokens +deploy-foo: + $(info ************ deploy foo ************) + @echo "" | gnokey maketx addpkg -pkgdir $(ROOT_DIR)/__local/grc20_tokens/onbloc/foo -pkgpath gno.land/r/onbloc/foo -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin + @echo + +deploy-bar: + $(info ************ deploy bar ************) + @echo "" | gnokey maketx addpkg -pkgdir $(ROOT_DIR)/__local/grc20_tokens/onbloc/bar -pkgpath gno.land/r/onbloc/bar -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin + @echo + +deploy-baz: + $(info ************ deploy baz ************) + @echo "" | gnokey maketx addpkg -pkgdir $(ROOT_DIR)/__local/grc20_tokens/onbloc/baz -pkgpath gno.land/r/onbloc/baz -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin + @echo + +deploy-qux: + $(info ************ deploy qux ************) + @echo "" | gnokey maketx addpkg -pkgdir $(ROOT_DIR)/__local/grc20_tokens/onbloc/qux -pkgpath gno.land/r/onbloc/qux -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin + @echo + +deploy-obl: + $(info ************ deploy obl ************) + @echo "" | gnokey maketx addpkg -pkgdir $(ROOT_DIR)/__local/grc20_tokens/onbloc/obl -pkgpath gno.land/r/onbloc/obl -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin + @echo + + +# deploy base tokens +deploy-gns: + $(info ************ deploy gns ************) + @echo "" | gnokey maketx addpkg -pkgdir $(ROOT_DIR)/_deploy/r/gnoswap/gns -pkgpath gno.land/r/gnoswap/v1/gns -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin + @echo + +deploy-gnft: + $(info ************ deploy gnft ************) + @echo "" | gnokey maketx addpkg -pkgdir $(ROOT_DIR)/_deploy/r/gnoswap/gnft -pkgpath gno.land/r/gnoswap/v1/gnft -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin + @echo + +deploy-usdc: + $(info ************ deploy usdc ************) + @echo "" | gnokey maketx addpkg -pkgdir $(ROOT_DIR)/__local/grc20_tokens/onbloc/usdc -pkgpath gno.land/r/onbloc/usdc -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin + @echo + + +# deploy packages +deploy-uint256: + $(info ************ deploy uint256 ************) + @echo "" | gnokey maketx addpkg -pkgdir $(ROOT_DIR)/_deploy/p/gnoswap/uint256 -pkgpath gno.land/p/gnoswap/uint256 -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin + @echo + +deploy-int256: + $(info ************ deploy int256 ************) + @echo "" | gnokey maketx addpkg -pkgdir $(ROOT_DIR)/_deploy/p/gnoswap/int256 -pkgpath gno.land/p/gnoswap/int256 -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin + @echo + +deploy-package-pool: + $(info ************ deploy package pool ************) + @echo "" | gnokey maketx addpkg -pkgdir $(ROOT_DIR)/_deploy/p/gnoswap/pool -pkgpath gno.land/p/gnoswap/pool -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin + @echo + + +# deploy common realms +deploy-consts: + $(info ************ deploy consts ************) + @echo "" | gnokey maketx addpkg -pkgdir $(ROOT_DIR)/_deploy/r/gnoswap/consts -pkgpath gno.land/r/gnoswap/v1/consts -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin + @echo + +deploy-common: + $(info ************ deploy common ************) + @echo "" | gnokey maketx addpkg -pkgdir $(ROOT_DIR)/_deploy/r/gnoswap/common -pkgpath gno.land/r/gnoswap/v1/common -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin + @echo + + +# deploy gnoswap realms +deploy-xgns: + $(info ************ deploy xgns ************) + @echo "" | gnokey maketx addpkg -pkgdir $(ROOT_DIR)/gov/xgns -pkgpath gno.land/r/gnoswap/v1/gov/xgns -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin + @echo + +deploy-emission: + $(info ************ deploy emission ************) + @echo "" | gnokey maketx addpkg -pkgdir $(ROOT_DIR)/emission -pkgpath gno.land/r/gnoswap/v1/emission -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin + @echo + +deploy-pool: + $(info ************ deploy pool ************) + @echo "" | gnokey maketx addpkg -pkgdir $(ROOT_DIR)/pool -pkgpath gno.land/r/gnoswap/v1/pool -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin + @echo + +deploy-position: + $(info ************ deploy position ************) + @echo "" | gnokey maketx addpkg -pkgdir $(ROOT_DIR)/position -pkgpath gno.land/r/gnoswap/v1/position -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin + @echo + +deploy-router: + $(info ************ deploy router ************) + @echo "" | gnokey maketx addpkg -pkgdir $(ROOT_DIR)/router -pkgpath gno.land/r/gnoswap/v1/router -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin + @echo + +deploy-staker: + $(info ************ deploy staker ************) + @echo "" | gnokey maketx addpkg -pkgdir $(ROOT_DIR)/staker -pkgpath gno.land/r/gnoswap/v1/staker -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin + @echo + +deploy-community_pool: + $(info ************ deploy community_pool ************) + @echo "" | gnokey maketx addpkg -pkgdir $(ROOT_DIR)/community_pool -pkgpath gno.land/r/gnoswap/v1/community_pool -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin + @echo + +deploy-protocol_fee: + $(info ************ deploy protocol_fee ************) + @echo "" | gnokey maketx addpkg -pkgdir $(ROOT_DIR)/protocol_fee -pkgpath gno.land/r/gnoswap/v1/protocol_fee -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin + @echo + +deploy-gov-staker: + $(info ************ deploy gov/staker ************) + @echo "" | gnokey maketx addpkg -pkgdir $(ROOT_DIR)/gov/staker -pkgpath gno.land/r/gnoswap/v1/gov/staker -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin + @echo + +deploy-gov-governance: + $(info ************ deploy gov/governance ************) + @echo "" | gnokey maketx addpkg -pkgdir $(ROOT_DIR)/gov/governance -pkgpath gno.land/r/gnoswap/v1/gov/governance -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin + @echo + +# Register +register-token: + $(info ************ deploy register_gnodev ************) + @echo "" | gnokey maketx addpkg -pkgdir $(ROOT_DIR)/__local/grc20_tokens/g1er355fkjksqpdtwmhf5penwa82p0rhqxkkyhk5/register_gnodev -pkgpath gno.land/r/g1er355fkjksqpdtwmhf5penwa82p0rhqxkkyhk5/v2/register_gnodev -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" register + @echo + +# default pool create +pool-create-gns-wugnot-default: + $(info ************ create default pool (GNS:WUGNOT:0.03%) ************) + # APPROVE + @echo "" | gnokey maketx call -pkgpath gno.land/r/gnoswap/v1/gns -func Approve -args $(ADDR_POOL) -args $(MAX_UINT64) -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin + + + # tick 0 ≈ x1 ≈ 79228162514264337593543950337 + @echo "" | gnokey maketx call -pkgpath gno.land/r/gnoswap/v1/pool -func CreatePool -args "gno.land/r/demo/wugnot" -args "gno.land/r/gnoswap/v1/gns" -args 3000 -args 79228162514264337593543950337 -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin + @echo + +# test pool create +pool-create-bar-baz: + $(info ************ create pool bar:baz ************) + # tick -10 ≈ x0.99900054978007157835406815138412639498710632324219 ≈ 79188560314459151373725315960 + @echo "" | gnokey maketx call -pkgpath gno.land/r/gnoswap/v1/pool -func CreatePool -args "gno.land/r/onbloc/bar" -args "gno.land/r/onbloc/baz" -args 100 -args 79188560314459151373725315960 -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin + @echo + +pool-create-foo-qux: + $(info ************ create pool foo:qux ************) + # tick -10 ≈ x0.99900054978007157835406815138412639498710632324219 ≈ 79188560314459151373725315960 + @echo "" | gnokey maketx call -pkgpath gno.land/r/gnoswap/v1/pool -func CreatePool -args "gno.land/r/onbloc/foo" -args "gno.land/r/onbloc/qux" -args 100 -args 79188560314459151373725315960 -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin + @echo + +mint-gns-gnot: + $(info ************ mint position(1) to gns:wugnot // gnoswap_admin ************) + # APPROVE FISRT + @echo "" | gnokey maketx call -pkgpath gno.land/r/gnoswap/v1/gns -func Approve -args $(ADDR_POOL) -args $(MAX_UINT64) -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin + @echo "" | gnokey maketx call -pkgpath gno.land/r/demo/wugnot -func Approve -args $(ADDR_POOL) -args $(MAX_UINT64) -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin + @echo + + # APPROVE WUGNOT TO POSITION, to get refund wugnot left after wrap -> mint + @echo "" | gnokey maketx call -pkgpath gno.land/r/demo/wugnot -func Approve -args $(ADDR_POSITION) -args $(MAX_UINT64) -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin + + # THEN MINT + @echo "" | gnokey maketx call -pkgpath gno.land/r/gnoswap/v1/position -func Mint -send "20000000ugnot" -args "gno.land/r/gnoswap/v1/gns" -args "gnot" -args 3000 -args "-49980" -args "49980" -args 20000000 -args 20000000 -args 1 -args 1 -args $(TX_EXPIRE) -args $(ADDR_GSA) -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin + @echo + + # Set-Token-Uri + @echo "" | gnokey maketx call -pkgpath gno.land/r/gnoswap/v1/gnft -func SetTokenURILast -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin + @echo + +stake-token-1: + $(info ************ stake token 1 // gnoswap_admin ************) + @echo "" | gnokey maketx call -pkgpath gno.land/r/gnoswap/v1/gnft -func Approve -args $(ADDR_STAKER) -args 1 -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin + @echo "" | gnokey maketx call -pkgpath gno.land/r/gnoswap/v1/staker -func StakeToken -args 1 -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin + @echo + + +delegate-1: + $(info ************ delegate 1_000_000_000 to self // gnoswap_admin ************) + # APPROVE FIRST + @echo "" | gnokey maketx call -pkgpath gno.land/r/gnoswap/v1/gns -func Approve -args $(ADDR_GOV_STAKER) -args $(MAX_UINT64) -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin + + # DELEGATE + @echo "" | gnokey maketx call -pkgpath gno.land/r/gnoswap/v1/gov/staker -func Delegate -args $(ADDR_GSA) -args 1000000000 -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin + @echo + +delegate-2: + $(info ************ delegate 1_500_000_000 to lp_01 // gnoswap_admin ************) + # APPROVE FIRST + @echo "" | gnokey maketx call -pkgpath gno.land/r/gnoswap/v1/gns -func Approve -args $(ADDR_GOV_STAKER) -args $(MAX_UINT64) -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin + + # DELEGATE + @echo "" | gnokey maketx call -pkgpath gno.land/r/gnoswap/v1/gov/staker -func Delegate -args $(ADDR_LP01) -args 1500000000 -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin + @echo + +redelegate: + $(info ************ redelegate 1_000_000_000 from lp_01 to self // gnoswap_admin ************) + @echo "" | gnokey maketx call -pkgpath gno.land/r/gnoswap/v1/gov/staker -func Redelegate -args $(ADDR_LP01) -args $(ADDR_GSA) -args 1000000000 -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin + @echo + +undelegate: + $(info ************ undelegate 1_000_000_000 from self // gnoswap_admin ************) + @echo "" | gnokey maketx call -pkgpath gno.land/r/gnoswap/v1/gov/staker -func Undelegate -args $(ADDR_GSA) -args 1000000000 -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin + @echo + +collect-undelegated: + $(info ************ collect undelegated // gnoswap_admin ************) + @echo "" | gnokey maketx call -pkgpath gno.land/r/gnoswap/v1/gov/staker -func CollectUndelegated -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin + @echo + +collect-gov-reward: + # GRC20 TRANSFER TO PF + @echo "" | gnokey maketx call -pkgpath gno.land/r/onbloc/bar -func Transfer -args $(ADDR_PROTOCOL_FEE) -args "1000000000" -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin + @echo "" | gnokey maketx call -pkgpath gno.land/r/onbloc/baz -func Transfer -args $(ADDR_PROTOCOL_FEE) -args "1000000000" -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin + @echo "" | gnokey maketx call -pkgpath gno.land/r/onbloc/foo -func Transfer -args $(ADDR_PROTOCOL_FEE) -args "1000000000" -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin + @echo "" | gnokey maketx call -pkgpath gno.land/r/onbloc/obl -func Transfer -args $(ADDR_PROTOCOL_FEE) -args "1000000000" -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin + @echo "" | gnokey maketx call -pkgpath gno.land/r/onbloc/qux -func Transfer -args $(ADDR_PROTOCOL_FEE) -args "1000000000" -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin + @echo "" | gnokey maketx call -pkgpath gno.land/r/onbloc/usdc -func Transfer -args $(ADDR_PROTOCOL_FEE) -args "1000000000" -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin + @echo "" + + $(info ************ collect reward // gnoswap_admin ************) + @echo "" | gnokey maketx call -pkgpath gno.land/r/gnoswap/v1/gov/staker -func CollectReward -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin + @echo + + +propose-text: + $(info ************ propose text // gnoswap_admin ************) + @echo "" | gnokey maketx call -pkgpath gno.land/r/gnoswap/v1/gov/governance -func ProposeText -args "title_for_text" -args "desc_for_text" -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin + @echo + +cancel-text: + $(info ************ cancel text // gnoswap_admin ************) + @echo "" | gnokey maketx call -pkgpath gno.land/r/gnoswap/v1/gov/governance -func Cancel -args 1 -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin + @echo + + +propose-community: + $(info ************ propose community pool spend // gnoswap_admin ************) + @echo "" | gnokey maketx call -pkgpath gno.land/r/gnoswap/v1/gov/governance -func ProposeCommunityPoolSpend -args "title_for_spend" -args "desc_for_spend" -args $(ADDR_GSA) -args "gno.land/r/gnoswap/v1/gns" -args 1 -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin + @echo + +vote-community: + $(info ************ vote community pool spend // gnoswap_admin ************) + @echo "" | gnokey maketx call -pkgpath gno.land/r/gnoswap/v1/gov/governance -func Vote -args 2 -args true -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin + @echo + +execute-community: + $(info ************ execute community pool spend // gnoswap_admin ************) + @echo "" | gnokey maketx call -pkgpath gno.land/r/gnoswap/v1/gov/governance -func Execute -args 2 -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin + @echo + + +propose-param: + $(info ************ propose param change // gnoswap_admin ************) + @echo "" | gnokey maketx call -pkgpath gno.land/r/gnoswap/v1/gov/governance -func ProposeParameterChange -args "title param change" -args "desc param change" -args "2" -args "gno.land/r/gnoswap/v1/gns*EXE*SetAvgBlockTimeInMs*EXE*123*GOV*gno.land/r/gnoswap/v1/community_pool*EXE*TransferToken*EXE*gno.land/r/gnoswap/v1/gns,g17290cwvmrapvp869xfnhhawa8sm9edpufzat7d,905" -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin + @echo + +vote-param: + $(info ************ vote param change // gnoswap_admin ************) + @echo "" | gnokey maketx call -pkgpath gno.land/r/gnoswap/v1/gov/governance -func Vote -args 3 -args true -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin + @echo + +execute-param: + $(info ************ execute param change // gnoswap_admin ************) + @echo "" | gnokey maketx call -pkgpath gno.land/r/gnoswap/v1/gov/governance -func Execute -args 3 -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin + @echo + + + +gov-reconfigure: + $(info ************ change governance config // gnoswap_admin ************) + @echo "" | gnokey maketx call -pkgpath gno.land/r/gnoswap/v1/gov/governance -func Reconfigure -args 123 -args 456 -args 789 -args 1234 -args 5678 -args 9012 -args 3456 -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin + @echo + + +## TRANSFER FOR QA +ADDR_ROH := g16a7etgm9z2r653ucl36rj0l2yqcxgrz2jyegzx +transfer-roh: + $(info ************ TO roh account // transfer COIN(ugnot), GRC20(bar, baz, foo, obl, qux, usdc) ************) + @echo "" | gnokey maketx send -send 10000000000ugnot -to $(ADDR_ROH) -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" test1 + @echo "" | gnokey maketx call -pkgpath gno.land/r/onbloc/bar -func Transfer -args $(ADDR_ROH) -args "1000000000" -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin + @echo "" | gnokey maketx call -pkgpath gno.land/r/onbloc/baz -func Transfer -args $(ADDR_ROH) -args "1000000000" -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin + @echo "" | gnokey maketx call -pkgpath gno.land/r/onbloc/foo -func Transfer -args $(ADDR_ROH) -args "1000000000" -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin + @echo "" | gnokey maketx call -pkgpath gno.land/r/onbloc/obl -func Transfer -args $(ADDR_ROH) -args "1000000000" -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin + @echo "" | gnokey maketx call -pkgpath gno.land/r/onbloc/qux -func Transfer -args $(ADDR_ROH) -args "1000000000" -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin + @echo "" | gnokey maketx call -pkgpath gno.land/r/onbloc/usdc -func Transfer -args $(ADDR_ROH) -args "1000000000" -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin + @echo "" + +ADDR_TEST1 := g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5 +transfer-test1: + $(info ************ TO test1 account // transfer COIN(ugnot), GRC20(bar, baz, foo, obl, qux, usdc) ************) + @echo "" | gnokey maketx send -send 10000000000ugnot -to $(ADDR_TEST1) -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" test1 + @echo "" | gnokey maketx call -pkgpath gno.land/r/onbloc/bar -func Transfer -args $(ADDR_TEST1) -args "1000000000" -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin + @echo "" | gnokey maketx call -pkgpath gno.land/r/onbloc/baz -func Transfer -args $(ADDR_TEST1) -args "1000000000" -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin + @echo "" | gnokey maketx call -pkgpath gno.land/r/onbloc/foo -func Transfer -args $(ADDR_TEST1) -args "1000000000" -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin + @echo "" | gnokey maketx call -pkgpath gno.land/r/onbloc/obl -func Transfer -args $(ADDR_TEST1) -args "1000000000" -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin + @echo "" | gnokey maketx call -pkgpath gno.land/r/onbloc/qux -func Transfer -args $(ADDR_TEST1) -args "1000000000" -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin + @echo "" | gnokey maketx call -pkgpath gno.land/r/onbloc/usdc -func Transfer -args $(ADDR_TEST1) -args "1000000000" -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin + @echo "" \ No newline at end of file diff --git a/__local/test/launchpad_test.mk b/__local/test/launchpad_test.mk new file mode 100644 index 000000000..a0fdef7be --- /dev/null +++ b/__local/test/launchpad_test.mk @@ -0,0 +1,503 @@ +# make -f __local/test/launchpad.mk init init-test launchpad-test + +ADDR_GSA := g17290cwvmrapvp869xfnhhawa8sm9edpufzat7d +ADDR_REGISTER := g1er355fkjksqpdtwmhf5penwa82p0rhqxkkyhk5 + +ADDR_LP01 := g1qf5863trkaq447zr2xdmql83g0twzl37dm9qqt +ADDR_LP02 := g1ta0w7j4f586kwqu584z5h5sjurzywz3na7qg0a + +ADDR_TR01 := g14m6fj3t8005u77ku6zyzazq9vd9hwhl00ppt8j # use this as project's recipient for testing launchpad + +# use this as project's recipient for testing launchpad +ADDR_QA01 := g1kcdd3n0d472g2p5l8svyg9t0wq6h5857nq992f +ADDR_QA02 := g1d598tyfatprdstalqutk62cnzpm3thvyy9mypg +ADDR_QA03 := g1w4qqmxdk59xsh3x5hnp2z78s4ymyva8pnenfem +ADDR_QA04 := g14supzhx0v5sza947sdh4x74wnws9xvcfwdecef +ADDR_QA05 := g1apl4u79zhexrxcf4h48y5qlyjncskdlrxtz6vg +ADDR_QA06 := g12g569s05c293zu2kxk0z426yylxmmthx8hcudd +ADDR_QA07 := g1dag2p05ax7s2dvmj77j0tgfez4duspdyeh48pv + +ADDR_POOL := g148tjamj80yyrm309z7rk690an22thd2l3z8ank +ADDR_POSITION := g1q646ctzhvn60v492x8ucvyqnrj2w30cwh6efk5 +ADDR_ROUTER := g1lm2l7tf49h3mykesct7rhfml30yx8dw5xrval7 +ADDR_STAKER := g1cceshmzzlmrh7rr3z30j2t5mrvsq9yccysw9nu +ADDR_PROTOCOL_FEE := g1f7wpek7q67tkns27sw495u5yuu3a5wwjxw5l6l + +ADDR_GOV_STAKER := g17e3ykyqk9jmqe2y9wxe9zhep3p7cw56davjqwa +ADR_GOV_GOV := g17s8w2ve7k85fwfnrk59lmlhthkjdted8whvqxd + +ADDR_LAUNCHPAD := g122mau2lp2rc0scs8d27pkkuys4w54mdy2tuer3 + +## TOKENS +ADDR_GNS := g1jgqwaa2le3yr63d533fj785qkjspumzv22ys5m +ADDR_GNFT := g1wxv2rdfn53qc84nt3nn646f9yh3nly8lm7j89t + +ADDR_WUGNOT := g1pf6dv9fjk3rn0m4jjcne306ga4he3mzmupfjl6 + + +MAX_UINT64 := 18446744073709551615 +TX_EXPIRE := 9999999999 + +MAKEFILE := $(shell realpath $(firstword $(MAKEFILE_LIST))) + +# GNOLAND_RPC_URL ?= http://localhost:26657 +# CHAINID ?= dev + + +GNOLAND_RPC_URL ?= https://dev.rpc.gnoswap.io:443 +CHAINID ?= dev.gnoswap + +ROOT_DIR:=$(shell dirname $(MAKEFILE))/../../ + + +## INIT +.PHONY: init +init: wait send-ugnot-must deploy-libraries deploy-base-tokens deploy-gnoswap-realms deploy-test-tokens register-token pool-create-gns-wugnot-default + +.PHONY: deploy-libraries +deploy-libraries: deploy-uint256 deploy-int256 deploy-consts deploy-package-pool deploy-common + +.PHONY: deploy-base-tokens +deploy-base-tokens: deploy-gns deploy-usdc deploy-gnft + +.PHONY: deploy-test-tokens +deploy-test-tokens: deploy-foo deploy-bar deploy-baz deploy-qux deploy-obl + +.PHONY: deploy-gnoswap-realms +deploy-gnoswap-realms: deploy-xgns deploy-emission deploy-pool deploy-position deploy-staker deploy-router deploy-community_pool deploy-protocol_fee deploy-gov-staker deploy-gov-governance deploy-launchpad + +### TEST AFTER INIT +.PHONY: init-test +init-test: test-position-mint test-stake-token + +.PHONY: test-pool-create +test-pool-create: pool-create-bar-baz pool-create-foo-qux + +.PHONY: test-position-mint +test-position-mint: mint-gns-gnot + +.PHONY: test-stake-token +test-stake-token: stake-token-1 + +## TEST GOV +.PHONY: gov-test +gov-test: gov-staker gov-propose-proposals gov-propose-cancel # gov-vote gov-execute + +.PHONY: gov-staker +gov-staker: delegate-1 delegate-2 redelegate undelegate collect-undelegated collect-gov-reward + +.PHONY: gov-propose-proposals +gov-propose-proposals: propose-text propose-community propose-param + +.PHONY: gov-propose-cancel +gov-propose-cancel: cancel-text + +.PHONY: gov-vote +gov-toe: vote-community vote-param + +.PHONY: gov-execute +gov-execute: execute-community execute-param + +## TEST LAUNCHPAD +.PHONY: launchpad-test +launchpad-test: launchpad-create-project # launchpad-deposit launchpad-collect-protocol launchpad-collect-reward # use return value from create-project(project_id) to deposit, collect-protocol, collect-reward + +# wait chain to start +wait: + $(info ************ [ETC] wait 1 seconds for chain to start ************) + $(shell sleep 1) + @echo + + +# send ugnot to necessary accounts +send-ugnot-must: + $(info ************ send ugnot to necessary accounts ************) + @echo "" | gnokey maketx send -send 10000000000ugnot -to $(ADDR_GSA) -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" test1 + @echo "" | gnokey maketx send -send 10000000000ugnot -to $(ADDR_PROTOCOL_FEE) -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" test1 + @echo "" | gnokey maketx send -send 10000000000ugnot -to $(ADDR_REGISTER) -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" test1 + @echo "" | gnokey maketx send -send 10000000000ugnot -to $(ADDR_TR01) -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" test1 + @echo + +dummy-tx: + @echo "" | gnokey maketx send -send 1ugnot -to $(ADDR_GSA) -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" test1 + +# deploy test grc20 tokens +deploy-foo: + $(info ************ deploy foo ************) + @echo "" | gnokey maketx addpkg -pkgdir $(ROOT_DIR)/__local/grc20_tokens/onbloc/foo -pkgpath gno.land/r/onbloc/foo -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin + @echo + +deploy-bar: + $(info ************ deploy bar ************) + @echo "" | gnokey maketx addpkg -pkgdir $(ROOT_DIR)/__local/grc20_tokens/onbloc/bar -pkgpath gno.land/r/onbloc/bar -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin + @echo + +deploy-baz: + $(info ************ deploy baz ************) + @echo "" | gnokey maketx addpkg -pkgdir $(ROOT_DIR)/__local/grc20_tokens/onbloc/baz -pkgpath gno.land/r/onbloc/baz -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin + @echo + +deploy-qux: + $(info ************ deploy qux ************) + @echo "" | gnokey maketx addpkg -pkgdir $(ROOT_DIR)/__local/grc20_tokens/onbloc/qux -pkgpath gno.land/r/onbloc/qux -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin + @echo + +deploy-obl: + $(info ************ deploy obl ************) + @echo "" | gnokey maketx addpkg -pkgdir $(ROOT_DIR)/__local/grc20_tokens/onbloc/obl -pkgpath gno.land/r/onbloc/obl -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin + @echo + + +# deploy base tokens +deploy-gns: + $(info ************ deploy gns ************) + @echo "" | gnokey maketx addpkg -pkgdir $(ROOT_DIR)/_deploy/r/gnoswap/gns -pkgpath gno.land/r/gnoswap/v1/gns -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin + @echo + +deploy-gnft: + $(info ************ deploy gnft ************) + @echo "" | gnokey maketx addpkg -pkgdir $(ROOT_DIR)/_deploy/r/gnoswap/gnft -pkgpath gno.land/r/gnoswap/v1/gnft -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin + @echo + +deploy-usdc: + $(info ************ deploy usdc ************) + @echo "" | gnokey maketx addpkg -pkgdir $(ROOT_DIR)/__local/grc20_tokens/onbloc/usdc -pkgpath gno.land/r/onbloc/usdc -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin + @echo + + +# deploy packages +deploy-uint256: + $(info ************ deploy uint256 ************) + @echo "" | gnokey maketx addpkg -pkgdir $(ROOT_DIR)/_deploy/p/gnoswap/uint256 -pkgpath gno.land/p/gnoswap/uint256 -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin + @echo + +deploy-int256: + $(info ************ deploy int256 ************) + @echo "" | gnokey maketx addpkg -pkgdir $(ROOT_DIR)/_deploy/p/gnoswap/int256 -pkgpath gno.land/p/gnoswap/int256 -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin + @echo + +deploy-package-pool: + $(info ************ deploy package pool ************) + @echo "" | gnokey maketx addpkg -pkgdir $(ROOT_DIR)/_deploy/p/gnoswap/pool -pkgpath gno.land/p/gnoswap/pool -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin + @echo + + +# deploy common realms +deploy-consts: + $(info ************ deploy consts ************) + @echo "" | gnokey maketx addpkg -pkgdir $(ROOT_DIR)/_deploy/r/gnoswap/consts -pkgpath gno.land/r/gnoswap/v1/consts -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin + @echo + +deploy-common: + $(info ************ deploy common ************) + @echo "" | gnokey maketx addpkg -pkgdir $(ROOT_DIR)/_deploy/r/gnoswap/common -pkgpath gno.land/r/gnoswap/v1/common -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin + @echo + + +# deploy gnoswap realms +deploy-xgns: + $(info ************ deploy xgns ************) + @echo "" | gnokey maketx addpkg -pkgdir $(ROOT_DIR)/gov/xgns -pkgpath gno.land/r/gnoswap/v1/gov/xgns -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin + @echo + +deploy-emission: + $(info ************ deploy emission ************) + @echo "" | gnokey maketx addpkg -pkgdir $(ROOT_DIR)/emission -pkgpath gno.land/r/gnoswap/v1/emission -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin + @echo + +deploy-pool: + $(info ************ deploy pool ************) + @echo "" | gnokey maketx addpkg -pkgdir $(ROOT_DIR)/pool -pkgpath gno.land/r/gnoswap/v1/pool -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin + @echo + +deploy-position: + $(info ************ deploy position ************) + @echo "" | gnokey maketx addpkg -pkgdir $(ROOT_DIR)/position -pkgpath gno.land/r/gnoswap/v1/position -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin + @echo + +deploy-router: + $(info ************ deploy router ************) + @echo "" | gnokey maketx addpkg -pkgdir $(ROOT_DIR)/router -pkgpath gno.land/r/gnoswap/v1/router -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin + @echo + +deploy-staker: + $(info ************ deploy staker ************) + @echo "" | gnokey maketx addpkg -pkgdir $(ROOT_DIR)/staker -pkgpath gno.land/r/gnoswap/v1/staker -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin + @echo + +deploy-community_pool: + $(info ************ deploy community_pool ************) + @echo "" | gnokey maketx addpkg -pkgdir $(ROOT_DIR)/community_pool -pkgpath gno.land/r/gnoswap/v1/community_pool -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin + @echo + +deploy-protocol_fee: + $(info ************ deploy protocol_fee ************) + @echo "" | gnokey maketx addpkg -pkgdir $(ROOT_DIR)/protocol_fee -pkgpath gno.land/r/gnoswap/v1/protocol_fee -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin + @echo + +deploy-gov-staker: + $(info ************ deploy gov/staker ************) + @echo "" | gnokey maketx addpkg -pkgdir $(ROOT_DIR)/gov/staker -pkgpath gno.land/r/gnoswap/v1/gov/staker -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin + @echo + +deploy-gov-governance: + $(info ************ deploy gov/governance ************) + @echo "" | gnokey maketx addpkg -pkgdir $(ROOT_DIR)/gov/governance -pkgpath gno.land/r/gnoswap/v1/gov/governance -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin + @echo + +deploy-launchpad: + $(info ************ deploy launchpad ************) + @echo "" | gnokey maketx addpkg -pkgdir $(ROOT_DIR)/launchpad -pkgpath gno.land/r/gnoswap/v1/launchpad -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin + @echo + +# Register +register-token: + $(info ************ deploy register_gnodev ************) + @echo "" | gnokey maketx addpkg -pkgdir $(ROOT_DIR)/__local/grc20_tokens/g1er355fkjksqpdtwmhf5penwa82p0rhqxkkyhk5/register_gnodev -pkgpath gno.land/r/g1er355fkjksqpdtwmhf5penwa82p0rhqxkkyhk5/v2/register_gnodev -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" register + @echo + +# default pool create +pool-create-gns-wugnot-default: + $(info ************ create default pool (GNS:WUGNOT:0.03%) ************) + # APPROVE + @echo "" | gnokey maketx call -pkgpath gno.land/r/gnoswap/v1/gns -func Approve -args $(ADDR_POOL) -args $(MAX_UINT64) -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin + + + # tick 0 ≈ x1 ≈ 79228162514264337593543950337 + @echo "" | gnokey maketx call -pkgpath gno.land/r/gnoswap/v1/pool -func CreatePool -args "gno.land/r/demo/wugnot" -args "gno.land/r/gnoswap/v1/gns" -args 3000 -args 79228162514264337593543950337 -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin + @echo + +# test pool create +pool-create-bar-baz: + $(info ************ create pool bar:baz ************) + # tick -10 ≈ x0.99900054978007157835406815138412639498710632324219 ≈ 79188560314459151373725315960 + @echo "" | gnokey maketx call -pkgpath gno.land/r/gnoswap/v1/pool -func CreatePool -args "gno.land/r/onbloc/bar" -args "gno.land/r/onbloc/baz" -args 100 -args 79188560314459151373725315960 -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin + @echo + +pool-create-foo-qux: + $(info ************ create pool foo:qux ************) + # tick -10 ≈ x0.99900054978007157835406815138412639498710632324219 ≈ 79188560314459151373725315960 + @echo "" | gnokey maketx call -pkgpath gno.land/r/gnoswap/v1/pool -func CreatePool -args "gno.land/r/onbloc/foo" -args "gno.land/r/onbloc/qux" -args 100 -args 79188560314459151373725315960 -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin + @echo + +mint-gns-gnot: + $(info ************ mint position(1) to gns:wugnot // gnoswap_admin ************) + # APPROVE FISRT + @echo "" | gnokey maketx call -pkgpath gno.land/r/gnoswap/v1/gns -func Approve -args $(ADDR_POOL) -args $(MAX_UINT64) -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin + @echo "" | gnokey maketx call -pkgpath gno.land/r/demo/wugnot -func Approve -args $(ADDR_POOL) -args $(MAX_UINT64) -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin + @echo + + # APPROVE WUGNOT TO POSITION, to get refund wugnot left after wrap -> mint + @echo "" | gnokey maketx call -pkgpath gno.land/r/demo/wugnot -func Approve -args $(ADDR_POSITION) -args $(MAX_UINT64) -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin + + # THEN MINT + @echo "" | gnokey maketx call -pkgpath gno.land/r/gnoswap/v1/position -func Mint -send "20000000ugnot" -args "gno.land/r/gnoswap/v1/gns" -args "gnot" -args 3000 -args "-49980" -args "49980" -args 20000000 -args 20000000 -args 1 -args 1 -args $(TX_EXPIRE) -args $(ADDR_GSA) -args $(ADDR_GSA) -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin + @echo + + # Set-Token-Uri + @echo "" | gnokey maketx call -pkgpath gno.land/r/gnoswap/v1/gnft -func SetTokenURILast -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin + @echo + +stake-token-1: + $(info ************ stake token 1 // gnoswap_admin ************) + @echo "" | gnokey maketx call -pkgpath gno.land/r/gnoswap/v1/gnft -func Approve -args $(ADDR_STAKER) -args 1 -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin + @echo "" | gnokey maketx call -pkgpath gno.land/r/gnoswap/v1/staker -func StakeToken -args 1 -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin + @echo + + +delegate-1: + $(info ************ delegate 1_000_000_000 to self // gnoswap_admin ************) + # APPROVE FIRST + @echo "" | gnokey maketx call -pkgpath gno.land/r/gnoswap/v1/gns -func Approve -args $(ADDR_GOV_STAKER) -args $(MAX_UINT64) -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin + + # DELEGATE + @echo "" | gnokey maketx call -pkgpath gno.land/r/gnoswap/v1/gov/staker -func Delegate -args $(ADDR_GSA) -args 1000000000 -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin + @echo + +delegate-2: + $(info ************ delegate 1_500_000_000 to lp_01 // gnoswap_admin ************) + # APPROVE FIRST + @echo "" | gnokey maketx call -pkgpath gno.land/r/gnoswap/v1/gns -func Approve -args $(ADDR_GOV_STAKER) -args $(MAX_UINT64) -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin + + # DELEGATE + @echo "" | gnokey maketx call -pkgpath gno.land/r/gnoswap/v1/gov/staker -func Delegate -args $(ADDR_LP01) -args 1500000000 -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin + @echo + +redelegate: + $(info ************ redelegate 1_000_000_000 from lp_01 to self // gnoswap_admin ************) + @echo "" | gnokey maketx call -pkgpath gno.land/r/gnoswap/v1/gov/staker -func Redelegate -args $(ADDR_LP01) -args $(ADDR_GSA) -args 1000000000 -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin + @echo + +undelegate: + $(info ************ undelegate 1_000_000_000 from self // gnoswap_admin ************) + @echo "" | gnokey maketx call -pkgpath gno.land/r/gnoswap/v1/gov/staker -func Undelegate -args $(ADDR_GSA) -args 1000000000 -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin + @echo + +collect-undelegated: + $(info ************ collect undelegated // gnoswap_admin ************) + @echo "" | gnokey maketx call -pkgpath gno.land/r/gnoswap/v1/gov/staker -func CollectUndelegated -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin + @echo + +collect-gov-reward: + # GRC20 TRANSFER TO PF + @echo "" | gnokey maketx call -pkgpath gno.land/r/onbloc/bar -func Transfer -args $(ADDR_PROTOCOL_FEE) -args "1000000000" -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin + @echo "" | gnokey maketx call -pkgpath gno.land/r/onbloc/baz -func Transfer -args $(ADDR_PROTOCOL_FEE) -args "1000000000" -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin + @echo "" | gnokey maketx call -pkgpath gno.land/r/onbloc/foo -func Transfer -args $(ADDR_PROTOCOL_FEE) -args "1000000000" -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin + @echo "" | gnokey maketx call -pkgpath gno.land/r/onbloc/obl -func Transfer -args $(ADDR_PROTOCOL_FEE) -args "1000000000" -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin + @echo "" | gnokey maketx call -pkgpath gno.land/r/onbloc/qux -func Transfer -args $(ADDR_PROTOCOL_FEE) -args "1000000000" -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin + @echo "" | gnokey maketx call -pkgpath gno.land/r/onbloc/usdc -func Transfer -args $(ADDR_PROTOCOL_FEE) -args "1000000000" -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin + @echo "" + + $(info ************ collect reward // gnoswap_admin ************) + @echo "" | gnokey maketx call -pkgpath gno.land/r/gnoswap/v1/gov/staker -func CollectReward -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin + @echo + + +propose-text: + $(info ************ propose text // gnoswap_admin ************) + @echo "" | gnokey maketx call -pkgpath gno.land/r/gnoswap/v1/gov/governance -func ProposeText -args "title_for_text" -args "desc_for_text" -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin + @echo + +cancel-text: + $(info ************ cancel text // gnoswap_admin ************) + @echo "" | gnokey maketx call -pkgpath gno.land/r/gnoswap/v1/gov/governance -func Cancel -args 1 -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin + @echo + + +propose-community: + $(info ************ propose community pool spend // gnoswap_admin ************) + @echo "" | gnokey maketx call -pkgpath gno.land/r/gnoswap/v1/gov/governance -func ProposeCommunityPoolSpend -args "title_for_spend" -args "desc_for_spend" -args $(ADDR_GSA) -args "gno.land/r/gnoswap/v1/gns" -args 1 -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin + @echo + +vote-community: + $(info ************ vote community pool spend // gnoswap_admin ************) + @echo "" | gnokey maketx call -pkgpath gno.land/r/gnoswap/v1/gov/governance -func Vote -args 2 -args true -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin + @echo + +execute-community: + $(info ************ execute community pool spend // gnoswap_admin ************) + @echo "" | gnokey maketx call -pkgpath gno.land/r/gnoswap/v1/gov/governance -func Execute -args 2 -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin + @echo + + +propose-param: + $(info ************ propose param change // gnoswap_admin ************) + @echo "" | gnokey maketx call -pkgpath gno.land/r/gnoswap/v1/gov/governance -func ProposeParameterChange -args "title param change" -args "desc param change" -args "2" -args "gno.land/r/gnoswap/v1/gns*EXE*SetAvgBlockTimeInMs*EXE*123*GOV*gno.land/r/gnoswap/v1/community_pool*EXE*TransferToken*EXE*gno.land/r/gnoswap/v1/gns,g17290cwvmrapvp869xfnhhawa8sm9edpufzat7d,905" -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin + @echo + +vote-param: + $(info ************ vote param change // gnoswap_admin ************) + @echo "" | gnokey maketx call -pkgpath gno.land/r/gnoswap/v1/gov/governance -func Vote -args 3 -args true -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin + @echo + +execute-param: + $(info ************ execute param change // gnoswap_admin ************) + @echo "" | gnokey maketx call -pkgpath gno.land/r/gnoswap/v1/gov/governance -func Execute -args 3 -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin + @echo + +gov-reconfigure: + $(info ************ change governance config // gnoswap_admin ************) + @echo "" | gnokey maketx call -pkgpath gno.land/r/gnoswap/v1/gov/governance -func Reconfigure -args 123 -args 456 -args 789 -args 1234 -args 5678 -args 9012 -args 3456 -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin + @echo + +## LAUNCHPAD QA +launchpad-qa: + ## APPROVE + @echo "" | gnokey maketx call -pkgpath gno.land/r/onbloc/obl -func Approve -args $(ADDR_LAUNCHPAD) -args $(MAX_UINT64) -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin + + ## CREATE PROJECT + $(info ************ create project // gnoswap_admin ************) + @echo "" | gnokey maketx call -pkgpath gno.land/r/gnoswap/v1/launchpad -func CreateProject -args "gno.land/r/onbloc/obl" -args $(ADDR_QA01) -args 1000000000000 -args "" -args "" -args 50 -args 30 -args 20 -args 1728975600 -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin + + ## APPROVE + @echo "" | gnokey maketx call -pkgpath gno.land/r/onbloc/foo -func Approve -args $(ADDR_LAUNCHPAD) -args $(MAX_UINT64) -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin + + ## CREATE PROJECT + $(info ************ create project // gnoswap_admin ************) + @echo "" | gnokey maketx call -pkgpath gno.land/r/gnoswap/v1/launchpad -func CreateProject -args "gno.land/r/onbloc/foo" -args $(ADDR_QA02) -args 2000000000000 -args "" -args "" -args 50 -args 25 -args 25 -args 1728975600 -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin + + ## APPROVE + @echo "" | gnokey maketx call -pkgpath gno.land/r/onbloc/baz -func Approve -args $(ADDR_LAUNCHPAD) -args $(MAX_UINT64) -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin + + ## CREATE PROJECT + $(info ************ create project // gnoswap_admin ************) + @echo "" | gnokey maketx call -pkgpath gno.land/r/gnoswap/v1/launchpad -func CreateProject -args "gno.land/r/onbloc/baz" -args $(ADDR_QA03) -args 2500000000000 -args "" -args "" -args 60 -args 25 -args 15 -args 1728975600 -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin + + ## APPROVE + @echo "" | gnokey maketx call -pkgpath gno.land/r/onbloc/bar -func Approve -args $(ADDR_LAUNCHPAD) -args $(MAX_UINT64) -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin + + ## CREATE PROJECT + $(info ************ create project // gnoswap_admin ************) + @echo "" | gnokey maketx call -pkgpath gno.land/r/gnoswap/v1/launchpad -func CreateProject -args "gno.land/r/onbloc/bar" -args $(ADDR_QA04) -args 1000000000000 -args "" -args "" -args 40 -args 40 -args 20 -args 1728975600 -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin + + ## APPROVE + @echo "" | gnokey maketx call -pkgpath gno.land/r/onbloc/qux -func Approve -args $(ADDR_LAUNCHPAD) -args $(MAX_UINT64) -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin + + ## CREATE PROJECT + $(info ************ create project // gnoswap_admin ************) + @echo "" | gnokey maketx call -pkgpath gno.land/r/gnoswap/v1/launchpad -func CreateProject -args "gno.land/r/onbloc/qux" -args $(ADDR_QA05) -args 2000000000000 -args "" -args "" -args 50 -args 30 -args 20 -args 1729040400 -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin + + ## APPROVE + @echo "" | gnokey maketx call -pkgpath gno.land/r/onbloc/qux -func Approve -args $(ADDR_LAUNCHPAD) -args $(MAX_UINT64) -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin + + ## CREATE PROJECT + $(info ************ create project // gnoswap_admin ************) + @echo "" | gnokey maketx call -pkgpath gno.land/r/gnoswap/v1/launchpad -func CreateProject -args "gno.land/r/onbloc/qux" -args $(ADDR_QA06) -args 5000000000000 -args "gno.land/r/gnoswap/v1/gov/xgns*PAD*gno.land/r/onbloc/usdc" -args "100000000*PAD*200000000" -args 50 -args 30 -args 20 -args 1729047600 -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin + + ## APPROVE + @echo "" | gnokey maketx call -pkgpath gno.land/r/onbloc/obl -func Approve -args $(ADDR_LAUNCHPAD) -args $(MAX_UINT64) -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin + + ## CREATE PROJECT + $(info ************ create project // gnoswap_admin ************) + @echo "" | gnokey maketx call -pkgpath gno.land/r/gnoswap/v1/launchpad -func CreateProject -args "gno.land/r/onbloc/obl" -args $(ADDR_QA07) -args 1500000000000 -args "gno.land/r/gnoswap/v1/gov/xgns" -args "500000000" -args 50 -args 30 -args 20 -args 1729126800 -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin + + +## LAUNCHPAD +launchpad-create-project: + ## APPROVE + @echo "" | gnokey maketx call -pkgpath gno.land/r/onbloc/obl -func Approve -args $(ADDR_LAUNCHPAD) -args $(MAX_UINT64) -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin + + ## CREATE PROJECT + $(info ************ create project // gnoswap_admin ************) + @echo "" | gnokey maketx call -pkgpath gno.land/r/gnoswap/v1/launchpad -func CreateProject -args "gno.land/r/onbloc/obl" -args $(ADDR_TR01) -args 1000000000 -args "" -args "" -args 10 -args 20 -args 70 -args $(shell echo $$(($(shell date +%s) + 10))) -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin + +launchpad-deposit: + ## APPROVE + @echo "" | gnokey maketx call -pkgpath gno.land/r/gnoswap/v1/gns -func Approve -args $(ADDR_LAUNCHPAD) -args $(MAX_UINT64) -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin + + ## DEPOSIT TO PROJECT ( tier 30 ) + $(info ************ deposit to project // gnoswap_admin ************) + @echo "" | gnokey maketx call -pkgpath gno.land/r/gnoswap/v1/launchpad -func DepositGns -args "gno.land/r/onbloc/obl:62:30" -args 1000000 -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin + +launchpad-collect-protocol: + $(info ************ collect protocol fee by projects recipients // tr01 ************) + @echo "" | gnokey maketx call -pkgpath gno.land/r/gnoswap/v1/launchpad -func CollectProtocolFee -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_tr01 + +launchpad-collect-reward: + $(info ************ collect reward bt project id // gnoswap_admin ************) + @echo "" | gnokey maketx call -pkgpath gno.land/r/gnoswap/v1/launchpad -func CollectRewardByProjectId -args "gno.land/r/onbloc/obl:62" -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin + +## TRANSFER FOR QA +ADDR_ROH := g16a7etgm9z2r653ucl36rj0l2yqcxgrz2jyegzx +transfer-roh: + $(info ************ TO roh account // transfer COIN(ugnot), GRC20(bar, baz, foo, obl, qux, usdc) ************) + @echo "" | gnokey maketx send -send 10000000000ugnot -to $(ADDR_ROH) -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" test1 + @echo "" | gnokey maketx call -pkgpath gno.land/r/onbloc/bar -func Transfer -args $(ADDR_ROH) -args "1000000000" -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin + @echo "" | gnokey maketx call -pkgpath gno.land/r/onbloc/baz -func Transfer -args $(ADDR_ROH) -args "1000000000" -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin + @echo "" | gnokey maketx call -pkgpath gno.land/r/onbloc/foo -func Transfer -args $(ADDR_ROH) -args "1000000000" -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin + @echo "" | gnokey maketx call -pkgpath gno.land/r/onbloc/obl -func Transfer -args $(ADDR_ROH) -args "1000000000" -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin + @echo "" | gnokey maketx call -pkgpath gno.land/r/onbloc/qux -func Transfer -args $(ADDR_ROH) -args "1000000000" -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin + @echo "" | gnokey maketx call -pkgpath gno.land/r/onbloc/usdc -func Transfer -args $(ADDR_ROH) -args "1000000000" -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin + @echo "" + +ADDR_TEST1 := g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5 +transfer-test1: + $(info ************ TO test1 account // transfer COIN(ugnot), GRC20(bar, baz, foo, obl, qux, usdc) ************) + @echo "" | gnokey maketx send -send 10000000000ugnot -to $(ADDR_TEST1) -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" test1 + @echo "" | gnokey maketx call -pkgpath gno.land/r/onbloc/bar -func Transfer -args $(ADDR_TEST1) -args "1000000000" -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin + @echo "" | gnokey maketx call -pkgpath gno.land/r/onbloc/baz -func Transfer -args $(ADDR_TEST1) -args "1000000000" -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin + @echo "" | gnokey maketx call -pkgpath gno.land/r/onbloc/foo -func Transfer -args $(ADDR_TEST1) -args "1000000000" -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin + @echo "" | gnokey maketx call -pkgpath gno.land/r/onbloc/obl -func Transfer -args $(ADDR_TEST1) -args "1000000000" -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin + @echo "" | gnokey maketx call -pkgpath gno.land/r/onbloc/qux -func Transfer -args $(ADDR_TEST1) -args "1000000000" -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin + @echo "" | gnokey maketx call -pkgpath gno.land/r/onbloc/usdc -func Transfer -args $(ADDR_TEST1) -args "1000000000" -insecure-password-stdin=true -remote $(GNOLAND_RPC_URL) -broadcast=true -chainid $(CHAINID) -gas-fee 1ugnot -gas-wanted 100000000 -memo "" gnoswap_admin + @echo "" diff --git a/_deploy/p/gnoswap/int256/LICENSE b/_deploy/p/gnoswap/int256/LICENSE new file mode 100644 index 000000000..fc7e78a48 --- /dev/null +++ b/_deploy/p/gnoswap/int256/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 Trịnh Đức Bảo Linh(Kevin) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/_deploy/p/gnoswap/int256/README.md b/_deploy/p/gnoswap/int256/README.md new file mode 100644 index 000000000..136e64689 --- /dev/null +++ b/_deploy/p/gnoswap/int256/README.md @@ -0,0 +1,6 @@ +# Fixed size signed 256-bit math library + +1. This is a library specialized at replacing the big.Int library for math based on signed 256-bit types. +2. It uses [uint256](https://github.com/gnolang/gno/tree/master/examples/gno.land/p/gnoswap/uint256) as the underlying type. + +ported from [mempooler/int256](https://github.com/mempooler/int256) diff --git a/_deploy/p/gnoswap/int256/absolute.gno b/_deploy/p/gnoswap/int256/absolute.gno new file mode 100644 index 000000000..fbc1f9913 --- /dev/null +++ b/_deploy/p/gnoswap/int256/absolute.gno @@ -0,0 +1,20 @@ +package int256 + +import ( + "gno.land/p/gnoswap/uint256" +) + +// Abs returns |z| +func (z *Int) Abs() *uint256.Uint { + return z.abs.Clone() +} + +// AbsGt returns true if |z| > x, where x is a uint256 +func (z *Int) AbsGt(x *uint256.Uint) bool { + return z.abs.Gt(x) +} + +// AbsLt returns true if |z| < x, where x is a uint256 +func (z *Int) AbsLt(x *uint256.Uint) bool { + return z.abs.Lt(x) +} diff --git a/_deploy/p/gnoswap/int256/absolute_test.gno b/_deploy/p/gnoswap/int256/absolute_test.gno new file mode 100644 index 000000000..05d801310 --- /dev/null +++ b/_deploy/p/gnoswap/int256/absolute_test.gno @@ -0,0 +1,105 @@ +package int256 + +import ( + "testing" + + "gno.land/p/gnoswap/uint256" +) + +func TestAbs(t *testing.T) { + tests := []struct { + x, want string + }{ + {"0", "0"}, + {"1", "1"}, + {"-1", "1"}, + {"-2", "2"}, + {"-115792089237316195423570985008687907853269984665640564039457584007913129639935", "115792089237316195423570985008687907853269984665640564039457584007913129639935"}, + } + + for _, tc := range tests { + x, err := FromDecimal(tc.x) + if err != nil { + t.Error(err) + continue + } + + got := x.Abs() + + if got.ToString() != tc.want { + t.Errorf("Abs(%s) = %v, want %v", tc.x, got.ToString(), tc.want) + } + } +} + +func TestAbsGt(t *testing.T) { + tests := []struct { + x, y, want string + }{ + {"0", "0", "false"}, + {"1", "0", "true"}, + {"-1", "0", "true"}, + {"-1", "1", "false"}, + {"-2", "1", "true"}, + {"-115792089237316195423570985008687907853269984665640564039457584007913129639935", "0", "true"}, + {"-115792089237316195423570985008687907853269984665640564039457584007913129639935", "1", "true"}, + {"-115792089237316195423570985008687907853269984665640564039457584007913129639935", "115792089237316195423570985008687907853269984665640564039457584007913129639935", "false"}, + } + + for _, tc := range tests { + x, err := FromDecimal(tc.x) + if err != nil { + t.Error(err) + continue + } + + y, err := uint256.FromDecimal(tc.y) + if err != nil { + t.Error(err) + continue + } + + got := x.AbsGt(y) + + if got != (tc.want == "true") { + t.Errorf("AbsGt(%s, %s) = %v, want %v", tc.x, tc.y, got, tc.want) + } + } +} + +func TestAbsLt(t *testing.T) { + tests := []struct { + x, y, want string + }{ + {"0", "0", "false"}, + {"1", "0", "false"}, + {"-1", "0", "false"}, + {"-1", "1", "false"}, + {"-2", "1", "false"}, + {"-5", "10", "true"}, + {"31330", "31337", "true"}, + {"-115792089237316195423570985008687907853269984665640564039457584007913129639935", "0", "false"}, + {"-115792089237316195423570985008687907853269984665640564039457584007913129639935", "1", "false"}, + {"-115792089237316195423570985008687907853269984665640564039457584007913129639935", "115792089237316195423570985008687907853269984665640564039457584007913129639935", "false"}, + } + + for _, tc := range tests { + x, err := FromDecimal(tc.x) + if err != nil { + t.Error(err) + continue + } + + y, err := uint256.FromDecimal(tc.y) + if err != nil { + t.Error(err) + continue + } + + got := x.AbsLt(y) + + if got != (tc.want == "true") { + t.Errorf("AbsLt(%s, %s) = %v, want %v", tc.x, tc.y, got, tc.want) + } + } +} diff --git a/_deploy/p/gnoswap/int256/arithmetic.gno b/_deploy/p/gnoswap/int256/arithmetic.gno new file mode 100644 index 000000000..5ffb66166 --- /dev/null +++ b/_deploy/p/gnoswap/int256/arithmetic.gno @@ -0,0 +1,203 @@ +package int256 + +import "gno.land/p/gnoswap/uint256" + +func (z *Int) Add(x, y *Int) *Int { + z.initiateAbs() + + if x.neg == y.neg { + // If both numbers have the same sign, add their absolute values + z.abs.Add(x.abs, y.abs) + z.neg = x.neg + } else { + // If signs are different, subtract the smaller absolute value from the larger + if x.abs.Cmp(y.abs) >= 0 { + z.abs.Sub(x.abs, y.abs) + z.neg = x.neg + } else { + z.abs.Sub(y.abs, x.abs) + z.neg = y.neg + } + } + + // Ensure zero is always positive + if z.abs.IsZero() { + z.neg = false + } + + return z +} + +// AddUint256 set z to the sum x + y, where y is a uint256, and returns z +func (z *Int) AddUint256(x *Int, y *uint256.Uint) *Int { + if x.neg { + if x.abs.Gt(y) { + z.abs.Sub(x.abs, y) + z.neg = true + } else { + z.abs.Sub(y, x.abs) + z.neg = false + } + } else { + z.abs.Add(x.abs, y) + z.neg = false + } + return z +} + +// Sets z to the sum x + y, where z and x are uint256s and y is an int256. +func AddDelta(z, x *uint256.Uint, y *Int) { + if y.neg { + z.Sub(x, y.abs) + } else { + z.Add(x, y.abs) + } +} + +// Sets z to the sum x + y, where z and x are uint256s and y is an int256. +func AddDeltaOverflow(z, x *uint256.Uint, y *Int) bool { + var overflow bool + if y.neg { + _, overflow = z.SubOverflow(x, y.abs) + } else { + _, overflow = z.AddOverflow(x, y.abs) + } + return overflow +} + +// Sub sets z to the difference x-y and returns z. +func (z *Int) Sub(x, y *Int) *Int { + z.initiateAbs() + + if x.neg != y.neg { + // If sign are different, add the absolute values + z.abs.Add(x.abs, y.abs) + z.neg = x.neg + } else { + // If signs are the same, subtract the smaller absolute value from the larger + if x.abs.Cmp(y.abs) >= 0 { + z.abs = z.abs.Sub(x.abs, y.abs) + z.neg = x.neg + } else { + z.abs.Sub(y.abs, x.abs) + z.neg = !x.neg + } + } + + // Ensure zero is always positive + if z.abs.IsZero() { + z.neg = false + } + return z +} + +// SubUint256 set z to the difference x - y, where y is a uint256, and returns z +func (z *Int) SubUint256(x *Int, y *uint256.Uint) *Int { + if x.neg { + z.abs.Add(x.abs, y) + z.neg = true + } else { + if x.abs.Lt(y) { + z.abs.Sub(y, x.abs) + z.neg = true + } else { + z.abs.Sub(x.abs, y) + z.neg = false + } + } + return z +} + +// Mul sets z to the product x*y and returns z. +func (z *Int) Mul(x, y *Int) *Int { + z.initiateAbs() + + z.abs = z.abs.Mul(x.abs, y.abs) + z.neg = x.neg != y.neg && !z.abs.IsZero() // 0 has no sign + return z +} + +// MulUint256 sets z to the product x*y, where y is a uint256, and returns z +func (z *Int) MulUint256(x *Int, y *uint256.Uint) *Int { + z.abs.Mul(x.abs, y) + if z.abs.IsZero() { + z.neg = false + } else { + z.neg = x.neg + } + return z +} + +// Div sets z to the quotient x/y for y != 0 and returns z. +func (z *Int) Div(x, y *Int) *Int { + z.initiateAbs() + + if y.abs.IsZero() { + panic("division by zero") + } + + z.abs.Div(x.abs, y.abs) + z.neg = (x.neg != y.neg) && !z.abs.IsZero() // 0 has no sign + + return z +} + +// DivUint256 sets z to the quotient x/y, where y is a uint256, and returns z +// If y == 0, z is set to 0 +func (z *Int) DivUint256(x *Int, y *uint256.Uint) *Int { + z.abs.Div(x.abs, y) + if z.abs.IsZero() { + z.neg = false + } else { + z.neg = x.neg + } + return z +} + +// Quo sets z to the quotient x/y for y != 0 and returns z. +// If y == 0, a division-by-zero run-time panic occurs. +// OBS: differs from mempooler int256, we need to panic manually if y == 0 +// Quo implements truncated division (like Go); see QuoRem for more details. +func (z *Int) Quo(x, y *Int) *Int { + if y.IsZero() { + panic("division by zero") + } + + z.initiateAbs() + + z.abs = z.abs.Div(x.abs, y.abs) + z.neg = !(z.abs.IsZero()) && x.neg != y.neg // 0 has no sign + return z +} + +// Rem sets z to the remainder x%y for y != 0 and returns z. +// If y == 0, a division-by-zero run-time panic occurs. +// OBS: differs from mempooler int256, we need to panic manually if y == 0 +// Rem implements truncated modulus (like Go); see QuoRem for more details. +func (z *Int) Rem(x, y *Int) *Int { + if y.IsZero() { + panic("division by zero") + } + + z.initiateAbs() + + z.abs.Mod(x.abs, y.abs) + z.neg = z.abs.Sign() > 0 && x.neg // 0 has no sign + return z +} + +// Mod sets z to the modulus x%y for y != 0 and returns z. +// If y == 0, z is set to 0 (OBS: differs from the big.Int) +func (z *Int) Mod(x, y *Int) *Int { + if x.neg { + z.abs.Div(x.abs, y.abs) + z.abs.Add(z.abs, one) + z.abs.Mul(z.abs, y.abs) + z.abs.Sub(z.abs, x.abs) + z.abs.Mod(z.abs, y.abs) + } else { + z.abs.Mod(x.abs, y.abs) + } + z.neg = false + return z +} diff --git a/_deploy/p/gnoswap/int256/arithmetic_test.gno b/_deploy/p/gnoswap/int256/arithmetic_test.gno new file mode 100644 index 000000000..019c33872 --- /dev/null +++ b/_deploy/p/gnoswap/int256/arithmetic_test.gno @@ -0,0 +1,571 @@ +package int256 + +import ( + "testing" + + "gno.land/p/gnoswap/uint256" +) + +func TestAdd(t *testing.T) { + tests := []struct { + x, y, want string + }{ + {"0", "1", "1"}, + {"1", "0", "1"}, + {"1", "1", "2"}, + {"1", "2", "3"}, + // NEGATIVE + {"-1", "1", "0"}, + {"1", "-1", "0"}, + {"3", "-3", "0"}, + {"-1", "-1", "-2"}, + {"-1", "-2", "-3"}, + {"-1", "3", "2"}, + {"3", "-1", "2"}, + // OVERFLOW + {"115792089237316195423570985008687907853269984665640564039457584007913129639935", "1", "0"}, + } + + for _, tc := range tests { + x, err := FromDecimal(tc.x) + if err != nil { + t.Error(err) + continue + } + + y, err := FromDecimal(tc.y) + if err != nil { + t.Error(err) + continue + } + + want, err := FromDecimal(tc.want) + if err != nil { + t.Error(err) + continue + } + + got := New() + got.Add(x, y) + + if got.Neq(want) { + t.Errorf("Add(%s, %s) = %v, want %v", tc.x, tc.y, got.ToString(), want.ToString()) + } + } +} + +func TestAddUint256(t *testing.T) { + tests := []struct { + x, y, want string + }{ + {"0", "1", "1"}, + {"1", "0", "1"}, + {"1", "1", "2"}, + {"1", "2", "3"}, + {"-1", "1", "0"}, + {"-1", "3", "2"}, + {"-115792089237316195423570985008687907853269984665640564039457584007913129639934", "115792089237316195423570985008687907853269984665640564039457584007913129639935", "1"}, + {"-115792089237316195423570985008687907853269984665640564039457584007913129639935", "115792089237316195423570985008687907853269984665640564039457584007913129639934", "-1"}, + // OVERFLOW + {"-115792089237316195423570985008687907853269984665640564039457584007913129639935", "115792089237316195423570985008687907853269984665640564039457584007913129639935", "0"}, + } + + for _, tc := range tests { + x, err := FromDecimal(tc.x) + if err != nil { + t.Error(err) + continue + } + + y, err := uint256.FromDecimal(tc.y) + if err != nil { + t.Error(err) + continue + } + + want, err := FromDecimal(tc.want) + if err != nil { + t.Error(err) + continue + } + + got := New() + got.AddUint256(x, y) + + if got.Neq(want) { + t.Errorf("AddUint256(%s, %s) = %v, want %v", tc.x, tc.y, got.ToString(), want.ToString()) + } + } +} + +func TestAddDelta(t *testing.T) { + tests := []struct { + z, x, y, want string + }{ + {"0", "0", "0", "0"}, + {"0", "0", "1", "1"}, + {"0", "1", "0", "1"}, + {"0", "1", "1", "2"}, + {"1", "2", "3", "5"}, + {"5", "10", "-3", "7"}, + // underflow + {"1", "2", "-3", "115792089237316195423570985008687907853269984665640564039457584007913129639935"}, + } + + for _, tc := range tests { + z, err := uint256.FromDecimal(tc.z) + if err != nil { + t.Error(err) + continue + } + + x, err := uint256.FromDecimal(tc.x) + if err != nil { + t.Error(err) + continue + } + + y, err := FromDecimal(tc.y) + if err != nil { + t.Error(err) + continue + } + + want, err := uint256.FromDecimal(tc.want) + if err != nil { + t.Error(err) + continue + } + + AddDelta(z, x, y) + + if z.Neq(want) { + t.Errorf("AddDelta(%s, %s, %s) = %v, want %v", tc.z, tc.x, tc.y, z.ToString(), want.ToString()) + } + } +} + +func TestAddDeltaOverflow(t *testing.T) { + tests := []struct { + z, x, y string + want bool + }{ + {"0", "0", "0", false}, + // underflow + {"1", "2", "-3", true}, + } + + for _, tc := range tests { + z, err := uint256.FromDecimal(tc.z) + if err != nil { + t.Error(err) + continue + } + + x, err := uint256.FromDecimal(tc.x) + if err != nil { + t.Error(err) + continue + } + + y, err := FromDecimal(tc.y) + if err != nil { + t.Error(err) + continue + } + + result := AddDeltaOverflow(z, x, y) + if result != tc.want { + t.Errorf("AddDeltaOverflow(%s, %s, %s) = %v, want %v", tc.z, tc.x, tc.y, result, tc.want) + } + } +} + +func TestSub(t *testing.T) { + tests := []struct { + x, y, want string + }{ + {"1", "0", "1"}, + {"1", "1", "0"}, + {"-1", "1", "-2"}, + {"1", "-1", "2"}, + {"-1", "-1", "0"}, + {"-115792089237316195423570985008687907853269984665640564039457584007913129639935", "-115792089237316195423570985008687907853269984665640564039457584007913129639935", "0"}, + {"-115792089237316195423570985008687907853269984665640564039457584007913129639935", "0", "-115792089237316195423570985008687907853269984665640564039457584007913129639935"}, + {x: "-115792089237316195423570985008687907853269984665640564039457584007913129639935", y: "1", want: "0"}, + } + + for _, tc := range tests { + x, err := FromDecimal(tc.x) + if err != nil { + t.Error(err) + continue + } + + y, err := FromDecimal(tc.y) + if err != nil { + t.Error(err) + continue + } + + want, err := FromDecimal(tc.want) + if err != nil { + t.Error(err) + continue + } + + got := New() + got.Sub(x, y) + + if got.Neq(want) { + t.Errorf("Sub(%s, %s) = %v, want %v", tc.x, tc.y, got.ToString(), want.ToString()) + } + } +} + +func TestSubUint256(t *testing.T) { + tests := []struct { + x, y, want string + }{ + {"0", "1", "-1"}, + {"1", "0", "1"}, + {"1", "1", "0"}, + {"1", "2", "-1"}, + {"-1", "1", "-2"}, + {"-1", "3", "-4"}, + // underflow + {"-115792089237316195423570985008687907853269984665640564039457584007913129639935", "1", "-0"}, + {"-115792089237316195423570985008687907853269984665640564039457584007913129639935", "2", "-1"}, + {"-115792089237316195423570985008687907853269984665640564039457584007913129639935", "3", "-2"}, + } + + for _, tc := range tests { + x, err := FromDecimal(tc.x) + if err != nil { + t.Error(err) + continue + } + + y, err := uint256.FromDecimal(tc.y) + if err != nil { + t.Error(err) + continue + } + + want, err := FromDecimal(tc.want) + if err != nil { + t.Error(err) + continue + } + + got := New() + got.SubUint256(x, y) + + if got.Neq(want) { + t.Errorf("SubUint256(%s, %s) = %v, want %v", tc.x, tc.y, got.ToString(), want.ToString()) + } + } +} + +func TestMul(t *testing.T) { + tests := []struct { + x, y, want string + }{ + {"5", "3", "15"}, + {"-5", "3", "-15"}, + {"5", "-3", "-15"}, + {"0", "3", "0"}, + {"3", "0", "0"}, + } + + for _, tc := range tests { + x, err := FromDecimal(tc.x) + if err != nil { + t.Error(err) + continue + } + + y, err := FromDecimal(tc.y) + if err != nil { + t.Error(err) + continue + } + + want, err := FromDecimal(tc.want) + if err != nil { + t.Error(err) + continue + } + + got := New() + got.Mul(x, y) + + if got.Neq(want) { + t.Errorf("Mul(%s, %s) = %v, want %v", tc.x, tc.y, got.ToString(), want.ToString()) + } + } +} + +func TestMulUint256(t *testing.T) { + tests := []struct { + x, y, want string + }{ + {"0", "1", "0"}, + {"1", "0", "0"}, + {"1", "1", "1"}, + {"1", "2", "2"}, + {"-1", "1", "-1"}, + {"-1", "3", "-3"}, + {"3", "4", "12"}, + {"-3", "4", "-12"}, + {"-115792089237316195423570985008687907853269984665640564039457584007913129639934", "2", "-115792089237316195423570985008687907853269984665640564039457584007913129639932"}, + {"115792089237316195423570985008687907853269984665640564039457584007913129639934", "2", "115792089237316195423570985008687907853269984665640564039457584007913129639932"}, + } + + for _, tc := range tests { + x, err := FromDecimal(tc.x) + if err != nil { + t.Error(err) + continue + } + + y, err := uint256.FromDecimal(tc.y) + if err != nil { + t.Error(err) + continue + } + + want, err := FromDecimal(tc.want) + if err != nil { + t.Error(err) + continue + } + + got := New() + got.MulUint256(x, y) + + if got.Neq(want) { + t.Errorf("MulUint256(%s, %s) = %v, want %v", tc.x, tc.y, got.ToString(), want.ToString()) + } + } +} + +func TestDiv(t *testing.T) { + tests := []struct { + x, y, expected string + }{ + {"1", "1", "1"}, + {"0", "1", "0"}, + {"-1", "1", "-1"}, + {"1", "-1", "-1"}, + {"-1", "-1", "1"}, + {"-6", "3", "-2"}, + {"10", "-2", "-5"}, + {"-10", "3", "-3"}, + {"7", "3", "2"}, + {"-7", "3", "-2"}, + {"115792089237316195423570985008687907853269984665640564039457584007913129639935", "2", "57896044618658097711785492504343953926634992332820282019728792003956564819967"}, // Max uint256 / 2 + } + + for _, tt := range tests { + t.Run(tt.x+"/"+tt.y, func(t *testing.T) { + x := MustFromDecimal(tt.x) + y := MustFromDecimal(tt.y) + result := Zero().Div(x, y) + if result.ToString() != tt.expected { + t.Errorf("Div(%s, %s) = %s, want %s", tt.x, tt.y, result.ToString(), tt.expected) + } + if result.abs.IsZero() && result.neg { + t.Errorf("Div(%s, %s) resulted in negative zero", tt.x, tt.y) + } + }) + } + + t.Run("Division by zero", func(t *testing.T) { + defer func() { + if r := recover(); r == nil { + t.Errorf("Div(1, 0) did not panic") + } + }() + x := MustFromDecimal("1") + y := MustFromDecimal("0") + Zero().Div(x, y) + }) +} + +func TestDivUint256(t *testing.T) { + tests := []struct { + x, y, want string + }{ + {"0", "1", "0"}, + {"1", "0", "0"}, + {"1", "1", "1"}, + {"1", "2", "0"}, + {"-1", "1", "-1"}, + {"-1", "3", "0"}, + {"4", "3", "1"}, + {"25", "5", "5"}, + {"25", "4", "6"}, + {"-115792089237316195423570985008687907853269984665640564039457584007913129639934", "2", "-57896044618658097711785492504343953926634992332820282019728792003956564819967"}, + {"115792089237316195423570985008687907853269984665640564039457584007913129639934", "2", "57896044618658097711785492504343953926634992332820282019728792003956564819967"}, + } + + for _, tc := range tests { + x, err := FromDecimal(tc.x) + if err != nil { + t.Error(err) + continue + } + + y, err := uint256.FromDecimal(tc.y) + if err != nil { + t.Error(err) + continue + } + + want, err := FromDecimal(tc.want) + if err != nil { + t.Error(err) + continue + } + + got := New() + got.DivUint256(x, y) + + if got.Neq(want) { + t.Errorf("DivUint256(%s, %s) = %v, want %v", tc.x, tc.y, got.ToString(), want.ToString()) + } + } +} + +func TestQuo(t *testing.T) { + tests := []struct { + x, y, want string + }{ + {"0", "1", "0"}, + {"0", "-1", "0"}, + {"10", "1", "10"}, + {"10", "-1", "-10"}, + {"-10", "1", "-10"}, + {"-10", "-1", "10"}, + {"10", "-3", "-3"}, + {"10", "3", "3"}, + } + + for _, tc := range tests { + x, err := FromDecimal(tc.x) + if err != nil { + t.Error(err) + continue + } + + y, err := FromDecimal(tc.y) + if err != nil { + t.Error(err) + continue + } + + want, err := FromDecimal(tc.want) + if err != nil { + t.Error(err) + continue + } + + got := New() + got.Quo(x, y) + + if got.Neq(want) { + t.Errorf("Quo(%s, %s) = %v, want %v", tc.x, tc.y, got.ToString(), want.ToString()) + } + } +} + +func TestRem(t *testing.T) { + tests := []struct { + x, y, want string + }{ + {"0", "1", "0"}, + {"0", "-1", "0"}, + {"10", "1", "0"}, + {"10", "-1", "0"}, + {"-10", "1", "0"}, + {"-10", "-1", "0"}, + {"10", "3", "1"}, + {"10", "-3", "1"}, + {"-10", "3", "-1"}, + {"-10", "-3", "-1"}, + } + + for _, tc := range tests { + x, err := FromDecimal(tc.x) + if err != nil { + t.Error(err) + continue + } + + y, err := FromDecimal(tc.y) + if err != nil { + t.Error(err) + continue + } + + want, err := FromDecimal(tc.want) + if err != nil { + t.Error(err) + continue + } + + got := New() + got.Rem(x, y) + + if got.Neq(want) { + t.Errorf("Rem(%s, %s) = %v, want %v", tc.x, tc.y, got.ToString(), want.ToString()) + } + } +} + +func TestMod(t *testing.T) { + tests := []struct { + x, y, want string + }{ + {"0", "1", "0"}, + {"0", "-1", "0"}, + {"10", "0", "0"}, + {"10", "1", "0"}, + {"10", "-1", "0"}, + {"-10", "0", "0"}, + {"-10", "1", "0"}, + {"-10", "-1", "0"}, + {"10", "3", "1"}, + {"10", "-3", "1"}, + {"-10", "3", "2"}, + {"-10", "-3", "2"}, + } + + for _, tc := range tests { + x, err := FromDecimal(tc.x) + if err != nil { + t.Error(err) + continue + } + + y, err := FromDecimal(tc.y) + if err != nil { + t.Error(err) + continue + } + + want, err := FromDecimal(tc.want) + if err != nil { + t.Error(err) + continue + } + + got := New() + got.Mod(x, y) + + if got.Neq(want) { + t.Errorf("Mod(%s, %s) = %v, want %v", tc.x, tc.y, got.ToString(), want.ToString()) + } + } +} diff --git a/_deploy/p/gnoswap/int256/bitwise.gno b/_deploy/p/gnoswap/int256/bitwise.gno new file mode 100644 index 000000000..b7c4120dd --- /dev/null +++ b/_deploy/p/gnoswap/int256/bitwise.gno @@ -0,0 +1,94 @@ +package int256 + +import ( + "gno.land/p/gnoswap/uint256" +) + +// Or sets z = x | y and returns z. +func (z *Int) Or(x, y *Int) *Int { + if x.neg == y.neg { + if x.neg { + // (-x) | (-y) == ^(x-1) | ^(y-1) == ^((x-1) & (y-1)) == -(((x-1) & (y-1)) + 1) + x1 := new(uint256.Uint).Sub(x.abs, one) + y1 := new(uint256.Uint).Sub(y.abs, one) + z.abs = z.abs.Add(z.abs.And(x1, y1), one) + z.neg = true // z cannot be zero if x and y are negative + return z + } + + // x | y == x | y + z.abs = z.abs.Or(x.abs, y.abs) + z.neg = false + return z + } + + // x.neg != y.neg + if x.neg { + x, y = y, x // | is symmetric + } + + // x | (-y) == x | ^(y-1) == ^((y-1) &^ x) == -(^((y-1) &^ x) + 1) + y1 := new(uint256.Uint).Sub(y.abs, one) + z.abs = z.abs.Add(z.abs.AndNot(y1, x.abs), one) + z.neg = true // z cannot be zero if one of x or y is negative + + return z +} + +// And sets z = x & y and returns z. +func (z *Int) And(x, y *Int) *Int { + if x.neg == y.neg { + if x.neg { + // (-x) & (-y) == ^(x-1) & ^(y-1) == ^((x-1) | (y-1)) == -(((x-1) | (y-1)) + 1) + x1 := new(uint256.Uint).Sub(x.abs, one) + y1 := new(uint256.Uint).Sub(y.abs, one) + z.abs = z.abs.Add(z.abs.Or(x1, y1), one) + z.neg = true // z cannot be zero if x and y are negative + return z + } + + // x & y == x & y + z.abs = z.abs.And(x.abs, y.abs) + z.neg = false + return z + } + + // x.neg != y.neg + // REF: https://cs.opensource.google/go/go/+/refs/tags/go1.22.1:src/math/big/int.go;l=1192-1202;drc=d57303e65f00b84b528ee682747dbe1fd3316d30 + if x.neg { + x, y = y, x // & is symmetric + } + + // x & (-y) == x & ^(y-1) == x &^ (y-1) + y1 := new(uint256.Uint).Sub(y.abs, uint256.One()) + z.abs = z.abs.AndNot(x.abs, y1) + z.neg = false + return z +} + +// Rsh sets z = x >> n and returns z. +// OBS: Different from original implementation it was using math.Big +func (z *Int) Rsh(x *Int, n uint) *Int { + if !x.neg { + z.abs.Rsh(x.abs, n) + z.neg = x.neg + return z + } + + // REF: https://cs.opensource.google/go/go/+/refs/tags/go1.22.1:src/math/big/int.go;l=1118-1126;drc=d57303e65f00b84b528ee682747dbe1fd3316d30 + t := NewInt(0).Sub(FromUint256(x.abs), NewInt(1)) + t = t.Rsh(t, n) + + _tmp := t.Add(t, NewInt(1)) + z.abs = _tmp.Abs() + z.neg = true + + return z +} + +// Lsh sets z = x << n and returns z. +func (z *Int) Lsh(x *Int, n uint) *Int { + z.abs.Lsh(x.abs, n) + z.neg = x.neg + return z +} diff --git a/_deploy/p/gnoswap/int256/bitwise_test.gno b/_deploy/p/gnoswap/int256/bitwise_test.gno new file mode 100644 index 000000000..904ee36cc --- /dev/null +++ b/_deploy/p/gnoswap/int256/bitwise_test.gno @@ -0,0 +1,201 @@ +package int256 + +import ( + "gno.land/p/gnoswap/uint256" +) + +import ( + "testing" +) + +func TestOr(t *testing.T) { + tests := []struct { + name string + x, y, want Int + }{ + { + name: "all zeroes", + x: Int{abs: &uint256.Uint{arr: [4]uint64{0, 0, 0, 0}}, neg: false}, + y: Int{abs: &uint256.Uint{arr: [4]uint64{0, 0, 0, 0}}, neg: false}, + want: Int{abs: &uint256.Uint{arr: [4]uint64{0, 0, 0, 0}}, neg: false}, + }, + { + name: "all ones", + x: Int{abs: &uint256.Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}}, neg: false}, + y: Int{abs: &uint256.Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}}, neg: false}, + want: Int{abs: &uint256.Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}}, neg: false}, + }, + { + name: "mixed", + x: Int{abs: &uint256.Uint{arr: [4]uint64{^uint64(0), ^uint64(0), 0, 0}}, neg: false}, + y: Int{abs: &uint256.Uint{arr: [4]uint64{0, 0, ^uint64(0), ^uint64(0)}}, neg: false}, + want: Int{abs: &uint256.Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}}, neg: false}, + }, + { + name: "one operand all ones", + x: Int{abs: &uint256.Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}}, neg: false}, + y: Int{abs: &uint256.Uint{arr: [4]uint64{0x5555555555555555, 0xAAAAAAAAAAAAAAAA, 0xFFFFFFFFFFFFFFFF, 0x0000000000000000}}, neg: false}, + want: Int{abs: &uint256.Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}}, neg: false}, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + got := New() + got.Or(&tc.x, &tc.y) + + if got.Neq(&tc.want) { + t.Errorf("Or(%v, %v) = %v, want %v", tc.x, tc.y, got, tc.want) + } + }) + } +} + +func TestAnd(t *testing.T) { + tests := []struct { + name string + x, y, want Int + }{ + { + name: "all zeroes", + x: Int{abs: &uint256.Uint{arr: [4]uint64{0, 0, 0, 0}}, neg: false}, + y: Int{abs: &uint256.Uint{arr: [4]uint64{0, 0, 0, 0}}, neg: false}, + want: Int{abs: &uint256.Uint{arr: [4]uint64{0, 0, 0, 0}}, neg: false}, + }, + { + name: "all ones", + x: Int{abs: &uint256.Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}}, neg: false}, + y: Int{abs: &uint256.Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}}, neg: false}, + want: Int{abs: &uint256.Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}}, neg: false}, + }, + { + name: "mixed", + x: Int{abs: &uint256.Uint{arr: [4]uint64{0, 0, 0, 0}}, neg: false}, + y: Int{abs: &uint256.Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}}, neg: false}, + want: Int{abs: &uint256.Uint{arr: [4]uint64{0, 0, 0, 0}}, neg: false}, + }, + { + name: "mixed 2", + x: Int{abs: &uint256.Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}}, neg: false}, + y: Int{abs: &uint256.Uint{arr: [4]uint64{0, 0, 0, 0}}, neg: false}, + want: Int{abs: &uint256.Uint{arr: [4]uint64{0, 0, 0, 0}}, neg: false}, + }, + { + name: "mixed 3", + x: Int{abs: &uint256.Uint{arr: [4]uint64{^uint64(0), ^uint64(0), 0, 0}}, neg: false}, + y: Int{abs: &uint256.Uint{arr: [4]uint64{0, 0, ^uint64(0), ^uint64(0)}}, neg: false}, + want: Int{abs: &uint256.Uint{arr: [4]uint64{0, 0, 0, 0}}, neg: false}, + }, + { + name: "one operand zero", + x: Int{abs: &uint256.Uint{arr: [4]uint64{0, 0, 0, 0}}, neg: false}, + y: Int{abs: &uint256.Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}}, neg: false}, + want: Int{abs: &uint256.Uint{arr: [4]uint64{0, 0, 0, 0}}, neg: false}, + }, + { + name: "one operand all ones", + x: Int{abs: &uint256.Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}}, neg: false}, + y: Int{abs: &uint256.Uint{arr: [4]uint64{0x5555555555555555, 0xAAAAAAAAAAAAAAAA, 0xFFFFFFFFFFFFFFFF, 0x0000000000000000}}, neg: false}, + want: Int{abs: &uint256.Uint{arr: [4]uint64{0x5555555555555555, 0xAAAAAAAAAAAAAAAA, 0xFFFFFFFFFFFFFFFF, 0x0000000000000000}}, neg: false}, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + got := New() + got.And(&tc.x, &tc.y) + + if got.Neq(&tc.want) { + t.Errorf("And(%v, %v) = %v, want %v", tc.x, tc.y, got, tc.want) + } + }) + } +} + +func TestRsh(t *testing.T) { + tests := []struct { + x string + n uint + want string + }{ + {"1024", 0, "1024"}, + {"1024", 1, "512"}, + {"1024", 2, "256"}, + {"1024", 10, "1"}, + {"1024", 11, "0"}, + {"18446744073709551615", 0, "18446744073709551615"}, + {"18446744073709551615", 1, "9223372036854775807"}, + {"18446744073709551615", 62, "3"}, + {"18446744073709551615", 63, "1"}, + {"18446744073709551615", 64, "0"}, + {"115792089237316195423570985008687907853269984665640564039457584007913129639935", 0, "115792089237316195423570985008687907853269984665640564039457584007913129639935"}, + {"115792089237316195423570985008687907853269984665640564039457584007913129639935", 1, "57896044618658097711785492504343953926634992332820282019728792003956564819967"}, + {"115792089237316195423570985008687907853269984665640564039457584007913129639935", 128, "340282366920938463463374607431768211455"}, + {"115792089237316195423570985008687907853269984665640564039457584007913129639935", 255, "1"}, + {"115792089237316195423570985008687907853269984665640564039457584007913129639935", 256, "0"}, + {"-1024", 0, "-1024"}, + {"-1024", 1, "-512"}, + {"-1024", 2, "-256"}, + {"-1024", 10, "-1"}, + {"-1024", 10, "-1"}, + {"-9223372036854775808", 0, "-9223372036854775808"}, + {"-9223372036854775808", 1, "-4611686018427387904"}, + {"-9223372036854775808", 62, "-2"}, + {"-9223372036854775808", 63, "-1"}, + {"-9223372036854775808", 64, "-1"}, + {"-57896044618658097711785492504343953926634992332820282019728792003956564819968", 0, "-57896044618658097711785492504343953926634992332820282019728792003956564819968"}, + {"-57896044618658097711785492504343953926634992332820282019728792003956564819968", 1, "-28948022309329048855892746252171976963317496166410141009864396001978282409984"}, + {"-57896044618658097711785492504343953926634992332820282019728792003956564819968", 253, "-4"}, + {"-57896044618658097711785492504343953926634992332820282019728792003956564819968", 254, "-2"}, + {"-57896044618658097711785492504343953926634992332820282019728792003956564819968", 255, "-1"}, + {"-57896044618658097711785492504343953926634992332820282019728792003956564819968", 256, "-1"}, + } + + for _, tc := range tests { + x, err := FromDecimal(tc.x) + if err != nil { + t.Error(err) + continue + } + + got := New() + got.Rsh(x, tc.n) + + if got.ToString() != tc.want { + t.Errorf("Rsh(%s, %d) = %v, want %v", tc.x, tc.n, got.ToString(), tc.want) + } + } +} + +func TestLsh(t *testing.T) { + tests := []struct { + x string + n uint + want string + }{ + {"1", 0, "1"}, + {"1", 1, "2"}, + {"1", 2, "4"}, + {"2", 0, "2"}, + {"2", 1, "4"}, + {"2", 2, "8"}, + {"-2", 0, "-2"}, + {"-4", 0, "-4"}, + {"-8", 0, "-8"}, + } + + for _, tc := range tests { + x, err := FromDecimal(tc.x) + if err != nil { + t.Error(err) + continue + } + + got := New() + got.Lsh(x, tc.n) + + if got.ToString() != tc.want { + t.Errorf("Lsh(%s, %d) = %v, want %v", tc.x, tc.n, got.ToString(), tc.want) + } + } +} diff --git a/_deploy/p/gnoswap/int256/cmp.gno b/_deploy/p/gnoswap/int256/cmp.gno new file mode 100644 index 000000000..6371def6f --- /dev/null +++ b/_deploy/p/gnoswap/int256/cmp.gno @@ -0,0 +1,101 @@ +package int256 + +// Eq returns true if z == x +func (z *Int) Eq(x *Int) bool { + if z == nil || x == nil { + panic("int256: comparing with nil") + } + return (z.neg == x.neg) && z.abs.Eq(x.abs) +} + +// Neq returns true if z != x +func (z *Int) Neq(x *Int) bool { + if z == nil || x == nil { + panic("int256: comparing with nil") + } + return !z.Eq(x) +} + +// Cmp compares x and y and returns: +// +// -1 if x < y +// 0 if x == y +// +1 if x > y +func (z *Int) Cmp(x *Int) (r int) { + if z == nil || x == nil { + panic("int256: comparing with nil") + } + // x cmp y == x cmp y + // x cmp (-y) == x + // (-x) cmp y == y + // (-x) cmp (-y) == -(x cmp y) + switch { + case z == x: + // nothing to do + case z.neg == x.neg: + r = z.abs.Cmp(x.abs) + if z.neg { + r = -r + } + case z.neg: + r = -1 + default: + r = 1 + } + return +} + +// IsZero returns true if z == 0 +func (z *Int) IsZero() bool { + return z.abs.IsZero() +} + +// IsNeg returns true if z < 0 +func (z *Int) IsNeg() bool { + return z.neg +} + +// Lt returns true if z < x +func (z *Int) Lt(x *Int) bool { + if z == nil || x == nil { + panic("int256: comparing with nil") + } + if z.neg { + if x.neg { + return z.abs.Gt(x.abs) + } else { + return true + } + } else { + if x.neg { + return false + } else { + return z.abs.Lt(x.abs) + } + } +} + +// Gt returns true if z > x +func (z *Int) Gt(x *Int) bool { + if z == nil || x == nil { + panic("int256: comparing with nil") + } + if z.neg { + if x.neg { + return z.abs.Lt(x.abs) + } else { + return false + } + } else { + if x.neg { + return true + } else { + return z.abs.Gt(x.abs) + } + } +} + +// Clone creates a new Int identical to z +func (z *Int) Clone() *Int { + return &Int{z.abs.Clone(), z.neg} +} diff --git a/_deploy/p/gnoswap/int256/cmp_test.gno b/_deploy/p/gnoswap/int256/cmp_test.gno new file mode 100644 index 000000000..221c1eab9 --- /dev/null +++ b/_deploy/p/gnoswap/int256/cmp_test.gno @@ -0,0 +1,317 @@ +package int256 + +import ( + "testing" +) + +func TestEq(t *testing.T) { + tests := []struct { + x, y string + want bool + }{ + {"0", "0", true}, + {"0", "1", false}, + {"1", "0", false}, + {"-1", "0", false}, + {"0", "-1", false}, + {"1", "1", true}, + {"-1", "-1", true}, + {"115792089237316195423570985008687907853269984665640564039457584007913129639935", "-115792089237316195423570985008687907853269984665640564039457584007913129639935", false}, + {"-115792089237316195423570985008687907853269984665640564039457584007913129639935", "-115792089237316195423570985008687907853269984665640564039457584007913129639935", true}, + } + + for _, tc := range tests { + x, err := FromDecimal(tc.x) + if err != nil { + t.Error(err) + continue + } + + y, err := FromDecimal(tc.y) + if err != nil { + t.Error(err) + continue + } + + got := x.Eq(y) + if got != tc.want { + t.Errorf("Eq(%s, %s) = %v, want %v", tc.x, tc.y, got, tc.want) + } + } +} + +func TestNeq(t *testing.T) { + tests := []struct { + x, y string + want bool + }{ + {"0", "0", false}, + {"0", "1", true}, + {"1", "0", true}, + {"-1", "0", true}, + {"0", "-1", true}, + {"1", "1", false}, + {"-1", "-1", false}, + {"115792089237316195423570985008687907853269984665640564039457584007913129639935", "-115792089237316195423570985008687907853269984665640564039457584007913129639935", true}, + {"-115792089237316195423570985008687907853269984665640564039457584007913129639935", "-115792089237316195423570985008687907853269984665640564039457584007913129639935", false}, + } + + for _, tc := range tests { + x, err := FromDecimal(tc.x) + if err != nil { + t.Error(err) + continue + } + + y, err := FromDecimal(tc.y) + if err != nil { + t.Error(err) + continue + } + + got := x.Neq(y) + if got != tc.want { + t.Errorf("Neq(%s, %s) = %v, want %v", tc.x, tc.y, got, tc.want) + } + } +} + +func TestCmp(t *testing.T) { + tests := []struct { + x, y string + want int + }{ + {"0", "0", 0}, + {"0", "1", -1}, + {"1", "0", 1}, + {"-1", "0", -1}, + {"0", "-1", 1}, + {"1", "1", 0}, + {"115792089237316195423570985008687907853269984665640564039457584007913129639935", "-115792089237316195423570985008687907853269984665640564039457584007913129639935", 1}, + } + + for _, tc := range tests { + x, err := FromDecimal(tc.x) + if err != nil { + t.Error(err) + continue + } + + y, err := FromDecimal(tc.y) + if err != nil { + t.Error(err) + continue + } + + got := x.Cmp(y) + if got != tc.want { + t.Errorf("Cmp(%s, %s) = %v, want %v", tc.x, tc.y, got, tc.want) + } + } +} + +func TestIsZero(t *testing.T) { + tests := []struct { + x string + want bool + }{ + {"0", true}, + {"-0", true}, + {"1", false}, + {"-1", false}, + {"10", false}, + } + + for _, tc := range tests { + x, err := FromDecimal(tc.x) + if err != nil { + t.Error(err) + continue + } + + got := x.IsZero() + if got != tc.want { + t.Errorf("IsZero(%s) = %v, want %v", tc.x, got, tc.want) + } + } +} + +func TestIsNeg(t *testing.T) { + tests := []struct { + x string + want bool + }{ + {"0", false}, + {"-0", true}, // TODO: should this be false? + {"1", false}, + {"-1", true}, + {"10", false}, + {"-10", true}, + } + + for _, tc := range tests { + x, err := FromDecimal(tc.x) + if err != nil { + t.Error(err) + continue + } + + got := x.IsNeg() + if got != tc.want { + t.Errorf("IsNeg(%s) = %v, want %v", tc.x, got, tc.want) + } + } +} + +func TestLt(t *testing.T) { + tests := []struct { + x, y string + want bool + }{ + {"0", "0", false}, + {"0", "1", true}, + {"1", "0", false}, + {"-1", "0", true}, + {"0", "-1", false}, + {"1", "1", false}, + {"-1", "-1", false}, + {"115792089237316195423570985008687907853269984665640564039457584007913129639935", "-115792089237316195423570985008687907853269984665640564039457584007913129639935", false}, + } + + for _, tc := range tests { + x, err := FromDecimal(tc.x) + if err != nil { + t.Error(err) + continue + } + + y, err := FromDecimal(tc.y) + if err != nil { + t.Error(err) + continue + } + + got := x.Lt(y) + if got != tc.want { + t.Errorf("Lt(%s, %s) = %v, want %v", tc.x, tc.y, got, tc.want) + } + } +} + +func TestGt(t *testing.T) { + tests := []struct { + x, y string + want bool + }{ + {"0", "0", false}, + {"0", "1", false}, + {"1", "0", true}, + {"-1", "0", false}, + {"0", "-1", true}, + {"1", "1", false}, + {"-1", "-1", false}, + {"115792089237316195423570985008687907853269984665640564039457584007913129639935", "-115792089237316195423570985008687907853269984665640564039457584007913129639935", true}, + } + + for _, tc := range tests { + x, err := FromDecimal(tc.x) + if err != nil { + t.Error(err) + continue + } + + y, err := FromDecimal(tc.y) + if err != nil { + t.Error(err) + continue + } + + got := x.Gt(y) + if got != tc.want { + t.Errorf("Gt(%s, %s) = %v, want %v", tc.x, tc.y, got, tc.want) + } + } +} + +func TestClone(t *testing.T) { + tests := []struct { + x string + }{ + {"0"}, + {"-0"}, + {"1"}, + {"-1"}, + {"10"}, + {"-10"}, + {"115792089237316195423570985008687907853269984665640564039457584007913129639935"}, + {"-115792089237316195423570985008687907853269984665640564039457584007913129639935"}, + } + + for _, tc := range tests { + x, err := FromDecimal(tc.x) + if err != nil { + t.Error(err) + continue + } + + y := x.Clone() + + if x.Cmp(y) != 0 { + t.Errorf("Clone(%s) = %v, want %v", tc.x, y, x) + } + } +} + + +func TestNilChecks(t *testing.T) { + validInt := NewInt(123) + + tests := []struct { + name string + fn func() + wantPanic string + }{ + { + name: "Eq with nil", + fn: func() { validInt.Eq(nil) }, + wantPanic: "int256: comparing with nil", + }, + { + name: "Neq with nil", + fn: func() { validInt.Neq(nil) }, + wantPanic: "int256: comparing with nil", + }, + { + name: "Cmp with nil", + fn: func() { validInt.Cmp(nil) }, + wantPanic: "int256: comparing with nil", + }, + { + name: "Lt with nil", + fn: func() { validInt.Lt(nil) }, + wantPanic: "int256: comparing with nil", + }, + { + name: "Gt with nil", + fn: func() { validInt.Gt(nil) }, + wantPanic: "int256: comparing with nil", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + defer func() { + r := recover() + if r == nil { + t.Errorf("%s: expected panic but got none", tt.name) + return + } + if r.(string) != tt.wantPanic { + t.Errorf("%s: got panic %v, want %v", tt.name, r, tt.wantPanic) + } + }() + + tt.fn() + }) + } +} diff --git a/_deploy/p/gnoswap/int256/conversion.gno b/_deploy/p/gnoswap/int256/conversion.gno new file mode 100644 index 000000000..23b9b7791 --- /dev/null +++ b/_deploy/p/gnoswap/int256/conversion.gno @@ -0,0 +1,88 @@ +package int256 + +import ( + "gno.land/p/gnoswap/uint256" +) + +// SetInt64 sets z to x and returns z. +func (z *Int) SetInt64(x int64) *Int { + z.initiateAbs() + + neg := false + if x < 0 { + neg = true + x = -x + } + if z.abs == nil { + panic("int256_SetInt64()__abs is nil") + } + z.abs = z.abs.SetUint64(uint64(x)) + z.neg = neg + return z +} + +// SetUint64 sets z to x and returns z. +func (z *Int) SetUint64(x uint64) *Int { + z.initiateAbs() + + if z.abs == nil { + panic("int256_SetUint64()__abs is nil") + } + z.abs = z.abs.SetUint64(x) + z.neg = false + return z +} + +// Uint64 returns the lower 64-bits of z +func (z *Int) Uint64() uint64 { + return z.abs.Uint64() +} + +// Int64 returns the lower 64-bits of z +func (z *Int) Int64() int64 { + _abs := z.abs.Clone() + + if z.neg { + return -int64(_abs.Uint64()) + } + return int64(_abs.Uint64()) +} + +// Neg sets z to -x and returns z.) +func (z *Int) Neg(x *Int) *Int { + z.abs.Set(x.abs) + if z.abs.IsZero() { + z.neg = false + } else { + z.neg = !x.neg + } + return z +} + +// Set sets z to x and returns z. +func (z *Int) Set(x *Int) *Int { + z.abs.Set(x.abs) + z.neg = x.neg + return z +} + +// SetFromUint256 converts a uint256.Uint to Int and sets the value to z. +func (z *Int) SetUint256(x *uint256.Uint) *Int { + z.abs.Set(x) + z.neg = false + return z +} + +// OBS, differs from original mempooler int256 +// ToString returns the decimal representation of z. +func (z *Int) ToString() string { + if z == nil { + panic("int256: nil pointer to ToString()") + } + + t := z.abs.Dec() + if z.neg { + return "-" + t + } + return t +} diff --git a/_deploy/p/gnoswap/int256/conversion_test.gno b/_deploy/p/gnoswap/int256/conversion_test.gno new file mode 100644 index 000000000..4e87df6e0 --- /dev/null +++ b/_deploy/p/gnoswap/int256/conversion_test.gno @@ -0,0 +1,234 @@ +package int256 + +import ( + "testing" + + "gno.land/p/gnoswap/uint256" +) + +func TestSetInt64(t *testing.T) { + tests := []struct { + x int64 + want string + }{ + {0, "0"}, + {1, "1"}, + {-1, "-1"}, + {9223372036854775807, "9223372036854775807"}, + {-9223372036854775808, "-9223372036854775808"}, + } + + for _, tc := range tests { + var z Int + z.SetInt64(tc.x) + + got := z.ToString() + if got != tc.want { + t.Errorf("SetInt64(%d) = %s, want %s", tc.x, got, tc.want) + } + } +} + +func TestSetUint64(t *testing.T) { + tests := []struct { + x uint64 + want string + }{ + {0, "0"}, + {1, "1"}, + } + + for _, tc := range tests { + var z Int + z.SetUint64(tc.x) + + got := z.ToString() + if got != tc.want { + t.Errorf("SetUint64(%d) = %s, want %s", tc.x, got, tc.want) + } + } +} + +func TestUint64(t *testing.T) { + tests := []struct { + x string + want uint64 + }{ + {"0", 0}, + {"1", 1}, + {"9223372036854775807", 9223372036854775807}, + {"9223372036854775808", 9223372036854775808}, + {"18446744073709551615", 18446744073709551615}, + {"18446744073709551616", 0}, + {"18446744073709551617", 1}, + {"-1", 1}, + {"-18446744073709551615", 18446744073709551615}, + {"-18446744073709551616", 0}, + {"-18446744073709551617", 1}, + } + + for _, tc := range tests { + z := MustFromDecimal(tc.x) + + got := z.Uint64() + if got != tc.want { + t.Errorf("Uint64(%s) = %d, want %d", tc.x, got, tc.want) + } + } +} + +func TestInt64(t *testing.T) { + tests := []struct { + x string + want int64 + }{ + {"0", 0}, + {"1", 1}, + {"9223372036854775807", 9223372036854775807}, + {"18446744073709551616", 0}, + {"18446744073709551617", 1}, + {"-1", -1}, + {"-9223372036854775808", -9223372036854775808}, + } + + for _, tc := range tests { + z := MustFromDecimal(tc.x) + + got := z.Int64() + if got != tc.want { + t.Errorf("Uint64(%s) = %d, want %d", tc.x, got, tc.want) + } + } +} + +func TestNeg(t *testing.T) { + tests := []struct { + x string + want string + }{ + {"0", "0"}, + {"1", "-1"}, + {"-1", "1"}, + {"9223372036854775807", "-9223372036854775807"}, + {"-18446744073709551615", "18446744073709551615"}, + } + + for _, tc := range tests { + z := MustFromDecimal(tc.x) + z.Neg(z) + + got := z.ToString() + if got != tc.want { + t.Errorf("Neg(%s) = %s, want %s", tc.x, got, tc.want) + } + } +} + +func TestSet(t *testing.T) { + tests := []struct { + x string + want string + }{ + {"0", "0"}, + {"1", "1"}, + {"-1", "-1"}, + {"9223372036854775807", "9223372036854775807"}, + {"-18446744073709551615", "-18446744073709551615"}, + } + + for _, tc := range tests { + z := MustFromDecimal(tc.x) + z.Set(z) + + got := z.ToString() + if got != tc.want { + t.Errorf("Set(%s) = %s, want %s", tc.x, got, tc.want) + } + } +} + +func TestSetUint256(t *testing.T) { + tests := []struct { + x string + want string + }{ + {"0", "0"}, + {"1", "1"}, + {"9223372036854775807", "9223372036854775807"}, + {"18446744073709551615", "18446744073709551615"}, + } + + for _, tc := range tests { + got := New() + + z := uint256.MustFromDecimal(tc.x) + got.SetUint256(z) + + if got.ToString() != tc.want { + t.Errorf("SetUint256(%s) = %s, want %s", tc.x, got.ToString(), tc.want) + } + } +} + +func TestToString(t *testing.T) { + tests := []struct { + name string + setup func() *Int + expected string + }{ + { + name: "Zero from subtraction", + setup: func() *Int { + minusThree := MustFromDecimal("-3") + three := MustFromDecimal("3") + return Zero().Add(minusThree, three) + }, + expected: "0", + }, + { + name: "Zero from right shift", + setup: func() *Int { + return Zero().Rsh(One(), 1234) + }, + expected: "0", + }, + { + name: "Positive number", + setup: func() *Int { + return MustFromDecimal("42") + }, + expected: "42", + }, + { + name: "Negative number", + setup: func() *Int { + return MustFromDecimal("-42") + }, + expected: "-42", + }, + { + name: "Large positive number", + setup: func() *Int { + return MustFromDecimal("115792089237316195423570985008687907853269984665640564039457584007913129639935") + }, + expected: "115792089237316195423570985008687907853269984665640564039457584007913129639935", + }, + { + name: "Large negative number", + setup: func() *Int { + return MustFromDecimal("-115792089237316195423570985008687907853269984665640564039457584007913129639935") + }, + expected: "-115792089237316195423570985008687907853269984665640564039457584007913129639935", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + z := tt.setup() + result := z.ToString() + if result != tt.expected { + t.Errorf("ToString() = %s, want %s", result, tt.expected) + } + }) + } +} diff --git a/_deploy/p/gnoswap/int256/gno.mod b/_deploy/p/gnoswap/int256/gno.mod new file mode 100644 index 000000000..0836dead6 --- /dev/null +++ b/_deploy/p/gnoswap/int256/gno.mod @@ -0,0 +1,3 @@ +module gno.land/p/gnoswap/int256 + +require gno.land/p/gnoswap/uint256 v0.0.0-latest diff --git a/_deploy/p/gnoswap/int256/int256.gno b/_deploy/p/gnoswap/int256/int256.gno new file mode 100644 index 000000000..58a3e0037 --- /dev/null +++ b/_deploy/p/gnoswap/int256/int256.gno @@ -0,0 +1,126 @@ +// This package provides a 256-bit signed integer type, Int, and associated functions. +package int256 + +import ( + "gno.land/p/gnoswap/uint256" +) + +var one = uint256.NewUint(1) + +type Int struct { + abs *uint256.Uint + neg bool +} + +// Zero returns a new Int set to 0. +func Zero() *Int { + return NewInt(0) +} + +// One returns a new Int set to 1. +func One() *Int { + return NewInt(1) +} + +// Sign returns: +// +// -1 if x < 0 +// 0 if x == 0 +// +1 if x > 0 +func (z *Int) Sign() int { + z.initiateAbs() + + if z.abs.IsZero() { + return 0 + } + if z.neg { + return -1 + } + return 1 +} + +// New returns a new Int set to 0. +func New() *Int { + return &Int{ + abs: new(uint256.Uint), + } +} + +// NewInt allocates and returns a new Int set to x. +func NewInt(x int64) *Int { + return New().SetInt64(x) +} + +// FromDecimal returns a new Int from a decimal string. +// Returns a new Int and an error if the string is not a valid decimal. +func FromDecimal(s string) (*Int, error) { + return new(Int).SetString(s) +} + +// MustFromDecimal returns a new Int from a decimal string. +// Panics if the string is not a valid decimal. +func MustFromDecimal(s string) *Int { + z, err := FromDecimal(s) + if err != nil { + panic(err) + } + return z +} + +// SetString sets s to the value of z and returns z and a boolean indicating success. +func (z *Int) SetString(s string) (*Int, error) { + neg := false + // Remove max one leading + + if len(s) > 0 && s[0] == '+' { + neg = false + s = s[1:] + } + + if len(s) > 0 && s[0] == '-' { + neg = true + s = s[1:] + } + var ( + abs *uint256.Uint + err error + ) + abs, err = uint256.FromDecimal(s) + if err != nil { + return nil, err + } + + return &Int{ + abs, + neg, + }, nil +} + +// FromUint256 is a convenience-constructor from uint256.Uint. +// Returns a new Int and whether overflow occurred. +// OBS: If u is `nil`, this method returns `nil, false` +func FromUint256(x *uint256.Uint) *Int { + if x == nil { + return nil + } + z := Zero() + + z.SetUint256(x) + return z +} + +// OBS, differs from original mempooler int256 +// NilToZero sets z to 0 and return it if it's nil, otherwise it returns z +func (z *Int) NilToZero() *Int { + if z == nil { + return NewInt(0) + } + return z +} + +// initiateAbs sets default value for `z` or `z.abs` value if is nil +// OBS: differs from mempooler int256. It checks not only `z.abs` but also `z` +func (z *Int) initiateAbs() { + if z == nil || z.abs == nil { + z.abs = new(uint256.Uint) + } +} diff --git a/_deploy/p/gnoswap/int256/int256_test.gno b/_deploy/p/gnoswap/int256/int256_test.gno new file mode 100644 index 000000000..974cc6ed8 --- /dev/null +++ b/_deploy/p/gnoswap/int256/int256_test.gno @@ -0,0 +1,25 @@ +// ported from github.com/mempooler/int256 +package int256 + +import ( + "testing" +) + +func TestSign(t *testing.T) { + tests := []struct { + x string + want int + }{ + {"0", 0}, + {"1", 1}, + {"-1", -1}, + } + + for _, tc := range tests { + z := MustFromDecimal(tc.x) + got := z.Sign() + if got != tc.want { + t.Errorf("Sign(%s) = %d, want %d", tc.x, got, tc.want) + } + } +} diff --git a/_deploy/p/gnoswap/pool/__TEST_0_INIT_VARIABLE_AND_HELPER_test.gno b/_deploy/p/gnoswap/pool/__TEST_0_INIT_VARIABLE_AND_HELPER_test.gno new file mode 100644 index 000000000..335400f9e --- /dev/null +++ b/_deploy/p/gnoswap/pool/__TEST_0_INIT_VARIABLE_AND_HELPER_test.gno @@ -0,0 +1,39 @@ +package pool + +import ( + "testing" +) + +func shouldEQ(t *testing.T, got, expected interface{}) { + if got != expected { + t.Errorf("got %v, expected %v", got, expected) + } +} + +func shouldNEQ(t *testing.T, got, expected interface{}) { + if got == expected { + t.Errorf("got %v, didn't expected %v", got, expected) + } +} + +func shouldPanic(t *testing.T, f func()) { + defer func() { + if r := recover(); r == nil { + t.Errorf("expected panic") + } + }() + f() +} + +func shouldPanicWithMsg(t *testing.T, f func(), msg string) { + defer func() { + if r := recover(); r == nil { + t.Errorf("The code did not panic") + } else { + if r != msg { + t.Errorf("excepted panic(%v), got(%v)", msg, r) + } + } + }() + f() +} diff --git a/_deploy/p/gnoswap/pool/__TEST_bit_math_test.gnoA b/_deploy/p/gnoswap/pool/__TEST_bit_math_test.gnoA new file mode 100644 index 000000000..fdb86ab6e --- /dev/null +++ b/_deploy/p/gnoswap/pool/__TEST_bit_math_test.gnoA @@ -0,0 +1,71 @@ +package pool + +import ( + "testing" + + u256 "gno.land/p/gnoswap/uint256" +) + +func TestBitMathMostSignificantBit(t *testing.T) { + t.Run("0", func(t *testing.T) { + shouldPanic( + t, + func() { + BitMathMostSignificantBit(u256.Zero()) + }, + ) + }) + + t.Run("1", func(t *testing.T) { + shouldEQ(t, BitMathMostSignificantBit(u256.One()), uint8(0)) + }) + + t.Run("2", func(t *testing.T) { + shouldEQ(t, BitMathMostSignificantBit(u256.NewUint(2)), uint8(1)) + }) + + t.Run("all powers of 2", func(t *testing.T) { + for i := 0; i < 256; i++ { + num := u256.Zero() + num.Lsh(u256.One(), uint(i)) + shouldEQ(t, BitMathMostSignificantBit(num), uint8(i)) + } + }) + + t.Run("uint256(-1)", func(t *testing.T) { + // BigNumber.from(2).pow(256).sub(1)) + shouldEQ(t, BitMathMostSignificantBit(u256.MustFromDecimal("115792089237316195423570985008687907853269984665640564039457584007913129639935")), uint8(255)) + }) +} + +func TestBitMathLeastSignificantBit(t *testing.T) { + t.Run("0", func(t *testing.T) { + shouldPanic( + t, + func() { + BitMathLeastSignificantBit(u256.Zero()) + }, + ) + }) + + t.Run("1", func(t *testing.T) { + shouldEQ(t, BitMathLeastSignificantBit(u256.One()), uint8(0)) + }) + + t.Run("2", func(t *testing.T) { + shouldEQ(t, BitMathLeastSignificantBit(u256.NewUint(2)), uint8(1)) + }) + + t.Run("all powers of 2", func(t *testing.T) { + for i := 0; i < 256; i++ { + num := u256.Zero() + num.Lsh(u256.One(), uint(i)) + shouldEQ(t, BitMathLeastSignificantBit(num), uint8(i)) + } + }) + + t.Run("uint256(-1)", func(t *testing.T) { + // BigNumber.from(2).pow(256).sub(1)) + shouldEQ(t, BitMathLeastSignificantBit(u256.MustFromDecimal("115792089237316195423570985008687907853269984665640564039457584007913129639935")), uint8(0)) + }) +} diff --git a/_deploy/p/gnoswap/pool/bit_math.gno b/_deploy/p/gnoswap/pool/bit_math.gno new file mode 100644 index 000000000..3c3820308 --- /dev/null +++ b/_deploy/p/gnoswap/pool/bit_math.gno @@ -0,0 +1,65 @@ +package pool + +import ( + u256 "gno.land/p/gnoswap/uint256" +) + +type bitShift struct { + bitPattern *u256.Uint + shift uint +} + +func BitMathMostSignificantBit(x *u256.Uint) uint8 { + if x.IsZero() { + panic("BitMathMostSignificantBit: x should not be zero") + } + + shifts := []bitShift{ + {u256.MustFromDecimal(Q128), 128}, // 2^128 + {u256.MustFromDecimal(Q64), 64}, // 2^64 + {u256.NewUint(0x100000000), 32}, + {u256.NewUint(0x10000), 16}, + {u256.NewUint(0x100), 8}, + {u256.NewUint(0x10), 4}, + {u256.NewUint(0x4), 2}, + {u256.NewUint(0x2), 1}, + } + + r := uint8(0) + for _, s := range shifts { + if x.Gte(s.bitPattern) { + x = new(u256.Uint).Rsh(x, s.shift) + r += uint8(s.shift) + } + } + + return r +} + +func BitMathLeastSignificantBit(x *u256.Uint) uint8 { + if x.IsZero() { + panic("BitMathLeastSignificantBit: x should not be zero") + } + + shifts := []bitShift{ + {u256.MustFromDecimal(MAX_UINT128), 128}, + {u256.MustFromDecimal(MAX_UINT64), 64}, + {u256.MustFromDecimal(MAX_UINT32), 32}, + {u256.MustFromDecimal(MAX_UINT16), 16}, + {u256.MustFromDecimal(MAX_UINT8), 8}, + {u256.NewUint(0xf), 4}, + {u256.NewUint(0x3), 2}, + {u256.NewUint(0x1), 1}, + } + + r := uint8(255) + for _, s := range shifts { + if new(u256.Uint).And(x, s.bitPattern).Gt(u256.Zero()) { + r -= uint8(s.shift) + } else { + x = new(u256.Uint).Rsh(x, s.shift) + } + } + + return r +} diff --git a/_deploy/p/gnoswap/pool/consts.gno b/_deploy/p/gnoswap/pool/consts.gno new file mode 100644 index 000000000..e8b0b33e1 --- /dev/null +++ b/_deploy/p/gnoswap/pool/consts.gno @@ -0,0 +1,16 @@ +package pool + +const ( + MAX_UINT8 string = "255" + MAX_UINT16 string = "65535" + MAX_UINT32 string = "4294967295" + MAX_UINT64 string = "18446744073709551615" + MAX_UINT128 string = "340282366920938463463374607431768211455" + MAX_UINT160 string = "1461501637330902918203684832716283019655932542975" + MAX_UINT256 string = "115792089237316195423570985008687907853269984665640564039457584007913129639935" + MAX_INT256 string = "57896044618658097711785492504343953926634992332820282019728792003956564819967" + + Q64 string = "18446744073709551616" // 2 ** 64 + Q96 string = "79228162514264337593543950336" // 2 ** 96 + Q128 string = "340282366920938463463374607431768211456" // 2 ** 128 +) diff --git a/_deploy/p/gnoswap/pool/gno.mod b/_deploy/p/gnoswap/pool/gno.mod new file mode 100644 index 000000000..ea092a3fa --- /dev/null +++ b/_deploy/p/gnoswap/pool/gno.mod @@ -0,0 +1 @@ +module gno.land/p/gnoswap/pool diff --git a/_deploy/p/gnoswap/pool/sqrt_price_math.gno b/_deploy/p/gnoswap/pool/sqrt_price_math.gno new file mode 100644 index 000000000..34a37d655 --- /dev/null +++ b/_deploy/p/gnoswap/pool/sqrt_price_math.gno @@ -0,0 +1,398 @@ +package pool + +import ( + i256 "gno.land/p/gnoswap/int256" + u256 "gno.land/p/gnoswap/uint256" +) + +var ( + Q96_RESOLUTION = uint(96) + Q160_RESOLUTION = uint(160) +) + +// sqrtPriceMathGetNextSqrtPriceFromAmount0RoundingUp calculates the next square root price +// based on the amount of token0 added or removed from the pool. +// NOTE: Always rounds up, because in the exact output case (increasing price) we need to move the price at least +// far enough to get the desired output amount, and in the exact input case (decreasing price) we need to move the +// price less in order to not send too much output. +// The most precise formula for this is liquidity * sqrtPX96 / (liquidity +- amount * sqrtPX96), +// if this is impossible because of overflow, we calculate liquidity / (liquidity / sqrtPX96 +- amount). +// +// Parameters: +// - sqrtPX96: The current square root price as a Q96 fixed-point number (uint160). +// - liquidity: The pool's active liquidity as a Q128 fixed-point number (uint128). +// - amount: The amount of token0 to be added or removed from the pool (uint256). +// - add: A boolean indicating whether the amount of token0 is being added (true) or removed (false). +// +// Returns: +// - The price after adding or removing amount, depending on add +// +// Notes: +// - When `add` is true, the function calculates the new square root price after adding `amount` of token0. +// - When `add` is false, the function calculates the new square root price after removing `amount` of token0. +// - The function uses high-precision math (MulDivRoundingUp, DivRoundingUp) to handle division rounding issues. +// - The function validates input conditions and panics if the state is invalid. +func sqrtPriceMathGetNextSqrtPriceFromAmount0RoundingUp( + sqrtPX96 *u256.Uint, + liquidity *u256.Uint, + amount *u256.Uint, + add bool, +) *u256.Uint { + // we short circuit amount == 0 because the result is otherwise not guaranteed to equal the input price + if amount.IsZero() { + return sqrtPX96 + } + + numerator1 := new(u256.Uint).Lsh(liquidity, Q96_RESOLUTION) + product := new(u256.Uint).Mul(amount, sqrtPX96) + + if add { + if new(u256.Uint).Div(product, amount).Eq(sqrtPX96) { + denominator := new(u256.Uint).Add(numerator1, product) + + if denominator.Gte(numerator1) { + return u256.MulDivRoundingUp(numerator1, sqrtPX96, denominator) + } + } + + divValue := new(u256.Uint).Div(numerator1, sqrtPX96) + addValue := new(u256.Uint).Add(divValue, amount) + return u256.DivRoundingUp(numerator1, addValue) + } else { + cond1 := new(u256.Uint).Div(product, amount).Eq(sqrtPX96) + cond2 := numerator1.Gt(product) + + if !(cond1 && cond2) { + panic("invalid pool sqrt price calculation: product/amount != sqrtPX96 or numerator1 <= product") + } + + denominator := new(u256.Uint).Sub(numerator1, product) + nextSqrtPrice := u256.MulDivRoundingUp(numerator1, sqrtPX96, denominator) + max160 := u256.MustFromDecimal(MAX_UINT160) + if nextSqrtPrice.Gt(max160) { + panic("nextSqrtPrice overflows uint160") + } + return nextSqrtPrice + } +} + +// sqrtPriceMathGetNextSqrtPriceFromAmount1RoundingDown calculates the next square root price +// based on the amount of token1 added or removed from the pool, with rounding down. +// NOTE: Always rounds down, because in the exact output case (decreasing price) we need to move the price at least +// far enough to get the desired output amount, and in the exact input case (increasing price) we need to move the +// price less in order to not send too much output. +// The formula we compute is within <1 wei of the lossless version: sqrtPX96 +- amount / liquidity +// +// Parameters: +// - sqrtPX96: The current square root price as a Q96 fixed-point number (uint160). +// - liquidity: The pool's active liquidity as a Q128 fixed-point number (uint128). +// - amount: The amount of token1 to be added or removed from the pool (uint256). +// - add: A boolean indicating whether the amount of token1 is being added (true) or removed (false). +// +// Returns: +// - The next square root price as a Q96 fixed-point number (uint160). +// +// Notes: +// - When `add` is true, the function calculates the new square root price after adding `amount` of token1. +// - When `add` is false, the function calculates the new square root price after removing `amount` of token1. +// - The function uses high-precision math (MulDiv and DivRoundingUp) to handle division and prevent precision loss. +// - The function validates input conditions and panics if the state is invalid. +func sqrtPriceMathGetNextSqrtPriceFromAmount1RoundingDown( + sqrtPX96 *u256.Uint, // uint160 + liquidity *u256.Uint, // uint1288 + amount *u256.Uint, // uint256 + add bool, +) *u256.Uint { // uint160 + quotient := u256.Zero() + max160 := u256.MustFromDecimal(MAX_UINT160) + + // if we're adding (subtracting), rounding down requires rounding the quotient down (up) + // in both cases, avoid a mulDiv for most inputs + if add { + if amount.Lte(u256.MustFromDecimal(MAX_UINT160)) { + value := new(u256.Uint).Lsh(amount, Q96_RESOLUTION) + quotient = new(u256.Uint).Div(value, liquidity) + } else { + quotient = u256.MulDiv(amount, u256.MustFromDecimal(Q96), liquidity) + } + + res := new(u256.Uint).Add(sqrtPX96, quotient) + if res.Gt(max160) { + panic("GetNextSqrtPriceFromAmount1RoundingDown sqrtPx96 + quotient overflow uint160") + } + return res + } else { + if amount.Lte(u256.MustFromDecimal(MAX_UINT160)) { + value := new(u256.Uint).Lsh(amount, Q96_RESOLUTION) + quotient = u256.DivRoundingUp(value, liquidity) + } else { + quotient = u256.MulDivRoundingUp(amount, u256.MustFromDecimal(Q96), liquidity) + } + + if !(sqrtPX96.Gt(quotient)) { + panic("sqrt price exceeds calculated quotient") + } + + res := new(u256.Uint).Sub(sqrtPX96, quotient) + if res.Gt(max160) { + mask := new(u256.Uint).Lsh(u256.One(), Q160_RESOLUTION) + mask = mask.Sub(mask, u256.One()) + res = res.And(res, mask) + } + return res + } +} + +// sqrtPriceMathGetNextSqrtPriceFromInput calculates the next square root price +// based on the amount of token0 or token1 added to the pool. +// NOTE: Always rounds up, because in the exact output case (increasing price) we need to move the price at least +// far enough to get the desired output amount, and in the exact input case (decreasing price) we need to move the +// price less in order to not send too much output. +// The most precise formula for this is liquidity * sqrtPX96 / (liquidity +- amount * sqrtPX96), +// if this is impossible because of overflow, we calculate liquidity / (liquidity / sqrtPX96 +- amount). +// +// Parameters: +// - sqrtPX96: The current square root price as a Q96 fixed-point number (uint160). +// - liquidity: The pool's active liquidity as a Q128 fixed-point number (uint128). +// - amountIn: The amount of token0 or token1 to be added to the pool (uint256). +// - zeroForOne: A boolean indicating whether the amount is being added to token0 (true) or token1 (false). +// +// Returns: +// - The price after adding amountIn, depending on zeroForOne +func sqrtPriceMathGetNextSqrtPriceFromInput( + sqrtPX96 *u256.Uint, + liquidity *u256.Uint, + amountIn *u256.Uint, + zeroForOne bool, +) *u256.Uint { + if sqrtPX96.IsZero() { + panic("sqrtPX96 should not be zero") + } + + if liquidity.IsZero() { + panic("liquidity should not be zero") + } + + if zeroForOne { + return sqrtPriceMathGetNextSqrtPriceFromAmount0RoundingUp(sqrtPX96, liquidity, amountIn, true) + } else { + return sqrtPriceMathGetNextSqrtPriceFromAmount1RoundingDown(sqrtPX96, liquidity, amountIn, true) + } +} + +// sqrtPriceMathGetNextSqrtPriceFromOutput calculates the next square root price +// based on the amount of token0 or token1 removed from the pool. +// +// NOTE: +// - For zeroForOne == true (Token0 -> Token1): The calculation uses rounding down. +// - For zeroForOne == false (Token1 -> Token0): The calculation uses rounding up. +// +// The most precise formula for this is: +// - liquidity * sqrtPX96 / (liquidity ± amount * sqrtPX96) +// If overflow occurs, it falls back to: +// - liquidity / (liquidity / sqrtPX96 ± amount) +// +// Parameters: +// - sqrtPX96: The current square root price as a Q96 fixed-point number (uint160). +// - liquidity: The pool's active liquidity as a Q128 fixed-point number (uint128). +// - amountOut: The amount of token0 or token1 to be removed from the pool (uint256). +// - zeroForOne: A boolean indicating whether the amount is being removed from token0 (true) or token1 (false). +// +// Returns: +// - The price after removing amountOut, depending on zeroForOne. +// +// Notes: +// - Rounding direction depends on the swap direction (zeroForOne). +// - Relies on helper functions: +// - `sqrtPriceMathGetNextSqrtPriceFromAmount1RoundingDown` for Token0 -> Token1. +// - `sqrtPriceMathGetNextSqrtPriceFromAmount0RoundingUp` for Token1 -> Token0. +func sqrtPriceMathGetNextSqrtPriceFromOutput( + sqrtPX96 *u256.Uint, + liquidity *u256.Uint, + amountOut *u256.Uint, + zeroForOne bool, +) *u256.Uint { + if sqrtPX96.IsZero() { + panic("sqrtPX96 should not be zero") + } + + if liquidity.IsZero() { + panic("liquidity should not be zero") + } + + if zeroForOne { + return sqrtPriceMathGetNextSqrtPriceFromAmount1RoundingDown(sqrtPX96, liquidity, amountOut, false) + } else { + return sqrtPriceMathGetNextSqrtPriceFromAmount0RoundingUp(sqrtPX96, liquidity, amountOut, false) + } +} + +// sqrtPriceMathGetAmount0DeltaHelper calculates the absolute difference between the amounts of token0 in two +// liquidity ranges defined by the square root prices sqrtRatioAX96 and sqrtRatioBX96. The difference is +// calculated relative to the range [sqrtRatioAX96, sqrtRatioBX96]. +// +// If sqrtRatioAX96 > sqrtRatioBX96, their values are swapped to ensure sqrtRatioAX96 is the lower bound. +// +// Parameters: +// - sqrtRatioAX96: The lower bound of the range as a Q96 fixed-point number (uint160). +// - sqrtRatioBX96: The upper bound of the range as a Q96 fixed-point number (uint160). +// - liquidity: The pool's active liquidity as a Q128 fixed-point number (uint128). +// - roundUp: A boolean indicating whether the result should be rounded up (true) or down (false). +// +// Returns: +// - The absolute difference between the amounts of token0 in the two ranges as a uint256. +// +// Notes: +// - If sqrtRatioAX96 is zero or negative, the function panics. +// - The result is calculated using high-precision fixed-point arithmetic. +// - Rounding is applied based on the roundUp parameter. +func sqrtPriceMathGetAmount0DeltaHelper( + sqrtRatioAX96 *u256.Uint, + sqrtRatioBX96 *u256.Uint, + liquidity *u256.Uint, + roundUp bool, +) *u256.Uint { + if sqrtRatioAX96.Gt(sqrtRatioBX96) { + sqrtRatioAX96, sqrtRatioBX96 = sqrtRatioBX96, sqrtRatioAX96 + } + + numerator1 := new(u256.Uint).Lsh(liquidity, Q96_RESOLUTION) + numerator2 := new(u256.Uint).Sub(sqrtRatioBX96, sqrtRatioAX96) + + if !(sqrtRatioAX96.Gt(u256.Zero())) { + panic("sqrtRatioAX96 must be greater than zero") + } + + if roundUp { + value := u256.MulDivRoundingUp(numerator1, numerator2, sqrtRatioBX96) + return u256.DivRoundingUp(value, sqrtRatioAX96) + } else { + value := u256.MulDiv(numerator1, numerator2, sqrtRatioBX96) + return new(u256.Uint).Div(value, sqrtRatioAX96) + } +} + +// sqrtPriceMathGetAmount1DeltaHelper calculates the absolute difference between the amounts of token1 in two +// liquidity ranges defined by the square root prices sqrtRatioAX96 and sqrtRatioBX96. The difference is +// calculated relative to the range [sqrtRatioAX96, sqrtRatioBX96]. +// +// If sqrtRatioAX96 > sqrtRatioBX96, their values are swapped to ensure sqrtRatioAX96 is the lower bound. +// +// Parameters: +// - sqrtRatioAX96: The lower bound of the range as a Q96 fixed-point number (uint160). +// - sqrtRatioBX96: The upper bound of the range as a Q96 fixed-point number (uint160). +// - liquidity: The pool's active liquidity as a Q128 fixed-point number (uint128). +// - roundUp: A boolean indicating whether the result should be rounded up (true) or down (false). +// +// Returns: +// - The absolute difference between the amounts of token1 in the two ranges as a uint256. +// +// Notes: +// - Rounding is applied based on the roundUp parameter. +// - The function swaps sqrtRatioAX96 and sqrtRatioBX96 if sqrtRatioAX96 > sqrtRatioBX96. +func sqrtPriceMathGetAmount1DeltaHelper( + sqrtRatioAX96 *u256.Uint, + sqrtRatioBX96 *u256.Uint, + liquidity *u256.Uint, + roundUp bool, +) *u256.Uint { + if sqrtRatioAX96.Gt(sqrtRatioBX96) { + sqrtRatioAX96, sqrtRatioBX96 = sqrtRatioBX96, sqrtRatioAX96 + } + + diff := new(u256.Uint).Sub(sqrtRatioBX96, sqrtRatioAX96) + if roundUp { + return u256.MulDivRoundingUp(liquidity, diff, u256.MustFromDecimal(Q96)) + } else { + return u256.MulDiv(liquidity, diff, u256.MustFromDecimal(Q96)) + } +} + +// SqrtPriceMathGetAmount0DeltaStr calculates the difference in the amount of token0 +// within a specified liquidity range defined by two square root prices (sqrtRatioAX96 and sqrtRatioBX96). +// This function returns the result as a string representation of an int256 value. +// +// If the liquidity is negative, the result is also negative. +// +// Parameters: +// - sqrtRatioAX96: The lower bound of the range as a Q96 fixed-point number (uint160). +// - sqrtRatioBX96: The upper bound of the range as a Q96 fixed-point number (uint160). +// - liquidity: The pool's active liquidity as a signed Q128 fixed-point number (int128). +// +// Returns: +// - A string representation of the int256 value representing the difference in token0 amounts +// within the specified range. The value is negative if the liquidity is negative. +// +// Notes: +// - This function relies on the helper function `sqrtPriceMathGetAmount0DeltaHelper` to perform the core calculation. +// - The helper function calculates the absolute difference between token0 amounts within the range. +// - If the computed result exceeds the maximum allowable value for int256 (2**255 - 1), the function will panic +// with an appropriate overflow error. +// - The rounding behavior of the result is controlled by the `roundUp` parameter passed to the helper function: +// - For negative liquidity, rounding is always down. +// - For positive liquidity, rounding is always up. +func SqrtPriceMathGetAmount0DeltaStr( + sqrtRatioAX96 *u256.Uint, + sqrtRatioBX96 *u256.Uint, + liquidity *i256.Int, +) string { + if liquidity.IsNeg() { + u := sqrtPriceMathGetAmount0DeltaHelper(sqrtRatioAX96, sqrtRatioBX96, liquidity.Abs(), false) + if u.Gt(u256.MustFromDecimal(MAX_INT256)) { + // if u > (2**255 - 1), cannot cast to int256 + panic("SqrtPriceMathGetAmount0DeltaStr: overflow") + } + i := i256.FromUint256(u) + return i256.Zero().Neg(i).ToString() + } else { + u := sqrtPriceMathGetAmount0DeltaHelper(sqrtRatioAX96, sqrtRatioBX96, liquidity.Abs(), true) + if u.Gt(u256.MustFromDecimal(MAX_INT256)) { + // if u > (2**255 - 1), cannot cast to int256 + panic("SqrtPriceMathGetAmount0DeltaStr: overflow") + } + return i256.FromUint256(u).ToString() + } +} + +// SqrtPriceMathGetAmount1DeltaStr calculates the difference in the amount of token1 +// within a specified liquidity range defined by two square root prices (sqrtRatioAX96 and sqrtRatioBX96). +// This function returns the result as a string representation of an int256 value. +// +// If the liquidity is negative, the result is also negative. +// +// Parameters: +// - sqrtRatioAX96: The lower bound of the range as a Q96 fixed-point number (uint160). +// - sqrtRatioBX96: The upper bound of the range as a Q96 fixed-point number (uint160). +// - liquidity: The pool's active liquidity as a signed Q128 fixed-point number (int128). +// +// Returns: +// - A string representation of the int256 value representing the difference in token1 amounts +// within the specified range. The value is negative if the liquidity is negative. +// +// Notes: +// - This function relies on the helper function `sqrtPriceMathGetAmount1DeltaHelper` to perform the core calculation. +// - The rounding behavior of the result is controlled by the `roundUp` parameter passed to the helper function: +// - For negative liquidity, rounding is always down. +// - For positive liquidity, rounding is always up. +func SqrtPriceMathGetAmount1DeltaStr( + sqrtRatioAX96 *u256.Uint, + sqrtRatioBX96 *u256.Uint, + liquidity *i256.Int, +) string { + if liquidity.IsNeg() { + u := sqrtPriceMathGetAmount1DeltaHelper(sqrtRatioAX96, sqrtRatioBX96, liquidity.Abs(), false) + if u.Gt(u256.MustFromDecimal(MAX_INT256)) { + // if u > (2**255 - 1), cannot cast to int256 + panic("SqrtPriceMathGetAmount1DeltaStr: overflow") + } + i := i256.FromUint256(u) + return i256.Zero().Neg(i).ToString() + } else { + u := sqrtPriceMathGetAmount1DeltaHelper(sqrtRatioAX96, sqrtRatioBX96, liquidity.Abs(), true) + if u.Gt(u256.MustFromDecimal(MAX_INT256)) { + // if u > (2**255 - 1), cannot cast to int256 + panic("SqrtPriceMathGetAmount1DeltaStr: overflow") + } + return i256.FromUint256(u).ToString() + } +} diff --git a/_deploy/p/gnoswap/pool/sqrt_price_math_test.gno b/_deploy/p/gnoswap/pool/sqrt_price_math_test.gno new file mode 100644 index 000000000..24f450d51 --- /dev/null +++ b/_deploy/p/gnoswap/pool/sqrt_price_math_test.gno @@ -0,0 +1,643 @@ +package pool + +import ( + "testing" + + "gno.land/p/demo/uassert" + + i256 "gno.land/p/gnoswap/int256" + u256 "gno.land/p/gnoswap/uint256" +) + +func TestSqrtPriceMathGetNextSqrtPriceFromAmount0RoundingUp(t *testing.T) { + t.Run("zero amount returns same price", func(t *testing.T) { + sqrtPX96 := u256.MustFromDecimal("1000000") + liquidity := u256.MustFromDecimal("2000000") + amount := u256.Zero() + + result := sqrtPriceMathGetNextSqrtPriceFromAmount0RoundingUp( + sqrtPX96, + liquidity, + amount, + true, + ) + + if !result.Eq(sqrtPX96) { + t.Errorf("Expected %s, got %s", sqrtPX96.ToString(), result.ToString()) + } + }) + + t.Run("remove token0", func(t *testing.T) { + sqrtPX96 := u256.MustFromDecimal("1000000") + liquidity := u256.MustFromDecimal("2000000") + amount := u256.MustFromDecimal("500000") + + result := sqrtPriceMathGetNextSqrtPriceFromAmount0RoundingUp( + sqrtPX96, + liquidity, + amount, + false, + ) + + if result.Lte(sqrtPX96) { + t.Error("Price should increase when removing token0") + } + }) +} + +func TestSqrtPriceMathGetNextSqrtPriceFromAmount1RoundingDown(t *testing.T) { + t.Run("add token1 small amount", func(t *testing.T) { + sqrtPX96 := u256.MustFromDecimal("1000000") + liquidity := u256.MustFromDecimal("2000000") + amount := u256.MustFromDecimal("100000") + + result := sqrtPriceMathGetNextSqrtPriceFromAmount1RoundingDown( + sqrtPX96, + liquidity, + amount, + true, + ) + + if result.Lte(sqrtPX96) { + t.Error("Price should increase when adding token1") + } + }) +} + +func TestSqrtPriceMathGetAmount0DeltaStr(t *testing.T) { + t.Run("positive liquidity", func(t *testing.T) { + ratioA := u256.MustFromDecimal("1000000") + ratioB := u256.MustFromDecimal("2000000") + liquidity := i256.FromUint256(u256.MustFromDecimal("5000000")) + + result := SqrtPriceMathGetAmount0DeltaStr(ratioA, ratioB, liquidity) + + if result[0] == '-' { + t.Error("Result should be positive for positive liquidity") + } + }) + + t.Run("negative liquidity", func(t *testing.T) { + ratioA := u256.MustFromDecimal("1000000") + ratioB := u256.MustFromDecimal("2000000") + liquidity := i256.Zero().Neg(i256.FromUint256(u256.MustFromDecimal("5000000"))) + + result := SqrtPriceMathGetAmount0DeltaStr(ratioA, ratioB, liquidity) + + if result[0] != '-' { + t.Error("Result should be negative for negative liquidity") + } + }) + + t.Run("panic overflow when getting amount0 with positive liquidity", func(t *testing.T) { + defer func() { + if r := recover(); r == nil { + t.Error("Expected panic for overflow amount0") + } else { + uassert.Equal(t, "SqrtPriceMathGetAmount0DeltaStr: overflow", r) + } + }() + + // Inputs to trigger panic + sqrtRatioAX96 := u256.MustFromDecimal("1") // very low value + sqrtRatioBX96 := u256.MustFromDecimal("340282366920938463463374607431768211455") // very high value(2^128-1) + liquidity := i256.FromUint256(u256.MustFromDecimal("115792089237316195423570985008687907853269984665640564039457584007913129639935")) + + SqrtPriceMathGetAmount0DeltaStr(sqrtRatioAX96, sqrtRatioBX96, liquidity) + }) + + t.Run("panic overflow when getting amount0 with negative liquidity", func(t *testing.T) { + defer func() { + if r := recover(); r == nil { + t.Error("Expected panic for overflow amount0") + } else { + uassert.Equal(t, "SqrtPriceMathGetAmount0DeltaStr: overflow", r) + } + }() + + // Inputs to trigger panic + sqrtRatioAX96 := u256.MustFromDecimal("1") // very low value + sqrtRatioBX96 := u256.MustFromDecimal("340282366920938463463374607431768211455") // very high value(2^128-1) + liquidity := i256.FromUint256(u256.MustFromDecimal("115792089237316195423570985008687907853269984665640564039457584007913129639935")) + liquidity = liquidity.Neg(liquidity) // Make liquidity negative + + SqrtPriceMathGetAmount0DeltaStr(sqrtRatioAX96, sqrtRatioBX96, liquidity) + }) +} + +func TestSqrtPriceMathGetAmount1DeltaStr(t *testing.T) { + t.Run("positive liquidity", func(t *testing.T) { + ratioA := u256.MustFromDecimal("1000000") + ratioB := u256.MustFromDecimal("2000000") + liquidity := i256.FromUint256(u256.MustFromDecimal("5000000")) + + result := SqrtPriceMathGetAmount1DeltaStr(ratioA, ratioB, liquidity) + + if result[0] == '-' { + t.Error("Result should be positive for positive liquidity") + } + }) + + t.Run("negative liquidity", func(t *testing.T) { + ratioA := u256.MustFromDecimal("1000000") + ratioB := u256.MustFromDecimal("2000000") + liquidity := i256.Zero().Neg(i256.FromUint256(u256.MustFromDecimal("5000000"))) + + result := SqrtPriceMathGetAmount0DeltaStr(ratioA, ratioB, liquidity) + + if result[0] != '-' { + t.Error("Result should be negative for negative liquidity") + } + }) + + t.Run("panic overflow when getting amount1 with positive liquidity", func(t *testing.T) { + defer func() { + if r := recover(); r == nil { + t.Error("Expected panic for overflow amount1") + } else { + uassert.Equal(t, "SqrtPriceMathGetAmount1DeltaStr: overflow", r) + } + }() + + // Inputs to trigger panic + sqrtRatioAX96 := u256.MustFromDecimal("1") // very low value + sqrtRatioBX96 := u256.MustFromDecimal("79228162514264337593543950335") // slightly below Q96 + liquidity := i256.FromUint256(u256.MustFromDecimal("115792089237316195423570985008687907853269984665640564039457584007913129639935")) + + SqrtPriceMathGetAmount1DeltaStr(sqrtRatioAX96, sqrtRatioBX96, liquidity) + }) + + t.Run("panic overflow when getting amount1 with negative liquidity", func(t *testing.T) { + defer func() { + if r := recover(); r == nil { + t.Error("Expected panic for overflow amount1") + } else { + uassert.Equal(t, "SqrtPriceMathGetAmount1DeltaStr: overflow", r) + } + }() + + // Inputs to trigger panic + sqrtRatioAX96 := u256.MustFromDecimal("1") // very low value + sqrtRatioBX96 := u256.MustFromDecimal("79228162514264337593543950335") // slightly below Q96 + liquidity := i256.FromUint256(u256.MustFromDecimal("115792089237316195423570985008687907853269984665640564039457584007913129639935")) + liquidity = liquidity.Neg(liquidity) // Make liquidity negative + + SqrtPriceMathGetAmount1DeltaStr(sqrtRatioAX96, sqrtRatioBX96, liquidity) + }) +} + +func TestSqrtPriceMathGetNextSqrtPriceFromInput(t *testing.T) { + tests := []struct { + name string + sqrtPriceX96 *u256.Uint + liquidity *u256.Uint + amountIn *u256.Uint + zeroForOne bool + shouldPanic bool + panicMsg string + expectedSqrtPriceX96 string + }{ + { + name: "fails if price is zero", + sqrtPriceX96: u256.Zero(), + liquidity: u256.Zero(), + amountIn: u256.MustFromDecimal("100000000000000000"), + zeroForOne: false, + shouldPanic: true, + panicMsg: "sqrtPX96 should not be zero", + }, + { + name: "fails if liquidity is zero", + sqrtPriceX96: u256.One(), + liquidity: u256.Zero(), + amountIn: u256.MustFromDecimal("100000000000000000"), + zeroForOne: true, + shouldPanic: true, + panicMsg: "liquidity should not be zero", + }, + { + name: "fails if input amount overflows the price", + sqrtPriceX96: u256.MustFromDecimal("1461501637330902918203684832716283019655932542975"), // 2^160 - 1 + liquidity: u256.MustFromDecimal("1024"), + amountIn: u256.MustFromDecimal("1024"), + zeroForOne: false, + shouldPanic: true, + panicMsg: "sqrtPx96 + quotient overflow uint160", + }, + { + name: "any input amount cannot underflow the price", + sqrtPriceX96: u256.MustFromDecimal("1"), + liquidity: u256.MustFromDecimal("1"), + amountIn: u256.MustFromDecimal("57896044618658097711785492504343953926634992332820282019728792003956564819968"), // 2^255 + zeroForOne: true, + expectedSqrtPriceX96: "1", + }, + { + name: "returns input price if amount in is zero and zeroForOne = true", + sqrtPriceX96: u256.MustFromDecimal("79228162514264337593543950336"), + liquidity: u256.MustFromDecimal("100000000000000000"), + amountIn: u256.Zero(), + zeroForOne: true, + expectedSqrtPriceX96: "79228162514264337593543950336", + }, + { + name: "returns input price if amount in is zero and zeroForOne = false", + sqrtPriceX96: u256.MustFromDecimal("79228162514264337593543950336"), + liquidity: u256.MustFromDecimal("100000000000000000"), + amountIn: u256.Zero(), + zeroForOne: false, + expectedSqrtPriceX96: "79228162514264337593543950336", + }, + { + name: "returns the minimum price for max inputs", + sqrtPriceX96: u256.MustFromDecimal("1461501637330902918203684832716283019655932542975"), // 2^160 - 1 + liquidity: u256.MustFromDecimal("340282366920938463463374607431768211455"), // 2^128 - 1 + amountIn: u256.MustFromDecimal("115792089237316195423570985008687907853269984665640564039439137263839420088320"), + zeroForOne: true, + expectedSqrtPriceX96: "1", + }, + { + name: "input amount of 0.1 token1", + sqrtPriceX96: encodePriceSqrt("1", "1"), + liquidity: u256.MustFromDecimal("1000000000000000000"), + amountIn: u256.MustFromDecimal("100000000000000000"), + zeroForOne: false, + expectedSqrtPriceX96: "87150978765690771352898345369", + }, + { + name: "input amount of 0.1 token0", + sqrtPriceX96: encodePriceSqrt("1", "1"), + liquidity: u256.MustFromDecimal("1000000000000000000"), + amountIn: u256.MustFromDecimal("100000000000000000"), + zeroForOne: true, + expectedSqrtPriceX96: "72025602285694852357767227579", + }, + { + name: "amountIn > type(uint96).max and zeroForOne = true", + sqrtPriceX96: encodePriceSqrt("1", "1"), + liquidity: u256.MustFromDecimal("10000000000000000000"), + amountIn: u256.MustFromDecimal("1267650600228229401496703205376"), // 2^128 - 1 + zeroForOne: true, + expectedSqrtPriceX96: "624999999995069620", + }, + { + name: "can return 1 with enough amountIn and zeroForOne = true", + sqrtPriceX96: encodePriceSqrt("1", "1"), + liquidity: u256.One(), + amountIn: u256.MustFromDecimal("57896044618658097711785492504343953926634992332820282019728792003956564819967"), + zeroForOne: true, + expectedSqrtPriceX96: "1", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.shouldPanic { + uassert.PanicsWithMessage(t, tt.panicMsg, func() { + sqrtPriceMathGetNextSqrtPriceFromInput(tt.sqrtPriceX96, tt.liquidity, tt.amountIn, tt.zeroForOne) + }) + } else { + actual := sqrtPriceMathGetNextSqrtPriceFromInput(tt.sqrtPriceX96, tt.liquidity, tt.amountIn, tt.zeroForOne) + uassert.Equal(t, tt.expectedSqrtPriceX96, actual.ToString()) + } + }) + } +} + +func TestSqrtPriceMathGetNextSqrtPriceFromInput2(t *testing.T) { + t.Run("zero sqrtPX96 should panic", func(t *testing.T) { + defer func() { + if r := recover(); r == nil { + t.Error("Expected panic for zero sqrtPX96") + } + }() + + sqrtPriceMathGetNextSqrtPriceFromInput( + u256.Zero(), + u256.MustFromDecimal("1000000"), + u256.MustFromDecimal("500000"), + true, + ) + }) + + t.Run("zero liquidity should panic", func(t *testing.T) { + defer func() { + if r := recover(); r == nil { + t.Error("Expected panic for zero liquidity") + } + }() + + sqrtPriceMathGetNextSqrtPriceFromInput( + u256.MustFromDecimal("1000000"), + u256.Zero(), + u256.MustFromDecimal("500000"), + true, + ) + }) +} + +func TestSqrtPriceMathGetNextSqrtPriceFromOutput(t *testing.T) { + tests := []struct { + name string + sqrtPriceX96 *u256.Uint + liquidity *u256.Uint + amountOut *u256.Uint + zeroForOne bool + shouldPanic bool + expectedSqrtPriceX96 string + }{ + { + name: "fails if price is zero", + sqrtPriceX96: u256.Zero(), + liquidity: u256.Zero(), + amountOut: u256.MustFromDecimal("100000000000000000"), + zeroForOne: false, + shouldPanic: true, + }, + { + name: "fails if liquidity is zero", + sqrtPriceX96: u256.One(), + liquidity: u256.Zero(), + amountOut: u256.MustFromDecimal("100000000000000000"), + zeroForOne: true, + shouldPanic: true, + }, + { + name: "fails if output amount is exactly the virtual reserves of token0", + sqrtPriceX96: u256.MustFromDecimal("20282409603651670423947251286016"), + liquidity: u256.MustFromDecimal("1024"), + amountOut: u256.NewUint(4), + zeroForOne: false, + shouldPanic: true, + }, + { + name: "fails if output amount is greater than virtual reserves of token0", + sqrtPriceX96: u256.MustFromDecimal("20282409603651670423947251286016"), + liquidity: u256.MustFromDecimal("1024"), + amountOut: u256.NewUint(5), + zeroForOne: false, + shouldPanic: true, + }, + { + name: "fails if output amount is greater than virtual reserves of token1", + sqrtPriceX96: u256.MustFromDecimal("20282409603651670423947251286016"), + liquidity: u256.MustFromDecimal("1024"), + amountOut: u256.NewUint(262145), + zeroForOne: true, + shouldPanic: true, + }, + { + name: "fails if output amount is exactly the virtual reserves of token1", + sqrtPriceX96: u256.MustFromDecimal("20282409603651670423947251286016"), + liquidity: u256.MustFromDecimal("1024"), + amountOut: u256.NewUint(262144), + zeroForOne: true, + shouldPanic: true, + }, + { + name: "succeeds if output amount is just less than the virtual reserves of token1", + sqrtPriceX96: u256.MustFromDecimal("20282409603651670423947251286016"), + liquidity: u256.MustFromDecimal("1024"), + amountOut: u256.NewUint(262143), + zeroForOne: true, + expectedSqrtPriceX96: "77371252455336267181195264", + }, + { + name: "puzzling echidna test", + sqrtPriceX96: u256.MustFromDecimal("20282409603651670423947251286016"), + liquidity: u256.MustFromDecimal("1024"), + amountOut: u256.NewUint(4), + zeroForOne: false, + shouldPanic: true, + }, + { + name: "returns input price if amount in is zero and zeroForOne = true", + sqrtPriceX96: encodePriceSqrt("1", "1"), + liquidity: u256.MustFromDecimal("100000000000000000"), + amountOut: u256.Zero(), + zeroForOne: true, + expectedSqrtPriceX96: encodePriceSqrt("1", "1").ToString(), + }, + { + name: "returns input price if amount in is zero and zeroForOne = false", + sqrtPriceX96: encodePriceSqrt("1", "1"), + liquidity: u256.MustFromDecimal("100000000000000000"), + amountOut: u256.Zero(), + zeroForOne: false, + expectedSqrtPriceX96: encodePriceSqrt("1", "1").ToString(), + }, + { + name: "output amount of 0.1 token1, zeroForOne = false", + sqrtPriceX96: encodePriceSqrt("1", "1"), + liquidity: u256.MustFromDecimal("1000000000000000000"), + amountOut: u256.MustFromDecimal("100000000000000000"), + zeroForOne: false, + expectedSqrtPriceX96: "88031291682515930659493278152", + }, + { + name: "output amount of 0.1 token1, zeroForOne = true", + sqrtPriceX96: encodePriceSqrt("1", "1"), + liquidity: u256.MustFromDecimal("1000000000000000000"), + amountOut: u256.MustFromDecimal("100000000000000000"), + zeroForOne: true, + expectedSqrtPriceX96: "71305346262837903834189555302", + }, + { + name: "reverts if amountOut is impossible in zero for one direction", + sqrtPriceX96: encodePriceSqrt("1", "1"), + liquidity: u256.NewUint(1), + amountOut: u256.MustFromDecimal(MAX_UINT256), + zeroForOne: true, + shouldPanic: true, + }, + { + name: "reverts if amountOut is impossible in one for zero direction", + sqrtPriceX96: encodePriceSqrt("1", "1"), + liquidity: u256.NewUint(1), + amountOut: u256.MustFromDecimal(MAX_UINT256), + zeroForOne: false, + shouldPanic: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.shouldPanic { + defer func() { + if r := recover(); r == nil { + t.Errorf("Expected panic for %s", tt.name) + } + }() + sqrtPriceMathGetNextSqrtPriceFromOutput(tt.sqrtPriceX96, tt.liquidity, tt.amountOut, tt.zeroForOne) + + } else { + actual := sqrtPriceMathGetNextSqrtPriceFromOutput(tt.sqrtPriceX96, tt.liquidity, tt.amountOut, tt.zeroForOne) + uassert.Equal(t, tt.expectedSqrtPriceX96, actual.ToString()) + } + }) + } +} + +func TestSqrtPriceMathGetAmount0DeltaHelper(t *testing.T) { + tests := []struct { + name string + sqrtRatioAX96, sqrtRatioBX96, liquidity *u256.Uint + roundUp bool + expectedAmount0Delta string + }{ + { + name: "returns 0 if liquidity is 0", + sqrtRatioAX96: encodePriceSqrt("1", "1"), + sqrtRatioBX96: encodePriceSqrt("2", "1"), + liquidity: u256.Zero(), + roundUp: true, + expectedAmount0Delta: "0", + }, + { + name: "returns 0 if prices are equal", + sqrtRatioAX96: encodePriceSqrt("1", "1"), + sqrtRatioBX96: encodePriceSqrt("1", "1"), + liquidity: u256.Zero(), + roundUp: true, + expectedAmount0Delta: "0", + }, + { + name: "returns 0.1 amount1 for price of 1 to 1.21, roundUp = true", + sqrtRatioAX96: encodePriceSqrt("1", "1"), + sqrtRatioBX96: encodePriceSqrt("121", "100"), + liquidity: u256.MustFromDecimal("1000000000000000000"), + roundUp: true, + expectedAmount0Delta: "90909090909090910", + }, + { + name: "returns 0.1 amount1 for price of 1 to 1.21, roundUp = false", + sqrtRatioAX96: encodePriceSqrt("1", "1"), + sqrtRatioBX96: encodePriceSqrt("121", "100"), + liquidity: u256.MustFromDecimal("1000000000000000000"), + roundUp: false, + expectedAmount0Delta: "90909090909090909", + }, + { + name: "works for prices that overflow, roundUp = true", + sqrtRatioAX96: u256.MustFromDecimal("43556142965880123323311949751266331066368"), + sqrtRatioBX96: u256.MustFromDecimal("22300745198530623141535718272648361505980416"), + liquidity: u256.MustFromDecimal("1000000000000000000"), + roundUp: true, + expectedAmount0Delta: "1815437", + }, + { + name: "works for prices that overflow, roundUp = false", + sqrtRatioAX96: u256.MustFromDecimal("43556142965880123323311949751266331066368"), + sqrtRatioBX96: u256.MustFromDecimal("22300745198530623141535718272648361505980416"), + liquidity: u256.MustFromDecimal("1000000000000000000"), + roundUp: false, + expectedAmount0Delta: "1815436", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + actual := sqrtPriceMathGetAmount0DeltaHelper(tt.sqrtRatioAX96, tt.sqrtRatioBX96, tt.liquidity, tt.roundUp) + uassert.Equal(t, tt.expectedAmount0Delta, actual.ToString()) + }) + } +} + +func TestSqrtPriceMathGetAmount1DeltaHelper(t *testing.T) { + tests := []struct { + name string + sqrtRatioAX96, sqrtRatioBX96, liquidity *u256.Uint + roundUp bool + expectedAmount1Delta string + }{ + { + name: "returns 0 if liquidity is 0", + sqrtRatioAX96: encodePriceSqrt("1", "1"), + sqrtRatioBX96: encodePriceSqrt("2", "1"), + liquidity: u256.Zero(), + roundUp: true, + expectedAmount1Delta: "0", + }, + { + name: "returns 0 if prices are equal", + sqrtRatioAX96: encodePriceSqrt("1", "1"), + sqrtRatioBX96: encodePriceSqrt("1", "1"), + liquidity: u256.Zero(), + roundUp: true, + expectedAmount1Delta: "0", + }, + { + name: "returns 0.1 amount1 for price of 1 to 1.21, roundUp = true", + sqrtRatioAX96: encodePriceSqrt("1", "1"), + sqrtRatioBX96: encodePriceSqrt("121", "100"), + liquidity: u256.MustFromDecimal("1000000000000000000"), + roundUp: true, + expectedAmount1Delta: "100000000000000000", + }, + { + name: "returns 0.1 amount1 for price of 1 to 1.21, roundUp = false", + sqrtRatioAX96: encodePriceSqrt("1", "1"), + sqrtRatioBX96: encodePriceSqrt("121", "100"), + liquidity: u256.MustFromDecimal("1000000000000000000"), + roundUp: false, + expectedAmount1Delta: "99999999999999999", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + actual := sqrtPriceMathGetAmount1DeltaHelper(tt.sqrtRatioAX96, tt.sqrtRatioBX96, tt.liquidity, tt.roundUp) + uassert.Equal(t, tt.expectedAmount1Delta, actual.ToString()) + }) + } +} + +func TestSwapComputation_SqrtP_SqrtQ_Mul_Overflow(t *testing.T) { + sqrtP := u256.MustFromDecimal("1025574284609383690408304870162715216695788925244") + liquidity := u256.MustFromDecimal("50015962439936049619261659728067971248") + amountIn := u256.MustFromDecimal("406") + zeroForOne := true + + sqrtQ := sqrtPriceMathGetNextSqrtPriceFromInput(sqrtP, liquidity, amountIn, zeroForOne) + uassert.Equal(t, "1025574284609383582644711336373707553698163132913", sqrtQ.ToString()) + + amount0Delta := sqrtPriceMathGetAmount0DeltaHelper(sqrtQ, sqrtP, liquidity, true) + uassert.Equal(t, "406", amount0Delta.ToString()) +} + +// encodePriceSqrt calculates the sqrt((reserve1 << 192) / reserve0) +func encodePriceSqrt(reserve1, reserve0 string) *u256.Uint { + reserve1Uint := u256.MustFromDecimal(reserve1) + reserve0Uint := u256.MustFromDecimal(reserve0) + + if reserve0Uint.IsZero() { + panic("division by zero") + } + + // numerator = reserve1 * (2^192) + two192 := new(u256.Uint).Lsh(u256.NewUint(1), 192) + numerator := new(u256.Uint).Mul(reserve1Uint, two192) + + // ratioX192 = numerator / reserve0 + ratioX192 := new(u256.Uint).Div(numerator, reserve0Uint) + + // Return sqrt(ratioX192) + return sqrt(ratioX192) +} + +// sqrt computes the integer square root of a u256.Uint +func sqrt(x *u256.Uint) *u256.Uint { + if x.IsZero() { + return u256.NewUint(0) + } + + z := new(u256.Uint).Set(x) + y := new(u256.Uint).Rsh(z, 1) // Initial guess is x / 2 + + for y.Cmp(z) < 0 { + z.Set(y) + temp := new(u256.Uint).Div(x, z) + y.Add(z, temp).Rsh(y, 1) + } + return z +} diff --git a/_deploy/p/gnoswap/pool/swap_math.gno b/_deploy/p/gnoswap/pool/swap_math.gno new file mode 100644 index 000000000..81b9c2d28 --- /dev/null +++ b/_deploy/p/gnoswap/pool/swap_math.gno @@ -0,0 +1,143 @@ +package pool + +import ( + i256 "gno.land/p/gnoswap/int256" + u256 "gno.land/p/gnoswap/uint256" +) + +// SwapMathComputeSwapStepStr computes the next sqrt price, amount in, amount out, and fee amount +// Computes the result of swapping some amount in, or amount out, given the parameters of the swap +// The fee, plus the amount in, will never exceed the amount remaining if the swap's `amountSpecified` is positive +// +// input: +// - sqrtRatioCurrentX96: the current sqrt price of the pool +// - sqrtRatioTargetX96: The price that cannot be exceeded, from which the direction of the swap is inferred +// - liquidity: The usable liquidity of the pool +// - amountRemaining: How much input or output amount is remaining to be swapped in/out +// - feePips: The fee taken from the input amount, expressed in hundredths of a bip +// +// output: +// - sqrtRatioNextX96: The price after swapping the amount in/out, not to exceed the price target +// - amountIn: The amount to be swapped in, of either token0 or token1, based on the direction of the swap +// - amountOut: The amount to be received, of either token0 or token1, based on the direction of the swap +// - feeAmount: The amount of input that will be taken as a fee +func SwapMathComputeSwapStepStr( + sqrtRatioCurrentX96 *u256.Uint, + sqrtRatioTargetX96 *u256.Uint, + liquidity *u256.Uint, + amountRemaining *i256.Int, + feePips uint64, +) (string, string, string, string) { + if sqrtRatioCurrentX96 == nil || sqrtRatioTargetX96 == nil || liquidity == nil || amountRemaining == nil { + panic("SwapMathComputeSwapStepStr: invalid input") + } + + zeroForOne := sqrtRatioCurrentX96.Gte(sqrtRatioTargetX96) + + // POSTIVIE == EXACT_IN => Estimated AmountOut + // NEGATIVE == EXACT_OUT => Estimated AmountIn + exactIn := !(amountRemaining.IsNeg()) // amountRemaining >= 0 + + sqrtRatioNextX96 := u256.Zero() + amountIn := u256.Zero() + amountOut := u256.Zero() + feeAmount := u256.Zero() + + if exactIn { + amountRemainingLessFee := u256.MulDiv(amountRemaining.Abs(), u256.NewUint(1000000-feePips), u256.NewUint(1000000)) + if zeroForOne { + amountIn = sqrtPriceMathGetAmount0DeltaHelper( + sqrtRatioTargetX96.Clone(), + sqrtRatioCurrentX96.Clone(), + liquidity.Clone(), + true) + } else { + amountIn = sqrtPriceMathGetAmount1DeltaHelper( + sqrtRatioCurrentX96.Clone(), + sqrtRatioTargetX96.Clone(), + liquidity.Clone(), + true) + } + + if amountRemainingLessFee.Gte(amountIn) { + sqrtRatioNextX96 = sqrtRatioTargetX96.Clone() + } else { + sqrtRatioNextX96 = sqrtPriceMathGetNextSqrtPriceFromInput( + sqrtRatioCurrentX96.Clone(), + liquidity.Clone(), + amountRemainingLessFee.Clone(), + zeroForOne, + ) + } + } else { + if zeroForOne { + amountOut = sqrtPriceMathGetAmount1DeltaHelper( + sqrtRatioTargetX96.Clone(), + sqrtRatioCurrentX96.Clone(), + liquidity.Clone(), + false) + } else { + amountOut = sqrtPriceMathGetAmount0DeltaHelper( + sqrtRatioCurrentX96.Clone(), + sqrtRatioTargetX96.Clone(), + liquidity.Clone(), + false) + } + + if amountRemaining.Abs().Gte(amountOut) { + sqrtRatioNextX96 = sqrtRatioTargetX96.Clone() + } else { + sqrtRatioNextX96 = sqrtPriceMathGetNextSqrtPriceFromOutput( + sqrtRatioCurrentX96.Clone(), + liquidity.Clone(), + amountRemaining.Abs(), + zeroForOne, + ) + } + } + + isMax := sqrtRatioTargetX96.Eq(sqrtRatioNextX96) + + if zeroForOne { + if !(isMax && exactIn) { + amountIn = sqrtPriceMathGetAmount0DeltaHelper( + sqrtRatioNextX96.Clone(), + sqrtRatioCurrentX96.Clone(), + liquidity.Clone(), + true) + } + if !(isMax && !exactIn) { + amountOut = sqrtPriceMathGetAmount1DeltaHelper( + sqrtRatioNextX96.Clone(), + sqrtRatioCurrentX96.Clone(), + liquidity.Clone(), + false) + } + } else { + if !(isMax && exactIn) { + amountIn = sqrtPriceMathGetAmount1DeltaHelper( + sqrtRatioCurrentX96.Clone(), + sqrtRatioNextX96.Clone(), + liquidity.Clone(), + true) + } + if !(isMax && !exactIn) { + amountOut = sqrtPriceMathGetAmount0DeltaHelper( + sqrtRatioCurrentX96.Clone(), + sqrtRatioNextX96.Clone(), + liquidity.Clone(), + false) + } + } + + if !exactIn && amountOut.Gt(amountRemaining.Abs()) { + amountOut = amountRemaining.Abs() + } + if exactIn && !(sqrtRatioNextX96.Eq(sqrtRatioTargetX96)) { + feeAmount = new(u256.Uint).Sub(amountRemaining.Abs(), amountIn) + } else { + feeAmount = u256.MulDivRoundingUp(amountIn, u256.NewUint(feePips), new(u256.Uint).Sub(u256.NewUint(1000000), u256.NewUint(feePips))) + } + + return sqrtRatioNextX96.ToString(), amountIn.ToString(), amountOut.ToString(), feeAmount.ToString() +} diff --git a/_deploy/p/gnoswap/pool/swap_math_test.gno b/_deploy/p/gnoswap/pool/swap_math_test.gno new file mode 100644 index 000000000..505a8e716 --- /dev/null +++ b/_deploy/p/gnoswap/pool/swap_math_test.gno @@ -0,0 +1,273 @@ +package pool + +import ( + "testing" + + "gno.land/p/demo/uassert" + + i256 "gno.land/p/gnoswap/int256" + u256 "gno.land/p/gnoswap/uint256" +) + +func TestSwapMathComputeSwapStepStr(t *testing.T) { + tests := []struct { + name string + currentX96, targetX96 *u256.Uint + liquidity *u256.Uint + amountRemaining *i256.Int + feePips uint64 + sqrtNextX96 *u256.Uint + chkSqrtNextX96 func(sqrtRatioNextX96, priceTarget *u256.Uint) + amountIn, amountOut, feeAmount string + }{ + { + name: "exact amount in that gets capped at price target in one for zero", + currentX96: encodePriceSqrt(t, "1", "1"), + targetX96: encodePriceSqrt(t, "101", "100"), + liquidity: u256.MustFromDecimal("2000000000000000000"), + amountRemaining: i256.MustFromDecimal("1000000000000000000"), + feePips: 600, + sqrtNextX96: encodePriceSqrt(t, "101", "100"), + chkSqrtNextX96: func(sqrtRatioNextX96, priceTarget *u256.Uint) { + uassert.True(t, sqrtRatioNextX96.Eq(priceTarget)) + }, + amountIn: "9975124224178055", + amountOut: "9925619580021728", + feeAmount: "5988667735148", + }, + { + name: "exact amount out that gets capped at price target in one for zero", + currentX96: encodePriceSqrt(t, "1", "1"), + targetX96: encodePriceSqrt(t, "101", "100"), + liquidity: u256.MustFromDecimal("2000000000000000000"), + amountRemaining: i256.MustFromDecimal("-1000000000000000000"), + feePips: 600, + sqrtNextX96: encodePriceSqrt(t, "101", "100"), + chkSqrtNextX96: func(sqrtRatioNextX96, priceTarget *u256.Uint) { + uassert.True(t, sqrtRatioNextX96.Eq(priceTarget)) + }, + amountIn: "9975124224178055", + amountOut: "9925619580021728", + feeAmount: "5988667735148", + }, + { + name: "exact amount in that is fully spent in one for zero", + currentX96: encodePriceSqrt(t, "1", "1"), + targetX96: encodePriceSqrt(t, "1000", "100"), + liquidity: u256.MustFromDecimal("2000000000000000000"), + amountRemaining: i256.MustFromDecimal("1000000000000000000"), + sqrtNextX96: encodePriceSqrt(t, "1000", "100"), + feePips: 600, + chkSqrtNextX96: func(sqrtRatioNextX96, priceTarget *u256.Uint) { + uassert.True(t, sqrtRatioNextX96.Lte(priceTarget)) + }, + amountIn: "999400000000000000", + amountOut: "666399946655997866", + feeAmount: "600000000000000", + }, + { + name: "exact amount out that is fully received in one for zero", + currentX96: encodePriceSqrt(t, "1", "1"), + targetX96: encodePriceSqrt(t, "1000", "100"), + liquidity: u256.MustFromDecimal("2000000000000000000"), + amountRemaining: i256.MustFromDecimal("-1000000000000000000"), + feePips: 600, + sqrtNextX96: encodePriceSqrt(t, "1000", "100"), + chkSqrtNextX96: func(sqrtRatioNextX96, priceTarget *u256.Uint) { + uassert.True(t, sqrtRatioNextX96.Lt(priceTarget)) + }, + amountIn: "2000000000000000000", + amountOut: "1000000000000000000", + feeAmount: "1200720432259356", + }, + { + name: "amount out is capped at the desired amount out", + currentX96: u256.MustFromDecimal("417332158212080721273783715441582"), + targetX96: u256.MustFromDecimal("1452870262520218020823638996"), + liquidity: u256.MustFromDecimal("159344665391607089467575320103"), + amountRemaining: i256.MustFromDecimal("-1"), + feePips: 1, + sqrtNextX96: u256.MustFromDecimal("417332158212080721273783715441581"), + chkSqrtNextX96: func(sqrtRatioNextX96, priceTarget *u256.Uint) { + uassert.True(t, sqrtRatioNextX96.Eq(priceTarget)) + }, + amountIn: "1", + amountOut: "1", + feeAmount: "1", + }, + { + name: "target price of 1 uses partial input amount", + currentX96: u256.MustFromDecimal("2"), + targetX96: u256.MustFromDecimal("1"), + liquidity: u256.MustFromDecimal("1"), + amountRemaining: i256.MustFromDecimal("3915081100057732413702495386755767"), + feePips: 1, + sqrtNextX96: u256.MustFromDecimal("1"), + chkSqrtNextX96: func(sqrtRatioNextX96, priceTarget *u256.Uint) { + uassert.True(t, sqrtRatioNextX96.Eq(priceTarget)) + }, + amountIn: "39614081257132168796771975168", + amountOut: "0", + feeAmount: "39614120871253040049813", + }, + { + name: "entire input amount taken as fee", + currentX96: u256.MustFromDecimal("2413"), + targetX96: u256.MustFromDecimal("79887613182836312"), + liquidity: u256.MustFromDecimal("1985041575832132834610021537970"), + amountRemaining: i256.MustFromDecimal("10"), + feePips: 1872, + sqrtNextX96: u256.MustFromDecimal("2413"), + chkSqrtNextX96: func(sqrtRatioNextX96, priceTarget *u256.Uint) { + uassert.True(t, sqrtRatioNextX96.Eq(priceTarget)) + }, + amountIn: "0", + amountOut: "0", + feeAmount: "10", + }, + { + name: "handles intermediate insufficient liquidity in zero for one exact output case", + currentX96: u256.MustFromDecimal("20282409603651670423947251286016"), + targetX96: u256.MustFromDecimal("22310650564016837466341976414617"), + liquidity: u256.MustFromDecimal("1024"), + amountRemaining: i256.MustFromDecimal("-4"), + feePips: 3000, + sqrtNextX96: u256.MustFromDecimal("22310650564016837466341976414617"), + chkSqrtNextX96: func(sqrtRatioNextX96, priceTarget *u256.Uint) { + uassert.True(t, sqrtRatioNextX96.Eq(priceTarget)) + }, + amountIn: "26215", + amountOut: "0", + feeAmount: "79", + }, + { + name: "handles intermediate insufficient liquidity in one for zero exact output case", + currentX96: u256.MustFromDecimal("20282409603651670423947251286016"), + targetX96: u256.MustFromDecimal("18254168643286503381552526157414"), + liquidity: u256.MustFromDecimal("1024"), + amountRemaining: i256.MustFromDecimal("-263000"), + feePips: 3000, + sqrtNextX96: u256.MustFromDecimal("18254168643286503381552526157414"), + chkSqrtNextX96: func(sqrtRatioNextX96, priceTarget *u256.Uint) { + uassert.True(t, sqrtRatioNextX96.Eq(priceTarget)) + }, + amountIn: "1", + amountOut: "26214", + feeAmount: "1", + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + sqrtRatioNextX96, amountIn, amountOut, feeAmount := SwapMathComputeSwapStepStr(test.currentX96, test.targetX96, test.liquidity, test.amountRemaining, test.feePips) + test.chkSqrtNextX96(u256.MustFromDecimal(sqrtRatioNextX96), test.sqrtNextX96) + uassert.Equal(t, amountIn, test.amountIn) + uassert.Equal(t, amountOut, test.amountOut) + uassert.Equal(t, feeAmount, test.feeAmount) + }) + } +} + +func TestSwapMathComputeSwapStepStrFail(t *testing.T) { + tests := []struct { + name string + currentX96, targetX96 *u256.Uint + liquidity *u256.Uint + amountRemaining *i256.Int + feePips uint64 + sqrtNextX96 *u256.Uint + chkSqrtNextX96 func(sqrtRatioNextX96, priceTarget *u256.Uint) + amountIn, amountOut, feeAmount string + shouldPanic bool + expectedMessage string + }{ + { + name: "input parameter is nil", + currentX96: nil, + targetX96: nil, + liquidity: nil, + amountRemaining: nil, + feePips: 600, + sqrtNextX96: encodePriceSqrt(t, "101", "100"), + chkSqrtNextX96: func(sqrtRatioNextX96, priceTarget *u256.Uint) { + uassert.True(t, sqrtRatioNextX96.Eq(priceTarget)) + }, + amountIn: "9975124224178055", + amountOut: "9925619580021728", + feeAmount: "5988667735148", + shouldPanic: true, + expectedMessage: "SwapMathComputeSwapStepStr: invalid input", + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + defer func() { + if r := recover(); r != nil { + if test.shouldPanic { + if errMsg, ok := r.(string); ok { + uassert.Equal(t, test.expectedMessage, errMsg) + } else { + t.Errorf("expected a panic with message, got: %v", r) + } + } else { + t.Errorf("unexpected panic: %v", r) + } + } else { + if test.shouldPanic { + t.Errorf("expected a panic, but none occurred") + } + } + }() + + SwapMathComputeSwapStepStr( + test.currentX96, + test.targetX96, + test.liquidity, + test.amountRemaining, + test.feePips) + }) + } +} + +// encodePriceSqrt calculates the sqrt((reserve1 << 192) / reserve0) +func encodePriceSqrt(t *testing.T, reserve1, reserve0 string) *u256.Uint { + t.Helper() + + reserve1Uint := u256.MustFromDecimal(reserve1) + reserve0Uint := u256.MustFromDecimal(reserve0) + + if reserve0Uint.IsZero() { + panic("division by zero") + } + + // numerator = reserve1 * (2^192) + two192 := new(u256.Uint).Lsh(u256.NewUint(1), 192) + numerator := new(u256.Uint).Mul(reserve1Uint, two192) + + // ratioX192 = numerator / reserve0 + ratioX192 := new(u256.Uint).Div(numerator, reserve0Uint) + + // Return sqrt(ratioX192) + return sqrt(t, ratioX192) +} + +// sqrt computes the integer square root of a u256.Uint +func sqrt(t *testing.T, x *u256.Uint) *u256.Uint { + t.Helper() + + if x.IsZero() { + return u256.NewUint(0) + } + + z := new(u256.Uint).Set(x) + y := new(u256.Uint).Rsh(z, 1) // Initial guess is x / 2 + + temp := new(u256.Uint) + for y.Cmp(z) < 0 { + z.Set(y) + temp.Div(x, z) + y.Add(z, temp).Rsh(y, 1) + } + return z +} diff --git a/_deploy/p/gnoswap/uint256/LICENSE b/_deploy/p/gnoswap/uint256/LICENSE new file mode 100644 index 000000000..505e4324f --- /dev/null +++ b/_deploy/p/gnoswap/uint256/LICENSE @@ -0,0 +1,28 @@ +BSD 3-Clause License + +Copyright 2020 uint256 Authors + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/_deploy/p/gnoswap/uint256/README.md b/_deploy/p/gnoswap/uint256/README.md new file mode 100644 index 000000000..b580cd33d --- /dev/null +++ b/_deploy/p/gnoswap/uint256/README.md @@ -0,0 +1,5 @@ +# Fixed size 256-bit math library + +This is a library specialized at replacing the `big.Int` library for math based on 256-bit types. + +original repository: [uint256]() diff --git a/_deploy/p/gnoswap/uint256/__TEST_0_INIT_VARIABLE_AND_HELPER_test.gno b/_deploy/p/gnoswap/uint256/__TEST_0_INIT_VARIABLE_AND_HELPER_test.gno new file mode 100644 index 000000000..d867bb9c9 --- /dev/null +++ b/_deploy/p/gnoswap/uint256/__TEST_0_INIT_VARIABLE_AND_HELPER_test.gno @@ -0,0 +1,39 @@ +package uint256 + +import ( + "testing" +) + +func shouldEQ(t *testing.T, got, expected interface{}) { + if got != expected { + t.Errorf("got %v, expected %v", got, expected) + } +} + +func shouldNEQ(t *testing.T, got, expected interface{}) { + if got == expected { + t.Errorf("got %v, didn't expected %v", got, expected) + } +} + +func shouldPanic(t *testing.T, f func()) { + defer func() { + if r := recover(); r == nil { + t.Errorf("expected panic") + } + }() + f() +} + +func shouldPanicWithMsg(t *testing.T, f func(), msg string) { + defer func() { + if r := recover(); r == nil { + t.Errorf("The code did not panic") + } else { + if r != msg { + t.Errorf("excepted panic(%v), got(%v)", msg, r) + } + } + }() + f() +} diff --git a/_deploy/p/gnoswap/uint256/__TEST_u256_test.gno b/_deploy/p/gnoswap/uint256/__TEST_u256_test.gno new file mode 100644 index 000000000..c9646b812 --- /dev/null +++ b/_deploy/p/gnoswap/uint256/__TEST_u256_test.gno @@ -0,0 +1,321 @@ +package uint256 + +import ( + "testing" +) + +func TestMulDiv_1(t *testing.T) { + // reverts if denominator is 0 + Q128 := MustFromDecimal("340282366920938463463374607431768211456") // 2**128 + + x := NewUint(5) + y := Zero() + + shouldPanic( + t, + func() { + MulDiv(Q128, x, y) + }, + ) +} + +func TestMulDiv_2(t *testing.T) { + // reverts if denominator is 0 and numerator overflows + Q128 := MustFromDecimal("340282366920938463463374607431768211456") // 2**128 + x := NewUint(5) + y := Zero() + + shouldPanic( + t, + func() { + MulDiv(Q128, Q128, y) + }, + ) +} + +func TestMulDiv_3(t *testing.T) { + // reverts if output overflows uint256 + Q128 := MustFromDecimal("340282366920938463463374607431768211456") // 2**128 + y := One() + + shouldPanic( + t, + func() { + MulDiv(Q128, Q128, y) + }, + ) +} + +func TestMulDiv_4(t *testing.T) { + // reverts on overflow with all max inputs + MaxUint256 := MustFromHex("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff") + MaxUint256Sub1 := MustFromDecimal("115792089237316195423570985008687907853269984665640564039457584007913129639934") + if MaxUint256.ToString() != "115792089237316195423570985008687907853269984665640564039457584007913129639935" { + t.Errorf("MustFromHex is Failed") + } + shouldPanic( + t, + func() { + MulDiv(MaxUint256, MaxUint256, MaxUint256Sub1) + }, + ) +} + +func TestMulDiv_5(t *testing.T) { + // all max inputs + MaxUint256 := MustFromHex("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff") + + got := MulDiv(MaxUint256, MaxUint256, MaxUint256) + + rst := got.Eq(MaxUint256) + if !rst { + t.Errorf("MaxUint256 * MaxUint256 / MaxUint256 is not same to MaxUint256") + } +} + +func TestMulDiv_6(t *testing.T) { + denom := NewUint(100) + + // accurate without phantom overflow #1 + Q128 := MustFromDecimal("340282366920938463463374607431768211456") // 2**128 + Q128Const := MustFromDecimal("340282366920938463463374607431768211456") // 2**128 + expected := Q128.Div(Q128, NewUint(3)) + x := NewUint(50) + x1 := MulDiv(x, Q128Const, denom) + y := NewUint(150) + y1 := MulDiv(y, Q128Const, denom) + + got := MulDiv(Q128Const, x1, y1) + + rst := got.Eq(expected) + if !rst { + t.Errorf("Q128/3 is not smae to Q128 * (50*Q128/100) / (150*Q128/100)") + } +} + +func TestMulDiv_6_1(t *testing.T) { + denom := NewUint(100) + + // accurate without phantom overflow #1_1 + Q128 := MustFromDecimal("340282366920938463463374607431768211456") // 2**128 + expected := new(Uint).Div(Q128, NewUint(3)) + x := NewUint(50) + x1 := MulDiv(x, Q128, denom) + y := NewUint(150) + y1 := MulDiv(y, Q128, denom) + + got := MulDiv(Q128, x1, y1) + + rst := got.Eq(expected) + if !rst { + t.Errorf("Q128/3 is not smae to Q128 * (50*Q128/100) / (150*Q128/100)") + } +} + +func TestMulDiv_7(t *testing.T) { + // accurate with phantom overflow #2 + Q128 := MustFromDecimal("340282366920938463463374607431768211456") // 2**128 + Q128Const := MustFromDecimal("340282366920938463463374607431768211456") // 2**128 + x := NewUint(4375) + expected := MulDiv(x, Q128Const, NewUint(1000)) + + y1 := NewUint(35) + y1.Mul(y1, Q128Const) + + denom := NewUint(8) + denom.Mul(denom, Q128Const) + + got := MulDiv(Q128Const, y1, denom) + + rst := got.Eq(expected) + if !rst { + t.Errorf("4375*Q128/1000 is not smae to Q128*35*Q128/(8*Q128)") + } +} + +func TestMulDiv_8(t *testing.T) { + // accurate with phantom overflow and repeating decimal + Q128 := MustFromDecimal("340282366920938463463374607431768211456") // 2**128 + Q128Const := MustFromDecimal("340282366920938463463374607431768211456") // 2**128 + expected := MulDiv(One(), Q128Const, NewUint(3)) + + y1 := NewUint(1000) + y1.Mul(y1, Q128Const) + + denom := NewUint(3000) + denom.Mul(denom, Q128Const) + + got := MulDiv(Q128Const, y1, denom) + + rst := got.Eq(expected) + if !rst { + t.Errorf("1*Q128/3 is not smae to Q128*1000*Q128/(3000*Q128)") + } +} + +func TestMulDivRoundingUp_1(t *testing.T) { + // reverts if denominator is 0 + Q128 := MustFromDecimal("340282366920938463463374607431768211456") // 2**128 + + x := NewUint(5) + y := Zero() + + shouldPanic( + t, + func() { + MulDivRoundingUp(Q128, x, y) + }, + ) +} + +func TestMulDivRoundingUp_2(t *testing.T) { + // reverts if denominator is 0 and numerator overflows + + Q128 := MustFromDecimal("340282366920938463463374607431768211456") // 2**128 + y := Zero() + + shouldPanic( + t, + func() { + MulDivRoundingUp(Q128, Q128, y) + }, + ) +} + +func TestMulDivRoundingUp_3(t *testing.T) { + // reverts if output overflows uint256 + + Q128 := MustFromDecimal("340282366920938463463374607431768211456") // 2**128 + y := One() + + shouldPanic( + t, + func() { + MulDivRoundingUp(Q128, Q128, y) + }, + ) +} + +func TestMulDivRoundingUp_4(t *testing.T) { + // reverts on overflow with all max inputs + + MaxUint256 := MustFromHex("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff") + MaxUint256Sub1 := MustFromDecimal("115792089237316195423570985008687907853269984665640564039457584007913129639934") + + shouldPanic( + t, + func() { + MulDivRoundingUp(MaxUint256, MaxUint256, MaxUint256Sub1) + }, + ) +} + +func TestMulDivRoundingUp_5(t *testing.T) { + // reverts if mulDiv overflows 256 bits after rounding up + + x := MustFromDecimal("535006138814359") + y := MustFromDecimal("432862656469423142931042426214547535783388063929571229938474969") + + shouldPanic( + t, + func() { + MulDivRoundingUp(x, y, NewUint(2)) + }, + ) +} + +func TestMulDivRoundingUp_6(t *testing.T) { + // reverts if mulDiv overflows 256 bits after rounding up case 2 + + x := MustFromDecimal("115792089237316195423570985008687907853269984659341747863450311749907997002549") + y := MustFromDecimal("115792089237316195423570985008687907853269984659341747863450311749907997002550") + z := MustFromDecimal("115792089237316195423570985008687907853269984653042931687443039491902864365164") + + shouldPanic( + t, + func() { + MulDivRoundingUp(x, y, z) + }, + ) +} + +func TestMulDivRoundingUp_7(t *testing.T) { + // all max inputs + MaxUint256 := MustFromHex("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff") + + got := MulDivRoundingUp(MaxUint256, MaxUint256, MaxUint256) + + rst := got.Eq(MaxUint256) + + if !rst { + t.Errorf("MaxUin256*MaxUint256/MaxUint256 RoudingUp is not same to MaxUint256") + } +} + +func TestMulDivRoundingUp_8(t *testing.T) { + // accurate without phantom overflow + denom := NewUint(100) + + // accurate without phantom overflow #1 + Q128 := MustFromDecimal("340282366920938463463374607431768211456") // 2**128 + Q128Const := MustFromDecimal("340282366920938463463374607431768211456") // 2**128 + Q128.Div(Q128, NewUint(3)) + expected := Q128.Add(Q128, NewUint(1)) + + x1 := NewUint(50) + x1.Mul(x1, Q128Const) + + y1 := NewUint(150) + y1.Mul(y1, Q128Const) + + got := MulDivRoundingUp(Q128Const, x1, y1) + + rst := got.Eq(expected) + if !rst { + t.Errorf("Q128*50*Q128/100/(150*Q128/100) should be equal to Q128/3") + } +} + +func TestMulDivRoundingUp_9(t *testing.T) { + // accurate with phantom overflow #2 + Q128 := MustFromDecimal("340282366920938463463374607431768211456") // 2**128 + Q128Const := MustFromDecimal("340282366920938463463374607431768211456") // 2**128 + x := NewUint(4375) + expected := MulDiv(x, Q128Const, NewUint(1000)) + + y1 := NewUint(35) + y1.Mul(y1, Q128Const) + + denom := NewUint(8) + denom.Mul(denom, Q128Const) + + got := MulDiv(Q128Const, y1, denom) + + rst := got.Eq(expected) + if !rst { + t.Errorf("4375*Q128/1000 is not smae to Q128*35*Q128/(8*Q128)") + } +} + +func TestMulDivRoundingUp_10(t *testing.T) { + // accurate with phantom overflow and repeating decimal + + Q128 := MustFromDecimal("340282366920938463463374607431768211456") // 2**128 + Q128Const := MustFromDecimal("340282366920938463463374607431768211456") // 2**128 + + expected := MulDiv(One(), Q128Const, NewUint(3)) + expected.Add(expected, One()) + + y1 := NewUint(1000) + y1.Mul(y1, Q128Const) + + denom := NewUint(3000) + denom.Mul(denom, Q128Const) + + got := MulDivRoundingUp(Q128Const, y1, denom) + + rst := got.Eq(expected) + if !rst { + t.Errorf("Q128*100*Q128/300*Q128 should be eq to 1*Q128+1") + } +} diff --git a/_deploy/p/gnoswap/uint256/arithmetic.gno b/_deploy/p/gnoswap/uint256/arithmetic.gno new file mode 100644 index 000000000..a10436f8e --- /dev/null +++ b/_deploy/p/gnoswap/uint256/arithmetic.gno @@ -0,0 +1,476 @@ +// arithmetic provides arithmetic operations for Uint objects. +// This includes basic binary operations such as addition, subtraction, multiplication, division, and modulo operations +// as well as overflow checks, and negation. These functions are essential for numeric +// calculations using 256-bit unsigned integers. +package uint256 + +import ( + "math/bits" +) + +// Add sets z to the sum x+y +func (z *Uint) Add(x, y *Uint) *Uint { + var carry uint64 + z.arr[0], carry = bits.Add64(x.arr[0], y.arr[0], 0) + z.arr[1], carry = bits.Add64(x.arr[1], y.arr[1], carry) + z.arr[2], carry = bits.Add64(x.arr[2], y.arr[2], carry) + z.arr[3], _ = bits.Add64(x.arr[3], y.arr[3], carry) + return z +} + +// AddOverflow sets z to the sum x+y, and returns z and whether overflow occurred +func (z *Uint) AddOverflow(x, y *Uint) (*Uint, bool) { + var carry uint64 + z.arr[0], carry = bits.Add64(x.arr[0], y.arr[0], 0) + z.arr[1], carry = bits.Add64(x.arr[1], y.arr[1], carry) + z.arr[2], carry = bits.Add64(x.arr[2], y.arr[2], carry) + z.arr[3], carry = bits.Add64(x.arr[3], y.arr[3], carry) + return z, carry != 0 +} + +// Sub sets z to the difference x-y +func (z *Uint) Sub(x, y *Uint) *Uint { + var carry uint64 + z.arr[0], carry = bits.Sub64(x.arr[0], y.arr[0], 0) + z.arr[1], carry = bits.Sub64(x.arr[1], y.arr[1], carry) + z.arr[2], carry = bits.Sub64(x.arr[2], y.arr[2], carry) + z.arr[3], _ = bits.Sub64(x.arr[3], y.arr[3], carry) + return z +} + +// SubOverflow sets z to the difference x-y and returns z and true if the operation underflowed +func (z *Uint) SubOverflow(x, y *Uint) (*Uint, bool) { + var carry uint64 + z.arr[0], carry = bits.Sub64(x.arr[0], y.arr[0], 0) + z.arr[1], carry = bits.Sub64(x.arr[1], y.arr[1], carry) + z.arr[2], carry = bits.Sub64(x.arr[2], y.arr[2], carry) + z.arr[3], carry = bits.Sub64(x.arr[3], y.arr[3], carry) + return z, carry != 0 +} + +// Neg returns -x mod 2^256. +func (z *Uint) Neg(x *Uint) *Uint { + return z.Sub(new(Uint), x) +} + +// commented out for possible overflow +// Mul sets z to the product x*y +func (z *Uint) Mul(x, y *Uint) *Uint { + var ( + res Uint + carry uint64 + res1, res2, res3 uint64 + ) + + carry, res.arr[0] = bits.Mul64(x.arr[0], y.arr[0]) + carry, res1 = umulHop(carry, x.arr[1], y.arr[0]) + carry, res2 = umulHop(carry, x.arr[2], y.arr[0]) + res3 = x.arr[3]*y.arr[0] + carry + + carry, res.arr[1] = umulHop(res1, x.arr[0], y.arr[1]) + carry, res2 = umulStep(res2, x.arr[1], y.arr[1], carry) + res3 = res3 + x.arr[2]*y.arr[1] + carry + + carry, res.arr[2] = umulHop(res2, x.arr[0], y.arr[2]) + res3 = res3 + x.arr[1]*y.arr[2] + carry + + res.arr[3] = res3 + x.arr[0]*y.arr[3] + + return z.Set(&res) +} + +// MulOverflow sets z to the product x*y, and returns z and whether overflow occurred +func (z *Uint) MulOverflow(x, y *Uint) (*Uint, bool) { + p := umul(x, y) + copy(z.arr[:], p[:4]) + return z, (p[4] | p[5] | p[6] | p[7]) != 0 +} + +// commented out for possible overflow +// Div sets z to the quotient x/y for returns z. +// If y == 0, z is set to 0 +func (z *Uint) Div(x, y *Uint) *Uint { + if y.IsZero() || y.Gt(x) { + return z.Clear() + } + if x.Eq(y) { + return z.SetOne() + } + // Shortcut some cases + if x.IsUint64() { + return z.SetUint64(x.Uint64() / y.Uint64()) + } + + // At this point, we know + // x/y ; x > y > 0 + + var quot Uint + udivrem(quot.arr[:], x.arr[:], y) + return z.Set(") +} + +// MulMod calculates the modulo-m multiplication of x and y and +// returns z. +// If m == 0, z is set to 0 (OBS: differs from the big.Int) +func (z *Uint) MulMod(x, y, m *Uint) *Uint { + if x.IsZero() || y.IsZero() || m.IsZero() { + return z.Clear() + } + p := umul(x, y) + + if m.arr[3] != 0 { + mu := Reciprocal(m) + r := reduce4(p, m, mu) + return z.Set(&r) + } + + var ( + pl Uint + ph Uint + ) + + pl = Uint{arr: [4]uint64{p[0], p[1], p[2], p[3]}} + ph = Uint{arr: [4]uint64{p[4], p[5], p[6], p[7]}} + + // If the multiplication is within 256 bits use Mod(). + if ph.IsZero() { + return z.Mod(&pl, m) + } + + var quot [8]uint64 + rem := udivrem(quot[:], p[:], m) + return z.Set(&rem) +} + +// Mod sets z to the modulus x%y for y != 0 and returns z. +// If y == 0, z is set to 0 (OBS: differs from the big.Uint) +func (z *Uint) Mod(x, y *Uint) *Uint { + if x.IsZero() || y.IsZero() { + return z.Clear() + } + switch x.Cmp(y) { + case -1: + // x < y + copy(z.arr[:], x.arr[:]) + return z + case 0: + // x == y + return z.Clear() // They are equal + } + + // At this point: + // x != 0 + // y != 0 + // x > y + + // Shortcut trivial case + if x.IsUint64() { + return z.SetUint64(x.Uint64() % y.Uint64()) + } + + var quot Uint + *z = udivrem(quot.arr[:], x.arr[:], y) + return z +} + +// DivMod sets z to the quotient x div y and m to the modulus x mod y and returns the pair (z, m) for y != 0. +// If y == 0, both z and m are set to 0 (OBS: differs from the big.Int) +func (z *Uint) DivMod(x, y, m *Uint) (*Uint, *Uint) { + if y.IsZero() { + return z.Clear(), m.Clear() + } + var quot Uint + *m = udivrem(quot.arr[:], x.arr[:], y) + *z = quot + return z, m +} + +// Exp sets z = base**exponent mod 2**256, and returns z. +func (z *Uint) Exp(base, exponent *Uint) *Uint { + res := Uint{arr: [4]uint64{1, 0, 0, 0}} + multiplier := *base + expBitLen := exponent.BitLen() + + curBit := 0 + word := exponent.arr[0] + for ; curBit < expBitLen && curBit < 64; curBit++ { + if word&1 == 1 { + res.Mul(&res, &multiplier) + } + multiplier.squared() + word >>= 1 + } + + word = exponent.arr[1] + for ; curBit < expBitLen && curBit < 128; curBit++ { + if word&1 == 1 { + res.Mul(&res, &multiplier) + } + multiplier.squared() + word >>= 1 + } + + word = exponent.arr[2] + for ; curBit < expBitLen && curBit < 192; curBit++ { + if word&1 == 1 { + res.Mul(&res, &multiplier) + } + multiplier.squared() + word >>= 1 + } + + word = exponent.arr[3] + for ; curBit < expBitLen && curBit < 256; curBit++ { + if word&1 == 1 { + res.Mul(&res, &multiplier) + } + multiplier.squared() + word >>= 1 + } + return z.Set(&res) +} + +func (z *Uint) squared() { + var ( + res Uint + carry0, carry1, carry2 uint64 + res1, res2 uint64 + ) + + carry0, res.arr[0] = bits.Mul64(z.arr[0], z.arr[0]) + carry0, res1 = umulHop(carry0, z.arr[0], z.arr[1]) + carry0, res2 = umulHop(carry0, z.arr[0], z.arr[2]) + + carry1, res.arr[1] = umulHop(res1, z.arr[0], z.arr[1]) + carry1, res2 = umulStep(res2, z.arr[1], z.arr[1], carry1) + + carry2, res.arr[2] = umulHop(res2, z.arr[0], z.arr[2]) + + res.arr[3] = 2*(z.arr[0]*z.arr[3]+z.arr[1]*z.arr[2]) + carry0 + carry1 + carry2 + + z.Set(&res) +} + +// udivrem divides u by d and produces both quotient and remainder. +// The quotient is stored in provided quot - len(u)-len(d)+1 words. +// It loosely follows the Knuth's division algorithm (sometimes referenced as "schoolbook" division) using 64-bit words. +// See Knuth, Volume 2, section 4.3.1, Algorithm D. +func udivrem(quot, u []uint64, d *Uint) (rem Uint) { + var dLen int + for i := len(d.arr) - 1; i >= 0; i-- { + if d.arr[i] != 0 { + dLen = i + 1 + break + } + } + + shift := uint(bits.LeadingZeros64(d.arr[dLen-1])) + + var dnStorage Uint + dn := dnStorage.arr[:dLen] + for i := dLen - 1; i > 0; i-- { + dn[i] = (d.arr[i] << shift) | (d.arr[i-1] >> (64 - shift)) + } + dn[0] = d.arr[0] << shift + + var uLen int + for i := len(u) - 1; i >= 0; i-- { + if u[i] != 0 { + uLen = i + 1 + break + } + } + + if uLen < dLen { + copy(rem.arr[:], u) + return rem + } + + var unStorage [9]uint64 + un := unStorage[:uLen+1] + un[uLen] = u[uLen-1] >> (64 - shift) + for i := uLen - 1; i > 0; i-- { + un[i] = (u[i] << shift) | (u[i-1] >> (64 - shift)) + } + un[0] = u[0] << shift + + // TODO: Skip the highest word of numerator if not significant. + + if dLen == 1 { + r := udivremBy1(quot, un, dn[0]) + rem.SetUint64(r >> shift) + return rem + } + + udivremKnuth(quot, un, dn) + + for i := 0; i < dLen-1; i++ { + rem.arr[i] = (un[i] >> shift) | (un[i+1] << (64 - shift)) + } + rem.arr[dLen-1] = un[dLen-1] >> shift + + return rem +} + +// umul computes full 256 x 256 -> 512 multiplication. +func umul(x, y *Uint) [8]uint64 { + var ( + res [8]uint64 + carry, carry4, carry5, carry6 uint64 + res1, res2, res3, res4, res5 uint64 + ) + + carry, res[0] = bits.Mul64(x.arr[0], y.arr[0]) + carry, res1 = umulHop(carry, x.arr[1], y.arr[0]) + carry, res2 = umulHop(carry, x.arr[2], y.arr[0]) + carry4, res3 = umulHop(carry, x.arr[3], y.arr[0]) + + carry, res[1] = umulHop(res1, x.arr[0], y.arr[1]) + carry, res2 = umulStep(res2, x.arr[1], y.arr[1], carry) + carry, res3 = umulStep(res3, x.arr[2], y.arr[1], carry) + carry5, res4 = umulStep(carry4, x.arr[3], y.arr[1], carry) + + carry, res[2] = umulHop(res2, x.arr[0], y.arr[2]) + carry, res3 = umulStep(res3, x.arr[1], y.arr[2], carry) + carry, res4 = umulStep(res4, x.arr[2], y.arr[2], carry) + carry6, res5 = umulStep(carry5, x.arr[3], y.arr[2], carry) + + carry, res[3] = umulHop(res3, x.arr[0], y.arr[3]) + carry, res[4] = umulStep(res4, x.arr[1], y.arr[3], carry) + carry, res[5] = umulStep(res5, x.arr[2], y.arr[3], carry) + res[7], res[6] = umulStep(carry6, x.arr[3], y.arr[3], carry) + + return res +} + +// umulStep computes (hi * 2^64 + lo) = z + (x * y) + carry. +func umulStep(z, x, y, carry uint64) (hi, lo uint64) { + hi, lo = bits.Mul64(x, y) + lo, carry = bits.Add64(lo, carry, 0) + hi, _ = bits.Add64(hi, 0, carry) + lo, carry = bits.Add64(lo, z, 0) + hi, _ = bits.Add64(hi, 0, carry) + return hi, lo +} + +// umulHop computes (hi * 2^64 + lo) = z + (x * y) +func umulHop(z, x, y uint64) (hi, lo uint64) { + hi, lo = bits.Mul64(x, y) + lo, carry := bits.Add64(lo, z, 0) + hi, _ = bits.Add64(hi, 0, carry) + return hi, lo +} + +// udivremBy1 divides u by single normalized word d and produces both quotient and remainder. +// The quotient is stored in provided quot. +func udivremBy1(quot, u []uint64, d uint64) (rem uint64) { + reciprocal := reciprocal2by1(d) + rem = u[len(u)-1] // Set the top word as remainder. + for j := len(u) - 2; j >= 0; j-- { + quot[j], rem = udivrem2by1(rem, u[j], d, reciprocal) + } + return rem +} + +// udivremKnuth implements the division of u by normalized multiple word d from the Knuth's division algorithm. +// The quotient is stored in provided quot - len(u)-len(d) words. +// Updates u to contain the remainder - len(d) words. +func udivremKnuth(quot, u, d []uint64) { + dh := d[len(d)-1] + dl := d[len(d)-2] + reciprocal := reciprocal2by1(dh) + + for j := len(u) - len(d) - 1; j >= 0; j-- { + u2 := u[j+len(d)] + u1 := u[j+len(d)-1] + u0 := u[j+len(d)-2] + + var qhat, rhat uint64 + if u2 >= dh { // Division overflows. + qhat = ^uint64(0) + // TODO: Add "qhat one to big" adjustment (not needed for correctness, but helps avoiding "add back" case). + } else { + qhat, rhat = udivrem2by1(u2, u1, dh, reciprocal) + ph, pl := bits.Mul64(qhat, dl) + if ph > rhat || (ph == rhat && pl > u0) { + qhat-- + // TODO: Add "qhat one to big" adjustment (not needed for correctness, but helps avoiding "add back" case). + } + } + + // Multiply and subtract. + borrow := subMulTo(u[j:], d, qhat) + u[j+len(d)] = u2 - borrow + if u2 < borrow { // Too much subtracted, add back. + qhat-- + u[j+len(d)] += addTo(u[j:], d) + } + + quot[j] = qhat // Store quotient digit. + } +} + +// isBitSet returns true if bit n-th is set, where n = 0 is LSB. +// The n must be <= 255. +func (z *Uint) isBitSet(n uint) bool { + return (z.arr[n/64] & (1 << (n % 64))) != 0 +} + +func (z *Uint) IsOverflow() bool { + return z.isBitSet(255) +} + +// addTo computes x += y. +// Requires len(x) >= len(y). +func addTo(x, y []uint64) uint64 { + var carry uint64 + for i := 0; i < len(y); i++ { + x[i], carry = bits.Add64(x[i], y[i], carry) + } + return carry +} + +// subMulTo computes x -= y * multiplier. +// Requires len(x) >= len(y). +func subMulTo(x, y []uint64, multiplier uint64) uint64 { + var borrow uint64 + for i := 0; i < len(y); i++ { + s, carry1 := bits.Sub64(x[i], borrow, 0) + ph, pl := bits.Mul64(y[i], multiplier) + t, carry2 := bits.Sub64(s, pl, 0) + x[i] = t + borrow = ph + carry1 + carry2 + } + return borrow +} + +// reciprocal2by1 computes <^d, ^0> / d. +func reciprocal2by1(d uint64) uint64 { + reciprocal, _ := bits.Div64(^d, ^uint64(0), d) + return reciprocal +} + +// udivrem2by1 divides / d and produces both quotient and remainder. +// It uses the provided d's reciprocal. +// Implementation ported from https://github.com/chfast/intx and is based on +// "Improved division by invariant integers", Algorithm 4. +func udivrem2by1(uh, ul, d, reciprocal uint64) (quot, rem uint64) { + qh, ql := bits.Mul64(reciprocal, uh) + ql, carry := bits.Add64(ql, ul, 0) + qh, _ = bits.Add64(qh, uh, carry) + qh++ + + r := ul - qh*d + + if r > ql { + qh-- + r += d + } + + if r >= d { + qh++ + r -= d + } + + return qh, r +} diff --git a/_deploy/p/gnoswap/uint256/arithmetic_test.gno b/_deploy/p/gnoswap/uint256/arithmetic_test.gno new file mode 100644 index 000000000..09d83eb18 --- /dev/null +++ b/_deploy/p/gnoswap/uint256/arithmetic_test.gno @@ -0,0 +1,369 @@ +package uint256 + +import "testing" + +type binOp2Test struct { + x, y, want string +} + +func TestIsOverflow(t *testing.T) { + tests := []struct { + name string + input *Uint + expected bool + }{ + { + name: "Number greater than max value", + input: &Uint{arr: [4]uint64{ + ^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0), + }}, + expected: true, + }, + { + name: "Max value", + input: &Uint{arr: [4]uint64{ + ^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0) >> 1, + }}, + expected: false, + }, + { + name: "0", + input: &Uint{arr: [4]uint64{0, 0, 0, 0}}, + expected: false, + }, + { + name: "Only 255th bit set", + input: &Uint{arr: [4]uint64{ + 0, 0, 0, uint64(1) << 63, + }}, + expected: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.input.IsOverflow(); got != tt.expected { + t.Errorf("IsOverflow() = %v, expected %v", got, tt.expected) + } + }) + } +} + +func TestAdd(t *testing.T) { + tests := []binOp2Test{ + {"0", "1", "1"}, + {"1", "0", "1"}, + {"1", "1", "2"}, + {"1", "3", "4"}, + {"10", "10", "20"}, + {"18446744073709551615", "18446744073709551615", "36893488147419103230"}, // uint64 overflow + } + + for _, tc := range tests { + x, err := FromDecimal(tc.x) + if err != nil { + t.Error(err) + continue + } + + y, err := FromDecimal(tc.y) + if err != nil { + t.Error(err) + continue + } + + want, err := FromDecimal(tc.want) + if err != nil { + t.Error(err) + continue + } + + got := &Uint{} + got.Add(x, y) + + if got.Neq(want) { + t.Errorf("Add(%s, %s) = %v, want %v", tc.x, tc.y, got.ToString(), want.ToString()) + } + } +} + +func TestSub(t *testing.T) { + tests := []binOp2Test{ + {"1", "0", "1"}, + {"1", "1", "0"}, + {"10", "10", "0"}, + {"31337", "1337", "30000"}, + {"2", "3", "115792089237316195423570985008687907853269984665640564039457584007913129639935"}, // underflow + } + + for _, tc := range tests { + x, err := FromDecimal(tc.x) + if err != nil { + t.Error(err) + continue + } + + y, err := FromDecimal(tc.y) + if err != nil { + t.Error(err) + continue + } + + want, err := FromDecimal(tc.want) + if err != nil { + t.Error(err) + continue + } + + got := &Uint{} + got.Sub(x, y) + + if got.Neq(want) { + t.Errorf("Sub(%s, %s) = %v, want %v", tc.x, tc.y, got.ToString(), want.ToString()) + } + } +} + +func TestMul(t *testing.T) { + tests := []binOp2Test{ + {"1", "0", "0"}, + {"1", "1", "1"}, + {"10", "10", "100"}, + {"18446744073709551615", "2", "36893488147419103230"}, // uint64 overflow + } + + for _, tc := range tests { + x, err := FromDecimal(tc.x) + if err != nil { + t.Error(err) + continue + } + + y, err := FromDecimal(tc.y) + if err != nil { + t.Error(err) + continue + } + + want, err := FromDecimal(tc.want) + if err != nil { + t.Error(err) + continue + } + + got := &Uint{} + got.Mul(x, y) + + if got.Neq(want) { + t.Errorf("Mul(%s, %s) = %v, want %v", tc.x, tc.y, got.ToString(), want.ToString()) + } + } +} + +func TestDiv(t *testing.T) { + tests := []binOp2Test{ + {"31337", "3", "10445"}, + {"31337", "0", "0"}, + {"0", "31337", "0"}, + {"1", "1", "1"}, + } + + for _, tc := range tests { + x, err := FromDecimal(tc.x) + if err != nil { + t.Error(err) + continue + } + + y, err := FromDecimal(tc.y) + if err != nil { + t.Error(err) + continue + } + + want, err := FromDecimal(tc.want) + if err != nil { + t.Error(err) + continue + } + + got := &Uint{} + got.Div(x, y) + + if got.Neq(want) { + t.Errorf("Div(%s, %s) = %v, want %v", tc.x, tc.y, got.ToString(), want.ToString()) + } + } +} + +func TestMod(t *testing.T) { + tests := []binOp2Test{ + {"31337", "3", "2"}, + {"31337", "0", "0"}, + {"0", "31337", "0"}, + {"2", "31337", "2"}, + {"1", "1", "0"}, + } + + for _, tc := range tests { + x, err := FromDecimal(tc.x) + if err != nil { + t.Error(err) + continue + } + + y, err := FromDecimal(tc.y) + if err != nil { + t.Error(err) + continue + } + + want, err := FromDecimal(tc.want) + if err != nil { + t.Error(err) + continue + } + + got := &Uint{} + got.Mod(x, y) + + if got.Neq(want) { + t.Errorf("Mod(%s, %s) = %v, want %v", tc.x, tc.y, got.ToString(), want.ToString()) + } + } +} + +func TestDivMod(t *testing.T) { + tests := []struct { + x string + y string + wantDiv string + wantMod string + }{ + {"1", "1", "1", "0"}, + {"10", "10", "1", "0"}, + {"100", "10", "10", "0"}, + {"31337", "3", "10445", "2"}, + {"31337", "0", "0", "0"}, + {"0", "31337", "0", "0"}, + {"2", "31337", "0", "2"}, + } + + for _, tc := range tests { + x, err := FromDecimal(tc.x) + if err != nil { + t.Error(err) + continue + } + + y, err := FromDecimal(tc.y) + if err != nil { + t.Error(err) + continue + } + + wantDiv, err := FromDecimal(tc.wantDiv) + if err != nil { + t.Error(err) + continue + } + + wantMod, err := FromDecimal(tc.wantMod) + if err != nil { + t.Error(err) + continue + } + + gotDiv := new(Uint) + gotMod := new(Uint) + gotDiv.DivMod(x, y, gotMod) + + for i := range gotDiv.arr { + if gotDiv.arr[i] != wantDiv.arr[i] { + t.Errorf("DivMod(%s, %s) got Div %v, want Div %v", tc.x, tc.y, gotDiv, wantDiv) + break + } + } + for i := range gotMod.arr { + if gotMod.arr[i] != wantMod.arr[i] { + t.Errorf("DivMod(%s, %s) got Mod %v, want Mod %v", tc.x, tc.y, gotMod, wantMod) + break + } + } + } +} + +func TestNeg(t *testing.T) { + tests := []struct { + x string + want string + }{ + {"31337", "115792089237316195423570985008687907853269984665640564039457584007913129608599"}, + {"115792089237316195423570985008687907853269984665640564039457584007913129608599", "31337"}, + {"0", "0"}, + {"2", "115792089237316195423570985008687907853269984665640564039457584007913129639934"}, + {"1", "115792089237316195423570985008687907853269984665640564039457584007913129639935"}, + } + + for _, tc := range tests { + x, err := FromDecimal(tc.x) + if err != nil { + t.Error(err) + continue + } + + want, err := FromDecimal(tc.want) + if err != nil { + t.Error(err) + continue + } + + got := &Uint{} + got.Neg(x) + + if got.Neq(want) { + t.Errorf("Neg(%s) = %v, want %v", tc.x, got.ToString(), want.ToString()) + } + } +} + +func TestExp(t *testing.T) { + tests := []binOp2Test{ + {"31337", "3", "30773171189753"}, + {"31337", "0", "1"}, + {"0", "31337", "0"}, + {"1", "1", "1"}, + {"2", "3", "8"}, + {"2", "64", "18446744073709551616"}, + {"2", "128", "340282366920938463463374607431768211456"}, + {"2", "255", "57896044618658097711785492504343953926634992332820282019728792003956564819968"}, + {"2", "256", "0"}, // overflow + } + + for _, tc := range tests { + x, err := FromDecimal(tc.x) + if err != nil { + t.Error(err) + continue + } + + y, err := FromDecimal(tc.y) + if err != nil { + t.Error(err) + continue + } + + want, err := FromDecimal(tc.want) + if err != nil { + t.Error(err) + continue + } + + got := &Uint{} + got.Exp(x, y) + + if got.Neq(want) { + t.Errorf("Exp(%s, %s) = %v, want %v", tc.x, tc.y, got.ToString(), want.ToString()) + } + } +} diff --git a/_deploy/p/gnoswap/uint256/bits_table.gno b/_deploy/p/gnoswap/uint256/bits_table.gno new file mode 100644 index 000000000..53dbea948 --- /dev/null +++ b/_deploy/p/gnoswap/uint256/bits_table.gno @@ -0,0 +1,79 @@ +// Copyright 2017 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Code generated by go run make_tables.go. DO NOT EDIT. + +package uint256 + +const ntz8tab = "" + + "\x08\x00\x01\x00\x02\x00\x01\x00\x03\x00\x01\x00\x02\x00\x01\x00" + + "\x04\x00\x01\x00\x02\x00\x01\x00\x03\x00\x01\x00\x02\x00\x01\x00" + + "\x05\x00\x01\x00\x02\x00\x01\x00\x03\x00\x01\x00\x02\x00\x01\x00" + + "\x04\x00\x01\x00\x02\x00\x01\x00\x03\x00\x01\x00\x02\x00\x01\x00" + + "\x06\x00\x01\x00\x02\x00\x01\x00\x03\x00\x01\x00\x02\x00\x01\x00" + + "\x04\x00\x01\x00\x02\x00\x01\x00\x03\x00\x01\x00\x02\x00\x01\x00" + + "\x05\x00\x01\x00\x02\x00\x01\x00\x03\x00\x01\x00\x02\x00\x01\x00" + + "\x04\x00\x01\x00\x02\x00\x01\x00\x03\x00\x01\x00\x02\x00\x01\x00" + + "\x07\x00\x01\x00\x02\x00\x01\x00\x03\x00\x01\x00\x02\x00\x01\x00" + + "\x04\x00\x01\x00\x02\x00\x01\x00\x03\x00\x01\x00\x02\x00\x01\x00" + + "\x05\x00\x01\x00\x02\x00\x01\x00\x03\x00\x01\x00\x02\x00\x01\x00" + + "\x04\x00\x01\x00\x02\x00\x01\x00\x03\x00\x01\x00\x02\x00\x01\x00" + + "\x06\x00\x01\x00\x02\x00\x01\x00\x03\x00\x01\x00\x02\x00\x01\x00" + + "\x04\x00\x01\x00\x02\x00\x01\x00\x03\x00\x01\x00\x02\x00\x01\x00" + + "\x05\x00\x01\x00\x02\x00\x01\x00\x03\x00\x01\x00\x02\x00\x01\x00" + + "\x04\x00\x01\x00\x02\x00\x01\x00\x03\x00\x01\x00\x02\x00\x01\x00" + +const pop8tab = "" + + "\x00\x01\x01\x02\x01\x02\x02\x03\x01\x02\x02\x03\x02\x03\x03\x04" + + "\x01\x02\x02\x03\x02\x03\x03\x04\x02\x03\x03\x04\x03\x04\x04\x05" + + "\x01\x02\x02\x03\x02\x03\x03\x04\x02\x03\x03\x04\x03\x04\x04\x05" + + "\x02\x03\x03\x04\x03\x04\x04\x05\x03\x04\x04\x05\x04\x05\x05\x06" + + "\x01\x02\x02\x03\x02\x03\x03\x04\x02\x03\x03\x04\x03\x04\x04\x05" + + "\x02\x03\x03\x04\x03\x04\x04\x05\x03\x04\x04\x05\x04\x05\x05\x06" + + "\x02\x03\x03\x04\x03\x04\x04\x05\x03\x04\x04\x05\x04\x05\x05\x06" + + "\x03\x04\x04\x05\x04\x05\x05\x06\x04\x05\x05\x06\x05\x06\x06\x07" + + "\x01\x02\x02\x03\x02\x03\x03\x04\x02\x03\x03\x04\x03\x04\x04\x05" + + "\x02\x03\x03\x04\x03\x04\x04\x05\x03\x04\x04\x05\x04\x05\x05\x06" + + "\x02\x03\x03\x04\x03\x04\x04\x05\x03\x04\x04\x05\x04\x05\x05\x06" + + "\x03\x04\x04\x05\x04\x05\x05\x06\x04\x05\x05\x06\x05\x06\x06\x07" + + "\x02\x03\x03\x04\x03\x04\x04\x05\x03\x04\x04\x05\x04\x05\x05\x06" + + "\x03\x04\x04\x05\x04\x05\x05\x06\x04\x05\x05\x06\x05\x06\x06\x07" + + "\x03\x04\x04\x05\x04\x05\x05\x06\x04\x05\x05\x06\x05\x06\x06\x07" + + "\x04\x05\x05\x06\x05\x06\x06\x07\x05\x06\x06\x07\x06\x07\x07\x08" + +const rev8tab = "" + + "\x00\x80\x40\xc0\x20\xa0\x60\xe0\x10\x90\x50\xd0\x30\xb0\x70\xf0" + + "\x08\x88\x48\xc8\x28\xa8\x68\xe8\x18\x98\x58\xd8\x38\xb8\x78\xf8" + + "\x04\x84\x44\xc4\x24\xa4\x64\xe4\x14\x94\x54\xd4\x34\xb4\x74\xf4" + + "\x0c\x8c\x4c\xcc\x2c\xac\x6c\xec\x1c\x9c\x5c\xdc\x3c\xbc\x7c\xfc" + + "\x02\x82\x42\xc2\x22\xa2\x62\xe2\x12\x92\x52\xd2\x32\xb2\x72\xf2" + + "\x0a\x8a\x4a\xca\x2a\xaa\x6a\xea\x1a\x9a\x5a\xda\x3a\xba\x7a\xfa" + + "\x06\x86\x46\xc6\x26\xa6\x66\xe6\x16\x96\x56\xd6\x36\xb6\x76\xf6" + + "\x0e\x8e\x4e\xce\x2e\xae\x6e\xee\x1e\x9e\x5e\xde\x3e\xbe\x7e\xfe" + + "\x01\x81\x41\xc1\x21\xa1\x61\xe1\x11\x91\x51\xd1\x31\xb1\x71\xf1" + + "\x09\x89\x49\xc9\x29\xa9\x69\xe9\x19\x99\x59\xd9\x39\xb9\x79\xf9" + + "\x05\x85\x45\xc5\x25\xa5\x65\xe5\x15\x95\x55\xd5\x35\xb5\x75\xf5" + + "\x0d\x8d\x4d\xcd\x2d\xad\x6d\xed\x1d\x9d\x5d\xdd\x3d\xbd\x7d\xfd" + + "\x03\x83\x43\xc3\x23\xa3\x63\xe3\x13\x93\x53\xd3\x33\xb3\x73\xf3" + + "\x0b\x8b\x4b\xcb\x2b\xab\x6b\xeb\x1b\x9b\x5b\xdb\x3b\xbb\x7b\xfb" + + "\x07\x87\x47\xc7\x27\xa7\x67\xe7\x17\x97\x57\xd7\x37\xb7\x77\xf7" + + "\x0f\x8f\x4f\xcf\x2f\xaf\x6f\xef\x1f\x9f\x5f\xdf\x3f\xbf\x7f\xff" + +const len8tab = "" + + "\x00\x01\x02\x02\x03\x03\x03\x03\x04\x04\x04\x04\x04\x04\x04\x04" + + "\x05\x05\x05\x05\x05\x05\x05\x05\x05\x05\x05\x05\x05\x05\x05\x05" + + "\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06" + + "\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06" + + "\x07\x07\x07\x07\x07\x07\x07\x07\x07\x07\x07\x07\x07\x07\x07\x07" + + "\x07\x07\x07\x07\x07\x07\x07\x07\x07\x07\x07\x07\x07\x07\x07\x07" + + "\x07\x07\x07\x07\x07\x07\x07\x07\x07\x07\x07\x07\x07\x07\x07\x07" + + "\x07\x07\x07\x07\x07\x07\x07\x07\x07\x07\x07\x07\x07\x07\x07\x07" + + "\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08" + + "\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08" + + "\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08" + + "\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08" + + "\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08" + + "\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08" + + "\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08" + + "\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08" diff --git a/_deploy/p/gnoswap/uint256/bitwise.gno b/_deploy/p/gnoswap/uint256/bitwise.gno new file mode 100644 index 000000000..fc6e098dc --- /dev/null +++ b/_deploy/p/gnoswap/uint256/bitwise.gno @@ -0,0 +1,264 @@ +// bitwise contains bitwise operations for Uint instances. +// This file includes functions to perform bitwise AND, OR, XOR, and NOT operations, as well as bit shifting. +// These operations are crucial for manipulating individual bits within a 256-bit unsigned integer. +package uint256 + +// Or sets z = x | y and returns z. +func (z *Uint) Or(x, y *Uint) *Uint { + z.arr[0] = x.arr[0] | y.arr[0] + z.arr[1] = x.arr[1] | y.arr[1] + z.arr[2] = x.arr[2] | y.arr[2] + z.arr[3] = x.arr[3] | y.arr[3] + return z +} + +// And sets z = x & y and returns z. +func (z *Uint) And(x, y *Uint) *Uint { + z.arr[0] = x.arr[0] & y.arr[0] + z.arr[1] = x.arr[1] & y.arr[1] + z.arr[2] = x.arr[2] & y.arr[2] + z.arr[3] = x.arr[3] & y.arr[3] + return z +} + +// Not sets z = ^x and returns z. +func (z *Uint) Not(x *Uint) *Uint { + z.arr[3], z.arr[2], z.arr[1], z.arr[0] = ^x.arr[3], ^x.arr[2], ^x.arr[1], ^x.arr[0] + return z +} + +// AndNot sets z = x &^ y and returns z. +func (z *Uint) AndNot(x, y *Uint) *Uint { + z.arr[0] = x.arr[0] &^ y.arr[0] + z.arr[1] = x.arr[1] &^ y.arr[1] + z.arr[2] = x.arr[2] &^ y.arr[2] + z.arr[3] = x.arr[3] &^ y.arr[3] + return z +} + +// Xor sets z = x ^ y and returns z. +func (z *Uint) Xor(x, y *Uint) *Uint { + z.arr[0] = x.arr[0] ^ y.arr[0] + z.arr[1] = x.arr[1] ^ y.arr[1] + z.arr[2] = x.arr[2] ^ y.arr[2] + z.arr[3] = x.arr[3] ^ y.arr[3] + return z +} + +// Lsh sets z = x << n and returns z. +func (z *Uint) Lsh(x *Uint, n uint) *Uint { + // n % 64 == 0 + if n&0x3f == 0 { + switch n { + case 0: + return z.Set(x) + case 64: + return z.lsh64(x) + case 128: + return z.lsh128(x) + case 192: + return z.lsh192(x) + default: + return z.Clear() + } + } + var a, b uint64 + // Big swaps first + switch { + case n > 192: + if n > 256 { + return z.Clear() + } + z.lsh192(x) + n -= 192 + goto sh192 + case n > 128: + z.lsh128(x) + n -= 128 + goto sh128 + case n > 64: + z.lsh64(x) + n -= 64 + goto sh64 + default: + z.Set(x) + } + + // remaining shifts + a = z.arr[0] >> (64 - n) + z.arr[0] = z.arr[0] << n + +sh64: + b = z.arr[1] >> (64 - n) + z.arr[1] = (z.arr[1] << n) | a + +sh128: + a = z.arr[2] >> (64 - n) + z.arr[2] = (z.arr[2] << n) | b + +sh192: + z.arr[3] = (z.arr[3] << n) | a + + return z +} + +// Rsh sets z = x >> n and returns z. +func (z *Uint) Rsh(x *Uint, n uint) *Uint { + // n % 64 == 0 + if n&0x3f == 0 { + switch n { + case 0: + return z.Set(x) + case 64: + return z.rsh64(x) + case 128: + return z.rsh128(x) + case 192: + return z.rsh192(x) + default: + return z.Clear() + } + } + var a, b uint64 + // Big swaps first + switch { + case n > 192: + if n > 256 { + return z.Clear() + } + z.rsh192(x) + n -= 192 + goto sh192 + case n > 128: + z.rsh128(x) + n -= 128 + goto sh128 + case n > 64: + z.rsh64(x) + n -= 64 + goto sh64 + default: + z.Set(x) + } + + // remaining shifts + a = z.arr[3] << (64 - n) + z.arr[3] = z.arr[3] >> n + +sh64: + b = z.arr[2] << (64 - n) + z.arr[2] = (z.arr[2] >> n) | a + +sh128: + a = z.arr[1] << (64 - n) + z.arr[1] = (z.arr[1] >> n) | b + +sh192: + z.arr[0] = (z.arr[0] >> n) | a + + return z +} + +// SRsh (Signed/Arithmetic right shift) +// considers z to be a signed integer, during right-shift +// and sets z = x >> n and returns z. +func (z *Uint) SRsh(x *Uint, n uint) *Uint { + // If the MSB is 0, SRsh is same as Rsh. + if !x.isBitSet(255) { + return z.Rsh(x, n) + } + if n%64 == 0 { + switch n { + case 0: + return z.Set(x) + case 64: + return z.srsh64(x) + case 128: + return z.srsh128(x) + case 192: + return z.srsh192(x) + default: + return z.SetAllOne() + } + } + var a uint64 = MaxUint64 << (64 - n%64) + // Big swaps first + switch { + case n > 192: + if n > 256 { + return z.SetAllOne() + } + z.srsh192(x) + n -= 192 + goto sh192 + case n > 128: + z.srsh128(x) + n -= 128 + goto sh128 + case n > 64: + z.srsh64(x) + n -= 64 + goto sh64 + default: + z.Set(x) + } + + // remaining shifts + z.arr[3], a = (z.arr[3]>>n)|a, z.arr[3]<<(64-n) + +sh64: + z.arr[2], a = (z.arr[2]>>n)|a, z.arr[2]<<(64-n) + +sh128: + z.arr[1], a = (z.arr[1]>>n)|a, z.arr[1]<<(64-n) + +sh192: + z.arr[0] = (z.arr[0] >> n) | a + + return z +} + +func (z *Uint) lsh64(x *Uint) *Uint { + z.arr[3], z.arr[2], z.arr[1], z.arr[0] = x.arr[2], x.arr[1], x.arr[0], 0 + return z +} + +func (z *Uint) lsh128(x *Uint) *Uint { + z.arr[3], z.arr[2], z.arr[1], z.arr[0] = x.arr[1], x.arr[0], 0, 0 + return z +} + +func (z *Uint) lsh192(x *Uint) *Uint { + z.arr[3], z.arr[2], z.arr[1], z.arr[0] = x.arr[0], 0, 0, 0 + return z +} + +func (z *Uint) rsh64(x *Uint) *Uint { + z.arr[3], z.arr[2], z.arr[1], z.arr[0] = 0, x.arr[3], x.arr[2], x.arr[1] + return z +} + +func (z *Uint) rsh128(x *Uint) *Uint { + z.arr[3], z.arr[2], z.arr[1], z.arr[0] = 0, 0, x.arr[3], x.arr[2] + return z +} + +func (z *Uint) rsh192(x *Uint) *Uint { + z.arr[3], z.arr[2], z.arr[1], z.arr[0] = 0, 0, 0, x.arr[3] + return z +} + +func (z *Uint) srsh64(x *Uint) *Uint { + z.arr[3], z.arr[2], z.arr[1], z.arr[0] = MaxUint64, x.arr[3], x.arr[2], x.arr[1] + return z +} + +func (z *Uint) srsh128(x *Uint) *Uint { + z.arr[3], z.arr[2], z.arr[1], z.arr[0] = MaxUint64, MaxUint64, x.arr[3], x.arr[2] + return z +} + +func (z *Uint) srsh192(x *Uint) *Uint { + z.arr[3], z.arr[2], z.arr[1], z.arr[0] = MaxUint64, MaxUint64, MaxUint64, x.arr[3] + return z +} diff --git a/_deploy/p/gnoswap/uint256/bitwise_test.gno b/_deploy/p/gnoswap/uint256/bitwise_test.gno new file mode 100644 index 000000000..4437e7d5c --- /dev/null +++ b/_deploy/p/gnoswap/uint256/bitwise_test.gno @@ -0,0 +1,346 @@ +package uint256 + +import ( + "testing" +) + +type logicOpTest struct { + name string + x Uint + y Uint + want Uint +} + +func TestOr(t *testing.T) { + tests := []logicOpTest{ + { + name: "all zeros", + x: Uint{arr: [4]uint64{0, 0, 0, 0}}, + y: Uint{arr: [4]uint64{0, 0, 0, 0}}, + want: Uint{arr: [4]uint64{0, 0, 0, 0}}, + }, + { + name: "all ones", + x: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}}, + y: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}}, + want: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}}, + }, + { + name: "mixed", + x: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), 0, 0}}, + y: Uint{arr: [4]uint64{0, 0, ^uint64(0), ^uint64(0)}}, + want: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}}, + }, + { + name: "one operand all ones", + x: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}}, + y: Uint{arr: [4]uint64{0x5555555555555555, 0xAAAAAAAAAAAAAAAA, 0xFFFFFFFFFFFFFFFF, 0x0000000000000000}}, + want: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}}, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + res := new(Uint).Or(&tc.x, &tc.y) + if *res != tc.want { + t.Errorf("Or(%s, %s) = %s, want %s", tc.x.ToString(), tc.y.ToString(), res.ToString(), (tc.want).ToString()) + } + }) + } +} + +func TestAnd(t *testing.T) { + tests := []logicOpTest{ + { + name: "all zeros", + x: Uint{arr: [4]uint64{0, 0, 0, 0}}, + y: Uint{arr: [4]uint64{0, 0, 0, 0}}, + want: Uint{arr: [4]uint64{0, 0, 0, 0}}, + }, + { + name: "all ones", + x: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}}, + y: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}}, + want: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}}, + }, + { + name: "mixed", + x: Uint{arr: [4]uint64{0, 0, 0, 0}}, + y: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}}, + want: Uint{arr: [4]uint64{0, 0, 0, 0}}, + }, + { + name: "mixed 2", + x: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}}, + y: Uint{arr: [4]uint64{0, 0, 0, 0}}, + want: Uint{arr: [4]uint64{0, 0, 0, 0}}, + }, + { + name: "mixed 3", + x: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), 0, 0}}, + y: Uint{arr: [4]uint64{0, 0, ^uint64(0), ^uint64(0)}}, + want: Uint{arr: [4]uint64{0, 0, 0, 0}}, + }, + { + name: "one operand zero", + x: Uint{arr: [4]uint64{0, 0, 0, 0}}, + y: Uint{arr: [4]uint64{0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF}}, + want: Uint{arr: [4]uint64{0, 0, 0, 0}}, + }, + { + name: "one operand all ones", + x: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}}, + y: Uint{arr: [4]uint64{0x5555555555555555, 0xAAAAAAAAAAAAAAAA, 0xFFFFFFFFFFFFFFFF, 0x0000000000000000}}, + want: Uint{arr: [4]uint64{0x5555555555555555, 0xAAAAAAAAAAAAAAAA, 0xFFFFFFFFFFFFFFFF, 0x0000000000000000}}, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + res := new(Uint).And(&tc.x, &tc.y) + if *res != tc.want { + t.Errorf("And(%s, %s) = %s, want %s", tc.x.ToString(), tc.y.ToString(), res.ToString(), (tc.want).ToString()) + } + }) + } +} + +func TestNot(t *testing.T) { + tests := []struct { + name string + x Uint + want Uint + }{ + { + name: "all zeros", + x: Uint{arr: [4]uint64{0, 0, 0, 0}}, + want: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}}, + }, + { + name: "all ones", + x: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}}, + want: Uint{arr: [4]uint64{0, 0, 0, 0}}, + }, + { + name: "mixed", + x: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), 0, 0}}, + want: Uint{arr: [4]uint64{0, 0, ^uint64(0), ^uint64(0)}}, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + res := new(Uint).Not(&tc.x) + if *res != tc.want { + t.Errorf("Not(%s) = %s, want %s", tc.x.ToString(), res.ToString(), (tc.want).ToString()) + } + }) + } +} + +func TestAndNot(t *testing.T) { + tests := []logicOpTest{ + { + name: "all zeros", + x: Uint{arr: [4]uint64{0, 0, 0, 0}}, + y: Uint{arr: [4]uint64{0, 0, 0, 0}}, + want: Uint{arr: [4]uint64{0, 0, 0, 0}}, + }, + { + name: "all ones", + x: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}}, + y: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}}, + want: Uint{arr: [4]uint64{0, 0, 0, 0}}, + }, + { + name: "mixed", + x: Uint{arr: [4]uint64{0, 0, 0, 0}}, + y: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}}, + want: Uint{arr: [4]uint64{0, 0, 0, 0}}, + }, + { + name: "mixed 2", + x: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}}, + y: Uint{arr: [4]uint64{0, 0, 0, 0}}, + want: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}}, + }, + { + name: "mixed 3", + x: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), 0, 0}}, + y: Uint{arr: [4]uint64{0, 0, ^uint64(0), ^uint64(0)}}, + want: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), 0, 0}}, + }, + { + name: "one operand zero", + x: Uint{arr: [4]uint64{0, 0, 0, 0}}, + y: Uint{arr: [4]uint64{0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF}}, + want: Uint{arr: [4]uint64{0, 0, 0, 0}}, + }, + { + name: "one operand all ones", + x: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}}, + y: Uint{arr: [4]uint64{0x5555555555555555, 0xAAAAAAAAAAAAAAAA, 0xFFFFFFFFFFFFFFFF, 0x0000000000000000}}, + want: Uint{arr: [4]uint64{0xAAAAAAAAAAAAAAAA, 0x5555555555555555, 0x0000000000000000, ^uint64(0)}}, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + res := new(Uint).AndNot(&tc.x, &tc.y) + if *res != tc.want { + t.Errorf("AndNot(%s, %s) = %s, want %s", tc.x.ToString(), tc.y.ToString(), res.ToString(), (tc.want).ToString()) + } + }) + } +} + +func TestXor(t *testing.T) { + tests := []logicOpTest{ + { + name: "all zeros", + x: Uint{arr: [4]uint64{0, 0, 0, 0}}, + y: Uint{arr: [4]uint64{0, 0, 0, 0}}, + want: Uint{arr: [4]uint64{0, 0, 0, 0}}, + }, + { + name: "all ones", + x: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}}, + y: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}}, + want: Uint{arr: [4]uint64{0, 0, 0, 0}}, + }, + { + name: "mixed", + x: Uint{arr: [4]uint64{0, 0, 0, 0}}, + y: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}}, + want: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}}, + }, + { + name: "mixed 2", + x: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}}, + y: Uint{arr: [4]uint64{0, 0, 0, 0}}, + want: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}}, + }, + { + name: "mixed 3", + x: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), 0, 0}}, + y: Uint{arr: [4]uint64{0, 0, ^uint64(0), ^uint64(0)}}, + want: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}}, + }, + { + name: "one operand zero", + x: Uint{arr: [4]uint64{0, 0, 0, 0}}, + y: Uint{arr: [4]uint64{0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF}}, + want: Uint{arr: [4]uint64{0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF}}, + }, + { + name: "one operand all ones", + x: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}}, + y: Uint{arr: [4]uint64{0x5555555555555555, 0xAAAAAAAAAAAAAAAA, 0xFFFFFFFFFFFFFFFF, 0x0000000000000000}}, + want: Uint{arr: [4]uint64{0xAAAAAAAAAAAAAAAA, 0x5555555555555555, 0x0000000000000000, ^uint64(0)}}, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + res := new(Uint).Xor(&tc.x, &tc.y) + if *res != tc.want { + t.Errorf("Xor(%s, %s) = %s, want %s", tc.x.ToString(), tc.y.ToString(), res.ToString(), (tc.want).ToString()) + } + }) + } +} + +func TestLsh(t *testing.T) { + tests := []struct { + x string + y uint + want string + }{ + {"0", 0, "0"}, + {"0", 1, "0"}, + {"0", 64, "0"}, + {"1", 0, "1"}, + {"1", 1, "2"}, + {"1", 64, "18446744073709551616"}, + {"1", 128, "340282366920938463463374607431768211456"}, + {"1", 192, "6277101735386680763835789423207666416102355444464034512896"}, + {"1", 255, "57896044618658097711785492504343953926634992332820282019728792003956564819968"}, + {"1", 256, "0"}, + {"31337", 0, "31337"}, + {"31337", 1, "62674"}, + {"31337", 64, "578065619037836218990592"}, + {"31337", 128, "10663428532201448629551770073089320442396672"}, + {"31337", 192, "196705537081812415096322133155058642481399512563169449530621952"}, + {"31337", 193, "393411074163624830192644266310117284962799025126338899061243904"}, + {"31337", 255, "57896044618658097711785492504343953926634992332820282019728792003956564819968"}, + {"31337", 256, "0"}, + } + + for _, tc := range tests { + x, err := FromDecimal(tc.x) + if err != nil { + t.Error(err) + continue + } + + want, err := FromDecimal(tc.want) + if err != nil { + t.Error(err) + continue + } + + got := &Uint{} + got.Lsh(x, tc.y) + + if got.Neq(want) { + t.Errorf("Lsh(%s, %d) = %s, want %s", tc.x, tc.y, got.ToString(), want.ToString()) + } + } +} + +func TestRsh(t *testing.T) { + tests := []struct { + x string + y uint + want string + }{ + {"0", 0, "0"}, + {"0", 1, "0"}, + {"0", 64, "0"}, + {"1", 0, "1"}, + {"1", 1, "0"}, + {"1", 64, "0"}, + {"1", 128, "0"}, + {"1", 192, "0"}, + {"1", 255, "0"}, + {"57896044618658097711785492504343953926634992332820282019728792003956564819968", 255, "1"}, + {"6277101735386680763835789423207666416102355444464034512896", 192, "1"}, + {"340282366920938463463374607431768211456", 128, "1"}, + {"18446744073709551616", 64, "1"}, + {"393411074163624830192644266310117284962799025126338899061243904", 193, "31337"}, + {"196705537081812415096322133155058642481399512563169449530621952", 192, "31337"}, + {"10663428532201448629551770073089320442396672", 128, "31337"}, + {"578065619037836218990592", 64, "31337"}, + } + + for _, tc := range tests { + x, err := FromDecimal(tc.x) + if err != nil { + t.Error(err) + continue + } + + want, err := FromDecimal(tc.want) + if err != nil { + t.Error(err) + continue + } + + got := &Uint{} + got.Rsh(x, tc.y) + + if got.Neq(want) { + t.Errorf("Rsh(%s, %d) = %s, want %s", tc.x, tc.y, got.ToString(), want.ToString()) + } + } +} diff --git a/_deploy/p/gnoswap/uint256/cmp.gno b/_deploy/p/gnoswap/uint256/cmp.gno new file mode 100644 index 000000000..a43a31e5f --- /dev/null +++ b/_deploy/p/gnoswap/uint256/cmp.gno @@ -0,0 +1,125 @@ +// cmp (or, comparisons) includes methods for comparing Uint instances. +// These comparison functions cover a range of operations including equality checks, less than/greater than +// evaluations, and specialized comparisons such as signed greater than. These are fundamental for logical +// decision making based on Uint values. +package uint256 + +import ( + "math/bits" +) + +// Cmp compares z and x and returns: +// +// -1 if z < x +// 0 if z == x +// +1 if z > x +func (z *Uint) Cmp(x *Uint) (r int) { + // z < x <=> z - x < 0 i.e. when subtraction overflows. + d0, carry := bits.Sub64(z.arr[0], x.arr[0], 0) + d1, carry := bits.Sub64(z.arr[1], x.arr[1], carry) + d2, carry := bits.Sub64(z.arr[2], x.arr[2], carry) + d3, carry := bits.Sub64(z.arr[3], x.arr[3], carry) + if carry == 1 { + return -1 + } + if d0|d1|d2|d3 == 0 { + return 0 + } + return 1 +} + +// IsZero returns true if z == 0 +func (z *Uint) IsZero() bool { + return (z.arr[0] | z.arr[1] | z.arr[2] | z.arr[3]) == 0 +} + +// Sign returns: +// +// -1 if z < 0 +// 0 if z == 0 +// +1 if z > 0 +// +// Where z is interpreted as a two's complement signed number +func (z *Uint) Sign() int { + if z.IsZero() { + return 0 + } + if z.arr[3] < 0x8000000000000000 { + return 1 + } + return -1 +} + +// LtUint64 returns true if z is smaller than n +func (z *Uint) LtUint64(n uint64) bool { + return z.arr[0] < n && (z.arr[1]|z.arr[2]|z.arr[3]) == 0 +} + +// GtUint64 returns true if z is larger than n +func (z *Uint) GtUint64(n uint64) bool { + return z.arr[0] > n || (z.arr[1]|z.arr[2]|z.arr[3]) != 0 +} + +// Lt returns true if z < x +func (z *Uint) Lt(x *Uint) bool { + // z < x <=> z - x < 0 i.e. when subtraction overflows. + _, carry := bits.Sub64(z.arr[0], x.arr[0], 0) + _, carry = bits.Sub64(z.arr[1], x.arr[1], carry) + _, carry = bits.Sub64(z.arr[2], x.arr[2], carry) + _, carry = bits.Sub64(z.arr[3], x.arr[3], carry) + + return carry != 0 +} + +// Gt returns true if z > x +func (z *Uint) Gt(x *Uint) bool { + return x.Lt(z) +} + +// Lte returns true if z <= x +func (z *Uint) Lte(x *Uint) bool { + cond1 := z.Lt(x) + cond2 := z.Eq(x) + + if cond1 || cond2 { + return true + } + return false +} + +// Gte returns true if z >= x +func (z *Uint) Gte(x *Uint) bool { + cond1 := z.Gt(x) + cond2 := z.Eq(x) + + if cond1 || cond2 { + return true + } + return false +} + +// Eq returns true if z == x +func (z *Uint) Eq(x *Uint) bool { + return (z.arr[0] == x.arr[0]) && (z.arr[1] == x.arr[1]) && (z.arr[2] == x.arr[2]) && (z.arr[3] == x.arr[3]) +} + +// Neq returns true if z != x +func (z *Uint) Neq(x *Uint) bool { + return !z.Eq(x) +} + +// Sgt interprets z and x as signed integers, and returns +// true if z > x +func (z *Uint) Sgt(x *Uint) bool { + zSign := z.Sign() + xSign := x.Sign() + + switch { + case zSign >= 0 && xSign < 0: + return true + case zSign < 0 && xSign >= 0: + return false + default: + return z.Gt(x) + } +} diff --git a/_deploy/p/gnoswap/uint256/cmp_test.gno b/_deploy/p/gnoswap/uint256/cmp_test.gno new file mode 100644 index 000000000..930079f70 --- /dev/null +++ b/_deploy/p/gnoswap/uint256/cmp_test.gno @@ -0,0 +1,163 @@ +package uint256 + +import ( + "strings" + "testing" +) + +func TestCmp(t *testing.T) { + tests := []struct { + x, y string + want int + }{ + {"0", "0", 0}, + {"0", "1", -1}, + {"1", "0", 1}, + {"1", "1", 0}, + {"10", "10", 0}, + {"10", "11", -1}, + {"11", "10", 1}, + } + + for _, tc := range tests { + x, err := FromDecimal(tc.x) + if err != nil { + t.Error(err) + continue + } + + y, err := FromDecimal(tc.y) + if err != nil { + t.Error(err) + continue + } + + got := x.Cmp(y) + if got != tc.want { + t.Errorf("Cmp(%s, %s) = %v, want %v", tc.x, tc.y, got, tc.want) + } + } +} + +func TestIsZero(t *testing.T) { + tests := []struct { + x string + want bool + }{ + {"0", true}, + {"1", false}, + {"10", false}, + } + + for _, tc := range tests { + x, err := FromDecimal(tc.x) + if err != nil { + t.Error(err) + continue + } + + got := x.IsZero() + if got != tc.want { + t.Errorf("IsZero(%s) = %v, want %v", tc.x, got, tc.want) + } + } +} + +func TestLtUint64(t *testing.T) { + tests := []struct { + x string + y uint64 + want bool + }{ + {"0", 1, true}, + {"1", 0, false}, + {"10", 10, false}, + {"0xffffffffffffffff", 0, false}, + {"0x10000000000000000", 10000000000000000, false}, + } + + for _, tc := range tests { + var x *Uint + var err error + + if strings.HasPrefix(tc.x, "0x") { + x, err = FromHex(tc.x) + if err != nil { + t.Error(err) + continue + } + } else { + x, err = FromDecimal(tc.x) + if err != nil { + t.Error(err) + continue + } + } + + got := x.LtUint64(tc.y) + + if got != tc.want { + t.Errorf("LtUint64(%s, %d) = %v, want %v", tc.x, tc.y, got, tc.want) + } + } +} + +func TestSGT(t *testing.T) { + x := MustFromHex("0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe") + y := MustFromHex("0x0") + actual := x.Sgt(y) + if actual { + t.Fatalf("Expected %v false", actual) + } + + x = MustFromHex("0x0") + y = MustFromHex("0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe") + actual = x.Sgt(y) + if !actual { + t.Fatalf("Expected %v true", actual) + } +} + +func TestEq(t *testing.T) { + tests := []struct { + x string + y string + want bool + }{ + {"0xffffffffffffffff", "18446744073709551615", true}, + {"0x10000000000000000", "18446744073709551616", true}, + {"0", "0", true}, + {"115792089237316195423570985008687907853269984665640564039457584007913129639935", "115792089237316195423570985008687907853269984665640564039457584007913129639935", true}, + } + + for i, tc := range tests { + var x *Uint + var err error + + if strings.HasPrefix(tc.x, "0x") { + x, err = FromHex(tc.x) + if err != nil { + t.Error(err) + continue + } + } else { + x, err = FromDecimal(tc.x) + if err != nil { + t.Error(err) + continue + } + } + + y, err := FromDecimal(tc.y) + if err != nil { + t.Error(err) + continue + } + + got := x.Eq(y) + + if got != tc.want { + t.Errorf("Eq(%s, %s) = %v, want %v", tc.x, tc.y, got, tc.want) + } + } +} diff --git a/_deploy/p/gnoswap/uint256/conversion.gno b/_deploy/p/gnoswap/uint256/conversion.gno new file mode 100644 index 000000000..4ef90602a --- /dev/null +++ b/_deploy/p/gnoswap/uint256/conversion.gno @@ -0,0 +1,570 @@ +// conversions contains methods for converting Uint instances to other types and vice versa. +// This includes conversions to and from basic types such as uint64 and int32, as well as string representations +// and byte slices. Additionally, it covers marshaling and unmarshaling for JSON and other text formats. +package uint256 + +import ( + "encoding/binary" + "errors" + "strconv" + "strings" +) + +// Uint64 returns the lower 64-bits of z +func (z *Uint) Uint64() uint64 { + return z.arr[0] +} + +// Uint64WithOverflow returns the lower 64-bits of z and bool whether overflow occurred +func (z *Uint) Uint64WithOverflow() (uint64, bool) { + return z.arr[0], (z.arr[1] | z.arr[2] | z.arr[3]) != 0 +} + +// SetUint64 sets z to the value x +func (z *Uint) SetUint64(x uint64) *Uint { + z.arr[3], z.arr[2], z.arr[1], z.arr[0] = 0, 0, 0, x + return z +} + +// IsUint64 reports whether z can be represented as a uint64. +func (z *Uint) IsUint64() bool { + return (z.arr[1] | z.arr[2] | z.arr[3]) == 0 +} + +// Dec returns the decimal representation of z. +func (z *Uint) Dec() string { + if z.IsZero() { + return "0" + } + if z.IsUint64() { + return strconv.FormatUint(z.Uint64(), 10) + } + + // The max uint64 value being 18446744073709551615, the largest + // power-of-ten below that is 10000000000000000000. + // When we do a DivMod using that number, the remainder that we + // get back is the lower part of the output. + // + // The ascii-output of remainder will never exceed 19 bytes (since it will be + // below 10000000000000000000). + // + // Algorithm example using 100 as divisor + // + // 12345 % 100 = 45 (rem) + // 12345 / 100 = 123 (quo) + // -> output '45', continue iterate on 123 + var ( + // out is 98 bytes long: 78 (max size of a string without leading zeroes, + // plus slack so we can copy 19 bytes every iteration). + // We init it with zeroes, because when strconv appends the ascii representations, + // it will omit leading zeroes. + out = []byte("00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000") + divisor = NewUint(10000000000000000000) // 20 digits + y = new(Uint).Set(z) // copy to avoid modifying z + pos = len(out) // position to write to + buf = make([]byte, 0, 19) // buffer to write uint64:s to + ) + for { + // Obtain Q and R for divisor + var quot Uint + rem := udivrem(quot.arr[:], y.arr[:], divisor) + y.Set(") // Set Q for next loop + // Convert the R to ascii representation + buf = strconv.AppendUint(buf[:0], rem.Uint64(), 10) + // Copy in the ascii digits + copy(out[pos-len(buf):], buf) + if y.IsZero() { + break + } + // Move 19 digits left + pos -= 19 + } + // skip leading zeroes by only using the 'used size' of buf + return string(out[pos-len(buf):]) +} + +func (z *Uint) Scan(src interface{}) error { + if src == nil { + z.Clear() + return nil + } + + switch src := src.(type) { + case string: + return z.scanScientificFromString(src) + case []byte: + return z.scanScientificFromString(string(src)) + } + return errors.New("default // unsupported type: can't convert to uint256.Uint") +} + +func (z *Uint) scanScientificFromString(src string) error { + if len(src) == 0 { + z.Clear() + return nil + } + + idx := strings.IndexByte(src, 'e') + if idx == -1 { + return z.SetFromDecimal(src) + } + if err := z.SetFromDecimal(src[:idx]); err != nil { + return err + } + if src[(idx+1):] == "0" { + return nil + } + exp := new(Uint) + if err := exp.SetFromDecimal(src[(idx + 1):]); err != nil { + return err + } + if exp.GtUint64(77) { // 10**78 is larger than 2**256 + return ErrBig256Range + } + exp.Exp(NewUint(10), exp) + if _, overflow := z.MulOverflow(z, exp); overflow { + return ErrBig256Range + } + return nil +} + +// ToString returns the decimal string representation of z. It returns an empty string if z is nil. +// OBS: doesn't exist from holiman's uint256 +func (z *Uint) ToString() string { + if z == nil { + return "" + } + + return z.Dec() +} + +// MarshalJSON implements json.Marshaler. +// MarshalJSON marshals using the 'decimal string' representation. This is _not_ compatible +// with big.Uint: big.Uint marshals into JSON 'native' numeric format. +// +// The JSON native format is, on some platforms, (e.g. javascript), limited to 53-bit large +// integer space. Thus, U256 uses string-format, which is not compatible with +// big.int (big.Uint refuses to unmarshal a string representation). +func (z *Uint) MarshalJSON() ([]byte, error) { + return []byte(`"` + z.Dec() + `"`), nil +} + +// UnmarshalJSON implements json.Unmarshaler. UnmarshalJSON accepts either +// - Quoted string: either hexadecimal OR decimal +// - Not quoted string: only decimal +func (z *Uint) UnmarshalJSON(input []byte) error { + if len(input) < 2 || input[0] != '"' || input[len(input)-1] != '"' { + // if not quoted, it must be decimal + return z.fromDecimal(string(input)) + } + return z.UnmarshalText(input[1 : len(input)-1]) +} + +// MarshalText implements encoding.TextMarshaler +// MarshalText marshals using the decimal representation (compatible with big.Uint) +func (z *Uint) MarshalText() ([]byte, error) { + return []byte(z.Dec()), nil +} + +// UnmarshalText implements encoding.TextUnmarshaler. This method +// can unmarshal either hexadecimal or decimal. +// - For hexadecimal, the input _must_ be prefixed with 0x or 0X +func (z *Uint) UnmarshalText(input []byte) error { + if len(input) >= 2 && input[0] == '0' && (input[1] == 'x' || input[1] == 'X') { + return z.fromHex(string(input)) + } + return z.fromDecimal(string(input)) +} + +// SetBytes interprets buf as the bytes of a big-endian unsigned +// integer, sets z to that value, and returns z. +// If buf is larger than 32 bytes, the last 32 bytes is used. +func (z *Uint) SetBytes(buf []byte) *Uint { + switch l := len(buf); l { + case 0: + z.Clear() + case 1: + z.SetBytes1(buf) + case 2: + z.SetBytes2(buf) + case 3: + z.SetBytes3(buf) + case 4: + z.SetBytes4(buf) + case 5: + z.SetBytes5(buf) + case 6: + z.SetBytes6(buf) + case 7: + z.SetBytes7(buf) + case 8: + z.SetBytes8(buf) + case 9: + z.SetBytes9(buf) + case 10: + z.SetBytes10(buf) + case 11: + z.SetBytes11(buf) + case 12: + z.SetBytes12(buf) + case 13: + z.SetBytes13(buf) + case 14: + z.SetBytes14(buf) + case 15: + z.SetBytes15(buf) + case 16: + z.SetBytes16(buf) + case 17: + z.SetBytes17(buf) + case 18: + z.SetBytes18(buf) + case 19: + z.SetBytes19(buf) + case 20: + z.SetBytes20(buf) + case 21: + z.SetBytes21(buf) + case 22: + z.SetBytes22(buf) + case 23: + z.SetBytes23(buf) + case 24: + z.SetBytes24(buf) + case 25: + z.SetBytes25(buf) + case 26: + z.SetBytes26(buf) + case 27: + z.SetBytes27(buf) + case 28: + z.SetBytes28(buf) + case 29: + z.SetBytes29(buf) + case 30: + z.SetBytes30(buf) + case 31: + z.SetBytes31(buf) + default: + z.SetBytes32(buf[l-32:]) + } + return z +} + +// SetBytes1 is identical to SetBytes(in[:1]), but panics is input is too short +func (z *Uint) SetBytes1(in []byte) *Uint { + z.arr[3], z.arr[2], z.arr[1] = 0, 0, 0 + z.arr[0] = uint64(in[0]) + return z +} + +// SetBytes2 is identical to SetBytes(in[:2]), but panics is input is too short +func (z *Uint) SetBytes2(in []byte) *Uint { + _ = in[1] // bounds check hint to compiler; see golang.org/issue/14808 + z.arr[3], z.arr[2], z.arr[1] = 0, 0, 0 + z.arr[0] = uint64(binary.BigEndian.Uint16(in[0:2])) + return z +} + +// SetBytes3 is identical to SetBytes(in[:3]), but panics is input is too short +func (z *Uint) SetBytes3(in []byte) *Uint { + _ = in[2] // bounds check hint to compiler; see golang.org/issue/14808 + z.arr[3], z.arr[2], z.arr[1] = 0, 0, 0 + z.arr[0] = uint64(binary.BigEndian.Uint16(in[1:3])) | uint64(in[0])<<16 + return z +} + +// SetBytes4 is identical to SetBytes(in[:4]), but panics is input is too short +func (z *Uint) SetBytes4(in []byte) *Uint { + _ = in[3] // bounds check hint to compiler; see golang.org/issue/14808 + z.arr[3], z.arr[2], z.arr[1] = 0, 0, 0 + z.arr[0] = uint64(binary.BigEndian.Uint32(in[0:4])) + return z +} + +// SetBytes5 is identical to SetBytes(in[:5]), but panics is input is too short +func (z *Uint) SetBytes5(in []byte) *Uint { + _ = in[4] // bounds check hint to compiler; see golang.org/issue/14808 + z.arr[3], z.arr[2], z.arr[1] = 0, 0, 0 + z.arr[0] = bigEndianUint40(in[0:5]) + return z +} + +// SetBytes6 is identical to SetBytes(in[:6]), but panics is input is too short +func (z *Uint) SetBytes6(in []byte) *Uint { + _ = in[5] // bounds check hint to compiler; see golang.org/issue/14808 + z.arr[3], z.arr[2], z.arr[1] = 0, 0, 0 + z.arr[0] = bigEndianUint48(in[0:6]) + return z +} + +// SetBytes7 is identical to SetBytes(in[:7]), but panics is input is too short +func (z *Uint) SetBytes7(in []byte) *Uint { + _ = in[6] // bounds check hint to compiler; see golang.org/issue/14808 + z.arr[3], z.arr[2], z.arr[1] = 0, 0, 0 + z.arr[0] = bigEndianUint56(in[0:7]) + return z +} + +// SetBytes8 is identical to SetBytes(in[:8]), but panics is input is too short +func (z *Uint) SetBytes8(in []byte) *Uint { + _ = in[7] // bounds check hint to compiler; see golang.org/issue/14808 + z.arr[3], z.arr[2], z.arr[1] = 0, 0, 0 + z.arr[0] = binary.BigEndian.Uint64(in[0:8]) + return z +} + +// SetBytes9 is identical to SetBytes(in[:9]), but panics is input is too short +func (z *Uint) SetBytes9(in []byte) *Uint { + _ = in[8] // bounds check hint to compiler; see golang.org/issue/14808 + z.arr[3], z.arr[2] = 0, 0 + z.arr[1] = uint64(in[0]) + z.arr[0] = binary.BigEndian.Uint64(in[1:9]) + return z +} + +// SetBytes10 is identical to SetBytes(in[:10]), but panics is input is too short +func (z *Uint) SetBytes10(in []byte) *Uint { + _ = in[9] // bounds check hint to compiler; see golang.org/issue/14808 + z.arr[3], z.arr[2] = 0, 0 + z.arr[1] = uint64(binary.BigEndian.Uint16(in[0:2])) + z.arr[0] = binary.BigEndian.Uint64(in[2:10]) + return z +} + +// SetBytes11 is identical to SetBytes(in[:11]), but panics is input is too short +func (z *Uint) SetBytes11(in []byte) *Uint { + _ = in[10] // bounds check hint to compiler; see golang.org/issue/14808 + z.arr[3], z.arr[2] = 0, 0 + z.arr[1] = uint64(binary.BigEndian.Uint16(in[1:3])) | uint64(in[0])<<16 + z.arr[0] = binary.BigEndian.Uint64(in[3:11]) + return z +} + +// SetBytes12 is identical to SetBytes(in[:12]), but panics is input is too short +func (z *Uint) SetBytes12(in []byte) *Uint { + _ = in[11] // bounds check hint to compiler; see golang.org/issue/14808 + z.arr[3], z.arr[2] = 0, 0 + z.arr[1] = uint64(binary.BigEndian.Uint32(in[0:4])) + z.arr[0] = binary.BigEndian.Uint64(in[4:12]) + return z +} + +// SetBytes13 is identical to SetBytes(in[:13]), but panics is input is too short +func (z *Uint) SetBytes13(in []byte) *Uint { + _ = in[12] // bounds check hint to compiler; see golang.org/issue/14808 + z.arr[3], z.arr[2] = 0, 0 + z.arr[1] = bigEndianUint40(in[0:5]) + z.arr[0] = binary.BigEndian.Uint64(in[5:13]) + return z +} + +// SetBytes14 is identical to SetBytes(in[:14]), but panics is input is too short +func (z *Uint) SetBytes14(in []byte) *Uint { + _ = in[13] // bounds check hint to compiler; see golang.org/issue/14808 + z.arr[3], z.arr[2] = 0, 0 + z.arr[1] = bigEndianUint48(in[0:6]) + z.arr[0] = binary.BigEndian.Uint64(in[6:14]) + return z +} + +// SetBytes15 is identical to SetBytes(in[:15]), but panics is input is too short +func (z *Uint) SetBytes15(in []byte) *Uint { + _ = in[14] // bounds check hint to compiler; see golang.org/issue/14808 + z.arr[3], z.arr[2] = 0, 0 + z.arr[1] = bigEndianUint56(in[0:7]) + z.arr[0] = binary.BigEndian.Uint64(in[7:15]) + return z +} + +// SetBytes16 is identical to SetBytes(in[:16]), but panics is input is too short +func (z *Uint) SetBytes16(in []byte) *Uint { + _ = in[15] // bounds check hint to compiler; see golang.org/issue/14808 + z.arr[3], z.arr[2] = 0, 0 + z.arr[1] = binary.BigEndian.Uint64(in[0:8]) + z.arr[0] = binary.BigEndian.Uint64(in[8:16]) + return z +} + +// SetBytes17 is identical to SetBytes(in[:17]), but panics is input is too short +func (z *Uint) SetBytes17(in []byte) *Uint { + _ = in[16] // bounds check hint to compiler; see golang.org/issue/14808 + z.arr[3] = 0 + z.arr[2] = uint64(in[0]) + z.arr[1] = binary.BigEndian.Uint64(in[1:9]) + z.arr[0] = binary.BigEndian.Uint64(in[9:17]) + return z +} + +// SetBytes18 is identical to SetBytes(in[:18]), but panics is input is too short +func (z *Uint) SetBytes18(in []byte) *Uint { + _ = in[17] // bounds check hint to compiler; see golang.org/issue/14808 + z.arr[3] = 0 + z.arr[2] = uint64(binary.BigEndian.Uint16(in[0:2])) + z.arr[1] = binary.BigEndian.Uint64(in[2:10]) + z.arr[0] = binary.BigEndian.Uint64(in[10:18]) + return z +} + +// SetBytes19 is identical to SetBytes(in[:19]), but panics is input is too short +func (z *Uint) SetBytes19(in []byte) *Uint { + _ = in[18] // bounds check hint to compiler; see golang.org/issue/14808 + z.arr[3] = 0 + z.arr[2] = uint64(binary.BigEndian.Uint16(in[1:3])) | uint64(in[0])<<16 + z.arr[1] = binary.BigEndian.Uint64(in[3:11]) + z.arr[0] = binary.BigEndian.Uint64(in[11:19]) + return z +} + +// SetBytes20 is identical to SetBytes(in[:20]), but panics is input is too short +func (z *Uint) SetBytes20(in []byte) *Uint { + _ = in[19] // bounds check hint to compiler; see golang.org/issue/14808 + z.arr[3] = 0 + z.arr[2] = uint64(binary.BigEndian.Uint32(in[0:4])) + z.arr[1] = binary.BigEndian.Uint64(in[4:12]) + z.arr[0] = binary.BigEndian.Uint64(in[12:20]) + return z +} + +// SetBytes21 is identical to SetBytes(in[:21]), but panics is input is too short +func (z *Uint) SetBytes21(in []byte) *Uint { + _ = in[20] // bounds check hint to compiler; see golang.org/issue/14808 + z.arr[3] = 0 + z.arr[2] = bigEndianUint40(in[0:5]) + z.arr[1] = binary.BigEndian.Uint64(in[5:13]) + z.arr[0] = binary.BigEndian.Uint64(in[13:21]) + return z +} + +// SetBytes22 is identical to SetBytes(in[:22]), but panics is input is too short +func (z *Uint) SetBytes22(in []byte) *Uint { + _ = in[21] // bounds check hint to compiler; see golang.org/issue/14808 + z.arr[3] = 0 + z.arr[2] = bigEndianUint48(in[0:6]) + z.arr[1] = binary.BigEndian.Uint64(in[6:14]) + z.arr[0] = binary.BigEndian.Uint64(in[14:22]) + return z +} + +// SetBytes23 is identical to SetBytes(in[:23]), but panics is input is too short +func (z *Uint) SetBytes23(in []byte) *Uint { + _ = in[22] // bounds check hint to compiler; see golang.org/issue/14808 + z.arr[3] = 0 + z.arr[2] = bigEndianUint56(in[0:7]) + z.arr[1] = binary.BigEndian.Uint64(in[7:15]) + z.arr[0] = binary.BigEndian.Uint64(in[15:23]) + return z +} + +// SetBytes24 is identical to SetBytes(in[:24]), but panics is input is too short +func (z *Uint) SetBytes24(in []byte) *Uint { + _ = in[23] // bounds check hint to compiler; see golang.org/issue/14808 + z.arr[3] = 0 + z.arr[2] = binary.BigEndian.Uint64(in[0:8]) + z.arr[1] = binary.BigEndian.Uint64(in[8:16]) + z.arr[0] = binary.BigEndian.Uint64(in[16:24]) + return z +} + +// SetBytes25 is identical to SetBytes(in[:25]), but panics is input is too short +func (z *Uint) SetBytes25(in []byte) *Uint { + _ = in[24] // bounds check hint to compiler; see golang.org/issue/14808 + z.arr[3] = uint64(in[0]) + z.arr[2] = binary.BigEndian.Uint64(in[1:9]) + z.arr[1] = binary.BigEndian.Uint64(in[9:17]) + z.arr[0] = binary.BigEndian.Uint64(in[17:25]) + return z +} + +// SetBytes26 is identical to SetBytes(in[:26]), but panics is input is too short +func (z *Uint) SetBytes26(in []byte) *Uint { + _ = in[25] // bounds check hint to compiler; see golang.org/issue/14808 + z.arr[3] = uint64(binary.BigEndian.Uint16(in[0:2])) + z.arr[2] = binary.BigEndian.Uint64(in[2:10]) + z.arr[1] = binary.BigEndian.Uint64(in[10:18]) + z.arr[0] = binary.BigEndian.Uint64(in[18:26]) + return z +} + +// SetBytes27 is identical to SetBytes(in[:27]), but panics is input is too short +func (z *Uint) SetBytes27(in []byte) *Uint { + _ = in[26] // bounds check hint to compiler; see golang.org/issue/14808 + z.arr[3] = uint64(binary.BigEndian.Uint16(in[1:3])) | uint64(in[0])<<16 + z.arr[2] = binary.BigEndian.Uint64(in[3:11]) + z.arr[1] = binary.BigEndian.Uint64(in[11:19]) + z.arr[0] = binary.BigEndian.Uint64(in[19:27]) + return z +} + +// SetBytes28 is identical to SetBytes(in[:28]), but panics is input is too short +func (z *Uint) SetBytes28(in []byte) *Uint { + _ = in[27] // bounds check hint to compiler; see golang.org/issue/14808 + z.arr[3] = uint64(binary.BigEndian.Uint32(in[0:4])) + z.arr[2] = binary.BigEndian.Uint64(in[4:12]) + z.arr[1] = binary.BigEndian.Uint64(in[12:20]) + z.arr[0] = binary.BigEndian.Uint64(in[20:28]) + return z +} + +// SetBytes29 is identical to SetBytes(in[:29]), but panics is input is too short +func (z *Uint) SetBytes29(in []byte) *Uint { + _ = in[23] // bounds check hint to compiler; see golang.org/issue/14808 + z.arr[3] = bigEndianUint40(in[0:5]) + z.arr[2] = binary.BigEndian.Uint64(in[5:13]) + z.arr[1] = binary.BigEndian.Uint64(in[13:21]) + z.arr[0] = binary.BigEndian.Uint64(in[21:29]) + return z +} + +// SetBytes30 is identical to SetBytes(in[:30]), but panics is input is too short +func (z *Uint) SetBytes30(in []byte) *Uint { + _ = in[29] // bounds check hint to compiler; see golang.org/issue/14808 + z.arr[3] = bigEndianUint48(in[0:6]) + z.arr[2] = binary.BigEndian.Uint64(in[6:14]) + z.arr[1] = binary.BigEndian.Uint64(in[14:22]) + z.arr[0] = binary.BigEndian.Uint64(in[22:30]) + return z +} + +// SetBytes31 is identical to SetBytes(in[:31]), but panics is input is too short +func (z *Uint) SetBytes31(in []byte) *Uint { + _ = in[30] // bounds check hint to compiler; see golang.org/issue/14808 + z.arr[3] = bigEndianUint56(in[0:7]) + z.arr[2] = binary.BigEndian.Uint64(in[7:15]) + z.arr[1] = binary.BigEndian.Uint64(in[15:23]) + z.arr[0] = binary.BigEndian.Uint64(in[23:31]) + return z +} + +// SetBytes32 sets z to the value of the big-endian 256-bit unsigned integer in. +func (z *Uint) SetBytes32(in []byte) *Uint { + _ = in[31] // bounds check hint to compiler; see golang.org/issue/14808 + z.arr[3] = binary.BigEndian.Uint64(in[0:8]) + z.arr[2] = binary.BigEndian.Uint64(in[8:16]) + z.arr[1] = binary.BigEndian.Uint64(in[16:24]) + z.arr[0] = binary.BigEndian.Uint64(in[24:32]) + return z +} + +// Utility methods that are "missing" among the bigEndian.UintXX methods. + +// bigEndianUint40 returns the uint64 value represented by the 5 bytes in big-endian order. +func bigEndianUint40(b []byte) uint64 { + _ = b[4] // bounds check hint to compiler; see golang.org/issue/14808 + return uint64(b[4]) | uint64(b[3])<<8 | uint64(b[2])<<16 | uint64(b[1])<<24 | + uint64(b[0])<<32 +} + +// bigEndianUint56 returns the uint64 value represented by the 7 bytes in big-endian order. +func bigEndianUint56(b []byte) uint64 { + _ = b[6] // bounds check hint to compiler; see golang.org/issue/14808 + return uint64(b[6]) | uint64(b[5])<<8 | uint64(b[4])<<16 | uint64(b[3])<<24 | + uint64(b[2])<<32 | uint64(b[1])<<40 | uint64(b[0])<<48 +} + +// bigEndianUint48 returns the uint64 value represented by the 6 bytes in big-endian order. +func bigEndianUint48(b []byte) uint64 { + _ = b[5] // bounds check hint to compiler; see golang.org/issue/14808 + return uint64(b[5]) | uint64(b[4])<<8 | uint64(b[3])<<16 | uint64(b[2])<<24 | + uint64(b[1])<<32 | uint64(b[0])<<40 +} diff --git a/_deploy/p/gnoswap/uint256/conversion_test.gno b/_deploy/p/gnoswap/uint256/conversion_test.gno new file mode 100644 index 000000000..12ae99cc0 --- /dev/null +++ b/_deploy/p/gnoswap/uint256/conversion_test.gno @@ -0,0 +1,60 @@ +package uint256 + +import ( + "testing" +) + +func TestIsUint64(t *testing.T) { + tests := []struct { + x string + want bool + }{ + {"0x0", true}, + {"0x1", true}, + {"0x10", true}, + {"0xffffffffffffffff", true}, + {"0x10000000000000000", false}, + } + + for _, tc := range tests { + x := MustFromHex(tc.x) + got := x.IsUint64() + + if got != tc.want { + t.Errorf("IsUint64(%s) = %v, want %v", tc.x, got, tc.want) + } + } +} + +func TestDec(t *testing.T) { + testCases := []struct { + name string + z Uint + want string + }{ + { + name: "zero", + z: Uint{arr: [4]uint64{0, 0, 0, 0}}, + want: "0", + }, + { + name: "less than 20 digits", + z: Uint{arr: [4]uint64{1234567890, 0, 0, 0}}, + want: "1234567890", + }, + { + name: "max possible value", + z: Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}}, + want: "115792089237316195423570985008687907853269984665640564039457584007913129639935", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + result := tc.z.Dec() + if result != tc.want { + t.Errorf("Dec(%v) = %s, want %s", tc.z, result, tc.want) + } + }) + } +} diff --git a/_deploy/p/gnoswap/uint256/error.gno b/_deploy/p/gnoswap/uint256/error.gno new file mode 100644 index 000000000..d200bb9cc --- /dev/null +++ b/_deploy/p/gnoswap/uint256/error.gno @@ -0,0 +1,73 @@ +package uint256 + +import ( + "errors" +) + +var ( + ErrEmptyString = errors.New("empty hex string") + ErrSyntax = errors.New("invalid hex string") + ErrRange = errors.New("number out of range") + ErrMissingPrefix = errors.New("hex string without 0x prefix") + ErrEmptyNumber = errors.New("hex string \"0x\"") + ErrLeadingZero = errors.New("hex number with leading zero digits") + ErrBig256Range = errors.New("hex number > 256 bits") + ErrBadBufferLength = errors.New("bad ssz buffer length") + ErrBadEncodedLength = errors.New("bad ssz encoded length") + ErrInvalidBase = errors.New("invalid base") + ErrInvalidBitSize = errors.New("invalid bit size") +) + +type u256Error struct { + fn string // function name + input string + err error +} + +func (e *u256Error) Error() string { + return e.fn + ": " + e.input + ": " + e.err.Error() +} + +func (e *u256Error) Unwrap() error { + return e.err +} + +func errEmptyString(fn, input string) error { + return &u256Error{fn: fn, input: input, err: ErrEmptyString} +} + +func errSyntax(fn, input string) error { + return &u256Error{fn: fn, input: input, err: ErrSyntax} +} + +func errMissingPrefix(fn, input string) error { + return &u256Error{fn: fn, input: input, err: ErrMissingPrefix} +} + +func errEmptyNumber(fn, input string) error { + return &u256Error{fn: fn, input: input, err: ErrEmptyNumber} +} + +func errLeadingZero(fn, input string) error { + return &u256Error{fn: fn, input: input, err: ErrLeadingZero} +} + +func errRange(fn, input string) error { + return &u256Error{fn: fn, input: input, err: ErrRange} +} + +func errBig256Range(fn, input string) error { + return &u256Error{fn: fn, input: input, err: ErrBig256Range} +} + +func errBadBufferLength(fn, input string) error { + return &u256Error{fn: fn, input: input, err: ErrBadBufferLength} +} + +func errInvalidBase(fn string, base int) error { + return &u256Error{fn: fn, input: string(base), err: ErrInvalidBase} +} + +func errInvalidBitSize(fn string, bitSize int) error { + return &u256Error{fn: fn, input: string(bitSize), err: ErrInvalidBitSize} +} diff --git a/_deploy/p/gnoswap/uint256/fullmath.gno b/_deploy/p/gnoswap/uint256/fullmath.gno new file mode 100644 index 000000000..b8b079943 --- /dev/null +++ b/_deploy/p/gnoswap/uint256/fullmath.gno @@ -0,0 +1,151 @@ +// REF: https://github.com/Uniswap/v3-core/blob/main/contracts/libraries/FullMath.sol +package uint256 + +import ( + "gno.land/p/demo/ufmt" +) + +const ( + MAX_UINT256 = "115792089237316195423570985008687907853269984665640564039457584007913129639935" +) + +func MulDiv( + a, b, denominator *Uint, +) *Uint { + prod0 := Zero() + prod1 := Zero() + + { + mm := new(Uint).MulMod(a, b, new(Uint).Not(Zero())) + prod0 = new(Uint).Mul(a, b) + + ltBool := mm.Lt(prod0) + ltUint := Zero() + if ltBool { + ltUint = One() + } + prod1 = new(Uint).Sub(new(Uint).Sub(mm, prod0), ltUint) + } + + // Handle non-overflow cases, 256 by 256 division + if prod1.IsZero() { + if !(denominator.Gt(Zero())) { // require(denominator > 0); + panic(ufmt.Sprintf("uint256_MulDiv()__denominator(%s) > 0", denominator.ToString())) + } + + result := new(Uint).Div(prod0, denominator) + return result + } + + // Make sure the result is less than 2**256. + // Also prevents denominator == 0 + if !(denominator.Gt(prod1)) { // require(denominator > prod1) + panic(ufmt.Sprintf("uint256_MulDiv()__denominator(%s) > prod1(%s)", denominator.ToString(), prod1.ToString())) + } + + /////////////////////////////////////////////// + // 512 by 256 division. + /////////////////////////////////////////////// + + // Make division exact by subtracting the remainder from [prod1 prod0] + // Compute remainder using mulmod + remainder := Zero() + remainder = new(Uint).MulMod(a, b, denominator) + + // Subtract 256 bit number from 512 bit number + gtBool := remainder.Gt(prod0) + gtUint := Zero() + if gtBool { + gtUint = One() + } + prod1 = new(Uint).Sub(prod1, gtUint) + prod0 = new(Uint).Sub(prod0, remainder) + + // Factor powers of two out of denominator + // Compute largest power of two divisor of denominator. + // Always >= 1. + twos := Zero() + twos = new(Uint).And(new(Uint).Neg(denominator), denominator) + + // Divide denominator by power of two + denominator = new(Uint).Div(denominator, twos) + + // Divide [prod1 prod0] by the factors of two + prod0 = new(Uint).Div(prod0, twos) + + // Shift in bits from prod1 into prod0. For this we need + // to flip `twos` such that it is 2**256 / twos. + // If twos is zero, then it becomes one + twos = new(Uint).Add( + new(Uint).Div( + new(Uint).Sub(Zero(), twos), + twos, + ), + One(), + ) + prod0 = new(Uint).Or(prod0, new(Uint).Mul(prod1, twos)) + + // Invert denominator mod 2**256 + // Now that denominator is an odd number, it has an inverse + // modulo 2**256 such that denominator * inv = 1 mod 2**256. + // Compute the inverse by starting with a seed that is correct + // correct for four bits. That is, denominator * inv = 1 mod 2**4 + inv := Zero() + inv = new(Uint).Mul(NewUint(3), denominator) + inv = new(Uint).Xor(inv, NewUint(2)) + + // Now use Newton-Raphson iteration to improve the precision. + // Thanks to Hensel's lifting lemma, this also works in modular + // arithmetic, doubling the correct bits in each step. + + inv = new(Uint).Mul(inv, new(Uint).Sub(NewUint(2), new(Uint).Mul(denominator, inv))) // inverse mod 2**8 + inv = new(Uint).Mul(inv, new(Uint).Sub(NewUint(2), new(Uint).Mul(denominator, inv))) // inverse mod 2**16 + inv = new(Uint).Mul(inv, new(Uint).Sub(NewUint(2), new(Uint).Mul(denominator, inv))) // inverse mod 2**32 + inv = new(Uint).Mul(inv, new(Uint).Sub(NewUint(2), new(Uint).Mul(denominator, inv))) // inverse mod 2**64 + inv = new(Uint).Mul(inv, new(Uint).Sub(NewUint(2), new(Uint).Mul(denominator, inv))) // inverse mod 2**128 + inv = new(Uint).Mul(inv, new(Uint).Sub(NewUint(2), new(Uint).Mul(denominator, inv))) // inverse mod 2**256 + + // Because the division is now exact we can divide by multiplying + // with the modular inverse of denominator. This will give us the + // correct result modulo 2**256. Since the precoditions guarantee + // that the outcome is less than 2**256, this is the final result. + // We don't need to compute the high bits of the result and prod1 + // is no longer required. + result := new(Uint).Mul(prod0, inv) + return result +} + +func MulDivRoundingUp( + a, b, denominator *Uint, +) *Uint { + result := MulDiv(a, b, denominator) + + if new(Uint).MulMod(a, b, denominator).Gt(Zero()) { + if !(result.Lt(MustFromDecimal(MAX_UINT256))) { // require(result < MAX_UINT256) + panic(ufmt.Sprintf("uint256_MulDivRoundingUp()__result(%s) < MAX_UINT256", result.ToString())) + } + + result = new(Uint).Add(result, One()) + } + + return result +} + +// UnsafeMath +// https://github.com/Uniswap/v3-core/blob/d8b1c635c275d2a9450bd6a78f3fa2484fef73eb/contracts/libraries/UnsafeMath.sol +func DivRoundingUp( + x, y *Uint, +) *Uint { + div := new(Uint).Div(x, y) + mod := new(Uint).Mod(x, y) + + z := new(Uint).Add(div, gt(mod, Zero())) + return z +} + +func gt(x, y *Uint) *Uint { + if x.Gt(y) { + return One() + } + return Zero() +} diff --git a/_deploy/p/gnoswap/uint256/fullmath_test.gno b/_deploy/p/gnoswap/uint256/fullmath_test.gno new file mode 100644 index 000000000..71e3917e9 --- /dev/null +++ b/_deploy/p/gnoswap/uint256/fullmath_test.gno @@ -0,0 +1,256 @@ +package uint256 + +import ( + "testing" +) + +var Q128 *Uint + +func init() { + Q128 = MustFromDecimal("340282366920938463463374607431768211456") // 2**128 +} + +func TestFullMathMulDiv(t *testing.T) { + t.Run("reverts if denominator is 0", func(t *testing.T) { + x := NewUint(5) + y := Zero() + + shouldPanic( + t, + func() { + MulDiv(Q128, x, y) + }, + ) + }) + + t.Run("reverts if denominator is 0 and numerator overflows", func(t *testing.T) { + y := Zero() + + shouldPanic( + t, + func() { + MulDiv(Q128, Q128, y) + }, + ) + }) + + t.Run("reverts if output overflows uint256", func(t *testing.T) { + y := One() + + shouldPanic( + t, + func() { + MulDiv(Q128, Q128, y) + }, + ) + }) + + t.Run("reverts on overflow with all max inputs", func(t *testing.T) { + MaxUint256 := MustFromDecimal("115792089237316195423570985008687907853269984665640564039457584007913129639935") + MaxUint256Sub1 := MustFromDecimal("115792089237316195423570985008687907853269984665640564039457584007913129639934") + + shouldPanic( + t, + func() { + MulDiv(MaxUint256, MaxUint256, MaxUint256Sub1) + }, + ) + }) + + t.Run("all max inputs", func(t *testing.T) { + MaxUint256 := MustFromDecimal("115792089237316195423570985008687907853269984665640564039457584007913129639935") + + got := MulDiv(MaxUint256, MaxUint256, MaxUint256) + + rst := got.Eq(MaxUint256) + if !rst { + t.Errorf("MaxUin256*MaxUint256/MaxUint256 is not same to MaxUint256") + } + }) + + t.Run("accurate without phantom overflow", func(t *testing.T) { + + result := new(Uint).Div(Q128, NewUint(3)) + + x := NewUint(50) + x = x.Mul(x, Q128) + x = x.Div(x, NewUint(100)) /*0.5=*/ + + y := NewUint(150) + y = y.Mul(y, Q128) + y = y.Div(y, NewUint(100)) /*1.5=*/ + + got := MulDiv(Q128, x, y) + if !got.Eq(result) { + t.Errorf("Q128/3 is not same to Q128 * (50*Q128/100) / (150*Q128/100)") + } + }) + + t.Run("accurate with phantom overflow", func(t *testing.T) { + + x := NewUint(4375) + expected := MulDiv(x, Q128, NewUint(1000)) + + y := NewUint(35) + y = y.Mul(y, Q128) + + denom := NewUint(8) + denom = denom.Mul(denom, Q128) + + got := MulDiv(Q128, y, denom) + if !got.Eq(expected) { + t.Errorf("4375*Q128/1000 is not same to Q128 * 35*Q128 / 8*Q128") + } + }) + + t.Run("accurate with phantom overflow and repeating decimal", func(t *testing.T) { + + expected := MulDiv(One(), Q128, NewUint(3)) + + y := NewUint(1000) + y = y.Mul(y, Q128) + + denom := NewUint(3000) + denom = denom.Mul(denom, Q128) + + got := MulDiv(Q128, y, denom) + if !got.Eq(expected) { + t.Errorf("1*Q128/3 is not same to Q128 * 1000*Q128 / 3000*Q128") + } + }) +} + +func TestMulDivRoundingUp(t *testing.T) { + t.Run("reverts if denominator is 0", func(t *testing.T) { + x := NewUint(5) + y := Zero() + + shouldPanic( + t, + func() { + MulDivRoundingUp(Q128, x, y) + }, + ) + }) + + t.Run("reverts if denominator is 0 and numerator overflows", func(t *testing.T) { + y := Zero() + + shouldPanic( + t, + func() { + MulDivRoundingUp(Q128, Q128, y) + }, + ) + }) + + t.Run("reverts if output overflows uint256", func(t *testing.T) { + y := One() + + shouldPanic( + t, + func() { + MulDivRoundingUp(Q128, Q128, y) + }, + ) + }) + + t.Run("reverts on overflow with all max inputs", func(t *testing.T) { + MaxUint256 := MustFromDecimal("115792089237316195423570985008687907853269984665640564039457584007913129639935") + MaxUint256Sub1 := MustFromDecimal("115792089237316195423570985008687907853269984665640564039457584007913129639934") + + shouldPanic( + t, + func() { + MulDivRoundingUp(MaxUint256, MaxUint256, MaxUint256Sub1) + }, + ) + }) + + t.Run("reverts if mulDiv overflows 256 bits after rounding up", func(t *testing.T) { + x := MustFromDecimal("535006138814359") + y := MustFromDecimal("432862656469423142931042426214547535783388063929571229938474969") + + shouldPanic( + t, + func() { + MulDivRoundingUp(x, y, NewUint(2)) + }, + ) + }) + + t.Run("reverts if mulDiv overflows 256 bits after rounding up case 2", func(t *testing.T) { + x := MustFromDecimal("115792089237316195423570985008687907853269984659341747863450311749907997002549") + y := MustFromDecimal("115792089237316195423570985008687907853269984659341747863450311749907997002550") + z := MustFromDecimal("115792089237316195423570985008687907853269984653042931687443039491902864365164") + + shouldPanic( + t, + func() { + MulDivRoundingUp(x, y, z) + }, + ) + }) + + t.Run("all max inputs", func(t *testing.T) { + MaxUint256 := MustFromDecimal("115792089237316195423570985008687907853269984665640564039457584007913129639935") + + got := MulDivRoundingUp(MaxUint256, MaxUint256, MaxUint256) + + rst := got.Eq(MaxUint256) + if !rst { + t.Errorf("MaxUin256*MaxUint256/MaxUint256 RoudingUp is not same to MaxUint256") + } + }) + + t.Run("accurate without phantom overflow", func(t *testing.T) { + + result := new(Uint).Div(Q128, NewUint(3)) + result = result.Add(result, One()) + + x := NewUint(50) + x = x.Mul(x, Q128) + x = x.Div(x, NewUint(100)) /*0.5=*/ + + y := NewUint(150) + y = y.Mul(y, Q128) + y = y.Div(y, NewUint(100)) /*1.5=*/ + + got := MulDivRoundingUp(Q128, x, y) + if !got.Eq(result) { + t.Errorf("Q128/3 is not same to Q128 * (50*Q128/100) / (150*Q128/100)") + } + }) + + t.Run("accurate with phantom overflow", func(t *testing.T) { + x := NewUint(4375) + expected := MulDiv(x, Q128, NewUint(1000)) + + y := NewUint(35) + y = y.Mul(y, Q128) + + denom := NewUint(8) + denom = denom.Mul(denom, Q128) + + got := MulDivRoundingUp(Q128, y, denom) + if !got.Eq(expected) { + t.Errorf("4375*Q128/1000 is not same to Q128 * 35*Q128 / 8*Q128") + } + }) + + t.Run("accurate with phantom overflow and repeating decimal", func(t *testing.T) { + expected := MulDiv(One(), Q128, NewUint(3)) + expected = expected.Add(expected, One()) + + y := NewUint(1000) + y = y.Mul(y, Q128) + + denom := NewUint(3000) + denom = denom.Mul(denom, Q128) + + got := MulDivRoundingUp(Q128, y, denom) + if !got.Eq(expected) { + t.Errorf("1*Q128/3+1 is not same to Q128 * 1000*Q128 / 3000*Q128") + } + }) +} diff --git a/_deploy/p/gnoswap/uint256/gno.mod b/_deploy/p/gnoswap/uint256/gno.mod new file mode 100644 index 000000000..a3b515356 --- /dev/null +++ b/_deploy/p/gnoswap/uint256/gno.mod @@ -0,0 +1,3 @@ +module gno.land/p/gnoswap/uint256 + +require gno.land/p/demo/ufmt v0.0.0-latest diff --git a/_deploy/p/gnoswap/uint256/gs_pointer.gno b/_deploy/p/gnoswap/uint256/gs_pointer.gno new file mode 100644 index 000000000..e24e6bf33 --- /dev/null +++ b/_deploy/p/gnoswap/uint256/gs_pointer.gno @@ -0,0 +1,8 @@ +package uint256 + +func (z *Uint) NilToZero() *Uint { + if z == nil { + z = NewUint(0) + } + return z +} diff --git a/_deploy/p/gnoswap/uint256/mod.gno b/_deploy/p/gnoswap/uint256/mod.gno new file mode 100644 index 000000000..f6ff0967e --- /dev/null +++ b/_deploy/p/gnoswap/uint256/mod.gno @@ -0,0 +1,605 @@ +package uint256 + +import ( + "math/bits" +) + +// Some utility functions + +// Reciprocal computes a 320-bit value representing 1/m +// +// Notes: +// - specialized for m.arr[3] != 0, hence limited to 2^192 <= m < 2^256 +// - returns zero if m.arr[3] == 0 +// - starts with a 32-bit division, refines with newton-raphson iterations +func Reciprocal(m *Uint) (mu [5]uint64) { + if m.arr[3] == 0 { + return mu + } + + s := bits.LeadingZeros64(m.arr[3]) // Replace with leadingZeros(m) for general case + p := 255 - s // floor(log_2(m)), m>0 + + // 0 or a power of 2? + + // Check if at least one bit is set in m.arr[2], m.arr[1] or m.arr[0], + // or at least two bits in m.arr[3] + + if m.arr[0]|m.arr[1]|m.arr[2]|(m.arr[3]&(m.arr[3]-1)) == 0 { + + mu[4] = ^uint64(0) >> uint(p&63) + mu[3] = ^uint64(0) + mu[2] = ^uint64(0) + mu[1] = ^uint64(0) + mu[0] = ^uint64(0) + + return mu + } + + // Maximise division precision by left-aligning divisor + + var ( + y Uint // left-aligned copy of m + r0 uint32 // estimate of 2^31/y + ) + + y.Lsh(m, uint(s)) // 1/2 < y < 1 + + // Extract most significant 32 bits + + yh := uint32(y.arr[3] >> 32) + + if yh == 0x80000000 { // Avoid overflow in division + r0 = 0xffffffff + } else { + r0, _ = bits.Div32(0x80000000, 0, yh) + } + + // First iteration: 32 -> 64 + + t1 := uint64(r0) // 2^31/y + t1 *= t1 // 2^62/y^2 + t1, _ = bits.Mul64(t1, y.arr[3]) // 2^62/y^2 * 2^64/y / 2^64 = 2^62/y + + r1 := uint64(r0) << 32 // 2^63/y + r1 -= t1 // 2^63/y - 2^62/y = 2^62/y + r1 *= 2 // 2^63/y + + if (r1 | (y.arr[3] << 1)) == 0 { + r1 = ^uint64(0) + } + + // Second iteration: 64 -> 128 + + // square: 2^126/y^2 + a2h, a2l := bits.Mul64(r1, r1) + + // multiply by y: e2h:e2l:b2h = 2^126/y^2 * 2^128/y / 2^128 = 2^126/y + b2h, _ := bits.Mul64(a2l, y.arr[2]) + c2h, c2l := bits.Mul64(a2l, y.arr[3]) + d2h, d2l := bits.Mul64(a2h, y.arr[2]) + e2h, e2l := bits.Mul64(a2h, y.arr[3]) + + b2h, c := bits.Add64(b2h, c2l, 0) + e2l, c = bits.Add64(e2l, c2h, c) + e2h, _ = bits.Add64(e2h, 0, c) + + _, c = bits.Add64(b2h, d2l, 0) + e2l, c = bits.Add64(e2l, d2h, c) + e2h, _ = bits.Add64(e2h, 0, c) + + // subtract: t2h:t2l = 2^127/y - 2^126/y = 2^126/y + t2l, b := bits.Sub64(0, e2l, 0) + t2h, _ := bits.Sub64(r1, e2h, b) + + // double: r2h:r2l = 2^127/y + r2l, c := bits.Add64(t2l, t2l, 0) + r2h, _ := bits.Add64(t2h, t2h, c) + + if (r2h | r2l | (y.arr[3] << 1)) == 0 { + r2h = ^uint64(0) + r2l = ^uint64(0) + } + + // Third iteration: 128 -> 192 + + // square r2 (keep 256 bits): 2^190/y^2 + a3h, a3l := bits.Mul64(r2l, r2l) + b3h, b3l := bits.Mul64(r2l, r2h) + c3h, c3l := bits.Mul64(r2h, r2h) + + a3h, c = bits.Add64(a3h, b3l, 0) + c3l, c = bits.Add64(c3l, b3h, c) + c3h, _ = bits.Add64(c3h, 0, c) + + a3h, c = bits.Add64(a3h, b3l, 0) + c3l, c = bits.Add64(c3l, b3h, c) + c3h, _ = bits.Add64(c3h, 0, c) + + // multiply by y: q = 2^190/y^2 * 2^192/y / 2^192 = 2^190/y + + x0 := a3l + x1 := a3h + x2 := c3l + x3 := c3h + + var q0, q1, q2, q3, q4, t0 uint64 + + q0, _ = bits.Mul64(x2, y.arr[0]) + q1, t0 = bits.Mul64(x3, y.arr[0]) + q0, c = bits.Add64(q0, t0, 0) + q1, _ = bits.Add64(q1, 0, c) + + t1, _ = bits.Mul64(x1, y.arr[1]) + q0, c = bits.Add64(q0, t1, 0) + q2, t0 = bits.Mul64(x3, y.arr[1]) + q1, c = bits.Add64(q1, t0, c) + q2, _ = bits.Add64(q2, 0, c) + + t1, t0 = bits.Mul64(x2, y.arr[1]) + q0, c = bits.Add64(q0, t0, 0) + q1, c = bits.Add64(q1, t1, c) + q2, _ = bits.Add64(q2, 0, c) + + t1, t0 = bits.Mul64(x1, y.arr[2]) + q0, c = bits.Add64(q0, t0, 0) + q1, c = bits.Add64(q1, t1, c) + q3, t0 = bits.Mul64(x3, y.arr[2]) + q2, c = bits.Add64(q2, t0, c) + q3, _ = bits.Add64(q3, 0, c) + + t1, _ = bits.Mul64(x0, y.arr[2]) + q0, c = bits.Add64(q0, t1, 0) + t1, t0 = bits.Mul64(x2, y.arr[2]) + q1, c = bits.Add64(q1, t0, c) + q2, c = bits.Add64(q2, t1, c) + q3, _ = bits.Add64(q3, 0, c) + + t1, t0 = bits.Mul64(x1, y.arr[3]) + q1, c = bits.Add64(q1, t0, 0) + q2, c = bits.Add64(q2, t1, c) + q4, t0 = bits.Mul64(x3, y.arr[3]) + q3, c = bits.Add64(q3, t0, c) + q4, _ = bits.Add64(q4, 0, c) + + t1, t0 = bits.Mul64(x0, y.arr[3]) + q0, c = bits.Add64(q0, t0, 0) + q1, c = bits.Add64(q1, t1, c) + t1, t0 = bits.Mul64(x2, y.arr[3]) + q2, c = bits.Add64(q2, t0, c) + q3, c = bits.Add64(q3, t1, c) + q4, _ = bits.Add64(q4, 0, c) + + // subtract: t3 = 2^191/y - 2^190/y = 2^190/y + _, b = bits.Sub64(0, q0, 0) + _, b = bits.Sub64(0, q1, b) + t3l, b := bits.Sub64(0, q2, b) + t3m, b := bits.Sub64(r2l, q3, b) + t3h, _ := bits.Sub64(r2h, q4, b) + + // double: r3 = 2^191/y + r3l, c := bits.Add64(t3l, t3l, 0) + r3m, c := bits.Add64(t3m, t3m, c) + r3h, _ := bits.Add64(t3h, t3h, c) + + // Fourth iteration: 192 -> 320 + + // square r3 + + a4h, a4l := bits.Mul64(r3l, r3l) + b4h, b4l := bits.Mul64(r3l, r3m) + c4h, c4l := bits.Mul64(r3l, r3h) + d4h, d4l := bits.Mul64(r3m, r3m) + e4h, e4l := bits.Mul64(r3m, r3h) + f4h, f4l := bits.Mul64(r3h, r3h) + + b4h, c = bits.Add64(b4h, c4l, 0) + e4l, c = bits.Add64(e4l, c4h, c) + e4h, _ = bits.Add64(e4h, 0, c) + + a4h, c = bits.Add64(a4h, b4l, 0) + d4l, c = bits.Add64(d4l, b4h, c) + d4h, c = bits.Add64(d4h, e4l, c) + f4l, c = bits.Add64(f4l, e4h, c) + f4h, _ = bits.Add64(f4h, 0, c) + + a4h, c = bits.Add64(a4h, b4l, 0) + d4l, c = bits.Add64(d4l, b4h, c) + d4h, c = bits.Add64(d4h, e4l, c) + f4l, c = bits.Add64(f4l, e4h, c) + f4h, _ = bits.Add64(f4h, 0, c) + + // multiply by y + + x1, x0 = bits.Mul64(d4h, y.arr[0]) + x3, x2 = bits.Mul64(f4h, y.arr[0]) + t1, t0 = bits.Mul64(f4l, y.arr[0]) + x1, c = bits.Add64(x1, t0, 0) + x2, c = bits.Add64(x2, t1, c) + x3, _ = bits.Add64(x3, 0, c) + + t1, t0 = bits.Mul64(d4h, y.arr[1]) + x1, c = bits.Add64(x1, t0, 0) + x2, c = bits.Add64(x2, t1, c) + x4, t0 := bits.Mul64(f4h, y.arr[1]) + x3, c = bits.Add64(x3, t0, c) + x4, _ = bits.Add64(x4, 0, c) + t1, t0 = bits.Mul64(d4l, y.arr[1]) + x0, c = bits.Add64(x0, t0, 0) + x1, c = bits.Add64(x1, t1, c) + t1, t0 = bits.Mul64(f4l, y.arr[1]) + x2, c = bits.Add64(x2, t0, c) + x3, c = bits.Add64(x3, t1, c) + x4, _ = bits.Add64(x4, 0, c) + + t1, t0 = bits.Mul64(a4h, y.arr[2]) + x0, c = bits.Add64(x0, t0, 0) + x1, c = bits.Add64(x1, t1, c) + t1, t0 = bits.Mul64(d4h, y.arr[2]) + x2, c = bits.Add64(x2, t0, c) + x3, c = bits.Add64(x3, t1, c) + x5, t0 := bits.Mul64(f4h, y.arr[2]) + x4, c = bits.Add64(x4, t0, c) + x5, _ = bits.Add64(x5, 0, c) + t1, t0 = bits.Mul64(d4l, y.arr[2]) + x1, c = bits.Add64(x1, t0, 0) + x2, c = bits.Add64(x2, t1, c) + t1, t0 = bits.Mul64(f4l, y.arr[2]) + x3, c = bits.Add64(x3, t0, c) + x4, c = bits.Add64(x4, t1, c) + x5, _ = bits.Add64(x5, 0, c) + + t1, t0 = bits.Mul64(a4h, y.arr[3]) + x1, c = bits.Add64(x1, t0, 0) + x2, c = bits.Add64(x2, t1, c) + t1, t0 = bits.Mul64(d4h, y.arr[3]) + x3, c = bits.Add64(x3, t0, c) + x4, c = bits.Add64(x4, t1, c) + x6, t0 := bits.Mul64(f4h, y.arr[3]) + x5, c = bits.Add64(x5, t0, c) + x6, _ = bits.Add64(x6, 0, c) + t1, t0 = bits.Mul64(a4l, y.arr[3]) + x0, c = bits.Add64(x0, t0, 0) + x1, c = bits.Add64(x1, t1, c) + t1, t0 = bits.Mul64(d4l, y.arr[3]) + x2, c = bits.Add64(x2, t0, c) + x3, c = bits.Add64(x3, t1, c) + t1, t0 = bits.Mul64(f4l, y.arr[3]) + x4, c = bits.Add64(x4, t0, c) + x5, c = bits.Add64(x5, t1, c) + x6, _ = bits.Add64(x6, 0, c) + + // subtract + _, b = bits.Sub64(0, x0, 0) + _, b = bits.Sub64(0, x1, b) + r4l, b := bits.Sub64(0, x2, b) + r4k, b := bits.Sub64(0, x3, b) + r4j, b := bits.Sub64(r3l, x4, b) + r4i, b := bits.Sub64(r3m, x5, b) + r4h, _ := bits.Sub64(r3h, x6, b) + + // Multiply candidate for 1/4y by y, with full precision + + x0 = r4l + x1 = r4k + x2 = r4j + x3 = r4i + x4 = r4h + + q1, q0 = bits.Mul64(x0, y.arr[0]) + q3, q2 = bits.Mul64(x2, y.arr[0]) + q5, q4 := bits.Mul64(x4, y.arr[0]) + + t1, t0 = bits.Mul64(x1, y.arr[0]) + q1, c = bits.Add64(q1, t0, 0) + q2, c = bits.Add64(q2, t1, c) + t1, t0 = bits.Mul64(x3, y.arr[0]) + q3, c = bits.Add64(q3, t0, c) + q4, c = bits.Add64(q4, t1, c) + q5, _ = bits.Add64(q5, 0, c) + + t1, t0 = bits.Mul64(x0, y.arr[1]) + q1, c = bits.Add64(q1, t0, 0) + q2, c = bits.Add64(q2, t1, c) + t1, t0 = bits.Mul64(x2, y.arr[1]) + q3, c = bits.Add64(q3, t0, c) + q4, c = bits.Add64(q4, t1, c) + q6, t0 := bits.Mul64(x4, y.arr[1]) + q5, c = bits.Add64(q5, t0, c) + q6, _ = bits.Add64(q6, 0, c) + + t1, t0 = bits.Mul64(x1, y.arr[1]) + q2, c = bits.Add64(q2, t0, 0) + q3, c = bits.Add64(q3, t1, c) + t1, t0 = bits.Mul64(x3, y.arr[1]) + q4, c = bits.Add64(q4, t0, c) + q5, c = bits.Add64(q5, t1, c) + q6, _ = bits.Add64(q6, 0, c) + + t1, t0 = bits.Mul64(x0, y.arr[2]) + q2, c = bits.Add64(q2, t0, 0) + q3, c = bits.Add64(q3, t1, c) + t1, t0 = bits.Mul64(x2, y.arr[2]) + q4, c = bits.Add64(q4, t0, c) + q5, c = bits.Add64(q5, t1, c) + q7, t0 := bits.Mul64(x4, y.arr[2]) + q6, c = bits.Add64(q6, t0, c) + q7, _ = bits.Add64(q7, 0, c) + + t1, t0 = bits.Mul64(x1, y.arr[2]) + q3, c = bits.Add64(q3, t0, 0) + q4, c = bits.Add64(q4, t1, c) + t1, t0 = bits.Mul64(x3, y.arr[2]) + q5, c = bits.Add64(q5, t0, c) + q6, c = bits.Add64(q6, t1, c) + q7, _ = bits.Add64(q7, 0, c) + + t1, t0 = bits.Mul64(x0, y.arr[3]) + q3, c = bits.Add64(q3, t0, 0) + q4, c = bits.Add64(q4, t1, c) + t1, t0 = bits.Mul64(x2, y.arr[3]) + q5, c = bits.Add64(q5, t0, c) + q6, c = bits.Add64(q6, t1, c) + q8, t0 := bits.Mul64(x4, y.arr[3]) + q7, c = bits.Add64(q7, t0, c) + q8, _ = bits.Add64(q8, 0, c) + + t1, t0 = bits.Mul64(x1, y.arr[3]) + q4, c = bits.Add64(q4, t0, 0) + q5, c = bits.Add64(q5, t1, c) + t1, t0 = bits.Mul64(x3, y.arr[3]) + q6, c = bits.Add64(q6, t0, c) + q7, c = bits.Add64(q7, t1, c) + q8, _ = bits.Add64(q8, 0, c) + + // Final adjustment + + // subtract q from 1/4 + _, b = bits.Sub64(0, q0, 0) + _, b = bits.Sub64(0, q1, b) + _, b = bits.Sub64(0, q2, b) + _, b = bits.Sub64(0, q3, b) + _, b = bits.Sub64(0, q4, b) + _, b = bits.Sub64(0, q5, b) + _, b = bits.Sub64(0, q6, b) + _, b = bits.Sub64(0, q7, b) + _, b = bits.Sub64(uint64(1)<<62, q8, b) + + // decrement the result + x0, t := bits.Sub64(r4l, 1, 0) + x1, t = bits.Sub64(r4k, 0, t) + x2, t = bits.Sub64(r4j, 0, t) + x3, t = bits.Sub64(r4i, 0, t) + x4, _ = bits.Sub64(r4h, 0, t) + + // commit the decrement if the subtraction underflowed (reciprocal was too large) + if b != 0 { + r4h, r4i, r4j, r4k, r4l = x4, x3, x2, x1, x0 + } + + // Shift to correct bit alignment, truncating excess bits + + p = (p & 63) - 1 + + x0, c = bits.Add64(r4l, r4l, 0) + x1, c = bits.Add64(r4k, r4k, c) + x2, c = bits.Add64(r4j, r4j, c) + x3, c = bits.Add64(r4i, r4i, c) + x4, _ = bits.Add64(r4h, r4h, c) + + if p < 0 { + r4h, r4i, r4j, r4k, r4l = x4, x3, x2, x1, x0 + p = 0 // avoid negative shift below + } + + { + r := uint(p) // right shift + l := uint(64 - r) // left shift + + x0 = (r4l >> r) | (r4k << l) + x1 = (r4k >> r) | (r4j << l) + x2 = (r4j >> r) | (r4i << l) + x3 = (r4i >> r) | (r4h << l) + x4 = (r4h >> r) + } + + if p > 0 { + r4h, r4i, r4j, r4k, r4l = x4, x3, x2, x1, x0 + } + + mu[0] = r4l + mu[1] = r4k + mu[2] = r4j + mu[3] = r4i + mu[4] = r4h + + return mu +} + +// reduce4 computes the least non-negative residue of x modulo m +// +// requires a four-word modulus (m.arr[3] > 1) and its inverse (mu) +func reduce4(x [8]uint64, m *Uint, mu [5]uint64) (z Uint) { + // NB: Most variable names in the comments match the pseudocode for + // Barrett reduction in the Handbook of Applied Cryptography. + + // q1 = x/2^192 + + x0 := x[3] + x1 := x[4] + x2 := x[5] + x3 := x[6] + x4 := x[7] + + // q2 = q1 * mu; q3 = q2 / 2^320 + + var q0, q1, q2, q3, q4, q5, t0, t1, c uint64 + + q0, _ = bits.Mul64(x3, mu[0]) + q1, t0 = bits.Mul64(x4, mu[0]) + q0, c = bits.Add64(q0, t0, 0) + q1, _ = bits.Add64(q1, 0, c) + + t1, _ = bits.Mul64(x2, mu[1]) + q0, c = bits.Add64(q0, t1, 0) + q2, t0 = bits.Mul64(x4, mu[1]) + q1, c = bits.Add64(q1, t0, c) + q2, _ = bits.Add64(q2, 0, c) + + t1, t0 = bits.Mul64(x3, mu[1]) + q0, c = bits.Add64(q0, t0, 0) + q1, c = bits.Add64(q1, t1, c) + q2, _ = bits.Add64(q2, 0, c) + + t1, t0 = bits.Mul64(x2, mu[2]) + q0, c = bits.Add64(q0, t0, 0) + q1, c = bits.Add64(q1, t1, c) + q3, t0 = bits.Mul64(x4, mu[2]) + q2, c = bits.Add64(q2, t0, c) + q3, _ = bits.Add64(q3, 0, c) + + t1, _ = bits.Mul64(x1, mu[2]) + q0, c = bits.Add64(q0, t1, 0) + t1, t0 = bits.Mul64(x3, mu[2]) + q1, c = bits.Add64(q1, t0, c) + q2, c = bits.Add64(q2, t1, c) + q3, _ = bits.Add64(q3, 0, c) + + t1, _ = bits.Mul64(x0, mu[3]) + q0, c = bits.Add64(q0, t1, 0) + t1, t0 = bits.Mul64(x2, mu[3]) + q1, c = bits.Add64(q1, t0, c) + q2, c = bits.Add64(q2, t1, c) + q4, t0 = bits.Mul64(x4, mu[3]) + q3, c = bits.Add64(q3, t0, c) + q4, _ = bits.Add64(q4, 0, c) + + t1, t0 = bits.Mul64(x1, mu[3]) + q0, c = bits.Add64(q0, t0, 0) + q1, c = bits.Add64(q1, t1, c) + t1, t0 = bits.Mul64(x3, mu[3]) + q2, c = bits.Add64(q2, t0, c) + q3, c = bits.Add64(q3, t1, c) + q4, _ = bits.Add64(q4, 0, c) + + t1, t0 = bits.Mul64(x0, mu[4]) + _, c = bits.Add64(q0, t0, 0) + q1, c = bits.Add64(q1, t1, c) + t1, t0 = bits.Mul64(x2, mu[4]) + q2, c = bits.Add64(q2, t0, c) + q3, c = bits.Add64(q3, t1, c) + q5, t0 = bits.Mul64(x4, mu[4]) + q4, c = bits.Add64(q4, t0, c) + q5, _ = bits.Add64(q5, 0, c) + + t1, t0 = bits.Mul64(x1, mu[4]) + q1, c = bits.Add64(q1, t0, 0) + q2, c = bits.Add64(q2, t1, c) + t1, t0 = bits.Mul64(x3, mu[4]) + q3, c = bits.Add64(q3, t0, c) + q4, c = bits.Add64(q4, t1, c) + q5, _ = bits.Add64(q5, 0, c) + + // Drop the fractional part of q3 + + q0 = q1 + q1 = q2 + q2 = q3 + q3 = q4 + q4 = q5 + + // r1 = x mod 2^320 + + x0 = x[0] + x1 = x[1] + x2 = x[2] + x3 = x[3] + x4 = x[4] + + // r2 = q3 * m mod 2^320 + + var r0, r1, r2, r3, r4 uint64 + + r4, r3 = bits.Mul64(q0, m.arr[3]) + _, t0 = bits.Mul64(q1, m.arr[3]) + r4, _ = bits.Add64(r4, t0, 0) + + t1, r2 = bits.Mul64(q0, m.arr[2]) + r3, c = bits.Add64(r3, t1, 0) + _, t0 = bits.Mul64(q2, m.arr[2]) + r4, _ = bits.Add64(r4, t0, c) + + t1, t0 = bits.Mul64(q1, m.arr[2]) + r3, c = bits.Add64(r3, t0, 0) + r4, _ = bits.Add64(r4, t1, c) + + t1, r1 = bits.Mul64(q0, m.arr[1]) + r2, c = bits.Add64(r2, t1, 0) + t1, t0 = bits.Mul64(q2, m.arr[1]) + r3, c = bits.Add64(r3, t0, c) + r4, _ = bits.Add64(r4, t1, c) + + t1, t0 = bits.Mul64(q1, m.arr[1]) + r2, c = bits.Add64(r2, t0, 0) + r3, c = bits.Add64(r3, t1, c) + _, t0 = bits.Mul64(q3, m.arr[1]) + r4, _ = bits.Add64(r4, t0, c) + + t1, r0 = bits.Mul64(q0, m.arr[0]) + r1, c = bits.Add64(r1, t1, 0) + t1, t0 = bits.Mul64(q2, m.arr[0]) + r2, c = bits.Add64(r2, t0, c) + r3, c = bits.Add64(r3, t1, c) + _, t0 = bits.Mul64(q4, m.arr[0]) + r4, _ = bits.Add64(r4, t0, c) + + t1, t0 = bits.Mul64(q1, m.arr[0]) + r1, c = bits.Add64(r1, t0, 0) + r2, c = bits.Add64(r2, t1, c) + t1, t0 = bits.Mul64(q3, m.arr[0]) + r3, c = bits.Add64(r3, t0, c) + r4, _ = bits.Add64(r4, t1, c) + + // r = r1 - r2 + + var b uint64 + + r0, b = bits.Sub64(x0, r0, 0) + r1, b = bits.Sub64(x1, r1, b) + r2, b = bits.Sub64(x2, r2, b) + r3, b = bits.Sub64(x3, r3, b) + r4, b = bits.Sub64(x4, r4, b) + + // if r<0 then r+=m + + if b != 0 { + r0, c = bits.Add64(r0, m.arr[0], 0) + r1, c = bits.Add64(r1, m.arr[1], c) + r2, c = bits.Add64(r2, m.arr[2], c) + r3, c = bits.Add64(r3, m.arr[3], c) + r4, _ = bits.Add64(r4, 0, c) + } + + // while (r>=m) r-=m + + for { + // q = r - m + q0, b = bits.Sub64(r0, m.arr[0], 0) + q1, b = bits.Sub64(r1, m.arr[1], b) + q2, b = bits.Sub64(r2, m.arr[2], b) + q3, b = bits.Sub64(r3, m.arr[3], b) + q4, b = bits.Sub64(r4, 0, b) + + // if borrow break + if b != 0 { + break + } + + // r = q + r4, r3, r2, r1, r0 = q4, q3, q2, q1, q0 + } + + z.arr[3], z.arr[2], z.arr[1], z.arr[0] = r3, r2, r1, r0 + + return z +} diff --git a/_deploy/p/gnoswap/uint256/uint256.gno b/_deploy/p/gnoswap/uint256/uint256.gno new file mode 100644 index 000000000..35a399f22 --- /dev/null +++ b/_deploy/p/gnoswap/uint256/uint256.gno @@ -0,0 +1,291 @@ +// Ported from https://github.com/holiman/uint256 +// This package provides a 256-bit unsigned integer type, Uint256, and associated functions. +package uint256 + +import ( + "errors" + "strconv" + "math/bits" +) + +const ( + MaxUint64 = 1<<64 - 1 +) + +// Uint is represented as an array of 4 uint64, in little-endian order, +// so that Uint[3] is the most significant, and Uint[0] is the least significant +type Uint struct { + arr [4]uint64 +} + +// NewUint returns a new initialized Uint. +func NewUint(val uint64) *Uint { + z := &Uint{arr: [4]uint64{val, 0, 0, 0}} + return z +} + +// Zero returns a new Uint initialized to zero. +func Zero() *Uint { + return NewUint(0) +} + +// One returns a new Uint initialized to one. +func One() *Uint { + return NewUint(1) +} + +// SetAllOne sets all the bits of z to 1 +func (z *Uint) SetAllOne() *Uint { + z.arr[3], z.arr[2], z.arr[1], z.arr[0] = MaxUint64, MaxUint64, MaxUint64, MaxUint64 + return z +} + +// Set sets z to x and returns z. +func (z *Uint) Set(x *Uint) *Uint { + *z = *x + + return z +} + +// SetOne sets z to 1 +func (z *Uint) SetOne() *Uint { + z.arr[3], z.arr[2], z.arr[1], z.arr[0] = 0, 0, 0, 1 + return z +} + +const twoPow256Sub1 = "115792089237316195423570985008687907853269984665640564039457584007913129639935" + +// SetFromDecimal sets z from the given string, interpreted as a decimal number. +// OBS! This method is _not_ strictly identical to the (*big.Uint).SetString(..., 10) method. +// Notable differences: +// - This method does not accept underscore input, e.g. "100_000", +// - This method does not accept negative zero as valid, e.g "-0", +// - (this method does not accept any negative input as valid)) +func (z *Uint) SetFromDecimal(s string) (err error) { + // Remove max one leading + + if len(s) > 0 && s[0] == '+' { + s = s[1:] + } + // Remove any number of leading zeroes + if len(s) > 0 && s[0] == '0' { + var i int + var c rune + for i, c = range s { + if c != '0' { + break + } + } + s = s[i:] + } + if len(s) < len(twoPow256Sub1) { + return z.fromDecimal(s) + } + if len(s) == len(twoPow256Sub1) { + if s > twoPow256Sub1 { + return ErrBig256Range + } + return z.fromDecimal(s) + } + return ErrBig256Range +} + +// FromDecimal is a convenience-constructor to create an Uint from a +// decimal (base 10) string. Numbers larger than 256 bits are not accepted. +func FromDecimal(decimal string) (*Uint, error) { + var z Uint + if err := z.SetFromDecimal(decimal); err != nil { + return nil, err + } + return &z, nil +} + +// MustFromDecimal is a convenience-constructor to create an Uint from a +// decimal (base 10) string. +// Returns a new Uint and panics if any error occurred. +func MustFromDecimal(decimal string) *Uint { + var z Uint + if err := z.SetFromDecimal(decimal); err != nil { + panic(err) + } + return &z +} + +// multipliers holds the values that are needed for fromDecimal +var multipliers = [5]*Uint{ + nil, // represents first round, no multiplication needed + {[4]uint64{10000000000000000000, 0, 0, 0}}, // 10 ^ 19 + {[4]uint64{687399551400673280, 5421010862427522170, 0, 0}}, // 10 ^ 38 + {[4]uint64{5332261958806667264, 17004971331911604867, 2938735877055718769, 0}}, // 10 ^ 57 + {[4]uint64{0, 8607968719199866880, 532749306367912313, 1593091911132452277}}, // 10 ^ 76 +} + +// fromDecimal is a helper function to only ever be called via SetFromDecimal +// this function takes a string and chunks it up, calling ParseUint on it up to 5 times +// these chunks are then multiplied by the proper power of 10, then added together. +func (z *Uint) fromDecimal(bs string) error { + // first clear the input + z.Clear() + // the maximum value of uint64 is 18446744073709551615, which is 20 characters + // one less means that a string of 19 9's is always within the uint64 limit + var ( + num uint64 + err error + remaining = len(bs) + ) + if remaining == 0 { + return errors.New("EOF") + } + // We proceed in steps of 19 characters (nibbles), from least significant to most significant. + // This means that the first (up to) 19 characters do not need to be multiplied. + // In the second iteration, our slice of 19 characters needs to be multipleied + // by a factor of 10^19. Et cetera. + for i, mult := range multipliers { + if remaining <= 0 { + return nil // Done + } else if remaining > 19 { + num, err = strconv.ParseUint(bs[remaining-19:remaining], 10, 64) + } else { + // Final round + num, err = strconv.ParseUint(bs, 10, 64) + } + if err != nil { + return err + } + // add that number to our running total + if i == 0 { + z.SetUint64(num) + } else { + base := NewUint(num) + z.Add(z, base.Mul(base, mult)) + } + // Chop off another 19 characters + if remaining > 19 { + bs = bs[0 : remaining-19] + } + remaining -= 19 + } + return nil +} + +// Byte sets z to the value of the byte at position n, +// with 'z' considered as a big-endian 32-byte integer +// if 'n' > 32, f is set to 0 +// Example: f = '5', n=31 => 5 +func (z *Uint) Byte(n *Uint) *Uint { + // in z, z.arr[0] is the least significant + if number, overflow := n.Uint64WithOverflow(); !overflow { + if number < 32 { + number := z.arr[4-1-number/8] + offset := (n.arr[0] & 0x7) << 3 // 8*(n.d % 8) + z.arr[0] = (number & (0xff00000000000000 >> offset)) >> (56 - offset) + z.arr[3], z.arr[2], z.arr[1] = 0, 0, 0 + return z + } + } + + return z.Clear() +} + +// BitLen returns the number of bits required to represent z +func (z *Uint) BitLen() int { + switch { + case z.arr[3] != 0: + return 192 + bits.Len64(z.arr[3]) + case z.arr[2] != 0: + return 128 + bits.Len64(z.arr[2]) + case z.arr[1] != 0: + return 64 + bits.Len64(z.arr[1]) + default: + return bits.Len64(z.arr[0]) + } +} + +// ByteLen returns the number of bytes required to represent z +func (z *Uint) ByteLen() int { + return (z.BitLen() + 7) / 8 +} + +// Clear sets z to 0 +func (z *Uint) Clear() *Uint { + z.arr[3], z.arr[2], z.arr[1], z.arr[0] = 0, 0, 0, 0 + return z +} + +const ( + // hextable = "0123456789abcdef" + bintable = "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00\x01\x02\x03\x04\x05\x06\a\b\t\xff\xff\xff\xff\xff\xff\xff\n\v\f\r\x0e\x0f\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\n\v\f\r\x0e\x0f\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" + badNibble = 0xff +) + +// SetFromHex sets z from the given string, interpreted as a hexadecimal number. +// OBS! This method is _not_ strictly identical to the (*big.Int).SetString(..., 16) method. +// Notable differences: +// - This method _require_ "0x" or "0X" prefix. +// - This method does not accept zero-prefixed hex, e.g. "0x0001" +// - This method does not accept underscore input, e.g. "100_000", +// - This method does not accept negative zero as valid, e.g "-0x0", +// - (this method does not accept any negative input as valid) +func (z *Uint) SetFromHex(hex string) error { + return z.fromHex(hex) +} + +// fromHex is the internal implementation of parsing a hex-string. +func (z *Uint) fromHex(hex string) error { + if err := checkNumberS(hex); err != nil { + return err + } + if len(hex) > 66 { + return ErrBig256Range + } + z.Clear() + end := len(hex) + for i := 0; i < 4; i++ { + start := end - 16 + if start < 2 { + start = 2 + } + for ri := start; ri < end; ri++ { + nib := bintable[hex[ri]] + if nib == badNibble { + return ErrSyntax + } + z.arr[i] = z.arr[i] << 4 + z.arr[i] += uint64(nib) + } + end = start + } + return nil +} + +// FromHex is a convenience-constructor to create an Uint from +// a hexadecimal string. The string is required to be '0x'-prefixed +// Numbers larger than 256 bits are not accepted. +func FromHex(hex string) (*Uint, error) { + var z Uint + if err := z.fromHex(hex); err != nil { + return nil, err + } + return &z, nil +} + +// MustFromHex is a convenience-constructor to create an Uint from +// a hexadecimal string. +// Returns a new Uint and panics if any error occurred. +func MustFromHex(hex string) *Uint { + var z Uint + if err := z.fromHex(hex); err != nil { + panic(err) + } + return &z +} + +// Clone creates a new Uint identical to z +func (z *Uint) Clone() *Uint { + var x Uint + x.arr[0] = z.arr[0] + x.arr[1] = z.arr[1] + x.arr[2] = z.arr[2] + x.arr[3] = z.arr[3] + + return &x +} diff --git a/_deploy/p/gnoswap/uint256/utils.gno b/_deploy/p/gnoswap/uint256/utils.gno new file mode 100644 index 000000000..bcc7bb283 --- /dev/null +++ b/_deploy/p/gnoswap/uint256/utils.gno @@ -0,0 +1,20 @@ +package uint256 + +func checkNumberS(input string) error { + const fn = "UnmarshalText" + l := len(input) + if l == 0 { + return errEmptyString(fn, input) + } + if l < 2 || input[0] != '0' || + (input[1] != 'x' && input[1] != 'X') { + return errMissingPrefix(fn, input) + } + if l == 2 { + return errEmptyNumber(fn, input) + } + if len(input) > 3 && input[2] == '0' { + return errLeadingZero(fn, input) + } + return nil +} diff --git a/_deploy/r/gnoswap/common/access.gno b/_deploy/r/gnoswap/common/access.gno new file mode 100644 index 000000000..0297c4a60 --- /dev/null +++ b/_deploy/r/gnoswap/common/access.gno @@ -0,0 +1,108 @@ +package common + +import ( + "std" + + "gno.land/p/demo/ufmt" + "gno.land/r/gnoswap/v1/consts" +) + +const ( + ErrNoPermission = "caller(%s) has no permission" +) + +// AssertCaller checks if the caller is the given address. +func AssertCaller(caller, addr std.Address) error { + if caller != addr { + return ufmt.Errorf(ErrNoPermission, caller.String()) + } + return nil +} + +func SatisfyCond(cond bool) error { + if !cond { + return ufmt.Errorf("given condition is not satisfied the permission check") + } + return nil +} + +// AdminOnly checks if the caller is the admin. +func AdminOnly(caller std.Address) error { + if caller != consts.ADMIN { + return ufmt.Errorf(ErrNoPermission, caller.String()) + } + return nil +} + +// GovernanceOnly checks if the caller is the gov governance contract. +func GovernanceOnly(caller std.Address) error { + if caller != consts.GOV_GOVERNANCE_ADDR { + return ufmt.Errorf(ErrNoPermission, caller.String()) + } + return nil +} + +// GovStakerOnly checks if the caller is the gov staker contract. +func GovStakerOnly(caller std.Address) error { + if caller != consts.GOV_STAKER_ADDR { + return ufmt.Errorf(ErrNoPermission, caller.String()) + } + return nil +} + +// RouterOnly checks if the caller is the router contract. +func RouterOnly(caller std.Address) error { + if caller != consts.ROUTER_ADDR { + return ufmt.Errorf(ErrNoPermission, caller.String()) + } + return nil +} + +// PositionOnly checks if the caller is the position contract. +func PositionOnly(caller std.Address) error { + if caller != consts.POSITION_ADDR { + return ufmt.Errorf(ErrNoPermission, caller.String()) + } + return nil +} + +// StakerOnly checks if the caller is the staker contract. +func StakerOnly(caller std.Address) error { + if caller != consts.STAKER_ADDR { + return ufmt.Errorf(ErrNoPermission, caller.String()) + } + return nil +} + +// LaunchpadOnly checks if the caller is the launchpad contract. +func LaunchpadOnly(caller std.Address) error { + if caller != consts.LAUNCHPAD_ADDR { + return ufmt.Errorf(ErrNoPermission, caller.String()) + } + return nil +} + +// EmissionOnly checks if the caller is the emission contract. +func EmissionOnly(caller std.Address) error { + if caller != consts.EMISSION_ADDR { + return ufmt.Errorf(ErrNoPermission, caller.String()) + } + return nil +} + +// DEPRECATED +// TODO: remove after r/grc20reg is applied for all contracts +func TokenRegisterOnly(caller std.Address) error { + if caller != consts.TOKEN_REGISTER { + return ufmt.Errorf(ErrNoPermission, caller.String()) + } + return nil +} + +// UserOnly checks if the caller is a user. +func UserOnly(prev std.Realm) error { + if !prev.IsUser() { + return ufmt.Errorf("caller(%s) is not a user", prev.PkgPath()) + } + return nil +} diff --git a/_deploy/r/gnoswap/common/access_test.gno b/_deploy/r/gnoswap/common/access_test.gno new file mode 100644 index 000000000..0c1dd91b5 --- /dev/null +++ b/_deploy/r/gnoswap/common/access_test.gno @@ -0,0 +1,136 @@ +package common + +import ( + "std" + "testing" + + "gno.land/p/demo/testutils" + "gno.land/p/demo/uassert" + + "gno.land/r/gnoswap/v1/consts" +) + +var ( + addr01 = testutils.TestAddress("addr01") + addr02 = testutils.TestAddress("addr02") +) + +func TestAssertCaller(t *testing.T) { + t.Run("same caller", func(t *testing.T) { + uassert.NoError(t, AssertCaller(addr01, addr01)) + }) + + t.Run("different caller", func(t *testing.T) { + uassert.Error(t, AssertCaller(addr01, addr02)) + }) +} + +func TestSatisfyCond(t *testing.T) { + t.Run("true", func(t *testing.T) { + uassert.NoError(t, SatisfyCond(true)) + }) + + t.Run("false", func(t *testing.T) { + uassert.Error(t, SatisfyCond(false)) + }) +} + +func TestAdminOnly(t *testing.T) { + t.Run("caller is admin", func(t *testing.T) { + uassert.NoError(t, AdminOnly(consts.ADMIN)) + }) + + t.Run("caller is not admin", func(t *testing.T) { + uassert.Error(t, AdminOnly(addr01)) + }) +} + +func TestGovernanceOnly(t *testing.T) { + t.Run("caller is governance", func(t *testing.T) { + uassert.NoError(t, GovernanceOnly(consts.GOV_GOVERNANCE_ADDR)) + }) + + t.Run("caller is not governance", func(t *testing.T) { + uassert.Error(t, GovernanceOnly(addr01)) + }) +} + +func TestGovStakerOnly(t *testing.T) { + t.Run("caller is gov staker", func(t *testing.T) { + uassert.NoError(t, GovStakerOnly(consts.GOV_STAKER_ADDR)) + }) + + t.Run("caller is not gov staker", func(t *testing.T) { + uassert.Error(t, GovStakerOnly(addr01)) + }) +} + +func TestRouterOnly(t *testing.T) { + t.Run("caller is router", func(t *testing.T) { + uassert.NoError(t, RouterOnly(consts.ROUTER_ADDR)) + }) + + t.Run("caller is not router", func(t *testing.T) { + uassert.Error(t, RouterOnly(addr01)) + }) +} + +func TestPositionOnly(t *testing.T) { + t.Run("caller is position", func(t *testing.T) { + uassert.NoError(t, PositionOnly(consts.POSITION_ADDR)) + }) + + t.Run("caller is not position", func(t *testing.T) { + uassert.Error(t, PositionOnly(addr01)) + }) +} + +func TestStakerOnly(t *testing.T) { + t.Run("caller is staker", func(t *testing.T) { + uassert.NoError(t, StakerOnly(consts.STAKER_ADDR)) + }) + + t.Run("caller is not staker", func(t *testing.T) { + uassert.Error(t, StakerOnly(addr01)) + }) +} + +func TestLaunchpadOnly(t *testing.T) { + t.Run("caller is launchpad", func(t *testing.T) { + uassert.NoError(t, LaunchpadOnly(consts.LAUNCHPAD_ADDR)) + }) + + t.Run("caller is not launchpad", func(t *testing.T) { + uassert.Error(t, LaunchpadOnly(addr01)) + }) +} + +func TestEmissionOnly(t *testing.T) { + t.Run("caller is emission", func(t *testing.T) { + uassert.NoError(t, EmissionOnly(consts.EMISSION_ADDR)) + }) + + t.Run("caller is not emission", func(t *testing.T) { + uassert.Error(t, EmissionOnly(addr01)) + }) +} + +func TestTokenRegisterOnly(t *testing.T) { + t.Run("caller is token register", func(t *testing.T) { + uassert.NoError(t, TokenRegisterOnly(consts.TOKEN_REGISTER)) + }) + + t.Run("caller is not token register", func(t *testing.T) { + uassert.Error(t, TokenRegisterOnly(addr01)) + }) +} + +func TestUserOnly(t *testing.T) { + t.Run("caller is user", func(t *testing.T) { + uassert.NoError(t, UserOnly(std.NewUserRealm(addr01))) + }) + + t.Run("caller is not user", func(t *testing.T) { + uassert.Error(t, UserOnly(std.NewCodeRealm("gno.land/r/realm"))) + }) +} diff --git a/_deploy/r/gnoswap/common/address_and_username.gno b/_deploy/r/gnoswap/common/address_and_username.gno new file mode 100644 index 000000000..bc8de54bb --- /dev/null +++ b/_deploy/r/gnoswap/common/address_and_username.gno @@ -0,0 +1,29 @@ +package common + +import ( + "std" + + pusers "gno.land/p/demo/users" + "gno.land/r/demo/users" +) + +// AddrToUser converts a type from address to AddressOrName. +// It panics if the address is invalid. +func AddrToUser(addr std.Address) pusers.AddressOrName { + AssertValidAddr(addr) + return pusers.AddressOrName(addr) +} + +// UserToAddr converts a type from AddressOrName to address. +// by resolving the user through the users realms. +func UserToAddr(user pusers.AddressOrName) std.Address { + return users.Resolve(user) +} + +// AssertValidAddr checks if the given address is valid. +// It panics with a detailed error message if the address is invalid. +func AssertValidAddr(addr std.Address) { + if !addr.IsValid() { + panic(newErrorWithDetail(errInvalidAddr, addr.String())) + } +} diff --git a/_deploy/r/gnoswap/common/address_and_username_test.gno b/_deploy/r/gnoswap/common/address_and_username_test.gno new file mode 100644 index 000000000..d988f047f --- /dev/null +++ b/_deploy/r/gnoswap/common/address_and_username_test.gno @@ -0,0 +1,115 @@ +package common + +import ( + "std" + "testing" + + "gno.land/p/demo/uassert" + pusers "gno.land/p/demo/users" +) + +func TestAddrToUser(t *testing.T) { + tests := []struct { + name string + addr std.Address + want pusers.AddressOrName + shouldPanic bool + panicMsg string + }{ + { + name: "valid address", + addr: std.Address("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5"), + want: pusers.AddressOrName("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5"), + }, + { + name: "empty address", + addr: std.Address(""), + shouldPanic: true, + panicMsg: `[GNOSWAP-COMMON-005] invalid address || `, + }, + { + name: "invalid address", + addr: std.Address("invalid"), + shouldPanic: true, + panicMsg: `[GNOSWAP-COMMON-005] invalid address || invalid`, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.shouldPanic { + uassert.PanicsWithMessage(t, tt.panicMsg, func() { + AddrToUser(tt.addr) + }) + } else { + got := AddrToUser(tt.addr) + if got != tt.want { + t.Errorf("AddrToUser() = %v, want %v", got, tt.want) + } + } + }) + } +} + +func TestUserToAddr(t *testing.T) { + tests := []struct { + name string + user pusers.AddressOrName + want std.Address + }{ + { + name: "address string with user type", + user: pusers.AddressOrName("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5"), + want: std.Address("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5"), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := UserToAddr(tt.user) + if got != tt.want { + t.Errorf("UserToAddr() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestAssertValidAddr(t *testing.T) { + tests := []struct { + name string + addr std.Address + shouldPanic bool + panicMsg string + }{ + { + name: "valid address", + addr: std.Address("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5"), + }, + { + name: "empty address", + addr: std.Address(""), + shouldPanic: true, + panicMsg: `[GNOSWAP-COMMON-005] invalid address || `, + }, + { + name: "invalid address", + addr: std.Address("invalid"), + shouldPanic: true, + panicMsg: `[GNOSWAP-COMMON-005] invalid address || invalid`, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.shouldPanic { + uassert.PanicsWithMessage(t, tt.panicMsg, func() { + AssertValidAddr(tt.addr) + }) + } else { + uassert.NotPanics(t, func() { + AssertValidAddr(tt.addr) + }) + } + }) + } +} diff --git a/_deploy/r/gnoswap/common/errors.gno b/_deploy/r/gnoswap/common/errors.gno new file mode 100644 index 000000000..f96124477 --- /dev/null +++ b/_deploy/r/gnoswap/common/errors.gno @@ -0,0 +1,32 @@ +package common + +import ( + "errors" + + "gno.land/p/demo/ufmt" +) + +var ( + errNoPermission = errors.New("[GNOSWAP-COMMON-001] caller has no permission") + errHalted = errors.New("[GNOSWAP-COMMON-002] halted") + errOutOfRange = errors.New("[GNOSWAP-COMMON-003] value out of range") + errNotRegistered = errors.New("[GNOSWAP-COMMON-004] token is not registered") + errInvalidAddr = errors.New("[GNOSWAP-COMMON-005] invalid address") + errOverflow = errors.New("[GNOSWAP-COMMON-006] overflow") + errInvalidTokenId = errors.New("[GNOSWAP-COMMON-007] invalid tokenId") + errInvalidInput = errors.New("[GNOSWAP-COMMON-008] invalid input data") + errOverFlow = errors.New("[GNOSWAP-COMMON-009] overflow") + errIdenticalTicks = errors.New("[GNOSWAP-COMMON-010] identical ticks") +) + +// newErrorWithDetail appends additional context or details to an existing error message. +// +// Parameters: +// - err: The original error (error). +// - detail: Additional context or detail to append to the error message (string). +// +// Returns: +// - string: The combined error message in the format " || ". +func newErrorWithDetail(err error, detail string) string { + return ufmt.Errorf("%s || %s", err.Error(), detail).Error() +} diff --git a/_deploy/r/gnoswap/common/gno.mod b/_deploy/r/gnoswap/common/gno.mod new file mode 100644 index 000000000..ad0e3a333 --- /dev/null +++ b/_deploy/r/gnoswap/common/gno.mod @@ -0,0 +1 @@ +module gno.land/r/gnoswap/v1/common diff --git a/_deploy/r/gnoswap/common/grc20reg_helper.gno b/_deploy/r/gnoswap/common/grc20reg_helper.gno new file mode 100644 index 000000000..5f521a857 --- /dev/null +++ b/_deploy/r/gnoswap/common/grc20reg_helper.gno @@ -0,0 +1,100 @@ +package common + +import ( + "regexp" + "std" + "strings" + + "gno.land/p/demo/grc/grc20" + "gno.land/p/demo/ufmt" + "gno.land/r/demo/grc20reg" +) + +var ( + re = regexp.MustCompile(`\[gno\.land/r/[^\]]+\]`) +) + +// GetToken returns a grc20.Token instance +// if token is not registered, it will panic +// token instance supports following methods: +// - GetName +// - GetSymbol +// - GetDecimals +// - TotalSupply +// - KnownAccounts +// - BalanceOf +// - Allowance +// - RenderHome +func GetToken(path string) *grc20.Token { + tokenGetter := grc20reg.MustGet(path) // if token is not registered, it will panic + + return tokenGetter() +} + +// GetTokenTeller returns a grc20.Teller instance +// if token is not registered, it will panic +// teller instance supports following methods: +// - Transfer +// - Approve +// - TransferFrom +func GetTokenTeller(path string) grc20.Teller { + tokenGetter := grc20reg.MustGet(path) // if token is not registered, it will panic + token := tokenGetter() + return token.CallerTeller() +} + +// IsRegistered returns nil if token is registered to grc20reg +// otherwise, it returns an error +func IsRegistered(path string) error { + getter := grc20reg.Get(path) + if getter == nil { + return ufmt.Errorf("token(%s) is not registered to grc20reg", path) + } + return nil +} + +// MustRegistered is a helper function to check if token is registered to grc20reg +// if token is not registered, it will panic +func MustRegistered(path string) { + if err := IsRegistered(path); err != nil { + panic(newErrorWithDetail( + errNotRegistered, + ufmt.Sprintf("token(%s)", path), + )) + } +} + +// ListRegisteredTokens returns the list of registered tokens +// NOTE: +// - Unfortunate, grc20reg doesn't support this. +// - We need to parse the rendered grc20reg page to get the list of registered tokens. +func ListRegisteredTokens() []string { + render := grc20reg.Render("") + return extractTokenPathsFromRender(render) +} + +func extractTokenPathsFromRender(render string) []string { + matches := re.FindAllString(render, -1) + + tokenPaths := make([]string, 0, len(matches)) + for _, match := range matches { + tokenPath := strings.Trim(match, "[]") // Remove the brackets + tokenPaths = append(tokenPaths, tokenPath) + } + return tokenPaths +} + +// TotalSupply returns the total supply of the token +func TotalSupply(path string) uint64 { + return GetToken(path).TotalSupply() +} + +// BalanceOf returns the balance of the token for the given address +func BalanceOf(path string, addr std.Address) uint64 { + return GetToken(path).BalanceOf(addr) +} + +// Allowance returns the allowance of the token for the given owner and spender +func Allowance(path string, owner, spender std.Address) uint64 { + return GetToken(path).Allowance(owner, spender) +} diff --git a/_deploy/r/gnoswap/common/grc20reg_helper_test.gno b/_deploy/r/gnoswap/common/grc20reg_helper_test.gno new file mode 100644 index 000000000..cbb0707f2 --- /dev/null +++ b/_deploy/r/gnoswap/common/grc20reg_helper_test.gno @@ -0,0 +1,261 @@ +package common + +import ( + "std" + "testing" + + "gno.land/p/demo/testutils" + "gno.land/p/demo/uassert" + "gno.land/p/demo/ufmt" + + "gno.land/r/demo/foo20" +) + +var ( + tokenPath = "gno.land/r/demo/foo20" +) + +func TestGetToken(t *testing.T) { + t.Run("get regsitered token", func(t *testing.T) { + token := GetToken(tokenPath) + if token == nil { + t.Error("Expected non-nil token for foo20") + } + }) + + t.Run("get non registered token", func(t *testing.T) { + defer func() { + if r := recover(); r == nil { + t.Errorf("Expected panic for non-registered token") + } + }() + GetToken("not_registered_token") + }) +} + +func TestTokenMethod(t *testing.T) { + token := GetToken(tokenPath) + + t.Run("GetName()", func(t *testing.T) { + uassert.Equal(t, "Foo", token.GetName()) + }) + + t.Run("GetSymbol()", func(t *testing.T) { + uassert.Equal(t, "FOO", token.GetSymbol()) + }) + + t.Run("GetDecimals()", func(t *testing.T) { + uassert.Equal(t, uint(4), token.GetDecimals()) + }) + + t.Run("TotalSupply()", func(t *testing.T) { + uassert.Equal(t, uint64(10000000000), token.TotalSupply()) + }) + + t.Run("KnownAccounts()", func(t *testing.T) { + uassert.Equal(t, int(1), token.KnownAccounts()) + }) + + t.Run("BalanceOf()", func(t *testing.T) { + uassert.Equal(t, uint64(10000000000), token.BalanceOf(std.Address("g1manfred47kzduec920z88wfr64ylksmdcedlf5"))) + }) + + t.Run("Allowance()", func(t *testing.T) { + uassert.Equal(t, uint64(0), token.Allowance(std.Address("g1manfred47kzduec920z88wfr64ylksmdcedlf5"), std.Address("g1pf6dv9fjk3rn0m4jjcne306ga4he3mzmupfjl6"))) + }) + + t.Run("RenderHome()", func(t *testing.T) { + expected := "" + expected += ufmt.Sprintf("# %s ($%s)\n\n", "Foo", "FOO") + expected += ufmt.Sprintf("* **Decimals**: %d\n", 4) + expected += ufmt.Sprintf("* **Total supply**: %d\n", 10000000000) + expected += ufmt.Sprintf("* **Known accounts**: %d\n", 1) + uassert.Equal(t, expected, token.RenderHome()) + }) +} + +func TestGetTokenTeller(t *testing.T) { + t.Run("get registered token teller", func(t *testing.T) { + teller := GetTokenTeller(tokenPath) + if teller == nil { + t.Error("Expected non-nil teller for foo20") + } + }) + + t.Run("get non registered token teller", func(t *testing.T) { + defer func() { + if r := recover(); r == nil { + t.Errorf("Expected panic for non-registered token teller") + } + }() + GetTokenTeller("not_registered_teller") + }) +} + +func TestTellerMethod(t *testing.T) { + teller := GetTokenTeller(tokenPath) + token := GetToken(tokenPath) + defaultHolder := std.Address("g1manfred47kzduec920z88wfr64ylksmdcedlf5") + addr01 := testutils.TestAddress("addr01") + + t.Run("Transfer()", func(t *testing.T) { + std.TestSetRealm(std.NewUserRealm(defaultHolder)) + + uassert.Equal(t, uint64(10000000000), token.BalanceOf(defaultHolder)) + + uassert.NoError(t, teller.Transfer(addr01, uint64(10000000000))) // transfer all balance to addr01 + + uassert.Equal(t, uint64(0), token.BalanceOf(defaultHolder)) + uassert.Equal(t, uint64(10000000000), token.BalanceOf(addr01)) + + uassert.Error(t, teller.Transfer(addr01, uint64(10000000000))) // not enough balance + }) + + t.Run("Approve()", func(t *testing.T) { + std.TestSetRealm(std.NewUserRealm(addr01)) + uassert.NoError(t, teller.Approve(defaultHolder, uint64(500))) + uassert.Equal(t, uint64(500), token.Allowance(addr01, defaultHolder)) + }) + + t.Run("TransferFrom()", func(t *testing.T) { + std.TestSetRealm(std.NewUserRealm(defaultHolder)) + uassert.NoError(t, teller.TransferFrom(addr01, defaultHolder, uint64(500))) + uassert.Equal(t, uint64(9999999500), token.BalanceOf(addr01)) + uassert.Equal(t, uint64(500), token.BalanceOf(defaultHolder)) + uassert.Equal(t, uint64(0), token.Allowance(addr01, defaultHolder)) + + uassert.Error(t, teller.TransferFrom(addr01, defaultHolder, uint64(500))) // not enough allowance + }) +} + +func TestIsRegistered(t *testing.T) { + t.Run("check if token is registered", func(t *testing.T) { + uassert.NoError(t, IsRegistered(tokenPath)) + }) + + t.Run("check if token is not registered", func(t *testing.T) { + uassert.Error(t, IsRegistered("not_registered_token")) + }) +} + +func TestMustRegistered(t *testing.T) { + t.Run("must be registered", func(t *testing.T) { + MustRegistered(tokenPath) + }) + + t.Run("panic for non registered token", func(t *testing.T) { + defer func() { + if r := recover(); r == nil { + t.Errorf("Expected panic for non-registered token") + } + }() + MustRegistered("not_registered") + }) +} + +func TestListRegisteredTokens(t *testing.T) { + t.Skip("skipping tests -> can not mock grc20reg.Render() || testing extractTokenPathsFromRender() does cover this") +} + +func TestExtractTokenPathsFromRender(t *testing.T) { + var ( + wugnotPath = "gno.land/r/demo/wugnot" + gnsPath = "gno.land/r/gnoswap/v1/gns" + fooPath = "gno.land/r/onbloc/foo" + quxPath = "gno.land/r/onbloc/qux" + ) + + // NOTE: following strings are return from grc20reg.Render() + renderList := []string{ + // no registered token + `No registered token.`, + + // 1 token + `- **wrapped GNOT** - [gno.land/r/demo/wugnot](/r/demo/wugnot) - [info](/r/demo/grc20reg:gno.land/r/demo/wugnot)`, + + // 2 tokens + `- **wrapped GNOT** - [gno.land/r/demo/wugnot](/r/demo/wugnot) - [info](/r/demo/grc20reg:gno.land/r/demo/wugnot) +- **Gnoswap** - [gno.land/r/gnoswap/v1/gns](/r/gnoswap/v1/gns) - [info](/r/demo/grc20reg:gno.land/r/gnoswap/v1/gns) +`, + + // 4 tokens + `- **wrapped GNOT** - [gno.land/r/demo/wugnot](/r/demo/wugnot) - [info](/r/demo/grc20reg:gno.land/r/demo/wugnot) +- **Gnoswap** - [gno.land/r/gnoswap/v1/gns](/r/gnoswap/v1/gns) - [info](/r/demo/grc20reg:gno.land/r/gnoswap/v1/gns) +- **Baz** - [gno.land/r/onbloc/foo](/r/onbloc/foo) - [info](/r/demo/grc20reg:gno.land/r/onbloc/foo) +- **Qux** - [gno.land/r/onbloc/qux](/r/onbloc/qux) - [info](/r/demo/grc20reg:gno.land/r/onbloc/qux) +`, + } + + tests := []struct { + name string + render string + expected []string + }{ + { + name: "no registered token", + render: renderList[0], + expected: []string{}, + }, + { + name: "1 registered token", + render: renderList[1], + expected: []string{wugnotPath}, + }, + { + name: "2 registered tokens", + render: renderList[2], + expected: []string{wugnotPath, gnsPath}, + }, + { + name: "4 registered tokens", + render: renderList[3], + expected: []string{wugnotPath, gnsPath, fooPath, quxPath}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + extracted := extractTokenPathsFromRender(tt.render) + uassert.True(t, areSlicesEqual(t, tt.expected, extracted)) + }) + } +} + +func TestTotalSupply(t *testing.T) { + // result from grc2reg and (direct import/call) should be the same + uassert.Equal(t, foo20.TotalSupply(), TotalSupply(tokenPath)) +} + +func TestBalanceOf(t *testing.T) { + defaultHolder := std.Address("g1manfred47kzduec920z88wfr64ylksmdcedlf5") + + // result from grc2reg and (direct import/call) should be the same + uassert.Equal(t, foo20.BalanceOf(AddrToUser(defaultHolder)), BalanceOf(tokenPath, defaultHolder)) +} + +func TestAllowance(t *testing.T) { + owner := std.Address("g1manfred47kzduec920z88wfr64ylksmdcedlf5") + spender := std.Address("g1pf6dv9fjk3rn0m4jjcne306ga4he3mzmupfjl6") + + // result from grc2reg and (direct import/call) should be the same + uassert.Equal(t, foo20.Allowance(AddrToUser(owner), AddrToUser(spender)), Allowance(tokenPath, owner, spender)) +} + +// areSlicesEqual compares two slices of strings +func areSlicesEqual(t *testing.T, a, b []string) bool { + t.Helper() + + // Check if lengths are different + if len(a) != len(b) { + return false + } + + // Compare each element + for i := range a { + if a[i] != b[i] { + return false + } + } + + return true +} diff --git a/_deploy/r/gnoswap/common/grc721_token_id.gno b/_deploy/r/gnoswap/common/grc721_token_id.gno new file mode 100644 index 000000000..033fb4991 --- /dev/null +++ b/_deploy/r/gnoswap/common/grc721_token_id.gno @@ -0,0 +1,41 @@ +package common + +import ( + "strconv" + + "gno.land/p/demo/ufmt" + + "gno.land/p/demo/grc/grc721" +) + +// TokenIdFrom converts tokenId to grc721.TokenID type +// NOTE: input parameter tokenId can be string, int, uint64, or grc721.TokenID +// if tokenId is nil or not supported, it will panic +// if input type is not supported, it will panic +// input: tokenId interface{} +// output: grc721.TokenID +func TokenIdFrom(tokenId interface{}) grc721.TokenID { + if tokenId == nil { + panic(newErrorWithDetail( + errInvalidTokenId, + "can not be nil", + )) + } + + switch tokenId.(type) { + case string: + return grc721.TokenID(tokenId.(string)) + case int: + return grc721.TokenID(strconv.Itoa(tokenId.(int))) + case uint64: + return grc721.TokenID(strconv.Itoa(int(tokenId.(uint64)))) + case grc721.TokenID: + return tokenId.(grc721.TokenID) + default: + estimatedType := ufmt.Sprintf("%T", tokenId) + panic(newErrorWithDetail( + errInvalidTokenId, + ufmt.Sprintf("unsupported tokenId type: %s", estimatedType), + )) + } +} diff --git a/_deploy/r/gnoswap/common/grc721_token_id_test.gno b/_deploy/r/gnoswap/common/grc721_token_id_test.gno new file mode 100644 index 000000000..6f15e955e --- /dev/null +++ b/_deploy/r/gnoswap/common/grc721_token_id_test.gno @@ -0,0 +1,64 @@ +package common + +import ( + "testing" + + "gno.land/p/demo/grc/grc721" +) + +func TestTokenIdFrom(t *testing.T) { + tests := []struct { + name string + input interface{} + want grc721.TokenID + wantPanic bool + }{ + { + name: "string input", + input: "123", + want: grc721.TokenID("123"), + }, + { + name: "int input", + input: 123, + want: grc721.TokenID("123"), + }, + { + name: "uint64 input", + input: uint64(123), + want: grc721.TokenID("123"), + }, + { + name: "grc721.TokenID input", + input: grc721.TokenID("123"), + want: grc721.TokenID("123"), + }, + { + name: "nil input", + input: nil, + wantPanic: true, + }, + { + name: "unsupported type (byte)", + input: []byte("123"), + wantPanic: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.wantPanic { + defer func() { + if r := recover(); r == nil { + t.Errorf("TokenIdFrom() should have panicked") + } + }() + } + + got := TokenIdFrom(tt.input) + if got != tt.want { + t.Errorf("TokenIdFrom() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/_deploy/r/gnoswap/common/halt.gno b/_deploy/r/gnoswap/common/halt.gno new file mode 100644 index 000000000..2ea37b471 --- /dev/null +++ b/_deploy/r/gnoswap/common/halt.gno @@ -0,0 +1,99 @@ +package common + +import ( + "std" + + "gno.land/p/demo/ufmt" + "gno.land/r/gnoswap/v1/consts" +) + +// halted is a global flag that indicates whether the GnoSwap is currently halted. +// When true, most operations are disabled to prevent further actions. +// Default value is false, meaning the GnoSwap is active by default. +var halted bool = false + +// GetHalt returns the current halted status of the GnoSwap. +// +// Returns: +// - bool: true if the GnoSwap is halted, false otherwise. +func GetHalt() bool { + return halted +} + +// IsHalted checks if the GnoSwap is currently halted. +// If the GnoSwap is halted, the function panics with an errHalted error. +// +// Panics: +// - If the halted flag is true, indicating that the GnoSwap is inactive. +func IsHalted() { + if halted { + panic(newErrorWithDetail( + errHalted, + "GnoSwap is halted", + )) + } +} + +// SetHaltByAdmin allows an admin to set the halt status of the GnoSwap. +// Only an admin can execute this function. If a non-admin attempts to call this function, +// the function panics with an errNoPermission error. +// +// Parameters: +// - halt (bool): The new halt status to set (true to halt, false to unhalt). +// +// Panics: +// - If the caller is not an admin, the function will panic with an errNoPermission error. +func SetHaltByAdmin(halt bool) { + caller := getPrevAddr() + if err := AdminOnly(caller); err != nil { + panic(newErrorWithDetail( + errNoPermission, + ufmt.Sprintf( + "only admin(%s) can set halt, called from %s", + consts.ADMIN, + caller, + ), + )) + } + setHalt(halt) +} + +// SetHalt allows the governance contract to set the halt status of the GnoSwap. +// Only the governance contract can execute this function through a proposal process. +// +// Parameters: +// - halt (bool): The new halt status to set (true to halt, false to unhalt). +// +// Panics: +// - If the caller is not the governance contract, the function will panic with an errNoPermission error. +func SetHalt(halt bool) { + caller := getPrevAddr() + if err := GovernanceOnly(caller); err != nil { + panic(newErrorWithDetail( + errNoPermission, + ufmt.Sprintf( + "only governance(%s) can set halt, called from %s", + consts.GOV_GOVERNANCE_ADDR, + caller, + ), + )) + } + setHalt(halt) +} + +// setHalt updates the halted flag to the specified value. +// This is an internal function that should only be called by SetHalt or SetHaltByAdmin. +// +// Parameters: +// - halt (bool): The new halt status to set. +func setHalt(halt bool) { + halted = halt + + prevAddr, prevRealm := getPrevAsString() + std.Emit( + "setHalt", + "prevAddr", prevAddr, + "prevRealm", prevRealm, + "halt", ufmt.Sprintf("%t", halt), + ) +} diff --git a/_deploy/r/gnoswap/common/halt_test.gno b/_deploy/r/gnoswap/common/halt_test.gno new file mode 100644 index 000000000..f048a43df --- /dev/null +++ b/_deploy/r/gnoswap/common/halt_test.gno @@ -0,0 +1,81 @@ +package common + +import ( + "std" + "testing" + + "gno.land/p/demo/uassert" + + "gno.land/r/gnoswap/v1/consts" +) + +var ( + adminRealm = std.NewUserRealm(consts.ADMIN) + govRealm = std.NewCodeRealm(consts.GOV_GOVERNANCE_PATH) +) + +func TestHalts(t *testing.T) { + t.Run("GetHalt() initial value", func(t *testing.T) { + uassert.False(t, GetHalt()) + }) + + t.Run("IsHalted() success", func(t *testing.T) { + uassert.NotPanics(t, IsHalted) + }) +} + +func TestSetHaltByAdmin(t *testing.T) { + t.Run("with non-admin privilege, panics", func(t *testing.T) { + uassert.PanicsWithMessage( + t, + `[GNOSWAP-COMMON-001] caller has no permission || only admin(g17290cwvmrapvp869xfnhhawa8sm9edpufzat7d) can set halt, called from g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm`, + func() { SetHaltByAdmin(false) }, + ) + }) + + t.Run("with governance privilege, panics", func(t *testing.T) { + std.TestSetRealm(govRealm) + uassert.PanicsWithMessage( + t, + `[GNOSWAP-COMMON-001] caller has no permission || only admin(g17290cwvmrapvp869xfnhhawa8sm9edpufzat7d) can set halt, called from g17s8w2ve7k85fwfnrk59lmlhthkjdted8whvqxd`, + func() { SetHaltByAdmin(false) }, + ) + }) + + t.Run("with admin privilege, success", func(t *testing.T) { + std.TestSetRealm(adminRealm) + + uassert.False(t, GetHalt()) + + SetHaltByAdmin(true) + uassert.True(t, GetHalt()) + }) +} + +func TestSetHalt(t *testing.T) { + t.Run("with non-governance privilege, panics", func(t *testing.T) { + uassert.PanicsWithMessage( + t, + `[GNOSWAP-COMMON-001] caller has no permission || only governance(g17s8w2ve7k85fwfnrk59lmlhthkjdted8whvqxd) can set halt, called from g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm`, + func() { SetHalt(false) }, + ) + }) + + t.Run("with admin privilege, panics", func(t *testing.T) { + std.TestSetRealm(adminRealm) + uassert.PanicsWithMessage( + t, + `[GNOSWAP-COMMON-001] caller has no permission || only governance(g17s8w2ve7k85fwfnrk59lmlhthkjdted8whvqxd) can set halt, called from g17290cwvmrapvp869xfnhhawa8sm9edpufzat7d`, + func() { SetHalt(false) }, + ) + }) + + t.Run("with governance privilege, success", func(t *testing.T) { + std.TestSetRealm(govRealm) + + uassert.True(t, GetHalt()) + + SetHalt(false) + uassert.False(t, GetHalt()) + }) +} diff --git a/_deploy/r/gnoswap/common/limit_caller.gno b/_deploy/r/gnoswap/common/limit_caller.gno new file mode 100644 index 000000000..c12d3611b --- /dev/null +++ b/_deploy/r/gnoswap/common/limit_caller.gno @@ -0,0 +1,52 @@ +package common + +import ( + "std" + + "gno.land/p/demo/ufmt" +) + +// limitCaller is a global boolean flag that controls whether function calls are restricted. +// Default value is true, meaning call restrictions are enabled by default. +var limitCaller bool = true + +// GetLimitCaller returns the current state of the limitCaller flag. +// If true, call restrictions are active; if false, call restrictions are disabled. +// +// Returns: +// - bool: Current state of the limitCaller (true if active, false if inactive). +func GetLimitCaller() bool { + return limitCaller +} + +// SetLimitCaller updates the limitCaller flag to either enable or disable call restrictions. +// This function can only be called by an admin. If a non-admin attempts to call this function, +// the function will panic. +// +// Parameters: +// - v (bool): The new state for the limitCaller flag (true to enable, false to disable). +// +// Panics: +// - If the caller is not an admin, the function panics with an errNoPermission error. +func SetLimitCaller(v bool) { + caller := getPrevAddr() + if err := AdminOnly(caller); err != nil { + panic(newErrorWithDetail( + errNoPermission, + ufmt.Sprintf( + "only Admin can set halt, called from %s", + caller, + )), + ) + } + + limitCaller = v + + prevAddr, prevPkgPath := getPrevAsString() + std.Emit( + "SetLimitCaller", + "prevAddr", prevAddr, + "prevRealm", prevPkgPath, + "limitCaller", ufmt.Sprintf("%t", v), + ) +} diff --git a/_deploy/r/gnoswap/common/limit_caller_test.gno b/_deploy/r/gnoswap/common/limit_caller_test.gno new file mode 100644 index 000000000..d8007140e --- /dev/null +++ b/_deploy/r/gnoswap/common/limit_caller_test.gno @@ -0,0 +1,28 @@ +package common + +import ( + "std" + "testing" + + "gno.land/p/demo/uassert" + "gno.land/r/gnoswap/v1/consts" +) + +func TestSetLimitCaller(t *testing.T) { + t.Run("initial check", func(t *testing.T) { + uassert.True(t, GetLimitCaller()) + }) + + t.Run("with non-admin privilege, panics", func(t *testing.T) { + uassert.PanicsWithMessage(t, + `[GNOSWAP-COMMON-001] caller has no permission || only Admin can set halt, called from g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm`, + func() { SetLimitCaller(false) }, + ) + }) + + t.Run("with admin privilege, success", func(t *testing.T) { + std.TestSetRealm(std.NewUserRealm(consts.ADMIN)) + SetLimitCaller(false) + uassert.False(t, GetLimitCaller()) + }) +} diff --git a/_deploy/r/gnoswap/common/liquidity_amounts.gno b/_deploy/r/gnoswap/common/liquidity_amounts.gno new file mode 100644 index 000000000..09be46b62 --- /dev/null +++ b/_deploy/r/gnoswap/common/liquidity_amounts.gno @@ -0,0 +1,314 @@ +package common + +import ( + "gno.land/p/demo/ufmt" + u256 "gno.land/p/gnoswap/uint256" + "gno.land/r/gnoswap/v1/consts" +) + +// toAscendingOrder checks if the first value is greater than +// the second then swaps two values. +func toAscendingOrder(a, b *u256.Uint) (*u256.Uint, *u256.Uint) { + if a.Gt(b) { + return b, a + } + + return a, b +} + +// toUint128 ensures a *u256.Uint value fits within the uint128 range. +// +// This function validates that the given `value` is properly initialized (not nil) and checks whether +// it exceeds the maximum value of uint128. If the value exceeds the uint128 range, +// it applies a masking operation to truncate the value to fit within the uint128 limit. +// +// Parameters: +// - value: *u256.Uint, the value to be checked and possibly truncated. +// +// Returns: +// - *u256.Uint: A value guaranteed to fit within the uint128 range. +// +// Notes: +// - The function first checks if the value is not nil to avoid potential runtime errors. +// - The mask ensures that only the lower 128 bits of the value are retained. +// - If the input value is already within the uint128 range, it remains unchanged. +// - MAX_UINT128 is a constant representing `2^128 - 1`. +func toUint128(value *u256.Uint) *u256.Uint { + assertOnlyNotNil(value) + if value.Gt(u256.MustFromDecimal(consts.MAX_UINT128)) { + mask := new(u256.Uint).Lsh(u256.One(), consts.Q128_RESOLUTION) + mask = new(u256.Uint).Sub(mask, u256.One()) + value = value.And(value, mask) + } + return value +} + +// safeConvertToUint128 safely ensures a *u256.Uint value fits within the uint128 range. +// +// This function verifies that the provided unsigned 256-bit integer does not exceed the maximum value for uint128 (`2^128 - 1`). +// If the value is within the uint128 range, it is returned as is; otherwise, the function triggers a panic. +// +// Parameters: +// - value (*u256.Uint): The unsigned 256-bit integer to be checked. +// +// Returns: +// - *u256.Uint: The same value if it is within the uint128 range. +// +// Panics: +// - If the value exceeds the maximum uint128 value (`2^128 - 1`), the function will panic with a descriptive error +// indicating the overflow and the original value. +// +// Notes: +// - The constant `MAX_UINT128` is defined as `340282366920938463463374607431768211455` (the largest uint128 value). +// - No actual conversion occurs since the function works directly with *u256.Uint types. +// +// Example: +// validUint128 := safeConvertToUint128(u256.MustFromDecimal("340282366920938463463374607431768211455")) // Valid +// safeConvertToUint128(u256.MustFromDecimal("340282366920938463463374607431768211456")) // Panics due to overflow +func safeConvertToUint128(value *u256.Uint) *u256.Uint { + if value.Gt(u256.MustFromDecimal(consts.MAX_UINT128)) { + panic(ufmt.Sprintf( + "%v: amount(%s) overflows uint128 range", + errOverFlow, value.ToString())) + } + return value +} + +// computeLiquidityForAmount0 calculates the liquidity for a given amount of token0. +// +// This function computes the maximum possible liquidity that can be provided for `token0` +// based on the provided price boundaries (sqrtRatioAX96 and sqrtRatioBX96) in Q64.96 format. +// +// Parameters: +// - sqrtRatioAX96: *u256.Uint - The square root price at the lower tick boundary (Q64.96). +// - sqrtRatioBX96: *u256.Uint - The square root price at the upper tick boundary (Q64.96). +// - amount0: *u256.Uint - The amount of token0 to be converted to liquidity. +// +// Returns: +// - *u256.Uint: The calculated liquidity, represented as an unsigned 128-bit integer (uint128). +// +// Panics: +// - If the resulting liquidity exceeds the uint128 range, `safeConvertToUint128` will trigger a panic. +func computeLiquidityForAmount0(sqrtRatioAX96, sqrtRatioBX96, amount0 *u256.Uint) *u256.Uint { + sqrtRatioAX96, sqrtRatioBX96 = toAscendingOrder(sqrtRatioAX96, sqrtRatioBX96) + intermediate := u256.MulDiv(sqrtRatioAX96, sqrtRatioBX96, u256.MustFromDecimal(consts.Q96)) + + diff := new(u256.Uint).Sub(sqrtRatioBX96, sqrtRatioAX96) + if diff.IsZero() { + panic(newErrorWithDetail( + errIdenticalTicks, + ufmt.Sprintf("sqrtRatioAX96 (%s) and sqrtRatioBX96 (%s) are identical", sqrtRatioAX96.ToString(), sqrtRatioBX96.ToString()), + )) + } + res := u256.MulDiv(amount0, intermediate, diff) + return safeConvertToUint128(res) +} + +// computeLiquidityForAmount1 calculates liquidity based on the provided token1 amount and price range. +// +// This function computes the liquidity for a given amount of token1 by using the difference +// between the upper and lower square root price ratios. The calculation uses Q96 fixed-point +// arithmetic to maintain precision. +// +// Parameters: +// - sqrtRatioAX96: *u256.Uint - The square root ratio of price at the lower tick, represented in Q96 format. +// - sqrtRatioBX96: *u256.Uint - The square root ratio of price at the upper tick, represented in Q96 format. +// - amount1: *u256.Uint - The amount of token1 to calculate liquidity for. +// +// Returns: +// - *u256.Uint: The calculated liquidity based on the provided amount of token1 and price range. +// +// Notes: +// - The result is not directly limited to uint128, as liquidity values can exceed uint128 bounds. +// - If `sqrtRatioAX96 == sqrtRatioBX96`, the function will panic due to division by zero. +// - Q96 is a constant representing `2^96`, ensuring that precision is maintained during division. +// +// Panics: +// - If the resulting liquidity exceeds the uint128 range, `safeConvertToUint128` will trigger a panic. +func computeLiquidityForAmount1(sqrtRatioAX96, sqrtRatioBX96, amount1 *u256.Uint) *u256.Uint { + sqrtRatioAX96, sqrtRatioBX96 = toAscendingOrder(sqrtRatioAX96, sqrtRatioBX96) + + diff := new(u256.Uint).Sub(sqrtRatioBX96, sqrtRatioAX96) + if diff.IsZero() { + panic(newErrorWithDetail( + errIdenticalTicks, + ufmt.Sprintf("sqrtRatioAX96 (%s) and sqrtRatioBX96 (%s) are identical", sqrtRatioAX96.ToString(), sqrtRatioBX96.ToString()), + )) + } + res := u256.MulDiv(amount1, u256.MustFromDecimal(consts.Q96), diff) + return safeConvertToUint128(res) +} + +// GetLiquidityForAmounts calculates the maximum liquidity given the current price (sqrtRatioX96), +// upper and lower price bounds (sqrtRatioAX96 and sqrtRatioBX96), and token amounts (amount0, amount1). +// +// This function evaluates how much liquidity can be obtained for specified amounts of token0 and token1 +// within the provided price range. It returns the lesser liquidity based on available token0 or token1 +// to ensure the pool remains balanced. +// +// Parameters: +// - sqrtRatioX96: The current price as a square root ratio in Q64.96 format (*u256.Uint). +// - sqrtRatioAX96: The lower bound of the price range as a square root ratio in Q64.96 format (*u256.Uint). +// - sqrtRatioBX96: The upper bound of the price range as a square root ratio in Q64.96 format (*u256.Uint). +// - amount0: The amount of token0 available to provide liquidity (*u256.Uint). +// - amount1: The amount of token1 available to provide liquidity (*u256.Uint). +// +// Returns: +// - *u256.Uint: The maximum possible liquidity that can be minted. +// +// Notes: +// - The `Clone` method is used to prevent modification of the original values during computation. +// - The function ensures that liquidity calculations handle edge cases when the current price +// is outside the specified range by returning liquidity based on the dominant token. +func GetLiquidityForAmounts(sqrtRatioX96, sqrtRatioAX96, sqrtRatioBX96, amount0, amount1 *u256.Uint) *u256.Uint { + if amount0.IsZero() || amount1.IsZero() { + return u256.Zero() + } + + sqrtRatioAX96, sqrtRatioBX96 = toAscendingOrder(sqrtRatioAX96.Clone(), sqrtRatioBX96.Clone()) + var liquidity *u256.Uint + + if sqrtRatioX96.Lte(sqrtRatioAX96) { + liquidity = computeLiquidityForAmount0(sqrtRatioAX96.Clone(), sqrtRatioBX96.Clone(), amount0.Clone()) + } else if sqrtRatioX96.Lt(sqrtRatioBX96) { + liquidity0 := computeLiquidityForAmount0(sqrtRatioX96.Clone(), sqrtRatioBX96.Clone(), amount0.Clone()) + liquidity1 := computeLiquidityForAmount1(sqrtRatioAX96.Clone(), sqrtRatioX96.Clone(), amount1.Clone()) + + if liquidity0.Lt(liquidity1) { + liquidity = liquidity0 + } else { + liquidity = liquidity1 + } + } else { + liquidity = computeLiquidityForAmount1(sqrtRatioAX96.Clone(), sqrtRatioBX96.Clone(), amount1.Clone()) + } + return liquidity +} + +// computeAmount0ForLiquidity calculates the required amount of token0 for a given liquidity level +// within a specified price range (represented by sqrt ratios). +// +// This function determines the amount of token0 needed to provide a specified amount of liquidity +// within a price range defined by sqrtRatioAX96 (lower bound) and sqrtRatioBX96 (upper bound). +// +// Parameters: +// - sqrtRatioAX96: The lower bound of the price range as a square root ratio in Q64.96 format (*u256.Uint). +// - sqrtRatioBX96: The upper bound of the price range as a square root ratio in Q64.96 format (*u256.Uint). +// - liquidity: The liquidity to be provided (*u256.Uint). +// +// Returns: +// - *u256.Uint: The amount of token0 required to achieve the specified liquidity level. +// +// Notes: +// - This function assumes the price bounds are expressed in Q64.96 fixed-point format. +// - The function returns 0 if the liquidity is 0 or the price bounds are invalid. +// - Handles edge cases where sqrtRatioAX96 equals sqrtRatioBX96 by returning 0 (to prevent division by zero). +func computeAmount0ForLiquidity(sqrtRatioAX96, sqrtRatioBX96, liquidity *u256.Uint) *u256.Uint { + sqrtRatioAX96, sqrtRatioBX96 = toAscendingOrder(sqrtRatioAX96, sqrtRatioBX96) + if sqrtRatioAX96.IsZero() || sqrtRatioBX96.IsZero() || liquidity.IsZero() || sqrtRatioAX96.Eq(sqrtRatioBX96) { + return u256.Zero() + } + + val1 := new(u256.Uint).Lsh(liquidity, consts.Q96_RESOLUTION) + val2 := new(u256.Uint).Sub(sqrtRatioBX96, sqrtRatioAX96) + + res := u256.MulDiv(val1, val2, sqrtRatioBX96) + res = new(u256.Uint).Div(res, sqrtRatioAX96) + + return res +} + +// computeAmount1ForLiquidity calculates the required amount of token1 for a given liquidity level +// within a specified price range (represented by sqrt ratios). +// +// This function determines the amount of token1 needed to provide liquidity between the +// lower (sqrtRatioAX96) and upper (sqrtRatioBX96) price bounds. The calculation is performed +// in Q64.96 fixed-point format, which is standard for many liquidity calculations. +// +// Parameters: +// - sqrtRatioAX96: The lower bound of the price range as a square root ratio in Q64.96 format (*u256.Uint). +// - sqrtRatioBX96: The upper bound of the price range as a square root ratio in Q64.96 format (*u256.Uint). +// - liquidity: The liquidity amount to be used in the calculation (*u256.Uint). +// +// Returns: +// - *u256.Uint: The amount of token1 required to achieve the specified liquidity level. +// +// Notes: +// - This function handles edge cases where the liquidity is zero or when sqrtRatioAX96 equals sqrtRatioBX96 +// to prevent division by zero. +// - The calculation assumes sqrtRatioAX96 is always less than or equal to sqrtRatioBX96 after the initial +// ascending order sorting. +func computeAmount1ForLiquidity(sqrtRatioAX96, sqrtRatioBX96, liquidity *u256.Uint) *u256.Uint { + sqrtRatioAX96, sqrtRatioBX96 = toAscendingOrder(sqrtRatioAX96, sqrtRatioBX96) + if liquidity.IsZero() || sqrtRatioAX96.Eq(sqrtRatioBX96) { + return u256.Zero() + } + + diff := new(u256.Uint).Sub(sqrtRatioBX96, sqrtRatioAX96) + res := u256.MulDiv(liquidity, diff, u256.MustFromDecimal(consts.Q96)) + + return res +} + +// GetAmountsForLiquidity calculates the amounts of token0 and token1 required +// to provide a specified liquidity within a price range. +// +// This function determines the quantities of token0 and token1 necessary to achieve +// a given liquidity level, depending on the current price (sqrtRatioX96) and the +// bounds of the price range (sqrtRatioAX96 and sqrtRatioBX96). The function returns +// the calculated amounts of token0 and token1 as strings. +// +// If the current price is below the lower bound of the price range, only token0 is required. +// If the current price is above the upper bound, only token1 is required. When the +// price is within the range, both token0 and token1 are calculated. +// +// Parameters: +// - sqrtRatioX96: The current price represented as a square root ratio in Q64.96 format (*u256.Uint). +// - sqrtRatioAX96: The lower bound of the price range as a square root ratio in Q64.96 format (*u256.Uint). +// - sqrtRatioBX96: The upper bound of the price range as a square root ratio in Q64.96 format (*u256.Uint). +// - liquidity: The amount of liquidity to be provided (*u256.Uint). +// +// Returns: +// - string: The calculated amount of token0 required to achieve the specified liquidity. +// - string: The calculated amount of token1 required to achieve the specified liquidity. +// +// Notes: +// - If liquidity is zero, the function returns "0" for both token0 and token1. +// - The function guarantees that sqrtRatioAX96 is always the lower bound and +// sqrtRatioBX96 is the upper bound by calling toAscendingOrder(). +// - Edge cases where the current price is exactly on the bounds are handled without division by zero. +// +// Example: +// ``` +// amount0, amount1 := GetAmountsForLiquidity( +// +// u256.MustFromDecimal("79228162514264337593543950336"), // sqrtRatioX96 (1.0 in Q64.96) +// u256.MustFromDecimal("39614081257132168796771975168"), // sqrtRatioAX96 (0.5 in Q64.96) +// u256.MustFromDecimal("158456325028528675187087900672"), // sqrtRatioBX96 (2.0 in Q64.96) +// u256.MustFromDecimal("1000000"), // Liquidity +// +// ) +// fmt.Println("Token0:", amount0, "Token1:", amount1) +// // Example output: Token0: 500000, Token1: 250000 +// ``` +func GetAmountsForLiquidity(sqrtRatioX96, sqrtRatioAX96, sqrtRatioBX96, liquidity *u256.Uint) (string, string) { + if liquidity.IsZero() { + return "0", "0" + } + + sqrtRatioAX96, sqrtRatioBX96 = toAscendingOrder(sqrtRatioAX96, sqrtRatioBX96) + + amount0 := u256.Zero() + amount1 := u256.Zero() + + if sqrtRatioX96.Lte(sqrtRatioAX96) { + amount0 = computeAmount0ForLiquidity(sqrtRatioAX96, sqrtRatioBX96, liquidity) + } else if sqrtRatioX96.Lt(sqrtRatioBX96) { + amount0 = computeAmount0ForLiquidity(sqrtRatioX96, sqrtRatioBX96, liquidity) + amount1 = computeAmount1ForLiquidity(sqrtRatioAX96, sqrtRatioX96, liquidity) + } else { + amount1 = computeAmount1ForLiquidity(sqrtRatioAX96, sqrtRatioBX96, liquidity) + } + + return amount0.ToString(), amount1.ToString() +} diff --git a/_deploy/r/gnoswap/common/liquidity_amounts_test.gno b/_deploy/r/gnoswap/common/liquidity_amounts_test.gno new file mode 100644 index 000000000..d471db2d9 --- /dev/null +++ b/_deploy/r/gnoswap/common/liquidity_amounts_test.gno @@ -0,0 +1,506 @@ +package common + +import ( + "testing" + + "gno.land/p/demo/uassert" + "gno.land/p/demo/ufmt" + u256 "gno.land/p/gnoswap/uint256" + "gno.land/r/gnoswap/v1/consts" +) + +func TestToAscendingOrder(t *testing.T) { + tests := []struct { + name string + a *u256.Uint + b *u256.Uint + expectedA string + expectedB string + }{ + { + name: "Ascending order - a < b", + a: u256.MustFromDecimal("10"), + b: u256.MustFromDecimal("20"), + expectedA: "10", + expectedB: "20", + }, + { + name: "Descending order - a > b", + a: u256.MustFromDecimal("50"), + b: u256.MustFromDecimal("30"), + expectedA: "30", + expectedB: "50", + }, + { + name: "Equal values - a == b", + a: u256.MustFromDecimal("100"), + b: u256.MustFromDecimal("100"), + expectedA: "100", + expectedB: "100", + }, + { + name: "Large numbers", + a: u256.MustFromDecimal("340282366920938463463374607431768211455"), // 2^128 - 1 + b: u256.MustFromDecimal("170141183460469231731687303715884105727"), // 2^127 - 1 + expectedA: "170141183460469231731687303715884105727", + expectedB: "340282366920938463463374607431768211455", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + min, max := toAscendingOrder(tt.a, tt.b) + + if min.ToString() != tt.expectedA { + t.Errorf("Expected min to be %s, got %s", tt.expectedA, min.ToString()) + } + + if max.ToString() != tt.expectedB { + t.Errorf("Expected max to be %s, got %s", tt.expectedB, max.ToString()) + } + }) + } +} + +func TestSafeConvertToUint128(t *testing.T) { + tests := []struct { + name string + input *u256.Uint + expected *u256.Uint + shouldPanic bool + }{ + { + name: "Valid uint128 value", + input: u256.MustFromDecimal("340282366920938463463374607431768211455"), // MAX_UINT128 + expected: u256.MustFromDecimal("340282366920938463463374607431768211455"), + shouldPanic: false, + }, + { + name: "Value exceeding uint128 range", + input: u256.MustFromDecimal("340282366920938463463374607431768211456"), // MAX_UINT128 + 1 + shouldPanic: true, + }, + { + name: "Zero value", + input: u256.Zero(), + expected: u256.Zero(), + shouldPanic: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.shouldPanic { + defer func() { + if r := recover(); r == nil { + t.Errorf("Expected panic but got none") + } + }() + safeConvertToUint128(tt.input) + } else { + got := safeConvertToUint128(tt.input) + if !got.Eq(tt.expected) { + t.Errorf("Expected %s, got %s", tt.expected.ToString(), got.ToString()) + } + } + }) + } +} + +func TestComputeLiquidityForAmount0(t *testing.T) { + testCases := []struct { + name string + sqrtRatioA string + sqrtRatioB string + amount0 string + expected string + expectPanic bool + }{ + { + name: "Basic liquidity calculation", + sqrtRatioA: "79228162514264337593543950336", // sqrt(1) << 96 + sqrtRatioB: "158456325028528675187087900672", // sqrt(4) << 96 + amount0: "1000000", + expected: "2000000", // Expected liquidity + }, + { + name: "No liquidity (zero amount)", + sqrtRatioA: "79228162514264337593543950336", + sqrtRatioB: "158456325028528675187087900672", + amount0: "0", + expected: "0", + }, + { + name: "Liquidity overflow (exceeds uint128)", + sqrtRatioA: "158456325028528675187087900672", + sqrtRatioB: "316912650057057350374175801344", + amount0: "340282366920938463463374607431768211456", // Exceeds uint128 + expectPanic: true, + }, + { + name: "Zero liquidity with equal ratios", + sqrtRatioA: "79228162514264337593543950336", + sqrtRatioB: "79228162514264337593543950336", + amount0: "1000000", + expected: "0", + expectPanic: true, + }, + { + name: "Panic with identical ticks", + sqrtRatioA: "79228162514264337593543950336", + sqrtRatioB: "79228162514264337593543950336", + amount0: "1000000", + expectPanic: true, + }, + { + name: "Large liquidity calculation", + sqrtRatioA: "79228162514264337593543950336", + sqrtRatioB: "158456325028528675187087900672", + amount0: "1000000000", + expected: "2000000000", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + defer func() { + if r := recover(); r != nil { + if !tc.expectPanic { + t.Errorf("Unexpected panic for test case: %s", tc.name) + } + } + }() + + sqrtRatioA := u256.MustFromDecimal(tc.sqrtRatioA) + sqrtRatioB := u256.MustFromDecimal(tc.sqrtRatioB) + amount0 := u256.MustFromDecimal(tc.amount0) + + result := computeLiquidityForAmount0(sqrtRatioA, sqrtRatioB, amount0) + if !tc.expectPanic { + if result.ToString() != tc.expected { + t.Errorf("Expected %s but got %s", tc.expected, result.ToString()) + } + } + }) + } +} + +func TestComputeLiquidityForAmount1(t *testing.T) { + q96 := u256.MustFromDecimal(consts.Q96) + amount1 := u256.MustFromDecimal("1000000") + + t.Run("Basic liquidity calculation", func(t *testing.T) { + sqrtRatioAX96 := q96 // 2^96 (1 in Q96) + sqrtRatioBX96 := new(u256.Uint).Mul(q96, u256.MustFromDecimal("4")) // 4^96 (4 in Q96) + + expected := u256.MustFromDecimal("333333") // Expected liquidity + result := computeLiquidityForAmount1(sqrtRatioAX96, sqrtRatioBX96, amount1) + + if !result.Eq(expected) { + t.Errorf("Expected %s but got %s", expected.ToString(), result.ToString()) + } + }) + + t.Run("Zero liquidity with equal ratios", func(t *testing.T) { + sqrtRatioAX96 := q96 // 2^96 (1 in Q96) + sqrtRatioBX96 := q96 // Same as lower tick + + uassert.PanicsWithMessage(t, + "[GNOSWAP-COMMON-010] identical ticks || sqrtRatioAX96 (79228162514264337593543950336) and sqrtRatioBX96 (79228162514264337593543950336) are identical", + func() { + _ = computeLiquidityForAmount1(sqrtRatioAX96, sqrtRatioBX96, amount1) + }) + }) + + t.Run("Large liquidity calculation", func(t *testing.T) { + sqrtRatioAX96 := q96 // 1x + sqrtRatioBX96 := new(u256.Uint).Mul(q96, u256.NewUint(16)) // 16x + largeAmount := u256.MustFromDecimal("1000000000") + + expected := u256.MustFromDecimal("66666666") // 1B / 16 = 62.5M + result := computeLiquidityForAmount1(sqrtRatioAX96, sqrtRatioBX96, largeAmount) + + if !result.Eq(expected) { + t.Errorf("Expected %s but got %s", expected.ToString(), result.ToString()) + } + }) +} + +func TestGetLiquidityForAmounts(t *testing.T) { + q96 := u256.MustFromDecimal(consts.Q96) + + tests := []struct { + name string + sqrtRatioX96 string + sqrtRatioAX96 string + sqrtRatioBX96 string + amount0 string + amount1 string + expected string + }{ + { + name: "Basic Liquidity Calculation - Token0 Dominant", + sqrtRatioX96: q96.ToString(), // 현재 가격이 Q96 + sqrtRatioAX96: (new(u256.Uint).Mul(q96, u256.NewUint(4))).ToString(), + sqrtRatioBX96: (new(u256.Uint).Mul(q96, u256.NewUint(16))).ToString(), + amount0: "1000000", + amount1: "1000000", + expected: "5333333", + }, + { + name: "Within Range - Both Token0 and Token1", + sqrtRatioX96: (new(u256.Uint).Mul(q96, u256.NewUint(2))).ToString(), + sqrtRatioAX96: (new(u256.Uint).Mul(q96, u256.NewUint(4))).ToString(), + sqrtRatioBX96: (new(u256.Uint).Mul(q96, u256.NewUint(16))).ToString(), + amount0: "2000000", + amount1: "3000000", + expected: "10666666", + }, + { + name: "Token1 Dominant - Price Above Upper Bound", + sqrtRatioX96: (new(u256.Uint).Mul(q96, u256.NewUint(20))).ToString(), + sqrtRatioAX96: (new(u256.Uint).Mul(q96, u256.NewUint(4))).ToString(), + sqrtRatioBX96: (new(u256.Uint).Mul(q96, u256.NewUint(16))).ToString(), + amount0: "1500000", + amount1: "3000000", + expected: "250000", + }, + { + name: "Edge Case - sqrtRatioX96 = Lower Bound", + sqrtRatioX96: (new(u256.Uint).Mul(q96, u256.NewUint(4))).ToString(), + sqrtRatioAX96: (new(u256.Uint).Mul(q96, u256.NewUint(4))).ToString(), + sqrtRatioBX96: (new(u256.Uint).Mul(q96, u256.NewUint(16))).ToString(), + amount0: "500000", + amount1: "500000", + expected: "2666666", + }, + { + name: "Edge Case - sqrtRatioX96 = Upper Bound", + sqrtRatioX96: (new(u256.Uint).Mul(q96, u256.NewUint(16))).ToString(), + sqrtRatioAX96: (new(u256.Uint).Mul(q96, u256.NewUint(4))).ToString(), + sqrtRatioBX96: (new(u256.Uint).Mul(q96, u256.NewUint(16))).ToString(), + amount0: "1000000", + amount1: "1000000", + expected: "83333", + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + sqrtRatioX96 := u256.MustFromDecimal(tc.sqrtRatioX96) + sqrtRatioAX96 := u256.MustFromDecimal(tc.sqrtRatioAX96) + sqrtRatioBX96 := u256.MustFromDecimal(tc.sqrtRatioBX96) + amount0 := u256.MustFromDecimal(tc.amount0) + amount1 := u256.MustFromDecimal(tc.amount1) + + result := GetLiquidityForAmounts(sqrtRatioX96, sqrtRatioAX96, sqrtRatioBX96, amount0, amount1) + expected := u256.MustFromDecimal(tc.expected) + + uassert.Equal(t, expected.ToString(), result.ToString()) + }) + } +} + +func TestComputeAmount0ForLiquidity(t *testing.T) { + q96 := u256.MustFromDecimal("79228162514264337593543950336") // 2^96 + + tests := []struct { + name string + sqrtRatioAX96 string + sqrtRatioBX96 string + liquidity string + expected string + }{ + { + name: "Basic Case - Small Range", + sqrtRatioAX96: new(u256.Uint).Mul(q96, u256.NewUint(4)).ToString(), + sqrtRatioBX96: new(u256.Uint).Mul(q96, u256.NewUint(8)).ToString(), + liquidity: "1000000", + expected: "125000", + }, + { + name: "Large Liquidity - Wide Range", + sqrtRatioAX96: new(u256.Uint).Mul(q96, u256.NewUint(2)).ToString(), + sqrtRatioBX96: new(u256.Uint).Mul(q96, u256.NewUint(16)).ToString(), + liquidity: "5000000000", + expected: "2187500000", + }, + { + name: "Edge Case - Equal Bounds", + sqrtRatioAX96: new(u256.Uint).Mul(q96, u256.NewUint(8)).ToString(), + sqrtRatioBX96: new(u256.Uint).Mul(q96, u256.NewUint(8)).ToString(), + liquidity: "1000000", + expected: "0", + }, + { + name: "Minimum Liquidity", + sqrtRatioAX96: new(u256.Uint).Mul(q96, u256.NewUint(5)).ToString(), + sqrtRatioBX96: new(u256.Uint).Mul(q96, u256.NewUint(10)).ToString(), + liquidity: "1", + expected: "0", + }, + { + name: "Max Liquidity", + sqrtRatioAX96: new(u256.Uint).Mul(q96, u256.NewUint(1)).ToString(), + sqrtRatioBX96: new(u256.Uint).Mul(q96, u256.NewUint(32)).ToString(), + liquidity: "1000000000000000000", + expected: "968750000000000000", + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + sqrtRatioAX96 := u256.MustFromDecimal(tc.sqrtRatioAX96) + sqrtRatioBX96 := u256.MustFromDecimal(tc.sqrtRatioBX96) + liquidity := u256.MustFromDecimal(tc.liquidity) + + result := computeAmount0ForLiquidity(sqrtRatioAX96, sqrtRatioBX96, liquidity) + expected := u256.MustFromDecimal(tc.expected) + + if result.ToString() != expected.ToString() { + t.Errorf("expected %s but got %s", expected.ToString(), result.ToString()) + } + }) + } +} + +func TestComputeAmount1ForLiquidity(t *testing.T) { + q96 := u256.MustFromDecimal(consts.Q96) // 2^96 = 79228162514264337593543950336 + + tests := []struct { + name string + sqrtRatioAX96 *u256.Uint + sqrtRatioBX96 *u256.Uint + liquidity *u256.Uint + expectedAmount string + }{ + { + name: "Basic Case - Small Liquidity", + sqrtRatioAX96: q96, + sqrtRatioBX96: new(u256.Uint).Mul(q96, u256.NewUint(4)), // sqrtRatioBX96 = 4 * Q96 + liquidity: u256.NewUint(1000000000), + expectedAmount: "3000000000", // (4-1)*liquidity = 3 * 10^9 + }, + { + name: "Edge Case - Equal Ratios", + sqrtRatioAX96: q96, + sqrtRatioBX96: q96, + liquidity: u256.NewUint(1000000), + expectedAmount: "0", + }, + { + name: "Zero Liquidity", + sqrtRatioAX96: q96, + sqrtRatioBX96: new(u256.Uint).Mul(q96, u256.NewUint(2)), + liquidity: u256.Zero(), + expectedAmount: "0", + }, + { + name: "Large Liquidity", + sqrtRatioAX96: q96, + sqrtRatioBX96: new(u256.Uint).Mul(q96, u256.NewUint(16)), // sqrtRatioBX96 = 16 * Q96 + liquidity: u256.NewUint(1000000000000000000), // 1e18 liquidity + expectedAmount: "15000000000000000000", // (16-1) * 1e18 = 15 * 1e18 + }, + { + name: "Descending Ratios (Order Correction)", + sqrtRatioAX96: new(u256.Uint).Mul(q96, u256.NewUint(8)), + sqrtRatioBX96: q96, + liquidity: u256.NewUint(500000), + expectedAmount: "3500000", // (8-1)*500000 + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := computeAmount1ForLiquidity(tt.sqrtRatioAX96, tt.sqrtRatioBX96, tt.liquidity) + uassert.Equal(t, tt.expectedAmount, result.ToString(), ufmt.Sprintf("expected %s but got %s", tt.expectedAmount, result.ToString())) + }) + } +} + +func TestGetAmountsForLiquidity(t *testing.T) { + q96 := u256.MustFromDecimal(consts.Q96) // 2^96 = 79228162514264337593543950336 + + tests := []struct { + name string + sqrtRatioX96 *u256.Uint + sqrtRatioAX96 *u256.Uint + sqrtRatioBX96 *u256.Uint + liquidity *u256.Uint + expectedAmount0 string + expectedAmount1 string + }{ + { + name: "Basic Case - Within Range", + sqrtRatioX96: new(u256.Uint).Mul(q96, u256.NewUint(2)), // Current price at 2 * Q96 + sqrtRatioAX96: q96, // Lower bound at 1 * Q96 + sqrtRatioBX96: new(u256.Uint).Mul(q96, u256.NewUint(4)), // Upper bound at 4 * Q96 + liquidity: u256.NewUint(1000000), + expectedAmount0: "250000", + expectedAmount1: "1000000", + }, + { + name: "Edge Case - At Lower Bound (sqrtRatioX96 == sqrtRatioAX96)", + sqrtRatioX96: q96, + sqrtRatioAX96: q96, + sqrtRatioBX96: new(u256.Uint).Mul(q96, u256.NewUint(8)), + liquidity: u256.NewUint(1000000), + expectedAmount0: "875000", + expectedAmount1: "0", + }, + { + name: "Edge Case - At Upper Bound (sqrtRatioX96 == sqrtRatioBX96)", + sqrtRatioX96: new(u256.Uint).Mul(q96, u256.NewUint(8)), + sqrtRatioAX96: q96, + sqrtRatioBX96: new(u256.Uint).Mul(q96, u256.NewUint(8)), + liquidity: u256.NewUint(1000000), + expectedAmount0: "0", + expectedAmount1: "7000000", + }, + { + name: "Out of Range - Below Lower Bound", + sqrtRatioX96: new(u256.Uint).Div(q96, u256.NewUint(2)), // Current price at 0.5 * Q96 + sqrtRatioAX96: q96, + sqrtRatioBX96: new(u256.Uint).Mul(q96, u256.NewUint(8)), + liquidity: u256.NewUint(500000), + expectedAmount0: "437500", + expectedAmount1: "0", + }, + { + name: "Out of Range - Above Upper Bound", + sqrtRatioX96: new(u256.Uint).Mul(q96, u256.NewUint(10)), // Current price at 10 * Q96 + sqrtRatioAX96: q96, + sqrtRatioBX96: new(u256.Uint).Mul(q96, u256.NewUint(8)), + liquidity: u256.NewUint(2000000), + expectedAmount0: "0", + expectedAmount1: "14000000", + }, + { + name: "Zero Liquidity", + sqrtRatioX96: q96, + sqrtRatioAX96: q96, + sqrtRatioBX96: new(u256.Uint).Mul(q96, u256.NewUint(16)), + liquidity: u256.Zero(), + expectedAmount0: "0", + expectedAmount1: "0", + }, + { + name: "Descending Ratios (Order Correction)", + sqrtRatioX96: q96, + sqrtRatioAX96: new(u256.Uint).Mul(q96, u256.NewUint(8)), + sqrtRatioBX96: q96, + liquidity: u256.NewUint(1000000), + expectedAmount0: "875000", + expectedAmount1: "0", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + amount0, amount1 := GetAmountsForLiquidity(tt.sqrtRatioX96, tt.sqrtRatioAX96, tt.sqrtRatioBX96, tt.liquidity) + uassert.Equal(t, tt.expectedAmount0, amount0, ufmt.Sprintf("expected %s but got %s for amount0", tt.expectedAmount0, amount0)) + uassert.Equal(t, tt.expectedAmount1, amount1, ufmt.Sprintf("expected %s but got %s for amount1", tt.expectedAmount1, amount1)) + }) + } +} diff --git a/_deploy/r/gnoswap/common/math.gno b/_deploy/r/gnoswap/common/math.gno new file mode 100644 index 000000000..ac7a67b2d --- /dev/null +++ b/_deploy/r/gnoswap/common/math.gno @@ -0,0 +1,100 @@ +package common + +import ( + "gno.land/p/demo/ufmt" + + i256 "gno.land/p/gnoswap/int256" + u256 "gno.land/p/gnoswap/uint256" + + "gno.land/r/gnoswap/v1/consts" +) + +// I64Min returns the minimum of two int64 values +func I64Min(x, y int64) int64 { + if x < y { + return x + } + return y +} + +// I64Max returns the maximum of two int64 values +func I64Max(x, y int64) int64 { + if x > y { + return x + } + return y +} + +// U64Min returns the minimum of two uint64 values +func U64Min(x, y uint64) uint64 { + if x < y { + return x + } + return y +} + +// U64Max returns the maximum of two uint64 values +func U64Max(x, y uint64) uint64 { + if x > y { + return x + } + return y +} + +// I256Min returns the minimum of two Int256 values +func I256Min(x, y *i256.Int) *i256.Int { + if x.Cmp(y) < 0 { + return x + } + return y +} + +// I256Max returns the maximum of two Int256 values +func I256Max(x, y *i256.Int) *i256.Int { + if x.Cmp(y) > 0 { + return x + } + return y +} + +// U256Min returns the minimum of two Uint256 values +func U256Min(x, y *u256.Uint) *u256.Uint { + if x.Cmp(y) < 0 { + return x + } + return y +} + +// U256Max returns the maximum of two Uint256 values +func U256Max(x, y *u256.Uint) *u256.Uint { + if x.Cmp(y) > 0 { + return x + } + return y +} + +// SafeConvertUint256ToInt256 converts a uint256.Uint to int256.Int and returns it. +// If the value is greater than the maximum int256 value, it panics. +func SafeConvertUint256ToInt256(x *u256.Uint) *i256.Int { + if x.Gt(u256.MustFromDecimal(consts.MAX_INT256)) { + panic(newErrorWithDetail( + errOverflow, + ufmt.Sprintf("can not convert %s to int256", x.ToString()), + )) + } + return i256.FromUint256(x) +} + +// SafeConvertUint256ToUint64 converts a uint256.Uint to uint64 and returns it. +// If the value is greater than the maximum uint64 value, it panics. +func SafeConvertUint256ToUint64(x *u256.Uint) uint64 { + value, overflow := x.Uint64WithOverflow() + if overflow { + panic(newErrorWithDetail( + errOverflow, + ufmt.Sprintf("can not convert %s to uint64", x.ToString()), + )) + } + + return value +} diff --git a/_deploy/r/gnoswap/common/math_test.gno b/_deploy/r/gnoswap/common/math_test.gno new file mode 100644 index 000000000..8c9088a96 --- /dev/null +++ b/_deploy/r/gnoswap/common/math_test.gno @@ -0,0 +1,234 @@ +package common + +import ( + "testing" + + i256 "gno.land/p/gnoswap/int256" + u256 "gno.land/p/gnoswap/uint256" +) + +func TestI64Min(t *testing.T) { + tests := []struct { + x, y, want int64 + }{ + {1, 2, 1}, + {-1, 1, -1}, + {5, 5, 5}, + {-10, -5, -10}, + {9223372036854775807, 0, 0}, // Max int64 + } + + for _, tt := range tests { + got := I64Min(tt.x, tt.y) + if got != tt.want { + t.Errorf("I64Min(%v, %v) = %v, want %v", tt.x, tt.y, got, tt.want) + } + } +} + +func TestI64Max(t *testing.T) { + tests := []struct { + x, y, want int64 + }{ + {1, 2, 2}, + {-1, 1, 1}, + {5, 5, 5}, + {-10, -5, -5}, + {9223372036854775807, 0, 9223372036854775807}, // Max int64 + } + + for _, tt := range tests { + got := I64Max(tt.x, tt.y) + if got != tt.want { + t.Errorf("I64Max(%v, %v) = %v, want %v", tt.x, tt.y, got, tt.want) + } + } +} + +func TestU64Min(t *testing.T) { + tests := []struct { + x, y, want uint64 + }{ + {1, 2, 1}, + {0, 1, 0}, + {5, 5, 5}, + {10, 5, 5}, + {18446744073709551615, 0, 0}, // Max uint64 + } + + for _, tt := range tests { + got := U64Min(tt.x, tt.y) + if got != tt.want { + t.Errorf("U64Min(%v, %v) = %v, want %v", tt.x, tt.y, got, tt.want) + } + } +} + +func TestU64Max(t *testing.T) { + tests := []struct { + x, y, want uint64 + }{ + {1, 2, 2}, + {0, 1, 1}, + {5, 5, 5}, + {10, 5, 10}, + {18446744073709551615, 0, 18446744073709551615}, // Max uint64 + } + + for _, tt := range tests { + got := U64Max(tt.x, tt.y) + if got != tt.want { + t.Errorf("U64Max(%v, %v) = %v, want %v", tt.x, tt.y, got, tt.want) + } + } +} + +func TestI256Min(t *testing.T) { + tests := []struct { + x, y string // hex strings for creating Int + want string + }{ + {"1", "2", "1"}, + {"-1", "1", "-1"}, + {"5", "5", "5"}, + {"-10", "-5", "-10"}, + } + + for _, tt := range tests { + x, _ := i256.FromDecimal(tt.x) + y, _ := i256.FromDecimal(tt.y) + want, _ := i256.FromDecimal(tt.want) + got := I256Min(x, y) + if got.Cmp(want) != 0 { + t.Errorf("I256Min(%v, %v) = %v, want %v", tt.x, tt.y, got, want) + } + } +} + +func TestI256Max(t *testing.T) { + tests := []struct { + x, y string // hex strings for creating Int + want string + }{ + {"1", "2", "2"}, + {"-1", "1", "1"}, + {"5", "5", "5"}, + {"-10", "-5", "-5"}, + } + + for _, tt := range tests { + x, _ := i256.FromDecimal(tt.x) + y, _ := i256.FromDecimal(tt.y) + want, _ := i256.FromDecimal(tt.want) + got := I256Max(x, y) + if got.Cmp(want) != 0 { + t.Errorf("I256Max(%v, %v) = %v, want %v", tt.x, tt.y, got, want) + } + } +} + +func TestU256Min(t *testing.T) { + tests := []struct { + x, y string // decimal strings for creating Uint + want string + }{ + {"1", "2", "1"}, + {"0", "1", "0"}, + {"5", "5", "5"}, + {"10", "5", "5"}, + } + + for _, tt := range tests { + x, _ := u256.FromDecimal(tt.x) + y, _ := u256.FromDecimal(tt.y) + want, _ := u256.FromDecimal(tt.want) + got := U256Min(x, y) + if got.Cmp(want) != 0 { + t.Errorf("U256Min(%v, %v) = %v, want %v", tt.x, tt.y, got, want) + } + } +} + +func TestU256Max(t *testing.T) { + tests := []struct { + x, y string // decimal strings for creating Uint + want string + }{ + {"1", "2", "2"}, + {"0", "1", "1"}, + {"5", "5", "5"}, + {"10", "5", "10"}, + } + + for _, tt := range tests { + x, _ := u256.FromDecimal(tt.x) + y, _ := u256.FromDecimal(tt.y) + want, _ := u256.FromDecimal(tt.want) + got := U256Max(x, y) + if got.Cmp(want) != 0 { + t.Errorf("U256Max(%v, %v) = %v, want %v", tt.x, tt.y, got, want) + } + } +} + +func TestSafeConvertUint256ToInt256(t *testing.T) { + tests := []struct { + x, want string + shouldPanic bool + }{ + {"0", "0", false}, + {"1", "1", false}, + // max int256 + {"57896044618658097711785492504343953926634992332820282019728792003956564819968", "57896044618658097711785492504343953926634992332820282019728792003956564819968", false}, + // max int256 + 1 (overflow) + {"57896044618658097711785492504343953926634992332820282019728792003956564819969", "", true}, + } + + for _, tt := range tests { + if tt.shouldPanic { + defer func() { + if r := recover(); r == nil { + t.Errorf("SafeConvertUint256ToInt256(%v) did not panic", tt.x) + } + }() + } + + x := u256.MustFromDecimal(tt.x) + got := SafeConvertUint256ToInt256(x) + want := i256.MustFromDecimal(tt.want) + if got.Cmp(want) != 0 { + t.Errorf("SafeConvertUint256ToInt256(%v) = %v, want %v", tt.x, got, want) + } + } +} + +func TestSafeConvertUint256ToUint64(t *testing.T) { + tests := []struct { + x string + want uint64 + shouldPanic bool + }{ + {"0", 0, false}, + {"1", 1, false}, + // max uint64 + {"18446744073709551615", 18446744073709551615, false}, + // max uint64 + 1 (overflow) + {"18446744073709551616", 0, true}, + } + + for _, tt := range tests { + if tt.shouldPanic { + defer func() { + if r := recover(); r == nil { + t.Errorf("SafeConvertUint256ToUint64(%v) did not panic", tt.x) + } + }() + } + + x := u256.MustFromDecimal(tt.x) + got := SafeConvertUint256ToUint64(x) + if got != tt.want { + t.Errorf("SafeConvertUint256ToUint64(%v) = %v, want %v", tt.x, got, tt.want) + } + } +} diff --git a/_deploy/r/gnoswap/common/strings.gno b/_deploy/r/gnoswap/common/strings.gno new file mode 100644 index 000000000..c14b45303 --- /dev/null +++ b/_deploy/r/gnoswap/common/strings.gno @@ -0,0 +1,59 @@ +package common + +import ( + "strconv" + "strings" + + "gno.land/p/demo/ufmt" +) + +func Split(input string, sep string, length int) ([]string, error) { + result := strings.Split(input, sep) + if len(result) != length { + return nil, ufmt.Errorf("invalid length: %d", len(result)) + } + + return result, nil +} + +// EncodeUint converts an uint64 number into a zero-padded 20-character string. +// +// Parameters: +// - num (uint64): The number to encode. +// +// Returns: +// - string: A zero-padded string representation of the number. +// +// Example: +// Input: 12345 +// Output: "00000000000000012345" +func EncodeUint(num uint64) string { + // Convert the value to a decimal string. + s := strconv.FormatUint(num, 10) + + // Zero-pad to a total length of 20 characters. + zerosNeeded := 20 - len(s) + return strings.Repeat("0", zerosNeeded) + s +} + +// DecodeUint converts a zero-padded string back into a uint64 number. +// +// Parameters: +// - s (string): The zero-padded string. +// +// Returns: +// - uint64: The decoded number. +// +// Panics: +// - If the string cannot be parsed into a uint64. +// +// Example: +// Input: "00000000000000012345" +// Output: 12345 +func DecodeUint(s string) uint64 { + num, err := strconv.ParseUint(s, 10, 64) + if err != nil { + panic(err) + } + return num +} diff --git a/_deploy/r/gnoswap/common/tick_math.gno b/_deploy/r/gnoswap/common/tick_math.gno new file mode 100644 index 000000000..646573d77 --- /dev/null +++ b/_deploy/r/gnoswap/common/tick_math.gno @@ -0,0 +1,378 @@ +package common + +import ( + "gno.land/p/demo/avl" + "gno.land/p/demo/ufmt" + + i256 "gno.land/p/gnoswap/int256" + u256 "gno.land/p/gnoswap/uint256" +) + +const ( + MAX_UINT8 string = "255" + MAX_UINT16 string = "65535" + MAX_UINT32 string = "4294967295" + MAX_UINT64 string = "18446744073709551615" + MAX_UINT128 string = "340282366920938463463374607431768211455" + MAX_UINT160 string = "1461501637330902918203684832716283019655932542975" + MAX_UINT256 string = "115792089237316195423570985008687907853269984665640564039457584007913129639935" + MAX_INT256 string = "57896044618658097711785492504343953926634992332820282019728792003956564819967" + + Q64 string = "18446744073709551616" // 2 ** 64 + Q96 string = "79228162514264337593543950336" // 2 ** 96 + Q128 string = "340282366920938463463374607431768211456" // 2 ** 128 +) + +var ( + tickRatioTree = NewTickRatioTree() + binaryLogTree = NewBinaryLogTree() + + shift1By32Left = u256.MustFromDecimal("4294967296") // (1 << 32) + maxTick = int32(887272) +) + +// TreeValue wraps u256.Uint to implement custom value type for avl.Tree +type TreeValue struct { + value *u256.Uint +} + +func (tv TreeValue) String() string { + return tv.value.ToString() +} + +// TickRatioTree manages tick ratios +type TickRatioTree struct { + tree *avl.Tree +} + +// NewTickRatioTree initializes a new TickRatioTree with predefined values +func NewTickRatioTree() *TickRatioTree { + tree := avl.NewTree() + + ratios := []struct { + key int32 + value string + }{ + {0x1, "340265354078544963557816517032075149313"}, + {0x2, "340248342086729790484326174814286782778"}, + {0x4, "340214320654664324051920982716015181260"}, + {0x8, "340146287995602323631171512101879684304"}, + {0x10, "340010263488231146823593991679159461444"}, + {0x20, "339738377640345403697157401104375502016"}, + {0x40, "339195258003219555707034227454543997025"}, + {0x80, "338111622100601834656805679988414885971"}, + {0x100, "335954724994790223023589805789778977700"}, + {0x200, "331682121138379247127172139078559817300"}, + {0x400, "323299236684853023288211250268160618739"}, + {0x800, "307163716377032989948697243942600083929"}, + {0x1000, "277268403626896220162999269216087595045"}, + {0x2000, "225923453940442621947126027127485391333"}, + {0x4000, "149997214084966997727330242082538205943"}, + {0x8000, "66119101136024775622716233608466517926"}, + {0x10000, "12847376061809297530290974190478138313"}, + {0x20000, "485053260817066172746253684029974020"}, + {0x40000, "691415978906521570653435304214168"}, + {0x80000, "1404880482679654955896180642"}, + } + + for _, ratio := range ratios { + tick := ufmt.Sprintf("%d", ratio.key) + value := TreeValue{u256.MustFromDecimal(ratio.value)} + tree.Set(tick, value) + } + + return &TickRatioTree{tree} +} + +// GetRatio retrieves the ratio for a given tick +func (t *TickRatioTree) GetRatio(key int32) (*u256.Uint, bool) { + strKey := ufmt.Sprintf("%d", key) + value, exists := t.tree.Get(strKey) + + if !exists { + return nil, false + } + + if tv, ok := value.(TreeValue); ok { + return tv.value, true + } + + return nil, false +} + +// BinaryLogTree manages binary log constants +type BinaryLogTree struct { + tree *avl.Tree +} + +// NewBinaryLogTree initializes a new BinaryLogTree +func NewBinaryLogTree() *BinaryLogTree { + tree := avl.NewTree() + + logs := [8]string{ + "0", + "3", + "15", + "255", + "65535", + "4294967295", + "18446744073709551615", + "340282366920938463463374607431768211455", + } + + for i, value := range logs { + key := ufmt.Sprintf("%d", i) + tree.Set(key, TreeValue{u256.MustFromDecimal(value)}) + } + + return &BinaryLogTree{tree} +} + +// GetLog retrieves the binary log constant at given index +func (t *BinaryLogTree) GetLog(idx int) (*u256.Uint, bool) { + strKey := ufmt.Sprintf("%d", idx) + value, exists := t.tree.Get(strKey) + + if !exists { + return nil, false + } + + if tv, ok := value.(TreeValue); ok { + return tv.value, true + } + + return nil, false +} + +// TickMathGetSqrtRatioAtTick calculates the square root price ratio for a given tick. +// +// This function computes the square root ratio (sqrt(price)) at a specific tick, +// using a precomputed mapping of ratios. The result is returned as a 160-bit +// fixed-point value (Q64.96 format). +// +// Parameters: +// - tick (int32): The tick index for which the square root ratio is calculated. +// +// Returns: +// - *u256.Uint: The square root price ratio at the given tick, represented as a 160-bit unsigned integer. +// +// Behavior: +// 1. Validates that the tick is within the acceptable range by asserting its absolute value. +// 2. Initializes the ratio based on whether the least significant bit of the tick is set, using a lookup table (`tickRatioMap`). +// 3. Iteratively adjusts the ratio by multiplying and right-shifting with precomputed values for each relevant bit set in the tick value. +// 4. If the tick is positive, the ratio is inverted by dividing a maximum uint256 value by the computed ratio. +// 5. The result is split into upper 128 bits and lower 32 bits for precision handling. +// 6. If the lower 32 bits are non-zero, the upper 128 bits are incremented by 1 to ensure rounding up. +// +// Example: +// - For a tick of `0`, the ratio represents `1.0000` in Q64.96 format. +// - For a tick of `-887272` (minimum tick), the ratio represents the smallest possible price. +// - For a tick of `887272` (maximum tick), the ratio represents the highest possible price. +// +// Panics: +// - If the absolute tick value exceeds the maximum allowed tick range. +// +// Notes: +// - The function relies on a precomputed map `tickRatioMap` to optimize calculations. +// - Handles rounding by adding 1 if the remainder of the division is non-zero. +func TickMathGetSqrtRatioAtTick(tick int32) *u256.Uint { // uint160 sqrtPriceX96 + absTick := abs(tick) + assertValidTickRange(absTick) + + ratio := u256.MustFromDecimal("340282366920938463463374607431768211456") // consts.Q128 + initialBit := int32(0x1) + + if val, exists := tickRatioTree.GetRatio(initialBit); exists && (absTick&initialBit) != 0 { + ratio = val + } + + calculateRatio := func(mask int32) *u256.Uint { + if value, exists := tickRatioTree.GetRatio(mask); exists && absTick&mask != 0 { + return new(u256.Uint).Rsh( + new(u256.Uint).Mul(ratio, value), + 128, + ) + } + return ratio + } + + for mask := int32(0x2); mask <= 0x80000; mask *= 2 { + ratio = calculateRatio(mask) + } + + if tick > 0 { + maxUint256 := u256.MustFromDecimal(MAX_UINT256) // consts.MAX_UINT256 + if ratio.IsZero() { + return u256.Zero() + } + ratio = new(u256.Uint).Div(maxUint256, ratio) + } + + upper128Bits := new(u256.Uint).Rsh(ratio, 32) // ratio >> 32 + lower32Bits := ratio.Mod(ratio, shift1By32Left) // ratio % (1 << 32) + + var roundUp *u256.Uint + if lower32Bits.IsZero() { + roundUp = u256.Zero() + } else { + roundUp = u256.One() + } + + return new(u256.Uint).Add(upper128Bits, roundUp) +} + +func TickMathGetTickAtSqrtRatio(sqrtPriceX96 *u256.Uint) int32 { + cond1 := sqrtPriceX96.Gte(u256.MustFromDecimal("4295128739")) // MIN_SQRT_RATIO + cond2 := sqrtPriceX96.Lt(u256.MustFromDecimal("1461446703485210103287273052203988822378723970342")) // MAX_SQRT_RATIO + if !(cond1 && cond2) { + panic(newErrorWithDetail( + errOutOfRange, + ufmt.Sprintf("sqrtPriceX96 is out of range, sqrtPriceX96: %s", sqrtPriceX96.ToString()), + )) + } + + ratio := new(u256.Uint).Lsh(sqrtPriceX96, 32) + + msb, adjustedRatio := findMSB(ratio) + adjustedRatio = adjustRatio(ratio, msb) + + log2 := calculateLog2(msb, adjustedRatio) + tick := getTickValue(log2, sqrtPriceX96) + + return tick +} + +// findMSB computes the MSB (most significant bit) of the given ratio. +func findMSB(ratio *u256.Uint) (*u256.Uint, *u256.Uint) { + msb := u256.Zero() + + calculateMSB := func(i int) (*u256.Uint, *u256.Uint) { + if logConst, exists := binaryLogTree.GetLog(i); exists { + f := new(u256.Uint).Lsh(gt(ratio, logConst), uint(i)) + msb = new(u256.Uint).Or(msb, f) + ratio = new(u256.Uint).Rsh(ratio, uint(f.Uint64())) + } + return msb, ratio + } + + for i := 7; i >= 1; i-- { + msb, ratio = calculateMSB(i) + } + + // handle the remaining bits + { + f := gt(ratio, u256.One()) // 0x1 + // msb = msb | f + msb = new(u256.Uint).Or(msb, f) + } + + return msb, ratio +} + +// adjustRatio adjusts the given ratio based on the MSB found. +// +// This adjustment ensures that the ratio falls within the specific range. +func adjustRatio(ratio, msb *u256.Uint) *u256.Uint { + if msb.Gte(u256.NewUint(128)) { + return new(u256.Uint).Rsh(ratio, uint(msb.Uint64()-127)) + } + + return new(u256.Uint).Lsh(ratio, uint(127-msb.Uint64())) +} + +// calculateLog2 calculates the binary logarith, of the adjusted ratio using a fixed-point arithmetic. +// +// This function iteratively squares the ratio and adjusts the result to compute the log base 2, which will determine the tick value. +func calculateLog2(msb, ratio *u256.Uint) *i256.Int { + _msb := i256.FromUint256(msb) + _128 := i256.NewInt(128) + + log_2 := i256.Zero().Sub(_msb, _128) + log_2 = log_2.Lsh(log_2, 64) + + for i := 63; i >= 51; i-- { + ratio = new(u256.Uint).Mul(ratio, ratio) + ratio = ratio.Rsh(ratio, 127) + + f := i256.FromUint256(new(u256.Uint).Rsh(ratio, 128)) + + // log_2 = log_2 | (f << i) + log_2 = i256.Zero().Or(log_2, i256.Zero().Lsh(f, uint(i))) + + // ratio = ratio >> uint64(f) + ratio = ratio.Rsh(ratio, uint(f.Uint64())) + } + + // handle the remaining bits + { + // ratio = ratio * ratio >> 127 + ratio = new(u256.Uint).Mul(ratio, ratio) + ratio = new(u256.Uint).Rsh(ratio, 127) + + f := i256.FromUint256(new(u256.Uint).Rsh(ratio, 128)) + + log_2 = i256.Zero().Or(log_2, i256.Zero().Lsh(f, 50)) + } + + return log_2 +} + +// getTickValue determines the tick value corresponding to a given sqrtPriveX96. +// +// It calculates the upper and lower bounds for each tick, and selects the appropriate tock value +// based on the given sqrtPriceX96. +func getTickValue(log2 *i256.Int, sqrtPriceX96 *u256.Uint) int32 { + // ref: https://github.com/Uniswap/v3-core/issues/500 + // 2^64 / log2 (√1.0001) = 255738958999603826347141 + log_sqrt10001 := i256.Zero().Mul(log2, i256.MustFromDecimal("255738958999603826347141")) + + // ref: https://ethereum.stackexchange.com/questions/113844/how-does-uniswap-v3s-logarithm-library-tickmath-sol-work/113912#113912 + // 0.010000497 x 2^128 = 3402992956809132418596140100660247210 + tickLow256 := i256.Zero().Sub(log_sqrt10001, i256.MustFromDecimal("3402992956809132418596140100660247210")) + tickLow256 = tickLow256.Rsh(tickLow256, 128) + tickLow := int32(tickLow256.Int64()) + + // ref: https://ethereum.stackexchange.com/questions/113844/how-does-uniswap-v3s-logarithm-library-tickmath-sol-work/113912#113912 + // 0.856 x 2^128 = 291339464771989622907027621153398088495 + tickHi256 := i256.Zero().Add(log_sqrt10001, i256.MustFromDecimal("291339464771989622907027621153398088495")) + tickHi256 = tickHi256.Rsh(tickHi256, 128) + tickHi := int32(tickHi256.Int64()) + + var tick int32 + if tickLow == tickHi { + tick = tickLow + } else if TickMathGetSqrtRatioAtTick(tickHi).Lte(sqrtPriceX96) { + tick = tickHi + } else { + tick = tickLow + } + + return tick +} + +func gt(x, y *u256.Uint) *u256.Uint { + if x.Gt(y) { + return u256.One() + } + + return u256.Zero() +} + +// abs returns the absolute value of the given integer. +func abs(x int32) int32 { + if x < 0 { + return -x + } + + return x +} + +// assertValidTickRange validates that the absolute tick value is within the acceptable range. +func assertValidTickRange(absTick int32) { + if absTick > maxTick { + panic(newErrorWithDetail( + errOutOfRange, + ufmt.Sprintf("abs tick is out of range (larger than 887272), abs tick: %d", absTick), + )) + } +} diff --git a/_deploy/r/gnoswap/common/tick_math_test.gno b/_deploy/r/gnoswap/common/tick_math_test.gno new file mode 100644 index 000000000..bc3a70365 --- /dev/null +++ b/_deploy/r/gnoswap/common/tick_math_test.gno @@ -0,0 +1,209 @@ +package common + +import ( + "testing" + + "gno.land/p/demo/uassert" + + u256 "gno.land/p/gnoswap/uint256" +) + +var ( + MIN_TICK = int32(-887272) + MIN_SQRT_RATIO = "4295128739" + + MAX_TICK = int32(887272) + MAX_SQRT_RATIO = "1461446703485210103287273052203988822378723970342" +) + +func TestTickMathGetSqrtRatioAtTick(t *testing.T) { + t.Run("throws for too low", func(t *testing.T) { + tick := MIN_TICK - 1 + + uassert.PanicsWithMessage( + t, + "[GNOSWAP-COMMON-003] value out of range || abs tick is out of range (larger than 887272), abs tick: 887273", + func() { + TickMathGetSqrtRatioAtTick(tick) + }, + ) + }) + + t.Run("throws for too high", func(t *testing.T) { + tick := MAX_TICK + 1 + + uassert.PanicsWithMessage( + t, + "[GNOSWAP-COMMON-003] value out of range || abs tick is out of range (larger than 887272), abs tick: 887273", + func() { + TickMathGetSqrtRatioAtTick(tick) + }, + ) + }) + + t.Run("min tick", func(t *testing.T) { + tick := MIN_TICK + sqrtPriceX96 := TickMathGetSqrtRatioAtTick(tick) + uassert.Equal(t, "4295128739", sqrtPriceX96.ToString()) + }) + + t.Run("min tick + 1", func(t *testing.T) { + tick := MIN_TICK + 1 + sqrtPriceX96 := TickMathGetSqrtRatioAtTick(tick) + uassert.Equal(t, "4295343490", sqrtPriceX96.ToString()) + }) + + t.Run("min tick ratio is less than js implementation", func(t *testing.T) { + // encodePriceSqrt(1, BigNumber.from(2).pow(127)) = 6085630636 + sqrtPriceX96 := TickMathGetSqrtRatioAtTick(MIN_TICK) + uassert.False(t, sqrtPriceX96.Cmp(u256.MustFromDecimal("6085630636")) > 0, "should be less than 6085630636") + }) + + t.Run("max tick ratio is greater than js implementation", func(t *testing.T) { + // encodePriceSqrt(BigNumber.from(2).pow(127),1) = 1033437718471923706666374484006904511252097097914 + sqrtPriceX96 := TickMathGetSqrtRatioAtTick(MAX_TICK) + uassert.False(t, sqrtPriceX96.Cmp(u256.MustFromDecimal("1033437718471923706666374484006904511252097097914")) < 0, "should be greater than 1033437718471923706666374484006904511252097097914") + }) + + t.Run("max tick ratio is equal to js implementation", func(t *testing.T) { + sqrtPriceX96 := TickMathGetSqrtRatioAtTick(MAX_TICK) + uassert.Equal(t, "1461446703485210103287273052203988822378723970342", sqrtPriceX96.ToString()) + }) + + t.Run("multiple ticks", func(t *testing.T) { + absTicks := []int32{50, 100, 250, 500, 1000, 2500, 3000, 4000, 5000, 50000, 150000, 250000, 500000, 738203} + expectedResults := []string{ + "79030349367926598376800521322", + "79426470787362580746886972461", + "78833030112140176575862854579", + "79625275426524748796330556128", + "78244023372248365697264290337", + "80224679980005306637834519095", + "77272108795590369356373805297", + "81233731461783161732293370115", + "75364347830767020784054125655", + "83290069058676223003182343270", + "69919044979842180277688105136", + "89776708723587163891445672585", + "68192822843687888778582228483", + "92049301871182272007977902845", + "64867181785621769311890333195", + "96768528593268422080558758223", + "61703726247759831737814779831", + "101729702841318637793976746270", + "6504256538020985011912221507", + "965075977353221155028623082916", + "43836292794701720435367485", + "143194173941309278083010301478497", + "295440463448801648376846", + "21246587762933397357449903968194344", + "1101692437043807371", + "5697689776495288729098254600827762987878", + "7409801140451", + "847134979253254120489401328389043031315994541", + } + + for i, absTick := range absTicks { + for j, tick := range []int32{-absTick, absTick} { + result := TickMathGetSqrtRatioAtTick(tick) + uassert.Equal(t, expectedResults[i*2+j], result.ToString()) + } + } + }) + + t.Run("min_sqrt_ratio", func(t *testing.T) { + sqrtPriceX96 := TickMathGetSqrtRatioAtTick(MIN_TICK) + uassert.Equal(t, "4295128739", sqrtPriceX96.ToString()) + }) + + t.Run("max_sqrt_ratio", func(t *testing.T) { + sqrtPriceX96 := TickMathGetSqrtRatioAtTick(MAX_TICK) + uassert.Equal(t, MAX_SQRT_RATIO, sqrtPriceX96.ToString()) + }) + + t.Run("throws for too low sqrt_ratio", func(t *testing.T) { + sqrtPriceX96 := u256.MustFromDecimal(MIN_SQRT_RATIO) + sqrtPriceX96.Sub(sqrtPriceX96, u256.One()) + + uassert.PanicsWithMessage( + t, + "[GNOSWAP-COMMON-003] value out of range || sqrtPriceX96 is out of range, sqrtPriceX96: 4295128738", + func() { + TickMathGetTickAtSqrtRatio(sqrtPriceX96) + }, + ) + }) + + t.Run("throws for too high sqrt_ratio", func(t *testing.T) { + sqrtPriceX96 := u256.MustFromDecimal(MAX_SQRT_RATIO) + sqrtPriceX96.Add(sqrtPriceX96, u256.One()) + + uassert.PanicsWithMessage( + t, + "[GNOSWAP-COMMON-003] value out of range || sqrtPriceX96 is out of range, sqrtPriceX96: 1461446703485210103287273052203988822378723970343", + func() { + TickMathGetTickAtSqrtRatio(sqrtPriceX96) + }, + ) + }) + + t.Run("ratio of min tick", func(t *testing.T) { + sqrtPriceX96 := TickMathGetSqrtRatioAtTick(MIN_TICK) + uassert.Equal(t, MIN_SQRT_RATIO, sqrtPriceX96.ToString()) + }) + + t.Run("ratio of min tick + 1", func(t *testing.T) { + sqrtPriceX96 := u256.MustFromDecimal("4295343490") + uassert.Equal(t, MIN_TICK+1, TickMathGetTickAtSqrtRatio(sqrtPriceX96)) + }) + + t.Run("multiple sqrt_ratios", func(t *testing.T) { + ratios := []string{ + "4295128739", + "79228162514264337593543950336000000", + "79228162514264337593543950336000", + "9903520314283042199192993792", + "28011385487393069959365969113", + "56022770974786139918731938227", + "79228162514264337593543950336", + "112045541949572279837463876454", + "224091083899144559674927752909", + "633825300114114700748351602688", + "79228162514264337593543950", + "79228162514264337593543", + "1461446703485210103287273052203988822378723970341", + } + expectedResults := []int32{ + -887272, + 276324, + 138162, + -41591, + -20796, + -6932, + 0, // got -1, expected 0 + 6931, + 20795, + 41590, + -138163, + -276325, + 887271, + } + + for i, ratio := range ratios { + sqrtPriceX96 := u256.MustFromDecimal(ratio) + rst := TickMathGetTickAtSqrtRatio(sqrtPriceX96) + uassert.Equal(t, expectedResults[i], rst) + } + }) + + t.Run("ratio closest to max tick", func(t *testing.T) { + maxSqrtRatio := u256.MustFromDecimal(MAX_SQRT_RATIO) + maxSqrtRatioSub1 := maxSqrtRatio.Sub(maxSqrtRatio, u256.One()) + + maxTick := MAX_TICK + maxTickSub1 := maxTick - 1 + + rst := TickMathGetTickAtSqrtRatio(maxSqrtRatioSub1) + uassert.Equal(t, maxTickSub1, rst) + }) +} diff --git a/_deploy/r/gnoswap/common/util.gno b/_deploy/r/gnoswap/common/util.gno new file mode 100644 index 000000000..1fe2f40af --- /dev/null +++ b/_deploy/r/gnoswap/common/util.gno @@ -0,0 +1,33 @@ +package common + +import ( + "std" + + u256 "gno.land/p/gnoswap/uint256" +) + +// assertOnlyNotNil panics if the value is nil. +func assertOnlyNotNil(value *u256.Uint) { + if value == nil { + panic(newErrorWithDetail( + errInvalidInput, + "value is nil", + )) + } +} + +// getPrevRealm returns object of the previous realm. +func getPrevRealm() std.Realm { + return std.PrevRealm() +} + +// getPrevAddr returns the address of the previous realm. +func getPrevAddr() std.Address { + return std.PrevRealm().Addr() +} + +// getPrevAsString returns the address and package path of the previous realm. +func getPrevAsString() (string, string) { + prev := getPrevRealm() + return prev.Addr().String(), prev.PkgPath() +} diff --git a/_deploy/r/gnoswap/consts/consts.gno b/_deploy/r/gnoswap/consts/consts.gno new file mode 100644 index 000000000..2b400c5de --- /dev/null +++ b/_deploy/r/gnoswap/consts/consts.gno @@ -0,0 +1,152 @@ +package consts + +import ( + "std" +) + +// GNOSWAP SERVICE +const ( + ADMIN std.Address = "g17290cwvmrapvp869xfnhhawa8sm9edpufzat7d" + DEV_OPS std.Address = "g1mjvd83nnjee3z2g7683er55me9f09688pd4mj9" + TOKEN_REGISTER std.Address = "g1er355fkjksqpdtwmhf5penwa82p0rhqxkkyhk5" + + TOKEN_REGISTER_NAMESPACE string = "gno.land/r/g1er355fkjksqpdtwmhf5penwa82p0rhqxkkyhk5" + + BLOCK_GENERATION_INTERVAL int64 = 2 // seconds +) + +// WRAP & UNWRAP +const ( + GNOT string = "gnot" + UGNOT string = "ugnot" + WRAPPED_WUGNOT string = "gno.land/r/demo/wugnot" + + // defined in https://github.com/gnolang/gno/blob/81a88a2976ba9f2f9127ebbe7fb7d1e1f7fa4bd4/examples/gno.land/r/demo/wugnot/wugnot.gno#L19 + UGNOT_MIN_DEPOSIT_TO_WRAP uint64 = 1000 +) + +// CONTRACT PATH & ADDRESS +const ( + POOL_PATH string = "gno.land/r/gnoswap/v1/pool" + POOL_ADDR std.Address = "g148tjamj80yyrm309z7rk690an22thd2l3z8ank" + + POSITION_PATH string = "gno.land/r/gnoswap/v1/position" + POSITION_ADDR std.Address = "g1q646ctzhvn60v492x8ucvyqnrj2w30cwh6efk5" + + ROUTER_PATH string = "gno.land/r/gnoswap/v1/router" + ROUTER_ADDR std.Address = "g1lm2l7tf49h3mykesct7rhfml30yx8dw5xrval7" + + STAKER_PATH string = "gno.land/r/gnoswap/v1/staker" + STAKER_ADDR std.Address = "g1cceshmzzlmrh7rr3z30j2t5mrvsq9yccysw9nu" + + GNS_PATH string = "gno.land/r/gnoswap/v1/gns" + GNS_ADDR std.Address = "g1jgqwaa2le3yr63d533fj785qkjspumzv22ys5m" + + GNFT_PATH string = "gno.land/r/gnoswap/v1/gnft" + GNFT_ADDR std.Address = "g1wxv2rdfn53qc84nt3nn646f9yh3nly8lm7j89t" + + WUGNOT_PATH string = "gno.land/r/demo/wugnot" + WUGNOT_ADDR std.Address = "g1pf6dv9fjk3rn0m4jjcne306ga4he3mzmupfjl6" + + EMISSION_PATH string = "gno.land/r/gnoswap/v1/emission" + EMISSION_ADDR std.Address = "g10xg6559w9e93zfttlhvdmaaa0er3zewcr7nh20" + + PROTOCOL_FEE_PATH string = "gno.land/r/gnoswap/v1/protocol_fee" + PROTOCOL_FEE_ADDR std.Address = "g1f7wpek7q67tkns27sw495u5yuu3a5wwjxw5l6l" + + COMMUNITY_POOL_PATH string = "gno.land/r/gnoswap/v1/community_pool" + COMMUNITY_POOL_ADDR std.Address = "g100fnnlz5eh87p5hvwt8pf279lxaelm8k8md049" + + GOV_XGNS_PATH string = "gno.land/r/gnoswap/v1/gov/xgns" + GOV_XGNS_ADDR std.Address = "g1wwh55uwzlz2zzr2qcvvxf83qhcvmx2t8779l9r" + + GOV_STAKER_PATH string = "gno.land/r/gnoswap/v1/gov/staker" + GOV_STAKER_ADDR std.Address = "g17e3ykyqk9jmqe2y9wxe9zhep3p7cw56davjqwa" + + GOV_GOVERNANCE_PATH string = "gno.land/r/gnoswap/v1/gov/governance" + GOV_GOVERNANCE_ADDR std.Address = "g17s8w2ve7k85fwfnrk59lmlhthkjdted8whvqxd" + + COMMON_PATH string = "gno.land/r/gnoswap/v1/common" + COMMON_ADDR std.Address = "g14ytarn5u7h3xywygt8hzhs3m23frljz72ta9xk" + + LAUNCHPAD_PATH string = "gno.land/r/gnoswap/v1/launchpad" + LAUNCHPAD_ADDR std.Address = "g122mau2lp2rc0scs8d27pkkuys4w54mdy2tuer3" + + INIT_REGISTER_PATH string = "gno.land/r/g1er355fkjksqpdtwmhf5penwa82p0rhqxkkyhk5/v2/register_gnodev" +) + +// NUMBER +const ( + // calculated by https://mathiasbynens.be/demo/integer-range + MAX_UINT8 string = "255" + UINT8_MAX uint8 = 255 + + MAX_UINT16 string = "65535" + UINT16_MAX uint16 = 65535 + + MAX_UINT32 string = "4294967295" + UINT32_MAX uint32 = 4294967295 + + MAX_UINT64 string = "18446744073709551615" + UINT64_MAX uint64 = 18446744073709551615 + + MAX_UINT128 string = "340282366920938463463374607431768211455" + MAX_UINT160 string = "1461501637330902918203684832716283019655932542975" + MAX_UINT256 string = "115792089237316195423570985008687907853269984665640564039457584007913129639935" + + MAX_INT128 string = "170141183460469231731687303715884105727" + MAX_INT256 string = "57896044618658097711785492504343953926634992332820282019728792003956564819968" + + // Tick Related + MIN_TICK int32 = -887272 + MAX_TICK int32 = 887272 + + MIN_SQRT_RATIO string = "4295128739" // same as TickMathGetSqrtRatioAtTick(MIN_TICK) + MAX_SQRT_RATIO string = "1461446703485210103287273052203988822378723970342" // same as TickMathGetSqrtRatioAtTick(MAX_TICK) + + MIN_PRICE string = "4295128740" // MIN_SQRT_RATIO + 1 + MAX_PRICE string = "1461446703485210103287273052203988822378723970341" // MAX_SQRT_RATIO - 1 + + // ETC + Q64 string = "18446744073709551616" // 2 ** 64 + Q96 string = "79228162514264337593543950336" // 2 ** 96 + Q128 string = "340282366920938463463374607431768211456" // 2 ** 128 + + Q96_RESOLUTION uint = 96 + Q128_RESOLUTION uint = 128 + Q160_RESOLUTION uint = 160 +) + +// TIMESTAMP & DAY +const ( + SECOND_IN_MILLISECOND = 1000 + + // in seconds + TIMESTAMP_MINUTE = 60 + TIMESTAMP_HOUR = 3600 + TIMESTAMP_DAY = 86400 + TIMESTAMP_YEAR = 31536000 + + DAY_PER_YEAR = 365 +) + +// ETCs +const ( + // REF: https://github.com/gnolang/gno/pull/2401#discussion_r1648064219 + ZERO_ADDRESS std.Address = "g100000000000000000000000000000000dnmcnx" +) + +// EMISSION +// TODO: +// This is a temporary solution to prevent the emission from being refactored. +// 1. After refactoring, remove this var +var ( + EMISSION_REFACTORED bool = true +) + +func setEmissionRefactored(value bool) { + caller := std.PrevRealm().Addr() + if caller == ADMIN { + EMISSION_REFACTORED = value + } +} diff --git a/_deploy/r/gnoswap/consts/gno.mod b/_deploy/r/gnoswap/consts/gno.mod new file mode 100644 index 000000000..2eba8c660 --- /dev/null +++ b/_deploy/r/gnoswap/consts/gno.mod @@ -0,0 +1 @@ +module gno.land/r/gnoswap/v1/consts diff --git a/_deploy/r/gnoswap/gnft/errors.gno b/_deploy/r/gnoswap/gnft/errors.gno new file mode 100644 index 000000000..6565d9c65 --- /dev/null +++ b/_deploy/r/gnoswap/gnft/errors.gno @@ -0,0 +1,19 @@ +package gnft + +import ( + "errors" + + "gno.land/p/demo/ufmt" +) + +var ( + errNoPermission = errors.New("[GNOSWAP-GNFT-001] caller has no permission") + errCannotSetURI = errors.New("[GNOSWAP-GNFT-002] cannot set URI") + errNoTokenForCaller = errors.New("[GNOSWAP-GNFT-003] no token for caller") + errInvalidAddress = errors.New("[GNOSWAP-GNFT-004] invalid addresss") +) + +func addDetailToError(err error, detail string) string { + finalErr := ufmt.Errorf("%s || %s", err.Error(), detail) + return finalErr.Error() +} diff --git a/_deploy/r/gnoswap/gnft/gnft.gno b/_deploy/r/gnoswap/gnft/gnft.gno new file mode 100644 index 000000000..6a132aa73 --- /dev/null +++ b/_deploy/r/gnoswap/gnft/gnft.gno @@ -0,0 +1,507 @@ +package gnft + +import ( + "math/rand" + "std" + "time" + + "gno.land/p/demo/avl" + "gno.land/p/demo/grc/grc721" + "gno.land/p/demo/ownable" + "gno.land/p/demo/ufmt" + "gno.land/r/gnoswap/v1/common" + "gno.land/r/gnoswap/v1/consts" +) + +var ( + gnft = grc721.NewBasicNFT("GNOSWAP NFT", "GNFT") +) + +var ( + owner *ownable.Ownable + tokenList *avl.Tree // addr -> []grc721.TokenID +) + +func init() { + owner = ownable.NewWithAddress(consts.POSITION_ADDR) // deployed position contract + tokenList = avl.NewTree() +} + +// Name returns the full name of the NFT +// Returns: +// - string: The name of the NFT collection +func Name() string { + return gnft.Name() +} + +// Symbol returns the token symbol of the NFT +// Returns: +// - string: The symbol of the NFT collection +func Symbol() string { + return gnft.Symbol() +} + +// TotalSupply returns the total number of NFTs minted +// Returns: +// - uint64: The total number of tokens that have been minted +func TotalSupply() uint64 { + return gnft.TokenCount() +} + +// TokenURI retrieves the metadata URI for a specific token ID +// Parameters: +// - tid: The unique identifier of the token +// +// Returns: +// - string: The metadata URI associated with the token +func TokenURI(tid grc721.TokenID) string { + uri, err := gnft.TokenURI(tid) + if err != nil { + panic(err.Error()) + } + + return string(uri) +} + +// BalanceOf returns the number of NFTs owned by the specified address. +// Parameters: +// - owner (std.Address): The address to check the NFT balance for. +// +// Returns: +// - uint64: The number of NFTs owned by the address. +// - error: Returns an error if the balance retrieval fails. +func BalanceOf(owner std.Address) (uint64, error) { + balance, err := gnft.BalanceOf(owner) + if err != nil { + panic(err.Error()) + } + return balance, nil +} + +// OwnerOf returns the current owner's address of a specific token ID +// Parameters: +// - tid: The token ID to check ownership of +// +// Returns: +// - std.Address: The address of the token owner +func OwnerOf(tid grc721.TokenID) (std.Address, error) { + ownerAddr, err := gnft.OwnerOf(tid) + if err != nil { + return "", err + } + + return ownerAddr, nil +} + +// MustOwnerOf returns the current owner's address of a specific token ID +// Parameters: +// - tid: The token ID to check ownership of +// +// Returns: +// - std.Address: The address of the token owner +// +// Panics: +// - If the token ID is invalid +func MustOwnerOf(tid grc721.TokenID) std.Address { + ownerAddr, err := OwnerOf(tid) + if err != nil { + panic(err.Error()) + } + + return ownerAddr +} + +// SetTokenURI sets the metadata URI using a randomly generated SVG image +// Parameters: +// - tid (grc721.TokenID): The token ID for which the URI will be updated. +// - tURI (grc721.TokenURI): The new metadata URI to associate with the token. +// +// Returns: +// - bool: Returns `true` if the operation is successful. +// - error: Returns an error if the operation fails or the caller is not authorized. +// +// Panics: +// - If the caller is not the token owner, the function panics. +// - If the URI update fails, the function panics with the associated error. +func SetTokenURI(tid grc721.TokenID, tURI grc721.TokenURI) (bool, error) { + assertOnlyNotHalted() + assertCallerIsOwnerOfToken(tid) + + err := setTokenURI(tid, tURI) + if err != nil { + panic(addDetailToError( + errCannotSetURI, + ufmt.Sprintf("token id (%s)", tid), + )) + } + return true, nil +} + +// SafeTransferFrom securely transfers ownership of a token from one address to another. +// +// This function enforces several checks to ensure the transfer is valid and authorized: +// - Ensures the contract is not halted. +// - Validates the addresses involved in the transfer. +// - Checks that the caller is the token owner or has been approved to transfer the token. +// +// After validation, the function updates the internal token lists by removing the token from the sender's list +// and appending it to the recipient's list. It then calls the underlying transfer logic through `gnft.TransferFrom`. +// +// Parameters: +// - from (std.Address): The current owner's address of the token being transferred. +// - to (std.Address): The recipient's address to receive the token. +// - tid (grc721.TokenID): The ID of the token to be transferred. +// +// Returns: +// - error: Returns `nil` if the transfer is successful; otherwise, it raises an error. +// +// Panics: +// - If the contract is halted. +// - If either `from` or `to` addresses are invalid. +// - If the caller is not the owner or approved operator of the token. +// - If the internal transfer (`gnft.TransferFrom`) fails. +func SafeTransferFrom(from, to std.Address, tid grc721.TokenID) error { + assertOnlyNotHalted() + + assertValidAddr(from) + assertValidAddr(to) + + caller := getPrevAddr() + ownerAddr, _ := OwnerOf(tid) + approved, _ := GetApproved(tid) + if (caller != ownerAddr) && (caller != approved) { + panic(addDetailToError( + errNoPermission, + ufmt.Sprintf("caller (%s) is not the owner or operator of token (%s)", caller, string(tid)), + )) + } + + removeTokenList(from, tid) + appendTokenList(to, tid) + + checkErr(gnft.TransferFrom(from, to, tid)) + return nil +} + +// TransferFrom transfers a token from one address to another +// This function is a direct wrapper around `SafeTransferFrom`, which performs the actual transfer. +// +// Parameters: +// - from (std.Address): The current owner's address of the token being transferred. +// - to (std.Address): The recipient's address to receive the token. +// - tid (grc721.TokenID): The ID of the token to be transferred. +// +// Returns: +// - error: Returns `nil` if the transfer is successful; otherwise, returns an error. +func TransferFrom(from, to std.Address, tid grc721.TokenID) error { + return SafeTransferFrom(from, to, tid) +} + +// Approve grants permission to transfer a specific token ID to another address. +// +// Parameters: +// - approved (std.Address): The address to grant transfer approval to. +// - tid (grc721.TokenID): The token ID to approve for transfer. +// +// Returns: +// - error: Returns `nil` if the approval is successful, otherwise returns an error. +// +// Panics: +// - If the contract is halted. +// - If the caller is not the token owner. +// - If the `Approve` call fails. +func Approve(approved std.Address, tid grc721.TokenID) error { + assertOnlyNotHalted() + assertCallerIsOwnerOfToken(tid) + + err := gnft.Approve(approved, tid) + if err != nil { + panic(err.Error()) + } + return nil +} + +// SetApprovalForAll enables or disables approval for a third party (`operator`) to manage all tokens owned by the caller. +// +// Parameters: +// - operator (std.Address): The address to grant or revoke operator permissions for. +// - approved (bool): `true` to enable approval, `false` to revoke approval. +// +// Returns: +// - error: Returns `nil` if the operation is successful, otherwise returns an error. +// +// Panics: +// - If the contract is halted. +// - If the `SetApprovalForAll` operation fails. +func SetApprovalForAll(operator std.Address, approved bool) error { + assertOnlyNotHalted() + checkErr(gnft.SetApprovalForAll(operator, approved)) + return nil +} + +// GetApproved returns the approved address for a specific token ID. +// +// Parameters: +// - tid (grc721.TokenID): The token ID to check for approval. +// +// Returns: +// - std.Address: The address approved to manage the token. Returns an empty address if no approval exists. +// - error: Returns an error if the lookup fails or the token ID is invalid. +func GetApproved(tid grc721.TokenID) (std.Address, error) { + addr, err := gnft.GetApproved(tid) + if err != nil { + return "", err + } + + return addr, nil +} + +// IsApprovedForAll checks if an operator is approved to manage all tokens of an owner. +// +// Parameters: +// - owner (std.Address): The address of the token owner. +// - operator (std.Address): The address to check if it has approval to manage the owner's tokens. +// +// Returns: +// - bool: true if the operator is approved to manage all tokens of the owner, false otherwise. +func IsApprovedForAll(owner, operator std.Address) bool { + return gnft.IsApprovedForAll(owner, operator) +} + +// SetTokenURIByImageURI generates and sets a new token URI for a specified token ID using a random image URI. +// +// Parameters: +// - tid (grc721.TokenID): The ID of the token for which the URI will be set. +// +// Panics: +// - If the contract is halted. +// - If the caller is not the owner of the token. +// - If the token URI cannot be set. +func SetTokenURIByImageURI(tid grc721.TokenID) { + assertOnlyNotHalted() + assertCallerIsOwnerOfToken(tid) + + tokenURI := genImageURI(generateRandInstance()) + + err := setTokenURI(tid, grc721.TokenURI(tokenURI)) + if err != nil { + panic(addDetailToError( + errCannotSetURI, + ufmt.Sprintf("%s (%s)", err.Error(), string(tid)), + )) + } +} + +// SetTokenURILast sets the token URI for the last token owned by the caller using a randomly generated image URI. +// +// This function ensures the contract is active and the caller owns at least one token. +// It retrieves the list of tokens owned by the caller and applies a new token URI to the most recently minted token. +// +// Panics: +// - If the contract is halted. +// - If the caller does not own any tokens (empty token list). +// - If URI generation or assignment fails. +func SetTokenURILast() { + assertOnlyNotHalted() + + caller := getPrevAddr() + tokenListByCaller, _ := getTokenList(caller) + lenTokenListByCaller := len(tokenListByCaller) + if lenTokenListByCaller == 0 { + panic(addDetailToError( + errNoTokenForCaller, + ufmt.Sprintf("caller (%s)", caller), + )) + } + + lastTokenId := tokenListByCaller[lenTokenListByCaller-1] + SetTokenURIByImageURI(lastTokenId) +} + +// Mint creates a new NFT and assigns it to the specified address (only callable by owner) +// Parameters: +// - to: The address or username to mint the token to +// - tid: The token ID to assign to the new NFT +// +// Returns: +// - grc721.TokenID: The ID of the newly minted token +func Mint(to std.Address, tid grc721.TokenID) grc721.TokenID { + owner.AssertCallerIsOwner() + assertOnlyNotHalted() + + checkErr(gnft.Mint(to, tid)) + + appendTokenList(to, tid) + return tid +} + +// Burn removes a specific token ID (only callable by owner) +// Parameters: +// - tid: The token ID to burn +func Burn(tid grc721.TokenID) { + owner.AssertCallerIsOwner() + assertOnlyNotHalted() + + ownerAddr, err := OwnerOf(tid) + if err != nil { + panic(err.Error()) + } + removeTokenList(ownerAddr, tid) + + checkErr(gnft.Burn(tid)) +} + +// Render returns the HTML representation of the NFT +// Parameters: +// - path: The path to render +// +// Returns: +// - string: HTML representation of the NFT or 404 if path is invalid +func Render(path string) string { + switch { + case path == "": + return gnft.RenderHome() + default: + return "404\n" + } +} + +// setTokenURI sets the metadata URI for a specific token ID +func setTokenURI(tid grc721.TokenID, tURI grc721.TokenURI) error { + assertOnlyEmptyTokenURI(tid) + _, err := gnft.SetTokenURI(tid, tURI) + if err != nil { + return err + } + + prevAddr, prevPkgPath := getPrevAsString() + std.Emit( + "SetTokenURI", + "prevAddr", prevAddr, + "prevRealm", prevPkgPath, + "lpTokenId", "tid", + "tokenURI", "tURI", + ) + + return nil +} + +// generateRandInstnace generates a new random instance +// Returns: +// - *rand.Rand: A new random instance +func generateRandInstance() *rand.Rand { + seed1 := uint64(time.Now().Unix()) + TotalSupply() + seed2 := uint64(time.Now().UnixNano()) + TotalSupply() + pcg := rand.NewPCG(seed1, seed2) + return rand.New(pcg) +} + +// getTokenList retrieves the list of nft tokens for an address +// Parameters: +// - addr: The address to check for nft tokens +// +// Returns: +// - []grc721.TokenID: Array of token IDs +// - bool: true if tokens exist for the address, false otherwise +func getTokenList(addr std.Address) ([]grc721.TokenID, bool) { + iTokens, exists := tokenList.Get(addr.String()) + if !exists { + return []grc721.TokenID{}, false + } + + return iTokens.([]grc721.TokenID), true +} + +// mustGetTokenList same as getTokenList but panics if tokens don't exist +// Parameters: +// - addr: The address to check for nft tokens +// +// Returns: +// - []grc721.TokenID: Array of token IDs +func mustGetTokenList(addr std.Address) []grc721.TokenID { + tokens, exists := getTokenList(addr) + if !exists { + panic(ufmt.Sprintf("user %s has no minted nft tokens", addr.String())) + } + + return tokens +} + +// appendTokenList adds a token ID to the list of nft tokens +// Parameters: +// - addr: The address to append the token for +// - tid: The token ID to append +func appendTokenList(addr std.Address, tid grc721.TokenID) { + prevTokenList, _ := getTokenList(addr) + prevTokenList = append(prevTokenList, tid) + tokenList.Set(addr.String(), prevTokenList) +} + +// removeTokenList removes a token ID from the list of nft tokens +// Parameters: +// - addr: The address to remove the token for +// - tid: The token ID to remove +func removeTokenList(addr std.Address, tid grc721.TokenID) { + prevTokenList, exist := getTokenList(addr) + if !exist { + return + } + + for i, token := range prevTokenList { + if token == tid { + prevTokenList = append(prevTokenList[:i], prevTokenList[i+1:]...) + break + } + } + + tokenList.Set(addr.String(), prevTokenList) +} + +// checkErr helper function to panic if an error occurs +// Parameters: +// - err: The error to check +func checkErr(err error) { + if err != nil { + panic(err.Error()) + } +} + +// assertCallerIsOwnerOfToken asserts that the caller is the owner of the token +// Parameters: +// - tid: The token ID to check ownership of +func assertCallerIsOwnerOfToken(tid grc721.TokenID) { + caller := getPrevAddr() + owner, _ := OwnerOf(tid) + if caller != owner { + panic(addDetailToError( + errNoPermission, + ufmt.Sprintf("caller (%s) is not the owner of token (%s)", caller, string(tid)), + )) + } +} + +// assertOnlyNotHalted panics if the contract is halted. +func assertOnlyNotHalted() { + common.IsHalted() +} + +// assertValidAddr panics if the address is invalid.s +func assertValidAddr(addr std.Address) { + if !addr.IsValid() { + panic(addDetailToError( + errInvalidAddress, + addr.String(), + )) + } +} + +// assertOnlyEmptyTokenURI panics if the token URI is not empty. +func assertOnlyEmptyTokenURI(tid grc721.TokenID) { + uri, _ := gnft.TokenURI(tid) + if string(uri) != "" { + panic(addDetailToError( + errCannotSetURI, + ufmt.Sprintf("token id (%s) has already set URI", string(tid)), + )) + } +} diff --git a/_deploy/r/gnoswap/gnft/gnft_test.gno b/_deploy/r/gnoswap/gnft/gnft_test.gno new file mode 100644 index 000000000..02f92ebee --- /dev/null +++ b/_deploy/r/gnoswap/gnft/gnft_test.gno @@ -0,0 +1,529 @@ +package gnft + +import ( + "std" + "testing" + + "gno.land/p/demo/avl" + "gno.land/p/demo/grc/grc721" + "gno.land/p/demo/testutils" + "gno.land/p/demo/uassert" + + "gno.land/r/gnoswap/v1/consts" +) + +const ( + errInvalidTokenId = "invalid token id" +) + +var ( + positionAddr = consts.POSITION_ADDR + positionRealm = std.NewCodeRealm(consts.POSITION_PATH) + + addr01 = testutils.TestAddress("addr01") + addr01Realm = std.NewUserRealm(addr01) + + addr02 = testutils.TestAddress("addr02") + addr02Realm = std.NewUserRealm(addr02) +) + +func TestMetadata(t *testing.T) { + tests := []struct { + name string + fn func() string + expected string + }{ + {"Name()", Name, "GNOSWAP NFT"}, + {"Symbol()", Symbol, "GNFT"}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + uassert.Equal(t, tt.expected, tt.fn()) + }) + } +} + +func TestTotalSupply(t *testing.T) { + tests := []struct { + name string + setup func() + expected uint64 + }{ + { + name: "initial total supply", + expected: uint64(0), + }, + { + name: "total supply after minting", + setup: func() { + std.TestSetRealm(positionRealm) + Mint(addr01, tid(1)) + Mint(addr01, tid(2)) + }, + expected: uint64(2), + }, + { + name: "total supply after burning", + setup: func() { + std.TestSetRealm(positionRealm) + Burn(tid(2)) + }, + expected: uint64(1), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.setup != nil { + tt.setup() + } + uassert.Equal(t, tt.expected, TotalSupply()) + }) + } +} + +func TestBalanceOf(t *testing.T) { + tests := []struct { + name string + addr std.Address + expected uint64 + }{ + {"BalanceOf(addr01)", addr01, uint64(1)}, + {"BalanceOf(addr02)", addr02, uint64(0)}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + balance, _ := BalanceOf(tt.addr) + uassert.Equal(t, tt.expected, balance) + }) + } +} + +func TestOwnerOf(t *testing.T) { + tests := []struct { + name string + tokenId uint64 + shouldPanic bool + panicMsg string + expected std.Address + }{ + {"OwnerOf(1)", 1, false, "", addr01}, + {"OwnerOf(500)", 500, false, errInvalidTokenId, addr01}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.shouldPanic { + uassert.PanicsWithMessage(t, tt.panicMsg, func() { + OwnerOf(tid(tt.tokenId)) + }) + } else { + ownerAddr, err := OwnerOf(tid(tt.tokenId)) + if err != nil { + uassert.Equal(t, tt.panicMsg, err.Error()) + } else { + uassert.Equal(t, tt.expected, ownerAddr) + } + } + }) + } +} + +func TestIsApprovedForAll(t *testing.T) { + tests := []struct { + name string + setup func() + expected bool + }{ + { + name: "IsApprovedForAll(addr01, addr02)", + expected: false, + }, + { + name: "IsApprovedForAll(addr01, addr02) after setting approval", + setup: func() { + std.TestSetRealm(addr01Realm) + SetApprovalForAll((addr02), true) + }, + expected: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.setup != nil { + tt.setup() + } + uassert.Equal(t, tt.expected, IsApprovedForAll((addr01), (addr02))) + }) + } +} + +func TestGetApproved(t *testing.T) { + tests := []struct { + name string + setup func() + expectedAddr std.Address + }{ + { + name: "GetApproved(1)", + expectedAddr: std.Address(""), + }, + { + name: "GetApproved(1) after approving", + setup: func() { + std.TestSetRealm(addr01Realm) + Approve(addr02, tid(1)) + }, + expectedAddr: addr02, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.setup != nil { + tt.setup() + } + + addr, _ := GetApproved(tid(1)) + uassert.Equal(t, tt.expectedAddr, addr) + }) + } +} + +func TestTransferFrom(t *testing.T) { + resetObject(t) + std.TestSetRealm(positionRealm) + Mint(addr01, tid(1)) + + tests := []struct { + name string + setup func() + callerRealm std.Realm + fromAddr std.Address + toAddr std.Address + tokenIdToTransfer uint64 + shouldPanic bool + panicMsg string + expected std.Address + verifyTokenList func() + }{ + { + name: "transfer non-existent token id", + callerRealm: std.NewUserRealm(addr01), + fromAddr: addr01, + toAddr: addr02, + tokenIdToTransfer: 99, + shouldPanic: true, + panicMsg: "[GNOSWAP-GNFT-001] caller has no permission || caller (g1q646ctzhvn60v492x8ucvyqnrj2w30cwh6efk5) is not the owner or operator of token (99)", + }, + { + name: "transfer token owned by other user without approval", + callerRealm: std.NewUserRealm(addr02), + fromAddr: addr01, + toAddr: addr02, + tokenIdToTransfer: 1, + shouldPanic: true, + panicMsg: "[GNOSWAP-GNFT-001] caller has no permission || caller (g1q646ctzhvn60v492x8ucvyqnrj2w30cwh6efk5) is not the owner or operator of token (1)", + }, + { + name: "transfer token owned by other user with approval", + setup: func() { + std.TestSetRealm(addr01Realm) + Approve((addr02), tid(1)) + }, + callerRealm: std.NewUserRealm(addr02), + fromAddr: addr01, + toAddr: addr02, + tokenIdToTransfer: 1, + verifyTokenList: func() { + uassert.Equal(t, 0, len(mustGetTokenList(addr01))) + uassert.Equal(t, 1, len(mustGetTokenList(addr02))) + }, + }, + { + name: "transfer token owned by caller", + callerRealm: std.NewUserRealm(addr02), + fromAddr: addr02, + toAddr: addr01, + tokenIdToTransfer: 1, + verifyTokenList: func() { + uassert.Equal(t, 1, len(mustGetTokenList(addr01))) + uassert.Equal(t, 0, len(mustGetTokenList(addr02))) + }, + }, + { + name: "transfer from is invalid address", + callerRealm: std.NewUserRealm(addr01), + fromAddr: std.Address(""), + toAddr: addr02, + tokenIdToTransfer: 1, + shouldPanic: true, + panicMsg: "[GNOSWAP-GNFT-004] invalid addresss || ", + }, + { + name: "transfer to is invalid address", + callerRealm: std.NewUserRealm(addr01), + fromAddr: addr01, + toAddr: std.Address("this_is_invalid_address"), + tokenIdToTransfer: 1, + shouldPanic: true, + panicMsg: "[GNOSWAP-GNFT-004] invalid addresss || this_is_invalid_address", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.setup != nil { + tt.setup() + } + + if tt.shouldPanic { + uassert.PanicsWithMessage(t, tt.panicMsg, func() { + TransferFrom((tt.fromAddr), (tt.toAddr), tid(tt.tokenIdToTransfer)) + }) + } else { + std.TestSetRealm(tt.callerRealm) + TransferFrom((tt.fromAddr), (tt.toAddr), tid(tt.tokenIdToTransfer)) + tt.verifyTokenList() + } + }) + } +} + +func TestMint(t *testing.T) { + resetObject(t) + + tests := []struct { + name string + callerRealm std.Realm + tokenIdToMint uint64 + addressToMint std.Address + shouldPanic bool + panicMsg string + expected string + verifyTokenList func() + }{ + { + name: "mint without permission", + shouldPanic: true, + panicMsg: "ownable: caller is not owner", + }, + { + name: "mint first nft to addr01", + callerRealm: std.NewCodeRealm(consts.POSITION_PATH), + tokenIdToMint: 1, + addressToMint: addr01, + expected: "1", + verifyTokenList: func() { + uassert.Equal(t, 1, len(mustGetTokenList(addr01))) + }, + }, + { + name: "mint second nft to addr02", + callerRealm: std.NewCodeRealm(consts.POSITION_PATH), + tokenIdToMint: 2, + addressToMint: addr02, + expected: "2", + verifyTokenList: func() { + uassert.Equal(t, 1, len(mustGetTokenList(addr02))) + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.shouldPanic { + uassert.PanicsWithMessage(t, tt.panicMsg, func() { + Mint((tt.addressToMint), tid(tt.tokenIdToMint)) + }) + + } else { + std.TestSetRealm(tt.callerRealm) + mintedTokenId := Mint((tt.addressToMint), tid(tt.tokenIdToMint)) + uassert.Equal(t, tt.expected, string(mintedTokenId)) + tt.verifyTokenList() + } + }) + } +} + +func TestBurn(t *testing.T) { + tests := []struct { + name string + callerRealm std.Realm + tokenIdToBurn uint64 + shouldPanic bool + panicMsg string + verifyTokenList func() + }{ + { + name: "burn without permission", + tokenIdToBurn: 1, + shouldPanic: true, + panicMsg: "ownable: caller is not owner", + }, + { + name: "burn non-existent token id", + callerRealm: std.NewCodeRealm(consts.POSITION_PATH), + tokenIdToBurn: 99, + shouldPanic: true, + panicMsg: errInvalidTokenId, + }, + { + name: "burn token id(2)", + callerRealm: std.NewCodeRealm(consts.POSITION_PATH), + tokenIdToBurn: 2, + shouldPanic: false, + verifyTokenList: func() { + uassert.Equal(t, 0, len(mustGetTokenList(addr02))) + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + std.TestSetRealm(tt.callerRealm) + + if tt.shouldPanic { + uassert.PanicsWithMessage(t, tt.panicMsg, func() { + Burn(tid(tt.tokenIdToBurn)) + }) + } else { + uassert.NotPanics(t, func() { + Burn(tid(tt.tokenIdToBurn)) + }) + tt.verifyTokenList() + } + }) + } +} + +func TestSetTokenURI(t *testing.T) { + tests := []struct { + name string + callerRealm std.Realm + tokenId uint64 + shouldPanic bool + panicMsg string + }{ + { + name: "set token uri without permission", + tokenId: 1, + shouldPanic: true, + panicMsg: `[GNOSWAP-GNFT-001] caller has no permission || caller () is not the owner of token (1)`, + }, + { + name: "set token uri of non-minted token id", + tokenId: 99, + shouldPanic: true, + panicMsg: `[GNOSWAP-GNFT-002] cannot set URI || invalid token id (99)`, + }, + { + name: "set token uri of token id(1)", + callerRealm: addr01Realm, + tokenId: 1, + }, + { + name: "set token uri of token id(1) - twice", + callerRealm: addr01Realm, + tokenId: 1, + shouldPanic: true, + panicMsg: "[GNOSWAP-GNFT-002] cannot set URI || token id (1) has already set URI", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + std.TestSetRealm(tt.callerRealm) + + if tt.shouldPanic { + uassert.PanicsWithMessage(t, tt.panicMsg, func() { + SetTokenURIByImageURI(tid(tt.tokenId)) + }) + } else { + uassert.NotPanics(t, func() { + SetTokenURIByImageURI(tid(tt.tokenId)) + }) + } + }) + } +} + +func TestTokenURI(t *testing.T) { + resetObject(t) + + tests := []struct { + name string + setup func() + tokenId uint64 + shouldPanic bool + panicMsg string + }{ + { + name: "get token uri of non-minted token id", + tokenId: 99, + shouldPanic: true, + panicMsg: errInvalidTokenId, + }, + { + name: "get token uri of minted token but not set token uri", + setup: func() { + std.TestSetRealm(positionRealm) + Mint((addr01), tid(1)) + }, + tokenId: 1, + shouldPanic: true, + panicMsg: errInvalidTokenId, + }, + { + name: "get token uri of minted token after setting token uri", + setup: func() { + std.TestSetRealm(addr01Realm) + SetTokenURIByImageURI(tid(1)) + }, + tokenId: 1, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.setup != nil { + tt.setup() + } + + if tt.shouldPanic { + uassert.PanicsWithMessage(t, tt.panicMsg, func() { + TokenURI(tid(tt.tokenId)) + }) + } else { + uassert.NotEmpty(t, TokenURI(tid(tt.tokenId))) + } + }) + } +} + +func TestSetTokenURILast(t *testing.T) { + resetObject(t) + std.TestSetRealm(positionRealm) + Mint(addr01, tid(1)) + Mint(addr01, tid(2)) // last minted + + t.Run("set token uri last", func(t *testing.T) { + std.TestSetRealm(addr01Realm) + SetTokenURILast() + }) + + t.Run("token uri(2)", func(t *testing.T) { + uassert.NotEmpty(t, TokenURI(tid(2))) + }) +} + +func resetObject(t *testing.T) { + t.Helper() + + gnft = grc721.NewBasicNFT("GNOSWAP NFT", "GNFT") + tokenList = avl.NewTree() +} diff --git a/_deploy/r/gnoswap/gnft/gno.mod b/_deploy/r/gnoswap/gnft/gno.mod new file mode 100644 index 000000000..3af907ab9 --- /dev/null +++ b/_deploy/r/gnoswap/gnft/gno.mod @@ -0,0 +1 @@ +module gno.land/r/gnoswap/v1/gnft diff --git a/_deploy/r/gnoswap/gnft/svg_generator.gno b/_deploy/r/gnoswap/gnft/svg_generator.gno new file mode 100644 index 000000000..49d6e3186 --- /dev/null +++ b/_deploy/r/gnoswap/gnft/svg_generator.gno @@ -0,0 +1,67 @@ +package gnft + +import ( + b64 "encoding/base64" + "math/rand" + "strings" + + "gno.land/p/demo/ufmt" +) + +var baseTempalte = ` + + + + + + + + + + + + + + + + + + + + + + + +` + +// range for hex color +const charset = "0123456789ABCDEF" + +func genImageURI(r *rand.Rand) string { + imageRaw := genImageRaw(r) + sEnc := b64.StdEncoding.EncodeToString([]byte(imageRaw)) + + return "data:image/svg+xml;base64," + sEnc +} + +func genImageRaw(r *rand.Rand) string { + x1 := 7 + r.Uint64N(7) + y1 := 7 + r.Uint64N(7) + + x2 := 121 + r.Uint64N(6) + y2 := 121 + r.Uint64N(6) + + var color1, color2 strings.Builder + color1.Grow(7) + color2.Grow(7) + color1.WriteByte('#') + color2.WriteByte('#') + + for i := 0; i < 6; i++ { + color1.WriteByte(charset[r.IntN(16)]) + color2.WriteByte(charset[r.IntN(16)]) + } + + randImage := ufmt.Sprintf(baseTempalte, x1, y1, x2, y2, color1.String(), color2.String()) + return randImage +} diff --git a/_deploy/r/gnoswap/gnft/svg_generator_test.gno b/_deploy/r/gnoswap/gnft/svg_generator_test.gno new file mode 100644 index 000000000..4ef09cc7d --- /dev/null +++ b/_deploy/r/gnoswap/gnft/svg_generator_test.gno @@ -0,0 +1,25 @@ +package gnft + +import ( + "math/rand" + "testing" + "time" + + "gno.land/p/demo/uassert" +) + +func TestGenImageURI(t *testing.T) { + seed1 := uint64(time.Now().Unix()) + seed2 := uint64(time.Now().UnixNano()) + pcg := rand.NewPCG(seed1, seed2) + r := rand.New(pcg) + + var uri string + uassert.NotPanics(t, func() { + uri = genImageURI(r) + }) + + expectedUri := `data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTM1IiBoZWlnaHQ9IjEzNSIgdmlld0JveD0iMCAwIDEzNSAxMzUiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMF83Njk4XzU2ODQ2KSI+CjxjaXJjbGUgY3g9IjY3LjUiIGN5PSI2Ny41IiByPSI2Ny41IiBmaWxsPSJ1cmwoI3BhaW50MF9saW5lYXJfNzY5OF81Njg0NikiLz4KPHBhdGggZD0iTTUxLjI5MDUgNDIuOTQ0OUw2Ni40ODk1IDMzTDk3IDUyLjgwNjFMODEuODI0MSA2Mi43NDI1TDUxLjI5MDUgNDIuOTQ0OVoiIGZpbGw9IndoaXRlIi8+CjxwYXRoIGQ9Ik01MS42MDU1IDY3LjUwNTlMNjYuODA0NCA1Ny41NjFMOTcgNzcuMDY1N0w4Mi4xMDQ2IDg3LjE3OTNMNTEuNjA1NSA2Ny41MDU5WiIgZmlsbD0id2hpdGUiIGZpbGwtb3BhY2l0eT0iMC40Ii8+CjxwYXRoIGQ9Ik0zNi4wNDY0IDgxLjc1NTlMNTEuMjkwNSA3MS44MTFMODEuNzMzNiA5MS42NTQ3TDY2LjQ4OTUgMTAxLjUwOEwzNi4wNDY0IDgxLjc1NTlaIiBmaWxsPSJ3aGl0ZSIgZmlsbC1vcGFjaXR5PSIwLjYiLz4KPHBhdGggZD0iTTM2LjAwMSA1Mi44MDU1TDUxLjI4ODQgNDIuOTE3N0w1MS4yODg0IDcxLjgxNDVMMzYuMDAxIDgxLjc3OUwzNi4wMDEgNTIuODA1NVoiIGZpbGw9IndoaXRlIi8+CjxwYXRoIGQ9Ik04Mi4xMDUxIDg3LjE3OTdMOTcuMDAxNiA3Ny4wNjYyTDk3LjAwMTYgODEuNzAyOUw4MS43ODk2IDkxLjYyOUw4Mi4xMDUxIDg3LjE3OTdaIiBmaWxsPSJ3aGl0ZSIgZmlsbC1vcGFjaXR5PSIwLjUiLz4KPHBhdGggZD0iTTUxLjI5MDUgNDIuOTQ0OUw2Ni40ODk1IDMzTDk3IDUyLjgwNjFMODEuODI0MSA2Mi43NDI1TDUxLjI5MDUgNDIuOTQ0OVoiIGZpbGw9IndoaXRlIi8+CjxwYXRoIGQ9Ik01MS42MDU1IDY3LjUwNTlMNjYuODA0NCA1Ny41NjFMOTcgNzcuMDY1N0w4Mi4xMDQ2IDg3LjE3OTNMNTEuNjA1NSA2Ny41MDU5WiIgZmlsbD0id2hpdGUiIGZpbGwtb3BhY2l0eT0iMC40Ii8+CjxwYXRoIGQ9Ik0zNi4wNDY0IDgxLjc1NTlMNTEuMjkwNSA3MS44MTFMODEuNzMzNiA5MS42NTQ3TDY2LjQ4OTUgMTAxLjUwOEwzNi4wNDY0IDgxLjc1NTlaIiBmaWxsPSJ3aGl0ZSIgZmlsbC1vcGFjaXR5PSIwLjYiLz4KPHBhdGggZD0iTTM2LjAwMSA1Mi44MDU1TDUxLjI4ODQgNDIuOTE3N0w1MS4yODg0IDcxLjgxNDVMMzYuMDAxIDgxLjc3OUwzNi4wMDEgNTIuODA1NVoiIGZpbGw9IndoaXRlIi8+CjxwYXRoIGQ9Ik04Mi4xMDUxIDg3LjE3OTdMOTcuMDAxNiA3Ny4wNjYyTDk3LjAwMTYgODEuNzAyOUw4MS43ODk2IDkxLjYyOUw4Mi4xMDUxIDg3LjE3OTdaIiBmaWxsPSJ3aGl0ZSIgZmlsbC1vcGFjaXR5PSIwLjUiLz4KPC9nPgo8ZGVmcz4KPGxpbmVhckdyYWRpZW50IGlkPSJwYWludDBfbGluZWFyXzc2OThfNTY4NDYiIHgxPSI3IiB5MT0iMTAiIHgyPSIxMjMiIHkyPSIxMjIiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIj4KPHN0b3Agc3RvcC1jb2xvcj0iI0ZBN0EwNyIvPgo8c3RvcCBvZmZzZXQ9IjEiIHN0b3AtY29sb3I9IiMyMDYwRDciLz4KPC9saW5lYXJHcmFkaWVudD4KPGNsaXBQYXRoIGlkPSJjbGlwMF83Njk4XzU2ODQ2Ij4KPHJlY3Qgd2lkdGg9IjEzNSIgaGVpZ2h0PSIxMzUiIGZpbGw9IndoaXRlIi8+CjwvY2xpcFBhdGg+CjwvZGVmcz4KPC9zdmc+Cg==` + + uassert.Equal(t, expectedUri, uri) +} diff --git a/_deploy/r/gnoswap/gnft/utils.gno b/_deploy/r/gnoswap/gnft/utils.gno new file mode 100644 index 000000000..0e75908cd --- /dev/null +++ b/_deploy/r/gnoswap/gnft/utils.gno @@ -0,0 +1,59 @@ +package gnft + +import ( + "std" + + "gno.land/p/demo/grc/grc721" + "gno.land/p/demo/ufmt" + pusers "gno.land/p/demo/users" +) + +// getPrevAsString returns the address and package path of the previous realm. +func getPrevAsString() (string, string) { + prev := std.PrevRealm() + return prev.Addr().String(), prev.PkgPath() +} + +// getPrevAddr returns the address of the previous realm. +func getPrevAddr() std.Address { + return std.PrevRealm().Addr() +} + +// a2u converts std.Address to pusers.AddressOrName. +// pusers is a package that contains the user-related functions. +// +// Input: +// - addr: the address to convert +// +// Output: +// - pusers.AddressOrName: the converted address +func a2u(addr std.Address) pusers.AddressOrName { + return pusers.AddressOrName(addr) +} + +// tid converts uint64 to grc721.TokenID. +// +// Input: +// - id: the uint64 to convert +// +// Output: +// - grc721.TokenID: the converted token ID +func tid(id uint64) grc721.TokenID { + return grc721.TokenID(ufmt.Sprintf("%d", id)) +} + +// Exists checks if a token ID exists. +// +// Input: +// - tid: the token ID to check +// +// Output: +// - bool: true if the token ID exists, false otherwise +func Exists(tid grc721.TokenID) bool { + _, err := gnft.OwnerOf(tid) + if err != nil { + return false + } + + return true +} diff --git a/_deploy/r/gnoswap/gns/_helper_test.gno b/_deploy/r/gnoswap/gns/_helper_test.gno new file mode 100644 index 000000000..a5c7a743f --- /dev/null +++ b/_deploy/r/gnoswap/gns/_helper_test.gno @@ -0,0 +1,43 @@ +package gns + +import ( + "std" + "testing" + "time" + + "gno.land/p/demo/grc/grc20" + "gno.land/p/demo/ownable" + + "gno.land/r/gnoswap/v1/consts" +) + +func resetObject(t *testing.T) { + t.Helper() + + resetGnsTokenObject(t) + resetHalvingRelatedObject(t) + + height := std.GetHeight() + lastMintedHeight = height + startHeight = height + startTimestamp = time.Now().Unix() +} + +func resetGnsTokenObject(t *testing.T) { + t.Helper() + + Token, privateLedger = grc20.NewToken("Gnoswap", "GNS", 6) + UserTeller = Token.CallerTeller() + owner = ownable.NewWithAddress(consts.ADMIN) + privateLedger.Mint(owner.Owner(), INITIAL_MINT_AMOUNT) +} + +func resetHalvingRelatedObject(t *testing.T) { + t.Helper() + + startHeight = std.GetHeight() + startTimestamp = time.Now().Unix() + + emissionState = GetEmissionState() + setEndTimestamp(startTimestamp + consts.TIMESTAMP_YEAR*HALVING_END_YEAR) +} diff --git a/_deploy/r/gnoswap/gns/errors.gno b/_deploy/r/gnoswap/gns/errors.gno new file mode 100644 index 000000000..af5bb4e8c --- /dev/null +++ b/_deploy/r/gnoswap/gns/errors.gno @@ -0,0 +1,17 @@ +package gns + +import ( + "errors" + + "gno.land/p/demo/ufmt" +) + +var ( + errInvalidYear = errors.New("[GNOSWAP-GNS-001] invalid year") + errTooManyEmission = errors.New("[GNOSWAP-GNS-002] too many emission reward") +) + +func addDetailToError(err error, detail string) string { + finalErr := ufmt.Errorf("%s || %s", err.Error(), detail) + return finalErr.Error() +} diff --git a/_deploy/r/gnoswap/gns/gno.mod b/_deploy/r/gnoswap/gns/gno.mod new file mode 100644 index 000000000..67209d1d3 --- /dev/null +++ b/_deploy/r/gnoswap/gns/gno.mod @@ -0,0 +1 @@ +module gno.land/r/gnoswap/v1/gns diff --git a/_deploy/r/gnoswap/gns/gns.gno b/_deploy/r/gnoswap/gns/gns.gno new file mode 100644 index 000000000..9b75adb27 --- /dev/null +++ b/_deploy/r/gnoswap/gns/gns.gno @@ -0,0 +1,344 @@ +package gns + +import ( + "std" + "strconv" + "strings" + + "gno.land/p/demo/grc/grc20" + "gno.land/p/demo/ownable" + "gno.land/p/demo/ufmt" + pusers "gno.land/p/demo/users" + + "gno.land/r/demo/grc20reg" + "gno.land/r/demo/users" + + "gno.land/r/gnoswap/v1/common" + "gno.land/r/gnoswap/v1/consts" +) + +const ( + MAXIMUM_SUPPLY = uint64(1_000_000_000_000_000) + INITIAL_MINT_AMOUNT = uint64(100_000_000_000_000) + MAX_EMISSION_AMOUNT = uint64(900_000_000_000_000) // MAXIMUM_SUPPLY - INITIAL_MINT_AMOUNT +) + +var ( + owner *ownable.Ownable + Token *grc20.Token + privateLedger *grc20.PrivateLedger + UserTeller grc20.Teller + + leftEmissionAmount uint64 + mintedEmissionAmount uint64 + lastMintedHeight int64 + + burnAmount uint64 +) + +func init() { + owner = ownable.NewWithAddress(consts.ADMIN) + Token, privateLedger = grc20.NewToken("Gnoswap", "GNS", 6) + UserTeller = Token.CallerTeller() + + privateLedger.Mint(owner.Owner(), INITIAL_MINT_AMOUNT) + getter := func() *grc20.Token { return Token } + grc20reg.Register(getter, "") + + // Initial amount set to 900_000_000_000_000 (MAXIMUM_SUPPLY - INITIAL_MINT_AMOUNT). + // leftEmissionAmount will decrease as tokens are minted. + setLeftEmissionAmount(MAX_EMISSION_AMOUNT) + setMintedEmissionAmount(uint64(0)) + setLastMintedHeight(std.GetHeight()) + burnAmount = uint64(0) +} + +func GetName() string { + return Token.GetName() +} + +func GetSymbol() string { + return Token.GetSymbol() +} + +func GetDecimals() uint { + return Token.GetDecimals() +} + +func TotalSupply() uint64 { + return Token.TotalSupply() +} + +func KnownAccounts() int { + return Token.KnownAccounts() +} + +func BalanceOfAddress(owner std.Address) uint64 { + common.AssertValidAddr(owner) + return Token.BalanceOf(owner) +} + +func AllowanceOfAddress(owner, spender std.Address) uint64 { + common.AssertValidAddr(owner) + common.AssertValidAddr(spender) + return Token.Allowance(owner, spender) +} + +func BalanceOf(owner pusers.AddressOrName) uint64 { + ownerAddr := users.Resolve(owner) + return UserTeller.BalanceOf(ownerAddr) +} + +func Allowance(owner, spender pusers.AddressOrName) uint64 { + ownerAddr := users.Resolve(owner) + spenderAddr := users.Resolve(spender) + return UserTeller.Allowance(ownerAddr, spenderAddr) +} + +func SpendAllowance(owner, spender pusers.AddressOrName, amount uint64) { + ownerAddr := users.Resolve(owner) + spenderAddr := users.Resolve(spender) + checkErr(privateLedger.SpendAllowance(ownerAddr, spenderAddr, amount)) +} + +func MintGns(address pusers.AddressOrName) uint64 { + lastGNSMintedHeight := GetLastMintedHeight() + currentHeight := std.GetHeight() + + // skip minting process if following conditions are met + // - if gns for current block is already minted + // - if last minted height is same or later than emission end height + if lastGNSMintedHeight == currentHeight || lastGNSMintedHeight >= GetEndHeight() { + return 0 + } + + assertShouldNotBeHalted() + assertCallerIsEmission() + + // calculate gns amount to mint + amountToMint := calculateAmountToMint(lastGNSMintedHeight+1, currentHeight) + + // update + setLastMintedHeight(currentHeight) + setMintedEmissionAmount(GetMintedEmissionAmount() + amountToMint) + setLeftEmissionAmount(GetLeftEmissionAmount() - amountToMint) + + // mint calculated amount to address + err := privateLedger.Mint(users.Resolve(address), amountToMint) + if err != nil { + panic(err.Error()) + } + + prevAddr, prevPkgPath := getPrev() + std.Emit( + "MintGNS", + "prevAddr", prevAddr, + "prevRealm", prevPkgPath, + "mintedBlockHeight", strconv.FormatInt(currentHeight, 10), + "mintedGNSAmount", strconv.FormatUint(amountToMint, 10), + "accumMintedGNSAmount", strconv.FormatUint(GetMintedEmissionAmount(), 10), + "accumLeftMintGNSAmount", strconv.FormatUint(GetLeftEmissionAmount(), 10), + ) + + return amountToMint +} + +func Burn(from pusers.AddressOrName, amount uint64) { + owner.AssertCallerIsOwner() + fromAddr := users.Resolve(from) + checkErr(privateLedger.Burn(fromAddr, amount)) + + burnAmount += amount + + prevAddr, prevPkgPath := getPrev() + std.Emit( + "Burn", + "prevAddr", prevAddr, + "prevRealm", prevPkgPath, + "burnedBlockHeight", strconv.FormatInt(std.GetHeight(), 10), + "burnFrom", fromAddr.String(), + "burnedGNSAmount", strconv.FormatUint(amount, 10), + "accumBurnedGNSAmount", strconv.FormatUint(GetBurnAmount(), 10), + ) +} + +func Transfer(to pusers.AddressOrName, amount uint64) { + toAddr := users.Resolve(to) + checkErr(UserTeller.Transfer(toAddr, amount)) +} + +func Approve(spender pusers.AddressOrName, amount uint64) { + spenderAddr := users.Resolve(spender) + checkErr(UserTeller.Approve(spenderAddr, amount)) +} + +func TransferFrom(from, to pusers.AddressOrName, amount uint64) { + fromAddr := users.Resolve(from) + toAddr := users.Resolve(to) + checkErr(UserTeller.TransferFrom(fromAddr, toAddr, amount)) +} + +func Render(path string) string { + parts := strings.Split(path, "/") + c := len(parts) + + switch { + case path == "": + return Token.RenderHome() + case c == 2 && parts[0] == "balance": + owner := pusers.AddressOrName(parts[1]) + ownerAddr := users.Resolve(owner) + balance := UserTeller.BalanceOf(ownerAddr) + return ufmt.Sprintf("%d\n", balance) + default: + return "404\n" + } +} + +func checkErr(err error) { + if err != nil { + panic(err.Error()) + } +} + +// calculateAmountToMint calculates the amount of gns to mint +// It calculates the amount of gns to mint for each halving year for block range. +// It also handles the left emission amount if the current block range includes halving year end block. +func calculateAmountToMint(fromHeight, toHeight int64) uint64 { + prevHeight := fromHeight + currentHeight := toHeight + + // if toHeight is greater than emission end height, set toHeight to emission end height + endH := GetEndHeight() + if toHeight > endH { + toHeight = endH + } + + if fromHeight > toHeight { + return 0 + } + + fromYear := GetHalvingYearByHeight(fromHeight) + toYear := GetHalvingYearByHeight(toHeight) + + totalAmountToMint := uint64(0) + + curFrom := fromHeight + + for year := fromYear; year <= toYear; year++ { + yearEndHeight := GetHalvingYearEndBlock(year) + mintUntilHeight := i64Min(yearEndHeight, toHeight) + + // how many blocks to calculate + blocks := uint64(mintUntilHeight - curFrom + 1) + if blocks <= 0 { + break + } + + // amount of gns to mint for each block for current year + singleBlockAmount := GetAmountPerBlockPerHalvingYear(year) + + // amount of gns to mint for current year + yearAmountToMint := singleBlockAmount * blocks + + // if last block of halving year, handle left emission amount + if mintUntilHeight >= yearEndHeight { + leftover := handleLeftEmissionAmount(year, yearAmountToMint) + yearAmountToMint += leftover + } + + totalAmountToMint += yearAmountToMint + + setHalvingYearMintAmount(year, GetHalvingYearMintAmount(year)+yearAmountToMint) + setHalvingYearLeftAmount(year, GetHalvingYearLeftAmount(year)-yearAmountToMint) + + // update fromHeight for next year (if necessary) + curFrom = mintUntilHeight + 1 + if curFrom > toHeight { + break + } + } + + if prevHeight == currentHeight { + addPerBlockMintUpdate(uint64(currentHeight), GetAmountPerBlockPerHalvingYear(GetHalvingYearByHeight(currentHeight))) + } else { + addPerBlockMintUpdate(uint64(prevHeight), GetAmountPerBlockPerHalvingYear(GetHalvingYearByHeight(prevHeight))) + addPerBlockMintUpdate(uint64(currentHeight), GetAmountPerBlockPerHalvingYear(GetHalvingYearByHeight(currentHeight))) + } + + assertTooManyEmission(totalAmountToMint) + return totalAmountToMint +} + +// isLastBlockOfHalvingYear returns true if the current block is the last block of a halving year. +func isLastBlockOfHalvingYear(height int64) bool { + year := GetHalvingYearByHeight(height) + lastBlock := GetHalvingYearEndBlock(year) + + return height == lastBlock +} + +// handleLeftEmissionAmount handles the left emission amount for a halving year. +// It calculates the left emission amount by subtracting the halving year mint amount from the halving year amount. +func handleLeftEmissionAmount(year int64, amount uint64) uint64 { + return GetHalvingYearLeftAmount(year) - amount +} + +// skipIfSameHeight returns true if the current block height is the same as the last minted height. +// This prevents multiple gns minting inside the same block. +func skipIfSameHeight(lastMintedHeight, currentHeight int64) bool { + return lastMintedHeight == currentHeight +} + +// skipIfEmissionEnded returns true if the emission has ended. +func skipIfEmissionEnded(height int64) bool { + if isEmissionEnded(height) { + return true + } + + return false +} + +// GetLastMintedHeight returns the last block height that gns was minted. +func GetLastMintedHeight() int64 { + return lastMintedHeight +} + +// GetLeftEmissionAmount returns the amount of GNS can be minted. +func GetLeftEmissionAmount() uint64 { + return leftEmissionAmount +} + +// GetBurnAmount returns the amount of GNS that has been burned. +func GetBurnAmount() uint64 { + return burnAmount +} + +// GetMintedEmissionAmount returns the amount of GNS that has been minted by the emission contract. +// It does not include initial minted amount. +func GetMintedEmissionAmount() uint64 { + return mintedEmissionAmount +} + +func setLastMintedHeight(height int64) { + lastMintedHeight = height +} + +func setLeftEmissionAmount(amount uint64) { + leftEmissionAmount = amount +} + +func setMintedEmissionAmount(amount uint64) { + mintedEmissionAmount = amount +} + +// assertTooManyEmission asserts if the amount of gns to mint is too many. +// It checks if the amount of gns to mint is greater than the left emission amount or the total emission amount. +func assertTooManyEmission(amount uint64) { + if (amount + GetMintedEmissionAmount()) > MAX_EMISSION_AMOUNT { + panic(addDetailToError( + errTooManyEmission, + ufmt.Sprintf("amount: %d", amount), + )) + } +} diff --git a/_deploy/r/gnoswap/gns/gns_test.gno b/_deploy/r/gnoswap/gns/gns_test.gno new file mode 100644 index 000000000..1c72e2729 --- /dev/null +++ b/_deploy/r/gnoswap/gns/gns_test.gno @@ -0,0 +1,400 @@ +package gns + +import ( + "fmt" + "std" + "testing" + + "gno.land/p/demo/testutils" + "gno.land/p/demo/uassert" + pusers "gno.land/p/demo/users" + + "gno.land/r/gnoswap/v1/consts" +) + +const ( + // gnoVM test context default height + // ref: https://github.com/gnolang/gno/blob/a85a53d5b38f0a21d66262a823a8b07f4f836b68/gnovm/pkg/test/test.go#L31-L32 + GNO_VM_DEFAULT_HEIGHT = int64(123) +) + +var ( + emissionRealm = std.NewCodeRealm(consts.EMISSION_PATH) + adminRealm = std.NewUserRealm(consts.ADMIN) +) + +var ( + alice = testutils.TestAddress("alice") + bob = testutils.TestAddress("bob") +) + +func TestGetName(t *testing.T) { + uassert.Equal(t, "Gnoswap", GetName()) +} + +func TestGetSymbol(t *testing.T) { + uassert.Equal(t, "GNS", GetSymbol()) +} + +func TestGetDecimals(t *testing.T) { + uassert.Equal(t, uint(6), GetDecimals()) +} + +func TestTotalSupply(t *testing.T) { + + uassert.Equal(t, INITIAL_MINT_AMOUNT, TotalSupply()) +} + +func TestKnownAccounts(t *testing.T) { + uassert.Equal(t, int(1), KnownAccounts()) +} + +func TestBalanceOfAddress(t *testing.T) { + t.Run( + "should return balance of address", + func(t *testing.T) { + uassert.Equal(t, INITIAL_MINT_AMOUNT, BalanceOfAddress(consts.ADMIN)) + }, + ) + t.Run( + "should panic if address is not valid", + func(t *testing.T) { + uassert.PanicsWithMessage(t, "[GNOSWAP-COMMON-005] invalid address || 0xabcdefg", func() { + BalanceOfAddress("0xabcdefg") + }) + }, + ) +} + +func TestAllowanceOfAddress(t *testing.T) { + t.Run( + "should return allowance of address", + func(t *testing.T) { + uassert.Equal(t, uint64(0), AllowanceOfAddress(consts.ADMIN, alice)) + + std.TestSetOrigCaller(consts.ADMIN) + Approve(pusers.AddressOrName(alice), uint64(123)) + uassert.Equal(t, uint64(123), AllowanceOfAddress(consts.ADMIN, alice)) + }, + ) + t.Run( + "should panic if address is not valid", + func(t *testing.T) { + uassert.PanicsWithMessage(t, "[GNOSWAP-COMMON-005] invalid address || 0xabcdefg", func() { + AllowanceOfAddress("0xabcdefg", alice) + }) + }, + ) + t.Run( + "should panic if spender is not valid", + func(t *testing.T) { + uassert.PanicsWithMessage(t, "[GNOSWAP-COMMON-005] invalid address || 0xabcdefg", func() { + AllowanceOfAddress(consts.ADMIN, "0xabcdefg") + }) + }, + ) +} + +func TestBalanceOf(t *testing.T) { + uassert.Equal(t, INITIAL_MINT_AMOUNT, BalanceOf(a2u(consts.ADMIN))) +} + +func TestSpendAllowance(t *testing.T) { + t.Run( + "should spend allowance", + func(t *testing.T) { + std.TestSetOrigCaller(consts.ADMIN) + Approve(a2u(alice), uint64(123)) + + SpendAllowance(a2u(consts.ADMIN), a2u(alice), uint64(23)) + uassert.Equal(t, uint64(100), Allowance(a2u(consts.ADMIN), a2u(alice))) + }, + ) + t.Run( + "should panic if address is not valid", + func(t *testing.T) { + uassert.PanicsWithMessage(t, "invalid address", func() { + SpendAllowance("0xabcdefg", a2u(alice), uint64(123)) + }) + }, + ) + t.Run( + "should panic if spender is not valid", + func(t *testing.T) { + uassert.PanicsWithMessage(t, "invalid address", func() { + SpendAllowance(a2u(consts.ADMIN), "0xabcdefg", uint64(123)) + }) + }, + ) + t.Run( + "should panic if allowance is not enough", + func(t *testing.T) { + uassert.PanicsWithMessage(t, "insufficient allowance", func() { + SpendAllowance(a2u(consts.ADMIN), a2u(alice), uint64(123)) + }) + }, + ) +} + +func TestAssertTooManyEmission(t *testing.T) { + tests := []struct { + name string + amount uint64 + shouldPanic bool + panicMsg string + }{ + { + name: "should panic if emission amount is too large", + amount: MAXIMUM_SUPPLY, + shouldPanic: true, + panicMsg: "[GNOSWAP-GNS-002] too many emission reward || amount: 1000000000000000", + }, + { + name: "should not panic if emission amount is not too large", + amount: 123, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.shouldPanic { + uassert.PanicsWithMessage(t, tt.panicMsg, func() { + assertTooManyEmission(tt.amount) + }) + } else { + uassert.NotPanics(t, func() { assertTooManyEmission(tt.amount) }) + } + }) + } +} + +func TestIsLastBlockOfHalvingYear(t *testing.T) { + tests := make([]struct { + name string + height int64 + want bool + }, 0, 24) + + for i := HALVING_START_YEAR; i <= HALVING_END_YEAR; i++ { + tests = append(tests, struct { + name string + height int64 + want bool + }{ + name: fmt.Sprintf("last block of halving year %d", i), + height: GetHalvingYearEndBlock(i), + want: true, + }) + + tests = append(tests, struct { + name string + height int64 + want bool + }{ + name: fmt.Sprintf("not last block of halving year %d", i), + height: GetHalvingYearEndBlock(i) - 1, + want: false, + }) + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + uassert.Equal(t, tt.want, isLastBlockOfHalvingYear(tt.height)) + }) + } +} + +func TestHandleLeftEmissionAmount(t *testing.T) { + tests := make([]struct { + name string + year int64 + amount uint64 + want uint64 + }, 0, 24) + + for i := int64(1); i <= 12; i++ { + tests = append(tests, struct { + name string + year int64 + amount uint64 + want uint64 + }{ + name: fmt.Sprintf("handle left emission amount for year %d, non minted", i), + year: i, + amount: 0, + want: GetHalvingYearMaxAmount(i), + }) + + tests = append(tests, struct { + name string + year int64 + amount uint64 + want uint64 + }{ + name: fmt.Sprintf("handle left emission amount for year %d, minted", i), + year: i, + amount: uint64(123456), + want: GetHalvingYearMaxAmount(i) - uint64(123456), + }) + } +} + +func TestSkipIfSameHeight(t *testing.T) { + t.Run("should skip if height is same", func(t *testing.T) { + uassert.True(t, skipIfSameHeight(1, 1)) + }) + + t.Run("should not skip if height is different", func(t *testing.T) { + uassert.False(t, skipIfSameHeight(1, 2)) + }) +} + +func TestSkipIfEmissionEnded(t *testing.T) { + t.Run("should skip if emission has ended", func(t *testing.T) { + uassert.True(t, skipIfEmissionEnded(GetEndHeight()+1)) + }) + + t.Run("should not skip if emission has not ended", func(t *testing.T) { + uassert.False(t, skipIfEmissionEnded(std.GetHeight())) + }) +} + +func TestGetterSetter(t *testing.T) { + t.Run("last minted height", func(t *testing.T) { + value := int64(123) + setLastMintedHeight(value) + uassert.Equal(t, value, GetLastMintedHeight()) + }) + + t.Run("left emission amount", func(t *testing.T) { + value := uint64(0) + setLeftEmissionAmount(value) + uassert.Equal(t, value, GetLeftEmissionAmount()) + }) +} + +func TestGrc20Methods(t *testing.T) { + tests := []struct { + name string + fn func() + shouldPanic bool + panicMsg string + }{ + { + name: "TotalSupply", + fn: func() { + uassert.Equal(t, INITIAL_MINT_AMOUNT, TotalSupply()) + }, + }, + { + name: "BalanceOf(admin)", + fn: func() { + uassert.Equal(t, INITIAL_MINT_AMOUNT, BalanceOf(a2u(consts.ADMIN))) + }, + }, + { + name: "BalanceOf(alice)", + fn: func() { + uassert.Equal(t, uint64(0), BalanceOf(a2u(alice))) + }, + }, + { + name: "Allowance(admin, alice)", + fn: func() { + uassert.Equal(t, uint64(0), Allowance(a2u(consts.ADMIN), a2u(alice))) + }, + }, + { + name: "MintGns success", + fn: func() { + std.TestSetRealm(emissionRealm) + MintGns(a2u(consts.ADMIN)) + }, + }, + { + name: "MintGns without permission should panic", + fn: func() { + std.TestSkipHeights(1) + MintGns(a2u(consts.ADMIN)) + }, + shouldPanic: true, + panicMsg: `caller(g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm) has no permission`, + }, + { + name: "Burn success", + fn: func() { + std.TestSetRealm(adminRealm) + Burn(a2u(consts.ADMIN), uint64(1)) + }, + }, + { + name: "Burn without permission should panic", + fn: func() { + Burn(a2u(consts.ADMIN), uint64(1)) + }, + shouldPanic: true, + panicMsg: `ownable: caller is not owner`, + }, + { + name: "Transfer success", + fn: func() { + std.TestSetRealm(adminRealm) + Transfer(a2u(alice), uint64(1)) + }, + }, + { + name: "Transfer without enough balance should panic", + fn: func() { + std.TestSetRealm(std.NewUserRealm(alice)) + Transfer(a2u(bob), uint64(1)) + }, + shouldPanic: true, + panicMsg: `insufficient balance`, + }, + { + name: "Transfer to self should panic", + fn: func() { + std.TestSetRealm(adminRealm) + Transfer(a2u(consts.ADMIN), uint64(1)) + }, + shouldPanic: true, + panicMsg: `cannot send transfer to self`, + }, + { + name: "TransferFrom success", + fn: func() { + // approve first + std.TestSetRealm(adminRealm) + Approve(a2u(alice), uint64(1)) + + // alice transfer admin's balance to bob + std.TestSetRealm(std.NewUserRealm(alice)) + TransferFrom(a2u(consts.ADMIN), a2u(bob), uint64(1)) + }, + }, + { + name: "TransferFrom without enough allowance should panic", + fn: func() { + std.TestSetRealm(adminRealm) + Approve(a2u(alice), uint64(1)) + + std.TestSetRealm(std.NewUserRealm(alice)) + TransferFrom(a2u(consts.ADMIN), a2u(bob), uint64(2)) + }, + shouldPanic: true, + panicMsg: `insufficient allowance`, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resetGnsTokenObject(t) + + if tt.shouldPanic { + uassert.PanicsWithMessage(t, tt.panicMsg, tt.fn) + } else { + uassert.NotPanics(t, func() { tt.fn() }) + } + }) + } +} diff --git a/_deploy/r/gnoswap/gns/halving.gno b/_deploy/r/gnoswap/gns/halving.gno new file mode 100644 index 000000000..f634ae25f --- /dev/null +++ b/_deploy/r/gnoswap/gns/halving.gno @@ -0,0 +1,586 @@ +package gns + +import ( + "std" + "strconv" + "time" + + "gno.land/p/demo/avl" + "gno.land/p/demo/json" + "gno.land/r/gnoswap/v1/common" + "gno.land/r/gnoswap/v1/consts" +) + +// init 12 years halving tier block +/* + NOTE: assume block will be created every 1 second by default + 1 second = 1 block + 1 minute = 60 block + 1 hour = 3600 block + 1 day = 86400 block + (365 days) 1 year = 31536000 block +*/ + +const ( + HALVING_START_YEAR = int64(1) + HALVING_END_YEAR = int64(12) +) + +var ( + HALVING_AMOUNTS_PER_YEAR = [HALVING_END_YEAR]uint64{ + 18_750_000_000_000 * 12, // Year 1: 225000000000000 + 18_750_000_000_000 * 12, // Year 2: 225000000000000 + 9_375_000_000_000 * 12, // Year 3: 112500000000000 + 9_375_000_000_000 * 12, // Year 4: 112500000000000 + 4_687_500_000_000 * 12, // Year 5: 56250000000000 + 4_687_500_000_000 * 12, // Year 6: 56250000000000 + 2_343_750_000_000 * 12, // Year 7: 28125000000000 + 2_343_750_000_000 * 12, // Year 8: 28125000000000 + 1_171_875_000_000 * 12, // Year 9: 14062500000000 + 1_171_875_000_000 * 12, // Year 10: 14062500000000 + 1_171_875_000_000 * 12, // Year 11: 14062500000000 + 1_171_875_000_000 * 12, // Year 12: 14062500000000 + } +) + +var ( + blockPerYear = consts.TIMESTAMP_YEAR / consts.BLOCK_GENERATION_INTERVAL + blockPerDay = consts.TIMESTAMP_DAY / consts.BLOCK_GENERATION_INTERVAL + + avgBlockTimeMs int64 = consts.SECOND_IN_MILLISECOND * consts.BLOCK_GENERATION_INTERVAL + perBlockMint = avl.NewTree() // height => uint64 +) + +type HalvingData struct { + startBlockHeight []int64 + endBlockHeight []int64 + startTimestamp []int64 + maxAmount []uint64 + mintedAmount []uint64 + leftAmount []uint64 + accumAmount []uint64 + amountPerBlock []uint64 +} + +func (h *HalvingData) getStartBlockHeight(year int64) int64 { + if year == 0 { + return 0 + } + return h.startBlockHeight[year-1] +} + +func (h *HalvingData) setStartBlockHeight(year int64, height int64) { + assertValidYear(year) + h.startBlockHeight[year-1] = height +} + +func (h *HalvingData) getEndBlockHeight(year int64) int64 { + if year == 0 { + return 0 + } + return h.endBlockHeight[year-1] +} + +func (h *HalvingData) setEndBlockHeight(year int64, height int64) { + assertValidYear(year) + h.endBlockHeight[year-1] = height +} + +func (h *HalvingData) getStartTimestamp(year int64) int64 { + if year == 0 { + return 0 + } + return h.startTimestamp[year-1] +} + +func (h *HalvingData) setStartTimestamp(year int64, timestamp int64) { + assertValidYear(year) + h.startTimestamp[year-1] = timestamp +} + +func (h *HalvingData) getMaxAmount(year int64) uint64 { + if year == 0 { + return 0 + } + return h.maxAmount[year-1] +} + +func (h *HalvingData) setMaxAmount(year int64, amount uint64) { + assertValidYear(year) + h.maxAmount[year-1] = amount +} + +func (h *HalvingData) getMintedAmount(year int64) uint64 { + if year == 0 { + return 0 + } + return h.mintedAmount[year-1] +} + +func (h *HalvingData) setMintedAmount(year int64, amount uint64) { + assertValidYear(year) + h.mintedAmount[year-1] = amount +} + +func (h *HalvingData) getLeftAmount(year int64) uint64 { + if year == 0 { + return 0 + } + return h.leftAmount[year-1] +} + +func (h *HalvingData) setLeftAmount(year int64, amount uint64) { + assertValidYear(year) + h.leftAmount[year-1] = amount +} + +func (h *HalvingData) getAccumAmount(year int64) uint64 { + if year == 0 { + return 0 + } + return h.accumAmount[year-1] +} + +func (h *HalvingData) setAccumAmount(year int64, amount uint64) { + assertValidYear(year) + h.accumAmount[year-1] = amount +} + +func (h *HalvingData) addAccumAmount(year int64, amount uint64) { + assertValidYear(year) + h.accumAmount[year-1] += amount +} + +func (h *HalvingData) getAmountPerBlock(year int64) uint64 { + if year == 0 { + return 0 + } + return h.amountPerBlock[year-1] +} + +func (h *HalvingData) setAmountPerBlock(year int64, amount uint64) { + assertValidYear(year) + h.amountPerBlock[year-1] = amount +} + +func (h *HalvingData) initialize(startHeight, startTimestamp int64) { + for year := HALVING_START_YEAR; year <= HALVING_END_YEAR; year++ { + // set max emission amount per year + // each year can not mint more than this amount + currentYearMaxAmount := GetHalvingAmountsPerYear(year) + h.setMaxAmount(year, currentYearMaxAmount) + + if year == HALVING_START_YEAR { + h.setAccumAmount(year, currentYearMaxAmount) + h.setStartBlockHeight(year, startHeight) + h.setEndBlockHeight(year, startHeight+(blockPerYear*year)-1) + } else { + // accumulate amount until current year, is the sum of current year max amount and accumulated amount until previous year + h.setAccumAmount(year, currentYearMaxAmount+h.getAccumAmount(year-1)) + + // start block of current year, is the next block of previous year of end block + h.setStartBlockHeight(year, h.getEndBlockHeight(year-1)+1) + + // end block of current year, is sum of start block and block per year + h.setEndBlockHeight(year, h.getStartBlockHeight(year)+blockPerYear-1) + } + + h.setStartTimestamp(year, startTimestamp+(consts.TIMESTAMP_YEAR*(year-1))) + + amountPerDay := currentYearMaxAmount / consts.DAY_PER_YEAR + amountPerBlock := amountPerDay / uint64(blockPerDay) + h.setAmountPerBlock(year, uint64(amountPerBlock)) + h.setMintedAmount(year, uint64(0)) + h.setLeftAmount(year, currentYearMaxAmount) + } +} + +type EmissionState struct { + startHeight int64 + startTimestamp int64 + endTimestamp int64 + halvingData HalvingData +} + +func GetEmissionState() *EmissionState { + if emissionState == nil { + emissionState = NewEmissionState() + emissionState.initializeHalvingData() + } + return emissionState +} + +func NewEmissionState() *EmissionState { + now := time.Now().Unix() + consts.BLOCK_GENERATION_INTERVAL + emissionEndTime := now + consts.TIMESTAMP_YEAR*HALVING_END_YEAR + + return &EmissionState{ + startHeight: std.GetHeight() + 1, + startTimestamp: now, + endTimestamp: emissionEndTime, + halvingData: HalvingData{ + startBlockHeight: make([]int64, HALVING_END_YEAR), + endBlockHeight: make([]int64, HALVING_END_YEAR), + startTimestamp: make([]int64, HALVING_END_YEAR), + maxAmount: make([]uint64, HALVING_END_YEAR), + mintedAmount: make([]uint64, HALVING_END_YEAR), + leftAmount: make([]uint64, HALVING_END_YEAR), + accumAmount: make([]uint64, HALVING_END_YEAR), + amountPerBlock: make([]uint64, HALVING_END_YEAR), + }, + } +} + +func (e *EmissionState) getStartHeight() int64 { + return e.startHeight +} + +func (e *EmissionState) setStartHeight(height int64) { + e.startHeight = height +} + +func (e *EmissionState) getStartTimestamp() int64 { + return e.startTimestamp +} + +func (e *EmissionState) setStartTimestamp(timestamp int64) { + e.startTimestamp = timestamp +} + +func (e *EmissionState) getEndTimestamp() int64 { + return e.endTimestamp +} + +func (e *EmissionState) setEndTimestamp(timestamp int64) { + e.endTimestamp = timestamp +} + +func (e *EmissionState) getHalvingData() HalvingData { + return e.halvingData +} + +func (e *EmissionState) setHalvingData(data HalvingData) { + e.halvingData = data +} + +// initializeHalvingData initializes the halving data +// it should be called only once, so we call this in init() +func (e *EmissionState) initializeHalvingData() { + halvingData := e.getHalvingData() + halvingData.initialize(e.getStartHeight(), e.getStartTimestamp()) + e.setHalvingData(halvingData) +} + +var emissionState *EmissionState + +func init() { + emissionState = GetEmissionState() +} + +func GetAvgBlockTimeInMs() int64 { + return avgBlockTimeMs +} + +// SetAvgBlockTimeInMsByAdmin sets the average block time in millisecond. +func SetAvgBlockTimeInMsByAdmin(ms int64) { + assertCallerIsAdmin() + + prevAvgBlockTimeInMs := GetAvgBlockTimeInMs() + setAvgBlockTimeInMs(ms) + + prevAddr, prevPkgPath := getPrev() + std.Emit( + "SetAvgBlockTimeInMsByAdmin", + "prevAddr", prevAddr, + "prevRealm", prevPkgPath, + "prevAvgBlockTimeInMs", strconv.FormatInt(prevAvgBlockTimeInMs, 10), + "avgBlockTimeInMs", strconv.FormatInt(ms, 10), + ) +} + +// SetAvgBlockTimeInMs sets the average block time in millisecond. +// Only governance contract can execute this function via proposal +func SetAvgBlockTimeInMs(ms int64) { + assertCallerIsGovernance() + + prevAvgBlockTimeInMs := GetAvgBlockTimeInMs() + setAvgBlockTimeInMs(ms) + + prevAddr, prevPkgPath := getPrev() + std.Emit( + "SetAvgBlockTimeInMs", + "prevAddr", prevAddr, + "prevRealm", prevPkgPath, + "prevAvgBlockTimeInMs", strconv.FormatInt(prevAvgBlockTimeInMs, 10), + "avgBlockTimeInMs", strconv.FormatInt(ms, 10), + ) +} + +func setAvgBlockTimeInMs(ms int64) { + assertShouldNotBeHalted() + + now := time.Now().Unix() + height := std.GetHeight() + + // update block per year + yearByMilliSec := secToMs(consts.TIMESTAMP_YEAR) + blockPerYear = yearByMilliSec / ms + + // get the halving year and end timestamp of current time + currentYear, currentYearEndTimestamp := getHalvingYearAndEndTimestamp(now) + + // how much time left for current halving year + timeLeft := currentYearEndTimestamp - now + timeLeftMs := secToMs(timeLeft) + + // how many block left for current halving year + blockLeft := timeLeftMs / ms + // how many reward left for current halving year + minted := GetMintedEmissionAmount() + amountLeft := GetHalvingYearAccuAmount(currentYear) - minted + + // how much reward should be minted per block for current halving year + adjustedAmountPerBlock := amountLeft / uint64(blockLeft) + // update it + setAmountPerBlockPerHalvingYear(currentYear, adjustedAmountPerBlock) + addPerBlockMintUpdate(uint64(std.GetHeight()), adjustedAmountPerBlock) + + for year := HALVING_START_YEAR; year <= HALVING_END_YEAR; year++ { + if year < currentYear { + // pass past halving years + continue + } + + yearEndTimestamp := GetHalvingYearTimestamp(year) + consts.TIMESTAMP_YEAR + timeLeftForYear := yearEndTimestamp - now + numBlock := (timeLeftForYear * consts.SECOND_IN_MILLISECOND) / ms + yearEndHeight := height + numBlock + + if year == currentYear { + // for current year, update only end block + setHalvingYearEndBlock(year, yearEndHeight) + } else { + // update start block + prevYearEnd := GetHalvingYearEndBlock(year - 1) + nextYearStart := prevYearEnd + 1 + nextYearEnd := nextYearStart + blockPerYear + + setHalvingYearStartBlock(year, nextYearStart) + setHalvingYearEndBlock(year, nextYearEnd) + } + } + + avgBlockTimeMs = ms +} + +// GetAmountByHeight returns the amount of gns to mint by height +func GetAmountByHeight(height int64) uint64 { + if isEmissionEnded(height) { + return 0 + } + + halvingYear := GetHalvingYearByHeight(height) + return GetAmountPerBlockPerHalvingYear(halvingYear) +} + +// GetHalvingYearByHeight returns the halving year by height +func GetHalvingYearByHeight(height int64) int64 { + if isEmissionEnded(height) { + return 0 + } + + for year := HALVING_START_YEAR; year <= HALVING_END_YEAR; year++ { + endBlock := GetHalvingYearEndBlock(year) + if height <= endBlock { + return year + } + } + + return 0 +} + +func GetHalvingYearStartBlock(year int64) int64 { + halvingData := GetEmissionState().getHalvingData() + return halvingData.getStartBlockHeight(year) +} + +func setHalvingYearStartBlock(year int64, block int64) { + halvingData := GetEmissionState().getHalvingData() + halvingData.setStartBlockHeight(year, block) + GetEmissionState().setHalvingData(halvingData) +} + +func GetHalvingYearEndBlock(year int64) int64 { + halvingData := GetEmissionState().getHalvingData() + return halvingData.getEndBlockHeight(year) +} + +func setHalvingYearEndBlock(year int64, block int64) { + halvingData := GetEmissionState().getHalvingData() + halvingData.setEndBlockHeight(year, block) + GetEmissionState().setHalvingData(halvingData) +} + +func GetHalvingYearTimestamp(year int64) int64 { + halvingData := GetEmissionState().getHalvingData() + return halvingData.getStartTimestamp(year) +} + +func setHalvingYearTimestamp(year int64, timestamp int64) { + halvingData := GetEmissionState().getHalvingData() + halvingData.setStartTimestamp(year, timestamp) + GetEmissionState().setHalvingData(halvingData) +} + +func GetHalvingYearMaxAmount(year int64) uint64 { + halvingData := GetEmissionState().getHalvingData() + return halvingData.getMaxAmount(year) +} + +func setHalvingYearMaxAmount(year int64, amount uint64) { + halvingData := GetEmissionState().getHalvingData() + halvingData.setMaxAmount(year, amount) + GetEmissionState().setHalvingData(halvingData) +} + +func GetHalvingYearMintAmount(year int64) uint64 { + halvingData := GetEmissionState().getHalvingData() + return halvingData.getMintedAmount(year) +} + +func setHalvingYearMintAmount(year int64, amount uint64) { + halvingData := GetEmissionState().getHalvingData() + halvingData.setMintedAmount(year, amount) + GetEmissionState().setHalvingData(halvingData) +} + +func GetHalvingYearLeftAmount(year int64) uint64 { + halvingData := GetEmissionState().getHalvingData() + return halvingData.getLeftAmount(year) +} + +func setHalvingYearLeftAmount(year int64, amount uint64) { + halvingData := GetEmissionState().getHalvingData() + halvingData.setLeftAmount(year, amount) + GetEmissionState().setHalvingData(halvingData) +} + +func GetHalvingYearAccuAmount(year int64) uint64 { + halvingData := GetEmissionState().getHalvingData() + return halvingData.getAccumAmount(year) +} + +func setHalvingYearAccuAmount(year int64, amount uint64) { + halvingData := GetEmissionState().getHalvingData() + halvingData.setAccumAmount(year, amount) + GetEmissionState().setHalvingData(halvingData) +} + +func GetAmountPerBlockPerHalvingYear(year int64) uint64 { + halvingData := GetEmissionState().getHalvingData() + return halvingData.getAmountPerBlock(year) +} + +func setAmountPerBlockPerHalvingYear(year int64, amount uint64) { + halvingData := GetEmissionState().getHalvingData() + halvingData.setAmountPerBlock(year, amount) + GetEmissionState().setHalvingData(halvingData) +} + +func GetHalvingAmountsPerYear(year int64) uint64 { + return HALVING_AMOUNTS_PER_YEAR[year-1] +} + +func GetEndHeight() int64 { + // last block of last halving year(12) is last block of emission + // later than this block, no more gns will be minted + return GetHalvingYearEndBlock(HALVING_END_YEAR) +} + +func GetEndTimestamp() int64 { + return GetEmissionState().getEndTimestamp() +} + +func setEndTimestamp(timestamp int64) { + GetEmissionState().setEndTimestamp(timestamp) +} + +func GetHalvingInfo() string { + height := std.GetHeight() + now := time.Now().Unix() + + halvings := make([]*json.Node, 0) + + for year := HALVING_START_YEAR; year <= HALVING_END_YEAR; year++ { + halvings = append(halvings, json.ObjectNode("", map[string]*json.Node{ + "year": json.StringNode("year", strconv.FormatInt(year, 10)), + "block": json.NumberNode("block", float64(GetHalvingYearStartBlock(year))), + "amount": json.NumberNode("amount", float64(GetAmountPerBlockPerHalvingYear(year))), + })) + } + + node := json.ObjectNode("", map[string]*json.Node{ + "height": json.NumberNode("height", float64(height)), + "timestamp": json.NumberNode("timestamp", float64(now)), + "avgBlockTimeMs": json.NumberNode("avgBlockTimeMs", float64(avgBlockTimeMs)), + "halvings": json.ArrayNode("", halvings), + }) + + b, err := json.Marshal(node) + if err != nil { + panic(err.Error()) + } + + return string(b) +} + +func isEmissionEnded(height int64) bool { + return height > GetEndHeight() +} + +// getHalvingYearAndEndTimestamp returns the halving year and end timestamp of the given timestamp +// if the timestamp is not in any halving year, it returns 0, 0 +func getHalvingYearAndEndTimestamp(timestamp int64) (int64, int64) { + state := GetEmissionState() + + endTimestamp := state.getEndTimestamp() + startTimestamp := state.getStartTimestamp() + + if timestamp > endTimestamp { // after 12 years + return 0, 0 + } + + timestamp -= startTimestamp + + year := timestamp / consts.TIMESTAMP_YEAR + year += 1 // since we subtract startTimestamp at line 215, we need to add 1 to get the correct year + + return year, startTimestamp + (consts.TIMESTAMP_YEAR * year) +} + +func GetCurrentEmission() uint64 { + emission := uint64(0) + perBlockMint.ReverseIterate("", common.EncodeUint(uint64(std.GetHeight())), func(key string, value interface{}) bool { + emission = value.(uint64) + return true + }) + return emission +} + +func EmissionUpdates(startHeight uint64, endHeight uint64) ([]uint64, []uint64) { + heights := make([]uint64, 0) + updates := make([]uint64, 0) + println("EmissionUpdates : ", startHeight, ", ", endHeight) + perBlockMint.Iterate(common.EncodeUint(startHeight), common.EncodeUint(endHeight), func(key string, value interface{}) bool { + println("EmissionUpdates : ", key, ", ", value) + heights = append(heights, common.DecodeUint(key)) + updates = append(updates, value.(uint64)) + return false + }) + + return heights, updates +} + +func addPerBlockMintUpdate(height uint64, amount uint64) { + perBlockMint.Set(common.EncodeUint(height), amount) +} diff --git a/_deploy/r/gnoswap/gns/halving_test.gno b/_deploy/r/gnoswap/gns/halving_test.gno new file mode 100644 index 000000000..478fade0f --- /dev/null +++ b/_deploy/r/gnoswap/gns/halving_test.gno @@ -0,0 +1,191 @@ +package gns + +import ( + "std" + "testing" + "time" + + "gno.land/p/demo/json" + "gno.land/p/demo/uassert" + + "gno.land/r/gnoswap/v1/consts" +) + +var ( + govRealm = std.NewCodeRealm(consts.GOV_GOVERNANCE_PATH) + adminRealm = std.NewUserRealm(consts.ADMIN) + + startHeight int64 = std.GetHeight() + startTimestamp int64 = time.Now().Unix() + consts.BLOCK_GENERATION_INTERVAL +) + +var FIRST_BLOCK_OF_YEAR = []int64{ + startHeight + (blockPerYear * 0), + startHeight + (blockPerYear * 1) + 1, + startHeight + (blockPerYear * 2) + 2, + startHeight + (blockPerYear * 3) + 3, + startHeight + (blockPerYear * 4) + 4, + startHeight + (blockPerYear * 5) + 5, + startHeight + (blockPerYear * 6) + 6, + startHeight + (blockPerYear * 7) + 7, + startHeight + (blockPerYear * 8) + 8, + startHeight + (blockPerYear * 9) + 9, + startHeight + (blockPerYear * 10) + 10, + startHeight + (blockPerYear * 11) + 11, +} + +var FIRST_TIMESTAMP_OF_YEAR = []int64{ + startTimestamp + consts.TIMESTAMP_YEAR*0, + startTimestamp + consts.TIMESTAMP_YEAR*1, + startTimestamp + consts.TIMESTAMP_YEAR*2, + startTimestamp + consts.TIMESTAMP_YEAR*3, + startTimestamp + consts.TIMESTAMP_YEAR*4, + startTimestamp + consts.TIMESTAMP_YEAR*5, + startTimestamp + consts.TIMESTAMP_YEAR*6, + startTimestamp + consts.TIMESTAMP_YEAR*7, + startTimestamp + consts.TIMESTAMP_YEAR*8, + startTimestamp + consts.TIMESTAMP_YEAR*9, + startTimestamp + consts.TIMESTAMP_YEAR*10, + startTimestamp + consts.TIMESTAMP_YEAR*11, +} + +var END_TIMESTAMP_OF_YEAR = []int64{ + startTimestamp + consts.TIMESTAMP_YEAR*1, + startTimestamp + consts.TIMESTAMP_YEAR*2, + startTimestamp + consts.TIMESTAMP_YEAR*3, + startTimestamp + consts.TIMESTAMP_YEAR*4, + startTimestamp + consts.TIMESTAMP_YEAR*5, + startTimestamp + consts.TIMESTAMP_YEAR*6, + startTimestamp + consts.TIMESTAMP_YEAR*7, + startTimestamp + consts.TIMESTAMP_YEAR*8, + startTimestamp + consts.TIMESTAMP_YEAR*9, + startTimestamp + consts.TIMESTAMP_YEAR*10, + startTimestamp + consts.TIMESTAMP_YEAR*11, + startTimestamp + consts.TIMESTAMP_YEAR*12, +} + +func TestGetAmountByHeight(t *testing.T) { + for year := HALVING_START_YEAR; year <= HALVING_END_YEAR; year++ { + firstBlockOfYear := FIRST_BLOCK_OF_YEAR[year-1] + uassert.Equal(t, GetAmountPerBlockPerHalvingYear(year), GetAmountByHeight(firstBlockOfYear)) + if year == HALVING_START_YEAR { + uassert.Equal(t, GetHalvingYearAccuAmount(year), GetHalvingYearMaxAmount(year)) + } else { + uassert.Equal(t, GetHalvingYearAccuAmount(year), GetHalvingYearAccuAmount(year-1)+GetHalvingYearMaxAmount(year)) + uassert.Equal(t, int64(1), GetHalvingYearStartBlock(year)-GetHalvingYearEndBlock(year-1)) + } + uassert.Equal(t, blockPerYear, (GetHalvingYearEndBlock(year) - GetHalvingYearStartBlock(year) + 1)) + + if year == HALVING_START_YEAR { + uassert.Equal(t, GetHalvingYearMaxAmount(year)+GetAmountPerBlockPerHalvingYear(year+1), calculateAmountToMint(GetHalvingYearStartBlock(year), GetHalvingYearStartBlock(year+1))) + } else if year == HALVING_END_YEAR { + uassert.Equal(t, GetHalvingYearMaxAmount(year)-GetAmountPerBlockPerHalvingYear(year), calculateAmountToMint(GetHalvingYearStartBlock(year)+1, GetHalvingYearEndBlock(year)+1)) + } else { + uassert.Equal(t, GetHalvingYearMaxAmount(year)-GetAmountPerBlockPerHalvingYear(year)+GetAmountPerBlockPerHalvingYear(year+1), calculateAmountToMint(GetHalvingYearStartBlock(year)+1, GetHalvingYearStartBlock(year+1))) + } + } +} + +func TestGetHalvingYearByHeight(t *testing.T) { + t.Run("during halving years", func(t *testing.T) { + for year := HALVING_START_YEAR; year <= HALVING_END_YEAR; year++ { + firstBlockOfYear := FIRST_BLOCK_OF_YEAR[year-1] + uassert.Equal(t, year, GetHalvingYearByHeight(firstBlockOfYear)) + } + }) + + t.Run("no year after 12 years", func(t *testing.T) { + uassert.Equal(t, int64(0), GetHalvingYearByHeight(GetEndHeight()+1)) + }) +} + +func TestGetHalvingYearAndEndTimestamp(t *testing.T) { + t.Run("bit of extra timestamp for each year", func(t *testing.T) { + for year := HALVING_START_YEAR; year <= HALVING_END_YEAR; year++ { + firstTimestampOfYear := FIRST_TIMESTAMP_OF_YEAR[year-1] + gotYear, gotEndTimestamp := getHalvingYearAndEndTimestamp(firstTimestampOfYear + 5) // after 5s + uassert.Equal(t, year, gotYear) + uassert.Equal(t, gotEndTimestamp, END_TIMESTAMP_OF_YEAR[year-1]) + } + }) + + t.Run("after 12 years", func(t *testing.T) { + year, endTimestamp := getHalvingYearAndEndTimestamp(GetEndTimestamp() + 1) + uassert.Equal(t, int64(0), year) + uassert.Equal(t, int64(0), endTimestamp) + }) +} + +func TestHalvingYearStartBlock(t *testing.T) { + setHalvingYearStartBlock(1, 100) + uassert.Equal(t, GetHalvingYearStartBlock(1), int64(100)) +} + +func TestHalvingYearTimestamp(t *testing.T) { + setHalvingYearTimestamp(2, 200) + uassert.Equal(t, GetHalvingYearTimestamp(2), int64(200)) +} + +func TestHalvingYearMaxAmount(t *testing.T) { + setHalvingYearMaxAmount(3, 300) + uassert.Equal(t, GetHalvingYearMaxAmount(3), uint64(300)) +} + +func TestHalvingYearMintAmount(t *testing.T) { + setHalvingYearMintAmount(4, 400) + uassert.Equal(t, GetHalvingYearMintAmount(4), uint64(400)) +} + +func TestHalvingYearAccuAmount(t *testing.T) { + setHalvingYearAccuAmount(5, 500) + uassert.Equal(t, GetHalvingYearAccuAmount(5), uint64(500)) +} + +func TestAmountPerBlockPerHalvingYear(t *testing.T) { + setAmountPerBlockPerHalvingYear(6, 600) + uassert.Equal(t, GetAmountPerBlockPerHalvingYear(6), uint64(600)) +} + +func TestGetHalvingInfo(t *testing.T) { + jsonStr, err := json.Unmarshal([]byte(GetHalvingInfo())) + uassert.NoError(t, err) + + halving := jsonStr.MustKey("halvings").MustArray() + uassert.Equal(t, len(halving), 12) +} + +func TestSetAvgBlockTimeInMsByAdmin(t *testing.T) { + t.Run("panic if caller is not admin", func(t *testing.T) { + uassert.PanicsWithMessage(t, + "caller(g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm) has no permission", + func() { + SetAvgBlockTimeInMsByAdmin(1) + }, + ) + }) + + t.Run("success if caller is admin", func(t *testing.T) { + std.TestSkipHeights(1) + std.TestSetRealm(adminRealm) + SetAvgBlockTimeInMsByAdmin(2) + uassert.Equal(t, GetAvgBlockTimeInMs(), int64(2)) + }) +} + +func TestSetAvgBlockTimeInMs(t *testing.T) { + t.Run("panic if caller is not governance contract", func(t *testing.T) { + uassert.PanicsWithMessage(t, + "caller(g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm) has no permission", + func() { + SetAvgBlockTimeInMs(3) + }, + ) + }) + + t.Run("success if caller is governance contract", func(t *testing.T) { + std.TestSkipHeights(3) + std.TestSetRealm(govRealm) + SetAvgBlockTimeInMs(4) + uassert.Equal(t, GetAvgBlockTimeInMs(), int64(4)) + }) +} diff --git a/_deploy/r/gnoswap/gns/tests/gns_calculate_and_mint_test.gnoA b/_deploy/r/gnoswap/gns/tests/gns_calculate_and_mint_test.gnoA new file mode 100644 index 000000000..2cb56ff19 --- /dev/null +++ b/_deploy/r/gnoswap/gns/tests/gns_calculate_and_mint_test.gnoA @@ -0,0 +1,106 @@ +package gns + +import ( + "std" + "testing" + + "gno.land/p/demo/uassert" + + "gno.land/r/gnoswap/v1/common" + "gno.land/r/gnoswap/v1/consts" +) + +var ( + emissionRealm = std.NewCodeRealm(consts.EMISSION_PATH) + adminRealm = std.NewUserRealm(consts.ADMIN) +) + +func TestCalculateAmountToMint(t *testing.T) { + t.Run("1 block for same year 01", func(t *testing.T) { + amount := calculateAmountToMint(GetLastMintedHeight()+1, GetLastMintedHeight()+1) + uassert.Equal(t, amountPerBlockPerHalvingYear[0], amount) + }) + + t.Run("2 block for same year 01", func(t *testing.T) { + amount := calculateAmountToMint(GetLastMintedHeight()+2, GetLastMintedHeight()+3) + uassert.Equal(t, amountPerBlockPerHalvingYear[1]*2, amount) + }) + + t.Run("entire block for year 01 + 1 block for year 02", func(t *testing.T) { + minted := calculateAmountToMint(GetLastMintedHeight()+4, GetHalvingYearStartBlock(2)) + + // minted all amount for year 01 + uassert.Equal(t, GetHalvingYearMaxAmount(1), GetHalvingYearMintAmount(1)) + + // minted 1 block for year 02 + uassert.Equal(t, amountPerBlockPerHalvingYear[1], GetHalvingYearMintAmount(2)) + }) + + t.Run("entire block for 12 years", func(t *testing.T) { + calculateAmountToMint(GetHalvingYearStartBlock(1), GetHalvingYearEndBlock(12)) + + for year := int64(1); year <= 12; year++ { + uassert.Equal(t, GetHalvingYearMaxAmount(year), GetHalvingYearMintAmount(year)) + } + }) + + t.Run("no emission amount for after 12 years", func(t *testing.T) { + amount := calculateAmountToMint(GetHalvingYearStartBlock(12), GetHalvingYearEndBlock(12)+1) + uassert.Equal(t, uint64(0), amount) + }) +} + +func TestMintGns(t *testing.T) { + t.Run("panic for swap is halted", func(t *testing.T) { + std.TestSetRealm(adminRealm) + std.TestSkipHeights(123) // pass some block to bypass last block check + common.SetHaltByAdmin(true) // set halt + uassert.PanicsWithMessage(t, "[GNOSWAP-COMMON-002] halted || gnoswap halted", func() { + MintGns(a2u(consts.ADMIN)) + }) + + common.SetHaltByAdmin(false) // unset halt + }) + + t.Run("panic if caller is not emission contract", func(t *testing.T) { + uassert.PanicsWithMessage(t, "caller(g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm) has no permission", func() { + MintGns(a2u(consts.ADMIN)) + }) + }) + + t.Run("do not mint for same block", func(t *testing.T) { + std.TestSetRealm(emissionRealm) + std.TestSkipHeights(-123) // revert height to get caught by last block check + mintedAmount := MintGns(a2u(consts.ADMIN)) + uassert.Equal(t, uint64(0), mintedAmount) + }) + + t.Run("mint by year, until emission ends", func(t *testing.T) { + resetObject(t) + for year := int64(1); year <= 12; year++ { + skipUntilLastHeightOfHalvingYear(t, year) + + std.TestSetRealm(emissionRealm) + mintedAmount := MintGns(a2u(consts.ADMIN)) + + uassert.Equal(t, GetHalvingYearMaxAmount(year), mintedAmount) + uassert.Equal(t, GetHalvingYearMaxAmount(year), GetHalvingYearMintAmount(year)) + uassert.Equal(t, GetHalvingYearAccuAmount(year), GetMintedEmissionAmount()) + } + }) + + t.Run("no more emission after it ends", func(t *testing.T) { + std.TestSkipHeights(blockPerYear) + + std.TestSetRealm(emissionRealm) + mintedAmount := MintGns(a2u(consts.ADMIN)) + uassert.Equal(t, uint64(0), mintedAmount) + }) +} + +func skipUntilLastHeightOfHalvingYear(t *testing.T, year int64) { + t.Helper() + + lastHeight := GetHalvingYearEndBlock(year) + std.TestSkipHeights(lastHeight - std.GetHeight()) +} diff --git a/_deploy/r/gnoswap/gns/tests/minted_and_left_emission_amount_test.gnoA b/_deploy/r/gnoswap/gns/tests/minted_and_left_emission_amount_test.gnoA new file mode 100644 index 000000000..9a47d2187 --- /dev/null +++ b/_deploy/r/gnoswap/gns/tests/minted_and_left_emission_amount_test.gnoA @@ -0,0 +1,98 @@ +package gns + +import ( + "std" + "testing" + + "gno.land/p/demo/uassert" + + "gno.land/r/gnoswap/v1/consts" +) + +var ( + emissionRealm = std.NewCodeRealm(consts.EMISSION_PATH) + adminRealm = std.NewUserRealm(consts.ADMIN) +) + +func TestCheckInitialData(t *testing.T) { + t.Run("totalSupply", func(t *testing.T) { + uassert.Equal(t, INITIAL_MINT_AMOUNT, TotalSupply()) + }) + + t.Run("mintedAmount", func(t *testing.T) { + uassert.Equal(t, uint64(0), GetMintedEmissionAmount()) + }) + + t.Run("leftEmissionAmount", func(t *testing.T) { + uassert.Equal(t, MAX_EMISSION_AMOUNT, GetLeftEmissionAmount()) + }) +} + +func TestMintAndCheckRelativeData(t *testing.T) { + // before mint + oldTotalSupply := TotalSupply() + oldMintedAmount := GetMintedEmissionAmount() + oldLeftEmissionAmount := GetLeftEmissionAmount() + + // mint + mintAmountFor10Blocks := uint64(142694060) + t.Run("mint for 10 blocks", func(t *testing.T) { + std.TestSetRealm(emissionRealm) + std.TestSkipHeights(10) + mintedAmount := MintGns(a2u(consts.ADMIN)) + uassert.Equal(t, mintAmountFor10Blocks, mintedAmount) + }) + + // after mint + t.Run("increment of totalSupply", func(t *testing.T) { + uassert.Equal(t, oldTotalSupply+mintAmountFor10Blocks, TotalSupply()) + }) + + t.Run("increment of mintedAmount", func(t *testing.T) { + uassert.Equal(t, oldMintedAmount+mintAmountFor10Blocks, GetMintedEmissionAmount()) + }) + + t.Run("decrement of leftEmissionAmount", func(t *testing.T) { + uassert.Equal(t, oldLeftEmissionAmount-mintAmountFor10Blocks, GetLeftEmissionAmount()) + }) +} + +func TestBurnAndCheckRelativeData(t *testing.T) { + // before burn + oldTotalSupply := TotalSupply() + oldMintedAmount := GetMintedEmissionAmount() + oldLeftEmissionAmount := GetLeftEmissionAmount() + oldBurnAmount := GetBurnAmount() + + // burn + burnAmount := uint64(100000000) + t.Run("burn amount", func(t *testing.T) { + std.TestSetRealm(adminRealm) + Burn(a2u(consts.ADMIN), burnAmount) + }) + + // after burn + t.Run("decrement of totalSupply", func(t *testing.T) { + uassert.Equal(t, oldTotalSupply-burnAmount, TotalSupply()) + }) + + t.Run("same for mintedAmount", func(t *testing.T) { + // it is already `minted` amount, therefore it is not affected by burn + uassert.Equal(t, oldMintedAmount, GetMintedEmissionAmount()) + }) + + t.Run("totalSupply should be same or less than inital mint + acutal mint amount", func(t *testing.T) { + // burn does affect totalSupply + uassert.True(t, TotalSupply() <= INITIAL_MINT_AMOUNT+GetMintedEmissionAmount()) + }) + + t.Run("same for leftEmissionAmount", func(t *testing.T) { + // leftEmissionAmount gets affected by only mint + uassert.Equal(t, oldLeftEmissionAmount, GetLeftEmissionAmount()) + }) + + t.Run("increment of burnAmount", func(t *testing.T) { + // `burn` only increments burnAmount + uassert.Equal(t, oldBurnAmount+burnAmount, GetBurnAmount()) + }) +} diff --git a/_deploy/r/gnoswap/gns/tests/z1_filetest.gno b/_deploy/r/gnoswap/gns/tests/z1_filetest.gno new file mode 100644 index 000000000..d98424197 --- /dev/null +++ b/_deploy/r/gnoswap/gns/tests/z1_filetest.gno @@ -0,0 +1,113 @@ +// change block avg time from 2s to 2.5s + +// PKGPATH: gno.land/r/gnoswap/v1/gns_test +package gns_test + +import ( + "std" + "testing" + + "gno.land/p/demo/testutils" + "gno.land/p/demo/uassert" + pusers "gno.land/p/demo/users" + + "gno.land/r/gnoswap/v1/consts" + "gno.land/r/gnoswap/v1/gns" +) + +var t *testing.T + +var ( + user01Addr = testutils.TestAddress("user01Addr") + user01Realm = std.NewUserRealm(user01Addr) +) + +func main() { + skip50Blocks() + blockTime2500ms() + reachAlmostFirstHalving() + reachExactFirstHalving() + startSecondHalving() +} + +func skip50Blocks() { + std.TestSkipHeights(50) + uassert.Equal(t, std.GetHeight(), int64(173)) + + std.TestSetRealm(std.NewCodeRealm(consts.EMISSION_PATH)) + mintedAmount := gns.MintGns(a2u(user01Addr)) + uassert.Equal(t, uint64(713470300), mintedAmount) // 14269406 * 50 + uassert.Equal(t, uint64(713470300), gns.GetMintedEmissionAmount()) +} + +func blockTime2500ms() { + std.TestSetRealm(std.NewUserRealm(consts.ADMIN)) + gns.SetAvgBlockTimeInMsByAdmin(2500) + // for block time 2.5s, amount per block is 17836759 + + // first halving year end block = 15768122 + // first halving year end timestamp = 1234567990 + + // 50 block minted from L#38 + // > current height = 173 + // > current timestamp = 1234568140 + + // 1266103890 - 1234567990 = 31535900 // timestamp left for current halving year + // 31535900000 / 2500(new block avg time/ms) = 12614360 // number of block left for current halving year + + // 713470300 // already minted amount + + // first halving year should mint 225000000000000 + // 225000000000000 - 713470300 = 224999286529700 + // 224999286529700 / 12614360 = 17836759 + + std.TestSetRealm(std.NewCodeRealm(consts.EMISSION_PATH)) + std.TestSkipHeights(1) + mintedAmount := gns.MintGns(a2u(user01Addr)) + uassert.Equal(t, uint64(17836757), mintedAmount) + uassert.Equal(t, uint64(713470300+17836757), gns.GetMintedEmissionAmount()) // 731307057 +} + +func reachAlmostFirstHalving() { + + firstEnds := gns.GetHalvingYearEndBlock(1) + blockLeftUntilEnd := firstEnds - std.GetHeight() + + std.TestSkipHeights(blockLeftUntilEnd - 1) // 1 block left until first halving year ends + std.TestSetRealm(std.NewCodeRealm(consts.EMISSION_PATH)) + gns.MintGns(a2u(user01Addr)) + + uassert.Equal(t, uint64(224999969664063), gns.GetMintedEmissionAmount()) + // 224999969664063 - 731307057 = 224999238357006 + // 224999238357006 / 12614358 = 17836757 +} + +func reachExactFirstHalving() { + std.TestSkipHeights(1) + std.TestSetRealm(std.NewCodeRealm(consts.EMISSION_PATH)) + gns.MintGns(a2u(user01Addr)) + + // minted all amount for first halving year + uassert.Equal(t, uint64(225000000000000), gns.GetMintedEmissionAmount()) + + year := gns.GetHalvingYearByHeight(std.GetHeight()) + uassert.Equal(t, int64(1), year) +} + +func startSecondHalving() { + std.TestSkipHeights(1) + + year := gns.GetHalvingYearByHeight(std.GetHeight()) + uassert.Equal(t, int64(2), year) + + amount := gns.GetAmountByHeight(std.GetHeight()) + uassert.Equal(t, uint64(14269406), amount) + + std.TestSetRealm(std.NewCodeRealm(consts.EMISSION_PATH)) + gns.MintGns(a2u(user01Addr)) + uassert.Equal(t, uint64(225000000000000+14269406), gns.GetMintedEmissionAmount()) +} + +func a2u(addr std.Address) pusers.AddressOrName { + return pusers.AddressOrName(addr) +} diff --git a/_deploy/r/gnoswap/gns/tests/z2_filetest.gno b/_deploy/r/gnoswap/gns/tests/z2_filetest.gno new file mode 100644 index 000000000..c5ec13db1 --- /dev/null +++ b/_deploy/r/gnoswap/gns/tests/z2_filetest.gno @@ -0,0 +1,136 @@ +// change block avg time from 2s to 4s + +// PKGPATH: gno.land/r/gnoswap/v1/gns_test +package gns_test + +import ( + "std" + "testing" + + "gno.land/p/demo/testutils" + "gno.land/p/demo/uassert" + pusers "gno.land/p/demo/users" + + "gno.land/r/gnoswap/v1/consts" + "gno.land/r/gnoswap/v1/gns" +) + +var t *testing.T + +var ( + user01Addr = testutils.TestAddress("user01Addr") + user01Realm = std.NewUserRealm(user01Addr) +) + +func main() { + skip50Blocks() + blockTime4000ms() + reachFirstHalving() + startSecondHalving() + reachSecondHalving() +} + +func skip50Blocks() { + std.TestSkipHeights(50) + uassert.Equal(t, std.GetHeight(), int64(173)) + + std.TestSetRealm(std.NewCodeRealm(consts.EMISSION_PATH)) + gns.MintGns(a2u(user01Addr)) // 14269406 * 50 = 713470300 + uassert.Equal(t, uint64(713470300), gns.GetMintedEmissionAmount()) +} + +func blockTime4000ms() { + std.TestSetRealm(std.NewUserRealm(consts.ADMIN)) + gns.SetAvgBlockTimeInMsByAdmin(4000) + // for block time 4s, amount per block is 28538812 + + // first halving year end block = 15768122 + // first halving year end timestamp = 1234567990 + + // 50 block minted from L#38 + // > current height = 173 + // > current timestamp = 1234568140 + + // 1266103890 - 1234567990 = 31535900 // timestamp left for current halving year + // 31535900000 / 4000(new block avg time/ms) = 7883975 // number of block left for current halving year + + // 713470300 // already minted amount + + // first halving year should mint 225000000000000 + // 225000000000000 - 713470300 = 224999286529700 + // 224999286529700 / 7883975 ≈ 28538812 + + std.TestSetRealm(std.NewCodeRealm(consts.EMISSION_PATH)) + std.TestSkipHeights(1) + mintedAmount := gns.MintGns(a2u(user01Addr)) // 28538812 + uassert.Equal(t, uint64(713470300+28538812), gns.GetMintedEmissionAmount()) // 742009112 + + firstYearAmountPerBlock := gns.GetAmountPerBlockPerHalvingYear(1) + uassert.Equal(t, uint64(28538812), firstYearAmountPerBlock) + + // next halving year start/end block + uassert.Equal(t, int64(7884149), gns.GetHalvingYearStartBlock(2)) + uassert.Equal(t, int64(15768149), gns.GetHalvingYearEndBlock(2)) + + // orig year01 start block = 123 + // 50 block mined from L#38 + // current block = 173 + // current timestamp = 1234567990 + // orig year01 end timestamp = 1266103890 + + // 1266103890 - 1234567990 = 31535900 // timestamp left for current halving year + // 31535900000(ms) / 4000(ms) = 7883975 // number of block left for current halving year + + // 173 + 7883975 = 7884148 // end block of current halving year + // 7884148 + 1 = 7884149 // start block of next halving year + + // 7884000 // based on 4s block, how many block in a year + // 7884149 + 7884000 = 15768149 // end block of next halving year +} + +func reachFirstHalving() { + // current := 174 + // nextHalving := 7884148 + // 7884148 - 174 = 7883974 + + std.TestSkipHeights(7883974) + std.TestSetRealm(std.NewCodeRealm(consts.EMISSION_PATH)) + gns.MintGns(a2u(user01Addr)) + + // minted all amount for first halving year + uassert.Equal(t, uint64(225000000000000), gns.GetMintedEmissionAmount()) + + year := gns.GetHalvingYearByHeight(std.GetHeight()) + uassert.Equal(t, int64(1), year) +} + +func startSecondHalving() { + std.TestSkipHeights(1) + + year := gns.GetHalvingYearByHeight(std.GetHeight()) + uassert.Equal(t, int64(2), year) + + amount := gns.GetAmountByHeight(std.GetHeight()) + uassert.Equal(t, uint64(14269406), amount) + + std.TestSetRealm(std.NewCodeRealm(consts.EMISSION_PATH)) + gns.MintGns(a2u(user01Addr)) + uassert.Equal(t, uint64(225000000000000+14269406), gns.GetMintedEmissionAmount()) +} + +func reachSecondHalving() { + blockLeftUntilEndOfYear02 := gns.GetHalvingYearEndBlock(2) - std.GetHeight() + + std.TestSkipHeights(int64(blockLeftUntilEndOfYear02)) + std.TestSetRealm(std.NewCodeRealm(consts.EMISSION_PATH)) + gns.MintGns(a2u(user01Addr)) + + // minted all amount until second halving + year01Max := gns.GetHalvingYearMaxAmount(1) + year02Max := gns.GetHalvingYearMaxAmount(2) + uassert.Equal(t, year01Max+year02Max, gns.GetMintedEmissionAmount()) +} + +func a2u(addr std.Address) pusers.AddressOrName { + return pusers.AddressOrName(addr) +} diff --git a/_deploy/r/gnoswap/gns/tests/z3_filetest.gno b/_deploy/r/gnoswap/gns/tests/z3_filetest.gno new file mode 100644 index 000000000..1f81f34f7 --- /dev/null +++ b/_deploy/r/gnoswap/gns/tests/z3_filetest.gno @@ -0,0 +1,108 @@ +// change block avg time from 2s > 4s> 3s + +// PKGPATH: gno.land/r/gnoswap/v1/gns_test +package gns_test + +import ( + "std" + "testing" + + "gno.land/p/demo/testutils" + "gno.land/p/demo/uassert" + pusers "gno.land/p/demo/users" + + "gno.land/r/gnoswap/v1/consts" + "gno.land/r/gnoswap/v1/gns" +) + +var t *testing.T + +var ( + user01Addr = testutils.TestAddress("user01Addr") + user01Realm = std.NewUserRealm(user01Addr) +) + +func main() { + skip50Blocks() + blockTime4000ms() // 4s + reachAboutHalfOfFirstHalving() + blockTime3000ms() // 3s + reachFirstHalving() + startSecondHalving() +} + +func skip50Blocks() { + std.TestSkipHeights(50) + uassert.Equal(t, std.GetHeight(), int64(173)) + + std.TestSetRealm(std.NewCodeRealm(consts.EMISSION_PATH)) + gns.MintGns(a2u(user01Addr)) // 14269406 * 50 = 713470300 + uassert.Equal(t, uint64(713470300), gns.GetMintedEmissionAmount()) +} + +func blockTime4000ms() { + std.TestSetRealm(std.NewUserRealm(consts.ADMIN)) + gns.SetAvgBlockTimeInMsByAdmin(4000) + + std.TestSkipHeights(1) + std.TestSetRealm(std.NewCodeRealm(consts.EMISSION_PATH)) + gns.MintGns(a2u(user01Addr)) // 28538812 + uassert.Equal(t, uint64(713470300+28538812), gns.GetMintedEmissionAmount()) // 742009112 + + // next halving year start/end block + uassert.Equal(t, int64(7884149), gns.GetHalvingYearStartBlock(2)) + uassert.Equal(t, int64(15768149), gns.GetHalvingYearEndBlock(2)) +} + +func reachAboutHalfOfFirstHalving() { + // current block = 174 + // end block of first halving year = 7884148 + // 7884148 - 174 = 7883974 // block left to next halving + + std.TestSkipHeights(3941987) + std.TestSetRealm(std.NewCodeRealm(consts.EMISSION_PATH)) + gns.MintGns(a2u(user01Addr)) + + // stil in first halving year + year := gns.GetHalvingYearByHeight(std.GetHeight()) + uassert.Equal(t, int64(1), year) +} + +func blockTime3000ms() { + std.TestSetRealm(std.NewUserRealm(consts.ADMIN)) + gns.SetAvgBlockTimeInMsByAdmin(3000) + std.TestSkipHeights(1) +} + +func reachFirstHalving() { + blockLeftUntilEndOfYear01 := gns.GetHalvingYearEndBlock(1) - std.GetHeight() + std.TestSkipHeights(blockLeftUntilEndOfYear01) + + std.TestSetRealm(std.NewCodeRealm(consts.EMISSION_PATH)) + gns.MintGns(a2u(user01Addr)) + + // minted all amount for first halving year + uassert.Equal(t, gns.GetHalvingYearMaxAmount(1), gns.GetMintedEmissionAmount()) + + // we're at the end of first halving year + year := gns.GetHalvingYearByHeight(std.GetHeight()) + uassert.Equal(t, int64(1), year) +} + +func startSecondHalving() { + std.TestSkipHeights(1) + + year := gns.GetHalvingYearByHeight(std.GetHeight()) + uassert.Equal(t, int64(2), year) + + amount := gns.GetAmountByHeight(std.GetHeight()) + uassert.Equal(t, uint64(14269406), amount) + + std.TestSetRealm(std.NewCodeRealm(consts.EMISSION_PATH)) + gns.MintGns(a2u(user01Addr)) + uassert.Equal(t, uint64(225000000000000+14269406), gns.GetMintedEmissionAmount()) +} + +func a2u(addr std.Address) pusers.AddressOrName { + return pusers.AddressOrName(addr) +} diff --git a/_deploy/r/gnoswap/gns/tests/z4_filetest.gno b/_deploy/r/gnoswap/gns/tests/z4_filetest.gno new file mode 100644 index 000000000..f4ea61add --- /dev/null +++ b/_deploy/r/gnoswap/gns/tests/z4_filetest.gno @@ -0,0 +1,93 @@ +// reach all having years and minted all amount +// and then no gns will be minted + +// PKGPATH: gno.land/r/gnoswap/v1/gns_test +package gns_test + +import ( + "std" + "testing" + + "gno.land/p/demo/testutils" + "gno.land/p/demo/uassert" + "gno.land/p/demo/ufmt" + pusers "gno.land/p/demo/users" + + "gno.land/r/gnoswap/v1/consts" + "gno.land/r/gnoswap/v1/gns" +) + +var t *testing.T + +var ( + user01Addr = testutils.TestAddress("user01Addr") + user01Realm = std.NewUserRealm(user01Addr) +) + +func main() { + reachAlmostEndOfEmission() + skip50Blocks() + moreBlocksAfterEmissionEnds() +} + +func reachAlmostEndOfEmission() { + endHeight := gns.GetEndHeight() + untilEnds := endHeight - std.GetHeight() + std.TestSkipHeights(untilEnds - 10) // 10 blocks before end of emission + + std.TestSetRealm(std.NewCodeRealm(consts.EMISSION_PATH)) + minted := gns.MintGns(a2u(user01Addr)) + + // minted all amount for year 01 ~ 11 + for year := int64(1); year <= 11; year++ { + uassert.Equal( + t, + gns.GetHalvingYearMaxAmount(year), + gns.GetHalvingYearMintAmount(year), + ufmt.Sprintf("shoud been minted max amount for year %d", year), + ) + } + + // we're at the end of last halving year + year := gns.GetHalvingYearByHeight(std.GetHeight()) + uassert.Equal(t, int64(12), year) +} + +func skip50Blocks() { + // 10 block left until emission ends + // skipping 50 blocks will reach the end of emission + // and it should mint for only 10 blocks + + std.TestSetRealm(std.NewCodeRealm(consts.EMISSION_PATH)) + std.TestSkipHeights(50) + gns.MintGns(a2u(user01Addr)) + + // total supply + totalSupply := gns.TotalSupply() + uassert.Equal(t, gns.MAXIMUM_SUPPLY, totalSupply) + + // minted amount + mintedAmount := gns.GetMintedEmissionAmount() + uassert.Equal(t, gns.MAX_EMISSION_AMOUNT, mintedAmount) +} + +func moreBlocksAfterEmissionEnds() { + std.TestSetRealm(std.NewCodeRealm(consts.EMISSION_PATH)) + std.TestSkipHeights(10) + + // no gns will be minted after emission ends + minted := gns.MintGns(a2u(user01Addr)) + uassert.Equal(t, uint64(0), minted) + + // total supply + totalSupply := gns.TotalSupply() + uassert.Equal(t, gns.MAXIMUM_SUPPLY, totalSupply) + + // minted amount + mintedAmount := gns.GetMintedEmissionAmount() + uassert.Equal(t, gns.MAX_EMISSION_AMOUNT, mintedAmount) +} + +func a2u(addr std.Address) pusers.AddressOrName { + return pusers.AddressOrName(addr) +} diff --git a/_deploy/r/gnoswap/gns/utils.gno b/_deploy/r/gnoswap/gns/utils.gno new file mode 100644 index 000000000..894a8d2c5 --- /dev/null +++ b/_deploy/r/gnoswap/gns/utils.gno @@ -0,0 +1,66 @@ +package gns + +import ( + "std" + + "gno.land/p/demo/ufmt" + pusers "gno.land/p/demo/users" + + "gno.land/r/gnoswap/v1/common" + "gno.land/r/gnoswap/v1/consts" +) + +func getPrev() (string, string) { + prev := std.PrevRealm() + return prev.Addr().String(), prev.PkgPath() +} + +func getPrevAddr() std.Address { + return std.PrevRealm().Addr() +} + +func a2u(addr std.Address) pusers.AddressOrName { + return pusers.AddressOrName(addr) +} + +func assertShouldNotBeHalted() { + common.IsHalted() +} + +func assertCallerIsEmission() { + caller := getPrevAddr() + if err := common.EmissionOnly(caller); err != nil { + panic(err) + } +} + +func assertCallerIsAdmin() { + caller := getPrevAddr() + if err := common.AdminOnly(caller); err != nil { + panic(err) + } +} + +func assertCallerIsGovernance() { + caller := getPrevAddr() + if err := common.GovernanceOnly(caller); err != nil { + panic(err) + } +} + +func assertValidYear(year int64) { + if year < HALVING_START_YEAR || year > HALVING_END_YEAR { + panic(addDetailToError(errInvalidYear, ufmt.Sprintf("year: %d", year))) + } +} + +func i64Min(x, y int64) int64 { + if x < y { + return x + } + return y +} + +func secToMs(sec int64) int64 { + return sec * consts.SECOND_IN_MILLISECOND +} diff --git a/audits/[BigInteger] Gnoswap Audit Report (ENG)_Final.pdf b/audits/[BigInteger] Gnoswap Audit Report (ENG)_Final.pdf new file mode 100644 index 0000000000000000000000000000000000000000..487afedae5c7aa836e5b9b78e8b94f16e0ed8b9a GIT binary patch literal 622618 zcmb@tby$>L+wZR;-7TSn(ltE{-HoJlNOyNjND7E_gQRqWG}1^&OG`IMDs*~OCY~Pz*HRDBDLm`@b zWq{tdaqCZV^*+C`TdjT;EM!Vn{Z8gt@$<&Lt61>JZubb0KBkHs*9()tDxD*Zz2 z5PV7X*VnE;H{yA-h1SuV>h-w*#S~5TRX<{L2@Kxe7GJ?6ynmK?UEPR~K}?4~G*^;i z;5@*LQGI20AMe3&LfMlTD;H4AH^v7?3L6kq1uod-rSJutkqjFZ3}0$PCp2C$NeVI>-VUV0m@R zplz8ap&ASkb<0mLbPX#tls-hnihU(`$6x2|dXvx4PF9s%SttLoC|+nGj&$^pJ1EZt zt8MW@YFtGH!hLabHsknfp0D|_)|HU3!q;4ux&_|3-dv@1`-dN)_o1R!7|jxI6^{!p z30QqC{G&R+hEPLTCSUfUH4Lr7Mf6;sF^>d|GJjruxL)h9_n&-- zh= z##NgBGYIEK_3A9~v*53(8!w7oHPg>kW3kov;{&{wG4<HEX+pj8-Sg3dow5Gu0kE)jn=ebC~{A$7~ zMR&OrS1lkhRzRHD8iL&#qPcp$__}bANR3bZ{U4=TEfb5-cn!-{#|B zJ=}D`r~_xG?!CTAI|Am$rOGF&H+?c&`C#o9NL^92aK|Hl-Ut+N*-+UTFAQ+_>5~;F zW-tHH8KF7m@d;o>)to5rigNETXXTZ+`ud=3Si+X>LejkOPXG(|!h5+v{`H^sv|i_j zH%@rV%I*^(1w%rub%M5UrU>+7oZB17t^p|{qeIch-0bCFCEuJ~>GupCAXqI2qIHik ze)3XV0uAelTPn^G>kY&+*o+zZn4a;&GcZ2xA#uaKCvsL_6X^seCBN^H8p(J)v+04h zwBD;KN}CyQKN4F%z4uu6yIAYd*i+*(-7a6|+eXJ}ol!<=g)diXI(~y^o)PxC2z(Kp z7sKF35r->s JqhP5wbMoWj$Wk=DAb=S`W6-Pea#0`+}(U{B?W)P$%?_~`*Fu1_1CTl)U=sYk zg$u4%?oVqN`qWOcAukH`!;;~9OaybH3|6MEw9}(aUq!!Y8n}m^T?T5Wh*vOq709Yi z@y+N(8AvFf>2PFXF0sJw@qoDBFosQh`E<3$!N|&vXC1J2kzrq$%4#eF;HYrwXYuY_ zZ8Im=$E;RAC6^z=MSK>_nn}Me9M3k^3LY4vxB2uzpIc^j!f#~FVl2G}vcYjORroB#;=XJuCP zus5OQ61KIqb4D)bE{4v3{Vr!=YlX!nK?^~y>~@aGoPt&xfV>Bwi^ZjEV&qJ#4Fhw+ z;Rq-QMhgdkIYBTeH=Gs-264h+AP@pU3kLx>5pWuTmfBFOl0dfK%a5%D0Ku8P- zk^@SM?4#do@c*Sxpte>nwxBy0u>Tn2PBcKsXaK<6U|J}0Jb;l+I4um!{Tq!Nd?y${ zD<@Zoi|ZYVI|=Fk)$kO@Z7tHf|;s+;`%E_=CLyg&~l`6$l5@f{?=m^t&%;{~Pb$ zqJVzuz`u4i|8LsyPuD=908R)9jzswJ`e~}7ycXX-=qBJih3t1KnN!sjDSOFpOUwD1X4Z_P$bHovGOmxe~vafAifzarET2bA=c}p;j(7cf#@~g%&xJk)ss> zM=rs?)eadTB+9?={yoZ{lv@Y@&IyAF%HyLfjl}kjFB%)*$zPP>|<4e@<<| z$a#a{My74FU=WBC20>!b{x{ygNBNU#`@2_Q5HL3~C;()G8`%QGki+*k-5nBJ3r8^6 zmD>bn;ADFzD1TCH!C)9CGGpaN3LH{PAPD57{ukW82l{q(Xo94jcpl z(ISuA{(`$h;%?{Rgs?I4Ft>1Wyc3c?skC5Z+6+VHuYV^@U}OS^B>5NJzeo9#N((}Y zE(iuk0zi@C0_TPxg91Vz=k{;BJ1ABbt|rdrZZ__A;5+JuTsQyR>;b_boCwhGWEKd6 zbApgM2L#goH{ZW!`IAcfd#pm?2qX�po-Kfe08a2)Xt6i|-DKC-QvN3TWbH?q~+T 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�ANAQt^U)b5c`zL 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

>J5NZ)FM>+cm`QDN(DSm+;L(XTy|(qzHb+V1>eL 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%0AWN)v!}|>22?O1 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∓^>Je;%BOHpJL(9N>dw->EFX<4POnwm~on4j#5i>Op@6U$}UfLQ}Ui)F+dq~ z^x*zl>*V$EA!l-nADZ@8XAt05%(3@!YDdO#)TZkCBr&Q-SiU_c6L<)MD~Xew4^HW+ z>7JiHuhC%m_JIm?`_H^s#U1q6ljruVC}ZcSkwMfbU!u|}xoQxRC*}%i}U;ny3TXU7^q3y-_UZPuJf` zM*(E->dxjdp6%FH?rO>Uve9}tr23BmpBTR+oc%f)XTu+?>#&9F3bhyC+jx95qwUyKHT z7Psa=+K@XykhI58<*@0hBGo`;bD;Zh-rA74)Wls29Yha`i1o6u#_+ay-2DlFs$^sq}A84g@AnO^9yb?1%EenvXU@<$xY*h zCV#D5 z%Vtekr-hZ*@B1Cu7^FV;Z;ON>5`zY2k-PI82^*h%OfmdaJ024siFk2-A4dK}TG$0G zRigJTw|k$_^&ZrSPkp~fDR2NHjCe(bfCkzIH5*VI?UsjS(Y|UcoU7rA)y9A1DyuRH zG@|N_hvh_mOs#(yAtN8JMb1)x_!5YDlE8rncE}9^eF^9e#3O_7J1ygJ`*^+T>3)Bz zsNt{f`Tp$L`TV&5iT5%1-1~|5ae8`MshCAD@IRNcQRa4Kyf2#F<#_UPtZ^FUYR ze-J*lNs%k|4S6a2$0*OUB#0CVtAGEY z;uiB?wGt^*76J^hq+S0k&~leKxAYu9Xy)0ou=TIytEyQDH`dHooMHU+Ao`#@7a%_F zONktt#>*K~a^#1PXjDP@tf>%zZTwixZ|YD)lqDn)j?_iun`{ZwBgET6Q@2!3@4_rvE zRF|S6sctSZ(fl{AIL4s+2@QkKPc8u-xUZ=A+>&GmgF_pf67Q`qRLvy|BZj2}d+t*} z>K|H!4eDU)z*Nr0P;}YOXgtFcPV{8B{dg9lw_SLraIkTGSDx%4p^fSy_T-6wP-=T# zqgZ4B7Ub4#OfD@Rv*zd4ORCM544hf z`?^s3w}jv2l+(0d8x)J&`l2JELhSe|PoGXn&*eN76g_h>x=EE2KeVzeS#xIiFog!U zlFQ0Mb<)Y;(51!2*BVKuNM%86Rw42{EA#EDPK$T*h8puI`rrx3RFh;wN_jnb`cWE{ zGRi4Qb25;m!jO<#Mq!+4VMRzxZz%YbOKrC+qG;`H&*O#ziS&t%XwYGuhs0_Lqh&#d z=@h;BpVnZ!FU~5*U4Tc4e!f61E5iP_BRA%MY>WX3N{5+jsm*M7he%=*$BaCw?+jLK z#Xp@Qu&m1&Zk@>slHo<7c$GGs%jmLQ=yZapvzos~(eKfnRhx$-7@7h~ zzeA#-v29(NEXI@*Y7!*l!58#e{Y)a=GGG3P-IG{DRoQB!PErsLdR3*NL5Z+dR=BlV z-WQK?hI*OJ2p~fa=VqE_qGe2x4+i7}Z8XXc5&q#j{=Dh6Qy+BLN4+%sSyuaC4=Cfl zD(W;7#6QJ!Sz=3e*%P_{Fv2>hiwQ-;-VSADf3WF;1E0$#rFw$0g92Pl>`{XXRknE1kgzQ{Wt|(c8?HSsJ{CpF;rRp6FEQ-juQbFYSmlaLkb_2en7F8ZZS=F|YJQ7E- z$~9$u<9W4@#X5*2(0=!j*#D5=ddUjgPsd5;tOiKh01p4$BJ}+lx^cazfDAh6JoHz} zC%x{8_@U3AMki$e-NeuYYSK=$PQmHF`F1CF=d38^U!0_sV+I<>b0;vNb$Qf)8y#kI zhCJuDS@wIUPaU)3;Bn;oQrLG7gC&;Q#1*7-iHfAAA)3RB)${WL^>1+}^0*88e3676 zM~e1%6*VRX@m)Z%@|5d>Oapxvm+<%H7VH8?GQpjo?3uKYD^pKwRqAc4 zm(l!NXqlA9d93wK0IAN`u#@SS{o=EgiF})kjR|6eZvgIiNqVBEc$0ACP2TTqqD2jk zXe?0Kubj70)vRp&eTW=u$p;bOeiVAKf+#^YZvvi2RSo!OU%HtweoOI^mL?vrt@~~} z47^EK1D8rm16_Tkc>&DslkU40NRLos@cvBLs#Qp?ZIXjGdd>N@&!qEi4#oS)9Wn~+ zI-Ws74zw^f$64x9cwgrtppIpCL&2)Sg}$hK84lB3fr*Vpb4(@99ZFuE#tA0&=bF*V z+F=~EIWqH#)ZnMU)RMhz0Wc{xWwKVbk-eL|GlkEh?;m4ZiQDY$_pfJ2uQcd%@=AC+C%z+ zApO#{VsAox^GuBvCGAV&-H@)J;=OTXI{x)L2veafW_I^@um}{bkr$_tH&(I)zYN4C zrdqG7DMggK-Lx>qCp$<+MKGp5+PVsaLJDptI2ya9ht9LxhNs=(MSrnMZ+v_f7*u=>aBj?hx|d>>8ce#pr`E55YC{!KU6_ZxBQu|)e=f1DbgDfiUmTOo+pvzg~9o4npihEWc+9osu&y*UKBss%E9S1 zGi%o+6nvqMiJ3DuCli&tWvLw1jW@3AIP_8m9|l5|(So1kifRKY>hKB(-o{P_>=hVB zLTkgj;{AbWvVa>cWaMPS;LWY970*=`_!2j2nSaC=vAPyz4Wcaf3Y_elOiwhG1Q=bAQ+cwVo20u_f#l4-}lac%eG{vNGGB1 z(_o9T2Bz8J*I8f;)Opl*qhm7MdLZV=C1YI(76n-g(PM56j1%ve2v{@gnCOdxAVH$s zdTxgGN))e?>FD1JFz*4L|sq=j-;I7?coSM`+6TOuV$+w7!>Fc72f#dJmU(*zLy9O@DSoz=tAi^{}$;)vp6k zmc<#Ub-+dKvWLKLkbhdt7gL5*>w5ev;fvCe|748VK9cVl3$Pp#@{eTpEl`tlFpp04 zQ-jkXIPRIP^F=8w4`pRgeF(ysquaEg46}B5hf6d#(_FCp<>BzMyKTKSosbbpCR=7r zvufVZxKy93xrCIR?q%i}_~)?tuf$)vXUUA9`{ys#$FUm-Z_uEuQ|5e)6)q<=S4)miyFdmB?X#6Tlq;P8PA*Ie>`S|E7C zeak6?ruwn2!cjHfGk~h9sRjqGCNpIf?}j4ysW^xN{IQ#G=F$9(2dbaMo(F0-HOxu= zFQ_5i*GQqg@d8^l%FD`mp&72r?`%u@G|TR%t67eHcKsZ97lCcgLBE0G*Jm`MJVDY|EnBgRwfR5HfAy~eyfU($7^M?Tjgl|8^zN_r#@075{v7YaLP7U7!4g!3g zZh4&a&WzXk1S4RNg1+J{?`F9jWQ`cRC z<6btzQ`hKPbOb2tMk3-)vh>=FSdis~S^2U{(-brx(Lxd{f=QXu>ltsfjO&yb1rGqx zXbpa-lLOoG+s3qncOHxakLNhUUSpmU{1XbJs3(DGW0g?Ry%#7(mC>qi_k>8H;i$;cFF zIBrX$JyFwlA7^BEs=0{Tjl-A~ok(&S3{uU$rO9Zpwlg_~D|4NU^L19-+TFJ4QPXET}S17CLoPbUu3~UxQbEGAg+Cp*7Ssgfu{M zUvyjLneAAiW@S8dm9=XcYMQ&Eh-$wjD8~h z`IbPO{nfYQ<@mwFKn4Qo7LrWudz0taqo8juO?J6Y;vfih*E^YfA%gAHF_-@X4wmSD zVibA5eIL|$mT%Z+ei~*GQ6B}TM>GYY4mmZb0PLFq^H&y^-OIhPTF|T3IRB8jS}ks3 zV@u=^`&^--F5ex1#6wHR9_}a17Ki3yEjU2FfCI#`mOv1cX#W{{PO>y$u4Hx;VnMtgBxvNxO!fT- zf|QixZR@>%0}&)<3Gj@3tkBch?KBA&;RHsDRSDUTMoDc^=M`6kuvjD#FbV|)W<&Np zr%{YR1N38*(j5%G#|-J&I)6-*x!>Aet>(Y|5+yhBu5EfM=dBtL&!@!bq=BAJslmpS z*!S8-W93t1%grTu3c~)~MQBO&w6`JN>L1xyI%RZd6%w%^O}m)x z${#Go+15Z{=k^-#*Kq)%XgV7UxTH9)=wo13n)(mG@hj4bpa6O>H;54z;tF#h0||j# zDUXzS-tiTpFaYn=oC69u3?!QF&f$qqgJXh$uI)H}?vO z$kKb@-*HO9PnJp4#)v;X2C#BH8Kh6$o0~XImM(Rb_N3$$ahl&;Fz+L-kw-_Ul?s1( zns?hG)cm2hBDo3auc;yOmKU?CErwLRt&}R)l?|kpEyEpc&`|Kqml7ZYRr}lPL9FrJ zbF6c8C%>`D!cdj@ZM|3Vq4oRL;kV_9_l*e3U%Cig_6U(~#5$}kY-IEY>E}2Y%xZF1 zrC(lK@aOGme2dX~+qI^F3Hblgl{x;hb#EeVtu%5Si6pw8i~RT((hlp$C~mS&wG(fY zqSmwal~;+F5U|^Gb&J_Ce|%s=aAz5Ep&?#ku%tJoH?^|1+z6lv%tW3XeJtq98;1z(~=7>TyLXo=^v(vU1TCT z$tB!}wMmT8_WGuxe=F}ssNB0FLXq6XO-u$!bqQX` z7Rb7Vdy-6b^6tXeUR}({&7$5yC^O>vT)mWo9&h%t@1XIbgOAe%ZL?IQi@H>csH(Tg z#lc=g98qY#h%hMSXUZkuTFk5(k~$?;`d};8BNnF~M#5eg3f&AO__F_0wityK^|=o% zCRJM^9EGOhP}TRdmv|zDP_oX^t+kNE(DiYpL>H+@Z4v1hHS@6Qy1hlfmgalWn?%qG zjl6CIFH>X&oOqx1(KV0lFv2L~(5Qm~a2?kzaYEt6I)c}!mzint2lE*RRc3ir4$CxbC^3>XA}TAC3i7q-ynU@;98bsK4_x&F;cb}CHy zW2Q%1)FpRuYzN=ZzPCh1cRzbo+-5}TreK?*C`fTrEX()^rh*nuNL0|eX&%^RzG30k zsX%m(R7Ur-D#0FV8VlN3>@YW9u?u;l>O~;#<7Iacd^|l$#b&`_Z7jSj<>aqD_fLk$ z*M7z3MvfO2hN1V;WC<<msnjA zr~D#o&)oLxlA-?TwaLTi-lhE2JTAOVAd zJwNvIAmcSgeQ3bA<7Ocw0G>5U)JW2pwWT2R4>o+NZ?wt#GM{d->Y8&>gPG&lA-)tF z^_aN9`32qaUaxZ(Yh_QxV zRmr?olV=-&G@~!v$Q&|7D;Xf*mcH)m4*`eP^=bc6F(;VBV1#bXh^}q+EUep*-nT_Cl<#5B{N8JMgPU?M{+__ znw<@4VW#I4NT1MIM_zCz_e>M#E3l6Eg4bVAtSc~}&c4(#oE`msQFlm}9O4!694e@N zdf|gV+@_TR@CsLVN2{sBs4j^SJW_~v){hOFlHAFJZf(SKBG+OGuu`R zpUoYWyddi@R`16Bs#25Ttg)(vFzVLEuEZWhimL?d*Vy-;ge;+{nRS=5*BV#Z_=q%& zNpw?g+ZAjE?C;)`P{8f8Wxuyem{sv&g6p`!-`~TL9#x6-#~Tj=(nVV*BW_V@dir_v z6@NuKga{3`9N4|JrVfUtKY{-OhD#(4JFehs9RYdEbB_C4K^=NcM4^Zq#a3PZ0qKpS zD)q;1382IA7&q=a&Ecd$%c@&L4CEd?6mo~FSC$U4?Z4L=uSn0wIMlzVlW1Gr<4o*q z;D#lt26hkyqp{AV=Dm6E3JFSc&>w%Xhe6t(4$HXike060`GxJKr($5!NX}h!Q$u;w zH0{%N>5J}m9IC9A^|n3{;o*gWMa$o2LEb{Cmq%Tc*TL8W&&63#eOf_R9J~@e4fUFi zk#jfX8iMNKJ<#YRd!KFoO`K8Nd;kROH%jK@O+(FoYb)|a3;vS&yz-V9Sa|g2f2HK6mKVMBhi+hHYc#L zM6~hkDB$7Fmzm7{_VFa_X4)&|L6u!B^rQoqCpET)sf>lnui=})qw4#iPG#qiE_Doo zY50xQvgV*#g`WVZ`C@U;Xy-@qkrS}a_&?R~|A#KYA3gQ|rufitAp4>Az5Ixg*?XK>L;O(t)^f#=8?9g-Pg2rZPRui~t<0Zaqxdom86@~gB8Ub23h6kHV?v0B-pVwzGaYP(zmX?;mF&$jJZgIvPHpn;f4+m!haHKgIM^%kI%BiS2nFg zgMe+*x2eDnXn|N=(?@}{hR3o36(ESX)0WQl$Ks{hHn9Fh4+FDDCHCUP+|PkG$;1Ha z&`vVfse~8}y zEXshi;Tv(!@;J4FF|~)vYXyFh)y+Wm@%M#b_0GoJ>l^j&?Wvx z?k83ZsP-O3@S(7yB8_YO*t^+USx)#Y_I^Eyt_7nIK-I!$VinxEy)(kxd_apu6a#@a zXQgLwvqwys9-Q*OGBJ;=e@!$ysetNGJ)tEgL{kD<$2%Wl9Xm;|LOQNJ0%R ztrBLO2u8MF2#I!Yg2h!k28LVPv-k-5dztlVOqCO9usA_Gy;@duJE<)d4DYEPE_obX zkx;6h%xt4%i?vMFTYgJ_2`S0q(7ql9i1}q8UtL9@Jp7IklR#Rcdfh0DtVxv8i)9Nz zTLV0%0-Pj89=>r2%Y2Hj47{@TWwcx1%;Auj@it`&L*7ih?5h)>R z`{pehBQokf$PxdQEH=TE{)FSbuhp@4B++)iAYyMIEuv}L1#gxaXj#(9`!q`B&mloZ z)af83diw3^xN$cAM8~cpjF(WOCDUqx#MZS)Z5S#aZ}>^SU#2WvHX+=#H>BwZg}5PP z6S(B5GB5ijK?ssm?md$PAEG6ZX_xQpHm^7U^opJ#Hxd)#o)Hlk8_aw028fv0c2xDa z6eTAVf_RKX9y`?Gk5^h>PU}37s&F@-bcEC5{%^W{A*9(ml=-x`N;!+Hof{qm)Zcm) zP4z6vN8zdk*xrH!X&=88FsBy&Dl5WC5m+t4?PTN3Lr)*(Z%zl2_W@w9mA?xNmh1Z& z)#C5pcRhXJ)i_k2^tdia%b3K_+0+bC0m6jdaJRt^3`RqsJBk!~vh;r}j*~{6PLz4( z28q>)&L``&o zR{wH-!oY_ z1t{xaC&tL@R?~0rD|>>6Rpt)K18&7BL;B>SBeXP*XEfsrmcn_uDakPF8J1uQYk-u^ zZ=v!-;V24vp9;)%OfK9qb#9BHAAo@MJj;~$B~-(6;Og&ZR=X{cuvOUL$3oIO*@0~b z(udH&nIvkMf1}+5CePvx>XlnUMCvvCi-TE}-zykfk?kNW z3^m~H?e&kF6=|h_^CW)Xc-?c;!}DO)SX~T_p;&G`sX!_ub_zgy`D39=`DYZxcAw-c zY?WkHh(6msdzwWyU zlZp|O1kU_)#@Lv~NAfEJRZ-A^g!N8%P@hzlr5^ePc|h?ec*T8z?yCW{WZl*3sprHE z1D)G(t%4Y#O1uWqH<)Bz9= zY30G-OEb|v_)%sjGGHoUrfgeU)&H1@R+O!7pp@__Tug=eThw|#q5CAoUcgt@sMo9$ zf#V;@&{>%q4#33_t#@-1XG+^d9_S<8EpzzzZ%7@JX)vw~xrhqsHLo-Pk1}dmN`ctU zX}1-=D@UmL;sNSVMY!r!j7>LNX^JE{X$UBX9VkB3OV}-9F763%37 zj?oauneoVP%7)#)xTsS=vQ{OiK-fj%;a>^DLQhjMsVmF#>(vZnsjv~F4^oU`S=4d_ zz{5^uN=uoWkXX|u!-M%lEkTp_-zMY6=MdM8V9!Hnuma)cyxoiTit-#}5HW3@LCTDb z3!mqDsb@T_XWBFipf=j~vzxb% zg9HSEchB>G$kQyKGc)e}grRqk3GO8cVc`Kr!}+muIoFSk(gS@l_#C?E)2G{5w-Hgd>kzuAl6+4`^809T5Sy#V}c?8)<@D!{Df5o%Qx$2M2d*9ay?9t*SdbdASCD-SE zHsgSC6%kqQD1P29FPF`?wf01nRgQ_UwLa@NNKuxLr`BJhN2U++8=5ItJ}BLxj%+Gs1%f+ic_3XWT`g3GQ7;up?7pNy*s0sPjEcG8 zmdjXX6<|lQmCXZBjI=^o&AN7mmcz-dSBlDwc%&QK#|kr`qAJjL&>-xauJ~`E#iR^t zuubaW?~5YDG2q}dM7=>-R6)~&PvG?1LACJqb6wUrV1q-l)wB`DAz>!MtuX+bTCDWz zV~%#FqfCzGjWZwFfZEibul-7jRO}nBU(`=MW(GJm|W8SEHMMPi9TEj~286~`@82oE7;(4fxDA!N`E9hoUm z%8%7q_emQ1Nuuw5?{7KC!C-ZfBi+2veLmw17*p z7I0OQ@Cgl?1cC}%U2K_Yee5n{+5mtIJFVx|+E&wzAGbfoM?~GAo#y>|-61iKKn`7IVL@m4O zj-oF}IR!Jjpf;$37|;Ofr|Gu#g>!1niHD!<(&;?-3o6-=q4qnw10FSv!l7N1*TA+$ zK$keFj|K<%ctjbg*Csrgiz?x=MamyZm7GRBdd9rKiKK88y%}(>hx6*Ox^$aRu${N zOBZoiovU|ssfxK(iMo9jj0tL}m#gC`BW!ogdLBDT)j{R-TrR+Gj zdU|2_ zqnB&$#a2h|EtC!RQ>g0wfnXBLzkbQF@BS`-zp9JPg*N)7rt0tP+-e`wvGu*i_UwPd zM{FAVE9K>o94>!t`mYfVaJzxSDp$fPGrqvb(Q$`<@i(ZWIhOGMP{DKjFAi#PD_hqe zgEWJpQ zCvJUcnfabiu^#pRzI(Vm8aUZe5cskU-TC$yUlFMfQeKD4@#p(=|Hs2e0{o-rFOdR;!KiVcdD zqAZC%ibx7xJbBMYqIXA=p`j}TP{BR+4Il1Dn*VE7B+F71moO`qiB_^SQw>r)5{=Jp zw=S102y|plT_jZX{*i_<(QB!o1iOjT>8i;bEpETX$h#?#q7~T3r8`LJ7smi^ZudSi zy&ig5TL-(ANm=EiPFuH|d5JYKK0;|QsLCi`pSEka=uv)}yH3m3lqpI7!YK8%_4iXVEv6=qRzcib*|IWp!^Lf8P&@Lvzbd@M~4$pQ5b_yHrOZLtLN(d8+4I;Afo!bnjG5GoUPD4_k z!2(hQTp)d(jw-Yvohj6W>fm@o=Y@Q2hE zx=rW{6{ceAH^Hl0tJ{vL9X&9wg}obGFt*1t+SR*p`ST&px2y>{1mD`7r`-D#gMLuT z)kZhJHp&|uxUhJo(v#L4`#s|)B}EN5)bG9Lc{QYq@<2w_C<<^>oOUenuHgs9D7Cg> zIz64*p5_Rb)G2SK$4G(BVg%1O4>AhVYg=9x2rpgxV_0rt{;Wz<_Cx*0&G` z?_w;zQ;$Zo_{PF=z5XSnlJtU#*KfGGfxjffy{GPv(O|>=wf zKLGno=2>TiMqTz8HmriA>-AZq6)As%A!mkv2LC=ET`F>a?@NDjDq zuFt-?xrm=DKZO2sWw1c2g1@7y=H)!59lqb??5+xGo2;liTEih`G3Q}pSfkCrl=M-M zR2MvKH?fHXbF2vaM#2UCkUi?$bu?Q^gZ6dS5ZK`}N*Qy`C7lL4|CE*AEhdF6Yf+u% zP}ZF1+%A0UKu;2cNAqabXwzN1U_0K^mO|m2(LEvZpTQYSp+Y)jtCDO*SK*2#c1Q64 zoc8=1wG~vsl3jt4>u)~WMI{T^-)2ctc_SfG#Irlm`uPP6B4c|9Vm_2`8y`eyQSIN7 zi-Z?=xe&y5e8hb z(r!Bxs@aBk-Lqd_h*lb+!p7`>9)A&w(IvJZH?aR_I8yT<$ykc`1XTyqpMPwk#wyJY z=N1q6jH)PdK|^uESrW4v2Kr&Gd*@g2jMUZ!xWZGp`w|hEI!a|Ag?7boXk5c96FC}D zT_2uFm0V+E$`vOe`Y&V@@Y5-<6(L6F=!k(hDI~iji0Jof$f8!69D}IfJZVe zZ?mGzQE z&7^SC6~szQ;uMmd%4Qarn#Kwb=ac#@ZE2&Z#iFiD^V+PW5n8@!clBw~V+wlP!yvoE zd};rYU)B_Hg76bs&6$_lDc7s=O+`K)Thxy1G{+B4$wFuUI#1V%e-DnO+R{p0vzs)z zOhKmCNsEj#%o@2oTSqHb5%Co(&9j1;DwZ8GZ=qhs3^D2-CNv`b4W6UK2Doz}7we?j z8?}d=Ex)jD)$0czYH;)84ZB1RCu~in>2Zgxc(Qa&cNCP;Ltz|BQzVEec@&i%tx7W5bzpxWk(^k>#hF1|zg!7y8<3H4B=VQ@ZxaG8VB` zMtq&))FdNIub3Km>p)+w39@gsMY>^|Me0<7>@SzrJq&qujskA5I!(kn3me$!|DvEN!I)ENMEUqyhe!_vR~OG7k0zUoVd{ zE@@u~D5F+A|5L&7zYwec|3^0cN8-!O#>~R`Bl~5cXW?LBVMW9I)Z`{npQ^u;rC{y((6 zQ;aB4_wC#EZrir)-fi2qZFldsZQHhO+qP}r{?60+ZvHtr$<0kF>#1HUSyeUXTw{zM z$A1UdYf0K-k05kEs7^9K=BlVZH30DU#wR+B6EFbg{25_HTou`8|LfyZ?rQ2{()QWz zY2r$<0Yyo#LXl9^=Z~NbR7wJo9hL&?gxFIz z6yt7i;9e0jhmYHsu%5Ax@!^({Kz6&fawf-Y>&5SKVTRbV`D5Y5p2N5_Tq zFk+BQcaV)&auk+)PfVuMey)zOIZ2D2;}Dzh1&IVo#7fC-$%0)NB^hUmCm zy@n1`B-AO;cNl-yH%HP>xu%zpfTHBhI>{i^;;psCUQ1OPY$cSRbTzD5SFHGx-qrG| z%I+aO(Zd~f=Mc8iKz6|W0JZTjojxHF;Eyhvro8M^K3?j4ZnjZI%4+d=r~?1T=$>R* zr>!5kwj3De>TBip@h-X; z9b+tgH?g|zgKkbY-j$m>iB{LVf;bO7q@?GMt<~-G)$LPYqRXPpFj$YR+lx~H zK>uZbE`Sg?fvgW-g0lWpx?1;qSgp>IC1Zme=8>d`cIl}HUC`5uCxyvLli~o8#ce6S z+`xyp;!_*ZZxDhj9nSO z`3o+DEz=1GLkxx>a9^gCmc4QvW3EJhQ}w^v5bLZJzguRQDh z31p=C-+(_;16>y^F3_pQXO2`quy@DHm|UnxZ&3l+$^5`?6$i51AS*JTW&8$Nv1W32 z$if;XxaWZ?{~&R-R+rvGdvgg2*dWpbqrKA$PoprCm=XTm2LobJ6lbvE95c{WH3Sb+ z|LqCb!*M=ZhU2?DUmPV6sV{bBjT=mU)ntlcS%zD{1zEh-Zz8qMQLi;hpcyi`_7m|9 zL2kY{>}9}m8DrfWaTZ!zp!{yvZ_YXq*vR8mPL$v^Cs@Jw-9JQ+sJCkC4JyoIwXw81@*o?M+lq8V(FXM@W)XQ`y^G8{UIW_*HD$! z+}Z4F(~chHXbRG^X!}>O8M9jiQ(=oPrc zS(*Hs5_F_S1WcP%Doc_<-*B*)G0ZUDO^w2wRzx3ug8XHj{wIjh0)BQ^I|aF|vPy_E z6&D`3oYf_c`~$^7U7!!yOI6e;;XMGgDzXFfK(cH?>yRDmX^t-yB@5eiwXdU&e*(7V zdu?tB7U718f-?3hl#&&tSbYeYcy2y}3|jtn*}|Ovg9n|LTqKqBS*;RiISrj|R!sYz z0_T7SM+fAdV4aAZMZ8qggXN^TsSDo~Jx-xY%#&Sk9E}^m;v6@38X3Bg4wUJJ6AT`S z-|CHb#0i{MYiiRioKk+Gu@(Y!b|x2n)?sCX02^+UHXLa0^vgGQbo$x_zG`la9I!lg6@VK;?T(!PWObjF{GIlQ_xLep!5n&bjSjV{4R#<>NU#400F2>w)-xJ%9&7ukr!ET=q;ysPH`7U$KN@0Pk@xy4G5P$i4;~fh?F9- zo_+#JQw8tQ8+7JN?x;??R7sBwlQjy0vs9a^JJ=0~P+8LmbzU3SdR$8*rN)4Tc-w!M z$z9BRBhl$`55d%@a;sX5c#?1l);?O5Z$fT}(x7ySIRWl>dEVzcMJxdS7mOkQHc=MYpyR zk3!Bi$ji>-J(OPU$9=^yMhLHZ@Ixjmp{0kUoU4)C%<^D6KW!C1lVnk-@GJ4ETsSSDEc3&Ug+IQb4&P+upqj3VW_L z=}L=v(yzZWy5wqn+vxHK^+9(v&Cb{0Ih52hf!S?&IgBIvT>*i`Z0H2@TkC`C*r|h& zl7!=w)4t6(Wawb@el6I4Ls-?&kwf;uvh-DRvbaCPIU|2Q*uwc~J8-)&+qpIMe6A^O z$u=hvw(uZdqEZ^zCTIuEBB~0>>_ppIP5*2glVCjfS2F6hX+anpu<)2%zF}d*LN&35 ziB+&@vhmZSYX~F}MW2VA^0B{=W^%W?${yY%ZF*S(gAbOHjFfIC4#whijdE8fREtb0 z%(J-@dZ5x8S*F({B-fs#4B$G8oHV7$Ky1zgI?|LvkrG9ws+r!Z-iKqr>K9*wKX1>) zKw)<8;ClUD^rlXf6e!yOJAJT4w6fD^*H8v+xfp1vDQSzUGERvYA=!iyRYCV`EkYF* zoaMOGHOMA8JnpWbuSpdBg{?8lA5zove!|Ar<^JIDaz*#^b{tZ2^ZhY#8xrIDbMX6% zwflLstKLr?R!-&PL%!8BZE#Y;&w4JEsRA3zfT?%w39UtqW?gQM$Yr(>WB`^%H!rU^ zMeE_4a>~H@td%}{-Ez7Mw zwcl?hJ~dy}-DFPf=4C;U;l1a`%3>&hQ8IP=!9VijimR?eH z0N4VeR398urqW1WP7sl7S&)^lga<6$a)-H<&#?QG8<$(xhnKH!RC7iRiZhL_WMdmY zXgh=QR@~Pu3yS-@^P3Z8gG}m08+l~$5lXe|lGEMs-DbHl-+Uld zb=R2rmb2>LwDUl9|Ioacx>eq@G4O37E4Krzthr#es;&iS%=<_5!A8kS*tgD2uU|KW zElytwIX{`OV3aMZU$SDAwP-{=6B;UW!rzO{;r%e^`u``cnnR*tpJ&ObI!QAYF-dYhMhjYnO5Id=l# z(yq%nlYEbh*)@EH{V(04$Ygbw>df(sA?%-SC%`v&CqoN(*WH{4Hr<4+L%DQscTT~R z$cmIdP?ifbOo}zn(-kN!>DZRmR!nh>C=*t~jg$v>dxHx<09ehZ?*CIa^1qo7|4+vs zEcE|x%z=0eaa&dQO3i42wH`Aeg*+lXdalRA82HAY&WGL^!`0e9HN78iF_V#-lnEMy zgbm5n+5FP7<&4Dghaz`*kaGco^s{r`c-%leUIe0!)va_;=VYKIhI3hT2Il9*0--mlrv_*((%56U@jIBpyjK>4XX-#5qLcmY554^MP( zV}Rpy#>pj8!1i3XZx@X{7p$@hQ_Au^$?Ux5GS}D5(Rw?+0 z!3v1hX!KED^rCt_m*<7VsDH*otYlo1O4p?Jdv53)_ zK9KM4)I$o!F^s{MMZT`+wEk&Uj$F7>{CMzwx2i`YD1 z_ra5%aD>D4zpZkUx3G@r$kC#Jz`^AX?b+jmYGb~_EMHl09b4Y9~m&B&Nc{@T@y4zMDiq; z<)E~T9Hgtltewh#*=Qtz5hGyHNA>EzGEaKmASx@l*(-*Q^Bo!l_*q{Ja2U%tsJ zs0btN;hODn8||N}zZxJrr;Ro4unv^BI514=k8AySuC>duK4V~3 z2|xzCt9KA7z7!yldrG0zIrKHE_ z>)_|h^dTb0_xpj4jqkhl?e^?|B#mAAddHTRvUzOGWExH8fdoOiFx~Arn><@P{ve*k z3I28<`yUWMAdl6fS>G(umJiUFxK}~ti_nH#ja?jiI*x1oem%}jip{8yr@X;T+Oc**{ zm#PD(;xtB<3^78+GWzpB@!*iizixy5vKU>%TY68^E4l z!)m#=&KG@~Bq(?4OPb1nn~Lg(0`BD z%c)P*#+73K0uRY?^ir_vV%rxwVVreWFgLn79#9tx6iPi0TWd#p zD<19=8kEj1vtncZvmZ*eKqmv9YhPwy8DJXRhkCV*E)kGN8x<_I+JrH$JhgZ{OC+z> z9NoQhL4+4XAXYrNCDo`f=oYRc2^TrFHRz7okSo@w$7blkc!3%thd+XP*`AZ>$j8aE z#k1%O8$+}}-Gl+&B=)QUB@TV%0x$e9V^nl7?mSll$9w^F`~Jb`a`=nQw2w>nQ>bD&ZUU?u}qEDNnR zzuaL7T{Zm3>)(k0SZMA8gMADx?pHPunl^fpF^uCUN3me_w`OKpy@GMRVB!My7TiZT@ppn)W% ztRCsf+8iwv=hiTE^@W+~_Xgom?j4NY&&Xt?Zh0=4yy%^_Q~QOCFKUH_M14F}bzD>J z&_8|J3CPym5um5En4>W*3YCzEZlh4Dly=cpr#nIV^v8dy|Y=v~y%bX&- zXn8r!pj0@3Rnj?i`;%k+@ldvUZaG^m=OM*)b~?eVp_^`rdOk&~ zSl~ue>MoKrCe6ZrEtV$klQlupzr{PU&a&3;rIcyFo3!BOX=6