Skip to content

Commit

Permalink
Add balanceDiff on simulate bundle endpoint (#89)
Browse files Browse the repository at this point in the history
* add token holder repository

* feat: add token holder endpoint

* add tenderly bundle simulation endpoint

* refactor: remove unused code

* feat: add ethplorer token holder repository

* add null value cache on token holder

* chore: add fallback strategy on token holder

* refactor: add generic fallback repository

* refactor: add generic cache repository factory

* revert: usdPrice changes

* refactor: rename tenderly endpoint to simulation

* rename tenderly repository and service to simulation

* refactor cache factory to receive converfns

* fix import typo

* revert refactoring to the fallback and cache factories

* chore: add balance diff

* rename balancesDiff to cumulativeBalancesDiff

* implement shoom3301 suggestion
  • Loading branch information
yvesfracari authored Oct 22, 2024
1 parent a77087e commit ab05a2b
Show file tree
Hide file tree
Showing 6 changed files with 280 additions and 9 deletions.
13 changes: 12 additions & 1 deletion apps/api/src/app/routes/__chainId/simulation/simulateBundle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ const successSchema = {
type: 'array',
items: {
type: 'object',
required: ['status', 'id', 'link'],
required: ['status', 'id', 'link', 'cumulativeBalancesDiff'],
additionalProperties: false,
properties: {
status: {
Expand All @@ -38,6 +38,17 @@ const successSchema = {
description: 'Link to the transaction on Tenderly.',
type: 'string',
},
cumulativeBalancesDiff: {
title: 'Balances Diff',
description: 'Changes in balances of the token holders.',
type: 'object',
additionalProperties: {
type: 'object',
additionalProperties: {
type: 'string',
},
},
},
},
},
} as const satisfies JSONSchema;
Expand Down
2 changes: 1 addition & 1 deletion apps/api/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ const logger = pino({
});

const host = process.env.HOST ?? 'localhost';
const port = process.env.PORT ? Number(process.env.PORT) : 3000;
const port = process.env.PORT ? Number(process.env.PORT) : 3001;

// Instantiate Fastify with some config
export const server = Fastify({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ export interface SimulationData {
link: string;
status: boolean;
id: string;
// { [address: string]: { [token: string]: balanceDiff: string } }
// example: { '0x123': { '0x456': '100', '0xabc': '-100' } }
cumulativeBalancesDiff: Record<string, Record<string, string>>;
}

export const simulationRepositorySymbol = Symbol.for('SimulationRepository');
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { SupportedChainId } from '@cowprotocol/shared';
import {
AssetChange,
SimulationError,
TenderlyBundleSimulationResponse,
TenderlySimulatePayload,
Expand All @@ -15,6 +16,7 @@ import {
SimulationInput,
SimulationRepository,
} from './SimulationRepository';
import { BigNumber } from 'ethers';

export const tenderlyRepositorySymbol = Symbol.for('TenderlyRepository');

Expand Down Expand Up @@ -48,17 +50,70 @@ export class SimulationRepositoryTenderly implements SimulationRepository {
return null;
}

// TODO: Add ERC20 state diffs
return response.simulation_results.map(({ simulation }) => ({
status: simulation.status,
id: simulation.id,
link: getTenderlySimulationLink(simulation.id),
}));
const balancesDiff = this.buildBalancesDiff(
response.simulation_results.map(
(result) => result.transaction.transaction_info.asset_changes || []
)
);

return response.simulation_results.map((simulation_result, i) => {
return {
status: simulation_result.simulation.status,
id: simulation_result.simulation.id,
link: getTenderlySimulationLink(simulation_result.simulation.id),
cumulativeBalancesDiff: balancesDiff[i],
};
});
}

checkBundleSimulationError(
response: TenderlyBundleSimulationResponse | SimulationError
): response is SimulationError {
return (response as SimulationError).error !== undefined;
}

buildBalancesDiff(
assetChangesList: AssetChange[][]
): Record<string, Record<string, string>>[] {
const cumulativeBalancesDiff: Record<string, Record<string, string>> = {};

return assetChangesList.map((assetChanges) => {
assetChanges.forEach((change) => {
const { token_info, from, to, raw_amount } = change;
const { contract_address } = token_info;

// Helper function to update balance
const updateBalance = (
address: string,
tokenSymbol: string,
changeAmount: string
) => {
if (!cumulativeBalancesDiff[address]) {
cumulativeBalancesDiff[address] = {};
}
if (!cumulativeBalancesDiff[address][tokenSymbol]) {
cumulativeBalancesDiff[address][tokenSymbol] = '0';
}

const currentBalance = BigNumber.from(
cumulativeBalancesDiff[address][tokenSymbol]
);
const changeValue = BigNumber.from(changeAmount);
const newBalance = currentBalance.add(changeValue);

cumulativeBalancesDiff[address][tokenSymbol] = newBalance.toString();
};

if (from) {
updateBalance(from, contract_address, `-${raw_amount}`);
}

if (to) {
updateBalance(to, contract_address, raw_amount);
}
});

return { ...cumulativeBalancesDiff };
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
TENDERLY_ORG_NAME,
TENDERLY_PROJECT_NAME,
} from '../datasources/tenderlyApi';
import { AssetChange } from './tenderlyTypes';

// Transfering ETH from WETH to NULL ADDRESS
const TENDERLY_SIMULATION = {
Expand Down Expand Up @@ -45,7 +46,7 @@ describe('SimulationRepositoryTenderly', () => {
expect(TENDERLY_PROJECT_NAME).toBeDefined();
});

describe('getTopTokenHolders', () => {
describe('postBundleSimulation', () => {
it('should return simulation data for success simulation', async () => {
const tenderlySimulationResult =
await tenderlyRepository.postBundleSimulation(
Expand Down Expand Up @@ -80,4 +81,184 @@ describe('SimulationRepositoryTenderly', () => {
expect(tenderlySimulationResult?.[0].status).toBeFalsy();
}, 100000);
});
describe('buildBalancesDiff', () => {
it('should correctly process a single asset change', () => {
const input: AssetChange[][] = [
[
{
token_info: {
standard: 'ERC20',
type: 'TOKEN',
contract_address: '0x123',
symbol: 'TEST',
name: 'Test Token',
logo: 'test.png',
decimals: 18,
dollar_value: '1.00',
},
type: 'TRANSFER',
from: 'address1',
to: 'address2',
amount: '100',
raw_amount: '100000000000000000000',
},
],
];

const expected = [
{
address1: {
'0x123': '-100000000000000000000',
},
address2: {
'0x123': '100000000000000000000',
},
},
];

expect(tenderlyRepository.buildBalancesDiff(input)).toEqual(expected);
});

it('should correctly process multiple asset changes', () => {
const input: AssetChange[][] = [
[
{
token_info: {
standard: 'ERC20',
type: 'TOKEN',
contract_address: '0x123',
symbol: 'TEST',
name: 'Test Token',
logo: 'test.png',
decimals: 18,
dollar_value: '1.00',
},
type: 'TRANSFER',
from: 'address1',
to: 'address2',
amount: '100',
raw_amount: '100000000000000000000',
},
],
[
{
token_info: {
standard: 'ERC20',
type: 'TOKEN',
contract_address: '0x456',
symbol: 'TEST2',
name: 'Test Token 2',
logo: 'test2.png',
decimals: 18,
dollar_value: '2.00',
},
type: 'TRANSFER',
from: 'address2',
to: 'address3',
amount: '50',
raw_amount: '50000000000000000000',
},
],
];

const expected = [
{
address1: {
'0x123': '-100000000000000000000',
},
address2: {
'0x123': '100000000000000000000',
},
},
{
address1: {
'0x123': '-100000000000000000000',
},
address2: {
'0x123': '100000000000000000000',
'0x456': '-50000000000000000000',
},
address3: {
'0x456': '50000000000000000000',
},
},
];

expect(tenderlyRepository.buildBalancesDiff(input)).toEqual(expected);
});

it('should handle empty input', () => {
const input: AssetChange[][] = [];
expect(tenderlyRepository.buildBalancesDiff(input)).toEqual([]);
});

it('should handle input with empty asset changes', () => {
const input: AssetChange[][] = [[], []];
expect(tenderlyRepository.buildBalancesDiff(input)).toEqual([{}, {}]);
});

it('should correctly handle cumulative changes', () => {
const input: AssetChange[][] = [
[
{
token_info: {
standard: 'ERC20',
type: 'TOKEN',
contract_address: '0x123',
symbol: 'TEST',
name: 'Test Token',
logo: 'test.png',
decimals: 18,
dollar_value: '1.00',
},
type: 'TRANSFER',
from: 'address1',
to: 'address2',
amount: '100',
raw_amount: '100000000000000000000',
},
],
[
{
token_info: {
standard: 'ERC20',
type: 'TOKEN',
contract_address: '0x123',
symbol: 'TEST',
name: 'Test Token',
logo: 'test.png',
decimals: 18,
dollar_value: '1.00',
},
type: 'TRANSFER',
from: 'address2',
to: 'address1',
amount: '50',
raw_amount: '50000000000000000000',
},
],
];

const expected = [
{
address1: {
'0x123': '-100000000000000000000',
},
address2: {
'0x123': '100000000000000000000',
},
},
{
address1: {
'0x123': '-50000000000000000000',
},
address2: {
'0x123': '50000000000000000000',
},
},
];

expect(tenderlyRepository.buildBalancesDiff(input)).toEqual(expected);
});
});
});
21 changes: 21 additions & 0 deletions libs/repositories/src/SimulationRepository/tenderlyTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,27 @@ interface TransactionInfo {
raw_state_diff: null;
console_logs: null;
created_at: Date;
// Note: manually added
asset_changes?: AssetChange[];
}

// Note: manually added
export interface AssetChange {
token_info: {
standard: string;
type: string;
contract_address: string;
symbol: string;
name: string;
logo: string;
decimals: number;
dollar_value: string;
};
type: string;
from?: string;
to?: string;
amount: string;
raw_amount: string;
}

interface StackTrace {
Expand Down

0 comments on commit ab05a2b

Please # to comment.