From 0781d07dac9f7bb70ac3dfbee5940ca3fb1881b1 Mon Sep 17 00:00:00 2001 From: jon-vdb Date: Sat, 6 May 2023 12:19:44 +0700 Subject: [PATCH 1/4] added address of erc20 to be emitted from PoolAdded event and createPoolWithGrants and createPool function. --- .env.example | 6 - .../ILinearVestingProjectUpgradeable.sol | 53 ++++++-- .../LinearVestingProjectUpgradeable.sol | 73 +++++++--- hardhat.config.ts | 43 +++--- test/linear-vesting/pools.ts | 78 +++++++---- test/linear-vesting/util/fixtures.ts | 125 +++++++++++++----- 6 files changed, 262 insertions(+), 116 deletions(-) delete mode 100644 .env.example diff --git a/.env.example b/.env.example deleted file mode 100644 index b0effda..0000000 --- a/.env.example +++ /dev/null @@ -1,6 +0,0 @@ -RPC_URL= -DEPLOYER_KEY= -DEPLOYER_ADDRESS=
-ETHERSCAN_API_KEY= -EXISTING_FACTORY= -EXISTING_TOKEN= diff --git a/contracts/linear-vesting/ILinearVestingProjectUpgradeable.sol b/contracts/linear-vesting/ILinearVestingProjectUpgradeable.sol index 4f49673..ac8c6a1 100644 --- a/contracts/linear-vesting/ILinearVestingProjectUpgradeable.sol +++ b/contracts/linear-vesting/ILinearVestingProjectUpgradeable.sol @@ -7,10 +7,10 @@ pragma solidity ^0.8.4; * Taken from https://github.com/dandelionlabs-io/linear-vesting-contracts/blob/master/contracts/linear-vesting/ILinearVestingProjectUpgradeable.sol */ interface ILinearVestingProjectUpgradeable { - /// @notice Pool definition struct Pool { - string name; // Name of the pool + address token; + string metadataUrl; // Name of the pool uint startTime; // Starting time of the vesting period in unix timestamp format uint endTime; // Ending time of the vesting period in unix timestamp format uint vestingDuration; // In seconds @@ -27,13 +27,29 @@ interface ILinearVestingProjectUpgradeable { } /// @notice Event emitted when a new pool is created - event PoolAdded(uint indexed index, address createdBy, string name, uint startTime, uint endTime); + event PoolAdded( + address token, + string metadataUrl, + uint indexed index, + address createdBy, + uint startTime, + uint endTime + ); /// @notice Event emitted when a new grant is created - event GrantAdded(uint indexed poolIndex, address indexed addedBy, address indexed recipient, uint amount); + event GrantAdded( + uint indexed poolIndex, + address indexed addedBy, + address indexed recipient, + uint amount + ); /// @notice Event emitted when tokens are claimed by a recipient from a grant - event GrantClaimed(uint indexed poolIndex, address indexed recipient, uint amountClaimed); + event GrantClaimed( + uint indexed poolIndex, + address indexed recipient, + uint amountClaimed + ); /// @notice Event emitted when metadata url is changed event MetadataUrlChanged(address indexed changedBy, string metadataUrl); @@ -43,21 +59,37 @@ interface ILinearVestingProjectUpgradeable { * @param _startTime starting time of the vesting period in timestamp format * @param _vestingDuration duration time of the vesting period in timestamp format */ - function createPool(string memory _name, uint256 _startTime, uint256 _vestingDuration) external returns (uint256); + function createPool( + address token, + string memory _metadataUrl, + uint256 _startTime, + uint256 _vestingDuration + ) external returns (uint256); /** * @notice Creates a new pool * @param _startTime starting time of the vesting period in timestamp format * @param _vestingDuration duration time of the vesting period in timestamp format */ - function createPoolWithGrants(string memory _name, uint256 _startTime, uint256 _vestingDuration, address[] memory _recipients, uint256[] memory _amounts) external returns (uint256); + function createPoolWithGrants( + address token, + string memory _metadataUrl, + uint256 _startTime, + uint256 _vestingDuration, + address[] memory _recipients, + uint256[] memory _amounts + ) external returns (uint256); /** * @notice Add list of grants in batch. * @param _recipients list of addresses of the stakeholders * @param _amounts list of amounts to be assigned to the stakeholders */ - function addGrants(uint _poolIndex, address[] memory _recipients, uint256[] memory _amounts) external; + function addGrants( + uint _poolIndex, + address[] memory _recipients, + uint256[] memory _amounts + ) external; /** * @notice Calculate the vested and unclaimed tokens available for `recipient` to claim @@ -65,7 +97,10 @@ interface ILinearVestingProjectUpgradeable { * @param _recipient The address that has a grant * @return The amount recipient can claim */ - function calculateGrantClaim(uint _poolIndex, address _recipient) external view returns (uint256); + function calculateGrantClaim( + uint _poolIndex, + address _recipient + ) external view returns (uint256); /** * @notice Allows a grant recipient to claim their vested tokens diff --git a/contracts/linear-vesting/LinearVestingProjectUpgradeable.sol b/contracts/linear-vesting/LinearVestingProjectUpgradeable.sol index 5e96234..0c99eba 100644 --- a/contracts/linear-vesting/LinearVestingProjectUpgradeable.sol +++ b/contracts/linear-vesting/LinearVestingProjectUpgradeable.sol @@ -11,7 +11,10 @@ import "./ILinearVestingProjectUpgradeable.sol"; * @dev Linear Vesting project for linear vesting grants distribution. * Taken from https://github.com/dandelionlabs-io/linear-vesting-contracts/blob/master/contracts/LinearVestingProjectUpgradeable.sol */ -contract LinearVestingProjectUpgradeable is ManageableUpgradeable, ILinearVestingProjectUpgradeable { +contract LinearVestingProjectUpgradeable is + ManageableUpgradeable, + ILinearVestingProjectUpgradeable +{ using SafeMathUpgradeable for uint; /// @dev Used to translate vesting periods specified in days to seconds @@ -33,7 +36,10 @@ contract LinearVestingProjectUpgradeable is ManageableUpgradeable, ILinearVestin * @notice Construct a new Vesting contract * @param _token Address of ERC20 token */ - function __LinearVestingProject_initialize(address _token, string calldata _metadataUrl) external initializer { + function __LinearVestingProject_initialize( + address _token, + string calldata _metadataUrl + ) external initializer { __ManageableUpgradeable_init(); require( @@ -51,7 +57,8 @@ contract LinearVestingProjectUpgradeable is ManageableUpgradeable, ILinearVestin * @param _vestingDuration duration time of the vesting period in timestamp format */ function createPool( - string memory _name, + address _token, + string memory _metadataUrl, uint256 _startTime, uint256 _vestingDuration ) public override onlyManager returns (uint256) { @@ -74,28 +81,44 @@ contract LinearVestingProjectUpgradeable is ManageableUpgradeable, ILinearVestin ); } - pools.push(Pool({ - name: _name, - startTime: _startTime, - vestingDuration: _vestingDuration, - endTime: _startTime.add(_vestingDuration), - amount: 0, - totalClaimed: 0, - grants: 0 - })); + pools.push( + Pool({ + token: _token, + metadataUrl: _metadataUrl, + startTime: _startTime, + vestingDuration: _vestingDuration, + endTime: _startTime.add(_vestingDuration), + amount: 0, + totalClaimed: 0, + grants: 0 + }) + ); - emit PoolAdded(pools.length.sub(1), msg.sender, _name, _startTime, _startTime.add(_vestingDuration)); + emit PoolAdded( + _token, + _metadataUrl, + pools.length.sub(1), + msg.sender, + _startTime, + _startTime.add(_vestingDuration) + ); return pools.length.sub(1); } function createPoolWithGrants( - string memory _name, + address _token, + string memory _metadataUrl, uint256 _startTime, uint256 _vestingDuration, address[] memory _recipients, uint256[] memory _amounts ) public override onlyManager returns (uint) { - uint poolIndex = createPool(_name, _startTime, _vestingDuration); + uint poolIndex = createPool( + _token, + _metadataUrl, + _startTime, + _vestingDuration + ); addGrants(poolIndex, _recipients, _amounts); return poolIndex; } @@ -159,7 +182,12 @@ contract LinearVestingProjectUpgradeable is ManageableUpgradeable, ILinearVestin perSecond: _amounts[i].div(pool.vestingDuration) }); grants[_poolIndex][_recipients[i]] = grant; - emit GrantAdded(_poolIndex, msg.sender, _recipients[i], _amounts[i]); + emit GrantAdded( + _poolIndex, + msg.sender, + _recipients[i], + _amounts[i] + ); } pool.amount = pool.amount.add(amountSum); @@ -172,7 +200,10 @@ contract LinearVestingProjectUpgradeable is ManageableUpgradeable, ILinearVestin * @param _recipient The address that has a grant * @return The amount recipient can claim */ - function calculateGrantClaim(uint _poolIndex, address _recipient) public view override returns (uint256) { + function calculateGrantClaim( + uint _poolIndex, + address _recipient + ) public view override returns (uint256) { require( _poolIndex < pools.length, "LinearVestingProject::calculateGrantClaim: invalid pool index" @@ -241,15 +272,15 @@ contract LinearVestingProjectUpgradeable is ManageableUpgradeable, ILinearVestin Grant storage grant = grants[_poolIndex][msg.sender]; - grant.totalClaimed = uint256( - grant.totalClaimed.add(amountVested) - ); + grant.totalClaimed = uint256(grant.totalClaimed.add(amountVested)); pool.totalClaimed = pool.totalClaimed.add(amountVested); emit GrantClaimed(_poolIndex, msg.sender, amountVested); } - function setMetadataUrl(string calldata _metadataUrl) external override onlyManager { + function setMetadataUrl( + string calldata _metadataUrl + ) external override onlyManager { metadataUrl = _metadataUrl; emit MetadataUrlChanged(msg.sender, _metadataUrl); } diff --git a/hardhat.config.ts b/hardhat.config.ts index 4f50662..047235c 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -1,33 +1,34 @@ import * as dotenv from "dotenv"; import { HardhatUserConfig } from "hardhat/config"; import "@nomicfoundation/hardhat-toolbox"; -import '@openzeppelin/hardhat-upgrades'; +import "@openzeppelin/hardhat-upgrades"; dotenv.config(); const config: HardhatUserConfig = { defaultNetwork: "hardhat", networks: { - hardhat: { - }, + hardhat: {}, local: { - url: 'http://127.0.0.1:7545', - accounts: ['0x0e0e6ca08cc4b4cd0eb43c5b9934f51926b634d0387ecbf7c7c4dd456041db03'] + url: "http://127.0.0.1:7545", + accounts: [ + "0x0e0e6ca08cc4b4cd0eb43c5b9934f51926b634d0387ecbf7c7c4dd456041db03", + ], }, sepolia: { // network: 11155111, - url: 'https://sepolia.infura.io/v3/b280b8aa6cda4dba845afb03d46c2396', - accounts: process.env.DEPLOYER_KEY ? [process.env.DEPLOYER_KEY] : [] + url: "https://sepolia.infura.io/v3/b280b8aa6cda4dba845afb03d46c2396", + accounts: process.env.DEPLOYER_KEY ? [process.env.DEPLOYER_KEY] : [], }, mumbai: { - url: 'https://polygon-mumbai.infura.io/v3/b280b8aa6cda4dba845afb03d46c2396', - accounts: process.env.DEPLOYER_KEY ? [process.env.DEPLOYER_KEY] : [] + url: "https://polygon-mumbai.infura.io/v3/b280b8aa6cda4dba845afb03d46c2396", + accounts: process.env.DEPLOYER_KEY ? [process.env.DEPLOYER_KEY] : [], }, mainnet: { // network: 1, - url: process.env.RPC_URL || '', - accounts: process.env.DEPLOYER_KEY ? [process.env.DEPLOYER_KEY] : [] - } + url: process.env.RPC_URL || "", + accounts: process.env.DEPLOYER_KEY ? [process.env.DEPLOYER_KEY] : [], + }, }, solidity: { version: "0.8.4", @@ -39,19 +40,19 @@ const config: HardhatUserConfig = { }, }, gasReporter: { - coinmarketcap: 'b5aa6c8e-e54a-4291-b372-6afac549d9fc', - currency: 'USD', + coinmarketcap: "b5aa6c8e-e54a-4291-b372-6afac549d9fc", + currency: "USD", enabled: true, - token: 'ETH' + token: "ETH", }, etherscan: { apiKey: { - mainnet: 'T3UIPRSIK9Q776Y87B48YWCIFC6EV71B96', - sepolia: 'T3UIPRSIK9Q776Y87B48YWCIFC6EV71B96', - mumbai: 'X1X9XZVFXI3S235YQGQ7A99CG648ADCUHF', - matic: 'X1X9XZVFXI3S235YQGQ7A99CG648ADCUHF', - } - } + mainnet: "T3UIPRSIK9Q776Y87B48YWCIFC6EV71B96", + sepolia: "T3UIPRSIK9Q776Y87B48YWCIFC6EV71B96", + mumbai: "X1X9XZVFXI3S235YQGQ7A99CG648ADCUHF", + matic: "X1X9XZVFXI3S235YQGQ7A99CG648ADCUHF", + }, + }, }; export default config; diff --git a/test/linear-vesting/pools.ts b/test/linear-vesting/pools.ts index 5f8df74..747c8e2 100644 --- a/test/linear-vesting/pools.ts +++ b/test/linear-vesting/pools.ts @@ -1,54 +1,84 @@ -import {loadFixture} from "@nomicfoundation/hardhat-network-helpers"; +import { loadFixture } from "@nomicfoundation/hardhat-network-helpers"; import { expect } from "chai"; -import {deployProjectFactoryWithProject} from "./util/fixtures"; -import {BigNumber} from "ethers"; -import {getLastBlockTimestamp} from "./util/time"; +import { deployProjectFactoryWithProject } from "./util/fixtures"; +import { BigNumber } from "ethers"; +import { getLastBlockTimestamp } from "./util/time"; describe("Pools creation", function () { - - const poolVestingTime = 100 - const poolName = 'Pool 1' + const poolVestingTime = 100; + const poolName = "Pool 1"; let amount, projectFactory, erc20, project, owner, manager, user1, user2; beforeEach(async () => { - ({projectFactory, erc20, project, owner, manager, user1, user2 } = await loadFixture(deployProjectFactoryWithProject)); + ({ projectFactory, erc20, project, owner, manager, user1, user2 } = + await loadFixture(deployProjectFactoryWithProject)); }); it("create pool with grants should work for manager", async function () { - const poolVestingTime = 100 - const amount = BigNumber.from("100000000000000000000") - const poolName = 'Pool 1' + const poolVestingTime = 100; + const amount = BigNumber.from("100000000000000000000"); + const poolName = "Pool 1"; + const token = erc20.address; - const allowTx = await erc20.increaseAllowance(project.address, amount) - await allowTx.wait() + const allowTx = await erc20.increaseAllowance(project.address, amount); + await allowTx.wait(); const currentTimeStamp = await getLastBlockTimestamp(); - let startingPeriodTime = currentTimeStamp + 100 + let startingPeriodTime = currentTimeStamp + 100; await expect( - project.createPoolWithGrants(poolName, startingPeriodTime, poolVestingTime, [user1.address], [amount]) - ).to.emit(project, "PoolAdded").withArgs(0, owner.address, poolName, startingPeriodTime, startingPeriodTime + poolVestingTime) + project.createPoolWithGrants( + token, + poolName, + startingPeriodTime, + poolVestingTime, + [user1.address], + [amount] + ) + ) + .to.emit(project, "PoolAdded") + .withArgs( + token, + poolName, + 0, + owner.address, + startingPeriodTime, + startingPeriodTime + poolVestingTime + ); }); it("create pool without grants should work for manager", async function () { - const currentTimeStamp = await getLastBlockTimestamp(); - let startingPeriodTime = currentTimeStamp + 100 + let startingPeriodTime = currentTimeStamp + 100; + let token = erc20.address; await expect( - project.createPool(poolName, startingPeriodTime, poolVestingTime) - ).to.emit(project, "PoolAdded").withArgs(0, owner.address, poolName, startingPeriodTime, startingPeriodTime + poolVestingTime) + project.createPool(token, poolName, startingPeriodTime, poolVestingTime) + ) + .to.emit(project, "PoolAdded") + .withArgs( + token, + poolName, + 0, + owner.address, + startingPeriodTime, + startingPeriodTime + poolVestingTime + ); }); it("user without manager role create pool should fail", async function () { - const currentTimeStamp = await getLastBlockTimestamp(); - let startingPeriodTime = currentTimeStamp + 100 + let startingPeriodTime = currentTimeStamp + 100; + let token = erc20.address; await expect( - project.connect(user1).createPool(poolName, startingPeriodTime, poolVestingTime) - ).to.rejectedWith("ManageableUpgradeable::onlyManager: the caller is not an manager."); + project + .connect(user1) + .createPool(token, poolName, startingPeriodTime, poolVestingTime) + ).to.rejectedWith( + "ManageableUpgradeable::onlyManager: the caller is not an manager." + ); }); }); diff --git a/test/linear-vesting/util/fixtures.ts b/test/linear-vesting/util/fixtures.ts index 6ad237e..25aaa43 100644 --- a/test/linear-vesting/util/fixtures.ts +++ b/test/linear-vesting/util/fixtures.ts @@ -1,17 +1,25 @@ import { ethers, upgrades } from "hardhat"; -import {getLastBlockTimestamp} from "./time"; -import {BigNumber} from "ethers"; +import { getLastBlockTimestamp } from "./time"; +import { BigNumber } from "ethers"; async function deployProjectFactory() { const [owner, manager, user1, user2] = await ethers.getSigners(); - const LinearVestingProjectRemovableUpgradeable = await ethers.getContractFactory("LinearVestingProjectRemovableUpgradeable"); - const LinearVestingProjectFactoryUpgradeable = await ethers.getContractFactory("LinearVestingProjectFactoryUpgradeable"); + const LinearVestingProjectRemovableUpgradeable = + await ethers.getContractFactory("LinearVestingProjectRemovableUpgradeable"); + const LinearVestingProjectFactoryUpgradeable = + await ethers.getContractFactory("LinearVestingProjectFactoryUpgradeable"); - const projectBase = await LinearVestingProjectRemovableUpgradeable.connect(owner).deploy(); + const projectBase = await LinearVestingProjectRemovableUpgradeable.connect( + owner + ).deploy(); - const projectFactory = await upgrades.deployProxy(LinearVestingProjectFactoryUpgradeable, [projectBase.address], { initializer: '__LinearVestingProjectFactory_initialize' }); - await projectFactory.deployed() + const projectFactory = await upgrades.deployProxy( + LinearVestingProjectFactoryUpgradeable, + [projectBase.address], + { initializer: "__LinearVestingProjectFactory_initialize" } + ); + await projectFactory.deployed(); const ERC20 = await ethers.getContractFactory("ERC20Mock"); @@ -21,40 +29,87 @@ async function deployProjectFactory() { return { projectFactory, projectBase, erc20, owner, manager, user1, user2 }; } - async function deployProjectFactoryWithProject() { - const { projectFactory, projectBase, owner, erc20, manager, user1, user2 } = await deployProjectFactory() - - const LinearVestingProjectRemovableUpgradeable = await ethers.getContractFactory("LinearVestingProjectRemovableUpgradeable"); - - - const tx = await projectFactory.createProject(erc20.address, 'Project Example', 'asd') - await tx.wait(1) - - const address = await projectFactory.getProjectAddress(0) - - const project = LinearVestingProjectRemovableUpgradeable.attach(address) - - return { projectFactory, projectBase, project, erc20, owner, manager, user1, user2 }; + const { projectFactory, projectBase, owner, erc20, manager, user1, user2 } = + await deployProjectFactory(); + + const LinearVestingProjectRemovableUpgradeable = + await ethers.getContractFactory("LinearVestingProjectRemovableUpgradeable"); + + const tx = await projectFactory.createProject( + erc20.address, + "Project Example", + "asd" + ); + await tx.wait(1); + + const address = await projectFactory.getProjectAddress(0); + + const project = LinearVestingProjectRemovableUpgradeable.attach(address); + + return { + projectFactory, + projectBase, + project, + erc20, + owner, + manager, + user1, + user2, + }; } async function deployProjectFactoryWithProjectAndPool() { - const { projectFactory, projectBase, owner, project, erc20, manager, user1, user2 } = await deployProjectFactoryWithProject() - - const poolVestingTime = 100 - const amount = BigNumber.from("100000000000000000000") - - const allowTx = await erc20.increaseAllowance(project.address, amount) - await allowTx.wait() + const { + projectFactory, + projectBase, + owner, + project, + erc20, + manager, + user1, + user2, + } = await deployProjectFactoryWithProject(); + + const poolVestingTime = 100; + const amount = BigNumber.from("100000000000000000000"); + const token = erc20.address; + + const allowTx = await erc20.increaseAllowance(project.address, amount); + await allowTx.wait(); const currentTimeStamp = await getLastBlockTimestamp(); - let startingPeriodTime = currentTimeStamp + 100 - - let tx = await project.createPoolWithGrants('Pool 1', startingPeriodTime, poolVestingTime, [user1.address], [amount]); - await tx.wait() - - return { projectFactory, projectBase, project, erc20, owner, manager, user1, user2, poolVestingTime, amount, startingPeriodTime }; + let startingPeriodTime = currentTimeStamp + 100; + + let tx = await project.createPoolWithGrants( + token, + "Pool 1", + startingPeriodTime, + poolVestingTime, + [user1.address], + [amount] + ); + await tx.wait(); + + return { + projectFactory, + projectBase, + project, + erc20, + owner, + manager, + user1, + user2, + token, + startingPeriodTime, + poolVestingTime, + amount, + }; } -export { deployProjectFactory, deployProjectFactoryWithProject, deployProjectFactoryWithProjectAndPool }; +export { + deployProjectFactory, + deployProjectFactoryWithProject, + deployProjectFactoryWithProjectAndPool, +}; From a5ae3be038be241b5348898b02029de0aa2b4e50 Mon Sep 17 00:00:00 2001 From: jon-vdb Date: Mon, 29 May 2023 10:48:09 +0700 Subject: [PATCH 2/4] Renamed 'Pool'. Now 'Vesting', poolIndex now vestingIndex, tests passing --- ...map-LinearVestingProjectUpgradeable.drawio | 845 ++++++++++++++++++ contracts-layout.drawio | 65 ++ ... ITestLinearVestingProjectUpgradeable.sol} | 22 +- ...LinearVestingProjectFactoryUpgradeable.sol | 21 +- ...rVestingProjectTransferableUpgradeable.sol | 38 +- ...> TestLinearVestingProjectUpgradeable.sol} | 112 +-- ...nearVestingProjectRemovableUpgradeable.sol | 15 +- ...nearVestingProjectRemovableUpgradeable.sol | 19 +- proxy-beacon-map-diagram.drawio | 74 ++ scripts/deploy-test.ts | 69 +- test/linear-vesting/claims.ts | 61 +- test/linear-vesting/factory.ts | 42 +- test/linear-vesting/grants.ts | 120 +-- test/linear-vesting/pools.ts | 22 +- test/linear-vesting/util/fixtures.ts | 4 +- 15 files changed, 1300 insertions(+), 229 deletions(-) create mode 100644 contract-map-LinearVestingProjectUpgradeable.drawio create mode 100644 contracts-layout.drawio rename contracts/linear-vesting/{ILinearVestingProjectUpgradeable.sol => ITestLinearVestingProjectUpgradeable.sol} (89%) rename contracts/linear-vesting/{LinearVestingProjectUpgradeable.sol => TestLinearVestingProjectUpgradeable.sol} (70%) create mode 100644 proxy-beacon-map-diagram.drawio diff --git a/contract-map-LinearVestingProjectUpgradeable.drawio b/contract-map-LinearVestingProjectUpgradeable.drawio new file mode 100644 index 0000000..4a5e946 --- /dev/null +++ b/contract-map-LinearVestingProjectUpgradeable.drawio @@ -0,0 +1,845 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/contracts-layout.drawio b/contracts-layout.drawio new file mode 100644 index 0000000..32bfc8e --- /dev/null +++ b/contracts-layout.drawio @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/contracts/linear-vesting/ILinearVestingProjectUpgradeable.sol b/contracts/linear-vesting/ITestLinearVestingProjectUpgradeable.sol similarity index 89% rename from contracts/linear-vesting/ILinearVestingProjectUpgradeable.sol rename to contracts/linear-vesting/ITestLinearVestingProjectUpgradeable.sol index ac8c6a1..2c657ed 100644 --- a/contracts/linear-vesting/ILinearVestingProjectUpgradeable.sol +++ b/contracts/linear-vesting/ITestLinearVestingProjectUpgradeable.sol @@ -6,9 +6,9 @@ pragma solidity ^0.8.4; * @dev Linear Vesting project for linear vesting grants distribution. * Taken from https://github.com/dandelionlabs-io/linear-vesting-contracts/blob/master/contracts/linear-vesting/ILinearVestingProjectUpgradeable.sol */ -interface ILinearVestingProjectUpgradeable { +interface ITestLinearVestingProjectUpgradeable { /// @notice Pool definition - struct Pool { + struct VestingSchedule { address token; string metadataUrl; // Name of the pool uint startTime; // Starting time of the vesting period in unix timestamp format @@ -27,7 +27,7 @@ interface ILinearVestingProjectUpgradeable { } /// @notice Event emitted when a new pool is created - event PoolAdded( + event VestingScheduleAdded( address token, string metadataUrl, uint indexed index, @@ -38,7 +38,7 @@ interface ILinearVestingProjectUpgradeable { /// @notice Event emitted when a new grant is created event GrantAdded( - uint indexed poolIndex, + uint indexed vestingIndex, address indexed addedBy, address indexed recipient, uint amount @@ -46,7 +46,7 @@ interface ILinearVestingProjectUpgradeable { /// @notice Event emitted when tokens are claimed by a recipient from a grant event GrantClaimed( - uint indexed poolIndex, + uint indexed vestingIndex, address indexed recipient, uint amountClaimed ); @@ -59,7 +59,7 @@ interface ILinearVestingProjectUpgradeable { * @param _startTime starting time of the vesting period in timestamp format * @param _vestingDuration duration time of the vesting period in timestamp format */ - function createPool( + function createVestingSchedule( address token, string memory _metadataUrl, uint256 _startTime, @@ -71,7 +71,7 @@ interface ILinearVestingProjectUpgradeable { * @param _startTime starting time of the vesting period in timestamp format * @param _vestingDuration duration time of the vesting period in timestamp format */ - function createPoolWithGrants( + function createVestingScheduleWithGrants( address token, string memory _metadataUrl, uint256 _startTime, @@ -86,7 +86,7 @@ interface ILinearVestingProjectUpgradeable { * @param _amounts list of amounts to be assigned to the stakeholders */ function addGrants( - uint _poolIndex, + uint _vestingIndex, address[] memory _recipients, uint256[] memory _amounts ) external; @@ -98,7 +98,7 @@ interface ILinearVestingProjectUpgradeable { * @return The amount recipient can claim */ function calculateGrantClaim( - uint _poolIndex, + uint _vestingIndex, address _recipient ) external view returns (uint256); @@ -107,14 +107,14 @@ interface ILinearVestingProjectUpgradeable { * @dev Errors if no tokens have vested * @dev It is advised recipients check they are entitled to claim via `calculateGrantClaim` before calling this */ - function claimVestedTokens(uint _poolIndex) external; + function claimVestedTokens(uint _vestingIndex) external; /** * @notice Allows a grant recipient to claim multiple vested tokens * @dev Errors if no tokens have vested * @dev It is advised recipients check they are entitled to claim via `calculateGrantClaim` before calling this */ - function claimMultiplePools(uint[] memory _poolIndexes) external; + function claimMultipleVestings(uint[] memory _vestingIndexes) external; function setMetadataUrl(string memory _metadata) external; } diff --git a/contracts/linear-vesting/LinearVestingProjectFactoryUpgradeable.sol b/contracts/linear-vesting/LinearVestingProjectFactoryUpgradeable.sol index a2a9f73..7c38dd9 100644 --- a/contracts/linear-vesting/LinearVestingProjectFactoryUpgradeable.sol +++ b/contracts/linear-vesting/LinearVestingProjectFactoryUpgradeable.sol @@ -4,17 +4,16 @@ pragma solidity ^0.8.0; import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; import "@openzeppelin/contracts/proxy/beacon/BeaconProxy.sol"; -import "./LinearVestingProjectUpgradeable.sol"; +import "./TestLinearVestingProjectUpgradeable.sol"; import "./LinearVestingProjectBeacon.sol"; contract LinearVestingProjectFactoryUpgradeable is OwnableUpgradeable { - address[] private projects; uint public projectsCount; LinearVestingProjectBeacon beacon; /** - * @dev Emitted when a new project is created. + * @dev Emitted when a new project is created. */ event ProjectCreated( uint indexed index, @@ -24,12 +23,16 @@ contract LinearVestingProjectFactoryUpgradeable is OwnableUpgradeable { string metadataUrl ); - function __LinearVestingProjectFactory_initialize(address _initBlueprint) public initializer onlyInitializing { + function __LinearVestingProjectFactory_initialize( + address _initBlueprint + ) public initializer onlyInitializing { __Ownable_init(); __LinearVestingProjectFactory_initialize_unchained(_initBlueprint); } - function __LinearVestingProjectFactory_initialize_unchained(address _initBlueprint) public onlyInitializing { + function __LinearVestingProjectFactory_initialize_unchained( + address _initBlueprint + ) public onlyInitializing { beacon = new LinearVestingProjectBeacon(_initBlueprint); } @@ -41,7 +44,9 @@ contract LinearVestingProjectFactoryUpgradeable is OwnableUpgradeable { BeaconProxy project = new BeaconProxy( address(beacon), abi.encodeWithSelector( - LinearVestingProjectUpgradeable(address(0)).__LinearVestingProject_initialize.selector, + TestLinearVestingProjectUpgradeable(address(0)) + .__LinearVestingProject_initialize + .selector, _token, _metadataUrl ) @@ -69,7 +74,9 @@ contract LinearVestingProjectFactoryUpgradeable is OwnableUpgradeable { return address(beacon); } - function getProjectAddress(uint projectIndex) external view returns (address) { + function getProjectAddress( + uint projectIndex + ) external view returns (address) { return projects[projectIndex]; } diff --git a/contracts/linear-vesting/LinearVestingProjectTransferableUpgradeable.sol b/contracts/linear-vesting/LinearVestingProjectTransferableUpgradeable.sol index d6a19a1..ae638ea 100644 --- a/contracts/linear-vesting/LinearVestingProjectTransferableUpgradeable.sol +++ b/contracts/linear-vesting/LinearVestingProjectTransferableUpgradeable.sol @@ -1,18 +1,18 @@ -//// SPDX-License-Identifier: MIT -//pragma solidity ^0.8.4; -// -//import "./LinearVestingProjectUpgradeable.sol"; -// -//abstract contract LinearVestingProjectTransferableUpgradeable is LinearVestingProjectUpgradeable { +// // SPDX-License-Identifier: MIT +// pragma solidity ^0.8.4; + +// import "./LinearVestingProjectUpgradeable.sol"; + +// abstract contract LinearVestingProjectTransferableUpgradeable is LinearVestingProjectUpgradeable { // using SafeMathUpgradeable for uint256; -// + // /// @notice Event emitted when the grant investor is changed // event GrantChanged(address indexed oldOwner, address indexed newOwner); -// + // /// @notice List of investors who got blacklist tokens. // /// @dev Structure of the map: investor => new address // mapping(address => address) public blacklist; -// + // /** // * @notice In case if the user doesn't want to change the grant. // * @param _oldAddress existing address from the investor which we want to change @@ -38,19 +38,19 @@ // tokenGrants[_oldAddress].amount > 0, // "VestingPeriod::changeInvestor: oldAddress has no remaining balance" // ); -// + // tokenGrants[_newAddress] = Grant( // tokenGrants[_oldAddress].amount, // tokenGrants[_oldAddress].totalClaimed, // tokenGrants[_oldAddress].perSecond // ); // delete tokenGrants[_oldAddress]; -// + // blacklist[_oldAddress] = _newAddress; -// + // emit GrantChanged(_oldAddress, _newAddress); // } -// + // function addTokenGrants( // address[] memory _recipients, // uint256[] memory _amounts @@ -67,7 +67,7 @@ // _recipients.length == _amounts.length, // "VestingPeriod::addTokenGrants: invalid parameters length (they should be same)" // ); -// + // uint256 amountSum = 0; // for (uint16 i = 0; i < _recipients.length; i++) { // require( @@ -82,20 +82,20 @@ // blacklist[_recipients[i]] == address(0), // "VestingPeriod:addTOkenGrants: Blacklisted address" // ); -// + // require( // _amounts[i] > 0, // "VestingPeriod::addTokenGrant: amount == 0" // ); // amountSum = amountSum.add(_amounts[i]); // } -// + // // Transfer the grant tokens under the control of the vesting contract // require( // token.transferFrom(msg.sender, address(this), amountSum), // "VestingPeriod::addTokenGrants: transfer failed" // ); -// + // for (uint16 i = 0; i < _recipients.length; i++) { // Grant memory grant = Grant({ // amount: _amounts[i], @@ -105,7 +105,7 @@ // tokenGrants[_recipients[i]] = grant; // emit GrantAdded(_recipients[i], _amounts[i]); // } -// + // pool.amount = pool.amount.add(amountSum); // } -//} +// } diff --git a/contracts/linear-vesting/LinearVestingProjectUpgradeable.sol b/contracts/linear-vesting/TestLinearVestingProjectUpgradeable.sol similarity index 70% rename from contracts/linear-vesting/LinearVestingProjectUpgradeable.sol rename to contracts/linear-vesting/TestLinearVestingProjectUpgradeable.sol index 0c99eba..da86efc 100644 --- a/contracts/linear-vesting/LinearVestingProjectUpgradeable.sol +++ b/contracts/linear-vesting/TestLinearVestingProjectUpgradeable.sol @@ -4,16 +4,16 @@ pragma solidity ^0.8.4; import "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol"; import "@openzeppelin/contracts-upgradeable/utils/math/SafeMathUpgradeable.sol"; import "../access/ManageableUpgradeable.sol"; -import "./ILinearVestingProjectUpgradeable.sol"; +import "./ITestLinearVestingProjectUpgradeable.sol"; /** * @title LinearVestingProjectUpgradeable * @dev Linear Vesting project for linear vesting grants distribution. * Taken from https://github.com/dandelionlabs-io/linear-vesting-contracts/blob/master/contracts/LinearVestingProjectUpgradeable.sol */ -contract LinearVestingProjectUpgradeable is +contract TestLinearVestingProjectUpgradeable is ManageableUpgradeable, - ILinearVestingProjectUpgradeable + ITestLinearVestingProjectUpgradeable { using SafeMathUpgradeable for uint; @@ -23,8 +23,8 @@ contract LinearVestingProjectUpgradeable is /// @notice ERC20 token IERC20Upgradeable public token; - /// @dev Each Linear Vesting project has many pools - Pool[] public pools; + /// @dev Each Organization has many vesting schedules + VestingSchedule[] public vestingSchedules; /// @notice Mapping of recipient address > token grant mapping(uint => mapping(address => Grant)) public grants; @@ -56,7 +56,7 @@ contract LinearVestingProjectUpgradeable is * @param _startTime starting time of the vesting period in timestamp format * @param _vestingDuration duration time of the vesting period in timestamp format */ - function createPool( + function createVestingSchedule( address _token, string memory _metadataUrl, uint256 _startTime, @@ -64,25 +64,27 @@ contract LinearVestingProjectUpgradeable is ) public override onlyManager returns (uint256) { require( _startTime > 0 && _vestingDuration > 0, - "LinearVestingProject::createPool: One of the time parameters is 0" + "LinearVestingProject::createVestingSchedule: One of the time parameters is 0" ); require( _startTime > block.timestamp, - "LinearVestingProject::createPool: Starting time shall be in a future time" + "LinearVestingProject::createVestingSchedule: Starting time shall be in a future time" ); require( _vestingDuration > 0, - "LinearVestingProject::createPool: Duration of the period must be > 0" + "LinearVestingProject::createVestingSchedule: Duration of the period must be > 0" ); if (_vestingDuration < SECONDS_PER_DAY) { require( _vestingDuration <= SECONDS_PER_DAY.mul(10).mul(365), - "LinearVestingProject::createPool: Duration should be less than 10 years" + "LinearVestingProject::createVestingSchedule: Duration should be less than 10 years" ); } - pools.push( - Pool({ + uint256 vestingIndex = vestingSchedules.length; + + vestingSchedules.push( + VestingSchedule({ token: _token, metadataUrl: _metadataUrl, startTime: _startTime, @@ -94,18 +96,18 @@ contract LinearVestingProjectUpgradeable is }) ); - emit PoolAdded( + emit VestingScheduleAdded( _token, _metadataUrl, - pools.length.sub(1), + vestingIndex, msg.sender, _startTime, _startTime.add(_vestingDuration) ); - return pools.length.sub(1); + return vestingIndex; } - function createPoolWithGrants( + function createVestingScheduleWithGrants( address _token, string memory _metadataUrl, uint256 _startTime, @@ -113,14 +115,14 @@ contract LinearVestingProjectUpgradeable is address[] memory _recipients, uint256[] memory _amounts ) public override onlyManager returns (uint) { - uint poolIndex = createPool( + uint vestingIndex = createVestingSchedule( _token, _metadataUrl, _startTime, _vestingDuration ); - addGrants(poolIndex, _recipients, _amounts); - return poolIndex; + addGrants(vestingIndex, _recipients, _amounts); + return vestingIndex; } /** @@ -129,7 +131,7 @@ contract LinearVestingProjectUpgradeable is * @param _amounts list of amounts to be assigned to the stakeholders */ function addGrants( - uint _poolIndex, + uint _vestingIndex, address[] memory _recipients, uint256[] memory _amounts ) public virtual override onlyManager { @@ -146,11 +148,11 @@ contract LinearVestingProjectUpgradeable is "LinearVestingProject::addTokenGrants: invalid parameters length (they should be same)" ); require( - _poolIndex < pools.length, + _vestingIndex < vestingSchedules.length, "LinearVestingProject::addTokenGrants: invalid pool index" ); - Pool memory pool = pools[_poolIndex]; + VestingSchedule memory vesting = vestingSchedules[_vestingIndex]; uint256 amountSum = 0; for (uint16 i = 0; i < _recipients.length; i++) { @@ -159,7 +161,7 @@ contract LinearVestingProjectUpgradeable is "LinearVestingProject:addTokenGrants: there is an address with value 0" ); require( - grants[_poolIndex][_recipients[i]].amount == 0, + grants[_vestingIndex][_recipients[i]].totalClaimed == 0, "LinearVestingProject::addTokenGrants: a grant already exists for one of the accounts" ); require( @@ -179,19 +181,19 @@ contract LinearVestingProjectUpgradeable is Grant memory grant = Grant({ amount: _amounts[i], totalClaimed: 0, - perSecond: _amounts[i].div(pool.vestingDuration) + perSecond: _amounts[i].div(vesting.vestingDuration) }); - grants[_poolIndex][_recipients[i]] = grant; + grants[_vestingIndex][_recipients[i]] = grant; emit GrantAdded( - _poolIndex, + _vestingIndex, msg.sender, _recipients[i], _amounts[i] ); } - pool.amount = pool.amount.add(amountSum); - pool.grants = pool.grants.add(_recipients.length); + vesting.amount = vesting.amount.add(amountSum); + vesting.grants = vesting.grants.add(_recipients.length); } /** @@ -201,37 +203,37 @@ contract LinearVestingProjectUpgradeable is * @return The amount recipient can claim */ function calculateGrantClaim( - uint _poolIndex, + uint _vestingIndex, address _recipient ) public view override returns (uint256) { require( - _poolIndex < pools.length, + _vestingIndex < vestingSchedules.length, "LinearVestingProject::calculateGrantClaim: invalid pool index" ); - Pool memory pool = pools[_poolIndex]; + VestingSchedule memory vesting = vestingSchedules[_vestingIndex]; // For grants created with a future start date, that hasn't been reached, return 0, 0 - if (block.timestamp < pool.startTime) { + if (block.timestamp < vesting.startTime) { return 0; } uint256 cap = block.timestamp; - if (cap > pool.endTime) { - cap = pool.endTime; + if (cap > vesting.endTime) { + cap = vesting.endTime; } - uint256 elapsedTime = cap.sub(pool.startTime); + uint256 elapsedTime = cap.sub(vesting.startTime); // If over vesting duration, all tokens vested - if (elapsedTime >= pool.vestingDuration) { - uint256 remainingGrant = grants[_poolIndex][_recipient].amount.sub( - grants[_poolIndex][_recipient].totalClaimed - ); + if (elapsedTime >= vesting.vestingDuration) { + uint256 remainingGrant = grants[_vestingIndex][_recipient] + .totalClaimed + .sub(grants[_vestingIndex][_recipient].amount); return remainingGrant; } else { - uint256 amountVested = grants[_poolIndex][_recipient].perSecond.mul( - elapsedTime - ); + uint256 amountVested = grants[_vestingIndex][_recipient] + .perSecond + .mul(elapsedTime); uint256 claimableAmount = amountVested.sub( - grants[_poolIndex][_recipient].totalClaimed + grants[_vestingIndex][_recipient].totalClaimed ); return claimableAmount; } @@ -242,9 +244,11 @@ contract LinearVestingProjectUpgradeable is * @dev Errors if no tokens have vested * @dev It is advised recipients check they are entitled to claim via `calculateGrantClaim` before calling this */ - function claimMultiplePools(uint[] memory _poolIndexes) external override { - for (uint i = 0; i < _poolIndexes.length; i++) { - claimVestedTokens(_poolIndexes[i]); + function claimMultipleVestings( + uint[] memory _vestingIndexes + ) external override { + for (uint i = 0; i < _vestingIndexes.length; i++) { + claimVestedTokens(_vestingIndexes[i]); } } @@ -253,14 +257,14 @@ contract LinearVestingProjectUpgradeable is * @dev Errors if no tokens have vested * @dev It is advised recipients check they are entitled to claim via `calculateGrantClaim` before calling this */ - function claimVestedTokens(uint _poolIndex) public override { + function claimVestedTokens(uint _vestingIndex) public override { require( - _poolIndex < pools.length, - "LinearVestingProject::calculateGrantClaim: invalid pool index" + _vestingIndex < vestingSchedules.length, + "LinearVestingProject::calculateGrantClaim: invalid vesting index" ); - Pool memory pool = pools[_poolIndex]; + VestingSchedule memory vesting = vestingSchedules[_vestingIndex]; - uint256 amountVested = calculateGrantClaim(_poolIndex, msg.sender); + uint256 amountVested = calculateGrantClaim(_vestingIndex, msg.sender); require( amountVested > 0, "VestingPeriod::claimVestedTokens: amountVested is 0" @@ -270,12 +274,12 @@ contract LinearVestingProjectUpgradeable is "VestingPeriod::claimVestedTokens: transfer failed" ); - Grant storage grant = grants[_poolIndex][msg.sender]; + Grant storage grant = grants[_vestingIndex][msg.sender]; grant.totalClaimed = uint256(grant.totalClaimed.add(amountVested)); - pool.totalClaimed = pool.totalClaimed.add(amountVested); + vesting.totalClaimed = vesting.totalClaimed.add(amountVested); - emit GrantClaimed(_poolIndex, msg.sender, amountVested); + emit GrantClaimed(_vestingIndex, msg.sender, amountVested); } function setMetadataUrl( diff --git a/contracts/linear-vesting/removable/ILinearVestingProjectRemovableUpgradeable.sol b/contracts/linear-vesting/removable/ILinearVestingProjectRemovableUpgradeable.sol index ce02ece..f37b3fc 100644 --- a/contracts/linear-vesting/removable/ILinearVestingProjectRemovableUpgradeable.sol +++ b/contracts/linear-vesting/removable/ILinearVestingProjectRemovableUpgradeable.sol @@ -1,20 +1,25 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.4; -import "../ILinearVestingProjectUpgradeable.sol"; +import "../ITestLinearVestingProjectUpgradeable.sol"; /** * @title ILinearVestingProjectRemovableUpgradeable interface * @dev Linear Vesting project for linear vesting grants distribution. * Taken from https://github.com/dandelionlabs-io/linear-vesting-contracts/blob/master/contracts/linear-vesting/ILinearVestingProjectRemovableUpgradeable.sol */ -interface ILinearVestingProjectRemovableUpgradeable is ILinearVestingProjectUpgradeable { - +interface ILinearVestingProjectRemovableUpgradeable is + ITestLinearVestingProjectUpgradeable +{ /// @notice Event emitted when the grant stakeholder is deleted - event GrantRemoved(uint indexed poolIndex, address indexed removedBy, address indexed recipient); + event GrantRemoved( + uint indexed vestingIndex, + address indexed removedBy, + address indexed recipient + ); /** * @notice Deletes a grant from a pool by Id, refunds the remaining tokens from a grant. * @param _address existing address from the investor which we want to delete */ - function removeGrant(uint _poolIndex, address _address) external; + function removeGrant(uint _vestingIndex, address _address) external; } diff --git a/contracts/linear-vesting/removable/LinearVestingProjectRemovableUpgradeable.sol b/contracts/linear-vesting/removable/LinearVestingProjectRemovableUpgradeable.sol index 4c80e23..9585489 100644 --- a/contracts/linear-vesting/removable/LinearVestingProjectRemovableUpgradeable.sol +++ b/contracts/linear-vesting/removable/LinearVestingProjectRemovableUpgradeable.sol @@ -1,30 +1,35 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.4; -import "../LinearVestingProjectUpgradeable.sol"; +import "../TestLinearVestingProjectUpgradeable.sol"; import "./ILinearVestingProjectRemovableUpgradeable.sol"; -contract LinearVestingProjectRemovableUpgradeable is LinearVestingProjectUpgradeable, ILinearVestingProjectRemovableUpgradeable { - +contract LinearVestingProjectRemovableUpgradeable is + TestLinearVestingProjectUpgradeable, + ILinearVestingProjectRemovableUpgradeable +{ /** * @notice Deletes a grant from a pool by Id, refunds the remaining tokens from a grant. * @param _address existing address from the investor which we want to delete */ - function removeGrant(uint _poolIndex, address _address) external override onlyManager { - Grant memory grant = grants[_poolIndex][_address]; + function removeGrant( + uint _vestingIndex, + address _address + ) external override onlyManager { + Grant memory grant = grants[_vestingIndex][_address]; require( grant.amount > 0, "LinearVestingProjectRemovable::removeGrant: grant not active" ); uint256 refund = grant.amount - grant.totalClaimed; - delete grants[_poolIndex][_address]; + delete grants[_vestingIndex][_address]; require( token.transfer(owner(), refund), "LinearVestingProjectRemovable::removeGrant: transfer failed" ); - emit GrantRemoved(_poolIndex, msg.sender, _address); + emit GrantRemoved(_vestingIndex, msg.sender, _address); } } diff --git a/proxy-beacon-map-diagram.drawio b/proxy-beacon-map-diagram.drawio new file mode 100644 index 0000000..dfd2708 --- /dev/null +++ b/proxy-beacon-map-diagram.drawio @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/scripts/deploy-test.ts b/scripts/deploy-test.ts index 3d7bddf..80d03e7 100644 --- a/scripts/deploy-test.ts +++ b/scripts/deploy-test.ts @@ -1,35 +1,48 @@ -import {ethers, upgrades} from "hardhat"; +import { ethers, upgrades } from "hardhat"; -require('dotenv').config() +require("dotenv").config(); async function main() { - - const LinearVestingProjectUpgradeable = await ethers.getContractFactory("LinearVestingProjectUpgradeable"); - const LinearVestingProjectFactoryUpgradeable = await ethers.getContractFactory("LinearVestingProjectFactoryUpgradeable"); - const ERC20 = await ethers.getContractFactory("ERC20Mock"); - - const erc20 = await ERC20.deploy(); - await erc20.deployed(); - - console.log("ERC20 Mock token deployed to:", erc20.address); - - const projectBase = await LinearVestingProjectUpgradeable.deploy(); - - console.log("Vesting Project for beacon proxy deployed to:", projectBase.address); - - const projectFactory = await upgrades.deployProxy(LinearVestingProjectFactoryUpgradeable, [projectBase.address], {initializer: '__LinearVestingProjectFactory_initialize'}); - await projectFactory.deployed() - - console.log("Vesting Project Factory deployed to:", projectFactory.address); - - let tx = await projectFactory.create(erc20.address); - await tx.wait() - - const linearVestingProjectAddress = await projectFactory.getCollectionAddress(0) - console.log(`New linear vesting project deployed to ${linearVestingProjectAddress}`); + const LinearVestingProjectUpgradeable = await ethers.getContractFactory( + "TestLinearVestingProjectUpgradeable" + ); + const LinearVestingProjectFactoryUpgradeable = + await ethers.getContractFactory("LinearVestingProjectFactoryUpgradeable"); + const ERC20 = await ethers.getContractFactory("ERC20Mock"); + + const erc20 = await ERC20.deploy(); + await erc20.deployed(); + + console.log("ERC20 Mock token deployed to:", erc20.address); + + const projectBase = await LinearVestingProjectUpgradeable.deploy(); + + console.log( + "Vesting Project for beacon proxy deployed to:", + projectBase.address + ); + + const projectFactory = await upgrades.deployProxy( + LinearVestingProjectFactoryUpgradeable, + [projectBase.address], + { initializer: "__LinearVestingProjectFactory_initialize" } + ); + await projectFactory.deployed(); + + console.log("Vesting Project Factory deployed to:", projectFactory.address); + + let tx = await projectFactory.create(erc20.address); + await tx.wait(); + + const linearVestingProjectAddress = await projectFactory.getCollectionAddress( + 0 + ); + console.log( + `New linear vesting project deployed to ${linearVestingProjectAddress}` + ); } main().catch((error) => { - console.error(error); - process.exitCode = 1; + console.error(error); + process.exitCode = 1; }); diff --git a/test/linear-vesting/claims.ts b/test/linear-vesting/claims.ts index e41f489..938eb44 100644 --- a/test/linear-vesting/claims.ts +++ b/test/linear-vesting/claims.ts @@ -1,49 +1,68 @@ import { loadFixture } from "@nomicfoundation/hardhat-network-helpers"; import { expect } from "chai"; -import {deployProjectFactoryWithProjectAndPool} from "./util/fixtures"; -import {setNextBlockTimestamp} from "./util/time"; +import { deployProjectFactoryWithProjectAndPool } from "./util/fixtures"; +import { setNextBlockTimestamp } from "./util/time"; describe("Claims", function () { - - let poolVestingTime, startingPeriodTime, amount, projectFactory, erc20, project, owner, manager, user1, user2; + let poolVestingTime, + startingPeriodTime, + amount, + projectFactory, + erc20, + project, + owner, + manager, + user1, + user2; beforeEach(async () => { - ({startingPeriodTime, poolVestingTime, amount, projectFactory, erc20, project, owner, manager, user1, user2 } = await loadFixture(deployProjectFactoryWithProjectAndPool)); + ({ + startingPeriodTime, + poolVestingTime, + amount, + projectFactory, + erc20, + project, + owner, + manager, + user1, + user2, + } = await loadFixture(deployProjectFactoryWithProjectAndPool)); }); it("ongoing grant claim should succeed", async function () { - const nextTs = startingPeriodTime + (poolVestingTime / 2); // elapsed half-time + const nextTs = startingPeriodTime + poolVestingTime / 2; // elapsed half-time await setNextBlockTimestamp(nextTs); - await expect( - project.connect(user1).claimVestedTokens(0) - ).to.emit(project, "GrantClaimed").withArgs(0, user1.address, amount.div(2)) + await expect(project.connect(user1).claimVestedTokens(0)) + .to.emit(project, "GrantClaimed") + .withArgs(0, user1.address, amount.div(2)); - const grant = await project.grants(0, user1.address) + const grant = await project.grants(0, user1.address); expect(grant.totalClaimed).to.equal(amount.div(2)); }); it("invalid pool index should fail", async function () { - const nextTs = startingPeriodTime + (poolVestingTime / 2); // elapsed half-time + const nextTs = startingPeriodTime + poolVestingTime / 2; // elapsed half-time await setNextBlockTimestamp(nextTs); - await expect( - project.connect(user1).claimVestedTokens(1) - ).to.rejectedWith("LinearVestingProject::calculateGrantClaim: invalid pool index"); + await expect(project.connect(user1).claimVestedTokens(1)).to.rejectedWith( + "LinearVestingProject::calculateGrantClaim: invalid vesting index" + ); }); it("invalid grantee should fail", async function () { - const nextTs = startingPeriodTime + (poolVestingTime / 2); // elapsed half-time + const nextTs = startingPeriodTime + poolVestingTime / 2; // elapsed half-time await setNextBlockTimestamp(nextTs); - await expect( - project.connect(user2).claimVestedTokens(0) - ).to.rejectedWith("VestingPeriod::claimVestedTokens: amountVested is 0'"); + await expect(project.connect(user2).claimVestedTokens(0)).to.rejectedWith( + "VestingPeriod::claimVestedTokens: amountVested is 0'" + ); }); it("claimable balance 0 should fail", async function () { - await expect( - project.connect(user1).claimVestedTokens(0) - ).to.rejectedWith("VestingPeriod::claimVestedTokens: amountVested is 0'"); + await expect(project.connect(user1).claimVestedTokens(0)).to.rejectedWith( + "VestingPeriod::claimVestedTokens: amountVested is 0'" + ); }); }); diff --git a/test/linear-vesting/factory.ts b/test/linear-vesting/factory.ts index 430c29d..32dcae0 100644 --- a/test/linear-vesting/factory.ts +++ b/test/linear-vesting/factory.ts @@ -1,38 +1,46 @@ import { loadFixture } from "@nomicfoundation/hardhat-network-helpers"; import { expect } from "chai"; -import {deployProjectFactory} from "./util/fixtures"; -import {ethers, upgrades} from "hardhat"; -import {BigNumber} from "ethers"; +import { deployProjectFactory } from "./util/fixtures"; +import { ethers, upgrades } from "hardhat"; +import { BigNumber } from "ethers"; describe("Factory", function () { - let projectFactory, erc20, owner, manager, user1, user2; beforeEach(async () => { - ({projectFactory, erc20, owner, manager, user1, user2 } = await loadFixture(deployProjectFactory)); + ({ projectFactory, erc20, owner, manager, user1, user2 } = + await loadFixture(deployProjectFactory)); }); it("deploy factory should succeed", async function () { - const LinearVestingProjectRemovableUpgradeable = await ethers.getContractFactory("LinearVestingProjectRemovableUpgradeable"); - const LinearVestingProjectFactoryUpgradeable = await ethers.getContractFactory("LinearVestingProjectFactoryUpgradeable"); - - const projectBase = await LinearVestingProjectRemovableUpgradeable.connect(owner).deploy(); - - const projectFactory = await upgrades.deployProxy(LinearVestingProjectFactoryUpgradeable, [projectBase.address], { initializer: '__LinearVestingProjectFactory_initialize' }); - await projectFactory.deployed() + const LinearVestingProjectRemovableUpgradeable = + await ethers.getContractFactory( + "LinearVestingProjectRemovableUpgradeable" + ); + const LinearVestingProjectFactoryUpgradeable = + await ethers.getContractFactory("LinearVestingProjectFactoryUpgradeable"); + + const projectBase = await LinearVestingProjectRemovableUpgradeable.connect( + owner + ).deploy(); + + const projectFactory = await upgrades.deployProxy( + LinearVestingProjectFactoryUpgradeable, + [projectBase.address], + { initializer: "__LinearVestingProjectFactory_initialize" } + ); + await projectFactory.deployed(); }); it("create new project should succeed", async function () { expect(await projectFactory.projectsCount()).to.equal(BigNumber.from(0)); await expect( - projectFactory.createProject(erc20.address, 'Project Example', 'asd') - ).to.emit(projectFactory, "ProjectCreated") + projectFactory.createProject(erc20.address, "Project Example", "asd") + ).to.emit(projectFactory, "ProjectCreated"); expect(await projectFactory.projectsCount()).to.equal(BigNumber.from(1)); }); - it.skip("upgrade beacon should succeed", async function () { - - }); + it.skip("upgrade beacon should succeed", async function () {}); }); diff --git a/test/linear-vesting/grants.ts b/test/linear-vesting/grants.ts index 4f9df65..ab82034 100644 --- a/test/linear-vesting/grants.ts +++ b/test/linear-vesting/grants.ts @@ -1,104 +1,120 @@ -import {loadFixture, mine} from "@nomicfoundation/hardhat-network-helpers"; +import { loadFixture, mine } from "@nomicfoundation/hardhat-network-helpers"; import { expect } from "chai"; -import {deployProjectFactoryWithProjectAndPool} from "./util/fixtures"; +import { deployProjectFactoryWithProjectAndPool } from "./util/fixtures"; describe("Grants management", function () { - - let poolVestingTime, startingPeriodTime, amount, projectFactory, erc20, project, owner, manager, user1, user2; + let poolVestingTime, + startingPeriodTime, + amount, + projectFactory, + erc20, + project, + owner, + manager, + user1, + user2; beforeEach(async () => { - ({startingPeriodTime, poolVestingTime, amount, projectFactory, erc20, project, owner, manager, user1, user2 } = await loadFixture(deployProjectFactoryWithProjectAndPool)); + ({ + startingPeriodTime, + poolVestingTime, + amount, + projectFactory, + erc20, + project, + owner, + manager, + user1, + user2, + } = await loadFixture(deployProjectFactoryWithProjectAndPool)); }); describe("Adding new grants", function () { - it("add new grants by a manager should succeed", async function () { + const allowTx = await erc20.increaseAllowance(project.address, amount); + await allowTx.wait(); - const allowTx = await erc20.increaseAllowance(project.address, amount) - await allowTx.wait() + await expect(project.addGrants(0, [user2.address], [amount])) + .to.emit(project, "GrantAdded") + .withArgs(0, owner.address, user2.address, amount); - await expect( - project.addGrants(0, [user2.address], [amount]) - ).to.emit(project, "GrantAdded").withArgs(0, owner.address, user2.address, amount) - - const newGrant = await project.grants(0, user2.address) + const newGrant = await project.grants(0, user2.address); + console.log("Amount: " + amount); expect(newGrant.amount).to.equal(amount); }); it("user without manager role adding should fail", async function () { - - await expect( - project.connect(user1).addGrants(0, [], []) - ).to.rejectedWith("ManageableUpgradeable::onlyManager: the caller is not an manager."); + await expect(project.connect(user1).addGrants(0, [], [])).to.rejectedWith( + "ManageableUpgradeable::onlyManager: the caller is not an manager." + ); }); it("no grantees should fail", async function () { - - await expect( - project.addGrants(0, [], []) - ).to.rejectedWith("LinearVestingProject::addTokenGrants: no recipients"); + await expect(project.addGrants(0, [], [])).to.rejectedWith( + "LinearVestingProject::addTokenGrants: no recipients" + ); }); it("more than 100 grantees should fail", async function () { - - const grantAddresses = [] - const grantAmounts = [] - for (let i = 0; i < 101; i++){ - grantAddresses.push(user1.address) - grantAmounts.push(amount) + const grantAddresses = []; + const grantAmounts = []; + for (let i = 0; i < 101; i++) { + grantAddresses.push(user1.address); + grantAmounts.push(amount); } await expect( - project.addGrants(0, grantAddresses, grantAmounts) - ).to.rejectedWith("LinearVestingProject::addTokenGrants: too many grants, it will probably fail"); + project.addGrants(0, grantAddresses, grantAmounts) + ).to.rejectedWith( + "LinearVestingProject::addTokenGrants: too many grants, it will probably fail" + ); }); it("different quantity of grantees and amounts should fail", async function () { - - await expect( - project.addGrants(0, [user1.address], []) - ).to.rejectedWith("LinearVestingProject::addTokenGrants: invalid parameters length (they should be same)"); + await expect(project.addGrants(0, [user1.address], [])).to.rejectedWith( + "LinearVestingProject::addTokenGrants: invalid parameters length (they should be same)" + ); }); it("invalid pool index should fail", async function () { - await expect( - project.addGrants(1, [user1.address], [amount]) - ).to.rejectedWith("LinearVestingProject::addTokenGrants: invalid pool index"); + project.addGrants(1, [user1.address], [amount]) + ).to.rejectedWith( + "LinearVestingProject::addTokenGrants: invalid pool index" + ); }); }); describe("Remove existing grants", function () { it("remove existing grants by a manager should succeed", async function () { - const newGrant = await project.grants(0, user1.address) + const newGrant = await project.grants(0, user1.address); expect(newGrant.amount).to.equal(amount); - await expect( - project.removeGrant(0, user1.address) - ).to.emit(project, "GrantRemoved").withArgs(0, owner.address, user1.address) + await expect(project.removeGrant(0, user1.address)) + .to.emit(project, "GrantRemoved") + .withArgs(0, owner.address, user1.address); - const removedGrant = await project.grants(0, user1.address) + const removedGrant = await project.grants(0, user1.address); expect(removedGrant.amount).to.equal(0); }); it("user without manager role remove should fail", async function () { - await expect( - project.connect(user1).removeGrant(0, user1.address) - ).to.rejectedWith("ManageableUpgradeable::onlyManager: the caller is not an manager."); + project.connect(user1).removeGrant(0, user1.address) + ).to.rejectedWith( + "ManageableUpgradeable::onlyManager: the caller is not an manager." + ); }); it("invalid grant or without funds available should fail", async function () { - - await expect( - project.removeGrant(0, user2.address) - ).to.rejectedWith("LinearVestingProjectRemovable::removeGrant: grant not active"); + await expect(project.removeGrant(0, user2.address)).to.rejectedWith( + "LinearVestingProjectRemovable::removeGrant: grant not active" + ); }); it("invalid pool index should fail", async function () { - - await expect( - project.removeGrant(1, user1.address) - ).to.rejectedWith("LinearVestingProjectRemovable::removeGrant: grant not active"); + await expect(project.removeGrant(1, user1.address)).to.rejectedWith( + "LinearVestingProjectRemovable::removeGrant: grant not active" + ); }); }); }); diff --git a/test/linear-vesting/pools.ts b/test/linear-vesting/pools.ts index 747c8e2..2f69579 100644 --- a/test/linear-vesting/pools.ts +++ b/test/linear-vesting/pools.ts @@ -1,7 +1,7 @@ import { loadFixture } from "@nomicfoundation/hardhat-network-helpers"; import { expect } from "chai"; -import { deployProjectFactoryWithProject } from "./util/fixtures"; import { BigNumber } from "ethers"; +import { deployProjectFactoryWithProject } from "./util/fixtures"; import { getLastBlockTimestamp } from "./util/time"; describe("Pools creation", function () { @@ -29,7 +29,7 @@ describe("Pools creation", function () { let startingPeriodTime = currentTimeStamp + 100; await expect( - project.createPoolWithGrants( + project.createVestingScheduleWithGrants( token, poolName, startingPeriodTime, @@ -38,7 +38,7 @@ describe("Pools creation", function () { [amount] ) ) - .to.emit(project, "PoolAdded") + .to.emit(project, "VestingScheduleAdded") .withArgs( token, poolName, @@ -54,9 +54,14 @@ describe("Pools creation", function () { let startingPeriodTime = currentTimeStamp + 100; let token = erc20.address; await expect( - project.createPool(token, poolName, startingPeriodTime, poolVestingTime) + project.createVestingSchedule( + token, + poolName, + startingPeriodTime, + poolVestingTime + ) ) - .to.emit(project, "PoolAdded") + .to.emit(project, "VestingScheduleAdded") .withArgs( token, poolName, @@ -76,7 +81,12 @@ describe("Pools creation", function () { await expect( project .connect(user1) - .createPool(token, poolName, startingPeriodTime, poolVestingTime) + .createVestingSchedule( + token, + poolName, + startingPeriodTime, + poolVestingTime + ) ).to.rejectedWith( "ManageableUpgradeable::onlyManager: the caller is not an manager." ); diff --git a/test/linear-vesting/util/fixtures.ts b/test/linear-vesting/util/fixtures.ts index 25aaa43..1dfbdac 100644 --- a/test/linear-vesting/util/fixtures.ts +++ b/test/linear-vesting/util/fixtures.ts @@ -63,9 +63,9 @@ async function deployProjectFactoryWithProjectAndPool() { const { projectFactory, projectBase, - owner, project, erc20, + owner, manager, user1, user2, @@ -82,7 +82,7 @@ async function deployProjectFactoryWithProjectAndPool() { let startingPeriodTime = currentTimeStamp + 100; - let tx = await project.createPoolWithGrants( + let tx = await project.createVestingScheduleWithGrants( token, "Pool 1", startingPeriodTime, From 9fec5ff999bfbe621ecb7d334fc5da997ceaf7a7 Mon Sep 17 00:00:00 2001 From: jon-vdb Date: Mon, 29 May 2023 10:50:13 +0700 Subject: [PATCH 3/4] Renamed 'Pool'. Now 'Vesting', poolIndex now vestingIndex, tests passing --- .../ITestLinearVestingProjectUpgradeable.sol | 120 ------- .../TestLinearVestingProjectUpgradeable.sol | 293 ------------------ 2 files changed, 413 deletions(-) delete mode 100644 contracts/linear-vesting/ITestLinearVestingProjectUpgradeable.sol delete mode 100644 contracts/linear-vesting/TestLinearVestingProjectUpgradeable.sol diff --git a/contracts/linear-vesting/ITestLinearVestingProjectUpgradeable.sol b/contracts/linear-vesting/ITestLinearVestingProjectUpgradeable.sol deleted file mode 100644 index 2c657ed..0000000 --- a/contracts/linear-vesting/ITestLinearVestingProjectUpgradeable.sol +++ /dev/null @@ -1,120 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; - -/** - * @title ILinearVestingProjectUpgradeable interface - * @dev Linear Vesting project for linear vesting grants distribution. - * Taken from https://github.com/dandelionlabs-io/linear-vesting-contracts/blob/master/contracts/linear-vesting/ILinearVestingProjectUpgradeable.sol - */ -interface ITestLinearVestingProjectUpgradeable { - /// @notice Pool definition - struct VestingSchedule { - address token; - string metadataUrl; // Name of the pool - uint startTime; // Starting time of the vesting period in unix timestamp format - uint endTime; // Ending time of the vesting period in unix timestamp format - uint vestingDuration; // In seconds - uint amount; // Total size of pool - uint totalClaimed; // Total amount claimed till moment - uint grants; // Amount of stakeholders - } - - /// @notice Grant definition - struct Grant { - uint amount; // Total amount to claim - uint totalClaimed; // Already claimed - uint perSecond; // Reward per second - } - - /// @notice Event emitted when a new pool is created - event VestingScheduleAdded( - address token, - string metadataUrl, - uint indexed index, - address createdBy, - uint startTime, - uint endTime - ); - - /// @notice Event emitted when a new grant is created - event GrantAdded( - uint indexed vestingIndex, - address indexed addedBy, - address indexed recipient, - uint amount - ); - - /// @notice Event emitted when tokens are claimed by a recipient from a grant - event GrantClaimed( - uint indexed vestingIndex, - address indexed recipient, - uint amountClaimed - ); - - /// @notice Event emitted when metadata url is changed - event MetadataUrlChanged(address indexed changedBy, string metadataUrl); - - /** - * @notice Creates a new pool - * @param _startTime starting time of the vesting period in timestamp format - * @param _vestingDuration duration time of the vesting period in timestamp format - */ - function createVestingSchedule( - address token, - string memory _metadataUrl, - uint256 _startTime, - uint256 _vestingDuration - ) external returns (uint256); - - /** - * @notice Creates a new pool - * @param _startTime starting time of the vesting period in timestamp format - * @param _vestingDuration duration time of the vesting period in timestamp format - */ - function createVestingScheduleWithGrants( - address token, - string memory _metadataUrl, - uint256 _startTime, - uint256 _vestingDuration, - address[] memory _recipients, - uint256[] memory _amounts - ) external returns (uint256); - - /** - * @notice Add list of grants in batch. - * @param _recipients list of addresses of the stakeholders - * @param _amounts list of amounts to be assigned to the stakeholders - */ - function addGrants( - uint _vestingIndex, - address[] memory _recipients, - uint256[] memory _amounts - ) external; - - /** - * @notice Calculate the vested and unclaimed tokens available for `recipient` to claim - * @dev Due to rounding errors once grant duration is reached, returns the entire left grant amount - * @param _recipient The address that has a grant - * @return The amount recipient can claim - */ - function calculateGrantClaim( - uint _vestingIndex, - address _recipient - ) external view returns (uint256); - - /** - * @notice Allows a grant recipient to claim their vested tokens - * @dev Errors if no tokens have vested - * @dev It is advised recipients check they are entitled to claim via `calculateGrantClaim` before calling this - */ - function claimVestedTokens(uint _vestingIndex) external; - - /** - * @notice Allows a grant recipient to claim multiple vested tokens - * @dev Errors if no tokens have vested - * @dev It is advised recipients check they are entitled to claim via `calculateGrantClaim` before calling this - */ - function claimMultipleVestings(uint[] memory _vestingIndexes) external; - - function setMetadataUrl(string memory _metadata) external; -} diff --git a/contracts/linear-vesting/TestLinearVestingProjectUpgradeable.sol b/contracts/linear-vesting/TestLinearVestingProjectUpgradeable.sol deleted file mode 100644 index da86efc..0000000 --- a/contracts/linear-vesting/TestLinearVestingProjectUpgradeable.sol +++ /dev/null @@ -1,293 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; - -import "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol"; -import "@openzeppelin/contracts-upgradeable/utils/math/SafeMathUpgradeable.sol"; -import "../access/ManageableUpgradeable.sol"; -import "./ITestLinearVestingProjectUpgradeable.sol"; - -/** - * @title LinearVestingProjectUpgradeable - * @dev Linear Vesting project for linear vesting grants distribution. - * Taken from https://github.com/dandelionlabs-io/linear-vesting-contracts/blob/master/contracts/LinearVestingProjectUpgradeable.sol - */ -contract TestLinearVestingProjectUpgradeable is - ManageableUpgradeable, - ITestLinearVestingProjectUpgradeable -{ - using SafeMathUpgradeable for uint; - - /// @dev Used to translate vesting periods specified in days to seconds - uint internal constant SECONDS_PER_DAY = 86400; - - /// @notice ERC20 token - IERC20Upgradeable public token; - - /// @dev Each Organization has many vesting schedules - VestingSchedule[] public vestingSchedules; - - /// @notice Mapping of recipient address > token grant - mapping(uint => mapping(address => Grant)) public grants; - - /// @dev metadata url - string public metadataUrl; - - /** - * @notice Construct a new Vesting contract - * @param _token Address of ERC20 token - */ - function __LinearVestingProject_initialize( - address _token, - string calldata _metadataUrl - ) external initializer { - __ManageableUpgradeable_init(); - - require( - _token != address(0), - "LinearVestingProject::__init_LinearVestingProject: _token must be valid token address" - ); - - token = IERC20Upgradeable(_token); - metadataUrl = _metadataUrl; - } - - /** - * @notice Creates a new pool - * @param _startTime starting time of the vesting period in timestamp format - * @param _vestingDuration duration time of the vesting period in timestamp format - */ - function createVestingSchedule( - address _token, - string memory _metadataUrl, - uint256 _startTime, - uint256 _vestingDuration - ) public override onlyManager returns (uint256) { - require( - _startTime > 0 && _vestingDuration > 0, - "LinearVestingProject::createVestingSchedule: One of the time parameters is 0" - ); - require( - _startTime > block.timestamp, - "LinearVestingProject::createVestingSchedule: Starting time shall be in a future time" - ); - require( - _vestingDuration > 0, - "LinearVestingProject::createVestingSchedule: Duration of the period must be > 0" - ); - if (_vestingDuration < SECONDS_PER_DAY) { - require( - _vestingDuration <= SECONDS_PER_DAY.mul(10).mul(365), - "LinearVestingProject::createVestingSchedule: Duration should be less than 10 years" - ); - } - - uint256 vestingIndex = vestingSchedules.length; - - vestingSchedules.push( - VestingSchedule({ - token: _token, - metadataUrl: _metadataUrl, - startTime: _startTime, - vestingDuration: _vestingDuration, - endTime: _startTime.add(_vestingDuration), - amount: 0, - totalClaimed: 0, - grants: 0 - }) - ); - - emit VestingScheduleAdded( - _token, - _metadataUrl, - vestingIndex, - msg.sender, - _startTime, - _startTime.add(_vestingDuration) - ); - return vestingIndex; - } - - function createVestingScheduleWithGrants( - address _token, - string memory _metadataUrl, - uint256 _startTime, - uint256 _vestingDuration, - address[] memory _recipients, - uint256[] memory _amounts - ) public override onlyManager returns (uint) { - uint vestingIndex = createVestingSchedule( - _token, - _metadataUrl, - _startTime, - _vestingDuration - ); - addGrants(vestingIndex, _recipients, _amounts); - return vestingIndex; - } - - /** - * @notice Add list of grants in batch. - * @param _recipients list of addresses of the stakeholders - * @param _amounts list of amounts to be assigned to the stakeholders - */ - function addGrants( - uint _vestingIndex, - address[] memory _recipients, - uint256[] memory _amounts - ) public virtual override onlyManager { - require( - _recipients.length > 0, - "LinearVestingProject::addTokenGrants: no recipients" - ); - require( - _recipients.length <= 100, - "LinearVestingProject::addTokenGrants: too many grants, it will probably fail" - ); - require( - _recipients.length == _amounts.length, - "LinearVestingProject::addTokenGrants: invalid parameters length (they should be same)" - ); - require( - _vestingIndex < vestingSchedules.length, - "LinearVestingProject::addTokenGrants: invalid pool index" - ); - - VestingSchedule memory vesting = vestingSchedules[_vestingIndex]; - - uint256 amountSum = 0; - for (uint16 i = 0; i < _recipients.length; i++) { - require( - _recipients[i] != address(0), - "LinearVestingProject:addTokenGrants: there is an address with value 0" - ); - require( - grants[_vestingIndex][_recipients[i]].totalClaimed == 0, - "LinearVestingProject::addTokenGrants: a grant already exists for one of the accounts" - ); - require( - _amounts[i] > 0, - "LinearVestingProject::addTokenGrant: amount == 0" - ); - amountSum = amountSum.add(_amounts[i]); - } - - // Transfer the grant tokens under the control of the vesting contract - require( - token.transferFrom(msg.sender, address(this), amountSum), - "LinearVestingProject::addTokenGrants: transfer failed" - ); - - for (uint16 i = 0; i < _recipients.length; i++) { - Grant memory grant = Grant({ - amount: _amounts[i], - totalClaimed: 0, - perSecond: _amounts[i].div(vesting.vestingDuration) - }); - grants[_vestingIndex][_recipients[i]] = grant; - emit GrantAdded( - _vestingIndex, - msg.sender, - _recipients[i], - _amounts[i] - ); - } - - vesting.amount = vesting.amount.add(amountSum); - vesting.grants = vesting.grants.add(_recipients.length); - } - - /** - * @notice Calculate the vested and unclaimed tokens available for `recipient` to claim - * @dev Due to rounding errors once grant duration is reached, returns the entire left grant amount - * @param _recipient The address that has a grant - * @return The amount recipient can claim - */ - function calculateGrantClaim( - uint _vestingIndex, - address _recipient - ) public view override returns (uint256) { - require( - _vestingIndex < vestingSchedules.length, - "LinearVestingProject::calculateGrantClaim: invalid pool index" - ); - VestingSchedule memory vesting = vestingSchedules[_vestingIndex]; - // For grants created with a future start date, that hasn't been reached, return 0, 0 - if (block.timestamp < vesting.startTime) { - return 0; - } - - uint256 cap = block.timestamp; - if (cap > vesting.endTime) { - cap = vesting.endTime; - } - uint256 elapsedTime = cap.sub(vesting.startTime); - - // If over vesting duration, all tokens vested - if (elapsedTime >= vesting.vestingDuration) { - uint256 remainingGrant = grants[_vestingIndex][_recipient] - .totalClaimed - .sub(grants[_vestingIndex][_recipient].amount); - return remainingGrant; - } else { - uint256 amountVested = grants[_vestingIndex][_recipient] - .perSecond - .mul(elapsedTime); - uint256 claimableAmount = amountVested.sub( - grants[_vestingIndex][_recipient].totalClaimed - ); - return claimableAmount; - } - } - - /** - * @notice Allows a grant recipient to claim multiple vested tokens - * @dev Errors if no tokens have vested - * @dev It is advised recipients check they are entitled to claim via `calculateGrantClaim` before calling this - */ - function claimMultipleVestings( - uint[] memory _vestingIndexes - ) external override { - for (uint i = 0; i < _vestingIndexes.length; i++) { - claimVestedTokens(_vestingIndexes[i]); - } - } - - /** - * @notice Allows a grant recipient to claim their vested tokens - * @dev Errors if no tokens have vested - * @dev It is advised recipients check they are entitled to claim via `calculateGrantClaim` before calling this - */ - function claimVestedTokens(uint _vestingIndex) public override { - require( - _vestingIndex < vestingSchedules.length, - "LinearVestingProject::calculateGrantClaim: invalid vesting index" - ); - VestingSchedule memory vesting = vestingSchedules[_vestingIndex]; - - uint256 amountVested = calculateGrantClaim(_vestingIndex, msg.sender); - require( - amountVested > 0, - "VestingPeriod::claimVestedTokens: amountVested is 0" - ); - require( - token.transfer(msg.sender, amountVested), - "VestingPeriod::claimVestedTokens: transfer failed" - ); - - Grant storage grant = grants[_vestingIndex][msg.sender]; - - grant.totalClaimed = uint256(grant.totalClaimed.add(amountVested)); - vesting.totalClaimed = vesting.totalClaimed.add(amountVested); - - emit GrantClaimed(_vestingIndex, msg.sender, amountVested); - } - - function setMetadataUrl( - string calldata _metadataUrl - ) external override onlyManager { - metadataUrl = _metadataUrl; - emit MetadataUrlChanged(msg.sender, _metadataUrl); - } - - uint256[49] private __gap; -} From 790874494af5321f94f00e6b4e0148fe9f830b3f Mon Sep 17 00:00:00 2001 From: jon-vdb Date: Mon, 29 May 2023 10:58:04 +0700 Subject: [PATCH 4/4] Renamed Pool to Vesting, poolIndex to vestingIndex --- .../ILinearVestingProjectUpgradeable.sol | 120 +++++++ ...LinearVestingProjectFactoryUpgradeable.sol | 4 +- .../LinearVestingProjectUpgradeable.sol | 293 ++++++++++++++++++ ...nearVestingProjectRemovableUpgradeable.sol | 4 +- ...nearVestingProjectRemovableUpgradeable.sol | 4 +- 5 files changed, 419 insertions(+), 6 deletions(-) create mode 100644 contracts/linear-vesting/ILinearVestingProjectUpgradeable.sol create mode 100644 contracts/linear-vesting/LinearVestingProjectUpgradeable.sol diff --git a/contracts/linear-vesting/ILinearVestingProjectUpgradeable.sol b/contracts/linear-vesting/ILinearVestingProjectUpgradeable.sol new file mode 100644 index 0000000..311d22b --- /dev/null +++ b/contracts/linear-vesting/ILinearVestingProjectUpgradeable.sol @@ -0,0 +1,120 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.4; + +/** + * @title ILinearVestingProjectUpgradeable interface + * @dev Linear Vesting project for linear vesting grants distribution. + * Taken from https://github.com/dandelionlabs-io/linear-vesting-contracts/blob/master/contracts/linear-vesting/ILinearVestingProjectUpgradeable.sol + */ +interface ILinearVestingProjectUpgradeable { + /// @notice Vesting Schedule definition + struct VestingSchedule { + address token; + string metadataUrl; // Name of the vesting schedule + uint startTime; // Starting time of the vesting period in unix timestamp format + uint endTime; // Ending time of the vesting period in unix timestamp format + uint vestingDuration; // In seconds + uint amount; // Total size of vesting + uint totalClaimed; // Total amount claimed till moment + uint grants; // Amount of stakeholders + } + + /// @notice Grant definition + struct Grant { + uint amount; // Total amount to claim + uint totalClaimed; // Already claimed + uint perSecond; // Reward per second + } + + /// @notice Event emitted when a new vesting schedule is created + event VestingScheduleAdded( + address token, + string metadataUrl, + uint indexed index, + address createdBy, + uint startTime, + uint endTime + ); + + /// @notice Event emitted when a new grant is created + event GrantAdded( + uint indexed vestingIndex, + address indexed addedBy, + address indexed recipient, + uint amount + ); + + /// @notice Event emitted when tokens are claimed by a recipient from a grant + event GrantClaimed( + uint indexed vestingIndex, + address indexed recipient, + uint amountClaimed + ); + + /// @notice Event emitted when metadata url is changed + event MetadataUrlChanged(address indexed changedBy, string metadataUrl); + + /** + * @notice Creates a new vesting schedule + * @param _startTime starting time of the vesting period in timestamp format + * @param _vestingDuration duration time of the vesting period in timestamp format + */ + function createVestingSchedule( + address token, + string memory _metadataUrl, + uint256 _startTime, + uint256 _vestingDuration + ) external returns (uint256); + + /** + * @notice Creates a new vesting schedule grants + * @param _startTime starting time of the vesting period in timestamp format + * @param _vestingDuration duration time of the vesting period in timestamp format + */ + function createVestingScheduleWithGrants( + address token, + string memory _metadataUrl, + uint256 _startTime, + uint256 _vestingDuration, + address[] memory _recipients, + uint256[] memory _amounts + ) external returns (uint256); + + /** + * @notice Add list of grants in batch. + * @param _recipients list of addresses of the beneficiaries + * @param _amounts list of amounts to be assigned to the beneficiaries + */ + function addGrants( + uint _vestingIndex, + address[] memory _recipients, + uint256[] memory _amounts + ) external; + + /** + * @notice Calculate the vested and unclaimed tokens available for `recipient` to claim + * @dev Due to rounding errors once grant duration is reached, returns the entire left grant amount + * @param _recipient The address that has a grant + * @return The amount recipient can claim + */ + function calculateGrantClaim( + uint _vestingIndex, + address _recipient + ) external view returns (uint256); + + /** + * @notice Allows a grant recipient to claim their vested tokens + * @dev Errors if no tokens have vested + * @dev It is advised recipients check they are entitled to claim via `calculateGrantClaim` before calling this + */ + function claimVestedTokens(uint _vestingIndex) external; + + /** + * @notice Allows a grant recipient to claim multiple vested tokens + * @dev Errors if no tokens have vested + * @dev It is advised recipients check they are entitled to claim via `calculateGrantClaim` before calling this + */ + function claimMultipleVestings(uint[] memory _vestingIndexes) external; + + function setMetadataUrl(string memory _metadata) external; +} diff --git a/contracts/linear-vesting/LinearVestingProjectFactoryUpgradeable.sol b/contracts/linear-vesting/LinearVestingProjectFactoryUpgradeable.sol index 7c38dd9..afe0c4f 100644 --- a/contracts/linear-vesting/LinearVestingProjectFactoryUpgradeable.sol +++ b/contracts/linear-vesting/LinearVestingProjectFactoryUpgradeable.sol @@ -4,7 +4,7 @@ pragma solidity ^0.8.0; import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; import "@openzeppelin/contracts/proxy/beacon/BeaconProxy.sol"; -import "./TestLinearVestingProjectUpgradeable.sol"; +import "./LinearVestingProjectUpgradeable.sol"; import "./LinearVestingProjectBeacon.sol"; contract LinearVestingProjectFactoryUpgradeable is OwnableUpgradeable { @@ -44,7 +44,7 @@ contract LinearVestingProjectFactoryUpgradeable is OwnableUpgradeable { BeaconProxy project = new BeaconProxy( address(beacon), abi.encodeWithSelector( - TestLinearVestingProjectUpgradeable(address(0)) + LinearVestingProjectUpgradeable(address(0)) .__LinearVestingProject_initialize .selector, _token, diff --git a/contracts/linear-vesting/LinearVestingProjectUpgradeable.sol b/contracts/linear-vesting/LinearVestingProjectUpgradeable.sol new file mode 100644 index 0000000..856eb3c --- /dev/null +++ b/contracts/linear-vesting/LinearVestingProjectUpgradeable.sol @@ -0,0 +1,293 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.4; + +import "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol"; +import "@openzeppelin/contracts-upgradeable/utils/math/SafeMathUpgradeable.sol"; +import "../access/ManageableUpgradeable.sol"; +import "./ILinearVestingProjectUpgradeable.sol"; + +/** + * @title LinearVestingProjectUpgradeable + * @dev Linear Vesting project for linear vesting grants distribution. + * Taken from https://github.com/dandelionlabs-io/linear-vesting-contracts/blob/master/contracts/LinearVestingProjectUpgradeable.sol + */ +contract LinearVestingProjectUpgradeable is + ManageableUpgradeable, + ILinearVestingProjectUpgradeable +{ + using SafeMathUpgradeable for uint; + + /// @dev Used to translate vesting periods specified in days to seconds + uint internal constant SECONDS_PER_DAY = 86400; + + /// @notice ERC20 token + IERC20Upgradeable public token; + + /// @dev Each Organization has many vesting schedules + VestingSchedule[] public vestingSchedules; + + /// @notice Mapping of recipient address > token grant + mapping(uint => mapping(address => Grant)) public grants; + + /// @dev metadata url + string public metadataUrl; + + /** + * @notice Construct a new Vesting contract + * @param _token Address of ERC20 token + */ + function __LinearVestingProject_initialize( + address _token, + string calldata _metadataUrl + ) external initializer { + __ManageableUpgradeable_init(); + + require( + _token != address(0), + "LinearVestingProject::__init_LinearVestingProject: _token must be valid token address" + ); + + token = IERC20Upgradeable(_token); + metadataUrl = _metadataUrl; + } + + /** + * @notice Creates a new pool + * @param _startTime starting time of the vesting period in timestamp format + * @param _vestingDuration duration time of the vesting period in timestamp format + */ + function createVestingSchedule( + address _token, + string memory _metadataUrl, + uint256 _startTime, + uint256 _vestingDuration + ) public override onlyManager returns (uint256) { + require( + _startTime > 0 && _vestingDuration > 0, + "LinearVestingProject::createVestingSchedule: One of the time parameters is 0" + ); + require( + _startTime > block.timestamp, + "LinearVestingProject::createVestingSchedule: Starting time shall be in a future time" + ); + require( + _vestingDuration > 0, + "LinearVestingProject::createVestingSchedule: Duration of the period must be > 0" + ); + if (_vestingDuration < SECONDS_PER_DAY) { + require( + _vestingDuration <= SECONDS_PER_DAY.mul(10).mul(365), + "LinearVestingProject::createVestingSchedule: Duration should be less than 10 years" + ); + } + + uint256 vestingIndex = vestingSchedules.length; + + vestingSchedules.push( + VestingSchedule({ + token: _token, + metadataUrl: _metadataUrl, + startTime: _startTime, + vestingDuration: _vestingDuration, + endTime: _startTime.add(_vestingDuration), + amount: 0, + totalClaimed: 0, + grants: 0 + }) + ); + + emit VestingScheduleAdded( + _token, + _metadataUrl, + vestingIndex, + msg.sender, + _startTime, + _startTime.add(_vestingDuration) + ); + return vestingIndex; + } + + function createVestingScheduleWithGrants( + address _token, + string memory _metadataUrl, + uint256 _startTime, + uint256 _vestingDuration, + address[] memory _recipients, + uint256[] memory _amounts + ) public override onlyManager returns (uint) { + uint vestingIndex = createVestingSchedule( + _token, + _metadataUrl, + _startTime, + _vestingDuration + ); + addGrants(vestingIndex, _recipients, _amounts); + return vestingIndex; + } + + /** + * @notice Add list of grants in batch. + * @param _recipients list of addresses of the stakeholders + * @param _amounts list of amounts to be assigned to the stakeholders + */ + function addGrants( + uint _vestingIndex, + address[] memory _recipients, + uint256[] memory _amounts + ) public virtual override onlyManager { + require( + _recipients.length > 0, + "LinearVestingProject::addTokenGrants: no recipients" + ); + require( + _recipients.length <= 100, + "LinearVestingProject::addTokenGrants: too many grants, it will probably fail" + ); + require( + _recipients.length == _amounts.length, + "LinearVestingProject::addTokenGrants: invalid parameters length (they should be same)" + ); + require( + _vestingIndex < vestingSchedules.length, + "LinearVestingProject::addTokenGrants: invalid pool index" + ); + + VestingSchedule memory vesting = vestingSchedules[_vestingIndex]; + + uint256 amountSum = 0; + for (uint16 i = 0; i < _recipients.length; i++) { + require( + _recipients[i] != address(0), + "LinearVestingProject:addTokenGrants: there is an address with value 0" + ); + require( + grants[_vestingIndex][_recipients[i]].totalClaimed == 0, + "LinearVestingProject::addTokenGrants: a grant already exists for one of the accounts" + ); + require( + _amounts[i] > 0, + "LinearVestingProject::addTokenGrant: amount == 0" + ); + amountSum = amountSum.add(_amounts[i]); + } + + // Transfer the grant tokens under the control of the vesting contract + require( + token.transferFrom(msg.sender, address(this), amountSum), + "LinearVestingProject::addTokenGrants: transfer failed" + ); + + for (uint16 i = 0; i < _recipients.length; i++) { + Grant memory grant = Grant({ + amount: _amounts[i], + totalClaimed: 0, + perSecond: _amounts[i].div(vesting.vestingDuration) + }); + grants[_vestingIndex][_recipients[i]] = grant; + emit GrantAdded( + _vestingIndex, + msg.sender, + _recipients[i], + _amounts[i] + ); + } + + vesting.amount = vesting.amount.add(amountSum); + vesting.grants = vesting.grants.add(_recipients.length); + } + + /** + * @notice Calculate the vested and unclaimed tokens available for `recipient` to claim + * @dev Due to rounding errors once grant duration is reached, returns the entire left grant amount + * @param _recipient The address that has a grant + * @return The amount recipient can claim + */ + function calculateGrantClaim( + uint _vestingIndex, + address _recipient + ) public view override returns (uint256) { + require( + _vestingIndex < vestingSchedules.length, + "LinearVestingProject::calculateGrantClaim: invalid pool index" + ); + VestingSchedule memory vesting = vestingSchedules[_vestingIndex]; + // For grants created with a future start date, that hasn't been reached, return 0, 0 + if (block.timestamp < vesting.startTime) { + return 0; + } + + uint256 cap = block.timestamp; + if (cap > vesting.endTime) { + cap = vesting.endTime; + } + uint256 elapsedTime = cap.sub(vesting.startTime); + + // If over vesting duration, all tokens vested + if (elapsedTime >= vesting.vestingDuration) { + uint256 remainingGrant = grants[_vestingIndex][_recipient] + .totalClaimed + .sub(grants[_vestingIndex][_recipient].amount); + return remainingGrant; + } else { + uint256 amountVested = grants[_vestingIndex][_recipient] + .perSecond + .mul(elapsedTime); + uint256 claimableAmount = amountVested.sub( + grants[_vestingIndex][_recipient].totalClaimed + ); + return claimableAmount; + } + } + + /** + * @notice Allows a grant recipient to claim multiple vested tokens + * @dev Errors if no tokens have vested + * @dev It is advised recipients check they are entitled to claim via `calculateGrantClaim` before calling this + */ + function claimMultipleVestings( + uint[] memory _vestingIndexes + ) external override { + for (uint i = 0; i < _vestingIndexes.length; i++) { + claimVestedTokens(_vestingIndexes[i]); + } + } + + /** + * @notice Allows a grant recipient to claim their vested tokens + * @dev Errors if no tokens have vested + * @dev It is advised recipients check they are entitled to claim via `calculateGrantClaim` before calling this + */ + function claimVestedTokens(uint _vestingIndex) public override { + require( + _vestingIndex < vestingSchedules.length, + "LinearVestingProject::calculateGrantClaim: invalid vesting index" + ); + VestingSchedule memory vesting = vestingSchedules[_vestingIndex]; + + uint256 amountVested = calculateGrantClaim(_vestingIndex, msg.sender); + require( + amountVested > 0, + "VestingPeriod::claimVestedTokens: amountVested is 0" + ); + require( + token.transfer(msg.sender, amountVested), + "VestingPeriod::claimVestedTokens: transfer failed" + ); + + Grant storage grant = grants[_vestingIndex][msg.sender]; + + grant.totalClaimed = uint256(grant.totalClaimed.add(amountVested)); + vesting.totalClaimed = vesting.totalClaimed.add(amountVested); + + emit GrantClaimed(_vestingIndex, msg.sender, amountVested); + } + + function setMetadataUrl( + string calldata _metadataUrl + ) external override onlyManager { + metadataUrl = _metadataUrl; + emit MetadataUrlChanged(msg.sender, _metadataUrl); + } + + uint256[49] private __gap; +} diff --git a/contracts/linear-vesting/removable/ILinearVestingProjectRemovableUpgradeable.sol b/contracts/linear-vesting/removable/ILinearVestingProjectRemovableUpgradeable.sol index f37b3fc..c8f9962 100644 --- a/contracts/linear-vesting/removable/ILinearVestingProjectRemovableUpgradeable.sol +++ b/contracts/linear-vesting/removable/ILinearVestingProjectRemovableUpgradeable.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.4; -import "../ITestLinearVestingProjectUpgradeable.sol"; +import "../ILinearVestingProjectUpgradeable.sol"; /** * @title ILinearVestingProjectRemovableUpgradeable interface @@ -8,7 +8,7 @@ import "../ITestLinearVestingProjectUpgradeable.sol"; * Taken from https://github.com/dandelionlabs-io/linear-vesting-contracts/blob/master/contracts/linear-vesting/ILinearVestingProjectRemovableUpgradeable.sol */ interface ILinearVestingProjectRemovableUpgradeable is - ITestLinearVestingProjectUpgradeable + ILinearVestingProjectUpgradeable { /// @notice Event emitted when the grant stakeholder is deleted event GrantRemoved( diff --git a/contracts/linear-vesting/removable/LinearVestingProjectRemovableUpgradeable.sol b/contracts/linear-vesting/removable/LinearVestingProjectRemovableUpgradeable.sol index 9585489..9dce6f0 100644 --- a/contracts/linear-vesting/removable/LinearVestingProjectRemovableUpgradeable.sol +++ b/contracts/linear-vesting/removable/LinearVestingProjectRemovableUpgradeable.sol @@ -1,11 +1,11 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.4; -import "../TestLinearVestingProjectUpgradeable.sol"; +import "../LinearVestingProjectUpgradeable.sol"; import "./ILinearVestingProjectRemovableUpgradeable.sol"; contract LinearVestingProjectRemovableUpgradeable is - TestLinearVestingProjectUpgradeable, + LinearVestingProjectUpgradeable, ILinearVestingProjectRemovableUpgradeable { /**