diff --git a/apps/authz/src/app/app.module.ts b/apps/authz/src/app/app.module.ts index 50dd4b84d..5c9ef0bdb 100644 --- a/apps/authz/src/app/app.module.ts +++ b/apps/authz/src/app/app.module.ts @@ -2,7 +2,7 @@ import { AdminService } from '@app/authz/app/core/admin.service' import { AdminController } from '@app/authz/app/http/rest/controller/admin.controller' import { AdminRepository } from '@app/authz/app/persistence/repository/admin.repository' import { PersistenceModule } from '@app/authz/shared/module/persistence/persistence.module' -import { Logger, Module, OnApplicationBootstrap, ValidationPipe } from '@nestjs/common' +import { Module, ValidationPipe } from '@nestjs/common' import { ConfigModule } from '@nestjs/config' import { APP_PIPE } from '@nestjs/core' import { load } from './app.config' @@ -30,14 +30,4 @@ import { OpaService } from './opa/opa.service' } ] }) -export class AppModule implements OnApplicationBootstrap { - private logger = new Logger(AppModule.name) - - constructor(private opaService: OpaService) {} - - async onApplicationBootstrap() { - this.logger.log('Armory Engine app module boot') - - await this.opaService.onApplicationBootstrap() - } -} +export class AppModule {} diff --git a/apps/authz/src/app/opa/opa.service.ts b/apps/authz/src/app/opa/opa.service.ts index 9c72451f1..97cbf7d95 100644 --- a/apps/authz/src/app/opa/opa.service.ts +++ b/apps/authz/src/app/opa/opa.service.ts @@ -1,7 +1,7 @@ import { AdminRepository } from '@app/authz/app/persistence/repository/admin.repository' import { RegoData, User, UserGroup, WalletGroup } from '@app/authz/shared/types/entities.types' import { OpaResult, RegoInput } from '@app/authz/shared/types/rego' -import { Injectable, Logger } from '@nestjs/common' +import { Injectable, Logger, OnApplicationBootstrap } from '@nestjs/common' import { loadPolicy } from '@open-policy-agent/opa-wasm' import { readFileSync } from 'fs' import path from 'path' @@ -13,7 +13,7 @@ type OpaEngine = PromiseType> const OPA_WASM_PATH = path.join(process.cwd(), './rego-build/policy.wasm') @Injectable() -export class OpaService { +export class OpaService implements OnApplicationBootstrap { private logger = new Logger(OpaService.name) private opaEngine: OpaEngine | undefined diff --git a/apps/authz/src/opa/rego/__test__/main_test.rego b/apps/authz/src/opa/rego/__test__/main_test.rego index dbe05d640..8b5240749 100644 --- a/apps/authz/src/opa/rego/__test__/main_test.rego +++ b/apps/authz/src/opa/rego/__test__/main_test.rego @@ -52,47 +52,56 @@ approvalsReq = [ {"userId": "0xaaa8ee1cbaa1856f4550c6fc24abb16c5c9b2a43"}, ] -transfersReq = [ +feedsReq = [ { - "amount": "3051000000", - "from": "eip155:eoa:0xddcf208f219a6e6af072f2cfdc615b2c1805f98e", - "token": "eip155:137/erc20:0x2791bca1f2de4661ed88a30c99a7a9449aa84174", - "rates": {"fiat:usd": "0.99", "fiat:eur": "1.10"}, - "timestamp": elevenHoursAgo, - "chainId": 137, - "initiatedBy": "test-alice-uid", - }, - { - "amount": "2000000000", - "from": "eip155:eoa:0xddcf208f219a6e6af072f2cfdc615b2c1805f98e", - "token": "eip155:137/erc20:0x2791bca1f2de4661ed88a30c99a7a9449aa84174", - "rates": {"fiat:usd": "0.99", "fiat:eur": "1.10"}, - "timestamp": tenHoursAgo, - "chainId": 137, - "initiatedBy": "test-alice-uid", + "source": "armory/price-feed", + "sig": {}, + "data": { + "eip155:137/slip44:966": { + "fiat:usd": "0.99", + "fiat:eur": "1.10", + }, + "eip155:137/erc20:0x2791bca1f2de4661ed88a30c99a7a9449aa84174": { + "fiat:usd": "0.99", + "fiat:eur": "1.10", + }, + }, }, { - "amount": "1500000000", - "from": "eip155:eoa:0xddcf208f219a6e6af072f2cfdc615b2c1805f98e", - "token": "eip155:137/erc20:0x2791bca1f2de4661ed88a30c99a7a9449aa84174", - "rates": {"fiat:usd": "0.99", "fiat:eur": "1.10"}, - "timestamp": twentyHoursAgo, - "chainId": 137, - "initiatedBy": "test-alice-uid", + "source": "armory/historical-transfer-feed", + "sig": {}, + "data": [ + { + "amount": "3051000000", + "from": "eip155:eoa:0xddcf208f219a6e6af072f2cfdc615b2c1805f98e", + "token": "eip155:137/erc20:0x2791bca1f2de4661ed88a30c99a7a9449aa84174", + "rates": {"fiat:usd": "0.99", "fiat:eur": "1.10"}, + "timestamp": elevenHoursAgo, + "chainId": 137, + "initiatedBy": "test-alice-uid", + }, + { + "amount": "2000000000", + "from": "eip155:eoa:0xddcf208f219a6e6af072f2cfdc615b2c1805f98e", + "token": "eip155:137/erc20:0x2791bca1f2de4661ed88a30c99a7a9449aa84174", + "rates": {"fiat:usd": "0.99", "fiat:eur": "1.10"}, + "timestamp": tenHoursAgo, + "chainId": 137, + "initiatedBy": "test-alice-uid", + }, + { + "amount": "1500000000", + "from": "eip155:eoa:0xddcf208f219a6e6af072f2cfdc615b2c1805f98e", + "token": "eip155:137/erc20:0x2791bca1f2de4661ed88a30c99a7a9449aa84174", + "rates": {"fiat:usd": "0.99", "fiat:eur": "1.10"}, + "timestamp": twentyHoursAgo, + "chainId": 137, + "initiatedBy": "test-alice-uid", + }, + ], }, ] -pricesReq = { - "eip155:137/slip44:966": { - "fiat:usd": "0.99", - "fiat:eur": "1.10", - }, - "eip155:137/erc20:0x2791bca1f2de4661ed88a30c99a7a9449aa84174": { - "fiat:usd": "0.99", - "fiat:eur": "1.10", - }, -} - request = { "action": "signTransaction", "transactionRequest": transactionRequestReq, @@ -100,8 +109,7 @@ request = { "resource": resourceReq, "intent": intentReq, "approvals": approvalsReq, - "transfers": transfersReq, - "prices": pricesReq, + "feeds": feedsReq, } entities = { diff --git a/apps/authz/src/opa/rego/__test__/policies/e2e_test.rego b/apps/authz/src/opa/rego/__test__/policies/e2e_test.rego index bffbd89ed..3c73125b1 100644 --- a/apps/authz/src/opa/rego/__test__/policies/e2e_test.rego +++ b/apps/authz/src/opa/rego/__test__/policies/e2e_test.rego @@ -48,64 +48,74 @@ e2e_req = { "pubKey": "0xab88c8785D0C00082dE75D801Fcb1d5066a6311e", }, ], - "transfers": [ + "feeds": [ { - "amount": "100000000000000000", - "from": "eip155:137:0x90d03a8971a2faa19a9d7ffdcbca28fe826a289b", - "to": "eip155:137:0x08a08d0504d4f3363a5b7fda1f5fff1c7bca8ad4", - "chainId": 137, - "token": "eip155:137/slip44:966", - "rates": { - "fiat:usd": "0.99", - "fiat:eur": "1.10", - }, - "initiatedBy": "matt@narval.xyz", - "timestamp": elevenHoursAgo, - }, - { - "amount": "100000000000000000", - "from": "eip155:137:0x90d03a8971a2faa19a9d7ffdcbca28fe826a289b", - "to": "eip155:137:0x08a08d0504d4f3363a5b7fda1f5fff1c7bca8ad4", - "chainId": 137, - "token": "eip155:137/slip44:966", - "rates": { - "fiat:usd": "0.99", - "fiat:eur": "1.10", - }, - "initiatedBy": "matt@narval.xyz", - "timestamp": tenHoursAgo, + "source": "armory/historical-transfer-feed", + "sig": {}, + "data": [ + { + "amount": "100000000000000000", + "from": "eip155:137:0x90d03a8971a2faa19a9d7ffdcbca28fe826a289b", + "to": "eip155:137:0x08a08d0504d4f3363a5b7fda1f5fff1c7bca8ad4", + "chainId": 137, + "token": "eip155:137/slip44:966", + "rates": { + "fiat:usd": "0.99", + "fiat:eur": "1.10", + }, + "initiatedBy": "matt@narval.xyz", + "timestamp": elevenHoursAgo, + }, + { + "amount": "100000000000000000", + "from": "eip155:137:0x90d03a8971a2faa19a9d7ffdcbca28fe826a289b", + "to": "eip155:137:0x08a08d0504d4f3363a5b7fda1f5fff1c7bca8ad4", + "chainId": 137, + "token": "eip155:137/slip44:966", + "rates": { + "fiat:usd": "0.99", + "fiat:eur": "1.10", + }, + "initiatedBy": "matt@narval.xyz", + "timestamp": tenHoursAgo, + }, + { + "amount": "100000000000000000", + "from": "eip155:137:0x90d03a8971a2faa19a9d7ffdcbca28fe826a289b", + "to": "eip155:137:0x08a08d0504d4f3363a5b7fda1f5fff1c7bca8ad4", + "chainId": 137, + "token": "eip155:137/slip44:966", + "rates": { + "fiat:usd": "0.99", + "fiat:eur": "1.10", + }, + "initiatedBy": "matt@narval.xyz", + "timestamp": elevenHoursAgo, + }, + { + "amount": "100000000000000000", + "from": "eip155:137:0x90d03a8971a2faa19a9d7ffdcbca28fe826a289b", + "to": "eip155:137:0x08a08d0504d4f3363a5b7fda1f5fff1c7bca8ad4", + "chainId": 137, + "token": "eip155:137/slip44:966", + "rates": { + "fiat:usd": "0.99", + "fiat:eur": "1.10", + }, + "initiatedBy": "matt@narval.xyz", + "timestamp": tenHoursAgo, + }, + ], }, { - "amount": "100000000000000000", - "from": "eip155:137:0x90d03a8971a2faa19a9d7ffdcbca28fe826a289b", - "to": "eip155:137:0x08a08d0504d4f3363a5b7fda1f5fff1c7bca8ad4", - "chainId": 137, - "token": "eip155:137/slip44:966", - "rates": { - "fiat:usd": "0.99", - "fiat:eur": "1.10", - }, - "initiatedBy": "matt@narval.xyz", - "timestamp": elevenHoursAgo, - }, - { - "amount": "100000000000000000", - "from": "eip155:137:0x90d03a8971a2faa19a9d7ffdcbca28fe826a289b", - "to": "eip155:137:0x08a08d0504d4f3363a5b7fda1f5fff1c7bca8ad4", - "chainId": 137, - "token": "eip155:137/slip44:966", - "rates": { + "source": "armory/price-feed", + "sig": {}, + "data": {"eip155:137/slip44:966": { "fiat:usd": "0.99", "fiat:eur": "1.10", - }, - "initiatedBy": "matt@narval.xyz", - "timestamp": tenHoursAgo, + }}, }, ], - "prices": {"eip155:137/slip44:966": { - "fiat:usd": "0.99", - "fiat:eur": "1.10", - }}, } e2e_entities = { diff --git a/apps/authz/src/opa/rego/__test__/policies/spendings_test.rego b/apps/authz/src/opa/rego/__test__/policies/spendings_test.rego index b6cd154f1..c385e3ce1 100644 --- a/apps/authz/src/opa/rego/__test__/policies/spendings_test.rego +++ b/apps/authz/src/opa/rego/__test__/policies/spendings_test.rego @@ -39,35 +39,57 @@ test_spendingLimitByWalletResource { } test_spendingLimitByUserGroup { - spendingLimitByUserGroupReq = object.union(spendingLimitReq, {"principal": {"userId": "test-bar-uid"}, "transfers": [ - { - "amount": "3051000000", - "from": "eip155:eoa:0xddcf208f219a6e6af072f2cfdc615b2c1805f98e", - "token": "eip155:137/erc20:0x2791bca1f2de4661ed88a30c99a7a9449aa84174", - "rates": {"fiat:usd": "0.99"}, - "timestamp": elevenHoursAgo, - "chainId": 137, - "initiatedBy": "test-alice-uid", - }, - { - "amount": "2000000000", - "from": "eip155:eoa:0xddcf208f219a6e6af072f2cfdc615b2c1805f98e", - "token": "eip155:137/erc20:0x2791bca1f2de4661ed88a30c99a7a9449aa84174", - "rates": {"fiat:usd": "0.99"}, - "timestamp": tenHoursAgo, - "chainId": 137, - "initiatedBy": "test-bar-uid", - }, - { - "amount": "1500000000", - "from": "eip155:eoa:0xddcf208f219a6e6af072f2cfdc615b2c1805f98e", - "token": "eip155:137/erc20:0x2791bca1f2de4661ed88a30c99a7a9449aa84174", - "rates": {"fiat:usd": "0.99"}, - "timestamp": twentyHoursAgo, - "chainId": 137, - "initiatedBy": "test-alice-uid", - }, - ]}) + spendingLimitByUserGroupReq = object.union(spendingLimitReq, { + "principal": {"userId": "test-bar-uid"}, + "feeds": [ + { + "source": "armory/price-feed", + "sig": {}, + "data": { + "eip155:137/slip44:966": { + "fiat:usd": "0.99", + "fiat:eur": "1.10", + }, + "eip155:137/erc20:0x2791bca1f2de4661ed88a30c99a7a9449aa84174": { + "fiat:usd": "0.99", + "fiat:eur": "1.10", + }, + }, + }, + { + "source": "armory/historical-transfer-feed", + "data": [ + { + "amount": "3051000000", + "from": "eip155:eoa:0xddcf208f219a6e6af072f2cfdc615b2c1805f98e", + "token": "eip155:137/erc20:0x2791bca1f2de4661ed88a30c99a7a9449aa84174", + "rates": {"fiat:usd": "0.99"}, + "timestamp": elevenHoursAgo, + "chainId": 137, + "initiatedBy": "test-alice-uid", + }, + { + "amount": "2000000000", + "from": "eip155:eoa:0xddcf208f219a6e6af072f2cfdc615b2c1805f98e", + "token": "eip155:137/erc20:0x2791bca1f2de4661ed88a30c99a7a9449aa84174", + "rates": {"fiat:usd": "0.99"}, + "timestamp": tenHoursAgo, + "chainId": 137, + "initiatedBy": "test-bar-uid", + }, + { + "amount": "1500000000", + "from": "eip155:eoa:0xddcf208f219a6e6af072f2cfdc615b2c1805f98e", + "token": "eip155:137/erc20:0x2791bca1f2de4661ed88a30c99a7a9449aa84174", + "rates": {"fiat:usd": "0.99"}, + "timestamp": twentyHoursAgo, + "chainId": 137, + "initiatedBy": "test-alice-uid", + }, + ], + }, + ], + }) res = forbid[{"policyId": "spendingLimitByUserGroup"}] with input as spendingLimitByUserGroupReq with data.entities as entities @@ -82,33 +104,52 @@ test_spendingLimitByUserGroup { test_spendingLimitByWalletGroup { spendingLimitByWalletGroupReq = object.union(spendingLimitReq, { "principal": {"userId": "test-bar-uid"}, - "transfers": [ - { - "amount": "3000000000", - "from": "eip155:eoa:0xbbbb208f219a6e6af072f2cfdc615b2c1805f98e", - "token": "eip155:137/erc20:0x2791bca1f2de4661ed88a30c99a7a9449aa84174", - "rates": {"fiat:usd": "0.99"}, - "timestamp": elevenHoursAgo, - "chainId": 137, - "initiatedBy": "test-alice-uid", - }, + "feeds": [ { - "amount": "2000000000", - "from": "eip155:eoa:0xddcf208f219a6e6af072f2cfdc615b2c1805f98e", - "token": "eip155:137/erc20:0x2791bca1f2de4661ed88a30c99a7a9449aa84174", - "rates": {"fiat:usd": "0.99"}, - "timestamp": tenHoursAgo, - "chainId": 137, - "initiatedBy": "test-bar-uid", + "source": "armory/price-feed", + "sig": {}, + "data": { + "eip155:137/slip44:966": { + "fiat:usd": "0.99", + "fiat:eur": "1.10", + }, + "eip155:137/erc20:0x2791bca1f2de4661ed88a30c99a7a9449aa84174": { + "fiat:usd": "0.99", + "fiat:eur": "1.10", + }, + }, }, { - "amount": "1500000000", - "from": "eip155:eoa:0xddcf208f219a6e6af072f2cfdc615b2c1805f98e", - "token": "eip155:137/erc20:0x2791bca1f2de4661ed88a30c99a7a9449aa84174", - "rates": {"fiat:usd": "0.99"}, - "timestamp": twentyHoursAgo, - "chainId": 137, - "initiatedBy": "test-alice-uid", + "source": "armory/historical-transfer-feed", + "data": [ + { + "amount": "3000000000", + "from": "eip155:eoa:0xbbbb208f219a6e6af072f2cfdc615b2c1805f98e", + "token": "eip155:137/erc20:0x2791bca1f2de4661ed88a30c99a7a9449aa84174", + "rates": {"fiat:usd": "0.99"}, + "timestamp": elevenHoursAgo, + "chainId": 137, + "initiatedBy": "test-alice-uid", + }, + { + "amount": "2000000000", + "from": "eip155:eoa:0xddcf208f219a6e6af072f2cfdc615b2c1805f98e", + "token": "eip155:137/erc20:0x2791bca1f2de4661ed88a30c99a7a9449aa84174", + "rates": {"fiat:usd": "0.99"}, + "timestamp": tenHoursAgo, + "chainId": 137, + "initiatedBy": "test-bar-uid", + }, + { + "amount": "1500000000", + "from": "eip155:eoa:0xddcf208f219a6e6af072f2cfdc615b2c1805f98e", + "token": "eip155:137/erc20:0x2791bca1f2de4661ed88a30c99a7a9449aa84174", + "rates": {"fiat:usd": "0.99"}, + "timestamp": twentyHoursAgo, + "chainId": 137, + "initiatedBy": "test-alice-uid", + }, + ], }, ], }) diff --git a/apps/authz/src/opa/rego/input.json b/apps/authz/src/opa/rego/input.json index 0189191f6..be6327a53 100644 --- a/apps/authz/src/opa/rego/input.json +++ b/apps/authz/src/opa/rego/input.json @@ -46,64 +46,74 @@ "pubKey": "0xab88c8785D0C00082dE75D801Fcb1d5066a6311e" } ], - "transfers": [ + "feeds": [ { - "amount": "3000000000", - "from": "eip155:137:0x90d03a8971a2faa19a9d7ffdcbca28fe826a289b", - "to": "eip155:137:0x08a08d0504d4f3363a5b7fda1f5fff1c7bca8ad4", - "chainId": 137, - "token": "eip155:137/slip44:966", - "rates": { - "fiat:usd": "0.99", - "fiat:eur": "1.10" - }, - "initiatedBy": "matt@narval.xyz", - "timestamp": 1705934992613 + "source": "armory/price-feed", + "sig": {}, + "data": { + "eip155:137/slip44:966": { + "fiat:usd": "0.99", + "fiat:eur": "1.10" + } + } }, { - "amount": "2000000000", - "from": "eip155:137:0x90d03a8971a2faa19a9d7ffdcbca28fe826a289b", - "to": "eip155:137:0x08a08d0504d4f3363a5b7fda1f5fff1c7bca8ad4", - "chainId": 137, - "token": "eip155:137/slip44:966", - "rates": { - "fiat:usd": "0.99", - "fiat:eur": "1.10" - }, - "initiatedBy": "matt@narval.xyz", - "timestamp": 1705934992613 - }, - { - "amount": "1500000000", - "from": "eip155:137:0x90d03a8971a2faa19a9d7ffdcbca28fe826a289b", - "to": "eip155:137:0x08a08d0504d4f3363a5b7fda1f5fff1c7bca8ad4", - "chainId": 137, - "token": "eip155:137/slip44:966", - "rates": { - "fiat:usd": "0.99", - "fiat:eur": "1.10" - }, - "initiatedBy": "matt@narval.xyz", - "timestamp": 1705934992613 - }, - { - "amount": "1000000000", - "from": "eip155:137:0x90d03a8971a2faa19a9d7ffdcbca28fe826a289b", - "to": "eip155:137:0x08a08d0504d4f3363a5b7fda1f5fff1c7bca8ad4", - "chainId": 137, - "token": "eip155:137/slip44:966", - "rates": { - "fiat:usd": "0.99", - "fiat:eur": "1.10" - }, - "initiatedBy": "matt@narval.xyz", - "timestamp": 1705934992613 - } - ], - "prices": { - "eip155:137/slip44:966": { - "fiat:usd": "0.99", - "fiat:eur": "1.10" + "source": "armory/historical-transfer-feed", + "sig": {}, + "data": [ + { + "amount": "3000000000", + "from": "eip155:137:0x90d03a8971a2faa19a9d7ffdcbca28fe826a289b", + "to": "eip155:137:0x08a08d0504d4f3363a5b7fda1f5fff1c7bca8ad4", + "chainId": 137, + "token": "eip155:137/slip44:966", + "rates": { + "fiat:usd": "0.99", + "fiat:eur": "1.10" + }, + "initiatedBy": "matt@narval.xyz", + "timestamp": 1705934992613 + }, + { + "amount": "2000000000", + "from": "eip155:137:0x90d03a8971a2faa19a9d7ffdcbca28fe826a289b", + "to": "eip155:137:0x08a08d0504d4f3363a5b7fda1f5fff1c7bca8ad4", + "chainId": 137, + "token": "eip155:137/slip44:966", + "rates": { + "fiat:usd": "0.99", + "fiat:eur": "1.10" + }, + "initiatedBy": "matt@narval.xyz", + "timestamp": 1705934992613 + }, + { + "amount": "1500000000", + "from": "eip155:137:0x90d03a8971a2faa19a9d7ffdcbca28fe826a289b", + "to": "eip155:137:0x08a08d0504d4f3363a5b7fda1f5fff1c7bca8ad4", + "chainId": 137, + "token": "eip155:137/slip44:966", + "rates": { + "fiat:usd": "0.99", + "fiat:eur": "1.10" + }, + "initiatedBy": "matt@narval.xyz", + "timestamp": 1705934992613 + }, + { + "amount": "1000000000", + "from": "eip155:137:0x90d03a8971a2faa19a9d7ffdcbca28fe826a289b", + "to": "eip155:137:0x08a08d0504d4f3363a5b7fda1f5fff1c7bca8ad4", + "chainId": 137, + "token": "eip155:137/slip44:966", + "rates": { + "fiat:usd": "0.99", + "fiat:eur": "1.10" + }, + "initiatedBy": "matt@narval.xyz", + "timestamp": 1705934992613 + } + ] } - } + ] } diff --git a/apps/authz/src/opa/rego/lib/criteria/intent/amount.rego b/apps/authz/src/opa/rego/lib/criteria/intent/amount.rego index 6eec31617..844cae26e 100644 --- a/apps/authz/src/opa/rego/lib/criteria/intent/amount.rego +++ b/apps/authz/src/opa/rego/lib/criteria/intent/amount.rego @@ -11,7 +11,7 @@ intentAmount(currency) = result { currency != wildcard amount = to_number(input.intent.amount) token = input.intent.token - price = to_number(input.prices[token][currency]) + price = to_number(priceFeed[token][currency]) result = amount * price } @@ -19,7 +19,7 @@ intentAmount(currency) = result { currency != wildcard amount = to_number(input.intent.amount) contract = input.intent.contract - price = to_number(input.prices[contract][currency]) + price = to_number(priceFeed[contract][currency]) result = amount * price } diff --git a/apps/authz/src/opa/rego/lib/criteria/spendingLimit.rego b/apps/authz/src/opa/rego/lib/criteria/spendingLimit.rego index 356cfd8a5..a5e624a0f 100644 --- a/apps/authz/src/opa/rego/lib/criteria/spendingLimit.rego +++ b/apps/authz/src/opa/rego/lib/criteria/spendingLimit.rego @@ -134,7 +134,7 @@ checkSpendingLimit(params) { amount = intentAmount(currency) spendings = sum([spending | - transfer = input.transfers[_] + transfer = transferFeed[_] # filter by tokens checkSpendingCondition(transfer.token, filters.tokens) diff --git a/apps/authz/src/opa/rego/lib/criteria/transactionRequest/gas.rego b/apps/authz/src/opa/rego/lib/criteria/transactionRequest/gas.rego index d2089e270..cae57a39e 100644 --- a/apps/authz/src/opa/rego/lib/criteria/transactionRequest/gas.rego +++ b/apps/authz/src/opa/rego/lib/criteria/transactionRequest/gas.rego @@ -13,7 +13,7 @@ gasFeeAmount(currency) = result { currency != wildcard chainId = numberToString(input.transactionRequest.chainId) token = chainAssetId[chainId] - price = to_number(input.prices[token][currency]) + price = to_number(priceFeed[token][currency]) result = gasFee * price } diff --git a/apps/authz/src/opa/rego/lib/main.rego b/apps/authz/src/opa/rego/lib/main.rego index b8aee8ff7..32997920f 100644 --- a/apps/authz/src/opa/rego/lib/main.rego +++ b/apps/authz/src/opa/rego/lib/main.rego @@ -33,6 +33,18 @@ chainAssetId = { "43114": "eip155:43114/slip44:9000", } +priceFeed = result { + feed = input.feeds[_] + feed.source == "armory/price-feed" + result = feed.data +} + +transferFeed = result { + feed = input.feeds[_] + feed.source == "armory/historical-transfer-feed" + result = feed.data +} + default evaluate = { "permit": false, "reasons": set(), diff --git a/apps/orchestration/src/orchestration.constant.ts b/apps/orchestration/src/orchestration.constant.ts index d1e32633d..331a054fd 100644 --- a/apps/orchestration/src/orchestration.constant.ts +++ b/apps/orchestration/src/orchestration.constant.ts @@ -23,7 +23,7 @@ export const AUTHORIZATION_REQUEST_PROCESSING_QUEUE_BACKOFF: BackoffOptions = { // export const ASSET_ID_MAINNET_USDC: AssetId = 'eip155:1/erc20:0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48' -export const ASSET_ID_POLYGON_USDC: AssetId = 'eip155:1/erc20:0x2791bca1f2de4661ed88a30c99a7a9449aa84174' +export const ASSET_ID_POLYGON_USDC: AssetId = 'eip155:137/erc20:0x2791bca1f2de4661ed88a30c99a7a9449aa84174' export const FIAT_ID_USD: FiatId = 'fiat:usd' diff --git a/apps/orchestration/src/policy-engine/policy-engine.module.ts b/apps/orchestration/src/policy-engine/policy-engine.module.ts index 67d1c4d3a..279997fc6 100644 --- a/apps/orchestration/src/policy-engine/policy-engine.module.ts +++ b/apps/orchestration/src/policy-engine/policy-engine.module.ts @@ -11,7 +11,7 @@ import { BullAdapter } from '@bull-board/api/bullAdapter' import { BullBoardModule } from '@bull-board/nestjs' import { HttpModule } from '@nestjs/axios' import { BullModule } from '@nestjs/bull' -import { ClassSerializerInterceptor, Logger, Module, OnApplicationBootstrap, ValidationPipe } from '@nestjs/common' +import { ClassSerializerInterceptor, Module, ValidationPipe } from '@nestjs/common' import { ConfigModule } from '@nestjs/config' import { APP_FILTER, APP_INTERCEPTOR, APP_PIPE } from '@nestjs/core' import { AuthorizationRequestService } from './core/service/authorization-request.service' @@ -64,14 +64,4 @@ import { AuthorizationRequestProcessingProducer } from './queue/producer/authori } ] }) -export class PolicyEngineModule implements OnApplicationBootstrap { - private logger = new Logger(PolicyEngineModule.name) - - constructor(private authzRequestProcessingProducer: AuthorizationRequestProcessingProducer) {} - - async onApplicationBootstrap() { - this.logger.log('Policy Engine module boot') - - await this.authzRequestProcessingProducer.onApplicationBootstrap() - } -} +export class PolicyEngineModule {} diff --git a/apps/orchestration/src/policy-engine/queue/producer/authorization-request-processing.producer.ts b/apps/orchestration/src/policy-engine/queue/producer/authorization-request-processing.producer.ts index c7f3b0bd2..781d6cf7b 100644 --- a/apps/orchestration/src/policy-engine/queue/producer/authorization-request-processing.producer.ts +++ b/apps/orchestration/src/policy-engine/queue/producer/authorization-request-processing.producer.ts @@ -10,7 +10,7 @@ import { } from '@app/orchestration/policy-engine/core/type/domain.type' import { AuthorizationRequestRepository } from '@app/orchestration/policy-engine/persistence/repository/authorization-request.repository' import { InjectQueue } from '@nestjs/bull' -import { Injectable, Logger } from '@nestjs/common' +import { Injectable, Logger, OnApplicationBootstrap } from '@nestjs/common' import { BackoffOptions, Job, Queue } from 'bull' type JobOption = { @@ -24,7 +24,7 @@ export const DEFAULT_JOB_OPTIONS: JobOption = { } @Injectable() -export class AuthorizationRequestProcessingProducer { +export class AuthorizationRequestProcessingProducer implements OnApplicationBootstrap { private logger = new Logger(AuthorizationRequestProcessingProducer.name) constructor( diff --git a/apps/orchestration/src/price/core/service/__test__/unit/price.service.spec.ts b/apps/orchestration/src/price/core/service/__test__/unit/price.service.spec.ts index 9313eee82..adb4bb6b1 100644 --- a/apps/orchestration/src/price/core/service/__test__/unit/price.service.spec.ts +++ b/apps/orchestration/src/price/core/service/__test__/unit/price.service.spec.ts @@ -3,7 +3,7 @@ import { PriceException } from '@app/orchestration/price/core/exception/price.ex import { PriceService } from '@app/orchestration/price/core/service/price.service' import { CoinGeckoClient } from '@app/orchestration/price/http/client/coin-gecko/coin-gecko.client' import { CoinGeckoAssetRepository } from '@app/orchestration/price/persistence/repository/coin-gecko-asset.repository' -import { getAssetId } from '@narval/authz-shared' +import { AssetType, getAddress, getAssetId, toAssetId } from '@narval/authz-shared' import { Test, TestingModule } from '@nestjs/testing' import { mock } from 'jest-mock-extended' @@ -86,5 +86,32 @@ describe(PriceService.name, () => { }) ).rejects.toThrow(PriceException) }) + + describe('when requesting for prices of the same address on differet chains', () => { + const xDao = getAddress('0x71eeba415a523f5c952cc2f06361d5443545ad28') + const xDaoMainnet = toAssetId({ chainId: 1, address: xDao, assetType: AssetType.ERC20 }) + const xDaoPolygon = toAssetId({ chainId: 137, address: xDao, assetType: AssetType.ERC20 }) + const xDaoDollarPrice = 0.6069648381377055 + + it('responds with prices for both asset ids', async () => { + jest.spyOn(coinGeckoClientMock, 'getSimplePrice').mockResolvedValue({ + xdao: { usd: xDaoDollarPrice } + }) + + const prices = await service.getPrices({ + from: [xDaoMainnet, xDaoPolygon], + to: [FIAT_ID_USD] + }) + + expect(prices).toEqual({ + [xDaoMainnet]: { + [FIAT_ID_USD]: xDaoDollarPrice + }, + [xDaoPolygon]: { + [FIAT_ID_USD]: xDaoDollarPrice + } + }) + }) + }) }) }) diff --git a/apps/orchestration/src/price/core/service/price.service.ts b/apps/orchestration/src/price/core/service/price.service.ts index 792756a2c..3098ad7a1 100644 --- a/apps/orchestration/src/price/core/service/price.service.ts +++ b/apps/orchestration/src/price/core/service/price.service.ts @@ -39,7 +39,7 @@ export class PriceService { } }) - const prices = this.buildPrices(simplePrice) + const prices = this.buildPrices(options.from, simplePrice) this.logger.log('Received prices', { options, @@ -49,23 +49,55 @@ export class PriceService { return prices } - private buildPrices(prices: SimplePrice): Prices { - return Object.keys(prices).reduce((acc, coinId) => { - const assetId = this.coinGeckoAssetRepository.getAssetId(coinId) + // TODO (@wcalderipe, 05/02/24): Move everything related to CoinGecko to a + // Price repository if one day we have another price source or cache. - if (assetId) { - return { + private buildPrices(assetIds: AssetId[], simplePrice: SimplePrice): Prices { + return ( + this.getAssetsPriceInformation(simplePrice) + // Assets with the same address on different chains have the same source ID + // but different asset ID. This filter removes unrequested asset IDs. + .filter(({ sourceAssetId }) => assetIds.includes(sourceAssetId)) + .reduce((acc, price) => { + return { + ...acc, + [price.sourceAssetId]: price.values + } + }, {}) + ) + } + + private getAssetsPriceInformation(simplePrice: SimplePrice) { + return Object.keys(simplePrice).reduce((acc, coinId) => { + const sourceAssetIds = this.coinGeckoAssetRepository.getAssetIds(coinId) + + if (sourceAssetIds) { + return [ ...acc, - [assetId]: Object.keys(prices[coinId]).reduce((result, fiat) => { - return { - ...result, - [`fiat:${fiat}`]: prices[coinId].usd - } - }, {}) - } + ...sourceAssetIds.map((sourceAssetId) => ({ + sourceAssetId, + coinId, + values: this.getValues(simplePrice, coinId) + })) + ] } return acc + }, [] as { sourceAssetId: AssetId; coinId: string; values: unknown }[]) + } + + private getValues(simplePrice: SimplePrice, coinId: string) { + return Object.keys(simplePrice[coinId]).reduce((prices, price) => { + const value = simplePrice[coinId][price as keyof (typeof simplePrice)[string]] + + if (price) { + return { + ...prices, + [`fiat:${price}`]: value + } + } + + return prices }, {}) } } diff --git a/apps/orchestration/src/price/persistence/repository/coin-gecko-asset.repository.ts b/apps/orchestration/src/price/persistence/repository/coin-gecko-asset.repository.ts index ccbe2c577..f7525267c 100644 --- a/apps/orchestration/src/price/persistence/repository/coin-gecko-asset.repository.ts +++ b/apps/orchestration/src/price/persistence/repository/coin-gecko-asset.repository.ts @@ -17,17 +17,21 @@ export class CoinGeckoAssetRepository { return CoinGeckoAssetIdIndex[assetId as keyof typeof CoinGeckoAssetIdIndex] || null } - getAssetId(sourceId: string): string | null { + getAssetIds(sourceId: string): AssetId[] | null { const chain = findChain(({ coinGecko }) => coinGecko.coinId === sourceId) if (chain) { - return chain.coin.id + return [chain.coin.id] } - for (const key in CoinGeckoAssetIdIndex) { - if (CoinGeckoAssetIdIndex[key as keyof typeof CoinGeckoAssetIdIndex] === sourceId) { - return getAssetId(key) - } + // Assets with the same address on different chains have the same source ID + // but different asset ID. + const assetIds = Object.keys(CoinGeckoAssetIdIndex).filter( + (assetId) => CoinGeckoAssetIdIndex[assetId as keyof typeof CoinGeckoAssetIdIndex] === sourceId + ) + + if (assetIds.length) { + return assetIds.map(getAssetId) } return null