Skip to content

Commit

Permalink
merge with master
Browse files Browse the repository at this point in the history
  • Loading branch information
maxlm-devico committed Oct 10, 2024
2 parents 2389a46 + 606b453 commit 044a9d0
Show file tree
Hide file tree
Showing 5 changed files with 142 additions and 72 deletions.
16 changes: 16 additions & 0 deletions paymaster/migration/MigrationContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
PROVIDER_RPC_URL,
MIGRATION_CONTRACT_ADDRESS,
XDEFI_TOKEN_ADDRESS,
VXDEFI_TOKEN_ADDRESS,
} from "../config/config";
import { xdefiToCtrlMigrationAbi } from "../data/abi/xdefiToCtrlMigrationAbi";
import { erc20Abi } from "../data/abi/erc20Abi";
Expand All @@ -13,9 +14,11 @@ import { DataSource } from "typeorm";
export type MigrationContext = {
migrationContract: Contract;
xdefiContract: Contract;
vXdefiContract: Contract;
provider: JsonRpcProvider;
wallet: Wallet;
dataSource: DataSource;
getTokenContract: (tokenAddress: string) => Contract;
};

let context: MigrationContext;
Expand All @@ -35,12 +38,25 @@ async function createMigrationContext(): Promise<MigrationContext> {

const xdefiContract = new Contract(XDEFI_TOKEN_ADDRESS, erc20Abi, provider);

const vXdefiContract = new Contract(VXDEFI_TOKEN_ADDRESS, erc20Abi, provider);

return {
migrationContract,
xdefiContract,
vXdefiContract,
provider,
wallet,
dataSource,
getTokenContract(tokenAddress) {
switch (tokenAddress.toLowerCase()) {
case XDEFI_TOKEN_ADDRESS.toLowerCase():
return xdefiContract;
case VXDEFI_TOKEN_ADDRESS.toLowerCase():
return vXdefiContract;
default:
throw new Error(`Unsupported token address ${tokenAddress}`);
}
},
};
}

Expand Down
10 changes: 10 additions & 0 deletions paymaster/migration/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export function resolveWithTimeout<TValue extends any>(
value: TValue,
timeout: number
) {
return new Promise<TValue>((ok) => {
setTimeout(() => {
ok(value);
}, timeout);
});
}
27 changes: 17 additions & 10 deletions paymaster/migration/validation/isMigrationAllowed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,18 @@ export async function isXdefiMigrationAllowed(
ctx: MigrationContext,
user: string
) {
const holding = getTokenHolding(XDEFI_TOKEN_ADDRESS, user);
// const holding = getTokenHolding(XDEFI_TOKEN_ADDRESS, user);
// const balance = BigInt(holding?.balance || "0");
// if (!holding) {
// // const holding = getTokenHolding(XDEFI_TOKEN_ADDRESS, user);

if (!holding) {
return [false, `address ${user} missing on token holders list`];
}
// return [false, `address ${user} missing on token holders list`];
// }
/***uncomment ^^^^above^^^^ if check against holders list is required */

const balance = (await ctx.xdefiContract.balanceOf(user)) as bigint;

const isAllowed = BigInt(holding.balance) >= XDEFI_VALUE_THRESHOLD;
const isAllowed = balance >= XDEFI_VALUE_THRESHOLD;

const message = isAllowed
? ""
Expand All @@ -64,14 +69,16 @@ export async function isVXdefiMigrationAllowed(
ctx: MigrationContext,
user: string
) {
const holding = getTokenHolding(VXDEFI_TOKEN_ADDRESS, user);
// const holding = getTokenHolding(VXDEFI_TOKEN_ADDRESS, user);

if (!holding) {
return [false, `address ${user} missing on token holders list`];
}
// if (!holding) {
// return [false, `address ${user} missing on token holders list`];
// }

const balance = BigInt(holding.balance || 0);
// const balance = BigInt(holding.balance || 0);
/***uncomment ^^^^above^^^^ if check against holders list is required */

const balance = (await ctx.vXdefiContract.balanceOf(user)) as bigint;
const isAllowed = balance >= VXDEFI_VALUE_THRESHOLD;

const message = isAllowed
Expand Down
35 changes: 28 additions & 7 deletions paymaster/paymaster.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { isMigrationAllowed } from "./migration/validation/isMigrationAllowed";
import { migrationRequestSchema } from "./migration/validation/migrationRequestSchema";
import { isAlreadyMigrated } from "./migration/validation/isAlreadyMigrated";
import { saveMigrationRecord } from "./migration/saveMigrationRecord";
import { resolveWithTimeout } from "./migration/utils";

const headers = {
"Access-Control-Allow-Headers": "Content-Type",
Expand All @@ -28,6 +29,7 @@ export const handler = async (
body: JSON.stringify({
message: "Request validation failed",
fieldErrors: result.error.flatten().fieldErrors,
canRetry: true,
}),
};
}
Expand All @@ -49,6 +51,7 @@ export const handler = async (
headers,
body: JSON.stringify({
message,
canRetry: false,
}),
};
}
Expand All @@ -68,16 +71,17 @@ export const handler = async (
};
}

