Skip to content

fix: data cleanup + precompile/predeploy separation #23

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

Merged
merged 10 commits into from
Jul 31, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
"next-themes": "^0.2.1",
"react": "18.2.0",
"react-dom": "18.2.0",
"viem": "^0.3.37"
"viem": "^1.5.0"
},
"devDependencies": {
"@tailwindcss/forms": "^0.5.3",
Expand All @@ -29,7 +29,9 @@
"@types/react-dom": "18.2.3",
"@typescript-eslint/eslint-plugin": "^6.1.0",
"@typescript-eslint/parser": "^6.1.0",
"abitype": "^0.9.6",
"autoprefixer": "10.4.14",
"clipboardy": "^3.0.0",
"eslint": "8.39.0",
"eslint-config-next": "13.4.0",
"eslint-plugin-react": "^7.32.2",
Expand Down
69 changes: 60 additions & 9 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

97 changes: 97 additions & 0 deletions script/fetch-abi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
// Given an Etherscan URL, fetch the ABI and convert it to viem's human readable format.
// Example usage:
// bun script/fetch-abi.ts https://arbiscan.io/address/0x0000000000000000000000000000000000000064
import { formatAbi } from 'abitype';
import clipboardy from 'clipboardy';
import { Address } from 'viem';

type ChainConfig = {
name: string;
urlPart: string;
apiPrefix: string;
apiKey: string;
};

const chains: Record<string, ChainConfig> = {
arbitrum: {
name: 'Arbitrum',
urlPart: 'arbiscan.io',
apiPrefix: 'api.',
apiKey: process.env.ARBISCAN_API_KEY!,
},
optimism: {
name: 'Optimism',
urlPart: 'optimistic.etherscan.io',
apiPrefix: 'api-',
apiKey: process.env.OPTIMISM_API_KEY!,
},
};

async function getAbi(apiUrl: string) {
// Fetch the ABI from the API.
const response = await fetch(apiUrl);
const data = await response.json();
if (data.status !== '1') throw new Error('API request failed: ' + data.message);

// Return formatted ABI.
const abi = JSON.parse(data.result);
return formatAbi(abi);
}

function getChain(url: string): ChainConfig {
for (const chainName in chains) {
if (url.includes(chains[chainName].urlPart)) {
const chain = chains[chainName];
if (!chain.apiKey) throw new Error(`API key not found for chain ${chainName}`);
return chain;
}
}
throw new Error(`Chain not recognized from URL: ${url}`);
}

function getEtherscanUrl(chain: ChainConfig, address: Address, kind: 'abi' | 'source') {
const query = kind === 'abi' ? 'getabi' : 'getsourcecode';
const { apiPrefix, apiKey, urlPart } = chain;
return `https://${apiPrefix}${urlPart}/api?module=contract&action=${query}&address=${address}&apikey=${apiKey}`;
}

async function main() {
// Use the first argument as the URL and get the contract address from it.
const url = process.argv[2];
if (!url) throw new Error('URL is required');

// Get the address and chain from the URL.
const address = url.split('/').pop() as Address;
if (!address) throw new Error('Could not parse address from URL');
const chain = getChain(url);

// Get the ABI.
const abiRequestUrl = getEtherscanUrl(chain, address, 'abi');
const formattedAbi1 = await getAbi(abiRequestUrl);

// Now we check if it's a proxy contract.
// Note that for some optimism predeploys that are proxies, this does not return the
// implementation address, so it's not guaranteed to work.
const sourceRequestUrl = getEtherscanUrl(chain, address, 'source');
const sourceResponse = await fetch(sourceRequestUrl);
const data = await sourceResponse.json();
if (data.status !== '1') throw new Error('API request failed: ' + data.message);
const implementationAddress = data.result[0].Implementation as '' | Address;

// If it is a proxy contract, fetch the implementation ABI.
let formattedAbi2;
if (implementationAddress) {
const implAbiRequestUrl = getEtherscanUrl(chain, implementationAddress, 'abi');
formattedAbi2 = await getAbi(implAbiRequestUrl);
}

// If we have a second ABI, separate the two with a divider so they can be easily distinguished.
const fullAbi = formattedAbi2 ? formattedAbi1.concat('--------', formattedAbi2) : formattedAbi1;

// Copy the formatted ABI to the clipboard.
clipboardy.writeSync(JSON.stringify(fullAbi));
const kind = formattedAbi2 ? ' proxy and implementation ' : ' ';
console.log(`✅ Copied${kind}ABI to clipboard for ${chain.name} contract ${address}`);
}

main().catch(console.error);
10 changes: 10 additions & 0 deletions script/json-abi-to-viem-human-readable.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// Given a JSON ABI on the clipboard, replace the clipboard contents with a viem human-readable ABI.
// Example usage:
// bun script/json-abi-to-viem-human-readable.ts
import { formatAbi } from 'abitype';
import clipboardy from 'clipboardy';

const abi = clipboardy.readSync();
const formattedAbi = formatAbi(JSON.parse(abi));
clipboardy.writeSync(JSON.stringify(formattedAbi));
console.log('✅ Copied formatted ABI to clipboard');
5 changes: 5 additions & 0 deletions script/optimism-difficulty.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
for i in {1..100}; do
cast call 0xca11bde05977b3631167028862be2a173976ca11 "aggregate((address,bytes)[])(uint256, bytes[])" "[(0xca11bde05977b3631167028862be2a173976ca11,0x72425d9d)]" --rpc-url $MAINNET_RPC_URL
cast call 0xca11bde05977b3631167028862be2a173976ca11 "aggregate((address,bytes)[])(uint256, bytes[])" "[(0xca11bde05977b3631167028862be2a173976ca11,0x72425d9d)]" --rpc-url $OPTIMISM_RPC_URL
echo "---"
done
83 changes: 83 additions & 0 deletions script/precompile-check.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
// Given an Etherscan URL, fetch the ABI and convert it to viem's human readable format.
// Example usage:
// bun script/precompile-check.ts optimism
import { Address, createPublicClient, getAddress, http } from 'viem';
import { Chain, arbitrum, optimism } from 'viem/chains';

type ChainConfig = {
addresses: Address[];
chain: Chain;
rpcUrl: string;
};

const addressMap: Record<string, ChainConfig> = {
arbitrum: {
chain: arbitrum,
rpcUrl: process.env.ARBITRUM_RPC_URL!,
addresses: [
'0x5288c571Fd7aD117beA99bF60FE0846C4E84F933',
'0x09e9222E96E7B4AE2a407B98d48e330053351EEe',
'0x096760F208390250649E3e8763348E783AEF5562',
'0x6c411aD3E74De3E7Bd422b94A27770f5B86C623B',
'0x82aF49447D8a07e3bd95BD0d56f35241523fBab1',
'0xd570aCE65C43af47101fC6250FD6fC63D1c22a86',
'0x0000000000000000000000000000000000000064',
'0x000000000000000000000000000000000000006E',
'0x000000000000000000000000000000000000006C',
'0x0000000000000000000000000000000000000066',
'0x000000000000000000000000000000000000006F',
'0x00000000000000000000000000000000000000C8',
'0x0000000000000000000000000000000000000067',
'0x0000000000000000000000000000000000000065',
'0x0000000000000000000000000000000000000070',
'0x000000000000000000000000000000000000006b',
'0x000000000000000000000000000000000000006D',
'0x0000000000000000000000000000000000000068',
],
},
optimism: {
chain: optimism,
rpcUrl: process.env.OPTIMISM_RPC_URL!,
addresses: [
'0x4200000000000000000000000000000000000000',
'0x4200000000000000000000000000000000000002',
'0xDeadDeAddeAddEAddeadDEaDDEAdDeaDDeAD0000',
'0x4200000000000000000000000000000000000006',
'0x4200000000000000000000000000000000000007',
'0x4200000000000000000000000000000000000010',
'0x4200000000000000000000000000000000000011',
'0x4200000000000000000000000000000000000012',
'0x4200000000000000000000000000000000000013',
'0x420000000000000000000000000000000000000F',
'0x4200000000000000000000000000000000000042',
'0x4200000000000000000000000000000000000015',
'0x4200000000000000000000000000000000000016',
'0x4200000000000000000000000000000000000014',
'0x4200000000000000000000000000000000000017',
'0x4200000000000000000000000000000000000018',
'0x4200000000000000000000000000000000000019',
'0x420000000000000000000000000000000000001a',
],
},
};

async function main() {
const chainName = process.argv[2];
const chainData = addressMap[chainName];
const { chain, rpcUrl, addresses } = chainData;
if (!addresses) throw new Error(`Unknown chain: ${chainName}`);
if (!rpcUrl) throw new Error(`Undefined RPC URL for chain: ${chainName}`);

const transport = http(rpcUrl, { batch: true });
const client = createPublicClient({ chain, transport });

const promises = addresses.map((address) => client.getBytecode({ address }));
const codes = await Promise.all(promises);
const kinds = codes.map((code) =>
code === '0x' || code === '0xfe' ? 'precompile' : 'predeploy'
);

addresses.forEach((address, i) => console.log(`${getAddress(address)} ${kinds[i]}`));
}

main().catch(console.error);
2 changes: 2 additions & 0 deletions src/chains/arbitrum/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@ import { Chain } from '@/types';
import { signatureTypes } from './signatureTypes';
import { opcodes } from './vm/opcodes';
import { precompiles } from './vm/precompiles';
import { predeploys } from './vm/predeploys';

export const arbitrum: Chain = {
metadata: arbitrumMetadata,
precompiles,
predeploys,
signatureTypes: sortedArrayByField(signatureTypes, 'prefixByte'),
opcodes: sortedArrayByField(opcodes, 'number'),
};
13 changes: 13 additions & 0 deletions src/chains/arbitrum/vm/opcodes/block/blockhash.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,19 @@ export const blockhash: Omit<Opcode, 'supportedHardforks'> = {
...opcode,
description:
'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.',
outputs: [
{
name: 'hash',
description:
'The pseudo-random hash for the input block number, or 0 if the block number is not in the valid range',
},
],
examples: [
{
input: '17813636',
output: '0xfe4f20b10608dbb75f84782733dd434832c50192993f7389386dfa40f6feda4b',
},
],
references: [
{
name: 'Differences between Arbitrum and Ethereum opcodes',
Expand Down
Loading