From 3eff5c0b4053fd6c4e224f211b1c312b73723588 Mon Sep 17 00:00:00 2001 From: ququzone Date: Mon, 21 Oct 2024 16:38:14 +0800 Subject: [PATCH 1/3] add FixedRewardPool --- contracts/VestingMaster.sol | 1 + contracts/Voter.sol | 1 + contracts/art/BokkyPooBahsDateTimeLibrary.sol | 225 ++++++++++-------- contracts/art/PerlinNoise.sol | 3 +- contracts/art/Trig.sol | 30 +-- contracts/factories/FactoryRegistry.sol | 8 +- contracts/gauges/FixedRewardPool.sol | 146 ++++++++++++ contracts/test/TestDeviceNFT.sol | 3 +- 8 files changed, 300 insertions(+), 117 deletions(-) create mode 100644 contracts/gauges/FixedRewardPool.sol diff --git a/contracts/VestingMaster.sol b/contracts/VestingMaster.sol index b855c9c..b16c7c9 100644 --- a/contracts/VestingMaster.sol +++ b/contracts/VestingMaster.sol @@ -12,6 +12,7 @@ contract VestingMaster is ReentrancyGuard, Ownable { uint256 locked; uint256 timestamp; } + event Lock(address indexed user, uint256 amount); event Claim(address indexed user, uint256 amount); event LockerAdded(address indexed locker); diff --git a/contracts/Voter.sol b/contracts/Voter.sol index 6dfc8f0..8ab6461 100644 --- a/contracts/Voter.sol +++ b/contracts/Voter.sol @@ -23,6 +23,7 @@ import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol contract Voter is IVoter, ERC2771Context, ReentrancyGuard { using SafeERC20 for IERC20; /// @inheritdoc IVoter + address public immutable forwarder; /// @inheritdoc IVoter address public strategyManager; diff --git a/contracts/art/BokkyPooBahsDateTimeLibrary.sol b/contracts/art/BokkyPooBahsDateTimeLibrary.sol index b45076c..2552d08 100644 --- a/contracts/art/BokkyPooBahsDateTimeLibrary.sol +++ b/contracts/art/BokkyPooBahsDateTimeLibrary.sol @@ -27,18 +27,18 @@ pragma solidity 0.8.19; // ---------------------------------------------------------------------------- library BokkyPooBahsDateTimeLibrary { - uint constant SECONDS_PER_DAY = 24 * 60 * 60; - uint constant SECONDS_PER_HOUR = 60 * 60; - uint constant SECONDS_PER_MINUTE = 60; - int constant OFFSET19700101 = 2440588; - - uint constant DOW_MON = 1; - uint constant DOW_TUE = 2; - uint constant DOW_WED = 3; - uint constant DOW_THU = 4; - uint constant DOW_FRI = 5; - uint constant DOW_SAT = 6; - uint constant DOW_SUN = 7; + uint256 constant SECONDS_PER_DAY = 24 * 60 * 60; + uint256 constant SECONDS_PER_HOUR = 60 * 60; + uint256 constant SECONDS_PER_MINUTE = 60; + int256 constant OFFSET19700101 = 2440588; + + uint256 constant DOW_MON = 1; + uint256 constant DOW_TUE = 2; + uint256 constant DOW_WED = 3; + uint256 constant DOW_THU = 4; + uint256 constant DOW_FRI = 5; + uint256 constant DOW_SAT = 6; + uint256 constant DOW_SUN = 7; // ------------------------------------------------------------------------ // Calculate the number of days from 1970/01/01 to year/month/day using @@ -53,13 +53,13 @@ library BokkyPooBahsDateTimeLibrary { // - 3 * ((year + 4900 + (month - 14) / 12) / 100) / 4 // - offset // ------------------------------------------------------------------------ - function _daysFromDate(uint year, uint month, uint day) internal pure returns (uint _days) { + function _daysFromDate(uint256 year, uint256 month, uint256 day) internal pure returns (uint256 _days) { require(year >= 1970); - int _year = int(year); - int _month = int(month); - int _day = int(day); + int256 _year = int256(year); + int256 _month = int256(month); + int256 _day = int256(day); - int __days = _day - + int256 __days = _day - 32075 + (1461 * (_year + 4800 + (_month - 14) / 12)) / 4 + @@ -69,7 +69,7 @@ library BokkyPooBahsDateTimeLibrary { 4 - OFFSET19700101; - _days = uint(__days); + _days = uint256(__days); } // ------------------------------------------------------------------------ @@ -89,36 +89,37 @@ library BokkyPooBahsDateTimeLibrary { // month = month + 2 - 12 * L // year = 100 * (N - 49) + year + L // ------------------------------------------------------------------------ - function _daysToDate(uint _days) internal pure returns (uint year, uint month, uint day) { - int __days = int(_days); + function _daysToDate(uint256 _days) internal pure returns (uint256 year, uint256 month, uint256 day) { + int256 __days = int256(_days); - int L = __days + 68569 + OFFSET19700101; - int N = (4 * L) / 146097; + int256 L = __days + 68569 + OFFSET19700101; + int256 N = (4 * L) / 146097; L = L - (146097 * N + 3) / 4; - int _year = (4000 * (L + 1)) / 1461001; + int256 _year = (4000 * (L + 1)) / 1461001; L = L - (1461 * _year) / 4 + 31; - int _month = (80 * L) / 2447; - int _day = L - (2447 * _month) / 80; + int256 _month = (80 * L) / 2447; + int256 _day = L - (2447 * _month) / 80; L = _month / 11; _month = _month + 2 - 12 * L; _year = 100 * (N - 49) + _year + L; - year = uint(_year); - month = uint(_month); - day = uint(_day); + year = uint256(_year); + month = uint256(_month); + day = uint256(_day); } - function timestampFromDate(uint year, uint month, uint day) internal pure returns (uint timestamp) { + function timestampFromDate(uint256 year, uint256 month, uint256 day) internal pure returns (uint256 timestamp) { timestamp = _daysFromDate(year, month, day) * SECONDS_PER_DAY; } + function timestampFromDateTime( - uint year, - uint month, - uint day, - uint hour, - uint minute, - uint second - ) internal pure returns (uint timestamp) { + uint256 year, + uint256 month, + uint256 day, + uint256 hour, + uint256 minute, + uint256 second + ) internal pure returns (uint256 timestamp) { timestamp = _daysFromDate(year, month, day) * SECONDS_PER_DAY + @@ -128,35 +129,38 @@ library BokkyPooBahsDateTimeLibrary { SECONDS_PER_MINUTE + second; } - function timestampToDate(uint timestamp) internal pure returns (uint year, uint month, uint day) { + + function timestampToDate(uint256 timestamp) internal pure returns (uint256 year, uint256 month, uint256 day) { (year, month, day) = _daysToDate(timestamp / SECONDS_PER_DAY); } + function timestampToDateTime( - uint timestamp - ) internal pure returns (uint year, uint month, uint day, uint hour, uint minute, uint second) { + uint256 timestamp + ) internal pure returns (uint256 year, uint256 month, uint256 day, uint256 hour, uint256 minute, uint256 second) { (year, month, day) = _daysToDate(timestamp / SECONDS_PER_DAY); - uint secs = timestamp % SECONDS_PER_DAY; + uint256 secs = timestamp % SECONDS_PER_DAY; hour = secs / SECONDS_PER_HOUR; secs = secs % SECONDS_PER_HOUR; minute = secs / SECONDS_PER_MINUTE; second = secs % SECONDS_PER_MINUTE; } - function isValidDate(uint year, uint month, uint day) internal pure returns (bool valid) { + function isValidDate(uint256 year, uint256 month, uint256 day) internal pure returns (bool valid) { if (year >= 1970 && month > 0 && month <= 12) { - uint daysInMonth = _getDaysInMonth(year, month); + uint256 daysInMonth = _getDaysInMonth(year, month); if (day > 0 && day <= daysInMonth) { valid = true; } } } + function isValidDateTime( - uint year, - uint month, - uint day, - uint hour, - uint minute, - uint second + uint256 year, + uint256 month, + uint256 day, + uint256 hour, + uint256 minute, + uint256 second ) internal pure returns (bool valid) { if (isValidDate(year, month, day)) { if (hour < 24 && minute < 60 && second < 60) { @@ -164,24 +168,30 @@ library BokkyPooBahsDateTimeLibrary { } } } - function isLeapYear(uint timestamp) internal pure returns (bool leapYear) { - (uint year, , ) = _daysToDate(timestamp / SECONDS_PER_DAY); + + function isLeapYear(uint256 timestamp) internal pure returns (bool leapYear) { + (uint256 year, , ) = _daysToDate(timestamp / SECONDS_PER_DAY); leapYear = _isLeapYear(year); } - function _isLeapYear(uint year) internal pure returns (bool leapYear) { + + function _isLeapYear(uint256 year) internal pure returns (bool leapYear) { leapYear = ((year % 4 == 0) && (year % 100 != 0)) || (year % 400 == 0); } - function isWeekDay(uint timestamp) internal pure returns (bool weekDay) { + + function isWeekDay(uint256 timestamp) internal pure returns (bool weekDay) { weekDay = getDayOfWeek(timestamp) <= DOW_FRI; } - function isWeekEnd(uint timestamp) internal pure returns (bool weekEnd) { + + function isWeekEnd(uint256 timestamp) internal pure returns (bool weekEnd) { weekEnd = getDayOfWeek(timestamp) >= DOW_SAT; } - function getDaysInMonth(uint timestamp) internal pure returns (uint daysInMonth) { - (uint year, uint month, ) = _daysToDate(timestamp / SECONDS_PER_DAY); + + function getDaysInMonth(uint256 timestamp) internal pure returns (uint256 daysInMonth) { + (uint256 year, uint256 month, ) = _daysToDate(timestamp / SECONDS_PER_DAY); daysInMonth = _getDaysInMonth(year, month); } - function _getDaysInMonth(uint year, uint month) internal pure returns (uint daysInMonth) { + + function _getDaysInMonth(uint256 year, uint256 month) internal pure returns (uint256 daysInMonth) { if (month == 1 || month == 3 || month == 5 || month == 7 || month == 8 || month == 10 || month == 12) { daysInMonth = 31; } else if (month != 2) { @@ -191,135 +201,156 @@ library BokkyPooBahsDateTimeLibrary { } } // 1 = Monday, 7 = Sunday - function getDayOfWeek(uint timestamp) internal pure returns (uint dayOfWeek) { - uint _days = timestamp / SECONDS_PER_DAY; + + function getDayOfWeek(uint256 timestamp) internal pure returns (uint256 dayOfWeek) { + uint256 _days = timestamp / SECONDS_PER_DAY; dayOfWeek = ((_days + 3) % 7) + 1; } - function getYear(uint timestamp) internal pure returns (uint year) { + function getYear(uint256 timestamp) internal pure returns (uint256 year) { (year, , ) = _daysToDate(timestamp / SECONDS_PER_DAY); } - function getMonth(uint timestamp) internal pure returns (uint month) { + + function getMonth(uint256 timestamp) internal pure returns (uint256 month) { (, month, ) = _daysToDate(timestamp / SECONDS_PER_DAY); } - function getDay(uint timestamp) internal pure returns (uint day) { + + function getDay(uint256 timestamp) internal pure returns (uint256 day) { (, , day) = _daysToDate(timestamp / SECONDS_PER_DAY); } - function getHour(uint timestamp) internal pure returns (uint hour) { - uint secs = timestamp % SECONDS_PER_DAY; + + function getHour(uint256 timestamp) internal pure returns (uint256 hour) { + uint256 secs = timestamp % SECONDS_PER_DAY; hour = secs / SECONDS_PER_HOUR; } - function getMinute(uint timestamp) internal pure returns (uint minute) { - uint secs = timestamp % SECONDS_PER_HOUR; + + function getMinute(uint256 timestamp) internal pure returns (uint256 minute) { + uint256 secs = timestamp % SECONDS_PER_HOUR; minute = secs / SECONDS_PER_MINUTE; } - function getSecond(uint timestamp) internal pure returns (uint second) { + + function getSecond(uint256 timestamp) internal pure returns (uint256 second) { second = timestamp % SECONDS_PER_MINUTE; } - function addYears(uint timestamp, uint _years) internal pure returns (uint newTimestamp) { - (uint year, uint month, uint day) = _daysToDate(timestamp / SECONDS_PER_DAY); + function addYears(uint256 timestamp, uint256 _years) internal pure returns (uint256 newTimestamp) { + (uint256 year, uint256 month, uint256 day) = _daysToDate(timestamp / SECONDS_PER_DAY); year += _years; - uint daysInMonth = _getDaysInMonth(year, month); + uint256 daysInMonth = _getDaysInMonth(year, month); if (day > daysInMonth) { day = daysInMonth; } newTimestamp = _daysFromDate(year, month, day) * SECONDS_PER_DAY + (timestamp % SECONDS_PER_DAY); require(newTimestamp >= timestamp); } - function addMonths(uint timestamp, uint _months) internal pure returns (uint newTimestamp) { - (uint year, uint month, uint day) = _daysToDate(timestamp / SECONDS_PER_DAY); + + function addMonths(uint256 timestamp, uint256 _months) internal pure returns (uint256 newTimestamp) { + (uint256 year, uint256 month, uint256 day) = _daysToDate(timestamp / SECONDS_PER_DAY); month += _months; year += (month - 1) / 12; month = ((month - 1) % 12) + 1; - uint daysInMonth = _getDaysInMonth(year, month); + uint256 daysInMonth = _getDaysInMonth(year, month); if (day > daysInMonth) { day = daysInMonth; } newTimestamp = _daysFromDate(year, month, day) * SECONDS_PER_DAY + (timestamp % SECONDS_PER_DAY); require(newTimestamp >= timestamp); } - function addDays(uint timestamp, uint _days) internal pure returns (uint newTimestamp) { + + function addDays(uint256 timestamp, uint256 _days) internal pure returns (uint256 newTimestamp) { newTimestamp = timestamp + _days * SECONDS_PER_DAY; require(newTimestamp >= timestamp); } - function addHours(uint timestamp, uint _hours) internal pure returns (uint newTimestamp) { + + function addHours(uint256 timestamp, uint256 _hours) internal pure returns (uint256 newTimestamp) { newTimestamp = timestamp + _hours * SECONDS_PER_HOUR; require(newTimestamp >= timestamp); } - function addMinutes(uint timestamp, uint _minutes) internal pure returns (uint newTimestamp) { + + function addMinutes(uint256 timestamp, uint256 _minutes) internal pure returns (uint256 newTimestamp) { newTimestamp = timestamp + _minutes * SECONDS_PER_MINUTE; require(newTimestamp >= timestamp); } - function addSeconds(uint timestamp, uint _seconds) internal pure returns (uint newTimestamp) { + + function addSeconds(uint256 timestamp, uint256 _seconds) internal pure returns (uint256 newTimestamp) { newTimestamp = timestamp + _seconds; require(newTimestamp >= timestamp); } - function subYears(uint timestamp, uint _years) internal pure returns (uint newTimestamp) { - (uint year, uint month, uint day) = _daysToDate(timestamp / SECONDS_PER_DAY); + function subYears(uint256 timestamp, uint256 _years) internal pure returns (uint256 newTimestamp) { + (uint256 year, uint256 month, uint256 day) = _daysToDate(timestamp / SECONDS_PER_DAY); year -= _years; - uint daysInMonth = _getDaysInMonth(year, month); + uint256 daysInMonth = _getDaysInMonth(year, month); if (day > daysInMonth) { day = daysInMonth; } newTimestamp = _daysFromDate(year, month, day) * SECONDS_PER_DAY + (timestamp % SECONDS_PER_DAY); require(newTimestamp <= timestamp); } - function subMonths(uint timestamp, uint _months) internal pure returns (uint newTimestamp) { - (uint year, uint month, uint day) = _daysToDate(timestamp / SECONDS_PER_DAY); - uint yearMonth = year * 12 + (month - 1) - _months; + + function subMonths(uint256 timestamp, uint256 _months) internal pure returns (uint256 newTimestamp) { + (uint256 year, uint256 month, uint256 day) = _daysToDate(timestamp / SECONDS_PER_DAY); + uint256 yearMonth = year * 12 + (month - 1) - _months; year = yearMonth / 12; month = (yearMonth % 12) + 1; - uint daysInMonth = _getDaysInMonth(year, month); + uint256 daysInMonth = _getDaysInMonth(year, month); if (day > daysInMonth) { day = daysInMonth; } newTimestamp = _daysFromDate(year, month, day) * SECONDS_PER_DAY + (timestamp % SECONDS_PER_DAY); require(newTimestamp <= timestamp); } - function subDays(uint timestamp, uint _days) internal pure returns (uint newTimestamp) { + + function subDays(uint256 timestamp, uint256 _days) internal pure returns (uint256 newTimestamp) { newTimestamp = timestamp - _days * SECONDS_PER_DAY; require(newTimestamp <= timestamp); } - function subHours(uint timestamp, uint _hours) internal pure returns (uint newTimestamp) { + + function subHours(uint256 timestamp, uint256 _hours) internal pure returns (uint256 newTimestamp) { newTimestamp = timestamp - _hours * SECONDS_PER_HOUR; require(newTimestamp <= timestamp); } - function subMinutes(uint timestamp, uint _minutes) internal pure returns (uint newTimestamp) { + + function subMinutes(uint256 timestamp, uint256 _minutes) internal pure returns (uint256 newTimestamp) { newTimestamp = timestamp - _minutes * SECONDS_PER_MINUTE; require(newTimestamp <= timestamp); } - function subSeconds(uint timestamp, uint _seconds) internal pure returns (uint newTimestamp) { + + function subSeconds(uint256 timestamp, uint256 _seconds) internal pure returns (uint256 newTimestamp) { newTimestamp = timestamp - _seconds; require(newTimestamp <= timestamp); } - function diffYears(uint fromTimestamp, uint toTimestamp) internal pure returns (uint _years) { + function diffYears(uint256 fromTimestamp, uint256 toTimestamp) internal pure returns (uint256 _years) { require(fromTimestamp <= toTimestamp); - (uint fromYear, , ) = _daysToDate(fromTimestamp / SECONDS_PER_DAY); - (uint toYear, , ) = _daysToDate(toTimestamp / SECONDS_PER_DAY); + (uint256 fromYear, , ) = _daysToDate(fromTimestamp / SECONDS_PER_DAY); + (uint256 toYear, , ) = _daysToDate(toTimestamp / SECONDS_PER_DAY); _years = toYear - fromYear; } - function diffMonths(uint fromTimestamp, uint toTimestamp) internal pure returns (uint _months) { + + function diffMonths(uint256 fromTimestamp, uint256 toTimestamp) internal pure returns (uint256 _months) { require(fromTimestamp <= toTimestamp); - (uint fromYear, uint fromMonth, ) = _daysToDate(fromTimestamp / SECONDS_PER_DAY); - (uint toYear, uint toMonth, ) = _daysToDate(toTimestamp / SECONDS_PER_DAY); + (uint256 fromYear, uint256 fromMonth, ) = _daysToDate(fromTimestamp / SECONDS_PER_DAY); + (uint256 toYear, uint256 toMonth, ) = _daysToDate(toTimestamp / SECONDS_PER_DAY); _months = toYear * 12 + toMonth - fromYear * 12 - fromMonth; } - function diffDays(uint fromTimestamp, uint toTimestamp) internal pure returns (uint _days) { + + function diffDays(uint256 fromTimestamp, uint256 toTimestamp) internal pure returns (uint256 _days) { require(fromTimestamp <= toTimestamp); _days = (toTimestamp - fromTimestamp) / SECONDS_PER_DAY; } - function diffHours(uint fromTimestamp, uint toTimestamp) internal pure returns (uint _hours) { + + function diffHours(uint256 fromTimestamp, uint256 toTimestamp) internal pure returns (uint256 _hours) { require(fromTimestamp <= toTimestamp); _hours = (toTimestamp - fromTimestamp) / SECONDS_PER_HOUR; } - function diffMinutes(uint fromTimestamp, uint toTimestamp) internal pure returns (uint _minutes) { + + function diffMinutes(uint256 fromTimestamp, uint256 toTimestamp) internal pure returns (uint256 _minutes) { require(fromTimestamp <= toTimestamp); _minutes = (toTimestamp - fromTimestamp) / SECONDS_PER_MINUTE; } - function diffSeconds(uint fromTimestamp, uint toTimestamp) internal pure returns (uint _seconds) { + + function diffSeconds(uint256 fromTimestamp, uint256 toTimestamp) internal pure returns (uint256 _seconds) { require(fromTimestamp <= toTimestamp); _seconds = toTimestamp - fromTimestamp; } diff --git a/contracts/art/PerlinNoise.sol b/contracts/art/PerlinNoise.sol index fbf09b0..06ec856 100644 --- a/contracts/art/PerlinNoise.sol +++ b/contracts/art/PerlinNoise.sol @@ -7,7 +7,6 @@ pragma solidity 0.8.19; * @notice An implementation of Perlin Noise that uses 16 bit fixed point arithmetic. * @notice Updated solidity version from 0.5.0 to 0.8.12. */ - library PerlinNoise { /** * @notice Computes the noise value for a 2D point. @@ -22,7 +21,7 @@ library PerlinNoise { function noise2d(int256 x, int256 y) public pure returns (int256) { int256 temp = ptable((x >> 16) & 0xff /* Unit square X */); - int256 a = ptable((temp >> 8) + ((y >> 16) & 0xff /* Unit square Y */)); + int256 a = ptable((temp >> 8) + ((y >> 16) & 0xff) /* Unit square Y */); int256 b = ptable((temp & 0xff) + ((y >> 16) & 0xff)); x &= 0xffff; // Square relative X diff --git a/contracts/art/Trig.sol b/contracts/art/Trig.sol index ffb06bb..c3ad3bf 100644 --- a/contracts/art/Trig.sol +++ b/contracts/art/Trig.sol @@ -3,8 +3,8 @@ pragma solidity 0.8.19; library Trig { /*--- -Trigonometry - Implementation 1 ----*/ + Trigonometry - Implementation 1 + ---*/ // Implementation 1 requires wrapping inputs between 2 * PI and 4 * PI to avoid precision errors, but it is more accurate than Implementation 2. @@ -56,7 +56,7 @@ Trigonometry - Implementation 1 uint8 constant entry_bytes = 4; // each entry in the lookup table is 4 bytes uint256 constant entry_mask = ((1 << (8 * entry_bytes)) - 1); // mask used to cast bytes32 -> lookup table entry bytes constant sin_table = - hex"00_00_00_00_00_c9_0f_88_01_92_1d_20_02_5b_26_d7_03_24_2a_bf_03_ed_26_e6_04_b6_19_5d_05_7f_00_35_06_47_d9_7c_07_10_a3_45_07_d9_5b_9e_08_a2_00_9a_09_6a_90_49_0a_33_08_bc_0a_fb_68_05_0b_c3_ac_35_0c_8b_d3_5e_0d_53_db_92_0e_1b_c2_e4_0e_e3_87_66_0f_ab_27_2b_10_72_a0_48_11_39_f0_cf_12_01_16_d5_12_c8_10_6e_13_8e_db_b1_14_55_76_b1_15_1b_df_85_15_e2_14_44_16_a8_13_05_17_6d_d9_de_18_33_66_e8_18_f8_b8_3c_19_bd_cb_f3_1a_82_a0_25_1b_47_32_ef_1c_0b_82_6a_1c_cf_8c_b3_1d_93_4f_e5_1e_56_ca_1e_1f_19_f9_7b_1f_dc_dc_1b_20_9f_70_1c_21_61_b3_9f_22_23_a4_c5_22_e5_41_af_23_a6_88_7e_24_67_77_57_25_28_0c_5d_25_e8_45_b6_26_a8_21_85_27_67_9d_f4_28_26_b9_28_28_e5_71_4a_29_a3_c4_85_2a_61_b1_01_2b_1f_34_eb_2b_dc_4e_6f_2c_98_fb_ba_2d_55_3a_fb_2e_11_0a_62_2e_cc_68_1e_2f_87_52_62_30_41_c7_60_30_fb_c5_4d_31_b5_4a_5d_32_6e_54_c7_33_26_e2_c2_33_de_f2_87_34_96_82_4f_35_4d_90_56_36_04_1a_d9_36_ba_20_13_37_6f_9e_46_38_24_93_b0_38_d8_fe_93_39_8c_dd_32_3a_40_2d_d1_3a_f2_ee_b7_3b_a5_1e_29_3c_56_ba_70_3d_07_c1_d5_3d_b8_32_a5_3e_68_0b_2c_3f_17_49_b7_3f_c5_ec_97_40_73_f2_1d_41_21_58_9a_41_ce_1e_64_42_7a_41_d0_43_25_c1_35_43_d0_9a_ec_44_7a_cd_50_45_24_56_bc_45_cd_35_8f_46_75_68_27_47_1c_ec_e6_47_c3_c2_2e_48_69_e6_64_49_0f_57_ee_49_b4_15_33_4a_58_1c_9d_4a_fb_6c_97_4b_9e_03_8f_4c_3f_df_f3_4c_e1_00_34_4d_81_62_c3_4e_21_06_17_4e_bf_e8_a4_4f_5e_08_e2_4f_fb_65_4c_50_97_fc_5e_51_33_cc_94_51_ce_d4_6e_52_69_12_6e_53_02_85_17_53_9b_2a_ef_54_33_02_7d_54_ca_0a_4a_55_60_40_e2_55_f5_a4_d2_56_8a_34_a9_57_1d_ee_f9_57_b0_d2_55_58_42_dd_54_58_d4_0e_8c_59_64_64_97_59_f3_de_12_5a_82_79_99_5b_10_35_ce_5b_9d_11_53_5c_29_0a_cc_5c_b4_20_df_5d_3e_52_36_5d_c7_9d_7b_5e_50_01_5d_5e_d7_7c_89_5f_5e_0d_b2_5f_e3_b3_8d_60_68_6c_ce_60_ec_38_2f_61_6f_14_6b_61_f1_00_3e_62_71_fa_68_62_f2_01_ac_63_71_14_cc_63_ef_32_8f_64_6c_59_bf_64_e8_89_25_65_63_bf_91_65_dd_fb_d2_66_57_3c_bb_66_cf_81_1f_67_46_c7_d7_67_bd_0f_bc_68_32_57_aa_68_a6_9e_80_69_19_e3_1f_69_8c_24_6b_69_fd_61_4a_6a_6d_98_a3_6a_dc_c9_64_6b_4a_f2_78_6b_b8_12_d0_6c_24_29_5f_6c_8f_35_1b_6c_f9_34_fb_6d_62_27_f9_6d_ca_0d_14_6e_30_e3_49_6e_96_a9_9c_6e_fb_5f_11_6f_5f_02_b1_6f_c1_93_84_70_23_10_99_70_83_78_fe_70_e2_cb_c5_71_41_08_04_71_9e_2c_d1_71_fa_39_48_72_55_2c_84_72_af_05_a6_73_07_c3_cf_73_5f_66_25_73_b5_eb_d0_74_0b_53_fa_74_5f_9d_d0_74_b2_c8_83_75_04_d3_44_75_55_bd_4b_75_a5_85_ce_75_f4_2c_0a_76_41_af_3c_76_8e_0e_a5_76_d9_49_88_77_23_5f_2c_77_6c_4e_da_77_b4_17_df_77_fa_b9_88_78_40_33_28_78_84_84_13_78_c7_ab_a1_79_09_a9_2c_79_4a_7c_11_79_8a_23_b0_79_c8_9f_6d_7a_05_ee_ac_7a_42_10_d8_7a_7d_05_5a_7a_b6_cb_a3_7a_ef_63_23_7b_26_cb_4e_7b_5d_03_9d_7b_92_0b_88_7b_c5_e2_8f_7b_f8_88_2f_7c_29_fb_ed_7c_5a_3d_4f_7c_89_4b_dd_7c_b7_27_23_7c_e3_ce_b1_7d_0f_42_17_7d_39_80_eb_7d_62_8a_c5_7d_8a_5f_3f_7d_b0_fd_f7_7d_d6_66_8e_7d_fa_98_a7_7e_1d_93_e9_7e_3f_57_fe_7e_5f_e4_92_7e_7f_39_56_7e_9d_55_fb_7e_ba_3a_38_7e_d5_e5_c5_7e_f0_58_5f_7f_09_91_c3_7f_21_91_b3_7f_38_57_f5_7f_4d_e4_50_7f_62_36_8e_7f_75_4e_7f_7f_87_2b_f2_7f_97_ce_bc_7f_a7_36_b3_7f_b5_63_b2_7f_c2_55_95_7f_ce_0c_3d_7f_d8_87_8d_7f_e1_c7_6a_7f_e9_cb_bf_7f_f0_94_77_7f_f6_21_81_7f_fa_72_d0_7f_fd_88_59_7f_ff_62_15_7f_ff_ff_ff"; + hex"0000000000c90f8801921d20025b26d703242abf03ed26e604b6195d057f00350647d97c0710a34507d95b9e08a2009a096a90490a3308bc0afb68050bc3ac350c8bd35e0d53db920e1bc2e40ee387660fab272b1072a0481139f0cf120116d512c8106e138edbb1145576b1151bdf8515e2144416a81305176dd9de183366e818f8b83c19bdcbf31a82a0251b4732ef1c0b826a1ccf8cb31d934fe51e56ca1e1f19f97b1fdcdc1b209f701c2161b39f2223a4c522e541af23a6887e2467775725280c5d25e845b626a8218527679df42826b92828e5714a29a3c4852a61b1012b1f34eb2bdc4e6f2c98fbba2d553afb2e110a622ecc681e2f8752623041c76030fbc54d31b54a5d326e54c73326e2c233def2873496824f354d905636041ad936ba2013376f9e46382493b038d8fe93398cdd323a402dd13af2eeb73ba51e293c56ba703d07c1d53db832a53e680b2c3f1749b73fc5ec974073f21d4121589a41ce1e64427a41d04325c13543d09aec447acd50452456bc45cd358f46756827471cece647c3c22e4869e664490f57ee49b415334a581c9d4afb6c974b9e038f4c3fdff34ce100344d8162c34e2106174ebfe8a44f5e08e24ffb654c5097fc5e5133cc9451ced46e5269126e53028517539b2aef5433027d54ca0a4a556040e255f5a4d2568a34a9571deef957b0d2555842dd5458d40e8c5964649759f3de125a8279995b1035ce5b9d11535c290acc5cb420df5d3e52365dc79d7b5e50015d5ed77c895f5e0db25fe3b38d60686cce60ec382f616f146b61f1003e6271fa6862f201ac637114cc63ef328f646c59bf64e889256563bf9165ddfbd266573cbb66cf811f6746c7d767bd0fbc683257aa68a69e806919e31f698c246b69fd614a6a6d98a36adcc9646b4af2786bb812d06c24295f6c8f351b6cf934fb6d6227f96dca0d146e30e3496e96a99c6efb5f116f5f02b16fc1938470231099708378fe70e2cbc571410804719e2cd171fa394872552c8472af05a67307c3cf735f662573b5ebd0740b53fa745f9dd074b2c8837504d3447555bd4b75a585ce75f42c0a7641af3c768e0ea576d9498877235f2c776c4eda77b417df77fab988784033287884841378c7aba17909a92c794a7c11798a23b079c89f6d7a05eeac7a4210d87a7d055a7ab6cba37aef63237b26cb4e7b5d039d7b920b887bc5e28f7bf8882f7c29fbed7c5a3d4f7c894bdd7cb727237ce3ceb17d0f42177d3980eb7d628ac57d8a5f3f7db0fdf77dd6668e7dfa98a77e1d93e97e3f57fe7e5fe4927e7f39567e9d55fb7eba3a387ed5e5c57ef0585f7f0991c37f2191b37f3857f57f4de4507f62368e7f754e7f7f872bf27f97cebc7fa736b37fb563b27fc255957fce0c3d7fd8878d7fe1c76a7fe9cbbf7ff094777ff621817ffa72d07ffd88597fff62157fffffff"; /** * @notice Return the sine of a value, specified in radians scaled by 1e18 @@ -142,15 +142,15 @@ Trigonometry - Implementation 1 } /*--- -Trigonometry - Implementation 2 ----*/ + Trigonometry - Implementation 2 + ---*/ // Implementation 2 is less accurate than Implementation 1, but it allows for unbounded input in degrees. Output should be downscaled by 1e6. Sintable array loaded twice to avoid stack too deep errors. //Computes sine using degrees unit input. - function dsin(int degrees) external pure returns (int) { - int[360] memory sintable = [ - int(0), + function dsin(int256 degrees) external pure returns (int256) { + int256[360] memory sintable = [ + int256(0), 17452, 34899, 52336, @@ -512,16 +512,16 @@ Trigonometry - Implementation 2 -17452 ]; if (degrees > -1) { - return sintable[uint(degrees) % 360]; + return sintable[uint256(degrees) % 360]; } else { - return sintable[uint(degrees * -1) % 360] * -1; + return sintable[uint256(degrees * -1) % 360] * -1; } } //Computes cosine with degrees unit input. - function dcos(int degrees) external pure returns (int) { - int[360] memory sintable = [ - int(0), + function dcos(int256 degrees) external pure returns (int256) { + int256[360] memory sintable = [ + int256(0), 17452, 34899, 52336, @@ -883,9 +883,9 @@ Trigonometry - Implementation 2 -17452 ]; if ((degrees + 90) > -1) { - return sintable[uint(degrees + 90) % 360]; + return sintable[uint256(degrees + 90) % 360]; } else { - return sintable[uint((degrees + 90) * -1) % 360] * -1; + return sintable[uint256((degrees + 90) * -1) % 360] * -1; } } } diff --git a/contracts/factories/FactoryRegistry.sol b/contracts/factories/FactoryRegistry.sol index 71e738a..6d14767 100644 --- a/contracts/factories/FactoryRegistry.sol +++ b/contracts/factories/FactoryRegistry.sol @@ -26,6 +26,7 @@ contract FactoryRegistry is IFactoryRegistry, Ownable { address gaugeFactory; } /// @dev the factories linked to the poolFactory + mapping(address => FactoriesToPoolFactory) private _factoriesToPoolsFactory; constructor(address _fallbackPoolFactory, address _fallbackIncentiveFactory, address _fallbackGaugeFactory) { @@ -36,7 +37,9 @@ contract FactoryRegistry is IFactoryRegistry, Ownable { /// @inheritdoc IFactoryRegistry function approve(address poolFactory, address incentiveFactory, address gaugeFactory) public onlyOwner { - if (poolFactory == address(0) || incentiveFactory == address(0) || gaugeFactory == address(0)) revert ZeroAddress(); + if (poolFactory == address(0) || incentiveFactory == address(0) || gaugeFactory == address(0)) { + revert ZeroAddress(); + } if (_poolFactories.contains(poolFactory)) revert PathAlreadyApproved(); FactoriesToPoolFactory memory usedFactories = _factoriesToPoolsFactory[poolFactory]; @@ -48,8 +51,9 @@ contract FactoryRegistry is IFactoryRegistry, Ownable { } else { // If the poolFactory *has* been approved before, can only approve the same used gauge/votingRewards factory to // to maintain state within Voter - if (incentiveFactory != usedFactories.incentiveFactory || gaugeFactory != usedFactories.gaugeFactory) + if (incentiveFactory != usedFactories.incentiveFactory || gaugeFactory != usedFactories.gaugeFactory) { revert InvalidFactoriesToPoolFactory(); + } } _poolFactories.add(poolFactory); diff --git a/contracts/gauges/FixedRewardPool.sol b/contracts/gauges/FixedRewardPool.sol new file mode 100644 index 0000000..71a44cc --- /dev/null +++ b/contracts/gauges/FixedRewardPool.sol @@ -0,0 +1,146 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol"; +import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; +import {ReentrancyGuardUpgradeable} from "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol"; + +import {IWeightedNFT} from "../interfaces/IWeightedNFT.sol"; + +contract FixedRewardPool is OwnableUpgradeable, ReentrancyGuardUpgradeable { + event Deposit(address indexed user, uint256 tokenId, uint256 weight); + event Withdraw(address indexed user, uint256 tokenId, uint256 weight); + event EmergencyWithdraw(address indexed user, uint256 tokenId, uint256 weight); + + struct UserInfo { + uint256 amount; // How many staked tokens the user has provided + uint256 rewardDebt; // Reward debt + } + + // The precision factor + uint256 immutable PRECISION_FACTOR = 1e12; + + // The weighted NFT contract + IWeightedNFT public weightNFT; + // Accrued token per share + uint256 public accTokenPerShare; + // The block number of the last pool update + uint256 public lastRewardBlock; + // Reward tokens created per block. + uint256 public rewardPerBlock; + // Total staked weight + uint256 public totalStakedWeight; + // Info of each user that stakes tokens (stakedToken) + mapping(address => UserInfo) public userInfo; + + // Mapping from tokenId to staker + mapping(uint256 => address) public tokenStaker; + // Mapping from tokenId to weight + mapping(uint256 => uint256) public tokenWeight; + + function initialize(address _nft, uint256 _startBlock, uint256 _rewardPerBlock) external initializer { + require(_startBlock >= block.number, "invalid start block"); + require(_rewardPerBlock > 0, "invalid reward per block"); + + __Ownable_init(); + __ReentrancyGuard_init(); + weightNFT = IWeightedNFT(_nft); + lastRewardBlock = _startBlock; + rewardPerBlock = _rewardPerBlock; + } + + function deposit(uint256 _tokenId) external nonReentrant { + UserInfo storage user = userInfo[msg.sender]; + _updatePool(); + + if (user.amount > 0) { + uint256 pending = (user.amount * accTokenPerShare) / PRECISION_FACTOR - user.rewardDebt; + if (pending > 0) { + (bool success, ) = msg.sender.call{value: pending}(""); + require(success, "Failed to send reward"); + } + } + + uint _amount = weightNFT.weight(_tokenId); + if (_amount > 0) { + user.amount = user.amount + _amount; + IERC721(weightNFT.nft()).safeTransferFrom(msg.sender, address(this), _tokenId); + tokenStaker[_tokenId] = msg.sender; + tokenWeight[_tokenId] = _amount; + totalStakedWeight = totalStakedWeight + _amount; + } + + user.rewardDebt = (user.amount * accTokenPerShare) / PRECISION_FACTOR; + + emit Deposit(msg.sender, _tokenId, _amount); + } + + function withdraw(uint256 _tokenId) external nonReentrant { + require(tokenStaker[_tokenId] == msg.sender, "Invalid staker"); + UserInfo storage user = userInfo[msg.sender]; + + _updatePool(); + + uint256 pending = (user.amount * accTokenPerShare) / PRECISION_FACTOR - user.rewardDebt; + + uint256 _amount = tokenWeight[_tokenId]; + user.amount = user.amount - _amount; + IERC721(weightNFT.nft()).safeTransferFrom(address(this), msg.sender, _tokenId); + tokenStaker[_tokenId] = address(0); + tokenWeight[_tokenId] = 0; + totalStakedWeight = totalStakedWeight - _amount; + + if (pending > 0) { + (bool success, ) = msg.sender.call{value: pending}(""); + require(success, "Failed to send reward"); + } + + user.rewardDebt = (user.amount * accTokenPerShare) / PRECISION_FACTOR; + + emit Withdraw(msg.sender, _tokenId, _amount); + } + + function emergencyWithdraw(uint256 _tokenId) external nonReentrant { + require(tokenStaker[_tokenId] == msg.sender, "Invalid staker"); + UserInfo storage user = userInfo[msg.sender]; + + uint256 _amount = tokenWeight[_tokenId]; + + user.amount = user.amount - _amount; + IERC721(weightNFT.nft()).safeTransferFrom(address(this), msg.sender, _tokenId); + tokenStaker[_tokenId] = address(0); + tokenWeight[_tokenId] = 0; + totalStakedWeight = totalStakedWeight - _amount; + + emit EmergencyWithdraw(msg.sender, _tokenId, user.amount); + } + + function pendingReward(address _user) external view returns (uint256) { + UserInfo storage user = userInfo[_user]; + uint256 _totalStakedWeight = totalStakedWeight; + if (block.number > lastRewardBlock && _totalStakedWeight != 0) { + uint256 rewards = (block.number - lastRewardBlock) * rewardPerBlock; + uint256 adjustedTokenPerShare = accTokenPerShare + (rewards * PRECISION_FACTOR) / _totalStakedWeight; + return (user.amount * adjustedTokenPerShare) / PRECISION_FACTOR - user.rewardDebt; + } else { + return (user.amount * accTokenPerShare) / PRECISION_FACTOR - user.rewardDebt; + } + } + + function _updatePool() internal { + if (block.number <= lastRewardBlock) { + return; + } + + if (totalStakedWeight == 0) { + lastRewardBlock = block.number; + return; + } + + uint256 rewards = (block.number - lastRewardBlock) * rewardPerBlock; + accTokenPerShare = accTokenPerShare + ((rewards * PRECISION_FACTOR) / totalStakedWeight); + lastRewardBlock = block.number; + } + + receive() external payable {} +} diff --git a/contracts/test/TestDeviceNFT.sol b/contracts/test/TestDeviceNFT.sol index 2c52c1b..e06d494 100644 --- a/contracts/test/TestDeviceNFT.sol +++ b/contracts/test/TestDeviceNFT.sol @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import {ERC721} from "@openzeppelin/contracts/token/ERC721/ERC721.sol"; @@ -27,7 +28,7 @@ contract TestDeviceNFT is IWeightedNFT, ERC721 { weightOf[_tokenId] = _weight; } - function mint(address to, uint tokenId) external { + function mint(address to, uint256 tokenId) external { _mint(to, tokenId); weightOf[tokenId] = 1 ether; } From ad78f9023f5cb84ef7dc5d42a7ae0faf3da8e13c Mon Sep 17 00:00:00 2001 From: ququzone Date: Mon, 21 Oct 2024 18:34:29 +0800 Subject: [PATCH 2/3] add poke and claim rewards --- contracts/gauges/FixedRewardPool.sol | 70 +++++++++++++++++++++++----- 1 file changed, 58 insertions(+), 12 deletions(-) diff --git a/contracts/gauges/FixedRewardPool.sol b/contracts/gauges/FixedRewardPool.sol index 71a44cc..b7c0078 100644 --- a/contracts/gauges/FixedRewardPool.sol +++ b/contracts/gauges/FixedRewardPool.sol @@ -2,15 +2,18 @@ pragma solidity ^0.8.0; import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol"; +import {ERC721Holder} from "@openzeppelin/contracts/token/ERC721/utils/ERC721Holder.sol"; import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; import {ReentrancyGuardUpgradeable} from "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol"; import {IWeightedNFT} from "../interfaces/IWeightedNFT.sol"; -contract FixedRewardPool is OwnableUpgradeable, ReentrancyGuardUpgradeable { - event Deposit(address indexed user, uint256 tokenId, uint256 weight); - event Withdraw(address indexed user, uint256 tokenId, uint256 weight); +contract FixedRewardPool is OwnableUpgradeable, ReentrancyGuardUpgradeable, ERC721Holder { + event Deposit(address indexed user, uint256 tokenId, uint256 rewards, uint256 weight); + event Withdraw(address indexed user, uint256 tokenId, uint256 rewards, uint256 weight); event EmergencyWithdraw(address indexed user, uint256 tokenId, uint256 weight); + event Poke(address indexed user, uint256 tokenId, uint256 rewards, uint256 originalWeight, uint256 newWeight); + event ClaimRewards(address indexed user, uint256 rewards); struct UserInfo { uint256 amount; // How many staked tokens the user has provided @@ -53,10 +56,11 @@ contract FixedRewardPool is OwnableUpgradeable, ReentrancyGuardUpgradeable { UserInfo storage user = userInfo[msg.sender]; _updatePool(); + uint256 _pending = 0; if (user.amount > 0) { - uint256 pending = (user.amount * accTokenPerShare) / PRECISION_FACTOR - user.rewardDebt; - if (pending > 0) { - (bool success, ) = msg.sender.call{value: pending}(""); + _pending = (user.amount * accTokenPerShare) / PRECISION_FACTOR - user.rewardDebt; + if (_pending > 0) { + (bool success, ) = msg.sender.call{value: _pending}(""); require(success, "Failed to send reward"); } } @@ -72,7 +76,7 @@ contract FixedRewardPool is OwnableUpgradeable, ReentrancyGuardUpgradeable { user.rewardDebt = (user.amount * accTokenPerShare) / PRECISION_FACTOR; - emit Deposit(msg.sender, _tokenId, _amount); + emit Deposit(msg.sender, _tokenId, _pending, _amount); } function withdraw(uint256 _tokenId) external nonReentrant { @@ -81,7 +85,7 @@ contract FixedRewardPool is OwnableUpgradeable, ReentrancyGuardUpgradeable { _updatePool(); - uint256 pending = (user.amount * accTokenPerShare) / PRECISION_FACTOR - user.rewardDebt; + uint256 _pending = (user.amount * accTokenPerShare) / PRECISION_FACTOR - user.rewardDebt; uint256 _amount = tokenWeight[_tokenId]; user.amount = user.amount - _amount; @@ -90,14 +94,14 @@ contract FixedRewardPool is OwnableUpgradeable, ReentrancyGuardUpgradeable { tokenWeight[_tokenId] = 0; totalStakedWeight = totalStakedWeight - _amount; - if (pending > 0) { - (bool success, ) = msg.sender.call{value: pending}(""); + if (_pending > 0) { + (bool success, ) = msg.sender.call{value: _pending}(""); require(success, "Failed to send reward"); } user.rewardDebt = (user.amount * accTokenPerShare) / PRECISION_FACTOR; - emit Withdraw(msg.sender, _tokenId, _amount); + emit Withdraw(msg.sender, _tokenId, _pending, _amount); } function emergencyWithdraw(uint256 _tokenId) external nonReentrant { @@ -115,6 +119,48 @@ contract FixedRewardPool is OwnableUpgradeable, ReentrancyGuardUpgradeable { emit EmergencyWithdraw(msg.sender, _tokenId, user.amount); } + function poke(uint256 _tokenId) external nonReentrant { + address _staker = tokenStaker[_tokenId]; + require(_staker == address(0), "Invalid token"); + + UserInfo storage user = userInfo[_staker]; + + _updatePool(); + + uint256 _pending = (user.amount * accTokenPerShare) / PRECISION_FACTOR - user.rewardDebt; + if (_pending > 0) { + (bool success, ) = _staker.call{value: _pending}(""); + require(success, "Failed to send reward"); + } + + uint256 _originalWeight = tokenWeight[_tokenId]; + uint256 _newWeight = weightNFT.weight(_tokenId); + if (_originalWeight != _newWeight) { + user.amount = user.amount - _originalWeight + _newWeight; + tokenWeight[_tokenId] = _newWeight; + totalStakedWeight = totalStakedWeight - _originalWeight + _newWeight; + } + + user.rewardDebt = (user.amount * accTokenPerShare) / PRECISION_FACTOR; + + emit Poke(_staker, _tokenId, _pending, _originalWeight, _newWeight); + } + + function claimRewards(address _user) external nonReentrant { + UserInfo storage user = userInfo[_user]; + + _updatePool(); + + uint256 _pending = (user.amount * accTokenPerShare) / PRECISION_FACTOR - user.rewardDebt; + if (_pending > 0) { + (bool success, ) = _user.call{value: _pending}(""); + require(success, "Failed to send reward"); + } + + user.rewardDebt = (user.amount * accTokenPerShare) / PRECISION_FACTOR; + emit ClaimRewards(_user, _pending); + } + function pendingReward(address _user) external view returns (uint256) { UserInfo storage user = userInfo[_user]; uint256 _totalStakedWeight = totalStakedWeight; @@ -138,7 +184,7 @@ contract FixedRewardPool is OwnableUpgradeable, ReentrancyGuardUpgradeable { } uint256 rewards = (block.number - lastRewardBlock) * rewardPerBlock; - accTokenPerShare = accTokenPerShare + ((rewards * PRECISION_FACTOR) / totalStakedWeight); + accTokenPerShare = accTokenPerShare + (rewards * PRECISION_FACTOR) / totalStakedWeight; lastRewardBlock = block.number; } From f89b916ff81f2ac2fae061e99229d4894c24a2ba Mon Sep 17 00:00:00 2001 From: ququzone Date: Mon, 21 Oct 2024 22:55:09 +0800 Subject: [PATCH 3/3] add tests for fixed reward pool --- contracts/gauges/FixedRewardPool.sol | 2 +- contracts/wrapper/OwnedWeightedNFT.sol | 38 ++++++++ test/FixedRewardPool.t.sol | 118 +++++++++++++++++++++++++ 3 files changed, 157 insertions(+), 1 deletion(-) create mode 100644 contracts/wrapper/OwnedWeightedNFT.sol create mode 100644 test/FixedRewardPool.t.sol diff --git a/contracts/gauges/FixedRewardPool.sol b/contracts/gauges/FixedRewardPool.sol index b7c0078..2fbfa85 100644 --- a/contracts/gauges/FixedRewardPool.sol +++ b/contracts/gauges/FixedRewardPool.sol @@ -121,7 +121,7 @@ contract FixedRewardPool is OwnableUpgradeable, ReentrancyGuardUpgradeable, ERC7 function poke(uint256 _tokenId) external nonReentrant { address _staker = tokenStaker[_tokenId]; - require(_staker == address(0), "Invalid token"); + require(_staker != address(0), "Invalid token"); UserInfo storage user = userInfo[_staker]; diff --git a/contracts/wrapper/OwnedWeightedNFT.sol b/contracts/wrapper/OwnedWeightedNFT.sol new file mode 100644 index 0000000..649b3e0 --- /dev/null +++ b/contracts/wrapper/OwnedWeightedNFT.sol @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol"; +import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; + +import {IWeightedNFT} from "../interfaces/IWeightedNFT.sol"; + +contract OwnedWeightedNFT is Ownable, IWeightedNFT { + event WeightChanged(uint256 tokenId, uint256 weight); + + uint256 public immutable DEFAULT_WEIGHT = 100; + + address public immutable override nft; + mapping(uint256 => uint256) _weights; + + constructor(address _nft, address _owner) { + nft = _nft; + transferOwnership(_owner); + } + + function weight(uint256 tokenId) external view override returns (uint256) { + require(IERC721(nft).ownerOf(tokenId) != address(0), "token not minted"); + + uint256 _weight = _weights[tokenId]; + if (_weight == 0) { + return DEFAULT_WEIGHT; + } + return _weights[tokenId]; + } + + function setWeight(uint256 tokenId, uint256 _weight) external onlyOwner { + require(_weight > 0, "invalid weight"); + + _weights[tokenId] = _weight; + emit WeightChanged(tokenId, _weight); + } +} diff --git a/test/FixedRewardPool.t.sol b/test/FixedRewardPool.t.sol new file mode 100644 index 0000000..2164a4f --- /dev/null +++ b/test/FixedRewardPool.t.sol @@ -0,0 +1,118 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +import {Test, console} from "forge-std/Test.sol"; +import {FixedRewardPool} from "../contracts/gauges/FixedRewardPool.sol"; +import {OwnedWeightedNFT} from "../contracts/wrapper/OwnedWeightedNFT.sol"; +import {ERC721} from "@openzeppelin/contracts/token/ERC721/ERC721.sol"; + +contract TestNFT is ERC721 { + constructor() ERC721("TestNFT", "TEST") {} + + function mint(address to, uint256 tokenId) external { + _mint(to, tokenId); + } +} + +contract TestFixedRewardPool is Test { + FixedRewardPool public fixedRewardPool; + TestNFT public testNFT; + OwnedWeightedNFT public ownedWeightedNFT; + address public alice = address(0xa); + address public bob = address(0xb); + address public feeCollector = address(0xf); + + function setUp() public { + testNFT = new TestNFT(); + ownedWeightedNFT = new OwnedWeightedNFT(address(testNFT), alice); + fixedRewardPool = new FixedRewardPool(); + fixedRewardPool.initialize(address(ownedWeightedNFT), 10, 0.1 ether); + } + + function testOwnership() public { + assertEq(ownedWeightedNFT.owner(), alice); + assertEq(fixedRewardPool.owner(), address(this)); + } + + function testSetWeight() public { + vm.expectRevert("ERC721: invalid token ID"); + ownedWeightedNFT.weight(1); + + testNFT.mint(bob, 1); + assertEq(ownedWeightedNFT.weight(1), 100); + + vm.prank(bob); + vm.expectRevert("Ownable: caller is not the owner"); + ownedWeightedNFT.setWeight(1, 200); + + vm.prank(alice); + ownedWeightedNFT.setWeight(1, 200); + assertEq(ownedWeightedNFT.weight(1), 200); + } + + function testMining() public { + assertEq(fixedRewardPool.lastRewardBlock(), 10); + assertEq(fixedRewardPool.rewardPerBlock(), 0.1 ether); + + testNFT.mint(bob, 1); + testNFT.mint(bob, 2); + testNFT.mint(alice, 3); + + vm.startPrank(bob); + vm.expectRevert("ERC721: caller is not token owner or approved"); + fixedRewardPool.deposit(1); + testNFT.approve(address(fixedRewardPool), 1); + fixedRewardPool.deposit(1); + assertEq(fixedRewardPool.totalStakedWeight(), 100); + (uint256 amount, uint256 rewardDebt) = fixedRewardPool.userInfo(bob); + assertEq(amount, 100); + assertEq(rewardDebt, 0); + vm.stopPrank(); + + vm.roll(5); + assertEq(fixedRewardPool.pendingReward(bob), 0); + + vm.roll(11); + assertEq(fixedRewardPool.pendingReward(bob), 0.1 ether); + + vm.expectRevert("Failed to send reward"); + fixedRewardPool.claimRewards(bob); + + payable(address(fixedRewardPool)).transfer(0.1 ether); + fixedRewardPool.claimRewards(bob); + assertEq(bob.balance, 0.1 ether); + assertEq(fixedRewardPool.pendingReward(bob), 0); + assertEq(address(fixedRewardPool).balance, 0); + vm.startPrank(alice); + testNFT.approve(address(fixedRewardPool), 3); + fixedRewardPool.deposit(3); + vm.stopPrank(); + assertEq(fixedRewardPool.totalStakedWeight(), 200); + + vm.deal(address(fixedRewardPool), 100 ether); + vm.roll(12); + + vm.prank(alice); + ownedWeightedNFT.setWeight(1, 300); + assertEq(fixedRewardPool.totalStakedWeight(), 200); + assertEq(bob.balance, 0.1 ether); + fixedRewardPool.poke(1); + assertEq(bob.balance, 0.15 ether); + assertEq(fixedRewardPool.totalStakedWeight(), 400); + assertEq(alice.balance, 0); + fixedRewardPool.poke(3); + assertEq(alice.balance, 0.05 ether); + assertEq(fixedRewardPool.totalStakedWeight(), 400); + + vm.roll(13); + assertEq(testNFT.ownerOf(1), address(fixedRewardPool)); + vm.prank(bob); + fixedRewardPool.withdraw(1); + assertEq(bob.balance, 0.225 ether); + assertEq(testNFT.ownerOf(1), bob); + fixedRewardPool.claimRewards(alice); + assertEq(alice.balance, 0.075 ether); + assertEq(fixedRewardPool.totalStakedWeight(), 100); + assertEq(fixedRewardPool.tokenWeight(1), 0); + } +}