diff --git a/src/models/evm_smart_contract.ts b/src/models/evm_smart_contract.ts index 4d7005103..6dbc616b6 100644 --- a/src/models/evm_smart_contract.ts +++ b/src/models/evm_smart_contract.ts @@ -1,5 +1,6 @@ /* eslint-disable import/no-cycle */ import { Model } from 'objection'; +import { keccak256, toHex } from 'viem'; import BaseModel from './base'; import { EvmProxyHistory } from './evm_proxy_history'; @@ -60,6 +61,7 @@ export class EVMSmartContract extends BaseModel { PROXY_EIP_1822: 'PROXY_EIP_1822', PROXY_OPEN_ZEPPELIN_IMPLEMENTATION: 'PROXY_OPEN_ZEPPELIN_IMPLEMENTATION', PROXY_EIP_1167: 'PROXY_EIP_1167', + PROXY_EIP_1967_BEACON: 'PROXY_EIP_1967_BEACON', }; } @@ -82,4 +84,24 @@ export class EVMSmartContract extends BaseModel { }, }; } + + static PROXY_EVENT_TOPIC0 = { + BEACON_UPGRADED: keccak256(toHex('BeaconUpgraded(address)')), + }; + + static BEACON_ABI = [ + { + inputs: [], + name: 'implementation', + outputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + ]; } diff --git a/src/services/evm/constant.ts b/src/services/evm/constant.ts index 9588e70fe..bdf370d9d 100644 --- a/src/services/evm/constant.ts +++ b/src/services/evm/constant.ts @@ -56,10 +56,10 @@ export const EIPProxyContractSupportByteCode = { TYPE: EVMSmartContract.TYPES.PROXY_EIP_1967, }, // TODO: support beacon soon. - // EIP_1967_BEACON: { - // SLOT: 'a3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50', // eip1967.proxy.beacon - // TYPE: EVMSmartContract.TYPES.PROXY_EIP_1967, - // }, + EIP_1967_BEACON: { + SLOT: 'a3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50', // eip1967.proxy.beacon + TYPE: EVMSmartContract.TYPES.PROXY_EIP_1967_BEACON, + }, // EIP_1967_ADMIN: { // SLOT: 'b53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103', // eip1967.proxy.admin // TYPE: EVMSmartContract.TYPES.PROXY_EIP_1967, diff --git a/src/services/evm/crawl_contract_evm.service.ts b/src/services/evm/crawl_contract_evm.service.ts index 85bbe1209..d98376d27 100644 --- a/src/services/evm/crawl_contract_evm.service.ts +++ b/src/services/evm/crawl_contract_evm.service.ts @@ -11,6 +11,7 @@ import { BlockCheckpoint, EVMSmartContract, EVMTransaction, + EvmEvent, EvmInternalTransaction, } from '../../models'; import { @@ -169,8 +170,22 @@ export default class CrawlSmartContractEVMService extends BullableService { ); const bytecodesByAddress: _.Dictionary = await this.getBytecodeContracts(notFoundAddresses); + const beaconContracts = ( + await EvmEvent.query() + .where('evm_tx_id', '>=', fromTx.id) + .andWhere('evm_tx_id', '<=', toTx.id) + .andWhere('topic0', EVMSmartContract.PROXY_EVENT_TOPIC0.BEACON_UPGRADED) + .select('address', 'topic1') + ).map((e) => ({ + address: e.address, + beacon: `0x${e.topic1.slice(26)}`, + })); const proxyInfoByAddress: _.Dictionary = - await this.getContractsProxyInfo(notFoundAddresses, bytecodesByAddress); + await this.getContractsProxyInfo( + notFoundAddresses, + bytecodesByAddress, + beaconContracts + ); notFoundAddresses.forEach((address: string) => { const code = bytecodesByAddress[address]; // check if this address has code -> is smart contract @@ -304,7 +319,11 @@ export default class CrawlSmartContractEVMService extends BullableService { async getContractsProxyInfo( addrs: string[], - bytecodes: _.Dictionary + bytecodes: _.Dictionary, + beaconContracts: { + address: string; + beacon: string; + }[] ) { const result: _.Dictionary = {}; const proxiesInfo = await Promise.all( @@ -334,6 +353,9 @@ export default class CrawlSmartContractEVMService extends BullableService { byteCode, EIPProxyContractSupportByteCode.EIP_1167_IMPLEMENTATION.SLOT ), + this.contractHelper.detectBeaconProxyContract( + beaconContracts.find((e) => e.address === addr)?.beacon + ), ]).catch(() => Promise.resolve(null)); }) ); diff --git a/src/services/evm/helpers/contract_helper.ts b/src/services/evm/helpers/contract_helper.ts index 991240802..785090b83 100644 --- a/src/services/evm/helpers/contract_helper.ts +++ b/src/services/evm/helpers/contract_helper.ts @@ -1,5 +1,6 @@ import _ from 'lodash'; -import { PublicClient } from 'viem'; +import { getContract, PublicClient } from 'viem'; +import { EvmEvent, EVMSmartContract } from '../../../models'; import '../../../../fetch-polyfill.js'; import { DetectEVMProxyContract, @@ -62,6 +63,24 @@ export class ContractHelper { return resultReturn; } + public async detectBeaconProxyContract( + beacon?: string + ): Promise { + if (!beacon) { + throw Error('Not beacon contract!'); + } + const contract = getContract({ + address: beacon as `0x${string}`, + abi: EVMSmartContract.BEACON_ABI, + client: this.viemClient, + }); + const implementation = (await contract.read.implementation()) as string; + return { + logicContractAddress: implementation, + EIP: EIPProxyContractSupportByteCode.EIP_1967_BEACON.TYPE, + }; + } + // Detect contract is proxy contract or not public async isContractProxy( contractAddress: string, @@ -78,6 +97,16 @@ export class ContractHelper { if (!byteCode) { return null; } + let beaconContract = ( + await EvmEvent.query() + .where('address', contractAddress) + .andWhere('topic0', EVMSmartContract.PROXY_EVENT_TOPIC0.BEACON_UPGRADED) + .first() + .select('topic1') + )?.topic1.slice(26); + if (beaconContract) { + beaconContract = `0x${beaconContract}`; + } try { if (byteCodeSlot) { result = await this.detectProxyContractByByteCode( @@ -100,13 +129,7 @@ export class ContractHelper { EIPProxyContractSupportByteCode.EIP_1967_IMPLEMENTATION.SLOT, blockHeight ), - // TODO: support beacon soon. - // this.detectProxyContractByByteCode( - // contractAddress, - // byteCode, - // EIPProxyContractSupportByteCode.EIP_1967_BEACON.SLOT, - // blockHeight - // ), + this.detectBeaconProxyContract(beaconContract), this.detectProxyContractByByteCode( contractAddress, byteCode,