Skip to content

Commit

Permalink
debugging the false positive
Browse files Browse the repository at this point in the history
  • Loading branch information
Ptroger committed Sep 3, 2024
1 parent 641028b commit 9652202
Show file tree
Hide file tree
Showing 11 changed files with 221 additions and 76 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,8 @@ export class OpenPolicyAgentEngine implements Engine<OpenPolicyAgentEngine> {
// an array of results with an inner result. We perform a typecast here to
// satisfy TypeScript compiler. Later, we parse the schema a few lines
// below to ensure type-safety for data coming from external sources.

console.log('###input ', JSON.stringify(input, null, 2))
const results = (await this.opa.evaluate(input, POLICY_ENTRYPOINT)) as { result: unknown }[]

const parse = z.array(resultSchema).safeParse(results.map(({ result }) => result))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,12 @@ test_checkIntentAmountValue {
checkIntentAmount({"currency": "fiat:usd", "operator": operators.greaterThanOrEqual, "value": oneMaticValue}) with input as requestWithEip1559Transaction with data.entities as entities
checkIntentAmount({"currency": "fiat:usd", "operator": operators.lessThanOrEqual, "value": oneMaticValue}) with input as requestWithEip1559Transaction with data.entities as entities
}

test_checkIntentAmount2 {
requ := object.union(requestWithEip1559Transaction, {
"intent": object.union(requestWithEip1559Transaction.intent, {
"amount": "100000000000000000000000000000000000000000000000000000000000000000000",
}),
})
checkIntentAmount({"operator": operators.greaterThan, "value": "10" }) with input as requ with data.entities as entities
}
Original file line number Diff line number Diff line change
Expand Up @@ -219,4 +219,18 @@ permit[{"policyId": "spendingLimitWithRange"}] = reason {
"approvalsSatisfied": [],
"approvalsMissing": [],
}
}

