// SPDX-License-Identifier: CC0-1.0
pragmasolidity^0.8.13;import"forge-std/Test.sol";import"../src/ERC5643.sol";contractERC5643MockisERC5643{constructor(stringmemoryname_,stringmemorysymbol_)ERC5643(name_,symbol_){}functionmint(addressto,uint256tokenId)public{_mint(to,tokenId);}}contractERC5643TestisTest{eventSubscriptionUpdate(uint256indexedtokenId,uint64expiration);addressuser1;uint256tokenId;ERC5643Mockerc5643;functionsetUp()public{tokenId=1;user1=address(0x1);erc5643=newERC5643Mock("erc5369","ERC5643");erc5643.mint(user1,tokenId);}functiontestRenewalValid()public{vm.warp(1000);vm.prank(user1);vm.expectEmit(true,true,false,true);emitSubscriptionUpdate(tokenId,3000);erc5643.renewSubscription(tokenId,2000);}functiontestRenewalNotOwner()public{vm.expectRevert("Caller is not owner nor approved");erc5643.renewSubscription(tokenId,2000);}functiontestCancelValid()public{vm.prank(user1);vm.expectEmit(true,true,false,true);emitSubscriptionUpdate(tokenId,0);erc5643.cancelSubscription(tokenId);}functiontestCancelNotOwner()public{vm.expectRevert("Caller is not owner nor approved");erc5643.cancelSubscription(tokenId);}functiontestExpiresAt()public{vm.warp(1000);assertEq(erc5643.expiresAt(tokenId),0);vm.startPrank(user1);erc5643.renewSubscription(tokenId,2000);assertEq(erc5643.expiresAt(tokenId),3000);erc5643.cancelSubscription(tokenId);assertEq(erc5643.expiresAt(tokenId),0);}}
参考实现
实现: ERC5643.sol
// SPDX-License-Identifier: CC0-1.0
pragmasolidity^0.8.13;import"@openzeppelin/contracts/token/ERC721/ERC721.sol";import"./IERC5643.sol";contractERC5643isERC721,IERC5643{mapping(uint256=>uint64)private_expirations;constructor(stringmemoryname_,stringmemorysymbol_)ERC721(name_,symbol_){}functionrenewSubscription(uint256tokenId,uint64duration)externalpayable{require(_isApprovedOrOwner(msg.sender,tokenId),"Caller is not owner nor approved");uint64currentExpiration=_expirations[tokenId];uint64newExpiration;if(currentExpiration==0){newExpiration=uint64(block.timestamp)+duration;}else{if(!_isRenewable(tokenId)){revertSubscriptionNotRenewable();}newExpiration=currentExpiration+duration;}_expirations[tokenId]=newExpiration;emitSubscriptionUpdate(tokenId,newExpiration);}functioncancelSubscription(uint256tokenId)externalpayable{require(_isApprovedOrOwner(msg.sender,tokenId),"Caller is not owner nor approved");delete_expirations[tokenId];emitSubscriptionUpdate(tokenId,0);}functionexpiresAt(uint256tokenId)externalviewreturns(uint64){return_expirations[tokenId];}functionisRenewable(uint256tokenId)externalpurereturns(bool){returntrue;}functionsupportsInterface(bytes4interfaceId)publicviewvirtualoverridereturns(bool){returninterfaceId==type(IERC5643).interfaceId||super.supportsInterface(interfaceId);}}