Skip to content

Commit b8b8e59

Browse files
Add custom subscription
1 parent 62c20c5 commit b8b8e59

2 files changed

Lines changed: 139 additions & 0 deletions

File tree

‎contracts/talent_plus/TalentPlusSubscription.sol‎

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,43 @@ contract TalentPlusSubscription is Ownable, ReentrancyGuard {
212212
});
213213
}
214214

215+
/**
216+
* @notice Adds or updates a user subscription with a specific expiration time
217+
* @param wallet The wallet address of the user
218+
* @param expirationTime The specific expiration timestamp for the subscription
219+
* @dev Can only be called by trusted signers
220+
* @dev Allows setting custom expiration times for administrative purposes
221+
* @dev Replaces any existing active subscription
222+
* @dev Automatically sets subscription slug as "custom"
223+
*/
224+
function addUserSubscriptionWithExpiration(
225+
address wallet,
226+
uint256 expirationTime
227+
) external nonReentrant {
228+
require(trustedSigners[msg.sender], "Only trusted signers can add user subscriptions");
229+
require(wallet != address(0), "Invalid wallet address");
230+
require(expirationTime > block.timestamp, "Expiration time must be in the future");
231+
232+
string memory subscriptionSlug = "custom";
233+
234+
// Get current active subscription
235+
UserActiveSubscription memory currentActive = userActiveSubscription[wallet];
236+
237+
// If user has an active subscription, emit replacement event
238+
if (bytes(currentActive.subscriptionSlug).length > 0) {
239+
emit UserSubscriptionReplaced(wallet, currentActive.subscriptionSlug, subscriptionSlug, expirationTime, block.timestamp);
240+
} else {
241+
emit UserSubscriptionAdded(wallet, subscriptionSlug, expirationTime, block.timestamp);
242+
}
243+
244+
// Update user subscription with custom expiration time
245+
userActiveSubscription[wallet] = UserActiveSubscription({
246+
subscriptionSlug: subscriptionSlug,
247+
expirationTime: expirationTime,
248+
startTime: block.timestamp
249+
});
250+
}
251+
215252
/**
216253
* @notice Checks if a user has an active subscription
217254
* @param wallet The wallet address of the user to check

‎test/contracts/talent_plus/TalentPlusSubscription.ts‎

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,108 @@ describe("TalentPlusSubscription", () => {
271271
});
272272
});
273273

274+
describe("User Subscription Management with Custom Expiration", () => {
275+
const basicSlug = "basic";
276+
const premiumSlug = "premium";
277+
const basicDuration = 30 * 24 * 60 * 60; // 30 days
278+
const premiumDuration = 90 * 24 * 60 * 60; // 90 days
279+
const basicPrice = ethers.utils.parseEther("50");
280+
const premiumPrice = ethers.utils.parseEther("100");
281+
282+
beforeEach(async () => {
283+
await talentPlusSubscription.connect(admin).addSubscriptionModel(basicSlug, basicDuration, basicPrice);
284+
await talentPlusSubscription.connect(admin).addSubscriptionModel(premiumSlug, premiumDuration, premiumPrice);
285+
});
286+
287+
it("should allow trusted signer to add user subscription with custom expiration", async () => {
288+
const customExpiration = Math.floor(Date.now() / 1000) + 60 * 24 * 60 * 60; // 60 days from now
289+
290+
const tx = await talentPlusSubscription.connect(trustedSigner).addUserSubscriptionWithExpiration(
291+
user1.address,
292+
customExpiration
293+
);
294+
295+
const event = await findEvent(tx, "UserSubscriptionAdded");
296+
expect(event).to.exist;
297+
298+
expect(await talentPlusSubscription.hasActiveSubscription(user1.address)).to.eq(true);
299+
expect(await talentPlusSubscription.hasActiveSubscriptionForModel(user1.address, "custom")).to.eq(true);
300+
301+
const expiration = await talentPlusSubscription.getSubscriptionExpiration(user1.address);
302+
expect(expiration.toNumber()).to.be.closeTo(customExpiration, 60); // Allow 1 minute tolerance
303+
});
304+
305+
it("should not allow non-trusted signer to add user subscription with custom expiration", async () => {
306+
const customExpiration = Math.floor(Date.now() / 1000) + 60 * 24 * 60 * 60; // 60 days from now
307+
308+
const action = talentPlusSubscription.connect(nonTrustedSigner).addUserSubscriptionWithExpiration(
309+
user1.address,
310+
customExpiration
311+
);
312+
await expect(action).to.be.revertedWith("Only trusted signers can add user subscriptions");
313+
});
314+
315+
it("should not allow subscription for zero address with custom expiration", async () => {
316+
const customExpiration = Math.floor(Date.now() / 1000) + 60 * 24 * 60 * 60; // 60 days from now
317+
318+
const action = talentPlusSubscription.connect(trustedSigner).addUserSubscriptionWithExpiration(
319+
ethers.constants.AddressZero,
320+
customExpiration
321+
);
322+
await expect(action).to.be.revertedWith("Invalid wallet address");
323+
});
324+
325+
it("should not allow expiration time in the past", async () => {
326+
const pastExpiration = Math.floor(Date.now() / 1000) - 60 * 24 * 60 * 60; // 60 days ago
327+
328+
const action = talentPlusSubscription.connect(trustedSigner).addUserSubscriptionWithExpiration(
329+
user1.address,
330+
pastExpiration
331+
);
332+
await expect(action).to.be.revertedWith("Expiration time must be in the future");
333+
});
334+
335+
it("should replace existing subscription when adding with custom expiration", async () => {
336+
// First add a basic subscription
337+
await talentPlusSubscription.connect(trustedSigner).addUserSubscription(user1.address, basicSlug);
338+
339+
// Then replace with premium subscription with custom expiration
340+
const customExpiration = Math.floor(Date.now() / 1000) + 120 * 24 * 60 * 60; // 120 days from now
341+
342+
const tx = await talentPlusSubscription.connect(trustedSigner).addUserSubscriptionWithExpiration(
343+
user1.address,
344+
customExpiration
345+
);
346+
347+
const event = await findEvent(tx, "UserSubscriptionReplaced");
348+
expect(event).to.exist;
349+
350+
expect(await talentPlusSubscription.hasActiveSubscriptionForModel(user1.address, "custom")).to.eq(true);
351+
expect(await talentPlusSubscription.hasActiveSubscriptionForModel(user1.address, basicSlug)).to.eq(false);
352+
353+
const expiration = await talentPlusSubscription.getSubscriptionExpiration(user1.address);
354+
expect(expiration.toNumber()).to.be.closeTo(customExpiration, 60); // Allow 1 minute tolerance
355+
});
356+
357+
it("should allow setting subscription with expiration very close to current time", async () => {
358+
const currentBlockTime = (await ethers.provider.getBlock('latest')).timestamp;
359+
const nearFutureExpiration = currentBlockTime + 86400; // 1 day from current block time
360+
361+
const tx = await talentPlusSubscription.connect(trustedSigner).addUserSubscriptionWithExpiration(
362+
user1.address,
363+
nearFutureExpiration
364+
);
365+
366+
const event = await findEvent(tx, "UserSubscriptionAdded");
367+
expect(event).to.exist;
368+
369+
expect(await talentPlusSubscription.hasActiveSubscription(user1.address)).to.eq(true);
370+
371+
const expiration = await talentPlusSubscription.getSubscriptionExpiration(user1.address);
372+
expect(expiration.toNumber()).to.be.closeTo(nearFutureExpiration, 60); // Allow 1 minute tolerance
373+
});
374+
});
375+
274376
describe("Subscription Queries", () => {
275377
const basicSlug = "basic";
276378
const basicDuration = 30 * 24 * 60 * 60; // 30 days

0 commit comments

Comments
 (0)