Skip to content

Commit 74fa553

Browse files
authored
Fix Lido and EtherFi Actions (#291)
* Fix Lido request stETH withdrawals * Update comment to include EtherFi
1 parent 044e6e0 commit 74fa553

2 files changed

Lines changed: 127 additions & 5 deletions

File tree

src/js/utils/arm.js

Lines changed: 47 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,15 @@ const isMissingSelectorError = (err) =>
141141
err?.data === "0x" ||
142142
err?.info?.error?.data === "0x";
143143

144+
const isMissingSelectorOrBareRevertError = (err) =>
145+
isMissingSelectorError(err) ||
146+
(err?.name === "ProviderError" && err?.message === "execution reverted");
147+
148+
const errorSummary = (err) =>
149+
[err?.name, err?.code, err?.message, err?.data || err?.info?.error?.data]
150+
.filter(Boolean)
151+
.join(": ");
152+
144153
const legacyArmContract = async (arm, signerOrProvider) =>
145154
new Contract(
146155
await arm.getAddress(),
@@ -414,17 +423,25 @@ const getArmBuffer = async (arm, blockTag) => {
414423

415424
const getOutstandingWithdrawals = async (arm, blockTag) => {
416425
const opts = blockTag === undefined ? [] : [{ blockTag }];
426+
let reservedWithdrawLiquidityError;
427+
let currentAbiWithdrawalsError;
417428
// Liquidity reserved for outstanding LP withdrawal requests (asset-denominated).
418429
// Newer ARMs track the asset-denominated amount directly in
419-
// reservedWithdrawLiquidity(). Legacy ARMs expose
430+
// reservedWithdrawLiquidity(). Legacy Lido and EtherFi ARMs expose
420431
// withdrawsQueued()/withdrawsClaimed(); several new ABIs dropped these getters
421432
// even though the deployed legacy contracts still implement them on-chain, so
422433
// fall back to the legacy ABI.
423434
if (arm.reservedWithdrawLiquidity) {
424435
try {
425436
return await arm.reservedWithdrawLiquidity(...opts);
426437
} catch (err) {
427-
if (!isMissingSelectorError(err)) throw err;
438+
reservedWithdrawLiquidityError = err;
439+
if (!isMissingSelectorOrBareRevertError(err)) {
440+
throw new Error(
441+
`Failed to read outstanding withdrawals via reservedWithdrawLiquidity(): ${errorSummary(err)}`,
442+
{ cause: err },
443+
);
444+
}
428445
}
429446
}
430447

@@ -435,7 +452,13 @@ const getOutstandingWithdrawals = async (arm, blockTag) => {
435452
]);
436453
return queued - claimed;
437454
} catch (err) {
438-
if (!isMissingSelectorError(err)) throw err;
455+
currentAbiWithdrawalsError = err;
456+
if (!isMissingSelectorError(err)) {
457+
throw new Error(
458+
`Failed to read outstanding withdrawals via withdrawsQueued()/withdrawsClaimed(): ${errorSummary(err)}`,
459+
{ cause: err },
460+
);
461+
}
439462
}
440463
try {
441464
const legacyArm = await legacyArmContract(arm);
@@ -445,8 +468,27 @@ const getOutstandingWithdrawals = async (arm, blockTag) => {
445468
]);
446469
return queued - claimed;
447470
} catch (err) {
448-
if (!isMissingSelectorError(err)) throw err;
449-
return arm.reservedWithdrawLiquidity(...opts);
471+
if (!isMissingSelectorError(err)) {
472+
throw new Error(
473+
`Failed to read outstanding withdrawals via legacy withdrawsQueued()/withdrawsClaimed(): ${errorSummary(err)}`,
474+
{ cause: err },
475+
);
476+
}
477+
478+
const armAddress = await arm.getAddress();
479+
const reservedResult = reservedWithdrawLiquidityError
480+
? `failed (${errorSummary(reservedWithdrawLiquidityError)})`
481+
: "was not available in the current ABI";
482+
const currentAbiResult = currentAbiWithdrawalsError
483+
? `failed (${errorSummary(currentAbiWithdrawalsError)})`
484+
: "was not attempted";
485+
throw new Error(
486+
`Unable to read outstanding withdrawals for ARM ${armAddress}: ` +
487+
`reservedWithdrawLiquidity() ${reservedResult}; ` +
488+
`current ABI withdrawsQueued()/withdrawsClaimed() ${currentAbiResult}; ` +
489+
`legacy ABI withdrawsQueued()/withdrawsClaimed() failed (${errorSummary(err)})`,
490+
{ cause: err },
491+
);
450492
}
451493
};
452494

test/js/arm.test.js

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
const assert = require("assert");
2+
3+
const { AbiCoder, Contract, id } = require("ethers");
4+
5+
const { getOutstandingWithdrawals } = require("../../src/js/utils/arm");
6+
7+
const coder = AbiCoder.defaultAbiCoder();
8+
9+
const selector = (signature) => id(signature).slice(0, 10);
10+
11+
const providerRevert = () => {
12+
const err = new Error("execution reverted");
13+
err.name = "ProviderError";
14+
return err;
15+
};
16+
17+
const run = async () => {
18+
const selectors = {
19+
reservedWithdrawLiquidity: selector("reservedWithdrawLiquidity()"),
20+
withdrawsQueued: selector("withdrawsQueued()"),
21+
withdrawsClaimed: selector("withdrawsClaimed()"),
22+
};
23+
24+
{
25+
const runner = {
26+
call: async (tx) => {
27+
if (tx.data === selectors.reservedWithdrawLiquidity) {
28+
throw providerRevert();
29+
}
30+
if (tx.data === selectors.withdrawsQueued) {
31+
return coder.encode(["uint256"], [123n]);
32+
}
33+
if (tx.data === selectors.withdrawsClaimed) {
34+
return coder.encode(["uint256"], [23n]);
35+
}
36+
throw new Error(`unexpected selector ${tx.data}`);
37+
},
38+
};
39+
40+
const arm = new Contract(
41+
"0x0000000000000000000000000000000000000001",
42+
["function reservedWithdrawLiquidity() view returns (uint256)"],
43+
runner,
44+
);
45+
46+
assert.strictEqual(await getOutstandingWithdrawals(arm), 100n);
47+
}
48+
49+
{
50+
const runner = {
51+
call: async (tx) => {
52+
if (tx.data === selectors.reservedWithdrawLiquidity) {
53+
throw providerRevert();
54+
}
55+
if (
56+
tx.data === selectors.withdrawsQueued ||
57+
tx.data === selectors.withdrawsClaimed
58+
) {
59+
const err = providerRevert();
60+
err.code = "CALL_EXCEPTION";
61+
throw err;
62+
}
63+
throw new Error(`unexpected selector ${tx.data}`);
64+
},
65+
};
66+
67+
const arm = new Contract(
68+
"0x0000000000000000000000000000000000000001",
69+
["function reservedWithdrawLiquidity() view returns (uint256)"],
70+
runner,
71+
);
72+
73+
await assert.rejects(
74+
() => getOutstandingWithdrawals(arm),
75+
/Unable to read outstanding withdrawals for ARM .*reservedWithdrawLiquidity\(\).*legacy ABI withdrawsQueued\(\)\/withdrawsClaimed\(\) failed/,
76+
);
77+
}
78+
};
79+
80+
run().then(() => console.log("arm tests passed"));

0 commit comments

Comments
 (0)