From 4c68daf4d0ac3812be2e729d1a6dc129d3c125da Mon Sep 17 00:00:00 2001 From: Builddddder <147791546+Builddddder@users.noreply.github.com> Date: Tue, 24 Oct 2023 02:29:09 +0800 Subject: [PATCH] feat: As a user, I want to have an example ERC-1271 module (#314) --- .../src/examples/modules/ERC1271Module.sol | 76 +++++++++++++++++++ contracts/test/example/ERC1271Module.t.sol | 76 +++++++++++++++++++ 2 files changed, 152 insertions(+) create mode 100644 contracts/src/examples/modules/ERC1271Module.sol create mode 100644 contracts/test/example/ERC1271Module.t.sol diff --git a/contracts/src/examples/modules/ERC1271Module.sol b/contracts/src/examples/modules/ERC1271Module.sol new file mode 100644 index 00000000..185303c1 --- /dev/null +++ b/contracts/src/examples/modules/ERC1271Module.sol @@ -0,0 +1,76 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.21; + +import { AbstractModule } from "../../interface/AbstractModule.sol"; +import { AttestationPayload } from "../../types/Structs.sol"; + +contract ERC1271Module is AbstractModule { + address public owner; + + error InvalidSignature(); + error WrongSender(); + + constructor(address _owner) { + owner = _owner; + } + + /** + * @notice Recover the signer of hash, assuming it's an EOA account + * @dev Only for EthSign signatures + * @param _hash Hash of message that was signed + * @param _signature Signature encoded as (bytes32 r, bytes32 s, uint8 v) + */ + function recoverSigner(bytes32 _hash, bytes memory _signature) internal pure returns (address signer) { + require(_signature.length == 65, "Invalid signature length"); + bytes32 r; + bytes32 s; + uint8 v; + assembly { + r := mload(add(_signature, 0x20)) + s := mload(add(_signature, 0x40)) + v := byte(0, mload(add(_signature, 0x60))) + } + if (uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) { + revert("SignatureValidator#recoverSigner: invalid signature 's' value"); + } + if (v != 27 && v != 28) { + revert("SignatureValidator#recoverSigner: invalid signature 'v' value"); + } + + signer = ecrecover(_hash, v, r, s); + // Prevent signer from being 0x0 + require(signer != address(0x0), "SignatureValidator#recoverSigner: INVALID_SIGNER"); + return signer; + } + + /** + * @notice Verifies that the signer is the owner of the signing contract. + */ + function isValidSignature(bytes32 _hash, bytes memory _signature) internal view returns (bytes4) { + // Validate signatures + address signer = recoverSigner(_hash, _signature); + if (signer == owner) { + return 0x1626ba7e; + } else { + return 0xffffffff; + } + } + + function run( + AttestationPayload memory attestationPayload, + bytes memory validationPayload, + address txSender, + uint256 /*value*/ + ) public view override { + if (txSender != owner) { + revert WrongSender(); + } + address signee = abi.decode(attestationPayload.subject, (address)); + uint256 nonce = abi.decode(attestationPayload.attestationData, (uint256)); + bytes memory message = abi.encodePacked(signee, nonce); + bytes32 digest = keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n", message)); + if (isValidSignature(digest, validationPayload) != 0x1626ba7e) { + revert InvalidSignature(); + } + } +} diff --git a/contracts/test/example/ERC1271Module.t.sol b/contracts/test/example/ERC1271Module.t.sol new file mode 100644 index 00000000..60161c9b --- /dev/null +++ b/contracts/test/example/ERC1271Module.t.sol @@ -0,0 +1,76 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.21; + +import { Test } from "forge-std/Test.sol"; +import { AbstractModule } from "../../src/interface/AbstractModule.sol"; +import { ERC1271Module } from "../../src/examples/modules/ERC1271Module.sol"; +import { AttestationPayload } from "../../src/types/Structs.sol"; + +contract ERC1271ModuleTest is Test { + ERC1271Module private erc1271Module; + uint256 internal signerPrivateKey; + address internal signerAddress; + + event ModuleRegistered(string name, string description, address moduleAddress); + + function setUp() public { + (signerAddress, signerPrivateKey) = makeAddrAndKey("veraxUser"); + erc1271Module = new ERC1271Module(signerAddress); + vm.deal(signerAddress, 1 ether); + } + + function test_ERC1271Module_verifySignature() public { + address user = makeAddr("user"); + uint256 nonce = 1234567; + AttestationPayload memory attestationPayload = AttestationPayload( + bytes32(uint256(1234)), + 0, + abi.encode(user), + abi.encode(nonce) + ); + + bytes32 hash = keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n", abi.encodePacked(user, nonce))); + (uint8 v, bytes32 r, bytes32 s) = vm.sign(signerPrivateKey, hash); + + bytes memory signature = abi.encodePacked(r, s, v); + + erc1271Module.run(attestationPayload, signature, signerAddress, 0); + } + + function test_ERC1271Module_InvalidSignature() public { + address user = makeAddr("user"); + uint256 nonce = 1234567; + AttestationPayload memory attestationPayload = AttestationPayload( + bytes32(uint256(1234)), + 0, + abi.encode(user), + abi.encode(nonce) + ); + + bytes32 hash = keccak256(abi.encodePacked("\x19Not a correct SignMessage:\n", abi.encodePacked(user, nonce))); + (uint8 v, bytes32 r, bytes32 s) = vm.sign(signerPrivateKey, hash); + + bytes memory signature = abi.encodePacked(r, s, v); + + vm.expectRevert(ERC1271Module.InvalidSignature.selector); + erc1271Module.run(attestationPayload, signature, signerAddress, 0); + } + + function test_ERC1271Module_WrongSender() public { + address user = makeAddr("NotASigner"); + AttestationPayload memory attestationPayload = AttestationPayload( + bytes32(uint256(1234)), + 0, + bytes("subject"), + new bytes(1) + ); + + vm.expectRevert(ERC1271Module.WrongSender.selector); + erc1271Module.run(attestationPayload, bytes("0"), user, 0); + } + + function test_EcRecoverModule_supportsInterface() public { + bool isAbstractModuleSupported = erc1271Module.supportsInterface(type(AbstractModule).interfaceId); + assertEq(isAbstractModuleSupported, true); + } +}