Skip to content

Commit

Permalink
v2 first pass
Browse files Browse the repository at this point in the history
  • Loading branch information
Jehosephat committed Feb 6, 2025
1 parent 1db5079 commit 8bde312
Show file tree
Hide file tree
Showing 4 changed files with 73 additions and 47 deletions.
45 changes: 44 additions & 1 deletion chain-api/src/types/TokenBalance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -495,6 +495,12 @@ export class TokenBalance extends ChainObject {
let remainingQuantityToUnlock = quantity;

for (const hold of unexpiredLockedHolds) {
// don't try to unlock vesting holds
if (hold.starts !== undefined) {
updated.push(hold);
continue;
}

// if neither the authority nor the name match, just leave this hold alone
if (!this.isCallingUserAuthorized(hold, name, callingUser, isTokenAuthority)) {
updated.push(hold);
Expand Down Expand Up @@ -535,10 +541,18 @@ export class TokenBalance extends ChainObject {
}

private getCurrentLockedQuantity(currentTime: number): BigNumber {
return this.getUnexpiredLockedHolds(currentTime).reduce(
const unexpiredHolds = this.getUnexpiredLockedHolds(currentTime)

const totalNonVestingLockedQuantity = unexpiredHolds.filter(h => h.starts === undefined).reduce(
(sum, h) => sum.plus(h.quantity),
new BigNumber(0)
);
const totalVestingLockedQuantity = unexpiredHolds.filter(h => h.starts !== undefined).reduce(
(sum, h) => sum.plus(TokenHold.getLockedVestingQuantity(h, currentTime)),
new BigNumber(0)
);

return totalNonVestingLockedQuantity.plus(totalVestingLockedQuantity);
}

private ensureContainsNoNftInstances(): void {
Expand Down Expand Up @@ -598,6 +612,10 @@ export class TokenHold {
@IsUserAlias()
lockAuthority?: string;

@Min(0)
@IsInt()
public readonly starts?: number;

public constructor(params?: {
createdBy: string;
instanceId: BigNumber;
Expand All @@ -606,6 +624,7 @@ export class TokenHold {
expires?: number;
name?: string;
lockAuthority?: string;
starts?: number;
}) {
if (params) {
this.createdBy = params.createdBy;
Expand All @@ -619,6 +638,9 @@ export class TokenHold {
if (params.lockAuthority) {
this.lockAuthority = params.lockAuthority;
}
if (params.starts) {
this.starts = params.starts;
}
}
}

Expand All @@ -630,6 +652,7 @@ export class TokenHold {
expires: number | undefined;
name: string | undefined;
lockAuthority: string | undefined;
starts: number | undefined;
}): Promise<TokenHold> {
const hold = new TokenHold({ ...params });

Expand Down Expand Up @@ -661,4 +684,24 @@ export class TokenHold {
return -1;
}
}

// For vesting holds, this returns the quantity that is currently locked by vesting
public static getLockedVestingQuantity(hold: TokenHold, currentTime: number): BigNumber {
if (hold.starts === undefined) {
return new BigNumber(0);
}
// if the current time is before the vesting starts, the full quantity is locked (cliff)
const timeSinceStart = currentTime - hold.starts;
if (timeSinceStart < 0) {
return hold.quantity;
}
// if the current time is after the vesting expires, the full quantity is unlocked
if (timeSinceStart > hold.expires - hold.starts) {
return new BigNumber(0);
}
// if the current time is between the vesting starts and expires, the quantity is partially unlocked
const perPeriodQuantity = hold.quantity.div(hold.expires - hold.starts);
const vestedQuantity = perPeriodQuantity.times(timeSinceStart);
return hold.quantity.minus(vestedQuantity);
}
}
6 changes: 5 additions & 1 deletion chaincode/src/locks/lockTokens.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ export interface LockTokenParams {
allowancesToUse: string[];
expires: number;
name: string | undefined;
starts: number | undefined;
verifyAuthorizedOnBehalf: (c: TokenClassKey) => Promise<AuthorizedOnBehalf | undefined>;
}

Expand All @@ -57,6 +58,7 @@ export async function lockToken(
allowancesToUse,
name,
expires,
starts,
verifyAuthorizedOnBehalf
}: LockTokenParams
): Promise<TokenBalance> {
Expand Down Expand Up @@ -124,7 +126,8 @@ export async function lockToken(
created: ctx.txUnixTime,
expires: expires,
name: name,
lockAuthority
lockAuthority,
starts
});

if (tokenInstanceKey.isFungible()) {
Expand Down Expand Up @@ -169,6 +172,7 @@ export async function lockTokens(
allowancesToUse,
name,
expires,
starts: undefined, // don't allow vesting locks on batch locking
verifyAuthorizedOnBehalf: verifyAuthorizedOnBehalf
});
responses.push(updatedBalance);
Expand Down
19 changes: 6 additions & 13 deletions chaincode/src/vesting/vestingToken.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,11 +124,12 @@ describe("VestingToken", () => {
plainToInstance(TokenHold, {
created: ctx.txUnixTime,
createdBy: users.testUser1Id,
expires: vestingTokenDto.startDate + 1000 * 24 * 60 * 60 * allocation1.cliff,
expires: vestingTokenDto.startDate + 1000 * 24 * 60 * 60 * (allocation1.cliff + 1),
instanceId: 0,
lockAuthority: users.testAdminId,
name: "SuperTokenTGE-allocation1-0",
quantity: 100
quantity: 100,
starts: vestingTokenDto.startDate + 1000 * 24 * 60 * 60 * allocation1.cliff
})
]
}),
Expand All @@ -141,20 +142,12 @@ describe("VestingToken", () => {
plainToInstance(TokenHold, {
created: ctx.txUnixTime,
createdBy: users.testUser2Id,
expires: vestingTokenDto.startDate + 1000 * 24 * 60 * 60 * allocation2.cliff,
expires: vestingTokenDto.startDate + 1000 * 24 * 60 * 60 * (allocation2.cliff + 2),
instanceId: 0,
lockAuthority: users.testAdminId,
name: "SuperTokenTGE-allocation2-0",
quantity: 25
}),
plainToInstance(TokenHold, {
created: ctx.txUnixTime,
createdBy: users.testUser2Id,
expires: vestingTokenDto.startDate + 1000 * 24 * 60 * 60 * (allocation2.cliff + 1),
instanceId: 0,
lockAuthority: users.testAdminId,
name: "SuperTokenTGE-allocation2-1",
quantity: 25
quantity: 25,
starts: vestingTokenDto.startDate + 1000 * 24 * 60 * 60 * allocation2.cliff
})
]
}),
Expand Down
50 changes: 18 additions & 32 deletions chaincode/src/vesting/vestingToken.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ export async function createVestingToken(
): Promise<VestingToken> {
// TODO validations
// allocations add up to total supply/max cap
// start date is in the future and before expiration

const tokenClassParams: CreateTokenClassParams = {
...params
Expand All @@ -129,42 +130,27 @@ export async function createVestingToken(
};
const mintResponse = await mintTokenWithAllowance(ctx, mintParams);

//calculate lock amount per day using vesting period
const perLockQuantity = new BigNumber(allocation.quantity)
.dividedBy(new BigNumber(allocation.vestingDays))
.decimalPlaces(params.decimals);

//first lock expires on startDate + cliff (verify this is right)
//first lock period vests on startDate + cliff (verify this is right)
const expiration = params.startDate + daysToMilliseconds(allocation.cliff);

// Calculate total amount that will be locked based on perLockQuantity
const totalToLock = perLockQuantity.multipliedBy(allocation.vestingDays);
let remainingQuantity = allocation.quantity;

for (let i = 0; i < allocation.vestingDays; i++) {
// For the last iteration, use remaining quantity instead of perLockQuantity
const currentLockQuantity = i === allocation.vestingDays - 1 ? remainingQuantity : perLockQuantity;

const verifyAuthorizedOnBehalf = async () => {
return {
callingOnBehalf: allocation.owner,
callingUser: ctx.callingUser
};
const verifyAuthorizedOnBehalf = async () => {
return {
callingOnBehalf: allocation.owner,
callingUser: ctx.callingUser
};
};

const lockResponse = await lockToken(ctx, {
owner: allocation.owner,
lockAuthority: ctx.callingUser,
tokenInstanceKey,
quantity: currentLockQuantity,
allowancesToUse: [],
name: `${params.vestingName}-${allocation.name}-${i}`,
expires: expiration + daysToMilliseconds(i),
verifyAuthorizedOnBehalf
});

remainingQuantity = remainingQuantity.minus(currentLockQuantity);
}
const lockResponse = await lockToken(ctx, {
owner: allocation.owner,
lockAuthority: ctx.callingUser,
tokenInstanceKey,
quantity: allocation.quantity,
allowancesToUse: [],
name: `${params.vestingName}-${allocation.name}-${i}`,
expires: expiration + daysToMilliseconds(i),
verifyAuthorizedOnBehalf,
starts: params.startDate + daysToMilliseconds(allocation.cliff)
});
}

// construct and save VestingToken object
Expand Down

0 comments on commit 8bde312

Please sign in to comment.