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);
});
});
102 changes: 102 additions & 0 deletions apps/ui/src/models/solana/deserializeLegacySolanaPoolState.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import {
array,
bool,
i64,
publicKey,
struct,
u128,
u32,
u64,
u8,
} from "@project-serum/borsh";
import type { Layout } from "@project-serum/borsh";
import type { SolanaEcosystemId, SolanaPoolState } from "@swim-io/solana";
import { SOLANA_ECOSYSTEM_ID } from "@swim-io/solana";
import type BN from "bn.js";

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;
}

interface DecimalBN {
readonly value: BN;
readonly decimals: number;
}

const decimal = (property = "decimal"): Layout<DecimalBN> =>
struct([u64("value"), u8("decimals")], property);

interface AmpFactor {
readonly initialValue: DecimalBN;
readonly initialTs: BN;
readonly targetValue: DecimalBN;
readonly targetTs: BN;
}

const ampFactor = (property = "ampFactor"): Layout<AmpFactor> =>
struct(
[
decimal("initialValue"),
i64("initialTs"),
decimal("targetValue"),
i64("targetTs"),
],
property,
);
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@nicomiicro I had to copy-paste these from the @swim-io/solana package because otherwise borsh complained that they weren't instances of Layout. Any ideas why? I thought the peer dependencies adjustment was supposed to prevent this kind of issue.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't get a type error locally 🤔
I pushed 14b5fe3
Let's see the output of the CI https://github.com/swim-io/swim/actions/runs/3323839509/jobs/5494688131

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh now I see it, it's a runtime error. Checking...

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh yes, it's just because you are using the package from the workspace and not a published one. Thus it will load the one from its own node_modules. This should go away once it gets published.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another (good) reason to move to pre-releases for PRs?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ahh. Hmm, I don't really want to have to publish twice as often as I do currently.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah I meant when we automate this...


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