Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Enhance ERC registry update with efficient merge and deduplication #1136

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -108,33 +108,49 @@ export class RegistryGenerator {
}

/**
* Updates a registry file with new contracts, removing duplicates if any.
* @param {string} filePath - Path to the registry file
* @param {ERCOutputInterface[]} newContracts - New contracts to add to registry
* @returns {Promise<void>} Promise that resolves when registry is updated
* Updates a registry file with new contracts by merging them with existing contracts,
* ensuring the registry remains sorted and free of duplicates.
*
* @param {string} filePath - The file path to the registry file.
* @param {ERCOutputInterface[]} newContracts - The new contracts to add to the registry.
* @returns {Promise<void>} - A promise that resolves once the registry is successfully updated.
*
* @private
*/
private async updateRegistry(
filePath: string,
newContracts: ERCOutputInterface[]
): Promise<void> {
let uniqueContracts: ERCOutputInterface[] = [];
const fileContent = this.readContentsFromFile(filePath);
const existingContracts = fileContent
? (JSON.parse(fileContent) as ERCOutputInterface[])
: [];

// Create a Map to deduplicate contracts by contractId
const contractMap = new Map(
[...existingContracts, ...newContracts].map((contract) => [
contract.contractId,
contract,
])
);
if (!existingContracts.length) {
uniqueContracts = newContracts;
} else if (
// Since both arrays are sorted in ascending order, if the `contractId` of the last item in `existingContracts`
// is less than the `contractId` of the first item in `newContracts`, just merged the contracts and remove dups without sorting.
existingContracts[existingContracts.length - 1].contractId <
newContracts[0].contractId
) {
// Create a Map to deduplicate contracts by contractId
const contractMap = new Map(
[...existingContracts, ...newContracts].map((contract) => [
contract.contractId,
contract,
])
);
uniqueContracts = Array.from(contractMap.values());
} else {
uniqueContracts = Helper.mergeAndSort(existingContracts, newContracts);
}

await this.writeContentsToFile(filePath, uniqueContracts);

// Convert Map values back to array for file writing
const uniqueContracts = Array.from(contractMap.values());

await this.writeContentsToFile(filePath, uniqueContracts);
console.log(
`Finished writing ${newContracts.length} new ERC token contracts to registry.`
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,9 @@
*/

import axios, { AxiosInstance } from 'axios';
import path from 'path';
import constants from './constants';
import { ERCOutputInterface } from '../schemas/ERCRegistrySchemas';
import path from 'path';

export class Helper {
/**
Expand Down Expand Up @@ -91,4 +92,54 @@ export class Helper {
}),
};
}

/**
* Merges two sorted arrays of contract objects and returns a single sorted array without duplicates.
* This function assumes both input arrays are sorted by `contractId` in ascending order.
*
* @param {ERCOutputInterface[]} existingContracts - The first array of contract objects, already sorted by `contractId`.
* @param {ERCOutputInterface[]} newContracts - The second array of contract objects, already sorted by `contractId`.
* @returns {ERCOutputInterface[]} A merged and sorted array of contract objects, with duplicate `contractId` entries removed.
*/
static mergeAndSort(
existingContracts: ERCOutputInterface[],
newContracts: ERCOutputInterface[]
): ERCOutputInterface[] {
let i = 0;
let j = 0;
const mergedContracts: ERCOutputInterface[] = [];

// Merge two sorted arrays
while (i < existingContracts.length && j < newContracts.length) {
const existing = existingContracts[i];
const incoming = newContracts[j];

const existingContractIdComparedValue = Number(
existing.contractId.split('.')[2]
);
const incomingContractIdComparedValue = Number(
incoming.contractId.split('.')[2]
);

if (existingContractIdComparedValue < incomingContractIdComparedValue) {
mergedContracts.push(existing);
i++;
} else if (
existingContractIdComparedValue > incomingContractIdComparedValue
) {
mergedContracts.push(incoming);
j++;
} else {
// remove duplicated objects
mergedContracts.push(existing);
i++;
j++;
}
}

// Add any remaining elements from each array
return mergedContracts
.concat(existingContracts.slice(i))
.concat(newContracts.slice(j));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/*-
*
* Hedera Smart Contracts
*
* Copyright (C) 2024 Hedera Hashgraph, LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/

import { Helper } from '../../../src/utils/helper';

describe('Helper', () => {
describe('mergeAndSort', () => {
const existingContracts = [
{
contractId: '0.0.14902',
address: '0x0000...',
name: '',
symbol: '',
totalSupply: 0,
decimals: 0,
},
{
contractId: '0.0.15701',
address: '0x0000...',
name: 'CUSD',
symbol: 'CUSD',
totalSupply: 0,
decimals: 18,
},
];

const newContracts = [
{
contractId: '0.0.14903',
address: '0x0000...',
name: 'CUSD',
symbol: 'CUSD',
totalSupply: 0,
decimals: 18,
},
{
contractId: '0.0.15701',
address: '0x0000...',
name: 'CUSD',
symbol: 'CUSD',
totalSupply: 0,
decimals: 18,
},
{
contractId: '0.0.15707',
address: '0x0000...',
name: 'CUSD',
symbol: 'CUSD',
totalSupply: 0,
decimals: 18,
},
];

it('merges, sorts, and removes duplicate objects from the two contract arrays', () => {
const result = Helper.mergeAndSort(existingContracts, newContracts);

const expectedResult = [
existingContracts[0], // '0.0.14902'
newContracts[0], // '0.0.14903'
existingContracts[1], // '0.0.15701' (duplicate removed)
newContracts[2], // '0.0.15707'
];

expect(result).toEqual(expectedResult);
});
});
});