Skip to content

Commit 3dcb4b1

Browse files
authored
Merge pull request #531 from morpho-org/Rubilmax/fix-gettoken-eip5267-deployless-signed
fix(blue-sdk-viem): avoid deployless EIP-5267 reverts
2 parents cdba338 + 2962119 commit 3dcb4b1

File tree

5 files changed

+217
-19
lines changed

5 files changed

+217
-19
lines changed

packages/blue-sdk-viem/contracts/GetToken.sol

Lines changed: 163 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,26 +17,181 @@ struct TokenResponse {
1717
}
1818

1919
contract GetToken {
20+
uint256 private constant STRING_HEAD_SIZE = 0x40;
21+
uint256 private constant EIP5267_DOMAIN_HEAD_SIZE = 0xe0;
22+
2023
function query(IERC20 token, bool isWstEth) external view returns (TokenResponse memory res) {
21-
try token.name() returns (string memory name) {
24+
(bool hasName, string memory name) = _queryString(address(token), abi.encodeCall(IERC20.name, ()));
25+
if (hasName) {
2226
res.hasName = true;
2327
res.name = name;
24-
} catch {}
28+
}
2529

26-
try token.symbol() returns (string memory symbol) {
30+
(bool hasSymbol, string memory symbol) = _queryString(address(token), abi.encodeCall(IERC20.symbol, ()));
31+
if (hasSymbol) {
2732
res.hasSymbol = true;
2833
res.symbol = symbol;
29-
} catch {}
34+
}
3035

31-
try token.decimals() returns (uint8 decimals) {
36+
(bool hasDecimals, uint8 decimals) = _queryUint8(address(token), abi.encodeCall(IERC20.decimals, ()));
37+
if (hasDecimals) {
3238
res.decimals = decimals;
33-
} catch {}
39+
}
3440

3541
if (isWstEth) res.stEthPerWstEth = IWstEth(address(token)).stEthPerToken();
3642

37-
try IERC20Permit(address(token)).eip712Domain() returns (Eip5267Domain memory eip5267Domain) {
43+
(bool hasEip5267Domain, Eip5267Domain memory eip5267Domain) = _queryEip5267Domain(address(token));
44+
if (hasEip5267Domain) {
3845
res.hasEip5267Domain = true;
3946
res.eip5267Domain = eip5267Domain;
40-
} catch {}
47+
}
48+
}
49+
50+
function _queryString(
51+
address target,
52+
bytes memory callData
53+
) private view returns (bool success, string memory value) {
54+
bytes memory returnData;
55+
(success, returnData) = target.staticcall(callData);
56+
if (!success) return (false, "");
57+
58+
if (_isValidStringReturnData(returnData)) {
59+
value = abi.decode(returnData, (string));
60+
return (true, value);
61+
}
62+
63+
if (returnData.length != 0x20) return (false, "");
64+
65+
return (true, _bytes32ToString(bytes32(_loadWord(returnData, 0))));
66+
}
67+
68+
function _queryUint8(address target, bytes memory callData) private view returns (bool success, uint8 value) {
69+
bytes memory returnData;
70+
(success, returnData) = target.staticcall(callData);
71+
if (!success || returnData.length != 0x20) return (false, 0);
72+
73+
uint256 decoded = _loadWord(returnData, 0);
74+
if (decoded > type(uint8).max) return (false, 0);
75+
76+
return (true, uint8(decoded));
77+
}
78+
79+
function _queryEip5267Domain(address target) private view returns (bool success, Eip5267Domain memory value) {
80+
bytes memory returnData;
81+
(success, returnData) = target.staticcall(abi.encodeCall(IERC20Permit.eip712Domain, ()));
82+
if (!success || !_isValidEip5267DomainReturnData(returnData)) return (false, value);
83+
84+
// Work around a Solidity via-IR decoding regression hit by valid EIP-5267 domains
85+
// such as Treehouse ETH (tETH) on mainnet. Decoding raw returndata locally avoids
86+
// the deployless helper revert while keeping optional metadata reads best-effort.
87+
(
88+
bytes1 fields,
89+
string memory name,
90+
string memory version,
91+
uint256 chainId,
92+
address verifyingContract,
93+
bytes32 salt,
94+
uint256[] memory extensions
95+
) = abi.decode(returnData, (bytes1, string, string, uint256, address, bytes32, uint256[]));
96+
97+
value = Eip5267Domain({
98+
fields: fields,
99+
name: name,
100+
version: version,
101+
chainId: chainId,
102+
verifyingContract: verifyingContract,
103+
salt: salt,
104+
extensions: extensions
105+
});
106+
success = true;
107+
}
108+
109+
function _isValidStringReturnData(bytes memory returnData) private pure returns (bool) {
110+
if (returnData.length < STRING_HEAD_SIZE) return false;
111+
112+
uint256 offset = _loadWord(returnData, 0);
113+
if (offset != 0x20) return false;
114+
115+
return _isValidStringTail(returnData, offset, 0x20);
116+
}
117+
118+
function _isValidEip5267DomainReturnData(bytes memory returnData) private pure returns (bool) {
119+
if (returnData.length < EIP5267_DOMAIN_HEAD_SIZE) return false;
120+
121+
uint256 fieldsWord = _loadWord(returnData, 0);
122+
if (fieldsWord << 8 != 0) return false;
123+
124+
uint256 verifyingContractWord = _loadWord(returnData, 0x80);
125+
if (verifyingContractWord >> 160 != 0) return false;
126+
127+
uint256 nameOffset = _loadWord(returnData, 0x20);
128+
uint256 versionOffset = _loadWord(returnData, 0x40);
129+
uint256 extensionsOffset = _loadWord(returnData, 0xc0);
130+
131+
if (!_isValidStringTail(returnData, nameOffset, EIP5267_DOMAIN_HEAD_SIZE)) return false;
132+
if (!_isValidStringTail(returnData, versionOffset, EIP5267_DOMAIN_HEAD_SIZE)) return false;
133+
return _isValidUintArrayTail(returnData, extensionsOffset, EIP5267_DOMAIN_HEAD_SIZE);
134+
}
135+
136+
function _isValidStringTail(
137+
bytes memory returnData,
138+
uint256 offset,
139+
uint256 minimumOffset
140+
) private pure returns (bool) {
141+
if (!_isValidDynamicOffset(returnData.length, offset, minimumOffset)) return false;
142+
143+
uint256 length = _loadWord(returnData, offset);
144+
unchecked {
145+
return length <= returnData.length - offset - 0x20;
146+
}
147+
}
148+
149+
function _isValidUintArrayTail(
150+
bytes memory returnData,
151+
uint256 offset,
152+
uint256 minimumOffset
153+
) private pure returns (bool) {
154+
if (!_isValidDynamicOffset(returnData.length, offset, minimumOffset)) return false;
155+
156+
uint256 length = _loadWord(returnData, offset);
157+
unchecked {
158+
return length <= (returnData.length - offset - 0x20) / 0x20;
159+
}
160+
}
161+
162+
function _isValidDynamicOffset(
163+
uint256 totalLength,
164+
uint256 offset,
165+
uint256 minimumOffset
166+
) private pure returns (bool) {
167+
if (offset < minimumOffset || offset & 0x1f != 0) return false;
168+
unchecked {
169+
return offset <= totalLength - 0x20;
170+
}
171+
}
172+
173+
function _loadWord(bytes memory data, uint256 offset) private pure returns (uint256 value) {
174+
assembly ("memory-safe") {
175+
value := mload(add(add(data, 0x20), offset))
176+
}
177+
}
178+
179+
function _bytes32ToString(bytes32 value) private pure returns (string memory) {
180+
uint256 length;
181+
while (length < 0x20 && value[length] != 0) {
182+
unchecked {
183+
++length;
184+
}
185+
}
186+
187+
bytes memory buffer = new bytes(length);
188+
for (uint256 i; i < length; ) {
189+
buffer[i] = value[i];
190+
unchecked {
191+
++i;
192+
}
193+
}
194+
195+
return string(buffer);
41196
}
42197
}

