Skip to content

Commit

Permalink
auto: fallbackLoad
Browse files Browse the repository at this point in the history
  • Loading branch information
shazow committed Sep 18, 2024
1 parent e8b9dd0 commit 10bcbfb
Show file tree
Hide file tree
Showing 2 changed files with 100 additions and 0 deletions.
50 changes: 50 additions & 0 deletions src/__tests__/auto.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,3 +70,53 @@ online_test('autoload non-contract', async ({ provider, env }) => {
});
expect(abi).toStrictEqual([]);
});

online_test('autoload fallback', async ({ provider, env }) => {
const address = "0xdAC17F958D2ee523a2206206994597C13D831ec7"; // USDT, available on both PulseChain and Mainnet (but only verified on mainnet)
const pulseChainProvider = makeProvider("https://pulsechain-rpc.publicnode.com");
{
// Fails to find verified ABI on pulseChain
const result = await autoload(address, {
provider: pulseChainProvider,
signatureLookup: false,
abiLoader: false,
});
expect(result.isVerified).toBeFalsy();
expect(result.abi).not.toContainEqual({
"constant": true,
"inputs": [],
"name": "totalSupply",
"outputs": [ { "name": "", "type": "uint256", }],
"payable": false,
"stateMutability": "view",
"type": "function",
});
}
{
// Succeeds by cross-loading mainnet
const result = await autoload(address, {
provider: pulseChainProvider,
signatureLookup: false,
abiLoader: false,
// onProgress: (phase: string, ...args: any[]) => { console.debug("Mainnet PROGRESS", phase, args); },
fallbackLoad: {
provider,
signatureLookup: false,
// onProgress: (phase: string, ...args: any[]) => { console.debug("fallback PROGRESS", phase, args); },
...whatsabi.loaders.defaultsWithEnv(env),
}
});
expect(result.isVerified).toBeTruthy();
expect(result.abi).toContainEqual({
"constant": true,
"inputs": [],
"name": "totalSupply",
"outputs": [ { "name": "", "type": "uint256", }],
"payable": false,
"stateMutability": "view",
"type": "function",
});
}
});


50 changes: 50 additions & 0 deletions src/auto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,23 @@ export type AutoloadConfig = {
*/
followProxies?: boolean;

/**
* By default, we'll only try to load the ABI from the respective chain.
* But if it doesn't exist, we can check if the same verified contract exists on another network and use it instead.
* Suggest setting { signatureLookup: false } for this, as it would be redundant.
*
* @group Settings
*/
fallbackLoad?: AutoloadConfig;

/**
* Keccak256 hash of the contract bytecode (0x-prefixed hex string).
*
* Confirm that the bytecode we get for the contract matches some existing assumed bytecode.
* This is used by fallbackLoad to verify that the bytecode matches on both chains.
*/
assertBytecodeHash?: string;

/**
* Enable pulling additional metadata from WhatsABI's static analysis, still unreliable
*
Expand Down Expand Up @@ -159,6 +176,22 @@ export async function autoload(address: string, config: AutoloadConfig): Promise
}
if (!bytecode) return result; // Must be an EOA

// We only need to get the hash if we're asserting, so let's be lazy
let bytecodeHash : string = (config.assertBytecodeHash || config.fallbackLoad) ? keccak256(bytecode) : "";

if (config.assertBytecodeHash) {
if (!config.assertBytecodeHash.startsWith("0x")) {
throw new errors.AutoloadError(`assertBytecodeHash must be a hex string starting with 0x`, {
context: { address, assertBytecodeHash: config.assertBytecodeHash },
});
}
if (config.assertBytecodeHash !== bytecodeHash) {
throw new errors.AutoloadError(`Contract bytecode hash does not match assertBytecodeHash`, {
context: { address, bytecodeHash, assertBytecodeHash: config.assertBytecodeHash },
});
}
}

const program = disasm(bytecode);

// FIXME: Sort them in some reasonable way
Expand Down Expand Up @@ -214,6 +247,23 @@ export async function autoload(address: string, config: AutoloadConfig): Promise
}
}

if (config.fallbackLoad) {
onProgress("crossChainLoad", { address });
try {
const r = await autoload(
address,
Object.assign({ assertBytecodeHash: bytecodeHash }, config.fallbackLoad),
);
if (r.isVerified) {
result.abi = r.abi;
result.isVerified = true; // Bytecode is asserted, so we claim it's verified
return result;
}
} catch (error: any) {
if (onError("crossChainLoad", error) === true) return result;
}
}

// Load from code
onProgress("abiFromBytecode", { address });
result.abi = abiFromBytecode(program);
Expand Down

0 comments on commit 10bcbfb

Please sign in to comment.