From 5c3cb203ac2d8ed9898b48e6232212d0328fef20 Mon Sep 17 00:00:00 2001 From: Dristpunk Date: Tue, 26 Dec 2023 16:44:27 +0300 Subject: [PATCH] fix: gori comments --- foundry.toml | 6 +- package.json | 5 +- solidity/contracts/Greeter.sol | 2 +- solidity/contracts/Unlock.sol | 60 +++++++++------- solidity/interfaces/IGreeter.sol | 2 +- solidity/interfaces/IUnlock.sol | 12 ++-- solidity/scripts/Deploy.sol | 2 +- solidity/test/integration/IntegrationBase.sol | 4 +- solidity/test/integration/Unlock.t.sol | 68 ++++++++++++------- solidity/test/utils/IOwnable2Steps.sol | 10 +++ solidity/test/utils/IOwned.sol | 6 -- yarn.lock | 11 ++- 12 files changed, 113 insertions(+), 75 deletions(-) create mode 100644 solidity/test/utils/IOwnable2Steps.sol delete mode 100644 solidity/test/utils/IOwned.sol diff --git a/foundry.toml b/foundry.toml index 849e352..c5db5e0 100644 --- a/foundry.toml +++ b/foundry.toml @@ -8,11 +8,11 @@ number_underscore = 'thousands' multiline_func_header = 'params_first' [profile.default] -solc_version = '0.8.19' +solc_version = '0.8.20' src = 'solidity' test = 'solidity/test' out = 'out' -libs = ['node_modules'] +libs = ["node_modules", "lib"] optimizer_runs = 10_000 [profile.optimized] @@ -31,4 +31,4 @@ src = 'solidity/interfaces/' runs = 1000 [rpc_endpoints] -mainnet = "${MAINNET_RPC}" \ No newline at end of file +mainnet = "${MAINNET_RPC}" diff --git a/package.json b/package.json index 5b0755e..aafe7a9 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "scripts": { "build": "forge build", "build:optimized": "FOUNDRY_PROFILE=optimized forge build", - "coverage": "forge coverage --match-contract Unit", + "coverage": "forge coverage --match-contract Integration", "deploy:goerli": "bash -c 'source .env && forge script DeployGoerli --rpc-url $GOERLI_RPC --broadcast --private-key $GOERLI_DEPLOYER_PK --verify --etherscan-api-key $ETHERSCAN_API_KEY'", "deploy:mainnet": "bash -c 'source .env && forge script DeployMainnet --rpc-url $MAINNET_RPC --broadcast --private-key $MAINNET_DEPLOYER_PK --verify --etherscan-api-key $ETHERSCAN_API_KEY'", "lint:check": "yarn lint:sol-tests && yarn lint:sol-logic && forge fmt --check", @@ -32,6 +32,7 @@ "package.json": "sort-package-json" }, "dependencies": { + "@openzeppelin/contracts": "^5.0.1", "isolmate": "github:defi-wonderland/isolmate#59e1804", "solmate": "^6.2.0" }, @@ -39,7 +40,7 @@ "@commitlint/cli": "17.0.3", "@commitlint/config-conventional": "17.0.3", "ds-test": "github:dapphub/ds-test#e282159", - "forge-std": "github:foundry-rs/forge-std#v1.7.3", + "forge-std": "github:foundry-rs/forge-std#v1.7.4", "husky": ">=8", "lint-staged": ">=10", "solhint": "3.6.2", diff --git a/solidity/contracts/Greeter.sol b/solidity/contracts/Greeter.sol index 4143dae..dcabb9a 100644 --- a/solidity/contracts/Greeter.sol +++ b/solidity/contracts/Greeter.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity =0.8.19; +pragma solidity =0.8.20; import {IERC20} from 'isolmate/interfaces/tokens/IERC20.sol'; import {IGreeter} from 'interfaces/IGreeter.sol'; diff --git a/solidity/contracts/Unlock.sol b/solidity/contracts/Unlock.sol index 938be06..7c96d1d 100644 --- a/solidity/contracts/Unlock.sol +++ b/solidity/contracts/Unlock.sol @@ -1,45 +1,55 @@ // SPDX-License-Identifier: MIT -pragma solidity =0.8.19; +pragma solidity 0.8.20; import {IERC20} from 'isolmate/interfaces/tokens/IERC20.sol'; import {IUnlock} from 'interfaces/IUnlock.sol'; -import {Owned} from 'solmate/auth/Owned.sol'; +import {Ownable2Step, Ownable} from '@openzeppelin/contracts/access/Ownable2Step.sol'; -contract Unlock is Owned, IUnlock { - uint256 public constant TOTAL_SUPPLY = 24_960_000 ether; +contract Unlock is Ownable2Step, IUnlock { + uint256 public totalAmount; uint256 public startTime; - mapping(address _token => uint256 _amount) public withdrawedSupply; + mapping(address _token => uint256 _amount) public withdrawnSupply; - constructor(uint256 _startTime, address _owner) Owned(_owner) { + constructor(uint256 _startTime, address _owner, uint256 _totalAmount) Ownable(_owner) { startTime = _startTime; + totalAmount = _totalAmount; } - function _unlockedSupply(address _token, uint256 _timestamp) internal view returns (uint256 _unlockedSupplyReturn) { - if (_timestamp < startTime + 365 days) { - _unlockedSupplyReturn = 0; - } else { - _unlockedSupplyReturn = - TOTAL_SUPPLY / 13 + (TOTAL_SUPPLY * 12 / 13) * (_timestamp - startTime - 365 days) / 365 days; - _unlockedSupplyReturn -= withdrawedSupply[_token]; - } + function _unlockedSupply(uint256 _timestamp) internal view returns (uint256 _unlockedSupplyReturn) { + uint256 _firstMilestoneTime = startTime + 365 days; // 1st milestone is 1 year after start time + + if (_timestamp < _firstMilestoneTime) return _unlockedSupplyReturn; // return 0 if not reached + + uint256 _firstMilestoneUnlockedAmount = totalAmount / 13; // 1st milestone unlock amount + uint256 _restAmount = totalAmount - _firstMilestoneUnlockedAmount; // rest amount after 1st milestone + uint256 _timePassed = _timestamp - startTime - 365 days; // time passed after 1st milestone + uint256 _totalTime = 365 days; // total unlock time after 1st milestone + + // f(x) = ax + b + // b = totalAmount / 13 + // a = restAmount / totalTime + // x = timePassed + _unlockedSupplyReturn = _firstMilestoneUnlockedAmount + (_restAmount * _timePassed) / _totalTime; } - function unlockedSupply(address _token) external view returns (uint256 _unlockedSupplyReturn) { - _unlockedSupplyReturn = _unlockedSupply(_token, block.timestamp); + function unlockedSupply() external view returns (uint256 _unlockedSupplyReturn) { + _unlockedSupplyReturn = _unlockedSupply(block.timestamp); } - function unlockedAtTimestamp( - address _token, - uint256 _timestamp - ) external view returns (uint256 _unlockedSupplyReturn) { - _unlockedSupplyReturn = _unlockedSupply(_token, _timestamp); + function unlockedAtTimestamp(uint256 _timestamp) external view returns (uint256 _unlockedSupplyReturn) { + _unlockedSupplyReturn = _unlockedSupply(_timestamp); } - function withdraw(address _receiver, address _token, uint256 _amount) external { - if (_amount > _unlockedSupply(_token, block.timestamp)) revert InsufficientUnlockedSupply(); - if (msg.sender != owner) revert Unauthorized(); - withdrawedSupply[_token] += _amount; + function withdraw(address _receiver, address _token) external { + if (msg.sender != owner()) revert Unauthorized(); + + uint256 _amount = _unlockedSupply(block.timestamp) - withdrawnSupply[_token]; + uint256 _balance = IERC20(_token).balanceOf(address(this)); + + if (_amount > _balance) _amount = _balance; + + withdrawnSupply[_token] += _amount; IERC20(_token).transfer(_receiver, _amount); } } diff --git a/solidity/interfaces/IGreeter.sol b/solidity/interfaces/IGreeter.sol index 1087702..0ce1eb6 100644 --- a/solidity/interfaces/IGreeter.sol +++ b/solidity/interfaces/IGreeter.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity =0.8.19; +pragma solidity =0.8.20; import {IERC20} from 'isolmate/interfaces/tokens/IERC20.sol'; diff --git a/solidity/interfaces/IUnlock.sol b/solidity/interfaces/IUnlock.sol index 1a3c818..f00bcfb 100644 --- a/solidity/interfaces/IUnlock.sol +++ b/solidity/interfaces/IUnlock.sol @@ -1,15 +1,15 @@ // SPDX-License-Identifier: MIT -pragma solidity =0.8.19; +pragma solidity =0.8.20; interface IUnlock { error InsufficientUnlockedSupply(); error Unauthorized(); - function unlockedSupply(address _token) external view returns (uint256 _unlockedSupply); - function unlockedAtTimestamp(address _token, uint256 _timestamp) external view returns (uint256 _unlockedSupply); - function withdraw(address _receiver, address _token, uint256 _amount) external; + function unlockedSupply() external view returns (uint256 _unlockedSupply); + function unlockedAtTimestamp(uint256 _timestamp) external view returns (uint256 _unlockedSupply); + function withdraw(address _receiver, address _token) external; function startTime() external view returns (uint256 _startTime); - function withdrawedSupply(address _token) external view returns (uint256 _withdrawedSupply); - function TOTAL_SUPPLY() external view returns (uint256 _totalSupply); + function withdrawnSupply(address _token) external view returns (uint256 _withdrawedSupply); + function totalAmount() external view returns (uint256 _totalAmount); } diff --git a/solidity/scripts/Deploy.sol b/solidity/scripts/Deploy.sol index 2f7a1e0..7311dcb 100644 --- a/solidity/scripts/Deploy.sol +++ b/solidity/scripts/Deploy.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity =0.8.19; +pragma solidity =0.8.20; import {Script} from 'forge-std/Script.sol'; import {Greeter} from 'contracts/Greeter.sol'; diff --git a/solidity/test/integration/IntegrationBase.sol b/solidity/test/integration/IntegrationBase.sol index 55256bd..f46ff6e 100644 --- a/solidity/test/integration/IntegrationBase.sol +++ b/solidity/test/integration/IntegrationBase.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity =0.8.19; +pragma solidity =0.8.20; import {Test} from 'forge-std/Test.sol'; @@ -20,6 +20,6 @@ contract IntegrationBase is Test { _startTime = block.timestamp + 10 minutes; vm.prank(_alice); - _unlock = new Unlock(_startTime, _owner); + _unlock = new Unlock(_startTime, _owner, 24_960_000 ether); } } diff --git a/solidity/test/integration/Unlock.t.sol b/solidity/test/integration/Unlock.t.sol index f1d9b7a..07992b2 100644 --- a/solidity/test/integration/Unlock.t.sol +++ b/solidity/test/integration/Unlock.t.sol @@ -1,71 +1,89 @@ // SPDX-License-Identifier: MIT -pragma solidity =0.8.19; +pragma solidity =0.8.20; import {IntegrationBase} from 'test/integration/IntegrationBase.sol'; -import {IOwned} from 'test/utils/IOwned.sol'; +import {IOwnable2Steps} from 'test/utils/IOwnable2Steps.sol'; +import {Ownable} from '@openzeppelin/contracts/access/Ownable.sol'; import {IUnlock} from 'interfaces/IUnlock.sol'; import {IERC20} from 'isolmate/interfaces/tokens/IERC20.sol'; contract IntegrationUnlock is IntegrationBase { function test_Constructor() public { - assertEq(IOwned(address(_unlock)).owner(), _owner); + assertEq(IOwnable2Steps(address(_unlock)).owner(), _owner); assertEq(_unlock.startTime(), block.timestamp + 10 minutes); } function test_UnlockedAtTimestamp() public { - assertEq(_unlock.unlockedAtTimestamp(_nextToken, _startTime), 0); - assertEq(_unlock.unlockedAtTimestamp(_nextToken, _startTime + 364 days), 0 ether); - assertEq(_unlock.unlockedAtTimestamp(_nextToken, _startTime + 365 days), 1_920_000 ether); + assertEq(_unlock.unlockedAtTimestamp(_startTime), 0); + assertEq(_unlock.unlockedAtTimestamp(_startTime + 364 days), 0 ether); + assertEq(_unlock.unlockedAtTimestamp(_startTime + 365 days), 1_920_000 ether); - assertEq(_unlock.unlockedAtTimestamp(_nextToken, _startTime + 365 days + 10 days) - 2_551_232 ether < 1 ether, true); - assertEq( - _unlock.unlockedAtTimestamp(_nextToken, _startTime + 365 days + 100 days) - 8_232_328 ether < 1 ether, true - ); + assertEq(_unlock.unlockedAtTimestamp(_startTime + 365 days + 10 days) - 2_551_232 ether < 1 ether, true); + assertEq(_unlock.unlockedAtTimestamp(_startTime + 365 days + 100 days) - 8_232_328 ether < 1 ether, true); - assertEq(_unlock.unlockedAtTimestamp(_nextToken, _startTime + 365 days + 365 days), 24_960_000 ether); + assertEq(_unlock.unlockedAtTimestamp(_startTime + 365 days + 365 days), 24_960_000 ether); } function test_UnlockedAmount() public { - assertEq(_unlock.unlockedSupply(_nextToken), 0); + assertEq(_unlock.unlockedSupply(), 0); vm.warp(_startTime + 364 days); - assertEq(_unlock.unlockedSupply(_nextToken), 0); + assertEq(_unlock.unlockedSupply(), 0); vm.warp(_startTime + 365 days); - assertEq(_unlock.unlockedSupply(_nextToken), 1_920_000 ether); + assertEq(_unlock.unlockedSupply(), 1_920_000 ether); vm.warp(_startTime + 365 days + 10 days); - assertEq(_unlock.unlockedSupply(_nextToken) - 2_551_232 ether < 1 ether, true); + assertEq(_unlock.unlockedSupply() - 2_551_232 ether < 1 ether, true); vm.warp(_startTime + 365 days + 100 days); - assertEq(_unlock.unlockedSupply(_nextToken) - 8_232_328 ether < 1 ether, true); + assertEq(_unlock.unlockedSupply() - 8_232_328 ether < 1 ether, true); vm.warp(_startTime + 365 days + 365 days); - assertEq(_unlock.unlockedSupply(_nextToken), 24_960_000 ether); + assertEq(_unlock.unlockedSupply(), 24_960_000 ether); } function test_WithdrawNoSupply() public { vm.warp(_startTime + 364 days); vm.prank(_owner); - vm.expectRevert(abi.encodeWithSelector(IUnlock.InsufficientUnlockedSupply.selector)); - _unlock.withdraw(_alice, _nextToken, 1); + _unlock.withdraw(_alice, _nextToken); + assertEq(_unlock.withdrawnSupply(_nextToken), 0); + assertEq(IERC20(_nextToken).balanceOf(_alice), 0); } function test_WithdrawUnauthorized() public { vm.warp(_startTime + 365 days); vm.expectRevert(abi.encodeWithSelector(IUnlock.Unauthorized.selector)); - _unlock.withdraw(_alice, _nextToken, 1); + _unlock.withdraw(_alice, _nextToken); } function test_WithdrawLegit() public { - deal(_nextToken, address(_unlock), 1_920_000 ether); + deal(_nextToken, address(_unlock), 2_000_000 ether); // deal more than withrawable vm.warp(_startTime + 365 days); vm.startPrank(_owner); - _unlock.withdraw(_alice, _nextToken, 1_920_000 ether); - assertEq(_unlock.withdrawedSupply(_nextToken), 1_920_000 ether); + _unlock.withdraw(_alice, _nextToken); + assertEq(_unlock.withdrawnSupply(_nextToken), 1_920_000 ether); assertEq(IERC20(_nextToken).balanceOf(_alice), 1_920_000 ether); - vm.expectRevert(abi.encodeWithSelector(IUnlock.InsufficientUnlockedSupply.selector)); - _unlock.withdraw(_alice, _nextToken, 1); + // try again and expect no changes + _unlock.withdraw(_alice, _nextToken); + assertEq(_unlock.withdrawnSupply(_nextToken), 1_920_000 ether); + assertEq(IERC20(_nextToken).balanceOf(_alice), 1_920_000 ether); vm.stopPrank(); } + + function test_transferOwnership() public { + vm.prank(_owner); + IOwnable2Steps(address(_unlock)).transferOwnership(_alice); + assertEq(IOwnable2Steps(address(_unlock)).pendingOwner(), _alice); + assertEq(IOwnable2Steps(address(_unlock)).owner(), _owner); + + address _bob = makeAddr('bob'); + vm.prank(_bob); + vm.expectRevert(abi.encodeWithSelector(Ownable.OwnableUnauthorizedAccount.selector, _bob)); + IOwnable2Steps(address(_unlock)).acceptOwnership(); + + vm.prank(_alice); + IOwnable2Steps(address(_unlock)).acceptOwnership(); + assertEq(IOwnable2Steps(address(_unlock)).owner(), _alice); + } } diff --git a/solidity/test/utils/IOwnable2Steps.sol b/solidity/test/utils/IOwnable2Steps.sol new file mode 100644 index 0000000..3071c7c --- /dev/null +++ b/solidity/test/utils/IOwnable2Steps.sol @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: MIT +pragma solidity =0.8.20; + +interface IOwnable2Steps { + function transferOwnership(address _newOwner) external; + function acceptOwnership() external; + + function pendingOwner() external view returns (address _pendingOwner); + function owner() external view returns (address _owner); +} diff --git a/solidity/test/utils/IOwned.sol b/solidity/test/utils/IOwned.sol deleted file mode 100644 index 77f95ac..0000000 --- a/solidity/test/utils/IOwned.sol +++ /dev/null @@ -1,6 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity =0.8.19; - -interface IOwned { - function owner() external view returns (address _owner); -} diff --git a/yarn.lock b/yarn.lock index 892657b..5e27240 100644 --- a/yarn.lock +++ b/yarn.lock @@ -231,6 +231,11 @@ "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" +"@openzeppelin/contracts@^5.0.1": + version "5.0.1" + resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-5.0.1.tgz#93da90fc209a0a4ff09c1deb037fbb35e4020890" + integrity sha512-yQJaT5HDp9hYOOp4jTYxMsR02gdFZFXhewX5HW9Jo4fsqSVqqyIO/xTHdWDaKX5a3pv1txmf076Lziz+sO7L1w== + "@solidity-parser/parser@^0.14.1": version "0.14.5" resolved "https://registry.yarnpkg.com/@solidity-parser/parser/-/parser-0.14.5.tgz#87bc3cc7b068e08195c219c91cd8ddff5ef1a804" @@ -1067,9 +1072,9 @@ flatted@^2.0.0: resolved "https://registry.yarnpkg.com/flatted/-/flatted-2.0.2.tgz#4575b21e2bcee7434aa9be662f4b7b5f9c2b5138" integrity sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA== -"forge-std@github:foundry-rs/forge-std#v1.7.3": - version "1.7.3" - resolved "https://codeload.github.com/foundry-rs/forge-std/tar.gz/2f112697506eab12d433a65fdc31a639548fe365" +"forge-std@github:foundry-rs/forge-std#v1.7.4": + version "1.7.4" + resolved "https://codeload.github.com/foundry-rs/forge-std/tar.gz/155d547c449afa8715f538d69454b83944117811" fs-extra@^11.0.0: version "11.1.1"