diff --git a/apps/authz/src/app/opa/rego/input.json b/apps/authz/src/app/opa/rego/input.json index 9a1531680..19a081e50 100644 --- a/apps/authz/src/app/opa/rego/input.json +++ b/apps/authz/src/app/opa/rego/input.json @@ -1,56 +1,107 @@ { "action": "signTransaction", - "principal": {"uid": "test-foo-uid"}, - "resource": {"uid": "eip155:eoa:0xddcf208f219a6e6af072f2cfdc615b2c1805f98e"}, + "principal": { "uid": "test-foo-uid" }, + "resource": { "uid": "eip155:eoa:0xddcf208f219a6e6af072f2cfdc615b2c1805f98e" }, "request": { - "type": "eip1559", - "chain_id": 137, - "max_fee_per_gas": "20000000000", - "max_priority_fee_per_gas": "3000000000", - "gas": "21000", - "nonce": 1, - "from": "0xddcf208f219a6e6af072f2cfdc615b2c1805f98e", - "to": "0xa45e21e9370ba031c5e1f47dedca74a7ce2ed7a3" + "type": "eip1559", + "chain_id": 137, + "max_fee_per_gas": "20000000000", + "max_priority_fee_per_gas": "3000000000", + "gas": "21000", + "nonce": 1, + "from": "0xddcf208f219a6e6af072f2cfdc615b2c1805f98e", + "to": "0xa45e21e9370ba031c5e1f47dedca74a7ce2ed7a3" }, "intent": { - "type": "transferToken", - "from": { - "uid": "eip155:eoa:0xddcf208f219a6e6af072f2cfdc615b2c1805f98e", - "address": "0xddcf208f219a6e6af072f2cfdc615b2c1805f98e" - }, - "to": { - "uid": "eip155:137:0xa45e21e9370ba031c5e1f47dedca74a7ce2ed7a3", - "chain_id": 137, - "address": "0xa45e21e9370ba031c5e1f47dedca74a7ce2ed7a3" - }, - "amount": "1000000000000000000", - "token": { - "uid": "eip155:137/erc20:0x2791bca1f2de4661ed88a30c99a7a9449aa84174", - "address": "0x2791bca1f2de4661ed88a30c99a7a9449aa84174", - "chainId": 137, - "classification": "internal" - } + "type": "transferToken", + "from": { + "uid": "eip155:eoa:0xddcf208f219a6e6af072f2cfdc615b2c1805f98e", + "address": "0xddcf208f219a6e6af072f2cfdc615b2c1805f98e" + }, + "to": { + "uid": "eip155:137:0xa45e21e9370ba031c5e1f47dedca74a7ce2ed7a3", + "chain_id": 137, + "address": "0xa45e21e9370ba031c5e1f47dedca74a7ce2ed7a3" + }, + "amount": "1000000000000000000", + "token": { + "uid": "eip155:137/erc20:0x2791bca1f2de4661ed88a30c99a7a9449aa84174", + "address": "0x2791bca1f2de4661ed88a30c99a7a9449aa84174", + "chainId": 137, + "classification": "internal" + } }, "signatures": [ + { + "signer": "test-bob-uid", + "hash": "0x894ee391f2fb86469042159c46084add956d1d1f997bb4c43d9c8d2a52970a615b790c416077ec5d199ede5ae0fc925859c80c52c5c74328e25d9e9d5195e3981c" + }, + { + "signer": "test-alice-uid", + "hash": "0x894ee391f2fb86469042159c46084add956d1d1f997bb4c43d9c8d2a52970a615b790c416077ec5d199ede5ae0fc925859c80c52c5c74328e25d9e9d5195e3981c" + }, + { + "signer": "test-foo-uid", + "hash": "0x894ee391f2fb86469042159c46084add956d1d1f997bb4c43d9c8d2a52970a615b790c416077ec5d199ede5ae0fc925859c80c52c5c74328e25d9e9d5195e3981c" + }, + { + "signer": "0xaaa8ee1cbaa1856f4550c6fc24abb16c5c9b2a43", + "hash": "0x894ee391f2fb86469042159c46084add956d1d1f997bb4c43d9c8d2a52970a615b790c416077ec5d199ede5ae0fc925859c80c52c5c74328e25d9e9d5195e3981c" + }, + { + "signer": "test-bar-uid", + "hash": "0x894ee391f2fb86469042159c46084add956d1d1f997bb4c43d9c8d2a52970a615b790c416077ec5d199ede5ae0fc925859c80c52c5c74328e25d9e9d5195e3981c" + } + ], + "tokens": { + "eip155:137/erc20:0x2791bca1f2de4661ed88a30c99a7a9449aa84174": { + "uid": "eip155:137/erc20:0x2791bca1f2de4661ed88a30c99a7a9449aa84174", + "address": "0x2791bca1f2de4661ed88a30c99a7a9449aa84174", + "symbol": "USDC", + "chain_id": 137, + "decimals": 6 + } + }, + "spendings": { + "source": "narval-spendings-feed", + "signature": "some-random-signature", + "data": [ { - "signer": "test-bob-uid", - "hash": "0x894ee391f2fb86469042159c46084add956d1d1f997bb4c43d9c8d2a52970a615b790c416077ec5d199ede5ae0fc925859c80c52c5c74328e25d9e9d5195e3981c" - }, - { - "signer": "test-alice-uid", - "hash": "0x894ee391f2fb86469042159c46084add956d1d1f997bb4c43d9c8d2a52970a615b790c416077ec5d199ede5ae0fc925859c80c52c5c74328e25d9e9d5195e3981c" + "amount": "3000", + "smallest_unit": "3000000000", + "token": "eip155:137/erc20:0x2791bca1f2de4661ed88a30c99a7a9449aa84174", + "rates": { "USD": "0.99" }, + "timestamp": 1705332968316, + "chain_id": 137, + "initiated_by": "0xaaa8ee1cbaa1856f4550c6fc24abb16c5c9b2a43" }, { - "signer": "test-foo-uid", - "hash": "0x894ee391f2fb86469042159c46084add956d1d1f997bb4c43d9c8d2a52970a615b790c416077ec5d199ede5ae0fc925859c80c52c5c74328e25d9e9d5195e3981c" + "amount": "3000", + "smallest_unit": "3000000000", + "token": "eip155:137/erc20:0x2791bca1f2de4661ed88a30c99a7a9449aa84174", + "rates": { "USD": "0.99" }, + "timestamp": 1705332971916, + "chain_id": 137, + "initiated_by": "0xaaa8ee1cbaa1856f4550c6fc24abb16c5c9b2a43" }, { - "signer": "0xaaa8ee1cbaa1856f4550c6fc24abb16c5c9b2a43", - "hash": "0x894ee391f2fb86469042159c46084add956d1d1f997bb4c43d9c8d2a52970a615b790c416077ec5d199ede5ae0fc925859c80c52c5c74328e25d9e9d5195e3981c" + "amount": "1500", + "smallest_unit": "1500000000", + "token": "eip155:137/erc20:0x2791bca1f2de4661ed88a30c99a7a9449aa84174", + "rates": { "USD": "0.99" }, + "timestamp": 1705332975516, + "chain_id": 137, + "initiated_by": "0xaaa8ee1cbaa1856f4550c6fc24abb16c5c9b2a43" }, { - "signer": "test-bar-uid", - "hash": "0x894ee391f2fb86469042159c46084add956d1d1f997bb4c43d9c8d2a52970a615b790c416077ec5d199ede5ae0fc925859c80c52c5c74328e25d9e9d5195e3981c" - } - ] + "amount": "1500", + "smallest_unit": "1500000000", + "token": "eip155:137/erc20:0x2791bca1f2de4661ed88a30c99a7a9449aa84174", + "rates": { "USD": "0.99" }, + "timestamp": 1705332935916, + "chain_id": 137, + "initiated_by": "0xaaa8ee1cbaa1856f4550c6fc24abb16c5c9b2a43" + } + ] + } } diff --git a/apps/authz/src/app/opa/rego/lib/criteria/accumulation.rego b/apps/authz/src/app/opa/rego/lib/criteria/accumulation.rego new file mode 100644 index 000000000..69c495239 --- /dev/null +++ b/apps/authz/src/app/opa/rego/lib/criteria/accumulation.rego @@ -0,0 +1,13 @@ +package main + +import future.keywords.in + +get_spending_amount(tokens, start) = result { + result := sum([usd_amount | + transfer := input.spendings.data[_] + transfer.initiated_by == principal.uid + transfer.timestamp >= start + transfer.token in tokens + usd_amount := to_number(transfer.smallest_unit) * to_number(transfer.rates.USD) + ]) +} diff --git a/apps/authz/src/app/opa/rego/lib/criteria/transfer_token.rego b/apps/authz/src/app/opa/rego/lib/criteria/transfer_token.rego index b89e47e86..455c8330c 100644 --- a/apps/authz/src/app/opa/rego/lib/criteria/transfer_token.rego +++ b/apps/authz/src/app/opa/rego/lib/criteria/transfer_token.rego @@ -2,28 +2,24 @@ package main import future.keywords.in -transfer_token_type := input.intent.type +transfer_token_type = input.intent.type -transfer_token_amount := input.intent.amount +transfer_token_amount = to_number(input.intent.amount) -transfer_token_address := result { +transfer_token_address = input.intent.native.address + +transfer_token_address = input.intent.token.address + +transfer_token_address = result { not input.intent.native.address result := input.intent.native } -transfer_token_address := result { - result := input.intent.native.address -} - -transfer_token_address := result { +transfer_token_address = result { not input.intent.token.address result := input.intent.token } -transfer_token_address := result { - result := input.intent.token.address -} - check_transfer_token_type(values) { values == wildcard } @@ -46,30 +42,30 @@ check_transfer_token_operation(operation) { check_transfer_token_operation(operation) { operation.operator == "eq" - to_number(operation.value) == to_number(transfer_token_amount) + to_number(operation.value) == transfer_token_amount } check_transfer_token_operation(operation) { operation.operator == "neq" - to_number(operation.value) != to_number(transfer_token_amount) + to_number(operation.value) != transfer_token_amount } check_transfer_token_operation(operation) { operation.operator == "gt" - to_number(operation.value) < to_number(transfer_token_amount) + to_number(operation.value) < transfer_token_amount } check_transfer_token_operation(operation) { operation.operator == "lt" - to_number(operation.value) > to_number(transfer_token_amount) + to_number(operation.value) > transfer_token_amount } check_transfer_token_operation(operation) { operation.operator == "gte" - to_number(operation.value) <= to_number(transfer_token_amount) + to_number(operation.value) <= transfer_token_amount } check_transfer_token_operation(operation) { operation.operator == "lte" - to_number(operation.value) >= to_number(transfer_token_amount) + to_number(operation.value) >= transfer_token_amount } diff --git a/apps/authz/src/app/opa/rego/lib/main_test.rego b/apps/authz/src/app/opa/rego/lib/main_test.rego index 8937e82dc..0205f76d8 100644 --- a/apps/authz/src/app/opa/rego/lib/main_test.rego +++ b/apps/authz/src/app/opa/rego/lib/main_test.rego @@ -3,6 +3,16 @@ package main import future.keywords.every import future.keywords.in +mock_now_s = 1630540800 + +twenty_hours_ago = mock_now_s - ((20 * 60) * 60) + +eleven_hours_ago = mock_now_s - ((11 * 60) * 60) + +ten_hours_ago = mock_now_s - ((10 * 60) * 60) + +nine_hours_ago = mock_now_s - ((9 * 60) * 60) + request = { "action": "signTransaction", "principal": {"uid": "test-bob-uid"}, @@ -54,6 +64,46 @@ request = { "hash": "0x894ee391f2fb86469042159c46084add956d1d1f997bb4c43d9c8d2a52970a615b790c416077ec5d199ede5ae0fc925859c80c52c5c74328e25d9e9d5195e3981c", }, ], + "tokens": {"eip155:137/erc20:0x2791bca1f2de4661ed88a30c99a7a9449aa84174": { + "uid": "eip155:137/erc20:0x2791bca1f2de4661ed88a30c99a7a9449aa84174", + "address": "0x2791bca1f2de4661ed88a30c99a7a9449aa84174", + "symbol": "USDC", + "chain_id": 137, + "decimals": 6, + }}, + "spendings": { + "source": "narval-spendings-feed", + "signature": "some-random-signature", + "data": [ + { + "amount": "3051", + "smallest_unit": "3051000000", + "token": "eip155:137/erc20:0x2791bca1f2de4661ed88a30c99a7a9449aa84174", + "rates": {"USD": "0.99"}, + "timestamp": eleven_hours_ago, + "chain_id": 137, + "initiated_by": "test-bob-uid", + }, + { + "amount": "2000", + "smallest_unit": "2000000000", + "token": "eip155:137/erc20:0x2791bca1f2de4661ed88a30c99a7a9449aa84174", + "rates": {"USD": "0.99"}, + "timestamp": ten_hours_ago, + "chain_id": 137, + "initiated_by": "test-bob-uid", + }, + { + "amount": "1500", + "smallest_unit": "1500000000", + "token": "eip155:137/erc20:0x2791bca1f2de4661ed88a30c99a7a9449aa84174", + "rates": {"USD": "0.99"}, + "timestamp": twenty_hours_ago, + "chain_id": 137, + "initiated_by": "test-bob-uid", + }, + ], + }, } entities = { @@ -159,14 +209,14 @@ test_is_principal_assigned_to_wallet { } test_principal { - res := principal with input as request + res = principal with input as request with data.entities as entities res == {"uid": "test-bob-uid", "role": "root"} } test_resource { - res := resource with input as request + res = resource with input as request with data.entities as entities res == { @@ -178,7 +228,7 @@ test_resource { } test_source { - res := source with input as request + res = source with input as request with data.entities as entities res == { @@ -190,7 +240,7 @@ test_source { } test_destination { - res := destination with input as request + res = destination with input as request with data.entities as entities res == { @@ -202,7 +252,7 @@ test_destination { } test_principal_groups { - groups := principal_groups with input as request + groups = principal_groups with input as request with data.entities as entities groups == {"test-user-group-one-uid", "test-user-group-two-uid"} @@ -216,14 +266,14 @@ test_wallet_groups { } test_signers_roles { - roles := signers_roles with input as request + roles = signers_roles with input as request with data.entities as entities roles == {"root", "member", "admin"} } test_signers_groups { - groups := signers_groups with input as request + groups = signers_groups with input as request with data.entities as entities groups == {"test-user-group-one-uid", "test-user-group-two-uid"} @@ -336,7 +386,7 @@ test_check_approval { "entityIds": ["test-bob-uid", "test-bar-uid", "test-signer-uid"], } - res := check_approval(required_approval) with input as request with data.entities as entities + res = check_approval(required_approval) with input as request with data.entities as entities res == { "approval": required_approval, @@ -356,7 +406,7 @@ test_check_approval { "entityIds": ["test-user-group-one-uid"], } - res := check_approval(required_approval) with input as request with data.entities as entities + res = check_approval(required_approval) with input as request with data.entities as entities res == { "approval": required_approval, @@ -376,7 +426,7 @@ test_check_approval { "entityIds": ["test-user-group-one-uid"], } - res := check_approval(required_approval) with input as request with data.entities as entities + res = check_approval(required_approval) with input as request with data.entities as entities res == { "approval": required_approval, @@ -396,7 +446,7 @@ test_check_approval { "entityIds": ["root", "admin"], } - res := check_approval(required_approval) with input as request with data.entities as entities + res = check_approval(required_approval) with input as request with data.entities as entities res == { "approval": required_approval, @@ -416,7 +466,7 @@ test_check_approval { "entityIds": ["root", "admin"], } - res := check_approval(required_approval) with input as request with data.entities as entities + res = check_approval(required_approval) with input as request with data.entities as entities res == { "approval": required_approval, @@ -429,7 +479,7 @@ test_check_approval { } test_get_approvals_result { - res := get_approvals_result([approvals_satisfied, approvals_missing]) + res = get_approvals_result([approvals_satisfied, approvals_missing]) res == { "approvalsSatisfied": [approvals_satisfied], @@ -437,8 +487,19 @@ test_get_approvals_result { } } +test_get_spending_amount { + tokens = { + "eip155:137/erc20:0x2791bca1f2de4661ed88a30c99a7a9449aa84174", + "eip155:1/erc20:0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", + } + start = substract_from_date(mock_now_s, (12 * 60) * 60) + res = get_spending_amount(tokens, start) with input as request with data.entities as entities + + res == (3051000000 * 0.99) + (2000000000 * 0.99) +} + test_permit { - res := permit with input as request with data.entities as entities + res = permit with input as request with data.entities as entities res == {{"policyId": "allow-root-user"}: { "policyId": "allow-root-user", @@ -447,8 +508,14 @@ test_permit { }} } +test_forbid { + res = forbid with input as request with data.entities as entities + + res == set() +} + test_evaluate { - res := evaluate with input as request with data.entities as entities + res = evaluate with input as request with data.entities as entities res == { "permit": true, diff --git a/apps/authz/src/app/opa/rego/lib/utils/time.rego b/apps/authz/src/app/opa/rego/lib/utils/time.rego index 47eca62f1..1058bb642 100644 --- a/apps/authz/src/app/opa/rego/lib/utils/time.rego +++ b/apps/authz/src/app/opa/rego/lib/utils/time.rego @@ -1,13 +1,9 @@ package main -seconds_to_nanoseconds(epoch_s) = epoch_ns { - epoch_ns := epoch_s * 1000000000 -} +seconds_to_nanoseconds(epoch_s) = epoch_s * 1000000000 -nanoseconds_to_seconds(epoch_ns) = epoch_s { - epoch_s := epoch_ns / 1000000000 -} +nanoseconds_to_seconds(epoch_ns) = epoch_ns / 1000000000 -now_s = now { - now := nanoseconds_to_seconds(time.now_ns()) -} +now_s = nanoseconds_to_seconds(time.now_ns()) + +substract_from_date(date, epoch_s) = date - epoch_s diff --git a/apps/authz/src/app/opa/rego/policies.tar.gz b/apps/authz/src/app/opa/rego/policies.tar.gz new file mode 100644 index 000000000..d766c4340 Binary files /dev/null and b/apps/authz/src/app/opa/rego/policies.tar.gz differ diff --git a/apps/authz/src/app/opa/rego/policies/accumulation-policy.rego b/apps/authz/src/app/opa/rego/policies/accumulation-policy.rego new file mode 100644 index 000000000..af253189a --- /dev/null +++ b/apps/authz/src/app/opa/rego/policies/accumulation-policy.rego @@ -0,0 +1,22 @@ +package main + +import future.keywords.in + +forbid[{"policyId": "test-accumulation-policy"}] { + not is_principal_root_user + is_principal_assigned_to_wallet + input.activityType == "signTransaction" + tokens = {"eip155:137/erc20:0x2791bca1f2de4661ed88a30c99a7a9449aa84174", "eip155:1/erc20:0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"} + check_principal_role({"member"}) + check_transfer_token_type({"transferToken"}) + check_transfer_token_address(tokens) + + start = substract_from_date(now_s, (12 * 60) * 60) + spendings = get_spending_amount(tokens, start) + spendings + transfer_token_amount > 5000000000 + + reason := { + "policyId": "test-accumulation-policy", + "message": "You have reached the your spending limit.", + } +}