pemrmit[{"policyId": "minimalSpendingLimit"}] = reason {
checkSpendingLimit({
"limit": "1",
"operator": "lte",
})

reason = {
"type": "permit",
"policyId": "minimalSpendingLimit",
"approvalsSatisfied": [],
"approvalsMissing": [],
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -373,3 +373,51 @@ test_spendingLimitWithRange {
"type": "permit",
}
}

test_spendingLimitTooHighForRange {
spendingLimitTooHighForRangeReq = {
"action": "signTransaction",
"principal": {"userId": "test-alice-uid"},
"resource": {"uid": "eip155:eoa:0xddcf208f219a6e6af072f2cfdc615b2c1805f98e"},
"approvals": [
{
"id": "0x6af10b6d5024963972ba832486ea1ae29f1b99cb1191abe444b52e98c69f7487",
"userId": "test-antoine-user-uid",
"key": {
"kty": "EC",
"alg": "ES256K",
"kid": "0x6af10b6d5024963972ba832486ea1ae29f1b99cb1191abe444b52e98c69f7487",
"crv": "secp256k1",
"x": "QwUuAC2s22VKwoS5uPTZgcTN_ztkwt9VWKRae3bikEQ",
"y": "lZgwfE7ZDz9af9_PZxq9B7pVwAarfIaFESATYp-Q7Uk"
}
}
],
"intent": {
"to": "eip155:1:0x76d1b7f9b3f69c435eef76a98a415332084a856f",
"from": "eip155:1:0x0301e2724a40e934cce3345928b88956901aa127",
"type": "transferNative",
"amount": "1000000000000000000000",
"token": "eip155:1/slip44:60"
},
"transactionRequest": {
"chainId": 1,
"from": "0x0301e2724a40e934cce3345928b88956901aa127",
"to": "0x76d1b7f9b3f69c435eef76a98a415332084a856f",
"value": "0x8AC7230489E80000"
},
"feeds": [
{
"source": "armory/price-feed",
"sig": "eyJhbGciOiJFSVAxOTEiLCJraWQiOiIweDBjNjIwZjRiYzhlOTMxMTBiZDljZDc5ZTVkNjM3YTI0MGQ1NWUwZjI3MzNmZDdlOTViNzM0N2QzYjA2MjMxZmMiLCJ0eXAiOiJKV1QifQ.eyJkYXRhIjoiMHg0NDEzNmZhMzU1YjM2NzhhMTE0NmFkMTZmN2U4NjQ5ZTk0ZmI0ZmMyMWZlNzdlODMxMGMwNjBmNjFjYWFmZjhhIiwiaWF0IjoxNzI1Mjg3MjEwLCJpc3MiOiJodHRwczovL2FybW9yeS5uYXJ2YWwueHl6Iiwic3ViIjoiMHg2OTY2MzEzNDAwMTZGY2FFMmJCYmEyREQ3QmYxZjFBMkY4ZTJBNTRmIn0.jgr7A-dB_tHX42IDG0Cx8fE7Mtu1xBb5g3oW3qdbRl4-j4XmOdZjdOS6m73yNQ-Dz-RbCxQrbSk3zwaQODrHJBw",
"data": {}
},
{
"source": "armory/historical-transfer-feed",
"sig": "eyJhbGciOiJFSVAxOTEiLCJraWQiOiIweDY2YTY3YWI1ODI2OWY0NGFhYmE2NDUxNzZmNGI5M2Y1ZTY3MTU2N2I0NTQ0MjkwZTE5OGU5ODYxYzM0OTNkMmQiLCJ0eXAiOiJKV1QifQ.eyJkYXRhIjoiMHg0ZjUzY2RhMThjMmJhYTBjMDM1NGJiNWY5YTNlY2JlNWVkMTJhYjRkOGUxMWJhODczYzJmMTExNjEyMDJiOTQ1IiwiaWF0IjoxNzI1Mjg3MjEwLCJpc3MiOiJodHRwczovL2FybW9yeS5uYXJ2YWwueHl6Iiwic3ViIjoiMHhkOWYzYjNhMDY3ZmU0NmI2M0U0YjBkZUZlQjJBMGI3YWU2N2E4MjIxIn0.jzwVozBI4oOaBUQn3b1TdDlg1I9IN29xVwx2xy3MujN53TuZIweIgTcBqmRtnppRqAdZUo51R7k8Y6PDO8j-gBw",
"data": []
}
]
}
not permit[{"policyId": "minimalSpendingLimit"}] with input as spendingLimitTooHighForRangeReq with data.entities as {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,10 @@ calculateCurrentSpendings(params) = result {
conditions = object.union(spendingWildcardConditions, params)
timeWindow = conditions.timeWindow
filters = conditions.filters
print("filters: ", filters)
print("timeWindow: ", timeWindow)
print("\n\ntransferFeed", transferFeed)
print("\n\nintentTransferObjects", intentTransferObjects)
transfers = array.concat(transferFeed, intentTransferObjects)

result = sum([spending |
Expand Down Expand Up @@ -107,7 +111,9 @@ calculateCurrentSpendings(params) = result {

checkSpendingLimit(params) {
conditions = object.union(spendingWildcardConditions, params)
print("conditions: ", conditions)
spendings = calculateCurrentSpendings(conditions)
print("spendings: ", spendings)
operator = conditions.operator
limit = to_number(conditions.limit)

Expand Down
5 changes: 4 additions & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,10 @@ services:
environment:
ALLOW_EMPTY_PASSWORD: 'true'
logging:
driver: none
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
volumes:
- ./.data/redis:/data

Expand Down
2 changes: 1 addition & 1 deletion examples/approvals-by-spending-limit/4-transfer-a-to-b.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ const transactionRequest = {
from: '0x0301e2724a40E934Cce3345928b88956901aA127', // Account A
to: '0x76d1b7f9b3F69C435eeF76a98A415332084A856F', // Account B
chainId: 1,
value: '0x429D069189E0000', // 0.3 ETH
value: '0x7FFFFFFFFFFFFDFF', // 0.3 ETH
gas: 123n,
maxFeePerGas: 789n,
maxPriorityFeePerGas: 456n,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/* eslint-disable jest/consistent-test-it */
import { Action, Decision, entitiesSchema, FIXTURE, policySchema, Request } from '@narval/policy-engine-shared'
import { Action, Decision, entitiesSchema, FIXTURE, policySchema, Request, ValueOperators } from '@narval/policy-engine-shared'
import { v4 } from 'uuid'
import defaultEntities from '../../../../resource/entity/default.json'
import tieredEthTransfer from '../../../../resource/policy/set/tiered-eth-transfer.json'
Expand Down Expand Up @@ -36,10 +36,14 @@ describe('tiered approvals and spending limits', () => {
// Generate a new client ID for each test run, otherwise historical data with persist between tests if using a long-lived db.
const clientId = v4()

// 18446744073709551615
// 9223372036854776000
beforeAll(async () => {
const entities = entitiesSchema.parse(defaultEntities)

const policies = tieredEthTransfer.map((policy) => policySchema.parse(policy))
const policies = tieredEthTransfer.map((policy) => {
return policySchema.parse(policy)
})

await createClient(systemManagerHexPk, {
clientId,
Expand All @@ -50,64 +54,57 @@ describe('tiered approvals and spending limits', () => {
clientId,
host: getAuthHost(),
entities,
policies
})
})

it('permits member to transfer less than or equal to 1 ETH', async () => {
const { authClient } = await buildAuthClient(antoinePrivateKey, {
host: getAuthHost(),
clientId
policies: [ {
"id": "tier1-low-value-transfers",
"description": "Permit members to transfer up to 0.00000000001 ETH per day without approval",
"when": [
{
"criterion": "checkPrincipalRole",
"args": [
"member"
]
},
{
"criterion": "checkIntentToken",
"args": [
"eip155:1/slip44:60"
]
},
{
"criterion": "checkIntentAmount",
"args": {
"value": "1",
"operator": "lte" as ValueOperators
}
}
],
"then": "permit"
}]
})

const lowValueRequest = genNonce({
...request,
transactionRequest: {
...request.transactionRequest,
value: '0xDE0B6B3A7640000' // 0.3 ETH
}
})

const response = await authClient.requestAccessToken(lowValueRequest)
expect(response).toMatchObject({ value: expect.any(String) })
})

it('requires manager approval for transfers between 1 and 10 ETH', async () => {
expect.assertions(2)

const { authClient: managerClient } = await buildAuthClient(carolPrivateKey, {
host: getAuthHost(),
clientId
})

const { authClient } = await buildAuthClient(antoinePrivateKey, {
host: getAuthHost(),
clientId
})

const mediumValueRequest = genNonce({
...request,
transactionRequest: {
...request.transactionRequest,
value: '0x4563918244F40000' // 5 ETH
}
})

const res = await authClient.authorize(mediumValueRequest)
expect(res.decision).toEqual(Decision.CONFIRM)
// it('permits member to transfer less than or equal to 1 ETH', async () => {
// const { authClient } = await buildAuthClient(antoinePrivateKey, {
// host: getAuthHost(),
// clientId
// })

if (res.decision === Decision.CONFIRM) {
await managerClient.approve(res.authId)
// const lowValueRequest = genNonce({
// ...request,
// transactionRequest: {
// ...request.transactionRequest,
// value: '0xDE0B6B3A7640000' // 0.3 ETH
// }
// })

const accessToken = await authClient.getAccessToken(res.authId)
expect(accessToken).toMatchObject({ value: expect.any(String) })
}
})
// const response = await authClient.requestAccessToken(lowValueRequest)
// expect(response).toMatchObject({ value: expect.any(String) })
// })

// it('requires admin approval for transfers between 10 and 100 ETH', async () => {
// it('requires manager approval for transfers between 1 and 10 ETH', async () => {
// expect.assertions(2)

// const { authClient: adminClient } = await buildAuthClient(alicePrivateKey, {
// const { authClient: managerClient } = await buildAuthClient(carolPrivateKey, {
// host: getAuthHost(),
// clientId
// })
Expand All @@ -117,25 +114,62 @@ describe('tiered approvals and spending limits', () => {
// clientId
// })

// const highValueRequest = genNonce({
// const mediumValueRequest = genNonce({
// ...request,
// transactionRequest: {
// ...request.transactionRequest,
// value: '0x8AC7230489E80000' // 10 ETH
// value: '0x4563918244F40000' // 5 ETH
// }
// })

// const res = await authClient.authorize(highValueRequest)
// const res = await authClient.authorize(mediumValueRequest)
// expect(res.decision).toEqual(Decision.CONFIRM)

// if (res.decision === Decision.CONFIRM) {
// await adminClient.approve(res.authId)
// await managerClient.approve(res.authId)

// const accessToken = await authClient.getAccessToken(res.authId)
// expect(accessToken).toMatchObject({ value: expect.any(String) })
// }
// })

it('requires admin approval for transfers between 10 and 100 ETH', async () => {
expect.assertions(1)

const { authClient: adminClient } = await buildAuthClient(alicePrivateKey, {
host: getAuthHost(),
clientId
})

const { authClient } = await buildAuthClient(antoinePrivateKey, {
host: getAuthHost(),
clientId
})

const highValueRequest = genNonce({
action: Action.SIGN_TRANSACTION,
nonce: 'test-nonce-4',
transactionRequest: {
from: '0x0301e2724a40E934Cce3345928b88956901aA127',
to: '0x76d1b7f9b3F69C435eeF76a98A415332084A856F',
value: '0x7FFFFFFFFFFFFDFF', // 10 ETH
chainId: 1
},
resourceId: 'eip155:eoa:0x0301e2724a40e934cce3345928b88956901aa127'
})

const res = await authClient.authorize(highValueRequest)
expect(res.decision).toEqual(Decision.FORBID)

if (res.decision === Decision.CONFIRM) {
await adminClient.approve(res.authId)

const accessToken = await authClient.getAccessToken(res.authId)
expect(accessToken).toMatchObject({ value: expect.any(String) })
}
})


// it('requires two admin approvals for transfers above 100 ETH', async () => {
// expect.assertions(3)

Expand Down
1 change: 1 addition & 0 deletions packages/armory-sdk/src/lib/__test__/util/setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { AuthAdminClient, AuthClient, AuthConfig } from '../../auth'
import { createHttpDataStore, DataStoreConfig, EntityStoreClient, PolicyStoreClient } from '../../data-store'
import { VaultAdminClient, VaultConfig } from '../../vault'


export const getAuthHost = () => 'http://localhost:3005'
export const getAuthAdminApiKey = () => 'armory-admin-api-key'

Expand Down
Loading

0 comments on commit 9652202

Please sign in to comment.