Skip to content
This repository was archived by the owner on Feb 8, 2025. It is now read-only.

Commit a5504b8

Browse files
authored
Merge pull request #278 from zallo-labs/Z-338-fix-policy-activation
Z 338 fix policy activation
2 parents bd03dca + ddfa927 commit a5504b8

14 files changed

+53
-17
lines changed

api/dbschema/edgeql-js/__spec__.ts

+1-1
Large diffs are not rendered by default.

api/dbschema/edgeql-js/modules/default.ts

+1
Original file line numberDiff line numberDiff line change
@@ -479,6 +479,7 @@ export type $PolicyλShape = $.typeutil.flatten<$PolicyStateλShape & {
479479
"delay": $.PropertyDesc<$uint32, $.Cardinality.One, false, false, false, true>;
480480
"name": $.PropertyDesc<$BoundedStr, $.Cardinality.One, false, false, false, false>;
481481
"threshold": $.PropertyDesc<$uint16, $.Cardinality.One, false, false, false, false>;
482+
"hash": $.PropertyDesc<$Bytes32, $.Cardinality.One, false, false, false, false>;
482483
"<policies[is Account]": $.LinkDesc<$Account, $.Cardinality.Many, {}, false, false, false, false>;
483484
"<policy[is Proposal]": $.LinkDesc<$Proposal, $.Cardinality.Many, {}, false, false, false, false>;
484485
"<policy[is Transaction]": $.LinkDesc<$Transaction, $.Cardinality.Many, {}, false, false, false, false>;

api/dbschema/interfaces.ts

+1
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,7 @@ export namespace $default {
228228
"delay": number;
229229
"name": string;
230230
"threshold": number;
231+
"hash": string;
231232
}
232233
export interface Rejection extends ProposalResponse {}
233234
export interface RemovedPolicy extends PolicyState {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
CREATE MIGRATION m17arqyctfz6btivr4soubbql7ruianngjqd3y4p5sdnbqcgvcz4tq
2+
ONTO m1ob6arfwyamzcpgk2hgzhby3iwv3moewthqacorsw2mtyjkopdoaq
3+
{
4+
ALTER TYPE default::Policy {
5+
CREATE REQUIRED PROPERTY hash: default::Bytes32 {
6+
SET REQUIRED USING (<default::Bytes32>'0x0000000000000000000000000000000000000000000000000000000000000000');
7+
};
8+
CREATE INDEX ON ((.account, .key, .hash));
9+
};
10+
};

api/dbschema/policy.esdl

+4-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ module default {
1313
rewrite insert using (
1414
.isLatest if (__specified__.isLatest) else
1515
# ((.activationBlock ?? 0n) > (latestPolcy(.account, .key)).activationBlock ?? -1n)
16-
((.activationBlock ?? 0n) > (assert_single((select Policy filter .account = __subject__.account and .key = __subject__.key and .isLatest)).activationBlock ?? -1n))
16+
((.activationBlock ?? 0n) > (assert_single((select Policy filter .account = __subject__.account and .key = __subject__.key and .isLatest)).activationBlock ?? -1n))
1717
)
1818
}
1919
required initState := .activationBlock ?= 0;
@@ -39,6 +39,7 @@ module default {
3939
}
4040

4141
type Policy extending PolicyState {
42+
required hash: Bytes32;
4243
required name: BoundedStr;
4344
required threshold: uint16;
4445
multi approvers: Approver;
@@ -47,6 +48,8 @@ module default {
4748
required allowMessages: bool { default := false; }
4849
required delay: uint32 { default := 0; }
4950

51+
index on ((.account, .key, .hash));
52+
5053
trigger update_proposals_when_latest after insert, update for each
5154
when (__new__.isLatest) do (
5255
update Proposal filter .account = __new__.account and .policy.key = __new__.key and

api/schema.graphql

+1
Original file line numberDiff line numberDiff line change
@@ -389,6 +389,7 @@ type Policy implements Node & PolicyState {
389389
delay: Float!
390390
draft: PolicyState
391391
hasBeenActive: Boolean!
392+
hash: Bytes32!
392393
id: ID!
393394
initState: Boolean!
394395
isActive: Boolean!

api/src/feat/policies/activate-policy.edgeql

+5-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
with account := (select Account filter .address = <UAddress>$account),
2-
proposal := (select SystemTx filter .hash = <Bytes32>$systxHash).proposal,
32
key := <uint16>$key,
4-
new := assert_single((select PolicyState filter .account = account and .key = key and (.proposal ?= proposal or .initState))),
3+
new := assert_single((
4+
select PolicyState filter .account = account and .key = key and
5+
([is Policy].hash ?= <optional Bytes32>$hash or PolicyState is RemovedPolicy) and
6+
(not exists .activationBlock or .activationBlock ?= 0)
7+
)),
58
old := assert_single((select PolicyState filter .account = account and key = .key and .isLatest and .id != new.id)),
69
activationBlock := <bigint>$activationBlock,
710
isLater := (activationBlock > (old.activationBlock ?? -1n)),

api/src/feat/policies/activate-policy.query.ts

+6-3
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ import type {Executor} from "edgedb";
44

55
export type ActivatePolicyArgs = {
66
readonly "account": string;
7-
readonly "systxHash": string;
87
readonly "key": number;
8+
readonly "hash"?: string | null;
99
readonly "activationBlock": bigint;
1010
};
1111

@@ -18,9 +18,12 @@ export type ActivatePolicyReturns = {
1818
export function activatePolicy(client: Executor, args: ActivatePolicyArgs): Promise<ActivatePolicyReturns> {
1919
return client.queryRequiredSingle(`\
2020
with account := (select Account filter .address = <UAddress>$account),
21-
proposal := (select SystemTx filter .hash = <Bytes32>$systxHash).proposal,
2221
key := <uint16>$key,
23-
new := assert_single((select PolicyState filter .account = account and .key = key and (.proposal ?= proposal or .initState))),
22+
new := assert_single((
23+
select PolicyState filter .account = account and .key = key and
24+
([is Policy].hash ?= <optional Bytes32>$hash or PolicyState is RemovedPolicy) and
25+
(not exists .activationBlock or .activationBlock ?= 0)
26+
)),
2427
old := assert_single((select PolicyState filter .account = account and key = .key and .isLatest and .id != new.id)),
2528
activationBlock := <bigint>$activationBlock,
2629
isLater := (activationBlock > (old.activationBlock ?? -1n)),

api/src/feat/policies/insert-policies.edgeql

+1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ with account := (select Account filter .address = <UAddress>$account),
33
tx := ((select Transaction filter .id = txId) if exists txId else {})
44
for p in array_unpack(<array<json>>$policies) union (
55
with policy := (insert Policy {
6+
hash := <Bytes32>p['hash'],
67
account := account,
78
key := <uint16>p['key'],
89
proposal := tx,

api/src/feat/policies/insert-policies.query.ts

+1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ with account := (select Account filter .address = <UAddress>$account),
2020
tx := ((select Transaction filter .id = txId) if exists txId else {})
2121
for p in array_unpack(<array<json>>$policies) union (
2222
with policy := (insert Policy {
23+
hash := <Bytes32>p['hash'],
2324
account := account,
2425
key := <uint16>p['key'],
2526
proposal := tx,

api/src/feat/policies/policies.events.ts

+8-6
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { Injectable } from '@nestjs/common';
22
import { EventsWorker, EventData, Log } from '../events/events.worker';
3-
import { ACCOUNT_ABI, PolicyKey, asPolicyKey, asUAddress, asUUID } from 'lib';
3+
import { ACCOUNT_ABI, Hex, PolicyKey, asPolicyKey, asUAddress, asUUID } from 'lib';
44
import { Chain } from 'chains';
55
import { DatabaseService } from '~/core/database';
66
import { getAbiItem } from 'viem';
@@ -29,6 +29,7 @@ export class PoliciesEventsProcessor {
2929
chain,
3030
log,
3131
asPolicyKey(log.args.key),
32+
log.args.hash,
3233
);
3334

3435
if (policyId)
@@ -46,20 +47,21 @@ export class PoliciesEventsProcessor {
4647
this.policies.event({ event: PolicyEvent.removed, account, policyId: asUUID(policyId) });
4748
}
4849

49-
private async markStateAsActive(chain: Chain, log: Log, key: PolicyKey) {
50-
// FIXME: when multiple policies are activated in one block, the wrong one may be marked as active
51-
// This *always* occurs when a policy is activated by a policy update transaction
52-
50+
private async markStateAsActive(chain: Chain, log: Log, key: PolicyKey, hash?: Hex) {
5351
const account = asUAddress(log.address, chain);
5452
const r = await this.db.exec(activatePolicy, {
5553
account,
5654
key,
57-
systxHash: log.transactionHash,
55+
hash,
5856
activationBlock: log.blockNumber,
5957
});
6058

6159
await Promise.all(r.pendingTransactions.map((id) => this.transactions.tryExecute(asUUID(id))));
6260

61+
if (!r.new) {
62+
// TODO: this shouldn't happen
63+
}
64+
6365
return { account, old: r.old, new: r.new };
6466
}
6567
}

api/src/feat/policies/policies.model.ts

+5-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { GraphQLBigInt } from 'graphql-scalars';
33
import { Account } from '../accounts/accounts.model';
44
import { Transaction } from '../transactions/transactions.model';
55
import * as eql from '~/edgeql-interfaces';
6-
import { Address, PolicyKey, Selector, UAddress } from 'lib';
6+
import { Address, Hex, PolicyKey, Selector, UAddress } from 'lib';
77
import { Approver } from '../approvers/approvers.model';
88
import {
99
CustomNodeType,
@@ -22,6 +22,7 @@ import {
2222
AbiFunctionField,
2323
PolicyKeyField,
2424
UAddressField,
25+
Bytes32Field,
2526
} from '~/common/scalars';
2627

2728
@NodeType()
@@ -113,6 +114,9 @@ export class PolicyState extends Node {
113114

114115
@NodeType({ implements: [PolicyState] })
115116
export class Policy extends PolicyState {
117+
@Bytes32Field()
118+
hash: Hex;
119+
116120
@Field(() => String)
117121
name: string;
118122

api/src/feat/policies/policies.service.ts

+6-3
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
UUID,
1414
asUUID,
1515
PolicyKey,
16+
hashPolicy,
1617
} from 'lib';
1718
import { TransactionsService } from '../transactions/transactions.service';
1819
import {
@@ -128,9 +129,10 @@ export class PoliciesService {
128129
return {
129130
policy,
130131
state: {
131-
name: 'Policy ' + input.key,
132132
...policyState,
133+
name: input.name || 'Policy ' + input.key,
133134
},
135+
hash: hashPolicy(policy),
134136
};
135137
})
136138
.filter(Boolean);
@@ -157,9 +159,10 @@ export class PoliciesService {
157159
await this.db.exec(insertPolicies, {
158160
account,
159161
transaction,
160-
policies: changedPolicies.map(({ state }) => ({
161-
...(isInitialization && { activationBlock: 0n }),
162+
policies: changedPolicies.map(({ state, hash }) => ({
163+
hash,
162164
...state,
165+
...(isInitialization && { activationBlock: 0n }),
163166
})),
164167
})
165168
).map((p) => ({ ...p, id: asUUID(p.id), key: asPolicyKey(p.key) }));

api/src/feat/transactions/transactions.service.spec.ts

+3
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import Decimal from 'decimal.js';
2222
import { PoliciesService } from '../policies/policies.service';
2323
import { PricesService } from '~/feat/prices/prices.service';
2424
import { TokensService } from '~/feat/tokens/tokens.service';
25+
import { zeroHash } from 'viem';
2526

2627
const signature = '0x1234' as Hex;
2728

@@ -124,6 +125,7 @@ describe(TransactionsService.name, () => {
124125
await e
125126
.insert(e.Policy, {
126127
account: selectAccount(accountId),
128+
hash: zeroHash,
127129
key: 0,
128130
name: 'Policy 0',
129131
threshold: 0,
@@ -438,6 +440,7 @@ describe(TransactionsService.name, () => {
438440
const policy = await db.query(
439441
e.insert(e.Policy, {
440442
account: selectAccount(user1Account1),
443+
hash: zeroHash,
441444
key: 1,
442445
name: 'Policy 1',
443446
proposal: selectTransaction(id),

0 commit comments

Comments
 (0)