Skip to content

Commit 055bcd6

Browse files
committed
fix: implement PriceFeedStore
1 parent 711d9f6 commit 055bcd6

File tree

6 files changed

+329
-5
lines changed

6 files changed

+329
-5
lines changed

src/dev/PriceFeedStore.ts

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import type {
2+
AbiParametersToPrimitiveTypes,
3+
ExtractAbiFunction,
4+
} from "abitype";
5+
import type { Address } from "viem";
6+
7+
import type { GearboxSDK, ILogger, PriceFeedTreeNode, Unarray } from "../sdk";
8+
import {
9+
AddressMap,
10+
AP_PRICE_FEED_COMPRESSOR,
11+
iPriceFeedCompressorAbi,
12+
rawTxToMulticallPriceUpdate,
13+
SDKConstruct,
14+
} from "../sdk";
15+
import { iPriceFeedStoreAbi } from "./abi";
16+
17+
export type ConnectedPriceFeed = Unarray<
18+
AbiParametersToPrimitiveTypes<
19+
ExtractAbiFunction<
20+
typeof iPriceFeedStoreAbi,
21+
"getTokenPriceFeedsMap"
22+
>["outputs"]
23+
>[0]
24+
>;
25+
26+
export class PriceFeedStore extends SDKConstruct {
27+
readonly #store: Address;
28+
readonly #compressor: Address;
29+
readonly #logger?: ILogger;
30+
31+
constructor(sdk: GearboxSDK) {
32+
super(sdk);
33+
this.#store = this.sdk.addressProvider.getAddress("PRICE_FEED_STORE");
34+
this.#compressor = this.sdk.addressProvider.getAddress(
35+
AP_PRICE_FEED_COMPRESSOR,
36+
);
37+
this.#logger = sdk.logger?.child?.({
38+
module: "PriceFeedStore",
39+
});
40+
}
41+
42+
public async load(update = true): Promise<AddressMap<PriceFeedTreeNode[]>> {
43+
const pfMap = await this.provider.publicClient.readContract({
44+
address: this.#store,
45+
abi: iPriceFeedStoreAbi,
46+
functionName: "getTokenPriceFeedsMap",
47+
});
48+
const addresses = pfMap.flatMap(f => f.priceFeeds);
49+
const nodes = await this.#loadFromCompressor(addresses, update);
50+
const result = new AddressMap<PriceFeedTreeNode[]>();
51+
for (const { token, priceFeeds } of pfMap) {
52+
result.upsert(
53+
token,
54+
priceFeeds
55+
.map(pf => nodes.find(n => n.baseParams.addr === pf)!)
56+
.filter(Boolean),
57+
);
58+
}
59+
return result;
60+
}
61+
62+
async #loadFromCompressor(
63+
priceFeeds: Address[],
64+
update = true,
65+
): Promise<PriceFeedTreeNode[]> {
66+
let result = await this.provider.publicClient.readContract({
67+
address: this.#compressor,
68+
abi: iPriceFeedCompressorAbi,
69+
functionName: "loadPriceFeedTree",
70+
args: [priceFeeds],
71+
});
72+
if (update) {
73+
const feeds = result.map(f => this.sdk.priceFeeds.create(f));
74+
const { txs } =
75+
await this.sdk.priceFeeds.generatePriceFeedsUpdateTxs(feeds);
76+
const resp = await this.provider.publicClient.multicall({
77+
contracts: [
78+
...txs.map(rawTxToMulticallPriceUpdate),
79+
{
80+
address: this.#compressor,
81+
abi: iPriceFeedCompressorAbi,
82+
functionName: "loadPriceFeedTree",
83+
args: [priceFeeds],
84+
},
85+
],
86+
allowFailure: false,
87+
});
88+
result = resp.pop() as PriceFeedTreeNode[];
89+
}
90+
this.#logger?.debug(
91+
`loaded ${result.length} price feed nodes from compressor`,
92+
);
93+
return [...result];
94+
}
95+
}

src/dev/abi/iPriceFeedStore.ts

