diff --git a/.release-please-manifest.json b/.release-please-manifest.json new file mode 100644 index 000000000..8032c17e8 --- /dev/null +++ b/.release-please-manifest.json @@ -0,0 +1,3 @@ +{ + ".": "0.12.0" +} diff --git a/constants.ts b/constants.ts index aca2e5f28..6b614ea6a 100644 --- a/constants.ts +++ b/constants.ts @@ -123,8 +123,11 @@ export type LSP4DigitalAssetMetadata = { export type ImageMetadata = { width: number; height: number; - hashFunction: string; - hash: string; + verification?: { + method: string; + data: string; + source?: string; + }; url: string; }; @@ -134,8 +137,11 @@ export type LinkMetadata = { }; export type AssetMetadata = { - hashFunction: string; - hash: string; + verification?: { + method: string; + data: string; + source?: string; + }; url: string; fileType: string; }; diff --git a/contracts/LSP0ERC725Account/LSP0Constants.sol b/contracts/LSP0ERC725Account/LSP0Constants.sol index 600f9426a..31a4d4ddb 100644 --- a/contracts/LSP0ERC725Account/LSP0Constants.sol +++ b/contracts/LSP0ERC725Account/LSP0Constants.sol @@ -14,7 +14,7 @@ bytes4 constant _ERC1271_FAILVALUE = 0xffffffff; // keccak256('LSP0ValueReceived') bytes32 constant _TYPEID_LSP0_VALUE_RECEIVED = 0x9c4705229491d365fb5434052e12a386d6771d976bea61070a8c694e8affea3d; -// Ownerhsip Transfer Type IDs +// Ownership Transfer Type IDs // keccak256('LSP0OwnershipTransferStarted') bytes32 constant _TYPEID_LSP0_OwnershipTransferStarted = 0xe17117c9d2665d1dbeb479ed8058bbebde3c50ac50e2e65619f60006caac6926; diff --git a/contracts/LSP0ERC725Account/LSP0ERC725Account.sol b/contracts/LSP0ERC725Account/LSP0ERC725Account.sol index c8a80460a..10c006c94 100644 --- a/contracts/LSP0ERC725Account/LSP0ERC725Account.sol +++ b/contracts/LSP0ERC725Account/LSP0ERC725Account.sol @@ -2,13 +2,13 @@ pragma solidity ^0.8.4; // modules +import {Version} from "../Version.sol"; import {LSP0ERC725AccountCore} from "./LSP0ERC725AccountCore.sol"; import { OwnableUnset } from "@erc725/smart-contracts/contracts/custom/OwnableUnset.sol"; // constants - import {_TYPEID_LSP0_VALUE_RECEIVED} from "./LSP0Constants.sol"; /** @@ -26,10 +26,14 @@ import {_TYPEID_LSP0_VALUE_RECEIVED} from "./LSP0Constants.sol"; * - Extending the account with new functions and interfaceIds of future standards using [LSP-17-ContractExtension] * - Verifying calls on the owner to make it easier to interact with the account directly using [LSP-20-CallVerification] */ -contract LSP0ERC725Account is LSP0ERC725AccountCore { +contract LSP0ERC725Account is LSP0ERC725AccountCore, Version { /** * @notice Deploying a LSP0ERC725Account contract with owner set to address `initialOwner`. - * @dev Set `initialOwner` as the contract owner. The `constructor` also allows funding the contract on deployment. + * + * @dev Set `initialOwner` as the contract owner. + * - The `constructor` also allows funding the contract on deployment. + * - The `initialOwner` will then be allowed to call protected functions marked with the `onlyOwner` modifier. + * * @param initialOwner The owner of the contract. * * @custom:events diff --git a/contracts/LSP0ERC725Account/LSP0ERC725AccountCore.sol b/contracts/LSP0ERC725Account/LSP0ERC725AccountCore.sol index 7f6f7e593..97189366b 100644 --- a/contracts/LSP0ERC725Account/LSP0ERC725AccountCore.sol +++ b/contracts/LSP0ERC725Account/LSP0ERC725AccountCore.sol @@ -23,7 +23,6 @@ import {LSP1Utils} from "../LSP1UniversalReceiver/LSP1Utils.sol"; import {LSP2Utils} from "../LSP2ERC725YJSONSchema/LSP2Utils.sol"; // modules -import {Version} from "../Version.sol"; import {ERC725YCore} from "@erc725/smart-contracts/contracts/ERC725YCore.sol"; import {ERC725XCore} from "@erc725/smart-contracts/contracts/ERC725XCore.sol"; import { @@ -78,7 +77,6 @@ import { abstract contract LSP0ERC725AccountCore is ERC725XCore, ERC725YCore, - Version, IERC1271, ILSP0ERC725Account, ILSP1UniversalReceiver, @@ -95,7 +93,10 @@ abstract contract LSP0ERC725AccountCore is * - When receiving some native tokens without any additional data. * - On empty calls to the contract. * - * @custom:events {UniversalReceiver} event when receiving native tokens. + * @custom:info This function internally delegates the handling of native tokens to the {universalReceiver} function + * passing `_TYPEID_LSP0_VALUE_RECEIVED` as typeId and an empty bytes array as received data. + * + * @custom:events Emits a {UniversalReceiver} event when the `universalReceiver` logic is executed upon receiving native tokens. */ receive() external payable virtual { if (msg.value != 0) { @@ -122,16 +123,19 @@ abstract contract LSP0ERC725AccountCore is * * 2. If the data sent to this function is of length less than 4 bytes (not a function selector), return. * + * @custom:info Whenever the call is associated with native tokens, the function will delegate the handling of native tokens internally to the {universalReceiver} function + * passing `_TYPEID_LSP0_VALUE_RECEIVED` as typeId and the calldata as received data, except when the native token will be sent directly to the extension. + * * @custom:events {UniversalReceiver} event when receiving native tokens and extension function selector is not found or not payable. */ - // solhint-disable-next-line no-complex-fallback fallback( bytes calldata callData ) external payable virtual returns (bytes memory) { if (msg.data.length < 4) { // if value is associated with the extension call, use the universalReceiver - if (msg.value != 0) + if (msg.value != 0) { universalReceiver(_TYPEID_LSP0_VALUE_RECEIVED, callData); + } return ""; } @@ -434,6 +438,8 @@ abstract contract LSP0ERC725AccountCore is * * - If yes, call this address with the typeId and data (params), along with additional calldata consisting of 20 bytes of `msg.sender` and 32 bytes of `msg.value`. If not, continue the execution of the function. * + * This function delegates internally the handling of native tokens to the {universalReceiver} function itself passing `_TYPEID_LSP0_VALUE_RECEIVED` as typeId and the calldata as received data. + * * @param typeId The type of call received. * @param receivedData The data received. * @@ -541,9 +547,9 @@ abstract contract LSP0ERC725AccountCore is emit OwnershipTransferStarted(currentOwner, pendingNewOwner); // notify the pending owner through LSP1 - pendingNewOwner.tryNotifyUniversalReceiver( + pendingNewOwner.notifyUniversalReceiver( _TYPEID_LSP0_OwnershipTransferStarted, - "" + abi.encode(currentOwner, pendingNewOwner) ); // reset the transfer ownership lock @@ -561,9 +567,9 @@ abstract contract LSP0ERC725AccountCore is emit OwnershipTransferStarted(currentOwner, pendingNewOwner); // notify the pending owner through LSP1 - pendingNewOwner.tryNotifyUniversalReceiver( + pendingNewOwner.notifyUniversalReceiver( _TYPEID_LSP0_OwnershipTransferStarted, - "" + abi.encode(currentOwner, pendingNewOwner) ); // reset the transfer ownership lock @@ -585,7 +591,7 @@ abstract contract LSP0ERC725AccountCore is * - When notifying the previous owner via LSP1, the typeId used must be the `keccak256(...)` hash of [LSP0OwnershipTransferred_SenderNotification]. * - When notifying the new owner via LSP1, the typeId used must be the `keccak256(...)` hash of [LSP0OwnershipTransferred_RecipientNotification]. */ - function acceptOwnership() public virtual override NotInTransferOwnership { + function acceptOwnership() public virtual override notInTransferOwnership { address previousOwner = owner(); address pendingOwnerAddress = pendingOwner(); @@ -598,20 +604,21 @@ abstract contract LSP0ERC725AccountCore is _setOwner(pendingOwnerAddress); delete _pendingOwner; + delete _renounceOwnershipStartedAt; } else { _acceptOwnership(); } // notify the previous owner if supports LSP1 - previousOwner.tryNotifyUniversalReceiver( + previousOwner.notifyUniversalReceiver( _TYPEID_LSP0_OwnershipTransferred_SenderNotification, - "" + abi.encode(previousOwner, pendingOwnerAddress) ); // notify the pending owner if supports LSP1 - pendingOwnerAddress.tryNotifyUniversalReceiver( + pendingOwnerAddress.notifyUniversalReceiver( _TYPEID_LSP0_OwnershipTransferred_RecipientNotification, - "" + abi.encode(previousOwner, pendingOwnerAddress) ); // If msg.sender != pendingOwnerAddress & verifyAfter is true, Call {lsp20VerifyCallResult} on the new owner @@ -638,27 +645,35 @@ abstract contract LSP0ERC725AccountCore is // If the caller is the owner perform renounceOwnership directly if (msg.sender == accountOwner) { - return LSP14Ownable2Step._renounceOwnership(); - } + address previousOwner = owner(); + LSP14Ownable2Step._renounceOwnership(); - // If the caller is not the owner, call {lsp20VerifyCall} on the owner - // Depending on the returnedStatus, a second call is done after transferring ownership - bool verifyAfter = _verifyCall(accountOwner); + if (owner() == address(0)) { + previousOwner.notifyUniversalReceiver( + _TYPEID_LSP0_OwnershipTransferred_SenderNotification, + abi.encode(accountOwner, address(0)) + ); + } + } else { + // If the caller is not the owner, call {lsp20VerifyCall} on the owner + // Depending on the returnedStatus, a second call is done after transferring ownership + bool verifyAfter = _verifyCall(accountOwner); - address previousOwner = owner(); - LSP14Ownable2Step._renounceOwnership(); + address previousOwner = owner(); + LSP14Ownable2Step._renounceOwnership(); - if (owner() == address(0)) { - previousOwner.tryNotifyUniversalReceiver( - _TYPEID_LSP0_OwnershipTransferred_SenderNotification, - "" - ); - } + if (owner() == address(0)) { + previousOwner.notifyUniversalReceiver( + _TYPEID_LSP0_OwnershipTransferred_SenderNotification, + abi.encode(accountOwner, address(0)) + ); + } - // If verifyAfter is true, Call {lsp20VerifyCallResult} on the owner - // The transferOwnership function does not return, second parameter of {_verifyCallResult} will be empty - if (verifyAfter) { - _verifyCallResult(accountOwner, ""); + // If verifyAfter is true, Call {lsp20VerifyCallResult} on the owner + // The transferOwnership function does not return, second parameter of {_verifyCallResult} will be empty + if (verifyAfter) { + _verifyCallResult(accountOwner, ""); + } } } @@ -717,6 +732,11 @@ abstract contract LSP0ERC725AccountCore is * @param signature A signature that can validate the previous parameter (Hash). * * @return returnedStatus A `bytes4` value that indicates if the signature is valid or not. + * + * @custom:warning This function does not enforce by default the inclusion of the address of this contract in the signature digest. + * It is recommended that protocols or applications using this contract include the targeted address (= this contract) in the data to sign. + * To ensure that a signature is valid for a specific LSP0ERC725Account and prevent signatures from the same EOA to be replayed + * across different LSP0ERC725Accounts. */ function isValidSignature( bytes32 dataHash, @@ -775,14 +795,7 @@ abstract contract LSP0ERC725AccountCore is * If there is an extension for the function selector being called, it calls the extension with the * `CALL` opcode, passing the `msg.data` appended with the 20 bytes of the {msg.sender} and 32 bytes of the `msg.value`. * - * @custom:hint This function does not forward to the extension contract the `msg.value` received by the contract that inherits `LSP17Extendable`. - * If you would like to forward the `msg.value` to the extension contract, you can override the code of this internal function as follow: - * - * ```solidity - * (bool success, bytes memory result) = extension.call{value: msg.value}( - * abi.encodePacked(callData, msg.sender, msg.value) - * ); - * ``` + * @custom:hint If you would like to forward the `msg.value` to the extension contract, you should store an additional `0x01` byte after the address of the extension under the corresponding LSP17 data key. */ function _fallbackLSP17Extendable( bytes calldata callData @@ -794,8 +807,9 @@ abstract contract LSP0ERC725AccountCore is ) = _getExtensionAndForwardValue(msg.sig); // if value is associated with the extension call and extension function selector is not payable, use the universalReceiver - if (msg.value != 0 && !isForwardingValue) + if (msg.value != 0 && !isForwardingValue) { universalReceiver(_TYPEID_LSP0_VALUE_RECEIVED, callData); + } // if no extension was found for bytes4(0) return don't revert if (msg.sig == bytes4(0) && extension == address(0)) return ""; @@ -821,9 +835,10 @@ abstract contract LSP0ERC725AccountCore is } /** - * @dev Returns the extension address stored under the following data key: + * @dev Returns the extension address and the boolean indicating whether to forward the value received to the extension, stored under the following data key: * - {_LSP17_EXTENSION_PREFIX} + `` (Check [LSP2-ERC725YJSONSchema] for encoding the data key). * - If no extension is stored, returns the address(0). + * - If the stored value is 20 bytes, return false for the boolean */ function _getExtensionAndForwardValue( bytes4 functionSelector @@ -838,6 +853,12 @@ abstract contract LSP0ERC725AccountCore is mappedExtensionDataKey ); + // Prevent casting data shorter than 20 bytes to an address to avoid + // unintentionally calling a different extension, return address(0) instead. + if (extensionData.length < 20) { + return (address(0), false); + } + // CHECK if the `extensionData` is 21 bytes long // - 20 bytes = extension's address // - 1 byte `0x01` as a boolean indicating if the contract should forward the value to the extension or not diff --git a/contracts/LSP0ERC725Account/LSP0ERC725AccountInit.sol b/contracts/LSP0ERC725Account/LSP0ERC725AccountInit.sol index bbbeaded1..c0fa0d2ce 100644 --- a/contracts/LSP0ERC725Account/LSP0ERC725AccountInit.sol +++ b/contracts/LSP0ERC725Account/LSP0ERC725AccountInit.sol @@ -2,6 +2,7 @@ pragma solidity ^0.8.4; // modules +import {Version} from "../Version.sol"; import { LSP0ERC725AccountInitAbstract } from "./LSP0ERC725AccountInitAbstract.sol"; @@ -21,7 +22,7 @@ import { * - Extending the account with new functions and interfaceIds of future standards using [LSP-17-ContractExtension] * - Verifying calls on the owner to make it easier to interact with the account directly using [LSP-20-CallVerification] */ -contract LSP0ERC725AccountInit is LSP0ERC725AccountInitAbstract { +contract LSP0ERC725AccountInit is LSP0ERC725AccountInitAbstract, Version { /** * @notice deploying a `LSP0ERC725AccountInit` base contract to be used behind proxy * @dev Locks the base contract on deployment, so that it cannot be initialized, owned and controlled by anyone after it has been deployed. This is intended so that the sole purpose of this contract is to be used as a base contract behind a proxy. diff --git a/contracts/LSP0ERC725Account/LSP0ERC725AccountInitAbstract.sol b/contracts/LSP0ERC725Account/LSP0ERC725AccountInitAbstract.sol index 5091710ba..1ba4f4d38 100644 --- a/contracts/LSP0ERC725Account/LSP0ERC725AccountInitAbstract.sol +++ b/contracts/LSP0ERC725Account/LSP0ERC725AccountInitAbstract.sol @@ -24,10 +24,12 @@ abstract contract LSP0ERC725AccountInitAbstract is { /** * @dev Set `initialOwner` as the contract owner. + * The `initialOwner` will then be allowed to call protected functions marked with the `onlyOwner` modifier. * * @param initialOwner The owner of the contract. * - * @custom:warning ERC725X & ERC725Y parent contracts are not initialised as they don't have non-zero initial state. If you decide to add non-zero initial state to any of those contracts, you must initialize them here. + * @custom:warning ERC725X & ERC725Y parent contracts are not initialixed as they don't have non-zero initial state. + * If you decide to add non-zero initial state to any of those contracts, you MUST initialize them here. * * @custom:events * - {UniversalReceiver} event when funding the contract on deployment. diff --git a/contracts/LSP14Ownable2Step/LSP14Ownable2Step.sol b/contracts/LSP14Ownable2Step/LSP14Ownable2Step.sol index 124eb1a75..561e27c83 100644 --- a/contracts/LSP14Ownable2Step/LSP14Ownable2Step.sol +++ b/contracts/LSP14Ownable2Step/LSP14Ownable2Step.sol @@ -65,7 +65,7 @@ abstract contract LSP14Ownable2Step is ILSP14Ownable2Step, OwnableUnset { /** * @dev reverts when {_inTransferOwnership} variable is true */ - modifier NotInTransferOwnership() virtual { + modifier notInTransferOwnership() virtual { if (_inTransferOwnership) { revert LSP14MustAcceptOwnershipInSeparateTransaction(); } @@ -97,9 +97,9 @@ abstract contract LSP14Ownable2Step is ILSP14Ownable2Step, OwnableUnset { address currentOwner = owner(); emit OwnershipTransferStarted(currentOwner, newOwner); - newOwner.tryNotifyUniversalReceiver( + newOwner.notifyUniversalReceiver( _TYPEID_LSP14_OwnershipTransferStarted, - "" + abi.encode(currentOwner, newOwner) ); // reset the transfer ownership lock @@ -111,19 +111,19 @@ abstract contract LSP14Ownable2Step is ILSP14Ownable2Step, OwnableUnset { * * @custom:requirements This function can only be called by the {pendingOwner()}. */ - function acceptOwnership() public virtual override NotInTransferOwnership { + function acceptOwnership() public virtual override notInTransferOwnership { address previousOwner = owner(); _acceptOwnership(); - previousOwner.tryNotifyUniversalReceiver( + previousOwner.notifyUniversalReceiver( _TYPEID_LSP14_OwnershipTransferred_SenderNotification, - "" + abi.encode(previousOwner, msg.sender) ); - msg.sender.tryNotifyUniversalReceiver( + msg.sender.notifyUniversalReceiver( _TYPEID_LSP14_OwnershipTransferred_RecipientNotification, - "" + abi.encode(previousOwner, msg.sender) ); } @@ -142,9 +142,9 @@ abstract contract LSP14Ownable2Step is ILSP14Ownable2Step, OwnableUnset { _renounceOwnership(); if (owner() == address(0)) { - previousOwner.tryNotifyUniversalReceiver( + previousOwner.notifyUniversalReceiver( _TYPEID_LSP14_OwnershipTransferred_SenderNotification, - "" + abi.encode(previousOwner, address(0)) ); } } @@ -175,6 +175,7 @@ abstract contract LSP14Ownable2Step is ILSP14Ownable2Step, OwnableUnset { _setOwner(msg.sender); delete _pendingOwner; + delete _renounceOwnershipStartedAt; } /** diff --git a/contracts/LSP17Extensions/OnERC721ReceivedExtension.sol b/contracts/LSP17Extensions/OnERC721ReceivedExtension.sol index 47f6be65e..887c39802 100644 --- a/contracts/LSP17Extensions/OnERC721ReceivedExtension.sol +++ b/contracts/LSP17Extensions/OnERC721ReceivedExtension.sol @@ -4,12 +4,13 @@ pragma solidity ^0.8.4; import { ERC721Holder } from "@openzeppelin/contracts/token/ERC721/utils/ERC721Holder.sol"; +import {LSP17Extension} from "../LSP17ContractExtension/LSP17Extension.sol"; /** * @dev LSP17 Extension that can be attached to a LSP17Extendable contract * to allow it to receive ERC721 tokens via `safeTransferFrom`. */ // solhint-disable-next-line no-empty-blocks -contract OnERC721ReceivedExtension is ERC721Holder { +contract OnERC721ReceivedExtension is ERC721Holder, LSP17Extension { } diff --git a/contracts/LSP1UniversalReceiver/LSP1UniversalReceiverDelegateUP/LSP1UniversalReceiverDelegateUP.sol b/contracts/LSP1UniversalReceiver/LSP1UniversalReceiverDelegateUP/LSP1UniversalReceiverDelegateUP.sol index 89b67a221..d2b82a414 100644 --- a/contracts/LSP1UniversalReceiver/LSP1UniversalReceiverDelegateUP/LSP1UniversalReceiverDelegateUP.sol +++ b/contracts/LSP1UniversalReceiver/LSP1UniversalReceiverDelegateUP/LSP1UniversalReceiverDelegateUP.sol @@ -11,8 +11,8 @@ import { import {ILSP7DigitalAsset} from "../../LSP7DigitalAsset/ILSP7DigitalAsset.sol"; // modules -import {Version} from "../../Version.sol"; import {ERC165} from "@openzeppelin/contracts/utils/introspection/ERC165.sol"; +import {Version} from "../../Version.sol"; // libraries import { @@ -60,6 +60,26 @@ contract LSP1UniversalReceiverDelegateUP is { using ERC165Checker for address; + /** + * @dev When receiving notifications about: + * - LSP7 Tokens sent or received + * - LSP8 Tokens sent or received + * - LSP9 Vaults sent or received + * The notifier should be either the LSP7 or LSP8 or LSP9 contract. + * + * We revert to avoid registering the EOA as asset (spam protection) + * if we received a typeId associated with tokens or vaults transfers. + * + * @param notifier The address that notified. + */ + modifier notEOA(address notifier) { + // solhint-disable-next-line avoid-tx-origin + if (notifier == tx.origin) { + revert CannotRegisterEOAsAsAssets(notifier); + } + _; + } + /** * @dev * 1. Writes the data keys of the received [LSP-7-DigitalAsset], [LSP-8-IdentifiableDigitalAsset] and [LSP-9-Vault] contract addresses into the account storage according to the [LSP-5-ReceivedAssets] and [LSP-10-ReceivedVaults] Standard. @@ -123,14 +143,9 @@ contract LSP1UniversalReceiverDelegateUP is * * @param notifier The LSP7 or LSP8 token address. */ - function _tokenSender(address notifier) internal returns (bytes memory) { - // The notifier is supposed to be either the LSP7 or LSP8 or LSP9 contract - // If it's EOA we revert (spam protection) - // solhint-disable-next-line avoid-tx-origin - if (notifier == tx.origin) { - revert CannotRegisterEOAsAsAssets(notifier); - } - + function _tokenSender( + address notifier + ) internal notEOA(notifier) returns (bytes memory) { // if the amount sent is not the full balance, then do not update the keys try ILSP7DigitalAsset(notifier).balanceOf(msg.sender) returns ( uint256 balance @@ -168,14 +183,7 @@ contract LSP1UniversalReceiverDelegateUP is function _tokenRecipient( address notifier, bytes4 interfaceId - ) internal returns (bytes memory) { - // The notifier is supposed to be either the LSP7 or LSP8 or LSP9 contract - // If it's EOA we revert to avoid registering the EOA as asset or vault (spam protection) - // solhint-disable-next-line avoid-tx-origin - if (notifier == tx.origin) { - revert CannotRegisterEOAsAsAssets(notifier); - } - + ) internal notEOA(notifier) returns (bytes memory) { // CHECK balance only when the Token contract is already deployed, // not when tokens are being transferred on deployment through the `constructor` if (notifier.code.length != 0) { @@ -213,14 +221,9 @@ contract LSP1UniversalReceiverDelegateUP is * * @param notifier The LSP9 vault address. */ - function _vaultSender(address notifier) internal returns (bytes memory) { - // The notifier is supposed to be either the LSP7 or LSP8 or LSP9 contract - // If it's EOA we revert (spam protection) - // solhint-disable-next-line avoid-tx-origin - if (notifier == tx.origin) { - revert CannotRegisterEOAsAsAssets(notifier); - } - + function _vaultSender( + address notifier + ) internal notEOA(notifier) returns (bytes memory) { (bytes32[] memory dataKeys, bytes[] memory dataValues) = LSP10Utils .generateSentVaultKeys(msg.sender, notifier); @@ -243,14 +246,9 @@ contract LSP1UniversalReceiverDelegateUP is * * @param notifier The LSP9 vault address. */ - function _vaultRecipient(address notifier) internal returns (bytes memory) { - // The notifier is supposed to be either the LSP7 or LSP8 or LSP9 contract - // If it's EOA we revert to avoid registering the EOA as asset or vault (spam protection) - // solhint-disable-next-line avoid-tx-origin - if (notifier == tx.origin) { - revert CannotRegisterEOAsAsAssets(notifier); - } - + function _vaultRecipient( + address notifier + ) internal notEOA(notifier) returns (bytes memory) { (bytes32[] memory dataKeys, bytes[] memory dataValues) = LSP10Utils .generateReceivedVaultKeys(msg.sender, notifier); diff --git a/contracts/LSP1UniversalReceiver/LSP1UniversalReceiverDelegateVault/LSP1UniversalReceiverDelegateVault.sol b/contracts/LSP1UniversalReceiver/LSP1UniversalReceiverDelegateVault/LSP1UniversalReceiverDelegateVault.sol index 994ec5347..1e0f3eb67 100644 --- a/contracts/LSP1UniversalReceiver/LSP1UniversalReceiverDelegateVault/LSP1UniversalReceiverDelegateVault.sol +++ b/contracts/LSP1UniversalReceiver/LSP1UniversalReceiverDelegateVault/LSP1UniversalReceiverDelegateVault.sol @@ -11,8 +11,8 @@ import { import {ILSP7DigitalAsset} from "../../LSP7DigitalAsset/ILSP7DigitalAsset.sol"; // modules -import {Version} from "../../Version.sol"; import {ERC165} from "@openzeppelin/contracts/utils/introspection/ERC165.sol"; +import {Version} from "../../Version.sol"; // libraries import {LSP5Utils} from "../../LSP5ReceivedAssets/LSP5Utils.sol"; @@ -48,6 +48,25 @@ contract LSP1UniversalReceiverDelegateVault is Version, ILSP1UniversalReceiverDelegate { + /** + * @dev When receiving notifications about: + * - LSP7 Tokens sent or received + * - LSP8 Tokens sent or received + * The notifier should be either the LSP7 or LSP8 contract. + * + * We revert to avoid registering the EOA as asset (spam protection) + * if we received a typeId associated with tokens transfers. + * + * @param notifier The address that notified. + */ + modifier notEOA(address notifier) { + // solhint-disable-next-line avoid-tx-origin + if (notifier == tx.origin) { + revert CannotRegisterEOAsAsAssets(notifier); + } + _; + } + /** * @inheritdoc ILSP1UniversalReceiverDelegate * @dev Handles two cases: @@ -99,14 +118,9 @@ contract LSP1UniversalReceiverDelegateVault is * * @param notifier The LSP7 or LSP8 token address. */ - function _tokenSender(address notifier) internal returns (bytes memory) { - // The notifier is supposed to be either the LSP7 or LSP8 contract - // If it's EOA we revert to avoid registering the EOA as asset (spam protection) - // solhint-disable-next-line avoid-tx-origin - if (notifier == tx.origin) { - revert CannotRegisterEOAsAsAssets(notifier); - } - + function _tokenSender( + address notifier + ) internal notEOA(notifier) returns (bytes memory) { // if the amount sent is not the full balance, then do not update the keys try ILSP7DigitalAsset(notifier).balanceOf(msg.sender) returns ( uint256 balance @@ -144,14 +158,7 @@ contract LSP1UniversalReceiverDelegateVault is function _tokenRecipient( address notifier, bytes4 interfaceId - ) internal returns (bytes memory) { - // The notifier is supposed to be either the LSP7 or LSP8 contract - // If it's EOA we revert to avoid registering the EOA as asset (spam protection) - // solhint-disable-next-line avoid-tx-origin - if (notifier == tx.origin) { - revert CannotRegisterEOAsAsAssets(notifier); - } - + ) internal notEOA(notifier) returns (bytes memory) { // CHECK balance only when the Token contract is already deployed, // not when tokens are being transferred on deployment through the `constructor` if (notifier.code.length != 0) { diff --git a/contracts/LSP1UniversalReceiver/LSP1Utils.sol b/contracts/LSP1UniversalReceiver/LSP1Utils.sol index 20f954851..026c3e8e9 100644 --- a/contracts/LSP1UniversalReceiver/LSP1Utils.sol +++ b/contracts/LSP1UniversalReceiver/LSP1Utils.sol @@ -35,7 +35,7 @@ library LSP1Utils { * @param typeId A `bytes32` typeId. * @param data Any optional data to send to the `universalReceiver` function to the `lsp1Implementation` address. */ - function tryNotifyUniversalReceiver( + function notifyUniversalReceiver( address lsp1Implementation, bytes32 typeId, bytes memory data diff --git a/contracts/LSP25ExecuteRelayCall/LSP25MultiChannelNonce.sol b/contracts/LSP25ExecuteRelayCall/LSP25MultiChannelNonce.sol index fb8c99015..7e43cb9d9 100644 --- a/contracts/LSP25ExecuteRelayCall/LSP25MultiChannelNonce.sol +++ b/contracts/LSP25ExecuteRelayCall/LSP25MultiChannelNonce.sol @@ -139,10 +139,6 @@ abstract contract LSP25MultiChannelNonce { address from, uint256 idx ) internal view virtual returns (bool) { - uint256 mask = ~uint128(0); - // Alternatively: - // uint256 mask = (1<<128)-1; - // uint256 mask = 0xffffffffffffffffffffffffffffffff; - return (idx & mask) == _nonceStore[from][idx >> 128]; + return uint128(idx) == _nonceStore[from][idx >> 128]; } } diff --git a/contracts/LSP6KeyManager/LSP6KeyManager.sol b/contracts/LSP6KeyManager/LSP6KeyManager.sol index 6e9e9cc32..f30ce78de 100644 --- a/contracts/LSP6KeyManager/LSP6KeyManager.sol +++ b/contracts/LSP6KeyManager/LSP6KeyManager.sol @@ -2,6 +2,7 @@ pragma solidity ^0.8.5; // modules +import {Version} from "../Version.sol"; import {LSP6KeyManagerCore} from "./LSP6KeyManagerCore.sol"; import {InvalidLSP6Target} from "./LSP6Errors.sol"; @@ -10,7 +11,7 @@ import {InvalidLSP6Target} from "./LSP6Errors.sol"; * @author Fabian Vogelsteller , Jean Cavallera (CJ42), Yamen Merhi (YamenMerhi) * @dev All the permissions can be set on the ERC725 Account using `setData(bytes32,bytes)` or `setData(bytes32[],bytes[])`. */ -contract LSP6KeyManager is LSP6KeyManagerCore { +contract LSP6KeyManager is LSP6KeyManagerCore, Version { /** * @notice Deploying a LSP6KeyManager linked to the contract at address `target_`. * @dev Deploy a Key Manager and set the `target_` address in the contract storage, diff --git a/contracts/LSP6KeyManager/LSP6KeyManagerCore.sol b/contracts/LSP6KeyManager/LSP6KeyManagerCore.sol index 54513b0af..b4dae6dcb 100644 --- a/contracts/LSP6KeyManager/LSP6KeyManagerCore.sol +++ b/contracts/LSP6KeyManager/LSP6KeyManagerCore.sol @@ -18,7 +18,6 @@ import { } from "../LSP25ExecuteRelayCall/ILSP25ExecuteRelayCall.sol"; // modules -import {Version} from "../Version.sol"; import {ILSP14Ownable2Step} from "../LSP14Ownable2Step/ILSP14Ownable2Step.sol"; import {ERC725Y} from "@erc725/smart-contracts/contracts/ERC725Y.sol"; import {ERC165} from "@openzeppelin/contracts/utils/introspection/ERC165.sol"; @@ -88,7 +87,6 @@ abstract contract LSP6KeyManagerCore is ILSP6, ILSP20, ILSP25, - Version, LSP6SetDataModule, LSP6ExecuteModule, LSP6ExecuteRelayCallModule, @@ -152,6 +150,11 @@ abstract contract LSP6KeyManagerCore is * If the signer is a controller with the permission `SIGN`, it will return the ERC1271 success value. * * @return returnedStatus `0x1626ba7e` on success, or `0xffffffff` on failure. + * + * @custom:warning This function does not enforce by default the inclusion of the address of this contract in the signature digest. + * It is recommended that protocols or applications using this contract include the targeted address (= this contract) in the data to sign. + * To ensure that a signature is valid for a specific LSP6KeyManager and prevent signatures from the same EOA to be replayed + * across different LSP6KeyManager. */ function isValidSignature( bytes32 dataHash, @@ -510,13 +513,14 @@ abstract contract LSP6KeyManagerCore is value: msgValue, gas: gasleft() }(payload); + bytes memory result = Address.verifyCallResult( success, returnData, "LSP6: failed executing payload" ); - return result.length != 0 ? abi.decode(result, (bytes)) : result; + return result; } /** @@ -612,7 +616,7 @@ abstract contract LSP6KeyManagerCore is revert ERC725X_ExecuteParametersEmptyArray(); } - for (uint256 ii = 0; ii < operationTypes.length; ii++) { + for (uint256 ii; ii < operationTypes.length; ii++) { LSP6ExecuteModule._verifyCanExecute( targetContract, from, @@ -635,9 +639,11 @@ abstract contract LSP6KeyManagerCore is } /** - * @dev Update the status from `_NON_ENTERED` to `_ENTERED` and checks if - * the status is `_ENTERED` in order to revert the call unless the caller has the REENTRANCY permission - * Used in the beginning of the `nonReentrant` modifier, before the method execution starts. + * @dev Check if we are in the context of a reentrant call, by checking if the reentrancy status is `true`. + * - If the status is `true`, the caller (or signer for relay call) MUST have the `REENTRANCY` permission. Otherwise, the call is reverted. + * - If the status is `false`, it is set to `true` only if we are not dealing with a call to the functions `setData` or `setDataBatch`. + * Used at the beginning of the {`lsp20VerifyCall`}, {`_execute`} and {`_executeRelayCall`} functions, before the methods execution starts. + * */ function _nonReentrantBefore( address targetContract, @@ -661,8 +667,8 @@ abstract contract LSP6KeyManagerCore is } /** - * @dev Resets the status to `false` - * Used in the end of the `nonReentrant` modifier after the method execution is terminated + * @dev Resets the reentrancy status to `false` + * Used at the end of the {`lsp20VerifyCall`}, {`_execute`} and {`_executeRelayCall`} functions after the functions' execution is terminated. */ function _nonReentrantAfter(address targetContract) internal virtual { // By storing the original value once again, a refund is triggered (see diff --git a/contracts/LSP6KeyManager/LSP6KeyManagerInit.sol b/contracts/LSP6KeyManager/LSP6KeyManagerInit.sol index dc55b8fdd..92570729b 100644 --- a/contracts/LSP6KeyManager/LSP6KeyManagerInit.sol +++ b/contracts/LSP6KeyManager/LSP6KeyManagerInit.sol @@ -2,6 +2,7 @@ pragma solidity ^0.8.5; // modules +import {Version} from "../Version.sol"; import {LSP6KeyManagerInitAbstract} from "./LSP6KeyManagerInitAbstract.sol"; /** @@ -9,7 +10,7 @@ import {LSP6KeyManagerInitAbstract} from "./LSP6KeyManagerInitAbstract.sol"; * @author Fabian Vogelsteller , Jean Cavallera (CJ42), Yamen Merhi (YamenMerhi) * @dev All the permissions can be set on the ERC725 Account using `setData(...)` with the keys constants below */ -contract LSP6KeyManagerInit is LSP6KeyManagerInitAbstract { +contract LSP6KeyManagerInit is LSP6KeyManagerInitAbstract, Version { /** * @notice Deploying a LSP6KeyManagerInit to be used as base contract behind proxy. * @dev Initialize (= lock) base implementation contract on deployment. diff --git a/contracts/LSP6KeyManager/LSP6Modules/LSP6ExecuteModule.sol b/contracts/LSP6KeyManager/LSP6Modules/LSP6ExecuteModule.sol index f3aab5d24..04d5d90ba 100644 --- a/contracts/LSP6KeyManager/LSP6Modules/LSP6ExecuteModule.sol +++ b/contracts/LSP6KeyManager/LSP6Modules/LSP6ExecuteModule.sol @@ -250,16 +250,7 @@ abstract contract LSP6ExecuteModule { revert NoCallsAllowed(controllerAddress); } - bool isEmptyCall = data.length == 0; - - // CHECK if there is at least a 4 bytes function selector - bytes4 selector = data.length >= 4 ? bytes4(data) : bytes4(0); - - bytes4 requiredCallTypes = _extractCallType( - operationType, - value, - isEmptyCall - ); + bytes4 requiredCallTypes = _extractCallType(operationType, value, data); for (uint256 ii; ii < allowedCalls.length; ii += 34) { /// @dev structure of an AllowedCall @@ -294,11 +285,11 @@ abstract contract LSP6ExecuteModule { _isAllowedCallType(allowedCall, requiredCallTypes) && _isAllowedAddress(allowedCall, to) && _isAllowedStandard(allowedCall, to) && - _isAllowedFunction(allowedCall, selector) + _isAllowedFunction(allowedCall, data) ) return; } - revert NotAllowedCall(controllerAddress, to, selector); + revert NotAllowedCall(controllerAddress, to, bytes4(data)); } /** @@ -309,7 +300,7 @@ abstract contract LSP6ExecuteModule { function _extractCallType( uint256 operationType, uint256 value, - bool isEmptyCall + bytes memory data ) internal pure returns (bytes4 requiredCallTypes) { // if there is value being transferred, add the extra bit // for the first bit for Value Transfer in the `requiredCallTypes` @@ -317,7 +308,12 @@ abstract contract LSP6ExecuteModule { requiredCallTypes |= _ALLOWEDCALLS_TRANSFERVALUE; } - if (!isEmptyCall) { + bool isCallDataPresent = data.length != 0; + bool isEmptyCallWithoutValue = !isCallDataPresent && value == 0; + + // if we are doing a message call with some data + // or if we are doing an empty call without value + if (isCallDataPresent || isEmptyCallWithoutValue) { if (operationType == OPERATION_0_CALL) { requiredCallTypes |= _ALLOWEDCALLS_CALL; } else if (operationType == OPERATION_3_STATICCALL) { @@ -363,7 +359,7 @@ abstract contract LSP6ExecuteModule { function _isAllowedFunction( bytes memory allowedCall, - bytes4 requiredFunction + bytes memory data ) internal pure virtual returns (bool) { // = 28 bytes x 8 bits = 224 bits // @@ -372,7 +368,9 @@ abstract contract LSP6ExecuteModule { // 0000000ncafecafecafecafecafecafecafecafecafecafe5a5a5a5af1f1f1f1 bytes4 allowedFunction = bytes4(bytes32(allowedCall) << 224); - bool isFunctionCall = requiredFunction != bytes4(0); + bool isFunctionCall = data.length >= 4; + + bytes4 requiredFunction = bytes4(data); // ANY function = 0xffffffff return diff --git a/contracts/LSP6KeyManager/LSP6Modules/LSP6SetDataModule.sol b/contracts/LSP6KeyManager/LSP6Modules/LSP6SetDataModule.sol index 03c43945f..9d7ae470f 100644 --- a/contracts/LSP6KeyManager/LSP6Modules/LSP6SetDataModule.sol +++ b/contracts/LSP6KeyManager/LSP6Modules/LSP6SetDataModule.sol @@ -321,8 +321,12 @@ abstract contract LSP6SetDataModule { // LSP17Extension: } else if (bytes12(inputDataKey) == _LSP17_EXTENSION_PREFIX) { - // CHECK that `dataValue` contains exactly 20 bytes, which corresponds to an address for a LSP17 Extension - if (inputDataValue.length != 20 && inputDataValue.length != 0) { + // CHECK that `dataValue` contains exactly 20 or 21 bytes (if setting to forward value), which corresponds to an address for a LSP17 Extension + if ( + inputDataValue.length != 20 && + inputDataValue.length != 21 && + inputDataValue.length != 0 + ) { revert InvalidDataValuesForDataKeys( inputDataKey, inputDataValue diff --git a/contracts/LSP7DigitalAsset/LSP7DigitalAssetCore.sol b/contracts/LSP7DigitalAsset/LSP7DigitalAssetCore.sol index 671b0b4e8..56418c3f3 100644 --- a/contracts/LSP7DigitalAsset/LSP7DigitalAssetCore.sol +++ b/contracts/LSP7DigitalAsset/LSP7DigitalAssetCore.sol @@ -8,7 +8,6 @@ import { import {ILSP7DigitalAsset} from "./ILSP7DigitalAsset.sol"; // modules -import {Version} from "../Version.sol"; // libraries import { @@ -52,7 +51,7 @@ import { * Similar to ERC20, the non-standard {increaseAllowance} and {decreaseAllowance} functions * have been added to mitigate the well-known issues around setting allowances. */ -abstract contract LSP7DigitalAssetCore is ILSP7DigitalAsset, Version { +abstract contract LSP7DigitalAssetCore is ILSP7DigitalAsset { using EnumerableSet for EnumerableSet.AddressSet; using ERC165Checker for address; using LSP1Utils for address; @@ -133,10 +132,7 @@ abstract contract LSP7DigitalAssetCore is ILSP7DigitalAsset, Version { operatorNotificationData ); - operator.tryNotifyUniversalReceiver( - _TYPEID_LSP7_TOKENOPERATOR, - lsp1Data - ); + operator.notifyUniversalReceiver(_TYPEID_LSP7_TOKENOPERATOR, lsp1Data); } /** @@ -162,7 +158,7 @@ abstract contract LSP7DigitalAssetCore is ILSP7DigitalAsset, Version { operatorNotificationData ); - operator.tryNotifyUniversalReceiver( + operator.notifyUniversalReceiver( _TYPEID_LSP7_TOKENOPERATOR, lsp1Data ); @@ -220,10 +216,7 @@ abstract contract LSP7DigitalAssetCore is ILSP7DigitalAsset, Version { operatorNotificationData ); - operator.tryNotifyUniversalReceiver( - _TYPEID_LSP7_TOKENOPERATOR, - lsp1Data - ); + operator.notifyUniversalReceiver(_TYPEID_LSP7_TOKENOPERATOR, lsp1Data); } /** @@ -257,10 +250,7 @@ abstract contract LSP7DigitalAssetCore is ILSP7DigitalAsset, Version { operatorNotificationData ); - operator.tryNotifyUniversalReceiver( - _TYPEID_LSP7_TOKENOPERATOR, - lsp1Data - ); + operator.notifyUniversalReceiver(_TYPEID_LSP7_TOKENOPERATOR, lsp1Data); } // --- Transfer functionality @@ -474,7 +464,7 @@ abstract contract LSP7DigitalAssetCore is ILSP7DigitalAsset, Version { _afterTokenTransfer(from, address(0), amount, data); bytes memory lsp1Data = abi.encode(from, address(0), amount, data); - from.tryNotifyUniversalReceiver(_TYPEID_LSP7_TOKENSSENDER, lsp1Data); + from.notifyUniversalReceiver(_TYPEID_LSP7_TOKENSSENDER, lsp1Data); } /** @@ -572,7 +562,7 @@ abstract contract LSP7DigitalAssetCore is ILSP7DigitalAsset, Version { bytes memory lsp1Data = abi.encode(from, to, amount, data); - from.tryNotifyUniversalReceiver(_TYPEID_LSP7_TOKENSSENDER, lsp1Data); + from.notifyUniversalReceiver(_TYPEID_LSP7_TOKENSSENDER, lsp1Data); _notifyTokenReceiver(to, force, lsp1Data); } diff --git a/contracts/LSP8IdentifiableDigitalAsset/LSP8IdentifiableDigitalAssetCore.sol b/contracts/LSP8IdentifiableDigitalAsset/LSP8IdentifiableDigitalAssetCore.sol index 0bc85d354..48ceb4a91 100644 --- a/contracts/LSP8IdentifiableDigitalAsset/LSP8IdentifiableDigitalAssetCore.sol +++ b/contracts/LSP8IdentifiableDigitalAsset/LSP8IdentifiableDigitalAssetCore.sol @@ -9,9 +9,6 @@ import { ILSP8IdentifiableDigitalAsset } from "./ILSP8IdentifiableDigitalAsset.sol"; -// modules -import {Version} from "../Version.sol"; - // libraries import { EnumerableSet @@ -52,8 +49,7 @@ import { * @dev Core Implementation of a LSP8 compliant contract. */ abstract contract LSP8IdentifiableDigitalAssetCore is - ILSP8IdentifiableDigitalAsset, - Version + ILSP8IdentifiableDigitalAsset { using EnumerableSet for EnumerableSet.AddressSet; using EnumerableSet for EnumerableSet.Bytes32Set; @@ -160,10 +156,7 @@ abstract contract LSP8IdentifiableDigitalAssetCore is operatorNotificationData ); - operator.tryNotifyUniversalReceiver( - _TYPEID_LSP8_TOKENOPERATOR, - lsp1Data - ); + operator.notifyUniversalReceiver(_TYPEID_LSP8_TOKENOPERATOR, lsp1Data); } /** @@ -205,7 +198,7 @@ abstract contract LSP8IdentifiableDigitalAssetCore is operatorNotificationData ); - operator.tryNotifyUniversalReceiver( + operator.notifyUniversalReceiver( _TYPEID_LSP8_TOKENOPERATOR, lsp1Data ); @@ -467,10 +460,7 @@ abstract contract LSP8IdentifiableDigitalAssetCore is data ); - tokenOwner.tryNotifyUniversalReceiver( - _TYPEID_LSP8_TOKENSSENDER, - lsp1Data - ); + tokenOwner.notifyUniversalReceiver(_TYPEID_LSP8_TOKENSSENDER, lsp1Data); } /** @@ -536,7 +526,7 @@ abstract contract LSP8IdentifiableDigitalAssetCore is bytes memory lsp1Data = abi.encode(from, to, tokenId, data); - from.tryNotifyUniversalReceiver(_TYPEID_LSP8_TOKENSSENDER, lsp1Data); + from.notifyUniversalReceiver(_TYPEID_LSP8_TOKENSSENDER, lsp1Data); _notifyTokenReceiver(to, force, lsp1Data); } diff --git a/contracts/LSP8IdentifiableDigitalAsset/extensions/LSP8CompatibleERC721.sol b/contracts/LSP8IdentifiableDigitalAsset/extensions/LSP8CompatibleERC721.sol index 0d7e3a46e..5bb539fb7 100644 --- a/contracts/LSP8IdentifiableDigitalAsset/extensions/LSP8CompatibleERC721.sol +++ b/contracts/LSP8IdentifiableDigitalAsset/extensions/LSP8CompatibleERC721.sol @@ -357,10 +357,7 @@ abstract contract LSP8CompatibleERC721 is tokenId, operatorNotificationData ); - operator.tryNotifyUniversalReceiver( - _TYPEID_LSP8_TOKENOPERATOR, - lsp1Data - ); + operator.notifyUniversalReceiver(_TYPEID_LSP8_TOKENOPERATOR, lsp1Data); } /** diff --git a/contracts/LSP8IdentifiableDigitalAsset/extensions/LSP8CompatibleERC721InitAbstract.sol b/contracts/LSP8IdentifiableDigitalAsset/extensions/LSP8CompatibleERC721InitAbstract.sol index b48080620..1f57d45f2 100644 --- a/contracts/LSP8IdentifiableDigitalAsset/extensions/LSP8CompatibleERC721InitAbstract.sol +++ b/contracts/LSP8IdentifiableDigitalAsset/extensions/LSP8CompatibleERC721InitAbstract.sol @@ -366,10 +366,7 @@ abstract contract LSP8CompatibleERC721InitAbstract is tokenId, operatorNotificationData ); - operator.tryNotifyUniversalReceiver( - _TYPEID_LSP8_TOKENOPERATOR, - lsp1Data - ); + operator.notifyUniversalReceiver(_TYPEID_LSP8_TOKENOPERATOR, lsp1Data); } /** diff --git a/contracts/LSP9Vault/LSP9Constants.sol b/contracts/LSP9Vault/LSP9Constants.sol index f2ddb03d9..943120b6d 100644 --- a/contracts/LSP9Vault/LSP9Constants.sol +++ b/contracts/LSP9Vault/LSP9Constants.sol @@ -17,7 +17,7 @@ bytes constant _LSP9_SUPPORTED_STANDARDS_VALUE = hex"7c0334a1"; // keccak256('LSP9ValueReceived') bytes32 constant _TYPEID_LSP9_VALUE_RECEIVED = 0x468cd1581d7bc001c3b685513d2b929b55437be34700410383d58f3aa1ea0abc; -// Ownerhsip Transfer Type IDs +// Ownership Transfer Type IDs // keccak256('LSP9OwnershipTransferStarted') bytes32 constant _TYPEID_LSP9_OwnershipTransferStarted = 0xaefd43f45fed1bcd8992f23c803b6f4ec45cf6b62b0d404d565f290a471e763f; diff --git a/contracts/LSP9Vault/LSP9Vault.sol b/contracts/LSP9Vault/LSP9Vault.sol index cfe709014..7fa70ce43 100644 --- a/contracts/LSP9Vault/LSP9Vault.sol +++ b/contracts/LSP9Vault/LSP9Vault.sol @@ -5,6 +5,7 @@ pragma solidity ^0.8.4; import { OwnableUnset } from "@erc725/smart-contracts/contracts/custom/OwnableUnset.sol"; +import {Version} from "../Version.sol"; import {LSP9VaultCore} from "./LSP9VaultCore.sol"; // libraries @@ -23,7 +24,7 @@ import { * @author Fabian Vogelsteller, Yamen Merhi, Jean Cavallera * @dev Could be owned by an EOA or by a contract and is able to receive and send assets. Also allows for registering received assets by leveraging the key-value storage. */ -contract LSP9Vault is LSP9VaultCore { +contract LSP9Vault is LSP9VaultCore, Version { using LSP1Utils for address; /** @@ -57,7 +58,7 @@ contract LSP9Vault is LSP9VaultCore { _LSP9_SUPPORTED_STANDARDS_VALUE ); - newOwner.tryNotifyUniversalReceiver( + newOwner.notifyUniversalReceiver( _TYPEID_LSP9_OwnershipTransferred_RecipientNotification, "" ); diff --git a/contracts/LSP9Vault/LSP9VaultCore.sol b/contracts/LSP9Vault/LSP9VaultCore.sol index 10205a790..db3b333ef 100644 --- a/contracts/LSP9Vault/LSP9VaultCore.sol +++ b/contracts/LSP9Vault/LSP9VaultCore.sol @@ -20,7 +20,6 @@ import {LSP1Utils} from "../LSP1UniversalReceiver/LSP1Utils.sol"; import {LSP2Utils} from "../LSP2ERC725YJSONSchema/LSP2Utils.sol"; // modules -import {Version} from "../Version.sol"; import {ERC725XCore} from "@erc725/smart-contracts/contracts/ERC725XCore.sol"; import {ERC725YCore} from "@erc725/smart-contracts/contracts/ERC725YCore.sol"; import { @@ -81,7 +80,6 @@ import { contract LSP9VaultCore is ERC725XCore, ERC725YCore, - Version, LSP14Ownable2Step, LSP17Extendable, ILSP1UniversalReceiver, @@ -438,9 +436,9 @@ contract LSP9VaultCore is address currentOwner = owner(); emit OwnershipTransferStarted(currentOwner, newOwner); - newOwner.tryNotifyUniversalReceiver( + newOwner.notifyUniversalReceiver( _TYPEID_LSP9_OwnershipTransferStarted, - "" + abi.encode(currentOwner, newOwner) ); // reset the transfer ownership lock @@ -455,19 +453,19 @@ contract LSP9VaultCore is * - When notifying the previous owner via LSP1, the typeId used must be the `keccak256(...)` hash of [LSP0OwnershipTransferred_SenderNotification]. * - When notifying the new owner via LSP1, the typeId used must be the `keccak256(...)` hash of [LSP0OwnershipTransferred_RecipientNotification]. */ - function acceptOwnership() public virtual override NotInTransferOwnership { + function acceptOwnership() public virtual override notInTransferOwnership { address previousOwner = owner(); _acceptOwnership(); - previousOwner.tryNotifyUniversalReceiver( + previousOwner.notifyUniversalReceiver( _TYPEID_LSP9_OwnershipTransferred_SenderNotification, - "" + abi.encode(previousOwner, msg.sender) ); - msg.sender.tryNotifyUniversalReceiver( + msg.sender.notifyUniversalReceiver( _TYPEID_LSP9_OwnershipTransferred_RecipientNotification, - "" + abi.encode(previousOwner, msg.sender) ); } @@ -488,9 +486,9 @@ contract LSP9VaultCore is LSP14Ownable2Step._renounceOwnership(); if (owner() == address(0)) { - previousOwner.tryNotifyUniversalReceiver( + previousOwner.notifyUniversalReceiver( _TYPEID_LSP9_OwnershipTransferred_SenderNotification, - "" + abi.encode(previousOwner, address(0)) ); } } @@ -603,10 +601,19 @@ contract LSP9VaultCore is mappedExtensionDataKey ); - // Check if the extensionData is 21 bytes long (20 bytes of address + 1 byte as bool indicator ot forwards the value) + // Prevent casting data shorter than 20 bytes to an address to avoid + // unintentionally calling a different extension, return address(0) instead. + if (extensionData.length < 20) { + return (address(0), false); + } + + // CHECK if the `extensionData` is 21 bytes long + // - 20 bytes = extension's address + // - 1 byte `0x01` as a boolean indicating if the contract should forward the value to the extension or not if (extensionData.length == 21) { - // Check if the last byte is 1 (true) - if (extensionData[20] == hex"01") { + // If the last byte is set to `0x01` (`true`) + // this indicates that the contract should forward the value to the extension + if (extensionData[20] == 0x01) { // Return the address of the extension return (address(bytes20(extensionData)), true); } @@ -616,7 +623,7 @@ contract LSP9VaultCore is } /** - * @dev Modifier restricting the call to the owner of the contract and the UniversalReceiverDelegate + * @dev Internal method restricting the call to the owner of the contract and the UniversalReceiverDelegate */ function _validateAndIdentifyCaller() internal diff --git a/contracts/LSP9Vault/LSP9VaultInit.sol b/contracts/LSP9Vault/LSP9VaultInit.sol index 2548d09d8..427275349 100644 --- a/contracts/LSP9Vault/LSP9VaultInit.sol +++ b/contracts/LSP9Vault/LSP9VaultInit.sol @@ -2,6 +2,7 @@ pragma solidity ^0.8.4; // modules +import {Version} from "../Version.sol"; import {LSP9VaultInitAbstract} from "./LSP9VaultInitAbstract.sol"; /** @@ -9,7 +10,7 @@ import {LSP9VaultInitAbstract} from "./LSP9VaultInitAbstract.sol"; * @author Fabian Vogelsteller, Yamen Merhi, Jean Cavallera * @dev Could be owned by a UniversalProfile and able to register received asset with UniversalReceiverDelegateVault */ -contract LSP9VaultInit is LSP9VaultInitAbstract { +contract LSP9VaultInit is LSP9VaultInitAbstract, Version { /** * @dev initialize (= lock) base implementation contract on deployment */ diff --git a/contracts/LSP9Vault/LSP9VaultInitAbstract.sol b/contracts/LSP9Vault/LSP9VaultInitAbstract.sol index bcc988812..97f67ac1c 100644 --- a/contracts/LSP9Vault/LSP9VaultInitAbstract.sol +++ b/contracts/LSP9Vault/LSP9VaultInitAbstract.sol @@ -60,7 +60,7 @@ abstract contract LSP9VaultInitAbstract is Initializable, LSP9VaultCore { _LSP9_SUPPORTED_STANDARDS_VALUE ); - newOwner.tryNotifyUniversalReceiver( + newOwner.notifyUniversalReceiver( _TYPEID_LSP9_OwnershipTransferred_RecipientNotification, "" ); diff --git a/contracts/Mocks/Executor.sol b/contracts/Mocks/Executor.sol index 2a234ad65..4f8704b6b 100644 --- a/contracts/Mocks/Executor.sol +++ b/contracts/Mocks/Executor.sol @@ -26,10 +26,8 @@ contract Executor { LSP6KeyManager private _keyManager; UniversalProfile private _universalProfile; - // payable modifier is required as _account is non-payable by default - // but UniversalProfile has a payable fallback function - constructor(address payable account_, address keyManager_) { - _universalProfile = UniversalProfile(account_); + constructor(UniversalProfile account_, address keyManager_) { + _universalProfile = account_; _keyManager = LSP6KeyManager(keyManager_); } diff --git a/contracts/Mocks/ExecutorLSP20.sol b/contracts/Mocks/ExecutorLSP20.sol index ac77e27c1..ac7a57617 100644 --- a/contracts/Mocks/ExecutorLSP20.sol +++ b/contracts/Mocks/ExecutorLSP20.sol @@ -23,10 +23,8 @@ contract ExecutorLSP20 { UniversalProfile private _universalProfile; - // payable modifier is required as _account is non-payable by default - // but UniversalProfile has a payable fallback function - constructor(address payable account_) { - _universalProfile = UniversalProfile(account_); + constructor(UniversalProfile account_) { + _universalProfile = account_; } // contract calls diff --git a/contracts/Mocks/KeyManager/KeyManagerInternalsTester.sol b/contracts/Mocks/KeyManager/KeyManagerInternalsTester.sol index 12facc51f..a6edba89f 100644 --- a/contracts/Mocks/KeyManager/KeyManagerInternalsTester.sol +++ b/contracts/Mocks/KeyManager/KeyManagerInternalsTester.sol @@ -33,7 +33,7 @@ contract KeyManagerInternalTester is LSP6KeyManager { } function verifyAllowedCall( - address _sender, + address controller, uint256 operationType, address to, uint256 value, @@ -41,7 +41,7 @@ contract KeyManagerInternalTester is LSP6KeyManager { ) public view { super._verifyAllowedCall( _target, - _sender, + controller, operationType, to, value, diff --git a/contracts/Mocks/LSP1TypeIDsTester.sol b/contracts/Mocks/LSP1TypeIDsTester.sol new file mode 100644 index 000000000..ac9615aba --- /dev/null +++ b/contracts/Mocks/LSP1TypeIDsTester.sol @@ -0,0 +1,108 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.4; + +import { + _TYPEID_LSP0_VALUE_RECEIVED, + _TYPEID_LSP0_OwnershipTransferStarted, + _TYPEID_LSP0_OwnershipTransferred_SenderNotification, + _TYPEID_LSP0_OwnershipTransferred_RecipientNotification +} from "../LSP0ERC725Account/LSP0Constants.sol"; +import { + _TYPEID_LSP7_TOKENSSENDER, + _TYPEID_LSP7_TOKENSRECIPIENT, + _TYPEID_LSP7_TOKENOPERATOR +} from "../LSP7DigitalAsset/LSP7Constants.sol"; +import { + _TYPEID_LSP8_TOKENSSENDER, + _TYPEID_LSP8_TOKENSRECIPIENT, + _TYPEID_LSP8_TOKENOPERATOR +} from "../LSP8IdentifiableDigitalAsset/LSP8Constants.sol"; +import { + _TYPEID_LSP9_VALUE_RECEIVED, + _TYPEID_LSP9_OwnershipTransferStarted, + _TYPEID_LSP9_OwnershipTransferred_SenderNotification, + _TYPEID_LSP9_OwnershipTransferred_RecipientNotification +} from "../LSP9Vault/LSP9Constants.sol"; +import { + _TYPEID_LSP14_OwnershipTransferStarted, + _TYPEID_LSP14_OwnershipTransferred_SenderNotification, + _TYPEID_LSP14_OwnershipTransferred_RecipientNotification +} from "../LSP14Ownable2Step/LSP14Constants.sol"; + +error LSP1TypeIdHashIsWrong(bytes32 typeIdHash, string typeIdname); + +contract LSP1TypeIDsTester { + mapping(string => bytes32) private _typeIds; + + constructor() { + // ------ LSP0 ------ + _typeIds["LSP0ValueReceived"] = _TYPEID_LSP0_VALUE_RECEIVED; + _typeIds[ + "LSP0OwnershipTransferStarted" + ] = _TYPEID_LSP0_OwnershipTransferStarted; + _typeIds[ + "LSP0OwnershipTransferred_SenderNotification" + ] = _TYPEID_LSP0_OwnershipTransferred_SenderNotification; + _typeIds[ + "LSP0OwnershipTransferred_RecipientNotification" + ] = _TYPEID_LSP0_OwnershipTransferred_RecipientNotification; + // ------------------ + + // ------ LSP7 ------ + _typeIds["LSP7Tokens_SenderNotification"] = _TYPEID_LSP7_TOKENSSENDER; + _typeIds[ + "LSP7Tokens_RecipientNotification" + ] = _TYPEID_LSP7_TOKENSRECIPIENT; + _typeIds[ + "LSP7Tokens_OperatorNotification" + ] = _TYPEID_LSP7_TOKENOPERATOR; + // ------------------ + + // ------ LSP8 ------ + _typeIds["LSP8Tokens_SenderNotification"] = _TYPEID_LSP8_TOKENSSENDER; + _typeIds[ + "LSP8Tokens_RecipientNotification" + ] = _TYPEID_LSP8_TOKENSRECIPIENT; + _typeIds[ + "LSP8Tokens_OperatorNotification" + ] = _TYPEID_LSP8_TOKENOPERATOR; + // ------------------ + + // ------ LSP9 ------ + _typeIds["LSP9ValueReceived"] = _TYPEID_LSP9_VALUE_RECEIVED; + _typeIds[ + "LSP9OwnershipTransferStarted" + ] = _TYPEID_LSP9_OwnershipTransferStarted; + _typeIds[ + "LSP9OwnershipTransferred_SenderNotification" + ] = _TYPEID_LSP9_OwnershipTransferred_SenderNotification; + _typeIds[ + "LSP9OwnershipTransferred_RecipientNotification" + ] = _TYPEID_LSP9_OwnershipTransferred_RecipientNotification; + // ------------------ + + // ------ LSP14 ------ + _typeIds[ + "LSP14OwnershipTransferStarted" + ] = _TYPEID_LSP14_OwnershipTransferStarted; + _typeIds[ + "LSP14OwnershipTransferred_SenderNotification" + ] = _TYPEID_LSP14_OwnershipTransferred_SenderNotification; + _typeIds[ + "LSP14OwnershipTransferred_RecipientNotification" + ] = _TYPEID_LSP14_OwnershipTransferred_RecipientNotification; + // ------------------- + } + + function verifyLSP1TypeID( + string calldata typeIdName + ) public view returns (bytes32) { + bytes32 typeIdHash = _typeIds[typeIdName]; + + if (typeIdHash != keccak256(bytes(typeIdName))) { + revert LSP1TypeIdHashIsWrong(typeIdHash, typeIdName); + } + + return typeIdHash; + } +} diff --git a/contracts/Mocks/LSP20Owners/OwnerWIthURD.sol b/contracts/Mocks/LSP20Owners/OwnerWIthURD.sol new file mode 100644 index 000000000..f95800550 --- /dev/null +++ b/contracts/Mocks/LSP20Owners/OwnerWIthURD.sol @@ -0,0 +1,68 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.4; + +import { + ILSP1UniversalReceiver +} from "../../LSP1UniversalReceiver/ILSP1UniversalReceiver.sol"; + +import { + ILSP14Ownable2Step +} from "../../LSP14Ownable2Step/ILSP14Ownable2Step.sol"; + +import { + ILSP20CallVerifier +} from "../../LSP20CallVerification/ILSP20CallVerifier.sol"; +import { + _LSP20_VERIFY_CALL_SUCCESS_VALUE_WITH_POST_VERIFICATION, + _LSP20_VERIFY_CALL_RESULT_SUCCESS_VALUE +} from "../../LSP20CallVerification/LSP20Constants.sol"; + +contract OwnerWithURD is ILSP20CallVerifier, ILSP1UniversalReceiver { + address private immutable _OWNED_CONTRACT; + + constructor(address ownedContract) { + _OWNED_CONTRACT = ownedContract; + } + + function renounceOwnership() public { + ILSP14Ownable2Step(_OWNED_CONTRACT).renounceOwnership(); + } + + function acceptOwnership() public { + ILSP14Ownable2Step(_OWNED_CONTRACT).acceptOwnership(); + } + + function lsp20VerifyCall( + address, + address, + address, + uint256, + bytes memory + ) public pure returns (bytes4 returnedStatus) { + return _LSP20_VERIFY_CALL_SUCCESS_VALUE_WITH_POST_VERIFICATION; + } + + function lsp20VerifyCallResult( + bytes32, + bytes memory + ) external pure returns (bytes4) { + return _LSP20_VERIFY_CALL_RESULT_SUCCESS_VALUE; + } + + function supportsInterface(bytes4 interfaceId) public pure returns (bool) { + return interfaceId == type(ILSP1UniversalReceiver).interfaceId; + } + + function universalReceiver( + bytes32 typeId, + bytes memory receivedData + ) public payable virtual override returns (bytes memory returnedValues) { + emit UniversalReceiver( + msg.sender, + msg.value, + typeId, + receivedData, + returnedValues + ); + } +} diff --git a/contracts/Mocks/TargetContract.sol b/contracts/Mocks/TargetContract.sol index 225bba71f..e4a01750d 100644 --- a/contracts/Mocks/TargetContract.sol +++ b/contracts/Mocks/TargetContract.sol @@ -38,4 +38,15 @@ contract TargetContract { function revertCall() public pure { revert("TargetContract:revertCall: this function has reverted!"); } + + function getDynamicArrayOf2Numbers() + public + pure + returns (uint256[] memory) + { + uint256[] memory results = new uint256[](2); + results[0] = 10; + results[1] = 20; + return results; + } } diff --git a/contracts/UniversalProfile.sol b/contracts/UniversalProfile.sol index cda387553..027c5cd34 100644 --- a/contracts/UniversalProfile.sol +++ b/contracts/UniversalProfile.sol @@ -19,7 +19,9 @@ contract UniversalProfile is LSP0ERC725Account { /** * @notice Deploying a UniversalProfile contract with owner set to address `initialOwner`. * - * @dev Set `initialOwner` as the contract owner and the `SupportedStandards:LSP3UniversalProfile` data key in the ERC725Y data key/value store. The `constructor` also allows funding the contract on deployment. + * @dev Set `initialOwner` as the contract owner and the `SupportedStandards:LSP3Profile` data key in the ERC725Y data key/value store. + * - The `constructor` is payable and allows funding the contract on deployment. + * - The `initialOwner` will then be allowed to call protected functions marked with the `onlyOwner` modifier. * * @param initialOwner the owner of the contract * @@ -29,7 +31,7 @@ contract UniversalProfile is LSP0ERC725Account { * - {DataChanged} event when setting the {_LSP3_SUPPORTED_STANDARDS_KEY}. */ constructor(address initialOwner) payable LSP0ERC725Account(initialOwner) { - // set data key SupportedStandards:LSP3UniversalProfile + // set data key SupportedStandards:LSP3Profile _setData( _LSP3_SUPPORTED_STANDARDS_KEY, _LSP3_SUPPORTED_STANDARDS_VALUE diff --git a/contracts/UniversalProfileInit.sol b/contracts/UniversalProfileInit.sol index 06fc853af..bcac3b039 100644 --- a/contracts/UniversalProfileInit.sol +++ b/contracts/UniversalProfileInit.sol @@ -2,6 +2,7 @@ pragma solidity ^0.8.4; // modules +import {Version} from "./Version.sol"; import {UniversalProfileInitAbstract} from "./UniversalProfileInitAbstract.sol"; /** @@ -9,12 +10,11 @@ import {UniversalProfileInitAbstract} from "./UniversalProfileInitAbstract.sol"; * @author Fabian Vogelsteller * @dev Implementation of the ERC725Account + LSP1 universalReceiver */ -contract UniversalProfileInit is UniversalProfileInitAbstract { +contract UniversalProfileInit is UniversalProfileInitAbstract, Version { /** * @notice deploying a `UniversalProfileInit` base contract to be used behind proxy - * @dev Locks the base contract on deployment, so that it cannot be initialized, owned and controlled by anyone - * after it has been deployed. This is intended so that the sole purpose of this contract is to be used as a base - * contract behind a proxy. + * @dev Locks the base contract on deployment, so that it cannot be initialized, owned and controlled by anyone after it has been deployed. + * This is intended so that the sole purpose of this contract is to be used as a base contract behind a proxy. */ constructor() { _disableInitializers(); @@ -23,13 +23,16 @@ contract UniversalProfileInit is UniversalProfileInitAbstract { /** * @notice Initializing a UniversalProfile contract with owner set to address `initialOwner`. * - * @dev Set `initialOwner` as the contract owner and the `SupportedStandards:LSP3UniversalProfile` data key in the ERC725Y data key/value store. The `constructor` also allows funding the contract on deployment. The `initialOwner` will then be allowed to call protected functions marked with the `onlyOwner` modifier. The `initialize(address)` function also allows funding the contract on initialization. + * @dev Set `initialOwner` as the contract owner and the `SupportedStandards:LSP3Profile` data key in the ERC725Y data key/value store. + * - The `initialize(address)` function is payable and allows funding the contract on initialization. + * - The `initialOwner` will then be allowed to call protected functions marked with the `onlyOwner` modifier. * * @param initialOwner the owner of the contract * * @custom:events * - {UniversalReceiver} event when funding the contract on deployment. * - {OwnershipTransferred} event when `initialOwner` is set as the contract {owner}. + * - {DataChanged} event when setting the {_LSP3_SUPPORTED_STANDARDS_KEY}. */ function initialize( address initialOwner diff --git a/contracts/UniversalProfileInitAbstract.sol b/contracts/UniversalProfileInitAbstract.sol index 424997c23..f49f14203 100644 --- a/contracts/UniversalProfileInitAbstract.sol +++ b/contracts/UniversalProfileInitAbstract.sol @@ -21,7 +21,8 @@ abstract contract UniversalProfileInitAbstract is LSP0ERC725AccountInitAbstract { /** - * @dev Set `initialOwner` as the contract owner and the `SupportedStandards:LSP3UniversalProfile` data key in the ERC725Y data key/value store. + * @dev Set `initialOwner` as the contract owner and the `SupportedStandards:LSP3Profile` data key in the ERC725Y data key/value store. + * The `initialOwner` will then be allowed to call protected functions marked with the `onlyOwner` modifier. * * @param initialOwner The owner of the contract. * @@ -30,13 +31,14 @@ abstract contract UniversalProfileInitAbstract is * @custom:events * - {UniversalReceiver} event when funding the contract on deployment. * - {OwnershipTransferred} event when `initialOwner` is set as the contract {owner}. + * - {DataChanged} event when setting the {_LSP3_SUPPORTED_STANDARDS_KEY}. */ function _initialize( address initialOwner ) internal virtual override onlyInitializing { LSP0ERC725AccountInitAbstract._initialize(initialOwner); - // set data key SupportedStandards:LSP3UniversalProfile + // set data key SupportedStandards:LSP3Profile _setData( _LSP3_SUPPORTED_STANDARDS_KEY, _LSP3_SUPPORTED_STANDARDS_VALUE diff --git a/contracts/Version.sol b/contracts/Version.sol index 099e75ebc..2b954c6bc 100644 --- a/contracts/Version.sol +++ b/contracts/Version.sol @@ -2,15 +2,16 @@ pragma solidity ^0.8.4; abstract contract Version { - string internal constant _VERSION = "0.12.0"; - /** * @dev Get the version of the contract. * @notice Contract version. * * @return The version of the the contract. */ - function version() public view virtual returns (string memory) { - return _VERSION; - } + // DO NOT CHANGE + // Comments block below is used by release-please to automatically update the version in this file. + // x-release-please-start-version + string public constant VERSION = "0.12.0"; + + // x-release-please-end } diff --git a/deploy/001_deploy_universal_profile.ts b/deploy/001_deploy_universal_profile.ts index 999e0ac3d..3c35d1c2b 100644 --- a/deploy/001_deploy_universal_profile.ts +++ b/deploy/001_deploy_universal_profile.ts @@ -9,10 +9,12 @@ const deployUniversalProfile: DeployFunction = async ({ const { deploy } = deployments; const { owner } = await getNamedAccounts(); + const gasPrice = await ethers.provider.getGasPrice(); + await deploy('UniversalProfile', { from: owner, args: [owner], - gasPrice: ethers.BigNumber.from(20_000_000_000), // in wei + gasPrice, log: true, }); }; diff --git a/deploy/002_deploy_key_manager.ts b/deploy/002_deploy_key_manager.ts index 9be14acd4..0f357baab 100644 --- a/deploy/002_deploy_key_manager.ts +++ b/deploy/002_deploy_key_manager.ts @@ -11,10 +11,12 @@ const deployKeyManager: DeployFunction = async ({ const UniversalProfile = await deployments.get('UniversalProfile'); + const gasPrice = await ethers.provider.getGasPrice(); + await deploy('LSP6KeyManager', { from: owner, args: [UniversalProfile.address], - gasPrice: ethers.BigNumber.from(20_000_000_000), // in wei + gasPrice, log: true, }); }; diff --git a/deploy/003_deploy_universal_receiver_delegate.ts b/deploy/003_deploy_universal_receiver_delegate.ts index f0017cd09..a3f7d4d4a 100644 --- a/deploy/003_deploy_universal_receiver_delegate.ts +++ b/deploy/003_deploy_universal_receiver_delegate.ts @@ -10,9 +10,11 @@ const deployUniversalReceiverDelegateUPDeterministic: DeployFunction = async ({ const { deploy } = deployments; const { owner: deployer } = await getNamedAccounts(); + const gasPrice = await ethers.provider.getGasPrice(); + await deploy('LSP1UniversalReceiverDelegateUP', { from: deployer, - gasPrice: ethers.BigNumber.from(20_000_000_000), // in wei + gasPrice, log: true, deterministicDeployment: SALT, }); diff --git a/deploy/005_deploy_universal_receiver_delegate_vault.ts b/deploy/005_deploy_universal_receiver_delegate_vault.ts index d82402909..795933b4b 100644 --- a/deploy/005_deploy_universal_receiver_delegate_vault.ts +++ b/deploy/005_deploy_universal_receiver_delegate_vault.ts @@ -10,9 +10,11 @@ const deployUniversalReceiverDelegateVaultDeterministic: DeployFunction = async const { deploy } = deployments; const { owner: deployer } = await getNamedAccounts(); + const gasPrice = await ethers.provider.getGasPrice(); + await deploy('LSP1UniversalReceiverDelegateVault', { from: deployer, - gasPrice: ethers.BigNumber.from(20_000_000_000), // in wei + gasPrice, log: true, deterministicDeployment: SALT, }); diff --git a/deploy/006_deploy_base_universal_profile.ts b/deploy/006_deploy_base_universal_profile.ts index de728569b..1064f4c67 100644 --- a/deploy/006_deploy_base_universal_profile.ts +++ b/deploy/006_deploy_base_universal_profile.ts @@ -10,10 +10,12 @@ const deployBaseUniversalProfileDeterministic: DeployFunction = async ({ const { deploy } = deployments; const { owner: deployer } = await getNamedAccounts(); + const gasPrice = await ethers.provider.getGasPrice(); + await deploy('UniversalProfileInit', { from: deployer, log: true, - gasPrice: ethers.BigNumber.from(20_000_000_000), // in wei + gasPrice, deterministicDeployment: SALT, }); }; diff --git a/deploy/007_deploy_base_key_manager.ts b/deploy/007_deploy_base_key_manager.ts index a382df610..2b2ed835e 100644 --- a/deploy/007_deploy_base_key_manager.ts +++ b/deploy/007_deploy_base_key_manager.ts @@ -10,11 +10,13 @@ const deployBaseKeyManagerDeterministic: DeployFunction = async ({ const { deploy } = deployments; const { owner: deployer } = await getNamedAccounts(); + const gasPrice = await ethers.provider.getGasPrice(); + await deploy('LSP6KeyManagerInit', { from: deployer, log: true, gasLimit: 5_000_000, - gasPrice: ethers.BigNumber.from(20_000_000_000), // in wei + gasPrice, deterministicDeployment: SALT, }); }; diff --git a/deploy/008_deploy_lsp7_mintable.ts b/deploy/008_deploy_lsp7_mintable.ts index 728be5323..0f1ecc62e 100644 --- a/deploy/008_deploy_lsp7_mintable.ts +++ b/deploy/008_deploy_lsp7_mintable.ts @@ -9,10 +9,12 @@ const deployLSP7Mintable: DeployFunction = async ({ const { deploy } = deployments; const { owner } = await getNamedAccounts(); + const gasPrice = await ethers.provider.getGasPrice(); + await deploy('LSP7Mintable', { from: owner, args: ['LSP7 Mintable', 'LSP7M', owner, false], - gasPrice: ethers.BigNumber.from(20_000_000_000), // in wei, + gasPrice, log: true, }); }; diff --git a/deploy/009_deploy_lsp8_mintable.ts b/deploy/009_deploy_lsp8_mintable.ts index e75cc6810..1ba09cee4 100644 --- a/deploy/009_deploy_lsp8_mintable.ts +++ b/deploy/009_deploy_lsp8_mintable.ts @@ -9,10 +9,12 @@ const deployLSP8MintableDeterministic: DeployFunction = async ({ const { deploy } = deployments; const { owner: deployer } = await getNamedAccounts(); + const gasPrice = await ethers.provider.getGasPrice(); + await deploy('LSP8Mintable', { from: deployer, args: ['LSP8 Mintable', 'LSP8M', deployer], - gasPrice: ethers.BigNumber.from(20_000_000_000), // in wei, + gasPrice, log: true, deterministicDeployment: true, }); diff --git a/deploy/010_deploy_base_lsp7_mintable.ts b/deploy/010_deploy_base_lsp7_mintable.ts index 984a80921..e6e5a708a 100644 --- a/deploy/010_deploy_base_lsp7_mintable.ts +++ b/deploy/010_deploy_base_lsp7_mintable.ts @@ -10,9 +10,11 @@ const deployBaseLSP7MintableDeterministic: DeployFunction = async ({ const { deploy } = deployments; const { owner: deployer } = await getNamedAccounts(); + const gasPrice = await ethers.provider.getGasPrice(); + await deploy('LSP7MintableInit', { from: deployer, - gasPrice: ethers.BigNumber.from(20_000_000_000), // in wei, + gasPrice, log: true, deterministicDeployment: SALT, }); diff --git a/deploy/011_deploy_base_lsp8_mintable.ts b/deploy/011_deploy_base_lsp8_mintable.ts index 2054c1da3..6b2f8915e 100644 --- a/deploy/011_deploy_base_lsp8_mintable.ts +++ b/deploy/011_deploy_base_lsp8_mintable.ts @@ -10,9 +10,11 @@ const deployBaseLSP8Mintable: DeployFunction = async ({ const { deploy } = deployments; const { owner } = await getNamedAccounts(); + const gasPrice = await ethers.provider.getGasPrice(); + await deploy('LSP8MintableInit', { from: owner, - gasPrice: ethers.BigNumber.from(20_000_000_000), // in wei, + gasPrice, log: true, deterministicDeployment: SALT, }); diff --git a/deploy/012_deploy_vault.ts b/deploy/012_deploy_vault.ts index b3db5d6bd..55ab6e4d0 100644 --- a/deploy/012_deploy_vault.ts +++ b/deploy/012_deploy_vault.ts @@ -9,10 +9,12 @@ const deployVault: DeployFunction = async ({ const { deploy } = deployments; const { owner } = await getNamedAccounts(); + const gasPrice = await ethers.provider.getGasPrice(); + await deploy('LSP9Vault', { from: owner, args: [owner], - gasPrice: ethers.BigNumber.from(20_000_000_000), // in wei + gasPrice, log: true, }); }; diff --git a/deploy/013_deploy_base_vault.ts b/deploy/013_deploy_base_vault.ts index c9aaa47d0..b98e67c12 100644 --- a/deploy/013_deploy_base_vault.ts +++ b/deploy/013_deploy_base_vault.ts @@ -10,10 +10,12 @@ const deployBaseVaultDeterministic: DeployFunction = async ({ const { deploy } = deployments; const { owner: deployer } = await getNamedAccounts(); + const gasPrice = await ethers.provider.getGasPrice(); + await deploy('LSP9VaultInit', { from: deployer, log: true, - gasPrice: ethers.BigNumber.from(20_000_000_000), // in wei + gasPrice, deterministicDeployment: SALT, }); }; diff --git a/docs/contracts/LSP0ERC725Account/LSP0ERC725Account.md b/docs/contracts/LSP0ERC725Account/LSP0ERC725Account.md index 5e89b44a5..5d7b71515 100644 --- a/docs/contracts/LSP0ERC725Account/LSP0ERC725Account.md +++ b/docs/contracts/LSP0ERC725Account/LSP0ERC725Account.md @@ -54,7 +54,11 @@ constructor(address initialOwner); _Deploying a LSP0ERC725Account contract with owner set to address `initialOwner`._ -Set `initialOwner` as the contract owner. The `constructor` also allows funding the contract on deployment. +Set `initialOwner` as the contract owner. + +- The `constructor` also allows funding the contract on deployment. + +- The `initialOwner` will then be allowed to call protected functions marked with the `onlyOwner` modifier.
@@ -82,6 +86,13 @@ Set `initialOwner` as the contract owner. The `constructor` also allows funding ::: +:::info + +Whenever the call is associated with native tokens, the function will delegate the handling of native tokens internally to the {universalReceiver} function +passing `_TYPEID_LSP0_VALUE_RECEIVED` as typeId and the calldata as received data, except when the native token will be sent directly to the extension. + +::: + ```solidity fallback(bytes calldata callData) external payable returns (bytes memory); ``` @@ -125,6 +136,13 @@ This function is executed when: ::: +:::info + +This function internally delegates the handling of native tokens to the {universalReceiver} function +passing `_TYPEID_LSP0_VALUE_RECEIVED` as typeId and an empty bytes array as received data. + +::: + ```solidity receive() external payable; ``` @@ -139,7 +157,7 @@ Executed: **Emitted events:** -- [`UniversalReceiver`](#universalreceiver) event when receiving native tokens. +- Emits a [`UniversalReceiver`](#universalreceiver) event when the `universalReceiver` logic is executed upon receiving native tokens.
@@ -197,6 +215,31 @@ function RENOUNCE_OWNERSHIP_CONFIRMATION_PERIOD()
+### VERSION + +:::note References + +- Specification details: [**LSP-0-ERC725Account**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-0-ERC725Account.md#version) +- Solidity implementation: [`LSP0ERC725Account.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP0ERC725Account/LSP0ERC725Account.sol) +- Function signature: `VERSION()` +- Function selector: `0xffa1ad74` + +::: + +```solidity +function VERSION() external view returns (string); +``` + +_Contract version._ + +#### Returns + +| Name | Type | Description | +| ---- | :------: | ----------- | +| `0` | `string` | - | + +
+ ### acceptOwnership :::note References @@ -487,6 +530,12 @@ Get in the ERC725Y storage the bytes data stored at multiple data keys `dataKeys ::: +:::caution Warning + +This function does not enforce by default the inclusion of the address of this contract in the signature digest. It is recommended that protocols or applications using this contract include the targeted address (= this contract) in the data to sign. To ensure that a signature is valid for a specific LSP0ERC725Account and prevent signatures from the same EOA to be replayed across different LSP0ERC725Accounts. + +::: + ```solidity function isValidSignature( bytes32 dataHash, @@ -812,7 +861,7 @@ Achieves the goal of [LSP-1-UniversalReceiver] by allowing the account to be not - If there is an address stored under the data key, check if this address supports the LSP1 interfaceId. -- If yes, call this address with the typeId and data (params), along with additional calldata consisting of 20 bytes of `msg.sender` and 32 bytes of `msg.value`. If not, continue the execution of the function. +- If yes, call this address with the typeId and data (params), along with additional calldata consisting of 20 bytes of `msg.sender` and 32 bytes of `msg.value`. If not, continue the execution of the function. This function delegates internally the handling of native tokens to the [`universalReceiver`](#universalreceiver) function itself passing `_TYPEID_LSP0_VALUE_RECEIVED` as typeId and the calldata as received data.
@@ -838,33 +887,6 @@ Achieves the goal of [LSP-1-UniversalReceiver] by allowing the account to be not
-### version - -:::note References - -- Specification details: [**LSP-0-ERC725Account**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-0-ERC725Account.md#version) -- Solidity implementation: [`LSP0ERC725Account.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP0ERC725Account/LSP0ERC725Account.sol) -- Function signature: `version()` -- Function selector: `0x54fd4d50` - -::: - -```solidity -function version() external view returns (string); -``` - -_Contract version._ - -Get the version of the contract. - -#### Returns - -| Name | Type | Description | -| ---- | :------: | -------------------------------- | -| `0` | `string` | The version of the the contract. | - -
- ## Internal Methods Any method labeled as `internal` serves as utility function within the contract. They can be used when writing solidity contracts that inherit from this contract. These methods can be extended or modified by overriding their internal behavior to suit specific needs. @@ -1180,26 +1202,21 @@ function _getExtensionAndForwardValue( ) internal view returns (address, bool); ``` -Returns the extension address stored under the following data key: +Returns the extension address and the boolean indicating whether to forward the value received to the extension, stored under the following data key: - [`_LSP17_EXTENSION_PREFIX`](#_lsp17_extension_prefix) + `` (Check [LSP2-ERC725YJSONSchema] for encoding the data key). - If no extension is stored, returns the address(0). +- If the stored value is 20 bytes, return false for the boolean +
### \_fallbackLSP17Extendable :::tip Hint -This function does not forward to the extension contract the `msg.value` received by the contract that inherits `LSP17Extendable`. -If you would like to forward the `msg.value` to the extension contract, you can override the code of this internal function as follow: - -```solidity -(bool success, bytes memory result) = extension.call{value: msg.value}( - abi.encodePacked(callData, msg.sender, msg.value) -); -``` +If you would like to forward the `msg.value` to the extension contract, you should store an additional `0x01` byte after the address of the extension under the corresponding LSP17 data key. ::: @@ -1464,11 +1481,11 @@ Emitted when the [`universalReceiver`](#universalreceiver) function was called w | Name | Type | Description | | ---------------------- | :-------: | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `from` **`indexed`** | `address` | The address of the EOA or smart contract that called the {universalReceiver(...)} function. | -| `value` **`indexed`** | `uint256` | The amount sent to the {universalReceiver(...)} function. | +| `from` **`indexed`** | `address` | The address of the EOA or smart contract that called the [`universalReceiver(...)`](#universalreceiver) function. | +| `value` **`indexed`** | `uint256` | The amount sent to the [`universalReceiver(...)`](#universalreceiver) function. | | `typeId` **`indexed`** | `bytes32` | A `bytes32` unique identifier (= _"hook"_)that describe the type of notification, information or transaction received by the contract. Can be related to a specific standard or a hook. | -| `receivedData` | `bytes` | Any arbitrary data that was sent to the {universalReceiver(...)} function. | -| `returnedValue` | `bytes` | The value returned by the {universalReceiver(...)} function. | +| `receivedData` | `bytes` | Any arbitrary data that was sent to the [`universalReceiver(...)`](#universalreceiver) function. | +| `returnedValue` | `bytes` | The value returned by the [`universalReceiver(...)`](#universalreceiver) function. |
diff --git a/docs/contracts/LSP17ContractExtension/LSP17Extension.md b/docs/contracts/LSP17ContractExtension/LSP17Extension.md index fe023c4dc..9206a06c9 100644 --- a/docs/contracts/LSP17ContractExtension/LSP17Extension.md +++ b/docs/contracts/LSP17ContractExtension/LSP17Extension.md @@ -23,61 +23,59 @@ Implementation of the extension logic according to LSP17ContractExtension. This Public methods are accessible externally from users, allowing interaction with this function from dApps or other smart contracts. When marked as 'public', a method can be called both externally and internally, on the other hand, when marked as 'external', a method can only be called externally. -### supportsInterface +### VERSION :::note References -- Specification details: [**LSP-17-ContractExtension**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-17-ContractExtension.md#supportsinterface) +- Specification details: [**LSP-17-ContractExtension**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-17-ContractExtension.md#version) - Solidity implementation: [`LSP17Extension.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP17ContractExtension/LSP17Extension.sol) -- Function signature: `supportsInterface(bytes4)` -- Function selector: `0x01ffc9a7` +- Function signature: `VERSION()` +- Function selector: `0xffa1ad74` ::: ```solidity -function supportsInterface(bytes4 interfaceId) external view returns (bool); +function VERSION() external view returns (string); ``` -See [`IERC165-supportsInterface`](#ierc165-supportsinterface). - -#### Parameters - -| Name | Type | Description | -| ------------- | :------: | ----------- | -| `interfaceId` | `bytes4` | - | +_Contract version._ #### Returns -| Name | Type | Description | -| ---- | :----: | ----------- | -| `0` | `bool` | - | +| Name | Type | Description | +| ---- | :------: | ----------- | +| `0` | `string` | - |
-### version +### supportsInterface :::note References -- Specification details: [**LSP-17-ContractExtension**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-17-ContractExtension.md#version) +- Specification details: [**LSP-17-ContractExtension**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-17-ContractExtension.md#supportsinterface) - Solidity implementation: [`LSP17Extension.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP17ContractExtension/LSP17Extension.sol) -- Function signature: `version()` -- Function selector: `0x54fd4d50` +- Function signature: `supportsInterface(bytes4)` +- Function selector: `0x01ffc9a7` ::: ```solidity -function version() external view returns (string); +function supportsInterface(bytes4 interfaceId) external view returns (bool); ``` -_Contract version._ +See [`IERC165-supportsInterface`](#ierc165-supportsinterface). -Get the version of the contract. +#### Parameters + +| Name | Type | Description | +| ------------- | :------: | ----------- | +| `interfaceId` | `bytes4` | - | #### Returns -| Name | Type | Description | -| ---- | :------: | -------------------------------- | -| `0` | `string` | The version of the the contract. | +| Name | Type | Description | +| ---- | :----: | ----------- | +| `0` | `bool` | - |
diff --git a/docs/contracts/LSP17Extensions/Extension4337.md b/docs/contracts/LSP17Extensions/Extension4337.md index bbb7be43d..75f90750f 100644 --- a/docs/contracts/LSP17Extensions/Extension4337.md +++ b/docs/contracts/LSP17Extensions/Extension4337.md @@ -40,6 +40,31 @@ constructor(address entryPoint_);
+### VERSION + +:::note References + +- Specification details: [**LSP-17-Extensions**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-17-Extensions.md#version) +- Solidity implementation: [`Extension4337.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP17Extensions/Extension4337.sol) +- Function signature: `VERSION()` +- Function selector: `0xffa1ad74` + +::: + +```solidity +function VERSION() external view returns (string); +``` + +_Contract version._ + +#### Returns + +| Name | Type | Description | +| ---- | :------: | ----------- | +| `0` | `string` | - | + +
+ ### entryPoint :::note References @@ -135,33 +160,6 @@ Must validate caller is the entryPoint. Must validate the signature and nonce
-### version - -:::note References - -- Specification details: [**LSP-17-Extensions**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-17-Extensions.md#version) -- Solidity implementation: [`Extension4337.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP17Extensions/Extension4337.sol) -- Function signature: `version()` -- Function selector: `0x54fd4d50` - -::: - -```solidity -function version() external view returns (string); -``` - -_Contract version._ - -Get the version of the contract. - -#### Returns - -| Name | Type | Description | -| ---- | :------: | -------------------------------- | -| `0` | `string` | The version of the the contract. | - -
- ## Internal Methods Any method labeled as `internal` serves as utility function within the contract. They can be used when writing solidity contracts that inherit from this contract. These methods can be extended or modified by overriding their internal behavior to suit specific needs. diff --git a/docs/contracts/LSP17Extensions/OnERC721ReceivedExtension.md b/docs/contracts/LSP17Extensions/OnERC721ReceivedExtension.md index 1f504f9a8..bd763aff9 100644 --- a/docs/contracts/LSP17Extensions/OnERC721ReceivedExtension.md +++ b/docs/contracts/LSP17Extensions/OnERC721ReceivedExtension.md @@ -21,6 +21,31 @@ LSP17 Extension that can be attached to a LSP17Extendable contract to allow it t Public methods are accessible externally from users, allowing interaction with this function from dApps or other smart contracts. When marked as 'public', a method can be called both externally and internally, on the other hand, when marked as 'external', a method can only be called externally. +### VERSION + +:::note References + +- Specification details: [**LSP-17-Extensions**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-17-Extensions.md#version) +- Solidity implementation: [`OnERC721ReceivedExtension.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP17Extensions/OnERC721ReceivedExtension.sol) +- Function signature: `VERSION()` +- Function selector: `0xffa1ad74` + +::: + +```solidity +function VERSION() external view returns (string); +``` + +_Contract version._ + +#### Returns + +| Name | Type | Description | +| ---- | :------: | ----------- | +| `0` | `string` | - | + +
+ ### onERC721Received :::note References @@ -59,3 +84,71 @@ See [`IERC721Receiver-onERC721Received`](#ierc721receiver-onerc721received). Alw | `0` | `bytes4` | - |
+ +### supportsInterface + +:::note References + +- Specification details: [**LSP-17-Extensions**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-17-Extensions.md#supportsinterface) +- Solidity implementation: [`OnERC721ReceivedExtension.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP17Extensions/OnERC721ReceivedExtension.sol) +- Function signature: `supportsInterface(bytes4)` +- Function selector: `0x01ffc9a7` + +::: + +```solidity +function supportsInterface(bytes4 interfaceId) external view returns (bool); +``` + +See [`IERC165-supportsInterface`](#ierc165-supportsinterface). + +#### Parameters + +| Name | Type | Description | +| ------------- | :------: | ----------- | +| `interfaceId` | `bytes4` | - | + +#### Returns + +| Name | Type | Description | +| ---- | :----: | ----------- | +| `0` | `bool` | - | + +
+ +## Internal Methods + +Any method labeled as `internal` serves as utility function within the contract. They can be used when writing solidity contracts that inherit from this contract. These methods can be extended or modified by overriding their internal behavior to suit specific needs. + +Internal functions cannot be called externally, whether from other smart contracts, dApp interfaces, or backend services. Their restricted accessibility ensures that they remain exclusively available within the context of the current contract, promoting controlled and encapsulated usage of these internal utilities. + +### \_extendableMsgData + +```solidity +function _extendableMsgData() internal view returns (bytes); +``` + +Returns the original `msg.data` passed to the extendable contract +without the appended `msg.sender` and `msg.value`. + +
+ +### \_extendableMsgSender + +```solidity +function _extendableMsgSender() internal view returns (address); +``` + +Returns the original `msg.sender` calling the extendable contract. + +
+ +### \_extendableMsgValue + +```solidity +function _extendableMsgValue() internal view returns (uint256); +``` + +Returns the original `msg.value` sent to the extendable contract. + +
diff --git a/docs/contracts/LSP1UniversalReceiver/LSP1UniversalReceiverDelegateUP/LSP1UniversalReceiverDelegateUP.md b/docs/contracts/LSP1UniversalReceiver/LSP1UniversalReceiverDelegateUP/LSP1UniversalReceiverDelegateUP.md index af880d4a2..fe770703b 100644 --- a/docs/contracts/LSP1UniversalReceiver/LSP1UniversalReceiverDelegateUP/LSP1UniversalReceiverDelegateUP.md +++ b/docs/contracts/LSP1UniversalReceiver/LSP1UniversalReceiverDelegateUP/LSP1UniversalReceiverDelegateUP.md @@ -27,6 +27,31 @@ The [`LSP1UniversalReceiverDelegateUP`](#lsp1universalreceiverdelegateup) follow Public methods are accessible externally from users, allowing interaction with this function from dApps or other smart contracts. When marked as 'public', a method can be called both externally and internally, on the other hand, when marked as 'external', a method can only be called externally. +### VERSION + +:::note References + +- Specification details: [**LSP-1-UniversalReceiver**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-1-UniversalReceiver.md#version) +- Solidity implementation: [`LSP1UniversalReceiverDelegateUP.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP1UniversalReceiver/LSP1UniversalReceiverDelegateUP/LSP1UniversalReceiverDelegateUP.sol) +- Function signature: `VERSION()` +- Function selector: `0xffa1ad74` + +::: + +```solidity +function VERSION() external view returns (string); +``` + +_Contract version._ + +#### Returns + +| Name | Type | Description | +| ---- | :------: | ----------- | +| `0` | `string` | - | + +
+ ### supportsInterface :::note References @@ -123,33 +148,6 @@ _Reacted on received notification with `typeId`._
-### version - -:::note References - -- Specification details: [**LSP-1-UniversalReceiver**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-1-UniversalReceiver.md#version) -- Solidity implementation: [`LSP1UniversalReceiverDelegateUP.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP1UniversalReceiver/LSP1UniversalReceiverDelegateUP/LSP1UniversalReceiverDelegateUP.sol) -- Function signature: `version()` -- Function selector: `0x54fd4d50` - -::: - -```solidity -function version() external view returns (string); -``` - -_Contract version._ - -Get the version of the contract. - -#### Returns - -| Name | Type | Description | -| ---- | :------: | -------------------------------- | -| `0` | `string` | The version of the the contract. | - -
- ## Internal Methods Any method labeled as `internal` serves as utility function within the contract. They can be used when writing solidity contracts that inherit from this contract. These methods can be extended or modified by overriding their internal behavior to suit specific needs. diff --git a/docs/contracts/LSP1UniversalReceiver/LSP1UniversalReceiverDelegateVault/LSP1UniversalReceiverDelegateVault.md b/docs/contracts/LSP1UniversalReceiver/LSP1UniversalReceiverDelegateVault/LSP1UniversalReceiverDelegateVault.md index 609078570..368b82286 100644 --- a/docs/contracts/LSP1UniversalReceiver/LSP1UniversalReceiverDelegateVault/LSP1UniversalReceiverDelegateVault.md +++ b/docs/contracts/LSP1UniversalReceiver/LSP1UniversalReceiverDelegateVault/LSP1UniversalReceiverDelegateVault.md @@ -25,6 +25,31 @@ The [`LSP1UniversalReceiverDelegateVault`](#lsp1universalreceiverdelegatevault) Public methods are accessible externally from users, allowing interaction with this function from dApps or other smart contracts. When marked as 'public', a method can be called both externally and internally, on the other hand, when marked as 'external', a method can only be called externally. +### VERSION + +:::note References + +- Specification details: [**LSP-1-UniversalReceiver**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-1-UniversalReceiver.md#version) +- Solidity implementation: [`LSP1UniversalReceiverDelegateVault.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP1UniversalReceiver/LSP1UniversalReceiverDelegateVault/LSP1UniversalReceiverDelegateVault.sol) +- Function signature: `VERSION()` +- Function selector: `0xffa1ad74` + +::: + +```solidity +function VERSION() external view returns (string); +``` + +_Contract version._ + +#### Returns + +| Name | Type | Description | +| ---- | :------: | ----------- | +| `0` | `string` | - | + +
+ ### supportsInterface :::note References @@ -112,33 +137,6 @@ Handles two cases: Writes the received [LSP-7-DigitalAsset] or [LSP-8-Identifiab
-### version - -:::note References - -- Specification details: [**LSP-1-UniversalReceiver**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-1-UniversalReceiver.md#version) -- Solidity implementation: [`LSP1UniversalReceiverDelegateVault.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP1UniversalReceiver/LSP1UniversalReceiverDelegateVault/LSP1UniversalReceiverDelegateVault.sol) -- Function signature: `version()` -- Function selector: `0x54fd4d50` - -::: - -```solidity -function version() external view returns (string); -``` - -_Contract version._ - -Get the version of the contract. - -#### Returns - -| Name | Type | Description | -| ---- | :------: | -------------------------------- | -| `0` | `string` | The version of the the contract. | - -
- ## Internal Methods Any method labeled as `internal` serves as utility function within the contract. They can be used when writing solidity contracts that inherit from this contract. These methods can be extended or modified by overriding their internal behavior to suit specific needs. diff --git a/docs/contracts/LSP25ExecuteRelayCall/LSP25MultiChannelNonce.md b/docs/contracts/LSP25ExecuteRelayCall/LSP25MultiChannelNonce.md index 872dfe71f..e789fed02 100644 --- a/docs/contracts/LSP25ExecuteRelayCall/LSP25MultiChannelNonce.md +++ b/docs/contracts/LSP25ExecuteRelayCall/LSP25MultiChannelNonce.md @@ -86,13 +86,13 @@ The address of the signer will be recovered using the LSP25 signature format. #### Parameters -| Name | Type | Description | -| -------------------- | :-------: | ----------------------------------------------------------------------------------------------------------------------- | -| `signature` | `bytes` | A 65 bytes long signature generated according to the signature format specified in the LSP25 standard. | -| `nonce` | `uint256` | The nonce that the signer used to generate the `signature`. | -| `validityTimestamps` | `uint256` | The validity timestamp that the signer used to generate the signature (See {\_verifyValidityTimestamps} to learn more). | -| `msgValue` | `uint256` | The amount of native tokens intended to be sent for the relay transaction. | -| `callData` | `bytes` | The calldata to execute as a relay transaction that the signer signed for. | +| Name | Type | Description | +| -------------------- | :-------: | ---------------------------------------------------------------------------------------------------------------------------------------------------- | +| `signature` | `bytes` | A 65 bytes long signature generated according to the signature format specified in the LSP25 standard. | +| `nonce` | `uint256` | The nonce that the signer used to generate the `signature`. | +| `validityTimestamps` | `uint256` | The validity timestamp that the signer used to generate the signature (See [`_verifyValidityTimestamps`](#_verifyvaliditytimestamps) to learn more). | +| `msgValue` | `uint256` | The amount of native tokens intended to be sent for the relay transaction. | +| `callData` | `bytes` | The calldata to execute as a relay transaction that the signer signed for. | #### Returns diff --git a/docs/contracts/LSP6KeyManager/LSP6KeyManager.md b/docs/contracts/LSP6KeyManager/LSP6KeyManager.md index 5c55131a7..007557455 100644 --- a/docs/contracts/LSP6KeyManager/LSP6KeyManager.md +++ b/docs/contracts/LSP6KeyManager/LSP6KeyManager.md @@ -48,6 +48,31 @@ Deploy a Key Manager and set the `target_` address in the contract storage, maki
+### VERSION + +:::note References + +- Specification details: [**LSP-6-KeyManager**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-6-KeyManager.md#version) +- Solidity implementation: [`LSP6KeyManager.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP6KeyManager/LSP6KeyManager.sol) +- Function signature: `VERSION()` +- Function selector: `0xffa1ad74` + +::: + +```solidity +function VERSION() external view returns (string); +``` + +_Contract version._ + +#### Returns + +| Name | Type | Description | +| ---- | :------: | ----------- | +| `0` | `string` | - | + +
+ ### execute :::note References @@ -77,15 +102,15 @@ Execute A `payload` on the linked [`target`](#target) contract after having veri #### Parameters -| Name | Type | Description | -| --------- | :-----: | ---------------------------------------------------------------- | -| `payload` | `bytes` | The abi-encoded function call to execute on the linked {target}. | +| Name | Type | Description | +| --------- | :-----: | --------------------------------------------------------------------------- | +| `payload` | `bytes` | The abi-encoded function call to execute on the linked [`target`](#target). | #### Returns -| Name | Type | Description | -| ---- | :-----: | ---------------------------------------------------------------------------- | -| `0` | `bytes` | The abi-decoded data returned by the function called on the linked {target}. | +| Name | Type | Description | +| ---- | :-----: | --------------------------------------------------------------------------------------- | +| `0` | `bytes` | The abi-decoded data returned by the function called on the linked [`target`](#target). |
@@ -125,16 +150,16 @@ Same as [`execute`](#execute) but execute a batch of payloads (abi-encoded funct #### Parameters -| Name | Type | Description | -| ---------- | :---------: | -------------------------------------------------------------------------------------- | -| `values` | `uint256[]` | An array of amount of native tokens to be transferred for each `payload`. | -| `payloads` | `bytes[]` | An array of abi-encoded function calls to execute successively on the linked {target}. | +| Name | Type | Description | +| ---------- | :---------: | ------------------------------------------------------------------------------------------------- | +| `values` | `uint256[]` | An array of amount of native tokens to be transferred for each `payload`. | +| `payloads` | `bytes[]` | An array of abi-encoded function calls to execute successively on the linked [`target`](#target). | #### Returns -| Name | Type | Description | -| ---- | :-------: | ------------------------------------------------------------------------------------- | -| `0` | `bytes[]` | An array of abi-decoded data returned by the functions called on the linked {target}. | +| Name | Type | Description | +| ---- | :-------: | ------------------------------------------------------------------------------------------------ | +| `0` | `bytes[]` | An array of abi-decoded data returned by the functions called on the linked [`target`](#target). |
@@ -181,7 +206,7 @@ Allows any address (executor) to execute a payload (= abi-encoded function call) | Name | Type | Description | | -------------------- | :-------: | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `signature` | `bytes` | A 65 bytes long signature for a meta transaction according to LSP25. | -| `nonce` | `uint256` | The nonce of the address that signed the calldata (in a specific `_channel`), obtained via {getNonce}. Used to prevent replay attack. | +| `nonce` | `uint256` | The nonce of the address that signed the calldata (in a specific `_channel`), obtained via [`getNonce`](#getnonce). Used to prevent replay attack. | | `validityTimestamps` | `uint256` | Two `uint128` timestamps concatenated together that describes when the relay transaction is valid "from" (left `uint128`) and "until" as a deadline (right `uint128`). | | `payload` | `bytes` | The abi-encoded function call to execute. | @@ -229,13 +254,13 @@ Same as [`executeRelayCall`](#executerelaycall) but execute a batch of signed ca #### Parameters -| Name | Type | Description | -| -------------------- | :---------: | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `signatures` | `bytes[]` | An array of 65 bytes long signatures for meta transactions according to LSP25. | -| `nonces` | `uint256[]` | An array of nonces of the addresses that signed the calldata payloads (in specific channels). Obtained via {getNonce}. Used to prevent replay attack. | -| `validityTimestamps` | `uint256[]` | An array of two `uint128` concatenated timestamps that describe when the relay transaction is valid "from" (left `uint128`) and "until" (right `uint128`). | -| `values` | `uint256[]` | An array of amount of native tokens to be transferred for each calldata `payload`. | -| `payloads` | `bytes[]` | An array of abi-encoded function calls to be executed successively. | +| Name | Type | Description | +| -------------------- | :---------: | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `signatures` | `bytes[]` | An array of 65 bytes long signatures for meta transactions according to LSP25. | +| `nonces` | `uint256[]` | An array of nonces of the addresses that signed the calldata payloads (in specific channels). Obtained via [`getNonce`](#getnonce). Used to prevent replay attack. | +| `validityTimestamps` | `uint256[]` | An array of two `uint128` concatenated timestamps that describe when the relay transaction is valid "from" (left `uint128`) and "until" (right `uint128`). | +| `values` | `uint256[]` | An array of amount of native tokens to be transferred for each calldata `payload`. | +| `payloads` | `bytes[]` | An array of abi-encoded function calls to be executed successively. | #### Returns @@ -302,6 +327,12 @@ Get the nonce for a specific `from` address that can be used for signing relay t ::: +:::caution Warning + +This function does not enforce by default the inclusion of the address of this contract in the signature digest. It is recommended that protocols or applications using this contract include the targeted address (= this contract) in the data to sign. To ensure that a signature is valid for a specific LSP6KeyManager and prevent signatures from the same EOA to be replayed across different LSP6KeyManager. + +::: + ```solidity function isValidSignature( bytes32 dataHash, @@ -463,33 +494,6 @@ Get The address of the contract linked to this Key Manager.
-### version - -:::note References - -- Specification details: [**LSP-6-KeyManager**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-6-KeyManager.md#version) -- Solidity implementation: [`LSP6KeyManager.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP6KeyManager/LSP6KeyManager.sol) -- Function signature: `version()` -- Function selector: `0x54fd4d50` - -::: - -```solidity -function version() external view returns (string); -``` - -_Contract version._ - -Get the version of the contract. - -#### Returns - -| Name | Type | Description | -| ---- | :------: | -------------------------------- | -| `0` | `string` | The version of the the contract. | - -
- ## Internal Methods Any method labeled as `internal` serves as utility function within the contract. They can be used when writing solidity contracts that inherit from this contract. These methods can be extended or modified by overriding their internal behavior to suit specific needs. @@ -906,7 +910,7 @@ function _verifyAllowedCall( function _extractCallType( uint256 operationType, uint256 value, - bool isEmptyCall + bytes data ) internal pure returns (bytes4 requiredCallTypes); ``` @@ -918,7 +922,7 @@ extract the bytes4 representation of a single bit for the type of call according | --------------- | :-------: | -------------------------------------------- | | `operationType` | `uint256` | 0 = CALL, 3 = STATICCALL or 3 = DELEGATECALL | | `value` | `uint256` | - | -| `isEmptyCall` | `bool` | - | +| `data` | `bytes` | - | #### Returns @@ -955,7 +959,7 @@ function _isAllowedStandard( ```solidity function _isAllowedFunction( bytes allowedCall, - bytes4 requiredFunction + bytes data ) internal pure returns (bool); ``` @@ -1056,13 +1060,13 @@ The address of the signer will be recovered using the LSP25 signature format. #### Parameters -| Name | Type | Description | -| -------------------- | :-------: | ----------------------------------------------------------------------------------------------------------------------- | -| `signature` | `bytes` | A 65 bytes long signature generated according to the signature format specified in the LSP25 standard. | -| `nonce` | `uint256` | The nonce that the signer used to generate the `signature`. | -| `validityTimestamps` | `uint256` | The validity timestamp that the signer used to generate the signature (See {\_verifyValidityTimestamps} to learn more). | -| `msgValue` | `uint256` | The amount of native tokens intended to be sent for the relay transaction. | -| `callData` | `bytes` | The calldata to execute as a relay transaction that the signer signed for. | +| Name | Type | Description | +| -------------------- | :-------: | ---------------------------------------------------------------------------------------------------------------------------------------------------- | +| `signature` | `bytes` | A 65 bytes long signature generated according to the signature format specified in the LSP25 standard. | +| `nonce` | `uint256` | The nonce that the signer used to generate the `signature`. | +| `validityTimestamps` | `uint256` | The validity timestamp that the signer used to generate the signature (See [`_verifyValidityTimestamps`](#_verifyvaliditytimestamps) to learn more). | +| `msgValue` | `uint256` | The amount of native tokens intended to be sent for the relay transaction. | +| `callData` | `bytes` | The calldata to execute as a relay transaction that the signer signed for. | #### Returns @@ -1176,17 +1180,17 @@ _Execute the `payload` passed to `execute(...)` or `executeRelayCall(...)`_ #### Parameters -| Name | Type | Description | -| ---------------- | :-------: | ------------------------------------------------------------------ | -| `targetContract` | `address` | - | -| `msgValue` | `uint256` | - | -| `payload` | `bytes` | The abi-encoded function call to execute on the {target} contract. | +| Name | Type | Description | +| ---------------- | :-------: | ----------------------------------------------------------------------------- | +| `targetContract` | `address` | - | +| `msgValue` | `uint256` | - | +| `payload` | `bytes` | The abi-encoded function call to execute on the [`target`](#target) contract. | #### Returns -| Name | Type | Description | -| ---- | :-----: | ------------------------------------------------------------------------- | -| `0` | `bytes` | bytes The data returned by the call made to the linked {target} contract. | +| Name | Type | Description | +| ---- | :-----: | ------------------------------------------------------------------------------------ | +| `0` | `bytes` | bytes The data returned by the call made to the linked [`target`](#target) contract. |
@@ -1205,12 +1209,12 @@ Verify if the `from` address is allowed to execute the `payload` on the [`target #### Parameters -| Name | Type | Description | -| ---------------- | :-------: | ------------------------------------------------------------------- | -| `targetContract` | `address` | The contract that is owned by the Key Manager | -| `from` | `address` | Either the caller of {execute} or the signer of {executeRelayCall}. | -| `isRelayedCall` | `bool` | - | -| `payload` | `bytes` | The abi-encoded function call to execute on the {target} contract. | +| Name | Type | Description | +| ---------------- | :-------: | ---------------------------------------------------------------------------------------------------- | +| `targetContract` | `address` | The contract that is owned by the Key Manager | +| `from` | `address` | Either the caller of [`execute`](#execute) or the signer of [`executeRelayCall`](#executerelaycall). | +| `isRelayedCall` | `bool` | - | +| `payload` | `bytes` | The abi-encoded function call to execute on the [`target`](#target) contract. |
@@ -1224,9 +1228,12 @@ function _nonReentrantBefore( ) internal nonpayable returns (bool reentrancyStatus); ``` -Update the status from `_NON_ENTERED` to `_ENTERED` and checks if -the status is `_ENTERED` in order to revert the call unless the caller has the REENTRANCY permission -Used in the beginning of the `nonReentrant` modifier, before the method execution starts. +Check if we are in the context of a reentrant call, by checking if the reentrancy status is `true`. + +- If the status is `true`, the caller (or signer for relay call) MUST have the `REENTRANCY` permission. Otherwise, the call is reverted. + +- If the status is `false`, it is set to `true` only if we are not dealing with a call to the functions `setData` or `setDataBatch`. + Used at the beginning of the [`lsp20VerifyCall`](#`lsp20verifycall`), [`_execute`](#`_execute`) and [`_executeRelayCall`](#`_executerelaycall`) functions, before the methods execution starts.
@@ -1236,8 +1243,8 @@ Used in the beginning of the `nonReentrant` modifier, before the method executio function _nonReentrantAfter(address targetContract) internal nonpayable; ``` -Resets the status to `false` -Used in the end of the `nonReentrant` modifier after the method execution is terminated +Resets the reentrancy status to `false` +Used at the end of the [`lsp20VerifyCall`](#`lsp20verifycall`), [`_execute`](#`_execute`) and [`_executeRelayCall`](#`_executerelaycall`) functions after the functions' execution is terminated.
@@ -1264,11 +1271,11 @@ Emitted when the LSP6KeyManager contract verified the permissions of the `signer #### Parameters -| Name | Type | Description | -| ------------------------ | :-------: | -------------------------------------------------------------------------------------------------------------------------------------------------- | -| `signer` **`indexed`** | `address` | The address of the controller that executed the calldata payload (either directly via {execute} or via meta transaction using {executeRelayCall}). | -| `value` **`indexed`** | `uint256` | The amount of native token to be transferred in the calldata payload. | -| `selector` **`indexed`** | `bytes4` | The bytes4 function of the function that was executed on the linked {target} | +| Name | Type | Description | +| ------------------------ | :-------: | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `signer` **`indexed`** | `address` | The address of the controller that executed the calldata payload (either directly via [`execute`](#execute) or via meta transaction using [`executeRelayCall`](#executerelaycall)). | +| `value` **`indexed`** | `uint256` | The amount of native token to be transferred in the calldata payload. | +| `selector` **`indexed`** | `bytes4` | The bytes4 function of the function that was executed on the linked [`target`](#target) |
@@ -1476,9 +1483,9 @@ Reverts when trying to call a function on the linked [`target`](#target), that i #### Parameters -| Name | Type | Description | -| ----------------- | :------: | ---------------------------------------------------------------------------------------------------------------- | -| `invalidFunction` | `bytes4` | The `bytes4` selector of the function that was attempted to be called on the linked {target} but not recognised. | +| Name | Type | Description | +| ----------------- | :------: | --------------------------------------------------------------------------------------------------------------------------- | +| `invalidFunction` | `bytes4` | The `bytes4` selector of the function that was attempted to be called on the linked [`target`](#target) but not recognised. |
@@ -1827,7 +1834,7 @@ Reverts when `from` is not authorised to call the `execute(uint256,address,uint2 | Name | Type | Description | | ---------- | :-------: | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `from` | `address` | The controller that tried to call the `execute(uint256,address,uint256,bytes)` function. | -| `to` | `address` | The address of an EOA or contract that `from` tried to call using the linked {target} | +| `to` | `address` | The address of an EOA or contract that `from` tried to call using the linked [`target`](#target) | | `selector` | `bytes4` | If `to` is a contract, the bytes4 selector of the function that `from` is trying to call. If no function is called (_e.g: a native token transfer_), selector = `0x00000000` |
@@ -1853,10 +1860,10 @@ Reverts when address `from` is not authorised to set the key `disallowedKey` on #### Parameters -| Name | Type | Description | -| --------------- | :-------: | ------------------------------------------------------------------------------------------------------ | -| `from` | `address` | address The controller that tried to `setData` on the linked {target}. | -| `disallowedKey` | `bytes32` | A bytes32 data key that `from` is not authorised to set on the ERC725Y storage of the linked {target}. | +| Name | Type | Description | +| --------------- | :-------: | ----------------------------------------------------------------------------------------------------------------- | +| `from` | `address` | address The controller that tried to `setData` on the linked [`target`](#target). | +| `disallowedKey` | `bytes32` | A bytes32 data key that `from` is not authorised to set on the ERC725Y storage of the linked [`target`](#target). |
diff --git a/docs/contracts/LSP7DigitalAsset/LSP7DigitalAsset.md b/docs/contracts/LSP7DigitalAsset/LSP7DigitalAsset.md index 02025db4c..3ea9eb046 100644 --- a/docs/contracts/LSP7DigitalAsset/LSP7DigitalAsset.md +++ b/docs/contracts/LSP7DigitalAsset/LSP7DigitalAsset.md @@ -696,33 +696,6 @@ Transfers ownership of the contract to a new account (`newOwner`). Can only be c
-### version - -:::note References - -- Specification details: [**LSP-7-DigitalAsset**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-7-DigitalAsset.md#version) -- Solidity implementation: [`LSP7DigitalAsset.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP7DigitalAsset/LSP7DigitalAsset.sol) -- Function signature: `version()` -- Function selector: `0x54fd4d50` - -::: - -```solidity -function version() external view returns (string); -``` - -_Contract version._ - -Get the version of the contract. - -#### Returns - -| Name | Type | Description | -| ---- | :------: | -------------------------------- | -| `0` | `string` | The version of the the contract. | - -
- ## Internal Methods Any method labeled as `internal` serves as utility function within the contract. They can be used when writing solidity contracts that inherit from this contract. These methods can be extended or modified by overriding their internal behavior to suit specific needs. @@ -848,12 +821,12 @@ Mints `amount` of tokens and transfers it to `to`. #### Parameters -| Name | Type | Description | -| -------- | :-------: | ------------------------------------------------------------------------------------------------------------------------- | -| `to` | `address` | The address to mint tokens for. | -| `amount` | `uint256` | The amount of tokens to mint. | -| `force` | `bool` | A boolean that describe if transfer to a `to` address that does not support LSP1 is allowed or not. | -| `data` | `bytes` | Additional data the caller wants included in the emitted {Transfer} event, and sent in the LSP1 hook to the `to` address. | +| Name | Type | Description | +| -------- | :-------: | -------------------------------------------------------------------------------------------------------------------------------------- | +| `to` | `address` | The address to mint tokens for. | +| `amount` | `uint256` | The amount of tokens to mint. | +| `force` | `bool` | A boolean that describe if transfer to a `to` address that does not support LSP1 is allowed or not. | +| `data` | `bytes` | Additional data the caller wants included in the emitted [`Transfer`](#transfer) event, and sent in the LSP1 hook to the `to` address. |
@@ -1044,7 +1017,7 @@ If `to` is is an EOA or a contract that does not support the LSP1 interface, the | Name | Type | Description | | ---------- | :-------: | --------------------------------------------------------------------------------------------------- | -| `to` | `address` | The address to call the {universalReceiver} function on. | +| `to` | `address` | The address to call the [`universalReceiver`](#universalreceiver) function on. | | `force` | `bool` | A boolean that describe if transfer to a `to` address that does not support LSP1 is allowed or not. | | `lsp1Data` | `bytes` | The data to be sent to the `to` address in the `universalReceiver(...)` call. | diff --git a/docs/contracts/LSP7DigitalAsset/extensions/LSP7Burnable.md b/docs/contracts/LSP7DigitalAsset/extensions/LSP7Burnable.md index 28572334b..5f71d36be 100644 --- a/docs/contracts/LSP7DigitalAsset/extensions/LSP7Burnable.md +++ b/docs/contracts/LSP7DigitalAsset/extensions/LSP7Burnable.md @@ -721,33 +721,6 @@ Transfers ownership of the contract to a new account (`newOwner`). Can only be c
-### version - -:::note References - -- Specification details: [**LSP-7-DigitalAsset**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-7-DigitalAsset.md#version) -- Solidity implementation: [`LSP7Burnable.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP7DigitalAsset/extensions/LSP7Burnable.sol) -- Function signature: `version()` -- Function selector: `0x54fd4d50` - -::: - -```solidity -function version() external view returns (string); -``` - -_Contract version._ - -Get the version of the contract. - -#### Returns - -| Name | Type | Description | -| ---- | :------: | -------------------------------- | -| `0` | `string` | The version of the the contract. | - -
- ## Internal Methods Any method labeled as `internal` serves as utility function within the contract. They can be used when writing solidity contracts that inherit from this contract. These methods can be extended or modified by overriding their internal behavior to suit specific needs. @@ -873,12 +846,12 @@ Mints `amount` of tokens and transfers it to `to`. #### Parameters -| Name | Type | Description | -| -------- | :-------: | ------------------------------------------------------------------------------------------------------------------------- | -| `to` | `address` | The address to mint tokens for. | -| `amount` | `uint256` | The amount of tokens to mint. | -| `force` | `bool` | A boolean that describe if transfer to a `to` address that does not support LSP1 is allowed or not. | -| `data` | `bytes` | Additional data the caller wants included in the emitted {Transfer} event, and sent in the LSP1 hook to the `to` address. | +| Name | Type | Description | +| -------- | :-------: | -------------------------------------------------------------------------------------------------------------------------------------- | +| `to` | `address` | The address to mint tokens for. | +| `amount` | `uint256` | The amount of tokens to mint. | +| `force` | `bool` | A boolean that describe if transfer to a `to` address that does not support LSP1 is allowed or not. | +| `data` | `bytes` | Additional data the caller wants included in the emitted [`Transfer`](#transfer) event, and sent in the LSP1 hook to the `to` address. |
@@ -1069,7 +1042,7 @@ If `to` is is an EOA or a contract that does not support the LSP1 interface, the | Name | Type | Description | | ---------- | :-------: | --------------------------------------------------------------------------------------------------- | -| `to` | `address` | The address to call the {universalReceiver} function on. | +| `to` | `address` | The address to call the [`universalReceiver`](#universalreceiver) function on. | | `force` | `bool` | A boolean that describe if transfer to a `to` address that does not support LSP1 is allowed or not. | | `lsp1Data` | `bytes` | The data to be sent to the `to` address in the `universalReceiver(...)` call. | diff --git a/docs/contracts/LSP7DigitalAsset/extensions/LSP7CappedSupply.md b/docs/contracts/LSP7DigitalAsset/extensions/LSP7CappedSupply.md index 38ca3799e..c9a8bb576 100644 --- a/docs/contracts/LSP7DigitalAsset/extensions/LSP7CappedSupply.md +++ b/docs/contracts/LSP7DigitalAsset/extensions/LSP7CappedSupply.md @@ -721,33 +721,6 @@ Transfers ownership of the contract to a new account (`newOwner`). Can only be c
-### version - -:::note References - -- Specification details: [**LSP-7-DigitalAsset**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-7-DigitalAsset.md#version) -- Solidity implementation: [`LSP7CappedSupply.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP7DigitalAsset/extensions/LSP7CappedSupply.sol) -- Function signature: `version()` -- Function selector: `0x54fd4d50` - -::: - -```solidity -function version() external view returns (string); -``` - -_Contract version._ - -Get the version of the contract. - -#### Returns - -| Name | Type | Description | -| ---- | :------: | -------------------------------- | -| `0` | `string` | The version of the the contract. | - -
- ## Internal Methods Any method labeled as `internal` serves as utility function within the contract. They can be used when writing solidity contracts that inherit from this contract. These methods can be extended or modified by overriding their internal behavior to suit specific needs. @@ -1043,7 +1016,7 @@ If `to` is is an EOA or a contract that does not support the LSP1 interface, the | Name | Type | Description | | ---------- | :-------: | --------------------------------------------------------------------------------------------------- | -| `to` | `address` | The address to call the {universalReceiver} function on. | +| `to` | `address` | The address to call the [`universalReceiver`](#universalreceiver) function on. | | `force` | `bool` | A boolean that describe if transfer to a `to` address that does not support LSP1 is allowed or not. | | `lsp1Data` | `bytes` | The data to be sent to the `to` address in the `universalReceiver(...)` call. | diff --git a/docs/contracts/LSP7DigitalAsset/extensions/LSP7CompatibleERC20.md b/docs/contracts/LSP7DigitalAsset/extensions/LSP7CompatibleERC20.md index 49e6a5ceb..4a64caca8 100644 --- a/docs/contracts/LSP7DigitalAsset/extensions/LSP7CompatibleERC20.md +++ b/docs/contracts/LSP7DigitalAsset/extensions/LSP7CompatibleERC20.md @@ -898,33 +898,6 @@ Transfers ownership of the contract to a new account (`newOwner`). Can only be c
-### version - -:::note References - -- Specification details: [**LSP-7-DigitalAsset**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-7-DigitalAsset.md#version) -- Solidity implementation: [`LSP7CompatibleERC20.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP7DigitalAsset/extensions/LSP7CompatibleERC20.sol) -- Function signature: `version()` -- Function selector: `0x54fd4d50` - -::: - -```solidity -function version() external view returns (string); -``` - -_Contract version._ - -Get the version of the contract. - -#### Returns - -| Name | Type | Description | -| ---- | :------: | -------------------------------- | -| `0` | `string` | The version of the the contract. | - -
- ## Internal Methods Any method labeled as `internal` serves as utility function within the contract. They can be used when writing solidity contracts that inherit from this contract. These methods can be extended or modified by overriding their internal behavior to suit specific needs. @@ -1132,7 +1105,7 @@ If `to` is is an EOA or a contract that does not support the LSP1 interface, the | Name | Type | Description | | ---------- | :-------: | --------------------------------------------------------------------------------------------------- | -| `to` | `address` | The address to call the {universalReceiver} function on. | +| `to` | `address` | The address to call the [`universalReceiver`](#universalreceiver) function on. | | `force` | `bool` | A boolean that describe if transfer to a `to` address that does not support LSP1 is allowed or not. | | `lsp1Data` | `bytes` | The data to be sent to the `to` address in the `universalReceiver(...)` call. | diff --git a/docs/contracts/LSP7DigitalAsset/presets/LSP7CompatibleERC20Mintable.md b/docs/contracts/LSP7DigitalAsset/presets/LSP7CompatibleERC20Mintable.md index 0abc22ff6..3050fa6f8 100644 --- a/docs/contracts/LSP7DigitalAsset/presets/LSP7CompatibleERC20Mintable.md +++ b/docs/contracts/LSP7DigitalAsset/presets/LSP7CompatibleERC20Mintable.md @@ -932,33 +932,6 @@ Transfers ownership of the contract to a new account (`newOwner`). Can only be c
-### version - -:::note References - -- Specification details: [**LSP-7-DigitalAsset**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-7-DigitalAsset.md#version) -- Solidity implementation: [`LSP7CompatibleERC20Mintable.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP7DigitalAsset/presets/LSP7CompatibleERC20Mintable.sol) -- Function signature: `version()` -- Function selector: `0x54fd4d50` - -::: - -```solidity -function version() external view returns (string); -``` - -_Contract version._ - -Get the version of the contract. - -#### Returns - -| Name | Type | Description | -| ---- | :------: | -------------------------------- | -| `0` | `string` | The version of the the contract. | - -
- ## Internal Methods Any method labeled as `internal` serves as utility function within the contract. They can be used when writing solidity contracts that inherit from this contract. These methods can be extended or modified by overriding their internal behavior to suit specific needs. @@ -1166,7 +1139,7 @@ If `to` is is an EOA or a contract that does not support the LSP1 interface, the | Name | Type | Description | | ---------- | :-------: | --------------------------------------------------------------------------------------------------- | -| `to` | `address` | The address to call the {universalReceiver} function on. | +| `to` | `address` | The address to call the [`universalReceiver`](#universalreceiver) function on. | | `force` | `bool` | A boolean that describe if transfer to a `to` address that does not support LSP1 is allowed or not. | | `lsp1Data` | `bytes` | The data to be sent to the `to` address in the `universalReceiver(...)` call. | diff --git a/docs/contracts/LSP7DigitalAsset/presets/LSP7Mintable.md b/docs/contracts/LSP7DigitalAsset/presets/LSP7Mintable.md index 5bafb806f..15907384f 100644 --- a/docs/contracts/LSP7DigitalAsset/presets/LSP7Mintable.md +++ b/docs/contracts/LSP7DigitalAsset/presets/LSP7Mintable.md @@ -758,33 +758,6 @@ Transfers ownership of the contract to a new account (`newOwner`). Can only be c
-### version - -:::note References - -- Specification details: [**LSP-7-DigitalAsset**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-7-DigitalAsset.md#version) -- Solidity implementation: [`LSP7Mintable.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP7DigitalAsset/presets/LSP7Mintable.sol) -- Function signature: `version()` -- Function selector: `0x54fd4d50` - -::: - -```solidity -function version() external view returns (string); -``` - -_Contract version._ - -Get the version of the contract. - -#### Returns - -| Name | Type | Description | -| ---- | :------: | -------------------------------- | -| `0` | `string` | The version of the the contract. | - -
- ## Internal Methods Any method labeled as `internal` serves as utility function within the contract. They can be used when writing solidity contracts that inherit from this contract. These methods can be extended or modified by overriding their internal behavior to suit specific needs. @@ -910,12 +883,12 @@ Mints `amount` of tokens and transfers it to `to`. #### Parameters -| Name | Type | Description | -| -------- | :-------: | ------------------------------------------------------------------------------------------------------------------------- | -| `to` | `address` | The address to mint tokens for. | -| `amount` | `uint256` | The amount of tokens to mint. | -| `force` | `bool` | A boolean that describe if transfer to a `to` address that does not support LSP1 is allowed or not. | -| `data` | `bytes` | Additional data the caller wants included in the emitted {Transfer} event, and sent in the LSP1 hook to the `to` address. | +| Name | Type | Description | +| -------- | :-------: | -------------------------------------------------------------------------------------------------------------------------------------- | +| `to` | `address` | The address to mint tokens for. | +| `amount` | `uint256` | The amount of tokens to mint. | +| `force` | `bool` | A boolean that describe if transfer to a `to` address that does not support LSP1 is allowed or not. | +| `data` | `bytes` | Additional data the caller wants included in the emitted [`Transfer`](#transfer) event, and sent in the LSP1 hook to the `to` address. |
@@ -1106,7 +1079,7 @@ If `to` is is an EOA or a contract that does not support the LSP1 interface, the | Name | Type | Description | | ---------- | :-------: | --------------------------------------------------------------------------------------------------- | -| `to` | `address` | The address to call the {universalReceiver} function on. | +| `to` | `address` | The address to call the [`universalReceiver`](#universalreceiver) function on. | | `force` | `bool` | A boolean that describe if transfer to a `to` address that does not support LSP1 is allowed or not. | | `lsp1Data` | `bytes` | The data to be sent to the `to` address in the `universalReceiver(...)` call. | diff --git a/docs/contracts/LSP8IdentifiableDigitalAsset/LSP8IdentifiableDigitalAsset.md b/docs/contracts/LSP8IdentifiableDigitalAsset/LSP8IdentifiableDigitalAsset.md index c3c5c282d..77aa1e88d 100644 --- a/docs/contracts/LSP8IdentifiableDigitalAsset/LSP8IdentifiableDigitalAsset.md +++ b/docs/contracts/LSP8IdentifiableDigitalAsset/LSP8IdentifiableDigitalAsset.md @@ -663,33 +663,6 @@ Transfers ownership of the contract to a new account (`newOwner`). Can only be c
-### version - -:::note References - -- Specification details: [**LSP-8-IdentifiableDigitalAsset**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-8-IdentifiableDigitalAsset.md#version) -- Solidity implementation: [`LSP8IdentifiableDigitalAsset.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP8IdentifiableDigitalAsset/LSP8IdentifiableDigitalAsset.sol) -- Function signature: `version()` -- Function selector: `0x54fd4d50` - -::: - -```solidity -function version() external view returns (string); -``` - -_Contract version._ - -Get the version of the contract. - -#### Returns - -| Name | Type | Description | -| ---- | :------: | -------------------------------- | -| `0` | `string` | The version of the the contract. | - -
- ## Internal Methods Any method labeled as `internal` serves as utility function within the contract. They can be used when writing solidity contracts that inherit from this contract. These methods can be extended or modified by overriding their internal behavior to suit specific needs. @@ -1196,13 +1169,13 @@ Emitted when `tokenOwner` disables `operator` to transfer or burn `tokenId` on i #### Parameters -| Name | Type | Description | -| -------------------------- | :-------: | --------------------------------------------------------------- | -| `operator` **`indexed`** | `address` | The address revoked from the operator array ({getOperatorsOf}). | -| `tokenOwner` **`indexed`** | `address` | The owner of the `tokenId`. | -| `tokenId` **`indexed`** | `bytes32` | The tokenId `operator` is revoked from operating on. | -| `notified` | `bool` | Bool indicating whether the operator has been notified or not | -| `operatorNotificationData` | `bytes` | The data to notify the operator about via LSP1. | +| Name | Type | Description | +| -------------------------- | :-------: | ---------------------------------------------------------------------------------- | +| `operator` **`indexed`** | `address` | The address revoked from the operator array ([`getOperatorsOf`](#getoperatorsof)). | +| `tokenOwner` **`indexed`** | `address` | The owner of the `tokenId`. | +| `tokenId` **`indexed`** | `bytes32` | The tokenId `operator` is revoked from operating on. | +| `notified` | `bool` | Bool indicating whether the operator has been notified or not | +| `operatorNotificationData` | `bytes` | The data to notify the operator about via LSP1. |
diff --git a/docs/contracts/LSP8IdentifiableDigitalAsset/extensions/LSP8Burnable.md b/docs/contracts/LSP8IdentifiableDigitalAsset/extensions/LSP8Burnable.md index 9b444005b..940acb46a 100644 --- a/docs/contracts/LSP8IdentifiableDigitalAsset/extensions/LSP8Burnable.md +++ b/docs/contracts/LSP8IdentifiableDigitalAsset/extensions/LSP8Burnable.md @@ -689,33 +689,6 @@ Transfers ownership of the contract to a new account (`newOwner`). Can only be c
-### version - -:::note References - -- Specification details: [**LSP-8-IdentifiableDigitalAsset**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-8-IdentifiableDigitalAsset.md#version) -- Solidity implementation: [`LSP8Burnable.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP8IdentifiableDigitalAsset/extensions/LSP8Burnable.sol) -- Function signature: `version()` -- Function selector: `0x54fd4d50` - -::: - -```solidity -function version() external view returns (string); -``` - -_Contract version._ - -Get the version of the contract. - -#### Returns - -| Name | Type | Description | -| ---- | :------: | -------------------------------- | -| `0` | `string` | The version of the the contract. | - -
- ## Internal Methods Any method labeled as `internal` serves as utility function within the contract. They can be used when writing solidity contracts that inherit from this contract. These methods can be extended or modified by overriding their internal behavior to suit specific needs. @@ -1222,13 +1195,13 @@ Emitted when `tokenOwner` disables `operator` to transfer or burn `tokenId` on i #### Parameters -| Name | Type | Description | -| -------------------------- | :-------: | --------------------------------------------------------------- | -| `operator` **`indexed`** | `address` | The address revoked from the operator array ({getOperatorsOf}). | -| `tokenOwner` **`indexed`** | `address` | The owner of the `tokenId`. | -| `tokenId` **`indexed`** | `bytes32` | The tokenId `operator` is revoked from operating on. | -| `notified` | `bool` | Bool indicating whether the operator has been notified or not | -| `operatorNotificationData` | `bytes` | The data to notify the operator about via LSP1. | +| Name | Type | Description | +| -------------------------- | :-------: | ---------------------------------------------------------------------------------- | +| `operator` **`indexed`** | `address` | The address revoked from the operator array ([`getOperatorsOf`](#getoperatorsof)). | +| `tokenOwner` **`indexed`** | `address` | The owner of the `tokenId`. | +| `tokenId` **`indexed`** | `bytes32` | The tokenId `operator` is revoked from operating on. | +| `notified` | `bool` | Bool indicating whether the operator has been notified or not | +| `operatorNotificationData` | `bytes` | The data to notify the operator about via LSP1. |
diff --git a/docs/contracts/LSP8IdentifiableDigitalAsset/extensions/LSP8CappedSupply.md b/docs/contracts/LSP8IdentifiableDigitalAsset/extensions/LSP8CappedSupply.md index a2688e0ef..a67a24c75 100644 --- a/docs/contracts/LSP8IdentifiableDigitalAsset/extensions/LSP8CappedSupply.md +++ b/docs/contracts/LSP8IdentifiableDigitalAsset/extensions/LSP8CappedSupply.md @@ -688,33 +688,6 @@ Transfers ownership of the contract to a new account (`newOwner`). Can only be c
-### version - -:::note References - -- Specification details: [**LSP-8-IdentifiableDigitalAsset**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-8-IdentifiableDigitalAsset.md#version) -- Solidity implementation: [`LSP8CappedSupply.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP8IdentifiableDigitalAsset/extensions/LSP8CappedSupply.sol) -- Function signature: `version()` -- Function selector: `0x54fd4d50` - -::: - -```solidity -function version() external view returns (string); -``` - -_Contract version._ - -Get the version of the contract. - -#### Returns - -| Name | Type | Description | -| ---- | :------: | -------------------------------- | -| `0` | `string` | The version of the the contract. | - -
- ## Internal Methods Any method labeled as `internal` serves as utility function within the contract. They can be used when writing solidity contracts that inherit from this contract. These methods can be extended or modified by overriding their internal behavior to suit specific needs. @@ -1196,13 +1169,13 @@ Emitted when `tokenOwner` disables `operator` to transfer or burn `tokenId` on i #### Parameters -| Name | Type | Description | -| -------------------------- | :-------: | --------------------------------------------------------------- | -| `operator` **`indexed`** | `address` | The address revoked from the operator array ({getOperatorsOf}). | -| `tokenOwner` **`indexed`** | `address` | The owner of the `tokenId`. | -| `tokenId` **`indexed`** | `bytes32` | The tokenId `operator` is revoked from operating on. | -| `notified` | `bool` | Bool indicating whether the operator has been notified or not | -| `operatorNotificationData` | `bytes` | The data to notify the operator about via LSP1. | +| Name | Type | Description | +| -------------------------- | :-------: | ---------------------------------------------------------------------------------- | +| `operator` **`indexed`** | `address` | The address revoked from the operator array ([`getOperatorsOf`](#getoperatorsof)). | +| `tokenOwner` **`indexed`** | `address` | The owner of the `tokenId`. | +| `tokenId` **`indexed`** | `bytes32` | The tokenId `operator` is revoked from operating on. | +| `notified` | `bool` | Bool indicating whether the operator has been notified or not | +| `operatorNotificationData` | `bytes` | The data to notify the operator about via LSP1. |
diff --git a/docs/contracts/LSP8IdentifiableDigitalAsset/extensions/LSP8CompatibleERC721.md b/docs/contracts/LSP8IdentifiableDigitalAsset/extensions/LSP8CompatibleERC721.md index a23d9573d..dda3eb65b 100644 --- a/docs/contracts/LSP8IdentifiableDigitalAsset/extensions/LSP8CompatibleERC721.md +++ b/docs/contracts/LSP8IdentifiableDigitalAsset/extensions/LSP8CompatibleERC721.md @@ -1039,33 +1039,6 @@ Transfers ownership of the contract to a new account (`newOwner`). Can only be c
-### version - -:::note References - -- Specification details: [**LSP-8-IdentifiableDigitalAsset**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-8-IdentifiableDigitalAsset.md#version) -- Solidity implementation: [`LSP8CompatibleERC721.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP8IdentifiableDigitalAsset/extensions/LSP8CompatibleERC721.sol) -- Function signature: `version()` -- Function selector: `0x54fd4d50` - -::: - -```solidity -function version() external view returns (string); -``` - -_Contract version._ - -Get the version of the contract. - -#### Returns - -| Name | Type | Description | -| ---- | :------: | -------------------------------- | -| `0` | `string` | The version of the the contract. | - -
- ## Internal Methods Any method labeled as `internal` serves as utility function within the contract. They can be used when writing solidity contracts that inherit from this contract. These methods can be extended or modified by overriding their internal behavior to suit specific needs. @@ -1560,13 +1533,13 @@ Emitted when `tokenOwner` disables `operator` to transfer or burn `tokenId` on i #### Parameters -| Name | Type | Description | -| -------------------------- | :-------: | --------------------------------------------------------------- | -| `operator` **`indexed`** | `address` | The address revoked from the operator array ({getOperatorsOf}). | -| `tokenOwner` **`indexed`** | `address` | The owner of the `tokenId`. | -| `tokenId` **`indexed`** | `bytes32` | The tokenId `operator` is revoked from operating on. | -| `notified` | `bool` | Bool indicating whether the operator has been notified or not | -| `operatorNotificationData` | `bytes` | The data to notify the operator about via LSP1. | +| Name | Type | Description | +| -------------------------- | :-------: | ---------------------------------------------------------------------------------- | +| `operator` **`indexed`** | `address` | The address revoked from the operator array ([`getOperatorsOf`](#getoperatorsof)). | +| `tokenOwner` **`indexed`** | `address` | The owner of the `tokenId`. | +| `tokenId` **`indexed`** | `bytes32` | The tokenId `operator` is revoked from operating on. | +| `notified` | `bool` | Bool indicating whether the operator has been notified or not | +| `operatorNotificationData` | `bytes` | The data to notify the operator about via LSP1. |
diff --git a/docs/contracts/LSP8IdentifiableDigitalAsset/extensions/LSP8Enumerable.md b/docs/contracts/LSP8IdentifiableDigitalAsset/extensions/LSP8Enumerable.md index a9dec5aa0..c14d1cfdf 100644 --- a/docs/contracts/LSP8IdentifiableDigitalAsset/extensions/LSP8Enumerable.md +++ b/docs/contracts/LSP8IdentifiableDigitalAsset/extensions/LSP8Enumerable.md @@ -694,33 +694,6 @@ Transfers ownership of the contract to a new account (`newOwner`). Can only be c
-### version - -:::note References - -- Specification details: [**LSP-8-IdentifiableDigitalAsset**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-8-IdentifiableDigitalAsset.md#version) -- Solidity implementation: [`LSP8Enumerable.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP8IdentifiableDigitalAsset/extensions/LSP8Enumerable.sol) -- Function signature: `version()` -- Function selector: `0x54fd4d50` - -::: - -```solidity -function version() external view returns (string); -``` - -_Contract version._ - -Get the version of the contract. - -#### Returns - -| Name | Type | Description | -| ---- | :------: | -------------------------------- | -| `0` | `string` | The version of the the contract. | - -
- ## Internal Methods Any method labeled as `internal` serves as utility function within the contract. They can be used when writing solidity contracts that inherit from this contract. These methods can be extended or modified by overriding their internal behavior to suit specific needs. @@ -1224,13 +1197,13 @@ Emitted when `tokenOwner` disables `operator` to transfer or burn `tokenId` on i #### Parameters -| Name | Type | Description | -| -------------------------- | :-------: | --------------------------------------------------------------- | -| `operator` **`indexed`** | `address` | The address revoked from the operator array ({getOperatorsOf}). | -| `tokenOwner` **`indexed`** | `address` | The owner of the `tokenId`. | -| `tokenId` **`indexed`** | `bytes32` | The tokenId `operator` is revoked from operating on. | -| `notified` | `bool` | Bool indicating whether the operator has been notified or not | -| `operatorNotificationData` | `bytes` | The data to notify the operator about via LSP1. | +| Name | Type | Description | +| -------------------------- | :-------: | ---------------------------------------------------------------------------------- | +| `operator` **`indexed`** | `address` | The address revoked from the operator array ([`getOperatorsOf`](#getoperatorsof)). | +| `tokenOwner` **`indexed`** | `address` | The owner of the `tokenId`. | +| `tokenId` **`indexed`** | `bytes32` | The tokenId `operator` is revoked from operating on. | +| `notified` | `bool` | Bool indicating whether the operator has been notified or not | +| `operatorNotificationData` | `bytes` | The data to notify the operator about via LSP1. |
diff --git a/docs/contracts/LSP8IdentifiableDigitalAsset/presets/LSP8CompatibleERC721Mintable.md b/docs/contracts/LSP8IdentifiableDigitalAsset/presets/LSP8CompatibleERC721Mintable.md index 4c33c7597..07f2900f8 100644 --- a/docs/contracts/LSP8IdentifiableDigitalAsset/presets/LSP8CompatibleERC721Mintable.md +++ b/docs/contracts/LSP8IdentifiableDigitalAsset/presets/LSP8CompatibleERC721Mintable.md @@ -1081,33 +1081,6 @@ Transfers ownership of the contract to a new account (`newOwner`). Can only be c
-### version - -:::note References - -- Specification details: [**LSP-8-IdentifiableDigitalAsset**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-8-IdentifiableDigitalAsset.md#version) -- Solidity implementation: [`LSP8CompatibleERC721Mintable.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP8IdentifiableDigitalAsset/presets/LSP8CompatibleERC721Mintable.sol) -- Function signature: `version()` -- Function selector: `0x54fd4d50` - -::: - -```solidity -function version() external view returns (string); -``` - -_Contract version._ - -Get the version of the contract. - -#### Returns - -| Name | Type | Description | -| ---- | :------: | -------------------------------- | -| `0` | `string` | The version of the the contract. | - -
- ## Internal Methods Any method labeled as `internal` serves as utility function within the contract. They can be used when writing solidity contracts that inherit from this contract. These methods can be extended or modified by overriding their internal behavior to suit specific needs. @@ -1602,13 +1575,13 @@ Emitted when `tokenOwner` disables `operator` to transfer or burn `tokenId` on i #### Parameters -| Name | Type | Description | -| -------------------------- | :-------: | --------------------------------------------------------------- | -| `operator` **`indexed`** | `address` | The address revoked from the operator array ({getOperatorsOf}). | -| `tokenOwner` **`indexed`** | `address` | The owner of the `tokenId`. | -| `tokenId` **`indexed`** | `bytes32` | The tokenId `operator` is revoked from operating on. | -| `notified` | `bool` | Bool indicating whether the operator has been notified or not | -| `operatorNotificationData` | `bytes` | The data to notify the operator about via LSP1. | +| Name | Type | Description | +| -------------------------- | :-------: | ---------------------------------------------------------------------------------- | +| `operator` **`indexed`** | `address` | The address revoked from the operator array ([`getOperatorsOf`](#getoperatorsof)). | +| `tokenOwner` **`indexed`** | `address` | The owner of the `tokenId`. | +| `tokenId` **`indexed`** | `bytes32` | The tokenId `operator` is revoked from operating on. | +| `notified` | `bool` | Bool indicating whether the operator has been notified or not | +| `operatorNotificationData` | `bytes` | The data to notify the operator about via LSP1. |
diff --git a/docs/contracts/LSP8IdentifiableDigitalAsset/presets/LSP8Mintable.md b/docs/contracts/LSP8IdentifiableDigitalAsset/presets/LSP8Mintable.md index 8308d8036..0610de4a7 100644 --- a/docs/contracts/LSP8IdentifiableDigitalAsset/presets/LSP8Mintable.md +++ b/docs/contracts/LSP8IdentifiableDigitalAsset/presets/LSP8Mintable.md @@ -727,33 +727,6 @@ Transfers ownership of the contract to a new account (`newOwner`). Can only be c
-### version - -:::note References - -- Specification details: [**LSP-8-IdentifiableDigitalAsset**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-8-IdentifiableDigitalAsset.md#version) -- Solidity implementation: [`LSP8Mintable.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP8IdentifiableDigitalAsset/presets/LSP8Mintable.sol) -- Function signature: `version()` -- Function selector: `0x54fd4d50` - -::: - -```solidity -function version() external view returns (string); -``` - -_Contract version._ - -Get the version of the contract. - -#### Returns - -| Name | Type | Description | -| ---- | :------: | -------------------------------- | -| `0` | `string` | The version of the the contract. | - -
- ## Internal Methods Any method labeled as `internal` serves as utility function within the contract. They can be used when writing solidity contracts that inherit from this contract. These methods can be extended or modified by overriding their internal behavior to suit specific needs. @@ -1260,13 +1233,13 @@ Emitted when `tokenOwner` disables `operator` to transfer or burn `tokenId` on i #### Parameters -| Name | Type | Description | -| -------------------------- | :-------: | --------------------------------------------------------------- | -| `operator` **`indexed`** | `address` | The address revoked from the operator array ({getOperatorsOf}). | -| `tokenOwner` **`indexed`** | `address` | The owner of the `tokenId`. | -| `tokenId` **`indexed`** | `bytes32` | The tokenId `operator` is revoked from operating on. | -| `notified` | `bool` | Bool indicating whether the operator has been notified or not | -| `operatorNotificationData` | `bytes` | The data to notify the operator about via LSP1. | +| Name | Type | Description | +| -------------------------- | :-------: | ---------------------------------------------------------------------------------- | +| `operator` **`indexed`** | `address` | The address revoked from the operator array ([`getOperatorsOf`](#getoperatorsof)). | +| `tokenOwner` **`indexed`** | `address` | The owner of the `tokenId`. | +| `tokenId` **`indexed`** | `bytes32` | The tokenId `operator` is revoked from operating on. | +| `notified` | `bool` | Bool indicating whether the operator has been notified or not | +| `operatorNotificationData` | `bytes` | The data to notify the operator about via LSP1. |
diff --git a/docs/contracts/LSP9Vault/LSP9Vault.md b/docs/contracts/LSP9Vault/LSP9Vault.md index 087d8f578..8ffdb9f81 100644 --- a/docs/contracts/LSP9Vault/LSP9Vault.md +++ b/docs/contracts/LSP9Vault/LSP9Vault.md @@ -183,6 +183,31 @@ function RENOUNCE_OWNERSHIP_CONFIRMATION_PERIOD()
+### VERSION + +:::note References + +- Specification details: [**LSP-9-Vault**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-9-Vault.md#version) +- Solidity implementation: [`LSP9Vault.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP9Vault/LSP9Vault.sol) +- Function signature: `VERSION()` +- Function selector: `0xffa1ad74` + +::: + +```solidity +function VERSION() external view returns (string); +``` + +_Contract version._ + +#### Returns + +| Name | Type | Description | +| ---- | :------: | ----------- | +| `0` | `string` | - | + +
+ ### acceptOwnership :::note References @@ -778,33 +803,6 @@ Achieves the goal of [LSP-1-UniversalReceiver] by allowing the account to be not
-### version - -:::note References - -- Specification details: [**LSP-9-Vault**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-9-Vault.md#version) -- Solidity implementation: [`LSP9Vault.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP9Vault/LSP9Vault.sol) -- Function signature: `version()` -- Function selector: `0x54fd4d50` - -::: - -```solidity -function version() external view returns (string); -``` - -_Contract version._ - -Get the version of the contract. - -#### Returns - -| Name | Type | Description | -| ---- | :------: | -------------------------------- | -| `0` | `string` | The version of the the contract. | - -
- ## Internal Methods Any method labeled as `internal` serves as utility function within the contract. They can be used when writing solidity contracts that inherit from this contract. These methods can be extended or modified by overriding their internal behavior to suit specific needs. @@ -1180,7 +1178,7 @@ If there is an extension for the function selector being called, it calls the ex function _validateAndIdentifyCaller() internal view returns (bool isURD); ``` -Modifier restricting the call to the owner of the contract and the UniversalReceiverDelegate +Internal method restricting the call to the owner of the contract and the UniversalReceiverDelegate
@@ -1393,11 +1391,11 @@ Emitted when the [`universalReceiver`](#universalreceiver) function was called w | Name | Type | Description | | ---------------------- | :-------: | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `from` **`indexed`** | `address` | The address of the EOA or smart contract that called the {universalReceiver(...)} function. | -| `value` **`indexed`** | `uint256` | The amount sent to the {universalReceiver(...)} function. | +| `from` **`indexed`** | `address` | The address of the EOA or smart contract that called the [`universalReceiver(...)`](#universalreceiver) function. | +| `value` **`indexed`** | `uint256` | The amount sent to the [`universalReceiver(...)`](#universalreceiver) function. | | `typeId` **`indexed`** | `bytes32` | A `bytes32` unique identifier (= _"hook"_)that describe the type of notification, information or transaction received by the contract. Can be related to a specific standard or a hook. | -| `receivedData` | `bytes` | Any arbitrary data that was sent to the {universalReceiver(...)} function. | -| `returnedValue` | `bytes` | The value returned by the {universalReceiver(...)} function. | +| `receivedData` | `bytes` | Any arbitrary data that was sent to the [`universalReceiver(...)`](#universalreceiver) function. | +| `returnedValue` | `bytes` | The value returned by the [`universalReceiver(...)`](#universalreceiver) function. |
diff --git a/docs/contracts/UniversalProfile.md b/docs/contracts/UniversalProfile.md index 4ec73c3b3..d2c642fce 100644 --- a/docs/contracts/UniversalProfile.md +++ b/docs/contracts/UniversalProfile.md @@ -38,7 +38,11 @@ constructor(address initialOwner); _Deploying a UniversalProfile contract with owner set to address `initialOwner`._ -Set `initialOwner` as the contract owner and the `SupportedStandards:LSP3UniversalProfile` data key in the ERC725Y data key/value store. The `constructor` also allows funding the contract on deployment. +Set `initialOwner` as the contract owner and the `SupportedStandards:LSP3Profile` data key in the ERC725Y data key/value store. + +- The `constructor` is payable and allows funding the contract on deployment. + +- The `initialOwner` will then be allowed to call protected functions marked with the `onlyOwner` modifier.
@@ -140,6 +144,31 @@ function RENOUNCE_OWNERSHIP_CONFIRMATION_PERIOD()
+### VERSION + +:::note References + +- Specification details: [**UniversalProfile**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-3-UniversalProfile-Metadata.md#version) +- Solidity implementation: [`UniversalProfile.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/UniversalProfile.sol) +- Function signature: `VERSION()` +- Function selector: `0xffa1ad74` + +::: + +```solidity +function VERSION() external view returns (string); +``` + +_Contract version._ + +#### Returns + +| Name | Type | Description | +| ---- | :------: | ----------- | +| `0` | `string` | - | + +
+ ### acceptOwnership :::note References @@ -430,6 +459,12 @@ Get in the ERC725Y storage the bytes data stored at multiple data keys `dataKeys ::: +:::caution Warning + +This function does not enforce by default the inclusion of the address of this contract in the signature digest. It is recommended that protocols or applications using this contract include the targeted address (= this contract) in the data to sign. To ensure that a signature is valid for a specific LSP0ERC725Account and prevent signatures from the same EOA to be replayed across different LSP0ERC725Accounts. + +::: + ```solidity function isValidSignature( bytes32 dataHash, @@ -755,7 +790,7 @@ Achieves the goal of [LSP-1-UniversalReceiver] by allowing the account to be not - If there is an address stored under the data key, check if this address supports the LSP1 interfaceId. -- If yes, call this address with the typeId and data (params), along with additional calldata consisting of 20 bytes of `msg.sender` and 32 bytes of `msg.value`. If not, continue the execution of the function. +- If yes, call this address with the typeId and data (params), along with additional calldata consisting of 20 bytes of `msg.sender` and 32 bytes of `msg.value`. If not, continue the execution of the function. This function delegates internally the handling of native tokens to the [`universalReceiver`](#universalreceiver) function itself passing `_TYPEID_LSP0_VALUE_RECEIVED` as typeId and the calldata as received data.
@@ -781,33 +816,6 @@ Achieves the goal of [LSP-1-UniversalReceiver] by allowing the account to be not
-### version - -:::note References - -- Specification details: [**UniversalProfile**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-3-UniversalProfile-Metadata.md#version) -- Solidity implementation: [`UniversalProfile.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/UniversalProfile.sol) -- Function signature: `version()` -- Function selector: `0x54fd4d50` - -::: - -```solidity -function version() external view returns (string); -``` - -_Contract version._ - -Get the version of the contract. - -#### Returns - -| Name | Type | Description | -| ---- | :------: | -------------------------------- | -| `0` | `string` | The version of the the contract. | - -
- ## Internal Methods Any method labeled as `internal` serves as utility function within the contract. They can be used when writing solidity contracts that inherit from this contract. These methods can be extended or modified by overriding their internal behavior to suit specific needs. @@ -1123,26 +1131,21 @@ function _getExtensionAndForwardValue( ) internal view returns (address, bool); ``` -Returns the extension address stored under the following data key: +Returns the extension address and the boolean indicating whether to forward the value received to the extension, stored under the following data key: - [`_LSP17_EXTENSION_PREFIX`](#_lsp17_extension_prefix) + `` (Check [LSP2-ERC725YJSONSchema] for encoding the data key). - If no extension is stored, returns the address(0). +- If the stored value is 20 bytes, return false for the boolean +
### \_fallbackLSP17Extendable :::tip Hint -This function does not forward to the extension contract the `msg.value` received by the contract that inherits `LSP17Extendable`. -If you would like to forward the `msg.value` to the extension contract, you can override the code of this internal function as follow: - -```solidity -(bool success, bytes memory result) = extension.call{value: msg.value}( - abi.encodePacked(callData, msg.sender, msg.value) -); -``` +If you would like to forward the `msg.value` to the extension contract, you should store an additional `0x01` byte after the address of the extension under the corresponding LSP17 data key. ::: @@ -1407,11 +1410,11 @@ Emitted when the [`universalReceiver`](#universalreceiver) function was called w | Name | Type | Description | | ---------------------- | :-------: | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `from` **`indexed`** | `address` | The address of the EOA or smart contract that called the {universalReceiver(...)} function. | -| `value` **`indexed`** | `uint256` | The amount sent to the {universalReceiver(...)} function. | +| `from` **`indexed`** | `address` | The address of the EOA or smart contract that called the [`universalReceiver(...)`](#universalreceiver) function. | +| `value` **`indexed`** | `uint256` | The amount sent to the [`universalReceiver(...)`](#universalreceiver) function. | | `typeId` **`indexed`** | `bytes32` | A `bytes32` unique identifier (= _"hook"_)that describe the type of notification, information or transaction received by the contract. Can be related to a specific standard or a hook. | -| `receivedData` | `bytes` | Any arbitrary data that was sent to the {universalReceiver(...)} function. | -| `returnedValue` | `bytes` | The value returned by the {universalReceiver(...)} function. | +| `receivedData` | `bytes` | Any arbitrary data that was sent to the [`universalReceiver(...)`](#universalreceiver) function. | +| `returnedValue` | `bytes` | The value returned by the [`universalReceiver(...)`](#universalreceiver) function. |
diff --git a/docs/libraries/LSP1UniversalReceiver/LSP1Utils.md b/docs/libraries/LSP1UniversalReceiver/LSP1Utils.md index c116ad6c8..a897fa7d0 100644 --- a/docs/libraries/LSP1UniversalReceiver/LSP1Utils.md +++ b/docs/libraries/LSP1UniversalReceiver/LSP1Utils.md @@ -24,10 +24,10 @@ Any method labeled as `internal` serves as utility function within the contract. Internal functions cannot be called externally, whether from other smart contracts, dApp interfaces, or backend services. Their restricted accessibility ensures that they remain exclusively available within the context of the current contract, promoting controlled and encapsulated usage of these internal utilities. -### tryNotifyUniversalReceiver +### notifyUniversalReceiver ```solidity -function tryNotifyUniversalReceiver( +function notifyUniversalReceiver( address lsp1Implementation, bytes32 typeId, bytes data diff --git a/dodoc/config.ts b/dodoc/config.ts index 02cb032b2..836ac0590 100644 --- a/dodoc/config.ts +++ b/dodoc/config.ts @@ -362,7 +362,7 @@ const formatParamDescription = (textToFormat: string) => { formatedText = formatedText.replace('>', '<'); } - return formatedText; + return createLocalLinks(formatedText); }; const formatParamType = (textToFormat: string) => { diff --git a/foundry.toml b/foundry.toml index 7d758faf7..a10f28357 100644 --- a/foundry.toml +++ b/foundry.toml @@ -8,3 +8,4 @@ test = 'tests/foundry' solc_version = "0.8.17" [fuzz] runs = 10_000 +max_test_rejects = 200_000 diff --git a/hardhat.config.ts b/hardhat.config.ts index e9934cee8..fa0b73099 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -79,7 +79,7 @@ const config: HardhatUserConfig = { network: 'luksoTestnet', chainId: 4201, urls: { - apiURL: 'https://explorer.execution.testnet.lukso.network/api', + apiURL: 'https://api.explorer.execution.testnet.lukso.network/api', browserURL: 'https://explorer.execution.testnet.lukso.network/', }, }, diff --git a/release-please-config.json b/release-please-config.json new file mode 100644 index 000000000..399b7e0f9 --- /dev/null +++ b/release-please-config.json @@ -0,0 +1,14 @@ +{ + "packages": { + ".": { + "changelog-path": "CHANGELOG.md", + "release-type": "node", + "bump-minor-pre-major": true, + "bump-patch-for-minor-pre-major": false, + "draft": false, + "prerelease": false, + "extra-files": ["contracts/Version.sol"] + } + }, + "$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json" +} diff --git a/tests/LSP17ContractExtension/LSP17Extendable.behaviour.ts b/tests/LSP17ContractExtension/LSP17Extendable.behaviour.ts index 0f9710b30..184d9dfce 100644 --- a/tests/LSP17ContractExtension/LSP17Extendable.behaviour.ts +++ b/tests/LSP17ContractExtension/LSP17Extendable.behaviour.ts @@ -911,6 +911,35 @@ export const shouldBehaveLikeLSP17 = (buildContext: () => Promise { + describe('when setting less than 20 bytes as data value for the LSP17Extension data key', () => { + const randomSelector = ethers.utils.hexlify(ethers.utils.randomBytes(4)); + const randomBytes10Value = ethers.utils.hexlify(ethers.utils.randomBytes(10)); + + const lsp17DataKey = + ERC725YDataKeys.LSP17.LSP17ExtensionPrefix + + randomSelector.substring(2) + + '00'.repeat(16); + + it('should pass when setting the bytes', async () => { + await expect(context.contract.setData(lsp17DataKey, randomBytes10Value)) + .to.emit(context.contract, 'DataChanged') + .withArgs(lsp17DataKey, randomBytes10Value); + }); + + it('should revert with no ExtensionFoundForSelector when calling the function selector mapped to the 10 random bytes', async () => { + await expect( + context.accounts[0].sendTransaction({ + to: context.contract.address, + data: randomSelector, + }), + ) + .to.be.revertedWithCustomError(context.contract, 'NoExtensionFoundForFunctionSelector') + .withArgs(randomSelector); + }); + }); + }); + describe('use cases', async () => { describe('when interacting with a contract that require the recipient to implement onERC721Received function to mint', () => { let token: RequireCallbackToken; diff --git a/tests/LSP1UniversalReceiver/LSP1UniversalReceiverDelegateUP.behaviour.ts b/tests/LSP1UniversalReceiver/LSP1UniversalReceiverDelegateUP.behaviour.ts index f5b241ff1..036d0e187 100644 --- a/tests/LSP1UniversalReceiver/LSP1UniversalReceiverDelegateUP.behaviour.ts +++ b/tests/LSP1UniversalReceiver/LSP1UniversalReceiverDelegateUP.behaviour.ts @@ -1573,7 +1573,10 @@ export const shouldBehaveLikeLSP1Delegate = ( vault.address, 0, LSP1_TYPE_IDS.LSP9OwnershipTransferred_SenderNotification, - '0x', + abiCoder.encode( + ['address', 'address'], + [context.universalProfile1.address, context.accounts.owner1.address], + ), lsp1ReturnedData, ); }); @@ -1649,7 +1652,10 @@ export const shouldBehaveLikeLSP1Delegate = ( vault.address, 0, LSP1_TYPE_IDS.LSP9OwnershipTransferred_SenderNotification, - '0x', + abiCoder.encode( + ['address', 'address'], + [context.universalProfile1.address, context.accounts.owner1.address], + ), lsp1ReturnedData, ); }); @@ -1732,7 +1738,10 @@ export const shouldBehaveLikeLSP1Delegate = ( vault.address, 0, LSP1_TYPE_IDS.LSP9OwnershipTransferred_SenderNotification, - '0x', + abiCoder.encode( + ['address', 'address'], + [context.universalProfile1.address, context.accounts.owner1.address], + ), lsp1ReturnedData, ); }); @@ -2882,7 +2891,10 @@ export const shouldBehaveLikeLSP1Delegate = ( vault.address, 0, LSP1_TYPE_IDS.LSP9OwnershipTransferred_SenderNotification, - '0x', + abiCoder.encode( + ['address', 'address'], + [testContext.universalProfile.address, newVaultOwner.address], + ), expectedReturnedValues, ); @@ -3004,7 +3016,10 @@ export const shouldBehaveLikeLSP1Delegate = ( vault.address, 0, LSP1_TYPE_IDS.LSP9OwnershipTransferred_RecipientNotification, - '0x', + abiCoder.encode( + ['address', 'address'], + [vaultOwner.address, context.universalProfile1.address], + ), expectedReturnedValues, ); diff --git a/tests/LSP20CallVerification/LSP20CallVerification.behaviour.ts b/tests/LSP20CallVerification/LSP20CallVerification.behaviour.ts index 27223c2bc..712194e87 100644 --- a/tests/LSP20CallVerification/LSP20CallVerification.behaviour.ts +++ b/tests/LSP20CallVerification/LSP20CallVerification.behaviour.ts @@ -20,10 +20,13 @@ import { LSP0ERC725Account, ILSP20CallVerifier, ILSP20CallVerifier__factory, + OwnerWithURD, + OwnerWithURD__factory, } from '../../types'; // constants -import { LSP20_SUCCESS_VALUES, OPERATION_TYPES } from '../../constants'; +import { LSP1_TYPE_IDS, LSP20_SUCCESS_VALUES, OPERATION_TYPES } from '../../constants'; +import { abiCoder } from './..//utils/helpers'; export type LSP20TestContext = { accounts: SignerWithAddress[]; @@ -200,6 +203,49 @@ export const shouldBehaveLikeLSP20 = (buildContext: () => Promise { + let newContractOwner: OwnerWithURD; + + before('Use custom owner that implements LSP1', async () => { + newContractOwner = await new OwnerWithURD__factory(context.accounts[0]).deploy( + context.universalProfile.address, + ); + + await context.universalProfile + .connect(context.deployParams.owner) + .transferOwnership(newContractOwner.address); + + await newContractOwner.acceptOwnership(); + }); + + after('`renounceOwnership()` was used, build new context', async () => { + context = await buildContext(); + }); + + it('should renounce ownership of the contract and call the URD of the previous owner', async () => { + await context.universalProfile.connect(context.accounts[0]).renounceOwnership(); + + await network.provider.send('hardhat_mine', [ethers.utils.hexValue(199)]); + + const tx = await context.universalProfile + .connect(context.accounts[0]) + .renounceOwnership(); + + await expect(tx) + .to.emit(newContractOwner, 'UniversalReceiver') + .withArgs( + context.universalProfile.address, + 0, + LSP1_TYPE_IDS.LSP0OwnershipTransferred_SenderNotification, + abiCoder.encode( + ['address', 'address'], + [newContractOwner.address, ethers.constants.AddressZero], + ), + '0x', + ); + }); + }); }); }); diff --git a/tests/LSP20CallVerification/LSP6/Admin/PermissionChangeAddExtensions.test.ts b/tests/LSP20CallVerification/LSP6/Admin/PermissionChangeAddExtensions.test.ts index 7b96c5875..40f050a36 100644 --- a/tests/LSP20CallVerification/LSP6/Admin/PermissionChangeAddExtensions.test.ts +++ b/tests/LSP20CallVerification/LSP6/Admin/PermissionChangeAddExtensions.test.ts @@ -962,5 +962,57 @@ export const shouldBehaveLikePermissionChangeOrAddExtensions = ( }); }); }); + + describe('when setting random bytes under the LSP17Extension data key ', () => { + it('should be allowed to set a 20 bytes long address', async () => { + const key = + ERC725YDataKeys.LSP17.LSP17ExtensionPrefix + + ethers.utils.hexlify(ethers.utils.randomBytes(20)).substring(2); + const value = ethers.Wallet.createRandom().address.toLowerCase(); + + await context.universalProfile.connect(context.mainController).setData(key, value); + + const result = await context.universalProfile.getData(key); + expect(result).to.equal(value); + }); + + it('should be allowed to set a 21 bytes long address', async () => { + const key = + ERC725YDataKeys.LSP17.LSP17ExtensionPrefix + + ethers.utils.hexlify(ethers.utils.randomBytes(20)).substring(2); + const value = ethers.Wallet.createRandom().address.toLowerCase() + '00'; + + await context.universalProfile.connect(context.mainController).setData(key, value); + + const result = await context.universalProfile.getData(key); + expect(result).to.equal(value); + }); + + it('should revert when setting a random 10 bytes value', async () => { + const key = + ERC725YDataKeys.LSP17.LSP17ExtensionPrefix + + ethers.utils.hexlify(ethers.utils.randomBytes(20)).substring(2); + const randomValue = '0xcafecafecafecafecafe'; + + await expect( + context.universalProfile.connect(context.mainController).setData(key, randomValue), + ) + .to.be.revertedWithCustomError(context.keyManager, 'InvalidDataValuesForDataKeys') + .withArgs(key, randomValue); + }); + + it('should revert when setting a random 30 bytes value', async () => { + const key = + ERC725YDataKeys.LSP17.LSP17ExtensionPrefix + + ethers.utils.hexlify(ethers.utils.randomBytes(20)).substring(2); + const randomValue = '0xbeefbeefbeefbeefbeefbeefbeefbeefbeefbeefbeefbeefbeefbeefbeef'; + + await expect( + context.universalProfile.connect(context.mainController).setData(key, randomValue), + ) + .to.be.revertedWithCustomError(context.keyManager, 'InvalidDataValuesForDataKeys') + .withArgs(key, randomValue); + }); + }); }); }; diff --git a/tests/LSP6KeyManager/Admin/PermissionChangeAddExtensions.test.ts b/tests/LSP6KeyManager/Admin/PermissionChangeAddExtensions.test.ts index 5cb9a7ab2..561c9b5db 100644 --- a/tests/LSP6KeyManager/Admin/PermissionChangeAddExtensions.test.ts +++ b/tests/LSP6KeyManager/Admin/PermissionChangeAddExtensions.test.ts @@ -274,6 +274,58 @@ export const shouldBehaveLikePermissionChangeOrAddExtensions = ( const result = await context.universalProfile.getDataBatch(payloadParam.dataKeys); expect(result).to.deep.equal(payloadParam.dataValues); }); + + describe('when setting random bytes under the LSP17Extension data key ', () => { + it('should be allowed to set a 20 bytes long address', async () => { + const key = + ERC725YDataKeys.LSP17.LSP17ExtensionPrefix + + ethers.utils.hexlify(ethers.utils.randomBytes(20)).substring(2); + const value = ethers.Wallet.createRandom().address.toLowerCase(); + + await context.universalProfile.connect(context.mainController).setData(key, value); + + const result = await context.universalProfile.getData(key); + expect(result).to.equal(value); + }); + + it('should be allowed to set a 21 bytes long address', async () => { + const key = + ERC725YDataKeys.LSP17.LSP17ExtensionPrefix + + ethers.utils.hexlify(ethers.utils.randomBytes(20)).substring(2); + const value = ethers.Wallet.createRandom().address.toLowerCase() + '00'; + + await context.universalProfile.connect(context.mainController).setData(key, value); + + const result = await context.universalProfile.getData(key); + expect(result).to.equal(value); + }); + + it('should revert when setting a random 10 bytes value', async () => { + const key = + ERC725YDataKeys.LSP17.LSP17ExtensionPrefix + + ethers.utils.hexlify(ethers.utils.randomBytes(20)).substring(2); + const randomValue = '0xcafecafecafecafecafe'; + + await expect( + context.universalProfile.connect(context.mainController).setData(key, randomValue), + ) + .to.be.revertedWithCustomError(context.keyManager, 'InvalidDataValuesForDataKeys') + .withArgs(key, randomValue); + }); + + it('should revert when setting a random 30 bytes value', async () => { + const key = + ERC725YDataKeys.LSP17.LSP17ExtensionPrefix + + ethers.utils.hexlify(ethers.utils.randomBytes(20)).substring(2); + const randomValue = '0xbeefbeefbeefbeefbeefbeefbeefbeefbeefbeefbeefbeefbeefbeefbeef'; + + await expect( + context.universalProfile.connect(context.mainController).setData(key, randomValue), + ) + .to.be.revertedWithCustomError(context.keyManager, 'InvalidDataValuesForDataKeys') + .withArgs(key, randomValue); + }); + }); }); describe('when caller is an address with ADD/CHANGE Extensions permission', () => { @@ -327,10 +379,62 @@ export const shouldBehaveLikePermissionChangeOrAddExtensions = ( const result = await context.universalProfile.getData(payloadParam.dataKey); expect(result).to.equal(payloadParam.dataValue); }); + + describe('when setting random bytes under the LSP17Extension data key ', () => { + it('should be allowed to set a 20 bytes long address', async () => { + const key = + ERC725YDataKeys.LSP17.LSP17ExtensionPrefix + + ethers.utils.hexlify(ethers.utils.randomBytes(20)).substring(2); + const value = ethers.Wallet.createRandom().address.toLowerCase(); + + await context.universalProfile.connect(canAddAndChangeExtensions).setData(key, value); + + const result = await context.universalProfile.getData(key); + expect(result).to.equal(value); + }); + + it('should be allowed to set a 21 bytes long address', async () => { + const key = + ERC725YDataKeys.LSP17.LSP17ExtensionPrefix + + ethers.utils.hexlify(ethers.utils.randomBytes(20)).substring(2); + const value = ethers.Wallet.createRandom().address.toLowerCase() + '00'; + + await context.universalProfile.connect(canAddAndChangeExtensions).setData(key, value); + + const result = await context.universalProfile.getData(key); + expect(result).to.equal(value); + }); + + it('should revert when setting a random 10 bytes value', async () => { + const key = + ERC725YDataKeys.LSP17.LSP17ExtensionPrefix + + ethers.utils.hexlify(ethers.utils.randomBytes(20)).substring(2); + const randomValue = '0xcafecafecafecafecafe'; + + await expect( + context.universalProfile.connect(canAddAndChangeExtensions).setData(key, randomValue), + ) + .to.be.revertedWithCustomError(context.keyManager, 'InvalidDataValuesForDataKeys') + .withArgs(key, randomValue); + }); + + it('should revert when setting a random 30 bytes value', async () => { + const key = + ERC725YDataKeys.LSP17.LSP17ExtensionPrefix + + ethers.utils.hexlify(ethers.utils.randomBytes(20)).substring(2); + const randomValue = '0xbeefbeefbeefbeefbeefbeefbeefbeefbeefbeefbeefbeefbeefbeefbeef'; + + await expect( + context.universalProfile.connect(canAddAndChangeExtensions).setData(key, randomValue), + ) + .to.be.revertedWithCustomError(context.keyManager, 'InvalidDataValuesForDataKeys') + .withArgs(key, randomValue); + }); + }); }); describe('when caller is an address with ADDExtensions permission', () => { - it('should be allowed to ADD a ExtensionHandler key', async () => { + it('should be allowed to ADD an ExtensionHandler key', async () => { const payloadParam = { dataKey: extensionHandlerKey5, dataValue: extensionA, @@ -347,6 +451,56 @@ export const shouldBehaveLikePermissionChangeOrAddExtensions = ( expect(result).to.equal(payloadParam.dataValue); }); + it('should be allowed to set a 20 bytes long address for a new handler', async () => { + const key = + ERC725YDataKeys.LSP17.LSP17ExtensionPrefix + + ethers.utils.hexlify(ethers.utils.randomBytes(20)).substring(2); + const value = ethers.Wallet.createRandom().address.toLowerCase(); + + await context.universalProfile.connect(canOnlyAddExtensions).setData(key, value); + + const result = await context.universalProfile.getData(key); + expect(result).to.equal(value); + }); + + it('should be allowed to set a 21 bytes long address for a new handler', async () => { + const key = + ERC725YDataKeys.LSP17.LSP17ExtensionPrefix + + ethers.utils.hexlify(ethers.utils.randomBytes(20)).substring(2); + const value = ethers.Wallet.createRandom().address.toLowerCase() + '00'; + + await context.universalProfile.connect(canOnlyAddExtensions).setData(key, value); + + const result = await context.universalProfile.getData(key); + expect(result).to.equal(value); + }); + + it('should revert with `InvalidDataValuesForDataKeys` when setting a random 10 bytes value for a new handler', async () => { + const key = + ERC725YDataKeys.LSP17.LSP17ExtensionPrefix + + ethers.utils.hexlify(ethers.utils.randomBytes(20)).substring(2); + const randomValue = '0xcafecafecafecafecafe'; + + await expect( + context.universalProfile.connect(canOnlyAddExtensions).setData(key, randomValue), + ) + .to.be.revertedWithCustomError(context.keyManager, 'InvalidDataValuesForDataKeys') + .withArgs(key, randomValue); + }); + + it('should revert with `InvalidDataValuesForDataKeys` when setting a random 30 bytes value for a new handler', async () => { + const key = + ERC725YDataKeys.LSP17.LSP17ExtensionPrefix + + ethers.utils.hexlify(ethers.utils.randomBytes(20)).substring(2); + const randomValue = '0xbeefbeefbeefbeefbeefbeefbeefbeefbeefbeefbeefbeefbeefbeefbeef'; + + await expect( + context.universalProfile.connect(canOnlyAddExtensions).setData(key, randomValue), + ) + .to.be.revertedWithCustomError(context.keyManager, 'InvalidDataValuesForDataKeys') + .withArgs(key, randomValue); + }); + it("should NOT be allowed to edit the ExtensionHandler key set even if it's setting existing data", async () => { const payloadParam = { dataKey: extensionHandlerKey5, @@ -394,6 +548,28 @@ export const shouldBehaveLikePermissionChangeOrAddExtensions = ( .to.be.revertedWithCustomError(context.keyManager, 'NotAuthorised') .withArgs(canOnlyAddExtensions.address, 'CHANGEEXTENSIONS'); }); + + it('should revert with `InvalidDataValuesForDataKeys` error when setting a random 10 bytes value for an existing handler', async () => { + const key = extensionHandlerKey5; + const randomValue = '0xcafecafecafecafecafe'; + + await expect( + context.universalProfile.connect(canOnlyAddExtensions).setData(key, randomValue), + ) + .to.be.revertedWithCustomError(context.keyManager, 'InvalidDataValuesForDataKeys') + .withArgs(key, randomValue); + }); + + it('should revert with `InvalidDataValuesForDataKeys` error when setting a random 30 bytes value for an existing handler', async () => { + const key = extensionHandlerKey5; + const randomValue = '0xbeefbeefbeefbeefbeefbeefbeefbeefbeefbeefbeefbeefbeefbeefbeef'; + + await expect( + context.universalProfile.connect(canOnlyAddExtensions).setData(key, randomValue), + ) + .to.be.revertedWithCustomError(context.keyManager, 'InvalidDataValuesForDataKeys') + .withArgs(key, randomValue); + }); }); describe('when caller is an address with CHANGEExtensions permission', () => { @@ -413,6 +589,64 @@ export const shouldBehaveLikePermissionChangeOrAddExtensions = ( .withArgs(canOnlyChangeExtensions.address, 'ADDEXTENSIONS'); }); + it('should NOT be allowed to set a 20 bytes long address for a new handler', async () => { + const key = + ERC725YDataKeys.LSP17.LSP17ExtensionPrefix + + ethers.utils.hexlify(ethers.utils.randomBytes(20)).substring(2); + const value = ethers.Wallet.createRandom().address.toLowerCase(); + + const payload = context.universalProfile.interface.encodeFunctionData('setData', [ + key, + value, + ]); + + await expect(context.keyManager.connect(canOnlyChangeExtensions).execute(payload)) + .to.be.revertedWithCustomError(context.keyManager, 'NotAuthorised') + .withArgs(canOnlyChangeExtensions.address, 'ADDEXTENSIONS'); + }); + + it('should NOT be allowed to set a 21 bytes long address for a new handler', async () => { + const key = + ERC725YDataKeys.LSP17.LSP17ExtensionPrefix + + ethers.utils.hexlify(ethers.utils.randomBytes(20)).substring(2); + const value = ethers.Wallet.createRandom().address.toLowerCase() + '00'; + + const payload = context.universalProfile.interface.encodeFunctionData('setData', [ + key, + value, + ]); + + await expect(context.keyManager.connect(canOnlyChangeExtensions).execute(payload)) + .to.be.revertedWithCustomError(context.keyManager, 'NotAuthorised') + .withArgs(canOnlyChangeExtensions.address, 'ADDEXTENSIONS'); + }); + + it('should revert with `InvalidValueForDataKey` error when setting a random 10 bytes value for a new handler', async () => { + const key = + ERC725YDataKeys.LSP17.LSP17ExtensionPrefix + + ethers.utils.hexlify(ethers.utils.randomBytes(20)).substring(2); + const randomValue = '0xcafecafecafecafecafe'; + + await expect( + context.universalProfile.connect(canOnlyChangeExtensions).setData(key, randomValue), + ) + .to.be.revertedWithCustomError(context.keyManager, 'InvalidDataValuesForDataKeys') + .withArgs(key, randomValue); + }); + + it('should revert with `InvalidValueForDataKey` error when setting a random 30 bytes value for a new handler', async () => { + const key = + ERC725YDataKeys.LSP17.LSP17ExtensionPrefix + + ethers.utils.hexlify(ethers.utils.randomBytes(20)).substring(2); + const randomValue = '0xbeefbeefbeefbeefbeefbeefbeefbeefbeefbeefbeefbeefbeefbeefbeef'; + + await expect( + context.universalProfile.connect(canOnlyChangeExtensions).setData(key, randomValue), + ) + .to.be.revertedWithCustomError(context.keyManager, 'InvalidDataValuesForDataKeys') + .withArgs(key, randomValue); + }); + it('should be allowed to edit the ExtensionHandler key set', async () => { const payloadParam = { dataKey: extensionHandlerKey5, @@ -466,6 +700,7 @@ export const shouldBehaveLikePermissionChangeOrAddExtensions = ( await context.keyManager.connect(context.mainController).execute(payload); }); + it('should NOT be allowed to ADD another ExtensionHandler key', async () => { const payloadParam = { dataKey: extensionHandlerKey1, @@ -1029,6 +1264,7 @@ export const shouldBehaveLikePermissionChangeOrAddExtensions = ( .withArgs(canOnlySuperSetData.address, 'ADDEXTENSIONS'); }); }); + describe('when Adding multiple ExtensionHandler keys with adding ERC725Y Data Key', () => { it("should revert because caller don't have ADDExtensions permission", async () => { const payloadParam = { @@ -1108,6 +1344,7 @@ export const shouldBehaveLikePermissionChangeOrAddExtensions = ( await context.keyManager.connect(context.mainController).execute(payload); }); + describe('When adding ExtensionHandler key and one of his allowedERC725Y Data Key', () => { it('should pass', async () => { const payloadParam = { diff --git a/tests/LSP6KeyManager/Interactions/AllowedStandards.test.ts b/tests/LSP6KeyManager/Interactions/AllowedStandards.test.ts index ebb34b810..5943849ea 100644 --- a/tests/LSP6KeyManager/Interactions/AllowedStandards.test.ts +++ b/tests/LSP6KeyManager/Interactions/AllowedStandards.test.ts @@ -137,11 +137,14 @@ export const shouldBehaveLikeAllowedStandards = (buildContext: () => Promise { @@ -181,11 +184,14 @@ export const shouldBehaveLikeAllowedStandards = (buildContext: () => Promise { - it('should pass and return data', async () => { + it('should pass and return a `string`', async () => { const expectedName = await targetContract.callStatic.getName(); const targetContractPayload = targetContract.interface.encodeFunctionData('getName'); @@ -111,16 +111,40 @@ export const shouldBehaveLikePermissionStaticCall = ( .connect(context.mainController) .callStatic.execute(executePayload); - const [decodedResult] = abiCoder.decode(['string'], result); + const [decodedBytes] = abiCoder.decode(['bytes'], result); + + const [decodedResult] = abiCoder.decode(['string'], decodedBytes); expect(decodedResult).to.equal(expectedName); }); + + it('should pass and return an array of number `uint256[]`', async () => { + const expectedNumbers = await targetContract.callStatic.getDynamicArrayOf2Numbers(); + + const targetContractPayload = targetContract.interface.encodeFunctionData( + 'getDynamicArrayOf2Numbers', + ); + + const executePayload = context.universalProfile.interface.encodeFunctionData('execute', [ + OPERATION_TYPES.STATICCALL, + targetContract.address, + 0, + targetContractPayload, + ]); + + const result = await context.keyManager + .connect(context.mainController) + .callStatic.execute(executePayload); + + const [decodedBytes] = abiCoder.decode(['bytes'], result); + + const [decodedResult] = abiCoder.decode(['uint256[]'], decodedBytes); + expect(decodedResult).to.deep.equal(expectedNumbers); + }); }); describe('when caller has permission STATICCALL + some allowed calls', () => { describe('when calling a `view` function on a target contract', () => { it('should pass and return data when `value` param is 0 ', async () => { - const expectedName = await targetContract.callStatic.getName(); - const targetContractPayload = targetContract.interface.encodeFunctionData('getName'); const executePayload = context.universalProfile.interface.encodeFunctionData('execute', [ @@ -134,7 +158,11 @@ export const shouldBehaveLikePermissionStaticCall = ( .connect(addressCanMakeStaticCall) .callStatic.execute(executePayload); - const [decodedResult] = abiCoder.decode(['string'], result); + const [decodedBytes] = abiCoder.decode(['bytes'], result); + + const expectedName = await targetContract.callStatic.getName(); + + const [decodedResult] = abiCoder.decode(['string'], decodedBytes); expect(decodedResult).to.equal(expectedName); }); @@ -182,8 +210,10 @@ export const shouldBehaveLikePermissionStaticCall = ( .connect(addressCanMakeStaticCall) .callStatic.execute(executePayload); - const [decodedResult] = abiCoder.decode(['bytes4'], result); - expect(decodedResult).to.equal(ERC1271_VALUES.SUCCESS_VALUE); + const [decodedBytes] = abiCoder.decode(['bytes'], result); + + const [decodedBytes4] = abiCoder.decode(['bytes4'], decodedBytes); + expect(decodedBytes4).to.equal(ERC1271_VALUES.SUCCESS_VALUE); }); it('should revert with error `ERC725X_MsgValueDisallowedInStaticCall` if `value` param is not 0', async () => { @@ -228,7 +258,8 @@ export const shouldBehaveLikePermissionStaticCall = ( ); // the important part is that the function is `view` and return the correct value - const expectedReturnValue = '0x150b7a02'; + const expectedReturnValue = + onERC721ReceivedContract.interface.getSighash('onERC721Received'); const executePayload = context.universalProfile.interface.encodeFunctionData('execute', [ OPERATION_TYPES.STATICCALL, @@ -241,8 +272,10 @@ export const shouldBehaveLikePermissionStaticCall = ( .connect(addressCanMakeStaticCall) .callStatic.execute(executePayload); - const [decodedResult] = abiCoder.decode(['bytes4'], result); - expect(decodedResult).to.equal(expectedReturnValue); + const [decodedBytes] = abiCoder.decode(['bytes'], result); + + const [decodedBytes4] = abiCoder.decode(['bytes4'], decodedBytes); + expect(decodedBytes4).to.equal(expectedReturnValue); }); }); @@ -274,52 +307,53 @@ export const shouldBehaveLikePermissionStaticCall = ( 'ERC725X_MsgValueDisallowedInStaticCall', ); }); - }), - describe('when calling a state changing function at the target contract', () => { - it('should revert (silently) if `value` parameter is 0', async () => { - const initialValue = await targetContract.callStatic.getName(); + }); - const targetContractPayload = targetContract.interface.encodeFunctionData('setName', [ - 'modified name', - ]); + describe('when calling a state changing function at the target contract', () => { + it('should revert (silently) if `value` parameter is 0', async () => { + const initialValue = await targetContract.callStatic.getName(); - const executePayload = context.universalProfile.interface.encodeFunctionData('execute', [ - OPERATION_TYPES.STATICCALL, - targetContract.address, - 0, - targetContractPayload, - ]); + const targetContractPayload = targetContract.interface.encodeFunctionData('setName', [ + 'modified name', + ]); - await expect(context.keyManager.connect(addressCanMakeStaticCall).execute(executePayload)) - .to.be.reverted; + const executePayload = context.universalProfile.interface.encodeFunctionData('execute', [ + OPERATION_TYPES.STATICCALL, + targetContract.address, + 0, + targetContractPayload, + ]); - // ensure state hasn't changed. - const newValue = await targetContract.callStatic.getName(); - expect(initialValue).to.equal(newValue); - }); + await expect(context.keyManager.connect(addressCanMakeStaticCall).execute(executePayload)) + .to.be.reverted; - it('should revert with error `ERC725X_MsgValueDisallowedInStaticCall` if `value` parameter is not 0', async () => { - const lyxAmount = ethers.utils.parseEther('3'); + // ensure state hasn't changed. + const newValue = await targetContract.callStatic.getName(); + expect(initialValue).to.equal(newValue); + }); - const targetContractPayload = targetContract.interface.encodeFunctionData('setName', [ - 'modified name', - ]); + it('should revert with error `ERC725X_MsgValueDisallowedInStaticCall` if `value` parameter is not 0', async () => { + const lyxAmount = ethers.utils.parseEther('3'); - const executePayload = context.universalProfile.interface.encodeFunctionData('execute', [ - OPERATION_TYPES.STATICCALL, - targetContract.address, - lyxAmount, - targetContractPayload, - ]); + const targetContractPayload = targetContract.interface.encodeFunctionData('setName', [ + 'modified name', + ]); - await expect( - context.keyManager.connect(addressCanMakeStaticCall).execute(executePayload), - ).to.be.revertedWithCustomError( - context.universalProfile, - 'ERC725X_MsgValueDisallowedInStaticCall', - ); - }); + const executePayload = context.universalProfile.interface.encodeFunctionData('execute', [ + OPERATION_TYPES.STATICCALL, + targetContract.address, + lyxAmount, + targetContractPayload, + ]); + + await expect( + context.keyManager.connect(addressCanMakeStaticCall).execute(executePayload), + ).to.be.revertedWithCustomError( + context.universalProfile, + 'ERC725X_MsgValueDisallowedInStaticCall', + ); }); + }); }); describe('when caller has permission STATICCALL + no allowed calls', () => { @@ -415,8 +449,6 @@ export const shouldBehaveLikePermissionStaticCall = ( it('should allow to call view function -> getName()', async () => { const targetContract = allowedTargetContracts[0]; - const name = await targetContract.getName(); - const payload = context.universalProfile.interface.encodeFunctionData('execute', [ OPERATION_TYPES.STATICCALL, targetContract.address, @@ -426,15 +458,17 @@ export const shouldBehaveLikePermissionStaticCall = ( const result = await context.keyManager.connect(caller).callStatic.execute(payload); - const [decodedResult] = abiCoder.decode(['string'], result); - expect(decodedResult).to.equal(name); + const [decodedResult] = abiCoder.decode(['bytes'], result); + + const expectedName = await targetContract.getName(); + + const [decodedString] = abiCoder.decode(['string'], decodedResult); + expect(decodedString).to.equal(expectedName); }); it('should allow to call view function -> getNumber()', async () => { const targetContract = allowedTargetContracts[0]; - const number = await targetContract.getNumber(); - const payload = context.universalProfile.interface.encodeFunctionData('execute', [ OPERATION_TYPES.STATICCALL, targetContract.address, @@ -444,8 +478,12 @@ export const shouldBehaveLikePermissionStaticCall = ( const result = await context.keyManager.connect(caller).callStatic.execute(payload); - const [decodedResult] = abiCoder.decode(['uint256'], result); - expect(decodedResult).to.equal(number); + const [decodedResult] = abiCoder.decode(['bytes'], result); + + const expectedNumber = await targetContract.getNumber(); + + const [decodedNumber] = abiCoder.decode(['uint256'], decodedResult); + expect(decodedNumber).to.equal(expectedNumber); }); it('should revert when calling state changing function -> setName(string)', async () => { @@ -483,8 +521,6 @@ export const shouldBehaveLikePermissionStaticCall = ( it('should allow to interact with 2nd allowed contract - getName()', async () => { const targetContract = allowedTargetContracts[1]; - const name = await targetContract.getName(); - const payload = context.universalProfile.interface.encodeFunctionData('execute', [ OPERATION_TYPES.STATICCALL, targetContract.address, @@ -494,15 +530,17 @@ export const shouldBehaveLikePermissionStaticCall = ( const result = await context.keyManager.connect(caller).callStatic.execute(payload); - const [decodedResult] = abiCoder.decode(['string'], result); - expect(decodedResult).to.equal(name); + const [decodedResult] = abiCoder.decode(['bytes'], result); + + const expectedName = await targetContract.getName(); + + const [decodedString] = abiCoder.decode(['string'], decodedResult); + expect(decodedString).to.equal(expectedName); }); it('should allow to interact with 2nd allowed contract - getNumber()', async () => { const targetContract = allowedTargetContracts[1]; - const number = await targetContract.getNumber(); - const payload = context.universalProfile.interface.encodeFunctionData('execute', [ OPERATION_TYPES.STATICCALL, targetContract.address, @@ -512,8 +550,12 @@ export const shouldBehaveLikePermissionStaticCall = ( const result = await context.keyManager.connect(caller).callStatic.execute(payload); - const [decodedResult] = abiCoder.decode(['uint256'], result); - expect(decodedResult).to.equal(number); + const [decodedResult] = abiCoder.decode(['bytes'], result); + + const expectedNumber = await targetContract.getNumber(); + + const [decodedNumber] = abiCoder.decode(['uint256'], decodedResult); + expect(decodedNumber).to.equal(expectedNumber); }); it('should revert when calling state changing function -> setName(string)', async () => { @@ -586,8 +628,6 @@ export const shouldBehaveLikePermissionStaticCall = ( it('should bypass allowed calls check + allow ton interact with a random contract', async () => { const randomContract = await new TargetContract__factory(context.accounts[0]).deploy(); - const name = await randomContract.getName(); - const payload = context.universalProfile.interface.encodeFunctionData('execute', [ OPERATION_TYPES.STATICCALL, randomContract.address, @@ -599,8 +639,12 @@ export const shouldBehaveLikePermissionStaticCall = ( .connect(addressWithSuperStaticCall) .callStatic.execute(payload); - const [decodedResult] = abiCoder.decode(['string'], result); - expect(decodedResult).to.equal(name); + const [decodedBytes] = abiCoder.decode(['bytes'], result); + + const name = await randomContract.getName(); + + const [decodedString] = abiCoder.decode(['string'], decodedBytes); + expect(decodedString).to.equal(name); }); it('should revert with error `ERC725X_MsgValueDisallowedInStaticCall` when `value` param is not 0', async () => { @@ -746,8 +790,12 @@ export const shouldBehaveLikePermissionStaticCall = ( .connect(addressCanMakeStaticCall) .callStatic.execute(executePayload); - const [decodedResult] = abiCoder.decode(['string'], result); - expect(decodedResult).to.equal(await targetContract.getName()); + const [decodedBytes] = abiCoder.decode(['bytes'], result); + + const expectedName = await targetContract.getName(); + + const [decodedString] = abiCoder.decode(['string'], decodedBytes); + expect(decodedString).to.equal(expectedName); }); it('should revert with error `ERC725X_MsgValueDisallowedInStaticCall` when `value` param is not 0', async () => { @@ -784,8 +832,12 @@ export const shouldBehaveLikePermissionStaticCall = ( .connect(addressCanMakeStaticCall) .callStatic.execute(executePayload); - const [decodedResult] = abiCoder.decode(['string'], result); - expect(decodedResult).to.equal(await targetContract.getName()); + const [decodedResult] = abiCoder.decode(['bytes'], result); + + const expectedName = await targetContract.getName(); + + const [decodedString] = abiCoder.decode(['string'], decodedResult); + expect(decodedString).to.equal(expectedName); }); it('should revert with error `ERC725X_MsgValueDisallowedInStaticCall` when `value` param is not 0', async () => { diff --git a/tests/LSP6KeyManager/Interactions/PermissionTransferValue.test.ts b/tests/LSP6KeyManager/Interactions/PermissionTransferValue.test.ts index c59d65538..4a0e2d0e6 100644 --- a/tests/LSP6KeyManager/Interactions/PermissionTransferValue.test.ts +++ b/tests/LSP6KeyManager/Interactions/PermissionTransferValue.test.ts @@ -52,10 +52,10 @@ export const shouldBehaveLikePermissionTransferValue = ( describe('when caller = EOA', () => { let canTransferValue: SignerWithAddress, canTransferValueAndCall: SignerWithAddress, - cannotTransferValue: SignerWithAddress; + canCallOnly: SignerWithAddress, + canNeitherCallNorTransferValue: SignerWithAddress; let recipient; - let recipientUP: UniversalProfile; // used to test when sending data as graffiti @@ -66,9 +66,10 @@ export const shouldBehaveLikePermissionTransferValue = ( canTransferValue = context.accounts[1]; canTransferValueAndCall = context.accounts[2]; - cannotTransferValue = context.accounts[3]; - recipient = context.accounts[4]; + canCallOnly = context.accounts[3]; + canNeitherCallNorTransferValue = context.accounts[4]; + recipient = context.accounts[5]; recipientUP = await new UniversalProfile__factory(context.accounts[0]).deploy( context.accounts[0].address, ); @@ -77,8 +78,8 @@ export const shouldBehaveLikePermissionTransferValue = ( const lsp17ExtensionDataKeyForGraffiti = ERC725YDataKeys.LSP17['LSP17ExtensionPrefix'] + - '00000000' + // selector for graffiti data, - '00000000000000000000000000000000'; // zero padded + '00'.repeat(4) + // bytes4 selector for graffiti data, + '00'.repeat(16); // zero padded on the right await recipientUP .connect(context.accounts[0]) @@ -90,23 +91,28 @@ export const shouldBehaveLikePermissionTransferValue = ( ethers.utils.getAddress(await recipientUP.getData(lsp17ExtensionDataKeyForGraffiti)), ).to.equal(graffitiExtension.address); + // prettier-ignore const permissionsKeys = [ - ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + - context.mainController.address.substring(2), - ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + - canTransferValue.address.substring(2), - ERC725YDataKeys.LSP6['AddressPermissions:AllowedCalls'] + - canTransferValue.address.substring(2), - ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + - canTransferValueAndCall.address.substring(2), - ERC725YDataKeys.LSP6['AddressPermissions:AllowedCalls'] + - canTransferValueAndCall.address.substring(2), - ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + - cannotTransferValue.address.substring(2), + // main controller + ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + context.mainController.address.substring(2), + // canTransferValue + ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + canTransferValue.address.substring(2), + ERC725YDataKeys.LSP6['AddressPermissions:AllowedCalls'] + canTransferValue.address.substring(2), + // canTransferValueAndCall + ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + canTransferValueAndCall.address.substring(2), + ERC725YDataKeys.LSP6['AddressPermissions:AllowedCalls'] + canTransferValueAndCall.address.substring(2), + // canCallOnly (not Transfer Value) + ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + canCallOnly.address.substring(2), + ERC725YDataKeys.LSP6['AddressPermissions:AllowedCalls'] + canCallOnly.address.substring(2), + // canNeitherCallNorTransferValue + ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + canNeitherCallNorTransferValue.address.substring(2), + ERC725YDataKeys.LSP6['AddressPermissions:AllowedCalls'] + canNeitherCallNorTransferValue.address.substring(2), ]; const permissionsValues = [ + // main controller ALL_PERMISSIONS, + // canTransferValue PERMISSIONS.TRANSFERVALUE, combineAllowedCalls( [CALLTYPE.VALUE, CALLTYPE.VALUE], @@ -114,6 +120,7 @@ export const shouldBehaveLikePermissionTransferValue = ( ['0xffffffff', '0xffffffff'], ['0xffffffff', '0xffffffff'], ), + // canTransferValueAndCall combinePermissions(PERMISSIONS.TRANSFERVALUE, PERMISSIONS.CALL), combineAllowedCalls( [ @@ -124,7 +131,26 @@ export const shouldBehaveLikePermissionTransferValue = ( ['0xffffffff', '0xffffffff'], ['0xffffffff', '0xffffffff'], ), + // canCallOnly (not Transfer Value) PERMISSIONS.CALL, + combineAllowedCalls( + [CALLTYPE.CALL, CALLTYPE.CALL], + [recipient.address, recipientUP.address], + ['0xffffffff', '0xffffffff'], + ['0xffffffff', '0xffffffff'], + ), + // canNeitherCallNorTransferValue + PERMISSIONS.SIGN, + combineAllowedCalls( + // we set call types but test that it should not reach the point of checking the call types + [ + combineCallTypes(CALLTYPE.VALUE, CALLTYPE.CALL), + combineCallTypes(CALLTYPE.VALUE, CALLTYPE.CALL), + ], + [recipient.address, recipientUP.address], + ['0xffffffff', '0xffffffff'], + ['0xffffffff', '0xffffffff'], + ), ]; await setupKeyManager(context, permissionsKeys, permissionsValues); @@ -135,7 +161,7 @@ export const shouldBehaveLikePermissionTransferValue = ( describe('when transferring value without bytes `_data`', () => { const data = '0x'; - it('should pass when caller has ALL PERMISSIONS', async () => { + it('should pass when controller has ALL PERMISSIONS', async () => { const amount = ethers.utils.parseEther('3'); const transferPayload = universalProfileInterface.encodeFunctionData('execute', [ @@ -157,7 +183,7 @@ export const shouldBehaveLikePermissionTransferValue = ( ); }); - it('should pass when caller has permission TRANSFERVALUE only', async () => { + it('should pass when controller has permission TRANSFERVALUE only', async () => { const amount = ethers.utils.parseEther('3'); const transferPayload = universalProfileInterface.encodeFunctionData('execute', [ @@ -175,7 +201,7 @@ export const shouldBehaveLikePermissionTransferValue = ( ); }); - it('should pass when caller has permission TRANSFERVALUE + CALL', async () => { + it('should pass when controller has permission TRANSFERVALUE + CALL', async () => { const amount = ethers.utils.parseEther('3'); const transferPayload = universalProfileInterface.encodeFunctionData('execute', [ @@ -193,7 +219,7 @@ export const shouldBehaveLikePermissionTransferValue = ( ); }); - it('should fail when caller does not have permission TRANSFERVALUE', async () => { + it('should fail when controller has permission CALL only but not TRANSFERVALUE', async () => { const initialBalanceUP = await provider.getBalance(context.universalProfile.address); const initialBalanceRecipient = await provider.getBalance(recipient.address); @@ -204,9 +230,34 @@ export const shouldBehaveLikePermissionTransferValue = ( data, ]); - await expect(context.keyManager.connect(cannotTransferValue).execute(transferPayload)) + await expect(context.keyManager.connect(canCallOnly).execute(transferPayload)) .to.be.revertedWithCustomError(context.keyManager, 'NotAuthorised') - .withArgs(cannotTransferValue.address, 'TRANSFERVALUE'); + .withArgs(canCallOnly.address, 'TRANSFERVALUE'); + + const newBalanceUP = await provider.getBalance(context.universalProfile.address); + const newBalanceRecipient = await provider.getBalance(recipient.address); + + // verify that native token balances have not changed + expect(newBalanceUP).to.equal(initialBalanceUP); + expect(initialBalanceRecipient).to.equal(newBalanceRecipient); + }); + + it('should fail when controller has neither CALL nor TRANSFERVALUE permissions', async () => { + const initialBalanceUP = await provider.getBalance(context.universalProfile.address); + const initialBalanceRecipient = await provider.getBalance(recipient.address); + + const transferPayload = universalProfileInterface.encodeFunctionData('execute', [ + OPERATION_TYPES.CALL, + recipient.address, + ethers.utils.parseEther('3'), + data, + ]); + + await expect( + context.keyManager.connect(canNeitherCallNorTransferValue).execute(transferPayload), + ) + .to.be.revertedWithCustomError(context.keyManager, 'NotAuthorised') + .withArgs(canNeitherCallNorTransferValue.address, 'TRANSFERVALUE'); const newBalanceUP = await provider.getBalance(context.universalProfile.address); const newBalanceRecipient = await provider.getBalance(recipient.address); @@ -220,7 +271,7 @@ export const shouldBehaveLikePermissionTransferValue = ( describe('when transferring value with bytes `_data`', () => { const data = '0xaabbccdd'; - it('should pass when caller has ALL PERMISSIONS', async () => { + it('should pass when controller has ALL PERMISSIONS', async () => { const initialBalanceUP = await provider.getBalance(context.universalProfile.address); const initialBalanceRecipient = await provider.getBalance(recipient.address); @@ -241,7 +292,7 @@ export const shouldBehaveLikePermissionTransferValue = ( expect(newBalanceRecipient).to.be.gt(initialBalanceRecipient); }); - it('should pass when caller has permission TRANSFERVALUE + CALL', async () => { + it('should pass when controller has permission TRANSFERVALUE + CALL', async () => { const amount = ethers.utils.parseEther('3'); const transferPayload = universalProfileInterface.encodeFunctionData('execute', [ @@ -259,7 +310,7 @@ export const shouldBehaveLikePermissionTransferValue = ( ); }); - it('should fail when caller has permission TRANSFERVALUE only', async () => { + it('should fail when controller has permission TRANSFERVALUE only', async () => { const initialBalanceUP = await provider.getBalance(context.universalProfile.address); const initialBalanceRecipient = await provider.getBalance(recipient.address); @@ -282,7 +333,7 @@ export const shouldBehaveLikePermissionTransferValue = ( expect(initialBalanceRecipient).to.equal(newBalanceRecipient); }); - it('should fail when caller does not have permission TRANSFERVALUE', async () => { + it('should fail when controller has permission CALL only but not TRANSFERVALUE', async () => { const initialBalanceUP = await provider.getBalance(context.universalProfile.address); const initialBalanceRecipient = await provider.getBalance(recipient.address); @@ -293,9 +344,34 @@ export const shouldBehaveLikePermissionTransferValue = ( data, ]); - await expect(context.keyManager.connect(cannotTransferValue).execute(transferPayload)) + await expect(context.keyManager.connect(canCallOnly).execute(transferPayload)) .to.be.revertedWithCustomError(context.keyManager, 'NotAuthorised') - .withArgs(cannotTransferValue.address, 'TRANSFERVALUE'); + .withArgs(canCallOnly.address, 'TRANSFERVALUE'); + + const newBalanceUP = await provider.getBalance(context.universalProfile.address); + const newBalanceRecipient = await provider.getBalance(recipient.address); + + // verify that native token balances have not changed + expect(newBalanceUP).to.equal(initialBalanceUP); + expect(initialBalanceRecipient).to.equal(newBalanceRecipient); + }); + + it('should fail when controller has neither CALL nor TRANSFERVALUE permissions', async () => { + const initialBalanceUP = await provider.getBalance(context.universalProfile.address); + const initialBalanceRecipient = await provider.getBalance(recipient.address); + + const transferPayload = universalProfileInterface.encodeFunctionData('execute', [ + OPERATION_TYPES.CALL, + recipient.address, + ethers.utils.parseEther('3'), + data, + ]); + + await expect( + context.keyManager.connect(canNeitherCallNorTransferValue).execute(transferPayload), + ) + .to.be.revertedWithCustomError(context.keyManager, 'NotAuthorised') + .withArgs(canNeitherCallNorTransferValue.address, 'TRANSFERVALUE'); const newBalanceUP = await provider.getBalance(context.universalProfile.address); const newBalanceRecipient = await provider.getBalance(recipient.address); @@ -309,7 +385,7 @@ export const shouldBehaveLikePermissionTransferValue = ( describe('when transferring value with graffiti `_data` (prefixed with `bytes4(0)`)', () => { const data = '0x00000000aabbccdd'; - it('should fail when caller has permission TRANSFERVALUE only', async () => { + it('should fail when controller has permission TRANSFERVALUE only', async () => { const initialBalanceUP = await provider.getBalance(context.universalProfile.address); const initialBalanceRecipient = await provider.getBalance(recipient.address); @@ -334,6 +410,56 @@ export const shouldBehaveLikePermissionTransferValue = ( expect(initialBalanceRecipient).to.equal(newBalanceRecipient); }); + it('it should fail when controller has permission CALL only', async () => { + const initialBalanceUP = await provider.getBalance(context.universalProfile.address); + const initialBalanceRecipient = await provider.getBalance(recipient.address); + + const transferPayload = universalProfileInterface.encodeFunctionData('execute', [ + OPERATION_TYPES.CALL, + recipient.address, + ethers.utils.parseEther('3'), + data, + ]); + + await expect(context.keyManager.connect(canCallOnly)['execute(bytes)'](transferPayload)) + .to.be.revertedWithCustomError(context.keyManager, 'NotAuthorised') + .withArgs(canCallOnly.address, 'TRANSFERVALUE'); + + const newBalanceUP = await provider.getBalance(context.universalProfile.address); + const newBalanceRecipient = await provider.getBalance(recipient.address); + + // verify that native token balances have not changed + expect(newBalanceUP).to.equal(initialBalanceUP); + expect(initialBalanceRecipient).to.equal(newBalanceRecipient); + }); + + it('it should fail when caller has neither permissions CALL nor TRANSFERVALUE', async () => { + const initialBalanceUP = await provider.getBalance(context.universalProfile.address); + const initialBalanceRecipient = await provider.getBalance(recipient.address); + + const transferPayload = universalProfileInterface.encodeFunctionData('execute', [ + OPERATION_TYPES.CALL, + recipient.address, + ethers.utils.parseEther('3'), + data, + ]); + + await expect( + context.keyManager + .connect(canNeitherCallNorTransferValue) + ['execute(bytes)'](transferPayload), + ) + .to.be.revertedWithCustomError(context.keyManager, 'NotAuthorised') + .withArgs(canNeitherCallNorTransferValue.address, 'TRANSFERVALUE'); + + const newBalanceUP = await provider.getBalance(context.universalProfile.address); + const newBalanceRecipient = await provider.getBalance(recipient.address); + + // verify that native token balances have not changed + expect(newBalanceUP).to.equal(initialBalanceUP); + expect(initialBalanceRecipient).to.equal(newBalanceRecipient); + }); + it('should pass when caller has permission TRANSFERVALUE + CALL', async () => { const amount = ethers.utils.parseEther('3'); @@ -451,7 +577,7 @@ export const shouldBehaveLikePermissionTransferValue = ( describe('when transferring value with graffiti `_data` (prefixed with `bytes4(0)`)', () => { const data = '0x00000000aabbccdd'; - it('should fail when caller has permission TRANSFERVALUE only', async () => { + it('should fail when controller has permission TRANSFERVALUE only', async () => { const initialBalanceUP = await provider.getBalance(context.universalProfile.address); const initialBalanceRecipient = await provider.getBalance(recipientUP.address); @@ -476,7 +602,30 @@ export const shouldBehaveLikePermissionTransferValue = ( expect(initialBalanceRecipient).to.equal(newBalanceRecipient); }); - it('should pass when caller has permission TRANSFERVALUE + CALL', async () => { + it('should fail when controller has permission CALL only', async () => { + const initialBalanceUP = await provider.getBalance(context.universalProfile.address); + const initialBalanceRecipient = await provider.getBalance(recipientUP.address); + + const transferPayload = universalProfileInterface.encodeFunctionData('execute', [ + OPERATION_TYPES.CALL, + recipientUP.address, + ethers.utils.parseEther('3'), + data, + ]); + + await expect(context.keyManager.connect(canCallOnly)['execute(bytes)'](transferPayload)) + .to.be.revertedWithCustomError(context.keyManager, 'NotAuthorised') + .withArgs(canCallOnly.address, 'TRANSFERVALUE'); + + const newBalanceUP = await provider.getBalance(context.universalProfile.address); + const newBalanceRecipient = await provider.getBalance(recipientUP.address); + + // verify that native token balances have not changed + expect(newBalanceUP).to.equal(initialBalanceUP); + expect(initialBalanceRecipient).to.equal(newBalanceRecipient); + }); + + it('should pass when controller has permission TRANSFERVALUE + CALL', async () => { const amount = ethers.utils.parseEther('3'); const transferPayload = universalProfileInterface.encodeFunctionData('execute', [ diff --git a/tests/LSP6KeyManager/Relay/ExecuteRelayCall.test.ts b/tests/LSP6KeyManager/Relay/ExecuteRelayCall.test.ts index a3c49da70..97bcc5ff1 100644 --- a/tests/LSP6KeyManager/Relay/ExecuteRelayCall.test.ts +++ b/tests/LSP6KeyManager/Relay/ExecuteRelayCall.test.ts @@ -25,6 +25,7 @@ import { // helpers import { + abiCoder, combineAllowedCalls, combinePermissions, createValidityTimestamps, @@ -107,6 +108,7 @@ export const shouldBehaveLikeExecuteRelayCall = ( await setupKeyManager(context, permissionKeys, permissionsValues); }); + describe('When signer does not have EXECUTE_RELAY_CALL permission', () => { it('should revert', async () => { const executeRelayCallPayload = context.universalProfile.interface.encodeFunctionData( @@ -165,6 +167,7 @@ export const shouldBehaveLikeExecuteRelayCall = ( .withArgs(signerWithoutExecuteRelayCall.address, 'EXECUTE_RELAY_CALL'); }); }); + describe('When testing signed message', () => { describe('When testing msg.value', () => { describe('When sending more than the signed msg.value', () => { @@ -1179,6 +1182,180 @@ export const shouldBehaveLikeExecuteRelayCall = ( }); }); }); + + describe('when calling `executeRelayCall -> LSP0.execute(uint256,address,uint256,bytes) -> TargetContract`', () => { + describe('when TargetContract returns an `uint256[]` array of 2 numbers', () => { + it('should return a `bytes` that can be decoded as a `uint256[]', async () => { + const targetContract = await new TargetContract__factory(context.accounts[0]).deploy(); + + const channelId = 0; + const validityTimestamp = 0; + + const keyManagerNonce = await context.keyManager.getNonce( + context.mainController.address, + channelId, + ); + + const getDynamicArrayOf2NumbersSig = targetContract.interface.getSighash( + 'getDynamicArrayOf2Numbers', + ); + + const erc725xExecutePayload = context.universalProfile.interface.encodeFunctionData( + 'execute', + [OPERATION_TYPES.STATICCALL, targetContract.address, 0, getDynamicArrayOf2NumbersSig], + ); + + const executeRelayCallSignature = await signLSP6ExecuteRelayCall( + context.keyManager, + keyManagerNonce.toHexString(), + validityTimestamp, + LOCAL_PRIVATE_KEYS.ACCOUNT0, + 0, + erc725xExecutePayload, + ); + + const result = await context.keyManager + .connect(context.mainController) + .callStatic.executeRelayCall( + executeRelayCallSignature, + keyManagerNonce, + validityTimestamp, + erc725xExecutePayload, + ); + + // Since we are calling the function `execute(uint256,address,uint256,bytes)` on the LSP0 contract + // and this function `returns(bytes memory)` + // we need to decode the result as `bytes` first before decoding to the expected type + // returned by the function targeted on the target contract + const [decodedResult] = abiCoder.decode(['bytes'], result); + + const expectedArrayOfNumbers = await targetContract.getDynamicArrayOf2Numbers(); + + const [decodedUint256Array] = abiCoder.decode(['uint256[]'], decodedResult); + expect(decodedUint256Array).to.deep.equal(expectedArrayOfNumbers); + }); + }); + }); + + describe('when calling `executeRelayCall -> LSP0.executeBatch(uint256[],address[],uint256[],bytes[])` and doing 2 x STATICCALLs in the batch', () => { + it('should return an array of `bytes[]` where each entry can be decoded individually', async () => { + const targetContract = await new TargetContract__factory(context.accounts[0]).deploy(); + + const channelId = 0; + const validityTimestamp = 0; + + const keyManagerNonce = await context.keyManager.getNonce( + context.mainController.address, + channelId, + ); + + const getNameSelector = targetContract.interface.getSighash('getName'); + const getNumberSelector = targetContract.interface.getSighash('getNumber'); + + const erc725xExecuteBatchPayload = context.universalProfile.interface.encodeFunctionData( + 'executeBatch', + [ + [OPERATION_TYPES.STATICCALL, OPERATION_TYPES.STATICCALL], + [targetContract.address, targetContract.address], + [0, 0], + [getNameSelector, getNumberSelector], + ], + ); + + const executeRelayCallSignature = await signLSP6ExecuteRelayCall( + context.keyManager, + keyManagerNonce.toHexString(), + validityTimestamp, + LOCAL_PRIVATE_KEYS.ACCOUNT0, + 0, + erc725xExecuteBatchPayload, + ); + + const result = await context.keyManager + .connect(context.mainController) + .callStatic.executeRelayCall( + executeRelayCallSignature, + keyManagerNonce, + validityTimestamp, + erc725xExecuteBatchPayload, + ); + + const expectedString = await targetContract.getName(); + const expectedNumber = await targetContract.getNumber(); + + // Since we are calling the function `executeBatch(uint256[],address[],uint256[],bytes[])` on the LSP0 contract + // and this function `returns(bytes[] memory)` + // we need to decode the result as `bytes[]` first before decoding each entry inside to the expected type + // returned by each functions called on the target contract + const [decodedBytesArray] = abiCoder.decode(['bytes[]'], result); + + const [decodedString] = abiCoder.decode(['string'], decodedBytesArray[0]); + expect(decodedString).to.equal(expectedString); + + const [decodedNumber] = abiCoder.decode(['uint256'], decodedBytesArray[1]); + expect(decodedNumber).to.equal(expectedNumber); + }); + }); + + describe('when calling `executeRelayCall -> LSP0.transferOwnership(address)`', () => { + it('should return nothing 0x, set the `pendingOwner` and emit `PermissionsVerified` event with right arguments', async () => { + const channelId = 0; + const validityTimestamp = 0; + + const keyManagerNonce = await context.keyManager.getNonce( + context.mainController.address, + channelId, + ); + + const newOwner = context.accounts[1].address; + + const transferOwnershipPayload = context.universalProfile.interface.encodeFunctionData( + 'transferOwnership', + [newOwner], + ); + + const executeRelayCallSignature = await signLSP6ExecuteRelayCall( + context.keyManager, + keyManagerNonce.toHexString(), + validityTimestamp, + LOCAL_PRIVATE_KEYS.ACCOUNT0, + 0, + transferOwnershipPayload, + ); + + const result = await context.keyManager + .connect(context.mainController) + .callStatic.executeRelayCall( + executeRelayCallSignature, + keyManagerNonce, + validityTimestamp, + transferOwnershipPayload, + ); + + // Since the function transferOwnership does not `returns` anything, the result should be 0x + expect(result).to.equal('0x'); + + // Run the transaction + const tx = await context.keyManager + .connect(context.mainController) + .executeRelayCall( + executeRelayCallSignature, + keyManagerNonce, + validityTimestamp, + transferOwnershipPayload, + ); + + // CHECK that the pendingOwner is set + expect(await context.universalProfile.pendingOwner()).to.equal(newOwner); + + // CHECK the `PermissionsVerified` event was emitted + await expect(tx).to.emit(context.keyManager, 'PermissionsVerified').withArgs( + context.mainController.address, // signer + 0, // value + context.universalProfile.interface.getSighash('transferOwnership'), // selector + ); + }); + }); }); }); @@ -1419,7 +1596,7 @@ export const shouldBehaveLikeExecuteRelayCall = ( describe('when specifying msg.value', () => { describe('when total `values[]` is LESS than `msg.value`', () => { - it('should revert because insufficent `msg.value`', async () => { + it('should revert because insufficient `msg.value`', async () => { const firstRecipient = context.accounts[1].address; const secondRecipient = context.accounts[2].address; const thirdRecipient = context.accounts[3].address; diff --git a/tests/LSP9Vault/LSP9Vault.behaviour.ts b/tests/LSP9Vault/LSP9Vault.behaviour.ts index e1debc273..e51ffb2ba 100644 --- a/tests/LSP9Vault/LSP9Vault.behaviour.ts +++ b/tests/LSP9Vault/LSP9Vault.behaviour.ts @@ -767,7 +767,10 @@ export const shouldBehaveLikeLSP9 = ( context.lsp9Vault.address, 0, LSP1_TYPE_IDS.LSP9OwnershipTransferStarted, - '0x', + abiCoder.encode( + ['address', 'address'], + [context.accounts.owner.address, context.universalProfile.address], + ), abiCoder.encode( ['bytes', 'bytes'], [ethers.utils.hexlify(ethers.utils.toUtf8Bytes('LSP1: typeId out of scope')), '0x'], diff --git a/tests/Mocks/LSP1TypeIDs.test.ts b/tests/Mocks/LSP1TypeIDs.test.ts new file mode 100644 index 000000000..3c00401cf --- /dev/null +++ b/tests/Mocks/LSP1TypeIDs.test.ts @@ -0,0 +1,34 @@ +import { ethers } from 'hardhat'; +import { expect } from 'chai'; +import { hexlify, keccak256, toUtf8Bytes } from 'ethers/lib/utils'; +import { LSP1TypeIDsTester, LSP1TypeIDsTester__factory } from '../../types'; +import { LSP1_TYPE_IDS } from '../../constants'; + +describe('calculate LSP1 Type IDs', () => { + const LSP1TypeIds = Object.entries(LSP1_TYPE_IDS); + + describe('Testing javascript constants', () => { + LSP1TypeIds.forEach(([typeIdName, typeIdHash]) => + it(`Testing LSP1 Type ID: ${typeIdName}`, async () => { + expect(typeIdHash).to.equal(hexlify(keccak256(toUtf8Bytes(typeIdName)))); + }), + ); + }); + + describe('Testing solidity constants', () => { + let LSP1TypeIDsTester: LSP1TypeIDsTester; + + before('Deploying `LSP1TypeIDs` tester contract', async () => { + const signers = await ethers.getSigners(); + LSP1TypeIDsTester = await new LSP1TypeIDsTester__factory(signers[0]).deploy(); + }); + + LSP1TypeIds.forEach(([typeIdName, typeIdHash]) => + it(`Testing LSP1 Type ID: ${typeIdName}`, async () => { + const returnedTypeId = await LSP1TypeIDsTester.verifyLSP1TypeID(typeIdName); + + expect(returnedTypeId).to.equal(typeIdHash); + }), + ); + }); +}); diff --git a/tests/UniversalProfile.behaviour.ts b/tests/UniversalProfile.behaviour.ts index b222c73af..f44a6dc5f 100644 --- a/tests/UniversalProfile.behaviour.ts +++ b/tests/UniversalProfile.behaviour.ts @@ -1,5 +1,5 @@ import { expect } from 'chai'; -import { ethers } from 'hardhat'; +import { ethers, network } from 'hardhat'; import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'; // types @@ -11,6 +11,8 @@ import { UniversalReceiverDelegateDataLYX, EmitEventExtension, EmitEventExtension__factory, + OwnerWithURD__factory, + OwnerWithURD, } from '../types'; // helpers @@ -736,6 +738,49 @@ export const shouldBehaveLikeLSP3 = ( }); }); }); + + describe('when using `renounceOwnership()`', () => { + describe('when caller is owner', () => { + let newContractOwner: OwnerWithURD; + + before('Use custom owner that implements LSP1', async () => { + newContractOwner = await new OwnerWithURD__factory(context.accounts[0]).deploy( + context.universalProfile.address, + ); + + await context.universalProfile + .connect(context.deployParams.owner) + .transferOwnership(newContractOwner.address); + + await newContractOwner.acceptOwnership(); + }); + + after('`renounceOwnership()` was used, build new context', async () => { + context = await buildContext(); + }); + + it('should renounce ownership of the contract and call the URD of the previous owner', async () => { + await newContractOwner.connect(context.accounts[0]).renounceOwnership(); + + await network.provider.send('hardhat_mine', [ethers.utils.hexValue(199)]); + + const tx = await newContractOwner.connect(context.accounts[0]).renounceOwnership(); + + await expect(tx) + .to.emit(newContractOwner, 'UniversalReceiver') + .withArgs( + context.universalProfile.address, + 0, + LSP1_TYPE_IDS.LSP0OwnershipTransferred_SenderNotification, + abiCoder.encode( + ['address', 'address'], + [newContractOwner.address, ethers.constants.AddressZero], + ), + '0x', + ); + }); + }); + }); }; export const shouldInitializeLikeLSP3 = (buildContext: () => Promise) => { diff --git a/tests/foundry/LSP14Ownable2Step/AcceptOwnershipCleanState.sol b/tests/foundry/LSP14Ownable2Step/AcceptOwnershipCleanState.sol new file mode 100644 index 000000000..8e1fe7f0c --- /dev/null +++ b/tests/foundry/LSP14Ownable2Step/AcceptOwnershipCleanState.sol @@ -0,0 +1,80 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.13; + +import "forge-std/Test.sol"; +import "@erc725/smart-contracts/contracts/constants.sol"; +import "../../../contracts/LSP0ERC725Account/LSP0ERC725Account.sol"; + +import { + LSP20EOACannotVerifyCall +} from "../../../contracts/LSP20CallVerification/LSP20Errors.sol"; + +contract LSP0Implementation is LSP0ERC725Account { + constructor(address _addr) LSP0ERC725Account(_addr) {} + + function renounceOwnershipStartedAt() public view returns (uint256) { + return _renounceOwnershipStartedAt; + } +} + +contract LSP0StorageUpdater { + // _renounceOwnershipStartedAt is at slot 2 for LSP0ERC725Account + bytes32[2] __gap; + uint256 _renounceOwnershipStartedAt; + + function setRenounceOwnershipStartedAt( + uint256 newRenounceOwnershipStartedAt + ) external { + _renounceOwnershipStartedAt = newRenounceOwnershipStartedAt; + } +} + +contract OwnershipAccepter { + function acceptOwnership(address _account) public { + LSP0Implementation(payable(_account)).acceptOwnership(); + } +} + +contract TwoStepRenounceOwnershipTest is Test { + LSP0Implementation account; + OwnershipAccepter ownershipAccepter; + + function setUp() public { + // Deploy LSP0 account with this address as owner + account = new LSP0Implementation(address(this)); + ownershipAccepter = new OwnershipAccepter(); + } + + function testRenounceOwnershipVariableClearedAfterAcceptOwnership() public { + // Call transferOwnership so we can check acceptOwnership behavior + account.transferOwnership(address(ownershipAccepter)); + + // Overwrite _renounceOwnershipAt using a delegatecall + LSP0StorageUpdater implementation = new LSP0StorageUpdater(); + + uint256 newRenounceOwnershipStartedAt = 10_000_000; // number of blocks + + account.execute( + OPERATION_4_DELEGATECALL, + address(implementation), + 0, + abi.encodeWithSelector( + LSP0StorageUpdater.setRenounceOwnershipStartedAt.selector, + newRenounceOwnershipStartedAt + ) + ); + + // _renounceOwnershipAt is now set to this value + assertEq( + account.renounceOwnershipStartedAt(), + newRenounceOwnershipStartedAt + ); + + // Calling LSP0's `acceptOwnership()` function that + // should reset _renounceOwnershipAt variable + ownershipAccepter.acceptOwnership(address(account)); + + // Make sure the _renounceOwnershipAt is reset after acceptOwnership call + assertEq(account.renounceOwnershipStartedAt(), 0); + } +} diff --git a/tests/foundry/LSP6KeyManager/LSP6AllowedCallsTest.t.sol b/tests/foundry/LSP6KeyManager/LSP6AllowedCallsTest.t.sol new file mode 100644 index 000000000..7b75c7c43 --- /dev/null +++ b/tests/foundry/LSP6KeyManager/LSP6AllowedCallsTest.t.sol @@ -0,0 +1,336 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.4; + +// libraries +import "forge-std/Test.sol"; +import { + LSP2Utils +} from "../../../contracts/LSP2ERC725YJSONSchema/LSP2Utils.sol"; + +// modules +import {UniversalProfile} from "../../../contracts/UniversalProfile.sol"; +import { + KeyManagerInternalTester +} from "../../../contracts/Mocks/KeyManager/KeyManagerInternalsTester.sol"; + +// constants +import { + OPERATION_0_CALL, + OPERATION_3_STATICCALL, + OPERATION_4_DELEGATECALL +} from "@erc725/smart-contracts/contracts/constants.sol"; +import { + _LSP6KEY_ADDRESSPERMISSIONS_ALLOWEDCALLS_PREFIX, + _ALLOWEDCALLS_TRANSFERVALUE, + _ALLOWEDCALLS_CALL, + _ALLOWEDCALLS_STATICCALL, + _ALLOWEDCALLS_DELEGATECALL +} from "../../../contracts/LSP6KeyManager/LSP6Constants.sol"; + +// errors to test +import {NotAllowedCall} from "../../../contracts/LSP6KeyManager/LSP6Errors.sol"; + +// mock contracts for testing +import { + FallbackInitializer +} from "../../../contracts/Mocks/FallbackInitializer.sol"; +import {TargetContract} from "../../../contracts/Mocks/TargetContract.sol"; + +contract LSP6AllowedCallsTest is Test { + using LSP2Utils for *; + + UniversalProfile universalProfile; + KeyManagerInternalTester keyManager; + + TargetContract targetContract; + FallbackInitializer targetWithFallback; + + function setUp() public { + universalProfile = new UniversalProfile(address(this)); + keyManager = new KeyManagerInternalTester(address(universalProfile)); + + targetContract = new TargetContract(); + targetWithFallback = new FallbackInitializer(); + } + + function _setupCallTypes( + bytes4 callTypesAllowed, + address contractToAllow, + bytes4 allowedSelector + ) internal { + // setup allowed calls for this controller, when we will read them from storage + bytes32 allowedCallsDataKey = LSP2Utils.generateMappingWithGroupingKey({ + keyPrefix: _LSP6KEY_ADDRESSPERMISSIONS_ALLOWEDCALLS_PREFIX, + bytes20Value: bytes20(address(this)) + }); + + bytes memory allowedCallsDataValue = abi.encodePacked( + hex"0020", // 2 bytes to specify the entry is 32 bytes long (32 = 0x0020 in hex) + callTypesAllowed, // restrictionOperations (= callTypes allowed) + contractToAllow, // address + bytes4(0xffffffff), // any standard + allowedSelector // function + ); + + universalProfile.setData(allowedCallsDataKey, allowedCallsDataValue); + } + + function testShouldRevertWithEmptyMessageCallWithCallTypeAllowedValueOnly() + public + { + // setup allowed calls for this controller, when we will read them from storage + _setupCallTypes( + _ALLOWEDCALLS_TRANSFERVALUE, + address(targetContract), + bytes4(0xffffffff) // for any function + ); + + bytes memory expectedRevertData = abi.encodeWithSelector( + NotAllowedCall.selector, + address(this), + targetContract, + bytes4(0) + ); + + // Test with CALL + vm.expectRevert(expectedRevertData); + keyManager.verifyAllowedCall( + address(this), + OPERATION_0_CALL, + address(targetContract), + 0, + "" + ); + + // Test with STATICCALL + vm.expectRevert(expectedRevertData); + keyManager.verifyAllowedCall( + address(this), + OPERATION_3_STATICCALL, + address(targetContract), + 0, + "" + ); + + // Test with DELEGATECALL + vm.expectRevert(expectedRevertData); + keyManager.verifyAllowedCall( + address(this), + OPERATION_4_DELEGATECALL, + address(targetContract), + 0, + "" + ); + } + + function testFail_ShouldRevertForAnyMessageCallToTargetWithNoCallTypeAllowed( + uint8 operationType, + uint256 value, + bytes memory callData + ) public { + _setupCallTypes(bytes4(0), address(targetContract), bytes4(0xffffffff)); // for any function + + // We don't test for operation `CREATE` or `CREATE2` + vm.assume(operationType != 1 && operationType != 2); + // we should use a valid operation type + vm.assume(operationType <= 4); + + keyManager.verifyAllowedCall( + address(this), + uint256(operationType), + address(targetContract), + value, + callData + ); + } + + function testFail_ShouldRevertWithEmptyCallNoValueWhenAssociatedCallTypeIsNotSet( + uint8 operationType, + bytes4 callTypeToGrant + ) public { + // We don't test for operation `CREATE` or `CREATE2` + vm.assume(operationType != 1 && operationType != 2); + // we should use a valid operation type + vm.assume(operationType <= 4); + + // Check for testing that the callType is not set for the associated operationType + if (operationType == OPERATION_0_CALL) { + vm.assume( + callTypeToGrant & _ALLOWEDCALLS_CALL != _ALLOWEDCALLS_CALL + ); + } + + if (operationType == OPERATION_3_STATICCALL) { + vm.assume( + callTypeToGrant & _ALLOWEDCALLS_STATICCALL != + _ALLOWEDCALLS_STATICCALL + ); + } + + if (operationType == OPERATION_4_DELEGATECALL) { + vm.assume( + callTypeToGrant & _ALLOWEDCALLS_DELEGATECALL != + _ALLOWEDCALLS_DELEGATECALL + ); + } + + _setupCallTypes( + callTypeToGrant, + address(targetWithFallback), + bytes4(0xffffffff) + ); // for any function + + keyManager.verifyAllowedCall( + address(this), + uint256(operationType), + address(targetWithFallback), + 0, + "" + ); + } + + function test_ShouldPassWithEmptyCallNoValueWhenAssociatedCallTypeIsSet( + uint8 operationType, + bytes4 callTypeToGrant + ) public { + // We don't test for operation `CREATE` or `CREATE2` + vm.assume(operationType != 1 && operationType != 2); + // we should use a valid operation type + vm.assume(operationType <= 4); + + // We should have at least one bit set in the callTypes + vm.assume(callTypeToGrant != bytes4(0)); + + // Check for testing that the callType is not set for the associated operationType + if (operationType == OPERATION_0_CALL) { + vm.assume( + callTypeToGrant & _ALLOWEDCALLS_CALL == _ALLOWEDCALLS_CALL + ); + } + + if (operationType == OPERATION_3_STATICCALL) { + vm.assume( + callTypeToGrant & _ALLOWEDCALLS_STATICCALL == + _ALLOWEDCALLS_STATICCALL + ); + } + + if (operationType == OPERATION_4_DELEGATECALL) { + vm.assume( + callTypeToGrant & _ALLOWEDCALLS_DELEGATECALL == + _ALLOWEDCALLS_DELEGATECALL + ); + } + + _setupCallTypes( + callTypeToGrant, + address(targetWithFallback), + bytes4(0xffffffff) + ); // for any function + + keyManager.verifyAllowedCall( + address(this), + uint256(operationType), + address(targetWithFallback), + 0, + "" + ); + } + + function test_ShouldPassWithCallDataAs0x00000000WhenCallTypeAllowBytes4ZeroSelector( + uint8 operationType, + bytes4 callTypeToGrant + ) public { + // We don't test for operation `CREATE` or `CREATE2` + vm.assume(operationType != 1 && operationType != 2); + // we should use a valid operation type + vm.assume(operationType <= 4); + + // Check for testing that the callType is not set for the associated operationType + if (operationType == OPERATION_0_CALL) { + vm.assume( + callTypeToGrant & _ALLOWEDCALLS_CALL == _ALLOWEDCALLS_CALL + ); + } + + if (operationType == OPERATION_3_STATICCALL) { + vm.assume( + callTypeToGrant & _ALLOWEDCALLS_STATICCALL == + _ALLOWEDCALLS_STATICCALL + ); + } + + if (operationType == OPERATION_4_DELEGATECALL) { + vm.assume( + callTypeToGrant & _ALLOWEDCALLS_DELEGATECALL == + _ALLOWEDCALLS_DELEGATECALL + ); + } + + _setupCallTypes( + callTypeToGrant, + address(targetWithFallback), + bytes4(0) // only for the bytes4(0) selector + ); + + keyManager.verifyAllowedCall( + address(this), + uint256(operationType), + address(targetWithFallback), + 0, + hex"00000000" + ); + } + + function testFail_ShouldRevertWithCallDataAs0x00000000WhenCallTypeDoesNotAllowBytes4ZeroSelector( + uint8 operationType, + bytes4 callTypeToGrant, + bytes4 randomFunctionSelectorToAllow + ) public { + // We don't test for operation `CREATE` or `CREATE2` + vm.assume(operationType != 1 && operationType != 2); + // we should use a valid operation type + vm.assume(operationType <= 4); + + // exclude the bytes4(0) selector for graffiti, and 0xffffffff for any function allowed + vm.assume( + randomFunctionSelectorToAllow != bytes4(0) && + randomFunctionSelectorToAllow != 0xffffffff + ); + + // Check for testing that the callType is not set for the associated operationType + if (operationType == OPERATION_0_CALL) { + vm.assume( + callTypeToGrant & _ALLOWEDCALLS_CALL == _ALLOWEDCALLS_CALL + ); + } + + if (operationType == OPERATION_3_STATICCALL) { + vm.assume( + callTypeToGrant & _ALLOWEDCALLS_STATICCALL == + _ALLOWEDCALLS_STATICCALL + ); + } + + if (operationType == OPERATION_4_DELEGATECALL) { + vm.assume( + callTypeToGrant & _ALLOWEDCALLS_DELEGATECALL == + _ALLOWEDCALLS_DELEGATECALL + ); + } + + _setupCallTypes( + callTypeToGrant, + address(targetWithFallback), + randomFunctionSelectorToAllow + ); + + keyManager.verifyAllowedCall( + address(this), + uint256(operationType), + address(targetWithFallback), + 0, + hex"00000000" + ); + } +}