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

feat(api): stable deployment account deployment addresses when proxy … #290

Merged
merged 1 commit into from
Aug 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion api/dbschema/bootstrap.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ configure instance set effective_io_concurrency := 1000;
configure instance set query_work_mem := <cfg::memory>"8MiB";

# Memory available to cache data - 50%
configure instance set shared_buffers := <cfg::memory>"2GiB";
configure instance set shared_buffers := <cfg::memory>"4GiB";

# Total memory available to the database for caching - 75%
configure instance set effective_cache_size := <cfg::memory>"6GiB";
2 changes: 1 addition & 1 deletion api/dbschema/default.esdl
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ module default {
type Account extending Labelled {
overloaded required address: UAddress { constraint exclusive; }
required implementation: Address;
required salt: Bytes32;
required initialization: tuple<salt: Bytes32, bytecodeHash: Bytes32, aaVersion: uint16>;
activationEthFee: decimal { constraint min_value(0); }
upgradedAtBlock: bigint { constraint min_value(0); }
photo: Url;
Expand Down
3 changes: 2 additions & 1 deletion api/dbschema/edgeql-js/__spec__.ts

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion api/dbschema/edgeql-js/modules/default.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,13 +96,13 @@ export type $AccountλShape = $.typeutil.flatten<Omit<$LabelledλShape, "address
"active": $.PropertyDesc<_std.$bool, $.Cardinality.One, false, true, false, false>;
"implementation": $.PropertyDesc<$Address, $.Cardinality.One, false, false, false, false>;
"photo": $.PropertyDesc<$Url, $.Cardinality.AtMostOne, false, false, false, false>;
"salt": $.PropertyDesc<$Bytes32, $.Cardinality.One, false, false, false, false>;
"policies": $.LinkDesc<$Policy, $.Cardinality.Many, {}, false, true, false, false>;
"approvers": $.LinkDesc<$Approver, $.Cardinality.Many, {}, false, true, false, false>;
"messages": $.LinkDesc<$Message, $.Cardinality.Many, {}, false, true, false, false>;
"proposals": $.LinkDesc<$Proposal, $.Cardinality.Many, {}, false, true, false, false>;
"transactions": $.LinkDesc<$Transaction, $.Cardinality.Many, {}, false, true, false, false>;
"transfers": $.LinkDesc<$Transfer, $.Cardinality.Many, {}, false, true, false, false>;
"initialization": $.PropertyDesc<$.NamedTupleType<{salt: $Bytes32, bytecodeHash: $Bytes32, aaVersion: $uint16}>, $.Cardinality.One, false, false, false, false>;
"<account[is Proposal]": $.LinkDesc<$Proposal, $.Cardinality.Many, {}, false, false, false, false>;
"<account[is Message]": $.LinkDesc<$Message, $.Cardinality.Many, {}, false, false, false, false>;
"<account[is Transaction]": $.LinkDesc<$Transaction, $.Cardinality.Many, {}, false, false, false, false>;
Expand Down
2 changes: 1 addition & 1 deletion api/dbschema/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,13 +87,13 @@ export namespace $default {
"active": boolean;
"implementation": string;
"photo"?: string | null;
"salt": string;
"policies": Policy[];
"approvers": Approver[];
"messages": Message[];
"proposals": Proposal[];
"transactions": Transaction[];
"transfers": Transfer[];
"initialization": {salt: string, bytecodeHash: string, aaVersion: number};
}
export interface Action extends std.$Object {
"functions": ActionFunction[];
Expand Down
16 changes: 16 additions & 0 deletions api/dbschema/migrations/00006-m13y2dt.edgeql
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
CREATE MIGRATION m13y2dtmcrd7yvmorrgn62mp2cakpvrcbz7p7g2rr4wk34b6ow7m7q
ONTO m174x3tft7rqwxzekegp7qad4qlscof7tojfcmw4wielhp7h4mqsya
{
ALTER TYPE default::Account {
CREATE REQUIRED PROPERTY initialization: tuple<salt: default::Bytes32, bytecodeHash: default::Bytes32, aaVersion: default::uint16> {
SET REQUIRED USING ((
salt := .salt,
bytecodeHash := '0x0100007b3eebe76a9052ad76c1efe68151404a98aee77b96cbdbc62df0660b27',
aaVersion := 1
));
};
};
ALTER TYPE default::Account {
DROP PROPERTY salt;
};
};
15 changes: 8 additions & 7 deletions api/src/feat/accounts/accounts.service.spec.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { createMock, DeepMocked } from '@golevelup/ts-jest';
import { Test } from '@nestjs/testing';
import { UserContext, asUser, getUserCtx } from '~/core/context';
import { randomLabel, randomAddress, randomUAddress, randomUser } from '~/util/test';
import { getProxyAddress, UAddress } from 'lib';
import { randomAddress, randomLabel, randomUAddress, randomUser } from '~/util/test';
import { UAddress } from 'lib';
import { PoliciesService } from '../policies/policies.service';
import { BullModule, getQueueToken } from '@nestjs/bullmq';
import { ActivationsQueue } from '../activations/activations.queue';
Expand All @@ -12,13 +12,14 @@ import e from '~/edgeql-js';
import { uuid } from 'edgedb/dist/codecs/ifaces';
import { AccountsCacheService } from '../auth/accounts.cache.service';
import { TypedQueue } from '~/core/bull/bull.util';
import { create2Address } from 'zksync-ethers/build/utils';

jest.mock('lib', () => ({
...jest.requireActual('lib'),
getProxyAddress: jest.fn(),
jest.mock('zksync-ethers/build/utils', () => ({
...jest.requireActual('zksync-ethers/build/utils'),
create2Address: jest.fn(),
}));

const getProxyAddressMock = jest.mocked(getProxyAddress);
const create2AddressMock = jest.mocked(create2Address);

describe(AccountsService.name, () => {
let service: AccountsService;
Expand Down Expand Up @@ -51,7 +52,7 @@ describe(AccountsService.name, () => {
const createAccount = async () => {
const userCtx = getUserCtx();

getProxyAddressMock.mockReturnValue(randomAddress());
create2AddressMock.mockReturnValue(randomAddress());

return service.createAccount({
chain: 'zksync-local',
Expand Down
21 changes: 14 additions & 7 deletions api/src/feat/accounts/accounts.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,14 @@ import e from '~/edgeql-js';
import {
asPolicyKey,
randomDeploySalt,
getProxyAddress,
Address,
UAddress,
asAddress,
ACCOUNT_IMPLEMENTATION,
asUAddress,
PLACEHOLDER_ACCOUNT_ADDRESS,
ACCOUNT_PROXY,
encodeProxyConstructorArgs,
} from 'lib';
import { CREATE2_FACTORY } from 'lib/dapps';
import { ShapeFunc } from '~/core/database';
Expand All @@ -32,6 +33,8 @@ import { v4 as uuid } from 'uuid';
import { selectAccount2 } from './accounts.util';
import { AccountEvent } from './accounts.model';
import { PolicyInput } from '../policies/policies.input';
import { utils as zkUtils } from 'zksync-ethers';
import { toHex } from 'viem';

const accountTrigger = (account: UAddress) => `account.updated:${account}`;
const accountApproverTrigger = (approver: Address) => `account.updated:approver:${approver}`;
Expand Down Expand Up @@ -107,13 +110,17 @@ export class AccountsService {
throw new UserInputError('Duplicate policy keys');

const implementation = ACCOUNT_IMPLEMENTATION.address[chain];
const bytecodeHash = toHex(zkUtils.hashBytecode(ACCOUNT_PROXY.bytecode));
const address = asUAddress(
getProxyAddress({
deployer: CREATE2_FACTORY.address,
implementation,
zkUtils.create2Address(
CREATE2_FACTORY.address,
bytecodeHash,
salt,
policies: policies.map((p) => inputAsPolicy(p.key, p)),
}),
encodeProxyConstructorArgs({
implementation,
policies: policies.map((p) => inputAsPolicy(p.key, p)),
}),
),
chain,
);

Expand Down Expand Up @@ -147,7 +154,7 @@ export class AccountsService {
address,
name,
implementation,
salt,
initialization: { salt, bytecodeHash, aaVersion: 1 },
}),
);

Expand Down
2 changes: 1 addition & 1 deletion api/src/feat/accounts/insert-account.edgeql
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@ insert Account {
address := <UAddress>$address,
name := <str>$name,
implementation := <Address>$implementation,
salt := <Bytes32>$salt
initialization := <tuple<salt: Bytes32, bytecodeHash: Bytes32, aaVersion: uint16>>$initialization
}
8 changes: 6 additions & 2 deletions api/src/feat/accounts/insert-account.query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@ export type InsertAccountArgs = {
readonly "address": string;
readonly "name": string;
readonly "implementation": string;
readonly "salt": string;
readonly "initialization": {
readonly "salt": string;
readonly "bytecodeHash": string;
readonly "aaVersion": number;
};
};

export type InsertAccountReturns = {
Expand All @@ -21,7 +25,7 @@ insert Account {
address := <UAddress>$address,
name := <str>$name,
implementation := <Address>$implementation,
salt := <Bytes32>$salt
initialization := <tuple<salt: Bytes32, bytecodeHash: Bytes32, aaVersion: uint16>>$initialization
}`, args);

}
29 changes: 12 additions & 17 deletions api/src/feat/activations/activations.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import {
replaceSelfAddress,
PLACEHOLDER_ACCOUNT_ADDRESS,
UUID,
ACCOUNT_PROXY,
encodeProxyConstructorArgs,
} from 'lib';
import { CREATE2_FACTORY } from 'lib/dapps';
Expand All @@ -21,13 +20,6 @@ import { FlowJob } from 'bullmq';
import { ConfirmationQueue } from '../system-txs/confirmations.queue';
import Decimal from 'decimal.js';
import { SimulationsQueue } from '../simulations/simulations.worker';
import { toHex } from 'viem';
import { utils as zkUtils } from 'zksync-ethers';

interface FeeParams {
account: UAddress;
feePerGas: Decimal;
}

@Injectable()
export class ActivationsService {
Expand Down Expand Up @@ -61,15 +53,15 @@ export class ActivationsService {
} satisfies FlowJob;
}

async fee({ account, feePerGas }: FeeParams): Promise<Decimal | null> {
const a = await this.db.queryWith(
async fee(account: UAddress): Promise<Decimal | null> {
const a = await this.db.queryWith2(
{ address: e.UAddress },
{ address: account },
({ address }) =>
e.select(e.Account, () => ({
filter_single: { address },
activationEthFee: true,
})),
{ address: account },
);
if (!a) return null;
if (a.activationEthFee) return new Decimal(a.activationEthFee);
Expand All @@ -79,8 +71,11 @@ export class ActivationsService {

const network = this.networks.get(account);
try {
const gas = await network.estimateContractGas(request);
return feePerGas.mul(gas.toString());
const [gas, maxFeePerGas] = await Promise.all([
network.estimateContractGas(request),
network.maxFeePerGas(),
]);
return maxFeePerGas.mul(gas.toString());
} catch (e) {
const isDeployed = !!(await network.getCode({ address: asAddress(account) }))?.length;
if (isDeployed) return null;
Expand All @@ -97,7 +92,7 @@ export class ActivationsService {
filter_single: { address },
active: true,
implementation: true,
salt: true,
initialization: true,
initPolicies: e.select(a.policies, (p) => ({
filter: p.initState,
...PolicyShape,
Expand Down Expand Up @@ -126,10 +121,10 @@ export class ActivationsService {
address: CREATE2_FACTORY.address,
functionName: 'create2Account' as const,
args: [
asHex(account.salt),
toHex(zkUtils.hashBytecode(ACCOUNT_PROXY.bytecode)),
asHex(account.initialization.salt),
asHex(account.initialization.bytecodeHash),
constructorArgs,
1, // AccountAbstractionVersion.Version1
account.initialization.aaVersion,
] as const,
// factoryDeps: [ACCOUNT_PROXY.bytecode], // Throws "rpc method is not whitelisted" if provided; bytecode must be deployed using SystemContractDeployer first
// gas: 3_000_000n * BigInt(params.policies.length), // ~1M per policy; gas estimation panics if not provided
Expand Down
8 changes: 7 additions & 1 deletion api/src/feat/activations/activations.worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import { ActivationsQueue } from './activations.queue';
import { DatabaseService } from '~/core/database';
import e from '~/edgeql-js';
import { selectTransaction2 } from '../transactions/transactions.util';
import { UnrecoverableError } from 'bullmq';
import { asAddress } from 'lib';

@Injectable()
@Processor(ActivationsQueue.name, { autorun: false })
Expand Down Expand Up @@ -42,7 +44,11 @@ export class ActivationsWorker extends Worker<ActivationsQueue> {
const request = await this.activations.request(account);
if (!request) return null;

await network.simulateContract(request); // Throws on error
const sim = await network.simulateContract(request); // Throws on error
if (sim.result !== asAddress(account))
throw new UnrecoverableError(
`Simulated deployment address ${sim.result} doesn't match expected address ${asAddress(account)}`,
);

const { account: _, ...req } = request;
const receipt = await network.useWallet((wallet) => wallet.writeContract(req));
Expand Down
4 changes: 1 addition & 3 deletions api/src/feat/paymasters/paymasters.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,7 @@ export class PaymastersService implements OnModuleInit {
}

async paymasterFees({ account }: PaymasterFeesParams): Promise<PaymasterFeeParts> {
const feePerGas = await this.networks.get(account).maxFeePerGas();

const activation = await this.activations.fee({ account, feePerGas });
const activation = await this.activations.fee(account);

return { activation: activation ?? new Decimal(0) };
}
Expand Down
15 changes: 4 additions & 11 deletions api/src/feat/policies/policies.service.spec.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,7 @@
import { Test } from '@nestjs/testing';
import { createMock, DeepMocked } from '@golevelup/ts-jest';
import { PoliciesService, ProposePoliciesParams } from './policies.service';
import {
asPolicyKey,
asSelector,
asUUID,
randomDeploySalt,
randomHex,
UAddress,
ZERO_ADDR,
} from 'lib';
import { asPolicyKey, asSelector, asUUID, randomHex, UAddress, ZERO_ADDR } from 'lib';
import { UserContext } from '~/core/context';
import { asUser, getUserCtx } from '~/core/context';
import { randomAddress, randomLabel, randomUAddress, randomUser } from '~/util/test';
Expand All @@ -20,6 +12,7 @@ import { inputAsPolicy, policyStateAsPolicy, PolicyShape, selectPolicy } from '.
import { PolicyInput } from './policies.input';
import { v1 as uuidv1 } from 'uuid';
import { selectAccount } from '../accounts/accounts.util';
import { zeroHash } from 'viem';

describe(PoliciesService.name, () => {
let service: PoliciesService;
Expand Down Expand Up @@ -58,8 +51,8 @@ describe(PoliciesService.name, () => {
id: accountId,
address: account,
name: randomLabel(),
implementation: randomAddress(),
salt: randomDeploySalt(),
implementation: ZERO_ADDR,
initialization: { salt: zeroHash, bytecodeHash: zeroHash, aaVersion: 1 },
}),
);

Expand Down
6 changes: 3 additions & 3 deletions api/src/feat/transactions/transactions.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Test } from '@nestjs/testing';
import { createMock, DeepMocked } from '@golevelup/ts-jest';
import { asUser, getUserCtx, UserContext } from '~/core/context';
import { DeepPartial, randomAddress, randomLabel, randomUAddress, randomUser } from '~/util/test';
import { randomDeploySalt, Hex, UAddress, ZERO_ADDR, asUUID, asPolicyKey } from 'lib';
import { Hex, UAddress, ZERO_ADDR, asUUID, asPolicyKey } from 'lib';
import { Network, NetworksService } from '~/core/networks/networks.service';
import { ProposeTransactionInput } from './transactions.input';
import { DatabaseService } from '~/core/database';
Expand Down Expand Up @@ -112,8 +112,8 @@ describe(TransactionsService.name, () => {
id: accountId,
address: account,
name: randomLabel(),
implementation: randomAddress(),
salt: randomDeploySalt(),
implementation: ZERO_ADDR,
initialization: { salt: zeroHash, bytecodeHash: zeroHash, aaVersion: 1 },
upgradedAtBlock: 1n,
})
.unlessConflict(),
Expand Down
8 changes: 4 additions & 4 deletions api/src/feat/transfers/transfers.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,13 @@ import {
asChain,
asUAddress,
asUUID,
randomDeploySalt,
} from 'lib';
import { randomAddress, randomLabel, randomUAddress, randomUser } from '~/util/test';
import { randomLabel, randomUAddress, randomUser } from '~/util/test';
import e from '~/edgeql-js';
import { v1 as uuidv1 } from 'uuid';
import { InsertShape } from '~/edgeql-js/insert';
import { $Transfer } from '~/edgeql-js/modules/default';
import { zeroHash } from 'viem';

describe(TransfersService.name, () => {
let service: TransfersService;
Expand All @@ -45,8 +45,8 @@ describe(TransfersService.name, () => {
id,
address,
name: randomLabel(),
implementation: randomAddress(),
salt: randomDeploySalt(),
implementation: ZERO_ADDR,
initialization: { salt: zeroHash, bytecodeHash: zeroHash, aaVersion: 1 },
upgradedAtBlock: 1n,
})
.unlessConflict()
Expand Down
2 changes: 1 addition & 1 deletion contracts/script/deployProxy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { TASK_COMPILE } from 'hardhat/builtin-tasks/task-names';
import AccountProxy from './contracts/AccountProxy';
import { deploy } from './util/deploy';

const ACCOUNT = '0xc0c1c0692F1aCd4FA91ccc73fB9aCFCd60Dd571a';
const ACCOUNT = '0x696532D64a358a4CC2eCDBE698a4a08c7841af8c';

// Deploys an uninitialized proxy
// Used for first-time proxy bytecode deployment
Expand Down
Loading
Loading