Lines changed: 227 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,227 @@
1+
export const iPriceFeedStoreAbi = [
2+
{
3+
type: "function",
4+
name: "addPriceFeed",
5+
inputs: [
6+
{ name: "priceFeed", type: "address", internalType: "address" },
7+
{ name: "stalenessPeriod", type: "uint32", internalType: "uint32" },
8+
],
9+
outputs: [],
10+
stateMutability: "nonpayable",
11+
},
12+
{
13+
type: "function",
14+
name: "allowPriceFeed",
15+
inputs: [
16+
{ name: "token", type: "address", internalType: "address" },
17+
{ name: "priceFeed", type: "address", internalType: "address" },
18+
],
19+
outputs: [],
20+
stateMutability: "nonpayable",
21+
},
22+
{
23+
type: "function",
24+
name: "contractType",
25+
inputs: [],
26+
outputs: [{ name: "", type: "bytes32", internalType: "bytes32" }],
27+
stateMutability: "view",
28+
},
29+
{
30+
type: "function",
31+
name: "forbidPriceFeed",
32+
inputs: [
33+
{ name: "token", type: "address", internalType: "address" },
34+
{ name: "priceFeed", type: "address", internalType: "address" },
35+
],
36+
outputs: [],
37+
stateMutability: "nonpayable",
38+
},
39+
{
40+
type: "function",
41+
name: "getAllowanceTimestamp",
42+
inputs: [
43+
{ name: "token", type: "address", internalType: "address" },
44+
{ name: "priceFeed", type: "address", internalType: "address" },
45+
],
46+
outputs: [{ name: "", type: "uint256", internalType: "uint256" }],
47+
stateMutability: "view",
48+
},
49+
{
50+
type: "function",
51+
name: "getKnownPriceFeeds",
52+
inputs: [],
53+
outputs: [{ name: "", type: "address[]", internalType: "address[]" }],
54+
stateMutability: "view",
55+
},
56+
{
57+
type: "function",
58+
name: "getKnownTokens",
59+
inputs: [],
60+
outputs: [{ name: "", type: "address[]", internalType: "address[]" }],
61+
stateMutability: "view",
62+
},
63+
{
64+
type: "function",
65+
name: "getPriceFeeds",
66+
inputs: [{ name: "token", type: "address", internalType: "address" }],
67+
outputs: [{ name: "", type: "address[]", internalType: "address[]" }],
68+
stateMutability: "view",
69+
},
70+
{
71+
type: "function",
72+
name: "getStalenessPeriod",
73+
inputs: [{ name: "priceFeed", type: "address", internalType: "address" }],
74+
outputs: [{ name: "", type: "uint32", internalType: "uint32" }],
75+
stateMutability: "view",
76+
},
77+
{
78+
type: "function",
79+
name: "getTokenPriceFeedsMap",
80+
inputs: [],
81+
outputs: [
82+
{
83+
name: "",
84+
type: "tuple[]",
85+
internalType: "struct ConnectedPriceFeed[]",
86+
components: [
87+
{ name: "token", type: "address", internalType: "address" },
88+
{ name: "priceFeeds", type: "address[]", internalType: "address[]" },
89+
],
90+
},
91+
],
92+
stateMutability: "view",
93+
},
94+
{
95+
type: "function",
96+
name: "isAllowedPriceFeed",
97+
inputs: [
98+
{ name: "token", type: "address", internalType: "address" },
99+
{ name: "priceFeed", type: "address", internalType: "address" },
100+
],
101+
outputs: [{ name: "", type: "bool", internalType: "bool" }],
102+
stateMutability: "view",
103+
},
104+
{
105+
type: "function",
106+
name: "owner",
107+
inputs: [],
108+
outputs: [{ name: "", type: "address", internalType: "address" }],
109+
stateMutability: "view",
110+
},
111+
{
112+
type: "function",
113+
name: "setStalenessPeriod",
114+
inputs: [
115+
{ name: "priceFeed", type: "address", internalType: "address" },
116+
{ name: "stalenessPeriod", type: "uint32", internalType: "uint32" },
117+
],
118+
outputs: [],
119+
stateMutability: "nonpayable",
120+
},
121+
{
122+
type: "function",
123+
name: "version",
124+
inputs: [],
125+
outputs: [{ name: "", type: "uint256", internalType: "uint256" }],
126+
stateMutability: "view",
127+
},
128+
{
129+
type: "event",
130+
name: "AddPriceFeed",
131+
inputs: [
132+
{
133+
name: "priceFeed",
134+
type: "address",
135+
indexed: false,
136+
internalType: "address",
137+
},
138+
{
139+
name: "stalenessPeriod",
140+
type: "uint32",
141+
indexed: false,
142+
internalType: "uint32",
143+
},
144+
],
145+
anonymous: false,
146+
},
147+
{
148+
type: "event",
149+
name: "AllowPriceFeed",
150+
inputs: [
151+
{
152+
name: "token",
153+
type: "address",
154+
indexed: false,
155+
internalType: "address",
156+
},
157+
{
158+
name: "priceFeed",
159+
type: "address",
160+
indexed: false,
161+
internalType: "address",
162+
},
163+
],
164+
anonymous: false,
165+
},
166+
{
167+
type: "event",
168+
name: "ForbidPriceFeed",
169+
inputs: [
170+
{
171+
name: "token",
172+
type: "address",
173+
indexed: false,
174+
internalType: "address",
175+
},
176+
{
177+
name: "priceFeed",
178+
type: "address",
179+
indexed: false,
180+
internalType: "address",
181+
},
182+
],
183+
anonymous: false,
184+
},
185+
{
186+
type: "event",
187+
name: "SetStalenessPeriod",
188+
inputs: [
189+
{
190+
name: "priceFeed",
191+
type: "address",
192+
indexed: false,
193+
internalType: "address",
194+
},
195+
{
196+
name: "stalenessPeriod",
197+
type: "uint32",
198+
indexed: false,
199+
internalType: "uint32",
200+
},
201+
],
202+
anonymous: false,
203+
},
204+
{
205+
type: "error",
206+
name: "CallerIsNotOwnerException",
207+
inputs: [{ name: "caller", type: "address", internalType: "address" }],
208+
},
209+
{
210+
type: "error",
211+
name: "PriceFeedAlreadyAddedException",
212+
inputs: [{ name: "priceFeed", type: "address", internalType: "address" }],
213+
},
214+
{
215+
type: "error",
216+
name: "PriceFeedIsNotAllowedException",
217+
inputs: [
218+
{ name: "token", type: "address", internalType: "address" },
219+
{ name: "priceFeed", type: "address", internalType: "address" },
220+
],
221+
},
222+
{
223+
type: "error",
224+
name: "PriceFeedNotKnownException",
225+
inputs: [{ name: "priceFeed", type: "address", internalType: "address" }],
226+
},
227+
] as const;

