Skip to content

Commit

Permalink
Rewrite proxy and decode transaction guides (#197)
Browse files Browse the repository at this point in the history
  • Loading branch information
anastasiarods authored Jan 12, 2025
1 parent 21796d4 commit 7970424
Show file tree
Hide file tree
Showing 6 changed files with 109 additions and 94 deletions.
7 changes: 7 additions & 0 deletions apps/docs/src/content/components/abi-store-interface.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
```ts title="vanilla.ts"
export interface VanillaAbiStore {
strategies?: readonly ContractAbiResolverStrategy[]
get: (key: AbiParams) => Promise<ContractAbiResult>
set: (key: AbiParams, val: ContractAbiResult) => Promise<void>
}
```
7 changes: 7 additions & 0 deletions apps/docs/src/content/components/meta-store-interface.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
```ts title="vanilla.ts"
export interface VanillaContractMetaStore {
strategies?: readonly VanillaContractMetaStategy[]
get: (key: ContractMetaParams) => Promise<ContractMetaResult>
set: (key: ContractMetaParams, val: ContractMetaResult) => Promise<void>
}
```
91 changes: 54 additions & 37 deletions apps/docs/src/content/docs/guides/decode-transaction.mdx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
title: How to decode an Ethereum Transaction
title: Decode an Ethereum Transaction (detailed)
description: On this page you will provide a step-by-step guide on how to decode and interpret an Ethereum transaction using Loop Decoder.
sidebar:
order: 1
Expand All @@ -8,64 +8,87 @@ sidebar:
import { Content as MemoryAbiLoader } from '../../components/memory-abi-loader.md'
import { Content as MemoryContractLoader } from '../../components/memory-contract-loader.md'
import { Content as RpcProvider } from '../../components/rpc-provider.md'
import { Content as AbiStoreInterface } from '../../components/abi-store-interface.md'
import { Content as MetaStoreInterface } from '../../components/meta-store-interface.md'

In this guide, we will go through the process of decoding an Ethereum transaction using Loop Decoder. For the simplicity of the example, we assume that that contract ABIs involved in the transaction are verified on Etherscan.
This guide explains how to decode Ethereum transactions using Loop Decoder. We'll cover:

We recomend to copy all snipepts to a typescript project and run it at the end of this guide, or or you can copy the whole example from this file: [Full Example Code](https://stackblitz.com/~/github.com/3loop/loop-decoder/tree/main/sandbox/quick-start). Do not forget to replace the placeholder `YourApiKeyToken` with your own free Etherscan API key.
- Setting up data loading strategies for ABIs and contract metadata
- Configuring data stores for Contract ABIs and metadata
- Decoding transactions

## Prerequisites
## Installation

### Create a new project

Optionally, you can create a new project to follow along, or skip to [Required packages](#required-packages).

1. Install Bun:
First, make sure you have Bun installed on your system. If you haven't installed it yet, you can do so using npm:

```bash
npm install -g bun
```

1. Generate and initialize a new project:
Generate and initialize a new project:

```bash
mkdir example-decode && cd example-decode
bun init
```

### Required packages

For this guide, you will need to have the following packages installed:
Install required packages:

```bash
bun install @3loop/transaction-decoder viem
```

## Data Sources
## Setup Loop Decoder

Loop Decoder requires three components:

Loop Decoder requires some data sources to be able to decode transactions. We will need an RPC provider, a data source to fetch Contracts ABIs and a data source to fetch contract meta-information, such as token name, decimals, symbol, etc.
1. RPC Provider: Fetches raw transaction data
2. ABI Data Store: Retrieves and caches contract ABIs
3. Contract Metadata Store: Retrieves and caches contract metadata (e.g., token name, symbol, decimals)

### RPC Provider
### 1. RPC Provider

We will start by creating a function which will return an object with PublicClient based on the chain ID. For the sake of this example, we will only support mainnet.
Create a `getPublicClient` function that accepts a chain ID and returns an object with [Viem](https://viem.sh/) `PublicClient`.

<RpcProvider />

### ABI loader
For detailed configuration options and trace API settings, see the [RPC Provider](/reference/rpc-provider/) documentation.

### 2. ABI Data Store

The ABI Data Store handles:

- Fetching ABIs using predefined strategies (e.g., Etherscan, 4byte). Some strategies like Etherscan require an API key. See the full list of strategies in [Data Loaders (ABI Strategies)](/reference/data-loaders/#abi-strategies)
- Caching fetched ABIs

To create a custom ABI Data Store, implement the `VanillaAbiStore` interface:

To avoid making unecessary calls to third-party APIs, Loop Decoder uses an API that allows cache. For this example, we will keep it simple and use an in-memory cache. We will also use some strategies to download contract ABIs from Etherscan and 4byte.directory. You can find more information about the strategies in the [Strategies](/reference/data-loaders/) reference.
<AbiStoreInterface />

Create a cache for contract ABI and add your free Etherscan API key instead of the placeholder `YourApiKeyToken`:
#### Example: an ABI data store with Etherscan and 4byte strategies and in-memory cache

<MemoryAbiLoader />

### Contract Metadata loader
:::tip
You can use persistent data stores, like a database or file system, by redefining the `get` and `set` methods from the example above.
:::

Create an in-memory cache for contract meta-information. Using `ERC20RPCStrategyResolver` we will automatically retrieve token meta information from the contract such as token name, decimals, symbol, etc.
### 3. Contract Metadata Store

The Contract Metadata Store handles:

- Fetching contract metadata using predefined strategies (e.g., ERC20, NFT). See the full list of strategies in [Data Loaders (Contract Metadata)](/reference/data-loaders/#contract-metadata)
- Caching fetched contract metadata

To create a custom Contract Metadata Store, implement the `VanillaContractMetaStore` interface:

<MetaStoreInterface />

#### Example: a Contract Metadata Store with ERC20 strategy and in-memory cache

<MemoryContractLoader />

Finally, you can create a new instance of the LoopDecoder class:
:::tip
You can use persistent data stores, like a database or file system, by redefining the `get` and `set` methods from the example above.
:::

### 4. Initializing Loop Decoder

Finally, you can create a new instance of the TransactionDecoder class:

```ts
import { TransactionDecoder } from '@3loop/transaction-decoder'
Expand All @@ -77,9 +100,9 @@ const decoder = new TransactionDecoder({
})
```

## Decoding a Transaction
## Example: Decoding a Transaction

Now that we have all the necessary components, we can start decoding a transaction. For this example, we will use the following transaction:
Once the `TransactionDecoder` is set up, you can use it to decode a transaction by calling the `decodeTransaction` method:

```ts
async function main() {
Expand All @@ -98,12 +121,6 @@ async function main() {
main()
```

Run the script:

```bash
bun run index.ts
```

Expected output:

```json
Expand Down
4 changes: 2 additions & 2 deletions apps/docs/src/content/docs/reference/data-loaders.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ Loop Decoder provides some strategies out of the box:

ABI strategies will receive the contract address, and event or function signature as input and would return the ABI as a stringified JSON. Loop Decoder provides some strategies out of the box:

- `EtherscanStrategyResolver` - resolves the ABI from Etherscan
- `EtherscanV2StrategyResolver` - resolves the ABI from Etherscan v2
- `EtherscanStrategyResolver` - resolves the ABI from Etherscan, requires an API key to work properly
- `EtherscanV2StrategyResolver` - resolves the ABI from Etherscan v2, requires an API key to work properly
- `SourcifyStrategyResolver` - resolves the ABI from Sourcify
- `FourByteStrategyResolver` - resolves the ABI from 4byte.directory
- `OpenchainStrategyResolver` - resolves the ABI from Openchain
Expand Down
70 changes: 27 additions & 43 deletions apps/docs/src/content/docs/reference/proxy-resolution.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,64 +5,48 @@ sidebar:
order: 3
---

Proxies are smart contracts that delegate calls to another contract, typically referred to as the implementation or logic contract. They allow the implementation to be updated without changing the proxy's address, ensuring that users continue interacting with the same address.
Proxy contracts are smart contracts that forward their calls to another contract - known as the implementation or logic contract. This pattern enables contract upgrades without changing the proxy's address, ensuring users can continue interacting with the same address.

## Types of Proxies
Loop Decoder automatically detects and handles proxy contracts when resolving ABIs and decoding function parameters.

#### 1. Standard Proxy
## Supported Proxy Types

Standard proxies are primarily used for upgradeable contracts, allowing the implementation contract to be updated without changing the address of the proxy contract. They have a higher deployment cost.
| Proxy Type | Description |
| ---------- | -------------------------------------------------- |
| EIP-1967 | The standard transparent proxy implementation. |
| EIP-1167 | Minimal proxy implementation. |
| Safe | Gnosis Safe multi-signature wallet implementation. |
| Zeppelin | OpenZeppelin's proxy implementation. |

Standard proxy contracts often use standardized storage slots to store the address of the implementation contract. The most common slot is the EIP-1967 standard, but there are also additional slots used by frameworks like OpenZeppelin or blockchain-specific implementations.

#### 2. Minimal Proxy

Minimal Proxies, also known as EIP-1167 proxies, are designed for efficient deployment of multiple instances of the same contract logic. They are not upgradeable and have a fixed implementation address.

#### 3. UUPS (Universal Upgradeable Proxy Standard)

UUPS proxies are an evolution of the standard proxy pattern. They move the upgrade functionality to the implementation contract, reducing the proxy contract's complexity and gas costs.

#### 4. Beacon Proxy
## How Loop Decoder resolves proxy contracts

Beacon Proxies introduce an additional contract called a beacon, which stores the implementation address. Multiple proxy contracts can point to the same beacon, allowing for simultaneous upgrades of multiple proxies by updating the beacon's implementation address.
For each smart contract, Loop Decoder resolves the implementation address using standard JSON-RPC API calls and the following methods.

## Proxy Detection Methods
1. Bytecode analysis
2. Static slot-based detection
3. Call-based detection

#### 1. Static Slot-Based Detection
### 1. Bytecode analysis

This method checks specific storage slots in a contract (e.g., EIP-1967 or custom slots) to retrieve the implementation address. It's efficient but limited to proxies that follow standard storage patterns.
This method provides the most reliable and accurate way to detect proxy contracts. It examines the contract's bytecode to identify the delegatecall instruction.

#### 2. Call-Based Detection
For `EIP-1167` proxies, resolution occurs during this step. For other types, bytecode analysis generates a list of possible proxy implementations.

This approach involves analyzing how a contract responds to function calls, which can indicate whether it is delegating calls as a proxy. This method works for both standard and non-standard proxies but requires more computational resources.
### 2. Static slot-based detection

#### 3. Bytecode Analysis
This method examines specific storage slots in a contract (e.g., `EIP-1967` or custom slots) to retrieve the implementation address. While efficient, it only works with proxies that follow standard storage patterns.

Examining a contract’s bytecode for specific patterns, such as delegatecall instructions, can help identify proxy contracts. This method is particularly useful for detecting Minimal Proxies
### 3. Call-based detection

## How Loop Decoder resolves proxy contracts
This approach analyzes how a contract responds to function calls, determining whether it delegates calls as a proxy. The method works for both standard and non-standard proxies but consumes more computational resources.

Loop Decoder uses a static slot-based detection to identify proxy contracts. It checks for the presence of the implementation address in known storage slots by calling the `getStorageAt` RPC method.
## Example

```typescript
const storageSlots: Hex[] = [
'0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc', //eipEIP1967
'0x7050c9e0f4ca769c69bd3a8ef740bc37934f8e2c036e5a723fd8ee048ed3f8c3', //zeppelin
// ... and more
]

storageSlots.map((slot) => {
const res = await publicClient.getStorageAt({
address: contractAddress,
slot,
})

if (res === zeroSlot) {
// if the slot is empty it means that this contract is not a proxy
return undefined
}

return res
import { getProxyImplementation } from '@3loop/transaction-decoder'

const implementationAddress = await getProxyImplementation({
address: '0x1234567890abcdef',
chainId: 1,
})
```
24 changes: 12 additions & 12 deletions apps/docs/src/content/docs/welcome/getting-started.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,15 @@ import { Content as RpcProvider } from '../../components/rpc-provider.md'
npm i @3loop/transaction-decoder viem effect
```

### Overview
## Quick Start

To begin using the Loop Decoder, you must provide three components:
Loop Decoder requires three components:

- RPC Provider
- ABI Loader
- Contract Metadata Loader
1. RPC Provider
2. ABI Data Store
3. Contract Metadata Store

This guide demonstrates setup using the default in-memory implementations for data loaders. For custom storage solutions, see our [How To Decode Transaction](/guides/decode-transaction/) guide.
This guide demonstrates setup using the default in-memory implementations for Data Stores. For custom storage solutions, see our [How To Decode Transaction](/guides/decode-transaction/) guide.

### 1. Set up your RPC Provider

Expand All @@ -31,9 +31,9 @@ Create a `getPublicClient` function that accepts a chain ID and returns an objec

For detailed configuration options and trace API settings, see the [RPC Provider](/reference/rpc-provider/) documentation.

### 2. Initialize ABI Loader
### 2. Initialize ABI Data Store

The `InMemoryAbiStoreLive` provides default ABI loading functionality:
The `InMemoryAbiStoreLive` provides default ABI loading and caching functionality:

- Fetches ABIs from multiple sources (Etherscan, 4bytes, Openchain, Sourcify)
- Caches results in memory
Expand All @@ -48,9 +48,9 @@ const ABILoaderLayer = Layer.setConfigProvider(Config)
const abiStore = InMemoryAbiStoreLive.pipe(Layer.provide(ABILoaderLayer))
```

For a custom implementation, see our [How To Decode Transaction, ABI Loader](/guides/decode-transaction/#abi-loader) guide.
For a custom implementation, see our [How To Decode Transaction (ABI Data Store)](/guides/decode-transaction/#2-abi-data-store) guide.

### 3. Initialize Contract Metadata Loader
### 3. Initialize Contract Metadata Store

The `InMemoryContractMetaStoreLive` handles contract metadata resolution:

Expand All @@ -63,9 +63,9 @@ import { InMemoryContractMetaStoreLive } from '@3loop/transaction-decoder/in-mem
const contractMetaStore = InMemoryContractMetaStoreLive
```

For a custom implementation, see our [How To Decode Transaction, Contract Metadata Loader](/guides/decode-transaction/#contract-metadata-loader) guide.
For a custom implementation, see our [How To Decode Transaction (Contract Metadata Store)](/guides/decode-transaction/#3-contract-metadata-store) guide.

### 4. Final Step
### 4. Decode a Transaction

Finally, you can create a new instance of the LoopDecoder class and invoke `decodeTransaction` method with the transaction hash and chain ID:

Expand Down

0 comments on commit 7970424

Please sign in to comment.