diff --git a/.vscode/settings.json b/.vscode/settings.json index 1f727d6..6f97bc8 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,5 +1,12 @@ { "wake.compiler.solc.remappings": [ - "forge-std/=lib/forge-std/src/" - ] -} \ No newline at end of file + "@openzeppelin/=lib/openzeppelin-contracts/", + "@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/", + "ds-test/=lib/openzeppelin-contracts/lib/forge-std/lib/ds-test/src/", + "erc4626-tests/=lib/openzeppelin-contracts/lib/erc4626-tests/", + "forge-std/=lib/forge-std/src/", + "openzeppelin-contracts/=lib/openzeppelin-contracts/" + ], + "solidity.packageDefaultDependenciesContractsDirectory": "src", + "solidity.packageDefaultDependenciesDirectory": "lib" +} diff --git a/src/stamps/Stamp.sol b/src/stamps/Stamp.sol new file mode 100644 index 0000000..eb5b20d --- /dev/null +++ b/src/stamps/Stamp.sol @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol"; +import "@openzeppelin/contracts/utils/cryptography/EIP712.sol"; +import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; + +abstract contract Stamp is ERC721Enumerable, EIP712 { + using ECDSA for bytes32; + + // Signer address + address public immutable signer; + + constructor( + string memory stampName, + string memory stampSymbol, + string memory eip712version, + address _signer + ) ERC721(stampName, stampSymbol) EIP712("Plasa Stamps", eip712version) { + signer = _signer; + } + + // Abstract function to be implemented by child contracts + function getTypedDataHash( + bytes memory data + ) public view virtual returns (bytes32); + + // Custom error for when a user has already minted a stamp + error AlreadyMintedStamp(address user, uint256 stampId); + // New custom error for invalid signature + error InvalidSignature(); + + // Internal minting function + function _mintStamp( + address to, + bytes calldata data, + bytes calldata signature + ) internal virtual returns (uint256) { + if (!_verify(data, signature)) { + revert InvalidSignature(); + } + + if (balanceOf(to) > 0) { + revert AlreadyMintedStamp(to, tokenOfOwnerByIndex(to, 0)); + } + + uint256 newStampId = totalSupply() + 1; + _safeMint(to, newStampId); + + return newStampId; + } + + // Signature verification + function _verify( + bytes calldata data, + bytes calldata signature + ) internal view returns (bool) { + return + signer == + _hashTypedDataV4(getTypedDataHash(data)).recover(signature); + } + + // Override required by Solidity + function supportsInterface( + bytes4 interfaceId + ) public view virtual override(ERC721Enumerable) returns (bool) { + return super.supportsInterface(interfaceId); + } +}