Skip to content

Commit

Permalink
feat: update token for upgradable transactions where implementation i…
Browse files Browse the repository at this point in the history
…s token
  • Loading branch information
kiriyaga committed Dec 4, 2024
1 parent 0d144bc commit e39da6d
Show file tree
Hide file tree
Showing 10 changed files with 311 additions and 1 deletion.
2 changes: 2 additions & 0 deletions packages/data-fetcher/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { TransferService } from "./transfer/transfer.service";
import { TokenService } from "./token/token.service";
import { JsonRpcProviderModule } from "./rpcProvider/jsonRpcProvider.module";
import { MetricsModule } from "./metrics";
import { UpgradableService } from "./upgradable/upgradable.service";

@Module({
imports: [
Expand All @@ -26,6 +27,7 @@ import { MetricsModule } from "./metrics";
providers: [
BlockchainService,
AddressService,
UpgradableService,
BalanceService,
TransferService,
TokenService,
Expand Down
39 changes: 39 additions & 0 deletions packages/data-fetcher/src/log/log.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,23 @@ import { TokenService, Token } from "../token/token.service";
import { AddressService } from "../address/address.service";
import { BalanceService } from "../balance/balance.service";
import { ContractAddress } from "../address/interface/contractAddress.interface";
import { UpgradableService } from "../upgradable/upgradable.service";
import { ProxyAddress } from "src/upgradable/interface/proxyAddress.interface";

describe("LogService", () => {
let logService: LogService;
let addressServiceMock: AddressService;
let balanceServiceMock: BalanceService;
let transferServiceMock: TransferService;
let tokenServiceMock: TokenService;
let upgradableServiceMock: UpgradableService;

beforeEach(async () => {
addressServiceMock = mock<AddressService>();
balanceServiceMock = mock<BalanceService>();
transferServiceMock = mock<TransferService>();
tokenServiceMock = mock<TokenService>();
upgradableServiceMock = mock<UpgradableService>();

const app = await Test.createTestingModule({
providers: [
Expand All @@ -42,6 +46,10 @@ describe("LogService", () => {
provide: TokenService,
useValue: tokenServiceMock,
},
{
provide: UpgradableService,
useValue: upgradableServiceMock,
},
],
}).compile();

Expand All @@ -61,6 +69,17 @@ describe("LogService", () => {
mock<ContractAddress>({ address: "0xD144ca8Aa2E7DFECD56a3CCcBa1cd873c8e5db58" }),
];

const upgradableAddresses = [
mock<ProxyAddress>({
address: "0xEBf9D3ead9A8c2bb8cEa438B8Dfa9f1AFf44bfa7",
implementationAddress: "0xf43624d811c5DC9eF91cF237ab9B8eE220D438eE",
}),
mock<ProxyAddress>({
address: "0xdc187378edD8Ed1585fb47549Cc5fe633295d571",
implementationAddress: "0xD144ca8Aa2E7DFECD56a3CCcBa1cd873c8e5db58",
}),
];

const transfers = [
{ from: "from1", to: "to1", logIndex: 0 } as Transfer,
{ from: "from2", to: "to2", logIndex: 1 } as Transfer,
Expand Down Expand Up @@ -92,6 +111,7 @@ describe("LogService", () => {

describe("when transaction details and receipt are defined", () => {
beforeEach(() => {
jest.spyOn(upgradableServiceMock, "getUpgradableAddresses").mockResolvedValueOnce([]);
transactionReceipt = mock<types.TransactionReceipt>({
index: 0,
logs: logs,
Expand Down Expand Up @@ -133,6 +153,9 @@ describe("LogService", () => {
});

describe("when transaction details and receipt are not defined", () => {
beforeEach(() => {
jest.spyOn(upgradableServiceMock, "getUpgradableAddresses").mockResolvedValueOnce([]);
});
it("tracks changed balances", async () => {
await logService.getData(logs, blockDetails);
expect(balanceServiceMock.trackChangedBalances).toHaveBeenCalledTimes(1);
Expand All @@ -146,5 +169,21 @@ describe("LogService", () => {
expect(logsData.transfers).toEqual(transfers);
});
});

describe("when there are upgradable addresses", () => {
beforeEach(() => {
jest.spyOn(upgradableServiceMock, "getUpgradableAddresses").mockResolvedValueOnce(upgradableAddresses);
});
it("returns data with upgradable addresses", async () => {
const logsData = await logService.getData(logs, blockDetails, transactionDetails, transactionReceipt);
expect(upgradableServiceMock.getUpgradableAddresses).toHaveBeenCalledTimes(1);
expect(tokenServiceMock.getERC20Token).toHaveBeenCalledTimes(3);
expect(logsData.tokens).toEqual([
{ l1Address: "l1Address1" },
{ l1Address: "l1Address2" },
{ l2Address: "0xEBf9D3ead9A8c2bb8cEa438B8Dfa9f1AFf44bfa7" },
]);
});
});
});
});
33 changes: 32 additions & 1 deletion packages/data-fetcher/src/log/log.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { TokenService } from "../token/token.service";
import { Transfer } from "../transfer/interfaces/transfer.interface";
import { ContractAddress } from "../address/interface/contractAddress.interface";
import { Token } from "../token/token.service";
import { UpgradableService } from "../upgradable/upgradable.service";

export interface LogsData {
transfers: Transfer[];
Expand All @@ -22,7 +23,8 @@ export class LogService {
private readonly addressService: AddressService,
private readonly balanceService: BalanceService,
private readonly transferService: TransferService,
private readonly tokenService: TokenService
private readonly tokenService: TokenService,
private readonly upgradableService: UpgradableService
) {
this.logger = new Logger(LogService.name);
}
Expand Down Expand Up @@ -60,6 +62,35 @@ export class LogService {
)
).filter((token) => !!token);

this.logger.debug({
message: "Extracting upgradable addresses",
blockNumber: blockDetails.number,
transactionHash,
});

const upgradableAddresses = await this.upgradableService.getUpgradableAddresses(logs, transactionReceipt);
const upgradableTokens = (
await Promise.all(
upgradableAddresses
.filter(
(address) => !contractAddresses.some((contractAddress) => contractAddress.address === address.address)
)
.map(async (upgradableAddress) => {
const proxyAddress = upgradableAddress.address;
const implementationAddress = upgradableAddress.implementationAddress;
const token = await this.tokenService.getERC20Token(
{ ...upgradableAddress, address: implementationAddress },
transactionReceipt
);
return {
...token,
l2Address: proxyAddress,
};
})
)
).filter((token) => !!token);
tokens.push(...upgradableTokens);

logsData.contractAddresses = contractAddresses;
logsData.tokens = tokens;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { types } from "zksync-ethers";
import { mock } from "jest-mock-extended";
import { defaultContractUpgradableHandler } from "./default.handler";

describe("defaultContractUpgradableHandler", () => {
let log: types.Log;
beforeEach(() => {
log = mock<types.Log>({
transactionIndex: 1,
blockNumber: 3233097,
transactionHash: "0x5e018d2a81dbd1ef80ff45171dd241cb10670dcb091e324401ff8f52293841b0",
address: "0x1BEB2aBb1678D8a25431d9728A425455f29d12B7",
topics: [
"0xbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b",
"0x000000000000000000000000a1810a1f32F4DC6c5112b5b837b6975E56b489cc",
],
data: "0x",
index: 8,
blockHash: "0xdfd071dcb9c802f7d11551f4769ca67842041ffb81090c49af7f089c5823f39c",
l1BatchNumber: 604161,
});
});

describe("matches", () => {
it("returns true", () => {
const result = defaultContractUpgradableHandler.matches(log);
expect(result).toBe(true);
});
});

describe("extract", () => {
let transactionReceipt;

beforeEach(() => {
transactionReceipt = mock<types.TransactionReceipt>({
blockNumber: 10,
hash: "transactionHash",
from: "from",
});
});

it("extracts upgraded contract address", () => {
const result = defaultContractUpgradableHandler.extract(log, transactionReceipt);
expect(result.address).toBe("0x1BEB2aBb1678D8a25431d9728A425455f29d12B7");
});

it("extracts block number for the upgraded contract", () => {
const result = defaultContractUpgradableHandler.extract(log, transactionReceipt);
expect(result.blockNumber).toBe(transactionReceipt.blockNumber);
});

it("extracts transaction hash for the upgraded contract", () => {
const result = defaultContractUpgradableHandler.extract(log, transactionReceipt);
expect(result.transactionHash).toBe(transactionReceipt.hash);
});

it("extracts creator address for the upgraded contract", () => {
const result = defaultContractUpgradableHandler.extract(log, transactionReceipt);
expect(result.creatorAddress).toBe(transactionReceipt.from);
});

it("extracts logIndex for the upgraded contract", () => {
const result = defaultContractUpgradableHandler.extract(log, transactionReceipt);
expect(result.logIndex).toBe(log.index);
});

it("extracts implementation address for the upgraded contract", () => {
const result = defaultContractUpgradableHandler.extract(log, transactionReceipt);
expect(result.implementationAddress).toBe("0xa1810a1f32F4DC6c5112b5b837b6975E56b489cc");
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { types } from "zksync-ethers";
import { AbiCoder, keccak256 } from "ethers";
import { ExtractProxyAddressHandler } from "../interface/extractProxyHandler.interface";
import { ProxyAddress } from "../interface/proxyAddress.interface";

const abiCoder: AbiCoder = AbiCoder.defaultAbiCoder();

export const encodedUpgradableEvents = [
"Upgraded(address)",
"BeaconUpgraded(address)",
"OwnershipTransferred(address,address)",
"AdminChanged(address,address)",
"OwnershipTransferred(address,address)",
];

const decodedUpgradableEvents = encodedUpgradableEvents.map((event) => `${keccak256(Buffer.from(event)).toString()}`);

export const defaultContractUpgradableHandler: ExtractProxyAddressHandler = {
matches: (log: types.Log): boolean => {
return decodedUpgradableEvents.includes(log.topics[0]);
},
extract: (log: types.Log, txReceipt: types.TransactionReceipt): ProxyAddress => {
if (!log.topics[1]) {
return null;
}

const [address] = abiCoder.decode(["address"], log.topics[1]);
return {
address: log.address,
blockNumber: txReceipt.blockNumber,
transactionHash: txReceipt.hash,
creatorAddress: txReceipt.from,
logIndex: log.index,
implementationAddress: address,
};
},
};
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./default.handler";
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { types } from "zksync-ethers";
import { ProxyAddress } from "./proxyAddress.interface";

export interface ExtractProxyAddressHandler {
matches: (log: types.Log) => boolean;
extract: (log: types.Log, txReceipt: types.TransactionReceipt) => ProxyAddress | null;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { ContractAddress } from "../../address/interface/contractAddress.interface";

export type ProxyAddress = ContractAddress & {
implementationAddress: string;
};
82 changes: 82 additions & 0 deletions packages/data-fetcher/src/upgradable/upgradable.service.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import { Test } from "@nestjs/testing";
import { Logger } from "@nestjs/common";
import { mock } from "jest-mock-extended";
import { types } from "zksync-ethers";

import { UpgradableService } from "./upgradable.service";
describe("UpgradableService", () => {
let upgradableService: UpgradableService;

beforeEach(async () => {
const app = await Test.createTestingModule({
providers: [UpgradableService],
}).compile();

app.useLogger(mock<Logger>());

upgradableService = app.get<UpgradableService>(UpgradableService);
});

describe("getUpgradableAddresses", () => {
const logs = [
mock<types.Log>({
topics: [
"0x290afdae231a3fc0bbae8b1af63698b0a1d79b21ad17df0342dfb952fe74f8e5",
"0x000000000000000000000000c7e0220d02d549c4846a6ec31d89c3b670ebe35c",
"0x0100014340e955cbf39159da998b3374bee8f3c0b3c75a7a9e3df6b85052379d",
"0x000000000000000000000000dc187378edd8ed1585fb47549cc5fe633295d571",
],
index: 1,
}),
mock<types.Log>({
topics: [
"0xbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b",
"0x0000000000000000000000000db321efaa9e380d0b37b55b530cdaa62728b9a3",
],
address: "0xdc187378edD8Ed1585fb47549Cc5fe633295d571",
index: 2,
}),
mock<types.Log>({
topics: [
"0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0",
"0x000000000000000000000000481e48ce19781c3ca573967216dee75fdcf70f54",
],
address: "0xD144ca8Aa2E7DFECD56a3CCcBa1cd873c8e5db58",
index: 3,
}),
];

const transactionReceipt = mock<types.TransactionReceipt>({
blockNumber: 10,
hash: "transactionHash",
from: "from",
});

it("returns upgradable addresses", async () => {
const upgradableAddresses = await upgradableService.getUpgradableAddresses(logs, transactionReceipt);
expect(upgradableAddresses).toStrictEqual([
{
address: "0xdc187378edD8Ed1585fb47549Cc5fe633295d571",
implementationAddress: "0x0Db321EFaa9E380d0B37B55B530CDaA62728B9a3",
blockNumber: transactionReceipt.blockNumber,
transactionHash: transactionReceipt.hash,
creatorAddress: transactionReceipt.from,
logIndex: logs[1].index,
},
{
address: "0xD144ca8Aa2E7DFECD56a3CCcBa1cd873c8e5db58",
implementationAddress: "0x481E48Ce19781c3cA573967216deE75FDcF70F54",
blockNumber: transactionReceipt.blockNumber,
transactionHash: transactionReceipt.hash,
creatorAddress: transactionReceipt.from,
logIndex: logs[2].index,
},
]);
});

it("returns an empty array if no logs specified", async () => {
const result = await upgradableService.getUpgradableAddresses(null, transactionReceipt);
expect(result).toStrictEqual([]);
});
});
});
Loading

0 comments on commit e39da6d

Please sign in to comment.