diff --git a/Makefile b/Makefile index cfc1567..30a108a 100644 --- a/Makefile +++ b/Makefile @@ -5,6 +5,8 @@ ORGS += pimlico ORGS += daimo ORGS += zerodev ORGS += alchemy +ORGS += rhinestone +ORGS += biconomy .PHONY: all $(ORGS) @@ -19,3 +21,5 @@ $(ORGS): zerodev: eth-infinitism daimo alchemy: eth-infinitism + +biconomy: eth-infinitism rhinestone diff --git a/README.md b/README.md index 008c917..d787f2f 100644 --- a/README.md +++ b/README.md @@ -53,6 +53,10 @@ make - [alchemy-light-account:1.1.0](https://usecannon.com/packages/alchemy-light-account/1.1.0/13370-main) ([source](https://github.com/alchemyplatform/light-account/releases/tag/v1.1.0)) - [alchemy-light-account:2.0.0](https://usecannon.com/packages/alchemy-light-account/2.0.0/13370-main) ([source](https://github.com/alchemyplatform/light-account/releases/tag/v2.0.0)) - [alchemy-modular-account:2.0.0](https://usecannon.com/packages/alchemy-modular-account/2.0.0/13370-main) ([source](https://github.com/alchemyplatform/modular-account/releases/tag/v2.0.0)) +- [rhinestone-registry:1.0.0](https://usecannon.com/packages/rhinestone-registry/1.0.0/13370-main) ([source](https://github.com/rhinestonewtf/registry/tree/v1.0)) +- [biconomy-kee:0.0.4](https://usecannon.com/packages/biconomy-mee/0.0.4/13370-main) ([source](https://github.com/bcnmy/mee-contracts/tree/v0.0.4)) +- [biconomy-nexus:1.0.2](https://usecannon.com/packages/biconomy-nexux/1.0.2/13370-main) ([source](https://github.com/bcnmy/nexus/tree/v1.0.2)) +- [biconomy-nexus:1.2.0](https://usecannon.com/packages/biconomy-nexux/1.2.0/13370-main) ([source](https://github.com/bcnmy/nexus/tree/v1.2.0)) ## Useful tools diff --git a/biconomy/Makefile b/biconomy/Makefile new file mode 100644 index 0000000..67865f1 --- /dev/null +++ b/biconomy/Makefile @@ -0,0 +1,10 @@ +PROJECTS += mee +PROJECTS += nexus + +.PHONY: all $(PROJECTS) +all: $(PROJECTS) + +$(PROJECTS): + @$(MAKE) -C $@ + +nexus: mee diff --git a/biconomy/mee/0.0.4/cannonfile.toml b/biconomy/mee/0.0.4/cannonfile.toml new file mode 100644 index 0000000..e4def23 --- /dev/null +++ b/biconomy/mee/0.0.4/cannonfile.toml @@ -0,0 +1,9 @@ +name = "biconomy-mee" +version = "0.0.4" +description = "Nexus ERC-4337 infrastructure" + +[deploy.K1MeeValidator] +artifact = "K1MeeValidator" +create2 = true +salt = "0x000000000000000000000000000000000000000047819504fd5006001ee95238" +ifExists = "continue" diff --git a/biconomy/mee/0.0.4/contracts/interfaces/ISessionValidator.sol b/biconomy/mee/0.0.4/contracts/interfaces/ISessionValidator.sol new file mode 100644 index 0000000..bf4295e --- /dev/null +++ b/biconomy/mee/0.0.4/contracts/interfaces/ISessionValidator.sol @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.23; + +import {IModule} from "erc7579/interfaces/IERC7579Module.sol"; + +uint256 constant ERC7579_MODULE_TYPE_STATELESS_VALIDATOR = 7; + +/** + * ISessionValidator is a contract that validates signatures for a given session. + * this interface expects to validate the signature in a stateless way. + * all parameters required to validate the signature are passed in the function call. + * Only one ISessionValidator is responsible to validate a userOp. + * if you want to use multiple validators, you can create a ISessionValidator that aggregates multiple signatures that + * are packed into userOp.signature + * It is used to validate the signature of a session. + * hash The userOp hash + * sig The signature of userOp + * data the config data that is used to validate the signature + */ +interface ISessionValidator is IModule { + function validateSignatureWithData(bytes32 hash, bytes calldata sig, bytes calldata data) + external + view + returns (bool validSig); +} diff --git a/biconomy/mee/0.0.4/contracts/lib/fusion/NoMeeFlowLib.sol b/biconomy/mee/0.0.4/contracts/lib/fusion/NoMeeFlowLib.sol new file mode 100644 index 0000000..fbdace3 --- /dev/null +++ b/biconomy/mee/0.0.4/contracts/lib/fusion/NoMeeFlowLib.sol @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.27; + +import "account-abstraction/interfaces/PackedUserOperation.sol"; +import "account-abstraction/core/Helpers.sol"; +import "../util/EcdsaLib.sol"; + +library NoMeeFlowLib { + /** + * Standard userOp validator - validates by simply checking if the userOpHash was signed by the account's EOA owner. + * + * @param userOpHash userOpHash being validated. + * @param parsedSignature Signature + * @param expectedSigner Signer expected to be recovered + */ + function validateUserOp(bytes32 userOpHash, bytes memory parsedSignature, address expectedSigner) + internal + view + returns (uint256) + { + if (!EcdsaLib.isValidSignature(expectedSigner, userOpHash, parsedSignature)) { + return SIG_VALIDATION_FAILED; + } + return SIG_VALIDATION_SUCCESS; + } + + /** + * @notice Validates the signature against the expected signer (owner) + * @param expectedSigner Signer expected to be recovered + * @param hash Hash of the userOp + * @param parsedSignature Signature + */ + function validateSignatureForOwner(address expectedSigner, bytes32 hash, bytes memory parsedSignature) + internal + view + returns (bool) + { + return EcdsaLib.isValidSignature(expectedSigner, hash, parsedSignature); + } +} diff --git a/biconomy/mee/0.0.4/contracts/lib/fusion/PermitValidatorLib.sol b/biconomy/mee/0.0.4/contracts/lib/fusion/PermitValidatorLib.sol new file mode 100644 index 0000000..bdd811e --- /dev/null +++ b/biconomy/mee/0.0.4/contracts/lib/fusion/PermitValidatorLib.sol @@ -0,0 +1,213 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.27; + +import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol"; +import {MerkleProof} from "openzeppelin/utils/cryptography/MerkleProof.sol"; +import {EcdsaLib} from "../util/EcdsaLib.sol"; +import {MEEUserOpHashLib} from "../util/MEEUserOpHashLib.sol"; +import {IERC20Permit} from "openzeppelin/token/ERC20/extensions/IERC20Permit.sol"; +import {IERC20} from "openzeppelin/token/ERC20/IERC20.sol"; +import "account-abstraction/core/Helpers.sol"; + +/** + * @dev Library to validate the signature for MEE ERC-2612 Permit mode + * This is the mode where superTx hash is pasted into deadline field of the ERC-2612 Permit + * So the whole permit is signed along with the superTx hash + * For more details see Fusion docs: + * - https://ethresear.ch/t/fusion-module-7702-alternative-with-no-protocol-changes/20949 + * - https://docs.biconomy.io/explained/eoa#fusion-module + * + * @dev Important: since ERC20 permit token knows nothing about the MEE, it will treat the superTx hash as a deadline: + * - if (very unlikely) the superTx hash being converted to uint256 is a timestamp in the past, the permit will fail + * - the deadline with most superTx hashes will be very far in the future + * + * @dev Since at this point bytes32 superTx hash is a blind hash, users and wallets should pay attention if + * the permit2 deadline field does not make sense as the timestamp. In this case, it can be a sign of a + * phishing attempt (injecting super txn hash as the deadline) and the user should not sign the permit. + * This is going to be mitigated in the future by making superTx hash a EIP-712 hash. + */ +bytes32 constant PERMIT_TYPEHASH = + keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"); + +struct DecodedErc20PermitSig { + IERC20Permit token; + address spender; + bytes32 domainSeparator; + uint256 amount; + uint256 nonce; + bool isPermitTx; + bytes32 superTxHash; + uint48 lowerBoundTimestamp; + uint48 upperBoundTimestamp; + uint8 v; + bytes32 r; + bytes32 s; + bytes32[] proof; +} + +struct DecodedErc20PermitSigShort { + address spender; + bytes32 domainSeparator; + uint256 amount; + uint256 nonce; + bytes32 superTxHash; + uint8 v; + bytes32 r; + bytes32 s; + bytes32[] proof; +} + +library PermitValidatorLib { + error PermitFailed(); + + uint8 constant EIP_155_MIN_V_VALUE = 37; + + using MessageHashUtils for bytes32; + + /** + * This function parses the given userOpSignature into a DecodedErc20PermitSig data structure. + * + * Once parsed, the function will check for two conditions: + * 1. is the userOp part of the merkle tree + * 2. is the recovered message signer equal to the expected signer? + * + * NOTES: This function will revert if either of following is met: + * 1. the userOpSignature couldn't be abi.decoded into a valid DecodedErc20PermitSig struct as defined in this contract + * 2. userOp is not part of the merkle tree + * 3. recovered Permit message signer wasn't equal to the expected signer + * + * The function will also perform the Permit approval on the given token in case the + * isPermitTx flag was set to true in the decoded signature struct. + * + * @param userOpHash UserOp hash being validated. + * @param parsedSignature Signature provided as the userOp.signature parameter (minus the prepended tx type byte). + * @param expectedSigner Signer expected to be recovered when decoding the ERC20OPermit signature. + */ + function validateUserOp(bytes32 userOpHash, bytes calldata parsedSignature, address expectedSigner) + internal + returns (uint256) + { + DecodedErc20PermitSig memory decodedSig = _decodeFullPermitSig(parsedSignature); + + bytes32 meeUserOpHash = MEEUserOpHashLib.getMEEUserOpHash( + userOpHash, decodedSig.lowerBoundTimestamp, decodedSig.upperBoundTimestamp + ); + + if ( + !EcdsaLib.isValidSignature( + expectedSigner, + _getSignedDataHash(expectedSigner, decodedSig), + abi.encodePacked(decodedSig.r, decodedSig.s, uint8(decodedSig.v)) + ) + ) { + return SIG_VALIDATION_FAILED; + } + + if (!MerkleProof.verify(decodedSig.proof, decodedSig.superTxHash, meeUserOpHash)) { + return SIG_VALIDATION_FAILED; + } + + if (decodedSig.isPermitTx) { + try decodedSig.token.permit( + expectedSigner, + decodedSig.spender, + decodedSig.amount, + uint256(decodedSig.superTxHash), + uint8(decodedSig.v), + decodedSig.r, + decodedSig.s + ) { + // all good + } catch { + // check if by some reason this permit was already successfully used (and not spent yet) + if (IERC20(address(decodedSig.token)).allowance(expectedSigner, decodedSig.spender) < decodedSig.amount) + { + // if the above expectationis not true, revert + revert PermitFailed(); + } + } + } + + return _packValidationData(false, decodedSig.upperBoundTimestamp, decodedSig.lowerBoundTimestamp); + } + + function validateSignatureForOwner(address expectedSigner, bytes32 dataHash, bytes calldata parsedSignature) + internal + view + returns (bool) + { + DecodedErc20PermitSigShort calldata decodedSig = _decodeShortPermitSig(parsedSignature); + + if ( + !EcdsaLib.isValidSignature( + expectedSigner, + _getSignedDataHash(expectedSigner, decodedSig), + abi.encodePacked(decodedSig.r, decodedSig.s, uint8(decodedSig.v)) + ) + ) { + return false; + } + + if (!MerkleProof.verify(decodedSig.proof, decodedSig.superTxHash, dataHash)) { + return false; + } + + return true; + } + + function _decodeFullPermitSig(bytes calldata parsedSignature) + private + pure + returns (DecodedErc20PermitSig calldata decodedSig) + { + assembly { + decodedSig := add(parsedSignature.offset, 0x20) + } + } + + function _decodeShortPermitSig(bytes calldata parsedSignature) + private + pure + returns (DecodedErc20PermitSigShort calldata) + { + DecodedErc20PermitSigShort calldata decodedSig; + assembly { + decodedSig := add(parsedSignature.offset, 0x20) + } + return decodedSig; + } + + function _getSignedDataHash(address expectedSigner, DecodedErc20PermitSig memory decodedSig) + private + pure + returns (bytes32) + { + uint256 deadline = uint256(decodedSig.superTxHash); + + bytes32 structHash = keccak256( + abi.encode( + PERMIT_TYPEHASH, expectedSigner, decodedSig.spender, decodedSig.amount, decodedSig.nonce, deadline + ) + ); + return _hashTypedData(structHash, decodedSig.domainSeparator); + } + + function _getSignedDataHash(address expectedSigner, DecodedErc20PermitSigShort memory decodedSig) + private + pure + returns (bytes32) + { + uint256 deadline = uint256(decodedSig.superTxHash); + + bytes32 structHash = keccak256( + abi.encode( + PERMIT_TYPEHASH, expectedSigner, decodedSig.spender, decodedSig.amount, decodedSig.nonce, deadline + ) + ); + return _hashTypedData(structHash, decodedSig.domainSeparator); + } + + function _hashTypedData(bytes32 structHash, bytes32 domainSeparator) private pure returns (bytes32) { + return MessageHashUtils.toTypedDataHash(domainSeparator, structHash); + } +} diff --git a/biconomy/mee/0.0.4/contracts/lib/fusion/SimpleValidatorLib.sol b/biconomy/mee/0.0.4/contracts/lib/fusion/SimpleValidatorLib.sol new file mode 100644 index 0000000..ab9509f --- /dev/null +++ b/biconomy/mee/0.0.4/contracts/lib/fusion/SimpleValidatorLib.sol @@ -0,0 +1,103 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.27; + +import {MerkleProof} from "openzeppelin/utils/cryptography/MerkleProof.sol"; +import {EcdsaLib} from "../util/EcdsaLib.sol"; +import {MEEUserOpHashLib} from "../util/MEEUserOpHashLib.sol"; +import "account-abstraction/core/Helpers.sol"; + +/** + * @dev Library to validate the signature for MEE Simple mode + * In this mode, Fusion is not involved and just the superTx hash is signed + */ +library SimpleValidatorLib { + /** + * This function parses the given userOpSignature into a Supertransaction signature + * + * Once parsed, the function will check for two conditions: + * 1. is the root supertransaction hash signed by the account owner's EOA + * 2. is the userOp actually a part of the given supertransaction + * by checking the leaf based on this userOpHash is a part of the merkle tree represented by root hash = superTxHash + * + * If both conditions are met - outside contract can be sure that the expected signer has indeed + * approved the given userOp - and the userOp is successfully validate. + * + * @param userOpHash UserOp hash being validated. + * @param signatureData Signature provided as the userOp.signature parameter (minus the prepended tx type byte). + * @param expectedSigner Signer expected to be recovered when decoding the ERC20OPermit signature. + */ + function validateUserOp(bytes32 userOpHash, bytes calldata signatureData, address expectedSigner) + internal + view + returns (uint256) + { + bytes32 superTxHash; + uint48 lowerBoundTimestamp; + uint48 upperBoundTimestamp; + bytes32[] calldata proof; + bytes calldata secp256k1Signature; + + assembly { + superTxHash := calldataload(signatureData.offset) + lowerBoundTimestamp := calldataload(add(signatureData.offset, 0x20)) + upperBoundTimestamp := calldataload(add(signatureData.offset, 0x40)) + let u := calldataload(add(signatureData.offset, 0x60)) + let s := add(signatureData.offset, u) + proof.offset := add(s, 0x20) + proof.length := calldataload(s) + u := mul(proof.length, 0x20) + s := add(proof.offset, u) + secp256k1Signature.offset := add(s, 0x20) + secp256k1Signature.length := calldataload(s) + } + + bytes32 leaf = MEEUserOpHashLib.getMEEUserOpHash(userOpHash, lowerBoundTimestamp, upperBoundTimestamp); + if (!EcdsaLib.isValidSignature(expectedSigner, superTxHash, secp256k1Signature)) { + return SIG_VALIDATION_FAILED; + } + + if (!MerkleProof.verify(proof, superTxHash, leaf)) { + return SIG_VALIDATION_FAILED; + } + + return _packValidationData(false, upperBoundTimestamp, lowerBoundTimestamp); + } + + /** + * @notice Validates the signature against the expected signer (owner) + * @param owner Signer expected to be recovered + * @param dataHash data hash being validated. + * @param signatureData Signature + */ + function validateSignatureForOwner(address owner, bytes32 dataHash, bytes calldata signatureData) + internal + view + returns (bool) + { + bytes32 superTxHash; + bytes32[] calldata proof; + bytes calldata secp256k1Signature; + + assembly { + superTxHash := calldataload(signatureData.offset) + let u := calldataload(add(signatureData.offset, 0x20)) + let s := add(signatureData.offset, u) + proof.offset := add(s, 0x20) + proof.length := calldataload(s) + u := mul(proof.length, 0x20) + s := add(proof.offset, u) + secp256k1Signature.offset := add(s, 0x20) + secp256k1Signature.length := calldataload(s) + } + + if (!EcdsaLib.isValidSignature(owner, superTxHash, secp256k1Signature)) { + return false; + } + + if (!MerkleProof.verify(proof, superTxHash, dataHash)) { + return false; + } + + return true; + } +} diff --git a/biconomy/mee/0.0.4/contracts/lib/fusion/TxValidatorLib.sol b/biconomy/mee/0.0.4/contracts/lib/fusion/TxValidatorLib.sol new file mode 100644 index 0000000..50db41e --- /dev/null +++ b/biconomy/mee/0.0.4/contracts/lib/fusion/TxValidatorLib.sol @@ -0,0 +1,312 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.27; + +import {MerkleProof} from "openzeppelin/utils/cryptography/MerkleProof.sol"; +import {RLPReader as RLPDecoder} from "rlp-reader/RLPReader.sol"; +import {RLPEncoder} from "../rlp/RLPEncoder.sol"; +import {MEEUserOpHashLib} from "../util/MEEUserOpHashLib.sol"; +import {EcdsaLib} from "../util/EcdsaLib.sol"; +import {BytesLib} from "byteslib/BytesLib.sol"; +import "account-abstraction/core/Helpers.sol"; + +/** + * @dev Library to validate the signature for MEE on-chain Txn mode + * This is the mode where superTx hash is appended to a regular txn (legacy or 1559) calldata + * Type 1 (EIP-2930) transactions are not supported. + * The whole txn is signed along with the superTx hash + * Txn is executed prior to a superTx, so it can pass some funds from the EOA to the smart account + * For more details see Fusion docs: + * - https://ethresear.ch/t/fusion-module-7702-alternative-with-no-protocol-changes/20949 + * - https://docs.biconomy.io/explained/eoa#fusion-module + * @dev Some smart contracts may not be able to consume the txn with bytes32 appended to the calldata. + * However this is very small subset. One of the cases when it can happen is when the smart contract + * is has separate receive() and fallback() functions. Then if a txn is a value transfer, it will + * be expected to be consumed by the receive() function. However, if there's bytes32 appended to the calldata, + * it will be consumed by the fallback() function which may not be expected. In this case, the provided + * contracts/forwarder/Forwarder.sol can be used to 'clear' the bytes32 from the calldata. + * @dev In theory, the last 32 bytes of calldata from any transaction by the EOA can be interpreted as + * a superTx hash. Even if it was not assumed. This introduces the potential risk of phishing attacks + * where the user may unknowingly sign a transaction where the last 32 bytes of the calldata end up + * being a superTx hash. However, it is not easy to craft a txn that makes sense for a user and allows + * arbitrary bytes32 as last 32 bytes. Thus, wallets and users should be aware of this potential risk + * and should not sign txns where the last 32 bytes of the calldata do not belong to the function arguments + * and are just appended at the end. + */ +library TxValidatorLib { + uint8 constant LEGACY_TX_TYPE = 0x00; + uint8 constant EIP1559_TX_TYPE = 0x02; + + uint8 constant EIP_155_MIN_V_VALUE = 37; + uint8 constant HASH_BYTE_SIZE = 32; + + uint8 constant TIMESTAMP_BYTE_SIZE = 6; + uint8 constant PROOF_ITEM_BYTE_SIZE = 32; + uint8 constant ITX_HASH_BYTE_SIZE = 32; + + using RLPDecoder for RLPDecoder.RLPItem; + using RLPDecoder for bytes; + using RLPEncoder for uint256; + using BytesLib for bytes; + + struct TxData { + uint8 txType; + uint8 v; + bytes32 r; + bytes32 s; + bytes32 utxHash; + bytes32 superTxHash; + bytes32[] proof; + uint48 lowerBoundTimestamp; + uint48 upperBoundTimestamp; + } + + // To save a bit of gas, not pass timestamps where not needed + struct TxDataShort { + uint8 txType; + uint8 v; + bytes32 r; + bytes32 s; + bytes32 utxHash; + bytes32 superTxHash; + bytes32[] proof; + } + + struct TxParams { + uint256 v; + bytes32 r; + bytes32 s; + bytes callData; + } + + /** + * This function parses the given userOpSignature into a valid fully signed EVM transaction. + * Once parsed, the function will check for three conditions: + * 1. is the userOp part of the superTX merkle tree + * 2. is the recovered tx signer equal to the expected signer? + * 3. is the given UserOp a part of the merkle tree + * + * If all the conditions are met - outside contract can be sure that the expected signer has indeed + * approved the given hash by performing given on-chain transaction. + * + * NOTES: This function will revert if either of following is met: + * 1. the userOpSignature couldn't be parsed to a valid fully signed EVM transaction + * 2. hash couldn't be extracted from the tx.data + * 3. extracted hash wasn't equal to the provided expected hash + * 4. recovered signer wasn't equal to the expected signer + * + * @param userOpHash UserOp hash being validated. + * @param parsedSignature Signature provided as the userOp.signature parameter (minus the prepended tx type byte). + * Expecting to receive fully signed serialized EVM transaction here of type 0x00 (LEGACY) + * or 0x02 (EIP1556). + * For LEGACY tx type the "0x00" prefix has to be added manually while the EIP1559 tx type + * already contains 0x02 prefix. + * @param expectedSigner Expected EOA signer of the given EVM transaction => superTX. + */ + function validateUserOp(bytes32 userOpHash, bytes calldata parsedSignature, address expectedSigner) + internal + view + returns (uint256) + { + TxData memory decodedTx = decodeTx(parsedSignature); + + bytes32 meeUserOpHash = + MEEUserOpHashLib.getMEEUserOpHash(userOpHash, decodedTx.lowerBoundTimestamp, decodedTx.upperBoundTimestamp); + + bytes memory signature = abi.encodePacked(decodedTx.r, decodedTx.s, decodedTx.v); + if (!EcdsaLib.isValidSignature(expectedSigner, decodedTx.utxHash, signature)) { + return SIG_VALIDATION_FAILED; + } + + if (!MerkleProof.verify(decodedTx.proof, decodedTx.superTxHash, meeUserOpHash)) { + return SIG_VALIDATION_FAILED; + } + + return _packValidationData(false, decodedTx.upperBoundTimestamp, decodedTx.lowerBoundTimestamp); + } + + /** + * @dev validate the signature for the owner of the superTx + * used fot the 1271 flow and for the stateless validators (erc7579 module type 7) + * @param expectedSigner the expected signer of the superTx + * @param dataHash the hash of the data to be signed + * @param parsedSignature the signature to be validated + * @return true if the signature is valid, false otherwise + */ + function validateSignatureForOwner(address expectedSigner, bytes32 dataHash, bytes calldata parsedSignature) + internal + view + returns (bool) + { + TxDataShort memory decodedTx = decodeTxShort(parsedSignature); + + bytes memory signature = abi.encodePacked(decodedTx.r, decodedTx.s, decodedTx.v); + + if (!EcdsaLib.isValidSignature(expectedSigner, decodedTx.utxHash, signature)) { + return false; + } + + if (!MerkleProof.verify(decodedTx.proof, decodedTx.superTxHash, dataHash)) { + return false; + } + return true; + } + + function decodeTx(bytes calldata self) internal pure returns (TxData memory) { + uint8 txType = uint8(self[0]); //first byte is tx type + uint48 lowerBoundTimestamp = + uint48(bytes6((self[self.length - 2 * TIMESTAMP_BYTE_SIZE:self.length - TIMESTAMP_BYTE_SIZE]))); + uint48 upperBoundTimestamp = uint48(bytes6(self[self.length - TIMESTAMP_BYTE_SIZE:])); + uint8 proofItemsCount = uint8(self[self.length - 2 * TIMESTAMP_BYTE_SIZE - 1]); + uint256 appendedDataLen = (uint256(proofItemsCount) * PROOF_ITEM_BYTE_SIZE + 1) + 2 * TIMESTAMP_BYTE_SIZE; + bytes calldata rlpEncodedTx = self[1:self.length - appendedDataLen]; + RLPDecoder.RLPItem memory parsedRlpEncodedTx = rlpEncodedTx.toRlpItem(); + RLPDecoder.RLPItem[] memory parsedRlpEncodedTxItems = parsedRlpEncodedTx.toList(); + TxParams memory params = extractParams(txType, parsedRlpEncodedTxItems); + + return TxData( + txType, + _adjustV(params.v), + params.r, + params.s, + calculateUnsignedTxHash(txType, rlpEncodedTx, parsedRlpEncodedTx.payloadLen(), params.v, params.r, params.s), + extractAppendedHash(params.callData), + extractProof(self, proofItemsCount), + lowerBoundTimestamp, + upperBoundTimestamp + ); + } + + function decodeTxShort(bytes calldata self) internal pure returns (TxDataShort memory) { + uint8 txType = uint8(self[0]); //first byte is tx type + uint8 proofItemsCount = uint8(self[self.length - 1]); + uint256 appendedDataLen = (uint256(proofItemsCount) * PROOF_ITEM_BYTE_SIZE + 1); + bytes calldata rlpEncodedTx = self[1:self.length - appendedDataLen]; + RLPDecoder.RLPItem memory parsedRlpEncodedTx = rlpEncodedTx.toRlpItem(); + RLPDecoder.RLPItem[] memory parsedRlpEncodedTxItems = parsedRlpEncodedTx.toList(); + TxParams memory params = extractParams(txType, parsedRlpEncodedTxItems); + + return TxDataShort( + txType, + _adjustV(params.v), + params.r, + params.s, + calculateUnsignedTxHash(txType, rlpEncodedTx, parsedRlpEncodedTx.payloadLen(), params.v, params.r, params.s), + extractAppendedHash(params.callData), + extractProofShort(self, proofItemsCount) + ); + } + + function extractParams(uint8 txType, RLPDecoder.RLPItem[] memory items) + private + pure + returns (TxParams memory params) + { + uint8 dataPos; + uint8 vPos; + uint8 rPos; + uint8 sPos; + + if (txType == LEGACY_TX_TYPE) { + dataPos = 5; + vPos = 6; + rPos = 7; + sPos = 8; + } else if (txType == EIP1559_TX_TYPE) { + dataPos = 7; + vPos = 9; + rPos = 10; + sPos = 11; + } else { + revert("TxValidatorLib:: unsupported evm tx type"); + } + + return TxParams( + items[vPos].toUint(), bytes32(items[rPos].toUint()), bytes32(items[sPos].toUint()), items[dataPos].toBytes() + ); + } + + function extractAppendedHash(bytes memory callData) private pure returns (bytes32 iTxHash) { + if (callData.length < ITX_HASH_BYTE_SIZE) revert("TxDecoder:: callData length too short"); + iTxHash = bytes32(callData.slice(callData.length - ITX_HASH_BYTE_SIZE, ITX_HASH_BYTE_SIZE)); + } + + function extractProof(bytes calldata signedTx, uint8 proofItemsCount) + private + pure + returns (bytes32[] memory proof) + { + proof = new bytes32[](proofItemsCount); + uint256 pos = signedTx.length - 2 * TIMESTAMP_BYTE_SIZE - 1; + for (proofItemsCount; proofItemsCount > 0; proofItemsCount--) { + proof[proofItemsCount - 1] = bytes32(signedTx[pos - PROOF_ITEM_BYTE_SIZE:pos]); + pos = pos - PROOF_ITEM_BYTE_SIZE; + } + } + + function extractProofShort(bytes calldata signedTx, uint8 proofItemsCount) + private + pure + returns (bytes32[] memory proof) + { + proof = new bytes32[](proofItemsCount); + uint256 pos = signedTx.length - 1; + for (proofItemsCount; proofItemsCount > 0; proofItemsCount--) { + proof[proofItemsCount - 1] = bytes32(signedTx[pos - PROOF_ITEM_BYTE_SIZE:pos]); + pos = pos - PROOF_ITEM_BYTE_SIZE; + } + } + + function calculateUnsignedTxHash( + uint8 txType, + bytes memory rlpEncodedTx, + uint256 rlpEncodedTxPayloadLen, + uint256 v, + bytes32 r, + bytes32 s + ) private pure returns (bytes32 hash) { + uint256 totalSignatureSize = + uint256(r).encodeUint().length + uint256(s).encodeUint().length + v.encodeUint().length; + uint256 totalPrefixSize = rlpEncodedTx.length - rlpEncodedTxPayloadLen; + bytes memory rlpEncodedTxNoSigAndPrefix = + rlpEncodedTx.slice(totalPrefixSize, rlpEncodedTx.length - totalSignatureSize - totalPrefixSize); + if (txType == EIP1559_TX_TYPE) { + return keccak256(abi.encodePacked(txType, prependRlpContentSize(rlpEncodedTxNoSigAndPrefix, ""))); + } else if (txType == LEGACY_TX_TYPE) { + if (v >= EIP_155_MIN_V_VALUE) { + return keccak256( + prependRlpContentSize( + rlpEncodedTxNoSigAndPrefix, + abi.encodePacked( + uint256(_extractChainIdFromV(v)).encodeUint(), + uint256(0).encodeUint(), + uint256(0).encodeUint() + ) + ) + ); + } else { + return keccak256(prependRlpContentSize(rlpEncodedTxNoSigAndPrefix, "")); + } + } else { + revert("TxValidatorLib:: unsupported tx type"); + } + } + + function prependRlpContentSize(bytes memory content, bytes memory extraData) public pure returns (bytes memory) { + bytes memory combinedContent = abi.encodePacked(content, extraData); + return abi.encodePacked(combinedContent.length.encodeLength(RLPDecoder.LIST_SHORT_START), combinedContent); + } + + function _adjustV(uint256 v) internal pure returns (uint8) { + if (v >= EIP_155_MIN_V_VALUE) { + return uint8((v - 2 * _extractChainIdFromV(v) - 35) + 27); + } else if (v <= 1) { + return uint8(v + 27); + } else { + return uint8(v); + } + } + + function _extractChainIdFromV(uint256 v) internal pure returns (uint256 chainId) { + chainId = (v - 35) / 2; + } +} diff --git a/biconomy/mee/0.0.4/contracts/lib/rlp/RLPEncoder.sol b/biconomy/mee/0.0.4/contracts/lib/rlp/RLPEncoder.sol new file mode 100644 index 0000000..e514ca9 --- /dev/null +++ b/biconomy/mee/0.0.4/contracts/lib/rlp/RLPEncoder.sol @@ -0,0 +1,99 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.27; + +// Had to keep it copypasted as the og lib https://github.com/bakaoh/solidity-rlp-encode has incompatible solc version + +import "byteslib/BytesLib.sol"; + +/** + * @title RLPEncoder + * @dev A simple RLP encoding library. + * @author Bakaoh + */ +library RLPEncoder { + using BytesLib for bytes; + + /* + * Internal functions + */ + + /** + * @dev RLP encodes a byte string. + * @param self The byte string to encode. + * @return The RLP encoded string in bytes. + */ + function encodeBytes(bytes memory self) internal pure returns (bytes memory) { + bytes memory encoded; + if (self.length == 1 && uint8(self[0]) < 128) { + encoded = self; + } else { + encoded = encodeLength(self.length, 128).concat(self); + } + return encoded; + } + + /** + * @dev RLP encodes a uint. + * @param self The uint to encode. + * @return The RLP encoded uint in bytes. + */ + function encodeUint(uint256 self) internal pure returns (bytes memory) { + return encodeBytes(toBinary(self)); + } + + /** + * @dev Encode the first byte, followed by the `len` in binary form if `length` is more than 55. + * @param self The length of the string or the payload. + * @param offset 128 if item is string, 192 if item is list. + * @return RLP encoded bytes. + */ + function encodeLength(uint256 self, uint256 offset) internal pure returns (bytes memory) { + bytes memory encoded; + if (self < 56) { + encoded = new bytes(1); + encoded[0] = bytes32(self + offset)[31]; + } else { + uint256 lenLen; + uint256 i = 1; + while (self / i != 0) { + lenLen++; + i *= 256; + } + + encoded = new bytes(lenLen + 1); + encoded[0] = bytes32(lenLen + offset + 55)[31]; + for (i = 1; i <= lenLen; i++) { + encoded[i] = bytes32((self / (256 ** (lenLen - i))) % 256)[31]; + } + } + return encoded; + } + + /* + * Private functions + */ + + /** + * @dev Encode integer in big endian binary form with no leading zeroes. + * @notice TODO: This should be optimized with assembly to save gas costs. + * @param _x The integer to encode. + * @return RLP encoded bytes. + */ + function toBinary(uint256 _x) private pure returns (bytes memory) { + bytes memory b = new bytes(32); + assembly { + mstore(add(b, 32), _x) + } + uint256 i; + for (i = 0; i < 32; i++) { + if (b[i] != 0) { + break; + } + } + bytes memory res = new bytes(32 - i); + for (uint256 j = 0; j < res.length; j++) { + res[j] = b[i++]; + } + return res; + } +} diff --git a/biconomy/mee/0.0.4/contracts/lib/util/EcdsaLib.sol b/biconomy/mee/0.0.4/contracts/lib/util/EcdsaLib.sol new file mode 100644 index 0000000..dad3450 --- /dev/null +++ b/biconomy/mee/0.0.4/contracts/lib/util/EcdsaLib.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.27; + +import {ECDSA} from "solady/utils/ECDSA.sol"; + +library EcdsaLib { + using ECDSA for bytes32; + + /** + * @dev Solady ECDSA does not revert on incorrect signatures. + * Instead, it returns address(0) as the recovered address. + * Make sure to never pass address(0) as expectedSigner to this function. + */ + function isValidSignature(address expectedSigner, bytes32 hash, bytes memory signature) + internal + view + returns (bool) + { + if (hash.tryRecover(signature) == expectedSigner) return true; + if (hash.toEthSignedMessageHash().tryRecover(signature) == expectedSigner) return true; + return false; + } +} diff --git a/biconomy/mee/0.0.4/contracts/lib/util/MEEUserOpHashLib.sol b/biconomy/mee/0.0.4/contracts/lib/util/MEEUserOpHashLib.sol new file mode 100644 index 0000000..e5849bd --- /dev/null +++ b/biconomy/mee/0.0.4/contracts/lib/util/MEEUserOpHashLib.sol @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: Unlicense +/* + * @title MEE UserOp Hash Lib + * + * @dev Calculates userOp hash for the new type of transaction - SuperTransaction (as a part of MEE stack) + */ +pragma solidity ^0.8.27; + +library MEEUserOpHashLib { + /** + * Calculates userOp hash. Almost works like a regular 4337 userOp hash with few fields added. + * + * @param userOpHash userOp hash to calculate the hash for + * @param lowerBoundTimestamp lower bound timestamp set when constructing userOp + * @param upperBoundTimestamp upper bound timestamp set when constructing userOp + * Timestamps are used by the MEE node to schedule the execution of the userOps within the superTx + */ + function getMEEUserOpHash(bytes32 userOpHash, uint256 lowerBoundTimestamp, uint256 upperBoundTimestamp) + internal + pure + returns (bytes32 meeUserOpHash) + { + meeUserOpHash = + keccak256(bytes.concat(keccak256(abi.encode(userOpHash, lowerBoundTimestamp, upperBoundTimestamp)))); + } +} diff --git a/biconomy/mee/0.0.4/contracts/types/Constants.sol b/biconomy/mee/0.0.4/contracts/types/Constants.sol new file mode 100644 index 0000000..4556c37 --- /dev/null +++ b/biconomy/mee/0.0.4/contracts/types/Constants.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.27; + +bytes3 constant SIG_TYPE_MEE_FLOW = 0x177eee; + +bytes4 constant SIG_TYPE_SIMPLE = 0x177eee00; +bytes4 constant SIG_TYPE_ON_CHAIN = 0x177eee01; +bytes4 constant SIG_TYPE_ERC20_PERMIT = 0x177eee02; +// ...other sig types: ERC-7683, Permit2, etc + +bytes4 constant EIP1271_SUCCESS = 0x1626ba7e; +bytes4 constant EIP1271_FAILED = 0xffffffff; + +uint256 constant MODULE_TYPE_STATELESS_VALIDATOR = 7; diff --git a/biconomy/mee/0.0.4/contracts/validators/K1MeeValidator.sol b/biconomy/mee/0.0.4/contracts/validators/K1MeeValidator.sol new file mode 100644 index 0000000..0316152 --- /dev/null +++ b/biconomy/mee/0.0.4/contracts/validators/K1MeeValidator.sol @@ -0,0 +1,338 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.27; + +import {IValidator, MODULE_TYPE_VALIDATOR} from "erc7579/interfaces/IERC7579Module.sol"; +import {ISessionValidator} from "contracts/interfaces/ISessionValidator.sol"; +import {EnumerableSet} from "EnumerableSet4337/EnumerableSet4337.sol"; +import {PackedUserOperation} from "account-abstraction/interfaces/PackedUserOperation.sol"; +import {ERC7739Validator} from "erc7739Validator/ERC7739Validator.sol"; +import { + SIG_TYPE_SIMPLE, + SIG_TYPE_ON_CHAIN, + SIG_TYPE_ERC20_PERMIT, + EIP1271_SUCCESS, + EIP1271_FAILED, + MODULE_TYPE_STATELESS_VALIDATOR, + SIG_TYPE_MEE_FLOW +} from "contracts/types/Constants.sol"; +// Fusion libraries - validate userOp using on-chain tx or off-chain permit +import {PermitValidatorLib} from "contracts/lib/fusion/PermitValidatorLib.sol"; +import {TxValidatorLib} from "contracts/lib/fusion/TxValidatorLib.sol"; +import {SimpleValidatorLib} from "contracts/lib/fusion/SimpleValidatorLib.sol"; +import {NoMeeFlowLib} from "contracts/lib/fusion/NoMeeFlowLib.sol"; +import {EcdsaLib} from "contracts/lib/util/EcdsaLib.sol"; +/** + * @title K1MeeValidator + * @dev An ERC-7579 validator (module type 1) and stateless validator (module type 7) for the MEE stack. + * Supports 3 MEE modes: + * - Simple (Super Tx hash is signed) + * - On-chain Tx (Super Tx hash is appended to a regular txn and signed) + * - ERC-2612 Permit (Super Tx hash is pasted into deadline field of the ERC-2612 Permit and signed) + * + * Further improvements: + * - Further gas optimizations + * - Use EIP-712 to make superTx hash not blind => use 7739 for the MEE 1271 flows + * + * Using erc7739 for MEE flows makes no sense currently because user signs blind hashes anyways + * (except permit mode, but the superTx hash is still blind in it). + * So we just hash smart account address into the og hash for 1271 MEE flow currently. + * In future full scale 7739 will replace it when superTx hash is 712 and transparent. + * + */ + +contract K1MeeValidator is IValidator, ISessionValidator, ERC7739Validator { + using EnumerableSet for EnumerableSet.AddressSet; + /*////////////////////////////////////////////////////////////////////////// + CONSTANTS & STORAGE + //////////////////////////////////////////////////////////////////////////*/ + + /// @notice Mapping of smart account addresses to their respective owner addresses + mapping(address => address) public smartAccountOwners; + + /// @notice Set of safe senders for each smart account + EnumerableSet.AddressSet private _safeSenders; + + /// @notice Error to indicate that no owner was provided during installation + error NoOwnerProvided(); + + /// @notice Error to indicate that the new owner cannot be the zero address + error ZeroAddressNotAllowed(); + + /// @notice Error to indicate the module is already initialized + error ModuleAlreadyInitialized(); + + /// @notice Error to indicate that the new owner cannot be a contract address + error NewOwnerIsContract(); + + /// @notice Error to indicate that the owner cannot be the zero address + error OwnerCannotBeZeroAddress(); + + /// @notice Error to indicate that the data length is invalid + error InvalidDataLength(); + + /// @notice Error to indicate that the safe senders length is invalid + error SafeSendersLengthInvalid(); + + /*////////////////////////////////////////////////////////////////////////// + CONFIG + //////////////////////////////////////////////////////////////////////////*/ + + /** + * Initialize the module with the given data + * + * @param data The data to initialize the module with + */ + function onInstall(bytes calldata data) external override { + require(data.length != 0, NoOwnerProvided()); + require(!_isInitialized(msg.sender), ModuleAlreadyInitialized()); + address newOwner = address(bytes20(data[:20])); + require(newOwner != address(0), OwnerCannotBeZeroAddress()); + require(!_isContract(newOwner), NewOwnerIsContract()); + smartAccountOwners[msg.sender] = newOwner; + if (data.length > 20) { + _fillSafeSenders(data[20:]); + } + } + + /** + * De-initialize the module with the given data + */ + function onUninstall(bytes calldata) external override { + delete smartAccountOwners[msg.sender]; + _safeSenders.removeAll(msg.sender); + } + + /// @notice Transfers ownership of the validator to a new owner + /// @param newOwner The address of the new owner + function transferOwnership(address newOwner) external { + require(newOwner != address(0), ZeroAddressNotAllowed()); + require(!_isContract(newOwner), NewOwnerIsContract()); + smartAccountOwners[msg.sender] = newOwner; + } + + /** + * Check if the module is initialized + * @param smartAccount The smart account to check + * + * @return true if the module is initialized, false otherwise + */ + function isInitialized(address smartAccount) external view returns (bool) { + return _isInitialized(smartAccount); + } + + /// @notice Adds a safe sender to the _safeSenders list for the smart account + function addSafeSender(address sender) external { + _safeSenders.add(msg.sender, sender); + } + + /// @notice Removes a safe sender from the _safeSenders list for the smart account + function removeSafeSender(address sender) external { + _safeSenders.remove(msg.sender, sender); + } + + /// @notice Checks if a sender is in the _safeSenders list for the smart account + function isSafeSender(address sender, address smartAccount) external view returns (bool) { + return _safeSenders.contains(smartAccount, sender); + } + + /*////////////////////////////////////////////////////////////////////////// + MODULE LOGIC + //////////////////////////////////////////////////////////////////////////*/ + + /** + * Validates PackedUserOperation + * + * @param userOp UserOperation to be validated + * @param userOpHash Hash of the UserOperation to be validated + * @dev fallback flow => non MEE flow => no dedicated prefix introduced for the sake of compatibility. + * It may lead to a case where some signature turns out to have first bytes matching the prefix. + * However, this is very unlikely to happen and even if it does, the consequences are just + * that the signature is not validated which is easily solved by altering userOp => hash => sig. + * + * @return uint256 the result of the signature validation, which can be: + * - 0 if the signature is valid + * - 1 if the signature is invalid + * - <20-byte> aggregatorOrSigFail, <6-byte> validUntil and <6-byte> validAfter (see ERC-4337 + * for more details) + */ + function validateUserOp(PackedUserOperation calldata userOp, bytes32 userOpHash) + external + override + returns (uint256) + { + bytes4 sigType = bytes4(userOp.signature[0:4]); + address owner = getOwner(userOp.sender); + + if (sigType == SIG_TYPE_SIMPLE) { + return SimpleValidatorLib.validateUserOp(userOpHash, userOp.signature[4:], owner); + } else if (sigType == SIG_TYPE_ON_CHAIN) { + return TxValidatorLib.validateUserOp(userOpHash, userOp.signature[4:], owner); + } else if (sigType == SIG_TYPE_ERC20_PERMIT) { + return PermitValidatorLib.validateUserOp(userOpHash, userOp.signature[4:], owner); + } else { + // fallback flow => non MEE flow => no prefix + return NoMeeFlowLib.validateUserOp(userOpHash, userOp.signature, owner); + } + } + + /** + * Validates an ERC-1271 signature + * + * @param sender The sender of the ERC-1271 call to the account + * @param hash The hash of the message + * @param signature The signature of the message + * + * @return sigValidationResult the result of the signature validation, which can be: + * - EIP1271_SUCCESS if the signature is valid + * - EIP1271_FAILED if the signature is invalid + */ + function isValidSignatureWithSender(address sender, bytes32 hash, bytes calldata signature) + external + view + virtual + override + returns (bytes4 sigValidationResult) + { + if (bytes3(signature[0:3]) != SIG_TYPE_MEE_FLOW) { + // Non MEE 7739 flow + // goes to ERC7739Validator to apply 7739 magic and then returns back + // to this contract's _erc1271IsValidSignatureNowCalldata() method. + return _erc1271IsValidSignatureWithSender(sender, hash, _erc1271UnwrapSignature(signature)); + } else { + // non-7739 flow + // hash the SA into the `hash` to protect against two SA's with same owner vector + return _validateSignatureForOwner( + getOwner(msg.sender), keccak256(abi.encodePacked(hash, msg.sender)), _erc1271UnwrapSignature(signature) + ) ? EIP1271_SUCCESS : EIP1271_FAILED; + } + } + + /// @notice ISessionValidator interface for smart session + /// @param hash The hash of the data to validate + /// @param sig The signature data + /// @param data The data to validate against (owner address in this case) + function validateSignatureWithData(bytes32 hash, bytes calldata sig, bytes calldata data) + external + view + returns (bool validSig) + { + require(data.length >= 20, InvalidDataLength()); + return _validateSignatureForOwner(address(bytes20(data[:20])), hash, sig); + } + + /** + * Get the owner of the smart account + * @param smartAccount The address of the smart account + * @return The owner of the smart account + */ + function getOwner(address smartAccount) public view returns (address) { + address owner = smartAccountOwners[smartAccount]; + return owner == address(0) ? smartAccount : owner; + } + + /*////////////////////////////////////////////////////////////////////////// + METADATA + //////////////////////////////////////////////////////////////////////////*/ + + /// @notice Returns the name of the module + /// @return The name of the module + function name() external pure returns (string memory) { + return "K1MeeValidator"; + } + + /// @notice Returns the version of the module + /// @return The version of the module + function version() external pure returns (string memory) { + return "1.0.1"; + } + + /// @notice Checks if the module is of the specified type + /// @param typeId The type ID to check + /// @return True if the module is of the specified type, false otherwise + function isModuleType(uint256 typeId) external pure returns (bool) { + return typeId == MODULE_TYPE_VALIDATOR || typeId == MODULE_TYPE_STATELESS_VALIDATOR; + } + + /*////////////////////////////////////////////////////////////////////////// + INTERNAL + //////////////////////////////////////////////////////////////////////////*/ + + /// @notice Internal method that does the job of validating the signature via ECDSA (secp256k1) + /// @param owner The address of the owner + /// @param hash The hash of the data to validate + /// @param signature The signature data + function _validateSignatureForOwner(address owner, bytes32 hash, bytes calldata signature) + internal + view + returns (bool) + { + bytes4 sigType = bytes4(signature[0:4]); + + if (sigType == SIG_TYPE_SIMPLE) { + return SimpleValidatorLib.validateSignatureForOwner(owner, hash, signature[4:]); + } else if (sigType == SIG_TYPE_ON_CHAIN) { + return TxValidatorLib.validateSignatureForOwner(owner, hash, signature[4:]); + } else if (sigType == SIG_TYPE_ERC20_PERMIT) { + return PermitValidatorLib.validateSignatureForOwner(owner, hash, signature[4:]); + } else { + // fallback flow => non MEE flow => no prefix + return NoMeeFlowLib.validateSignatureForOwner(owner, hash, signature); + } + } + + /// @notice Checks if the smart account is initialized with an owner + /// @param smartAccount The address of the smart account + /// @return True if the smart account has an owner, false otherwise + function _isInitialized(address smartAccount) private view returns (bool) { + return smartAccountOwners[smartAccount] != address(0); + } + + // @notice Fills the _safeSenders list from the given data + function _fillSafeSenders(bytes calldata data) private { + require(data.length % 20 == 0, SafeSendersLengthInvalid()); + for (uint256 i; i < data.length / 20; i++) { + _safeSenders.add(msg.sender, address(bytes20(data[20 * i:20 * (i + 1)]))); + } + } + + /// @notice Checks if the address is a contract + /// @param account The address to check + /// @return True if the address is a contract, false otherwise + function _isContract(address account) private view returns (bool) { + uint256 size; + assembly { + size := extcodesize(account) + } + return size > 0; + } + + /// @dev Returns whether the `hash` and `signature` are valid. + /// Obtains the authorized signer's credentials and calls some + /// module's specific internal function to validate the signature + /// against credentials. + function _erc1271IsValidSignatureNowCalldata(bytes32 hash, bytes calldata signature) + internal + view + override + returns (bool) + { + // call custom internal function to validate the signature against credentials + return EcdsaLib.isValidSignature(getOwner(msg.sender), hash, signature); + } + + /// @dev Returns whether the `sender` is considered safe, such + /// that we don't need to use the nested EIP-712 workflow. + /// See: https://mirror.xyz/curiousapple.eth/pFqAdW2LiJ-6S4sg_u1z08k4vK6BCJ33LcyXpnNb8yU + // The canonical `MulticallerWithSigner` at 0x000000000000D9ECebf3C23529de49815Dac1c4c + // is known to include the account in the hash to be signed. + // msg.sender = Smart Account + // sender = 1271 og request sender + function _erc1271CallerIsSafe(address sender) internal view virtual override returns (bool) { + return ( + sender == 0x000000000000D9ECebf3C23529de49815Dac1c4c // MulticallerWithSigner + || sender == msg.sender // Smart Account. Assume smart account never sends non safe eip-712 struct + || _safeSenders.contains(msg.sender, sender) + ); // check if sender is in _safeSenders for the Smart Account + } +} diff --git a/biconomy/mee/0.0.4/expected b/biconomy/mee/0.0.4/expected new file mode 100644 index 0000000..b61594c --- /dev/null +++ b/biconomy/mee/0.0.4/expected @@ -0,0 +1 @@ +K1MeeValidator 0x00000000d12897DDAdC2044614A9677B191A2d95 diff --git a/biconomy/mee/0.0.4/foundry.toml b/biconomy/mee/0.0.4/foundry.toml new file mode 100644 index 0000000..932937e --- /dev/null +++ b/biconomy/mee/0.0.4/foundry.toml @@ -0,0 +1,15 @@ +[profile.default] +src = "contracts" +out = "out" +libs = ["lib"] +auto_detect_remappings = false +auto_detect_solc = false +solc_version = "0.8.27" +evm_version = "cancun" +via_ir = true +cbor_metadata = true +use_literal_content = false +bytecode_hash = "none" +optimizer = true +optimizer_runs = 999 +libraries = [] diff --git a/biconomy/mee/0.0.4/node_modules/@openzeppelin/contracts/token/ERC20/IERC20.sol b/biconomy/mee/0.0.4/node_modules/@openzeppelin/contracts/token/ERC20/IERC20.sol new file mode 100644 index 0000000..db01cf4 --- /dev/null +++ b/biconomy/mee/0.0.4/node_modules/@openzeppelin/contracts/token/ERC20/IERC20.sol @@ -0,0 +1,79 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/IERC20.sol) + +pragma solidity ^0.8.20; + +/** + * @dev Interface of the ERC20 standard as defined in the EIP. + */ +interface IERC20 { + /** + * @dev Emitted when `value` tokens are moved from one account (`from`) to + * another (`to`). + * + * Note that `value` may be zero. + */ + event Transfer(address indexed from, address indexed to, uint256 value); + + /** + * @dev Emitted when the allowance of a `spender` for an `owner` is set by + * a call to {approve}. `value` is the new allowance. + */ + event Approval(address indexed owner, address indexed spender, uint256 value); + + /** + * @dev Returns the value of tokens in existence. + */ + function totalSupply() external view returns (uint256); + + /** + * @dev Returns the value of tokens owned by `account`. + */ + function balanceOf(address account) external view returns (uint256); + + /** + * @dev Moves a `value` amount of tokens from the caller's account to `to`. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * Emits a {Transfer} event. + */ + function transfer(address to, uint256 value) external returns (bool); + + /** + * @dev Returns the remaining number of tokens that `spender` will be + * allowed to spend on behalf of `owner` through {transferFrom}. This is + * zero by default. + * + * This value changes when {approve} or {transferFrom} are called. + */ + function allowance(address owner, address spender) external view returns (uint256); + + /** + * @dev Sets a `value` amount of tokens as the allowance of `spender` over the + * caller's tokens. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * IMPORTANT: Beware that changing an allowance with this method brings the risk + * that someone may use both the old and the new allowance by unfortunate + * transaction ordering. One possible solution to mitigate this race + * condition is to first reduce the spender's allowance to 0 and set the + * desired value afterwards: + * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 + * + * Emits an {Approval} event. + */ + function approve(address spender, uint256 value) external returns (bool); + + /** + * @dev Moves a `value` amount of tokens from `from` to `to` using the + * allowance mechanism. `value` is then deducted from the caller's + * allowance. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * Emits a {Transfer} event. + */ + function transferFrom(address from, address to, uint256 value) external returns (bool); +} diff --git a/biconomy/mee/0.0.4/node_modules/@openzeppelin/contracts/token/ERC20/extensions/IERC20Permit.sol b/biconomy/mee/0.0.4/node_modules/@openzeppelin/contracts/token/ERC20/extensions/IERC20Permit.sol new file mode 100644 index 0000000..5af4810 --- /dev/null +++ b/biconomy/mee/0.0.4/node_modules/@openzeppelin/contracts/token/ERC20/extensions/IERC20Permit.sol @@ -0,0 +1,90 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/extensions/IERC20Permit.sol) + +pragma solidity ^0.8.20; + +/** + * @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in + * https://eips.ethereum.org/EIPS/eip-2612[EIP-2612]. + * + * Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by + * presenting a message signed by the account. By not relying on {IERC20-approve}, the token holder account doesn't + * need to send a transaction, and thus is not required to hold Ether at all. + * + * ==== Security Considerations + * + * There are two important considerations concerning the use of `permit`. The first is that a valid permit signature + * expresses an allowance, and it should not be assumed to convey additional meaning. In particular, it should not be + * considered as an intention to spend the allowance in any specific way. The second is that because permits have + * built-in replay protection and can be submitted by anyone, they can be frontrun. A protocol that uses permits should + * take this into consideration and allow a `permit` call to fail. Combining these two aspects, a pattern that may be + * generally recommended is: + * + * ```solidity + * function doThingWithPermit(..., uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) public { + * try token.permit(msg.sender, address(this), value, deadline, v, r, s) {} catch {} + * doThing(..., value); + * } + * + * function doThing(..., uint256 value) public { + * token.safeTransferFrom(msg.sender, address(this), value); + * ... + * } + * ``` + * + * Observe that: 1) `msg.sender` is used as the owner, leaving no ambiguity as to the signer intent, and 2) the use of + * `try/catch` allows the permit to fail and makes the code tolerant to frontrunning. (See also + * {SafeERC20-safeTransferFrom}). + * + * Additionally, note that smart contract wallets (such as Argent or Safe) are not able to produce permit signatures, so + * contracts should have entry points that don't rely on permit. + */ +interface IERC20Permit { + /** + * @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens, + * given ``owner``'s signed approval. + * + * IMPORTANT: The same issues {IERC20-approve} has related to transaction + * ordering also apply here. + * + * Emits an {Approval} event. + * + * Requirements: + * + * - `spender` cannot be the zero address. + * - `deadline` must be a timestamp in the future. + * - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner` + * over the EIP712-formatted function arguments. + * - the signature must use ``owner``'s current nonce (see {nonces}). + * + * For more information on the signature format, see the + * https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP + * section]. + * + * CAUTION: See Security Considerations above. + */ + function permit( + address owner, + address spender, + uint256 value, + uint256 deadline, + uint8 v, + bytes32 r, + bytes32 s + ) external; + + /** + * @dev Returns the current nonce for `owner`. This value must be + * included whenever a signature is generated for {permit}. + * + * Every successful call to {permit} increases ``owner``'s nonce by one. This + * prevents a signature from being used multiple times. + */ + function nonces(address owner) external view returns (uint256); + + /** + * @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}. + */ + // solhint-disable-next-line func-name-mixedcase + function DOMAIN_SEPARATOR() external view returns (bytes32); +} diff --git a/biconomy/mee/0.0.4/node_modules/@openzeppelin/contracts/utils/Strings.sol b/biconomy/mee/0.0.4/node_modules/@openzeppelin/contracts/utils/Strings.sol new file mode 100644 index 0000000..b2c0a40 --- /dev/null +++ b/biconomy/mee/0.0.4/node_modules/@openzeppelin/contracts/utils/Strings.sol @@ -0,0 +1,94 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (utils/Strings.sol) + +pragma solidity ^0.8.20; + +import {Math} from "./math/Math.sol"; +import {SignedMath} from "./math/SignedMath.sol"; + +/** + * @dev String operations. + */ +library Strings { + bytes16 private constant HEX_DIGITS = "0123456789abcdef"; + uint8 private constant ADDRESS_LENGTH = 20; + + /** + * @dev The `value` string doesn't fit in the specified `length`. + */ + error StringsInsufficientHexLength(uint256 value, uint256 length); + + /** + * @dev Converts a `uint256` to its ASCII `string` decimal representation. + */ + function toString(uint256 value) internal pure returns (string memory) { + unchecked { + uint256 length = Math.log10(value) + 1; + string memory buffer = new string(length); + uint256 ptr; + /// @solidity memory-safe-assembly + assembly { + ptr := add(buffer, add(32, length)) + } + while (true) { + ptr--; + /// @solidity memory-safe-assembly + assembly { + mstore8(ptr, byte(mod(value, 10), HEX_DIGITS)) + } + value /= 10; + if (value == 0) break; + } + return buffer; + } + } + + /** + * @dev Converts a `int256` to its ASCII `string` decimal representation. + */ + function toStringSigned(int256 value) internal pure returns (string memory) { + return string.concat(value < 0 ? "-" : "", toString(SignedMath.abs(value))); + } + + /** + * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation. + */ + function toHexString(uint256 value) internal pure returns (string memory) { + unchecked { + return toHexString(value, Math.log256(value) + 1); + } + } + + /** + * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length. + */ + function toHexString(uint256 value, uint256 length) internal pure returns (string memory) { + uint256 localValue = value; + bytes memory buffer = new bytes(2 * length + 2); + buffer[0] = "0"; + buffer[1] = "x"; + for (uint256 i = 2 * length + 1; i > 1; --i) { + buffer[i] = HEX_DIGITS[localValue & 0xf]; + localValue >>= 4; + } + if (localValue != 0) { + revert StringsInsufficientHexLength(value, length); + } + return string(buffer); + } + + /** + * @dev Converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal + * representation. + */ + function toHexString(address addr) internal pure returns (string memory) { + return toHexString(uint256(uint160(addr)), ADDRESS_LENGTH); + } + + /** + * @dev Returns true if the two strings are equal. + */ + function equal(string memory a, string memory b) internal pure returns (bool) { + return bytes(a).length == bytes(b).length && keccak256(bytes(a)) == keccak256(bytes(b)); + } +} diff --git a/biconomy/mee/0.0.4/node_modules/@openzeppelin/contracts/utils/cryptography/MerkleProof.sol b/biconomy/mee/0.0.4/node_modules/@openzeppelin/contracts/utils/cryptography/MerkleProof.sol new file mode 100644 index 0000000..525f5da --- /dev/null +++ b/biconomy/mee/0.0.4/node_modules/@openzeppelin/contracts/utils/cryptography/MerkleProof.sol @@ -0,0 +1,232 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (utils/cryptography/MerkleProof.sol) + +pragma solidity ^0.8.20; + +/** + * @dev These functions deal with verification of Merkle Tree proofs. + * + * The tree and the proofs can be generated using our + * https://github.com/OpenZeppelin/merkle-tree[JavaScript library]. + * You will find a quickstart guide in the readme. + * + * WARNING: You should avoid using leaf values that are 64 bytes long prior to + * hashing, or use a hash function other than keccak256 for hashing leaves. + * This is because the concatenation of a sorted pair of internal nodes in + * the Merkle tree could be reinterpreted as a leaf value. + * OpenZeppelin's JavaScript library generates Merkle trees that are safe + * against this attack out of the box. + */ +library MerkleProof { + /** + *@dev The multiproof provided is not valid. + */ + error MerkleProofInvalidMultiproof(); + + /** + * @dev Returns true if a `leaf` can be proved to be a part of a Merkle tree + * defined by `root`. For this, a `proof` must be provided, containing + * sibling hashes on the branch from the leaf to the root of the tree. Each + * pair of leaves and each pair of pre-images are assumed to be sorted. + */ + function verify(bytes32[] memory proof, bytes32 root, bytes32 leaf) internal pure returns (bool) { + return processProof(proof, leaf) == root; + } + + /** + * @dev Calldata version of {verify} + */ + function verifyCalldata(bytes32[] calldata proof, bytes32 root, bytes32 leaf) internal pure returns (bool) { + return processProofCalldata(proof, leaf) == root; + } + + /** + * @dev Returns the rebuilt hash obtained by traversing a Merkle tree up + * from `leaf` using `proof`. A `proof` is valid if and only if the rebuilt + * hash matches the root of the tree. When processing the proof, the pairs + * of leafs & pre-images are assumed to be sorted. + */ + function processProof(bytes32[] memory proof, bytes32 leaf) internal pure returns (bytes32) { + bytes32 computedHash = leaf; + for (uint256 i = 0; i < proof.length; i++) { + computedHash = _hashPair(computedHash, proof[i]); + } + return computedHash; + } + + /** + * @dev Calldata version of {processProof} + */ + function processProofCalldata(bytes32[] calldata proof, bytes32 leaf) internal pure returns (bytes32) { + bytes32 computedHash = leaf; + for (uint256 i = 0; i < proof.length; i++) { + computedHash = _hashPair(computedHash, proof[i]); + } + return computedHash; + } + + /** + * @dev Returns true if the `leaves` can be simultaneously proven to be a part of a Merkle tree defined by + * `root`, according to `proof` and `proofFlags` as described in {processMultiProof}. + * + * CAUTION: Not all Merkle trees admit multiproofs. See {processMultiProof} for details. + */ + function multiProofVerify( + bytes32[] memory proof, + bool[] memory proofFlags, + bytes32 root, + bytes32[] memory leaves + ) internal pure returns (bool) { + return processMultiProof(proof, proofFlags, leaves) == root; + } + + /** + * @dev Calldata version of {multiProofVerify} + * + * CAUTION: Not all Merkle trees admit multiproofs. See {processMultiProof} for details. + */ + function multiProofVerifyCalldata( + bytes32[] calldata proof, + bool[] calldata proofFlags, + bytes32 root, + bytes32[] memory leaves + ) internal pure returns (bool) { + return processMultiProofCalldata(proof, proofFlags, leaves) == root; + } + + /** + * @dev Returns the root of a tree reconstructed from `leaves` and sibling nodes in `proof`. The reconstruction + * proceeds by incrementally reconstructing all inner nodes by combining a leaf/inner node with either another + * leaf/inner node or a proof sibling node, depending on whether each `proofFlags` item is true or false + * respectively. + * + * CAUTION: Not all Merkle trees admit multiproofs. To use multiproofs, it is sufficient to ensure that: 1) the tree + * is complete (but not necessarily perfect), 2) the leaves to be proven are in the opposite order they are in the + * tree (i.e., as seen from right to left starting at the deepest layer and continuing at the next layer). + */ + function processMultiProof( + bytes32[] memory proof, + bool[] memory proofFlags, + bytes32[] memory leaves + ) internal pure returns (bytes32 merkleRoot) { + // This function rebuilds the root hash by traversing the tree up from the leaves. The root is rebuilt by + // consuming and producing values on a queue. The queue starts with the `leaves` array, then goes onto the + // `hashes` array. At the end of the process, the last hash in the `hashes` array should contain the root of + // the Merkle tree. + uint256 leavesLen = leaves.length; + uint256 proofLen = proof.length; + uint256 totalHashes = proofFlags.length; + + // Check proof validity. + if (leavesLen + proofLen != totalHashes + 1) { + revert MerkleProofInvalidMultiproof(); + } + + // The xxxPos values are "pointers" to the next value to consume in each array. All accesses are done using + // `xxx[xxxPos++]`, which return the current value and increment the pointer, thus mimicking a queue's "pop". + bytes32[] memory hashes = new bytes32[](totalHashes); + uint256 leafPos = 0; + uint256 hashPos = 0; + uint256 proofPos = 0; + // At each step, we compute the next hash using two values: + // - a value from the "main queue". If not all leaves have been consumed, we get the next leaf, otherwise we + // get the next hash. + // - depending on the flag, either another value from the "main queue" (merging branches) or an element from the + // `proof` array. + for (uint256 i = 0; i < totalHashes; i++) { + bytes32 a = leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++]; + bytes32 b = proofFlags[i] + ? (leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++]) + : proof[proofPos++]; + hashes[i] = _hashPair(a, b); + } + + if (totalHashes > 0) { + if (proofPos != proofLen) { + revert MerkleProofInvalidMultiproof(); + } + unchecked { + return hashes[totalHashes - 1]; + } + } else if (leavesLen > 0) { + return leaves[0]; + } else { + return proof[0]; + } + } + + /** + * @dev Calldata version of {processMultiProof}. + * + * CAUTION: Not all Merkle trees admit multiproofs. See {processMultiProof} for details. + */ + function processMultiProofCalldata( + bytes32[] calldata proof, + bool[] calldata proofFlags, + bytes32[] memory leaves + ) internal pure returns (bytes32 merkleRoot) { + // This function rebuilds the root hash by traversing the tree up from the leaves. The root is rebuilt by + // consuming and producing values on a queue. The queue starts with the `leaves` array, then goes onto the + // `hashes` array. At the end of the process, the last hash in the `hashes` array should contain the root of + // the Merkle tree. + uint256 leavesLen = leaves.length; + uint256 proofLen = proof.length; + uint256 totalHashes = proofFlags.length; + + // Check proof validity. + if (leavesLen + proofLen != totalHashes + 1) { + revert MerkleProofInvalidMultiproof(); + } + + // The xxxPos values are "pointers" to the next value to consume in each array. All accesses are done using + // `xxx[xxxPos++]`, which return the current value and increment the pointer, thus mimicking a queue's "pop". + bytes32[] memory hashes = new bytes32[](totalHashes); + uint256 leafPos = 0; + uint256 hashPos = 0; + uint256 proofPos = 0; + // At each step, we compute the next hash using two values: + // - a value from the "main queue". If not all leaves have been consumed, we get the next leaf, otherwise we + // get the next hash. + // - depending on the flag, either another value from the "main queue" (merging branches) or an element from the + // `proof` array. + for (uint256 i = 0; i < totalHashes; i++) { + bytes32 a = leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++]; + bytes32 b = proofFlags[i] + ? (leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++]) + : proof[proofPos++]; + hashes[i] = _hashPair(a, b); + } + + if (totalHashes > 0) { + if (proofPos != proofLen) { + revert MerkleProofInvalidMultiproof(); + } + unchecked { + return hashes[totalHashes - 1]; + } + } else if (leavesLen > 0) { + return leaves[0]; + } else { + return proof[0]; + } + } + + /** + * @dev Sorts the pair (a, b) and hashes the result. + */ + function _hashPair(bytes32 a, bytes32 b) private pure returns (bytes32) { + return a < b ? _efficientHash(a, b) : _efficientHash(b, a); + } + + /** + * @dev Implementation of keccak256(abi.encode(a, b)) that doesn't allocate or expand memory. + */ + function _efficientHash(bytes32 a, bytes32 b) private pure returns (bytes32 value) { + /// @solidity memory-safe-assembly + assembly { + mstore(0x00, a) + mstore(0x20, b) + value := keccak256(0x00, 0x40) + } + } +} diff --git a/biconomy/mee/0.0.4/node_modules/@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol b/biconomy/mee/0.0.4/node_modules/@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol new file mode 100644 index 0000000..8836693 --- /dev/null +++ b/biconomy/mee/0.0.4/node_modules/@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol @@ -0,0 +1,86 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (utils/cryptography/MessageHashUtils.sol) + +pragma solidity ^0.8.20; + +import {Strings} from "../Strings.sol"; + +/** + * @dev Signature message hash utilities for producing digests to be consumed by {ECDSA} recovery or signing. + * + * The library provides methods for generating a hash of a message that conforms to the + * https://eips.ethereum.org/EIPS/eip-191[EIP 191] and https://eips.ethereum.org/EIPS/eip-712[EIP 712] + * specifications. + */ +library MessageHashUtils { + /** + * @dev Returns the keccak256 digest of an EIP-191 signed data with version + * `0x45` (`personal_sign` messages). + * + * The digest is calculated by prefixing a bytes32 `messageHash` with + * `"\x19Ethereum Signed Message:\n32"` and hashing the result. It corresponds with the + * hash signed when using the https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`] JSON-RPC method. + * + * NOTE: The `messageHash` parameter is intended to be the result of hashing a raw message with + * keccak256, although any bytes32 value can be safely used because the final digest will + * be re-hashed. + * + * See {ECDSA-recover}. + */ + function toEthSignedMessageHash(bytes32 messageHash) internal pure returns (bytes32 digest) { + /// @solidity memory-safe-assembly + assembly { + mstore(0x00, "\x19Ethereum Signed Message:\n32") // 32 is the bytes-length of messageHash + mstore(0x1c, messageHash) // 0x1c (28) is the length of the prefix + digest := keccak256(0x00, 0x3c) // 0x3c is the length of the prefix (0x1c) + messageHash (0x20) + } + } + + /** + * @dev Returns the keccak256 digest of an EIP-191 signed data with version + * `0x45` (`personal_sign` messages). + * + * The digest is calculated by prefixing an arbitrary `message` with + * `"\x19Ethereum Signed Message:\n" + len(message)` and hashing the result. It corresponds with the + * hash signed when using the https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`] JSON-RPC method. + * + * See {ECDSA-recover}. + */ + function toEthSignedMessageHash(bytes memory message) internal pure returns (bytes32) { + return + keccak256(bytes.concat("\x19Ethereum Signed Message:\n", bytes(Strings.toString(message.length)), message)); + } + + /** + * @dev Returns the keccak256 digest of an EIP-191 signed data with version + * `0x00` (data with intended validator). + * + * The digest is calculated by prefixing an arbitrary `data` with `"\x19\x00"` and the intended + * `validator` address. Then hashing the result. + * + * See {ECDSA-recover}. + */ + function toDataWithIntendedValidatorHash(address validator, bytes memory data) internal pure returns (bytes32) { + return keccak256(abi.encodePacked(hex"19_00", validator, data)); + } + + /** + * @dev Returns the keccak256 digest of an EIP-712 typed data (EIP-191 version `0x01`). + * + * The digest is calculated from a `domainSeparator` and a `structHash`, by prefixing them with + * `\x19\x01` and hashing the result. It corresponds to the hash signed by the + * https://eips.ethereum.org/EIPS/eip-712[`eth_signTypedData`] JSON-RPC method as part of EIP-712. + * + * See {ECDSA-recover}. + */ + function toTypedDataHash(bytes32 domainSeparator, bytes32 structHash) internal pure returns (bytes32 digest) { + /// @solidity memory-safe-assembly + assembly { + let ptr := mload(0x40) + mstore(ptr, hex"19_01") + mstore(add(ptr, 0x02), domainSeparator) + mstore(add(ptr, 0x22), structHash) + digest := keccak256(ptr, 0x42) + } + } +} diff --git a/biconomy/mee/0.0.4/node_modules/@openzeppelin/contracts/utils/math/Math.sol b/biconomy/mee/0.0.4/node_modules/@openzeppelin/contracts/utils/math/Math.sol new file mode 100644 index 0000000..9681524 --- /dev/null +++ b/biconomy/mee/0.0.4/node_modules/@openzeppelin/contracts/utils/math/Math.sol @@ -0,0 +1,415 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (utils/math/Math.sol) + +pragma solidity ^0.8.20; + +/** + * @dev Standard math utilities missing in the Solidity language. + */ +library Math { + /** + * @dev Muldiv operation overflow. + */ + error MathOverflowedMulDiv(); + + enum Rounding { + Floor, // Toward negative infinity + Ceil, // Toward positive infinity + Trunc, // Toward zero + Expand // Away from zero + } + + /** + * @dev Returns the addition of two unsigned integers, with an overflow flag. + */ + function tryAdd(uint256 a, uint256 b) internal pure returns (bool, uint256) { + unchecked { + uint256 c = a + b; + if (c < a) return (false, 0); + return (true, c); + } + } + + /** + * @dev Returns the subtraction of two unsigned integers, with an overflow flag. + */ + function trySub(uint256 a, uint256 b) internal pure returns (bool, uint256) { + unchecked { + if (b > a) return (false, 0); + return (true, a - b); + } + } + + /** + * @dev Returns the multiplication of two unsigned integers, with an overflow flag. + */ + function tryMul(uint256 a, uint256 b) internal pure returns (bool, uint256) { + unchecked { + // Gas optimization: this is cheaper than requiring 'a' not being zero, but the + // benefit is lost if 'b' is also tested. + // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522 + if (a == 0) return (true, 0); + uint256 c = a * b; + if (c / a != b) return (false, 0); + return (true, c); + } + } + + /** + * @dev Returns the division of two unsigned integers, with a division by zero flag. + */ + function tryDiv(uint256 a, uint256 b) internal pure returns (bool, uint256) { + unchecked { + if (b == 0) return (false, 0); + return (true, a / b); + } + } + + /** + * @dev Returns the remainder of dividing two unsigned integers, with a division by zero flag. + */ + function tryMod(uint256 a, uint256 b) internal pure returns (bool, uint256) { + unchecked { + if (b == 0) return (false, 0); + return (true, a % b); + } + } + + /** + * @dev Returns the largest of two numbers. + */ + function max(uint256 a, uint256 b) internal pure returns (uint256) { + return a > b ? a : b; + } + + /** + * @dev Returns the smallest of two numbers. + */ + function min(uint256 a, uint256 b) internal pure returns (uint256) { + return a < b ? a : b; + } + + /** + * @dev Returns the average of two numbers. The result is rounded towards + * zero. + */ + function average(uint256 a, uint256 b) internal pure returns (uint256) { + // (a + b) / 2 can overflow. + return (a & b) + (a ^ b) / 2; + } + + /** + * @dev Returns the ceiling of the division of two numbers. + * + * This differs from standard division with `/` in that it rounds towards infinity instead + * of rounding towards zero. + */ + function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) { + if (b == 0) { + // Guarantee the same behavior as in a regular Solidity division. + return a / b; + } + + // (a + b - 1) / b can overflow on addition, so we distribute. + return a == 0 ? 0 : (a - 1) / b + 1; + } + + /** + * @notice Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or + * denominator == 0. + * @dev Original credit to Remco Bloemen under MIT license (https://xn--2-umb.com/21/muldiv) with further edits by + * Uniswap Labs also under MIT license. + */ + function mulDiv(uint256 x, uint256 y, uint256 denominator) internal pure returns (uint256 result) { + unchecked { + // 512-bit multiply [prod1 prod0] = x * y. Compute the product mod 2^256 and mod 2^256 - 1, then use + // use the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256 + // variables such that product = prod1 * 2^256 + prod0. + uint256 prod0 = x * y; // Least significant 256 bits of the product + uint256 prod1; // Most significant 256 bits of the product + assembly { + let mm := mulmod(x, y, not(0)) + prod1 := sub(sub(mm, prod0), lt(mm, prod0)) + } + + // Handle non-overflow cases, 256 by 256 division. + if (prod1 == 0) { + // Solidity will revert if denominator == 0, unlike the div opcode on its own. + // The surrounding unchecked block does not change this fact. + // See https://docs.soliditylang.org/en/latest/control-structures.html#checked-or-unchecked-arithmetic. + return prod0 / denominator; + } + + // Make sure the result is less than 2^256. Also prevents denominator == 0. + if (denominator <= prod1) { + revert MathOverflowedMulDiv(); + } + + /////////////////////////////////////////////// + // 512 by 256 division. + /////////////////////////////////////////////// + + // Make division exact by subtracting the remainder from [prod1 prod0]. + uint256 remainder; + assembly { + // Compute remainder using mulmod. + remainder := mulmod(x, y, denominator) + + // Subtract 256 bit number from 512 bit number. + prod1 := sub(prod1, gt(remainder, prod0)) + prod0 := sub(prod0, remainder) + } + + // Factor powers of two out of denominator and compute largest power of two divisor of denominator. + // Always >= 1. See https://cs.stackexchange.com/q/138556/92363. + + uint256 twos = denominator & (0 - denominator); + assembly { + // Divide denominator by twos. + denominator := div(denominator, twos) + + // Divide [prod1 prod0] by twos. + prod0 := div(prod0, twos) + + // Flip twos such that it is 2^256 / twos. If twos is zero, then it becomes one. + twos := add(div(sub(0, twos), twos), 1) + } + + // Shift in bits from prod1 into prod0. + prod0 |= prod1 * twos; + + // Invert denominator mod 2^256. Now that denominator is an odd number, it has an inverse modulo 2^256 such + // that denominator * inv = 1 mod 2^256. Compute the inverse by starting with a seed that is correct for + // four bits. That is, denominator * inv = 1 mod 2^4. + uint256 inverse = (3 * denominator) ^ 2; + + // Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also + // works in modular arithmetic, doubling the correct bits in each step. + inverse *= 2 - denominator * inverse; // inverse mod 2^8 + inverse *= 2 - denominator * inverse; // inverse mod 2^16 + inverse *= 2 - denominator * inverse; // inverse mod 2^32 + inverse *= 2 - denominator * inverse; // inverse mod 2^64 + inverse *= 2 - denominator * inverse; // inverse mod 2^128 + inverse *= 2 - denominator * inverse; // inverse mod 2^256 + + // Because the division is now exact we can divide by multiplying with the modular inverse of denominator. + // This will give us the correct result modulo 2^256. Since the preconditions guarantee that the outcome is + // less than 2^256, this is the final result. We don't need to compute the high bits of the result and prod1 + // is no longer required. + result = prod0 * inverse; + return result; + } + } + + /** + * @notice Calculates x * y / denominator with full precision, following the selected rounding direction. + */ + function mulDiv(uint256 x, uint256 y, uint256 denominator, Rounding rounding) internal pure returns (uint256) { + uint256 result = mulDiv(x, y, denominator); + if (unsignedRoundsUp(rounding) && mulmod(x, y, denominator) > 0) { + result += 1; + } + return result; + } + + /** + * @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded + * towards zero. + * + * Inspired by Henry S. Warren, Jr.'s "Hacker's Delight" (Chapter 11). + */ + function sqrt(uint256 a) internal pure returns (uint256) { + if (a == 0) { + return 0; + } + + // For our first guess, we get the biggest power of 2 which is smaller than the square root of the target. + // + // We know that the "msb" (most significant bit) of our target number `a` is a power of 2 such that we have + // `msb(a) <= a < 2*msb(a)`. This value can be written `msb(a)=2**k` with `k=log2(a)`. + // + // This can be rewritten `2**log2(a) <= a < 2**(log2(a) + 1)` + // → `sqrt(2**k) <= sqrt(a) < sqrt(2**(k+1))` + // → `2**(k/2) <= sqrt(a) < 2**((k+1)/2) <= 2**(k/2 + 1)` + // + // Consequently, `2**(log2(a) / 2)` is a good first approximation of `sqrt(a)` with at least 1 correct bit. + uint256 result = 1 << (log2(a) >> 1); + + // At this point `result` is an estimation with one bit of precision. We know the true value is a uint128, + // since it is the square root of a uint256. Newton's method converges quadratically (precision doubles at + // every iteration). We thus need at most 7 iteration to turn our partial result with one bit of precision + // into the expected uint128 result. + unchecked { + result = (result + a / result) >> 1; + result = (result + a / result) >> 1; + result = (result + a / result) >> 1; + result = (result + a / result) >> 1; + result = (result + a / result) >> 1; + result = (result + a / result) >> 1; + result = (result + a / result) >> 1; + return min(result, a / result); + } + } + + /** + * @notice Calculates sqrt(a), following the selected rounding direction. + */ + function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) { + unchecked { + uint256 result = sqrt(a); + return result + (unsignedRoundsUp(rounding) && result * result < a ? 1 : 0); + } + } + + /** + * @dev Return the log in base 2 of a positive value rounded towards zero. + * Returns 0 if given 0. + */ + function log2(uint256 value) internal pure returns (uint256) { + uint256 result = 0; + unchecked { + if (value >> 128 > 0) { + value >>= 128; + result += 128; + } + if (value >> 64 > 0) { + value >>= 64; + result += 64; + } + if (value >> 32 > 0) { + value >>= 32; + result += 32; + } + if (value >> 16 > 0) { + value >>= 16; + result += 16; + } + if (value >> 8 > 0) { + value >>= 8; + result += 8; + } + if (value >> 4 > 0) { + value >>= 4; + result += 4; + } + if (value >> 2 > 0) { + value >>= 2; + result += 2; + } + if (value >> 1 > 0) { + result += 1; + } + } + return result; + } + + /** + * @dev Return the log in base 2, following the selected rounding direction, of a positive value. + * Returns 0 if given 0. + */ + function log2(uint256 value, Rounding rounding) internal pure returns (uint256) { + unchecked { + uint256 result = log2(value); + return result + (unsignedRoundsUp(rounding) && 1 << result < value ? 1 : 0); + } + } + + /** + * @dev Return the log in base 10 of a positive value rounded towards zero. + * Returns 0 if given 0. + */ + function log10(uint256 value) internal pure returns (uint256) { + uint256 result = 0; + unchecked { + if (value >= 10 ** 64) { + value /= 10 ** 64; + result += 64; + } + if (value >= 10 ** 32) { + value /= 10 ** 32; + result += 32; + } + if (value >= 10 ** 16) { + value /= 10 ** 16; + result += 16; + } + if (value >= 10 ** 8) { + value /= 10 ** 8; + result += 8; + } + if (value >= 10 ** 4) { + value /= 10 ** 4; + result += 4; + } + if (value >= 10 ** 2) { + value /= 10 ** 2; + result += 2; + } + if (value >= 10 ** 1) { + result += 1; + } + } + return result; + } + + /** + * @dev Return the log in base 10, following the selected rounding direction, of a positive value. + * Returns 0 if given 0. + */ + function log10(uint256 value, Rounding rounding) internal pure returns (uint256) { + unchecked { + uint256 result = log10(value); + return result + (unsignedRoundsUp(rounding) && 10 ** result < value ? 1 : 0); + } + } + + /** + * @dev Return the log in base 256 of a positive value rounded towards zero. + * Returns 0 if given 0. + * + * Adding one to the result gives the number of pairs of hex symbols needed to represent `value` as a hex string. + */ + function log256(uint256 value) internal pure returns (uint256) { + uint256 result = 0; + unchecked { + if (value >> 128 > 0) { + value >>= 128; + result += 16; + } + if (value >> 64 > 0) { + value >>= 64; + result += 8; + } + if (value >> 32 > 0) { + value >>= 32; + result += 4; + } + if (value >> 16 > 0) { + value >>= 16; + result += 2; + } + if (value >> 8 > 0) { + result += 1; + } + } + return result; + } + + /** + * @dev Return the log in base 256, following the selected rounding direction, of a positive value. + * Returns 0 if given 0. + */ + function log256(uint256 value, Rounding rounding) internal pure returns (uint256) { + unchecked { + uint256 result = log256(value); + return result + (unsignedRoundsUp(rounding) && 1 << (result << 3) < value ? 1 : 0); + } + } + + /** + * @dev Returns whether a provided rounding mode is considered rounding up for unsigned integers. + */ + function unsignedRoundsUp(Rounding rounding) internal pure returns (bool) { + return uint8(rounding) % 2 == 1; + } +} diff --git a/biconomy/mee/0.0.4/node_modules/@openzeppelin/contracts/utils/math/SignedMath.sol b/biconomy/mee/0.0.4/node_modules/@openzeppelin/contracts/utils/math/SignedMath.sol new file mode 100644 index 0000000..66a6151 --- /dev/null +++ b/biconomy/mee/0.0.4/node_modules/@openzeppelin/contracts/utils/math/SignedMath.sol @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (utils/math/SignedMath.sol) + +pragma solidity ^0.8.20; + +/** + * @dev Standard signed math utilities missing in the Solidity language. + */ +library SignedMath { + /** + * @dev Returns the largest of two signed numbers. + */ + function max(int256 a, int256 b) internal pure returns (int256) { + return a > b ? a : b; + } + + /** + * @dev Returns the smallest of two signed numbers. + */ + function min(int256 a, int256 b) internal pure returns (int256) { + return a < b ? a : b; + } + + /** + * @dev Returns the average of two signed numbers without overflow. + * The result is rounded towards zero. + */ + function average(int256 a, int256 b) internal pure returns (int256) { + // Formula from the book "Hacker's Delight" + int256 x = (a & b) + ((a ^ b) >> 1); + return x + (int256(uint256(x) >> 255) & (a ^ b)); + } + + /** + * @dev Returns the absolute unsigned value of a signed value. + */ + function abs(int256 n) internal pure returns (uint256) { + unchecked { + // must be unchecked in order to support `n = type(int256).min` + return uint256(n >= 0 ? n : -n); + } + } +} diff --git a/biconomy/mee/0.0.4/node_modules/Solidity-RLP/contracts/RLPReader.sol b/biconomy/mee/0.0.4/node_modules/Solidity-RLP/contracts/RLPReader.sol new file mode 100644 index 0000000..77d6551 --- /dev/null +++ b/biconomy/mee/0.0.4/node_modules/Solidity-RLP/contracts/RLPReader.sol @@ -0,0 +1,356 @@ +// SPDX-License-Identifier: Apache-2.0 + +/* + * @author Hamdi Allam hamdi.allam97@gmail.com + * Please reach out with any questions or concerns + */ +pragma solidity >=0.5.10 <0.9.0; + +library RLPReader { + uint8 constant STRING_SHORT_START = 0x80; + uint8 constant STRING_LONG_START = 0xb8; + uint8 constant LIST_SHORT_START = 0xc0; + uint8 constant LIST_LONG_START = 0xf8; + uint8 constant WORD_SIZE = 32; + + struct RLPItem { + uint256 len; + uint256 memPtr; + } + + struct Iterator { + RLPItem item; // Item that's being iterated over. + uint256 nextPtr; // Position of the next item in the list. + } + + /* + * @dev Returns the next element in the iteration. Reverts if it has not next element. + * @param self The iterator. + * @return The next element in the iteration. + */ + function next(Iterator memory self) internal pure returns (RLPItem memory) { + require(hasNext(self)); + + uint256 ptr = self.nextPtr; + uint256 itemLength = _itemLength(ptr); + self.nextPtr = ptr + itemLength; + + return RLPItem(itemLength, ptr); + } + + /* + * @dev Returns true if the iteration has more elements. + * @param self The iterator. + * @return true if the iteration has more elements. + */ + function hasNext(Iterator memory self) internal pure returns (bool) { + RLPItem memory item = self.item; + return self.nextPtr < item.memPtr + item.len; + } + + /* + * @param item RLP encoded bytes + */ + function toRlpItem(bytes memory item) internal pure returns (RLPItem memory) { + uint256 memPtr; + assembly { + memPtr := add(item, 0x20) + } + + return RLPItem(item.length, memPtr); + } + + /* + * @dev Create an iterator. Reverts if item is not a list. + * @param self The RLP item. + * @return An 'Iterator' over the item. + */ + function iterator(RLPItem memory self) internal pure returns (Iterator memory) { + require(isList(self)); + + uint256 ptr = self.memPtr + _payloadOffset(self.memPtr); + return Iterator(self, ptr); + } + + /* + * @param the RLP item. + */ + function rlpLen(RLPItem memory item) internal pure returns (uint256) { + return item.len; + } + + /* + * @param the RLP item. + * @return (memPtr, len) pair: location of the item's payload in memory. + */ + function payloadLocation(RLPItem memory item) internal pure returns (uint256, uint256) { + uint256 offset = _payloadOffset(item.memPtr); + uint256 memPtr = item.memPtr + offset; + uint256 len = item.len - offset; // data length + return (memPtr, len); + } + + /* + * @param the RLP item. + */ + function payloadLen(RLPItem memory item) internal pure returns (uint256) { + (, uint256 len) = payloadLocation(item); + return len; + } + + /* + * @param the RLP item containing the encoded list. + */ + function toList(RLPItem memory item) internal pure returns (RLPItem[] memory) { + require(isList(item)); + + uint256 items = numItems(item); + RLPItem[] memory result = new RLPItem[](items); + + uint256 memPtr = item.memPtr + _payloadOffset(item.memPtr); + uint256 dataLen; + for (uint256 i = 0; i < items; i++) { + dataLen = _itemLength(memPtr); + result[i] = RLPItem(dataLen, memPtr); + memPtr = memPtr + dataLen; + } + + require(memPtr - item.memPtr == item.len); + + return result; + } + + // @return indicator whether encoded payload is a list. negate this function call for isData. + function isList(RLPItem memory item) internal pure returns (bool) { + if (item.len == 0) return false; + + uint8 byte0; + uint256 memPtr = item.memPtr; + assembly { + byte0 := byte(0, mload(memPtr)) + } + + if (byte0 < LIST_SHORT_START) return false; + return true; + } + + /* + * @dev A cheaper version of keccak256(toRlpBytes(item)) that avoids copying memory. + * @return keccak256 hash of RLP encoded bytes. + */ + function rlpBytesKeccak256(RLPItem memory item) internal pure returns (bytes32) { + uint256 ptr = item.memPtr; + uint256 len = item.len; + bytes32 result; + assembly { + result := keccak256(ptr, len) + } + return result; + } + + /* + * @dev A cheaper version of keccak256(toBytes(item)) that avoids copying memory. + * @return keccak256 hash of the item payload. + */ + function payloadKeccak256(RLPItem memory item) internal pure returns (bytes32) { + (uint256 memPtr, uint256 len) = payloadLocation(item); + bytes32 result; + assembly { + result := keccak256(memPtr, len) + } + return result; + } + + /** RLPItem conversions into data types **/ + + // @returns raw rlp encoding in bytes + function toRlpBytes(RLPItem memory item) internal pure returns (bytes memory) { + bytes memory result = new bytes(item.len); + if (result.length == 0) return result; + + uint256 ptr; + assembly { + ptr := add(0x20, result) + } + + copy(item.memPtr, ptr, item.len); + return result; + } + + // any non-zero byte except "0x80" is considered true + function toBoolean(RLPItem memory item) internal pure returns (bool) { + require(item.len == 1); + uint256 result; + uint256 memPtr = item.memPtr; + assembly { + result := byte(0, mload(memPtr)) + } + + // SEE Github Issue #5. + // Summary: Most commonly used RLP libraries (i.e Geth) will encode + // "0" as "0x80" instead of as "0". We handle this edge case explicitly + // here. + if (result == 0 || result == STRING_SHORT_START) { + return false; + } else { + return true; + } + } + + function toAddress(RLPItem memory item) internal pure returns (address) { + // 1 byte for the length prefix + require(item.len == 21); + + return address(uint160(toUint(item))); + } + + function toUint(RLPItem memory item) internal pure returns (uint256) { + require(item.len > 0 && item.len <= 33); + + (uint256 memPtr, uint256 len) = payloadLocation(item); + + uint256 result; + assembly { + result := mload(memPtr) + + // shift to the correct location if neccesary + if lt(len, 32) { + result := div(result, exp(256, sub(32, len))) + } + } + + return result; + } + + // enforces 32 byte length + function toUintStrict(RLPItem memory item) internal pure returns (uint256) { + // one byte prefix + require(item.len == 33); + + uint256 result; + uint256 memPtr = item.memPtr + 1; + assembly { + result := mload(memPtr) + } + + return result; + } + + function toBytes(RLPItem memory item) internal pure returns (bytes memory) { + require(item.len > 0); + + (uint256 memPtr, uint256 len) = payloadLocation(item); + bytes memory result = new bytes(len); + + uint256 destPtr; + assembly { + destPtr := add(0x20, result) + } + + copy(memPtr, destPtr, len); + return result; + } + + /* + * Private Helpers + */ + + // @return number of payload items inside an encoded list. + function numItems(RLPItem memory item) private pure returns (uint256) { + if (item.len == 0) return 0; + + uint256 count = 0; + uint256 currPtr = item.memPtr + _payloadOffset(item.memPtr); + uint256 endPtr = item.memPtr + item.len; + while (currPtr < endPtr) { + currPtr = currPtr + _itemLength(currPtr); // skip over an item + count++; + } + + return count; + } + + // @return entire rlp item byte length + function _itemLength(uint256 memPtr) private pure returns (uint256) { + uint256 itemLen; + uint256 byte0; + assembly { + byte0 := byte(0, mload(memPtr)) + } + + if (byte0 < STRING_SHORT_START) { + itemLen = 1; + } else if (byte0 < STRING_LONG_START) { + itemLen = byte0 - STRING_SHORT_START + 1; + } else if (byte0 < LIST_SHORT_START) { + assembly { + let byteLen := sub(byte0, 0xb7) // # of bytes the actual length is + memPtr := add(memPtr, 1) // skip over the first byte + + /* 32 byte word size */ + let dataLen := div(mload(memPtr), exp(256, sub(32, byteLen))) // right shifting to get the len + itemLen := add(dataLen, add(byteLen, 1)) + } + } else if (byte0 < LIST_LONG_START) { + itemLen = byte0 - LIST_SHORT_START + 1; + } else { + assembly { + let byteLen := sub(byte0, 0xf7) + memPtr := add(memPtr, 1) + + let dataLen := div(mload(memPtr), exp(256, sub(32, byteLen))) // right shifting to the correct length + itemLen := add(dataLen, add(byteLen, 1)) + } + } + + return itemLen; + } + + // @return number of bytes until the data + function _payloadOffset(uint256 memPtr) private pure returns (uint256) { + uint256 byte0; + assembly { + byte0 := byte(0, mload(memPtr)) + } + + if (byte0 < STRING_SHORT_START) { + return 0; + } else if (byte0 < STRING_LONG_START || (byte0 >= LIST_SHORT_START && byte0 < LIST_LONG_START)) { + return 1; + } else if (byte0 < LIST_SHORT_START) { + // being explicit + return byte0 - (STRING_LONG_START - 1) + 1; + } else { + return byte0 - (LIST_LONG_START - 1) + 1; + } + } + + /* + * @param src Pointer to source + * @param dest Pointer to destination + * @param len Amount of memory to copy from the source + */ + function copy(uint256 src, uint256 dest, uint256 len) private pure { + if (len == 0) return; + + // copy as many word sizes as possible + for (; len >= WORD_SIZE; len -= WORD_SIZE) { + assembly { + mstore(dest, mload(src)) + } + + src += WORD_SIZE; + dest += WORD_SIZE; + } + + if (len > 0) { + // left over bytes. Mask is used to remove unwanted bytes from the word + uint256 mask = 256**(WORD_SIZE - len) - 1; + assembly { + let srcpart := and(mload(src), not(mask)) // zero out src + let destpart := and(mload(dest), mask) // retrieve the bytes + mstore(dest, or(destpart, srcpart)) + } + } + } +} diff --git a/biconomy/mee/0.0.4/node_modules/account-abstraction/contracts/core/Helpers.sol b/biconomy/mee/0.0.4/node_modules/account-abstraction/contracts/core/Helpers.sol new file mode 100644 index 0000000..8579008 --- /dev/null +++ b/biconomy/mee/0.0.4/node_modules/account-abstraction/contracts/core/Helpers.sol @@ -0,0 +1,106 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity ^0.8.23; + +/* solhint-disable no-inline-assembly */ + + + /* + * For simulation purposes, validateUserOp (and validatePaymasterUserOp) + * must return this value in case of signature failure, instead of revert. + */ +uint256 constant SIG_VALIDATION_FAILED = 1; + + +/* + * For simulation purposes, validateUserOp (and validatePaymasterUserOp) + * return this value on success. + */ +uint256 constant SIG_VALIDATION_SUCCESS = 0; + + +/** + * Returned data from validateUserOp. + * validateUserOp returns a uint256, which is created by `_packedValidationData` and + * parsed by `_parseValidationData`. + * @param aggregator - address(0) - The account validated the signature by itself. + * address(1) - The account failed to validate the signature. + * otherwise - This is an address of a signature aggregator that must + * be used to validate the signature. + * @param validAfter - This UserOp is valid only after this timestamp. + * @param validaUntil - This UserOp is valid only up to this timestamp. + */ +struct ValidationData { + address aggregator; + uint48 validAfter; + uint48 validUntil; +} + +/** + * Extract sigFailed, validAfter, validUntil. + * Also convert zero validUntil to type(uint48).max. + * @param validationData - The packed validation data. + */ +function _parseValidationData( + uint256 validationData +) pure returns (ValidationData memory data) { + address aggregator = address(uint160(validationData)); + uint48 validUntil = uint48(validationData >> 160); + if (validUntil == 0) { + validUntil = type(uint48).max; + } + uint48 validAfter = uint48(validationData >> (48 + 160)); + return ValidationData(aggregator, validAfter, validUntil); +} + +/** + * Helper to pack the return value for validateUserOp. + * @param data - The ValidationData to pack. + */ +function _packValidationData( + ValidationData memory data +) pure returns (uint256) { + return + uint160(data.aggregator) | + (uint256(data.validUntil) << 160) | + (uint256(data.validAfter) << (160 + 48)); +} + +/** + * Helper to pack the return value for validateUserOp, when not using an aggregator. + * @param sigFailed - True for signature failure, false for success. + * @param validUntil - Last timestamp this UserOperation is valid (or zero for infinite). + * @param validAfter - First timestamp this UserOperation is valid. + */ +function _packValidationData( + bool sigFailed, + uint48 validUntil, + uint48 validAfter +) pure returns (uint256) { + return + (sigFailed ? 1 : 0) | + (uint256(validUntil) << 160) | + (uint256(validAfter) << (160 + 48)); +} + +/** + * keccak function over calldata. + * @dev copy calldata into memory, do keccak and drop allocated memory. Strangely, this is more efficient than letting solidity do it. + */ + function calldataKeccak(bytes calldata data) pure returns (bytes32 ret) { + assembly ("memory-safe") { + let mem := mload(0x40) + let len := data.length + calldatacopy(mem, data.offset, len) + ret := keccak256(mem, len) + } + } + + +/** + * The minimum of two numbers. + * @param a - First number. + * @param b - Second number. + */ + function min(uint256 a, uint256 b) pure returns (uint256) { + return a < b ? a : b; + } diff --git a/biconomy/mee/0.0.4/node_modules/account-abstraction/contracts/interfaces/PackedUserOperation.sol b/biconomy/mee/0.0.4/node_modules/account-abstraction/contracts/interfaces/PackedUserOperation.sol new file mode 100644 index 0000000..fe20de5 --- /dev/null +++ b/biconomy/mee/0.0.4/node_modules/account-abstraction/contracts/interfaces/PackedUserOperation.sol @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity >=0.7.5; + +/** + * User Operation struct + * @param sender - The sender account of this request. + * @param nonce - Unique value the sender uses to verify it is not a replay. + * @param initCode - If set, the account contract will be created by this constructor/ + * @param callData - The method call to execute on this account. + * @param accountGasLimits - Packed gas limits for validateUserOp and gas limit passed to the callData method call. + * @param preVerificationGas - Gas not calculated by the handleOps method, but added to the gas paid. + * Covers batch overhead. + * @param gasFees - packed gas fields maxPriorityFeePerGas and maxFeePerGas - Same as EIP-1559 gas parameters. + * @param paymasterAndData - If set, this field holds the paymaster address, verification gas limit, postOp gas limit and paymaster-specific extra data + * The paymaster will pay for the transaction instead of the sender. + * @param signature - Sender-verified signature over the entire request, the EntryPoint address and the chain ID. + */ +struct PackedUserOperation { + address sender; + uint256 nonce; + bytes initCode; + bytes callData; + bytes32 accountGasLimits; + uint256 preVerificationGas; + bytes32 gasFees; + bytes paymasterAndData; + bytes signature; +} diff --git a/biconomy/mee/0.0.4/node_modules/enumerablemap4337/src/AssociatedArrayLib.sol b/biconomy/mee/0.0.4/node_modules/enumerablemap4337/src/AssociatedArrayLib.sol new file mode 100644 index 0000000..be593a1 --- /dev/null +++ b/biconomy/mee/0.0.4/node_modules/enumerablemap4337/src/AssociatedArrayLib.sol @@ -0,0 +1,280 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +/** + * Dynamic arrays associated with an account address as per ERC-7562/ERC-4337 + * @author filio.eth (Biconomy), zeroknots.eth (rhinestone) + */ +library AssociatedArrayLib { + using AssociatedArrayLib for *; + + error AssociatedArray_OutOfBounds(uint256 index); + + struct Array { + uint256 _spacer; + } + + function _slot(Array storage s, address account) private pure returns (bytes32 __slot) { + assembly { + mstore(0x00, account) + mstore(0x20, s.slot) + __slot := keccak256(0x00, 0x40) + } + } + + function _length(Array storage s, address account) private view returns (uint256 __length) { + bytes32 slot = _slot(s, account); + assembly { + __length := sload(slot) + } + } + + function _get(Array storage s, address account, uint256 index) private view returns (bytes32 value) { + return _get(_slot(s, account), index); + } + + function _get(bytes32 slot, uint256 index) private view returns (bytes32 value) { + assembly { + //if (index >= _length(s, account)) revert AssociatedArray_OutOfBounds(index); + if iszero(lt(index, sload(slot))) { + mstore(0, 0x8277484f) // `AssociatedArray_OutOfBounds(uint256)` + mstore(0x20, index) + revert(0x1c, 0x24) + } + value := sload(add(slot, mul(0x20, add(index, 1)))) + } + } + + function _getAll(Array storage s, address account) private view returns (bytes32[] memory values) { + bytes32 slot = _slot(s, account); + uint256 __length; + assembly { + __length := sload(slot) + } + values = new bytes32[](__length); + for (uint256 i; i < __length; i++) { + values[i] = _get(slot, i); + } + } + + // inefficient. complexity = O(n) + // use with caution + // in case of large arrays, consider using EnumerableSet4337 instead + function _contains(Array storage s, address account, bytes32 value) private view returns (bool) { + bytes32 slot = _slot(s, account); + uint256 __length; + assembly { + __length := sload(slot) + } + for (uint256 i; i < __length; i++) { + if (_get(slot, i) == value) { + return true; + } + } + return false; + } + + function _set(Array storage s, address account, uint256 index, bytes32 value) private { + _set(_slot(s, account), index, value); + } + + function _set(bytes32 slot, uint256 index, bytes32 value) private { + assembly { + //if (index >= _length(s, account)) revert AssociatedArray_OutOfBounds(index); + if iszero(lt(index, sload(slot))) { + mstore(0, 0x8277484f) // `AssociatedArray_OutOfBounds(uint256)` + mstore(0x20, index) + revert(0x1c, 0x24) + } + sstore(add(slot, mul(0x20, add(index, 1))), value) + } + } + + function _push(Array storage s, address account, bytes32 value) private { + bytes32 slot = _slot(s, account); + assembly { + // load length (stored @ slot), add 1 to it => index. + // mul index by 0x20 and add it to orig slot to get the next free slot + let index := add(sload(slot), 1) + sstore(add(slot, mul(0x20, index)), value) + sstore(slot, index) //increment length by 1 + } + } + + function _pop(Array storage s, address account) private { + bytes32 slot = _slot(s, account); + uint256 __length; + assembly { + __length := sload(slot) + } + if (__length == 0) return; + _set(slot, __length - 1, 0); + assembly { + sstore(slot, sub(__length, 1)) + } + } + + function _remove(Array storage s, address account, uint256 index) private { + bytes32 slot = _slot(s, account); + uint256 __length; + assembly { + __length := sload(slot) + if iszero(lt(index, __length)) { + mstore(0, 0x8277484f) // `AssociatedArray_OutOfBounds(uint256)` + mstore(0x20, index) + revert(0x1c, 0x24) + } + } + _set(slot, index, _get(s, account, __length - 1)); + + assembly { + // clear the last slot + // this is the 'unchecked' version of _set(slot, __length - 1, 0) + // as we use length-1 as index, so the check is excessive. + // also removes extra -1 and +1 operations + sstore(add(slot, mul(0x20, __length)), 0) + // store new length + sstore(slot, sub(__length, 1)) + } + } + + struct Bytes32Array { + Array _inner; + } + + function length(Bytes32Array storage s, address account) internal view returns (uint256) { + return _length(s._inner, account); + } + + function get(Bytes32Array storage s, address account, uint256 index) internal view returns (bytes32) { + return _get(s._inner, account, index); + } + + function getAll(Bytes32Array storage s, address account) internal view returns (bytes32[] memory) { + return _getAll(s._inner, account); + } + + function contains(Bytes32Array storage s, address account, bytes32 value) internal view returns (bool) { + return _contains(s._inner, account, value); + } + + function add(Bytes32Array storage s, address account, bytes32 value) internal { + if (!_contains(s._inner, account, value)) { + _push(s._inner, account, value); + } + } + + function set(Bytes32Array storage s, address account, uint256 index, bytes32 value) internal { + _set(s._inner, account, index, value); + } + + function push(Bytes32Array storage s, address account, bytes32 value) internal { + _push(s._inner, account, value); + } + + function pop(Bytes32Array storage s, address account) internal { + _pop(s._inner, account); + } + + function remove(Bytes32Array storage s, address account, uint256 index) internal { + _remove(s._inner, account, index); + } + + struct AddressArray { + Array _inner; + } + + function length(AddressArray storage s, address account) internal view returns (uint256) { + return _length(s._inner, account); + } + + function get(AddressArray storage s, address account, uint256 index) internal view returns (address) { + return address(uint160(uint256(_get(s._inner, account, index)))); + } + + function getAll(AddressArray storage s, address account) internal view returns (address[] memory) { + bytes32[] memory bytes32Array = _getAll(s._inner, account); + address[] memory addressArray; + + /// @solidity memory-safe-assembly + assembly { + addressArray := bytes32Array + } + return addressArray; + } + + function contains(AddressArray storage s, address account, address value) internal view returns (bool) { + return _contains(s._inner, account, bytes32(uint256(uint160(value)))); + } + + function add(AddressArray storage s, address account, address value) internal { + if (!_contains(s._inner, account, bytes32(uint256(uint160(value))))) { + _push(s._inner, account, bytes32(uint256(uint160(value)))); + } + } + + function set(AddressArray storage s, address account, uint256 index, address value) internal { + _set(s._inner, account, index, bytes32(uint256(uint160(value)))); + } + + function push(AddressArray storage s, address account, address value) internal { + _push(s._inner, account, bytes32(uint256(uint160(value)))); + } + + function pop(AddressArray storage s, address account) internal { + _pop(s._inner, account); + } + + function remove(AddressArray storage s, address account, uint256 index) internal { + _remove(s._inner, account, index); + } + + struct UintArray { + Array _inner; + } + + function length(UintArray storage s, address account) internal view returns (uint256) { + return _length(s._inner, account); + } + + function get(UintArray storage s, address account, uint256 index) internal view returns (uint256) { + return uint256(_get(s._inner, account, index)); + } + + function getAll(UintArray storage s, address account) internal view returns (uint256[] memory) { + bytes32[] memory bytes32Array = _getAll(s._inner, account); + uint256[] memory uintArray; + + /// @solidity memory-safe-assembly + assembly { + uintArray := bytes32Array + } + return uintArray; + } + + function contains(UintArray storage s, address account, uint256 value) internal view returns (bool) { + return _contains(s._inner, account, bytes32(value)); + } + + function add(UintArray storage s, address account, uint256 value) internal { + if (!_contains(s._inner, account, bytes32(value))) { + _push(s._inner, account, bytes32(value)); + } + } + + function set(UintArray storage s, address account, uint256 index, uint256 value) internal { + _set(s._inner, account, index, bytes32(value)); + } + + function push(UintArray storage s, address account, uint256 value) internal { + _push(s._inner, account, bytes32(value)); + } + + function pop(UintArray storage s, address account) internal { + _pop(s._inner, account); + } + + function remove(UintArray storage s, address account, uint256 index) internal { + _remove(s._inner, account, index); + } +} diff --git a/biconomy/mee/0.0.4/node_modules/enumerablemap4337/src/EnumerableSet4337.sol b/biconomy/mee/0.0.4/node_modules/enumerablemap4337/src/EnumerableSet4337.sol new file mode 100644 index 0000000..ad62b37 --- /dev/null +++ b/biconomy/mee/0.0.4/node_modules/enumerablemap4337/src/EnumerableSet4337.sol @@ -0,0 +1,371 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import "./AssociatedArrayLib.sol"; + +/** + * Fork of OZ's EnumerableSet that makes all storage access ERC-4337 compliant via associated storage + * @author zeroknots.eth (rhinestone) + */ +library EnumerableSet { + using AssociatedArrayLib for AssociatedArrayLib.Bytes32Array; + // To implement this library for multiple types with as little code + // repetition as possible, we write it in terms of a generic Set type with + // bytes32 values. + // The Set implementation uses private functions, and user-facing + // implementations (such as AddressSet) are just wrappers around the + // underlying Set. + // This means that we can only create new EnumerableSets for types that fit + // in bytes32. + + struct Set { + // Storage of set values + AssociatedArrayLib.Bytes32Array _values; + // Position is the index of the value in the `values` array plus 1. + // Position 0 is used to mean a value is not in the set. + mapping(bytes32 value => mapping(address account => uint256)) _positions; + } + + /** + * @dev Add a value to a set. O(1). + * + * Returns true if the value was added to the set, that is if it was not + * already present. + */ + function _add(Set storage set, address account, bytes32 value) private returns (bool) { + if (!_contains(set, account, value)) { + set._values.push(account, value); + // The value is stored at length-1, but we add 1 to all indexes + // and use 0 as a sentinel value + set._positions[value][account] = set._values.length(account); + return true; + } else { + return false; + } + } + + /** + * @dev Removes a value from a set. O(1). + * + * Returns true if the value was removed from the set, that is if it was + * present. + */ + function _remove(Set storage set, address account, bytes32 value) private returns (bool) { + // We cache the value's position to prevent multiple reads from the same storage slot + uint256 position = set._positions[value][account]; + + if (position != 0) { + // Equivalent to contains(set, value) + // To delete an element from the _values array in O(1), we swap the element to delete with the last one in + // the array, and then remove the last element (sometimes called as 'swap and pop'). + // This modifies the order of the array, as noted in {at}. + + uint256 valueIndex = position - 1; + uint256 lastIndex = set._values.length(account) - 1; + + if (valueIndex != lastIndex) { + bytes32 lastValue = set._values.get(account, lastIndex); + + // Move the lastValue to the index where the value to delete is + set._values.set(account, valueIndex, lastValue); + // Update the tracked position of the lastValue (that was just moved) + set._positions[lastValue][account] = position; + } + + // Delete the slot where the moved value was stored + set._values.pop(account); + + // Delete the tracked position for the deleted slot + delete set._positions[value][account]; + + return true; + } else { + return false; + } + } + + function _removeAll(Set storage set, address account) internal { + // get length of the array + uint256 len = _length(set, account); + for (uint256 i = 1; i <= len; i++) { + // get last value + bytes32 value = _at(set, account, len - i); + _remove(set, account, value); + } + } + + /** + * @dev Returns true if the value is in the set. O(1). + */ + function _contains(Set storage set, address account, bytes32 value) private view returns (bool) { + return set._positions[value][account] != 0; + } + + /** + * @dev Returns the number of values on the set. O(1). + */ + function _length(Set storage set, address account) private view returns (uint256) { + return set._values.length(account); + } + + /** + * @dev Returns the value stored at position `index` in the set. O(1). + * + * Note that there are no guarantees on the ordering of values inside the + * array, and it may change when more values are added or removed. + * + * Requirements: + * + * - `index` must be strictly less than {length}. + */ + function _at(Set storage set, address account, uint256 index) private view returns (bytes32) { + return set._values.get(account, index); + } + + /** + * @dev Return the entire set in an array + * + * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed + * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that + * this function has an unbounded cost, and using it as part of a state-changing function may render the function + * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block. + */ + function _values(Set storage set, address account) private view returns (bytes32[] memory) { + return set._values.getAll(account); + } + + // Bytes32Set + + struct Bytes32Set { + Set _inner; + } + + /** + * @dev Add a value to a set. O(1). + * + * Returns true if the value was added to the set, that is if it was not + * already present. + */ + function add(Bytes32Set storage set, address account, bytes32 value) internal returns (bool) { + return _add(set._inner, account, value); + } + + /** + * @dev Removes a value from a set. O(1). + * + * Returns true if the value was removed from the set, that is if it was + * present. + */ + function remove(Bytes32Set storage set, address account, bytes32 value) internal returns (bool) { + return _remove(set._inner, account, value); + } + + function removeAll(Bytes32Set storage set, address account) internal { + return _removeAll(set._inner, account); + } + + /** + * @dev Returns true if the value is in the set. O(1). + */ + function contains(Bytes32Set storage set, address account, bytes32 value) internal view returns (bool) { + return _contains(set._inner, account, value); + } + + /** + * @dev Returns the number of values in the set. O(1). + */ + function length(Bytes32Set storage set, address account) internal view returns (uint256) { + return _length(set._inner, account); + } + + /** + * @dev Returns the value stored at position `index` in the set. O(1). + * + * Note that there are no guarantees on the ordering of values inside the + * array, and it may change when more values are added or removed. + * + * Requirements: + * + * - `index` must be strictly less than {length}. + */ + function at(Bytes32Set storage set, address account, uint256 index) internal view returns (bytes32) { + return _at(set._inner, account, index); + } + + /** + * @dev Return the entire set in an array + * + * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed + * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that + * this function has an unbounded cost, and using it as part of a state-changing function may render the function + * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block. + */ + function values(Bytes32Set storage set, address account) internal view returns (bytes32[] memory) { + bytes32[] memory store = _values(set._inner, account); + bytes32[] memory result; + + /// @solidity memory-safe-assembly + assembly { + result := store + } + + return result; + } + + // AddressSet + + struct AddressSet { + Set _inner; + } + + /** + * @dev Add a value to a set. O(1). + * + * Returns true if the value was added to the set, that is if it was not + * already present. + */ + function add(AddressSet storage set, address account, address value) internal returns (bool) { + return _add(set._inner, account, bytes32(uint256(uint160(value)))); + } + + /** + * @dev Removes a value from a set. O(1). + * + * Returns true if the value was removed from the set, that is if it was + * present. + */ + function remove(AddressSet storage set, address account, address value) internal returns (bool) { + return _remove(set._inner, account, bytes32(uint256(uint160(value)))); + } + + function removeAll(AddressSet storage set, address account) internal { + return _removeAll(set._inner, account); + } + + /** + * @dev Returns true if the value is in the set. O(1). + */ + function contains(AddressSet storage set, address account, address value) internal view returns (bool) { + return _contains(set._inner, account, bytes32(uint256(uint160(value)))); + } + + /** + * @dev Returns the number of values in the set. O(1). + */ + function length(AddressSet storage set, address account) internal view returns (uint256) { + return _length(set._inner, account); + } + + /** + * @dev Returns the value stored at position `index` in the set. O(1). + * + * Note that there are no guarantees on the ordering of values inside the + * array, and it may change when more values are added or removed. + * + * Requirements: + * + * - `index` must be strictly less than {length}. + */ + function at(AddressSet storage set, address account, uint256 index) internal view returns (address) { + return address(uint160(uint256(_at(set._inner, account, index)))); + } + + /** + * @dev Return the entire set in an array + * + * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed + * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that + * this function has an unbounded cost, and using it as part of a state-changing function may render the function + * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block. + */ + function values(AddressSet storage set, address account) internal view returns (address[] memory) { + bytes32[] memory store = _values(set._inner, account); + address[] memory result; + + /// @solidity memory-safe-assembly + assembly { + result := store + } + + return result; + } + + // UintSet + + struct UintSet { + Set _inner; + } + + /** + * @dev Add a value to a set. O(1). + * + * Returns true if the value was added to the set, that is if it was not + * already present. + */ + function add(UintSet storage set, address account, uint256 value) internal returns (bool) { + return _add(set._inner, account, bytes32(value)); + } + + /** + * @dev Removes a value from a set. O(1). + * + * Returns true if the value was removed from the set, that is if it was + * present. + */ + function remove(UintSet storage set, address account, uint256 value) internal returns (bool) { + return _remove(set._inner, account, bytes32(value)); + } + + function removeAll(UintSet storage set, address account) internal { + return _removeAll(set._inner, account); + } + + /** + * @dev Returns true if the value is in the set. O(1). + */ + function contains(UintSet storage set, address account, uint256 value) internal view returns (bool) { + return _contains(set._inner, account, bytes32(value)); + } + + /** + * @dev Returns the number of values in the set. O(1). + */ + function length(UintSet storage set, address account) internal view returns (uint256) { + return _length(set._inner, account); + } + + /** + * @dev Returns the value stored at position `index` in the set. O(1). + * + * Note that there are no guarantees on the ordering of values inside the + * array, and it may change when more values are added or removed. + * + * Requirements: + * + * - `index` must be strictly less than {length}. + */ + function at(UintSet storage set, address account, uint256 index) internal view returns (uint256) { + return uint256(_at(set._inner, account, index)); + } + + /** + * @dev Return the entire set in an array + * + * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed + * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that + * this function has an unbounded cost, and using it as part of a state-changing function may render the function + * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block. + */ + function values(UintSet storage set, address account) internal view returns (uint256[] memory) { + bytes32[] memory store = _values(set._inner, account); + uint256[] memory result; + + /// @solidity memory-safe-assembly + assembly { + result := store + } + + return result; + } +} diff --git a/biconomy/mee/0.0.4/node_modules/erc7579/src/interfaces/IERC7579Module.sol b/biconomy/mee/0.0.4/node_modules/erc7579/src/interfaces/IERC7579Module.sol new file mode 100644 index 0000000..b29827d --- /dev/null +++ b/biconomy/mee/0.0.4/node_modules/erc7579/src/interfaces/IERC7579Module.sol @@ -0,0 +1,120 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.21; + +import { PackedUserOperation } from "account-abstraction/interfaces/PackedUserOperation.sol"; + +uint256 constant VALIDATION_SUCCESS = 0; +uint256 constant VALIDATION_FAILED = 1; + +uint256 constant MODULE_TYPE_VALIDATOR = 1; +uint256 constant MODULE_TYPE_EXECUTOR = 2; +uint256 constant MODULE_TYPE_FALLBACK = 3; +uint256 constant MODULE_TYPE_HOOK = 4; +uint256 constant MODULE_TYPE_PREVALIDATION_HOOK_ERC1271 = 8; +uint256 constant MODULE_TYPE_PREVALIDATION_HOOK_ERC4337 = 9; + +interface IModule { + error AlreadyInitialized(address smartAccount); + error NotInitialized(address smartAccount); + + /** + * @dev This function is called by the smart account during installation of the module + * @param data arbitrary data that may be required on the module during `onInstall` + * initialization + * + * MUST revert on error (i.e. if module is already enabled) + */ + function onInstall(bytes calldata data) external; + + /** + * @dev This function is called by the smart account during uninstallation of the module + * @param data arbitrary data that may be required on the module during `onUninstall` + * de-initialization + * + * MUST revert on error + */ + function onUninstall(bytes calldata data) external; + + /** + * @dev Returns boolean value if module is a certain type + * @param moduleTypeId the module type ID according the ERC-7579 spec + * + * MUST return true if the module is of the given type and false otherwise + */ + function isModuleType(uint256 moduleTypeId) external view returns (bool); + + /** + * @dev Returns if the module was already initialized for a provided smartaccount + */ + function isInitialized(address smartAccount) external view returns (bool); +} + +interface IValidator is IModule { + error InvalidTargetAddress(address target); + + /** + * @dev Validates a transaction on behalf of the account. + * This function is intended to be called by the MSA during the ERC-4337 validaton phase + * Note: solely relying on bytes32 hash and signature is not suffcient for some + * validation implementations (i.e. SessionKeys often need access to userOp.calldata) + * @param userOp The user operation to be validated. The userOp MUST NOT contain any metadata. + * The MSA MUST clean up the userOp before sending it to the validator. + * @param userOpHash The hash of the user operation to be validated + * @return return value according to ERC-4337 + */ + function validateUserOp( + PackedUserOperation calldata userOp, + bytes32 userOpHash + ) + external + returns (uint256); + + /** + * Validator can be used for ERC-1271 validation + */ + function isValidSignatureWithSender( + address sender, + bytes32 hash, + bytes calldata data + ) + external + view + returns (bytes4); +} + +interface IExecutor is IModule { } + +interface IHook is IModule { + function preCheck( + address msgSender, + uint256 msgValue, + bytes calldata msgData + ) + external + returns (bytes memory hookData); + + function postCheck(bytes calldata hookData) external; +} + +interface IFallback is IModule { } + +interface IPreValidationHookERC1271 is IModule { + function preValidationHookERC1271( + address sender, + bytes32 hash, + bytes calldata data + ) + external + view + returns (bytes32 hookHash, bytes memory hookSignature); +} + +interface IPreValidationHookERC4337 is IModule { + function preValidationHookERC4337( + PackedUserOperation calldata userOp, + uint256 missingAccountFunds, + bytes32 userOpHash + ) + external + returns (bytes32 hookHash, bytes memory hookSignature); +} diff --git a/biconomy/mee/0.0.4/node_modules/erc7739-validator-base/src/ERC7739Validator.sol b/biconomy/mee/0.0.4/node_modules/erc7739-validator-base/src/ERC7739Validator.sol new file mode 100644 index 0000000..5bba434 --- /dev/null +++ b/biconomy/mee/0.0.4/node_modules/erc7739-validator-base/src/ERC7739Validator.sol @@ -0,0 +1,389 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.27; + +interface IERC5267 { + function eip712Domain() external view returns ( + bytes1 fields, + string memory name, + string memory version, + uint256 chainId, + address verifyingContract, + bytes32 salt, + uint256[] memory extensions + ); +} + +/// @title ERC-7739: Nested Typed Data Sign Support for ERC-7579 Validators +abstract contract ERC7739Validator { + error InvalidSignature(); + + /// @dev `keccak256("PersonalSign(bytes prefixed)")`. + bytes32 internal constant _PERSONAL_SIGN_TYPEHASH = 0x983e65e5148e570cd828ead231ee759a8d7958721a768f93bc4483ba005c32de; + bytes32 internal constant _DOMAIN_TYPEHASH = 0x8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f; + bytes4 internal constant SUPPORTS_ERC7739_V1 = 0x77390001; + + /*////////////////////////////////////////////////////////////////////////// + INTERNAL + //////////////////////////////////////////////////////////////////////////*/ + + /// @dev Returns whether the `signature` is valid for the `hash. + /// Use this in your validator's `isValidSignatureWithSender` implementation. + function _erc1271IsValidSignatureWithSender(address sender, bytes32 hash, bytes calldata signature) + internal + view + virtual + returns (bytes4) + { + // detection request + // this check only takes 17 gas units + // in theory, it can be moved out of this function so it doesn't apply to every + // isValidSignatureWithSender() call, but it would require an additional standard + // interface for SA to check if the IValidator supports ERC-7739 + // while isValidSignatureWithSender() is specified by ERC-7579, so + // it makes sense to use it in SA to check if the validator supports ERC-7739 + unchecked { + if (signature.length == uint256(0)) { + // Forces the compiler to optimize for smaller bytecode size. + if (uint256(hash) == ~signature.length / 0xffff * 0x7739) + return SUPPORTS_ERC7739_V1; + } + } + + // sig malleability prevention + bytes32 s; + assembly { + // same as `s := mload(add(signature, 0x40))` but for calldata + s := calldataload(add(signature.offset, 0x20)) + } + if (uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) { + revert InvalidSignature(); + } + + bool success = _erc1271IsValidSignatureViaSafeCaller(sender, hash, signature) + || _erc1271IsValidSignatureViaNestedEIP712(hash, signature) + || _erc1271IsValidSignatureViaRPC(hash, signature); + + bytes4 sigValidationResult; + assembly { + // `success ? bytes4(keccak256("isValidSignature(bytes32,bytes)")) : 0xffffffff`. + // We use `0xffffffff` for invalid, in convention with the reference implementation. + sigValidationResult := shl(224, or(0x1626ba7e, sub(0, iszero(success)))) + } + return sigValidationResult; + } + + /// @dev Returns whether the `msg.sender` is considered safe, such + /// that we don't need to use the nested EIP-712 workflow. + /// Override to return true for more callers. + /// See: https://mirror.xyz/curiousapple.eth/pFqAdW2LiJ-6S4sg_u1z08k4vK6BCJ33LcyXpnNb8yU + function _erc1271CallerIsSafe(address sender) internal view virtual returns (bool) { + // The canonical `MulticallerWithSigner` at 0x000000000000D9ECebf3C23529de49815Dac1c4c + // is known to include the account in the hash to be signed. + return sender == 0x000000000000D9ECebf3C23529de49815Dac1c4c; + } + + /// @dev Returns whether the `hash` and `signature` are valid. + /// Obtains the authorized signer's credentials and calls some + /// module's specific internal function to validate the signature + /// against credentials. + /// Override for your module's custom logic. + function _erc1271IsValidSignatureNowCalldata(bytes32 hash, bytes calldata signature) + internal + view + virtual + returns (bool); + + /// @dev Unwraps and returns the signature. + function _erc1271UnwrapSignature(bytes calldata signature) + internal + view + virtual + returns (bytes calldata result) + { + result = signature; + /// @solidity memory-safe-assembly + assembly { + // Unwraps the ERC6492 wrapper if it exists. + // See: https://eips.ethereum.org/EIPS/eip-6492 + if eq( + calldataload(add(result.offset, sub(result.length, 0x20))), + mul(0x6492, div(not(shr(address(), address())), 0xffff)) // `0x6492...6492`. + ) { + let o := add(result.offset, calldataload(add(result.offset, 0x40))) + result.length := calldataload(o) + result.offset := add(o, 0x20) + } + } + } + + /// @dev Performs the signature validation without nested EIP-712 if the caller is + /// a safe caller. A safe caller must include the address of this account in the hash. + function _erc1271IsValidSignatureViaSafeCaller(address sender, bytes32 hash, bytes calldata signature) + internal + view + virtual + returns (bool result) + { + if (_erc1271CallerIsSafe(sender)) result = _erc1271IsValidSignatureNowCalldata(hash, signature); + } + + /// @dev ERC1271 signature validation (Nested EIP-712 workflow). + /// + /// This uses ECDSA recovery by default (see: `_erc1271IsValidSignatureNowCalldata`). + /// It also uses a nested EIP-712 approach to prevent signature replays when a single EOA + /// owns multiple smart contract accounts, + /// while still enabling wallet UIs (e.g. Metamask) to show the EIP-712 values. + /// + /// Crafted for phishing resistance, efficiency, flexibility. + /// __________________________________________________________________________________________ + /// + /// Glossary: + /// + /// - `APP_DOMAIN_SEPARATOR`: The domain separator of the `hash` passed in by the application. + /// Provided by the front end. Intended to be the domain separator of the contract + /// that will call `isValidSignature` on this account. + /// + /// - `ACCOUNT_DOMAIN_SEPARATOR`: The domain separator of this account. + /// See: `EIP712._domainSeparator()`. + /// __________________________________________________________________________________________ + /// + /// For the `TypedDataSign` workflow, the final hash will be: + /// ``` + /// keccak256(\x19\x01 ‖ APP_DOMAIN_SEPARATOR ‖ + /// hashStruct(TypedDataSign({ + /// contents: hashStruct(originalStruct), + /// name: keccak256(bytes(eip712Domain().name)), + /// version: keccak256(bytes(eip712Domain().version)), + /// chainId: eip712Domain().chainId, + /// verifyingContract: eip712Domain().verifyingContract, + /// salt: eip712Domain().salt + /// })) + /// ) + /// ``` + /// where `‖` denotes the concatenation operator for bytes. + /// The order of the fields is important: `contents` comes before `name`. + /// + /// The signature will be `r ‖ s ‖ v ‖ APP_DOMAIN_SEPARATOR ‖ + /// contents ‖ contentsDescription ‖ uint16(contentsDescription.length)`, + /// where: + /// - `contents` is the bytes32 struct hash of the original struct. + /// - `contentsDescription` can be either: + /// a) `contentsType` (implicit mode) + /// where `contentsType` starts with `contentsName`. + /// b) `contentsType ‖ contentsName` (explicit mode) + /// where `contentsType` may not necessarily start with `contentsName`. + /// + /// The `APP_DOMAIN_SEPARATOR` and `contents` will be used to verify if `hash` is indeed correct. + /// __________________________________________________________________________________________ + /// + /// For the `PersonalSign` workflow, the final hash will be: + /// ``` + /// keccak256(\x19\x01 ‖ ACCOUNT_DOMAIN_SEPARATOR ‖ + /// hashStruct(PersonalSign({ + /// prefixed: keccak256(bytes(\x19Ethereum Signed Message:\n ‖ + /// base10(bytes(someString).length) ‖ someString)) + /// })) + /// ) + /// ``` + /// where `‖` denotes the concatenation operator for bytes. + /// + /// The `PersonalSign` type hash will be `keccak256("PersonalSign(bytes prefixed)")`. + /// The signature will be `r ‖ s ‖ v`. + /// __________________________________________________________________________________________ + /// + /// For demo and typescript code, see: + /// - https://github.com/junomonster/nested-eip-712 + /// - https://github.com/frangio/eip712-wrapper-for-eip1271 + /// + /// Their nomenclature may differ from ours, although the high-level idea is similar. + /// + /// Of course, if you have control over the codebase of the wallet client(s) too, + /// you can choose a more minimalistic signature scheme like + /// `keccak256(abi.encode(address(this), hash))` instead of all these acrobatics. + /// All these are just for widespread out-of-the-box compatibility with other wallet clients. + /// We want to create bazaars, not walled castles. + /// And we'll use push the Turing Completeness of the EVM to the limits to do so. + function _erc1271IsValidSignatureViaNestedEIP712(bytes32 hash, bytes calldata signature) + internal + view + virtual + returns (bool result) + { + //bytes32 t = _typedDataSignFieldsForAccount(msg.sender); + uint256 t = uint256(uint160(address(this))); + // Forces the compiler to pop the variables after the scope, avoiding stack-too-deep. + if (t != uint256(0)) { + ( + , + string memory name, + string memory version, + uint256 chainId, + address verifyingContract, + bytes32 salt, + ) = IERC5267(msg.sender).eip712Domain(); + /// @solidity memory-safe-assembly + assembly { + t := mload(0x40) // Grab the free memory pointer. + // Skip 2 words for the `typedDataSignTypehash` and `contents` struct hash. + mstore(add(t, 0x40), keccak256(add(name, 0x20), mload(name))) + mstore(add(t, 0x60), keccak256(add(version, 0x20), mload(version))) + mstore(add(t, 0x80), chainId) + mstore(add(t, 0xa0), shr(96, shl(96, verifyingContract))) + mstore(add(t, 0xc0), salt) + mstore(0x40, add(t, 0xe0)) // Allocate the memory. + } + } + + /// @solidity memory-safe-assembly + assembly { + let m := mload(0x40) // Cache the free memory pointer. + // `c` is `contentsDescription.length`, which is stored in the last 2 bytes of the signature. + let c := shr(240, calldataload(add(signature.offset, sub(signature.length, 2)))) + for {} 1 {} { + let l := add(0x42, c) // Total length of appended data (32 + 32 + c + 2). + let o := add(signature.offset, sub(signature.length, l)) // Offset of appended data. + mstore(0x00, 0x1901) // Store the "\x19\x01" prefix. + calldatacopy(0x20, o, 0x40) // Copy the `APP_DOMAIN_SEPARATOR` and `contents` struct hash. + // Use the `PersonalSign` workflow if the reconstructed hash doesn't match, + // or if the appended data is invalid, i.e. + // `appendedData.length > signature.length || contentsDescription.length == 0`. + if or(xor(keccak256(0x1e, 0x42), hash), or(lt(signature.length, l), iszero(c))) { + t := 0 // Set `t` to 0, denoting that we need to `hash = _hashTypedData(hash)`. + mstore(t, _PERSONAL_SIGN_TYPEHASH) + mstore(0x20, hash) // Store the `prefixed`. + hash := keccak256(t, 0x40) // Compute the `PersonalSign` struct hash. + break + } + // Else, use the `TypedDataSign` workflow. + // `TypedDataSign({ContentsName} contents,string name,...){ContentsType}`. + mstore(m, "TypedDataSign(") // Store the start of `TypedDataSign`'s type encoding. + let p := add(m, 0x0e) // Advance 14 bytes to skip "TypedDataSign(". + + calldatacopy(p, add(o, 0x40), c) // Copy `contentsName`, optimistically. + mstore(add(p, c), 40) // Store a '(' after the end. + if iszero(eq(byte(0, mload(sub(add(p, c), 1))), 41)) { + let e := 0 // Length of `contentsName` in explicit mode. + for { let q := sub(add(p, c), 1) } 1 {} { + e := add(e, 1) // Scan backwards until we encounter a ')'. + if iszero(gt(lt(e, c), eq(byte(0, mload(sub(q, e))), 41))) { break } + } + c := sub(c, e) // Truncate `contentsDescription` to `contentsType`. + calldatacopy(p, add(add(o, 0x40), c), e) // Copy `contentsName`. + mstore8(add(p, e), 40) // Store a '(' exactly right after the end. + } + + // `d & 1 == 1` means that `contentsName` is invalid. + let d := shr(byte(0, mload(p)), 0x7fffffe000000000000010000000000) // Starts with `[a-z(]`. + // Advance `p` until we encounter '('. + for {} iszero(eq(byte(0, mload(p)), 40)) { p := add(p, 1) } { + d := or(shr(byte(0, mload(p)), 0x120100000001), d) // Has a byte in ", )\x00". + } + mstore(p, " contents,string name,string") // Store the rest of the encoding. + mstore(add(p, 0x1c), " version,uint256 chainId,address") + mstore(add(p, 0x3c), " verifyingContract,bytes32 salt)") + p := add(p, 0x5c) + calldatacopy(p, add(o, 0x40), c) // Copy `contentsType`. + // Fill in the missing fields of the `TypedDataSign`. + calldatacopy(t, o, 0x40) // Copy the `contents` struct hash to `add(t, 0x20)`. + mstore(t, keccak256(m, sub(add(p, c), m))) // Store `typedDataSignTypehash`. + // The "\x19\x01" prefix is already at 0x00. + // `APP_DOMAIN_SEPARATOR` is already at 0x20. + mstore(0x40, keccak256(t, 0xe0)) // `hashStruct(typedDataSign)`. + // Compute the final hash, corrupted if `contentsName` is invalid. + hash := keccak256(0x1e, add(0x42, and(1, d))) + signature.length := sub(signature.length, l) // Truncate the signature. + break + } + mstore(0x40, m) // Restore the free memory pointer. + } + if (t == uint256(0)) hash = _hashTypedDataForAccount(msg.sender, hash); // `PersonalSign` workflow. + result = _erc1271IsValidSignatureNowCalldata(hash, signature); + } + + /// @dev Performs the signature validation without nested EIP-712 to allow for easy sign ins. + /// This function must always return false or revert if called on-chain. + function _erc1271IsValidSignatureViaRPC(bytes32 hash, bytes calldata signature) + internal + view + virtual + returns (bool result) + { + // Non-zero gasprice is a heuristic to check if a call is on-chain, + // but we can't fully depend on it because it can be manipulated. + // See: https://x.com/NoahCitron/status/1580359718341484544 + if (tx.gasprice == uint256(0)) { + /// @solidity memory-safe-assembly + assembly { + mstore(gasprice(), gasprice()) + // See: https://gist.github.com/Vectorized/3c9b63524d57492b265454f62d895f71 + let b := 0x000000000000378eDCD5B5B0A24f5342d8C10485 // Basefee contract, + pop(staticcall(0xffff, b, codesize(), gasprice(), gasprice(), 0x20)) + // If `gasprice < basefee`, the call cannot be on-chain, and we can skip the gas burn. + if iszero(mload(gasprice())) { + let m := mload(0x40) // Cache the free memory pointer. + mstore(gasprice(), 0x1626ba7e) // `isValidSignature(bytes32,bytes)`. + mstore(0x20, b) // Recycle `b` to denote if we need to burn gas. + mstore(0x40, 0x40) + let gasToBurn := or(add(0xffff, gaslimit()), gaslimit()) + // Burns gas computationally efficiently. Also, requires that `gas > gasToBurn`. + if or(eq(hash, b), lt(gas(), gasToBurn)) { invalid() } + // Make a call to this with `b`, efficiently burning the gas provided. + // No valid transaction can consume more than the gaslimit. + // See: https://ethereum.github.io/yellowpaper/paper.pdf + // Most RPCs perform calls with a gas budget greater than the gaslimit. + pop(staticcall(gasToBurn, address(), 0x1c, 0x64, gasprice(), gasprice())) + mstore(0x40, m) // Restore the free memory pointer. + } + } + result = _erc1271IsValidSignatureNowCalldata(hash, signature); + } + } + + /// @notice Hashes typed data according to eip-712 + /// Uses account's domain separator + /// @param account the smart account, who's domain separator will be used + /// @param structHash the typed data struct hash + function _hashTypedDataForAccount(address account, bytes32 structHash) private view returns (bytes32 digest) { + ( + /*bytes1 fields*/, + string memory name, + string memory version, + uint256 chainId, + address verifyingContract, + /*bytes32 salt*/, + /*uint256[] memory extensions*/ + ) = IERC5267(account).eip712Domain(); + + /// @solidity memory-safe-assembly + assembly { + //Rebuild domain separator out of 712 domain + let m := mload(0x40) // Load the free memory pointer. + mstore(m, _DOMAIN_TYPEHASH) + mstore(add(m, 0x20), keccak256(add(name, 0x20), mload(name))) // Name hash. + mstore(add(m, 0x40), keccak256(add(version, 0x20), mload(version))) // Version hash. + mstore(add(m, 0x60), chainId) + mstore(add(m, 0x80), verifyingContract) + digest := keccak256(m, 0xa0) //domain separator + + // Hash typed data + mstore(0x00, 0x1901000000000000) // Store "\x19\x01". + mstore(0x1a, digest) // Store the domain separator. + mstore(0x3a, structHash) // Store the struct hash. + digest := keccak256(0x18, 0x42) + // Restore the part of the free memory slot that was overwritten. + mstore(0x3a, 0) + } + } + + /// @dev Backwards compatibility stuff + /// For automatic detection that the smart account supports the nested EIP-712 workflow. + /// By default, it returns `bytes32(bytes4(keccak256("supportsNestedTypedDataSign()")))`, + /// denoting support for the default behavior, as implemented in + /// `_erc1271IsValidSignatureViaNestedEIP712`, which is called in `isValidSignature`. + /// Future extensions should return a different non-zero `result` to denote different behavior. + /// This method intentionally returns bytes32 to allow freedom for future extensions. + function supportsNestedTypedDataSign() public view virtual returns (bytes32 result) { + result = bytes4(0xd620c85a); + } + +} diff --git a/biconomy/mee/0.0.4/node_modules/solady/src/utils/ECDSA.sol b/biconomy/mee/0.0.4/node_modules/solady/src/utils/ECDSA.sol new file mode 100644 index 0000000..a793e1e --- /dev/null +++ b/biconomy/mee/0.0.4/node_modules/solady/src/utils/ECDSA.sol @@ -0,0 +1,442 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.4; + +/// @notice Gas optimized ECDSA wrapper. +/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/ECDSA.sol) +/// @author Modified from Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/ECDSA.sol) +/// @author Modified from OpenZeppelin (https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/cryptography/ECDSA.sol) +/// +/// @dev Note: +/// - The recovery functions use the ecrecover precompile (0x1). +/// - As of Solady version 0.0.68, the `recover` variants will revert upon recovery failure. +/// This is for more safety by default. +/// Use the `tryRecover` variants if you need to get the zero address back +/// upon recovery failure instead. +/// - As of Solady version 0.0.134, all `bytes signature` variants accept both +/// regular 65-byte `(r, s, v)` and EIP-2098 `(r, vs)` short form signatures. +/// See: https://eips.ethereum.org/EIPS/eip-2098 +/// This is for calldata efficiency on smart accounts prevalent on L2s. +/// +/// WARNING! Do NOT directly use signatures as unique identifiers: +/// - The recovery operations do NOT check if a signature is non-malleable. +/// - Use a nonce in the digest to prevent replay attacks on the same contract. +/// - Use EIP-712 for the digest to prevent replay attacks across different chains and contracts. +/// EIP-712 also enables readable signing of typed data for better user safety. +/// - If you need a unique hash from a signature, please use the `canonicalHash` functions. +library ECDSA { + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* CONSTANTS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev The order of the secp256k1 elliptic curve. + uint256 internal constant N = 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141; + + /// @dev `N/2 + 1`. Used for checking the malleability of the signature. + uint256 private constant _HALF_N_PLUS_1 = + 0x7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a1; + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* CUSTOM ERRORS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev The signature is invalid. + error InvalidSignature(); + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* RECOVERY OPERATIONS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev Recovers the signer's address from a message digest `hash`, and the `signature`. + function recover(bytes32 hash, bytes memory signature) internal view returns (address result) { + /// @solidity memory-safe-assembly + assembly { + for { let m := mload(0x40) } 1 { + mstore(0x00, 0x8baa579f) // `InvalidSignature()`. + revert(0x1c, 0x04) + } { + switch mload(signature) + case 64 { + let vs := mload(add(signature, 0x40)) + mstore(0x20, add(shr(255, vs), 27)) // `v`. + mstore(0x60, shr(1, shl(1, vs))) // `s`. + } + case 65 { + mstore(0x20, byte(0, mload(add(signature, 0x60)))) // `v`. + mstore(0x60, mload(add(signature, 0x40))) // `s`. + } + default { continue } + mstore(0x00, hash) + mstore(0x40, mload(add(signature, 0x20))) // `r`. + result := mload(staticcall(gas(), 1, 0x00, 0x80, 0x01, 0x20)) + mstore(0x60, 0) // Restore the zero slot. + mstore(0x40, m) // Restore the free memory pointer. + // `returndatasize()` will be `0x20` upon success, and `0x00` otherwise. + if returndatasize() { break } + } + } + } + + /// @dev Recovers the signer's address from a message digest `hash`, and the `signature`. + function recoverCalldata(bytes32 hash, bytes calldata signature) + internal + view + returns (address result) + { + /// @solidity memory-safe-assembly + assembly { + for { let m := mload(0x40) } 1 { + mstore(0x00, 0x8baa579f) // `InvalidSignature()`. + revert(0x1c, 0x04) + } { + switch signature.length + case 64 { + let vs := calldataload(add(signature.offset, 0x20)) + mstore(0x20, add(shr(255, vs), 27)) // `v`. + mstore(0x40, calldataload(signature.offset)) // `r`. + mstore(0x60, shr(1, shl(1, vs))) // `s`. + } + case 65 { + mstore(0x20, byte(0, calldataload(add(signature.offset, 0x40)))) // `v`. + calldatacopy(0x40, signature.offset, 0x40) // Copy `r` and `s`. + } + default { continue } + mstore(0x00, hash) + result := mload(staticcall(gas(), 1, 0x00, 0x80, 0x01, 0x20)) + mstore(0x60, 0) // Restore the zero slot. + mstore(0x40, m) // Restore the free memory pointer. + // `returndatasize()` will be `0x20` upon success, and `0x00` otherwise. + if returndatasize() { break } + } + } + } + + /// @dev Recovers the signer's address from a message digest `hash`, + /// and the EIP-2098 short form signature defined by `r` and `vs`. + function recover(bytes32 hash, bytes32 r, bytes32 vs) internal view returns (address result) { + /// @solidity memory-safe-assembly + assembly { + let m := mload(0x40) // Cache the free memory pointer. + mstore(0x00, hash) + mstore(0x20, add(shr(255, vs), 27)) // `v`. + mstore(0x40, r) + mstore(0x60, shr(1, shl(1, vs))) // `s`. + result := mload(staticcall(gas(), 1, 0x00, 0x80, 0x01, 0x20)) + // `returndatasize()` will be `0x20` upon success, and `0x00` otherwise. + if iszero(returndatasize()) { + mstore(0x00, 0x8baa579f) // `InvalidSignature()`. + revert(0x1c, 0x04) + } + mstore(0x60, 0) // Restore the zero slot. + mstore(0x40, m) // Restore the free memory pointer. + } + } + + /// @dev Recovers the signer's address from a message digest `hash`, + /// and the signature defined by `v`, `r`, `s`. + function recover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) + internal + view + returns (address result) + { + /// @solidity memory-safe-assembly + assembly { + let m := mload(0x40) // Cache the free memory pointer. + mstore(0x00, hash) + mstore(0x20, and(v, 0xff)) + mstore(0x40, r) + mstore(0x60, s) + result := mload(staticcall(gas(), 1, 0x00, 0x80, 0x01, 0x20)) + // `returndatasize()` will be `0x20` upon success, and `0x00` otherwise. + if iszero(returndatasize()) { + mstore(0x00, 0x8baa579f) // `InvalidSignature()`. + revert(0x1c, 0x04) + } + mstore(0x60, 0) // Restore the zero slot. + mstore(0x40, m) // Restore the free memory pointer. + } + } + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* TRY-RECOVER OPERATIONS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + // WARNING! + // These functions will NOT revert upon recovery failure. + // Instead, they will return the zero address upon recovery failure. + // It is critical that the returned address is NEVER compared against + // a zero address (e.g. an uninitialized address variable). + + /// @dev Recovers the signer's address from a message digest `hash`, and the `signature`. + function tryRecover(bytes32 hash, bytes memory signature) + internal + view + returns (address result) + { + /// @solidity memory-safe-assembly + assembly { + for { let m := mload(0x40) } 1 {} { + switch mload(signature) + case 64 { + let vs := mload(add(signature, 0x40)) + mstore(0x20, add(shr(255, vs), 27)) // `v`. + mstore(0x60, shr(1, shl(1, vs))) // `s`. + } + case 65 { + mstore(0x20, byte(0, mload(add(signature, 0x60)))) // `v`. + mstore(0x60, mload(add(signature, 0x40))) // `s`. + } + default { break } + mstore(0x00, hash) + mstore(0x40, mload(add(signature, 0x20))) // `r`. + pop(staticcall(gas(), 1, 0x00, 0x80, 0x40, 0x20)) + mstore(0x60, 0) // Restore the zero slot. + // `returndatasize()` will be `0x20` upon success, and `0x00` otherwise. + result := mload(xor(0x60, returndatasize())) + mstore(0x40, m) // Restore the free memory pointer. + break + } + } + } + + /// @dev Recovers the signer's address from a message digest `hash`, and the `signature`. + function tryRecoverCalldata(bytes32 hash, bytes calldata signature) + internal + view + returns (address result) + { + /// @solidity memory-safe-assembly + assembly { + for { let m := mload(0x40) } 1 {} { + switch signature.length + case 64 { + let vs := calldataload(add(signature.offset, 0x20)) + mstore(0x20, add(shr(255, vs), 27)) // `v`. + mstore(0x40, calldataload(signature.offset)) // `r`. + mstore(0x60, shr(1, shl(1, vs))) // `s`. + } + case 65 { + mstore(0x20, byte(0, calldataload(add(signature.offset, 0x40)))) // `v`. + calldatacopy(0x40, signature.offset, 0x40) // Copy `r` and `s`. + } + default { break } + mstore(0x00, hash) + pop(staticcall(gas(), 1, 0x00, 0x80, 0x40, 0x20)) + mstore(0x60, 0) // Restore the zero slot. + // `returndatasize()` will be `0x20` upon success, and `0x00` otherwise. + result := mload(xor(0x60, returndatasize())) + mstore(0x40, m) // Restore the free memory pointer. + break + } + } + } + + /// @dev Recovers the signer's address from a message digest `hash`, + /// and the EIP-2098 short form signature defined by `r` and `vs`. + function tryRecover(bytes32 hash, bytes32 r, bytes32 vs) + internal + view + returns (address result) + { + /// @solidity memory-safe-assembly + assembly { + let m := mload(0x40) // Cache the free memory pointer. + mstore(0x00, hash) + mstore(0x20, add(shr(255, vs), 27)) // `v`. + mstore(0x40, r) + mstore(0x60, shr(1, shl(1, vs))) // `s`. + pop(staticcall(gas(), 1, 0x00, 0x80, 0x40, 0x20)) + mstore(0x60, 0) // Restore the zero slot. + // `returndatasize()` will be `0x20` upon success, and `0x00` otherwise. + result := mload(xor(0x60, returndatasize())) + mstore(0x40, m) // Restore the free memory pointer. + } + } + + /// @dev Recovers the signer's address from a message digest `hash`, + /// and the signature defined by `v`, `r`, `s`. + function tryRecover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) + internal + view + returns (address result) + { + /// @solidity memory-safe-assembly + assembly { + let m := mload(0x40) // Cache the free memory pointer. + mstore(0x00, hash) + mstore(0x20, and(v, 0xff)) + mstore(0x40, r) + mstore(0x60, s) + pop(staticcall(gas(), 1, 0x00, 0x80, 0x40, 0x20)) + mstore(0x60, 0) // Restore the zero slot. + // `returndatasize()` will be `0x20` upon success, and `0x00` otherwise. + result := mload(xor(0x60, returndatasize())) + mstore(0x40, m) // Restore the free memory pointer. + } + } + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* HASHING OPERATIONS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev Returns an Ethereum Signed Message, created from a `hash`. + /// This produces a hash corresponding to the one signed with the + /// [`eth_sign`](https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_sign) + /// JSON-RPC method as part of EIP-191. + function toEthSignedMessageHash(bytes32 hash) internal pure returns (bytes32 result) { + /// @solidity memory-safe-assembly + assembly { + mstore(0x20, hash) // Store into scratch space for keccak256. + mstore(0x00, "\x00\x00\x00\x00\x19Ethereum Signed Message:\n32") // 28 bytes. + result := keccak256(0x04, 0x3c) // `32 * 2 - (32 - 28) = 60 = 0x3c`. + } + } + + /// @dev Returns an Ethereum Signed Message, created from `s`. + /// This produces a hash corresponding to the one signed with the + /// [`eth_sign`](https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_sign) + /// JSON-RPC method as part of EIP-191. + /// Note: Supports lengths of `s` up to 999999 bytes. + function toEthSignedMessageHash(bytes memory s) internal pure returns (bytes32 result) { + /// @solidity memory-safe-assembly + assembly { + let sLength := mload(s) + let o := 0x20 + mstore(o, "\x19Ethereum Signed Message:\n") // 26 bytes, zero-right-padded. + mstore(0x00, 0x00) + // Convert the `s.length` to ASCII decimal representation: `base10(s.length)`. + for { let temp := sLength } 1 {} { + o := sub(o, 1) + mstore8(o, add(48, mod(temp, 10))) + temp := div(temp, 10) + if iszero(temp) { break } + } + let n := sub(0x3a, o) // Header length: `26 + 32 - o`. + // Throw an out-of-offset error (consumes all gas) if the header exceeds 32 bytes. + returndatacopy(returndatasize(), returndatasize(), gt(n, 0x20)) + mstore(s, or(mload(0x00), mload(n))) // Temporarily store the header. + result := keccak256(add(s, sub(0x20, n)), add(n, sLength)) + mstore(s, sLength) // Restore the length. + } + } + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* CANONICAL HASH FUNCTIONS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + // The following functions returns the hash of the signature in it's canonicalized format, + // which is the 65-byte `abi.encodePacked(r, s, uint8(v))`, where `v` is either 27 or 28. + // If `s` is greater than `N / 2` then it will be converted to `N - s` + // and the `v` value will be flipped. + // If the signature has an invalid length, or if `v` is invalid, + // a uniquely corrupt hash will be returned. + // These functions are useful for "poor-mans-VRF". + + /// @dev Returns the canonical hash of `signature`. + function canonicalHash(bytes memory signature) internal pure returns (bytes32 result) { + // @solidity memory-safe-assembly + assembly { + let l := mload(signature) + for {} 1 {} { + mstore(0x00, mload(add(signature, 0x20))) // `r`. + let s := mload(add(signature, 0x40)) + let v := mload(add(signature, 0x41)) + if eq(l, 64) { + v := add(shr(255, s), 27) + s := shr(1, shl(1, s)) + } + if iszero(lt(s, _HALF_N_PLUS_1)) { + v := xor(v, 7) + s := sub(N, s) + } + mstore(0x21, v) + mstore(0x20, s) + result := keccak256(0x00, 0x41) + mstore(0x21, 0) // Restore the overwritten part of the free memory pointer. + break + } + + // If the length is neither 64 nor 65, return a uniquely corrupted hash. + if iszero(lt(sub(l, 64), 2)) { + // `bytes4(keccak256("InvalidSignatureLength"))`. + result := xor(keccak256(add(signature, 0x20), l), 0xd62f1ab2) + } + } + } + + /// @dev Returns the canonical hash of `signature`. + function canonicalHashCalldata(bytes calldata signature) + internal + pure + returns (bytes32 result) + { + // @solidity memory-safe-assembly + assembly { + for {} 1 {} { + mstore(0x00, calldataload(signature.offset)) // `r`. + let s := calldataload(add(signature.offset, 0x20)) + let v := calldataload(add(signature.offset, 0x21)) + if eq(signature.length, 64) { + v := add(shr(255, s), 27) + s := shr(1, shl(1, s)) + } + if iszero(lt(s, _HALF_N_PLUS_1)) { + v := xor(v, 7) + s := sub(N, s) + } + mstore(0x21, v) + mstore(0x20, s) + result := keccak256(0x00, 0x41) + mstore(0x21, 0) // Restore the overwritten part of the free memory pointer. + break + } + // If the length is neither 64 nor 65, return a uniquely corrupted hash. + if iszero(lt(sub(signature.length, 64), 2)) { + calldatacopy(mload(0x40), signature.offset, signature.length) + // `bytes4(keccak256("InvalidSignatureLength"))`. + result := xor(keccak256(mload(0x40), signature.length), 0xd62f1ab2) + } + } + } + + /// @dev Returns the canonical hash of `signature`. + function canonicalHash(bytes32 r, bytes32 vs) internal pure returns (bytes32 result) { + // @solidity memory-safe-assembly + assembly { + mstore(0x00, r) // `r`. + let v := add(shr(255, vs), 27) + let s := shr(1, shl(1, vs)) + mstore(0x21, v) + mstore(0x20, s) + result := keccak256(0x00, 0x41) + mstore(0x21, 0) // Restore the overwritten part of the free memory pointer. + } + } + + /// @dev Returns the canonical hash of `signature`. + function canonicalHash(uint8 v, bytes32 r, bytes32 s) internal pure returns (bytes32 result) { + // @solidity memory-safe-assembly + assembly { + mstore(0x00, r) // `r`. + if iszero(lt(s, _HALF_N_PLUS_1)) { + v := xor(v, 7) + s := sub(N, s) + } + mstore(0x21, v) + mstore(0x20, s) + result := keccak256(0x00, 0x41) + mstore(0x21, 0) // Restore the overwritten part of the free memory pointer. + } + } + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* EMPTY CALLDATA HELPERS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev Returns an empty calldata bytes. + function emptySignature() internal pure returns (bytes calldata signature) { + /// @solidity memory-safe-assembly + assembly { + signature.length := 0 + } + } +} diff --git a/biconomy/mee/0.0.4/node_modules/solidity-bytes-utils/contracts/BytesLib.sol b/biconomy/mee/0.0.4/node_modules/solidity-bytes-utils/contracts/BytesLib.sol new file mode 100644 index 0000000..532897a --- /dev/null +++ b/biconomy/mee/0.0.4/node_modules/solidity-bytes-utils/contracts/BytesLib.sol @@ -0,0 +1,510 @@ +// SPDX-License-Identifier: Unlicense +/* + * @title Solidity Bytes Arrays Utils + * @author Gonçalo Sá + * + * @dev Bytes tightly packed arrays utility library for ethereum contracts written in Solidity. + * The library lets you concatenate, slice and type cast bytes arrays both in memory and storage. + */ +pragma solidity >=0.8.0 <0.9.0; + + +library BytesLib { + function concat( + bytes memory _preBytes, + bytes memory _postBytes + ) + internal + pure + returns (bytes memory) + { + bytes memory tempBytes; + + assembly { + // Get a location of some free memory and store it in tempBytes as + // Solidity does for memory variables. + tempBytes := mload(0x40) + + // Store the length of the first bytes array at the beginning of + // the memory for tempBytes. + let length := mload(_preBytes) + mstore(tempBytes, length) + + // Maintain a memory counter for the current write location in the + // temp bytes array by adding the 32 bytes for the array length to + // the starting location. + let mc := add(tempBytes, 0x20) + // Stop copying when the memory counter reaches the length of the + // first bytes array. + let end := add(mc, length) + + for { + // Initialize a copy counter to the start of the _preBytes data, + // 32 bytes into its memory. + let cc := add(_preBytes, 0x20) + } lt(mc, end) { + // Increase both counters by 32 bytes each iteration. + mc := add(mc, 0x20) + cc := add(cc, 0x20) + } { + // Write the _preBytes data into the tempBytes memory 32 bytes + // at a time. + mstore(mc, mload(cc)) + } + + // Add the length of _postBytes to the current length of tempBytes + // and store it as the new length in the first 32 bytes of the + // tempBytes memory. + length := mload(_postBytes) + mstore(tempBytes, add(length, mload(tempBytes))) + + // Move the memory counter back from a multiple of 0x20 to the + // actual end of the _preBytes data. + mc := end + // Stop copying when the memory counter reaches the new combined + // length of the arrays. + end := add(mc, length) + + for { + let cc := add(_postBytes, 0x20) + } lt(mc, end) { + mc := add(mc, 0x20) + cc := add(cc, 0x20) + } { + mstore(mc, mload(cc)) + } + + // Update the free-memory pointer by padding our last write location + // to 32 bytes: add 31 bytes to the end of tempBytes to move to the + // next 32 byte block, then round down to the nearest multiple of + // 32. If the sum of the length of the two arrays is zero then add + // one before rounding down to leave a blank 32 bytes (the length block with 0). + mstore(0x40, and( + add(add(end, iszero(add(length, mload(_preBytes)))), 31), + not(31) // Round down to the nearest 32 bytes. + )) + } + + return tempBytes; + } + + function concatStorage(bytes storage _preBytes, bytes memory _postBytes) internal { + assembly { + // Read the first 32 bytes of _preBytes storage, which is the length + // of the array. (We don't need to use the offset into the slot + // because arrays use the entire slot.) + let fslot := sload(_preBytes.slot) + // Arrays of 31 bytes or less have an even value in their slot, + // while longer arrays have an odd value. The actual length is + // the slot divided by two for odd values, and the lowest order + // byte divided by two for even values. + // If the slot is even, bitwise and the slot with 255 and divide by + // two to get the length. If the slot is odd, bitwise and the slot + // with -1 and divide by two. + let slength := div(and(fslot, sub(mul(0x100, iszero(and(fslot, 1))), 1)), 2) + let mlength := mload(_postBytes) + let newlength := add(slength, mlength) + // slength can contain both the length and contents of the array + // if length < 32 bytes so let's prepare for that + // v. http://solidity.readthedocs.io/en/latest/miscellaneous.html#layout-of-state-variables-in-storage + switch add(lt(slength, 32), lt(newlength, 32)) + case 2 { + // Since the new array still fits in the slot, we just need to + // update the contents of the slot. + // uint256(bytes_storage) = uint256(bytes_storage) + uint256(bytes_memory) + new_length + sstore( + _preBytes.slot, + // all the modifications to the slot are inside this + // next block + add( + // we can just add to the slot contents because the + // bytes we want to change are the LSBs + fslot, + add( + mul( + div( + // load the bytes from memory + mload(add(_postBytes, 0x20)), + // zero all bytes to the right + exp(0x100, sub(32, mlength)) + ), + // and now shift left the number of bytes to + // leave space for the length in the slot + exp(0x100, sub(32, newlength)) + ), + // increase length by the double of the memory + // bytes length + mul(mlength, 2) + ) + ) + ) + } + case 1 { + // The stored value fits in the slot, but the combined value + // will exceed it. + // get the keccak hash to get the contents of the array + mstore(0x0, _preBytes.slot) + let sc := add(keccak256(0x0, 0x20), div(slength, 32)) + + // save new length + sstore(_preBytes.slot, add(mul(newlength, 2), 1)) + + // The contents of the _postBytes array start 32 bytes into + // the structure. Our first read should obtain the `submod` + // bytes that can fit into the unused space in the last word + // of the stored array. To get this, we read 32 bytes starting + // from `submod`, so the data we read overlaps with the array + // contents by `submod` bytes. Masking the lowest-order + // `submod` bytes allows us to add that value directly to the + // stored value. + + let submod := sub(32, slength) + let mc := add(_postBytes, submod) + let end := add(_postBytes, mlength) + let mask := sub(exp(0x100, submod), 1) + + sstore( + sc, + add( + and( + fslot, + 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00 + ), + and(mload(mc), mask) + ) + ) + + for { + mc := add(mc, 0x20) + sc := add(sc, 1) + } lt(mc, end) { + sc := add(sc, 1) + mc := add(mc, 0x20) + } { + sstore(sc, mload(mc)) + } + + mask := exp(0x100, sub(mc, end)) + + sstore(sc, mul(div(mload(mc), mask), mask)) + } + default { + // get the keccak hash to get the contents of the array + mstore(0x0, _preBytes.slot) + // Start copying to the last used word of the stored array. + let sc := add(keccak256(0x0, 0x20), div(slength, 32)) + + // save new length + sstore(_preBytes.slot, add(mul(newlength, 2), 1)) + + // Copy over the first `submod` bytes of the new data as in + // case 1 above. + let slengthmod := mod(slength, 32) + let mlengthmod := mod(mlength, 32) + let submod := sub(32, slengthmod) + let mc := add(_postBytes, submod) + let end := add(_postBytes, mlength) + let mask := sub(exp(0x100, submod), 1) + + sstore(sc, add(sload(sc), and(mload(mc), mask))) + + for { + sc := add(sc, 1) + mc := add(mc, 0x20) + } lt(mc, end) { + sc := add(sc, 1) + mc := add(mc, 0x20) + } { + sstore(sc, mload(mc)) + } + + mask := exp(0x100, sub(mc, end)) + + sstore(sc, mul(div(mload(mc), mask), mask)) + } + } + } + + function slice( + bytes memory _bytes, + uint256 _start, + uint256 _length + ) + internal + pure + returns (bytes memory) + { + require(_length + 31 >= _length, "slice_overflow"); + require(_bytes.length >= _start + _length, "slice_outOfBounds"); + + bytes memory tempBytes; + + assembly { + switch iszero(_length) + case 0 { + // Get a location of some free memory and store it in tempBytes as + // Solidity does for memory variables. + tempBytes := mload(0x40) + + // The first word of the slice result is potentially a partial + // word read from the original array. To read it, we calculate + // the length of that partial word and start copying that many + // bytes into the array. The first word we copy will start with + // data we don't care about, but the last `lengthmod` bytes will + // land at the beginning of the contents of the new array. When + // we're done copying, we overwrite the full first word with + // the actual length of the slice. + let lengthmod := and(_length, 31) + + // The multiplication in the next line is necessary + // because when slicing multiples of 32 bytes (lengthmod == 0) + // the following copy loop was copying the origin's length + // and then ending prematurely not copying everything it should. + let mc := add(add(tempBytes, lengthmod), mul(0x20, iszero(lengthmod))) + let end := add(mc, _length) + + for { + // The multiplication in the next line has the same exact purpose + // as the one above. + let cc := add(add(add(_bytes, lengthmod), mul(0x20, iszero(lengthmod))), _start) + } lt(mc, end) { + mc := add(mc, 0x20) + cc := add(cc, 0x20) + } { + mstore(mc, mload(cc)) + } + + mstore(tempBytes, _length) + + //update free-memory pointer + //allocating the array padded to 32 bytes like the compiler does now + mstore(0x40, and(add(mc, 31), not(31))) + } + //if we want a zero-length slice let's just return a zero-length array + default { + tempBytes := mload(0x40) + //zero out the 32 bytes slice we are about to return + //we need to do it because Solidity does not garbage collect + mstore(tempBytes, 0) + + mstore(0x40, add(tempBytes, 0x20)) + } + } + + return tempBytes; + } + + function toAddress(bytes memory _bytes, uint256 _start) internal pure returns (address) { + require(_bytes.length >= _start + 20, "toAddress_outOfBounds"); + address tempAddress; + + assembly { + tempAddress := div(mload(add(add(_bytes, 0x20), _start)), 0x1000000000000000000000000) + } + + return tempAddress; + } + + function toUint8(bytes memory _bytes, uint256 _start) internal pure returns (uint8) { + require(_bytes.length >= _start + 1 , "toUint8_outOfBounds"); + uint8 tempUint; + + assembly { + tempUint := mload(add(add(_bytes, 0x1), _start)) + } + + return tempUint; + } + + function toUint16(bytes memory _bytes, uint256 _start) internal pure returns (uint16) { + require(_bytes.length >= _start + 2, "toUint16_outOfBounds"); + uint16 tempUint; + + assembly { + tempUint := mload(add(add(_bytes, 0x2), _start)) + } + + return tempUint; + } + + function toUint32(bytes memory _bytes, uint256 _start) internal pure returns (uint32) { + require(_bytes.length >= _start + 4, "toUint32_outOfBounds"); + uint32 tempUint; + + assembly { + tempUint := mload(add(add(_bytes, 0x4), _start)) + } + + return tempUint; + } + + function toUint64(bytes memory _bytes, uint256 _start) internal pure returns (uint64) { + require(_bytes.length >= _start + 8, "toUint64_outOfBounds"); + uint64 tempUint; + + assembly { + tempUint := mload(add(add(_bytes, 0x8), _start)) + } + + return tempUint; + } + + function toUint96(bytes memory _bytes, uint256 _start) internal pure returns (uint96) { + require(_bytes.length >= _start + 12, "toUint96_outOfBounds"); + uint96 tempUint; + + assembly { + tempUint := mload(add(add(_bytes, 0xc), _start)) + } + + return tempUint; + } + + function toUint128(bytes memory _bytes, uint256 _start) internal pure returns (uint128) { + require(_bytes.length >= _start + 16, "toUint128_outOfBounds"); + uint128 tempUint; + + assembly { + tempUint := mload(add(add(_bytes, 0x10), _start)) + } + + return tempUint; + } + + function toUint256(bytes memory _bytes, uint256 _start) internal pure returns (uint256) { + require(_bytes.length >= _start + 32, "toUint256_outOfBounds"); + uint256 tempUint; + + assembly { + tempUint := mload(add(add(_bytes, 0x20), _start)) + } + + return tempUint; + } + + function toBytes32(bytes memory _bytes, uint256 _start) internal pure returns (bytes32) { + require(_bytes.length >= _start + 32, "toBytes32_outOfBounds"); + bytes32 tempBytes32; + + assembly { + tempBytes32 := mload(add(add(_bytes, 0x20), _start)) + } + + return tempBytes32; + } + + function equal(bytes memory _preBytes, bytes memory _postBytes) internal pure returns (bool) { + bool success = true; + + assembly { + let length := mload(_preBytes) + + // if lengths don't match the arrays are not equal + switch eq(length, mload(_postBytes)) + case 1 { + // cb is a circuit breaker in the for loop since there's + // no said feature for inline assembly loops + // cb = 1 - don't breaker + // cb = 0 - break + let cb := 1 + + let mc := add(_preBytes, 0x20) + let end := add(mc, length) + + for { + let cc := add(_postBytes, 0x20) + // the next line is the loop condition: + // while(uint256(mc < end) + cb == 2) + } eq(add(lt(mc, end), cb), 2) { + mc := add(mc, 0x20) + cc := add(cc, 0x20) + } { + // if any of these checks fails then arrays are not equal + if iszero(eq(mload(mc), mload(cc))) { + // unsuccess: + success := 0 + cb := 0 + } + } + } + default { + // unsuccess: + success := 0 + } + } + + return success; + } + + function equalStorage( + bytes storage _preBytes, + bytes memory _postBytes + ) + internal + view + returns (bool) + { + bool success = true; + + assembly { + // we know _preBytes_offset is 0 + let fslot := sload(_preBytes.slot) + // Decode the length of the stored array like in concatStorage(). + let slength := div(and(fslot, sub(mul(0x100, iszero(and(fslot, 1))), 1)), 2) + let mlength := mload(_postBytes) + + // if lengths don't match the arrays are not equal + switch eq(slength, mlength) + case 1 { + // slength can contain both the length and contents of the array + // if length < 32 bytes so let's prepare for that + // v. http://solidity.readthedocs.io/en/latest/miscellaneous.html#layout-of-state-variables-in-storage + if iszero(iszero(slength)) { + switch lt(slength, 32) + case 1 { + // blank the last byte which is the length + fslot := mul(div(fslot, 0x100), 0x100) + + if iszero(eq(fslot, mload(add(_postBytes, 0x20)))) { + // unsuccess: + success := 0 + } + } + default { + // cb is a circuit breaker in the for loop since there's + // no said feature for inline assembly loops + // cb = 1 - don't breaker + // cb = 0 - break + let cb := 1 + + // get the keccak hash to get the contents of the array + mstore(0x0, _preBytes.slot) + let sc := keccak256(0x0, 0x20) + + let mc := add(_postBytes, 0x20) + let end := add(mc, mlength) + + // the next line is the loop condition: + // while(uint256(mc < end) + cb == 2) + for {} eq(add(lt(mc, end), cb), 2) { + sc := add(sc, 1) + mc := add(mc, 0x20) + } { + if iszero(eq(sload(sc), mload(mc))) { + // unsuccess: + success := 0 + cb := 0 + } + } + } + } + } + default { + // unsuccess: + success := 0 + } + } + + return success; + } +} diff --git a/biconomy/mee/0.0.4/remappings.txt b/biconomy/mee/0.0.4/remappings.txt new file mode 100644 index 0000000..3b6c5ea --- /dev/null +++ b/biconomy/mee/0.0.4/remappings.txt @@ -0,0 +1,9 @@ +openzeppelin/=node_modules/@openzeppelin/contracts/ +account-abstraction/=node_modules/account-abstraction/contracts/ +erc7739Validator/=node_modules/erc7739-validator-base/src/ +solady/=node_modules/solady/src/ +EnumerableSet4337/=node_modules/enumerablemap4337/src/ +byteslib/=node_modules/solidity-bytes-utils/contracts/ +rlp-reader/=node_modules/Solidity-RLP/contracts/ +erc7579/=node_modules/erc7579/src/ +@openzeppelin/=node_modules/@openzeppelin/ diff --git a/biconomy/mee/Makefile b/biconomy/mee/Makefile new file mode 100644 index 0000000..4230178 --- /dev/null +++ b/biconomy/mee/Makefile @@ -0,0 +1,7 @@ +VERSIONS += 0.0.4 + +.PHONY: all $(VERSIONS) +all: $(VERSIONS) + +$(VERSIONS): + @$(MAKE) -C $@ -f ../../../Makefile.project diff --git a/biconomy/nexus/1.0.2/cannonfile.toml b/biconomy/nexus/1.0.2/cannonfile.toml new file mode 100644 index 0000000..f41e4e3 --- /dev/null +++ b/biconomy/nexus/1.0.2/cannonfile.toml @@ -0,0 +1,54 @@ +name = "biconomy-nexus" +version = "1.0.2" +description = "Nexus ERC-4337 infrastructure" + +[var.Settings] +owner = "0x129443cA2a9Dec2020808a2868b38dDA457eaCC7" + +[pull.entryPoint] +source = "eth-infinitism-entrypoint:0.7.0" + +[pull.registry] +source = "rhinestone-registry:1.0.0" + +[deploy.Nexus] +artifact = "Nexus" +args = ["<%= entryPoint.EntryPoint.address %>"] +create2 = true +salt = "0x0000000000000000000000000000000000000000d94e66ffea57d5033465d361" +ifExists = "continue" + +[deploy.NexusAccountFactory] +artifact = "NexusAccountFactory" +args = [ + "<%= contracts.Nexus.address %>", + "<%= settings.owner %>" +] +create2 = true +salt = "0x0000000000000000000000000000000000000000663d6c9d31481c0429f9d44c" +ifExists = "continue" + +[deploy.NexusBootstrap] +artifact = "NexusBootstrap" +create2 = true +salt = "0x00000000000000000000000000000000000000005e620e103460b60399842649" +ifExists = "continue" + +[deploy.K1Validator] +artifact = "K1Validator" +create2 = true +salt = "0x000000000000000000000000000000000000000014fedeb9e1c61d030943b78e" +ifExists = "continue" + +[deploy.K1ValidatorFactory] +artifact = "K1ValidatorFactory" +args = [ + "<%= contracts.Nexus.address %>", + "<%= settings.owner %>", + "<%= contracts.K1Validator.address %>", + "<%= contracts.NexusBootstrap.address %>", + "0x000000000069E2a187AEFFb852bF3cCdC95151B2" +] +create2 = true +salt = "0x0000000000000000000000000000000000000000ab83b9fa3c3b1b041f8fa33b" +ifExists = "continue" diff --git a/biconomy/nexus/1.0.2/contracts/Nexus.sol b/biconomy/nexus/1.0.2/contracts/Nexus.sol new file mode 100644 index 0000000..d3917a0 --- /dev/null +++ b/biconomy/nexus/1.0.2/contracts/Nexus.sol @@ -0,0 +1,368 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.27; + +// ────────────────────────────────────────────────────────────────────────────── +// _ __ _ __ +// / | / /__ | |/ /_ _______ +// / |/ / _ \| / / / / ___/ +// / /| / __/ / /_/ (__ ) +// /_/ |_/\___/_/|_\__,_/____/ +// +// ────────────────────────────────────────────────────────────────────────────── +// Nexus: A suite of contracts for Modular Smart Accounts compliant with ERC-7579 and ERC-4337, developed by Biconomy. +// Learn more at https://biconomy.io. To report security issues, please contact us at: security@biconomy.io + +import { UUPSUpgradeable } from "solady/utils/UUPSUpgradeable.sol"; +import { PackedUserOperation } from "account-abstraction/interfaces/PackedUserOperation.sol"; +import { ExecLib } from "./lib/ExecLib.sol"; +import { INexus } from "./interfaces/INexus.sol"; +import { BaseAccount } from "./base/BaseAccount.sol"; +import { IERC7484 } from "./interfaces/IERC7484.sol"; +import { ModuleManager } from "./base/ModuleManager.sol"; +import { ExecutionHelper } from "./base/ExecutionHelper.sol"; +import { IValidator } from "./interfaces/modules/IValidator.sol"; +import { MODULE_TYPE_VALIDATOR, MODULE_TYPE_EXECUTOR, MODULE_TYPE_FALLBACK, MODULE_TYPE_HOOK, MODULE_TYPE_MULTI, SUPPORTS_ERC7739 } from "./types/Constants.sol"; +import { ModeLib, ExecutionMode, ExecType, CallType, CALLTYPE_BATCH, CALLTYPE_SINGLE, CALLTYPE_DELEGATECALL, EXECTYPE_DEFAULT, EXECTYPE_TRY } from "./lib/ModeLib.sol"; +import { NonceLib } from "./lib/NonceLib.sol"; +import { SentinelListLib, SENTINEL, ZERO_ADDRESS } from "sentinellist/SentinelList.sol"; + +/// @title Nexus - Smart Account +/// @notice This contract integrates various functionalities to handle modular smart accounts compliant with ERC-7579 and ERC-4337 standards. +/// @dev Comprehensive suite of methods for managing smart accounts, integrating module management, execution management, and upgradability via UUPS. +/// @author @livingrockrises | Biconomy | chirag@biconomy.io +/// @author @aboudjem | Biconomy | adam.boudjemaa@biconomy.io +/// @author @filmakarov | Biconomy | filipp.makarov@biconomy.io +/// @author @zeroknots | Rhinestone.wtf | zeroknots.eth +/// Special thanks to the Solady team for foundational contributions: https://github.com/Vectorized/solady +contract Nexus is INexus, BaseAccount, ExecutionHelper, ModuleManager, UUPSUpgradeable { + using ModeLib for ExecutionMode; + using ExecLib for bytes; + using NonceLib for uint256; + using SentinelListLib for SentinelListLib.SentinelList; + + /// @dev The timelock period for emergency hook uninstallation. + uint256 internal constant _EMERGENCY_TIMELOCK = 1 days; + + /// @dev The event emitted when an emergency hook uninstallation is initiated. + event EmergencyHookUninstallRequest(address hook, uint256 timestamp); + + /// @dev The event emitted when an emergency hook uninstallation request is reset. + event EmergencyHookUninstallRequestReset(address hook, uint256 timestamp); + + /// @notice Initializes the smart account with the specified entry point. + constructor(address anEntryPoint) { + require(address(anEntryPoint) != address(0), EntryPointCanNotBeZero()); + _ENTRYPOINT = anEntryPoint; + _initModuleManager(); + } + + /// @notice Validates a user operation against a specified validator, extracted from the operation's nonce. + /// @param op The user operation to validate, encapsulating all transaction details. + /// @param userOpHash Hash of the user operation data, used for signature validation. + /// @param missingAccountFunds Funds missing from the account's deposit necessary for transaction execution. + /// This can be zero if covered by a paymaster or if sufficient deposit exists. + /// @return validationData Encoded validation result or failure, propagated from the validator module. + /// - Encoded format in validationData: + /// - First 20 bytes: Address of the Validator module, to which the validation task is forwarded. + /// The validator module returns: + /// - `SIG_VALIDATION_SUCCESS` (0) indicates successful validation. + /// - `SIG_VALIDATION_FAILED` (1) indicates signature validation failure. + /// @dev Expects the validator's address to be encoded in the upper 96 bits of the user operation's nonce. + /// This method forwards the validation task to the extracted validator module address. + /// @dev The entryPoint calls this function. If validation fails, it returns `VALIDATION_FAILED` (1) otherwise `0`. + /// @dev Features Module Enable Mode. + /// This Module Enable Mode flow is intended for the module acting as the validator + /// for the user operation that triggers the Module Enable Flow. Otherwise, a call to + /// `Nexus.installModule` should be included in `userOp.callData`. + function validateUserOp( + PackedUserOperation calldata op, + bytes32 userOpHash, + uint256 missingAccountFunds + ) external virtual payPrefund(missingAccountFunds) onlyEntryPoint returns (uint256 validationData) { + address validator = op.nonce.getValidator(); + if (op.nonce.isModuleEnableMode()) { + PackedUserOperation memory userOp = op; + userOp.signature = _enableMode(userOpHash, op.signature); + require(_isValidatorInstalled(validator), ValidatorNotInstalled(validator)); + validationData = IValidator(validator).validateUserOp(userOp, userOpHash); + } else { + require(_isValidatorInstalled(validator), ValidatorNotInstalled(validator)); + validationData = IValidator(validator).validateUserOp(op, userOpHash); + } + } + + /// @notice Executes transactions in single or batch modes as specified by the execution mode. + /// @param mode The execution mode detailing how transactions should be handled (single, batch, default, try/catch). + /// @param executionCalldata The encoded transaction data to execute. + /// @dev This function handles transaction execution flexibility and is protected by the `onlyEntryPoint` modifier. + /// @dev This function also goes through hook checks via withHook modifier. + function execute(ExecutionMode mode, bytes calldata executionCalldata) external payable onlyEntryPoint withHook { + (CallType callType, ExecType execType) = mode.decodeBasic(); + if (callType == CALLTYPE_SINGLE) { + _handleSingleExecution(executionCalldata, execType); + } else if (callType == CALLTYPE_BATCH) { + _handleBatchExecution(executionCalldata, execType); + } else if (callType == CALLTYPE_DELEGATECALL) { + _handleDelegateCallExecution(executionCalldata, execType); + } else { + revert UnsupportedCallType(callType); + } + } + + /// @notice Executes transactions from an executor module, supporting both single and batch transactions. + /// @param mode The execution mode (single or batch, default or try). + /// @param executionCalldata The transaction data to execute. + /// @return returnData The results of the transaction executions, which may include errors in try mode. + /// @dev This function is callable only by an executor module and goes through hook checks. + function executeFromExecutor( + ExecutionMode mode, + bytes calldata executionCalldata + ) external payable onlyExecutorModule withHook withRegistry(msg.sender, MODULE_TYPE_EXECUTOR) returns (bytes[] memory returnData) { + (CallType callType, ExecType execType) = mode.decodeBasic(); + // check if calltype is batch or single or delegate call + if (callType == CALLTYPE_SINGLE) { + returnData = _handleSingleExecutionAndReturnData(executionCalldata, execType); + } else if (callType == CALLTYPE_BATCH) { + returnData = _handleBatchExecutionAndReturnData(executionCalldata, execType); + } else if (callType == CALLTYPE_DELEGATECALL) { + returnData = _handleDelegateCallExecutionAndReturnData(executionCalldata, execType); + } else { + revert UnsupportedCallType(callType); + } + } + + /// @notice Executes a user operation via a call using the contract's context. + /// @param userOp The user operation to execute, containing transaction details. + /// @param - Hash of the user operation. + /// @dev Only callable by the EntryPoint. Decodes the user operation calldata, skipping the first four bytes, and executes the inner call. + function executeUserOp(PackedUserOperation calldata userOp, bytes32) external payable virtual onlyEntryPoint withHook { + bytes calldata callData = userOp.callData[4:]; + (bool success, bytes memory innerCallRet) = address(this).delegatecall(callData); + if (success) { + emit Executed(userOp, innerCallRet); + } else revert ExecutionFailed(); + } + + /// @notice Installs a new module to the smart account. + /// @param moduleTypeId The type identifier of the module being installed, which determines its role: + /// - 1 for Validator + /// - 2 for Executor + /// - 3 for Fallback + /// - 4 for Hook + /// @param module The address of the module to install. + /// @param initData Initialization data for the module. + /// @dev This function can only be called by the EntryPoint or the account itself for security reasons. + /// @dev This function goes through hook checks via withHook modifier through internal function _installModule. + function installModule(uint256 moduleTypeId, address module, bytes calldata initData) external payable onlyEntryPointOrSelf { + _installModule(moduleTypeId, module, initData); + emit ModuleInstalled(moduleTypeId, module); + } + + /// @notice Uninstalls a module from the smart account. + /// @param moduleTypeId The type ID of the module to be uninstalled, matching the installation type: + /// - 1 for Validator + /// - 2 for Executor + /// - 3 for Fallback + /// - 4 for Hook + /// @param module The address of the module to uninstall. + /// @param deInitData De-initialization data for the module. + /// @dev Ensures that the operation is authorized and valid before proceeding with the uninstallation. + function uninstallModule(uint256 moduleTypeId, address module, bytes calldata deInitData) external payable onlyEntryPointOrSelf withHook { + require(_isModuleInstalled(moduleTypeId, module, deInitData), ModuleNotInstalled(moduleTypeId, module)); + emit ModuleUninstalled(moduleTypeId, module); + + if (moduleTypeId == MODULE_TYPE_VALIDATOR) { + _uninstallValidator(module, deInitData); + } else if (moduleTypeId == MODULE_TYPE_EXECUTOR) { + _uninstallExecutor(module, deInitData); + } else if (moduleTypeId == MODULE_TYPE_FALLBACK) { + _uninstallFallbackHandler(module, deInitData); + } else if (moduleTypeId == MODULE_TYPE_HOOK) { + _uninstallHook(module, deInitData); + } + } + + function emergencyUninstallHook(address hook, bytes calldata deInitData) external payable onlyEntryPoint { + require(_isModuleInstalled(MODULE_TYPE_HOOK, hook, deInitData), ModuleNotInstalled(MODULE_TYPE_HOOK, hook)); + AccountStorage storage accountStorage = _getAccountStorage(); + uint256 hookTimelock = accountStorage.emergencyUninstallTimelock[hook]; + + if (hookTimelock == 0) { + // if the timelock hasnt been initiated, initiate it + accountStorage.emergencyUninstallTimelock[hook] = block.timestamp; + emit EmergencyHookUninstallRequest(hook, block.timestamp); + } else if (block.timestamp >= hookTimelock + 3 * _EMERGENCY_TIMELOCK) { + // if the timelock has been left for too long, reset it + accountStorage.emergencyUninstallTimelock[hook] = block.timestamp; + emit EmergencyHookUninstallRequestReset(hook, block.timestamp); + } else if (block.timestamp >= hookTimelock + _EMERGENCY_TIMELOCK) { + // if the timelock expired, clear it and uninstall the hook + accountStorage.emergencyUninstallTimelock[hook] = 0; + _uninstallHook(hook, deInitData); + emit ModuleUninstalled(MODULE_TYPE_HOOK, hook); + } else { + // if the timelock is initiated but not expired, revert + revert EmergencyTimeLockNotExpired(); + } + } + + function initializeAccount(bytes calldata initData) external payable virtual { + _initModuleManager(); + (address bootstrap, bytes memory bootstrapCall) = abi.decode(initData, (address, bytes)); + (bool success, ) = bootstrap.delegatecall(bootstrapCall); + + require(success, NexusInitializationFailed()); + require(_hasValidators(), NoValidatorInstalled()); + } + + function setRegistry(IERC7484 newRegistry, address[] calldata attesters, uint8 threshold) external payable onlyEntryPointOrSelf { + _configureRegistry(newRegistry, attesters, threshold); + } + + /// @notice Validates a signature according to ERC-1271 standards. + /// @param hash The hash of the data being validated. + /// @param signature Signature data that needs to be validated. + /// @return The status code of the signature validation (`0x1626ba7e` if valid). + /// bytes4(keccak256("isValidSignature(bytes32,bytes)") = 0x1626ba7e + /// @dev Delegates the validation to a validator module specified within the signature data. + function isValidSignature(bytes32 hash, bytes calldata signature) external view virtual override returns (bytes4) { + // Handle potential ERC7739 support detection request + if (signature.length == 0) { + // Forces the compiler to optimize for smaller bytecode size. + if (uint256(hash) == (~signature.length / 0xffff) * 0x7739) { + return checkERC7739Support(hash, signature); + } + } + // else proceed with normal signature verification + + // First 20 bytes of data will be validator address and rest of the bytes is complete signature. + address validator = address(bytes20(signature[0:20])); + require(_isValidatorInstalled(validator), ValidatorNotInstalled(validator)); + try IValidator(validator).isValidSignatureWithSender(msg.sender, hash, signature[20:]) returns (bytes4 res) { + return res; + } catch { + return bytes4(0xffffffff); + } + } + + /// @notice Retrieves the address of the current implementation from the EIP-1967 slot. + /// @notice Checks the 1967 implementation slot, if not found then checks the slot defined by address (Biconomy V2 smart account) + /// @return implementation The address of the current contract implementation. + function getImplementation() external view returns (address implementation) { + assembly { + implementation := sload(_ERC1967_IMPLEMENTATION_SLOT) + } + if (implementation == address(0)) { + assembly { + implementation := sload(address()) + } + } + } + + /// @notice Checks if a specific module type is supported by this smart account. + /// @param moduleTypeId The identifier of the module type to check. + /// @return True if the module type is supported, false otherwise. + function supportsModule(uint256 moduleTypeId) external view virtual returns (bool) { + if (moduleTypeId == MODULE_TYPE_VALIDATOR) return true; + else if (moduleTypeId == MODULE_TYPE_EXECUTOR) return true; + else if (moduleTypeId == MODULE_TYPE_FALLBACK) return true; + else if (moduleTypeId == MODULE_TYPE_HOOK) return true; + else if (moduleTypeId == MODULE_TYPE_MULTI) return true; + else return false; + } + + /// @notice Determines if a specific execution mode is supported. + /// @param mode The execution mode to evaluate. + /// @return isSupported True if the execution mode is supported, false otherwise. + function supportsExecutionMode(ExecutionMode mode) external view virtual returns (bool isSupported) { + (CallType callType, ExecType execType) = mode.decodeBasic(); + + // Return true if both the call type and execution type are supported. + return + (callType == CALLTYPE_SINGLE || callType == CALLTYPE_BATCH || callType == CALLTYPE_DELEGATECALL) && + (execType == EXECTYPE_DEFAULT || execType == EXECTYPE_TRY); + } + + /// @notice Determines whether a module is installed on the smart account. + /// @param moduleTypeId The ID corresponding to the type of module (Validator, Executor, Fallback, Hook). + /// @param module The address of the module to check. + /// @param additionalContext Optional context that may be needed for certain checks. + /// @return True if the module is installed, false otherwise. + function isModuleInstalled(uint256 moduleTypeId, address module, bytes calldata additionalContext) external view returns (bool) { + return _isModuleInstalled(moduleTypeId, module, additionalContext); + } + + /// @dev EIP712 hashTypedData method. + function hashTypedData(bytes32 structHash) external view returns (bytes32) { + return _hashTypedData(structHash); + } + + /// @dev EIP712 domain separator. + // solhint-disable func-name-mixedcase + function DOMAIN_SEPARATOR() external view returns (bytes32) { + return _domainSeparator(); + } + + /// Returns the account's implementation ID. + /// @return The unique identifier for this account implementation. + function accountId() external pure virtual returns (string memory) { + return _ACCOUNT_IMPLEMENTATION_ID; + } + + /// Upgrades the contract to a new implementation and calls a function on the new contract. + /// @notice Updates two slots 1. ERC1967 slot and + /// 2. address() slot in case if it's potentially upgraded earlier from Biconomy V2 account, + /// as Biconomy v2 Account (proxy) reads implementation from the slot that is defined by its address + /// @param newImplementation The address of the new contract implementation. + /// @param data The calldata to be sent to the new implementation. + function upgradeToAndCall(address newImplementation, bytes calldata data) public payable virtual override withHook { + require(newImplementation != address(0), InvalidImplementationAddress()); + bool res; + assembly { + res := gt(extcodesize(newImplementation), 0) + } + require(res, InvalidImplementationAddress()); + // update the address() storage slot as well. + assembly { + sstore(address(), newImplementation) + } + UUPSUpgradeable.upgradeToAndCall(newImplementation, data); + } + + /// @dev For automatic detection that the smart account supports the ERC7739 workflow + /// Iterates over all the validators but only if this is a detection request + /// ERC-7739 spec assumes that if the account doesn't support ERC-7739 + /// it will try to handle the detection request as it was normal sig verification + /// request and will return 0xffffffff since it won't be able to verify the 0x signature + /// against 0x7739...7739 hash. + /// So this approach is consistent with the ERC-7739 spec. + /// If no validator supports ERC-7739, this function returns false + /// thus the account will proceed with normal signature verification + /// and return 0xffffffff as a result. + function checkERC7739Support(bytes32 hash, bytes calldata signature) public view virtual returns (bytes4) { + bytes4 result; + unchecked { + SentinelListLib.SentinelList storage validators = _getAccountStorage().validators; + address next = validators.entries[SENTINEL]; + while (next != ZERO_ADDRESS && next != SENTINEL) { + bytes4 support = IValidator(next).isValidSignatureWithSender(msg.sender, hash, signature); + if (bytes2(support) == bytes2(SUPPORTS_ERC7739) && support > result) { + result = support; + } + next = validators.getNext(next); + } + } + return result == bytes4(0) ? bytes4(0xffffffff) : result; + } + + /// @dev Ensures that only authorized callers can upgrade the smart contract implementation. + /// This is part of the UUPS (Universal Upgradeable Proxy Standard) pattern. + /// @param newImplementation The address of the new implementation to upgrade to. + function _authorizeUpgrade(address newImplementation) internal virtual override(UUPSUpgradeable) onlyEntryPointOrSelf {} + + /// @dev EIP712 domain name and version. + function _domainNameAndVersion() internal pure override returns (string memory name, string memory version) { + name = "Nexus"; + version = "1.0.2"; + } +} diff --git a/biconomy/nexus/1.0.2/contracts/base/BaseAccount.sol b/biconomy/nexus/1.0.2/contracts/base/BaseAccount.sol new file mode 100644 index 0000000..7a2fd52 --- /dev/null +++ b/biconomy/nexus/1.0.2/contracts/base/BaseAccount.sol @@ -0,0 +1,130 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.27; + +// ────────────────────────────────────────────────────────────────────────────── +// _ __ _ __ +// / | / /__ | |/ /_ _______ +// / |/ / _ \| / / / / ___/ +// / /| / __/ / /_/ (__ ) +// /_/ |_/\___/_/|_\__,_/____/ +// +// ────────────────────────────────────────────────────────────────────────────── +// Nexus: A suite of contracts for Modular Smart Accounts compliant with ERC-7579 and ERC-4337, developed by Biconomy. +// Learn more at https://biconomy.io. To report security issues, please contact us at: security@biconomy.io + +import { IEntryPoint } from "account-abstraction/interfaces/IEntryPoint.sol"; +import { IBaseAccount } from "../interfaces/base/IBaseAccount.sol"; + +/// @title Nexus - BaseAccount +/// @notice Implements ERC-4337 and ERC-7579 standards for account management and access control within the Nexus suite. +/// @dev Manages entry points and configurations as specified in the ERC-4337 and ERC-7579 documentation. +/// @author @livingrockrises | Biconomy | chirag@biconomy.io +/// @author @aboudjem | Biconomy | adam.boudjemaa@biconomy.io +/// @author @filmakarov | Biconomy | filipp.makarov@biconomy.io +/// @author @zeroknots | Rhinestone.wtf | zeroknots.eth +/// Special thanks to the Solady team for foundational contributions: https://github.com/Vectorized/solady +contract BaseAccount is IBaseAccount { + /// @notice Identifier for this implementation on the network + string internal constant _ACCOUNT_IMPLEMENTATION_ID = "biconomy.nexus.1.0.2"; + + /// @notice The canonical address for the ERC4337 EntryPoint contract, version 0.7. + /// This address is consistent across all supported networks. + address internal immutable _ENTRYPOINT; + + /// @dev Ensures the caller is either the EntryPoint or this account itself. + /// Reverts with AccountAccessUnauthorized if the check fails. + modifier onlyEntryPointOrSelf() { + require(msg.sender == _ENTRYPOINT || msg.sender == address(this), AccountAccessUnauthorized()); + _; + } + + /// @dev Ensures the caller is the EntryPoint. + /// Reverts with AccountAccessUnauthorized if the check fails. + modifier onlyEntryPoint() { + require(msg.sender == _ENTRYPOINT, AccountAccessUnauthorized()); + _; + } + + /// @dev Sends to the EntryPoint (i.e. `msg.sender`) the missing funds for this transaction. + /// Subclass MAY override this modifier for better funds management. + /// (e.g. send to the EntryPoint more than the minimum required, so that in future transactions + /// it will not be required to send again) + /// + /// `missingAccountFunds` is the minimum value this modifier should send the EntryPoint, + /// which MAY be zero, in case there is enough deposit, or the userOp has a paymaster. + modifier payPrefund(uint256 missingAccountFunds) virtual { + _; + /// @solidity memory-safe-assembly + assembly { + if missingAccountFunds { + // Ignore failure (it's EntryPoint's job to verify, not the account's). + pop(call(gas(), caller(), missingAccountFunds, codesize(), 0x00, codesize(), 0x00)) + } + } + } + + /// @notice Adds deposit to the EntryPoint to fund transactions. + function addDeposit() external payable virtual { + address entryPointAddress = _ENTRYPOINT; + /// @solidity memory-safe-assembly + assembly { + // The EntryPoint has balance accounting logic in the `receive()` function. + if iszero(call(gas(), entryPointAddress, callvalue(), codesize(), 0x00, codesize(), 0x00)) { + revert(codesize(), 0x00) + } // For gas estimation. + } + } + + /// @notice Withdraws ETH from the EntryPoint to a specified address. + /// @param to The address to receive the withdrawn funds. + /// @param amount The amount to withdraw. + function withdrawDepositTo(address to, uint256 amount) external payable virtual onlyEntryPointOrSelf { + address entryPointAddress = _ENTRYPOINT; + assembly { + let freeMemPtr := mload(0x40) // Store the free memory pointer. + mstore(0x14, to) // Store the `to` argument. + mstore(0x34, amount) // Store the `amount` argument. + mstore(0x00, 0x205c2878000000000000000000000000) // `withdrawTo(address,uint256)`. + if iszero(call(gas(), entryPointAddress, 0, 0x10, 0x44, codesize(), 0x00)) { + returndatacopy(freeMemPtr, 0x00, returndatasize()) + revert(freeMemPtr, returndatasize()) + } + mstore(0x34, 0) // Restore the part of the free memory pointer that was overwritten. + } + } + + /// @notice Gets the nonce for a particular key. + /// @param key The nonce key. + /// @return The nonce associated with the key. + function nonce(uint192 key) external view virtual returns (uint256) { + return IEntryPoint(_ENTRYPOINT).getNonce(address(this), key); + } + + /// @notice Returns the current deposit balance of this account on the EntryPoint. + /// @return result The current balance held at the EntryPoint. + function getDeposit() external view virtual returns (uint256 result) { + address entryPointAddress = _ENTRYPOINT; + /// @solidity memory-safe-assembly + assembly { + mstore(0x20, address()) // Store the `account` argument. + mstore(0x00, 0x70a08231) // `balanceOf(address)`. + result := mul( + // Returns 0 if the EntryPoint does not exist. + mload(0x20), + and( + // The arguments of `and` are evaluated from right to left. + gt(returndatasize(), 0x1f), // At least 32 bytes returned. + staticcall(gas(), entryPointAddress, 0x1c, 0x24, 0x20, 0x20) + ) + ) + } + } + + /// @notice Retrieves the address of the EntryPoint contract, currently using version 0.7. + /// @dev This function returns the address of the canonical ERC4337 EntryPoint contract. + /// It can be overridden to return a different EntryPoint address if needed. + /// @return The address of the EntryPoint contract. + function entryPoint() external view returns (address) { + return _ENTRYPOINT; + } +} diff --git a/biconomy/nexus/1.0.2/contracts/base/ExecutionHelper.sol b/biconomy/nexus/1.0.2/contracts/base/ExecutionHelper.sol new file mode 100644 index 0000000..5158b16 --- /dev/null +++ b/biconomy/nexus/1.0.2/contracts/base/ExecutionHelper.sol @@ -0,0 +1,266 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.27; + +// ────────────────────────────────────────────────────────────────────────────── +// _ __ _ __ +// / | / /__ | |/ /_ _______ +// / |/ / _ \| / / / / ___/ +// / /| / __/ / /_/ (__ ) +// /_/ |_/\___/_/|_\__,_/____/ +// +// ────────────────────────────────────────────────────────────────────────────── +// Nexus: A suite of contracts for Modular Smart Accounts compliant with ERC-7579 and ERC-4337, developed by Biconomy. +// Learn more at https://biconomy.io. To report security issues, please contact us at: security@biconomy.io + +import { Execution } from "../types/DataTypes.sol"; +import { IExecutionHelperEventsAndErrors } from "../interfaces/base/IExecutionHelper.sol"; +import { ExecType, EXECTYPE_DEFAULT, EXECTYPE_TRY } from "../lib/ModeLib.sol"; +import { ExecLib } from "../lib/ExecLib.sol"; + +/// @title Nexus - ExecutionHelper +/// @notice Implements execution management within the Nexus suite, facilitating transaction execution strategies and +/// error handling. +/// @dev Provides mechanisms for direct and batched transactions with both committed and tentative execution strategies +/// as per ERC-4337 and ERC-7579 standards. +/// @author @livingrockrises | Biconomy | chirag@biconomy.io +/// @author @aboudjem | Biconomy | adam.boudjemaa@biconomy.io +/// @author @filmakarov | Biconomy | filipp.makarov@biconomy.io +/// @author @zeroknots | Rhinestone.wtf | zeroknots.eth +/// Special thanks to the Solady team for foundational contributions: https://github.com/Vectorized/solady +contract ExecutionHelper is IExecutionHelperEventsAndErrors { + using ExecLib for bytes; + + /// @notice Executes a call to a target address with specified value and data. + /// @notice calls to an EOA should be counted as successful. + /// @param target The address to execute the call on. + /// @param value The amount of wei to send with the call. + /// @param callData The calldata to send. + /// @return result The bytes returned from the execution, which contains the returned data from the target address. + function _execute(address target, uint256 value, bytes calldata callData) internal virtual returns (bytes memory result) { + /// @solidity memory-safe-assembly + assembly { + result := mload(0x40) + calldatacopy(result, callData.offset, callData.length) + if iszero(call(gas(), target, value, result, callData.length, codesize(), 0x00)) { + // Bubble up the revert if the call reverts. + returndatacopy(result, 0x00, returndatasize()) + revert(result, returndatasize()) + } + mstore(result, returndatasize()) // Store the length. + let o := add(result, 0x20) + returndatacopy(o, 0x00, returndatasize()) // Copy the returndata. + mstore(0x40, add(o, returndatasize())) // Allocate the memory. + } + } + + /// @notice Executes a call to a target address with specified value and data. + /// Same as _execute but without return data for gas optimization. + function _executeNoReturndata(address target, uint256 value, bytes calldata callData) internal virtual { + /// @solidity memory-safe-assembly + assembly { + let result := mload(0x40) + calldatacopy(result, callData.offset, callData.length) + if iszero(call(gas(), target, value, result, callData.length, codesize(), 0x00)) { + // Bubble up the revert if the call reverts. + returndatacopy(result, 0x00, returndatasize()) + revert(result, returndatasize()) + } + mstore(0x40, add(result, callData.length)) //allocate memory + } + } + + /// @notice Tries to execute a call and captures if it was successful or not. + /// @dev Similar to _execute but returns a success boolean and catches reverts instead of propagating them. + /// @notice calls to an EOA should be counted as successful. + /// @param target The address to execute the call on. + /// @param value The amount of wei to send with the call. + /// @param callData The calldata to send. + /// @return success True if the execution was successful, false otherwise. + /// @return result The bytes returned from the execution, which contains the returned data from the target address. + function _tryExecute(address target, uint256 value, bytes calldata callData) internal virtual returns (bool success, bytes memory result) { + /// @solidity memory-safe-assembly + assembly { + result := mload(0x40) + calldatacopy(result, callData.offset, callData.length) + success := call(gas(), target, value, result, callData.length, codesize(), 0x00) + mstore(result, returndatasize()) // Store the length. + let o := add(result, 0x20) + returndatacopy(o, 0x00, returndatasize()) // Copy the returndata. + mstore(0x40, add(o, returndatasize())) // Allocate the memory. + } + } + + /// @notice Executes a batch of calls. + /// @param executions An array of Execution structs each containing target, value, and calldata. + /// @return result An array of bytes returned from each executed call, corresponding to the returndata from each target address. + function _executeBatch(Execution[] calldata executions) internal returns (bytes[] memory result) { + result = new bytes[](executions.length); + + Execution calldata exec; + for (uint256 i; i < executions.length; i++) { + exec = executions[i]; + result[i] = _execute(exec.target, exec.value, exec.callData); + } + } + + /// @notice Executes a batch of calls without returning the result. + /// @param executions An array of Execution structs each containing target, value, and calldata. + function _executeBatchNoReturndata(Execution[] calldata executions) internal { + Execution calldata exec; + for (uint256 i; i < executions.length; i++) { + exec = executions[i]; + _executeNoReturndata(exec.target, exec.value, exec.callData); + } + } + + /// @notice Tries to execute a batch of calls and emits an event for each unsuccessful call. + /// @param executions An array of Execution structs. + /// @return result An array of bytes returned from each executed call, with unsuccessful calls marked by events. + function _tryExecuteBatch(Execution[] calldata executions) internal returns (bytes[] memory result) { + result = new bytes[](executions.length); + + Execution calldata exec; + for (uint256 i; i < executions.length; i++) { + exec = executions[i]; + bool success; + (success, result[i]) = _tryExecute(exec.target, exec.value, exec.callData); + if (!success) emit TryExecuteUnsuccessful(exec.callData, result[i]); + } + } + + /// @dev Execute a delegatecall with `delegate` on this account. + /// @return result The bytes returned from the delegatecall, which contains the returned data from the delegate contract. + function _executeDelegatecall(address delegate, bytes calldata callData) internal returns (bytes memory result) { + /// @solidity memory-safe-assembly + assembly { + result := mload(0x40) + calldatacopy(result, callData.offset, callData.length) + // Forwards the `data` to `delegate` via delegatecall. + if iszero(delegatecall(gas(), delegate, result, callData.length, codesize(), 0x00)) { + // Bubble up the revert if the call reverts. + returndatacopy(result, 0x00, returndatasize()) + revert(result, returndatasize()) + } + mstore(result, returndatasize()) // Store the length. + let o := add(result, 0x20) + returndatacopy(o, 0x00, returndatasize()) // Copy the returndata. + mstore(0x40, add(o, returndatasize())) // Allocate the memory. + } + } + + /// @dev Execute a delegatecall with `delegate` on this account. + /// Same as _executeDelegatecall but without return data for gas optimization. + function _executeDelegatecallNoReturndata(address delegate, bytes calldata callData) internal { + /// @solidity memory-safe-assembly + assembly { + let result := mload(0x40) + calldatacopy(result, callData.offset, callData.length) + if iszero(delegatecall(gas(), delegate, result, callData.length, codesize(), 0x00)) { + // Bubble up the revert if the call reverts. + returndatacopy(result, 0x00, returndatasize()) + revert(result, returndatasize()) + } + mstore(0x40, add(result, callData.length)) //allocate memory + } + } + + /// @dev Execute a delegatecall with `delegate` on this account and catch reverts. + /// @return success True if the delegatecall was successful, false otherwise. + /// @return result The bytes returned from the delegatecall, which contains the returned data from the delegate contract. + function _tryExecuteDelegatecall(address delegate, bytes calldata callData) internal returns (bool success, bytes memory result) { + /// @solidity memory-safe-assembly + assembly { + result := mload(0x40) + calldatacopy(result, callData.offset, callData.length) + // Forwards the `data` to `delegate` via delegatecall. + success := delegatecall(gas(), delegate, result, callData.length, codesize(), 0x00) + mstore(result, returndatasize()) // Store the length. + let o := add(result, 0x20) + returndatacopy(o, 0x00, returndatasize()) // Copy the returndata. + mstore(0x40, add(o, returndatasize())) // Allocate the memory. + } + } + + /// @dev Executes a single transaction based on the specified execution type. + /// @param executionCalldata The calldata containing the transaction details (target address, value, and data). + /// @param execType The execution type, which can be DEFAULT (revert on failure) or TRY (return on failure). + function _handleSingleExecution(bytes calldata executionCalldata, ExecType execType) internal { + (address target, uint256 value, bytes calldata callData) = executionCalldata.decodeSingle(); + if (execType == EXECTYPE_DEFAULT) _executeNoReturndata(target, value, callData); + else if (execType == EXECTYPE_TRY) { + (bool success, bytes memory result) = _tryExecute(target, value, callData); + if (!success) emit TryExecuteUnsuccessful(callData, result); + } else revert UnsupportedExecType(execType); + } + + /// @dev Executes a batch of transactions based on the specified execution type. + /// @param executionCalldata The calldata for a batch of transactions. + /// @param execType The execution type, which can be DEFAULT (revert on failure) or TRY (return on failure). + function _handleBatchExecution(bytes calldata executionCalldata, ExecType execType) internal { + Execution[] calldata executions = executionCalldata.decodeBatch(); + if (execType == EXECTYPE_DEFAULT) _executeBatchNoReturndata(executions); + else if (execType == EXECTYPE_TRY) _tryExecuteBatch(executions); + else revert UnsupportedExecType(execType); + } + + /// @dev Executes a single transaction based on the specified execution type. + /// @param executionCalldata The calldata containing the transaction details (target address, value, and data). + /// @param execType The execution type, which can be DEFAULT (revert on failure) or TRY (return on failure). + function _handleDelegateCallExecution(bytes calldata executionCalldata, ExecType execType) internal { + (address delegate, bytes calldata callData) = executionCalldata.decodeDelegateCall(); + if (execType == EXECTYPE_DEFAULT) _executeDelegatecallNoReturndata(delegate, callData); + else if (execType == EXECTYPE_TRY) { + (bool success, bytes memory result) = _tryExecuteDelegatecall(delegate, callData); + if (!success) emit TryDelegateCallUnsuccessful(callData, result); + } else revert UnsupportedExecType(execType); + } + + /// @dev Executes a single transaction based on the specified execution type. + /// @param executionCalldata The calldata containing the transaction details (target address, value, and data). + /// @param execType The execution type, which can be DEFAULT (revert on failure) or TRY (return on failure). + /// @return returnData An array containing the execution result. In the case of a single transaction, the array contains one element. + function _handleSingleExecutionAndReturnData(bytes calldata executionCalldata, ExecType execType) internal returns (bytes[] memory returnData) { + (address target, uint256 value, bytes calldata callData) = executionCalldata.decodeSingle(); + returnData = new bytes[](1); + bool success; + // check if execType is revert(default) or try + if (execType == EXECTYPE_DEFAULT) { + returnData[0] = _execute(target, value, callData); + } else if (execType == EXECTYPE_TRY) { + (success, returnData[0]) = _tryExecute(target, value, callData); + if (!success) emit TryExecuteUnsuccessful(callData, returnData[0]); + } else { + revert UnsupportedExecType(execType); + } + } + + /// @dev Executes a batch of transactions based on the specified execution type. + /// @param executionCalldata The calldata for a batch of transactions. + /// @param execType The execution type, which can be DEFAULT (revert on failure) or TRY (return on failure). + /// @return returnData An array containing the execution results for each transaction in the batch. + function _handleBatchExecutionAndReturnData(bytes calldata executionCalldata, ExecType execType) internal returns (bytes[] memory returnData) { + Execution[] calldata executions = executionCalldata.decodeBatch(); + if (execType == EXECTYPE_DEFAULT) returnData = _executeBatch(executions); + else if (execType == EXECTYPE_TRY) returnData = _tryExecuteBatch(executions); + else revert UnsupportedExecType(execType); + } + + /// @dev Executes a single transaction based on the specified execution type. + /// @param executionCalldata The calldata containing the transaction details (target address, value, and data). + /// @param execType The execution type, which can be DEFAULT (revert on failure) or TRY (return on failure). + /// @return returnData An array containing the result of the delegatecall execution. + function _handleDelegateCallExecutionAndReturnData( + bytes calldata executionCalldata, + ExecType execType + ) internal returns (bytes[] memory returnData) { + (address delegate, bytes calldata callData) = executionCalldata.decodeDelegateCall(); + returnData = new bytes[](1); + bool success; + if (execType == EXECTYPE_DEFAULT) { + returnData[0] = _executeDelegatecall(delegate, callData); + } else if (execType == EXECTYPE_TRY) { + (success, returnData[0]) = _tryExecuteDelegatecall(delegate, callData); + if (!success) emit TryDelegateCallUnsuccessful(callData, returnData[0]); + } else revert UnsupportedExecType(execType); + } +} diff --git a/biconomy/nexus/1.0.2/contracts/base/ModuleManager.sol b/biconomy/nexus/1.0.2/contracts/base/ModuleManager.sol new file mode 100644 index 0000000..9197132 --- /dev/null +++ b/biconomy/nexus/1.0.2/contracts/base/ModuleManager.sol @@ -0,0 +1,500 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.27; + +// ────────────────────────────────────────────────────────────────────────────── +// _ __ _ __ +// / | / /__ | |/ /_ _______ +// / |/ / _ \| / / / / ___/ +// / /| / __/ / /_/ (__ ) +// /_/ |_/\___/_/|_\__,_/____/ +// +// ────────────────────────────────────────────────────────────────────────────── +// Nexus: A suite of contracts for Modular Smart Accounts compliant with ERC-7579 and ERC-4337, developed by Biconomy. +// Learn more at https://biconomy.io. To report security issues, please contact us at: security@biconomy.io + +import { SentinelListLib } from "sentinellist/SentinelList.sol"; +import { Storage } from "./Storage.sol"; +import { IHook } from "../interfaces/modules/IHook.sol"; +import { IModule } from "../interfaces/modules/IModule.sol"; +import { IExecutor } from "../interfaces/modules/IExecutor.sol"; +import { IFallback } from "../interfaces/modules/IFallback.sol"; +import { IValidator } from "../interfaces/modules/IValidator.sol"; +import { CallType, CALLTYPE_SINGLE, CALLTYPE_STATIC } from "../lib/ModeLib.sol"; +import { ExecLib } from "../lib/ExecLib.sol"; +import { LocalCallDataParserLib } from "../lib/local/LocalCallDataParserLib.sol"; +import { IModuleManagerEventsAndErrors } from "../interfaces/base/IModuleManagerEventsAndErrors.sol"; +import { MODULE_TYPE_VALIDATOR, MODULE_TYPE_EXECUTOR, MODULE_TYPE_FALLBACK, MODULE_TYPE_HOOK, MODULE_TYPE_MULTI, MODULE_ENABLE_MODE_TYPE_HASH, ERC1271_MAGICVALUE } from "../types/Constants.sol"; +import { EIP712 } from "solady/utils/EIP712.sol"; +import { ExcessivelySafeCall } from "excessively-safe-call/ExcessivelySafeCall.sol"; +import { RegistryAdapter } from "./RegistryAdapter.sol"; + +/// @title Nexus - ModuleManager +/// @notice Manages Validator, Executor, Hook, and Fallback modules within the Nexus suite, supporting +/// @dev Implements SentinelList for managing modules via a linked list structure, adhering to ERC-7579. +/// @author @livingrockrises | Biconomy | chirag@biconomy.io +/// @author @aboudjem | Biconomy | adam.boudjemaa@biconomy.io +/// @author @filmakarov | Biconomy | filipp.makarov@biconomy.io +/// @author @zeroknots | Rhinestone.wtf | zeroknots.eth +/// Special thanks to the Solady team for foundational contributions: https://github.com/Vectorized/solady +abstract contract ModuleManager is Storage, EIP712, IModuleManagerEventsAndErrors, RegistryAdapter { + using SentinelListLib for SentinelListLib.SentinelList; + using LocalCallDataParserLib for bytes; + using ExecLib for address; + using ExcessivelySafeCall for address; + + /// @notice Ensures the message sender is a registered executor module. + modifier onlyExecutorModule() virtual { + require(_getAccountStorage().executors.contains(msg.sender), InvalidModule(msg.sender)); + _; + } + + /// @notice Does pre-checks and post-checks using an installed hook on the account. + /// @dev sender, msg.data and msg.value is passed to the hook to implement custom flows. + modifier withHook() { + address hook = _getHook(); + if (hook == address(0)) { + _; + } else { + bytes memory hookData = IHook(hook).preCheck(msg.sender, msg.value, msg.data); + _; + IHook(hook).postCheck(hookData); + } + } + + receive() external payable {} + + /// @dev Fallback function to manage incoming calls using designated handlers based on the call type. + /// Hooked manually in the _fallback function + fallback() external payable { + _fallback(msg.data); + } + + /// @dev Retrieves a paginated list of validator addresses from the linked list. + /// This utility function is not defined by the ERC-7579 standard and is implemented to facilitate + /// easier management and retrieval of large sets of validator modules. + /// @param cursor The address to start pagination from, or zero to start from the first entry. + /// @param size The number of validator addresses to return. + /// @return array An array of validator addresses. + /// @return next The address to use as a cursor for the next page of results. + function getValidatorsPaginated(address cursor, uint256 size) external view returns (address[] memory array, address next) { + (array, next) = _paginate(_getAccountStorage().validators, cursor, size); + } + + /// @dev Retrieves a paginated list of executor addresses from the linked list. + /// This utility function is not defined by the ERC-7579 standard and is implemented to facilitate + /// easier management and retrieval of large sets of executor modules. + /// @param cursor The address to start pagination from, or zero to start from the first entry. + /// @param size The number of executor addresses to return. + /// @return array An array of executor addresses. + /// @return next The address to use as a cursor for the next page of results. + function getExecutorsPaginated(address cursor, uint256 size) external view returns (address[] memory array, address next) { + (array, next) = _paginate(_getAccountStorage().executors, cursor, size); + } + + /// @notice Retrieves the currently active hook address. + /// @return hook The address of the active hook module. + function getActiveHook() external view returns (address hook) { + return _getHook(); + } + + /// @notice Fetches the fallback handler for a specific selector. + /// @param selector The function selector to query. + /// @return calltype The type of call that the handler manages. + /// @return handler The address of the fallback handler. + function getFallbackHandlerBySelector(bytes4 selector) external view returns (CallType, address) { + FallbackHandler memory handler = _getAccountStorage().fallbacks[selector]; + return (handler.calltype, handler.handler); + } + + /// @dev Initializes the module manager by setting up default states for validators and executors. + function _initModuleManager() internal virtual { + // account module storage + AccountStorage storage ams = _getAccountStorage(); + ams.executors.init(); + ams.validators.init(); + } + + /// @dev Implements Module Enable Mode flow. + /// @param packedData Data source to parse data required to perform Module Enable mode from. + /// @return userOpSignature the clean signature which can be further used for userOp validation + function _enableMode(bytes32 userOpHash, bytes calldata packedData) internal returns (bytes calldata userOpSignature) { + address module; + uint256 moduleType; + bytes calldata moduleInitData; + bytes calldata enableModeSignature; + + (module, moduleType, moduleInitData, enableModeSignature, userOpSignature) = packedData.parseEnableModeData(); + + if (!_checkEnableModeSignature(_getEnableModeDataHash(module, moduleType, userOpHash, moduleInitData), enableModeSignature)) + revert EnableModeSigError(); + + _installModule(moduleType, module, moduleInitData); + } + + /// @notice Installs a new module to the smart account. + /// @param moduleTypeId The type identifier of the module being installed, which determines its role: + /// - 0 for MultiType + /// - 1 for Validator + /// - 2 for Executor + /// - 3 for Fallback + /// - 4 for Hook + /// @param module The address of the module to install. + /// @param initData Initialization data for the module. + /// @dev This function goes through hook checks via withHook modifier. + /// @dev No need to check that the module is already installed, as this check is done + /// when trying to sstore the module in an appropriate SentinelList + function _installModule(uint256 moduleTypeId, address module, bytes calldata initData) internal withHook { + if (module == address(0)) revert ModuleAddressCanNotBeZero(); + if (moduleTypeId == MODULE_TYPE_VALIDATOR) { + _installValidator(module, initData); + } else if (moduleTypeId == MODULE_TYPE_EXECUTOR) { + _installExecutor(module, initData); + } else if (moduleTypeId == MODULE_TYPE_FALLBACK) { + _installFallbackHandler(module, initData); + } else if (moduleTypeId == MODULE_TYPE_HOOK) { + _installHook(module, initData); + } else if (moduleTypeId == MODULE_TYPE_MULTI) { + _multiTypeInstall(module, initData); + } else { + revert InvalidModuleTypeId(moduleTypeId); + } + } + + /// @dev Installs a new validator module after checking if it matches the required module type. + /// @param validator The address of the validator module to be installed. + /// @param data Initialization data to configure the validator upon installation. + function _installValidator(address validator, bytes calldata data) internal virtual withRegistry(validator, MODULE_TYPE_VALIDATOR) { + if (!IValidator(validator).isModuleType(MODULE_TYPE_VALIDATOR)) revert MismatchModuleTypeId(MODULE_TYPE_VALIDATOR); + _getAccountStorage().validators.push(validator); + IValidator(validator).onInstall(data); + } + + /// @dev Uninstalls a validator module /!\ ensuring the account retains at least one validator. + /// @param validator The address of the validator to be uninstalled. + /// @param data De-initialization data to configure the validator upon uninstallation. + function _uninstallValidator(address validator, bytes calldata data) internal virtual { + SentinelListLib.SentinelList storage validators = _getAccountStorage().validators; + + (address prev, bytes memory disableModuleData) = abi.decode(data, (address, bytes)); + + // Perform the removal first + validators.pop(prev, validator); + + // Sentinel pointing to itself / zero means the list is empty / uninitialized, so check this after removal + // Below error is very specific to uninstalling validators. + require(_hasValidators(), CanNotRemoveLastValidator()); + validator.excessivelySafeCall(gasleft(), 0, 0, abi.encodeWithSelector(IModule.onUninstall.selector, disableModuleData)); + } + + /// @dev Installs a new executor module after checking if it matches the required module type. + /// @param executor The address of the executor module to be installed. + /// @param data Initialization data to configure the executor upon installation. + function _installExecutor(address executor, bytes calldata data) internal virtual withRegistry(executor, MODULE_TYPE_EXECUTOR) { + if (!IExecutor(executor).isModuleType(MODULE_TYPE_EXECUTOR)) revert MismatchModuleTypeId(MODULE_TYPE_EXECUTOR); + _getAccountStorage().executors.push(executor); + IExecutor(executor).onInstall(data); + } + + /// @dev Uninstalls an executor module by removing it from the executors list. + /// @param executor The address of the executor to be uninstalled. + /// @param data De-initialization data to configure the executor upon uninstallation. + function _uninstallExecutor(address executor, bytes calldata data) internal virtual { + (address prev, bytes memory disableModuleData) = abi.decode(data, (address, bytes)); + _getAccountStorage().executors.pop(prev, executor); + executor.excessivelySafeCall(gasleft(), 0, 0, abi.encodeWithSelector(IModule.onUninstall.selector, disableModuleData)); + } + + /// @dev Installs a hook module, ensuring no other hooks are installed before proceeding. + /// @param hook The address of the hook to be installed. + /// @param data Initialization data to configure the hook upon installation. + function _installHook(address hook, bytes calldata data) internal virtual withRegistry(hook, MODULE_TYPE_HOOK) { + if (!IHook(hook).isModuleType(MODULE_TYPE_HOOK)) revert MismatchModuleTypeId(MODULE_TYPE_HOOK); + address currentHook = _getHook(); + require(currentHook == address(0), HookAlreadyInstalled(currentHook)); + _setHook(hook); + IHook(hook).onInstall(data); + } + + /// @dev Uninstalls a hook module, ensuring the current hook matches the one intended for uninstallation. + /// @param hook The address of the hook to be uninstalled. + /// @param data De-initialization data to configure the hook upon uninstallation. + function _uninstallHook(address hook, bytes calldata data) internal virtual { + _setHook(address(0)); + hook.excessivelySafeCall(gasleft(), 0, 0, abi.encodeWithSelector(IModule.onUninstall.selector, data)); + } + + /// @dev Sets the current hook in the storage to the specified address. + /// @param hook The new hook address. + function _setHook(address hook) internal virtual { + _getAccountStorage().hook = IHook(hook); + } + + /// @dev Installs a fallback handler for a given selector with initialization data. + /// @param handler The address of the fallback handler to install. + /// @param params The initialization parameters including the selector and call type. + function _installFallbackHandler(address handler, bytes calldata params) internal virtual withRegistry(handler, MODULE_TYPE_FALLBACK) { + if (!IFallback(handler).isModuleType(MODULE_TYPE_FALLBACK)) revert MismatchModuleTypeId(MODULE_TYPE_FALLBACK); + // Extract the function selector from the provided parameters. + bytes4 selector = bytes4(params[0:4]); + + // Extract the call type from the provided parameters. + CallType calltype = CallType.wrap(bytes1(params[4])); + + require(calltype == CALLTYPE_SINGLE || calltype == CALLTYPE_STATIC, FallbackCallTypeInvalid()); + + // Extract the initialization data from the provided parameters. + bytes memory initData = params[5:]; + + // Revert if the selector is either `onInstall(bytes)` (0x6d61fe70) or `onUninstall(bytes)` (0x8a91b0e3) or explicit bytes(0). + // These selectors are explicitly forbidden to prevent security vulnerabilities. + // Allowing these selectors would enable unauthorized users to uninstall and reinstall critical modules. + // If a validator module is uninstalled and reinstalled without proper authorization, it can compromise + // the account's security and integrity. By restricting these selectors, we ensure that the fallback handler + // cannot be manipulated to disrupt the expected behavior and security of the account. + require(!(selector == bytes4(0x6d61fe70) || selector == bytes4(0x8a91b0e3) || selector == bytes4(0)), FallbackSelectorForbidden()); + + // Revert if a fallback handler is already installed for the given selector. + // This check ensures that we do not overwrite an existing fallback handler, which could lead to unexpected behavior. + require(!_isFallbackHandlerInstalled(selector), FallbackAlreadyInstalledForSelector(selector)); + + // Store the fallback handler and its call type in the account storage. + // This maps the function selector to the specified fallback handler and call type. + _getAccountStorage().fallbacks[selector] = FallbackHandler(handler, calltype); + + // Invoke the `onInstall` function of the fallback handler with the provided initialization data. + // This step allows the fallback handler to perform any necessary setup or initialization. + IFallback(handler).onInstall(initData); + } + + /// @dev Uninstalls a fallback handler for a given selector. + /// @param fallbackHandler The address of the fallback handler to uninstall. + /// @param data The de-initialization data containing the selector. + function _uninstallFallbackHandler(address fallbackHandler, bytes calldata data) internal virtual { + _getAccountStorage().fallbacks[bytes4(data[0:4])] = FallbackHandler(address(0), CallType.wrap(0x00)); + fallbackHandler.excessivelySafeCall(gasleft(), 0, 0, abi.encodeWithSelector(IModule.onUninstall.selector, data[4:])); + } + + /// @notice Installs a module with multiple types in a single operation. + /// @dev This function handles installing a multi-type module by iterating through each type and initializing it. + /// The initData should include an ABI-encoded tuple of (uint[] types, bytes[] initDatas). + /// @param module The address of the multi-type module. + /// @param initData Initialization data for each type within the module. + function _multiTypeInstall(address module, bytes calldata initData) internal virtual { + (uint256[] calldata types, bytes[] calldata initDatas) = initData.parseMultiTypeInitData(); + + uint256 length = types.length; + if (initDatas.length != length) revert InvalidInput(); + + // iterate over all module types and install the module as a type accordingly + for (uint256 i; i < length; i++) { + uint256 theType = types[i]; + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* INSTALL VALIDATORS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + if (theType == MODULE_TYPE_VALIDATOR) { + _installValidator(module, initDatas[i]); + } + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* INSTALL EXECUTORS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + else if (theType == MODULE_TYPE_EXECUTOR) { + _installExecutor(module, initDatas[i]); + } + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* INSTALL FALLBACK */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + else if (theType == MODULE_TYPE_FALLBACK) { + _installFallbackHandler(module, initDatas[i]); + } + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* INSTALL HOOK (global only, not sig-specific) */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + else if (theType == MODULE_TYPE_HOOK) { + _installHook(module, initDatas[i]); + } + } + } + + /// @notice Checks if an enable mode signature is valid. + /// @param structHash data hash. + /// @param sig Signature. + function _checkEnableModeSignature(bytes32 structHash, bytes calldata sig) internal view returns (bool) { + address enableModeSigValidator = address(bytes20(sig[0:20])); + if (!_isValidatorInstalled(enableModeSigValidator)) { + revert ValidatorNotInstalled(enableModeSigValidator); + } + bytes32 eip712Digest = _hashTypedData(structHash); + + // Use standard IERC-1271/ERC-7739 interface. + // Even if the validator doesn't support 7739 under the hood, it is still secure, + // as eip712digest is already built based on 712Domain of this Smart Account + // This interface should always be exposed by validators as per ERC-7579 + try IValidator(enableModeSigValidator).isValidSignatureWithSender(address(this), eip712Digest, sig[20:]) returns (bytes4 res) { + return res == ERC1271_MAGICVALUE; + } catch { + return false; + } + } + + /// @notice Builds the enable mode data hash as per eip712 + /// @param module Module being enabled + /// @param moduleType Type of the module as per EIP-7579 + /// @param userOpHash Hash of the User Operation + /// @param initData Module init data. + /// @return structHash data hash + function _getEnableModeDataHash(address module, uint256 moduleType, bytes32 userOpHash, bytes calldata initData) internal view returns (bytes32) { + return keccak256(abi.encode(MODULE_ENABLE_MODE_TYPE_HASH, module, moduleType, userOpHash, keccak256(initData))); + } + + /// @notice Checks if a module is installed on the smart account. + /// @param moduleTypeId The module type ID. + /// @param module The module address. + /// @param additionalContext Additional context for checking installation. + /// @return True if the module is installed, false otherwise. + function _isModuleInstalled(uint256 moduleTypeId, address module, bytes calldata additionalContext) internal view returns (bool) { + additionalContext; + if (moduleTypeId == MODULE_TYPE_VALIDATOR) { + return _isValidatorInstalled(module); + } else if (moduleTypeId == MODULE_TYPE_EXECUTOR) { + return _isExecutorInstalled(module); + } else if (moduleTypeId == MODULE_TYPE_FALLBACK) { + bytes4 selector; + if (additionalContext.length >= 4) { + selector = bytes4(additionalContext[0:4]); + } else { + selector = bytes4(0x00000000); + } + return _isFallbackHandlerInstalled(selector, module); + } else if (moduleTypeId == MODULE_TYPE_HOOK) { + return _isHookInstalled(module); + } else { + return false; + } + } + + /// @dev Checks if a fallback handler is set for a given selector. + /// @param selector The function selector to check. + /// @return True if a fallback handler is set, otherwise false. + function _isFallbackHandlerInstalled(bytes4 selector) internal view virtual returns (bool) { + FallbackHandler storage handler = _getAccountStorage().fallbacks[selector]; + return handler.handler != address(0); + } + + /// @dev Checks if the expected fallback handler is installed for a given selector. + /// @param selector The function selector to check. + /// @param expectedHandler The address of the handler expected to be installed. + /// @return True if the installed handler matches the expected handler, otherwise false. + function _isFallbackHandlerInstalled(bytes4 selector, address expectedHandler) internal view returns (bool) { + FallbackHandler storage handler = _getAccountStorage().fallbacks[selector]; + return handler.handler == expectedHandler; + } + + /// @dev Checks if a validator is currently installed. + /// @param validator The address of the validator to check. + /// @return True if the validator is installed, otherwise false. + function _isValidatorInstalled(address validator) internal view virtual returns (bool) { + return _getAccountStorage().validators.contains(validator); + } + + /// @dev Checks if there is at least one validator installed. + /// @return True if there is at least one validator, otherwise false. + function _hasValidators() internal view returns (bool) { + return + _getAccountStorage().validators.getNext(address(0x01)) != address(0x01) && + _getAccountStorage().validators.getNext(address(0x01)) != address(0x00); + } + + /// @dev Checks if an executor is currently installed. + /// @param executor The address of the executor to check. + /// @return True if the executor is installed, otherwise false. + function _isExecutorInstalled(address executor) internal view virtual returns (bool) { + return _getAccountStorage().executors.contains(executor); + } + + /// @dev Checks if a hook is currently installed. + /// @param hook The address of the hook to check. + /// @return True if the hook is installed, otherwise false. + function _isHookInstalled(address hook) internal view returns (bool) { + return _getHook() == hook; + } + + /// @dev Retrieves the current hook from the storage. + /// @return hook The address of the current hook. + function _getHook() internal view returns (address hook) { + hook = address(_getAccountStorage().hook); + } + + function _fallback(bytes calldata callData) private { + bool success; + bytes memory result; + FallbackHandler storage $fallbackHandler = _getAccountStorage().fallbacks[msg.sig]; + address handler = $fallbackHandler.handler; + CallType calltype = $fallbackHandler.calltype; + + if (handler != address(0)) { + // hook manually + address hook = _getHook(); + bytes memory hookData; + if (hook != address(0)) { + hookData = IHook(hook).preCheck(msg.sender, msg.value, msg.data); + } + //if there's a fallback handler, call it + if (calltype == CALLTYPE_STATIC) { + (success, result) = handler.staticcall(ExecLib.get2771CallData(callData)); + } else if (calltype == CALLTYPE_SINGLE) { + (success, result) = handler.call{ value: msg.value }(ExecLib.get2771CallData(callData)); + } else { + revert UnsupportedCallType(calltype); + } + + // Use revert message from fallback handler if the call was not successful + assembly { + if iszero(success) { + revert(add(result, 0x20), mload(result)) + } + } + + // hook post check + if (hook != address(0)) { + IHook(hook).postCheck(hookData); + } + + // return the result + assembly { + return(add(result, 0x20), mload(result)) + } + } + + // If there's no handler, the call can be one of onERCXXXReceived() + // No need to hook this as no execution is done here + bytes32 s; + /// @solidity memory-safe-assembly + assembly { + s := shr(224, calldataload(0)) + // 0x150b7a02: `onERC721Received(address,address,uint256,bytes)`. + // 0xf23a6e61: `onERC1155Received(address,address,uint256,uint256,bytes)`. + // 0xbc197c81: `onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)`. + if or(eq(s, 0x150b7a02), or(eq(s, 0xf23a6e61), eq(s, 0xbc197c81))) { + mstore(0x20, s) // Store `msg.sig`. + return(0x3c, 0x20) // Return `msg.sig`. + } + } + // if there was no handler and it is not the onERCXXXReceived call, revert + revert MissingFallbackHandler(msg.sig); + } + + /// @dev Helper function to paginate entries in a SentinelList. + /// @param list The SentinelList to paginate. + /// @param cursor The cursor to start paginating from. + /// @param size The number of entries to return. + /// @return array The array of addresses in the list. + /// @return nextCursor The cursor for the next page of entries. + function _paginate( + SentinelListLib.SentinelList storage list, + address cursor, + uint256 size + ) private view returns (address[] memory array, address nextCursor) { + (array, nextCursor) = list.getEntriesPaginated(cursor, size); + } +} diff --git a/biconomy/nexus/1.0.2/contracts/base/RegistryAdapter.sol b/biconomy/nexus/1.0.2/contracts/base/RegistryAdapter.sol new file mode 100644 index 0000000..f3744c6 --- /dev/null +++ b/biconomy/nexus/1.0.2/contracts/base/RegistryAdapter.sol @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.27; + +import { IERC7484 } from "../interfaces/IERC7484.sol"; + +/// @title RegistryAdapter +/// @notice This contract provides an interface for interacting with an ERC-7484 compliant registry. +/// @dev The registry feature is opt-in, allowing the smart account owner to select and trust specific attesters. +abstract contract RegistryAdapter { + IERC7484 public registry; + + /// @notice Emitted when a new ERC-7484 registry is configured for the account. + /// @param registry The configured registry contract. + event ERC7484RegistryConfigured(IERC7484 indexed registry); + + /// @notice Modifier to check if a module meets the required attestations in the registry. + /// @param module The module to check. + /// @param moduleType The type of the module to verify in the registry. + modifier withRegistry(address module, uint256 moduleType) { + _checkRegistry(module, moduleType); + _; + } + + /// @notice Configures the ERC-7484 registry and sets trusted attesters. + /// @param newRegistry The new registry contract to use. + /// @param attesters The list of attesters to trust. + /// @param threshold The number of attestations required. + function _configureRegistry(IERC7484 newRegistry, address[] calldata attesters, uint8 threshold) internal { + registry = newRegistry; + if (address(newRegistry) != address(0)) { + newRegistry.trustAttesters(threshold, attesters); + } + emit ERC7484RegistryConfigured(newRegistry); + } + + /// @notice Checks the registry to ensure sufficient valid attestations for a module. + /// @param module The module to check. + /// @param moduleType The type of the module to verify in the registry. + /// @dev Reverts if the required attestations are not met. + function _checkRegistry(address module, uint256 moduleType) internal view { + IERC7484 moduleRegistry = registry; + if (address(moduleRegistry) != address(0)) { + // This will revert if attestations or the threshold are not met. + moduleRegistry.check(module, moduleType); + } + } +} diff --git a/biconomy/nexus/1.0.2/contracts/base/Storage.sol b/biconomy/nexus/1.0.2/contracts/base/Storage.sol new file mode 100644 index 0000000..ddfd807 --- /dev/null +++ b/biconomy/nexus/1.0.2/contracts/base/Storage.sol @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.27; + +// ────────────────────────────────────────────────────────────────────────────── +// _ __ _ __ +// / | / /__ | |/ /_ _______ +// / |/ / _ \| / / / / ___/ +// / /| / __/ / /_/ (__ ) +// /_/ |_/\___/_/|_\__,_/____/ +// +// ────────────────────────────────────────────────────────────────────────────── +// Nexus: A suite of contracts for Modular Smart Accounts compliant with ERC-7579 and ERC-4337, developed by Biconomy. +// Learn more at https://biconomy.io. To report security issues, please contact us at: security@biconomy.io + +import { IStorage } from "../interfaces/base/IStorage.sol"; + +/// @title Nexus - Storage +/// @notice Manages isolated storage spaces for Modular Smart Account in compliance with ERC-7201 standard to ensure collision-resistant storage. +/// @dev Implements the ERC-7201 namespaced storage pattern to maintain secure and isolated storage sections for different states within Nexus suite. +/// @author @livingrockrises | Biconomy | chirag@biconomy.io +/// @author @aboudjem | Biconomy | adam.boudjemaa@biconomy.io +/// @author @filmakarov | Biconomy | filipp.makarov@biconomy.io +/// @author @zeroknots | Rhinestone.wtf | zeroknots.eth +/// Special thanks to the Solady team for foundational contributions: https://github.com/Vectorized/solady +contract Storage is IStorage { + /// @custom:storage-location erc7201:biconomy.storage.Nexus + /// ERC-7201 namespaced via `keccak256(abi.encode(uint256(keccak256(bytes("biconomy.storage.Nexus"))) - 1)) & ~bytes32(uint256(0xff));` + bytes32 private constant _STORAGE_LOCATION = 0x0bb70095b32b9671358306b0339b4c06e7cbd8cb82505941fba30d1eb5b82f00; + + /// @dev Utilizes ERC-7201's namespaced storage pattern for isolated storage access. This method computes + /// the storage slot based on a predetermined location, ensuring collision-resistant storage for contract states. + /// @custom:storage-location ERC-7201 formula applied to "biconomy.storage.Nexus", facilitating unique + /// namespace identification and storage segregation, as detailed in the specification. + /// @return $ The proxy to the `AccountStorage` struct, providing a reference to the namespaced storage slot. + function _getAccountStorage() internal pure returns (AccountStorage storage $) { + assembly { + $.slot := _STORAGE_LOCATION + } + } +} diff --git a/biconomy/nexus/1.0.2/contracts/common/Stakeable.sol b/biconomy/nexus/1.0.2/contracts/common/Stakeable.sol new file mode 100644 index 0000000..a08023c --- /dev/null +++ b/biconomy/nexus/1.0.2/contracts/common/Stakeable.sol @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.27; + +// ────────────────────────────────────────────────────────────────────────────── +// _ __ _ __ +// / | / /__ | |/ /_ _______ +// / |/ / _ \| / / / / ___/ +// / /| / __/ / /_/ (__ ) +// /_/ |_/\___/_/|_\__,_/____/ +// +// ────────────────────────────────────────────────────────────────────────────── +// Nexus: A suite of contracts for Modular Smart Accounts compliant with ERC-7579 and ERC-4337, developed by Biconomy. +// Learn more at https://biconomy.io. For security issues, contact: security@biconomy.io + +import { Ownable } from "solady/auth/Ownable.sol"; +import { IEntryPoint } from "account-abstraction/interfaces/IEntryPoint.sol"; + +import { IStakeable } from "../interfaces/common/IStakeable.sol"; + +/// @title Stakeable Entity +/// @notice Provides functionality to stake, unlock, and withdraw Ether on an EntryPoint. +/// @author @livingrockrises | Biconomy | chirag@biconomy.io +/// @author @aboudjem | Biconomy | adam.boudjemaa@biconomy.io +/// @author @filmakarov | Biconomy | filipp.makarov@biconomy.io +/// @author @zeroknots | Rhinestone.wtf | zeroknots.eth +/// Special thanks to the Solady team for foundational contributions: https://github.com/Vectorized/solady +contract Stakeable is Ownable, IStakeable { + /// @notice Error thrown when an invalid EntryPoint address is provided. + error InvalidEntryPointAddress(); + + constructor(address newOwner) { + _setOwner(newOwner); + } + + /// @notice Stakes a certain amount of Ether on an EntryPoint. + /// @dev The contract should have enough Ether to cover the stake. + /// @param epAddress The address of the EntryPoint where the stake is added. + /// @param unstakeDelaySec The delay in seconds before the stake can be unlocked. + function addStake(address epAddress, uint32 unstakeDelaySec) external payable onlyOwner { + require(epAddress != address(0), InvalidEntryPointAddress()); + IEntryPoint(epAddress).addStake{ value: msg.value }(unstakeDelaySec); + } + + /// @notice Unlocks the stake on an EntryPoint. + /// @dev This starts the unstaking delay after which funds can be withdrawn. + /// @param epAddress The address of the EntryPoint from which the stake is to be unlocked. + function unlockStake(address epAddress) external onlyOwner { + require(epAddress != address(0), InvalidEntryPointAddress()); + IEntryPoint(epAddress).unlockStake(); + } + + /// @notice Withdraws the stake from an EntryPoint to a specified address. + /// @dev This can only be done after the unstaking delay has passed since the unlock. + /// @param epAddress The address of the EntryPoint where the stake is withdrawn from. + /// @param withdrawAddress The address to receive the withdrawn stake. + function withdrawStake(address epAddress, address payable withdrawAddress) external onlyOwner { + require(epAddress != address(0), InvalidEntryPointAddress()); + IEntryPoint(epAddress).withdrawStake(withdrawAddress); + } +} diff --git a/biconomy/nexus/1.0.2/contracts/factory/K1ValidatorFactory.sol b/biconomy/nexus/1.0.2/contracts/factory/K1ValidatorFactory.sol new file mode 100644 index 0000000..2e6a237 --- /dev/null +++ b/biconomy/nexus/1.0.2/contracts/factory/K1ValidatorFactory.sol @@ -0,0 +1,123 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.27; + +// ────────────────────────────────────────────────────────────────────────────── +// _ __ _ __ +// / | / /__ | |/ /_ _______ +// / |/ / _ \| / / / / ___/ +// / /| / __/ / /_/ (__ ) +// /_/ |_/\___/_/|_\__,_/____/ +// +// ────────────────────────────────────────────────────────────────────────────── +// Nexus: A suite of contracts for Modular Smart Accounts compliant with ERC-7579 and ERC-4337, developed by Biconomy. +// Learn more at https://biconomy.io. For security issues, contact: security@biconomy.io + +import { LibClone } from "solady/utils/LibClone.sol"; +import { INexus } from "../interfaces/INexus.sol"; +import { BootstrapLib } from "../lib/BootstrapLib.sol"; +import { NexusBootstrap, BootstrapConfig } from "../utils/NexusBootstrap.sol"; +import { Stakeable } from "../common/Stakeable.sol"; +import { IERC7484 } from "../interfaces/IERC7484.sol"; + +/// @title K1ValidatorFactory for Nexus Account +/// @notice Manages the creation of Modular Smart Accounts compliant with ERC-7579 and ERC-4337 using a K1 validator. +/// @author @livingrockrises | Biconomy | chirag@biconomy.io +/// @author @aboudjem | Biconomy | adam.boudjemaa@biconomy.io +/// @author @filmakarov | Biconomy | filipp.makarov@biconomy.io +/// @author @zeroknots | Rhinestone.wtf | zeroknots.eth +/// Special thanks to the Solady team for foundational contributions: https://github.com/Vectorized/solady +contract K1ValidatorFactory is Stakeable { + /// @notice Stores the implementation contract address used to create new Nexus instances. + /// @dev This address is set once upon deployment and cannot be changed afterwards. + address public immutable ACCOUNT_IMPLEMENTATION; + + /// @notice Stores the K1 Validator module address. + /// @dev This address is set once upon deployment and cannot be changed afterwards. + address public immutable K1_VALIDATOR; + + /// @notice Stores the Bootstrapper module address. + /// @dev This address is set once upon deployment and cannot be changed afterwards. + NexusBootstrap public immutable BOOTSTRAPPER; + + IERC7484 public immutable REGISTRY; + + /// @notice Emitted when a new Smart Account is created, capturing the account details and associated module configurations. + event AccountCreated(address indexed account, address indexed owner, uint256 indexed index); + + /// @notice Error thrown when a zero address is provided for the implementation, K1 validator, or bootstrapper. + error ZeroAddressNotAllowed(); + + /// @notice Error thrown when an inner call fails. + error InnerCallFailed(); + + /// @notice Constructor to set the immutable variables. + /// @param implementation The address of the Nexus implementation to be used for all deployments. + /// @param factoryOwner The address of the factory owner. + /// @param k1Validator The address of the K1 Validator module to be used for all deployments. + /// @param bootstrapper The address of the Bootstrapper module to be used for all deployments. + constructor( + address implementation, + address factoryOwner, + address k1Validator, + NexusBootstrap bootstrapper, + IERC7484 registry + ) Stakeable(factoryOwner) { + require( + !(implementation == address(0) || k1Validator == address(0) || address(bootstrapper) == address(0) || factoryOwner == address(0)), + ZeroAddressNotAllowed() + ); + ACCOUNT_IMPLEMENTATION = implementation; + K1_VALIDATOR = k1Validator; + BOOTSTRAPPER = bootstrapper; + REGISTRY = registry; + } + + /// @notice Creates a new Nexus with a specific validator and initialization data. + /// @param eoaOwner The address of the EOA owner of the Nexus. + /// @param index The index of the Nexus. + /// @param attesters The list of attesters for the Nexus. + /// @param threshold The threshold for the Nexus. + /// @return The address of the newly created Nexus. + function createAccount( + address eoaOwner, + uint256 index, + address[] calldata attesters, + uint8 threshold + ) external payable returns (address payable) { + // Compute the actual salt for deterministic deployment + bytes32 actualSalt = keccak256(abi.encodePacked(eoaOwner, index, attesters, threshold)); + + // Deploy the Nexus contract using the computed salt + (bool alreadyDeployed, address account) = LibClone.createDeterministicERC1967(msg.value, ACCOUNT_IMPLEMENTATION, actualSalt); + + // Create the validator configuration using the NexusBootstrap library + BootstrapConfig memory validator = BootstrapLib.createSingleConfig(K1_VALIDATOR, abi.encodePacked(eoaOwner)); + bytes memory initData = BOOTSTRAPPER.getInitNexusWithSingleValidatorCalldata(validator, REGISTRY, attesters, threshold); + + // Initialize the account if it was not already deployed + if (!alreadyDeployed) { + INexus(account).initializeAccount(initData); + emit AccountCreated(account, eoaOwner, index); + } + return payable(account); + } + + /// @notice Computes the expected address of a Nexus contract using the factory's deterministic deployment algorithm. + /// @param eoaOwner The address of the EOA owner of the Nexus. + /// @param index The index of the Nexus. + /// @param attesters The list of attesters for the Nexus. + /// @param threshold The threshold for the Nexus. + /// @return expectedAddress The expected address at which the Nexus contract will be deployed if the provided parameters are used. + function computeAccountAddress( + address eoaOwner, + uint256 index, + address[] calldata attesters, + uint8 threshold + ) external view returns (address payable expectedAddress) { + // Compute the actual salt for deterministic deployment + bytes32 actualSalt = keccak256(abi.encodePacked(eoaOwner, index, attesters, threshold)); + + // Predict the deterministic address using the LibClone library + expectedAddress = payable(LibClone.predictDeterministicAddressERC1967(ACCOUNT_IMPLEMENTATION, actualSalt, address(this))); + } +} diff --git a/biconomy/nexus/1.0.2/contracts/factory/NexusAccountFactory.sol b/biconomy/nexus/1.0.2/contracts/factory/NexusAccountFactory.sol new file mode 100644 index 0000000..a968f07 --- /dev/null +++ b/biconomy/nexus/1.0.2/contracts/factory/NexusAccountFactory.sol @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.27; + +// ────────────────────────────────────────────────────────────────────────────── +// _ __ _ __ +// / | / /__ | |/ /_ _______ +// / |/ / _ \| / / / / ___/ +// / /| / __/ / /_/ (__ ) +// /_/ |_/\___/_/|_\__,_/____/ +// +// ────────────────────────────────────────────────────────────────────────────── +// Nexus: A suite of contracts for Modular Smart Accounts compliant with ERC-7579 and ERC-4337, developed by Biconomy. +// Learn more at https://biconomy.io. To report security issues, please contact us at: security@biconomy.io +import { LibClone } from "solady/utils/LibClone.sol"; +import { INexus } from "../interfaces/INexus.sol"; +import { Stakeable } from "../common/Stakeable.sol"; +import { INexusFactory } from "../interfaces/factory/INexusFactory.sol"; + +/// @title Nexus Account Factory +/// @notice Manages the creation of Modular Smart Accounts compliant with ERC-7579 and ERC-4337 using a factory pattern. +/// @author @livingrockrises | Biconomy | chirag@biconomy.io +/// @author @aboudjem | Biconomy | adam.boudjemaa@biconomy.io +/// @author @filmakarov | Biconomy | filipp.makarov@biconomy.io +/// @author @zeroknots | Rhinestone.wtf | zeroknots.eth +/// Special thanks to the Solady team for foundational contributions: https://github.com/Vectorized/solady +contract NexusAccountFactory is Stakeable, INexusFactory { + /// @notice Address of the implementation contract used to create new Nexus instances. + /// @dev This address is immutable and set upon deployment, ensuring the implementation cannot be changed. + address public immutable ACCOUNT_IMPLEMENTATION; + + /// @notice Constructor to set the smart account implementation address and the factory owner. + /// @param implementation_ The address of the Nexus implementation to be used for all deployments. + /// @param owner_ The address of the owner of the factory. + constructor(address implementation_, address owner_) Stakeable(owner_) { + require(implementation_ != address(0), ImplementationAddressCanNotBeZero()); + require(owner_ != address(0), ZeroAddressNotAllowed()); + ACCOUNT_IMPLEMENTATION = implementation_; + } + + /// @notice Creates a new Nexus account with the provided initialization data. + /// @param initData Initialization data to be called on the new Smart Account. + /// @param salt Unique salt for the Smart Account creation. + /// @return The address of the newly created Nexus account. + function createAccount(bytes calldata initData, bytes32 salt) external payable override returns (address payable) { + // Compute the actual salt for deterministic deployment + bytes32 actualSalt = keccak256(abi.encodePacked(initData, salt)); + + // Deploy the account using the deterministic address + (bool alreadyDeployed, address account) = LibClone.createDeterministicERC1967(msg.value, ACCOUNT_IMPLEMENTATION, actualSalt); + + if (!alreadyDeployed) { + INexus(account).initializeAccount(initData); + emit AccountCreated(account, initData, salt); + } + return payable(account); + } + + /// @notice Computes the expected address of a Nexus contract using the factory's deterministic deployment algorithm. + /// @param initData - Initialization data to be called on the new Smart Account. + /// @param salt - Unique salt for the Smart Account creation. + /// @return expectedAddress The expected address at which the Nexus contract will be deployed if the provided parameters are used. + function computeAccountAddress(bytes calldata initData, bytes32 salt) external view override returns (address payable expectedAddress) { + // Compute the actual salt for deterministic deployment + bytes32 actualSalt = keccak256(abi.encodePacked(initData, salt)); + expectedAddress = payable(LibClone.predictDeterministicAddressERC1967(ACCOUNT_IMPLEMENTATION, actualSalt, address(this))); + } +} diff --git a/biconomy/nexus/1.0.2/contracts/interfaces/IERC4337Account.sol b/biconomy/nexus/1.0.2/contracts/interfaces/IERC4337Account.sol new file mode 100644 index 0000000..d651cc5 --- /dev/null +++ b/biconomy/nexus/1.0.2/contracts/interfaces/IERC4337Account.sol @@ -0,0 +1,70 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.27; + +// ────────────────────────────────────────────────────────────────────────────── +// _ __ _ __ +// / | / /__ | |/ /_ _______ +// / |/ / _ \| / / / / ___/ +// / /| / __/ / /_/ (__ ) +// /_/ |_/\___/_/|_\__,_/____/ +// +// ────────────────────────────────────────────────────────────────────────────── +// Nexus: A suite of contracts for Modular Smart Accounts compliant with ERC-7579 and ERC-4337, developed by Biconomy. +// Learn more at https://biconomy.io. To report security issues, please contact us at: security@biconomy.io + +import { PackedUserOperation } from "account-abstraction/interfaces/PackedUserOperation.sol"; + +/// @title Nexus - IERC4337Account +/// @notice This interface defines the necessary validation and execution methods for smart accounts under the ERC-4337 standard. +/// @dev Provides a structure for implementing custom validation logic and execution methods that comply with ERC-4337 "account abstraction" specs. +/// The validation method ensures proper signature and nonce verification before proceeding with transaction execution, critical for securing userOps. +/// Also allows for the optional definition of an execution method to handle transactions post-validation, enhancing flexibility. +/// @author @livingrockrises | Biconomy | chirag@biconomy.io +/// @author @aboudjem | Biconomy | adam.boudjemaa@biconomy.io +/// @author @filmakarov | Biconomy | filipp.makarov@biconomy.io +/// @author @zeroknots | Rhinestone.wtf | zeroknots.eth +/// Special thanks to the Solady team for foundational contributions: https://github.com/Vectorized/solady +interface IERC4337Account { + /// Validate user's signature and nonce + /// the entryPoint will make the call to the recipient only if this validation call returns successfully. + /// signature failure should be reported by returning SIG_VALIDATION_FAILED (1). + /// This allows making a "simulation call" without a valid signature + /// Other failures (e.g. nonce mismatch, or invalid signature format) should still revert to signal failure. + /// + /// @dev ERC-4337-v-0.7 validation stage + /// @dev Must validate caller is the entryPoint. + /// Must validate the signature and nonce + /// @param userOp - The user operation that is about to be executed. + /// @param userOpHash - Hash of the user's request data. can be used as the basis for signature. + /// @param missingAccountFunds - Missing funds on the account's deposit in the entrypoint. + /// This is the minimum amount to transfer to the sender(entryPoint) to be + /// able to make the call. The excess is left as a deposit in the entrypoint + /// for future calls. Can be withdrawn anytime using "entryPoint.withdrawTo()". + /// In case there is a paymaster in the request (or the current deposit is high + /// enough), this value will be zero. + /// @return validationData - Packaged ValidationData structure. use `_packValidationData` and + /// `_unpackValidationData` to encode and decode. + /// <20-byte> sigAuthorizer - 0 for valid signature, 1 to mark signature failure, + /// otherwise, an address of an "authorizer" contract. + /// <6-byte> validUntil - Last timestamp this operation is valid. 0 for "indefinite" + /// <6-byte> validAfter - First timestamp this operation is valid + /// If an account doesn't use time-range, it is enough to + /// return SIG_VALIDATION_FAILED value (1) for signature failure. + /// Note that the validation code cannot use block.timestamp (or block.number) directly. + function validateUserOp( + PackedUserOperation calldata userOp, + bytes32 userOpHash, + uint256 missingAccountFunds + ) external returns (uint256 validationData); + + /// Account may implement this execute method. + /// passing this methodSig at the beginning of callData will cause the entryPoint to pass the + /// full UserOp (and hash) + /// to the account. + /// The account should skip the methodSig, and use the callData (and optionally, other UserOp + /// fields) + /// @dev ERC-4337-v-0.7 optional execution path + /// @param userOp - The operation that was just validated. + /// @param userOpHash - Hash of the user's request data. + function executeUserOp(PackedUserOperation calldata userOp, bytes32 userOpHash) external payable; +} diff --git a/biconomy/nexus/1.0.2/contracts/interfaces/IERC7484.sol b/biconomy/nexus/1.0.2/contracts/interfaces/IERC7484.sol new file mode 100644 index 0000000..ac7f722 --- /dev/null +++ b/biconomy/nexus/1.0.2/contracts/interfaces/IERC7484.sol @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.27; + +interface IERC7484 { + event NewTrustedAttesters(); + + /** + * Allows Smart Accounts - the end users of the registry - to appoint + * one or many attesters as trusted. + * @dev this function reverts, if address(0), or duplicates are provided in attesters[] + * + * @param threshold The minimum number of attestations required for a module + * to be considered secure. + * @param attesters The addresses of the attesters to be trusted. + */ + function trustAttesters(uint8 threshold, address[] calldata attesters) external; + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* Check with Registry internal attesters */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + function check(address module) external view; + + function checkForAccount(address smartAccount, address module) external view; + + function check(address module, uint256 moduleType) external view; + + function checkForAccount(address smartAccount, address module, uint256 moduleType) external view; + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* Check with external attester(s) */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + function check(address module, address[] calldata attesters, uint256 threshold) external view; + + function check(address module, uint256 moduleType, address[] calldata attesters, uint256 threshold) external view; +} diff --git a/biconomy/nexus/1.0.2/contracts/interfaces/IERC7579Account.sol b/biconomy/nexus/1.0.2/contracts/interfaces/IERC7579Account.sol new file mode 100644 index 0000000..6b49d84 --- /dev/null +++ b/biconomy/nexus/1.0.2/contracts/interfaces/IERC7579Account.sol @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.27; + +// ────────────────────────────────────────────────────────────────────────────── +// _ __ _ __ +// / | / /__ | |/ /_ _______ +// / |/ / _ \| / / / / ___/ +// / /| / __/ / /_/ (__ ) +// /_/ |_/\___/_/|_\__,_/____/ +// +// ────────────────────────────────────────────────────────────────────────────── +// Nexus: A suite of contracts for Modular Smart Accounts compliant with ERC-7579 and ERC-4337, developed by Biconomy. +// Learn more at https://biconomy.io. To report security issues, please contact us at: security@biconomy.io + +import { IAccountConfig } from "./base/IAccountConfig.sol"; +import { IExecutionHelper } from "./base/IExecutionHelper.sol"; +import { IModuleManager } from "./base/IModuleManager.sol"; + +/// @title Nexus - IERC7579Account +/// @notice This interface integrates the functionalities required for a modular smart account compliant with ERC-7579 and ERC-4337 standards. +/// @dev Combines configurations and operational management for smart accounts, bridging IAccountConfig, IExecutionHelper, and IModuleManager. +/// Interfaces designed to support the comprehensive management of smart account operations including execution management and modular configurations. +/// @author @livingrockrises | Biconomy | chirag@biconomy.io +/// @author @aboudjem | Biconomy | adam.boudjemaa@biconomy.io +/// @author @filmakarov | Biconomy | filipp.makarov@biconomy.io +/// @author @zeroknots | Rhinestone.wtf | zeroknots.eth +/// Special thanks to the Solady team for foundational contributions: https://github.com/Vectorized/solady +interface IERC7579Account is IAccountConfig, IExecutionHelper, IModuleManager { + /// @dev Validates a smart account signature according to ERC-1271 standards. + /// This method may delegate the call to a validator module to check the signature. + /// @param hash The hash of the data being validated. + /// @param data The signed data to validate. + function isValidSignature(bytes32 hash, bytes calldata data) external view returns (bytes4); +} diff --git a/biconomy/nexus/1.0.2/contracts/interfaces/INexus.sol b/biconomy/nexus/1.0.2/contracts/interfaces/INexus.sol new file mode 100644 index 0000000..31b379d --- /dev/null +++ b/biconomy/nexus/1.0.2/contracts/interfaces/INexus.sol @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.27; + +// ────────────────────────────────────────────────────────────────────────────── +// _ __ _ __ +// / | / /__ | |/ /_ _______ +// / |/ / _ \| / / / / ___/ +// / /| / __/ / /_/ (__ ) +// /_/ |_/\___/_/|_\__,_/____/ +// +// ────────────────────────────────────────────────────────────────────────────── +// Nexus: A suite of contracts for Modular Smart Accounts compliant with ERC-7579 and ERC-4337, developed by Biconomy. +// Learn more at https://biconomy.io. To report security issues, please contact us at: security@biconomy.io + +import { IERC4337Account } from "./IERC4337Account.sol"; +import { IERC7579Account } from "./IERC7579Account.sol"; +import { INexusEventsAndErrors } from "./INexusEventsAndErrors.sol"; + +/// @title Nexus - INexus Interface +/// @notice Integrates ERC-4337 and ERC-7579 standards to manage smart accounts within the Nexus suite. +/// @dev Consolidates ERC-4337 user operations and ERC-7579 configurations into a unified interface for smart account management. +/// It extends both IERC4337Account and IERC7579Account, enhancing modular capabilities and supporting advanced contract architectures. +/// Includes error definitions for robust handling of common issues such as unsupported module types and execution failures. +/// The initialize function sets up the account with validators and configurations, ensuring readiness for use. +/// @author @livingrockrises | Biconomy | chirag@biconomy.io +/// @author @aboudjem | Biconomy | adam.boudjemaa@biconomy.io +/// @author @filmakarov | Biconomy | filipp.makarov@biconomy.io +/// @author @zeroknots | Rhinestone.wtf | zeroknots.eth +/// Special thanks to the Solady team for foundational contributions: https://github.com/Vectorized/solady +interface INexus is IERC4337Account, IERC7579Account, INexusEventsAndErrors { + /// @notice Initializes the smart account with a validator and custom data. + /// @dev This method sets up the account for operation, linking it with a validator and initializing it with specific data. + /// Can be called directly or via a factory. + /// @param initData Encoded data used for the account's configuration during initialization. + function initializeAccount(bytes calldata initData) external payable; +} diff --git a/biconomy/nexus/1.0.2/contracts/interfaces/INexusEventsAndErrors.sol b/biconomy/nexus/1.0.2/contracts/interfaces/INexusEventsAndErrors.sol new file mode 100644 index 0000000..d44beb9 --- /dev/null +++ b/biconomy/nexus/1.0.2/contracts/interfaces/INexusEventsAndErrors.sol @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.27; + +// ────────────────────────────────────────────────────────────────────────────── +// _ __ _ __ +// / | / /__ | |/ /_ _______ +// / |/ / _ \| / / / / ___/ +// / /| / __/ / /_/ (__ ) +// /_/ |_/\___/_/|_\__,_/____/ +// +// ────────────────────────────────────────────────────────────────────────────── +// Nexus: A suite of contracts for Modular Smart Accounts compliant with ERC-7579 and ERC-4337, developed by Biconomy. +// Learn more at https://biconomy.io. To report security issues, please contact us at: security@biconomy.io + +import { PackedUserOperation } from "account-abstraction/interfaces/PackedUserOperation.sol"; + +/// @title Nexus - INexus Events and Errors +/// @notice Defines common errors for the Nexus smart account management interface. +/// @author @livingrockrises | Biconomy | chirag@biconomy.io +/// @author @aboudjem | Biconomy | adam.boudjemaa@biconomy.io +/// @author @filmakarov | Biconomy | filipp.makarov@biconomy.io +/// @author @zeroknots | Rhinestone.wtf | zeroknots.eth +/// Special thanks to the Solady team for foundational contributions: https://github.com/Vectorized/solady +interface INexusEventsAndErrors { + /// @notice Emitted when a user operation is executed from `executeUserOp` + /// @param userOp The user operation that was executed. + /// @param innerCallRet The return data from the inner call execution. + event Executed(PackedUserOperation userOp, bytes innerCallRet); + + /// @notice Error thrown when an unsupported ModuleType is requested. + /// @param moduleTypeId The ID of the unsupported module type. + error UnsupportedModuleType(uint256 moduleTypeId); + + /// @notice Error thrown on failed execution. + error ExecutionFailed(); + + /// @notice Error thrown when the Factory fails to initialize the account with posted bootstrap data. + error NexusInitializationFailed(); + + /// @notice Error thrown when a zero address is provided as the Entry Point address. + error EntryPointCanNotBeZero(); + + /// @notice Error thrown when the provided implementation address is invalid. + error InvalidImplementationAddress(); + + /// @notice Error thrown when the provided implementation address is not a contract. + error ImplementationIsNotAContract(); + + /// @notice Error thrown when an inner call fails. + error InnerCallFailed(); + + /// @notice Error thrown when attempted to emergency-uninstall a hook + error EmergencyTimeLockNotExpired(); +} diff --git a/biconomy/nexus/1.0.2/contracts/interfaces/base/IAccountConfig.sol b/biconomy/nexus/1.0.2/contracts/interfaces/base/IAccountConfig.sol new file mode 100644 index 0000000..b08ffb3 --- /dev/null +++ b/biconomy/nexus/1.0.2/contracts/interfaces/base/IAccountConfig.sol @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.27; + +// ────────────────────────────────────────────────────────────────────────────── +// _ __ _ __ +// / | / /__ | |/ /_ _______ +// / |/ / _ \| / / / / ___/ +// / /| / __/ / /_/ (__ ) +// /_/ |_/\___/_/|_\__,_/____/ +// +// ────────────────────────────────────────────────────────────────────────────── +// Nexus: A suite of contracts for Modular Smart Accounts compliant with ERC-7579 and ERC-4337, developed by Biconomy. +// Learn more at https://biconomy.io. To report security issues, please contact us at: security@biconomy.io + +import { ExecutionMode } from "../../lib/ModeLib.sol"; + +/// @title Nexus - ERC-7579 Account Configuration Interface +/// @notice Interface for querying and verifying configurations of Smart Accounts compliant with ERC-7579. +/// @dev Provides methods to check supported execution modes and module types for Smart Accounts, ensuring flexible and extensible configuration. +/// @author @livingrockrises | Biconomy | chirag@biconomy.io +/// @author @aboudjem | Biconomy | adam.boudjemaa@biconomy.io +/// @author @filmakarov | Biconomy | filipp.makarov@biconomy.io +/// @author @zeroknots | Rhinestone.wtf | zeroknots.eth +/// Special thanks to the Solady team for foundational contributions: https://github.com/Vectorized/solady +interface IAccountConfig { + /// @notice Returns the account ID in a structured format: "vendorname.accountname.semver" + /// @return accountImplementationId The account ID of the smart account + function accountId() external view returns (string memory accountImplementationId); + + /// @notice Checks if the account supports a certain execution mode. + /// @param encodedMode The encoded mode to verify. + /// @return supported True if the account supports the mode, false otherwise. + function supportsExecutionMode(ExecutionMode encodedMode) external view returns (bool supported); + + /// @notice Checks if the account supports a specific module type. + /// @param moduleTypeId The module type ID to verify. + /// @return supported True if the account supports the module type, false otherwise. + function supportsModule(uint256 moduleTypeId) external view returns (bool supported); +} diff --git a/biconomy/nexus/1.0.2/contracts/interfaces/base/IBaseAccount.sol b/biconomy/nexus/1.0.2/contracts/interfaces/base/IBaseAccount.sol new file mode 100644 index 0000000..d847099 --- /dev/null +++ b/biconomy/nexus/1.0.2/contracts/interfaces/base/IBaseAccount.sol @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.27; + +// ────────────────────────────────────────────────────────────────────────────── +// _ __ _ __ +// / | / /__ | |/ /_ _______ +// / |/ / _ \| / / / / ___/ +// / /| / __/ / /_/ (__ ) +// /_/ |_/\___/_/|_\__,_/____/ +// +// ────────────────────────────────────────────────────────────────────────────── +// Nexus: A suite of contracts for Modular Smart Accounts compliant with ERC-7579 and ERC-4337, developed by Biconomy. +// Learn more at https://biconomy.io. To report security issues, please contact us at: security@biconomy.io + +import { IBaseAccountEventsAndErrors } from "./IBaseAccountEventsAndErrors.sol"; + +/// @title Nexus - IBaseAccount +/// @notice Interface for the BaseAccount functionalities compliant with ERC-7579 and ERC-4337. +/// @dev Interface for organizing the base functionalities using the Nexus suite. +/// @author @livingrockrises | Biconomy | chirag@biconomy.io +/// @author @aboudjem | Biconomy | adam.boudjemaa@biconomy.io +/// @author @filmakarov | Biconomy | filipp.makarov@biconomy.io +/// @author @zeroknots | Rhinestone.wtf | zeroknots.eth +/// Special thanks to the Solady team for foundational contributions: https://github.com/Vectorized/solady +interface IBaseAccount is IBaseAccountEventsAndErrors { + /// @notice Adds deposit to the EntryPoint to fund transactions. + function addDeposit() external payable; + + /// @notice Withdraws ETH from the EntryPoint to a specified address. + /// @param to The address to receive the withdrawn funds. + /// @param amount The amount to withdraw. + function withdrawDepositTo(address to, uint256 amount) external payable; + + /// @notice Gets the nonce for a particular key. + /// @param key The nonce key. + /// @return The nonce associated with the key. + function nonce(uint192 key) external view returns (uint256); + + /// @notice Returns the current deposit balance of this account on the EntryPoint. + /// @return The current balance held at the EntryPoint. + function getDeposit() external view returns (uint256); + + /// @notice Retrieves the address of the EntryPoint contract, currently using version 0.7. + /// @return The address of the EntryPoint contract. + function entryPoint() external view returns (address); +} diff --git a/biconomy/nexus/1.0.2/contracts/interfaces/base/IBaseAccountEventsAndErrors.sol b/biconomy/nexus/1.0.2/contracts/interfaces/base/IBaseAccountEventsAndErrors.sol new file mode 100644 index 0000000..d003370 --- /dev/null +++ b/biconomy/nexus/1.0.2/contracts/interfaces/base/IBaseAccountEventsAndErrors.sol @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.27; + +// ────────────────────────────────────────────────────────────────────────────── +// _ __ _ __ +// / | / /__ | |/ /_ _______ +// / |/ / _ \| / / / / ___/ +// / /| / __/ / /_/ (__ ) +// /_/ |_/\___/_/|_\__,_/____/ +// +// ────────────────────────────────────────────────────────────────────────────── +// Nexus: A suite of contracts for Modular Smart Accounts compliant with ERC-7579 and ERC-4337, developed by Biconomy. +// Learn more at https://biconomy.io. To report security issues, please contact us at: security@biconomy.io + +/// @title Execution Manager Events and Errors Interface +/// @notice Interface for defining events and errors related to transaction execution processes within smart accounts. +/// @dev This interface defines events and errors used by execution manager to handle and report the operational status of smart account transactions. +/// It is a part of the Nexus suite of contracts aimed at implementing flexible and secure smart account operations. +/// @author @livingrockrises | Biconomy | chirag@biconomy.io +/// @author @aboudjem | Biconomy | adam.boudjemaa@biconomy.io +/// @author @filmakarov | Biconomy | filipp.makarov@biconomy.io +/// @author @zeroknots | Rhinestone.wtf | zeroknots.eth +/// Special thanks to the Solady team for foundational contributions: https://github.com/Vectorized/solady +interface IBaseAccountEventsAndErrors { + /// @dev Throws an error when a caller is not authorized to access an account. + error AccountAccessUnauthorized(); +} diff --git a/biconomy/nexus/1.0.2/contracts/interfaces/base/IExecutionHelper.sol b/biconomy/nexus/1.0.2/contracts/interfaces/base/IExecutionHelper.sol new file mode 100644 index 0000000..a2c5221 --- /dev/null +++ b/biconomy/nexus/1.0.2/contracts/interfaces/base/IExecutionHelper.sol @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.27; + +// ────────────────────────────────────────────────────────────────────────────── +// _ __ _ __ +// / | / /__ | |/ /_ _______ +// / |/ / _ \| / / / / ___/ +// / /| / __/ / /_/ (__ ) +// /_/ |_/\___/_/|_\__,_/____/ +// +// ────────────────────────────────────────────────────────────────────────────── +// Nexus: A suite of contracts for Modular Smart Accounts compliant with ERC-7579 and ERC-4337, developed by Biconomy. +// Learn more at https://biconomy.io. To report security issues, please contact us at: security@biconomy.io + +import { ExecutionMode } from "../../lib/ModeLib.sol"; + +import { IExecutionHelperEventsAndErrors } from "./IExecutionHelperEventsAndErrors.sol"; + +/// @title Nexus - IExecutionHelper +/// @notice Interface for executing transactions on behalf of smart accounts within the Nexus system. +/// @dev Extends functionality for transaction execution with error handling as defined in IExecutionHelperEventsAndErrors. +/// @author @livingrockrises | Biconomy | chirag@biconomy.io +/// @author @aboudjem | Biconomy | adam.boudjemaa@biconomy.io +/// @author @filmakarov | Biconomy | filipp.makarov@biconomy.io +/// @author @zeroknots | Rhinestone.wtf | zeroknots.eth +/// Special thanks to the Solady team for foundational contributions: https://github.com/Vectorized/solady +interface IExecutionHelper is IExecutionHelperEventsAndErrors { + /// @notice Executes a transaction with specified execution mode and calldata. + /// @param mode The execution mode, defining how the transaction is processed. + /// @param executionCalldata The calldata to execute. + /// @dev This function ensures that the execution complies with smart account execution policies and handles errors appropriately. + function execute(ExecutionMode mode, bytes calldata executionCalldata) external payable; + + /// @notice Allows an executor module to perform transactions on behalf of the account. + /// @param mode The execution mode that details how the transaction should be handled. + /// @param executionCalldata The transaction data to be executed. + /// @return returnData The result of the execution, allowing for error handling and results interpretation by the executor module. + function executeFromExecutor(ExecutionMode mode, bytes calldata executionCalldata) external payable returns (bytes[] memory returnData); +} diff --git a/biconomy/nexus/1.0.2/contracts/interfaces/base/IExecutionHelperEventsAndErrors.sol b/biconomy/nexus/1.0.2/contracts/interfaces/base/IExecutionHelperEventsAndErrors.sol new file mode 100644 index 0000000..34de7db --- /dev/null +++ b/biconomy/nexus/1.0.2/contracts/interfaces/base/IExecutionHelperEventsAndErrors.sol @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.27; + +// ────────────────────────────────────────────────────────────────────────────── +// _ __ _ __ +// / | / /__ | |/ /_ _______ +// / |/ / _ \| / / / / ___/ +// / /| / __/ / /_/ (__ ) +// /_/ |_/\___/_/|_\__,_/____/ +// +// ────────────────────────────────────────────────────────────────────────────── +// Nexus: A suite of contracts for Modular Smart Accounts compliant with ERC-7579 and ERC-4337, developed by Biconomy. +// Learn more at https://biconomy.io. To report security issues, please contact us at: security@biconomy.io + +/// @title Execution Manager Events and Errors Interface +/// @notice Interface for defining events and errors related to transaction execution processes within smart accounts. +/// @dev This interface defines events and errors used by execution manager to handle and report the operational status of smart account transactions. +/// It is a part of the Nexus suite of contracts aimed at implementing flexible and secure smart account operations. +/// @author @livingrockrises | Biconomy | chirag@biconomy.io +/// @author @aboudjem | Biconomy | adam.boudjemaa@biconomy.io +/// @author @filmakarov | Biconomy | filipp.makarov@biconomy.io +/// @author @zeroknots | Rhinestone.wtf | zeroknots.eth +/// Special thanks to the Solady team for foundational contributions: https://github.com/Vectorized/solady + +import { ExecType } from "../../lib/ModeLib.sol"; + +interface IExecutionHelperEventsAndErrors { + /// @notice Event emitted when a transaction fails to execute successfully. + event TryExecuteUnsuccessful(bytes callData, bytes result); + + /// @notice Event emitted when a transaction fails to execute successfully. + event TryDelegateCallUnsuccessful(bytes callData, bytes result); + + /// @notice Error thrown when an execution with an unsupported ExecType was made. + /// @param execType The unsupported execution type. + error UnsupportedExecType(ExecType execType); +} diff --git a/biconomy/nexus/1.0.2/contracts/interfaces/base/IModuleManager.sol b/biconomy/nexus/1.0.2/contracts/interfaces/base/IModuleManager.sol new file mode 100644 index 0000000..f56f7df --- /dev/null +++ b/biconomy/nexus/1.0.2/contracts/interfaces/base/IModuleManager.sol @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.27; + +// ────────────────────────────────────────────────────────────────────────────── +// _ __ _ __ +// / | / /__ | |/ /_ _______ +// / |/ / _ \| / / / / ___/ +// / /| / __/ / /_/ (__ ) +// /_/ |_/\___/_/|_\__,_/____/ +// +// ────────────────────────────────────────────────────────────────────────────── +// Nexus: A suite of contracts for Modular Smart Accounts compliant with ERC-7579 and ERC-4337, developed by Biconomy. +// Learn more at https://biconomy.io. To report security issues, please contact us at: security@biconomy.io + +import { IModuleManagerEventsAndErrors } from "./IModuleManagerEventsAndErrors.sol"; + +/// @title Nexus - IModuleManager +/// @notice Interface for managing modules within Smart Accounts, providing methods for installation and removal of modules. +/// @dev Extends the IModuleManagerEventsAndErrors interface to include event and error definitions. +/// @author @livingrockrises | Biconomy | chirag@biconomy.io +/// @author @aboudjem | Biconomy | adam.boudjemaa@biconomy.io +/// @author @filmakarov | Biconomy | filipp.makarov@biconomy.io +/// @author @zeroknots | Rhinestone.wtf | zeroknots.eth +/// Special thanks to the Solady team for foundational contributions: https://github.com/Vectorized/solady +interface IModuleManager is IModuleManagerEventsAndErrors { + /// @notice Installs a Module of a specific type onto the smart account. + /// @param moduleTypeId The identifier for the module type. + /// @param module The address of the module to be installed. + /// @param initData Initialization data for configuring the module upon installation. + function installModule(uint256 moduleTypeId, address module, bytes calldata initData) external payable; + + /// @notice Uninstalls a Module of a specific type from the smart account. + /// @param moduleTypeId The identifier for the module type being uninstalled. + /// @param module The address of the module to uninstall. + /// @param deInitData De-initialization data for configuring the module upon uninstallation. + function uninstallModule(uint256 moduleTypeId, address module, bytes calldata deInitData) external payable; + + /// @notice Checks if a specific module is installed on the smart account. + /// @param moduleTypeId The module type identifier to check. + /// @param module The address of the module. + /// @param additionalContext Additional information that may be required to verify the module's installation. + /// @return installed True if the module is installed, false otherwise. + function isModuleInstalled(uint256 moduleTypeId, address module, bytes calldata additionalContext) external view returns (bool installed); +} diff --git a/biconomy/nexus/1.0.2/contracts/interfaces/base/IModuleManagerEventsAndErrors.sol b/biconomy/nexus/1.0.2/contracts/interfaces/base/IModuleManagerEventsAndErrors.sol new file mode 100644 index 0000000..77ddc27 --- /dev/null +++ b/biconomy/nexus/1.0.2/contracts/interfaces/base/IModuleManagerEventsAndErrors.sol @@ -0,0 +1,99 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.27; + +// ────────────────────────────────────────────────────────────────────────────── +// _ __ _ __ +// / | / /__ | |/ /_ _______ +// / |/ / _ \| / / / / ___/ +// / /| / __/ / /_/ (__ ) +// /_/ |_/\___/_/|_\__,_/____/ +// +// ────────────────────────────────────────────────────────────────────────────── +// Nexus: A suite of contracts for Modular Smart Accounts compliant with ERC-7579 and ERC-4337, developed by Biconomy. +// Learn more at https://biconomy.io. To report security issues, please contact us at: security@biconomy.io + +import { CallType } from "../../lib/ModeLib.sol"; + +/// @title ERC-7579 Module Manager Events and Errors Interface +/// @notice Provides event and error definitions for actions related to module management in smart accounts. +/// @dev Used by IModuleManager to define the events and errors associated with the installation and management of modules. +/// @author @livingrockrises | Biconomy | chirag@biconomy.io +/// @author @aboudjem | Biconomy | adam.boudjemaa@biconomy.io +/// @author @filmakarov | Biconomy | filipp.makarov@biconomy.io +/// @author @zeroknots | Rhinestone.wtf | zeroknots.eth +/// Special thanks to the Solady team for foundational contributions: https://github.com/Vectorized/solady +interface IModuleManagerEventsAndErrors { + /// @notice Emitted when a module is installed onto a smart account. + /// @param moduleTypeId The identifier for the type of module installed. + /// @param module The address of the installed module. + event ModuleInstalled(uint256 moduleTypeId, address module); + + /// @notice Emitted when a module is uninstalled from a smart account. + /// @param moduleTypeId The identifier for the type of module uninstalled. + /// @param module The address of the uninstalled module. + event ModuleUninstalled(uint256 moduleTypeId, address module); + + /// @notice Thrown when attempting to remove the last validator. + error CanNotRemoveLastValidator(); + + /// @dev Thrown when the specified module address is not recognized as valid. + error ValidatorNotInstalled(address module); + + /// @dev Thrown when there is no installed validator detected. + error NoValidatorInstalled(); + + /// @dev Thrown when the specified module address is not recognized as valid. + error InvalidModule(address module); + + /// @dev Thrown when an invalid module type identifier is provided. + error InvalidModuleTypeId(uint256 moduleTypeId); + + /// @dev Thrown when there is an attempt to install a module that is already installed. + error ModuleAlreadyInstalled(uint256 moduleTypeId, address module); + + /// @dev Thrown when an operation is performed by an unauthorized operator. + error UnauthorizedOperation(address operator); + + /// @dev Thrown when there is an attempt to uninstall a module that is not installed. + error ModuleNotInstalled(uint256 moduleTypeId, address module); + + /// @dev Thrown when a module address is set to zero. + error ModuleAddressCanNotBeZero(); + + /// @dev Thrown when a post-check fails after hook execution. + error HookPostCheckFailed(); + + /// @dev Thrown when there is an attempt to install a hook while another is already installed. + error HookAlreadyInstalled(address currentHook); + + /// @dev Thrown when there is an attempt to install a fallback handler for a selector already having one. + error FallbackAlreadyInstalledForSelector(bytes4 selector); + + /// @dev Thrown when there is an attempt to uninstall a fallback handler for a selector that does not have one installed. + error FallbackNotInstalledForSelector(bytes4 selector); + + /// @dev Thrown when a fallback handler fails to uninstall properly. + error FallbackHandlerUninstallFailed(); + + /// @dev Thrown when no fallback handler is available for a given selector. + error MissingFallbackHandler(bytes4 selector); + + /// @dev Thrown when Invalid data is provided for MultiType install flow + error InvalidInput(); + + /// @dev Thrown when unable to validate Module Enable Mode signature + error EnableModeSigError(); + + /// Error thrown when account installs/uninstalls module with mismatched input `moduleTypeId` + error MismatchModuleTypeId(uint256 moduleTypeId); + + /// @dev Thrown when there is an attempt to install a forbidden selector as a fallback handler. + error FallbackSelectorForbidden(); + + /// @dev Thrown when there is an attempt to install a fallback handler with an invalid calltype for a given selector. + error FallbackCallTypeInvalid(); + + /// @notice Error thrown when an execution with an unsupported CallType was made. + /// @param callType The unsupported call type. + error UnsupportedCallType(CallType callType); +} diff --git a/biconomy/nexus/1.0.2/contracts/interfaces/base/IStorage.sol b/biconomy/nexus/1.0.2/contracts/interfaces/base/IStorage.sol new file mode 100644 index 0000000..b5ccb8c --- /dev/null +++ b/biconomy/nexus/1.0.2/contracts/interfaces/base/IStorage.sol @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.27; + +// ────────────────────────────────────────────────────────────────────────────── +// _ __ _ __ +// / | / /__ | |/ /_ _______ +// / |/ / _ \| / / / / ___/ +// / /| / __/ / /_/ (__ ) +// /_/ |_/\___/_/|_\__,_/____/ +// +// ────────────────────────────────────────────────────────────────────────────── +// Nexus: A suite of contracts for Modular Smart Accounts compliant with ERC-7579 and ERC-4337, developed by Biconomy. +// Learn more at https://biconomy.io. To report security issues, please contact us at: security@biconomy.io + +import { SentinelListLib } from "sentinellist/SentinelList.sol"; + +import { IHook } from "../modules/IHook.sol"; +import { CallType } from "../../lib/ModeLib.sol"; + +/// @title Nexus - IStorage Interface +/// @notice Provides structured storage for Modular Smart Account under the Nexus suite, compliant with ERC-7579 and ERC-4337. +/// @dev Manages structured storage using SentinelListLib for validators and executors, and a mapping for fallback handlers. +/// This interface utilizes ERC-7201 storage location practices to ensure isolated and collision-resistant storage spaces within smart contracts. +/// It is designed to support dynamic execution and modular management strategies essential for advanced smart account architectures. +/// @custom:storage-location erc7201:biconomy.storage.Nexus +/// @author @livingrockrises | Biconomy | chirag@biconomy.io +/// @author @aboudjem | Biconomy | adam.boudjemaa@biconomy.io +/// @author @filmakarov | Biconomy | filipp.makarov@biconomy.io +/// @author @zeroknots | Rhinestone.wtf | zeroknots.eth +/// Special thanks to the Solady team for foundational contributions: https://github.com/Vectorized/solady +interface IStorage { + /// @notice Struct storing validators and executors using Sentinel lists, and fallback handlers via mapping. + struct AccountStorage { + SentinelListLib.SentinelList validators; ///< List of validators, initialized upon contract deployment. + SentinelListLib.SentinelList executors; ///< List of executors, similarly initialized. + mapping(bytes4 => FallbackHandler) fallbacks; ///< Mapping of selectors to their respective fallback handlers. + IHook hook; ///< Current hook module associated with this account. + mapping(address hook => uint256) emergencyUninstallTimelock; ///< Mapping of hooks to requested timelocks. + } + + /// @notice Defines a fallback handler with an associated handler address and a call type. + struct FallbackHandler { + address handler; ///< The address of the fallback function handler. + CallType calltype; ///< The type of call this handler supports (e.g., static or call). + } +} diff --git a/biconomy/nexus/1.0.2/contracts/interfaces/common/IStakeable.sol b/biconomy/nexus/1.0.2/contracts/interfaces/common/IStakeable.sol new file mode 100644 index 0000000..94a2e15 --- /dev/null +++ b/biconomy/nexus/1.0.2/contracts/interfaces/common/IStakeable.sol @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.27; + +// ────────────────────────────────────────────────────────────────────────────── +// _ __ _ __ +// / | / /__ | |/ /_ _______ +// / |/ / _ \| / / / / ___/ +// / /| / __/ / /_/ (__ ) +// /_/ |_/\___/_/|_\__,_/____/ +// +// ────────────────────────────────────────────────────────────────────────────── +// Nexus: A suite of contracts for Modular Smart Accounts compliant with ERC-7579 and ERC-4337, developed by Biconomy. +// Learn more at https://biconomy.io. For security issues, contact: security@biconomy.io + +/// @title Stakeable Entity Interface +/// @notice Interface for staking, unlocking, and withdrawing Ether on an EntryPoint. +/// @dev Defines functions for managing stakes on an EntryPoint. +/// @author @livingrockrises | Biconomy | chirag@biconomy.io +/// @author @aboudjem | Biconomy | adam.boudjemaa@biconomy.io +/// @author @filmakarov | Biconomy | filipp.makarov@biconomy.io +/// @author @zeroknots | Rhinestone.wtf | zeroknots.eth +/// Special thanks to the Solady team for foundational contributions: https://github.com/Vectorized/solady +interface IStakeable { + /// @notice Stakes a certain amount of Ether on an EntryPoint. + /// @dev The contract should have enough Ether to cover the stake. + /// @param epAddress The address of the EntryPoint where the stake is added. + /// @param unstakeDelaySec The delay in seconds before the stake can be unlocked. + function addStake(address epAddress, uint32 unstakeDelaySec) external payable; + + /// @notice Unlocks the stake on an EntryPoint. + /// @dev This starts the unstaking delay after which funds can be withdrawn. + /// @param epAddress The address of the EntryPoint from which the stake is to be unlocked. + function unlockStake(address epAddress) external; + + /// @notice Withdraws the stake from an EntryPoint to a specified address. + /// @dev This can only be done after the unstaking delay has passed since the unlock. + /// @param epAddress The address of the EntryPoint where the stake is withdrawn from. + /// @param withdrawAddress The address to receive the withdrawn stake. + function withdrawStake(address epAddress, address payable withdrawAddress) external; +} diff --git a/biconomy/nexus/1.0.2/contracts/interfaces/factory/INexusFactory.sol b/biconomy/nexus/1.0.2/contracts/interfaces/factory/INexusFactory.sol new file mode 100644 index 0000000..2ad1858 --- /dev/null +++ b/biconomy/nexus/1.0.2/contracts/interfaces/factory/INexusFactory.sol @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.27; + +// ────────────────────────────────────────────────────────────────────────────── +// _ __ _ __ +// / | / /__ | |/ /_ _______ +// / |/ / _ \| / / / / ___/ +// / /| / __/ / /_/ (__ ) +// /_/ |_/\___/_/|_\__,_/____/ +// +// ────────────────────────────────────────────────────────────────────────────── +// Nexus: A suite of contracts for Modular Smart Accounts compliant with ERC-7579 and ERC-4337, developed by Biconomy. +// Learn more at https://biconomy.io. To report security issues, please contact us at: security@biconomy.io + +/// @title Interface for Abstract Nexus Factory +/// @notice Interface that provides the essential structure for Nexus factories. +/// @author @livingrockrises | Biconomy | chirag@biconomy.io +/// @author @aboudjem | Biconomy | adam.boudjemaa@biconomy.io +/// @author @filmakarov | Biconomy | filipp.makarov@biconomy.io +/// @author @zeroknots | Rhinestone.wtf | zeroknots.eth +/// Special thanks to the Solady team for foundational contributions: https://github.com/Vectorized/solady +interface INexusFactory { + /// @notice Emitted when a new Smart Account is created. + /// @param account The address of the newly created account. + /// @param initData Initialization data used for the new Smart Account. + /// @param salt Unique salt used during the creation of the Smart Account. + event AccountCreated(address indexed account, bytes indexed initData, bytes32 indexed salt); + + /// @notice Error indicating that the account is already deployed + /// @param account The address of the account that is already deployed + error AccountAlreadyDeployed(address account); + + /// @notice Error thrown when the owner address is zero. + error ZeroAddressNotAllowed(); + + /// @notice Error thrown when the implementation address is zero. + error ImplementationAddressCanNotBeZero(); + + /// @notice Creates a new Nexus with initialization data. + /// @param initData Initialization data to be called on the new Smart Account. + /// @param salt Unique salt for the Smart Account creation. + /// @return The address of the newly created Nexus. + function createAccount(bytes calldata initData, bytes32 salt) external payable returns (address payable); + + /// @notice Computes the expected address of a Nexus contract using the factory's deterministic deployment algorithm. + /// @param initData Initialization data to be called on the new Smart Account. + /// @param salt Unique salt for the Smart Account creation. + /// @return expectedAddress The expected address at which the Nexus contract will be deployed if the provided parameters are used. + function computeAccountAddress(bytes calldata initData, bytes32 salt) external view returns (address payable expectedAddress); +} diff --git a/biconomy/nexus/1.0.2/contracts/interfaces/modules/IExecutor.sol b/biconomy/nexus/1.0.2/contracts/interfaces/modules/IExecutor.sol new file mode 100644 index 0000000..111aa41 --- /dev/null +++ b/biconomy/nexus/1.0.2/contracts/interfaces/modules/IExecutor.sol @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.27; + +// ────────────────────────────────────────────────────────────────────────────── +// _ __ _ __ +// / | / /__ | |/ /_ _______ +// / |/ / _ \| / / / / ___/ +// / /| / __/ / /_/ (__ ) +// /_/ |_/\___/_/|_\__,_/____/ +// +// ────────────────────────────────────────────────────────────────────────────── +// Nexus: A suite of contracts for Modular Smart Accounts compliant with ERC-7579 and ERC-4337, developed by Biconomy. +// Learn more at https://biconomy.io. To report security issues, please contact us at: security@biconomy.io + +import { IModule } from "./IModule.sol"; + +/// @title Nexus - IExecutor Interface +/// @notice Defines the interface for Executor modules within the Nexus Smart Account framework, compliant with the ERC-7579 standard. +/// @dev Extends IModule to include functionalities specific to execution modules. +/// This interface is future-proof, allowing for expansion and integration of advanced features in subsequent versions. +/// @author @livingrockrises | Biconomy | chirag@biconomy.io +/// @author @aboudjem | Biconomy | adam.boudjemaa@biconomy.io +/// @author @filmakarov | Biconomy | filipp.makarov@biconomy.io +/// @author @zeroknots | Rhinestone.wtf | zeroknots.eth +/// Special thanks to the Solady team for foundational contributions: https://github.com/Vectorized/solady +interface IExecutor is IModule { + // Future methods for execution management will be defined here to accommodate evolving requirements. +} diff --git a/biconomy/nexus/1.0.2/contracts/interfaces/modules/IFallback.sol b/biconomy/nexus/1.0.2/contracts/interfaces/modules/IFallback.sol new file mode 100644 index 0000000..7412bdc --- /dev/null +++ b/biconomy/nexus/1.0.2/contracts/interfaces/modules/IFallback.sol @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.27; + +// ────────────────────────────────────────────────────────────────────────────── +// _ __ _ __ +// / | / /__ | |/ /_ _______ +// / |/ / _ \| / / / / ___/ +// / /| / __/ / /_/ (__ ) +// /_/ |_/\___/_/|_\__,_/____/ +// +// ────────────────────────────────────────────────────────────────────────────── +// Nexus: A suite of contracts for Modular Smart Accounts compliant with ERC-7579 and ERC-4337, developed by Biconomy. +// Learn more at https://biconomy.io. To report security issues, please contact us at: security@biconomy.io + +import { IModule } from "./IModule.sol"; + +/// @title Nexus - IFallback Interface +/// @notice Defines the interface for Fallback modules within the Nexus Smart Account framework, compliant with the ERC-7579 standard. +/// @dev Extends IModule to include functionalities specific to fallback modules. +/// This interface is future-proof, allowing for expansion and integration of advanced features in subsequent versions. +/// @author @livingrockrises | Biconomy | chirag@biconomy.io +/// @author @aboudjem | Biconomy | adam.boudjemaa@biconomy.io +/// @author @filmakarov | Biconomy | filipp.makarov@biconomy.io +/// @author @zeroknots | Rhinestone.wtf | zeroknots.eth +/// Special thanks to the Solady team for foundational contributions: https://github.com/Vectorized/solady +interface IFallback is IModule { + // Future methods for fallback management will be defined here to accommodate evolving blockchain technologies. +} diff --git a/biconomy/nexus/1.0.2/contracts/interfaces/modules/IHook.sol b/biconomy/nexus/1.0.2/contracts/interfaces/modules/IHook.sol new file mode 100644 index 0000000..d951f4c --- /dev/null +++ b/biconomy/nexus/1.0.2/contracts/interfaces/modules/IHook.sol @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.27; + +// ────────────────────────────────────────────────────────────────────────────── +// _ __ _ __ +// / | / /__ | |/ /_ _______ +// / |/ / _ \| / / / / ___/ +// / /| / __/ / /_/ (__ ) +// /_/ |_/\___/_/|_\__,_/____/ +// +// ────────────────────────────────────────────────────────────────────────────── +// Nexus: A suite of contracts for Modular Smart Accounts compliant with ERC-7579 and ERC-4337, developed by Biconomy. +// Learn more at https://biconomy.io. To report security issues, please contact us at: security@biconomy.io + +import { IModule } from "./IModule.sol"; + +/// @title Hook Management Interface +/// @notice Provides methods for pre-checks and post-checks of transactions to ensure conditions and state consistency. +/// @dev Defines two critical lifecycle hooks in the transaction process: `preCheck` and `postCheck`. +/// These methods facilitate validating conditions prior to execution and verifying state changes afterwards, respectively. +interface IHook is IModule { + /// @notice Performs checks before a transaction is executed, potentially modifying the transaction context. + /// @dev This method is called before the execution of a transaction to validate and possibly adjust execution context. + /// @param msgSender The original sender of the transaction. + /// @param msgValue The amount of wei sent with the call. + /// @param msgData The calldata of the transaction. + /// @return hookData Data that may be used or modified throughout the transaction lifecycle, passed to `postCheck`. + function preCheck(address msgSender, uint256 msgValue, bytes calldata msgData) external returns (bytes memory hookData); + + /// @notice Performs checks after a transaction is executed to ensure state consistency and log results. + /// @dev This method is called after the execution of a transaction to verify and react to the execution outcome. + /// @param hookData Data returned from `preCheck`, containing execution context or modifications. + function postCheck(bytes calldata hookData) external; +} diff --git a/biconomy/nexus/1.0.2/contracts/interfaces/modules/IModule.sol b/biconomy/nexus/1.0.2/contracts/interfaces/modules/IModule.sol new file mode 100644 index 0000000..efa9d87 --- /dev/null +++ b/biconomy/nexus/1.0.2/contracts/interfaces/modules/IModule.sol @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.27; + +// ────────────────────────────────────────────────────────────────────────────── +// _ __ _ __ +// / | / /__ | |/ /_ _______ +// / |/ / _ \| / / / / ___/ +// / /| / __/ / /_/ (__ ) +// /_/ |_/\___/_/|_\__,_/____/ +// +// ────────────────────────────────────────────────────────────────────────────── +// Nexus: A suite of contracts for Modular Smart Accounts compliant with ERC-7579 and ERC-4337, developed by Biconomy. +// Learn more at https://biconomy.io. To report security issues, please contact us at: security@biconomy.io + +/// @title Nexus - ERC-7579 Module Base Interface +/// @notice Interface for module management in smart accounts, complying with ERC-7579 specifications. +/// @dev Defines the lifecycle hooks and checks for modules within the smart account architecture. +/// This interface includes methods for installing, uninstalling, and verifying module types and initialization status. +/// @author @livingrockrises | Biconomy | chirag@biconomy.io +/// @author @aboudjem | Biconomy | adam.boudjemaa@biconomy.io +/// @author @filmakarov | Biconomy | filipp.makarov@biconomy.io +/// @author @zeroknots | Rhinestone.wtf | zeroknots.eth +/// Special thanks to the Solady team for foundational contributions: https://github.com/Vectorized/solady +interface IModule { + /// @notice Installs the module with necessary initialization data. + /// @dev Reverts if the module is already initialized. + /// @param data Arbitrary data required for initializing the module during `onInstall`. + function onInstall(bytes calldata data) external; + + /// @notice Uninstalls the module and allows for cleanup via arbitrary data. + /// @dev Reverts if any issues occur that prevent clean uninstallation. + /// @param data Arbitrary data required for deinitializing the module during `onUninstall`. + function onUninstall(bytes calldata data) external; + + /// @notice Determines if the module matches a specific module type. + /// @dev Should return true if the module corresponds to the type ID, false otherwise. + /// @param moduleTypeId Numeric ID of the module type as per ERC-7579 specifications. + /// @return True if the module is of the specified type, false otherwise. + function isModuleType(uint256 moduleTypeId) external view returns (bool); + + /// @notice Checks if the module has been initialized for a specific smart account. + /// @dev Returns true if initialized, false otherwise. + /// @param smartAccount Address of the smart account to check for initialization status. + /// @return True if the module is initialized for the given smart account, false otherwise. + function isInitialized(address smartAccount) external view returns (bool); +} diff --git a/biconomy/nexus/1.0.2/contracts/interfaces/modules/IValidator.sol b/biconomy/nexus/1.0.2/contracts/interfaces/modules/IValidator.sol new file mode 100644 index 0000000..7cbcb15 --- /dev/null +++ b/biconomy/nexus/1.0.2/contracts/interfaces/modules/IValidator.sol @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.27; + +// ────────────────────────────────────────────────────────────────────────────── +// _ __ _ __ +// / | / /__ | |/ /_ _______ +// / |/ / _ \| / / / / ___/ +// / /| / __/ / /_/ (__ ) +// /_/ |_/\___/_/|_\__,_/____/ +// +// ────────────────────────────────────────────────────────────────────────────── +// Nexus: A suite of contracts for Modular Smart Accounts compliant with ERC-7579 and ERC-4337, developed by Biconomy. +// Learn more at https://biconomy.io. To report security issues, please contact us at: security@biconomy.io + +import { PackedUserOperation } from "account-abstraction/interfaces/PackedUserOperation.sol"; + +import { IModule } from "./IModule.sol"; + +/// @author @livingrockrises | Biconomy | chirag@biconomy.io +/// @author @aboudjem | Biconomy | adam.boudjemaa@biconomy.io +/// @author @filmakarov | Biconomy | filipp.makarov@biconomy.io +/// @author @zeroknots | Rhinestone.wtf | zeroknots.eth +/// Special thanks to the Solady team for foundational contributions: https://github.com/Vectorized/solady +interface IValidator is IModule { + /// @notice Validates a user operation as per ERC-4337 standard requirements. + /// @dev Should ensure that the signature and nonce are verified correctly before the transaction is allowed to proceed. + /// The function returns a status code indicating validation success or failure. + /// @param userOp The user operation containing transaction details to be validated. + /// @param userOpHash The hash of the user operation data, used for verifying the signature. + /// @return status The result of the validation process, typically indicating success or the type of failure. + function validateUserOp(PackedUserOperation calldata userOp, bytes32 userOpHash) external returns (uint256); + + /// @notice Verifies a signature against a hash, using the sender's address as a contextual check. + /// @dev Used to confirm the validity of a signature against the specific conditions set by the sender. + /// @param sender The address from which the operation was initiated, adding an additional layer of validation against the signature. + /// @param hash The hash of the data signed. + /// @param data The signature data to validate. + /// @return magicValue A bytes4 value that corresponds to the ERC-1271 standard, indicating the validity of the signature. + function isValidSignatureWithSender(address sender, bytes32 hash, bytes calldata data) external view returns (bytes4); +} diff --git a/biconomy/nexus/1.0.2/contracts/lib/AssociatedArrayLib.sol b/biconomy/nexus/1.0.2/contracts/lib/AssociatedArrayLib.sol new file mode 100644 index 0000000..84c5c6a --- /dev/null +++ b/biconomy/nexus/1.0.2/contracts/lib/AssociatedArrayLib.sol @@ -0,0 +1,276 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.27; + +library AssociatedArrayLib { + using AssociatedArrayLib for *; + + struct Array { + uint256 _spacer; + } + + struct Bytes32Array { + Array _inner; + } + + struct AddressArray { + Array _inner; + } + + struct UintArray { + Array _inner; + } + + error AssociatedArray_OutOfBounds(uint256 index); + + function add(Bytes32Array storage s, address account, bytes32 value) internal { + if (!_contains(s._inner, account, value)) { + _push(s._inner, account, value); + } + } + + function set(Bytes32Array storage s, address account, uint256 index, bytes32 value) internal { + _set(s._inner, account, index, value); + } + + function push(Bytes32Array storage s, address account, bytes32 value) internal { + _push(s._inner, account, value); + } + + function pop(Bytes32Array storage s, address account) internal { + _pop(s._inner, account); + } + + function remove(Bytes32Array storage s, address account, uint256 index) internal { + _remove(s._inner, account, index); + } + + function add(UintArray storage s, address account, uint256 value) internal { + if (!_contains(s._inner, account, bytes32(value))) { + _push(s._inner, account, bytes32(value)); + } + } + + function set(UintArray storage s, address account, uint256 index, uint256 value) internal { + _set(s._inner, account, index, bytes32(value)); + } + + function push(UintArray storage s, address account, uint256 value) internal { + _push(s._inner, account, bytes32(value)); + } + + function pop(UintArray storage s, address account) internal { + _pop(s._inner, account); + } + + function remove(UintArray storage s, address account, uint256 index) internal { + _remove(s._inner, account, index); + } + + function add(AddressArray storage s, address account, address value) internal { + if (!_contains(s._inner, account, bytes32(uint256(uint160(value))))) { + _push(s._inner, account, bytes32(uint256(uint160(value)))); + } + } + + function set(AddressArray storage s, address account, uint256 index, address value) internal { + _set(s._inner, account, index, bytes32(uint256(uint160(value)))); + } + + function push(AddressArray storage s, address account, address value) internal { + _push(s._inner, account, bytes32(uint256(uint160(value)))); + } + + function pop(AddressArray storage s, address account) internal { + _pop(s._inner, account); + } + + function remove(AddressArray storage s, address account, uint256 index) internal { + _remove(s._inner, account, index); + } + + function length(Bytes32Array storage s, address account) internal view returns (uint256) { + return _length(s._inner, account); + } + + function get(Bytes32Array storage s, address account, uint256 index) internal view returns (bytes32) { + return _get(s._inner, account, index); + } + + function getAll(Bytes32Array storage s, address account) internal view returns (bytes32[] memory) { + return _getAll(s._inner, account); + } + + function contains(Bytes32Array storage s, address account, bytes32 value) internal view returns (bool) { + return _contains(s._inner, account, value); + } + + function length(AddressArray storage s, address account) internal view returns (uint256) { + return _length(s._inner, account); + } + + function get(AddressArray storage s, address account, uint256 index) internal view returns (address) { + return address(uint160(uint256(_get(s._inner, account, index)))); + } + + function getAll(AddressArray storage s, address account) internal view returns (address[] memory) { + bytes32[] memory bytes32Array = _getAll(s._inner, account); + address[] memory addressArray; + + /// @solidity memory-safe-assembly + assembly { + addressArray := bytes32Array + } + return addressArray; + } + + function contains(AddressArray storage s, address account, address value) internal view returns (bool) { + return _contains(s._inner, account, bytes32(uint256(uint160(value)))); + } + + function length(UintArray storage s, address account) internal view returns (uint256) { + return _length(s._inner, account); + } + + function get(UintArray storage s, address account, uint256 index) internal view returns (uint256) { + return uint256(_get(s._inner, account, index)); + } + + function getAll(UintArray storage s, address account) internal view returns (uint256[] memory) { + bytes32[] memory bytes32Array = _getAll(s._inner, account); + uint256[] memory uintArray; + + /// @solidity memory-safe-assembly + assembly { + uintArray := bytes32Array + } + return uintArray; + } + + function contains(UintArray storage s, address account, uint256 value) internal view returns (bool) { + return _contains(s._inner, account, bytes32(value)); + } + + function _set(Array storage s, address account, uint256 index, bytes32 value) private { + _set(_slot(s, account), index, value); + } + + function _set(bytes32 slot, uint256 index, bytes32 value) private { + assembly { + //if (index >= _length(s, account)) revert AssociatedArray_OutOfBounds(index); + if iszero(lt(index, sload(slot))) { + mstore(0, 0x8277484f) // `AssociatedArray_OutOfBounds(uint256)` + mstore(0x20, index) + revert(0x1c, 0x24) + } + sstore(add(slot, mul(0x20, add(index, 1))), value) + } + } + + function _push(Array storage s, address account, bytes32 value) private { + bytes32 slot = _slot(s, account); + assembly { + // load length (stored @ slot), add 1 to it => index. + // mul index by 0x20 and add it to orig slot to get the next free slot + let index := add(sload(slot), 1) + sstore(add(slot, mul(0x20, index)), value) + sstore(slot, index) //increment length by 1 + } + } + + function _pop(Array storage s, address account) private { + bytes32 slot = _slot(s, account); + uint256 __length; + assembly { + __length := sload(slot) + } + if (__length == 0) return; + _set(slot, __length - 1, 0); + assembly { + sstore(slot, sub(__length, 1)) + } + } + + function _remove(Array storage s, address account, uint256 index) private { + bytes32 slot = _slot(s, account); + uint256 __length; + assembly { + __length := sload(slot) + if iszero(lt(index, __length)) { + mstore(0, 0x8277484f) // `AssociatedArray_OutOfBounds(uint256)` + mstore(0x20, index) + revert(0x1c, 0x24) + } + } + _set(slot, index, _get(s, account, __length - 1)); + + assembly { + // clear the last slot + // this is the 'unchecked' version of _set(slot, __length - 1, 0) + // as we use length-1 as index, so the check is excessive. + // also removes extra -1 and +1 operations + sstore(add(slot, mul(0x20, __length)), 0) + // store new length + sstore(slot, sub(__length, 1)) + } + } + + function _length(Array storage s, address account) private view returns (uint256 __length) { + bytes32 slot = _slot(s, account); + assembly { + __length := sload(slot) + } + } + + function _get(Array storage s, address account, uint256 index) private view returns (bytes32 value) { + return _get(_slot(s, account), index); + } + + function _get(bytes32 slot, uint256 index) private view returns (bytes32 value) { + assembly { + //if (index >= _length(s, account)) revert AssociatedArray_OutOfBounds(index); + if iszero(lt(index, sload(slot))) { + mstore(0, 0x8277484f) // `AssociatedArray_OutOfBounds(uint256)` + mstore(0x20, index) + revert(0x1c, 0x24) + } + value := sload(add(slot, mul(0x20, add(index, 1)))) + } + } + + function _getAll(Array storage s, address account) private view returns (bytes32[] memory values) { + bytes32 slot = _slot(s, account); + uint256 __length; + assembly { + __length := sload(slot) + } + values = new bytes32[](__length); + for (uint256 i; i < __length; i++) { + values[i] = _get(slot, i); + } + } + + // inefficient. complexity = O(n) + // use with caution + // in case of large arrays, consider using EnumerableSet4337 instead + function _contains(Array storage s, address account, bytes32 value) private view returns (bool) { + bytes32 slot = _slot(s, account); + uint256 __length; + assembly { + __length := sload(slot) + } + for (uint256 i; i < __length; i++) { + if (_get(slot, i) == value) { + return true; + } + } + return false; + } + + function _slot(Array storage s, address account) private pure returns (bytes32 __slot) { + assembly { + mstore(0x00, account) + mstore(0x20, s.slot) + __slot := keccak256(0x00, 0x40) + } + } +} diff --git a/biconomy/nexus/1.0.2/contracts/lib/BootstrapLib.sol b/biconomy/nexus/1.0.2/contracts/lib/BootstrapLib.sol new file mode 100644 index 0000000..5c614ec --- /dev/null +++ b/biconomy/nexus/1.0.2/contracts/lib/BootstrapLib.sol @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.27; + +import { BootstrapConfig } from "../utils/NexusBootstrap.sol"; + +/// @title NexusBootstrap Configuration Library +/// @notice Provides utility functions to create and manage BootstrapConfig structures. +/// @author @livingrockrises | Biconomy | chirag@biconomy.io +/// @author @aboudjem | Biconomy | adam.boudjemaa@biconomy.io +/// @author @filmakarov | Biconomy | filipp.makarov@biconomy.io +/// @author @zeroknots | Rhinestone.wtf | zeroknots.eth +/// Special thanks to the Solady team for foundational contributions: https://github.com/Vectorized/solady +library BootstrapLib { + /// @notice Creates a single BootstrapConfig structure. + /// @param module The address of the module. + /// @param data The initialization data for the module. + /// @return config A BootstrapConfig structure containing the module and its data. + function createSingleConfig(address module, bytes memory data) internal pure returns (BootstrapConfig memory config) { + config.module = module; + config.data = data; + } + + /// @notice Creates an array with a single BootstrapConfig structure. + /// @param module The address of the module. + /// @param data The initialization data for the module. + /// @return config An array containing a single BootstrapConfig structure. + function createArrayConfig(address module, bytes memory data) internal pure returns (BootstrapConfig[] memory config) { + config = new BootstrapConfig[](1); + config[0].module = module; + config[0].data = data; + } + + /// @notice Creates an array of BootstrapConfig structures. + /// @param modules An array of module addresses. + /// @param datas An array of initialization data for each module. + /// @return configs An array of BootstrapConfig structures. + function createMultipleConfigs(address[] memory modules, bytes[] memory datas) internal pure returns (BootstrapConfig[] memory configs) { + require(modules.length == datas.length, "BootstrapLib: length mismatch"); + configs = new BootstrapConfig[](modules.length); + + for (uint256 i = 0; i < modules.length; i++) { + configs[i] = createSingleConfig(modules[i], datas[i]); + } + } +} diff --git a/biconomy/nexus/1.0.2/contracts/lib/EnumerableSet4337.sol b/biconomy/nexus/1.0.2/contracts/lib/EnumerableSet4337.sol new file mode 100644 index 0000000..4d70b8c --- /dev/null +++ b/biconomy/nexus/1.0.2/contracts/lib/EnumerableSet4337.sol @@ -0,0 +1,371 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.27; + +import "./AssociatedArrayLib.sol"; + +/** + * Fork of OZ's EnumerableSet that makes all storage access ERC-4337 compliant via associated storage + * @author zeroknots.eth (rhinestone) + */ +library EnumerableSet { + using AssociatedArrayLib for AssociatedArrayLib.Bytes32Array; + // To implement this library for multiple types with as little code + // repetition as possible, we write it in terms of a generic Set type with + // bytes32 values. + // The Set implementation uses private functions, and user-facing + // implementations (such as AddressSet) are just wrappers around the + // underlying Set. + // This means that we can only create new EnumerableSets for types that fit + // in bytes32. + + struct Set { + // Storage of set values + AssociatedArrayLib.Bytes32Array _values; + // Position is the index of the value in the `values` array plus 1. + // Position 0 is used to mean a value is not in the set. + mapping(bytes32 value => mapping(address account => uint256)) _positions; + } + + // Bytes32Set + + struct Bytes32Set { + Set _inner; + } + + // AddressSet + + struct AddressSet { + Set _inner; + } + + // UintSet + + struct UintSet { + Set _inner; + } + + function _removeAll(Set storage set, address account) internal { + // get length of the array + uint256 len = _length(set, account); + for (uint256 i = 1; i <= len; i++) { + // get last value + bytes32 value = _at(set, account, len - i); + _remove(set, account, value); + } + } + + /** + * @dev Add a value to a set. O(1). + * + * Returns true if the value was added to the set, that is if it was not + * already present. + */ + function add(Bytes32Set storage set, address account, bytes32 value) internal returns (bool) { + return _add(set._inner, account, value); + } + + /** + * @dev Removes a value from a set. O(1). + * + * Returns true if the value was removed from the set, that is if it was + * present. + */ + function remove(Bytes32Set storage set, address account, bytes32 value) internal returns (bool) { + return _remove(set._inner, account, value); + } + + function removeAll(Bytes32Set storage set, address account) internal { + return _removeAll(set._inner, account); + } + + /** + * @dev Add a value to a set. O(1). + * + * Returns true if the value was added to the set, that is if it was not + * already present. + */ + function add(AddressSet storage set, address account, address value) internal returns (bool) { + return _add(set._inner, account, bytes32(uint256(uint160(value)))); + } + + /** + * @dev Removes a value from a set. O(1). + * + * Returns true if the value was removed from the set, that is if it was + * present. + */ + function remove(AddressSet storage set, address account, address value) internal returns (bool) { + return _remove(set._inner, account, bytes32(uint256(uint160(value)))); + } + + function removeAll(AddressSet storage set, address account) internal { + return _removeAll(set._inner, account); + } + + /** + * @dev Add a value to a set. O(1). + * + * Returns true if the value was added to the set, that is if it was not + * already present. + */ + function add(UintSet storage set, address account, uint256 value) internal returns (bool) { + return _add(set._inner, account, bytes32(value)); + } + + /** + * @dev Removes a value from a set. O(1). + * + * Returns true if the value was removed from the set, that is if it was + * present. + */ + function remove(UintSet storage set, address account, uint256 value) internal returns (bool) { + return _remove(set._inner, account, bytes32(value)); + } + + function removeAll(UintSet storage set, address account) internal { + return _removeAll(set._inner, account); + } + + /** + * @dev Returns true if the value is in the set. O(1). + */ + function contains(Bytes32Set storage set, address account, bytes32 value) internal view returns (bool) { + return _contains(set._inner, account, value); + } + + /** + * @dev Returns the number of values in the set. O(1). + */ + function length(Bytes32Set storage set, address account) internal view returns (uint256) { + return _length(set._inner, account); + } + + /** + * @dev Returns the value stored at position `index` in the set. O(1). + * + * Note that there are no guarantees on the ordering of values inside the + * array, and it may change when more values are added or removed. + * + * Requirements: + * + * - `index` must be strictly less than {length}. + */ + function at(Bytes32Set storage set, address account, uint256 index) internal view returns (bytes32) { + return _at(set._inner, account, index); + } + + /** + * @dev Return the entire set in an array + * + * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed + * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that + * this function has an unbounded cost, and using it as part of a state-changing function may render the function + * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block. + */ + function values(Bytes32Set storage set, address account) internal view returns (bytes32[] memory) { + bytes32[] memory store = _values(set._inner, account); + bytes32[] memory result; + + /// @solidity memory-safe-assembly + assembly { + result := store + } + + return result; + } + + /** + * @dev Returns true if the value is in the set. O(1). + */ + function contains(AddressSet storage set, address account, address value) internal view returns (bool) { + return _contains(set._inner, account, bytes32(uint256(uint160(value)))); + } + + /** + * @dev Returns the number of values in the set. O(1). + */ + function length(AddressSet storage set, address account) internal view returns (uint256) { + return _length(set._inner, account); + } + + /** + * @dev Returns the value stored at position `index` in the set. O(1). + * + * Note that there are no guarantees on the ordering of values inside the + * array, and it may change when more values are added or removed. + * + * Requirements: + * + * - `index` must be strictly less than {length}. + */ + function at(AddressSet storage set, address account, uint256 index) internal view returns (address) { + return address(uint160(uint256(_at(set._inner, account, index)))); + } + + /** + * @dev Return the entire set in an array + * + * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed + * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that + * this function has an unbounded cost, and using it as part of a state-changing function may render the function + * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block. + */ + function values(AddressSet storage set, address account) internal view returns (address[] memory) { + bytes32[] memory store = _values(set._inner, account); + address[] memory result; + + /// @solidity memory-safe-assembly + assembly { + result := store + } + + return result; + } + + /** + * @dev Returns true if the value is in the set. O(1). + */ + function contains(UintSet storage set, address account, uint256 value) internal view returns (bool) { + return _contains(set._inner, account, bytes32(value)); + } + + /** + * @dev Returns the number of values in the set. O(1). + */ + function length(UintSet storage set, address account) internal view returns (uint256) { + return _length(set._inner, account); + } + + /** + * @dev Returns the value stored at position `index` in the set. O(1). + * + * Note that there are no guarantees on the ordering of values inside the + * array, and it may change when more values are added or removed. + * + * Requirements: + * + * - `index` must be strictly less than {length}. + */ + function at(UintSet storage set, address account, uint256 index) internal view returns (uint256) { + return uint256(_at(set._inner, account, index)); + } + + /** + * @dev Return the entire set in an array + * + * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed + * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that + * this function has an unbounded cost, and using it as part of a state-changing function may render the function + * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block. + */ + function values(UintSet storage set, address account) internal view returns (uint256[] memory) { + bytes32[] memory store = _values(set._inner, account); + uint256[] memory result; + + /// @solidity memory-safe-assembly + assembly { + result := store + } + + return result; + } + + /** + * @dev Add a value to a set. O(1). + * + * Returns true if the value was added to the set, that is if it was not + * already present. + */ + function _add(Set storage set, address account, bytes32 value) private returns (bool) { + if (!_contains(set, account, value)) { + set._values.push(account, value); + // The value is stored at length-1, but we add 1 to all indexes + // and use 0 as a sentinel value + set._positions[value][account] = set._values.length(account); + return true; + } else { + return false; + } + } + + /** + * @dev Removes a value from a set. O(1). + * + * Returns true if the value was removed from the set, that is if it was + * present. + */ + function _remove(Set storage set, address account, bytes32 value) private returns (bool) { + // We cache the value's position to prevent multiple reads from the same storage slot + uint256 position = set._positions[value][account]; + + if (position != 0) { + // Equivalent to contains(set, value) + // To delete an element from the _values array in O(1), we swap the element to delete with the last one in + // the array, and then remove the last element (sometimes called as 'swap and pop'). + // This modifies the order of the array, as noted in {at}. + + uint256 valueIndex = position - 1; + uint256 lastIndex = set._values.length(account) - 1; + + if (valueIndex != lastIndex) { + bytes32 lastValue = set._values.get(account, lastIndex); + + // Move the lastValue to the index where the value to delete is + set._values.set(account, valueIndex, lastValue); + // Update the tracked position of the lastValue (that was just moved) + set._positions[lastValue][account] = position; + } + + // Delete the slot where the moved value was stored + set._values.pop(account); + + // Delete the tracked position for the deleted slot + delete set._positions[value][account]; + + return true; + } else { + return false; + } + } + + /** + * @dev Returns true if the value is in the set. O(1). + */ + function _contains(Set storage set, address account, bytes32 value) private view returns (bool) { + return set._positions[value][account] != 0; + } + + /** + * @dev Returns the number of values on the set. O(1). + */ + function _length(Set storage set, address account) private view returns (uint256) { + return set._values.length(account); + } + + /** + * @dev Returns the value stored at position `index` in the set. O(1). + * + * Note that there are no guarantees on the ordering of values inside the + * array, and it may change when more values are added or removed. + * + * Requirements: + * + * - `index` must be strictly less than {length}. + */ + function _at(Set storage set, address account, uint256 index) private view returns (bytes32) { + return set._values.get(account, index); + } + + /** + * @dev Return the entire set in an array + * + * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed + * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that + * this function has an unbounded cost, and using it as part of a state-changing function may render the function + * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block. + */ + function _values(Set storage set, address account) private view returns (bytes32[] memory) { + return set._values.getAll(account); + } +} diff --git a/biconomy/nexus/1.0.2/contracts/lib/ExecLib.sol b/biconomy/nexus/1.0.2/contracts/lib/ExecLib.sol new file mode 100644 index 0000000..5040af3 --- /dev/null +++ b/biconomy/nexus/1.0.2/contracts/lib/ExecLib.sol @@ -0,0 +1,68 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.27; + +import { Execution } from "../types/DataTypes.sol"; + +/// @title ExecutionLib +/// @author zeroknots.eth | rhinestone.wtf +/// Helper Library for decoding Execution calldata +/// malloc for memory allocation is bad for gas. use this assembly instead +library ExecLib { + error InvalidBatchCallData(); + + function get2771CallData(bytes calldata cd) internal view returns (bytes memory callData) { + /// @solidity memory-safe-assembly + (cd); + assembly { + // as per solidity docs + function allocate(length) -> pos { + pos := mload(0x40) + mstore(0x40, add(pos, length)) + } + + callData := allocate(add(calldatasize(), 0x20)) //allocate extra 0x20 to store length + mstore(callData, add(calldatasize(), 0x14)) //store length, extra 0x14 is for msg.sender address + calldatacopy(add(callData, 0x20), 0, calldatasize()) + + // The msg.sender address is shifted to the left by 12 bytes to remove the padding + // Then the address without padding is stored right after the calldata + let senderPtr := allocate(0x14) + mstore(senderPtr, shl(96, caller())) + } + } + + function decodeBatch(bytes calldata callData) internal view returns (Execution[] calldata executionBatch) { + /* + * Batch Call Calldata Layout + * Offset (in bytes) | Length (in bytes) | Contents + * 0x0 | 0x4 | bytes4 function selector + * 0x4 | - | abi.encode(IERC7579Execution.Execution[]) + */ + assembly ("memory-safe") { + let dataPointer := add(callData.offset, calldataload(callData.offset)) + // Extract the ERC7579 Executions + executionBatch.offset := add(dataPointer, 32) + executionBatch.length := calldataload(dataPointer) + } + } + + function encodeBatch(Execution[] memory executions) internal pure returns (bytes memory callData) { + callData = abi.encode(executions); + } + + function decodeSingle(bytes calldata executionCalldata) internal pure returns (address target, uint256 value, bytes calldata callData) { + target = address(bytes20(executionCalldata[0:20])); + value = uint256(bytes32(executionCalldata[20:52])); + callData = executionCalldata[52:]; + } + + function decodeDelegateCall(bytes calldata executionCalldata) internal pure returns (address delegate, bytes calldata callData) { + // destructure executionCallData according to single exec + delegate = address(uint160(bytes20(executionCalldata[0:20]))); + callData = executionCalldata[20:]; + } + + function encodeSingle(address target, uint256 value, bytes memory callData) internal pure returns (bytes memory userOpCalldata) { + userOpCalldata = abi.encodePacked(target, value, callData); + } +} diff --git a/biconomy/nexus/1.0.2/contracts/lib/ModeLib.sol b/biconomy/nexus/1.0.2/contracts/lib/ModeLib.sol new file mode 100644 index 0000000..b520c96 --- /dev/null +++ b/biconomy/nexus/1.0.2/contracts/lib/ModeLib.sol @@ -0,0 +1,156 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.27; + +/// @title ModeLib +/// @author zeroknots.eth | rhinestone.wtf +/// To allow smart accounts to be very simple, but allow for more complex execution, A custom mode +/// encoding is used. +/// Function Signature of execute function: +/// function execute(ExecutionMode mode, bytes calldata executionCalldata) external payable; +/// This allows for a single bytes32 to be used to encode the execution mode, calltype, execType and +/// context. +/// NOTE: Simple Account implementations only have to scope for the most significant byte. Account that +/// implement +/// more complex execution modes may use the entire bytes32. +/// +/// |--------------------------------------------------------------------| +/// | CALLTYPE | EXECTYPE | UNUSED | ModeSelector | ModePayload | +/// |--------------------------------------------------------------------| +/// | 1 byte | 1 byte | 4 bytes | 4 bytes | 22 bytes | +/// |--------------------------------------------------------------------| +/// +/// CALLTYPE: 1 byte +/// CallType is used to determine how the executeCalldata paramter of the execute function has to be +/// decoded. +/// It can be either single, batch or delegatecall. In the future different calls could be added. +/// CALLTYPE can be used by a validation module to determine how to decode . +/// +/// EXECTYPE: 1 byte +/// ExecType is used to determine how the account should handle the execution. +/// It can indicate if the execution should revert on failure or continue execution. +/// In the future more execution modes may be added. +/// Default Behavior (EXECTYPE = 0x00) is to revert on a single failed execution. If one execution in +/// a batch fails, the entire batch is reverted +/// +/// UNUSED: 4 bytes +/// Unused bytes are reserved for future use. +/// +/// ModeSelector: bytes4 +/// The "optional" mode selector can be used by account vendors, to implement custom behavior in +/// their accounts. +/// the way a ModeSelector is to be calculated is bytes4(keccak256("vendorname.featurename")) +/// this is to prevent collisions between different vendors, while allowing innovation and the +/// development of new features without coordination between ERC-7579 implementing accounts +/// +/// ModePayload: 22 bytes +/// Mode payload is used to pass additional data to the smart account execution, this may be +/// interpreted depending on the ModeSelector +/// +/// ExecutionCallData: n bytes +/// single, delegatecall or batch exec abi.encoded as bytes + +// Custom type for improved developer experience +type ExecutionMode is bytes32; + +type CallType is bytes1; + +type ExecType is bytes1; + +type ModeSelector is bytes4; + +type ModePayload is bytes22; + +// Default CallType +CallType constant CALLTYPE_SINGLE = CallType.wrap(0x00); +// Batched CallType +CallType constant CALLTYPE_BATCH = CallType.wrap(0x01); + +CallType constant CALLTYPE_STATIC = CallType.wrap(0xFE); + +// @dev Implementing delegatecall is OPTIONAL! +// implement delegatecall with extreme care. +CallType constant CALLTYPE_DELEGATECALL = CallType.wrap(0xFF); + +// @dev default behavior is to revert on failure +// To allow very simple accounts to use mode encoding, the default behavior is to revert on failure +// Since this is value 0x00, no additional encoding is required for simple accounts +ExecType constant EXECTYPE_DEFAULT = ExecType.wrap(0x00); +// @dev account may elect to change execution behavior. For example "try exec" / "allow fail" +ExecType constant EXECTYPE_TRY = ExecType.wrap(0x01); + +ModeSelector constant MODE_DEFAULT = ModeSelector.wrap(bytes4(0x00000000)); +// Example declaration of a custom mode selector +ModeSelector constant MODE_OFFSET = ModeSelector.wrap(bytes4(keccak256("default.mode.offset"))); + +/// @dev ModeLib is a helper library to encode/decode ModeCodes +library ModeLib { + function decode( + ExecutionMode mode + ) internal pure returns (CallType _calltype, ExecType _execType, ModeSelector _modeSelector, ModePayload _modePayload) { + assembly { + _calltype := mode + _execType := shl(8, mode) + _modeSelector := shl(48, mode) + _modePayload := shl(80, mode) + } + } + + function decodeBasic(ExecutionMode mode) internal pure returns (CallType _calltype, ExecType _execType) { + assembly { + _calltype := mode + _execType := shl(8, mode) + } + } + + function encode(CallType callType, ExecType execType, ModeSelector mode, ModePayload payload) internal pure returns (ExecutionMode) { + return ExecutionMode.wrap(bytes32(abi.encodePacked(callType, execType, bytes4(0), ModeSelector.unwrap(mode), payload))); + } + + function encodeSimpleBatch() internal pure returns (ExecutionMode mode) { + mode = encode(CALLTYPE_BATCH, EXECTYPE_DEFAULT, MODE_DEFAULT, ModePayload.wrap(0x00)); + } + + function encodeSimpleSingle() internal pure returns (ExecutionMode mode) { + mode = encode(CALLTYPE_SINGLE, EXECTYPE_DEFAULT, MODE_DEFAULT, ModePayload.wrap(0x00)); + } + + function encodeTrySingle() internal pure returns (ExecutionMode mode) { + mode = encode(CALLTYPE_SINGLE, EXECTYPE_TRY, MODE_DEFAULT, ModePayload.wrap(0x00)); + } + + function encodeTryBatch() internal pure returns (ExecutionMode mode) { + mode = encode(CALLTYPE_BATCH, EXECTYPE_TRY, MODE_DEFAULT, ModePayload.wrap(0x00)); + } + + function encodeCustom(CallType callType, ExecType execType) internal pure returns (ExecutionMode mode) { + mode = encode(callType, execType, MODE_DEFAULT, ModePayload.wrap(0x00)); + } + + function getCallType(ExecutionMode mode) internal pure returns (CallType calltype) { + assembly { + calltype := mode + } + } +} + +using { _eqModeSelector as == } for ModeSelector global; +using { _eqCallType as == } for CallType global; +using { _uneqCallType as != } for CallType global; +using { _eqExecType as == } for ExecType global; + +function _eqCallType(CallType a, CallType b) pure returns (bool) { + return CallType.unwrap(a) == CallType.unwrap(b); +} + +function _uneqCallType(CallType a, CallType b) pure returns (bool) { + return CallType.unwrap(a) != CallType.unwrap(b); +} + +function _eqExecType(ExecType a, ExecType b) pure returns (bool) { + return ExecType.unwrap(a) == ExecType.unwrap(b); +} + +//slither-disable-next-line dead-code +function _eqModeSelector(ModeSelector a, ModeSelector b) pure returns (bool) { + return ModeSelector.unwrap(a) == ModeSelector.unwrap(b); +} diff --git a/biconomy/nexus/1.0.2/contracts/lib/NonceLib.sol b/biconomy/nexus/1.0.2/contracts/lib/NonceLib.sol new file mode 100644 index 0000000..bbff750 --- /dev/null +++ b/biconomy/nexus/1.0.2/contracts/lib/NonceLib.sol @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.27; + +import { MODE_MODULE_ENABLE } from "../types/Constants.sol"; + +/** + Nonce structure + [3 bytes empty][1 bytes validation mode][20 bytes validator][8 bytes nonce] +*/ + +library NonceLib { + /// @dev Parses validator address out of nonce + /// @param nonce The nonce + /// @return validator + function getValidator(uint256 nonce) internal pure returns (address validator) { + assembly { + validator := shr(96, shl(32, nonce)) + } + } + + /// @dev Detects if Validaton Mode is Module Enable Mode + /// @param nonce The nonce + /// @return res boolean result, true if it is the Module Enable Mode + function isModuleEnableMode(uint256 nonce) internal pure returns (bool res) { + assembly { + let vmode := byte(3, nonce) + res := eq(shl(248, vmode), MODE_MODULE_ENABLE) + } + } +} diff --git a/biconomy/nexus/1.0.2/contracts/lib/local/LocalCallDataParserLib.sol b/biconomy/nexus/1.0.2/contracts/lib/local/LocalCallDataParserLib.sol new file mode 100644 index 0000000..b090a1a --- /dev/null +++ b/biconomy/nexus/1.0.2/contracts/lib/local/LocalCallDataParserLib.sol @@ -0,0 +1,65 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.27; + +library LocalCallDataParserLib { + /// @dev Parses the `userOp.signature` to extract the module type, module initialization data, + /// enable mode signature, and user operation signature. The `userOp.signature` must be + /// encoded in a specific way to be parsed correctly. + /// @param packedData The packed signature data, typically coming from `userOp.signature`. + /// @return module The address of the module. + /// @return moduleType The type of module as a `uint256`. + /// @return moduleInitData Initialization data specific to the module. + /// @return enableModeSignature Signature used to enable the module mode. + /// @return userOpSignature The remaining user operation signature data. + function parseEnableModeData( + bytes calldata packedData + ) + internal + pure + returns ( + address module, + uint256 moduleType, + bytes calldata moduleInitData, + bytes calldata enableModeSignature, + bytes calldata userOpSignature + ) + { + uint256 p; + assembly ("memory-safe") { + p := packedData.offset + module := shr(96, calldataload(p)) + + p := add(p, 0x14) + moduleType := calldataload(p) + + moduleInitData.length := shr(224, calldataload(add(p, 0x20))) + moduleInitData.offset := add(p, 0x24) + p := add(moduleInitData.offset, moduleInitData.length) + + enableModeSignature.length := shr(224, calldataload(p)) + enableModeSignature.offset := add(p, 0x04) + p := sub(add(enableModeSignature.offset, enableModeSignature.length), packedData.offset) + } + userOpSignature = packedData[p:]; + } + + /// @dev Parses the data to obtain types and initdata's for Multi Type module install mode + /// @param initData Multi Type module init data, abi.encoded + function parseMultiTypeInitData(bytes calldata initData) internal pure returns (uint256[] calldata types, bytes[] calldata initDatas) { + // equivalent of: + // (types, initDatas) = abi.decode(initData,(uint[],bytes[])) + assembly ("memory-safe") { + let offset := initData.offset + let baseOffset := offset + let dataPointer := add(baseOffset, calldataload(offset)) + + types.offset := add(dataPointer, 32) + types.length := calldataload(dataPointer) + offset := add(offset, 32) + + dataPointer := add(baseOffset, calldataload(offset)) + initDatas.offset := add(dataPointer, 32) + initDatas.length := calldataload(dataPointer) + } + } +} diff --git a/biconomy/nexus/1.0.2/contracts/modules/validators/K1Validator.sol b/biconomy/nexus/1.0.2/contracts/modules/validators/K1Validator.sol new file mode 100644 index 0000000..110fcfc --- /dev/null +++ b/biconomy/nexus/1.0.2/contracts/modules/validators/K1Validator.sol @@ -0,0 +1,276 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.27; + +// ────────────────────────────────────────────────────────────────────────────── +// _ __ _ __ +// / | / /__ | |/ /_ _______ +// / |/ / _ \| / / / / ___/ +// / /| / __/ / /_/ (__ ) +// /_/ |_/\___/_/|_\__,_/____/ +// +// ────────────────────────────────────────────────────────────────────────────── +// Nexus: A suite of contracts for Modular Smart Accounts compliant with ERC-7579 and ERC-4337, developed by Biconomy. +// Learn more at https://biconomy.io. To report security issues, please contact us at: security@biconomy.io + +import { ECDSA } from "solady/utils/ECDSA.sol"; +import { PackedUserOperation } from "account-abstraction/interfaces/PackedUserOperation.sol"; +import { ERC7739Validator } from "erc7739Validator/ERC7739Validator.sol"; +import { IValidator } from "../../interfaces/modules/IValidator.sol"; +import { EnumerableSet } from "../../lib/EnumerableSet4337.sol"; +import { MODULE_TYPE_VALIDATOR, VALIDATION_SUCCESS, VALIDATION_FAILED } from "../../types/Constants.sol"; + +/// @title Nexus - K1Validator (ECDSA) +/// @notice Validator module for smart accounts, verifying user operation signatures +/// based on the K1 curve (secp256k1), a widely used ECDSA algorithm. +/// @dev Implements secure ownership validation by checking signatures against registered +/// owners. This module supports ERC-7579 and ERC-4337 standards, ensuring only the +/// legitimate owner of a smart account can authorize transactions. +/// Implements ERC-7739 +/// @author @livingrockrises | Biconomy | chirag@biconomy.io +/// @author @aboudjem | Biconomy | adam.boudjemaa@biconomy.io +/// @author @filmakarov | Biconomy | filipp.makarov@biconomy.io +/// @author @zeroknots | Rhinestone.wtf | zeroknots.eth +/// Special thanks to the Solady team for foundational contributions: https://github.com/Vectorized/solady +contract K1Validator is IValidator, ERC7739Validator { + using ECDSA for bytes32; + using EnumerableSet for EnumerableSet.AddressSet; + + /*////////////////////////////////////////////////////////////////////////// + CONSTANTS & STORAGE + //////////////////////////////////////////////////////////////////////////*/ + + /// @notice Mapping of smart account addresses to their respective owner addresses + mapping(address => address) public smartAccountOwners; + + EnumerableSet.AddressSet private _safeSenders; + + /// @notice Error to indicate that no owner was provided during installation + error NoOwnerProvided(); + + /// @notice Error to indicate that the new owner cannot be the zero address + error ZeroAddressNotAllowed(); + + /// @notice Error to indicate the module is already initialized + error ModuleAlreadyInitialized(); + + /// @notice Error to indicate that the new owner cannot be a contract address + error NewOwnerIsContract(); + + /// @notice Error to indicate that the owner cannot be the zero address + error OwnerCannotBeZeroAddress(); + + /// @notice Error to indicate that the data length is invalid + error InvalidDataLength(); + + /*////////////////////////////////////////////////////////////////////////// + CONFIG + //////////////////////////////////////////////////////////////////////////*/ + + /** + * Initialize the module with the given data + * + * @param data The data to initialize the module with + */ + function onInstall(bytes calldata data) external override { + require(data.length != 0, NoOwnerProvided()); + require(!_isInitialized(msg.sender), ModuleAlreadyInitialized()); + address newOwner = address(bytes20(data[:20])); + require(newOwner != address(0), OwnerCannotBeZeroAddress()); + require(!_isContract(newOwner), NewOwnerIsContract()); + smartAccountOwners[msg.sender] = newOwner; + if (data.length > 20) { + _fillSafeSenders(data[20:]); + } + } + + /** + * De-initialize the module with the given data + */ + function onUninstall(bytes calldata) external override { + delete smartAccountOwners[msg.sender]; + _safeSenders.removeAll(msg.sender); + } + + /// @notice Transfers ownership of the validator to a new owner + /// @param newOwner The address of the new owner + function transferOwnership(address newOwner) external { + require(newOwner != address(0), ZeroAddressNotAllowed()); + require(!_isContract(newOwner), NewOwnerIsContract()); + smartAccountOwners[msg.sender] = newOwner; + } + + /// @notice Adds a safe sender to the _safeSenders list for the smart account + function addSafeSender(address sender) external { + _safeSenders.add(msg.sender, sender); + } + + /// @notice Removes a safe sender from the _safeSenders list for the smart account + function removeSafeSender(address sender) external { + _safeSenders.remove(msg.sender, sender); + } + + /// @notice Checks if a sender is in the _safeSenders list for the smart account + function isSafeSender(address sender, address smartAccount) external view returns (bool) { + return _safeSenders.contains(smartAccount, sender); + } + + /** + * Check if the module is initialized + * @param smartAccount The smart account to check + * + * @return true if the module is initialized, false otherwise + */ + function isInitialized(address smartAccount) external view returns (bool) { + return _isInitialized(smartAccount); + } + + /*////////////////////////////////////////////////////////////////////////// + MODULE LOGIC + //////////////////////////////////////////////////////////////////////////*/ + + /** + * Validates PackedUserOperation + * + * @param userOp UserOperation to be validated + * @param userOpHash Hash of the UserOperation to be validated + * + * @return uint256 the result of the signature validation, which can be: + * - 0 if the signature is valid + * - 1 if the signature is invalid + * - <20-byte> aggregatorOrSigFail, <6-byte> validUntil and <6-byte> validAfter (see ERC-4337 + * for more details) + */ + function validateUserOp(PackedUserOperation calldata userOp, bytes32 userOpHash) external view override returns (uint256) { + return _validateSignatureForOwner(smartAccountOwners[userOp.sender], userOpHash, userOp.signature) ? VALIDATION_SUCCESS : VALIDATION_FAILED; + } + + /** + * Validates an ERC-1271 signature + * @dev implements signature malleability prevention + * see: https://eips.ethereum.org/EIPS/eip-1271#reference-implementation + * Please note, that this prevention does not protect against replay attacks in general + * So the protocol using ERC-1271 should make sure hash is replay-safe. + * + * @param sender The sender of the ERC-1271 call to the account + * @param hash The hash of the message + * @param signature The signature of the message + * + * @return sigValidationResult the result of the signature validation, which can be: + * - EIP1271_SUCCESS if the signature is valid + * - EIP1271_FAILED if the signature is invalid + * - 0x7739000X if this is the ERC-7739 support detection request. + * Where X is the version of the ERC-7739 support. + */ + function isValidSignatureWithSender( + address sender, + bytes32 hash, + bytes calldata signature + ) external view virtual override returns (bytes4) { + return _erc1271IsValidSignatureWithSender(sender, hash, _erc1271UnwrapSignature(signature)); + } + + /// @notice ISessionValidator interface for smart session + /// @param hash The hash of the data to validate + /// @param sig The signature data + /// @param data The data to validate against (owner address in this case) + function validateSignatureWithData(bytes32 hash, bytes calldata sig, bytes calldata data) external view returns (bool validSig) { + require(data.length == 20, InvalidDataLength()); + address owner = address(bytes20(data[0:20])); + return _validateSignatureForOwner(owner, hash, sig); + } + + /*////////////////////////////////////////////////////////////////////////// + METADATA + //////////////////////////////////////////////////////////////////////////*/ + + /// @notice Returns the name of the module + /// @return The name of the module + function name() external pure returns (string memory) { + return "K1Validator"; + } + + /// @notice Returns the version of the module + /// @return The version of the module + function version() external pure returns (string memory) { + return "1.0.1"; + } + + /// @notice Checks if the module is of the specified type + /// @param typeId The type ID to check + /// @return True if the module is of the specified type, false otherwise + function isModuleType(uint256 typeId) external pure returns (bool) { + return typeId == MODULE_TYPE_VALIDATOR; + } + + /*////////////////////////////////////////////////////////////////////////// + INTERNAL + //////////////////////////////////////////////////////////////////////////*/ + + /// @notice Recovers the signer from a signature + /// @param hash The hash of the data to validate + /// @param signature The signature data + /// @return The recovered signer address + /// @notice tryRecover returns address(0) on invalid signature + function _recoverSigner(bytes32 hash, bytes calldata signature) internal view returns (address) { + return hash.tryRecover(signature); + } + + /// @dev Returns whether the `hash` and `signature` are valid. + /// Obtains the authorized signer's credentials and calls some + /// module's specific internal function to validate the signature + /// against credentials. + function _erc1271IsValidSignatureNowCalldata(bytes32 hash, bytes calldata signature) internal view override returns (bool) { + // call custom internal function to validate the signature against credentials + return _validateSignatureForOwner(smartAccountOwners[msg.sender], hash, signature); + } + + /// @dev Returns whether the `sender` is considered safe, such + /// that we don't need to use the nested EIP-712 workflow. + /// See: https://mirror.xyz/curiousapple.eth/pFqAdW2LiJ-6S4sg_u1z08k4vK6BCJ33LcyXpnNb8yU + // The canonical `MulticallerWithSigner` at 0x000000000000D9ECebf3C23529de49815Dac1c4c + // is known to include the account in the hash to be signed. + // msg.sender = Smart Account + // sender = 1271 og request sender + function _erc1271CallerIsSafe(address sender) internal view virtual override returns (bool) { + return (sender == 0x000000000000D9ECebf3C23529de49815Dac1c4c || // MulticallerWithSigner + sender == msg.sender || // Smart Account. Assume smart account never sends non safe eip-712 struct + _safeSenders.contains(msg.sender, sender)); // check if sender is in _safeSenders for the Smart Account + } + + /// @notice Internal method that does the job of validating the signature via ECDSA (secp256k1) + /// @param owner The address of the owner + /// @param hash The hash of the data to validate + /// @param signature The signature data + function _validateSignatureForOwner(address owner, bytes32 hash, bytes calldata signature) internal view returns (bool) { + // verify signer + // owner can not be zero address in this contract + if (_recoverSigner(hash, signature) == owner) return true; + if (_recoverSigner(hash.toEthSignedMessageHash(), signature) == owner) return true; + return false; + } + + // @notice Fills the _safeSenders list from the given data + function _fillSafeSenders(bytes calldata data) private { + for (uint256 i; i < data.length / 20; i++) { + _safeSenders.add(msg.sender, address(bytes20(data[20 * i:20 * (i + 1)]))); + } + } + + /// @notice Checks if the smart account is initialized with an owner + /// @param smartAccount The address of the smart account + /// @return True if the smart account has an owner, false otherwise + function _isInitialized(address smartAccount) private view returns (bool) { + return smartAccountOwners[smartAccount] != address(0); + } + + /// @notice Checks if the address is a contract + /// @param account The address to check + /// @return True if the address is a contract, false otherwise + function _isContract(address account) private view returns (bool) { + uint256 size; + assembly { + size := extcodesize(account) + } + return size > 0; + } +} diff --git a/biconomy/nexus/1.0.2/contracts/types/Constants.sol b/biconomy/nexus/1.0.2/contracts/types/Constants.sol new file mode 100644 index 0000000..22e00ba --- /dev/null +++ b/biconomy/nexus/1.0.2/contracts/types/Constants.sol @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.27; + +// ────────────────────────────────────────────────────────────────────────────── +// _ __ _ __ +// / | / /__ | |/ /_ _______ +// / |/ / _ \| / / / / ___/ +// / /| / __/ / /_/ (__ ) +// /_/ |_/\___/_/|_\__,_/____/ +// +// ────────────────────────────────────────────────────────────────────────────── +// Nexus: A suite of contracts for Modular Smart Accounts compliant with ERC-7579 and ERC-4337, developed by Biconomy. +// Learn more at https://biconomy.io. To report security issues, please contact us at: security@biconomy.io + +// Magic value for ERC-1271 valid signature +bytes4 constant ERC1271_MAGICVALUE = 0x1626ba7e; + +// Value indicating an invalid ERC-1271 signature +bytes4 constant ERC1271_INVALID = 0xFFFFFFFF; + +// Value indicating successful validation +uint256 constant VALIDATION_SUCCESS = 0; + +// Value indicating failed validation +uint256 constant VALIDATION_FAILED = 1; + +// Module type identifier for Multitype install +uint256 constant MODULE_TYPE_MULTI = 0; + +// Module type identifier for validators +uint256 constant MODULE_TYPE_VALIDATOR = 1; + +// Module type identifier for executors +uint256 constant MODULE_TYPE_EXECUTOR = 2; + +// Module type identifier for fallback handlers +uint256 constant MODULE_TYPE_FALLBACK = 3; + +// Module type identifier for hooks +uint256 constant MODULE_TYPE_HOOK = 4; + +string constant MODULE_ENABLE_MODE_NOTATION = "ModuleEnableMode(address module,uint256 moduleType,bytes32 userOpHash,bytes32 initDataHash)"; +bytes32 constant MODULE_ENABLE_MODE_TYPE_HASH = keccak256(bytes(MODULE_ENABLE_MODE_NOTATION)); + +// Validation modes +bytes1 constant MODE_VALIDATION = 0x00; +bytes1 constant MODE_MODULE_ENABLE = 0x01; + +bytes4 constant SUPPORTS_ERC7739 = 0x77390000; +bytes4 constant SUPPORTS_ERC7739_V1 = 0x77390001; \ No newline at end of file diff --git a/biconomy/nexus/1.0.2/contracts/types/DataTypes.sol b/biconomy/nexus/1.0.2/contracts/types/DataTypes.sol new file mode 100644 index 0000000..0215732 --- /dev/null +++ b/biconomy/nexus/1.0.2/contracts/types/DataTypes.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.27; + +// ────────────────────────────────────────────────────────────────────────────── +// _ __ _ __ +// / | / /__ | |/ /_ _______ +// / |/ / _ \| / / / / ___/ +// / /| / __/ / /_/ (__ ) +// /_/ |_/\___/_/|_\__,_/____/ +// +// ────────────────────────────────────────────────────────────────────────────── +// Nexus: A suite of contracts for Modular Smart Accounts compliant with ERC-7579 and ERC-4337, developed by Biconomy. +// Learn more at https://biconomy.io. To report security issues, please contact us at: security@biconomy.io + +/// @title Execution +/// @notice Struct to encapsulate execution data for a transaction +struct Execution { + /// @notice The target address for the transaction + address target; + /// @notice The value in wei to send with the transaction + uint256 value; + /// @notice The calldata for the transaction + bytes callData; +} diff --git a/biconomy/nexus/1.0.2/contracts/utils/NexusBootstrap.sol b/biconomy/nexus/1.0.2/contracts/utils/NexusBootstrap.sol new file mode 100644 index 0000000..3cebd3b --- /dev/null +++ b/biconomy/nexus/1.0.2/contracts/utils/NexusBootstrap.sol @@ -0,0 +1,165 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.27; + +// ────────────────────────────────────────────────────────────────────────────── +// _ __ _ __ +// / | / /__ | |/ /_ _______ +// / |/ / _ \| / / / / ___/ +// / /| / __/ / /_/ (__ ) +// /_/ |_/\___/_/|_\__,_/____/ +// +// ────────────────────────────────────────────────────────────────────────────── +// Nexus: A suite of contracts for Modular Smart Accounts compliant with ERC-7579 and ERC-4337, developed by Biconomy. +// Learn more at https://biconomy.io. For security issues, contact: security@biconomy.io + +import { ModuleManager } from "../base/ModuleManager.sol"; +import { IModule } from "../interfaces/modules/IModule.sol"; +import { IERC7484 } from "../interfaces/IERC7484.sol"; + +/// @title NexusBootstrap Configuration for Nexus +/// @notice Provides configuration and initialization for Nexus smart accounts. +/// @author @livingrockrises | Biconomy | chirag@biconomy.io +/// @author @aboudjem | Biconomy | adam.boudjemaa@biconomy.io +/// @author @filmakarov | Biconomy | filipp.makarov@biconomy.io +/// @author @zeroknots | Rhinestone.wtf | zeroknots.eth +/// Special thanks to the Solady team for foundational contributions: https://github.com/Vectorized/solady +struct BootstrapConfig { + address module; + bytes data; +} + +/// @title NexusBootstrap +/// @notice Manages the installation of modules into Nexus smart accounts using delegatecalls. +contract NexusBootstrap is ModuleManager { + /// @notice Initializes the Nexus account with a single validator. + /// @dev Intended to be called by the Nexus with a delegatecall. + /// @param validator The address of the validator module. + /// @param data The initialization data for the validator module. + function initNexusWithSingleValidator( + IModule validator, + bytes calldata data, + IERC7484 registry, + address[] calldata attesters, + uint8 threshold + ) external { + _configureRegistry(registry, attesters, threshold); + _installValidator(address(validator), data); + } + + /// @notice Initializes the Nexus account with multiple modules. + /// @dev Intended to be called by the Nexus with a delegatecall. + /// @param validators The configuration array for validator modules. + /// @param executors The configuration array for executor modules. + /// @param hook The configuration for the hook module. + /// @param fallbacks The configuration array for fallback handler modules. + function initNexus( + BootstrapConfig[] calldata validators, + BootstrapConfig[] calldata executors, + BootstrapConfig calldata hook, + BootstrapConfig[] calldata fallbacks, + IERC7484 registry, + address[] calldata attesters, + uint8 threshold + ) external { + _configureRegistry(registry, attesters, threshold); + + // Initialize validators + for (uint256 i = 0; i < validators.length; i++) { + _installValidator(validators[i].module, validators[i].data); + } + + // Initialize executors + for (uint256 i = 0; i < executors.length; i++) { + if (executors[i].module == address(0)) continue; + _installExecutor(executors[i].module, executors[i].data); + } + + // Initialize hook + if (hook.module != address(0)) { + _installHook(hook.module, hook.data); + } + + // Initialize fallback handlers + for (uint256 i = 0; i < fallbacks.length; i++) { + if (fallbacks[i].module == address(0)) continue; + _installFallbackHandler(fallbacks[i].module, fallbacks[i].data); + } + } + + /// @notice Initializes the Nexus account with a scoped set of modules. + /// @dev Intended to be called by the Nexus with a delegatecall. + /// @param validators The configuration array for validator modules. + /// @param hook The configuration for the hook module. + function initNexusScoped( + BootstrapConfig[] calldata validators, + BootstrapConfig calldata hook, + IERC7484 registry, + address[] calldata attesters, + uint8 threshold + ) external { + _configureRegistry(registry, attesters, threshold); + + // Initialize validators + for (uint256 i = 0; i < validators.length; i++) { + _installValidator(validators[i].module, validators[i].data); + } + + // Initialize hook + if (hook.module != address(0)) { + _installHook(hook.module, hook.data); + } + } + + /// @notice Prepares calldata for the initNexus function. + /// @param validators The configuration array for validator modules. + /// @param executors The configuration array for executor modules. + /// @param hook The configuration for the hook module. + /// @param fallbacks The configuration array for fallback handler modules. + /// @return init The prepared calldata for initNexus. + function getInitNexusCalldata( + BootstrapConfig[] calldata validators, + BootstrapConfig[] calldata executors, + BootstrapConfig calldata hook, + BootstrapConfig[] calldata fallbacks, + IERC7484 registry, + address[] calldata attesters, + uint8 threshold + ) external view returns (bytes memory init) { + init = abi.encode(address(this), abi.encodeCall(this.initNexus, (validators, executors, hook, fallbacks, registry, attesters, threshold))); + } + + /// @notice Prepares calldata for the initNexusScoped function. + /// @param validators The configuration array for validator modules. + /// @param hook The configuration for the hook module. + /// @return init The prepared calldata for initNexusScoped. + function getInitNexusScopedCalldata( + BootstrapConfig[] calldata validators, + BootstrapConfig calldata hook, + IERC7484 registry, + address[] calldata attesters, + uint8 threshold + ) external view returns (bytes memory init) { + init = abi.encode(address(this), abi.encodeCall(this.initNexusScoped, (validators, hook, registry, attesters, threshold))); + } + + /// @notice Prepares calldata for the initNexusWithSingleValidator function. + /// @param validator The configuration for the validator module. + /// @return init The prepared calldata for initNexusWithSingleValidator. + function getInitNexusWithSingleValidatorCalldata( + BootstrapConfig calldata validator, + IERC7484 registry, + address[] calldata attesters, + uint8 threshold + ) external view returns (bytes memory init) { + init = abi.encode( + address(this), + abi.encodeCall(this.initNexusWithSingleValidator, (IModule(validator.module), validator.data, registry, attesters, threshold)) + ); + } + + /// @dev EIP712 domain name and version. + function _domainNameAndVersion() internal pure override returns (string memory name, string memory version) { + name = "NexusBootstrap"; + version = "1.0.0"; + } +} diff --git a/biconomy/nexus/1.0.2/expected b/biconomy/nexus/1.0.2/expected new file mode 100644 index 0000000..1a60151 --- /dev/null +++ b/biconomy/nexus/1.0.2/expected @@ -0,0 +1,7 @@ +EntryPoint 0x0000000071727De22E5E9d8BAf0edAc6f37da032 +K1Validator 0x0000002D6DB27c52E3C11c1Cf24072004AC75cBa +K1ValidatorFactory 0x2828A0E0f36d8d8BeAE95F00E2BbF235e4230fAc +Nexus 0x000000aC74357BFEa72BBD0781833631F732cf19 +NexusAccountFactory 0x000000c3A93d2c5E02Cb053AC675665b1c4217F9 +NexusBootstrap 0x879fa30248eeb693dcCE3eA94a743622170a3658 +Registry 0x000000000069E2a187AEFFb852bF3cCdC95151B2 diff --git a/biconomy/nexus/1.0.2/foundry.toml b/biconomy/nexus/1.0.2/foundry.toml new file mode 100644 index 0000000..27ade23 --- /dev/null +++ b/biconomy/nexus/1.0.2/foundry.toml @@ -0,0 +1,15 @@ +[profile.default] +src = "contracts" +out = "out" +libs = ["lib"] +auto_detect_remappings = false +auto_detect_solc = false +solc_version = "0.8.27" +evm_version = "cancun" +via_ir = false +cbor_metadata = true +use_literal_content = false +bytecode_hash = "none" +optimizer = true +optimizer_runs = 999 +libraries = [] diff --git a/biconomy/nexus/1.0.2/node_modules/account-abstraction/contracts/interfaces/IAggregator.sol b/biconomy/nexus/1.0.2/node_modules/account-abstraction/contracts/interfaces/IAggregator.sol new file mode 100644 index 0000000..070d8f2 --- /dev/null +++ b/biconomy/nexus/1.0.2/node_modules/account-abstraction/contracts/interfaces/IAggregator.sol @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity >=0.7.5; + +import "./PackedUserOperation.sol"; + +/** + * Aggregated Signatures validator. + */ +interface IAggregator { + /** + * Validate aggregated signature. + * Revert if the aggregated signature does not match the given list of operations. + * @param userOps - Array of UserOperations to validate the signature for. + * @param signature - The aggregated signature. + */ + function validateSignatures( + PackedUserOperation[] calldata userOps, + bytes calldata signature + ) external view; + + /** + * Validate signature of a single userOp. + * This method should be called by bundler after EntryPointSimulation.simulateValidation() returns + * the aggregator this account uses. + * First it validates the signature over the userOp. Then it returns data to be used when creating the handleOps. + * @param userOp - The userOperation received from the user. + * @return sigForUserOp - The value to put into the signature field of the userOp when calling handleOps. + * (usually empty, unless account and aggregator support some kind of "multisig". + */ + function validateUserOpSignature( + PackedUserOperation calldata userOp + ) external view returns (bytes memory sigForUserOp); + + /** + * Aggregate multiple signatures into a single value. + * This method is called off-chain to calculate the signature to pass with handleOps() + * bundler MAY use optimized custom code perform this aggregation. + * @param userOps - Array of UserOperations to collect the signatures from. + * @return aggregatedSignature - The aggregated signature. + */ + function aggregateSignatures( + PackedUserOperation[] calldata userOps + ) external view returns (bytes memory aggregatedSignature); +} diff --git a/biconomy/nexus/1.0.2/node_modules/account-abstraction/contracts/interfaces/IEntryPoint.sol b/biconomy/nexus/1.0.2/node_modules/account-abstraction/contracts/interfaces/IEntryPoint.sol new file mode 100644 index 0000000..28c26f9 --- /dev/null +++ b/biconomy/nexus/1.0.2/node_modules/account-abstraction/contracts/interfaces/IEntryPoint.sol @@ -0,0 +1,223 @@ +/** + ** Account-Abstraction (EIP-4337) singleton EntryPoint implementation. + ** Only one instance required on each chain. + **/ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity >=0.7.5; + +/* solhint-disable avoid-low-level-calls */ +/* solhint-disable no-inline-assembly */ +/* solhint-disable reason-string */ + +import "./PackedUserOperation.sol"; +import "./IStakeManager.sol"; +import "./IAggregator.sol"; +import "./INonceManager.sol"; + +interface IEntryPoint is IStakeManager, INonceManager { + /*** + * An event emitted after each successful request. + * @param userOpHash - Unique identifier for the request (hash its entire content, except signature). + * @param sender - The account that generates this request. + * @param paymaster - If non-null, the paymaster that pays for this request. + * @param nonce - The nonce value from the request. + * @param success - True if the sender transaction succeeded, false if reverted. + * @param actualGasCost - Actual amount paid (by account or paymaster) for this UserOperation. + * @param actualGasUsed - Total gas used by this UserOperation (including preVerification, creation, + * validation and execution). + */ + event UserOperationEvent( + bytes32 indexed userOpHash, + address indexed sender, + address indexed paymaster, + uint256 nonce, + bool success, + uint256 actualGasCost, + uint256 actualGasUsed + ); + + /** + * Account "sender" was deployed. + * @param userOpHash - The userOp that deployed this account. UserOperationEvent will follow. + * @param sender - The account that is deployed + * @param factory - The factory used to deploy this account (in the initCode) + * @param paymaster - The paymaster used by this UserOp + */ + event AccountDeployed( + bytes32 indexed userOpHash, + address indexed sender, + address factory, + address paymaster + ); + + /** + * An event emitted if the UserOperation "callData" reverted with non-zero length. + * @param userOpHash - The request unique identifier. + * @param sender - The sender of this request. + * @param nonce - The nonce used in the request. + * @param revertReason - The return bytes from the (reverted) call to "callData". + */ + event UserOperationRevertReason( + bytes32 indexed userOpHash, + address indexed sender, + uint256 nonce, + bytes revertReason + ); + + /** + * An event emitted if the UserOperation Paymaster's "postOp" call reverted with non-zero length. + * @param userOpHash - The request unique identifier. + * @param sender - The sender of this request. + * @param nonce - The nonce used in the request. + * @param revertReason - The return bytes from the (reverted) call to "callData". + */ + event PostOpRevertReason( + bytes32 indexed userOpHash, + address indexed sender, + uint256 nonce, + bytes revertReason + ); + + /** + * UserOp consumed more than prefund. The UserOperation is reverted, and no refund is made. + * @param userOpHash - The request unique identifier. + * @param sender - The sender of this request. + * @param nonce - The nonce used in the request. + */ + event UserOperationPrefundTooLow( + bytes32 indexed userOpHash, + address indexed sender, + uint256 nonce + ); + + /** + * An event emitted by handleOps(), before starting the execution loop. + * Any event emitted before this event, is part of the validation. + */ + event BeforeExecution(); + + /** + * Signature aggregator used by the following UserOperationEvents within this bundle. + * @param aggregator - The aggregator used for the following UserOperationEvents. + */ + event SignatureAggregatorChanged(address indexed aggregator); + + /** + * A custom revert error of handleOps, to identify the offending op. + * Should be caught in off-chain handleOps simulation and not happen on-chain. + * Useful for mitigating DoS attempts against batchers or for troubleshooting of factory/account/paymaster reverts. + * NOTE: If simulateValidation passes successfully, there should be no reason for handleOps to fail on it. + * @param opIndex - Index into the array of ops to the failed one (in simulateValidation, this is always zero). + * @param reason - Revert reason. The string starts with a unique code "AAmn", + * where "m" is "1" for factory, "2" for account and "3" for paymaster issues, + * so a failure can be attributed to the correct entity. + */ + error FailedOp(uint256 opIndex, string reason); + + /** + * A custom revert error of handleOps, to report a revert by account or paymaster. + * @param opIndex - Index into the array of ops to the failed one (in simulateValidation, this is always zero). + * @param reason - Revert reason. see FailedOp(uint256,string), above + * @param inner - data from inner cought revert reason + * @dev note that inner is truncated to 2048 bytes + */ + error FailedOpWithRevert(uint256 opIndex, string reason, bytes inner); + + error PostOpReverted(bytes returnData); + + /** + * Error case when a signature aggregator fails to verify the aggregated signature it had created. + * @param aggregator The aggregator that failed to verify the signature + */ + error SignatureValidationFailed(address aggregator); + + // Return value of getSenderAddress. + error SenderAddressResult(address sender); + + // UserOps handled, per aggregator. + struct UserOpsPerAggregator { + PackedUserOperation[] userOps; + // Aggregator address + IAggregator aggregator; + // Aggregated signature + bytes signature; + } + + /** + * Execute a batch of UserOperations. + * No signature aggregator is used. + * If any account requires an aggregator (that is, it returned an aggregator when + * performing simulateValidation), then handleAggregatedOps() must be used instead. + * @param ops - The operations to execute. + * @param beneficiary - The address to receive the fees. + */ + function handleOps( + PackedUserOperation[] calldata ops, + address payable beneficiary + ) external; + + /** + * Execute a batch of UserOperation with Aggregators + * @param opsPerAggregator - The operations to execute, grouped by aggregator (or address(0) for no-aggregator accounts). + * @param beneficiary - The address to receive the fees. + */ + function handleAggregatedOps( + UserOpsPerAggregator[] calldata opsPerAggregator, + address payable beneficiary + ) external; + + /** + * Generate a request Id - unique identifier for this request. + * The request ID is a hash over the content of the userOp (except the signature), the entrypoint and the chainid. + * @param userOp - The user operation to generate the request ID for. + * @return hash the hash of this UserOperation + */ + function getUserOpHash( + PackedUserOperation calldata userOp + ) external view returns (bytes32); + + /** + * Gas and return values during simulation. + * @param preOpGas - The gas used for validation (including preValidationGas) + * @param prefund - The required prefund for this operation + * @param accountValidationData - returned validationData from account. + * @param paymasterValidationData - return validationData from paymaster. + * @param paymasterContext - Returned by validatePaymasterUserOp (to be passed into postOp) + */ + struct ReturnInfo { + uint256 preOpGas; + uint256 prefund; + uint256 accountValidationData; + uint256 paymasterValidationData; + bytes paymasterContext; + } + + /** + * Returned aggregated signature info: + * The aggregator returned by the account, and its current stake. + */ + struct AggregatorStakeInfo { + address aggregator; + StakeInfo stakeInfo; + } + + /** + * Get counterfactual sender address. + * Calculate the sender contract address that will be generated by the initCode and salt in the UserOperation. + * This method always revert, and returns the address in SenderAddressResult error + * @param initCode - The constructor code to be passed into the UserOperation. + */ + function getSenderAddress(bytes memory initCode) external; + + error DelegateAndRevert(bool success, bytes ret); + + /** + * Helper method for dry-run testing. + * @dev calling this method, the EntryPoint will make a delegatecall to the given data, and report (via revert) the result. + * The method always revert, so is only useful off-chain for dry run calls, in cases where state-override to replace + * actual EntryPoint code is less convenient. + * @param target a target contract to make a delegatecall from entrypoint + * @param data data to pass to target in a delegatecall + */ + function delegateAndRevert(address target, bytes calldata data) external; +} diff --git a/biconomy/nexus/1.0.2/node_modules/account-abstraction/contracts/interfaces/INonceManager.sol b/biconomy/nexus/1.0.2/node_modules/account-abstraction/contracts/interfaces/INonceManager.sol new file mode 100644 index 0000000..2f993f6 --- /dev/null +++ b/biconomy/nexus/1.0.2/node_modules/account-abstraction/contracts/interfaces/INonceManager.sol @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity >=0.7.5; + +interface INonceManager { + + /** + * Return the next nonce for this sender. + * Within a given key, the nonce values are sequenced (starting with zero, and incremented by one on each userop) + * But UserOp with different keys can come with arbitrary order. + * + * @param sender the account address + * @param key the high 192 bit of the nonce + * @return nonce a full nonce to pass for next UserOp with this sender. + */ + function getNonce(address sender, uint192 key) + external view returns (uint256 nonce); + + /** + * Manually increment the nonce of the sender. + * This method is exposed just for completeness.. + * Account does NOT need to call it, neither during validation, nor elsewhere, + * as the EntryPoint will update the nonce regardless. + * Possible use-case is call it with various keys to "initialize" their nonces to one, so that future + * UserOperations will not pay extra for the first transaction with a given key. + */ + function incrementNonce(uint192 key) external; +} diff --git a/biconomy/nexus/1.0.2/node_modules/account-abstraction/contracts/interfaces/IStakeManager.sol b/biconomy/nexus/1.0.2/node_modules/account-abstraction/contracts/interfaces/IStakeManager.sol new file mode 100644 index 0000000..69083e9 --- /dev/null +++ b/biconomy/nexus/1.0.2/node_modules/account-abstraction/contracts/interfaces/IStakeManager.sol @@ -0,0 +1,111 @@ +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity >=0.7.5; + +/** + * Manage deposits and stakes. + * Deposit is just a balance used to pay for UserOperations (either by a paymaster or an account). + * Stake is value locked for at least "unstakeDelay" by the staked entity. + */ +interface IStakeManager { + event Deposited(address indexed account, uint256 totalDeposit); + + event Withdrawn( + address indexed account, + address withdrawAddress, + uint256 amount + ); + + // Emitted when stake or unstake delay are modified. + event StakeLocked( + address indexed account, + uint256 totalStaked, + uint256 unstakeDelaySec + ); + + // Emitted once a stake is scheduled for withdrawal. + event StakeUnlocked(address indexed account, uint256 withdrawTime); + + event StakeWithdrawn( + address indexed account, + address withdrawAddress, + uint256 amount + ); + + /** + * @param deposit - The entity's deposit. + * @param staked - True if this entity is staked. + * @param stake - Actual amount of ether staked for this entity. + * @param unstakeDelaySec - Minimum delay to withdraw the stake. + * @param withdrawTime - First block timestamp where 'withdrawStake' will be callable, or zero if already locked. + * @dev Sizes were chosen so that deposit fits into one cell (used during handleOp) + * and the rest fit into a 2nd cell (used during stake/unstake) + * - 112 bit allows for 10^15 eth + * - 48 bit for full timestamp + * - 32 bit allows 150 years for unstake delay + */ + struct DepositInfo { + uint256 deposit; + bool staked; + uint112 stake; + uint32 unstakeDelaySec; + uint48 withdrawTime; + } + + // API struct used by getStakeInfo and simulateValidation. + struct StakeInfo { + uint256 stake; + uint256 unstakeDelaySec; + } + + /** + * Get deposit info. + * @param account - The account to query. + * @return info - Full deposit information of given account. + */ + function getDepositInfo( + address account + ) external view returns (DepositInfo memory info); + + /** + * Get account balance. + * @param account - The account to query. + * @return - The deposit (for gas payment) of the account. + */ + function balanceOf(address account) external view returns (uint256); + + /** + * Add to the deposit of the given account. + * @param account - The account to add to. + */ + function depositTo(address account) external payable; + + /** + * Add to the account's stake - amount and delay + * any pending unstake is first cancelled. + * @param _unstakeDelaySec - The new lock duration before the deposit can be withdrawn. + */ + function addStake(uint32 _unstakeDelaySec) external payable; + + /** + * Attempt to unlock the stake. + * The value can be withdrawn (using withdrawStake) after the unstake delay. + */ + function unlockStake() external; + + /** + * Withdraw from the (unlocked) stake. + * Must first call unlockStake and wait for the unstakeDelay to pass. + * @param withdrawAddress - The address to send withdrawn value. + */ + function withdrawStake(address payable withdrawAddress) external; + + /** + * Withdraw from the deposit. + * @param withdrawAddress - The address to send withdrawn value. + * @param withdrawAmount - The amount to withdraw. + */ + function withdrawTo( + address payable withdrawAddress, + uint256 withdrawAmount + ) external; +} diff --git a/biconomy/nexus/1.0.2/node_modules/account-abstraction/contracts/interfaces/PackedUserOperation.sol b/biconomy/nexus/1.0.2/node_modules/account-abstraction/contracts/interfaces/PackedUserOperation.sol new file mode 100644 index 0000000..fe20de5 --- /dev/null +++ b/biconomy/nexus/1.0.2/node_modules/account-abstraction/contracts/interfaces/PackedUserOperation.sol @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity >=0.7.5; + +/** + * User Operation struct + * @param sender - The sender account of this request. + * @param nonce - Unique value the sender uses to verify it is not a replay. + * @param initCode - If set, the account contract will be created by this constructor/ + * @param callData - The method call to execute on this account. + * @param accountGasLimits - Packed gas limits for validateUserOp and gas limit passed to the callData method call. + * @param preVerificationGas - Gas not calculated by the handleOps method, but added to the gas paid. + * Covers batch overhead. + * @param gasFees - packed gas fields maxPriorityFeePerGas and maxFeePerGas - Same as EIP-1559 gas parameters. + * @param paymasterAndData - If set, this field holds the paymaster address, verification gas limit, postOp gas limit and paymaster-specific extra data + * The paymaster will pay for the transaction instead of the sender. + * @param signature - Sender-verified signature over the entire request, the EntryPoint address and the chain ID. + */ +struct PackedUserOperation { + address sender; + uint256 nonce; + bytes initCode; + bytes callData; + bytes32 accountGasLimits; + uint256 preVerificationGas; + bytes32 gasFees; + bytes paymasterAndData; + bytes signature; +} diff --git a/biconomy/nexus/1.0.2/node_modules/erc7739-validator-base/src/ERC7739Validator.sol b/biconomy/nexus/1.0.2/node_modules/erc7739-validator-base/src/ERC7739Validator.sol new file mode 100644 index 0000000..46a7b3a --- /dev/null +++ b/biconomy/nexus/1.0.2/node_modules/erc7739-validator-base/src/ERC7739Validator.sol @@ -0,0 +1,389 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.27; + +interface IERC5267 { + function eip712Domain() external view returns ( + bytes1 fields, + string memory name, + string memory version, + uint256 chainId, + address verifyingContract, + bytes32 salt, + uint256[] memory extensions + ); +} + +/// @title ERC-7739: Nested Typed Data Sign Support for ERC-7579 Validators +abstract contract ERC7739Validator { + error InvalidSignature(); + + /// @dev `keccak256("PersonalSign(bytes prefixed)")`. + bytes32 internal constant _PERSONAL_SIGN_TYPEHASH = 0x983e65e5148e570cd828ead231ee759a8d7958721a768f93bc4483ba005c32de; + bytes32 internal constant _DOMAIN_TYPEHASH = 0x8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f; + bytes4 internal constant SUPPORTS_ERC7739 = 0x77390001; + + /*////////////////////////////////////////////////////////////////////////// + INTERNAL + //////////////////////////////////////////////////////////////////////////*/ + + /// @dev Returns whether the `signature` is valid for the `hash. + /// Use this in your validator's `isValidSignatureWithSender` implementation. + function _erc1271IsValidSignatureWithSender(address sender, bytes32 hash, bytes calldata signature) + internal + view + virtual + returns (bytes4) + { + // detection request + // this check only takes 17 gas units + // in theory, it can be moved out of this function so it doesn't apply to every + // isValidSignatureWithSender() call, but it would require an additional standard + // interface for SA to check if the IValidator supports ERC-7739 + // while isValidSignatureWithSender() is specified by ERC-7579, so + // it makes sense to use it in SA to check if the validator supports ERC-7739 + unchecked { + if (signature.length == uint256(0)) { + // Forces the compiler to optimize for smaller bytecode size. + if (uint256(hash) == ~signature.length / 0xffff * 0x7739) + return SUPPORTS_ERC7739; + } + } + + // sig malleability prevention + bytes32 s; + assembly { + // same as `s := mload(add(signature, 0x40))` but for calldata + s := calldataload(add(signature.offset, 0x20)) + } + if (uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) { + revert InvalidSignature(); + } + + bool success = _erc1271IsValidSignatureViaSafeCaller(sender, hash, signature) + || _erc1271IsValidSignatureViaNestedEIP712(hash, signature) + || _erc1271IsValidSignatureViaRPC(hash, signature); + + bytes4 sigValidationResult; + assembly { + // `success ? bytes4(keccak256("isValidSignature(bytes32,bytes)")) : 0xffffffff`. + // We use `0xffffffff` for invalid, in convention with the reference implementation. + sigValidationResult := shl(224, or(0x1626ba7e, sub(0, iszero(success)))) + } + return sigValidationResult; + } + + /// @dev Returns whether the `msg.sender` is considered safe, such + /// that we don't need to use the nested EIP-712 workflow. + /// Override to return true for more callers. + /// See: https://mirror.xyz/curiousapple.eth/pFqAdW2LiJ-6S4sg_u1z08k4vK6BCJ33LcyXpnNb8yU + function _erc1271CallerIsSafe(address sender) internal view virtual returns (bool) { + // The canonical `MulticallerWithSigner` at 0x000000000000D9ECebf3C23529de49815Dac1c4c + // is known to include the account in the hash to be signed. + return sender == 0x000000000000D9ECebf3C23529de49815Dac1c4c; + } + + /// @dev Returns whether the `hash` and `signature` are valid. + /// Obtains the authorized signer's credentials and calls some + /// module's specific internal function to validate the signature + /// against credentials. + /// Override for your module's custom logic. + function _erc1271IsValidSignatureNowCalldata(bytes32 hash, bytes calldata signature) + internal + view + virtual + returns (bool); + + /// @dev Unwraps and returns the signature. + function _erc1271UnwrapSignature(bytes calldata signature) + internal + view + virtual + returns (bytes calldata result) + { + result = signature; + /// @solidity memory-safe-assembly + assembly { + // Unwraps the ERC6492 wrapper if it exists. + // See: https://eips.ethereum.org/EIPS/eip-6492 + if eq( + calldataload(add(result.offset, sub(result.length, 0x20))), + mul(0x6492, div(not(shr(address(), address())), 0xffff)) // `0x6492...6492`. + ) { + let o := add(result.offset, calldataload(add(result.offset, 0x40))) + result.length := calldataload(o) + result.offset := add(o, 0x20) + } + } + } + + /// @dev Performs the signature validation without nested EIP-712 if the caller is + /// a safe caller. A safe caller must include the address of this account in the hash. + function _erc1271IsValidSignatureViaSafeCaller(address sender, bytes32 hash, bytes calldata signature) + internal + view + virtual + returns (bool result) + { + if (_erc1271CallerIsSafe(sender)) result = _erc1271IsValidSignatureNowCalldata(hash, signature); + } + + /// @dev ERC1271 signature validation (Nested EIP-712 workflow). + /// + /// This uses ECDSA recovery by default (see: `_erc1271IsValidSignatureNowCalldata`). + /// It also uses a nested EIP-712 approach to prevent signature replays when a single EOA + /// owns multiple smart contract accounts, + /// while still enabling wallet UIs (e.g. Metamask) to show the EIP-712 values. + /// + /// Crafted for phishing resistance, efficiency, flexibility. + /// __________________________________________________________________________________________ + /// + /// Glossary: + /// + /// - `APP_DOMAIN_SEPARATOR`: The domain separator of the `hash` passed in by the application. + /// Provided by the front end. Intended to be the domain separator of the contract + /// that will call `isValidSignature` on this account. + /// + /// - `ACCOUNT_DOMAIN_SEPARATOR`: The domain separator of this account. + /// See: `EIP712._domainSeparator()`. + /// __________________________________________________________________________________________ + /// + /// For the `TypedDataSign` workflow, the final hash will be: + /// ``` + /// keccak256(\x19\x01 ‖ APP_DOMAIN_SEPARATOR ‖ + /// hashStruct(TypedDataSign({ + /// contents: hashStruct(originalStruct), + /// name: keccak256(bytes(eip712Domain().name)), + /// version: keccak256(bytes(eip712Domain().version)), + /// chainId: eip712Domain().chainId, + /// verifyingContract: eip712Domain().verifyingContract, + /// salt: eip712Domain().salt + /// })) + /// ) + /// ``` + /// where `‖` denotes the concatenation operator for bytes. + /// The order of the fields is important: `contents` comes before `name`. + /// + /// The signature will be `r ‖ s ‖ v ‖ APP_DOMAIN_SEPARATOR ‖ + /// contents ‖ contentsDescription ‖ uint16(contentsDescription.length)`, + /// where: + /// - `contents` is the bytes32 struct hash of the original struct. + /// - `contentsDescription` can be either: + /// a) `contentsType` (implicit mode) + /// where `contentsType` starts with `contentsName`. + /// b) `contentsType ‖ contentsName` (explicit mode) + /// where `contentsType` may not necessarily start with `contentsName`. + /// + /// The `APP_DOMAIN_SEPARATOR` and `contents` will be used to verify if `hash` is indeed correct. + /// __________________________________________________________________________________________ + /// + /// For the `PersonalSign` workflow, the final hash will be: + /// ``` + /// keccak256(\x19\x01 ‖ ACCOUNT_DOMAIN_SEPARATOR ‖ + /// hashStruct(PersonalSign({ + /// prefixed: keccak256(bytes(\x19Ethereum Signed Message:\n ‖ + /// base10(bytes(someString).length) ‖ someString)) + /// })) + /// ) + /// ``` + /// where `‖` denotes the concatenation operator for bytes. + /// + /// The `PersonalSign` type hash will be `keccak256("PersonalSign(bytes prefixed)")`. + /// The signature will be `r ‖ s ‖ v`. + /// __________________________________________________________________________________________ + /// + /// For demo and typescript code, see: + /// - https://github.com/junomonster/nested-eip-712 + /// - https://github.com/frangio/eip712-wrapper-for-eip1271 + /// + /// Their nomenclature may differ from ours, although the high-level idea is similar. + /// + /// Of course, if you have control over the codebase of the wallet client(s) too, + /// you can choose a more minimalistic signature scheme like + /// `keccak256(abi.encode(address(this), hash))` instead of all these acrobatics. + /// All these are just for widespread out-of-the-box compatibility with other wallet clients. + /// We want to create bazaars, not walled castles. + /// And we'll use push the Turing Completeness of the EVM to the limits to do so. + function _erc1271IsValidSignatureViaNestedEIP712(bytes32 hash, bytes calldata signature) + internal + view + virtual + returns (bool result) + { + //bytes32 t = _typedDataSignFieldsForAccount(msg.sender); + uint256 t = uint256(uint160(address(this))); + // Forces the compiler to pop the variables after the scope, avoiding stack-too-deep. + if (t != uint256(0)) { + ( + , + string memory name, + string memory version, + uint256 chainId, + address verifyingContract, + bytes32 salt, + ) = IERC5267(msg.sender).eip712Domain(); + /// @solidity memory-safe-assembly + assembly { + t := mload(0x40) // Grab the free memory pointer. + // Skip 2 words for the `typedDataSignTypehash` and `contents` struct hash. + mstore(add(t, 0x40), keccak256(add(name, 0x20), mload(name))) + mstore(add(t, 0x60), keccak256(add(version, 0x20), mload(version))) + mstore(add(t, 0x80), chainId) + mstore(add(t, 0xa0), shr(96, shl(96, verifyingContract))) + mstore(add(t, 0xc0), salt) + mstore(0x40, add(t, 0xe0)) // Allocate the memory. + } + } + + /// @solidity memory-safe-assembly + assembly { + let m := mload(0x40) // Cache the free memory pointer. + // `c` is `contentsDescription.length`, which is stored in the last 2 bytes of the signature. + let c := shr(240, calldataload(add(signature.offset, sub(signature.length, 2)))) + for {} 1 {} { + let l := add(0x42, c) // Total length of appended data (32 + 32 + c + 2). + let o := add(signature.offset, sub(signature.length, l)) // Offset of appended data. + mstore(0x00, 0x1901) // Store the "\x19\x01" prefix. + calldatacopy(0x20, o, 0x40) // Copy the `APP_DOMAIN_SEPARATOR` and `contents` struct hash. + // Use the `PersonalSign` workflow if the reconstructed hash doesn't match, + // or if the appended data is invalid, i.e. + // `appendedData.length > signature.length || contentsDescription.length == 0`. + if or(xor(keccak256(0x1e, 0x42), hash), or(lt(signature.length, l), iszero(c))) { + t := 0 // Set `t` to 0, denoting that we need to `hash = _hashTypedData(hash)`. + mstore(t, _PERSONAL_SIGN_TYPEHASH) + mstore(0x20, hash) // Store the `prefixed`. + hash := keccak256(t, 0x40) // Compute the `PersonalSign` struct hash. + break + } + // Else, use the `TypedDataSign` workflow. + // `TypedDataSign({ContentsName} contents,string name,...){ContentsType}`. + mstore(m, "TypedDataSign(") // Store the start of `TypedDataSign`'s type encoding. + let p := add(m, 0x0e) // Advance 14 bytes to skip "TypedDataSign(". + + calldatacopy(p, add(o, 0x40), c) // Copy `contentsName`, optimistically. + mstore(add(p, c), 40) // Store a '(' after the end. + if iszero(eq(byte(0, mload(sub(add(p, c), 1))), 41)) { + let e := 0 // Length of `contentsName` in explicit mode. + for { let q := sub(add(p, c), 1) } 1 {} { + e := add(e, 1) // Scan backwards until we encounter a ')'. + if iszero(gt(lt(e, c), eq(byte(0, mload(sub(q, e))), 41))) { break } + } + c := sub(c, e) // Truncate `contentsDescription` to `contentsType`. + calldatacopy(p, add(add(o, 0x40), c), e) // Copy `contentsName`. + mstore8(add(p, e), 40) // Store a '(' exactly right after the end. + } + + // `d & 1 == 1` means that `contentsName` is invalid. + let d := shr(byte(0, mload(p)), 0x7fffffe000000000000010000000000) // Starts with `[a-z(]`. + // Advance `p` until we encounter '('. + for {} iszero(eq(byte(0, mload(p)), 40)) { p := add(p, 1) } { + d := or(shr(byte(0, mload(p)), 0x120100000001), d) // Has a byte in ", )\x00". + } + mstore(p, " contents,string name,string") // Store the rest of the encoding. + mstore(add(p, 0x1c), " version,uint256 chainId,address") + mstore(add(p, 0x3c), " verifyingContract,bytes32 salt)") + p := add(p, 0x5c) + calldatacopy(p, add(o, 0x40), c) // Copy `contentsType`. + // Fill in the missing fields of the `TypedDataSign`. + calldatacopy(t, o, 0x40) // Copy the `contents` struct hash to `add(t, 0x20)`. + mstore(t, keccak256(m, sub(add(p, c), m))) // Store `typedDataSignTypehash`. + // The "\x19\x01" prefix is already at 0x00. + // `APP_DOMAIN_SEPARATOR` is already at 0x20. + mstore(0x40, keccak256(t, 0xe0)) // `hashStruct(typedDataSign)`. + // Compute the final hash, corrupted if `contentsName` is invalid. + hash := keccak256(0x1e, add(0x42, and(1, d))) + signature.length := sub(signature.length, l) // Truncate the signature. + break + } + mstore(0x40, m) // Restore the free memory pointer. + } + if (t == uint256(0)) hash = _hashTypedDataForAccount(msg.sender, hash); // `PersonalSign` workflow. + result = _erc1271IsValidSignatureNowCalldata(hash, signature); + } + + /// @dev Performs the signature validation without nested EIP-712 to allow for easy sign ins. + /// This function must always return false or revert if called on-chain. + function _erc1271IsValidSignatureViaRPC(bytes32 hash, bytes calldata signature) + internal + view + virtual + returns (bool result) + { + // Non-zero gasprice is a heuristic to check if a call is on-chain, + // but we can't fully depend on it because it can be manipulated. + // See: https://x.com/NoahCitron/status/1580359718341484544 + if (tx.gasprice == uint256(0)) { + /// @solidity memory-safe-assembly + assembly { + mstore(gasprice(), gasprice()) + // See: https://gist.github.com/Vectorized/3c9b63524d57492b265454f62d895f71 + let b := 0x000000000000378eDCD5B5B0A24f5342d8C10485 // Basefee contract, + pop(staticcall(0xffff, b, codesize(), gasprice(), gasprice(), 0x20)) + // If `gasprice < basefee`, the call cannot be on-chain, and we can skip the gas burn. + if iszero(mload(gasprice())) { + let m := mload(0x40) // Cache the free memory pointer. + mstore(gasprice(), 0x1626ba7e) // `isValidSignature(bytes32,bytes)`. + mstore(0x20, b) // Recycle `b` to denote if we need to burn gas. + mstore(0x40, 0x40) + let gasToBurn := or(add(0xffff, gaslimit()), gaslimit()) + // Burns gas computationally efficiently. Also, requires that `gas > gasToBurn`. + if or(eq(hash, b), lt(gas(), gasToBurn)) { invalid() } + // Make a call to this with `b`, efficiently burning the gas provided. + // No valid transaction can consume more than the gaslimit. + // See: https://ethereum.github.io/yellowpaper/paper.pdf + // Most RPCs perform calls with a gas budget greater than the gaslimit. + pop(staticcall(gasToBurn, address(), 0x1c, 0x64, gasprice(), gasprice())) + mstore(0x40, m) // Restore the free memory pointer. + } + } + result = _erc1271IsValidSignatureNowCalldata(hash, signature); + } + } + + /// @notice Hashes typed data according to eip-712 + /// Uses account's domain separator + /// @param account the smart account, who's domain separator will be used + /// @param structHash the typed data struct hash + function _hashTypedDataForAccount(address account, bytes32 structHash) private view returns (bytes32 digest) { + ( + /*bytes1 fields*/, + string memory name, + string memory version, + uint256 chainId, + address verifyingContract, + /*bytes32 salt*/, + /*uint256[] memory extensions*/ + ) = IERC5267(account).eip712Domain(); + + /// @solidity memory-safe-assembly + assembly { + //Rebuild domain separator out of 712 domain + let m := mload(0x40) // Load the free memory pointer. + mstore(m, _DOMAIN_TYPEHASH) + mstore(add(m, 0x20), keccak256(add(name, 0x20), mload(name))) // Name hash. + mstore(add(m, 0x40), keccak256(add(version, 0x20), mload(version))) // Version hash. + mstore(add(m, 0x60), chainId) + mstore(add(m, 0x80), verifyingContract) + digest := keccak256(m, 0xa0) //domain separator + + // Hash typed data + mstore(0x00, 0x1901000000000000) // Store "\x19\x01". + mstore(0x1a, digest) // Store the domain separator. + mstore(0x3a, structHash) // Store the struct hash. + digest := keccak256(0x18, 0x42) + // Restore the part of the free memory slot that was overwritten. + mstore(0x3a, 0) + } + } + + /// @dev Backwards compatibility stuff + /// For automatic detection that the smart account supports the nested EIP-712 workflow. + /// By default, it returns `bytes32(bytes4(keccak256("supportsNestedTypedDataSign()")))`, + /// denoting support for the default behavior, as implemented in + /// `_erc1271IsValidSignatureViaNestedEIP712`, which is called in `isValidSignature`. + /// Future extensions should return a different non-zero `result` to denote different behavior. + /// This method intentionally returns bytes32 to allow freedom for future extensions. + function supportsNestedTypedDataSign() public view virtual returns (bytes32 result) { + result = bytes4(0xd620c85a); + } + +} diff --git a/biconomy/nexus/1.0.2/node_modules/excessively-safe-call/src/ExcessivelySafeCall.sol b/biconomy/nexus/1.0.2/node_modules/excessively-safe-call/src/ExcessivelySafeCall.sol new file mode 100644 index 0000000..eebf39c --- /dev/null +++ b/biconomy/nexus/1.0.2/node_modules/excessively-safe-call/src/ExcessivelySafeCall.sol @@ -0,0 +1,138 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity >=0.7.6; + +library ExcessivelySafeCall { + uint256 constant LOW_28_MASK = + 0x00000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffff; + + /// @notice Use when you _really_ really _really_ don't trust the called + /// contract. This prevents the called contract from causing reversion of + /// the caller in as many ways as we can. + /// @dev The main difference between this and a solidity low-level call is + /// that we limit the number of bytes that the callee can cause to be + /// copied to caller memory. This prevents stupid things like malicious + /// contracts returning 10,000,000 bytes causing a local OOG when copying + /// to memory. + /// @param _target The address to call + /// @param _gas The amount of gas to forward to the remote contract + /// @param _value The value in wei to send to the remote contract + /// @param _maxCopy The maximum number of bytes of returndata to copy + /// to memory. + /// @param _calldata The data to send to the remote contract + /// @return success and returndata, as `.call()`. Returndata is capped to + /// `_maxCopy` bytes. + function excessivelySafeCall( + address _target, + uint256 _gas, + uint256 _value, + uint16 _maxCopy, + bytes memory _calldata + ) internal returns (bool, bytes memory) { + // set up for assembly call + uint256 _toCopy; + bool _success; + bytes memory _returnData = new bytes(_maxCopy); + // dispatch message to recipient + // by assembly calling "handle" function + // we call via assembly to avoid memcopying a very large returndata + // returned by a malicious contract + assembly { + _success := call( + _gas, // gas + _target, // recipient + _value, // ether value + add(_calldata, 0x20), // inloc + mload(_calldata), // inlen + 0, // outloc + 0 // outlen + ) + // limit our copy to 256 bytes + _toCopy := returndatasize() + if gt(_toCopy, _maxCopy) { + _toCopy := _maxCopy + } + // Store the length of the copied bytes + mstore(_returnData, _toCopy) + // copy the bytes from returndata[0:_toCopy] + returndatacopy(add(_returnData, 0x20), 0, _toCopy) + } + return (_success, _returnData); + } + + /// @notice Use when you _really_ really _really_ don't trust the called + /// contract. This prevents the called contract from causing reversion of + /// the caller in as many ways as we can. + /// @dev The main difference between this and a solidity low-level call is + /// that we limit the number of bytes that the callee can cause to be + /// copied to caller memory. This prevents stupid things like malicious + /// contracts returning 10,000,000 bytes causing a local OOG when copying + /// to memory. + /// @param _target The address to call + /// @param _gas The amount of gas to forward to the remote contract + /// @param _maxCopy The maximum number of bytes of returndata to copy + /// to memory. + /// @param _calldata The data to send to the remote contract + /// @return success and returndata, as `.call()`. Returndata is capped to + /// `_maxCopy` bytes. + function excessivelySafeStaticCall( + address _target, + uint256 _gas, + uint16 _maxCopy, + bytes memory _calldata + ) internal view returns (bool, bytes memory) { + // set up for assembly call + uint256 _toCopy; + bool _success; + bytes memory _returnData = new bytes(_maxCopy); + // dispatch message to recipient + // by assembly calling "handle" function + // we call via assembly to avoid memcopying a very large returndata + // returned by a malicious contract + assembly { + _success := staticcall( + _gas, // gas + _target, // recipient + add(_calldata, 0x20), // inloc + mload(_calldata), // inlen + 0, // outloc + 0 // outlen + ) + // limit our copy to 256 bytes + _toCopy := returndatasize() + if gt(_toCopy, _maxCopy) { + _toCopy := _maxCopy + } + // Store the length of the copied bytes + mstore(_returnData, _toCopy) + // copy the bytes from returndata[0:_toCopy] + returndatacopy(add(_returnData, 0x20), 0, _toCopy) + } + return (_success, _returnData); + } + + /** + * @notice Swaps function selectors in encoded contract calls + * @dev Allows reuse of encoded calldata for functions with identical + * argument types but different names. It simply swaps out the first 4 bytes + * for the new selector. This function modifies memory in place, and should + * only be used with caution. + * @param _newSelector The new 4-byte selector + * @param _buf The encoded contract args + */ + function swapSelector(bytes4 _newSelector, bytes memory _buf) + internal + pure + { + require(_buf.length >= 4); + uint256 _mask = LOW_28_MASK; + assembly { + // load the first word of + let _word := mload(add(_buf, 0x20)) + // mask out the top 4 bytes + // /x + _word := and(_word, _mask) + _word := or(_newSelector, _word) + mstore(add(_buf, 0x20), _word) + } + } +} diff --git a/biconomy/nexus/1.0.2/node_modules/sentinellist/src/SentinelList.sol b/biconomy/nexus/1.0.2/node_modules/sentinellist/src/SentinelList.sol new file mode 100644 index 0000000..ffa9b57 --- /dev/null +++ b/biconomy/nexus/1.0.2/node_modules/sentinellist/src/SentinelList.sol @@ -0,0 +1,188 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +// Sentinel address +address constant SENTINEL = address(0x1); +// Zero address +address constant ZERO_ADDRESS = address(0x0); + +/** + * @title SentinelListLib + * @dev Library for managing a linked list of addresses + * @author Rhinestone + */ +library SentinelListLib { + // Struct to hold the linked list + struct SentinelList { + mapping(address => address) entries; + } + + error LinkedList_AlreadyInitialized(); + error LinkedList_InvalidPage(); + error LinkedList_InvalidEntry(address entry); + error LinkedList_EntryAlreadyInList(address entry); + + /** + * Initialize the linked list + * + * @param self The linked list + */ + function init(SentinelList storage self) internal { + if (alreadyInitialized(self)) revert LinkedList_AlreadyInitialized(); + self.entries[SENTINEL] = SENTINEL; + } + + /** + * Check if the linked list is already initialized + * + * @param self The linked list + * + * @return bool True if the linked list is already initialized + */ + function alreadyInitialized(SentinelList storage self) internal view returns (bool) { + return self.entries[SENTINEL] != ZERO_ADDRESS; + } + + /** + * Get the next entry in the linked list + * + * @param self The linked list + * @param entry The current entry + * + * @return address The next entry + */ + function getNext(SentinelList storage self, address entry) internal view returns (address) { + if (entry == ZERO_ADDRESS) { + revert LinkedList_InvalidEntry(entry); + } + return self.entries[entry]; + } + + /** + * Push a new entry to the linked list + * + * @param self The linked list + * @param newEntry The new entry + */ + function push(SentinelList storage self, address newEntry) internal { + if (newEntry == ZERO_ADDRESS || newEntry == SENTINEL) { + revert LinkedList_InvalidEntry(newEntry); + } + if (self.entries[newEntry] != ZERO_ADDRESS) revert LinkedList_EntryAlreadyInList(newEntry); + self.entries[newEntry] = self.entries[SENTINEL]; + self.entries[SENTINEL] = newEntry; + } + + /** + * Safe push a new entry to the linked list + * @dev This ensures that the linked list is initialized and initializes it if it is not + * + * @param self The linked list + * @param newEntry The new entry + */ + function safePush(SentinelList storage self, address newEntry) internal { + if (!alreadyInitialized({ self: self })) { + init({ self: self }); + } + push({ self: self, newEntry: newEntry }); + } + + /** + * Pop an entry from the linked list + * + * @param self The linked list + * @param prevEntry The entry before the entry to pop + * @param popEntry The entry to pop + */ + function pop(SentinelList storage self, address prevEntry, address popEntry) internal { + if (popEntry == ZERO_ADDRESS || popEntry == SENTINEL) { + revert LinkedList_InvalidEntry(prevEntry); + } + if (self.entries[prevEntry] != popEntry) revert LinkedList_InvalidEntry(popEntry); + self.entries[prevEntry] = self.entries[popEntry]; + self.entries[popEntry] = ZERO_ADDRESS; + } + + /** + * Pop all entries from the linked list + * + * @param self The linked list + */ + function popAll(SentinelList storage self) internal { + address next = self.entries[SENTINEL]; + while (next != ZERO_ADDRESS) { + address current = next; + next = self.entries[next]; + self.entries[current] = ZERO_ADDRESS; + } + } + + /** + * Check if the linked list contains an entry + * + * @param self The linked list + * @param entry The entry to check + * + * @return bool True if the linked list contains the entry + */ + function contains(SentinelList storage self, address entry) internal view returns (bool) { + return SENTINEL != entry && self.entries[entry] != ZERO_ADDRESS; + } + + /** + * Get all entries in the linked list + * + * @param self The linked list + * @param start The start entry + * @param pageSize The page size + * + * @return array All entries in the linked list + * @return next The next entry + */ + function getEntriesPaginated( + SentinelList storage self, + address start, + uint256 pageSize + ) + internal + view + returns (address[] memory array, address next) + { + if (start != SENTINEL && !contains(self, start)) revert LinkedList_InvalidEntry(start); + if (pageSize == 0) revert LinkedList_InvalidPage(); + // Init array with max page size + array = new address[](pageSize); + + // Populate return array + uint256 entryCount = 0; + next = self.entries[start]; + while (next != ZERO_ADDRESS && next != SENTINEL && entryCount < pageSize) { + array[entryCount] = next; + next = self.entries[next]; + entryCount++; + } + + /** + * Because of the argument validation, we can assume that the loop will always iterate over + * the valid entry list values + * and the `next` variable will either be an enabled entry or a sentinel address + * (signalling the end). + * + * If we haven't reached the end inside the loop, we need to set the next pointer to + * the last element of the entry array + * because the `next` variable (which is a entry by itself) acting as a pointer to the + * start of the next page is neither + * incSENTINELrent page, nor will it be included in the next one if you pass it as a + * start. + */ + if (next != SENTINEL && entryCount > 0) { + next = array[entryCount - 1]; + } + // Set correct size of returned array + // solhint-disable-next-line no-inline-assembly + /// @solidity memory-safe-assembly + assembly { + mstore(array, entryCount) + } + } +} diff --git a/biconomy/nexus/1.0.2/node_modules/solady/src/auth/Ownable.sol b/biconomy/nexus/1.0.2/node_modules/solady/src/auth/Ownable.sol new file mode 100644 index 0000000..a9d3214 --- /dev/null +++ b/biconomy/nexus/1.0.2/node_modules/solady/src/auth/Ownable.sol @@ -0,0 +1,278 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.4; + +/// @notice Simple single owner authorization mixin. +/// @author Solady (https://github.com/vectorized/solady/blob/main/src/auth/Ownable.sol) +/// +/// @dev Note: +/// This implementation does NOT auto-initialize the owner to `msg.sender`. +/// You MUST call the `_initializeOwner` in the constructor / initializer. +/// +/// While the ownable portion follows +/// [EIP-173](https://eips.ethereum.org/EIPS/eip-173) for compatibility, +/// the nomenclature for the 2-step ownership handover may be unique to this codebase. +abstract contract Ownable { + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* CUSTOM ERRORS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev The caller is not authorized to call the function. + error Unauthorized(); + + /// @dev The `newOwner` cannot be the zero address. + error NewOwnerIsZeroAddress(); + + /// @dev The `pendingOwner` does not have a valid handover request. + error NoHandoverRequest(); + + /// @dev Cannot double-initialize. + error AlreadyInitialized(); + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* EVENTS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev The ownership is transferred from `oldOwner` to `newOwner`. + /// This event is intentionally kept the same as OpenZeppelin's Ownable to be + /// compatible with indexers and [EIP-173](https://eips.ethereum.org/EIPS/eip-173), + /// despite it not being as lightweight as a single argument event. + event OwnershipTransferred(address indexed oldOwner, address indexed newOwner); + + /// @dev An ownership handover to `pendingOwner` has been requested. + event OwnershipHandoverRequested(address indexed pendingOwner); + + /// @dev The ownership handover to `pendingOwner` has been canceled. + event OwnershipHandoverCanceled(address indexed pendingOwner); + + /// @dev `keccak256(bytes("OwnershipTransferred(address,address)"))`. + uint256 private constant _OWNERSHIP_TRANSFERRED_EVENT_SIGNATURE = + 0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0; + + /// @dev `keccak256(bytes("OwnershipHandoverRequested(address)"))`. + uint256 private constant _OWNERSHIP_HANDOVER_REQUESTED_EVENT_SIGNATURE = + 0xdbf36a107da19e49527a7176a1babf963b4b0ff8cde35ee35d6cd8f1f9ac7e1d; + + /// @dev `keccak256(bytes("OwnershipHandoverCanceled(address)"))`. + uint256 private constant _OWNERSHIP_HANDOVER_CANCELED_EVENT_SIGNATURE = + 0xfa7b8eab7da67f412cc9575ed43464468f9bfbae89d1675917346ca6d8fe3c92; + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* STORAGE */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev The owner slot is given by: + /// `bytes32(~uint256(uint32(bytes4(keccak256("_OWNER_SLOT_NOT")))))`. + /// It is intentionally chosen to be a high value + /// to avoid collision with lower slots. + /// The choice of manual storage layout is to enable compatibility + /// with both regular and upgradeable contracts. + bytes32 internal constant _OWNER_SLOT = + 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffff74873927; + + /// The ownership handover slot of `newOwner` is given by: + /// ``` + /// mstore(0x00, or(shl(96, user), _HANDOVER_SLOT_SEED)) + /// let handoverSlot := keccak256(0x00, 0x20) + /// ``` + /// It stores the expiry timestamp of the two-step ownership handover. + uint256 private constant _HANDOVER_SLOT_SEED = 0x389a75e1; + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* INTERNAL FUNCTIONS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev Override to return true to make `_initializeOwner` prevent double-initialization. + function _guardInitializeOwner() internal pure virtual returns (bool guard) {} + + /// @dev Initializes the owner directly without authorization guard. + /// This function must be called upon initialization, + /// regardless of whether the contract is upgradeable or not. + /// This is to enable generalization to both regular and upgradeable contracts, + /// and to save gas in case the initial owner is not the caller. + /// For performance reasons, this function will not check if there + /// is an existing owner. + function _initializeOwner(address newOwner) internal virtual { + if (_guardInitializeOwner()) { + /// @solidity memory-safe-assembly + assembly { + let ownerSlot := _OWNER_SLOT + if sload(ownerSlot) { + mstore(0x00, 0x0dc149f0) // `AlreadyInitialized()`. + revert(0x1c, 0x04) + } + // Clean the upper 96 bits. + newOwner := shr(96, shl(96, newOwner)) + // Store the new value. + sstore(ownerSlot, or(newOwner, shl(255, iszero(newOwner)))) + // Emit the {OwnershipTransferred} event. + log3(0, 0, _OWNERSHIP_TRANSFERRED_EVENT_SIGNATURE, 0, newOwner) + } + } else { + /// @solidity memory-safe-assembly + assembly { + // Clean the upper 96 bits. + newOwner := shr(96, shl(96, newOwner)) + // Store the new value. + sstore(_OWNER_SLOT, newOwner) + // Emit the {OwnershipTransferred} event. + log3(0, 0, _OWNERSHIP_TRANSFERRED_EVENT_SIGNATURE, 0, newOwner) + } + } + } + + /// @dev Sets the owner directly without authorization guard. + function _setOwner(address newOwner) internal virtual { + if (_guardInitializeOwner()) { + /// @solidity memory-safe-assembly + assembly { + let ownerSlot := _OWNER_SLOT + // Clean the upper 96 bits. + newOwner := shr(96, shl(96, newOwner)) + // Emit the {OwnershipTransferred} event. + log3(0, 0, _OWNERSHIP_TRANSFERRED_EVENT_SIGNATURE, sload(ownerSlot), newOwner) + // Store the new value. + sstore(ownerSlot, or(newOwner, shl(255, iszero(newOwner)))) + } + } else { + /// @solidity memory-safe-assembly + assembly { + let ownerSlot := _OWNER_SLOT + // Clean the upper 96 bits. + newOwner := shr(96, shl(96, newOwner)) + // Emit the {OwnershipTransferred} event. + log3(0, 0, _OWNERSHIP_TRANSFERRED_EVENT_SIGNATURE, sload(ownerSlot), newOwner) + // Store the new value. + sstore(ownerSlot, newOwner) + } + } + } + + /// @dev Throws if the sender is not the owner. + function _checkOwner() internal view virtual { + /// @solidity memory-safe-assembly + assembly { + // If the caller is not the stored owner, revert. + if iszero(eq(caller(), sload(_OWNER_SLOT))) { + mstore(0x00, 0x82b42900) // `Unauthorized()`. + revert(0x1c, 0x04) + } + } + } + + /// @dev Returns how long a two-step ownership handover is valid for in seconds. + /// Override to return a different value if needed. + /// Made internal to conserve bytecode. Wrap it in a public function if needed. + function _ownershipHandoverValidFor() internal view virtual returns (uint64) { + return 48 * 3600; + } + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* PUBLIC UPDATE FUNCTIONS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev Allows the owner to transfer the ownership to `newOwner`. + function transferOwnership(address newOwner) public payable virtual onlyOwner { + /// @solidity memory-safe-assembly + assembly { + if iszero(shl(96, newOwner)) { + mstore(0x00, 0x7448fbae) // `NewOwnerIsZeroAddress()`. + revert(0x1c, 0x04) + } + } + _setOwner(newOwner); + } + + /// @dev Allows the owner to renounce their ownership. + function renounceOwnership() public payable virtual onlyOwner { + _setOwner(address(0)); + } + + /// @dev Request a two-step ownership handover to the caller. + /// The request will automatically expire in 48 hours (172800 seconds) by default. + function requestOwnershipHandover() public payable virtual { + unchecked { + uint256 expires = block.timestamp + _ownershipHandoverValidFor(); + /// @solidity memory-safe-assembly + assembly { + // Compute and set the handover slot to `expires`. + mstore(0x0c, _HANDOVER_SLOT_SEED) + mstore(0x00, caller()) + sstore(keccak256(0x0c, 0x20), expires) + // Emit the {OwnershipHandoverRequested} event. + log2(0, 0, _OWNERSHIP_HANDOVER_REQUESTED_EVENT_SIGNATURE, caller()) + } + } + } + + /// @dev Cancels the two-step ownership handover to the caller, if any. + function cancelOwnershipHandover() public payable virtual { + /// @solidity memory-safe-assembly + assembly { + // Compute and set the handover slot to 0. + mstore(0x0c, _HANDOVER_SLOT_SEED) + mstore(0x00, caller()) + sstore(keccak256(0x0c, 0x20), 0) + // Emit the {OwnershipHandoverCanceled} event. + log2(0, 0, _OWNERSHIP_HANDOVER_CANCELED_EVENT_SIGNATURE, caller()) + } + } + + /// @dev Allows the owner to complete the two-step ownership handover to `pendingOwner`. + /// Reverts if there is no existing ownership handover requested by `pendingOwner`. + function completeOwnershipHandover(address pendingOwner) public payable virtual onlyOwner { + /// @solidity memory-safe-assembly + assembly { + // Compute and set the handover slot to 0. + mstore(0x0c, _HANDOVER_SLOT_SEED) + mstore(0x00, pendingOwner) + let handoverSlot := keccak256(0x0c, 0x20) + // If the handover does not exist, or has expired. + if gt(timestamp(), sload(handoverSlot)) { + mstore(0x00, 0x6f5e8818) // `NoHandoverRequest()`. + revert(0x1c, 0x04) + } + // Set the handover slot to 0. + sstore(handoverSlot, 0) + } + _setOwner(pendingOwner); + } + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* PUBLIC READ FUNCTIONS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev Returns the owner of the contract. + function owner() public view virtual returns (address result) { + /// @solidity memory-safe-assembly + assembly { + result := sload(_OWNER_SLOT) + } + } + + /// @dev Returns the expiry timestamp for the two-step ownership handover to `pendingOwner`. + function ownershipHandoverExpiresAt(address pendingOwner) + public + view + virtual + returns (uint256 result) + { + /// @solidity memory-safe-assembly + assembly { + // Compute the handover slot. + mstore(0x0c, _HANDOVER_SLOT_SEED) + mstore(0x00, pendingOwner) + // Load the handover slot. + result := sload(keccak256(0x0c, 0x20)) + } + } + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* MODIFIERS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev Marks a function as only callable by the owner. + modifier onlyOwner() virtual { + _checkOwner(); + _; + } +} diff --git a/biconomy/nexus/1.0.2/node_modules/solady/src/utils/CallContextChecker.sol b/biconomy/nexus/1.0.2/node_modules/solady/src/utils/CallContextChecker.sol new file mode 100644 index 0000000..83a7d4d --- /dev/null +++ b/biconomy/nexus/1.0.2/node_modules/solady/src/utils/CallContextChecker.sol @@ -0,0 +1,95 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.4; + +/// @notice Call context checker mixin. +/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/CallContextChecker.sol) +contract CallContextChecker { + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* CUSTOM ERRORS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev The call is from an unauthorized call context. + error UnauthorizedCallContext(); + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* IMMUTABLES */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev For checking if the context is a delegate call. + /// + /// Note: To enable use cases with an immutable default implementation in the bytecode, + /// (see: ERC6551Proxy), we don't require that the proxy address must match the + /// value stored in the implementation slot, which may not be initialized. + uint256 private immutable __self = uint256(uint160(address(this))); + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* CALL CONTEXT CHECKS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + // A proxy call can be either via a `delegatecall` to an implementation, + // or a 7702 call on an authority that points to a delegation. + + /// @dev Returns whether the current call context is on a EIP7702 authority + /// (i.e. externally owned account). + function _onEIP7702Authority() internal view virtual returns (bool result) { + /// @solidity memory-safe-assembly + assembly { + extcodecopy(address(), 0x00, 0x00, 0x20) + // Note: Checking that it starts with hex"ef01" is the most general and futureproof. + // 7702 bytecode is `abi.encodePacked(hex"ef01", uint8(version), address(delegation))`. + result := eq(0xef01, shr(240, mload(0x00))) + } + } + + /// @dev Returns whether the current call context is on the implementation itself. + function _onImplementation() internal view virtual returns (bool) { + return __self == uint160(address(this)); + } + + /// @dev Requires that the current call context is performed via a EIP7702 authority. + function _checkOnlyEIP7702Authority() internal view virtual { + if (!_onEIP7702Authority()) _revertUnauthorizedCallContext(); + } + + /// @dev Requires that the current call context is performed via a proxy. + function _checkOnlyProxy() internal view virtual { + if (_onImplementation()) _revertUnauthorizedCallContext(); + } + + /// @dev Requires that the current call context is NOT performed via a proxy. + /// This is the opposite of `checkOnlyProxy`. + function _checkNotDelegated() internal view virtual { + if (!_onImplementation()) _revertUnauthorizedCallContext(); + } + + /// @dev Requires that the current call context is performed via a EIP7702 authority. + modifier onlyEIP7702Authority() virtual { + _checkOnlyEIP7702Authority(); + _; + } + + /// @dev Requires that the current call context is performed via a proxy. + modifier onlyProxy() virtual { + _checkOnlyProxy(); + _; + } + + /// @dev Requires that the current call context is NOT performed via a proxy. + /// This is the opposite of `onlyProxy`. + modifier notDelegated() virtual { + _checkNotDelegated(); + _; + } + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* PRIVATE HELPERS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + function _revertUnauthorizedCallContext() private pure { + /// @solidity memory-safe-assembly + assembly { + mstore(0x00, 0x9f03a026) // `UnauthorizedCallContext()`. + revert(0x1c, 0x04) + } + } +} diff --git a/biconomy/nexus/1.0.2/node_modules/solady/src/utils/ECDSA.sol b/biconomy/nexus/1.0.2/node_modules/solady/src/utils/ECDSA.sol new file mode 100644 index 0000000..a793e1e --- /dev/null +++ b/biconomy/nexus/1.0.2/node_modules/solady/src/utils/ECDSA.sol @@ -0,0 +1,442 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.4; + +/// @notice Gas optimized ECDSA wrapper. +/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/ECDSA.sol) +/// @author Modified from Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/ECDSA.sol) +/// @author Modified from OpenZeppelin (https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/cryptography/ECDSA.sol) +/// +/// @dev Note: +/// - The recovery functions use the ecrecover precompile (0x1). +/// - As of Solady version 0.0.68, the `recover` variants will revert upon recovery failure. +/// This is for more safety by default. +/// Use the `tryRecover` variants if you need to get the zero address back +/// upon recovery failure instead. +/// - As of Solady version 0.0.134, all `bytes signature` variants accept both +/// regular 65-byte `(r, s, v)` and EIP-2098 `(r, vs)` short form signatures. +/// See: https://eips.ethereum.org/EIPS/eip-2098 +/// This is for calldata efficiency on smart accounts prevalent on L2s. +/// +/// WARNING! Do NOT directly use signatures as unique identifiers: +/// - The recovery operations do NOT check if a signature is non-malleable. +/// - Use a nonce in the digest to prevent replay attacks on the same contract. +/// - Use EIP-712 for the digest to prevent replay attacks across different chains and contracts. +/// EIP-712 also enables readable signing of typed data for better user safety. +/// - If you need a unique hash from a signature, please use the `canonicalHash` functions. +library ECDSA { + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* CONSTANTS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev The order of the secp256k1 elliptic curve. + uint256 internal constant N = 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141; + + /// @dev `N/2 + 1`. Used for checking the malleability of the signature. + uint256 private constant _HALF_N_PLUS_1 = + 0x7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a1; + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* CUSTOM ERRORS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev The signature is invalid. + error InvalidSignature(); + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* RECOVERY OPERATIONS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev Recovers the signer's address from a message digest `hash`, and the `signature`. + function recover(bytes32 hash, bytes memory signature) internal view returns (address result) { + /// @solidity memory-safe-assembly + assembly { + for { let m := mload(0x40) } 1 { + mstore(0x00, 0x8baa579f) // `InvalidSignature()`. + revert(0x1c, 0x04) + } { + switch mload(signature) + case 64 { + let vs := mload(add(signature, 0x40)) + mstore(0x20, add(shr(255, vs), 27)) // `v`. + mstore(0x60, shr(1, shl(1, vs))) // `s`. + } + case 65 { + mstore(0x20, byte(0, mload(add(signature, 0x60)))) // `v`. + mstore(0x60, mload(add(signature, 0x40))) // `s`. + } + default { continue } + mstore(0x00, hash) + mstore(0x40, mload(add(signature, 0x20))) // `r`. + result := mload(staticcall(gas(), 1, 0x00, 0x80, 0x01, 0x20)) + mstore(0x60, 0) // Restore the zero slot. + mstore(0x40, m) // Restore the free memory pointer. + // `returndatasize()` will be `0x20` upon success, and `0x00` otherwise. + if returndatasize() { break } + } + } + } + + /// @dev Recovers the signer's address from a message digest `hash`, and the `signature`. + function recoverCalldata(bytes32 hash, bytes calldata signature) + internal + view + returns (address result) + { + /// @solidity memory-safe-assembly + assembly { + for { let m := mload(0x40) } 1 { + mstore(0x00, 0x8baa579f) // `InvalidSignature()`. + revert(0x1c, 0x04) + } { + switch signature.length + case 64 { + let vs := calldataload(add(signature.offset, 0x20)) + mstore(0x20, add(shr(255, vs), 27)) // `v`. + mstore(0x40, calldataload(signature.offset)) // `r`. + mstore(0x60, shr(1, shl(1, vs))) // `s`. + } + case 65 { + mstore(0x20, byte(0, calldataload(add(signature.offset, 0x40)))) // `v`. + calldatacopy(0x40, signature.offset, 0x40) // Copy `r` and `s`. + } + default { continue } + mstore(0x00, hash) + result := mload(staticcall(gas(), 1, 0x00, 0x80, 0x01, 0x20)) + mstore(0x60, 0) // Restore the zero slot. + mstore(0x40, m) // Restore the free memory pointer. + // `returndatasize()` will be `0x20` upon success, and `0x00` otherwise. + if returndatasize() { break } + } + } + } + + /// @dev Recovers the signer's address from a message digest `hash`, + /// and the EIP-2098 short form signature defined by `r` and `vs`. + function recover(bytes32 hash, bytes32 r, bytes32 vs) internal view returns (address result) { + /// @solidity memory-safe-assembly + assembly { + let m := mload(0x40) // Cache the free memory pointer. + mstore(0x00, hash) + mstore(0x20, add(shr(255, vs), 27)) // `v`. + mstore(0x40, r) + mstore(0x60, shr(1, shl(1, vs))) // `s`. + result := mload(staticcall(gas(), 1, 0x00, 0x80, 0x01, 0x20)) + // `returndatasize()` will be `0x20` upon success, and `0x00` otherwise. + if iszero(returndatasize()) { + mstore(0x00, 0x8baa579f) // `InvalidSignature()`. + revert(0x1c, 0x04) + } + mstore(0x60, 0) // Restore the zero slot. + mstore(0x40, m) // Restore the free memory pointer. + } + } + + /// @dev Recovers the signer's address from a message digest `hash`, + /// and the signature defined by `v`, `r`, `s`. + function recover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) + internal + view + returns (address result) + { + /// @solidity memory-safe-assembly + assembly { + let m := mload(0x40) // Cache the free memory pointer. + mstore(0x00, hash) + mstore(0x20, and(v, 0xff)) + mstore(0x40, r) + mstore(0x60, s) + result := mload(staticcall(gas(), 1, 0x00, 0x80, 0x01, 0x20)) + // `returndatasize()` will be `0x20` upon success, and `0x00` otherwise. + if iszero(returndatasize()) { + mstore(0x00, 0x8baa579f) // `InvalidSignature()`. + revert(0x1c, 0x04) + } + mstore(0x60, 0) // Restore the zero slot. + mstore(0x40, m) // Restore the free memory pointer. + } + } + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* TRY-RECOVER OPERATIONS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + // WARNING! + // These functions will NOT revert upon recovery failure. + // Instead, they will return the zero address upon recovery failure. + // It is critical that the returned address is NEVER compared against + // a zero address (e.g. an uninitialized address variable). + + /// @dev Recovers the signer's address from a message digest `hash`, and the `signature`. + function tryRecover(bytes32 hash, bytes memory signature) + internal + view + returns (address result) + { + /// @solidity memory-safe-assembly + assembly { + for { let m := mload(0x40) } 1 {} { + switch mload(signature) + case 64 { + let vs := mload(add(signature, 0x40)) + mstore(0x20, add(shr(255, vs), 27)) // `v`. + mstore(0x60, shr(1, shl(1, vs))) // `s`. + } + case 65 { + mstore(0x20, byte(0, mload(add(signature, 0x60)))) // `v`. + mstore(0x60, mload(add(signature, 0x40))) // `s`. + } + default { break } + mstore(0x00, hash) + mstore(0x40, mload(add(signature, 0x20))) // `r`. + pop(staticcall(gas(), 1, 0x00, 0x80, 0x40, 0x20)) + mstore(0x60, 0) // Restore the zero slot. + // `returndatasize()` will be `0x20` upon success, and `0x00` otherwise. + result := mload(xor(0x60, returndatasize())) + mstore(0x40, m) // Restore the free memory pointer. + break + } + } + } + + /// @dev Recovers the signer's address from a message digest `hash`, and the `signature`. + function tryRecoverCalldata(bytes32 hash, bytes calldata signature) + internal + view + returns (address result) + { + /// @solidity memory-safe-assembly + assembly { + for { let m := mload(0x40) } 1 {} { + switch signature.length + case 64 { + let vs := calldataload(add(signature.offset, 0x20)) + mstore(0x20, add(shr(255, vs), 27)) // `v`. + mstore(0x40, calldataload(signature.offset)) // `r`. + mstore(0x60, shr(1, shl(1, vs))) // `s`. + } + case 65 { + mstore(0x20, byte(0, calldataload(add(signature.offset, 0x40)))) // `v`. + calldatacopy(0x40, signature.offset, 0x40) // Copy `r` and `s`. + } + default { break } + mstore(0x00, hash) + pop(staticcall(gas(), 1, 0x00, 0x80, 0x40, 0x20)) + mstore(0x60, 0) // Restore the zero slot. + // `returndatasize()` will be `0x20` upon success, and `0x00` otherwise. + result := mload(xor(0x60, returndatasize())) + mstore(0x40, m) // Restore the free memory pointer. + break + } + } + } + + /// @dev Recovers the signer's address from a message digest `hash`, + /// and the EIP-2098 short form signature defined by `r` and `vs`. + function tryRecover(bytes32 hash, bytes32 r, bytes32 vs) + internal + view + returns (address result) + { + /// @solidity memory-safe-assembly + assembly { + let m := mload(0x40) // Cache the free memory pointer. + mstore(0x00, hash) + mstore(0x20, add(shr(255, vs), 27)) // `v`. + mstore(0x40, r) + mstore(0x60, shr(1, shl(1, vs))) // `s`. + pop(staticcall(gas(), 1, 0x00, 0x80, 0x40, 0x20)) + mstore(0x60, 0) // Restore the zero slot. + // `returndatasize()` will be `0x20` upon success, and `0x00` otherwise. + result := mload(xor(0x60, returndatasize())) + mstore(0x40, m) // Restore the free memory pointer. + } + } + + /// @dev Recovers the signer's address from a message digest `hash`, + /// and the signature defined by `v`, `r`, `s`. + function tryRecover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) + internal + view + returns (address result) + { + /// @solidity memory-safe-assembly + assembly { + let m := mload(0x40) // Cache the free memory pointer. + mstore(0x00, hash) + mstore(0x20, and(v, 0xff)) + mstore(0x40, r) + mstore(0x60, s) + pop(staticcall(gas(), 1, 0x00, 0x80, 0x40, 0x20)) + mstore(0x60, 0) // Restore the zero slot. + // `returndatasize()` will be `0x20` upon success, and `0x00` otherwise. + result := mload(xor(0x60, returndatasize())) + mstore(0x40, m) // Restore the free memory pointer. + } + } + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* HASHING OPERATIONS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev Returns an Ethereum Signed Message, created from a `hash`. + /// This produces a hash corresponding to the one signed with the + /// [`eth_sign`](https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_sign) + /// JSON-RPC method as part of EIP-191. + function toEthSignedMessageHash(bytes32 hash) internal pure returns (bytes32 result) { + /// @solidity memory-safe-assembly + assembly { + mstore(0x20, hash) // Store into scratch space for keccak256. + mstore(0x00, "\x00\x00\x00\x00\x19Ethereum Signed Message:\n32") // 28 bytes. + result := keccak256(0x04, 0x3c) // `32 * 2 - (32 - 28) = 60 = 0x3c`. + } + } + + /// @dev Returns an Ethereum Signed Message, created from `s`. + /// This produces a hash corresponding to the one signed with the + /// [`eth_sign`](https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_sign) + /// JSON-RPC method as part of EIP-191. + /// Note: Supports lengths of `s` up to 999999 bytes. + function toEthSignedMessageHash(bytes memory s) internal pure returns (bytes32 result) { + /// @solidity memory-safe-assembly + assembly { + let sLength := mload(s) + let o := 0x20 + mstore(o, "\x19Ethereum Signed Message:\n") // 26 bytes, zero-right-padded. + mstore(0x00, 0x00) + // Convert the `s.length` to ASCII decimal representation: `base10(s.length)`. + for { let temp := sLength } 1 {} { + o := sub(o, 1) + mstore8(o, add(48, mod(temp, 10))) + temp := div(temp, 10) + if iszero(temp) { break } + } + let n := sub(0x3a, o) // Header length: `26 + 32 - o`. + // Throw an out-of-offset error (consumes all gas) if the header exceeds 32 bytes. + returndatacopy(returndatasize(), returndatasize(), gt(n, 0x20)) + mstore(s, or(mload(0x00), mload(n))) // Temporarily store the header. + result := keccak256(add(s, sub(0x20, n)), add(n, sLength)) + mstore(s, sLength) // Restore the length. + } + } + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* CANONICAL HASH FUNCTIONS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + // The following functions returns the hash of the signature in it's canonicalized format, + // which is the 65-byte `abi.encodePacked(r, s, uint8(v))`, where `v` is either 27 or 28. + // If `s` is greater than `N / 2` then it will be converted to `N - s` + // and the `v` value will be flipped. + // If the signature has an invalid length, or if `v` is invalid, + // a uniquely corrupt hash will be returned. + // These functions are useful for "poor-mans-VRF". + + /// @dev Returns the canonical hash of `signature`. + function canonicalHash(bytes memory signature) internal pure returns (bytes32 result) { + // @solidity memory-safe-assembly + assembly { + let l := mload(signature) + for {} 1 {} { + mstore(0x00, mload(add(signature, 0x20))) // `r`. + let s := mload(add(signature, 0x40)) + let v := mload(add(signature, 0x41)) + if eq(l, 64) { + v := add(shr(255, s), 27) + s := shr(1, shl(1, s)) + } + if iszero(lt(s, _HALF_N_PLUS_1)) { + v := xor(v, 7) + s := sub(N, s) + } + mstore(0x21, v) + mstore(0x20, s) + result := keccak256(0x00, 0x41) + mstore(0x21, 0) // Restore the overwritten part of the free memory pointer. + break + } + + // If the length is neither 64 nor 65, return a uniquely corrupted hash. + if iszero(lt(sub(l, 64), 2)) { + // `bytes4(keccak256("InvalidSignatureLength"))`. + result := xor(keccak256(add(signature, 0x20), l), 0xd62f1ab2) + } + } + } + + /// @dev Returns the canonical hash of `signature`. + function canonicalHashCalldata(bytes calldata signature) + internal + pure + returns (bytes32 result) + { + // @solidity memory-safe-assembly + assembly { + for {} 1 {} { + mstore(0x00, calldataload(signature.offset)) // `r`. + let s := calldataload(add(signature.offset, 0x20)) + let v := calldataload(add(signature.offset, 0x21)) + if eq(signature.length, 64) { + v := add(shr(255, s), 27) + s := shr(1, shl(1, s)) + } + if iszero(lt(s, _HALF_N_PLUS_1)) { + v := xor(v, 7) + s := sub(N, s) + } + mstore(0x21, v) + mstore(0x20, s) + result := keccak256(0x00, 0x41) + mstore(0x21, 0) // Restore the overwritten part of the free memory pointer. + break + } + // If the length is neither 64 nor 65, return a uniquely corrupted hash. + if iszero(lt(sub(signature.length, 64), 2)) { + calldatacopy(mload(0x40), signature.offset, signature.length) + // `bytes4(keccak256("InvalidSignatureLength"))`. + result := xor(keccak256(mload(0x40), signature.length), 0xd62f1ab2) + } + } + } + + /// @dev Returns the canonical hash of `signature`. + function canonicalHash(bytes32 r, bytes32 vs) internal pure returns (bytes32 result) { + // @solidity memory-safe-assembly + assembly { + mstore(0x00, r) // `r`. + let v := add(shr(255, vs), 27) + let s := shr(1, shl(1, vs)) + mstore(0x21, v) + mstore(0x20, s) + result := keccak256(0x00, 0x41) + mstore(0x21, 0) // Restore the overwritten part of the free memory pointer. + } + } + + /// @dev Returns the canonical hash of `signature`. + function canonicalHash(uint8 v, bytes32 r, bytes32 s) internal pure returns (bytes32 result) { + // @solidity memory-safe-assembly + assembly { + mstore(0x00, r) // `r`. + if iszero(lt(s, _HALF_N_PLUS_1)) { + v := xor(v, 7) + s := sub(N, s) + } + mstore(0x21, v) + mstore(0x20, s) + result := keccak256(0x00, 0x41) + mstore(0x21, 0) // Restore the overwritten part of the free memory pointer. + } + } + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* EMPTY CALLDATA HELPERS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev Returns an empty calldata bytes. + function emptySignature() internal pure returns (bytes calldata signature) { + /// @solidity memory-safe-assembly + assembly { + signature.length := 0 + } + } +} diff --git a/biconomy/nexus/1.0.2/node_modules/solady/src/utils/EIP712.sol b/biconomy/nexus/1.0.2/node_modules/solady/src/utils/EIP712.sol new file mode 100644 index 0000000..54a28a3 --- /dev/null +++ b/biconomy/nexus/1.0.2/node_modules/solady/src/utils/EIP712.sol @@ -0,0 +1,241 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.4; + +/// @notice Contract for EIP-712 typed structured data hashing and signing. +/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/EIP712.sol) +/// @author Modified from Solbase (https://github.com/Sol-DAO/solbase/blob/main/src/utils/EIP712.sol) +/// @author Modified from OpenZeppelin (https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/cryptography/EIP712.sol) +/// +/// @dev Note, this implementation: +/// - Uses `address(this)` for the `verifyingContract` field. +/// - Does NOT use the optional EIP-712 salt. +/// - Does NOT use any EIP-712 extensions. +/// This is for simplicity and to save gas. +/// If you need to customize, please fork / modify accordingly. +abstract contract EIP712 { + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* CONSTANTS AND IMMUTABLES */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev `keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)")`. + bytes32 internal constant _DOMAIN_TYPEHASH = + 0x8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f; + + /// @dev `keccak256("EIP712Domain(string name,string version,address verifyingContract)")`. + /// This is only used in `_hashTypedDataSansChainId`. + bytes32 internal constant _DOMAIN_TYPEHASH_SANS_CHAIN_ID = + 0x91ab3d17e3a50a9d89e63fd30b92be7f5336b03b287bb946787a83a9d62a2766; + + uint256 private immutable _cachedThis; + uint256 private immutable _cachedChainId; + bytes32 private immutable _cachedNameHash; + bytes32 private immutable _cachedVersionHash; + bytes32 private immutable _cachedDomainSeparator; + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* CONSTRUCTOR */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev Cache the hashes for cheaper runtime gas costs. + /// In the case of upgradeable contracts (i.e. proxies), + /// or if the chain id changes due to a hard fork, + /// the domain separator will be seamlessly calculated on-the-fly. + constructor() { + _cachedThis = uint256(uint160(address(this))); + _cachedChainId = block.chainid; + + string memory name; + string memory version; + if (!_domainNameAndVersionMayChange()) (name, version) = _domainNameAndVersion(); + bytes32 nameHash = _domainNameAndVersionMayChange() ? bytes32(0) : keccak256(bytes(name)); + bytes32 versionHash = + _domainNameAndVersionMayChange() ? bytes32(0) : keccak256(bytes(version)); + _cachedNameHash = nameHash; + _cachedVersionHash = versionHash; + + bytes32 separator; + if (!_domainNameAndVersionMayChange()) { + /// @solidity memory-safe-assembly + assembly { + let m := mload(0x40) // Load the free memory pointer. + mstore(m, _DOMAIN_TYPEHASH) + mstore(add(m, 0x20), nameHash) + mstore(add(m, 0x40), versionHash) + mstore(add(m, 0x60), chainid()) + mstore(add(m, 0x80), address()) + separator := keccak256(m, 0xa0) + } + } + _cachedDomainSeparator = separator; + } + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* FUNCTIONS TO OVERRIDE */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev Please override this function to return the domain name and version. + /// ``` + /// function _domainNameAndVersion() + /// internal + /// pure + /// virtual + /// returns (string memory name, string memory version) + /// { + /// name = "Solady"; + /// version = "1"; + /// } + /// ``` + /// + /// Note: If the returned result may change after the contract has been deployed, + /// you must override `_domainNameAndVersionMayChange()` to return true. + function _domainNameAndVersion() + internal + view + virtual + returns (string memory name, string memory version); + + /// @dev Returns if `_domainNameAndVersion()` may change + /// after the contract has been deployed (i.e. after the constructor). + /// Default: false. + function _domainNameAndVersionMayChange() internal pure virtual returns (bool result) {} + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* HASHING OPERATIONS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev Returns the EIP-712 domain separator. + function _domainSeparator() internal view virtual returns (bytes32 separator) { + if (_domainNameAndVersionMayChange()) { + separator = _buildDomainSeparator(); + } else { + separator = _cachedDomainSeparator; + if (_cachedDomainSeparatorInvalidated()) separator = _buildDomainSeparator(); + } + } + + /// @dev Returns the hash of the fully encoded EIP-712 message for this domain, + /// given `structHash`, as defined in + /// https://eips.ethereum.org/EIPS/eip-712#definition-of-hashstruct. + /// + /// The hash can be used together with {ECDSA-recover} to obtain the signer of a message: + /// ``` + /// bytes32 digest = _hashTypedData(keccak256(abi.encode( + /// keccak256("Mail(address to,string contents)"), + /// mailTo, + /// keccak256(bytes(mailContents)) + /// ))); + /// address signer = ECDSA.recover(digest, signature); + /// ``` + function _hashTypedData(bytes32 structHash) internal view virtual returns (bytes32 digest) { + // We will use `digest` to store the domain separator to save a bit of gas. + if (_domainNameAndVersionMayChange()) { + digest = _buildDomainSeparator(); + } else { + digest = _cachedDomainSeparator; + if (_cachedDomainSeparatorInvalidated()) digest = _buildDomainSeparator(); + } + /// @solidity memory-safe-assembly + assembly { + // Compute the digest. + mstore(0x00, 0x1901000000000000) // Store "\x19\x01". + mstore(0x1a, digest) // Store the domain separator. + mstore(0x3a, structHash) // Store the struct hash. + digest := keccak256(0x18, 0x42) + // Restore the part of the free memory slot that was overwritten. + mstore(0x3a, 0) + } + } + + /// @dev Variant of `_hashTypedData` that excludes the chain ID. + /// We expect that most contracts will use `_hashTypedData` as the main hash, + /// and `_hashTypedDataSansChainId` only occasionally for cross-chain workflows. + /// Thus this is optimized for smaller bytecode size over runtime gas. + function _hashTypedDataSansChainId(bytes32 structHash) + internal + view + virtual + returns (bytes32 digest) + { + (string memory name, string memory version) = _domainNameAndVersion(); + /// @solidity memory-safe-assembly + assembly { + let m := mload(0x40) // Load the free memory pointer. + mstore(0x00, _DOMAIN_TYPEHASH_SANS_CHAIN_ID) + mstore(0x20, keccak256(add(name, 0x20), mload(name))) + mstore(0x40, keccak256(add(version, 0x20), mload(version))) + mstore(0x60, address()) + // Compute the digest. + mstore(0x20, keccak256(0x00, 0x80)) // Store the domain separator. + mstore(0x00, 0x1901) // Store "\x19\x01". + mstore(0x40, structHash) // Store the struct hash. + digest := keccak256(0x1e, 0x42) + mstore(0x40, m) // Restore the free memory pointer. + mstore(0x60, 0) // Restore the zero pointer. + } + } + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* EIP-5267 OPERATIONS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev See: https://eips.ethereum.org/EIPS/eip-5267 + function eip712Domain() + public + view + virtual + returns ( + bytes1 fields, + string memory name, + string memory version, + uint256 chainId, + address verifyingContract, + bytes32 salt, + uint256[] memory extensions + ) + { + fields = hex"0f"; // `0b01111`. + (name, version) = _domainNameAndVersion(); + chainId = block.chainid; + verifyingContract = address(this); + salt = salt; // `bytes32(0)`. + extensions = extensions; // `new uint256[](0)`. + } + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* PRIVATE HELPERS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev Returns the EIP-712 domain separator. + function _buildDomainSeparator() private view returns (bytes32 separator) { + // We will use `separator` to store the name hash to save a bit of gas. + bytes32 versionHash; + if (_domainNameAndVersionMayChange()) { + (string memory name, string memory version) = _domainNameAndVersion(); + separator = keccak256(bytes(name)); + versionHash = keccak256(bytes(version)); + } else { + separator = _cachedNameHash; + versionHash = _cachedVersionHash; + } + /// @solidity memory-safe-assembly + assembly { + let m := mload(0x40) // Load the free memory pointer. + mstore(m, _DOMAIN_TYPEHASH) + mstore(add(m, 0x20), separator) // Name hash. + mstore(add(m, 0x40), versionHash) + mstore(add(m, 0x60), chainid()) + mstore(add(m, 0x80), address()) + separator := keccak256(m, 0xa0) + } + } + + /// @dev Returns if the cached domain separator has been invalidated. + function _cachedDomainSeparatorInvalidated() private view returns (bool result) { + uint256 cachedChainId = _cachedChainId; + uint256 cachedThis = _cachedThis; + /// @solidity memory-safe-assembly + assembly { + result := iszero(and(eq(chainid(), cachedChainId), eq(address(), cachedThis))) + } + } +} diff --git a/biconomy/nexus/1.0.2/node_modules/solady/src/utils/LibClone.sol b/biconomy/nexus/1.0.2/node_modules/solady/src/utils/LibClone.sol new file mode 100644 index 0000000..d9f1d0c --- /dev/null +++ b/biconomy/nexus/1.0.2/node_modules/solady/src/utils/LibClone.sol @@ -0,0 +1,2858 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.4; + +/// @notice Minimal proxy library. +/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/LibClone.sol) +/// @author Minimal proxy by 0age (https://github.com/0age) +/// @author Clones with immutable args by wighawag, zefram.eth, Saw-mon & Natalie +/// (https://github.com/Saw-mon-and-Natalie/clones-with-immutable-args) +/// @author Minimal ERC1967 proxy by jtriley-eth (https://github.com/jtriley-eth/minimum-viable-proxy) +/// +/// @dev Minimal proxy: +/// Although the sw0nt pattern saves 5 gas over the ERC1167 pattern during runtime, +/// it is not supported out-of-the-box on Etherscan. Hence, we choose to use the 0age pattern, +/// which saves 4 gas over the ERC1167 pattern during runtime, and has the smallest bytecode. +/// - Automatically verified on Etherscan. +/// +/// @dev Minimal proxy (PUSH0 variant): +/// This is a new minimal proxy that uses the PUSH0 opcode introduced during Shanghai. +/// It is optimized first for minimal runtime gas, then for minimal bytecode. +/// The PUSH0 clone functions are intentionally postfixed with a jarring "_PUSH0" as +/// many EVM chains may not support the PUSH0 opcode in the early months after Shanghai. +/// Please use with caution. +/// - Automatically verified on Etherscan. +/// +/// @dev Clones with immutable args (CWIA): +/// The implementation of CWIA here is does NOT append the immutable args into the calldata +/// passed into delegatecall. It is simply an ERC1167 minimal proxy with the immutable arguments +/// appended to the back of the runtime bytecode. +/// - Uses the identity precompile (0x4) to copy args during deployment. +/// +/// @dev Minimal ERC1967 proxy: +/// An minimal ERC1967 proxy, intended to be upgraded with UUPS. +/// This is NOT the same as ERC1967Factory's transparent proxy, which includes admin logic. +/// - Automatically verified on Etherscan. +/// +/// @dev Minimal ERC1967 proxy with immutable args: +/// - Uses the identity precompile (0x4) to copy args during deployment. +/// - Automatically verified on Etherscan. +/// +/// @dev ERC1967I proxy: +/// An variant of the minimal ERC1967 proxy, with a special code path that activates +/// if `calldatasize() == 1`. This code path skips the delegatecall and directly returns the +/// `implementation` address. The returned implementation is guaranteed to be valid if the +/// keccak256 of the proxy's code is equal to `ERC1967I_CODE_HASH`. +/// +/// @dev ERC1967I proxy with immutable args: +/// An variant of the minimal ERC1967 proxy, with a special code path that activates +/// if `calldatasize() == 1`. This code path skips the delegatecall and directly returns the +/// - Uses the identity precompile (0x4) to copy args during deployment. +/// +/// @dev Minimal ERC1967 beacon proxy: +/// A minimal beacon proxy, intended to be upgraded with an upgradable beacon. +/// - Automatically verified on Etherscan. +/// +/// @dev Minimal ERC1967 beacon proxy with immutable args: +/// - Uses the identity precompile (0x4) to copy args during deployment. +/// - Automatically verified on Etherscan. +/// +/// @dev ERC1967I beacon proxy: +/// An variant of the minimal ERC1967 beacon proxy, with a special code path that activates +/// if `calldatasize() == 1`. This code path skips the delegatecall and directly returns the +/// `implementation` address. The returned implementation is guaranteed to be valid if the +/// keccak256 of the proxy's code is equal to `ERC1967I_CODE_HASH`. +/// +/// @dev ERC1967I proxy with immutable args: +/// An variant of the minimal ERC1967 beacon proxy, with a special code path that activates +/// if `calldatasize() == 1`. This code path skips the delegatecall and directly returns the +/// - Uses the identity precompile (0x4) to copy args during deployment. +library LibClone { + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* CONSTANTS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev The keccak256 of deployed code for the clone proxy, + /// with the implementation set to `address(0)`. + bytes32 internal constant CLONE_CODE_HASH = + 0x48db2cfdb2853fce0b464f1f93a1996469459df3ab6c812106074c4106a1eb1f; + + /// @dev The keccak256 of deployed code for the PUSH0 proxy, + /// with the implementation set to `address(0)`. + bytes32 internal constant PUSH0_CLONE_CODE_HASH = + 0x67bc6bde1b84d66e267c718ba44cf3928a615d29885537955cb43d44b3e789dc; + + /// @dev The keccak256 of deployed code for the ERC-1167 CWIA proxy, + /// with the implementation set to `address(0)`. + bytes32 internal constant CWIA_CODE_HASH = + 0x3cf92464268225a4513da40a34d967354684c32cd0edd67b5f668dfe3550e940; + + /// @dev The keccak256 of the deployed code for the ERC1967 proxy. + bytes32 internal constant ERC1967_CODE_HASH = + 0xaaa52c8cc8a0e3fd27ce756cc6b4e70c51423e9b597b11f32d3e49f8b1fc890d; + + /// @dev The keccak256 of the deployed code for the ERC1967I proxy. + bytes32 internal constant ERC1967I_CODE_HASH = + 0xce700223c0d4cea4583409accfc45adac4a093b3519998a9cbbe1504dadba6f7; + + /// @dev The keccak256 of the deployed code for the ERC1967 beacon proxy. + bytes32 internal constant ERC1967_BEACON_PROXY_CODE_HASH = + 0x14044459af17bc4f0f5aa2f658cb692add77d1302c29fe2aebab005eea9d1162; + + /// @dev The keccak256 of the deployed code for the ERC1967 beacon proxy. + bytes32 internal constant ERC1967I_BEACON_PROXY_CODE_HASH = + 0xf8c46d2793d5aa984eb827aeaba4b63aedcab80119212fce827309788735519a; + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* CUSTOM ERRORS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev Unable to deploy the clone. + error DeploymentFailed(); + + /// @dev The salt must start with either the zero address or `by`. + error SaltDoesNotStartWith(); + + /// @dev The ETH transfer has failed. + error ETHTransferFailed(); + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* MINIMAL PROXY OPERATIONS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev Deploys a clone of `implementation`. + function clone(address implementation) internal returns (address instance) { + instance = clone(0, implementation); + } + + /// @dev Deploys a clone of `implementation`. + /// Deposits `value` ETH during deployment. + function clone(uint256 value, address implementation) internal returns (address instance) { + /// @solidity memory-safe-assembly + assembly { + /** + * --------------------------------------------------------------------------+ + * CREATION (9 bytes) | + * --------------------------------------------------------------------------| + * Opcode | Mnemonic | Stack | Memory | + * --------------------------------------------------------------------------| + * 60 runSize | PUSH1 runSize | r | | + * 3d | RETURNDATASIZE | 0 r | | + * 81 | DUP2 | r 0 r | | + * 60 offset | PUSH1 offset | o r 0 r | | + * 3d | RETURNDATASIZE | 0 o r 0 r | | + * 39 | CODECOPY | 0 r | [0..runSize): runtime code | + * f3 | RETURN | | [0..runSize): runtime code | + * --------------------------------------------------------------------------| + * RUNTIME (44 bytes) | + * --------------------------------------------------------------------------| + * Opcode | Mnemonic | Stack | Memory | + * --------------------------------------------------------------------------| + * | + * ::: keep some values in stack ::::::::::::::::::::::::::::::::::::::::::: | + * 3d | RETURNDATASIZE | 0 | | + * 3d | RETURNDATASIZE | 0 0 | | + * 3d | RETURNDATASIZE | 0 0 0 | | + * 3d | RETURNDATASIZE | 0 0 0 0 | | + * | + * ::: copy calldata to memory ::::::::::::::::::::::::::::::::::::::::::::: | + * 36 | CALLDATASIZE | cds 0 0 0 0 | | + * 3d | RETURNDATASIZE | 0 cds 0 0 0 0 | | + * 3d | RETURNDATASIZE | 0 0 cds 0 0 0 0 | | + * 37 | CALLDATACOPY | 0 0 0 0 | [0..cds): calldata | + * | + * ::: delegate call to the implementation contract :::::::::::::::::::::::: | + * 36 | CALLDATASIZE | cds 0 0 0 0 | [0..cds): calldata | + * 3d | RETURNDATASIZE | 0 cds 0 0 0 0 | [0..cds): calldata | + * 73 addr | PUSH20 addr | addr 0 cds 0 0 0 0 | [0..cds): calldata | + * 5a | GAS | gas addr 0 cds 0 0 0 0 | [0..cds): calldata | + * f4 | DELEGATECALL | success 0 0 | [0..cds): calldata | + * | + * ::: copy return data to memory :::::::::::::::::::::::::::::::::::::::::: | + * 3d | RETURNDATASIZE | rds success 0 0 | [0..cds): calldata | + * 3d | RETURNDATASIZE | rds rds success 0 0 | [0..cds): calldata | + * 93 | SWAP4 | 0 rds success 0 rds | [0..cds): calldata | + * 80 | DUP1 | 0 0 rds success 0 rds | [0..cds): calldata | + * 3e | RETURNDATACOPY | success 0 rds | [0..rds): returndata | + * | + * 60 0x2a | PUSH1 0x2a | 0x2a success 0 rds | [0..rds): returndata | + * 57 | JUMPI | 0 rds | [0..rds): returndata | + * | + * ::: revert :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: | + * fd | REVERT | | [0..rds): returndata | + * | + * ::: return :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: | + * 5b | JUMPDEST | 0 rds | [0..rds): returndata | + * f3 | RETURN | | [0..rds): returndata | + * --------------------------------------------------------------------------+ + */ + mstore(0x21, 0x5af43d3d93803e602a57fd5bf3) + mstore(0x14, implementation) + mstore(0x00, 0x602c3d8160093d39f33d3d3d3d363d3d37363d73) + instance := create(value, 0x0c, 0x35) + if iszero(instance) { + mstore(0x00, 0x30116425) // `DeploymentFailed()`. + revert(0x1c, 0x04) + } + mstore(0x21, 0) // Restore the overwritten part of the free memory pointer. + } + } + + /// @dev Deploys a deterministic clone of `implementation` with `salt`. + function cloneDeterministic(address implementation, bytes32 salt) + internal + returns (address instance) + { + instance = cloneDeterministic(0, implementation, salt); + } + + /// @dev Deploys a deterministic clone of `implementation` with `salt`. + /// Deposits `value` ETH during deployment. + function cloneDeterministic(uint256 value, address implementation, bytes32 salt) + internal + returns (address instance) + { + /// @solidity memory-safe-assembly + assembly { + mstore(0x21, 0x5af43d3d93803e602a57fd5bf3) + mstore(0x14, implementation) + mstore(0x00, 0x602c3d8160093d39f33d3d3d3d363d3d37363d73) + instance := create2(value, 0x0c, 0x35, salt) + if iszero(instance) { + mstore(0x00, 0x30116425) // `DeploymentFailed()`. + revert(0x1c, 0x04) + } + mstore(0x21, 0) // Restore the overwritten part of the free memory pointer. + } + } + + /// @dev Returns the initialization code of the clone of `implementation`. + function initCode(address implementation) internal pure returns (bytes memory c) { + /// @solidity memory-safe-assembly + assembly { + c := mload(0x40) + mstore(add(c, 0x40), 0x5af43d3d93803e602a57fd5bf30000000000000000000000) + mstore(add(c, 0x28), implementation) + mstore(add(c, 0x14), 0x602c3d8160093d39f33d3d3d3d363d3d37363d73) + mstore(c, 0x35) // Store the length. + mstore(0x40, add(c, 0x60)) // Allocate memory. + } + } + + /// @dev Returns the initialization code hash of the clone of `implementation`. + function initCodeHash(address implementation) internal pure returns (bytes32 hash) { + /// @solidity memory-safe-assembly + assembly { + mstore(0x21, 0x5af43d3d93803e602a57fd5bf3) + mstore(0x14, implementation) + mstore(0x00, 0x602c3d8160093d39f33d3d3d3d363d3d37363d73) + hash := keccak256(0x0c, 0x35) + mstore(0x21, 0) // Restore the overwritten part of the free memory pointer. + } + } + + /// @dev Returns the address of the clone of `implementation`, with `salt` by `deployer`. + /// Note: The returned result has dirty upper 96 bits. Please clean if used in assembly. + function predictDeterministicAddress(address implementation, bytes32 salt, address deployer) + internal + pure + returns (address predicted) + { + bytes32 hash = initCodeHash(implementation); + predicted = predictDeterministicAddress(hash, salt, deployer); + } + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* MINIMAL PROXY OPERATIONS (PUSH0 VARIANT) */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev Deploys a PUSH0 clone of `implementation`. + function clone_PUSH0(address implementation) internal returns (address instance) { + instance = clone_PUSH0(0, implementation); + } + + /// @dev Deploys a PUSH0 clone of `implementation`. + /// Deposits `value` ETH during deployment. + function clone_PUSH0(uint256 value, address implementation) + internal + returns (address instance) + { + /// @solidity memory-safe-assembly + assembly { + /** + * --------------------------------------------------------------------------+ + * CREATION (9 bytes) | + * --------------------------------------------------------------------------| + * Opcode | Mnemonic | Stack | Memory | + * --------------------------------------------------------------------------| + * 60 runSize | PUSH1 runSize | r | | + * 5f | PUSH0 | 0 r | | + * 81 | DUP2 | r 0 r | | + * 60 offset | PUSH1 offset | o r 0 r | | + * 5f | PUSH0 | 0 o r 0 r | | + * 39 | CODECOPY | 0 r | [0..runSize): runtime code | + * f3 | RETURN | | [0..runSize): runtime code | + * --------------------------------------------------------------------------| + * RUNTIME (45 bytes) | + * --------------------------------------------------------------------------| + * Opcode | Mnemonic | Stack | Memory | + * --------------------------------------------------------------------------| + * | + * ::: keep some values in stack ::::::::::::::::::::::::::::::::::::::::::: | + * 5f | PUSH0 | 0 | | + * 5f | PUSH0 | 0 0 | | + * | + * ::: copy calldata to memory ::::::::::::::::::::::::::::::::::::::::::::: | + * 36 | CALLDATASIZE | cds 0 0 | | + * 5f | PUSH0 | 0 cds 0 0 | | + * 5f | PUSH0 | 0 0 cds 0 0 | | + * 37 | CALLDATACOPY | 0 0 | [0..cds): calldata | + * | + * ::: delegate call to the implementation contract :::::::::::::::::::::::: | + * 36 | CALLDATASIZE | cds 0 0 | [0..cds): calldata | + * 5f | PUSH0 | 0 cds 0 0 | [0..cds): calldata | + * 73 addr | PUSH20 addr | addr 0 cds 0 0 | [0..cds): calldata | + * 5a | GAS | gas addr 0 cds 0 0 | [0..cds): calldata | + * f4 | DELEGATECALL | success | [0..cds): calldata | + * | + * ::: copy return data to memory :::::::::::::::::::::::::::::::::::::::::: | + * 3d | RETURNDATASIZE | rds success | [0..cds): calldata | + * 5f | PUSH0 | 0 rds success | [0..cds): calldata | + * 5f | PUSH0 | 0 0 rds success | [0..cds): calldata | + * 3e | RETURNDATACOPY | success | [0..rds): returndata | + * | + * 60 0x29 | PUSH1 0x29 | 0x29 success | [0..rds): returndata | + * 57 | JUMPI | | [0..rds): returndata | + * | + * ::: revert :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: | + * 3d | RETURNDATASIZE | rds | [0..rds): returndata | + * 5f | PUSH0 | 0 rds | [0..rds): returndata | + * fd | REVERT | | [0..rds): returndata | + * | + * ::: return :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: | + * 5b | JUMPDEST | | [0..rds): returndata | + * 3d | RETURNDATASIZE | rds | [0..rds): returndata | + * 5f | PUSH0 | 0 rds | [0..rds): returndata | + * f3 | RETURN | | [0..rds): returndata | + * --------------------------------------------------------------------------+ + */ + mstore(0x24, 0x5af43d5f5f3e6029573d5ffd5b3d5ff3) // 16 + mstore(0x14, implementation) // 20 + mstore(0x00, 0x602d5f8160095f39f35f5f365f5f37365f73) // 9 + 9 + instance := create(value, 0x0e, 0x36) + if iszero(instance) { + mstore(0x00, 0x30116425) // `DeploymentFailed()`. + revert(0x1c, 0x04) + } + mstore(0x24, 0) // Restore the overwritten part of the free memory pointer. + } + } + + /// @dev Deploys a deterministic PUSH0 clone of `implementation` with `salt`. + function cloneDeterministic_PUSH0(address implementation, bytes32 salt) + internal + returns (address instance) + { + instance = cloneDeterministic_PUSH0(0, implementation, salt); + } + + /// @dev Deploys a deterministic PUSH0 clone of `implementation` with `salt`. + /// Deposits `value` ETH during deployment. + function cloneDeterministic_PUSH0(uint256 value, address implementation, bytes32 salt) + internal + returns (address instance) + { + /// @solidity memory-safe-assembly + assembly { + mstore(0x24, 0x5af43d5f5f3e6029573d5ffd5b3d5ff3) // 16 + mstore(0x14, implementation) // 20 + mstore(0x00, 0x602d5f8160095f39f35f5f365f5f37365f73) // 9 + 9 + instance := create2(value, 0x0e, 0x36, salt) + if iszero(instance) { + mstore(0x00, 0x30116425) // `DeploymentFailed()`. + revert(0x1c, 0x04) + } + mstore(0x24, 0) // Restore the overwritten part of the free memory pointer. + } + } + + /// @dev Returns the initialization code of the PUSH0 clone of `implementation`. + function initCode_PUSH0(address implementation) internal pure returns (bytes memory c) { + /// @solidity memory-safe-assembly + assembly { + c := mload(0x40) + mstore(add(c, 0x40), 0x5af43d5f5f3e6029573d5ffd5b3d5ff300000000000000000000) // 16 + mstore(add(c, 0x26), implementation) // 20 + mstore(add(c, 0x12), 0x602d5f8160095f39f35f5f365f5f37365f73) // 9 + 9 + mstore(c, 0x36) // Store the length. + mstore(0x40, add(c, 0x60)) // Allocate memory. + } + } + + /// @dev Returns the initialization code hash of the PUSH0 clone of `implementation`. + function initCodeHash_PUSH0(address implementation) internal pure returns (bytes32 hash) { + /// @solidity memory-safe-assembly + assembly { + mstore(0x24, 0x5af43d5f5f3e6029573d5ffd5b3d5ff3) // 16 + mstore(0x14, implementation) // 20 + mstore(0x00, 0x602d5f8160095f39f35f5f365f5f37365f73) // 9 + 9 + hash := keccak256(0x0e, 0x36) + mstore(0x24, 0) // Restore the overwritten part of the free memory pointer. + } + } + + /// @dev Returns the address of the PUSH0 clone of `implementation`, with `salt` by `deployer`. + /// Note: The returned result has dirty upper 96 bits. Please clean if used in assembly. + function predictDeterministicAddress_PUSH0( + address implementation, + bytes32 salt, + address deployer + ) internal pure returns (address predicted) { + bytes32 hash = initCodeHash_PUSH0(implementation); + predicted = predictDeterministicAddress(hash, salt, deployer); + } + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* CLONES WITH IMMUTABLE ARGS OPERATIONS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev Deploys a clone of `implementation` with immutable arguments encoded in `args`. + function clone(address implementation, bytes memory args) internal returns (address instance) { + instance = clone(0, implementation, args); + } + + /// @dev Deploys a clone of `implementation` with immutable arguments encoded in `args`. + /// Deposits `value` ETH during deployment. + function clone(uint256 value, address implementation, bytes memory args) + internal + returns (address instance) + { + /// @solidity memory-safe-assembly + assembly { + /** + * ---------------------------------------------------------------------------+ + * CREATION (10 bytes) | + * ---------------------------------------------------------------------------| + * Opcode | Mnemonic | Stack | Memory | + * ---------------------------------------------------------------------------| + * 61 runSize | PUSH2 runSize | r | | + * 3d | RETURNDATASIZE | 0 r | | + * 81 | DUP2 | r 0 r | | + * 60 offset | PUSH1 offset | o r 0 r | | + * 3d | RETURNDATASIZE | 0 o r 0 r | | + * 39 | CODECOPY | 0 r | [0..runSize): runtime code | + * f3 | RETURN | | [0..runSize): runtime code | + * ---------------------------------------------------------------------------| + * RUNTIME (45 bytes + extraLength) | + * ---------------------------------------------------------------------------| + * Opcode | Mnemonic | Stack | Memory | + * ---------------------------------------------------------------------------| + * | + * ::: copy calldata to memory :::::::::::::::::::::::::::::::::::::::::::::: | + * 36 | CALLDATASIZE | cds | | + * 3d | RETURNDATASIZE | 0 cds | | + * 3d | RETURNDATASIZE | 0 0 cds | | + * 37 | CALLDATACOPY | | [0..cds): calldata | + * | + * ::: delegate call to the implementation contract ::::::::::::::::::::::::: | + * 3d | RETURNDATASIZE | 0 | [0..cds): calldata | + * 3d | RETURNDATASIZE | 0 0 | [0..cds): calldata | + * 3d | RETURNDATASIZE | 0 0 0 | [0..cds): calldata | + * 36 | CALLDATASIZE | cds 0 0 0 | [0..cds): calldata | + * 3d | RETURNDATASIZE | 0 cds 0 0 0 0 | [0..cds): calldata | + * 73 addr | PUSH20 addr | addr 0 cds 0 0 0 0 | [0..cds): calldata | + * 5a | GAS | gas addr 0 cds 0 0 0 0 | [0..cds): calldata | + * f4 | DELEGATECALL | success 0 0 | [0..cds): calldata | + * | + * ::: copy return data to memory ::::::::::::::::::::::::::::::::::::::::::: | + * 3d | RETURNDATASIZE | rds success 0 | [0..cds): calldata | + * 82 | DUP3 | 0 rds success 0 | [0..cds): calldata | + * 80 | DUP1 | 0 0 rds success 0 | [0..cds): calldata | + * 3e | RETURNDATACOPY | success 0 | [0..rds): returndata | + * 90 | SWAP1 | 0 success | [0..rds): returndata | + * 3d | RETURNDATASIZE | rds 0 success | [0..rds): returndata | + * 91 | SWAP2 | success 0 rds | [0..rds): returndata | + * | + * 60 0x2b | PUSH1 0x2b | 0x2b success 0 rds | [0..rds): returndata | + * 57 | JUMPI | 0 rds | [0..rds): returndata | + * | + * ::: revert ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: | + * fd | REVERT | | [0..rds): returndata | + * | + * ::: return ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: | + * 5b | JUMPDEST | 0 rds | [0..rds): returndata | + * f3 | RETURN | | [0..rds): returndata | + * ---------------------------------------------------------------------------+ + */ + let m := mload(0x40) + let n := mload(args) + pop(staticcall(gas(), 4, add(args, 0x20), n, add(m, 0x43), n)) + mstore(add(m, 0x23), 0x5af43d82803e903d91602b57fd5bf3) + mstore(add(m, 0x14), implementation) + mstore(m, add(0xfe61002d3d81600a3d39f3363d3d373d3d3d363d73, shl(136, n))) + // Do a out-of-gas revert if `n` is greater than `0xffff - 0x2d = 0xffd2`. + instance := create(value, add(m, add(0x0b, lt(n, 0xffd3))), add(n, 0x37)) + if iszero(instance) { + mstore(0x00, 0x30116425) // `DeploymentFailed()`. + revert(0x1c, 0x04) + } + } + } + + /// @dev Deploys a deterministic clone of `implementation` + /// with immutable arguments encoded in `args` and `salt`. + function cloneDeterministic(address implementation, bytes memory args, bytes32 salt) + internal + returns (address instance) + { + instance = cloneDeterministic(0, implementation, args, salt); + } + + /// @dev Deploys a deterministic clone of `implementation` + /// with immutable arguments encoded in `args` and `salt`. + function cloneDeterministic( + uint256 value, + address implementation, + bytes memory args, + bytes32 salt + ) internal returns (address instance) { + /// @solidity memory-safe-assembly + assembly { + let m := mload(0x40) + let n := mload(args) + pop(staticcall(gas(), 4, add(args, 0x20), n, add(m, 0x43), n)) + mstore(add(m, 0x23), 0x5af43d82803e903d91602b57fd5bf3) + mstore(add(m, 0x14), implementation) + mstore(m, add(0xfe61002d3d81600a3d39f3363d3d373d3d3d363d73, shl(136, n))) + // Do a out-of-gas revert if `n` is greater than `0xffff - 0x2d = 0xffd2`. + instance := create2(value, add(m, add(0x0b, lt(n, 0xffd3))), add(n, 0x37), salt) + if iszero(instance) { + mstore(0x00, 0x30116425) // `DeploymentFailed()`. + revert(0x1c, 0x04) + } + } + } + + /// @dev Deploys a deterministic clone of `implementation` + /// with immutable arguments encoded in `args` and `salt`. + /// This method does not revert if the clone has already been deployed. + function createDeterministicClone(address implementation, bytes memory args, bytes32 salt) + internal + returns (bool alreadyDeployed, address instance) + { + return createDeterministicClone(0, implementation, args, salt); + } + + /// @dev Deploys a deterministic clone of `implementation` + /// with immutable arguments encoded in `args` and `salt`. + /// This method does not revert if the clone has already been deployed. + function createDeterministicClone( + uint256 value, + address implementation, + bytes memory args, + bytes32 salt + ) internal returns (bool alreadyDeployed, address instance) { + /// @solidity memory-safe-assembly + assembly { + let m := mload(0x40) + let n := mload(args) + pop(staticcall(gas(), 4, add(args, 0x20), n, add(m, 0x43), n)) + mstore(add(m, 0x23), 0x5af43d82803e903d91602b57fd5bf3) + mstore(add(m, 0x14), implementation) + // Do a out-of-gas revert if `n` is greater than `0xffff - 0x2d = 0xffd2`. + // forgefmt: disable-next-item + mstore(add(m, gt(n, 0xffd2)), add(0xfe61002d3d81600a3d39f3363d3d373d3d3d363d73, shl(136, n))) + // Compute and store the bytecode hash. + mstore8(0x00, 0xff) // Write the prefix. + mstore(0x35, keccak256(add(m, 0x0c), add(n, 0x37))) + mstore(0x01, shl(96, address())) + mstore(0x15, salt) + instance := keccak256(0x00, 0x55) + for {} 1 {} { + if iszero(extcodesize(instance)) { + instance := create2(value, add(m, 0x0c), add(n, 0x37), salt) + if iszero(instance) { + mstore(0x00, 0x30116425) // `DeploymentFailed()`. + revert(0x1c, 0x04) + } + break + } + alreadyDeployed := 1 + if iszero(value) { break } + if iszero(call(gas(), instance, value, codesize(), 0x00, codesize(), 0x00)) { + mstore(0x00, 0xb12d13eb) // `ETHTransferFailed()`. + revert(0x1c, 0x04) + } + break + } + mstore(0x35, 0) // Restore the overwritten part of the free memory pointer. + } + } + + /// @dev Returns the initialization code hash of the clone of `implementation` + /// using immutable arguments encoded in `args`. + function initCode(address implementation, bytes memory args) + internal + pure + returns (bytes memory c) + { + /// @solidity memory-safe-assembly + assembly { + c := mload(0x40) + let n := mload(args) + // Do a out-of-gas revert if `n` is greater than `0xffff - 0x2d = 0xffd2`. + returndatacopy(returndatasize(), returndatasize(), gt(n, 0xffd2)) + for { let i := 0 } lt(i, n) { i := add(i, 0x20) } { + mstore(add(add(c, 0x57), i), mload(add(add(args, 0x20), i))) + } + mstore(add(c, 0x37), 0x5af43d82803e903d91602b57fd5bf3) + mstore(add(c, 0x28), implementation) + mstore(add(c, 0x14), add(0x61002d3d81600a3d39f3363d3d373d3d3d363d73, shl(136, n))) + mstore(c, add(0x37, n)) // Store the length. + mstore(add(c, add(n, 0x57)), 0) // Zeroize the slot after the bytes. + mstore(0x40, add(c, add(n, 0x77))) // Allocate memory. + } + } + + /// @dev Returns the initialization code hash of the clone of `implementation` + /// using immutable arguments encoded in `args`. + function initCodeHash(address implementation, bytes memory args) + internal + pure + returns (bytes32 hash) + { + /// @solidity memory-safe-assembly + assembly { + let m := mload(0x40) + let n := mload(args) + // Do a out-of-gas revert if `n` is greater than `0xffff - 0x2d = 0xffd2`. + returndatacopy(returndatasize(), returndatasize(), gt(n, 0xffd2)) + for { let i := 0 } lt(i, n) { i := add(i, 0x20) } { + mstore(add(add(m, 0x43), i), mload(add(add(args, 0x20), i))) + } + mstore(add(m, 0x23), 0x5af43d82803e903d91602b57fd5bf3) + mstore(add(m, 0x14), implementation) + mstore(m, add(0x61002d3d81600a3d39f3363d3d373d3d3d363d73, shl(136, n))) + hash := keccak256(add(m, 0x0c), add(n, 0x37)) + } + } + + /// @dev Returns the address of the clone of + /// `implementation` using immutable arguments encoded in `args`, with `salt`, by `deployer`. + /// Note: The returned result has dirty upper 96 bits. Please clean if used in assembly. + function predictDeterministicAddress( + address implementation, + bytes memory data, + bytes32 salt, + address deployer + ) internal pure returns (address predicted) { + bytes32 hash = initCodeHash(implementation, data); + predicted = predictDeterministicAddress(hash, salt, deployer); + } + + /// @dev Equivalent to `argsOnClone(instance, 0, 2 ** 256 - 1)`. + function argsOnClone(address instance) internal view returns (bytes memory args) { + /// @solidity memory-safe-assembly + assembly { + args := mload(0x40) + mstore(args, and(0xffffffffff, sub(extcodesize(instance), 0x2d))) // Store the length. + extcodecopy(instance, add(args, 0x20), 0x2d, add(mload(args), 0x20)) + mstore(0x40, add(mload(args), add(args, 0x40))) // Allocate memory. + } + } + + /// @dev Equivalent to `argsOnClone(instance, start, 2 ** 256 - 1)`. + function argsOnClone(address instance, uint256 start) + internal + view + returns (bytes memory args) + { + /// @solidity memory-safe-assembly + assembly { + args := mload(0x40) + let n := and(0xffffffffff, sub(extcodesize(instance), 0x2d)) + extcodecopy(instance, add(args, 0x20), add(start, 0x2d), add(n, 0x20)) + mstore(args, mul(sub(n, start), lt(start, n))) // Store the length. + mstore(0x40, add(args, add(0x40, mload(args)))) // Allocate memory. + } + } + + /// @dev Returns a slice of the immutable arguments on `instance` from `start` to `end`. + /// `start` and `end` will be clamped to the range `[0, args.length]`. + /// The `instance` MUST be deployed via the clone with immutable args functions. + /// Otherwise, the behavior is undefined. + /// Out-of-gas reverts if `instance` does not have any code. + function argsOnClone(address instance, uint256 start, uint256 end) + internal + view + returns (bytes memory args) + { + /// @solidity memory-safe-assembly + assembly { + args := mload(0x40) + if iszero(lt(end, 0xffff)) { end := 0xffff } + let d := mul(sub(end, start), lt(start, end)) + extcodecopy(instance, args, add(start, 0x0d), add(d, 0x20)) + if iszero(and(0xff, mload(add(args, d)))) { + let n := sub(extcodesize(instance), 0x2d) + returndatacopy(returndatasize(), returndatasize(), shr(40, n)) + d := mul(gt(n, start), sub(d, mul(gt(end, n), sub(end, n)))) + } + mstore(args, d) // Store the length. + mstore(add(add(args, 0x20), d), 0) // Zeroize the slot after the bytes. + mstore(0x40, add(add(args, 0x40), d)) // Allocate memory. + } + } + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* MINIMAL ERC1967 PROXY OPERATIONS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + // Note: The ERC1967 proxy here is intended to be upgraded with UUPS. + // This is NOT the same as ERC1967Factory's transparent proxy, which includes admin logic. + + /// @dev Deploys a minimal ERC1967 proxy with `implementation`. + function deployERC1967(address implementation) internal returns (address instance) { + instance = deployERC1967(0, implementation); + } + + /// @dev Deploys a minimal ERC1967 proxy with `implementation`. + /// Deposits `value` ETH during deployment. + function deployERC1967(uint256 value, address implementation) + internal + returns (address instance) + { + /// @solidity memory-safe-assembly + assembly { + /** + * ---------------------------------------------------------------------------------+ + * CREATION (34 bytes) | + * ---------------------------------------------------------------------------------| + * Opcode | Mnemonic | Stack | Memory | + * ---------------------------------------------------------------------------------| + * 60 runSize | PUSH1 runSize | r | | + * 3d | RETURNDATASIZE | 0 r | | + * 81 | DUP2 | r 0 r | | + * 60 offset | PUSH1 offset | o r 0 r | | + * 3d | RETURNDATASIZE | 0 o r 0 r | | + * 39 | CODECOPY | 0 r | [0..runSize): runtime code | + * 73 impl | PUSH20 impl | impl 0 r | [0..runSize): runtime code | + * 60 slotPos | PUSH1 slotPos | slotPos impl 0 r | [0..runSize): runtime code | + * 51 | MLOAD | slot impl 0 r | [0..runSize): runtime code | + * 55 | SSTORE | 0 r | [0..runSize): runtime code | + * f3 | RETURN | | [0..runSize): runtime code | + * ---------------------------------------------------------------------------------| + * RUNTIME (61 bytes) | + * ---------------------------------------------------------------------------------| + * Opcode | Mnemonic | Stack | Memory | + * ---------------------------------------------------------------------------------| + * | + * ::: copy calldata to memory :::::::::::::::::::::::::::::::::::::::::::::::::::: | + * 36 | CALLDATASIZE | cds | | + * 3d | RETURNDATASIZE | 0 cds | | + * 3d | RETURNDATASIZE | 0 0 cds | | + * 37 | CALLDATACOPY | | [0..calldatasize): calldata | + * | + * ::: delegatecall to implementation ::::::::::::::::::::::::::::::::::::::::::::: | + * 3d | RETURNDATASIZE | 0 | | + * 3d | RETURNDATASIZE | 0 0 | | + * 36 | CALLDATASIZE | cds 0 0 | [0..calldatasize): calldata | + * 3d | RETURNDATASIZE | 0 cds 0 0 | [0..calldatasize): calldata | + * 7f slot | PUSH32 slot | s 0 cds 0 0 | [0..calldatasize): calldata | + * 54 | SLOAD | i 0 cds 0 0 | [0..calldatasize): calldata | + * 5a | GAS | g i 0 cds 0 0 | [0..calldatasize): calldata | + * f4 | DELEGATECALL | succ | [0..calldatasize): calldata | + * | + * ::: copy returndata to memory :::::::::::::::::::::::::::::::::::::::::::::::::: | + * 3d | RETURNDATASIZE | rds succ | [0..calldatasize): calldata | + * 60 0x00 | PUSH1 0x00 | 0 rds succ | [0..calldatasize): calldata | + * 80 | DUP1 | 0 0 rds succ | [0..calldatasize): calldata | + * 3e | RETURNDATACOPY | succ | [0..returndatasize): returndata | + * | + * ::: branch on delegatecall status :::::::::::::::::::::::::::::::::::::::::::::: | + * 60 0x38 | PUSH1 0x38 | dest succ | [0..returndatasize): returndata | + * 57 | JUMPI | | [0..returndatasize): returndata | + * | + * ::: delegatecall failed, revert :::::::::::::::::::::::::::::::::::::::::::::::: | + * 3d | RETURNDATASIZE | rds | [0..returndatasize): returndata | + * 60 0x00 | PUSH1 0x00 | 0 rds | [0..returndatasize): returndata | + * fd | REVERT | | [0..returndatasize): returndata | + * | + * ::: delegatecall succeeded, return ::::::::::::::::::::::::::::::::::::::::::::: | + * 5b | JUMPDEST | | [0..returndatasize): returndata | + * 3d | RETURNDATASIZE | rds | [0..returndatasize): returndata | + * 60 0x00 | PUSH1 0x00 | 0 rds | [0..returndatasize): returndata | + * f3 | RETURN | | [0..returndatasize): returndata | + * ---------------------------------------------------------------------------------+ + */ + let m := mload(0x40) // Cache the free memory pointer. + mstore(0x60, 0xcc3735a920a3ca505d382bbc545af43d6000803e6038573d6000fd5b3d6000f3) + mstore(0x40, 0x5155f3363d3d373d3d363d7f360894a13ba1a3210667c828492db98dca3e2076) + mstore(0x20, 0x6009) + mstore(0x1e, implementation) + mstore(0x0a, 0x603d3d8160223d3973) + instance := create(value, 0x21, 0x5f) + if iszero(instance) { + mstore(0x00, 0x30116425) // `DeploymentFailed()`. + revert(0x1c, 0x04) + } + mstore(0x40, m) // Restore the free memory pointer. + mstore(0x60, 0) // Restore the zero slot. + } + } + + /// @dev Deploys a deterministic minimal ERC1967 proxy with `implementation` and `salt`. + function deployDeterministicERC1967(address implementation, bytes32 salt) + internal + returns (address instance) + { + instance = deployDeterministicERC1967(0, implementation, salt); + } + + /// @dev Deploys a deterministic minimal ERC1967 proxy with `implementation` and `salt`. + /// Deposits `value` ETH during deployment. + function deployDeterministicERC1967(uint256 value, address implementation, bytes32 salt) + internal + returns (address instance) + { + /// @solidity memory-safe-assembly + assembly { + let m := mload(0x40) // Cache the free memory pointer. + mstore(0x60, 0xcc3735a920a3ca505d382bbc545af43d6000803e6038573d6000fd5b3d6000f3) + mstore(0x40, 0x5155f3363d3d373d3d363d7f360894a13ba1a3210667c828492db98dca3e2076) + mstore(0x20, 0x6009) + mstore(0x1e, implementation) + mstore(0x0a, 0x603d3d8160223d3973) + instance := create2(value, 0x21, 0x5f, salt) + if iszero(instance) { + mstore(0x00, 0x30116425) // `DeploymentFailed()`. + revert(0x1c, 0x04) + } + mstore(0x40, m) // Restore the free memory pointer. + mstore(0x60, 0) // Restore the zero slot. + } + } + + /// @dev Creates a deterministic minimal ERC1967 proxy with `implementation` and `salt`. + /// Note: This method is intended for use in ERC4337 factories, + /// which are expected to NOT revert if the proxy is already deployed. + function createDeterministicERC1967(address implementation, bytes32 salt) + internal + returns (bool alreadyDeployed, address instance) + { + return createDeterministicERC1967(0, implementation, salt); + } + + /// @dev Creates a deterministic minimal ERC1967 proxy with `implementation` and `salt`. + /// Deposits `value` ETH during deployment. + /// Note: This method is intended for use in ERC4337 factories, + /// which are expected to NOT revert if the proxy is already deployed. + function createDeterministicERC1967(uint256 value, address implementation, bytes32 salt) + internal + returns (bool alreadyDeployed, address instance) + { + /// @solidity memory-safe-assembly + assembly { + let m := mload(0x40) // Cache the free memory pointer. + mstore(0x60, 0xcc3735a920a3ca505d382bbc545af43d6000803e6038573d6000fd5b3d6000f3) + mstore(0x40, 0x5155f3363d3d373d3d363d7f360894a13ba1a3210667c828492db98dca3e2076) + mstore(0x20, 0x6009) + mstore(0x1e, implementation) + mstore(0x0a, 0x603d3d8160223d3973) + // Compute and store the bytecode hash. + mstore(add(m, 0x35), keccak256(0x21, 0x5f)) + mstore(m, shl(88, address())) + mstore8(m, 0xff) // Write the prefix. + mstore(add(m, 0x15), salt) + instance := keccak256(m, 0x55) + for {} 1 {} { + if iszero(extcodesize(instance)) { + instance := create2(value, 0x21, 0x5f, salt) + if iszero(instance) { + mstore(0x00, 0x30116425) // `DeploymentFailed()`. + revert(0x1c, 0x04) + } + break + } + alreadyDeployed := 1 + if iszero(value) { break } + if iszero(call(gas(), instance, value, codesize(), 0x00, codesize(), 0x00)) { + mstore(0x00, 0xb12d13eb) // `ETHTransferFailed()`. + revert(0x1c, 0x04) + } + break + } + mstore(0x40, m) // Restore the free memory pointer. + mstore(0x60, 0) // Restore the zero slot. + } + } + + /// @dev Returns the initialization code of the minimal ERC1967 proxy of `implementation`. + function initCodeERC1967(address implementation) internal pure returns (bytes memory c) { + /// @solidity memory-safe-assembly + assembly { + c := mload(0x40) + mstore(add(c, 0x60), 0x3735a920a3ca505d382bbc545af43d6000803e6038573d6000fd5b3d6000f300) + mstore(add(c, 0x40), 0x55f3363d3d373d3d363d7f360894a13ba1a3210667c828492db98dca3e2076cc) + mstore(add(c, 0x20), or(shl(24, implementation), 0x600951)) + mstore(add(c, 0x09), 0x603d3d8160223d3973) + mstore(c, 0x5f) // Store the length. + mstore(0x40, add(c, 0x80)) // Allocate memory. + } + } + + /// @dev Returns the initialization code hash of the minimal ERC1967 proxy of `implementation`. + function initCodeHashERC1967(address implementation) internal pure returns (bytes32 hash) { + /// @solidity memory-safe-assembly + assembly { + let m := mload(0x40) // Cache the free memory pointer. + mstore(0x60, 0xcc3735a920a3ca505d382bbc545af43d6000803e6038573d6000fd5b3d6000f3) + mstore(0x40, 0x5155f3363d3d373d3d363d7f360894a13ba1a3210667c828492db98dca3e2076) + mstore(0x20, 0x6009) + mstore(0x1e, implementation) + mstore(0x0a, 0x603d3d8160223d3973) + hash := keccak256(0x21, 0x5f) + mstore(0x40, m) // Restore the free memory pointer. + mstore(0x60, 0) // Restore the zero slot. + } + } + + /// @dev Returns the address of the ERC1967 proxy of `implementation`, with `salt` by `deployer`. + /// Note: The returned result has dirty upper 96 bits. Please clean if used in assembly. + function predictDeterministicAddressERC1967( + address implementation, + bytes32 salt, + address deployer + ) internal pure returns (address predicted) { + bytes32 hash = initCodeHashERC1967(implementation); + predicted = predictDeterministicAddress(hash, salt, deployer); + } + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* MINIMAL ERC1967 PROXY WITH IMMUTABLE ARGS OPERATIONS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev Deploys a minimal ERC1967 proxy with `implementation` and `args`. + function deployERC1967(address implementation, bytes memory args) + internal + returns (address instance) + { + instance = deployERC1967(0, implementation, args); + } + + /// @dev Deploys a minimal ERC1967 proxy with `implementation` and `args`. + /// Deposits `value` ETH during deployment. + function deployERC1967(uint256 value, address implementation, bytes memory args) + internal + returns (address instance) + { + /// @solidity memory-safe-assembly + assembly { + let m := mload(0x40) + let n := mload(args) + pop(staticcall(gas(), 4, add(args, 0x20), n, add(m, 0x60), n)) + mstore(add(m, 0x40), 0xcc3735a920a3ca505d382bbc545af43d6000803e6038573d6000fd5b3d6000f3) + mstore(add(m, 0x20), 0x5155f3363d3d373d3d363d7f360894a13ba1a3210667c828492db98dca3e2076) + mstore(0x16, 0x6009) + mstore(0x14, implementation) + // Do a out-of-gas revert if `n` is greater than `0xffff - 0x3d = 0xffc2`. + mstore(gt(n, 0xffc2), add(0xfe61003d3d8160233d3973, shl(56, n))) + mstore(m, mload(0x16)) + instance := create(value, m, add(n, 0x60)) + if iszero(instance) { + mstore(0x00, 0x30116425) // `DeploymentFailed()`. + revert(0x1c, 0x04) + } + } + } + + /// @dev Deploys a deterministic minimal ERC1967 proxy with `implementation`, `args` and `salt`. + function deployDeterministicERC1967(address implementation, bytes memory args, bytes32 salt) + internal + returns (address instance) + { + instance = deployDeterministicERC1967(0, implementation, args, salt); + } + + /// @dev Deploys a deterministic minimal ERC1967 proxy with `implementation`, `args` and `salt`. + /// Deposits `value` ETH during deployment. + function deployDeterministicERC1967( + uint256 value, + address implementation, + bytes memory args, + bytes32 salt + ) internal returns (address instance) { + /// @solidity memory-safe-assembly + assembly { + let m := mload(0x40) + let n := mload(args) + pop(staticcall(gas(), 4, add(args, 0x20), n, add(m, 0x60), n)) + mstore(add(m, 0x40), 0xcc3735a920a3ca505d382bbc545af43d6000803e6038573d6000fd5b3d6000f3) + mstore(add(m, 0x20), 0x5155f3363d3d373d3d363d7f360894a13ba1a3210667c828492db98dca3e2076) + mstore(0x16, 0x6009) + mstore(0x14, implementation) + // Do a out-of-gas revert if `n` is greater than `0xffff - 0x3d = 0xffc2`. + mstore(gt(n, 0xffc2), add(0xfe61003d3d8160233d3973, shl(56, n))) + mstore(m, mload(0x16)) + instance := create2(value, m, add(n, 0x60), salt) + if iszero(instance) { + mstore(0x00, 0x30116425) // `DeploymentFailed()`. + revert(0x1c, 0x04) + } + } + } + + /// @dev Creates a deterministic minimal ERC1967 proxy with `implementation`, `args` and `salt`. + /// Note: This method is intended for use in ERC4337 factories, + /// which are expected to NOT revert if the proxy is already deployed. + function createDeterministicERC1967(address implementation, bytes memory args, bytes32 salt) + internal + returns (bool alreadyDeployed, address instance) + { + return createDeterministicERC1967(0, implementation, args, salt); + } + + /// @dev Creates a deterministic minimal ERC1967 proxy with `implementation`, `args` and `salt`. + /// Deposits `value` ETH during deployment. + /// Note: This method is intended for use in ERC4337 factories, + /// which are expected to NOT revert if the proxy is already deployed. + function createDeterministicERC1967( + uint256 value, + address implementation, + bytes memory args, + bytes32 salt + ) internal returns (bool alreadyDeployed, address instance) { + /// @solidity memory-safe-assembly + assembly { + let m := mload(0x40) + let n := mload(args) + pop(staticcall(gas(), 4, add(args, 0x20), n, add(m, 0x60), n)) + mstore(add(m, 0x40), 0xcc3735a920a3ca505d382bbc545af43d6000803e6038573d6000fd5b3d6000f3) + mstore(add(m, 0x20), 0x5155f3363d3d373d3d363d7f360894a13ba1a3210667c828492db98dca3e2076) + mstore(0x16, 0x6009) + mstore(0x14, implementation) + // Do a out-of-gas revert if `n` is greater than `0xffff - 0x3d = 0xffc2`. + mstore(gt(n, 0xffc2), add(0xfe61003d3d8160233d3973, shl(56, n))) + mstore(m, mload(0x16)) + // Compute and store the bytecode hash. + mstore8(0x00, 0xff) // Write the prefix. + mstore(0x35, keccak256(m, add(n, 0x60))) + mstore(0x01, shl(96, address())) + mstore(0x15, salt) + instance := keccak256(0x00, 0x55) + for {} 1 {} { + if iszero(extcodesize(instance)) { + instance := create2(value, m, add(n, 0x60), salt) + if iszero(instance) { + mstore(0x00, 0x30116425) // `DeploymentFailed()`. + revert(0x1c, 0x04) + } + break + } + alreadyDeployed := 1 + if iszero(value) { break } + if iszero(call(gas(), instance, value, codesize(), 0x00, codesize(), 0x00)) { + mstore(0x00, 0xb12d13eb) // `ETHTransferFailed()`. + revert(0x1c, 0x04) + } + break + } + mstore(0x35, 0) // Restore the overwritten part of the free memory pointer. + } + } + + /// @dev Returns the initialization code of the minimal ERC1967 proxy of `implementation` and `args`. + function initCodeERC1967(address implementation, bytes memory args) + internal + pure + returns (bytes memory c) + { + /// @solidity memory-safe-assembly + assembly { + c := mload(0x40) + let n := mload(args) + // Do a out-of-gas revert if `n` is greater than `0xffff - 0x3d = 0xffc2`. + returndatacopy(returndatasize(), returndatasize(), gt(n, 0xffc2)) + for { let i := 0 } lt(i, n) { i := add(i, 0x20) } { + mstore(add(add(c, 0x80), i), mload(add(add(args, 0x20), i))) + } + mstore(add(c, 0x60), 0xcc3735a920a3ca505d382bbc545af43d6000803e6038573d6000fd5b3d6000f3) + mstore(add(c, 0x40), 0x5155f3363d3d373d3d363d7f360894a13ba1a3210667c828492db98dca3e2076) + mstore(add(c, 0x20), 0x6009) + mstore(add(c, 0x1e), implementation) + mstore(add(c, 0x0a), add(0x61003d3d8160233d3973, shl(56, n))) + mstore(c, add(n, 0x60)) // Store the length. + mstore(add(c, add(n, 0x80)), 0) // Zeroize the slot after the bytes. + mstore(0x40, add(c, add(n, 0xa0))) // Allocate memory. + } + } + + /// @dev Returns the initialization code hash of the minimal ERC1967 proxy of `implementation` and `args`. + function initCodeHashERC1967(address implementation, bytes memory args) + internal + pure + returns (bytes32 hash) + { + /// @solidity memory-safe-assembly + assembly { + let m := mload(0x40) + let n := mload(args) + // Do a out-of-gas revert if `n` is greater than `0xffff - 0x3d = 0xffc2`. + returndatacopy(returndatasize(), returndatasize(), gt(n, 0xffc2)) + for { let i := 0 } lt(i, n) { i := add(i, 0x20) } { + mstore(add(add(m, 0x60), i), mload(add(add(args, 0x20), i))) + } + mstore(add(m, 0x40), 0xcc3735a920a3ca505d382bbc545af43d6000803e6038573d6000fd5b3d6000f3) + mstore(add(m, 0x20), 0x5155f3363d3d373d3d363d7f360894a13ba1a3210667c828492db98dca3e2076) + mstore(0x16, 0x6009) + mstore(0x14, implementation) + mstore(0x00, add(0x61003d3d8160233d3973, shl(56, n))) + mstore(m, mload(0x16)) + hash := keccak256(m, add(n, 0x60)) + } + } + + /// @dev Returns the address of the ERC1967 proxy of `implementation`, `args`, with `salt` by `deployer`. + /// Note: The returned result has dirty upper 96 bits. Please clean if used in assembly. + function predictDeterministicAddressERC1967( + address implementation, + bytes memory args, + bytes32 salt, + address deployer + ) internal pure returns (address predicted) { + bytes32 hash = initCodeHashERC1967(implementation, args); + predicted = predictDeterministicAddress(hash, salt, deployer); + } + + /// @dev Equivalent to `argsOnERC1967(instance, start, 2 ** 256 - 1)`. + function argsOnERC1967(address instance) internal view returns (bytes memory args) { + /// @solidity memory-safe-assembly + assembly { + args := mload(0x40) + mstore(args, and(0xffffffffff, sub(extcodesize(instance), 0x3d))) // Store the length. + extcodecopy(instance, add(args, 0x20), 0x3d, add(mload(args), 0x20)) + mstore(0x40, add(mload(args), add(args, 0x40))) // Allocate memory. + } + } + + /// @dev Equivalent to `argsOnERC1967(instance, start, 2 ** 256 - 1)`. + function argsOnERC1967(address instance, uint256 start) + internal + view + returns (bytes memory args) + { + /// @solidity memory-safe-assembly + assembly { + args := mload(0x40) + let n := and(0xffffffffff, sub(extcodesize(instance), 0x3d)) + extcodecopy(instance, add(args, 0x20), add(start, 0x3d), add(n, 0x20)) + mstore(args, mul(sub(n, start), lt(start, n))) // Store the length. + mstore(0x40, add(args, add(0x40, mload(args)))) // Allocate memory. + } + } + + /// @dev Returns a slice of the immutable arguments on `instance` from `start` to `end`. + /// `start` and `end` will be clamped to the range `[0, args.length]`. + /// The `instance` MUST be deployed via the ERC1967 with immutable args functions. + /// Otherwise, the behavior is undefined. + /// Out-of-gas reverts if `instance` does not have any code. + function argsOnERC1967(address instance, uint256 start, uint256 end) + internal + view + returns (bytes memory args) + { + /// @solidity memory-safe-assembly + assembly { + args := mload(0x40) + if iszero(lt(end, 0xffff)) { end := 0xffff } + let d := mul(sub(end, start), lt(start, end)) + extcodecopy(instance, args, add(start, 0x1d), add(d, 0x20)) + if iszero(and(0xff, mload(add(args, d)))) { + let n := sub(extcodesize(instance), 0x3d) + returndatacopy(returndatasize(), returndatasize(), shr(40, n)) + d := mul(gt(n, start), sub(d, mul(gt(end, n), sub(end, n)))) + } + mstore(args, d) // Store the length. + mstore(add(add(args, 0x20), d), 0) // Zeroize the slot after the bytes. + mstore(0x40, add(add(args, 0x40), d)) // Allocate memory. + } + } + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* ERC1967I PROXY OPERATIONS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + // Note: This proxy has a special code path that activates if `calldatasize() == 1`. + // This code path skips the delegatecall and directly returns the `implementation` address. + // The returned implementation is guaranteed to be valid if the keccak256 of the + // proxy's code is equal to `ERC1967I_CODE_HASH`. + + /// @dev Deploys a ERC1967I proxy with `implementation`. + function deployERC1967I(address implementation) internal returns (address instance) { + instance = deployERC1967I(0, implementation); + } + + /// @dev Deploys a ERC1967I proxy with `implementation`. + /// Deposits `value` ETH during deployment. + function deployERC1967I(uint256 value, address implementation) + internal + returns (address instance) + { + /// @solidity memory-safe-assembly + assembly { + /** + * ---------------------------------------------------------------------------------+ + * CREATION (34 bytes) | + * ---------------------------------------------------------------------------------| + * Opcode | Mnemonic | Stack | Memory | + * ---------------------------------------------------------------------------------| + * 60 runSize | PUSH1 runSize | r | | + * 3d | RETURNDATASIZE | 0 r | | + * 81 | DUP2 | r 0 r | | + * 60 offset | PUSH1 offset | o r 0 r | | + * 3d | RETURNDATASIZE | 0 o r 0 r | | + * 39 | CODECOPY | 0 r | [0..runSize): runtime code | + * 73 impl | PUSH20 impl | impl 0 r | [0..runSize): runtime code | + * 60 slotPos | PUSH1 slotPos | slotPos impl 0 r | [0..runSize): runtime code | + * 51 | MLOAD | slot impl 0 r | [0..runSize): runtime code | + * 55 | SSTORE | 0 r | [0..runSize): runtime code | + * f3 | RETURN | | [0..runSize): runtime code | + * ---------------------------------------------------------------------------------| + * RUNTIME (82 bytes) | + * ---------------------------------------------------------------------------------| + * Opcode | Mnemonic | Stack | Memory | + * ---------------------------------------------------------------------------------| + * | + * ::: check calldatasize ::::::::::::::::::::::::::::::::::::::::::::::::::::::::: | + * 36 | CALLDATASIZE | cds | | + * 58 | PC | 1 cds | | + * 14 | EQ | eqs | | + * 60 0x43 | PUSH1 0x43 | dest eqs | | + * 57 | JUMPI | | | + * | + * ::: copy calldata to memory :::::::::::::::::::::::::::::::::::::::::::::::::::: | + * 36 | CALLDATASIZE | cds | | + * 3d | RETURNDATASIZE | 0 cds | | + * 3d | RETURNDATASIZE | 0 0 cds | | + * 37 | CALLDATACOPY | | [0..calldatasize): calldata | + * | + * ::: delegatecall to implementation ::::::::::::::::::::::::::::::::::::::::::::: | + * 3d | RETURNDATASIZE | 0 | | + * 3d | RETURNDATASIZE | 0 0 | | + * 36 | CALLDATASIZE | cds 0 0 | [0..calldatasize): calldata | + * 3d | RETURNDATASIZE | 0 cds 0 0 | [0..calldatasize): calldata | + * 7f slot | PUSH32 slot | s 0 cds 0 0 | [0..calldatasize): calldata | + * 54 | SLOAD | i 0 cds 0 0 | [0..calldatasize): calldata | + * 5a | GAS | g i 0 cds 0 0 | [0..calldatasize): calldata | + * f4 | DELEGATECALL | succ | [0..calldatasize): calldata | + * | + * ::: copy returndata to memory :::::::::::::::::::::::::::::::::::::::::::::::::: | + * 3d | RETURNDATASIZE | rds succ | [0..calldatasize): calldata | + * 60 0x00 | PUSH1 0x00 | 0 rds succ | [0..calldatasize): calldata | + * 80 | DUP1 | 0 0 rds succ | [0..calldatasize): calldata | + * 3e | RETURNDATACOPY | succ | [0..returndatasize): returndata | + * | + * ::: branch on delegatecall status :::::::::::::::::::::::::::::::::::::::::::::: | + * 60 0x3E | PUSH1 0x3E | dest succ | [0..returndatasize): returndata | + * 57 | JUMPI | | [0..returndatasize): returndata | + * | + * ::: delegatecall failed, revert :::::::::::::::::::::::::::::::::::::::::::::::: | + * 3d | RETURNDATASIZE | rds | [0..returndatasize): returndata | + * 60 0x00 | PUSH1 0x00 | 0 rds | [0..returndatasize): returndata | + * fd | REVERT | | [0..returndatasize): returndata | + * | + * ::: delegatecall succeeded, return ::::::::::::::::::::::::::::::::::::::::::::: | + * 5b | JUMPDEST | | [0..returndatasize): returndata | + * 3d | RETURNDATASIZE | rds | [0..returndatasize): returndata | + * 60 0x00 | PUSH1 0x00 | 0 rds | [0..returndatasize): returndata | + * f3 | RETURN | | [0..returndatasize): returndata | + * | + * ::: implementation , return :::::::::::::::::::::::::::::::::::::::::::::::::::: | + * 5b | JUMPDEST | | | + * 60 0x20 | PUSH1 0x20 | 32 | | + * 60 0x0F | PUSH1 0x0F | o 32 | | + * 3d | RETURNDATASIZE | 0 o 32 | | + * 39 | CODECOPY | | [0..32): implementation slot | + * 3d | RETURNDATASIZE | 0 | [0..32): implementation slot | + * 51 | MLOAD | slot | [0..32): implementation slot | + * 54 | SLOAD | impl | [0..32): implementation slot | + * 3d | RETURNDATASIZE | 0 impl | [0..32): implementation slot | + * 52 | MSTORE | | [0..32): implementation address | + * 59 | MSIZE | 32 | [0..32): implementation address | + * 3d | RETURNDATASIZE | 0 32 | [0..32): implementation address | + * f3 | RETURN | | [0..32): implementation address | + * ---------------------------------------------------------------------------------+ + */ + let m := mload(0x40) // Cache the free memory pointer. + mstore(0x60, 0x3d6000803e603e573d6000fd5b3d6000f35b6020600f3d393d51543d52593df3) + mstore(0x40, 0xa13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc545af4) + mstore(0x20, 0x600f5155f3365814604357363d3d373d3d363d7f360894) + mstore(0x09, or(shl(160, 0x60523d8160223d3973), shr(96, shl(96, implementation)))) + instance := create(value, 0x0c, 0x74) + if iszero(instance) { + mstore(0x00, 0x30116425) // `DeploymentFailed()`. + revert(0x1c, 0x04) + } + mstore(0x40, m) // Restore the free memory pointer. + mstore(0x60, 0) // Restore the zero slot. + } + } + + /// @dev Deploys a deterministic ERC1967I proxy with `implementation` and `salt`. + function deployDeterministicERC1967I(address implementation, bytes32 salt) + internal + returns (address instance) + { + instance = deployDeterministicERC1967I(0, implementation, salt); + } + + /// @dev Deploys a deterministic ERC1967I proxy with `implementation` and `salt`. + /// Deposits `value` ETH during deployment. + function deployDeterministicERC1967I(uint256 value, address implementation, bytes32 salt) + internal + returns (address instance) + { + /// @solidity memory-safe-assembly + assembly { + let m := mload(0x40) // Cache the free memory pointer. + mstore(0x60, 0x3d6000803e603e573d6000fd5b3d6000f35b6020600f3d393d51543d52593df3) + mstore(0x40, 0xa13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc545af4) + mstore(0x20, 0x600f5155f3365814604357363d3d373d3d363d7f360894) + mstore(0x09, or(shl(160, 0x60523d8160223d3973), shr(96, shl(96, implementation)))) + instance := create2(value, 0x0c, 0x74, salt) + if iszero(instance) { + mstore(0x00, 0x30116425) // `DeploymentFailed()`. + revert(0x1c, 0x04) + } + mstore(0x40, m) // Restore the free memory pointer. + mstore(0x60, 0) // Restore the zero slot. + } + } + + /// @dev Creates a deterministic ERC1967I proxy with `implementation` and `salt`. + /// Note: This method is intended for use in ERC4337 factories, + /// which are expected to NOT revert if the proxy is already deployed. + function createDeterministicERC1967I(address implementation, bytes32 salt) + internal + returns (bool alreadyDeployed, address instance) + { + return createDeterministicERC1967I(0, implementation, salt); + } + + /// @dev Creates a deterministic ERC1967I proxy with `implementation` and `salt`. + /// Deposits `value` ETH during deployment. + /// Note: This method is intended for use in ERC4337 factories, + /// which are expected to NOT revert if the proxy is already deployed. + function createDeterministicERC1967I(uint256 value, address implementation, bytes32 salt) + internal + returns (bool alreadyDeployed, address instance) + { + /// @solidity memory-safe-assembly + assembly { + let m := mload(0x40) // Cache the free memory pointer. + mstore(0x60, 0x3d6000803e603e573d6000fd5b3d6000f35b6020600f3d393d51543d52593df3) + mstore(0x40, 0xa13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc545af4) + mstore(0x20, 0x600f5155f3365814604357363d3d373d3d363d7f360894) + mstore(0x09, or(shl(160, 0x60523d8160223d3973), shr(96, shl(96, implementation)))) + // Compute and store the bytecode hash. + mstore(add(m, 0x35), keccak256(0x0c, 0x74)) + mstore(m, shl(88, address())) + mstore8(m, 0xff) // Write the prefix. + mstore(add(m, 0x15), salt) + instance := keccak256(m, 0x55) + for {} 1 {} { + if iszero(extcodesize(instance)) { + instance := create2(value, 0x0c, 0x74, salt) + if iszero(instance) { + mstore(0x00, 0x30116425) // `DeploymentFailed()`. + revert(0x1c, 0x04) + } + break + } + alreadyDeployed := 1 + if iszero(value) { break } + if iszero(call(gas(), instance, value, codesize(), 0x00, codesize(), 0x00)) { + mstore(0x00, 0xb12d13eb) // `ETHTransferFailed()`. + revert(0x1c, 0x04) + } + break + } + mstore(0x40, m) // Restore the free memory pointer. + mstore(0x60, 0) // Restore the zero slot. + } + } + + /// @dev Returns the initialization code of the ERC1967I proxy of `implementation`. + function initCodeERC1967I(address implementation) internal pure returns (bytes memory c) { + /// @solidity memory-safe-assembly + assembly { + c := mload(0x40) + mstore(add(c, 0x74), 0x3d6000803e603e573d6000fd5b3d6000f35b6020600f3d393d51543d52593df3) + mstore(add(c, 0x54), 0xa13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc545af4) + mstore(add(c, 0x34), 0x600f5155f3365814604357363d3d373d3d363d7f360894) + mstore(add(c, 0x1d), implementation) + mstore(add(c, 0x09), 0x60523d8160223d3973) + mstore(add(c, 0x94), 0) + mstore(c, 0x74) // Store the length. + mstore(0x40, add(c, 0xa0)) // Allocate memory. + } + } + + /// @dev Returns the initialization code hash of the ERC1967I proxy of `implementation`. + function initCodeHashERC1967I(address implementation) internal pure returns (bytes32 hash) { + /// @solidity memory-safe-assembly + assembly { + let m := mload(0x40) // Cache the free memory pointer. + mstore(0x60, 0x3d6000803e603e573d6000fd5b3d6000f35b6020600f3d393d51543d52593df3) + mstore(0x40, 0xa13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc545af4) + mstore(0x20, 0x600f5155f3365814604357363d3d373d3d363d7f360894) + mstore(0x09, or(shl(160, 0x60523d8160223d3973), shr(96, shl(96, implementation)))) + hash := keccak256(0x0c, 0x74) + mstore(0x40, m) // Restore the free memory pointer. + mstore(0x60, 0) // Restore the zero slot. + } + } + + /// @dev Returns the address of the ERC1967I proxy of `implementation`, with `salt` by `deployer`. + /// Note: The returned result has dirty upper 96 bits. Please clean if used in assembly. + function predictDeterministicAddressERC1967I( + address implementation, + bytes32 salt, + address deployer + ) internal pure returns (address predicted) { + bytes32 hash = initCodeHashERC1967I(implementation); + predicted = predictDeterministicAddress(hash, salt, deployer); + } + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* ERC1967I PROXY WITH IMMUTABLE ARGS OPERATIONS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev Deploys a minimal ERC1967I proxy with `implementation` and `args`. + function deployERC1967I(address implementation, bytes memory args) internal returns (address) { + return deployERC1967I(0, implementation, args); + } + + /// @dev Deploys a minimal ERC1967I proxy with `implementation` and `args`. + /// Deposits `value` ETH during deployment. + function deployERC1967I(uint256 value, address implementation, bytes memory args) + internal + returns (address instance) + { + /// @solidity memory-safe-assembly + assembly { + let m := mload(0x40) + let n := mload(args) + pop(staticcall(gas(), 4, add(args, 0x20), n, add(m, 0x8b), n)) + + mstore(add(m, 0x6b), 0x3d6000803e603e573d6000fd5b3d6000f35b6020600f3d393d51543d52593df3) + mstore(add(m, 0x4b), 0xa13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc545af4) + mstore(add(m, 0x2b), 0x600f5155f3365814604357363d3d373d3d363d7f360894) + mstore(add(m, 0x14), implementation) + mstore(m, add(0xfe6100523d8160233d3973, shl(56, n))) + + // Do a out-of-gas revert if `n` is greater than `0xffff - 0x52 = 0xffad`. + instance := create(value, add(m, add(0x15, lt(n, 0xffae))), add(0x75, n)) + if iszero(instance) { + mstore(0x00, 0x30116425) // `DeploymentFailed()`. + revert(0x1c, 0x04) + } + } + } + + /// @dev Deploys a deterministic ERC1967I proxy with `implementation`, `args`, and `salt`. + function deployDeterministicERC1967I(address implementation, bytes memory args, bytes32 salt) + internal + returns (address instance) + { + instance = deployDeterministicERC1967I(0, implementation, args, salt); + } + + /// @dev Deploys a deterministic ERC1967I proxy with `implementation`,`args`, and `salt`. + /// Deposits `value` ETH during deployment. + function deployDeterministicERC1967I( + uint256 value, + address implementation, + bytes memory args, + bytes32 salt + ) internal returns (address instance) { + /// @solidity memory-safe-assembly + assembly { + let m := mload(0x40) + let n := mload(args) + pop(staticcall(gas(), 4, add(args, 0x20), n, add(m, 0x8b), n)) + + mstore(add(m, 0x6b), 0x3d6000803e603e573d6000fd5b3d6000f35b6020600f3d393d51543d52593df3) + mstore(add(m, 0x4b), 0xa13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc545af4) + mstore(add(m, 0x2b), 0x600f5155f3365814604357363d3d373d3d363d7f360894) + mstore(add(m, 0x14), implementation) + mstore(m, add(0xfe6100523d8160233d3973, shl(56, n))) + + // Do a out-of-gas revert if `n` is greater than `0xffff - 0x52 = 0xffad`. + instance := create2(value, add(m, add(0x15, lt(n, 0xffae))), add(0x75, n), salt) + if iszero(instance) { + mstore(0x00, 0x30116425) // `DeploymentFailed()`. + revert(0x1c, 0x04) + } + } + } + + /// @dev Creates a deterministic ERC1967I proxy with `implementation`, `args` and `salt`. + /// Note: This method is intended for use in ERC4337 factories, + /// which are expected to NOT revert if the proxy is already deployed. + function createDeterministicERC1967I(address implementation, bytes memory args, bytes32 salt) + internal + returns (bool alreadyDeployed, address instance) + { + return createDeterministicERC1967I(0, implementation, args, salt); + } + + /// @dev Creates a deterministic ERC1967I proxy with `implementation`,`args` and `salt`. + /// Deposits `value` ETH during deployment. + /// Note: This method is intended for use in ERC4337 factories, + /// which are expected to NOT revert if the proxy is already deployed. + function createDeterministicERC1967I( + uint256 value, + address implementation, + bytes memory args, + bytes32 salt + ) internal returns (bool alreadyDeployed, address instance) { + /// @solidity memory-safe-assembly + assembly { + let m := mload(0x40) + let n := mload(args) + pop(staticcall(gas(), 4, add(args, 0x20), n, add(m, 0x75), n)) + mstore(add(m, 0x55), 0x3d6000803e603e573d6000fd5b3d6000f35b6020600f3d393d51543d52593df3) + mstore(add(m, 0x35), 0xa13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc545af4) + mstore(add(m, 0x15), 0x5155f3365814604357363d3d373d3d363d7f360894) + mstore(0x16, 0x600f) + mstore(0x14, implementation) + // Do a out-of-gas revert if `n` is greater than `0xffff - 0x52 = 0xffad`. + mstore(gt(n, 0xffad), add(0xfe6100523d8160233d3973, shl(56, n))) + mstore(m, mload(0x16)) + // Compute and store the bytecode hash. + mstore8(0x00, 0xff) // Write the prefix. + mstore(0x35, keccak256(m, add(n, 0x75))) + mstore(0x01, shl(96, address())) + mstore(0x15, salt) + instance := keccak256(0x00, 0x55) + for {} 1 {} { + if iszero(extcodesize(instance)) { + instance := create2(value, m, add(0x75, n), salt) + if iszero(instance) { + mstore(0x00, 0x30116425) // `DeploymentFailed()`. + revert(0x1c, 0x04) + } + break + } + alreadyDeployed := 1 + if iszero(value) { break } + if iszero(call(gas(), instance, value, codesize(), 0x00, codesize(), 0x00)) { + mstore(0x00, 0xb12d13eb) // `ETHTransferFailed()`. + revert(0x1c, 0x04) + } + break + } + mstore(0x35, 0) // Restore the overwritten part of the free memory pointer. + } + } + + /// @dev Returns the initialization code of the ERC1967I proxy of `implementation`and `args`. + function initCodeERC1967I(address implementation, bytes memory args) + internal + pure + returns (bytes memory c) + { + /// @solidity memory-safe-assembly + assembly { + c := mload(0x40) + let n := mload(args) + // Do a out-of-gas revert if `n` is greater than `0xffff - 0x52 = 0xffad`. + returndatacopy(returndatasize(), returndatasize(), gt(n, 0xffad)) + for { let i := 0 } lt(i, n) { i := add(i, 0x20) } { + mstore(add(add(c, 0x95), i), mload(add(add(args, 0x20), i))) + } + + mstore(add(c, 0x75), 0x3d6000803e603e573d6000fd5b3d6000f35b6020600f3d393d51543d52593df3) + mstore(add(c, 0x55), 0xa13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc545af4) + mstore(add(c, 0x35), 0x600f5155f3365814604357363d3d373d3d363d7f360894) + mstore(add(c, 0x1e), implementation) + mstore(add(c, 0x0a), add(0x6100523d8160233d3973, shl(56, n))) + mstore(add(c, add(n, 0x95)), 0) + mstore(c, add(0x75, n)) // Store the length. + mstore(0x40, add(c, add(n, 0xb5))) // Allocate memory. + } + } + + /// @dev Returns the initialization code hash of the ERC1967I proxy of `implementation` and `args. + function initCodeHashERC1967I(address implementation, bytes memory args) + internal + pure + returns (bytes32 hash) + { + /// @solidity memory-safe-assembly + assembly { + let m := mload(0x40) // Cache the free memory pointer. + let n := mload(args) + // Do a out-of-gas revert if `n` is greater than `0xffff - 0x52 = 0xffad`. + returndatacopy(returndatasize(), returndatasize(), gt(n, 0xffad)) + + for { let i := 0 } lt(i, n) { i := add(i, 0x20) } { + mstore(add(add(m, 0x75), i), mload(add(add(args, 0x20), i))) + } + + mstore(add(m, 0x55), 0x3d6000803e603e573d6000fd5b3d6000f35b6020600f3d393d51543d52593df3) + mstore(add(m, 0x35), 0xa13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc545af4) + mstore(add(m, 0x15), 0x5155f3365814604357363d3d373d3d363d7f360894) + mstore(0x16, 0x600f) + mstore(0x14, implementation) + mstore(0x00, add(0x6100523d8160233d3973, shl(56, n))) + mstore(m, mload(0x16)) + hash := keccak256(m, add(0x75, n)) + } + } + + /// @dev Returns the address of the ERC1967I proxy of `implementation`, 'args` with `salt` by `deployer`. + /// Note: The returned result has dirty upper 96 bits. Please clean if used in assembly. + function predictDeterministicAddressERC1967I( + address implementation, + bytes memory args, + bytes32 salt, + address deployer + ) internal pure returns (address predicted) { + bytes32 hash = initCodeHashERC1967I(implementation, args); + predicted = predictDeterministicAddress(hash, salt, deployer); + } + + /// @dev Equivalent to `argsOnERC1967I(instance, start, 2 ** 256 - 1)`. + function argsOnERC1967I(address instance) internal view returns (bytes memory args) { + /// @solidity memory-safe-assembly + assembly { + args := mload(0x40) + mstore(args, and(0xffffffffff, sub(extcodesize(instance), 0x52))) // Store the length. + extcodecopy(instance, add(args, 0x20), 0x52, add(mload(args), 0x20)) + mstore(0x40, add(mload(args), add(args, 0x40))) // Allocate memory. + } + } + + /// @dev Equivalent to `argsOnERC1967I(instance, start, 2 ** 256 - 1)`. + function argsOnERC1967I(address instance, uint256 start) + internal + view + returns (bytes memory args) + { + /// @solidity memory-safe-assembly + assembly { + args := mload(0x40) + let n := and(0xffffffffff, sub(extcodesize(instance), 0x52)) + extcodecopy(instance, add(args, 0x20), add(start, 0x52), add(n, 0x20)) + mstore(args, mul(sub(n, start), lt(start, n))) // Store the length. + mstore(0x40, add(mload(args), add(args, 0x40))) // Allocate memory. + } + } + + /// @dev Returns a slice of the immutable arguments on `instance` from `start` to `end`. + /// `start` and `end` will be clamped to the range `[0, args.length]`. + /// The `instance` MUST be deployed via the ERC1967 with immutable args functions. + /// Otherwise, the behavior is undefined. + /// Out-of-gas reverts if `instance` does not have any code. + function argsOnERC1967I(address instance, uint256 start, uint256 end) + internal + view + returns (bytes memory args) + { + /// @solidity memory-safe-assembly + assembly { + args := mload(0x40) + if iszero(lt(end, 0xffff)) { end := 0xffff } + let d := mul(sub(end, start), lt(start, end)) + extcodecopy(instance, args, add(start, 0x32), add(d, 0x20)) + if iszero(and(0xff, mload(add(args, d)))) { + let n := sub(extcodesize(instance), 0x52) + returndatacopy(returndatasize(), returndatasize(), shr(40, n)) + d := mul(gt(n, start), sub(d, mul(gt(end, n), sub(end, n)))) + } + mstore(args, d) // Store the length. + mstore(add(add(args, 0x20), d), 0) // Zeroize the slot after the bytes. + mstore(0x40, add(add(args, 0x40), d)) // Allocate memory. + } + } + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* ERC1967 BOOTSTRAP OPERATIONS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + // A bootstrap is a minimal UUPS implementation that allows an ERC1967 proxy + // pointing to it to be upgraded. The ERC1967 proxy can then be deployed to a + // deterministic address independent of the implementation: + // ``` + // address bootstrap = LibClone.erc1967Bootstrap(); + // address instance = LibClone.deployDeterministicERC1967(0, bootstrap, salt); + // LibClone.bootstrapERC1967(bootstrap, implementation); + // ``` + + /// @dev Deploys the ERC1967 bootstrap if it has not been deployed. + function erc1967Bootstrap() internal returns (address) { + return erc1967Bootstrap(address(this)); + } + + /// @dev Deploys the ERC1967 bootstrap if it has not been deployed. + function erc1967Bootstrap(address authorizedUpgrader) internal returns (address bootstrap) { + bytes memory c = initCodeERC1967Bootstrap(authorizedUpgrader); + bootstrap = predictDeterministicAddress(keccak256(c), bytes32(0), address(this)); + /// @solidity memory-safe-assembly + assembly { + if iszero(extcodesize(bootstrap)) { + if iszero(create2(0, add(c, 0x20), mload(c), 0)) { + mstore(0x00, 0x30116425) // `DeploymentFailed()`. + revert(0x1c, 0x04) + } + } + } + } + + /// @dev Replaces the implementation at `instance`. + function bootstrapERC1967(address instance, address implementation) internal { + /// @solidity memory-safe-assembly + assembly { + mstore(0x00, implementation) + if iszero(call(gas(), instance, 0, 0x0c, 0x14, codesize(), 0x00)) { + mstore(0x00, 0x30116425) // `DeploymentFailed()`. + revert(0x1c, 0x04) + } + } + } + + /// @dev Replaces the implementation at `instance`, and then call it with `data`. + function bootstrapERC1967AndCall(address instance, address implementation, bytes memory data) + internal + { + /// @solidity memory-safe-assembly + assembly { + let n := mload(data) + mstore(data, implementation) + if iszero(call(gas(), instance, 0, add(data, 0x0c), add(n, 0x14), codesize(), 0x00)) { + if iszero(returndatasize()) { + mstore(0x00, 0x30116425) // `DeploymentFailed()`. + revert(0x1c, 0x04) + } + returndatacopy(mload(0x40), 0x00, returndatasize()) + revert(mload(0x40), returndatasize()) + } + mstore(data, n) // Restore the length of `data`. + } + } + + /// @dev Returns the implementation address of the ERC1967 bootstrap for this contract. + function predictDeterministicAddressERC1967Bootstrap() internal view returns (address) { + return predictDeterministicAddressERC1967Bootstrap(address(this), address(this)); + } + + /// @dev Returns the implementation address of the ERC1967 bootstrap for this contract. + function predictDeterministicAddressERC1967Bootstrap( + address authorizedUpgrader, + address deployer + ) internal pure returns (address) { + bytes32 hash = initCodeHashERC1967Bootstrap(authorizedUpgrader); + return predictDeterministicAddress(hash, bytes32(0), deployer); + } + + /// @dev Returns the initialization code of the ERC1967 bootstrap. + function initCodeERC1967Bootstrap(address authorizedUpgrader) + internal + pure + returns (bytes memory c) + { + /// @solidity memory-safe-assembly + assembly { + c := mload(0x40) + mstore(add(c, 0x80), 0x3d3560601c5af46047573d6000383e3d38fd0000000000000000000000000000) + mstore(add(c, 0x60), 0xa920a3ca505d382bbc55601436116049575b005b363d3d373d3d601436036014) + mstore(add(c, 0x40), 0x0338573d3560601c7f360894a13ba1a3210667c828492db98dca3e2076cc3735) + mstore(add(c, 0x20), authorizedUpgrader) + mstore(add(c, 0x0c), 0x606880600a3d393df3fe3373) + mstore(c, 0x72) + mstore(0x40, add(c, 0xa0)) + } + } + + /// @dev Returns the initialization code hash of the ERC1967 bootstrap. + function initCodeHashERC1967Bootstrap(address authorizedUpgrader) + internal + pure + returns (bytes32) + { + return keccak256(initCodeERC1967Bootstrap(authorizedUpgrader)); + } + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* MINIMAL ERC1967 BEACON PROXY OPERATIONS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + // Note: If you use this proxy, you MUST make sure that the beacon is a + // valid ERC1967 beacon. This means that the beacon must always return a valid + // address upon a staticcall to `implementation()`, given sufficient gas. + // For performance, the deployment operations and the proxy assumes that the + // beacon is always valid and will NOT validate it. + + /// @dev Deploys a minimal ERC1967 beacon proxy. + function deployERC1967BeaconProxy(address beacon) internal returns (address instance) { + instance = deployERC1967BeaconProxy(0, beacon); + } + + /// @dev Deploys a minimal ERC1967 beacon proxy. + /// Deposits `value` ETH during deployment. + function deployERC1967BeaconProxy(uint256 value, address beacon) + internal + returns (address instance) + { + /// @solidity memory-safe-assembly + assembly { + /** + * ---------------------------------------------------------------------------------+ + * CREATION (34 bytes) | + * ---------------------------------------------------------------------------------| + * Opcode | Mnemonic | Stack | Memory | + * ---------------------------------------------------------------------------------| + * 60 runSize | PUSH1 runSize | r | | + * 3d | RETURNDATASIZE | 0 r | | + * 81 | DUP2 | r 0 r | | + * 60 offset | PUSH1 offset | o r 0 r | | + * 3d | RETURNDATASIZE | 0 o r 0 r | | + * 39 | CODECOPY | 0 r | [0..runSize): runtime code | + * 73 beac | PUSH20 beac | beac 0 r | [0..runSize): runtime code | + * 60 slotPos | PUSH1 slotPos | slotPos beac 0 r | [0..runSize): runtime code | + * 51 | MLOAD | slot beac 0 r | [0..runSize): runtime code | + * 55 | SSTORE | 0 r | [0..runSize): runtime code | + * f3 | RETURN | | [0..runSize): runtime code | + * ---------------------------------------------------------------------------------| + * RUNTIME (82 bytes) | + * ---------------------------------------------------------------------------------| + * Opcode | Mnemonic | Stack | Memory | + * ---------------------------------------------------------------------------------| + * | + * ::: copy calldata to memory :::::::::::::::::::::::::::::::::::::::::::::::::::: | + * 36 | CALLDATASIZE | cds | | + * 3d | RETURNDATASIZE | 0 cds | | + * 3d | RETURNDATASIZE | 0 0 cds | | + * 37 | CALLDATACOPY | | [0..calldatasize): calldata | + * | + * ::: delegatecall to implementation ::::::::::::::::::::::::::::::::::::::::::::: | + * 3d | RETURNDATASIZE | 0 | | + * 3d | RETURNDATASIZE | 0 0 | | + * 36 | CALLDATASIZE | cds 0 0 | [0..calldatasize): calldata | + * 3d | RETURNDATASIZE | 0 cds 0 0 | [0..calldatasize): calldata | + * | + * ~~~~~~~ beacon staticcall sub procedure ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | + * 60 0x20 | PUSH1 0x20 | 32 | | + * 36 | CALLDATASIZE | cds 32 | | + * 60 0x04 | PUSH1 0x04 | 4 cds 32 | | + * 36 | CALLDATASIZE | cds 4 cds 32 | | + * 63 0x5c60da1b | PUSH4 0x5c60da1b | 0x5c60da1b cds 4 cds 32 | | + * 60 0xe0 | PUSH1 0xe0 | 224 0x5c60da1b cds 4 cds 32 | | + * 1b | SHL | sel cds 4 cds 32 | | + * 36 | CALLDATASIZE | cds sel cds 4 cds 32 | | + * 52 | MSTORE | cds 4 cds 32 | sel | + * 7f slot | PUSH32 slot | s cds 4 cds 32 | sel | + * 54 | SLOAD | beac cds 4 cds 32 | sel | + * 5a | GAS | g beac cds 4 cds 32 | sel | + * fa | STATICCALL | succ | impl | + * 50 | POP | | impl | + * 36 | CALLDATASIZE | cds | impl | + * 51 | MLOAD | impl | impl | + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | + * 5a | GAS | g impl 0 cds 0 0 | [0..calldatasize): calldata | + * f4 | DELEGATECALL | succ | [0..calldatasize): calldata | + * | + * ::: copy returndata to memory :::::::::::::::::::::::::::::::::::::::::::::::::: | + * 3d | RETURNDATASIZE | rds succ | [0..calldatasize): calldata | + * 60 0x00 | PUSH1 0x00 | 0 rds succ | [0..calldatasize): calldata | + * 80 | DUP1 | 0 0 rds succ | [0..calldatasize): calldata | + * 3e | RETURNDATACOPY | succ | [0..returndatasize): returndata | + * | + * ::: branch on delegatecall status :::::::::::::::::::::::::::::::::::::::::::::: | + * 60 0x4d | PUSH1 0x4d | dest succ | [0..returndatasize): returndata | + * 57 | JUMPI | | [0..returndatasize): returndata | + * | + * ::: delegatecall failed, revert :::::::::::::::::::::::::::::::::::::::::::::::: | + * 3d | RETURNDATASIZE | rds | [0..returndatasize): returndata | + * 60 0x00 | PUSH1 0x00 | 0 rds | [0..returndatasize): returndata | + * fd | REVERT | | [0..returndatasize): returndata | + * | + * ::: delegatecall succeeded, return ::::::::::::::::::::::::::::::::::::::::::::: | + * 5b | JUMPDEST | | [0..returndatasize): returndata | + * 3d | RETURNDATASIZE | rds | [0..returndatasize): returndata | + * 60 0x00 | PUSH1 0x00 | 0 rds | [0..returndatasize): returndata | + * f3 | RETURN | | [0..returndatasize): returndata | + * ---------------------------------------------------------------------------------+ + */ + let m := mload(0x40) // Cache the free memory pointer. + mstore(0x60, 0xb3582b35133d50545afa5036515af43d6000803e604d573d6000fd5b3d6000f3) + mstore(0x40, 0x1b60e01b36527fa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6c) + mstore(0x20, 0x60195155f3363d3d373d3d363d602036600436635c60da) + mstore(0x09, or(shl(160, 0x60523d8160223d3973), shr(96, shl(96, beacon)))) + instance := create(value, 0x0c, 0x74) + if iszero(instance) { + mstore(0x00, 0x30116425) // `DeploymentFailed()`. + revert(0x1c, 0x04) + } + mstore(0x40, m) // Restore the free memory pointer. + mstore(0x60, 0) // Restore the zero slot. + } + } + + /// @dev Deploys a deterministic minimal ERC1967 beacon proxy with `salt`. + function deployDeterministicERC1967BeaconProxy(address beacon, bytes32 salt) + internal + returns (address instance) + { + instance = deployDeterministicERC1967BeaconProxy(0, beacon, salt); + } + + /// @dev Deploys a deterministic minimal ERC1967 beacon proxy with `salt`. + /// Deposits `value` ETH during deployment. + function deployDeterministicERC1967BeaconProxy(uint256 value, address beacon, bytes32 salt) + internal + returns (address instance) + { + /// @solidity memory-safe-assembly + assembly { + let m := mload(0x40) // Cache the free memory pointer. + mstore(0x60, 0xb3582b35133d50545afa5036515af43d6000803e604d573d6000fd5b3d6000f3) + mstore(0x40, 0x1b60e01b36527fa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6c) + mstore(0x20, 0x60195155f3363d3d373d3d363d602036600436635c60da) + mstore(0x09, or(shl(160, 0x60523d8160223d3973), shr(96, shl(96, beacon)))) + instance := create2(value, 0x0c, 0x74, salt) + if iszero(instance) { + mstore(0x00, 0x30116425) // `DeploymentFailed()`. + revert(0x1c, 0x04) + } + mstore(0x40, m) // Restore the free memory pointer. + mstore(0x60, 0) // Restore the zero slot. + } + } + + /// @dev Creates a deterministic minimal ERC1967 beacon proxy with `salt`. + /// Note: This method is intended for use in ERC4337 factories, + /// which are expected to NOT revert if the proxy is already deployed. + function createDeterministicERC1967BeaconProxy(address beacon, bytes32 salt) + internal + returns (bool alreadyDeployed, address instance) + { + return createDeterministicERC1967BeaconProxy(0, beacon, salt); + } + + /// @dev Creates a deterministic minimal ERC1967 beacon proxy with `salt`. + /// Deposits `value` ETH during deployment. + /// Note: This method is intended for use in ERC4337 factories, + /// which are expected to NOT revert if the proxy is already deployed. + function createDeterministicERC1967BeaconProxy(uint256 value, address beacon, bytes32 salt) + internal + returns (bool alreadyDeployed, address instance) + { + /// @solidity memory-safe-assembly + assembly { + let m := mload(0x40) // Cache the free memory pointer. + mstore(0x60, 0xb3582b35133d50545afa5036515af43d6000803e604d573d6000fd5b3d6000f3) + mstore(0x40, 0x1b60e01b36527fa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6c) + mstore(0x20, 0x60195155f3363d3d373d3d363d602036600436635c60da) + mstore(0x09, or(shl(160, 0x60523d8160223d3973), shr(96, shl(96, beacon)))) + // Compute and store the bytecode hash. + mstore(add(m, 0x35), keccak256(0x0c, 0x74)) + mstore(m, shl(88, address())) + mstore8(m, 0xff) // Write the prefix. + mstore(add(m, 0x15), salt) + instance := keccak256(m, 0x55) + for {} 1 {} { + if iszero(extcodesize(instance)) { + instance := create2(value, 0x0c, 0x74, salt) + if iszero(instance) { + mstore(0x00, 0x30116425) // `DeploymentFailed()`. + revert(0x1c, 0x04) + } + break + } + alreadyDeployed := 1 + if iszero(value) { break } + if iszero(call(gas(), instance, value, codesize(), 0x00, codesize(), 0x00)) { + mstore(0x00, 0xb12d13eb) // `ETHTransferFailed()`. + revert(0x1c, 0x04) + } + break + } + mstore(0x40, m) // Restore the free memory pointer. + mstore(0x60, 0) // Restore the zero slot. + } + } + + /// @dev Returns the initialization code of the minimal ERC1967 beacon proxy. + function initCodeERC1967BeaconProxy(address beacon) internal pure returns (bytes memory c) { + /// @solidity memory-safe-assembly + assembly { + c := mload(0x40) + mstore(add(c, 0x74), 0xb3582b35133d50545afa5036515af43d6000803e604d573d6000fd5b3d6000f3) + mstore(add(c, 0x54), 0x1b60e01b36527fa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6c) + mstore(add(c, 0x34), 0x60195155f3363d3d373d3d363d602036600436635c60da) + mstore(add(c, 0x1d), beacon) + mstore(add(c, 0x09), 0x60523d8160223d3973) + mstore(add(c, 0x94), 0) + mstore(c, 0x74) // Store the length. + mstore(0x40, add(c, 0xa0)) // Allocate memory. + } + } + + /// @dev Returns the initialization code hash of the minimal ERC1967 beacon proxy. + function initCodeHashERC1967BeaconProxy(address beacon) internal pure returns (bytes32 hash) { + /// @solidity memory-safe-assembly + assembly { + let m := mload(0x40) // Cache the free memory pointer. + mstore(0x60, 0xb3582b35133d50545afa5036515af43d6000803e604d573d6000fd5b3d6000f3) + mstore(0x40, 0x1b60e01b36527fa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6c) + mstore(0x20, 0x60195155f3363d3d373d3d363d602036600436635c60da) + mstore(0x09, or(shl(160, 0x60523d8160223d3973), shr(96, shl(96, beacon)))) + hash := keccak256(0x0c, 0x74) + mstore(0x40, m) // Restore the free memory pointer. + mstore(0x60, 0) // Restore the zero slot. + } + } + + /// @dev Returns the address of the ERC1967 beacon proxy, with `salt` by `deployer`. + /// Note: The returned result has dirty upper 96 bits. Please clean if used in assembly. + function predictDeterministicAddressERC1967BeaconProxy( + address beacon, + bytes32 salt, + address deployer + ) internal pure returns (address predicted) { + bytes32 hash = initCodeHashERC1967BeaconProxy(beacon); + predicted = predictDeterministicAddress(hash, salt, deployer); + } + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* ERC1967 BEACON PROXY WITH IMMUTABLE ARGS OPERATIONS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev Deploys a minimal ERC1967 beacon proxy with `args`. + function deployERC1967BeaconProxy(address beacon, bytes memory args) + internal + returns (address instance) + { + instance = deployERC1967BeaconProxy(0, beacon, args); + } + + /// @dev Deploys a minimal ERC1967 beacon proxy with `args`. + /// Deposits `value` ETH during deployment. + function deployERC1967BeaconProxy(uint256 value, address beacon, bytes memory args) + internal + returns (address instance) + { + /// @solidity memory-safe-assembly + assembly { + let m := mload(0x40) + let n := mload(args) + pop(staticcall(gas(), 4, add(args, 0x20), n, add(m, 0x8b), n)) + mstore(add(m, 0x6b), 0xb3582b35133d50545afa5036515af43d6000803e604d573d6000fd5b3d6000f3) + mstore(add(m, 0x4b), 0x1b60e01b36527fa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6c) + mstore(add(m, 0x2b), 0x60195155f3363d3d373d3d363d602036600436635c60da) + mstore(add(m, 0x14), beacon) + // Do a out-of-gas revert if `n` is greater than `0xffff - 0x52 = 0xffad`. + mstore(add(m, gt(n, 0xffad)), add(0xfe6100523d8160233d3973, shl(56, n))) + instance := create(value, add(m, 0x16), add(n, 0x75)) + if iszero(instance) { + mstore(0x00, 0x30116425) // `DeploymentFailed()`. + revert(0x1c, 0x04) + } + } + } + + /// @dev Deploys a deterministic minimal ERC1967 beacon proxy with `args` and `salt`. + function deployDeterministicERC1967BeaconProxy(address beacon, bytes memory args, bytes32 salt) + internal + returns (address instance) + { + instance = deployDeterministicERC1967BeaconProxy(0, beacon, args, salt); + } + + /// @dev Deploys a deterministic minimal ERC1967 beacon proxy with `args` and `salt`. + /// Deposits `value` ETH during deployment. + function deployDeterministicERC1967BeaconProxy( + uint256 value, + address beacon, + bytes memory args, + bytes32 salt + ) internal returns (address instance) { + /// @solidity memory-safe-assembly + assembly { + let m := mload(0x40) + let n := mload(args) + pop(staticcall(gas(), 4, add(args, 0x20), n, add(m, 0x8b), n)) + mstore(add(m, 0x6b), 0xb3582b35133d50545afa5036515af43d6000803e604d573d6000fd5b3d6000f3) + mstore(add(m, 0x4b), 0x1b60e01b36527fa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6c) + mstore(add(m, 0x2b), 0x60195155f3363d3d373d3d363d602036600436635c60da) + mstore(add(m, 0x14), beacon) + // Do a out-of-gas revert if `n` is greater than `0xffff - 0x52 = 0xffad`. + mstore(add(m, gt(n, 0xffad)), add(0xfe6100523d8160233d3973, shl(56, n))) + instance := create2(value, add(m, 0x16), add(n, 0x75), salt) + if iszero(instance) { + mstore(0x00, 0x30116425) // `DeploymentFailed()`. + revert(0x1c, 0x04) + } + } + } + + /// @dev Creates a deterministic minimal ERC1967 beacon proxy with `args` and `salt`. + /// Note: This method is intended for use in ERC4337 factories, + /// which are expected to NOT revert if the proxy is already deployed. + function createDeterministicERC1967BeaconProxy(address beacon, bytes memory args, bytes32 salt) + internal + returns (bool alreadyDeployed, address instance) + { + return createDeterministicERC1967BeaconProxy(0, beacon, args, salt); + } + + /// @dev Creates a deterministic minimal ERC1967 beacon proxy with `args` and `salt`. + /// Deposits `value` ETH during deployment. + /// Note: This method is intended for use in ERC4337 factories, + /// which are expected to NOT revert if the proxy is already deployed. + function createDeterministicERC1967BeaconProxy( + uint256 value, + address beacon, + bytes memory args, + bytes32 salt + ) internal returns (bool alreadyDeployed, address instance) { + /// @solidity memory-safe-assembly + assembly { + let m := mload(0x40) + let n := mload(args) + pop(staticcall(gas(), 4, add(args, 0x20), n, add(m, 0x8b), n)) + mstore(add(m, 0x6b), 0xb3582b35133d50545afa5036515af43d6000803e604d573d6000fd5b3d6000f3) + mstore(add(m, 0x4b), 0x1b60e01b36527fa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6c) + mstore(add(m, 0x2b), 0x60195155f3363d3d373d3d363d602036600436635c60da) + mstore(add(m, 0x14), beacon) + // Do a out-of-gas revert if `n` is greater than `0xffff - 0x52 = 0xffad`. + mstore(add(m, gt(n, 0xffad)), add(0xfe6100523d8160233d3973, shl(56, n))) + // Compute and store the bytecode hash. + mstore8(0x00, 0xff) // Write the prefix. + mstore(0x35, keccak256(add(m, 0x16), add(n, 0x75))) + mstore(0x01, shl(96, address())) + mstore(0x15, salt) + instance := keccak256(0x00, 0x55) + for {} 1 {} { + if iszero(extcodesize(instance)) { + instance := create2(value, add(m, 0x16), add(n, 0x75), salt) + if iszero(instance) { + mstore(0x00, 0x30116425) // `DeploymentFailed()`. + revert(0x1c, 0x04) + } + break + } + alreadyDeployed := 1 + if iszero(value) { break } + if iszero(call(gas(), instance, value, codesize(), 0x00, codesize(), 0x00)) { + mstore(0x00, 0xb12d13eb) // `ETHTransferFailed()`. + revert(0x1c, 0x04) + } + break + } + mstore(0x35, 0) // Restore the overwritten part of the free memory pointer. + } + } + + /// @dev Returns the initialization code of the minimal ERC1967 beacon proxy. + function initCodeERC1967BeaconProxy(address beacon, bytes memory args) + internal + pure + returns (bytes memory c) + { + /// @solidity memory-safe-assembly + assembly { + c := mload(0x40) + let n := mload(args) + // Do a out-of-gas revert if `n` is greater than `0xffff - 0x52 = 0xffad`. + returndatacopy(returndatasize(), returndatasize(), gt(n, 0xffad)) + for { let i := 0 } lt(i, n) { i := add(i, 0x20) } { + mstore(add(add(c, 0x95), i), mload(add(add(args, 0x20), i))) + } + mstore(add(c, 0x75), 0xb3582b35133d50545afa5036515af43d6000803e604d573d6000fd5b3d6000f3) + mstore(add(c, 0x55), 0x1b60e01b36527fa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6c) + mstore(add(c, 0x35), 0x60195155f3363d3d373d3d363d602036600436635c60da) + mstore(add(c, 0x1e), beacon) + mstore(add(c, 0x0a), add(0x6100523d8160233d3973, shl(56, n))) + mstore(c, add(n, 0x75)) // Store the length. + mstore(add(c, add(n, 0x95)), 0) // Zeroize the slot after the bytes. + mstore(0x40, add(c, add(n, 0xb5))) // Allocate memory. + } + } + + /// @dev Returns the initialization code hash of the minimal ERC1967 beacon proxy with `args`. + function initCodeHashERC1967BeaconProxy(address beacon, bytes memory args) + internal + pure + returns (bytes32 hash) + { + /// @solidity memory-safe-assembly + assembly { + let m := mload(0x40) + let n := mload(args) + // Do a out-of-gas revert if `n` is greater than `0xffff - 0x52 = 0xffad`. + returndatacopy(returndatasize(), returndatasize(), gt(n, 0xffad)) + for { let i := 0 } lt(i, n) { i := add(i, 0x20) } { + mstore(add(add(m, 0x8b), i), mload(add(add(args, 0x20), i))) + } + mstore(add(m, 0x6b), 0xb3582b35133d50545afa5036515af43d6000803e604d573d6000fd5b3d6000f3) + mstore(add(m, 0x4b), 0x1b60e01b36527fa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6c) + mstore(add(m, 0x2b), 0x60195155f3363d3d373d3d363d602036600436635c60da) + mstore(add(m, 0x14), beacon) + mstore(m, add(0x6100523d8160233d3973, shl(56, n))) + hash := keccak256(add(m, 0x16), add(n, 0x75)) + } + } + + /// @dev Returns the address of the ERC1967 beacon proxy with `args`, with `salt` by `deployer`. + /// Note: The returned result has dirty upper 96 bits. Please clean if used in assembly. + function predictDeterministicAddressERC1967BeaconProxy( + address beacon, + bytes memory args, + bytes32 salt, + address deployer + ) internal pure returns (address predicted) { + bytes32 hash = initCodeHashERC1967BeaconProxy(beacon, args); + predicted = predictDeterministicAddress(hash, salt, deployer); + } + + /// @dev Equivalent to `argsOnERC1967BeaconProxy(instance, start, 2 ** 256 - 1)`. + function argsOnERC1967BeaconProxy(address instance) internal view returns (bytes memory args) { + /// @solidity memory-safe-assembly + assembly { + args := mload(0x40) + mstore(args, and(0xffffffffff, sub(extcodesize(instance), 0x52))) // Store the length. + extcodecopy(instance, add(args, 0x20), 0x52, add(mload(args), 0x20)) + mstore(0x40, add(mload(args), add(args, 0x40))) // Allocate memory. + } + } + + /// @dev Equivalent to `argsOnERC1967BeaconProxy(instance, start, 2 ** 256 - 1)`. + function argsOnERC1967BeaconProxy(address instance, uint256 start) + internal + view + returns (bytes memory args) + { + /// @solidity memory-safe-assembly + assembly { + args := mload(0x40) + let n := and(0xffffffffff, sub(extcodesize(instance), 0x52)) + extcodecopy(instance, add(args, 0x20), add(start, 0x52), add(n, 0x20)) + mstore(args, mul(sub(n, start), lt(start, n))) // Store the length. + mstore(0x40, add(args, add(0x40, mload(args)))) // Allocate memory. + } + } + + /// @dev Returns a slice of the immutable arguments on `instance` from `start` to `end`. + /// `start` and `end` will be clamped to the range `[0, args.length]`. + /// The `instance` MUST be deployed via the ERC1967 beacon proxy with immutable args functions. + /// Otherwise, the behavior is undefined. + /// Out-of-gas reverts if `instance` does not have any code. + function argsOnERC1967BeaconProxy(address instance, uint256 start, uint256 end) + internal + view + returns (bytes memory args) + { + /// @solidity memory-safe-assembly + assembly { + args := mload(0x40) + if iszero(lt(end, 0xffff)) { end := 0xffff } + let d := mul(sub(end, start), lt(start, end)) + extcodecopy(instance, args, add(start, 0x32), add(d, 0x20)) + if iszero(and(0xff, mload(add(args, d)))) { + let n := sub(extcodesize(instance), 0x52) + returndatacopy(returndatasize(), returndatasize(), shr(40, n)) + d := mul(gt(n, start), sub(d, mul(gt(end, n), sub(end, n)))) + } + mstore(args, d) // Store the length. + mstore(add(add(args, 0x20), d), 0) // Zeroize the slot after the bytes. + mstore(0x40, add(add(args, 0x40), d)) // Allocate memory. + } + } + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* ERC1967I BEACON PROXY OPERATIONS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + // Note: This proxy has a special code path that activates if `calldatasize() == 1`. + // This code path skips the delegatecall and directly returns the `implementation` address. + // The returned implementation is guaranteed to be valid if the keccak256 of the + // proxy's code is equal to `ERC1967_BEACON_PROXY_CODE_HASH`. + // + // If you use this proxy, you MUST make sure that the beacon is a + // valid ERC1967 beacon. This means that the beacon must always return a valid + // address upon a staticcall to `implementation()`, given sufficient gas. + // For performance, the deployment operations and the proxy assumes that the + // beacon is always valid and will NOT validate it. + + /// @dev Deploys a ERC1967I beacon proxy. + function deployERC1967IBeaconProxy(address beacon) internal returns (address instance) { + instance = deployERC1967IBeaconProxy(0, beacon); + } + + /// @dev Deploys a ERC1967I beacon proxy. + /// Deposits `value` ETH during deployment. + function deployERC1967IBeaconProxy(uint256 value, address beacon) + internal + returns (address instance) + { + /// @solidity memory-safe-assembly + assembly { + /** + * ---------------------------------------------------------------------------------+ + * CREATION (34 bytes) | + * ---------------------------------------------------------------------------------| + * Opcode | Mnemonic | Stack | Memory | + * ---------------------------------------------------------------------------------| + * 60 runSize | PUSH1 runSize | r | | + * 3d | RETURNDATASIZE | 0 r | | + * 81 | DUP2 | r 0 r | | + * 60 offset | PUSH1 offset | o r 0 r | | + * 3d | RETURNDATASIZE | 0 o r 0 r | | + * 39 | CODECOPY | 0 r | [0..runSize): runtime code | + * 73 beac | PUSH20 beac | beac 0 r | [0..runSize): runtime code | + * 60 slotPos | PUSH1 slotPos | slotPos beac 0 r | [0..runSize): runtime code | + * 51 | MLOAD | slot beac 0 r | [0..runSize): runtime code | + * 55 | SSTORE | 0 r | [0..runSize): runtime code | + * f3 | RETURN | | [0..runSize): runtime code | + * ---------------------------------------------------------------------------------| + * RUNTIME (87 bytes) | + * ---------------------------------------------------------------------------------| + * Opcode | Mnemonic | Stack | Memory | + * ---------------------------------------------------------------------------------| + * | + * ::: copy calldata to memory :::::::::::::::::::::::::::::::::::::::::::::::::::: | + * 36 | CALLDATASIZE | cds | | + * 3d | RETURNDATASIZE | 0 cds | | + * 3d | RETURNDATASIZE | 0 0 cds | | + * 37 | CALLDATACOPY | | [0..calldatasize): calldata | + * | + * ::: delegatecall to implementation ::::::::::::::::::::::::::::::::::::::::::::: | + * 3d | RETURNDATASIZE | 0 | | + * 3d | RETURNDATASIZE | 0 0 | | + * 36 | CALLDATASIZE | cds 0 0 | [0..calldatasize): calldata | + * 3d | RETURNDATASIZE | 0 cds 0 0 | [0..calldatasize): calldata | + * | + * ~~~~~~~ beacon staticcall sub procedure ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | + * 60 0x20 | PUSH1 0x20 | 32 | | + * 36 | CALLDATASIZE | cds 32 | | + * 60 0x04 | PUSH1 0x04 | 4 cds 32 | | + * 36 | CALLDATASIZE | cds 4 cds 32 | | + * 63 0x5c60da1b | PUSH4 0x5c60da1b | 0x5c60da1b cds 4 cds 32 | | + * 60 0xe0 | PUSH1 0xe0 | 224 0x5c60da1b cds 4 cds 32 | | + * 1b | SHL | sel cds 4 cds 32 | | + * 36 | CALLDATASIZE | cds sel cds 4 cds 32 | | + * 52 | MSTORE | cds 4 cds 32 | sel | + * 7f slot | PUSH32 slot | s cds 4 cds 32 | sel | + * 54 | SLOAD | beac cds 4 cds 32 | sel | + * 5a | GAS | g beac cds 4 cds 32 | sel | + * fa | STATICCALL | succ | impl | + * ~~~~~~ check calldatasize ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | + * 36 | CALLDATASIZE | cds succ | | + * 14 | EQ | | impl | + * 60 0x52 | PUSH1 0x52 | | impl | + * 57 | JUMPI | | impl | + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | + * 36 | CALLDATASIZE | cds | impl | + * 51 | MLOAD | impl | impl | + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | + * 5a | GAS | g impl 0 cds 0 0 | [0..calldatasize): calldata | + * f4 | DELEGATECALL | succ | [0..calldatasize): calldata | + * | + * ::: copy returndata to memory :::::::::::::::::::::::::::::::::::::::::::::::::: | + * 3d | RETURNDATASIZE | rds succ | [0..calldatasize): calldata | + * 60 0x00 | PUSH1 0x00 | 0 rds succ | [0..calldatasize): calldata | + * 60 0x01 | PUSH1 0x01 | 1 0 rds succ | [0..calldatasize): calldata | + * 3e | RETURNDATACOPY | succ | [1..returndatasize): returndata | + * | + * ::: branch on delegatecall status :::::::::::::::::::::::::::::::::::::::::::::: | + * 60 0x52 | PUSH1 0x52 | dest succ | [1..returndatasize): returndata | + * 57 | JUMPI | | [1..returndatasize): returndata | + * | + * ::: delegatecall failed, revert :::::::::::::::::::::::::::::::::::::::::::::::: | + * 3d | RETURNDATASIZE | rds | [1..returndatasize): returndata | + * 60 0x01 | PUSH1 0x01 | 1 rds | [1..returndatasize): returndata | + * fd | REVERT | | [1..returndatasize): returndata | + * | + * ::: delegatecall succeeded, return ::::::::::::::::::::::::::::::::::::::::::::: | + * 5b | JUMPDEST | | [1..returndatasize): returndata | + * 3d | RETURNDATASIZE | rds | [1..returndatasize): returndata | + * 60 0x01 | PUSH1 0x01 | 1 rds | [1..returndatasize): returndata | + * f3 | RETURN | | [1..returndatasize): returndata | + * ---------------------------------------------------------------------------------+ + */ + let m := mload(0x40) // Cache the free memory pointer. + mstore(0x60, 0x3d50545afa361460525736515af43d600060013e6052573d6001fd5b3d6001f3) + mstore(0x40, 0x527fa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b3513) + mstore(0x20, 0x60195155f3363d3d373d3d363d602036600436635c60da1b60e01b36) + mstore(0x04, or(shl(160, 0x60573d8160223d3973), shr(96, shl(96, beacon)))) + instance := create(value, 0x07, 0x79) + if iszero(instance) { + mstore(0x00, 0x30116425) // `DeploymentFailed()`. + revert(0x1c, 0x04) + } + mstore(0x40, m) // Restore the free memory pointer. + mstore(0x60, 0) // Restore the zero slot. + } + } + + /// @dev Deploys a deterministic ERC1967I beacon proxy with `salt`. + function deployDeterministicERC1967IBeaconProxy(address beacon, bytes32 salt) + internal + returns (address instance) + { + instance = deployDeterministicERC1967IBeaconProxy(0, beacon, salt); + } + + /// @dev Deploys a deterministic ERC1967I beacon proxy with `salt`. + /// Deposits `value` ETH during deployment. + function deployDeterministicERC1967IBeaconProxy(uint256 value, address beacon, bytes32 salt) + internal + returns (address instance) + { + /// @solidity memory-safe-assembly + assembly { + let m := mload(0x40) // Cache the free memory pointer. + mstore(0x60, 0x3d50545afa361460525736515af43d600060013e6052573d6001fd5b3d6001f3) + mstore(0x40, 0x527fa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b3513) + mstore(0x20, 0x60195155f3363d3d373d3d363d602036600436635c60da1b60e01b36) + mstore(0x04, or(shl(160, 0x60573d8160223d3973), shr(96, shl(96, beacon)))) + instance := create2(value, 0x07, 0x79, salt) + if iszero(instance) { + mstore(0x00, 0x30116425) // `DeploymentFailed()`. + revert(0x1c, 0x04) + } + mstore(0x40, m) // Restore the free memory pointer. + mstore(0x60, 0) // Restore the zero slot. + } + } + + /// @dev Creates a deterministic ERC1967I beacon proxy with `salt`. + /// Note: This method is intended for use in ERC4337 factories, + /// which are expected to NOT revert if the proxy is already deployed. + function createDeterministicERC1967IBeaconProxy(address beacon, bytes32 salt) + internal + returns (bool alreadyDeployed, address instance) + { + return createDeterministicERC1967IBeaconProxy(0, beacon, salt); + } + + /// @dev Creates a deterministic ERC1967I beacon proxy with `salt`. + /// Deposits `value` ETH during deployment. + /// Note: This method is intended for use in ERC4337 factories, + /// which are expected to NOT revert if the proxy is already deployed. + function createDeterministicERC1967IBeaconProxy(uint256 value, address beacon, bytes32 salt) + internal + returns (bool alreadyDeployed, address instance) + { + /// @solidity memory-safe-assembly + assembly { + let m := mload(0x40) // Cache the free memory pointer. + mstore(0x60, 0x3d50545afa361460525736515af43d600060013e6052573d6001fd5b3d6001f3) + mstore(0x40, 0x527fa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b3513) + mstore(0x20, 0x60195155f3363d3d373d3d363d602036600436635c60da1b60e01b36) + mstore(0x04, or(shl(160, 0x60573d8160223d3973), shr(96, shl(96, beacon)))) + // Compute and store the bytecode hash. + mstore(add(m, 0x35), keccak256(0x07, 0x79)) + mstore(m, shl(88, address())) + mstore8(m, 0xff) // Write the prefix. + mstore(add(m, 0x15), salt) + instance := keccak256(m, 0x55) + for {} 1 {} { + if iszero(extcodesize(instance)) { + instance := create2(value, 0x07, 0x79, salt) + if iszero(instance) { + mstore(0x00, 0x30116425) // `DeploymentFailed()`. + revert(0x1c, 0x04) + } + break + } + alreadyDeployed := 1 + if iszero(value) { break } + if iszero(call(gas(), instance, value, codesize(), 0x00, codesize(), 0x00)) { + mstore(0x00, 0xb12d13eb) // `ETHTransferFailed()`. + revert(0x1c, 0x04) + } + break + } + mstore(0x40, m) // Restore the free memory pointer. + mstore(0x60, 0) // Restore the zero slot. + } + } + + /// @dev Returns the initialization code of the ERC1967I beacon proxy. + function initCodeERC1967IBeaconProxy(address beacon) internal pure returns (bytes memory c) { + /// @solidity memory-safe-assembly + assembly { + c := mload(0x40) + mstore(add(c, 0x79), 0x3d50545afa361460525736515af43d600060013e6052573d6001fd5b3d6001f3) + mstore(add(c, 0x59), 0x527fa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b3513) + mstore(add(c, 0x39), 0x60195155f3363d3d373d3d363d602036600436635c60da1b60e01b36) + mstore(add(c, 0x1d), beacon) + mstore(add(c, 0x09), 0x60573d8160223d3973) + mstore(add(c, 0x99), 0) + mstore(c, 0x79) // Store the length. + mstore(0x40, add(c, 0xa0)) // Allocate memory. + } + } + + /// @dev Returns the initialization code hash of the ERC1967I beacon proxy. + function initCodeHashERC1967IBeaconProxy(address beacon) internal pure returns (bytes32 hash) { + /// @solidity memory-safe-assembly + assembly { + let m := mload(0x40) // Cache the free memory pointer. + mstore(0x60, 0x3d50545afa361460525736515af43d600060013e6052573d6001fd5b3d6001f3) + mstore(0x40, 0x527fa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b3513) + mstore(0x20, 0x60195155f3363d3d373d3d363d602036600436635c60da1b60e01b36) + mstore(0x04, or(shl(160, 0x60573d8160223d3973), shr(96, shl(96, beacon)))) + hash := keccak256(0x07, 0x79) + mstore(0x40, m) // Restore the free memory pointer. + mstore(0x60, 0) // Restore the zero slot. + } + } + + /// @dev Returns the address of the ERC1967I beacon proxy, with `salt` by `deployer`. + /// Note: The returned result has dirty upper 96 bits. Please clean if used in assembly. + function predictDeterministicAddressERC1967IBeaconProxy( + address beacon, + bytes32 salt, + address deployer + ) internal pure returns (address predicted) { + bytes32 hash = initCodeHashERC1967IBeaconProxy(beacon); + predicted = predictDeterministicAddress(hash, salt, deployer); + } + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* ERC1967I BEACON PROXY WITH IMMUTABLE ARGS OPERATIONS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev Deploys a ERC1967I beacon proxy with `args. + function deployERC1967IBeaconProxy(address beacon, bytes memory args) + internal + returns (address instance) + { + instance = deployERC1967IBeaconProxy(0, beacon, args); + } + + /// @dev Deploys a ERC1967I beacon proxy with `args. + /// Deposits `value` ETH during deployment. + function deployERC1967IBeaconProxy(uint256 value, address beacon, bytes memory args) + internal + returns (address instance) + { + /// @solidity memory-safe-assembly + assembly { + let m := mload(0x40) // Cache the free memory pointer. + let n := mload(args) + pop(staticcall(gas(), 4, add(args, 0x20), n, add(m, 0x90), n)) + mstore(add(m, 0x70), 0x3d50545afa361460525736515af43d600060013e6052573d6001fd5b3d6001f3) + mstore(add(m, 0x50), 0x527fa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b3513) + mstore(add(m, 0x30), 0x60195155f3363d3d373d3d363d602036600436635c60da1b60e01b36) + mstore(add(m, 0x14), beacon) + // Do a out-of-gas revert if `n` is greater than `0xffff - 0x57 = 0xffa8`. + mstore(add(m, gt(n, 0xffa8)), add(0xfe6100573d8160233d3973, shl(56, n))) + instance := create(value, add(m, 0x16), add(n, 0x7a)) + if iszero(instance) { + mstore(0x00, 0x30116425) // `DeploymentFailed()`. + revert(0x1c, 0x04) + } + } + } + + /// @dev Deploys a deterministic ERC1967I beacon proxy with `args` and `salt`. + function deployDeterministicERC1967IBeaconProxy(address beacon, bytes memory args, bytes32 salt) + internal + returns (address instance) + { + instance = deployDeterministicERC1967IBeaconProxy(0, beacon, args, salt); + } + + /// @dev Deploys a deterministic ERC1967I beacon proxy with `args` and `salt`. + /// Deposits `value` ETH during deployment. + function deployDeterministicERC1967IBeaconProxy( + uint256 value, + address beacon, + bytes memory args, + bytes32 salt + ) internal returns (address instance) { + /// @solidity memory-safe-assembly + assembly { + let m := mload(0x40) // Cache the free memory pointer. + let n := mload(args) + pop(staticcall(gas(), 4, add(args, 0x20), n, add(m, 0x90), n)) + mstore(add(m, 0x70), 0x3d50545afa361460525736515af43d600060013e6052573d6001fd5b3d6001f3) + mstore(add(m, 0x50), 0x527fa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b3513) + mstore(add(m, 0x30), 0x60195155f3363d3d373d3d363d602036600436635c60da1b60e01b36) + mstore(add(m, 0x14), beacon) + // Do a out-of-gas revert if `n` is greater than `0xffff - 0x57 = 0xffa8`. + mstore(add(m, gt(n, 0xffa8)), add(0xfe6100573d8160233d3973, shl(56, n))) + instance := create2(value, add(m, 0x16), add(n, 0x7a), salt) + if iszero(instance) { + mstore(0x00, 0x30116425) // `DeploymentFailed()`. + revert(0x1c, 0x04) + } + } + } + + /// @dev Creates a deterministic ERC1967I beacon proxy with `args` and `salt`. + /// Note: This method is intended for use in ERC4337 factories, + /// which are expected to NOT revert if the proxy is already deployed. + function createDeterministicERC1967IBeaconProxy(address beacon, bytes memory args, bytes32 salt) + internal + returns (bool alreadyDeployed, address instance) + { + return createDeterministicERC1967IBeaconProxy(0, beacon, args, salt); + } + + /// @dev Creates a deterministic ERC1967I beacon proxy with `args` and `salt`. + /// Deposits `value` ETH during deployment. + /// Note: This method is intended for use in ERC4337 factories, + /// which are expected to NOT revert if the proxy is already deployed. + function createDeterministicERC1967IBeaconProxy( + uint256 value, + address beacon, + bytes memory args, + bytes32 salt + ) internal returns (bool alreadyDeployed, address instance) { + /// @solidity memory-safe-assembly + assembly { + let m := mload(0x40) + let n := mload(args) + pop(staticcall(gas(), 4, add(args, 0x20), n, add(m, 0x90), n)) + mstore(add(m, 0x70), 0x3d50545afa361460525736515af43d600060013e6052573d6001fd5b3d6001f3) + mstore(add(m, 0x50), 0x527fa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b3513) + mstore(add(m, 0x30), 0x60195155f3363d3d373d3d363d602036600436635c60da1b60e01b36) + mstore(add(m, 0x14), beacon) + // Do a out-of-gas revert if `n` is greater than `0xffff - 0x57 = 0xffa8`. + mstore(add(m, gt(n, 0xffa8)), add(0xfe6100573d8160233d3973, shl(56, n))) + // Compute and store the bytecode hash. + mstore8(0x00, 0xff) // Write the prefix. + mstore(0x35, keccak256(add(m, 0x16), add(n, 0x7a))) + mstore(0x01, shl(96, address())) + mstore(0x15, salt) + instance := keccak256(0x00, 0x55) + for {} 1 {} { + if iszero(extcodesize(instance)) { + instance := create2(value, add(m, 0x16), add(n, 0x7a), salt) + if iszero(instance) { + mstore(0x00, 0x30116425) // `DeploymentFailed()`. + revert(0x1c, 0x04) + } + break + } + alreadyDeployed := 1 + if iszero(value) { break } + if iszero(call(gas(), instance, value, codesize(), 0x00, codesize(), 0x00)) { + mstore(0x00, 0xb12d13eb) // `ETHTransferFailed()`. + revert(0x1c, 0x04) + } + break + } + mstore(0x35, 0) // Restore the overwritten part of the free memory pointer. + } + } + + /// @dev Returns the initialization code of the ERC1967I beacon proxy with `args`. + function initCodeERC1967IBeaconProxy(address beacon, bytes memory args) + internal + pure + returns (bytes memory c) + { + /// @solidity memory-safe-assembly + assembly { + c := mload(0x40) + let n := mload(args) + // Do a out-of-gas revert if `n` is greater than `0xffff - 0x57 = 0xffa8`. + returndatacopy(returndatasize(), returndatasize(), gt(n, 0xffa8)) + for { let i := 0 } lt(i, n) { i := add(i, 0x20) } { + mstore(add(add(c, 0x9a), i), mload(add(add(args, 0x20), i))) + } + mstore(add(c, 0x7a), 0x3d50545afa361460525736515af43d600060013e6052573d6001fd5b3d6001f3) + mstore(add(c, 0x5a), 0x527fa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b3513) + mstore(add(c, 0x3a), 0x60195155f3363d3d373d3d363d602036600436635c60da1b60e01b36) + mstore(add(c, 0x1e), beacon) + mstore(add(c, 0x0a), add(0x6100573d8160233d3973, shl(56, n))) + mstore(add(c, add(n, 0x9a)), 0) + mstore(c, add(n, 0x7a)) // Store the length. + mstore(0x40, add(c, add(n, 0xba))) // Allocate memory. + } + } + + /// @dev Returns the initialization code hash of the ERC1967I beacon proxy with `args`. + function initCodeHashERC1967IBeaconProxy(address beacon, bytes memory args) + internal + pure + returns (bytes32 hash) + { + /// @solidity memory-safe-assembly + assembly { + let c := mload(0x40) // Cache the free memory pointer. + let n := mload(args) + // Do a out-of-gas revert if `n` is greater than `0xffff - 0x57 = 0xffa8`. + returndatacopy(returndatasize(), returndatasize(), gt(n, 0xffa8)) + for { let i := 0 } lt(i, n) { i := add(i, 0x20) } { + mstore(add(add(c, 0x90), i), mload(add(add(args, 0x20), i))) + } + mstore(add(c, 0x70), 0x3d50545afa361460525736515af43d600060013e6052573d6001fd5b3d6001f3) + mstore(add(c, 0x50), 0x527fa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b3513) + mstore(add(c, 0x30), 0x60195155f3363d3d373d3d363d602036600436635c60da1b60e01b36) + mstore(add(c, 0x14), beacon) + mstore(c, add(0x6100573d8160233d3973, shl(56, n))) + hash := keccak256(add(c, 0x16), add(n, 0x7a)) + } + } + + /// @dev Returns the address of the ERC1967I beacon proxy, with `args` and salt` by `deployer`. + /// Note: The returned result has dirty upper 96 bits. Please clean if used in assembly. + function predictDeterministicAddressERC1967IBeaconProxy( + address beacon, + bytes memory args, + bytes32 salt, + address deployer + ) internal pure returns (address predicted) { + bytes32 hash = initCodeHashERC1967IBeaconProxy(beacon, args); + predicted = predictDeterministicAddress(hash, salt, deployer); + } + + /// @dev Equivalent to `argsOnERC1967IBeaconProxy(instance, start, 2 ** 256 - 1)`. + function argsOnERC1967IBeaconProxy(address instance) + internal + view + returns (bytes memory args) + { + /// @solidity memory-safe-assembly + assembly { + args := mload(0x40) + mstore(args, and(0xffffffffff, sub(extcodesize(instance), 0x57))) // Store the length. + extcodecopy(instance, add(args, 0x20), 0x57, add(mload(args), 0x20)) + mstore(0x40, add(mload(args), add(args, 0x40))) // Allocate memory. + } + } + + /// @dev Equivalent to `argsOnERC1967IBeaconProxy(instance, start, 2 ** 256 - 1)`. + function argsOnERC1967IBeaconProxy(address instance, uint256 start) + internal + view + returns (bytes memory args) + { + /// @solidity memory-safe-assembly + assembly { + args := mload(0x40) + let n := and(0xffffffffff, sub(extcodesize(instance), 0x57)) + extcodecopy(instance, add(args, 0x20), add(start, 0x57), add(n, 0x20)) + mstore(args, mul(sub(n, start), lt(start, n))) // Store the length. + mstore(0x40, add(args, add(0x40, mload(args)))) // Allocate memory. + } + } + + /// @dev Returns a slice of the immutable arguments on `instance` from `start` to `end`. + /// `start` and `end` will be clamped to the range `[0, args.length]`. + /// The `instance` MUST be deployed via the ERC1967I beacon proxy with immutable args functions. + /// Otherwise, the behavior is undefined. + /// Out-of-gas reverts if `instance` does not have any code. + function argsOnERC1967IBeaconProxy(address instance, uint256 start, uint256 end) + internal + view + returns (bytes memory args) + { + /// @solidity memory-safe-assembly + assembly { + args := mload(0x40) + if iszero(lt(end, 0xffff)) { end := 0xffff } + let d := mul(sub(end, start), lt(start, end)) + extcodecopy(instance, args, add(start, 0x37), add(d, 0x20)) + if iszero(and(0xff, mload(add(args, d)))) { + let n := sub(extcodesize(instance), 0x57) + returndatacopy(returndatasize(), returndatasize(), shr(40, n)) + d := mul(gt(n, start), sub(d, mul(gt(end, n), sub(end, n)))) + } + mstore(args, d) // Store the length. + mstore(add(add(args, 0x20), d), 0) // Zeroize the slot after the bytes. + mstore(0x40, add(add(args, 0x40), d)) // Allocate memory. + } + } + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* OTHER OPERATIONS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev Returns `address(0)` if the implementation address cannot be determined. + function implementationOf(address instance) internal view returns (address result) { + /// @solidity memory-safe-assembly + assembly { + for { extcodecopy(instance, 0x00, 0x00, 0x57) } 1 {} { + if mload(0x2d) { + // ERC1967I and ERC1967IBeaconProxy detection. + if or( + eq(keccak256(0x00, 0x52), ERC1967I_CODE_HASH), + eq(keccak256(0x00, 0x57), ERC1967I_BEACON_PROXY_CODE_HASH) + ) { + pop(staticcall(gas(), instance, 0x00, 0x01, 0x00, 0x20)) + result := mload(0x0c) + break + } + } + // 0age clone detection. + result := mload(0x0b) + codecopy(0x0b, codesize(), 0x14) // Zeroize the 20 bytes for the address. + if iszero(xor(keccak256(0x00, 0x2c), CLONE_CODE_HASH)) { break } + mstore(0x0b, result) // Restore the zeroized memory. + // CWIA detection. + result := mload(0x0a) + codecopy(0x0a, codesize(), 0x14) // Zeroize the 20 bytes for the address. + if iszero(xor(keccak256(0x00, 0x2d), CWIA_CODE_HASH)) { break } + mstore(0x0a, result) // Restore the zeroized memory. + // PUSH0 clone detection. + result := mload(0x09) + codecopy(0x09, codesize(), 0x14) // Zeroize the 20 bytes for the address. + result := shr(xor(keccak256(0x00, 0x2d), PUSH0_CLONE_CODE_HASH), result) + break + } + result := shr(96, result) + mstore(0x37, 0) // Restore the overwritten part of the free memory pointer. + } + } + + /// @dev Returns the address when a contract with initialization code hash, + /// `hash`, is deployed with `salt`, by `deployer`. + /// Note: The returned result has dirty upper 96 bits. Please clean if used in assembly. + function predictDeterministicAddress(bytes32 hash, bytes32 salt, address deployer) + internal + pure + returns (address predicted) + { + /// @solidity memory-safe-assembly + assembly { + // Compute and store the bytecode hash. + mstore8(0x00, 0xff) // Write the prefix. + mstore(0x35, hash) + mstore(0x01, shl(96, deployer)) + mstore(0x15, salt) + predicted := keccak256(0x00, 0x55) + mstore(0x35, 0) // Restore the overwritten part of the free memory pointer. + } + } + + /// @dev Requires that `salt` starts with either the zero address or `by`. + function checkStartsWith(bytes32 salt, address by) internal pure { + /// @solidity memory-safe-assembly + assembly { + // If the salt does not start with the zero address or `by`. + if iszero(or(iszero(shr(96, salt)), eq(shr(96, shl(96, by)), shr(96, salt)))) { + mstore(0x00, 0x0c4549ef) // `SaltDoesNotStartWith()`. + revert(0x1c, 0x04) + } + } + } + + /// @dev Returns the `bytes32` at `offset` in `args`, without any bounds checks. + /// To load an address, you can use `address(bytes20(argLoad(args, offset)))`. + function argLoad(bytes memory args, uint256 offset) internal pure returns (bytes32 result) { + /// @solidity memory-safe-assembly + assembly { + result := mload(add(add(args, 0x20), offset)) + } + } +} diff --git a/biconomy/nexus/1.0.2/node_modules/solady/src/utils/UUPSUpgradeable.sol b/biconomy/nexus/1.0.2/node_modules/solady/src/utils/UUPSUpgradeable.sol new file mode 100644 index 0000000..124c3a3 --- /dev/null +++ b/biconomy/nexus/1.0.2/node_modules/solady/src/utils/UUPSUpgradeable.sol @@ -0,0 +1,105 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.4; + +import {CallContextChecker} from "./CallContextChecker.sol"; + +/// @notice UUPS proxy mixin. +/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/UUPSUpgradeable.sol) +/// @author Modified from OpenZeppelin +/// (https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/proxy/utils/UUPSUpgradeable.sol) +/// +/// @dev Note: +/// - This implementation is intended to be used with ERC1967 proxies. +/// See: `LibClone.deployERC1967` and related functions. +/// - This implementation is NOT compatible with legacy OpenZeppelin proxies +/// which do not store the implementation at `_ERC1967_IMPLEMENTATION_SLOT`. +abstract contract UUPSUpgradeable is CallContextChecker { + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* CUSTOM ERRORS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev The upgrade failed. + error UpgradeFailed(); + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* EVENTS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev Emitted when the proxy's implementation is upgraded. + event Upgraded(address indexed implementation); + + /// @dev `keccak256(bytes("Upgraded(address)"))`. + uint256 private constant _UPGRADED_EVENT_SIGNATURE = + 0xbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b; + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* STORAGE */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev The ERC-1967 storage slot for the implementation in the proxy. + /// `uint256(keccak256("eip1967.proxy.implementation")) - 1`. + bytes32 internal constant _ERC1967_IMPLEMENTATION_SLOT = + 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc; + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* UUPS OPERATIONS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev Please override this function to check if `msg.sender` is authorized + /// to upgrade the proxy to `newImplementation`, reverting if not. + /// ``` + /// function _authorizeUpgrade(address) internal override onlyOwner {} + /// ``` + function _authorizeUpgrade(address newImplementation) internal virtual; + + /// @dev Returns the storage slot used by the implementation, + /// as specified in [ERC1822](https://eips.ethereum.org/EIPS/eip-1822). + /// + /// Note: The `notDelegated` modifier prevents accidental upgrades to + /// an implementation that is a proxy contract. + function proxiableUUID() public view virtual notDelegated returns (bytes32) { + // This function must always return `_ERC1967_IMPLEMENTATION_SLOT` to comply with ERC1967. + return _ERC1967_IMPLEMENTATION_SLOT; + } + + /// @dev Upgrades the proxy's implementation to `newImplementation`. + /// Emits a {Upgraded} event. + /// + /// Note: Passing in empty `data` skips the delegatecall to `newImplementation`. + function upgradeToAndCall(address newImplementation, bytes calldata data) + public + payable + virtual + onlyProxy + { + _authorizeUpgrade(newImplementation); + /// @solidity memory-safe-assembly + assembly { + newImplementation := shr(96, shl(96, newImplementation)) // Clears upper 96 bits. + mstore(0x00, returndatasize()) + mstore(0x01, 0x52d1902d) // `proxiableUUID()`. + let s := _ERC1967_IMPLEMENTATION_SLOT + // Check if `newImplementation` implements `proxiableUUID` correctly. + if iszero(eq(mload(staticcall(gas(), newImplementation, 0x1d, 0x04, 0x01, 0x20)), s)) { + mstore(0x01, 0x55299b49) // `UpgradeFailed()`. + revert(0x1d, 0x04) + } + // Emit the {Upgraded} event. + log2(codesize(), 0x00, _UPGRADED_EVENT_SIGNATURE, newImplementation) + sstore(s, newImplementation) // Updates the implementation. + + // Perform a delegatecall to `newImplementation` if `data` is non-empty. + if data.length { + // Forwards the `data` to `newImplementation` via delegatecall. + let m := mload(0x40) + calldatacopy(m, data.offset, data.length) + if iszero(delegatecall(gas(), newImplementation, m, data.length, codesize(), 0x00)) + { + // Bubble up the revert if the call reverts. + returndatacopy(m, 0x00, returndatasize()) + revert(m, returndatasize()) + } + } + } + } +} diff --git a/biconomy/nexus/1.0.2/remappings.txt b/biconomy/nexus/1.0.2/remappings.txt new file mode 100644 index 0000000..a450945 --- /dev/null +++ b/biconomy/nexus/1.0.2/remappings.txt @@ -0,0 +1,15 @@ +@openzeppelin/=node_modules/@openzeppelin/ +forge-std/=node_modules/forge-std/src/ +account-abstraction/=node_modules/account-abstraction/contracts/ +solady/=node_modules/solady/src/ +excessively-safe-call/=node_modules/excessively-safe-call/src/ +sentinellist/=node_modules/sentinellist/src/ +solarray/=node_modules/solarray/src/ +erc7579/=node_modules/erc7579/src/ +erc7739Validator/=node_modules/erc7739-validator-base/src/ +lib-prep/=node_modules/prep/src/ +composability/=node_modules/@biconomy/composability/contracts/ +openzeppelin/=node_modules/@openzeppelin/contracts/ +rlp-reader/=node_modules/Solidity-RLP/contracts/ +byteslib/=node_modules/solidity-bytes-utils/contracts/ +EnumerableSet4337/=node_modules/enumerablemap4337/src/ diff --git a/biconomy/nexus/1.2.0/cannonfile.toml b/biconomy/nexus/1.2.0/cannonfile.toml new file mode 100644 index 0000000..b948269 --- /dev/null +++ b/biconomy/nexus/1.2.0/cannonfile.toml @@ -0,0 +1,44 @@ +name = "biconomy-nexus" +version = "1.2.0" +description = "Nexus ERC-4337 infrastructure" + +[var.Settings] +owner = "0x129443cA2a9Dec2020808a2868b38dDA457eaCC7" +initData = "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee" + +[pull.entryPoint] +source = "eth-infinitism-entrypoint:0.7.0" + +[pull.validator] +source = "biconomy-mee:0.0.4" + +[deploy.Nexus] +artifact = "Nexus" +args = [ + "<%= entryPoint.EntryPoint.address %>", + "<%= validator.K1MeeValidator.address %>", + "<%= settings.initData %>" +] +create2 = true +salt = "0x0000000000000000000000000000000000000000f036b687a1f82003988bd953" +ifExists = "continue" + +[deploy.NexusAccountFactory] +artifact = "NexusAccountFactory" +args = [ + "<%= contracts.Nexus.address %>", + "<%= settings.owner %>" +] +create2 = true +salt = "0x0000000000000000000000000000000000000000e289724d34a3660389fc1ab0" +ifExists = "continue" + +[deploy.NexusBootstrap] +artifact = "NexusBootstrap" +args = [ + "<%= validator.K1MeeValidator.address %>", + "<%= settings.initData %>" +] +create2 = true +salt = "0x000000000000000000000000000000000000000069b8a8e8fec67700be0ca325" +ifExists = "continue" diff --git a/biconomy/nexus/1.2.0/contracts/Nexus.sol b/biconomy/nexus/1.2.0/contracts/Nexus.sol new file mode 100644 index 0000000..eba500a --- /dev/null +++ b/biconomy/nexus/1.2.0/contracts/Nexus.sol @@ -0,0 +1,576 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.27; + +// ────────────────────────────────────────────────────────────────────────────── +// _ __ _ __ +// / | / /__ | |/ /_ _______ +// / |/ / _ \| / / / / ___/ +// / /| / __/ / /_/ (__ ) +// /_/ |_/\___/_/|_\__,_/____/ +// +// ────────────────────────────────────────────────────────────────────────────── +// Nexus: A suite of contracts for Modular Smart Accounts compliant with ERC-7579 and ERC-4337, developed by Biconomy. +// Learn more at https://biconomy.io. To report security issues, please contact us at: security@biconomy.io + +import { UUPSUpgradeable } from "solady/utils/UUPSUpgradeable.sol"; +import { PackedUserOperation } from "account-abstraction/interfaces/PackedUserOperation.sol"; +import { ExecLib } from "./lib/ExecLib.sol"; +import { INexus } from "./interfaces/INexus.sol"; +import { BaseAccount } from "./base/BaseAccount.sol"; +import { IERC7484 } from "./interfaces/IERC7484.sol"; +import { ModuleManager } from "./base/ModuleManager.sol"; +import { ExecutionHelper } from "./base/ExecutionHelper.sol"; +import { IValidator } from "./interfaces/modules/IValidator.sol"; +import { + MODULE_TYPE_VALIDATOR, + MODULE_TYPE_EXECUTOR, + MODULE_TYPE_FALLBACK, + MODULE_TYPE_HOOK, + MODULE_TYPE_MULTI, + MODULE_TYPE_PREVALIDATION_HOOK_ERC1271, + MODULE_TYPE_PREVALIDATION_HOOK_ERC4337, + SUPPORTS_ERC7739, + VALIDATION_SUCCESS, + VALIDATION_FAILED +} from "./types/Constants.sol"; +import { + ModeLib, + ExecutionMode, + ExecType, + CallType, + CALLTYPE_BATCH, + CALLTYPE_SINGLE, + CALLTYPE_DELEGATECALL, + EXECTYPE_DEFAULT, + EXECTYPE_TRY +} from "./lib/ModeLib.sol"; +import { NonceLib } from "./lib/NonceLib.sol"; +import { SentinelListLib, SENTINEL, ZERO_ADDRESS } from "sentinellist/SentinelList.sol"; +import { Initializable } from "./lib/Initializable.sol"; +import { EmergencyUninstall } from "./types/DataTypes.sol"; +import { LibPREP } from "lib-prep/LibPREP.sol"; +import { ComposableExecutionBase, ComposableExecution } from "composability/ComposableExecutionBase.sol"; + +/// @title Nexus - Smart Account +/// @notice This contract integrates various functionalities to handle modular smart accounts compliant with ERC-7579 and ERC-4337 standards. +/// @dev Comprehensive suite of methods for managing smart accounts, integrating module management, execution management, and upgradability via UUPS. +/// @author @livingrockrises | Biconomy | chirag@biconomy.io +/// @author @aboudjem | Biconomy | adam.boudjemaa@biconomy.io +/// @author @filmakarov | Biconomy | filipp.makarov@biconomy.io +/// @author @zeroknots | Rhinestone.wtf | zeroknots.eth +/// Special thanks to the Solady team for foundational contributions: https://github.com/Vectorized/solady +contract Nexus is INexus, BaseAccount, ExecutionHelper, ModuleManager, UUPSUpgradeable, ComposableExecutionBase { + using ModeLib for ExecutionMode; + using ExecLib for bytes; + using NonceLib for uint256; + using SentinelListLib for SentinelListLib.SentinelList; + + /// @dev The timelock period for emergency hook uninstallation. + uint256 internal constant _EMERGENCY_TIMELOCK = 1 days; + + /// @dev The event emitted when an emergency hook uninstallation is initiated. + event EmergencyHookUninstallRequest(address hook, uint256 timestamp); + + /// @dev The event emitted when an emergency hook uninstallation request is reset. + event EmergencyHookUninstallRequestReset(address hook, uint256 timestamp); + + /// @notice Initializes the smart account with the specified entry point. + constructor( + address anEntryPoint, + address defaultValidator, + bytes memory initData + ) + ModuleManager(defaultValidator, initData) + { + require(address(anEntryPoint) != address(0), EntryPointCanNotBeZero()); + _ENTRYPOINT = anEntryPoint; + } + + /// @notice Validates a user operation against a specified validator, extracted from the operation's nonce. + /// @param op The user operation to validate, encapsulating all transaction details. + /// @param userOpHash Hash of the user operation data, used for signature validation. + /// @param missingAccountFunds Funds missing from the account's deposit necessary for transaction execution. + /// This can be zero if covered by a paymaster or if sufficient deposit exists. + /// @return validationData Encoded validation result or failure, propagated from the validator module. + /// - Encoded format in validationData: + /// - First 20 bytes: Address of the Validator module, to which the validation task is forwarded. + /// The validator module returns: + /// - `SIG_VALIDATION_SUCCESS` (0) indicates successful validation. + /// - `SIG_VALIDATION_FAILED` (1) indicates signature validation failure. + /// @dev Expects the validator's address to be encoded in the upper 96 bits of the user operation's nonce. + /// This method forwards the validation task to the extracted validator module address. + /// @dev The entryPoint calls this function. If validation fails, it returns `VALIDATION_FAILED` (1) otherwise `0`. + /// @dev Features Module Enable Mode. + /// This Module Enable Mode flow is intended for the module acting as the validator + /// for the user operation that triggers the Module Enable Flow. Otherwise, a call to + /// `Nexus.installModule` should be included in `userOp.callData`. + function validateUserOp( + PackedUserOperation calldata op, + bytes32 userOpHash, + uint256 missingAccountFunds + ) + external + virtual + payPrefund(missingAccountFunds) + onlyEntryPoint + returns (uint256 validationData) + { + address validator; + PackedUserOperation memory userOp = op; + + if (op.nonce.isValidateMode()) { + // do nothing special. This is introduced + // to quickly identify the most commonly used + // mode which is validate mode + // and avoid checking two above conditions + } else if (op.nonce.isModuleEnableMode()) { + // if it is module enable mode, we need to enable the module first + // and get the cleaned signature + userOp.signature = _enableMode(userOpHash, op.signature); + } else if (op.nonce.isPrepMode()) { + // PREP Mode. Authorize prep signature + // and initialize the account + // PREP mode is only used for the uninited PREPs + require(!isInitialized(), AccountAlreadyInitialized()); + bytes calldata initData; + (userOp.signature, initData) = _handlePREP(op.signature); + _initializeAccount(initData); + } + validator = _handleValidator(op.nonce.getValidator()); + (userOpHash, userOp.signature) = _withPreValidationHook(userOpHash, userOp, missingAccountFunds); + validationData = IValidator(validator).validateUserOp(userOp, userOpHash); + } + + /// @notice Executes transactions in single or batch modes as specified by the execution mode. + /// @param mode The execution mode detailing how transactions should be handled (single, batch, default, try/catch). + /// @param executionCalldata The encoded transaction data to execute. + /// @dev This function handles transaction execution flexibility and is protected by the `onlyEntryPoint` modifier. + /// @dev This function also goes through hook checks via withHook modifier. + function execute(ExecutionMode mode, bytes calldata executionCalldata) external payable onlyEntryPoint withHook { + (CallType callType, ExecType execType) = mode.decodeBasic(); + if (callType == CALLTYPE_SINGLE) { + _handleSingleExecution(executionCalldata, execType); + } else if (callType == CALLTYPE_BATCH) { + _handleBatchExecution(executionCalldata, execType); + } else if (callType == CALLTYPE_DELEGATECALL) { + _handleDelegateCallExecution(executionCalldata, execType); + } else { + revert UnsupportedCallType(callType); + } + } + + /// @notice Executes transactions from an executor module, supporting both single and batch transactions. + /// @param mode The execution mode (single or batch, default or try). + /// @param executionCalldata The transaction data to execute. + /// @return returnData The results of the transaction executions, which may include errors in try mode. + /// @dev This function is callable only by an executor module and goes through hook checks. + function executeFromExecutor( + ExecutionMode mode, + bytes calldata executionCalldata + ) + external + payable + onlyExecutorModule + withHook + withRegistry(msg.sender, MODULE_TYPE_EXECUTOR) + returns (bytes[] memory returnData) + { + (CallType callType, ExecType execType) = mode.decodeBasic(); + // check if calltype is batch or single or delegate call + if (callType == CALLTYPE_SINGLE) { + returnData = _handleSingleExecutionAndReturnData(executionCalldata, execType); + } else if (callType == CALLTYPE_BATCH) { + returnData = _handleBatchExecutionAndReturnData(executionCalldata, execType); + } else if (callType == CALLTYPE_DELEGATECALL) { + returnData = _handleDelegateCallExecutionAndReturnData(executionCalldata, execType); + } else { + revert UnsupportedCallType(callType); + } + } + + /// @notice Executes a user operation via a call using the contract's context. + /// @param userOp The user operation to execute, containing transaction details. + /// @param - Hash of the user operation. + /// @dev Only callable by the EntryPoint. Decodes the user operation calldata, skipping the first four bytes, and executes the inner call. + function executeUserOp(PackedUserOperation calldata userOp, bytes32) external payable virtual onlyEntryPoint withHook { + bytes calldata callData = userOp.callData[4:]; + (bool success, bytes memory innerCallRet) = address(this).delegatecall(callData); + if (!success) { + revert ExecutionFailed(); + } + } + + /// @notice Executes a composable execution + /// See more about composability here: https://docs.biconomy.io/composability + /// @param executions The composable executions to execute + function executeComposable(ComposableExecution[] calldata executions) external payable override onlyEntryPoint withHook { + _executeComposable(executions); + } + + /// @notice Executes a call to a target address with specified value and data. + /// @param to The address to execute the action on + /// @param value The value to send with the action + /// @param data The data to send with the action + /// @return result The result of the execution + function _executeAction(address to, uint256 value, bytes memory data) internal override returns (bytes memory) { + return _executeMemory(to, value, data); + } + + /// @notice Installs a new module to the smart account. + /// @param moduleTypeId The type identifier of the module being installed, which determines its role: + /// - 1 for Validator + /// - 2 for Executor + /// - 3 for Fallback + /// - 4 for Hook + /// - 8 for 1271 Prevalidation Hook + /// - 9 for 4337 Prevalidation Hook + /// @param module The address of the module to install. + /// @param initData Initialization data for the module. + /// @dev This function can only be called by the EntryPoint or the account itself for security reasons. + /// @dev This function goes through hook checks via withHook modifier through internal function _installModule. + function installModule(uint256 moduleTypeId, address module, bytes calldata initData) external payable virtual override onlyEntryPointOrSelf { + _installModule(moduleTypeId, module, initData); + emit ModuleInstalled(moduleTypeId, module); + } + + /// @notice Uninstalls a module from the smart account. + /// @param moduleTypeId The type ID of the module to be uninstalled, matching the installation type: + /// - 1 for Validator + /// - 2 for Executor + /// - 3 for Fallback + /// - 4 for Hook + /// - 8 for 1271 Prevalidation Hook + /// - 9 for 4337 Prevalidation Hook + /// @dev Attention: All the underlying functions _uninstall[ModuleType] are calling module.onInstall() method. + /// If the module is malicious (which is not likely because such a module won't be attested), it can prevent + /// itself from being uninstalled by spending all gas in the onUninstall() method. Then 1/64 gas left can + /// be not enough to finish the uninstallation, assuming there may be hook postCheck() call. + /// In this highly unlikely scenario, user will have to uninstall the hook, then uninstall the malicious + /// module => in this case 1/64 gas left should be enough to finish the uninstallation. + /// @param module The address of the module to uninstall. + /// @param deInitData De-initialization data for the module. + /// @dev Ensures that the operation is authorized and valid before proceeding with the uninstallation. + function uninstallModule( + uint256 moduleTypeId, + address module, + bytes calldata deInitData + ) + external + payable + onlyEntryPointOrSelf + withHook + { + require(_isModuleInstalled(moduleTypeId, module, deInitData), ModuleNotInstalled(moduleTypeId, module)); + + if (moduleTypeId == MODULE_TYPE_VALIDATOR) { + _uninstallValidator(module, deInitData); + _checkInitializedValidators(); + } else if (moduleTypeId == MODULE_TYPE_EXECUTOR) { + _uninstallExecutor(module, deInitData); + } else if (moduleTypeId == MODULE_TYPE_FALLBACK) { + _uninstallFallbackHandler(module, deInitData); + } else if ( + moduleTypeId == MODULE_TYPE_HOOK || moduleTypeId == MODULE_TYPE_PREVALIDATION_HOOK_ERC1271 || moduleTypeId == MODULE_TYPE_PREVALIDATION_HOOK_ERC4337 + ) { + _uninstallHook(module, moduleTypeId, deInitData); + } + emit ModuleUninstalled(moduleTypeId, module); + } + + function emergencyUninstallHook(EmergencyUninstall calldata data, bytes calldata signature) external payable { + // Validate the signature + _checkEmergencyUninstallSignature(data, signature); + // Parse uninstall data + (uint256 hookType, address hook, bytes calldata deInitData) = (data.hookType, data.hook, data.deInitData); + + // Validate the hook is of a supported type and is installed + require( + hookType == MODULE_TYPE_HOOK || hookType == MODULE_TYPE_PREVALIDATION_HOOK_ERC1271 || hookType == MODULE_TYPE_PREVALIDATION_HOOK_ERC4337, + UnsupportedModuleType(hookType) + ); + require(_isModuleInstalled(hookType, hook, deInitData), ModuleNotInstalled(hookType, hook)); + + // Get the account storage + AccountStorage storage accountStorage = _getAccountStorage(); + uint256 hookTimelock = accountStorage.emergencyUninstallTimelock[hook]; + + if (hookTimelock == 0) { + // if the timelock hasnt been initiated, initiate it + accountStorage.emergencyUninstallTimelock[hook] = block.timestamp; + emit EmergencyHookUninstallRequest(hook, block.timestamp); + } else if (block.timestamp >= hookTimelock + 3 * _EMERGENCY_TIMELOCK) { + // if the timelock has been left for too long, reset it + accountStorage.emergencyUninstallTimelock[hook] = block.timestamp; + emit EmergencyHookUninstallRequestReset(hook, block.timestamp); + } else if (block.timestamp >= hookTimelock + _EMERGENCY_TIMELOCK) { + // if the timelock expired, clear it and uninstall the hook + accountStorage.emergencyUninstallTimelock[hook] = 0; + _uninstallHook(hook, hookType, deInitData); + emit ModuleUninstalled(hookType, hook); + } else { + // if the timelock is initiated but not expired, revert + revert EmergencyTimeLockNotExpired(); + } + } + + /// @notice Initializes the smart account with the specified initialization data. + /// @param initData The initialization data for the smart account. + /// @dev This function can only be called by the account itself or the proxy factory. + /// When a 7702 account is created, the first userOp should contain self-call to initialize the account. + function initializeAccount(bytes calldata initData) external payable virtual { + // Protect this function to only be callable when used with the proxy factory or when + // account calls itself + if (msg.sender != address(this)) { + Initializable.requireInitializable(); + } + _initializeAccount(initData); + } + + function _initializeAccount(bytes calldata initData) internal { + require(initData.length >= 24, InvalidInitData()); + + address bootstrap; + bytes calldata bootstrapCall; + + assembly { + bootstrap := calldataload(initData.offset) + let s := calldataload(add(initData.offset, 0x20)) + let u := add(initData.offset, s) + bootstrapCall.offset := add(u, 0x20) + bootstrapCall.length := calldataload(u) + } + + (bool success, ) = bootstrap.delegatecall(bootstrapCall); + + require(success, NexusInitializationFailed()); + if(!_amIERC7702()) { + require(isInitialized(), AccountNotInitialized()); + } + } + + /// @notice Sets the registry for the smart account. + /// @param newRegistry The new registry to set. + /// @param attesters The attesters to set. + /// @param threshold The threshold to set. + /// @dev This function can only be called by the EntryPoint or the account itself. + function setRegistry(IERC7484 newRegistry, address[] calldata attesters, uint8 threshold) external payable { + require(msg.sender == address(this), AccountAccessUnauthorized()); + _configureRegistry(newRegistry, attesters, threshold); + } + + /// @notice Validates a signature according to ERC-1271 standards. + /// @param hash The hash of the data being validated. + /// @param signature Signature data that needs to be validated. + /// @return The status code of the signature validation (`0x1626ba7e` if valid). + /// bytes4(keccak256("isValidSignature(bytes32,bytes)") = 0x1626ba7e + /// @dev Delegates the validation to a validator module specified within the signature data. + function isValidSignature(bytes32 hash, bytes calldata signature) external view virtual override returns (bytes4) { + // Handle potential ERC7739 support detection request + if (signature.length == 0) { + // Forces the compiler to optimize for smaller bytecode size. + if (uint256(hash) == (~signature.length / 0xffff) * 0x7739) { + return checkERC7739Support(hash, signature); + } + } + // else proceed with normal signature verification + // First 20 bytes of data will be validator address and rest of the bytes is complete signature. + address validator = _handleValidator(address(bytes20(signature[0:20]))); + bytes memory signature_; + (hash, signature_) = _withPreValidationHook(hash, signature[20:]); + try IValidator(validator).isValidSignatureWithSender(msg.sender, hash, signature_) returns (bytes4 res) { + return res; + } catch { + return bytes4(0xffffffff); + } + } + + /// @notice Retrieves the address of the current implementation from the EIP-1967 slot. + /// @notice Checks the 1967 implementation slot, if not found then checks the slot defined by address (Biconomy V2 smart account) + /// @return implementation The address of the current contract implementation. + function getImplementation() external view returns (address implementation) { + assembly { + implementation := sload(_ERC1967_IMPLEMENTATION_SLOT) + } + if (implementation == address(0)) { + assembly { + implementation := sload(address()) + } + } + } + + /// @notice Checks if a specific module type is supported by this smart account. + /// @param moduleTypeId The identifier of the module type to check. + /// @return True if the module type is supported, false otherwise. + function supportsModule(uint256 moduleTypeId) external view virtual returns (bool) { + if (moduleTypeId == MODULE_TYPE_VALIDATOR || + moduleTypeId == MODULE_TYPE_EXECUTOR || + moduleTypeId == MODULE_TYPE_FALLBACK || + moduleTypeId == MODULE_TYPE_HOOK || + moduleTypeId == MODULE_TYPE_PREVALIDATION_HOOK_ERC1271 || + moduleTypeId == MODULE_TYPE_PREVALIDATION_HOOK_ERC4337 || + moduleTypeId == MODULE_TYPE_MULTI) + { + return true; + } + return false; + } + + /// @notice Determines if a specific execution mode is supported. + /// @param mode The execution mode to evaluate. + /// @return isSupported True if the execution mode is supported, false otherwise. + function supportsExecutionMode(ExecutionMode mode) external view virtual returns (bool isSupported) { + (CallType callType, ExecType execType) = mode.decodeBasic(); + + // Return true if both the call type and execution type are supported. + return (callType == CALLTYPE_SINGLE || callType == CALLTYPE_BATCH || callType == CALLTYPE_DELEGATECALL) + && (execType == EXECTYPE_DEFAULT || execType == EXECTYPE_TRY); + } + + /// @notice Determines whether a module is installed on the smart account. + /// @param moduleTypeId The ID corresponding to the type of module (Validator, Executor, Fallback, Hook). + /// @param module The address of the module to check. + /// @param additionalContext Optional context that may be needed for certain checks. + /// @return True if the module is installed, false otherwise. + function isModuleInstalled(uint256 moduleTypeId, address module, bytes calldata additionalContext) external view returns (bool) { + return _isModuleInstalled(moduleTypeId, module, additionalContext); + } + + /// @notice Checks if the smart account is initialized. + /// @return True if the smart account is initialized, false otherwise. + /// @dev In case default validator is initialized, two other SLOADS from _areSentinelListsInitialized() are not checked, + /// this method should not introduce huge gas overhead. + function isInitialized() public view returns (bool) { + return ( + IValidator(_DEFAULT_VALIDATOR).isInitialized(address(this)) || + _areSentinelListsInitialized() + ); + } + + /// Returns the account's implementation ID. + /// @return The unique identifier for this account implementation. + function accountId() external pure virtual returns (string memory) { + return _ACCOUNT_IMPLEMENTATION_ID; + } + + /// Upgrades the contract to a new implementation and calls a function on the new contract. + /// @notice Updates two slots 1. ERC1967 slot and + /// 2. address() slot in case if it's potentially upgraded earlier from Biconomy V2 account, + /// as Biconomy v2 Account (proxy) reads implementation from the slot that is defined by its address + /// @param newImplementation The address of the new contract implementation. + /// @param data The calldata to be sent to the new implementation. + function upgradeToAndCall(address newImplementation, bytes calldata data) public payable virtual override withHook { + require(newImplementation != address(0), InvalidImplementationAddress()); + bool res; + assembly { + res := gt(extcodesize(newImplementation), 0) + } + require(res, InvalidImplementationAddress()); + // update the address() storage slot as well. + assembly { + sstore(address(), newImplementation) + } + UUPSUpgradeable.upgradeToAndCall(newImplementation, data); + } + + /// @dev For automatic detection that the smart account supports the ERC7739 workflow + /// Iterates over all the validators but only if this is a detection request + /// ERC-7739 spec assumes that if the account doesn't support ERC-7739 + /// it will try to handle the detection request as it was normal sig verification + /// request and will return 0xffffffff since it won't be able to verify the 0x signature + /// against 0x7739...7739 hash. + /// So this approach is consistent with the ERC-7739 spec. + /// If no validator supports ERC-7739, this function returns false + /// thus the account will proceed with normal signature verification + /// and return 0xffffffff as a result. + function checkERC7739Support(bytes32 hash, bytes calldata signature) public view virtual returns (bytes4) { + bytes4 result; + unchecked { + SentinelListLib.SentinelList storage validators = _getAccountStorage().validators; + address next = validators.entries[SENTINEL]; + while (next != ZERO_ADDRESS && next != SENTINEL) { + result = _get7739Version(next, result, hash, signature); + next = validators.getNext(next); + } + } + result = _get7739Version(_DEFAULT_VALIDATOR, result, hash, signature); // check default validator + return result == bytes4(0) ? bytes4(0xffffffff) : result; + } + + function _get7739Version(address validator, bytes4 prevResult, bytes32 hash, bytes calldata signature) internal view returns (bytes4) { + bytes4 support = IValidator(validator).isValidSignatureWithSender(msg.sender, hash, signature); + if (bytes2(support) == bytes2(SUPPORTS_ERC7739) && support > prevResult) { + return support; + } + return prevResult; + } + + /// @dev Ensures that only authorized callers can upgrade the smart contract implementation. + /// This is part of the UUPS (Universal Upgradeable Proxy Standard) pattern. + /// @param newImplementation The address of the new implementation to upgrade to. + function _authorizeUpgrade(address newImplementation) internal virtual override(UUPSUpgradeable) onlyEntryPointOrSelf { + if(_amIERC7702()) { + revert ERC7702AccountCannotBeUpgradedThisWay(); + } + } + + /// @dev Handles the PREP initialization. + /// @param data The packed data to be handled. + /// @return cleanedSignature The cleaned signature for Nexus 4337 (validateUserOp) flow. + /// @return initData The data to initialize the account with. + function _handlePREP(bytes calldata data) internal returns (bytes calldata cleanedSignature, bytes calldata initData) { + bytes32 saltAndDelegation; + // unpack the data + assembly { + if lt(data.length, 0xf9) { + mstore(0x0, 0xaed59595) // NotInitializable() + revert(0x1c, 0x04) + } + + saltAndDelegation := calldataload(data.offset) + + // initData + let p := calldataload(add(data.offset, 0x20)) + let u := add(data.offset, p) + initData.offset := add(u, 0x20) + initData.length := calldataload(u) + + // cleanedSignature + p := calldataload(add(data.offset, 0x40)) + u := add(data.offset, p) + cleanedSignature.offset := add(u, 0x20) + cleanedSignature.length := calldataload(u) + } + + // check r is valid + bytes32 r = LibPREP.rPREP(address(this), keccak256(initData), saltAndDelegation); + if (r == bytes32(0)) { + revert InvalidPREP(); + } + emit PREPInitialized(r); + } + + // checks if there's at least one validator initialized + function _checkInitializedValidators() internal view { + if(!_amIERC7702() && !IValidator(_DEFAULT_VALIDATOR).isInitialized(address(this))) { + unchecked { + SentinelListLib.SentinelList storage validators = _getAccountStorage().validators; + address next = validators.entries[SENTINEL]; + while (next != ZERO_ADDRESS && next != SENTINEL) { + if(IValidator(next).isInitialized(address(this))) { + break; + } + next = validators.getNext(next); + } + if(next == SENTINEL) { //went through all validators and none was initialized + revert CanNotRemoveLastValidator(); + } + } + } + } + + /// @dev EIP712 domain name and version. + function _domainNameAndVersion() internal pure override returns (string memory name, string memory version) { + name = "Nexus"; + version = "1.2.0"; + } +} diff --git a/biconomy/nexus/1.2.0/contracts/base/BaseAccount.sol b/biconomy/nexus/1.2.0/contracts/base/BaseAccount.sol new file mode 100644 index 0000000..3a19e92 --- /dev/null +++ b/biconomy/nexus/1.2.0/contracts/base/BaseAccount.sol @@ -0,0 +1,130 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.27; + +// ────────────────────────────────────────────────────────────────────────────── +// _ __ _ __ +// / | / /__ | |/ /_ _______ +// / |/ / _ \| / / / / ___/ +// / /| / __/ / /_/ (__ ) +// /_/ |_/\___/_/|_\__,_/____/ +// +// ────────────────────────────────────────────────────────────────────────────── +// Nexus: A suite of contracts for Modular Smart Accounts compliant with ERC-7579 and ERC-4337, developed by Biconomy. +// Learn more at https://biconomy.io. To report security issues, please contact us at: security@biconomy.io + +import { IEntryPoint } from "account-abstraction/interfaces/IEntryPoint.sol"; +import { IBaseAccount } from "../interfaces/base/IBaseAccount.sol"; + +/// @title Nexus - BaseAccount +/// @notice Implements ERC-4337 and ERC-7579 standards for account management and access control within the Nexus suite. +/// @dev Manages entry points and configurations as specified in the ERC-4337 and ERC-7579 documentation. +/// @author @livingrockrises | Biconomy | chirag@biconomy.io +/// @author @aboudjem | Biconomy | adam.boudjemaa@biconomy.io +/// @author @filmakarov | Biconomy | filipp.makarov@biconomy.io +/// @author @zeroknots | Rhinestone.wtf | zeroknots.eth +/// Special thanks to the Solady team for foundational contributions: https://github.com/Vectorized/solady +contract BaseAccount is IBaseAccount { + /// @notice Identifier for this implementation on the network + string internal constant _ACCOUNT_IMPLEMENTATION_ID = "biconomy.nexus.1.2.0"; + + /// @notice The canonical address for the ERC4337 EntryPoint contract, version 0.7. + /// This address is consistent across all supported networks. + address internal immutable _ENTRYPOINT; + + /// @dev Ensures the caller is either the EntryPoint or this account itself. + /// Reverts with AccountAccessUnauthorized if the check fails. + modifier onlyEntryPointOrSelf() { + require(msg.sender == _ENTRYPOINT || msg.sender == address(this), AccountAccessUnauthorized()); + _; + } + + /// @dev Ensures the caller is the EntryPoint. + /// Reverts with AccountAccessUnauthorized if the check fails. + modifier onlyEntryPoint() { + require(msg.sender == _ENTRYPOINT, AccountAccessUnauthorized()); + _; + } + + /// @dev Sends to the EntryPoint (i.e. `msg.sender`) the missing funds for this transaction. + /// Subclass MAY override this modifier for better funds management. + /// (e.g. send to the EntryPoint more than the minimum required, so that in future transactions + /// it will not be required to send again) + /// + /// `missingAccountFunds` is the minimum value this modifier should send the EntryPoint, + /// which MAY be zero, in case there is enough deposit, or the userOp has a paymaster. + modifier payPrefund(uint256 missingAccountFunds) virtual { + _; + /// @solidity memory-safe-assembly + assembly { + if missingAccountFunds { + // Ignore failure (it's EntryPoint's job to verify, not the account's). + pop(call(gas(), caller(), missingAccountFunds, codesize(), 0x00, codesize(), 0x00)) + } + } + } + + /// @notice Adds deposit to the EntryPoint to fund transactions. + function addDeposit() external payable virtual { + address entryPointAddress = _ENTRYPOINT; + /// @solidity memory-safe-assembly + assembly { + // The EntryPoint has balance accounting logic in the `receive()` function. + if iszero(call(gas(), entryPointAddress, callvalue(), codesize(), 0x00, codesize(), 0x00)) { + revert(codesize(), 0x00) + } // For gas estimation. + } + } + + /// @notice Withdraws ETH from the EntryPoint to a specified address. + /// @param to The address to receive the withdrawn funds. + /// @param amount The amount to withdraw. + function withdrawDepositTo(address to, uint256 amount) external payable virtual onlyEntryPointOrSelf { + address entryPointAddress = _ENTRYPOINT; + assembly { + let freeMemPtr := mload(0x40) // Store the free memory pointer. + mstore(0x14, to) // Store the `to` argument. + mstore(0x34, amount) // Store the `amount` argument. + mstore(0x00, 0x205c2878000000000000000000000000) // `withdrawTo(address,uint256)`. + if iszero(call(gas(), entryPointAddress, 0, 0x10, 0x44, codesize(), 0x00)) { + returndatacopy(freeMemPtr, 0x00, returndatasize()) + revert(freeMemPtr, returndatasize()) + } + mstore(0x34, 0) // Restore the part of the free memory pointer that was overwritten. + } + } + + /// @notice Gets the nonce for a particular key. + /// @param key The nonce key. + /// @return The nonce associated with the key. + function nonce(uint192 key) external view virtual returns (uint256) { + return IEntryPoint(_ENTRYPOINT).getNonce(address(this), key); + } + + /// @notice Returns the current deposit balance of this account on the EntryPoint. + /// @return result The current balance held at the EntryPoint. + function getDeposit() external view virtual returns (uint256 result) { + address entryPointAddress = _ENTRYPOINT; + /// @solidity memory-safe-assembly + assembly { + mstore(0x20, address()) // Store the `account` argument. + mstore(0x00, 0x70a08231) // `balanceOf(address)`. + result := mul( + // Returns 0 if the EntryPoint does not exist. + mload(0x20), + and( + // The arguments of `and` are evaluated from right to left. + gt(returndatasize(), 0x1f), // At least 32 bytes returned. + staticcall(gas(), entryPointAddress, 0x1c, 0x24, 0x20, 0x20) + ) + ) + } + } + + /// @notice Retrieves the address of the EntryPoint contract, currently using version 0.7. + /// @dev This function returns the address of the canonical ERC4337 EntryPoint contract. + /// It can be overridden to return a different EntryPoint address if needed. + /// @return The address of the EntryPoint contract. + function entryPoint() external view returns (address) { + return _ENTRYPOINT; + } +} diff --git a/biconomy/nexus/1.2.0/contracts/base/ExecutionHelper.sol b/biconomy/nexus/1.2.0/contracts/base/ExecutionHelper.sol new file mode 100644 index 0000000..b07eb94 --- /dev/null +++ b/biconomy/nexus/1.2.0/contracts/base/ExecutionHelper.sol @@ -0,0 +1,284 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.27; + +// ────────────────────────────────────────────────────────────────────────────── +// _ __ _ __ +// / | / /__ | |/ /_ _______ +// / |/ / _ \| / / / / ___/ +// / /| / __/ / /_/ (__ ) +// /_/ |_/\___/_/|_\__,_/____/ +// +// ────────────────────────────────────────────────────────────────────────────── +// Nexus: A suite of contracts for Modular Smart Accounts compliant with ERC-7579 and ERC-4337, developed by Biconomy. +// Learn more at https://biconomy.io. To report security issues, please contact us at: security@biconomy.io + +import { Execution } from "../types/DataTypes.sol"; +import { IExecutionHelperEventsAndErrors } from "../interfaces/base/IExecutionHelper.sol"; +import { ExecType, EXECTYPE_DEFAULT, EXECTYPE_TRY } from "../lib/ModeLib.sol"; +import { ExecLib } from "../lib/ExecLib.sol"; + +/// @title Nexus - ExecutionHelper +/// @notice Implements execution management within the Nexus suite, facilitating transaction execution strategies and +/// error handling. +/// @dev Provides mechanisms for direct and batched transactions with both committed and tentative execution strategies +/// as per ERC-4337 and ERC-7579 standards. +/// @author @livingrockrises | Biconomy | chirag@biconomy.io +/// @author @aboudjem | Biconomy | adam.boudjemaa@biconomy.io +/// @author @filmakarov | Biconomy | filipp.makarov@biconomy.io +/// @author @zeroknots | Rhinestone.wtf | zeroknots.eth +/// Special thanks to the Solady team for foundational contributions: https://github.com/Vectorized/solady +contract ExecutionHelper is IExecutionHelperEventsAndErrors { + using ExecLib for bytes; + + /// @notice Executes a call to a target address with specified value and data. + /// @notice calls to an EOA should be counted as successful. + /// @param target The address to execute the call on. + /// @param value The amount of wei to send with the call. + /// @param callData The calldata to send. + /// @return result The bytes returned from the execution, which contains the returned data from the target address. + function _execute(address target, uint256 value, bytes calldata callData) internal virtual returns (bytes memory result) { + /// @solidity memory-safe-assembly + assembly { + result := mload(0x40) + calldatacopy(result, callData.offset, callData.length) + if iszero(call(gas(), target, value, result, callData.length, codesize(), 0x00)) { + // Bubble up the revert if the call reverts. + returndatacopy(result, 0x00, returndatasize()) + revert(result, returndatasize()) + } + mstore(result, returndatasize()) // Store the length. + let o := add(result, 0x20) + returndatacopy(o, 0x00, returndatasize()) // Copy the returndata. + mstore(0x40, add(o, returndatasize())) // Allocate the memory. + } + } + + /// @notice Executes a call to a target address with specified value and data. + /// same as _execute, but callData can be in memory + function _executeMemory(address target, uint256 value, bytes memory callData) internal virtual returns (bytes memory result) { + /// @solidity memory-safe-assembly + assembly { + result := mload(0x40) + if iszero(call(gas(), target, value, add(callData, 0x20), mload(callData), codesize(), 0x00)) { + // Bubble up the revert if the call reverts. + returndatacopy(result, 0x00, returndatasize()) + revert(result, returndatasize()) + } + mstore(result, returndatasize()) // Store the length. + let o := add(result, 0x20) + returndatacopy(o, 0x00, returndatasize()) // Copy the returndata. + mstore(0x40, add(o, returndatasize())) // Allocate the memory. + } + } + + /// @notice Executes a call to a target address with specified value and data. + /// Same as _execute but without return data for gas optimization. + function _executeNoReturndata(address target, uint256 value, bytes calldata callData) internal virtual { + /// @solidity memory-safe-assembly + assembly { + let result := mload(0x40) + calldatacopy(result, callData.offset, callData.length) + if iszero(call(gas(), target, value, result, callData.length, codesize(), 0x00)) { + // Bubble up the revert if the call reverts. + returndatacopy(result, 0x00, returndatasize()) + revert(result, returndatasize()) + } + mstore(0x40, add(result, callData.length)) //allocate memory + } + } + + /// @notice Tries to execute a call and captures if it was successful or not. + /// @dev Similar to _execute but returns a success boolean and catches reverts instead of propagating them. + /// @notice calls to an EOA should be counted as successful. + /// @param target The address to execute the call on. + /// @param value The amount of wei to send with the call. + /// @param callData The calldata to send. + /// @return success True if the execution was successful, false otherwise. + /// @return result The bytes returned from the execution, which contains the returned data from the target address. + function _tryExecute(address target, uint256 value, bytes calldata callData) internal virtual returns (bool success, bytes memory result) { + /// @solidity memory-safe-assembly + assembly { + result := mload(0x40) + calldatacopy(result, callData.offset, callData.length) + success := call(gas(), target, value, result, callData.length, codesize(), 0x00) + mstore(result, returndatasize()) // Store the length. + let o := add(result, 0x20) + returndatacopy(o, 0x00, returndatasize()) // Copy the returndata. + mstore(0x40, add(o, returndatasize())) // Allocate the memory. + } + } + + /// @notice Executes a batch of calls. + /// @param executions An array of Execution structs each containing target, value, and calldata. + /// @return result An array of bytes returned from each executed call, corresponding to the returndata from each target address. + function _executeBatch(Execution[] calldata executions) internal returns (bytes[] memory result) { + result = new bytes[](executions.length); + + Execution calldata exec; + for (uint256 i; i < executions.length; i++) { + exec = executions[i]; + result[i] = _execute(exec.target, exec.value, exec.callData); + } + } + + /// @notice Executes a batch of calls without returning the result. + /// @param executions An array of Execution structs each containing target, value, and calldata. + function _executeBatchNoReturndata(Execution[] calldata executions) internal { + Execution calldata exec; + for (uint256 i; i < executions.length; i++) { + exec = executions[i]; + _executeNoReturndata(exec.target, exec.value, exec.callData); + } + } + + /// @notice Tries to execute a batch of calls and emits an event for each unsuccessful call. + /// @param executions An array of Execution structs. + /// @return result An array of bytes returned from each executed call, with unsuccessful calls marked by events. + function _tryExecuteBatch(Execution[] calldata executions) internal returns (bytes[] memory result) { + result = new bytes[](executions.length); + + Execution calldata exec; + for (uint256 i; i < executions.length; i++) { + exec = executions[i]; + bool success; + (success, result[i]) = _tryExecute(exec.target, exec.value, exec.callData); + if (!success) emit TryExecuteUnsuccessful(exec.callData, result[i]); + } + } + + /// @dev Execute a delegatecall with `delegate` on this account. + /// @return result The bytes returned from the delegatecall, which contains the returned data from the delegate contract. + function _executeDelegatecall(address delegate, bytes calldata callData) internal returns (bytes memory result) { + /// @solidity memory-safe-assembly + assembly { + result := mload(0x40) + calldatacopy(result, callData.offset, callData.length) + // Forwards the `data` to `delegate` via delegatecall. + if iszero(delegatecall(gas(), delegate, result, callData.length, codesize(), 0x00)) { + // Bubble up the revert if the call reverts. + returndatacopy(result, 0x00, returndatasize()) + revert(result, returndatasize()) + } + mstore(result, returndatasize()) // Store the length. + let o := add(result, 0x20) + returndatacopy(o, 0x00, returndatasize()) // Copy the returndata. + mstore(0x40, add(o, returndatasize())) // Allocate the memory. + } + } + + /// @dev Execute a delegatecall with `delegate` on this account. + /// Same as _executeDelegatecall but without return data for gas optimization. + function _executeDelegatecallNoReturndata(address delegate, bytes calldata callData) internal { + /// @solidity memory-safe-assembly + assembly { + let result := mload(0x40) + calldatacopy(result, callData.offset, callData.length) + if iszero(delegatecall(gas(), delegate, result, callData.length, codesize(), 0x00)) { + // Bubble up the revert if the call reverts. + returndatacopy(result, 0x00, returndatasize()) + revert(result, returndatasize()) + } + mstore(0x40, add(result, callData.length)) //allocate memory + } + } + + /// @dev Execute a delegatecall with `delegate` on this account and catch reverts. + /// @return success True if the delegatecall was successful, false otherwise. + /// @return result The bytes returned from the delegatecall, which contains the returned data from the delegate contract. + function _tryExecuteDelegatecall(address delegate, bytes calldata callData) internal returns (bool success, bytes memory result) { + /// @solidity memory-safe-assembly + assembly { + result := mload(0x40) + calldatacopy(result, callData.offset, callData.length) + // Forwards the `data` to `delegate` via delegatecall. + success := delegatecall(gas(), delegate, result, callData.length, codesize(), 0x00) + mstore(result, returndatasize()) // Store the length. + let o := add(result, 0x20) + returndatacopy(o, 0x00, returndatasize()) // Copy the returndata. + mstore(0x40, add(o, returndatasize())) // Allocate the memory. + } + } + + /// @dev Executes a single transaction based on the specified execution type. + /// @param executionCalldata The calldata containing the transaction details (target address, value, and data). + /// @param execType The execution type, which can be DEFAULT (revert on failure) or TRY (return on failure). + function _handleSingleExecution(bytes calldata executionCalldata, ExecType execType) internal { + (address target, uint256 value, bytes calldata callData) = executionCalldata.decodeSingle(); + if (execType == EXECTYPE_DEFAULT) _executeNoReturndata(target, value, callData); + else if (execType == EXECTYPE_TRY) { + (bool success, bytes memory result) = _tryExecute(target, value, callData); + if (!success) emit TryExecuteUnsuccessful(callData, result); + } else revert UnsupportedExecType(execType); + } + + /// @dev Executes a batch of transactions based on the specified execution type. + /// @param executionCalldata The calldata for a batch of transactions. + /// @param execType The execution type, which can be DEFAULT (revert on failure) or TRY (return on failure). + function _handleBatchExecution(bytes calldata executionCalldata, ExecType execType) internal { + Execution[] calldata executions = executionCalldata.decodeBatch(); + if (execType == EXECTYPE_DEFAULT) _executeBatchNoReturndata(executions); + else if (execType == EXECTYPE_TRY) _tryExecuteBatch(executions); + else revert UnsupportedExecType(execType); + } + + /// @dev Executes a single transaction based on the specified execution type. + /// @param executionCalldata The calldata containing the transaction details (target address, value, and data). + /// @param execType The execution type, which can be DEFAULT (revert on failure) or TRY (return on failure). + function _handleDelegateCallExecution(bytes calldata executionCalldata, ExecType execType) internal { + (address delegate, bytes calldata callData) = executionCalldata.decodeDelegateCall(); + if (execType == EXECTYPE_DEFAULT) _executeDelegatecallNoReturndata(delegate, callData); + else if (execType == EXECTYPE_TRY) { + (bool success, bytes memory result) = _tryExecuteDelegatecall(delegate, callData); + if (!success) emit TryDelegateCallUnsuccessful(callData, result); + } else revert UnsupportedExecType(execType); + } + + /// @dev Executes a single transaction based on the specified execution type. + /// @param executionCalldata The calldata containing the transaction details (target address, value, and data). + /// @param execType The execution type, which can be DEFAULT (revert on failure) or TRY (return on failure). + /// @return returnData An array containing the execution result. In the case of a single transaction, the array contains one element. + function _handleSingleExecutionAndReturnData(bytes calldata executionCalldata, ExecType execType) internal returns (bytes[] memory returnData) { + (address target, uint256 value, bytes calldata callData) = executionCalldata.decodeSingle(); + returnData = new bytes[](1); + bool success; + // check if execType is revert(default) or try + if (execType == EXECTYPE_DEFAULT) { + returnData[0] = _execute(target, value, callData); + } else if (execType == EXECTYPE_TRY) { + (success, returnData[0]) = _tryExecute(target, value, callData); + if (!success) emit TryExecuteUnsuccessful(callData, returnData[0]); + } else { + revert UnsupportedExecType(execType); + } + } + + /// @dev Executes a batch of transactions based on the specified execution type. + /// @param executionCalldata The calldata for a batch of transactions. + /// @param execType The execution type, which can be DEFAULT (revert on failure) or TRY (return on failure). + /// @return returnData An array containing the execution results for each transaction in the batch. + function _handleBatchExecutionAndReturnData(bytes calldata executionCalldata, ExecType execType) internal returns (bytes[] memory returnData) { + Execution[] calldata executions = executionCalldata.decodeBatch(); + if (execType == EXECTYPE_DEFAULT) returnData = _executeBatch(executions); + else if (execType == EXECTYPE_TRY) returnData = _tryExecuteBatch(executions); + else revert UnsupportedExecType(execType); + } + + /// @dev Executes a single transaction based on the specified execution type. + /// @param executionCalldata The calldata containing the transaction details (target address, value, and data). + /// @param execType The execution type, which can be DEFAULT (revert on failure) or TRY (return on failure). + /// @return returnData An array containing the result of the delegatecall execution. + function _handleDelegateCallExecutionAndReturnData( + bytes calldata executionCalldata, + ExecType execType + ) internal returns (bytes[] memory returnData) { + (address delegate, bytes calldata callData) = executionCalldata.decodeDelegateCall(); + returnData = new bytes[](1); + bool success; + if (execType == EXECTYPE_DEFAULT) { + returnData[0] = _executeDelegatecall(delegate, callData); + } else if (execType == EXECTYPE_TRY) { + (success, returnData[0]) = _tryExecuteDelegatecall(delegate, callData); + if (!success) emit TryDelegateCallUnsuccessful(callData, returnData[0]); + } else revert UnsupportedExecType(execType); + } +} diff --git a/biconomy/nexus/1.2.0/contracts/base/ModuleManager.sol b/biconomy/nexus/1.2.0/contracts/base/ModuleManager.sol new file mode 100644 index 0000000..230cb5c --- /dev/null +++ b/biconomy/nexus/1.2.0/contracts/base/ModuleManager.sol @@ -0,0 +1,728 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.27; + +// ────────────────────────────────────────────────────────────────────────────── +// _ __ _ __ +// / | / /__ | |/ /_ _______ +// / |/ / _ \| / / / / ___/ +// / /| / __/ / /_/ (__ ) +// /_/ |_/\___/_/|_\__,_/____/ +// +// ────────────────────────────────────────────────────────────────────────────── +// Nexus: A suite of contracts for Modular Smart Accounts compliant with ERC-7579 and ERC-4337, developed by Biconomy. +// Learn more at https://biconomy.io. To report security issues, please contact us at: security@biconomy.io + +import { SentinelListLib } from "sentinellist/SentinelList.sol"; +import { Storage } from "./Storage.sol"; +import { IHook } from "../interfaces/modules/IHook.sol"; +import { IModule } from "../interfaces/modules/IModule.sol"; +import { IPreValidationHookERC1271, IPreValidationHookERC4337 } from "../interfaces/modules/IPreValidationHook.sol"; +import { IExecutor } from "../interfaces/modules/IExecutor.sol"; +import { IFallback } from "../interfaces/modules/IFallback.sol"; +import { IValidator } from "../interfaces/modules/IValidator.sol"; +import { CallType, CALLTYPE_SINGLE, CALLTYPE_STATIC } from "../lib/ModeLib.sol"; +import { ExecLib } from "../lib/ExecLib.sol"; +import { LocalCallDataParserLib } from "../lib/local/LocalCallDataParserLib.sol"; +import { IModuleManager } from "../interfaces/base/IModuleManager.sol"; +import { + MODULE_TYPE_VALIDATOR, + MODULE_TYPE_EXECUTOR, + MODULE_TYPE_FALLBACK, + MODULE_TYPE_HOOK, + MODULE_TYPE_PREVALIDATION_HOOK_ERC1271, + MODULE_TYPE_PREVALIDATION_HOOK_ERC4337, + MODULE_TYPE_MULTI, + MODULE_ENABLE_MODE_TYPE_HASH, + EMERGENCY_UNINSTALL_TYPE_HASH, + ERC1271_MAGICVALUE +} from "../types/Constants.sol"; +import { EIP712 } from "solady/utils/EIP712.sol"; +import { ExcessivelySafeCall } from "excessively-safe-call/ExcessivelySafeCall.sol"; +import { PackedUserOperation } from "account-abstraction/interfaces/PackedUserOperation.sol"; +import { RegistryAdapter } from "./RegistryAdapter.sol"; +import { EmergencyUninstall } from "../types/DataTypes.sol"; +import { ECDSA } from "solady/utils/ECDSA.sol"; + +/// @title Nexus - ModuleManager +/// @notice Manages Validator, Executor, Hook, and Fallback modules within the Nexus suite, supporting +/// @dev Implements SentinelList for managing modules via a linked list structure, adhering to ERC-7579. +/// @author @livingrockrises | Biconomy | chirag@biconomy.io +/// @author @aboudjem | Biconomy | adam.boudjemaa@biconomy.io +/// @author @filmakarov | Biconomy | filipp.makarov@biconomy.io +/// @author @zeroknots | Rhinestone.wtf | zeroknots.eth +/// Special thanks to the Solady team for foundational contributions: https://github.com/Vectorized/solady +abstract contract ModuleManager is Storage, EIP712, IModuleManager, RegistryAdapter { + using SentinelListLib for SentinelListLib.SentinelList; + using LocalCallDataParserLib for bytes; + using ExecLib for address; + using ExcessivelySafeCall for address; + using ECDSA for bytes32; + + /// @dev The default validator address. + /// @notice To explicitly initialize the default validator, Nexus.execute(_DEFAULT_VALIDATOR.onInstall(...)) should be called. + address internal immutable _DEFAULT_VALIDATOR; + + /// @dev initData should block the implementation from being used as a Smart Account + constructor(address _defaultValidator, bytes memory _initData) { + if (!IValidator(_defaultValidator).isModuleType(MODULE_TYPE_VALIDATOR)) + revert MismatchModuleTypeId(); + IValidator(_defaultValidator).onInstall(_initData); + _DEFAULT_VALIDATOR = _defaultValidator; + } + + /// @notice Ensures the message sender is a registered executor module. + modifier onlyExecutorModule() virtual { + require(_getAccountStorage().executors.contains(msg.sender), InvalidModule(msg.sender)); + _; + } + + /// @notice Does pre-checks and post-checks using an installed hook on the account. + /// @dev sender, msg.data and msg.value is passed to the hook to implement custom flows. + modifier withHook() { + address hook = _getHook(); + if (hook == address(0)) { + _; + } else { + bytes memory hookData = IHook(hook).preCheck(msg.sender, msg.value, msg.data); + _; + IHook(hook).postCheck(hookData); + } + } + + // receive function + receive() external payable { } + + /// @dev Fallback function to manage incoming calls using designated handlers based on the call type. + /// Hooked manually in the _fallback function + fallback() external payable { + _fallback(msg.data); + } + + /// @dev Retrieves a paginated list of validator addresses from the linked list. + /// This utility function is not defined by the ERC-7579 standard and is implemented to facilitate + /// easier management and retrieval of large sets of validator modules. + /// @param cursor The address to start pagination from, or zero to start from the first entry. + /// @param size The number of validator addresses to return. + /// @return array An array of validator addresses. + /// @return next The address to use as a cursor for the next page of results. + function getValidatorsPaginated(address cursor, uint256 size) external view returns (address[] memory array, address next) { + (array, next) = _paginate(_getAccountStorage().validators, cursor, size); + } + + /// @dev Retrieves a paginated list of executor addresses from the linked list. + /// This utility function is not defined by the ERC-7579 standard and is implemented to facilitate + /// easier management and retrieval of large sets of executor modules. + /// @param cursor The address to start pagination from, or zero to start from the first entry. + /// @param size The number of executor addresses to return. + /// @return array An array of executor addresses. + /// @return next The address to use as a cursor for the next page of results. + function getExecutorsPaginated(address cursor, uint256 size) external view returns (address[] memory array, address next) { + (array, next) = _paginate(_getAccountStorage().executors, cursor, size); + } + + /// @notice Retrieves the currently active hook address. + /// @return hook The address of the active hook module. + function getActiveHook() external view returns (address hook) { + return _getHook(); + } + + /// @notice Fetches the fallback handler for a specific selector. + /// @param selector The function selector to query. + /// @return calltype The type of call that the handler manages. + /// @return handler The address of the fallback handler. + function getFallbackHandlerBySelector(bytes4 selector) external view returns (CallType, address) { + FallbackHandler memory handler = _getAccountStorage().fallbacks[selector]; + return (handler.calltype, handler.handler); + } + + /// @dev Initializes the module manager by setting up default states for validators and executors. + function _initSentinelLists() internal virtual { + // account module storage + AccountStorage storage ams = _getAccountStorage(); + ams.executors.init(); + ams.validators.init(); + } + + /// @dev Implements Module Enable Mode flow. + /// @param packedData Data source to parse data required to perform Module Enable mode from. + /// @return userOpSignature the clean signature which can be further used for userOp validation + function _enableMode(bytes32 userOpHash, bytes calldata packedData) internal returns (bytes calldata userOpSignature) { + address module; + uint256 moduleType; + bytes calldata moduleInitData; + bytes calldata enableModeSignature; + + (module, moduleType, moduleInitData, enableModeSignature, userOpSignature) = packedData.parseEnableModeData(); + + address enableModeSigValidator = _handleValidator(address(bytes20(enableModeSignature[0:20]))); + + enableModeSignature = enableModeSignature[20:]; + + if (!_checkEnableModeSignature({ + structHash: _getEnableModeDataHash(module, moduleType, userOpHash, moduleInitData), + sig: enableModeSignature, + validator: enableModeSigValidator + })) { + revert EnableModeSigError(); + } + this.installModule(moduleType, module, moduleInitData); + } + + /// @notice Installs a new module to the smart account. + /// @param moduleTypeId The type identifier of the module being installed, which determines its role: + /// - 0 for MultiType + /// - 1 for Validator + /// - 2 for Executor + /// - 3 for Fallback + /// - 4 for Hook + /// - 8 for PreValidationHookERC1271 + /// - 9 for PreValidationHookERC4337 + /// @param module The address of the module to install. + /// @param initData Initialization data for the module. + /// @dev This function goes through hook checks via withHook modifier. + /// @dev No need to check that the module is already installed, as this check is done + /// when trying to sstore the module in an appropriate SentinelList + function _installModule(uint256 moduleTypeId, address module, bytes calldata initData) internal { + if (!_areSentinelListsInitialized()) { + _initSentinelLists(); + } + if (module == address(0)) revert ModuleAddressCanNotBeZero(); + if (moduleTypeId == MODULE_TYPE_VALIDATOR) { + _installValidator(module, initData); + } else if (moduleTypeId == MODULE_TYPE_EXECUTOR) { + _installExecutor(module, initData); + } else if (moduleTypeId == MODULE_TYPE_FALLBACK) { + _installFallbackHandler(module, initData); + } else if (moduleTypeId == MODULE_TYPE_HOOK) { + _installHook(module, initData); + } else if (moduleTypeId == MODULE_TYPE_PREVALIDATION_HOOK_ERC1271 || moduleTypeId == MODULE_TYPE_PREVALIDATION_HOOK_ERC4337) { + _installPreValidationHook(moduleTypeId, module, initData); + } else if (moduleTypeId == MODULE_TYPE_MULTI) { + _multiTypeInstall(module, initData); + } else { + revert InvalidModuleTypeId(moduleTypeId); + } + } + + /// @dev Installs a new validator module after checking if it matches the required module type. + /// @param validator The address of the validator module to be installed. + /// @param data Initialization data to configure the validator upon installation. + function _installValidator( + address validator, + bytes calldata data + ) + internal + virtual + withHook + withRegistry(validator, MODULE_TYPE_VALIDATOR) + { + if (!IValidator(validator).isModuleType(MODULE_TYPE_VALIDATOR)) revert MismatchModuleTypeId(); + if (validator == _DEFAULT_VALIDATOR) { + revert DefaultValidatorAlreadyInstalled(); + } + _getAccountStorage().validators.push(validator); + IValidator(validator).onInstall(data); + } + + /// @dev Uninstalls a validator module. + /// @param validator The address of the validator to be uninstalled. + /// @param data De-initialization data to configure the validator upon uninstallation. + function _uninstallValidator(address validator, bytes calldata data) internal virtual { + SentinelListLib.SentinelList storage validators = _getAccountStorage().validators; + + (address prev, bytes memory disableModuleData) = abi.decode(data, (address, bytes)); + + // Perform the removal first + validators.pop(prev, validator); + + validator.excessivelySafeCall(gasleft(), 0, 0, abi.encodeWithSelector(IModule.onUninstall.selector, disableModuleData)); + } + + /// @dev Installs a new executor module after checking if it matches the required module type. + /// @param executor The address of the executor module to be installed. + /// @param data Initialization data to configure the executor upon installation. + function _installExecutor( + address executor, + bytes calldata data + ) + internal + virtual + withHook + withRegistry(executor, MODULE_TYPE_EXECUTOR) + { + if (!IExecutor(executor).isModuleType(MODULE_TYPE_EXECUTOR)) revert MismatchModuleTypeId(); + _getAccountStorage().executors.push(executor); + IExecutor(executor).onInstall(data); + } + + /// @dev Uninstalls an executor module by removing it from the executors list. + /// @param executor The address of the executor to be uninstalled. + /// @param data De-initialization data to configure the executor upon uninstallation. + function _uninstallExecutor(address executor, bytes calldata data) internal virtual { + (address prev, bytes memory disableModuleData) = abi.decode(data, (address, bytes)); + _getAccountStorage().executors.pop(prev, executor); + executor.excessivelySafeCall(gasleft(), 0, 0, abi.encodeWithSelector(IModule.onUninstall.selector, disableModuleData)); + } + + /// @dev Installs a hook module, ensuring no other hooks are installed before proceeding. + /// @param hook The address of the hook to be installed. + /// @param data Initialization data to configure the hook upon installation. + function _installHook( + address hook, + bytes calldata data + ) + internal + virtual + withHook + withRegistry(hook, MODULE_TYPE_HOOK) + { + if (!IHook(hook).isModuleType(MODULE_TYPE_HOOK)) revert MismatchModuleTypeId(); + address currentHook = _getHook(); + require(currentHook == address(0), HookAlreadyInstalled(currentHook)); + _setHook(hook); + IHook(hook).onInstall(data); + } + + /// @dev Uninstalls a hook module, ensuring the current hook matches the one intended for uninstallation. + /// @param hook The address of the hook to be uninstalled. + /// @param hookType The type of the hook to be uninstalled. + /// @param data De-initialization data to configure the hook upon uninstallation. + function _uninstallHook(address hook, uint256 hookType, bytes calldata data) internal virtual { + if (hookType == MODULE_TYPE_HOOK) { + _setHook(address(0)); + } else if (hookType == MODULE_TYPE_PREVALIDATION_HOOK_ERC1271 || hookType == MODULE_TYPE_PREVALIDATION_HOOK_ERC4337) { + _uninstallPreValidationHook(hook, hookType, data); + } + hook.excessivelySafeCall(gasleft(), 0, 0, abi.encodeWithSelector(IModule.onUninstall.selector, data)); + } + + /// @dev Sets the current hook in the storage to the specified address. + /// @param hook The new hook address. + function _setHook(address hook) internal virtual { + _getAccountStorage().hook = IHook(hook); + } + + /// @dev Installs a fallback handler for a given selector with initialization data. + /// @param handler The address of the fallback handler to install. + /// @param params The initialization parameters including the selector and call type. + function _installFallbackHandler( + address handler, + bytes calldata params + ) + internal + virtual + withHook + withRegistry(handler, MODULE_TYPE_FALLBACK) + { + if (!IFallback(handler).isModuleType(MODULE_TYPE_FALLBACK)) revert MismatchModuleTypeId(); + // Extract the function selector from the provided parameters. + bytes4 selector = bytes4(params[0:4]); + + // Extract the call type from the provided parameters. + CallType calltype = CallType.wrap(bytes1(params[4])); + + require(calltype == CALLTYPE_SINGLE || calltype == CALLTYPE_STATIC, FallbackCallTypeInvalid()); + + // Extract the initialization data from the provided parameters. + bytes memory initData = params[5:]; + + // Revert if the selector is either `onInstall(bytes)` (0x6d61fe70) or `onUninstall(bytes)` (0x8a91b0e3) or explicit bytes(0). + // These selectors are explicitly forbidden to prevent security vulnerabilities. + // Allowing these selectors would enable unauthorized users to uninstall and reinstall critical modules. + // If a validator module is uninstalled and reinstalled without proper authorization, it can compromise + // the account's security and integrity. By restricting these selectors, we ensure that the fallback handler + // cannot be manipulated to disrupt the expected behavior and security of the account. + require(!(selector == bytes4(0x6d61fe70) || selector == bytes4(0x8a91b0e3) || selector == bytes4(0)), FallbackSelectorForbidden()); + + // Revert if a fallback handler is already installed for the given selector. + // This check ensures that we do not overwrite an existing fallback handler, which could lead to unexpected behavior. + require(!_isFallbackHandlerInstalled(selector), FallbackAlreadyInstalledForSelector(selector)); + + // Store the fallback handler and its call type in the account storage. + // This maps the function selector to the specified fallback handler and call type. + _getAccountStorage().fallbacks[selector] = FallbackHandler(handler, calltype); + + // Invoke the `onInstall` function of the fallback handler with the provided initialization data. + // This step allows the fallback handler to perform any necessary setup or initialization. + IFallback(handler).onInstall(initData); + } + + /// @dev Uninstalls a fallback handler for a given selector. + /// @param fallbackHandler The address of the fallback handler to uninstall. + /// @param data The de-initialization data containing the selector. + function _uninstallFallbackHandler(address fallbackHandler, bytes calldata data) internal virtual { + _getAccountStorage().fallbacks[bytes4(data[0:4])] = FallbackHandler(address(0), CallType.wrap(0x00)); + fallbackHandler.excessivelySafeCall(gasleft(), 0, 0, abi.encodeWithSelector(IModule.onUninstall.selector, data[4:])); + } + + /// @dev Installs a pre-validation hook module, ensuring no other pre-validation hooks are installed before proceeding. + /// @param preValidationHookType The type of the pre-validation hook. + /// @param preValidationHook The address of the pre-validation hook to be installed. + /// @param data Initialization data to configure the hook upon installation. + function _installPreValidationHook( + uint256 preValidationHookType, + address preValidationHook, + bytes calldata data + ) + internal + virtual + withHook + withRegistry(preValidationHook, preValidationHookType) + { + if (!IModule(preValidationHook).isModuleType(preValidationHookType)) revert MismatchModuleTypeId(); + address currentPreValidationHook = _getPreValidationHook(preValidationHookType); + require(currentPreValidationHook == address(0), PrevalidationHookAlreadyInstalled(currentPreValidationHook)); + _setPreValidationHook(preValidationHookType, preValidationHook); + IModule(preValidationHook).onInstall(data); + } + + /// @dev Uninstalls a pre-validation hook module + /// @param preValidationHook The address of the pre-validation hook to be uninstalled. + /// @param hookType The type of the pre-validation hook. + /// @param data De-initialization data to configure the hook upon uninstallation. + function _uninstallPreValidationHook(address preValidationHook, uint256 hookType, bytes calldata data) internal virtual { + _setPreValidationHook(hookType, address(0)); + } + + /// @dev Sets the current pre-validation hook in the storage to the specified address, based on the hook type. + /// @param hookType The type of the pre-validation hook. + /// @param hook The new hook address. + function _setPreValidationHook(uint256 hookType, address hook) internal virtual { + if (hookType == MODULE_TYPE_PREVALIDATION_HOOK_ERC1271) { + _getAccountStorage().preValidationHookERC1271 = IPreValidationHookERC1271(hook); + } else if (hookType == MODULE_TYPE_PREVALIDATION_HOOK_ERC4337) { + _getAccountStorage().preValidationHookERC4337 = IPreValidationHookERC4337(hook); + } + } + + /// @notice Installs a module with multiple types in a single operation. + /// @dev This function handles installing a multi-type module by iterating through each type and initializing it. + /// The initData should include an ABI-encoded tuple of (uint[] types, bytes[] initDatas). + /// @param module The address of the multi-type module. + /// @param initData Initialization data for each type within the module. + function _multiTypeInstall(address module, bytes calldata initData) internal virtual { + (uint256[] calldata types, bytes[] calldata initDatas) = initData.parseMultiTypeInitData(); + + uint256 length = types.length; + if (initDatas.length != length) revert InvalidInput(); + + // iterate over all module types and install the module as a type accordingly + for (uint256 i; i < length; i++) { + uint256 theType = types[i]; + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* INSTALL VALIDATORS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + if (theType == MODULE_TYPE_VALIDATOR) { + _installValidator(module, initDatas[i]); + } + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* INSTALL EXECUTORS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + else if (theType == MODULE_TYPE_EXECUTOR) { + _installExecutor(module, initDatas[i]); + } + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* INSTALL FALLBACK */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + else if (theType == MODULE_TYPE_FALLBACK) { + _installFallbackHandler(module, initDatas[i]); + } + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* INSTALL HOOK (global only, not sig-specific) */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + else if (theType == MODULE_TYPE_HOOK) { + _installHook(module, initDatas[i]); + } + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* INSTALL PRE-VALIDATION HOOK */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + else if (theType == MODULE_TYPE_PREVALIDATION_HOOK_ERC1271 || theType == MODULE_TYPE_PREVALIDATION_HOOK_ERC4337) { + _installPreValidationHook(theType, module, initDatas[i]); + } + } + } + + /// @notice Checks if an emergency uninstall signature is valid. + /// @param data The emergency uninstall data. + /// @param signature The signature to validate. + function _checkEmergencyUninstallSignature(EmergencyUninstall calldata data, bytes calldata signature) internal { + address validator = _handleValidator(address(bytes20(signature[0:20]))); + // Hash the data + bytes32 hash = _getEmergencyUninstallDataHash(data.hook, data.hookType, data.deInitData, data.nonce); + // Check if nonce is valid + require(!_getAccountStorage().nonces[data.nonce], InvalidNonce()); + // Mark nonce as used + _getAccountStorage().nonces[data.nonce] = true; + // Check if the signature is valid + require((IValidator(validator).isValidSignatureWithSender(address(this), hash, signature[20:]) == ERC1271_MAGICVALUE), EmergencyUninstallSigError()); + } + + /// @dev Retrieves the pre-validation hook from the storage based on the hook type. + /// @param preValidationHookType The type of the pre-validation hook. + /// @return preValidationHook The address of the pre-validation hook. + function _getPreValidationHook(uint256 preValidationHookType) internal view returns (address preValidationHook) { + preValidationHook = preValidationHookType == MODULE_TYPE_PREVALIDATION_HOOK_ERC1271 + ? address(_getAccountStorage().preValidationHookERC1271) + : address(_getAccountStorage().preValidationHookERC4337); + } + + /// @dev Calls the pre-validation hook for ERC-1271. + /// @param hash The hash of the user operation. + /// @param signature The signature to validate. + /// @return postHash The updated hash after the pre-validation hook. + /// @return postSig The updated signature after the pre-validation hook. + function _withPreValidationHook(bytes32 hash, bytes calldata signature) internal view virtual returns (bytes32 postHash, bytes memory postSig) { + // Get the pre-validation hook for ERC-1271 + address preValidationHook = _getPreValidationHook(MODULE_TYPE_PREVALIDATION_HOOK_ERC1271); + // If no pre-validation hook is installed, return the original hash and signature + if (preValidationHook == address(0)) return (hash, signature); + // Otherwise, call the pre-validation hook and return the updated hash and signature + else return IPreValidationHookERC1271(preValidationHook).preValidationHookERC1271(msg.sender, hash, signature); + } + + /// @dev Calls the pre-validation hook for ERC-4337. + /// @param hash The hash of the user operation. + /// @param userOp The user operation data. + /// @param missingAccountFunds The amount of missing account funds. + /// @return postHash The updated hash after the pre-validation hook. + /// @return postSig The updated signature after the pre-validation hook. + function _withPreValidationHook( + bytes32 hash, + PackedUserOperation memory userOp, + uint256 missingAccountFunds + ) + internal + virtual + returns (bytes32 postHash, bytes memory postSig) + { + // Get the pre-validation hook for ERC-4337 + address preValidationHook = _getPreValidationHook(MODULE_TYPE_PREVALIDATION_HOOK_ERC4337); + // If no pre-validation hook is installed, return the original hash and signature + if (preValidationHook == address(0)) return (hash, userOp.signature); + // Otherwise, call the pre-validation hook and return the updated hash and signature + else return IPreValidationHookERC4337(preValidationHook).preValidationHookERC4337(userOp, missingAccountFunds, hash); + } + + /// @notice Checks if an enable mode signature is valid. + /// @param structHash data hash. + /// @param sig Signature. + /// @param validator Validator address. + function _checkEnableModeSignature( + bytes32 structHash, + bytes calldata sig, + address validator + ) internal view returns (bool) { + bytes32 eip712Digest = _hashTypedData(structHash); + // Use standard IERC-1271/ERC-7739 interface. + // Even if the validator doesn't support 7739 under the hood, it is still secure, + // as eip712digest is already built based on 712Domain of this Smart Account + // This interface should always be exposed by validators as per ERC-7579 + try IValidator(validator).isValidSignatureWithSender(address(this), eip712Digest, sig) returns (bytes4 res) { + return res == ERC1271_MAGICVALUE; + } catch { + return false; + } + } + + /// @notice Builds the enable mode data hash as per eip712 + /// @param module Module being enabled + /// @param moduleType Type of the module as per EIP-7579 + /// @param userOpHash Hash of the User Operation + /// @param initData Module init data. + /// @return structHash data hash + function _getEnableModeDataHash(address module, uint256 moduleType, bytes32 userOpHash, bytes calldata initData) internal view returns (bytes32) { + return keccak256(abi.encode(MODULE_ENABLE_MODE_TYPE_HASH, module, moduleType, userOpHash, keccak256(initData))); + } + + /// @notice Builds the emergency uninstall data hash as per eip712 + /// @param hookType Type of the hook (4 for Hook, 8 for ERC-1271 Prevalidation Hook, 9 for ERC-4337 Prevalidation Hook) + /// @param hook address of the hook being uninstalled + /// @param data De-initialization data to configure the hook upon uninstallation. + /// @param nonce Unique nonce for the operation + /// @return structHash data hash + function _getEmergencyUninstallDataHash(address hook, uint256 hookType, bytes calldata data, uint256 nonce) internal view returns (bytes32) { + return _hashTypedData(keccak256(abi.encode(EMERGENCY_UNINSTALL_TYPE_HASH, hook, hookType, keccak256(data), nonce))); + } + + /// @notice Checks if a module is installed on the smart account. + /// @param moduleTypeId The module type ID. + /// @param module The module address. + /// @param additionalContext Additional context for checking installation. + /// @return True if the module is installed, false otherwise. + function _isModuleInstalled(uint256 moduleTypeId, address module, bytes calldata additionalContext) internal view returns (bool) { + additionalContext; + if (moduleTypeId == MODULE_TYPE_VALIDATOR) { + return _isValidatorInstalled(module); + } else if (moduleTypeId == MODULE_TYPE_EXECUTOR) { + return _isExecutorInstalled(module); + } else if (moduleTypeId == MODULE_TYPE_FALLBACK) { + bytes4 selector; + if (additionalContext.length >= 4) { + selector = bytes4(additionalContext[0:4]); + } else { + selector = bytes4(0x00000000); + } + return _isFallbackHandlerInstalled(selector, module); + } else if (moduleTypeId == MODULE_TYPE_HOOK) { + return _isHookInstalled(module); + } else if (moduleTypeId == MODULE_TYPE_PREVALIDATION_HOOK_ERC1271 || moduleTypeId == MODULE_TYPE_PREVALIDATION_HOOK_ERC4337) { + return _getPreValidationHook(moduleTypeId) == module; + } else { + return false; + } + } + + /// @dev Checks if the validator list is already initialized. + /// In theory it doesn't 100% mean there is a validator or executor installed. + /// Use below functions to check for validators and executors. + function _areSentinelListsInitialized() internal view virtual returns (bool) { + // account module storage + AccountStorage storage ams = _getAccountStorage(); + return ams.validators.alreadyInitialized() && ams.executors.alreadyInitialized(); + } + + /// @dev Checks if a fallback handler is set for a given selector. + /// @param selector The function selector to check. + /// @return True if a fallback handler is set, otherwise false. + function _isFallbackHandlerInstalled(bytes4 selector) internal view virtual returns (bool) { + FallbackHandler storage handler = _getAccountStorage().fallbacks[selector]; + return handler.handler != address(0); + } + + /// @dev Checks if the expected fallback handler is installed for a given selector. + /// @param selector The function selector to check. + /// @param expectedHandler The address of the handler expected to be installed. + /// @return True if the installed handler matches the expected handler, otherwise false. + function _isFallbackHandlerInstalled(bytes4 selector, address expectedHandler) internal view returns (bool) { + FallbackHandler storage handler = _getAccountStorage().fallbacks[selector]; + return handler.handler == expectedHandler; + } + + /// @dev Checks if a validator is currently installed. + /// @param validator The address of the validator to check. + /// @return True if the validator is installed, otherwise false. + function _isValidatorInstalled(address validator) internal view virtual returns (bool) { + return _getAccountStorage().validators.contains(validator); + } + + /// @dev Checks if an executor is currently installed. + /// @param executor The address of the executor to check. + /// @return True if the executor is installed, otherwise false. + function _isExecutorInstalled(address executor) internal view virtual returns (bool) { + return _getAccountStorage().executors.contains(executor); + } + + /// @dev Checks if a hook is currently installed. + /// @param hook The address of the hook to check. + /// @return True if the hook is installed, otherwise false. + function _isHookInstalled(address hook) internal view returns (bool) { + return _getHook() == hook; + } + + /// @dev Retrieves the current hook from the storage. + /// @return hook The address of the current hook. + function _getHook() internal view returns (address hook) { + hook = address(_getAccountStorage().hook); + } + + /// @dev Checks if the account is an ERC7702 account + function _amIERC7702() internal view returns (bool res) { + assembly { + // use extcodesize as the first cheapest check + if eq(extcodesize(address()), 23) { + // use extcodecopy to copy first 3 bytes of this contract and compare with 0xef0100 + extcodecopy(address(), 0, 0, 3) + res := eq(0xef0100, shr(232, mload(0x00))) + } + // if it is not 23, we do not even check the first 3 bytes + } + } + + /// @dev Returns the validator address to use + function _handleValidator(address validator) internal view returns (address) { + if (validator == address(0)) { + return _DEFAULT_VALIDATOR; + } else { + require(_isValidatorInstalled(validator), ValidatorNotInstalled(validator)); + return validator; + } + } + + function _fallback(bytes calldata callData) private { + bool success; + bytes memory result; + FallbackHandler storage $fallbackHandler = _getAccountStorage().fallbacks[msg.sig]; + address handler = $fallbackHandler.handler; + CallType calltype = $fallbackHandler.calltype; + + if (handler != address(0)) { + // hook manually + address hook = _getHook(); + bytes memory hookData; + if (hook != address(0)) { + hookData = IHook(hook).preCheck(msg.sender, msg.value, msg.data); + } + //if there's a fallback handler, call it + if (calltype == CALLTYPE_STATIC) { + (success, result) = handler.staticcall(ExecLib.get2771CallData(callData)); + } else if (calltype == CALLTYPE_SINGLE) { + (success, result) = handler.call{ value: msg.value }(ExecLib.get2771CallData(callData)); + } else { + revert UnsupportedCallType(calltype); + } + + // Use revert message from fallback handler if the call was not successful + assembly { + if iszero(success) { + revert(add(result, 0x20), mload(result)) + } + } + + // hook post check + if (hook != address(0)) { + IHook(hook).postCheck(hookData); + } + + // return the result + assembly { + return(add(result, 0x20), mload(result)) + } + } + + // If there's no handler, the call can be one of onERCXXXReceived() + // No need to hook this as no execution is done here + bytes32 s; + /// @solidity memory-safe-assembly + assembly { + s := shr(224, calldataload(0)) + // 0x150b7a02: `onERC721Received(address,address,uint256,bytes)`. + // 0xf23a6e61: `onERC1155Received(address,address,uint256,uint256,bytes)`. + // 0xbc197c81: `onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)`. + if or(eq(s, 0x150b7a02), or(eq(s, 0xf23a6e61), eq(s, 0xbc197c81))) { + mstore(0x20, s) // Store `msg.sig`. + return(0x3c, 0x20) // Return `msg.sig`. + } + } + // if there was no handler and it is not the onERCXXXReceived call, revert + revert MissingFallbackHandler(msg.sig); + } + + /// @dev Helper function to paginate entries in a SentinelList. + /// @param list The SentinelList to paginate. + /// @param cursor The cursor to start paginating from. + /// @param size The number of entries to return. + /// @return array The array of addresses in the list. + /// @return nextCursor The cursor for the next page of entries. + function _paginate( + SentinelListLib.SentinelList storage list, + address cursor, + uint256 size + ) + private + view + returns (address[] memory array, address nextCursor) + { + (array, nextCursor) = list.getEntriesPaginated(cursor, size); + } +} diff --git a/biconomy/nexus/1.2.0/contracts/base/RegistryAdapter.sol b/biconomy/nexus/1.2.0/contracts/base/RegistryAdapter.sol new file mode 100644 index 0000000..9c58278 --- /dev/null +++ b/biconomy/nexus/1.2.0/contracts/base/RegistryAdapter.sol @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.27; + +import { IERC7484 } from "../interfaces/IERC7484.sol"; +import { Storage } from "./Storage.sol"; + +/// @title RegistryAdapter +/// @notice This contract provides an interface for interacting with an ERC-7484 compliant registry. +/// @dev The registry feature is opt-in, allowing the smart account owner to select and trust specific attesters. +abstract contract RegistryAdapter is Storage { + + /// @notice Emitted when a new ERC-7484 registry is configured for the account. + /// @param registry The configured registry contract. + event ERC7484RegistryConfigured(IERC7484 indexed registry); + + /// @notice Modifier to check if a module meets the required attestations in the registry. + /// @param module The module to check. + /// @param moduleType The type of the module to verify in the registry. + modifier withRegistry(address module, uint256 moduleType) { + _checkRegistry(module, moduleType); + _; + } + + /// @notice Returns the configured ERC-7484 registry. + /// @return The configured registry contract. + function getRegistry() external view returns (IERC7484) { + return IERC7484(_getAccountStorage().registry); + } + + /// @notice Configures the ERC-7484 registry and sets trusted attesters. + /// @param newRegistry The new registry contract to use. + /// @param attesters The list of attesters to trust. + /// @param threshold The number of attestations required. + function _configureRegistry(IERC7484 newRegistry, address[] memory attesters, uint8 threshold) internal { + _getAccountStorage().registry = address(newRegistry); + if (address(newRegistry) != address(0)) { + newRegistry.trustAttesters(threshold, attesters); + } + emit ERC7484RegistryConfigured(newRegistry); + } + + /// @notice Checks the registry to ensure sufficient valid attestations for a module. + /// @param module The module to check. + /// @param moduleType The type of the module to verify in the registry. + /// @dev Reverts if the required attestations are not met. + function _checkRegistry(address module, uint256 moduleType) internal view { + IERC7484 moduleRegistry = IERC7484(_getAccountStorage().registry); + if (address(moduleRegistry) != address(0)) { + // This will revert if attestations or the threshold are not met. + moduleRegistry.check(module, moduleType); + } + } +} diff --git a/biconomy/nexus/1.2.0/contracts/base/Storage.sol b/biconomy/nexus/1.2.0/contracts/base/Storage.sol new file mode 100644 index 0000000..ddfd807 --- /dev/null +++ b/biconomy/nexus/1.2.0/contracts/base/Storage.sol @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.27; + +// ────────────────────────────────────────────────────────────────────────────── +// _ __ _ __ +// / | / /__ | |/ /_ _______ +// / |/ / _ \| / / / / ___/ +// / /| / __/ / /_/ (__ ) +// /_/ |_/\___/_/|_\__,_/____/ +// +// ────────────────────────────────────────────────────────────────────────────── +// Nexus: A suite of contracts for Modular Smart Accounts compliant with ERC-7579 and ERC-4337, developed by Biconomy. +// Learn more at https://biconomy.io. To report security issues, please contact us at: security@biconomy.io + +import { IStorage } from "../interfaces/base/IStorage.sol"; + +/// @title Nexus - Storage +/// @notice Manages isolated storage spaces for Modular Smart Account in compliance with ERC-7201 standard to ensure collision-resistant storage. +/// @dev Implements the ERC-7201 namespaced storage pattern to maintain secure and isolated storage sections for different states within Nexus suite. +/// @author @livingrockrises | Biconomy | chirag@biconomy.io +/// @author @aboudjem | Biconomy | adam.boudjemaa@biconomy.io +/// @author @filmakarov | Biconomy | filipp.makarov@biconomy.io +/// @author @zeroknots | Rhinestone.wtf | zeroknots.eth +/// Special thanks to the Solady team for foundational contributions: https://github.com/Vectorized/solady +contract Storage is IStorage { + /// @custom:storage-location erc7201:biconomy.storage.Nexus + /// ERC-7201 namespaced via `keccak256(abi.encode(uint256(keccak256(bytes("biconomy.storage.Nexus"))) - 1)) & ~bytes32(uint256(0xff));` + bytes32 private constant _STORAGE_LOCATION = 0x0bb70095b32b9671358306b0339b4c06e7cbd8cb82505941fba30d1eb5b82f00; + + /// @dev Utilizes ERC-7201's namespaced storage pattern for isolated storage access. This method computes + /// the storage slot based on a predetermined location, ensuring collision-resistant storage for contract states. + /// @custom:storage-location ERC-7201 formula applied to "biconomy.storage.Nexus", facilitating unique + /// namespace identification and storage segregation, as detailed in the specification. + /// @return $ The proxy to the `AccountStorage` struct, providing a reference to the namespaced storage slot. + function _getAccountStorage() internal pure returns (AccountStorage storage $) { + assembly { + $.slot := _STORAGE_LOCATION + } + } +} diff --git a/biconomy/nexus/1.2.0/contracts/common/Stakeable.sol b/biconomy/nexus/1.2.0/contracts/common/Stakeable.sol new file mode 100644 index 0000000..a08023c --- /dev/null +++ b/biconomy/nexus/1.2.0/contracts/common/Stakeable.sol @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.27; + +// ────────────────────────────────────────────────────────────────────────────── +// _ __ _ __ +// / | / /__ | |/ /_ _______ +// / |/ / _ \| / / / / ___/ +// / /| / __/ / /_/ (__ ) +// /_/ |_/\___/_/|_\__,_/____/ +// +// ────────────────────────────────────────────────────────────────────────────── +// Nexus: A suite of contracts for Modular Smart Accounts compliant with ERC-7579 and ERC-4337, developed by Biconomy. +// Learn more at https://biconomy.io. For security issues, contact: security@biconomy.io + +import { Ownable } from "solady/auth/Ownable.sol"; +import { IEntryPoint } from "account-abstraction/interfaces/IEntryPoint.sol"; + +import { IStakeable } from "../interfaces/common/IStakeable.sol"; + +/// @title Stakeable Entity +/// @notice Provides functionality to stake, unlock, and withdraw Ether on an EntryPoint. +/// @author @livingrockrises | Biconomy | chirag@biconomy.io +/// @author @aboudjem | Biconomy | adam.boudjemaa@biconomy.io +/// @author @filmakarov | Biconomy | filipp.makarov@biconomy.io +/// @author @zeroknots | Rhinestone.wtf | zeroknots.eth +/// Special thanks to the Solady team for foundational contributions: https://github.com/Vectorized/solady +contract Stakeable is Ownable, IStakeable { + /// @notice Error thrown when an invalid EntryPoint address is provided. + error InvalidEntryPointAddress(); + + constructor(address newOwner) { + _setOwner(newOwner); + } + + /// @notice Stakes a certain amount of Ether on an EntryPoint. + /// @dev The contract should have enough Ether to cover the stake. + /// @param epAddress The address of the EntryPoint where the stake is added. + /// @param unstakeDelaySec The delay in seconds before the stake can be unlocked. + function addStake(address epAddress, uint32 unstakeDelaySec) external payable onlyOwner { + require(epAddress != address(0), InvalidEntryPointAddress()); + IEntryPoint(epAddress).addStake{ value: msg.value }(unstakeDelaySec); + } + + /// @notice Unlocks the stake on an EntryPoint. + /// @dev This starts the unstaking delay after which funds can be withdrawn. + /// @param epAddress The address of the EntryPoint from which the stake is to be unlocked. + function unlockStake(address epAddress) external onlyOwner { + require(epAddress != address(0), InvalidEntryPointAddress()); + IEntryPoint(epAddress).unlockStake(); + } + + /// @notice Withdraws the stake from an EntryPoint to a specified address. + /// @dev This can only be done after the unstaking delay has passed since the unlock. + /// @param epAddress The address of the EntryPoint where the stake is withdrawn from. + /// @param withdrawAddress The address to receive the withdrawn stake. + function withdrawStake(address epAddress, address payable withdrawAddress) external onlyOwner { + require(epAddress != address(0), InvalidEntryPointAddress()); + IEntryPoint(epAddress).withdrawStake(withdrawAddress); + } +} diff --git a/biconomy/nexus/1.2.0/contracts/factory/NexusAccountFactory.sol b/biconomy/nexus/1.2.0/contracts/factory/NexusAccountFactory.sol new file mode 100644 index 0000000..980ea7f --- /dev/null +++ b/biconomy/nexus/1.2.0/contracts/factory/NexusAccountFactory.sol @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.27; + +// ────────────────────────────────────────────────────────────────────────────── +// _ __ _ __ +// / | / /__ | |/ /_ _______ +// / |/ / _ \| / / / / ___/ +// / /| / __/ / /_/ (__ ) +// /_/ |_/\___/_/|_\__,_/____/ +// +// ────────────────────────────────────────────────────────────────────────────── +// Nexus: A suite of contracts for Modular Smart Accounts compliant with ERC-7579 and ERC-4337, developed by Biconomy. +// Learn more at https://biconomy.io. To report security issues, please contact us at: security@biconomy.io + +import { Stakeable } from "../common/Stakeable.sol"; +import { INexusFactory } from "../interfaces/factory/INexusFactory.sol"; +import { ProxyLib } from "../lib/ProxyLib.sol"; + +/// @title Nexus Account Factory +/// @notice Manages the creation of Modular Smart Accounts compliant with ERC-7579 and ERC-4337 using a factory pattern. +/// @author @livingrockrises | Biconomy | chirag@biconomy.io +/// @author @aboudjem | Biconomy | adam.boudjemaa@biconomy.io +/// @author @filmakarov | Biconomy | filipp.makarov@biconomy.io +/// @author @zeroknots | Rhinestone.wtf | zeroknots.eth +/// Special thanks to the Solady team for foundational contributions: https://github.com/Vectorized/solady +contract NexusAccountFactory is Stakeable, INexusFactory { + /// @notice Address of the implementation contract used to create new Nexus instances. + /// @dev This address is immutable and set upon deployment, ensuring the implementation cannot be changed. + address public immutable ACCOUNT_IMPLEMENTATION; + + /// @notice Constructor to set the smart account implementation address and the factory owner. + /// @param implementation_ The address of the Nexus implementation to be used for all deployments. + /// @param owner_ The address of the owner of the factory. + constructor(address implementation_, address owner_) Stakeable(owner_) { + require(implementation_ != address(0), ImplementationAddressCanNotBeZero()); + require(owner_ != address(0), ZeroAddressNotAllowed()); + ACCOUNT_IMPLEMENTATION = implementation_; + } + + /// @notice Creates a new Nexus account with the provided initialization data. + /// @param initData Initialization data to be called on the new Smart Account. + /// @param salt Unique salt for the Smart Account creation. + /// @return The address of the newly created Nexus account. + function createAccount(bytes calldata initData, bytes32 salt) external payable override returns (address payable) { + // Deploy the Nexus account using the ProxyLib + (bool alreadyDeployed, address payable account) = ProxyLib.deployProxy(ACCOUNT_IMPLEMENTATION, salt, initData); + if (!alreadyDeployed) { + emit AccountCreated(account, initData, salt); + } + return account; + } + + /// @notice Computes the expected address of a Nexus contract using the factory's deterministic deployment algorithm. + /// @param initData - Initialization data to be called on the new Smart Account. + /// @param salt - Unique salt for the Smart Account creation. + /// @return expectedAddress The expected address at which the Nexus contract will be deployed if the provided parameters are used. + function computeAccountAddress(bytes calldata initData, bytes32 salt) external view override returns (address payable expectedAddress) { + // Return the expected address of the Nexus account using the provided initialization data and salt + return ProxyLib.predictProxyAddress(ACCOUNT_IMPLEMENTATION, salt, initData); + } +} diff --git a/biconomy/nexus/1.2.0/contracts/interfaces/IERC4337Account.sol b/biconomy/nexus/1.2.0/contracts/interfaces/IERC4337Account.sol new file mode 100644 index 0000000..d651cc5 --- /dev/null +++ b/biconomy/nexus/1.2.0/contracts/interfaces/IERC4337Account.sol @@ -0,0 +1,70 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.27; + +// ────────────────────────────────────────────────────────────────────────────── +// _ __ _ __ +// / | / /__ | |/ /_ _______ +// / |/ / _ \| / / / / ___/ +// / /| / __/ / /_/ (__ ) +// /_/ |_/\___/_/|_\__,_/____/ +// +// ────────────────────────────────────────────────────────────────────────────── +// Nexus: A suite of contracts for Modular Smart Accounts compliant with ERC-7579 and ERC-4337, developed by Biconomy. +// Learn more at https://biconomy.io. To report security issues, please contact us at: security@biconomy.io + +import { PackedUserOperation } from "account-abstraction/interfaces/PackedUserOperation.sol"; + +/// @title Nexus - IERC4337Account +/// @notice This interface defines the necessary validation and execution methods for smart accounts under the ERC-4337 standard. +/// @dev Provides a structure for implementing custom validation logic and execution methods that comply with ERC-4337 "account abstraction" specs. +/// The validation method ensures proper signature and nonce verification before proceeding with transaction execution, critical for securing userOps. +/// Also allows for the optional definition of an execution method to handle transactions post-validation, enhancing flexibility. +/// @author @livingrockrises | Biconomy | chirag@biconomy.io +/// @author @aboudjem | Biconomy | adam.boudjemaa@biconomy.io +/// @author @filmakarov | Biconomy | filipp.makarov@biconomy.io +/// @author @zeroknots | Rhinestone.wtf | zeroknots.eth +/// Special thanks to the Solady team for foundational contributions: https://github.com/Vectorized/solady +interface IERC4337Account { + /// Validate user's signature and nonce + /// the entryPoint will make the call to the recipient only if this validation call returns successfully. + /// signature failure should be reported by returning SIG_VALIDATION_FAILED (1). + /// This allows making a "simulation call" without a valid signature + /// Other failures (e.g. nonce mismatch, or invalid signature format) should still revert to signal failure. + /// + /// @dev ERC-4337-v-0.7 validation stage + /// @dev Must validate caller is the entryPoint. + /// Must validate the signature and nonce + /// @param userOp - The user operation that is about to be executed. + /// @param userOpHash - Hash of the user's request data. can be used as the basis for signature. + /// @param missingAccountFunds - Missing funds on the account's deposit in the entrypoint. + /// This is the minimum amount to transfer to the sender(entryPoint) to be + /// able to make the call. The excess is left as a deposit in the entrypoint + /// for future calls. Can be withdrawn anytime using "entryPoint.withdrawTo()". + /// In case there is a paymaster in the request (or the current deposit is high + /// enough), this value will be zero. + /// @return validationData - Packaged ValidationData structure. use `_packValidationData` and + /// `_unpackValidationData` to encode and decode. + /// <20-byte> sigAuthorizer - 0 for valid signature, 1 to mark signature failure, + /// otherwise, an address of an "authorizer" contract. + /// <6-byte> validUntil - Last timestamp this operation is valid. 0 for "indefinite" + /// <6-byte> validAfter - First timestamp this operation is valid + /// If an account doesn't use time-range, it is enough to + /// return SIG_VALIDATION_FAILED value (1) for signature failure. + /// Note that the validation code cannot use block.timestamp (or block.number) directly. + function validateUserOp( + PackedUserOperation calldata userOp, + bytes32 userOpHash, + uint256 missingAccountFunds + ) external returns (uint256 validationData); + + /// Account may implement this execute method. + /// passing this methodSig at the beginning of callData will cause the entryPoint to pass the + /// full UserOp (and hash) + /// to the account. + /// The account should skip the methodSig, and use the callData (and optionally, other UserOp + /// fields) + /// @dev ERC-4337-v-0.7 optional execution path + /// @param userOp - The operation that was just validated. + /// @param userOpHash - Hash of the user's request data. + function executeUserOp(PackedUserOperation calldata userOp, bytes32 userOpHash) external payable; +} diff --git a/biconomy/nexus/1.2.0/contracts/interfaces/IERC7484.sol b/biconomy/nexus/1.2.0/contracts/interfaces/IERC7484.sol new file mode 100644 index 0000000..ac7f722 --- /dev/null +++ b/biconomy/nexus/1.2.0/contracts/interfaces/IERC7484.sol @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.27; + +interface IERC7484 { + event NewTrustedAttesters(); + + /** + * Allows Smart Accounts - the end users of the registry - to appoint + * one or many attesters as trusted. + * @dev this function reverts, if address(0), or duplicates are provided in attesters[] + * + * @param threshold The minimum number of attestations required for a module + * to be considered secure. + * @param attesters The addresses of the attesters to be trusted. + */ + function trustAttesters(uint8 threshold, address[] calldata attesters) external; + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* Check with Registry internal attesters */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + function check(address module) external view; + + function checkForAccount(address smartAccount, address module) external view; + + function check(address module, uint256 moduleType) external view; + + function checkForAccount(address smartAccount, address module, uint256 moduleType) external view; + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* Check with external attester(s) */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + function check(address module, address[] calldata attesters, uint256 threshold) external view; + + function check(address module, uint256 moduleType, address[] calldata attesters, uint256 threshold) external view; +} diff --git a/biconomy/nexus/1.2.0/contracts/interfaces/IERC7579Account.sol b/biconomy/nexus/1.2.0/contracts/interfaces/IERC7579Account.sol new file mode 100644 index 0000000..471e1ce --- /dev/null +++ b/biconomy/nexus/1.2.0/contracts/interfaces/IERC7579Account.sol @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.27; + +// ────────────────────────────────────────────────────────────────────────────── +// _ __ _ __ +// / | / /__ | |/ /_ _______ +// / |/ / _ \| / / / / ___/ +// / /| / __/ / /_/ (__ ) +// /_/ |_/\___/_/|_\__,_/____/ +// +// ────────────────────────────────────────────────────────────────────────────── +// Nexus: A suite of contracts for Modular Smart Accounts compliant with ERC-7579 and ERC-4337, developed by Biconomy. +// Learn more at https://biconomy.io. To report security issues, please contact us at: security@biconomy.io + +import { IAccountConfig } from "./base/IAccountConfig.sol"; +import { IExecutionHelper } from "./base/IExecutionHelper.sol"; +import { IModuleManager } from "./base/IModuleManager.sol"; + +/// @title Nexus - IERC7579Account +/// @notice This interface integrates the functionalities required for a modular smart account compliant with ERC-7579 and ERC-4337 standards. +/// @dev Combines configurations and operational management for smart accounts, bridging IAccountConfig, IExecutionHelper, and IModuleManager. +/// Interfaces designed to support the comprehensive management of smart account operations including execution management and modular configurations. +/// @author @livingrockrises | Biconomy | chirag@biconomy.io +/// @author @aboudjem | Biconomy | adam.boudjemaa@biconomy.io +/// @author @filmakarov | Biconomy | filipp.makarov@biconomy.io +/// @author @zeroknots | Rhinestone.wtf | zeroknots.eth +/// Special thanks to the Solady team for foundational contributions: https://github.com/Vectorized/solady +interface IERC7579Account is IAccountConfig, IExecutionHelper { + /// @dev Validates a smart account signature according to ERC-1271 standards. + /// This method may delegate the call to a validator module to check the signature. + /// @param hash The hash of the data being validated. + /// @param data The signed data to validate. + function isValidSignature(bytes32 hash, bytes calldata data) external view returns (bytes4); +} diff --git a/biconomy/nexus/1.2.0/contracts/interfaces/INexus.sol b/biconomy/nexus/1.2.0/contracts/interfaces/INexus.sol new file mode 100644 index 0000000..31b379d --- /dev/null +++ b/biconomy/nexus/1.2.0/contracts/interfaces/INexus.sol @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.27; + +// ────────────────────────────────────────────────────────────────────────────── +// _ __ _ __ +// / | / /__ | |/ /_ _______ +// / |/ / _ \| / / / / ___/ +// / /| / __/ / /_/ (__ ) +// /_/ |_/\___/_/|_\__,_/____/ +// +// ────────────────────────────────────────────────────────────────────────────── +// Nexus: A suite of contracts for Modular Smart Accounts compliant with ERC-7579 and ERC-4337, developed by Biconomy. +// Learn more at https://biconomy.io. To report security issues, please contact us at: security@biconomy.io + +import { IERC4337Account } from "./IERC4337Account.sol"; +import { IERC7579Account } from "./IERC7579Account.sol"; +import { INexusEventsAndErrors } from "./INexusEventsAndErrors.sol"; + +/// @title Nexus - INexus Interface +/// @notice Integrates ERC-4337 and ERC-7579 standards to manage smart accounts within the Nexus suite. +/// @dev Consolidates ERC-4337 user operations and ERC-7579 configurations into a unified interface for smart account management. +/// It extends both IERC4337Account and IERC7579Account, enhancing modular capabilities and supporting advanced contract architectures. +/// Includes error definitions for robust handling of common issues such as unsupported module types and execution failures. +/// The initialize function sets up the account with validators and configurations, ensuring readiness for use. +/// @author @livingrockrises | Biconomy | chirag@biconomy.io +/// @author @aboudjem | Biconomy | adam.boudjemaa@biconomy.io +/// @author @filmakarov | Biconomy | filipp.makarov@biconomy.io +/// @author @zeroknots | Rhinestone.wtf | zeroknots.eth +/// Special thanks to the Solady team for foundational contributions: https://github.com/Vectorized/solady +interface INexus is IERC4337Account, IERC7579Account, INexusEventsAndErrors { + /// @notice Initializes the smart account with a validator and custom data. + /// @dev This method sets up the account for operation, linking it with a validator and initializing it with specific data. + /// Can be called directly or via a factory. + /// @param initData Encoded data used for the account's configuration during initialization. + function initializeAccount(bytes calldata initData) external payable; +} diff --git a/biconomy/nexus/1.2.0/contracts/interfaces/INexusEventsAndErrors.sol b/biconomy/nexus/1.2.0/contracts/interfaces/INexusEventsAndErrors.sol new file mode 100644 index 0000000..ca7ed87 --- /dev/null +++ b/biconomy/nexus/1.2.0/contracts/interfaces/INexusEventsAndErrors.sol @@ -0,0 +1,68 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.27; + +// ────────────────────────────────────────────────────────────────────────────── +// _ __ _ __ +// / | / /__ | |/ /_ _______ +// / |/ / _ \| / / / / ___/ +// / /| / __/ / /_/ (__ ) +// /_/ |_/\___/_/|_\__,_/____/ +// +// ────────────────────────────────────────────────────────────────────────────── +// Nexus: A suite of contracts for Modular Smart Accounts compliant with ERC-7579 and ERC-4337, developed by Biconomy. +// Learn more at https://biconomy.io. To report security issues, please contact us at: security@biconomy.io + +import { PackedUserOperation } from "account-abstraction/interfaces/PackedUserOperation.sol"; + +/// @title Nexus - INexus Events and Errors +/// @notice Defines common errors for the Nexus smart account management interface. +/// @author @livingrockrises | Biconomy | chirag@biconomy.io +/// @author @aboudjem | Biconomy | adam.boudjemaa@biconomy.io +/// @author @filmakarov | Biconomy | filipp.makarov@biconomy.io +/// @author @zeroknots | Rhinestone.wtf | zeroknots.eth +/// Special thanks to the Solady team for foundational contributions: https://github.com/Vectorized/solady +interface INexusEventsAndErrors { + /// @notice Emitted when a PREP is initialized. + /// @param r The r value of the PREP signature. + event PREPInitialized(bytes32 r); + + /// @notice Error thrown when an unsupported ModuleType is requested. + /// @param moduleTypeId The ID of the unsupported module type. + error UnsupportedModuleType(uint256 moduleTypeId); + + /// @notice Error thrown on failed execution. + error ExecutionFailed(); + + /// @notice Error thrown when the Factory fails to initialize the account with posted bootstrap data. + error NexusInitializationFailed(); + + /// @notice Error thrown when a zero address is provided as the Entry Point address. + error EntryPointCanNotBeZero(); + + /// @notice Error thrown when the provided implementation address is invalid. + error InvalidImplementationAddress(); + + /// @notice Error thrown when the provided implementation address is not a contract. + error ImplementationIsNotAContract(); + + /// @notice Error thrown when an inner call fails. + error InnerCallFailed(); + + /// @notice Error thrown when attempted to emergency-uninstall a hook + error EmergencyTimeLockNotExpired(); + + /// @notice Error thrown when attempted to upgrade an ERC7702 account via UUPS proxy upgrade mechanism + error ERC7702AccountCannotBeUpgradedThisWay(); + + /// @notice Error thrown when the provided initData is invalid. + error InvalidInitData(); + + /// @notice Error thrown when the provided authHash and erc7702AuthSignature are invalid. + error InvalidPREP(); + + /// @notice Error thrown when the account is already initialized. + error AccountAlreadyInitialized(); + + /// @notice Error thrown when the account is not initialized but expected to be. + error AccountNotInitialized(); +} diff --git a/biconomy/nexus/1.2.0/contracts/interfaces/base/IAccountConfig.sol b/biconomy/nexus/1.2.0/contracts/interfaces/base/IAccountConfig.sol new file mode 100644 index 0000000..b08ffb3 --- /dev/null +++ b/biconomy/nexus/1.2.0/contracts/interfaces/base/IAccountConfig.sol @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.27; + +// ────────────────────────────────────────────────────────────────────────────── +// _ __ _ __ +// / | / /__ | |/ /_ _______ +// / |/ / _ \| / / / / ___/ +// / /| / __/ / /_/ (__ ) +// /_/ |_/\___/_/|_\__,_/____/ +// +// ────────────────────────────────────────────────────────────────────────────── +// Nexus: A suite of contracts for Modular Smart Accounts compliant with ERC-7579 and ERC-4337, developed by Biconomy. +// Learn more at https://biconomy.io. To report security issues, please contact us at: security@biconomy.io + +import { ExecutionMode } from "../../lib/ModeLib.sol"; + +/// @title Nexus - ERC-7579 Account Configuration Interface +/// @notice Interface for querying and verifying configurations of Smart Accounts compliant with ERC-7579. +/// @dev Provides methods to check supported execution modes and module types for Smart Accounts, ensuring flexible and extensible configuration. +/// @author @livingrockrises | Biconomy | chirag@biconomy.io +/// @author @aboudjem | Biconomy | adam.boudjemaa@biconomy.io +/// @author @filmakarov | Biconomy | filipp.makarov@biconomy.io +/// @author @zeroknots | Rhinestone.wtf | zeroknots.eth +/// Special thanks to the Solady team for foundational contributions: https://github.com/Vectorized/solady +interface IAccountConfig { + /// @notice Returns the account ID in a structured format: "vendorname.accountname.semver" + /// @return accountImplementationId The account ID of the smart account + function accountId() external view returns (string memory accountImplementationId); + + /// @notice Checks if the account supports a certain execution mode. + /// @param encodedMode The encoded mode to verify. + /// @return supported True if the account supports the mode, false otherwise. + function supportsExecutionMode(ExecutionMode encodedMode) external view returns (bool supported); + + /// @notice Checks if the account supports a specific module type. + /// @param moduleTypeId The module type ID to verify. + /// @return supported True if the account supports the module type, false otherwise. + function supportsModule(uint256 moduleTypeId) external view returns (bool supported); +} diff --git a/biconomy/nexus/1.2.0/contracts/interfaces/base/IBaseAccount.sol b/biconomy/nexus/1.2.0/contracts/interfaces/base/IBaseAccount.sol new file mode 100644 index 0000000..d847099 --- /dev/null +++ b/biconomy/nexus/1.2.0/contracts/interfaces/base/IBaseAccount.sol @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.27; + +// ────────────────────────────────────────────────────────────────────────────── +// _ __ _ __ +// / | / /__ | |/ /_ _______ +// / |/ / _ \| / / / / ___/ +// / /| / __/ / /_/ (__ ) +// /_/ |_/\___/_/|_\__,_/____/ +// +// ────────────────────────────────────────────────────────────────────────────── +// Nexus: A suite of contracts for Modular Smart Accounts compliant with ERC-7579 and ERC-4337, developed by Biconomy. +// Learn more at https://biconomy.io. To report security issues, please contact us at: security@biconomy.io + +import { IBaseAccountEventsAndErrors } from "./IBaseAccountEventsAndErrors.sol"; + +/// @title Nexus - IBaseAccount +/// @notice Interface for the BaseAccount functionalities compliant with ERC-7579 and ERC-4337. +/// @dev Interface for organizing the base functionalities using the Nexus suite. +/// @author @livingrockrises | Biconomy | chirag@biconomy.io +/// @author @aboudjem | Biconomy | adam.boudjemaa@biconomy.io +/// @author @filmakarov | Biconomy | filipp.makarov@biconomy.io +/// @author @zeroknots | Rhinestone.wtf | zeroknots.eth +/// Special thanks to the Solady team for foundational contributions: https://github.com/Vectorized/solady +interface IBaseAccount is IBaseAccountEventsAndErrors { + /// @notice Adds deposit to the EntryPoint to fund transactions. + function addDeposit() external payable; + + /// @notice Withdraws ETH from the EntryPoint to a specified address. + /// @param to The address to receive the withdrawn funds. + /// @param amount The amount to withdraw. + function withdrawDepositTo(address to, uint256 amount) external payable; + + /// @notice Gets the nonce for a particular key. + /// @param key The nonce key. + /// @return The nonce associated with the key. + function nonce(uint192 key) external view returns (uint256); + + /// @notice Returns the current deposit balance of this account on the EntryPoint. + /// @return The current balance held at the EntryPoint. + function getDeposit() external view returns (uint256); + + /// @notice Retrieves the address of the EntryPoint contract, currently using version 0.7. + /// @return The address of the EntryPoint contract. + function entryPoint() external view returns (address); +} diff --git a/biconomy/nexus/1.2.0/contracts/interfaces/base/IBaseAccountEventsAndErrors.sol b/biconomy/nexus/1.2.0/contracts/interfaces/base/IBaseAccountEventsAndErrors.sol new file mode 100644 index 0000000..d003370 --- /dev/null +++ b/biconomy/nexus/1.2.0/contracts/interfaces/base/IBaseAccountEventsAndErrors.sol @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.27; + +// ────────────────────────────────────────────────────────────────────────────── +// _ __ _ __ +// / | / /__ | |/ /_ _______ +// / |/ / _ \| / / / / ___/ +// / /| / __/ / /_/ (__ ) +// /_/ |_/\___/_/|_\__,_/____/ +// +// ────────────────────────────────────────────────────────────────────────────── +// Nexus: A suite of contracts for Modular Smart Accounts compliant with ERC-7579 and ERC-4337, developed by Biconomy. +// Learn more at https://biconomy.io. To report security issues, please contact us at: security@biconomy.io + +/// @title Execution Manager Events and Errors Interface +/// @notice Interface for defining events and errors related to transaction execution processes within smart accounts. +/// @dev This interface defines events and errors used by execution manager to handle and report the operational status of smart account transactions. +/// It is a part of the Nexus suite of contracts aimed at implementing flexible and secure smart account operations. +/// @author @livingrockrises | Biconomy | chirag@biconomy.io +/// @author @aboudjem | Biconomy | adam.boudjemaa@biconomy.io +/// @author @filmakarov | Biconomy | filipp.makarov@biconomy.io +/// @author @zeroknots | Rhinestone.wtf | zeroknots.eth +/// Special thanks to the Solady team for foundational contributions: https://github.com/Vectorized/solady +interface IBaseAccountEventsAndErrors { + /// @dev Throws an error when a caller is not authorized to access an account. + error AccountAccessUnauthorized(); +} diff --git a/biconomy/nexus/1.2.0/contracts/interfaces/base/IExecutionHelper.sol b/biconomy/nexus/1.2.0/contracts/interfaces/base/IExecutionHelper.sol new file mode 100644 index 0000000..a2c5221 --- /dev/null +++ b/biconomy/nexus/1.2.0/contracts/interfaces/base/IExecutionHelper.sol @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.27; + +// ────────────────────────────────────────────────────────────────────────────── +// _ __ _ __ +// / | / /__ | |/ /_ _______ +// / |/ / _ \| / / / / ___/ +// / /| / __/ / /_/ (__ ) +// /_/ |_/\___/_/|_\__,_/____/ +// +// ────────────────────────────────────────────────────────────────────────────── +// Nexus: A suite of contracts for Modular Smart Accounts compliant with ERC-7579 and ERC-4337, developed by Biconomy. +// Learn more at https://biconomy.io. To report security issues, please contact us at: security@biconomy.io + +import { ExecutionMode } from "../../lib/ModeLib.sol"; + +import { IExecutionHelperEventsAndErrors } from "./IExecutionHelperEventsAndErrors.sol"; + +/// @title Nexus - IExecutionHelper +/// @notice Interface for executing transactions on behalf of smart accounts within the Nexus system. +/// @dev Extends functionality for transaction execution with error handling as defined in IExecutionHelperEventsAndErrors. +/// @author @livingrockrises | Biconomy | chirag@biconomy.io +/// @author @aboudjem | Biconomy | adam.boudjemaa@biconomy.io +/// @author @filmakarov | Biconomy | filipp.makarov@biconomy.io +/// @author @zeroknots | Rhinestone.wtf | zeroknots.eth +/// Special thanks to the Solady team for foundational contributions: https://github.com/Vectorized/solady +interface IExecutionHelper is IExecutionHelperEventsAndErrors { + /// @notice Executes a transaction with specified execution mode and calldata. + /// @param mode The execution mode, defining how the transaction is processed. + /// @param executionCalldata The calldata to execute. + /// @dev This function ensures that the execution complies with smart account execution policies and handles errors appropriately. + function execute(ExecutionMode mode, bytes calldata executionCalldata) external payable; + + /// @notice Allows an executor module to perform transactions on behalf of the account. + /// @param mode The execution mode that details how the transaction should be handled. + /// @param executionCalldata The transaction data to be executed. + /// @return returnData The result of the execution, allowing for error handling and results interpretation by the executor module. + function executeFromExecutor(ExecutionMode mode, bytes calldata executionCalldata) external payable returns (bytes[] memory returnData); +} diff --git a/biconomy/nexus/1.2.0/contracts/interfaces/base/IExecutionHelperEventsAndErrors.sol b/biconomy/nexus/1.2.0/contracts/interfaces/base/IExecutionHelperEventsAndErrors.sol new file mode 100644 index 0000000..34de7db --- /dev/null +++ b/biconomy/nexus/1.2.0/contracts/interfaces/base/IExecutionHelperEventsAndErrors.sol @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.27; + +// ────────────────────────────────────────────────────────────────────────────── +// _ __ _ __ +// / | / /__ | |/ /_ _______ +// / |/ / _ \| / / / / ___/ +// / /| / __/ / /_/ (__ ) +// /_/ |_/\___/_/|_\__,_/____/ +// +// ────────────────────────────────────────────────────────────────────────────── +// Nexus: A suite of contracts for Modular Smart Accounts compliant with ERC-7579 and ERC-4337, developed by Biconomy. +// Learn more at https://biconomy.io. To report security issues, please contact us at: security@biconomy.io + +/// @title Execution Manager Events and Errors Interface +/// @notice Interface for defining events and errors related to transaction execution processes within smart accounts. +/// @dev This interface defines events and errors used by execution manager to handle and report the operational status of smart account transactions. +/// It is a part of the Nexus suite of contracts aimed at implementing flexible and secure smart account operations. +/// @author @livingrockrises | Biconomy | chirag@biconomy.io +/// @author @aboudjem | Biconomy | adam.boudjemaa@biconomy.io +/// @author @filmakarov | Biconomy | filipp.makarov@biconomy.io +/// @author @zeroknots | Rhinestone.wtf | zeroknots.eth +/// Special thanks to the Solady team for foundational contributions: https://github.com/Vectorized/solady + +import { ExecType } from "../../lib/ModeLib.sol"; + +interface IExecutionHelperEventsAndErrors { + /// @notice Event emitted when a transaction fails to execute successfully. + event TryExecuteUnsuccessful(bytes callData, bytes result); + + /// @notice Event emitted when a transaction fails to execute successfully. + event TryDelegateCallUnsuccessful(bytes callData, bytes result); + + /// @notice Error thrown when an execution with an unsupported ExecType was made. + /// @param execType The unsupported execution type. + error UnsupportedExecType(ExecType execType); +} diff --git a/biconomy/nexus/1.2.0/contracts/interfaces/base/IModuleManager.sol b/biconomy/nexus/1.2.0/contracts/interfaces/base/IModuleManager.sol new file mode 100644 index 0000000..f56f7df --- /dev/null +++ b/biconomy/nexus/1.2.0/contracts/interfaces/base/IModuleManager.sol @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.27; + +// ────────────────────────────────────────────────────────────────────────────── +// _ __ _ __ +// / | / /__ | |/ /_ _______ +// / |/ / _ \| / / / / ___/ +// / /| / __/ / /_/ (__ ) +// /_/ |_/\___/_/|_\__,_/____/ +// +// ────────────────────────────────────────────────────────────────────────────── +// Nexus: A suite of contracts for Modular Smart Accounts compliant with ERC-7579 and ERC-4337, developed by Biconomy. +// Learn more at https://biconomy.io. To report security issues, please contact us at: security@biconomy.io + +import { IModuleManagerEventsAndErrors } from "./IModuleManagerEventsAndErrors.sol"; + +/// @title Nexus - IModuleManager +/// @notice Interface for managing modules within Smart Accounts, providing methods for installation and removal of modules. +/// @dev Extends the IModuleManagerEventsAndErrors interface to include event and error definitions. +/// @author @livingrockrises | Biconomy | chirag@biconomy.io +/// @author @aboudjem | Biconomy | adam.boudjemaa@biconomy.io +/// @author @filmakarov | Biconomy | filipp.makarov@biconomy.io +/// @author @zeroknots | Rhinestone.wtf | zeroknots.eth +/// Special thanks to the Solady team for foundational contributions: https://github.com/Vectorized/solady +interface IModuleManager is IModuleManagerEventsAndErrors { + /// @notice Installs a Module of a specific type onto the smart account. + /// @param moduleTypeId The identifier for the module type. + /// @param module The address of the module to be installed. + /// @param initData Initialization data for configuring the module upon installation. + function installModule(uint256 moduleTypeId, address module, bytes calldata initData) external payable; + + /// @notice Uninstalls a Module of a specific type from the smart account. + /// @param moduleTypeId The identifier for the module type being uninstalled. + /// @param module The address of the module to uninstall. + /// @param deInitData De-initialization data for configuring the module upon uninstallation. + function uninstallModule(uint256 moduleTypeId, address module, bytes calldata deInitData) external payable; + + /// @notice Checks if a specific module is installed on the smart account. + /// @param moduleTypeId The module type identifier to check. + /// @param module The address of the module. + /// @param additionalContext Additional information that may be required to verify the module's installation. + /// @return installed True if the module is installed, false otherwise. + function isModuleInstalled(uint256 moduleTypeId, address module, bytes calldata additionalContext) external view returns (bool installed); +} diff --git a/biconomy/nexus/1.2.0/contracts/interfaces/base/IModuleManagerEventsAndErrors.sol b/biconomy/nexus/1.2.0/contracts/interfaces/base/IModuleManagerEventsAndErrors.sol new file mode 100644 index 0000000..0a7ab96 --- /dev/null +++ b/biconomy/nexus/1.2.0/contracts/interfaces/base/IModuleManagerEventsAndErrors.sol @@ -0,0 +1,111 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.27; + +// ────────────────────────────────────────────────────────────────────────────── +// _ __ _ __ +// / | / /__ | |/ /_ _______ +// / |/ / _ \| / / / / ___/ +// / /| / __/ / /_/ (__ ) +// /_/ |_/\___/_/|_\__,_/____/ +// +// ────────────────────────────────────────────────────────────────────────────── +// Nexus: A suite of contracts for Modular Smart Accounts compliant with ERC-7579 and ERC-4337, developed by Biconomy. +// Learn more at https://biconomy.io. To report security issues, please contact us at: security@biconomy.io + +import { CallType } from "../../lib/ModeLib.sol"; + +/// @title ERC-7579 Module Manager Events and Errors Interface +/// @notice Provides event and error definitions for actions related to module management in smart accounts. +/// @dev Used by IModuleManager to define the events and errors associated with the installation and management of modules. +/// @author @livingrockrises | Biconomy | chirag@biconomy.io +/// @author @aboudjem | Biconomy | adam.boudjemaa@biconomy.io +/// @author @filmakarov | Biconomy | filipp.makarov@biconomy.io +/// @author @zeroknots | Rhinestone.wtf | zeroknots.eth +/// Special thanks to the Solady team for foundational contributions: https://github.com/Vectorized/solady +interface IModuleManagerEventsAndErrors { + /// @notice Emitted when a module is installed onto a smart account. + /// @param moduleTypeId The identifier for the type of module installed. + /// @param module The address of the installed module. + event ModuleInstalled(uint256 moduleTypeId, address module); + + /// @notice Emitted when a module is uninstalled from a smart account. + /// @param moduleTypeId The identifier for the type of module uninstalled. + /// @param module The address of the uninstalled module. + event ModuleUninstalled(uint256 moduleTypeId, address module); + + /// @notice Thrown when attempting to remove the last validator. + error CanNotRemoveLastValidator(); + + /// @dev Thrown when the specified module address is not recognized as valid. + error ValidatorNotInstalled(address module); + + /// @dev Thrown when there is no installed validator detected. + error NoValidatorInstalled(); + + /// @dev Thrown when the specified module address is not recognized as valid. + error InvalidModule(address module); + + /// @dev Thrown when an invalid module type identifier is provided. + error InvalidModuleTypeId(uint256 moduleTypeId); + + /// @dev Thrown when there is an attempt to install a module that is already installed. + error ModuleAlreadyInstalled(uint256 moduleTypeId, address module); + + /// @dev Thrown when an operation is performed by an unauthorized operator. + error UnauthorizedOperation(address operator); + + /// @dev Thrown when there is an attempt to uninstall a module that is not installed. + error ModuleNotInstalled(uint256 moduleTypeId, address module); + + /// @dev Thrown when a module address is set to zero. + error ModuleAddressCanNotBeZero(); + + /// @dev Thrown when a post-check fails after hook execution. + error HookPostCheckFailed(); + + /// @dev Thrown when there is an attempt to install a hook while another is already installed. + error HookAlreadyInstalled(address currentHook); + + /// @dev Thrown when there is an attempt to install a PreValidationHook while another is already installed. + error PrevalidationHookAlreadyInstalled(address currentPreValidationHook); + + /// @dev Thrown when there is an attempt to install a fallback handler for a selector already having one. + error FallbackAlreadyInstalledForSelector(bytes4 selector); + + /// @dev Thrown when there is an attempt to uninstall a fallback handler for a selector that does not have one installed. + error FallbackNotInstalledForSelector(bytes4 selector); + + /// @dev Thrown when a fallback handler fails to uninstall properly. + error FallbackHandlerUninstallFailed(); + + /// @dev Thrown when no fallback handler is available for a given selector. + error MissingFallbackHandler(bytes4 selector); + + /// @dev Thrown when Invalid data is provided for MultiType install flow + error InvalidInput(); + + /// @dev Thrown when unable to validate Module Enable Mode signature + error EnableModeSigError(); + + /// @dev Thrown when unable to validate Emergency Uninstall signature + error EmergencyUninstallSigError(); + + /// @notice Error thrown when an invalid nonce is used + error InvalidNonce(); + + /// Error thrown when account installs/uninstalls module with mismatched moduleTypeId + error MismatchModuleTypeId(); + + /// @dev Thrown when there is an attempt to install a forbidden selector as a fallback handler. + error FallbackSelectorForbidden(); + + /// @dev Thrown when there is an attempt to install a fallback handler with an invalid calltype for a given selector. + error FallbackCallTypeInvalid(); + + /// @notice Error thrown when an execution with an unsupported CallType was made. + /// @param callType The unsupported call type. + error UnsupportedCallType(CallType callType); + + /// @notice Error thrown when the default validator is already installed. + error DefaultValidatorAlreadyInstalled(); +} diff --git a/biconomy/nexus/1.2.0/contracts/interfaces/base/IStorage.sol b/biconomy/nexus/1.2.0/contracts/interfaces/base/IStorage.sol new file mode 100644 index 0000000..37886b4 --- /dev/null +++ b/biconomy/nexus/1.2.0/contracts/interfaces/base/IStorage.sol @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.27; + +// ────────────────────────────────────────────────────────────────────────────── +// _ __ _ __ +// / | / /__ | |/ /_ _______ +// / |/ / _ \| / / / / ___/ +// / /| / __/ / /_/ (__ ) +// /_/ |_/\___/_/|_\__,_/____/ +// +// ────────────────────────────────────────────────────────────────────────────── +// Nexus: A suite of contracts for Modular Smart Accounts compliant with ERC-7579 and ERC-4337, developed by Biconomy. +// Learn more at https://biconomy.io. To report security issues, please contact us at: security@biconomy.io + +import { SentinelListLib } from "sentinellist/SentinelList.sol"; +import { IPreValidationHookERC1271, IPreValidationHookERC4337 } from "../modules/IPreValidationHook.sol"; +import { IHook } from "../modules/IHook.sol"; +import { CallType } from "../../lib/ModeLib.sol"; + +/// @title Nexus - IStorage Interface +/// @notice Provides structured storage for Modular Smart Account under the Nexus suite, compliant with ERC-7579 and ERC-4337. +/// @dev Manages structured storage using SentinelListLib for validators and executors, and a mapping for fallback handlers. +/// This interface utilizes ERC-7201 storage location practices to ensure isolated and collision-resistant storage spaces within smart contracts. +/// It is designed to support dynamic execution and modular management strategies essential for advanced smart account architectures. +/// @custom:storage-location erc7201:biconomy.storage.Nexus +/// @author @livingrockrises | Biconomy | chirag@biconomy.io +/// @author @aboudjem | Biconomy | adam.boudjemaa@biconomy.io +/// @author @filmakarov | Biconomy | filipp.makarov@biconomy.io +/// @author @zeroknots | Rhinestone.wtf | zeroknots.eth +/// Special thanks to the Solady team for foundational contributions: https://github.com/Vectorized/solady +interface IStorage { + /// @notice Struct storing validators and executors using Sentinel lists, and fallback handlers via mapping. + struct AccountStorage { + ///< List of validators, initialized upon contract deployment. + SentinelListLib.SentinelList validators; + ///< List of executors, similarly initialized. + SentinelListLib.SentinelList executors; + ///< Mapping of selectors to their respective fallback handlers. + mapping(bytes4 => FallbackHandler) fallbacks; + ///< Current hook module associated with this account. + IHook hook; + ///< Mapping of hooks to requested timelocks. + mapping(address hook => uint256) emergencyUninstallTimelock; + ///< PreValidation hook for validateUserOp + IPreValidationHookERC4337 preValidationHookERC4337; + ///< PreValidation hook for isValidSignature + IPreValidationHookERC1271 preValidationHookERC1271; + ///< Mapping of used nonces for replay protection. + mapping(uint256 => bool) nonces; + ///< ERC-7484 registry + address registry; + } + + /// @notice Defines a fallback handler with an associated handler address and a call type. + struct FallbackHandler { + ///< The address of the fallback function handler. + address handler; + ///< The type of call this handler supports (e.g., static or call). + CallType calltype; + } +} diff --git a/biconomy/nexus/1.2.0/contracts/interfaces/common/IStakeable.sol b/biconomy/nexus/1.2.0/contracts/interfaces/common/IStakeable.sol new file mode 100644 index 0000000..94a2e15 --- /dev/null +++ b/biconomy/nexus/1.2.0/contracts/interfaces/common/IStakeable.sol @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.27; + +// ────────────────────────────────────────────────────────────────────────────── +// _ __ _ __ +// / | / /__ | |/ /_ _______ +// / |/ / _ \| / / / / ___/ +// / /| / __/ / /_/ (__ ) +// /_/ |_/\___/_/|_\__,_/____/ +// +// ────────────────────────────────────────────────────────────────────────────── +// Nexus: A suite of contracts for Modular Smart Accounts compliant with ERC-7579 and ERC-4337, developed by Biconomy. +// Learn more at https://biconomy.io. For security issues, contact: security@biconomy.io + +/// @title Stakeable Entity Interface +/// @notice Interface for staking, unlocking, and withdrawing Ether on an EntryPoint. +/// @dev Defines functions for managing stakes on an EntryPoint. +/// @author @livingrockrises | Biconomy | chirag@biconomy.io +/// @author @aboudjem | Biconomy | adam.boudjemaa@biconomy.io +/// @author @filmakarov | Biconomy | filipp.makarov@biconomy.io +/// @author @zeroknots | Rhinestone.wtf | zeroknots.eth +/// Special thanks to the Solady team for foundational contributions: https://github.com/Vectorized/solady +interface IStakeable { + /// @notice Stakes a certain amount of Ether on an EntryPoint. + /// @dev The contract should have enough Ether to cover the stake. + /// @param epAddress The address of the EntryPoint where the stake is added. + /// @param unstakeDelaySec The delay in seconds before the stake can be unlocked. + function addStake(address epAddress, uint32 unstakeDelaySec) external payable; + + /// @notice Unlocks the stake on an EntryPoint. + /// @dev This starts the unstaking delay after which funds can be withdrawn. + /// @param epAddress The address of the EntryPoint from which the stake is to be unlocked. + function unlockStake(address epAddress) external; + + /// @notice Withdraws the stake from an EntryPoint to a specified address. + /// @dev This can only be done after the unstaking delay has passed since the unlock. + /// @param epAddress The address of the EntryPoint where the stake is withdrawn from. + /// @param withdrawAddress The address to receive the withdrawn stake. + function withdrawStake(address epAddress, address payable withdrawAddress) external; +} diff --git a/biconomy/nexus/1.2.0/contracts/interfaces/factory/INexusFactory.sol b/biconomy/nexus/1.2.0/contracts/interfaces/factory/INexusFactory.sol new file mode 100644 index 0000000..2ad1858 --- /dev/null +++ b/biconomy/nexus/1.2.0/contracts/interfaces/factory/INexusFactory.sol @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.27; + +// ────────────────────────────────────────────────────────────────────────────── +// _ __ _ __ +// / | / /__ | |/ /_ _______ +// / |/ / _ \| / / / / ___/ +// / /| / __/ / /_/ (__ ) +// /_/ |_/\___/_/|_\__,_/____/ +// +// ────────────────────────────────────────────────────────────────────────────── +// Nexus: A suite of contracts for Modular Smart Accounts compliant with ERC-7579 and ERC-4337, developed by Biconomy. +// Learn more at https://biconomy.io. To report security issues, please contact us at: security@biconomy.io + +/// @title Interface for Abstract Nexus Factory +/// @notice Interface that provides the essential structure for Nexus factories. +/// @author @livingrockrises | Biconomy | chirag@biconomy.io +/// @author @aboudjem | Biconomy | adam.boudjemaa@biconomy.io +/// @author @filmakarov | Biconomy | filipp.makarov@biconomy.io +/// @author @zeroknots | Rhinestone.wtf | zeroknots.eth +/// Special thanks to the Solady team for foundational contributions: https://github.com/Vectorized/solady +interface INexusFactory { + /// @notice Emitted when a new Smart Account is created. + /// @param account The address of the newly created account. + /// @param initData Initialization data used for the new Smart Account. + /// @param salt Unique salt used during the creation of the Smart Account. + event AccountCreated(address indexed account, bytes indexed initData, bytes32 indexed salt); + + /// @notice Error indicating that the account is already deployed + /// @param account The address of the account that is already deployed + error AccountAlreadyDeployed(address account); + + /// @notice Error thrown when the owner address is zero. + error ZeroAddressNotAllowed(); + + /// @notice Error thrown when the implementation address is zero. + error ImplementationAddressCanNotBeZero(); + + /// @notice Creates a new Nexus with initialization data. + /// @param initData Initialization data to be called on the new Smart Account. + /// @param salt Unique salt for the Smart Account creation. + /// @return The address of the newly created Nexus. + function createAccount(bytes calldata initData, bytes32 salt) external payable returns (address payable); + + /// @notice Computes the expected address of a Nexus contract using the factory's deterministic deployment algorithm. + /// @param initData Initialization data to be called on the new Smart Account. + /// @param salt Unique salt for the Smart Account creation. + /// @return expectedAddress The expected address at which the Nexus contract will be deployed if the provided parameters are used. + function computeAccountAddress(bytes calldata initData, bytes32 salt) external view returns (address payable expectedAddress); +} diff --git a/biconomy/nexus/1.2.0/contracts/interfaces/modules/IExecutor.sol b/biconomy/nexus/1.2.0/contracts/interfaces/modules/IExecutor.sol new file mode 100644 index 0000000..111aa41 --- /dev/null +++ b/biconomy/nexus/1.2.0/contracts/interfaces/modules/IExecutor.sol @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.27; + +// ────────────────────────────────────────────────────────────────────────────── +// _ __ _ __ +// / | / /__ | |/ /_ _______ +// / |/ / _ \| / / / / ___/ +// / /| / __/ / /_/ (__ ) +// /_/ |_/\___/_/|_\__,_/____/ +// +// ────────────────────────────────────────────────────────────────────────────── +// Nexus: A suite of contracts for Modular Smart Accounts compliant with ERC-7579 and ERC-4337, developed by Biconomy. +// Learn more at https://biconomy.io. To report security issues, please contact us at: security@biconomy.io + +import { IModule } from "./IModule.sol"; + +/// @title Nexus - IExecutor Interface +/// @notice Defines the interface for Executor modules within the Nexus Smart Account framework, compliant with the ERC-7579 standard. +/// @dev Extends IModule to include functionalities specific to execution modules. +/// This interface is future-proof, allowing for expansion and integration of advanced features in subsequent versions. +/// @author @livingrockrises | Biconomy | chirag@biconomy.io +/// @author @aboudjem | Biconomy | adam.boudjemaa@biconomy.io +/// @author @filmakarov | Biconomy | filipp.makarov@biconomy.io +/// @author @zeroknots | Rhinestone.wtf | zeroknots.eth +/// Special thanks to the Solady team for foundational contributions: https://github.com/Vectorized/solady +interface IExecutor is IModule { + // Future methods for execution management will be defined here to accommodate evolving requirements. +} diff --git a/biconomy/nexus/1.2.0/contracts/interfaces/modules/IFallback.sol b/biconomy/nexus/1.2.0/contracts/interfaces/modules/IFallback.sol new file mode 100644 index 0000000..7412bdc --- /dev/null +++ b/biconomy/nexus/1.2.0/contracts/interfaces/modules/IFallback.sol @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.27; + +// ────────────────────────────────────────────────────────────────────────────── +// _ __ _ __ +// / | / /__ | |/ /_ _______ +// / |/ / _ \| / / / / ___/ +// / /| / __/ / /_/ (__ ) +// /_/ |_/\___/_/|_\__,_/____/ +// +// ────────────────────────────────────────────────────────────────────────────── +// Nexus: A suite of contracts for Modular Smart Accounts compliant with ERC-7579 and ERC-4337, developed by Biconomy. +// Learn more at https://biconomy.io. To report security issues, please contact us at: security@biconomy.io + +import { IModule } from "./IModule.sol"; + +/// @title Nexus - IFallback Interface +/// @notice Defines the interface for Fallback modules within the Nexus Smart Account framework, compliant with the ERC-7579 standard. +/// @dev Extends IModule to include functionalities specific to fallback modules. +/// This interface is future-proof, allowing for expansion and integration of advanced features in subsequent versions. +/// @author @livingrockrises | Biconomy | chirag@biconomy.io +/// @author @aboudjem | Biconomy | adam.boudjemaa@biconomy.io +/// @author @filmakarov | Biconomy | filipp.makarov@biconomy.io +/// @author @zeroknots | Rhinestone.wtf | zeroknots.eth +/// Special thanks to the Solady team for foundational contributions: https://github.com/Vectorized/solady +interface IFallback is IModule { + // Future methods for fallback management will be defined here to accommodate evolving blockchain technologies. +} diff --git a/biconomy/nexus/1.2.0/contracts/interfaces/modules/IHook.sol b/biconomy/nexus/1.2.0/contracts/interfaces/modules/IHook.sol new file mode 100644 index 0000000..d951f4c --- /dev/null +++ b/biconomy/nexus/1.2.0/contracts/interfaces/modules/IHook.sol @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.27; + +// ────────────────────────────────────────────────────────────────────────────── +// _ __ _ __ +// / | / /__ | |/ /_ _______ +// / |/ / _ \| / / / / ___/ +// / /| / __/ / /_/ (__ ) +// /_/ |_/\___/_/|_\__,_/____/ +// +// ────────────────────────────────────────────────────────────────────────────── +// Nexus: A suite of contracts for Modular Smart Accounts compliant with ERC-7579 and ERC-4337, developed by Biconomy. +// Learn more at https://biconomy.io. To report security issues, please contact us at: security@biconomy.io + +import { IModule } from "./IModule.sol"; + +/// @title Hook Management Interface +/// @notice Provides methods for pre-checks and post-checks of transactions to ensure conditions and state consistency. +/// @dev Defines two critical lifecycle hooks in the transaction process: `preCheck` and `postCheck`. +/// These methods facilitate validating conditions prior to execution and verifying state changes afterwards, respectively. +interface IHook is IModule { + /// @notice Performs checks before a transaction is executed, potentially modifying the transaction context. + /// @dev This method is called before the execution of a transaction to validate and possibly adjust execution context. + /// @param msgSender The original sender of the transaction. + /// @param msgValue The amount of wei sent with the call. + /// @param msgData The calldata of the transaction. + /// @return hookData Data that may be used or modified throughout the transaction lifecycle, passed to `postCheck`. + function preCheck(address msgSender, uint256 msgValue, bytes calldata msgData) external returns (bytes memory hookData); + + /// @notice Performs checks after a transaction is executed to ensure state consistency and log results. + /// @dev This method is called after the execution of a transaction to verify and react to the execution outcome. + /// @param hookData Data returned from `preCheck`, containing execution context or modifications. + function postCheck(bytes calldata hookData) external; +} diff --git a/biconomy/nexus/1.2.0/contracts/interfaces/modules/IModule.sol b/biconomy/nexus/1.2.0/contracts/interfaces/modules/IModule.sol new file mode 100644 index 0000000..efa9d87 --- /dev/null +++ b/biconomy/nexus/1.2.0/contracts/interfaces/modules/IModule.sol @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.27; + +// ────────────────────────────────────────────────────────────────────────────── +// _ __ _ __ +// / | / /__ | |/ /_ _______ +// / |/ / _ \| / / / / ___/ +// / /| / __/ / /_/ (__ ) +// /_/ |_/\___/_/|_\__,_/____/ +// +// ────────────────────────────────────────────────────────────────────────────── +// Nexus: A suite of contracts for Modular Smart Accounts compliant with ERC-7579 and ERC-4337, developed by Biconomy. +// Learn more at https://biconomy.io. To report security issues, please contact us at: security@biconomy.io + +/// @title Nexus - ERC-7579 Module Base Interface +/// @notice Interface for module management in smart accounts, complying with ERC-7579 specifications. +/// @dev Defines the lifecycle hooks and checks for modules within the smart account architecture. +/// This interface includes methods for installing, uninstalling, and verifying module types and initialization status. +/// @author @livingrockrises | Biconomy | chirag@biconomy.io +/// @author @aboudjem | Biconomy | adam.boudjemaa@biconomy.io +/// @author @filmakarov | Biconomy | filipp.makarov@biconomy.io +/// @author @zeroknots | Rhinestone.wtf | zeroknots.eth +/// Special thanks to the Solady team for foundational contributions: https://github.com/Vectorized/solady +interface IModule { + /// @notice Installs the module with necessary initialization data. + /// @dev Reverts if the module is already initialized. + /// @param data Arbitrary data required for initializing the module during `onInstall`. + function onInstall(bytes calldata data) external; + + /// @notice Uninstalls the module and allows for cleanup via arbitrary data. + /// @dev Reverts if any issues occur that prevent clean uninstallation. + /// @param data Arbitrary data required for deinitializing the module during `onUninstall`. + function onUninstall(bytes calldata data) external; + + /// @notice Determines if the module matches a specific module type. + /// @dev Should return true if the module corresponds to the type ID, false otherwise. + /// @param moduleTypeId Numeric ID of the module type as per ERC-7579 specifications. + /// @return True if the module is of the specified type, false otherwise. + function isModuleType(uint256 moduleTypeId) external view returns (bool); + + /// @notice Checks if the module has been initialized for a specific smart account. + /// @dev Returns true if initialized, false otherwise. + /// @param smartAccount Address of the smart account to check for initialization status. + /// @return True if the module is initialized for the given smart account, false otherwise. + function isInitialized(address smartAccount) external view returns (bool); +} diff --git a/biconomy/nexus/1.2.0/contracts/interfaces/modules/IPreValidationHook.sol b/biconomy/nexus/1.2.0/contracts/interfaces/modules/IPreValidationHook.sol new file mode 100644 index 0000000..70fcda4 --- /dev/null +++ b/biconomy/nexus/1.2.0/contracts/interfaces/modules/IPreValidationHook.sol @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.27; + +import { PackedUserOperation } from "account-abstraction/interfaces/PackedUserOperation.sol"; +import { IModule } from "./IModule.sol"; + +/// @title Nexus - IPreValidationHookERC1271 Interface +/// @notice Defines the interface for ERC-1271 pre-validation hooks +interface IPreValidationHookERC1271 is IModule { + /// @notice Performs pre-validation checks for isValidSignature + /// @dev This method is called before the validation of a signature on a validator within isValidSignature + /// @param sender The original sender of the request + /// @param hash The hash of signed data + /// @param data The signature data to validate + /// @return hookHash The hash after applying the pre-validation hook + /// @return hookSignature The signature after applying the pre-validation hook + function preValidationHookERC1271(address sender, bytes32 hash, bytes calldata data) external view returns (bytes32 hookHash, bytes memory hookSignature); +} + +/// @title Nexus - IPreValidationHookERC4337 Interface +/// @notice Defines the interface for ERC-4337 pre-validation hooks +interface IPreValidationHookERC4337 is IModule { + /// @notice Performs pre-validation checks for user operations + /// @dev This method is called before the validation of a user operation + /// @param userOp The user operation to be validated + /// @param missingAccountFunds The amount of funds missing in the account + /// @param userOpHash The hash of the user operation data + /// @return hookHash The hash after applying the pre-validation hook + /// @return hookSignature The signature after applying the pre-validation hook + function preValidationHookERC4337( + PackedUserOperation calldata userOp, + uint256 missingAccountFunds, + bytes32 userOpHash + ) + external + returns (bytes32 hookHash, bytes memory hookSignature); +} diff --git a/biconomy/nexus/1.2.0/contracts/interfaces/modules/IValidator.sol b/biconomy/nexus/1.2.0/contracts/interfaces/modules/IValidator.sol new file mode 100644 index 0000000..7cbcb15 --- /dev/null +++ b/biconomy/nexus/1.2.0/contracts/interfaces/modules/IValidator.sol @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.27; + +// ────────────────────────────────────────────────────────────────────────────── +// _ __ _ __ +// / | / /__ | |/ /_ _______ +// / |/ / _ \| / / / / ___/ +// / /| / __/ / /_/ (__ ) +// /_/ |_/\___/_/|_\__,_/____/ +// +// ────────────────────────────────────────────────────────────────────────────── +// Nexus: A suite of contracts for Modular Smart Accounts compliant with ERC-7579 and ERC-4337, developed by Biconomy. +// Learn more at https://biconomy.io. To report security issues, please contact us at: security@biconomy.io + +import { PackedUserOperation } from "account-abstraction/interfaces/PackedUserOperation.sol"; + +import { IModule } from "./IModule.sol"; + +/// @author @livingrockrises | Biconomy | chirag@biconomy.io +/// @author @aboudjem | Biconomy | adam.boudjemaa@biconomy.io +/// @author @filmakarov | Biconomy | filipp.makarov@biconomy.io +/// @author @zeroknots | Rhinestone.wtf | zeroknots.eth +/// Special thanks to the Solady team for foundational contributions: https://github.com/Vectorized/solady +interface IValidator is IModule { + /// @notice Validates a user operation as per ERC-4337 standard requirements. + /// @dev Should ensure that the signature and nonce are verified correctly before the transaction is allowed to proceed. + /// The function returns a status code indicating validation success or failure. + /// @param userOp The user operation containing transaction details to be validated. + /// @param userOpHash The hash of the user operation data, used for verifying the signature. + /// @return status The result of the validation process, typically indicating success or the type of failure. + function validateUserOp(PackedUserOperation calldata userOp, bytes32 userOpHash) external returns (uint256); + + /// @notice Verifies a signature against a hash, using the sender's address as a contextual check. + /// @dev Used to confirm the validity of a signature against the specific conditions set by the sender. + /// @param sender The address from which the operation was initiated, adding an additional layer of validation against the signature. + /// @param hash The hash of the data signed. + /// @param data The signature data to validate. + /// @return magicValue A bytes4 value that corresponds to the ERC-1271 standard, indicating the validity of the signature. + function isValidSignatureWithSender(address sender, bytes32 hash, bytes calldata data) external view returns (bytes4); +} diff --git a/biconomy/nexus/1.2.0/contracts/lib/ExecLib.sol b/biconomy/nexus/1.2.0/contracts/lib/ExecLib.sol new file mode 100644 index 0000000..90b8d0d --- /dev/null +++ b/biconomy/nexus/1.2.0/contracts/lib/ExecLib.sol @@ -0,0 +1,92 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.27; + +import { Execution } from "../types/DataTypes.sol"; + +/// @title ExecutionLib +/// @author zeroknots.eth | rhinestone.wtf +/// Helper Library for decoding Execution calldata +/// malloc for memory allocation is bad for gas. use this assembly instead +library ExecLib { + error InvalidBatchCallData(); + + function get2771CallData(bytes calldata cd) internal view returns (bytes memory callData) { + /// @solidity memory-safe-assembly + (cd); + assembly { + // as per solidity docs + function allocate(length) -> pos { + pos := mload(0x40) + mstore(0x40, add(pos, length)) + } + + callData := allocate(add(calldatasize(), 0x20)) //allocate extra 0x20 to store length + mstore(callData, add(calldatasize(), 0x14)) //store length, extra 0x14 is for msg.sender address + calldatacopy(add(callData, 0x20), 0, calldatasize()) + + // The msg.sender address is shifted to the left by 12 bytes to remove the padding + // Then the address without padding is stored right after the calldata + let senderPtr := allocate(0x14) + mstore(senderPtr, shl(96, caller())) + } + } + + /** + * @notice Decode a batch of `Execution` executionBatch from a `bytes` calldata. + * @dev code is copied from solady's LibERC7579.sol + * https://github.com/Vectorized/solady/blob/740812cedc9a1fc11e17cb3d4569744367dedf19/src/accounts/LibERC7579.sol#L146 + * Credits to Vectorized and the Solady Team + */ + function decodeBatch(bytes calldata executionCalldata) internal pure returns (Execution[] calldata executionBatch) { + /// @solidity memory-safe-assembly + assembly { + let u := calldataload(executionCalldata.offset) + let s := add(executionCalldata.offset, u) + let e := sub(add(executionCalldata.offset, executionCalldata.length), 0x20) + executionBatch.offset := add(s, 0x20) + executionBatch.length := calldataload(s) + if or(shr(64, u), gt(add(s, shl(5, executionBatch.length)), e)) { + mstore(0x00, 0xba597e7e) // `DecodingError()`. + revert(0x1c, 0x04) + } + if executionBatch.length { + // Perform bounds checks on the decoded `executionBatch`. + // Loop runs out-of-gas if `executionBatch.length` is big enough to cause overflows. + for { let i := executionBatch.length } 1 { } { + i := sub(i, 1) + let p := calldataload(add(executionBatch.offset, shl(5, i))) + let c := add(executionBatch.offset, p) + let q := calldataload(add(c, 0x40)) + let o := add(c, q) + // forgefmt: disable-next-item + if or(shr(64, or(calldataload(o), or(p, q))), + or(gt(add(c, 0x40), e), gt(add(o, calldataload(o)), e))) { + mstore(0x00, 0xba597e7e) // `DecodingError()`. + revert(0x1c, 0x04) + } + if iszero(i) { break } + } + } + } + } + + function encodeBatch(Execution[] memory executions) internal pure returns (bytes memory callData) { + callData = abi.encode(executions); + } + + function decodeSingle(bytes calldata executionCalldata) internal pure returns (address target, uint256 value, bytes calldata callData) { + target = address(bytes20(executionCalldata[0:20])); + value = uint256(bytes32(executionCalldata[20:52])); + callData = executionCalldata[52:]; + } + + function decodeDelegateCall(bytes calldata executionCalldata) internal pure returns (address delegate, bytes calldata callData) { + // destructure executionCallData according to single exec + delegate = address(uint160(bytes20(executionCalldata[0:20]))); + callData = executionCalldata[20:]; + } + + function encodeSingle(address target, uint256 value, bytes memory callData) internal pure returns (bytes memory userOpCalldata) { + userOpCalldata = abi.encodePacked(target, value, callData); + } +} diff --git a/biconomy/nexus/1.2.0/contracts/lib/Initializable.sol b/biconomy/nexus/1.2.0/contracts/lib/Initializable.sol new file mode 100644 index 0000000..6de1182 --- /dev/null +++ b/biconomy/nexus/1.2.0/contracts/lib/Initializable.sol @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.27; + +// keccak256(abi.encode(uint256(keccak256("initializable.transient.Nexus")) - 1)) & ~bytes32(uint256(0xff)); +bytes32 constant INIT_SLOT = 0x90b772c2cb8a51aa7a8a65fc23543c6d022d5b3f8e2b92eed79fba7eef829300; + +/// @title Initializable +/// @dev This library provides a way to set a transient flag on a contract to ensure that it is only initialized during the +/// constructor execution. This is useful to prevent a contract from being initialized multiple times. +library Initializable { + /// @dev Thrown when an attempt to initialize an already initialized contract is made + error NotInitializable(); + + /// @dev Sets the initializable flag in the transient storage slot to true + function setInitializable() internal { + bytes32 slot = INIT_SLOT; + assembly { + tstore(slot, 0x01) + } + } + + /// @dev Checks if the initializable flag is set in the transient storage slot, reverts with NotInitializable if not + function requireInitializable() internal view { + bytes32 slot = INIT_SLOT; + // Load the current value from the slot, revert if 0 + assembly { + let isInitializable := tload(slot) + if iszero(isInitializable) { + mstore(0x0, 0xaed59595) // NotInitializable() + revert(0x1c, 0x04) + } + } + } +} diff --git a/biconomy/nexus/1.2.0/contracts/lib/ModeLib.sol b/biconomy/nexus/1.2.0/contracts/lib/ModeLib.sol new file mode 100644 index 0000000..b520c96 --- /dev/null +++ b/biconomy/nexus/1.2.0/contracts/lib/ModeLib.sol @@ -0,0 +1,156 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.27; + +/// @title ModeLib +/// @author zeroknots.eth | rhinestone.wtf +/// To allow smart accounts to be very simple, but allow for more complex execution, A custom mode +/// encoding is used. +/// Function Signature of execute function: +/// function execute(ExecutionMode mode, bytes calldata executionCalldata) external payable; +/// This allows for a single bytes32 to be used to encode the execution mode, calltype, execType and +/// context. +/// NOTE: Simple Account implementations only have to scope for the most significant byte. Account that +/// implement +/// more complex execution modes may use the entire bytes32. +/// +/// |--------------------------------------------------------------------| +/// | CALLTYPE | EXECTYPE | UNUSED | ModeSelector | ModePayload | +/// |--------------------------------------------------------------------| +/// | 1 byte | 1 byte | 4 bytes | 4 bytes | 22 bytes | +/// |--------------------------------------------------------------------| +/// +/// CALLTYPE: 1 byte +/// CallType is used to determine how the executeCalldata paramter of the execute function has to be +/// decoded. +/// It can be either single, batch or delegatecall. In the future different calls could be added. +/// CALLTYPE can be used by a validation module to determine how to decode . +/// +/// EXECTYPE: 1 byte +/// ExecType is used to determine how the account should handle the execution. +/// It can indicate if the execution should revert on failure or continue execution. +/// In the future more execution modes may be added. +/// Default Behavior (EXECTYPE = 0x00) is to revert on a single failed execution. If one execution in +/// a batch fails, the entire batch is reverted +/// +/// UNUSED: 4 bytes +/// Unused bytes are reserved for future use. +/// +/// ModeSelector: bytes4 +/// The "optional" mode selector can be used by account vendors, to implement custom behavior in +/// their accounts. +/// the way a ModeSelector is to be calculated is bytes4(keccak256("vendorname.featurename")) +/// this is to prevent collisions between different vendors, while allowing innovation and the +/// development of new features without coordination between ERC-7579 implementing accounts +/// +/// ModePayload: 22 bytes +/// Mode payload is used to pass additional data to the smart account execution, this may be +/// interpreted depending on the ModeSelector +/// +/// ExecutionCallData: n bytes +/// single, delegatecall or batch exec abi.encoded as bytes + +// Custom type for improved developer experience +type ExecutionMode is bytes32; + +type CallType is bytes1; + +type ExecType is bytes1; + +type ModeSelector is bytes4; + +type ModePayload is bytes22; + +// Default CallType +CallType constant CALLTYPE_SINGLE = CallType.wrap(0x00); +// Batched CallType +CallType constant CALLTYPE_BATCH = CallType.wrap(0x01); + +CallType constant CALLTYPE_STATIC = CallType.wrap(0xFE); + +// @dev Implementing delegatecall is OPTIONAL! +// implement delegatecall with extreme care. +CallType constant CALLTYPE_DELEGATECALL = CallType.wrap(0xFF); + +// @dev default behavior is to revert on failure +// To allow very simple accounts to use mode encoding, the default behavior is to revert on failure +// Since this is value 0x00, no additional encoding is required for simple accounts +ExecType constant EXECTYPE_DEFAULT = ExecType.wrap(0x00); +// @dev account may elect to change execution behavior. For example "try exec" / "allow fail" +ExecType constant EXECTYPE_TRY = ExecType.wrap(0x01); + +ModeSelector constant MODE_DEFAULT = ModeSelector.wrap(bytes4(0x00000000)); +// Example declaration of a custom mode selector +ModeSelector constant MODE_OFFSET = ModeSelector.wrap(bytes4(keccak256("default.mode.offset"))); + +/// @dev ModeLib is a helper library to encode/decode ModeCodes +library ModeLib { + function decode( + ExecutionMode mode + ) internal pure returns (CallType _calltype, ExecType _execType, ModeSelector _modeSelector, ModePayload _modePayload) { + assembly { + _calltype := mode + _execType := shl(8, mode) + _modeSelector := shl(48, mode) + _modePayload := shl(80, mode) + } + } + + function decodeBasic(ExecutionMode mode) internal pure returns (CallType _calltype, ExecType _execType) { + assembly { + _calltype := mode + _execType := shl(8, mode) + } + } + + function encode(CallType callType, ExecType execType, ModeSelector mode, ModePayload payload) internal pure returns (ExecutionMode) { + return ExecutionMode.wrap(bytes32(abi.encodePacked(callType, execType, bytes4(0), ModeSelector.unwrap(mode), payload))); + } + + function encodeSimpleBatch() internal pure returns (ExecutionMode mode) { + mode = encode(CALLTYPE_BATCH, EXECTYPE_DEFAULT, MODE_DEFAULT, ModePayload.wrap(0x00)); + } + + function encodeSimpleSingle() internal pure returns (ExecutionMode mode) { + mode = encode(CALLTYPE_SINGLE, EXECTYPE_DEFAULT, MODE_DEFAULT, ModePayload.wrap(0x00)); + } + + function encodeTrySingle() internal pure returns (ExecutionMode mode) { + mode = encode(CALLTYPE_SINGLE, EXECTYPE_TRY, MODE_DEFAULT, ModePayload.wrap(0x00)); + } + + function encodeTryBatch() internal pure returns (ExecutionMode mode) { + mode = encode(CALLTYPE_BATCH, EXECTYPE_TRY, MODE_DEFAULT, ModePayload.wrap(0x00)); + } + + function encodeCustom(CallType callType, ExecType execType) internal pure returns (ExecutionMode mode) { + mode = encode(callType, execType, MODE_DEFAULT, ModePayload.wrap(0x00)); + } + + function getCallType(ExecutionMode mode) internal pure returns (CallType calltype) { + assembly { + calltype := mode + } + } +} + +using { _eqModeSelector as == } for ModeSelector global; +using { _eqCallType as == } for CallType global; +using { _uneqCallType as != } for CallType global; +using { _eqExecType as == } for ExecType global; + +function _eqCallType(CallType a, CallType b) pure returns (bool) { + return CallType.unwrap(a) == CallType.unwrap(b); +} + +function _uneqCallType(CallType a, CallType b) pure returns (bool) { + return CallType.unwrap(a) != CallType.unwrap(b); +} + +function _eqExecType(ExecType a, ExecType b) pure returns (bool) { + return ExecType.unwrap(a) == ExecType.unwrap(b); +} + +//slither-disable-next-line dead-code +function _eqModeSelector(ModeSelector a, ModeSelector b) pure returns (bool) { + return ModeSelector.unwrap(a) == ModeSelector.unwrap(b); +} diff --git a/biconomy/nexus/1.2.0/contracts/lib/NonceLib.sol b/biconomy/nexus/1.2.0/contracts/lib/NonceLib.sol new file mode 100644 index 0000000..c56f52e --- /dev/null +++ b/biconomy/nexus/1.2.0/contracts/lib/NonceLib.sol @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.27; + +import { MODE_MODULE_ENABLE, MODE_PREP, MODE_VALIDATION } from "../types/Constants.sol"; + +/** + Nonce structure + [3 bytes empty][1 bytes validation mode][20 bytes validator][8 bytes nonce] +*/ + +library NonceLib { + /// @dev Parses validator address out of nonce + /// @param nonce The nonce + /// @return validator + function getValidator(uint256 nonce) internal pure returns (address validator) { + assembly { + validator := shr(96, shl(32, nonce)) + } + } + + /// @dev Detects if Validaton Mode is Module Enable Mode + /// @param nonce The nonce + /// @return res boolean result, true if it is the Module Enable Mode + function isModuleEnableMode(uint256 nonce) internal pure returns (bool res) { + assembly { + let vmode := byte(3, nonce) + res := eq(shl(248, vmode), MODE_MODULE_ENABLE) + } + } + + /// @dev Detects if the validator provided in the nonce is address(0) + /// which means the default validator is used + /// @param nonce The nonce + /// @return res boolean result, true if it is the Default Validator Mode + function isDefaultValidatorMode(uint256 nonce) internal pure returns (bool res) { + assembly { + res := iszero(shr(96, shl(32, nonce))) + } + } + + /// @dev Detects if Validaton Mode is Prep Mode + /// @param nonce The nonce + /// @return res boolean result, true if it is the Prep Mode + function isPrepMode(uint256 nonce) internal pure returns (bool res) { + assembly { + let vmode := byte(3, nonce) + res := eq(shl(248, vmode), MODE_PREP) + } + } + + /// @dev Detects if Validaton Mode is Validate Mode + /// @param nonce The nonce + /// @return res boolean result, true if it is the Validate Mode + function isValidateMode(uint256 nonce) internal pure returns (bool res) { + assembly { + let vmode := byte(3, nonce) + res := eq(shl(248, vmode), MODE_VALIDATION) + } + } +} diff --git a/biconomy/nexus/1.2.0/contracts/lib/ProxyLib.sol b/biconomy/nexus/1.2.0/contracts/lib/ProxyLib.sol new file mode 100644 index 0000000..7889d1e --- /dev/null +++ b/biconomy/nexus/1.2.0/contracts/lib/ProxyLib.sol @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.27; + +import { NexusProxy } from "../utils/NexusProxy.sol"; +import { INexus } from "../interfaces/INexus.sol"; + +/// @title ProxyLib +/// @notice A library for deploying NexusProxy contracts +library ProxyLib { + /// @notice Error thrown when ETH transfer fails. + error EthTransferFailed(); + + /// @notice Deploys a new NexusProxy contract, returning the address of the new contract, if the contract is already deployed, + /// the msg.value will be forwarded to the existing contract. + /// @param implementation The address of the implementation contract. + /// @param salt The salt used for the contract creation. + /// @param initData The initialization data for the implementation contract. + /// @return alreadyDeployed A boolean indicating if the contract was already deployed. + /// @return account The address of the new contract or the existing contract. + function deployProxy(address implementation, bytes32 salt, bytes memory initData) internal returns (bool alreadyDeployed, address payable account) { + // Check if the contract is already deployed + account = predictProxyAddress(implementation, salt, initData); + alreadyDeployed = account.code.length > 0; + // Deploy a new contract if it is not already deployed + if (!alreadyDeployed) { + // Deploy the contract + new NexusProxy{ salt: salt, value: msg.value }(implementation, abi.encodeCall(INexus.initializeAccount, initData)); + } else { + // Forward the value to the existing contract + (bool success,) = account.call{ value: msg.value }(""); + require(success, EthTransferFailed()); + } + } + + /// @notice Predicts the address of a NexusProxy contract. + /// @param implementation The address of the implementation contract. + /// @param salt The salt used for the contract creation. + /// @param initData The initialization data for the implementation contract. + /// @return predictedAddress The predicted address of the new contract. + function predictProxyAddress(address implementation, bytes32 salt, bytes memory initData) internal view returns (address payable predictedAddress) { + // Get the init code hash + bytes32 initCodeHash = + keccak256(abi.encodePacked(type(NexusProxy).creationCode, abi.encode(implementation, abi.encodeCall(INexus.initializeAccount, initData)))); + + // Compute the predicted address + predictedAddress = payable(address(uint160(uint256(keccak256(abi.encodePacked(bytes1(0xff), address(this), salt, initCodeHash)))))); + } +} diff --git a/biconomy/nexus/1.2.0/contracts/lib/local/LocalCallDataParserLib.sol b/biconomy/nexus/1.2.0/contracts/lib/local/LocalCallDataParserLib.sol new file mode 100644 index 0000000..b090a1a --- /dev/null +++ b/biconomy/nexus/1.2.0/contracts/lib/local/LocalCallDataParserLib.sol @@ -0,0 +1,65 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.27; + +library LocalCallDataParserLib { + /// @dev Parses the `userOp.signature` to extract the module type, module initialization data, + /// enable mode signature, and user operation signature. The `userOp.signature` must be + /// encoded in a specific way to be parsed correctly. + /// @param packedData The packed signature data, typically coming from `userOp.signature`. + /// @return module The address of the module. + /// @return moduleType The type of module as a `uint256`. + /// @return moduleInitData Initialization data specific to the module. + /// @return enableModeSignature Signature used to enable the module mode. + /// @return userOpSignature The remaining user operation signature data. + function parseEnableModeData( + bytes calldata packedData + ) + internal + pure + returns ( + address module, + uint256 moduleType, + bytes calldata moduleInitData, + bytes calldata enableModeSignature, + bytes calldata userOpSignature + ) + { + uint256 p; + assembly ("memory-safe") { + p := packedData.offset + module := shr(96, calldataload(p)) + + p := add(p, 0x14) + moduleType := calldataload(p) + + moduleInitData.length := shr(224, calldataload(add(p, 0x20))) + moduleInitData.offset := add(p, 0x24) + p := add(moduleInitData.offset, moduleInitData.length) + + enableModeSignature.length := shr(224, calldataload(p)) + enableModeSignature.offset := add(p, 0x04) + p := sub(add(enableModeSignature.offset, enableModeSignature.length), packedData.offset) + } + userOpSignature = packedData[p:]; + } + + /// @dev Parses the data to obtain types and initdata's for Multi Type module install mode + /// @param initData Multi Type module init data, abi.encoded + function parseMultiTypeInitData(bytes calldata initData) internal pure returns (uint256[] calldata types, bytes[] calldata initDatas) { + // equivalent of: + // (types, initDatas) = abi.decode(initData,(uint[],bytes[])) + assembly ("memory-safe") { + let offset := initData.offset + let baseOffset := offset + let dataPointer := add(baseOffset, calldataload(offset)) + + types.offset := add(dataPointer, 32) + types.length := calldataload(dataPointer) + offset := add(offset, 32) + + dataPointer := add(baseOffset, calldataload(offset)) + initDatas.offset := add(dataPointer, 32) + initDatas.length := calldataload(dataPointer) + } + } +} diff --git a/biconomy/nexus/1.2.0/contracts/types/Constants.sol b/biconomy/nexus/1.2.0/contracts/types/Constants.sol new file mode 100644 index 0000000..881b333 --- /dev/null +++ b/biconomy/nexus/1.2.0/contracts/types/Constants.sol @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.27; + +// ────────────────────────────────────────────────────────────────────────────── +// _ __ _ __ +// / | / /__ | |/ /_ _______ +// / |/ / _ \| / / / / ___/ +// / /| / __/ / /_/ (__ ) +// /_/ |_/\___/_/|_\__,_/____/ +// +// ────────────────────────────────────────────────────────────────────────────── +// Nexus: A suite of contracts for Modular Smart Accounts compliant with ERC-7579 and ERC-4337, developed by Biconomy. +// Learn more at https://biconomy.io. To report security issues, please contact us at: security@biconomy.io + +// Magic value for ERC-1271 valid signature +bytes4 constant ERC1271_MAGICVALUE = 0x1626ba7e; + +// Value indicating an invalid ERC-1271 signature +bytes4 constant ERC1271_INVALID = 0xFFFFFFFF; + +// Value indicating successful validation +uint256 constant VALIDATION_SUCCESS = 0; + +// Value indicating failed validation +uint256 constant VALIDATION_FAILED = 1; + +// Module type identifier for Multitype install +uint256 constant MODULE_TYPE_MULTI = 0; + +// Module type identifier for validators +uint256 constant MODULE_TYPE_VALIDATOR = 1; + +// Module type identifier for executors +uint256 constant MODULE_TYPE_EXECUTOR = 2; + +// Module type identifier for fallback handlers +uint256 constant MODULE_TYPE_FALLBACK = 3; + +// Module type identifier for hooks +uint256 constant MODULE_TYPE_HOOK = 4; + +// Module type identifiers for pre-validation hooks +uint256 constant MODULE_TYPE_PREVALIDATION_HOOK_ERC1271 = 8; +uint256 constant MODULE_TYPE_PREVALIDATION_HOOK_ERC4337 = 9; + + +// keccak256("ModuleEnableMode(address module,uint256 moduleType,bytes32 userOpHash,bytes initData)") +bytes32 constant MODULE_ENABLE_MODE_TYPE_HASH = 0xf6c866c1cd985ce61f030431e576c0e82887de0643dfa8a2e6efc3463e638ed0; + +// keccak256("EmergencyUninstall(address hook,uint256 hookType,bytes deInitData,uint256 nonce)") +bytes32 constant EMERGENCY_UNINSTALL_TYPE_HASH = 0xd3ddfc12654178cc44d4a7b6b969cfdce7ffe6342326ba37825314cffa0fba9c; + +// Validation modes +bytes1 constant MODE_VALIDATION = 0x00; +bytes1 constant MODE_MODULE_ENABLE = 0x01; +bytes1 constant MODE_PREP = 0x02; + +bytes4 constant SUPPORTS_ERC7739 = 0x77390000; +bytes4 constant SUPPORTS_ERC7739_V1 = 0x77390001; diff --git a/biconomy/nexus/1.2.0/contracts/types/DataTypes.sol b/biconomy/nexus/1.2.0/contracts/types/DataTypes.sol new file mode 100644 index 0000000..b3e51a5 --- /dev/null +++ b/biconomy/nexus/1.2.0/contracts/types/DataTypes.sol @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.27; + +// ────────────────────────────────────────────────────────────────────────────── +// _ __ _ __ +// / | / /__ | |/ /_ _______ +// / |/ / _ \| / / / / ___/ +// / /| / __/ / /_/ (__ ) +// /_/ |_/\___/_/|_\__,_/____/ +// +// ────────────────────────────────────────────────────────────────────────────── +// Nexus: A suite of contracts for Modular Smart Accounts compliant with ERC-7579 and ERC-4337, developed by Biconomy. +// Learn more at https://biconomy.io. To report security issues, please contact us at: security@biconomy.io + +/// @title Execution +/// @notice Struct to encapsulate execution data for a transaction +struct Execution { + /// @notice The target address for the transaction + address target; + /// @notice The value in wei to send with the transaction + uint256 value; + /// @notice The calldata for the transaction + bytes callData; +} + +/// @title Emergency Uninstall +/// @notice Struct to encapsulate emergency uninstall data for a hook +struct EmergencyUninstall { + /// @notice The address of the hook to be uninstalled + address hook; + /// @notice The hook type identifier + uint256 hookType; + /// @notice Data used to uninstall the hook + bytes deInitData; + /// @notice Nonce used to prevent replay attacks + uint256 nonce; +} diff --git a/biconomy/nexus/1.2.0/contracts/utils/NexusBootstrap.sol b/biconomy/nexus/1.2.0/contracts/utils/NexusBootstrap.sol new file mode 100644 index 0000000..41d1b43 --- /dev/null +++ b/biconomy/nexus/1.2.0/contracts/utils/NexusBootstrap.sol @@ -0,0 +1,441 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.27; + +// ────────────────────────────────────────────────────────────────────────────── +// _ __ _ __ +// / | / /__ | |/ /_ _______ +// / |/ / _ \| / / / / ___/ +// / /| / __/ / /_/ (__ ) +// /_/ |_/\___/_/|_\__,_/____/ +// +// ────────────────────────────────────────────────────────────────────────────── +// Nexus: A suite of contracts for Modular Smart Accounts compliant with ERC-7579 and ERC-4337, developed by Biconomy. +// Learn more at https://biconomy.io. For security issues, contact: security@biconomy.io + +import { ModuleManager } from "../base/ModuleManager.sol"; +import { IModule } from "../interfaces/modules/IModule.sol"; +import { IERC7484 } from "../interfaces/IERC7484.sol"; +import { + MODULE_TYPE_VALIDATOR, + MODULE_TYPE_EXECUTOR, + MODULE_TYPE_FALLBACK, + MODULE_TYPE_HOOK +} from "../types/Constants.sol"; + +/// @title NexusBootstrap Configuration for Nexus +/// @notice Provides configuration and initialization for Nexus smart accounts. +/// @author @livingrockrises | Biconomy | chirag@biconomy.io +/// @author @aboudjem | Biconomy | adam.boudjemaa@biconomy.io +/// @author @filmakarov | Biconomy | filipp.makarov@biconomy.io +/// @author @zeroknots | Rhinestone.wtf | zeroknots.eth +/// Special thanks to the Solady team for foundational contributions: https://github.com/Vectorized/solady + struct BootstrapConfig { + address module; + bytes data; + } + + struct BootstrapPreValidationHookConfig { + uint256 hookType; + address module; + bytes data; + } + + struct RegistryConfig { + IERC7484 registry; + address[] attesters; + uint8 threshold; + } + +/// @title NexusBootstrap +/// @notice Manages the installation of modules into Nexus smart accounts using delegatecalls. +contract NexusBootstrap is ModuleManager { + + constructor(address defaultValidator, bytes memory initData) ModuleManager(defaultValidator, initData) {} + + modifier _withInitSentinelLists() { + _initSentinelLists(); + _; + } + + /// @notice Initializes the Nexus account with the default validator. + /// No registry is needed for the default validator. + /// @dev Intended to be called by the Nexus with a delegatecall. + /// @dev For gas savings purposes this method does not initialize the registry. + /// @dev The registry should be initialized via the `setRegistry` function on the Nexus contract later if needed. + /// @param data The initialization data for the default validator module. + function initNexusWithDefaultValidator( + bytes calldata data + ) + external + payable + { + IModule(_DEFAULT_VALIDATOR).onInstall(data); + } + + // ================================================ + // ===== DEFAULT VALIDATOR + OTHER MODULES ===== + // ================================================ + + /// @notice Initializes the Nexus account with the default validator and other modules and no registry. + /// @dev Intended to be called by the Nexus with a delegatecall. + /// @param defaultValidatorInitData The initialization data for the default validator module. + /// @param executors The configuration array for executor modules. + /// @param hook The configuration for the hook module. + /// @param fallbacks The configuration array for fallback handler modules. + function initNexusWithDefaultValidatorAndOtherModulesNoRegistry( + bytes calldata defaultValidatorInitData, + BootstrapConfig[] calldata validators, + BootstrapConfig[] calldata executors, + BootstrapConfig calldata hook, + BootstrapConfig[] calldata fallbacks, + BootstrapPreValidationHookConfig[] calldata preValidationHooks + ) + external + payable + { + RegistryConfig memory registryConfig = RegistryConfig({ + registry: IERC7484(address(0)), + attesters: new address[](0), + threshold: 0 + }); + + _initNexusWithDefaultValidatorAndOtherModules( + defaultValidatorInitData, + validators, + executors, + hook, + fallbacks, + preValidationHooks, + registryConfig + ); + } + + /// @notice Initializes the Nexus account with the default validator and other modules. + /// @dev Intended to be called by the Nexus with a delegatecall. + /// @param defaultValidatorInitData The initialization data for the default validator module. + /// @param executors The configuration array for executor modules. + /// @param hook The configuration for the hook module. + /// @param fallbacks The configuration array for fallback handler modules. + /// @param registryConfig The registry configuration. + function initNexusWithDefaultValidatorAndOtherModules( + bytes calldata defaultValidatorInitData, + BootstrapConfig[] calldata validators, + BootstrapConfig[] calldata executors, + BootstrapConfig calldata hook, + BootstrapConfig[] calldata fallbacks, + BootstrapPreValidationHookConfig[] calldata preValidationHooks, + RegistryConfig memory registryConfig + ) + external + payable + { + _initNexusWithDefaultValidatorAndOtherModules( + defaultValidatorInitData, + validators, + executors, + hook, + fallbacks, + preValidationHooks, + registryConfig + ); + } + + function _initNexusWithDefaultValidatorAndOtherModules( + bytes calldata defaultValidatorInitData, + BootstrapConfig[] calldata validators, + BootstrapConfig[] calldata executors, + BootstrapConfig calldata hook, + BootstrapConfig[] calldata fallbacks, + BootstrapPreValidationHookConfig[] calldata preValidationHooks, + RegistryConfig memory registryConfig + ) + internal + _withInitSentinelLists + { + _configureRegistry(registryConfig.registry, registryConfig.attesters, registryConfig.threshold); + + IModule(_DEFAULT_VALIDATOR).onInstall(defaultValidatorInitData); + + for (uint256 i; i < validators.length; i++) { + if (validators[i].module == address(0)) continue; + _installValidator(validators[i].module, validators[i].data); + emit ModuleInstalled(MODULE_TYPE_VALIDATOR, validators[i].module); + } + + for (uint256 i; i < executors.length; i++) { + if (executors[i].module == address(0)) continue; + _installExecutor(executors[i].module, executors[i].data); + emit ModuleInstalled(MODULE_TYPE_EXECUTOR, executors[i].module); + } + + // Initialize hook + if (hook.module != address(0)) { + _installHook(hook.module, hook.data); + emit ModuleInstalled(MODULE_TYPE_HOOK, hook.module); + } + + // Initialize fallback handlers + for (uint256 i; i < fallbacks.length; i++) { + if (fallbacks[i].module == address(0)) continue; + _installFallbackHandler(fallbacks[i].module, fallbacks[i].data); + emit ModuleInstalled(MODULE_TYPE_FALLBACK, fallbacks[i].module); + } + + // Initialize pre-validation hooks + for (uint256 i; i < preValidationHooks.length; i++) { + if (preValidationHooks[i].module == address(0)) continue; + _installPreValidationHook( + preValidationHooks[i].hookType, + preValidationHooks[i].module, + preValidationHooks[i].data + ); + emit ModuleInstalled(preValidationHooks[i].hookType, preValidationHooks[i].module); + } + } + + // ================================================ + // ===== SINGLE VALIDATOR ===== + // ================================================ + + /// @notice Initializes the Nexus account with a single validator and no registry. + /// @dev Intended to be called by the Nexus with a delegatecall. + /// @param validator The address of the validator module. + /// @param data The initialization data for the validator module. + function initNexusWithSingleValidatorNoRegistry( + address validator, + bytes calldata data + ) + external + payable + { + RegistryConfig memory registryConfig = RegistryConfig({ + registry: IERC7484(address(0)), + attesters: new address[](0), + threshold: 0 + }); + _initNexusWithSingleValidator(validator, data, registryConfig); + } + + /// @notice Initializes the Nexus account with a single validator. + /// @dev Intended to be called by the Nexus with a delegatecall. + /// @param validator The address of the validator module. + /// @param data The initialization data for the validator module. + /// @param registryConfig The registry configuration. + function initNexusWithSingleValidator( + address validator, + bytes calldata data, + RegistryConfig memory registryConfig + ) + external + payable + { + _initNexusWithSingleValidator(validator, data, registryConfig); + } + + function _initNexusWithSingleValidator( + address validator, + bytes calldata data, + RegistryConfig memory registryConfig + ) + internal + _withInitSentinelLists + { + _configureRegistry(registryConfig.registry, registryConfig.attesters, registryConfig.threshold); + _installValidator(validator, data); + emit ModuleInstalled(MODULE_TYPE_VALIDATOR, validator); + } + + // ================================================ + // ===== GENERALIZED FLOW ===== + // ================================================ + + /// @notice Initializes the Nexus account with multiple modules and no registry. + /// @dev Intended to be called by the Nexus with a delegatecall. + /// @param validators The configuration array for validator modules. + /// @param executors The configuration array for executor modules. + /// @param hook The configuration for the hook module. + /// @param fallbacks The configuration array for fallback handler modules. + function initNexusNoRegistry( + BootstrapConfig[] calldata validators, + BootstrapConfig[] calldata executors, + BootstrapConfig calldata hook, + BootstrapConfig[] calldata fallbacks, + BootstrapPreValidationHookConfig[] calldata preValidationHooks + ) + external + payable + { + RegistryConfig memory registryConfig = RegistryConfig({ + registry: IERC7484(address(0)), + attesters: new address[](0), + threshold: 0 + }); + + _initNexus(validators, executors, hook, fallbacks, preValidationHooks, registryConfig); + } + + /// @notice Initializes the Nexus account with multiple modules. + /// @dev Intended to be called by the Nexus with a delegatecall. + /// @param validators The configuration array for validator modules. + /// @param executors The configuration array for executor modules. + /// @param hook The configuration for the hook module. + /// @param fallbacks The configuration array for fallback handler modules. + /// @param registryConfig The registry configuration. + function initNexus( + BootstrapConfig[] calldata validators, + BootstrapConfig[] calldata executors, + BootstrapConfig calldata hook, + BootstrapConfig[] calldata fallbacks, + BootstrapPreValidationHookConfig[] calldata preValidationHooks, + RegistryConfig memory registryConfig + ) + external + payable + { + _initNexus({ + validators: validators, + executors: executors, + hook: hook, + fallbacks: fallbacks, + preValidationHooks: preValidationHooks, + registryConfig: registryConfig + }); + } + + function _initNexus( + BootstrapConfig[] calldata validators, + BootstrapConfig[] calldata executors, + BootstrapConfig calldata hook, + BootstrapConfig[] calldata fallbacks, + BootstrapPreValidationHookConfig[] calldata preValidationHooks, + RegistryConfig memory registryConfig + ) + internal + _withInitSentinelLists + { + _configureRegistry(registryConfig.registry, registryConfig.attesters, registryConfig.threshold); + + // Initialize validators + for (uint256 i = 0; i < validators.length; i++) { + _installValidator(validators[i].module, validators[i].data); + emit ModuleInstalled(MODULE_TYPE_VALIDATOR, validators[i].module); + } + + // Initialize executors + for (uint256 i = 0; i < executors.length; i++) { + if (executors[i].module == address(0)) continue; + _installExecutor(executors[i].module, executors[i].data); + emit ModuleInstalled(MODULE_TYPE_EXECUTOR, executors[i].module); + } + + // Initialize fallback handlers + for (uint256 i = 0; i < fallbacks.length; i++) { + if (fallbacks[i].module == address(0)) continue; + _installFallbackHandler(fallbacks[i].module, fallbacks[i].data); + emit ModuleInstalled(MODULE_TYPE_FALLBACK, fallbacks[i].module); + } + + // Initialize hook + if (hook.module != address(0)) { + _installHook(hook.module, hook.data); + emit ModuleInstalled(MODULE_TYPE_HOOK, hook.module); + } + + // Initialize pre-validation hooks + for (uint256 i = 0; i < preValidationHooks.length; i++) { + if (preValidationHooks[i].module == address(0)) continue; + _installPreValidationHook( + preValidationHooks[i].hookType, + preValidationHooks[i].module, + preValidationHooks[i].data + ); + emit ModuleInstalled(preValidationHooks[i].hookType, preValidationHooks[i].module); + } + } + + // ================================================ + // ===== SCOPED FLOW ===== + // ================================================ + + /// @notice Initializes the Nexus account with a scoped set of modules and no registry. + /// @dev Intended to be called by the Nexus with a delegatecall. + /// @param validators The configuration array for validator modules. + /// @param hook The configuration for the hook module. + function initNexusScopedNoRegistry( + BootstrapConfig[] calldata validators, + BootstrapConfig calldata hook + ) + external + payable + { + RegistryConfig memory registryConfig = RegistryConfig({ + registry: IERC7484(address(0)), + attesters: new address[](0), + threshold: 0 + }); + _initNexusScoped(validators, hook, registryConfig); + } + + /// @notice Initializes the Nexus account with a scoped set of modules. + /// @dev Intended to be called by the Nexus with a delegatecall. + /// @param validators The configuration array for validator modules. + /// @param hook The configuration for the hook module. + /// @param registryConfig The registry configuration. + function initNexusScoped( + BootstrapConfig[] calldata validators, + BootstrapConfig calldata hook, + RegistryConfig memory registryConfig + ) + external + payable + { + _initNexusScoped(validators, hook, registryConfig); + } + + /// @notice Initializes the Nexus account with a scoped set of modules. + /// @dev Intended to be called by the Nexus with a delegatecall. + /// @param validators The configuration array for validator modules. + /// @param hook The configuration for the hook module. + function _initNexusScoped( + BootstrapConfig[] calldata validators, + BootstrapConfig calldata hook, + RegistryConfig memory registryConfig + ) + internal + _withInitSentinelLists + { + _configureRegistry(registryConfig.registry, registryConfig.attesters, registryConfig.threshold); + + // Initialize validators + for (uint256 i = 0; i < validators.length; i++) { + _installValidator(validators[i].module, validators[i].data); + emit ModuleInstalled(MODULE_TYPE_VALIDATOR, validators[i].module); + } + + // Initialize hook + if (hook.module != address(0)) { + _installHook(hook.module, hook.data); + emit ModuleInstalled(MODULE_TYPE_HOOK, hook.module); + } + } + + /// @dev EIP712 domain name and version. + function _domainNameAndVersion() internal pure override returns (string memory name, string memory version) { + name = "NexusBootstrap"; + version = "1.2.1"; + } + + + // required implementations. Are not used. + function installModule(uint256 moduleTypeId, address module, bytes calldata initData) external payable override { + // do nothing + } + + function uninstallModule(uint256 moduleTypeId, address module, bytes calldata deInitData) external payable override { + // do nothing + } + + function isModuleInstalled(uint256 moduleTypeId, address module, bytes calldata additionalContext) external view override returns (bool installed) { + return false; + } +} diff --git a/biconomy/nexus/1.2.0/contracts/utils/NexusProxy.sol b/biconomy/nexus/1.2.0/contracts/utils/NexusProxy.sol new file mode 100644 index 0000000..794787b --- /dev/null +++ b/biconomy/nexus/1.2.0/contracts/utils/NexusProxy.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.27; + +import { Proxy } from "@openzeppelin/contracts/proxy/Proxy.sol"; +import { ERC1967Utils } from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Utils.sol"; +import { Initializable } from "../lib/Initializable.sol"; + +/// @title NexusProxy +/// @dev A proxy contract that uses the ERC1967 upgrade pattern and sets the initializable flag +/// in the constructor to prevent reinitialization +contract NexusProxy is Proxy { + constructor(address implementation, bytes memory data) payable { + Initializable.setInitializable(); + ERC1967Utils.upgradeToAndCall(implementation, data); + } + + function _implementation() internal view virtual override returns (address) { + return ERC1967Utils.getImplementation(); + } + + receive() external payable {} +} diff --git a/biconomy/nexus/1.2.0/expected b/biconomy/nexus/1.2.0/expected new file mode 100644 index 0000000..3841692 --- /dev/null +++ b/biconomy/nexus/1.2.0/expected @@ -0,0 +1,5 @@ +EntryPoint 0x0000000071727De22E5E9d8BAf0edAc6f37da032 +K1MeeValidator 0x00000000d12897DDAdC2044614A9677B191A2d95 +Nexus 0x000000004F43C49e93C970E84001853a70923B03 +NexusAccountFactory 0x000000001D1D5004a02bAfAb9de2D6CE5b7B13de +NexusBootstrap 0x00000000D3254452a909E4eeD47455Af7E27C289 diff --git a/biconomy/nexus/1.2.0/foundry.toml b/biconomy/nexus/1.2.0/foundry.toml new file mode 100644 index 0000000..c76628c --- /dev/null +++ b/biconomy/nexus/1.2.0/foundry.toml @@ -0,0 +1,15 @@ +[profile.default] +src = "contracts" +out = "out" +libs = ["lib"] +auto_detect_remappings = false +auto_detect_solc = false +solc_version = "0.8.27" +evm_version = "cancun" +via_ir = true +cbor_metadata = true +use_literal_content = false +bytecode_hash = "none" +optimizer = true +optimizer_runs = 777 +libraries = [] diff --git a/biconomy/nexus/1.2.0/node_modules/@biconomy/composability/contracts/ComposableExecutionBase.sol b/biconomy/nexus/1.2.0/node_modules/@biconomy/composability/contracts/ComposableExecutionBase.sol new file mode 100644 index 0000000..230986c --- /dev/null +++ b/biconomy/nexus/1.2.0/node_modules/@biconomy/composability/contracts/ComposableExecutionBase.sol @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.27; + +import {ComposableExecutionLib} from "./ComposableExecutionLib.sol"; +import {InputParam, OutputParam, ComposableExecution, Constraint, ConstraintType, InputParamFetcherType, OutputParamFetcherType} from "./types/ComposabilityDataTypes.sol"; +import {IComposableExecution} from "./interfaces/IComposableExecution.sol"; + +abstract contract ComposableExecutionBase is IComposableExecution { + using ComposableExecutionLib for InputParam[]; + using ComposableExecutionLib for OutputParam[]; + + /// @dev Override it in the account and introduce additional access control or other checks + function executeComposable(ComposableExecution[] calldata executions) external payable virtual; + + /// @dev internal function to execute the composable execution flow + /// First, processes the input parameters and returns the composed calldata + /// Then, executes the action + /// Then, processes the output parameters + function _executeComposable(ComposableExecution[] calldata executions) internal { + uint256 length = executions.length; + for (uint256 i; i < length; i++) { + ComposableExecution calldata execution = executions[i]; + bytes memory composedCalldata = execution.inputParams.processInputs(execution.functionSig); + bytes memory returnData; + if (execution.to != address(0)) { + returnData = _executeAction(execution.to, execution.value, composedCalldata); + } else { + returnData = new bytes(0); + } + execution.outputParams.processOutputs(returnData, address(this)); + } + } + + /// @dev Override this in the account + /// using account's native execution approach + function _executeAction(address to, uint256 value, bytes memory data) + internal + virtual + returns (bytes memory returnData); +} diff --git a/biconomy/nexus/1.2.0/node_modules/@biconomy/composability/contracts/ComposableExecutionLib.sol b/biconomy/nexus/1.2.0/node_modules/@biconomy/composability/contracts/ComposableExecutionLib.sol new file mode 100644 index 0000000..12c40e0 --- /dev/null +++ b/biconomy/nexus/1.2.0/node_modules/@biconomy/composability/contracts/ComposableExecutionLib.sol @@ -0,0 +1,150 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.27; + +import {Storage} from "./Storage.sol"; +import {InputParam, OutputParam, Constraint, ConstraintType, InputParamFetcherType, OutputParamFetcherType} from "./types/ComposabilityDataTypes.sol"; +// Library for composable execution handling +library ComposableExecutionLib { + + error ConstraintNotMet(ConstraintType constraintType); + error Output_StaticCallFailed(); + error InvalidParameterEncoding(); + error InvalidOutputParamFetcherType(); + error ComposableExecutionFailed(); + error InvalidConstraintType(); + + // Process the input parameters and return the composed calldata + function processInputs(InputParam[] calldata inputParams, bytes4 functionSig) + internal + view + returns (bytes memory) + { + bytes memory composedCalldata = abi.encodePacked(functionSig); + uint256 length = inputParams.length; + for (uint256 i; i < length; i++) { + composedCalldata = bytes.concat(composedCalldata, processInput(inputParams[i])); + } + return composedCalldata; + } + + // Process a single input parameter and return the composed calldata + function processInput(InputParam calldata param) internal view returns (bytes memory) { + if (param.fetcherType == InputParamFetcherType.RAW_BYTES) { + _validateConstraints(param.paramData, param.constraints); + return param.paramData; + } else if (param.fetcherType == InputParamFetcherType.STATIC_CALL) { + address contractAddr; + bytes calldata callData; + bytes calldata paramData = param.paramData; + assembly { + contractAddr := calldataload(paramData.offset) + let s := calldataload(add(paramData.offset, 0x20)) + let u := add(paramData.offset, s) + callData.offset := add(u, 0x20) + callData.length := calldataload(u) + } + (bool success, bytes memory returnData) = contractAddr.staticcall(callData); + if (!success) { + revert ComposableExecutionFailed(); + } + _validateConstraints(returnData, param.constraints); + return returnData; + } else { + revert InvalidParameterEncoding(); + } + } + + // Process the output parameters + function processOutputs(OutputParam[] calldata outputParams, bytes memory returnData, address account) internal { + uint256 length = outputParams.length; + for (uint256 i; i < length; i++) { + processOutput(outputParams[i], returnData, account); + } + } + + // Process a single output parameter and write to storage + function processOutput(OutputParam calldata param, bytes memory returnData, address account) internal { + // only static types are supported for now as return values + // can also process all the static return values which are before the first dynamic return value in the returnData + if (param.fetcherType == OutputParamFetcherType.EXEC_RESULT) { + uint256 returnValues; + address targetStorageContract; + bytes32 targetStorageSlot; + bytes calldata paramData = param.paramData; + assembly { + returnValues := calldataload(paramData.offset) + targetStorageContract := calldataload(add(paramData.offset, 0x20)) + targetStorageSlot := calldataload(add(paramData.offset, 0x40)) + } + _parseReturnDataAndWriteToStorage(returnValues, returnData, targetStorageContract, targetStorageSlot, account); + // same for static calls + } else if (param.fetcherType == OutputParamFetcherType.STATIC_CALL) { + uint256 returnValues; + address sourceContract; + bytes calldata sourceCallData; + address targetStorageContract; + bytes32 targetStorageSlot; + bytes calldata paramData = param.paramData; + assembly { + returnValues := calldataload(paramData.offset) + sourceContract := calldataload(add(paramData.offset, 0x20)) + let s := calldataload(add(paramData.offset, 0x40)) + let u := add(paramData.offset, s) + sourceCallData.offset := add(u, 0x20) + sourceCallData.length := calldataload(u) + targetStorageContract := calldataload(add(paramData.offset, 0x60)) + targetStorageSlot := calldataload(add(paramData.offset, 0x80)) + } + (bool outputSuccess, bytes memory outputReturnData) = sourceContract.staticcall(sourceCallData); + if (!outputSuccess) { + revert Output_StaticCallFailed(); + } + _parseReturnDataAndWriteToStorage(returnValues, outputReturnData, targetStorageContract, targetStorageSlot, account); + } else { + revert InvalidOutputParamFetcherType(); + } + } + + /// @dev Validate the constraints => compare the value with the reference data + function _validateConstraints(bytes memory rawValue, Constraint[] calldata constraints) + private + pure + { + if (constraints.length > 0) { + for (uint256 i; i < constraints.length; i++) { + Constraint memory constraint = constraints[i]; + bytes32 returnValue; + assembly { + returnValue := mload(add(rawValue, add(0x20, mul(i, 0x20)))) + } + if (constraint.constraintType == ConstraintType.EQ) { + require(returnValue == bytes32(constraint.referenceData), ConstraintNotMet(ConstraintType.EQ)); + } else if (constraint.constraintType == ConstraintType.GTE) { + require(returnValue >= bytes32(constraint.referenceData), ConstraintNotMet(ConstraintType.GTE)); + } else if (constraint.constraintType == ConstraintType.LTE) { + require(returnValue <= bytes32(constraint.referenceData), ConstraintNotMet(ConstraintType.LTE)); + } else if (constraint.constraintType == ConstraintType.IN) { + (bytes32 lowerBound, bytes32 upperBound) = abi.decode(constraint.referenceData, (bytes32, bytes32)); + require(returnValue >= lowerBound && returnValue <= upperBound, ConstraintNotMet(ConstraintType.IN)); + } else { + revert InvalidConstraintType(); + } + } + } + } + + /// @dev Parse the return data and write to the appropriate storage contract + function _parseReturnDataAndWriteToStorage(uint256 returnValues, bytes memory returnData, address targetStorageContract, bytes32 targetStorageSlot, address account) internal { + for (uint256 i; i < returnValues; i++) { + bytes32 value; + assembly { + value := mload(add(returnData, add(0x20, mul(i, 0x20)))) + } + Storage(targetStorageContract).writeStorage({ + slot: keccak256(abi.encodePacked(targetStorageSlot, i)), + value: value, + account: account + }); + } + } +} diff --git a/biconomy/nexus/1.2.0/node_modules/@biconomy/composability/contracts/Storage.sol b/biconomy/nexus/1.2.0/node_modules/@biconomy/composability/contracts/Storage.sol new file mode 100644 index 0000000..51568fa --- /dev/null +++ b/biconomy/nexus/1.2.0/node_modules/@biconomy/composability/contracts/Storage.sol @@ -0,0 +1,83 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.17; + +/** + * @title Storage + * @dev Contract to handle generic storage operations with cross-chain support + */ +contract Storage { + error SlotNotInitialized(); + + // Mapping to track initialized slots + mapping(bytes32 => bool) private initializedSlots; + + // Mapping to track length of dynamic data + mapping(bytes32 => uint256) private dynamicDataLength; + + /** + * @dev Internal function to write a value to a specific storage slot + */ + function _writeStorage(bytes32 slot, bytes32 value, bytes32 namespace) private { + bytes32 namespacedSlot = getNamespacedSlot(namespace, slot); + initializedSlots[namespacedSlot] = true; + assembly { + sstore(namespacedSlot, value) + } + } + + /** + * @dev Write a value to a specific storage slot + * @param slot The storage slot to write to + * @param value The value to write + */ + function writeStorage(bytes32 slot, bytes32 value, address account) external { + bytes32 namespace = getNamespace(account, msg.sender); + _writeStorage(slot, value, namespace); + } + + /** + * @dev Read a value from a specific namespace and slot + * @param namespace The namespace (typically a contract address) + * @param slot The storage slot to read from + * @return The value stored at the specified namespaced slot + */ + function readStorage(bytes32 namespace, bytes32 slot) external view returns (bytes32) { + bytes32 namespacedSlot = getNamespacedSlot(namespace, slot); + if (!initializedSlots[namespacedSlot]) { + revert SlotNotInitialized(); + } + bytes32 value; + assembly { + value := sload(namespacedSlot) + } + return value; + } + + /** + * @dev Generates a namespaced slot + * @param namespace The namespace (typically a contract address) + * @param slot The storage slot to read from + * @return The namespaced slot + */ + function getNamespacedSlot(bytes32 namespace, bytes32 slot) public pure returns (bytes32) { + return keccak256(abi.encodePacked(namespace, slot)); + } + + /** + * @dev Generates a namespace for a given account and caller + * @param account The account address + * @param caller The caller address + * @return The generated namespace + */ + function getNamespace(address account, address caller) public pure returns (bytes32) { + return keccak256(abi.encodePacked(account, caller)); + } + + /** + * @dev Check if a slot has been initialized + */ + function isSlotInitialized(bytes32 namespace, bytes32 slot) external view returns (bool) { + bytes32 namespacedSlot = getNamespacedSlot(namespace, slot); + return initializedSlots[namespacedSlot]; + } +} diff --git a/biconomy/nexus/1.2.0/node_modules/@biconomy/composability/contracts/interfaces/IComposableExecution.sol b/biconomy/nexus/1.2.0/node_modules/@biconomy/composability/contracts/interfaces/IComposableExecution.sol new file mode 100644 index 0000000..fb64637 --- /dev/null +++ b/biconomy/nexus/1.2.0/node_modules/@biconomy/composability/contracts/interfaces/IComposableExecution.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.23; + +import {ComposableExecution} from "../types/ComposabilityDataTypes.sol"; + +interface IComposableExecution { + function executeComposable(ComposableExecution[] calldata executions) external payable; +} + +interface IComposableExecutionModule is IComposableExecution { + function executeComposableCall(ComposableExecution[] calldata executions) external; + function executeComposableDelegateCall(ComposableExecution[] calldata executions) external; +} diff --git a/biconomy/nexus/1.2.0/node_modules/@biconomy/composability/contracts/types/ComposabilityDataTypes.sol b/biconomy/nexus/1.2.0/node_modules/@biconomy/composability/contracts/types/ComposabilityDataTypes.sol new file mode 100644 index 0000000..2337571 --- /dev/null +++ b/biconomy/nexus/1.2.0/node_modules/@biconomy/composability/contracts/types/ComposabilityDataTypes.sol @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.27; + +// Parameter type for composition +enum InputParamFetcherType { + RAW_BYTES, // Already encoded bytes + STATIC_CALL // Perform a static call + +} + +enum OutputParamFetcherType { + EXEC_RESULT, // The return of the execution call + STATIC_CALL // Call to some other function +} + +// Constraint type for parameter validation +enum ConstraintType { + EQ, // Equal to + GTE, // Greater than or equal to + LTE, // Less than or equal to + IN // In range +} + +// Constraint for parameter validation +struct Constraint { + ConstraintType constraintType; + bytes referenceData; +} + +// Structure to define parameter composition +struct InputParam { + InputParamFetcherType fetcherType; // How to fetch the parameter + bytes paramData; + Constraint[] constraints; +} + +// Structure to define return value handling +struct OutputParam { + OutputParamFetcherType fetcherType; // How to fetch the parameter + bytes paramData; +} + +// Structure to define a composable execution +struct ComposableExecution { + address to; + uint256 value; + bytes4 functionSig; + InputParam[] inputParams; + OutputParam[] outputParams; +} diff --git a/biconomy/nexus/1.2.0/node_modules/@openzeppelin/contracts/interfaces/IERC1967.sol b/biconomy/nexus/1.2.0/node_modules/@openzeppelin/contracts/interfaces/IERC1967.sol new file mode 100644 index 0000000..d285ec8 --- /dev/null +++ b/biconomy/nexus/1.2.0/node_modules/@openzeppelin/contracts/interfaces/IERC1967.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC1967.sol) + +pragma solidity ^0.8.20; + +/** + * @dev ERC-1967: Proxy Storage Slots. This interface contains the events defined in the ERC. + */ +interface IERC1967 { + /** + * @dev Emitted when the implementation is upgraded. + */ + event Upgraded(address indexed implementation); + + /** + * @dev Emitted when the admin account has changed. + */ + event AdminChanged(address previousAdmin, address newAdmin); + + /** + * @dev Emitted when the beacon is changed. + */ + event BeaconUpgraded(address indexed beacon); +} diff --git a/biconomy/nexus/1.2.0/node_modules/@openzeppelin/contracts/proxy/ERC1967/ERC1967Utils.sol b/biconomy/nexus/1.2.0/node_modules/@openzeppelin/contracts/proxy/ERC1967/ERC1967Utils.sol new file mode 100644 index 0000000..1f32013 --- /dev/null +++ b/biconomy/nexus/1.2.0/node_modules/@openzeppelin/contracts/proxy/ERC1967/ERC1967Utils.sol @@ -0,0 +1,177 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.1.0) (proxy/ERC1967/ERC1967Utils.sol) + +pragma solidity ^0.8.21; + +import {IBeacon} from "../beacon/IBeacon.sol"; +import {IERC1967} from "../../interfaces/IERC1967.sol"; +import {Address} from "../../utils/Address.sol"; +import {StorageSlot} from "../../utils/StorageSlot.sol"; + +/** + * @dev This library provides getters and event emitting update functions for + * https://eips.ethereum.org/EIPS/eip-1967[ERC-1967] slots. + */ +library ERC1967Utils { + /** + * @dev Storage slot with the address of the current implementation. + * This is the keccak-256 hash of "eip1967.proxy.implementation" subtracted by 1. + */ + // solhint-disable-next-line private-vars-leading-underscore + bytes32 internal constant IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc; + + /** + * @dev The `implementation` of the proxy is invalid. + */ + error ERC1967InvalidImplementation(address implementation); + + /** + * @dev The `admin` of the proxy is invalid. + */ + error ERC1967InvalidAdmin(address admin); + + /** + * @dev The `beacon` of the proxy is invalid. + */ + error ERC1967InvalidBeacon(address beacon); + + /** + * @dev An upgrade function sees `msg.value > 0` that may be lost. + */ + error ERC1967NonPayable(); + + /** + * @dev Returns the current implementation address. + */ + function getImplementation() internal view returns (address) { + return StorageSlot.getAddressSlot(IMPLEMENTATION_SLOT).value; + } + + /** + * @dev Stores a new address in the ERC-1967 implementation slot. + */ + function _setImplementation(address newImplementation) private { + if (newImplementation.code.length == 0) { + revert ERC1967InvalidImplementation(newImplementation); + } + StorageSlot.getAddressSlot(IMPLEMENTATION_SLOT).value = newImplementation; + } + + /** + * @dev Performs implementation upgrade with additional setup call if data is nonempty. + * This function is payable only if the setup call is performed, otherwise `msg.value` is rejected + * to avoid stuck value in the contract. + * + * Emits an {IERC1967-Upgraded} event. + */ + function upgradeToAndCall(address newImplementation, bytes memory data) internal { + _setImplementation(newImplementation); + emit IERC1967.Upgraded(newImplementation); + + if (data.length > 0) { + Address.functionDelegateCall(newImplementation, data); + } else { + _checkNonPayable(); + } + } + + /** + * @dev Storage slot with the admin of the contract. + * This is the keccak-256 hash of "eip1967.proxy.admin" subtracted by 1. + */ + // solhint-disable-next-line private-vars-leading-underscore + bytes32 internal constant ADMIN_SLOT = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103; + + /** + * @dev Returns the current admin. + * + * TIP: To get this value clients can read directly from the storage slot shown below (specified by ERC-1967) using + * the https://eth.wiki/json-rpc/API#eth_getstorageat[`eth_getStorageAt`] RPC call. + * `0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103` + */ + function getAdmin() internal view returns (address) { + return StorageSlot.getAddressSlot(ADMIN_SLOT).value; + } + + /** + * @dev Stores a new address in the ERC-1967 admin slot. + */ + function _setAdmin(address newAdmin) private { + if (newAdmin == address(0)) { + revert ERC1967InvalidAdmin(address(0)); + } + StorageSlot.getAddressSlot(ADMIN_SLOT).value = newAdmin; + } + + /** + * @dev Changes the admin of the proxy. + * + * Emits an {IERC1967-AdminChanged} event. + */ + function changeAdmin(address newAdmin) internal { + emit IERC1967.AdminChanged(getAdmin(), newAdmin); + _setAdmin(newAdmin); + } + + /** + * @dev The storage slot of the UpgradeableBeacon contract which defines the implementation for this proxy. + * This is the keccak-256 hash of "eip1967.proxy.beacon" subtracted by 1. + */ + // solhint-disable-next-line private-vars-leading-underscore + bytes32 internal constant BEACON_SLOT = 0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50; + + /** + * @dev Returns the current beacon. + */ + function getBeacon() internal view returns (address) { + return StorageSlot.getAddressSlot(BEACON_SLOT).value; + } + + /** + * @dev Stores a new beacon in the ERC-1967 beacon slot. + */ + function _setBeacon(address newBeacon) private { + if (newBeacon.code.length == 0) { + revert ERC1967InvalidBeacon(newBeacon); + } + + StorageSlot.getAddressSlot(BEACON_SLOT).value = newBeacon; + + address beaconImplementation = IBeacon(newBeacon).implementation(); + if (beaconImplementation.code.length == 0) { + revert ERC1967InvalidImplementation(beaconImplementation); + } + } + + /** + * @dev Change the beacon and trigger a setup call if data is nonempty. + * This function is payable only if the setup call is performed, otherwise `msg.value` is rejected + * to avoid stuck value in the contract. + * + * Emits an {IERC1967-BeaconUpgraded} event. + * + * CAUTION: Invoking this function has no effect on an instance of {BeaconProxy} since v5, since + * it uses an immutable beacon without looking at the value of the ERC-1967 beacon slot for + * efficiency. + */ + function upgradeBeaconToAndCall(address newBeacon, bytes memory data) internal { + _setBeacon(newBeacon); + emit IERC1967.BeaconUpgraded(newBeacon); + + if (data.length > 0) { + Address.functionDelegateCall(IBeacon(newBeacon).implementation(), data); + } else { + _checkNonPayable(); + } + } + + /** + * @dev Reverts if `msg.value` is not zero. It can be used to avoid `msg.value` stuck in the contract + * if an upgrade doesn't perform an initialization call. + */ + function _checkNonPayable() private { + if (msg.value > 0) { + revert ERC1967NonPayable(); + } + } +} diff --git a/biconomy/nexus/1.2.0/node_modules/@openzeppelin/contracts/proxy/Proxy.sol b/biconomy/nexus/1.2.0/node_modules/@openzeppelin/contracts/proxy/Proxy.sol new file mode 100644 index 0000000..0e73651 --- /dev/null +++ b/biconomy/nexus/1.2.0/node_modules/@openzeppelin/contracts/proxy/Proxy.sol @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (proxy/Proxy.sol) + +pragma solidity ^0.8.20; + +/** + * @dev This abstract contract provides a fallback function that delegates all calls to another contract using the EVM + * instruction `delegatecall`. We refer to the second contract as the _implementation_ behind the proxy, and it has to + * be specified by overriding the virtual {_implementation} function. + * + * Additionally, delegation to the implementation can be triggered manually through the {_fallback} function, or to a + * different contract through the {_delegate} function. + * + * The success and return data of the delegated call will be returned back to the caller of the proxy. + */ +abstract contract Proxy { + /** + * @dev Delegates the current call to `implementation`. + * + * This function does not return to its internal call site, it will return directly to the external caller. + */ + function _delegate(address implementation) internal virtual { + assembly { + // Copy msg.data. We take full control of memory in this inline assembly + // block because it will not return to Solidity code. We overwrite the + // Solidity scratch pad at memory position 0. + calldatacopy(0, 0, calldatasize()) + + // Call the implementation. + // out and outsize are 0 because we don't know the size yet. + let result := delegatecall(gas(), implementation, 0, calldatasize(), 0, 0) + + // Copy the returned data. + returndatacopy(0, 0, returndatasize()) + + switch result + // delegatecall returns 0 on error. + case 0 { + revert(0, returndatasize()) + } + default { + return(0, returndatasize()) + } + } + } + + /** + * @dev This is a virtual function that should be overridden so it returns the address to which the fallback + * function and {_fallback} should delegate. + */ + function _implementation() internal view virtual returns (address); + + /** + * @dev Delegates the current call to the address returned by `_implementation()`. + * + * This function does not return to its internal call site, it will return directly to the external caller. + */ + function _fallback() internal virtual { + _delegate(_implementation()); + } + + /** + * @dev Fallback function that delegates calls to the address returned by `_implementation()`. Will run if no other + * function in the contract matches the call data. + */ + fallback() external payable virtual { + _fallback(); + } +} diff --git a/biconomy/nexus/1.2.0/node_modules/@openzeppelin/contracts/proxy/beacon/IBeacon.sol b/biconomy/nexus/1.2.0/node_modules/@openzeppelin/contracts/proxy/beacon/IBeacon.sol new file mode 100644 index 0000000..36a3c76 --- /dev/null +++ b/biconomy/nexus/1.2.0/node_modules/@openzeppelin/contracts/proxy/beacon/IBeacon.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (proxy/beacon/IBeacon.sol) + +pragma solidity ^0.8.20; + +/** + * @dev This is the interface that {BeaconProxy} expects of its beacon. + */ +interface IBeacon { + /** + * @dev Must return an address that can be used as a delegate call target. + * + * {UpgradeableBeacon} will check that this address is a contract. + */ + function implementation() external view returns (address); +} diff --git a/biconomy/nexus/1.2.0/node_modules/@openzeppelin/contracts/utils/Address.sol b/biconomy/nexus/1.2.0/node_modules/@openzeppelin/contracts/utils/Address.sol new file mode 100644 index 0000000..a1c8af2 --- /dev/null +++ b/biconomy/nexus/1.2.0/node_modules/@openzeppelin/contracts/utils/Address.sol @@ -0,0 +1,150 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.1.0) (utils/Address.sol) + +pragma solidity ^0.8.20; + +import {Errors} from "./Errors.sol"; + +/** + * @dev Collection of functions related to the address type + */ +library Address { + /** + * @dev There's no code at `target` (it is not a contract). + */ + error AddressEmptyCode(address target); + + /** + * @dev Replacement for Solidity's `transfer`: sends `amount` wei to + * `recipient`, forwarding all available gas and reverting on errors. + * + * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost + * of certain opcodes, possibly making contracts go over the 2300 gas limit + * imposed by `transfer`, making them unable to receive funds via + * `transfer`. {sendValue} removes this limitation. + * + * https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more]. + * + * IMPORTANT: because control is transferred to `recipient`, care must be + * taken to not create reentrancy vulnerabilities. Consider using + * {ReentrancyGuard} or the + * https://solidity.readthedocs.io/en/v0.8.20/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern]. + */ + function sendValue(address payable recipient, uint256 amount) internal { + if (address(this).balance < amount) { + revert Errors.InsufficientBalance(address(this).balance, amount); + } + + (bool success, ) = recipient.call{value: amount}(""); + if (!success) { + revert Errors.FailedCall(); + } + } + + /** + * @dev Performs a Solidity function call using a low level `call`. A + * plain `call` is an unsafe replacement for a function call: use this + * function instead. + * + * If `target` reverts with a revert reason or custom error, it is bubbled + * up by this function (like regular Solidity function calls). However, if + * the call reverted with no returned reason, this function reverts with a + * {Errors.FailedCall} error. + * + * Returns the raw returned data. To convert to the expected return value, + * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`]. + * + * Requirements: + * + * - `target` must be a contract. + * - calling `target` with `data` must not revert. + */ + function functionCall(address target, bytes memory data) internal returns (bytes memory) { + return functionCallWithValue(target, data, 0); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], + * but also transferring `value` wei to `target`. + * + * Requirements: + * + * - the calling contract must have an ETH balance of at least `value`. + * - the called Solidity function must be `payable`. + */ + function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) { + if (address(this).balance < value) { + revert Errors.InsufficientBalance(address(this).balance, value); + } + (bool success, bytes memory returndata) = target.call{value: value}(data); + return verifyCallResultFromTarget(target, success, returndata); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], + * but performing a static call. + */ + function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) { + (bool success, bytes memory returndata) = target.staticcall(data); + return verifyCallResultFromTarget(target, success, returndata); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], + * but performing a delegate call. + */ + function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) { + (bool success, bytes memory returndata) = target.delegatecall(data); + return verifyCallResultFromTarget(target, success, returndata); + } + + /** + * @dev Tool to verify that a low level call to smart-contract was successful, and reverts if the target + * was not a contract or bubbling up the revert reason (falling back to {Errors.FailedCall}) in case + * of an unsuccessful call. + */ + function verifyCallResultFromTarget( + address target, + bool success, + bytes memory returndata + ) internal view returns (bytes memory) { + if (!success) { + _revert(returndata); + } else { + // only check if target is a contract if the call was successful and the return data is empty + // otherwise we already know that it was a contract + if (returndata.length == 0 && target.code.length == 0) { + revert AddressEmptyCode(target); + } + return returndata; + } + } + + /** + * @dev Tool to verify that a low level call was successful, and reverts if it wasn't, either by bubbling the + * revert reason or with a default {Errors.FailedCall} error. + */ + function verifyCallResult(bool success, bytes memory returndata) internal pure returns (bytes memory) { + if (!success) { + _revert(returndata); + } else { + return returndata; + } + } + + /** + * @dev Reverts with returndata if present. Otherwise reverts with {Errors.FailedCall}. + */ + function _revert(bytes memory returndata) private pure { + // Look for revert reason and bubble it up if present + if (returndata.length > 0) { + // The easiest way to bubble the revert reason is using memory via assembly + assembly ("memory-safe") { + let returndata_size := mload(returndata) + revert(add(32, returndata), returndata_size) + } + } else { + revert Errors.FailedCall(); + } + } +} diff --git a/biconomy/nexus/1.2.0/node_modules/@openzeppelin/contracts/utils/Errors.sol b/biconomy/nexus/1.2.0/node_modules/@openzeppelin/contracts/utils/Errors.sol new file mode 100644 index 0000000..442fc18 --- /dev/null +++ b/biconomy/nexus/1.2.0/node_modules/@openzeppelin/contracts/utils/Errors.sol @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.1.0) (utils/Errors.sol) + +pragma solidity ^0.8.20; + +/** + * @dev Collection of common custom errors used in multiple contracts + * + * IMPORTANT: Backwards compatibility is not guaranteed in future versions of the library. + * It is recommended to avoid relying on the error API for critical functionality. + * + * _Available since v5.1._ + */ +library Errors { + /** + * @dev The ETH balance of the account is not enough to perform the operation. + */ + error InsufficientBalance(uint256 balance, uint256 needed); + + /** + * @dev A call to an address target failed. The target may have reverted. + */ + error FailedCall(); + + /** + * @dev The deployment failed. + */ + error FailedDeployment(); + + /** + * @dev A necessary precompile is missing. + */ + error MissingPrecompile(address); +} diff --git a/biconomy/nexus/1.2.0/node_modules/@openzeppelin/contracts/utils/StorageSlot.sol b/biconomy/nexus/1.2.0/node_modules/@openzeppelin/contracts/utils/StorageSlot.sol new file mode 100644 index 0000000..aebb105 --- /dev/null +++ b/biconomy/nexus/1.2.0/node_modules/@openzeppelin/contracts/utils/StorageSlot.sol @@ -0,0 +1,143 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.1.0) (utils/StorageSlot.sol) +// This file was procedurally generated from scripts/generate/templates/StorageSlot.js. + +pragma solidity ^0.8.20; + +/** + * @dev Library for reading and writing primitive types to specific storage slots. + * + * Storage slots are often used to avoid storage conflict when dealing with upgradeable contracts. + * This library helps with reading and writing to such slots without the need for inline assembly. + * + * The functions in this library return Slot structs that contain a `value` member that can be used to read or write. + * + * Example usage to set ERC-1967 implementation slot: + * ```solidity + * contract ERC1967 { + * // Define the slot. Alternatively, use the SlotDerivation library to derive the slot. + * bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc; + * + * function _getImplementation() internal view returns (address) { + * return StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value; + * } + * + * function _setImplementation(address newImplementation) internal { + * require(newImplementation.code.length > 0); + * StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation; + * } + * } + * ``` + * + * TIP: Consider using this library along with {SlotDerivation}. + */ +library StorageSlot { + struct AddressSlot { + address value; + } + + struct BooleanSlot { + bool value; + } + + struct Bytes32Slot { + bytes32 value; + } + + struct Uint256Slot { + uint256 value; + } + + struct Int256Slot { + int256 value; + } + + struct StringSlot { + string value; + } + + struct BytesSlot { + bytes value; + } + + /** + * @dev Returns an `AddressSlot` with member `value` located at `slot`. + */ + function getAddressSlot(bytes32 slot) internal pure returns (AddressSlot storage r) { + assembly ("memory-safe") { + r.slot := slot + } + } + + /** + * @dev Returns a `BooleanSlot` with member `value` located at `slot`. + */ + function getBooleanSlot(bytes32 slot) internal pure returns (BooleanSlot storage r) { + assembly ("memory-safe") { + r.slot := slot + } + } + + /** + * @dev Returns a `Bytes32Slot` with member `value` located at `slot`. + */ + function getBytes32Slot(bytes32 slot) internal pure returns (Bytes32Slot storage r) { + assembly ("memory-safe") { + r.slot := slot + } + } + + /** + * @dev Returns a `Uint256Slot` with member `value` located at `slot`. + */ + function getUint256Slot(bytes32 slot) internal pure returns (Uint256Slot storage r) { + assembly ("memory-safe") { + r.slot := slot + } + } + + /** + * @dev Returns a `Int256Slot` with member `value` located at `slot`. + */ + function getInt256Slot(bytes32 slot) internal pure returns (Int256Slot storage r) { + assembly ("memory-safe") { + r.slot := slot + } + } + + /** + * @dev Returns a `StringSlot` with member `value` located at `slot`. + */ + function getStringSlot(bytes32 slot) internal pure returns (StringSlot storage r) { + assembly ("memory-safe") { + r.slot := slot + } + } + + /** + * @dev Returns an `StringSlot` representation of the string storage pointer `store`. + */ + function getStringSlot(string storage store) internal pure returns (StringSlot storage r) { + assembly ("memory-safe") { + r.slot := store.slot + } + } + + /** + * @dev Returns a `BytesSlot` with member `value` located at `slot`. + */ + function getBytesSlot(bytes32 slot) internal pure returns (BytesSlot storage r) { + assembly ("memory-safe") { + r.slot := slot + } + } + + /** + * @dev Returns an `BytesSlot` representation of the bytes storage pointer `store`. + */ + function getBytesSlot(bytes storage store) internal pure returns (BytesSlot storage r) { + assembly ("memory-safe") { + r.slot := store.slot + } + } +} diff --git a/biconomy/nexus/1.2.0/node_modules/account-abstraction/contracts/interfaces/IAggregator.sol b/biconomy/nexus/1.2.0/node_modules/account-abstraction/contracts/interfaces/IAggregator.sol new file mode 100644 index 0000000..070d8f2 --- /dev/null +++ b/biconomy/nexus/1.2.0/node_modules/account-abstraction/contracts/interfaces/IAggregator.sol @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity >=0.7.5; + +import "./PackedUserOperation.sol"; + +/** + * Aggregated Signatures validator. + */ +interface IAggregator { + /** + * Validate aggregated signature. + * Revert if the aggregated signature does not match the given list of operations. + * @param userOps - Array of UserOperations to validate the signature for. + * @param signature - The aggregated signature. + */ + function validateSignatures( + PackedUserOperation[] calldata userOps, + bytes calldata signature + ) external view; + + /** + * Validate signature of a single userOp. + * This method should be called by bundler after EntryPointSimulation.simulateValidation() returns + * the aggregator this account uses. + * First it validates the signature over the userOp. Then it returns data to be used when creating the handleOps. + * @param userOp - The userOperation received from the user. + * @return sigForUserOp - The value to put into the signature field of the userOp when calling handleOps. + * (usually empty, unless account and aggregator support some kind of "multisig". + */ + function validateUserOpSignature( + PackedUserOperation calldata userOp + ) external view returns (bytes memory sigForUserOp); + + /** + * Aggregate multiple signatures into a single value. + * This method is called off-chain to calculate the signature to pass with handleOps() + * bundler MAY use optimized custom code perform this aggregation. + * @param userOps - Array of UserOperations to collect the signatures from. + * @return aggregatedSignature - The aggregated signature. + */ + function aggregateSignatures( + PackedUserOperation[] calldata userOps + ) external view returns (bytes memory aggregatedSignature); +} diff --git a/biconomy/nexus/1.2.0/node_modules/account-abstraction/contracts/interfaces/IEntryPoint.sol b/biconomy/nexus/1.2.0/node_modules/account-abstraction/contracts/interfaces/IEntryPoint.sol new file mode 100644 index 0000000..28c26f9 --- /dev/null +++ b/biconomy/nexus/1.2.0/node_modules/account-abstraction/contracts/interfaces/IEntryPoint.sol @@ -0,0 +1,223 @@ +/** + ** Account-Abstraction (EIP-4337) singleton EntryPoint implementation. + ** Only one instance required on each chain. + **/ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity >=0.7.5; + +/* solhint-disable avoid-low-level-calls */ +/* solhint-disable no-inline-assembly */ +/* solhint-disable reason-string */ + +import "./PackedUserOperation.sol"; +import "./IStakeManager.sol"; +import "./IAggregator.sol"; +import "./INonceManager.sol"; + +interface IEntryPoint is IStakeManager, INonceManager { + /*** + * An event emitted after each successful request. + * @param userOpHash - Unique identifier for the request (hash its entire content, except signature). + * @param sender - The account that generates this request. + * @param paymaster - If non-null, the paymaster that pays for this request. + * @param nonce - The nonce value from the request. + * @param success - True if the sender transaction succeeded, false if reverted. + * @param actualGasCost - Actual amount paid (by account or paymaster) for this UserOperation. + * @param actualGasUsed - Total gas used by this UserOperation (including preVerification, creation, + * validation and execution). + */ + event UserOperationEvent( + bytes32 indexed userOpHash, + address indexed sender, + address indexed paymaster, + uint256 nonce, + bool success, + uint256 actualGasCost, + uint256 actualGasUsed + ); + + /** + * Account "sender" was deployed. + * @param userOpHash - The userOp that deployed this account. UserOperationEvent will follow. + * @param sender - The account that is deployed + * @param factory - The factory used to deploy this account (in the initCode) + * @param paymaster - The paymaster used by this UserOp + */ + event AccountDeployed( + bytes32 indexed userOpHash, + address indexed sender, + address factory, + address paymaster + ); + + /** + * An event emitted if the UserOperation "callData" reverted with non-zero length. + * @param userOpHash - The request unique identifier. + * @param sender - The sender of this request. + * @param nonce - The nonce used in the request. + * @param revertReason - The return bytes from the (reverted) call to "callData". + */ + event UserOperationRevertReason( + bytes32 indexed userOpHash, + address indexed sender, + uint256 nonce, + bytes revertReason + ); + + /** + * An event emitted if the UserOperation Paymaster's "postOp" call reverted with non-zero length. + * @param userOpHash - The request unique identifier. + * @param sender - The sender of this request. + * @param nonce - The nonce used in the request. + * @param revertReason - The return bytes from the (reverted) call to "callData". + */ + event PostOpRevertReason( + bytes32 indexed userOpHash, + address indexed sender, + uint256 nonce, + bytes revertReason + ); + + /** + * UserOp consumed more than prefund. The UserOperation is reverted, and no refund is made. + * @param userOpHash - The request unique identifier. + * @param sender - The sender of this request. + * @param nonce - The nonce used in the request. + */ + event UserOperationPrefundTooLow( + bytes32 indexed userOpHash, + address indexed sender, + uint256 nonce + ); + + /** + * An event emitted by handleOps(), before starting the execution loop. + * Any event emitted before this event, is part of the validation. + */ + event BeforeExecution(); + + /** + * Signature aggregator used by the following UserOperationEvents within this bundle. + * @param aggregator - The aggregator used for the following UserOperationEvents. + */ + event SignatureAggregatorChanged(address indexed aggregator); + + /** + * A custom revert error of handleOps, to identify the offending op. + * Should be caught in off-chain handleOps simulation and not happen on-chain. + * Useful for mitigating DoS attempts against batchers or for troubleshooting of factory/account/paymaster reverts. + * NOTE: If simulateValidation passes successfully, there should be no reason for handleOps to fail on it. + * @param opIndex - Index into the array of ops to the failed one (in simulateValidation, this is always zero). + * @param reason - Revert reason. The string starts with a unique code "AAmn", + * where "m" is "1" for factory, "2" for account and "3" for paymaster issues, + * so a failure can be attributed to the correct entity. + */ + error FailedOp(uint256 opIndex, string reason); + + /** + * A custom revert error of handleOps, to report a revert by account or paymaster. + * @param opIndex - Index into the array of ops to the failed one (in simulateValidation, this is always zero). + * @param reason - Revert reason. see FailedOp(uint256,string), above + * @param inner - data from inner cought revert reason + * @dev note that inner is truncated to 2048 bytes + */ + error FailedOpWithRevert(uint256 opIndex, string reason, bytes inner); + + error PostOpReverted(bytes returnData); + + /** + * Error case when a signature aggregator fails to verify the aggregated signature it had created. + * @param aggregator The aggregator that failed to verify the signature + */ + error SignatureValidationFailed(address aggregator); + + // Return value of getSenderAddress. + error SenderAddressResult(address sender); + + // UserOps handled, per aggregator. + struct UserOpsPerAggregator { + PackedUserOperation[] userOps; + // Aggregator address + IAggregator aggregator; + // Aggregated signature + bytes signature; + } + + /** + * Execute a batch of UserOperations. + * No signature aggregator is used. + * If any account requires an aggregator (that is, it returned an aggregator when + * performing simulateValidation), then handleAggregatedOps() must be used instead. + * @param ops - The operations to execute. + * @param beneficiary - The address to receive the fees. + */ + function handleOps( + PackedUserOperation[] calldata ops, + address payable beneficiary + ) external; + + /** + * Execute a batch of UserOperation with Aggregators + * @param opsPerAggregator - The operations to execute, grouped by aggregator (or address(0) for no-aggregator accounts). + * @param beneficiary - The address to receive the fees. + */ + function handleAggregatedOps( + UserOpsPerAggregator[] calldata opsPerAggregator, + address payable beneficiary + ) external; + + /** + * Generate a request Id - unique identifier for this request. + * The request ID is a hash over the content of the userOp (except the signature), the entrypoint and the chainid. + * @param userOp - The user operation to generate the request ID for. + * @return hash the hash of this UserOperation + */ + function getUserOpHash( + PackedUserOperation calldata userOp + ) external view returns (bytes32); + + /** + * Gas and return values during simulation. + * @param preOpGas - The gas used for validation (including preValidationGas) + * @param prefund - The required prefund for this operation + * @param accountValidationData - returned validationData from account. + * @param paymasterValidationData - return validationData from paymaster. + * @param paymasterContext - Returned by validatePaymasterUserOp (to be passed into postOp) + */ + struct ReturnInfo { + uint256 preOpGas; + uint256 prefund; + uint256 accountValidationData; + uint256 paymasterValidationData; + bytes paymasterContext; + } + + /** + * Returned aggregated signature info: + * The aggregator returned by the account, and its current stake. + */ + struct AggregatorStakeInfo { + address aggregator; + StakeInfo stakeInfo; + } + + /** + * Get counterfactual sender address. + * Calculate the sender contract address that will be generated by the initCode and salt in the UserOperation. + * This method always revert, and returns the address in SenderAddressResult error + * @param initCode - The constructor code to be passed into the UserOperation. + */ + function getSenderAddress(bytes memory initCode) external; + + error DelegateAndRevert(bool success, bytes ret); + + /** + * Helper method for dry-run testing. + * @dev calling this method, the EntryPoint will make a delegatecall to the given data, and report (via revert) the result. + * The method always revert, so is only useful off-chain for dry run calls, in cases where state-override to replace + * actual EntryPoint code is less convenient. + * @param target a target contract to make a delegatecall from entrypoint + * @param data data to pass to target in a delegatecall + */ + function delegateAndRevert(address target, bytes calldata data) external; +} diff --git a/biconomy/nexus/1.2.0/node_modules/account-abstraction/contracts/interfaces/INonceManager.sol b/biconomy/nexus/1.2.0/node_modules/account-abstraction/contracts/interfaces/INonceManager.sol new file mode 100644 index 0000000..2f993f6 --- /dev/null +++ b/biconomy/nexus/1.2.0/node_modules/account-abstraction/contracts/interfaces/INonceManager.sol @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity >=0.7.5; + +interface INonceManager { + + /** + * Return the next nonce for this sender. + * Within a given key, the nonce values are sequenced (starting with zero, and incremented by one on each userop) + * But UserOp with different keys can come with arbitrary order. + * + * @param sender the account address + * @param key the high 192 bit of the nonce + * @return nonce a full nonce to pass for next UserOp with this sender. + */ + function getNonce(address sender, uint192 key) + external view returns (uint256 nonce); + + /** + * Manually increment the nonce of the sender. + * This method is exposed just for completeness.. + * Account does NOT need to call it, neither during validation, nor elsewhere, + * as the EntryPoint will update the nonce regardless. + * Possible use-case is call it with various keys to "initialize" their nonces to one, so that future + * UserOperations will not pay extra for the first transaction with a given key. + */ + function incrementNonce(uint192 key) external; +} diff --git a/biconomy/nexus/1.2.0/node_modules/account-abstraction/contracts/interfaces/IStakeManager.sol b/biconomy/nexus/1.2.0/node_modules/account-abstraction/contracts/interfaces/IStakeManager.sol new file mode 100644 index 0000000..69083e9 --- /dev/null +++ b/biconomy/nexus/1.2.0/node_modules/account-abstraction/contracts/interfaces/IStakeManager.sol @@ -0,0 +1,111 @@ +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity >=0.7.5; + +/** + * Manage deposits and stakes. + * Deposit is just a balance used to pay for UserOperations (either by a paymaster or an account). + * Stake is value locked for at least "unstakeDelay" by the staked entity. + */ +interface IStakeManager { + event Deposited(address indexed account, uint256 totalDeposit); + + event Withdrawn( + address indexed account, + address withdrawAddress, + uint256 amount + ); + + // Emitted when stake or unstake delay are modified. + event StakeLocked( + address indexed account, + uint256 totalStaked, + uint256 unstakeDelaySec + ); + + // Emitted once a stake is scheduled for withdrawal. + event StakeUnlocked(address indexed account, uint256 withdrawTime); + + event StakeWithdrawn( + address indexed account, + address withdrawAddress, + uint256 amount + ); + + /** + * @param deposit - The entity's deposit. + * @param staked - True if this entity is staked. + * @param stake - Actual amount of ether staked for this entity. + * @param unstakeDelaySec - Minimum delay to withdraw the stake. + * @param withdrawTime - First block timestamp where 'withdrawStake' will be callable, or zero if already locked. + * @dev Sizes were chosen so that deposit fits into one cell (used during handleOp) + * and the rest fit into a 2nd cell (used during stake/unstake) + * - 112 bit allows for 10^15 eth + * - 48 bit for full timestamp + * - 32 bit allows 150 years for unstake delay + */ + struct DepositInfo { + uint256 deposit; + bool staked; + uint112 stake; + uint32 unstakeDelaySec; + uint48 withdrawTime; + } + + // API struct used by getStakeInfo and simulateValidation. + struct StakeInfo { + uint256 stake; + uint256 unstakeDelaySec; + } + + /** + * Get deposit info. + * @param account - The account to query. + * @return info - Full deposit information of given account. + */ + function getDepositInfo( + address account + ) external view returns (DepositInfo memory info); + + /** + * Get account balance. + * @param account - The account to query. + * @return - The deposit (for gas payment) of the account. + */ + function balanceOf(address account) external view returns (uint256); + + /** + * Add to the deposit of the given account. + * @param account - The account to add to. + */ + function depositTo(address account) external payable; + + /** + * Add to the account's stake - amount and delay + * any pending unstake is first cancelled. + * @param _unstakeDelaySec - The new lock duration before the deposit can be withdrawn. + */ + function addStake(uint32 _unstakeDelaySec) external payable; + + /** + * Attempt to unlock the stake. + * The value can be withdrawn (using withdrawStake) after the unstake delay. + */ + function unlockStake() external; + + /** + * Withdraw from the (unlocked) stake. + * Must first call unlockStake and wait for the unstakeDelay to pass. + * @param withdrawAddress - The address to send withdrawn value. + */ + function withdrawStake(address payable withdrawAddress) external; + + /** + * Withdraw from the deposit. + * @param withdrawAddress - The address to send withdrawn value. + * @param withdrawAmount - The amount to withdraw. + */ + function withdrawTo( + address payable withdrawAddress, + uint256 withdrawAmount + ) external; +} diff --git a/biconomy/nexus/1.2.0/node_modules/account-abstraction/contracts/interfaces/PackedUserOperation.sol b/biconomy/nexus/1.2.0/node_modules/account-abstraction/contracts/interfaces/PackedUserOperation.sol new file mode 100644 index 0000000..fe20de5 --- /dev/null +++ b/biconomy/nexus/1.2.0/node_modules/account-abstraction/contracts/interfaces/PackedUserOperation.sol @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity >=0.7.5; + +/** + * User Operation struct + * @param sender - The sender account of this request. + * @param nonce - Unique value the sender uses to verify it is not a replay. + * @param initCode - If set, the account contract will be created by this constructor/ + * @param callData - The method call to execute on this account. + * @param accountGasLimits - Packed gas limits for validateUserOp and gas limit passed to the callData method call. + * @param preVerificationGas - Gas not calculated by the handleOps method, but added to the gas paid. + * Covers batch overhead. + * @param gasFees - packed gas fields maxPriorityFeePerGas and maxFeePerGas - Same as EIP-1559 gas parameters. + * @param paymasterAndData - If set, this field holds the paymaster address, verification gas limit, postOp gas limit and paymaster-specific extra data + * The paymaster will pay for the transaction instead of the sender. + * @param signature - Sender-verified signature over the entire request, the EntryPoint address and the chain ID. + */ +struct PackedUserOperation { + address sender; + uint256 nonce; + bytes initCode; + bytes callData; + bytes32 accountGasLimits; + uint256 preVerificationGas; + bytes32 gasFees; + bytes paymasterAndData; + bytes signature; +} diff --git a/biconomy/nexus/1.2.0/node_modules/excessively-safe-call/src/ExcessivelySafeCall.sol b/biconomy/nexus/1.2.0/node_modules/excessively-safe-call/src/ExcessivelySafeCall.sol new file mode 100644 index 0000000..eebf39c --- /dev/null +++ b/biconomy/nexus/1.2.0/node_modules/excessively-safe-call/src/ExcessivelySafeCall.sol @@ -0,0 +1,138 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity >=0.7.6; + +library ExcessivelySafeCall { + uint256 constant LOW_28_MASK = + 0x00000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffff; + + /// @notice Use when you _really_ really _really_ don't trust the called + /// contract. This prevents the called contract from causing reversion of + /// the caller in as many ways as we can. + /// @dev The main difference between this and a solidity low-level call is + /// that we limit the number of bytes that the callee can cause to be + /// copied to caller memory. This prevents stupid things like malicious + /// contracts returning 10,000,000 bytes causing a local OOG when copying + /// to memory. + /// @param _target The address to call + /// @param _gas The amount of gas to forward to the remote contract + /// @param _value The value in wei to send to the remote contract + /// @param _maxCopy The maximum number of bytes of returndata to copy + /// to memory. + /// @param _calldata The data to send to the remote contract + /// @return success and returndata, as `.call()`. Returndata is capped to + /// `_maxCopy` bytes. + function excessivelySafeCall( + address _target, + uint256 _gas, + uint256 _value, + uint16 _maxCopy, + bytes memory _calldata + ) internal returns (bool, bytes memory) { + // set up for assembly call + uint256 _toCopy; + bool _success; + bytes memory _returnData = new bytes(_maxCopy); + // dispatch message to recipient + // by assembly calling "handle" function + // we call via assembly to avoid memcopying a very large returndata + // returned by a malicious contract + assembly { + _success := call( + _gas, // gas + _target, // recipient + _value, // ether value + add(_calldata, 0x20), // inloc + mload(_calldata), // inlen + 0, // outloc + 0 // outlen + ) + // limit our copy to 256 bytes + _toCopy := returndatasize() + if gt(_toCopy, _maxCopy) { + _toCopy := _maxCopy + } + // Store the length of the copied bytes + mstore(_returnData, _toCopy) + // copy the bytes from returndata[0:_toCopy] + returndatacopy(add(_returnData, 0x20), 0, _toCopy) + } + return (_success, _returnData); + } + + /// @notice Use when you _really_ really _really_ don't trust the called + /// contract. This prevents the called contract from causing reversion of + /// the caller in as many ways as we can. + /// @dev The main difference between this and a solidity low-level call is + /// that we limit the number of bytes that the callee can cause to be + /// copied to caller memory. This prevents stupid things like malicious + /// contracts returning 10,000,000 bytes causing a local OOG when copying + /// to memory. + /// @param _target The address to call + /// @param _gas The amount of gas to forward to the remote contract + /// @param _maxCopy The maximum number of bytes of returndata to copy + /// to memory. + /// @param _calldata The data to send to the remote contract + /// @return success and returndata, as `.call()`. Returndata is capped to + /// `_maxCopy` bytes. + function excessivelySafeStaticCall( + address _target, + uint256 _gas, + uint16 _maxCopy, + bytes memory _calldata + ) internal view returns (bool, bytes memory) { + // set up for assembly call + uint256 _toCopy; + bool _success; + bytes memory _returnData = new bytes(_maxCopy); + // dispatch message to recipient + // by assembly calling "handle" function + // we call via assembly to avoid memcopying a very large returndata + // returned by a malicious contract + assembly { + _success := staticcall( + _gas, // gas + _target, // recipient + add(_calldata, 0x20), // inloc + mload(_calldata), // inlen + 0, // outloc + 0 // outlen + ) + // limit our copy to 256 bytes + _toCopy := returndatasize() + if gt(_toCopy, _maxCopy) { + _toCopy := _maxCopy + } + // Store the length of the copied bytes + mstore(_returnData, _toCopy) + // copy the bytes from returndata[0:_toCopy] + returndatacopy(add(_returnData, 0x20), 0, _toCopy) + } + return (_success, _returnData); + } + + /** + * @notice Swaps function selectors in encoded contract calls + * @dev Allows reuse of encoded calldata for functions with identical + * argument types but different names. It simply swaps out the first 4 bytes + * for the new selector. This function modifies memory in place, and should + * only be used with caution. + * @param _newSelector The new 4-byte selector + * @param _buf The encoded contract args + */ + function swapSelector(bytes4 _newSelector, bytes memory _buf) + internal + pure + { + require(_buf.length >= 4); + uint256 _mask = LOW_28_MASK; + assembly { + // load the first word of + let _word := mload(add(_buf, 0x20)) + // mask out the top 4 bytes + // /x + _word := and(_word, _mask) + _word := or(_newSelector, _word) + mstore(add(_buf, 0x20), _word) + } + } +} diff --git a/biconomy/nexus/1.2.0/node_modules/prep/src/LibPREP.sol b/biconomy/nexus/1.2.0/node_modules/prep/src/LibPREP.sol new file mode 100644 index 0000000..1a99f2d --- /dev/null +++ b/biconomy/nexus/1.2.0/node_modules/prep/src/LibPREP.sol @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.23; + +import {EfficientHashLib} from "solady/utils/EfficientHashLib.sol"; +import {LibBit} from "solady/utils/LibBit.sol"; +import {ECDSA} from "solady/utils/ECDSA.sol"; +import {LibBytes} from "solady/utils/LibBytes.sol"; +import {LibEIP7702} from "solady/accounts/LibEIP7702.sol"; + +/// @title LibPREP +/// @notice A library to encapsulate the PREP (Provably Rootless EIP-7702 Proxy) workflow. +/// See: https://blog.biconomy.io/prep-deep-dive/ +library LibPREP { + /// @dev Validates if `digest` and `saltAndDelegation` results in `target`. + /// `saltAndDelegation` is `bytes32((uint256(salt) << 160) | uint160(delegation))`. + /// Returns a non-zero `r` for the PREP signature, if valid. + /// Otherwise returns 0. + /// `r` will be less than `2**160`, allowing for optional storage packing. + function rPREP(address target, bytes32 digest, bytes32 saltAndDelegation) + internal + view + returns (bytes32 r) + { + r = (EfficientHashLib.hash(digest, saltAndDelegation >> 160) << 96) >> 96; + if (!isValid(target, r, address(uint160(uint256(saltAndDelegation))))) r = 0; + } + + /// @dev Returns if `r` and `delegation` results in `target`. + function isValid(address target, bytes32 r, address delegation) internal view returns (bool) { + bytes32 s = EfficientHashLib.hash(r); + bytes32 h; // `keccak256(abi.encodePacked(hex"05", LibRLP.p(0).p(delegation).p(0).encode()))`. + assembly ("memory-safe") { + mstore(0x20, 0x80) + mstore(0x1f, delegation) + mstore(0x0b, 0x05d78094) + h := keccak256(0x27, 0x19) + } + return LibBit.and(target != address(0), ECDSA.tryRecover(h, 27, r, s) == target); + } + + /// @dev Returns if `target` is a PREP. + function isPREP(address target, bytes32 r) internal view returns (bool) { + address delegation = LibEIP7702.delegation(target); + return !LibBit.or(delegation == address(0), r == 0) && isValid(target, r, delegation); + } +} diff --git a/biconomy/nexus/1.2.0/node_modules/sentinellist/src/SentinelList.sol b/biconomy/nexus/1.2.0/node_modules/sentinellist/src/SentinelList.sol new file mode 100644 index 0000000..ffa9b57 --- /dev/null +++ b/biconomy/nexus/1.2.0/node_modules/sentinellist/src/SentinelList.sol @@ -0,0 +1,188 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +// Sentinel address +address constant SENTINEL = address(0x1); +// Zero address +address constant ZERO_ADDRESS = address(0x0); + +/** + * @title SentinelListLib + * @dev Library for managing a linked list of addresses + * @author Rhinestone + */ +library SentinelListLib { + // Struct to hold the linked list + struct SentinelList { + mapping(address => address) entries; + } + + error LinkedList_AlreadyInitialized(); + error LinkedList_InvalidPage(); + error LinkedList_InvalidEntry(address entry); + error LinkedList_EntryAlreadyInList(address entry); + + /** + * Initialize the linked list + * + * @param self The linked list + */ + function init(SentinelList storage self) internal { + if (alreadyInitialized(self)) revert LinkedList_AlreadyInitialized(); + self.entries[SENTINEL] = SENTINEL; + } + + /** + * Check if the linked list is already initialized + * + * @param self The linked list + * + * @return bool True if the linked list is already initialized + */ + function alreadyInitialized(SentinelList storage self) internal view returns (bool) { + return self.entries[SENTINEL] != ZERO_ADDRESS; + } + + /** + * Get the next entry in the linked list + * + * @param self The linked list + * @param entry The current entry + * + * @return address The next entry + */ + function getNext(SentinelList storage self, address entry) internal view returns (address) { + if (entry == ZERO_ADDRESS) { + revert LinkedList_InvalidEntry(entry); + } + return self.entries[entry]; + } + + /** + * Push a new entry to the linked list + * + * @param self The linked list + * @param newEntry The new entry + */ + function push(SentinelList storage self, address newEntry) internal { + if (newEntry == ZERO_ADDRESS || newEntry == SENTINEL) { + revert LinkedList_InvalidEntry(newEntry); + } + if (self.entries[newEntry] != ZERO_ADDRESS) revert LinkedList_EntryAlreadyInList(newEntry); + self.entries[newEntry] = self.entries[SENTINEL]; + self.entries[SENTINEL] = newEntry; + } + + /** + * Safe push a new entry to the linked list + * @dev This ensures that the linked list is initialized and initializes it if it is not + * + * @param self The linked list + * @param newEntry The new entry + */ + function safePush(SentinelList storage self, address newEntry) internal { + if (!alreadyInitialized({ self: self })) { + init({ self: self }); + } + push({ self: self, newEntry: newEntry }); + } + + /** + * Pop an entry from the linked list + * + * @param self The linked list + * @param prevEntry The entry before the entry to pop + * @param popEntry The entry to pop + */ + function pop(SentinelList storage self, address prevEntry, address popEntry) internal { + if (popEntry == ZERO_ADDRESS || popEntry == SENTINEL) { + revert LinkedList_InvalidEntry(prevEntry); + } + if (self.entries[prevEntry] != popEntry) revert LinkedList_InvalidEntry(popEntry); + self.entries[prevEntry] = self.entries[popEntry]; + self.entries[popEntry] = ZERO_ADDRESS; + } + + /** + * Pop all entries from the linked list + * + * @param self The linked list + */ + function popAll(SentinelList storage self) internal { + address next = self.entries[SENTINEL]; + while (next != ZERO_ADDRESS) { + address current = next; + next = self.entries[next]; + self.entries[current] = ZERO_ADDRESS; + } + } + + /** + * Check if the linked list contains an entry + * + * @param self The linked list + * @param entry The entry to check + * + * @return bool True if the linked list contains the entry + */ + function contains(SentinelList storage self, address entry) internal view returns (bool) { + return SENTINEL != entry && self.entries[entry] != ZERO_ADDRESS; + } + + /** + * Get all entries in the linked list + * + * @param self The linked list + * @param start The start entry + * @param pageSize The page size + * + * @return array All entries in the linked list + * @return next The next entry + */ + function getEntriesPaginated( + SentinelList storage self, + address start, + uint256 pageSize + ) + internal + view + returns (address[] memory array, address next) + { + if (start != SENTINEL && !contains(self, start)) revert LinkedList_InvalidEntry(start); + if (pageSize == 0) revert LinkedList_InvalidPage(); + // Init array with max page size + array = new address[](pageSize); + + // Populate return array + uint256 entryCount = 0; + next = self.entries[start]; + while (next != ZERO_ADDRESS && next != SENTINEL && entryCount < pageSize) { + array[entryCount] = next; + next = self.entries[next]; + entryCount++; + } + + /** + * Because of the argument validation, we can assume that the loop will always iterate over + * the valid entry list values + * and the `next` variable will either be an enabled entry or a sentinel address + * (signalling the end). + * + * If we haven't reached the end inside the loop, we need to set the next pointer to + * the last element of the entry array + * because the `next` variable (which is a entry by itself) acting as a pointer to the + * start of the next page is neither + * incSENTINELrent page, nor will it be included in the next one if you pass it as a + * start. + */ + if (next != SENTINEL && entryCount > 0) { + next = array[entryCount - 1]; + } + // Set correct size of returned array + // solhint-disable-next-line no-inline-assembly + /// @solidity memory-safe-assembly + assembly { + mstore(array, entryCount) + } + } +} diff --git a/biconomy/nexus/1.2.0/node_modules/solady/src/accounts/LibEIP7702.sol b/biconomy/nexus/1.2.0/node_modules/solady/src/accounts/LibEIP7702.sol new file mode 100644 index 0000000..a896367 --- /dev/null +++ b/biconomy/nexus/1.2.0/node_modules/solady/src/accounts/LibEIP7702.sol @@ -0,0 +1,150 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +/// @notice Library for EIP7702 operations. +/// @author Solady (https://github.com/vectorized/solady/blob/main/src/accounts/LibEIP7702.sol) +library LibEIP7702 { + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* CUSTOM ERRORS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev The proxy query has failed. + error ProxyQueryFailed(); + + /// @dev Failed to change the proxy admin. + error ChangeProxyAdminFailed(); + + /// @dev Failed to upgrade the proxy. + error UpgradeProxyFailed(); + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* CONSTANTS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev The ERC-1967 storage slot for the implementation in the proxy. + /// `uint256(keccak256("eip1967.proxy.implementation")) - 1`. + bytes32 internal constant ERC1967_IMPLEMENTATION_SLOT = + 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc; + + /// @dev The transient storage slot for requesting the proxy to initialize the implementation. + /// `uint256(keccak256("eip7702.proxy.delegation.initialization.request")) - 1`. + /// While we would love to use a smaller constant, this slot is used in both the proxy + /// and the delegation, so we shall just use bytes32 in case we want to standardize this. + bytes32 internal constant EIP7702_PROXY_DELEGATION_INITIALIZATION_REQUEST_SLOT = + 0x94e11c6e41e7fb92cb8bb65e13fdfbd4eba8b831292a1a220f7915c78c7c078f; + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* AUTHORITY OPERATIONS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev Returns the delegation of the account. + /// If the account is not an EIP7702 authority, the `delegation` will be `address(0)`. + function delegation(address account) internal view returns (address result) { + /// @solidity memory-safe-assembly + assembly { + extcodecopy(account, 0x00, 0x00, 0x20) + // Note: Checking that it starts with hex"ef01" is the most general and futureproof. + // 7702 bytecode is `abi.encodePacked(hex"ef01", uint8(version), address(delegation))`. + result := mul(shr(96, mload(0x03)), eq(0xef01, shr(240, mload(0x00)))) + } + } + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* PROXY OPERATIONS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev Returns the implementation of the proxy. + /// Assumes that the proxy is a proper EIP7702Proxy, if it exists. + function proxyImplementation(address proxy) internal view returns (address result) { + /// @solidity memory-safe-assembly + assembly { + // Although `implementation()` is supported, we'll use a less common + // function selector to avoid accidental collision with other delegations. + mstore(0x00, 0x7dae87cb) // `eip7702ProxyImplementation()`. + let t := staticcall(gas(), proxy, 0x1c, 0x04, 0x00, 0x20) + if iszero(and(gt(returndatasize(), 0x1f), t)) { + mstore(0x00, 0x26ec9b6a) // `ProxyQueryFailed()`. + revert(0x1c, 0x04) + } + result := mload(0x00) + } + } + + /// @dev Returns the admin of the proxy. + /// Assumes that the proxy is a proper EIP7702Proxy, if it exists. + function proxyAdmin(address proxy) internal view returns (address result) { + /// @solidity memory-safe-assembly + assembly { + mstore(0x00, 0xf851a440) // `admin()`. + let t := staticcall(gas(), proxy, 0x1c, 0x04, 0x00, 0x20) + if iszero(and(gt(returndatasize(), 0x1f), t)) { + mstore(0x00, 0x26ec9b6a) // `ProxyQueryFailed()`. + revert(0x1c, 0x04) + } + result := mload(0x00) + } + } + + /// @dev Changes the admin on the proxy. The caller must be the admin. + /// Assumes that the proxy is a proper EIP7702Proxy, if it exists. + function changeProxyAdmin(address proxy, address newAdmin) internal { + /// @solidity memory-safe-assembly + assembly { + mstore(0x00, 0x8f283970) // `changeAdmin(address)`. + mstore(0x20, newAdmin) // The implementation will clean the upper 96 bits. + if iszero(and(eq(mload(0x00), 1), call(gas(), proxy, 0, 0x1c, 0x24, 0x00, 0x20))) { + mstore(0x00, 0xc502e37e) // `ChangeProxyAdminFailed()`. + revert(0x1c, 0x04) + } + } + } + + /// @dev Changes the implementation on the proxy. The caller must be the admin. + /// Assumes that the proxy is a proper EIP7702Proxy, if it exists. + function upgradeProxy(address proxy, address newImplementation) internal { + /// @solidity memory-safe-assembly + assembly { + mstore(0x00, 0x0900f010) // `upgrade(address)`. + mstore(0x20, newImplementation) // The implementation will clean the upper 96 bits. + if iszero(and(eq(mload(0x00), 1), call(gas(), proxy, 0, 0x1c, 0x24, 0x00, 0x20))) { + mstore(0x00, 0xc6edd882) // `UpgradeProxyFailed()`. + revert(0x1c, 0x04) + } + } + } + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* PROXY DELEGATION OPERATIONS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev Upgrades the implementation. + /// The new implementation will NOT be active until the next UserOp or transaction. + /// To "auto-upgrade" to the latest implementation on the proxy, pass in `address(0)` to reset + /// the implementation slot. This causes the proxy to use the latest default implementation, + /// which may be optionally reinitialized via `requestProxyDelegationInitialization()`. + /// This function is intended to be used on the authority of an EIP7702Proxy delegation. + /// The most intended usage pattern is to wrap this in an access-gated admin function. + function upgradeProxyDelegation(address newImplementation) internal { + /// @solidity memory-safe-assembly + assembly { + let s := ERC1967_IMPLEMENTATION_SLOT + // Preserve the upper 96 bits when updating in case they are used for some stuff. + mstore(0x00, sload(s)) + mstore(0x0c, shl(96, newImplementation)) + sstore(s, mload(0x00)) + } + } + + /// @dev Requests the implementation to be initialized to the latest implementation on the proxy. + /// This function is intended to be used on the authority of an EIP7702Proxy delegation. + /// The most intended usage pattern is to place it at the end of an `execute` function. + function requestProxyDelegationInitialization() internal { + /// @solidity memory-safe-assembly + assembly { + if iszero(shl(96, sload(ERC1967_IMPLEMENTATION_SLOT))) { + // Use a dedicated transient storage slot for better Swiss-cheese-model safety. + tstore(EIP7702_PROXY_DELEGATION_INITIALIZATION_REQUEST_SLOT, address()) + } + } + } +} diff --git a/biconomy/nexus/1.2.0/node_modules/solady/src/auth/Ownable.sol b/biconomy/nexus/1.2.0/node_modules/solady/src/auth/Ownable.sol new file mode 100644 index 0000000..a9d3214 --- /dev/null +++ b/biconomy/nexus/1.2.0/node_modules/solady/src/auth/Ownable.sol @@ -0,0 +1,278 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.4; + +/// @notice Simple single owner authorization mixin. +/// @author Solady (https://github.com/vectorized/solady/blob/main/src/auth/Ownable.sol) +/// +/// @dev Note: +/// This implementation does NOT auto-initialize the owner to `msg.sender`. +/// You MUST call the `_initializeOwner` in the constructor / initializer. +/// +/// While the ownable portion follows +/// [EIP-173](https://eips.ethereum.org/EIPS/eip-173) for compatibility, +/// the nomenclature for the 2-step ownership handover may be unique to this codebase. +abstract contract Ownable { + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* CUSTOM ERRORS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev The caller is not authorized to call the function. + error Unauthorized(); + + /// @dev The `newOwner` cannot be the zero address. + error NewOwnerIsZeroAddress(); + + /// @dev The `pendingOwner` does not have a valid handover request. + error NoHandoverRequest(); + + /// @dev Cannot double-initialize. + error AlreadyInitialized(); + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* EVENTS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev The ownership is transferred from `oldOwner` to `newOwner`. + /// This event is intentionally kept the same as OpenZeppelin's Ownable to be + /// compatible with indexers and [EIP-173](https://eips.ethereum.org/EIPS/eip-173), + /// despite it not being as lightweight as a single argument event. + event OwnershipTransferred(address indexed oldOwner, address indexed newOwner); + + /// @dev An ownership handover to `pendingOwner` has been requested. + event OwnershipHandoverRequested(address indexed pendingOwner); + + /// @dev The ownership handover to `pendingOwner` has been canceled. + event OwnershipHandoverCanceled(address indexed pendingOwner); + + /// @dev `keccak256(bytes("OwnershipTransferred(address,address)"))`. + uint256 private constant _OWNERSHIP_TRANSFERRED_EVENT_SIGNATURE = + 0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0; + + /// @dev `keccak256(bytes("OwnershipHandoverRequested(address)"))`. + uint256 private constant _OWNERSHIP_HANDOVER_REQUESTED_EVENT_SIGNATURE = + 0xdbf36a107da19e49527a7176a1babf963b4b0ff8cde35ee35d6cd8f1f9ac7e1d; + + /// @dev `keccak256(bytes("OwnershipHandoverCanceled(address)"))`. + uint256 private constant _OWNERSHIP_HANDOVER_CANCELED_EVENT_SIGNATURE = + 0xfa7b8eab7da67f412cc9575ed43464468f9bfbae89d1675917346ca6d8fe3c92; + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* STORAGE */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev The owner slot is given by: + /// `bytes32(~uint256(uint32(bytes4(keccak256("_OWNER_SLOT_NOT")))))`. + /// It is intentionally chosen to be a high value + /// to avoid collision with lower slots. + /// The choice of manual storage layout is to enable compatibility + /// with both regular and upgradeable contracts. + bytes32 internal constant _OWNER_SLOT = + 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffff74873927; + + /// The ownership handover slot of `newOwner` is given by: + /// ``` + /// mstore(0x00, or(shl(96, user), _HANDOVER_SLOT_SEED)) + /// let handoverSlot := keccak256(0x00, 0x20) + /// ``` + /// It stores the expiry timestamp of the two-step ownership handover. + uint256 private constant _HANDOVER_SLOT_SEED = 0x389a75e1; + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* INTERNAL FUNCTIONS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev Override to return true to make `_initializeOwner` prevent double-initialization. + function _guardInitializeOwner() internal pure virtual returns (bool guard) {} + + /// @dev Initializes the owner directly without authorization guard. + /// This function must be called upon initialization, + /// regardless of whether the contract is upgradeable or not. + /// This is to enable generalization to both regular and upgradeable contracts, + /// and to save gas in case the initial owner is not the caller. + /// For performance reasons, this function will not check if there + /// is an existing owner. + function _initializeOwner(address newOwner) internal virtual { + if (_guardInitializeOwner()) { + /// @solidity memory-safe-assembly + assembly { + let ownerSlot := _OWNER_SLOT + if sload(ownerSlot) { + mstore(0x00, 0x0dc149f0) // `AlreadyInitialized()`. + revert(0x1c, 0x04) + } + // Clean the upper 96 bits. + newOwner := shr(96, shl(96, newOwner)) + // Store the new value. + sstore(ownerSlot, or(newOwner, shl(255, iszero(newOwner)))) + // Emit the {OwnershipTransferred} event. + log3(0, 0, _OWNERSHIP_TRANSFERRED_EVENT_SIGNATURE, 0, newOwner) + } + } else { + /// @solidity memory-safe-assembly + assembly { + // Clean the upper 96 bits. + newOwner := shr(96, shl(96, newOwner)) + // Store the new value. + sstore(_OWNER_SLOT, newOwner) + // Emit the {OwnershipTransferred} event. + log3(0, 0, _OWNERSHIP_TRANSFERRED_EVENT_SIGNATURE, 0, newOwner) + } + } + } + + /// @dev Sets the owner directly without authorization guard. + function _setOwner(address newOwner) internal virtual { + if (_guardInitializeOwner()) { + /// @solidity memory-safe-assembly + assembly { + let ownerSlot := _OWNER_SLOT + // Clean the upper 96 bits. + newOwner := shr(96, shl(96, newOwner)) + // Emit the {OwnershipTransferred} event. + log3(0, 0, _OWNERSHIP_TRANSFERRED_EVENT_SIGNATURE, sload(ownerSlot), newOwner) + // Store the new value. + sstore(ownerSlot, or(newOwner, shl(255, iszero(newOwner)))) + } + } else { + /// @solidity memory-safe-assembly + assembly { + let ownerSlot := _OWNER_SLOT + // Clean the upper 96 bits. + newOwner := shr(96, shl(96, newOwner)) + // Emit the {OwnershipTransferred} event. + log3(0, 0, _OWNERSHIP_TRANSFERRED_EVENT_SIGNATURE, sload(ownerSlot), newOwner) + // Store the new value. + sstore(ownerSlot, newOwner) + } + } + } + + /// @dev Throws if the sender is not the owner. + function _checkOwner() internal view virtual { + /// @solidity memory-safe-assembly + assembly { + // If the caller is not the stored owner, revert. + if iszero(eq(caller(), sload(_OWNER_SLOT))) { + mstore(0x00, 0x82b42900) // `Unauthorized()`. + revert(0x1c, 0x04) + } + } + } + + /// @dev Returns how long a two-step ownership handover is valid for in seconds. + /// Override to return a different value if needed. + /// Made internal to conserve bytecode. Wrap it in a public function if needed. + function _ownershipHandoverValidFor() internal view virtual returns (uint64) { + return 48 * 3600; + } + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* PUBLIC UPDATE FUNCTIONS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev Allows the owner to transfer the ownership to `newOwner`. + function transferOwnership(address newOwner) public payable virtual onlyOwner { + /// @solidity memory-safe-assembly + assembly { + if iszero(shl(96, newOwner)) { + mstore(0x00, 0x7448fbae) // `NewOwnerIsZeroAddress()`. + revert(0x1c, 0x04) + } + } + _setOwner(newOwner); + } + + /// @dev Allows the owner to renounce their ownership. + function renounceOwnership() public payable virtual onlyOwner { + _setOwner(address(0)); + } + + /// @dev Request a two-step ownership handover to the caller. + /// The request will automatically expire in 48 hours (172800 seconds) by default. + function requestOwnershipHandover() public payable virtual { + unchecked { + uint256 expires = block.timestamp + _ownershipHandoverValidFor(); + /// @solidity memory-safe-assembly + assembly { + // Compute and set the handover slot to `expires`. + mstore(0x0c, _HANDOVER_SLOT_SEED) + mstore(0x00, caller()) + sstore(keccak256(0x0c, 0x20), expires) + // Emit the {OwnershipHandoverRequested} event. + log2(0, 0, _OWNERSHIP_HANDOVER_REQUESTED_EVENT_SIGNATURE, caller()) + } + } + } + + /// @dev Cancels the two-step ownership handover to the caller, if any. + function cancelOwnershipHandover() public payable virtual { + /// @solidity memory-safe-assembly + assembly { + // Compute and set the handover slot to 0. + mstore(0x0c, _HANDOVER_SLOT_SEED) + mstore(0x00, caller()) + sstore(keccak256(0x0c, 0x20), 0) + // Emit the {OwnershipHandoverCanceled} event. + log2(0, 0, _OWNERSHIP_HANDOVER_CANCELED_EVENT_SIGNATURE, caller()) + } + } + + /// @dev Allows the owner to complete the two-step ownership handover to `pendingOwner`. + /// Reverts if there is no existing ownership handover requested by `pendingOwner`. + function completeOwnershipHandover(address pendingOwner) public payable virtual onlyOwner { + /// @solidity memory-safe-assembly + assembly { + // Compute and set the handover slot to 0. + mstore(0x0c, _HANDOVER_SLOT_SEED) + mstore(0x00, pendingOwner) + let handoverSlot := keccak256(0x0c, 0x20) + // If the handover does not exist, or has expired. + if gt(timestamp(), sload(handoverSlot)) { + mstore(0x00, 0x6f5e8818) // `NoHandoverRequest()`. + revert(0x1c, 0x04) + } + // Set the handover slot to 0. + sstore(handoverSlot, 0) + } + _setOwner(pendingOwner); + } + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* PUBLIC READ FUNCTIONS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev Returns the owner of the contract. + function owner() public view virtual returns (address result) { + /// @solidity memory-safe-assembly + assembly { + result := sload(_OWNER_SLOT) + } + } + + /// @dev Returns the expiry timestamp for the two-step ownership handover to `pendingOwner`. + function ownershipHandoverExpiresAt(address pendingOwner) + public + view + virtual + returns (uint256 result) + { + /// @solidity memory-safe-assembly + assembly { + // Compute the handover slot. + mstore(0x0c, _HANDOVER_SLOT_SEED) + mstore(0x00, pendingOwner) + // Load the handover slot. + result := sload(keccak256(0x0c, 0x20)) + } + } + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* MODIFIERS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev Marks a function as only callable by the owner. + modifier onlyOwner() virtual { + _checkOwner(); + _; + } +} diff --git a/biconomy/nexus/1.2.0/node_modules/solady/src/utils/CallContextChecker.sol b/biconomy/nexus/1.2.0/node_modules/solady/src/utils/CallContextChecker.sol new file mode 100644 index 0000000..83a7d4d --- /dev/null +++ b/biconomy/nexus/1.2.0/node_modules/solady/src/utils/CallContextChecker.sol @@ -0,0 +1,95 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.4; + +/// @notice Call context checker mixin. +/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/CallContextChecker.sol) +contract CallContextChecker { + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* CUSTOM ERRORS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev The call is from an unauthorized call context. + error UnauthorizedCallContext(); + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* IMMUTABLES */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev For checking if the context is a delegate call. + /// + /// Note: To enable use cases with an immutable default implementation in the bytecode, + /// (see: ERC6551Proxy), we don't require that the proxy address must match the + /// value stored in the implementation slot, which may not be initialized. + uint256 private immutable __self = uint256(uint160(address(this))); + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* CALL CONTEXT CHECKS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + // A proxy call can be either via a `delegatecall` to an implementation, + // or a 7702 call on an authority that points to a delegation. + + /// @dev Returns whether the current call context is on a EIP7702 authority + /// (i.e. externally owned account). + function _onEIP7702Authority() internal view virtual returns (bool result) { + /// @solidity memory-safe-assembly + assembly { + extcodecopy(address(), 0x00, 0x00, 0x20) + // Note: Checking that it starts with hex"ef01" is the most general and futureproof. + // 7702 bytecode is `abi.encodePacked(hex"ef01", uint8(version), address(delegation))`. + result := eq(0xef01, shr(240, mload(0x00))) + } + } + + /// @dev Returns whether the current call context is on the implementation itself. + function _onImplementation() internal view virtual returns (bool) { + return __self == uint160(address(this)); + } + + /// @dev Requires that the current call context is performed via a EIP7702 authority. + function _checkOnlyEIP7702Authority() internal view virtual { + if (!_onEIP7702Authority()) _revertUnauthorizedCallContext(); + } + + /// @dev Requires that the current call context is performed via a proxy. + function _checkOnlyProxy() internal view virtual { + if (_onImplementation()) _revertUnauthorizedCallContext(); + } + + /// @dev Requires that the current call context is NOT performed via a proxy. + /// This is the opposite of `checkOnlyProxy`. + function _checkNotDelegated() internal view virtual { + if (!_onImplementation()) _revertUnauthorizedCallContext(); + } + + /// @dev Requires that the current call context is performed via a EIP7702 authority. + modifier onlyEIP7702Authority() virtual { + _checkOnlyEIP7702Authority(); + _; + } + + /// @dev Requires that the current call context is performed via a proxy. + modifier onlyProxy() virtual { + _checkOnlyProxy(); + _; + } + + /// @dev Requires that the current call context is NOT performed via a proxy. + /// This is the opposite of `onlyProxy`. + modifier notDelegated() virtual { + _checkNotDelegated(); + _; + } + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* PRIVATE HELPERS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + function _revertUnauthorizedCallContext() private pure { + /// @solidity memory-safe-assembly + assembly { + mstore(0x00, 0x9f03a026) // `UnauthorizedCallContext()`. + revert(0x1c, 0x04) + } + } +} diff --git a/biconomy/nexus/1.2.0/node_modules/solady/src/utils/ECDSA.sol b/biconomy/nexus/1.2.0/node_modules/solady/src/utils/ECDSA.sol new file mode 100644 index 0000000..69d9ec3 --- /dev/null +++ b/biconomy/nexus/1.2.0/node_modules/solady/src/utils/ECDSA.sol @@ -0,0 +1,442 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.4; + +/// @notice Gas optimized ECDSA wrapper. +/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/ECDSA.sol) +/// @author Modified from Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/ECDSA.sol) +/// @author Modified from OpenZeppelin (https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/cryptography/ECDSA.sol) +/// +/// @dev Note: +/// - The recovery functions use the ecrecover precompile (0x1). +/// - As of Solady version 0.0.68, the `recover` variants will revert upon recovery failure. +/// This is for more safety by default. +/// Use the `tryRecover` variants if you need to get the zero address back +/// upon recovery failure instead. +/// - As of Solady version 0.0.134, all `bytes signature` variants accept both +/// regular 65-byte `(r, s, v)` and EIP-2098 `(r, vs)` short form signatures. +/// See: https://eips.ethereum.org/EIPS/eip-2098 +/// This is for calldata efficiency on smart accounts prevalent on L2s. +/// +/// WARNING! Do NOT directly use signatures as unique identifiers: +/// - The recovery operations do NOT check if a signature is non-malleable. +/// - Use a nonce in the digest to prevent replay attacks on the same contract. +/// - Use EIP-712 for the digest to prevent replay attacks across different chains and contracts. +/// EIP-712 also enables readable signing of typed data for better user safety. +/// - If you need a unique hash from a signature, please use the `canonicalHash` functions. +library ECDSA { + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* CONSTANTS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev The order of the secp256k1 elliptic curve. + uint256 internal constant N = 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141; + + /// @dev `N/2 + 1`. Used for checking the malleability of the signature. + uint256 private constant _HALF_N_PLUS_1 = + 0x7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a1; + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* CUSTOM ERRORS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev The signature is invalid. + error InvalidSignature(); + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* RECOVERY OPERATIONS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev Recovers the signer's address from a message digest `hash`, and the `signature`. + function recover(bytes32 hash, bytes memory signature) internal view returns (address result) { + /// @solidity memory-safe-assembly + assembly { + for { let m := mload(0x40) } 1 { + mstore(0x00, 0x8baa579f) // `InvalidSignature()`. + revert(0x1c, 0x04) + } { + switch mload(signature) + case 64 { + let vs := mload(add(signature, 0x40)) + mstore(0x20, add(shr(255, vs), 27)) // `v`. + mstore(0x60, shr(1, shl(1, vs))) // `s`. + } + case 65 { + mstore(0x20, byte(0, mload(add(signature, 0x60)))) // `v`. + mstore(0x60, mload(add(signature, 0x40))) // `s`. + } + default { continue } + mstore(0x00, hash) + mstore(0x40, mload(add(signature, 0x20))) // `r`. + result := mload(staticcall(gas(), 1, 0x00, 0x80, 0x01, 0x20)) + mstore(0x60, 0) // Restore the zero slot. + mstore(0x40, m) // Restore the free memory pointer. + // `returndatasize()` will be `0x20` upon success, and `0x00` otherwise. + if returndatasize() { break } + } + } + } + + /// @dev Recovers the signer's address from a message digest `hash`, and the `signature`. + function recoverCalldata(bytes32 hash, bytes calldata signature) + internal + view + returns (address result) + { + /// @solidity memory-safe-assembly + assembly { + for { let m := mload(0x40) } 1 { + mstore(0x00, 0x8baa579f) // `InvalidSignature()`. + revert(0x1c, 0x04) + } { + switch signature.length + case 64 { + let vs := calldataload(add(signature.offset, 0x20)) + mstore(0x20, add(shr(255, vs), 27)) // `v`. + mstore(0x40, calldataload(signature.offset)) // `r`. + mstore(0x60, shr(1, shl(1, vs))) // `s`. + } + case 65 { + mstore(0x20, byte(0, calldataload(add(signature.offset, 0x40)))) // `v`. + calldatacopy(0x40, signature.offset, 0x40) // Copy `r` and `s`. + } + default { continue } + mstore(0x00, hash) + result := mload(staticcall(gas(), 1, 0x00, 0x80, 0x01, 0x20)) + mstore(0x60, 0) // Restore the zero slot. + mstore(0x40, m) // Restore the free memory pointer. + // `returndatasize()` will be `0x20` upon success, and `0x00` otherwise. + if returndatasize() { break } + } + } + } + + /// @dev Recovers the signer's address from a message digest `hash`, + /// and the EIP-2098 short form signature defined by `r` and `vs`. + function recover(bytes32 hash, bytes32 r, bytes32 vs) internal view returns (address result) { + /// @solidity memory-safe-assembly + assembly { + let m := mload(0x40) // Cache the free memory pointer. + mstore(0x00, hash) + mstore(0x20, add(shr(255, vs), 27)) // `v`. + mstore(0x40, r) + mstore(0x60, shr(1, shl(1, vs))) // `s`. + result := mload(staticcall(gas(), 1, 0x00, 0x80, 0x01, 0x20)) + // `returndatasize()` will be `0x20` upon success, and `0x00` otherwise. + if iszero(returndatasize()) { + mstore(0x00, 0x8baa579f) // `InvalidSignature()`. + revert(0x1c, 0x04) + } + mstore(0x60, 0) // Restore the zero slot. + mstore(0x40, m) // Restore the free memory pointer. + } + } + + /// @dev Recovers the signer's address from a message digest `hash`, + /// and the signature defined by `v`, `r`, `s`. + function recover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) + internal + view + returns (address result) + { + /// @solidity memory-safe-assembly + assembly { + let m := mload(0x40) // Cache the free memory pointer. + mstore(0x00, hash) + mstore(0x20, and(v, 0xff)) + mstore(0x40, r) + mstore(0x60, s) + result := mload(staticcall(gas(), 1, 0x00, 0x80, 0x01, 0x20)) + // `returndatasize()` will be `0x20` upon success, and `0x00` otherwise. + if iszero(returndatasize()) { + mstore(0x00, 0x8baa579f) // `InvalidSignature()`. + revert(0x1c, 0x04) + } + mstore(0x60, 0) // Restore the zero slot. + mstore(0x40, m) // Restore the free memory pointer. + } + } + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* TRY-RECOVER OPERATIONS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + // WARNING! + // These functions will NOT revert upon recovery failure. + // Instead, they will return the zero address upon recovery failure. + // It is critical that the returned address is NEVER compared against + // a zero address (e.g. an uninitialized address variable). + + /// @dev Recovers the signer's address from a message digest `hash`, and the `signature`. + function tryRecover(bytes32 hash, bytes memory signature) + internal + view + returns (address result) + { + /// @solidity memory-safe-assembly + assembly { + for { let m := mload(0x40) } 1 {} { + switch mload(signature) + case 64 { + let vs := mload(add(signature, 0x40)) + mstore(0x20, add(shr(255, vs), 27)) // `v`. + mstore(0x60, shr(1, shl(1, vs))) // `s`. + } + case 65 { + mstore(0x20, byte(0, mload(add(signature, 0x60)))) // `v`. + mstore(0x60, mload(add(signature, 0x40))) // `s`. + } + default { break } + mstore(0x00, hash) + mstore(0x40, mload(add(signature, 0x20))) // `r`. + pop(staticcall(gas(), 1, 0x00, 0x80, 0x40, 0x20)) + mstore(0x60, 0) // Restore the zero slot. + // `returndatasize()` will be `0x20` upon success, and `0x00` otherwise. + result := mload(xor(0x60, returndatasize())) + mstore(0x40, m) // Restore the free memory pointer. + break + } + } + } + + /// @dev Recovers the signer's address from a message digest `hash`, and the `signature`. + function tryRecoverCalldata(bytes32 hash, bytes calldata signature) + internal + view + returns (address result) + { + /// @solidity memory-safe-assembly + assembly { + for { let m := mload(0x40) } 1 {} { + switch signature.length + case 64 { + let vs := calldataload(add(signature.offset, 0x20)) + mstore(0x20, add(shr(255, vs), 27)) // `v`. + mstore(0x40, calldataload(signature.offset)) // `r`. + mstore(0x60, shr(1, shl(1, vs))) // `s`. + } + case 65 { + mstore(0x20, byte(0, calldataload(add(signature.offset, 0x40)))) // `v`. + calldatacopy(0x40, signature.offset, 0x40) // Copy `r` and `s`. + } + default { break } + mstore(0x00, hash) + pop(staticcall(gas(), 1, 0x00, 0x80, 0x40, 0x20)) + mstore(0x60, 0) // Restore the zero slot. + // `returndatasize()` will be `0x20` upon success, and `0x00` otherwise. + result := mload(xor(0x60, returndatasize())) + mstore(0x40, m) // Restore the free memory pointer. + break + } + } + } + + /// @dev Recovers the signer's address from a message digest `hash`, + /// and the EIP-2098 short form signature defined by `r` and `vs`. + function tryRecover(bytes32 hash, bytes32 r, bytes32 vs) + internal + view + returns (address result) + { + /// @solidity memory-safe-assembly + assembly { + let m := mload(0x40) // Cache the free memory pointer. + mstore(0x00, hash) + mstore(0x20, add(shr(255, vs), 27)) // `v`. + mstore(0x40, r) + mstore(0x60, shr(1, shl(1, vs))) // `s`. + pop(staticcall(gas(), 1, 0x00, 0x80, 0x40, 0x20)) + mstore(0x60, 0) // Restore the zero slot. + // `returndatasize()` will be `0x20` upon success, and `0x00` otherwise. + result := mload(xor(0x60, returndatasize())) + mstore(0x40, m) // Restore the free memory pointer. + } + } + + /// @dev Recovers the signer's address from a message digest `hash`, + /// and the signature defined by `v`, `r`, `s`. + function tryRecover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) + internal + view + returns (address result) + { + /// @solidity memory-safe-assembly + assembly { + let m := mload(0x40) // Cache the free memory pointer. + mstore(0x00, hash) + mstore(0x20, and(v, 0xff)) + mstore(0x40, r) + mstore(0x60, s) + pop(staticcall(gas(), 1, 0x00, 0x80, 0x40, 0x20)) + mstore(0x60, 0) // Restore the zero slot. + // `returndatasize()` will be `0x20` upon success, and `0x00` otherwise. + result := mload(xor(0x60, returndatasize())) + mstore(0x40, m) // Restore the free memory pointer. + } + } + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* HASHING OPERATIONS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev Returns an Ethereum Signed Message, created from a `hash`. + /// This produces a hash corresponding to the one signed with the + /// [`eth_sign`](https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_sign) + /// JSON-RPC method as part of EIP-191. + function toEthSignedMessageHash(bytes32 hash) internal pure returns (bytes32 result) { + /// @solidity memory-safe-assembly + assembly { + mstore(0x20, hash) // Store into scratch space for keccak256. + mstore(0x00, "\x00\x00\x00\x00\x19Ethereum Signed Message:\n32") // 28 bytes. + result := keccak256(0x04, 0x3c) // `32 * 2 - (32 - 28) = 60 = 0x3c`. + } + } + + /// @dev Returns an Ethereum Signed Message, created from `s`. + /// This produces a hash corresponding to the one signed with the + /// [`eth_sign`](https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_sign) + /// JSON-RPC method as part of EIP-191. + /// Note: Supports lengths of `s` up to 999999 bytes. + function toEthSignedMessageHash(bytes memory s) internal pure returns (bytes32 result) { + /// @solidity memory-safe-assembly + assembly { + let sLength := mload(s) + let o := 0x20 + mstore(o, "\x19Ethereum Signed Message:\n") // 26 bytes, zero-right-padded. + mstore(0x00, 0x00) + // Convert the `s.length` to ASCII decimal representation: `base10(s.length)`. + for { let temp := sLength } 1 {} { + o := sub(o, 1) + mstore8(o, add(48, mod(temp, 10))) + temp := div(temp, 10) + if iszero(temp) { break } + } + let n := sub(0x3a, o) // Header length: `26 + 32 - o`. + // Throw an out-of-offset error (consumes all gas) if the header exceeds 32 bytes. + returndatacopy(returndatasize(), returndatasize(), gt(n, 0x20)) + mstore(s, or(mload(0x00), mload(n))) // Temporarily store the header. + result := keccak256(add(s, sub(0x20, n)), add(n, sLength)) + mstore(s, sLength) // Restore the length. + } + } + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* CANONICAL HASH FUNCTIONS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + // The following functions returns the hash of the signature in it's canonicalized format, + // which is the 65-byte `abi.encodePacked(r, s, uint8(v))`, where `v` is either 27 or 28. + // If `s` is greater than `N / 2` then it will be converted to `N - s` + // and the `v` value will be flipped. + // If the signature has an invalid length, or if `v` is invalid, + // a uniquely corrupt hash will be returned. + // These functions are useful for "poor-mans-VRF". + + /// @dev Returns the canonical hash of `signature`. + function canonicalHash(bytes memory signature) internal pure returns (bytes32 result) { + /// @solidity memory-safe-assembly + assembly { + let l := mload(signature) + for {} 1 {} { + mstore(0x00, mload(add(signature, 0x20))) // `r`. + let s := mload(add(signature, 0x40)) + let v := mload(add(signature, 0x41)) + if eq(l, 64) { + v := add(shr(255, s), 27) + s := shr(1, shl(1, s)) + } + if iszero(lt(s, _HALF_N_PLUS_1)) { + v := xor(v, 7) + s := sub(N, s) + } + mstore(0x21, v) + mstore(0x20, s) + result := keccak256(0x00, 0x41) + mstore(0x21, 0) // Restore the overwritten part of the free memory pointer. + break + } + + // If the length is neither 64 nor 65, return a uniquely corrupted hash. + if iszero(lt(sub(l, 64), 2)) { + // `bytes4(keccak256("InvalidSignatureLength"))`. + result := xor(keccak256(add(signature, 0x20), l), 0xd62f1ab2) + } + } + } + + /// @dev Returns the canonical hash of `signature`. + function canonicalHashCalldata(bytes calldata signature) + internal + pure + returns (bytes32 result) + { + /// @solidity memory-safe-assembly + assembly { + for {} 1 {} { + mstore(0x00, calldataload(signature.offset)) // `r`. + let s := calldataload(add(signature.offset, 0x20)) + let v := calldataload(add(signature.offset, 0x21)) + if eq(signature.length, 64) { + v := add(shr(255, s), 27) + s := shr(1, shl(1, s)) + } + if iszero(lt(s, _HALF_N_PLUS_1)) { + v := xor(v, 7) + s := sub(N, s) + } + mstore(0x21, v) + mstore(0x20, s) + result := keccak256(0x00, 0x41) + mstore(0x21, 0) // Restore the overwritten part of the free memory pointer. + break + } + // If the length is neither 64 nor 65, return a uniquely corrupted hash. + if iszero(lt(sub(signature.length, 64), 2)) { + calldatacopy(mload(0x40), signature.offset, signature.length) + // `bytes4(keccak256("InvalidSignatureLength"))`. + result := xor(keccak256(mload(0x40), signature.length), 0xd62f1ab2) + } + } + } + + /// @dev Returns the canonical hash of `signature`. + function canonicalHash(bytes32 r, bytes32 vs) internal pure returns (bytes32 result) { + /// @solidity memory-safe-assembly + assembly { + mstore(0x00, r) // `r`. + let v := add(shr(255, vs), 27) + let s := shr(1, shl(1, vs)) + mstore(0x21, v) + mstore(0x20, s) + result := keccak256(0x00, 0x41) + mstore(0x21, 0) // Restore the overwritten part of the free memory pointer. + } + } + + /// @dev Returns the canonical hash of `signature`. + function canonicalHash(uint8 v, bytes32 r, bytes32 s) internal pure returns (bytes32 result) { + /// @solidity memory-safe-assembly + assembly { + mstore(0x00, r) // `r`. + if iszero(lt(s, _HALF_N_PLUS_1)) { + v := xor(v, 7) + s := sub(N, s) + } + mstore(0x21, v) + mstore(0x20, s) + result := keccak256(0x00, 0x41) + mstore(0x21, 0) // Restore the overwritten part of the free memory pointer. + } + } + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* EMPTY CALLDATA HELPERS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev Returns an empty calldata bytes. + function emptySignature() internal pure returns (bytes calldata signature) { + /// @solidity memory-safe-assembly + assembly { + signature.length := 0 + } + } +} diff --git a/biconomy/nexus/1.2.0/node_modules/solady/src/utils/EIP712.sol b/biconomy/nexus/1.2.0/node_modules/solady/src/utils/EIP712.sol new file mode 100644 index 0000000..54a28a3 --- /dev/null +++ b/biconomy/nexus/1.2.0/node_modules/solady/src/utils/EIP712.sol @@ -0,0 +1,241 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.4; + +/// @notice Contract for EIP-712 typed structured data hashing and signing. +/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/EIP712.sol) +/// @author Modified from Solbase (https://github.com/Sol-DAO/solbase/blob/main/src/utils/EIP712.sol) +/// @author Modified from OpenZeppelin (https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/cryptography/EIP712.sol) +/// +/// @dev Note, this implementation: +/// - Uses `address(this)` for the `verifyingContract` field. +/// - Does NOT use the optional EIP-712 salt. +/// - Does NOT use any EIP-712 extensions. +/// This is for simplicity and to save gas. +/// If you need to customize, please fork / modify accordingly. +abstract contract EIP712 { + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* CONSTANTS AND IMMUTABLES */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev `keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)")`. + bytes32 internal constant _DOMAIN_TYPEHASH = + 0x8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f; + + /// @dev `keccak256("EIP712Domain(string name,string version,address verifyingContract)")`. + /// This is only used in `_hashTypedDataSansChainId`. + bytes32 internal constant _DOMAIN_TYPEHASH_SANS_CHAIN_ID = + 0x91ab3d17e3a50a9d89e63fd30b92be7f5336b03b287bb946787a83a9d62a2766; + + uint256 private immutable _cachedThis; + uint256 private immutable _cachedChainId; + bytes32 private immutable _cachedNameHash; + bytes32 private immutable _cachedVersionHash; + bytes32 private immutable _cachedDomainSeparator; + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* CONSTRUCTOR */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev Cache the hashes for cheaper runtime gas costs. + /// In the case of upgradeable contracts (i.e. proxies), + /// or if the chain id changes due to a hard fork, + /// the domain separator will be seamlessly calculated on-the-fly. + constructor() { + _cachedThis = uint256(uint160(address(this))); + _cachedChainId = block.chainid; + + string memory name; + string memory version; + if (!_domainNameAndVersionMayChange()) (name, version) = _domainNameAndVersion(); + bytes32 nameHash = _domainNameAndVersionMayChange() ? bytes32(0) : keccak256(bytes(name)); + bytes32 versionHash = + _domainNameAndVersionMayChange() ? bytes32(0) : keccak256(bytes(version)); + _cachedNameHash = nameHash; + _cachedVersionHash = versionHash; + + bytes32 separator; + if (!_domainNameAndVersionMayChange()) { + /// @solidity memory-safe-assembly + assembly { + let m := mload(0x40) // Load the free memory pointer. + mstore(m, _DOMAIN_TYPEHASH) + mstore(add(m, 0x20), nameHash) + mstore(add(m, 0x40), versionHash) + mstore(add(m, 0x60), chainid()) + mstore(add(m, 0x80), address()) + separator := keccak256(m, 0xa0) + } + } + _cachedDomainSeparator = separator; + } + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* FUNCTIONS TO OVERRIDE */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev Please override this function to return the domain name and version. + /// ``` + /// function _domainNameAndVersion() + /// internal + /// pure + /// virtual + /// returns (string memory name, string memory version) + /// { + /// name = "Solady"; + /// version = "1"; + /// } + /// ``` + /// + /// Note: If the returned result may change after the contract has been deployed, + /// you must override `_domainNameAndVersionMayChange()` to return true. + function _domainNameAndVersion() + internal + view + virtual + returns (string memory name, string memory version); + + /// @dev Returns if `_domainNameAndVersion()` may change + /// after the contract has been deployed (i.e. after the constructor). + /// Default: false. + function _domainNameAndVersionMayChange() internal pure virtual returns (bool result) {} + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* HASHING OPERATIONS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev Returns the EIP-712 domain separator. + function _domainSeparator() internal view virtual returns (bytes32 separator) { + if (_domainNameAndVersionMayChange()) { + separator = _buildDomainSeparator(); + } else { + separator = _cachedDomainSeparator; + if (_cachedDomainSeparatorInvalidated()) separator = _buildDomainSeparator(); + } + } + + /// @dev Returns the hash of the fully encoded EIP-712 message for this domain, + /// given `structHash`, as defined in + /// https://eips.ethereum.org/EIPS/eip-712#definition-of-hashstruct. + /// + /// The hash can be used together with {ECDSA-recover} to obtain the signer of a message: + /// ``` + /// bytes32 digest = _hashTypedData(keccak256(abi.encode( + /// keccak256("Mail(address to,string contents)"), + /// mailTo, + /// keccak256(bytes(mailContents)) + /// ))); + /// address signer = ECDSA.recover(digest, signature); + /// ``` + function _hashTypedData(bytes32 structHash) internal view virtual returns (bytes32 digest) { + // We will use `digest` to store the domain separator to save a bit of gas. + if (_domainNameAndVersionMayChange()) { + digest = _buildDomainSeparator(); + } else { + digest = _cachedDomainSeparator; + if (_cachedDomainSeparatorInvalidated()) digest = _buildDomainSeparator(); + } + /// @solidity memory-safe-assembly + assembly { + // Compute the digest. + mstore(0x00, 0x1901000000000000) // Store "\x19\x01". + mstore(0x1a, digest) // Store the domain separator. + mstore(0x3a, structHash) // Store the struct hash. + digest := keccak256(0x18, 0x42) + // Restore the part of the free memory slot that was overwritten. + mstore(0x3a, 0) + } + } + + /// @dev Variant of `_hashTypedData` that excludes the chain ID. + /// We expect that most contracts will use `_hashTypedData` as the main hash, + /// and `_hashTypedDataSansChainId` only occasionally for cross-chain workflows. + /// Thus this is optimized for smaller bytecode size over runtime gas. + function _hashTypedDataSansChainId(bytes32 structHash) + internal + view + virtual + returns (bytes32 digest) + { + (string memory name, string memory version) = _domainNameAndVersion(); + /// @solidity memory-safe-assembly + assembly { + let m := mload(0x40) // Load the free memory pointer. + mstore(0x00, _DOMAIN_TYPEHASH_SANS_CHAIN_ID) + mstore(0x20, keccak256(add(name, 0x20), mload(name))) + mstore(0x40, keccak256(add(version, 0x20), mload(version))) + mstore(0x60, address()) + // Compute the digest. + mstore(0x20, keccak256(0x00, 0x80)) // Store the domain separator. + mstore(0x00, 0x1901) // Store "\x19\x01". + mstore(0x40, structHash) // Store the struct hash. + digest := keccak256(0x1e, 0x42) + mstore(0x40, m) // Restore the free memory pointer. + mstore(0x60, 0) // Restore the zero pointer. + } + } + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* EIP-5267 OPERATIONS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev See: https://eips.ethereum.org/EIPS/eip-5267 + function eip712Domain() + public + view + virtual + returns ( + bytes1 fields, + string memory name, + string memory version, + uint256 chainId, + address verifyingContract, + bytes32 salt, + uint256[] memory extensions + ) + { + fields = hex"0f"; // `0b01111`. + (name, version) = _domainNameAndVersion(); + chainId = block.chainid; + verifyingContract = address(this); + salt = salt; // `bytes32(0)`. + extensions = extensions; // `new uint256[](0)`. + } + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* PRIVATE HELPERS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev Returns the EIP-712 domain separator. + function _buildDomainSeparator() private view returns (bytes32 separator) { + // We will use `separator` to store the name hash to save a bit of gas. + bytes32 versionHash; + if (_domainNameAndVersionMayChange()) { + (string memory name, string memory version) = _domainNameAndVersion(); + separator = keccak256(bytes(name)); + versionHash = keccak256(bytes(version)); + } else { + separator = _cachedNameHash; + versionHash = _cachedVersionHash; + } + /// @solidity memory-safe-assembly + assembly { + let m := mload(0x40) // Load the free memory pointer. + mstore(m, _DOMAIN_TYPEHASH) + mstore(add(m, 0x20), separator) // Name hash. + mstore(add(m, 0x40), versionHash) + mstore(add(m, 0x60), chainid()) + mstore(add(m, 0x80), address()) + separator := keccak256(m, 0xa0) + } + } + + /// @dev Returns if the cached domain separator has been invalidated. + function _cachedDomainSeparatorInvalidated() private view returns (bool result) { + uint256 cachedChainId = _cachedChainId; + uint256 cachedThis = _cachedThis; + /// @solidity memory-safe-assembly + assembly { + result := iszero(and(eq(chainid(), cachedChainId), eq(address(), cachedThis))) + } + } +} diff --git a/biconomy/nexus/1.2.0/node_modules/solady/src/utils/EfficientHashLib.sol b/biconomy/nexus/1.2.0/node_modules/solady/src/utils/EfficientHashLib.sol new file mode 100644 index 0000000..77e7dd4 --- /dev/null +++ b/biconomy/nexus/1.2.0/node_modules/solady/src/utils/EfficientHashLib.sol @@ -0,0 +1,934 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.4; + +/// @notice Library for efficiently performing keccak256 hashes. +/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/EfficientHashLib.sol) +/// @dev To avoid stack-too-deep, you can use: +/// ``` +/// bytes32[] memory buffer = EfficientHashLib.malloc(10); +/// EfficientHashLib.set(buffer, 0, value0); +/// .. +/// EfficientHashLib.set(buffer, 9, value9); +/// bytes32 finalHash = EfficientHashLib.hash(buffer); +/// ``` +library EfficientHashLib { + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* MALLOC-LESS HASHING OPERATIONS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev Returns `keccak256(abi.encode(v0))`. + function hash(bytes32 v0) internal pure returns (bytes32 result) { + /// @solidity memory-safe-assembly + assembly { + mstore(0x00, v0) + result := keccak256(0x00, 0x20) + } + } + + /// @dev Returns `keccak256(abi.encode(v0))`. + function hash(uint256 v0) internal pure returns (bytes32 result) { + /// @solidity memory-safe-assembly + assembly { + mstore(0x00, v0) + result := keccak256(0x00, 0x20) + } + } + + /// @dev Returns `keccak256(abi.encode(v0, v1))`. + function hash(bytes32 v0, bytes32 v1) internal pure returns (bytes32 result) { + /// @solidity memory-safe-assembly + assembly { + mstore(0x00, v0) + mstore(0x20, v1) + result := keccak256(0x00, 0x40) + } + } + + /// @dev Returns `keccak256(abi.encode(v0, v1))`. + function hash(uint256 v0, uint256 v1) internal pure returns (bytes32 result) { + /// @solidity memory-safe-assembly + assembly { + mstore(0x00, v0) + mstore(0x20, v1) + result := keccak256(0x00, 0x40) + } + } + + /// @dev Returns `keccak256(abi.encode(v0, v1, v2))`. + function hash(bytes32 v0, bytes32 v1, bytes32 v2) internal pure returns (bytes32 result) { + /// @solidity memory-safe-assembly + assembly { + let m := mload(0x40) + mstore(m, v0) + mstore(add(m, 0x20), v1) + mstore(add(m, 0x40), v2) + result := keccak256(m, 0x60) + } + } + + /// @dev Returns `keccak256(abi.encode(v0, v1, v2))`. + function hash(uint256 v0, uint256 v1, uint256 v2) internal pure returns (bytes32 result) { + /// @solidity memory-safe-assembly + assembly { + let m := mload(0x40) + mstore(m, v0) + mstore(add(m, 0x20), v1) + mstore(add(m, 0x40), v2) + result := keccak256(m, 0x60) + } + } + + /// @dev Returns `keccak256(abi.encode(v0, v1, v2, v3))`. + function hash(bytes32 v0, bytes32 v1, bytes32 v2, bytes32 v3) + internal + pure + returns (bytes32 result) + { + /// @solidity memory-safe-assembly + assembly { + let m := mload(0x40) + mstore(m, v0) + mstore(add(m, 0x20), v1) + mstore(add(m, 0x40), v2) + mstore(add(m, 0x60), v3) + result := keccak256(m, 0x80) + } + } + + /// @dev Returns `keccak256(abi.encode(v0, v1, v2, v3))`. + function hash(uint256 v0, uint256 v1, uint256 v2, uint256 v3) + internal + pure + returns (bytes32 result) + { + /// @solidity memory-safe-assembly + assembly { + let m := mload(0x40) + mstore(m, v0) + mstore(add(m, 0x20), v1) + mstore(add(m, 0x40), v2) + mstore(add(m, 0x60), v3) + result := keccak256(m, 0x80) + } + } + + /// @dev Returns `keccak256(abi.encode(v0, .., v4))`. + function hash(bytes32 v0, bytes32 v1, bytes32 v2, bytes32 v3, bytes32 v4) + internal + pure + returns (bytes32 result) + { + /// @solidity memory-safe-assembly + assembly { + let m := mload(0x40) + mstore(m, v0) + mstore(add(m, 0x20), v1) + mstore(add(m, 0x40), v2) + mstore(add(m, 0x60), v3) + mstore(add(m, 0x80), v4) + result := keccak256(m, 0xa0) + } + } + + /// @dev Returns `keccak256(abi.encode(v0, .., v4))`. + function hash(uint256 v0, uint256 v1, uint256 v2, uint256 v3, uint256 v4) + internal + pure + returns (bytes32 result) + { + /// @solidity memory-safe-assembly + assembly { + let m := mload(0x40) + mstore(m, v0) + mstore(add(m, 0x20), v1) + mstore(add(m, 0x40), v2) + mstore(add(m, 0x60), v3) + mstore(add(m, 0x80), v4) + result := keccak256(m, 0xa0) + } + } + + /// @dev Returns `keccak256(abi.encode(v0, .., v5))`. + function hash(bytes32 v0, bytes32 v1, bytes32 v2, bytes32 v3, bytes32 v4, bytes32 v5) + internal + pure + returns (bytes32 result) + { + /// @solidity memory-safe-assembly + assembly { + let m := mload(0x40) + mstore(m, v0) + mstore(add(m, 0x20), v1) + mstore(add(m, 0x40), v2) + mstore(add(m, 0x60), v3) + mstore(add(m, 0x80), v4) + mstore(add(m, 0xa0), v5) + result := keccak256(m, 0xc0) + } + } + + /// @dev Returns `keccak256(abi.encode(v0, .., v5))`. + function hash(uint256 v0, uint256 v1, uint256 v2, uint256 v3, uint256 v4, uint256 v5) + internal + pure + returns (bytes32 result) + { + /// @solidity memory-safe-assembly + assembly { + let m := mload(0x40) + mstore(m, v0) + mstore(add(m, 0x20), v1) + mstore(add(m, 0x40), v2) + mstore(add(m, 0x60), v3) + mstore(add(m, 0x80), v4) + mstore(add(m, 0xa0), v5) + result := keccak256(m, 0xc0) + } + } + + /// @dev Returns `keccak256(abi.encode(v0, .., v6))`. + function hash( + bytes32 v0, + bytes32 v1, + bytes32 v2, + bytes32 v3, + bytes32 v4, + bytes32 v5, + bytes32 v6 + ) internal pure returns (bytes32 result) { + /// @solidity memory-safe-assembly + assembly { + let m := mload(0x40) + mstore(m, v0) + mstore(add(m, 0x20), v1) + mstore(add(m, 0x40), v2) + mstore(add(m, 0x60), v3) + mstore(add(m, 0x80), v4) + mstore(add(m, 0xa0), v5) + mstore(add(m, 0xc0), v6) + result := keccak256(m, 0xe0) + } + } + + /// @dev Returns `keccak256(abi.encode(v0, .., v6))`. + function hash( + uint256 v0, + uint256 v1, + uint256 v2, + uint256 v3, + uint256 v4, + uint256 v5, + uint256 v6 + ) internal pure returns (bytes32 result) { + /// @solidity memory-safe-assembly + assembly { + let m := mload(0x40) + mstore(m, v0) + mstore(add(m, 0x20), v1) + mstore(add(m, 0x40), v2) + mstore(add(m, 0x60), v3) + mstore(add(m, 0x80), v4) + mstore(add(m, 0xa0), v5) + mstore(add(m, 0xc0), v6) + result := keccak256(m, 0xe0) + } + } + + /// @dev Returns `keccak256(abi.encode(v0, .., v7))`. + function hash( + bytes32 v0, + bytes32 v1, + bytes32 v2, + bytes32 v3, + bytes32 v4, + bytes32 v5, + bytes32 v6, + bytes32 v7 + ) internal pure returns (bytes32 result) { + /// @solidity memory-safe-assembly + assembly { + let m := mload(0x40) + mstore(m, v0) + mstore(add(m, 0x20), v1) + mstore(add(m, 0x40), v2) + mstore(add(m, 0x60), v3) + mstore(add(m, 0x80), v4) + mstore(add(m, 0xa0), v5) + mstore(add(m, 0xc0), v6) + mstore(add(m, 0xe0), v7) + result := keccak256(m, 0x100) + } + } + + /// @dev Returns `keccak256(abi.encode(v0, .., v7))`. + function hash( + uint256 v0, + uint256 v1, + uint256 v2, + uint256 v3, + uint256 v4, + uint256 v5, + uint256 v6, + uint256 v7 + ) internal pure returns (bytes32 result) { + /// @solidity memory-safe-assembly + assembly { + let m := mload(0x40) + mstore(m, v0) + mstore(add(m, 0x20), v1) + mstore(add(m, 0x40), v2) + mstore(add(m, 0x60), v3) + mstore(add(m, 0x80), v4) + mstore(add(m, 0xa0), v5) + mstore(add(m, 0xc0), v6) + mstore(add(m, 0xe0), v7) + result := keccak256(m, 0x100) + } + } + + /// @dev Returns `keccak256(abi.encode(v0, .., v8))`. + function hash( + bytes32 v0, + bytes32 v1, + bytes32 v2, + bytes32 v3, + bytes32 v4, + bytes32 v5, + bytes32 v6, + bytes32 v7, + bytes32 v8 + ) internal pure returns (bytes32 result) { + /// @solidity memory-safe-assembly + assembly { + let m := mload(0x40) + mstore(m, v0) + mstore(add(m, 0x20), v1) + mstore(add(m, 0x40), v2) + mstore(add(m, 0x60), v3) + mstore(add(m, 0x80), v4) + mstore(add(m, 0xa0), v5) + mstore(add(m, 0xc0), v6) + mstore(add(m, 0xe0), v7) + mstore(add(m, 0x100), v8) + result := keccak256(m, 0x120) + } + } + + /// @dev Returns `keccak256(abi.encode(v0, .., v8))`. + function hash( + uint256 v0, + uint256 v1, + uint256 v2, + uint256 v3, + uint256 v4, + uint256 v5, + uint256 v6, + uint256 v7, + uint256 v8 + ) internal pure returns (bytes32 result) { + /// @solidity memory-safe-assembly + assembly { + let m := mload(0x40) + mstore(m, v0) + mstore(add(m, 0x20), v1) + mstore(add(m, 0x40), v2) + mstore(add(m, 0x60), v3) + mstore(add(m, 0x80), v4) + mstore(add(m, 0xa0), v5) + mstore(add(m, 0xc0), v6) + mstore(add(m, 0xe0), v7) + mstore(add(m, 0x100), v8) + result := keccak256(m, 0x120) + } + } + + /// @dev Returns `keccak256(abi.encode(v0, .., v9))`. + function hash( + bytes32 v0, + bytes32 v1, + bytes32 v2, + bytes32 v3, + bytes32 v4, + bytes32 v5, + bytes32 v6, + bytes32 v7, + bytes32 v8, + bytes32 v9 + ) internal pure returns (bytes32 result) { + /// @solidity memory-safe-assembly + assembly { + let m := mload(0x40) + mstore(m, v0) + mstore(add(m, 0x20), v1) + mstore(add(m, 0x40), v2) + mstore(add(m, 0x60), v3) + mstore(add(m, 0x80), v4) + mstore(add(m, 0xa0), v5) + mstore(add(m, 0xc0), v6) + mstore(add(m, 0xe0), v7) + mstore(add(m, 0x100), v8) + mstore(add(m, 0x120), v9) + result := keccak256(m, 0x140) + } + } + + /// @dev Returns `keccak256(abi.encode(v0, .., v9))`. + function hash( + uint256 v0, + uint256 v1, + uint256 v2, + uint256 v3, + uint256 v4, + uint256 v5, + uint256 v6, + uint256 v7, + uint256 v8, + uint256 v9 + ) internal pure returns (bytes32 result) { + /// @solidity memory-safe-assembly + assembly { + let m := mload(0x40) + mstore(m, v0) + mstore(add(m, 0x20), v1) + mstore(add(m, 0x40), v2) + mstore(add(m, 0x60), v3) + mstore(add(m, 0x80), v4) + mstore(add(m, 0xa0), v5) + mstore(add(m, 0xc0), v6) + mstore(add(m, 0xe0), v7) + mstore(add(m, 0x100), v8) + mstore(add(m, 0x120), v9) + result := keccak256(m, 0x140) + } + } + + /// @dev Returns `keccak256(abi.encode(v0, .., v10))`. + function hash( + bytes32 v0, + bytes32 v1, + bytes32 v2, + bytes32 v3, + bytes32 v4, + bytes32 v5, + bytes32 v6, + bytes32 v7, + bytes32 v8, + bytes32 v9, + bytes32 v10 + ) internal pure returns (bytes32 result) { + /// @solidity memory-safe-assembly + assembly { + let m := mload(0x40) + mstore(m, v0) + mstore(add(m, 0x20), v1) + mstore(add(m, 0x40), v2) + mstore(add(m, 0x60), v3) + mstore(add(m, 0x80), v4) + mstore(add(m, 0xa0), v5) + mstore(add(m, 0xc0), v6) + mstore(add(m, 0xe0), v7) + mstore(add(m, 0x100), v8) + mstore(add(m, 0x120), v9) + mstore(add(m, 0x140), v10) + result := keccak256(m, 0x160) + } + } + + /// @dev Returns `keccak256(abi.encode(v0, .., v10))`. + function hash( + uint256 v0, + uint256 v1, + uint256 v2, + uint256 v3, + uint256 v4, + uint256 v5, + uint256 v6, + uint256 v7, + uint256 v8, + uint256 v9, + uint256 v10 + ) internal pure returns (bytes32 result) { + /// @solidity memory-safe-assembly + assembly { + let m := mload(0x40) + mstore(m, v0) + mstore(add(m, 0x20), v1) + mstore(add(m, 0x40), v2) + mstore(add(m, 0x60), v3) + mstore(add(m, 0x80), v4) + mstore(add(m, 0xa0), v5) + mstore(add(m, 0xc0), v6) + mstore(add(m, 0xe0), v7) + mstore(add(m, 0x100), v8) + mstore(add(m, 0x120), v9) + mstore(add(m, 0x140), v10) + result := keccak256(m, 0x160) + } + } + + /// @dev Returns `keccak256(abi.encode(v0, .., v11))`. + function hash( + bytes32 v0, + bytes32 v1, + bytes32 v2, + bytes32 v3, + bytes32 v4, + bytes32 v5, + bytes32 v6, + bytes32 v7, + bytes32 v8, + bytes32 v9, + bytes32 v10, + bytes32 v11 + ) internal pure returns (bytes32 result) { + /// @solidity memory-safe-assembly + assembly { + let m := mload(0x40) + mstore(m, v0) + mstore(add(m, 0x20), v1) + mstore(add(m, 0x40), v2) + mstore(add(m, 0x60), v3) + mstore(add(m, 0x80), v4) + mstore(add(m, 0xa0), v5) + mstore(add(m, 0xc0), v6) + mstore(add(m, 0xe0), v7) + mstore(add(m, 0x100), v8) + mstore(add(m, 0x120), v9) + mstore(add(m, 0x140), v10) + mstore(add(m, 0x160), v11) + result := keccak256(m, 0x180) + } + } + + /// @dev Returns `keccak256(abi.encode(v0, .., v11))`. + function hash( + uint256 v0, + uint256 v1, + uint256 v2, + uint256 v3, + uint256 v4, + uint256 v5, + uint256 v6, + uint256 v7, + uint256 v8, + uint256 v9, + uint256 v10, + uint256 v11 + ) internal pure returns (bytes32 result) { + /// @solidity memory-safe-assembly + assembly { + let m := mload(0x40) + mstore(m, v0) + mstore(add(m, 0x20), v1) + mstore(add(m, 0x40), v2) + mstore(add(m, 0x60), v3) + mstore(add(m, 0x80), v4) + mstore(add(m, 0xa0), v5) + mstore(add(m, 0xc0), v6) + mstore(add(m, 0xe0), v7) + mstore(add(m, 0x100), v8) + mstore(add(m, 0x120), v9) + mstore(add(m, 0x140), v10) + mstore(add(m, 0x160), v11) + result := keccak256(m, 0x180) + } + } + + /// @dev Returns `keccak256(abi.encode(v0, .., v12))`. + function hash( + bytes32 v0, + bytes32 v1, + bytes32 v2, + bytes32 v3, + bytes32 v4, + bytes32 v5, + bytes32 v6, + bytes32 v7, + bytes32 v8, + bytes32 v9, + bytes32 v10, + bytes32 v11, + bytes32 v12 + ) internal pure returns (bytes32 result) { + /// @solidity memory-safe-assembly + assembly { + let m := mload(0x40) + mstore(m, v0) + mstore(add(m, 0x20), v1) + mstore(add(m, 0x40), v2) + mstore(add(m, 0x60), v3) + mstore(add(m, 0x80), v4) + mstore(add(m, 0xa0), v5) + mstore(add(m, 0xc0), v6) + mstore(add(m, 0xe0), v7) + mstore(add(m, 0x100), v8) + mstore(add(m, 0x120), v9) + mstore(add(m, 0x140), v10) + mstore(add(m, 0x160), v11) + mstore(add(m, 0x180), v12) + result := keccak256(m, 0x1a0) + } + } + + /// @dev Returns `keccak256(abi.encode(v0, .., v12))`. + function hash( + uint256 v0, + uint256 v1, + uint256 v2, + uint256 v3, + uint256 v4, + uint256 v5, + uint256 v6, + uint256 v7, + uint256 v8, + uint256 v9, + uint256 v10, + uint256 v11, + uint256 v12 + ) internal pure returns (bytes32 result) { + /// @solidity memory-safe-assembly + assembly { + let m := mload(0x40) + mstore(m, v0) + mstore(add(m, 0x20), v1) + mstore(add(m, 0x40), v2) + mstore(add(m, 0x60), v3) + mstore(add(m, 0x80), v4) + mstore(add(m, 0xa0), v5) + mstore(add(m, 0xc0), v6) + mstore(add(m, 0xe0), v7) + mstore(add(m, 0x100), v8) + mstore(add(m, 0x120), v9) + mstore(add(m, 0x140), v10) + mstore(add(m, 0x160), v11) + mstore(add(m, 0x180), v12) + result := keccak256(m, 0x1a0) + } + } + + /// @dev Returns `keccak256(abi.encode(v0, .., v13))`. + function hash( + bytes32 v0, + bytes32 v1, + bytes32 v2, + bytes32 v3, + bytes32 v4, + bytes32 v5, + bytes32 v6, + bytes32 v7, + bytes32 v8, + bytes32 v9, + bytes32 v10, + bytes32 v11, + bytes32 v12, + bytes32 v13 + ) internal pure returns (bytes32 result) { + /// @solidity memory-safe-assembly + assembly { + let m := mload(0x40) + mstore(m, v0) + mstore(add(m, 0x20), v1) + mstore(add(m, 0x40), v2) + mstore(add(m, 0x60), v3) + mstore(add(m, 0x80), v4) + mstore(add(m, 0xa0), v5) + mstore(add(m, 0xc0), v6) + mstore(add(m, 0xe0), v7) + mstore(add(m, 0x100), v8) + mstore(add(m, 0x120), v9) + mstore(add(m, 0x140), v10) + mstore(add(m, 0x160), v11) + mstore(add(m, 0x180), v12) + mstore(add(m, 0x1a0), v13) + result := keccak256(m, 0x1c0) + } + } + + /// @dev Returns `keccak256(abi.encode(v0, .., v13))`. + function hash( + uint256 v0, + uint256 v1, + uint256 v2, + uint256 v3, + uint256 v4, + uint256 v5, + uint256 v6, + uint256 v7, + uint256 v8, + uint256 v9, + uint256 v10, + uint256 v11, + uint256 v12, + uint256 v13 + ) internal pure returns (bytes32 result) { + /// @solidity memory-safe-assembly + assembly { + let m := mload(0x40) + mstore(m, v0) + mstore(add(m, 0x20), v1) + mstore(add(m, 0x40), v2) + mstore(add(m, 0x60), v3) + mstore(add(m, 0x80), v4) + mstore(add(m, 0xa0), v5) + mstore(add(m, 0xc0), v6) + mstore(add(m, 0xe0), v7) + mstore(add(m, 0x100), v8) + mstore(add(m, 0x120), v9) + mstore(add(m, 0x140), v10) + mstore(add(m, 0x160), v11) + mstore(add(m, 0x180), v12) + mstore(add(m, 0x1a0), v13) + result := keccak256(m, 0x1c0) + } + } + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* BYTES32 BUFFER HASHING OPERATIONS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev Returns `keccak256(abi.encode(buffer[0], .., buffer[buffer.length - 1]))`. + function hash(bytes32[] memory buffer) internal pure returns (bytes32 result) { + /// @solidity memory-safe-assembly + assembly { + result := keccak256(add(buffer, 0x20), shl(5, mload(buffer))) + } + } + + /// @dev Sets `buffer[i]` to `value`, without a bounds check. + /// Returns the `buffer` for function chaining. + function set(bytes32[] memory buffer, uint256 i, bytes32 value) + internal + pure + returns (bytes32[] memory) + { + /// @solidity memory-safe-assembly + assembly { + mstore(add(buffer, shl(5, add(1, i))), value) + } + return buffer; + } + + /// @dev Sets `buffer[i]` to `value`, without a bounds check. + /// Returns the `buffer` for function chaining. + function set(bytes32[] memory buffer, uint256 i, uint256 value) + internal + pure + returns (bytes32[] memory) + { + /// @solidity memory-safe-assembly + assembly { + mstore(add(buffer, shl(5, add(1, i))), value) + } + return buffer; + } + + /// @dev Returns `new bytes32[](n)`, without zeroing out the memory. + function malloc(uint256 n) internal pure returns (bytes32[] memory buffer) { + /// @solidity memory-safe-assembly + assembly { + buffer := mload(0x40) + mstore(buffer, n) + mstore(0x40, add(shl(5, add(1, n)), buffer)) + } + } + + /// @dev Frees memory that has been allocated for `buffer`. + /// No-op if `buffer.length` is zero, or if new memory has been allocated after `buffer`. + function free(bytes32[] memory buffer) internal pure { + /// @solidity memory-safe-assembly + assembly { + let n := mload(buffer) + mstore(shl(6, lt(iszero(n), eq(add(shl(5, add(1, n)), buffer), mload(0x40)))), buffer) + } + } + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* EQUALITY CHECKS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev Returns `a == abi.decode(b, (bytes32))`. + function eq(bytes32 a, bytes memory b) internal pure returns (bool result) { + /// @solidity memory-safe-assembly + assembly { + result := and(eq(0x20, mload(b)), eq(a, mload(add(b, 0x20)))) + } + } + + /// @dev Returns `abi.decode(a, (bytes32)) == a`. + function eq(bytes memory a, bytes32 b) internal pure returns (bool result) { + /// @solidity memory-safe-assembly + assembly { + result := and(eq(0x20, mload(a)), eq(b, mload(add(a, 0x20)))) + } + } + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* BYTE SLICE HASHING OPERATIONS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev Returns the keccak256 of the slice from `start` to `end` (exclusive). + /// `start` and `end` are byte offsets. + function hash(bytes memory b, uint256 start, uint256 end) + internal + pure + returns (bytes32 result) + { + /// @solidity memory-safe-assembly + assembly { + let n := mload(b) + end := xor(end, mul(xor(end, n), lt(n, end))) + start := xor(start, mul(xor(start, n), lt(n, start))) + result := keccak256(add(add(b, 0x20), start), mul(gt(end, start), sub(end, start))) + } + } + + /// @dev Returns the keccak256 of the slice from `start` to the end of the bytes. + function hash(bytes memory b, uint256 start) internal pure returns (bytes32 result) { + /// @solidity memory-safe-assembly + assembly { + let n := mload(b) + start := xor(start, mul(xor(start, n), lt(n, start))) + result := keccak256(add(add(b, 0x20), start), mul(gt(n, start), sub(n, start))) + } + } + + /// @dev Returns the keccak256 of the bytes. + function hash(bytes memory b) internal pure returns (bytes32 result) { + /// @solidity memory-safe-assembly + assembly { + result := keccak256(add(b, 0x20), mload(b)) + } + } + + /// @dev Returns the keccak256 of the slice from `start` to `end` (exclusive). + /// `start` and `end` are byte offsets. + function hashCalldata(bytes calldata b, uint256 start, uint256 end) + internal + pure + returns (bytes32 result) + { + /// @solidity memory-safe-assembly + assembly { + end := xor(end, mul(xor(end, b.length), lt(b.length, end))) + start := xor(start, mul(xor(start, b.length), lt(b.length, start))) + let n := mul(gt(end, start), sub(end, start)) + calldatacopy(mload(0x40), add(b.offset, start), n) + result := keccak256(mload(0x40), n) + } + } + + /// @dev Returns the keccak256 of the slice from `start` to the end of the bytes. + function hashCalldata(bytes calldata b, uint256 start) internal pure returns (bytes32 result) { + /// @solidity memory-safe-assembly + assembly { + start := xor(start, mul(xor(start, b.length), lt(b.length, start))) + let n := mul(gt(b.length, start), sub(b.length, start)) + calldatacopy(mload(0x40), add(b.offset, start), n) + result := keccak256(mload(0x40), n) + } + } + + /// @dev Returns the keccak256 of the bytes. + function hashCalldata(bytes calldata b) internal pure returns (bytes32 result) { + /// @solidity memory-safe-assembly + assembly { + calldatacopy(mload(0x40), b.offset, b.length) + result := keccak256(mload(0x40), b.length) + } + } + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* SHA2-256 HELPERS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev Returns `sha256(abi.encode(b))`. Yes, it's more efficient. + function sha2(bytes32 b) internal view returns (bytes32 result) { + /// @solidity memory-safe-assembly + assembly { + mstore(0x00, b) + result := mload(staticcall(gas(), 2, 0x00, 0x20, 0x01, 0x20)) + if iszero(returndatasize()) { invalid() } + } + } + + /// @dev Returns the sha256 of the slice from `start` to `end` (exclusive). + /// `start` and `end` are byte offsets. + function sha2(bytes memory b, uint256 start, uint256 end) + internal + view + returns (bytes32 result) + { + /// @solidity memory-safe-assembly + assembly { + let n := mload(b) + end := xor(end, mul(xor(end, n), lt(n, end))) + start := xor(start, mul(xor(start, n), lt(n, start))) + // forgefmt: disable-next-item + result := mload(staticcall(gas(), 2, add(add(b, 0x20), start), + mul(gt(end, start), sub(end, start)), 0x01, 0x20)) + if iszero(returndatasize()) { invalid() } + } + } + + /// @dev Returns the sha256 of the slice from `start` to the end of the bytes. + function sha2(bytes memory b, uint256 start) internal view returns (bytes32 result) { + /// @solidity memory-safe-assembly + assembly { + let n := mload(b) + start := xor(start, mul(xor(start, n), lt(n, start))) + // forgefmt: disable-next-item + result := mload(staticcall(gas(), 2, add(add(b, 0x20), start), + mul(gt(n, start), sub(n, start)), 0x01, 0x20)) + if iszero(returndatasize()) { invalid() } + } + } + + /// @dev Returns the sha256 of the bytes. + function sha2(bytes memory b) internal view returns (bytes32 result) { + /// @solidity memory-safe-assembly + assembly { + result := mload(staticcall(gas(), 2, add(b, 0x20), mload(b), 0x01, 0x20)) + if iszero(returndatasize()) { invalid() } + } + } + + /// @dev Returns the sha256 of the slice from `start` to `end` (exclusive). + /// `start` and `end` are byte offsets. + function sha2Calldata(bytes calldata b, uint256 start, uint256 end) + internal + view + returns (bytes32 result) + { + /// @solidity memory-safe-assembly + assembly { + end := xor(end, mul(xor(end, b.length), lt(b.length, end))) + start := xor(start, mul(xor(start, b.length), lt(b.length, start))) + let n := mul(gt(end, start), sub(end, start)) + calldatacopy(mload(0x40), add(b.offset, start), n) + result := mload(staticcall(gas(), 2, mload(0x40), n, 0x01, 0x20)) + if iszero(returndatasize()) { invalid() } + } + } + + /// @dev Returns the sha256 of the slice from `start` to the end of the bytes. + function sha2Calldata(bytes calldata b, uint256 start) internal view returns (bytes32 result) { + /// @solidity memory-safe-assembly + assembly { + start := xor(start, mul(xor(start, b.length), lt(b.length, start))) + let n := mul(gt(b.length, start), sub(b.length, start)) + calldatacopy(mload(0x40), add(b.offset, start), n) + result := mload(staticcall(gas(), 2, mload(0x40), n, 0x01, 0x20)) + if iszero(returndatasize()) { invalid() } + } + } + + /// @dev Returns the sha256 of the bytes. + function sha2Calldata(bytes calldata b) internal view returns (bytes32 result) { + /// @solidity memory-safe-assembly + assembly { + calldatacopy(mload(0x40), b.offset, b.length) + result := mload(staticcall(gas(), 2, mload(0x40), b.length, 0x01, 0x20)) + if iszero(returndatasize()) { invalid() } + } + } +} diff --git a/biconomy/nexus/1.2.0/node_modules/solady/src/utils/LibBit.sol b/biconomy/nexus/1.2.0/node_modules/solady/src/utils/LibBit.sol new file mode 100644 index 0000000..2ea0b19 --- /dev/null +++ b/biconomy/nexus/1.2.0/node_modules/solady/src/utils/LibBit.sol @@ -0,0 +1,204 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.4; + +/// @notice Library for bit twiddling and boolean operations. +/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/LibBit.sol) +/// @author Inspired by (https://graphics.stanford.edu/~seander/bithacks.html) +library LibBit { + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* BIT TWIDDLING OPERATIONS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev Find last set. + /// Returns the index of the most significant bit of `x`, + /// counting from the least significant bit position. + /// If `x` is zero, returns 256. + function fls(uint256 x) internal pure returns (uint256 r) { + /// @solidity memory-safe-assembly + assembly { + r := or(shl(8, iszero(x)), shl(7, lt(0xffffffffffffffffffffffffffffffff, x))) + r := or(r, shl(6, lt(0xffffffffffffffff, shr(r, x)))) + r := or(r, shl(5, lt(0xffffffff, shr(r, x)))) + r := or(r, shl(4, lt(0xffff, shr(r, x)))) + r := or(r, shl(3, lt(0xff, shr(r, x)))) + // forgefmt: disable-next-item + r := or(r, byte(and(0x1f, shr(shr(r, x), 0x8421084210842108cc6318c6db6d54be)), + 0x0706060506020504060203020504030106050205030304010505030400000000)) + } + } + + /// @dev Count leading zeros. + /// Returns the number of zeros preceding the most significant one bit. + /// If `x` is zero, returns 256. + function clz(uint256 x) internal pure returns (uint256 r) { + /// @solidity memory-safe-assembly + assembly { + r := shl(7, lt(0xffffffffffffffffffffffffffffffff, x)) + r := or(r, shl(6, lt(0xffffffffffffffff, shr(r, x)))) + r := or(r, shl(5, lt(0xffffffff, shr(r, x)))) + r := or(r, shl(4, lt(0xffff, shr(r, x)))) + r := or(r, shl(3, lt(0xff, shr(r, x)))) + // forgefmt: disable-next-item + r := add(xor(r, byte(and(0x1f, shr(shr(r, x), 0x8421084210842108cc6318c6db6d54be)), + 0xf8f9f9faf9fdfafbf9fdfcfdfafbfcfef9fafdfafcfcfbfefafafcfbffffffff)), iszero(x)) + } + } + + /// @dev Find first set. + /// Returns the index of the least significant bit of `x`, + /// counting from the least significant bit position. + /// If `x` is zero, returns 256. + /// Equivalent to `ctz` (count trailing zeros), which gives + /// the number of zeros following the least significant one bit. + function ffs(uint256 x) internal pure returns (uint256 r) { + /// @solidity memory-safe-assembly + assembly { + // Isolate the least significant bit. + x := and(x, add(not(x), 1)) + // For the upper 3 bits of the result, use a De Bruijn-like lookup. + // Credit to adhusson: https://blog.adhusson.com/cheap-find-first-set-evm/ + // forgefmt: disable-next-item + r := shl(5, shr(252, shl(shl(2, shr(250, mul(x, + 0xb6db6db6ddddddddd34d34d349249249210842108c6318c639ce739cffffffff))), + 0x8040405543005266443200005020610674053026020000107506200176117077))) + // For the lower 5 bits of the result, use a De Bruijn lookup. + // forgefmt: disable-next-item + r := or(r, byte(and(div(0xd76453e0, shr(r, x)), 0x1f), + 0x001f0d1e100c1d070f090b19131c1706010e11080a1a141802121b1503160405)) + } + } + + /// @dev Returns the number of set bits in `x`. + function popCount(uint256 x) internal pure returns (uint256 c) { + /// @solidity memory-safe-assembly + assembly { + let max := not(0) + let isMax := eq(x, max) + x := sub(x, and(shr(1, x), div(max, 3))) + x := add(and(x, div(max, 5)), and(shr(2, x), div(max, 5))) + x := and(add(x, shr(4, x)), div(max, 17)) + c := or(shl(8, isMax), shr(248, mul(x, div(max, 255)))) + } + } + + /// @dev Returns whether `x` is a power of 2. + function isPo2(uint256 x) internal pure returns (bool result) { + /// @solidity memory-safe-assembly + assembly { + // Equivalent to `x && !(x & (x - 1))`. + result := iszero(add(and(x, sub(x, 1)), iszero(x))) + } + } + + /// @dev Returns `x` reversed at the bit level. + function reverseBits(uint256 x) internal pure returns (uint256 r) { + uint256 m0 = 0x0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f; + uint256 m1 = m0 ^ (m0 << 2); + uint256 m2 = m1 ^ (m1 << 1); + r = reverseBytes(x); + r = (m2 & (r >> 1)) | ((m2 & r) << 1); + r = (m1 & (r >> 2)) | ((m1 & r) << 2); + r = (m0 & (r >> 4)) | ((m0 & r) << 4); + } + + /// @dev Returns `x` reversed at the byte level. + function reverseBytes(uint256 x) internal pure returns (uint256 r) { + unchecked { + // Computing masks on-the-fly reduces bytecode size by about 200 bytes. + uint256 m0 = 0x100000000000000000000000000000001 * (~toUint(x == uint256(0)) >> 192); + uint256 m1 = m0 ^ (m0 << 32); + uint256 m2 = m1 ^ (m1 << 16); + uint256 m3 = m2 ^ (m2 << 8); + r = (m3 & (x >> 8)) | ((m3 & x) << 8); + r = (m2 & (r >> 16)) | ((m2 & r) << 16); + r = (m1 & (r >> 32)) | ((m1 & r) << 32); + r = (m0 & (r >> 64)) | ((m0 & r) << 64); + r = (r >> 128) | (r << 128); + } + } + + /// @dev Returns the common prefix of `x` and `y` at the bit level. + function commonBitPrefix(uint256 x, uint256 y) internal pure returns (uint256) { + unchecked { + uint256 s = 256 - clz(x ^ y); + return (x >> s) << s; + } + } + + /// @dev Returns the common prefix of `x` and `y` at the nibble level. + function commonNibblePrefix(uint256 x, uint256 y) internal pure returns (uint256) { + unchecked { + uint256 s = (64 - (clz(x ^ y) >> 2)) << 2; + return (x >> s) << s; + } + } + + /// @dev Returns the common prefix of `x` and `y` at the byte level. + function commonBytePrefix(uint256 x, uint256 y) internal pure returns (uint256) { + unchecked { + uint256 s = (32 - (clz(x ^ y) >> 3)) << 3; + return (x >> s) << s; + } + } + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* BOOLEAN OPERATIONS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + // A Solidity bool on the stack or memory is represented as a 256-bit word. + // Non-zero values are true, zero is false. + // A clean bool is either 0 (false) or 1 (true) under the hood. + // Usually, if not always, the bool result of a regular Solidity expression, + // or the argument of a public/external function will be a clean bool. + // You can usually use the raw variants for more performance. + // If uncertain, test (best with exact compiler settings). + // Or use the non-raw variants (compiler can sometimes optimize out the double `iszero`s). + + /// @dev Returns `x & y`. Inputs must be clean. + function rawAnd(bool x, bool y) internal pure returns (bool z) { + /// @solidity memory-safe-assembly + assembly { + z := and(x, y) + } + } + + /// @dev Returns `x & y`. + function and(bool x, bool y) internal pure returns (bool z) { + /// @solidity memory-safe-assembly + assembly { + z := and(iszero(iszero(x)), iszero(iszero(y))) + } + } + + /// @dev Returns `x | y`. Inputs must be clean. + function rawOr(bool x, bool y) internal pure returns (bool z) { + /// @solidity memory-safe-assembly + assembly { + z := or(x, y) + } + } + + /// @dev Returns `x | y`. + function or(bool x, bool y) internal pure returns (bool z) { + /// @solidity memory-safe-assembly + assembly { + z := or(iszero(iszero(x)), iszero(iszero(y))) + } + } + + /// @dev Returns 1 if `b` is true, else 0. Input must be clean. + function rawToUint(bool b) internal pure returns (uint256 z) { + /// @solidity memory-safe-assembly + assembly { + z := b + } + } + + /// @dev Returns 1 if `b` is true, else 0. + function toUint(bool b) internal pure returns (uint256 z) { + /// @solidity memory-safe-assembly + assembly { + z := iszero(iszero(b)) + } + } +} diff --git a/biconomy/nexus/1.2.0/node_modules/solady/src/utils/LibBytes.sol b/biconomy/nexus/1.2.0/node_modules/solady/src/utils/LibBytes.sol new file mode 100644 index 0000000..290c57c --- /dev/null +++ b/biconomy/nexus/1.2.0/node_modules/solady/src/utils/LibBytes.sol @@ -0,0 +1,729 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.4; + +/// @notice Library for byte related operations. +/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/LibBytes.sol) +library LibBytes { + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* STRUCTS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev Goated bytes storage struct that totally MOGs, no cap, fr. + /// Uses less gas and bytecode than Solidity's native bytes storage. It's meta af. + /// Packs length with the first 31 bytes if <255 bytes, so it’s mad tight. + struct BytesStorage { + bytes32 _spacer; + } + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* CONSTANTS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev The constant returned when the `search` is not found in the bytes. + uint256 internal constant NOT_FOUND = type(uint256).max; + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* BYTE STORAGE OPERATIONS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev Sets the value of the bytes storage `$` to `s`. + function set(BytesStorage storage $, bytes memory s) internal { + /// @solidity memory-safe-assembly + assembly { + let n := mload(s) + let packed := or(0xff, shl(8, n)) + for { let i := 0 } 1 {} { + if iszero(gt(n, 0xfe)) { + i := 0x1f + packed := or(n, shl(8, mload(add(s, i)))) + if iszero(gt(n, i)) { break } + } + let o := add(s, 0x20) + mstore(0x00, $.slot) + for { let p := keccak256(0x00, 0x20) } 1 {} { + sstore(add(p, shr(5, i)), mload(add(o, i))) + i := add(i, 0x20) + if iszero(lt(i, n)) { break } + } + break + } + sstore($.slot, packed) + } + } + + /// @dev Sets the value of the bytes storage `$` to `s`. + function setCalldata(BytesStorage storage $, bytes calldata s) internal { + /// @solidity memory-safe-assembly + assembly { + let packed := or(0xff, shl(8, s.length)) + for { let i := 0 } 1 {} { + if iszero(gt(s.length, 0xfe)) { + i := 0x1f + packed := or(s.length, shl(8, shr(8, calldataload(s.offset)))) + if iszero(gt(s.length, i)) { break } + } + mstore(0x00, $.slot) + for { let p := keccak256(0x00, 0x20) } 1 {} { + sstore(add(p, shr(5, i)), calldataload(add(s.offset, i))) + i := add(i, 0x20) + if iszero(lt(i, s.length)) { break } + } + break + } + sstore($.slot, packed) + } + } + + /// @dev Sets the value of the bytes storage `$` to the empty bytes. + function clear(BytesStorage storage $) internal { + delete $._spacer; + } + + /// @dev Returns whether the value stored is `$` is the empty bytes "". + function isEmpty(BytesStorage storage $) internal view returns (bool) { + return uint256($._spacer) & 0xff == uint256(0); + } + + /// @dev Returns the length of the value stored in `$`. + function length(BytesStorage storage $) internal view returns (uint256 result) { + result = uint256($._spacer); + /// @solidity memory-safe-assembly + assembly { + let n := and(0xff, result) + result := or(mul(shr(8, result), eq(0xff, n)), mul(n, iszero(eq(0xff, n)))) + } + } + + /// @dev Returns the value stored in `$`. + function get(BytesStorage storage $) internal view returns (bytes memory result) { + /// @solidity memory-safe-assembly + assembly { + result := mload(0x40) + let o := add(result, 0x20) + let packed := sload($.slot) + let n := shr(8, packed) + for { let i := 0 } 1 {} { + if iszero(eq(or(packed, 0xff), packed)) { + mstore(o, packed) + n := and(0xff, packed) + i := 0x1f + if iszero(gt(n, i)) { break } + } + mstore(0x00, $.slot) + for { let p := keccak256(0x00, 0x20) } 1 {} { + mstore(add(o, i), sload(add(p, shr(5, i)))) + i := add(i, 0x20) + if iszero(lt(i, n)) { break } + } + break + } + mstore(result, n) // Store the length of the memory. + mstore(add(o, n), 0) // Zeroize the slot after the bytes. + mstore(0x40, add(add(o, n), 0x20)) // Allocate memory. + } + } + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* BYTES OPERATIONS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev Returns `subject` all occurrences of `needle` replaced with `replacement`. + function replace(bytes memory subject, bytes memory needle, bytes memory replacement) + internal + pure + returns (bytes memory result) + { + /// @solidity memory-safe-assembly + assembly { + result := mload(0x40) + let needleLen := mload(needle) + let replacementLen := mload(replacement) + let d := sub(result, subject) // Memory difference. + let i := add(subject, 0x20) // Subject bytes pointer. + mstore(0x00, add(i, mload(subject))) // End of subject. + if iszero(gt(needleLen, mload(subject))) { + let subjectSearchEnd := add(sub(mload(0x00), needleLen), 1) + let h := 0 // The hash of `needle`. + if iszero(lt(needleLen, 0x20)) { h := keccak256(add(needle, 0x20), needleLen) } + let s := mload(add(needle, 0x20)) + for { let m := shl(3, sub(0x20, and(needleLen, 0x1f))) } 1 {} { + let t := mload(i) + // Whether the first `needleLen % 32` bytes of `subject` and `needle` matches. + if iszero(shr(m, xor(t, s))) { + if h { + if iszero(eq(keccak256(i, needleLen), h)) { + mstore(add(i, d), t) + i := add(i, 1) + if iszero(lt(i, subjectSearchEnd)) { break } + continue + } + } + // Copy the `replacement` one word at a time. + for { let j := 0 } 1 {} { + mstore(add(add(i, d), j), mload(add(add(replacement, 0x20), j))) + j := add(j, 0x20) + if iszero(lt(j, replacementLen)) { break } + } + d := sub(add(d, replacementLen), needleLen) + if needleLen { + i := add(i, needleLen) + if iszero(lt(i, subjectSearchEnd)) { break } + continue + } + } + mstore(add(i, d), t) + i := add(i, 1) + if iszero(lt(i, subjectSearchEnd)) { break } + } + } + let end := mload(0x00) + let n := add(sub(d, add(result, 0x20)), end) + // Copy the rest of the bytes one word at a time. + for {} lt(i, end) { i := add(i, 0x20) } { mstore(add(i, d), mload(i)) } + let o := add(i, d) + mstore(o, 0) // Zeroize the slot after the bytes. + mstore(0x40, add(o, 0x20)) // Allocate memory. + mstore(result, n) // Store the length. + } + } + + /// @dev Returns the byte index of the first location of `needle` in `subject`, + /// needleing from left to right, starting from `from`. + /// Returns `NOT_FOUND` (i.e. `type(uint256).max`) if the `needle` is not found. + function indexOf(bytes memory subject, bytes memory needle, uint256 from) + internal + pure + returns (uint256 result) + { + /// @solidity memory-safe-assembly + assembly { + result := not(0) // Initialize to `NOT_FOUND`. + for { let subjectLen := mload(subject) } 1 {} { + if iszero(mload(needle)) { + result := from + if iszero(gt(from, subjectLen)) { break } + result := subjectLen + break + } + let needleLen := mload(needle) + let subjectStart := add(subject, 0x20) + + subject := add(subjectStart, from) + let end := add(sub(add(subjectStart, subjectLen), needleLen), 1) + let m := shl(3, sub(0x20, and(needleLen, 0x1f))) + let s := mload(add(needle, 0x20)) + + if iszero(and(lt(subject, end), lt(from, subjectLen))) { break } + + if iszero(lt(needleLen, 0x20)) { + for { let h := keccak256(add(needle, 0x20), needleLen) } 1 {} { + if iszero(shr(m, xor(mload(subject), s))) { + if eq(keccak256(subject, needleLen), h) { + result := sub(subject, subjectStart) + break + } + } + subject := add(subject, 1) + if iszero(lt(subject, end)) { break } + } + break + } + for {} 1 {} { + if iszero(shr(m, xor(mload(subject), s))) { + result := sub(subject, subjectStart) + break + } + subject := add(subject, 1) + if iszero(lt(subject, end)) { break } + } + break + } + } + } + + /// @dev Returns the byte index of the first location of `needle` in `subject`, + /// needleing from left to right. + /// Returns `NOT_FOUND` (i.e. `type(uint256).max`) if the `needle` is not found. + function indexOf(bytes memory subject, bytes memory needle) internal pure returns (uint256) { + return indexOf(subject, needle, 0); + } + + /// @dev Returns the byte index of the first location of `needle` in `subject`, + /// needleing from right to left, starting from `from`. + /// Returns `NOT_FOUND` (i.e. `type(uint256).max`) if the `needle` is not found. + function lastIndexOf(bytes memory subject, bytes memory needle, uint256 from) + internal + pure + returns (uint256 result) + { + /// @solidity memory-safe-assembly + assembly { + for {} 1 {} { + result := not(0) // Initialize to `NOT_FOUND`. + let needleLen := mload(needle) + if gt(needleLen, mload(subject)) { break } + let w := result + + let fromMax := sub(mload(subject), needleLen) + if iszero(gt(fromMax, from)) { from := fromMax } + + let end := add(add(subject, 0x20), w) + subject := add(add(subject, 0x20), from) + if iszero(gt(subject, end)) { break } + // As this function is not too often used, + // we shall simply use keccak256 for smaller bytecode size. + for { let h := keccak256(add(needle, 0x20), needleLen) } 1 {} { + if eq(keccak256(subject, needleLen), h) { + result := sub(subject, add(end, 1)) + break + } + subject := add(subject, w) // `sub(subject, 1)`. + if iszero(gt(subject, end)) { break } + } + break + } + } + } + + /// @dev Returns the byte index of the first location of `needle` in `subject`, + /// needleing from right to left. + /// Returns `NOT_FOUND` (i.e. `type(uint256).max`) if the `needle` is not found. + function lastIndexOf(bytes memory subject, bytes memory needle) + internal + pure + returns (uint256) + { + return lastIndexOf(subject, needle, type(uint256).max); + } + + /// @dev Returns true if `needle` is found in `subject`, false otherwise. + function contains(bytes memory subject, bytes memory needle) internal pure returns (bool) { + return indexOf(subject, needle) != NOT_FOUND; + } + + /// @dev Returns whether `subject` starts with `needle`. + function startsWith(bytes memory subject, bytes memory needle) + internal + pure + returns (bool result) + { + /// @solidity memory-safe-assembly + assembly { + let n := mload(needle) + // Just using keccak256 directly is actually cheaper. + let t := eq(keccak256(add(subject, 0x20), n), keccak256(add(needle, 0x20), n)) + result := lt(gt(n, mload(subject)), t) + } + } + + /// @dev Returns whether `subject` ends with `needle`. + function endsWith(bytes memory subject, bytes memory needle) + internal + pure + returns (bool result) + { + /// @solidity memory-safe-assembly + assembly { + let n := mload(needle) + let notInRange := gt(n, mload(subject)) + // `subject + 0x20 + max(subject.length - needle.length, 0)`. + let t := add(add(subject, 0x20), mul(iszero(notInRange), sub(mload(subject), n))) + // Just using keccak256 directly is actually cheaper. + result := gt(eq(keccak256(t, n), keccak256(add(needle, 0x20), n)), notInRange) + } + } + + /// @dev Returns `subject` repeated `times`. + function repeat(bytes memory subject, uint256 times) + internal + pure + returns (bytes memory result) + { + /// @solidity memory-safe-assembly + assembly { + let l := mload(subject) // Subject length. + if iszero(or(iszero(times), iszero(l))) { + result := mload(0x40) + subject := add(subject, 0x20) + let o := add(result, 0x20) + for {} 1 {} { + // Copy the `subject` one word at a time. + for { let j := 0 } 1 {} { + mstore(add(o, j), mload(add(subject, j))) + j := add(j, 0x20) + if iszero(lt(j, l)) { break } + } + o := add(o, l) + times := sub(times, 1) + if iszero(times) { break } + } + mstore(o, 0) // Zeroize the slot after the bytes. + mstore(0x40, add(o, 0x20)) // Allocate memory. + mstore(result, sub(o, add(result, 0x20))) // Store the length. + } + } + } + + /// @dev Returns a copy of `subject` sliced from `start` to `end` (exclusive). + /// `start` and `end` are byte offsets. + function slice(bytes memory subject, uint256 start, uint256 end) + internal + pure + returns (bytes memory result) + { + /// @solidity memory-safe-assembly + assembly { + let l := mload(subject) // Subject length. + if iszero(gt(l, end)) { end := l } + if iszero(gt(l, start)) { start := l } + if lt(start, end) { + result := mload(0x40) + let n := sub(end, start) + let i := add(subject, start) + let w := not(0x1f) + // Copy the `subject` one word at a time, backwards. + for { let j := and(add(n, 0x1f), w) } 1 {} { + mstore(add(result, j), mload(add(i, j))) + j := add(j, w) // `sub(j, 0x20)`. + if iszero(j) { break } + } + let o := add(add(result, 0x20), n) + mstore(o, 0) // Zeroize the slot after the bytes. + mstore(0x40, add(o, 0x20)) // Allocate memory. + mstore(result, n) // Store the length. + } + } + } + + /// @dev Returns a copy of `subject` sliced from `start` to the end of the bytes. + /// `start` is a byte offset. + function slice(bytes memory subject, uint256 start) + internal + pure + returns (bytes memory result) + { + result = slice(subject, start, type(uint256).max); + } + + /// @dev Returns a copy of `subject` sliced from `start` to `end` (exclusive). + /// `start` and `end` are byte offsets. Faster than Solidity's native slicing. + function sliceCalldata(bytes calldata subject, uint256 start, uint256 end) + internal + pure + returns (bytes calldata result) + { + /// @solidity memory-safe-assembly + assembly { + end := xor(end, mul(xor(end, subject.length), lt(subject.length, end))) + start := xor(start, mul(xor(start, subject.length), lt(subject.length, start))) + result.offset := add(subject.offset, start) + result.length := mul(lt(start, end), sub(end, start)) + } + } + + /// @dev Returns a copy of `subject` sliced from `start` to the end of the bytes. + /// `start` is a byte offset. Faster than Solidity's native slicing. + function sliceCalldata(bytes calldata subject, uint256 start) + internal + pure + returns (bytes calldata result) + { + /// @solidity memory-safe-assembly + assembly { + start := xor(start, mul(xor(start, subject.length), lt(subject.length, start))) + result.offset := add(subject.offset, start) + result.length := mul(lt(start, subject.length), sub(subject.length, start)) + } + } + + /// @dev Reduces the size of `subject` to `n`. + /// If `n` is greater than the size of `subject`, this will be a no-op. + function truncate(bytes memory subject, uint256 n) + internal + pure + returns (bytes memory result) + { + /// @solidity memory-safe-assembly + assembly { + result := subject + mstore(mul(lt(n, mload(result)), result), n) + } + } + + /// @dev Returns a copy of `subject`, with the length reduced to `n`. + /// If `n` is greater than the size of `subject`, this will be a no-op. + function truncatedCalldata(bytes calldata subject, uint256 n) + internal + pure + returns (bytes calldata result) + { + /// @solidity memory-safe-assembly + assembly { + result.offset := subject.offset + result.length := xor(n, mul(xor(n, subject.length), lt(subject.length, n))) + } + } + + /// @dev Returns all the indices of `needle` in `subject`. + /// The indices are byte offsets. + function indicesOf(bytes memory subject, bytes memory needle) + internal + pure + returns (uint256[] memory result) + { + /// @solidity memory-safe-assembly + assembly { + let searchLen := mload(needle) + if iszero(gt(searchLen, mload(subject))) { + result := mload(0x40) + let i := add(subject, 0x20) + let o := add(result, 0x20) + let subjectSearchEnd := add(sub(add(i, mload(subject)), searchLen), 1) + let h := 0 // The hash of `needle`. + if iszero(lt(searchLen, 0x20)) { h := keccak256(add(needle, 0x20), searchLen) } + let s := mload(add(needle, 0x20)) + for { let m := shl(3, sub(0x20, and(searchLen, 0x1f))) } 1 {} { + let t := mload(i) + // Whether the first `searchLen % 32` bytes of `subject` and `needle` matches. + if iszero(shr(m, xor(t, s))) { + if h { + if iszero(eq(keccak256(i, searchLen), h)) { + i := add(i, 1) + if iszero(lt(i, subjectSearchEnd)) { break } + continue + } + } + mstore(o, sub(i, add(subject, 0x20))) // Append to `result`. + o := add(o, 0x20) + i := add(i, searchLen) // Advance `i` by `searchLen`. + if searchLen { + if iszero(lt(i, subjectSearchEnd)) { break } + continue + } + } + i := add(i, 1) + if iszero(lt(i, subjectSearchEnd)) { break } + } + mstore(result, shr(5, sub(o, add(result, 0x20)))) // Store the length of `result`. + // Allocate memory for result. + // We allocate one more word, so this array can be recycled for {split}. + mstore(0x40, add(o, 0x20)) + } + } + } + + /// @dev Returns a arrays of bytess based on the `delimiter` inside of the `subject` bytes. + function split(bytes memory subject, bytes memory delimiter) + internal + pure + returns (bytes[] memory result) + { + uint256[] memory indices = indicesOf(subject, delimiter); + /// @solidity memory-safe-assembly + assembly { + let w := not(0x1f) + let indexPtr := add(indices, 0x20) + let indicesEnd := add(indexPtr, shl(5, add(mload(indices), 1))) + mstore(add(indicesEnd, w), mload(subject)) + mstore(indices, add(mload(indices), 1)) + for { let prevIndex := 0 } 1 {} { + let index := mload(indexPtr) + mstore(indexPtr, 0x60) + if iszero(eq(index, prevIndex)) { + let element := mload(0x40) + let l := sub(index, prevIndex) + mstore(element, l) // Store the length of the element. + // Copy the `subject` one word at a time, backwards. + for { let o := and(add(l, 0x1f), w) } 1 {} { + mstore(add(element, o), mload(add(add(subject, prevIndex), o))) + o := add(o, w) // `sub(o, 0x20)`. + if iszero(o) { break } + } + mstore(add(add(element, 0x20), l), 0) // Zeroize the slot after the bytes. + // Allocate memory for the length and the bytes, rounded up to a multiple of 32. + mstore(0x40, add(element, and(add(l, 0x3f), w))) + mstore(indexPtr, element) // Store the `element` into the array. + } + prevIndex := add(index, mload(delimiter)) + indexPtr := add(indexPtr, 0x20) + if iszero(lt(indexPtr, indicesEnd)) { break } + } + result := indices + if iszero(mload(delimiter)) { + result := add(indices, 0x20) + mstore(result, sub(mload(indices), 2)) + } + } + } + + /// @dev Returns a concatenated bytes of `a` and `b`. + /// Cheaper than `bytes.concat()` and does not de-align the free memory pointer. + function concat(bytes memory a, bytes memory b) internal pure returns (bytes memory result) { + /// @solidity memory-safe-assembly + assembly { + result := mload(0x40) + let w := not(0x1f) + let aLen := mload(a) + // Copy `a` one word at a time, backwards. + for { let o := and(add(aLen, 0x20), w) } 1 {} { + mstore(add(result, o), mload(add(a, o))) + o := add(o, w) // `sub(o, 0x20)`. + if iszero(o) { break } + } + let bLen := mload(b) + let output := add(result, aLen) + // Copy `b` one word at a time, backwards. + for { let o := and(add(bLen, 0x20), w) } 1 {} { + mstore(add(output, o), mload(add(b, o))) + o := add(o, w) // `sub(o, 0x20)`. + if iszero(o) { break } + } + let totalLen := add(aLen, bLen) + let last := add(add(result, 0x20), totalLen) + mstore(last, 0) // Zeroize the slot after the bytes. + mstore(result, totalLen) // Store the length. + mstore(0x40, add(last, 0x20)) // Allocate memory. + } + } + + /// @dev Returns whether `a` equals `b`. + function eq(bytes memory a, bytes memory b) internal pure returns (bool result) { + /// @solidity memory-safe-assembly + assembly { + result := eq(keccak256(add(a, 0x20), mload(a)), keccak256(add(b, 0x20), mload(b))) + } + } + + /// @dev Returns whether `a` equals `b`, where `b` is a null-terminated small bytes. + function eqs(bytes memory a, bytes32 b) internal pure returns (bool result) { + /// @solidity memory-safe-assembly + assembly { + // These should be evaluated on compile time, as far as possible. + let m := not(shl(7, div(not(iszero(b)), 255))) // `0x7f7f ...`. + let x := not(or(m, or(b, add(m, and(b, m))))) + let r := shl(7, iszero(iszero(shr(128, x)))) + r := or(r, shl(6, iszero(iszero(shr(64, shr(r, x)))))) + r := or(r, shl(5, lt(0xffffffff, shr(r, x)))) + r := or(r, shl(4, lt(0xffff, shr(r, x)))) + r := or(r, shl(3, lt(0xff, shr(r, x)))) + // forgefmt: disable-next-item + result := gt(eq(mload(a), add(iszero(x), xor(31, shr(3, r)))), + xor(shr(add(8, r), b), shr(add(8, r), mload(add(a, 0x20))))) + } + } + + /// @dev Returns 0 if `a == b`, -1 if `a < b`, +1 if `a > b`. + /// If `a` == b[:a.length]`, and `a.length < b.length`, returns -1. + function cmp(bytes memory a, bytes memory b) internal pure returns (int256 result) { + /// @solidity memory-safe-assembly + assembly { + let aLen := mload(a) + let bLen := mload(b) + let n := and(xor(aLen, mul(xor(aLen, bLen), lt(bLen, aLen))), not(0x1f)) + if n { + for { let i := 0x20 } 1 {} { + let x := mload(add(a, i)) + let y := mload(add(b, i)) + if iszero(or(xor(x, y), eq(i, n))) { + i := add(i, 0x20) + continue + } + result := sub(gt(x, y), lt(x, y)) + break + } + } + // forgefmt: disable-next-item + if iszero(result) { + let l := 0x201f1e1d1c1b1a191817161514131211100f0e0d0c0b0a090807060504030201 + let x := and(mload(add(add(a, 0x20), n)), shl(shl(3, byte(sub(aLen, n), l)), not(0))) + let y := and(mload(add(add(b, 0x20), n)), shl(shl(3, byte(sub(bLen, n), l)), not(0))) + result := sub(gt(x, y), lt(x, y)) + if iszero(result) { result := sub(gt(aLen, bLen), lt(aLen, bLen)) } + } + } + } + + /// @dev Directly returns `a` without copying. + function directReturn(bytes memory a) internal pure { + /// @solidity memory-safe-assembly + assembly { + // Assumes that the bytes does not start from the scratch space. + let retStart := sub(a, 0x20) + let retUnpaddedSize := add(mload(a), 0x40) + // Right pad with zeroes. Just in case the bytes is produced + // by a method that doesn't zero right pad. + mstore(add(retStart, retUnpaddedSize), 0) + mstore(retStart, 0x20) // Store the return offset. + // End the transaction, returning the bytes. + return(retStart, and(not(0x1f), add(0x1f, retUnpaddedSize))) + } + } + + /// @dev Directly returns `a` with minimal copying. + function directReturn(bytes[] memory a) internal pure { + /// @solidity memory-safe-assembly + assembly { + let n := mload(a) // `a.length`. + let o := add(a, 0x20) // Start of elements in `a`. + let u := a // Highest memory slot. + let w := not(0x1f) + for { let i := 0 } iszero(eq(i, n)) { i := add(i, 1) } { + let c := add(o, shl(5, i)) // Location of pointer to `a[i]`. + let s := mload(c) // `a[i]`. + let l := mload(s) // `a[i].length`. + let r := and(l, 0x1f) // `a[i].length % 32`. + let z := add(0x20, and(l, w)) // Offset of last word in `a[i]` from `s`. + // If `s` comes before `o`, or `s` is not zero right padded. + if iszero(lt(lt(s, o), or(iszero(r), iszero(shl(shl(3, r), mload(add(s, z))))))) { + let m := mload(0x40) + mstore(m, l) // Copy `a[i].length`. + for {} 1 {} { + mstore(add(m, z), mload(add(s, z))) // Copy `a[i]`, backwards. + z := add(z, w) // `sub(z, 0x20)`. + if iszero(z) { break } + } + let e := add(add(m, 0x20), l) + mstore(e, 0) // Zeroize the slot after the copied bytes. + mstore(0x40, add(e, 0x20)) // Allocate memory. + s := m + } + mstore(c, sub(s, o)) // Convert to calldata offset. + let t := add(l, add(s, 0x20)) + if iszero(lt(t, u)) { u := t } + } + let retStart := add(a, w) // Assumes `a` doesn't start from scratch space. + mstore(retStart, 0x20) // Store the return offset. + return(retStart, add(0x40, sub(u, retStart))) // End the transaction. + } + } + + /// @dev Returns the word at `offset`, without any bounds checks. + /// To load an address, you can use `address(bytes20(load(a, offset)))`. + function load(bytes memory a, uint256 offset) internal pure returns (bytes32 result) { + /// @solidity memory-safe-assembly + assembly { + result := mload(add(add(a, 0x20), offset)) + } + } + + /// @dev Returns the word at `offset`, without any bounds checks. + /// To load an address, you can use `address(bytes20(loadCalldata(a, offset)))`. + function loadCalldata(bytes calldata a, uint256 offset) + internal + pure + returns (bytes32 result) + { + /// @solidity memory-safe-assembly + assembly { + result := calldataload(add(a.offset, offset)) + } + } + + /// @dev Returns empty calldata bytes. For silencing the compiler. + function emptyCalldata() internal pure returns (bytes calldata result) { + /// @solidity memory-safe-assembly + assembly { + result.length := 0 + } + } +} diff --git a/biconomy/nexus/1.2.0/node_modules/solady/src/utils/UUPSUpgradeable.sol b/biconomy/nexus/1.2.0/node_modules/solady/src/utils/UUPSUpgradeable.sol new file mode 100644 index 0000000..124c3a3 --- /dev/null +++ b/biconomy/nexus/1.2.0/node_modules/solady/src/utils/UUPSUpgradeable.sol @@ -0,0 +1,105 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.4; + +import {CallContextChecker} from "./CallContextChecker.sol"; + +/// @notice UUPS proxy mixin. +/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/UUPSUpgradeable.sol) +/// @author Modified from OpenZeppelin +/// (https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/proxy/utils/UUPSUpgradeable.sol) +/// +/// @dev Note: +/// - This implementation is intended to be used with ERC1967 proxies. +/// See: `LibClone.deployERC1967` and related functions. +/// - This implementation is NOT compatible with legacy OpenZeppelin proxies +/// which do not store the implementation at `_ERC1967_IMPLEMENTATION_SLOT`. +abstract contract UUPSUpgradeable is CallContextChecker { + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* CUSTOM ERRORS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev The upgrade failed. + error UpgradeFailed(); + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* EVENTS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev Emitted when the proxy's implementation is upgraded. + event Upgraded(address indexed implementation); + + /// @dev `keccak256(bytes("Upgraded(address)"))`. + uint256 private constant _UPGRADED_EVENT_SIGNATURE = + 0xbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b; + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* STORAGE */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev The ERC-1967 storage slot for the implementation in the proxy. + /// `uint256(keccak256("eip1967.proxy.implementation")) - 1`. + bytes32 internal constant _ERC1967_IMPLEMENTATION_SLOT = + 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc; + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* UUPS OPERATIONS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev Please override this function to check if `msg.sender` is authorized + /// to upgrade the proxy to `newImplementation`, reverting if not. + /// ``` + /// function _authorizeUpgrade(address) internal override onlyOwner {} + /// ``` + function _authorizeUpgrade(address newImplementation) internal virtual; + + /// @dev Returns the storage slot used by the implementation, + /// as specified in [ERC1822](https://eips.ethereum.org/EIPS/eip-1822). + /// + /// Note: The `notDelegated` modifier prevents accidental upgrades to + /// an implementation that is a proxy contract. + function proxiableUUID() public view virtual notDelegated returns (bytes32) { + // This function must always return `_ERC1967_IMPLEMENTATION_SLOT` to comply with ERC1967. + return _ERC1967_IMPLEMENTATION_SLOT; + } + + /// @dev Upgrades the proxy's implementation to `newImplementation`. + /// Emits a {Upgraded} event. + /// + /// Note: Passing in empty `data` skips the delegatecall to `newImplementation`. + function upgradeToAndCall(address newImplementation, bytes calldata data) + public + payable + virtual + onlyProxy + { + _authorizeUpgrade(newImplementation); + /// @solidity memory-safe-assembly + assembly { + newImplementation := shr(96, shl(96, newImplementation)) // Clears upper 96 bits. + mstore(0x00, returndatasize()) + mstore(0x01, 0x52d1902d) // `proxiableUUID()`. + let s := _ERC1967_IMPLEMENTATION_SLOT + // Check if `newImplementation` implements `proxiableUUID` correctly. + if iszero(eq(mload(staticcall(gas(), newImplementation, 0x1d, 0x04, 0x01, 0x20)), s)) { + mstore(0x01, 0x55299b49) // `UpgradeFailed()`. + revert(0x1d, 0x04) + } + // Emit the {Upgraded} event. + log2(codesize(), 0x00, _UPGRADED_EVENT_SIGNATURE, newImplementation) + sstore(s, newImplementation) // Updates the implementation. + + // Perform a delegatecall to `newImplementation` if `data` is non-empty. + if data.length { + // Forwards the `data` to `newImplementation` via delegatecall. + let m := mload(0x40) + calldatacopy(m, data.offset, data.length) + if iszero(delegatecall(gas(), newImplementation, m, data.length, codesize(), 0x00)) + { + // Bubble up the revert if the call reverts. + returndatacopy(m, 0x00, returndatasize()) + revert(m, returndatasize()) + } + } + } + } +} diff --git a/biconomy/nexus/1.2.0/remappings.txt b/biconomy/nexus/1.2.0/remappings.txt new file mode 100644 index 0000000..a450945 --- /dev/null +++ b/biconomy/nexus/1.2.0/remappings.txt @@ -0,0 +1,15 @@ +@openzeppelin/=node_modules/@openzeppelin/ +forge-std/=node_modules/forge-std/src/ +account-abstraction/=node_modules/account-abstraction/contracts/ +solady/=node_modules/solady/src/ +excessively-safe-call/=node_modules/excessively-safe-call/src/ +sentinellist/=node_modules/sentinellist/src/ +solarray/=node_modules/solarray/src/ +erc7579/=node_modules/erc7579/src/ +erc7739Validator/=node_modules/erc7739-validator-base/src/ +lib-prep/=node_modules/prep/src/ +composability/=node_modules/@biconomy/composability/contracts/ +openzeppelin/=node_modules/@openzeppelin/contracts/ +rlp-reader/=node_modules/Solidity-RLP/contracts/ +byteslib/=node_modules/solidity-bytes-utils/contracts/ +EnumerableSet4337/=node_modules/enumerablemap4337/src/ diff --git a/biconomy/nexus/Makefile b/biconomy/nexus/Makefile new file mode 100644 index 0000000..a3c2156 --- /dev/null +++ b/biconomy/nexus/Makefile @@ -0,0 +1,8 @@ +VERSIONS += 1.0.2 +VERSIONS += 1.2.0 + +.PHONY: all $(VERSIONS) +all: $(VERSIONS) + +$(VERSIONS): + @$(MAKE) -C $@ -f ../../../Makefile.project diff --git a/rhinestone/Makefile b/rhinestone/Makefile new file mode 100644 index 0000000..8ceb6f1 --- /dev/null +++ b/rhinestone/Makefile @@ -0,0 +1,7 @@ +PROJECTS += registry + +.PHONY: all $(PROJECTS) +all: $(PROJECTS) + +$(PROJECTS): + @$(MAKE) -C $@ diff --git a/rhinestone/registry/1.0.0/cannonfile.toml b/rhinestone/registry/1.0.0/cannonfile.toml new file mode 100644 index 0000000..0cbbe08 --- /dev/null +++ b/rhinestone/registry/1.0.0/cannonfile.toml @@ -0,0 +1,9 @@ +name = "rhinestone-registry" +version = "1.0.0" +description = "Rhinestone ERC-4337 infrastructure" + +[deploy.Registry] +artifact = "Registry" +create2 = "0x914d7Fec6aaC8cd542e72Bca78B30650d45643d7" +salt = "0x0000000000000000000000000000000000000000d7f995ff1ea60f02e52877f7" +ifExists = "continue" diff --git a/rhinestone/registry/1.0.0/expected b/rhinestone/registry/1.0.0/expected new file mode 100644 index 0000000..e79b993 --- /dev/null +++ b/rhinestone/registry/1.0.0/expected @@ -0,0 +1 @@ +Registry 0x000000000069E2a187AEFFb852bF3cCdC95151B2 diff --git a/rhinestone/registry/1.0.0/foundry.toml b/rhinestone/registry/1.0.0/foundry.toml new file mode 100644 index 0000000..3af7357 --- /dev/null +++ b/rhinestone/registry/1.0.0/foundry.toml @@ -0,0 +1,15 @@ +[profile.default] +src = "node_modules" +out = "out" +libs = ["lib"] +auto_detect_remappings = false +auto_detect_solc = false +solc_version = "0.8.25" +evm_version = "paris" +via_ir = false +cbor_metadata = true +use_literal_content = false +bytecode_hash = "ipfs" +optimizer = true +optimizer_runs = 200 +libraries = [] diff --git a/rhinestone/registry/1.0.0/lib/forge-std/src/interfaces/IERC165.sol b/rhinestone/registry/1.0.0/lib/forge-std/src/interfaces/IERC165.sol new file mode 100644 index 0000000..9af4bf8 --- /dev/null +++ b/rhinestone/registry/1.0.0/lib/forge-std/src/interfaces/IERC165.sol @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.6.2; + +interface IERC165 { + /// @notice Query if a contract implements an interface + /// @param interfaceID The interface identifier, as specified in ERC-165 + /// @dev Interface identification is specified in ERC-165. This function + /// uses less than 30,000 gas. + /// @return `true` if the contract implements `interfaceID` and + /// `interfaceID` is not 0xffffffff, `false` otherwise + function supportsInterface(bytes4 interfaceID) external view returns (bool); +} diff --git a/rhinestone/registry/1.0.0/node_modules/@rhinestone/registry/src/Common.sol b/rhinestone/registry/1.0.0/node_modules/@rhinestone/registry/src/Common.sol new file mode 100644 index 0000000..e2f3ff7 --- /dev/null +++ b/rhinestone/registry/1.0.0/node_modules/@rhinestone/registry/src/Common.sol @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.24; + +import { ResolverUID, SchemaUID, AttestationDataRef, ModuleType } from "./DataTypes.sol"; + +// A representation of an empty/uninitialized UID. +bytes32 constant EMPTY_UID = 0; +ResolverUID constant EMPTY_RESOLVER_UID = ResolverUID.wrap(EMPTY_UID); +SchemaUID constant EMPTY_SCHEMA_UID = SchemaUID.wrap(EMPTY_UID); + +// A zero expiration represents an non-expiring attestation. +uint256 constant ZERO_TIMESTAMP = 0; + +address constant ZERO_ADDRESS = address(0); +ModuleType constant ZERO_MODULE_TYPE = ModuleType.wrap(0); + +AttestationDataRef constant EMPTY_ATTESTATION_REF = AttestationDataRef.wrap(address(0)); + +/** + * @dev Returns the current's block timestamp. This method is overridden during tests and used to simulate the + * current block time. + */ +function _time() view returns (uint48) { + return uint48(block.timestamp); +} + +/** + * @dev Returns whether an address is a contract. + * @param addr The address to check. + * + * @return true if `account` is a contract, false otherwise. + */ +function _isContract(address addr) view returns (bool) { + return addr.code.length > 0; +} diff --git a/rhinestone/registry/1.0.0/node_modules/@rhinestone/registry/src/DataTypes.sol b/rhinestone/registry/1.0.0/node_modules/@rhinestone/registry/src/DataTypes.sol new file mode 100644 index 0000000..778d5b3 --- /dev/null +++ b/rhinestone/registry/1.0.0/node_modules/@rhinestone/registry/src/DataTypes.sol @@ -0,0 +1,121 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.24; + +import { IExternalSchemaValidator } from "./external/IExternalSchemaValidator.sol"; +import { IExternalResolver } from "./external/IExternalResolver.sol"; + +/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ +/* Storage Structs */ +/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + +struct AttestationRecord { + uint48 time; // The time when the attestation was created (Unix timestamp). + uint48 expirationTime; // The time when the attestation expires (Unix timestamp). + uint48 revocationTime; // The time when the attestation was revoked (Unix timestamp). + PackedModuleTypes moduleTypes; // bit-wise encoded module types. See ModuleTypeLib + address moduleAddress; // The implementation address of the module that is being attested. + address attester; // The attesting account. + AttestationDataRef dataPointer; // SSTORE2 pointer to the attestation data. + SchemaUID schemaUID; // The unique identifier of the schema. +} + +struct ModuleRecord { + ResolverUID resolverUID; // The unique identifier of the resolver. + address sender; // The address of the sender who deployed the contract + bytes metadata; // Additional data related to the contract deployment +} + +struct SchemaRecord { + uint48 registeredAt; // The time when the schema was registered (Unix timestamp). + IExternalSchemaValidator validator; // Optional external schema validator. + string schema; // Custom specification of the schema (e.g., an ABI). +} + +struct ResolverRecord { + IExternalResolver resolver; // Optional resolver. + address resolverOwner; // The address of the account used to register the resolver. +} + +// Struct that represents a trusted attester. +struct TrustedAttesterRecord { + uint8 attesterCount; // number of attesters in the linked list + uint8 threshold; // minimum number of attesters required + address attester; // first attester in linked list. (packed to save gas) + mapping(address attester => mapping(address account => address linkedAttester)) linkedAttesters; +} + +/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ +/* Attestation / Revocation Requests */ +/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + +/** + * @dev A struct representing the arguments of the attestation request. + */ +struct AttestationRequest { + address moduleAddress; // The moduleAddress of the attestation. + uint48 expirationTime; // The time when the attestation expires (Unix timestamp). + bytes data; // Custom attestation data. + ModuleType[] moduleTypes; // optional: The type(s) of the module. +} + +/** + * @dev A struct representing the arguments of the revocation request. + */ +struct RevocationRequest { + address moduleAddress; // The module address. +} + +/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ +/* Custom Types */ +/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + +//---------------------- SchemaUID ------------------------------| +type SchemaUID is bytes32; + +using { schemaEq as == } for SchemaUID global; +using { schemaNotEq as != } for SchemaUID global; + +function schemaEq(SchemaUID uid1, SchemaUID uid2) pure returns (bool) { + return SchemaUID.unwrap(uid1) == SchemaUID.unwrap(uid2); +} + +function schemaNotEq(SchemaUID uid1, SchemaUID uid2) pure returns (bool) { + return SchemaUID.unwrap(uid1) != SchemaUID.unwrap(uid2); +} + +//--------------------- ResolverUID -----------------------------| +type ResolverUID is bytes32; + +using { resolverEq as == } for ResolverUID global; +using { resolverNotEq as != } for ResolverUID global; + +function resolverEq(ResolverUID uid1, ResolverUID uid2) pure returns (bool) { + return ResolverUID.unwrap(uid1) == ResolverUID.unwrap(uid2); +} + +function resolverNotEq(ResolverUID uid1, ResolverUID uid2) pure returns (bool) { + return ResolverUID.unwrap(uid1) != ResolverUID.unwrap(uid2); +} + +type AttestationDataRef is address; + +using { attestationDataRefEq as == } for AttestationDataRef global; + +function attestationDataRefEq(AttestationDataRef uid1, AttestationDataRef uid2) pure returns (bool) { + return AttestationDataRef.unwrap(uid1) == AttestationDataRef.unwrap(uid2); +} + +type PackedModuleTypes is uint32; + +type ModuleType is uint256; + +using { moduleTypeEq as == } for ModuleType global; +using { moduleTypeNeq as != } for ModuleType global; + +function moduleTypeEq(ModuleType uid1, ModuleType uid2) pure returns (bool) { + return ModuleType.unwrap(uid1) == ModuleType.unwrap(uid2); +} + +function moduleTypeNeq(ModuleType uid1, ModuleType uid2) pure returns (bool) { + return ModuleType.unwrap(uid1) != ModuleType.unwrap(uid2); +} diff --git a/rhinestone/registry/1.0.0/node_modules/@rhinestone/registry/src/IRegistry.sol b/rhinestone/registry/1.0.0/node_modules/@rhinestone/registry/src/IRegistry.sol new file mode 100644 index 0000000..428dba2 --- /dev/null +++ b/rhinestone/registry/1.0.0/node_modules/@rhinestone/registry/src/IRegistry.sol @@ -0,0 +1,356 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.24; + +import { + AttestationDataRef, + AttestationRecord, + AttestationRequest, + ModuleType, + ModuleRecord, + ResolverUID, + ResolverRecord, + RevocationRequest, + SchemaUID, + SchemaRecord +} from "./DataTypes.sol"; +import { IExternalSchemaValidator } from "./external/IExternalSchemaValidator.sol"; +import { IExternalResolver } from "./external/IExternalResolver.sol"; +import { IERC7484 } from "./interfaces/IERC7484.sol"; + +/** + * Interface definition of all features of the registry: + * - Register Schemas + * - Register External Resolvers + * - Register Modules + * - Make Attestations + * - Make Revocations + * - Delegate Trust to Attester(s) + * + * @author rhinestone | zeroknots.eth, Konrad Kopp (@kopy-kat) + */ +interface IRegistry is IERC7484 { + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* Smart Account - Trust Management */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + error InvalidResolver(IExternalResolver resolver); + error InvalidResolverUID(ResolverUID uid); + error InvalidTrustedAttesterInput(); + error NoTrustedAttestersFound(); + error RevokedAttestation(address attester); + error InvalidModuleType(); + error AttestationNotFound(); + + error InsufficientAttestations(); + + /** + * Get trusted attester for a specific Smart Account + * @param smartAccount The address of the Smart Account + */ + function findTrustedAttesters(address smartAccount) external view returns (address[] memory attesters); + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* Attestations */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + event Revoked(address indexed moduleAddress, address indexed revoker, SchemaUID schema); + event Attested(address indexed moduleAddress, address indexed attester, SchemaUID schemaUID, AttestationDataRef indexed sstore2Pointer); + + error AlreadyRevoked(); + error AlreadyAttested(); + error ModuleNotFoundInRegistry(address module); + error AccessDenied(); + error InvalidAttestation(); + error InvalidExpirationTime(); + error DifferentResolvers(); + error InvalidSignature(); + error InvalidModuleTypes(); + + /** + * Allows `msg.sender` to attest to multiple modules' security status. + * The `AttestationRequest.Data` provided should match the attestation + * schema defined by the Schema corresponding to the SchemaUID + * + * @dev This function will revert if the same module is attested twice by the same attester. + * If you want to re-attest, you have to revoke your attestation first, and then attest again. + * + * @param schemaUID The SchemaUID of the schema the attestation is based on. + * @param request a single AttestationRequest + */ + function attest(SchemaUID schemaUID, AttestationRequest calldata request) external; + + /** + * Allows `msg.sender` to attest to multiple modules' security status. + * The `AttestationRequest.Data` provided should match the attestation + * schema defined by the Schema corresponding to the SchemaUID + * + * @dev This function will revert if the same module is attested twice by the same attester. + * If you want to re-attest, you have to revoke your attestation first, and then attest again. + * + * @param schemaUID The SchemaUID of the schema the attestation is based on. + * @param requests An array of AttestationRequest + */ + function attest(SchemaUID schemaUID, AttestationRequest[] calldata requests) external; + + /** + * Allows attester to attest by signing an `AttestationRequest` (`ECDSA` or `ERC1271`) + * The `AttestationRequest.Data` provided should match the attestation + * schema defined by the Schema corresponding to the SchemaUID + * + * @dev This function will revert if the same module is attested twice by the same attester. + * If you want to re-attest, you have to revoke your attestation first, and then attest again. + * + * @param schemaUID The SchemaUID of the schema the attestation is based on. + * @param attester The address of the attester + * @param request An AttestationRequest + * @param signature The signature of the attester. ECDSA or ERC1271 + */ + function attest(SchemaUID schemaUID, address attester, AttestationRequest calldata request, bytes calldata signature) external; + + /** + * Allows attester to attest by signing an `AttestationRequest` (`ECDSA` or `ERC1271`) + * The `AttestationRequest.Data` provided should match the attestation + * schema defined by the Schema corresponding to the SchemaUID + * + * @dev This function will revert if the same module is attested twice by the same attester. + * If you want to re-attest, you have to revoke your attestation first, and then attest again. + * + * @param schemaUID The SchemaUID of the schema the attestation is based on. + * @param attester The address of the attester + * @param requests An array of AttestationRequest + * @param signature The signature of the attester. ECDSA or ERC1271 + */ + function attest(SchemaUID schemaUID, address attester, AttestationRequest[] calldata requests, bytes calldata signature) external; + + /** + * Getter function to get AttestationRequest made by one attester + */ + function findAttestation(address module, address attester) external view returns (AttestationRecord memory attestation); + + /** + * Getter function to get AttestationRequest made by multiple attesters + */ + function findAttestations( + address module, + address[] calldata attesters + ) + external + view + returns (AttestationRecord[] memory attestations); + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* Revocations */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /** + * Allows `msg.sender` to revoke an attestation made by the same `msg.sender` + * + * @dev this function will revert if the attestation is not found + * @dev this function will revert if the attestation is already revoked + * + * @param request single RevocationRequest + */ + function revoke(RevocationRequest calldata request) external; + + /** + * Allows msg.sender to revoke multiple attestation made by the same msg.sender + * + * @dev this function will revert if the attestation is not found + * @dev this function will revert if the attestation is already revoked + * + * @param requests the RevocationRequests + */ + function revoke(RevocationRequest[] calldata requests) external; + + /** + * Allows attester to revoke an attestation by signing an `RevocationRequest` (`ECDSA` or `ERC1271`) + * + * @param attester the signer / revoker + * @param request single RevocationRequest + * @param signature ECDSA or ERC1271 signature + */ + function revoke(address attester, RevocationRequest calldata request, bytes calldata signature) external; + + /** + * Allows attester to revoke an attestation by signing an `RevocationRequest` (`ECDSA` or `ERC1271`) + * @dev if you want to revoke multiple attestations, but from different attesters, call this function multiple times + * + * @param attester the signer / revoker + * @param requests array of RevocationRequests + * @param signature ECDSA or ERC1271 signature + */ + function revoke(address attester, RevocationRequest[] calldata requests, bytes calldata signature) external; + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* Module Registration */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + // Event triggered when a module is deployed. + event ModuleRegistration(address indexed implementation); + + error AlreadyRegistered(address module); + error InvalidDeployment(); + error ModuleAddressIsNotContract(address moduleAddress); + error FactoryCallFailed(address factory); + + /** + * Module Developers can deploy their module Bytecode directly via the registry. + * This registry implements a `CREATE2` factory, that allows module developers to register and deploy module bytecode + * @param salt The salt to be used in the `CREATE2` factory. This adheres to Pr000xy/Create2Factory.sol salt formatting. + * The salt's first bytes20 should be the address of the sender + * or bytes20(0) to bypass the check (this will lose replay protection) + * @param resolverUID The resolverUID to be used in the `CREATE2` factory + * @param initCode The initCode to be used in the `CREATE2` factory + * @param metadata The metadata to be stored on the registry. + * This field is optional, and might be used by the module developer to store additional + * information about the module or facilitate business logic with the Resolver stub + * @param resolverContext bytes that will be passed to the resolver contract + */ + function deployModule( + bytes32 salt, + ResolverUID resolverUID, + bytes calldata initCode, + bytes calldata metadata, + bytes calldata resolverContext + ) + external + payable + returns (address moduleAddress); + + /** + * In order to make the integration into existing business logics possible, + * the Registry is able to utilize external factories that can be utilized to deploy the modules. + * @dev Registry can use other factories to deploy the module. + * @dev Note that this function will call the external factory via the FactoryTrampoline contract. + * Factory MUST not assume that msg.sender == registry + * @dev This function is used to deploy and register a module using a factory contract. + * Since one of the parameters of this function is a unique resolverUID and any + * registered module address can only be registered once, + * using this function is of risk for a frontrun attack + */ + function deployViaFactory( + address factory, + bytes calldata callOnFactory, + bytes calldata metadata, + ResolverUID resolverUID, + bytes calldata resolverContext + ) + external + payable + returns (address moduleAddress); + + /** + * Already deployed module addresses can be registered on the registry + * @dev This function is used to deploy and register an already deployed module. + * Since one of the parameters of this function is a unique resolverUID and any + * registered module address can only be registered once, + * using this function is of risk for a frontrun attack + * @dev the sender address of this registration is set to address(0) since anyone can invoke this function + * @param resolverUID The resolverUID to be used for the module + * @param moduleAddress The address of the module to be registered + * @param metadata The metadata to be stored on the registry. + * This field is optional, and might be used by the module developer to store additional + * information about the module or facilitate business logic with the Resolver stub + * @param resolverContext bytes that will be passed to the resolver contract + */ + function registerModule( + ResolverUID resolverUID, + address moduleAddress, + bytes calldata metadata, + bytes calldata resolverContext + ) + external; + + /** + * in conjunction with the deployModule() function, this function let's you + * predict the address of a CREATE2 module deployment + * @param salt CREATE2 salt + * @param initCode module initcode + * @return moduleAddress counterfactual address of the module deployment + */ + function calcModuleAddress(bytes32 salt, bytes calldata initCode) external view returns (address); + + /** + * Getter function to get the stored ModuleRecord for a specific module address. + * @param moduleAddress The address of the module + */ + function findModule(address moduleAddress) external view returns (ModuleRecord memory moduleRecord); + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* Manage Schemas */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + event SchemaRegistered(SchemaUID indexed uid, address indexed registerer); + + error SchemaAlreadyExists(SchemaUID uid); + + error InvalidSchema(); + error InvalidSchemaValidator(IExternalSchemaValidator validator); + + /** + * Register Schema and (optional) external `IExternalSchemaValidator` + * A Schema describe the structure of the data of attestations + * every attestation made on this registry, will reference a SchemaUID to + * make it possible to decode attestation data in human readable form + * overwriting a schema is not allowed, and will revert + * @param schema ABI schema used to encode attestations that are made with this schema + * @param validator (optional) external schema validator that will be used to validate attestations. + * use address(0), if you don't need an external validator + * @return uid SchemaUID of the registered schema + */ + function registerSchema( + string calldata schema, + IExternalSchemaValidator validator // OPTIONAL + ) + external + returns (SchemaUID uid); + + /** + * Getter function to retrieve SchemaRecord + */ + function findSchema(SchemaUID uid) external view returns (SchemaRecord memory record); + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* Manage Resolvers */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + event NewResolver(ResolverUID indexed uid, address indexed resolver); + event NewResolverOwner(ResolverUID indexed uid, address newOwner); + + error ResolverAlreadyExists(); + + /** + * Allows Marketplace Agents to register external resolvers. + * @param resolver external resolver contract + * @return uid ResolverUID of the registered resolver + */ + function registerResolver(IExternalResolver resolver) external returns (ResolverUID uid); + + /** + * Entities that previously registered an external resolver, may update the implementation address. + * @param uid The UID of the resolver. + * @param resolver The new resolver implementation address. + */ + function setResolver(ResolverUID uid, IExternalResolver resolver) external; + + /** + * Transfer ownership of resolverUID to a new address + * @param uid The UID of the resolver to transfer ownership for + * @param newOwner The address of the new owner + */ + function transferResolverOwnership(ResolverUID uid, address newOwner) external; + + /** + * Getter function to get the ResolverRecord of a registered resolver + * @param uid The UID of the resolver. + */ + function findResolver(ResolverUID uid) external view returns (ResolverRecord memory record); + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* Stub Errors */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + error ExternalError_SchemaValidation(); + error ExternalError_ResolveAttestation(); + error ExternalError_ResolveRevocation(); + error ExternalError_ModuleRegistration(); +} diff --git a/rhinestone/registry/1.0.0/node_modules/@rhinestone/registry/src/Registry.sol b/rhinestone/registry/1.0.0/node_modules/@rhinestone/registry/src/Registry.sol new file mode 100644 index 0000000..c28b681 --- /dev/null +++ b/rhinestone/registry/1.0.0/node_modules/@rhinestone/registry/src/Registry.sol @@ -0,0 +1,77 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.24; + +import { SignedAttestation } from "./core/SignedAttestation.sol"; +import { IRegistry } from "./IRegistry.sol"; + +/** + * ## Overview + * + * Account abstraction (or smart accounts) will deliver three key enhancements for the Ethereum ecosystem: + * improved UX, enhanced user security and greater wallet extensibility. Modular smart accounts are the next + * frontier for achieving these goals. However, it also opens up a number of new challenges that + * could drastically undermine the objective by opening up a plethora of new attack vectors and security concerns for accounts. + * + * The Registry aims to solve this concern by providing a means of verifying the legitimacy and + * security of independently built smart account modules for installation and use across any integrated + * smart account. It allows entities to attest to statements about modules and smart accounts to query these + * at module installation and/or execution time. The Registry is a Singleton that is free, open and + * permissionless. It also serves as the reference implementation + * for [ERC-7484](https://eips.ethereum.org/EIPS/eip-7484). + * ## Core Concepts + * ### Attestations + * Attestations on the Registry represent statements about Modules. An Attestation is made using a + * particular [Schema](./Schemas.md) that is used to encode and decode the Attestation data. The + * most important usecase for Attestations is to make statements about the security of a Module. + * + * An attestation consists of two primary elements: the Schema and the + * Attestation data. The Schema acts as a standardized structure for + * creating and validating Attestations, defining how the Attestation data is encoded and decoded. + * + * ### Schemas + * + * [Schemas](./docs/Schema.md) represent predefined structures utilized for the formation and + * verification of Attestation data. Using flexible Schemas rather than a single, fixed Schema + * allows Attesters to encode their data in a custom way, providing flexibility when creating + * Attestations. For example, the data of an Attestation about the outcome of the formal + * verification on a Module will have a very format than the data of an Attestation about what + * interfaces a Module supports. + * + * ### Resolvers + * + * Resolvers are external contracts that are tied to Modules and called when specific Registry actions are executed. These actions are: + * - attestation + * - revocation + * - module registration / deployment + * + * This architectural design aims to provide entities like Smart Account vendors or DAOs, with the + * flexibility to incorporate custom business logic while maintaining the + * robustness and security of the core functionalities implemented by the Registry + * + * ### Attesters + * Attesters are individuals or organizations responsible for + * creating and signing Attestations. They add the Attestation to the + * Registry, making it available for verification. + * + * ### Modules + * Modules are smart contracts that act as modular components that can be added to Smart Accounts. + * The registry is agnostic towards Smart Account or Module implementations. Only Module addresses and + * deployment metadata are stored on the registry. + * + * Modules are registered on the Registry either during, using `CREATE2`, `CREATE3` + * or a custom deployment factory, or after deployment. + * + * ## Architecture + * + * ![Sequence Diagram](https://raw.githubusercontent.com/rhinestonewtf/registry/main/public/docs/all.svg) + * + * Implementation of all features of the registry: + * - Register Schemas + * - Register External Resolvers + * - Register Modules + * - Make Attestations + * - Make Revocations + * - Delegate Trust to Attester(s) + * @author rhinestone | zeroknots.eth, Konrad Kopp (@kopy-kat) + */ +contract Registry is IRegistry, SignedAttestation { } diff --git a/rhinestone/registry/1.0.0/node_modules/@rhinestone/registry/src/core/Attestation.sol b/rhinestone/registry/1.0.0/node_modules/@rhinestone/registry/src/core/Attestation.sol new file mode 100644 index 0000000..5347aaa --- /dev/null +++ b/rhinestone/registry/1.0.0/node_modules/@rhinestone/registry/src/core/Attestation.sol @@ -0,0 +1,115 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.24; + +import { AttestationRecord, AttestationRequest, RevocationRequest, SchemaUID } from "../DataTypes.sol"; +import { AttestationManager } from "./AttestationManager.sol"; +import { IRegistry } from "../IRegistry.sol"; + +/** + * Abstract contract that implements the `IRegistry` interface + * + * Attestations on the Registry represent statements about Modules. + * An Attestation is made using a particular Schema that is used to encode and decode the Attestation data. + * The most important usecase for Attestations is to make statements about the security of a Module. + * + * ## Attestation Lifecycle + * + * When an Attester creates an Attestation, the Attestation data, structured according to the Schema provided + * by the Attester, is then added to the Registry. During the Attestation's lifecycle, the Registry can invoke + * hooks on the SchemaResolver during specific events like Attestation creation and revocation. + * This allows the SchemaResolver to ensure the integrity and correctness of the attestation throughout + * its lifecycle. + * + * ### AttestationRequest + * data is `abi.encode()` according to a defined schema. The data is not stored in the storage of the Registry, + * but is rather stored with `SSTORE2` to save gas and a pointer to this data is stored on the Registry. + * + * ![Sequence Diagram](public/docs/attestationOnly.svg) + * + * ### Interactions with the SchemaValidator + * + * Attestation data can be validated with an external contract than may to `abi.decode()` and validate all or specific fields. + * + * ### Interaction with the IExternalResolver + * + * Upon an Attestation's revocation, the Registry calls hooks on the associated IResolver, allowing the IResolver to + * update its internal state or + * perform other necessary actions. This allows for extended business logic integrations. + * + * ### The Revocation Process + * + * In the event that an Attester decides to revoke an Attestation, they issue a revocation call to the Registry. + * Upon receiving this call, the registry updates the revocationTime field within the attestation record. + * This timestamp acts as a clear indication that the attestation has been revoked, and any trust or claims + * that stem from it should be reconsidered. + * + * It's important to note that apart from the revocationTime, the rest of the attestation's metadata and data + * remains unchanged. + * Due to the nature of `SSTORE2`, all attestation data will remain onchain and thus preserves the history of + * attestations done. + * + * ### Editing Attestations + * + * Attestations can not be edited. Should attestation data change, the old attestation must be revoked and a new attestation issued. + * + * + * - A service opts to cover its users' Attestation costs (taking care of gas expenses) + * - An entity wishes to execute multiple Attestations but allows the recipient or a different party to + * handle the transaction fees for blockchain integration. + * Allows `msg.sender` to make attestations / revocations directly + * @author rhinestone | zeroknots.eth, Konrad Kopp (@kopy-kat) + */ +abstract contract Attestation is IRegistry, AttestationManager { + /** + * @inheritdoc IRegistry + */ + function attest(SchemaUID schemaUID, AttestationRequest calldata request) external { + _attest(msg.sender, schemaUID, request); + } + + /** + * @inheritdoc IRegistry + */ + function attest(SchemaUID schemaUID, AttestationRequest[] calldata requests) external { + _attest(msg.sender, schemaUID, requests); + } + + /** + * @inheritdoc IRegistry + */ + function revoke(RevocationRequest calldata request) external { + _revoke(msg.sender, request); + } + + /** + * @inheritdoc IRegistry + */ + function revoke(RevocationRequest[] calldata requests) external { + _revoke(msg.sender, requests); + } + + /** + * @inheritdoc IRegistry + */ + function findAttestation(address module, address attester) external view returns (AttestationRecord memory attestation) { + attestation = $getAttestation(module, attester); + } + + /** + * @inheritdoc IRegistry + */ + function findAttestations( + address module, + address[] calldata attesters + ) + external + view + returns (AttestationRecord[] memory attestations) + { + uint256 length = attesters.length; + attestations = new AttestationRecord[](length); + for (uint256 i; i < length; i++) { + attestations[i] = $getAttestation(module, attesters[i]); + } + } +} diff --git a/rhinestone/registry/1.0.0/node_modules/@rhinestone/registry/src/core/AttestationManager.sol b/rhinestone/registry/1.0.0/node_modules/@rhinestone/registry/src/core/AttestationManager.sol new file mode 100644 index 0000000..6ba1e93 --- /dev/null +++ b/rhinestone/registry/1.0.0/node_modules/@rhinestone/registry/src/core/AttestationManager.sol @@ -0,0 +1,245 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.24; + +import { + AttestationDataRef, + AttestationRecord, + AttestationRequest, + ModuleType, + ResolverUID, + RevocationRequest, + SchemaUID +} from "../DataTypes.sol"; +import { SchemaManager } from "./SchemaManager.sol"; +import { ModuleManager } from "./ModuleManager.sol"; +import { TrustManagerExternalAttesterList } from "./TrustManagerExternalAttesterList.sol"; +import { StubLib } from "../lib/StubLib.sol"; +import { AttestationLib } from "../lib/AttestationLib.sol"; +import { ModuleTypeLib } from "../lib/ModuleTypeLib.sol"; +import { IRegistry } from "../IRegistry.sol"; + +import { EMPTY_ATTESTATION_REF, EMPTY_RESOLVER_UID, _time, ZERO_TIMESTAMP } from "../Common.sol"; + +/** + * AttestationManager handles the registry's internal storage of new attestations and revocation of attestation + * @dev This contract is abstract and provides internal utility functions to store attestations and revocations. + * + * @author rhinestone | zeroknots.eth, Konrad Kopp (@kopy-kat) + */ +abstract contract AttestationManager is IRegistry, ModuleManager, SchemaManager, TrustManagerExternalAttesterList { + using StubLib for *; + using AttestationLib for AttestationDataRef; + using AttestationLib for AttestationRequest; + using AttestationLib for AttestationRequest[]; + using AttestationLib for address; + using ModuleTypeLib for ModuleType[]; + + mapping(address module => mapping(address attester => AttestationRecord attestation)) internal $moduleToAttesterToAttestations; + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* Attestation */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /** + * Processes an attestation request and stores the attestation in the registry. + * If the attestation was made for a module that was not registered, the function will revert. + * function will get the external Schema Validator for the supplied SchemaUID + * and call it, if an external `IExternalSchemaValidator` was set + * function will get the external `IExternalResolver` for the module - that the attestation is for + * and call it, if an external Resolver was set + * @param attester The address of the attesting account. + * @param schemaUID the UID of the schema that the attestation is made for + * @param request AttestationRequest send by attester via `calldata` + */ + function _attest(address attester, SchemaUID schemaUID, AttestationRequest calldata request) internal { + (AttestationRecord memory record, ResolverUID resolverUID) = + _storeAttestation({ schemaUID: schemaUID, attester: attester, request: request }); + + record.requireExternalSchemaValidation({ $schema: schemas[schemaUID] }); + record.requireExternalResolverOnAttestation({ $resolver: $resolvers[resolverUID] }); + } + + /** + * Processes an array of attestation requests and stores the attestations in the registry. + * If the attestation was made for a module that was not registered, the function will revert. + * function will get the external Schema Validator for the supplied SchemaUID + * and call it, if an external IExternalSchemaValidator was set + * function will get the external IExternalResolver for the module - that the attestation is for + * and call it, if an external Resolver was set + */ + function _attest(address attester, SchemaUID schemaUID, AttestationRequest[] calldata requests) internal { + uint256 length = requests.length; + AttestationRecord[] memory records = new AttestationRecord[](length); + // loop will check that the batched attestation is made ONLY for the same resolver + ResolverUID resolverUID; + for (uint256 i; i < length; i++) { + ResolverUID resolverUID_cache; + // save the attestation record into records array. + (records[i], resolverUID_cache) = _storeAttestation({ schemaUID: schemaUID, attester: attester, request: requests[i] }); + // cache the first resolverUID and compare it to the rest + // If the resolverUID is different, revert + // @dev if you want to use different resolvers, make different attestation calls + if (i == 0) resolverUID = resolverUID_cache; + else if (resolverUID_cache != resolverUID) revert DifferentResolvers(); + } + + // Use StubLib to call schema Validation and resolver if needed + records.requireExternalSchemaValidation({ $schema: schemas[schemaUID] }); + records.requireExternalResolverOnAttestation({ $resolver: $resolvers[resolverUID] }); + } + + /** + * Stores an attestation in the registry storage. + * The bytes encoded `AttestationRequest.Data` is not stored directly into the registry storage, + * but rather stored with `SSTORE2`. `SSTORE2/SLOAD2` is writing and reading contract storage + * paying a fraction of the cost, it uses contract code as storage, writing data takes the + * form of contract creations and reading data uses `EXTCODECOPY`. + * since attestation data is supposed to be immutable, it is a good candidate for `SSTORE2` + * + * @dev This function will revert if the same module is attested twice by the same attester. + * If you want to re-attest, you have to revoke your attestation first, and then attest again. + * + * @param attester The address of the attesting account. + * @param request The AttestationRequest that was supplied via calldata + * @return record The AttestationRecord of what was written into registry storage + * @return resolverUID The resolverUID in charge for the module + */ + function _storeAttestation( + SchemaUID schemaUID, + address attester, + AttestationRequest calldata request + ) + internal + returns (AttestationRecord memory record, ResolverUID resolverUID) + { + // if schema behind schemaUID is not registered, revert. + if (schemas[schemaUID].registeredAt == ZERO_TIMESTAMP) revert InvalidSchema(); + + uint48 timeNow = _time(); + // Ensure that either no expiration time was set or that it was set in the future. + if (request.expirationTime != ZERO_TIMESTAMP && request.expirationTime <= timeNow) { + revert InvalidExpirationTime(); + } + // caching module address. + address module = request.moduleAddress; + AttestationRecord storage $record = $getAttestation({ module: module, attester: attester }); + // If the attestation was already made for module, but not revoked, revert. + if ($record.time != ZERO_TIMESTAMP && $record.revocationTime == ZERO_TIMESTAMP) { + revert AlreadyAttested(); + } + // SLOAD the resolverUID from the moduleRecord + resolverUID = $moduleAddrToRecords[module].resolverUID; + // Ensure that attestation is for module that was registered. + if (resolverUID == EMPTY_RESOLVER_UID) { + revert ModuleNotFoundInRegistry(module); + } + + // use SSTORE2 to store the data in attestationRequest + // this will revert, if in a batched attestation, + // the same data is used twice by the same attester for the same module since the salt will be the same + AttestationDataRef sstore2Pointer = request.sstore2({ salt: attester.sstore2Salt(module) }); + + // write into memory allocated record, since that is the return value + record = AttestationRecord({ + time: timeNow, + expirationTime: request.expirationTime, + revocationTime: uint48(ZERO_TIMESTAMP), + moduleTypes: request.moduleTypes.pack(), + schemaUID: schemaUID, + moduleAddress: module, + attester: attester, + dataPointer: sstore2Pointer + }); + // SSTORE attestation to registry storage + $moduleToAttesterToAttestations[module][attester] = record; + + emit Attested({ moduleAddress: module, attester: attester, schemaUID: schemaUID, sstore2Pointer: sstore2Pointer }); + } + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* Revocation */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /** + * Revoke a single Revocation Request + * This function will write the `RevocationRequest` into storage, and get the stored `RevocationRecord` back, + * and pass the `RevocationRecord` to the resolver to check if the revocation is valid + */ + function _revoke(address attester, RevocationRequest calldata request) internal { + (AttestationRecord memory record, ResolverUID resolverUID) = _storeRevocation(attester, request); + record.tryExternalResolverOnRevocation({ $resolver: $resolvers[resolverUID] }); + } + + /** + * Revoke an array Revocation Request + * This function will write the `RevocationRequest` into storage, and get the stored `RevocationRecord` back, + * and pass the `RevocationRecord` to the resolver to check if the revocation is valid + */ + function _revoke(address attester, RevocationRequest[] calldata requests) internal { + uint256 length = requests.length; + AttestationRecord[] memory records = new AttestationRecord[](length); + ResolverUID resolverUID; + // loop over revocation requests. This function will revert if different resolverUIDs + // are responsible for the modules that are subject of the revocation. This is to reduce complexity + // @dev if you want to revoke attestations from different resolvers, make different revocation calls + for (uint256 i; i < length; i++) { + ResolverUID resolverUID_cache; + (records[i], resolverUID_cache) = _storeRevocation(attester, requests[i]); + if (i == 0) resolverUID = resolverUID_cache; + else if (resolverUID_cache != resolverUID) revert DifferentResolvers(); + } + + // No schema validation required during revocation. The attestation data was already checked against + records.tryExternalResolverOnRevocation({ $resolver: $resolvers[resolverUID] }); + } + + /** + * Gets the AttestationRecord for the supplied RevocationRequest and stores the revocation time in the registry storage + * @param revoker The address of the attesting account. + * @param request The AttestationRequest that was supplied via calldata + * @return record The AttestationRecord of what was written into registry storage + * @return resolverUID The resolverUID in charge for the module + */ + function _storeRevocation( + address revoker, + RevocationRequest calldata request + ) + internal + returns (AttestationRecord memory record, ResolverUID resolverUID) + { + AttestationRecord storage $attestation = $moduleToAttesterToAttestations[request.moduleAddress][revoker]; + + // SLOAD entire record. This will later be passed to the resolver + record = $attestation; + resolverUID = $moduleAddrToRecords[request.moduleAddress].resolverUID; + + // Ensure that we aren't attempting to revoke a non-existing attestation. + if (record.dataPointer == EMPTY_ATTESTATION_REF) { + revert AttestationNotFound(); + } + + // Allow only original attesters to revoke their attestations. + if (record.attester != revoker) { + revert AccessDenied(); + } + + // Ensure that we aren't trying to revoke the same attestation twice. + if (record.revocationTime != ZERO_TIMESTAMP) { + revert AlreadyRevoked(); + } + + // SSTORE revocation time to registry storage + // set revocation time to NOW + $attestation.revocationTime = _time(); + + emit Revoked({ moduleAddress: request.moduleAddress, revoker: revoker, schema: record.schemaUID }); + } + + /** + * Returns a storage reference to attestation records for the given module and attesters. + * This function is expected to be used by `TrustManager` and `TrustManagerExternalAttesterList` + */ + function $getAttestation(address module, address attester) internal view override returns (AttestationRecord storage $attestation) { + $attestation = $moduleToAttesterToAttestations[module][attester]; + } +} diff --git a/rhinestone/registry/1.0.0/node_modules/@rhinestone/registry/src/core/ModuleManager.sol b/rhinestone/registry/1.0.0/node_modules/@rhinestone/registry/src/core/ModuleManager.sol new file mode 100644 index 0000000..fce55de --- /dev/null +++ b/rhinestone/registry/1.0.0/node_modules/@rhinestone/registry/src/core/ModuleManager.sol @@ -0,0 +1,219 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.24; + +import { ModuleDeploymentLib } from "../lib/ModuleDeploymentLib.sol"; +import { StubLib } from "../lib/StubLib.sol"; + +import { _isContract, EMPTY_RESOLVER_UID, ZERO_ADDRESS } from "../Common.sol"; +import { ResolverRecord, ModuleRecord, ResolverUID } from "../DataTypes.sol"; +import { ResolverManager } from "./ResolverManager.sol"; +import { IRegistry } from "../IRegistry.sol"; + +/** + * In order to separate msg.sender context from registry, + * interactions with external Factories are done with this Trampoline contract. + */ +contract FactoryTrampoline { + error FactoryCallFailed(address factory); + + /** + * @param factory the address of the factory to call + * @param callOnFactory the call data to send to the factory + * @return moduleAddress the moduleAddress that was returned by the + */ + function deployViaFactory(address factory, bytes memory callOnFactory) external payable returns (address moduleAddress) { + // call external factory to deploy module + bool success; + /* solhint-disable no-inline-assembly */ + assembly ("memory-safe") { + success := call(gas(), factory, callvalue(), add(callOnFactory, 0x20), mload(callOnFactory), 0, 32) + moduleAddress := mload(0) + } + if (!success) { + revert FactoryCallFailed(factory); + } + } +} + +/** + * In order for Attesters to be able to make statements about a Module, the Module first needs to be registered on the Registry. + * This can be done as part of or after Module deployment. On registration, every module is tied to a + * [ResolverManager](../ModuleManager.sol/abstract.ResolverManager.html) that is triggered on certain registry actions. + * + * The ModuleManager contract is responsible for handling module the registration, + * storage and retrieval of modules on the Registry. + * This contract inherits from the `IModule` interface + * The primary responsibility of the Module is to deploy and manage modules. A module is a smart contract + * that has been deployed through the Module. The details of each module, such as its address, resolver UID + * sender address, deploy parameters hash, and additional metadata are stored in + * a struct and mapped to the module's address in + * the `_modules` mapping for easy access and management. + * @dev The module developer select the resolver to be used for attestations and revocations made of the module. + * @dev Important: only module registrations made through the `deployModule()` function are frontrun protected. + * + * @author rhinestone | zeroknots.eth, Konrad Kopp (@kopy-kat) + */ +abstract contract ModuleManager is IRegistry, ResolverManager { + using ModuleDeploymentLib for bytes; + using ModuleDeploymentLib for address; + using StubLib for *; + + mapping(address moduleAddress => ModuleRecord moduleRecord) internal $moduleAddrToRecords; + + FactoryTrampoline private immutable FACTORY_TRAMPOLINE; + + constructor() { + FACTORY_TRAMPOLINE = new FactoryTrampoline(); + } + + /** + * @inheritdoc IRegistry + */ + function deployModule( + bytes32 salt, + ResolverUID resolverUID, + bytes calldata initCode, + bytes calldata metadata, + bytes calldata resolverContext + ) + external + payable + returns (address moduleAddress) + { + ResolverRecord storage $resolver = $resolvers[resolverUID]; + if ($resolver.resolverOwner == ZERO_ADDRESS) revert InvalidResolverUID(resolverUID); + + moduleAddress = initCode.deploy(salt); + // _storeModuleRecord() will check if module is already registered, + // which should prevent reentry to any deploy function + ModuleRecord memory record = + _storeModuleRecord({ moduleAddress: moduleAddress, sender: msg.sender, resolverUID: resolverUID, metadata: metadata }); + + record.requireExternalResolverOnModuleRegistration({ + moduleAddress: moduleAddress, + $resolver: $resolver, + resolverContext: resolverContext + }); + } + + /** + * @inheritdoc IRegistry + */ + function calcModuleAddress(bytes32 salt, bytes calldata initCode) external view returns (address) { + return initCode.calcAddress(salt); + } + + /** + * @inheritdoc IRegistry + */ + function registerModule( + ResolverUID resolverUID, + address moduleAddress, + bytes calldata metadata, + bytes calldata resolverContext + ) + external + { + ResolverRecord storage $resolver = $resolvers[resolverUID]; + + // ensure that non-zero resolverUID was provided + if ($resolver.resolverOwner == ZERO_ADDRESS) revert InvalidResolverUID(resolverUID); + + ModuleRecord memory record = _storeModuleRecord({ + moduleAddress: moduleAddress, + sender: ZERO_ADDRESS, // setting sender to address(0) since anyone can invoke this function + resolverUID: resolverUID, + metadata: metadata + }); + + // resolve module registration + record.requireExternalResolverOnModuleRegistration({ + moduleAddress: moduleAddress, + $resolver: $resolver, + resolverContext: resolverContext + }); + } + + /** + * @inheritdoc IRegistry + */ + function deployViaFactory( + address factory, + bytes calldata callOnFactory, + bytes calldata metadata, + ResolverUID resolverUID, + bytes calldata resolverContext + ) + external + payable + returns (address moduleAddress) + { + ResolverRecord storage $resolver = $resolvers[resolverUID]; + if ($resolver.resolverOwner == ZERO_ADDRESS) revert InvalidResolverUID(resolverUID); + + // prevent someone from calling a registry function pretending its a factory + if (factory == address(this)) revert FactoryCallFailed(factory); + + // Call the factory via the trampoline contract. This will make sure that there is msg.sender separation + // Making "raw" calls to user supplied addresses could create security issues. + moduleAddress = FACTORY_TRAMPOLINE.deployViaFactory{ value: msg.value }({ factory: factory, callOnFactory: callOnFactory }); + + ModuleRecord memory record = _storeModuleRecord({ + moduleAddress: moduleAddress, + sender: ZERO_ADDRESS, // setting sender to address(0) since anyone can invoke this function + resolverUID: resolverUID, + metadata: metadata + }); + + record.requireExternalResolverOnModuleRegistration({ + moduleAddress: moduleAddress, + $resolver: $resolver, + resolverContext: resolverContext + }); + } + + /** + * Turns module registration artifacts to `ModuleRecord` to stores it in the registry storage + * @dev if a non-existent resolverUID is provided, this function reverts. + * @dev if moduleAddress is already registered, this function reverts. + * @dev if moduleAddress is not a contract, this function reverts. + * @param moduleAddress the address of the module to register + * @param sender the address of the sender who deployed the module + * @param resolverUID the unique identifier of the resolver + * @param metadata additional data related to the contract deployment. + * This parameter is optional and may be used to facilitate custom business logic on the external resolver + */ + function _storeModuleRecord( + address moduleAddress, + address sender, + ResolverUID resolverUID, + bytes calldata metadata + ) + internal + returns (ModuleRecord memory moduleRegistration) + { + // ensure moduleAddress is not already registered + if ($moduleAddrToRecords[moduleAddress].resolverUID != EMPTY_RESOLVER_UID) { + revert AlreadyRegistered(moduleAddress); + } + // revert if moduleAddress is NOT a contract + // this should catch address(0) + if (!_isContract(moduleAddress)) revert InvalidDeployment(); + + // Store module metadata in _modules mapping + moduleRegistration = ModuleRecord({ resolverUID: resolverUID, sender: sender, metadata: metadata }); + + // Store module record in _modules mapping + $moduleAddrToRecords[moduleAddress] = moduleRegistration; + + // Emit ModuleRegistration event + emit ModuleRegistration({ implementation: moduleAddress }); + } + + /** + * @inheritdoc IRegistry + */ + function findModule(address moduleAddress) external view returns (ModuleRecord memory moduleRecord) { + return $moduleAddrToRecords[moduleAddress]; + } +} diff --git a/rhinestone/registry/1.0.0/node_modules/@rhinestone/registry/src/core/ResolverManager.sol b/rhinestone/registry/1.0.0/node_modules/@rhinestone/registry/src/core/ResolverManager.sol new file mode 100644 index 0000000..627f2e9 --- /dev/null +++ b/rhinestone/registry/1.0.0/node_modules/@rhinestone/registry/src/core/ResolverManager.sol @@ -0,0 +1,132 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.24; + +import { ResolverRecord, ResolverUID } from "../DataTypes.sol"; +import { ZERO_ADDRESS } from "../Common.sol"; +import { IExternalResolver } from "../external/IExternalResolver.sol"; +import { UIDLib } from "../lib/Helpers.sol"; +import { IRegistry } from "../IRegistry.sol"; + +/** + * Resolvers are external contracts that are tied to Modules and called when specific Registry actions are executed. + * ## Resolver Hooks + * + * external Resolvers are called during: + * + * - attestation + * - revocation + * - module registration + * + * This architectural design aims to provide entities like Smart Account vendors or DAOs, with the + * flexibility to incorporate custom business logic while maintaining the + * robustness and security of the core functionalities implemented by the Registry. + * + * ## Role of Resolvers in extending Registry functionalities + * + * Entities utilizing the Registry frequently need to extend its core functionalities + * to cater to their unique business requirements. Resolvers are the mechanisms that + * make this possible, allowing for: + * + * - _Custom Business Logic Integration:_ Entities can build upon the foundational + * registry operations by introducing complex logic sequences, tailored to their + * operational needs. + * + * - _Security Assurance:_ One of the significant advantages of using resolvers is that + * they abstract away the intricacies of attestation storage and validation. + * This abstraction ensures that the foundational security of the Registry isn't compromised, + * even when new functionalities are added. + * + * - _Cost Efficiency in Audits:_ Given that the core attestation storage and validation + * logic remains untouched, auditing becomes more straightforward and cost-effective. + * Entities can focus their audit efforts on their custom logic without the need to + * re-audit the underlying core systems. + * + * ## The `IExternalResolver` interface: Standardizing Resolvers + * + * For any entity looking to employ a resolver, adherence to a standardized + * interface is essential. + * The [IExternalResolver interface](../../external/IExternalResolver.sol/interface.IExternalResolver.html) delineates the essential + * functions a resolver must implement to ensure seamless integration and operation with the Registry. + * @dev only `msg.sender` and the external `IExternalResolver` address are used to create a unique ID for the resolver + * This allows for a single resolver address to be possible across different chains + * @author rhinestone | zeroknots.eth, Konrad Kopp (@kopy-kat) + */ +abstract contract ResolverManager is IRegistry { + using UIDLib for ResolverRecord; + + mapping(ResolverUID uid => ResolverRecord resolver) internal $resolvers; + + /** + * @dev Modifier to require that the caller is the owner of a resolver + * + * @param uid The UID of the resolver. + */ + modifier onlyResolverOwner(ResolverUID uid) { + if ($resolvers[uid].resolverOwner != msg.sender) { + revert AccessDenied(); + } + _; + } + + /** + * If a resolver is not address(0), we check if it supports the `IExternalResolver` interface + */ + modifier onlyResolver(IExternalResolver resolver) { + if (address(resolver) == address(0) || !resolver.supportsInterface(type(IExternalResolver).interfaceId)) { + revert InvalidResolver(resolver); + } + _; + } + + /** + * @inheritdoc IRegistry + */ + function registerResolver(IExternalResolver resolver) external onlyResolver(resolver) returns (ResolverUID uid) { + // build a ResolverRecord from the input + ResolverRecord memory resolverRecord = ResolverRecord({ resolver: resolver, resolverOwner: msg.sender }); + + // Computing a unique ID for the schema using its properties + uid = resolverRecord.getUID(); + + // Checking if a schema with this UID already exists -> resolver can never be ZERO_ADDRESS + if (address($resolvers[uid].resolver) != ZERO_ADDRESS) { + revert ResolverAlreadyExists(); + } + + // SSTORE schema in the resolvers mapping + $resolvers[uid] = resolverRecord; + + emit NewResolver(uid, address(resolver)); + } + + /** + * @inheritdoc IRegistry + */ + function setResolver( + ResolverUID uid, + IExternalResolver resolver + ) + external + onlyResolver(resolver) + onlyResolverOwner(uid) // authorization control + { + ResolverRecord storage referrer = $resolvers[uid]; + referrer.resolver = resolver; + emit NewResolver(uid, address(resolver)); + } + + /** + * @inheritdoc IRegistry + */ + function transferResolverOwnership(ResolverUID uid, address newOwner) external onlyResolverOwner(uid) { + $resolvers[uid].resolverOwner = newOwner; + emit NewResolverOwner(uid, newOwner); + } + + /** + * @inheritdoc IRegistry + */ + function findResolver(ResolverUID uid) external view returns (ResolverRecord memory) { + return $resolvers[uid]; + } +} diff --git a/rhinestone/registry/1.0.0/node_modules/@rhinestone/registry/src/core/SchemaManager.sol b/rhinestone/registry/1.0.0/node_modules/@rhinestone/registry/src/core/SchemaManager.sol new file mode 100644 index 0000000..7d989e6 --- /dev/null +++ b/rhinestone/registry/1.0.0/node_modules/@rhinestone/registry/src/core/SchemaManager.sol @@ -0,0 +1,68 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.24; + +import { SchemaRecord, SchemaUID } from "../DataTypes.sol"; +import { IExternalSchemaValidator } from "../external/IExternalSchemaValidator.sol"; +import { UIDLib } from "../lib/Helpers.sol"; + +import { ZERO_TIMESTAMP, _time } from "../Common.sol"; +import { IRegistry } from "../IRegistry.sol"; + +/** + * Allows the creation registration and creation of schemas. + * Schemas are used to describe attestation data. It is recommended to use ABI encoded data as schema. + * An external schema validator contract may be provided, + * which will be called for every attestation made against the schema. + * arbitrary checks may be implemented, by SchemaValidators + * + * @author rhinestone | zeroknots.eth, Konrad Kopp (@kopy-kat) + */ +abstract contract SchemaManager is IRegistry { + using UIDLib for SchemaRecord; + + // The global mapping between schema records and their IDs. + mapping(SchemaUID uid => SchemaRecord schemaRecord) internal schemas; + + /** + * If a validator is not address(0), we check if it supports the IExternalSchemaValidator interface + */ + modifier onlySchemaValidator(IExternalSchemaValidator validator) { + if (address(validator) != address(0) && !validator.supportsInterface(type(IExternalSchemaValidator).interfaceId)) { + revert InvalidSchemaValidator(validator); + } + _; + } + + /** + * @inheritdoc IRegistry + */ + function registerSchema( + string calldata schema, + IExternalSchemaValidator validator + ) + external + onlySchemaValidator(validator) + returns (SchemaUID uid) + { + SchemaRecord memory schemaRecord = SchemaRecord({ validator: validator, registeredAt: _time(), schema: schema }); + + // Computing a unique ID for the schema using its properties + uid = schemaRecord.getUID(); + + // check if schema with this UID already exists. + // overwriting a schema is not allowed + if (schemas[uid].registeredAt != ZERO_TIMESTAMP) revert SchemaAlreadyExists(uid); + + // Storing schema in the _schemas mapping + schemas[uid] = schemaRecord; + + emit SchemaRegistered(uid, msg.sender); + } + + /** + * @inheritdoc IRegistry + */ + function findSchema(SchemaUID uid) external view override returns (SchemaRecord memory) { + return schemas[uid]; + } +} diff --git a/rhinestone/registry/1.0.0/node_modules/@rhinestone/registry/src/core/SignedAttestation.sol b/rhinestone/registry/1.0.0/node_modules/@rhinestone/registry/src/core/SignedAttestation.sol new file mode 100644 index 0000000..b47a493 --- /dev/null +++ b/rhinestone/registry/1.0.0/node_modules/@rhinestone/registry/src/core/SignedAttestation.sol @@ -0,0 +1,108 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.24; + +import { Attestation } from "./Attestation.sol"; +import { AttestationRequest, RevocationRequest, SchemaUID } from "../DataTypes.sol"; +import { AttestationLib } from "../lib/AttestationLib.sol"; +import { EIP712 } from "solady/utils/EIP712.sol"; +import { SignatureCheckerLib } from "solady/utils/SignatureCheckerLib.sol"; +import { IRegistry } from "../IRegistry.sol"; + +/** + * Implements similar functionality to Attestation.sol, but with the added feature of requiring a signature from the attester. + * + * ## Signed Attestations + * + * All Attestations leveraged within the Registry are designated as "signed/delegated". + * Such Attestations empower an entity to sign an attestation while enabling another entity to + * bear the transaction cost. With these attestations, the actual Attester and the one relaying the + * Attestation can be separate entities, thus accommodating a variety of use cases. + * This becomes particularly beneficial when: + * Signatures may be provided as `ECDSA` or `ERC-1271` + * + * @author rhinestone | zeroknots.eth, Konrad Kopp (@kopy-kat) + */ +contract SignedAttestation is IRegistry, Attestation, EIP712 { + using AttestationLib for AttestationRequest; + using AttestationLib for RevocationRequest; + using AttestationLib for AttestationRequest[]; + using AttestationLib for RevocationRequest[]; + + mapping(address attester => uint256 nonce) public attesterNonce; + + /** + * @inheritdoc IRegistry + */ + function attest(SchemaUID schemaUID, address attester, AttestationRequest calldata request, bytes calldata signature) external { + uint256 nonce = ++attesterNonce[attester]; + bytes32 digest = _hashTypedData(request.hash(nonce)); + bool valid = SignatureCheckerLib.isValidSignatureNow(attester, digest, signature); + if (!valid) revert InvalidSignature(); + + _attest({ attester: attester, schemaUID: schemaUID, request: request }); + } + + /** + * @inheritdoc IRegistry + */ + function attest(SchemaUID schemaUID, address attester, AttestationRequest[] calldata requests, bytes calldata signature) external { + uint256 nonce = ++attesterNonce[attester]; + bytes32 digest = _hashTypedData(requests.hash(nonce)); + bool valid = SignatureCheckerLib.isValidSignatureNow(attester, digest, signature); + if (!valid) revert InvalidSignature(); + + _attest({ attester: attester, schemaUID: schemaUID, requests: requests }); + } + + /** + * @inheritdoc IRegistry + */ + function revoke(address attester, RevocationRequest calldata request, bytes calldata signature) external { + uint256 nonce = ++attesterNonce[attester]; + bytes32 digest = _hashTypedData(request.hash(nonce)); + bool valid = SignatureCheckerLib.isValidSignatureNow(attester, digest, signature); + if (!valid) revert InvalidSignature(); + + _revoke({ attester: attester, request: request }); + } + + /** + * @inheritdoc IRegistry + */ + function revoke(address attester, RevocationRequest[] calldata requests, bytes calldata signature) external { + uint256 nonce = ++attesterNonce[attester]; + bytes32 digest = _hashTypedData(requests.hash(nonce)); + bool valid = SignatureCheckerLib.isValidSignatureNow(attester, digest, signature); + if (!valid) revert InvalidSignature(); + + _revoke({ attester: attester, requests: requests }); + } + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* EIP712 Digest Helpers */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /** + * override thats used by Solady's EIP712 cache (constructor) + */ + function _domainNameAndVersion() internal view virtual override returns (string memory name, string memory version) { + name = "RhinestoneRegistry"; + version = "v1.0"; + } + + function getDigest(AttestationRequest calldata request, address attester) external view returns (bytes32 digest) { + digest = _hashTypedData(request.hash(attesterNonce[attester] + 1)); + } + + function getDigest(AttestationRequest[] calldata requests, address attester) external view returns (bytes32 digest) { + digest = _hashTypedData(requests.hash(attesterNonce[attester] + 1)); + } + + function getDigest(RevocationRequest calldata request, address attester) external view returns (bytes32 digest) { + digest = _hashTypedData(request.hash(attesterNonce[attester] + 1)); + } + + function getDigest(RevocationRequest[] calldata requests, address attester) external view returns (bytes32 digest) { + digest = _hashTypedData(requests.hash(attesterNonce[attester] + 1)); + } +} diff --git a/rhinestone/registry/1.0.0/node_modules/@rhinestone/registry/src/core/TrustManager.sol b/rhinestone/registry/1.0.0/node_modules/@rhinestone/registry/src/core/TrustManager.sol new file mode 100644 index 0000000..2acac78 --- /dev/null +++ b/rhinestone/registry/1.0.0/node_modules/@rhinestone/registry/src/core/TrustManager.sol @@ -0,0 +1,171 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.24; + +import { AttestationRecord, PackedModuleTypes, ModuleType, TrustedAttesterRecord } from "../DataTypes.sol"; +import { ZERO_MODULE_TYPE, ZERO_ADDRESS } from "../Common.sol"; +// solhint-disable-next-line no-unused-import +import { IRegistry, IERC7484 } from "../IRegistry.sol"; +import { ModuleTypeLib } from "../lib/ModuleTypeLib.sol"; +import { TrustLib } from "../lib/TrustLib.sol"; +import { LibSort } from "solady/utils/LibSort.sol"; + +/** + * Allows smart accounts to query the registry for the security status of modules. + * Smart accounts may trust a list of attesters to attest to the security status of + * modules and configure a minimum threshold of how many attestations have to be in place + * to consider a module as "trust worthy" + * Implements `ERC-7484` to query attestations stored in the registry. + * @dev This contract is abstract and provides utility functions to query attestations. + * @author rhinestone | zeroknots.eth, Konrad Kopp (@kopy-kat) + */ +abstract contract TrustManager is IRegistry { + using ModuleTypeLib for PackedModuleTypes; + using TrustLib for AttestationRecord; + using LibSort for address[]; + + error InvalidThreshold(); + + mapping(address account => TrustedAttesterRecord attesters) internal $accountToAttester; + + /** + * @inheritdoc IERC7484 + */ + function trustAttesters(uint8 threshold, address[] calldata attesters) external { + uint256 attestersLength = attesters.length; + + if (!attesters.isSortedAndUniquified()) revert InvalidTrustedAttesterInput(); + + // if attesters array has duplicates or is too large revert + if (attestersLength == 0 || attestersLength > type(uint8).max) revert InvalidTrustedAttesterInput(); + if (attesters.length != attestersLength) revert InvalidTrustedAttesterInput(); + // revert if the first attester is the zero address + // other attesters can not be zero address, as the array was sorted + if (attesters[0] == ZERO_ADDRESS) revert InvalidTrustedAttesterInput(); + + TrustedAttesterRecord storage $trustedAttester = $accountToAttester[msg.sender]; + + // threshold cannot be greater than the number of attesters + if (threshold > attestersLength) { + revert InvalidThreshold(); + } + + $trustedAttester.attesterCount = uint8(attestersLength); + $trustedAttester.threshold = threshold; + $trustedAttester.attester = attesters[0]; + + attestersLength--; + + // setup the linked list of trusted attesters + for (uint256 i; i < attestersLength; i++) { + address _attester = attesters[i]; + $trustedAttester.linkedAttesters[_attester][msg.sender] = attesters[i + 1]; + } + + emit NewTrustedAttesters(msg.sender); + } + + /** + * @inheritdoc IERC7484 + */ + function check(address module) external view { + _check(msg.sender, module, ZERO_MODULE_TYPE); + } + + /** + * @inheritdoc IERC7484 + */ + function checkForAccount(address smartAccount, address module) external view { + _check(smartAccount, module, ZERO_MODULE_TYPE); + } + + /** + * @inheritdoc IERC7484 + */ + function check(address module, ModuleType moduleType) external view { + _check(msg.sender, module, moduleType); + } + + /** + * @inheritdoc IERC7484 + */ + function checkForAccount(address smartAccount, address module, ModuleType moduleType) external view { + _check(smartAccount, module, moduleType); + } + + /** + * Internal helper function to check for module's security attestations on behalf of a Smart Account + * will use registry's storage to get the trusted attester(s) of a smart account, and check if the module was attested + * @param smartAccount the smart account to check for + * @param module address of the module to check + * @param moduleType (optional param), setting moduleType = 0, will ignore moduleTypes in attestations + */ + function _check(address smartAccount, address module, ModuleType moduleType) internal view { + TrustedAttesterRecord storage $trustedAttesters = $accountToAttester[smartAccount]; + + // SLOAD from one slot + uint256 attesterCount = $trustedAttesters.attesterCount; + uint256 threshold = $trustedAttesters.threshold; + address attester = $trustedAttesters.attester; + + // smart account has no trusted attesters set + if (attester == ZERO_ADDRESS || threshold == 0) { + revert NoTrustedAttestersFound(); + } + // smart account only has ONE trusted attester + // use this condition to save gas + else if (threshold == 1) { + AttestationRecord storage $attestation = $getAttestation({ module: module, attester: attester }); + if ($attestation.checkValid(moduleType)) return; + + // if first attestation is not valid, iterate over the linked list of attesters + // and check if the attestation is valid + for (uint256 i = 1; i < attesterCount; i++) { + attester = $trustedAttesters.linkedAttesters[attester][smartAccount]; + $attestation = $getAttestation({ module: module, attester: attester }); + if ($attestation.checkValid(moduleType)) return; + } + // if no valid attestations were found in the for loop. the module is not valid + revert InsufficientAttestations(); + } + // smart account has more than one trusted attester + else { + // loop though list and check if the attestation is valid + AttestationRecord storage $attestation = $getAttestation({ module: module, attester: attester }); + if ($attestation.checkValid(moduleType)) threshold--; + + for (uint256 i = 1; i < attesterCount; i++) { + // get next attester from linked List + attester = $trustedAttesters.linkedAttesters[attester][smartAccount]; + $attestation = $getAttestation({ module: module, attester: attester }); + if ($attestation.checkValid(moduleType)) threshold--; + + // if threshold reached, exit loop + if (threshold == 0) return; + } + if (threshold > 0) revert InsufficientAttestations(); + } + } + + /** + * @inheritdoc IRegistry + */ + function findTrustedAttesters(address smartAccount) public view returns (address[] memory attesters) { + TrustedAttesterRecord storage $trustedAttesters = $accountToAttester[smartAccount]; + + uint256 count = $trustedAttesters.attesterCount; + address attester0 = $trustedAttesters.attester; + + // return if no trusted attesters are set + if (count == 0) return attesters; + + attesters = new address[](count); + attesters[0] = attester0; + + for (uint256 i = 1; i < count; i++) { + // get next attester from linked List + attesters[i] = $trustedAttesters.linkedAttesters[attesters[i - 1]][smartAccount]; + } + } + + function $getAttestation(address module, address attester) internal view virtual returns (AttestationRecord storage $attestation); +} diff --git a/rhinestone/registry/1.0.0/node_modules/@rhinestone/registry/src/core/TrustManagerExternalAttesterList.sol b/rhinestone/registry/1.0.0/node_modules/@rhinestone/registry/src/core/TrustManagerExternalAttesterList.sol new file mode 100644 index 0000000..f3d88db --- /dev/null +++ b/rhinestone/registry/1.0.0/node_modules/@rhinestone/registry/src/core/TrustManagerExternalAttesterList.sol @@ -0,0 +1,68 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.24; + +import { AttestationRecord, ModuleType } from "../DataTypes.sol"; +import { ZERO_MODULE_TYPE } from "../Common.sol"; +// solhint-disable-next-line no-unused-import +import { IRegistry, IERC7484 } from "../IRegistry.sol"; +import { TrustManager } from "./TrustManager.sol"; +import { TrustLib } from "../lib/TrustLib.sol"; + +/** + * If smart accounts want to query the registry, and supply a list of trusted attesters in calldata, this component can be used + * @dev This contract is abstract and provides utility functions to query attestations with a calldata provided list of attesters + * + * @author rhinestone | zeroknots.eth, Konrad Kopp (@kopy-kat) + */ +abstract contract TrustManagerExternalAttesterList is IRegistry, TrustManager { + using TrustLib for AttestationRecord; + + /** + * @inheritdoc IERC7484 + */ + function check(address module, address[] calldata attesters, uint256 threshold) external view { + uint256 attestersLength = attesters.length; + if (attestersLength == 0 || threshold == 0) { + revert NoTrustedAttestersFound(); + } else if (attestersLength < threshold) { + revert InsufficientAttestations(); + } + + address _attesterCache; + for (uint256 i; i < attestersLength; ++i) { + address attester = attesters[i]; + if (attester <= _attesterCache) revert InvalidTrustedAttesterInput(); + else _attesterCache = attester; + if ($getAttestation(module, attester).checkValid(ZERO_MODULE_TYPE)) { + --threshold; + } + if (threshold == 0) return; + } + revert InsufficientAttestations(); + } + + /** + * @inheritdoc IERC7484 + */ + function check(address module, ModuleType moduleType, address[] calldata attesters, uint256 threshold) external view { + uint256 attestersLength = attesters.length; + if (attestersLength == 0 || threshold == 0) { + revert NoTrustedAttestersFound(); + } else if (attestersLength < threshold) { + revert InsufficientAttestations(); + } + + address _attesterCache; + for (uint256 i; i < attestersLength; ++i) { + address attester = attesters[i]; + + if (attester <= _attesterCache) revert InvalidTrustedAttesterInput(); + else _attesterCache = attester; + if ($getAttestation(module, attester).checkValid(moduleType)) { + --threshold; + } + if (threshold == 0) return; + } + revert InsufficientAttestations(); + } +} diff --git a/rhinestone/registry/1.0.0/node_modules/@rhinestone/registry/src/external/IExternalResolver.sol b/rhinestone/registry/1.0.0/node_modules/@rhinestone/registry/src/external/IExternalResolver.sol new file mode 100644 index 0000000..e4ca6a8 --- /dev/null +++ b/rhinestone/registry/1.0.0/node_modules/@rhinestone/registry/src/external/IExternalResolver.sol @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.24; + +import { AttestationRecord, ModuleRecord } from "../DataTypes.sol"; +import { IERC165 } from "forge-std/interfaces/IERC165.sol"; + +/** + * @title The interface of an optional schema resolver. + * @dev The resolver is responsible for validating the schema and attestation data. + * @dev The resolver is also responsible for processing the attestation and revocation requests. + * + */ +interface IExternalResolver is IERC165 { + /** + * @dev Processes an attestation and verifies whether it's valid. + * + * @param attestation The new attestation. + * + * @return Whether the attestation is valid. + */ + function resolveAttestation(AttestationRecord calldata attestation) external payable returns (bool); + + function resolveAttestation(AttestationRecord[] calldata attestation) external payable returns (bool); + + /** + * @dev Processes an attestation revocation and verifies if it can be revoked. + * + * @param attestation The existing attestation to be revoked. + * + * @return Whether the attestation can be revoked. + */ + function resolveRevocation(AttestationRecord calldata attestation) external payable returns (bool); + function resolveRevocation(AttestationRecord[] calldata attestation) external payable returns (bool); + + /** + * @dev Processes a Module Registration + * + * @param sender The msg.sender of the module registration + * @param moduleAddress address of the module + * @param record Module registration artefact + * + * @return Whether the registration is valid + */ + function resolveModuleRegistration( + address sender, + address moduleAddress, + ModuleRecord calldata record, + bytes calldata resolverContext + ) + external + payable + returns (bool); +} diff --git a/rhinestone/registry/1.0.0/node_modules/@rhinestone/registry/src/external/IExternalSchemaValidator.sol b/rhinestone/registry/1.0.0/node_modules/@rhinestone/registry/src/external/IExternalSchemaValidator.sol new file mode 100644 index 0000000..96c722c --- /dev/null +++ b/rhinestone/registry/1.0.0/node_modules/@rhinestone/registry/src/external/IExternalSchemaValidator.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.24; + +import { AttestationRecord } from "../DataTypes.sol"; +import { IERC165 } from "forge-std/interfaces/IERC165.sol"; + +/** + * @title The interface of an optional schema resolver. + */ +interface IExternalSchemaValidator is IERC165 { + /** + * @notice Validates an attestation request. + */ + function validateSchema(AttestationRecord calldata attestation) external returns (bool); + + /** + * @notice Validates an array of attestation requests. + */ + function validateSchema(AttestationRecord[] calldata attestations) external returns (bool); +} diff --git a/rhinestone/registry/1.0.0/node_modules/@rhinestone/registry/src/interfaces/IERC7484.sol b/rhinestone/registry/1.0.0/node_modules/@rhinestone/registry/src/interfaces/IERC7484.sol new file mode 100644 index 0000000..eb0c129 --- /dev/null +++ b/rhinestone/registry/1.0.0/node_modules/@rhinestone/registry/src/interfaces/IERC7484.sol @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +import { ModuleType } from "../DataTypes.sol"; + +interface IERC7484 { + event NewTrustedAttesters(address indexed smartAccount); + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* Check with Registry internal attesters */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + function check(address module) external view; + + function checkForAccount(address smartAccount, address module) external view; + + function check(address module, ModuleType moduleType) external view; + + function checkForAccount(address smartAccount, address module, ModuleType moduleType) external view; + + /** + * Allows Smart Accounts - the end users of the registry - to appoint + * one or many attesters as trusted. + * @dev this function reverts, if address(0), or duplicates are provided in attesters[] + * + * @param threshold The minimum number of attestations required for a module + * to be considered secure. + * @param attesters The addresses of the attesters to be trusted. + */ + function trustAttesters(uint8 threshold, address[] calldata attesters) external; + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* Check with external attester(s) */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + function check(address module, address[] calldata attesters, uint256 threshold) external view; + + function check(address module, ModuleType moduleType, address[] calldata attesters, uint256 threshold) external view; +} diff --git a/rhinestone/registry/1.0.0/node_modules/@rhinestone/registry/src/lib/AttestationLib.sol b/rhinestone/registry/1.0.0/node_modules/@rhinestone/registry/src/lib/AttestationLib.sol new file mode 100644 index 0000000..bb6bd30 --- /dev/null +++ b/rhinestone/registry/1.0.0/node_modules/@rhinestone/registry/src/lib/AttestationLib.sol @@ -0,0 +1,146 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.24; + +import { AttestationRequest, RevocationRequest, AttestationDataRef } from "../DataTypes.sol"; +import { SSTORE2 } from "solady/utils/SSTORE2.sol"; + +library AttestationLib { + // The hash of the data type used to relay calls to the attest function. It's the value of + bytes32 internal constant ATTEST_REQUEST_TYPEHASH = + keccak256("AttestationRequest(address moduleAddress,uint48 expirationTime,bytes data,uint256[] moduleTypes)"); + // solhint-disable max-line-length + bytes32 internal constant ATTEST_TYPEHASH = keccak256( + "SignedAttestationRequest(AttestationRequest request,uint256 nonce)AttestationRequest(address moduleAddress,uint48 expirationTime,bytes data,uint256[] moduleTypes)" + ); + // solhint-disable max-line-length + bytes32 internal constant ATTEST_ARRAY_TYPEHASH = keccak256( + "SignedAttestationRequests(AttestationRequest[] requests,uint256 nonce)AttestationRequest(address moduleAddress,uint48 expirationTime,bytes data,uint256[] moduleTypes)" + ); + + // The hash of the data type used to relay calls to the revoke function. It's the value of + bytes32 internal constant REVOKE_REQUEST_TYPEHASH = keccak256("RevocationRequest(address moduleAddress)"); + bytes32 internal constant REVOKE_TYPEHASH = + keccak256("SignedRevocationRequest(RevocationRequest request,uint256 nonce)RevocationRequest(address moduleAddress)"); + bytes32 internal constant REVOKE_ARRAY_TYPEHASH = + keccak256("SignedRevocationRequests(RevocationRequest[] requests,uint256 nonce)RevocationRequest(address moduleAddress)"); + + /** + * Helper function to SSTORE2 read an attestation + * @param dataPointer the pointer to the attestation data + * @return data attestation data + */ + function sload2(AttestationDataRef dataPointer) internal view returns (bytes memory data) { + data = SSTORE2.read(AttestationDataRef.unwrap(dataPointer)); + } + + /** + * Helper function to SSTORE2 write an attestation + * @param request the attestation request + * @param salt the salt to use for the deterministic address generation + * @return dataPointer the pointer to the attestation data + */ + function sstore2(AttestationRequest calldata request, bytes32 salt) internal returns (AttestationDataRef dataPointer) { + /** + * @dev We are using CREATE2 to deterministically generate the address of the attestation data. + * Checking if an attestation pointer already exists, would cost more GAS in the average case. + */ + dataPointer = AttestationDataRef.wrap(SSTORE2.writeDeterministic(request.data, salt)); + } + + /** + * Create salt for SSTORE2. + * The salt is constructed out of: + * - attester address + * - module address + * - current timestamp + * - chain id + * @param attester the attester address + * @param module the module address + * @return salt the salt + */ + function sstore2Salt(address attester, address module) internal view returns (bytes32 salt) { + salt = keccak256(abi.encodePacked(attester, module, block.timestamp, block.chainid)); + } + + /** + * generate hash for EIP712 for one attestation request + * @param request attestation request + * @param nonce the nonce for attestation request + * @return _hash the hash + */ + function hash(AttestationRequest calldata request, uint256 nonce) internal pure returns (bytes32 _hash) { + _hash = keccak256( + abi.encode( + ATTEST_TYPEHASH, + keccak256( + abi.encode( + ATTEST_REQUEST_TYPEHASH, + request.moduleAddress, + request.expirationTime, + keccak256(request.data), + keccak256(abi.encodePacked(request.moduleTypes)) + ) + ), + nonce + ) + ); + } + + /** + * generate hash for EIP712 for multiple attestation requests + * @param requests attestation request + * @param nonce the nonce for attestation request + * @return _hash the hash + */ + function hash(AttestationRequest[] calldata requests, uint256 nonce) internal pure returns (bytes32 _hash) { + bytes memory concatinatedAttestations; + + uint256 length = requests.length; + for (uint256 i; i < length; i++) { + concatinatedAttestations = abi.encodePacked( + concatinatedAttestations, // concat previous + keccak256( + abi.encode( + ATTEST_REQUEST_TYPEHASH, + requests[i].moduleAddress, + requests[i].expirationTime, + keccak256(requests[i].data), + keccak256(abi.encodePacked(requests[i].moduleTypes)) + ) + ) + ); + } + + _hash = keccak256(abi.encode(ATTEST_ARRAY_TYPEHASH, keccak256(concatinatedAttestations), nonce)); + } + + /** + * generate hash for EIP712 for one revocation request + * @param request attestation request + * @param nonce the nonce for attestation request + * @return _hash the hash + */ + function hash(RevocationRequest calldata request, uint256 nonce) internal pure returns (bytes32 _hash) { + _hash = keccak256(abi.encode(REVOKE_TYPEHASH, keccak256(abi.encode(REVOKE_REQUEST_TYPEHASH, request.moduleAddress)), nonce)); + } + + /** + * generate hash for EIP712 for multiple revocation requests + * @param requests attestation request + * @param nonce the nonce for attestation request + * @return _hash the hash + */ + function hash(RevocationRequest[] calldata requests, uint256 nonce) internal pure returns (bytes32 _hash) { + bytes memory concatinatedAttestations; + + uint256 length = requests.length; + for (uint256 i; i < length; i++) { + concatinatedAttestations = abi.encodePacked( + concatinatedAttestations, // concat previous + keccak256(abi.encode(REVOKE_REQUEST_TYPEHASH, requests[i].moduleAddress)) + ); + } + + _hash = keccak256(abi.encode(REVOKE_ARRAY_TYPEHASH, keccak256(concatinatedAttestations), nonce)); + } +} diff --git a/rhinestone/registry/1.0.0/node_modules/@rhinestone/registry/src/lib/Helpers.sol b/rhinestone/registry/1.0.0/node_modules/@rhinestone/registry/src/lib/Helpers.sol new file mode 100644 index 0000000..86d8d15 --- /dev/null +++ b/rhinestone/registry/1.0.0/node_modules/@rhinestone/registry/src/lib/Helpers.sol @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.24; + +import { ResolverRecord, SchemaRecord, SchemaUID, ResolverUID } from "../DataTypes.sol"; + +library UIDLib { + /** + * @dev Calculates a UID for a given schema. + * + * @param schemaRecord The input schema. + * + * @return schema UID. + */ + function getUID(SchemaRecord memory schemaRecord) internal view returns (SchemaUID) { + return SchemaUID.wrap(keccak256(abi.encodePacked(msg.sender, schemaRecord.schema, address(schemaRecord.validator)))); + } + + /** + * @dev Calculates a UID for a given resolver. + * + * @param resolver The input schema. + * + * @return ResolverUID. + */ + function getUID(ResolverRecord memory resolver) internal view returns (ResolverUID) { + return ResolverUID.wrap(keccak256(abi.encodePacked(msg.sender, resolver.resolver))); + } +} diff --git a/rhinestone/registry/1.0.0/node_modules/@rhinestone/registry/src/lib/ModuleDeploymentLib.sol b/rhinestone/registry/1.0.0/node_modules/@rhinestone/registry/src/lib/ModuleDeploymentLib.sol new file mode 100644 index 0000000..b9b41b8 --- /dev/null +++ b/rhinestone/registry/1.0.0/node_modules/@rhinestone/registry/src/lib/ModuleDeploymentLib.sol @@ -0,0 +1,76 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.24; + +/** + * @title ModuleDeploymentLib + * @dev A library that can be used to deploy the Registry + * @author zeroknots + */ +library ModuleDeploymentLib { + error InvalidSalt(); + error InvalidAddress(); + // source: https://github.com/0age/metamorphic/blob/master/contracts/ImmutableCreate2Factory.sol#L194-L203 + + modifier containsCaller(bytes32 salt) { + // prevent contract submissions from being stolen from tx.pool by requiring + // that the first 20 bytes of the submitted salt match msg.sender. + if ((address(bytes20(salt)) != msg.sender) && (bytes20(salt) != bytes20(0))) revert InvalidSalt(); + _; + } + + function deploy(bytes calldata _initCode, bytes32 salt) internal containsCaller(salt) returns (address deploymentAddress) { + // move the initialization code from calldata to memory. + bytes memory initCode = _initCode; + + // determine the target address for contract deployment. + address targetDeploymentAddress = calcAddress(_initCode, salt); + + // using inline assembly: load data and length of data, then call CREATE2. + assembly { + let encoded_data := add(0x20, initCode) // load initialization code. + let encoded_size := mload(initCode) // load the init code's length. + deploymentAddress := + create2( // call CREATE2 with 4 arguments. + callvalue(), // forward any attached value. + encoded_data, // pass in initialization code. + encoded_size, // pass in init code's length. + salt // pass in the salt value. + ) + } + + // check address against target to ensure that deployment was successful. + if (deploymentAddress != targetDeploymentAddress) revert InvalidAddress(); + } + + /** + * @notice Calculates the deterministic address of a contract that would be deployed using the CREATE2 opcode. + * @dev The calculated address is based on the contract's code, a salt, and the address of the current contract. + * @dev This function uses the formula specified in EIP-1014 (https://eips.ethereum.org/EIPS/eip-1014). + * + * @param initCode The contract code that would be deployed. + * @param salt A salt used for the address calculation. + * This must be the same salt that would be passed to the CREATE2 opcode. + * + * @return targetDeploymentAddress The address that the contract would be deployed + * at if the CREATE2 opcode was called with the specified _code and _salt. + */ + function calcAddress(bytes calldata initCode, bytes32 salt) internal view returns (address targetDeploymentAddress) { + targetDeploymentAddress = address( + uint160( // downcast to match the address type. + uint256( // convert to uint to truncate upper digits. + keccak256( // compute the CREATE2 hash using 4 inputs. + abi.encodePacked( // pack all inputs to the hash together. + hex"ff", // start with 0xff to distinguish from RLP. + address(this), // this contract will be the caller. + salt, // pass in the supplied salt value. + keccak256( // pass in the hash of initialization code. + abi.encodePacked(initCode)) + ) + ) + ) + ) + ); + } + + error InvalidDeployment(); +} diff --git a/rhinestone/registry/1.0.0/node_modules/@rhinestone/registry/src/lib/ModuleTypeLib.sol b/rhinestone/registry/1.0.0/node_modules/@rhinestone/registry/src/lib/ModuleTypeLib.sol new file mode 100644 index 0000000..fe618d6 --- /dev/null +++ b/rhinestone/registry/1.0.0/node_modules/@rhinestone/registry/src/lib/ModuleTypeLib.sol @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.21; + +import { PackedModuleTypes, ModuleType } from "../DataTypes.sol"; +import { IRegistry } from "../IRegistry.sol"; + +library ModuleTypeLib { + function isType(PackedModuleTypes self, ModuleType moduleType) internal pure returns (bool) { + return (PackedModuleTypes.unwrap(self) & 2 ** ModuleType.unwrap(moduleType)) != 0; + } + + function isType(uint32 packed, uint256 check) internal pure returns (bool) { + return (packed & 2 ** check) != 0; + } + + function pack(ModuleType[] memory moduleTypes) internal pure returns (PackedModuleTypes) { + uint256 length = moduleTypes.length; + uint32 packed; + uint256 _type; + for (uint256 i; i < length; i++) { + _type = ModuleType.unwrap(moduleTypes[i]); + if (_type > 31 || isType(packed, _type)) revert IRegistry.InvalidModuleType(); + packed = packed + uint32(2 ** _type); + } + return PackedModuleTypes.wrap(packed); + } +} diff --git a/rhinestone/registry/1.0.0/node_modules/@rhinestone/registry/src/lib/StubLib.sol b/rhinestone/registry/1.0.0/node_modules/@rhinestone/registry/src/lib/StubLib.sol new file mode 100644 index 0000000..df77445 --- /dev/null +++ b/rhinestone/registry/1.0.0/node_modules/@rhinestone/registry/src/lib/StubLib.sol @@ -0,0 +1,168 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.24; + +import { AttestationRecord, ResolverRecord, SchemaRecord, ModuleRecord } from "../DataTypes.sol"; +import { IExternalSchemaValidator } from "../external/IExternalSchemaValidator.sol"; +import { IExternalResolver } from "../external/IExternalResolver.sol"; +import { ZERO_ADDRESS, ZERO_TIMESTAMP } from "../Common.sol"; +import { IRegistry } from "../IRegistry.sol"; + +/** + * Helper library for interacting with `IExternalResolver` and `IExternalSchemaValidator` + * @dev if a certain resolver or validator is not set, the function will return without reverting + */ +library StubLib { + event ResolverRevocationError(IExternalResolver resolver); + + /** + * Calls an external schema validator contract to validate the schema for a single attestation + * @dev if Schema Validator is set, it will call `validateSchema()` on the `IExternalSchemaValidator` contract + * @param attestationRecord the data record that will be written into registry for this attestation + * @param $schema the storage reference of the schema record + */ + function requireExternalSchemaValidation(AttestationRecord memory attestationRecord, SchemaRecord storage $schema) internal { + // only run this function if the selected schemaUID exists + if ($schema.registeredAt == ZERO_TIMESTAMP) revert IRegistry.InvalidSchema(); + // validate Schema + IExternalSchemaValidator validator = $schema.validator; + // if validator is set, call the validator + if (address(validator) != ZERO_ADDRESS && validator.validateSchema(attestationRecord) == false) { + revert IRegistry.ExternalError_SchemaValidation(); + } + } + + /** + * Calls an external schema validator contract to validate the schema for multiple attestation + * @dev if Schema Validator is set, it will call `validateSchema()` on the `IExternalSchemaValidator` contract + * @param attestationRecords the data records that will be written into registry for the attestations + * @param $schema the storage reference of the schema record + */ + function requireExternalSchemaValidation(AttestationRecord[] memory attestationRecords, SchemaRecord storage $schema) internal { + // only run this function if the selected schemaUID exists + if ($schema.registeredAt == ZERO_TIMESTAMP) revert IRegistry.InvalidSchema(); + // validate Schema + IExternalSchemaValidator validator = $schema.validator; + // if validator is set, call the validator + if (address(validator) != ZERO_ADDRESS && validator.validateSchema(attestationRecords) == false) { + revert IRegistry.ExternalError_SchemaValidation(); + } + } + + /** + * Calls an external resolver contract to resolve a single attestation + * @dev if a resolver is set, it will call `resolveAttestation()` on the `IExternalResolver` contract + * @param attestationRecord the data record that will be written into registry for the attestation + * @param $resolver the storage reference of the resolver record used for this attestation + */ + function requireExternalResolverOnAttestation(AttestationRecord memory attestationRecord, ResolverRecord storage $resolver) internal { + IExternalResolver resolverContract = $resolver.resolver; + + if (address(resolverContract) != ZERO_ADDRESS && resolverContract.resolveAttestation(attestationRecord) == false) { + revert IRegistry.ExternalError_ResolveAttestation(); + } + } + + /** + * Calls an external resolver contract to resolve multiple attestations + * @dev if a resolver is set, it will call `resolveAttestation()` on the `IExternalResolver` contract + * @param attestationRecords the data records that will be written into registry for the attestation + * @param $resolver the storage reference of the resolver record used for this attestation + */ + function requireExternalResolverOnAttestation( + AttestationRecord[] memory attestationRecords, + ResolverRecord storage $resolver + ) + internal + { + IExternalResolver resolverContract = $resolver.resolver; + + if (address(resolverContract) == ZERO_ADDRESS) return; + + if (resolverContract.resolveAttestation(attestationRecords) == false) { + revert IRegistry.ExternalError_ResolveAttestation(); + } + } + + /** + * Calls an external resolver contract to resolve a single revocation + * @dev if a resolver is set, it will call `resolveRevocation()` on the `IExternalResolver` contract + * @dev if the resolver contract reverts, the function will return without reverting. + * This prevents Resolvers from denying revocations + * @param attestationRecord the data records of the attestation that will be revoked + * @param $resolver the storage reference of the resolver record used for this attestation + */ + function tryExternalResolverOnRevocation( + AttestationRecord memory attestationRecord, + ResolverRecord storage $resolver + ) + internal + returns (bool resolved) + { + IExternalResolver resolverContract = $resolver.resolver; + + if (address(resolverContract) == ZERO_ADDRESS) return true; + try resolverContract.resolveRevocation(attestationRecord) returns (bool _resolved) { + if (_resolved) return true; + } catch { + emit ResolverRevocationError(resolverContract); + return false; + } + } + + /** + * Calls an external resolver contract to resolve multiple revocation + * @dev if a resolver is set, it will call `resolveRevocation()` on the `IExternalResolver` contract + * @dev if the resolver contract reverts, the function will return without reverting. + * This prevents Resolvers to stop DoS revocations + * @param attestationRecords the data records of the attestations that will be revoked + * @param $resolver the storage reference of the resolver record used for this attestation + */ + function tryExternalResolverOnRevocation( + AttestationRecord[] memory attestationRecords, + ResolverRecord storage $resolver + ) + internal + returns (bool resolved) + { + IExternalResolver resolverContract = $resolver.resolver; + + if (address(resolverContract) == ZERO_ADDRESS) return true; + try resolverContract.resolveRevocation(attestationRecords) returns (bool _resolved) { + if (_resolved) return true; + } catch { + emit ResolverRevocationError(resolverContract); + return false; + } + } + + /** + * Calls an external resolver contract to resolve a module registration + * @dev if a resolver is set, it will call `resolveModuleRegistration()` on the `IExternalResolver` contract + * @param moduleRecord the module record that will be written into registry for the module registration + * @param moduleAddress the address of the module to register. + * at the point of this call, the module MUST be already deployed (could be within the current transaction) + * @param $resolver the storage reference of the resolver record used for this module registration + */ + function requireExternalResolverOnModuleRegistration( + ModuleRecord memory moduleRecord, + address moduleAddress, + ResolverRecord storage $resolver, + bytes calldata resolverContext + ) + internal + { + IExternalResolver resolverContract = $resolver.resolver; + + if ( + address(resolverContract) != ZERO_ADDRESS + && resolverContract.resolveModuleRegistration({ + sender: msg.sender, + moduleAddress: moduleAddress, + record: moduleRecord, + resolverContext: resolverContext + }) == false + ) { + revert IRegistry.ExternalError_ModuleRegistration(); + } + } +} diff --git a/rhinestone/registry/1.0.0/node_modules/@rhinestone/registry/src/lib/TrustLib.sol b/rhinestone/registry/1.0.0/node_modules/@rhinestone/registry/src/lib/TrustLib.sol new file mode 100644 index 0000000..b8e9f54 --- /dev/null +++ b/rhinestone/registry/1.0.0/node_modules/@rhinestone/registry/src/lib/TrustLib.sol @@ -0,0 +1,131 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.24; + +import { AttestationRecord, PackedModuleTypes, ModuleType } from "../DataTypes.sol"; +import { ZERO_TIMESTAMP, ZERO_MODULE_TYPE } from "../Common.sol"; +import { IRegistry } from "../IRegistry.sol"; +import { ModuleTypeLib } from "../lib/ModuleTypeLib.sol"; + +/** + * Library implements checks to validate if a storage reference for an `AttestationRecord` is currently valid + * @author rhinestone | zeroknots.eth, Konrad Kopp (@kopy-kat) + */ +library TrustLib { + using ModuleTypeLib for PackedModuleTypes; + + /** + * Check that attestationRecord is valid: + * - not revoked + * - not expired + * - correct module type (if not ZERO_MODULE_TYPE) + * @notice this function reverts if the attestationRecord is not valid + * @param expectedType the expected module type. if this is ZERO_MODULE_TYPE, types specified in the attestation are ignored + * @param $attestation the storage reference of the attestation record to check + */ + function enforceValid(AttestationRecord storage $attestation, ModuleType expectedType) internal view { + uint256 attestedAt; + uint256 expirationTime; + uint256 revocationTime; + PackedModuleTypes packedModuleType; + + /* + * Ensure only one SLOAD + * Assembly equiv to: + * + * uint256 attestedAt = record.time; + * uint256 expirationTime = record.expirationTime; + * uint256 revocationTime = record.revocationTime; + * PackedModuleTypes packedModuleType = record.moduleTypes; + */ + assembly { + let mask := 0xFFFFFFFF + let slot := sload($attestation.slot) + attestedAt := and(mask, slot) + slot := shr(48, slot) + expirationTime := and(mask, slot) + slot := shr(48, slot) + revocationTime := and(mask, slot) + slot := shr(48, slot) + packedModuleType := and(mask, slot) + } + + // check if any attestation was made + if (attestedAt == ZERO_TIMESTAMP) { + revert IRegistry.AttestationNotFound(); + } + + // check if attestation has expired + if (expirationTime != ZERO_TIMESTAMP && block.timestamp > expirationTime) { + revert IRegistry.AttestationNotFound(); + } + + // check if attestation has been revoked + if (revocationTime != ZERO_TIMESTAMP) { + revert IRegistry.RevokedAttestation($attestation.attester); + } + + // if a expectedType is set, check if the attestation is for the correct module type + // if no expectedType is set, module type is not checked + if (expectedType != ZERO_MODULE_TYPE && !packedModuleType.isType(expectedType)) { + revert IRegistry.InvalidModuleType(); + } + } + + /** + * Check that attestationRecord is valid: + * - not revoked + * - not expired + * - correct module type (if not ZERO_MODULE_TYPE) + * @dev this function DOES NOT revert if the attestationRecord is not valid, but returns false + * @param expectedType the expected module type. if this is ZERO_MODULE_TYPE, types specified in the attestation are ignored + * @param $attestation the storage reference of the attestation record to check + */ + function checkValid(AttestationRecord storage $attestation, ModuleType expectedType) internal view returns (bool) { + uint256 attestedAt; + uint256 expirationTime; + uint256 revocationTime; + PackedModuleTypes packedModuleType; + + /* + * Ensure only one SLOAD + * Assembly equiv to: + * + * uint256 attestedAt = record.time; + * uint256 expirationTime = record.expirationTime; + * uint256 revocationTime = record.revocationTime; + * PackedModuleTypes packedModuleType = record.moduleTypes; + */ + assembly { + let mask := 0xFFFFFFFF + let slot := sload($attestation.slot) + attestedAt := and(mask, slot) + slot := shr(48, slot) + expirationTime := and(mask, slot) + slot := shr(48, slot) + revocationTime := and(mask, slot) + slot := shr(48, slot) + packedModuleType := and(mask, slot) + } + + // check if any attestation was made + if (attestedAt == ZERO_TIMESTAMP) { + return false; + } + + // check if attestation has expired + if (expirationTime != ZERO_TIMESTAMP && block.timestamp > expirationTime) { + return false; + } + + // check if attestation has been revoked + if (revocationTime != ZERO_TIMESTAMP) { + return false; + } + // if a expectedType is set, check if the attestation is for the correct module type + // if no expectedType is set, module type is not checked + if (expectedType != ZERO_MODULE_TYPE && !packedModuleType.isType(expectedType)) { + return false; + } + return true; + } +} diff --git a/rhinestone/registry/1.0.0/node_modules/forge-std/src/interfaces/IERC165.sol b/rhinestone/registry/1.0.0/node_modules/forge-std/src/interfaces/IERC165.sol new file mode 100644 index 0000000..9af4bf8 --- /dev/null +++ b/rhinestone/registry/1.0.0/node_modules/forge-std/src/interfaces/IERC165.sol @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.6.2; + +interface IERC165 { + /// @notice Query if a contract implements an interface + /// @param interfaceID The interface identifier, as specified in ERC-165 + /// @dev Interface identification is specified in ERC-165. This function + /// uses less than 30,000 gas. + /// @return `true` if the contract implements `interfaceID` and + /// `interfaceID` is not 0xffffffff, `false` otherwise + function supportsInterface(bytes4 interfaceID) external view returns (bool); +} diff --git a/rhinestone/registry/1.0.0/node_modules/solady/src/utils/EIP712.sol b/rhinestone/registry/1.0.0/node_modules/solady/src/utils/EIP712.sol new file mode 100644 index 0000000..f77ee14 --- /dev/null +++ b/rhinestone/registry/1.0.0/node_modules/solady/src/utils/EIP712.sol @@ -0,0 +1,208 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.4; + +/// @notice Contract for EIP-712 typed structured data hashing and signing. +/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/EIP712.sol) +/// @author Modified from Solbase (https://github.com/Sol-DAO/solbase/blob/main/src/utils/EIP712.sol) +/// @author Modified from OpenZeppelin (https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/cryptography/EIP712.sol) +/// +/// @dev Note, this implementation: +/// - Uses `address(this)` for the `verifyingContract` field. +/// - Does NOT use the optional EIP-712 salt. +/// - Does NOT use any EIP-712 extensions. +/// This is for simplicity and to save gas. +/// If you need to customize, please fork / modify accordingly. +abstract contract EIP712 { + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* CONSTANTS AND IMMUTABLES */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev `keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)")`. + bytes32 internal constant _DOMAIN_TYPEHASH = + 0x8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f; + + uint256 private immutable _cachedThis; + uint256 private immutable _cachedChainId; + bytes32 private immutable _cachedNameHash; + bytes32 private immutable _cachedVersionHash; + bytes32 private immutable _cachedDomainSeparator; + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* CONSTRUCTOR */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev Cache the hashes for cheaper runtime gas costs. + /// In the case of upgradeable contracts (i.e. proxies), + /// or if the chain id changes due to a hard fork, + /// the domain separator will be seamlessly calculated on-the-fly. + constructor() { + _cachedThis = uint256(uint160(address(this))); + _cachedChainId = block.chainid; + + string memory name; + string memory version; + if (!_domainNameAndVersionMayChange()) (name, version) = _domainNameAndVersion(); + bytes32 nameHash = _domainNameAndVersionMayChange() ? bytes32(0) : keccak256(bytes(name)); + bytes32 versionHash = + _domainNameAndVersionMayChange() ? bytes32(0) : keccak256(bytes(version)); + _cachedNameHash = nameHash; + _cachedVersionHash = versionHash; + + bytes32 separator; + if (!_domainNameAndVersionMayChange()) { + /// @solidity memory-safe-assembly + assembly { + let m := mload(0x40) // Load the free memory pointer. + mstore(m, _DOMAIN_TYPEHASH) + mstore(add(m, 0x20), nameHash) + mstore(add(m, 0x40), versionHash) + mstore(add(m, 0x60), chainid()) + mstore(add(m, 0x80), address()) + separator := keccak256(m, 0xa0) + } + } + _cachedDomainSeparator = separator; + } + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* FUNCTIONS TO OVERRIDE */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev Please override this function to return the domain name and version. + /// ``` + /// function _domainNameAndVersion() + /// internal + /// pure + /// virtual + /// returns (string memory name, string memory version) + /// { + /// name = "Solady"; + /// version = "1"; + /// } + /// ``` + /// + /// Note: If the returned result may change after the contract has been deployed, + /// you must override `_domainNameAndVersionMayChange()` to return true. + function _domainNameAndVersion() + internal + view + virtual + returns (string memory name, string memory version); + + /// @dev Returns if `_domainNameAndVersion()` may change + /// after the contract has been deployed (i.e. after the constructor). + /// Default: false. + function _domainNameAndVersionMayChange() internal pure virtual returns (bool result) {} + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* HASHING OPERATIONS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev Returns the EIP-712 domain separator. + function _domainSeparator() internal view virtual returns (bytes32 separator) { + if (_domainNameAndVersionMayChange()) { + separator = _buildDomainSeparator(); + } else { + separator = _cachedDomainSeparator; + if (_cachedDomainSeparatorInvalidated()) separator = _buildDomainSeparator(); + } + } + + /// @dev Returns the hash of the fully encoded EIP-712 message for this domain, + /// given `structHash`, as defined in + /// https://eips.ethereum.org/EIPS/eip-712#definition-of-hashstruct. + /// + /// The hash can be used together with {ECDSA-recover} to obtain the signer of a message: + /// ``` + /// bytes32 digest = _hashTypedData(keccak256(abi.encode( + /// keccak256("Mail(address to,string contents)"), + /// mailTo, + /// keccak256(bytes(mailContents)) + /// ))); + /// address signer = ECDSA.recover(digest, signature); + /// ``` + function _hashTypedData(bytes32 structHash) internal view virtual returns (bytes32 digest) { + // We will use `digest` to store the domain separator to save a bit of gas. + if (_domainNameAndVersionMayChange()) { + digest = _buildDomainSeparator(); + } else { + digest = _cachedDomainSeparator; + if (_cachedDomainSeparatorInvalidated()) digest = _buildDomainSeparator(); + } + /// @solidity memory-safe-assembly + assembly { + // Compute the digest. + mstore(0x00, 0x1901000000000000) // Store "\x19\x01". + mstore(0x1a, digest) // Store the domain separator. + mstore(0x3a, structHash) // Store the struct hash. + digest := keccak256(0x18, 0x42) + // Restore the part of the free memory slot that was overwritten. + mstore(0x3a, 0) + } + } + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* EIP-5267 OPERATIONS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev See: https://eips.ethereum.org/EIPS/eip-5267 + function eip712Domain() + public + view + virtual + returns ( + bytes1 fields, + string memory name, + string memory version, + uint256 chainId, + address verifyingContract, + bytes32 salt, + uint256[] memory extensions + ) + { + fields = hex"0f"; // `0b01111`. + (name, version) = _domainNameAndVersion(); + chainId = block.chainid; + verifyingContract = address(this); + salt = salt; // `bytes32(0)`. + extensions = extensions; // `new uint256[](0)`. + } + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* PRIVATE HELPERS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev Returns the EIP-712 domain separator. + function _buildDomainSeparator() private view returns (bytes32 separator) { + // We will use `separator` to store the name hash to save a bit of gas. + bytes32 versionHash; + if (_domainNameAndVersionMayChange()) { + (string memory name, string memory version) = _domainNameAndVersion(); + separator = keccak256(bytes(name)); + versionHash = keccak256(bytes(version)); + } else { + separator = _cachedNameHash; + versionHash = _cachedVersionHash; + } + /// @solidity memory-safe-assembly + assembly { + let m := mload(0x40) // Load the free memory pointer. + mstore(m, _DOMAIN_TYPEHASH) + mstore(add(m, 0x20), separator) // Name hash. + mstore(add(m, 0x40), versionHash) + mstore(add(m, 0x60), chainid()) + mstore(add(m, 0x80), address()) + separator := keccak256(m, 0xa0) + } + } + + /// @dev Returns if the cached domain separator has been invalidated. + function _cachedDomainSeparatorInvalidated() private view returns (bool result) { + uint256 cachedChainId = _cachedChainId; + uint256 cachedThis = _cachedThis; + /// @solidity memory-safe-assembly + assembly { + result := iszero(and(eq(chainid(), cachedChainId), eq(address(), cachedThis))) + } + } +} diff --git a/rhinestone/registry/1.0.0/node_modules/solady/src/utils/LibSort.sol b/rhinestone/registry/1.0.0/node_modules/solady/src/utils/LibSort.sol new file mode 100644 index 0000000..6e2c350 --- /dev/null +++ b/rhinestone/registry/1.0.0/node_modules/solady/src/utils/LibSort.sol @@ -0,0 +1,725 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.4; + +/// @notice Optimized sorts and operations for sorted arrays. +/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/Sort.sol) +library LibSort { + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* INSERTION SORT */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + // - Faster on small arrays (32 or lesser elements). + // - Faster on almost sorted arrays. + // - Smaller bytecode. + // - May be suitable for view functions intended for off-chain querying. + + /// @dev Sorts the array in-place with insertion sort. + function insertionSort(uint256[] memory a) internal pure { + /// @solidity memory-safe-assembly + assembly { + let n := mload(a) // Length of `a`. + mstore(a, 0) // For insertion sort's inner loop to terminate. + let h := add(a, shl(5, n)) // High slot. + let s := 0x20 + let w := not(0x1f) + for { let i := add(a, s) } 1 {} { + i := add(i, s) + if gt(i, h) { break } + let k := mload(i) // Key. + let j := add(i, w) // The slot before the current slot. + let v := mload(j) // The value of `j`. + if iszero(gt(v, k)) { continue } + for {} 1 {} { + mstore(add(j, s), v) + j := add(j, w) // `sub(j, 0x20)`. + v := mload(j) + if iszero(gt(v, k)) { break } + } + mstore(add(j, s), k) + } + mstore(a, n) // Restore the length of `a`. + } + } + + /// @dev Sorts the array in-place with insertion sort. + function insertionSort(int256[] memory a) internal pure { + _flipSign(a); + insertionSort(_toUints(a)); + _flipSign(a); + } + + /// @dev Sorts the array in-place with insertion sort. + function insertionSort(address[] memory a) internal pure { + insertionSort(_toUints(a)); + } + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* INTRO-QUICKSORT */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + // - Faster on larger arrays (more than 32 elements). + // - Robust performance. + // - Larger bytecode. + + /// @dev Sorts the array in-place with intro-quicksort. + function sort(uint256[] memory a) internal pure { + /// @solidity memory-safe-assembly + assembly { + let w := not(0x1f) + let s := 0x20 + let n := mload(a) // Length of `a`. + mstore(a, 0) // For insertion sort's inner loop to terminate. + + // Let the stack be the start of the free memory. + let stack := mload(0x40) + + for {} iszero(lt(n, 2)) {} { + // Push `l` and `h` to the stack. + // The `shl` by 5 is equivalent to multiplying by `0x20`. + let l := add(a, s) + let h := add(a, shl(5, n)) + + let j := l + // forgefmt: disable-next-item + for {} iszero(or(eq(j, h), gt(mload(j), mload(add(j, s))))) {} { + j := add(j, s) + } + // If the array is already sorted. + if eq(j, h) { break } + + j := h + // forgefmt: disable-next-item + for {} iszero(gt(mload(j), mload(add(j, w)))) {} { + j := add(j, w) // `sub(j, 0x20)`. + } + // If the array is reversed sorted. + if eq(j, l) { + for {} 1 {} { + let t := mload(l) + mstore(l, mload(h)) + mstore(h, t) + h := add(h, w) // `sub(h, 0x20)`. + l := add(l, s) + if iszero(lt(l, h)) { break } + } + break + } + + // Push `l` and `h` onto the stack. + mstore(stack, l) + mstore(add(stack, s), h) + stack := add(stack, 0x40) + break + } + + for { let stackBottom := mload(0x40) } iszero(eq(stack, stackBottom)) {} { + // Pop `l` and `h` from the stack. + stack := sub(stack, 0x40) + let l := mload(stack) + let h := mload(add(stack, s)) + + // Do insertion sort if `h - l <= 0x20 * 12`. + // Threshold is fine-tuned via trial and error. + if iszero(gt(sub(h, l), 0x180)) { + // Hardcode sort the first 2 elements. + let i := add(l, s) + if iszero(lt(mload(l), mload(i))) { + let t := mload(i) + mstore(i, mload(l)) + mstore(l, t) + } + for {} 1 {} { + i := add(i, s) + if gt(i, h) { break } + let k := mload(i) // Key. + let j := add(i, w) // The slot before the current slot. + let v := mload(j) // The value of `j`. + if iszero(gt(v, k)) { continue } + for {} 1 {} { + mstore(add(j, s), v) + j := add(j, w) + v := mload(j) + if iszero(gt(v, k)) { break } + } + mstore(add(j, s), k) + } + continue + } + // Pivot slot is the average of `l` and `h`. + let p := add(shl(5, shr(6, add(l, h))), and(31, l)) + // Median of 3 with sorting. + { + function swap(a_, b_) -> _b, _a { + _b := a_ + _a := b_ + } + let e0 := mload(l) + let e1 := mload(h) + if iszero(lt(e0, e1)) { e1, e0 := swap(e0, e1) } + let e2 := mload(p) + if iszero(lt(e2, e1)) { e2, e1 := swap(e1, e2) } + if iszero(lt(e0, e2)) { e2, e0 := swap(e0, e2) } + mstore(p, e2) + mstore(h, e1) + mstore(l, e0) + } + // Hoare's partition. + { + // The value of the pivot slot. + let x := mload(p) + p := h + for { let i := l } 1 {} { + for {} 1 {} { + i := add(i, s) + if iszero(gt(x, mload(i))) { break } + } + let j := p + for {} 1 {} { + j := add(j, w) + if iszero(lt(x, mload(j))) { break } + } + p := j + if iszero(lt(i, p)) { break } + // Swap slots `i` and `p`. + let t := mload(i) + mstore(i, mload(p)) + mstore(p, t) + } + } + // If slice on right of pivot is non-empty, push onto stack. + { + mstore(stack, add(p, s)) + // Skip `mstore(add(stack, 0x20), h)`, as it is already on the stack. + stack := add(stack, shl(6, lt(add(p, s), h))) + } + // If slice on left of pivot is non-empty, push onto stack. + { + mstore(stack, l) + mstore(add(stack, s), p) + stack := add(stack, shl(6, gt(p, l))) + } + } + mstore(a, n) // Restore the length of `a`. + } + } + + /// @dev Sorts the array in-place with intro-quicksort. + function sort(int256[] memory a) internal pure { + _flipSign(a); + sort(_toUints(a)); + _flipSign(a); + } + + /// @dev Sorts the array in-place with intro-quicksort. + function sort(address[] memory a) internal pure { + sort(_toUints(a)); + } + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* OTHER USEFUL OPERATIONS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + // For performance, the `uniquifySorted` methods will not revert if the + // array is not sorted -- it will simply remove consecutive duplicate elements. + + /// @dev Removes duplicate elements from a ascendingly sorted memory array. + function uniquifySorted(uint256[] memory a) internal pure { + /// @solidity memory-safe-assembly + assembly { + // If the length of `a` is greater than 1. + if iszero(lt(mload(a), 2)) { + let x := add(a, 0x20) + let y := add(a, 0x40) + let end := add(a, shl(5, add(mload(a), 1))) + for {} 1 {} { + if iszero(eq(mload(x), mload(y))) { + x := add(x, 0x20) + mstore(x, mload(y)) + } + y := add(y, 0x20) + if eq(y, end) { break } + } + mstore(a, shr(5, sub(x, a))) + } + } + } + + /// @dev Removes duplicate elements from a ascendingly sorted memory array. + function uniquifySorted(int256[] memory a) internal pure { + uniquifySorted(_toUints(a)); + } + + /// @dev Removes duplicate elements from a ascendingly sorted memory array. + function uniquifySorted(address[] memory a) internal pure { + uniquifySorted(_toUints(a)); + } + + /// @dev Returns whether `a` contains `needle`, and the index of `needle`. + /// `index` precedence: equal to > nearest before > nearest after. + function searchSorted(uint256[] memory a, uint256 needle) + internal + pure + returns (bool found, uint256 index) + { + (found, index) = _searchSorted(a, needle, 0); + } + + /// @dev Returns whether `a` contains `needle`, and the index of `needle`. + /// `index` precedence: equal to > nearest before > nearest after. + function searchSorted(int256[] memory a, int256 needle) + internal + pure + returns (bool found, uint256 index) + { + (found, index) = _searchSorted(_toUints(a), uint256(needle), 1 << 255); + } + + /// @dev Returns whether `a` contains `needle`, and the index of `needle`. + /// `index` precedence: equal to > nearest before > nearest after. + function searchSorted(address[] memory a, address needle) + internal + pure + returns (bool found, uint256 index) + { + (found, index) = _searchSorted(_toUints(a), uint256(uint160(needle)), 0); + } + + /// @dev Reverses the array in-place. + function reverse(uint256[] memory a) internal pure { + /// @solidity memory-safe-assembly + assembly { + if iszero(lt(mload(a), 2)) { + let s := 0x20 + let w := not(0x1f) + let h := add(a, shl(5, mload(a))) + for { a := add(a, s) } 1 {} { + let t := mload(a) + mstore(a, mload(h)) + mstore(h, t) + h := add(h, w) + a := add(a, s) + if iszero(lt(a, h)) { break } + } + } + } + } + + /// @dev Reverses the array in-place. + function reverse(int256[] memory a) internal pure { + reverse(_toUints(a)); + } + + /// @dev Reverses the array in-place. + function reverse(address[] memory a) internal pure { + reverse(_toUints(a)); + } + + /// @dev Returns a copy of the array. + function copy(uint256[] memory a) internal pure returns (uint256[] memory result) { + /// @solidity memory-safe-assembly + assembly { + result := mload(0x40) + let end := add(add(result, 0x20), shl(5, mload(a))) + let o := result + for { let d := sub(a, result) } 1 {} { + mstore(o, mload(add(o, d))) + o := add(0x20, o) + if eq(o, end) { break } + } + mstore(0x40, o) + } + } + + /// @dev Returns a copy of the array. + function copy(int256[] memory a) internal pure returns (int256[] memory result) { + result = _toInts(copy(_toUints(a))); + } + + /// @dev Returns a copy of the array. + function copy(address[] memory a) internal pure returns (address[] memory result) { + result = _toAddresses(copy(_toUints(a))); + } + + /// @dev Returns whether the array is sorted in ascending order. + function isSorted(uint256[] memory a) internal pure returns (bool result) { + /// @solidity memory-safe-assembly + assembly { + result := 1 + if iszero(lt(mload(a), 2)) { + let end := add(a, shl(5, mload(a))) + for { a := add(a, 0x20) } 1 {} { + let p := mload(a) + a := add(a, 0x20) + result := iszero(gt(p, mload(a))) + if iszero(mul(result, xor(a, end))) { break } + } + } + } + } + + /// @dev Returns whether the array is sorted in ascending order. + function isSorted(int256[] memory a) internal pure returns (bool result) { + /// @solidity memory-safe-assembly + assembly { + result := 1 + if iszero(lt(mload(a), 2)) { + let end := add(a, shl(5, mload(a))) + for { a := add(a, 0x20) } 1 {} { + let p := mload(a) + a := add(a, 0x20) + result := iszero(sgt(p, mload(a))) + if iszero(mul(result, xor(a, end))) { break } + } + } + } + } + + /// @dev Returns whether the array is sorted in ascending order. + function isSorted(address[] memory a) internal pure returns (bool result) { + result = isSorted(_toUints(a)); + } + + /// @dev Returns whether the array is strictly ascending (sorted and uniquified). + function isSortedAndUniquified(uint256[] memory a) internal pure returns (bool result) { + /// @solidity memory-safe-assembly + assembly { + result := 1 + if iszero(lt(mload(a), 2)) { + let end := add(a, shl(5, mload(a))) + for { a := add(a, 0x20) } 1 {} { + let p := mload(a) + a := add(a, 0x20) + result := lt(p, mload(a)) + if iszero(mul(result, xor(a, end))) { break } + } + } + } + } + + /// @dev Returns whether the array is strictly ascending (sorted and uniquified). + function isSortedAndUniquified(int256[] memory a) internal pure returns (bool result) { + /// @solidity memory-safe-assembly + assembly { + result := 1 + if iszero(lt(mload(a), 2)) { + let end := add(a, shl(5, mload(a))) + for { a := add(a, 0x20) } 1 {} { + let p := mload(a) + a := add(a, 0x20) + result := slt(p, mload(a)) + if iszero(mul(result, xor(a, end))) { break } + } + } + } + } + + /// @dev Returns whether the array is strictly ascending (sorted and uniquified). + function isSortedAndUniquified(address[] memory a) internal pure returns (bool result) { + result = isSortedAndUniquified(_toUints(a)); + } + + /// @dev Returns the sorted set difference of `a` and `b`. + /// Note: Behaviour is undefined if inputs are not sorted and uniquified. + function difference(uint256[] memory a, uint256[] memory b) + internal + pure + returns (uint256[] memory c) + { + c = _difference(a, b, 0); + } + + /// @dev Returns the sorted set difference between `a` and `b`. + /// Note: Behaviour is undefined if inputs are not sorted and uniquified. + function difference(int256[] memory a, int256[] memory b) + internal + pure + returns (int256[] memory c) + { + c = _toInts(_difference(_toUints(a), _toUints(b), 1 << 255)); + } + + /// @dev Returns the sorted set difference between `a` and `b`. + /// Note: Behaviour is undefined if inputs are not sorted and uniquified. + function difference(address[] memory a, address[] memory b) + internal + pure + returns (address[] memory c) + { + c = _toAddresses(_difference(_toUints(a), _toUints(b), 0)); + } + + /// @dev Returns the sorted set intersection between `a` and `b`. + /// Note: Behaviour is undefined if inputs are not sorted and uniquified. + function intersection(uint256[] memory a, uint256[] memory b) + internal + pure + returns (uint256[] memory c) + { + c = _intersection(a, b, 0); + } + + /// @dev Returns the sorted set intersection between `a` and `b`. + /// Note: Behaviour is undefined if inputs are not sorted and uniquified. + function intersection(int256[] memory a, int256[] memory b) + internal + pure + returns (int256[] memory c) + { + c = _toInts(_intersection(_toUints(a), _toUints(b), 1 << 255)); + } + + /// @dev Returns the sorted set intersection between `a` and `b`. + /// Note: Behaviour is undefined if inputs are not sorted and uniquified. + function intersection(address[] memory a, address[] memory b) + internal + pure + returns (address[] memory c) + { + c = _toAddresses(_intersection(_toUints(a), _toUints(b), 0)); + } + + /// @dev Returns the sorted set union of `a` and `b`. + /// Note: Behaviour is undefined if inputs are not sorted and uniquified. + function union(uint256[] memory a, uint256[] memory b) + internal + pure + returns (uint256[] memory c) + { + c = _union(a, b, 0); + } + + /// @dev Returns the sorted set union of `a` and `b`. + /// Note: Behaviour is undefined if inputs are not sorted and uniquified. + function union(int256[] memory a, int256[] memory b) + internal + pure + returns (int256[] memory c) + { + c = _toInts(_union(_toUints(a), _toUints(b), 1 << 255)); + } + + /// @dev Returns the sorted set union between `a` and `b`. + /// Note: Behaviour is undefined if inputs are not sorted and uniquified. + function union(address[] memory a, address[] memory b) + internal + pure + returns (address[] memory c) + { + c = _toAddresses(_union(_toUints(a), _toUints(b), 0)); + } + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* PRIVATE HELPERS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev Reinterpret cast to an uint256 array. + function _toUints(int256[] memory a) private pure returns (uint256[] memory casted) { + /// @solidity memory-safe-assembly + assembly { + casted := a + } + } + + /// @dev Reinterpret cast to an uint256 array. + function _toUints(address[] memory a) private pure returns (uint256[] memory casted) { + /// @solidity memory-safe-assembly + assembly { + // As any address written to memory will have the upper 96 bits + // of the word zeroized (as per Solidity spec), we can directly + // compare these addresses as if they are whole uint256 words. + casted := a + } + } + + /// @dev Reinterpret cast to an int array. + function _toInts(uint256[] memory a) private pure returns (int256[] memory casted) { + /// @solidity memory-safe-assembly + assembly { + casted := a + } + } + + /// @dev Reinterpret cast to an address array. + function _toAddresses(uint256[] memory a) private pure returns (address[] memory casted) { + /// @solidity memory-safe-assembly + assembly { + casted := a + } + } + + /// @dev Converts an array of signed integers to unsigned + /// integers suitable for sorting or vice versa. + function _flipSign(int256[] memory a) private pure { + /// @solidity memory-safe-assembly + assembly { + let w := shl(255, 1) + for { let end := add(a, shl(5, mload(a))) } iszero(eq(a, end)) {} { + a := add(a, 0x20) + mstore(a, add(mload(a), w)) + } + } + } + + /// @dev Returns whether `a` contains `needle`, and the index of `needle`. + /// `index` precedence: equal to > nearest before > nearest after. + function _searchSorted(uint256[] memory a, uint256 needle, uint256 signed) + private + pure + returns (bool found, uint256 index) + { + /// @solidity memory-safe-assembly + assembly { + let w := not(0) + let l := 1 + let h := mload(a) + let t := 0 + for { needle := add(signed, needle) } 1 {} { + index := shr(1, add(l, h)) + t := add(signed, mload(add(a, shl(5, index)))) + if or(gt(l, h), eq(t, needle)) { break } + // Decide whether to search the left or right half. + if iszero(gt(needle, t)) { + h := add(index, w) + continue + } + l := add(index, 1) + } + // `index` will be zero in the case of an empty array, + // or when the value is less than the smallest value in the array. + found := eq(t, needle) + t := iszero(iszero(index)) + index := mul(add(index, w), t) + found := and(found, t) + } + } + + /// @dev Returns the sorted set difference of `a` and `b`. + /// Note: Behaviour is undefined if inputs are not sorted and uniquified. + function _difference(uint256[] memory a, uint256[] memory b, uint256 signed) + private + pure + returns (uint256[] memory c) + { + /// @solidity memory-safe-assembly + assembly { + let s := 0x20 + let aEnd := add(a, shl(5, mload(a))) + let bEnd := add(b, shl(5, mload(b))) + c := mload(0x40) // Set `c` to the free memory pointer. + a := add(a, s) + b := add(b, s) + let k := c + for {} iszero(or(gt(a, aEnd), gt(b, bEnd))) {} { + let u := mload(a) + let v := mload(b) + if iszero(xor(u, v)) { + a := add(a, s) + b := add(b, s) + continue + } + if iszero(lt(add(u, signed), add(v, signed))) { + b := add(b, s) + continue + } + k := add(k, s) + mstore(k, u) + a := add(a, s) + } + for {} iszero(gt(a, aEnd)) {} { + k := add(k, s) + mstore(k, mload(a)) + a := add(a, s) + } + mstore(c, shr(5, sub(k, c))) // Store the length of `c`. + mstore(0x40, add(k, s)) // Allocate the memory for `c`. + } + } + + /// @dev Returns the sorted set intersection between `a` and `b`. + /// Note: Behaviour is undefined if inputs are not sorted and uniquified. + function _intersection(uint256[] memory a, uint256[] memory b, uint256 signed) + private + pure + returns (uint256[] memory c) + { + /// @solidity memory-safe-assembly + assembly { + let s := 0x20 + let aEnd := add(a, shl(5, mload(a))) + let bEnd := add(b, shl(5, mload(b))) + c := mload(0x40) // Set `c` to the free memory pointer. + a := add(a, s) + b := add(b, s) + let k := c + for {} iszero(or(gt(a, aEnd), gt(b, bEnd))) {} { + let u := mload(a) + let v := mload(b) + if iszero(xor(u, v)) { + k := add(k, s) + mstore(k, u) + a := add(a, s) + b := add(b, s) + continue + } + if iszero(lt(add(u, signed), add(v, signed))) { + b := add(b, s) + continue + } + a := add(a, s) + } + mstore(c, shr(5, sub(k, c))) // Store the length of `c`. + mstore(0x40, add(k, s)) // Allocate the memory for `c`. + } + } + + /// @dev Returns the sorted set union of `a` and `b`. + /// Note: Behaviour is undefined if inputs are not sorted and uniquified. + function _union(uint256[] memory a, uint256[] memory b, uint256 signed) + private + pure + returns (uint256[] memory c) + { + /// @solidity memory-safe-assembly + assembly { + let s := 0x20 + let aEnd := add(a, shl(5, mload(a))) + let bEnd := add(b, shl(5, mload(b))) + c := mload(0x40) // Set `c` to the free memory pointer. + a := add(a, s) + b := add(b, s) + let k := c + for {} iszero(or(gt(a, aEnd), gt(b, bEnd))) {} { + let u := mload(a) + let v := mload(b) + if iszero(xor(u, v)) { + k := add(k, s) + mstore(k, u) + a := add(a, s) + b := add(b, s) + continue + } + if iszero(lt(add(u, signed), add(v, signed))) { + k := add(k, s) + mstore(k, v) + b := add(b, s) + continue + } + k := add(k, s) + mstore(k, u) + a := add(a, s) + } + for {} iszero(gt(a, aEnd)) {} { + k := add(k, s) + mstore(k, mload(a)) + a := add(a, s) + } + for {} iszero(gt(b, bEnd)) {} { + k := add(k, s) + mstore(k, mload(b)) + b := add(b, s) + } + mstore(c, shr(5, sub(k, c))) // Store the length of `c`. + mstore(0x40, add(k, s)) // Allocate the memory for `c`. + } + } +} diff --git a/rhinestone/registry/1.0.0/node_modules/solady/src/utils/SSTORE2.sol b/rhinestone/registry/1.0.0/node_modules/solady/src/utils/SSTORE2.sol new file mode 100644 index 0000000..bc22e66 --- /dev/null +++ b/rhinestone/registry/1.0.0/node_modules/solady/src/utils/SSTORE2.sol @@ -0,0 +1,271 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.4; + +/// @notice Read and write to persistent storage at a fraction of the cost. +/// @author Solady (https://github.com/vectorized/solmady/blob/main/src/utils/SSTORE2.sol) +/// @author Saw-mon-and-Natalie (https://github.com/Saw-mon-and-Natalie) +/// @author Modified from Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/SSTORE2.sol) +/// @author Modified from 0xSequence (https://github.com/0xSequence/sstore2/blob/master/contracts/SSTORE2.sol) +library SSTORE2 { + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* CONSTANTS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev We skip the first byte as it's a STOP opcode, + /// which ensures the contract can't be called. + uint256 internal constant DATA_OFFSET = 1; + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* CUSTOM ERRORS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev Unable to deploy the storage contract. + error DeploymentFailed(); + + /// @dev The storage contract address is invalid. + error InvalidPointer(); + + /// @dev Attempt to read outside of the storage contract's bytecode bounds. + error ReadOutOfBounds(); + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* WRITE LOGIC */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev Writes `data` into the bytecode of a storage contract and returns its address. + function write(bytes memory data) internal returns (address pointer) { + /// @solidity memory-safe-assembly + assembly { + let originalDataLength := mload(data) + + // Add 1 to data size since we are prefixing it with a STOP opcode. + let dataSize := add(originalDataLength, DATA_OFFSET) + + /** + * ------------------------------------------------------------------------------+ + * Opcode | Mnemonic | Stack | Memory | + * ------------------------------------------------------------------------------| + * 61 dataSize | PUSH2 dataSize | dataSize | | + * 80 | DUP1 | dataSize dataSize | | + * 60 0xa | PUSH1 0xa | 0xa dataSize dataSize | | + * 3D | RETURNDATASIZE | 0 0xa dataSize dataSize | | + * 39 | CODECOPY | dataSize | [0..dataSize): code | + * 3D | RETURNDATASIZE | 0 dataSize | [0..dataSize): code | + * F3 | RETURN | | [0..dataSize): code | + * 00 | STOP | | | + * ------------------------------------------------------------------------------+ + * @dev Prefix the bytecode with a STOP opcode to ensure it cannot be called. + * Also PUSH2 is used since max contract size cap is 24,576 bytes which is less than 2 ** 16. + */ + mstore( + // Do a out-of-gas revert if `dataSize` is more than 2 bytes. + // The actual EVM limit may be smaller and may change over time. + add(data, gt(dataSize, 0xffff)), + // Left shift `dataSize` by 64 so that it lines up with the 0000 after PUSH2. + or(0xfd61000080600a3d393df300, shl(0x40, dataSize)) + ) + + // Deploy a new contract with the generated creation code. + pointer := create(0, add(data, 0x15), add(dataSize, 0xa)) + + // If `pointer` is zero, revert. + if iszero(pointer) { + // Store the function selector of `DeploymentFailed()`. + mstore(0x00, 0x30116425) + // Revert with (offset, size). + revert(0x1c, 0x04) + } + + // Restore original length of the variable size `data`. + mstore(data, originalDataLength) + } + } + + /// @dev Writes `data` into the bytecode of a storage contract with `salt` + /// and returns its deterministic address. + function writeDeterministic(bytes memory data, bytes32 salt) + internal + returns (address pointer) + { + /// @solidity memory-safe-assembly + assembly { + let originalDataLength := mload(data) + let dataSize := add(originalDataLength, DATA_OFFSET) + + mstore( + // Do a out-of-gas revert if `dataSize` is more than 2 bytes. + // The actual EVM limit may be smaller and may change over time. + add(data, gt(dataSize, 0xffff)), + // Left shift `dataSize` by 64 so that it lines up with the 0000 after PUSH2. + or(0xfd61000080600a3d393df300, shl(0x40, dataSize)) + ) + + // Deploy a new contract with the generated creation code. + pointer := create2(0, add(data, 0x15), add(dataSize, 0xa), salt) + + // If `pointer` is zero, revert. + if iszero(pointer) { + // Store the function selector of `DeploymentFailed()`. + mstore(0x00, 0x30116425) + // Revert with (offset, size). + revert(0x1c, 0x04) + } + + // Restore original length of the variable size `data`. + mstore(data, originalDataLength) + } + } + + /// @dev Returns the initialization code hash of the storage contract for `data`. + /// Used for mining vanity addresses with create2crunch. + function initCodeHash(bytes memory data) internal pure returns (bytes32 hash) { + /// @solidity memory-safe-assembly + assembly { + let originalDataLength := mload(data) + let dataSize := add(originalDataLength, DATA_OFFSET) + + // Do a out-of-gas revert if `dataSize` is more than 2 bytes. + // The actual EVM limit may be smaller and may change over time. + returndatacopy(returndatasize(), returndatasize(), shr(16, dataSize)) + + mstore(data, or(0x61000080600a3d393df300, shl(0x40, dataSize))) + + hash := keccak256(add(data, 0x15), add(dataSize, 0xa)) + + // Restore original length of the variable size `data`. + mstore(data, originalDataLength) + } + } + + /// @dev Returns the address of the storage contract for `data` + /// deployed with `salt` by `deployer`. + /// Note: The returned result has dirty upper 96 bits. Please clean if used in assembly. + function predictDeterministicAddress(bytes memory data, bytes32 salt, address deployer) + internal + pure + returns (address predicted) + { + bytes32 hash = initCodeHash(data); + /// @solidity memory-safe-assembly + assembly { + // Compute and store the bytecode hash. + mstore8(0x00, 0xff) // Write the prefix. + mstore(0x35, hash) + mstore(0x01, shl(96, deployer)) + mstore(0x15, salt) + predicted := keccak256(0x00, 0x55) + // Restore the part of the free memory pointer that has been overwritten. + mstore(0x35, 0) + } + } + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* READ LOGIC */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev Returns all the `data` from the bytecode of the storage contract at `pointer`. + function read(address pointer) internal view returns (bytes memory data) { + /// @solidity memory-safe-assembly + assembly { + let pointerCodesize := extcodesize(pointer) + if iszero(pointerCodesize) { + // Store the function selector of `InvalidPointer()`. + mstore(0x00, 0x11052bb4) + // Revert with (offset, size). + revert(0x1c, 0x04) + } + // Offset all indices by 1 to skip the STOP opcode. + let size := sub(pointerCodesize, DATA_OFFSET) + + // Get the pointer to the free memory and allocate + // enough 32-byte words for the data and the length of the data, + // then copy the code to the allocated memory. + // Masking with 0xffe0 will suffice, since contract size is less than 16 bits. + data := mload(0x40) + mstore(0x40, add(data, and(add(size, 0x3f), 0xffe0))) + mstore(data, size) + mstore(add(add(data, 0x20), size), 0) // Zeroize the last slot. + extcodecopy(pointer, add(data, 0x20), DATA_OFFSET, size) + } + } + + /// @dev Returns the `data` from the bytecode of the storage contract at `pointer`, + /// from the byte at `start`, to the end of the data stored. + function read(address pointer, uint256 start) internal view returns (bytes memory data) { + /// @solidity memory-safe-assembly + assembly { + let pointerCodesize := extcodesize(pointer) + if iszero(pointerCodesize) { + // Store the function selector of `InvalidPointer()`. + mstore(0x00, 0x11052bb4) + // Revert with (offset, size). + revert(0x1c, 0x04) + } + + // If `!(pointer.code.size > start)`, reverts. + // This also handles the case where `start + DATA_OFFSET` overflows. + if iszero(gt(pointerCodesize, start)) { + // Store the function selector of `ReadOutOfBounds()`. + mstore(0x00, 0x84eb0dd1) + // Revert with (offset, size). + revert(0x1c, 0x04) + } + let size := sub(pointerCodesize, add(start, DATA_OFFSET)) + + // Get the pointer to the free memory and allocate + // enough 32-byte words for the data and the length of the data, + // then copy the code to the allocated memory. + // Masking with 0xffe0 will suffice, since contract size is less than 16 bits. + data := mload(0x40) + mstore(0x40, add(data, and(add(size, 0x3f), 0xffe0))) + mstore(data, size) + mstore(add(add(data, 0x20), size), 0) // Zeroize the last slot. + extcodecopy(pointer, add(data, 0x20), add(start, DATA_OFFSET), size) + } + } + + /// @dev Returns the `data` from the bytecode of the storage contract at `pointer`, + /// from the byte at `start`, to the byte at `end` (exclusive) of the data stored. + function read(address pointer, uint256 start, uint256 end) + internal + view + returns (bytes memory data) + { + /// @solidity memory-safe-assembly + assembly { + let pointerCodesize := extcodesize(pointer) + if iszero(pointerCodesize) { + // Store the function selector of `InvalidPointer()`. + mstore(0x00, 0x11052bb4) + // Revert with (offset, size). + revert(0x1c, 0x04) + } + + // If `!(pointer.code.size > end) || (start > end)`, revert. + // This also handles the cases where + // `end + DATA_OFFSET` or `start + DATA_OFFSET` overflows. + if iszero( + and( + gt(pointerCodesize, end), // Within bounds. + iszero(gt(start, end)) // Valid range. + ) + ) { + // Store the function selector of `ReadOutOfBounds()`. + mstore(0x00, 0x84eb0dd1) + // Revert with (offset, size). + revert(0x1c, 0x04) + } + let size := sub(end, start) + + // Get the pointer to the free memory and allocate + // enough 32-byte words for the data and the length of the data, + // then copy the code to the allocated memory. + // Masking with 0xffe0 will suffice, since contract size is less than 16 bits. + data := mload(0x40) + mstore(0x40, add(data, and(add(size, 0x3f), 0xffe0))) + mstore(data, size) + mstore(add(add(data, 0x20), size), 0) // Zeroize the last slot. + extcodecopy(pointer, add(data, 0x20), add(start, DATA_OFFSET), size) + } + } +} diff --git a/rhinestone/registry/1.0.0/node_modules/solady/src/utils/SignatureCheckerLib.sol b/rhinestone/registry/1.0.0/node_modules/solady/src/utils/SignatureCheckerLib.sol new file mode 100644 index 0000000..6f9350f --- /dev/null +++ b/rhinestone/registry/1.0.0/node_modules/solady/src/utils/SignatureCheckerLib.sol @@ -0,0 +1,545 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.4; + +/// @notice Signature verification helper that supports both ECDSA signatures from EOAs +/// and ERC1271 signatures from smart contract wallets like Argent and Gnosis safe. +/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/SignatureCheckerLib.sol) +/// @author Modified from OpenZeppelin (https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/cryptography/SignatureChecker.sol) +/// +/// @dev Note: +/// - The signature checking functions use the ecrecover precompile (0x1). +/// - The `bytes memory signature` variants use the identity precompile (0x4) +/// to copy memory internally. +/// - Unlike ECDSA signatures, contract signatures are revocable. +/// - As of Solady version 0.0.134, all `bytes signature` variants accept both +/// regular 65-byte `(r, s, v)` and EIP-2098 `(r, vs)` short form signatures. +/// See: https://eips.ethereum.org/EIPS/eip-2098 +/// This is for calldata efficiency on smart accounts prevalent on L2s. +/// +/// WARNING! Do NOT use signatures as unique identifiers: +/// - Use a nonce in the digest to prevent replay attacks on the same contract. +/// - Use EIP-712 for the digest to prevent replay attacks across different chains and contracts. +/// EIP-712 also enables readable signing of typed data for better user safety. +/// This implementation does NOT check if a signature is non-malleable. +library SignatureCheckerLib { + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* SIGNATURE CHECKING OPERATIONS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev Returns whether `signature` is valid for `signer` and `hash`. + /// If `signer` is a smart contract, the signature is validated with ERC1271. + /// Otherwise, the signature is validated with `ECDSA.recover`. + function isValidSignatureNow(address signer, bytes32 hash, bytes memory signature) + internal + view + returns (bool isValid) + { + /// @solidity memory-safe-assembly + assembly { + // Clean the upper 96 bits of `signer` in case they are dirty. + for { signer := shr(96, shl(96, signer)) } signer {} { + let m := mload(0x40) + mstore(0x00, hash) + mstore(0x40, mload(add(signature, 0x20))) // `r`. + if eq(mload(signature), 64) { + let vs := mload(add(signature, 0x40)) + mstore(0x20, add(shr(255, vs), 27)) // `v`. + mstore(0x60, shr(1, shl(1, vs))) // `s`. + let t := + staticcall( + gas(), // Amount of gas left for the transaction. + 1, // Address of `ecrecover`. + 0x00, // Start of input. + 0x80, // Size of input. + 0x01, // Start of output. + 0x20 // Size of output. + ) + // `returndatasize()` will be `0x20` upon success, and `0x00` otherwise. + if iszero(or(iszero(returndatasize()), xor(signer, mload(t)))) { + isValid := 1 + mstore(0x60, 0) // Restore the zero slot. + mstore(0x40, m) // Restore the free memory pointer. + break + } + } + if eq(mload(signature), 65) { + mstore(0x20, byte(0, mload(add(signature, 0x60)))) // `v`. + mstore(0x60, mload(add(signature, 0x40))) // `s`. + let t := + staticcall( + gas(), // Amount of gas left for the transaction. + 1, // Address of `ecrecover`. + 0x00, // Start of input. + 0x80, // Size of input. + 0x01, // Start of output. + 0x20 // Size of output. + ) + // `returndatasize()` will be `0x20` upon success, and `0x00` otherwise. + if iszero(or(iszero(returndatasize()), xor(signer, mload(t)))) { + isValid := 1 + mstore(0x60, 0) // Restore the zero slot. + mstore(0x40, m) // Restore the free memory pointer. + break + } + } + mstore(0x60, 0) // Restore the zero slot. + mstore(0x40, m) // Restore the free memory pointer. + + let f := shl(224, 0x1626ba7e) + mstore(m, f) // `bytes4(keccak256("isValidSignature(bytes32,bytes)"))`. + mstore(add(m, 0x04), hash) + let d := add(m, 0x24) + mstore(d, 0x40) // The offset of the `signature` in the calldata. + // Copy the `signature` over. + let n := add(0x20, mload(signature)) + pop(staticcall(gas(), 4, signature, n, add(m, 0x44), n)) + // forgefmt: disable-next-item + isValid := and( + // Whether the returndata is the magic value `0x1626ba7e` (left-aligned). + eq(mload(d), f), + // Whether the staticcall does not revert. + // This must be placed at the end of the `and` clause, + // as the arguments are evaluated from right to left. + staticcall( + gas(), // Remaining gas. + signer, // The `signer` address. + m, // Offset of calldata in memory. + add(returndatasize(), 0x44), // Length of calldata in memory. + d, // Offset of returndata. + 0x20 // Length of returndata to write. + ) + ) + break + } + } + } + + /// @dev Returns whether `signature` is valid for `signer` and `hash`. + /// If `signer` is a smart contract, the signature is validated with ERC1271. + /// Otherwise, the signature is validated with `ECDSA.recover`. + function isValidSignatureNowCalldata(address signer, bytes32 hash, bytes calldata signature) + internal + view + returns (bool isValid) + { + /// @solidity memory-safe-assembly + assembly { + // Clean the upper 96 bits of `signer` in case they are dirty. + for { signer := shr(96, shl(96, signer)) } signer {} { + let m := mload(0x40) + mstore(0x00, hash) + if eq(signature.length, 64) { + let vs := calldataload(add(signature.offset, 0x20)) + mstore(0x20, add(shr(255, vs), 27)) // `v`. + mstore(0x40, calldataload(signature.offset)) // `r`. + mstore(0x60, shr(1, shl(1, vs))) // `s`. + let t := + staticcall( + gas(), // Amount of gas left for the transaction. + 1, // Address of `ecrecover`. + 0x00, // Start of input. + 0x80, // Size of input. + 0x01, // Start of output. + 0x20 // Size of output. + ) + // `returndatasize()` will be `0x20` upon success, and `0x00` otherwise. + if iszero(or(iszero(returndatasize()), xor(signer, mload(t)))) { + isValid := 1 + mstore(0x60, 0) // Restore the zero slot. + mstore(0x40, m) // Restore the free memory pointer. + break + } + } + if eq(signature.length, 65) { + mstore(0x20, byte(0, calldataload(add(signature.offset, 0x40)))) // `v`. + calldatacopy(0x40, signature.offset, 0x40) // `r`, `s`. + let t := + staticcall( + gas(), // Amount of gas left for the transaction. + 1, // Address of `ecrecover`. + 0x00, // Start of input. + 0x80, // Size of input. + 0x01, // Start of output. + 0x20 // Size of output. + ) + // `returndatasize()` will be `0x20` upon success, and `0x00` otherwise. + if iszero(or(iszero(returndatasize()), xor(signer, mload(t)))) { + isValid := 1 + mstore(0x60, 0) // Restore the zero slot. + mstore(0x40, m) // Restore the free memory pointer. + break + } + } + mstore(0x60, 0) // Restore the zero slot. + mstore(0x40, m) // Restore the free memory pointer. + + let f := shl(224, 0x1626ba7e) + mstore(m, f) // `bytes4(keccak256("isValidSignature(bytes32,bytes)"))`. + mstore(add(m, 0x04), hash) + let d := add(m, 0x24) + mstore(d, 0x40) // The offset of the `signature` in the calldata. + mstore(add(m, 0x44), signature.length) + // Copy the `signature` over. + calldatacopy(add(m, 0x64), signature.offset, signature.length) + // forgefmt: disable-next-item + isValid := and( + // Whether the returndata is the magic value `0x1626ba7e` (left-aligned). + eq(mload(d), f), + // Whether the staticcall does not revert. + // This must be placed at the end of the `and` clause, + // as the arguments are evaluated from right to left. + staticcall( + gas(), // Remaining gas. + signer, // The `signer` address. + m, // Offset of calldata in memory. + add(signature.length, 0x64), // Length of calldata in memory. + d, // Offset of returndata. + 0x20 // Length of returndata to write. + ) + ) + break + } + } + } + + /// @dev Returns whether the signature (`r`, `vs`) is valid for `signer` and `hash`. + /// If `signer` is a smart contract, the signature is validated with ERC1271. + /// Otherwise, the signature is validated with `ECDSA.recover`. + function isValidSignatureNow(address signer, bytes32 hash, bytes32 r, bytes32 vs) + internal + view + returns (bool isValid) + { + /// @solidity memory-safe-assembly + assembly { + // Clean the upper 96 bits of `signer` in case they are dirty. + for { signer := shr(96, shl(96, signer)) } signer {} { + let m := mload(0x40) + mstore(0x00, hash) + mstore(0x20, add(shr(255, vs), 27)) // `v`. + mstore(0x40, r) // `r`. + mstore(0x60, shr(1, shl(1, vs))) // `s`. + let t := + staticcall( + gas(), // Amount of gas left for the transaction. + 1, // Address of `ecrecover`. + 0x00, // Start of input. + 0x80, // Size of input. + 0x01, // Start of output. + 0x20 // Size of output. + ) + // `returndatasize()` will be `0x20` upon success, and `0x00` otherwise. + if iszero(or(iszero(returndatasize()), xor(signer, mload(t)))) { + isValid := 1 + mstore(0x60, 0) // Restore the zero slot. + mstore(0x40, m) // Restore the free memory pointer. + break + } + + let f := shl(224, 0x1626ba7e) + mstore(m, f) // `bytes4(keccak256("isValidSignature(bytes32,bytes)"))`. + mstore(add(m, 0x04), hash) + let d := add(m, 0x24) + mstore(d, 0x40) // The offset of the `signature` in the calldata. + mstore(add(m, 0x44), 65) // Length of the signature. + mstore(add(m, 0x64), r) // `r`. + mstore(add(m, 0x84), mload(0x60)) // `s`. + mstore8(add(m, 0xa4), mload(0x20)) // `v`. + // forgefmt: disable-next-item + isValid := and( + // Whether the returndata is the magic value `0x1626ba7e` (left-aligned). + eq(mload(d), f), + // Whether the staticcall does not revert. + // This must be placed at the end of the `and` clause, + // as the arguments are evaluated from right to left. + staticcall( + gas(), // Remaining gas. + signer, // The `signer` address. + m, // Offset of calldata in memory. + 0xa5, // Length of calldata in memory. + d, // Offset of returndata. + 0x20 // Length of returndata to write. + ) + ) + mstore(0x60, 0) // Restore the zero slot. + mstore(0x40, m) // Restore the free memory pointer. + break + } + } + } + + /// @dev Returns whether the signature (`v`, `r`, `s`) is valid for `signer` and `hash`. + /// If `signer` is a smart contract, the signature is validated with ERC1271. + /// Otherwise, the signature is validated with `ECDSA.recover`. + function isValidSignatureNow(address signer, bytes32 hash, uint8 v, bytes32 r, bytes32 s) + internal + view + returns (bool isValid) + { + /// @solidity memory-safe-assembly + assembly { + // Clean the upper 96 bits of `signer` in case they are dirty. + for { signer := shr(96, shl(96, signer)) } signer {} { + let m := mload(0x40) + mstore(0x00, hash) + mstore(0x20, and(v, 0xff)) // `v`. + mstore(0x40, r) // `r`. + mstore(0x60, s) // `s`. + let t := + staticcall( + gas(), // Amount of gas left for the transaction. + 1, // Address of `ecrecover`. + 0x00, // Start of input. + 0x80, // Size of input. + 0x01, // Start of output. + 0x20 // Size of output. + ) + // `returndatasize()` will be `0x20` upon success, and `0x00` otherwise. + if iszero(or(iszero(returndatasize()), xor(signer, mload(t)))) { + isValid := 1 + mstore(0x60, 0) // Restore the zero slot. + mstore(0x40, m) // Restore the free memory pointer. + break + } + + let f := shl(224, 0x1626ba7e) + mstore(m, f) // `bytes4(keccak256("isValidSignature(bytes32,bytes)"))`. + mstore(add(m, 0x04), hash) + let d := add(m, 0x24) + mstore(d, 0x40) // The offset of the `signature` in the calldata. + mstore(add(m, 0x44), 65) // Length of the signature. + mstore(add(m, 0x64), r) // `r`. + mstore(add(m, 0x84), s) // `s`. + mstore8(add(m, 0xa4), v) // `v`. + // forgefmt: disable-next-item + isValid := and( + // Whether the returndata is the magic value `0x1626ba7e` (left-aligned). + eq(mload(d), f), + // Whether the staticcall does not revert. + // This must be placed at the end of the `and` clause, + // as the arguments are evaluated from right to left. + staticcall( + gas(), // Remaining gas. + signer, // The `signer` address. + m, // Offset of calldata in memory. + 0xa5, // Length of calldata in memory. + d, // Offset of returndata. + 0x20 // Length of returndata to write. + ) + ) + mstore(0x60, 0) // Restore the zero slot. + mstore(0x40, m) // Restore the free memory pointer. + break + } + } + } + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* ERC1271 OPERATIONS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev Returns whether `signature` is valid for `hash` for an ERC1271 `signer` contract. + function isValidERC1271SignatureNow(address signer, bytes32 hash, bytes memory signature) + internal + view + returns (bool isValid) + { + /// @solidity memory-safe-assembly + assembly { + let m := mload(0x40) + let f := shl(224, 0x1626ba7e) + mstore(m, f) // `bytes4(keccak256("isValidSignature(bytes32,bytes)"))`. + mstore(add(m, 0x04), hash) + let d := add(m, 0x24) + mstore(d, 0x40) // The offset of the `signature` in the calldata. + // Copy the `signature` over. + let n := add(0x20, mload(signature)) + pop(staticcall(gas(), 4, signature, n, add(m, 0x44), n)) + // forgefmt: disable-next-item + isValid := and( + // Whether the returndata is the magic value `0x1626ba7e` (left-aligned). + eq(mload(d), f), + // Whether the staticcall does not revert. + // This must be placed at the end of the `and` clause, + // as the arguments are evaluated from right to left. + staticcall( + gas(), // Remaining gas. + signer, // The `signer` address. + m, // Offset of calldata in memory. + add(returndatasize(), 0x44), // Length of calldata in memory. + d, // Offset of returndata. + 0x20 // Length of returndata to write. + ) + ) + } + } + + /// @dev Returns whether `signature` is valid for `hash` for an ERC1271 `signer` contract. + function isValidERC1271SignatureNowCalldata( + address signer, + bytes32 hash, + bytes calldata signature + ) internal view returns (bool isValid) { + /// @solidity memory-safe-assembly + assembly { + let m := mload(0x40) + let f := shl(224, 0x1626ba7e) + mstore(m, f) // `bytes4(keccak256("isValidSignature(bytes32,bytes)"))`. + mstore(add(m, 0x04), hash) + let d := add(m, 0x24) + mstore(d, 0x40) // The offset of the `signature` in the calldata. + mstore(add(m, 0x44), signature.length) + // Copy the `signature` over. + calldatacopy(add(m, 0x64), signature.offset, signature.length) + // forgefmt: disable-next-item + isValid := and( + // Whether the returndata is the magic value `0x1626ba7e` (left-aligned). + eq(mload(d), f), + // Whether the staticcall does not revert. + // This must be placed at the end of the `and` clause, + // as the arguments are evaluated from right to left. + staticcall( + gas(), // Remaining gas. + signer, // The `signer` address. + m, // Offset of calldata in memory. + add(signature.length, 0x64), // Length of calldata in memory. + d, // Offset of returndata. + 0x20 // Length of returndata to write. + ) + ) + } + } + + /// @dev Returns whether the signature (`r`, `vs`) is valid for `hash` + /// for an ERC1271 `signer` contract. + function isValidERC1271SignatureNow(address signer, bytes32 hash, bytes32 r, bytes32 vs) + internal + view + returns (bool isValid) + { + /// @solidity memory-safe-assembly + assembly { + let m := mload(0x40) + let f := shl(224, 0x1626ba7e) + mstore(m, f) // `bytes4(keccak256("isValidSignature(bytes32,bytes)"))`. + mstore(add(m, 0x04), hash) + let d := add(m, 0x24) + mstore(d, 0x40) // The offset of the `signature` in the calldata. + mstore(add(m, 0x44), 65) // Length of the signature. + mstore(add(m, 0x64), r) // `r`. + mstore(add(m, 0x84), shr(1, shl(1, vs))) // `s`. + mstore8(add(m, 0xa4), add(shr(255, vs), 27)) // `v`. + // forgefmt: disable-next-item + isValid := and( + // Whether the returndata is the magic value `0x1626ba7e` (left-aligned). + eq(mload(d), f), + // Whether the staticcall does not revert. + // This must be placed at the end of the `and` clause, + // as the arguments are evaluated from right to left. + staticcall( + gas(), // Remaining gas. + signer, // The `signer` address. + m, // Offset of calldata in memory. + 0xa5, // Length of calldata in memory. + d, // Offset of returndata. + 0x20 // Length of returndata to write. + ) + ) + } + } + + /// @dev Returns whether the signature (`v`, `r`, `s`) is valid for `hash` + /// for an ERC1271 `signer` contract. + function isValidERC1271SignatureNow(address signer, bytes32 hash, uint8 v, bytes32 r, bytes32 s) + internal + view + returns (bool isValid) + { + /// @solidity memory-safe-assembly + assembly { + let m := mload(0x40) + let f := shl(224, 0x1626ba7e) + mstore(m, f) // `bytes4(keccak256("isValidSignature(bytes32,bytes)"))`. + mstore(add(m, 0x04), hash) + let d := add(m, 0x24) + mstore(d, 0x40) // The offset of the `signature` in the calldata. + mstore(add(m, 0x44), 65) // Length of the signature. + mstore(add(m, 0x64), r) // `r`. + mstore(add(m, 0x84), s) // `s`. + mstore8(add(m, 0xa4), v) // `v`. + // forgefmt: disable-next-item + isValid := and( + // Whether the returndata is the magic value `0x1626ba7e` (left-aligned). + eq(mload(d), f), + // Whether the staticcall does not revert. + // This must be placed at the end of the `and` clause, + // as the arguments are evaluated from right to left. + staticcall( + gas(), // Remaining gas. + signer, // The `signer` address. + m, // Offset of calldata in memory. + 0xa5, // Length of calldata in memory. + d, // Offset of returndata. + 0x20 // Length of returndata to write. + ) + ) + } + } + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* HASHING OPERATIONS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev Returns an Ethereum Signed Message, created from a `hash`. + /// This produces a hash corresponding to the one signed with the + /// [`eth_sign`](https://eth.wiki/json-rpc/API#eth_sign) + /// JSON-RPC method as part of EIP-191. + function toEthSignedMessageHash(bytes32 hash) internal pure returns (bytes32 result) { + /// @solidity memory-safe-assembly + assembly { + mstore(0x20, hash) // Store into scratch space for keccak256. + mstore(0x00, "\x00\x00\x00\x00\x19Ethereum Signed Message:\n32") // 28 bytes. + result := keccak256(0x04, 0x3c) // `32 * 2 - (32 - 28) = 60 = 0x3c`. + } + } + + /// @dev Returns an Ethereum Signed Message, created from `s`. + /// This produces a hash corresponding to the one signed with the + /// [`eth_sign`](https://eth.wiki/json-rpc/API#eth_sign) + /// JSON-RPC method as part of EIP-191. + /// Note: Supports lengths of `s` up to 999999 bytes. + function toEthSignedMessageHash(bytes memory s) internal pure returns (bytes32 result) { + /// @solidity memory-safe-assembly + assembly { + let sLength := mload(s) + let o := 0x20 + mstore(o, "\x19Ethereum Signed Message:\n") // 26 bytes, zero-right-padded. + mstore(0x00, 0x00) + // Convert the `s.length` to ASCII decimal representation: `base10(s.length)`. + for { let temp := sLength } 1 {} { + o := sub(o, 1) + mstore8(o, add(48, mod(temp, 10))) + temp := div(temp, 10) + if iszero(temp) { break } + } + let n := sub(0x3a, o) // Header length: `26 + 32 - o`. + // Throw an out-of-offset error (consumes all gas) if the header exceeds 32 bytes. + returndatacopy(returndatasize(), returndatasize(), gt(n, 0x20)) + mstore(s, or(mload(0x00), mload(n))) // Temporarily store the header. + result := keccak256(add(s, sub(0x20, n)), add(n, sLength)) + mstore(s, sLength) // Restore the length. + } + } + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* EMPTY CALLDATA HELPERS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev Returns an empty calldata bytes. + function emptySignature() internal pure returns (bytes calldata signature) { + /// @solidity memory-safe-assembly + assembly { + signature.length := 0 + } + } +} diff --git a/rhinestone/registry/1.0.0/remappings.txt b/rhinestone/registry/1.0.0/remappings.txt new file mode 100644 index 0000000..2130a59 --- /dev/null +++ b/rhinestone/registry/1.0.0/remappings.txt @@ -0,0 +1,19 @@ +forge-std/=lib/forge-std/src/ +@safe-global/=node_modules/@safe-global/ +ds-test/=node_modules/ds-test/src/ +safe-singleton-deployer/=node_modules/safe-singleton-deployer/src/ +@rhinestone/=node_modules/@rhinestone/ +@openzeppelin/=node_modules/@openzeppelin/ +solmate/=node_modules/solmate/src/ +solady/=node_modules/solady/src/ +erc7579/=node_modules/erc7579/src/ +solarray/=node_modules/solarray/src/ +@prb/math/=node_modules/@prb/math/src/ +@ERC4337/=node_modules/@ERC4337/ +sentinellist/=node_modules/@rhinestone/sentinellist/src/ +account-abstraction/=node_modules/@ERC4337/account-abstraction/contracts/ +account-abstraction-v0.6/=node_modules/@ERC4337/account-abstraction-v0.6/contracts/ +surl/=node_modules/surl/src/ +@gnosis.pm/=node_modules/@gnosis.pm/ +hardhat-deploy/=node_modules/hardhat-deploy/ +hardhat/=node_modules/hardhat/ \ No newline at end of file diff --git a/rhinestone/registry/Makefile b/rhinestone/registry/Makefile new file mode 100644 index 0000000..7ef2efe --- /dev/null +++ b/rhinestone/registry/Makefile @@ -0,0 +1,7 @@ +VERSIONS += 1.0.0 + +.PHONY: all $(VERSIONS) +all: $(VERSIONS) + +$(VERSIONS): + @$(MAKE) -C $@ -f ../../../Makefile.project