2

I have an interesting use case that I can't seem to solve.

Problem: Tokens get X points per day. I want to freeze ERC721 tokens (they have IDs) for a certain period of time (an ending timestamp). During that time, they get 0 points per day.

I have the following to calculate points:

uint32 public constant SECONDS_IN_DAY = 1 days;
struct UserInfo {
  uint256 itemCount;
  uint256 pendingPoints;
  uint256 lastUpdate;
}

mapping(address => UserInfo) public userInfo;


function pending(address account) public view returns (uint256) {
  uint256 pendingPoints = userInfo[account].pendingPoints + (((block.timestamp - userInfo[account].lastUpdate) / SECONDS_IN_DAY) * (userInfo[account].itemCount));
  return pendingPoints;
}

modifier updatePoints(address account) {
  userInfo[account].pendingPoints = pending(account);
  userInfo[account].lastUpdate = block.timestamp;
  _;
}

The problem I can't figure out:

  1. How do I store when each token is freezed for how long so that I can accurately determine when to reduce points in the pending function.
  2. Do this in a gas efficient way.

I've thought about adding a mapping that holds a timestamp and the amount per day that gets reduced in UserInfo struct but then I would have no way to retrieve this information.

mapping(uint256 => uint256) perDayPointDeductions;

I'm at a loss. Any help would be appreciated.

2 Answers 2

0

could you not store the number of frozen tokens in the user info in some way (e.g. currentlyFrozen) and calculate them in the pending function?

function pending(address account) public view returns (uint256) {
  uint256 pendingPoints = userInfo[account].pendingPoints + (((block.timestamp - userInfo[account].lastUpdate) / SECONDS_IN_DAY) * (userInfo[account].itemCount) - userInfo[account].currentlyFrozen));
  return pendingPoints;
}

or you could calculate the itemCount on the fly and count only "not frozen" tokens

2
  • I could, however currently frozen would have an ending timestamp. As far as I know, there would be no way to determine when the frozen items are unfrozen this way.
    – bryan
    Commented Apr 17, 2022 at 14:40
  • since it seems to be not really important which specific token is frozen but only the total number of frozen ones you could store the "end of freeze timestamps" of them in an array. the length would tell you the number of frozen tokens (itemCount - currentlyFrozen) and you could filter for timestamps in the past (and reinitialize the array if necessary). idk its quite tricky :P
    – davidhass
    Commented Apr 17, 2022 at 17:38
0

Here's my solution. It might not be gas efficient enough for you as it has loops, but I don't see a way to do what you want more efficiently.

    uint32 public constant SECONDS_IN_DAY = 1 days;

// this records the start and end time an item will be frozen
struct ItemFreeze {
    uint256 startTime;
    uint256 endTime;
}

struct UserInfo {
  uint256 itemCount;
  uint256 pendingPoints;
  uint256 lastUpdate;
  // list of time ranges during which a NFT is frozen
  ItemFreeze[] itemFreezes;
}

mapping(address => UserInfo) public userInfo;


function pending(address account) public view returns (uint256) {

    // count the number of items currently frozen
    uint256 frozenCount = 0;
    ItemFreeze[] storage itemFreezes = userInfo[account].itemFreezes;
    for (uint i = 0; i < itemFreezes.length; i++) {
        if (itemFreezes[i].startTime < block.timestamp && itemFreezes[i].endTime > block.timestamp) {
            frozenCount++;
        }
    }

  // don't add points for itemFreezes.length NFTs
  uint256 pendingPoints = userInfo[account].pendingPoints + (((block.timestamp - userInfo[account].lastUpdate) / SECONDS_IN_DAY) * (userInfo[account].itemCount - frozenCount));
  return pendingPoints;
}

// freeze an item, should add safety checks to avoid freezing an item twice for example
function freezeItem(uint256 itemId, uint256 duration) external updatePoints(msg.sender) /* onlyOwner */ {
    userInfo[msg.sender].itemFreezes.push(ItemFreeze(block.timestamp, block.timestamp + duration));
}

modifier updatePoints(address account) {

    // clear out freeze records that are in the past
    ItemFreeze[] storage itemFreezes = userInfo[account].itemFreezes;
    for (uint i = 0; i < itemFreezes.length; i++) {
        if (itemFreezes[i].endTime >= block.timestamp) {
            itemFreezes[i] = itemFreezes[itemFreezes.length - 1];
            itemFreezes.pop();
            i--;
        }
    }
  userInfo[account].pendingPoints = pending(account);
  userInfo[account].lastUpdate = block.timestamp;

  _;
}

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.