Skip to content

Commit

Permalink
feat: migrate to Unified Bridge, introduce the ICTT bridge (ava-labs#82)
Browse files Browse the repository at this point in the history
  • Loading branch information
meeh0w authored and Charon-Fan committed Dec 26, 2024
1 parent 0d9bc43 commit 18d0b71
Show file tree
Hide file tree
Showing 53 changed files with 1,671 additions and 3,135 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
"@avalabs/avalanche-module": "0.11.12",
"@avalabs/avalanchejs": "4.1.0-alpha.25",
"@avalabs/bitcoin-module": "0.11.12",
"@avalabs/bridge-unified": "0.0.0-feat-ictt-configs-20241009072139",
"@avalabs/bridge-unified": "4.0.0",
"@avalabs/core-bridge-sdk": "3.1.0-alpha.19",
"@avalabs/core-chains-sdk": "3.1.0-alpha.19",
"@avalabs/core-coingecko-sdk": "3.1.0-alpha.19",
Expand Down
12 changes: 12 additions & 0 deletions src/background/services/featureFlags/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ export enum FeatureGates {
SEEEDLESS_MFA_SETTINGS = 'seedless-mfa-settings',
SEEDLESS_OPTIONAL_MFA = 'seedless-optional-mfa',
UNIFIED_BRIDGE_CCTP = 'unified-bridge-cctp',
UNIFIED_BRIDGE_ICTT = 'unified-bridge-ictt',
UNIFIED_BRIDGE_AB_EVM = 'unified-bridge-ab-evm',
UNIFIED_BRIDGE_AB_AVA_TO_BTC = 'unified-bridge-ab-ava-to-btc',
UNIFIED_BRIDGE_AB_BTC_TO_AVA = 'unified-bridge-ab-btc-to-ava',
DEBANK_TRANSACTION_PARSING = 'debank-transaction-parsing',
DEBANK_TRANSACTION_PRE_EXECUTION = 'debank-transaction-pre-execution',
PRIMARY_ACCOUNT_REMOVAL = 'primary-account-removal',
Expand Down Expand Up @@ -77,6 +81,10 @@ export const DISABLED_FLAG_VALUES: FeatureFlags = {
[FeatureGates.SEEEDLESS_MFA_SETTINGS]: false,
[FeatureGates.SEEDLESS_OPTIONAL_MFA]: false,
[FeatureGates.UNIFIED_BRIDGE_CCTP]: false,
[FeatureGates.UNIFIED_BRIDGE_ICTT]: false,
[FeatureGates.UNIFIED_BRIDGE_AB_EVM]: false,
[FeatureGates.UNIFIED_BRIDGE_AB_AVA_TO_BTC]: false,
[FeatureGates.UNIFIED_BRIDGE_AB_BTC_TO_AVA]: false,
[FeatureGates.DEBANK_TRANSACTION_PARSING]: false,
[FeatureGates.DEBANK_TRANSACTION_PRE_EXECUTION]: false,
[FeatureGates.PRIMARY_ACCOUNT_REMOVAL]: false,
Expand Down Expand Up @@ -122,6 +130,10 @@ export const DEFAULT_FLAGS: FeatureFlags = {
[FeatureGates.SEEEDLESS_MFA_SETTINGS]: true,
[FeatureGates.SEEDLESS_OPTIONAL_MFA]: true,
[FeatureGates.UNIFIED_BRIDGE_CCTP]: true,
[FeatureGates.UNIFIED_BRIDGE_ICTT]: true,
[FeatureGates.UNIFIED_BRIDGE_AB_EVM]: true,
[FeatureGates.UNIFIED_BRIDGE_AB_AVA_TO_BTC]: true,
[FeatureGates.UNIFIED_BRIDGE_AB_BTC_TO_AVA]: true,
[FeatureGates.DEBANK_TRANSACTION_PARSING]: false,
[FeatureGates.DEBANK_TRANSACTION_PRE_EXECUTION]: false,
[FeatureGates.PRIMARY_ACCOUNT_REMOVAL]: true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,33 @@ describe('background/services/storage/schemaMigrations/migrations/unified_bridge
error: undefined,
value: stateWithPendingTransfers,
});

expect(
unified_bridge_v2.previousSchema.validate({
addresses: [],
pendingTransfers: {},
version: 1,
})
).toEqual({
error: undefined,
value: {
addresses: [],
pendingTransfers: {},
version: 1,
},
});

expect(
unified_bridge_v2.previousSchema.validate({ pendingTransfers: {} })
).toEqual({
error: undefined,
value: { pendingTransfers: {} },
});

expect(unified_bridge_v2.previousSchema.validate({})).toEqual({
error: undefined,
value: {},
});
});

it('rejects incorrect inputs', () => {
Expand Down Expand Up @@ -151,7 +178,7 @@ describe('background/services/storage/schemaMigrations/migrations/unified_bridge
},
targetConfirmationCount: 2,
targetRequiredConfirmationCount: 4,
targetStartBlockNumber: 1234567,
targetStartBlockNumber: 1234567n,
},
},
version: 2,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@ const VERSION = 2;
// Schemas & types below only list the relevant properties
// that actually changed. The rest is untouched & untyped.
type LegacyBridgeTransfer = {
amountDecimals: number;
amountDecimals?: number; // the very first version of the SDK did not have it
symbol: string;
sourceChain: {
chainId: string;
};
requiredSourceConfirmationCount: number;
requiredTargetConfirmationCount: number;
startBlockNumber?: number;
startBlockNumber?: number | bigint; // Even though types say it's always a bigint, I've seen it as a number in my storage.
};

type NewBridgeTransfer = {
Expand All @@ -25,12 +25,13 @@ type NewBridgeTransfer = {
};
sourceRequiredConfirmationCount: number;
targetRequiredConfirmationCount: number;
targetStartBlockNumber?: number;
targetStartBlockNumber?: bigint;
};

type PreviousSchema = {
pendingTransfers?: Record<string, LegacyBridgeTransfer>;
addresses?: string[];
version?: number;
};

type NewSchema = {
Expand Down Expand Up @@ -63,7 +64,7 @@ const previousSchema = Joi.object<PreviousSchema>({
sourceChain: Joi.object({ chainId: Joi.string() }).unknown(true),
}).unknown(true)
),
});
}).unknown(true);

