# @erc725/smart-contracts 4.1.1 ## ERC725X - Add execute Batch function: ```soldity= function execute( uint256[] memory operationsType, address[] memory targets, uint256[] memory values, bytes[] memory datas ) public payable virtual override onlyOwner returns (bytes[] memory) { return _execute(operationsType, targets, values, datas); } ``` - Overloaded function such as execute and execute batch, having the same name, they should be written in a different format in `ethers.js` or `web3.js`: ```javascript= // web3.js example // execute myContract.methods['execute(uint256,address,uint256,bytes)'](OPERATION_CALL, target.address, 2WEI, "0x").send(); // execute Array myContract.methods['execute(uint256[],address[],uint256[],bytes[])']([OPERATION_CALL, OPERATION_CREATE], [target.address, ZERO_ADDRESS], [2WEI, 0WEI], ["0x", CONTRACT_BYTECODE]).send() // OR // execute myContract.methods['0x44c028fe'](OPERATION_CALL, target.address, 2WEI, "0x").send(); // execute Array myContract.methods['0x13ced88d']([OPERATION_CALL, OPERATION_CREATE], [target.address, ZERO_ADDRESS], [2WEI, 0WEI], ["0x", CONTRACT_BYTECODE]).send() ``` - InterfaceId was changed from: `0x44c028fe` to `0x570ef073` - Add `salt` parameter to ERC725X `ContractCreated` event. In case of `CREATE` Operation, salt will be `bytes32(0)`, in case of `CREATE2` salt will be given by the user. ```solidity= // Before event ContractCreated(uint256 indexed operationType, address indexed contractAddress, uint256 indexed value); ``` ```solidity= // After event ContractCreated(uint256 indexed operationType, address indexed contractAddress, uint256 indexed value, bytes32 salt); ``` - Use custom errors instead of revert strings ```javascript= // Before require(value == 0, "ERC725X: cannot transfer value with operation DELEGATECALL"); ``` ```javascript= // After if (value != 0) revert ERC725X_MsgValueDisallowedInDelegateCall(); ``` - Internal function refactoring. ## ERC725Y - Rename internal ERC725Y mapping from `_store` to `store`. - Use custom errors instead of revert strings ```javascript= // Before require(dataKeys.length == dataValues.length, "Keys length not equal to values length"); ``` ```javascript= // After if (dataKeys.length != dataValues.length) { revert ERC725Y_DataKeysValuesLengthMismatch(dataKeys.length, dataValues.length); } ``` ## Constants.js - Added Event signatures under `EventSignatures` function. - Custom error signatures are found with their corresponding type and explanation message. ```javascript= ..., '0x5ac83135': { error: 'ERC725X_MsgValueDisallowedInDelegateCall()', message: 'ERC725X: Sending value is not allowed when making a delegatecall', }, ... ``` # @lukso/lsp-smart-contracts 0.8.0 ## LSP0 - InterfaceId was changed from: `0xeb6be62e` to : `0x66767497` As the interfaceId of `LSP0` is the XOR of all interfaceIds of the standards implemented. `ERC725X` interfaceId changed, `LSP14` interfaceId changed, and `LSP17Extendable` interfaceId was added, hence this changed the interfaceId of `LSP0`. LSP0 supports the following interfaceIds: - `ERC725X`: Before: `0x44c028fe` After: `0x570ef073` - `ERC725Y`: The same `0x714df77c` - `ERC1271`: The same `0x1626ba7e` - `LSP1`: The same `0x6bb56a14` - `LSP14`: Before (`ClaimOwnership`): `0xa375e9c6` After: `0x94be5999` - `LSP17Extendable`: New: `0xa918fa6b` - Before: `ValueReceived` event was emitted whenever the contract receive ether through `fallback()` or `receive()` functions. After:`ValueReceived` event is emitted whenever the contract receive ether through `fallback()` or `receive()` functions and whenever `payable` functions are executed when associated with `value`. Such as `execute(..)`, `execute(..) Array`, `universalReceiver(..)`. - Inherit new `execute(..) Array` function from `ERC725X`. ```soldity= function execute( uint256[] memory operationsType, address[] memory targets, uint256[] memory values, bytes[] memory datas ) public payable virtual override onlyOwner returns (bytes[] memory) { return _execute(operationsType, targets, values, datas); } ``` Function overloading should happen in LSP0 as well: ```javascript= // web3.js example // execute myContract.methods['execute(uint256,address,uint256,bytes)'](OPERATION_CALL, target.address, 2WEI, "0x").send(); // execute Array myContract.methods['execute(uint256[],address[],uint256[],bytes[])']([OPERATION_CALL, OPERATION_CREATE], [target.address, ZERO_ADDRESS], [2WEI, 0WEI], ["0x", CONTRACT_BYTECODE]).send() // OR // execute myContract.methods['0x44c028fe'](OPERATION_CALL, target.address, 2WEI, "0x").send(); // execute Array myContract.methods['0x13ced88d']([OPERATION_CALL, OPERATION_CREATE], [target.address, ZERO_ADDRESS], [2WEI, 0WEI], ["0x", CONTRACT_BYTECODE]).send() ``` - Add `salt` parameter to ERC725X `ContractCreated` event in `execute(..)` and `execute(..) Array` functions. In case of `CREATE` Operation, salt will be `bytes32(0)`, in case of `CREATE2` salt will be given by the user. ```solidity= // Before event ContractCreated(uint256 indexed operationType, address indexed contractAddress, uint256 indexed value); ``` ```solidity= // After event ContractCreated(uint256 indexed operationType, address indexed contractAddress, uint256 indexed value, bytes32 salt); ``` - Before: `isValidSignature(..)` function was requiring the owner contract to support `ERC1271` interfaceId through `ERC165` before calling isValidSignature on the owner. After: `isValidSignature(..)` function does not require that the owner supports `ERC1271` interfaceId. - Change in the call to the `UniversalReceiverDelegate`, instead of calling the `universalReceiverDelegate(..)` function on the `UniversalReceiverDelegate`, we call the `universalReceiver(..)` function on it. We append the `msg.sender` and `msg.value` as extra calldata, so the `UniversalReceiverDelegate` can know initially who called `universalReceiver(..)` function on LSP0. ```javascript= // Before function universalReceiverDelegate( address caller, uint256 value, bytes32 typeId, bytes memory data ) external returns (bytes memory result); ``` ```javascript= // After function universalReceiver( bytes32 typeId, bytes memory data ) external returns (bytes memory result); // The address of the caller of LSP0 and the value received to the LSP0, are not passed as parameters but rather appended to the call. // In this way, functions are unified, and LSP0 can delegate its universalReceiver functionality to another LSP0 since now the functions have the same name. ``` - Change in the `UniversalReceiver` event. Adjust indexed parameters and switching the `returnedValue` with `receivedData`, event signature didn’t change. ```javascript= // Before event UniversalReceiver( address indexed from, uint256 value, bytes32 indexed typeId, bytes indexed returnedValue, bytes receivedData ); ``` ```javascript= // After event UniversalReceiver( address indexed from, uint256 indexed value, bytes32 indexed typeId, bytes receivedData, bytes returnedValue ); ``` - Instead of just having the `UniversalReceiverDelegate` reacting on `universalReceiver(typeId,data)` function calls, `MappedUniversalReceiverDelegate` is introduced as a contract that can be set to react on certain typeIds. **Main differences:** - `UniversalReceiverDelegate` react on the whole call regardless of the typeId. - `MappedUniversalReceiverDelegate` react on calls with specific typeIds, if set by the owner. This approach creates a more scalable design for reacting on specific actions, as you don't need to modify existing `UniversalReceiverDelegate` to react on a new `typeId`. You can just simply add a `MappedUniversalReceiverDelegate` for this specific typeId. The `MappedUniversalReceiverDelegate` has a specific data key: ```json= { "name": "LSP1UniversalReceiverDelegate:<bytes32>", "key": "0x0cfc51aec37c55a4d0b10000<bytes32>", "keyType": "Mapping", "valueType": "address", "valueContent": "Address" } ``` > <bytes32> is the typeId to be reacted on. A good example would be the following: - You configure your `UniversalReceiverDelegate` to react on `LSP7` & `LSP8` transfers with a custom logic. - Later on, you decide that you want to react on `LSP9` Vaults, and on `LSPXX` Assets, instead of each time with the introduction of a new standard, you are forced to change the main `UniversalReceiverDelegate` to support more actions, instead, you can create a `MappedUniversalReceiverDelegate`for each typeId that will inform you about a specific action. In this case, you can keep the logic for LSP7 & LSP8 in the main `UniversalReceiverDelegate`, and you can add a `MappedUniversalReceiverDelegate`for `LSP9` and `LSPXX` using the following method: - You create a contract supporting `LSP1InterfaceId`with the `universalReceiver(..)` function - You add the custom logic to be triggered each time you receive `LSPXX` asset. - You deploy it. - You set its address under this following data key: ```json= { "name": "LSP1UniversalReceiverDelegate:<typeId of LSPXX>", "key": "0x0cfc51aec37c55a4d0b10000<typeId of LSPXX>", "keyType": "Mapping", "valueType": "address", "valueContent": "Address" } ``` - Each time you receive LSPXX asset (`universalReceiver(..)` function is called with typeId of LSPXX asset), the call will be forwarded to the `MappedUniversalReceiverDelegate`to react on the transfer. - **Before**: The return value of the `universalReceiver(..)` function was the return value of the `UniversalReceiverDelegate`. `0x` in case it didn’t exist. **After**: The return value of the `universalReceiver(..)` function is both return values of `UniversalReceiverDelegate` and `MappedUniversalReceiverDelegate` encoded as bytes. - Change the **Ownership management** from `ClaimOwnership` to `LSP14Ownable2Step`: - Changing function name from `claimOwnership(..)` to `acceptOwnership(..)`. - Change the event name from `RenounceOwnershipInitiated` to `RenounceOwnershipStarted`. - `renounceOwnership(..)` function is now 2 step process where once it’s called, the second call should happen after a delay of 100 block. If the second call was not made in another 100 block after the delay, the process is reset. - Introduce `OwnershipTransferStarted` and `OwnershipRenounced` events. - In `transferOwnership(..)`, the universalReceiver function on the `pendingOwner` will be called with a typeId: - `bytes32` `=>` `keccak256('LSP0OwnershipTransferStarted')` - In `acceptOwnership(..)`, the universalReceiver function on the `previousOwner` and `newOwner` will be called with a typeId: - `bytes32` `=>` `keccak256('LSP0OwnershipTransferred_SenderNotification')` for the `previousOwner`. - `bytes32` `=>` `keccak256('LSP0OwnershipTransferred_RecipientNotification')` for the `newOwner`. > This is useful to be aware when someone transfer the ownership to an account for you. - Introduced `LSP17` Support in `LSP0`: - **Before:** Whenever the contract was called with a non-existing function selector, or any random bytes, the call was allowed and won't revert. - **After:** Whenever the contract was called with random bytes of length less than 4 bytes or when the data is preprended with `bytes4(0)`, the call will be allowed and won't revert. **Example:** - Calling the LSP0 with `0xaabb` will pass since the data is less than 4 bytes of length. - Calling the LSP0 with `0x00000000aabbccddeeff11223344556677` will pass since the `aabbccddeeff11223344556677` data is preprended with `bytes4(0)`. This is useful for sending data **graffiti** `(= random data)`, mimicking the behavior of EOA (allow sending random bytes). Could be useful for sending on-chain messages. - Calling the LSP0 with any data equal to 4 bytes or longer, and not preprended with `bytes4(0)` will result in the following steps: - Query the storage of the LSP0, and check if there is an address under this following data Key: ```json= { "name": "LSP17Extension:<bytes4>", "key": "0xcee78b4094da860110960000<bytes4>", "keyType": "Mapping", "valueType": "address", "valueContent": "Address" } ``` > <bytes4> is the first 4 bytes of the data received - If there is not an address stored under this data key, revert with custom error `NoExtensionFoundForFunctionSelector(bytes4(data))` - If there is an address stored under the data key, forward the `msg.data` received to this address via a low level call with appending the `msg.sender` and `msg.value`as extra 52 bytes to the call. Then return the return value received after this low level call. - This feature is useful for making the LSP0 extendable, where you can add functions to be called on the LSP0 as extension. A good example would be about ERC721 tokens: - In order to accept safe ERC721 tokens transfers, as a contract it must implement the `onERC721Received(..)` function. However, in LSP0, we don't include this function natively. - Since the contract is Extendable with LSP17, we calculate the function selector of `onERC721Received(..)` equal to `0x150b7a02`. - We create a contract that implements this function, deploy it, and then take its address. - We store the address of the contract containing the `onERC721Received(..)` function under this data key: ```json= { "name": "LSP17Extension:<0x150b7a02>", "key": "0xcee78b4094da860110960000<0x150b7a02>", "keyType": "Mapping", "valueType": "address", "valueContent": "Address" } ``` > Check LSP2-ERC725YJSONSchema Standard to know how to encode this data key - Whenever a safe transfer is triggered with LSP0 as recipient, and the LSP0 have the `onERC721Received(..)` extension, the call will pass since the call to `onERC721Receied(..)` on the LSP0 will pass and resolve to the extension address. > Check LSP17-ContractExtension Standard to know more information. ## LSP1 - Remove the `IUniversalReceiverDelegate.sol` There is no special functions anymore for `UniversalReceiverDelegate`, the function that should be used is the `universalReceiver(typeId,data)` whether it's the main function of the contract or the function being delegated to. - Remove `LSP1Delegate` InterfaceId. - Internal refactoring for `LSP1UniversalReceiverDelegateUP`and `LSP1UniversalReceiverDelegateVault`, the main delegates used by the LSP0 and LSP9. (Functions refactoring and adding custom errors). - The main `UniversalReceiverDelegate` for the LSP0, if used with a combination of LSP6 X LSP0 X LSP1, requires the `SETDATA /OR/ SUPERSETDATA` and `REENTRANCY` Permissions. (Before it was just `SETDATA`). ## LSP2 - Added the compact bytes array type, more information here: https://github.com/lukso-network/LIPs/blob/main/LSPs/LSP-2-ERC725YJSONSchema.md#bytescompactbytesarray ## LSP6 - Introduce `execute(uint256[], bytes[])` batch and `executeRelayCall(bytes[], uint256[], uint256[], bytes[])` batch versions of `execute(bytes)` and `executeRelayCall(bytes,uint256,bytes)`. Since there is function overloading, using `web3.js` or `ether.js` there is a need to call these functions using function signatures, like the example above for execute on LSP0 and ERC725X. ```javascript= // web3.js example // execute myContract.methods['execute(bytes)'](payload).send(); // execute Array myContract.methods['execute(uint256[],bytes[])']([value1,value2], [payload1,payload2]).send(); // OR Using function selectors. // executeRelayCall myContract.methods['executeRelayCall(bytes,uint256,bytes)'](signature,nonce,payload).send(); // executeRelayCall Array myContract.methods['executeRelayCall(bytes[],uint256[], uint256[],bytes[])']([signature1,signature2],[nonce1,nonce2],[value1,value2], [payload1, payload2]).send(); // OR Using function selectors. ``` - Change the param of `getNonce(address,uint256)` from `uint256` to `uint128`: `getNonce(address,uint128)` - These 2 changes resulted in the change of the interfaceId from: `0xc403d48f` to `0xfb437414`. - The event `Executed` changed from: ```javascript= // Before Executed(uint256 indexed value, bytes4 selector) ``` ```javascript= // After Executed(bytes4 indexed selector, uint256 indexed value) ``` - Change in the permissions: ```javascript= // Before const PERMISSIONS = { CHANGEOWNER: "0x0000000000000000000000000000000000000000000000000000000000000001", // .... .... .... 0001 CHANGEPERMISSIONS: "0x0000000000000000000000000000000000000000000000000000000000000002", // .... .... .... 0010 ADDPERMISSIONS: "0x0000000000000000000000000000000000000000000000000000000000000004", // .... .... .... 0100 SETDATA: "0x0000000000000000000000000000000000000000000000000000000000000008", // .... .... .... 1000 CALL: "0x0000000000000000000000000000000000000000000000000000000000000010", // .... .... 0001 .... STATICCALL: "0x0000000000000000000000000000000000000000000000000000000000000020", // .... .... 0010 .... DELEGATECALL: "0x0000000000000000000000000000000000000000000000000000000000000040", // .... .... 0100 .... DEPLOY: "0x0000000000000000000000000000000000000000000000000000000000000080", // .... .... 1000 .... TRANSFERVALUE: "0x0000000000000000000000000000000000000000000000000000000000000100", // .... 0001 .... .... SIGN: "0x0000000000000000000000000000000000000000000000000000000000000200", // .... 0010 .... .... ENCRYPT: "0x0000000000000000000000000000000000000000000000000000000000000400", // .... 0100 .... .... SUPER_SETDATA: "0x0000000000000000000000000000000000000000000000000000000000000800", // .... 1000 .... .... SUPER_TRANSFERVALUE: "0x0000000000000000000000000000000000000000000000000000000000001000", // 0001 .... .... .... SUPER_CALL: "0x0000000000000000000000000000000000000000000000000000000000002000", // 0010 .... .... .... SUPER_STATICCALL: "0x0000000000000000000000000000000000000000000000000000000000004000", // 0100 .... .... .... SUPER_DELEGATECALL: "0x0000000000000000000000000000000000000000000000000000000000008000", // 1000 .... .... .... } ``` ```javascript= // After const PERMISSIONS = { CHANGEOWNER :"0x0000000000000000000000000000000000000000000000000000000000000001", ADDCONTROLLER :"0x0000000000000000000000000000000000000000000000000000000000000002", CHANGEPERMISSIONS :"0x0000000000000000000000000000000000000000000000000000000000000004", ADDEXTENSIONS :"0x0000000000000000000000000000000000000000000000000000000000000008", CHANGEEXTENSIONS :"0x0000000000000000000000000000000000000000000000000000000000000010", ADDUNIVERSALRECEIVERDELEGATE :"0x0000000000000000000000000000000000000000000000000000000000000020", CHANGEUNIVERSALRECEIVERDELEGATE :"0x0000000000000000000000000000000000000000000000000000000000000040", REENTRANCY :"0x0000000000000000000000000000000000000000000000000000000000000080", SUPER_TRANSFERVALUE :"0x0000000000000000000000000000000000000000000000000000000000000100", TRANSFERVALUE :"0x0000000000000000000000000000000000000000000000000000000000000200", SUPER_CALL :"0x0000000000000000000000000000000000000000000000000000000000000400", CALL :"0x0000000000000000000000000000000000000000000000000000000000000800", SUPER_STATICCALL :"0x0000000000000000000000000000000000000000000000000000000000001000", STATICCALL :"0x0000000000000000000000000000000000000000000000000000000000002000", SUPER_DELEGATECALL :"0x0000000000000000000000000000000000000000000000000000000000004000", DELEGATECALL :"0x0000000000000000000000000000000000000000000000000000000000008000", DEPLOY :"0x0000000000000000000000000000000000000000000000000000000000010000", SUPER_SETDATA :"0x0000000000000000000000000000000000000000000000000000000000020000", SETDATA :"0x0000000000000000000000000000000000000000000000000000000000040000", ENCRYPT :"0x0000000000000000000000000000000000000000000000000000000000080000", DECRYPT :"0x0000000000000000000000000000000000000000000000000000000000100000", SIGN :"0x0000000000000000000000000000000000000000000000000000000000200000", } ``` - Summarizing: - Change in the order of some permissions. - Change in the name of some permissions `ADDPERMISISON` changed to `ADDCONTROLLER` - Add new permissions such as `ADD/CHANGEEXTENSIONS` and `ADD/CHANGEUNIVERSALRECEIVERDELEGATE` that allows to add/change respectively the data stored under the data keys relative to `LSP17Extensions` and `LSP1UniversalReceiverDelegate` and `MappedUniversalReceiverDelegate` in LSP0. - Add `REENTRANCY` Permission that is required in order to reenter the call to the profile along with the respective permission. Example: A contract doing a call by reentering the profile needs the `CALL` and `REENTRANCY` Permissions. - The main `UniversalReceiverDelegate` developed by `LUKSO` with the name `LSP1UniversalReceiverDelegateUP` needs the `REENTRANCY` Permission in addition to `SETDATA` Permission. (This contract set the data keys in a reenter call after the initial call to transfer the LSP7/8 Assets) - To change the address of the `UniversalReceiverDelegate` or `MappedUniversalReceiverDelegate` or remove it, Permission needed is `CHANGEUNIVERSALRECEIVERDELEGATE`. - Super Permissions are inlined after their relevant permission, not at the end. - Allow `bytes32(0)` data key to be set a data value through the LSP6KeyManager. - Signed message in `executeRelayCall(..)` functions has a new format: **Before**, the signed message was formed of: `uint256 chainId, address KeyManager, uint256 nonce and bytes payloadToExecute` and was hashed according to solidity `keccak256`. resulting `messageHash`. Then signed with the normal signature process of `keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", messageHash))` provided by `ethers.js` with `signer.signMessage` / `web3.js` with `web3.eth.personal.sign`. ```javascript= // Before let hash = ethers.utils.solidityKeccak256( ["uint256", "address", "uint256", "bytes"], [ HARDHAT_CHAINID, context.keyManager.address, nonceBefore, executeRelayCallPayload, ] ); let signature = await signer.signMessage(ethers.utils.arrayify(hash)); await context.keyManager.connect(relayer) .executeRelayCall(signature, nonceBefore, executeRelayCallPayload); ``` **After:** - The data is formed of: `0x19 <0x00> <KeyManager address> <LSP6_VERSION> <chainId> <nonce> <value> <payload>` After constructing the data, it will be hashed according to solidity `keccak256`, then signed. ```md= 0x19: byte intended to ensure that the `signed_data` is not valid RLP. 0x00: version 0 of the EIP191. KeyManager address: The address of the Key Manager executing the payload. LSP6_VERSION: Version relative to the LSP6KeyManager defined as a uint256 equal to `6`. chainId: The chainId of the blockchain where the Key Manager is deployed, as a uint256. nonce: The nonce to sign the payload with, as a uint256. value: The amount of native token to transfer to the linked target contract alongside the call. payload: The payload to be executed. ``` - LUKSO developed a signer tool for this method: https://github.com/lukso-network/tools-eip191-signer To sign messages for the KeyManager, you can use if as follows: ```javascript= eip191Signer.signDataWithIntendedValidator( validatorAddress, // We Put address of LSP6KeyManager here: message, // Packed encoded of: <LSP6VERSION><chainId><nonce><value><payload> signingKey, // The private key of the signer ); ``` - Combined the `AllowedStandard`/`AllowedAddresses`/`AllowedFunctions` to `AllowedCalls`. ```javascript= // Before { "name": "AddressPermissions:AllowedAddresses:<address>", "key": "0x4b80742de2bfc6dd6b3c0000<address>", "keyType": "MappingWithGrouping", "valueType": "address[]", "valueContent": "Address" }, { "name": "AddressPermissions:AllowedFunctions:<address>", "key": "0x4b80742de2bf8efea1e80000<address>", "keyType": "MappingWithGrouping", "valueType": "bytes4[]", "valueContent": "Bytes4" }, { "name": "AddressPermissions:AllowedStandards:<address>", "key": "0x4b80742de2bf3efa94a30000<address>", "keyType": "MappingWithGrouping", "valueType": "bytes4[]", "valueContent": "Bytes4" }, ``` ```javascript= // After Combined to: { "name": "AddressPermissions:AllowedCalls:<address>", "key": "0x4b80742de2bf393a64c70000<address>", "keyType": "MappingWithGrouping", "valueType": "(bytes4,address,bytes4)[CompactBytesArray]", "valueContent": "(Bytes4,Address,Bytes4)" } ``` The functionalities of the old data keys are combined within 1 data key. This provide a more efficient and more precise approach. Each entry (Allowed call) is made of three elements concatenated together as a tuple that forms a final bytes28 long value. - The **ERC165 Standard** that `<address>` is allowed to interact with `(bytes4)` - The **Address** that `<address>` is allowed to interact with `(bytes20)` - The **Function selector** that `<address>` is allowed to interact with `(bytes4)` The full list of allowed calls MUST be constructed as a [CompactBytesArray](https://github.com/lukso-network/LIPs/blob/main/LSPs/LSP-2-ERC725YJSONSchema.md#bytescompactbytesarray) according to LSP2-ERC725YJSONSchema as follow: ```javascript= <001c> <bytes4 allowedInterfaceId> <bytes20 allowedAddress> <bytes4 allowedFunction> <001c> ... <001c> ... ``` > **NB:** the three dots `...` are placeholders for `<bytes4 allowedInterfaceId> <bytes20 allowedAddress> <bytes4 allowedFunction>` and used for brievity. - `001c`: **001c** in decimals is **28**, which is the sum of bytes length of the three elements below concatenated together. - `allowedInterfaceId`: The ERC165 interface id being supported by the contract called from the target. - `allowedAddress`: The address called by the target contract. - `allowedFunction`: The function selector being called on the contract called by the target contract. - Check is discarded for an element if the value is full `ff` bytes. e.g, `0xffffffff` for interfaceIds and function selectors and `0xffffffffffffffffffffffffffffffffffffffff` for addresses. There MUST be at most 2 discarded checks, meaning `0xffffffffffffffffffffffffffffffffffffffffffffffffffffffff` data key is disallowed. Instead the SUPER Permissions should be used. **Example 1:** If address A has CALL permission, and have the following value for AllowedCalls: ```javascript= 0x001c11223344cafecafecafecafecafecafecafecafecafecafebb11bb11 ``` The address A is allowed to interact with the function selector **`0xbb11bb11`** on the **`0xcafecafecafecafecafecafecafecafecafecafe`** address as long as the address supports **`0x11223344`** interfaceId through ERC165. **Example 2:** If address B has CALL permission, and have the following value for AllowedCalls: ```javascript= 0x001cffffffffcafecafecafecafecafecafecafecafecafecafeffffff001c68686868ffffffffffffffffffffffffffffffffffffffffffffffff ``` The `001c` is the length of each element, boiled down into: ```javascript= 1 > ||ffffffff||cafecafecafecafecafecafecafecafecafecafe||ffffffff|| 2 > ||68686868||ffffffffffffffffffffffffffffffffffffffff||ffffffff|| ``` The address B is allowed to interact with: - the address **`0xcafecafecafecafecafecafecafecafecafecafe`** without any restriction on the interfaceId or the function selector. - **any address** supporting the **`0x68686868`** interfaceId without any restriction on the function. If nothing (empty data) is stored under this data key for a specific address, he is not allowed to execute. - Change the Data key from `AllowedERC725YKeys` to `AllowedERC725YDataKeys`. ```javascript= // Before { "name": "AddressPermissions:AllowedERC725YKeys:<address>", "key": "0x4b80742de2bf90b8b4850000<address>", "keyType": "MappingWithGrouping", "valueType": "bytes32[]", "valueContent": "Bytes32" } ``` ```javascript= // After { "name": "AddressPermissions:AllowedERC725YDataKeys:<address>", "key": "0x4b80742de2bf866c29110000<address>", "keyType": "MappingWithGrouping", "valueType": "bytes[CompactBytesArray]", "valueContent": "Bytes" } ``` The encoding of AllowedERC725YDataKeys changed from `bytes32[]` to `bytes[CompactBytesArray]`. To set AllowedERC725YDataKeys for a specific address you need to store: ```javascript= <length of the data key prefix> <data key prefix> ``` - `length of the data key prefix`: The length of the prefix of the data key which the rest is dynamic. MUST be a number between `1` and `32`. - `data key prefix`: The prefix of the data key to be checked against the data keys being set. **Example 1:** - If address A has SETDATA permission, and have the following value for AllowedERC725YDataKeys: ```javasript= > 0x0020eafec4d89fa9619884b60000abe425d64acd861a49b8ddf5c0b6962110481f38 > 0x||0020||eafec4d89fa9619884b60000abe425d64acd861a49b8ddf5c0b6962110481f38 ``` > 0020 (32 in decimals) is the length of the data key to be set. Resolve to: Address A is only allowed to set the value for the data key attached above which is `0xeafec4d89fa9619884b60000abe425d64acd861a49b8ddf5c0b6962110481f38` **Example 2:** If address B has SETDATA permission, and have the following value for AllowedERC725YDataKeys: ```javascript= > 0x000aeafec4d89fa9619884b60020beefbeefbeefbeefbeefbeefbeefbeefbeefbeefbeefbeefbeefbeefbeefbeef > 0x||000a||eafec4d89fa9619884b6||0020||beefbeefbeefbeefbeefbeefbeefbeefbeefbeefbeefbeefbeefbeefbeefbeef ``` > 000a (10 in decimals) is the length of the `eafec4d89fa9619884b6` prefix Resolve to: Address B is only allowed to set the value for the data `0xbeefbeef..beef` data key and any data key that starts with `0xeafec4d89fa9619884b6`. So for example setting these data keys is automatically allowed: ```javascript= > 0xeafec4d89fa9619884b6000000000000000000000000000000000fffffffffff > 0xeafec4d89fa9619884b600000000000000000000000000000000000000000001 > 0xeafec4d89fa9619884b600000000000000011111100000000000000000000000 > 0xeafec4d89fa9619884b6000000000000000022220000000000000000006666666 ``` If nothing (empty data) is stored under the AllowedERC725YDataKey key for a specific address, he is not allowed to setData. ## LSP7 - InterfaceId was changed from: `0x5fcaac27` to : `0xda1f85e4` - Changing `bool` param to `bool[]` in token `transferBatch(..)` - Adjusted custom errors and add their signatures in `constants.js` ## LSP8 - Add `onERC721Received` for `safeTransferFrom` in `LSP8CompatibleERC721` - Changing `bool` param to `bool[]` in token `transferBatch(..)` - InterfaceId was changed from: `0x49399145` to : `0x622e7a01` ## LSP9 - Same Changes for LSP0 expect: - InterfaceId was changed from: `0xfd4d5c50` to `0x7050cee9`. - LSP9 supports the following interfaceIds: - `ERC725X`: Before: `0x44c028fe` After: `0x570ef073` - `ERC725Y`: The same `0x714df77c` - `LSP1`: The same `0x6bb56a14` - `LSP14`: Before (`ClaimOwnership`): `0xa375e9c6` After: `0x94be5999` - `LSP17Extendable`: New: `0xa918fa6b` - Whenever a `UniversalReceiverDelegate` either the main one, or the `MappedUniversalReceiverDelegate` is called by the `universalReceiver`function of LSP9, it have the ability to set normal data on the Vault using `setData(..)` functions. These data keys excludes by default: LSP1 & LSP6 (Permissions) & LSP17 Data keys. - For example, this is handy for `UniversalReceiverDelegates` to directly set the addresses of the assets you own in the vault storage instead of going through the owner of the vault, or the owner of the owner of the vault. - In `transferOwnership(..)`, the universalReceiver function on the `pendingOwner` will be called with a typeId: - `bytes32` `=>` `keccak256('LSP9OwnershipTransferStarted')` - In `acceptOwnership(..)`, the universalReceiver function on the `previousOwner` and `newOwner` will be called with a typeId: - `bytes32` `=>` `keccak256('LSP9OwnershipTransferred_SenderNotification')` for the `previousOwner`. - `bytes32` `=>` `keccak256('LSP9OwnershipTransferred_RecipientNotification')` for the `newOwner`. ## LSP11 The first version of the `LSP11BasicSocialRecovery` was introduced, this version is subject to potentiel changes in the future as it's not yet finalized. The LSP11 is a social recovery contract that works along with LSP6, after being granted the `ADDCONTROLLER` and `CHANGEPERMISSION` Permissions. It uses a combination of guardians and a secretHash for recovery. Guardians will select a controller to be recover access to the LSP0 through LSP6, if he reached a number of selection by the guardians equal or higher to the guardianThreshold initially set by the owner, the address selected needs to present a secret word that produce the secretHash initially set by the owner in order to be set as a contoller for LSP0 via LSP6. ## LSP14 `LSP14-Ownable2Step` is an extension for `ERC173` Standard, where `transferOnwership(..)` and `renounceOnwership(..)` are done in a 2 step proccesses. The transfer of ownership is conducted in two stages, where the owner should call `transferOwnership(..)` to set the pendingOwner. Second, the pendingOwner should call `acceptOwnership(..)` to confirm ownership transfer. During the transferOwnership, the `universalReceiver(..)` function is called on the pendingOwner if its a contract that supports LSP1 InterfaceId with: - `bytes32` `=>` `keccak256('LSP14OwnershipTransferStarted')` During acceptOwnership, the `universalReceiver(..)` function is called on the previousOwner, and the newOwner if its a contract that supports LSP1 InterfaceId with - `bytes32` `=>` `keccak256('LSP14OwnershipTransferred_SenderNotification')` for the `previousOwner`. - `bytes32` `=>` `keccak256('LSP14OwnershipTransferred_RecipientNotification')` for the `newOwner`. The process for renouncing ownership follows a similar structure, where an initial call is made to `renounceOwnership(..)` function, followed by a waiting period (100 blocks) and a specific timeframe during which the ownership can be renounced in (100 blocks) by calling again the `renounceOwnership(..)` function before the process is reset. ## LSP16 `LSP16-UniversalFactory` is a CREATE2 Factory contract, that allows deploying contracts at the same address on different chains, if and only if: - The LSP16 contract was deployed on the same address accross the different chains - The contract being deployed have the same bytecode - The same salt is being used - The same initializeCalldata/Constructor parameters are being used. This means if you are deploying a `UniversalProfile`: in order to have the same address on **chainA** and **chainB**, the UniversalProfile should be deployed with the same owner address. Later, this owner can be swapped or changed. The salt passed by the user is not the end-salt used for contract creation as it is handled internally to avoid squatting addresses for initializable contracts (base contracts, proxies, etc ..). > More information is provided in the natspec of the `LSP16-UniversalFactory.sol` contract. ## LSP17 `LSP17-ContractExtension` standard standardize a way for a contract to be extended after deployment where non-native functions can be called on the contract to be extended with the use of extensions. This standard uses a similar approach for diamount proxies, but instead of using `delegatecall` to call extensions (facets), it uses the `call` opcode. This approach is safer, especially if this standard will be used for contracts that holds high value assets such as a UniversalProfile, vaults, etc .. and can't be exposed to the risk of being destructed or storage manipulated. This standard does not inforce a specific way for setting extension or getting them per function selector. ## Constants.js - Replace most of the string errors in solidity contracts with Custom errors. The selector for the custom errors is present in constants.js. - `ALL_PERMISSIONS` constant was including all permissions except `DELEGATECALL` and `SUPER_DELEGATECALL`, now we add `REENTRANCY` also as an exception. This constant is useful for granting addresses most of the safe permissions without calculating them manually. - ⚠️ `ERC725YKeys` const function was renamed to `ERC725YDataKeys`, Make sure you change your references in your code base!. ```javascript= // Before const UniversalReceiverDelegateKey = ERC725YKeys.LSP1.LSP1UniversalReceiverDelegate; ``` ```javascript= // After const UniversalReceiverDelegateKey = ERC725YDataKeys.LSP1.LSP1UniversalReceiverDelegate; ```