Skip to content

Commit 5818030

Browse files
authored
fix: data cleanup + precompile/predeploy separation (#23)
* fix: separate precompile/predeploys * fix: html bugs * fix: difficulty/prevrandao fixes and cleanup * fix: handle empty diff scenario * fix: more data cleanup on diffs * fix: more accurate precompile notes * chore: remove unused import * feat: add predeploy proxy/logic ABIs and logic address * build: bump viem version * refactor: better precompile check script
1 parent c9fd962 commit 5818030

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+1515
-352
lines changed

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
"next-themes": "^0.2.1",
2020
"react": "18.2.0",
2121
"react-dom": "18.2.0",
22-
"viem": "^0.3.37"
22+
"viem": "^1.5.0"
2323
},
2424
"devDependencies": {
2525
"@tailwindcss/forms": "^0.5.3",
@@ -29,7 +29,9 @@
2929
"@types/react-dom": "18.2.3",
3030
"@typescript-eslint/eslint-plugin": "^6.1.0",
3131
"@typescript-eslint/parser": "^6.1.0",
32+
"abitype": "^0.9.6",
3233
"autoprefixer": "10.4.14",
34+
"clipboardy": "^3.0.0",
3335
"eslint": "8.39.0",
3436
"eslint-config-next": "13.4.0",
3537
"eslint-plugin-react": "^7.32.2",

pnpm-lock.yaml

Lines changed: 60 additions & 9 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

script/fetch-abi.ts

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
// Given an Etherscan URL, fetch the ABI and convert it to viem's human readable format.
2+
// Example usage:
3+
// bun script/fetch-abi.ts https://arbiscan.io/address/0x0000000000000000000000000000000000000064
4+
import { formatAbi } from 'abitype';
5+
import clipboardy from 'clipboardy';
6+
import { Address } from 'viem';
7+
8+
type ChainConfig = {
9+
name: string;
10+
urlPart: string;
11+
apiPrefix: string;
12+
apiKey: string;
13+
};
14+
15+
const chains: Record<string, ChainConfig> = {
16+
arbitrum: {
17+
name: 'Arbitrum',
18+
urlPart: 'arbiscan.io',
19+
apiPrefix: 'api.',
20+
apiKey: process.env.ARBISCAN_API_KEY!,
21+
},
22+
optimism: {
23+
name: 'Optimism',
24+
urlPart: 'optimistic.etherscan.io',
25+
apiPrefix: 'api-',
26+
apiKey: process.env.OPTIMISM_API_KEY!,
27+
},
28+
};
29+
30+
async function getAbi(apiUrl: string) {
31+
// Fetch the ABI from the API.
32+
const response = await fetch(apiUrl);
33+
const data = await response.json();
34+
if (data.status !== '1') throw new Error('API request failed: ' + data.message);
35+
36+
// Return formatted ABI.
37+
const abi = JSON.parse(data.result);
38+
return formatAbi(abi);
39+
}
40+
41+
function getChain(url: string): ChainConfig {
42+
for (const chainName in chains) {
43+
if (url.includes(chains[chainName].urlPart)) {
44+
const chain = chains[chainName];
45+
if (!chain.apiKey) throw new Error(`API key not found for chain ${chainName}`);
46+
return chain;
47+
}
48+
}
49+
throw new Error(`Chain not recognized from URL: ${url}`);
50+
}
51+
52+
function getEtherscanUrl(chain: ChainConfig, address: Address, kind: 'abi' | 'source') {
53+
const query = kind === 'abi' ? 'getabi' : 'getsourcecode';
54+
const { apiPrefix, apiKey, urlPart } = chain;
55+
return `https://${apiPrefix}${urlPart}/api?module=contract&action=${query}&address=${address}&apikey=${apiKey}`;
56+
}
57+
58+
async function main() {
59+
// Use the first argument as the URL and get the contract address from it.
60+
const url = process.argv[2];
61+
if (!url) throw new Error('URL is required');
62+
63+
// Get the address and chain from the URL.
64+
const address = url.split('/').pop() as Address;
65+
if (!address) throw new Error('Could not parse address from URL');
66+
const chain = getChain(url);
67+
68+
// Get the ABI.
69+
const abiRequestUrl = getEtherscanUrl(chain, address, 'abi');
70+
const formattedAbi1 = await getAbi(abiRequestUrl);
71+
72+
// Now we check if it's a proxy contract.
73+
// Note that for some optimism predeploys that are proxies, this does not return the
74+
// implementation address, so it's not guaranteed to work.
75+
const sourceRequestUrl = getEtherscanUrl(chain, address, 'source');
76+
const sourceResponse = await fetch(sourceRequestUrl);
77+
const data = await sourceResponse.json();
78+
if (data.status !== '1') throw new Error('API request failed: ' + data.message);
79+
const implementationAddress = data.result[0].Implementation as '' | Address;
80+
81+
// If it is a proxy contract, fetch the implementation ABI.
82+
let formattedAbi2;
83+
if (implementationAddress) {
84+
const implAbiRequestUrl = getEtherscanUrl(chain, implementationAddress, 'abi');
85+
formattedAbi2 = await getAbi(implAbiRequestUrl);
86+
}
87+
88+
// If we have a second ABI, separate the two with a divider so they can be easily distinguished.
89+
const fullAbi = formattedAbi2 ? formattedAbi1.concat('--------', formattedAbi2) : formattedAbi1;
90+
91+
// Copy the formatted ABI to the clipboard.
92+
clipboardy.writeSync(JSON.stringify(fullAbi));
93+
const kind = formattedAbi2 ? ' proxy and implementation ' : ' ';
94+
console.log(`✅ Copied${kind}ABI to clipboard for ${chain.name} contract ${address}`);
95+
}
96+
97+
main().catch(console.error);
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// Given a JSON ABI on the clipboard, replace the clipboard contents with a viem human-readable ABI.
2+
// Example usage:
3+
// bun script/json-abi-to-viem-human-readable.ts
4+
import { formatAbi } from 'abitype';
5+
import clipboardy from 'clipboardy';
6+
7+
const abi = clipboardy.readSync();
8+
const formattedAbi = formatAbi(JSON.parse(abi));
9+
clipboardy.writeSync(JSON.stringify(formattedAbi));
10+
console.log('✅ Copied formatted ABI to clipboard');

script/optimism-difficulty.sh

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
for i in {1..100}; do
2+
cast call 0xca11bde05977b3631167028862be2a173976ca11 "aggregate((address,bytes)[])(uint256, bytes[])" "[(0xca11bde05977b3631167028862be2a173976ca11,0x72425d9d)]" --rpc-url $MAINNET_RPC_URL
3+
cast call 0xca11bde05977b3631167028862be2a173976ca11 "aggregate((address,bytes)[])(uint256, bytes[])" "[(0xca11bde05977b3631167028862be2a173976ca11,0x72425d9d)]" --rpc-url $OPTIMISM_RPC_URL
4+
echo "---"
5+
done

script/precompile-check.ts

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
// Given an Etherscan URL, fetch the ABI and convert it to viem's human readable format.
2+
// Example usage:
3+
// bun script/precompile-check.ts optimism
4+
import { Address, createPublicClient, getAddress, http } from 'viem';
5+
import { Chain, arbitrum, optimism } from 'viem/chains';
6+
7+
type ChainConfig = {
8+
addresses: Address[];
9+
chain: Chain;
10+
rpcUrl: string;
11+
};
12+
13+
const addressMap: Record<string, ChainConfig> = {
14+
arbitrum: {
15+
chain: arbitrum,
16+
rpcUrl: process.env.ARBITRUM_RPC_URL!,
17+
addresses: [
18+
'0x5288c571Fd7aD117beA99bF60FE0846C4E84F933',
19+
'0x09e9222E96E7B4AE2a407B98d48e330053351EEe',
20+
'0x096760F208390250649E3e8763348E783AEF5562',
21+
'0x6c411aD3E74De3E7Bd422b94A27770f5B86C623B',
22+
'0x82aF49447D8a07e3bd95BD0d56f35241523fBab1',
23+
'0xd570aCE65C43af47101fC6250FD6fC63D1c22a86',
24+
'0x0000000000000000000000000000000000000064',
25+
'0x000000000000000000000000000000000000006E',
26+
'0x000000000000000000000000000000000000006C',
27+
'0x0000000000000000000000000000000000000066',
28+
'0x000000000000000000000000000000000000006F',
29+
'0x00000000000000000000000000000000000000C8',
30+
'0x0000000000000000000000000000000000000067',
31+
'0x0000000000000000000000000000000000000065',
32+
'0x0000000000000000000000000000000000000070',
33+
'0x000000000000000000000000000000000000006b',
34+
'0x000000000000000000000000000000000000006D',
35+
'0x0000000000000000000000000000000000000068',
36+
],
37+
},
38+
optimism: {
39+
chain: optimism,
40+
rpcUrl: process.env.OPTIMISM_RPC_URL!,
41+
addresses: [
42+
'0x4200000000000000000000000000000000000000',
43+
'0x4200000000000000000000000000000000000002',
44+
'0xDeadDeAddeAddEAddeadDEaDDEAdDeaDDeAD0000',
45+
'0x4200000000000000000000000000000000000006',
46+
'0x4200000000000000000000000000000000000007',
47+
'0x4200000000000000000000000000000000000010',
48+
'0x4200000000000000000000000000000000000011',
49+
'0x4200000000000000000000000000000000000012',
50+
'0x4200000000000000000000000000000000000013',
51+
'0x420000000000000000000000000000000000000F',
52+
'0x4200000000000000000000000000000000000042',
53+
'0x4200000000000000000000000000000000000015',
54+
'0x4200000000000000000000000000000000000016',
55+
'0x4200000000000000000000000000000000000014',
56+
'0x4200000000000000000000000000000000000017',
57+
'0x4200000000000000000000000000000000000018',
58+
'0x4200000000000000000000000000000000000019',
59+
'0x420000000000000000000000000000000000001a',
60+
],
61+
},
62+
};
63+
64+
async function main() {
65+
const chainName = process.argv[2];
66+
const chainData = addressMap[chainName];
67+
const { chain, rpcUrl, addresses } = chainData;
68+
if (!addresses) throw new Error(`Unknown chain: ${chainName}`);
69+
if (!rpcUrl) throw new Error(`Undefined RPC URL for chain: ${chainName}`);
70+
71+
const transport = http(rpcUrl, { batch: true });
72+
const client = createPublicClient({ chain, transport });
73+
74+
const promises = addresses.map((address) => client.getBytecode({ address }));
75+
const codes = await Promise.all(promises);
76+
const kinds = codes.map((code) =>
77+
code === '0x' || code === '0xfe' ? 'precompile' : 'predeploy'
78+
);
79+
80+
addresses.forEach((address, i) => console.log(`${getAddress(address)} ${kinds[i]}`));
81+
}
82+
83+
main().catch(console.error);

src/chains/arbitrum/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,12 @@ import { Chain } from '@/types';
44
import { signatureTypes } from './signatureTypes';
55
import { opcodes } from './vm/opcodes';
66
import { precompiles } from './vm/precompiles';
7+
import { predeploys } from './vm/predeploys';
78

89
export const arbitrum: Chain = {
910
metadata: arbitrumMetadata,
1011
precompiles,
12+
predeploys,
1113
signatureTypes: sortedArrayByField(signatureTypes, 'prefixByte'),
1214
opcodes: sortedArrayByField(opcodes, 'number'),
1315
};

src/chains/arbitrum/vm/opcodes/block/blockhash.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,19 @@ export const blockhash: Omit<Opcode, 'supportedHardforks'> = {
66
...opcode,
77
description:
88
'Returns a cryptographically insecure, pseudo-random hash for `x` within the range `block.number - 256 <= x < block.number`. If `x` is outside of this range, `blockhash(x)` will return 0. This includes `blockhash(block.number)`, which always returns 0 just like on Ethereum. The hashes returned do not come from L1.',
9+
outputs: [
10+
{
11+
name: 'hash',
12+
description:
13+
'The pseudo-random hash for the input block number, or 0 if the block number is not in the valid range',
14+
},
15+
],
16+
examples: [
17+
{
18+
input: '17813636',
19+
output: '0xfe4f20b10608dbb75f84782733dd434832c50192993f7389386dfa40f6feda4b',
20+
},
21+
],
922
references: [
1023
{
1124
name: 'Differences between Arbitrum and Ethereum opcodes',

0 commit comments

Comments
 (0)