Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixed Float Funding V2 Update #542

Open
wants to merge 5 commits into
base: @v2-bots
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 7 additions & 10 deletions fixed-float-funding-py/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,23 +1,20 @@
# Build stage: compile Python dependencies
FROM python:3.9-alpine as builder
FROM python:3.10-alpine as builder
RUN apk update
RUN apk add alpine-sdk
RUN python3 -m pip install --upgrade pip
COPY requirements.txt ./
COPY forta_bot-0.2.0.tar.gz ./
RUN python3 -m pip install --user -r requirements.txt

# Final stage: copy over Python dependencies and install production Node dependencies
FROM node:12-alpine
# this python version should match the build stage python version
RUN apk add python3
# Final stage: copy over Python dependencies
FROM python:3.10-alpine
COPY --from=builder /root/.local /root/.local
ENV PATH=/root/.local:$PATH
ENV NODE_ENV=production
# Uncomment the following line to enable agent logging
ENV FORTA_ENV=production
# Uncomment the following line to enable logging
LABEL "network.forta.settings.agent-logs.enable"="true"
WORKDIR /app
COPY ./src ./src
COPY package*.json ./
COPY LICENSE.md ./
RUN npm ci --production
CMD [ "npm", "run", "start:prod" ]
CMD [ "python3", "./src/agent.py" ]
2,301 changes: 939 additions & 1,362 deletions fixed-float-funding-py/package-lock.json

Large diffs are not rendered by default.