const userAmount = (await migrationContext.xdefiContract.balanceOf(
user
)) as bigint;
const tokenContract = migrationContext.getTokenContract(tokenAddress);

const userAmount = (await tokenContract.balanceOf(user)) as bigint;

if (userAmount < amount) {
return {
statusCode: 400,
headers,
body: JSON.stringify({
message: `Requested migration balance is less than actual balance. Requested: ${amount}. Actual: ${userAmount}`,
canRetry: true,
}),
};
}
Expand All @@ -91,13 +95,29 @@ export const handler = async (
s,
});

const receipt = await tx.wait();
const maybeReceipt = await Promise.race([
tx.wait(),
resolveWithTimeout("timedOut" as const, 29000),
]);

if (maybeReceipt === "timedOut") {
return {
statusCode: 200,
headers,
body: JSON.stringify({
txHash: tx.hash,
timedOut: true,
message:
"Transaction is still pending. Please check later for confirmation.",
}),
};
}

try {
await saveMigrationRecord(queryRunner, {
user,
tokenAddress,
txHash: receipt?.hash || "",
txHash: maybeReceipt?.hash || "",
});
} catch (e) {
console.error(e);
Expand All @@ -109,17 +129,18 @@ export const handler = async (
statusCode: 200,
headers,
body: JSON.stringify({
txHash: receipt?.hash,
txHash: maybeReceipt?.hash,
}),
};
} catch (e: any) {
console.error(e);
return {
statusCode: 400,
statusCode: 500,
headers,
body: JSON.stringify({
message:
"Failed to perform migration. Please try again later or contact support.",
canRetry: true,
}),
};
}
Expand Down
126 changes: 71 additions & 55 deletions paymaster/test/paymaster.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,59 +69,60 @@ describe("Paymaster handler", () => {
vi.clearAllMocks();
});

it("should return error when address is not on holders list", async () => {
const user = "0xTest-address";
const requestBody = {
deadline: Math.floor(Date.now() / 1000) + 60 * 60,
user,
v: 27,
s: "0xs",
r: "0xr",
tokenAddress: "0xXdefiTokenAddress",
amount: "10",
};
const event = {
body: JSON.stringify(requestBody),
} as APIGatewayEvent;

(getTokenHolding as Mock<typeof getTokenHolding>).mockReturnValueOnce(
undefined
);

(
migrationRequestSchema.safeParse as Mock<
typeof migrationRequestSchema.safeParse
>
).mockReturnValueOnce({
success: true,
data: requestBody as any,
});

(
getMigrationContext as Mock<typeof getMigrationContext>
).mockImplementationOnce(() => {
return Promise.resolve({
migrationContract: vi.fn(),
xdefiContract: {
balanceOf: vi.fn().mockResolvedValue(1_000_000_000n),
},
provider: vi.fn(),
wallet: vi.fn(),
dataSource: {
createQueryRunner: vi
.fn()
.mockReturnValue({ release: vi.fn().mockResolvedValue(undefined) }),
},
} as any as MigrationContext);
});

const response = await handler(event, {} as any);

expect(response.statusCode).equals(400);
expect(JSON.parse(response.body).message).equals(
`address ${user} missing on token holders list`
);
});
// it("should return error when address is not on holders list", async () => {
// const user = "0xTest-address";
// const requestBody = {
// deadline: Math.floor(Date.now() / 1000) + 60 * 60,
// user,
// v: 27,
// s: "0xs",
// r: "0xr",
// tokenAddress: "0xXdefiTokenAddress",
// amount: "10",
// };
// const event = {
// body: JSON.stringify(requestBody),
// } as APIGatewayEvent;

