Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Unify pool state #383

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
8 changes: 4 additions & 4 deletions apps/ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,12 +50,12 @@
"@sentry/types": "^7.9.0",
"@solana/spl-token": "^0.3.5",
"@solana/web3.js": "^1.62.0",
"@swim-io/aptos": "^0.40.0",
"@swim-io/core": "^0.40.0",
"@swim-io/evm": "^0.40.0",
"@swim-io/aptos": "workspace:^",
"@swim-io/core": "workspace:^",
"@swim-io/evm": "workspace:^",
"@swim-io/evm-contracts": "^0.40.0",
"@swim-io/pool-math": "^0.40.0",
"@swim-io/solana": "^0.40.0",
"@swim-io/solana": "workspace:^",
"@swim-io/solana-contracts": "^0.40.0",
"@swim-io/token-projects": "^0.40.0",
"@swim-io/utils": "^0.40.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ import {
} from "../solana";

export const useSolanaPoolOperationsMutation = () => {
const { env } = useEnvironment();
const config = useEnvironment(selectConfig, shallow);
const { pools } = config;
const { data: splTokenAccounts = [] } = useUserSolanaTokenAccountsQuery();
Expand Down Expand Up @@ -65,7 +64,6 @@ export const useSolanaPoolOperationsMutation = () => {
let inputTxId = inputState.txId;
if (inputTxId === null) {
inputTxId = await doSingleSolanaPoolOperation(
env,
solanaClient,
wallet,
splTokenAccounts,
Expand Down Expand Up @@ -97,7 +95,6 @@ export const useSolanaPoolOperationsMutation = () => {
inputTx,
);
const outputTxId = await doSingleSolanaPoolOperation(
env,
solanaClient,
wallet,
splTokenAccounts,
Expand Down
4 changes: 2 additions & 2 deletions apps/ui/src/hooks/swim/usePoolMaths.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,13 +71,13 @@ const getPoolMath = ({

// lpFee
const humanLpFee = atomicToHuman(
new Decimal(poolState.lpFee),
new Decimal(poolState.lpFee.value),
poolSpec.feeDecimals,
);

// governanceFee
const humanGovernanceFee = atomicToHuman(
new Decimal(poolState.governanceFee),
new Decimal(poolState.governanceFee.value),
poolSpec.feeDecimals,
);

Expand Down
21 changes: 8 additions & 13 deletions apps/ui/src/hooks/swim/usePoolStateQueries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,19 @@ import { EVM_ECOSYSTEMS } from "@swim-io/evm";
import { SOLANA_ECOSYSTEM_ID } from "@swim-io/solana";
import type { UseQueryResult } from "react-query";
import { useQueries } from "react-query";
import shallow from "zustand/shallow.js";

import type { PoolSpec } from "../../config";
import { selectConfig } from "../../core/selectors";
import { useEnvironment } from "../../core/store";
import type { PoolState } from "../../models";
import { getEvmPoolState, getSolanaPoolState } from "../../models";
import { getLegacySolanaPoolState } from "../../models";
import { useGetEvmClient } from "../evm";
import { useSolanaClient } from "../solana";

export const usePoolStateQueries = (
poolSpecs: readonly PoolSpec[],
): readonly UseQueryResult<PoolState | null, Error>[] => {
const { env } = useEnvironment();
const { tokens } = useEnvironment(selectConfig, shallow);
const getEvmConnection = useGetEvmClient();
const getEvmClient = useGetEvmClient();
const solanaClient = useSolanaClient();

return useQueries(
Expand All @@ -27,23 +24,21 @@ export const usePoolStateQueries = (
queryFn: async () => {
const { ecosystem } = poolSpec;
if (ecosystem === SOLANA_ECOSYSTEM_ID) {
return await getSolanaPoolState(solanaClient, poolSpec);
if (poolSpec.isLegacyPool) {
return await getLegacySolanaPoolState(solanaClient, poolSpec);
}
return await solanaClient.getPoolState(poolSpec.id);
}
if (ecosystem === APTOS_ECOSYSTEM_ID) {
return null; // TODO aptos
}
const evmConnection = getEvmConnection(ecosystem);
const evmClient = getEvmClient(ecosystem);
const routingContractAddress =
EVM_ECOSYSTEMS[ecosystem].chains[env]?.routingContractAddress ?? null;
if (routingContractAddress === null) {
return null;
}
return await getEvmPoolState(
evmConnection,
poolSpec,
tokens,
routingContractAddress,
);
return await evmClient.getPoolState(poolSpec.id);
},
})),
) as readonly UseQueryResult<PoolState | null, Error>[];
Expand Down
117 changes: 117 additions & 0 deletions apps/ui/src/models/solana/deserializeLegacySolanaPoolState.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import { Buffer } from "buffer";

import { PublicKey } from "@solana/web3.js";
import BN from "bn.js";

import type { LegacySolanaPoolState } from "./deserializeLegacySolanaPoolState";
import { deserializeLegacySolanaPoolState } from "./deserializeLegacySolanaPoolState";

describe("deserializeLegacySolanaPoolState", () => {
it("deserializes a SolanaPoolState", () => {
const serialized = Buffer.from(
"00000100000000000000000000000000000000e8030000000000000000000000000000002c01000064000000990e9632b0b9f2e636feb3f0a4220f8aadf9677b451c982a4151af42e0362e8800c6fa7af3bedbad3a3d65f36aabc97431b1bbe4c2d2f6e0e47ca60203452f5d61ce010e60afedb22717bd63192f54145a3f965a33bb82d2c7029eb2ce1e20826487f81d7f931ba1c5db9f5a8b2ac5e149ef9c76d9cf196615bd21163316e8c410bdd7aa20228a7bc21e67ddfe78d5d89b986d4bf5c8d5dc9d4574d81ab11e5a02012262c2067049b5c6d6a6869e7a37bbee162637f78192c3b15e8427676a422574616a65b31ff1d8f707eb279bf8a729a6644151b16d72f9694af6ae499881ed02020202000048ccc8aa094ba7b3495776e123587f2454a935671548ccdc3f4311a9febbdd18fb56a83f5d24d5e7513f96b8c24bff58e7259e92f2fd6f01162f9b0b5188d23e32bf5157ba942716dbab775cde82f881ededa5a96b325714e2bef602679dc3cd1205cdb06ade7ab0c78b50a6e7cc2dd83edfa10423348951b7ce231b6c920334bfcf845603efc68ddea00872ad53de92ed69227e373bdca1a21f78782ec87fec7b90e07d2a4fd0d055d08430d9524a755cacd7a7a531ed91705e8b823e59d820cf609300f5b15b7009876930926f1b5c4a6ecdbc035219127e8ff47ef369abbc7ef4d44674e963fe6e94097d729f1c29c382a3ca684cfd454347f97ee142959e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008d8fcd9e56d407000000000000000000",
"hex",
);

const expected: LegacySolanaPoolState = {
ecosystem: "solana",
bump: 0,
isPaused: false,
ampFactor: {
initialValue: {
value: new BN(1),
decimals: 0,
},
initialTs: new BN(0),
targetValue: {
value: new BN(1000),
decimals: 0,
},
targetTs: new BN(0),
},
lpFee: 300,
governanceFee: 100,
lpMintKey: new PublicKey("BJUH9GJLaMSLV1E7B3SQLCy9eCfyr6zsrwGcpS2MkqR1"),
lpDecimalEqualizer: 0,
tokenMintKeys: [
new PublicKey("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"),
new PublicKey("Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB"),
new PublicKey("A9mUU4qviSctJVPJdBJWkb28deg915LYJKrzQ19ji3FM"),
new PublicKey("Dn4noZ5jgGfkntzcQSUZ8czkreiZ1ForXYoV2H8Dm7S1"),
new PublicKey("5RpUwQ8wtdPCZHhu6MERp2RGrpobsbZ6MH5dDHkUjs2"),
new PublicKey("8qJSyQprMC57TWKaYEmetUR3UUiTP2M3hXdcvFhkZdmv"),
],
tokenDecimalEqualizers: [2, 2, 2, 2, 0, 0],
tokenKeys: [
new PublicKey("5uBU2zUG8xTLA6XwwcTFWib1p7EjCBzWbiy44eVASTfV"),
new PublicKey("Hv7yPYnGs6fpN3o1NZvkima9mKDrRDJtNxf23oKLCjau"),
new PublicKey("4R6b4aibi46JzAnuA8ZWXrHAsR1oZBTZ8dqkuer3LsbS"),
new PublicKey("2DMUL42YEb4g1HAKXhUxL3Yjfgoj4VvRqKwheorfFcPV"),
new PublicKey("DukQAFyxR41nbbq2FBUDMyrtF2CRmWBREjZaTVj4u9As"),
new PublicKey("9KMH3p8cUocvQRbJfKRAStKG52xCCWNmEPsJm5gc8fzw"),
],
governanceKey: new PublicKey(
"ExWoeFoyYwCFx2cp9PZzj4eYL5fsDEFQEpC8REsksNpb",
),
governanceFeeKey: new PublicKey(
"9Yau6DnqYasBUKcyxQJQZqThvUnqZ32ZQuUCcC2AdT9P",
),
preparedGovernanceKey: PublicKey.default,
governanceTransitionTs: new BN(0),
preparedLpFee: 0,
preparedGovernanceFee: 0,
feeTransitionTs: new BN(0),
previousDepth: new BN(2203793333522317),
};
const decoded = deserializeLegacySolanaPoolState(6, serialized);
expect(decoded.bump).toBe(expected.bump);
expect(decoded.isPaused).toBe(expected.isPaused);
expect(
decoded.ampFactor.initialValue.value.eq(
expected.ampFactor.initialValue.value,
),
).toBe(true);
expect(decoded.ampFactor.initialValue.decimals).toBe(
expected.ampFactor.initialValue.decimals,
);
expect(decoded.ampFactor.initialTs.eq(expected.ampFactor.initialTs)).toBe(
true,
);
expect(
decoded.ampFactor.targetValue.value.eq(
expected.ampFactor.targetValue.value,
),
).toBe(true);
expect(decoded.ampFactor.targetValue.decimals).toBe(
expected.ampFactor.targetValue.decimals,
);
expect(decoded.ampFactor.targetTs.eq(expected.ampFactor.targetTs)).toBe(
true,
);
expect(decoded.lpFee).toBe(expected.lpFee);
expect(decoded.governanceFee).toBe(expected.governanceFee);
expect(decoded.lpMintKey).toStrictEqual(expected.lpMintKey);
expect(decoded.lpDecimalEqualizer).toBe(expected.lpDecimalEqualizer);
decoded.tokenMintKeys.forEach((tokenMintKey, i) => {
expect(tokenMintKey).toStrictEqual(expected.tokenMintKeys[i]);
});
decoded.tokenDecimalEqualizers.forEach((tokenDecimalEqualizer, i) => {
expect(tokenDecimalEqualizer).toBe(expected.tokenDecimalEqualizers[i]);
});
decoded.tokenKeys.forEach((tokenKey, i) => {
expect(tokenKey).toStrictEqual(expected.tokenKeys[i]);
});
expect(decoded.governanceKey).toStrictEqual(expected.governanceKey);
expect(decoded.governanceFeeKey).toStrictEqual(expected.governanceFeeKey);
expect(decoded.preparedGovernanceKey).toStrictEqual(
expected.preparedGovernanceKey,
);
expect(
decoded.governanceTransitionTs.eq(expected.governanceTransitionTs),
).toBe(true);
expect(decoded.preparedLpFee).toBe(expected.preparedLpFee);
expect(decoded.preparedGovernanceFee).toBe(expected.preparedGovernanceFee);
expect(decoded.feeTransitionTs.eq(expected.feeTransitionTs)).toBe(true);
expect(decoded.previousDepth.eq(expected.previousDepth)).toBe(true);
});
});
74 changes: 74 additions & 0 deletions apps/ui/src/models/solana/deserializeLegacySolanaPoolState.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import {
array,
bool,
i64,
publicKey,
struct,
u128,
u32,
u8,
} from "@project-serum/borsh";
import type { Layout } from "@project-serum/borsh";
import type { SolanaEcosystemId, SolanaPoolState } from "@swim-io/solana";
import { SOLANA_ECOSYSTEM_ID, ampFactor } from "@swim-io/solana";

type U8 = (property?: string) => Layout<number>;

export interface LegacySolanaPoolState
extends Omit<
SolanaPoolState,
| "governanceFee"
| "lpFee"
| "pauseKey"
| "preparedGovernanceFee"
| "preparedLpFee"
> {
readonly governanceFee: number;
readonly lpFee: number;
readonly preparedGovernanceFee: number;
readonly preparedLpFee: number;
readonly ecosystem: SolanaEcosystemId;
}

export const solanaPool = (
numberOfTokens: number,
property = "solanaPool",
): Layout<Omit<LegacySolanaPoolState, "ecosystem">> =>
struct(
[
u8("bump"),
bool("isPaused"),
ampFactor(),
u32("lpFee"),
u32("governanceFee"),
publicKey("lpMintKey"),
u8("lpDecimalEqualizer"),
array(publicKey(), numberOfTokens, "tokenMintKeys"),
array((u8 as U8)(), numberOfTokens, "tokenDecimalEqualizers"),
array(publicKey(), numberOfTokens, "tokenKeys"),
publicKey("governanceKey"),
publicKey("governanceFeeKey"),
publicKey("preparedGovernanceKey"),
i64("governanceTransitionTs"),
u32("preparedLpFee"),
u32("preparedGovernanceFee"),
i64("feeTransitionTs"),
u128("previousDepth"),
],
property,
);

export const deserializeLegacySolanaPoolState = (
numberOfTokens: number,
accountData: Buffer,
): LegacySolanaPoolState => {
const layout = solanaPool(numberOfTokens);
if (accountData.length !== layout.span) {
throw new Error("Incorrect account data length");
}
const decoded = layout.decode(accountData);
return {
...decoded,
ecosystem: SOLANA_ECOSYSTEM_ID,
};
};
1 change: 1 addition & 0 deletions apps/ui/src/models/solana/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from "./deserializeLegacySolanaPoolState";
export * from "./findOrCreateSplTokenAccount";
export * from "./getSwimUsdBalanceChange";
4 changes: 0 additions & 4 deletions apps/ui/src/models/swim/SwimDefiInstructor.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { TOKEN_PROGRAM_ID, createApproveInstruction } from "@solana/spl-token";
import type { AccountMeta, Transaction } from "@solana/web3.js";
import { Keypair, PublicKey, TransactionInstruction } from "@solana/web3.js";
import type { Env } from "@swim-io/core";
import {
SOLANA_ECOSYSTEM_ID,
createMemoIx,
Expand Down Expand Up @@ -35,7 +34,6 @@ import type {
} from "./operation";

export class SwimDefiInstructor {
private readonly env: Env;
private readonly solanaClient: SolanaClient;
private readonly signer: SolanaWalletAdapter;
private readonly programId: PublicKey;
Expand All @@ -49,7 +47,6 @@ export class SwimDefiInstructor {
private userTokenAccounts: readonly PublicKey[];

public constructor(
env: Env,
solanaClient: SolanaClient,
signer: SolanaWalletAdapter,
swimProgramAddress: string,
Expand All @@ -72,7 +69,6 @@ export class SwimDefiInstructor {
"Number of user token accounts does not match number of token mints",
);
}
this.env = env;
this.solanaClient = solanaClient;
this.signer = signer;
this.programId = new PublicKey(swimProgramAddress);
Expand Down
6 changes: 4 additions & 2 deletions apps/ui/src/models/swim/SwimInitializer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,16 @@ import {
SystemProgram,
TransactionInstruction,
} from "@solana/web3.js";
import { createTx, swimPool } from "@swim-io/solana";
import { createTx } from "@swim-io/solana";
import type {
DecimalBN,
SolanaClient,
SolanaWalletAdapter,
} from "@swim-io/solana";
import { chunks } from "@swim-io/utils";

import { solanaPool } from "../solana";

import { SwimInstruction, initInstruction } from "./instructions";

export class SwimInitializer {
Expand Down Expand Up @@ -148,7 +150,7 @@ export class SwimInitializer {
if (!this.stateAccount) {
throw new Error("No state account");
}
const layout = swimPool(this.numberOfTokens);
const layout = solanaPool(this.numberOfTokens);
const lamports =
await this.solanaClient.connection.getMinimumBalanceForRentExemption(
layout.span,
Expand Down
Loading