54 changes: 24 additions & 30 deletions fixed-float-funding-py/package.json
Original file line number Diff line number Diff line change
@@ -1,27 +1,24 @@
{
"name": "fixed-float-funding",
"displayName": "Fixed Float Funding",
"version": "0.0.1",
"name": "fixed-float-funding-beta",
"displayName": "Fixed Float Funding (beta)",
"version": "0.1.0",
"engines": {
"node": ">=20"
},
"description": "Detecting addresses being funded by Fixed Float",
"repository": "https://github.com/forta-network/starter-kits/tree/main/fixed-float-funding-py",
"licenseUrl": "https://github.com/forta-network/forta-bot-sdk/blob/master/starter-project/LICENSE.md",
"promoUrl": "https://forta.org",
"chainIds": [
1,
56,
137,
43114,
42161
],
"chainSettings": {
"1": {
"shards": 10,
"target": 3
},
"56": {
"shards": 16,
"target": 3
},
"137": {
"shards": 20,
"target": 3
Expand All @@ -34,28 +31,25 @@
"scripts": {
"postinstall": "python3 -m pip install -r requirements_dev.txt",
"start": "npm run start:dev",
"start:dev": "nodemon --watch src --watch forta.config.json -e py --exec \"forta-agent run\"",
"start:prod": "forta-agent run --prod",
"tx": "forta-agent run --tx",
"block": "forta-agent run --block",
"range": "forta-agent run --range",
"alert": "forta-agent run --alert",
"sequence": "forta-agent run --sequence",
"file": "forta-agent run --file",
"publish": "forta-agent publish",
"info": "forta-agent info",
"logs": "forta-agent logs",
"push": "forta-agent push",
"disable": "forta-agent disable",
"enable": "forta-agent enable",
"keyfile": "forta-agent keyfile",
"stake": "forta-agent stake",
"start:dev": "nodemon --watch src --watch forta.config.json -e py --exec \"python3 ./src/agent.py\"",
"start:prod": "node ./src/agent.js",
"tx": "forta-bot run --tx",
"block": "forta-bot run --block",
"range": "forta-bot run --range",
"alert": "forta-bot run --alert",
"sequence": "forta-bot run --sequence",
"file": "forta-bot run --file",
"publish": "forta-bot publish",
"info": "forta-bot info",
"logs": "forta-bot logs",
"push": "forta-bot push",
"disable": "forta-bot disable",
"enable": "forta-bot enable",
"keyfile": "forta-bot keyfile",
"stake": "forta-bot stake",
"test": "python3 -m pytest"
},
"dependencies": {
"forta-agent": "^0.1.48"
},
"devDependencies": {
"nodemon": "^2.0.8"
"forta-bot-cli": "file:forta-bot-cli-0.2.0.tgz"
}
}
}
5 changes: 3 additions & 2 deletions fixed-float-funding-py/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
forta_agent>=0.1.27
setuptools>=61.3.1
forta_bot-0.2.0.tar.gz
setuptools>=61.3.1
async-lru==2.0.4
5 changes: 3 additions & 2 deletions fixed-float-funding-py/requirements_dev.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
-r requirements.txt
pytest==6.2.5
pytest-env==0.6.2
pytest==7.2.1
pytest-env==0.6.2
pytest-asyncio==0.23.4
87 changes: 54 additions & 33 deletions fixed-float-funding-py/src/agent.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
import logging
import sys

from forta_agent import get_json_rpc_url, Web3
from forta_bot import scan_ethereum, scan_polygon, scan_arbitrum, TransactionEvent, get_chain_id, run_health_check
from hexbytes import HexBytes
from functools import lru_cache
from async_lru import alru_cache
import asyncio
from web3 import Web3, AsyncWeb3

from src.constants import *
from src.findings import FundingFixedFloatFindings

# Initialize web3
web3 = Web3(Web3.HTTPProvider(get_json_rpc_url()))
from constants import *
from findings import FundingFixedFloatFindings

# Logging set up
root = logging.getLogger()
Expand Down Expand Up @@ -38,26 +37,27 @@ def initialize():
DENOMINATOR_COUNT = 0

global CHAIN_ID
CHAIN_ID = web3.eth.chain_id

CHAIN_ID = get_chain_id()

@lru_cache(maxsize=100000)
def is_contract(w3, address):
@alru_cache(maxsize=100000)
async def is_contract(w3, address):
"""
this function determines whether address is a contract
:return: is_contract: bool
"""
if address is None:
return True
code = w3.eth.get_code(Web3.toChecksumAddress(address))
code = await w3.eth.get_code(w3.to_checksum_address(address))
return code != HexBytes('0x')

@lru_cache(maxsize=100000)
def is_new_account(w3, address, block_number):
return w3.eth.get_transaction_count(Web3.toChecksumAddress(address), block_number) == 0
@alru_cache(maxsize=100000)
async def is_new_account(w3, address, block_number):
if address is None:
return True
return await w3.eth.get_transaction_count(w3.to_checksum_address(address), block_number) == 0


def detect_fixed_float_funding(w3, transaction_event):
async def detect_fixed_float_funding(w3, transaction_event):
global LOW_VOL_ALERT_COUNT
global NEW_EOA_ALERT_COUNT
global DENOMINATOR_COUNT
Expand All @@ -72,35 +72,56 @@ def detect_fixed_float_funding(w3, transaction_event):

native_value = transaction_event.transaction.value / 10e17

if (native_value > 0 and (native_value < fixed_float_threshold or is_new_account(w3, transaction_event.to, transaction_event.block_number)) and not is_contract(w3, transaction_event.to)):
is_new_acc = await is_new_account(w3, transaction_event.to, transaction_event.block_number)
is_contr = await is_contract(w3, transaction_event.to)

if (native_value > 0 and (native_value < fixed_float_threshold or is_new_acc) and not is_contr):
DENOMINATOR_COUNT += 1


"""
if the transaction is from Fixed Float, and not to a contract: check if transaction count is 0,
else check if value sent is less than the threshold
"""
if (transaction_event.from_ == FIXED_FLOAT_ADDRESS[CHAIN_ID] and not is_contract(w3, transaction_event.to)):
if is_new_account(w3, transaction_event.to, transaction_event.block_number):
if (transaction_event.from_ == FIXED_FLOAT_ADDRESS[CHAIN_ID] and not is_contr):
if is_new_acc:
NEW_EOA_ALERT_COUNT += 1
score = (1.0 * NEW_EOA_ALERT_COUNT) / DENOMINATOR_COUNT
score = str((1.0 * NEW_EOA_ALERT_COUNT) / DENOMINATOR_COUNT)
findings.append(FundingFixedFloatFindings.funding_fixed_float(transaction_event, "new-eoa", score, CHAIN_ID))
elif native_value < fixed_float_threshold:
LOW_VOL_ALERT_COUNT += 1
score = (1.0 * LOW_VOL_ALERT_COUNT) / DENOMINATOR_COUNT
score = str((1.0 * LOW_VOL_ALERT_COUNT) / DENOMINATOR_COUNT)
findings.append(FundingFixedFloatFindings.funding_fixed_float(transaction_event, "low-amount", score, CHAIN_ID))
return findings


def provide_handle_transaction(w3):
def handle_transaction(transaction_event):
return detect_fixed_float_funding(w3, transaction_event)

return handle_transaction


real_handle_transaction = provide_handle_transaction(web3)


def handle_transaction(transaction_event):
return real_handle_transaction(transaction_event)
async def handle_transaction(transaction_event: TransactionEvent, web3: AsyncWeb3.AsyncHTTPProvider):
return await detect_fixed_float_funding(web3, transaction_event)

async def main():
initialize()

await asyncio.gather(
scan_ethereum({
'rpc_url': "https://eth-mainnet.g.alchemy.com/v2",
'rpc_key_id': "e698634d-79c2-44fe-adf8-f7dac20dd33c",
'local_rpc_url': "1",
'handle_transaction': handle_transaction
}),
scan_polygon({
'rpc_url': "https://polygon-mainnet.g.alchemy.com/v2",
'rpc_key_id': "b9017deb-b785-48f8-bfb3-771f31190845",
'local_rpc_url': "137",
'handle_transaction': handle_transaction
}),
scan_arbitrum({
'rpc_url': "https://arb-mainnet.g.alchemy.com/v2",
'rpc_key_id': "c59959d5-3ab6-4fea-afc5-495f4571cf02",
'local_rpc_url': "42161",
'handle_transaction': handle_transaction
}),
run_health_check()
)

if __name__ == "__main__":
asyncio.run(main())
Loading