Adding Player Handles (cleanup) (#137)

This commit is contained in:
norswap
2024-07-21 19:45:30 +02:00
committed by GitHub
5 changed files with 210 additions and 0 deletions

View File

@@ -0,0 +1,16 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "./PlayerHandle.sol";
contract MockENSResolver is IENSResolver {
mapping(bytes32 => string) public names;
function setName(bytes32 node, string memory _name) public {
names[node] = _name;
}
function name(bytes32 node) external view override returns (string memory) {
return names[node];
}
}

View File

@@ -0,0 +1,106 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {Strings} from "@openzeppelin/contracts/utils/Strings.sol";
interface IENSResolver {
function name(bytes32 node) external view returns (string memory);
}
error InvalidHandle();
error HandleAlreadyTaken();
error PlayerNotEligible();
contract PlayerHandle is Ownable {
using Strings for uint256;
IENSResolver public ensResolver;
mapping(address => string) private handles;
mapping(string => address) private handleOwners;
mapping(address => bool) private useEns;
event HandleRegistered(address indexed player, string handle);
event HandleChanged(address indexed player, string newHandle);
event UseEnsSet(address indexed player, bool useEns);
constructor(address _ensResolver) {
ensResolver = IENSResolver(_ensResolver);
}
function registerHandle(string memory handle) external {
if (!checkHandleValidity(handle)) {
revert InvalidHandle();
}
if (handleOwners[handle] != address(0)) {
revert HandleAlreadyTaken();
}
if (!checkPlayerEligibility(msg.sender)) {
revert PlayerNotEligible();
}
if (bytes(handles[msg.sender]).length > 0) {
handleOwners[handles[msg.sender]] = address(0);
}
handles[msg.sender] = handle;
handleOwners[handle] = msg.sender;
emit HandleRegistered(msg.sender, handle);
}
function changeHandle(string memory newHandle) external {
if (!checkHandleValidity(newHandle)) {
revert InvalidHandle();
}
if (handleOwners[newHandle] != address(0)) {
revert HandleAlreadyTaken();
}
string memory oldHandle = handles[msg.sender];
handles[msg.sender] = newHandle;
handleOwners[newHandle] = msg.sender;
handleOwners[oldHandle] = address(0);
emit HandleChanged(msg.sender, newHandle);
}
function setUseEns(bool _useEns) external {
useEns[msg.sender] = _useEns;
emit UseEnsSet(msg.sender, _useEns);
}
function getPlayerHandle(address player) external view returns (string memory) {
if (useEns[player]) {
bytes32 node = keccak256(abi.encodePacked(addressToBytes32(player)));
string memory ensName = ensResolver.name(node);
if (bytes(ensName).length > 0) {
return ensName;
}
}
return handles[player];
}
function checkHandleValidity(string memory handle) public pure returns (bool) {
bytes memory b = bytes(handle);
if (b.length < 5 || b.length > 15) {
return false;
}
for (uint256 i; i < b.length; i++) {
bytes1 char = b[i];
if (!(char >= 0x30 && char <= 0x39) && !(char >= 0x41 && char <= 0x5A) && !(char >= 0x61 && char <= 0x7A)) {
return false;
}
}
return true;
}
function checkPlayerEligibility(address player) public pure returns (bool) {
return true; // Placeholder function; implement eligibility logic as needed
}
function addressToBytes32(address addr) private pure returns (bytes32) {
return bytes32(uint256(uint160(addr)));
}
}

View File

@@ -9,6 +9,8 @@ import {InventoryCardsCollection} from "../InventoryCardsCollection.sol";
import {Groth16Verifier as DrawVerifier} from "../verifiers/DrawVerifier.sol";
import {Groth16Verifier as DrawHandVerifier} from "../verifiers/DrawHandVerifier.sol";
import {Groth16Verifier as PlayVerifier} from "../verifiers/PlayVerifier.sol";
import {MockENSResolver} from "../MockResolver.sol";
import {PlayerHandle} from "../PlayerHandle.sol";
import {Script, console2} from "forge-std/Script.sol";
// import {Multicall3} from "multicall/Multicall3.sol";
@@ -24,6 +26,8 @@ contract Deploy is Script {
DrawHandVerifier public drawHandVerifier;
Game public game;
DeckAirdrop public airdrop;
MockENSResolver public mockEnsResolver;
PlayerHandle public playerHandle;
bool private doLog = true;
@@ -51,6 +55,8 @@ contract Deploy is Script {
bool noRandom = vm.envOr("NO_RANDOM", false);
game = new Game(inventory, drawVerifier, playVerifier, drawHandVerifier, checkProofs, noRandom);
airdrop = new DeckAirdrop(inventory);
mockEnsResolver = new MockENSResolver();
playerHandle = new PlayerHandle(address(mockEnsResolver));
// initialize
cardsCollection.setInventory(inventory);
@@ -63,6 +69,8 @@ contract Deploy is Script {
log("InventoryCardsCollection address", address(inventoryCardsCollection));
log("Game address", address(game));
log("DeckAirdrop address", address(airdrop));
log("MockENSResolver address", address(mockEnsResolver));
log("PlayerHandle address", address(playerHandle));
vm.stopBroadcast();

View File

@@ -0,0 +1,78 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "forge-std/Test.sol";
import "../PlayerHandle.sol";
import {MockENSResolver} from "../MockResolver.sol";
// running tests for PlayerHandle and MockENSResolver
contract PlayerHandleTest is Test {
PlayerHandle public playerHandle;
MockENSResolver public mockENSResolver;
address public alice = address(0x1);
address public bob = address(0x2);
function setUp() public {
mockENSResolver = new MockENSResolver();
playerHandle = new PlayerHandle(address(mockENSResolver));
}
function testRegisterHandle() public {
vm.prank(alice);
playerHandle.registerHandle("AliceHandle");
assertEq(playerHandle.getPlayerHandle(alice), "AliceHandle");
}
function testRegisterInvalidHandle() public {
vm.prank(alice);
vm.expectRevert(abi.encodeWithSignature("InvalidHandle()"));
playerHandle.registerHandle("a");
}
function testHandleAlreadyTaken() public {
vm.prank(alice);
playerHandle.registerHandle("AliceHandle");
vm.prank(bob);
vm.expectRevert(abi.encodeWithSignature("HandleAlreadyTaken()"));
playerHandle.registerHandle("AliceHandle");
}
function testChangeHandle() public {
vm.prank(alice);
playerHandle.registerHandle("AliceHandle");
vm.prank(alice);
playerHandle.changeHandle("NewAliceHandle");
assertEq(playerHandle.getPlayerHandle(alice), "NewAliceHandle");
}
function testSetUseEns() public {
bytes32 node = keccak256(abi.encodePacked(addressToBytes32(alice)));
mockENSResolver.setName(node, "alice.eth");
vm.prank(alice);
playerHandle.setUseEns(true);
assertEq(playerHandle.getPlayerHandle(alice), "alice.eth");
}
function testUnsetUseEns() public {
bytes32 node = keccak256(abi.encodePacked(addressToBytes32(alice)));
mockENSResolver.setName(node, "alice.eth");
vm.prank(alice);
playerHandle.registerHandle("AliceHandle");
playerHandle.setUseEns(true);
playerHandle.setUseEns(false);
assertEq(playerHandle.getPlayerHandle(alice), "AliceHandle");
}
function addressToBytes32(address addr) private pure returns (bytes32) {
return bytes32(uint256(uint160(addr)));
}
}

View File

@@ -15,6 +15,8 @@ export interface Deployment {
Game: Address
DeckAirdrop: Address
Multicall3: Address
PlayerHandle: Address
MockENSResolver: Address
}
// NOTE: This silly `default` affair is required for running the e2e tests which cause