Skip to content

Commit

Permalink
ERC20 WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
rokso committed Feb 11, 2025
1 parent c7b220a commit de337e0
Show file tree
Hide file tree
Showing 12 changed files with 353 additions and 16 deletions.
4 changes: 2 additions & 2 deletions networks.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
{
"optimism": {
"PositionRegistry": {
"address": "0x447BEbdE07F6115b2714fd3a28e311b9d4a9E6f6",
"startBlock": 128171866
"address": "0xeE156D8ea7b96a5524CcC3CF9283ab85E80E9534",
"startBlock": 131291973
}
}
}
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"build": "run-s codegen && graph build",
"codegen": "graph codegen",
"graph": "graph",
"deploy": "graph deploy odyssey-init --node https://api.studio.thegraph.com/deploy/",
"deploy": "graph deploy odyssey-erc20 --node https://api.studio.thegraph.com/deploy/",
"test": "graph test -d"
},
"devDependencies": {
Expand Down
28 changes: 28 additions & 0 deletions schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,38 @@ type Strategy @entity {
type SmartAccount @entity {
# smart account address
id: Bytes!
# entry point contract address
entryPoint: EntryPoint!
# position registry address
positionRegistry: PositionRegistry!
# total position of this smart account
positionCount: BigInt!
# positions of this smart account
positions: [Position!]! @derivedFrom(field: "owner")
# token balance of this smart account
balances: [Balance!]! @derivedFrom(field: "smartAccount")
}

type EntryPoint @entity {
# entry point address
id: Bytes!
# smart accounts created by entry point
smartAccounts: [SmartAccount!]! @derivedFrom(field: "entryPoint")
}

type Token @entity {
# token address
id: Bytes!
name: String!
symbol: String!
decimals: BigInt!
balances: [Balance!]! @derivedFrom(field: "token")
}

type Balance @entity {
# balance id
id: String!
token: Token!
smartAccount: SmartAccount!
value: BigInt!
}
16 changes: 10 additions & 6 deletions src/mappings/entry-point.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
import { AccountDeployed } from '../../generated/EntryPoint/EntryPoint'
import { SmartAccount } from '../../generated/schema'
import { ADDRESS_ZERO, BIGINT_ZERO } from '../utils/constants'
import { EntryPoint } from '../../generated/schema'
import { loadOrCreateSmartAccount } from '../utils/entity'

export function handleAccountDeployed(event: AccountDeployed): void {
const smartAccount = new SmartAccount(event.params.sender)
smartAccount.positionCount = BIGINT_ZERO
smartAccount.positionRegistry = ADDRESS_ZERO
smartAccount.save()
let entryPoint = EntryPoint.load(event.address)
if (!entryPoint) {
entryPoint = new EntryPoint(event.address)
}
entryPoint.save()

// this call will always end up creating smartAccount entity and that's the goal.
loadOrCreateSmartAccount(event.params.sender)
}
6 changes: 4 additions & 2 deletions src/mappings/position-registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,18 @@ import {
StrategyAdded as StrategyAddedEvent,
PositionRegistry as PositionRegistryContract,
} from '../../generated/PositionRegistry/PositionRegistry'
import { PositionRegistry, Position, Strategy, SmartAccount } from '../../generated/schema'
import { PositionRegistry, Position, Strategy } from '../../generated/schema'
import { Position as PositionTemplate } from '../../generated/templates'
import { ADDRESS_ZERO, BIGINT_ONE, BIGINT_ZERO } from '../utils/constants'
import { loadOrCreateSmartAccount } from '../utils/entity'

export function handlePositionDeployed(event: PositionDeployedEvent): void {
// at this point we know that positionRegistry is initialized
const registry = PositionRegistry.load(event.address)!

// create smartAccount entity if smartAccount is deployed before startBlock.
// load smartAccount entity, Set PositionRegistry, this update mark smartAccount as Odyssey smartAccount.
const smartAccount = SmartAccount.load(event.params.owner)!
const smartAccount = loadOrCreateSmartAccount(event.params.owner)
if (smartAccount.positionRegistry == ADDRESS_ZERO) {
smartAccount.positionRegistry = registry.id
registry.smartAccountCount = registry.smartAccountCount.plus(BIGINT_ONE)
Expand Down
1 change: 0 additions & 1 deletion src/mappings/position.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import {
} from '../../generated/templates/Position/Position'
import { Position } from '../../generated/schema'
import { ADDRESS_ZERO, BIGINT_ONE, BIGINT_ZERO } from '../utils/constants'
import { Bytes } from '@graphprotocol/graph-ts'

export function handlePositionOpened(event: PositionOpened): void {
const position = Position.load(event.address)!
Expand Down
149 changes: 149 additions & 0 deletions src/mappings/token.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
import { Address, Bytes, ethereum } from '@graphprotocol/graph-ts'
import { ERC20, Transfer } from '../../generated/ERC20/ERC20'
import { Token, Balance, EntryPoint, SmartAccount } from '../../generated/schema'
import { ENTRY_POINT } from '../utils/config'

// load or create token entity
function loadOrCreateToken(event: ethereum.Event): Token | null {
let token = Token.load(event.address)
if (!token) {
// bind address to call contract method
const erc20 = ERC20.bind(event.address)

const nameResult = erc20.try_name()
if (nameResult.reverted) {
return null
}

const symbolResult = erc20.try_symbol()
if (symbolResult.reverted) {
return null
}

const decimalsResult = erc20.try_decimals()
if (decimalsResult.reverted) {
return null
}

token = new Token(event.address)
token.name = nameResult.value
token.symbol = symbolResult.value
token.decimals = decimalsResult.value
token.save()
}
return token
}

// create or update balance entity
function createOrUpdateBalance(tokenId: Bytes, accountId: Address): Balance {
const id = tokenId.toHex().concat('-').concat(accountId.toHex())
let balance = Balance.load(id)
if (!balance) {
balance = new Balance(id)
balance.token = tokenId
balance.smartAccount = accountId
}
// Read balance from contract
balance.value = ERC20.bind(Address.fromBytes(tokenId)).balanceOf(accountId)
balance.save()
return balance as Balance
}

// function createBalance(tokenId: Bytes, accountId: Address): Balance | null {
// const id = tokenId.toHex().concat('-').concat(accountId.toHex())
// const balance = new Balance(id)
// balance.token = tokenId
// balance.smartAccount = accountId
// // First time read balance from contract
// // const result = ERC20.bind(Address.fromBytes(tokenId)).try_balanceOf(accountId)
// // if (result.reverted) {
// // return null
// // }
// // FIXME if 2 transfer are in same block then this read is reading final state and next transfer will messed up value
// balance.value = ERC20.bind(Address.fromBytes(tokenId)).balanceOf(accountId)
// balance.save()
// return balance
// }

// function loadBalance(tokenId: Bytes, accountId: Address): Balance | null {
// const id = tokenId.toHex().concat('-').concat(accountId.toHex())
// return Balance.load(id)
// }

// returns true if account exist in array
// function doesAccountExist(smartAccounts: SmartAccount[], accountId: Address): boolean {
// for (let i = 0; i < smartAccounts.length; i++) {
// if (smartAccounts[i].id == accountId) {
// return true
// }
// }
// return false
// }

// // returns true if account exist in array
// function doesAccountsExist(smartAccounts: SmartAccount[], from: Address, to: Address): boolean[] {
// let isFromExist = false
// let isToExist = false

// for (let i = 0; i < smartAccounts.length; i++) {
// if (smartAccounts[i].id == from) {
// isFromExist = true
// } else if (smartAccounts[i].id == to) {
// isToExist = true
// }
// if (isFromExist && isToExist) {
// break
// }
// }
// return [isFromExist, isToExist]
// }

// returns true if account exist in array
function doesAccountsExist(smartAccounts: SmartAccount[], from: Address, to: Address): boolean[] {
let isFromExist = false
let isToExist = false

for (let i = 0; i < smartAccounts.length; i++) {
if (smartAccounts[i].id == from) {
isFromExist = true
} else if (smartAccounts[i].id == to) {
isToExist = true
}
if (isFromExist && isToExist) {
break
}
}
return [isFromExist, isToExist]
}

// handle transfer event
export function handleTransfer(event: Transfer): void {
const entryPoint = EntryPoint.load(ENTRY_POINT)

if (!entryPoint) {
return
}
const smartAccounts = entryPoint.smartAccounts.load()

const from = event.params.from
const to = event.params.to

// Check whether SmartAccount exist in storage
const output = doesAccountsExist(smartAccounts, from, to)
const isFromExist = output[0]
const isToExist = output[1]

// if any of the smartAccount exist in storage then handle token and balance creation
if (isFromExist || isToExist) {
const token = loadOrCreateToken(event)
if (!token) {
return
}
if (isFromExist) {
createOrUpdateBalance(token.id, from)
}
if (isToExist) {
createOrUpdateBalance(token.id, to)
}
}
}
4 changes: 4 additions & 0 deletions src/utils/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { Address } from '@graphprotocol/graph-ts'

export const POSITION_REGISTRY = Address.fromString('0xeE156D8ea7b96a5524CcC3CF9283ab85E80E9534')
export const ENTRY_POINT = Address.fromString('0x0000000071727De22E5E9d8BAf0edAc6f37da032')
17 changes: 17 additions & 0 deletions src/utils/entity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { Address } from '@graphprotocol/graph-ts'
import { SmartAccount } from '../../generated/schema'
import { ENTRY_POINT } from './config'
import { ADDRESS_ZERO, BIGINT_ZERO } from './constants'

export function loadOrCreateSmartAccount(id: Address): SmartAccount {
let smartAccount = SmartAccount.load(id)
if (!smartAccount) {
smartAccount = new SmartAccount(id)
smartAccount.entryPoint = ENTRY_POINT
smartAccount.positionCount = BIGINT_ZERO
// PositionRegistry will be updated when smartAccount deploy a position.
smartAccount.positionRegistry = ADDRESS_ZERO
smartAccount.save()
}
return smartAccount
}
32 changes: 29 additions & 3 deletions subgraph.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@ dataSources:
name: PositionRegistry
network: optimism
source:
address: "0x447BEbdE07F6115b2714fd3a28e311b9d4a9E6f6"
# address: "0x447BEbdE07F6115b2714fd3a28e311b9d4a9E6f6"
address: "0xeE156D8ea7b96a5524CcC3CF9283ab85E80E9534"
abi: PositionRegistry
startBlock: 128171866
startBlock: 131291973
mapping:
kind: ethereum/events
apiVersion: 0.0.9
Expand Down Expand Up @@ -38,7 +39,7 @@ dataSources:
source:
address: "0x0000000071727De22E5E9d8BAf0edAc6f37da032"
abi: EntryPoint
startBlock: 128171866
startBlock: 131291973
mapping:
kind: ethereum/events
apiVersion: 0.0.9
Expand All @@ -54,6 +55,31 @@ dataSources:
- event: AccountDeployed(indexed bytes32,indexed address,address,address)
handler: handleAccountDeployed

- kind: ethereum
name: ERC20
network: optimism
source:
abi: ERC20
startBlock: 131291973
mapping:
kind: ethereum/events
apiVersion: 0.0.9
language: wasm/assemblyscript
file: ./src/mappings/token.ts
entities:
- SmartAccount
- Token
- Balance
abis:
- name: ERC20
file: ./abis/ERC20.json
eventHandlers:
- event: Transfer(indexed address,indexed address,uint256)
handler: handleTransfer
calls:
ERC20.balanceOf.from: ERC20[event.address].balanceOf(event.params.from)
ERC20.balanceOf.to: ERC20[event.address].balanceOf(event.params.to)

templates:
- kind: ethereum/contract
name: Position
Expand Down
Loading

0 comments on commit de337e0

Please sign in to comment.