Skip to content

Commit

Permalink
Merge branch 'master' into rotorsoft/auth-middleware-updated
Browse files Browse the repository at this point in the history
  • Loading branch information
rotorsoft committed Nov 12, 2024
2 parents 21d18a6 + 10a32ec commit 737c156
Show file tree
Hide file tree
Showing 23 changed files with 858 additions and 14 deletions.
1 change: 1 addition & 0 deletions libs/adapters/src/trpc/middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ export enum Tag {
SuperAdmin = 'SuperAdmin',
DiscordBot = 'DiscordBot',
Token = 'Token',
Contest = 'Contest',
}

export type Commit<Input extends ZodSchema, Output extends ZodSchema> = (
Expand Down
16 changes: 15 additions & 1 deletion libs/core/src/integration/chain-event.schemas.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
// TODO: temporary - will be deleted as part of chain-events removal
import { ETHERS_BIG_NUMBER, EVM_ADDRESS } from '@hicommonwealth/schemas';
import {
ETHERS_BIG_NUMBER,
EVM_ADDRESS,
zBoolean,
} from '@hicommonwealth/schemas';
import { z } from 'zod';

export const CommunityStakeTrade = z.tuple([
Expand All @@ -24,3 +28,13 @@ export const NamespaceDeployed = z.tuple([
export const LaunchpadTokenCreated = z.tuple([
z.string().describe('tokenAddress'),
]);

export const LaunchpadTrade = z.tuple([
EVM_ADDRESS.describe('trader'),
EVM_ADDRESS.describe('tokenAddress'), // The contract definition is incorrect (confirmed with Ian)
zBoolean.describe('isBuy'),
ETHERS_BIG_NUMBER.describe('communityTokenAmount'),
ETHERS_BIG_NUMBER.describe('ethAmount'),
ETHERS_BIG_NUMBER.describe('protocolEthAmount'),
ETHERS_BIG_NUMBER.describe('supply'),
]);
9 changes: 9 additions & 0 deletions libs/core/src/integration/events.schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { z } from 'zod';
import {
CommunityStakeTrade,
LaunchpadTokenCreated,
LaunchpadTrade,
NamespaceDeployed,
} from './chain-event.schemas';
import { EventMetadata } from './util.schemas';
Expand Down Expand Up @@ -185,6 +186,14 @@ export const ChainEventCreated = z.union([
}),
parsedArgs: LaunchpadTokenCreated,
}),
ChainEventCreatedBase.extend({
eventSource: ChainEventCreatedBase.shape.eventSource.extend({
eventSignature: z.literal(
'0x9adcf0ad0cda63c4d50f26a48925cf6405df27d422a39c456b5f03f661c82982',
),
}),
parsedArgs: LaunchpadTrade,
}),
]);

// on-chain contest manager events
Expand Down
3 changes: 3 additions & 0 deletions libs/model/src/chainEventSignatures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,6 @@ export const communityStakeTradeEventSignature =

export const launchpadTokenLaunchedEventSignature =
'0xd7ca5dc2f8c6bb37c3a4de2a81499b25f8ca8bbb3082010244fe747077d0f6cc';

export const launchpadTradeEventSignature =
'0x9adcf0ad0cda63c4d50f26a48925cf6405df27d422a39c456b5f03f661c82982';
5 changes: 5 additions & 0 deletions libs/model/src/models/associations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -215,4 +215,9 @@ export const buildAssociations = (db: DB) => {
onDelete: 'CASCADE',
},
);