const getUsdcAddressByChainId = (caipId: string) => {
switch (caipId) {
Expand Down Expand Up @@ -102,16 +103,19 @@ const up = async (
newTransfers[id] = {
...rest,
asset: {
// Prior to this schema upgrad, only USDC was possible to bridge (via CCTP)
decimals: amountDecimals,
// Prior to this schema upgrade, only USDC was possible to bridge (via CCTP)
decimals: amountDecimals ?? 6,
symbol,
type: 'erc20',
name: 'USD Coin',
address: getUsdcAddressByChainId(rest.sourceChain.chainId),
},
sourceRequiredConfirmationCount: requiredSourceConfirmationCount,
targetRequiredConfirmationCount: requiredTargetConfirmationCount,
targetStartBlockNumber: startBlockNumber,
targetStartBlockNumber:
typeof startBlockNumber !== 'undefined'
? BigInt(startBlockNumber)
: undefined,
};
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,10 @@ export const migrateToLatest = async <T>(
return result;
}

if (currentMigration.migration.previousSchema.validate(result).error) {
const { error: validationError } =
currentMigration.migration.previousSchema.validate(result);

if (validationError) {
throw new Error(
`Error while upgrading ${key} to version ${currentMigration.version}`
);
Expand Down
35 changes: 16 additions & 19 deletions src/background/services/unifiedBridge/UnifiedBridgeService.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ describe('src/background/services/unifiedBridge/UnifiedBridgeService', () => {
isMainnet: jest.fn(),
getNetwork: jest.fn(),
getProviderForNetwork: jest.fn(),
getBitcoinProvider: jest.fn(),
sendTransaction: jest.fn(),
} as any;

Expand All @@ -41,6 +42,10 @@ describe('src/background/services/unifiedBridge/UnifiedBridgeService', () => {
featureFlags: {
[FeatureGates.IMPORT_FIREBLOCKS]: true,
[FeatureGates.UNIFIED_BRIDGE_CCTP]: true,
[FeatureGates.UNIFIED_BRIDGE_ICTT]: true,
[FeatureGates.UNIFIED_BRIDGE_AB_AVA_TO_BTC]: true,
[FeatureGates.UNIFIED_BRIDGE_AB_BTC_TO_AVA]: true,
[FeatureGates.UNIFIED_BRIDGE_AB_EVM]: true,
},
addListener: jest.fn(),
} as any;
Expand All @@ -64,6 +69,7 @@ describe('src/background/services/unifiedBridge/UnifiedBridgeService', () => {
networkService.getNetwork.mockImplementation(async (chainId) => ({
chainId,
}));
networkService.getBitcoinProvider.mockResolvedValue({} as any);
});

it('creates core instance with proper environment', async () => {
Expand Down Expand Up @@ -118,6 +124,10 @@ describe('src/background/services/unifiedBridge/UnifiedBridgeService', () => {
// Toggle an irrelevant flag off
mockFeatureFlagChanges({
[FeatureGates.UNIFIED_BRIDGE_CCTP]: true,
[FeatureGates.UNIFIED_BRIDGE_ICTT]: true,
[FeatureGates.UNIFIED_BRIDGE_AB_AVA_TO_BTC]: true,
[FeatureGates.UNIFIED_BRIDGE_AB_BTC_TO_AVA]: true,
[FeatureGates.UNIFIED_BRIDGE_AB_EVM]: true,
[FeatureGates.IMPORT_FIREBLOCKS]: false,
});

Expand All @@ -127,6 +137,10 @@ describe('src/background/services/unifiedBridge/UnifiedBridgeService', () => {
// Toggle a relevant flag off
mockFeatureFlagChanges({
[FeatureGates.UNIFIED_BRIDGE_CCTP]: false,
[FeatureGates.UNIFIED_BRIDGE_ICTT]: true,
[FeatureGates.UNIFIED_BRIDGE_AB_AVA_TO_BTC]: true,
[FeatureGates.UNIFIED_BRIDGE_AB_BTC_TO_AVA]: true,
[FeatureGates.UNIFIED_BRIDGE_AB_EVM]: true,
[FeatureGates.IMPORT_FIREBLOCKS]: false,
});

Expand Down Expand Up @@ -180,30 +194,13 @@ describe('src/background/services/unifiedBridge/UnifiedBridgeService', () => {
});

new UnifiedBridgeService(networkService, storageService, flagsService);
await jest.runAllTimersAsync();
await jest.runAllTicks();

expect(getEnabledBridgeServices).toHaveBeenCalledTimes(1);
expect(getEnabledBridgeServices).toHaveBeenCalledTimes(4);
expect(wait).toHaveBeenNthCalledWith(1, 2000);

jest.advanceTimersByTime(2000);
await jest.runOnlyPendingTimers();
await jest.runAllTicks();

expect(getEnabledBridgeServices).toHaveBeenCalledTimes(2);
expect(wait).toHaveBeenNthCalledWith(2, 4000);

jest.advanceTimersByTime(4000);
await jest.runOnlyPendingTimers();
await jest.runAllTicks();

expect(getEnabledBridgeServices).toHaveBeenCalledTimes(3);
expect(wait).toHaveBeenNthCalledWith(3, 8000);

jest.advanceTimersByTime(8000);
await jest.runOnlyPendingTimers();
await jest.runAllTicks();

expect(getEnabledBridgeServices).toHaveBeenCalledTimes(4);
expect(createUnifiedBridgeService).toHaveBeenCalled();
});
});
Expand Down
56 changes: 47 additions & 9 deletions src/background/services/unifiedBridge/UnifiedBridgeService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@ import { singleton } from 'tsyringe';
import {
AnalyzeTxParams,
AnalyzeTxResult,
BridgeInitializer,
BridgeTransfer,
BridgeType,
createUnifiedBridgeService,
Environment,
getEnabledBridgeServices,
} from '@avalabs/bridge-unified';
import { BitcoinProvider } from '@avalabs/core-wallets-sdk';
import { wait } from '@avalabs/core-utils-sdk';
import EventEmitter from 'events';

Expand All @@ -32,6 +34,7 @@ import {
import sentryCaptureException, {
SentryExceptionTypes,
} from '@src/monitoring/sentryCaptureException';
import { getEnabledBridgeTypes } from '@src/utils/getEnabledBridgeTypes';

@singleton()
export class UnifiedBridgeService implements OnStorageReady {
Expand Down Expand Up @@ -110,16 +113,49 @@ export class UnifiedBridgeService implements OnStorageReady {
});
}

#getDisabledBridges(): BridgeType[] {
const bridges: BridgeType[] = [
BridgeType.ICTT_ERC20_ERC20,
BridgeType.AVALANCHE_EVM,
];
#getBridgeInitializers(
bitcoinProvider: BitcoinProvider
): BridgeInitializer[] {
return getEnabledBridgeTypes(this.#flagStates).map((type) =>
this.#getInitializerForBridgeType(type, bitcoinProvider)
);
}

if (!this.#flagStates[FeatureGates.UNIFIED_BRIDGE_CCTP]) {
bridges.push(BridgeType.CCTP);
#getInitializerForBridgeType(
type: BridgeType,
bitcoinProvider: BitcoinProvider
): BridgeInitializer {
// This backend service is only used for transaction tracking purposes,
// therefore we don't need to provide true signing capabilities.
const dummySigner = {
async sign() {
return '0x' as const;
},
};

switch (type) {
case BridgeType.CCTP:
case BridgeType.ICTT_ERC20_ERC20:
case BridgeType.AVALANCHE_EVM:
return {
type,
signer: dummySigner,
};

case BridgeType.AVALANCHE_AVA_BTC:
return {
type,
signer: dummySigner,
bitcoinFunctions: bitcoinProvider,
};

case BridgeType.AVALANCHE_BTC_AVA:
return {
type,
signer: dummySigner,
bitcoinFunctions: bitcoinProvider,
};
}
return bridges;
}

async #recreateService() {
Expand All @@ -128,11 +164,13 @@ export class UnifiedBridgeService implements OnStorageReady {
: Environment.TEST;

try {
const bitcoinProvider = await this.networkService.getBitcoinProvider();

this.#core = createUnifiedBridgeService({
environment,
enabledBridgeServices: await getEnabledBridgeServices(
environment,
this.#getDisabledBridges()
this.#getBridgeInitializers(bitcoinProvider)
),
});
this.#failedInitAttempts = 0;
Expand Down
9 changes: 8 additions & 1 deletion src/background/services/unifiedBridge/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,20 @@ export enum UnifiedBridgeError {
InvalidFee = 'invalid-fee',
UnsupportedNetwork = 'unsupported-network',
InvalidTxPayload = 'invalid-tx-payload',
NonBitcoinAccount = 'non-bitcoin-account',
}

export type UnifiedBridgeState = {
pendingTransfers: Record<string, BridgeTransfer>;
};

export const UNIFIED_BRIDGE_TRACKED_FLAGS = [FeatureGates.UNIFIED_BRIDGE_CCTP];
export const UNIFIED_BRIDGE_TRACKED_FLAGS = [
FeatureGates.UNIFIED_BRIDGE_CCTP,
FeatureGates.UNIFIED_BRIDGE_ICTT,
FeatureGates.UNIFIED_BRIDGE_AB_AVA_TO_BTC,
FeatureGates.UNIFIED_BRIDGE_AB_BTC_TO_AVA,
FeatureGates.UNIFIED_BRIDGE_AB_EVM,
];

export const UNIFIED_BRIDGE_DEFAULT_STATE: UnifiedBridgeState = {
pendingTransfers: {},
Expand Down
2 changes: 1 addition & 1 deletion src/components/common/ContainedDropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ export const ContainedDropdown = ({
width: width ?? '100%',
borderRadius: borderRadius ?? spacing(0, 0, 1, 1),
margin: margin ?? '0',
height: isOpen ? `${height || calculatedHeight}px` : 0,
height: isOpen ? `${height || calculatedHeight - top}px` : 0,
top,
opacity: isOpen ? 1 : 0,
}}
Expand Down
Loading

0 comments on commit 18d0b71

Please sign in to comment.