// (getTokenHolding as Mock<typeof getTokenHolding>).mockReturnValueOnce(
// undefined
// );

// (
// migrationRequestSchema.safeParse as Mock<
// typeof migrationRequestSchema.safeParse
// >
// ).mockReturnValueOnce({
// success: true,
// data: requestBody as any,
// });

// (
// getMigrationContext as Mock<typeof getMigrationContext>
// ).mockImplementationOnce(() => {
// return Promise.resolve({
// migrationContract: vi.fn(),
// xdefiContract: {
// balanceOf: vi.fn().mockResolvedValue(1_000_000_000n),
// },
// vXdefiContract: {
// balanceOf: vi.fn().mockResolvedValue(1_000_000_000n),
// },
// getTokenContract: {
// balanceOf: vi.fn().mockResolvedValue(1_000_000_000n),
// },
// provider: vi.fn(),
// wallet: vi.fn(),
// } as any as MigrationContext);
// });

// const response = await handler(event, {} as any);

// expect(response.statusCode).equals(400);
// expect(JSON.parse(response.body).message).equals(
// `address ${user} missing on token holders list`
// );
// });

it("should return request payload validation error", async () => {
const user = "0xTest-address";
Expand Down Expand Up @@ -166,6 +167,12 @@ describe("Paymaster handler", () => {
xdefiContract: {
balanceOf: vi.fn().mockResolvedValue(1_000_000_000n),
},
vXdefiContract: {
balanceOf: vi.fn().mockResolvedValue(1_000_000_000n),
},
getTokenContract: vi.fn().mockReturnValueOnce({
balanceOf: vi.fn().mockResolvedValue(1_000_000_000n),
}),
provider: vi.fn(),
wallet: vi.fn(),
dataSource: {
Expand Down Expand Up @@ -220,8 +227,11 @@ describe("Paymaster handler", () => {
return Promise.resolve({
migrationContract: vi.fn(),
xdefiContract: {
balanceOf: vi.fn().mockResolvedValue(100n * 10n ** 18n),
balanceOf: vi.fn().mockResolvedValue(9n * 10n ** 18n),
},
getTokenContract: vi.fn().mockReturnValueOnce({
balanceOf: vi.fn().mockResolvedValue(1_000_000_000n),
}),
provider: vi.fn(),
wallet: vi.fn(),
dataSource: {
Expand Down Expand Up @@ -281,6 +291,9 @@ describe("Paymaster handler", () => {
xdefiContract: {
balanceOf: vi.fn().mockResolvedValue(100n * 10n ** 18n),
},
getTokenContract: vi.fn().mockReturnValueOnce({
balanceOf: vi.fn().mockResolvedValue(1_000_000_000n),
}),
provider: vi.fn(),
wallet: vi.fn(),
dataSource: {
Expand Down Expand Up @@ -344,6 +357,9 @@ describe("Paymaster handler", () => {
.fn()
.mockRejectedValueOnce(new Error("generic error")),
},
getTokenContract: vi.fn().mockReturnValueOnce({
balanceOf: vi.fn().mockResolvedValue(1_000_000_000n),
}),
xdefiContract: {
balanceOf: vi.fn().mockResolvedValue(100n * 10n ** 18n),
},
Expand All @@ -359,7 +375,7 @@ describe("Paymaster handler", () => {

const response = await handler(event, {} as any);

expect(response.statusCode).equals(400);
expect(response.statusCode).equals(500);
expect(JSON.parse(response.body).message).equals(
"Failed to perform migration. Please try again later or contact support."
);
Expand Down

0 comments on commit 044a9d0

Please sign in to comment.