Skip to content

Commit

Permalink
added cw-vesting transformers and more formulas
Browse files Browse the repository at this point in the history
  • Loading branch information
NoahSaso committed Aug 2, 2024
1 parent daf41b5 commit 64c4d2f
Show file tree
Hide file tree
Showing 7 changed files with 395 additions and 16 deletions.
7 changes: 7 additions & 0 deletions src/formulas/formulas/contract/common.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { ContractFormula } from '@/types'

import { ContractInfo } from '../types'
import { makeSimpleContractFormula } from '../utils'

export const info: ContractFormula<ContractInfo> = {
compute: async ({ contractAddress, getTransformationMatch }) => {
Expand All @@ -16,6 +17,12 @@ export const info: ContractFormula<ContractInfo> = {
},
}

// cw-ownership
export const ownership = makeSimpleContractFormula({
transformation: 'ownership',
fallbackKeys: ['ownership'],
})

export const instantiatedAt: ContractFormula<string> = {
compute: async ({ contractAddress, getContract }) => {
const timestamp = (
Expand Down
142 changes: 135 additions & 7 deletions src/formulas/formulas/contract/external/cwVesting.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,151 @@
import { Uint128 } from '@dao-dao/types'

import { ContractFormula } from '@/types'
import { dbKeyToKeys } from '@/utils'

import { makeSimpleContractFormula } from '../../utils'
import { Vest } from './cwVesting.types'

type ValidatorStake = {
validator: string
timeMs: number
// Staked + unstaking
amount: string
}

export const info: ContractFormula = {
compute: async ({ contractAddress, get }) =>
await get(contractAddress, 'vesting'),
}
export { ownership } from '../common'

export const info: ContractFormula<Vest> = makeSimpleContractFormula({
transformation: 'vesting',
fallbackKeys: ['vesting'],
})

export const vested: ContractFormula = makeSimpleContractFormula<
Vest,
Uint128,
{
/**
* Nanosecond timestamp.
*/
t: string
}
>({
transformation: 'vesting',
fallbackKeys: ['vesting'],
transform: ({ vested, start_time }, { args: { t }, block }) => {
if (t && isNaN(Number(t))) {
throw new Error('Invalid timestamp (NaN).')
}

// Convert millisecond block timestamp to nanoseconds.
const tNanos = t ? BigInt(t) : block.timeUnixMs * 1_000_000n
const elapsed = tNanos - BigInt(start_time)

if ('constant' in vested) {
return vested.constant.y
} else if ('saturating_linear' in vested) {
// https://github.com/wynddao/wynddao/blob/909ee2f2b382eff06cf9f7af4102f0920da8c8a3/packages/utils/src/curve.rs#L202-L209

const minX = BigInt(vested.saturating_linear.min_x)
const maxX = BigInt(vested.saturating_linear.max_x)
const minY = BigInt(vested.saturating_linear.min_y)
const maxY = BigInt(vested.saturating_linear.max_y)

export const unbondingDurationSeconds: ContractFormula = {
compute: async ({ contractAddress, get }) =>
await get(contractAddress, 'ubs'),
if (elapsed < minX || minX === maxX) {
return minY.toString()
} else if (elapsed > maxX) {
return maxY.toString()
} else {
return interpolate([minX, minY], [maxX, maxY], elapsed).toString()
}
} else if ('piecewise_linear' in vested) {
// https://github.com/wynddao/wynddao/blob/909ee2f2b382eff06cf9f7af4102f0920da8c8a3/packages/utils/src/curve.rs#L270-L302

// figure out the pair of points it lies between
const steps = vested.piecewise_linear.steps
let [prev, next]: [[bigint, bigint] | undefined, [bigint, bigint]] = [
undefined,
[BigInt(steps[0][0]), BigInt(steps[0][1])],
]
for (const [stepX, stepY] of steps) {
// only break if x is not above prev
if (elapsed > next[0]) {
prev = next
next = [BigInt(stepX), BigInt(stepY)]
} else {
break
}
}
// at this time:
// prev may be None (this was lower than first point)
// x may equal prev.0 (use this value)
// x may be greater than next (if higher than last item)
// OR x may be between prev and next (interpolate)
if (prev) {
if (elapsed === prev[0]) {
// this handles exact match with low end
return prev[1].toString()
} else if (elapsed >= next[0]) {
// this handles both higher than all and exact match
return next[1].toString()
} else {
// here we do linear interpolation
return interpolate(prev, next, elapsed).toString()
}
} else {
// lower than all, use first
return next[1].toString()
}
} else {
throw new Error('Invalid curve')
}
},
})

// https://github.com/wynddao/wynddao/blob/909ee2f2b382eff06cf9f7af4102f0920da8c8a3/packages/utils/src/curve.rs#L248-L255
const interpolate = (
[minX, minY]: [bigint, bigint],
[maxX, maxY]: [bigint, bigint],
x: bigint
): bigint => {
if (maxY > minY) {
return minY + ((maxY - minY) * (x - minX)) / (maxX - minX)
} else {
// maxY <= minY
return minY - ((minY - maxY) * (x - minX)) / (maxX - minX)
}
}

export const totalToVest: ContractFormula<Uint128> = makeSimpleContractFormula<
Vest,
Uint128
>({
transformation: 'vesting',
fallbackKeys: ['vesting'],
transform: ({ vested }) => {
if ('constant' in vested) {
return vested.constant.y
} else if ('saturating_linear' in vested) {
const minY = BigInt(vested.saturating_linear.min_y)
const maxY = BigInt(vested.saturating_linear.max_y)
return (maxY > minY ? maxY : minY).toString()
} else if ('piecewise_linear' in vested) {
const maxY = vested.piecewise_linear.steps
.map(([_, y]) => BigInt(y))
.reduce((acc, y) => (y > acc ? y : acc), 0n)
return maxY.toString()
} else {
throw new Error('Invalid curve')
}
},
})

export const unbondingDurationSeconds: ContractFormula =
makeSimpleContractFormula({
transformation: 'ubs',
fallbackKeys: ['ubs'],
})

// The amount staked and unstaking for each validator over time.
export const validatorStakes: ContractFormula<ValidatorStake[]> = {
compute: async ({ contractAddress, getMap }) => {
Expand Down
211 changes: 211 additions & 0 deletions src/formulas/formulas/contract/external/cwVesting.types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
/**
* This file was automatically generated by @cosmwasm/[email protected].
* DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file,
* and run the @cosmwasm/ts-codegen generate command to regenerate this file.
*/

export type UncheckedDenom =
| {
native: string
}
| {
cw20: string
}
export type Schedule =
| 'saturating_linear'
| {
piecewise_linear: [number, Uint128][]
}
export type Uint128 = string
export type Timestamp = Uint64
export type Uint64 = string
export interface InstantiateMsg {
denom: UncheckedDenom
description?: string | null
owner?: string | null
recipient: string
schedule: Schedule
start_time?: Timestamp | null
title: string
total: Uint128
unbonding_duration_seconds: number
vesting_duration_seconds: number
}
export type ExecuteMsg =
| {
receive: Cw20ReceiveMsg
}
| {
distribute: {
amount?: Uint128 | null
}
}
| {
cancel: {}
}
| {
delegate: {
amount: Uint128
validator: string
}
}
| {
redelegate: {
amount: Uint128
dst_validator: string
src_validator: string
}
}
| {
undelegate: {
amount: Uint128
validator: string
}
}
| {
set_withdraw_address: {
address: string
}
}
| {
withdraw_delegator_reward: {
validator: string
}
}
| {
withdraw_canceled_payment: {
amount?: Uint128 | null
}
}
| {
register_slash: {
amount: Uint128
during_unbonding: boolean
time: Timestamp
validator: string
}
}
| {
update_ownership: Action
}
export type Binary = string
export type Action =
| {
transfer_ownership: {
expiry?: Expiration | null
new_owner: string
}
}
| 'accept_ownership'
| 'renounce_ownership'
export type Expiration =
| {
at_height: number
}
| {
at_time: Timestamp
}
| {
never: {}
}
export interface Cw20ReceiveMsg {
amount: Uint128
msg: Binary
sender: string
}
export type QueryMsg =
| {
ownership: {}
}
| {
info: {}
}
| {
distributable: {
t?: Timestamp | null
}
}
| {
vested: {
t?: Timestamp | null
}
}
| {
total_to_vest: {}
}
| {
vest_duration: {}
}
| {
stake: StakeTrackerQuery
}
export type StakeTrackerQuery =
| {
cardinality: {
t: Timestamp
}
}
| {
total_staked: {
t: Timestamp
}
}
| {
validator_staked: {
t: Timestamp
validator: string
}
}
export type CheckedDenom =
| {
native: string
}
| {
cw20: Addr
}
export type Addr = string
export type Status =
| ('unfunded' | 'funded')
| {
canceled: {
owner_withdrawable: Uint128
}
}
export type Curve =
| {
constant: {
y: Uint128
}
}
| {
saturating_linear: SaturatingLinear
}
| {
piecewise_linear: PiecewiseLinear
}
export interface Vest {
claimed: Uint128
denom: CheckedDenom
description?: string | null
recipient: Addr
slashed: Uint128
start_time: Timestamp
status: Status
title: string
vested: Curve
}
export interface SaturatingLinear {
max_x: number
max_y: Uint128
min_x: number
min_y: Uint128
}
export interface PiecewiseLinear {
steps: [number, Uint128][]
}
export interface OwnershipForAddr {
owner?: Addr | null
pending_expiry?: Expiration | null
pending_owner?: Addr | null
}
export type NullableUint64 = Uint64 | null
Loading

0 comments on commit 64c4d2f

Please sign in to comment.