Skip to content

Commit

Permalink
v4
Browse files Browse the repository at this point in the history
  • Loading branch information
ianhe8x committed Feb 14, 2024
1 parent 62fe30b commit a723c98
Show file tree
Hide file tree
Showing 4 changed files with 104 additions and 131 deletions.
146 changes: 49 additions & 97 deletions contracts/root/Vesting.sol
Original file line number Diff line number Diff line change
Expand Up @@ -18,32 +18,16 @@ contract Vesting is Ownable {
uint256 initialUnlockPercent;
}

struct UserAllocation {
uint64 planId;
uint256 allocation;
uint256 claimed;
}

struct StorageUserAllocation {
uint64 planId;
uint64 allocation;
uint128 claimed;
}

uint256 constant SCALE_FACTOR = 1e12;

address public token;
address public vtToken;
uint256 public vestingStartDate;
uint256 public totalAllocation;
uint256 public totalClaimed;
VestingPlan[] public plans;

// 18 decimal: 62 + 62 = 124 bit (15.5 bytes)
// 16 bit for planId = 16 bit 2 bytes
// number: 58 + 58 = 116 bit (14.5 bytes) ~= 288230,376,151,711,744
// number + decimal = 120 bit (15 bytes) unused 256 - 120 = 136
mapping(address => StorageUserAllocation) private allocations;
mapping(address => uint256) public userPlanId;
mapping(address => uint256) public allocations;
mapping(address => uint256) public claimed;

