diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..e69de29 diff --git a/.github/workflows/black.yml b/.github/workflows/black.yml new file mode 100644 index 0000000..6904b20 --- /dev/null +++ b/.github/workflows/black.yml @@ -0,0 +1,11 @@ +name: black-action +on: [push, pull_request] +jobs: + linter_name: + name: runner / black formatter + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: rickstaa/action-black@v1 + with: + black_args: ". --check --diff" diff --git a/.github/workflows/prettier.yml b/.github/workflows/prettier.yml new file mode 100644 index 0000000..b661c9e --- /dev/null +++ b/.github/workflows/prettier.yml @@ -0,0 +1,23 @@ +name: Continuous Integration + +on: [push, pull_request] + +jobs: + prettier: + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v2 + with: + # Make sure the actual branch is checked out when running on pull requests + ref: ${{ github.head_ref }} + # This is important to fetch the changes to the previous commit + fetch-depth: 0 + + - name: Prettify code + uses: creyD/prettier_action@v4.2 + with: + # This part is also where you can pass other options, for example: + prettier_options: --write **/*.{js,md} + only_changed: True diff --git a/.gitignore b/.gitignore index 6704566..911ff40 100644 --- a/.gitignore +++ b/.gitignore @@ -1,104 +1,16 @@ -# Logs -logs -*.log -npm-debug.log* -yarn-debug.log* -yarn-error.log* -lerna-debug.log* - -# Diagnostic reports (https://nodejs.org/api/report.html) -report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json - -# Runtime data -pids -*.pid -*.seed -*.pid.lock - -# Directory for instrumented libs generated by jscoverage/JSCover -lib-cov - -# Coverage directory used by tools like istanbul -coverage -*.lcov - -# nyc test coverage -.nyc_output - -# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) -.grunt - -# Bower dependency directory (https://bower.io/) -bower_components - -# node-waf configuration -.lock-wscript - -# Compiled binary addons (https://nodejs.org/api/addons.html) -build/Release - -# Dependency directories -node_modules/ -jspm_packages/ - -# TypeScript v1 declaration files -typings/ - -# TypeScript cache -*.tsbuildinfo - -# Optional npm cache directory -.npm - -# Optional eslint cache -.eslintcache - -# Microbundle cache -.rpt2_cache/ -.rts2_cache_cjs/ -.rts2_cache_es/ -.rts2_cache_umd/ - -# Optional REPL history -.node_repl_history - -# Output of 'npm pack' -*.tgz - -# Yarn Integrity file -.yarn-integrity - -# dotenv environment variables file +*.py[co] +.tox +secrets + +.idea/ +.eggs/ +.coverage +.vagrant/ +*.egg-info +.mypy_cache/ +build/ +.DS_Store .env -.env.test - -# parcel-bundler cache (https://parceljs.org/) -.cache - -# Next.js build output -.next - -# Nuxt.js build / generate output -.nuxt -dist - -# Gatsby files -.cache/ -# Comment in the public line in if your project uses Gatsby and *not* Next.js -# https://nextjs.org/blog/next-9-1#public-directory-support -# public - -# vuepress build output -.vuepress/dist - -# Serverless directories -.serverless/ - -# FuseBox cache -.fusebox/ - -# DynamoDB Local files -.dynamodb/ - -# TernJS port file -.tern-port +.pyenv +logs/ +env.json diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..cc218de --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,36 @@ +# HOWTO: https://pre-commit.com/#usage +# pip3 install pre-commit +# pre-commit install -t pre-commit -t pre-push + +repos: + - repo: https://github.com/psf/black + rev: 22.3.0 + hooks: + - id: black + - repo: https://github.com/asottile/pyupgrade + rev: v2.34.0 + hooks: + - id: pyupgrade + - repo: https://github.com/pre-commit/mirrors-prettier + rev: v2.6.2 + hooks: + - id: prettier + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.3.0 + hooks: + - id: check-added-large-files + - id: check-ast + - id: check-builtin-literals + - id: check-docstring-first + - id: check-executables-have-shebangs + - id: check-merge-conflict + - id: check-symlinks + - id: check-yaml + - id: detect-private-key + exclude: tests/conftest.py + - id: detect-aws-credentials + args: + - --allow-missing-credentials + - id: end-of-file-fixer + - id: mixed-line-ending + - id: trailing-whitespace diff --git a/README.md b/README.md index 09702b0..d535d7b 100644 --- a/README.md +++ b/README.md @@ -1 +1,5 @@ -# sentinal \ No newline at end of file +# Sentinel + +## Description + +## Setup diff --git a/env.json.example b/env.json.example new file mode 100644 index 0000000..b15c26b --- /dev/null +++ b/env.json.example @@ -0,0 +1,38 @@ +{ + "logLevel": "info", + "maxThreads": 5, + "networkDefaults": { + "privateKey": "0x00000000000000000000000", + "processes": [ + "bounty" + ] + }, + "networks": [ + { + "name": "ArbitriumRinkeby", + "chainId": "421611", + "type": "L2", + "rpc": "https://rinkeby.arbitrum.io/rpc", + "privateKey": "0x0000000000000000000000000000000000000000000000000000000000000001", + "srcContract": "0x9869Fc26826172eB8fB334b39B8D865Be36b01C3", + "destContract": "0xcb122d5dFD3e2b16b07dd95F78AB745CaC086c00", + "processes": [ + "bounty" + ] + }, + { + "name": "Rinkeby", + "chainId": "4", + "type": "L1", + "rpc": "https://rpc.ankr.com/eth_rinkeby", + "bridgeContract": "0xe396721BF9FD7c320c3c528077428847c4940C65" + }, + { + "name": "Goerli", + "chainId": "5", + "type": "L1", + "rpc": "https://rpc.ankr.com/eth_goerli", + "bridgeContract": "0x0000000000000000000000000000000000000000" + } + ] +} diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..76ae21c --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +web3==5.30.0 diff --git a/sentinel/__init__.py b/sentinel/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/sentinel/__main__.py b/sentinel/__main__.py new file mode 100644 index 0000000..2dad67d --- /dev/null +++ b/sentinel/__main__.py @@ -0,0 +1 @@ +import sentinel.sentinel diff --git a/sentinel/abi/BuddleBridge.json b/sentinel/abi/BuddleBridge.json new file mode 100644 index 0000000..44fea24 --- /dev/null +++ b/sentinel/abi/BuddleBridge.json @@ -0,0 +1,454 @@ +[ + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "destChain", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "address[]", + "name": "tokens", + "type": "address[]" + }, + { + "indexed": false, + "internalType": "uint256[]", + "name": "amounts", + "type": "uint256[]" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "timestamp", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "bytes32", + "name": "ticketId", + "type": "bytes32" + } + ], + "name": "FundsBridged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "inputs": [], + "name": "CHAIN", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "VERSION", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_chain", + "type": "uint256" + }, + { + "internalType": "address", + "name": "_contract", + "type": "address" + } + ], + "name": "addBuddleBridge", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_l2TokenAddress", + "type": "address" + }, + { + "internalType": "address", + "name": "_l1TokenAddress", + "type": "address" + } + ], + "name": "addTokenMap", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "_root", + "type": "bytes32" + } + ], + "name": "approveRoot", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "buddle", + "outputs": [ + { + "internalType": "address", + "name": "source", + "type": "address" + }, + { + "internalType": "address", + "name": "destination", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "buddleBridge", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "_ticket", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "_chain", + "type": "uint256" + }, + { + "internalType": "address[]", + "name": "_tokens", + "type": "address[]" + }, + { + "internalType": "uint256[]", + "name": "_amounts", + "type": "uint256[]" + }, + { + "internalType": "uint256[]", + "name": "_bounty", + "type": "uint256[]" + }, + { + "internalType": "uint256", + "name": "_firstIdForTicket", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_lastIdForTicket", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "stateRoot", + "type": "bytes32" + } + ], + "name": "claimBounty", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "_version", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "_messenger", + "type": "address" + }, + { + "internalType": "address", + "name": "_stdBridge", + "type": "address" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "knownBridges", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "messenger", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_dest", + "type": "address" + } + ], + "name": "setDestination", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_src", + "type": "address" + } + ], + "name": "setSource", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "stdBridge", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "tokenMap", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address[]", + "name": "_tokens", + "type": "address[]" + }, + { + "internalType": "uint256[]", + "name": "_amounts", + "type": "uint256[]" + }, + { + "internalType": "address", + "name": "bountySeeker", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "_ticket", + "type": "bytes32" + } + ], + "name": "transferFunds", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_chain", + "type": "uint256" + }, + { + "internalType": "address", + "name": "_contract", + "type": "address" + } + ], + "name": "updateBuddleBridge", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_newBridgeAddress", + "type": "address" + } + ], + "name": "updateStandardBridge", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_l2TokenAddress", + "type": "address" + }, + { + "internalType": "address", + "name": "_l1TokenAddress", + "type": "address" + } + ], + "name": "updateTokenMap", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_newMessengerAddress", + "type": "address" + } + ], + "name": "updateXDomainMessenger", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +] diff --git a/sentinel/abi/BuddleDest.json b/sentinel/abi/BuddleDest.json new file mode 100644 index 0000000..ee8382b --- /dev/null +++ b/sentinel/abi/BuddleDest.json @@ -0,0 +1,602 @@ +[ + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "sourceChain", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "transferID", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "address", + "name": "oldOwner", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "LiquidityOwnerChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "sourceChain", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "bytes32", + "name": "stateRoot", + "type": "bytes32" + } + ], + "name": "RootApproved", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "tokenAddress", + "type": "address" + }, + { + "internalType": "address", + "name": "destination", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "fee", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "startTime", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "feeRampup", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "chain", + "type": "uint256" + } + ], + "indexed": false, + "internalType": "struct IBuddleDestination.TransferData", + "name": "transferData", + "type": "tuple" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "transferID", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "sourceChain", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "address", + "name": "liquidityProvider", + "type": "address" + } + ], + "name": "TransferCompleted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "tokenAddress", + "type": "address" + }, + { + "internalType": "address", + "name": "destination", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "fee", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "startTime", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "feeRampup", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "chain", + "type": "uint256" + } + ], + "indexed": false, + "internalType": "struct IBuddleDestination.TransferData", + "name": "transferData", + "type": "tuple" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "transferID", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "sourceChain", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "address", + "name": "claimer", + "type": "address" + } + ], + "name": "WithdrawalEvent", + "type": "event" + }, + { + "inputs": [], + "name": "VERSION", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "sourceChain", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "stateRoot", + "type": "bytes32" + } + ], + "name": "approveStateRoot", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "buddleBridge", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "tokenAddress", + "type": "address" + }, + { + "internalType": "address", + "name": "destination", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "fee", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "startTime", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "feeRampup", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "chain", + "type": "uint256" + } + ], + "internalType": "struct IBuddleDestination.TransferData", + "name": "_data", + "type": "tuple" + }, + { + "internalType": "uint256", + "name": "_transferID", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "sourceChain", + "type": "uint256" + }, + { + "internalType": "address", + "name": "_owner", + "type": "address" + } + ], + "name": "changeOwner", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "tokenAddress", + "type": "address" + }, + { + "internalType": "address", + "name": "destination", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "fee", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "startTime", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "feeRampup", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "chain", + "type": "uint256" + } + ], + "internalType": "struct IBuddleDestination.TransferData", + "name": "transferData", + "type": "tuple" + }, + { + "internalType": "uint256", + "name": "transferID", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "sourceChain", + "type": "uint256" + } + ], + "name": "deposit", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "_version", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "_buddleBridge", + "type": "address" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "liquidityOwners", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "messenger", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_messenger", + "type": "address" + } + ], + "name": "setXDomainMessenger", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "transferFee", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_newBridgeAddress", + "type": "address" + } + ], + "name": "updateBuddleBridge", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_newMessenger", + "type": "address" + } + ], + "name": "updateXDomainMessenger", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "tokenAddress", + "type": "address" + }, + { + "internalType": "address", + "name": "destination", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "fee", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "startTime", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "feeRampup", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "chain", + "type": "uint256" + } + ], + "internalType": "struct IBuddleDestination.TransferData", + "name": "transferData", + "type": "tuple" + }, + { + "internalType": "uint256", + "name": "transferID", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "sourceChain", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "_node", + "type": "bytes32" + }, + { + "internalType": "bytes32[]", + "name": "_proof", + "type": "bytes32[]" + }, + { + "internalType": "bytes32", + "name": "_root", + "type": "bytes32" + } + ], + "name": "withdraw", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +] diff --git a/sentinel/abi/BuddleSrc.json b/sentinel/abi/BuddleSrc.json new file mode 100644 index 0000000..68d050e --- /dev/null +++ b/sentinel/abi/BuddleSrc.json @@ -0,0 +1,729 @@ +[ + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "bytes32", + "name": "ticket", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "bytes32", + "name": "stateRoot", + "type": "bytes32" + } + ], + "name": "TicketConfirmed", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "bytes32", + "name": "ticket", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "destChain", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "address[]", + "name": "tokens", + "type": "address[]" + }, + { + "indexed": false, + "internalType": "uint256[]", + "name": "amounts", + "type": "uint256[]" + }, + { + "indexed": false, + "internalType": "uint256[]", + "name": "bounty", + "type": "uint256[]" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "firstIdForTicket", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "lastIdForTicket", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "bytes32", + "name": "stateRoot", + "type": "bytes32" + } + ], + "name": "TicketCreated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "tokenAddress", + "type": "address" + }, + { + "internalType": "address", + "name": "destination", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "fee", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "startTime", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "feeRampup", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "chain", + "type": "uint256" + } + ], + "indexed": false, + "internalType": "struct IBuddleSource.TransferData", + "name": "transferData", + "type": "tuple" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "transferID", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "srcChain", + "type": "uint256" + } + ], + "name": "TransferStarted", + "type": "event" + }, + { + "inputs": [], + "name": "CHAIN", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "CONTRACT_FEE_BASIS_POINTS", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "CONTRACT_FEE_RAMP_UP", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "MAX_DEPOSIT_COUNT", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "MERKLE_TREE_DEPTH", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "VERSION", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_chain", + "type": "uint256" + }, + { + "internalType": "address", + "name": "_contract", + "type": "address" + } + ], + "name": "addDestination", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_l1Token", + "type": "address" + }, + { + "internalType": "address", + "name": "_l2Token", + "type": "address" + } + ], + "name": "addL1Token", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address[]", + "name": "_tokens", + "type": "address[]" + } + ], + "name": "addTokens", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "arbSys", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "buddleBridge", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "buddleDestination", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "_ticket", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "_destChain", + "type": "uint256" + }, + { + "internalType": "address[]", + "name": "_tokens", + "type": "address[]" + }, + { + "internalType": "uint256[]", + "name": "_tokenAmounts", + "type": "uint256[]" + }, + { + "internalType": "uint256[]", + "name": "_bountyAmounts", + "type": "uint256[]" + }, + { + "internalType": "uint256", + "name": "_firstTransferInTicket", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_lastTransferInTicket", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "_stateRoot", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "_provider", + "type": "address" + } + ], + "name": "confirmTicket", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_destChain", + "type": "uint256" + } + ], + "name": "createTicket", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_tokenAddress", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_amount", + "type": "uint256" + }, + { + "internalType": "address", + "name": "_destination", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_destChain", + "type": "uint256" + } + ], + "name": "deposit", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "_version", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "_feeBasisPoints", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_feeRampUp", + "type": "uint256" + }, + { + "internalType": "address", + "name": "_buddleBridge", + "type": "address" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "l1TokenMap", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "lastConfirmedTransfer", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "router", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_arbSys", + "type": "address" + }, + { + "internalType": "address", + "name": "_gatewayRouter", + "type": "address" + } + ], + "name": "setXDomainMessenger", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "tokenMapping", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "tokens", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "transferCount", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_arbSys", + "type": "address" + } + ], + "name": "updateArbSys", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_newBridgeAddress", + "type": "address" + } + ], + "name": "updateBuddleBridge", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_newContractFeeBasisPoints", + "type": "uint256" + } + ], + "name": "updateContractFeeBasisPoints", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_newContractFeeRampUp", + "type": "uint256" + } + ], + "name": "updateContractFeeRampUp", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_chain", + "type": "uint256" + }, + { + "internalType": "address", + "name": "_contract", + "type": "address" + } + ], + "name": "updateDestination", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_gatewayRouter", + "type": "address" + } + ], + "name": "updateGatewayRouter", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_l1Token", + "type": "address" + }, + { + "internalType": "address", + "name": "_l2Token", + "type": "address" + } + ], + "name": "updateL1Token", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +] diff --git a/sentinel/bounty.py b/sentinel/bounty.py new file mode 100644 index 0000000..db0269a --- /dev/null +++ b/sentinel/bounty.py @@ -0,0 +1,3 @@ +class Bounty: + def __init__(self) -> None: + pass diff --git a/sentinel/handlers/__init__.py b/sentinel/handlers/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/sentinel/handlers/baseHandler.py b/sentinel/handlers/baseHandler.py new file mode 100644 index 0000000..17a8eac --- /dev/null +++ b/sentinel/handlers/baseHandler.py @@ -0,0 +1,11 @@ +from sentinel.services.buddleNetwork import BuddleNetwork +from sentinel.services.singletonFactory import SingletonFactory + + +class BaseHandler: + def __init__(self, singletonFactory: SingletonFactory): + self.singletonFactory = singletonFactory + + # TODO: eventData should be a generic type for all possible events + def handleEvent(self, eventData): + pass diff --git a/sentinel/handlers/bountyHandler.py b/sentinel/handlers/bountyHandler.py new file mode 100644 index 0000000..1122172 --- /dev/null +++ b/sentinel/handlers/bountyHandler.py @@ -0,0 +1,9 @@ +from sentinel.handlers.baseHandler import BaseHandler +from sentinel.services.singletonFactory import SingletonFactory +from sentinel.types.configTypes import Network + + +class BountyHandler(BaseHandler): + def __init__(self, singletonFactory: SingletonFactory, network: Network): + super().__init__(singletonFactory) + pass diff --git a/sentinel/handlers/liquidityHandler.py b/sentinel/handlers/liquidityHandler.py new file mode 100644 index 0000000..56230e1 --- /dev/null +++ b/sentinel/handlers/liquidityHandler.py @@ -0,0 +1,10 @@ +from sentinel.handlers.baseHandler import BaseHandler +from sentinel.services.singletonFactory import SingletonFactory + + +class LiquidityHandler(BaseHandler): + def __init__(self, singletonFactory: SingletonFactory): + super().__init__(singletonFactory) + + def handleEvent(self, eventData): + pass diff --git a/sentinel/liquidity.py b/sentinel/liquidity.py new file mode 100644 index 0000000..3097946 --- /dev/null +++ b/sentinel/liquidity.py @@ -0,0 +1,6 @@ +import web3 + + +class Liquidity: + def __init__(self) -> None: + pass diff --git a/sentinel/listener.py b/sentinel/listener.py new file mode 100644 index 0000000..41144c5 --- /dev/null +++ b/sentinel/listener.py @@ -0,0 +1,21 @@ +from sentinel.handlers.baseHandler import BaseHandler +from sentinel.types.configTypes import Network +from sentinel.utils.threadable import Threadable + + +class Listener(Threadable): + def __init__(self, network: Network, handler: BaseHandler): + self.name = name + self.handler = handler + + def startThread(self): + pass + + def __str__(self): + return self.name + + def __call__(self, *args, **kwargs): + return self.callback(*args, **kwargs) + + def processEvent(self): + self.handler.handleEvent([]) diff --git a/sentinel/sentinel.py b/sentinel/sentinel.py new file mode 100644 index 0000000..2569549 --- /dev/null +++ b/sentinel/sentinel.py @@ -0,0 +1,27 @@ +from sentinel.services.configManager import ConfigManager +from sentinel.services.logManager import BuddleLogger +from sentinel.services.singletonFactory import SingletonFactory + + +class Sentinel: + def __init__(self) -> None: + self.singletonFactory = SingletonFactory() + self.logger = BuddleLogger.setupLogger(None, "DEBUG") + self.singletonFactory.addService(self.logger) + self.configManager = self.singletonFactory.getService(ConfigManager) + # self.logger = logging.getLogger(__name__) + + def start(self) -> None: + self.logger.info("Staring Sentinel...") + + +# Start Sentinel +def main(): + sentinel = Sentinel() + try: + sentinel.start() + except Exception: + print("Sentinel exited abnormally") + + +main() diff --git a/sentinel/services/__init__.py b/sentinel/services/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/sentinel/services/buddleNetwork.py b/sentinel/services/buddleNetwork.py new file mode 100644 index 0000000..219a49a --- /dev/null +++ b/sentinel/services/buddleNetwork.py @@ -0,0 +1,85 @@ +import json + +from eth_account.account import LocalAccount +from eth_account.datastructures import SignedTransaction +from sentinel.services.configManager import ConfigManager +from sentinel.services.singletonFactory import SingletonFactory +from sentinel.services.transactionManager import TransactionManager +from sentinel.types.configTypes import Network +from sentinel.types.enums import BuddleContract +from sentinel.types.transferData import TransferData +from web3 import Web3 +from web3.contract import Contract +from web3.types import BlockData + + +class BuddleNetwork: + def __init__(self, singletonFactory: SingletonFactory, chainID: int) -> None: + self.chainId = chainID + self.connected = False + self.w3 = None + self.contracts: dict[str, Contract] = {} + self._account: LocalAccount = None + self.configService: ConfigManager = singletonFactory.getService(ConfigManager) + self.transactionService: TransactionManager = singletonFactory.getService( + TransactionManager + ) + self.network: Network = self.configService.config.getNetwork(self.chainId) + + def connect(self) -> bool: + if not self.w3.isConnected(): + self.rpcUrl = self.network.rpc + self.w3 = Web3(Web3.HTTPProvider(self.rpcUrl)) + self.connected = True + + return self.connected + + def getBlock(self, blockNumber) -> BlockData: + return self.w3.eth.get_block(blockNumber, full_transactions=True) + + def readAbi(self, buddleContract: BuddleContract) -> dict: + with open(f"./../abi/{buddleContract.value}.json") as f: + info_json = json.load(f) + + return info_json + + def getContract(self, buddleContract: BuddleContract) -> Contract: + contract = self.contracts.get(buddleContract.name) + if not contract: + contract = self.w3.eth.contract( + address=self.network.getContractAddress(buddleContract), + abi=self.readAbi(buddleContract), + ) + self.contracts[buddleContract.name] = contract + + return contract + + @property + def account(self) -> LocalAccount: + if not self._account: + privateKey = self.network.privateKey + self._account = self.w3.eth.account.privateKeyToAccount(privateKey) + + return self._account + + # TODO: Relocate + def depositOnDestination( + self, transferData: TransferData, transferId: int, chainId: int + ) -> SignedTransaction: + contract = self.getContract(BuddleContract.DESTINATION) + tx = contract.functions.deposit( + transferData.rawData, transferId, chainId + ).buildTransaction( + { + "from": self.account.address, + "value": transferData[2], + "nonce": self.w3.eth.get_transaction_count( + self.configService.getPrivateKey(self.chainId) + ), + } + ) + signedTx = self.w3.eth.account.sign_transaction( + tx, private_key=self.configService.getPrivateKey(self.chainId) + ) + + return signedTx diff --git a/sentinel/services/buddleSrc.py b/sentinel/services/buddleSrc.py new file mode 100644 index 0000000..1be739e --- /dev/null +++ b/sentinel/services/buddleSrc.py @@ -0,0 +1,14 @@ +from buddleNetwork import BuddleNetwork +from web3.utils.filters import LogFilter + + +class BuddleSrc(BuddleNetwork): + def __init__(self, rpcUrl) -> None: + super().__init__(rpcUrl) + self.eventFilters: dict[str, LogFilter] = {} + + def readNewEvents(self, eventName: str): + pass + + def readNewBlocks(self): + pass diff --git a/sentinel/services/configManager.py b/sentinel/services/configManager.py new file mode 100644 index 0000000..4be2482 --- /dev/null +++ b/sentinel/services/configManager.py @@ -0,0 +1,49 @@ +import json +import os + +from sentinel.services.logManager import BuddleLogger +from sentinel.services.singletonFactory import SingletonFactory +from sentinel.types.configTypes import BuddleConfig +from sentinel.types.enums import BuddleContract +from sentinel.types.exceptions import RequiredConfigMissingException + + +class ConfigManager: + """ + This is a singleton class. Use the SingletonFactory to get an instance of this class. + """ + + def __init__(self, singletonFactory: SingletonFactory) -> None: + self.logger = singletonFactory.getService(BuddleLogger) + self._config: BuddleConfig = None + self.configDict = {} + if not self.readConfigFile(): + raise RequiredConfigMissingException("Error reading config file") + self.logger.debug("ConfigManager initialized") + + def readConfigFile(self) -> bool: + configPath = os.path.join(os.path.dirname(__file__), "./../../env.json") + try: + with open(configPath) as configFile: + self.configDict = json.load(configFile) + self._config = BuddleConfig(self.configDict) + except Exception: + self.logger.exception("Error reading config file") + return False + + return True + + @property + def config(self) -> BuddleConfig: + return self._config + + def getPrivateKey(self, chainId: int) -> str: + key = os.environ.get(f"PRIVATE_KEY_{chainId}") + if not key: + key = os.environ.get(f"PRIVATE_KEY_DEFAULT") + + return key + + def getEnv(self, key: str) -> str: + print("Type: ", type(os.environ.get(key))) + return self.configDict[key] diff --git a/sentinel/services/logManager.py b/sentinel/services/logManager.py new file mode 100644 index 0000000..339b563 --- /dev/null +++ b/sentinel/services/logManager.py @@ -0,0 +1,114 @@ +import logging +import logging.handlers as handlers +import os + +from sentinel.utils.constants import Constants +from sentinel.utils.fsUtils import FSUtils + + +class BuddleLogger(logging.Logger): + def __init__(self, name, level=logging.NOTSET): + self.errors = [] + self.warnings = [] + self.allErrors = [] + + super(BuddleLogger, self).__init__(name, level) + + def debug(self, msg, *args, **kwargs): + return super(BuddleLogger, self).debug(msg, *args, **kwargs) + + def info(self, msg, *args, **kwargs): + return super(BuddleLogger, self).info(msg, *args, **kwargs) + + def warning(self, msg, *args, **kwargs): + fmtmsg = str(msg) + if args: + fmtmsg = fmtmsg % args + + self.warnings.append(fmtmsg) + self.allErrors.append("WARN: " + fmtmsg) + + return super(BuddleLogger, self).warning(msg, *args, **kwargs) + + warn = warning + + def error(self, msg, *args, **kwargs): + fmtmsg = str(msg) + if args: + fmtmsg = fmtmsg % args + + self.errors.append(fmtmsg) + self.allErrors.append("ERROR: " + fmtmsg) + + return super(BuddleLogger, self).error(msg, *args, **kwargs) + + def exception(self, msg, *args, **kwargs): + fmtmsg = str(msg) + if args: + fmtmsg = fmtmsg % args + + self.errors.append(fmtmsg) + self.allErrors.append("ERROR: " + fmtmsg) + + kwargs["exc_info"] = 1 + return super(BuddleLogger, self).error(msg, *args, **kwargs) + + def critical(self, msg, *args, **kwargs): + fmtmsg = str(msg) + if args: + fmtmsg = fmtmsg % args + + self.errors.append(fmtmsg) + self.allErrors.append("CRITICAL: " + fmtmsg) + + return super(BuddleLogger, self).critical(msg, *args, **kwargs) + + fatal = critical + + def log(self, level, msg, *args, **kwargs): + return super(BuddleLogger, self).log(level, msg, *args, **kwargs) + + def findCaller(self, stack_info=False, stacklevel=3): + """ + Find the stack frame of the caller so that we can note the source + file name, line number and function name. + We override this to get the caller 1 frame above the usual retrieved + by logging.findCaller. Without this, caller information will always + refer to the log functions implemented in this derived class. + """ + return super(BuddleLogger, self).findCaller(stack_info, 3) + + @staticmethod + def setupLogger( + logLocation, logLevel="INFO" + ): # DEBUG, INFO, WARNING, ERROR, CRITICAL + logging.setLoggerClass(BuddleLogger) + logger = logging.getLogger(__name__) + try: + logger.setLevel(logLevel.upper()) + except Exception: + logLevel = logging.INFO + + try: + if logLocation is not None: + logFileDir = logLocation + else: + logFileDir = os.path.join(FSUtils.getScriptPath(), "logs") + + logFilePath = os.path.join(logFileDir, Constants.LOG_FILE_NAME) + FSUtils.createDirectory(logFileDir) + fileLogFormat = logging.Formatter( + "%(process)-5d %(asctime)s %(levelname)-7s %(module)-15s %(funcName)-20s %(message)s", + datefmt="%Y-%m-%d %H:%M:%S", + ) + fileLogHandler = handlers.RotatingFileHandler( + logFilePath, maxBytes=2000000, backupCount=4 + ) + fileLogHandler.setLevel(logging.DEBUG) + fileLogHandler.setFormatter(fileLogFormat) + logger.addHandler(fileLogHandler) + except Exception: + logger.exception("Could not set up file logger") + + logger.debug("Logger setup done!") + return logger diff --git a/sentinel/services/singletonFactory.py b/sentinel/services/singletonFactory.py new file mode 100644 index 0000000..1182358 --- /dev/null +++ b/sentinel/services/singletonFactory.py @@ -0,0 +1,33 @@ +from sentinel.types.exceptions import SingletonFactoryException +from typing import TypeVar, Callable + +R, D = TypeVar("R"), TypeVar("D") + + +class SingletonFactory: + def __init__(self) -> None: + self.classes: dict[type, object] = {} + + def addService(self, cls: object) -> None: + self.classes[type(cls)] = cls + + def removeService(self, clsType: type) -> bool: + if self.classes.get(clsType): + del self.classes[clsType] + return True + + return False + + def getService(self, clsType: Callable[[D], R]) -> R: + cls = self.classes.get(clsType) + if not cls: + cls = clsType(self) + self.addService(cls) + + return cls + + def updateService(self, cls: object) -> bool: + if self.classes.get(type(cls)): + self.addClass(cls) + else: + raise SingletonFactoryException("Class not found in factory") diff --git a/sentinel/services/threadManager.py b/sentinel/services/threadManager.py new file mode 100644 index 0000000..394c724 --- /dev/null +++ b/sentinel/services/threadManager.py @@ -0,0 +1,87 @@ +from concurrent.futures import ThreadPoolExecutor, Future +from threading import Event, Lock +from typing import Any + +from sentinel.services.configManager import ConfigManager +from sentinel.services.logManager import BuddleLogger +from sentinel.services.singletonFactory import SingletonFactory + + +# To signal the thread and gracefully shutdown +class Signal: + def __init__(self): + self._signalled: bool = False + + @property + def signalled(self) -> bool: + return self._signalled + + def signal(self): + self.signalled = True + + +class BuddleThread: + def __init__(self): + self.id: int = id + self.threadFuture: Future = None + self.event: Event = Event() + + +class ThreadManager: + def __init__(self, singletonFactory: SingletonFactory) -> None: + self.logger = singletonFactory.getService(BuddleLogger) + self.configService = singletonFactory.getService(ConfigManager) + self.maxThreads = self.configService.config.maxThreads + self.criticalSection = Lock() + self.threadPool = ThreadPoolExecutor(self.maxThreads) + # TODO: Maintain own thread pool. Use simple thread objects instead of submitting to threadPool as + # threadPool threads cannot be forcefully shut down. + self.threads: dict[int, BuddleThread] = {} + self.maxThreadId = 1 + + def startThread(self, startFunc: function, *args, **kwargs) -> int: + threadId = -1 + + with self.criticalSection: + threadId = self.maxThreadId + self.maxThreadId += 1 + + newThread = BuddleThread() + try: + threadFuture = self.threadPool.submit( + startFunc, newThread.event, *args, **kwargs + ) + newThread.id = threadId + newThread.threadFuture = threadFuture + self.threads[threadId] = newThread + except Exception: + self.logger.exception( + "Thread could not be started for function [%s]", startFunc.__name__ + ) + raise + + return threadId + + def stopThread(self, threadId: int, timeout: int = -1) -> bool: + curThread: BuddleThread = None + with self.criticalSection: + curThread = self.threads.get(threadId, None) + + if curThread is None: + self.logger.warn("Thread ID [%d] does not exist, nothing to do", threadId) + return True + + threadFuture = curThread.threadFuture + if threadFuture.done() or threadFuture.cancelled(): + self.logger.debug( + "Thread ID [%d] is already complete or cancelled", threadId + ) + + with self.criticalSection: + threadFuture.cancel() + curThread.event.set() + + if timeout > -1: + threadFuture.result(timeout=timeout) + # TODO: Join the thread with timeout and stop it otherwise + # Log if thread did not exit gracefully diff --git a/sentinel/services/transactionManager.py b/sentinel/services/transactionManager.py new file mode 100644 index 0000000..115f0ff --- /dev/null +++ b/sentinel/services/transactionManager.py @@ -0,0 +1,30 @@ +from eth_account.datastructures import SignedTransaction +from sentinel.services.singletonFactory import SingletonFactory +from sentinel.services.web3Manager import Web3Manager + +from threadManager import ThreadManager + + +class TransactionManager(ThreadManager): + """ + This is a singleton class. Use the SingletonFactory to get an instance of this class. + """ + + def __init__(self, singletonFactory: SingletonFactory) -> None: + self.web3Service: Web3Manager = singletonFactory.getService(Web3Manager) + + def queueTransaction(self, txn: SignedTransaction, chainId: int) -> str: + # TODO: This is test code. Write code to use a separate thread for transaction processing + + txnHash = self.web3Service.getInstance(chainId).eth.send_raw_transaction( + txn.rawTransaction + ) + self.web3Service.getInstance(chainId).eth.wait_for_transaction_receipt(txnHash) + + print(txnHash) + + def processTransaction(self) -> None: + pass + + def startProcessThread(self) -> None: + pass diff --git a/sentinel/services/web3Manager.py b/sentinel/services/web3Manager.py new file mode 100644 index 0000000..82682d3 --- /dev/null +++ b/sentinel/services/web3Manager.py @@ -0,0 +1,29 @@ +from sentinel.services.buddleNetwork import BuddleNetwork +from sentinel.services.configManager import ConfigManager +from sentinel.services.singletonFactory import SingletonFactory +from web3 import Web3 + + +class Web3Manager: + def __init__(self, singletonFactory: SingletonFactory) -> None: + self.networks: dict[int, BuddleNetwork] = {} + self.web3Instances: dict[int, Web3] = {} + self.singletonFactory: SingletonFactory = singletonFactory + self.configService: ConfigManager = singletonFactory.getService(ConfigManager) + + def getNetwork(self, chainId: int) -> BuddleNetwork: + buddleNetwork = self.networks.get(chainId) + if not buddleNetwork: + buddleNetwork = BuddleNetwork(self.singletonFactory, chainId) + self.networks[chainId] = buddleNetwork + + return buddleNetwork + + def getInstance(self, chainId: int) -> Web3: + web3Instance = self.web3Instances.get(chainId) + if not web3Instance: + web3Instance = Web3( + Web3.HTTPProvider(self.configService.config.getNetwork(chainId).rpc) + ) + self.web3Instances[chainId] = web3Instance + return web3Instance diff --git a/sentinel/testing.py b/sentinel/testing.py new file mode 100644 index 0000000..9be0b9d --- /dev/null +++ b/sentinel/testing.py @@ -0,0 +1,113 @@ +from web3 import Web3 +import time +import json +import asyncio +from dotenv import load_dotenv +from web3 import __version__ + +import os +from os.path import join, dirname +from dotenv import load_dotenv + +dotenv_path = join(dirname(__file__), "./../.env") +load_dotenv(dotenv_path) + +private_key = os.environ.get("PRIVATE_KEY") +print(private_key) + +w3 = Web3( + # Web3.WebsocketProvider("wss://rinkeby.arbitrum.io/feed") + Web3.HTTPProvider("https://rinkeby.arbitrum.io/rpc") +) + +w3_l1 = Web3(Web3.HTTPProvider("https://rpc.ankr.com/eth_rinkeby")) + +# w3 = Web3(EthereumTesterProvider()) +print(w3.isConnected()) +print(__version__) + +sourceAbi = open("abi/BuddleSrc.json", "r") + +with open("abi/BuddleSrc.json") as f: + info_json = json.load(f) +abi = info_json + +with open("abi/BuddleDest.json") as f: + dest_json = json.load(f) +abiDest = dest_json + +with open("abi/BuddleBridge.json") as f: + bridge_json = json.load(f) +abiBridge = bridge_json + +# arbi source - 0x9869Fc26826172eB8fB334b39B8D865Be36b01C3 +# arbi destination - 0xcb122d5dFD3e2b16b07dd95F78AB745CaC086c00 +# arbi Bridge rinkeby - 0xe396721BF9FD7c320c3c528077428847c4940C65 + +contractAddress = "0x9869Fc26826172eB8fB334b39B8D865Be36b01C3" +contract = w3.eth.contract(address=contractAddress, abi=abi) +accounts = w3.eth.accounts + +me = w3.eth.account.privateKeyToAccount(private_key) + +w3.eth.default_account = me + +destination_contract_address = "0xcb122d5dFD3e2b16b07dd95F78AB745CaC086c00" +contractDestination = w3.eth.contract(address=destination_contract_address, abi=abiDest) + + +def handle_event(event): + receipt = w3.eth.waitForTransactionReceipt(event["transactionHash"]) + result = contract.events.TransferStarted().processReceipt(receipt) + # print("Result[0]['args']"result[0]['args'], end="\n\n") + # print("Result:", result, end="\n\n") + transferData = result[0]["args"]["transferData"] + transferID = result[0]["args"]["transferID"] + print("transferData:", transferData, end="\n\n") + print(type(transferData[2])) + + depositTxn = contractDestination.functions.deposit( + transferData, transferID, 421611 + ).buildTransaction( + { + "from": me.address, + "value": transferData[2], + "nonce": w3.eth.getTransactionCount(me.address), + } + ) + + signed = w3.eth.account.signTransaction(depositTxn, private_key) + test = w3.eth.sendRawTransaction(signed.rawTransaction) + w3.eth.wait_for_transaction_receipt(test) + + print(test) + + +def log_loop(event_filter, poll_interval): + while True: + print("Waiting for events...") + for event in event_filter.get_all_entries(): # get_new_entries(): + print("Handling event...") + handle_event(event) + break + + time.sleep(poll_interval) + + +new_filter = contract.events.TransferStarted.createFilter(fromBlock="0x0") +print(log_loop(new_filter, 10)) + +# transferData +# address tokenAddress; +# address destination; +# uint256 amount; +# uint256 fee; +# uint256 startTime; +# uint256 feeRampup; +# uint256 chain; + +# TrabnsferID +# SourceChainID + +# block_filter = w3.eth.filter({'fromBlock':'latest', 'address':contractAddress}) +# log_loop(block_filter, 2) diff --git a/sentinel/types/__init__.py b/sentinel/types/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/sentinel/types/configTypes.py b/sentinel/types/configTypes.py new file mode 100644 index 0000000..5b7086f --- /dev/null +++ b/sentinel/types/configTypes.py @@ -0,0 +1,49 @@ +from sentinel.types.enums import BuddleContract + + +class Defaults: + def __init__(self, defaults): + if defaults is None: + return + + self.privateKey: str = defaults.get("privateKey", "") + self.processes: list[str] = defaults.get("processes", []) + + +class Network: + def __init__(self, network: dict, defaults: Defaults): + self.name: str = network.get("name") + self.chainId: str = network.get("chainId") + self.type: str = network.get("type") + self.rpc: str = network.get("rpc") + self.privateKey: str = network.get("privateKey", defaults.privateKey) + self.srcContract: str = network.get("srcContract") + self.destContract: str = network.get("destContract") + self.bridgeContract: str = network.get("bridgeContract") + self.processes: list[str] = network.get("processes", defaults.processes) + + def getContractAddress(self, buddleContract: BuddleContract): + if buddleContract == BuddleContract.SOURCE: + return self.srcContract + elif buddleContract == BuddleContract.DESTINATION: + return self.destContract + elif buddleContract == BuddleContract.BRIDGE: + return self.bridgeContract + + return None + + +class BuddleConfig: + def __init__(self, config: dict): + self.defaults = Defaults(config.get("networkDefaults")) + self.networks: list[Network] = [ + Network(network, self.defaults) for network in config.get("networks") + ] + self.networksDict: dict[int, Network] = { + network.chainId: network for network in self.networks + } + self.logLevel: str = config.get("logLevel", "info") + self.maxThreads: int = config.get("maxThreads", 10) + + def getNetwork(self, chainId: int) -> Network: + return self.networksDict.get(chainId) diff --git a/sentinel/types/constants.py b/sentinel/types/constants.py new file mode 100644 index 0000000..b89d7f4 --- /dev/null +++ b/sentinel/types/constants.py @@ -0,0 +1,2 @@ +class Constants: + LOG_FILE_NAME = "sentiel.log" diff --git a/sentinel/types/enums.py b/sentinel/types/enums.py new file mode 100644 index 0000000..aef409a --- /dev/null +++ b/sentinel/types/enums.py @@ -0,0 +1,7 @@ +from enum import Enum + + +class BuddleContract(Enum): + SOURCE = "BuddleSrc" + DESTINATION = "BuddleDest" + BRIDGE = "BuddleBridge" diff --git a/sentinel/types/exceptions.py b/sentinel/types/exceptions.py new file mode 100644 index 0000000..ebe04c6 --- /dev/null +++ b/sentinel/types/exceptions.py @@ -0,0 +1,8 @@ +class SingletonFactoryException(Exception): + def __init__(self, *args: object): + super().__init__(*args) + + +class RequiredConfigMissingException(Exception): + def __init__(self, *args: object): + super().__init__(*args) diff --git a/sentinel/types/transaction.py b/sentinel/types/transaction.py new file mode 100644 index 0000000..a2827f2 --- /dev/null +++ b/sentinel/types/transaction.py @@ -0,0 +1,3 @@ +class Transaction: + def __init__(self) -> None: + pass diff --git a/sentinel/types/transferData.py b/sentinel/types/transferData.py new file mode 100644 index 0000000..ca1764b --- /dev/null +++ b/sentinel/types/transferData.py @@ -0,0 +1,48 @@ +class TransferData: + def __init__(self, rawData: tuple) -> None: + self._rawData = rawData + self._tokenAddress: str = rawData[0] + self._destination: str = rawData[1] + self._amount: int = rawData[2] + self._fee: int = rawData[3] + self._startTime: int = rawData[4] + self._feeRampup: int = rawData[5] + self._chain: int = rawData[6] + + @property + def tokenAddress(self): + return self._tokenAddress + + @property + def destinationAddress(self): + return self._destination + + @property + def amount(self): + return self._amount + + @property + def fee(self): + return self._fee + + @property + def startTime(self): + return self._startTime + + @property + def feeRampup(self): + return self._feeRampup + + @property + def chain(self): + return self._chain + + @property + def rawData(self): + return self._rawData + + def __str__(self) -> str: + return str(self._rawData) + + def __repr__(self) -> str: + return self.__str__() diff --git a/sentinel/utils/__init__.py b/sentinel/utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/sentinel/utils/constants.py b/sentinel/utils/constants.py new file mode 100644 index 0000000..5de9265 --- /dev/null +++ b/sentinel/utils/constants.py @@ -0,0 +1,2 @@ +class Constants: + LOG_FILE_NAME = "sentinel.log" diff --git a/sentinel/utils/fsUtils.py b/sentinel/utils/fsUtils.py new file mode 100644 index 0000000..00ec099 --- /dev/null +++ b/sentinel/utils/fsUtils.py @@ -0,0 +1,40 @@ +import os +import logging + +logger = logging.getLogger(__name__) + + +class FSUtils: + @staticmethod + def getScriptPath(): + scriptPath = "" + try: + scriptPath = os.path.dirname(os.path.realpath(__file__)) + scriptPath = os.path.dirname(scriptPath) + except OSError: + logger.exception( + "Could not retrieve script path, using current working directory" + ) + try: + scriptPath = os.getcwd() + except OSError: + logger.exception( + "Could not retrieve current working directory either :(" + ) + + return scriptPath + + @staticmethod + def createDirectory(path): + try: + path = os.path.abspath(path) + if os.path.exists(path): + return True + else: + logger.debug("Creating directory [%s]", path) + + os.makedirs(path) + except Exception: + logger.exception("Could not create directory [%s]", path) + return False + return True diff --git a/sentinel/utils/threadable.py b/sentinel/utils/threadable.py new file mode 100644 index 0000000..6e484b4 --- /dev/null +++ b/sentinel/utils/threadable.py @@ -0,0 +1,50 @@ +# from multiprocessing import Event +# from concurrent.futures import Future + +# # https://docs.python.org/3/library/concurrent.futures.html + +# ''' +# class ThreadManager: +# self.threadSignals = {} + +# @threaded +# def listenEvent(self, signal, *args): +# while not signal.stop: +# self.doStuff(*args) + +# print("thread signalled") +# self.cleanup() +# print("thread exited gracefully") +# ''' + +# class Threadable: +# def __init__(self): +# self.threadStopped = False +# self.threadEvent = Event() + +# def startThread(self): +# thread = Thread(target=self.loop) +# thread.daemon = True +# thread.start() +# return thread + +# def stopThread(self): +# self.threadEvent.set() +# self.threadStopped = True + +# def loop(self): +# while not self.threadStopped: + +# self.threadEvent.wait(1) # Wait 1 seconds, returns bool of event set +# self.threadEvent.clear() # Reset event + +# self.processEvent() +# else: +# self.processStop() + +# def processEvent(self): +# """ This function must be overwritten """ +# pass + +# def processStop(self): +# pass diff --git a/tests/testWatcher.py b/tests/testWatcher.py new file mode 100644 index 0000000..e69de29