db.Token.withMany(db.LaunchpadTrade, {
foreignKey: 'token_address',
onDelete: 'NO ACTION',
});
};
2 changes: 2 additions & 0 deletions libs/model/src/models/factories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import EvmEventSource from './evmEventSource';
import Group from './group';
import GroupPermission from './groupPermission';
import LastProcessedEvmBlock from './lastProcessedEvmBlock';
import LaunchpadTrade from './launchpad_trade';
import Membership from './membership';
import Outbox from './outbox';
import Poll from './poll';
Expand Down Expand Up @@ -66,6 +67,7 @@ export const Factories = {
Group,
GroupPermission,
LastProcessedEvmBlock,
LaunchpadTrade,
Membership,
Outbox,
Poll,
Expand Down
1 change: 1 addition & 0 deletions libs/model/src/models/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ export * from './email_update_token';
export * from './evmEventSource';
export * from './group';
export * from './lastProcessedEvmBlock';
export * from './launchpad_trade';
export * from './membership';
export * from './outbox';
export * from './poll';
Expand Down
62 changes: 62 additions & 0 deletions libs/model/src/models/launchpad_trade.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { LaunchpadTrade } from '@hicommonwealth/schemas';
import Sequelize from 'sequelize';
import { z } from 'zod';
import type { ModelInstance } from './types';

export type LaunchpadTradeAttributes = z.infer<typeof LaunchpadTrade>;

export type LaunchpadTradeInstance = ModelInstance<LaunchpadTradeAttributes>;

export type LaunchpadTradeModelStatic =
Sequelize.ModelStatic<LaunchpadTradeInstance>;

export default (sequelize: Sequelize.Sequelize): LaunchpadTradeModelStatic =>
<LaunchpadTradeModelStatic>sequelize.define<LaunchpadTradeInstance>(
'LaunchpadTrade',
{
eth_chain_id: {
type: Sequelize.INTEGER,
allowNull: false,
primaryKey: true,
},
transaction_hash: {
type: Sequelize.STRING,
allowNull: false,
primaryKey: true,
},
token_address: {
type: Sequelize.STRING,
allowNull: false,
},
trader_address: {
type: Sequelize.STRING,
allowNull: false,
},
is_buy: {
type: Sequelize.BOOLEAN,
allowNull: false,
},
community_token_amount: {
type: Sequelize.DECIMAL(78, 0),
allowNull: false,
},
price: {
type: Sequelize.DECIMAL(78, 0),
allowNull: false,
},
floating_supply: {
type: Sequelize.DECIMAL(78, 0),
allowNull: false,
},
timestamp: {
type: Sequelize.INTEGER,
allowNull: false,
},
},
{
timestamps: false,
tableName: 'LaunchpadTrades',
underscored: true,
indexes: [{ fields: ['token_address'] }],
},
);
45 changes: 45 additions & 0 deletions libs/model/src/services/commonProtocol/launchpadHelpers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import Web3 from 'web3';

export async function getLaunchpadTradeTransaction({
rpc,
transactionHash,
}: {
rpc: string;
transactionHash: string;
}) {
const web3 = new Web3(rpc);

const txReceipt = await web3.eth.getTransactionReceipt(transactionHash);
if (!txReceipt) {
return;
}

const block = await web3.eth.getBlock(txReceipt.blockHash.toString());

const {
0: traderAddress,
1: tokenAddress,
2: isBuy,
3: communityTokenAmount,
4: ethAmount,
5: protocolEthAmount,
6: floatingSupply,
} = web3.eth.abi.decodeParameters(
['address', 'address', 'bool', 'uint256', 'uint256', 'uint256', 'uint256'],
txReceipt.logs[1].data!.toString(),
);

return {
txReceipt,
block,
parsedArgs: {
traderAddress: traderAddress as string,
tokenAddress: tokenAddress as string,
isBuy: isBuy as boolean,
communityTokenAmount: communityTokenAmount as bigint,
ethAmount: ethAmount as bigint,
protocolEthAmount: protocolEthAmount as bigint,
floatingSupply: floatingSupply as bigint,
},
};
}
74 changes: 74 additions & 0 deletions libs/model/src/token/CreateLaunchpadTrade.command.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { Command, InvalidState } from '@hicommonwealth/core';
import * as schemas from '@hicommonwealth/schemas';
import { commonProtocol } from '@hicommonwealth/shared';
import { models } from '../database';
import { mustExist } from '../middleware/guards';
import { getLaunchpadTradeTransaction } from '../services/commonProtocol/launchpadHelpers';

const launchpadEthChainIds = Object.values(
commonProtocol.factoryContracts,
).reduce<number[]>((acc, contract) => {
if (contract.launchpad) {
acc.push(contract.chainId);
}
return acc;
}, []);

export function CreateLaunchpadTrade(): Command<
typeof schemas.CreateLaunchpadTrade
> {
return {
...schemas.CreateLaunchpadTrade,
auth: [],
body: async ({ payload }) => {
const { eth_chain_id, transaction_hash } = payload;
if (!launchpadEthChainIds.includes(eth_chain_id)) {
throw Error(
`EVM Chain ${eth_chain_id} does not have deployed launchpad`,
);
}

const existingTrade = await models.LaunchpadTrade.findOne({
where: {
eth_chain_id,
transaction_hash,
},
});
if (existingTrade) {
return existingTrade?.get({ plain: true });
}

const chainNode = await models.ChainNode.scope('withPrivateData').findOne(
{
where: {
eth_chain_id,
},
},
);
mustExist('Chain Node', chainNode);

const result = await getLaunchpadTradeTransaction({
rpc: chainNode.private_url! || chainNode.url!,
transactionHash: transaction_hash,
});
if (!result) {
throw new InvalidState('Transaction not found');
}

const trade = await models.LaunchpadTrade.create({
eth_chain_id,
transaction_hash,
token_address: result.parsedArgs.tokenAddress,
trader_address: result.parsedArgs.traderAddress,
is_buy: result.parsedArgs.isBuy,
community_token_amount: result.parsedArgs.communityTokenAmount,
price:
result.parsedArgs.communityTokenAmount / result.parsedArgs.ethAmount,
floating_supply: result.parsedArgs.floatingSupply,
timestamp: Number(result.block.timestamp),
});

return trade.get({ plain: true });
},
};
}
1 change: 1 addition & 0 deletions libs/model/src/token/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from './CreateLaunchpadTrade.command';
export * from './CreateToken.command';
export * from './GetTokens.query';
41 changes: 34 additions & 7 deletions libs/model/test/launchpad/launchpad.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,14 @@ import chaiAsPromised from 'chai-as-promised';
import { bootstrap_testing, seed } from 'model/src/tester';
import { afterAll, beforeAll, describe, test } from 'vitest';
import { ChainNodeAttributes } from '../../src';
import { CreateToken } from '../../src/token';
import { CreateLaunchpadTrade, CreateToken } from '../../src/token';

chai.use(chaiAsPromised);

describe('Launchpad', () => {
const token_address = '0x99a3574fed7b8935709bb13f35448bf7922770ea';

describe('Launchpad Lifecycle', () => {
let actor: Actor;
let payload;
let community_id: string;
let node: ChainNodeAttributes;

Expand Down Expand Up @@ -68,7 +69,7 @@ describe('Launchpad', () => {
'Create Token works given txHash and chainNodeId',
{ timeout: 10_000 },
async () => {
payload = {
const payload = {
transaction_hash:
'0xc0e59dfc71f0e81f33b2f96e7fad5d80d4bf81298bf7dd5afdd8913771e47fad',
chain_node_id: node!.id!,
Expand All @@ -82,10 +83,36 @@ describe('Launchpad', () => {
payload,
});

expect(results?.token_address).to.equal(
'0x99a3574fed7b8935709bb13f35448bf7922770ea',
);
expect(results?.token_address).to.equal(token_address);
expect(results?.symbol).to.equal('tst');
},
);

// TODO: complete test in #9867
test.skip(
'Get a launchpad trade txn and project it',
{ timeout: 10_000 },
async () => {
const buyTxHash = '';
const payload = {
transaction_hash: buyTxHash,
eth_chain_id: commonProtocol.ValidChains.SepoliaBase,
};
const results = await command(CreateLaunchpadTrade(), {
actor,
payload,
});
expect(results).to.deep.equal({
eth_chain_id: commonProtocol.ValidChains.SepoliaBase,
transaction_hash: buyTxHash,
token_address,
trader_address: '',
is_buy: true,
community_token_amount: 1n,
price: 1n,
floating_supply: 1n,
timestamp: 1,
});
},
);
});
3 changes: 1 addition & 2 deletions libs/model/test/seed/model.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,7 @@ const generateSchemas = async () => {
const migration_schema = await get_info_schema(migration, {
ignore_columns: {
// Missing in model - migrations with backups
Comments: ['body_backup', 'text_backup', 'root_id'],
Threads: ['body_backup'],
Comments: ['root_id'],
Topics: ['default_offchain_template_backup'],
GroupPermissions: ['allowed_actions'],
},
Expand Down
10 changes: 9 additions & 1 deletion libs/schemas/src/commands/token.schemas.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { z } from 'zod';
import { AuthContext } from '../context';
import { Token } from '../entities';
import { LaunchpadTrade, Token } from '../entities';

export const CreateToken = {
input: z.object({
Expand All @@ -13,3 +13,11 @@ export const CreateToken = {
output: Token,
context: AuthContext,
};

export const CreateLaunchpadTrade = {
input: z.object({
eth_chain_id: z.number(),
transaction_hash: z.string().length(66),
}),
output: LaunchpadTrade,
};
1 change: 1 addition & 0 deletions libs/schemas/src/entities/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export * from './contract.schemas';
export * from './discordBotConfig.schemas';
export * from './group-permission.schemas';
export * from './group.schemas';
export * from './launchpad.schemas';
export * from './notification.schemas';
export * from './quest.schemas';
export * from './reaction.schemas';
Expand Down
Loading

0 comments on commit 737c156

Please sign in to comment.