event VestingPlanAdded(
uint256 planId,
Expand Down Expand Up @@ -73,25 +57,38 @@ contract Vesting is Ownable {
emit VestingPlanAdded(plans.length - 1, _lockPeriod, _vestingPeriod, _initialUnlockPercent);
}

// function allocateVesting(address user, uint64 planId, uint64 allocation) public onlyOwner {
// _saveUserAllocation(user, planId, allocation);
// totalAllocation += allocation;
// }
function _allocateVesting(address addr, uint256 planId, uint256 allocation) internal {
require(addr != address(0x0), 'V002');
require(allocations[addr] == 0, 'V003');
require(allocation > 0, 'V004');
require(planId < plans.length, 'PM012');

userPlanId[addr] = planId;
allocations[addr] = allocation;

ISQToken(vtToken).mint(addr, allocation);

emit VestingAllocated(addr, planId, allocation);
}

function batchAllocateVesting(
uint64[] calldata _planIds,
address[] calldata _users,
uint64[] calldata _allocations
uint256[] calldata _planIds,
address[] calldata _addrs,
uint256[] calldata _allocations
) external onlyOwner {
require(_users.length > 0, 'V005');
require(_users.length == _allocations.length, 'V006');
require(_users.length == _planIds.length, 'V006');
require(_addrs.length > 0, 'V005');
require(_addrs.length == _allocations.length, 'V006');
require(_addrs.length == _planIds.length, 'V006');

uint256 _total;
for (uint256 i = 0; i < _users.length; i++) {
_total += _saveUserAllocation(_users[i], _planIds[i], _allocations[i]);
for (uint256 i = 0; i < _addrs.length; i++) {
_allocateVesting(_addrs[i], _planIds[i], _allocations[i]);
_total += _allocations[i];
}

unchecked {
totalAllocation += _total;
}
totalAllocation += _total;
}

function withdrawAllByAdmin() external onlyOwner {
Expand All @@ -114,48 +111,40 @@ contract Vesting is Ownable {
_claim(msg.sender);
}

function claimFor(address user) external {
_claim(user);
function claimFor(address account) external {
_claim(account);
}

function _claim(address user) internal {
UserAllocation memory ua = userAllocation(user);
require(ua.allocation != 0, 'V011');
function _claim(address account) internal {
require(allocations[account] != 0, 'V011');

uint128 scaledAmount = _claimableAmount(user);
require(scaledAmount > 0, 'V012');
uint256 fullAmount = uint256(scaledAmount) * SCALE_FACTOR;
require(ua.claimed + fullAmount <= ua.allocation, 'V012');
uint256 amount = claimableAmount(account);
require(amount > 0, 'V012');

_updateUserAllocation(user, scaledAmount);
ISQToken(vtToken).burnFrom(user, fullAmount);
ISQToken(vtToken).burnFrom(account, amount);
claimed[account] += amount;
totalClaimed += amount;

totalClaimed += fullAmount;
require(claimed[account] <= allocations[account], 'V012');

require(IERC20(token).transfer(user, fullAmount), 'V008');
emit VestingClaimed(user, fullAmount);
require(IERC20(token).transfer(account, amount), 'V008');
emit VestingClaimed(account, amount);
}

function claimableAmount(address user) public view returns (uint256) {
return uint256(_claimableAmount(user)) * SCALE_FACTOR;
}

function _claimableAmount(address user) internal view returns (uint128) {
uint256 amount = unlockedAmount(user);
uint256 vtSQTAmount = IERC20(vtToken).balanceOf(user);
uint256 claimable = vtSQTAmount >= amount ? amount : vtSQTAmount;
return uint128(claimable / SCALE_FACTOR);
return vtSQTAmount >= amount ? amount : vtSQTAmount;
}

function unlockedAmount(address user) public view returns (uint256) {
UserAllocation memory ua = userAllocation(user);

// vesting start date is not set or allocation is empty
if (vestingStartDate == 0 || ua.allocation == 0) {
if (vestingStartDate == 0 || allocations[user] == 0) {
return 0;
}

VestingPlan memory plan = plans[ua.planId];
uint256 planId = userPlanId[user];
VestingPlan memory plan = plans[planId];
uint256 planStartDate = vestingStartDate + plan.lockPeriod;

if (block.timestamp <= planStartDate) {
Expand All @@ -165,54 +154,17 @@ contract Vesting is Ownable {
// no versting period or vesting period passed
uint256 planCompleteDate = planStartDate + plan.vestingPeriod;
if (plan.vestingPeriod == 0 || block.timestamp > planCompleteDate) {
return ua.allocation - ua.claimed;
return allocations[user] - claimed[user];
}

// druring plan period
uint256 vestedPeriod = block.timestamp - planStartDate;
uint256 initialAmount = (ua.allocation * plan.initialUnlockPercent) / 100;
uint256 vestingTokens = ua.allocation - initialAmount;
return initialAmount + (vestingTokens * vestedPeriod) / plan.vestingPeriod - ua.claimed;
uint256 initialAmount = (allocations[user] * plan.initialUnlockPercent) / 100;
uint256 vestingTokens = allocations[user] - initialAmount;
return initialAmount + (vestingTokens * vestedPeriod) / plan.vestingPeriod - claimed[user];
}

function plansLength() external view returns (uint256) {
return plans.length;
}

function userAllocation(address user) public view returns (UserAllocation memory) {
StorageUserAllocation memory ua = allocations[user];

uint256 allocation = uint256(ua.allocation) * SCALE_FACTOR;
uint256 claimed = uint256(ua.claimed) * SCALE_FACTOR;

return UserAllocation(ua.planId, allocation, claimed);
}

function _saveUserAllocation(
address user,
uint64 planId,
uint64 allocation
) private returns (uint256) {
StorageUserAllocation storage ua = allocations[user];

require(user != address(0x0), 'V002');
require(ua.allocation == 0, 'V003');
require(planId < plans.length, 'PM012');
require(allocation > 0, 'V004');
// require(allocation / 1e18 < 18446744073709551616, 'V005'); // 2 ** 64

ua.planId = planId;
ua.allocation = allocation;

uint256 fullLengthAllocation = uint256(allocation) * SCALE_FACTOR;

ISQToken(vtToken).mint(user, fullLengthAllocation);
emit VestingAllocated(user, planId, fullLengthAllocation);
return fullLengthAllocation;
}

function _updateUserAllocation(address user, uint128 amount) private {
StorageUserAllocation storage ua = allocations[user];
ua.claimed += amount;
}
}
75 changes: 48 additions & 27 deletions publish/ABI/Vesting.json
Original file line number Diff line number Diff line change
Expand Up @@ -135,19 +135,38 @@
{
"inputs": [
{
"internalType": "uint64[]",
"internalType": "address",
"name": "",
"type": "address"
}
],
"name": "allocations",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256[]",
"name": "_planIds",
"type": "uint64[]"
"type": "uint256[]"
},
{
"internalType": "address[]",
"name": "_users",
"name": "_addrs",
"type": "address[]"
},
{
"internalType": "uint64[]",
"internalType": "uint256[]",
"name": "_allocations",
"type": "uint64[]"
"type": "uint256[]"
}
],
"name": "batchAllocateVesting",
Expand All @@ -166,7 +185,7 @@
"inputs": [
{
"internalType": "address",
"name": "user",
"name": "account",
"type": "address"
}
],
Expand Down Expand Up @@ -194,6 +213,25 @@
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"name": "claimed",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "owner",
Expand Down Expand Up @@ -344,33 +382,16 @@
"inputs": [
{
"internalType": "address",
"name": "user",
"name": "",
"type": "address"
}
],
"name": "userAllocation",
"name": "userPlanId",
"outputs": [
{
"components": [
{
"internalType": "uint64",
"name": "planId",
"type": "uint64"
},
{
"internalType": "uint256",
"name": "allocation",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "claimed",
"type": "uint256"
}
],
"internalType": "struct Vesting.UserAllocation",
"internalType": "uint256",
"name": "",
"type": "tuple"
"type": "uint256"
}
],
"stateMutability": "view",
Expand Down
6 changes: 3 additions & 3 deletions publish/testnet.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,9 @@
},
"Vesting": {
"innerAddress": "",
"address": "0x61562768175432072cdD6C25De9ED74e24f1a458",
"bytecodeHash": "6474fb048ba8a9246340861f1dfdbb9dcacea8c13266c132a856e22442cb2507",
"lastUpdate": "Wed, 14 Feb 2024 07:56:53 GMT"
"address": "0x0E0730B1ec1D0E4C1AE15C73b0Be089d07261fBB",
"bytecodeHash": "7857569b0706b17a58c6a9c75b6636fae0c764ef967bafaf927f986882da4317",
"lastUpdate": "Wed, 14 Feb 2024 08:29:30 GMT"
}
},
"child": {
Expand Down
8 changes: 4 additions & 4 deletions test/Vesting.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import * as console from 'console';

type ClaimVestingEvent = { user: string; amount: BigNumber };

const SCALE_FACTOR = 1e12;
const SCALE_FACTOR = 1;
function formatAllocation(ethAmount: number | string): BigNumber {
return utils.parseEther(String(ethAmount)).div(SCALE_FACTOR);
}
Expand Down Expand Up @@ -62,8 +62,8 @@ describe('Vesting Contract', () => {
};

const checkAllocation = async (planId: number, user: string, allocation: number) => {
expect((await vestingContract.userAllocation(user)).planId).to.equal(planId);
expect((await vestingContract.userAllocation(user)).allocation).to.equal(parseEther(allocation));
expect(await vestingContract.userPlanId(user)).to.equal(planId);
expect(await vestingContract.allocations(user)).to.equal(parseEther(allocation));
expect(await vtSQToken.balanceOf(user)).to.equal(parseEther(allocation));
};

Expand Down Expand Up @@ -464,7 +464,7 @@ describe('Vesting Contract', () => {
await sqToken.transfer(vestingContract.address, utils.parseEther(total.toString()));
await vestingContract.startVesting(startDate);
for (const wallet of wallets) {
const allocation = (await vestingContract.userAllocation(wallet.address)).allocation;
const allocation = await vestingContract.allocations(wallet.address);
await vtSQToken.connect(wallet).approve(vestingContract.address, allocation);
}

Expand Down

0 comments on commit a723c98

Please sign in to comment.