packages/blue-sdk-viem/src/fetch/Token.ts

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -53,23 +53,26 @@ export async function fetchToken(
5353
const eip5267Domain = token.hasEip5267Domain
5454
? new Eip5267Domain(token.eip5267Domain)
5555
: undefined;
56+
const metadata = {
57+
address,
58+
decimals: token.decimals,
59+
symbol: token.hasSymbol ? token.symbol : undefined,
60+
name: token.hasName ? token.name : undefined,
61+
eip5267Domain,
62+
};
5663

5764
if (isWstEth && stEth != null)
5865
return new ExchangeRateWrappedToken(
59-
{ ...token, address, eip5267Domain },
66+
metadata,
6067
stEth,
6168
token.stEthPerWstEth,
6269
);
6370

6471
const unwrapToken = getUnwrappedToken(address, parameters.chainId);
6572
if (unwrapToken)
66-
return new ConstantWrappedToken(
67-
{ ...token, address, eip5267Domain },
68-
unwrapToken,
69-
token.decimals,
70-
);
73+
return new ConstantWrappedToken(metadata, unwrapToken, token.decimals);
7174

72-
return new Token({ ...token, address, eip5267Domain });
75+
return new Token(metadata);
7376
} catch (error) {
7477
if (deployless === "force") throw error;
7578
// Fallback to multicall if deployless call fails.

packages/blue-sdk-viem/src/queries/GetToken.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,4 +81,4 @@ export const abi = [
8181
] as const;
8282

8383
export const code =
84-
"0x60808060405234601557610635908161001a8239f35b5f80fdfe6080806040526004361015610012575f80fd5b5f3560e01c63287861f914610025575f80fd5b346103fb5760403660031901126103fb576004356001600160a01b03811691908290036103fb576024359182151583036103fb57610100820182811067ffffffffffffffff8211176103ff576040525f825260208201905f8252604083016060815260608401905f825260808501906060825260a08601945f865260c08701946040516100b181610542565b5f815260606020820152606060408201525f60608201525f60808201525f60a0820152606060c0820152865260e08801985f8a526040516306fdde0360e01b81525f81600481865afa5f9181610502575b506104f6575b506040516395d89b4160e01b81525f81600481865afa5f91816104d2575b506104c6575b5060405163313ce56760e01b8152602081600481865afa5f9181610488575b5061047d575b50610413575b5f600491604051928380926342580cb760e11b82525afa5f918161029b575b50916101ce959493916101b39361028f575b506040519860208a525160208a0152511515604089015251610100606089015261012088019061051e565b91511515608087015251858203601f190160a087015261051e565b915160c08401525192601f198383030160e084015260ff60f81b845116825260c061021d61020b602087015160e0602087015260e086019061051e565b6040870151858203604087015261051e565b946060810151606085015260018060a01b03608082015116608085015260a081015160a085015201519160c08186039101526020808351958681520192015f945b8086106102775750508293505115156101008301520390f35b9092602080600192865181520194019501949061025e565b60018b5287525f610188565b9091503d805f833e6102ad818361055e565b8101906020818303126103fb5780519067ffffffffffffffff82116103fb57019060e0828203126103fb57604051916102e583610542565b80516001600160f81b0319811681036103fb578352602081015167ffffffffffffffff81116103fb578261031a918301610580565b6020840152604081015167ffffffffffffffff81116103fb578261033f918301610580565b60408401526060818101519084015260808101516001600160a01b03811681036103fb57608084015260a081015160a084015260c08101519067ffffffffffffffff82116103fb57019080601f830112156103fb5781519167ffffffffffffffff83116103ff578260051b90604051936103bc602084018661055e565b84526020808501928201019283116103fb57602001905b8282106103eb5750505060c0820152906101b3610176565b81518152602091820191016103d3565b5f80fd5b634e487b7160e01b5f52604160045260245ffd5b6040516301afd7c160e11b8152602081600481855afa908115610472575f9161043f575b508752610157565b90506020813d60201161046a575b8161045a6020938361055e565b810103126103fb57516004610437565b3d915061044d565b6040513d5f823e3d90fd5b60ff1689525f610151565b9091506020813d6020116104be575b816104a46020938361055e565b810103126103fb575160ff811681036103fb57905f61014b565b3d9150610497565b6001845284525f61012c565b6104ef9192503d805f833e6104e7818361055e565b8101906105d6565b905f610126565b6001875285525f610108565b6105179192503d805f833e6104e7818361055e565b905f610102565b805180835260209291819084018484015e5f828201840152601f01601f1916010190565b60e0810190811067ffffffffffffffff8211176103ff57604052565b90601f8019910116810190811067ffffffffffffffff8211176103ff57604052565b81601f820112156103fb5780519067ffffffffffffffff82116103ff57604051926105b5601f8401601f19166020018561055e565b828452602083830101116103fb57815f9260208093018386015e8301015290565b906020828203126103fb57815167ffffffffffffffff81116103fb576105fc9201610580565b9056fea2646970667358221220389a149f2e0bd6ff1dee73cba200ac22d6611d304a114c8d87d33392f9d891a264736f6c634300081b0033";
84+
"0x608080604052346015576108b5908161001a8239f35b5f80fdfe6080806040526004361015610012575f80fd5b5f3560e01c63287861f914610025575f80fd5b346102cd5760403660031901126102cd576004356001600160a01b03811691908290036102cd576024359182151583036102cd57610100820182811067ffffffffffffffff821117610307576040525f8252602082015f8152604083019160608352606084015f8152608085016060815260a08601945f865260c08701946100ab61037d565b865260e08801985f8a526100dd6040516306fdde0360e01b6020820152600481526100d760248261035b565b87610444565b906102fb575b506040516395d89b4160e01b602082015260048152610107906100d760248261035b565b906102ef575b5060405163313ce56760e01b6020820152600481526101379061013160248261035b565b87610579565b906102e4575b5061026d575b90610185916101546101a0966105cb565b90610261575b506040519860208a525160208a0152511515604089015251610100606089015261012088019061031b565b91511515608087015251858203601f190160a087015261031b565b915160c08401525192601f198383030160e084015260ff60f81b845116825260c06101ef6101dd602087015160e0602087015260e086019061031b565b6040870151858203604087015261031b565b946060810151606085015260018060a01b03608082015116608085015260a081015160a085015201519160c08186039101526020808351958681520192015f945b8086106102495750508293505115156101008301520390f35b90926020806001928651815201940195019490610230565b60018b5287525f61015a565b6040516301afd7c160e11b815294602086600481845afa9586156102d9575f9661029b575b50948752610143565b955091906020863d6020116102d1575b816102b86020938361035b565b810103126102cd579451949091610154610292565b5f80fd5b3d91506102ab565b6040513d5f823e3d90fd5b60ff1689525f61013d565b6001835283525f61010d565b6001865284525f6100e3565b634e487b7160e01b5f52604160045260245ffd5b805180835260209291819084018484015e5f828201840152601f01601f1916010190565b60e0810190811067ffffffffffffffff82111761030757604052565b90601f8019910116810190811067ffffffffffffffff82111761030757604052565b6040519061038a8261033f565b606060c0835f81528260208201528260408201525f838201525f60808201525f60a08201520152565b67ffffffffffffffff811161030757601f01601f191660200190565b3d156103f9573d906103e0826103b3565b916103ee604051938461035b565b82523d5f602084013e565b606090565b81601f820112156102cd57805190610415826103b3565b92610423604051948561035b565b828452602083830101116102cd57815f9260208093018386015e8301015290565b5f918291602082519201905afa6104596103cf565b90156105285761046881610772565b61053f5760208151036105285760200151905f5b602081108061050c575b156104935760010161047c565b9161049d836103b3565b926104ab604051948561035b565b808452601f196104ba826103b3565b013660208601375f5b8181106104d35750505060019190565b60208110156104f85784518110156104f85780836001921a60208288010153016104c3565b634e487b7160e01b5f52603260045260245ffd5b156104f85782811a60f81b6001600160f81b0319161515610486565b505f9060405161053960208261035b565b5f815290565b80518101906020818303126102cd5760208101519167ffffffffffffffff83116102cd576105749260208092019201016103fe565b600191565b5f918291602082519201905afa61058e6103cf565b901580156105bf575b6105b857602001519060ff82116105b15760ff6001921690565b5f91508190565b505f905f90565b50602081511415610597565b5f806105d561037d565b9260405160208101906342580cb760e11b8252600481526105f760248261035b565b51915afa906106046103cf565b91158015610762575b61075b57508051810160e082602083019203126102cd5760208201516001600160f81b0319811692908390036102cd57604081015167ffffffffffffffff81116102cd57826020610660928401016103fe565b606082015167ffffffffffffffff81116102cd57836020610683928501016103fe565b608083015160a08401516001600160a01b03811693919291908490036102cd5760c08501519460e08101519067ffffffffffffffff82116102cd57019580603f880112156102cd5760208701519667ffffffffffffffff8811610307578760051b90604051986106f6602084018b61035b565b8952602080808b0193830101019283116102cd57604001905b82821061074b57505050604051966107268861033f565b8752602087015260408601526060850152608084015260a083015260c0820152600191565b815181526020918201910161070f565b5f92909150565b5061076c8261079f565b1561060d565b604081511061079a5760208101516020810361079457610791916107fd565b90565b50505f90565b505f90565b60e081511061079a57602081015160081b61079a5760a081015160a01c61079a57604081015160608201516107d960e08401519284610823565b156107f6576107e89083610823565b156107945761079191610832565b5050505f90565b80519061080c6020848461085b565b156107f657820160200151919003601f1901101590565b80519061080c60e0848461085b565b80519061084160e0848461085b565b156107f657820160200151919003601f190160051c101590565b909182108015610873575b61079457601f1901101590565b50601f8216151561086656fea2646970667358221220435fb6a1dbeb66dccc928b0ed9c2dec64283566ad336124d8fa31e5ecf03b89464736f6c634300081b0033";

packages/blue-sdk-viem/test/Token.test.ts

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { describe, expect } from "vitest";
2-
import { test } from "./setup.js";
2+
import { test, testTreehouseEth } from "./setup.js";
33

44
import {
55
ChainId,
@@ -65,7 +65,9 @@ describe("augment/Token", () => {
6565
expect(value).toStrictEqual(expectedData);
6666
});
6767

68-
test("should fetch token data with eip5267Domain", async ({ client }) => {
68+
test("should fetch token data with eip5267Domain using deployless reads", async ({
69+
client,
70+
}) => {
6971
const steakUSDC = "0xBEEF01735c132Ada46AA9aA4c54623cAA92A64CB";
7072
const expectedData = new Token({
7173
address: steakUSDC,
@@ -83,8 +85,38 @@ describe("augment/Token", () => {
8385
}),
8486
});
8587

86-
const value = await Token.fetch(expectedData.address, client);
88+
const value = await Token.fetch(expectedData.address, client, {
89+
deployless: "force",
90+
});
8791

8892
expect(value).toStrictEqual(expectedData);
8993
});
94+
95+
testTreehouseEth(
96+
"should fetch Treehouse ETH data with eip5267Domain using deployless reads",
97+
async ({ client }) => {
98+
const treehouseEth = "0xD11c452fc99cF405034ee446803b6F6c1F6d5ED8";
99+
const expectedData = new Token({
100+
address: treehouseEth,
101+
decimals: 18,
102+
symbol: "tETH",
103+
name: "Treehouse ETH",
104+
eip5267Domain: new Eip5267Domain({
105+
fields: "0x0f",
106+
name: "Treehouse ETH",
107+
version: "1",
108+
chainId: 1n,
109+
verifyingContract: treehouseEth,
110+
salt: zeroHash,
111+
extensions: [],
112+
}),
113+
});
114+
115+
const value = await Token.fetch(expectedData.address, client, {
116+
deployless: "force",
117+
});
118+
119+
expect(value).toStrictEqual(expectedData);
120+
},
121+
);
90122
});

packages/blue-sdk-viem/test/setup.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,14 @@ export const test2 = createViemTest(mainnet, {
1717
forkBlockNumber: 21_595_000,
1818
});
1919

20+
/**
21+
* This test will run on `mainnet` forked at block `24,671,815`.
22+
*/
23+
export const testTreehouseEth = createViemTest(mainnet, {
24+
forkUrl: process.env.MAINNET_RPC_URL,
25+
forkBlockNumber: 24_671_815,
26+
});
27+
2028
/**
2129
* This test will run on `mainnet` forked at block `21,950,00`.
2230
*/

0 commit comments

Comments
 (0)