From a8dae8d9dd96927919543079f32ada52c8c8e2d9 Mon Sep 17 00:00:00 2001 From: thurendous Date: Sat, 7 Sep 2024 00:11:48 +0900 Subject: [PATCH] changed the calculation of exchanging tokens and add some tests for it. (WIP) --- .env.example | 2 +- .vscode/settings.json | 3 +- LICENSE | 2 +- src/VotingPowerExchange.sol | 81 ++++++-- test/unit/VotingPowerExchange.t.sol | 280 ++++++++++++++++++++++++++++ 5 files changed, 345 insertions(+), 23 deletions(-) create mode 100644 test/unit/VotingPowerExchange.t.sol diff --git a/.env.example b/.env.example index b77824d..cea9231 100644 --- a/.env.example +++ b/.env.example @@ -1 +1 @@ -PRIVATE_KEY=0x... +PRIVATE_KEY=1234567890123456789012345678901234567890123456789012345678901234 diff --git a/.vscode/settings.json b/.vscode/settings.json index 6d22ea5..696f9c5 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -18,5 +18,6 @@ "titleBar.inactiveBackground": "#7db3df99", "titleBar.inactiveForeground": "#15202b99" }, - "peacock.color": "#7db3df" + "peacock.color": "#7db3df", + "editor.defaultFormatter": "JuanBlanco.solidity" } diff --git a/LICENSE b/LICENSE index 4b20fa9..db80888 100644 --- a/LICENSE +++ b/LICENSE @@ -9,7 +9,7 @@ Parameters Licensor: CodeFox.Inc -Licensed Work: SPARKN +Licensed Work: Voting Power Exchange The Licensed Work is (c) 2024 CodeFox.Inc Additional Use Grant: Any uses listed and defined at CodeFox.co.jp diff --git a/src/VotingPowerExchange.sol b/src/VotingPowerExchange.sol index 91afa24..8eb7ef7 100644 --- a/src/VotingPowerExchange.sol +++ b/src/VotingPowerExchange.sol @@ -29,7 +29,7 @@ contract VotingPowerExchange is AccessControl, EIP712 { error VotingPowerExchange__SignatureExpired(); error VotingPowerExchange__InvalidSignature(); error VotingPowerExchange__LevelIsLowerThanExisting(); - error VotingPowerExchange__VotingPowerIsHigherThanCap(); + error VotingPowerExchange__VotingPowerIsHigherThanCap(uint256 currentVotingPower); // Events event VotingPowerReceived(address indexed user, uint256 utilityTokenAmount, uint256 votingPowerAmount); @@ -42,10 +42,10 @@ contract VotingPowerExchange is AccessControl, EIP712 { // Roles for the contract. Default admin holds the highest authority to to set the manager and exchanger. bytes32 public constant MANAGER_ROLE = keccak256("MANAGER_ROLE"); bytes32 public constant EXCHANGER_ROLE = keccak256("EXCHANGER_ROLE"); - // PRICISION values for the calculation - uint256 private constant PRICISION_FIX = 1e9; - uint256 private constant PRICISION_FACTOR = 10; - uint256 private constant PRICISION = 1e18; + // PRECISION values for the calculation + uint256 private constant PRECISION_FIX = 1e9; + uint256 private constant PRECISION_FACTOR = 10; + uint256 private constant PRECISION = 1e18; // token instances IGovToken private immutable govToken; @@ -56,6 +56,7 @@ contract VotingPowerExchange is AccessControl, EIP712 { // voting power cap for limiting the voting power uint256 private votingPowerCap; + /// @notice The constructor of the VotingPowerExchange contract /// @param _govToken The address of the GovToken contract /// @param _utilityToken The address of the ERC20 token contract /// @param defaultAdmin The address of the default admin @@ -68,6 +69,7 @@ contract VotingPowerExchange is AccessControl, EIP712 { if (_govToken == address(0) || _utilityToken == address(0)) { revert VotingPowerExchange__GovOrUtilAddressIsZero(); } + govToken = IGovToken(_govToken); utilityToken = IERC20UpgradeableTokenV1(_utilityToken); _grantRole(DEFAULT_ADMIN_ROLE, defaultAdmin); @@ -101,7 +103,9 @@ contract VotingPowerExchange is AccessControl, EIP712 { if (block.timestamp > expiration) revert VotingPowerExchange__SignatureExpired(); // check the current gove token balance of the sender uint256 currentVotingPower = govToken.balanceOf(sender); - if (currentVotingPower >= votingPowerCap) revert VotingPowerExchange__VotingPowerIsHigherThanCap(); + if (currentVotingPower >= votingPowerCap) { + revert VotingPowerExchange__VotingPowerIsHigherThanCap(currentVotingPower); + } // create the digest for EIP-712 and validate the signature by the `sender` bytes32 digest = _hashTypedDataV4(keccak256(abi.encode(_EXCHANGE_TYPEHASH, sender, amount, nonce, expiration))); @@ -112,6 +116,7 @@ contract VotingPowerExchange is AccessControl, EIP712 { uint256 currentBurnedAmount = govToken.burnedAmountOfUtilToken(sender); + // TODO: change the logics here according to `calculateIncrementedVotingPower` etc // calculate the amount of voting power token amount to mint // increased voting power = increased level = increased token amount of govToken uint256 increasedVotingPower = calculateIncreasedVotingPower(amount, currentBurnedAmount); @@ -167,6 +172,7 @@ contract VotingPowerExchange is AccessControl, EIP712 { //////////////////////////////////// /////// pure/view functions //////// //////////////////////////////////// + // TODO: delete this function /** * @notice Calculate the increased voting power based on the amount of utility token to burn * @dev This function calculates the increased voting power based on the square root of the burned amount @@ -177,11 +183,12 @@ contract VotingPowerExchange is AccessControl, EIP712 { */ function calculateIncreasedVotingPower(uint256 amount, uint256 currentBurnedAmount) public pure returns (uint256) { uint256 increasedVotingPower = ( - Math.sqrt((currentBurnedAmount + amount) * PRICISION) - Math.sqrt(currentBurnedAmount * PRICISION) - ) / PRICISION_FACTOR; + Math.sqrt((currentBurnedAmount + amount) * PRECISION) - Math.sqrt(currentBurnedAmount * PRECISION) + ) / PRECISION_FACTOR; return increasedVotingPower; } + // TODO: delete this function /** * @notice Calculate the amount of tokens to burn (amount) to achieve the desired increased voting power * @dev This function calculates the amount based on the reverse calculation of the `calculateIncreasedVotingPower` function @@ -194,19 +201,55 @@ contract VotingPowerExchange is AccessControl, EIP712 { pure returns (uint256) { - // calculate sqrt(currentBurnedAmount * PRICISION) - uint256 sqrtCurrent = Math.sqrt(currentBurnedAmount * PRICISION); - // calculate increasedVotingPower * PRICISION_FACTOR - uint256 votingPowerWithPrecision = increasedVotingPower * PRICISION_FACTOR; + // calculate sqrt(currentBurnedAmount * PRECISION) + uint256 sqrtCurrent = Math.sqrt(currentBurnedAmount * PRECISION); + // calculate increasedVotingPower * PRECISION_FACTOR + uint256 votingPowerWithPrecision = increasedVotingPower * PRECISION_FACTOR; // calculate new sqrt uint256 sqrtNew = sqrtCurrent + votingPowerWithPrecision; - // calculate new sqrt's power 2 and set its pricision as 18 - uint256 sqrtNewSquared = (sqrtNew * sqrtNew) / PRICISION; + // calculate new sqrt's power 2 and set its prEcision as 18 + uint256 sqrtNewSquared = (sqrtNew * sqrtNew) / PRECISION; // calculate the final amount uint256 amount = sqrtNewSquared - currentBurnedAmount; return amount; } + // TODO: test this function + /** + * @notice Calculate the increased voting power based on the amount of utility token to burn + * @dev This function calculates the increased voting power based on the difference of the voting power from the burned amount + * @param amount The amount of utility token to burn + * @param currentBurnedAmount The current burned amount of the user + * @return increasedVotingPower The increased voting power + */ + function calculateIncrementedVotingPower(uint256 amount, uint256 currentBurnedAmount) + public + pure + returns (uint256) + { + return calculateVotingPowerFromBurnedAmount(amount + currentBurnedAmount) + - calculateVotingPowerFromBurnedAmount(currentBurnedAmount); + } + + // TODO: test this function + /** + * @notice Calculate the voting power based on the burned amount + * @dev This function calculates the voting power based on the burned amount + * @dev The formula is: `(2*SQRT(306.25 + 30*x) - 5) / 30 - 1`, which means: e.g. 3,350 utility token can be burned to get 20 voting power. + * @dev 0 utility token burned menas getting 0 voting power. + * @param amount The amount of utility token to burn + * @return votingPower The voting power + */ + function calculateVotingPowerFromBurnedAmount(uint256 amount) public pure returns (uint256) { + // calculate 306.25 + 30*x + uint256 innerValue = (30625 * 1e16 + 30 * amount); + // calculate 2*SQRT(306.25 + 30*x) + uint256 sqrtPart = 2 * Math.sqrt(innerValue) * PRECISION_FIX; + // calculate (2*SQRT(306.25+30*x)-5)/30 - 1 + uint256 result = (uint256(sqrtPart) - 5 * PRECISION) / 30 - PRECISION; + return result; + } + /** * @notice returns the current voting power cap * @dev This function is for convenience to check the current voting power cap @@ -222,14 +265,12 @@ contract VotingPowerExchange is AccessControl, EIP712 { function getConstants() external pure - returns (bytes32 __EXCHANGE_TYPEHASH, uint256 _PRICISION_FIX, uint256 _PRICISION_FACTOR, uint256 _PRICISION) + returns (bytes32 __EXCHANGE_TYPEHASH, uint256 _PRECISION_FIX, uint256 _PRECISION_FACTOR, uint256 _PRECISION) { - /* solhint-disable */ __EXCHANGE_TYPEHASH = _EXCHANGE_TYPEHASH; - _PRICISION_FIX = PRICISION_FIX; - _PRICISION_FACTOR = PRICISION_FACTOR; - _PRICISION = PRICISION; - /* solhint-enable */ + _PRECISION_FIX = PRECISION_FIX; + _PRECISION_FACTOR = PRECISION_FACTOR; + _PRECISION = PRECISION; } /** diff --git a/test/unit/VotingPowerExchange.t.sol b/test/unit/VotingPowerExchange.t.sol new file mode 100644 index 0000000..15e545b --- /dev/null +++ b/test/unit/VotingPowerExchange.t.sol @@ -0,0 +1,280 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import {Test, console} from "forge-std/Test.sol"; +import {VotingPowerExchange} from "src/VotingPowerExchange.sol"; +import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; + +contract VotingPowerExchangeUnitTest is Test { + VotingPowerExchange public votingPowerExchange; + + function setUp() public { + // We need to deploy VotingPowerExchange + votingPowerExchange = + new VotingPowerExchange(address(this), address(this), address(this), address(this), address(this)); + } + + function testSpecialCasesForCalculateVotingPowerFromBurnedAmount() public view { + // test case 1: 1950 * 1e18 -> 15 * 1e18 + uint256 amount1 = 1950 * 1e18; + uint256 expectedVotingPower1 = 15 * 1e18; + uint256 actualVotingPower1 = votingPowerExchange.calculateVotingPowerFromBurnedAmount(amount1); + + // assertApproxEqRel(actualVotingPower1, expectedVotingPower1, 0.001e18); + assertEq(actualVotingPower1, expectedVotingPower1); + + // test case 2: 0 -> 0 + uint256 amount2 = 0; + uint256 expectedVotingPower2 = 0; + uint256 actualVotingPower2 = votingPowerExchange.calculateVotingPowerFromBurnedAmount(amount2); + assertEq(actualVotingPower2, expectedVotingPower2); + + // test case 3: 3350 * 1e18 -> 20 * 1e18 + uint256 amount3 = 3350 * 1e18; + uint256 expectedVotingPower3 = 20 * 1e18; + uint256 actualVotingPower3 = votingPowerExchange.calculateVotingPowerFromBurnedAmount(amount3); + // assertApproxEqRel(actualVotingPower3, expectedVotingPower3, 0.001e18); + assertEq(actualVotingPower3, expectedVotingPower3); + } + + function testSomeMoreSpecialCasesForCalculateVotingPowerFromBurnedAmount() public view { + // test case 4: 20 * 1e18 -> approximately 1.840 * 1e18 + uint256 amount4 = 20 * 1e18; + uint256 expectedVotingPower4 = 0.84026 * 1e18; + uint256 actualVotingPower4 = votingPowerExchange.calculateVotingPowerFromBurnedAmount(amount4); + assertApproxEqRel(actualVotingPower4, expectedVotingPower4, 0.001e16); + + + // test case 5: 15092 * 1e18 -> 44.70679 * 1e18 + uint256 amount5 = 15092 * 1e18; + uint256 expectedVotingPower5 = 43.70679 * 1e18; + uint256 actualVotingPower5 = votingPowerExchange.calculateVotingPowerFromBurnedAmount(amount5); + assertApproxEqRel(actualVotingPower5, expectedVotingPower5, 0.001e16); + + // test case 6: 11_442 * 1e18 -> 37.9096 * 1e18 + uint256 amount6 = 11_442 * 1e18; + uint256 expectedVotingPower6 = 37.9096 * 1e18; + uint256 actualVotingPower6 = votingPowerExchange.calculateVotingPowerFromBurnedAmount(amount6); + assertApproxEqRel(actualVotingPower6, expectedVotingPower6, 0.001e16); + } + + function testCalculateVotingPowerFromBurnedAmount() public view { + uint256[100] memory burnedAmounts = [ + uint256(0), + uint256(25), + uint256(65), + uint256(120), + uint256(190), + uint256(275), + uint256(375), + uint256(490), + uint256(620), + uint256(765), + uint256(925), + uint256(1100), + uint256(1290), + uint256(1495), + uint256(1715), + uint256(1950), + uint256(2200), + uint256(2465), + uint256(2745), + uint256(3040), + uint256(3350), + uint256(3675), + uint256(4015), + uint256(4370), + uint256(4740), + uint256(5125), + uint256(5525), + uint256(5940), + uint256(6370), + uint256(6815), + uint256(7275), + uint256(7750), + uint256(8240), + uint256(8745), + uint256(9265), + uint256(9800), + uint256(10350), + uint256(10915), + uint256(11495), + uint256(12090), + uint256(12700), + uint256(13325), + uint256(13965), + uint256(14620), + uint256(15290), + uint256(15975), + uint256(16675), + uint256(17390), + uint256(18120), + uint256(18865), + uint256(19625), + uint256(20400), + uint256(21190), + uint256(21995), + uint256(22815), + uint256(23650), + uint256(24500), + uint256(25365), + uint256(26245), + uint256(27140), + uint256(28050), + uint256(28975), + uint256(29915), + uint256(30870), + uint256(31840), + uint256(32825), + uint256(33825), + uint256(34840), + uint256(35870), + uint256(36915), + uint256(37975), + uint256(39050), + uint256(40140), + uint256(41245), + uint256(42365), + uint256(43500), + uint256(44650), + uint256(45815), + uint256(46995), + uint256(48190), + uint256(49400), + uint256(50625), + uint256(51865), + uint256(53120), + uint256(54390), + uint256(55675), + uint256(56975), + uint256(58290), + uint256(59620), + uint256(60965), + uint256(62325), + uint256(63700), + uint256(65090), + uint256(66495), + uint256(67915), + uint256(69350), + uint256(70800), + uint256(72265), + uint256(73745), + uint256(75240) + ]; + + uint256[100] memory expectedVotingPowers = [ + uint256(0), + uint256(1), + uint256(2), + uint256(3), + uint256(4), + uint256(5), + uint256(6), + uint256(7), + uint256(8), + uint256(9), + uint256(10), + uint256(11), + uint256(12), + uint256(13), + uint256(14), + uint256(15), + uint256(16), + uint256(17), + uint256(18), + uint256(19), + uint256(20), + uint256(21), + uint256(22), + uint256(23), + uint256(24), + uint256(25), + uint256(26), + uint256(27), + uint256(28), + uint256(29), + uint256(30), + uint256(31), + uint256(32), + uint256(33), + uint256(34), + uint256(35), + uint256(36), + uint256(37), + uint256(38), + uint256(39), + uint256(40), + uint256(41), + uint256(42), + uint256(43), + uint256(44), + uint256(45), + uint256(46), + uint256(47), + uint256(48), + uint256(49), + uint256(50), + uint256(51), + uint256(52), + uint256(53), + uint256(54), + uint256(55), + uint256(56), + uint256(57), + uint256(58), + uint256(59), + uint256(60), + uint256(61), + uint256(62), + uint256(63), + uint256(64), + uint256(65), + uint256(66), + uint256(67), + uint256(68), + uint256(69), + uint256(70), + uint256(71), + uint256(72), + uint256(73), + uint256(74), + uint256(75), + uint256(76), + uint256(77), + uint256(78), + uint256(79), + uint256(80), + uint256(81), + uint256(82), + uint256(83), + uint256(84), + uint256(85), + uint256(86), + uint256(87), + uint256(88), + uint256(89), + uint256(90), + uint256(91), + uint256(92), + uint256(93), + uint256(94), + uint256(95), + uint256(96), + uint256(97), + uint256(98), + uint256(99) + ]; + + for (uint256 i = 0; i < burnedAmounts.length; i++) { + uint256 amount = burnedAmounts[i] * 1e18; + uint256 expectedVotingPower = expectedVotingPowers[i] * 1e18; + uint256 actualVotingPower = votingPowerExchange.calculateVotingPowerFromBurnedAmount(amount); + + assertEq( + actualVotingPower, + expectedVotingPower, + string(abi.encodePacked("Test case failed for burned amount: ", Strings.toString(burnedAmounts[i]))) + ); + } + } +}