src/dev/abi/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1+
export * from "./iPriceFeedStore";
12
export * from "./v3";

src/dev/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
export * from "./AccountOpener";
22
export * from "./calcLiquidatableLTs";
33
export * from "./createAnvilClient";
4+
export * from "./PriceFeedStore";
45
export * from "./SDKExample";
56
export * from "./setLTs";
67
export * from "./setLTZero";

src/sdk/base/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import type { Address, Hex } from "viem";
66

77
import type { iCreditAccountCompressorAbi, iMarketCompressorAbi } from "../abi";
88

9-
type Unarray<A> = A extends readonly unknown[] ? Unarray<A[number]> : A;
9+
export type Unarray<A> = A extends readonly unknown[] ? Unarray<A[number]> : A;
1010

1111
export interface BaseParams {
1212
addr: Address;

src/sdk/market/pricefeeds/PriceFeedsRegister.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ export class PriceFeedRegister
104104
if (existing?.loaded) {
105105
return existing;
106106
}
107-
const feed = this.#create(data);
107+
const feed = this.create(data);
108108
this.#feeds.upsert(data.baseParams.addr, feed);
109109
return feed;
110110
}
@@ -132,7 +132,7 @@ export class PriceFeedRegister
132132
pools,
133133
);
134134
for (const data of feedsData) {
135-
const feed = this.#create({ baseParams: data });
135+
const feed = this.create({ baseParams: data });
136136
this.#feeds.upsert(feed.address, feed);
137137
}
138138
}
@@ -151,7 +151,7 @@ export class PriceFeedRegister
151151
marketConfigurators,
152152
pools,
153153
);
154-
const feeds = feedsData.map(data => this.#create({ baseParams: data }));
154+
const feeds = feedsData.map(data => this.create({ baseParams: data }));
155155
const updates = await this.#generatePriceFeedsUpdateTxs(feeds);
156156

157157
return createRawTx(
@@ -245,7 +245,7 @@ export class PriceFeedRegister
245245
return result;
246246
}
247247

248-
#create(data: PartialPriceFeedTreeNode): IPriceFeedContract {
248+
public create(data: PartialPriceFeedTreeNode): IPriceFeedContract {
249249
const contractType = bytes32ToString(
250250
data.baseParams.contractType as Hex,
251251
) as PriceFeedContractType;

0 commit comments

Comments
 (0)