diff --git a/src/IdentityToken.sol b/src/IdentityToken.sol index d236506..1f614e5 100644 --- a/src/IdentityToken.sol +++ b/src/IdentityToken.sol @@ -12,6 +12,7 @@ contract IdentityToken is ERC721, IIdentityToken { error NonTransferable(); uint256 private _nextTokenId = 1; + uint256 public constant MAX_ENDORSEMENTS = 100; // wallet => tokenId (enforce one identity per wallet) mapping(address => uint256) public ownerToTokenId; @@ -160,6 +161,7 @@ contract IdentityToken is ERC721, IIdentityToken { if (_ownerOf(toTokenId) == address(0)) revert Errors.TargetInvalid(); DataTypes.Endorsement[] storage list = endorsements[toTokenId]; + if (list.length >= MAX_ENDORSEMENTS) revert Errors.IndexOutOfBounds(); // prevent duplicate active endorsements for (uint256 i = 0; i < list.length; i++) { diff --git a/test/IdentityToken.t.sol b/test/IdentityToken.t.sol index 27a8ef2..2038740 100644 --- a/test/IdentityToken.t.sol +++ b/test/IdentityToken.t.sol @@ -288,6 +288,23 @@ contract IdentityTokenTest is Test { assertEq(revokedAt, 0); } + function test_RevertIf_MaxEndorsementsReached() public { + vm.prank(alice); + uint256 aliceId = identityToken.mint(); + + vm.prank(bob); + uint256 bobId = identityToken.mint(); + + for (uint256 i = 0; i < 100; i++) { + vm.prank(alice); + identityToken.endorse(aliceId, bobId, keccak256(abi.encodePacked("Connection", i)), 0); + } + + vm.prank(alice); + vm.expectRevert(Errors.IndexOutOfBounds.selector); + identityToken.endorse(aliceId, bobId, keccak256(abi.encodePacked("Connection", uint256(100))), 0); + } + // --- deleteAttribute --- function test_DeleteAttribute() public {