From 4cd0893a16b87846825cc95154f18e2f5a3706a0 Mon Sep 17 00:00:00 2001 From: Noah Saso Date: Fri, 20 Oct 2023 14:25:45 -0700 Subject: [PATCH] Use actual rebalancer types. --- packages/state/recoil/selectors/index.ts | 1 + packages/state/recoil/selectors/valence.ts | 21 ++- .../ConfigureRebalancer/Component.tsx | 3 +- .../treasury/ConfigureRebalancer/index.tsx | 143 +++++++++++++++--- .../dao/DaoTreasuryHistoryGraph.tsx | 12 +- .../stateful/recoil/selectors/treasury.ts | 26 ++-- .../stateful/server/makeGetDaoStaticProps.ts | 21 ++- packages/types/account.ts | 13 +- .../contracts/ValenceServiceRebalancer.ts | 128 ++++++++++++++++ 9 files changed, 320 insertions(+), 48 deletions(-) create mode 100644 packages/types/contracts/ValenceServiceRebalancer.ts diff --git a/packages/state/recoil/selectors/index.ts b/packages/state/recoil/selectors/index.ts index 6e7094e9cb..361fd3b6a8 100644 --- a/packages/state/recoil/selectors/index.ts +++ b/packages/state/recoil/selectors/index.ts @@ -13,5 +13,6 @@ export * from './profile' export * from './proposal' export * from './token' export * from './treasury' +export * from './valence' export * from './wallet' export * from './wynd' diff --git a/packages/state/recoil/selectors/valence.ts b/packages/state/recoil/selectors/valence.ts index e79ee7d5a9..9841f3915e 100644 --- a/packages/state/recoil/selectors/valence.ts +++ b/packages/state/recoil/selectors/valence.ts @@ -37,6 +37,19 @@ export const valenceAccountsSelector = selectorFamily< // TODO(rebalancer): Get config const config: ValenceAccountConfig = { rebalancer: { + config: { + base_denom: '', + has_min_balance: false, + last_rebalance: '', + max_limit: '', + pid: { + d: '', + i: '', + p: '', + }, + target_override_strategy: 'proportional', + targets: [], + }, targets: [ { source: { @@ -46,7 +59,9 @@ export const valenceAccountsSelector = selectorFamily< targets: [ { timestamp: 0, - target: 0.75, + denom: 'untrn', + last_i: ['', false], + percentage: '0.75', }, ], }, @@ -58,7 +73,9 @@ export const valenceAccountsSelector = selectorFamily< targets: [ { timestamp: 0, - target: 0.25, + denom: 'untrn', + last_i: ['', false], + percentage: '0.25', }, ], }, diff --git a/packages/stateful/actions/core/treasury/ConfigureRebalancer/Component.tsx b/packages/stateful/actions/core/treasury/ConfigureRebalancer/Component.tsx index c143c328e7..3b05897991 100644 --- a/packages/stateful/actions/core/treasury/ConfigureRebalancer/Component.tsx +++ b/packages/stateful/actions/core/treasury/ConfigureRebalancer/Component.tsx @@ -24,6 +24,7 @@ import { ValenceAccount, } from '@dao-dao/types' import { ActionComponent } from '@dao-dao/types/actions' +import { TargetOverrideStrategy } from '@dao-dao/types/contracts/ValenceServiceRebalancer' import { convertMicroDenomToDenomWithDecimals, formatPercentOf100, @@ -49,7 +50,7 @@ export type ConfigureRebalancerData = { kd: number } maxLimitBps?: number - targetOverrideStrategy: string + targetOverrideStrategy: TargetOverrideStrategy } export type ConfigureRebalancerOptions = { diff --git a/packages/stateful/actions/core/treasury/ConfigureRebalancer/index.tsx b/packages/stateful/actions/core/treasury/ConfigureRebalancer/index.tsx index 4817c56c02..c5ab0bdf65 100644 --- a/packages/stateful/actions/core/treasury/ConfigureRebalancer/index.tsx +++ b/packages/stateful/actions/core/treasury/ConfigureRebalancer/index.tsx @@ -3,8 +3,13 @@ import { useCallback } from 'react' import { useFormContext } from 'react-hook-form' import { useRecoilValueLoadable, waitForAll } from 'recoil' +import { valenceAccountsSelector } from '@dao-dao/state' import { historicalUsdPriceSelector } from '@dao-dao/state/recoil/selectors/osmosis' -import { BalanceEmoji, ChainProvider } from '@dao-dao/stateless' +import { + BalanceEmoji, + ChainProvider, + useCachedLoadingWithError, +} from '@dao-dao/stateless' import { ChainId, TokenType, UseDecodedCosmosMsg } from '@dao-dao/types' import { ActionComponent, @@ -14,8 +19,15 @@ import { UseDefaults, UseTransformToCosmos, } from '@dao-dao/types/actions' +import { ExecuteMsg as ValenceAccountExecuteMsg } from '@dao-dao/types/contracts/ValenceAccount' +import { + RebalancerData, + RebalancerUpdateData, +} from '@dao-dao/types/contracts/ValenceServiceRebalancer' import { decodePolytoneExecuteMsg, + encodeMessageAsBase64, + getAccountAddress, getNativeIbcUsdc, getNativeTokenForChainId, loadableToLoadingData, @@ -34,17 +46,61 @@ import { const useDefaults: UseDefaults = () => { const { + address, chain: { chain_id: chainId }, + context, } = useActionOptions() - const nativeDenom = getNativeTokenForChainId(chainId).denomOrAddress - const usdcDenom = getNativeIbcUsdc(chainId)?.denomOrAddress + // Get Neutron account. + const neutronAddress = + context.type === ActionContextType.Dao + ? getAccountAddress({ + accounts: context.info.accounts, + chainId: ChainId.NeutronMainnet, + }) + : chainId === ChainId.NeutronMainnet + ? address + : null + if (!neutronAddress) { + throw new Error('Missing Neutron account.') + } + + const valenceAccountsLoading = useCachedLoadingWithError( + valenceAccountsSelector({ + chainId: ChainId.NeutronMainnet, + address, + }) + ) + const valenceAccount = + valenceAccountsLoading.loading || valenceAccountsLoading.errored + ? undefined + : valenceAccountsLoading.data[0] + + const nativeDenom = getNativeTokenForChainId( + ChainId.NeutronMainnet + ).denomOrAddress + const usdcDenom = getNativeIbcUsdc(ChainId.NeutronMainnet)?.denomOrAddress + + const rebalancerConfig = valenceAccount?.config.rebalancer?.config return { - chainId, + valenceAccount, + chainId: ChainId.NeutronMainnet, + trustee: rebalancerConfig?.trustee || undefined, baseDenom: - REBALANCER_BASE_TOKEN_ALLOWLIST[chainId as ChainId]?.[0] ?? nativeDenom, - tokens: [ + (rebalancerConfig?.base_denom || + REBALANCER_BASE_TOKEN_ALLOWLIST[ChainId.NeutronMainnet]?.[0]) ?? + nativeDenom, + tokens: rebalancerConfig?.targets.map( + ({ denom, percentage, min_balance }) => ({ + denom, + percent: Number(percentage), + minBalance: + min_balance && !isNaN(Number(min_balance)) + ? Number(min_balance) + : undefined, + }) + ) || [ { denom: nativeDenom, percent: 50, @@ -58,17 +114,21 @@ const useDefaults: UseDefaults = () => { ] : []), ], - // TODO: pick default + // TODO: pick defaults pid: { - kp: 0.1, - ki: 0.1, - kd: 0.1, + kp: Number(rebalancerConfig?.pid.p || 0.1), + ki: Number(rebalancerConfig?.pid.i || 0.1), + kd: Number(rebalancerConfig?.pid.d || 0.1), }, + maxLimitBps: + rebalancerConfig?.max_limit && !isNaN(Number(rebalancerConfig.max_limit)) + ? Number(rebalancerConfig.max_limit) + : // TODO: pick default + // 5% + 500, // TODO: pick default - // 5% - maxLimitBps: 500, - // TODO: pick default - targetOverrideStrategy: 'proportional', + targetOverrideStrategy: + rebalancerConfig?.target_override_strategy || 'proportional', } } @@ -244,6 +304,7 @@ const useDecodedCosmosMsg: UseDecodedCosmosMsg = ( if ( serviceName !== 'rebalancer' || + !data || !objectMatchesStructure(data, { base_denom: {}, targets: {}, @@ -263,7 +324,26 @@ const useDecodedCosmosMsg: UseDecodedCosmosMsg = ( return { match: true, - data: {}, + data: { + chainId, + trustee: data.trustee || undefined, + baseDenom: data.base_denom, + tokens: data.targets.map(({ denom, min_balance, percentage }) => ({ + denom, + percent: percentage, + minBalance: + min_balance && !isNaN(Number(min_balance)) + ? Number(min_balance) + : undefined, + })), + pid: { + kp: Number(data.pid.p), + ki: Number(data.pid.i), + kd: Number(data.pid.d), + }, + maxLimitBps: data.max_limit || undefined, + targetOverrideStrategy: data.target_override_strategy, + }, } } @@ -284,7 +364,16 @@ export const makeConfigureRebalancerAction: ActionMaker< ConfigureRebalancerData > = () => { return useCallback( - ({ valenceAccount, chainId, tokens, pid }: ConfigureRebalancerData) => { + ({ + valenceAccount, + chainId, + trustee, + baseDenom, + tokens, + pid, + maxLimitBps, + targetOverrideStrategy, + }: ConfigureRebalancerData) => { if (!valenceAccount) { throw new Error('Missing valence account.') } @@ -298,8 +387,26 @@ export const makeConfigureRebalancerAction: ActionMaker< contract_addr: valenceAccount.address, funds: [], msg: { - update_config: {}, - }, + update_service: { + data: encodeMessageAsBase64({ + base_denom: baseDenom, + max_limit: maxLimitBps, + pid: { + p: pid.kp.toString(), + i: pid.ki.toString(), + d: pid.kd.toString(), + }, + target_override_strategy: targetOverrideStrategy, + targets: tokens.map(({ denom, percent, minBalance }) => ({ + denom, + min_balance: + minBalance && BigInt(minBalance).toString(), + percentage: percent, + })), + trustee, + } as RebalancerUpdateData), + }, + } as ValenceAccountExecuteMsg, }, }, }) diff --git a/packages/stateful/components/dao/DaoTreasuryHistoryGraph.tsx b/packages/stateful/components/dao/DaoTreasuryHistoryGraph.tsx index 0fddf1cf7c..759de6ba1a 100644 --- a/packages/stateful/components/dao/DaoTreasuryHistoryGraph.tsx +++ b/packages/stateful/components/dao/DaoTreasuryHistoryGraph.tsx @@ -70,7 +70,11 @@ export const DaoTreasuryHistoryGraph = ({ precision, filter: account && { account, - rebalancerOnly: showRebalancer, + // Filter by rebalancer tokens. + tokens: + showRebalancer && account.type === AccountType.Valence + ? account.config.rebalancer?.targets.map(({ source }) => source) + : undefined, }, startSecondsAgo, }) @@ -143,17 +147,17 @@ export const DaoTreasuryHistoryGraph = ({ return null } - const { target } = targets[targetIndex] + const { percentage } = targets[targetIndex] // The target at this point is based on the total value. - return totalValue * target + return totalValue * Number(percentage) } ) // Add current target. const currentTarget = treasuryValueHistory.data.total.currentValue * - targets[targets.length - 1].target + Number(targets[targets.length - 1].percentage) data.push(currentTarget) const tokenIndex = treasuryValueHistory.data.tokens.findIndex( diff --git a/packages/stateful/recoil/selectors/treasury.ts b/packages/stateful/recoil/selectors/treasury.ts index 92fe156b01..dbbf8dd345 100644 --- a/packages/stateful/recoil/selectors/treasury.ts +++ b/packages/stateful/recoil/selectors/treasury.ts @@ -14,8 +14,8 @@ import { } from '@dao-dao/state' import { Account, - AccountType, GenericToken, + GenericTokenSource, LoadingTokens, TokenCardInfo, TokenType, @@ -214,11 +214,7 @@ export const treasuryTokenCardInfosForDaoSelector = selectorFamily< }, }) -const ACCOUNT_FILTER_PROPERTIES: (keyof Account)[] = [ - 'type', - 'chainId', - 'address', -] +const ACCOUNT_FILTER_PROPERTIES = ['type', 'chainId', 'address'] as const export const daoTreasuryValueHistorySelector = selectorFamily< { @@ -243,10 +239,9 @@ export const daoTreasuryValueHistorySelector = selectorFamily< startSecondsAgo: number filter?: { // Filter by any of the account properties. - account?: Partial - // If `account` is a valence account and `rebalancerOnly` is true, only - // show valence account assets that are being rebalanced. - rebalancerOnly?: boolean + account?: Partial> + // If defined, only show these tokens. + tokens?: GenericTokenSource[] } }> >({ @@ -355,13 +350,10 @@ export const daoTreasuryValueHistorySelector = selectorFamily< (token) => // Can only compute price if token decimals loaded correctly. token.decimals > 0 && - // Filter by rebalancer tokens in valence account. - (!filter || - !filter.rebalancerOnly || - filter.account?.type !== AccountType.Valence || - !filter.account.config?.rebalancer || - filter.account.config.rebalancer.targets.some( - ({ source }) => + // Filter by tokens. + (!filter?.tokens || + filter.tokens.some( + (source) => serializeTokenSource(source) === serializeTokenSource(token) )) ) diff --git a/packages/stateful/server/makeGetDaoStaticProps.ts b/packages/stateful/server/makeGetDaoStaticProps.ts index 5c29ce2504..f7732e6479 100644 --- a/packages/stateful/server/makeGetDaoStaticProps.ts +++ b/packages/stateful/server/makeGetDaoStaticProps.ts @@ -274,6 +274,19 @@ export const makeGetDaoStaticProps: GetDaoStaticPropsMaker = // TODO(rebalancer): Get config const config: ValenceAccountConfig = { rebalancer: { + config: { + base_denom: '', + has_min_balance: false, + last_rebalance: '', + max_limit: '', + pid: { + d: '', + i: '', + p: '', + }, + target_override_strategy: 'proportional', + targets: [], + }, targets: [ { source: { @@ -283,7 +296,9 @@ export const makeGetDaoStaticProps: GetDaoStaticPropsMaker = targets: [ { timestamp: 0, - target: 0.75, + denom: 'untrn', + last_i: ['', false], + percentage: '0.75', }, ], }, @@ -295,7 +310,9 @@ export const makeGetDaoStaticProps: GetDaoStaticPropsMaker = targets: [ { timestamp: 0, - target: 0.25, + denom: 'untrn', + last_i: ['', false], + percentage: '0.25', }, ], }, diff --git a/packages/types/account.ts b/packages/types/account.ts index 642eefe424..2591e2b524 100644 --- a/packages/types/account.ts +++ b/packages/types/account.ts @@ -2,6 +2,10 @@ // address on the native chain. `polytone` means it's a polytone account // controlled by an account on another chain. `valence` means it's a valence +import { + ParsedTarget, + RebalancerConfig, +} from './contracts/ValenceServiceRebalancer' import { GenericTokenSource } from './token' // account controlled by an account on the same or another chain. @@ -29,6 +33,8 @@ export type ValenceAccountTypeConfig = { export type ValenceAccountConfig = { // If rebalancer setup, this will be defined. rebalancer?: { + config: RebalancerConfig + // Process targest. targets: ValenceAccountRebalancerTarget[] } } @@ -36,11 +42,10 @@ export type ValenceAccountConfig = { export type ValenceAccountRebalancerTarget = { // The source that uniquely identifies a token. source: GenericTokenSource - targets: { + // Target changes over time for this token. + targets: ({ timestamp: number - // Proportion between 0 and 1. - target: number - }[] + } & ParsedTarget)[] } export type BaseAccount = { diff --git a/packages/types/contracts/ValenceServiceRebalancer.ts b/packages/types/contracts/ValenceServiceRebalancer.ts new file mode 100644 index 0000000000..52b49960c3 --- /dev/null +++ b/packages/types/contracts/ValenceServiceRebalancer.ts @@ -0,0 +1,128 @@ +import { Addr, Decimal, Timestamp, Uint128 } from './common' + +export interface InstantiateMsg { + auctions_manager_addr: string + base_denom_whitelist: string[] + cycle_start: Timestamp + denom_whitelist: string[] + services_manager_addr: string +} +export type ExecuteMsg = + | { + system_rebalance: { + limit?: number | null + } + } + | { + register: { + data?: RebalancerData | null + register_for: string + } + } + | { + deregister: { + deregister_for: string + } + } + | { + update: { + data: RebalancerUpdateData + update_for: string + } + } + | { + pause: { + pause_for: string + sender: string + } + } + | { + resume: { + resume_for: string + sender: string + } + } +export type TargetOverrideStrategy = 'proportional' | 'priority' +export type OptionalFieldForString = + | 'clear' + | { + set: string + } +export interface RebalancerData { + base_denom: string + max_limit?: number | null + pid: PID + target_override_strategy: TargetOverrideStrategy + targets: Target[] + trustee?: string | null +} +export interface PID { + d: string + i: string + p: string +} +export interface Target { + denom: string + min_balance?: Uint128 | null + percentage: number +} +export interface RebalancerUpdateData { + base_denom?: string | null + max_limit?: number | null + pid?: PID | null + target_override_strategy?: TargetOverrideStrategy | null + targets: Target[] + trustee?: OptionalFieldForString | null +} +export type QueryMsg = + | { + get_config: { + addr: string + } + } + | { + get_system_status: {} + } +export type SignedDecimal = [Decimal, boolean] +export interface RebalancerConfig { + base_denom: string + has_min_balance: boolean + is_paused?: Addr | null + last_rebalance: Timestamp + max_limit: Decimal + pid: ParsedPID + target_override_strategy: TargetOverrideStrategy + targets: ParsedTarget[] + trustee?: string | null +} +export interface ParsedPID { + d: Decimal + i: Decimal + p: Decimal +} +export interface ParsedTarget { + denom: string + last_i: SignedDecimal + last_input?: Decimal | null + min_balance?: Uint128 | null + percentage: Decimal +} +export type SystemRebalanceStatus = + | { + not_started: { + cycle_start: Timestamp + } + } + | { + processing: { + cycle_started: Timestamp + prices: [Pair, Decimal][] + start_from: Addr + } + } + | { + finished: { + next_cycle: Timestamp + } + } +export type Pair = [string, string]