From b78c61357cbf5bc3c382b1d32c934e5b43ca0911 Mon Sep 17 00:00:00 2001 From: imduchuyyy Date: Tue, 6 Aug 2024 15:02:08 +0700 Subject: [PATCH] refactor: Add SponsorPaymaster contract and related files --- src/paymasters/SponsorPaymaster.sol | 94 +++++++++++++++++++++++++++++ 1 file changed, 94 insertions(+) create mode 100644 src/paymasters/SponsorPaymaster.sol diff --git a/src/paymasters/SponsorPaymaster.sol b/src/paymasters/SponsorPaymaster.sol new file mode 100644 index 0000000..bbcadc2 --- /dev/null +++ b/src/paymasters/SponsorPaymaster.sol @@ -0,0 +1,94 @@ +// SPDX-License-Identifier: Apache +pragma solidity ^0.8.0; + +import "account-abstraction/core/BasePaymaster.sol"; +import "openzeppelin/access/Ownable.sol"; +import "openzeppelin/utils/cryptography/ECDSA.sol"; + +contract SponsorPaymaster is BasePaymaster { + + using ECDSA for bytes32; + using UserOperationLib for UserOperation; + + address public immutable verifyingSigner; + + uint256 private constant VALID_TIMESTAMP_OFFSET = 20; + + uint256 private constant SIGNATURE_OFFSET = 84; + + constructor(IEntryPoint _entryPoint, address _verifyingSigner) BasePaymaster(_entryPoint) { + verifyingSigner = _verifyingSigner; + } + + mapping(address => uint256) public senderNonce; + + function pack(UserOperation calldata userOp) internal pure returns (bytes memory ret) { + // lighter signature scheme. must match UserOp.ts#packUserOp + bytes calldata pnd = userOp.paymasterAndData; + // copy directly the userOp from calldata up to (but not including) the paymasterAndData. + // this encoding depends on the ABI encoding of calldata, but is much lighter to copy + // than referencing each field separately. + assembly { + let ofs := userOp + let len := sub(sub(pnd.offset, ofs), 32) + ret := mload(0x40) + mstore(0x40, add(ret, add(len, 32))) + mstore(ret, len) + calldatacopy(add(ret, 32), ofs, len) + } + } + + /** + * return the hash we're going to sign off-chain (and validate on-chain) + * this method is called by the off-chain service, to sign the request. + * it is called on-chain from the validatePaymasterUserOp, to validate the signature. + * note that this signature covers all fields of the UserOperation, except the "paymasterAndData", + * which will carry the signature itself. + */ + function getHash(UserOperation calldata userOp, uint48 validUntil, uint48 validAfter) + public view returns (bytes32) { + //can't use userOp.hash(), since it contains also the paymasterAndData itself. + + return keccak256(abi.encode( + pack(userOp), + block.chainid, + address(this), + senderNonce[userOp.getSender()], + validUntil, + validAfter + )); + } + + /** + * verify our external signer signed this request. + * the "paymasterAndData" is expected to be the paymaster and a signature over the entire request params + * paymasterAndData[:20] : address(this) + * paymasterAndData[20:84] : abi.encode(validUntil, validAfter) + * paymasterAndData[84:] : signature + */ + function _validatePaymasterUserOp(UserOperation calldata userOp, bytes32 /*userOpHash*/, uint256 requiredPreFund) + internal override returns (bytes memory context, uint256 validationData) { + (requiredPreFund); + + (uint48 validUntil, uint48 validAfter, bytes calldata signature) = parsePaymasterAndData(userOp.paymasterAndData); + //ECDSA library supports both 64 and 65-byte long signatures. + // we only "require" it here so that the revert reason on invalid signature will be of "VerifyingPaymaster", and not "ECDSA" + require(signature.length == 64 || signature.length == 65, "VerifyingPaymaster: invalid signature length in paymasterAndData"); + bytes32 hash = ECDSA.toEthSignedMessageHash(getHash(userOp, validUntil, validAfter)); + senderNonce[userOp.getSender()]++; + + //don't revert on signature failure: return SIG_VALIDATION_FAILED + if (verifyingSigner != ECDSA.recover(hash, signature)) { + return ("",_packValidationData(true,validUntil,validAfter)); + } + + //no need for other on-chain validation: entire UserOp should have been checked + // by the external service prior to signing it. + return ("",_packValidationData(false,validUntil,validAfter)); + } + + function parsePaymasterAndData(bytes calldata paymasterAndData) public pure returns(uint48 validUntil, uint48 validAfter, bytes calldata signature) { + (validUntil, validAfter) = abi.decode(paymasterAndData[VALID_TIMESTAMP_OFFSET:SIGNATURE_OFFSET],(uint48, uint48)); + signature = paymasterAndData[SIGNATURE_OFFSET:]; + } +}