Skip to content
This repository has been archived by the owner on Apr 26, 2024. It is now read-only.

Commit

Permalink
Add AxelarProxy as an auto-detecting proxy type (#75)
Browse files Browse the repository at this point in the history
* AxelarProxy

* Rename

* Provide a way to get the slot of the implementation

* fetch admin addresses

* ylff

* Fetch owners and operators

* changeset
  • Loading branch information
mateuszradomski authored Oct 20, 2023
1 parent f73c11e commit 0f7094e
Show file tree
Hide file tree
Showing 7 changed files with 213 additions and 3 deletions.
6 changes: 6 additions & 0 deletions packages/discovery-types/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# @l2beat/discovery-types

## 0.4.1

### Patch Changes

- Add AxelarProxy as auto detecting proxy

## 0.4.0

### Minor Changes
Expand Down
2 changes: 1 addition & 1 deletion packages/discovery-types/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@l2beat/discovery-types",
"description": "Common types for @l2beat/discovery.",
"version": "0.4.0",
"version": "0.4.1",
"license": "MIT",
"repository": "https://github.com/l2beat/tools",
"bugs": {
Expand Down
12 changes: 12 additions & 0 deletions packages/discovery-types/src/proxyDetails.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ export type UpgradeabilityParameters =
| PolygonExtensionProxyUpgradeability
| ZkSpaceProxyUpgradeability
| OpticsBeaconProxyUpgradeability
| AxelarProxyUpgradeability

export interface ImmutableUpgradeability {
type: 'immutable'
Expand Down Expand Up @@ -157,3 +158,14 @@ export interface OpticsBeaconProxyUpgradeability {
beaconController: EthereumAddress
implementation: EthereumAddress
}

export interface AxelarProxyUpgradeability {
type: 'Axelar proxy'
admins: EthereumAddress[]
adminThreshold: number
owners: EthereumAddress[]
ownerThreshold: number
operators: EthereumAddress[]
operatorThreshold: number
implementation: EthereumAddress
}
8 changes: 8 additions & 0 deletions packages/discovery/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
# @l2beat/discovery

## 0.17.2

### Patch Changes

- Add AxelarProxy as auto detecting proxy
- Updated dependencies
- @l2beat/discovery-types@0.4.1

## 0.17.1

### Patch Changes
Expand Down
4 changes: 2 additions & 2 deletions packages/discovery/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@l2beat/discovery",
"description": "L2Beat discovery - engine & tooling utilized for keeping an eye on L2s",
"version": "0.17.1",
"version": "0.17.2",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"bin": {
Expand All @@ -19,7 +19,7 @@
},
"dependencies": {
"@l2beat/backend-tools": "^0.4.0",
"@l2beat/discovery-types": "^0.4.0",
"@l2beat/discovery-types": "^0.4.1",
"chalk": "^4.1.2",
"deep-diff": "^1.0.2",
"dotenv": "^16.0.3",
Expand Down
2 changes: 2 additions & 0 deletions packages/discovery/src/discovery/proxies/ProxyDetector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { EthereumAddress } from '../../utils/EthereumAddress'
import { DiscoveryLogger } from '../DiscoveryLogger'
import { DiscoveryProvider } from '../provider/DiscoveryProvider'
import { detectArbitrumProxy } from './auto/ArbitrumProxy'
import { detectAxelarProxy } from './auto/AxelarProxy'
import { detectEip897Proxy } from './auto/Eip897Proxy'
import { detectEip1967Proxy } from './auto/Eip1967Proxy'
import { detectEip2535proxy } from './auto/Eip2535Proxy'
Expand Down Expand Up @@ -38,6 +39,7 @@ const DEFAULT_AUTO_DETECTORS: Detector[] = [
detectEip897Proxy,
detectZeppelinOSProxy,
detectEip2535proxy,
detectAxelarProxy,
]

const MANUAL_DETECTORS: Record<ManualProxyType, Detector> = {
Expand Down
182 changes: 182 additions & 0 deletions packages/discovery/src/discovery/proxies/auto/AxelarProxy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
import { assert } from '@l2beat/backend-tools'
import { ProxyDetails } from '@l2beat/discovery-types'
import { BigNumber, BytesLike, utils } from 'ethers'

import { Bytes } from '../../../utils/Bytes'
import { EthereumAddress } from '../../../utils/EthereumAddress'
import { DiscoveryProvider } from '../../provider/DiscoveryProvider'
import { bytes32ToAddress } from '../../utils/address'

// keccak256(abi.encode(uint256(keccak256('eip1967.proxy.implementation')) - 1, s))
//
// where `s` is the slot of the `_addressStorage`, so in this case it's s = 2
const IMPLEMENTATION_SLOT = Bytes.fromHex(
'0x11141f466c69fd409e1990e063b49cd6d61ed2ecff27a2e402e259ca6b9a01a3',
)

async function getImplementation(
provider: DiscoveryProvider,
address: EthereumAddress,
blockNumber: number,
): Promise<EthereumAddress> {
return bytes32ToAddress(
await provider.getStorage(address, IMPLEMENTATION_SLOT, blockNumber),
)
}

export async function detectAxelarProxy(
provider: DiscoveryProvider,
address: EthereumAddress,
blockNumber: number,
): Promise<ProxyDetails | undefined> {
const implementation = await getImplementation(provider, address, blockNumber)
if (implementation === EthereumAddress.ZERO) {
return
}

const adminDeployment = await getAdminsDeployment(provider, address)
const owners = await getOwners(provider, address, blockNumber)
const operators = await getOperators(provider, address, blockNumber)
assert(owners && operators)

return {
implementations: [implementation],
relatives: [],
upgradeability: {
type: 'Axelar proxy',
admins: adminDeployment.adminAddresses,
adminThreshold: adminDeployment.adminThreshold,
owners: owners.ownerAddresses,
ownerThreshold: owners.ownerThreshold,
operators: operators.operatorAddresses,
operatorThreshold: operators.operatorThreshold,
implementation,
},
}
}

async function getAdminsDeployment(
provider: DiscoveryProvider,
address: EthereumAddress,
): Promise<{
adminAddresses: EthereumAddress[]
adminThreshold: number
}> {
const constructorInterface = utils.Fragment.from(
'constructor(bytes memory params)',
)
const result = await provider.getConstructorArgs(address)
const decodedArgs = utils.defaultAbiCoder.decode(
constructorInterface.inputs,
'0x' + result,
)

const decodedParams = utils.defaultAbiCoder.decode(
[
'address[]', // adminAddresses
'uint256', // adminThreshold
'address[]', // ownerAddresses
'uint256', // ownerThreshold
'address[]', // operatorAddresses
'uint256', // operatorThreshold
],
decodedArgs[0] as BytesLike,
)

const [adminAddresses, adminThreshold] = decodedParams

return {
adminAddresses: (adminAddresses as string[]).map((a) => EthereumAddress(a)),
adminThreshold: (adminThreshold as BigNumber).toNumber(),
}
}

async function getOwners(
provider: DiscoveryProvider,
address: EthereumAddress,
blockNumber: number,
): Promise<
| {
ownerAddresses: EthereumAddress[]
ownerThreshold: number
}
| false
> {
const event = new utils.Interface([
'event OwnershipTransferred(address[] preOwners, uint256 prevThreshold, address[] newOwners, uint256 newThreshold)',
])

const transfers = await provider.getLogs(
address,
[event.getEventTopic('OwnershipTransferred')],
0,
blockNumber,
)

const lastTransfer = transfers.at(-1)
if (lastTransfer === undefined) {
return false
}

const [_preOwners, _prevThreshold, newOwners, newThreshold] =
utils.defaultAbiCoder.decode(
[
'address[]', // preOwners
'uint256', // prevThreshold
'address[]', // newOwners
'uint256', // newThreshold
],
lastTransfer.data,
)

return {
ownerAddresses: (newOwners as string[]).map((a) => EthereumAddress(a)),
ownerThreshold: (newThreshold as BigNumber).toNumber(),
}
}

async function getOperators(
provider: DiscoveryProvider,
address: EthereumAddress,
blockNumber: number,
): Promise<
| {
operatorAddresses: EthereumAddress[]
operatorThreshold: number
}
| false
> {
const event = new utils.Interface([
'event OperatorshipTransferred(address[] preOperators, uint256 prevThreshold, address[] newOperators, uint256 newThreshold)',
])

const transfers = await provider.getLogs(
address,
[event.getEventTopic('OperatorshipTransferred')],
0,
blockNumber,
)

const lastTransfer = transfers.at(-1)
if (lastTransfer === undefined) {
return false
}

const [_preOperators, _prevThreshold, newOperators, newThreshold] =
utils.defaultAbiCoder.decode(
[
'address[]', // preOperators
'uint256', // prevThreshold
'address[]', // newOperators
'uint256', // newThreshold
],
lastTransfer.data,
)

return {
operatorAddresses: (newOperators as string[]).map((a) =>
EthereumAddress(a),
),
operatorThreshold: (newThreshold as BigNumber).toNumber(),
}
}

0 comments on commit 0f7094e

Please sign in to comment.