Skip to content

Commit

Permalink
Merge pull request #124 from hackdays-io/feature/activation
Browse files Browse the repository at this point in the history
Fix: Double-counting time after hat reactivation in HatsTimeFrameModule
  • Loading branch information
yu23ki14 authored Oct 19, 2024
2 parents 53556a6 + 39f390f commit 169e48e
Show file tree
Hide file tree
Showing 2 changed files with 128 additions and 24 deletions.
57 changes: 50 additions & 7 deletions pkgs/contract/contracts/timeframe/HatsTimeFrameModule.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,18 @@ contract HatsTimeFrameModule is
HatsModule,
IHatsTimeFrameModule
{
// hatId => wearer => wore timestamp
mapping(uint256 => mapping(address => uint256)) private woreTime;

// hatId => wearer => last deactivation timestamp
mapping(uint256 => mapping(address => uint256)) private deactivatedTime;

// hatId => wearer => total active time
mapping(uint256 => mapping(address => uint256)) private totalActiveTime;

// hatId => wearer => isActive
mapping(uint256 => mapping(address => bool)) private isActive;

/**
* @dev Constructor to initialize the trusted forwarder.
* @param _trustedForwarder Address of the trusted forwarder contract.
Expand All @@ -22,8 +32,37 @@ contract HatsTimeFrameModule is
) ERC2771Context(_trustedForwarder) HatsModule(_version) {}

function mintHat(uint256 hatId, address wearer) external {
HATS().mintHat(hatId, wearer);
_setWoreTime(wearer, hatId);
isActive[hatId][wearer] = true;
HATS().mintHat(hatId, wearer);
}

/**
* @dev Deactivate the hat, pausing the contribution time.
* Calculate the contribution time up to deactivation.
* @param wearer The address of the person who received the hat.
* @param hatId The ID of the hat that was minted.
*/
function deactivate(uint256 hatId, address wearer) external {
// msg.sender should be the owner of the hat or parent hat owner
require(isActive[hatId][wearer], "Hat is already inactive");
isActive[hatId][wearer] = false;
deactivatedTime[hatId][wearer] = block.timestamp;
totalActiveTime[hatId][wearer] +=
block.timestamp -
woreTime[hatId][wearer];
}

/**
* @dev Reactivate the hat, resuming the contribution time.
* Reset woreTime for new active period.
* @param wearer The address of the person who received the hat.
* @param hatId The ID of the hat that was minted.
*/
function reactivate(uint256 hatId, address wearer) external {
require(!isActive[hatId][wearer], "Hat is already active");
isActive[hatId][wearer] = true;
woreTime[hatId][wearer] = block.timestamp;
}

/**
Expand All @@ -50,6 +89,8 @@ contract HatsTimeFrameModule is

/**
* @dev Gets the elapsed time in seconds since the specific hat was minted for a specific address.
* If the hat is active, calculate time from the last wear time to the current time.
* If the hat is inactive, calculate time up to the deactivation.
* @param wearer The address of the person who received the hat.
* @param hatId The ID of the hat that was minted.
* @return The elapsed time in seconds.
Expand All @@ -58,12 +99,14 @@ contract HatsTimeFrameModule is
address wearer,
uint256 hatId
) external view returns (uint256) {
uint256 mintTime = woreTime[hatId][wearer];
require(
mintTime != 0,
"Hat has not been minted for this wearer and hatId"
);
return block.timestamp - mintTime;
uint256 activeTime = totalActiveTime[hatId][wearer];

if (isActive[hatId][wearer]) {
// If active, calculate time from the last woreTime to the current time
activeTime += block.timestamp - woreTime[hatId][wearer];
}

return activeTime;
}

/**
Expand Down
95 changes: 78 additions & 17 deletions pkgs/contract/test/HatsTimeFrameModule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -134,29 +134,90 @@ describe("HatsTimeFrameModule", () => {
}
}

const initialTime = BigInt(await time.latest());

await HatsTimeFrameModule.write.mintHat([
roleHatId,
address1.account?.address!,
]);

const afterMintTime = BigInt(await time.latest());

let woreTime = await HatsTimeFrameModule.read.getWoreTime([
address1.account?.address!,
roleHatId,
]);

expect(woreTime).to.equal(afterMintTime);

await time.increaseTo(initialTime + 100n);

const currentTime1 = BigInt(await time.latest());

let expectedElapsedTime = currentTime1 - woreTime;

let elapsedTime = await HatsTimeFrameModule.read.getWearingElapsedTime([
address1.account?.address!,
roleHatId,
]);

expect(elapsedTime).to.equal(expectedElapsedTime);

await time.increaseTo(initialTime + 200n);

const currentTime2 = BigInt(await time.latest());

expectedElapsedTime = currentTime2 - woreTime;

elapsedTime = await HatsTimeFrameModule.read.getWearingElapsedTime([
address1.account?.address!,
roleHatId,
]);

expect(
await Hats.read.balanceOf([address1.account?.address!, roleHatId])
).equal(BigInt(1));
expect(elapsedTime).to.equal(expectedElapsedTime);

expect(
await HatsTimeFrameModule.read.getWoreTime([
address1.account?.address!,
roleHatId,
])
).equal(BigInt(await time.latest()));
await HatsTimeFrameModule.write.deactivate([
roleHatId,
address1.account?.address!,
]);

await time.increase(100);
const afterDeactivateTime = BigInt(await time.latest());

expect(
await HatsTimeFrameModule.read.getWearingElapsedTime([
address1.account?.address!,
roleHatId,
])
).equal(BigInt(100));
const totalActiveTimeAfterDeactivation = afterDeactivateTime - woreTime;

expectedElapsedTime = totalActiveTimeAfterDeactivation;

// Increase time to initialTime + 250 seconds (during inactivity)
await time.increaseTo(initialTime + 250n);

// Elapsed time should remain the same
elapsedTime = await HatsTimeFrameModule.read.getWearingElapsedTime([
address1.account?.address!,
roleHatId,
]);

expect(elapsedTime).to.equal(expectedElapsedTime);

// Reactivate the hat
await HatsTimeFrameModule.write.reactivate([
roleHatId,
address1.account?.address!,
]);

// Get woreTime after reactivation
woreTime = BigInt(await time.latest());

await time.increaseTo(initialTime + 350n);

const currentTime3 = BigInt(await time.latest());

expectedElapsedTime = totalActiveTimeAfterDeactivation + (currentTime3 - woreTime);

elapsedTime = await HatsTimeFrameModule.read.getWearingElapsedTime([
address1.account?.address!,
roleHatId,
]);

expect(elapsedTime).to.equal(expectedElapsedTime);
});
});
});

0 comments on commit 169e48e

Please sign in to comment.