diff --git a/scam-detector-py/README.md b/scam-detector-py/README.md index f42198ad..9ed5d295 100644 --- a/scam-detector-py/README.md +++ b/scam-detector-py/README.md @@ -51,8 +51,6 @@ The Scam Detector is opinionated, and consumes evidence of and issues judgment a - **Scammer Deployed Contracts**: When an EOA is labeled a scammer, the Scam Detector queries for all contracts created by the EOA at the time the EOA was labeled. This includes direct contract creations (e.g. scammer deploys a token) as well as indirect contract creations (e.g. scammer adds liquidity to a pool that may result in the pool creation). To capture future contract creations, the Scam Detector also monitors for new contract creations by known scammers and emits a new ‘scammer’ label. - **Scammer Association**: When an EOA is labeled a scammer, label propagation will label associated scammers (e.g. addresses that are a destination for stolen assets) as scammers. [A graph-based approach](https://forta.org/blog/discovering-scammer-networks-with-machine-learning/) is utilized. The threat category of these labels is ‘scammer-association’. - - - **Similar to Scammer**: For all contracts labeled scam by the Scam Detector, a contract similarity bot will identify and emit labels about contracts that resemble known scammer contacts. The threat category of these labels is ‘similar-contract’. **False Positive Mitigation** @@ -161,11 +159,6 @@ The complete list of scammer label threat-categories, and conditions under which Passthrough Label At times, the specific threat category can not be identified, but there is confidence in the address being associated with a scam. In those cases, the threat category is set to unknown. - - similar-contract - Propagation Label - Emitted to identify a newly deployed contract that is similar to a known scammer contract - scammer-deployed-contract Propagation Label @@ -241,11 +234,11 @@ For reference, each field is described below: base_bot_alert_ids - When the label is emitted via passthrough, ML, similar contract, or an association alert, this field will contain the alert ids of the base bot alerts utilized to derive the label. + When the label is emitted via passthrough, ML, or an association alert, this field will contain the alert ids of the base bot alerts utilized to derive the label. base_bot_alert_hashes - When the label is emitted via passthrough, ML, similar contract, or an association alert, this field will contain the alert hashes of the base bot alerts utilized to derive the label. + When the label is emitted via passthrough, ML, or an association alert, this field will contain the alert hashes of the base bot alerts utilized to derive the label. deployer_info @@ -327,7 +320,7 @@ Address poisoners are the initiator of the address poisoning activity. A simple Native ice phishing are straight transfers of native assets to the scammer. Matching the to address of the transaction against Forta threat intelligence yield transactions for this type of scam. -### soft-rug-pull, hard-rug-pull, rake-token, impersonating-token, similar-contract, scammer-deployed-contract +### soft-rug-pull, hard-rug-pull, rake-token, impersonating-token, scammer-deployed-contract These threat categories all point to contracts that a user should not be interacting with. A check of the to address or the transaction trace data against Forta threat intelligence yields transactions where this may be the case. diff --git a/scam-detector-py/base_bots_additions.md b/scam-detector-py/base_bots_additions.md index 7bd3a299..78efe258 100644 --- a/scam-detector-py/base_bots_additions.md +++ b/scam-detector-py/base_bots_additions.md @@ -19,13 +19,22 @@ Once added, the new bot/alert id should be handled by the Scam Detector. It is h ## Contract Similarity Bot -Currently, we only have one contract similarity bot configured. A new similarity bot could be configured by addition to the `CONTRACT_SIMILARITY_BOTS` with the `CONTRACT_SIMILARITY_BOT_THRESHOLDS` to be utilized. A contract similarity bot would need to emit the following fields in the metadata: - - new_scammer_contract_address - - new_scammer_eoa - - scammer_contract_address - - scammer_eoa - - similarity_hash - - similarity_score +Currently, we do not have a contract similarity bot configured. A new similarity bot could be configured by the addition of: +- `CONTRACT_SIMILARITY_BOTS` +- `CONTRACT_SIMILARITY_BOT_THRESHOLDS` to be utilized +- new entries to `BASE_BOTS` and `CONFIDENCE_MAPPINGS` +- `ALERTED_ENTITIES_SIMILAR_CONTRACT_KEY` and `ALERTED_ENTITIES_SIMILAR_CONTRACT_QUEUE_SIZE` for persistence + +in `constants.py`. A contract similarity bot would need to emit the following fields in the metadata (and the necessary logic can be added to `findings.py`): + +- new_scammer_contract_address +- new_scammer_eoa +- scammer_contract_address +- scammer_eoa +- similarity_hash +- similarity_score + +The Scam Detector's logic itself would then need to be updated for the newly added items listed above. A previous implementation of such logic can be found at this commit, [420ce3cced8ee7acb7e1ddb23ccf4e27019da8dc](https://github.com/forta-network/starter-kits/tree/420ce3cced8ee7acb7e1ddb23ccf4e27019da8dc/scam-detector-py), to more easily reintroduce. In addition, the deployer of the new contract needs to be extracted. This should be configured in the `basebot_parsing_config.csv`. diff --git a/scam-detector-py/package-lock.json b/scam-detector-py/package-lock.json index 6267b3a1..efe6acc8 100644 --- a/scam-detector-py/package-lock.json +++ b/scam-detector-py/package-lock.json @@ -687,25 +687,25 @@ } }, "node_modules/@grpc/grpc-js": { - "version": "1.9.12", - "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.9.12.tgz", - "integrity": "sha512-Um5MBuge32TS3lAKX02PGCnFM4xPT996yLgZNb5H03pn6NyJ4Iwn5YcPq6Jj9yxGRk7WOgaZFtVRH5iTdYBeUg==", + "version": "1.10.9", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.10.9.tgz", + "integrity": "sha512-5tcgUctCG0qoNyfChZifz2tJqbRbXVO9J7X6duFcOjY3HUNCxg5D0ZCK7EP9vIcZ0zRpLU9bWkyCqVCLZ46IbQ==", "dependencies": { - "@grpc/proto-loader": "^0.7.8", - "@types/node": ">=12.12.47" + "@grpc/proto-loader": "^0.7.13", + "@js-sdsl/ordered-map": "^4.4.2" }, "engines": { - "node": "^8.13.0 || >=10.10.0" + "node": ">=12.10.0" } }, "node_modules/@grpc/grpc-js/node_modules/@grpc/proto-loader": { - "version": "0.7.10", - "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.10.tgz", - "integrity": "sha512-CAqDfoaQ8ykFd9zqBDn4k6iWT9loLAlc2ETmDFS9JCD70gDcnA4L3AFEo2iV7KyAtAAHFW9ftq1Fz+Vsgq80RQ==", + "version": "0.7.13", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.13.tgz", + "integrity": "sha512-AiXO/bfe9bmxBjxxtYxFAXGZvMaN5s8kO+jBHAJCON8rJoB5YS/D6X7ZNc6XQkuHNmyl4CYaMI1fJ/Gn27RGGw==", "dependencies": { "lodash.camelcase": "^4.3.0", "long": "^5.0.0", - "protobufjs": "^7.2.4", + "protobufjs": "^7.2.5", "yargs": "^17.7.2" }, "bin": { @@ -721,9 +721,9 @@ "integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==" }, "node_modules/@grpc/grpc-js/node_modules/protobufjs": { - "version": "7.2.5", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.2.5.tgz", - "integrity": "sha512-gGXRSXvxQ7UiPgfw8gevrfRWcTlSbOFg+p/N+JVJEK5VhueL2miT6qTymqAmjr1Q5WbOCyJbyrk6JfWKwlFn6A==", + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.3.2.tgz", + "integrity": "sha512-RXyHaACeqXeqAKGLDl68rQKbmObRsTIn4TYVUUug1KfS47YWCo5MacGITEryugIgZqORCvJWEk4l449POg5Txg==", "hasInstallScript": true, "dependencies": { "@protobufjs/aspromise": "^1.1.2", @@ -796,6 +796,15 @@ "node": ">=10" } }, + "node_modules/@js-sdsl/ordered-map": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/@js-sdsl/ordered-map/-/ordered-map-4.4.2.tgz", + "integrity": "sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/js-sdsl" + } + }, "node_modules/@protobufjs/aspromise": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", @@ -956,11 +965,13 @@ } }, "node_modules/axios": { - "version": "0.21.4", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz", - "integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==", + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.2.tgz", + "integrity": "sha512-2A8QhOMrbomlDuiLeK9XibIBzuHeRcqqNOHp0Cyp5EoJ1IFDh+XZH3A6BkXtv0K4gFGCI0Y4BM7B1wOEi0Rmgw==", "dependencies": { - "follow-redirects": "^1.14.0" + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" } }, "node_modules/balanced-match": { @@ -1024,12 +1035,12 @@ } }, "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, "dependencies": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" }, "engines": { "node": ">=8" @@ -1423,9 +1434,9 @@ "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==" }, "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, "dependencies": { "to-regex-range": "^5.0.1" @@ -1453,9 +1464,9 @@ "integrity": "sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==" }, "node_modules/follow-redirects": { - "version": "1.15.3", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.3.tgz", - "integrity": "sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q==", + "version": "1.15.6", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", + "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", "funding": [ { "type": "individual", @@ -1485,16 +1496,16 @@ } }, "node_modules/forta-agent": { - "version": "0.1.45", - "resolved": "https://registry.npmjs.org/forta-agent/-/forta-agent-0.1.45.tgz", - "integrity": "sha512-QP+qsWPmA1kvyHVFSpGSnXxTODOpeoxfGil0HmcpO6dOuH+f6FY54oPyjrJ2Eg+kiB1skO9a1SIdW16N1UmELg==", + "version": "0.1.48", + "resolved": "https://registry.npmjs.org/forta-agent/-/forta-agent-0.1.48.tgz", + "integrity": "sha512-fk3mar7/Avqg/4OHFmgv01ww/azr1XM+g5KcSnwvNxZy3KDMi7aFp1jAjPCsBjs8ZyVcR03ITUlbtFpRVgZB4Q==", "dependencies": { "@grpc/grpc-js": "^1.3.6", "@grpc/proto-loader": "^0.6.4", "@types/uuid": "^8.3.4", "async-retry": "^1.3.3", "awilix": "^4.3.4", - "axios": "^0.21.1", + "axios": "^1.6.2", "base64-arraybuffer": "^1.0.2", "ethers": "^5.5.1", "flat-cache": "^3.0.4", @@ -2158,6 +2169,11 @@ "pbts": "bin/pbts" } }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, "node_modules/pstree.remy": { "version": "1.1.8", "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", @@ -2995,22 +3011,22 @@ } }, "@grpc/grpc-js": { - "version": "1.9.12", - "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.9.12.tgz", - "integrity": "sha512-Um5MBuge32TS3lAKX02PGCnFM4xPT996yLgZNb5H03pn6NyJ4Iwn5YcPq6Jj9yxGRk7WOgaZFtVRH5iTdYBeUg==", + "version": "1.10.9", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.10.9.tgz", + "integrity": "sha512-5tcgUctCG0qoNyfChZifz2tJqbRbXVO9J7X6duFcOjY3HUNCxg5D0ZCK7EP9vIcZ0zRpLU9bWkyCqVCLZ46IbQ==", "requires": { - "@grpc/proto-loader": "^0.7.8", - "@types/node": ">=12.12.47" + "@grpc/proto-loader": "^0.7.13", + "@js-sdsl/ordered-map": "^4.4.2" }, "dependencies": { "@grpc/proto-loader": { - "version": "0.7.10", - "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.10.tgz", - "integrity": "sha512-CAqDfoaQ8ykFd9zqBDn4k6iWT9loLAlc2ETmDFS9JCD70gDcnA4L3AFEo2iV7KyAtAAHFW9ftq1Fz+Vsgq80RQ==", + "version": "0.7.13", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.13.tgz", + "integrity": "sha512-AiXO/bfe9bmxBjxxtYxFAXGZvMaN5s8kO+jBHAJCON8rJoB5YS/D6X7ZNc6XQkuHNmyl4CYaMI1fJ/Gn27RGGw==", "requires": { "lodash.camelcase": "^4.3.0", "long": "^5.0.0", - "protobufjs": "^7.2.4", + "protobufjs": "^7.2.5", "yargs": "^17.7.2" } }, @@ -3020,9 +3036,9 @@ "integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==" }, "protobufjs": { - "version": "7.2.5", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.2.5.tgz", - "integrity": "sha512-gGXRSXvxQ7UiPgfw8gevrfRWcTlSbOFg+p/N+JVJEK5VhueL2miT6qTymqAmjr1Q5WbOCyJbyrk6JfWKwlFn6A==", + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.3.2.tgz", + "integrity": "sha512-RXyHaACeqXeqAKGLDl68rQKbmObRsTIn4TYVUUug1KfS47YWCo5MacGITEryugIgZqORCvJWEk4l449POg5Txg==", "requires": { "@protobufjs/aspromise": "^1.1.2", "@protobufjs/base64": "^1.1.2", @@ -3083,6 +3099,11 @@ } } }, + "@js-sdsl/ordered-map": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/@js-sdsl/ordered-map/-/ordered-map-4.4.2.tgz", + "integrity": "sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw==" + }, "@protobufjs/aspromise": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", @@ -3230,11 +3251,13 @@ } }, "axios": { - "version": "0.21.4", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz", - "integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==", + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.2.tgz", + "integrity": "sha512-2A8QhOMrbomlDuiLeK9XibIBzuHeRcqqNOHp0Cyp5EoJ1IFDh+XZH3A6BkXtv0K4gFGCI0Y4BM7B1wOEi0Rmgw==", "requires": { - "follow-redirects": "^1.14.0" + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" } }, "balanced-match": { @@ -3278,12 +3301,12 @@ } }, "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, "requires": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" } }, "brorand": { @@ -3624,9 +3647,9 @@ "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==" }, "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, "requires": { "to-regex-range": "^5.0.1" @@ -3648,9 +3671,9 @@ "integrity": "sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==" }, "follow-redirects": { - "version": "1.15.3", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.3.tgz", - "integrity": "sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q==" + "version": "1.15.6", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", + "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==" }, "form-data": { "version": "4.0.0", @@ -3663,16 +3686,16 @@ } }, "forta-agent": { - "version": "0.1.45", - "resolved": "https://registry.npmjs.org/forta-agent/-/forta-agent-0.1.45.tgz", - "integrity": "sha512-QP+qsWPmA1kvyHVFSpGSnXxTODOpeoxfGil0HmcpO6dOuH+f6FY54oPyjrJ2Eg+kiB1skO9a1SIdW16N1UmELg==", + "version": "0.1.48", + "resolved": "https://registry.npmjs.org/forta-agent/-/forta-agent-0.1.48.tgz", + "integrity": "sha512-fk3mar7/Avqg/4OHFmgv01ww/azr1XM+g5KcSnwvNxZy3KDMi7aFp1jAjPCsBjs8ZyVcR03ITUlbtFpRVgZB4Q==", "requires": { "@grpc/grpc-js": "^1.3.6", "@grpc/proto-loader": "^0.6.4", "@types/uuid": "^8.3.4", "async-retry": "^1.3.3", "awilix": "^4.3.4", - "axios": "^0.21.1", + "axios": "^1.6.2", "base64-arraybuffer": "^1.0.2", "ethers": "^5.5.1", "flat-cache": "^3.0.4", @@ -4186,6 +4209,11 @@ "long": "^4.0.0" } }, + "proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, "pstree.remy": { "version": "1.1.8", "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", diff --git a/scam-detector-py/package.json b/scam-detector-py/package.json index cfdd035c..c4ff125e 100644 --- a/scam-detector-py/package.json +++ b/scam-detector-py/package.json @@ -1,7 +1,7 @@ { "name": "scam-detector-feed", "displayName": "Scam Detector Feed", - "version": "2.24.5", + "version": "2.24.6", "repository": "https://github.com/forta-network/starter-kits/tree/main/scam-detector-py", "description": "Provides real-time intelligence on scammers engaged in over 10 unique scam types.", "longDescription": "The Scam Detector data feed provides real-time intelligence about EOAs, contracts and URLs involved in a variety of Web3 scams. It is jointly maintained by the Forta Foundation, Nethermind, Blocksec, ChainPatrol and members of the Forta developer community. It features market leading scam type coverage on ice phishing, address poisoning, rake tokens, token impersonation, fraudulent NFT orders, pig butchering, gas minting, sleep minting, hard rug pulls, soft rug pulls, and wash trading. Used by Web3 wallets, exchanges, crypto compliance companies and other Web3 security teams and tools. Teams can use Scam Detector labels to warn end-users during the pre-signing transaction approval process, to identify and prevent money laundering through regulated platforms, and to supplement existing blacklists among other use cases. Learn more in the documentation below, and request a free trial today.", diff --git a/scam-detector-py/release.md b/scam-detector-py/release.md index f12e6ecf..5cb8720a 100644 --- a/scam-detector-py/release.md +++ b/scam-detector-py/release.md @@ -1,6 +1,10 @@ # Scam Detector Bot Release Notes -## 2.24.4 (prod - 4/3/2024) +## 2.24.6 (beta - 6/26/2024) + +- removed Contract Similarity base bot and corresponding logic + +## 2.24.5 (prod - 4/3/2024) - removed ADDRESS-POISONING-FAKE-TOKEN alert diff --git a/scam-detector-py/src/agent.py b/scam-detector-py/src/agent.py index 80a7f3c5..a2bf3430 100644 --- a/scam-detector-py/src/agent.py +++ b/scam-detector-py/src/agent.py @@ -22,9 +22,9 @@ from forta_agent import Finding, FindingType, FindingSeverity, get_alerts, get_labels from web3 import Web3 -from src.constants import (BASE_BOTS, ALERTED_ENTITIES_ML_KEY, ALERTED_ENTITIES_ML_QUEUE_SIZE, ALERTED_ENTITIES_PASSTHROUGH_KEY, ALERTED_ENTITIES_PASSTHROUGH_QUEUE_SIZE, ALERTED_ENTITIES_SCAMMER_ASSOCIATION_KEY, ALERTED_ENTITIES_SCAMMER_ASSOCIATION_QUEUE_SIZE, ALERTED_ENTITIES_SIMILAR_CONTRACT_KEY, ALERTED_ENTITIES_SIMILAR_CONTRACT_QUEUE_SIZE, ALERTED_ENTITIES_MANUAL_KEY, ALERTED_ENTITIES_MANUAL_QUEUE_SIZE, ALERTED_ENTITIES_MANUAL_METAMASK_KEY, ALERTED_ENTITIES_MANUAL_METAMASK_QUEUE_SIZE, ALERT_LOOKBACK_WINDOW_IN_DAYS, ENTITY_CLUSTER_BOTS, +from src.constants import (BASE_BOTS, ALERTED_ENTITIES_ML_KEY, ALERTED_ENTITIES_ML_QUEUE_SIZE, ALERTED_ENTITIES_PASSTHROUGH_KEY, ALERTED_ENTITIES_PASSTHROUGH_QUEUE_SIZE, ALERTED_ENTITIES_SCAMMER_ASSOCIATION_KEY, ALERTED_ENTITIES_SCAMMER_ASSOCIATION_QUEUE_SIZE, ALERTED_ENTITIES_MANUAL_KEY, ALERTED_ENTITIES_MANUAL_QUEUE_SIZE, ALERTED_ENTITIES_MANUAL_METAMASK_KEY, ALERTED_ENTITIES_MANUAL_METAMASK_QUEUE_SIZE, ALERT_LOOKBACK_WINDOW_IN_DAYS, ENTITY_CLUSTER_BOTS, FINDINGS_CACHE_ALERT_KEY, FINDINGS_CACHE_BLOCK_KEY, ALERTED_FP_CLUSTERS_KEY, FINDINGS_CACHE_TRANSACTION_KEY, - ALERTED_FP_CLUSTERS_QUEUE_SIZE, SCAM_DETECTOR_BOT_ID, SCAM_DETECTOR_BETA_BOT_ID, SCAM_DETECTOR_BETA_ALT_BOT_ID, CONTRACT_SIMILARITY_BOTS, CONTRACT_SIMILARITY_BOT_THRESHOLDS, EOA_ASSOCIATION_BOTS, + ALERTED_FP_CLUSTERS_QUEUE_SIZE, SCAM_DETECTOR_BOT_ID, SCAM_DETECTOR_BETA_BOT_ID, SCAM_DETECTOR_BETA_ALT_BOT_ID, EOA_ASSOCIATION_BOTS, EOA_ASSOCIATION_BOT_THRESHOLDS, PAIRCREATED_EVENT_ABI, SWAP_FACTORY_ADDRESSES, POOLCREATED_EVENT_ABI, ENCRYPTED_BOTS, MODEL_ALERT_THRESHOLD_LOOSE, MODEL_ALERT_THRESHOLD_STRICT, MODEL_FEATURES, MODEL_NAME, DEBUG_ALERT_ENABLED, ENABLE_METAMASK_CONSUMPTION) from src.storage import s3_client, dynamo_table, get_secrets, bucket_name @@ -50,7 +50,6 @@ ALERTED_ENTITIES_ML = OrderedDict() # cluster -> alert_id ALERTED_ENTITIES_PASSTHROUGH = OrderedDict() # cluster -> alert_id ALERTED_ENTITIES_SCAMMER_ASSOCIATION = OrderedDict() # cluster -> alert_id -ALERTED_ENTITIES_SIMILAR_CONTRACT = OrderedDict() # cluster -> alert_id ALERTED_ENTITIES_MANUAL = OrderedDict() # cluster -> alert_id ALERTED_ENTITIES_MANUAL_METAMASK = OrderedDict() # cluster -> alert_id ALERTED_ENTITIES_MANUAL_METAMASK_LIST = [] # Used to reduce size of persisted item @@ -60,7 +59,6 @@ FINDINGS_CACHE_TRANSACTION = [] REACTIVE_LIKELY_FPS = {} # address -> list of label metadata (addresses that are yet to be checked) SCAMMER_ASSOCIATION_LABELS = None -SIMILAR_CONTRACT_LABELS = None DF_CONTRACT_SIGNATURES = None MODEL = None @@ -112,10 +110,6 @@ def initialize(test = False): alerted_entities_scammer_association = load(CHAIN_ID, ALERTED_ENTITIES_SCAMMER_ASSOCIATION_KEY) ALERTED_ENTITIES_SCAMMER_ASSOCIATION = OrderedDict() if alerted_entities_scammer_association is None else OrderedDict(alerted_entities_scammer_association) - global ALERTED_ENTITIES_SIMILAR_CONTRACT - alerted_entities_similar_contract = load(CHAIN_ID, ALERTED_ENTITIES_SIMILAR_CONTRACT_KEY) - ALERTED_ENTITIES_SIMILAR_CONTRACT = OrderedDict() if alerted_entities_similar_contract is None else OrderedDict(alerted_entities_similar_contract) - global ALERTED_ENTITIES_MANUAL alerted_entities_manual = load(CHAIN_ID, ALERTED_ENTITIES_MANUAL_KEY) ALERTED_ENTITIES_MANUAL = OrderedDict() if alerted_entities_manual is None else OrderedDict(alerted_entities_manual) @@ -450,7 +444,7 @@ def get_model_score(df_feature_vector: pd.DataFrame) -> float: def already_alerted(entity: str, alert_id: str, logic = ""): - global ALERTED_ENTITIES_ML, ALERTED_ENTITIES_PASSTHROUGH, ALERTED_ENTITIES_SCAMMER_ASSOCIATION, ALERTED_ENTITIES_SIMILAR_CONTRACT, ALERTED_ENTITIES_MANUAL, ALERTED_ENTITIES_MANUAL_METAMASK + global ALERTED_ENTITIES_ML, ALERTED_ENTITIES_PASSTHROUGH, ALERTED_ENTITIES_SCAMMER_ASSOCIATION, ALERTED_ENTITIES_MANUAL, ALERTED_ENTITIES_MANUAL_METAMASK if logic == "ml": alerted_entities = ALERTED_ENTITIES_ML @@ -458,8 +452,6 @@ def already_alerted(entity: str, alert_id: str, logic = ""): alerted_entities = ALERTED_ENTITIES_PASSTHROUGH elif logic == "scammer_association": alerted_entities = ALERTED_ENTITIES_SCAMMER_ASSOCIATION - elif logic == "similar_contract": - alerted_entities = ALERTED_ENTITIES_SIMILAR_CONTRACT elif logic == "manual": alerted_entities = ALERTED_ENTITIES_MANUAL elif logic == "manual_metamask": @@ -630,43 +622,6 @@ def emit_passthrough_finding(w3, alert_event: forta_agent.alert_event.AlertEvent logging.info(f"{BOT_VERSION}: alert {alert_event.alert_hash} {alert_event.bot_id} {alert_event.alert.alert_id} - return total findings: {len(findings)}") return findings -def emit_contract_similarity_finding(w3, alert_event: forta_agent.alert_event.AlertEvent) -> list: - global ALERTED_ENTITIES_SIMILAR_CONTRACT - global ALERTED_ENTITIES_SIMILAR_CONTRACT_QUEUE_SIZE - global CONTRACT_SIMILARITY_BOT_THRESHOLDS - global CHAIN_ID - - findings = [] - scammer_addresses_lower = BaseBotParser.get_scammer_addresses(w3, alert_event) - logging.info(f"{BOT_VERSION}: alert {alert_event.alert_hash} {alert_event.bot_id} {alert_event.alert.alert_id} - got contract similarity bot alert; got {len(scammer_addresses_lower)} scammer addresses.") - for scammer_address_lower in scammer_addresses_lower: - # Check if the address is in the manual FP list - if Utils.is_in_fp_mitigation_list(scammer_address_lower): - logging.info(f"Skipped alert for {scammer_address_lower} as it is in the manual FP list.") - continue - - logging.info(f"{BOT_VERSION}: alert {alert_event.alert_hash} {alert_event.bot_id} {alert_event.alert.alert_id} - processing contract similarity bot address {scammer_address_lower}") - - similarity_score = float(alert_event.alert.metadata['similarity_score']) if 'similarity_score' in alert_event.alert.metadata else float(alert_event.alert.metadata['similarityScore']) - logging.info(f"{BOT_VERSION}: alert {alert_event.alert_hash} {alert_event.bot_id} {alert_event.alert.alert_id} - {scammer_address_lower} similarity score {similarity_score}") - if similarity_score > CONTRACT_SIMILARITY_BOT_THRESHOLDS[0]: - logging.info(f"{BOT_VERSION}: alert {alert_event.alert_hash} {alert_event.bot_id} {alert_event.alert.alert_id} - similarity score {similarity_score} is above threshold {CONTRACT_SIMILARITY_BOT_THRESHOLDS[0]}") - if not Utils.is_fp(w3, scammer_address_lower, CHAIN_ID, FINDINGS_CACHE_ALERT): - - if not already_alerted(scammer_address_lower, "SCAM-DETECTOR-SIMILAR-CONTRACT", "similar_contract"): - logging.info(f"{BOT_VERSION}: alert {alert_event.alert_hash} {alert_event.bot_id} {alert_event.alert.alert_id} - address {scammer_address_lower}; emitting finding") - update_list(ALERTED_ENTITIES_SIMILAR_CONTRACT, ALERTED_ENTITIES_SIMILAR_CONTRACT_QUEUE_SIZE, scammer_address_lower, "SCAM-DETECTOR-SIMILAR-CONTRACT", "ALERTED_ENTITIES_SIMILAR_CONTRACT", "similar_contract") - finding = ScamDetectorFinding.alert_similar_contract(block_chain_indexer, forta_explorer, alert_event.alert.alert_id, alert_event.alert_hash, alert_event.alert.metadata, CHAIN_ID) - if(finding is not None): - findings.append(finding) - else: - logging.info(f"{BOT_VERSION}: alert {alert_event.alert_hash} {alert_event.bot_id} {alert_event.alert.alert_id} - finding is none due to original threat category not being in list flagged for propagation") - else: - logging.info(f"{BOT_VERSION}: alert {alert_event.alert_hash} {alert_event.bot_id} {alert_event.alert.alert_id} - address {scammer_address_lower} already alerted") - else: - logging.info(f"{BOT_VERSION}: alert {alert_event.alert_hash} {alert_event.bot_id} {alert_event.alert.alert_id} - address {scammer_address_lower} in FP.") - return findings - def emit_eoa_association_finding(w3, alert_event: forta_agent.alert_event.AlertEvent) -> list: global ALERTED_ENTITIES_SCAMMER_ASSOCIATION @@ -898,16 +853,10 @@ def detect_scam(w3, alert_event: forta_agent.alert_event.AlertEvent, clear_state put_entity_cluster(alert_event.alert.created_at, address, cluster) # for basebots, three paths: - # for contract similarity, a bit more work # for passthroughs, simply emit an alert (pot with some adjustments on mappings) # for combination base bots store in dynamo; then query dynamo for the cluster (this will pull all alerts from multiple shards), build feature vector and then evaluate detection heuristic - if in_list(alert_event, CONTRACT_SIMILARITY_BOTS): - start = time.time() - logging.info(f"{BOT_VERSION}: alert {alert_event.alert_hash} is contract similarity alert") - findings.extend(emit_contract_similarity_finding(w3, alert_event)) - logging.info(f"{BOT_VERSION}: alert {alert_event.alert_hash} is contract similarity alert. Processing took {time.time() - start} seconds.") - elif in_list(alert_event, EOA_ASSOCIATION_BOTS): + if in_list(alert_event, EOA_ASSOCIATION_BOTS): start = time.time() logging.info(f"{BOT_VERSION}: alert {alert_event.alert_hash} is eoa association alert") findings.extend(emit_eoa_association_finding(w3, alert_event)) @@ -958,7 +907,6 @@ def emit_new_fp_finding(w3) -> list: raise Exception("CHAIN_ID not set") findings = [] - similar_contract_labels = None scammer_association_labels = None try: @@ -974,10 +922,8 @@ def emit_new_fp_finding(w3) -> list: for address in cluster.split(','): if scammer_association_labels is None: scammer_association_labels = get_scammer_association_labels(w3, forta_explorer) - if similar_contract_labels is None: - similar_contract_labels = get_similar_contract_labels(w3, forta_explorer) - for (entity, label, metadata, unique_key) in obtain_all_fp_labels(w3, address, block_chain_indexer, forta_explorer, similar_contract_labels, scammer_association_labels, CHAIN_ID): + for (entity, label, metadata, unique_key) in obtain_all_fp_labels(w3, address, block_chain_indexer, forta_explorer, scammer_association_labels, CHAIN_ID): logging.info(f"{BOT_VERSION}: Emitting FP mitigation finding for {entity} {label}") update_list(ALERTED_FP_CLUSTERS, ALERTED_FP_CLUSTERS_QUEUE_SIZE, entity, "SCAM-DETECTOR-FALSE-POSITIVE", "ALERTED_FP_CLUSTERS") findings.append(ScamDetectorFinding.alert_FP(w3, entity, label, metadata, [unique_key])) @@ -999,7 +945,6 @@ def update_reactive_likely_fps(w3, current_date) -> list: logging.info(f"{BOT_VERSION}: update reactive likely fps called") global REACTIVE_LIKELY_FPS global ALERTED_FP_CLUSTERS - global SIMILAR_CONTRACT_LABELS global SCAMMER_ASSOCIATION_LABELS global LAST_PROCESSED_TIME findings = [] @@ -1056,7 +1001,6 @@ def update_reactive_likely_fps(w3, current_date) -> list: if REACTIVE_LIKELY_FPS: if current_date.minute == 5: # Refresh the data every hour (at the 05 minute) - SIMILAR_CONTRACT_LABELS = None SCAMMER_ASSOCIATION_LABELS = None address = next(iter(REACTIVE_LIKELY_FPS), None) @@ -1068,9 +1012,7 @@ def update_reactive_likely_fps(w3, current_date) -> list: findings.append(ScamDetectorFinding.alert_FP(w3, address, "scammer", metadata_array, unique_keys_array)) if SCAMMER_ASSOCIATION_LABELS is None: SCAMMER_ASSOCIATION_LABELS = get_scammer_association_labels(w3, forta_explorer) - if SIMILAR_CONTRACT_LABELS is None: - SIMILAR_CONTRACT_LABELS = get_similar_contract_labels(w3, forta_explorer) - for (entity, label, metadata, unique_key) in obtain_all_fp_labels(w3, address, block_chain_indexer, forta_explorer, SIMILAR_CONTRACT_LABELS, SCAMMER_ASSOCIATION_LABELS, CHAIN_ID): + for (entity, label, metadata, unique_key) in obtain_all_fp_labels(w3, address, block_chain_indexer, forta_explorer, SCAMMER_ASSOCIATION_LABELS, CHAIN_ID): logging.info(f"{BOT_VERSION}: Processing entity: {entity} - {label}") if entity != address: logging.info(f"{BOT_VERSION}: Emitting FP mitigation finding for {entity} {label}") @@ -1091,19 +1033,6 @@ def get_value(items: dict, key: str): return v -# contains from_entity, from_entity_deployer, to_entity, to_entity_deployer -def get_similar_contract_labels(w3, forta_explorer) -> pd.DataFrame: - source_id = SCAM_DETECTOR_BETA_ALT_BOT_ID if Utils.is_beta_alt() else (SCAM_DETECTOR_BETA_BOT_ID if Utils.is_beta() else SCAM_DETECTOR_BOT_ID) - df_labels = forta_explorer.get_labels(source_id, datetime(2023,3,1), datetime.now(), label_query = "similar-contract") - df_labels.rename(columns={'entity': 'to_entity'}, inplace=True) - df_labels['from_entity'] = df_labels['metadata'].apply(lambda x: get_value(x, "associated_scammer_contract")) - df_labels['deployer_info'] = df_labels['metadata'].apply(lambda x: get_value(x, "deployer_info")) - df_labels['from_entity_deployer'] = df_labels['deployer_info'].apply(lambda x: x[216:216+42]) - df_labels['to_entity_deployer'] = df_labels['deployer_info'].apply(lambda x: x[9:9+42]) - # drop all but from_entity and to_entity - df_labels.drop(df_labels.columns.difference(['from_entity', 'from_entity_deployer', 'to_entity', 'to_entity_deployer']), axis=1, inplace=True) - return df_labels - # contains from_entity and to_entity @@ -1123,7 +1052,7 @@ def get_scammer_association_labels(w3, forta_explorer) -> pd.DataFrame: # this function returns a list of all labels that need to be removed with the address as a starting point # it contain a queue of addresses to process and a set of addresses that have already been processed # returns a tuple of (entity, threat_category, metadata); metadata is a tuple of key=value pairs because its not hashable otherwise -def obtain_all_fp_labels(w3, starting_address: str, block_chain_indexer, forta_explorer, similar_contract_labels: pd.DataFrame, scammer_association_labels: pd.DataFrame, chain_id: int) -> set: +def obtain_all_fp_labels(w3, starting_address: str, block_chain_indexer, forta_explorer, scammer_association_labels: pd.DataFrame, chain_id: int) -> set: global ALERTED_FP_CLUSTERS global ALERTED_FP_CLUSTERS_QUEUE_SIZE @@ -1154,20 +1083,6 @@ def obtain_all_fp_labels(w3, starting_address: str, block_chain_indexer, forta_e logging.info(f"{BOT_VERSION}: {starting_address} adding FP label threat category {threat_category} for contract {address}") fp_labels.add((address,label, tuple([f"{k}={v}" for k, v in row['metadata'].items()]), unique_key)) - similar_contract_labels_for_address = similar_contract_labels[similar_contract_labels['from_entity'] == address] - for index, row in similar_contract_labels_for_address.iterrows(): - logging.info(f"{BOT_VERSION}: {starting_address} adding to process due to contract similarity from_entity {address} -> to_entity {row['to_entity']}, to_entity_deployer {row['to_entity_deployer']}, from_entity_deployer {row['from_entity_deployer']}") - to_process.add(row['to_entity']) - to_process.add(row['to_entity_deployer']) - to_process.add(row['from_entity_deployer']) - - similar_contract_labels_for_address = similar_contract_labels[similar_contract_labels['to_entity'] == address] - for index, row in similar_contract_labels_for_address.iterrows(): - logging.info(f"{BOT_VERSION}: {starting_address} adding to process due to contract similarity to_entity {address} -> from_entity {row['from_entity']}, from_entity_deployer {row['from_entity_deployer']}, to_entity_deployer {row['to_entity_deployer']}") - to_process.add(row['from_entity']) - to_process.add(row['from_entity_deployer']) - to_process.add(row['to_entity_deployer']) - else: forta_labels = forta_explorer.get_labels(source_id, datetime(2023,1,1), datetime.now(), entity=address) @@ -1302,7 +1217,6 @@ def clear_state(): L2Cache.remove(CHAIN_ID, ALERTED_ENTITIES_ML_KEY) L2Cache.remove(CHAIN_ID, ALERTED_ENTITIES_PASSTHROUGH_KEY) L2Cache.remove(CHAIN_ID, ALERTED_ENTITIES_SCAMMER_ASSOCIATION_KEY) - L2Cache.remove(CHAIN_ID, ALERTED_ENTITIES_SIMILAR_CONTRACT_KEY) L2Cache.remove(CHAIN_ID, ALERTED_ENTITIES_MANUAL_KEY) L2Cache.remove(CHAIN_ID, ALERTED_ENTITIES_MANUAL_METAMASK_KEY) L2Cache.remove(CHAIN_ID, ALERTED_FP_CLUSTERS_KEY) @@ -1325,9 +1239,6 @@ def persist_state(): global ALERTED_ENTITIES_SCAMMER_ASSOCIATION global ALERTED_ENTITIES_SCAMMER_ASSOCIATION_KEY - global ALERTED_ENTITIES_SIMILAR_CONTRACT - global ALERTED_ENTITIES_SIMILAR_CONTRACT_KEY - global ALERTED_ENTITIES_MANUAL global ALERTED_ENTITIES_MANUAL_KEY @@ -1353,7 +1264,6 @@ def persist_state(): persist(ALERTED_ENTITIES_ML, CHAIN_ID, ALERTED_ENTITIES_ML_KEY) persist(ALERTED_ENTITIES_PASSTHROUGH, CHAIN_ID, ALERTED_ENTITIES_PASSTHROUGH_KEY) persist(ALERTED_ENTITIES_SCAMMER_ASSOCIATION, CHAIN_ID, ALERTED_ENTITIES_SCAMMER_ASSOCIATION_KEY) - persist(ALERTED_ENTITIES_SIMILAR_CONTRACT, CHAIN_ID, ALERTED_ENTITIES_SIMILAR_CONTRACT_KEY) persist(ALERTED_ENTITIES_MANUAL, CHAIN_ID, ALERTED_ENTITIES_MANUAL_KEY) persist(ALERTED_FP_CLUSTERS, CHAIN_ID, ALERTED_FP_CLUSTERS_KEY) persist(FINDINGS_CACHE_BLOCK, CHAIN_ID, FINDINGS_CACHE_BLOCK_KEY) diff --git a/scam-detector-py/src/agent_test.py b/scam-detector-py/src/agent_test.py index 53fe784a..db1591c7 100644 --- a/scam-detector-py/src/agent_test.py +++ b/scam-detector-py/src/agent_test.py @@ -824,56 +824,6 @@ def test_detect_twitter_bot_scammer(self): assert finding.metadata is not None, "metadata should not be empty" assert len(finding.labels) > 0, "labels should not be empty" - def test_detect_alert_similar_contract(self): - agent.initialize() - agent.item_id_prefix = "test_" + str(random.randint(0, 1000000)) - - # Read the content of package.json and store the original "name" field - original_name = "" - with open("package.json", "r") as package_file: - package_data = json.load(package_file) - original_name = package_data["name"] - - # Modify the "name" field to "beta" (as alt doesn't return labels for the test) - package_data["name"] = "beta" - with open("package.json", "w") as package_file: - json.dump(package_data, package_file, indent=2) - - bot_id = "0x3acf759d5e180c05ecabac2dbd11b79a1f07e746121fc3c86910aaace8910560" - alert_id = "NEW-SCAMMER-CONTRACT-CODE-HASH" - description = "0xd359b4058cfbc9a5ef2889bc484cbbffbe3fa254f6f36845be6a4f5618531bd5 (NEW-SCAMMER-CONTRACT-CODE-HASH)" - - metadata = {"alert_hash":"0xcfc5f89ac8c801901724621470fb7e3efec1b0cb5e1af625b82d587b788cdc86","new_scammer_contract_address":"0xfe551e214563283c8ab5df967d7d69f630b64079","new_scammer_eoa":"0xa4f58353711f9f29b483fe41be8f0dcc893d9f8a","scammer_contract_address":"0x200c5fa46720e40c375dd276a816da905b19081e","scammer_eoa":"0x43cf4c4759ebe43aa6e21e13ece8546dcfcb728c","similarity_hash":"20d794469ef5c3f5937d8b2ad1505e57a97b6fa0205b9fba965d71e9a4f66ea6","similarity_score":"0.9768354296684265"} - alert_event = TestScamDetector.generate_alert(bot_id, alert_id, description, metadata) - - findings = agent.detect_scam(w3, alert_event, True) - - # Revert the "name" field back to its original value - package_data["name"] = original_name - with open("package.json", "w") as package_file: - json.dump(package_data, package_file, indent=2) - - assert len(findings) == 1, "this should have triggered a finding" - assert findings[0].alert_id == "SCAM-DETECTOR-SIMILAR-CONTRACT" - assert findings[0].metadata['scammer_address'] == "0xa4f58353711f9f29b483fe41be8f0dcc893d9f8a", "metadata should not be empty" - assert findings[0].metadata['scammer_contract_address'] == "0xfe551e214563283c8ab5df967d7d69f630b64079", "metadata should not be empty" - assert findings[0].metadata['existing_scammer_address'] == "0x43cf4c4759ebe43aa6e21e13ece8546dcfcb728c", "metadata should not be empty" - assert findings[0].metadata['existing_scammer_contract_address'] == "0x200c5fa46720e40c375dd276a816da905b19081e", "metadata should not be empty" - assert findings[0].metadata['similarity_score'] == "0.9768354296684265", "metadata should not be empty" - assert findings[0].metadata['involved_threat_categories'] == "soft-rug-pull", "metadata should not be empty" - assert findings[0].metadata['involved_alert_hash_1'] == "0xcfc5f89ac8c801901724621470fb7e3efec1b0cb5e1af625b82d587b788cdc86", "metadata should not be empty" - - assert findings[0].labels is not None, "labels should not be empty" - label = findings[0].labels[0] - assert label.entity == "0xa4f58353711f9f29b483fe41be8f0dcc893d9f8a", "entity should be attacker address" - assert label.label == "scammer", "entity should labeled as scam" - assert label.confidence == Utils.get_confidence_value('similar-contract'), "entity should labeled with 0.7 confidence" - - label = findings[0].labels[1] - assert label.entity == "0xfe551e214563283c8ab5df967d7d69f630b64079", "entity should be attacker address" - assert label.label == "scammer", "entity should labeled as scam" - assert label.confidence == Utils.get_confidence_value('similar-contract'), "entity should labeled with 0.7 confidence" - def test_put_entity_cluster(self): agent.initialize() agent.item_id_prefix = "test_" + str(random.randint(0, 1000000)) @@ -1051,6 +1001,7 @@ def test_get_score_empty_features(self): score = agent.get_model_score(df_expected_feature_vector) assert score < MODEL_ALERT_THRESHOLD_LOOSE, "should less than model threshold" + # TODO: Update test because score is below threshold def test_scam_critical(self): agent.initialize() agent.item_id_prefix = "test_" + str(random.randint(0, 1000000)) @@ -1152,21 +1103,6 @@ def test_fp_mitigation_proper_chain_id(self): assert label.entity == "0x8cc6b83d52b67f629fb3c5978cda3a6c2a456edc" assert label.metadata['address_type'] == "EOA" - def test_get_similar_contract_labels(self): - agent.clear_state() - agent.initialize() - similar_contract_labels = agent.get_similar_contract_labels(w3, forta_explorer) - - # from_address was detected first and it propagated its label to the to_address - from_address = "0xfa8c1a1dddea2c06364c9e6ab31772f020f5efc6" - from_address_deployer = "0x2320a28f52334d62622cc2eafa15de55f9987ecc" - to_address = "0xfa8c1a1dddea2c06364c9e6ab31772f020f5efc5" - to_address_deployer = "0x2320a28f52334d62622cc2eafa15de55f9987eaa" - - assert similar_contract_labels[similar_contract_labels['from_entity'] == from_address].iloc[0]['to_entity'] == to_address - assert similar_contract_labels[similar_contract_labels['from_entity'] == from_address].iloc[0]['from_entity_deployer'] == from_address_deployer - assert similar_contract_labels[similar_contract_labels['to_entity'] == to_address].iloc[0]['to_entity_deployer'] == to_address_deployer - def test_get_scammer_association_labels(self): agent.clear_state() agent.initialize() @@ -1183,10 +1119,9 @@ def test_obtain_all_fp_labels_deployed_contracts(self): agent.clear_state() agent.initialize() - similar_contract_labels = pd.DataFrame(columns=['from_entity', 'to_entity']) scammer_association_labels = pd.DataFrame(columns=['from_entity', 'to_entity']) - fp_labels = agent.obtain_all_fp_labels(w3, EOA_ADDRESS_SMALL_TX, block_chain_indexer, forta_explorer, similar_contract_labels, scammer_association_labels, 1) + fp_labels = agent.obtain_all_fp_labels(w3, EOA_ADDRESS_SMALL_TX, block_chain_indexer, forta_explorer, scammer_association_labels, 1) sorted_fp_labels = sorted(fp_labels, key=lambda x: x[0]) sorted_fp_labels = list(sorted_fp_labels) assert len(sorted_fp_labels) == 2, "should have two FP label; one for the EOA, one for the contract" @@ -1208,11 +1143,10 @@ def test_obtain_all_fp_labels_scammer_association(self): agent.clear_state() agent.initialize() - similar_contract_labels = pd.DataFrame(columns=['from_entity', 'to_entity']) scammer_association_labels = pd.DataFrame(columns=['from_entity', 'to_entity']) scammer_association_labels = pd.concat([scammer_association_labels, pd.DataFrame({'from_entity': [EOA_ADDRESS_LARGE_TX.lower()], 'to_entity': [EOA_ADDRESS_SMALL_TX.lower()]})], ignore_index=True) - fp_labels = agent.obtain_all_fp_labels(w3, EOA_ADDRESS_LARGE_TX, block_chain_indexer, forta_explorer, similar_contract_labels, scammer_association_labels, 1) + fp_labels = agent.obtain_all_fp_labels(w3, EOA_ADDRESS_LARGE_TX, block_chain_indexer, forta_explorer, scammer_association_labels, 1) sorted_fp_labels = sorted(fp_labels, key=lambda x: x[0]) sorted_fp_labels = list(sorted_fp_labels) assert len(sorted_fp_labels) == 4, "should have four FP labels; one for each EOA and contract" @@ -1229,22 +1163,7 @@ def test_obtain_all_fp_labels_scammer_association(self): assert 'threat_category=address-poisoner' in label_3[2] - def test_obtain_all_fp_labels_similar_contract(self): - # got address A that deployed contract B; contract B propagated to contract D - agent.clear_state() - agent.initialize() - - similar_contract_labels = pd.DataFrame(columns=['from_entity', 'to_entity']) - new_labels = pd.DataFrame({'from_entity': [CONTRACT.lower()], 'from_entity_deployer': [EOA_ADDRESS_LARGE_TX.lower()], 'to_entity_deployer': [EOA_ADDRESS_SMALL_TX.lower()], 'to_entity': [CONTRACT2.lower()]}) - similar_contract_labels = pd.concat([similar_contract_labels, new_labels], ignore_index=True) - scammer_association_labels = pd.DataFrame(columns=['from_entity', 'to_entity']) - - fp_labels = agent.obtain_all_fp_labels(w3, EOA_ADDRESS_LARGE_TX, block_chain_indexer, forta_explorer, similar_contract_labels, scammer_association_labels, 1) - sorted_fp_labels = sorted(fp_labels, key=lambda x: x[0]) - sorted_fp_labels = list(sorted_fp_labels) - assert len(sorted_fp_labels) == 4, "should have four FP labels; one for each EOA and contract" - - # 11/22/2023 - removed because we have not been able to ship this for some time now + # 11/22/2023 - removed because we have not been able to ship this for some time now # def test_detect_ice_phishing_ml(self): # agent.initialize() # agent.item_id_prefix = "test_" + str(random.randint(0, 1000000)) diff --git a/scam-detector-py/src/constants.py b/scam-detector-py/src/constants.py index f943864c..6c322f0d 100644 --- a/scam-detector-py/src/constants.py +++ b/scam-detector-py/src/constants.py @@ -5,9 +5,6 @@ ENTITY_CLUSTER_BOTS = [("0xd3061db4662d5b3406b52b20f34234e462d2c275b99414d76dc644e2486be3e9", "ENTITY-CLUSTER")] -CONTRACT_SIMILARITY_BOTS = [("0x3acf759d5e180c05ecabac2dbd11b79a1f07e746121fc3c86910aaace8910560", "NEW-SCAMMER-CONTRACT-CODE-HASH")] -CONTRACT_SIMILARITY_BOT_THRESHOLDS = [0.97] - EOA_ASSOCIATION_BOTS = [("0xcd9988f3d5c993592b61048628c28a7424235794ada5dc80d55eeb70ec513848", "SCAMMER-LABEL-PROPAGATION-1")] EOA_ASSOCIATION_BOT_THRESHOLDS = [0.0] @@ -23,8 +20,6 @@ ALERTED_ENTITIES_PASSTHROUGH_QUEUE_SIZE = 75000 ALERTED_ENTITIES_SCAMMER_ASSOCIATION_KEY = "alerted_entities_scammer_association_per_alert_id_key" ALERTED_ENTITIES_SCAMMER_ASSOCIATION_QUEUE_SIZE = 100000 -ALERTED_ENTITIES_SIMILAR_CONTRACT_KEY = "alerted_entities_similar_contract_per_alert_id_key" -ALERTED_ENTITIES_SIMILAR_CONTRACT_QUEUE_SIZE = 100000 ALERTED_ENTITIES_MANUAL_KEY = "alerted_entities_manual_per_alert_id_key" ALERTED_ENTITIES_MANUAL_QUEUE_SIZE = 100000 ALERTED_ENTITIES_MANUAL_METAMASK_KEY = "alerted_entities_manual_metamask_per_alert_id_key" @@ -79,7 +74,6 @@ ("0x1a69f5ec8ef436e4093f9ec4ce1a55252b7a9a2d2c386e3f950b79d164bc99e0", "NIP-1", "PassThrough", "SCAM-DETECTOR-SOCIAL-ENG-NATIVE-ICE-PHISHING"), # Native ice phishing with a social eng component (aka a function parameter) ("0x8732dbb3858d65844d940f5de3705b4161c05258bdfedf1ff5afb6683e1274e5", "NFT-WASH-TRADE", "PassThrough", "SCAM-DETECTOR-WASH-TRADE"), # wash trading bot maintained by nethermind ("0x067e4c4f771f288c686efa574b685b98a92918f038a478b82c9ac5b5b6472732", "NFT-WASH-TRADE", "Combination", ""), # wash trading bot - for ML bot; need to replace after retraining - ("0x3acf759d5e180c05ecabac2dbd11b79a1f07e746121fc3c86910aaace8910560", "NEW-SCAMMER-CONTRACT-CODE-HASH", "PassThrough", "SCAM-DETECTOR-SIMILAR-CONTRACT"), # contract similarity bot ("0x1a69f5ec8ef436e4093f9ec4ce1a55252b7a9a2d2c386e3f950b79d164bc99e0", "NIP-5", "PassThrough", "SCAM-DETECTOR-SOCIAL-ENG-NATIVE-ICE-PHISHING"), # Native ice phishing using soc eng contract (static) ("0x1a69f5ec8ef436e4093f9ec4ce1a55252b7a9a2d2c386e3f950b79d164bc99e0", "NIP-6", "PassThrough", "SCAM-DETECTOR-SOCIAL-ENG-NATIVE-ICE-PHISHING"), # Native ice phishing using soc eng contract (dynamic) ("0x1a69f5ec8ef436e4093f9ec4ce1a55252b7a9a2d2c386e3f950b79d164bc99e0", "NIP-8", "PassThrough", "SCAM-DETECTOR-SOCIAL-ENG-NATIVE-ICE-PHISHING"), # Native ice phishing using soc eng contract (dynamic) @@ -296,7 +290,6 @@ "address-poisoner": 0.85, "impersonating-token": 0.99, "attack-stages": 0.25, - "similar-contract": 0.99, "scammer-deployed-contract": 0.99, "scammer-association": 0.60, "private-key-compromise": 0.4, diff --git a/scam-detector-py/src/findings.py b/scam-detector-py/src/findings.py index 47115f78..336da4a8 100644 --- a/scam-detector-py/src/findings.py +++ b/scam-detector-py/src/findings.py @@ -81,8 +81,6 @@ def get_threat_category(alert_id: str) -> str: return "rake-token" elif alert_id == "SCAM-DETECTOR-IMPERSONATING-TOKEN": return "impersonating-token" - elif alert_id == "SCAM-DETECTOR-SIMILAR-CONTRACT": - return "similar-contract" elif alert_id == "SCAM-DETECTOR-SCAMMER-ASSOCIATION": return "scammer-association" elif alert_id == "SCAM-DETECTOR-SCAMMER-DEPLOYED-CONTRACT": @@ -101,145 +99,6 @@ def get_threat_category(alert_id: str) -> str: return "unknown" else: return "" - - @staticmethod - def alert_similar_contract(block_chain_indexer, forta_explorer, base_bot_alert_id: str, base_bot_alert_hash: str, metadata: dict, chain_id:int) -> Optional[Finding]: - - # {"alert_hash":"0x92f0e1c5f9677a3ea2903047641213ba62e5a00d62f363efc1a85cd1e184e016", - # "new_scammer_contract_address":"0x75577bd21803a13d6ec3e0d784f84e0e7e31cbd2", - # "new_scammer_eoa":"0x7e6b6f2be1bb8d2e1d5fcefa2d6df86b6e03b8d0", - # "scammer_contract_address":"0xe22536ac6f6a20dbb283e7f61a880993eab63313", - # "scammer_eoa":"0xc1015eb4d9aa4f77d79cf04825cbfb7fc04e232e", - # "similarity_hash":"68e6432db785f93986a9d49b19077067f8b694612f2bc1e8ef5cd38af2c8727e", - # "similarity_score":"0.9347575306892395"} - - alert_hash = metadata["alertHash"] if "alertHash" in metadata else metadata["alert_hash"] - existing_scammer_contract_address = metadata["scammerContractAddress"] if "scammerContractAddress" in metadata else metadata["scammer_contract_address"] - existing_scammer_address = metadata["scammerEoa"] if "scammerEoa" in metadata else metadata["scammer_eoa"] - scammer_contract_address = metadata["newScammerContractAddress"] if "newScammerContractAddress" in metadata else metadata["new_scammer_contract_address"] - scammer_address = metadata["newScammerEoa"] if "newScammerEoa" in metadata else metadata["new_scammer_eoa"] - similarity_score = metadata["similarityScore"] if "similarityScore" in metadata else metadata["similarity_score"] - - alert_id = "SCAM-DETECTOR-SIMILAR-CONTRACT" # only used in context of alerts; in context of labels we talk about threat-categories - - original_threat_categories = set() # scammer-eoa/* threat categories of the original scammer - source_id = SCAM_DETECTOR_BETA_ALT_BOT_ID if Utils.is_beta_alt() else (SCAM_DETECTOR_BETA_BOT_ID if Utils.is_beta() else SCAM_DETECTOR_BOT_ID) - df_labels = forta_explorer.get_labels(source_id, datetime(2023,1,1), datetime.now(), entity = existing_scammer_contract_address.lower()) - - for index, row in df_labels.iterrows(): - if row['metadata'] is not None and "address_type" in row['metadata'].keys() and "threat_category" in row['metadata'].keys() and row['metadata']['address_type'] == 'contract': - original_threat_category = row['metadata']['threat_category'] - original_threat_categories.add(original_threat_category) - logging.info(f"retrieved original threat category for label {existing_scammer_contract_address.lower()}: {original_threat_category}") - - - if len(original_threat_categories.intersection(set(['address-poisoner', 'native-ice-phishing-social-engineering', 'hard-rug-pull', 'soft-rug-pull', 'rake-token', 'impersonating-token'])))>0: - labels = [] - threat_category = ScamDetectorFinding.get_threat_category(alert_id) - confidence = Utils.get_confidence_value(threat_category) - labels.append(Label({ - 'entityType': EntityType.Address, - 'label': 'scammer', - 'entity': scammer_address, - 'confidence': confidence, - 'metadata': { - 'address_type': 'EOA', - 'chain_id': chain_id, - 'base_bot_alert_ids': base_bot_alert_id, # base bot alert id: contract similarity alert id - 'base_bot_alert_hashes': base_bot_alert_hash, - 'associated_scammer_contract': existing_scammer_contract_address, - 'associated_scammer_threat_categories': ','.join(original_threat_categories), - 'associated_scammer_alert_hashes': alert_hash, - 'deployer_info': f"Deployer {scammer_address} deployed a contract {scammer_contract_address} that is similar to a contract {existing_scammer_contract_address} deployed by a known scammer {existing_scammer_address} involved in {','.join(original_threat_categories)} scam (alert hash: {alert_hash}).", - 'threat_category': threat_category, - 'threat_description_url': ScamDetectorFinding.get_threat_description_url(alert_id), - 'bot_version': Utils.get_bot_version(), - 'label_version': ScamDetectorFinding.LABEL_VERSION, - 'logic': 'propagation' - } - })) - - common_scammer_contract_label_properties = { - 'entityType': EntityType.Address, - 'entity': scammer_contract_address, - 'confidence': confidence, - 'metadata': { - 'address_type': 'contract', - 'chain_id': chain_id, - 'base_bot_alert_ids': base_bot_alert_id, # base bot alert id: contract similarity alert id - 'base_bot_alert_hashes': base_bot_alert_hash, - 'associated_scammer_contract': existing_scammer_contract_address, - 'associated_scammer_threat_categories': ','.join(original_threat_categories), - 'associated_scammer_alert_hashes': alert_hash, - 'deployer_info': f"Deployer {scammer_address} deployed a contract {scammer_contract_address} that is similar to a contract {existing_scammer_contract_address} deployed by a known scammer {existing_scammer_address} involved in {','.join(original_threat_categories)} scam (alert hash: {alert_hash}); this contract may or may not be related to this particular scam, but was created by the scammer.", - 'threat_category': threat_category, - 'threat_description_url': ScamDetectorFinding.get_threat_description_url(alert_id), - 'bot_version': Utils.get_bot_version(), - 'label_version': ScamDetectorFinding.LABEL_VERSION, - 'logic': 'propagation' - } - } - - labels.append(Label({ - 'label': 'scammer', - **common_scammer_contract_label_properties - })) - - labels.append(Label({ - 'label': 'similar-contract', - **common_scammer_contract_label_properties - })) - - # get all deployed contracts by EOA and add label for those using etherscan or allium - try: - contracts = block_chain_indexer.get_contracts(scammer_address, chain_id) - for contract in contracts: - labels.append(Label({ - 'entityType': EntityType.Address, - 'label': 'scammer', - 'entity': contract, - 'confidence': confidence * 0.8, - 'metadata': { - 'address_type': 'contract', - 'chain_id': chain_id, - 'base_bot_alert_ids': base_bot_alert_id, # base bot alert id: contract similarity alert id - 'base_bot_alert_hashes': base_bot_alert_hash, - 'associated_scammer_contract': existing_scammer_contract_address, - 'associated_scammer_threat_categories': ','.join(original_threat_categories), - 'associated_scammer_alert_hashes': alert_hash, - 'deployer_info': f"Deployer {scammer_address} involved in {','.join(original_threat_categories)} scam; this contract may or may not be related to this particular scam, but was created by the scammer.", - 'threat_category': ScamDetectorFinding.get_threat_category("SCAM-DETECTOR-SCAMMER-DEPLOYED-CONTRACT"), - 'threat_description_url': ScamDetectorFinding.get_threat_description_url(alert_id), - 'bot_version': Utils.get_bot_version(), - 'label_version': ScamDetectorFinding.LABEL_VERSION, - 'logic': 'propagation' - } - })) - except Exception as e: - logging.warning(f"Error getting contracts for scammer address {scammer_address}: {e}") - Utils.ERROR_CACHE.add(Utils.alert_error(str(e), "findings.alert_similar_contract", traceback.format_exc())) - - metadata = {} - metadata['scammer_address'] = scammer_address - metadata['scammer_contract_address'] = scammer_contract_address - metadata['existing_scammer_address'] = existing_scammer_address - metadata['existing_scammer_contract_address'] = existing_scammer_contract_address - metadata['similarity_score'] = similarity_score - metadata['involved_threat_categories'] = ','.join(original_threat_categories) - metadata['involved_alert_hash_1'] = alert_hash - - return Finding({ - 'name': 'Scam detector identified an EOA with past alerts mapping to scam behavior', - 'description': f'{scammer_address} likely involved in a scam ({alert_id}, propagation)', - 'alert_id': alert_id, - 'type': FindingType.Scam, - 'severity': FindingSeverity.Critical, - 'metadata': metadata, - 'labels': labels - }) - - else: - return None @staticmethod def get_url(metadata:dict) -> str: diff --git a/scam-detector-py/src/findings_test.py b/scam-detector-py/src/findings_test.py index 91f05833..14044075 100644 --- a/scam-detector-py/src/findings_test.py +++ b/scam-detector-py/src/findings_test.py @@ -180,85 +180,15 @@ def test_scam_finding_only_url(self): - def test_scam_similar_contract(self): - chain_id = 1 - metadata = {"alert_hash":"0x92f0e1c5f9677a3ea2903047641213ba62e5a00d62f363efc1a85cd1e184e016", - "new_scammer_contract_address":"0x75577bd21803a13d6ec3e0d784f84e0e7e31cbd2", - "new_scammer_eoa":"0x7e6b6f2be1bb8d2e1d5fcefa2d6df86b6e03b8d0", - "scammer_contract_address":"0xe22536ac6f6a20dbb283e7f61a880993eab63313", - "scammer_eoa":"0xc1015eb4d9aa4f77d79cf04825cbfb7fc04e232e", - "similarity_hash":"68e6432db785f93986a9d49b19077067f8b694612f2bc1e8ef5cd38af2c8727e", - "similarity_score":"0.9347575306892395"} - base_bot_alert_id = "" - base_bot_alert_hash = "0x8192" - finding = ScamDetectorFinding.alert_similar_contract(block_chain_indexer, forta_explorer, base_bot_alert_id, base_bot_alert_hash, metadata, chain_id) - - assert finding is not None - alert_id = "SCAM-DETECTOR-SIMILAR-CONTRACT" - assert finding.alert_id == alert_id - assert finding.severity == FindingSeverity.Critical - assert finding.type == FindingType.Scam - assert finding.name == f'Scam detector identified an EOA with past alerts mapping to scam behavior' - assert finding.description == f"0x7e6b6f2be1bb8d2e1d5fcefa2d6df86b6e03b8d0 likely involved in a scam ({alert_id}, propagation)" - assert finding.metadata is not None - assert finding.labels is not None - - assert finding.metadata['scammer_address'] == "0x7e6b6f2be1bb8d2e1d5fcefa2d6df86b6e03b8d0" - assert finding.metadata['scammer_contract_address'] == "0x75577bd21803a13d6ec3e0d784f84e0e7e31cbd2" - assert finding.metadata['existing_scammer_address'] == "0xc1015eb4d9aa4f77d79cf04825cbfb7fc04e232e" - assert finding.metadata['existing_scammer_contract_address'] == "0xe22536ac6f6a20dbb283e7f61a880993eab63313" - assert finding.metadata['similarity_score'] == "0.9347575306892395" - assert finding.metadata['involved_threat_categories'] == "address-poisoner" - assert finding.metadata['involved_alert_hash_1'] == "0x92f0e1c5f9677a3ea2903047641213ba62e5a00d62f363efc1a85cd1e184e016" - - assert len(finding.labels) == 3 - - assert finding.labels[0].entity_type == EntityType.Address - assert finding.labels[0].entity == "0x7e6b6f2be1bb8d2e1d5fcefa2d6df86b6e03b8d0" - assert finding.labels[0].label == "scammer" - assert finding.labels[0].confidence == Utils.get_confidence_value('similar-contract') - assert finding.labels[0].metadata["address_type"] == "EOA" - assert finding.labels[0].metadata["logic"] == "propagation" - assert finding.labels[0].metadata["base_bot_alert_ids"] == base_bot_alert_id - assert finding.labels[0].metadata["base_bot_alert_hashes"] == base_bot_alert_hash - assert finding.labels[0].metadata["deployer_info"] == f'Deployer {metadata["new_scammer_eoa"]} deployed a contract {metadata["new_scammer_contract_address"]} that is similar to a contract {metadata["scammer_contract_address"]} deployed by a known scammer {metadata["scammer_eoa"]} involved in address-poisoner scam (alert hash: {metadata["alert_hash"]}).' - assert finding.labels[0].metadata["threat_category"] == "similar-contract" - assert finding.labels[0].metadata["threat_description_url"] == ScamDetectorFinding.get_threat_description_url(alert_id) - - assert finding.labels[1].entity_type == EntityType.Address - assert finding.labels[1].entity == "0x75577bd21803a13d6ec3e0d784f84e0e7e31cbd2" - assert finding.labels[1].label == "scammer" - assert finding.labels[1].confidence == Utils.get_confidence_value('similar-contract') - assert finding.labels[1].metadata["address_type"] == "contract" - assert finding.labels[1].metadata["logic"] == "propagation" - assert finding.labels[1].metadata["base_bot_alert_ids"] == base_bot_alert_id - assert finding.labels[1].metadata["base_bot_alert_hashes"] == base_bot_alert_hash - assert finding.labels[1].metadata["deployer_info"] == "Deployer 0x7e6b6f2be1bb8d2e1d5fcefa2d6df86b6e03b8d0 deployed a contract 0x75577bd21803a13d6ec3e0d784f84e0e7e31cbd2 that is similar to a contract 0xe22536ac6f6a20dbb283e7f61a880993eab63313 deployed by a known scammer 0xc1015eb4d9aa4f77d79cf04825cbfb7fc04e232e involved in address-poisoner scam (alert hash: 0x92f0e1c5f9677a3ea2903047641213ba62e5a00d62f363efc1a85cd1e184e016); this contract may or may not be related to this particular scam, but was created by the scammer." - assert finding.labels[1].metadata["threat_category"] == "similar-contract" - assert finding.labels[1].metadata["threat_description_url"] == ScamDetectorFinding.get_threat_description_url(alert_id) - - assert finding.labels[2].entity_type == EntityType.Address - assert finding.labels[2].entity == "0x75577bd21803a13d6ec3e0d784f84e0e7e31cbd2" - assert finding.labels[2].label == "similar-contract" - assert finding.labels[2].confidence == Utils.get_confidence_value('similar-contract') - assert finding.labels[2].metadata["address_type"] == "contract" - assert finding.labels[2].metadata["logic"] == "propagation" - assert finding.labels[2].metadata["base_bot_alert_ids"] == base_bot_alert_id - assert finding.labels[2].metadata["base_bot_alert_hashes"] == base_bot_alert_hash - assert finding.labels[2].metadata["deployer_info"] == "Deployer 0x7e6b6f2be1bb8d2e1d5fcefa2d6df86b6e03b8d0 deployed a contract 0x75577bd21803a13d6ec3e0d784f84e0e7e31cbd2 that is similar to a contract 0xe22536ac6f6a20dbb283e7f61a880993eab63313 deployed by a known scammer 0xc1015eb4d9aa4f77d79cf04825cbfb7fc04e232e involved in address-poisoner scam (alert hash: 0x92f0e1c5f9677a3ea2903047641213ba62e5a00d62f363efc1a85cd1e184e016); this contract may or may not be related to this particular scam, but was created by the scammer." - assert finding.labels[2].metadata["threat_category"] == "similar-contract" - assert finding.labels[2].metadata["threat_description_url"] == ScamDetectorFinding.get_threat_description_url(alert_id) - - def test_alert_FP(self): - finding = ScamDetectorFinding.alert_FP(w3, EOA_ADDRESS_LARGE_TX, "scammer", ("threat_category=similar-contract", "address_type=EOA", "logic=propagation"), [""]) + finding = ScamDetectorFinding.alert_FP(w3, EOA_ADDRESS_LARGE_TX, "scammer", ("threat_category=scammer-association", "address_type=EOA", "logic=propagation"), [""]) assert finding.alert_id == "SCAM-DETECTOR-FALSE-POSITIVE", "should be FP" assert finding.description == f'{EOA_ADDRESS_LARGE_TX} likely not involved in a scam (SCAM-DETECTOR-FALSE-POSITIVE, manual)', "should be FP" assert len(finding.labels) == 1, "should be 1" assert finding.labels[0].label == "scammer" assert finding.labels[0].remove == 'true', "should be remove" assert finding.labels[0].metadata["address_type"] == "EOA" - assert finding.labels[0].metadata["threat_category"] == "similar-contract" + assert finding.labels[0].metadata["threat_category"] == "scammer-association" assert finding.labels[0].metadata["logic"] == "propagation" assert finding.labels[0].entity == EOA_ADDRESS_LARGE_TX, "should be EOA_ADDRESS_LARGE_TX" diff --git a/scam-detector-py/src/forta_explorer_mock.py b/scam-detector-py/src/forta_explorer_mock.py index 9913ae38..84b34f05 100644 --- a/scam-detector-py/src/forta_explorer_mock.py +++ b/scam-detector-py/src/forta_explorer_mock.py @@ -88,25 +88,6 @@ def get_labels(source_id: str, start_date: datetime, end_date: datetime, entity: labels_df = pd.concat([labels_df, temp]) - if entity == '' and label_query == 'similar-contract': - temp = pd.DataFrame(columns = ['createdAt', 'id', 'label', 'source', 'alertId', 'alertHash', 'chainId', 'labelstr', 'entity', 'entityType', 'remove', 'confidence', 'metadata', 'botVersion', ], data = [[ - '2023-03-05 16:01:00', - '0x1d646c4045189991fdfd24a66b192a294158b839a6ec121d740474bdacbaaaaa', - 'label_obj', - 'source_obj', - 'SCAM-DETECTOR-SIMILAR-CONTRACT', - '0x1d646c4045189991fdfd24a66b192a294158b839a6ec121d740474bdacbbbbbb', - 1, - 'scammer', - '0xfa8c1a1dddea2c06364c9e6ab31772f020f5efc5', - 'addresss', - False, - 0.9, - {"threat_category":"similar-contract","address_type":"contract","logic":"propagation","base_bot_alert_ids":"ADDRESS-POISONING-FAKE-TOKEN","base_bot_alert_hashes":"0x003e7643042d22f54b817ed14003ad6acbee18f40a818b4e5edadd75d9e9b617","threat_description_url":"https://forta.org/attacks#address-poisoning","bot_version":"0.2.2","associated_scammer_contract":"0xfa8c1a1dddea2c06364c9e6ab31772f020f5efc6","deployer_info":"Deployer 0x2320a28f52334d62622cc2eafa15de55f9987eaa deployed a contract 0xfa8c1a1dddea2c06364c9e6ab31772f020f5efc5 that is similar to a contract 0xfa8c1a1dddea2c06364c9e6ab31772f020f5efc6 deployed by a known scammer 0x2320a28f52334d62622cc2eafa15de55f9987ecc"}, - '0.2.0' - ]]) - labels_df = pd.concat([labels_df, temp]) - if entity == '' and label_query == 'scammer-association': temp = pd.DataFrame(columns = ['createdAt', 'id', 'label', 'source', 'alertId', 'alertHash', 'chainId', 'labelstr', 'entity', 'entityType', 'remove', 'confidence', 'metadata', 'botVersion', ], data = [[ '2023-03-05 16:01:00',