diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index 0fb243e1f..edb5a2057 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -1,5 +1,7 @@ # This workflow benchmark the gas usage of Universal Profile for common interactions - +# It compare the gas cost of the changes made between: +# - a feature branch (where a PR is opened) +# - a target branch (where the PR will be merged) name: ๐Ÿ†™ ๐Ÿ“Š Universal Profile Benchmark on: @@ -22,7 +24,11 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - name: Checkout base branch + uses: actions/checkout@v3 + with: + ref: ${{ github.event.pull_request.base.sha }} + fetch-depth: 0 - name: Use Node.js '16.15.0' uses: actions/setup-node@v2 @@ -37,11 +43,40 @@ jobs: run: npx hardhat compile - name: ๐Ÿงช Run Benchmark tests - run: npm run test:benchmark + # Rename the file to be able to generate benchmark JSON report + run: | + npm run test:benchmark + mv gas_benchmark_result.json gas_benchmark_before.json + + - name: Checkout current branch + uses: actions/checkout@v3 + # Do not run `git clean -ffdx && git reset --hard HEAD` to prevent removing `gas_benchmark_before.json` + with: + clean: false + + - name: Use Node.js '16.15.0' + uses: actions/setup-node@v2 + with: + node-version: "16.15.0" + cache: "npm" + + - name: ๐Ÿ“ฆ Install dependencies + run: npm ci + + - name: ๐Ÿ—๏ธ Build contract artifacts + run: npx hardhat compile + + - name: ๐Ÿงช Run Benchmark tests + run: | + npm run test:benchmark + mv gas_benchmark_result.json gas_benchmark_after.json - name: ๐Ÿ“Š Generate Benchmark Report + run: npx hardhat gas-benchmark --compare gas_benchmark_after.json --against gas_benchmark_before.json + + - name: ๐Ÿ’ฌ Add Gas Report as comment in PR uses: peter-evans/create-or-update-comment@v2 with: token: ${{ secrets.GITHUB_TOKEN }} issue-number: ${{ github.event.pull_request.number }} - body-file: "./benchmark.md" + body-file: "./gas_benchmark.md" diff --git a/.github/workflows/build-lint-test.yml b/.github/workflows/build-lint-test.yml index 223d60b02..d5cf13392 100644 --- a/.github/workflows/build-lint-test.yml +++ b/.github/workflows/build-lint-test.yml @@ -44,6 +44,17 @@ jobs: - name: ๐Ÿงช run import/requires tests run: npm run test:importRequire + - name: ๐Ÿ“š generate ABI docs + run: npm run build:docs + + - name: ๐Ÿ” check if ABI auto-generated docs need to be updated + run: |- + if [[ $(git diff --name-only) != "" ]]; + then + echo "Error: Please generate ABI docs after making changes to Solidity code and Natspec comments!" + exit 1 + fi + test-suites: strategy: matrix: @@ -53,7 +64,6 @@ jobs: "upinit", "lsp1", "lsp2", - "lsp4", "lsp6", "lsp6init", "lsp7", @@ -65,6 +75,7 @@ jobs: "lsp11", "lsp11init", "lsp17", + "lsp17extensions", "lsp20", "lsp20init", "lsp23", diff --git a/.github/workflows/solc_version.yml b/.github/workflows/solc_version.yml index 48e660532..2f25c4c45 100644 --- a/.github/workflows/solc_version.yml +++ b/.github/workflows/solc_version.yml @@ -3,6 +3,16 @@ name: Solidity Compiler Versions on: + workflow_dispatch: + + # Used to check pragma settings for `.sol` files are correct before releasing + push: + branches: + - "develop" + # compare gas diff only when editing Solidity smart contract code + paths: + - "contracts/**/*.sol" + pull_request: types: [opened] @@ -16,10 +26,9 @@ jobs: strategy: matrix: solc: [ - # TODO: wait for a patch release of @erc725/smart-contracts to compile on these 3 versions - # "0.8.5", - # "0.8.6", - # "0.8.7", + "0.8.5", + "0.8.6", + "0.8.7", "0.8.8", "0.8.9", "0.8.10", @@ -55,17 +64,23 @@ jobs: solc-select install ${{ matrix.solc }} solc-select use ${{ matrix.solc }} + - name: Compare versions to filter contracts to compile + uses: madhead/semver-utils@latest + id: comparison + with: + version: ${{ matrix.solc }} + compare-to: 0.8.12 + - name: Compile Smart Contracts run: | - if [[ ${{ matrix.solc }} < 0.8.12 ]] + if [[ "<" == "${{ steps.comparison.outputs.comparison-result }}" ]] then - solc $(ls contracts/**/*.sol | grep -v "Compatible") --allow-paths $(pwd)/node_modules/ \ - @erc725/smart-contracts/=node_modules/@erc725/smart-contracts/ \ - @openzeppelin/=node_modules/@openzeppelin/ \ - solidity-bytes-utils/=node_modules/solidity-bytes-utils/ + solc $(ls contracts/**/*.sol | grep -v "Compatible" | grep -v "Extension4337") --allow-paths $(pwd)/node_modules/ \ + @=node_modules/@ \ + solidity-bytes-utils/=node_modules/solidity-bytes-utils/ \ + ../=$(pwd)/contracts/ else solc contracts/**/*.sol \ - @erc725/smart-contracts/=node_modules/@erc725/smart-contracts/ \ - @openzeppelin/=node_modules/@openzeppelin/ \ + @=node_modules/@ \ solidity-bytes-utils/=node_modules/solidity-bytes-utils/ fi; diff --git a/.gitignore b/.gitignore index 9b491c2bd..c1824b6ac 100644 --- a/.gitignore +++ b/.gitignore @@ -132,7 +132,7 @@ out/ forge-cache/ # generated gas benchmark -benchmark.md +gas_benchmark.md # Exclude build output folders /common diff --git a/.solhint.json b/.solhint.json index 098ebd76c..26e01c48a 100644 --- a/.solhint.json +++ b/.solhint.json @@ -15,8 +15,11 @@ "private-vars-leading-underscore": ["error", { "strict": false }], "imports-on-top": "error", "visibility-modifier-order": "error", + "no-unused-import": "error", + "no-global-import": "error", "reason-string": ["warn", { "maxLength": 120 }], "avoid-low-level-calls": "off", - "no-empty-blocks": "off" + "no-empty-blocks": ["error", { "ignoreConstructors": true }], + "custom-errors": "off" } } diff --git a/CHANGELOG.md b/CHANGELOG.md index 7739d4f1e..94c52c0c5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,11 +2,55 @@ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. +## [0.12.0-rc.0](https://github.com/lukso-network/lsp-smart-contracts/compare/v0.11.1...v0.12.0-rc.0) (2023-10-18) + +### โš  BREAKING CHANGES + +- remove `LSP4Compatibility` contract + ERC20 Compatible token interfaces `ILSP7CompatibleERC20` / `ILSP8CompatibleERC721` (#749) +- add extra parameter `requestor` to `lsp20VerifyCall` (#753) +- Roll back to old `LSP1UniversalReceiverDelegate` Interface and functions (#741) +- remove `isEncodedArray(...)` from `LSP2utils.sol` (#746) +- add callee params to lsp20 & EXECUTE_RELAY_CALL permission & mapping reentrancyStatus (#729) +- set LSP8 TokenId Type on deployment / initialization (#712) +- re-change param name from `allowNonLSP1Recipient` to `force` + +### Deprecation + +- re-change param name from `allowNonLSP1Recipient` to `force` ([d59a2ff](https://github.com/lukso-network/lsp-smart-contracts/commit/d59a2ff4712a5373ce72ba1ccd63b2d796f60cd9)) +- remove `isEncodedArray(...)` from `LSP2utils.sol` ([#746](https://github.com/lukso-network/lsp-smart-contracts/issues/746)) ([1ff7cd4](https://github.com/lukso-network/lsp-smart-contracts/commit/1ff7cd4e34a91c53ce72f19fb8d469d2ae0c9a09)) +- remove `LSP4Compatibility` contract + ERC20 Compatible token interfaces `ILSP7CompatibleERC20` / `ILSP8CompatibleERC721` ([#749](https://github.com/lukso-network/lsp-smart-contracts/issues/749)) ([b038412](https://github.com/lukso-network/lsp-smart-contracts/commit/b038412c99d5149f25a83156322539e817e1575b)) +- Roll back to old `LSP1UniversalReceiverDelegate` Interface and functions ([#741](https://github.com/lukso-network/lsp-smart-contracts/issues/741)) ([dab41a1](https://github.com/lukso-network/lsp-smart-contracts/commit/dab41a1baf61876865191424a5e19548845f1630)) +- set LSP8 TokenId Type on deployment / initialization ([#712](https://github.com/lukso-network/lsp-smart-contracts/issues/712)) ([67cb333](https://github.com/lukso-network/lsp-smart-contracts/commit/67cb3333256e31a0c432a9bcd62f08e83e969222)) + +### Features + +- add `_afterTokenTransfer` hook in LSP7 + LSP8 ([4e3adc2](https://github.com/lukso-network/lsp-smart-contracts/commit/4e3adc24e233138b8f1471320e3b1bb3307ef524)) +- add `data` param in `_before` and `_after` token transfer hooks ([0cd0976](https://github.com/lukso-network/lsp-smart-contracts/commit/0cd097604193957aeb2d6bf181d9193719621eac)) +- add `callee` params to lsp20 & `EXECUTE_RELAY_CALL` permission & `mapping reentrancyStatus` ([#729](https://github.com/lukso-network/lsp-smart-contracts/issues/729)) ([0ae4c83](https://github.com/lukso-network/lsp-smart-contracts/commit/0ae4c83d80227e53c614d46dce96f8b727822839)) +- add lsp20 to `acceptOwnership` in LSP0 ([#747](https://github.com/lukso-network/lsp-smart-contracts/issues/747)) ([804779a](https://github.com/lukso-network/lsp-smart-contracts/commit/804779a5f7ac76b21695a00c622a7c0e5801192a)) +- allow `endingTimestamp` to be 0 ([a8c730f](https://github.com/lukso-network/lsp-smart-contracts/commit/a8c730f608fdd585c94b69704272bdaec938a565)) +- allow `renounceOwnership()` through LSP6 ([dd74b56](https://github.com/lukso-network/lsp-smart-contracts/commit/dd74b56af54cc01a5e28b50feac2ce6659403cda)) +- allow sending value when using `setData(..)` through the LSP6 ([#725](https://github.com/lukso-network/lsp-smart-contracts/issues/725)) ([624c4a6](https://github.com/lukso-network/lsp-smart-contracts/commit/624c4a6dfa0c5c83c94f0b5e952adc783098d12c)) +- create Extension4337 ([#735](https://github.com/lukso-network/lsp-smart-contracts/issues/735)) ([d1df1d0](https://github.com/lukso-network/lsp-smart-contracts/commit/d1df1d0c106f47bd24427f1c20c3423a00bb993c)) +- add extra parameter `requestor` to `lsp20VerifyCall` ([#753](https://github.com/lukso-network/lsp-smart-contracts/pull/753)) ([f82626d](https://github.com/lukso-network/lsp-smart-contracts/commit/f82626d5f64efd396a40690687dfb5a2dc9c036e)) + +### Bug Fixes + +- add `receive()` function in LSP7 & LSP8 to silent compiler warning ([#711](https://github.com/lukso-network/lsp-smart-contracts/pull/711)) ([e6fb55d](https://github.com/lukso-network/lsp-smart-contracts/commit/e6fb55d9acc7bd63b9d66920f2f346ecf812c289)) +- bug in dodoc config, incorrect signature ([06b1f04](https://github.com/lukso-network/lsp-smart-contracts/commit/06b1f04f83158f27efb19670ed81f566d7b151ba)) +- return `bytes32(0)` when permission value retrieved is not exactly 32 bytes long ([7422ab0](https://github.com/lukso-network/lsp-smart-contracts/commit/7422ab053b27f1139dc8b917218bebd2407009b9)) +- ts configs and tests ([#733](https://github.com/lukso-network/lsp-smart-contracts/issues/733)) ([a977312](https://github.com/lukso-network/lsp-smart-contracts/commit/a977312f99a3f957e581f5b502ef818f033f3846)) +- update logic to check against controller permissions when setting its Allowed Calls or ERC725Y Data Keys ([#734](https://github.com/lukso-network/lsp-smart-contracts/issues/734)) ([0d43077](https://github.com/lukso-network/lsp-smart-contracts/commit/0d43077a96b542f645d50cf9389f26c292c8b39e)) + ## [0.11.1](https://github.com/lukso-network/lsp-smart-contracts/compare/v0.11.0-rc.1...v0.11.0) (2023-09-07) ### โš  BREAKING CHANGES -- Include LSP20 in interfaceId calculation ([#668](https://github.com/lukso-network/lsp-smart-contracts/pull/668)), from: `0x3e89ad98` to `0x24871b3d` +- change visibility of `_reentrancyStatus` state variable from `private` to `internal` in `LSP6KeyManagerCore` ([#651](https://github.com/lukso-network/lsp-smart-contracts/pull/651)) + +- change data key for `SupportedStandards` from `LSP3UniversalProfile` to `LSP3Profile` in `LSP3Constants.sol` and `constants.ts` ([#664](https://github.com/lukso-network/lsp-smart-contracts/pull/664)) + +- Include LSP20 in interfaceId calculation ([#668](https://github.com/lukso-network/lsp-smart-contracts/pull/668)), from: `0x3e89ad98` to `0x24871b3d`. - Return instead of revert when the `LSP1UniversalReceiverDelegateUP` is not able to register LSP5-LSP10 data keys. ([#672](https://github.com/lukso-network/lsp-smart-contracts/pull/672)) @@ -22,20 +66,54 @@ All notable changes to this project will be documented in this file. See [standa - Remove `LSP0Utils.sol`. ([#683](https://github.com/lukso-network/lsp-smart-contracts/pull/683)) -- Change token LSP1 notification data format from `abi.encodepacked` to `abi.encode`. ([#699](https://github.com/lukso-network/lsp-smart-contracts/pull/699)) +- Add LSP17 in inheritance of LSP7 and LSP8 ([#697](https://github.com/lukso-network/lsp-smart-contracts/pull/697)) + +- Change token LSP1 notification data format from `abi.encodePacked` to `abi.encode`. ([#699](https://github.com/lukso-network/lsp-smart-contracts/pull/699)) - Notify Operator via LSP1 in `authorizeOperator` in LSP7 and LSP8. ([#700](https://github.com/lukso-network/lsp-smart-contracts/pull/700)) ### Features -- Add LSP25ExecuteRelayCall as its seperate standard. ([#678](https://github.com/lukso-network/lsp-smart-contracts/pull/678)) +- Mark multiple functions as `virtual` across the smart contracts, so that their behaviour can be overriden through inheritance [#644](https://github.com/lukso-network/lsp-smart-contracts/pull/644). + +- Change visibility of `_reentrancyStatus` state variable from `private` to `internal` in `LSP6KeyManagerCore` ([#651](https://github.com/lukso-network/lsp-smart-contracts/pull/651)) + +- Create implementation of `LSP23LinkedContractsFactory` ([#658](https://github.com/lukso-network/lsp-smart-contracts/pull/658)) + +- Add external call (= hook) to LSP1 in LSP14 `renounceOwnership` function ([#663](https://github.com/lukso-network/lsp-smart-contracts/pull/663)) + +- Add `LSP25ExecuteRelayCall` as its separate standard. ([#678](https://github.com/lukso-network/lsp-smart-contracts/pull/678)) + +- Add `getOperatorsOf(address)` function to LSP7 ([#698](https://github.com/lukso-network/lsp-smart-contracts/pull/698)) + +- Add LSP17 in inheritance of LSP7 and LSP8 ([#697](https://github.com/lukso-network/lsp-smart-contracts/pull/697)) + +- Notify Operator via LSP1 in authorizeOperator in LSP7 and LSP8. (https://github.com/lukso-network/lsp-smart-contracts/pull/700) + +### Perfs + +- Improve deployment + runtime cost of LSP6 Key Manager by replacing calldata slices with `abi.decode` when verifying `ERC725X.execute(uint256,address,uint256,bytes)` calldata payloads ([#682](https://github.com/lukso-network/lsp-smart-contracts/pull/682)) ### Bug Fixes +- Add lock guard when transferring ownership in LSP14 ([#645](https://github.com/lukso-network/lsp-smart-contracts/pull/645)) + +- Delete pending when confirming renounce ownership the second time ([#646](https://github.com/lukso-network/lsp-smart-contracts/pull/646)) + +- Disallowing setting LSP6 Key Manager as a LSP17 extension in setData paths in Key Manager ([#648](https://github.com/lukso-network/lsp-smart-contracts/pull/648)) + +- Add check for `length == 0` when checking for Allowed ERC725Y Data Keys in Key Manager to prevent mask from allowing any data keys ([#659](https://github.com/lukso-network/lsp-smart-contracts/pull/659)) + +- Use bitwise OR `|` operator in `LSP6Utils` function `combinePermissions(...)` to prevent from adding same permission twice and generate incorrect `bytes32` permission value ([#660](https://github.com/lukso-network/lsp-smart-contracts/pull/660)) + +- Resolve inheritance of `LSP8Burnable` to include LSP4 ([#661](https://github.com/lukso-network/lsp-smart-contracts/pull/661)) + - Refactor `_fallbackLSP17Extendable` function to enable to run code after it is called + prevent potential solc bug "storage write removal". ([#674](https://github.com/lukso-network/lsp-smart-contracts/pull/674)) - Update lsp8 compatible approve() logic to allow operators themselves to authorize operators. ([#681](https://github.com/lukso-network/lsp-smart-contracts/pull/681)) +- Add input validations for LSP6, LSP1 and LSP17 data keys when setting data in `LSP6SetDataModule` ([#679](https://github.com/lukso-network/lsp-smart-contracts/pull/679)) + ### Build - upgrade `@erc725/smart-contracts` version to 5.2.0 ([#696](https://github.com/lukso-network/lsp-smart-contracts/pull/696)) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 92db00f36..9490584a7 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -54,6 +54,10 @@ When merging a branch to `develop` PRs should be squashed into one conventional ## Solidity Code Comments +A good level of documentation is crucial for understanding the intended behaviour of the smart contracts and for identifying any potential discrepancies between the implementation and the intended behaviour. + +When making contributions, each smart contracts and functions should be well-documented, with clear comments explaining the purpose and functionality of each function and module. + When changing or adding NatSpec comments to any `function`, `error` or `event` in any contract make sure to adhere to the following guidelines: 1. `@dev` and `@notice` tags can both contain text descriptions and two types of lists: bullet points or numbered lists. Make sure that those tags always start with text descriptions first, not with lists. diff --git a/README.md b/README.md index f79b2666e..42c6fd102 100644 --- a/README.md +++ b/README.md @@ -70,6 +70,7 @@ import { ERC725YDataKeys, PERMISSIONS, ALL_PERMISSIONS, + LSP8_TOKEN_ID_TYPES, LSP25_VERSION, ErrorSelectors, EventSigHashes, @@ -106,14 +107,20 @@ See the [issue related to Hardhat Typescript + ES Modules](https://hardhat.org/h ### Typescript types -The following additional typescript types are also available. +The following additional typescript types are also available, including types for the JSON format of the LSP3 Profile and LSP4 Digital Asset metadata. ```ts import { LSP2ArrayKey, LSPSupportedStandard, LSP6PermissionName, - LSPErrorInfo, + LSP3ProfileMetadataJSON, + LSP3ProfileMetadata, + LSP4DigitalAssetMetadataJSON, + LSP4DigitalAssetMetadata, + ImageMetadata, + LinkMetadata, + AssetMetadata, } from "@lukso/lsp-smart-contracts/constants"; ``` @@ -130,6 +137,7 @@ The following audits and formal verification were conducted. All high-level issu - Runtime Verification - Formal Verification, 2023-02-20, Final Result: [RuntimeVerification_formalVerification_2023_02_20.pdf](./audits/RuntimeVerification_formalVerification_2023_02_20.pdf) - Trust Audit, 2023-04-13, Final Result: [Trust_audit_2023_04_13.pdf](./audits/Trust_audit_2023_04_13.pdf) - Watchpug Audit, 2023-04-21, Final Result: [Watchpug_audit_2023_04_21.pdf](./audits/Watchpug_audit_2023_04_21.pdf) +- Code4Rena Audit Contest, 2023-06-30 to 2023-07-14, Final Result: [See Code4Rena audit report on Code4rena.com website](https://code4rena.com/reports/2023-06-lukso) ## Contributors โœจ diff --git a/audits/README.md b/audits/README.md new file mode 100644 index 000000000..7871ca659 --- /dev/null +++ b/audits/README.md @@ -0,0 +1,3 @@ +# Smart Contracts Audits + +In addition to the audits reports in pdf available in this page, you can find the audit report of the **Code4Rena audit contest** at the following link: [https://code4rena.com/reports/2021-05-lukso](https://code4rena.com/reports/2021-05-lukso) diff --git a/constants.ts b/constants.ts index 61688c84e..c7802d7a8 100644 --- a/constants.ts +++ b/constants.ts @@ -17,6 +17,7 @@ export const INTERFACE_IDS = { ERC165: '0x01ffc9a7', ERC1271: '0x1626ba7e', ERC20: '0x36372b07', + ERC20Metadata: '0xa219a025', ERC223: '0x87d43052', ERC721: '0x80ac58cd', ERC721Metadata: '0x5b5e139f', @@ -26,7 +27,8 @@ export const INTERFACE_IDS = { ERC725Y: '0x629aa694', LSP0ERC725Account: '0x24871b3d', LSP1UniversalReceiver: '0x6bb56a14', - LSP6KeyManager: '0x66918867', + LSP1UniversalReceiverDelegate: '0xa245bbda', + LSP6KeyManager: '0x23f34c62', LSP7DigitalAsset: '0x05519512', LSP8IdentifiableDigitalAsset: '0x1ae9ba1f', LSP9Vault: '0x28af17e6', @@ -35,7 +37,7 @@ export const INTERFACE_IDS = { LSP17Extendable: '0xa918fa6b', LSP17Extension: '0xcee78b40', LSP20CallVerification: '0x1a0eb6a5', - LSP20CallVerifier: '0x480c0ec2', + LSP20CallVerifier: '0x0d6ecac7', LSP25ExecuteRelayCall: '0x5ac79908', }; @@ -47,7 +49,7 @@ export const INTERFACE_IDS = { * Can be used to check if a signature is valid or not. */ export const ERC1271_VALUES = { - MAGIC_VALUE: '0x1626ba7e', + SUCCESS_VALUE: '0x1626ba7e', FAIL_VALUE: '0xffffffff', }; @@ -58,12 +60,12 @@ export const ERC1271_VALUES = { * @dev values returned by the `lsp20VerifyCall` and `lsp20VerifyCallResult` functions of the LSP20 standard. * Can be used to check if a calldata payload was check and verified. */ -export const LSP20_MAGIC_VALUES = { +export const LSP20_SUCCESS_VALUES = { VERIFY_CALL: { - // bytes3(keccak256("lsp20VerifyCall(address,uint256,bytes)")) + "0x01" - WITH_POST_VERIFICATION: '0x9bf04b01', - // bytes3(keccak256("lsp20VerifyCall(address,uint256,bytes)")) + "0x00" - NO_POST_VERIFICATION: '0x9bf04b00', + // bytes3(keccak256("lsp20VerifyCall(address,address,address,uint256,bytes)")) + "0x00" + NO_POST_VERIFICATION: '0xde928f00', + // bytes3(keccak256("lsp20VerifyCall(address,address,address,uint256,bytes)")) + "0x01" + WITH_POST_VERIFICATION: '0xde928f01', }, // bytes4(keccak256("lsp20VerifyCallResult(bytes32,bytes)")) VERIFY_CALL_RESULT: '0xd3fc45d3', @@ -90,6 +92,54 @@ export const OPERATION_TYPES = { export type LSP2ArrayKey = { length: string; index: string }; export type LSPSupportedStandard = { key: string; value: string }; +// JSON Metadata + +export type LSP3ProfileMetadataJSON = { + LSP3Profile: LSP3ProfileMetadata; +}; + +export type LSP3ProfileMetadata = { + name: string; + description: string; + profileImage?: ImageMetadata[]; + backgroundImage?: ImageMetadata[]; + tags?: string[]; + links?: LinkMetadata[]; + avatar?: AssetMetadata[]; +}; + +export type LSP4DigitalAssetMetadataJSON = { + LSP4Metadata: LSP4DigitalAssetMetadata; +}; + +export type LSP4DigitalAssetMetadata = { + description: string; + links: LinkMetadata[]; + images: ImageMetadata[][]; + assets: AssetMetadata[]; + icon: ImageMetadata[]; +}; + +export type ImageMetadata = { + width: number; + height: number; + hashFunction: string; + hash: string; + url: string; +}; + +export type LinkMetadata = { + title: string; + url: string; +}; + +export type AssetMetadata = { + hashFunction: string; + hash: string; + url: string; + fileType: string; +}; + /** * @dev list of ERC725Y keys from the LSP standards. * Can be used to detect if a contract implements a specific LSP Metadata standard @@ -178,6 +228,9 @@ export const ERC725YDataKeys = { // AddressPermissions:AllowedCalls:
+ bytes2(0) 'AddressPermissions:AllowedCalls': '0x4b80742de2bf393a64c70000', }, + LSP8: { + LSP8TokenIdType: '0x715f248956de7ce65e94d9d836bfead479f7e70d69b718d47bfe7b00e05b4fe4', + }, LSP9: { SupportedStandards_LSP9: SupportedStandards.LSP9Vault.key, }, @@ -223,7 +276,7 @@ export const CALLTYPE = { /** * @dev `bytes32` hex value for all the LSP6 permissions excluding REENTRANCY, DELEGATECALL and SUPER_DELEGATECALL for security (these should be set manually) */ -export const ALL_PERMISSIONS = '0x00000000000000000000000000000000000000000000000000000000003f3f7f'; +export const ALL_PERMISSIONS = '0x00000000000000000000000000000000000000000000000000000000007f3f7f'; export type LSP6PermissionName = keyof typeof PERMISSIONS; @@ -232,28 +285,29 @@ export type LSP6PermissionName = keyof typeof PERMISSIONS; */ // prettier-ignore export const PERMISSIONS = { - CHANGEOWNER: "0x0000000000000000000000000000000000000000000000000000000000000001", - ADDCONTROLLER: "0x0000000000000000000000000000000000000000000000000000000000000002", - EDITPERMISSIONS: "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", + CHANGEOWNER: '0x0000000000000000000000000000000000000000000000000000000000000001', // .... .... .... .... .... 0001 + ADDCONTROLLER: '0x0000000000000000000000000000000000000000000000000000000000000002', // .... .... .... .... .... 0010 + EDITPERMISSIONS: '0x0000000000000000000000000000000000000000000000000000000000000004', // .... .... .... .... .... 0100 + ADDEXTENSIONS: '0x0000000000000000000000000000000000000000000000000000000000000008', // .... .... .... .... .... 1000 + CHANGEEXTENSIONS: '0x0000000000000000000000000000000000000000000000000000000000000010', // .... .... .... .... 0001 0000 + ADDUNIVERSALRECEIVERDELEGATE: '0x0000000000000000000000000000000000000000000000000000000000000020', // .... .... .... .... 0010 0000 + CHANGEUNIVERSALRECEIVERDELEGATE: '0x0000000000000000000000000000000000000000000000000000000000000040', // .... .... .... .... 0100 0000 + REENTRANCY: '0x0000000000000000000000000000000000000000000000000000000000000080', // .... .... .... .... 1000 0000 + SUPER_TRANSFERVALUE: '0x0000000000000000000000000000000000000000000000000000000000000100', // .... .... .... 0001 0000 0000 + TRANSFERVALUE: '0x0000000000000000000000000000000000000000000000000000000000000200', // .... .... .... 0010 0000 0000 + SUPER_CALL: '0x0000000000000000000000000000000000000000000000000000000000000400', // .... .... .... 0100 0000 0000 + CALL: '0x0000000000000000000000000000000000000000000000000000000000000800', // .... .... .... 1000 0000 0000 + SUPER_STATICCALL: '0x0000000000000000000000000000000000000000000000000000000000001000', // .... .... 0001 0000 0000 0000 + STATICCALL: '0x0000000000000000000000000000000000000000000000000000000000002000', // .... .... 0010 0000 0000 0000 + SUPER_DELEGATECALL: '0x0000000000000000000000000000000000000000000000000000000000004000', // .... .... 0100 0000 0000 0000 + DELEGATECALL: '0x0000000000000000000000000000000000000000000000000000000000008000', // .... .... 1000 0000 0000 0000 + DEPLOY: '0x0000000000000000000000000000000000000000000000000000000000010000', // .... 0001 0000 0000 0000 0000 + SUPER_SETDATA: '0x0000000000000000000000000000000000000000000000000000000000020000', // .... 0010 0000 0000 0000 0000 + SETDATA: '0x0000000000000000000000000000000000000000000000000000000000040000', // .... 0100 0000 0000 0000 0000 + ENCRYPT: '0x0000000000000000000000000000000000000000000000000000000000080000', // .... 1000 0000 0000 0000 0000 + DECRYPT: '0x0000000000000000000000000000000000000000000000000000000000100000', // 0001 0000 0000 0000 0000 0000 + SIGN: '0x0000000000000000000000000000000000000000000000000000000000200000', // 0010 0000 0000 0000 0000 0000 + EXECUTE_RELAY_CALL: '0x0000000000000000000000000000000000000000000000000000000000400000', // 0100 0000 0000 0000 0000 0000 } /** @@ -323,6 +377,21 @@ export const LSP1_TYPE_IDS = { '0xe32c7debcb817925ba4883fdbfc52797187f28f73f860641dab1a68d9b32902c', }; +// LSP8 +// ---------- + +/** + * @dev list of LSP8 Token ID types that can be used to create different types of NFTs. + * @see for details see: https://github.com/lukso-network/LIPs/blob/main/LSPs/LSP-8-IdentifiableDigitalAsset.md#lsp8tokenidtype + */ +export const LSP8_TOKEN_ID_TYPES = { + NUMBER: 0, + STRING: 1, + UNIQUE_ID: 2, + HASH: 3, + ADDRESS: 4, +}; + // LSP25 // ---------- diff --git a/contracts/Factories/Create2Factory.sol b/contracts/Factories/Create2Factory.sol index 2f77452d9..05cc2052d 100644 --- a/contracts/Factories/Create2Factory.sol +++ b/contracts/Factories/Create2Factory.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: MIT +// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.4; // DO NOT TOUCH diff --git a/contracts/LSP0ERC725Account/ILSP0ERC725Account.sol b/contracts/LSP0ERC725Account/ILSP0ERC725Account.sol index f930c39da..f2704401a 100644 --- a/contracts/LSP0ERC725Account/ILSP0ERC725Account.sol +++ b/contracts/LSP0ERC725Account/ILSP0ERC725Account.sol @@ -1,20 +1,6 @@ // SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.4; -// interfaces -import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; -import { - IERC725X -} from "@erc725/smart-contracts/contracts/interfaces/IERC725X.sol"; -import { - IERC725Y -} from "@erc725/smart-contracts/contracts/interfaces/IERC725Y.sol"; -import {IERC1271} from "@openzeppelin/contracts/interfaces/IERC1271.sol"; -import { - ILSP1UniversalReceiver -} from "../LSP1UniversalReceiver/ILSP1UniversalReceiver.sol"; -import {ILSP14Ownable2Step} from "../LSP14Ownable2Step/ILSP14Ownable2Step.sol"; - /** * @title Interface of the [LSP-0-ERC725Account] standard, an account based smart contract that represents an identity on-chain. * diff --git a/contracts/LSP0ERC725Account/LSP0Constants.sol b/contracts/LSP0ERC725Account/LSP0Constants.sol index 98948eea4..b714b80d6 100644 --- a/contracts/LSP0ERC725Account/LSP0Constants.sol +++ b/contracts/LSP0ERC725Account/LSP0Constants.sol @@ -6,7 +6,7 @@ bytes4 constant _INTERFACEID_LSP0 = 0x24871b3d; bytes4 constant _INTERFACEID_ERC1271 = 0x1626ba7e; // ERC1271 - Standard Signature Validation -bytes4 constant _ERC1271_MAGICVALUE = 0x1626ba7e; +bytes4 constant _ERC1271_SUCCESSVALUE = 0x1626ba7e; bytes4 constant _ERC1271_FAILVALUE = 0xffffffff; // Ownerhsip Transfer Type IDs diff --git a/contracts/LSP0ERC725Account/LSP0ERC725AccountCore.sol b/contracts/LSP0ERC725Account/LSP0ERC725AccountCore.sol index 133318d74..4ff55adb7 100644 --- a/contracts/LSP0ERC725Account/LSP0ERC725AccountCore.sol +++ b/contracts/LSP0ERC725Account/LSP0ERC725AccountCore.sol @@ -8,6 +8,10 @@ import { ILSP1UniversalReceiver } from "../LSP1UniversalReceiver/ILSP1UniversalReceiver.sol"; +import { + ILSP1UniversalReceiverDelegate as ILSP1Delegate +} from "../LSP1UniversalReceiver/ILSP1UniversalReceiverDelegate.sol"; + // libraries import {BytesLib} from "solidity-bytes-utils/contracts/BytesLib.sol"; import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; @@ -19,14 +23,8 @@ import {LSP1Utils} from "../LSP1UniversalReceiver/LSP1Utils.sol"; import {LSP2Utils} from "../LSP2ERC725YJSONSchema/LSP2Utils.sol"; // modules -import { - ERC725YCore, - IERC725Y -} from "@erc725/smart-contracts/contracts/ERC725YCore.sol"; -import { - ERC725XCore, - IERC725X -} from "@erc725/smart-contracts/contracts/ERC725XCore.sol"; +import {ERC725YCore} from "@erc725/smart-contracts/contracts/ERC725YCore.sol"; +import {ERC725XCore} from "@erc725/smart-contracts/contracts/ERC725XCore.sol"; import { OwnableUnset } from "@erc725/smart-contracts/contracts/custom/OwnableUnset.sol"; @@ -37,18 +35,18 @@ import { } from "../LSP20CallVerification/LSP20CallVerification.sol"; // constants -import "@erc725/smart-contracts/contracts/constants.sol"; import { _INTERFACEID_LSP0, _INTERFACEID_ERC1271, - _ERC1271_MAGICVALUE, + _ERC1271_SUCCESSVALUE, _ERC1271_FAILVALUE, _TYPEID_LSP0_OwnershipTransferStarted, _TYPEID_LSP0_OwnershipTransferred_SenderNotification, _TYPEID_LSP0_OwnershipTransferred_RecipientNotification -} from "../LSP0ERC725Account/LSP0Constants.sol"; +} from "./LSP0Constants.sol"; import { _INTERFACEID_LSP1, + _INTERFACEID_LSP1_DELEGATE, _LSP1_UNIVERSAL_RECEIVER_DELEGATE_PREFIX, _LSP1_UNIVERSAL_RECEIVER_DELEGATE_KEY } from "../LSP1UniversalReceiver/LSP1Constants.sol"; @@ -63,16 +61,13 @@ import { // errors import { - ERC725Y_DataKeysValuesLengthMismatch + ERC725Y_DataKeysValuesLengthMismatch, + ERC725Y_DataKeysValuesEmptyArray } from "@erc725/smart-contracts/contracts/errors.sol"; import { NoExtensionFoundForFunctionSelector } from "../LSP17ContractExtension/LSP17Errors.sol"; -import { - LSP14MustAcceptOwnershipInSeparateTransaction -} from "../LSP14Ownable2Step/LSP14Errors.sol"; - /** * @title The Core Implementation of [LSP-0-ERC725Account] Standard. * @@ -105,8 +100,6 @@ abstract contract LSP0ERC725AccountCore is } } - // solhint-disable no-complex-fallback - /** * @notice The `fallback` function was called with the following amount of native tokens: `msg.value`; and the following calldata: `callData`. * @@ -128,6 +121,7 @@ abstract contract LSP0ERC725AccountCore is * * @custom:events {ValueReceived} event when receiving native tokens. */ + // solhint-disable-next-line no-complex-fallback fallback( bytes calldata callData ) external payable virtual returns (bytes memory) { @@ -149,7 +143,7 @@ abstract contract LSP0ERC725AccountCore is */ function batchCalls( bytes[] calldata data - ) public virtual returns (bytes[] memory results) { + ) public virtual override returns (bytes[] memory results) { results = new bytes[](data.length); for (uint256 i; i < data.length; ) { (bool success, bytes memory result) = address(this).delegatecall( @@ -158,7 +152,7 @@ abstract contract LSP0ERC725AccountCore is if (!success) { // Look for revert reason and bubble it up if present - if (result.length > 0) { + if (result.length != 0) { // The easiest way to bubble the revert reason is using memory via assembly // solhint-disable no-inline-assembly /// @solidity memory-safe-assembly @@ -180,7 +174,7 @@ abstract contract LSP0ERC725AccountCore is } /** - * @inheritdoc IERC725X + * @inheritdoc ERC725XCore * * @custom:requirements * - Can be only called by the {owner} or by an authorised address that pass the verification check performed on the owner. @@ -203,16 +197,16 @@ abstract contract LSP0ERC725AccountCore is emit ValueReceived(msg.sender, msg.value); } - address _owner = owner(); + address accountOwner = owner(); // If the caller is the owner perform execute directly - if (msg.sender == _owner) { + if (msg.sender == accountOwner) { return ERC725XCore._execute(operationType, target, value, data); } // If the caller is not the owner, call {lsp20VerifyCall} on the owner - // Depending on the magicValue returned, a second call is done after execution - bool verifyAfter = LSP20CallVerification._verifyCall(_owner); + // Depending on the returnedStatus, a second call is done after execution + bool verifyAfter = LSP20CallVerification._verifyCall(accountOwner); // Perform the execution bytes memory result = ERC725XCore._execute( @@ -224,14 +218,17 @@ abstract contract LSP0ERC725AccountCore is // if verifyAfter is true, Call {lsp20VerifyCallResult} on the owner if (verifyAfter) { - LSP20CallVerification._verifyCallResult(_owner, abi.encode(result)); + LSP20CallVerification._verifyCallResult( + accountOwner, + abi.encode(result) + ); } return result; } /** - * @inheritdoc IERC725X + * @inheritdoc ERC725XCore * * @custom:requirements * - The length of the parameters provided must be equal. @@ -240,6 +237,9 @@ abstract contract LSP0ERC725AccountCore is * - If the operation type is `CREATE` (1) or `CREATE2` (2), `target` must be `address(0)`. * - If the operation type is `STATICCALL` (3) or `DELEGATECALL` (4), `value` transfer is disallowed and must be 0. * + * @custom:warning + * - The `msg.value` should not be trusted for any method called within the batch with `operationType`: `DELEGATECALL` (4). + * * @custom:events * - {Executed} event for each call that uses under `operationType`: `CALL` (0), `STATICCALL` (3) and `DELEGATECALL` (4). (each iteration) * - {ContractCreated} event, when a contract is created under `operationType`: `CREATE` (1) and `CREATE2` (2) (each iteration) @@ -255,10 +255,10 @@ abstract contract LSP0ERC725AccountCore is emit ValueReceived(msg.sender, msg.value); } - address _owner = owner(); + address accountOwner = owner(); // If the caller is the owner perform execute directly - if (msg.sender == _owner) { + if (msg.sender == accountOwner) { return ERC725XCore._executeBatch( operationsType, @@ -269,8 +269,8 @@ abstract contract LSP0ERC725AccountCore is } // If the caller is not the owner, call {lsp20VerifyCall} on the owner - // Depending on the magicValue returned, a second call is done after execution - bool verifyAfter = LSP20CallVerification._verifyCall(_owner); + // Depending on the returnedStatus, a second call is done after execution + bool verifyAfter = LSP20CallVerification._verifyCall(accountOwner); // Perform the execution bytes[] memory results = ERC725XCore._executeBatch( @@ -283,7 +283,7 @@ abstract contract LSP0ERC725AccountCore is // if verifyAfter is true, Call {lsp20VerifyCallResult} on the owner if (verifyAfter) { LSP20CallVerification._verifyCallResult( - _owner, + accountOwner, abi.encode(results) ); } @@ -292,7 +292,7 @@ abstract contract LSP0ERC725AccountCore is } /** - * @inheritdoc IERC725Y + * @inheritdoc ERC725YCore * * @custom:requirements Can be only called by the {owner} or by an authorised address that pass the verification check performed on the owner. * @@ -308,28 +308,28 @@ abstract contract LSP0ERC725AccountCore is emit ValueReceived(msg.sender, msg.value); } - address _owner = owner(); + address accountOwner = owner(); // If the caller is the owner perform setData directly - if (msg.sender == _owner) { + if (msg.sender == accountOwner) { return _setData(dataKey, dataValue); } // If the caller is not the owner, call {lsp20VerifyCall} on the owner - // Depending on the magicValue returned, a second call is done after setting data - bool verifyAfter = _verifyCall(_owner); + // Depending on the returnedStatus, a second call is done after setting data + bool verifyAfter = _verifyCall(accountOwner); _setData(dataKey, dataValue); // If verifyAfter is true, Call {lsp20VerifyCallResult} on the owner // The setData function does not return, second parameter of {_verifyCallResult} will be empty if (verifyAfter) { - _verifyCallResult(_owner, ""); + _verifyCallResult(accountOwner, ""); } } /** - * @inheritdoc IERC725Y + * @inheritdoc ERC725YCore * * @custom:requirements Can be only called by the {owner} or by an authorised address that pass the verification check performed on the owner. * @@ -349,11 +349,15 @@ abstract contract LSP0ERC725AccountCore is revert ERC725Y_DataKeysValuesLengthMismatch(); } - address _owner = owner(); + if (dataKeys.length == 0) { + revert ERC725Y_DataKeysValuesEmptyArray(); + } + + address accountOwner = owner(); // If the caller is the owner perform setData directly - if (msg.sender == _owner) { - for (uint256 i = 0; i < dataKeys.length; ) { + if (msg.sender == accountOwner) { + for (uint256 i; i < dataKeys.length; ) { _setData(dataKeys[i], dataValues[i]); unchecked { @@ -365,10 +369,10 @@ abstract contract LSP0ERC725AccountCore is } // If the caller is not the owner, call {lsp20VerifyCall} on the owner - // Depending on the magicValue returned, a second call is done after setting data - bool verifyAfter = _verifyCall(_owner); + // Depending on the returnedStatus, a second call is done after setting data + bool verifyAfter = _verifyCall(accountOwner); - for (uint256 i = 0; i < dataKeys.length; ) { + for (uint256 i; i < dataKeys.length; ) { _setData(dataKeys[i], dataValues[i]); unchecked { @@ -379,7 +383,7 @@ abstract contract LSP0ERC725AccountCore is // If verifyAfter is true, Call {lsp20VerifyCallResult} on the owner // The setData function does not return, second parameter of {_verifyCallResult} will be empty if (verifyAfter) { - _verifyCallResult(_owner, ""); + _verifyCallResult(accountOwner, ""); } } @@ -416,7 +420,7 @@ abstract contract LSP0ERC725AccountCore is function universalReceiver( bytes32 typeId, bytes calldata receivedData - ) public payable virtual returns (bytes memory returnedValues) { + ) public payable virtual override returns (bytes memory returnedValues) { if (msg.value != 0) { emit ValueReceived(msg.sender, msg.value); } @@ -428,55 +432,49 @@ abstract contract LSP0ERC725AccountCore is bytes memory resultDefaultDelegate; if (lsp1DelegateValue.length >= 20) { - address universalReceiverDelegate = address( - bytes20(lsp1DelegateValue) - ); + address lsp1Delegate = address(bytes20(lsp1DelegateValue)); // Checking LSP1 InterfaceId support if ( - universalReceiverDelegate.supportsERC165InterfaceUnchecked( - _INTERFACEID_LSP1 + lsp1Delegate.supportsERC165InterfaceUnchecked( + _INTERFACEID_LSP1_DELEGATE ) ) { - // calling {universalReceiver} function on URD appending the caller and the value sent - resultDefaultDelegate = universalReceiverDelegate - .callUniversalReceiverWithCallerInfos( - typeId, - receivedData, + resultDefaultDelegate = ILSP1Delegate(lsp1Delegate) + .universalReceiverDelegate( msg.sender, - msg.value + msg.value, + typeId, + receivedData ); } } - // Generate the data key {_LSP1_UNIVERSAL_RECEIVER_DELEGATE_KEY + } + // Generate the data key {_LSP1_UNIVERSAL_RECEIVER_DELEGATE_PREFIX + } bytes32 lsp1typeIdDelegateKey = LSP2Utils.generateMappingKey( _LSP1_UNIVERSAL_RECEIVER_DELEGATE_PREFIX, bytes20(typeId) ); - // Query the ERC725Y storage with the data key {_LSP1_UNIVERSAL_RECEIVER_DELEGATE_KEY + } + // Query the ERC725Y storage with the data key {_LSP1_UNIVERSAL_RECEIVER_DELEGATE_PREFIX + } bytes memory lsp1TypeIdDelegateValue = _getData(lsp1typeIdDelegateKey); bytes memory resultTypeIdDelegate; if (lsp1TypeIdDelegateValue.length >= 20) { - address universalReceiverDelegate = address( - bytes20(lsp1TypeIdDelegateValue) - ); + address lsp1Delegate = address(bytes20(lsp1TypeIdDelegateValue)); // Checking LSP1 InterfaceId support if ( - universalReceiverDelegate.supportsERC165InterfaceUnchecked( - _INTERFACEID_LSP1 + lsp1Delegate.supportsERC165InterfaceUnchecked( + _INTERFACEID_LSP1_DELEGATE ) ) { - // calling {universalReceiver} function on URD appending the caller and the value sent - resultTypeIdDelegate = universalReceiverDelegate - .callUniversalReceiverWithCallerInfos( - typeId, - receivedData, + resultTypeIdDelegate = ILSP1Delegate(lsp1Delegate) + .universalReceiverDelegate( msg.sender, - msg.value + msg.value, + typeId, + receivedData ); } } @@ -526,7 +524,7 @@ abstract contract LSP0ERC725AccountCore is _inTransferOwnership = false; } else { // If the caller is not the owner, call {lsp20VerifyCall} on the owner - // Depending on the magicValue returned, a second call is done after transferring ownership + // Depending on the returnedStatus, a second call is done after transferring ownership bool verifyAfter = _verifyCall(currentOwner); // set the transfer ownership lock @@ -563,8 +561,20 @@ abstract contract LSP0ERC725AccountCore is */ function acceptOwnership() public virtual override NotInTransferOwnership { address previousOwner = owner(); + address pendingOwnerAddress = pendingOwner(); + + bool verifyAfter; - _acceptOwnership(); + if (msg.sender != pendingOwnerAddress) { + // If the caller is not the owner, call {lsp20VerifyCall} on the pending owner + // Depending on the successStatus returned, a second call is done after transferring ownership + verifyAfter = _verifyCall(pendingOwnerAddress); + + _setOwner(pendingOwnerAddress); + delete _pendingOwner; + } else { + _acceptOwnership(); + } // notify the previous owner if supports LSP1 previousOwner.tryNotifyUniversalReceiver( @@ -573,10 +583,16 @@ abstract contract LSP0ERC725AccountCore is ); // notify the pending owner if supports LSP1 - msg.sender.tryNotifyUniversalReceiver( + pendingOwnerAddress.tryNotifyUniversalReceiver( _TYPEID_LSP0_OwnershipTransferred_RecipientNotification, "" ); + + // If msg.sender != pendingOwnerAddress & verifyAfter is true, Call {lsp20VerifyCallResult} on the new owner + // The transferOwnership function does not return, second parameter of {_verifyCallResult} will be empty + if (verifyAfter) { + _verifyCallResult(pendingOwnerAddress, ""); + } } /** @@ -584,7 +600,7 @@ abstract contract LSP0ERC725AccountCore is * * @custom:requirements Can be only called by the {owner} or by an authorised address that pass the verification check performed on the owner. * - * @custom:danger Leaves the contract without an owner. Once ownership of the contract has been renounced, any functions that are restricted to be called by the owner will be permanently inaccessible, making these functions not callable anymore and unusable. + * @custom:danger Leaves the contract without an owner. Once ownership of the contract has been renounced, any functions that are restricted to be called by the owner or an address allowed by the owner will be permanently inaccessible, making these functions not callable anymore and unusable. * */ function renounceOwnership() @@ -592,16 +608,16 @@ abstract contract LSP0ERC725AccountCore is virtual override(LSP14Ownable2Step, OwnableUnset) { - address _owner = owner(); + address accountOwner = owner(); // If the caller is the owner perform renounceOwnership directly - if (msg.sender == _owner) { + if (msg.sender == accountOwner) { return LSP14Ownable2Step._renounceOwnership(); } // If the caller is not the owner, call {lsp20VerifyCall} on the owner - // Depending on the magicValue returned, a second call is done after transferring ownership - bool verifyAfter = _verifyCall(_owner); + // Depending on the returnedStatus, a second call is done after transferring ownership + bool verifyAfter = _verifyCall(accountOwner); address previousOwner = owner(); LSP14Ownable2Step._renounceOwnership(); @@ -616,7 +632,7 @@ abstract contract LSP0ERC725AccountCore is // 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(_owner, ""); + _verifyCallResult(accountOwner, ""); } } @@ -661,29 +677,29 @@ abstract contract LSP0ERC725AccountCore is * * 1. If the owner is an EOA, recovers an address from the hash and the signature provided: * - * - Returns the `magicValue` if the address recovered is the same as the owner, indicating that it was a valid signature. + * - Returns the `_ERC1271_SUCCESSVALUE` if the address recovered is the same as the owner, indicating that it was a valid signature. * - * - If the address is different, it returns the fail value indicating that the signature is not valid. + * - If the address is different, it returns the `_ERC1271_FAILVALUE` indicating that the signature is not valid. * * 2. If the owner is a smart contract, it forwards the call of {isValidSignature()} to the owner contract: * - * - If the contract fails or returns the fail value, the {isValidSignature()} on the account returns the fail value, indicating that the signature is not valid. + * - If the contract fails or returns the `_ERC1271_FAILVALUE`, the {isValidSignature()} on the account returns the `_ERC1271_FAILVALUE`, indicating that the signature is not valid. * - * - If the {isValidSignature()} on the owner returned the `magicValue`, the {isValidSignature()} on the account returns the `magicValue`, indicating that it's a valid signature. + * - If the {isValidSignature()} on the owner returned the `_ERC1271_SUCCESSVALUE`, the {isValidSignature()} on the account returns the `_ERC1271_SUCCESSVALUE`, indicating that it's a valid signature. * * @param dataHash The hash of the data to be validated. * @param signature A signature that can validate the previous parameter (Hash). * - * @return magicValue A `bytes4` value that indicates if the signature is valid or not. + * @return returnedStatus A `bytes4` value that indicates if the signature is valid or not. */ function isValidSignature( bytes32 dataHash, bytes memory signature - ) public view virtual returns (bytes4 magicValue) { + ) public view virtual override returns (bytes4 returnedStatus) { address _owner = owner(); // If owner is a contract - if (_owner.code.length > 0) { + if (_owner.code.length != 0) { (bool success, bytes memory result) = _owner.staticcall( abi.encodeWithSelector( IERC1271.isValidSignature.selector, @@ -694,9 +710,10 @@ abstract contract LSP0ERC725AccountCore is bool isValid = (success && result.length == 32 && - abi.decode(result, (bytes32)) == bytes32(_ERC1271_MAGICVALUE)); + abi.decode(result, (bytes32)) == + bytes32(_ERC1271_SUCCESSVALUE)); - return isValid ? _ERC1271_MAGICVALUE : _ERC1271_FAILVALUE; + return isValid ? _ERC1271_SUCCESSVALUE : _ERC1271_FAILVALUE; } // If owner is an EOA else { @@ -709,11 +726,11 @@ abstract contract LSP0ERC725AccountCore is return _ERC1271_FAILVALUE; // if recovering is successful and the recovered address matches the owner's address, - // return the ERC1271 magic value. Otherwise, return the ERC1271 fail value + // return the ERC1271 success value. Otherwise, return the ERC1271 fail value // matches the address of the owner, otherwise return fail value return recoveredAddress == _owner - ? _ERC1271_MAGICVALUE + ? _ERC1271_SUCCESSVALUE : _ERC1271_FAILVALUE; } } @@ -723,11 +740,22 @@ abstract contract LSP0ERC725AccountCore is /** * @dev Forwards the call to an extension mapped to a function selector. * - * Calls {_getExtension} to get the address of the extension mapped to the function selector being called on the account. If there is no extension, the `address(0)` will be returned. + * Calls {_getExtension} to get the address of the extension mapped to the function selector being + * called on the account. If there is no extension, the `address(0)` will be returned. + * + * Reverts if there is no extension for the function being called, except for the `bytes4(0)` function selector, which passes even if there is no extension for it. + * + * 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`. * - * Reverts if there is no extension for the function being called, except for the bytes4(0) function selector, which passes even if there is no extension for it. + * @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: * - * 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` + * ```solidity + * (bool success, bytes memory result) = extension.call{value: msg.value}( + * abi.encodePacked(callData, msg.sender, msg.value) + * ); + * ``` */ function _fallbackLSP17Extendable( bytes calldata callData diff --git a/contracts/LSP10ReceivedVaults/LSP10Utils.sol b/contracts/LSP10ReceivedVaults/LSP10Utils.sol index 869358bc7..0321eec00 100644 --- a/contracts/LSP10ReceivedVaults/LSP10Utils.sol +++ b/contracts/LSP10ReceivedVaults/LSP10Utils.sol @@ -7,12 +7,14 @@ import { } from "@erc725/smart-contracts/contracts/interfaces/IERC725Y.sol"; // libraries -import {BytesLib} from "solidity-bytes-utils/contracts/BytesLib.sol"; import {LSP2Utils} from "../LSP2ERC725YJSONSchema/LSP2Utils.sol"; // constants -import "../LSP10ReceivedVaults/LSP10Constants.sol"; -import "../LSP9Vault/LSP9Constants.sol"; +import {_INTERFACEID_LSP9} from "../LSP9Vault/LSP9Constants.sol"; +import { + _LSP10_VAULTS_MAP_KEY_PREFIX, + _LSP10_VAULTS_ARRAY_KEY +} from "../LSP10ReceivedVaults/LSP10Constants.sol"; /** * @title LSP10 Utility library. diff --git a/contracts/LSP11BasicSocialRecovery/LSP11BasicSocialRecoveryCore.sol b/contracts/LSP11BasicSocialRecovery/LSP11BasicSocialRecoveryCore.sol index 96c47b785..0ce82e03c 100644 --- a/contracts/LSP11BasicSocialRecovery/LSP11BasicSocialRecoveryCore.sol +++ b/contracts/LSP11BasicSocialRecovery/LSP11BasicSocialRecoveryCore.sol @@ -20,7 +20,17 @@ import { // constants import {ALL_REGULAR_PERMISSIONS} from "../LSP6KeyManager/LSP6Constants.sol"; import {_INTERFACEID_LSP11} from "./LSP11Constants.sol"; -import "./LSP11Errors.sol"; +import { + CallerIsNotGuardian, + GuardianAlreadyExist, + GuardianDoNotExist, + GuardiansNumberCannotGoBelowThreshold, + ThresholdCannotBeHigherThanGuardiansNumber, + SecretHashCannotBeZero, + AddressZeroNotAllowed, + ThresholdNotReachedForRecoverer, + WrongPlainSecret +} from "./LSP11Errors.sol"; /** * @title Core Implementation of LSP11-BasicSocialRecovery standard @@ -75,42 +85,68 @@ abstract contract LSP11BasicSocialRecoveryCore is /** * @inheritdoc ILSP11BasicSocialRecovery */ - function target() public view virtual returns (address) { + function target() public view virtual override returns (address) { return _target; } /** * @inheritdoc ILSP11BasicSocialRecovery */ - function getRecoveryCounter() public view virtual returns (uint256) { + function getRecoveryCounter() + public + view + virtual + override + returns (uint256) + { return _recoveryCounter; } /** * @inheritdoc ILSP11BasicSocialRecovery */ - function getGuardians() public view virtual returns (address[] memory) { + function getGuardians() + public + view + virtual + override + returns (address[] memory) + { return _guardians.values(); } /** * @inheritdoc ILSP11BasicSocialRecovery */ - function isGuardian(address _address) public view virtual returns (bool) { + function isGuardian( + address _address + ) public view virtual override returns (bool) { return _guardians.contains(_address); } /** * @inheritdoc ILSP11BasicSocialRecovery */ - function getGuardiansThreshold() public view virtual returns (uint256) { + function getGuardiansThreshold() + public + view + virtual + override + returns (uint256) + { return _guardiansThreshold; } /** * @inheritdoc ILSP11BasicSocialRecovery */ - function getRecoverySecretHash() public view virtual returns (bytes32) { + function getRecoverySecretHash() + public + view + virtual + override + returns (bytes32) + { return _recoverySecretHash; } @@ -119,14 +155,16 @@ abstract contract LSP11BasicSocialRecoveryCore is */ function getGuardianChoice( address guardian - ) public view virtual returns (address) { + ) public view virtual override returns (address) { return _guardiansChoice[_recoveryCounter][guardian]; } /** * @inheritdoc ILSP11BasicSocialRecovery */ - function addGuardian(address newGuardian) public virtual onlyOwner { + function addGuardian( + address newGuardian + ) public virtual override onlyOwner { if (_guardians.contains(newGuardian)) revert GuardianAlreadyExist(newGuardian); @@ -137,7 +175,9 @@ abstract contract LSP11BasicSocialRecoveryCore is /** * @inheritdoc ILSP11BasicSocialRecovery */ - function removeGuardian(address existingGuardian) public virtual onlyOwner { + function removeGuardian( + address existingGuardian + ) public virtual override onlyOwner { if (!_guardians.contains(existingGuardian)) revert GuardianDoNotExist(existingGuardian); if (_guardians.length() == _guardiansThreshold) @@ -152,7 +192,7 @@ abstract contract LSP11BasicSocialRecoveryCore is */ function setGuardiansThreshold( uint256 newThreshold - ) public virtual onlyOwner { + ) public virtual override onlyOwner { if (newThreshold > _guardians.length()) revert ThresholdCannotBeHigherThanGuardiansNumber( newThreshold, @@ -169,7 +209,7 @@ abstract contract LSP11BasicSocialRecoveryCore is */ function setRecoverySecretHash( bytes32 newRecoverSecretHash - ) public virtual onlyOwner { + ) public virtual override onlyOwner { if (newRecoverSecretHash == bytes32(0)) revert SecretHashCannotBeZero(); _recoverySecretHash = newRecoverSecretHash; @@ -181,7 +221,7 @@ abstract contract LSP11BasicSocialRecoveryCore is */ function selectNewController( address addressSelected - ) public virtual onlyGuardians { + ) public virtual override onlyGuardians { uint256 currentRecoveryCounter = _recoveryCounter; _guardiansChoice[currentRecoveryCounter][msg.sender] = addressSelected; @@ -199,7 +239,7 @@ abstract contract LSP11BasicSocialRecoveryCore is address recoverer, string memory plainSecret, bytes32 newSecretHash - ) public virtual { + ) public virtual override { // caching storage variables uint256 currentRecoveryCounter = _recoveryCounter; address[] memory guardians = _guardians.values(); diff --git a/contracts/LSP11BasicSocialRecovery/LSP11Constants.sol b/contracts/LSP11BasicSocialRecovery/LSP11Constants.sol index 65b9a2cf9..66ceb1657 100644 --- a/contracts/LSP11BasicSocialRecovery/LSP11Constants.sol +++ b/contracts/LSP11BasicSocialRecovery/LSP11Constants.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: CC0-1.0 +// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.0; // --- ERC165 interface ids diff --git a/contracts/LSP11BasicSocialRecovery/LSP11Errors.sol b/contracts/LSP11BasicSocialRecovery/LSP11Errors.sol index 599289aad..6c6951c2b 100644 --- a/contracts/LSP11BasicSocialRecovery/LSP11Errors.sol +++ b/contracts/LSP11BasicSocialRecovery/LSP11Errors.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: CC0-1.0 +// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.0; // --- Errors diff --git a/contracts/LSP14Ownable2Step/ILSP14Ownable2Step.sol b/contracts/LSP14Ownable2Step/ILSP14Ownable2Step.sol index f41b2845b..00ded5eaf 100644 --- a/contracts/LSP14Ownable2Step/ILSP14Ownable2Step.sol +++ b/contracts/LSP14Ownable2Step/ILSP14Ownable2Step.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: MIT +// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.4; /** @@ -59,7 +59,7 @@ interface ILSP14Ownable2Step { * @dev Transfer ownership of the contract from the current {owner()} to the {pendingOwner()}. * * Once this function is called: - * - The current {owner()} will loose access to the functions restricted to the {owner()} only. + * - The current {owner()} will lose access to the functions restricted to the {owner()} only. * - The {pendingOwner()} will gain access to the functions restricted to the {owner()} only. * * @notice `msg.sender` is accepting ownership of contract: `address(this)`. diff --git a/contracts/LSP14Ownable2Step/LSP14Constants.sol b/contracts/LSP14Ownable2Step/LSP14Constants.sol index 130d41a46..7e889eb7f 100644 --- a/contracts/LSP14Ownable2Step/LSP14Constants.sol +++ b/contracts/LSP14Ownable2Step/LSP14Constants.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: MIT +// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.4; bytes4 constant _INTERFACEID_LSP14 = 0x94be5999; diff --git a/contracts/LSP14Ownable2Step/LSP14Errors.sol b/contracts/LSP14Ownable2Step/LSP14Errors.sol index 45c8526c5..2544f9f44 100644 --- a/contracts/LSP14Ownable2Step/LSP14Errors.sol +++ b/contracts/LSP14Ownable2Step/LSP14Errors.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: MIT +// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.4; /** @@ -8,7 +8,7 @@ pragma solidity ^0.8.4; * @param renounceOwnershipStart The start timestamp when one can confirm the renouncement of ownership. * @param renounceOwnershipEnd The end timestamp when one can confirm the renouncement of ownership. */ -error NotInRenounceOwnershipInterval( +error LSP14NotInRenounceOwnershipInterval( uint256 renounceOwnershipStart, uint256 renounceOwnershipEnd ); @@ -17,10 +17,16 @@ error NotInRenounceOwnershipInterval( * @dev Reverts when trying to transfer ownership to the `address(this)`. * @notice Cannot transfer ownership to the address of the contract itself. */ -error CannotTransferOwnershipToSelf(); +error LSP14CannotTransferOwnershipToSelf(); /** * @dev Reverts when pending owner accept ownership in the same transaction of transferring ownership. * @notice Cannot accept ownership in the same transaction with {transferOwnership(...)}. */ error LSP14MustAcceptOwnershipInSeparateTransaction(); + +/** + * @dev Reverts when the `caller` that is trying to accept ownership of the contract is not the pending owner. + * @param caller The address that tried to accept ownership. + */ +error LSP14CallerNotPendingOwner(address caller); diff --git a/contracts/LSP14Ownable2Step/LSP14Ownable2Step.sol b/contracts/LSP14Ownable2Step/LSP14Ownable2Step.sol index e66a30632..124eb1a75 100644 --- a/contracts/LSP14Ownable2Step/LSP14Ownable2Step.sol +++ b/contracts/LSP14Ownable2Step/LSP14Ownable2Step.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: MIT +// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.4; // interfaces @@ -13,7 +13,12 @@ import { import {LSP1Utils} from "../LSP1UniversalReceiver/LSP1Utils.sol"; // errors -import "./LSP14Errors.sol"; +import { + LSP14CallerNotPendingOwner, + LSP14MustAcceptOwnershipInSeparateTransaction, + LSP14CannotTransferOwnershipToSelf, + LSP14NotInRenounceOwnershipInterval +} from "./LSP14Errors.sol"; // constants import { @@ -45,12 +50,12 @@ abstract contract LSP14Ownable2Step is ILSP14Ownable2Step, OwnableUnset { /** * @dev The block number saved when initiating the process of renouncing ownerhsip. */ - uint256 private _renounceOwnershipStartedAt; + uint256 internal _renounceOwnershipStartedAt; /** * @dev see {pendingOwner()} */ - address private _pendingOwner; + address internal _pendingOwner; /** * @dev The boolean that indicates whether the contract is in an active ownership transfer phase @@ -72,7 +77,7 @@ abstract contract LSP14Ownable2Step is ILSP14Ownable2Step, OwnableUnset { * * @custom:info If no ownership transfer is in progress, the pendingOwner will be `address(0).`. */ - function pendingOwner() public view virtual returns (address) { + function pendingOwner() public view virtual override returns (address) { return _pendingOwner; } @@ -106,7 +111,7 @@ abstract contract LSP14Ownable2Step is ILSP14Ownable2Step, OwnableUnset { * * @custom:requirements This function can only be called by the {pendingOwner()}. */ - function acceptOwnership() public virtual NotInTransferOwnership { + function acceptOwnership() public virtual override NotInTransferOwnership { address previousOwner = owner(); _acceptOwnership(); @@ -154,7 +159,8 @@ abstract contract LSP14Ownable2Step is ILSP14Ownable2Step, OwnableUnset { * @custom:requirements `newOwner` cannot be the address of the contract itself. */ function _transferOwnership(address newOwner) internal virtual { - if (newOwner == address(this)) revert CannotTransferOwnershipToSelf(); + if (newOwner == address(this)) + revert LSP14CannotTransferOwnershipToSelf(); _pendingOwner = newOwner; delete _renounceOwnershipStartedAt; @@ -164,10 +170,8 @@ abstract contract LSP14Ownable2Step is ILSP14Ownable2Step, OwnableUnset { * @dev Set the pending owner of the contract as the new owner. */ function _acceptOwnership() internal virtual { - require( - msg.sender == pendingOwner(), - "LSP14: caller is not the pendingOwner" - ); + if (msg.sender != pendingOwner()) + revert LSP14CallerNotPendingOwner(msg.sender); _setOwner(msg.sender); delete _pendingOwner; @@ -196,7 +200,7 @@ abstract contract LSP14Ownable2Step is ILSP14Ownable2Step, OwnableUnset { } if (currentBlock < confirmationPeriodStart) { - revert NotInRenounceOwnershipInterval( + revert LSP14NotInRenounceOwnershipInterval( confirmationPeriodStart, confirmationPeriodEnd ); diff --git a/contracts/LSP16UniversalFactory/LSP16UniversalFactory.sol b/contracts/LSP16UniversalFactory/LSP16UniversalFactory.sol index d0606af24..7c7f469ab 100644 --- a/contracts/LSP16UniversalFactory/LSP16UniversalFactory.sol +++ b/contracts/LSP16UniversalFactory/LSP16UniversalFactory.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: MIT +// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.4; // libraries @@ -8,13 +8,13 @@ import {Clones} from "@openzeppelin/contracts/proxy/Clones.sol"; // errors /** - * @dev Reverts when there is no revert reason bubbled up by the contract created when initializing + * @notice Couldn't initialize the contract. + * @dev Reverts when there is no revert reason bubbled up by the created contract when initializing */ error ContractInitializationFailed(); /** - * @dev Reverts when msg.value sent to {deployCreate2AndInitialize} function is not equal to the sum of the - * `initializeCalldataMsgValue` and `constructorMsgValue` + * @dev Reverts when `msg.value` sent to {deployCreate2AndInitialize(..)} function is not equal to the sum of the `initializeCalldataMsgValue` and `constructorMsgValue` */ error InvalidValueSum(); @@ -61,16 +61,17 @@ contract LSP16UniversalFactory { bytes private constant _EMPTY_BYTE = ""; /** - * @dev Emitted whenever a contract is created - * @param contractCreated The address of the contract created - * @param providedSalt The salt provided by the deployer, which will be used to generate the final salt - * that will be used by the `CREATE2` opcode for contract deployment - * @param generatedSalt The salt used by the `CREATE2` opcode for contract deployment - * @param initialized The Boolean that specifies if the contract must be initialized or not - * @param initializeCalldata The bytes provided as initializeCalldata (Empty string when `initialized` is set to false) + * @notice Contract created. Contract address: `createdContract`. + * @dev Emitted whenever a contract is created. + * + * @param createdContract The address of the contract created. + * @param providedSalt The salt provided by the deployer, which will be used to generate the final salt that will be used by the `CREATE2` opcode for contract deployment. + * @param generatedSalt The salt used by the `CREATE2` opcode for contract deployment. + * @param initialized The Boolean that specifies if the contract must be initialized or not. + * @param initializeCalldata The bytes provided as initializeCalldata (Empty string when `initialized` is set to false). */ event ContractCreated( - address indexed contractCreated, + address indexed createdContract, bytes32 indexed providedSalt, bytes32 generatedSalt, bool indexed initialized, @@ -78,24 +79,20 @@ contract LSP16UniversalFactory { ); /** - * @dev Deploys a contract using the CREATE2 opcode. The address where the contract will be deployed - * can be known in advance via the {computeAddress} function. + * @notice Contract deployed. Salt, `providedSalt`, used. + * + * @dev Deploys a contract using the CREATE2 opcode. The address where the contract will be deployed can be known in advance via the {computeAddress} function. * * This function deploys contracts without initialization (external call after deployment). * - * The `providedSalt` parameter is not used directly as the salt by the CREATE2 opcode. Instead, it is hashed with - * keccak256: `keccak256(abi.encodePacked(false, providedSalt))`. See {generateSalt} function for more details. + * The `providedSalt` parameter is not used directly as the salt by the CREATE2 opcode. Instead, it is hashed with keccak256: `keccak256(abi.encodePacked(false, providedSalt))`. See {generateSalt} function for more details. * - * Using the same `byteCode` and `providedSalt` multiple times will revert, as the contract cannot be deployed - * twice at the same address. + * Using the same `byteCode` and `providedSalt` multiple times will revert, as the contract cannot be deployed twice at the same address. * - * If the constructor of the contract to deploy is payable, value can be sent to this function to fund - * the created contract. However, sending value to this function while the constructor is not payable will - * result in a revert. + * If the constructor of the contract to deploy is payable, value can be sent to this function to fund the created contract. However, sending value to this function while the constructor is not payable will result in a revert. * * @param byteCode The bytecode of the contract to be deployed - * @param providedSalt The salt provided by the deployer, which will be used to generate the final salt - * that will be used by the `CREATE2` opcode for contract deployment + * @param providedSalt The salt provided by the deployer, which will be used to generate the final salt that will be used by the `CREATE2` opcode for contract deployment * * @return The address of the deployed contract */ @@ -121,28 +118,22 @@ contract LSP16UniversalFactory { } /** - * @dev Deploys a contract using the CREATE2 opcode. The address where the contract will be deployed - * can be known in advance via the {computeAddress} function. + * @notice Contract deployed. Salt, `providedSalt`, used. + * + * @dev Deploys a contract using the CREATE2 opcode. The address where the contract will be deployed can be known in advance via the {computeAddress} function. * * This function deploys contracts with initialization (external call after deployment). * - * The `providedSalt` parameter is not used directly as the salt by the CREATE2 opcode. Instead, it is hashed with - * keccak256: `keccak256(abi.encodePacked(true, initializeCalldata, providedSalt))`. - * See {generateSalt} function for more details. + * The `providedSalt` parameter is not used directly as the salt by the CREATE2 opcode. Instead, it is hashed with keccak256: `keccak256(abi.encodePacked(true, initializeCalldata, providedSalt))`. See {generateSalt} function for more details. * - * Using the same `byteCode`, `providedSalt` and `initializeCalldata` multiple times will revert, as the - * contract cannot be deployed twice at the same address. + * Using the same `byteCode`, `providedSalt` and `initializeCalldata` multiple times will revert, as the contract cannot be deployed twice at the same address. * - * If the constructor or the initialize function of the contract to deploy is payable, value can be sent along - * with the deployment/initialization to fund the created contract. However, sending value to this function while - * the constructor/initialize function is not payable will result in a revert. + * If the constructor or the initialize function of the contract to deploy is payable, value can be sent along with the deployment/initialization to fund the created contract. However, sending value to this function while the constructor/initialize function is not payable will result in a revert. * - * Will revert if the `msg.value` sent to the function is not equal to the sum of `constructorMsgValue` and - * `initializeCalldataMsgValue`. + * Will revert if the `msg.value` sent to the function is not equal to the sum of `constructorMsgValue` and `initializeCalldataMsgValue`. * * @param byteCode The bytecode of the contract to be deployed - * @param providedSalt The salt provided by the deployer, which will be used to generate the final salt - * that will be used by the `CREATE2` opcode for contract deployment + * @param providedSalt The salt provided by the deployer, which will be used to generate the final salt that will be used by the `CREATE2` opcode for contract deployment * @param initializeCalldata The calldata to be executed on the created contract * @param constructorMsgValue The value sent to the contract during deployment * @param initializeCalldataMsgValue The value sent to the contract during initialization @@ -186,24 +177,22 @@ contract LSP16UniversalFactory { } /** - * @dev Deploys an ERC1167 minimal proxy contract using the CREATE2 opcode. The address where the contract will be deployed - * can be known in advance via the {computeERC1167Address} function. + * @notice Proxy deployed. Salt, `providedSalt`, used. + * + * @dev Deploys an ERC1167 minimal proxy contract using the CREATE2 opcode. The address where the contract will be deployed can be known in advance via the {computeERC1167Address} function. * * This function deploys contracts without initialization (external call after deployment). * - * The `providedSalt` parameter is not used directly as the salt by the CREATE2 opcode. Instead, it is hashed with - * keccak256: `keccak256(abi.encodePacked(false, providedSalt))`. See {generateSalt} function for more details. + * The `providedSalt` parameter is not used directly as the salt by the CREATE2 opcode. Instead, it is hashed with keccak256: `keccak256(abi.encodePacked(false, providedSalt))`. See {generateSalt} function for more details. * * See {generateSalt} function for more details. * - * Using the same `implementationContract` and `providedSalt` multiple times will revert, as the contract cannot be deployed - * twice at the same address. + * Using the same `implementationContract` and `providedSalt` multiple times will revert, as the contract cannot be deployed twice at the same address. * * Sending value to the contract created is not possible since the constructor of the ERC1167 minimal proxy is not payable. * * @param implementationContract The contract address to use as the base implementation behind the proxy that will be deployed - * @param providedSalt The salt provided by the deployer, which will be used to generate the final salt - * that will be used by the `CREATE2` opcode for contract deployment + * @param providedSalt The salt provided by the deployer, which will be used to generate the final salt that will be used by the `CREATE2` opcode for contract deployment * * @return The address of the minimal proxy deployed */ @@ -229,25 +218,22 @@ contract LSP16UniversalFactory { } /** + * @notice Proxy deployed & initialized. Salt, `providedSalt`, used. + * * @dev Deploys an ERC1167 minimal proxy contract using the CREATE2 opcode. The address where the contract will be deployed * can be known in advance via the {computeERC1167Address} function. * * This function deploys contracts with initialization (external call after deployment). * - * The `providedSalt` parameter is not used directly as the salt by the CREATE2 opcode. Instead, it is hashed with - * keccak256: `keccak256(abi.encodePacked(true, initializeCalldata, providedSalt))`. + * The `providedSalt` parameter is not used directly as the salt by the CREATE2 opcode. Instead, it is hashed with keccak256: `keccak256(abi.encodePacked(true, initializeCalldata, providedSalt))`. * See {generateSalt} function for more details. * - * Using the same `implementationContract`, `providedSalt` and `initializeCalldata` multiple times will revert, as the - * contract cannot be deployed twice at the same address. + * Using the same `implementationContract`, `providedSalt` and `initializeCalldata` multiple times will revert, as the contract cannot be deployed twice at the same address. * - * If the initialize function of the contract to deploy is payable, value can be sent along to fund the created - * contract while initializing. However, sending value to this function while the initialize function is not - * payable will result in a revert. + * If the initialize function of the contract to deploy is payable, value can be sent along to fund the created contract while initializing. However, sending value to this function while the initialize function is not payable will result in a revert. * * @param implementationContract The contract address to use as the base implementation behind the proxy that will be deployed - * @param providedSalt The salt provided by the deployer, which will be used to generate the final salt - * that will be used by the `CREATE2` opcode for contract deployment + * @param providedSalt The salt provided by the deployer, which will be used to generate the final salt that will be used by the `CREATE2` opcode for contract deployment * @param initializeCalldata The calldata to be executed on the created contract * * @return The address of the minimal proxy deployed @@ -285,14 +271,12 @@ contract LSP16UniversalFactory { /** * @dev Computes the address of a contract to be deployed using CREATE2, based on the input parameters. - * Any change in one of these parameters will result in a different address. When the `initializable` - * boolean is set to `false`, `initializeCalldata` will not affect the function output. + * + * Any change in one of these parameters will result in a different address. When the `initializable` boolean is set to `false`, `initializeCalldata` will not affect the function output. * * @param byteCodeHash The keccak256 hash of the bytecode to be deployed - * @param providedSalt The salt provided by the deployer, which will be used to generate the final salt - * that will be used by the `CREATE2` opcode for contract deployment - * @param initializable A boolean that indicates whether an external call should be made to initialize the - * contract after deployment + * @param providedSalt The salt provided by the deployer, which will be used to generate the final salt that will be used by the `CREATE2` opcode for contract deployment + * @param initializable A boolean that indicates whether an external call should be made to initialize the contract after deployment * @param initializeCalldata The calldata to be executed on the created contract if `initializable` is set to `true` * * @return The address where the contract will be deployed @@ -313,14 +297,12 @@ contract LSP16UniversalFactory { /** * @dev Computes the address of an ERC1167 proxy contract based on the input parameters. - * Any change in one of these parameters will result in a different address. When the `initializable` - * boolean is set to `false`, `initializeCalldata` will not affect the function output. + * + * Any change in one of these parameters will result in a different address. When the `initializable` boolean is set to `false`, `initializeCalldata` will not affect the function output. * * @param implementationContract The contract to create a clone of according to ERC1167 - * @param providedSalt The salt provided by the deployer, which will be used to generate the final salt - * that will be used by the `CREATE2` opcode for contract deployment - * @param initializable A boolean that indicates whether an external call should be made to initialize the - * proxy contract after deployment + * @param providedSalt The salt provided by the deployer, which will be used to generate the final salt that will be used by the `CREATE2` opcode for contract deployment + * @param initializable A boolean that indicates whether an external call should be made to initialize the proxy contract after deployment * @param initializeCalldata The calldata to be executed on the created contract if `initializable` is set to `true` * * @return The address where the ERC1167 proxy contract will be deployed @@ -344,61 +326,48 @@ contract LSP16UniversalFactory { } /** - * @dev Generates the salt used to deploy the contract by hashing the following parameters (concatenated - * together) with keccak256: - * - * - the `providedSalt` - * - the `initializable` boolean - * - the `initializeCalldata`, only if the contract is initializable (the `initializable` boolean is set to `true`) - * - * The `providedSalt` parameter is not used directly as the salt by the CREATE2 opcode. Instead, it is - * used along with these parameters: - * - `initializable` boolean - * - `initializeCalldata` (when the contract is initializable and `initializable` is set to `true`). - * These three parameters are concatenated together and hashed to generate the final salt for CREATE2. + * @dev Generates the salt used to deploy the contract by hashing the following parameters (concatenated together) with keccak256: + * 1. the `providedSalt` + * 2. the `initializable` boolean + * 3. the `initializeCalldata`, only if the contract is initializable (the `initializable` boolean is set to `true`) * - * This approach ensures that in order to reproduce an initializable contract at the same address on another chain, - * not only the `providedSalt` is required to be the same, but also the initialize parameters within the `initializeCalldata` - * must also be the same. + * - The `providedSalt` parameter is not used directly as the salt by the CREATE2 opcode. Instead, it is used along with these parameters: + * 1. `initializable` boolean + * 2. `initializeCalldata` (when the contract is initializable and `initializable` is set to `true`). * - * This maintains consistent deployment behaviour. Users are required to initialize contracts with the same parameters across - * different chains to ensure contracts are deployed at the same address across different chains. + * - This approach ensures that in order to reproduce an initializable contract at the same address on another chain, not only the `providedSalt` is required to be the same, but also the initialize parameters within the `initializeCalldata` must also be the same. This maintains consistent deployment behaviour. Users are required to initialize contracts with the same parameters across different chains to ensure contracts are deployed at the same address across different chains. * - * ----------- - * Example (for initializable contracts) + * 1. Example (for initializable contracts) * - * For an existing contract A on chain 1 owned by X, to replicate the same contract at the same address with + * - For an existing contract A on chain 1 owned by X, to replicate the same contract at the same address with * the same owner X on chain 2, the salt used to generate the address should include the initializeCalldata * that assigns X as the owner of contract A. * - * For instance, if another user, Y, tries to deploy the contract at the same address + * - For instance, if another user, Y, tries to deploy the contract at the same address * on chain 2 using the same providedSalt, but with a different initializeCalldata to make Y the owner instead of X, * the generated address would be different, preventing Y from deploying the contract with different ownership * at the same address. - * ----------- * - * However, for non-initializable contracts, if the constructor has arguments that specify the deployment behavior, they + * - However, for non-initializable contracts, if the constructor has arguments that specify the deployment behavior, they * will be included in the bytecode. Any change in the constructor arguments will lead to a different contract's bytecode * which will result in a different address on other chains. * - * ----------- - * Example (for non-initializable contracts) + * 2. Example (for non-initializable contracts) * - * If a contract is deployed with specific constructor arguments on chain 1, these arguments are embedded within the bytecode. + * - If a contract is deployed with specific constructor arguments on chain 1, these arguments are embedded within the bytecode. * For instance, if contract B is deployed with a specific `tokenName` and `tokenSymbol` on chain 1, and a user wants to deploy * the same contract with the same `tokenName` and `tokenSymbol` on chain 2, they must use the same constructor arguments to * produce the same bytecode. This ensures that the same deployment behaviour is maintained across different chains, * as long as the same bytecode is used. * - * If another user Z, tries to deploy the same contract B at the same address on chain 2 using the same `providedSalt` + * - If another user Z, tries to deploy the same contract B at the same address on chain 2 using the same `providedSalt` * but different constructor arguments (a different `tokenName` and/or `tokenSymbol`), the generated address will be different. * This prevents user Z from deploying the contract with different constructor arguments at the same address on chain 2. - * ----------- * - * The providedSalt was hashed to produce the salt used by CREATE2 opcode to prevent users from deploying initializable contracts + * - The providedSalt was hashed to produce the salt used by CREATE2 opcode to prevent users from deploying initializable contracts * using non-initializable functions such as {deployCreate2} without having the initialization call. * - * In other words, if the providedSalt was not hashed and was used as it is as the salt by the CREATE2 opcode, malicious users + * - In other words, if the providedSalt was not hashed and was used as it is as the salt by the CREATE2 opcode, malicious users * can check the generated salt used for the already deployed initializable contract on chain 1, and deploy the contract * from {deployCreate2} function on chain 2, with passing the generated salt of the deployed contract as providedSalt * that will produce the same address but without the initialization, where the malicious user can initialize after. diff --git a/contracts/LSP17ContractExtension/LSP17Errors.sol b/contracts/LSP17ContractExtension/LSP17Errors.sol index 3a2d7da49..a39ef197c 100644 --- a/contracts/LSP17ContractExtension/LSP17Errors.sol +++ b/contracts/LSP17ContractExtension/LSP17Errors.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: MIT +// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.4; /** diff --git a/contracts/LSP17ContractExtension/LSP17Extendable.sol b/contracts/LSP17ContractExtension/LSP17Extendable.sol index 075f53711..f2b70b873 100644 --- a/contracts/LSP17ContractExtension/LSP17Extendable.sol +++ b/contracts/LSP17ContractExtension/LSP17Extendable.sol @@ -11,7 +11,7 @@ import { import {_INTERFACEID_LSP17_EXTENDABLE} from "./LSP17Constants.sol"; // errors -import "./LSP17Errors.sol"; +import {NoExtensionFoundForFunctionSelector} from "./LSP17Errors.sol"; /** * @title Module to add more functionalities to a contract using extensions. @@ -70,18 +70,21 @@ abstract contract LSP17Extendable is ERC165 { * @dev Forwards the call to an extension mapped to a function selector. * * Calls {_getExtension} to get the address of the extension mapped to the function selector being - * called on the account. If there is no extension, the address(0) will be returned. + * called on the account. If there is no extension, the `address(0)` will be returned. * * Reverts if there is no extension for the function being called. * * 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} + * `CALL` opcode, passing the `msg.data` appended with the 20 bytes of the {msg.sender} and 32 bytes of the `msg.value`. * - * Because the function uses assembly {return()/revert()} to terminate the call, it cannot be - * called before other codes in fallback(). + * @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: * - * Otherwise, the codes after _fallbackLSP17Extendable() may never be reached. + * ```solidity + * (bool success, bytes memory result) = extension.call{value: msg.value}( + * abi.encodePacked(callData, msg.sender, msg.value) + * ); + * ``` */ function _fallbackLSP17Extendable( bytes calldata callData diff --git a/contracts/LSP17ContractExtension/LSP17Extension.sol b/contracts/LSP17ContractExtension/LSP17Extension.sol index d33c0eed8..dafbe52bc 100644 --- a/contracts/LSP17ContractExtension/LSP17Extension.sol +++ b/contracts/LSP17ContractExtension/LSP17Extension.sol @@ -26,8 +26,8 @@ abstract contract LSP17Extension is ERC165 { } /** - * @dev Returns the original msg.data passed to the extendable contract - * without the appended msg.sender and msg.value + * @dev Returns the original `msg.data` passed to the extendable contract + * without the appended `msg.sender` and `msg.value`. */ function _extendableMsgData() internal @@ -39,7 +39,7 @@ abstract contract LSP17Extension is ERC165 { } /** - * @dev Returns the original msg.sender calling the extendable contract + * @dev Returns the original `msg.sender` calling the extendable contract. */ function _extendableMsgSender() internal view virtual returns (address) { return @@ -49,7 +49,7 @@ abstract contract LSP17Extension is ERC165 { } /** - * @dev Returns the original msg.value sent to the extendable contract + * @dev Returns the original `msg.value` sent to the extendable contract. */ function _extendableMsgValue() internal view virtual returns (uint256) { return uint256(bytes32(msg.data[msg.data.length - 32:])); diff --git a/contracts/LSP17Extensions/Extension4337.sol b/contracts/LSP17Extensions/Extension4337.sol new file mode 100644 index 000000000..133abe0ad --- /dev/null +++ b/contracts/LSP17Extensions/Extension4337.sol @@ -0,0 +1,101 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.12; + +// interfaces +import {IAccount} from "@account-abstraction/contracts/interfaces/IAccount.sol"; +import { + IERC725Y +} from "@erc725/smart-contracts/contracts/interfaces/IERC725Y.sol"; +import { + ILSP20CallVerifier +} from "../LSP20CallVerification/ILSP20CallVerifier.sol"; + +// modules +import {LSP14Ownable2Step} from "../LSP14Ownable2Step/LSP14Ownable2Step.sol"; +import {LSP17Extension} from "../LSP17ContractExtension/LSP17Extension.sol"; + +// librairies +import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; +import {LSP6Utils} from "../LSP6KeyManager/LSP6Utils.sol"; + +// constants +import { + UserOperation +} from "@account-abstraction/contracts/interfaces/UserOperation.sol"; + +contract Extension4337 is LSP17Extension, IAccount { + using ECDSA for bytes32; + using LSP6Utils for *; + + address internal immutable _ENTRY_POINT; + + // permission needed to be able to use this extension + bytes32 internal constant _4337_PERMISSION = + 0x0000000000000000000000000000000000000000000000000000000000800000; + + // error code returned when signature or permission validation fails + uint256 internal constant _SIG_VALIDATION_FAILED = 1; + + constructor(address entryPoint_) { + _ENTRY_POINT = entryPoint_; + } + + /** + * @inheritdoc IAccount + */ + function validateUserOp( + UserOperation calldata userOp, + bytes32 userOpHash, + uint256 /* missingAccountFunds */ + ) external returns (uint256) { + require( + _extendableMsgSender() == _ENTRY_POINT, + "Only EntryPoint contract can call this" + ); + + // recover initiator of the tx from the signature + bytes32 hash = userOpHash.toEthSignedMessageHash(); + address recovered = hash.recover(userOp.signature); + + // verify that the recovered address has the _4337_PERMISSION + if ( + !LSP6Utils.hasPermission( + IERC725Y(msg.sender).getPermissionsFor(recovered), + _4337_PERMISSION + ) + ) { + return _SIG_VALIDATION_FAILED; + } + + // retrieve owner from caller + address owner = LSP14Ownable2Step(msg.sender).owner(); + + // verify that the recovered address can execute the userOp.callData + bytes4 returnedStatus = ILSP20CallVerifier(owner).lsp20VerifyCall({ + requestor: _ENTRY_POINT, + target: msg.sender, + caller: recovered, + value: 0, + callData: userOp.callData + }); + + // if the returnedStatus is a value different than the success value, return signature validation failed + if ( + bytes3(returnedStatus) != + bytes3(ILSP20CallVerifier.lsp20VerifyCall.selector) + ) { + return _SIG_VALIDATION_FAILED; + } + + // if sig validation passed, return 0 + return 0; + } + + /** + * @dev Get the address of the Entry Point contract that will execute the user operation. + * @return The address of the EntryPoint contract + */ + function entryPoint() public view returns (address) { + return _ENTRY_POINT; + } +} diff --git a/contracts/LSP17Extensions/OnERC721ReceivedExtension.sol b/contracts/LSP17Extensions/OnERC721ReceivedExtension.sol new file mode 100644 index 000000000..47f6be65e --- /dev/null +++ b/contracts/LSP17Extensions/OnERC721ReceivedExtension.sol @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.4; + +import { + ERC721Holder +} from "@openzeppelin/contracts/token/ERC721/utils/ERC721Holder.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 { + +} diff --git a/contracts/LSP1UniversalReceiver/ILSP1UniversalReceiver.sol b/contracts/LSP1UniversalReceiver/ILSP1UniversalReceiver.sol index 4e8ba6834..215bc5439 100644 --- a/contracts/LSP1UniversalReceiver/ILSP1UniversalReceiver.sol +++ b/contracts/LSP1UniversalReceiver/ILSP1UniversalReceiver.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: CC0-1.0 +// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.4; /** @@ -8,7 +8,6 @@ pragma solidity ^0.8.4; interface ILSP1UniversalReceiver { /** * @dev Emitted when the {universalReceiver} function was called with a specific `typeId` and some `receivedData` -s * @notice Address `from` called the `universalReceiver(...)` function while sending `value` LYX. Notification type (typeId): `typeId` - Data received: `receivedData`. * * @param from The address of the EOA or smart contract that called the {universalReceiver(...)} function. diff --git a/contracts/LSP1UniversalReceiver/ILSP1UniversalReceiverDelegate.sol b/contracts/LSP1UniversalReceiver/ILSP1UniversalReceiverDelegate.sol new file mode 100644 index 000000000..423686b7c --- /dev/null +++ b/contracts/LSP1UniversalReceiver/ILSP1UniversalReceiverDelegate.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.4; + +/** + * @title Interface of the LSP1 - Universal Receiver Delegate standard. + * @dev This interface allows contracts implementing the LSP1UniversalReceiver function to delegate the reaction logic to another contract or account. By doing so, the main logic doesn't need to reside within the `universalReceiver` function itself, offering modularity and flexibility. + */ +interface ILSP1UniversalReceiverDelegate { + /** + * @dev A delegate function that reacts to calls forwarded from the `universalReceiver(..)` function. This allows for modular handling of the logic based on the `typeId` and `data` provided by the initial caller. + * @notice Reacted on received notification forwarded from `universalReceiver` with `typeId` & `data`. + * + * @param sender The address of the EOA or smart contract that initially called the `universalReceiver` function. + * @param value The amount sent by the `sender` to the `universalReceiver` function. + * @param typeId The hash of a specific standard or a hook. + * @param data The arbitrary data received with the initial call to `universalReceiver`. + */ + function universalReceiverDelegate( + address sender, + uint256 value, + bytes32 typeId, + bytes memory data + ) external returns (bytes memory); +} diff --git a/contracts/LSP1UniversalReceiver/LSP1Constants.sol b/contracts/LSP1UniversalReceiver/LSP1Constants.sol index be8b0a73f..2a4fe0214 100644 --- a/contracts/LSP1UniversalReceiver/LSP1Constants.sol +++ b/contracts/LSP1UniversalReceiver/LSP1Constants.sol @@ -3,6 +3,7 @@ pragma solidity ^0.8.4; // --- ERC165 interface ids bytes4 constant _INTERFACEID_LSP1 = 0x6bb56a14; +bytes4 constant _INTERFACEID_LSP1_DELEGATE = 0xa245bbda; // --- ERC725Y Data Keys diff --git a/contracts/LSP1UniversalReceiver/LSP1Errors.sol b/contracts/LSP1UniversalReceiver/LSP1Errors.sol index 087473ab0..e336d1923 100644 --- a/contracts/LSP1UniversalReceiver/LSP1Errors.sol +++ b/contracts/LSP1UniversalReceiver/LSP1Errors.sol @@ -8,9 +8,3 @@ pragma solidity ^0.8.4; * @param caller The address of the EOA */ error CannotRegisterEOAsAsAssets(address caller); - -/** - * @dev Reverts when the {universalReceiver} function in the LSP1 Universal Receiver Delegate contract is called while sending some native tokens along the call (`msg.value` different than `0`) - * @notice Cannot send native tokens to {universalReceiver(...)} function of the delegated contract. - */ -error NativeTokensNotAccepted(); diff --git a/contracts/LSP1UniversalReceiver/LSP1UniversalReceiverDelegateUP/LSP1UniversalReceiverDelegateUP.sol b/contracts/LSP1UniversalReceiver/LSP1UniversalReceiverDelegateUP/LSP1UniversalReceiverDelegateUP.sol index eef3894e2..889e1aa93 100644 --- a/contracts/LSP1UniversalReceiver/LSP1UniversalReceiverDelegateUP/LSP1UniversalReceiverDelegateUP.sol +++ b/contracts/LSP1UniversalReceiver/LSP1UniversalReceiverDelegateUP/LSP1UniversalReceiverDelegateUP.sol @@ -1,11 +1,13 @@ -// SPDX-License-Identifier: CC0-1.0 +// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.4; // interfaces import { IERC725Y } from "@erc725/smart-contracts/contracts/interfaces/IERC725Y.sol"; -import {ILSP1UniversalReceiver} from "../ILSP1UniversalReceiver.sol"; +import { + ILSP1UniversalReceiverDelegate +} from "../ILSP1UniversalReceiverDelegate.sol"; import {ILSP7DigitalAsset} from "../../LSP7DigitalAsset/ILSP7DigitalAsset.sol"; // modules @@ -15,9 +17,11 @@ import {ERC165} from "@openzeppelin/contracts/utils/introspection/ERC165.sol"; import { ERC165Checker } from "@openzeppelin/contracts/utils/introspection/ERC165Checker.sol"; -import {LSP1Utils} from "../LSP1Utils.sol"; -import {LSP2Utils} from "../../LSP2ERC725YJSONSchema/LSP2Utils.sol"; import {LSP5Utils} from "../../LSP5ReceivedAssets/LSP5Utils.sol"; +import {LSP10Utils} from "../../LSP10ReceivedVaults/LSP10Utils.sol"; + +// constants +import {_INTERFACEID_LSP1_DELEGATE} from "../LSP1Constants.sol"; import { _TYPEID_LSP7_TOKENSSENDER, _TYPEID_LSP7_TOKENSRECIPIENT, @@ -32,18 +36,9 @@ import { _TYPEID_LSP9_OwnershipTransferred_SenderNotification, _TYPEID_LSP9_OwnershipTransferred_RecipientNotification } from "../../LSP9Vault/LSP9Constants.sol"; -import {LSP10Utils} from "../../LSP10ReceivedVaults/LSP10Utils.sol"; - -// constants -import "../LSP1Constants.sol"; -import "../../LSP0ERC725Account/LSP0Constants.sol"; -import "../../LSP6KeyManager/LSP6Constants.sol"; -import "../../LSP9Vault/LSP9Constants.sol"; -import "../../LSP10ReceivedVaults/LSP10Constants.sol"; -import "../../LSP14Ownable2Step/LSP14Constants.sol"; // errors -import "../LSP1Errors.sol"; +import {CannotRegisterEOAsAsAssets} from "../LSP1Errors.sol"; /** * @title Implementation of a UniversalReceiverDelegate for the [LSP-0-ERC725Account] @@ -57,7 +52,10 @@ import "../LSP1Errors.sol"; * - Writes the data keys representing the owned vaults from type [LSP-9-Vault] into your account storage, and removes them when transferring ownership to other accounts according to the [LSP-10-ReceivedVaults] Standard. * */ -contract LSP1UniversalReceiverDelegateUP is ERC165, ILSP1UniversalReceiver { +contract LSP1UniversalReceiverDelegateUP is + ERC165, + ILSP1UniversalReceiverDelegate +{ using ERC165Checker for address; /** @@ -80,20 +78,15 @@ contract LSP1UniversalReceiverDelegateUP is ERC165, ILSP1UniversalReceiver { * @param typeId Unique identifier for a specific notification. * @return The result of the reaction for `typeId`. */ - function universalReceiver( + function universalReceiverDelegate( + address notifier, + uint256 /*value*/, bytes32 typeId, bytes memory /* data */ - ) public payable virtual returns (bytes memory) { - // CHECK that we did not send any native tokens to the LSP1 Delegate, as it cannot transfer them back. - if (msg.value != 0) { - revert NativeTokensNotAccepted(); - } - - address notifier = address(bytes20(msg.data[msg.data.length - 52:])); - + ) public virtual override 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 avoid-tx-origin + // solhint-disable-next-line avoid-tx-origin if (notifier == tx.origin) { revert CannotRegisterEOAsAsAssets(notifier); } @@ -176,7 +169,7 @@ contract LSP1UniversalReceiverDelegateUP is ERC165, ILSP1UniversalReceiver { ) internal 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) { + if (notifier.code.length != 0) { // if the amount sent is 0, then do not update the keys try ILSP7DigitalAsset(notifier).balanceOf(msg.sender) returns ( uint256 balance @@ -275,7 +268,7 @@ contract LSP1UniversalReceiverDelegateUP is ERC165, ILSP1UniversalReceiver { bytes4 interfaceId ) public view virtual override returns (bool) { return - interfaceId == _INTERFACEID_LSP1 || + interfaceId == _INTERFACEID_LSP1_DELEGATE || super.supportsInterface(interfaceId); } } diff --git a/contracts/LSP1UniversalReceiver/LSP1UniversalReceiverDelegateVault/LSP1UniversalReceiverDelegateVault.sol b/contracts/LSP1UniversalReceiver/LSP1UniversalReceiverDelegateVault/LSP1UniversalReceiverDelegateVault.sol index 669049f06..8477cc5dc 100644 --- a/contracts/LSP1UniversalReceiver/LSP1UniversalReceiverDelegateVault/LSP1UniversalReceiverDelegateVault.sol +++ b/contracts/LSP1UniversalReceiver/LSP1UniversalReceiverDelegateVault/LSP1UniversalReceiverDelegateVault.sol @@ -1,23 +1,23 @@ -// SPDX-License-Identifier: CC0-1.0 +// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.4; // interfaces import { IERC725Y } from "@erc725/smart-contracts/contracts/interfaces/IERC725Y.sol"; -import {ILSP1UniversalReceiver} from "../ILSP1UniversalReceiver.sol"; +import { + ILSP1UniversalReceiverDelegate +} from "../ILSP1UniversalReceiverDelegate.sol"; import {ILSP7DigitalAsset} from "../../LSP7DigitalAsset/ILSP7DigitalAsset.sol"; // modules import {ERC165} from "@openzeppelin/contracts/utils/introspection/ERC165.sol"; // libraries -import {LSP1Utils} from "../LSP1Utils.sol"; -import {LSP2Utils} from "../../LSP2ERC725YJSONSchema/LSP2Utils.sol"; import {LSP5Utils} from "../../LSP5ReceivedAssets/LSP5Utils.sol"; // constants -import "../LSP1Constants.sol"; +import {_INTERFACEID_LSP1_DELEGATE} from "../LSP1Constants.sol"; import { _TYPEID_LSP7_TOKENSSENDER, _TYPEID_LSP7_TOKENSRECIPIENT, @@ -28,10 +28,9 @@ import { _TYPEID_LSP8_TOKENSRECIPIENT, _INTERFACEID_LSP8 } from "../../LSP8IdentifiableDigitalAsset/LSP8Constants.sol"; -import "../../LSP9Vault/LSP9Constants.sol"; // errors -import "../LSP1Errors.sol"; +import {CannotRegisterEOAsAsAssets} from "../LSP1Errors.sol"; /** * @title Implementation of a UniversalReceiverDelegate for the [LSP9Vault] @@ -43,9 +42,12 @@ import "../LSP1Errors.sol"; * * - Writes the data keys representing assets received from type [LSP-7-DigitalAsset] and [LSP-8-IdentifiableDigitalAsset] into the account storage, and removes them when the balance is zero according to the [LSP-5-ReceivedAssets] Standard. */ -contract LSP1UniversalReceiverDelegateVault is ERC165, ILSP1UniversalReceiver { +contract LSP1UniversalReceiverDelegateVault is + ERC165, + ILSP1UniversalReceiverDelegate +{ /** - * @inheritdoc ILSP1UniversalReceiver + * @inheritdoc ILSP1UniversalReceiverDelegate * @dev Handles two cases: * * Writes the received [LSP-7-DigitalAsset] or [LSP-8-IdentifiableDigitalAsset] assets into the vault storage according to the [LSP-5-ReceivedAssets] standard. @@ -60,20 +62,15 @@ contract LSP1UniversalReceiverDelegateVault is ERC165, ILSP1UniversalReceiver { * @param typeId Unique identifier for a specific notification. * @return The result of the reaction for `typeId`. */ - function universalReceiver( + function universalReceiverDelegate( + address notifier, + uint256 /*value*/, bytes32 typeId, bytes memory /* data */ - ) public payable virtual returns (bytes memory) { - // CHECK that we did not send any native tokens to the LSP1 Delegate, as it cannot transfer them back. - if (msg.value != 0) { - revert NativeTokensNotAccepted(); - } - - address notifier = address(bytes20(msg.data[msg.data.length - 52:])); - + ) public virtual override 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 avoid-tx-origin + // solhint-disable-next-line avoid-tx-origin if (notifier == tx.origin) { revert CannotRegisterEOAsAsAssets(notifier); } @@ -148,7 +145,7 @@ contract LSP1UniversalReceiverDelegateVault is ERC165, ILSP1UniversalReceiver { ) internal 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) { + if (notifier.code.length != 0) { // if the amount sent is 0, then do not update the keys try ILSP7DigitalAsset(notifier).balanceOf(msg.sender) returns ( uint256 balance @@ -201,7 +198,7 @@ contract LSP1UniversalReceiverDelegateVault is ERC165, ILSP1UniversalReceiver { bytes4 interfaceId ) public view virtual override returns (bool) { return - interfaceId == _INTERFACEID_LSP1 || + interfaceId == _INTERFACEID_LSP1_DELEGATE || super.supportsInterface(interfaceId); } } diff --git a/contracts/LSP1UniversalReceiver/LSP1Utils.sol b/contracts/LSP1UniversalReceiver/LSP1Utils.sol index f954020c9..cfd523f28 100644 --- a/contracts/LSP1UniversalReceiver/LSP1Utils.sol +++ b/contracts/LSP1UniversalReceiver/LSP1Utils.sol @@ -2,14 +2,13 @@ pragma solidity ^0.8.4; // libraries -import {Address} from "@openzeppelin/contracts/utils/Address.sol"; import { ERC165Checker } from "@openzeppelin/contracts/utils/introspection/ERC165Checker.sol"; import {LSP2Utils} from "../LSP2ERC725YJSONSchema/LSP2Utils.sol"; // constants -import "./ILSP1UniversalReceiver.sol"; +import {ILSP1UniversalReceiver as ILSP1} from "./ILSP1UniversalReceiver.sol"; // constants import { @@ -45,53 +44,10 @@ library LSP1Utils { _INTERFACEID_LSP1 ) ) { - ILSP1UniversalReceiver(lsp1Implementation).universalReceiver( - typeId, - data - ); + ILSP1(lsp1Implementation).universalReceiver(typeId, data); } } - /** - * @dev Call a LSP1UniversalReceiverDelegate contract at `universalReceiverDelegate` address and append `msgSender` and `msgValue` - * as additional informations in the calldata. - * - * @param universalReceiverDelegate The address of the LSP1UniversalReceiverDelegate to delegate the `universalReceiver` function to. - * @param typeId A `bytes32` typeId. - * @param receivedData The data sent initially to the `universalReceiver` function. - * @param msgSender The address that initially called the `universalReceiver` function. - * @param msgValue The amount of native token received initially by the `universalReceiver` function. - * - * @return The data returned by the LSP1UniversalReceiverDelegate contract. - */ - function callUniversalReceiverWithCallerInfos( - address universalReceiverDelegate, - bytes32 typeId, - bytes calldata receivedData, - address msgSender, - uint256 msgValue - ) internal returns (bytes memory) { - bytes memory callData = abi.encodePacked( - abi.encodeWithSelector( - ILSP1UniversalReceiver.universalReceiver.selector, - typeId, - receivedData - ), - msgSender, - msgValue - ); - - (bool success, bytes memory result) = universalReceiverDelegate.call( - callData - ); - Address.verifyCallResult( - success, - result, - "Call to universalReceiver failed" - ); - return result.length != 0 ? abi.decode(result, (bytes)) : result; - } - /** * @notice Retrieving the value stored under the ERC725Y data key `LSP1UniversalReceiverDelegate`. * diff --git a/contracts/LSP20CallVerification/ILSP20CallVerifier.sol b/contracts/LSP20CallVerification/ILSP20CallVerifier.sol index 5ac63eb6d..f23eee193 100644 --- a/contracts/LSP20CallVerification/ILSP20CallVerifier.sol +++ b/contracts/LSP20CallVerification/ILSP20CallVerifier.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: MIT +// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.4; /** @@ -8,28 +8,32 @@ pragma solidity ^0.8.4; */ interface ILSP20CallVerifier { /** - * @return magicValue MUST return the first 3 bytes of `lsp20VerifyCall(address,uint256,bytes)` function selector if the call to + * @return returnedStatus MUST return the first 3 bytes of `lsp20VerifyCall(address,uint256,bytes)` function selector if the call to * the function is allowed, concatened with a byte that determines if the lsp20VerifyCallResult function should * be called after the original function call. The byte that invoke the lsp20VerifyCallResult function is strictly `0x01`. * - * @param caller The address who called the function on the msg.sender + * @param requestor The address that requested to make the call to `target`. + * @param target The address of the contract that implements the `LSP20CallVerification` interface. + * @param caller The address who called the function on the `target` contract. * @param value The value sent by the caller to the function called on the msg.sender - * @param receivedCalldata The receivedCalldata sent by the caller to the msg.sender + * @param callData The calldata sent by the caller to the msg.sender */ function lsp20VerifyCall( + address requestor, + address target, address caller, uint256 value, - bytes memory receivedCalldata - ) external returns (bytes4 magicValue); + bytes memory callData + ) external returns (bytes4 returnedStatus); /** * @return MUST return the lsp20VerifyCallResult function selector if the call to the function is allowed * - * @param callHash The keccak256 of the parameters of {lsp20VerifyCall} concatenated - * @param result The value result of the function called on the msg.sender + * @param callHash The keccak256 hash of the parameters of {lsp20VerifyCall} concatenated + * @param callResult The value result of the function called on the msg.sender */ function lsp20VerifyCallResult( bytes32 callHash, - bytes memory result + bytes memory callResult ) external returns (bytes4); } diff --git a/contracts/LSP20CallVerification/LSP20CallVerification.sol b/contracts/LSP20CallVerification/LSP20CallVerification.sol index 81f2c5cd2..6a7732222 100644 --- a/contracts/LSP20CallVerification/LSP20CallVerification.sol +++ b/contracts/LSP20CallVerification/LSP20CallVerification.sol @@ -2,31 +2,39 @@ pragma solidity ^0.8.4; // interfaces - import {ILSP20CallVerifier as ILSP20} from "./ILSP20CallVerifier.sol"; // errors -import "./LSP20Errors.sol"; +import { + LSP20CallVerificationFailed, + LSP20CallingVerifierFailed, + LSP20EOACannotVerifyCall +} from "./LSP20Errors.sol"; /** * @title Implementation of a contract calling the verification functions according to LSP20 - Call Verification standard. * * @dev Module to be inherited used to verify the execution of functions according to a verifier address. - * Verification can happen before or after execution based on a magicValue. + * Verification can happen before or after execution based on a returnedStatus. */ abstract contract LSP20CallVerification { /** * @dev Calls {lsp20VerifyCall} function on the logicVerifier. - * Reverts in case the value returned does not match the magic value (lsp20VerifyCall selector) - * Returns whether a verification after the execution should happen based on the last byte of the magicValue + * Reverts in case the value returned does not match the success value (lsp20VerifyCall selector) + * Returns whether a verification after the execution should happen based on the last byte of the returnedStatus */ function _verifyCall( address logicVerifier ) internal virtual returns (bool verifyAfter) { + if (logicVerifier.code.length == 0) + revert LSP20EOACannotVerifyCall(logicVerifier); + (bool success, bytes memory returnedData) = logicVerifier.call( abi.encodeWithSelector( ILSP20.lsp20VerifyCall.selector, msg.sender, + address(this), + msg.sender, msg.value, msg.data ) @@ -34,17 +42,18 @@ abstract contract LSP20CallVerification { _validateCall(false, success, returnedData); - bytes4 magicValue = abi.decode(returnedData, (bytes4)); + bytes4 returnedStatus = abi.decode(returnedData, (bytes4)); - if (bytes3(magicValue) != bytes3(ILSP20.lsp20VerifyCall.selector)) - revert LSP20InvalidMagicValue(false, returnedData); + if (bytes3(returnedStatus) != bytes3(ILSP20.lsp20VerifyCall.selector)) { + revert LSP20CallVerificationFailed(false, returnedData); + } - return magicValue[3] == 0x01 ? true : false; + return returnedStatus[3] == 0x01; } /** * @dev Calls {lsp20VerifyCallResult} function on the logicVerifier. - * Reverts in case the value returned does not match the magic value (lsp20VerifyCallResult selector) + * Reverts in case the value returned does not match the success value (lsp20VerifyCallResult selector) */ function _verifyCallResult( address logicVerifier, @@ -53,7 +62,15 @@ abstract contract LSP20CallVerification { (bool success, bytes memory returnedData) = logicVerifier.call( abi.encodeWithSelector( ILSP20.lsp20VerifyCallResult.selector, - keccak256(abi.encodePacked(msg.sender, msg.value, msg.data)), + keccak256( + abi.encodePacked( + msg.sender, + address(this), + msg.sender, + msg.value, + msg.data + ) + ), callResult ) ); @@ -63,7 +80,11 @@ abstract contract LSP20CallVerification { if ( abi.decode(returnedData, (bytes4)) != ILSP20.lsp20VerifyCallResult.selector - ) revert LSP20InvalidMagicValue(true, returnedData); + ) + revert LSP20CallVerificationFailed({ + postCall: true, + returnedData: returnedData + }); } function _validateCall( @@ -78,7 +99,11 @@ abstract contract LSP20CallVerification { if ( returnedData.length < 32 || bytes28(bytes32(returnedData) << 32) != bytes28(0) - ) revert LSP20InvalidMagicValue(postCall, returnedData); + ) + revert LSP20CallVerificationFailed({ + postCall: postCall, + returnedData: returnedData + }); } function _revertWithLSP20DefaultError( @@ -86,7 +111,7 @@ abstract contract LSP20CallVerification { bytes memory returnedData ) internal pure virtual { // Look for revert reason and bubble it up if present - if (returnedData.length > 0) { + if (returnedData.length != 0) { // The easiest way to bubble the revert reason is using memory via assembly // solhint-disable no-inline-assembly /// @solidity memory-safe-assembly diff --git a/contracts/LSP20CallVerification/LSP20Constants.sol b/contracts/LSP20CallVerification/LSP20Constants.sol index ea3b2b1d9..9837c368c 100644 --- a/contracts/LSP20CallVerification/LSP20Constants.sol +++ b/contracts/LSP20CallVerification/LSP20Constants.sol @@ -1,17 +1,17 @@ -// SPDX-License-Identifier: MIT +// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.4; // bytes4(keccak256("LSP20CallVerification")) bytes4 constant _INTERFACEID_LSP20_CALL_VERIFICATION = 0x1a0eb6a5; -// `lsp20VerifyCall(address,uint256,bytes)` selector XOR `lsp20VerifyCallResult(bytes32,bytes)` selector -bytes4 constant _INTERFACEID_LSP20_CALL_VERIFIER = 0x480c0ec2; +// `lsp20VerifyCall(address,address,address,uint256,bytes)` selector XOR `lsp20VerifyCallResult(bytes32,bytes)` selector +bytes4 constant _INTERFACEID_LSP20_CALL_VERIFIER = 0x0d6ecac7; // bytes4(bytes.concat(bytes3(ILSP20.lsp20VerifyCall.selector), hex"01")) -bytes4 constant _LSP20_VERIFY_CALL_MAGIC_VALUE_WITH_POST_VERIFICATION = 0x9bf04b01; +bytes4 constant _LSP20_VERIFY_CALL_SUCCESS_VALUE_WITH_POST_VERIFICATION = 0xde928f01; // bytes4(bytes.concat(bytes3(ILSP20.lsp20VerifyCall.selector), hex"00")) -bytes4 constant _LSP20_VERIFY_CALL_MAGIC_VALUE_WITHOUT_POST_VERIFICATION = 0x9bf04b00; +bytes4 constant _LSP20_VERIFY_CALL_SUCCESS_VALUE_WITHOUT_POST_VERIFICATION = 0xde928f00; // bytes4(ILSP20.lsp20VerifyCallResult.selector) -bytes4 constant _LSP20_VERIFY_CALL_RESULT_MAGIC_VALUE = 0xd3fc45d3; +bytes4 constant _LSP20_VERIFY_CALL_RESULT_SUCCESS_VALUE = 0xd3fc45d3; diff --git a/contracts/LSP20CallVerification/LSP20Errors.sol b/contracts/LSP20CallVerification/LSP20Errors.sol index 15e417a89..40e99bc4d 100644 --- a/contracts/LSP20CallVerification/LSP20Errors.sol +++ b/contracts/LSP20CallVerification/LSP20Errors.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: MIT +// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.4; /** @@ -8,8 +8,14 @@ pragma solidity ^0.8.4; error LSP20CallingVerifierFailed(bool postCall); /** - * @dev reverts when the call to the owner does not return the magic value + * @dev reverts when the call to the owner does not return the LSP20 success value * @param postCall True if the execution call was done, False otherwise * @param returnedData The data returned by the call to the logic verifier */ -error LSP20InvalidMagicValue(bool postCall, bytes returnedData); +error LSP20CallVerificationFailed(bool postCall, bytes returnedData); + +/** + * @dev Reverts when the logic verifier is an Externally Owned Account (EOA) that cannot return the LSP20 success value. + * @param logicVerifier The address of the logic verifier + */ +error LSP20EOACannotVerifyCall(address logicVerifier); diff --git a/contracts/LSP23LinkedContractsDeployment/ILSP23LinkedContractsFactory.sol b/contracts/LSP23LinkedContractsFactory/ILSP23LinkedContractsFactory.sol similarity index 99% rename from contracts/LSP23LinkedContractsDeployment/ILSP23LinkedContractsFactory.sol rename to contracts/LSP23LinkedContractsFactory/ILSP23LinkedContractsFactory.sol index dd3eeb28d..e01c94213 100644 --- a/contracts/LSP23LinkedContractsDeployment/ILSP23LinkedContractsFactory.sol +++ b/contracts/LSP23LinkedContractsFactory/ILSP23LinkedContractsFactory.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: MIT +// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.4; interface ILSP23LinkedContractsFactory { diff --git a/contracts/LSP23LinkedContractsDeployment/IPostDeploymentModule.sol b/contracts/LSP23LinkedContractsFactory/IPostDeploymentModule.sol similarity index 95% rename from contracts/LSP23LinkedContractsDeployment/IPostDeploymentModule.sol rename to contracts/LSP23LinkedContractsFactory/IPostDeploymentModule.sol index dda7d22d6..98746811f 100644 --- a/contracts/LSP23LinkedContractsDeployment/IPostDeploymentModule.sol +++ b/contracts/LSP23LinkedContractsFactory/IPostDeploymentModule.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: MIT +// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.4; interface IPostDeploymentModule { diff --git a/contracts/LSP23LinkedContractsDeployment/LSP23Errors.sol b/contracts/LSP23LinkedContractsFactory/LSP23Errors.sol similarity index 95% rename from contracts/LSP23LinkedContractsDeployment/LSP23Errors.sol rename to contracts/LSP23LinkedContractsFactory/LSP23Errors.sol index 0bbb109ab..c1a661ae8 100644 --- a/contracts/LSP23LinkedContractsDeployment/LSP23Errors.sol +++ b/contracts/LSP23LinkedContractsFactory/LSP23Errors.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: MIT +// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.4; /** diff --git a/contracts/LSP23LinkedContractsDeployment/LSP23LinkedContractsFactory.sol b/contracts/LSP23LinkedContractsFactory/LSP23LinkedContractsFactory.sol similarity index 99% rename from contracts/LSP23LinkedContractsDeployment/LSP23LinkedContractsFactory.sol rename to contracts/LSP23LinkedContractsFactory/LSP23LinkedContractsFactory.sol index 1e641943b..4239a1f29 100644 --- a/contracts/LSP23LinkedContractsDeployment/LSP23LinkedContractsFactory.sol +++ b/contracts/LSP23LinkedContractsFactory/LSP23LinkedContractsFactory.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: MIT +// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.4; import {Create2} from "@openzeppelin/contracts/utils/Create2.sol"; @@ -23,6 +23,7 @@ contract LSP23LinkedContractsFactory is ILSP23LinkedContractsFactory { ) public payable + override returns ( address primaryContractAddress, address secondaryContractAddress @@ -81,6 +82,7 @@ contract LSP23LinkedContractsFactory is ILSP23LinkedContractsFactory { ) public payable + override returns ( address primaryContractAddress, address secondaryContractAddress @@ -140,6 +142,7 @@ contract LSP23LinkedContractsFactory is ILSP23LinkedContractsFactory { ) public view + override returns ( address primaryContractAddress, address secondaryContractAddress @@ -187,6 +190,7 @@ contract LSP23LinkedContractsFactory is ILSP23LinkedContractsFactory { ) public view + override returns ( address primaryContractAddress, address secondaryContractAddress diff --git a/contracts/LSP23LinkedContractsFactory/modules/README.md b/contracts/LSP23LinkedContractsFactory/modules/README.md new file mode 100644 index 000000000..e02d730cd --- /dev/null +++ b/contracts/LSP23LinkedContractsFactory/modules/README.md @@ -0,0 +1,23 @@ +# LSP23 Linked Contracts Deployment Module + +This folder contains modules related to the deployment of LSP23 Linked Contracts. The modules are essential for initializing and post-deploying actions for Universal Profiles. + +## Modules + +- **UniversalProfileInitPostDeploymentModule**: This module is responsible for the initial setup after the deployment of a Universal Profile Init contract. + + - **Standardized Address**: `0x000000000066093407b6704B89793beFfD0D8F00` + - **Standardized Salt**: `0x12a6712f113536d8b01d99f72ce168c7e10901240d73e80eeb821d01aa4c2b1a` + - [More Details](./deployment-UP-init-module.md) + +- **UniversalProfilePostDeploymentModule**: This module is responsible for the initial setup after the deployment of a Universal Profile contract. + - **Standardized Address**: `0x0000005aD606bcFEF9Ea6D0BbE5b79847054BcD7` + - **Standardized Salt**: `0x42ff55d7957589c62da54a4368b10a2bc549f2038bbb6880ec6b3e0ecae2ba58` + - [More Details](./deployment-UP-module.md) + +## Setup + +Before deploying any of these modules, make sure that the following contracts are already deployed on the same network: + +- [Nick's Factory contract](https://github.com/Arachnid/deterministic-deployment-proxy/tree/master) +- [LSP23 Linked Contracts Factory](https://github.com/lukso-network/LIPs/LSPs/LSP-23-LinkedContractsFactory.md#lsp23linkedcontractsfactory-deployment) diff --git a/contracts/LSP23LinkedContractsDeployment/modules/UniversalProfileInitPostDeploymentModule.sol b/contracts/LSP23LinkedContractsFactory/modules/UniversalProfileInitPostDeploymentModule.sol similarity index 98% rename from contracts/LSP23LinkedContractsDeployment/modules/UniversalProfileInitPostDeploymentModule.sol rename to contracts/LSP23LinkedContractsFactory/modules/UniversalProfileInitPostDeploymentModule.sol index 90a905181..6e94fe845 100644 --- a/contracts/LSP23LinkedContractsDeployment/modules/UniversalProfileInitPostDeploymentModule.sol +++ b/contracts/LSP23LinkedContractsFactory/modules/UniversalProfileInitPostDeploymentModule.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: MIT +// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.4; import { diff --git a/contracts/LSP23LinkedContractsDeployment/modules/UniversalProfilePostDeploymentModule.sol b/contracts/LSP23LinkedContractsFactory/modules/UniversalProfilePostDeploymentModule.sol similarity index 98% rename from contracts/LSP23LinkedContractsDeployment/modules/UniversalProfilePostDeploymentModule.sol rename to contracts/LSP23LinkedContractsFactory/modules/UniversalProfilePostDeploymentModule.sol index 5baf9566d..7e2f861ff 100644 --- a/contracts/LSP23LinkedContractsDeployment/modules/UniversalProfilePostDeploymentModule.sol +++ b/contracts/LSP23LinkedContractsFactory/modules/UniversalProfilePostDeploymentModule.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: MIT +// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.4; import { diff --git a/contracts/LSP23LinkedContractsFactory/modules/deployment-UP-init-module.md b/contracts/LSP23LinkedContractsFactory/modules/deployment-UP-init-module.md new file mode 100644 index 000000000..13c2fc307 --- /dev/null +++ b/contracts/LSP23LinkedContractsFactory/modules/deployment-UP-init-module.md @@ -0,0 +1,92 @@ +# Setup + +Before the deployment of the `UniversalProfileInitPostDeploymentModule` on any network, people should make sure that the [Nick's Factory contract](https://github.com/Arachnid/deterministic-deployment-proxy/tree/master) is deployed on the same network. +You also need to make sure that the LSP23 Linked Contracts Factory is deployed on the same network. Please refer to [LSP23 Linked Contracts Deployment Factory LIP](https://github.com/lukso-network/LIPs/LSPs/LSP-23-LinkedContractsFactory.md#lsp23linkedcontractsfactory-deployment) in order to deploy it. + +# Deployment of the Universal Profile Init Post Deployment Module + +## Standardized Address + +`0x000000000066093407b6704B89793beFfD0D8F00` + +## Standardized Salt + +`0x12a6712f113536d8b01d99f72ce168c7e10901240d73e80eeb821d01aa4c2b1a` + +## Standardized Bytecode + +```solidity +0x60806040523480156200001157600080fd5b506200001c6200002c565b620000266200002c565b620000ed565b600054610100900460ff1615620000995760405162461bcd60e51b815260206004820152602760248201527f496e697469616c697a61626c653a20636f6e747261637420697320696e697469604482015266616c697a696e6760c81b606482015260840160405180910390fd5b60005460ff90811614620000eb576000805460ff191660ff9081179091556040519081527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b565b61497980620000fd6000396000f3fe6080604052600436106101635760003560e01c8063715018a6116100c0578063c4d66de811610074578063e30c397811610059578063e30c397814610451578063ead3fbdf1461020d578063f2fde38b1461047c5761019e565b8063c4d66de81461041e578063dedff9c6146104315761019e565b80637f23690c116100a55780637f23690c146103a65780638da5cb5b146103b9578063979024211461040b5761019e565b8063715018a61461037c57806379ba5097146103915761019e565b806344c028fe1161011757806354f6127f116100fc57806354f6127f146103295780636963d438146103495780636bb56a14146103695761019e565b806344c028fe146102f65780634f04d60a146103165761019e565b80631626ba7e116101485780631626ba7e1461026557806328c4d14e146102b657806331858452146102d65761019e565b806301bfba611461020d57806301ffc9a7146102355761019e565b3661019e57341561019c57604051349033907f7e71433ddf847725166244795048ecf3e3f9f35628254ecbf73605666423349390600090a35b005b600036606034156101d757604051349033907f7e71433ddf847725166244795048ecf3e3f9f35628254ecbf73605666423349390600090a35b60043610156101f55750604080516020810190915260008152610202565b6101ff838361049c565b90505b915050805190602001f35b34801561021957600080fd5b5061022260c881565b6040519081526020015b60405180910390f35b34801561024157600080fd5b50610255610250366004613a93565b610677565b604051901515815260200161022c565b34801561027157600080fd5b50610285610280366004613bca565b61080c565b6040517fffffffff00000000000000000000000000000000000000000000000000000000909116815260200161022c565b3480156102c257600080fd5b5061019c6102d1366004613c7c565b610ac3565b6102e96102e4366004613de7565b610c2a565b60405161022c9190613fb2565b610309610304366004613fc5565b610cf4565b60405161022c919061401a565b61019c61032436600461402d565b610d95565b34801561033557600080fd5b506103096103443660046140a1565b610f19565b34801561035557600080fd5b506102e96103643660046140ba565b610f24565b61030961037736600461412f565b61109a565b34801561038857600080fd5b5061019c6112a2565b34801561039d57600080fd5b5061019c6113a7565b61019c6103b4366004613bca565b6114b1565b3480156103c557600080fd5b5060005462010000900473ffffffffffffffffffffffffffffffffffffffff165b60405173ffffffffffffffffffffffffffffffffffffffff909116815260200161022c565b61019c61041936600461417b565b611552565b61019c61042c3660046141d5565b611681565b34801561043d57600080fd5b506102e961044c3660046141f0565b611815565b34801561045d57600080fd5b5060035473ffffffffffffffffffffffffffffffffffffffff166103e6565b34801561048857600080fd5b5061019c6104973660046141d5565b6118c0565b606060006104cd6000357fffffffff0000000000000000000000000000000000000000000000000000000016611b4f565b90506000357fffffffff0000000000000000000000000000000000000000000000000000000016158015610515575073ffffffffffffffffffffffffffffffffffffffff8116155b15610530575050604080516020810190915260008152610671565b73ffffffffffffffffffffffffffffffffffffffff81166105a8576040517fbb370b2b0000000000000000000000000000000000000000000000000000000081527fffffffff000000000000000000000000000000000000000000000000000000006000351660048201526024015b60405180910390fd5b6000808273ffffffffffffffffffffffffffffffffffffffff16868633346040516020016105d99493929190614225565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529082905261061191614268565b6000604051808303816000865af19150503d806000811461064e576040519150601f19603f3d011682016040523d82523d6000602084013e610653565b606091505b50915091508115610668579250610671915050565b80518060208301fd5b92915050565b60007fffffffff0000000000000000000000000000000000000000000000000000000082167f1626ba7e00000000000000000000000000000000000000000000000000000000148061070a57507fffffffff0000000000000000000000000000000000000000000000000000000082167f24871b3d00000000000000000000000000000000000000000000000000000000145b8061075657507fffffffff0000000000000000000000000000000000000000000000000000000082167f6bb56a1400000000000000000000000000000000000000000000000000000000145b806107a257507fffffffff0000000000000000000000000000000000000000000000000000000082167f94be599900000000000000000000000000000000000000000000000000000000145b806107ee57507fffffffff0000000000000000000000000000000000000000000000000000000082167f1a0eb6a500000000000000000000000000000000000000000000000000000000145b806107fd57506107fd82611bbf565b80610671575061067182611c15565b6000805462010000900473ffffffffffffffffffffffffffffffffffffffff16803b156109e0576000808273ffffffffffffffffffffffffffffffffffffffff16631626ba7e60e01b8787604051602401610868929190614284565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529181526020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fffffffff000000000000000000000000000000000000000000000000000000009094169390931790925290516108f19190614268565b600060405180830381855afa9150503d806000811461092c576040519150601f19603f3d011682016040523d82523d6000602084013e610931565b606091505b50915091506000828015610946575081516020145b8015610986575081517f1626ba7e0000000000000000000000000000000000000000000000000000000090610984908401602090810190850161429d565b145b9050806109b3577fffffffff000000000000000000000000000000000000000000000000000000006109d5565b7f1626ba7e000000000000000000000000000000000000000000000000000000005b945050505050610671565b6000806109ed8686611c78565b90925090506000816004811115610a0657610a066142b6565b14610a3757507fffffffff000000000000000000000000000000000000000000000000000000009250610671915050565b8273ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1614610a90577fffffffff00000000000000000000000000000000000000000000000000000000610ab2565b7f1626ba7e000000000000000000000000000000000000000000000000000000005b9350505050610671565b5092915050565b600080610ad28385018561417b565b915091508573ffffffffffffffffffffffffffffffffffffffff166344c028fe600430600086868b604051602401610b0c939291906142e5565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529181526020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167f4f04d60a000000000000000000000000000000000000000000000000000000001790525160e086901b7fffffffff00000000000000000000000000000000000000000000000000000000168152610bbc949392919060040161435a565b6000604051808303816000875af1158015610bdb573d6000803e3d6000fd5b505050506040513d6000823e601f3d9081017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0168201604052610c219190810190614395565b50505050505050565b60603415610c6057604051349033907f7e71433ddf847725166244795048ecf3e3f9f35628254ecbf73605666423349390600090a35b60005462010000900473ffffffffffffffffffffffffffffffffffffffff1633819003610c9b57610c9386868686611cbd565b915050610cec565b6000610ca682611e4d565b90506000610cb688888888611cbd565b90508115610ce757610ce78382604051602001610cd39190613fb2565b604051602081830303815290604052612060565b925050505b949350505050565b60603415610d2a57604051349033907f7e71433ddf847725166244795048ecf3e3f9f35628254ecbf73605666423349390600090a35b60005462010000900473ffffffffffffffffffffffffffffffffffffffff1633819003610d5d57610c9386868686612233565b6000610d6882611e4d565b90506000610d7888888888612233565b90508115610ce757610ce78382604051602001610cd3919061401a565b60005462010000900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614610eba576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152606860248201527f556e6976657273616c50726f66696c65496e6974506f73744465706c6f796d6560448201527f6e744d6f64756c653a2073657444617461416e645472616e736665724f776e6560648201527f7273686970206f6e6c7920616c6c6f776564207468726f7567682064656c656760848201527f6174652063616c6c00000000000000000000000000000000000000000000000060a482015260c40161059f565b60005b8351811015610f0a57610f02848281518110610edb57610edb614403565b6020026020010151848381518110610ef557610ef5614403565b60200260200101516123d5565b600101610ebd565b50610f1481612449565b505050565b6060610671826124ef565b60608167ffffffffffffffff811115610f3f57610f3f613ab0565b604051908082528060200260200182016040528015610f7257816020015b6060815260200190600190039081610f5d5790505b50905060005b82811015610abc5760008030868685818110610f9657610f96614403565b9050602002810190610fa89190614432565b604051610fb6929190614497565b600060405180830381855af49150503d8060008114610ff1576040519150601f19603f3d011682016040523d82523d6000602084013e610ff6565b606091505b509150915081611072578051156110105780518082602001fd5b6040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601960248201527f4c5350303a20626174636843616c6c7320726576657274656400000000000000604482015260640161059f565b8084848151811061108557611085614403565b60209081029190910101525050600101610f78565b606034156110d057604051349033907f7e71433ddf847725166244795048ecf3e3f9f35628254ecbf73605666423349390600090a35b60006110fb7f0cfc51aec37c55a4d0b1a65c6255c4bf2fbdf6277f3cc0730c45b828b6db8b476124ef565b905060606014825110611170576000611113836144a7565b60601c9050611142817f6bb56a1400000000000000000000000000000000000000000000000000000000612591565b1561116e5761116b73ffffffffffffffffffffffffffffffffffffffff82168888883334612660565b91505b505b600061119c7f0cfc51aec37c55a4d0b10000000000000000000000000000000000000000000088612800565b905060006111a9826124ef565b90506060601482511061121e5760006111c1836144a7565b60601c90506111f0817f6bb56a1400000000000000000000000000000000000000000000000000000000612591565b1561121c5761121973ffffffffffffffffffffffffffffffffffffffff82168b8b8b3334612660565b91505b505b83816040516020016112319291906144f7565b604051602081830303815290604052955088343373ffffffffffffffffffffffffffffffffffffffff167f9c3ba68eb5742b8e3961aea0afc7371a71bf433c8a67a831803b64c064a178c28b8b8b60405161128e93929190614565565b60405180910390a450505050509392505050565b60005462010000900473ffffffffffffffffffffffffffffffffffffffff16338190036112d4576112d161287c565b50565b60006112df82611e4d565b9050600061130960005473ffffffffffffffffffffffffffffffffffffffff620100009091041690565b905061131361287c565b60005462010000900473ffffffffffffffffffffffffffffffffffffffff16611388576040805160208101909152600081526113889073ffffffffffffffffffffffffffffffffffffffff8316907fa4e59c931d14f7c8a7a35027f92ee40b5f2886b9fdcdb78f30bc5ecce5a2f814906129b8565b8115610f1457610f148360405180602001604052806000815250612060565b60035474010000000000000000000000000000000000000000900460ff16156113fc576040517f5758dd0700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60005462010000900473ffffffffffffffffffffffffffffffffffffffff16611423612aa0565b6040805160208101909152600081526114759073ffffffffffffffffffffffffffffffffffffffff8316907fa4e59c931d14f7c8a7a35027f92ee40b5f2886b9fdcdb78f30bc5ecce5a2f814906129b8565b6040805160208101909152600081526112d19033907fceca317f109c43507871523e82dc2a3cc64dfa18f12da0b6db14f6e23f995538906129b8565b34156114e557604051349033907f7e71433ddf847725166244795048ecf3e3f9f35628254ecbf73605666423349390600090a35b60005462010000900473ffffffffffffffffffffffffffffffffffffffff163381900361151657610f1483836123d5565b600061152182611e4d565b905061152d84846123d5565b801561154c5761154c8260405180602001604052806000815250612060565b50505050565b341561158657604051349033907f7e71433ddf847725166244795048ecf3e3f9f35628254ecbf73605666423349390600090a35b80518251146115c1576040517f3bcc897900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60005462010000900473ffffffffffffffffffffffffffffffffffffffff16338190036116115760005b835181101561154c57611609848281518110610edb57610edb614403565b6001016115eb565b600061161c82611e4d565b905060005b84518110156116615761165985828151811061163f5761163f614403565b6020026020010151858381518110610ef557610ef5614403565b600101611621565b50801561154c5761154c8260405180602001604052806000815250612060565b600054610100900460ff16158080156116a15750600054600160ff909116105b806116bb5750303b1580156116bb575060005460ff166001145b611747576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201527f647920696e697469616c697a6564000000000000000000000000000000000000606482015260840161059f565b600080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0016600117905580156117a557600080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ff166101001790555b6117ae82612b7a565b801561181157600080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ff169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b5050565b6060815167ffffffffffffffff81111561183157611831613ab0565b60405190808252806020026020018201604052801561186457816020015b606081526020019060019003908161184f5790505b50905060005b82518110156118ba5761189583828151811061188857611888614403565b60200260200101516124ef565b8282815181106118a7576118a7614403565b602090810291909101015260010161186a565b50919050565b60005462010000900473ffffffffffffffffffffffffffffffffffffffff1633819003611a0757600380547fffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffff167401000000000000000000000000000000000000000017905561192f82612c7c565b8173ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff167f38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e2270060405160405180910390a36040805160208101909152600081526119db9073ffffffffffffffffffffffffffffffffffffffff8416907fe17117c9d2665d1dbeb479ed8058bbebde3c50ac50e2e65619f60006caac6926906129b8565b600380547fffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffff1690555050565b6000611a1282611e4d565b600380547fffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffff16740100000000000000000000000000000000000000001790559050611a5c83612c7c565b8273ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff167f38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e2270060405160405180910390a3604080516020810190915260008152611b089073ffffffffffffffffffffffffffffffffffffffff8516907fe17117c9d2665d1dbeb479ed8058bbebde3c50ac50e2e65619f60006caac6926906129b8565b600380547fffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffff1690558015610f1457610f148260405180602001604052806000815250612060565b600080611b9e7fcee78b4094da86011096000000000000000000000000000000000000000000007fffffffff000000000000000000000000000000000000000000000000000000008516612800565b90506000611bab826124ef565b611bb4906144a7565b60601c949350505050565b60007fffffffff0000000000000000000000000000000000000000000000000000000082167fa918fa6b000000000000000000000000000000000000000000000000000000001480610671575061067182612d17565b600080611c417f01ffc9a700000000000000000000000000000000000000000000000000000000611b4f565b905073ffffffffffffffffffffffffffffffffffffffff8116611c675750600092915050565b611c718184612591565b9392505050565b6000808251604103611cae5760208301516040840151606085015160001a611ca287828585612d6d565b94509450505050611cb6565b506000905060025b9250929050565b606083518551141580611cde575082518451141580611cde57508151835114155b15611d15576040517f3ff55f4d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8451600003611d50576040517fe9ad2b5f00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000855167ffffffffffffffff811115611d6c57611d6c613ab0565b604051908082528060200260200182016040528015611d9f57816020015b6060815260200190600190039081611d8a5790505b50905060005b8651811015611e4357611e1e878281518110611dc357611dc3614403565b6020026020010151878381518110611ddd57611ddd614403565b6020026020010151878481518110611df757611df7614403565b6020026020010151878581518110611e1157611e11614403565b6020026020010151612233565b828281518110611e3057611e30614403565b6020908102919091010152600101611da5565b5095945050505050565b60008060008373ffffffffffffffffffffffffffffffffffffffff16639bf04b1160e01b3334600036604051602401611e89949392919061458b565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529181526020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fffffffff00000000000000000000000000000000000000000000000000000000909416939093179092529051611f129190614268565b6000604051808303816000865af19150503d8060008114611f4f576040519150601f19603f3d011682016040523d82523d6000602084013e611f54565b606091505b5091509150611f6560008383612e5c565b600081806020019051810190611f7b91906145c1565b90507fffffff000000000000000000000000000000000000000000000000000000000081167f9bf04b000000000000000000000000000000000000000000000000000000000014611ffd576000826040517fd088ec4000000000000000000000000000000000000000000000000000000000815260040161059f9291906145de565b7f01000000000000000000000000000000000000000000000000000000000000007fff00000000000000000000000000000000000000000000000000000000000000600383901a60f81b1614612054576000612057565b60015b95945050505050565b6000808373ffffffffffffffffffffffffffffffffffffffff1663d3fc45d360e01b333460003660405160200161209a94939291906145f9565b60405160208183030381529060405280519060200120856040516024016120c2929190614284565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529181526020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fffffffff0000000000000000000000000000000000000000000000000000000090941693909317909252905161214b9190614268565b6000604051808303816000865af19150503d8060008114612188576040519150601f19603f3d011682016040523d82523d6000602084013e61218d565b606091505b509150915061219e60018383612e5c565b80517fd3fc45d300000000000000000000000000000000000000000000000000000000906121d590830160209081019084016145c1565b7fffffffff00000000000000000000000000000000000000000000000000000000161461154c576001816040517fd088ec4000000000000000000000000000000000000000000000000000000000815260040161059f9291906145de565b60608461224c57612245848484612ee5565b9050610cec565b600185036122ac5773ffffffffffffffffffffffffffffffffffffffff8416156122a2576040517f3041824a00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b612245838361305c565b6002850361230c5773ffffffffffffffffffffffffffffffffffffffff841615612302576040517f3041824a00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b61224583836131d5565b6003850361235657821561234c576040517f72f2bc6a00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b61224584836132f8565b600485036123a0578215612396576040517f5ac8313500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6122458483613420565b6040517f7583b3bc0000000000000000000000000000000000000000000000000000000081526004810186905260240161059f565b60008281526001602052604090206123ed82826146da565b50817fece574603820d07bc9b91f2a932baadf4628aabcb8afba49776529c14a6104b26101008351111561242e576124298360006101006134fe565b612430565b825b60405161243d919061401a565b60405180910390a25050565b60005473ffffffffffffffffffffffffffffffffffffffff8281166201000090920416146112d1576000805473ffffffffffffffffffffffffffffffffffffffff838116620100008181027fffffffffffffffffffff0000000000000000000000000000000000000000ffff851617855560405193049190911692909183917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e091a35050565b600081815260016020526040902080546060919061250c9061463f565b80601f01602080910402602001604051908101604052809291908181526020018280546125389061463f565b80156125855780601f1061255a57610100808354040283529160200191612585565b820191906000526020600020905b81548152906001019060200180831161256857829003601f168201915b50505050509050919050565b604080517fffffffff000000000000000000000000000000000000000000000000000000008316602480830191909152825180830390910181526044909101909152602080820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167f01ffc9a700000000000000000000000000000000000000000000000000000000178152825160009392849283928392918391908a617530fa92503d91506000519050828015612649575060208210155b80156126555750600081115b979650505050505050565b60606000636bb56a1460e01b878787604051602401612681939291906147f4565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0818403018152918152602080830180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fffffffff00000000000000000000000000000000000000000000000000000000909516949094179093525161270e92879187910161480e565b60405160208183030381529060405290506000808973ffffffffffffffffffffffffffffffffffffffff16836040516127479190614268565b6000604051808303816000865af19150503d8060008114612784576040519150601f19603f3d011682016040523d82523d6000602084013e612789565b606091505b50915091506127ce82826040518060400160405280602081526020017f43616c6c20746f20756e6976657273616c5265636569766572206661696c6564815250613678565b5080516000036127de57806127f2565b808060200190518101906127f29190614395565b9a9950505050505050505050565b604080517fffffffffffffffffffff00000000000000000000000000000000000000000000841660208201526000602a82018190527fffffffffffffffffffffffffffffffffffffffff0000000000000000000000008416602c83015291829101604051602081830303815290604052905080610cec90614860565b60025443906000906128909060c8906148d1565b9050600061289f60c8836148d1565b9050808311806128af5750600254155b1561290f576002839055600380547fffffffffffffffffffffffff00000000000000000000000000000000000000001690556040517f81b7f830f1f0084db6497c486cbe6974c86488dcc4e3738eab94ab6d6b1653e790600090a1505050565b81831015612953576040517f8b9bf507000000000000000000000000000000000000000000000000000000008152600481018390526024810182905260440161059f565b61295d6000612449565b60006002819055600380547fffffffffffffffffffffffff00000000000000000000000000000000000000001690556040517fd1f66c3d2bc1993a86be5e3d33709d98f0442381befcedd29f578b9b2506b1ce9190a1505050565b6129e2837f6bb56a1400000000000000000000000000000000000000000000000000000000612591565b15610f14576040517f6bb56a1400000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff841690636bb56a1490612a3b9085908590600401614284565b6000604051808303816000875af1158015612a5a573d6000803e3d6000fd5b505050506040513d6000823e601f3d9081017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016820160405261154c9190810190614395565b60035473ffffffffffffffffffffffffffffffffffffffff163314612b47576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602560248201527f4c535031343a2063616c6c6572206973206e6f74207468652070656e64696e6760448201527f4f776e6572000000000000000000000000000000000000000000000000000000606482015260840161059f565b612b5033612449565b600380547fffffffffffffffffffffffff0000000000000000000000000000000000000000169055565b600054610100900460ff16612c11576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602b60248201527f496e697469616c697a61626c653a20636f6e7472616374206973206e6f74206960448201527f6e697469616c697a696e67000000000000000000000000000000000000000000606482015260840161059f565b612c1a81613691565b6112d17feafec4d89fa9619884b600005ef83ad9559033e6e941db7d7c495acdce61634760001b6040518060400160405280600481526020017f5ef83ad9000000000000000000000000000000000000000000000000000000008152506123d5565b3073ffffffffffffffffffffffffffffffffffffffff821603612ccb576040517f43b248cd00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600380547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff929092169190911790556000600255565b60007fffffffff0000000000000000000000000000000000000000000000000000000082167f629aa694000000000000000000000000000000000000000000000000000000001480610671575061067182613765565b6000807f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a0831115612da45750600090506003612e53565b6040805160008082526020820180845289905260ff881692820192909252606081018690526080810185905260019060a0016020604051602081039080840390855afa158015612df8573d6000803e3d6000fd5b50506040517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0015191505073ffffffffffffffffffffffffffffffffffffffff8116612e4c57600060019250925050612e53565b9150600090505b94509492505050565b81612e6b57612e6b83826137fc565b602081511080612eaa575060006020612e8383614860565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffff00000000911b1614155b15610f145782816040517fd088ec4000000000000000000000000000000000000000000000000000000000815260040161059f9291906145de565b606082471015612f2a576040517f0df9a8f80000000000000000000000000000000000000000000000000000000081524760048201526024810184905260440161059f565b8273ffffffffffffffffffffffffffffffffffffffff851660007f4810874456b8e6487bd861375cf6abd8e1c8bb5858c8ce36a86a04dabfac199e612f6e866148e4565b6040517fffffffff00000000000000000000000000000000000000000000000000000000909116815260200160405180910390a46000808573ffffffffffffffffffffffffffffffffffffffff168585604051612fcb9190614268565b60006040518083038185875af1925050503d8060008114613008576040519150601f19603f3d011682016040523d82523d6000602084013e61300d565b606091505b509150915061305282826040518060400160405280601681526020017f455243373235583a20556e6b6e6f776e204572726f7200000000000000000000815250613678565b9695505050505050565b6060824710156130a1576040517f0df9a8f80000000000000000000000000000000000000000000000000000000081524760048201526024810184905260440161059f565b81516000036130dc576040517fb81cd8d900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600082516020840185f0905073ffffffffffffffffffffffffffffffffffffffff8116613135576040517f0b07489b00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b838173ffffffffffffffffffffffffffffffffffffffff1660017fa1fb700aaee2ae4a2ff6f91ce7eba292f89c2f5488b8ec4c5c5c8150692595c36000801b60405161318391815260200190565b60405180910390a46040517fffffffffffffffffffffffffffffffffffffffff000000000000000000000000606083901b16602082015260340160405160208183030381529060405291505092915050565b60608151600003613212576040517fb81cd8d900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600061322b83602085516132269190614930565b613842565b90506000613248846000602087516132439190614930565b6134fe565b905060006132578684846138c2565b9050858173ffffffffffffffffffffffffffffffffffffffff1660027fa1fb700aaee2ae4a2ff6f91ce7eba292f89c2f5488b8ec4c5c5c8150692595c3866040516132a491815260200190565b60405180910390a46040517fffffffffffffffffffffffffffffffffffffffff000000000000000000000000606083901b166020820152603401604051602081830303815290604052935050505092915050565b6060600073ffffffffffffffffffffffffffffffffffffffff841660037f4810874456b8e6487bd861375cf6abd8e1c8bb5858c8ce36a86a04dabfac199e61333f866148e4565b6040517fffffffff00000000000000000000000000000000000000000000000000000000909116815260200160405180910390a46000808473ffffffffffffffffffffffffffffffffffffffff168460405161339b9190614268565b600060405180830381855afa9150503d80600081146133d6576040519150601f19603f3d011682016040523d82523d6000602084013e6133db565b606091505b509150915061205782826040518060400160405280601681526020017f455243373235583a20556e6b6e6f776e204572726f7200000000000000000000815250613678565b6060600073ffffffffffffffffffffffffffffffffffffffff841660047f4810874456b8e6487bd861375cf6abd8e1c8bb5858c8ce36a86a04dabfac199e613467866148e4565b6040517fffffffff00000000000000000000000000000000000000000000000000000000909116815260200160405180910390a46000808473ffffffffffffffffffffffffffffffffffffffff16846040516134c39190614268565b600060405180830381855af49150503d80600081146133d6576040519150601f19603f3d011682016040523d82523d6000602084013e6133db565b60608161350c81601f6148d1565b1015613574576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600e60248201527f736c6963655f6f766572666c6f77000000000000000000000000000000000000604482015260640161059f565b61357e82846148d1565b845110156135e8576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601160248201527f736c6963655f6f75744f66426f756e6473000000000000000000000000000000604482015260640161059f565b606082158015613607576040519150600082526020820160405261366f565b6040519150601f8416801560200281840101858101878315602002848b0101015b81831015613640578051835260209283019201613628565b5050858452601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016604052505b50949350505050565b60608315613687575081611c71565b611c718383613a21565b600054610100900460ff16613728576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602b60248201527f496e697469616c697a61626c653a20636f6e7472616374206973206e6f74206960448201527f6e697469616c697a696e67000000000000000000000000000000000000000000606482015260840161059f565b341561375c57604051349033907f7e71433ddf847725166244795048ecf3e3f9f35628254ecbf73605666423349390600090a35b6112d181612449565b60007fffffffff0000000000000000000000000000000000000000000000000000000082167f7545acac00000000000000000000000000000000000000000000000000000000148061067157507f01ffc9a7000000000000000000000000000000000000000000000000000000007fffffffff00000000000000000000000000000000000000000000000000000000831614610671565b80511561380c5780518082602001fd5b6040517f8c6a8ae3000000000000000000000000000000000000000000000000000000008152821515600482015260240161059f565b600061384f8260206148d1565b835110156138b9576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601560248201527f746f427974657333325f6f75744f66426f756e64730000000000000000000000604482015260640161059f565b50016020015190565b60008347101561392e576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601d60248201527f437265617465323a20696e73756666696369656e742062616c616e6365000000604482015260640161059f565b8151600003613999576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820181905260248201527f437265617465323a2062797465636f6465206c656e677468206973207a65726f604482015260640161059f565b8282516020840186f5905073ffffffffffffffffffffffffffffffffffffffff8116611c71576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601960248201527f437265617465323a204661696c6564206f6e206465706c6f7900000000000000604482015260640161059f565b815115613a315781518083602001fd5b806040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161059f919061401a565b7fffffffff00000000000000000000000000000000000000000000000000000000811681146112d157600080fd5b600060208284031215613aa557600080fd5b8135611c7181613a65565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016810167ffffffffffffffff81118282101715613b2657613b26613ab0565b604052919050565b600067ffffffffffffffff821115613b4857613b48613ab0565b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01660200190565b600082601f830112613b8557600080fd5b8135613b98613b9382613b2e565b613adf565b818152846020838601011115613bad57600080fd5b816020850160208301376000918101602001919091529392505050565b60008060408385031215613bdd57600080fd5b82359150602083013567ffffffffffffffff811115613bfb57600080fd5b613c0785828601613b74565b9150509250929050565b803573ffffffffffffffffffffffffffffffffffffffff81168114613c3557600080fd5b919050565b60008083601f840112613c4c57600080fd5b50813567ffffffffffffffff811115613c6457600080fd5b602083019150836020828501011115611cb657600080fd5b60008060008060608587031215613c9257600080fd5b613c9b85613c11565b9350613ca960208601613c11565b9250604085013567ffffffffffffffff811115613cc557600080fd5b613cd187828801613c3a565b95989497509550505050565b600067ffffffffffffffff821115613cf757613cf7613ab0565b5060051b60200190565b600082601f830112613d1257600080fd5b81356020613d22613b9383613cdd565b82815260059290921b84018101918181019086841115613d4157600080fd5b8286015b84811015613d5c5780358352918301918301613d45565b509695505050505050565b600082601f830112613d7857600080fd5b81356020613d88613b9383613cdd565b82815260059290921b84018101918181019086841115613da757600080fd5b8286015b84811015613d5c57803567ffffffffffffffff811115613dcb5760008081fd5b613dd98986838b0101613b74565b845250918301918301613dab565b60008060008060808587031215613dfd57600080fd5b843567ffffffffffffffff80821115613e1557600080fd5b613e2188838901613d01565b9550602091508187013581811115613e3857600080fd5b8701601f81018913613e4957600080fd5b8035613e57613b9382613cdd565b81815260059190911b8201840190848101908b831115613e7657600080fd5b928501925b82841015613e9b57613e8c84613c11565b82529285019290850190613e7b565b97505050506040870135915080821115613eb457600080fd5b613ec088838901613d01565b93506060870135915080821115613ed657600080fd5b50613ee387828801613d67565b91505092959194509250565b60005b83811015613f0a578181015183820152602001613ef2565b50506000910152565b60008151808452613f2b816020860160208601613eef565b601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160200192915050565b600081518084526020808501808196508360051b8101915082860160005b85811015613fa5578284038952613f93848351613f13565b98850198935090840190600101613f7b565b5091979650505050505050565b602081526000611c716020830184613f5d565b60008060008060808587031215613fdb57600080fd5b84359350613feb60208601613c11565b925060408501359150606085013567ffffffffffffffff81111561400e57600080fd5b613ee387828801613b74565b602081526000611c716020830184613f13565b60008060006060848603121561404257600080fd5b833567ffffffffffffffff8082111561405a57600080fd5b61406687838801613d01565b9450602086013591508082111561407c57600080fd5b5061408986828701613d67565b92505061409860408501613c11565b90509250925092565b6000602082840312156140b357600080fd5b5035919050565b600080602083850312156140cd57600080fd5b823567ffffffffffffffff808211156140e557600080fd5b818501915085601f8301126140f957600080fd5b81358181111561410857600080fd5b8660208260051b850101111561411d57600080fd5b60209290920196919550909350505050565b60008060006040848603121561414457600080fd5b83359250602084013567ffffffffffffffff81111561416257600080fd5b61416e86828701613c3a565b9497909650939450505050565b6000806040838503121561418e57600080fd5b823567ffffffffffffffff808211156141a657600080fd5b6141b286838701613d01565b935060208501359150808211156141c857600080fd5b50613c0785828601613d67565b6000602082840312156141e757600080fd5b611c7182613c11565b60006020828403121561420257600080fd5b813567ffffffffffffffff81111561421957600080fd5b610cec84828501613d01565b8385823760609290921b7fffffffffffffffffffffffffffffffffffffffff00000000000000000000000016919092019081526014810191909152603401919050565b6000825161427a818460208701613eef565b9190910192915050565b828152604060208201526000610cec6040830184613f13565b6000602082840312156142af57600080fd5b5051919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b606080825284519082018190526000906020906080840190828801845b8281101561431e57815184529284019290840190600101614302565b505050838103828501526143328187613f5d565b9250505073ffffffffffffffffffffffffffffffffffffffff83166040830152949350505050565b84815273ffffffffffffffffffffffffffffffffffffffff841660208201528260408201526080606082015260006130526080830184613f13565b6000602082840312156143a757600080fd5b815167ffffffffffffffff8111156143be57600080fd5b8201601f810184136143cf57600080fd5b80516143dd613b9382613b2e565b8181528560208385010111156143f257600080fd5b612057826020830160208601613eef565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b60008083357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe184360301811261446757600080fd5b83018035915067ffffffffffffffff82111561448257600080fd5b602001915036819003821315611cb657600080fd5b8183823760009101908152919050565b6000815160208301517fffffffffffffffffffffffffffffffffffffffff000000000000000000000000808216935060148310156144ef5780818460140360031b1b83161693505b505050919050565b60408152600061450a6040830185613f13565b82810360208401526120578185613f13565b8183528181602085013750600060208284010152600060207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f840116840101905092915050565b60408152600061457960408301858761451c565b82810360208401526130528185613f13565b73ffffffffffffffffffffffffffffffffffffffff8516815283602082015260606040820152600061305260608301848661451c565b6000602082840312156145d357600080fd5b8151611c7181613a65565b8215158152604060208201526000610cec6040830184613f13565b7fffffffffffffffffffffffffffffffffffffffff0000000000000000000000008560601b16815283601482015281836034830137600091016034019081529392505050565b600181811c9082168061465357607f821691505b6020821081036118ba577f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b601f821115610f1457600081815260208120601f850160051c810160208610156146b35750805b601f850160051c820191505b818110156146d2578281556001016146bf565b505050505050565b815167ffffffffffffffff8111156146f4576146f4613ab0565b61470881614702845461463f565b8461468c565b602080601f83116001811461475b57600084156147255750858301515b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600386901b1c1916600185901b1785556146d2565b6000858152602081207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08616915b828110156147a857888601518255948401946001909101908401614789565b50858210156147e457878501517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600388901b60f8161c191681555b5050505050600190811b01905550565b83815260406020820152600061205760408301848661451c565b60008451614820818460208901613eef565b60609490941b7fffffffffffffffffffffffffffffffffffffffff0000000000000000000000001691909301908152601481019190915260340192915050565b805160208083015191908110156118ba577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff60209190910360031b1b16919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b80820180821115610671576106716148a2565b6000815160208301517fffffffff00000000000000000000000000000000000000000000000000000000808216935060048310156144ef5760049290920360031b82901b161692915050565b81810381811115610671576106716148a256fea26469706673582212204c716f85d1145bcbe75de9c2eb2914430942e4f65ea5e7afda664b1551460c7f64736f6c63430008110033 +``` + +## Universal Profile Init Post Deployment Module Source Code + +This is an exact copy of the code of the [`UniversalProfileInitPostDeploymentModule` smart contract]. + +- The source code is generated with `solc` compiler version `0.8.17` and with `9999999` optimization runs. +- The imported contracts are part of the `4.9.2` version of the `@openzeppelin/contracts` package. +- Navigate to [lsp-smart-contract](https://github.com/lukso-network/lsp-smart-contracts) repo and checkout to `b8eca3c5696acf85239130ef67edec9e8c134bfa` commit to obtain the exact copy of the code, change the compiler settings in `hardhat.config.ts` and compile to produce the same bytecode. + +```solidity +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.4; + +import { OPERATION_4_DELEGATECALL } from "@erc725/smart-contracts/contracts/constants.sol"; + +import { UniversalProfileInit } from "../../UniversalProfileInit.sol"; + +contract UniversalProfileInitPostDeploymentModule is UniversalProfileInit { + constructor() { + _disableInitializers(); + } + + function setDataAndTransferOwnership( + bytes32[] memory dataKeys, + bytes[] memory dataValues, + address newOwner + ) public payable { + // check that the msg.sender is the owner + require( + msg.sender == owner(), + "UniversalProfileInitPostDeploymentModule: setDataAndTransferOwnership only allowed through delegate call" + ); + + // update the dataKeys and dataValues in the UniversalProfile contract + for (uint256 i = 0; i < dataKeys.length; ) { + _setData(dataKeys[i], dataValues[i]); + + unchecked { + ++i; + } + } + + // transfer the ownership of the UniversalProfile contract to the newOwner + _setOwner(newOwner); + } + + function executePostDeployment( + address universalProfile, + address keyManager, + bytes calldata setDataBatchBytes + ) public { + // retrieve the dataKeys and dataValues to setData from the initializationCalldata bytes + (bytes32[] memory dataKeys, bytes[] memory dataValues) = abi.decode( + setDataBatchBytes, + (bytes32[], bytes[]) + ); + + // call the execute function with delegate_call on the universalProfile contract to setData and transferOwnership + UniversalProfileInit(payable(universalProfile)).execute( + OPERATION_4_DELEGATECALL, + address(this), + 0, + abi.encodeWithSignature( + "setDataAndTransferOwnership(bytes32[],bytes[],address)", + dataKeys, + dataValues, + keyManager + ) + ); + } +} +``` diff --git a/contracts/LSP23LinkedContractsFactory/modules/deployment-UP-module.md b/contracts/LSP23LinkedContractsFactory/modules/deployment-UP-module.md new file mode 100644 index 000000000..d046f4a4a --- /dev/null +++ b/contracts/LSP23LinkedContractsFactory/modules/deployment-UP-module.md @@ -0,0 +1,90 @@ +# Setup + +Before the deployment of the `UniversalProfilePostDeploymentModule` on any network, people should make sure that the [Nick's Factory contract](https://github.com/Arachnid/deterministic-deployment-proxy/tree/master) is deployed on the same network. +You also need to make sure that the LSP23 Linked Contracts Factory is deployed on the same network. Please refer to [LSP23 Linked Contracts Deployment Factory LIP](https://github.com/lukso-network/LIPs/LSPs/LSP-23-LinkedContractsFactory.md#lsp23linkedcontractsfactory-deployment) in order to deploy it. + +# Deployment of the Universal Profile Post Deployment Module + +## Standardized Address + +`0x0000005aD606bcFEF9Ea6D0BbE5b79847054BcD7` + +## Standardized Salt + +`0x42ff55d7957589c62da54a4368b10a2bc549f2038bbb6880ec6b3e0ecae2ba58 ` + +## Standardized Bytecode + +```solidity +0x60806040523480156200001157600080fd5b5060008034156200004a57604051349033907f7e71433ddf847725166244795048ecf3e3f9f35628254ecbf73605666423349390600090a35b6200006081620000b060201b620019381760201c565b506040805180820190915260048152635ef83ad960e01b6020820152620000a9907feafec4d89fa9619884b600005ef83ad9559033e6e941db7d7c495acdce6163479062000117565b50620004a4565b6000546001600160a01b038281169116146200011457600080546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a3505b50565b600082815260016020526040902062000131828262000360565b50817fece574603820d07bc9b91f2a932baadf4628aabcb8afba49776529c14a6104b26101008351111562000181576200017b8360006101006200019e60201b620019cf1760201c565b62000183565b825b6040516200019291906200042c565b60405180910390a25050565b606081620001ae81601f6200047c565b1015620001f35760405162461bcd60e51b815260206004820152600e60248201526d736c6963655f6f766572666c6f7760901b60448201526064015b60405180910390fd5b620001ff82846200047c565b84511015620002455760405162461bcd60e51b8152602060048201526011602482015270736c6963655f6f75744f66426f756e647360781b6044820152606401620001ea565b606082158015620002665760405191506000825260208201604052620002b2565b6040519150601f8416801560200281840101858101878315602002848b0101015b81831015620002a157805183526020928301920162000287565b5050858452601f01601f1916604052505b50949350505050565b634e487b7160e01b600052604160045260246000fd5b600181811c90821680620002e657607f821691505b6020821081036200030757634e487b7160e01b600052602260045260246000fd5b50919050565b601f8211156200035b57600081815260208120601f850160051c81016020861015620003365750805b601f850160051c820191505b81811015620003575782815560010162000342565b5050505b505050565b81516001600160401b038111156200037c576200037c620002bb565b62000394816200038d8454620002d1565b846200030d565b602080601f831160018114620003cc5760008415620003b35750858301515b600019600386901b1c1916600185901b17855562000357565b600085815260208120601f198616915b82811015620003fd57888601518255948401946001909101908401620003dc565b50858210156200041c5787850151600019600388901b60f8161c191681555b5050505050600190811b01905550565b600060208083528351808285015260005b818110156200045b578581018301518582016040015282016200043d565b506000604082860101526040601f19601f8301168501019250505092915050565b808201808211156200049e57634e487b7160e01b600052601160045260246000fd5b92915050565b61457880620004b46000396000f3fe6080604052600436106101485760003560e01c80636bb56a14116100c05780639790242111610074578063e30c397811610059578063e30c39781461041d578063ead3fbdf146101f2578063f2fde38b1461044857610183565b806397902421146103ea578063dedff9c6146103fd57610183565b806379ba5097116100a557806379ba5097146103765780637f23690c1461038b5780638da5cb5b1461039e57610183565b80636bb56a141461034e578063715018a61461036157610183565b806331858452116101175780634f04d60a116100fc5780634f04d60a146102fb57806354f6127f1461030e5780636963d4381461032e57610183565b806331858452146102bb57806344c028fe146102db57610183565b806301bfba61146101f257806301ffc9a71461021a5780631626ba7e1461024a57806328c4d14e1461029b57610183565b3661018357341561018157604051349033907f7e71433ddf847725166244795048ecf3e3f9f35628254ecbf73605666423349390600090a35b005b600036606034156101bc57604051349033907f7e71433ddf847725166244795048ecf3e3f9f35628254ecbf73605666423349390600090a35b60043610156101da57506040805160208101909152600081526101e7565b6101e48383610468565b90505b915050805190602001f35b3480156101fe57600080fd5b5061020760c881565b6040519081526020015b60405180910390f35b34801561022657600080fd5b5061023a610235366004613692565b610643565b6040519015158152602001610211565b34801561025657600080fd5b5061026a6102653660046137c9565b6107d8565b6040517fffffffff000000000000000000000000000000000000000000000000000000009091168152602001610211565b3480156102a757600080fd5b506101816102b636600461387b565b610aa9565b6102ce6102c93660046139e6565b610c10565b6040516102119190613bb1565b6102ee6102e9366004613bc4565b610cd4565b6040516102119190613c19565b610181610309366004613c2c565b610d6f565b34801561031a57600080fd5b506102ee610329366004613ca0565b610ec1565b34801561033a57600080fd5b506102ce610349366004613cb9565b610ecc565b6102ee61035c366004613d2e565b611042565b34801561036d57600080fd5b5061018161124a565b34801561038257600080fd5b5061018161133c565b6101816103993660046137c9565b611440565b3480156103aa57600080fd5b5060005473ffffffffffffffffffffffffffffffffffffffff165b60405173ffffffffffffffffffffffffffffffffffffffff9091168152602001610211565b6101816103f8366004613d7a565b6114db565b34801561040957600080fd5b506102ce610418366004613dd4565b611604565b34801561042957600080fd5b5060035473ffffffffffffffffffffffffffffffffffffffff166103c5565b34801561045457600080fd5b50610181610463366004613e09565b6116af565b606060006104996000357fffffffff0000000000000000000000000000000000000000000000000000000016611b4b565b90506000357fffffffff00000000000000000000000000000000000000000000000000000000161580156104e1575073ffffffffffffffffffffffffffffffffffffffff8116155b156104fc57505060408051602081019091526000815261063d565b73ffffffffffffffffffffffffffffffffffffffff8116610574576040517fbb370b2b0000000000000000000000000000000000000000000000000000000081527fffffffff000000000000000000000000000000000000000000000000000000006000351660048201526024015b60405180910390fd5b6000808273ffffffffffffffffffffffffffffffffffffffff16868633346040516020016105a59493929190613e24565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0818403018152908290526105dd91613e67565b6000604051808303816000865af19150503d806000811461061a576040519150601f19603f3d011682016040523d82523d6000602084013e61061f565b606091505b5091509150811561063457925061063d915050565b80518060208301fd5b92915050565b60007fffffffff0000000000000000000000000000000000000000000000000000000082167f1626ba7e0000000000000000000000000000000000000000000000000000000014806106d657507fffffffff0000000000000000000000000000000000000000000000000000000082167f24871b3d00000000000000000000000000000000000000000000000000000000145b8061072257507fffffffff0000000000000000000000000000000000000000000000000000000082167f6bb56a1400000000000000000000000000000000000000000000000000000000145b8061076e57507fffffffff0000000000000000000000000000000000000000000000000000000082167f94be599900000000000000000000000000000000000000000000000000000000145b806107ba57507fffffffff0000000000000000000000000000000000000000000000000000000082167f1a0eb6a500000000000000000000000000000000000000000000000000000000145b806107c957506107c982611bbb565b8061063d575061063d82611c11565b6000806107fa60005473ffffffffffffffffffffffffffffffffffffffff1690565b905073ffffffffffffffffffffffffffffffffffffffff81163b156109c6576000808273ffffffffffffffffffffffffffffffffffffffff16631626ba7e60e01b878760405160240161084e929190613e83565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529181526020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fffffffff000000000000000000000000000000000000000000000000000000009094169390931790925290516108d79190613e67565b600060405180830381855afa9150503d8060008114610912576040519150601f19603f3d011682016040523d82523d6000602084013e610917565b606091505b5091509150600082801561092c575081516020145b801561096c575081517f1626ba7e000000000000000000000000000000000000000000000000000000009061096a9084016020908101908501613e9c565b145b905080610999577fffffffff000000000000000000000000000000000000000000000000000000006109bb565b7f1626ba7e000000000000000000000000000000000000000000000000000000005b94505050505061063d565b6000806109d38686611c6d565b909250905060008160048111156109ec576109ec613eb5565b14610a1d57507fffffffff00000000000000000000000000000000000000000000000000000000925061063d915050565b8273ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1614610a76577fffffffff00000000000000000000000000000000000000000000000000000000610a98565b7f1626ba7e000000000000000000000000000000000000000000000000000000005b935050505061063d565b5092915050565b600080610ab883850185613d7a565b915091508573ffffffffffffffffffffffffffffffffffffffff166344c028fe600430600086868b604051602401610af293929190613ee4565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529181526020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167f4f04d60a000000000000000000000000000000000000000000000000000000001790525160e086901b7fffffffff00000000000000000000000000000000000000000000000000000000168152610ba29493929190600401613f59565b6000604051808303816000875af1158015610bc1573d6000803e3d6000fd5b505050506040513d6000823e601f3d9081017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0168201604052610c079190810190613f94565b50505050505050565b60603415610c4657604051349033907f7e71433ddf847725166244795048ecf3e3f9f35628254ecbf73605666423349390600090a35b60005473ffffffffffffffffffffffffffffffffffffffff1633819003610c7b57610c7386868686611cb2565b915050610ccc565b6000610c8682611e42565b90506000610c9688888888611cb2565b90508115610cc757610cc78382604051602001610cb39190613bb1565b604051602081830303815290604052612055565b925050505b949350505050565b60603415610d0a57604051349033907f7e71433ddf847725166244795048ecf3e3f9f35628254ecbf73605666423349390600090a35b60005473ffffffffffffffffffffffffffffffffffffffff1633819003610d3757610c7386868686612228565b6000610d4282611e42565b90506000610d5288888888612228565b90508115610cc757610cc78382604051602001610cb39190613c19565b60005473ffffffffffffffffffffffffffffffffffffffff163314610e62576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152606860248201527f556e6976657273616c50726f66696c65496e6974506f73744465706c6f796d6560448201527f6e744d6f64756c653a2073657444617461416e645472616e736665724f776e6560648201527f7273686970206f6e6c7920616c6c6f776564207468726f7567682064656c656760848201527f6174652063616c6c00000000000000000000000000000000000000000000000060a482015260c40161056b565b60005b8351811015610eb257610eaa848281518110610e8357610e83614002565b6020026020010151848381518110610e9d57610e9d614002565b60200260200101516123ca565b600101610e65565b50610ebc81611938565b505050565b606061063d8261243e565b60608167ffffffffffffffff811115610ee757610ee76136af565b604051908082528060200260200182016040528015610f1a57816020015b6060815260200190600190039081610f055790505b50905060005b82811015610aa25760008030868685818110610f3e57610f3e614002565b9050602002810190610f509190614031565b604051610f5e929190614096565b600060405180830381855af49150503d8060008114610f99576040519150601f19603f3d011682016040523d82523d6000602084013e610f9e565b606091505b50915091508161101a57805115610fb85780518082602001fd5b6040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601960248201527f4c5350303a20626174636843616c6c7320726576657274656400000000000000604482015260640161056b565b8084848151811061102d5761102d614002565b60209081029190910101525050600101610f20565b6060341561107857604051349033907f7e71433ddf847725166244795048ecf3e3f9f35628254ecbf73605666423349390600090a35b60006110a37f0cfc51aec37c55a4d0b1a65c6255c4bf2fbdf6277f3cc0730c45b828b6db8b4761243e565b9050606060148251106111185760006110bb836140a6565b60601c90506110ea817f6bb56a14000000000000000000000000000000000000000000000000000000006124e0565b156111165761111373ffffffffffffffffffffffffffffffffffffffff821688888833346125af565b91505b505b60006111447f0cfc51aec37c55a4d0b1000000000000000000000000000000000000000000008861274f565b905060006111518261243e565b9050606060148251106111c6576000611169836140a6565b60601c9050611198817f6bb56a14000000000000000000000000000000000000000000000000000000006124e0565b156111c4576111c173ffffffffffffffffffffffffffffffffffffffff82168b8b8b33346125af565b91505b505b83816040516020016111d99291906140f6565b604051602081830303815290604052955088343373ffffffffffffffffffffffffffffffffffffffff167f9c3ba68eb5742b8e3961aea0afc7371a71bf433c8a67a831803b64c064a178c28b8b8b60405161123693929190614164565b60405180910390a450505050509392505050565b60005473ffffffffffffffffffffffffffffffffffffffff1633819003611276576112736127cb565b50565b600061128182611e42565b905060006112a460005473ffffffffffffffffffffffffffffffffffffffff1690565b90506112ae6127cb565b60005473ffffffffffffffffffffffffffffffffffffffff1661131d5760408051602081019091526000815261131d9073ffffffffffffffffffffffffffffffffffffffff8316907fa4e59c931d14f7c8a7a35027f92ee40b5f2886b9fdcdb78f30bc5ecce5a2f81490612907565b8115610ebc57610ebc8360405180602001604052806000815250612055565b60035474010000000000000000000000000000000000000000900460ff1615611391576040517f5758dd0700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60005473ffffffffffffffffffffffffffffffffffffffff166113b26129ef565b6040805160208101909152600081526114049073ffffffffffffffffffffffffffffffffffffffff8316907fa4e59c931d14f7c8a7a35027f92ee40b5f2886b9fdcdb78f30bc5ecce5a2f81490612907565b6040805160208101909152600081526112739033907fceca317f109c43507871523e82dc2a3cc64dfa18f12da0b6db14f6e23f99553890612907565b341561147457604051349033907f7e71433ddf847725166244795048ecf3e3f9f35628254ecbf73605666423349390600090a35b60005473ffffffffffffffffffffffffffffffffffffffff163381900361149f57610ebc83836123ca565b60006114aa82611e42565b90506114b684846123ca565b80156114d5576114d58260405180602001604052806000815250612055565b50505050565b341561150f57604051349033907f7e71433ddf847725166244795048ecf3e3f9f35628254ecbf73605666423349390600090a35b805182511461154a576040517f3bcc897900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60005473ffffffffffffffffffffffffffffffffffffffff16338190036115945760005b83518110156114d55761158c848281518110610e8357610e83614002565b60010161156e565b600061159f82611e42565b905060005b84518110156115e4576115dc8582815181106115c2576115c2614002565b6020026020010151858381518110610e9d57610e9d614002565b6001016115a4565b5080156114d5576114d58260405180602001604052806000815250612055565b6060815167ffffffffffffffff811115611620576116206136af565b60405190808252806020026020018201604052801561165357816020015b606081526020019060019003908161163e5790505b50905060005b82518110156116a95761168483828151811061167757611677614002565b602002602001015161243e565b82828151811061169657611696614002565b6020908102919091010152600101611659565b50919050565b60005473ffffffffffffffffffffffffffffffffffffffff16338190036117f057600380547fffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffff167401000000000000000000000000000000000000000017905561171882612ac9565b8173ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff167f38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e2270060405160405180910390a36040805160208101909152600081526117c49073ffffffffffffffffffffffffffffffffffffffff8416907fe17117c9d2665d1dbeb479ed8058bbebde3c50ac50e2e65619f60006caac692690612907565b600380547fffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffff1690555050565b60006117fb82611e42565b600380547fffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffff1674010000000000000000000000000000000000000000179055905061184583612ac9565b8273ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff167f38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e2270060405160405180910390a36040805160208101909152600081526118f19073ffffffffffffffffffffffffffffffffffffffff8516907fe17117c9d2665d1dbeb479ed8058bbebde3c50ac50e2e65619f60006caac692690612907565b600380547fffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffff1690558015610ebc57610ebc8260405180602001604052806000815250612055565b60005473ffffffffffffffffffffffffffffffffffffffff828116911614611273576000805473ffffffffffffffffffffffffffffffffffffffff8381167fffffffffffffffffffffffff0000000000000000000000000000000000000000831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b6060816119dd81601f6141b9565b1015611a45576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600e60248201527f736c6963655f6f766572666c6f77000000000000000000000000000000000000604482015260640161056b565b611a4f82846141b9565b84511015611ab9576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601160248201527f736c6963655f6f75744f66426f756e6473000000000000000000000000000000604482015260640161056b565b606082158015611ad85760405191506000825260208201604052611b40565b6040519150601f8416801560200281840101858101878315602002848b0101015b81831015611b11578051835260209283019201611af9565b5050858452601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016604052505b5090505b9392505050565b600080611b9a7fcee78b4094da86011096000000000000000000000000000000000000000000007fffffffff00000000000000000000000000000000000000000000000000000000851661274f565b90506000611ba78261243e565b611bb0906140a6565b60601c949350505050565b60007fffffffff0000000000000000000000000000000000000000000000000000000082167fa918fa6b00000000000000000000000000000000000000000000000000000000148061063d575061063d82612b64565b600080611c3d7f01ffc9a700000000000000000000000000000000000000000000000000000000611b4b565b905073ffffffffffffffffffffffffffffffffffffffff8116611c635750600092915050565b611b4481846124e0565b6000808251604103611ca35760208301516040840151606085015160001a611c9787828585612bba565b94509450505050611cab565b506000905060025b9250929050565b606083518551141580611cd3575082518451141580611cd357508151835114155b15611d0a576040517f3ff55f4d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8451600003611d45576040517fe9ad2b5f00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000855167ffffffffffffffff811115611d6157611d616136af565b604051908082528060200260200182016040528015611d9457816020015b6060815260200190600190039081611d7f5790505b50905060005b8651811015611e3857611e13878281518110611db857611db8614002565b6020026020010151878381518110611dd257611dd2614002565b6020026020010151878481518110611dec57611dec614002565b6020026020010151878581518110611e0657611e06614002565b6020026020010151612228565b828281518110611e2557611e25614002565b6020908102919091010152600101611d9a565b5095945050505050565b60008060008373ffffffffffffffffffffffffffffffffffffffff16639bf04b1160e01b3334600036604051602401611e7e94939291906141cc565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529181526020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fffffffff00000000000000000000000000000000000000000000000000000000909416939093179092529051611f079190613e67565b6000604051808303816000865af19150503d8060008114611f44576040519150601f19603f3d011682016040523d82523d6000602084013e611f49565b606091505b5091509150611f5a60008383612ca9565b600081806020019051810190611f709190614202565b90507fffffff000000000000000000000000000000000000000000000000000000000081167f9bf04b000000000000000000000000000000000000000000000000000000000014611ff2576000826040517fd088ec4000000000000000000000000000000000000000000000000000000000815260040161056b92919061421f565b7f01000000000000000000000000000000000000000000000000000000000000007fff00000000000000000000000000000000000000000000000000000000000000600383901a60f81b161461204957600061204c565b60015b95945050505050565b6000808373ffffffffffffffffffffffffffffffffffffffff1663d3fc45d360e01b333460003660405160200161208f949392919061423a565b60405160208183030381529060405280519060200120856040516024016120b7929190613e83565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529181526020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fffffffff000000000000000000000000000000000000000000000000000000009094169390931790925290516121409190613e67565b6000604051808303816000865af19150503d806000811461217d576040519150601f19603f3d011682016040523d82523d6000602084013e612182565b606091505b509150915061219360018383612ca9565b80517fd3fc45d300000000000000000000000000000000000000000000000000000000906121ca9083016020908101908401614202565b7fffffffff0000000000000000000000000000000000000000000000000000000016146114d5576001816040517fd088ec4000000000000000000000000000000000000000000000000000000000815260040161056b92919061421f565b6060846122415761223a848484612d32565b9050610ccc565b600185036122a15773ffffffffffffffffffffffffffffffffffffffff841615612297576040517f3041824a00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b61223a8383612ea9565b600285036123015773ffffffffffffffffffffffffffffffffffffffff8416156122f7576040517f3041824a00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b61223a8383613022565b6003850361234b578215612341576040517f72f2bc6a00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b61223a8483613145565b6004850361239557821561238b576040517f5ac8313500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b61223a848361326d565b6040517f7583b3bc0000000000000000000000000000000000000000000000000000000081526004810186905260240161056b565b60008281526001602052604090206123e2828261431b565b50817fece574603820d07bc9b91f2a932baadf4628aabcb8afba49776529c14a6104b2610100835111156124235761241e8360006101006119cf565b612425565b825b6040516124329190613c19565b60405180910390a25050565b600081815260016020526040902080546060919061245b90614280565b80601f016020809104026020016040519081016040528092919081815260200182805461248790614280565b80156124d45780601f106124a9576101008083540402835291602001916124d4565b820191906000526020600020905b8154815290600101906020018083116124b757829003601f168201915b50505050509050919050565b604080517fffffffff000000000000000000000000000000000000000000000000000000008316602480830191909152825180830390910181526044909101909152602080820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167f01ffc9a700000000000000000000000000000000000000000000000000000000178152825160009392849283928392918391908a617530fa92503d91506000519050828015612598575060208210155b80156125a45750600081115b979650505050505050565b60606000636bb56a1460e01b8787876040516024016125d093929190614435565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0818403018152918152602080830180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fffffffff00000000000000000000000000000000000000000000000000000000909516949094179093525161265d92879187910161444f565b60405160208183030381529060405290506000808973ffffffffffffffffffffffffffffffffffffffff16836040516126969190613e67565b6000604051808303816000865af19150503d80600081146126d3576040519150601f19603f3d011682016040523d82523d6000602084013e6126d8565b606091505b509150915061271d82826040518060400160405280602081526020017f43616c6c20746f20756e6976657273616c5265636569766572206661696c656481525061334b565b50805160000361272d5780612741565b808060200190518101906127419190613f94565b9a9950505050505050505050565b604080517fffffffffffffffffffff00000000000000000000000000000000000000000000841660208201526000602a82018190527fffffffffffffffffffffffffffffffffffffffff0000000000000000000000008416602c83015291829101604051602081830303815290604052905080610ccc906144a1565b60025443906000906127df9060c8906141b9565b905060006127ee60c8836141b9565b9050808311806127fe5750600254155b1561285e576002839055600380547fffffffffffffffffffffffff00000000000000000000000000000000000000001690556040517f81b7f830f1f0084db6497c486cbe6974c86488dcc4e3738eab94ab6d6b1653e790600090a1505050565b818310156128a2576040517f8b9bf507000000000000000000000000000000000000000000000000000000008152600481018390526024810182905260440161056b565b6128ac6000611938565b60006002819055600380547fffffffffffffffffffffffff00000000000000000000000000000000000000001690556040517fd1f66c3d2bc1993a86be5e3d33709d98f0442381befcedd29f578b9b2506b1ce9190a1505050565b612931837f6bb56a14000000000000000000000000000000000000000000000000000000006124e0565b15610ebc576040517f6bb56a1400000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff841690636bb56a149061298a9085908590600401613e83565b6000604051808303816000875af11580156129a9573d6000803e3d6000fd5b505050506040513d6000823e601f3d9081017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01682016040526114d59190810190613f94565b60035473ffffffffffffffffffffffffffffffffffffffff163314612a96576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602560248201527f4c535031343a2063616c6c6572206973206e6f74207468652070656e64696e6760448201527f4f776e6572000000000000000000000000000000000000000000000000000000606482015260840161056b565b612a9f33611938565b600380547fffffffffffffffffffffffff0000000000000000000000000000000000000000169055565b3073ffffffffffffffffffffffffffffffffffffffff821603612b18576040517f43b248cd00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600380547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff929092169190911790556000600255565b60007fffffffff0000000000000000000000000000000000000000000000000000000082167f629aa69400000000000000000000000000000000000000000000000000000000148061063d575061063d82613364565b6000807f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a0831115612bf15750600090506003612ca0565b6040805160008082526020820180845289905260ff881692820192909252606081018690526080810185905260019060a0016020604051602081039080840390855afa158015612c45573d6000803e3d6000fd5b50506040517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0015191505073ffffffffffffffffffffffffffffffffffffffff8116612c9957600060019250925050612ca0565b9150600090505b94509492505050565b81612cb857612cb883826133fb565b602081511080612cf7575060006020612cd0836144a1565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffff00000000911b1614155b15610ebc5782816040517fd088ec4000000000000000000000000000000000000000000000000000000000815260040161056b92919061421f565b606082471015612d77576040517f0df9a8f80000000000000000000000000000000000000000000000000000000081524760048201526024810184905260440161056b565b8273ffffffffffffffffffffffffffffffffffffffff851660007f4810874456b8e6487bd861375cf6abd8e1c8bb5858c8ce36a86a04dabfac199e612dbb866144e3565b6040517fffffffff00000000000000000000000000000000000000000000000000000000909116815260200160405180910390a46000808573ffffffffffffffffffffffffffffffffffffffff168585604051612e189190613e67565b60006040518083038185875af1925050503d8060008114612e55576040519150601f19603f3d011682016040523d82523d6000602084013e612e5a565b606091505b5091509150612e9f82826040518060400160405280601681526020017f455243373235583a20556e6b6e6f776e204572726f720000000000000000000081525061334b565b9695505050505050565b606082471015612eee576040517f0df9a8f80000000000000000000000000000000000000000000000000000000081524760048201526024810184905260440161056b565b8151600003612f29576040517fb81cd8d900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600082516020840185f0905073ffffffffffffffffffffffffffffffffffffffff8116612f82576040517f0b07489b00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b838173ffffffffffffffffffffffffffffffffffffffff1660017fa1fb700aaee2ae4a2ff6f91ce7eba292f89c2f5488b8ec4c5c5c8150692595c36000801b604051612fd091815260200190565b60405180910390a46040517fffffffffffffffffffffffffffffffffffffffff000000000000000000000000606083901b16602082015260340160405160208183030381529060405291505092915050565b6060815160000361305f576040517fb81cd8d900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60006130788360208551613073919061452f565b613441565b9050600061309584600060208751613090919061452f565b6119cf565b905060006130a48684846134c1565b9050858173ffffffffffffffffffffffffffffffffffffffff1660027fa1fb700aaee2ae4a2ff6f91ce7eba292f89c2f5488b8ec4c5c5c8150692595c3866040516130f191815260200190565b60405180910390a46040517fffffffffffffffffffffffffffffffffffffffff000000000000000000000000606083901b166020820152603401604051602081830303815290604052935050505092915050565b6060600073ffffffffffffffffffffffffffffffffffffffff841660037f4810874456b8e6487bd861375cf6abd8e1c8bb5858c8ce36a86a04dabfac199e61318c866144e3565b6040517fffffffff00000000000000000000000000000000000000000000000000000000909116815260200160405180910390a46000808473ffffffffffffffffffffffffffffffffffffffff16846040516131e89190613e67565b600060405180830381855afa9150503d8060008114613223576040519150601f19603f3d011682016040523d82523d6000602084013e613228565b606091505b509150915061204c82826040518060400160405280601681526020017f455243373235583a20556e6b6e6f776e204572726f720000000000000000000081525061334b565b6060600073ffffffffffffffffffffffffffffffffffffffff841660047f4810874456b8e6487bd861375cf6abd8e1c8bb5858c8ce36a86a04dabfac199e6132b4866144e3565b6040517fffffffff00000000000000000000000000000000000000000000000000000000909116815260200160405180910390a46000808473ffffffffffffffffffffffffffffffffffffffff16846040516133109190613e67565b600060405180830381855af49150503d8060008114613223576040519150601f19603f3d011682016040523d82523d6000602084013e613228565b6060831561335a575081611b44565b611b448383613620565b60007fffffffff0000000000000000000000000000000000000000000000000000000082167f7545acac00000000000000000000000000000000000000000000000000000000148061063d57507f01ffc9a7000000000000000000000000000000000000000000000000000000007fffffffff0000000000000000000000000000000000000000000000000000000083161461063d565b80511561340b5780518082602001fd5b6040517f8c6a8ae3000000000000000000000000000000000000000000000000000000008152821515600482015260240161056b565b600061344e8260206141b9565b835110156134b8576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601560248201527f746f427974657333325f6f75744f66426f756e64730000000000000000000000604482015260640161056b565b50016020015190565b60008347101561352d576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601d60248201527f437265617465323a20696e73756666696369656e742062616c616e6365000000604482015260640161056b565b8151600003613598576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820181905260248201527f437265617465323a2062797465636f6465206c656e677468206973207a65726f604482015260640161056b565b8282516020840186f5905073ffffffffffffffffffffffffffffffffffffffff8116611b44576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601960248201527f437265617465323a204661696c6564206f6e206465706c6f7900000000000000604482015260640161056b565b8151156136305781518083602001fd5b806040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161056b9190613c19565b7fffffffff000000000000000000000000000000000000000000000000000000008116811461127357600080fd5b6000602082840312156136a457600080fd5b8135611b4481613664565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016810167ffffffffffffffff81118282101715613725576137256136af565b604052919050565b600067ffffffffffffffff821115613747576137476136af565b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01660200190565b600082601f83011261378457600080fd5b81356137976137928261372d565b6136de565b8181528460208386010111156137ac57600080fd5b816020850160208301376000918101602001919091529392505050565b600080604083850312156137dc57600080fd5b82359150602083013567ffffffffffffffff8111156137fa57600080fd5b61380685828601613773565b9150509250929050565b803573ffffffffffffffffffffffffffffffffffffffff8116811461383457600080fd5b919050565b60008083601f84011261384b57600080fd5b50813567ffffffffffffffff81111561386357600080fd5b602083019150836020828501011115611cab57600080fd5b6000806000806060858703121561389157600080fd5b61389a85613810565b93506138a860208601613810565b9250604085013567ffffffffffffffff8111156138c457600080fd5b6138d087828801613839565b95989497509550505050565b600067ffffffffffffffff8211156138f6576138f66136af565b5060051b60200190565b600082601f83011261391157600080fd5b81356020613921613792836138dc565b82815260059290921b8401810191818101908684111561394057600080fd5b8286015b8481101561395b5780358352918301918301613944565b509695505050505050565b600082601f83011261397757600080fd5b81356020613987613792836138dc565b82815260059290921b840181019181810190868411156139a657600080fd5b8286015b8481101561395b57803567ffffffffffffffff8111156139ca5760008081fd5b6139d88986838b0101613773565b8452509183019183016139aa565b600080600080608085870312156139fc57600080fd5b843567ffffffffffffffff80821115613a1457600080fd5b613a2088838901613900565b9550602091508187013581811115613a3757600080fd5b8701601f81018913613a4857600080fd5b8035613a56613792826138dc565b81815260059190911b8201840190848101908b831115613a7557600080fd5b928501925b82841015613a9a57613a8b84613810565b82529285019290850190613a7a565b97505050506040870135915080821115613ab357600080fd5b613abf88838901613900565b93506060870135915080821115613ad557600080fd5b50613ae287828801613966565b91505092959194509250565b60005b83811015613b09578181015183820152602001613af1565b50506000910152565b60008151808452613b2a816020860160208601613aee565b601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160200192915050565b600081518084526020808501808196508360051b8101915082860160005b85811015613ba4578284038952613b92848351613b12565b98850198935090840190600101613b7a565b5091979650505050505050565b602081526000611b446020830184613b5c565b60008060008060808587031215613bda57600080fd5b84359350613bea60208601613810565b925060408501359150606085013567ffffffffffffffff811115613c0d57600080fd5b613ae287828801613773565b602081526000611b446020830184613b12565b600080600060608486031215613c4157600080fd5b833567ffffffffffffffff80821115613c5957600080fd5b613c6587838801613900565b94506020860135915080821115613c7b57600080fd5b50613c8886828701613966565b925050613c9760408501613810565b90509250925092565b600060208284031215613cb257600080fd5b5035919050565b60008060208385031215613ccc57600080fd5b823567ffffffffffffffff80821115613ce457600080fd5b818501915085601f830112613cf857600080fd5b813581811115613d0757600080fd5b8660208260051b8501011115613d1c57600080fd5b60209290920196919550909350505050565b600080600060408486031215613d4357600080fd5b83359250602084013567ffffffffffffffff811115613d6157600080fd5b613d6d86828701613839565b9497909650939450505050565b60008060408385031215613d8d57600080fd5b823567ffffffffffffffff80821115613da557600080fd5b613db186838701613900565b93506020850135915080821115613dc757600080fd5b5061380685828601613966565b600060208284031215613de657600080fd5b813567ffffffffffffffff811115613dfd57600080fd5b610ccc84828501613900565b600060208284031215613e1b57600080fd5b611b4482613810565b8385823760609290921b7fffffffffffffffffffffffffffffffffffffffff00000000000000000000000016919092019081526014810191909152603401919050565b60008251613e79818460208701613aee565b9190910192915050565b828152604060208201526000610ccc6040830184613b12565b600060208284031215613eae57600080fd5b5051919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b606080825284519082018190526000906020906080840190828801845b82811015613f1d57815184529284019290840190600101613f01565b50505083810382850152613f318187613b5c565b9250505073ffffffffffffffffffffffffffffffffffffffff83166040830152949350505050565b84815273ffffffffffffffffffffffffffffffffffffffff84166020820152826040820152608060608201526000612e9f6080830184613b12565b600060208284031215613fa657600080fd5b815167ffffffffffffffff811115613fbd57600080fd5b8201601f81018413613fce57600080fd5b8051613fdc6137928261372d565b818152856020838501011115613ff157600080fd5b61204c826020830160208601613aee565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b60008083357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe184360301811261406657600080fd5b83018035915067ffffffffffffffff82111561408157600080fd5b602001915036819003821315611cab57600080fd5b8183823760009101908152919050565b6000815160208301517fffffffffffffffffffffffffffffffffffffffff000000000000000000000000808216935060148310156140ee5780818460140360031b1b83161693505b505050919050565b6040815260006141096040830185613b12565b828103602084015261204c8185613b12565b8183528181602085013750600060208284010152600060207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f840116840101905092915050565b60408152600061417860408301858761411b565b8281036020840152612e9f8185613b12565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b8082018082111561063d5761063d61418a565b73ffffffffffffffffffffffffffffffffffffffff85168152836020820152606060408201526000612e9f60608301848661411b565b60006020828403121561421457600080fd5b8151611b4481613664565b8215158152604060208201526000610ccc6040830184613b12565b7fffffffffffffffffffffffffffffffffffffffff0000000000000000000000008560601b16815283601482015281836034830137600091016034019081529392505050565b600181811c9082168061429457607f821691505b6020821081036116a9577f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b601f821115610ebc57600081815260208120601f850160051c810160208610156142f45750805b601f850160051c820191505b8181101561431357828155600101614300565b505050505050565b815167ffffffffffffffff811115614335576143356136af565b614349816143438454614280565b846142cd565b602080601f83116001811461439c57600084156143665750858301515b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600386901b1c1916600185901b178555614313565b6000858152602081207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08616915b828110156143e9578886015182559484019460019091019084016143ca565b508582101561442557878501517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600388901b60f8161c191681555b5050505050600190811b01905550565b83815260406020820152600061204c60408301848661411b565b60008451614461818460208901613aee565b60609490941b7fffffffffffffffffffffffffffffffffffffffff0000000000000000000000001691909301908152601481019190915260340192915050565b805160208083015191908110156116a9577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff60209190910360031b1b16919050565b6000815160208301517fffffffff00000000000000000000000000000000000000000000000000000000808216935060048310156140ee5760049290920360031b82901b161692915050565b8181038181111561063d5761063d61418a56fea2646970667358221220709024acd2bc0a3533c747974ab8f11d519f7708b55eb0107adc4574f225e7d964736f6c63430008110033 +``` + +## Universal Profile Post Deployment Module Source Code + +This is an exact copy of the code of the [`UniversalProfilePostDeploymentModule` smart contract]. + +- The source code is generated with `solc` compiler version `0.8.17` and with `9999999` optimization runs. +- The imported contracts are part of the `4.9.2` version of the `@openzeppelin/contracts` package. +- Navigate to [lsp-smart-contract](https://github.com/lukso-network/lsp-smart-contracts) repo and checkout to `b8eca3c5696acf85239130ef67edec9e8c134bfa` commit to obtain the exact copy of the code, change the compiler settings in `hardhat.config.ts` and compile to produce the same bytecode. + +```solidity +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.4; + +import { OPERATION_4_DELEGATECALL } from "@erc725/smart-contracts/contracts/constants.sol"; + +import { UniversalProfile } from "../../UniversalProfile.sol"; + +contract UniversalProfilePostDeploymentModule is UniversalProfile { + constructor() UniversalProfile(address(0)) {} + + function setDataAndTransferOwnership( + bytes32[] memory dataKeys, + bytes[] memory dataValues, + address newOwner + ) public payable { + // check that the msg.sender is the owner + require( + msg.sender == owner(), + "UniversalProfilePostDeploymentModule: setDataAndTransferOwnership only allowed through delegate call" + ); + + // update the dataKeys and dataValues in the UniversalProfile contract + for (uint256 i = 0; i < dataKeys.length; ) { + _setData(dataKeys[i], dataValues[i]); + + unchecked { + ++i; + } + } + + // transfer the ownership of the UniversalProfile contract to the newOwner + _setOwner(newOwner); + } + + function executePostDeployment( + address universalProfile, + address keyManager, + bytes calldata setDataBatchBytes + ) public { + // retrieve the dataKeys and dataValues to setData from the initializationCalldata bytes + (bytes32[] memory dataKeys, bytes[] memory dataValues) = abi.decode( + setDataBatchBytes, + (bytes32[], bytes[]) + ); + + // call the execute function with delegate_call on the universalProfile contract to setData and transferOwnership + UniversalProfile(payable(universalProfile)).execute( + OPERATION_4_DELEGATECALL, + address(this), + 0, + abi.encodeWithSignature( + "setDataAndTransferOwnership(bytes32[],bytes[],address)", + dataKeys, + dataValues, + keyManager + ) + ); + } +} +``` diff --git a/contracts/LSP25ExecuteRelayCall/LSP25Constants.sol b/contracts/LSP25ExecuteRelayCall/LSP25Constants.sol index 5b0a4b5df..a71e3202d 100644 --- a/contracts/LSP25ExecuteRelayCall/LSP25Constants.sol +++ b/contracts/LSP25ExecuteRelayCall/LSP25Constants.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: CC0-1.0 +// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.4; // --- ERC165 interface ids diff --git a/contracts/LSP25ExecuteRelayCall/LSP25MultiChannelNonce.sol b/contracts/LSP25ExecuteRelayCall/LSP25MultiChannelNonce.sol index ff90206f1..fb8c99015 100644 --- a/contracts/LSP25ExecuteRelayCall/LSP25MultiChannelNonce.sol +++ b/contracts/LSP25ExecuteRelayCall/LSP25MultiChannelNonce.sol @@ -56,8 +56,7 @@ abstract contract LSP25MultiChannelNonce { address from, uint128 channelId ) internal view virtual returns (uint256 idx) { - uint256 nonceInChannel = _nonceStore[from][channelId]; - return (uint256(channelId) << 128) | nonceInChannel; + return (uint256(channelId) << 128) | _nonceStore[from][channelId]; } /** @@ -109,10 +108,16 @@ abstract contract LSP25MultiChannelNonce { uint128 startingTimestamp = uint128(validityTimestamps >> 128); uint128 endingTimestamp = uint128(validityTimestamps); - // solhint-disable not-rely-on-time + // solhint-disable-next-line not-rely-on-time if (block.timestamp < startingTimestamp) { revert RelayCallBeforeStartTime(); } + + // Allow `endingTimestamp` to be 0 + // Allow execution anytime past `startingTimestamp` + if (endingTimestamp == 0) return; + + // solhint-disable-next-line not-rely-on-time if (block.timestamp > endingTimestamp) { revert RelayCallExpired(); } diff --git a/contracts/LSP2ERC725YJSONSchema/LSP2Utils.sol b/contracts/LSP2ERC725YJSONSchema/LSP2Utils.sol index ff5d87553..adabf11d7 100644 --- a/contracts/LSP2ERC725YJSONSchema/LSP2Utils.sol +++ b/contracts/LSP2ERC725YJSONSchema/LSP2Utils.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: GPL-3.0 +// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.4; // interfaces @@ -6,9 +6,6 @@ import { IERC725Y } from "@erc725/smart-contracts/contracts/interfaces/IERC725Y.sol"; -// libraries -import {BytesLib} from "solidity-bytes-utils/contracts/BytesLib.sol"; - /** * @title LSP2 Utility library. * @author Jean Cavallera , Yamen Merhi , Daniel Afteni @@ -17,8 +14,6 @@ import {BytesLib} from "solidity-bytes-utils/contracts/BytesLib.sol"; * Based on LSP2 ERC725Y JSON Schema standard. */ library LSP2Utils { - using BytesLib for bytes; - /** * @dev Generates a data key of keyType Singleton by hashing the string `keyName`. As: * @@ -285,97 +280,6 @@ library LSP2Utils { return abi.encodePacked(bytes4(hashFunctionDigest), jsonDigest, url); } - /** - * @dev Verify if `data` is an abi-encoded array. - * - * @param data The bytes value to verify. - * - * @return `true` if the `data` represents an abi-encoded array, `false` otherwise. - */ - function isEncodedArray(bytes memory data) internal pure returns (bool) { - uint256 nbOfBytes = data.length; - - // there must be at least 32 x length bytes after offset - uint256 offset = uint256(bytes32(data)); - if (nbOfBytes < offset + 32) return false; - uint256 arrayLength = data.toUint256(offset); - - // 32 bytes word (= offset) - // + 32 bytes word (= array length) - // + remaining bytes that make each element of the array - if (nbOfBytes < (offset + 32 + (arrayLength * 32))) return false; - - return true; - } - - /** - * @dev Verify if `data` is an abi-encoded array of addresses (`address[]`) encoded according to the ABI specs. - * - * @param data The bytes value to verify. - * - * @return `true` if the `data` represents an abi-encoded array of addresses, `false` otherwise. - */ - function isEncodedArrayOfAddresses( - bytes memory data - ) internal pure returns (bool) { - if (!isEncodedArray(data)) return false; - - uint256 offset = uint256(bytes32(data)); - uint256 arrayLength = data.toUint256(offset); - - uint256 pointer = offset + 32; - - for (uint256 ii = 0; ii < arrayLength; ) { - bytes32 key = data.toBytes32(pointer); - - // check that the leading bytes are zero bytes "00" - // NB: address type is padded on the left (unlike bytes20 type that is padded on the right) - if (bytes12(key) != bytes12(0)) return false; - - // increment the pointer - pointer += 32; - - unchecked { - ++ii; - } - } - - return true; - } - - /** - * @dev Verify if `data` is an abi-array of `bytes4` values (`bytes4[]`) encoded according to the ABI specs. - * - * @param data The bytes value to verify. - * - * @return `true` if the `data` represents an abi-encoded array of `bytes4`, `false` otherwise. - */ - function isBytes4EncodedArray( - bytes memory data - ) internal pure returns (bool) { - if (!isEncodedArray(data)) return false; - - uint256 offset = uint256(bytes32(data)); - uint256 arrayLength = data.toUint256(offset); - uint256 pointer = offset + 32; - - for (uint256 ii = 0; ii < arrayLength; ) { - bytes32 key = data.toBytes32(pointer); - - // check that the trailing bytes are zero bytes "00" - if (uint224(uint256(key)) != 0) return false; - - // increment the pointer - pointer += 32; - - unchecked { - ++ii; - } - } - - return true; - } - /** * @dev Verify if `data` is a valid array of value encoded as a `CompactBytesArray` according to the LSP2 `CompactBytesArray` valueType specification. * diff --git a/contracts/LSP3ProfileMetadata/LSP3Constants.sol b/contracts/LSP3ProfileMetadata/LSP3Constants.sol index 724e259f8..30f137e28 100644 --- a/contracts/LSP3ProfileMetadata/LSP3Constants.sol +++ b/contracts/LSP3ProfileMetadata/LSP3Constants.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: CC0-1.0 +// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.4; // bytes10(keccak256('SupportedStandards')) + bytes2(0) + bytes20(keccak256('LSP3Profile')) diff --git a/contracts/LSP4DigitalAssetMetadata/ILSP4Compatibility.sol b/contracts/LSP4DigitalAssetMetadata/ILSP4Compatibility.sol deleted file mode 100644 index 5fcd791b7..000000000 --- a/contracts/LSP4DigitalAssetMetadata/ILSP4Compatibility.sol +++ /dev/null @@ -1,25 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.4; - -// interfaces -import { - IERC725Y -} from "@erc725/smart-contracts/contracts/interfaces/IERC725Y.sol"; - -/** - * @dev LSP4 extension, for compatibility with clients & tools that expect ERC20/721. - */ -interface ILSP4Compatibility is IERC725Y { - /** - * @dev Returns the name of the token. - * @return The name of the token - */ - function name() external view returns (string memory); - - /** - * @dev Returns the symbol of the token, usually a shorter version of the name. - * @return The symbol of the token - */ - function symbol() external view returns (string memory); -} diff --git a/contracts/LSP4DigitalAssetMetadata/LSP4Compatibility.sol b/contracts/LSP4DigitalAssetMetadata/LSP4Compatibility.sol deleted file mode 100644 index 7442f739e..000000000 --- a/contracts/LSP4DigitalAssetMetadata/LSP4Compatibility.sol +++ /dev/null @@ -1,38 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; - -// interfaces -import {ILSP4Compatibility} from "./ILSP4Compatibility.sol"; - -// modules -import {ERC725YCore} from "@erc725/smart-contracts/contracts/ERC725YCore.sol"; - -// constants -import "./LSP4Constants.sol"; - -/** - * @title LSP4Compatibility - * @author Matthew Stevens - * @dev LSP4 extension, for compatibility with clients & tools that expect ERC20/721. - */ -abstract contract LSP4Compatibility is ILSP4Compatibility, ERC725YCore { - // --- Token queries - - /** - * @dev Returns the name of the token. - * @return The name of the token - */ - function name() public view virtual override returns (string memory) { - bytes memory data = _getData(_LSP4_TOKEN_NAME_KEY); - return string(data); - } - - /** - * @dev Returns the symbol of the token, usually a shorter version of the name. - * @return The symbol of the token - */ - function symbol() public view virtual override returns (string memory) { - bytes memory data = _getData(_LSP4_TOKEN_SYMBOL_KEY); - return string(data); - } -} diff --git a/contracts/LSP4DigitalAssetMetadata/LSP4Constants.sol b/contracts/LSP4DigitalAssetMetadata/LSP4Constants.sol index 2ce7a1a4b..09547e37a 100644 --- a/contracts/LSP4DigitalAssetMetadata/LSP4Constants.sol +++ b/contracts/LSP4DigitalAssetMetadata/LSP4Constants.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: CC0-1.0 +// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.4; // --- ERC725Y entries diff --git a/contracts/LSP4DigitalAssetMetadata/LSP4DigitalAssetMetadata.sol b/contracts/LSP4DigitalAssetMetadata/LSP4DigitalAssetMetadata.sol index 9aedc4f0b..a57731572 100644 --- a/contracts/LSP4DigitalAssetMetadata/LSP4DigitalAssetMetadata.sol +++ b/contracts/LSP4DigitalAssetMetadata/LSP4DigitalAssetMetadata.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: MIT +// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.4; // modules @@ -8,7 +8,12 @@ import {ERC725Y} from "@erc725/smart-contracts/contracts/ERC725Y.sol"; import {BytesLib} from "solidity-bytes-utils/contracts/BytesLib.sol"; // constants -import "./LSP4Constants.sol"; +import { + _LSP4_SUPPORTED_STANDARDS_KEY, + _LSP4_SUPPORTED_STANDARDS_VALUE, + _LSP4_TOKEN_NAME_KEY, + _LSP4_TOKEN_SYMBOL_KEY +} from "./LSP4Constants.sol"; // errors import { @@ -46,7 +51,7 @@ abstract contract LSP4DigitalAssetMetadata is ERC725Y { /** * @dev The ERC725Y data keys `LSP4TokenName` and `LSP4TokenSymbol` cannot be changed - * via this function once the digital asset contract has been deployed. + * via this function once the digital asset contract has been deployed. * * @dev Save gas by emitting the {DataChanged} event with only the first 256 bytes of dataValue */ diff --git a/contracts/LSP4DigitalAssetMetadata/LSP4DigitalAssetMetadataInitAbstract.sol b/contracts/LSP4DigitalAssetMetadata/LSP4DigitalAssetMetadataInitAbstract.sol index a77c55c9f..56603d4ff 100644 --- a/contracts/LSP4DigitalAssetMetadata/LSP4DigitalAssetMetadataInitAbstract.sol +++ b/contracts/LSP4DigitalAssetMetadata/LSP4DigitalAssetMetadataInitAbstract.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: MIT +// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.4; // modules @@ -10,7 +10,12 @@ import { import {BytesLib} from "solidity-bytes-utils/contracts/BytesLib.sol"; // constants -import "./LSP4Constants.sol"; +import { + _LSP4_SUPPORTED_STANDARDS_KEY, + _LSP4_SUPPORTED_STANDARDS_VALUE, + _LSP4_TOKEN_NAME_KEY, + _LSP4_TOKEN_SYMBOL_KEY +} from "./LSP4Constants.sol"; // errors import { @@ -50,7 +55,7 @@ abstract contract LSP4DigitalAssetMetadataInitAbstract is ERC725YInitAbstract { /** * @dev the ERC725Y data keys `LSP4TokenName` and `LSP4TokenSymbol` cannot be changed - * via this function once the digital asset contract has been deployed. + * via this function once the digital asset contract has been deployed. * * @dev Save gas by emitting the {DataChanged} event with only the first 256 bytes of dataValue */ diff --git a/contracts/LSP5ReceivedAssets/LSP5Utils.sol b/contracts/LSP5ReceivedAssets/LSP5Utils.sol index cea4fdf93..83ea23d46 100644 --- a/contracts/LSP5ReceivedAssets/LSP5Utils.sol +++ b/contracts/LSP5ReceivedAssets/LSP5Utils.sol @@ -7,12 +7,13 @@ import { } from "@erc725/smart-contracts/contracts/interfaces/IERC725Y.sol"; // libraries -import {BytesLib} from "solidity-bytes-utils/contracts/BytesLib.sol"; import {LSP2Utils} from "../LSP2ERC725YJSONSchema/LSP2Utils.sol"; // constants -import "../LSP5ReceivedAssets/LSP5Constants.sol"; -import "../LSP7DigitalAsset/LSP7Constants.sol"; +import { + _LSP5_RECEIVED_ASSETS_MAP_KEY_PREFIX, + _LSP5_RECEIVED_ASSETS_ARRAY_KEY +} from "../LSP5ReceivedAssets/LSP5Constants.sol"; /** * @title LSP5 Utility library. diff --git a/contracts/LSP6KeyManager/LSP6Constants.sol b/contracts/LSP6KeyManager/LSP6Constants.sol index faeba9d97..79d3fd7e1 100644 --- a/contracts/LSP6KeyManager/LSP6Constants.sol +++ b/contracts/LSP6KeyManager/LSP6Constants.sol @@ -1,8 +1,8 @@ -// SPDX-License-Identifier: CC0-1.0 +// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.4; // --- ERC165 interface ids -bytes4 constant _INTERFACEID_LSP6 = 0x66918867; +bytes4 constant _INTERFACEID_LSP6 = 0x23f34c62; // --- ERC725Y Data Keys @@ -52,9 +52,10 @@ bytes32 constant _PERMISSION_SETDATA = 0x000000000000000 bytes32 constant _PERMISSION_ENCRYPT = 0x0000000000000000000000000000000000000000000000000000000000080000; bytes32 constant _PERMISSION_DECRYPT = 0x0000000000000000000000000000000000000000000000000000000000100000; bytes32 constant _PERMISSION_SIGN = 0x0000000000000000000000000000000000000000000000000000000000200000; +bytes32 constant _PERMISSION_EXECUTE_RELAY_CALL = 0x0000000000000000000000000000000000000000000000000000000000400000; // All Permissions currently exclude REENTRANCY, DELEGATECALL and SUPER_DELEGATECALL for security -bytes32 constant ALL_REGULAR_PERMISSIONS = 0x00000000000000000000000000000000000000000000000000000000003f3f7f; +bytes32 constant ALL_REGULAR_PERMISSIONS = 0x00000000000000000000000000000000000000000000000000000000007f3f7f; // AllowedCalls types bytes4 constant _ALLOWEDCALLS_TRANSFERVALUE = 0x00000001; // 0000 0001 diff --git a/contracts/LSP6KeyManager/LSP6Errors.sol b/contracts/LSP6KeyManager/LSP6Errors.sol index 3126cd933..924c16068 100644 --- a/contracts/LSP6KeyManager/LSP6Errors.sol +++ b/contracts/LSP6KeyManager/LSP6Errors.sol @@ -73,8 +73,9 @@ error InvalidRelayNonce(address signer, uint256 invalidNonce, bytes signature); * - `setData(bytes32,bytes)` (ERC725Y) * - `setDataBatch(bytes32[],bytes[])` (ERC725Y) * - `execute(uint256,address,uint256,bytes)` (ERC725X) - * - `transferOwnership(address)` + * - `transferOwnership(address)` (LSP14) * - `acceptOwnership()` (LSP14) + * - `renounceOwnership()` (LSP14) * * @param invalidFunction The `bytes4` selector of the function that was attempted * to be called on the linked {target} but not recognised. @@ -199,14 +200,6 @@ error DelegateCallDisallowedViaKeyManager(); */ error InvalidPayload(bytes payload); -/** - * @notice Cannot sent native tokens while setting data. - * - * @dev Reverts when calling the `setData(byte32,bytes)` or `setData(bytes32[],bytes[]) functions - * on the linked {target} while sending value. - */ -error CannotSendValueToSetData(); - /** * @notice Calling the Key Manager address for this transaction is disallowed. * @@ -214,20 +207,6 @@ error CannotSendValueToSetData(); */ error CallingKeyManagerNotAllowed(); -/** - * @notice Relay call not valid yet. - * - * @dev Reverts when the start timestamp provided to {executeRelayCall} function is bigger than the current timestamp. - */ -error RelayCallBeforeStartTime(); - -/** - * @notice The date of the relay call expired. - * - * @dev Reverts when the period to execute the relay call has expired. - */ -error RelayCallExpired(); - /** * @notice Key Manager cannot be used as an LSP17 extension for LSP20 functions. * diff --git a/contracts/LSP6KeyManager/LSP6KeyManager.sol b/contracts/LSP6KeyManager/LSP6KeyManager.sol index d6c6d75d5..6e9e9cc32 100644 --- a/contracts/LSP6KeyManager/LSP6KeyManager.sol +++ b/contracts/LSP6KeyManager/LSP6KeyManager.sol @@ -21,6 +21,5 @@ contract LSP6KeyManager is LSP6KeyManagerCore { constructor(address target_) { if (target_ == address(0)) revert InvalidLSP6Target(); _target = target_; - _setupLSP6ReentrancyGuard(); } } diff --git a/contracts/LSP6KeyManager/LSP6KeyManagerCore.sol b/contracts/LSP6KeyManager/LSP6KeyManagerCore.sol index a20d146fd..0a25eec83 100644 --- a/contracts/LSP6KeyManager/LSP6KeyManagerCore.sol +++ b/contracts/LSP6KeyManager/LSP6KeyManagerCore.sol @@ -23,6 +23,9 @@ import {ERC725Y} from "@erc725/smart-contracts/contracts/ERC725Y.sol"; import {ERC165} from "@openzeppelin/contracts/utils/introspection/ERC165.sol"; import {LSP6SetDataModule} from "./LSP6Modules/LSP6SetDataModule.sol"; import {LSP6ExecuteModule} from "./LSP6Modules/LSP6ExecuteModule.sol"; +import { + LSP6ExecuteRelayCallModule +} from "./LSP6Modules/LSP6ExecuteRelayCallModule.sol"; import {LSP6OwnershipModule} from "./LSP6Modules/LSP6OwnershipModule.sol"; import { LSP25MultiChannelNonce @@ -43,13 +46,12 @@ import { InvalidPayload, InvalidRelayNonce, NoPermissionsSet, - InvalidERC725Function, - CannotSendValueToSetData + InvalidERC725Function } from "./LSP6Errors.sol"; import { _INTERFACEID_ERC1271, - _ERC1271_MAGICVALUE, + _ERC1271_SUCCESSVALUE, _ERC1271_FAILVALUE } from "../LSP0ERC725Account/LSP0Constants.sol"; import { @@ -57,7 +59,12 @@ import { _PERMISSION_SIGN, _PERMISSION_REENTRANCY } from "./LSP6Constants.sol"; -import "../LSP20CallVerification/LSP20Constants.sol"; +import { + _INTERFACEID_LSP20_CALL_VERIFIER, + _LSP20_VERIFY_CALL_SUCCESS_VALUE_WITHOUT_POST_VERIFICATION, + _LSP20_VERIFY_CALL_SUCCESS_VALUE_WITH_POST_VERIFICATION, + _LSP20_VERIFY_CALL_RESULT_SUCCESS_VALUE +} from "../LSP20CallVerification/LSP20Constants.sol"; import {_INTERFACEID_LSP25} from "../LSP25ExecuteRelayCall/LSP25Constants.sol"; /** @@ -72,11 +79,13 @@ import {_INTERFACEID_LSP25} from "../LSP25ExecuteRelayCall/LSP25Constants.sol"; */ abstract contract LSP6KeyManagerCore is ERC165, + IERC1271, ILSP6, ILSP20, ILSP25, LSP6SetDataModule, LSP6ExecuteModule, + LSP6ExecuteRelayCallModule, LSP6OwnershipModule, LSP25MultiChannelNonce { @@ -86,14 +95,12 @@ abstract contract LSP6KeyManagerCore is address internal _target; - // Variables, methods and modifier used for ReentrancyGuard are taken from the link below and modified accordingly. - // https://github.com/OpenZeppelin/openzeppelin-contracts/blob/release-v4.8/contracts/security/ReentrancyGuard.sol - bool internal _reentrancyStatus; + mapping(address => bool) internal _reentrancyStatus; /** * @inheritdoc ILSP6 */ - function target() public view returns (address) { + function target() public view override returns (address) { return _target; } @@ -128,7 +135,7 @@ abstract contract LSP6KeyManagerCore is function getNonce( address from, uint128 channelId - ) public view virtual returns (uint256) { + ) public view virtual override returns (uint256) { return LSP25MultiChannelNonce._getNonce(from, channelId); } @@ -136,14 +143,14 @@ abstract contract LSP6KeyManagerCore is * @inheritdoc IERC1271 * * @dev Checks if a signature was signed by a controller that has the permission `SIGN`. - * If the signer is a controller with the permission `SIGN`, it will return the ERC1271 magic value. + * If the signer is a controller with the permission `SIGN`, it will return the ERC1271 success value. * - * @return magicValue `0x1626ba7e` on success, or `0xffffffff` on failure. + * @return returnedStatus `0x1626ba7e` on success, or `0xffffffff` on failure. */ function isValidSignature( bytes32 dataHash, bytes memory signature - ) public view virtual returns (bytes4 magicValue) { + ) public view virtual override returns (bytes4 returnedStatus) { // if isValidSignature fail, the error is catched in returnedError (address recoveredAddress, ECDSA.RecoverError returnedError) = ECDSA .tryRecover(dataHash, signature); @@ -152,12 +159,12 @@ abstract contract LSP6KeyManagerCore is if (returnedError != ECDSA.RecoverError.NoError) return _ERC1271_FAILVALUE; - // if the address recovered has SIGN permission return the ERC1271 magic value, otherwise the fail value + // if the address recovered has SIGN permission return the ERC1271 success value, otherwise the fail value return ( ERC725Y(_target).getPermissionsFor(recoveredAddress).hasPermission( _PERMISSION_SIGN ) - ? _ERC1271_MAGICVALUE + ? _ERC1271_SUCCESSVALUE : _ERC1271_FAILVALUE ); } @@ -169,7 +176,7 @@ abstract contract LSP6KeyManagerCore is */ function execute( bytes calldata payload - ) public payable virtual returns (bytes memory) { + ) public payable virtual override returns (bytes memory) { return _execute(msg.value, payload); } @@ -181,7 +188,7 @@ abstract contract LSP6KeyManagerCore is function executeBatch( uint256[] calldata values, bytes[] calldata payloads - ) public payable virtual returns (bytes[] memory) { + ) public payable virtual override returns (bytes[] memory) { if (values.length != payloads.length) { revert BatchExecuteParamsLengthMismatch(); } @@ -189,7 +196,7 @@ abstract contract LSP6KeyManagerCore is bytes[] memory results = new bytes[](payloads.length); uint256 totalValues; - for (uint256 ii = 0; ii < payloads.length; ) { + for (uint256 ii; ii < payloads.length; ) { if ((totalValues += values[ii]) > msg.value) { revert LSP6BatchInsufficientValueSent(totalValues, msg.value); } @@ -229,7 +236,7 @@ abstract contract LSP6KeyManagerCore is uint256 nonce, uint256 validityTimestamps, bytes calldata payload - ) public payable virtual returns (bytes memory) { + ) public payable virtual override returns (bytes memory) { return _executeRelayCall( signature, @@ -259,7 +266,7 @@ abstract contract LSP6KeyManagerCore is uint256[] calldata validityTimestamps, uint256[] calldata values, bytes[] calldata payloads - ) public payable virtual returns (bytes[] memory) { + ) public payable virtual override returns (bytes[] memory) { if ( signatures.length != nonces.length || nonces.length != validityTimestamps.length || @@ -272,7 +279,7 @@ abstract contract LSP6KeyManagerCore is bytes[] memory results = new bytes[](payloads.length); uint256 totalValues; - for (uint256 ii = 0; ii < payloads.length; ) { + for (uint256 ii; ii < payloads.length; ) { if ((totalValues += values[ii]) > msg.value) { revert LSP6BatchInsufficientValueSent(totalValues, msg.value); } @@ -299,53 +306,65 @@ abstract contract LSP6KeyManagerCore is /** * @inheritdoc ILSP20 + * + * @custom:hint This function can call by any other address than the {`target`}. + * This allows to verify permissions in a _"read-only"_ manner. + * + * Anyone can call this function to verify if the `caller` has the right permissions to perform the abi-encoded function call `data` + * on the {`target`} contract (while sending `msgValue` alongside the call). + * + * If the permissions have been verified successfully and `caller` is authorized, one of the following two LSP20 success value will be returned: + * - `0x1a238000`: LSP20 success value **without** post verification (last byte is `0x00`). + * - `0x1a238001`: LSP20 success value **with** post-verification (last byte is `0x01`). */ function lsp20VerifyCall( + address /* requestor */, + address targetContract, address caller, uint256 msgValue, - bytes calldata data - ) external virtual returns (bytes4) { - bool isSetData = false; - if ( - bytes4(data) == IERC725Y.setData.selector || - bytes4(data) == IERC725Y.setDataBatch.selector - ) { - isSetData = true; - } + bytes calldata callData + ) external virtual override returns (bytes4) { + bool isSetData = bytes4(callData) == IERC725Y.setData.selector || + bytes4(callData) == IERC725Y.setDataBatch.selector; // If target is invoking the verification, emit the event and change the reentrancy guard - if (msg.sender == _target) { - bool isReentrantCall = _nonReentrantBefore(isSetData, caller); + if (msg.sender == targetContract) { + bool reentrancyStatus = _nonReentrantBefore( + targetContract, + isSetData, + caller + ); + + _verifyPermissions(targetContract, caller, false, callData); - _verifyPermissions(caller, msgValue, data); - emit PermissionsVerified(caller, msgValue, bytes4(data)); + emit PermissionsVerified(caller, msgValue, bytes4(callData)); // if it's a setData call, do not invoke the `lsp20VerifyCallResult(..)` function return - isSetData || isReentrantCall - ? _LSP20_VERIFY_CALL_MAGIC_VALUE_WITHOUT_POST_VERIFICATION - : _LSP20_VERIFY_CALL_MAGIC_VALUE_WITH_POST_VERIFICATION; + isSetData || reentrancyStatus + ? _LSP20_VERIFY_CALL_SUCCESS_VALUE_WITHOUT_POST_VERIFICATION + : _LSP20_VERIFY_CALL_SUCCESS_VALUE_WITH_POST_VERIFICATION; } - // If a different address is invoking the verification, do not change the state - // and do read-only verification + /// @dev If a different address is invoking the verification, + /// do not change the state or emit the event to allow read-only verification else { - bool isReentrantCall = _reentrancyStatus; + bool reentrancyStatus = _reentrancyStatus[targetContract]; - if (isReentrantCall) { + if (reentrancyStatus) { _requirePermissions( caller, - ERC725Y(_target).getPermissionsFor(caller), + ERC725Y(targetContract).getPermissionsFor(caller), _PERMISSION_REENTRANCY ); } - _verifyPermissions(caller, msgValue, data); + _verifyPermissions(targetContract, caller, false, callData); // if it's a setData call, do not invoke the `lsp20VerifyCallResult(..)` function return - isSetData || isReentrantCall - ? _LSP20_VERIFY_CALL_MAGIC_VALUE_WITHOUT_POST_VERIFICATION - : _LSP20_VERIFY_CALL_MAGIC_VALUE_WITH_POST_VERIFICATION; + isSetData || reentrancyStatus + ? _LSP20_VERIFY_CALL_SUCCESS_VALUE_WITHOUT_POST_VERIFICATION + : _LSP20_VERIFY_CALL_SUCCESS_VALUE_WITH_POST_VERIFICATION; } } @@ -353,15 +372,15 @@ abstract contract LSP6KeyManagerCore is * @inheritdoc ILSP20 */ function lsp20VerifyCallResult( - bytes32 /*callHash*/, - bytes memory /*result*/ - ) external virtual returns (bytes4) { + bytes32 /* callHash */, + bytes memory /* callResult */ + ) external virtual override returns (bytes4) { // If it's the target calling, set back the reentrancy guard - // to false, if not return the magic value + // to false, if not return the success value if (msg.sender == _target) { - _nonReentrantAfter(); + _nonReentrantAfter(msg.sender); } - return _LSP20_VERIFY_CALL_RESULT_MAGIC_VALUE; + return _LSP20_VERIFY_CALL_RESULT_SUCCESS_VALUE; } function _execute( @@ -372,23 +391,29 @@ abstract contract LSP6KeyManagerCore is revert InvalidPayload(payload); } - bool isSetData = false; - if ( - bytes4(payload) == IERC725Y.setData.selector || - bytes4(payload) == IERC725Y.setDataBatch.selector - ) { - isSetData = true; - } + bool isSetData = bytes4(payload) == IERC725Y.setData.selector || + bytes4(payload) == IERC725Y.setDataBatch.selector; - bool isReentrantCall = _nonReentrantBefore(isSetData, msg.sender); + address targetContract = _target; + + bool reentrancyStatus = _nonReentrantBefore( + targetContract, + isSetData, + msg.sender + ); + + _verifyPermissions(targetContract, msg.sender, false, payload); - _verifyPermissions(msg.sender, msgValue, payload); emit PermissionsVerified(msg.sender, msgValue, bytes4(payload)); - bytes memory result = _executePayload(msgValue, payload); + bytes memory result = _executePayload( + targetContract, + msgValue, + payload + ); - if (!isReentrantCall && !isSetData) { - _nonReentrantAfter(); + if (!reentrancyStatus && !isSetData) { + _nonReentrantAfter(targetContract); } return result; @@ -419,6 +444,8 @@ abstract contract LSP6KeyManagerCore is revert InvalidPayload(payload); } + address targetContract = _target; + address signer = LSP25MultiChannelNonce ._recoverSignerFromLSP25Signature( signature, @@ -437,23 +464,27 @@ abstract contract LSP6KeyManagerCore is LSP25MultiChannelNonce._verifyValidityTimestamps(validityTimestamps); - bool isSetData = false; - if ( - bytes4(payload) == IERC725Y.setData.selector || - bytes4(payload) == IERC725Y.setDataBatch.selector - ) { - isSetData = true; - } + bool isSetData = bytes4(payload) == IERC725Y.setData.selector || + bytes4(payload) == IERC725Y.setDataBatch.selector; - bool isReentrantCall = _nonReentrantBefore(isSetData, signer); + bool reentrancyStatus = _nonReentrantBefore( + targetContract, + isSetData, + signer + ); + + _verifyPermissions(targetContract, signer, true, payload); - _verifyPermissions(signer, msgValue, payload); emit PermissionsVerified(signer, msgValue, bytes4(payload)); - bytes memory result = _executePayload(msgValue, payload); + bytes memory result = _executePayload( + targetContract, + msgValue, + payload + ); - if (!isReentrantCall && !isSetData) { - _nonReentrantAfter(); + if (!reentrancyStatus && !isSetData) { + _nonReentrantAfter(targetContract); } return result; @@ -465,10 +496,11 @@ abstract contract LSP6KeyManagerCore is * @return bytes The data returned by the call made to the linked {target} contract. */ function _executePayload( + address targetContract, uint256 msgValue, bytes calldata payload ) internal virtual returns (bytes memory) { - (bool success, bytes memory returnData) = _target.call{ + (bool success, bytes memory returnData) = targetContract.call{ value: msgValue, gas: gasleft() }(payload); @@ -483,29 +515,37 @@ abstract contract LSP6KeyManagerCore is /** * @dev Verify if the `from` address is allowed to execute the `payload` on the {target} contract linked to this Key Manager. + * @param targetContract The contract that is owned by the Key Manager * @param from Either the caller of {execute} or the signer of {executeRelayCall}. * @param payload The abi-encoded function call to execute on the {target} contract. */ function _verifyPermissions( + address targetContract, address from, - uint256 msgValue, + bool isRelayedCall, bytes calldata payload ) internal view virtual { - bytes32 permissions = ERC725Y(_target).getPermissionsFor(from); + bytes32 permissions = ERC725Y(targetContract).getPermissionsFor(from); if (permissions == bytes32(0)) revert NoPermissionsSet(from); + if (isRelayedCall) { + LSP6ExecuteRelayCallModule._verifyExecuteRelayCallPermission( + from, + permissions + ); + } + bytes4 erc725Function = bytes4(payload); // ERC725Y.setData(bytes32,bytes) if (erc725Function == IERC725Y.setData.selector) { - if (msgValue != 0) revert CannotSendValueToSetData(); (bytes32 inputKey, bytes memory inputValue) = abi.decode( payload[4:], (bytes32, bytes) ); LSP6SetDataModule._verifyCanSetData( - _target, + targetContract, from, permissions, inputKey, @@ -514,12 +554,11 @@ abstract contract LSP6KeyManagerCore is // ERC725Y.setDataBatch(bytes32[],bytes[]) } else if (erc725Function == IERC725Y.setDataBatch.selector) { - if (msgValue != 0) revert CannotSendValueToSetData(); (bytes32[] memory inputKeys, bytes[] memory inputValues) = abi .decode(payload[4:], (bytes32[], bytes[])); LSP6SetDataModule._verifyCanSetData( - _target, + targetContract, from, permissions, inputKeys, @@ -536,7 +575,7 @@ abstract contract LSP6KeyManagerCore is ) = abi.decode(payload[4:], (uint256, address, uint256, bytes)); LSP6ExecuteModule._verifyCanExecute( - _target, + targetContract, from, permissions, operationType, @@ -546,7 +585,8 @@ abstract contract LSP6KeyManagerCore is ); } else if ( erc725Function == ILSP14Ownable2Step.transferOwnership.selector || - erc725Function == ILSP14Ownable2Step.acceptOwnership.selector + erc725Function == ILSP14Ownable2Step.acceptOwnership.selector || + erc725Function == ILSP14Ownable2Step.renounceOwnership.selector ) { LSP6OwnershipModule._verifyOwnershipPermissions(from, permissions); } else { @@ -554,45 +594,40 @@ abstract contract LSP6KeyManagerCore is } } - /** - * @dev Initialise _reentrancyStatus to _NOT_ENTERED. - */ - function _setupLSP6ReentrancyGuard() internal virtual { - _reentrancyStatus = false; - } - /** * @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. */ function _nonReentrantBefore( + address targetContract, bool isSetData, address from - ) internal virtual returns (bool isReentrantCall) { - isReentrantCall = _reentrancyStatus; - if (isReentrantCall) { + ) internal virtual returns (bool reentrancyStatus) { + reentrancyStatus = _reentrancyStatus[targetContract]; + + if (reentrancyStatus) { // CHECK the caller has REENTRANCY permission _requirePermissions( from, - ERC725Y(_target).getPermissionsFor(from), + ERC725Y(targetContract).getPermissionsFor(from), _PERMISSION_REENTRANCY ); } else { if (!isSetData) { - _reentrancyStatus = true; + _reentrancyStatus[targetContract] = true; } } } /** - * @dev Resets the status to `_NOT_ENTERED` + * @dev Resets the status to `false` * Used in the end of the `nonReentrant` modifier after the method execution is terminated */ - function _nonReentrantAfter() internal virtual { + function _nonReentrantAfter(address targetContract) internal virtual { // By storing the original value once again, a refund is triggered (see // https://eips.ethereum.org/EIPS/eip-2200) - _reentrancyStatus = false; + _reentrancyStatus[targetContract] = false; } /** diff --git a/contracts/LSP6KeyManager/LSP6KeyManagerInitAbstract.sol b/contracts/LSP6KeyManager/LSP6KeyManagerInitAbstract.sol index 87f44138d..7d29da922 100644 --- a/contracts/LSP6KeyManager/LSP6KeyManagerInitAbstract.sol +++ b/contracts/LSP6KeyManager/LSP6KeyManagerInitAbstract.sol @@ -20,6 +20,5 @@ abstract contract LSP6KeyManagerInitAbstract is function _initialize(address target_) internal virtual onlyInitializing { if (target_ == address(0)) revert InvalidLSP6Target(); _target = target_; - _setupLSP6ReentrancyGuard(); } } diff --git a/contracts/LSP6KeyManager/LSP6Modules/LSP6ExecuteModule.sol b/contracts/LSP6KeyManager/LSP6Modules/LSP6ExecuteModule.sol index 84c85939a..f3aab5d24 100644 --- a/contracts/LSP6KeyManager/LSP6Modules/LSP6ExecuteModule.sol +++ b/contracts/LSP6KeyManager/LSP6Modules/LSP6ExecuteModule.sol @@ -41,7 +41,6 @@ import { InvalidEncodedAllowedCalls, InvalidWhitelistedCall, NotAuthorised, - InvalidPayload, CallingKeyManagerNotAllowed } from "../LSP6Errors.sol"; @@ -262,7 +261,7 @@ abstract contract LSP6ExecuteModule { isEmptyCall ); - for (uint256 ii = 0; ii < allowedCalls.length; ii += 34) { + for (uint256 ii; ii < allowedCalls.length; ii += 34) { /// @dev structure of an AllowedCall // /// AllowedCall = 0x00200000000ncafecafecafecafecafecafecafecafecafecafe5a5a5a5af1f1f1f1 diff --git a/contracts/LSP6KeyManager/LSP6Modules/LSP6ExecuteRelayCallModule.sol b/contracts/LSP6KeyManager/LSP6Modules/LSP6ExecuteRelayCallModule.sol new file mode 100644 index 000000000..4e0fd9447 --- /dev/null +++ b/contracts/LSP6KeyManager/LSP6Modules/LSP6ExecuteRelayCallModule.sol @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.5; + +// libraries +import {LSP6Utils} from "../LSP6Utils.sol"; + +// constants +import {_PERMISSION_EXECUTE_RELAY_CALL} from "../LSP6Constants.sol"; + +// errors +import {NotAuthorised} from "../LSP6Errors.sol"; + +abstract contract LSP6ExecuteRelayCallModule { + function _verifyExecuteRelayCallPermission( + address controllerAddress, + bytes32 controllerPermissions + ) internal pure { + if ( + !LSP6Utils.hasPermission( + controllerPermissions, + _PERMISSION_EXECUTE_RELAY_CALL + ) + ) { + revert NotAuthorised(controllerAddress, "EXECUTE_RELAY_CALL"); + } + } +} diff --git a/contracts/LSP6KeyManager/LSP6Modules/LSP6SetDataModule.sol b/contracts/LSP6KeyManager/LSP6Modules/LSP6SetDataModule.sol index 78aa6c1a0..ab623774e 100644 --- a/contracts/LSP6KeyManager/LSP6Modules/LSP6SetDataModule.sol +++ b/contracts/LSP6KeyManager/LSP6Modules/LSP6SetDataModule.sol @@ -431,22 +431,26 @@ abstract contract LSP6SetDataModule { address controlledContract, bytes32 inputPermissionDataKey ) internal view virtual returns (bytes32) { + // extract the address of the controller from the data key `AddressPermissions:Permissions:` + address controller = address(bytes20(inputPermissionDataKey << 96)); + + bytes32 controllerPermissions = ERC725Y(controlledContract) + .getPermissionsFor(controller); + return // if there is nothing stored under the data key, we are trying to ADD a new controller. // if there are already some permissions set under the data key, we are trying to CHANGE the permissions of a controller. - bytes32( - ERC725Y(controlledContract).getData(inputPermissionDataKey) - ) == bytes32(0) + controllerPermissions == bytes32(0) ? _PERMISSION_ADDCONTROLLER : _PERMISSION_EDITPERMISSIONS; } /** - * @dev retrieve the permission required to set some AllowedCalls for a controller. - * @param controlledContract the address of the ERC725Y contract where the data key is verified. - * @param dataKey `AddressPermissions:AllowedCalls:`. - * @param dataValue the updated value for the `dataKey`. MUST be a bytes28[CompactBytesArray] of Allowed Calls. - * @return either ADD or CHANGE PERMISSIONS. + * @dev Retrieve the permission required to set some AllowedCalls for a controller. + * @param controlledContract The address of the ERC725Y contract from which to fetch the value of `dataKey`. + * @param dataKey A data key ion the format `AddressPermissions:AllowedCalls:`. + * @param dataValue The updated value for the `dataKey`. MUST be a bytes32[CompactBytesArray] of Allowed Calls. + * @return Either ADD or EDIT PERMISSIONS. */ function _getPermissionToSetAllowedCalls( address controlledContract, @@ -462,23 +466,24 @@ abstract contract LSP6SetDataModule { // No permission required as CHECK is already done. We don't need to read `target` storage. if (hasBothAddControllerAndEditPermissions) return bytes32(0); - // if nothing is stored under the controller's Allowed Calls of the controller, - // we are trying to ADD a list of restricted calls (standards + address + function selector) - // - // if some data is already set under the Allowed Calls of the controller, - // we are trying to CHANGE (= edit) these restrictions. + // extract the address of the controller from the data key `AddressPermissions:AllowedCalls:` + address controller = address(bytes20(dataKey << 96)); + + // if the controller exists and has some permissions set, this is considered as EDIT a "sub-set" of its permissions. + // (even if the controller does not have any allowed calls set). return - ERC725Y(controlledContract).getData(dataKey).length == 0 + ERC725Y(controlledContract).getPermissionsFor(controller) == + bytes32(0) ? _PERMISSION_ADDCONTROLLER : _PERMISSION_EDITPERMISSIONS; } /** - * @dev retrieve the permission required to set some Allowed ERC725Y Data Keys for a controller. - * @param controlledContract the address of the ERC725Y contract where the data key is verified. - * @param dataKey or `AddressPermissions:AllowedERC725YDataKeys:`. - * @param dataValue the updated value for the `dataKey`. MUST be a bytes[CompactBytesArray] of Allowed ERC725Y Data Keys. - * @return either ADD or CHANGE PERMISSIONS. + * @dev Retrieve the permission required to set some Allowed ERC725Y Data Keys for a controller. + * @param controlledContract the address of the ERC725Y contract from which to fetch the value of `dataKey`. + * @param dataKey A data key in the format `AddressPermissions:AllowedERC725YDataKeys:`. + * @param dataValue The updated value for the `dataKey`. MUST be a bytes[CompactBytesArray] of Allowed ERC725Y Data Keys. + * @return Either ADD or EDIT PERMISSIONS. */ function _getPermissionToSetAllowedERC725YDataKeys( address controlledContract, @@ -497,13 +502,14 @@ abstract contract LSP6SetDataModule { // CHECK is already done. We don't need to read `target` storage. if (hasBothAddControllerAndEditPermissions) return bytes32(0); - // if there is nothing stored under the Allowed ERC725Y Data Keys of the controller, - // we are trying to ADD a list of restricted ERC725Y Data Keys. - // - // if there are already some data set under the Allowed ERC725Y Data Keys of the controller, - // we are trying to CHANGE (= edit) these restricted ERC725Y data keys. + // extract the address of the controller from the data key `AddressPermissions:AllowedERC725YDataKeys:` + address controller = address(bytes20(dataKey << 96)); + + // if the controller exists and has some permissions set, this is considered as EDIT a "sub-set" of its permissions. + // (even if the controller does not have any allowed ERC725Y data keys set). return - ERC725Y(controlledContract).getData(dataKey).length == 0 + ERC725Y(controlledContract).getPermissionsFor(controller) == + bytes32(0) ? _PERMISSION_ADDCONTROLLER : _PERMISSION_EDITPERMISSIONS; } diff --git a/contracts/LSP6KeyManager/LSP6Utils.sol b/contracts/LSP6KeyManager/LSP6Utils.sol index da04f1612..ccc9a2f38 100644 --- a/contracts/LSP6KeyManager/LSP6Utils.sol +++ b/contracts/LSP6KeyManager/LSP6Utils.sol @@ -11,7 +11,27 @@ import {ILSP6KeyManager} from "./ILSP6KeyManager.sol"; import {LSP2Utils} from "../LSP2ERC725YJSONSchema/LSP2Utils.sol"; // constants -import "./LSP6Constants.sol"; +import { + _LSP6KEY_ADDRESSPERMISSIONS_PERMISSIONS_PREFIX, + _LSP6KEY_ADDRESSPERMISSIONS_ALLOWEDCALLS_PREFIX, + _LSP6KEY_ADDRESSPERMISSIONS_AllowedERC725YDataKeys_PREFIX, + _LSP6KEY_ADDRESSPERMISSIONS_ARRAY, + _PERMISSION_CHANGEOWNER, + _PERMISSION_EDITPERMISSIONS, + _PERMISSION_ADDCONTROLLER, + _PERMISSION_ADDEXTENSIONS, + _PERMISSION_CHANGEEXTENSIONS, + _PERMISSION_ADDUNIVERSALRECEIVERDELEGATE, + _PERMISSION_CHANGEUNIVERSALRECEIVERDELEGATE, + _PERMISSION_REENTRANCY, + _PERMISSION_SETDATA, + _PERMISSION_CALL, + _PERMISSION_STATICCALL, + _PERMISSION_DELEGATECALL, + _PERMISSION_DEPLOY, + _PERMISSION_TRANSFERVALUE, + _PERMISSION_SIGN +} from "./LSP6Constants.sol"; /** * @title LSP6 Utility library. @@ -30,6 +50,9 @@ library LSP6Utils { * @param caller The controller address to read the permissions from. * * @return A `bytes32` BitArray containing the permissions of a controller address. + * + * @custom:info If the raw value fetched from the ERC725Y storage of `target` is not 32 bytes long, this is considered + * like _"no permissions are set"_ and will return 32 x `0x00` bytes as `bytes32(0)`. */ function getPermissionsFor( IERC725Y target, @@ -42,6 +65,10 @@ library LSP6Utils { ) ); + if (permissions.length != 32) { + return bytes32(0); + } + return bytes32(permissions); } @@ -186,7 +213,7 @@ library LSP6Utils { bytes32[] memory permissions ) internal pure returns (bytes32) { bytes32 result; - for (uint256 i = 0; i < permissions.length; i++) { + for (uint256 i; i < permissions.length; i++) { result |= permissions[i]; } return result; diff --git a/contracts/LSP7DigitalAsset/ILSP7DigitalAsset.sol b/contracts/LSP7DigitalAsset/ILSP7DigitalAsset.sol index 93ec0792a..8cb969b93 100644 --- a/contracts/LSP7DigitalAsset/ILSP7DigitalAsset.sol +++ b/contracts/LSP7DigitalAsset/ILSP7DigitalAsset.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: MIT +// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.4; @@ -20,7 +20,7 @@ interface ILSP7DigitalAsset is IERC165, IERC725Y { * @param from The address which tokens were sent from (balance decreased by `-amount`). * @param to The address that received the tokens (balance increased by `+amount`). * @param amount The amount of tokens transferred. - * @param allowNonLSP1Recipient if the transferred enforced the `to` recipient address to be a contract that implements the LSP1 standard or not. + * @param force if the transferred enforced the `to` recipient address to be a contract that implements the LSP1 standard or not. * @param data Any additional data included by the caller during the transfer, and sent in the LSP1 hooks to the `from` and `to` addresses. */ event Transfer( @@ -28,7 +28,7 @@ interface ILSP7DigitalAsset is IERC165, IERC725Y { address indexed from, address indexed to, uint256 amount, - bool allowNonLSP1Recipient, + bool force, bytes data ); @@ -173,7 +173,7 @@ interface ILSP7DigitalAsset is IERC165, IERC725Y { * @param from The sender address. * @param to The recipient address. * @param amount The amount of tokens to transfer. - * @param allowNonLSP1Recipient When set to `true`, the `to` address CAN be any address. When set to `false`, the `to` address MUST be a contract that supports the LSP1 UniversalReceiver standard. + * @param force When set to `true`, the `to` address CAN be any address. When set to `false`, the `to` address MUST be a contract that supports the LSP1 UniversalReceiver standard. * @param data Any additional data the caller wants included in the emitted event, and sent in the hooks of the `from` and `to` addresses. * * @custom:requirements @@ -188,19 +188,19 @@ interface ILSP7DigitalAsset is IERC165, IERC725Y { * - if the transfer is triggered by an operator, either the {AuthorizedOperator} event will be emitted with the updated allowance or the {RevokedOperator} * event will be emitted if the operator has no more allowance left. * - * @custom:hint The `allowNonLSP1Recipient` parameter **MUST be set to `true`** to transfer tokens to Externally Owned Accounts (EOAs) + * @custom:hint The `force` parameter **MUST be set to `true`** to transfer tokens to Externally Owned Accounts (EOAs) * or contracts that do not implement the LSP1 Universal Receiver Standard. Otherwise the function will revert making the transfer fail. * - * @custom:info if the `to` address is a contract that implements LSP1, it will always be notified via its `universalReceiver(...)` function, regardless if `allowNonLSP1Recipient` is set to `true` or `false`. + * @custom:info if the `to` address is a contract that implements LSP1, it will always be notified via its `universalReceiver(...)` function, regardless if `force` is set to `true` or `false`. * * @custom:warning Be aware that when either the sender or the recipient can have logic that revert in their `universalReceiver(...)` function when being notified. - * This even if the `allowNonLSP1Recipient` was set to `true`. + * This even if the `force` was set to `true`. */ function transfer( address from, address to, uint256 amount, - bool allowNonLSP1Recipient, + bool force, bytes memory data ) external; @@ -212,7 +212,7 @@ interface ILSP7DigitalAsset is IERC165, IERC725Y { * @param from An array of sending addresses. * @param to An array of receiving addresses. * @param amount An array of amount of tokens to transfer for each `from -> to` transfer. - * @param allowNonLSP1Recipient For each transfer, when set to `true`, the `to` address CAN be any address. When set to `false`, the `to` address MUST be a contract that supports the LSP1 UniversalReceiver standard. + * @param force For each transfer, when set to `true`, the `to` address CAN be any address. When set to `false`, the `to` address MUST be a contract that supports the LSP1 UniversalReceiver standard. * @param data An array of additional data the caller wants included in the emitted event, and sent in the hooks to `from` and `to` addresses. * * @custom:requirements @@ -228,7 +228,7 @@ interface ILSP7DigitalAsset is IERC165, IERC725Y { address[] memory from, address[] memory to, uint256[] memory amount, - bool[] memory allowNonLSP1Recipient, + bool[] memory force, bytes[] memory data ) external; } diff --git a/contracts/LSP7DigitalAsset/LSP7Constants.sol b/contracts/LSP7DigitalAsset/LSP7Constants.sol index 7c73dd4eb..bfbba9e1b 100644 --- a/contracts/LSP7DigitalAsset/LSP7Constants.sol +++ b/contracts/LSP7DigitalAsset/LSP7Constants.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: CC0-1.0 +// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.4; // --- ERC165 interface ids diff --git a/contracts/LSP7DigitalAsset/LSP7DigitalAsset.sol b/contracts/LSP7DigitalAsset/LSP7DigitalAsset.sol index 533e408c5..abe41cbb0 100644 --- a/contracts/LSP7DigitalAsset/LSP7DigitalAsset.sol +++ b/contracts/LSP7DigitalAsset/LSP7DigitalAsset.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: CC0-1.0 +// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.4; // interfaces @@ -17,8 +17,11 @@ import {LSP2Utils} from "../LSP2ERC725YJSONSchema/LSP2Utils.sol"; // constants import {_INTERFACEID_LSP7} from "./LSP7Constants.sol"; +import {LSP7TokenContractCannotHoldValue} from "./LSP7Errors.sol"; -import "../LSP17ContractExtension/LSP17Constants.sol"; +import { + _LSP17_EXTENSION_PREFIX +} from "../LSP17ContractExtension/LSP17Constants.sol"; // errors @@ -60,7 +63,6 @@ abstract contract LSP7DigitalAsset is // fallback function - // solhint-disable no-complex-fallback /** * @notice The `fallback` function was called with the following amount of native tokens: `msg.value`; and the following calldata: `callData`. * @@ -80,6 +82,7 @@ abstract contract LSP7DigitalAsset is * * 2. If the data sent to this function is of length less than 4 bytes (not a function selector), revert. */ + // solhint-disable-next-line no-complex-fallback fallback( bytes calldata callData ) external payable virtual returns (bytes memory) { @@ -89,6 +92,19 @@ abstract contract LSP7DigitalAsset is return _fallbackLSP17Extendable(callData); } + /** + * @dev Reverts whenever someone tries to send native tokens to a LSP7 contract. + * @notice LSP7 contract cannot receive native tokens. + */ + receive() external payable virtual { + // revert on empty calls with no value + if (msg.value == 0) { + revert InvalidFunctionSelector(hex"00000000"); + } + + revert LSP7TokenContractCannotHoldValue(); + } + /** * @dev Forwards the call with the received value to an extension mapped to a function selector. * @@ -101,10 +117,8 @@ abstract contract LSP7DigitalAsset is * CALL opcode, passing the {msg.data} appended with the 20 bytes of the {msg.sender} and * 32 bytes of the {msg.value} * - * Because the function uses assembly {return()/revert()} to terminate the call, it cannot be - * called before other codes in fallback(). - * - * Otherwise, the codes after _fallbackLSP17Extendable() may never be reached. + * @custom:info The LSP7 Token contract should not hold any native tokens. Any native tokens received by the contract + * will be forwarded to the extension address mapped to the selector from `msg.sig`. */ function _fallbackLSP17Extendable( bytes calldata callData @@ -125,8 +139,8 @@ abstract contract LSP7DigitalAsset is } else { // `mload(result)` -> offset in memory where `result.length` is located // `add(result, 32)` -> offset in memory where `result` data starts - // solhint-disable no-inline-assembly /// @solidity memory-safe-assembly + // solhint-disable-next-line no-inline-assembly assembly { let resultdata_size := mload(result) revert(add(result, 32), resultdata_size) diff --git a/contracts/LSP7DigitalAsset/LSP7DigitalAssetCore.sol b/contracts/LSP7DigitalAsset/LSP7DigitalAssetCore.sol index d542a8581..3f01760cf 100644 --- a/contracts/LSP7DigitalAsset/LSP7DigitalAssetCore.sol +++ b/contracts/LSP7DigitalAsset/LSP7DigitalAssetCore.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: CC0-1.0 +// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.4; // interfaces @@ -17,7 +17,18 @@ import { } from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; // errors -import "./LSP7Errors.sol"; +import { + LSP7CannotSendToSelf, + LSP7AmountExceedsAuthorizedAmount, + LSP7InvalidTransferBatch, + LSP7AmountExceedsBalance, + LSP7DecreasedAllowanceBelowZero, + LSP7CannotUseAddressZeroAsOperator, + LSP7TokenOwnerCannotBeOperator, + LSP7CannotSendWithAddressZero, + LSP7NotifyTokenReceiverContractMissingLSP1Interface, + LSP7NotifyTokenReceiverIsEOA +} from "./LSP7Errors.sol"; // constants import {_INTERFACEID_LSP1} from "../LSP1UniversalReceiver/LSP1Constants.sol"; @@ -39,35 +50,36 @@ import { */ abstract contract LSP7DigitalAssetCore is ILSP7DigitalAsset { using EnumerableSet for EnumerableSet.AddressSet; + // --- Storage + bool internal _isNonDivisible; + + uint256 internal _existingTokens; + // Mapping from `tokenOwner` to an `amount` of tokens mapping(address => uint256) internal _tokenOwnerBalances; - // Mapping a `tokenOwner` to an `operator` to `amount` of tokens. - mapping(address => mapping(address => uint256)) - internal _operatorAuthorizedAmount; - // Mapping an `address` to its authorized operator addresses. mapping(address => EnumerableSet.AddressSet) internal _operators; - uint256 internal _existingTokens; - - bool internal _isNonDivisible; + // Mapping a `tokenOwner` to an `operator` to `amount` of tokens. + mapping(address => mapping(address => uint256)) + internal _operatorAuthorizedAmount; // --- Token queries /** * @inheritdoc ILSP7DigitalAsset */ - function decimals() public view virtual returns (uint8) { + function decimals() public view virtual override returns (uint8) { return _isNonDivisible ? 0 : 18; } /** * @inheritdoc ILSP7DigitalAsset */ - function totalSupply() public view virtual returns (uint256) { + function totalSupply() public view virtual override returns (uint256) { return _existingTokens; } @@ -78,7 +90,7 @@ abstract contract LSP7DigitalAssetCore is ILSP7DigitalAsset { */ function balanceOf( address tokenOwner - ) public view virtual returns (uint256) { + ) public view virtual override returns (uint256) { return _tokenOwnerBalances[tokenOwner]; } @@ -100,7 +112,7 @@ abstract contract LSP7DigitalAssetCore is ILSP7DigitalAsset { address operator, uint256 amount, bytes memory operatorNotificationData - ) public virtual { + ) public virtual override { _updateOperator(msg.sender, operator, amount, operatorNotificationData); bytes memory lsp1Data = abi.encode( @@ -117,7 +129,7 @@ abstract contract LSP7DigitalAssetCore is ILSP7DigitalAsset { function revokeOperator( address operator, bytes memory operatorNotificationData - ) public virtual { + ) public virtual override { _updateOperator(msg.sender, operator, 0, operatorNotificationData); bytes memory lsp1Data = abi.encode( @@ -134,7 +146,7 @@ abstract contract LSP7DigitalAssetCore is ILSP7DigitalAsset { function authorizedAmountFor( address operator, address tokenOwner - ) public view virtual returns (uint256) { + ) public view virtual override returns (uint256) { if (tokenOwner == operator) { return _tokenOwnerBalances[tokenOwner]; } else { @@ -147,7 +159,7 @@ abstract contract LSP7DigitalAssetCore is ILSP7DigitalAsset { */ function getOperatorsOf( address tokenOwner - ) public view virtual returns (address[] memory) { + ) public view virtual override returns (address[] memory) { return _operators[tokenOwner].values(); } @@ -160,27 +172,20 @@ abstract contract LSP7DigitalAssetCore is ILSP7DigitalAsset { address from, address to, uint256 amount, - bool allowNonLSP1Recipient, + bool force, bytes memory data - ) public virtual { + ) public virtual override { if (from == to) revert LSP7CannotSendToSelf(); - address operator = msg.sender; - if (operator != from) { - uint256 operatorAmount = _operatorAuthorizedAmount[from][operator]; - if (amount > operatorAmount) { - revert LSP7AmountExceedsAuthorizedAmount( - from, - operatorAmount, - operator, - amount - ); - } - - _updateOperator(from, operator, operatorAmount - amount, ""); + if (msg.sender != from) { + _spendAllowance({ + operator: msg.sender, + tokenOwner: from, + amountToSpend: amount + }); } - _transfer(from, to, amount, allowNonLSP1Recipient, data); + _transfer(from, to, amount, force, data); } /** @@ -190,28 +195,22 @@ abstract contract LSP7DigitalAssetCore is ILSP7DigitalAsset { address[] memory from, address[] memory to, uint256[] memory amount, - bool[] memory allowNonLSP1Recipient, + bool[] memory force, bytes[] memory data - ) public virtual { + ) public virtual override { uint256 fromLength = from.length; if ( fromLength != to.length || fromLength != amount.length || - fromLength != allowNonLSP1Recipient.length || + fromLength != force.length || fromLength != data.length ) { revert LSP7InvalidTransferBatch(); } - for (uint256 i = 0; i < fromLength; ) { + for (uint256 i; i < fromLength; ) { // using the public transfer function to handle updates to operator authorized amounts - transfer( - from[i], - to[i], - amount[i], - allowNonLSP1Recipient[i], - data[i] - ); + transfer(from[i], to[i], amount[i], force[i], data[i]); unchecked { ++i; @@ -230,8 +229,8 @@ abstract contract LSP7DigitalAssetCore is ILSP7DigitalAsset { * This is an alternative approach to {authorizeOperator} that can be used as a mitigation * for the double spending allowance problem. * - * @param operator the operator to increase the allowance for `msg.sender` - * @param addedAmount the additional amount to add on top of the current operator's allowance + * @param operator The operator to increase the allowance for `msg.sender` + * @param addedAmount The additional amount to add on top of the current operator's allowance * * @custom:requirements * - `operator` cannot be the same address as `msg.sender` @@ -246,6 +245,7 @@ abstract contract LSP7DigitalAssetCore is ILSP7DigitalAsset { ) public virtual { uint256 newAllowance = authorizedAmountFor(operator, msg.sender) + addedAmount; + _updateOperator( msg.sender, operator, @@ -266,7 +266,7 @@ abstract contract LSP7DigitalAssetCore is ILSP7DigitalAsset { * It has been added in the LSP7 contract implementation so that it can be used as a prevention mechanism * against the double spending allowance vulnerability. * - * @notice Decrease the allowance of `operator` by -`substractedAmount` + * @notice Decrease the allowance of `operator` by -`subtractedAmount` * * @dev Atomically decreases the allowance granted to `operator` by the caller. * This is an alternative approach to {authorizeOperator} that can be used as a mitigation @@ -274,29 +274,29 @@ abstract contract LSP7DigitalAssetCore is ILSP7DigitalAsset { * * @custom:events * - {AuthorizedOperator} event indicating the updated allowance after decreasing it. - * - {RevokeOperator} event if `substractedAmount` is the full allowance, + * - {RevokeOperator} event if `subtractedAmount` is the full allowance, * indicating `operator` does not have any alauthorizedAmountForlowance left for `msg.sender`. * - * @param operator the operator to decrease allowance for `msg.sender` - * @param substractedAmount the amount to decrease by in the operator's allowance. + * @param operator The operator to decrease allowance for `msg.sender` + * @param subtractedAmount The amount to decrease by in the operator's allowance. * * @custom:requirements * - `operator` cannot be the zero address. - * - `operator` must have allowance for the caller of at least `substractedAmount`. + * - `operator` must have allowance for the caller of at least `subtractedAmount`. */ function decreaseAllowance( address operator, - uint256 substractedAmount, + uint256 subtractedAmount, bytes memory operatorNotificationData ) public virtual { uint256 currentAllowance = authorizedAmountFor(operator, msg.sender); - if (currentAllowance < substractedAmount) { + if (currentAllowance < subtractedAmount) { revert LSP7DecreasedAllowanceBelowZero(); } uint256 newAllowance; unchecked { - newAllowance = currentAllowance - substractedAmount; + newAllowance = currentAllowance - subtractedAmount; _updateOperator( msg.sender, operator, @@ -318,6 +318,10 @@ abstract contract LSP7DigitalAssetCore is ILSP7DigitalAsset { * If the amount is zero the operator is removed from the list of operators, otherwise he is added to the list of operators. * If the amount is zero then the operator is being revoked, otherwise the operator amount is being modified. * + * @param tokenOwner The address that will give `operator` an allowance for on its balance. + * @param operator The address to grant an allowance to spend. + * @param allowance The maximum amount of token that `operator` can spend from the `tokenOwner`'s balance. + * * @custom:events * - {RevokedOperator} event when operator's allowance is set to `0`. * - {AuthorizedOperator} event when operator's allowance is set to any other amount. @@ -329,7 +333,7 @@ abstract contract LSP7DigitalAssetCore is ILSP7DigitalAsset { function _updateOperator( address tokenOwner, address operator, - uint256 amount, + uint256 allowance, bytes memory operatorNotificationData ) internal virtual { if (operator == address(0)) { @@ -340,14 +344,14 @@ abstract contract LSP7DigitalAssetCore is ILSP7DigitalAsset { revert LSP7TokenOwnerCannotBeOperator(); } - _operatorAuthorizedAmount[tokenOwner][operator] = amount; + _operatorAuthorizedAmount[tokenOwner][operator] = allowance; - if (amount != 0) { + if (allowance != 0) { _operators[tokenOwner].add(operator); emit AuthorizedOperator( operator, tokenOwner, - amount, + allowance, operatorNotificationData ); } else { @@ -363,9 +367,13 @@ abstract contract LSP7DigitalAssetCore is ILSP7DigitalAsset { /** * @dev Mints `amount` of tokens and transfers it to `to`. * - * @param to the address to mint tokens for. - * @param amount the amount of tokens to mint. - * @param allowNonLSP1Recipient a boolean that describe if transfer to a `to` address that does not support LSP1 is allowed or not. + * @custom:info Any logic in the: + * - {_beforeTokenTransfer} function will run before updating the balances. + * - {_afterTokenTransfer} function will run after updating the balances, **but before notifying the recipient via LSP1**. + * + * @param to The address to mint tokens for. + * @param amount The amount of tokens to mint. + * @param force A boolean that describe if transfer to a `to` address that does not support LSP1 is allowed or not. * @param data Additional data the caller wants included in the emitted {Transfer} event, and sent in the LSP1 hook to the `to` address. * * @custom:requirements @@ -376,33 +384,26 @@ abstract contract LSP7DigitalAssetCore is ILSP7DigitalAsset { function _mint( address to, uint256 amount, - bool allowNonLSP1Recipient, + bool force, bytes memory data ) internal virtual { if (to == address(0)) { revert LSP7CannotSendWithAddressZero(); } - address operator = msg.sender; - - _beforeTokenTransfer(address(0), to, amount); + _beforeTokenTransfer(address(0), to, amount, data); // tokens being minted _existingTokens += amount; _tokenOwnerBalances[to] += amount; - emit Transfer( - operator, - address(0), - to, - amount, - allowNonLSP1Recipient, - data - ); + emit Transfer(msg.sender, address(0), to, amount, force, data); + + _afterTokenTransfer(address(0), to, amount, data); bytes memory lsp1Data = abi.encode(address(0), to, amount, data); - _notifyTokenReceiver(to, allowNonLSP1Recipient, lsp1Data); + _notifyTokenReceiver(to, force, lsp1Data); } /** @@ -412,10 +413,12 @@ abstract contract LSP7DigitalAssetCore is ILSP7DigitalAsset { * function, if they are contracts that support the LSP1 interface. Their `universalReceiver` function will receive * all the parameters in the calldata packed encoded. * - * Any logic in the {_beforeTokenTransfer} function will run before updating the balances. + * @custom:info Any logic in the: + * - {_beforeTokenTransfer} function will run before updating the balances. + * - {_afterTokenTransfer} function will run after updating the balances, **but before notifying the sender via LSP1**. * - * @param from the address to burn tokens from its balance. - * @param amount the amount of tokens to burn. + * @param from The address to burn tokens from its balance. + * @param amount The amount of tokens to burn. * @param data Additional data the caller wants included in the emitted event, and sent in the LSP1 hook to the `from` and `to` address. * * @custom:hint In dApps, you can know which address is burning tokens by listening for the `Transfer` event and filter with the zero address as `to`. @@ -442,35 +445,71 @@ abstract contract LSP7DigitalAssetCore is ILSP7DigitalAsset { revert LSP7AmountExceedsBalance(balance, from, amount); } - address operator = msg.sender; - if (operator != from) { - uint256 authorizedAmount = _operatorAuthorizedAmount[from][ - operator - ]; - if (amount > authorizedAmount) { - revert LSP7AmountExceedsAuthorizedAmount( - from, - authorizedAmount, - operator, - amount - ); - } - _operatorAuthorizedAmount[from][operator] -= amount; - } - - _beforeTokenTransfer(from, address(0), amount); + _beforeTokenTransfer(from, address(0), amount, data); // tokens being burnt _existingTokens -= amount; _tokenOwnerBalances[from] -= amount; - emit Transfer(operator, from, address(0), amount, false, data); + emit Transfer(msg.sender, from, address(0), amount, false, data); + emit Transfer({ + operator: msg.sender, + from: from, + to: address(0), + amount: amount, + force: false, + data: data + }); + + _afterTokenTransfer(from, address(0), amount, data); bytes memory lsp1Data = abi.encode(from, address(0), amount, data); _notifyTokenSender(from, lsp1Data); } + /** + * @dev Spend `amountToSpend` from the `operator`'s authorized on behalf of the `tokenOwner`. + * + * @param operator The address of the operator to decrease the allowance of. + * @param tokenOwner The address that granted an allowance on its balance to `operator`. + * @param amountToSpend The amount of tokens to substract in allowance of `operator`. + * + * @custom:events + * - {RevokedOperator} event when operator's allowance is set to `0`. + * - {AuthorizedOperator} event when operator's allowance is set to any other amount. + * + * @custom:requirements + * - The `amountToSpend` MUST be at least the allowance granted to `operator` (accessible via {`authorizedAmountFor}`) + * - `operator` cannot be the zero address. + * - `operator` cannot be the same address as `tokenOwner`. + */ + function _spendAllowance( + address operator, + address tokenOwner, + uint256 amountToSpend + ) internal virtual { + uint256 authorizedAmount = _operatorAuthorizedAmount[tokenOwner][ + operator + ]; + + if (amountToSpend > authorizedAmount) { + revert LSP7AmountExceedsAuthorizedAmount( + tokenOwner, + authorizedAmount, + operator, + amountToSpend + ); + } + + _updateOperator({ + tokenOwner: tokenOwner, + operator: operator, + allowance: authorizedAmount - amountToSpend, + operatorNotificationData: "" + }); + } + /** * @dev Transfer tokens from `from` to `to` by decreasing the balance of `from` by `-amount` and increasing the balance * of `to` by `+amount`. @@ -479,12 +518,14 @@ abstract contract LSP7DigitalAssetCore is ILSP7DigitalAsset { * function, if they are contracts that support the LSP1 interface. Their `universalReceiver` function will receive * all the parameters in the calldata packed encoded. * - * Any logic in the {_beforeTokenTransfer} function will run before updating the balances. + * @custom:info Any logic in the: + * - {_beforeTokenTransfer} function will run before updating the balances. + * - {_afterTokenTransfer} function will run after updating the balances, **but before notifying the sender/recipient via LSP1**. * - * @param from the address to decrease the balance. - * @param to the address to increase the balance. - * @param amount the amount of tokens to transfer from `from` to `to`. - * @param allowNonLSP1Recipient a boolean that describe if transfer to a `to` address that does not support LSP1 is allowed or not. + * @param from The address to decrease the balance. + * @param to The address to increase the balance. + * @param amount The amount of tokens to transfer from `from` to `to`. + * @param force A boolean that describe if transfer to a `to` address that does not support LSP1 is allowed or not. * @param data Additional data the caller wants included in the emitted event, and sent in the LSP1 hook to the `from` and `to` address. * * @custom:requirements @@ -498,7 +539,7 @@ abstract contract LSP7DigitalAssetCore is ILSP7DigitalAsset { address from, address to, uint256 amount, - bool allowNonLSP1Recipient, + bool force, bytes memory data ) internal virtual { if (from == address(0) || to == address(0)) { @@ -510,19 +551,19 @@ abstract contract LSP7DigitalAssetCore is ILSP7DigitalAsset { revert LSP7AmountExceedsBalance(balance, from, amount); } - address operator = msg.sender; - - _beforeTokenTransfer(from, to, amount); + _beforeTokenTransfer(from, to, amount, data); _tokenOwnerBalances[from] -= amount; _tokenOwnerBalances[to] += amount; - emit Transfer(operator, from, to, amount, allowNonLSP1Recipient, data); + emit Transfer(msg.sender, from, to, amount, force, data); + + _afterTokenTransfer(from, to, amount, data); bytes memory lsp1Data = abi.encode(from, to, amount, data); _notifyTokenSender(from, lsp1Data); - _notifyTokenReceiver(to, allowNonLSP1Recipient, lsp1Data); + _notifyTokenReceiver(to, force, lsp1Data); } /** @@ -532,11 +573,29 @@ abstract contract LSP7DigitalAssetCore is ILSP7DigitalAsset { * @param from The sender address * @param to The recipient address * @param amount The amount of token to transfer + * @param data The data sent alongside the transfer */ function _beforeTokenTransfer( address from, address to, - uint256 amount + uint256 amount, + bytes memory data // solhint-disable-next-line no-empty-blocks + ) internal virtual {} + + /** + * @dev Hook that is called after any token transfer, including minting and burning. + * Allows to run custom logic after updating balances, but **before notifiying sender/recipient** by overriding this function. + * + * @param from The sender address + * @param to The recipient address + * @param amount The amount of token to transfer + * @param data The data sent alongside the transfer + */ + function _afterTokenTransfer( + address from, + address to, + uint256 amount, + bytes memory data // solhint-disable-next-line no-empty-blocks ) internal virtual {} /** @@ -557,13 +616,16 @@ abstract contract LSP7DigitalAssetCore is ILSP7DigitalAsset { _INTERFACEID_LSP1 ) ) { - operator.call( - abi.encodeWithSelector( - ILSP1UniversalReceiver.universalReceiver.selector, + try + ILSP1UniversalReceiver(operator).universalReceiver( _TYPEID_LSP7_TOKENOPERATOR, lsp1Data ) - ); + { + return; + } catch { + return; + } } } @@ -596,17 +658,17 @@ abstract contract LSP7DigitalAssetCore is ILSP7DigitalAsset { * @dev Attempt to notify the token receiver `to` about the `amount` tokens being received. * This is done by calling its {universalReceiver} function with the `_TYPEID_LSP7_TOKENSRECIPIENT` as typeId, if `to` is a contract that supports the LSP1 interface. * - * If `to` is is an EOA or a contract that does not support the LSP1 interface, the behaviour will depend on the `allowNonLSP1Recipient` boolean flag. - * - if `allowNonLSP1Recipient` is set to `true`, nothing will happen and no notification will be sent. - * - if `allowNonLSP1Recipient` is set to `false, the transaction will revert. + * If `to` is is an EOA or a contract that does not support the LSP1 interface, the behaviour will depend on the `force` boolean flag. + * - if `force` is set to `true`, nothing will happen and no notification will be sent. + * - if `force` is set to `false, the transaction will revert. * * @param to The address to call the {universalReceiver} function on. - * @param allowNonLSP1Recipient a boolean that describe if transfer to a `to` address that does not support LSP1 is allowed or not. - * @param lsp1Data the data to be sent to the `to` address in the `universalReceiver(...)` call. + * @param force A boolean that describe if transfer to a `to` address that does not support LSP1 is allowed or not. + * @param lsp1Data The data to be sent to the `to` address in the `universalReceiver(...)` call. */ function _notifyTokenReceiver( address to, - bool allowNonLSP1Recipient, + bool force, bytes memory lsp1Data ) internal virtual { if ( @@ -619,8 +681,8 @@ abstract contract LSP7DigitalAssetCore is ILSP7DigitalAsset { _TYPEID_LSP7_TOKENSRECIPIENT, lsp1Data ); - } else if (!allowNonLSP1Recipient) { - if (to.code.length > 0) { + } else if (!force) { + if (to.code.length != 0) { revert LSP7NotifyTokenReceiverContractMissingLSP1Interface(to); } else { revert LSP7NotifyTokenReceiverIsEOA(to); diff --git a/contracts/LSP7DigitalAsset/LSP7DigitalAssetInitAbstract.sol b/contracts/LSP7DigitalAsset/LSP7DigitalAssetInitAbstract.sol index 30e4dfcde..acd9e2751 100644 --- a/contracts/LSP7DigitalAsset/LSP7DigitalAssetInitAbstract.sol +++ b/contracts/LSP7DigitalAsset/LSP7DigitalAssetInitAbstract.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: CC0-1.0 +// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.4; // interfaces @@ -18,8 +18,11 @@ import {LSP2Utils} from "../LSP2ERC725YJSONSchema/LSP2Utils.sol"; // constants import {_INTERFACEID_LSP7} from "./LSP7Constants.sol"; +import {LSP7TokenContractCannotHoldValue} from "./LSP7Errors.sol"; -import "../LSP17ContractExtension/LSP17Constants.sol"; +import { + _LSP17_EXTENSION_PREFIX +} from "../LSP17ContractExtension/LSP17Constants.sol"; // errors @@ -55,7 +58,6 @@ abstract contract LSP7DigitalAssetInitAbstract is // fallback function - // solhint-disable no-complex-fallback /** * @notice The `fallback` function was called with the following amount of native tokens: `msg.value`; and the following calldata: `callData`. * @@ -75,6 +77,7 @@ abstract contract LSP7DigitalAssetInitAbstract is * * 2. If the data sent to this function is of length less than 4 bytes (not a function selector), revert. */ + // solhint-disable-next-line no-complex-fallback fallback( bytes calldata callData ) external payable virtual returns (bytes memory) { @@ -84,6 +87,19 @@ abstract contract LSP7DigitalAssetInitAbstract is return _fallbackLSP17Extendable(callData); } + /** + * @dev Reverts whenever someone tries to send native tokens to a LSP7 contract. + * @notice LSP7 contract cannot receive native tokens. + */ + receive() external payable virtual { + // revert on empty calls with no value + if (msg.value == 0) { + revert InvalidFunctionSelector(hex"00000000"); + } + + revert LSP7TokenContractCannotHoldValue(); + } + /** * @dev Forwards the call with the received value to an extension mapped to a function selector. * @@ -96,10 +112,8 @@ abstract contract LSP7DigitalAssetInitAbstract is * CALL opcode, passing the {msg.data} appended with the 20 bytes of the {msg.sender} and * 32 bytes of the {msg.value} * - * Because the function uses assembly {return()/revert()} to terminate the call, it cannot be - * called before other codes in fallback(). - * - * Otherwise, the codes after _fallbackLSP17Extendable() may never be reached. + * @custom:info The LSP7 Token contract should not hold any native tokens. Any native tokens received by the contract + * will be forwarded to the extension address mapped to the selector from `msg.sig`. */ function _fallbackLSP17Extendable( bytes calldata callData diff --git a/contracts/LSP7DigitalAsset/LSP7Errors.sol b/contracts/LSP7DigitalAsset/LSP7Errors.sol index b01947b81..cc9388941 100644 --- a/contracts/LSP7DigitalAsset/LSP7Errors.sol +++ b/contracts/LSP7DigitalAsset/LSP7Errors.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: CC0-1.0 +// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.4; // --- Errors @@ -48,14 +48,14 @@ error LSP7InvalidTransferBatch(); /** * @dev reverts if the `tokenReceiver` does not implement LSP1 - * when minting or transferring tokens with `bool allowNonLSP1Recipient` set as `false`. + * when minting or transferring tokens with `bool force` set as `false`. */ error LSP7NotifyTokenReceiverContractMissingLSP1Interface( address tokenReceiver ); /** - * @dev reverts if the `tokenReceiver` is an EOA when minting or transferring tokens with `bool allowNonLSP1Recipient` set as `false`. + * @dev reverts if the `tokenReceiver` is an EOA when minting or transferring tokens with `bool force` set as `false`. */ error LSP7NotifyTokenReceiverIsEOA(address tokenReceiver); @@ -68,3 +68,12 @@ error LSP7TokenOwnerCannotBeOperator(); * @dev Reverts when trying to decrease an operator's allowance to more than its current allowance. */ error LSP7DecreasedAllowanceBelowZero(); + +/** + * @dev Error occurs when sending native tokens to the LSP7 contract without sending any data. + * + * E.g. Sending value without passing a bytes4 function selector to call a LSP17 Extension. + * + * @notice LSP7 contract cannot receive native tokens. + */ +error LSP7TokenContractCannotHoldValue(); diff --git a/contracts/LSP7DigitalAsset/extensions/ILSP7CompatibleERC20.sol b/contracts/LSP7DigitalAsset/extensions/ILSP7CompatibleERC20.sol deleted file mode 100644 index a4c2eb361..000000000 --- a/contracts/LSP7DigitalAsset/extensions/ILSP7CompatibleERC20.sol +++ /dev/null @@ -1,83 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.4; - -// interfaces -import {ILSP7DigitalAsset} from "../ILSP7DigitalAsset.sol"; - -/** - * @dev LSP7 extension, for compatibility for clients / tools that expect ERC20. - */ -interface ILSP7CompatibleERC20 is ILSP7DigitalAsset { - /** - * @dev ERC20 `Transfer` event emitted when `amount` tokens is transferred from `from` to `to`. - * To provide compatibility with indexing ERC20 events. - * - * @param from The sending address - * @param to The receiving address - * @param value The amount of tokens transfered. - */ - event Transfer(address indexed from, address indexed to, uint256 value); - - /** - * @dev ERC20 `Approval` event emitted when `owner` enables `spender` for `value` tokens. - * To provide compatibility with indexing ERC20 events. - * - * @param owner The account giving approval - * @param spender The account receiving approval - * @param value The amount of tokens `spender` has access to from `owner` - */ - event Approval( - address indexed owner, - address indexed spender, - uint256 value - ); - - /* - * @dev Transfer function from the ERC20 standard interface. - - * @param to The address receiving tokens. - * @param amount The amount of tokens to transfer. - * - * @return `true` on successful transfer. - */ - function transfer(address to, uint256 amount) external returns (bool); - - /* - * @dev Transfer functions for operators from the ERC20 standard interface. - - * @param from The address sending tokens. - * @param to The address receiving tokens. - * @param amount The amount of tokens to transfer. - * - * @return `true` on successful transfer. - */ - function transferFrom( - address from, - address to, - uint256 amount - ) external returns (bool); - - /* - * @dev Approval function from th ERC20 standard interface. - - * @param operator The address to approve for `amount` - * @param amount The amount to approve. - * - * @return `true` on successful approval. - */ - function approve(address operator, uint256 amount) external returns (bool); - - /* - * @dev Function to get operator allowance allowed to spend on behalf of `tokenOwner` from the ERC20 standard interface. - - * @param tokenOwner The address of the token owner - * @param operator The address approved by the `tokenOwner` - * - * @return The amount `operator` is approved by `tokenOwner` - */ - function allowance( - address tokenOwner, - address operator - ) external view returns (uint256); -} diff --git a/contracts/LSP7DigitalAsset/extensions/LSP7Burnable.sol b/contracts/LSP7DigitalAsset/extensions/LSP7Burnable.sol index f28967ccf..e95130639 100644 --- a/contracts/LSP7DigitalAsset/extensions/LSP7Burnable.sol +++ b/contracts/LSP7DigitalAsset/extensions/LSP7Burnable.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: MIT +// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.4; // modules @@ -17,6 +17,10 @@ abstract contract LSP7Burnable is LSP7DigitalAsset { uint256 amount, bytes memory data ) public virtual { + if (msg.sender != from) { + _spendAllowance(msg.sender, from, amount); + } + _burn(from, amount, data); } } diff --git a/contracts/LSP7DigitalAsset/extensions/LSP7BurnableInitAbstract.sol b/contracts/LSP7DigitalAsset/extensions/LSP7BurnableInitAbstract.sol index 7b542d189..9915fafa7 100644 --- a/contracts/LSP7DigitalAsset/extensions/LSP7BurnableInitAbstract.sol +++ b/contracts/LSP7DigitalAsset/extensions/LSP7BurnableInitAbstract.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: MIT +// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.4; // modules @@ -19,6 +19,9 @@ abstract contract LSP7BurnableInitAbstract is LSP7DigitalAssetInitAbstract { uint256 amount, bytes memory data ) public virtual { + if (msg.sender != from) { + _spendAllowance(msg.sender, from, amount); + } _burn(from, amount, data); } } diff --git a/contracts/LSP7DigitalAsset/extensions/LSP7CappedSupply.sol b/contracts/LSP7DigitalAsset/extensions/LSP7CappedSupply.sol index 0356132bb..6d3eb98f9 100644 --- a/contracts/LSP7DigitalAsset/extensions/LSP7CappedSupply.sol +++ b/contracts/LSP7DigitalAsset/extensions/LSP7CappedSupply.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: MIT +// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.4; @@ -24,7 +24,7 @@ abstract contract LSP7CappedSupply is LSP7DigitalAsset { error LSP7CappedSupplyCannotMintOverCap(); // --- Storage - uint256 private immutable _tokenSupplyCap; + uint256 private immutable _TOKEN_SUPPLY_CAP; /** * @notice Deploying a `LSP7CappedSupply` token contract with max token supply cap set to `tokenSupplyCap_`. @@ -41,13 +41,13 @@ abstract contract LSP7CappedSupply is LSP7DigitalAsset { revert LSP7CappedSupplyRequired(); } - _tokenSupplyCap = tokenSupplyCap_; + _TOKEN_SUPPLY_CAP = tokenSupplyCap_; } // --- Token queries /** - * @notice The maximum supply amount of tokens allowed to exist is `_tokenSupplyCap`. + * @notice The maximum supply amount of tokens allowed to exist is `_TOKEN_SUPPLY_CAP`. * * @dev Get the maximum number of tokens that can exist to circulate. Once {totalSupply} reaches * reaches {totalSuuplyCap}, it is not possible to mint more tokens. @@ -55,7 +55,7 @@ abstract contract LSP7CappedSupply is LSP7DigitalAsset { * @return The maximum number of tokens that can exist in the contract. */ function tokenSupplyCap() public view virtual returns (uint256) { - return _tokenSupplyCap; + return _TOKEN_SUPPLY_CAP; } // --- Transfer functionality @@ -71,13 +71,13 @@ abstract contract LSP7CappedSupply is LSP7DigitalAsset { function _mint( address to, uint256 amount, - bool allowNonLSP1Recipient, + bool force, bytes memory data ) internal virtual override { if (totalSupply() + amount > tokenSupplyCap()) { revert LSP7CappedSupplyCannotMintOverCap(); } - super._mint(to, amount, allowNonLSP1Recipient, data); + super._mint(to, amount, force, data); } } diff --git a/contracts/LSP7DigitalAsset/extensions/LSP7CappedSupplyInitAbstract.sol b/contracts/LSP7DigitalAsset/extensions/LSP7CappedSupplyInitAbstract.sol index 66d0c8686..86acdb471 100644 --- a/contracts/LSP7DigitalAsset/extensions/LSP7CappedSupplyInitAbstract.sol +++ b/contracts/LSP7DigitalAsset/extensions/LSP7CappedSupplyInitAbstract.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: MIT +// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.4; @@ -74,13 +74,13 @@ abstract contract LSP7CappedSupplyInitAbstract is LSP7DigitalAssetInitAbstract { function _mint( address to, uint256 amount, - bool allowNonLSP1Recipient, + bool force, bytes memory data ) internal virtual override { if (totalSupply() + amount > tokenSupplyCap()) { revert LSP7CappedSupplyCannotMintOverCap(); } - super._mint(to, amount, allowNonLSP1Recipient, data); + super._mint(to, amount, force, data); } } diff --git a/contracts/LSP7DigitalAsset/extensions/LSP7CompatibleERC20.sol b/contracts/LSP7DigitalAsset/extensions/LSP7CompatibleERC20.sol index 207bded2b..bc7cfa546 100644 --- a/contracts/LSP7DigitalAsset/extensions/LSP7CompatibleERC20.sol +++ b/contracts/LSP7DigitalAsset/extensions/LSP7CompatibleERC20.sol @@ -1,29 +1,25 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.12; +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.7; // interfaces -import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; -import {ILSP7CompatibleERC20} from "./ILSP7CompatibleERC20.sol"; +import { + IERC20Metadata, + IERC20 +} from "@openzeppelin/contracts/interfaces/IERC20Metadata.sol"; // modules +import {LSP7DigitalAssetCore, LSP7DigitalAsset} from "../LSP7DigitalAsset.sol"; + +// constants import { - LSP4Compatibility -} from "../../LSP4DigitalAssetMetadata/LSP4Compatibility.sol"; -import { - LSP7DigitalAsset, - LSP7DigitalAssetCore, - LSP4DigitalAssetMetadata, - ERC725YCore -} from "../LSP7DigitalAsset.sol"; + _LSP4_TOKEN_NAME_KEY, + _LSP4_TOKEN_SYMBOL_KEY +} from "../../LSP4DigitalAssetMetadata/LSP4Constants.sol"; /** * @dev LSP7 extension, for compatibility for clients / tools that expect ERC20. */ -abstract contract LSP7CompatibleERC20 is - ILSP7CompatibleERC20, - LSP4Compatibility, - LSP7DigitalAsset -{ +abstract contract LSP7CompatibleERC20 is IERC20Metadata, LSP7DigitalAsset { /** * @notice Deploying a `LSP7CompatibleERC20` token contract with: token name = `name_`, token symbol = `symbol_`, and * address `newOwner_` as the token contract owner. @@ -39,22 +35,90 @@ abstract contract LSP7CompatibleERC20 is ) LSP7DigitalAsset(name_, symbol_, newOwner_, false) {} /** - * @inheritdoc LSP7DigitalAsset + * @inheritdoc IERC20Metadata + * @dev Returns the name of the token. + * For compatibility with clients & tools that expect ERC20. + * + * @return The name of the token */ - function supportsInterface( - bytes4 interfaceId + function name() public view virtual override returns (string memory) { + bytes memory data = _getData(_LSP4_TOKEN_NAME_KEY); + return string(data); + } + + /** + * @inheritdoc IERC20Metadata + * @dev Returns the symbol of the token, usually a shorter version of the name. + * For compatibility with clients & tools that expect ERC20. + * + * @return The symbol of the token + */ + function symbol() public view virtual override returns (string memory) { + bytes memory data = _getData(_LSP4_TOKEN_SYMBOL_KEY); + return string(data); + } + + /** + * @inheritdoc LSP7DigitalAssetCore + */ + function decimals() + public + view + virtual + override(IERC20Metadata, LSP7DigitalAssetCore) + returns (uint8) + { + return super.decimals(); + } + + /** + * @inheritdoc LSP7DigitalAssetCore + */ + function totalSupply() + public + view + virtual + override(IERC20, LSP7DigitalAssetCore) + returns (uint256) + { + return super.totalSupply(); + } + + /** + * @inheritdoc LSP7DigitalAssetCore + */ + function balanceOf( + address tokenOwner ) public view virtual - override(IERC165, ERC725YCore, LSP7DigitalAsset) - returns (bool) + override(IERC20, LSP7DigitalAssetCore) + returns (uint256) { - return super.supportsInterface(interfaceId); + return super.balanceOf(tokenOwner); } /** - * @inheritdoc ILSP7CompatibleERC20 + * @inheritdoc LSP7DigitalAsset + */ + function supportsInterface( + bytes4 interfaceId + ) public view virtual override returns (bool) { + return + interfaceId == type(IERC20).interfaceId || + interfaceId == type(IERC20Metadata).interfaceId || + super.supportsInterface(interfaceId); + } + + /** + * @inheritdoc IERC20 + * @dev Function to get operator allowance allowed to spend on behalf of `tokenOwner` from the ERC20 standard interface. + * + * @param tokenOwner The address of the token owner + * @param operator The address approved by the `tokenOwner` + * + * @return The amount `operator` is approved by `tokenOwner` */ function allowance( address tokenOwner, @@ -64,7 +128,13 @@ abstract contract LSP7CompatibleERC20 is } /** - * @inheritdoc ILSP7CompatibleERC20 + * @inheritdoc IERC20 + * @dev Approval function from th ERC20 standard interface. + * + * @param operator The address to approve for `amount` + * @param amount The amount to approve. + * + * @return `true` on successful approval. */ function approve( address operator, @@ -75,9 +145,16 @@ abstract contract LSP7CompatibleERC20 is } /** - * @inheritdoc ILSP7CompatibleERC20 + * @inheritdoc IERC20 + * @dev Transfer functions for operators from the ERC20 standard interface. + * + * @param from The address sending tokens. + * @param to The address receiving tokens. + * @param amount The amount of tokens to transfer. * - * @custom:info This function uses the `allowNonLSP1Recipient` parameter as `true` so that EOA and any contract can receive tokens. + * @return `true` on successful transfer. + * + * @custom:info This function uses the `force` parameter as `true` so that EOA and any contract can receive tokens. */ function transferFrom( address from, @@ -91,9 +168,15 @@ abstract contract LSP7CompatibleERC20 is // --- Overrides /** - * @inheritdoc ILSP7CompatibleERC20 + * @inheritdoc IERC20 + * @dev Transfer function from the ERC20 standard interface. + + * @param to The address receiving tokens. + * @param amount The amount of tokens to transfer. + * + * @return `true` on successful transfer. * - * @custom:info This function uses the `allowNonLSP1Recipient` parameter as `true` so that EOA and any contract can receive tokens. + * @custom:info This function uses the `force` parameter as `true` so that EOA and any contract can receive tokens. */ function transfer( address to, @@ -118,12 +201,10 @@ abstract contract LSP7CompatibleERC20 is amount, operatorNotificationData ); - emit Approval(tokenOwner, operator, amount); + emit IERC20.Approval(tokenOwner, operator, amount); } /** - * @inheritdoc LSP7DigitalAssetCore - * * @custom:events * - LSP7 {Transfer} event. * - ERC20 {Transfer} event. @@ -132,16 +213,14 @@ abstract contract LSP7CompatibleERC20 is address from, address to, uint256 amount, - bool allowNonLSP1Recipient, + bool force, bytes memory data ) internal virtual override { - emit Transfer(from, to, amount); - super._transfer(from, to, amount, allowNonLSP1Recipient, data); + emit IERC20.Transfer(from, to, amount); + super._transfer(from, to, amount, force, data); } /** - * @inheritdoc LSP7DigitalAssetCore - * * @custom:events * - LSP7 {Transfer} event with `address(0)` as `from`. * - ERC20 {Transfer} event with `address(0)` as `from`. @@ -149,16 +228,14 @@ abstract contract LSP7CompatibleERC20 is function _mint( address to, uint256 amount, - bool allowNonLSP1Recipient, + bool force, bytes memory data ) internal virtual override { - emit Transfer(address(0), to, amount); - super._mint(to, amount, allowNonLSP1Recipient, data); + emit IERC20.Transfer(address(0), to, amount); + super._mint(to, amount, force, data); } /** - * @inheritdoc LSP7DigitalAssetCore - * * @custom:events * - LSP7 {Transfer} event with `address(0)` as the `to` address. * - ERC20 {Transfer} event with `address(0)` as the `to` address. @@ -168,17 +245,7 @@ abstract contract LSP7CompatibleERC20 is uint256 amount, bytes memory data ) internal virtual override { - emit Transfer(from, address(0), amount); + emit IERC20.Transfer(from, address(0), amount); super._burn(from, amount, data); } - - /** - * @inheritdoc LSP4DigitalAssetMetadata - */ - function _setData( - bytes32 key, - bytes memory value - ) internal virtual override(LSP4DigitalAssetMetadata, ERC725YCore) { - super._setData(key, value); - } } diff --git a/contracts/LSP7DigitalAsset/extensions/LSP7CompatibleERC20InitAbstract.sol b/contracts/LSP7DigitalAsset/extensions/LSP7CompatibleERC20InitAbstract.sol index c79f81e7c..840e537ed 100644 --- a/contracts/LSP7DigitalAsset/extensions/LSP7CompatibleERC20InitAbstract.sol +++ b/contracts/LSP7DigitalAsset/extensions/LSP7CompatibleERC20InitAbstract.sol @@ -1,27 +1,29 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.12; +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.7; // interfaces -import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; -import {ILSP7CompatibleERC20} from "./ILSP7CompatibleERC20.sol"; +import { + IERC20Metadata, + IERC20 +} from "@openzeppelin/contracts/interfaces/IERC20Metadata.sol"; // modules -import { - LSP4Compatibility -} from "../../LSP4DigitalAssetMetadata/LSP4Compatibility.sol"; import { LSP7DigitalAssetCore, - LSP7DigitalAssetInitAbstract, - LSP4DigitalAssetMetadataInitAbstract, - ERC725YCore + LSP7DigitalAssetInitAbstract } from "../LSP7DigitalAssetInitAbstract.sol"; +// constants +import { + _LSP4_TOKEN_NAME_KEY, + _LSP4_TOKEN_SYMBOL_KEY +} from "../../LSP4DigitalAssetMetadata/LSP4Constants.sol"; + /** * @dev LSP7 extension, for compatibility for clients / tools that expect ERC20. */ abstract contract LSP7CompatibleERC20InitAbstract is - ILSP7CompatibleERC20, - LSP4Compatibility, + IERC20Metadata, LSP7DigitalAssetInitAbstract { /** @@ -46,32 +48,106 @@ abstract contract LSP7CompatibleERC20InitAbstract is } /** - * @inheritdoc LSP7DigitalAssetInitAbstract + * @inheritdoc IERC20Metadata + * @dev Returns the name of the token. + * For compatibility with clients & tools that expect ERC20. + * + * @return The name of the token */ - function supportsInterface( - bytes4 interfaceId + function name() public view virtual override returns (string memory) { + bytes memory data = _getData(_LSP4_TOKEN_NAME_KEY); + return string(data); + } + + /** + * @inheritdoc IERC20Metadata + * @dev Returns the symbol of the token, usually a shorter version of the name. + * For compatibility with clients & tools that expect ERC20. + * + * @return The symbol of the token + */ + function symbol() public view virtual override returns (string memory) { + bytes memory data = _getData(_LSP4_TOKEN_SYMBOL_KEY); + return string(data); + } + + /** + * @inheritdoc LSP7DigitalAssetCore + */ + function decimals() + public + view + virtual + override(IERC20Metadata, LSP7DigitalAssetCore) + returns (uint8) + { + return super.decimals(); + } + + /** + * @inheritdoc LSP7DigitalAssetCore + */ + function totalSupply() + public + view + virtual + override(IERC20, LSP7DigitalAssetCore) + returns (uint256) + { + return super.totalSupply(); + } + + /** + * @inheritdoc LSP7DigitalAssetCore + */ + function balanceOf( + address tokenOwner ) public view virtual - override(IERC165, ERC725YCore, LSP7DigitalAssetInitAbstract) - returns (bool) + override(IERC20, LSP7DigitalAssetCore) + returns (uint256) { - return super.supportsInterface(interfaceId); + return super.balanceOf(tokenOwner); + } + + /** + * @inheritdoc LSP7DigitalAssetInitAbstract + */ + function supportsInterface( + bytes4 interfaceId + ) public view virtual override returns (bool) { + return + interfaceId == type(IERC20).interfaceId || + interfaceId == type(IERC20Metadata).interfaceId || + super.supportsInterface(interfaceId); } /** - * @inheritdoc ILSP7CompatibleERC20 + * @inheritdoc IERC20 + * @dev Function to get operator allowance allowed to spend on behalf of `tokenOwner` from the ERC20 standard interface. + * + * @param tokenOwner The address of the token owner + * @param operator The address approved by the `tokenOwner` + * + * @return The amount `operator` is approved by `tokenOwner` */ function allowance( address tokenOwner, address operator - ) public view virtual returns (uint256) { + ) public view virtual override returns (uint256) { return authorizedAmountFor(operator, tokenOwner); } /** - * @inheritdoc ILSP7CompatibleERC20 + * @inheritdoc IERC20 + * @dev Approval function from th ERC20 standard interface. + * + * @param operator The address to approve for `amount` + * @param amount The amount to approve. + * + * @return `true` on successful approval. */ function approve( address operator, @@ -82,9 +158,16 @@ abstract contract LSP7CompatibleERC20InitAbstract is } /** - * @inheritdoc ILSP7CompatibleERC20 + * @inheritdoc IERC20 + * @dev Transfer functions for operators from the ERC20 standard interface. + * + * @param from The address sending tokens. + * @param to The address receiving tokens. + * @param amount The amount of tokens to transfer. * - * @custom:info This function uses the `allowNonLSP1Recipient` parameter as `true` so that EOA and any contract can receive tokens. + * @return `true` on successful transfer. + * + * @custom:info This function uses the `force` parameter as `true` so that EOA and any contract can receive tokens. */ function transferFrom( address from, @@ -98,9 +181,15 @@ abstract contract LSP7CompatibleERC20InitAbstract is // --- Overrides /** - * @inheritdoc ILSP7CompatibleERC20 + * @inheritdoc IERC20 + * @dev Transfer function from the ERC20 standard interface. + * + * @param to The address receiving tokens. + * @param amount The amount of tokens to transfer. * - * @custom:info This function uses the `allowNonLSP1Recipient` parameter as `true` so that EOA and any contract can receive tokens. + * @return `true` on successful transfer. + * + * @custom:info This function uses the `force` parameter as `true` so that EOA and any contract can receive tokens. */ function transfer( address to, @@ -125,12 +214,10 @@ abstract contract LSP7CompatibleERC20InitAbstract is amount, operatorNotificationData ); - emit Approval(tokenOwner, operator, amount); + emit IERC20.Approval(tokenOwner, operator, amount); } /** - * @inheritdoc LSP7DigitalAssetCore - * * @custom:events * - LSP7 {Transfer} event. * - ERC20 {Transfer} event. @@ -139,16 +226,14 @@ abstract contract LSP7CompatibleERC20InitAbstract is address from, address to, uint256 amount, - bool allowNonLSP1Recipient, + bool force, bytes memory data ) internal virtual override { - emit Transfer(from, to, amount); - super._transfer(from, to, amount, allowNonLSP1Recipient, data); + emit IERC20.Transfer(from, to, amount); + super._transfer(from, to, amount, force, data); } /** - * @inheritdoc LSP7DigitalAssetCore - * * @custom:events * - LSP7 {Transfer} event with `address(0)` as `from`. * - ERC20 {Transfer} event with `address(0)` as `from`. @@ -156,16 +241,14 @@ abstract contract LSP7CompatibleERC20InitAbstract is function _mint( address to, uint256 amount, - bool allowNonLSP1Recipient, + bool force, bytes memory data ) internal virtual override { - emit Transfer(address(0), to, amount); - super._mint(to, amount, allowNonLSP1Recipient, data); + emit IERC20.Transfer(address(0), to, amount); + super._mint(to, amount, force, data); } /** - * @inheritdoc LSP7DigitalAssetCore - * * @custom:events * - LSP7 {Transfer} event with `address(0)` as the `to` address. * - ERC20 {Transfer} event with `address(0)` as the `to` address. @@ -175,21 +258,7 @@ abstract contract LSP7CompatibleERC20InitAbstract is uint256 amount, bytes memory data ) internal virtual override { - emit Transfer(from, address(0), amount); + emit IERC20.Transfer(from, address(0), amount); super._burn(from, amount, data); } - - /** - * @inheritdoc LSP4DigitalAssetMetadataInitAbstract - */ - function _setData( - bytes32 key, - bytes memory value - ) - internal - virtual - override(LSP4DigitalAssetMetadataInitAbstract, ERC725YCore) - { - super._setData(key, value); - } } diff --git a/contracts/LSP7DigitalAsset/presets/ILSP7Mintable.sol b/contracts/LSP7DigitalAsset/presets/ILSP7Mintable.sol index 7624bf8d6..7ec9c5615 100644 --- a/contracts/LSP7DigitalAsset/presets/ILSP7Mintable.sol +++ b/contracts/LSP7DigitalAsset/presets/ILSP7Mintable.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: MIT +// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.4; @@ -12,7 +12,7 @@ interface ILSP7Mintable is ILSP7DigitalAsset { /** * @param to The address to mint tokens * @param amount The amount to mint - * @param allowNonLSP1Recipient When set to TRUE, to may be any address but + * @param force When set to TRUE, to may be any address but * when set to FALSE to must be a contract that supports LSP1 UniversalReceiver * @param data Additional data the caller wants included in the emitted event, and sent in the hooks to `from` and `to` addresses. * @dev Mints `amount` tokens and transfers it to `to`. @@ -26,7 +26,7 @@ interface ILSP7Mintable is ILSP7DigitalAsset { function mint( address to, uint256 amount, - bool allowNonLSP1Recipient, + bool force, bytes memory data ) external; } diff --git a/contracts/LSP7DigitalAsset/presets/LSP7CompatibleERC20Mintable.sol b/contracts/LSP7DigitalAsset/presets/LSP7CompatibleERC20Mintable.sol index dc25a81c3..c3833e16e 100644 --- a/contracts/LSP7DigitalAsset/presets/LSP7CompatibleERC20Mintable.sol +++ b/contracts/LSP7DigitalAsset/presets/LSP7CompatibleERC20Mintable.sol @@ -1,5 +1,5 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.12; +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.7; import {LSP7CompatibleERC20} from "../extensions/LSP7CompatibleERC20.sol"; @@ -27,9 +27,9 @@ contract LSP7CompatibleERC20Mintable is LSP7CompatibleERC20 { function mint( address to, uint256 amount, - bool allowNonLSP1Recipient, + bool force, bytes memory data ) public virtual onlyOwner { - _mint(to, amount, allowNonLSP1Recipient, data); + _mint(to, amount, force, data); } } diff --git a/contracts/LSP7DigitalAsset/presets/LSP7CompatibleERC20MintableInit.sol b/contracts/LSP7DigitalAsset/presets/LSP7CompatibleERC20MintableInit.sol index 7bf424ce6..83ace62b5 100644 --- a/contracts/LSP7DigitalAsset/presets/LSP7CompatibleERC20MintableInit.sol +++ b/contracts/LSP7DigitalAsset/presets/LSP7CompatibleERC20MintableInit.sol @@ -1,5 +1,5 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.12; +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.7; // modules import { diff --git a/contracts/LSP7DigitalAsset/presets/LSP7CompatibleERC20MintableInitAbstract.sol b/contracts/LSP7DigitalAsset/presets/LSP7CompatibleERC20MintableInitAbstract.sol index fe5b69b88..9b831d11a 100644 --- a/contracts/LSP7DigitalAsset/presets/LSP7CompatibleERC20MintableInitAbstract.sol +++ b/contracts/LSP7DigitalAsset/presets/LSP7CompatibleERC20MintableInitAbstract.sol @@ -1,5 +1,5 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.12; +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.7; // modules import { @@ -29,9 +29,9 @@ abstract contract LSP7CompatibleERC20MintableInitAbstract is function mint( address to, uint256 amount, - bool allowNonLSP1Recipient, + bool force, bytes memory data ) public virtual onlyOwner { - _mint(to, amount, allowNonLSP1Recipient, data); + _mint(to, amount, force, data); } } diff --git a/contracts/LSP7DigitalAsset/presets/LSP7Mintable.sol b/contracts/LSP7DigitalAsset/presets/LSP7Mintable.sol index a11d44804..5091862cf 100644 --- a/contracts/LSP7DigitalAsset/presets/LSP7Mintable.sol +++ b/contracts/LSP7DigitalAsset/presets/LSP7Mintable.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: MIT +// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.4; @@ -34,9 +34,9 @@ contract LSP7Mintable is LSP7DigitalAsset, ILSP7Mintable { function mint( address to, uint256 amount, - bool allowNonLSP1Recipient, + bool force, bytes memory data - ) public virtual onlyOwner { - _mint(to, amount, allowNonLSP1Recipient, data); + ) public virtual override onlyOwner { + _mint(to, amount, force, data); } } diff --git a/contracts/LSP7DigitalAsset/presets/LSP7MintableInit.sol b/contracts/LSP7DigitalAsset/presets/LSP7MintableInit.sol index 21bb6e87e..d3e395f26 100644 --- a/contracts/LSP7DigitalAsset/presets/LSP7MintableInit.sol +++ b/contracts/LSP7DigitalAsset/presets/LSP7MintableInit.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: MIT +// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.4; diff --git a/contracts/LSP7DigitalAsset/presets/LSP7MintableInitAbstract.sol b/contracts/LSP7DigitalAsset/presets/LSP7MintableInitAbstract.sol index 234577d4d..a32716124 100644 --- a/contracts/LSP7DigitalAsset/presets/LSP7MintableInitAbstract.sol +++ b/contracts/LSP7DigitalAsset/presets/LSP7MintableInitAbstract.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: MIT +// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.4; @@ -45,9 +45,9 @@ abstract contract LSP7MintableInitAbstract is function mint( address to, uint256 amount, - bool allowNonLSP1Recipient, + bool force, bytes memory data - ) public virtual onlyOwner { - _mint(to, amount, allowNonLSP1Recipient, data); + ) public virtual override onlyOwner { + _mint(to, amount, force, data); } } diff --git a/contracts/LSP8IdentifiableDigitalAsset/ILSP8IdentifiableDigitalAsset.sol b/contracts/LSP8IdentifiableDigitalAsset/ILSP8IdentifiableDigitalAsset.sol index 82c401837..53aec6be2 100644 --- a/contracts/LSP8IdentifiableDigitalAsset/ILSP8IdentifiableDigitalAsset.sol +++ b/contracts/LSP8IdentifiableDigitalAsset/ILSP8IdentifiableDigitalAsset.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: MIT +// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.4; @@ -20,7 +20,7 @@ interface ILSP8IdentifiableDigitalAsset is IERC165, IERC725Y { * @param from The previous owner of the `tokenId` * @param to The new owner of `tokenId` * @param tokenId The tokenId that was transferred - * @param allowNonLSP1Recipient If the token transfer enforces the `to` recipient address to be a contract that implements the LSP1 standard or not. + * @param force If the token transfer enforces the `to` recipient address to be a contract that implements the LSP1 standard or not. * @param data Any additional data the caller included by the caller during the transfer, and sent in the hooks to the `from` and `to` addresses. */ event Transfer( @@ -28,7 +28,7 @@ interface ILSP8IdentifiableDigitalAsset is IERC165, IERC725Y { address indexed from, address indexed to, bytes32 indexed tokenId, - bool allowNonLSP1Recipient, + bool force, bytes data ); @@ -182,13 +182,13 @@ interface ILSP8IdentifiableDigitalAsset is IERC165, IERC725Y { * * If operators are set for a specific `tokenId`, all the operators are revoked after the tokenId have been transferred. * - * The `allowNonLSP1Recipient` parameter MUST be set to `true` when transferring tokens to Externally Owned Accounts (EOAs) + * The `force` parameter MUST be set to `true` when transferring tokens to Externally Owned Accounts (EOAs) * or contracts that do not implement the LSP1 standard. * * @param from The address that owns the given `tokenId`. * @param to The address that will receive the `tokenId`. * @param tokenId The token ID to transfer. - * @param allowNonLSP1Recipient When set to `true`, the `to` address CAN be any addres. + * @param force When set to `true`, the `to` address CAN be any addres. * When set to `false`, the `to` address MUST be a contract that supports the LSP1 UniversalReceiver standard. * @param data Any additional data the caller wants included in the emitted event, and sent in the hooks of the `from` and `to` addresses. * @@ -202,19 +202,19 @@ interface ILSP8IdentifiableDigitalAsset is IERC165, IERC725Y { * @custom:events * - {Transfer} event when the `tokenId` is successfully transferred. * - * @custom:hint The `allowNonLSP1Recipient` parameter **MUST be set to `true`** to transfer tokens to Externally Owned Accounts (EOAs) + * @custom:hint The `force` parameter **MUST be set to `true`** to transfer tokens to Externally Owned Accounts (EOAs) * or contracts that do not implement the LSP1 Universal Receiver Standard. Otherwise the function will revert making the transfer fail. * - * @custom:info if the `to` address is a contract that implements LSP1, it will always be notified via its `universalReceiver(...)` function, regardless if `allowNonLSP1Recipient` is set to `true` or `false`. + * @custom:info if the `to` address is a contract that implements LSP1, it will always be notified via its `universalReceiver(...)` function, regardless if `force` is set to `true` or `false`. * * @custom:warning Be aware that when either the sender or the recipient can have logic that revert in their `universalReceiver(...)` function when being notified. - * This even if the `allowNonLSP1Recipient` was set to `true`. + * This even if the `force` was set to `true`. */ function transfer( address from, address to, bytes32 tokenId, - bool allowNonLSP1Recipient, + bool force, bytes memory data ) external; @@ -225,7 +225,7 @@ interface ILSP8IdentifiableDigitalAsset is IERC165, IERC725Y { * @param from An array of sending addresses. * @param to An array of recipient addresses. * @param tokenId An array of token IDs to transfer. - * @param allowNonLSP1Recipient When set to `true`, `to` may be any address. + * @param force When set to `true`, `to` may be any address. * When set to `false`, `to` must be a contract that supports the LSP1 standard and not revert. * @param data Any additional data the caller wants included in the emitted event, and sent in the hooks to the `from` and `to` addresses. * @@ -245,7 +245,7 @@ interface ILSP8IdentifiableDigitalAsset is IERC165, IERC725Y { address[] memory from, address[] memory to, bytes32[] memory tokenId, - bool[] memory allowNonLSP1Recipient, + bool[] memory force, bytes[] memory data ) external; } diff --git a/contracts/LSP8IdentifiableDigitalAsset/LSP8Constants.sol b/contracts/LSP8IdentifiableDigitalAsset/LSP8Constants.sol index 72c71c754..a117eeaf1 100644 --- a/contracts/LSP8IdentifiableDigitalAsset/LSP8Constants.sol +++ b/contracts/LSP8IdentifiableDigitalAsset/LSP8Constants.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: CC0-1.0 +// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.4; // --- ERC165 interface ids @@ -6,6 +6,9 @@ bytes4 constant _INTERFACEID_LSP8 = 0x1ae9ba1f; // --- ERC725Y Data Keys +// keccak256('LSP8TokenIdType') +bytes32 constant _LSP8_TOKENID_TYPE_KEY = 0x715f248956de7ce65e94d9d836bfead479f7e70d69b718d47bfe7b00e05b4fe4; + // bytes10(keccak256('LSP8MetadataAddress')) + bytes2(0) bytes12 constant _LSP8_METADATA_ADDRESS_KEY_PREFIX = 0x73dcc7c3c4096cdc7f8a0000; @@ -22,3 +25,11 @@ bytes32 constant _TYPEID_LSP8_TOKENSRECIPIENT = 0x0b084a55ebf70fd3c06fd755269dac // keccak256('LSP8Tokens_OperatorNotification') bytes32 constant _TYPEID_LSP8_TOKENOPERATOR = 0x8a1c15a8799f71b547e08e2bcb2e85257e81b0a07eee2ce6712549eef1f00970; + +// --- Types of token IDs + +uint256 constant _LSP8_TOKENID_TYPE_NUMBER = 0; +uint256 constant _LSP8_TOKENID_TYPE_STRING = 1; +uint256 constant _LSP8_TOKENID_TYPE_UNIQUE_ID = 2; +uint256 constant _LSP8_TOKENID_TYPE_HASH = 3; +uint256 constant _LSP8_TOKENID_TYPE_ADDRESS = 4; diff --git a/contracts/LSP8IdentifiableDigitalAsset/LSP8Errors.sol b/contracts/LSP8IdentifiableDigitalAsset/LSP8Errors.sol index c69ce9198..3a37b8ca3 100644 --- a/contracts/LSP8IdentifiableDigitalAsset/LSP8Errors.sol +++ b/contracts/LSP8IdentifiableDigitalAsset/LSP8Errors.sol @@ -1,73 +1,89 @@ -// SPDX-License-Identifier: CC0-1.0 +// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.4; // --- Errors /** - * @dev reverts when `tokenId` has not been minted. + * @dev Reverts when `tokenId` has not been minted. */ error LSP8NonExistentTokenId(bytes32 tokenId); /** - * @dev reverts when `caller` is not the `tokenOwner` of the `tokenId`. + * @dev Reverts when `caller` is not the `tokenOwner` of the `tokenId`. */ error LSP8NotTokenOwner(address tokenOwner, bytes32 tokenId, address caller); /** - * @dev reverts when `caller` is not an allowed operator for `tokenId`. + * @dev Reverts when `caller` is not an allowed operator for `tokenId`. */ error LSP8NotTokenOperator(bytes32 tokenId, address caller); /** - * @dev reverts when `operator` is already authorized for the `tokenId`. + * @dev Reverts when `operator` is already authorized for the `tokenId`. */ error LSP8OperatorAlreadyAuthorized(address operator, bytes32 tokenId); /** - * @dev reverts when trying to set the zero address as an operator. + * @dev Reverts when trying to set the zero address as an operator. */ error LSP8CannotUseAddressZeroAsOperator(); /** - * @dev reverts when trying to send token to the zero address. + * @dev Reverts when trying to send token to the zero address. */ error LSP8CannotSendToAddressZero(); /** - * @dev reverts when specifying the same address for `from` and `to` in a token transfer. + * @dev Reverts when specifying the same address for `from` and `to` in a token transfer. */ error LSP8CannotSendToSelf(); /** - * @dev reverts when `operator` is not an operator for the `tokenId`. + * @dev Reverts when `operator` is not an operator for the `tokenId`. */ error LSP8NonExistingOperator(address operator, bytes32 tokenId); /** - * @dev reverts when `tokenId` has already been minted. + * @dev Reverts when `tokenId` has already been minted. */ error LSP8TokenIdAlreadyMinted(bytes32 tokenId); /** - * @dev reverts when the parameters used for `transferBatch` have different lengths. + * @dev Reverts when the parameters used for `transferBatch` have different lengths. */ error LSP8InvalidTransferBatch(); /** - * @dev reverts if the `tokenReceiver` does not implement LSP1 - * when minting or transferring tokens with `bool allowNonLSP1Recipient` set as `false`. + * @dev Reverts if the `tokenReceiver` does not implement LSP1 + * when minting or transferring tokens with `bool force` set as `false`. */ error LSP8NotifyTokenReceiverContractMissingLSP1Interface( address tokenReceiver ); /** - * @dev reverts if the `tokenReceiver` is an EOA - * when minting or transferring tokens with `bool allowNonLSP1Recipient` set as `false`. + * @dev Reverts if the `tokenReceiver` is an EOA + * when minting or transferring tokens with `bool force` set as `false`. */ error LSP8NotifyTokenReceiverIsEOA(address tokenReceiver); /** - * @dev reverts when trying to authorize or revoke the token's owner as an operator. + * @dev Reverts when trying to authorize or revoke the token's owner as an operator. */ error LSP8TokenOwnerCannotBeOperator(); + +/** + * @dev Error occurs when sending native tokens to the LSP8 contract without sending any data. + * + * E.g. Sending value without passing a bytes4 function selector to call a LSP17 Extension. + * + * @notice LSP8 contract cannot receive native tokens. + */ +error LSP8TokenContractCannotHoldValue(); + +/** + * @dev Reverts when trying to edit the data key `LSP8TokenIdType` after the identifiable digital asset contract has been deployed. + * The `LSP8TokenIdType` data key is located inside the ERC725Y Data key-value store of the identifiable digital asset contract. + * It can be set only once inside the constructor/initializer when the identifiable digital asset contract is being deployed. + */ +error LSP8TokenIdTypeNotEditable(); diff --git a/contracts/LSP8IdentifiableDigitalAsset/LSP8IdentifiableDigitalAsset.sol b/contracts/LSP8IdentifiableDigitalAsset/LSP8IdentifiableDigitalAsset.sol index 44065d764..0143d4ae0 100644 --- a/contracts/LSP8IdentifiableDigitalAsset/LSP8IdentifiableDigitalAsset.sol +++ b/contracts/LSP8IdentifiableDigitalAsset/LSP8IdentifiableDigitalAsset.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: CC0-1.0 +// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.4; // interfaces @@ -19,9 +19,17 @@ import {LSP17Extendable} from "../LSP17ContractExtension/LSP17Extendable.sol"; import {LSP2Utils} from "../LSP2ERC725YJSONSchema/LSP2Utils.sol"; // constants -import {_INTERFACEID_LSP8} from "./LSP8Constants.sol"; +import {_INTERFACEID_LSP8, _LSP8_TOKENID_TYPE_KEY} from "./LSP8Constants.sol"; -import "../LSP17ContractExtension/LSP17Constants.sol"; +// errors +import { + LSP8TokenContractCannotHoldValue, + LSP8TokenIdTypeNotEditable +} from "./LSP8Errors.sol"; + +import { + _LSP17_EXTENSION_PREFIX +} from "../LSP17ContractExtension/LSP17Constants.sol"; // errors @@ -48,20 +56,35 @@ abstract contract LSP8IdentifiableDigitalAsset is LSP17Extendable { /** - * @notice Sets the token-Metadata + * @notice Deploying a LSP8IdentifiableDigitalAsset with name `name_`, symbol `symbol_`, owned by address `newOwner_` + * with tokenId type `tokenIdType_`. + * + * @dev Deploy a `LSP8IdentifiableDigitalAsset` contract and set the tokenId type inside the ERC725Y storage of the contract. + * This will also set the token `name_` and `symbol_` under the ERC725Y data keys `LSP4TokenName` and `LSP4TokenSymbol`. + * * @param name_ The name of the token * @param symbol_ The symbol of the token * @param newOwner_ The owner of the the token-Metadata + * @param tokenIdType_ The type of tokenIds (= NFTs) that this contract will create. + * Available options are: NUMBER = `0`; STRING = `1`; UNIQUE_ID = `2`; HASH = `3`; ADDRESS = `4`. + * + * @custom:warning Make sure the tokenId type provided on deployment is correct, as it can only be set once + * and cannot be changed in the ERC725Y storage after the contract has been deployed. */ constructor( string memory name_, string memory symbol_, - address newOwner_ - ) LSP4DigitalAssetMetadata(name_, symbol_, newOwner_) {} + address newOwner_, + uint256 tokenIdType_ + ) LSP4DigitalAssetMetadata(name_, symbol_, newOwner_) { + LSP4DigitalAssetMetadata._setData( + _LSP8_TOKENID_TYPE_KEY, + abi.encode(tokenIdType_) + ); + } // fallback function - // solhint-disable no-complex-fallback /** * @notice The `fallback` function was called with the following amount of native tokens: `msg.value`; and the following calldata: `callData`. * @@ -81,6 +104,7 @@ abstract contract LSP8IdentifiableDigitalAsset is * * 2. If the data sent to this function is of length less than 4 bytes (not a function selector), revert. */ + // solhint-disable-next-line no-complex-fallback fallback( bytes calldata callData ) external payable virtual returns (bytes memory) { @@ -90,6 +114,19 @@ abstract contract LSP8IdentifiableDigitalAsset is return _fallbackLSP17Extendable(callData); } + /** + * @dev Reverts whenever someone tries to send native tokens to a LSP8 contract. + * @notice LSP8 contract cannot receive native tokens. + */ + receive() external payable virtual { + // revert on empty calls with no value + if (msg.value == 0) { + revert InvalidFunctionSelector(hex"00000000"); + } + + revert LSP8TokenContractCannotHoldValue(); + } + /** * @dev Forwards the call with the received value to an extension mapped to a function selector. * @@ -102,10 +139,8 @@ abstract contract LSP8IdentifiableDigitalAsset is * CALL opcode, passing the {msg.data} appended with the 20 bytes of the {msg.sender} and * 32 bytes of the {msg.value} * - * Because the function uses assembly {return()/revert()} to terminate the call, it cannot be - * called before other codes in fallback(). - * - * Otherwise, the codes after _fallbackLSP17Extendable() may never be reached. + * @custom:info The LSP8 Token contract should not hold any native tokens. Any native tokens received by the contract + * will be forwarded to the extension address mapped to the selector from `msg.sig`. */ function _fallbackLSP17Extendable( bytes calldata callData @@ -176,4 +211,19 @@ abstract contract LSP8IdentifiableDigitalAsset is super.supportsInterface(interfaceId) || LSP17Extendable._supportsInterfaceInERC165Extension(interfaceId); } + + /** + * @inheritdoc LSP4DigitalAssetMetadata + * @dev The ERC725Y data key `_LSP8_TOKENID_TYPE_KEY` cannot be changed + * once the identifiable digital asset contract has been deployed. + */ + function _setData( + bytes32 dataKey, + bytes memory dataValue + ) internal virtual override { + if (dataKey == _LSP8_TOKENID_TYPE_KEY) { + revert LSP8TokenIdTypeNotEditable(); + } + LSP4DigitalAssetMetadata._setData(dataKey, dataValue); + } } diff --git a/contracts/LSP8IdentifiableDigitalAsset/LSP8IdentifiableDigitalAssetCore.sol b/contracts/LSP8IdentifiableDigitalAsset/LSP8IdentifiableDigitalAssetCore.sol index 2a8c93257..3a0d0e995 100644 --- a/contracts/LSP8IdentifiableDigitalAsset/LSP8IdentifiableDigitalAssetCore.sol +++ b/contracts/LSP8IdentifiableDigitalAsset/LSP8IdentifiableDigitalAssetCore.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: CC0-1.0 +// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.4; // interfaces @@ -18,7 +18,21 @@ import { } from "@openzeppelin/contracts/utils/introspection/ERC165Checker.sol"; // errors -import "./LSP8Errors.sol"; +import { + LSP8NonExistentTokenId, + LSP8NotTokenOwner, + LSP8CannotUseAddressZeroAsOperator, + LSP8TokenOwnerCannotBeOperator, + LSP8OperatorAlreadyAuthorized, + LSP8NotTokenOperator, + LSP8InvalidTransferBatch, + LSP8NonExistingOperator, + LSP8CannotSendToAddressZero, + LSP8TokenIdAlreadyMinted, + LSP8CannotSendToSelf, + LSP8NotifyTokenReceiverContractMissingLSP1Interface, + LSP8NotifyTokenReceiverIsEOA +} from "./LSP8Errors.sol"; // constants import {_INTERFACEID_LSP1} from "../LSP1UniversalReceiver/LSP1Constants.sol"; @@ -59,7 +73,7 @@ abstract contract LSP8IdentifiableDigitalAssetCore is /** * @inheritdoc ILSP8IdentifiableDigitalAsset */ - function totalSupply() public view virtual returns (uint256) { + function totalSupply() public view virtual override returns (uint256) { return _existingTokens; } @@ -70,7 +84,7 @@ abstract contract LSP8IdentifiableDigitalAssetCore is */ function balanceOf( address tokenOwner - ) public view virtual returns (uint256) { + ) public view virtual override returns (uint256) { return _ownedTokens[tokenOwner].length(); } @@ -79,7 +93,7 @@ abstract contract LSP8IdentifiableDigitalAssetCore is */ function tokenOwnerOf( bytes32 tokenId - ) public view virtual returns (address) { + ) public view virtual override returns (address) { address tokenOwner = _tokenOwners[tokenId]; if (tokenOwner == address(0)) { @@ -94,7 +108,7 @@ abstract contract LSP8IdentifiableDigitalAssetCore is */ function tokenIdsOf( address tokenOwner - ) public view virtual returns (bytes32[] memory) { + ) public view virtual override returns (bytes32[] memory) { return _ownedTokens[tokenOwner].values(); } @@ -107,7 +121,7 @@ abstract contract LSP8IdentifiableDigitalAssetCore is address operator, bytes32 tokenId, bytes memory operatorNotificationData - ) public virtual { + ) public virtual override { address tokenOwner = tokenOwnerOf(tokenId); if (tokenOwner != msg.sender) { @@ -147,7 +161,7 @@ abstract contract LSP8IdentifiableDigitalAssetCore is address operator, bytes32 tokenId, bytes memory operatorNotificationData - ) public virtual { + ) public virtual override { address tokenOwner = tokenOwnerOf(tokenId); if (tokenOwner != msg.sender) { @@ -183,7 +197,7 @@ abstract contract LSP8IdentifiableDigitalAssetCore is function isOperatorFor( address operator, bytes32 tokenId - ) public view virtual returns (bool) { + ) public view virtual override returns (bool) { _existsOrError(tokenId); return _isOperatorOrOwner(operator, tokenId); @@ -194,7 +208,7 @@ abstract contract LSP8IdentifiableDigitalAssetCore is */ function getOperatorsOf( bytes32 tokenId - ) public view virtual returns (address[] memory) { + ) public view virtual override returns (address[] memory) { _existsOrError(tokenId); return _operators[tokenId].values(); @@ -208,9 +222,8 @@ abstract contract LSP8IdentifiableDigitalAssetCore is address caller, bytes32 tokenId ) internal view virtual returns (bool) { - address tokenOwner = tokenOwnerOf(tokenId); - - return (caller == tokenOwner || _operators[tokenId].contains(caller)); + return (caller == tokenOwnerOf(tokenId) || + _operators[tokenId].contains(caller)); } // --- Transfer functionality @@ -222,16 +235,14 @@ abstract contract LSP8IdentifiableDigitalAssetCore is address from, address to, bytes32 tokenId, - bool allowNonLSP1Recipient, + bool force, bytes memory data - ) public virtual { - address operator = msg.sender; - - if (!_isOperatorOrOwner(operator, tokenId)) { - revert LSP8NotTokenOperator(tokenId, operator); + ) public virtual override { + if (!_isOperatorOrOwner(msg.sender, tokenId)) { + revert LSP8NotTokenOperator(tokenId, msg.sender); } - _transfer(from, to, tokenId, allowNonLSP1Recipient, data); + _transfer(from, to, tokenId, force, data); } /** @@ -241,27 +252,21 @@ abstract contract LSP8IdentifiableDigitalAssetCore is address[] memory from, address[] memory to, bytes32[] memory tokenId, - bool[] memory allowNonLSP1Recipient, + bool[] memory force, bytes[] memory data - ) public virtual { + ) public virtual override { uint256 fromLength = from.length; if ( fromLength != to.length || fromLength != tokenId.length || - fromLength != allowNonLSP1Recipient.length || + fromLength != force.length || fromLength != data.length ) { revert LSP8InvalidTransferBatch(); } - for (uint256 i = 0; i < fromLength; ) { - transfer( - from[i], - to[i], - tokenId[i], - allowNonLSP1Recipient[i], - data[i] - ); + for (uint256 i; i < fromLength; ) { + transfer(from[i], to[i], tokenId[i], force[i], data[i]); unchecked { ++i; @@ -280,6 +285,7 @@ abstract contract LSP8IdentifiableDigitalAssetCore is ) internal virtual { bool isRemoved = _operators[tokenId].remove(operator); if (!isRemoved) revert LSP8NonExistingOperator(operator, tokenId); + emit RevokedOperator( operator, tokenOwner, @@ -309,9 +315,10 @@ abstract contract LSP8IdentifiableDigitalAssetCore is ]; uint256 operatorListLength = operatorsForTokenId.length(); - for (uint256 i = 0; i < operatorListLength; ) { + address operator; + for (uint256 i; i < operatorListLength; ) { // we are emptying the list, always remove from index 0 - address operator = operatorsForTokenId.at(0); + operator = operatorsForTokenId.at(0); _revokeOperator(operator, tokenOwner, tokenId, ""); unchecked { @@ -341,52 +348,50 @@ abstract contract LSP8IdentifiableDigitalAssetCore is /** * @dev Create `tokenId` by minting it and transfers it to `to`. * - * @custom:requirements - * - `tokenId` must not exist and not have been already minted. - * - `to` cannot be the zero address. + * @custom:info Any logic in the: + * - {_beforeTokenTransfer} function will run before updating the balances and ownership of `tokenId`s. + * - {_afterTokenTransfer} function will run after updating the balances and ownership of `tokenId`s, **but before notifying the recipient via LSP1**. * * @param to The address that will receive the minted `tokenId`. * @param tokenId The token ID to create (= mint). - * @param allowNonLSP1Recipient When set to `true`, `to` may be any address. When set to `false`, `to` must be a contract that supports the LSP1 standard. + * @param force When set to `true`, `to` may be any address. When set to `false`, `to` must be a contract that supports the LSP1 standard. * @param data Any additional data the caller wants included in the emitted event, and sent in the hook of the `to` address. * + * @custom:requirements + * - `tokenId` must not exist and not have been already minted. + * - `to` cannot be the zero address. + * @custom:events {Transfer} event with `address(0)` as `from` address. */ function _mint( address to, bytes32 tokenId, - bool allowNonLSP1Recipient, + bool force, bytes memory data ) internal virtual { if (to == address(0)) { revert LSP8CannotSendToAddressZero(); } + _beforeTokenTransfer(address(0), to, tokenId, data); + + // Check that `tokenId` was not minted inside the `_beforeTokenTransfer` hook if (_exists(tokenId)) { revert LSP8TokenIdAlreadyMinted(tokenId); } - address operator = msg.sender; - - _beforeTokenTransfer(address(0), to, tokenId); - // token being minted - _existingTokens += 1; + ++_existingTokens; _ownedTokens[to].add(tokenId); _tokenOwners[tokenId] = to; - emit Transfer( - operator, - address(0), - to, - tokenId, - allowNonLSP1Recipient, - data - ); + emit Transfer(msg.sender, address(0), to, tokenId, force, data); + + _afterTokenTransfer(address(0), to, tokenId, data); bytes memory lsp1Data = abi.encode(address(0), to, tokenId, data); - _notifyTokenReceiver(to, allowNonLSP1Recipient, lsp1Data); + _notifyTokenReceiver(to, force, lsp1Data); } /** @@ -397,7 +402,9 @@ abstract contract LSP8IdentifiableDigitalAssetCore is * function, if it is a contract that supports the LSP1 interface. Its {universalReceiver} function will receive * all the parameters in the calldata packed encoded. * - * Any logic in the {_beforeTokenTransfer} function will run before burning `tokenId` and updating the balances. + * @custom:info Any logic in the: + * - {_beforeTokenTransfer} function will run before updating the balances and ownership of `tokenId`s. + * - {_afterTokenTransfer} function will run after updating the balances and ownership of `tokenId`s, **but before notifying the sender via LSP1**. * * @param tokenId The token to burn. * @param data Any additional data the caller wants included in the emitted event, and sent in the LSP1 hook on the token owner's address. @@ -411,19 +418,24 @@ abstract contract LSP8IdentifiableDigitalAssetCore is */ function _burn(bytes32 tokenId, bytes memory data) internal virtual { address tokenOwner = tokenOwnerOf(tokenId); - address operator = msg.sender; - _beforeTokenTransfer(tokenOwner, address(0), tokenId); + _beforeTokenTransfer(tokenOwner, address(0), tokenId, data); + + // Re-fetch and update `tokenOwner` in case `tokenId` + // was transferred inside the `_beforeTokenTransfer` hook + tokenOwner = tokenOwnerOf(tokenId); // token being burned - _existingTokens -= 1; + --_existingTokens; _clearOperators(tokenOwner, tokenId); _ownedTokens[tokenOwner].remove(tokenId); delete _tokenOwners[tokenId]; - emit Transfer(operator, tokenOwner, address(0), tokenId, false, data); + emit Transfer(msg.sender, tokenOwner, address(0), tokenId, false, data); + + _afterTokenTransfer(tokenOwner, address(0), tokenId, data); bytes memory lsp1Data = abi.encode( tokenOwner, @@ -441,12 +453,14 @@ abstract contract LSP8IdentifiableDigitalAssetCore is * function, if they are contracts that support the LSP1 interface. Their `universalReceiver` function will receive * all the parameters in the calldata packed encoded. * - * Any logic in the {_beforeTokenTransfer} function will run before changing the owner of `tokenId`. + * @custom:info Any logic in the: + * - {_beforeTokenTransfer} function will run before updating the balances and ownership of `tokenId`s. + * - {_afterTokenTransfer} function will run after updating the balances and ownership of `tokenId`s, **but before notifying the sender/recipient via LSP1**. * * @param from The sender address. * @param to The recipient address. * @param tokenId The token to transfer. - * @param allowNonLSP1Recipient When set to `true`, `to` may be any address. When set to `false`, `to` must be a contract that supports the LSP1 standard. + * @param force When set to `true`, `to` may be any address. When set to `false`, `to` must be a contract that supports the LSP1 standard. * @param data Additional data the caller wants included in the emitted event, and sent in the hooks to `from` and `to` addresses. * * @custom:requirements @@ -461,7 +475,7 @@ abstract contract LSP8IdentifiableDigitalAssetCore is address from, address to, bytes32 tokenId, - bool allowNonLSP1Recipient, + bool force, bytes memory data ) internal virtual { if (from == to) { @@ -477,9 +491,11 @@ abstract contract LSP8IdentifiableDigitalAssetCore is revert LSP8CannotSendToAddressZero(); } - address operator = msg.sender; + _beforeTokenTransfer(from, to, tokenId, data); - _beforeTokenTransfer(from, to, tokenId); + // Re-fetch and update `tokenOwner` in case `tokenId` + // was transferred inside the `_beforeTokenTransfer` hook + tokenOwner = tokenOwnerOf(tokenId); _clearOperators(from, tokenId); @@ -487,26 +503,46 @@ abstract contract LSP8IdentifiableDigitalAssetCore is _ownedTokens[to].add(tokenId); _tokenOwners[tokenId] = to; - emit Transfer(operator, from, to, tokenId, allowNonLSP1Recipient, data); + emit Transfer(msg.sender, from, to, tokenId, force, data); + + _afterTokenTransfer(from, to, tokenId, data); bytes memory lsp1Data = abi.encode(from, to, tokenId, data); _notifyTokenSender(from, lsp1Data); - _notifyTokenReceiver(to, allowNonLSP1Recipient, lsp1Data); + _notifyTokenReceiver(to, force, lsp1Data); } /** * @dev Hook that is called before any token transfer, including minting and burning. - * * Allows to run custom logic before updating balances and notifiying sender/recipient by overriding this function. + * Allows to run custom logic before updating balances and notifiying sender/recipient by overriding this function. * * @param from The sender address * @param to The recipient address * @param tokenId The tokenId to transfer + * @param data The data sent alongside the transfer */ function _beforeTokenTransfer( address from, address to, - bytes32 tokenId + bytes32 tokenId, + bytes memory data // solhint-disable-next-line no-empty-blocks + ) internal virtual {} + + /** + * @dev Hook that is called after any token transfer, including minting and burning. + * Allows to run custom logic after updating balances, but **before notifiying sender/recipient via LSP1** by overriding this function. + * + * @param from The sender address + * @param to The recipient address + * @param tokenId The tokenId to transfer + * @param data The data sent alongside the transfer + */ + function _afterTokenTransfer( + address from, + address to, + bytes32 tokenId, + bytes memory data // solhint-disable-next-line no-empty-blocks ) internal virtual {} /** @@ -527,13 +563,16 @@ abstract contract LSP8IdentifiableDigitalAssetCore is _INTERFACEID_LSP1 ) ) { - operator.call( - abi.encodeWithSelector( - ILSP1UniversalReceiver.universalReceiver.selector, + try + ILSP1UniversalReceiver(operator).universalReceiver( _TYPEID_LSP8_TOKENOPERATOR, lsp1Data ) - ); + { + return; + } catch { + return; + } } } @@ -560,13 +599,13 @@ abstract contract LSP8IdentifiableDigitalAssetCore is /** * @dev An attempt is made to notify the token receiver about the `tokenId` changing owners - * using LSP1 interface. When allowNonLSP1Recipient is FALSE the token receiver MUST support LSP1. + * using LSP1 interface. When force is FALSE the token receiver MUST support LSP1. * * The receiver may revert when the token being sent is not wanted. */ function _notifyTokenReceiver( address to, - bool allowNonLSP1Recipient, + bool force, bytes memory lsp1Data ) internal virtual { if ( @@ -579,8 +618,8 @@ abstract contract LSP8IdentifiableDigitalAssetCore is _TYPEID_LSP8_TOKENSRECIPIENT, lsp1Data ); - } else if (!allowNonLSP1Recipient) { - if (to.code.length > 0) { + } else if (!force) { + if (to.code.length != 0) { revert LSP8NotifyTokenReceiverContractMissingLSP1Interface(to); } else { revert LSP8NotifyTokenReceiverIsEOA(to); diff --git a/contracts/LSP8IdentifiableDigitalAsset/LSP8IdentifiableDigitalAssetInitAbstract.sol b/contracts/LSP8IdentifiableDigitalAsset/LSP8IdentifiableDigitalAssetInitAbstract.sol index 0187cc919..bfcbffa8b 100644 --- a/contracts/LSP8IdentifiableDigitalAsset/LSP8IdentifiableDigitalAssetInitAbstract.sol +++ b/contracts/LSP8IdentifiableDigitalAsset/LSP8IdentifiableDigitalAssetInitAbstract.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: CC0-1.0 +// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.4; // interfaces @@ -19,9 +19,17 @@ import {LSP17Extendable} from "../LSP17ContractExtension/LSP17Extendable.sol"; import {LSP2Utils} from "../LSP2ERC725YJSONSchema/LSP2Utils.sol"; // constants -import {_INTERFACEID_LSP8} from "./LSP8Constants.sol"; +import {_INTERFACEID_LSP8, _LSP8_TOKENID_TYPE_KEY} from "./LSP8Constants.sol"; -import "../LSP17ContractExtension/LSP17Constants.sol"; +// errors +import { + LSP8TokenContractCannotHoldValue, + LSP8TokenIdTypeNotEditable +} from "./LSP8Errors.sol"; + +import { + _LSP17_EXTENSION_PREFIX +} from "../LSP17ContractExtension/LSP17Constants.sol"; // errors @@ -47,21 +55,39 @@ abstract contract LSP8IdentifiableDigitalAssetInitAbstract is LSP8IdentifiableDigitalAssetCore, LSP17Extendable { + /** + * @dev Initialize a `LSP8IdentifiableDigitalAsset` contract and set the tokenId type inside the ERC725Y storage of the contract. + * This will also set the token `name_` and `symbol_` under the ERC725Y data keys `LSP4TokenName` and `LSP4TokenSymbol`. + * + * @param name_ The name of the token + * @param symbol_ The symbol of the token + * @param newOwner_ The owner of the the token-Metadata + * @param tokenIdType_ The type of tokenIds (= NFTs) that this contract will create. + * Available options are: NUMBER = `0`; STRING = `1`; UNIQUE_ID = `2`; HASH = `3`; ADDRESS = `4`. + * + * @custom:warning Make sure the tokenId type provided on deployment is correct, as it can only be set once + * and cannot be changed in the ERC725Y storage after the contract has been initialized. + */ function _initialize( string memory name_, string memory symbol_, - address newOwner_ - ) internal virtual override onlyInitializing { + address newOwner_, + uint256 tokenIdType_ + ) internal virtual onlyInitializing { LSP4DigitalAssetMetadataInitAbstract._initialize( name_, symbol_, newOwner_ ); + + LSP4DigitalAssetMetadataInitAbstract._setData( + _LSP8_TOKENID_TYPE_KEY, + abi.encode(tokenIdType_) + ); } // fallback function - // solhint-disable no-complex-fallback /** * @notice The `fallback` function was called with the following amount of native tokens: `msg.value`; and the following calldata: `callData`. * @@ -81,6 +107,7 @@ abstract contract LSP8IdentifiableDigitalAssetInitAbstract is * * 2. If the data sent to this function is of length less than 4 bytes (not a function selector), revert. */ + // solhint-disable-next-line no-complex-fallback fallback( bytes calldata callData ) external payable virtual returns (bytes memory) { @@ -90,6 +117,19 @@ abstract contract LSP8IdentifiableDigitalAssetInitAbstract is return _fallbackLSP17Extendable(callData); } + /** + * @dev Reverts whenever someone tries to send native tokens to a LSP8 contract. + * @notice LSP8 contract cannot receive native tokens. + */ + receive() external payable virtual { + // revert on empty calls with no value + if (msg.value == 0) { + revert InvalidFunctionSelector(hex"00000000"); + } + + revert LSP8TokenContractCannotHoldValue(); + } + /** * @dev Forwards the call with the received value to an extension mapped to a function selector. * @@ -102,10 +142,8 @@ abstract contract LSP8IdentifiableDigitalAssetInitAbstract is * CALL opcode, passing the {msg.data} appended with the 20 bytes of the {msg.sender} and * 32 bytes of the {msg.value} * - * Because the function uses assembly {return()/revert()} to terminate the call, it cannot be - * called before other codes in fallback(). - * - * Otherwise, the codes after _fallbackLSP17Extendable() may never be reached. + * @custom:info The LSP8 Token contract should not hold any native tokens. Any native tokens received by the contract + * will be forwarded to the extension address mapped to the selector from `msg.sig`. */ function _fallbackLSP17Extendable( bytes calldata callData @@ -176,4 +214,19 @@ abstract contract LSP8IdentifiableDigitalAssetInitAbstract is super.supportsInterface(interfaceId) || LSP17Extendable._supportsInterfaceInERC165Extension(interfaceId); } + + /** + * @inheritdoc LSP4DigitalAssetMetadataInitAbstract + * @dev The ERC725Y data key `_LSP8_TOKENID_TYPE_KEY` cannot be changed + * once the identifiable digital asset contract has been deployed. + */ + function _setData( + bytes32 dataKey, + bytes memory dataValue + ) internal virtual override { + if (dataKey == _LSP8_TOKENID_TYPE_KEY) { + revert LSP8TokenIdTypeNotEditable(); + } + LSP4DigitalAssetMetadataInitAbstract._setData(dataKey, dataValue); + } } diff --git a/contracts/LSP8IdentifiableDigitalAsset/extensions/ILSP8CompatibleERC721.sol b/contracts/LSP8IdentifiableDigitalAsset/extensions/ILSP8CompatibleERC721.sol deleted file mode 100644 index e5b32faf4..000000000 --- a/contracts/LSP8IdentifiableDigitalAsset/extensions/ILSP8CompatibleERC721.sol +++ /dev/null @@ -1,175 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; - -// interfaces -import { - ILSP8IdentifiableDigitalAsset -} from "../ILSP8IdentifiableDigitalAsset.sol"; - -// --- ERC165 interface ids -bytes4 constant _INTERFACEID_ERC721 = 0x80ac58cd; -bytes4 constant _INTERFACEID_ERC721METADATA = 0x5b5e139f; - -/** - * @dev LSP8 extension, for compatibility for clients / tools that expect ERC721. - */ -interface ILSP8CompatibleERC721 is ILSP8IdentifiableDigitalAsset { - /** - * @notice ERC721 `Transfer` compatible event emitted. Successfully transferred tokenId `tokenId` from `from` to `to`. - * - * @dev ERC721 `Transfer` event emitted when `tokenId` token is transferred from `from` to `to`. - * To provide compatibility with indexing ERC721 events. - * - * @param from The sending address. - * @param to The receiving address. - * @param tokenId The tokenId to transfer. - */ - event Transfer( - address indexed from, - address indexed to, - uint256 indexed tokenId - ); - - /** - * @notice ERC721 `Approval` compatible event emitted. Successfully approved operator `operator` to operate on tokenId `tokenId` on behalf of token owner `owner`. - * - * @dev ERC721 `Approval` event emitted when `owner` enables `operator` for `tokenId`. - * To provide compatibility with indexing ERC721 events. - * - * @param owner The address of the owner of the `tokenId`. - * @param operator The address set as operator. - * @param tokenId The approved tokenId. - */ - event Approval( - address indexed owner, - address indexed operator, - uint256 indexed tokenId - ); - - /** - * @notice ERC721 `ApprovalForAll` compatible event emitted. Successfully set "approved for all" status to `approved` for operator `operator` for token owner `owner`. - * - * @dev ERC721 `ApprovalForAll` event emitted when an `operator` is enabled or disabled for an owner - * to transfer any of its tokenIds. The operator can manage all NFTs of the owner. - * - * @param owner The address of the owner of tokenIds. - * @param operator The address set as operator. - * @param approved If `operator` is approved for all NFTs or not. - */ - event ApprovalForAll( - address indexed owner, - address indexed operator, - bool approved - ); - - /** - * @notice Calling `transferFrom` function on `ILSP8CompatibleERC721` contract. Transferring tokenId `tokenId` from address `from` to address `to`. - * - * @dev Transfer functions from the ERC721 standard interface. - * - * @param from The sending address. - * @param to The receiving address. - * @param tokenId The tokenId to transfer. - */ - function transferFrom(address from, address to, uint256 tokenId) external; - - /** - * @notice Calling `safeTransferFrom` function on `ILSP8CompatibleERC721` contract. Transferring tokenId `tokenId` from address `from` to address `to`. - * - * @dev Safe Transfer function without optional data from the ERC721 standard interface. - * - * @param from The sending address. - * @param to The receiving address. - * @param tokenId The tokenId to transfer. - */ - function safeTransferFrom( - address from, - address to, - uint256 tokenId - ) external; - - /** - * @notice Calling `safeTransferFrom` function with `data` on `ILSP8CompatibleERC721` contract. Transferring tokenId `tokenId` from address `from` to address `to`. - * - * @dev Safe Transfer function with optional data from the ERC721 standard interface. - * - * @param from The sending address. - * @param to The receiving address. - * @param tokenId The tokenId to transfer. - * @param data The data to be sent with the transfer. - */ - function safeTransferFrom( - address from, - address to, - uint256 tokenId, - bytes memory data - ) external; - - /** - * @notice Retrieving the address that own tokenId `tokenId`. - * - * @dev Compatible with ERC721 ownerOf. - * - * @param tokenId The tokenId to query. - * @return The owner of the tokenId. - */ - function ownerOf(uint256 tokenId) external view returns (address); - - /** - * @notice Calling `approve` function on `ILSP8CompatibleERC721` contract. Approving operator at address `operator` to transfer tokenId `tokenId` on behalf of its owner. - * - * @dev Approval function compatible with ERC721 `approve(address,uint256)`. - * - * @param operator The address to approve for `tokenId`. - * @param tokenId The tokenId to approve. - */ - function approve(address operator, uint256 tokenId) external; - - /** - * @notice Setting the "approval for all" status of operator `_operator` to `_approved` to allow it to transfer any tokenIds on behalf of `msg.sender`. - * - * @dev Enable or disable approval for a third party ("operator") to manage all of `msg.sender`'s assets. The contract MUST allow multiple operators per owner. - * - * @param _operator Address to add to the set of authorized operators. - * @param _approved True if the operator is approved, false to revoke approval. - * - * @custom:events {ApprovalForAll} event - */ - function setApprovalForAll(address _operator, bool _approved) external; - - /** - * @notice Retrieving the address other than the token owner that is approved to transfer tokenId `tokenId` on behalf of its owner. - * - * @dev Compatible with ERC721 getApproved. - * - * @param tokenId The tokenId to query. - * @return The address of the operator for `tokenId`. - */ - function getApproved(uint256 tokenId) external view returns (address); - - /* - * @notice Checking if address `operator` is approved to transfer any tokenId owned by address `owner`. - * - * @dev Compatible with ERC721 isApprovedForAll. - * - * @param owner The tokenOwner address to query. - * @param operator The operator address to query. - * - * @return Returns if the `operator` is allowed to manage all of the assets of `owner` - */ - function isApprovedForAll( - address owner, - address operator - ) external view returns (bool); - - /* - * @notice Retrieving the token URI of tokenId `tokenId`. - * - * @dev Compatible with ERC721Metadata tokenURI. - * - * @param tokenId The tokenId to query. - * - * @return The token URI. - */ - function tokenURI(uint256 tokenId) external returns (string memory); -} diff --git a/contracts/LSP8IdentifiableDigitalAsset/extensions/LSP8Burnable.sol b/contracts/LSP8IdentifiableDigitalAsset/extensions/LSP8Burnable.sol index de28b0b02..88bdd216d 100644 --- a/contracts/LSP8IdentifiableDigitalAsset/extensions/LSP8Burnable.sol +++ b/contracts/LSP8IdentifiableDigitalAsset/extensions/LSP8Burnable.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: MIT +// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.4; import { diff --git a/contracts/LSP8IdentifiableDigitalAsset/extensions/LSP8BurnableInitAbstract.sol b/contracts/LSP8IdentifiableDigitalAsset/extensions/LSP8BurnableInitAbstract.sol index 2c5c92a5b..064aefd3b 100644 --- a/contracts/LSP8IdentifiableDigitalAsset/extensions/LSP8BurnableInitAbstract.sol +++ b/contracts/LSP8IdentifiableDigitalAsset/extensions/LSP8BurnableInitAbstract.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: MIT +// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.4; import { diff --git a/contracts/LSP8IdentifiableDigitalAsset/extensions/LSP8CappedSupply.sol b/contracts/LSP8IdentifiableDigitalAsset/extensions/LSP8CappedSupply.sol index 8cf3336ed..102f4b819 100644 --- a/contracts/LSP8IdentifiableDigitalAsset/extensions/LSP8CappedSupply.sol +++ b/contracts/LSP8IdentifiableDigitalAsset/extensions/LSP8CappedSupply.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: MIT +// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.4; @@ -26,7 +26,7 @@ abstract contract LSP8CappedSupply is LSP8IdentifiableDigitalAsset { error LSP8CappedSupplyCannotMintOverCap(); // --- Storage - uint256 private immutable _tokenSupplyCap; + uint256 private immutable _TOKEN_SUPPLY_CAP; /** * @notice Deploying a `LSP8CappedSupply` token contract with max token supply cap set to `tokenSupplyCap_`. @@ -43,13 +43,13 @@ abstract contract LSP8CappedSupply is LSP8IdentifiableDigitalAsset { revert LSP8CappedSupplyRequired(); } - _tokenSupplyCap = tokenSupplyCap_; + _TOKEN_SUPPLY_CAP = tokenSupplyCap_; } // --- Token queries /** - * @notice The maximum supply amount of tokens allowed to exist is `_tokenSupplyCap`. + * @notice The maximum supply amount of tokens allowed to exist is `_TOKEN_SUPPLY_CAP`. * * @dev Get the maximum number of tokens that can exist to circulate. Once {totalSupply} reaches * reaches {totalSuuplyCap}, it is not possible to mint more tokens. @@ -57,7 +57,7 @@ abstract contract LSP8CappedSupply is LSP8IdentifiableDigitalAsset { * @return The maximum number of tokens that can exist in the contract. */ function tokenSupplyCap() public view virtual returns (uint256) { - return _tokenSupplyCap; + return _TOKEN_SUPPLY_CAP; } // --- Transfer functionality @@ -74,13 +74,13 @@ abstract contract LSP8CappedSupply is LSP8IdentifiableDigitalAsset { function _mint( address to, bytes32 tokenId, - bool allowNonLSP1Recipient, + bool force, bytes memory data ) internal virtual override { if (totalSupply() + 1 > tokenSupplyCap()) { revert LSP8CappedSupplyCannotMintOverCap(); } - super._mint(to, tokenId, allowNonLSP1Recipient, data); + super._mint(to, tokenId, force, data); } } diff --git a/contracts/LSP8IdentifiableDigitalAsset/extensions/LSP8CappedSupplyInitAbstract.sol b/contracts/LSP8IdentifiableDigitalAsset/extensions/LSP8CappedSupplyInitAbstract.sol index 026ed2227..6fab6a033 100644 --- a/contracts/LSP8IdentifiableDigitalAsset/extensions/LSP8CappedSupplyInitAbstract.sol +++ b/contracts/LSP8IdentifiableDigitalAsset/extensions/LSP8CappedSupplyInitAbstract.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: MIT +// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.4; @@ -77,13 +77,13 @@ abstract contract LSP8CappedSupplyInitAbstract is function _mint( address to, bytes32 tokenId, - bool allowNonLSP1Recipient, + bool force, bytes memory data ) internal virtual override { if (totalSupply() + 1 > tokenSupplyCap()) { revert LSP8CappedSupplyCannotMintOverCap(); } - super._mint(to, tokenId, allowNonLSP1Recipient, data); + super._mint(to, tokenId, force, data); } } diff --git a/contracts/LSP8IdentifiableDigitalAsset/extensions/LSP8CompatibleERC721.sol b/contracts/LSP8IdentifiableDigitalAsset/extensions/LSP8CompatibleERC721.sol index 9e21d8487..0363756f3 100644 --- a/contracts/LSP8IdentifiableDigitalAsset/extensions/LSP8CompatibleERC721.sol +++ b/contracts/LSP8IdentifiableDigitalAsset/extensions/LSP8CompatibleERC721.sol @@ -1,15 +1,15 @@ -// SPDX-License-Identifier: MIT +// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.12; // interfaces import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; import { IERC721Receiver -} from "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol"; -import {ILSP8CompatibleERC721} from "./ILSP8CompatibleERC721.sol"; +} from "@openzeppelin/contracts/interfaces/IERC721Receiver.sol"; import { - ILSP8IdentifiableDigitalAsset -} from "../ILSP8IdentifiableDigitalAsset.sol"; + IERC721Metadata, + IERC721 +} from "@openzeppelin/contracts/interfaces/IERC721Metadata.sol"; // libraries import { @@ -19,35 +19,31 @@ import {BytesLib} from "solidity-bytes-utils/contracts/BytesLib.sol"; // modules import { - LSP4Compatibility -} from "../../LSP4DigitalAssetMetadata/LSP4Compatibility.sol"; -import { - LSP8IdentifiableDigitalAsset, - LSP4DigitalAssetMetadata, - ERC725YCore + LSP8IdentifiableDigitalAssetCore, + LSP8IdentifiableDigitalAsset } from "../LSP8IdentifiableDigitalAsset.sol"; -import { - LSP8IdentifiableDigitalAssetCore -} from "../LSP8IdentifiableDigitalAssetCore.sol"; // errors -import "../LSP8Errors.sol"; +import { + LSP8NotTokenOwner, + LSP8CannotUseAddressZeroAsOperator, + LSP8TokenOwnerCannotBeOperator, + LSP8OperatorAlreadyAuthorized, + LSP8NotTokenOperator +} from "../LSP8Errors.sol"; // constants import { - _LSP4_METADATA_KEY + _LSP4_METADATA_KEY, + _LSP4_TOKEN_NAME_KEY, + _LSP4_TOKEN_SYMBOL_KEY } from "../../LSP4DigitalAssetMetadata/LSP4Constants.sol"; -import { - _INTERFACEID_ERC721, - _INTERFACEID_ERC721METADATA -} from "././ILSP8CompatibleERC721.sol"; /** * @dev LSP8 extension, for compatibility for clients / tools that expect ERC721. */ abstract contract LSP8CompatibleERC721 is - ILSP8CompatibleERC721, - LSP4Compatibility, + IERC721Metadata, LSP8IdentifiableDigitalAsset { using EnumerableSet for EnumerableSet.AddressSet; @@ -68,8 +64,48 @@ abstract contract LSP8CompatibleERC721 is constructor( string memory name_, string memory symbol_, - address newOwner_ - ) LSP8IdentifiableDigitalAsset(name_, symbol_, newOwner_) {} + address newOwner_, + uint256 tokenIdType_ + ) LSP8IdentifiableDigitalAsset(name_, symbol_, newOwner_, tokenIdType_) {} + + /** + * @inheritdoc IERC721Metadata + * @dev Returns the name of the token. + * For compatibility with clients & tools that expect ERC721. + * + * @return The name of the token + */ + function name() public view virtual override returns (string memory) { + bytes memory data = _getData(_LSP4_TOKEN_NAME_KEY); + return string(data); + } + + /** + * @inheritdoc IERC721Metadata + * @dev Returns the symbol of the token, usually a shorter version of the name. + * For compatibility with clients & tools that expect ERC721. + * + * @return The symbol of the token + */ + function symbol() public view virtual override returns (string memory) { + bytes memory data = _getData(_LSP4_TOKEN_SYMBOL_KEY); + return string(data); + } + + /** + * @inheritdoc LSP8IdentifiableDigitalAssetCore + */ + function balanceOf( + address tokenOwner + ) + public + view + virtual + override(IERC721, LSP8IdentifiableDigitalAssetCore) + returns (uint256) + { + return super.balanceOf(tokenOwner); + } /** * @inheritdoc LSP8IdentifiableDigitalAsset @@ -80,17 +116,22 @@ abstract contract LSP8CompatibleERC721 is public view virtual - override(IERC165, ERC725YCore, LSP8IdentifiableDigitalAsset) + override(IERC165, LSP8IdentifiableDigitalAsset) returns (bool) { return - interfaceId == _INTERFACEID_ERC721 || - interfaceId == _INTERFACEID_ERC721METADATA || + interfaceId == type(IERC721).interfaceId || + interfaceId == type(IERC721Metadata).interfaceId || super.supportsInterface(interfaceId); } - /* - * @inheritdoc ILSP8CompatibleERC721 + /** + * @inheritdoc IERC721Metadata + * @notice Retrieving the token URI of tokenId `tokenId`. + * + * @dev Compatible with ERC721Metadata tokenURI. Retrieve the tokenURI for a specific `tokenId`. + * + * @return The token URI. */ function tokenURI( uint256 /* tokenId */ @@ -109,18 +150,32 @@ abstract contract LSP8CompatibleERC721 is } /** - * @inheritdoc ILSP8CompatibleERC721 + * @inheritdoc IERC721 + * @notice Retrieving the address that own tokenId `tokenId`. + * + * @dev Compatible with ERC721 ownerOf. + * + * @param tokenId The tokenId to query. + * @return The owner of the tokenId. */ - function ownerOf(uint256 tokenId) public view virtual returns (address) { + function ownerOf( + uint256 tokenId + ) public view virtual override returns (address) { return tokenOwnerOf(bytes32(tokenId)); } /** - * @inheritdoc ILSP8CompatibleERC721 + * @inheritdoc IERC721 + * @notice Retrieving the address other than the token owner that is approved to transfer tokenId `tokenId` on behalf of its owner. + * + * @dev Compatible with ERC721 getApproved. + * + * @param tokenId The tokenId to query. + * @return The address of the operator for `tokenId`. */ function getApproved( uint256 tokenId - ) public view virtual returns (address) { + ) public view virtual override returns (address) { bytes32 tokenIdAsBytes32 = bytes32(tokenId); _existsOrError(tokenIdAsBytes32); @@ -140,74 +195,125 @@ abstract contract LSP8CompatibleERC721 is } } - /* - * @inheritdoc ILSP8CompatibleERC721 + /** + * @inheritdoc IERC721 + * @notice Checking if address `operator` is approved to transfer any tokenId owned by address `owner`. + * + * @dev Compatible with ERC721 isApprovedForAll. + * + * @param tokenOwner The tokenOwner address to query. + * @param operator The operator address to query. + * + * @return Returns if the `operator` is allowed to manage all of the assets of `owner` */ function isApprovedForAll( address tokenOwner, address operator - ) public view virtual returns (bool) { + ) public view virtual override returns (bool) { return _operatorApprovals[tokenOwner][operator]; } /** - * @inheritdoc ILSP8CompatibleERC721 + * @inheritdoc IERC721 + * @notice Calling `approve` function to approve operator at address `operator` to transfer tokenId `tokenId` on behalf of its owner. + * + * @dev Approval function compatible with ERC721 `approve(address,uint256)`. + * + * @param operator The address to approve for `tokenId`. + * @param tokenId The tokenId to approve. */ - function approve(address operator, uint256 tokenId) public virtual { + function approve( + address operator, + uint256 tokenId + ) public virtual override { authorizeOperator(operator, bytes32(tokenId), ""); } /** - * @dev See {_setApprovalForAll} + * @inheritdoc IERC721 + * @notice Setting the "approval for all" status of operator `_operator` to `_approved` to allow it to transfer any tokenIds on behalf of `msg.sender`. + * + * @dev Enable or disable approval for a third party ("operator") to manage all of `msg.sender`'s assets. The contract MUST allow multiple operators per owner. + * See {_setApprovalForAll} + * + * @param operator Address to add to the set of authorized operators. + * @param approved True if the operator is approved, false to revoke approval. + * + * @custom:events {ApprovalForAll} event */ - function setApprovalForAll(address operator, bool approved) public virtual { + function setApprovalForAll( + address operator, + bool approved + ) public virtual override { _setApprovalForAll(msg.sender, operator, approved); } /** - * @inheritdoc ILSP8CompatibleERC721 + * @inheritdoc IERC721 + * @notice Calling `transferFrom` function to transfer tokenId `tokenId` from address `from` to address `to`. * - * @custom:info This function sets the `allowNonLSP1Recipient` parameter to `true` so that EOAs and any contract can receive the `tokenId`. + * @dev Transfer functions from the ERC721 standard interface. + * + * @param from The sending address. + * @param to The receiving address. + * @param tokenId The tokenId to transfer. + * + * @custom:info This function sets the `force` parameter to `true` so that EOAs and any contract can receive the `tokenId`. */ function transferFrom( address from, address to, uint256 tokenId - ) public virtual { + ) public virtual override { _transfer(from, to, bytes32(tokenId), true, ""); } /** - * @inheritdoc ILSP8CompatibleERC721 + * @inheritdoc IERC721 + * @notice Calling `safeTransferFrom` function to transfer tokenId `tokenId` from address `from` to address `to`. + * + * @dev Safe Transfer function without optional data from the ERC721 standard interface. + * + * @param from The sending address. + * @param to The receiving address. + * @param tokenId The tokenId to transfer. * - * @custom:info This function sets the `allowNonLSP1Recipient` parameter to `true` so that EOAs and any contract can receive the `tokenId`. + * @custom:info This function sets the `force` parameter to `true` so that EOAs and any contract can receive the `tokenId`. */ function safeTransferFrom( address from, address to, uint256 tokenId - ) public virtual { + ) public virtual override { _safeTransfer(from, to, tokenId, ""); } /** - * @inheritdoc ILSP8CompatibleERC721 + * @inheritdoc IERC721 + * @notice Calling `safeTransferFrom` function to transfer tokenId `tokenId` from address `from` to address `to`. * - * @custom:info This function sets the `allowNonLSP1Recipient` parameter to `true` so that EOAs and any contract can receive the `tokenId`. + * @dev Safe Transfer function with optional data from the ERC721 standard interface. + * + * @param from The sending address. + * @param to The receiving address. + * @param tokenId The tokenId to transfer. + * @param data The data to be sent with the transfer. + * + * @custom:info This function sets the `force` parameter to `true` so that EOAs and any contract can receive the `tokenId`. */ function safeTransferFrom( address from, address to, uint256 tokenId, bytes memory data - ) public virtual { + ) public virtual override { _safeTransfer(from, to, tokenId, data); } // --- Overrides /** - * @inheritdoc ILSP8IdentifiableDigitalAsset + * @inheritdoc LSP8IdentifiableDigitalAssetCore * * @custom:events * - LSP7 {AuthorizedOperator} event. @@ -217,14 +323,7 @@ abstract contract LSP8CompatibleERC721 is address operator, bytes32 tokenId, bytes memory operatorNotificationData - ) - public - virtual - override( - ILSP8IdentifiableDigitalAsset, - LSP8IdentifiableDigitalAssetCore - ) - { + ) public virtual override { address tokenOwner = tokenOwnerOf(tokenId); if ( @@ -272,7 +371,7 @@ abstract contract LSP8CompatibleERC721 is address from, address to, bytes32 tokenId, - bool allowNonLSP1Recipient, + bool force, bytes memory data ) internal virtual override { if ( @@ -283,7 +382,7 @@ abstract contract LSP8CompatibleERC721 is } emit Transfer(from, to, uint256(tokenId)); - super._transfer(from, to, tokenId, allowNonLSP1Recipient, data); + super._transfer(from, to, tokenId, force, data); } /** @@ -314,11 +413,11 @@ abstract contract LSP8CompatibleERC721 is function _mint( address to, bytes32 tokenId, - bool allowNonLSP1Recipient, + bool force, bytes memory data ) internal virtual override { emit Transfer(address(0), to, uint256(tokenId)); - super._mint(to, tokenId, allowNonLSP1Recipient, data); + super._mint(to, tokenId, force, data); } /** @@ -339,7 +438,7 @@ abstract contract LSP8CompatibleERC721 is } /** - * @dev Approve `operator` to operate on all tokens of `tokensOwner` + * @dev Approve `operator` to operate on all tokens of `tokensOwner`. * * @custom:events {ApprovalForAll} event. */ @@ -399,14 +498,4 @@ abstract contract LSP8CompatibleERC721 is } } } - - /** - * @inheritdoc LSP4DigitalAssetMetadata - */ - function _setData( - bytes32 key, - bytes memory value - ) internal virtual override(LSP4DigitalAssetMetadata, ERC725YCore) { - super._setData(key, value); - } } diff --git a/contracts/LSP8IdentifiableDigitalAsset/extensions/LSP8CompatibleERC721InitAbstract.sol b/contracts/LSP8IdentifiableDigitalAsset/extensions/LSP8CompatibleERC721InitAbstract.sol index 31550e9e4..2fd3f5210 100644 --- a/contracts/LSP8IdentifiableDigitalAsset/extensions/LSP8CompatibleERC721InitAbstract.sol +++ b/contracts/LSP8IdentifiableDigitalAsset/extensions/LSP8CompatibleERC721InitAbstract.sol @@ -1,15 +1,15 @@ -// SPDX-License-Identifier: MIT +// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.12; // interfaces -import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; +import {IERC165} from "@openzeppelin/contracts/interfaces/IERC165.sol"; import { IERC721Receiver -} from "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol"; -import {ILSP8CompatibleERC721} from "./ILSP8CompatibleERC721.sol"; +} from "@openzeppelin/contracts/interfaces/IERC721Receiver.sol"; import { - ILSP8IdentifiableDigitalAsset -} from "../ILSP8IdentifiableDigitalAsset.sol"; + IERC721Metadata, + IERC721 +} from "@openzeppelin/contracts/interfaces/IERC721Metadata.sol"; // libraries import { @@ -19,36 +19,32 @@ import {BytesLib} from "solidity-bytes-utils/contracts/BytesLib.sol"; // modules import { - LSP4Compatibility -} from "../../LSP4DigitalAssetMetadata/LSP4Compatibility.sol"; -import { - LSP8IdentifiableDigitalAssetInitAbstract, - LSP4DigitalAssetMetadataInitAbstract, - ERC725YCore + LSP8IdentifiableDigitalAssetCore, + LSP8IdentifiableDigitalAssetInitAbstract } from "../LSP8IdentifiableDigitalAssetInitAbstract.sol"; -import { - LSP8IdentifiableDigitalAssetCore -} from "../LSP8IdentifiableDigitalAssetCore.sol"; // errors -import "../LSP8Errors.sol"; +import { + LSP8NotTokenOwner, + LSP8CannotUseAddressZeroAsOperator, + LSP8TokenOwnerCannotBeOperator, + LSP8OperatorAlreadyAuthorized, + LSP8NotTokenOperator +} from "../LSP8Errors.sol"; // constants import { - _LSP4_METADATA_KEY + _LSP4_METADATA_KEY, + _LSP4_TOKEN_NAME_KEY, + _LSP4_TOKEN_SYMBOL_KEY } from "../../LSP4DigitalAssetMetadata/LSP4Constants.sol"; -import { - _INTERFACEID_ERC721, - _INTERFACEID_ERC721METADATA -} from "./ILSP8CompatibleERC721.sol"; /** * @dev LSP8 extension, for compatibility for clients / tools that expect ERC721. */ abstract contract LSP8CompatibleERC721InitAbstract is - ILSP8CompatibleERC721, - LSP8IdentifiableDigitalAssetInitAbstract, - LSP4Compatibility + IERC721Metadata, + LSP8IdentifiableDigitalAssetInitAbstract { using EnumerableSet for EnumerableSet.AddressSet; @@ -69,15 +65,56 @@ abstract contract LSP8CompatibleERC721InitAbstract is function _initialize( string memory name_, string memory symbol_, - address newOwner_ + address newOwner_, + uint256 tokenIdType_ ) internal virtual override onlyInitializing { LSP8IdentifiableDigitalAssetInitAbstract._initialize( name_, symbol_, - newOwner_ + newOwner_, + tokenIdType_ ); } + /** + * @inheritdoc IERC721Metadata + * @dev Returns the name of the token. + * For compatibility with clients & tools that expect ERC721. + * + * @return The name of the token + */ + function name() public view virtual override returns (string memory) { + bytes memory data = _getData(_LSP4_TOKEN_NAME_KEY); + return string(data); + } + + /** + * @inheritdoc IERC721Metadata + * @dev Returns the symbol of the token, usually a shorter version of the name. + * For compatibility with clients & tools that expect ERC721. + * + * @return The symbol of the token + */ + function symbol() public view virtual override returns (string memory) { + bytes memory data = _getData(_LSP4_TOKEN_SYMBOL_KEY); + return string(data); + } + + /** + * @inheritdoc LSP8IdentifiableDigitalAssetCore + */ + function balanceOf( + address tokenOwner + ) + public + view + virtual + override(IERC721, LSP8IdentifiableDigitalAssetCore) + returns (uint256) + { + return super.balanceOf(tokenOwner); + } + /** * @inheritdoc LSP8IdentifiableDigitalAssetInitAbstract */ @@ -87,21 +124,26 @@ abstract contract LSP8CompatibleERC721InitAbstract is public view virtual - override(IERC165, ERC725YCore, LSP8IdentifiableDigitalAssetInitAbstract) + override(IERC165, LSP8IdentifiableDigitalAssetInitAbstract) returns (bool) { return - interfaceId == _INTERFACEID_ERC721 || - interfaceId == _INTERFACEID_ERC721METADATA || + interfaceId == type(IERC721).interfaceId || + interfaceId == type(IERC721Metadata).interfaceId || super.supportsInterface(interfaceId); } /** - * @inheritdoc ILSP8CompatibleERC721 + * @inheritdoc IERC721Metadata + * @notice Retrieving the token URI of tokenId `tokenId`. + * + * @dev Compatible with ERC721Metadata tokenURI. Retrieve the tokenURI for a specific `tokenId`. + * + * @return The token URI. */ function tokenURI( uint256 /* tokenId */ - ) public view virtual returns (string memory) { + ) public view virtual override returns (string memory) { bytes memory data = _getData(_LSP4_METADATA_KEY); // offset = bytes4(hashSig) + bytes32(contentHash) -> 4 + 32 = 36 @@ -116,18 +158,32 @@ abstract contract LSP8CompatibleERC721InitAbstract is } /** - * @inheritdoc ILSP8CompatibleERC721 + * @inheritdoc IERC721 + * @notice Retrieving the address that own tokenId `tokenId`. + * + * @dev Compatible with ERC721 ownerOf. + * + * @param tokenId The tokenId to query. + * @return The owner of the tokenId. */ - function ownerOf(uint256 tokenId) public view virtual returns (address) { + function ownerOf( + uint256 tokenId + ) public view virtual override returns (address) { return tokenOwnerOf(bytes32(tokenId)); } /** - * @inheritdoc ILSP8CompatibleERC721 + * @inheritdoc IERC721 + * @notice Retrieving the address other than the token owner that is approved to transfer tokenId `tokenId` on behalf of its owner. + * + * @dev Compatible with ERC721 getApproved. + * + * @param tokenId The tokenId to query. + * @return The address of the operator for `tokenId`. */ function getApproved( uint256 tokenId - ) public view virtual returns (address) { + ) public view virtual override returns (address) { bytes32 tokenIdAsBytes32 = bytes32(tokenId); _existsOrError(tokenIdAsBytes32); @@ -147,75 +203,126 @@ abstract contract LSP8CompatibleERC721InitAbstract is } } - /* - * @inheritdoc ILSP8CompatibleERC721 + /** + * @inheritdoc IERC721 + * @notice Checking if address `operator` is approved to transfer any tokenId owned by address `owner`. + * + * @dev Compatible with ERC721 isApprovedForAll. + * + * @param tokenOwner The tokenOwner address to query. + * @param operator The operator address to query. + * + * @return Returns if the `operator` is allowed to manage all of the assets of `owner` */ function isApprovedForAll( address tokenOwner, address operator - ) public view virtual returns (bool) { + ) public view virtual override returns (bool) { return _operatorApprovals[tokenOwner][operator]; } /** - * @inheritdoc ILSP8CompatibleERC721 + * @inheritdoc IERC721 + * @notice Calling `approve` function to approve operator at address `operator` to transfer tokenId `tokenId` on behalf of its owner. + * + * @dev Approval function compatible with ERC721 `approve(address,uint256)`. + * + * @param operator The address to approve for `tokenId`. + * @param tokenId The tokenId to approve. */ - function approve(address operator, uint256 tokenId) public virtual { + function approve( + address operator, + uint256 tokenId + ) public virtual override { authorizeOperator(operator, bytes32(tokenId), ""); emit Approval(tokenOwnerOf(bytes32(tokenId)), operator, tokenId); } /** - * @dev See {_setApprovalForAll} + * @inheritdoc IERC721 + * @notice Setting the "approval for all" status of operator `_operator` to `_approved` to allow it to transfer any tokenIds on behalf of `msg.sender`. + * + * @dev Enable or disable approval for a third party ("operator") to manage all of `msg.sender`'s assets. The contract MUST allow multiple operators per owner. + * See {_setApprovalForAll} + * + * @param operator Address to add to the set of authorized operators. + * @param approved True if the operator is approved, false to revoke approval. + * + * @custom:events {ApprovalForAll} event */ - function setApprovalForAll(address operator, bool approved) public virtual { + function setApprovalForAll( + address operator, + bool approved + ) public virtual override { _setApprovalForAll(msg.sender, operator, approved); } /** - * @inheritdoc ILSP8CompatibleERC721 + * @inheritdoc IERC721 + * @notice Calling `transferFrom` function to transfer tokenId `tokenId` from address `from` to address `to`. * - * @custom:info This function sets the `allowNonLSP1Recipient` parameter to `true` so that EOAs and any contract can receive the `tokenId`. + * @dev Transfer functions from the ERC721 standard interface. + * + * @param from The sending address. + * @param to The receiving address. + * @param tokenId The tokenId to transfer. + * + * @custom:info This function sets the `force` parameter to `true` so that EOAs and any contract can receive the `tokenId`. */ function transferFrom( address from, address to, uint256 tokenId - ) public virtual { + ) public virtual override { _transfer(from, to, bytes32(tokenId), true, ""); } /** - * @inheritdoc ILSP8CompatibleERC721 + * @inheritdoc IERC721 + * @notice Calling `safeTransferFrom` function to transfer tokenId `tokenId` from address `from` to address `to`. + * + * @dev Safe Transfer function without optional data from the ERC721 standard interface. + * + * @param from The sending address. + * @param to The receiving address. + * @param tokenId The tokenId to transfer. * - * @custom:info This function sets the `allowNonLSP1Recipient` parameter to `true` so that EOAs and any contract can receive the `tokenId`. + * @custom:info This function sets the `force` parameter to `true` so that EOAs and any contract can receive the `tokenId`. */ function safeTransferFrom( address from, address to, uint256 tokenId - ) public virtual { + ) public virtual override { _safeTransfer(from, to, tokenId, ""); } /** - * @inheritdoc ILSP8CompatibleERC721 + * @inheritdoc IERC721 + * @notice Calling `safeTransferFrom` function with `data` to transfer tokenId `tokenId` from address `from` to address `to`. * - * @custom:info This function sets the `allowNonLSP1Recipient` parameter to `true` so that EOAs and any contract can receive the `tokenId`. + * @dev Safe Transfer function with optional data from the ERC721 standard interface. + * + * @param from The sending address. + * @param to The receiving address. + * @param tokenId The tokenId to transfer. + * @param data The data to be sent with the transfer. + * + * @custom:info This function sets the `force` parameter to `true` so that EOAs and any contract can receive the `tokenId`. */ function safeTransferFrom( address from, address to, uint256 tokenId, bytes memory data - ) public virtual { + ) public virtual override { _safeTransfer(from, to, tokenId, data); } // --- Overrides /** - * @inheritdoc ILSP8IdentifiableDigitalAsset + * @inheritdoc LSP8IdentifiableDigitalAssetCore * * @custom:events * - LSP7 {AuthorizedOperator} event. @@ -225,14 +332,7 @@ abstract contract LSP8CompatibleERC721InitAbstract is address operator, bytes32 tokenId, bytes memory operatorNotificationData - ) - public - virtual - override( - ILSP8IdentifiableDigitalAsset, - LSP8IdentifiableDigitalAssetCore - ) - { + ) public virtual override { address tokenOwner = tokenOwnerOf(tokenId); if ( @@ -280,20 +380,18 @@ abstract contract LSP8CompatibleERC721InitAbstract is address from, address to, bytes32 tokenId, - bool allowNonLSP1Recipient, + bool force, bytes memory data ) internal virtual override { - address operator = msg.sender; - if ( - !isApprovedForAll(from, operator) && - !_isOperatorOrOwner(operator, tokenId) + !isApprovedForAll(from, msg.sender) && + !_isOperatorOrOwner(msg.sender, tokenId) ) { - revert LSP8NotTokenOperator(tokenId, operator); + revert LSP8NotTokenOperator(tokenId, msg.sender); } emit Transfer(from, to, uint256(tokenId)); - super._transfer(from, to, tokenId, allowNonLSP1Recipient, data); + super._transfer(from, to, tokenId, force, data); } /** @@ -324,11 +422,11 @@ abstract contract LSP8CompatibleERC721InitAbstract is function _mint( address to, bytes32 tokenId, - bool allowNonLSP1Recipient, + bool force, bytes memory data ) internal virtual override { emit Transfer(address(0), to, uint256(tokenId)); - super._mint(to, tokenId, allowNonLSP1Recipient, data); + super._mint(to, tokenId, force, data); } /** @@ -349,7 +447,7 @@ abstract contract LSP8CompatibleERC721InitAbstract is } /** - * @dev Approve `operator` to operate on all tokens of `tokensOwner` + * @dev Approve `operator` to operate on all tokens of `tokensOwner`. * * @custom:events {ApprovalForAll} event. */ @@ -409,18 +507,4 @@ abstract contract LSP8CompatibleERC721InitAbstract is } } } - - /** - * @inheritdoc LSP4DigitalAssetMetadataInitAbstract - */ - function _setData( - bytes32 key, - bytes memory value - ) - internal - virtual - override(LSP4DigitalAssetMetadataInitAbstract, ERC725YCore) - { - super._setData(key, value); - } } diff --git a/contracts/LSP8IdentifiableDigitalAsset/extensions/LSP8Enumerable.sol b/contracts/LSP8IdentifiableDigitalAsset/extensions/LSP8Enumerable.sol index 2e100bb13..11ed39e20 100644 --- a/contracts/LSP8IdentifiableDigitalAsset/extensions/LSP8Enumerable.sol +++ b/contracts/LSP8IdentifiableDigitalAsset/extensions/LSP8Enumerable.sol @@ -1,14 +1,12 @@ -// SPDX-License-Identifier: MIT +// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.4; // modules import { - LSP8IdentifiableDigitalAsset -} from "../LSP8IdentifiableDigitalAsset.sol"; -import { + LSP8IdentifiableDigitalAsset, LSP8IdentifiableDigitalAssetCore -} from "../LSP8IdentifiableDigitalAssetCore.sol"; +} from "../LSP8IdentifiableDigitalAsset.sol"; /** * @dev LSP8 extension that enables to enumerate over all the `tokenIds` ever minted. @@ -39,11 +37,13 @@ abstract contract LSP8Enumerable is LSP8IdentifiableDigitalAsset { * @param from The address sending the `tokenId` (`address(0)` when `tokenId` is being minted). * @param to The address receiving the `tokenId` (`address(0)` when `tokenId` is being burnt). * @param tokenId The bytes32 identifier of the token being transferred. + * @param data The data sent alongside the the token transfer. */ function _beforeTokenTransfer( address from, address to, - bytes32 tokenId + bytes32 tokenId, + bytes memory data ) internal virtual override(LSP8IdentifiableDigitalAssetCore) { // `tokenId` being minted if (from == address(0)) { @@ -65,6 +65,6 @@ abstract contract LSP8Enumerable is LSP8IdentifiableDigitalAsset { delete _tokenIndex[tokenId]; } - super._beforeTokenTransfer(from, to, tokenId); + super._beforeTokenTransfer(from, to, tokenId, data); } } diff --git a/contracts/LSP8IdentifiableDigitalAsset/extensions/LSP8EnumerableInitAbstract.sol b/contracts/LSP8IdentifiableDigitalAsset/extensions/LSP8EnumerableInitAbstract.sol index c4513bb93..e898fc082 100644 --- a/contracts/LSP8IdentifiableDigitalAsset/extensions/LSP8EnumerableInitAbstract.sol +++ b/contracts/LSP8IdentifiableDigitalAsset/extensions/LSP8EnumerableInitAbstract.sol @@ -1,14 +1,12 @@ -// SPDX-License-Identifier: MIT +// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.4; // modules import { - LSP8IdentifiableDigitalAssetInitAbstract -} from "../LSP8IdentifiableDigitalAssetInitAbstract.sol"; -import { + LSP8IdentifiableDigitalAssetInitAbstract, LSP8IdentifiableDigitalAssetCore -} from "../LSP8IdentifiableDigitalAssetCore.sol"; +} from "../LSP8IdentifiableDigitalAssetInitAbstract.sol"; /** * @dev LSP8 extension. @@ -39,11 +37,13 @@ abstract contract LSP8EnumerableInitAbstract is * @param from The address sending the `tokenId` (`address(0)` when `tokenId` is being minted). * @param to The address receiving the `tokenId` (`address(0)` when `tokenId` is being burnt). * @param tokenId The bytes32 identifier of the token being transferred. + * @param data The data sent alongside the the token transfer. */ function _beforeTokenTransfer( address from, address to, - bytes32 tokenId + bytes32 tokenId, + bytes memory data ) internal virtual override(LSP8IdentifiableDigitalAssetCore) { if (from == address(0)) { uint256 index = totalSupply(); @@ -60,6 +60,6 @@ abstract contract LSP8EnumerableInitAbstract is delete _indexToken[lastIndex]; delete _tokenIndex[tokenId]; } - super._beforeTokenTransfer(from, to, tokenId); + super._beforeTokenTransfer(from, to, tokenId, data); } } diff --git a/contracts/LSP8IdentifiableDigitalAsset/presets/ILSP8Mintable.sol b/contracts/LSP8IdentifiableDigitalAsset/presets/ILSP8Mintable.sol index da04c68a8..946d022b8 100644 --- a/contracts/LSP8IdentifiableDigitalAsset/presets/ILSP8Mintable.sol +++ b/contracts/LSP8IdentifiableDigitalAsset/presets/ILSP8Mintable.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: MIT +// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.4; @@ -14,7 +14,7 @@ interface ILSP8Mintable is ILSP8IdentifiableDigitalAsset { /** * @param to The address to mint tokens * @param tokenId The tokenId to mint - * @param allowNonLSP1Recipient When set to TRUE, to may be any address but + * @param force When set to TRUE, to may be any address but * when set to FALSE to must be a contract that supports LSP1 UniversalReceiver * @param data Additional data the caller wants included in the emitted event, and sent in the hooks to `from` and `to` addresses. * @dev Mints `amount` tokens and transfers it to `to`. @@ -28,7 +28,7 @@ interface ILSP8Mintable is ILSP8IdentifiableDigitalAsset { function mint( address to, bytes32 tokenId, - bool allowNonLSP1Recipient, + bool force, bytes memory data ) external; } diff --git a/contracts/LSP8IdentifiableDigitalAsset/presets/LSP8CompatibleERC721Mintable.sol b/contracts/LSP8IdentifiableDigitalAsset/presets/LSP8CompatibleERC721Mintable.sol index 872b06593..cec967433 100644 --- a/contracts/LSP8IdentifiableDigitalAsset/presets/LSP8CompatibleERC721Mintable.sol +++ b/contracts/LSP8IdentifiableDigitalAsset/presets/LSP8CompatibleERC721Mintable.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: MIT +// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.12; import {LSP8CompatibleERC721} from "../extensions/LSP8CompatibleERC721.sol"; @@ -18,25 +18,26 @@ contract LSP8CompatibleERC721Mintable is LSP8CompatibleERC721 { constructor( string memory name_, string memory symbol_, - address newOwner_ - ) LSP8CompatibleERC721(name_, symbol_, newOwner_) {} + address newOwner_, + uint256 tokenIdType_ + ) LSP8CompatibleERC721(name_, symbol_, newOwner_, tokenIdType_) {} /** - * @notice Minting tokenId `tokenId` for address `to` with the additional data `data` (Note: allow non-LSP1 recipient is set to `allowNonLSP1Recipient`). + * @notice Minting tokenId `tokenId` for address `to` with the additional data `data` (Note: allow non-LSP1 recipient is set to `force`). * * @dev Public {_mint} function only callable by the {owner}. * * @param to The address that will receive the minted `tokenId`. * @param tokenId The tokenId to mint. - * @param allowNonLSP1Recipient Set to `false` to ensure that you are minting for a recipient that implements LSP1, `false` otherwise for forcing the minting. + * @param force Set to `false` to ensure that you are minting for a recipient that implements LSP1, `false` otherwise for forcing the minting. * @param data Any addition data to be sent alongside the minting. */ function mint( address to, bytes32 tokenId, - bool allowNonLSP1Recipient, + bool force, bytes memory data ) public virtual onlyOwner { - _mint(to, tokenId, allowNonLSP1Recipient, data); + _mint(to, tokenId, force, data); } } diff --git a/contracts/LSP8IdentifiableDigitalAsset/presets/LSP8CompatibleERC721MintableInit.sol b/contracts/LSP8IdentifiableDigitalAsset/presets/LSP8CompatibleERC721MintableInit.sol index 9bb1b63ab..7558bc368 100644 --- a/contracts/LSP8IdentifiableDigitalAsset/presets/LSP8CompatibleERC721MintableInit.sol +++ b/contracts/LSP8IdentifiableDigitalAsset/presets/LSP8CompatibleERC721MintableInit.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: MIT +// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.12; // modules @@ -30,12 +30,14 @@ contract LSP8CompatibleERC721MintableInit is function initialize( string memory name_, string memory symbol_, - address newOwner_ + address newOwner_, + uint256 tokenIdType_ ) external virtual initializer { LSP8CompatibleERC721MintableInitAbstract._initialize( name_, symbol_, - newOwner_ + newOwner_, + tokenIdType_ ); } } diff --git a/contracts/LSP8IdentifiableDigitalAsset/presets/LSP8CompatibleERC721MintableInitAbstract.sol b/contracts/LSP8IdentifiableDigitalAsset/presets/LSP8CompatibleERC721MintableInitAbstract.sol index 697d1bfb9..58e7d31aa 100644 --- a/contracts/LSP8IdentifiableDigitalAsset/presets/LSP8CompatibleERC721MintableInitAbstract.sol +++ b/contracts/LSP8IdentifiableDigitalAsset/presets/LSP8CompatibleERC721MintableInitAbstract.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: MIT +// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.12; // modules @@ -18,27 +18,33 @@ contract LSP8CompatibleERC721MintableInitAbstract is function _initialize( string memory name_, string memory symbol_, - address newOwner_ + address newOwner_, + uint256 tokenIdType_ ) internal virtual override onlyInitializing { - LSP8CompatibleERC721InitAbstract._initialize(name_, symbol_, newOwner_); + LSP8CompatibleERC721InitAbstract._initialize( + name_, + symbol_, + newOwner_, + tokenIdType_ + ); } /** - * @notice Minting tokenId `tokenId` for address `to` with the additional data `data` (Note: allow non-LSP1 recipient is set to `allowNonLSP1Recipient`). + * @notice Minting tokenId `tokenId` for address `to` with the additional data `data` (Note: allow non-LSP1 recipient is set to `force`). * * @dev Public {_mint} function only callable by the {owner}. * * @param to The address that will receive the minted `tokenId`. * @param tokenId The tokenId to mint. - * @param allowNonLSP1Recipient Set to `false` to ensure that you are minting for a recipient that implements LSP1, `false` otherwise for forcing the minting. + * @param force Set to `false` to ensure that you are minting for a recipient that implements LSP1, `false` otherwise for forcing the minting. * @param data Any addition data to be sent alongside the minting. */ function mint( address to, bytes32 tokenId, - bool allowNonLSP1Recipient, + bool force, bytes memory data ) public virtual onlyOwner { - _mint(to, tokenId, allowNonLSP1Recipient, data); + _mint(to, tokenId, force, data); } } diff --git a/contracts/LSP8IdentifiableDigitalAsset/presets/LSP8Mintable.sol b/contracts/LSP8IdentifiableDigitalAsset/presets/LSP8Mintable.sol index 43ac8e0a0..66bc01675 100644 --- a/contracts/LSP8IdentifiableDigitalAsset/presets/LSP8Mintable.sol +++ b/contracts/LSP8IdentifiableDigitalAsset/presets/LSP8Mintable.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: MIT +// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.4; @@ -25,25 +25,26 @@ contract LSP8Mintable is LSP8IdentifiableDigitalAsset, ILSP8Mintable { constructor( string memory name_, string memory symbol_, - address newOwner_ - ) LSP8IdentifiableDigitalAsset(name_, symbol_, newOwner_) {} + address newOwner_, + uint256 tokenIdType_ + ) LSP8IdentifiableDigitalAsset(name_, symbol_, newOwner_, tokenIdType_) {} /** - * @notice Minting tokenId `tokenId` for address `to` with the additional data `data` (Note: allow non-LSP1 recipient is set to `allowNonLSP1Recipient`). + * @notice Minting tokenId `tokenId` for address `to` with the additional data `data` (Note: allow non-LSP1 recipient is set to `force`). * * @dev Public {_mint} function only callable by the {owner}. * * @param to The address that will receive the minted `tokenId`. * @param tokenId The tokenId to mint. - * @param allowNonLSP1Recipient Set to `false` to ensure that you are minting for a recipient that implements LSP1, `false` otherwise for forcing the minting. + * @param force Set to `false` to ensure that you are minting for a recipient that implements LSP1, `false` otherwise for forcing the minting. * @param data Any addition data to be sent alongside the minting. */ function mint( address to, bytes32 tokenId, - bool allowNonLSP1Recipient, + bool force, bytes memory data - ) public virtual onlyOwner { - _mint(to, tokenId, allowNonLSP1Recipient, data); + ) public virtual override onlyOwner { + _mint(to, tokenId, force, data); } } diff --git a/contracts/LSP8IdentifiableDigitalAsset/presets/LSP8MintableInit.sol b/contracts/LSP8IdentifiableDigitalAsset/presets/LSP8MintableInit.sol index 749ced640..72532187e 100644 --- a/contracts/LSP8IdentifiableDigitalAsset/presets/LSP8MintableInit.sol +++ b/contracts/LSP8IdentifiableDigitalAsset/presets/LSP8MintableInit.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: MIT +// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.4; @@ -27,8 +27,14 @@ contract LSP8MintableInit is LSP8MintableInitAbstract { function initialize( string memory name_, string memory symbol_, - address newOwner_ + address newOwner_, + uint256 tokenIdType_ ) external virtual initializer { - LSP8MintableInitAbstract._initialize(name_, symbol_, newOwner_); + LSP8MintableInitAbstract._initialize( + name_, + symbol_, + newOwner_, + tokenIdType_ + ); } } diff --git a/contracts/LSP8IdentifiableDigitalAsset/presets/LSP8MintableInitAbstract.sol b/contracts/LSP8IdentifiableDigitalAsset/presets/LSP8MintableInitAbstract.sol index 4e614fc0d..b59f023ab 100644 --- a/contracts/LSP8IdentifiableDigitalAsset/presets/LSP8MintableInitAbstract.sol +++ b/contracts/LSP8IdentifiableDigitalAsset/presets/LSP8MintableInitAbstract.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: MIT +// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.4; @@ -27,31 +27,33 @@ abstract contract LSP8MintableInitAbstract is function _initialize( string memory name_, string memory symbol_, - address newOwner_ + address newOwner_, + uint256 tokenIdType_ ) internal virtual override onlyInitializing { LSP8IdentifiableDigitalAssetInitAbstract._initialize( name_, symbol_, - newOwner_ + newOwner_, + tokenIdType_ ); } /** - * @notice Minting tokenId `tokenId` for address `to` with the additional data `data` (Note: allow non-LSP1 recipient is set to `allowNonLSP1Recipient`). + * @notice Minting tokenId `tokenId` for address `to` with the additional data `data` (Note: allow non-LSP1 recipient is set to `force`). * * @dev Public {_mint} function only callable by the {owner}. * * @param to The address that will receive the minted `tokenId`. * @param tokenId The tokenId to mint. - * @param allowNonLSP1Recipient Set to `false` to ensure that you are minting for a recipient that implements LSP1, `false` otherwise for forcing the minting. + * @param force Set to `false` to ensure that you are minting for a recipient that implements LSP1, `false` otherwise for forcing the minting. * @param data Any addition data to be sent alongside the minting. */ function mint( address to, bytes32 tokenId, - bool allowNonLSP1Recipient, + bool force, bytes memory data - ) public virtual onlyOwner { - _mint(to, tokenId, allowNonLSP1Recipient, data); + ) public virtual override onlyOwner { + _mint(to, tokenId, force, data); } } diff --git a/contracts/LSP9Vault/ILSP9Vault.sol b/contracts/LSP9Vault/ILSP9Vault.sol index d8649f4d7..33bc9d443 100644 --- a/contracts/LSP9Vault/ILSP9Vault.sol +++ b/contracts/LSP9Vault/ILSP9Vault.sol @@ -1,19 +1,6 @@ // SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.4; -// interfaces -import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; -import { - IERC725X -} from "@erc725/smart-contracts/contracts/interfaces/IERC725X.sol"; -import { - IERC725Y -} from "@erc725/smart-contracts/contracts/interfaces/IERC725Y.sol"; -import { - ILSP1UniversalReceiver -} from "../LSP1UniversalReceiver/ILSP1UniversalReceiver.sol"; -import {ILSP14Ownable2Step} from "../LSP14Ownable2Step/ILSP14Ownable2Step.sol"; - /** * @title Interface of LSP9 - Vault standard, a blockchain vault that can hold assets and interact with other smart contracts. * @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. diff --git a/contracts/LSP9Vault/LSP9Constants.sol b/contracts/LSP9Vault/LSP9Constants.sol index 72299c813..f17ed7a04 100644 --- a/contracts/LSP9Vault/LSP9Constants.sol +++ b/contracts/LSP9Vault/LSP9Constants.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: CC0-1.0 +// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.4; // --- ERC165 interface ids diff --git a/contracts/LSP9Vault/LSP9Errors.sol b/contracts/LSP9Vault/LSP9Errors.sol index 60ab7e520..38bf3a90a 100644 --- a/contracts/LSP9Vault/LSP9Errors.sol +++ b/contracts/LSP9Vault/LSP9Errors.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: CC0-1.0 +// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.4; /** diff --git a/contracts/LSP9Vault/LSP9VaultCore.sol b/contracts/LSP9Vault/LSP9VaultCore.sol index 64e73f34a..3e090c70d 100644 --- a/contracts/LSP9Vault/LSP9VaultCore.sol +++ b/contracts/LSP9Vault/LSP9VaultCore.sol @@ -5,12 +5,14 @@ pragma solidity ^0.8.4; import { ILSP1UniversalReceiver } from "../LSP1UniversalReceiver/ILSP1UniversalReceiver.sol"; + +import { + ILSP1UniversalReceiverDelegate +} from "../LSP1UniversalReceiver/ILSP1UniversalReceiverDelegate.sol"; import {ILSP9Vault} from "./ILSP9Vault.sol"; // libraries import {BytesLib} from "solidity-bytes-utils/contracts/BytesLib.sol"; - -import {Address} from "@openzeppelin/contracts/utils/Address.sol"; import { ERC165Checker } from "@openzeppelin/contracts/utils/introspection/ERC165Checker.sol"; @@ -18,14 +20,8 @@ import {LSP1Utils} from "../LSP1UniversalReceiver/LSP1Utils.sol"; import {LSP2Utils} from "../LSP2ERC725YJSONSchema/LSP2Utils.sol"; // modules -import { - ERC725XCore, - IERC725X -} from "@erc725/smart-contracts/contracts/ERC725XCore.sol"; -import { - ERC725YCore, - IERC725Y -} from "@erc725/smart-contracts/contracts/ERC725YCore.sol"; +import {ERC725XCore} from "@erc725/smart-contracts/contracts/ERC725XCore.sol"; +import {ERC725YCore} from "@erc725/smart-contracts/contracts/ERC725YCore.sol"; import { OwnableUnset } from "@erc725/smart-contracts/contracts/custom/OwnableUnset.sol"; @@ -33,7 +29,14 @@ import {LSP14Ownable2Step} from "../LSP14Ownable2Step/LSP14Ownable2Step.sol"; import {LSP17Extendable} from "../LSP17ContractExtension/LSP17Extendable.sol"; // constants -import "@erc725/smart-contracts/contracts/errors.sol"; +import { + ERC725Y_MsgValueDisallowed, + ERC725Y_DataKeysValuesLengthMismatch, + ERC725X_CreateOperationsRequireEmptyRecipientAddress, + ERC725X_CreateOperationsRequireEmptyRecipientAddress, + ERC725X_MsgValueDisallowedInStaticCall, + ERC725X_UnknownOperationType +} from "@erc725/smart-contracts/contracts/errors.sol"; import { OPERATION_0_CALL, OPERATION_1_CREATE, @@ -42,6 +45,7 @@ import { } from "@erc725/smart-contracts/contracts/constants.sol"; import { _INTERFACEID_LSP1, + _INTERFACEID_LSP1_DELEGATE, _LSP1_UNIVERSAL_RECEIVER_DELEGATE_PREFIX, _LSP1_UNIVERSAL_RECEIVER_DELEGATE_KEY } from "../LSP1UniversalReceiver/LSP1Constants.sol"; @@ -61,12 +65,11 @@ import { } from "../LSP17ContractExtension/LSP17Constants.sol"; // errors -import "./LSP9Errors.sol"; -import "../LSP17ContractExtension/LSP17Errors.sol"; +import {LSP1DelegateNotAllowedToSetDataKey} from "./LSP9Errors.sol"; import { - LSP14MustAcceptOwnershipInSeparateTransaction -} from "../LSP14Ownable2Step/LSP14Errors.sol"; + NoExtensionFoundForFunctionSelector +} from "../LSP17ContractExtension/LSP17Errors.sol"; /** * @title Core Implementation of LSP9Vault built on top of [ERC725], [LSP1UniversalReceiver] @@ -94,11 +97,9 @@ contract LSP9VaultCore is * @custom:events {ValueReceived} when receiving native tokens. */ receive() external payable virtual { - if (msg.value > 0) emit ValueReceived(msg.sender, msg.value); + if (msg.value != 0) emit ValueReceived(msg.sender, msg.value); } - // solhint-disable no-complex-fallback - /** * @notice The `fallback` function was called with the following amount of native tokens: `msg.value`; and the following calldata: `callData`. * @@ -120,6 +121,7 @@ contract LSP9VaultCore is * * @custom:events {ValueReceived} event when receiving native tokens. */ + // solhint-disable-next-line no-complex-fallback fallback( bytes calldata callData ) external payable virtual returns (bytes memory) { @@ -141,7 +143,7 @@ contract LSP9VaultCore is */ function batchCalls( bytes[] calldata data - ) public virtual returns (bytes[] memory results) { + ) public virtual override returns (bytes[] memory results) { results = new bytes[](data.length); for (uint256 i; i < data.length; ) { (bool success, bytes memory result) = address(this).delegatecall( @@ -150,7 +152,7 @@ contract LSP9VaultCore is if (!success) { // Look for revert reason and bubble it up if present - if (result.length > 0) { + if (result.length != 0) { // The easiest way to bubble the revert reason is using memory via assembly // solhint-disable no-inline-assembly /// @solidity memory-safe-assembly @@ -172,7 +174,7 @@ contract LSP9VaultCore is } /** - * @inheritdoc IERC725X + * @inheritdoc ERC725XCore * * @custom:requirements * - Can be only called by the {owner} or by an authorised address that pass the verification check performed on the owner. @@ -225,7 +227,7 @@ contract LSP9VaultCore is } /** - * @inheritdoc IERC725Y + * @inheritdoc ERC725YCore * * @custom:requirements Can be only called by the {owner} or by an authorised address that pass the verification check performed on the owner. * @@ -253,7 +255,7 @@ contract LSP9VaultCore is } /** - * @inheritdoc IERC725Y + * @inheritdoc ERC725YCore * * @custom:requirements Can be only called by the {owner} or by an authorised address that pass the verification check performed on the owner. * @@ -324,7 +326,7 @@ contract LSP9VaultCore is function universalReceiver( bytes32 typeId, bytes calldata receivedData - ) public payable virtual returns (bytes memory returnedValues) { + ) public payable virtual override returns (bytes memory returnedValues) { if (msg.value != 0) { emit ValueReceived(msg.sender, msg.value); } @@ -341,16 +343,17 @@ contract LSP9VaultCore is if ( universalReceiverDelegate.supportsERC165InterfaceUnchecked( - _INTERFACEID_LSP1 + _INTERFACEID_LSP1_DELEGATE ) ) { _reentrantDelegate = universalReceiverDelegate; - resultDefaultDelegate = universalReceiverDelegate - .callUniversalReceiverWithCallerInfos( - typeId, - receivedData, + resultDefaultDelegate = ILSP1UniversalReceiverDelegate( + universalReceiverDelegate + ).universalReceiverDelegate( msg.sender, - msg.value + msg.value, + typeId, + receivedData ); } } @@ -370,16 +373,17 @@ contract LSP9VaultCore is if ( universalReceiverDelegate.supportsERC165InterfaceUnchecked( - _INTERFACEID_LSP1 + _INTERFACEID_LSP1_DELEGATE ) ) { _reentrantDelegate = universalReceiverDelegate; - resultTypeIdDelegate = universalReceiverDelegate - .callUniversalReceiverWithCallerInfos( - typeId, - receivedData, + resultTypeIdDelegate = ILSP1UniversalReceiverDelegate( + universalReceiverDelegate + ).universalReceiverDelegate( msg.sender, - msg.value + msg.value, + typeId, + receivedData ); } } @@ -508,11 +512,22 @@ contract LSP9VaultCore is /** * @dev Forwards the call to an extension mapped to a function selector. * - * Calls {_getExtension} to get the address of the extension mapped to the function selector being called on the account. If there is no extension, the `address(0)` will be returned. + * Calls {_getExtension} to get the address of the extension mapped to the function selector being + * called on the account. If there is no extension, the `address(0)` will be returned. * - * Reverts if there is no extension for the function being called, except for the bytes4(0) function selector, which passes even if there is no extension for it. + * Reverts if there is no extension for the function being called, except for the `bytes4(0)` function selector, which passes even if there is no extension for it. * - * 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` + * 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) + * ); + * ``` */ function _fallbackLSP17Extendable( bytes calldata callData @@ -626,15 +641,17 @@ contract LSP9VaultCore is // Deploy with CREATE if (operationType == uint256(OPERATION_1_CREATE)) { - if (target != address(0)) + if (target != address(0)) { revert ERC725X_CreateOperationsRequireEmptyRecipientAddress(); + } return _deployCreate(value, data); } // Deploy with CREATE2 if (operationType == uint256(OPERATION_2_CREATE2)) { - if (target != address(0)) + if (target != address(0)) { revert ERC725X_CreateOperationsRequireEmptyRecipientAddress(); + } return _deployCreate2(value, data); } diff --git a/contracts/Mocks/ERC165Interfaces.sol b/contracts/Mocks/ERC165Interfaces.sol index 770ee562b..54b90ab3c 100644 --- a/contracts/Mocks/ERC165Interfaces.sol +++ b/contracts/Mocks/ERC165Interfaces.sol @@ -1,3 +1,4 @@ +// solhint-disable one-contract-per-file // SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.4; @@ -12,13 +13,16 @@ import { OwnableUnset } from "@erc725/smart-contracts/contracts/custom/OwnableUnset.sol"; -import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol"; +import {IERC20} from "@openzeppelin/contracts/interfaces/IERC20.sol"; +import { + IERC20Metadata +} from "@openzeppelin/contracts/interfaces/IERC20Metadata.sol"; +import {IERC721} from "@openzeppelin/contracts/interfaces/IERC721.sol"; import { IERC721Metadata -} from "@openzeppelin/contracts/token/ERC721/extensions/IERC721Metadata.sol"; -import {IERC777} from "@openzeppelin/contracts/token/ERC777/IERC777.sol"; -import {IERC1155} from "@openzeppelin/contracts/token/ERC1155/IERC1155.sol"; +} from "@openzeppelin/contracts/interfaces/IERC721Metadata.sol"; +import {IERC777} from "@openzeppelin/contracts/interfaces/IERC777.sol"; +import {IERC1155} from "@openzeppelin/contracts/interfaces/IERC1155.sol"; import {IERC1271} from "@openzeppelin/contracts/interfaces/IERC1271.sol"; import {IERC223} from "./Tokens/IERC223.sol"; @@ -120,6 +124,7 @@ contract CalculateLSPInterfaces { type(IERC1271).interfaceId ^ calculateInterfaceLSP20CallVerifier() ^ calculateInterfaceLSP25ExecuteRelayCall(); + require( interfaceId == _INTERFACEID_LSP6, "hardcoded _INTERFACEID_LSP6 does not match type(ILSP6).interfaceId" @@ -132,6 +137,7 @@ contract CalculateLSPInterfaces { bytes4 interfaceId = type(ILSP7).interfaceId ^ type(IERC725Y).interfaceId ^ calculateInterfaceLSP17Extendable(); + require( interfaceId == _INTERFACEID_LSP7, "hardcoded _INTERFACEID_LSP7 does not match type(ILSP7).interfaceId" @@ -144,6 +150,7 @@ contract CalculateLSPInterfaces { bytes4 interfaceId = type(ILSP8).interfaceId ^ type(IERC725Y).interfaceId ^ calculateInterfaceLSP17Extendable(); + require( interfaceId == _INTERFACEID_LSP8, "hardcoded _INTERFACEID_LSP8 does not match type(ILSP8).interfaceId" @@ -274,6 +281,10 @@ contract CalculateERCInterfaces { return type(IERC20).interfaceId; } + function calculateInterfaceERC20Metadata() public pure returns (bytes4) { + return type(IERC20Metadata).interfaceId; + } + function calculateInterfaceERC721() public pure returns (bytes4) { return type(IERC721).interfaceId; } diff --git a/contracts/Mocks/Executor.sol b/contracts/Mocks/Executor.sol index 340963a7c..2a234ad65 100644 --- a/contracts/Mocks/Executor.sol +++ b/contracts/Mocks/Executor.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: MIT +// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.4; // interfaces diff --git a/contracts/Mocks/ExecutorLSP20.sol b/contracts/Mocks/ExecutorLSP20.sol index 4d6c6aeb9..ac77e27c1 100644 --- a/contracts/Mocks/ExecutorLSP20.sol +++ b/contracts/Mocks/ExecutorLSP20.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: MIT +// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.4; // interfaces @@ -8,11 +8,9 @@ import { import { IERC725Y } from "@erc725/smart-contracts/contracts/interfaces/IERC725Y.sol"; -import {ILSP6KeyManager} from "../LSP6KeyManager/ILSP6KeyManager.sol"; // modules import {UniversalProfile} from "../UniversalProfile.sol"; -import {LSP6KeyManager} from "../LSP6KeyManager/LSP6KeyManager.sol"; // constants import { diff --git a/contracts/Mocks/FallbackExtensions/ERC165Extension.sol b/contracts/Mocks/FallbackExtensions/ERC165Extension.sol index 89baf4574..9e2907495 100644 --- a/contracts/Mocks/FallbackExtensions/ERC165Extension.sol +++ b/contracts/Mocks/FallbackExtensions/ERC165Extension.sol @@ -11,7 +11,7 @@ contract ERC165Extension is IERC165 { function supportsInterface( bytes4 interfaceId - ) public view virtual returns (bool) { + ) public pure override returns (bool) { return interfaceId == _RANDOM_INTERFACE_ID; } } diff --git a/contracts/Mocks/FallbackExtensions/OnERC721ReceivedExtension.sol b/contracts/Mocks/FallbackExtensions/OnERC721ReceivedExtension.sol deleted file mode 100644 index 6dfa3f4a9..000000000 --- a/contracts/Mocks/FallbackExtensions/OnERC721ReceivedExtension.sol +++ /dev/null @@ -1,16 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -pragma solidity ^0.8.4; - -/** - * @dev This contract is used only for testing purposes - */ -contract OnERC721ReceivedExtension { - function onERC721Received( - address /* operator */, - address /* from */, - uint256 /* tokenId */, - bytes calldata /* data */ - ) external pure returns (bytes4) { - return 0x150b7a02; - } -} diff --git a/contracts/Mocks/GenericExecutor.sol b/contracts/Mocks/GenericExecutor.sol index a51a81e42..2c9d040da 100644 --- a/contracts/Mocks/GenericExecutor.sol +++ b/contracts/Mocks/GenericExecutor.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: MIT +// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.4; /** diff --git a/contracts/Mocks/GenericExecutorWithBalanceOfFunction.sol b/contracts/Mocks/GenericExecutorWithBalanceOfFunction.sol index 17f477694..9bb9f038e 100644 --- a/contracts/Mocks/GenericExecutorWithBalanceOfFunction.sol +++ b/contracts/Mocks/GenericExecutorWithBalanceOfFunction.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: MIT +// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.4; /** diff --git a/contracts/Mocks/ImplementationTester.sol b/contracts/Mocks/ImplementationTester.sol index cc516941f..0cd7b6579 100644 --- a/contracts/Mocks/ImplementationTester.sol +++ b/contracts/Mocks/ImplementationTester.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: MIT +// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.4; import { diff --git a/contracts/Mocks/KeyManager/KeyManagerInternalsTester.sol b/contracts/Mocks/KeyManager/KeyManagerInternalsTester.sol index b16538adc..12facc51f 100644 --- a/contracts/Mocks/KeyManager/KeyManagerInternalsTester.sol +++ b/contracts/Mocks/KeyManager/KeyManagerInternalsTester.sol @@ -113,11 +113,15 @@ contract KeyManagerInternalTester is LSP6KeyManager { return _addressPermission.hasPermission(_permissions); } - function verifyPermissions( - address from, - uint256 msgValue, - bytes calldata payload - ) public view { - super._verifyPermissions(from, msgValue, payload); + function verifyPermissions(address from, bytes calldata payload) public { + super._verifyPermissions(_target, from, false, payload); + + // This event is emitted just for a sake of not marking this function as `view`, + // as Hardhat has a bug that does not catch error that occured from failed `abi.decode` + // inside view functions. + // See these issues in the Github repository of Hardhat: + // - https://github.com/NomicFoundation/hardhat/issues/3084 + // - https://github.com/NomicFoundation/hardhat/issues/3475 + emit PermissionsVerified(from, 0, bytes4(payload)); } } diff --git a/contracts/Mocks/KeyManagerInitWithExtraParams.sol b/contracts/Mocks/KeyManagerInitWithExtraParams.sol index 206b125bf..af9631860 100644 --- a/contracts/Mocks/KeyManagerInitWithExtraParams.sol +++ b/contracts/Mocks/KeyManagerInitWithExtraParams.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: MIT +// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.4; import {LSP6KeyManagerInit} from "../LSP6KeyManager/LSP6KeyManagerInit.sol"; diff --git a/contracts/Mocks/KeyManagerWithExtraParams.sol b/contracts/Mocks/KeyManagerWithExtraParams.sol index c901ef74d..be27c4ffd 100644 --- a/contracts/Mocks/KeyManagerWithExtraParams.sol +++ b/contracts/Mocks/KeyManagerWithExtraParams.sol @@ -1,18 +1,18 @@ -// SPDX-License-Identifier: MIT +// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.4; import {LSP6KeyManager} from "../LSP6KeyManager/LSP6KeyManager.sol"; contract KeyManagerWithExtraParams is LSP6KeyManager { - address public immutable firstParam; - address public immutable lastParam; + address public immutable FIRST_PARAM; + address public immutable LAST_PARAM; constructor( address firstParam_, address target_, address lastParam_ ) LSP6KeyManager(target_) { - firstParam = firstParam_; - lastParam = lastParam_; + FIRST_PARAM = firstParam_; + LAST_PARAM = lastParam_; } } diff --git a/contracts/Mocks/LSP20Owners/FallbackReturnMagicValue.sol b/contracts/Mocks/LSP20Owners/FallbackReturnMagicValue.sol index de1d904cd..40b829104 100644 --- a/contracts/Mocks/LSP20Owners/FallbackReturnMagicValue.sol +++ b/contracts/Mocks/LSP20Owners/FallbackReturnMagicValue.sol @@ -11,7 +11,7 @@ import { /** * @title sample contract used for testing */ -contract FallbackReturnMagicValue { +contract FallbackReturnSuccessValue { event FallbackCalled(bytes data); address public target; diff --git a/contracts/Mocks/LSP20Owners/FirstCallReturnExpandedInvalidValue.sol b/contracts/Mocks/LSP20Owners/FirstCallReturnExpandedInvalidValue.sol index 326fb0c88..320c08f45 100644 --- a/contracts/Mocks/LSP20Owners/FirstCallReturnExpandedInvalidValue.sol +++ b/contracts/Mocks/LSP20Owners/FirstCallReturnExpandedInvalidValue.sol @@ -1,9 +1,6 @@ // SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.4; -import { - ILSP20CallVerifier -} from "../../LSP20CallVerification/ILSP20CallVerifier.sol"; import { ILSP14Ownable2Step } from "../../LSP14Ownable2Step/ILSP14Ownable2Step.sol"; @@ -11,19 +8,24 @@ import { /** * @title sample contract used for testing */ -contract FirstCallReturnExpandedInvalidValue { +contract FirstCallReturnExpandedFailValue { event CallVerified(); address public target; function lsp20VerifyCall( + address requestor, + address targetContract, address caller, uint256 value, bytes memory data ) external returns (bytes32) { emit CallVerified(); - return keccak256(abi.encode(caller, value, data)); + return + keccak256( + abi.encode(requestor, targetContract, caller, value, data) + ); } function acceptOwnership(address newTarget) external { diff --git a/contracts/Mocks/LSP20Owners/FirstCallReturnInvalidMagicValue.sol b/contracts/Mocks/LSP20Owners/FirstCallReturnInvalidMagicValue.sol index aaf31bf9d..c9e6b60a2 100644 --- a/contracts/Mocks/LSP20Owners/FirstCallReturnInvalidMagicValue.sol +++ b/contracts/Mocks/LSP20Owners/FirstCallReturnInvalidMagicValue.sol @@ -1,9 +1,5 @@ // SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.4; - -import { - ILSP20CallVerifier -} from "../../LSP20CallVerification/ILSP20CallVerifier.sol"; import { ILSP14Ownable2Step } from "../../LSP14Ownable2Step/ILSP14Ownable2Step.sol"; @@ -11,12 +7,14 @@ import { /** * @title sample contract used for testing */ -contract FirstCallReturnInvalidMagicValue { +contract FirstCallReturnFailValue { event CallVerified(); address public target; function lsp20VerifyCall( + address, + address, address, uint256, bytes memory diff --git a/contracts/Mocks/LSP2UtilsLibraryTester.sol b/contracts/Mocks/LSP2UtilsLibraryTester.sol index f1471dba2..02e514ff8 100644 --- a/contracts/Mocks/LSP2UtilsLibraryTester.sol +++ b/contracts/Mocks/LSP2UtilsLibraryTester.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: MIT +// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.4; import {LSP2Utils} from "../LSP2ERC725YJSONSchema/LSP2Utils.sol"; @@ -6,10 +6,6 @@ import {LSP2Utils} from "../LSP2ERC725YJSONSchema/LSP2Utils.sol"; contract LSP2UtilsLibraryTester { using LSP2Utils for *; - function isEncodedArray(bytes memory data) public pure returns (bool) { - return data.isEncodedArray(); - } - function isCompactBytesArray(bytes memory data) public pure returns (bool) { return data.isCompactBytesArray(); } diff --git a/contracts/Mocks/MaliciousERC1271Wallet.sol b/contracts/Mocks/MaliciousERC1271Wallet.sol index 504006d97..383f704ad 100644 --- a/contracts/Mocks/MaliciousERC1271Wallet.sol +++ b/contracts/Mocks/MaliciousERC1271Wallet.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: MIT +// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.4; import {GenericExecutor} from "./GenericExecutor.sol"; @@ -8,7 +8,7 @@ import {GenericExecutor} from "./GenericExecutor.sol"; */ contract ERC1271MaliciousMock is GenericExecutor { /** - * @dev Returns a malicious 4-byte magic value. + * @dev Returns a malicious 4-byte value. * @return bytes4 The malicious 4-byte magic value. */ function isValidSignature( diff --git a/contracts/Mocks/PayableContract.sol b/contracts/Mocks/PayableContract.sol index 04493edc6..c638d0c7b 100644 --- a/contracts/Mocks/PayableContract.sol +++ b/contracts/Mocks/PayableContract.sol @@ -5,9 +5,12 @@ pragma solidity ^0.8.4; * @dev sample contract used for testing */ contract PayableContract { + // solhint-disable no-empty-blocks constructor() payable {} + // solhint-disable no-empty-blocks function payableTrue() public payable {} + // solhint-disable no-empty-blocks function payableFalse() public {} } diff --git a/contracts/Mocks/Reentrancy/BatchReentrancyRelayer.sol b/contracts/Mocks/Reentrancy/BatchReentrancyRelayer.sol index 32a01c962..4c6385f9e 100644 --- a/contracts/Mocks/Reentrancy/BatchReentrancyRelayer.sol +++ b/contracts/Mocks/Reentrancy/BatchReentrancyRelayer.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: MIT +// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.4; // interfaces diff --git a/contracts/Mocks/Reentrancy/LSP20ReentrantContract.sol b/contracts/Mocks/Reentrancy/LSP20ReentrantContract.sol index 3286842d2..e5e7e5586 100644 --- a/contracts/Mocks/Reentrancy/LSP20ReentrantContract.sol +++ b/contracts/Mocks/Reentrancy/LSP20ReentrantContract.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: MIT +// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.4; // interfaces @@ -10,8 +10,14 @@ import { } from "@erc725/smart-contracts/contracts/interfaces/IERC725X.sol"; // constants -import "../../LSP1UniversalReceiver/LSP1Constants.sol"; -import "../../LSP6KeyManager/LSP6Constants.sol"; +import { + _LSP1_UNIVERSAL_RECEIVER_DELEGATE_PREFIX +} from "../../LSP1UniversalReceiver/LSP1Constants.sol"; + +import { + _LSP6KEY_ADDRESSPERMISSIONS_PERMISSIONS_PREFIX, + ALL_REGULAR_PERMISSIONS +} from "../../LSP6KeyManager/LSP6Constants.sol"; contract LSP20ReentrantContract { event ValueReceived(uint256); diff --git a/contracts/Mocks/Reentrancy/ReentrantContract.sol b/contracts/Mocks/Reentrancy/ReentrantContract.sol index a1c59ffd5..b916e98c9 100644 --- a/contracts/Mocks/Reentrancy/ReentrantContract.sol +++ b/contracts/Mocks/Reentrancy/ReentrantContract.sol @@ -1,12 +1,17 @@ -// SPDX-License-Identifier: MIT +// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.4; // interfaces import {ILSP6KeyManager} from "../../LSP6KeyManager/ILSP6KeyManager.sol"; // constants -import "../../LSP1UniversalReceiver/LSP1Constants.sol"; -import "../../LSP6KeyManager/LSP6Constants.sol"; +import { + _LSP1_UNIVERSAL_RECEIVER_DELEGATE_PREFIX +} from "../../LSP1UniversalReceiver/LSP1Constants.sol"; + +import { + _LSP6KEY_ADDRESSPERMISSIONS_PERMISSIONS_PREFIX +} from "../../LSP6KeyManager/LSP6Constants.sol"; contract ReentrantContract { event ValueReceived(uint256); diff --git a/contracts/Mocks/Reentrancy/SingleReentrancyRelayer.sol b/contracts/Mocks/Reentrancy/SingleReentrancyRelayer.sol index f1e067636..0e24078de 100644 --- a/contracts/Mocks/Reentrancy/SingleReentrancyRelayer.sol +++ b/contracts/Mocks/Reentrancy/SingleReentrancyRelayer.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: MIT +// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.4; // interfaces diff --git a/contracts/Mocks/Reentrancy/ThreeReentrancy.sol b/contracts/Mocks/Reentrancy/ThreeReentrancy.sol index 0caea494e..d93f422de 100644 --- a/contracts/Mocks/Reentrancy/ThreeReentrancy.sol +++ b/contracts/Mocks/Reentrancy/ThreeReentrancy.sol @@ -1,4 +1,5 @@ -// SPDX-License-Identifier: MIT +// solhint-disable one-contract-per-file +// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.4; import { @@ -7,8 +8,7 @@ import { import { IERC725X } from "@erc725/smart-contracts/contracts/interfaces/IERC725X.sol"; -import {Address} from "@openzeppelin/contracts/utils/Address.sol"; -import "../../LSP6KeyManager/ILSP6KeyManager.sol"; +import {ILSP6KeyManager} from "../../LSP6KeyManager/ILSP6KeyManager.sol"; /** * The purpose of these contracts is to perform tests on chained reentrancy scenarios diff --git a/contracts/Mocks/Tokens/LSP4CompatibilityTester.sol b/contracts/Mocks/Tokens/LSP4CompatibilityTester.sol deleted file mode 100644 index 9ca432621..000000000 --- a/contracts/Mocks/Tokens/LSP4CompatibilityTester.sol +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.4; - -// modules -import {ERC725Y} from "@erc725/smart-contracts/contracts/ERC725Y.sol"; -import { - LSP4Compatibility -} from "../../LSP4DigitalAssetMetadata/LSP4Compatibility.sol"; - -// constants -import { - _LSP4_TOKEN_NAME_KEY, - _LSP4_TOKEN_SYMBOL_KEY -} from "../../LSP4DigitalAssetMetadata/LSP4Constants.sol"; - -contract LSP4CompatibilityTester is ERC725Y, LSP4Compatibility { - constructor( - string memory name, - string memory symbol, - address newOwner - ) ERC725Y(newOwner) { - _setData(_LSP4_TOKEN_NAME_KEY, bytes(name)); - _setData(_LSP4_TOKEN_SYMBOL_KEY, bytes(symbol)); - } -} diff --git a/contracts/Mocks/Tokens/LSP7CappedSupplyInitTester.sol b/contracts/Mocks/Tokens/LSP7CappedSupplyInitTester.sol index 8dbe9efd9..286d08d22 100644 --- a/contracts/Mocks/Tokens/LSP7CappedSupplyInitTester.sol +++ b/contracts/Mocks/Tokens/LSP7CappedSupplyInitTester.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: MIT +// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.4; @@ -22,7 +22,7 @@ contract LSP7CappedSupplyInitTester is LSP7CappedSupplyInitAbstract { } function mint(address to, uint256 amount) public { - // using allowNonLSP1Recipient=true so we can send to EOA in test + // using force=true so we can send to EOA in test _mint(to, amount, true, "token printer go brrr"); } diff --git a/contracts/Mocks/Tokens/LSP7CappedSupplyTester.sol b/contracts/Mocks/Tokens/LSP7CappedSupplyTester.sol index ec8c1cd68..252d985ce 100644 --- a/contracts/Mocks/Tokens/LSP7CappedSupplyTester.sol +++ b/contracts/Mocks/Tokens/LSP7CappedSupplyTester.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: MIT +// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.4; @@ -20,7 +20,7 @@ contract LSP7CappedSupplyTester is LSP7CappedSupply { {} function mint(address to, uint256 amount) public { - // using allowNonLSP1Recipient=true so we can send to EOA in test + // using force=true so we can send to EOA in test _mint(to, amount, true, "token printer go brrr"); } diff --git a/contracts/Mocks/Tokens/LSP7CompatibleERC20InitTester.sol b/contracts/Mocks/Tokens/LSP7CompatibleERC20InitTester.sol index 939d69baf..996b173a9 100644 --- a/contracts/Mocks/Tokens/LSP7CompatibleERC20InitTester.sol +++ b/contracts/Mocks/Tokens/LSP7CompatibleERC20InitTester.sol @@ -1,9 +1,8 @@ -// SPDX-License-Identifier: MIT +// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.4; // modules -import {LSP7DigitalAsset} from "../../LSP7DigitalAsset/LSP7DigitalAsset.sol"; import { LSP7CompatibleERC20InitAbstract } from "../../LSP7DigitalAsset/extensions/LSP7CompatibleERC20InitAbstract.sol"; @@ -25,7 +24,7 @@ contract LSP7CompatibleERC20InitTester is LSP7CompatibleERC20InitAbstract { } function mint(address to, uint256 amount, bytes calldata data) public { - // using allowNonLSP1Recipient=true so we can send to EOA in test + // using force=true so we can send to EOA in test _mint(to, amount, true, data); } diff --git a/contracts/Mocks/Tokens/LSP7CompatibleERC20Tester.sol b/contracts/Mocks/Tokens/LSP7CompatibleERC20Tester.sol index b768346ca..21b1e4891 100644 --- a/contracts/Mocks/Tokens/LSP7CompatibleERC20Tester.sol +++ b/contracts/Mocks/Tokens/LSP7CompatibleERC20Tester.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: MIT +// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.4; @@ -6,7 +6,6 @@ pragma solidity ^0.8.4; import { LSP7CompatibleERC20 } from "../../LSP7DigitalAsset/extensions/LSP7CompatibleERC20.sol"; -import {LSP7DigitalAsset} from "../../LSP7DigitalAsset/LSP7DigitalAsset.sol"; contract LSP7CompatibleERC20Tester is LSP7CompatibleERC20 { constructor( @@ -16,7 +15,7 @@ contract LSP7CompatibleERC20Tester is LSP7CompatibleERC20 { ) LSP7CompatibleERC20(name_, symbol_, newOwner_) {} function mint(address to, uint256 amount, bytes calldata data) public { - // using allowNonLSP1Recipient=true so we can send to EOA in test + // using force=true so we can send to EOA in test _mint(to, amount, true, data); } diff --git a/contracts/Mocks/Tokens/LSP7InitTester.sol b/contracts/Mocks/Tokens/LSP7InitTester.sol index 2a95b0ae9..5759191f6 100644 --- a/contracts/Mocks/Tokens/LSP7InitTester.sol +++ b/contracts/Mocks/Tokens/LSP7InitTester.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: MIT +// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.4; @@ -31,9 +31,9 @@ contract LSP7InitTester is function mint( address to, uint256 amount, - bool allowNonLSP1Recipient, + bool force, bytes memory data ) public { - _mint(to, amount, allowNonLSP1Recipient, data); + _mint(to, amount, force, data); } } diff --git a/contracts/Mocks/Tokens/LSP7MintWhenDeployed.sol b/contracts/Mocks/Tokens/LSP7MintWhenDeployed.sol index f21c3df60..7c4e15a5f 100644 --- a/contracts/Mocks/Tokens/LSP7MintWhenDeployed.sol +++ b/contracts/Mocks/Tokens/LSP7MintWhenDeployed.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: MIT +// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.4; import {LSP7DigitalAsset} from "../../LSP7DigitalAsset/LSP7DigitalAsset.sol"; diff --git a/contracts/Mocks/Tokens/LSP7Tester.sol b/contracts/Mocks/Tokens/LSP7Tester.sol index 4681979b2..ca0744ec8 100644 --- a/contracts/Mocks/Tokens/LSP7Tester.sol +++ b/contracts/Mocks/Tokens/LSP7Tester.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: MIT +// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.4; // modules @@ -15,9 +15,9 @@ contract LSP7Tester is LSP7DigitalAsset, LSP7Burnable { function mint( address to, uint256 amount, - bool allowNonLSP1Recipient, + bool force, bytes memory data ) public { - _mint(to, amount, allowNonLSP1Recipient, data); + _mint(to, amount, force, data); } } diff --git a/contracts/Mocks/Tokens/LSP8BurnableInitTester.sol b/contracts/Mocks/Tokens/LSP8BurnableInitTester.sol index 6f3e42e1a..0b63ce67b 100644 --- a/contracts/Mocks/Tokens/LSP8BurnableInitTester.sol +++ b/contracts/Mocks/Tokens/LSP8BurnableInitTester.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: MIT +// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.4; @@ -14,12 +14,14 @@ contract LSP8BurnableInitTester is LSP8BurnableInitAbstract { function initialize( string memory name_, string memory symbol_, - address newOwner_ + address newOwner_, + uint256 tokenIdType_ ) public virtual initializer { LSP8IdentifiableDigitalAssetInitAbstract._initialize( name_, symbol_, - newOwner_ + newOwner_, + tokenIdType_ ); } } diff --git a/contracts/Mocks/Tokens/LSP8BurnableTester.sol b/contracts/Mocks/Tokens/LSP8BurnableTester.sol index c0b037d3d..02c38dbb4 100644 --- a/contracts/Mocks/Tokens/LSP8BurnableTester.sol +++ b/contracts/Mocks/Tokens/LSP8BurnableTester.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: MIT +// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.4; @@ -14,6 +14,7 @@ contract LSP8BurnableTester is LSP8Burnable { constructor( string memory name_, string memory symbol_, - address newOwner_ - ) LSP8IdentifiableDigitalAsset(name_, symbol_, newOwner_) {} + address newOwner_, + uint256 tokenIdType_ + ) LSP8IdentifiableDigitalAsset(name_, symbol_, newOwner_, tokenIdType_) {} } diff --git a/contracts/Mocks/Tokens/LSP8CappedSupplyInitTester.sol b/contracts/Mocks/Tokens/LSP8CappedSupplyInitTester.sol index d411dd7f3..ece6a3b52 100644 --- a/contracts/Mocks/Tokens/LSP8CappedSupplyInitTester.sol +++ b/contracts/Mocks/Tokens/LSP8CappedSupplyInitTester.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: MIT +// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.4; @@ -15,12 +15,14 @@ contract LSP8CappedSupplyInitTester is LSP8CappedSupplyInitAbstract { string memory name_, string memory symbol_, address newOwner_, + uint256 tokenIdType_, uint256 tokenSupplyCap_ ) public virtual initializer { LSP8IdentifiableDigitalAssetInitAbstract._initialize( name_, symbol_, - newOwner_ + newOwner_, + tokenIdType_ ); LSP8CappedSupplyInitAbstract._initialize(tokenSupplyCap_); } diff --git a/contracts/Mocks/Tokens/LSP8CappedSupplyTester.sol b/contracts/Mocks/Tokens/LSP8CappedSupplyTester.sol index 015aa92be..5ee6bea0c 100644 --- a/contracts/Mocks/Tokens/LSP8CappedSupplyTester.sol +++ b/contracts/Mocks/Tokens/LSP8CappedSupplyTester.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: MIT +// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.4; @@ -15,9 +15,10 @@ contract LSP8CappedSupplyTester is LSP8CappedSupply { string memory name_, string memory symbol_, address newOwner_, + uint256 tokenIdType_, uint256 tokenSupplyCap_ ) - LSP8IdentifiableDigitalAsset(name_, symbol_, newOwner_) + LSP8IdentifiableDigitalAsset(name_, symbol_, newOwner_, tokenIdType_) LSP8CappedSupply(tokenSupplyCap_) {} diff --git a/contracts/Mocks/Tokens/LSP8CompatibleERC721Tester.sol b/contracts/Mocks/Tokens/LSP8CompatibleERC721Tester.sol index e90af1e27..e713ebe63 100644 --- a/contracts/Mocks/Tokens/LSP8CompatibleERC721Tester.sol +++ b/contracts/Mocks/Tokens/LSP8CompatibleERC721Tester.sol @@ -1,11 +1,8 @@ -// SPDX-License-Identifier: MIT +// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.4; // modules -import { - LSP8IdentifiableDigitalAsset -} from "../../LSP8IdentifiableDigitalAsset/LSP8IdentifiableDigitalAsset.sol"; import { LSP8CompatibleERC721 } from "../../LSP8IdentifiableDigitalAsset/extensions/LSP8CompatibleERC721.sol"; @@ -20,13 +17,14 @@ contract LSP8CompatibleERC721Tester is LSP8CompatibleERC721 { string memory name_, string memory symbol_, address newOwner_, + uint256 tokenIdType_, bytes memory tokenURIValue_ - ) LSP8CompatibleERC721(name_, symbol_, newOwner_) { + ) LSP8CompatibleERC721(name_, symbol_, newOwner_, tokenIdType_) { _setData(_LSP4_METADATA_KEY, tokenURIValue_); } function mint(address to, uint256 tokenId, bytes calldata data) public { - // using allowNonLSP1Recipient=true so we can send to EOA in test + // using force=true so we can send to EOA in test _mint(to, bytes32(tokenId), true, data); } diff --git a/contracts/Mocks/Tokens/LSP8CompatibleERC721TesterInit.sol b/contracts/Mocks/Tokens/LSP8CompatibleERC721TesterInit.sol index edbc0f4bd..51689eb2e 100644 --- a/contracts/Mocks/Tokens/LSP8CompatibleERC721TesterInit.sol +++ b/contracts/Mocks/Tokens/LSP8CompatibleERC721TesterInit.sol @@ -1,11 +1,8 @@ -// SPDX-License-Identifier: MIT +// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.4; // modules -import { - LSP8IdentifiableDigitalAsset -} from "../../LSP8IdentifiableDigitalAsset/LSP8IdentifiableDigitalAsset.sol"; import { LSP8CompatibleERC721InitAbstract } from "../../LSP8IdentifiableDigitalAsset/extensions/LSP8CompatibleERC721InitAbstract.sol"; @@ -27,15 +24,21 @@ contract LSP8CompatibleERC721InitTester is LSP8CompatibleERC721InitAbstract { string memory name_, string memory symbol_, address newOwner_, + uint256 tokenIdType_, bytes memory tokenURIValue_ ) public virtual initializer { - LSP8CompatibleERC721InitAbstract._initialize(name_, symbol_, newOwner_); + LSP8CompatibleERC721InitAbstract._initialize( + name_, + symbol_, + newOwner_, + tokenIdType_ + ); _setData(_LSP4_METADATA_KEY, tokenURIValue_); } function mint(address to, uint256 tokenId, bytes calldata data) public { - // using allowNonLSP1Recipient=true so we can send to EOA in test + // using force=true so we can send to EOA in test _mint(to, bytes32(tokenId), true, data); } diff --git a/contracts/Mocks/Tokens/LSP8EnumerableInitTester.sol b/contracts/Mocks/Tokens/LSP8EnumerableInitTester.sol index bfc536710..50aa38f66 100644 --- a/contracts/Mocks/Tokens/LSP8EnumerableInitTester.sol +++ b/contracts/Mocks/Tokens/LSP8EnumerableInitTester.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: MIT +// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.4; @@ -14,12 +14,14 @@ contract LSP8EnumerableInitTester is LSP8EnumerableInitAbstract { function initialize( string memory name, string memory symbol, - address newOwner + address newOwner, + uint256 tokenIdType ) public virtual initializer { LSP8IdentifiableDigitalAssetInitAbstract._initialize( name, symbol, - newOwner + newOwner, + tokenIdType ); } diff --git a/contracts/Mocks/Tokens/LSP8EnumerableTester.sol b/contracts/Mocks/Tokens/LSP8EnumerableTester.sol index 43b932e74..ef7d25858 100644 --- a/contracts/Mocks/Tokens/LSP8EnumerableTester.sol +++ b/contracts/Mocks/Tokens/LSP8EnumerableTester.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: MIT +// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.4; @@ -14,8 +14,9 @@ contract LSP8EnumerableTester is LSP8Enumerable { constructor( string memory name, string memory symbol, - address newOwner - ) LSP8IdentifiableDigitalAsset(name, symbol, newOwner) {} + address newOwner, + uint256 tokenIdType + ) LSP8IdentifiableDigitalAsset(name, symbol, newOwner, tokenIdType) {} function mint(address to, bytes32 tokenId) public { _mint(to, tokenId, true, "token printer go brrr"); diff --git a/contracts/Mocks/Tokens/LSP8InitTester.sol b/contracts/Mocks/Tokens/LSP8InitTester.sol index 7d9fcb046..9f8e3245d 100644 --- a/contracts/Mocks/Tokens/LSP8InitTester.sol +++ b/contracts/Mocks/Tokens/LSP8InitTester.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: MIT +// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.4; @@ -17,21 +17,23 @@ contract LSP8InitTester is function initialize( string memory name, string memory symbol, - address newOwner + address newOwner, + uint256 tokenIdType ) public initializer { LSP8IdentifiableDigitalAssetInitAbstract._initialize( name, symbol, - newOwner + newOwner, + tokenIdType ); } function mint( address to, bytes32 tokenId, - bool allowNonLSP1Recipient, + bool force, bytes memory data ) public { - _mint(to, tokenId, allowNonLSP1Recipient, data); + _mint(to, tokenId, force, data); } } diff --git a/contracts/Mocks/Tokens/LSP8Tester.sol b/contracts/Mocks/Tokens/LSP8Tester.sol index 0dbfd5138..337cdc2b9 100644 --- a/contracts/Mocks/Tokens/LSP8Tester.sol +++ b/contracts/Mocks/Tokens/LSP8Tester.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: MIT +// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.4; @@ -14,15 +14,16 @@ contract LSP8Tester is LSP8IdentifiableDigitalAsset, LSP8Burnable { constructor( string memory name, string memory symbol, - address newOwner - ) LSP8IdentifiableDigitalAsset(name, symbol, newOwner) {} + address newOwner, + uint256 tokenIdType + ) LSP8IdentifiableDigitalAsset(name, symbol, newOwner, tokenIdType) {} function mint( address to, bytes32 tokenId, - bool allowNonLSP1Recipient, + bool force, bytes memory data ) public { - _mint(to, tokenId, allowNonLSP1Recipient, data); + _mint(to, tokenId, force, data); } } diff --git a/contracts/Mocks/Tokens/RequireCallbackToken.sol b/contracts/Mocks/Tokens/RequireCallbackToken.sol index e91f1f6e1..e2d7dfaaa 100644 --- a/contracts/Mocks/Tokens/RequireCallbackToken.sol +++ b/contracts/Mocks/Tokens/RequireCallbackToken.sol @@ -1,8 +1,10 @@ -// SPDX-License-Identifier: MIT +// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.4; -import "../FallbackExtensions/OnERC721ReceivedExtension.sol"; +import { + OnERC721ReceivedExtension +} from "../../LSP17Extensions/OnERC721ReceivedExtension.sol"; /** * @dev This contract is used only for testing purposes diff --git a/contracts/Mocks/Tokens/TokenReceiverWithoutLSP1WithERC721Received.sol b/contracts/Mocks/Tokens/TokenReceiverWithoutLSP1WithERC721Received.sol index 5b9c97c90..fe714dafd 100644 --- a/contracts/Mocks/Tokens/TokenReceiverWithoutLSP1WithERC721Received.sol +++ b/contracts/Mocks/Tokens/TokenReceiverWithoutLSP1WithERC721Received.sol @@ -6,9 +6,6 @@ import { ERC721Holder } from "@openzeppelin/contracts/token/ERC721/utils/ERC721Holder.sol"; -// constants -import {_INTERFACEID_LSP1} from "../../LSP1UniversalReceiver/LSP1Constants.sol"; - contract TokenReceiverWithoutLSP1WithERC721Received is ERC721Holder { receive() external payable {} diff --git a/contracts/Mocks/Tokens/TokenReceiverWithoutLSP1WithERC721ReceivedInvalid.sol b/contracts/Mocks/Tokens/TokenReceiverWithoutLSP1WithERC721ReceivedInvalid.sol index d353d3128..a06e9eef0 100644 --- a/contracts/Mocks/Tokens/TokenReceiverWithoutLSP1WithERC721ReceivedInvalid.sol +++ b/contracts/Mocks/Tokens/TokenReceiverWithoutLSP1WithERC721ReceivedInvalid.sol @@ -6,9 +6,6 @@ import { ERC721Holder } from "@openzeppelin/contracts/token/ERC721/utils/ERC721Holder.sol"; -// constants -import {_INTERFACEID_LSP1} from "../../LSP1UniversalReceiver/LSP1Constants.sol"; - contract TokenReceiverWithoutLSP1WithERC721ReceivedInvalid is ERC721Holder { receive() external payable {} diff --git a/contracts/Mocks/Tokens/TokenReceiverWithoutLSP1WithERC721ReceivedRevert.sol b/contracts/Mocks/Tokens/TokenReceiverWithoutLSP1WithERC721ReceivedRevert.sol index 07563cde8..b3bbef6dc 100644 --- a/contracts/Mocks/Tokens/TokenReceiverWithoutLSP1WithERC721ReceivedRevert.sol +++ b/contracts/Mocks/Tokens/TokenReceiverWithoutLSP1WithERC721ReceivedRevert.sol @@ -6,9 +6,6 @@ import { ERC721Holder } from "@openzeppelin/contracts/token/ERC721/utils/ERC721Holder.sol"; -// constants -import {_INTERFACEID_LSP1} from "../../LSP1UniversalReceiver/LSP1Constants.sol"; - contract TokenReceiverWithoutLSP1WithERC721ReceivedRevert is ERC721Holder { receive() external payable {} diff --git a/contracts/Mocks/UPWithInstantAcceptOwnership.sol b/contracts/Mocks/UPWithInstantAcceptOwnership.sol index a1e7ed5f3..95b8f258b 100644 --- a/contracts/Mocks/UPWithInstantAcceptOwnership.sol +++ b/contracts/Mocks/UPWithInstantAcceptOwnership.sol @@ -11,9 +11,15 @@ import { import {LSP14Ownable2Step} from "../LSP14Ownable2Step/LSP14Ownable2Step.sol"; // constants -import "../LSP0ERC725Account/LSP0Constants.sol"; -import "../LSP9Vault/LSP9Constants.sol"; -import "../LSP14Ownable2Step/LSP14Constants.sol"; +import { + _TYPEID_LSP0_OwnershipTransferStarted +} from "../LSP0ERC725Account/LSP0Constants.sol"; +import { + _TYPEID_LSP9_OwnershipTransferStarted +} from "../LSP9Vault/LSP9Constants.sol"; +import { + _TYPEID_LSP14_OwnershipTransferStarted +} from "../LSP14Ownable2Step/LSP14Constants.sol"; /** * @dev This contract is used only for testing purposes diff --git a/contracts/Mocks/UniversalReceivers/UniversalReceiverDelegateDataUpdater.sol b/contracts/Mocks/UniversalReceivers/UniversalReceiverDelegateDataUpdater.sol index d118a887a..e3dbe3634 100644 --- a/contracts/Mocks/UniversalReceivers/UniversalReceiverDelegateDataUpdater.sol +++ b/contracts/Mocks/UniversalReceivers/UniversalReceiverDelegateDataUpdater.sol @@ -3,17 +3,13 @@ pragma solidity ^0.8.4; // interfaces import {ILSP6KeyManager} from "../../LSP6KeyManager/ILSP6KeyManager.sol"; -import { - IERC725X -} from "@erc725/smart-contracts/contracts/interfaces/IERC725X.sol"; -import { - IERC725Y -} from "@erc725/smart-contracts/contracts/interfaces/IERC725Y.sol"; import {LSP14Ownable2Step} from "../../LSP14Ownable2Step/LSP14Ownable2Step.sol"; +import { + ILSP1UniversalReceiverDelegate +} from "../../LSP1UniversalReceiver/ILSP1UniversalReceiverDelegate.sol"; + // modules -import {ERC725Y} from "@erc725/smart-contracts/contracts/ERC725Y.sol"; -import {BytesLib} from "solidity-bytes-utils/contracts/BytesLib.sol"; import { ERC165Storage } from "@openzeppelin/contracts/utils/introspection/ERC165Storage.sol"; @@ -23,17 +19,24 @@ import { _TYPEID_LSP7_TOKENSSENDER } from "../../LSP7DigitalAsset/LSP7Constants.sol"; -import {_INTERFACEID_LSP1} from "../../LSP1UniversalReceiver/LSP1Constants.sol"; +import { + _INTERFACEID_LSP1_DELEGATE +} from "../../LSP1UniversalReceiver/LSP1Constants.sol"; -contract UniversalReceiverDelegateDataUpdater is ERC165Storage { +contract UniversalReceiverDelegateDataUpdater is + ERC165Storage, + ILSP1UniversalReceiverDelegate +{ constructor() { - _registerInterface(_INTERFACEID_LSP1); + _registerInterface(_INTERFACEID_LSP1_DELEGATE); } - function universalReceiver( + function universalReceiverDelegate( + address /*sender*/, + uint256 /*value*/, bytes32 typeId, bytes memory /* data */ - ) public virtual returns (bytes memory) { + ) public virtual override returns (bytes memory) { if (typeId == _TYPEID_LSP7_TOKENSSENDER) { address keyManager = LSP14Ownable2Step(msg.sender).owner(); bytes memory setDataPayload = abi.encodeWithSignature( diff --git a/contracts/Mocks/UniversalReceivers/UniversalReceiverDelegateGasConsumer.sol b/contracts/Mocks/UniversalReceivers/UniversalReceiverDelegateGasConsumer.sol index 6f01b3cc3..56727ea6b 100644 --- a/contracts/Mocks/UniversalReceivers/UniversalReceiverDelegateGasConsumer.sol +++ b/contracts/Mocks/UniversalReceivers/UniversalReceiverDelegateGasConsumer.sol @@ -3,27 +3,32 @@ pragma solidity ^0.8.4; // interfaces import { - ILSP1UniversalReceiver -} from "../../LSP1UniversalReceiver/ILSP1UniversalReceiver.sol"; + ILSP1UniversalReceiverDelegate +} from "../../LSP1UniversalReceiver/ILSP1UniversalReceiverDelegate.sol"; import {ERC165} from "@openzeppelin/contracts/utils/introspection/ERC165.sol"; -import {_INTERFACEID_LSP1} from "../../LSP1UniversalReceiver/LSP1Constants.sol"; +import { + _INTERFACEID_LSP1_DELEGATE +} from "../../LSP1UniversalReceiver/LSP1Constants.sol"; /** * @dev This contract is used only for testing purposes */ contract UniversalReceiverDelegateGasConsumer is ERC165, - ILSP1UniversalReceiver + ILSP1UniversalReceiverDelegate { /** - * @inheritdoc ILSP1UniversalReceiver + * @inheritdoc ILSP1UniversalReceiverDelegate * @dev Allows to register arrayKeys and Map of incoming vaults and assets and removing them after being sent * @return result the return value of keyManager's execute function */ - function universalReceiver( - bytes32 /* typeId */, + function universalReceiverDelegate( + address /*sender*/, + uint256 /*value*/, + bytes32 /*typeId*/, bytes memory /* data */ - ) public payable virtual returns (bytes memory) { + ) public virtual override returns (bytes memory) { + // solhint-disable no-empty-blocks for (uint256 i = 0; ; i++) { // nothing } @@ -35,7 +40,7 @@ contract UniversalReceiverDelegateGasConsumer is bytes4 interfaceId ) public view virtual override returns (bool) { return - interfaceId == _INTERFACEID_LSP1 || + interfaceId == _INTERFACEID_LSP1_DELEGATE || super.supportsInterface(interfaceId); } } diff --git a/contracts/Mocks/UniversalReceivers/UniversalReceiverDelegateRevert.sol b/contracts/Mocks/UniversalReceivers/UniversalReceiverDelegateRevert.sol index ffbfdcc3a..e461d9acf 100644 --- a/contracts/Mocks/UniversalReceivers/UniversalReceiverDelegateRevert.sol +++ b/contracts/Mocks/UniversalReceivers/UniversalReceiverDelegateRevert.sol @@ -3,24 +3,31 @@ pragma solidity ^0.8.4; // interfaces import { - ILSP1UniversalReceiver -} from "../../LSP1UniversalReceiver/ILSP1UniversalReceiver.sol"; + ILSP1UniversalReceiverDelegate +} from "../../LSP1UniversalReceiver/ILSP1UniversalReceiverDelegate.sol"; import {ERC165} from "@openzeppelin/contracts/utils/introspection/ERC165.sol"; -import {_INTERFACEID_LSP1} from "../../LSP1UniversalReceiver/LSP1Constants.sol"; +import { + _INTERFACEID_LSP1_DELEGATE +} from "../../LSP1UniversalReceiver/LSP1Constants.sol"; /** * @dev This contract is used only for testing purposes */ -contract UniversalReceiverDelegateRevert is ERC165, ILSP1UniversalReceiver { +contract UniversalReceiverDelegateRevert is + ERC165, + ILSP1UniversalReceiverDelegate +{ /** - * @inheritdoc ILSP1UniversalReceiver + * @inheritdoc ILSP1UniversalReceiverDelegate * @dev Allows to register arrayKeys and Map of incoming vaults and assets and removing them after being sent * @return result the return value of keyManager's execute function */ - function universalReceiver( - bytes32 /* typeId */, + function universalReceiverDelegate( + address /*sender*/, + uint256 /*value*/, + bytes32 /*typeId*/, bytes memory /* data */ - ) public payable virtual returns (bytes memory) { + ) public virtual returns (bytes memory) { revert("I Revert"); } @@ -28,7 +35,7 @@ contract UniversalReceiverDelegateRevert is ERC165, ILSP1UniversalReceiver { bytes4 interfaceId ) public view virtual override returns (bool) { return - interfaceId == _INTERFACEID_LSP1 || + interfaceId == _INTERFACEID_LSP1_DELEGATE || super.supportsInterface(interfaceId); } } diff --git a/contracts/Mocks/UniversalReceivers/UniversalReceiverDelegateTokenReentrant.sol b/contracts/Mocks/UniversalReceivers/UniversalReceiverDelegateTokenReentrant.sol index 24085433c..e42b09760 100644 --- a/contracts/Mocks/UniversalReceivers/UniversalReceiverDelegateTokenReentrant.sol +++ b/contracts/Mocks/UniversalReceivers/UniversalReceiverDelegateTokenReentrant.sol @@ -7,39 +7,46 @@ import { } from "@erc725/smart-contracts/contracts/interfaces/IERC725X.sol"; import {ILSP6KeyManager} from "../../LSP6KeyManager/ILSP6KeyManager.sol"; +import { + ILSP1UniversalReceiverDelegate +} from "../../LSP1UniversalReceiver/ILSP1UniversalReceiverDelegate.sol"; + // modules import {ERC725Y} from "@erc725/smart-contracts/contracts/ERC725Y.sol"; -import {BytesLib} from "solidity-bytes-utils/contracts/BytesLib.sol"; import { ERC165Storage } from "@openzeppelin/contracts/utils/introspection/ERC165Storage.sol"; // constants import { - _TYPEID_LSP7_TOKENSSENDER, _TYPEID_LSP7_TOKENSRECIPIENT } from "../../LSP7DigitalAsset/LSP7Constants.sol"; import { - _TYPEID_LSP8_TOKENSSENDER, _TYPEID_LSP8_TOKENSRECIPIENT } from "../../LSP8IdentifiableDigitalAsset/LSP8Constants.sol"; -import {_INTERFACEID_LSP1} from "../../LSP1UniversalReceiver/LSP1Constants.sol"; +import { + _INTERFACEID_LSP1_DELEGATE +} from "../../LSP1UniversalReceiver/LSP1Constants.sol"; /** * @dev This contract is used only for testing purposes */ -contract UniversalReceiverDelegateTokenReentrant is ERC165Storage { +contract UniversalReceiverDelegateTokenReentrant is + ERC165Storage, + ILSP1UniversalReceiverDelegate +{ constructor() { - _registerInterface(_INTERFACEID_LSP1); + _registerInterface(_INTERFACEID_LSP1_DELEGATE); } - function universalReceiver( + function universalReceiverDelegate( + address sender, + uint256 /*value*/, bytes32 typeId, bytes memory data - ) public payable virtual returns (bytes memory result) { - address sender = address(bytes20(msg.data[msg.data.length - 52:])); + ) public virtual override returns (bytes memory result) { if ( typeId == _TYPEID_LSP7_TOKENSRECIPIENT || typeId == _TYPEID_LSP8_TOKENSRECIPIENT diff --git a/contracts/Mocks/UniversalReceivers/UniversalReceiverDelegateVaultMalicious.sol b/contracts/Mocks/UniversalReceivers/UniversalReceiverDelegateVaultMalicious.sol index 79ae53228..5d943463e 100644 --- a/contracts/Mocks/UniversalReceivers/UniversalReceiverDelegateVaultMalicious.sol +++ b/contracts/Mocks/UniversalReceivers/UniversalReceiverDelegateVaultMalicious.sol @@ -2,21 +2,18 @@ pragma solidity ^0.8.4; // interfaces -import {ILSP6KeyManager} from "../../LSP6KeyManager/ILSP6KeyManager.sol"; -import { - IERC725X -} from "@erc725/smart-contracts/contracts/interfaces/IERC725X.sol"; import { IERC725Y } from "@erc725/smart-contracts/contracts/interfaces/IERC725Y.sol"; -import {LSP14Ownable2Step} from "../../LSP14Ownable2Step/LSP14Ownable2Step.sol"; import { IERC725Y } from "@erc725/smart-contracts/contracts/interfaces/IERC725Y.sol"; +import { + ILSP1UniversalReceiverDelegate +} from "../../LSP1UniversalReceiver/ILSP1UniversalReceiverDelegate.sol"; + // modules -import {ERC725Y} from "@erc725/smart-contracts/contracts/ERC725Y.sol"; -import {BytesLib} from "solidity-bytes-utils/contracts/BytesLib.sol"; import { ERC165Storage } from "@openzeppelin/contracts/utils/introspection/ERC165Storage.sol"; @@ -24,24 +21,34 @@ import {Address} from "@openzeppelin/contracts/utils/Address.sol"; // constants import { - _TYPEID_LSP7_TOKENSSENDER -} from "../../LSP7DigitalAsset/LSP7Constants.sol"; -import "../../LSP1UniversalReceiver/LSP1Constants.sol"; -import "../../LSP6KeyManager/LSP6Constants.sol"; -import "../../LSP17ContractExtension/LSP17Constants.sol"; + _INTERFACEID_LSP1_DELEGATE, + _LSP1_UNIVERSAL_RECEIVER_DELEGATE_PREFIX +} from "../../LSP1UniversalReceiver/LSP1Constants.sol"; +import { + _LSP6KEY_ADDRESSPERMISSIONS_PREFIX +} from "../../LSP6KeyManager/LSP6Constants.sol"; + +import { + _LSP17_EXTENSION_PREFIX +} from "../../LSP17ContractExtension/LSP17Constants.sol"; /** * @dev This contract is used only for testing */ -contract UniversalReceiverDelegateVaultMalicious is ERC165Storage { +contract UniversalReceiverDelegateVaultMalicious is + ERC165Storage, + ILSP1UniversalReceiverDelegate +{ constructor() { - _registerInterface(_INTERFACEID_LSP1); + _registerInterface(_INTERFACEID_LSP1_DELEGATE); } - function universalReceiver( + function universalReceiverDelegate( + address /*sender*/, + uint256 /*value*/, bytes32 typeId, bytes memory data - ) public virtual returns (bytes memory) { + ) public virtual override returns (bytes memory) { if (typeId == keccak256(abi.encodePacked("setData"))) { if (data[0] == 0x00) { IERC725Y(msg.sender).setData( diff --git a/contracts/Mocks/UniversalReceivers/UniversalReceiverDelegateVaultReentrantA.sol b/contracts/Mocks/UniversalReceivers/UniversalReceiverDelegateVaultReentrantA.sol index dbd5bac55..4e468f9c5 100644 --- a/contracts/Mocks/UniversalReceivers/UniversalReceiverDelegateVaultReentrantA.sol +++ b/contracts/Mocks/UniversalReceivers/UniversalReceiverDelegateVaultReentrantA.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: CC0-1.0 +// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.4; // interfaces @@ -6,8 +6,8 @@ import { IERC725Y } from "@erc725/smart-contracts/contracts/interfaces/IERC725Y.sol"; import { - ILSP1UniversalReceiver -} from "../../LSP1UniversalReceiver/ILSP1UniversalReceiver.sol"; + ILSP1UniversalReceiverDelegate +} from "../../LSP1UniversalReceiver/ILSP1UniversalReceiverDelegate.sol"; // modules import { @@ -15,20 +15,27 @@ import { } from "@openzeppelin/contracts/utils/introspection/ERC165Storage.sol"; // constants -import {_INTERFACEID_LSP1} from "../../LSP1UniversalReceiver/LSP1Constants.sol"; +import { + _INTERFACEID_LSP1_DELEGATE +} from "../../LSP1UniversalReceiver/LSP1Constants.sol"; /** * @dev This contract is used only for testing purposes */ -contract UniversalReceiverDelegateVaultReentrantA is ERC165Storage { +contract UniversalReceiverDelegateVaultReentrantA is + ILSP1UniversalReceiverDelegate, + ERC165Storage +{ constructor() { - _registerInterface(_INTERFACEID_LSP1); + _registerInterface(_INTERFACEID_LSP1_DELEGATE); } - function universalReceiver( + function universalReceiverDelegate( + address /*sender*/, + uint256 /*value*/, bytes32 /* typeId */, bytes memory data - ) external returns (bytes memory) { + ) external override returns (bytes memory) { bytes32[] memory keys = new bytes32[](1); bytes[] memory values = new bytes[](1); diff --git a/contracts/Mocks/UniversalReceivers/UniversalReceiverDelegateVaultReentrantB.sol b/contracts/Mocks/UniversalReceivers/UniversalReceiverDelegateVaultReentrantB.sol index 893e6a4ed..114806f0b 100644 --- a/contracts/Mocks/UniversalReceivers/UniversalReceiverDelegateVaultReentrantB.sol +++ b/contracts/Mocks/UniversalReceivers/UniversalReceiverDelegateVaultReentrantB.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: CC0-1.0 +// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.4; // interfaces @@ -6,8 +6,8 @@ import { IERC725Y } from "@erc725/smart-contracts/contracts/interfaces/IERC725Y.sol"; import { - ILSP1UniversalReceiver -} from "../../LSP1UniversalReceiver/ILSP1UniversalReceiver.sol"; + ILSP1UniversalReceiverDelegate +} from "../../LSP1UniversalReceiver/ILSP1UniversalReceiverDelegate.sol"; // modules import { @@ -15,20 +15,27 @@ import { } from "@openzeppelin/contracts/utils/introspection/ERC165Storage.sol"; // constants -import {_INTERFACEID_LSP1} from "../../LSP1UniversalReceiver/LSP1Constants.sol"; +import { + _INTERFACEID_LSP1_DELEGATE +} from "../../LSP1UniversalReceiver/LSP1Constants.sol"; /** * @dev This contract is used only for testing purposes */ -contract UniversalReceiverDelegateVaultReentrantB is ERC165Storage { +contract UniversalReceiverDelegateVaultReentrantB is + ILSP1UniversalReceiverDelegate, + ERC165Storage +{ constructor() { - _registerInterface(_INTERFACEID_LSP1); + _registerInterface(_INTERFACEID_LSP1_DELEGATE); } - function universalReceiver( + function universalReceiverDelegate( + address /*sender*/, + uint256 /*value*/, bytes32 /* typeId */, bytes memory data - ) external returns (bytes memory) { + ) external override returns (bytes memory) { bytes32[] memory keys = new bytes32[](1); bytes[] memory values = new bytes[](1); diff --git a/contracts/Mocks/UniversalReceivers/UniversalReceiverDelegateVaultSetter.sol b/contracts/Mocks/UniversalReceivers/UniversalReceiverDelegateVaultSetter.sol index f448e1b6b..b2f134c70 100644 --- a/contracts/Mocks/UniversalReceivers/UniversalReceiverDelegateVaultSetter.sol +++ b/contracts/Mocks/UniversalReceivers/UniversalReceiverDelegateVaultSetter.sol @@ -1,13 +1,10 @@ -// SPDX-License-Identifier: CC0-1.0 +// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.4; // interfaces import { IERC725Y } from "@erc725/smart-contracts/contracts/interfaces/IERC725Y.sol"; -import { - ILSP1UniversalReceiver -} from "../../LSP1UniversalReceiver/ILSP1UniversalReceiver.sol"; // modules import { @@ -15,17 +12,19 @@ import { } from "@openzeppelin/contracts/utils/introspection/ERC165Storage.sol"; // constants -import {_INTERFACEID_LSP1} from "../../LSP1UniversalReceiver/LSP1Constants.sol"; +import { + _INTERFACEID_LSP1_DELEGATE +} from "../../LSP1UniversalReceiver/LSP1Constants.sol"; /** * @dev This contract is used only for testing purposes */ contract UniversalReceiverDelegateVaultSetter is ERC165Storage { constructor() { - _registerInterface(_INTERFACEID_LSP1); + _registerInterface(_INTERFACEID_LSP1_DELEGATE); } - function universalReceiver( + function universalReceiverDelegate( address vaultadd, bytes32 key, bytes memory value diff --git a/docs/_interface_ids_table.mdx b/docs/_interface_ids_table.mdx index dce5ae1a4..fbfb9856d 100644 --- a/docs/_interface_ids_table.mdx +++ b/docs/_interface_ids_table.mdx @@ -1,19 +1,20 @@ -| Contract | Interface ID | Description | -| :------------------------------- | :----------: | :------------------------------------------------------------------------------------------------------------------------------------------------------- | -| **ERC165** | `0x01ffc9a7` | Standard Interface Detection. | -| **ERC1271** | `0x1626ba7e` | Standard Signature Validation Method for Contracts. | -| **ERC725X** | `0x7545acac` | General executor. | -| **ERC725Y** | `0x629aa694` | General Data key-value store. | -| **LSP0ERC725Account** | `0x24871b3d` | Interface of the [LSP-0-ERC725Account] standard, an account based smart contract that represents an identity on-chain. | -| **LSP1UniversalReceiver** | `0x6bb56a14` | Interface of the LSP1 - Universal Receiver standard, an entry function for a contract to receive arbitrary information. | -| **LSP6KeyManager** | `0x66918867` | Interface of the LSP6 - Key Manager standard, a contract acting as a controller of an ERC725 Account using predfined permissions. | -| **LSP7DigitalAsset** | `0x05519512` | Interface of the LSP7 - Digital Asset standard, a fungible digital asset. | -| **LSP8IdentifiableDigitalAsset** | `0x1ae9ba1f` | Interface of the LSP8 - Identifiable Digital Asset standard, a non-fungible digital asset. | -| **LSP9Vault** | `0x28af17e6` | Interface of LSP9 - Vault standard, a blockchain vault that can hold assets and interact with other smart contracts. | -| **LSP11BasicSocialRecovery** | `0x049a28f1` | Interface of the LSP11 - Basic Social Recovery standard, a contract to recover access control into an account. | -| **LSP14Ownable2Step** | `0x94be5999` | Interface of the LSP14 - Ownable 2-step standard, an extension of the [EIP173] (Ownable) standard with 2-step process to transfer or renounce ownership. | -| **LSP17Extendable** | `0xa918fa6b` | Module to add more functionalities to a contract using extensions. | -| **LSP17Extension** | `0xcee78b40` | Module to create a contract that can act as an extension. | -| **LSP20CallVerification** | `0x1a0eb6a5` | Implementation of a contract calling the verification functions according to LSP20 - Call Verification standard. | -| **LSP20CallVerifier** | `0x480c0ec2` | Interface for the LSP20 Call Verification standard, a set of functions intended to perform verifications on behalf of another contract. | -| **LSP25ExecuteRelayCall** | `0x5ac79908` | | +| Contract | Interface ID | Description | +| :-------------------------------- | :----------: | :------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **ERC165** | `0x01ffc9a7` | Standard Interface Detection. | +| **ERC1271** | `0x1626ba7e` | Standard Signature Validation Method for Contracts. | +| **ERC725X** | `0x7545acac` | General executor. | +| **ERC725Y** | `0x629aa694` | General Data key-value store. | +| **LSP0ERC725Account** | `0x24871b3d` | Interface of the [LSP-0-ERC725Account] standard, an account based smart contract that represents an identity on-chain. | +| **LSP1UniversalReceiver** | `0x6bb56a14` | Interface of the LSP1 - Universal Receiver standard, an entry function for a contract to receive arbitrary information. | +| **LSP1UniversalReceiverDelegate** | `0xa245bbda` | Interface of the LSP1 - Universal Receiver Delegate standard. | +| **LSP6KeyManager** | `0x23f34c62` | Interface of the LSP6 - Key Manager standard, a contract acting as a controller of an ERC725 Account using predfined permissions. | +| **LSP7DigitalAsset** | `0x05519512` | Interface of the LSP7 - Digital Asset standard, a fungible digital asset. | +| **LSP8IdentifiableDigitalAsset** | `0x1ae9ba1f` | Interface of the LSP8 - Identifiable Digital Asset standard, a non-fungible digital asset. | +| **LSP9Vault** | `0x28af17e6` | Interface of LSP9 - Vault standard, a blockchain vault that can hold assets and interact with other smart contracts. | +| **LSP11BasicSocialRecovery** | `0x049a28f1` | Interface of the LSP11 - Basic Social Recovery standard, a contract to recover access control into an account. | +| **LSP14Ownable2Step** | `0x94be5999` | Interface of the LSP14 - Ownable 2-step standard, an extension of the [EIP173] (Ownable) standard with 2-step process to transfer or renounce ownership. | +| **LSP17Extendable** | `0xa918fa6b` | Module to add more functionalities to a contract using extensions. | +| **LSP17Extension** | `0xcee78b40` | Module to create a contract that can act as an extension. | +| **LSP20CallVerification** | `0x1a0eb6a5` | Implementation of a contract calling the verification functions according to LSP20 - Call Verification standard. | +| **LSP20CallVerifier** | `0x0d6ecac7` | Interface for the LSP20 Call Verification standard, a set of functions intended to perform verifications on behalf of another contract. | +| **LSP25ExecuteRelayCall** | `0x5ac79908` | | diff --git a/docs/contracts/ERC725/ERC725.md b/docs/contracts/ERC725/ERC725.md index 3b4365ec0..e739783b1 100644 --- a/docs/contracts/ERC725/ERC725.md +++ b/docs/contracts/ERC725/ERC725.md @@ -134,6 +134,12 @@ Generic executor function to: ::: +:::caution Warning + +- The `msg.value` should not be trusted for any method called with `operationType`: `DELEGATECALL` (4). + +::: + ```solidity function executeBatch( uint256[] operationsType, @@ -564,6 +570,12 @@ Perform low-level staticcall (operation type = 3) ### \_executeDelegateCall +:::caution Warning + +The `msg.value` should not be trusted for any method called with `operationType`: `DELEGATECALL` (4). + +::: + ```solidity function _executeDelegateCall( address target, @@ -716,7 +728,7 @@ event ContractCreated(uint256 indexed operationType, address indexed contractAdd _Deployed new contract at address `contractAddress` and funded with `value` wei (deployed using opcode: `operationType`)._ -Emitted whenever a contract is created +Emitted when a new contract was created and deployed. #### Parameters @@ -1053,3 +1065,47 @@ error ERC725Y_MsgValueDisallowed(); Reverts when sending value to the [`setData`](#setdata) or [`setDataBatch`](#setdatabatch) function.
+ +### OwnableCallerNotTheOwner + +:::note References + +- Specification details: [**ERC-725**](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-725.md#ownablecallernottheowner) +- Solidity implementation: [`ERC725.sol`](https://github.com/ERC725Alliance/ERC725/blob/main/implementations/contracts/ERC725.sol) +- Error signature: `OwnableCallerNotTheOwner(address)` +- Error hash: `0xbf1169c5` + +::: + +```solidity +error OwnableCallerNotTheOwner(address callerAddress); +``` + +Reverts when only the owner is allowed to call the function. + +#### Parameters + +| Name | Type | Description | +| --------------- | :-------: | ---------------------------------------- | +| `callerAddress` | `address` | The address that tried to make the call. | + +
+ +### OwnableCannotSetZeroAddressAsOwner + +:::note References + +- Specification details: [**ERC-725**](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-725.md#ownablecannotsetzeroaddressasowner) +- Solidity implementation: [`ERC725.sol`](https://github.com/ERC725Alliance/ERC725/blob/main/implementations/contracts/ERC725.sol) +- Error signature: `OwnableCannotSetZeroAddressAsOwner()` +- Error hash: `0x1ad8836c` + +::: + +```solidity +error OwnableCannotSetZeroAddressAsOwner(); +``` + +Reverts when trying to set `address(0)` as the contract owner when deploying the contract, initializing it or transferring ownership of the contract. + +
diff --git a/docs/contracts/LSP0ERC725Account/LSP0ERC725Account.md b/docs/contracts/LSP0ERC725Account/LSP0ERC725Account.md index 09d1b6dce..5da23d51a 100644 --- a/docs/contracts/LSP0ERC725Account/LSP0ERC725Account.md +++ b/docs/contracts/LSP0ERC725Account/LSP0ERC725Account.md @@ -216,7 +216,7 @@ _`msg.sender` is accepting ownership of contract: `address(this)`._ Transfer ownership of the contract from the current [`owner()`](#owner) to the [`pendingOwner()`](#pendingowner). Once this function is called: -- The current [`owner()`](#owner) will loose access to the functions restricted to the [`owner()`](#owner) only. +- The current [`owner()`](#owner) will lose access to the functions restricted to the [`owner()`](#owner) only. - The [`pendingOwner()`](#pendingowner) will gain access to the functions restricted to the [`owner()`](#owner) only. @@ -350,6 +350,12 @@ Generic executor function to: ::: +:::caution Warning + +- The `msg.value` should not be trusted for any method called within the batch with `operationType`: `DELEGATECALL` (4). + +::: + ```solidity function executeBatch( uint256[] operationsType, @@ -485,7 +491,7 @@ Get in the ERC725Y storage the bytes data stored at multiple data keys `dataKeys function isValidSignature( bytes32 dataHash, bytes signature -) external view returns (bytes4 magicValue); +) external view returns (bytes4 returnedStatus); ``` _Achieves the goal of [EIP-1271] by validating signatures of smart contracts according to their own logic._ @@ -494,15 +500,15 @@ Handles two cases: 1. If the owner is an EOA, recovers an address from the hash and the signature provided: -- Returns the `magicValue` if the address recovered is the same as the owner, indicating that it was a valid signature. +- Returns the `_ERC1271_SUCCESSVALUE` if the address recovered is the same as the owner, indicating that it was a valid signature. -- If the address is different, it returns the fail value indicating that the signature is not valid. +- If the address is different, it returns the `_ERC1271_FAILVALUE` indicating that the signature is not valid. 2. If the owner is a smart contract, it forwards the call of [`isValidSignature()`](#isvalidsignature) to the owner contract: -- If the contract fails or returns the fail value, the [`isValidSignature()`](#isvalidsignature) on the account returns the fail value, indicating that the signature is not valid. +- If the contract fails or returns the `_ERC1271_FAILVALUE`, the [`isValidSignature()`](#isvalidsignature) on the account returns the `_ERC1271_FAILVALUE`, indicating that the signature is not valid. -- If the [`isValidSignature()`](#isvalidsignature) on the owner returned the `magicValue`, the [`isValidSignature()`](#isvalidsignature) on the account returns the `magicValue`, indicating that it's a valid signature. +- If the [`isValidSignature()`](#isvalidsignature) on the owner returned the `_ERC1271_SUCCESSVALUE`, the [`isValidSignature()`](#isvalidsignature) on the account returns the `_ERC1271_SUCCESSVALUE`, indicating that it's a valid signature. #### Parameters @@ -513,9 +519,9 @@ Handles two cases: #### Returns -| Name | Type | Description | -| ------------ | :------: | ----------------------------------------------------------------- | -| `magicValue` | `bytes4` | A `bytes4` value that indicates if the signature is valid or not. | +| Name | Type | Description | +| ---------------- | :------: | ----------------------------------------------------------------- | +| `returnedStatus` | `bytes4` | A `bytes4` value that indicates if the signature is valid or not. |
@@ -588,7 +594,7 @@ The address that ownership of the contract is transferred to. This address may u :::danger -Leaves the contract without an owner. Once ownership of the contract has been renounced, any functions that are restricted to be called by the owner will be permanently inaccessible, making these functions not callable anymore and unusable. +Leaves the contract without an owner. Once ownership of the contract has been renounced, any functions that are restricted to be called by the owner or an address allowed by the owner will be permanently inaccessible, making these functions not callable anymore and unusable. ::: @@ -945,6 +951,12 @@ Perform low-level staticcall (operation type = 3) ### \_executeDelegateCall +:::caution Warning + +The `msg.value` should not be trusted for any method called with `operationType`: `DELEGATECALL` (4). + +::: + ```solidity function _executeDelegateCall( address target, @@ -1149,6 +1161,19 @@ Returns the extension address stored under the following data key: ### \_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) +); +``` + +::: + ```solidity function _fallbackLSP17Extendable( bytes callData @@ -1156,9 +1181,11 @@ function _fallbackLSP17Extendable( ``` Forwards the call to an extension mapped to a function selector. -Calls [`_getExtension`](#_getextension) to get the address of the extension mapped to the function selector being called on the account. If there is no extension, the `address(0)` will be returned. -Reverts if there is no extension for the function being called, except for the bytes4(0) function selector, which passes even if there is no extension for it. -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` +Calls [`_getExtension`](#_getextension) to get the address of the extension mapped to the function selector being +called on the account. If there is no extension, the `address(0)` will be returned. +Reverts if there is no extension for the function being called, except for the `bytes4(0)` function selector, which passes even if there is no extension for it. +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`](#msg.sender) and 32 bytes of the `msg.value`.
@@ -1171,8 +1198,8 @@ function _verifyCall( ``` Calls [`lsp20VerifyCall`](#lsp20verifycall) function on the logicVerifier. -Reverts in case the value returned does not match the magic value (lsp20VerifyCall selector) -Returns whether a verification after the execution should happen based on the last byte of the magicValue +Reverts in case the value returned does not match the success value (lsp20VerifyCall selector) +Returns whether a verification after the execution should happen based on the last byte of the returnedStatus
@@ -1186,7 +1213,7 @@ function _verifyCallResult( ``` Calls [`lsp20VerifyCallResult`](#lsp20verifycallresult) function on the logicVerifier. -Reverts in case the value returned does not match the magic value (lsp20VerifyCallResult selector) +Reverts in case the value returned does not match the success value (lsp20VerifyCallResult selector)
@@ -1416,7 +1443,7 @@ event UniversalReceiver(address indexed from, uint256 indexed value, bytes32 ind - Data received: `receivedData`.\* -Emitted when the [`universalReceiver`](#universalreceiver) function was called with a specific `typeId` and some `receivedData` s +Emitted when the [`universalReceiver`](#universalreceiver) function was called with a specific `typeId` and some `receivedData` #### Parameters @@ -1460,27 +1487,6 @@ Emitted when receiving native tokens. ## Errors -### CannotTransferOwnershipToSelf - -:::note References - -- Specification details: [**LSP-0-ERC725Account**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-0-ERC725Account.md#cannottransferownershiptoself) -- Solidity implementation: [`LSP0ERC725Account.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP0ERC725Account/LSP0ERC725Account.sol) -- Error signature: `CannotTransferOwnershipToSelf()` -- Error hash: `0x43b248cd` - -::: - -```solidity -error CannotTransferOwnershipToSelf(); -``` - -_Cannot transfer ownership to the address of the contract itself._ - -Reverts when trying to transfer ownership to the `address(this)`. - -
- ### ERC725X_ContractDeploymentFailed :::note References @@ -1665,6 +1671,25 @@ Reverts when the `operationTypeProvided` is none of the default operation types
+### ERC725Y_DataKeysValuesEmptyArray + +:::note References + +- Specification details: [**LSP-0-ERC725Account**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-0-ERC725Account.md#erc725y_datakeysvaluesemptyarray) +- Solidity implementation: [`LSP0ERC725Account.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP0ERC725Account/LSP0ERC725Account.sol) +- Error signature: `ERC725Y_DataKeysValuesEmptyArray()` +- Error hash: `0x97da5f95` + +::: + +```solidity +error ERC725Y_DataKeysValuesEmptyArray(); +``` + +Reverts when one of the array parameter provided to [`setDataBatch`](#setdatabatch) function is an empty array. + +
+ ### ERC725Y_DataKeysValuesLengthMismatch :::note References @@ -1684,6 +1709,52 @@ Reverts when there is not the same number of elements in the `datakeys` and `dat
+### LSP14CallerNotPendingOwner + +:::note References + +- Specification details: [**LSP-0-ERC725Account**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-0-ERC725Account.md#lsp14callernotpendingowner) +- Solidity implementation: [`LSP0ERC725Account.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP0ERC725Account/LSP0ERC725Account.sol) +- Error signature: `LSP14CallerNotPendingOwner(address)` +- Error hash: `0x451e4528` + +::: + +```solidity +error LSP14CallerNotPendingOwner(address caller); +``` + +Reverts when the `caller` that is trying to accept ownership of the contract is not the pending owner. + +#### Parameters + +| Name | Type | Description | +| -------- | :-------: | ------------------------------------------- | +| `caller` | `address` | The address that tried to accept ownership. | + +
+ +### LSP14CannotTransferOwnershipToSelf + +:::note References + +- Specification details: [**LSP-0-ERC725Account**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-0-ERC725Account.md#lsp14cannottransferownershiptoself) +- Solidity implementation: [`LSP0ERC725Account.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP0ERC725Account/LSP0ERC725Account.sol) +- Error signature: `LSP14CannotTransferOwnershipToSelf()` +- Error hash: `0xe052a6f8` + +::: + +```solidity +error LSP14CannotTransferOwnershipToSelf(); +``` + +_Cannot transfer ownership to the address of the contract itself._ + +Reverts when trying to transfer ownership to the `address(this)`. + +
+ ### LSP14MustAcceptOwnershipInSeparateTransaction :::note References @@ -1705,47 +1776,53 @@ Reverts when pending owner accept ownership in the same transaction of transferr
-### LSP20CallingVerifierFailed +### LSP14NotInRenounceOwnershipInterval :::note References -- Specification details: [**LSP-0-ERC725Account**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-0-ERC725Account.md#lsp20callingverifierfailed) +- Specification details: [**LSP-0-ERC725Account**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-0-ERC725Account.md#lsp14notinrenounceownershipinterval) - Solidity implementation: [`LSP0ERC725Account.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP0ERC725Account/LSP0ERC725Account.sol) -- Error signature: `LSP20CallingVerifierFailed(bool)` -- Error hash: `0x8c6a8ae3` +- Error signature: `LSP14NotInRenounceOwnershipInterval(uint256,uint256)` +- Error hash: `0x1b080942` ::: ```solidity -error LSP20CallingVerifierFailed(bool postCall); +error LSP14NotInRenounceOwnershipInterval( + uint256 renounceOwnershipStart, + uint256 renounceOwnershipEnd +); ``` -reverts when the call to the owner fail with no revert reason +_Cannot confirm ownership renouncement yet. The ownership renouncement is allowed from: `renounceOwnershipStart` until: `renounceOwnershipEnd`._ + +Reverts when trying to renounce ownership before the initial confirmation delay. #### Parameters -| Name | Type | Description | -| ---------- | :----: | ---------------------------------------------------- | -| `postCall` | `bool` | True if the execution call was done, False otherwise | +| Name | Type | Description | +| ------------------------ | :-------: | ----------------------------------------------------------------------- | +| `renounceOwnershipStart` | `uint256` | The start timestamp when one can confirm the renouncement of ownership. | +| `renounceOwnershipEnd` | `uint256` | The end timestamp when one can confirm the renouncement of ownership. |
-### LSP20InvalidMagicValue +### LSP20CallVerificationFailed :::note References -- Specification details: [**LSP-0-ERC725Account**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-0-ERC725Account.md#lsp20invalidmagicvalue) +- Specification details: [**LSP-0-ERC725Account**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-0-ERC725Account.md#lsp20callverificationfailed) - Solidity implementation: [`LSP0ERC725Account.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP0ERC725Account/LSP0ERC725Account.sol) -- Error signature: `LSP20InvalidMagicValue(bool,bytes)` -- Error hash: `0xd088ec40` +- Error signature: `LSP20CallVerificationFailed(bool,bytes)` +- Error hash: `0x00c28d0f` ::: ```solidity -error LSP20InvalidMagicValue(bool postCall, bytes returnedData); +error LSP20CallVerificationFailed(bool postCall, bytes returnedData); ``` -reverts when the call to the owner does not return the magic value +reverts when the call to the owner does not return the LSP20 success value #### Parameters @@ -1756,58 +1833,77 @@ reverts when the call to the owner does not return the magic value
-### NoExtensionFoundForFunctionSelector +### LSP20CallingVerifierFailed :::note References -- Specification details: [**LSP-0-ERC725Account**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-0-ERC725Account.md#noextensionfoundforfunctionselector) +- Specification details: [**LSP-0-ERC725Account**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-0-ERC725Account.md#lsp20callingverifierfailed) - Solidity implementation: [`LSP0ERC725Account.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP0ERC725Account/LSP0ERC725Account.sol) -- Error signature: `NoExtensionFoundForFunctionSelector(bytes4)` -- Error hash: `0xbb370b2b` +- Error signature: `LSP20CallingVerifierFailed(bool)` +- Error hash: `0x8c6a8ae3` ::: ```solidity -error NoExtensionFoundForFunctionSelector(bytes4 functionSelector); +error LSP20CallingVerifierFailed(bool postCall); ``` -reverts when there is no extension for the function selector being called with +reverts when the call to the owner fail with no revert reason #### Parameters -| Name | Type | Description | -| ------------------ | :------: | ----------- | -| `functionSelector` | `bytes4` | - | +| Name | Type | Description | +| ---------- | :----: | ---------------------------------------------------- | +| `postCall` | `bool` | True if the execution call was done, False otherwise |
-### NotInRenounceOwnershipInterval +### LSP20EOACannotVerifyCall :::note References -- Specification details: [**LSP-0-ERC725Account**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-0-ERC725Account.md#notinrenounceownershipinterval) +- Specification details: [**LSP-0-ERC725Account**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-0-ERC725Account.md#lsp20eoacannotverifycall) - Solidity implementation: [`LSP0ERC725Account.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP0ERC725Account/LSP0ERC725Account.sol) -- Error signature: `NotInRenounceOwnershipInterval(uint256,uint256)` -- Error hash: `0x8b9bf507` +- Error signature: `LSP20EOACannotVerifyCall(address)` +- Error hash: `0x0c392301` ::: ```solidity -error NotInRenounceOwnershipInterval( - uint256 renounceOwnershipStart, - uint256 renounceOwnershipEnd -); +error LSP20EOACannotVerifyCall(address logicVerifier); ``` -_Cannot confirm ownership renouncement yet. The ownership renouncement is allowed from: `renounceOwnershipStart` until: `renounceOwnershipEnd`._ +Reverts when the logic verifier is an Externally Owned Account (EOA) that cannot return the LSP20 success value. -Reverts when trying to renounce ownership before the initial confirmation delay. +#### Parameters + +| Name | Type | Description | +| --------------- | :-------: | --------------------------------- | +| `logicVerifier` | `address` | The address of the logic verifier | + +
+ +### NoExtensionFoundForFunctionSelector + +:::note References + +- Specification details: [**LSP-0-ERC725Account**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-0-ERC725Account.md#noextensionfoundforfunctionselector) +- Solidity implementation: [`LSP0ERC725Account.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP0ERC725Account/LSP0ERC725Account.sol) +- Error signature: `NoExtensionFoundForFunctionSelector(bytes4)` +- Error hash: `0xbb370b2b` + +::: + +```solidity +error NoExtensionFoundForFunctionSelector(bytes4 functionSelector); +``` + +reverts when there is no extension for the function selector being called with #### Parameters -| Name | Type | Description | -| ------------------------ | :-------: | ----------------------------------------------------------------------- | -| `renounceOwnershipStart` | `uint256` | The start timestamp when one can confirm the renouncement of ownership. | -| `renounceOwnershipEnd` | `uint256` | The end timestamp when one can confirm the renouncement of ownership. | +| Name | Type | Description | +| ------------------ | :------: | ----------- | +| `functionSelector` | `bytes4` | - |
diff --git a/docs/contracts/LSP11BasicSocialRecovery/LSP11BasicSocialRecovery.md b/docs/contracts/LSP11BasicSocialRecovery/LSP11BasicSocialRecovery.md index afeac358b..ce65e86c1 100644 --- a/docs/contracts/LSP11BasicSocialRecovery/LSP11BasicSocialRecovery.md +++ b/docs/contracts/LSP11BasicSocialRecovery/LSP11BasicSocialRecovery.md @@ -866,6 +866,50 @@ reverts when removing a guardian and the threshold is equal to the number of gua
+### OwnableCallerNotTheOwner + +:::note References + +- Specification details: [**LSP-11-BasicSocialRecovery**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-11-BasicSocialRecovery.md#ownablecallernottheowner) +- Solidity implementation: [`LSP11BasicSocialRecovery.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP11BasicSocialRecovery/LSP11BasicSocialRecovery.sol) +- Error signature: `OwnableCallerNotTheOwner(address)` +- Error hash: `0xbf1169c5` + +::: + +```solidity +error OwnableCallerNotTheOwner(address callerAddress); +``` + +Reverts when only the owner is allowed to call the function. + +#### Parameters + +| Name | Type | Description | +| --------------- | :-------: | ---------------------------------------- | +| `callerAddress` | `address` | The address that tried to make the call. | + +
+ +### OwnableCannotSetZeroAddressAsOwner + +:::note References + +- Specification details: [**LSP-11-BasicSocialRecovery**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-11-BasicSocialRecovery.md#ownablecannotsetzeroaddressasowner) +- Solidity implementation: [`LSP11BasicSocialRecovery.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP11BasicSocialRecovery/LSP11BasicSocialRecovery.sol) +- Error signature: `OwnableCannotSetZeroAddressAsOwner()` +- Error hash: `0x1ad8836c` + +::: + +```solidity +error OwnableCannotSetZeroAddressAsOwner(); +``` + +Reverts when trying to set `address(0)` as the contract owner when deploying the contract, initializing it or transferring ownership of the contract. + +
+ ### SecretHashCannotBeZero :::note References diff --git a/docs/contracts/LSP14Ownable2Step/LSP14Ownable2Step.md b/docs/contracts/LSP14Ownable2Step/LSP14Ownable2Step.md index a6e0464f0..a305eab1b 100644 --- a/docs/contracts/LSP14Ownable2Step/LSP14Ownable2Step.md +++ b/docs/contracts/LSP14Ownable2Step/LSP14Ownable2Step.md @@ -98,7 +98,7 @@ _`msg.sender` is accepting ownership of contract: `address(this)`._ Transfer ownership of the contract from the current [`owner()`](#owner) to the [`pendingOwner()`](#pendingowner). Once this function is called: -- The current [`owner()`](#owner) will loose access to the functions restricted to the [`owner()`](#owner) only. +- The current [`owner()`](#owner) will lose access to the functions restricted to the [`owner()`](#owner) only. - The [`pendingOwner()`](#pendingowner) will gain access to the functions restricted to the [`owner()`](#owner) only. @@ -403,19 +403,44 @@ Emitted when starting the [`renounceOwnership(..)`](#renounceownership) 2-step p ## Errors -### CannotTransferOwnershipToSelf +### LSP14CallerNotPendingOwner :::note References -- Specification details: [**LSP-14-Ownable2Step**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-14-Ownable2Step.md#cannottransferownershiptoself) +- Specification details: [**LSP-14-Ownable2Step**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-14-Ownable2Step.md#lsp14callernotpendingowner) - Solidity implementation: [`LSP14Ownable2Step.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP14Ownable2Step/LSP14Ownable2Step.sol) -- Error signature: `CannotTransferOwnershipToSelf()` -- Error hash: `0x43b248cd` +- Error signature: `LSP14CallerNotPendingOwner(address)` +- Error hash: `0x451e4528` ::: ```solidity -error CannotTransferOwnershipToSelf(); +error LSP14CallerNotPendingOwner(address caller); +``` + +Reverts when the `caller` that is trying to accept ownership of the contract is not the pending owner. + +#### Parameters + +| Name | Type | Description | +| -------- | :-------: | ------------------------------------------- | +| `caller` | `address` | The address that tried to accept ownership. | + +
+ +### LSP14CannotTransferOwnershipToSelf + +:::note References + +- Specification details: [**LSP-14-Ownable2Step**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-14-Ownable2Step.md#lsp14cannottransferownershiptoself) +- Solidity implementation: [`LSP14Ownable2Step.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP14Ownable2Step/LSP14Ownable2Step.sol) +- Error signature: `LSP14CannotTransferOwnershipToSelf()` +- Error hash: `0xe052a6f8` + +::: + +```solidity +error LSP14CannotTransferOwnershipToSelf(); ``` _Cannot transfer ownership to the address of the contract itself._ @@ -445,19 +470,19 @@ Reverts when pending owner accept ownership in the same transaction of transferr
-### NotInRenounceOwnershipInterval +### LSP14NotInRenounceOwnershipInterval :::note References -- Specification details: [**LSP-14-Ownable2Step**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-14-Ownable2Step.md#notinrenounceownershipinterval) +- Specification details: [**LSP-14-Ownable2Step**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-14-Ownable2Step.md#lsp14notinrenounceownershipinterval) - Solidity implementation: [`LSP14Ownable2Step.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP14Ownable2Step/LSP14Ownable2Step.sol) -- Error signature: `NotInRenounceOwnershipInterval(uint256,uint256)` -- Error hash: `0x8b9bf507` +- Error signature: `LSP14NotInRenounceOwnershipInterval(uint256,uint256)` +- Error hash: `0x1b080942` ::: ```solidity -error NotInRenounceOwnershipInterval( +error LSP14NotInRenounceOwnershipInterval( uint256 renounceOwnershipStart, uint256 renounceOwnershipEnd ); @@ -475,3 +500,28 @@ Reverts when trying to renounce ownership before the initial confirmation delay. | `renounceOwnershipEnd` | `uint256` | The end timestamp when one can confirm the renouncement of ownership. |
+ +### OwnableCallerNotTheOwner + +:::note References + +- Specification details: [**LSP-14-Ownable2Step**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-14-Ownable2Step.md#ownablecallernottheowner) +- Solidity implementation: [`LSP14Ownable2Step.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP14Ownable2Step/LSP14Ownable2Step.sol) +- Error signature: `OwnableCallerNotTheOwner(address)` +- Error hash: `0xbf1169c5` + +::: + +```solidity +error OwnableCallerNotTheOwner(address callerAddress); +``` + +Reverts when only the owner is allowed to call the function. + +#### Parameters + +| Name | Type | Description | +| --------------- | :-------: | ---------------------------------------- | +| `callerAddress` | `address` | The address that tried to make the call. | + +
diff --git a/docs/contracts/LSP16UniversalFactory/LSP16UniversalFactory.md b/docs/contracts/LSP16UniversalFactory/LSP16UniversalFactory.md index 48044bfd4..2478c481f 100644 --- a/docs/contracts/LSP16UniversalFactory/LSP16UniversalFactory.md +++ b/docs/contracts/LSP16UniversalFactory/LSP16UniversalFactory.md @@ -129,6 +129,8 @@ function deployCreate2( ) external payable returns (address); ``` +_Contract deployed. Salt, `providedSalt`, used._ + Deploys a contract using the CREATE2 opcode. The address where the contract will be deployed can be known in advance via the [`computeAddress`](#computeaddress) function. This function deploys contracts without initialization (external call after deployment). The `providedSalt` parameter is not used directly as the salt by the CREATE2 opcode. Instead, it is hashed with keccak256: `keccak256(abi.encodePacked(false, providedSalt))`. See [`generateSalt`](#generatesalt) function for more details. Using the same `byteCode` and `providedSalt` multiple times will revert, as the contract cannot be deployed twice at the same address. If the constructor of the contract to deploy is payable, value can be sent to this function to fund the created contract. However, sending value to this function while the constructor is not payable will result in a revert. #### Parameters @@ -167,6 +169,8 @@ function deployCreate2AndInitialize( ) external payable returns (address); ``` +_Contract deployed. Salt, `providedSalt`, used._ + Deploys a contract using the CREATE2 opcode. The address where the contract will be deployed can be known in advance via the [`computeAddress`](#computeaddress) function. This function deploys contracts with initialization (external call after deployment). The `providedSalt` parameter is not used directly as the salt by the CREATE2 opcode. Instead, it is hashed with keccak256: `keccak256(abi.encodePacked(true, initializeCalldata, providedSalt))`. See [`generateSalt`](#generatesalt) function for more details. Using the same `byteCode`, `providedSalt` and `initializeCalldata` multiple times will revert, as the contract cannot be deployed twice at the same address. If the constructor or the initialize function of the contract to deploy is payable, value can be sent along with the deployment/initialization to fund the created contract. However, sending value to this function while the constructor/initialize function is not payable will result in a revert. Will revert if the `msg.value` sent to the function is not equal to the sum of `constructorMsgValue` and `initializeCalldataMsgValue`. #### Parameters @@ -205,6 +209,8 @@ function deployERC1167Proxy( ) external nonpayable returns (address); ``` +_Proxy deployed. Salt, `providedSalt`, used._ + Deploys an ERC1167 minimal proxy contract using the CREATE2 opcode. The address where the contract will be deployed can be known in advance via the [`computeERC1167Address`](#computeerc1167address) function. This function deploys contracts without initialization (external call after deployment). The `providedSalt` parameter is not used directly as the salt by the CREATE2 opcode. Instead, it is hashed with keccak256: `keccak256(abi.encodePacked(false, providedSalt))`. See [`generateSalt`](#generatesalt) function for more details. See [`generateSalt`](#generatesalt) function for more details. Using the same `implementationContract` and `providedSalt` multiple times will revert, as the contract cannot be deployed twice at the same address. Sending value to the contract created is not possible since the constructor of the ERC1167 minimal proxy is not payable. #### Parameters @@ -241,6 +247,8 @@ function deployERC1167ProxyAndInitialize( ) external payable returns (address); ``` +_Proxy deployed & initialized. Salt, `providedSalt`, used._ + Deploys an ERC1167 minimal proxy contract using the CREATE2 opcode. The address where the contract will be deployed can be known in advance via the [`computeERC1167Address`](#computeerc1167address) function. This function deploys contracts with initialization (external call after deployment). The `providedSalt` parameter is not used directly as the salt by the CREATE2 opcode. Instead, it is hashed with keccak256: `keccak256(abi.encodePacked(true, initializeCalldata, providedSalt))`. See [`generateSalt`](#generatesalt) function for more details. Using the same `implementationContract`, `providedSalt` and `initializeCalldata` multiple times will revert, as the contract cannot be deployed twice at the same address. If the initialize function of the contract to deploy is payable, value can be sent along to fund the created contract while initializing. However, sending value to this function while the initialize function is not payable will result in a revert. #### Parameters @@ -280,17 +288,37 @@ function generateSalt( Generates the salt used to deploy the contract by hashing the following parameters (concatenated together) with keccak256: -- the `providedSalt` +1. the `providedSalt` -- the `initializable` boolean +2. the `initializable` boolean -- the `initializeCalldata`, only if the contract is initializable (the `initializable` boolean is set to `true`) The `providedSalt` parameter is not used directly as the salt by the CREATE2 opcode. Instead, it is used along with these parameters: +3. the `initializeCalldata`, only if the contract is initializable (the `initializable` boolean is set to `true`) -- `initializable` boolean +- The `providedSalt` parameter is not used directly as the salt by the CREATE2 opcode. Instead, it is used along with these parameters: + +1. `initializable` boolean + +2. `initializeCalldata` (when the contract is initializable and `initializable` is set to `true`). + +- This approach ensures that in order to reproduce an initializable contract at the same address on another chain, not only the `providedSalt` is required to be the same, but also the initialize parameters within the `initializeCalldata` must also be the same. This maintains consistent deployment behaviour. Users are required to initialize contracts with the same parameters across different chains to ensure contracts are deployed at the same address across different chains. + +1. Example (for initializable contracts) -- `initializeCalldata` (when the contract is initializable and `initializable` is set to `true`). These three parameters are concatenated together and hashed to generate the final salt for CREATE2. This approach ensures that in order to reproduce an initializable contract at the same address on another chain, not only the `providedSalt` is required to be the same, but also the initialize parameters within the `initializeCalldata` must also be the same. This maintains consistent deployment behaviour. Users are required to initialize contracts with the same parameters across different chains to ensure contracts are deployed at the same address across different chains. ----------- Example (for initializable contracts) For an existing contract A on chain 1 owned by X, to replicate the same contract at the same address with the same owner X on chain 2, the salt used to generate the address should include the initializeCalldata that assigns X as the owner of contract A. For instance, if another user, Y, tries to deploy the contract at the same address on chain 2 using the same providedSalt, but with a different initializeCalldata to make Y the owner instead of X, the generated address would be different, preventing Y from deploying the contract with different ownership at the same address. ----------- However, for non-initializable contracts, if the constructor has arguments that specify the deployment behavior, they will be included in the bytecode. Any change in the constructor arguments will lead to a different contract's bytecode which will result in a different address on other chains. ----------- Example (for non-initializable contracts) If a contract is deployed with specific constructor arguments on chain 1, these arguments are embedded within the bytecode. For instance, if contract B is deployed with a specific `tokenName` and `tokenSymbol` on chain 1, and a user wants to deploy the same contract with the same `tokenName` and `tokenSymbol` on chain 2, they must use the same constructor arguments to produce the same bytecode. This ensures that the same deployment behaviour is maintained across different chains, as long as the same bytecode is used. If another user Z, tries to deploy the same contract B at the same address on chain 2 using the same `providedSalt` but different constructor arguments (a different `tokenName` and/or `tokenSymbol`), the generated address will be different. This prevents user Z from deploying the contract with different constructor arguments at the same address on chain +- For an existing contract A on chain 1 owned by X, to replicate the same contract at the same address with the same owner X on chain 2, the salt used to generate the address should include the initializeCalldata that assigns X as the owner of contract A. -2. ----------- The providedSalt was hashed to produce the salt used by CREATE2 opcode to prevent users from deploying initializable contracts using non-initializable functions such as [`deployCreate2`](#deploycreate2) without having the initialization call. In other words, if the providedSalt was not hashed and was used as it is as the salt by the CREATE2 opcode, malicious users can check the generated salt used for the already deployed initializable contract on chain 1, and deploy the contract from [`deployCreate2`](#deploycreate2) function on chain 2, with passing the generated salt of the deployed contract as providedSalt that will produce the same address but without the initialization, where the malicious user can initialize after. +- For instance, if another user, Y, tries to deploy the contract at the same address on chain 2 using the same providedSalt, but with a different initializeCalldata to make Y the owner instead of X, the generated address would be different, preventing Y from deploying the contract with different ownership at the same address. + +- However, for non-initializable contracts, if the constructor has arguments that specify the deployment behavior, they will be included in the bytecode. Any change in the constructor arguments will lead to a different contract's bytecode which will result in a different address on other chains. + +2. Example (for non-initializable contracts) + +- If a contract is deployed with specific constructor arguments on chain 1, these arguments are embedded within the bytecode. For instance, if contract B is deployed with a specific `tokenName` and `tokenSymbol` on chain 1, and a user wants to deploy the same contract with the same `tokenName` and `tokenSymbol` on chain 2, they must use the same constructor arguments to produce the same bytecode. This ensures that the same deployment behaviour is maintained across different chains, as long as the same bytecode is used. + +- If another user Z, tries to deploy the same contract B at the same address on chain 2 using the same `providedSalt` but different constructor arguments (a different `tokenName` and/or `tokenSymbol`), the generated address will be different. This prevents user Z from deploying the contract with different constructor arguments at the same address on chain 2. + +- The providedSalt was hashed to produce the salt used by CREATE2 opcode to prevent users from deploying initializable contracts using non-initializable functions such as [`deployCreate2`](#deploycreate2) without having the initialization call. + +- In other words, if the providedSalt was not hashed and was used as it is as the salt by the CREATE2 opcode, malicious users can check the generated salt used for the already deployed initializable contract on chain 1, and deploy the contract from [`deployCreate2`](#deploycreate2) function on chain 2, with passing the generated salt of the deployed contract as providedSalt that will produce the same address but without the initialization, where the malicious user can initialize after. #### Parameters @@ -339,20 +367,22 @@ Bubble the revert reason if present, revert with `ContractInitializationFailed` ::: ```solidity -event ContractCreated(address indexed contractCreated, bytes32 indexed providedSalt, bytes32 generatedSalt, bool indexed initialized, bytes initializeCalldata); +event ContractCreated(address indexed createdContract, bytes32 indexed providedSalt, bytes32 generatedSalt, bool indexed initialized, bytes initializeCalldata); ``` -Emitted when a new contract was created and deployed. +_Contract created. Contract address: `createdContract`._ + +Emitted whenever a contract is created. #### Parameters -| Name | Type | Description | -| ------------------------------- | :-------: | -------------------------------------------------------------------------------------------------------------------------------------------------- | -| `contractCreated` **`indexed`** | `address` | The address of the contract created | -| `providedSalt` **`indexed`** | `bytes32` | The salt provided by the deployer, which will be used to generate the final salt that will be used by the `CREATE2` opcode for contract deployment | -| `generatedSalt` | `bytes32` | The salt used by the `CREATE2` opcode for contract deployment | -| `initialized` **`indexed`** | `bool` | The Boolean that specifies if the contract must be initialized or not | -| `initializeCalldata` | `bytes` | The bytes provided as initializeCalldata (Empty string when `initialized` is set to false) | +| Name | Type | Description | +| ------------------------------- | :-------: | --------------------------------------------------------------------------------------------------------------------------------------------------- | +| `createdContract` **`indexed`** | `address` | The address of the contract created. | +| `providedSalt` **`indexed`** | `bytes32` | The salt provided by the deployer, which will be used to generate the final salt that will be used by the `CREATE2` opcode for contract deployment. | +| `generatedSalt` | `bytes32` | The salt used by the `CREATE2` opcode for contract deployment. | +| `initialized` **`indexed`** | `bool` | The Boolean that specifies if the contract must be initialized or not. | +| `initializeCalldata` | `bytes` | The bytes provided as initializeCalldata (Empty string when `initialized` is set to false). |
@@ -373,7 +403,9 @@ Emitted when a new contract was created and deployed. error ContractInitializationFailed(); ``` -Reverts when there is no revert reason bubbled up by the contract created when initializing +_Couldn't initialize the contract._ + +Reverts when there is no revert reason bubbled up by the created contract when initializing
@@ -392,6 +424,6 @@ Reverts when there is no revert reason bubbled up by the contract created when i error InvalidValueSum(); ``` -Reverts when msg.value sent to [`deployCreate2AndInitialize`](#deploycreate2andinitialize) function is not equal to the sum of the `initializeCalldataMsgValue` and `constructorMsgValue` +Reverts when `msg.value` sent to [`deployCreate2AndInitialize(..)`](#deploycreate2andinitialize) function is not equal to the sum of the `initializeCalldataMsgValue` and `constructorMsgValue`
diff --git a/docs/contracts/LSP17ContractExtension/LSP17Extendable.md b/docs/contracts/LSP17ContractExtension/LSP17Extendable.md index 2a5ab320f..1c1702416 100644 --- a/docs/contracts/LSP17ContractExtension/LSP17Extendable.md +++ b/docs/contracts/LSP17ContractExtension/LSP17Extendable.md @@ -93,6 +93,19 @@ Up to the implementor contract to return an extension based on a function select ### \_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) +); +``` + +::: + ```solidity function _fallbackLSP17Extendable( bytes callData @@ -101,13 +114,9 @@ function _fallbackLSP17Extendable( Forwards the call to an extension mapped to a function selector. Calls [`_getExtension`](#_getextension) to get the address of the extension mapped to the function selector being -called on the account. If there is no extension, the address(0) will be returned. +called on the account. If there is no extension, the `address(0)` will be returned. Reverts if there is no extension for the function being called. If there is an extension for the function selector being called, it calls the extension with the -CALL opcode, passing the [`msg.data`](#msg.data) appended with the 20 bytes of the [`msg.sender`](#msg.sender) and -32 bytes of the [`msg.value`](#msg.value) -Because the function uses assembly [`return()/revert()`](#return) to terminate the call, it cannot be -called before other codes in fallback(). -Otherwise, the codes after \_fallbackLSP17Extendable() may never be reached. +`CALL` opcode, passing the `msg.data` appended with the 20 bytes of the [`msg.sender`](#msg.sender) and 32 bytes of the `msg.value`.
diff --git a/docs/contracts/LSP17ContractExtension/LSP17Extension.md b/docs/contracts/LSP17ContractExtension/LSP17Extension.md index 89aeee841..fd53b38f1 100644 --- a/docs/contracts/LSP17ContractExtension/LSP17Extension.md +++ b/docs/contracts/LSP17ContractExtension/LSP17Extension.md @@ -66,8 +66,8 @@ Internal functions cannot be called externally, whether from other smart contrac function _extendableMsgData() internal view returns (bytes); ``` -Returns the original msg.data passed to the extendable contract -without the appended msg.sender and msg.value +Returns the original `msg.data` passed to the extendable contract +without the appended `msg.sender` and `msg.value`.
@@ -77,7 +77,7 @@ without the appended msg.sender and msg.value function _extendableMsgSender() internal view returns (address); ``` -Returns the original msg.sender calling the extendable contract +Returns the original `msg.sender` calling the extendable contract.
@@ -87,6 +87,6 @@ Returns the original msg.sender calling the extendable contract function _extendableMsgValue() internal view returns (uint256); ``` -Returns the original msg.value sent to the extendable contract +Returns the original `msg.value` sent to the extendable contract.
diff --git a/docs/contracts/LSP17Extensions/Extension4337.md b/docs/contracts/LSP17Extensions/Extension4337.md new file mode 100644 index 000000000..d54e97a2e --- /dev/null +++ b/docs/contracts/LSP17Extensions/Extension4337.md @@ -0,0 +1,173 @@ + + + +# Extension4337 + +:::info Standard Specifications + +[`LSP-17-Extensions`](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-17-Extensions.md) + +::: +:::info Solidity implementation + +[`Extension4337.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP17Extensions/Extension4337.sol) + +::: + +## Public Methods + +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. + +### constructor + +:::note References + +- Specification details: [**LSP-17-Extensions**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-17-Extensions.md#constructor) +- Solidity implementation: [`Extension4337.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP17Extensions/Extension4337.sol) + +::: + +```solidity +constructor(address entryPoint_); +``` + +#### Parameters + +| Name | Type | Description | +| ------------- | :-------: | ----------- | +| `entryPoint_` | `address` | - | + +
+ +### entryPoint + +:::note References + +- Specification details: [**LSP-17-Extensions**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-17-Extensions.md#entrypoint) +- Solidity implementation: [`Extension4337.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP17Extensions/Extension4337.sol) +- Function signature: `entryPoint()` +- Function selector: `0xb0d691fe` + +::: + +```solidity +function entryPoint() external view returns (address); +``` + +Get the address of the Entry Point contract that will execute the user operation. + +#### Returns + +| Name | Type | Description | +| ---- | :-------: | -------------------------------------- | +| `0` | `address` | The address of the EntryPoint contract | + +
+ +### supportsInterface + +:::note References + +- Specification details: [**LSP-17-Extensions**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-17-Extensions.md#supportsinterface) +- Solidity implementation: [`Extension4337.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP17Extensions/Extension4337.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` | - | + +
+ +### validateUserOp + +:::note References + +- Specification details: [**LSP-17-Extensions**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-17-Extensions.md#validateuserop) +- Solidity implementation: [`Extension4337.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP17Extensions/Extension4337.sol) +- Function signature: `validateUserOp(UserOperation,bytes32,uint256)` +- Function selector: `0xe86fc51e` + +::: + +```solidity +function validateUserOp( + UserOperation userOp, + bytes32 userOpHash, + uint256 +) external nonpayable returns (uint256); +``` + +_Validate user's signature and nonce the entryPoint will make the call to the recipient only if this validation call returns successfully. signature failure should be reported by returning SIG_VALIDATION_FAILED (1). This allows making a "simulation call" without a valid signature Other failures (e.g. nonce mismatch, or invalid signature format) should still revert to signal failure._ + +Must validate caller is the entryPoint. Must validate the signature and nonce + +#### Parameters + +| Name | Type | Description | +| ------------ | :-------------: | ------------------------------------------------------------------------ | +| `userOp` | `UserOperation` | the operation that is about to be executed. | +| `userOpHash` | `bytes32` | hash of the user's request data. can be used as the basis for signature. | +| `_2` | `uint256` | - | + +#### Returns + +| Name | Type | Description | +| ---- | :-------: | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `0` | `uint256` | packaged ValidationData structure. use `_packValidationData` and `_unpackValidationData` to encode and decode <20-byte> sigAuthorizer - 0 for valid signature, 1 to mark signature failure, otherwise, an address of an "authorizer" contract. <6-byte> validUntil - last timestamp this operation is valid. 0 for "indefinite" <6-byte> validAfter - first timestamp this operation is valid If an account doesn't use time-range, it is enough to return SIG_VALIDATION_FAILED value (1) for signature failure. Note that the validation code cannot use block.timestamp (or block.number) directly. | + +
+ +## 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/LSP17Extensions/OnERC721ReceivedExtension.md b/docs/contracts/LSP17Extensions/OnERC721ReceivedExtension.md new file mode 100644 index 000000000..1f504f9a8 --- /dev/null +++ b/docs/contracts/LSP17Extensions/OnERC721ReceivedExtension.md @@ -0,0 +1,61 @@ + + + +# OnERC721ReceivedExtension + +:::info Standard Specifications + +[`LSP-17-Extensions`](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-17-Extensions.md) + +::: +:::info Solidity implementation + +[`OnERC721ReceivedExtension.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP17Extensions/OnERC721ReceivedExtension.sol) + +::: + +LSP17 Extension that can be attached to a LSP17Extendable contract to allow it to receive ERC721 tokens via `safeTransferFrom`. + +## Public Methods + +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. + +### onERC721Received + +:::note References + +- Specification details: [**LSP-17-Extensions**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-17-Extensions.md#onerc721received) +- Solidity implementation: [`OnERC721ReceivedExtension.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP17Extensions/OnERC721ReceivedExtension.sol) +- Function signature: `onERC721Received(address,address,uint256,bytes)` +- Function selector: `0x150b7a02` + +::: + +```solidity +function onERC721Received( + address, + address, + uint256, + bytes +) external nonpayable returns (bytes4); +``` + +See [`IERC721Receiver-onERC721Received`](#ierc721receiver-onerc721received). Always returns `IERC721Receiver.onERC721Received.selector`. + +#### Parameters + +| Name | Type | Description | +| ---- | :-------: | ----------- | +| `_0` | `address` | - | +| `_1` | `address` | - | +| `_2` | `uint256` | - | +| `_3` | `bytes` | - | + +#### Returns + +| Name | Type | Description | +| ---- | :------: | ----------- | +| `0` | `bytes4` | - | + +
diff --git a/docs/contracts/LSP1UniversalReceiver/LSP1UniversalReceiverDelegateUP/LSP1UniversalReceiverDelegateUP.md b/docs/contracts/LSP1UniversalReceiver/LSP1UniversalReceiverDelegateUP/LSP1UniversalReceiverDelegateUP.md index 04af77971..adb4cc5cd 100644 --- a/docs/contracts/LSP1UniversalReceiver/LSP1UniversalReceiverDelegateUP/LSP1UniversalReceiverDelegateUP.md +++ b/docs/contracts/LSP1UniversalReceiver/LSP1UniversalReceiverDelegateUP/LSP1UniversalReceiverDelegateUP.md @@ -58,14 +58,14 @@ See [`IERC165-supportsInterface`](#ierc165-supportsinterface).
-### universalReceiver +### universalReceiverDelegate :::note References -- Specification details: [**LSP-1-UniversalReceiver**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-1-UniversalReceiver.md#universalreceiver) +- Specification details: [**LSP-1-UniversalReceiver**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-1-UniversalReceiver.md#universalreceiverdelegate) - Solidity implementation: [`LSP1UniversalReceiverDelegateUP.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP1UniversalReceiver/LSP1UniversalReceiverDelegateUP/LSP1UniversalReceiverDelegateUP.sol) -- Function signature: `universalReceiver(bytes32,)` -- Function selector: `0x534e72c8` +- Function signature: `universalReceiverDelegate(address,uint256,bytes32,bytes)` +- Function selector: `0xa245bbda` ::: @@ -83,10 +83,12 @@ When the data stored in the ERC725Y storage of the LSP0 contract is corrupted (\ ::: ```solidity -function universalReceiver( +function universalReceiverDelegate( + address notifier, + uint256, bytes32 typeId, bytes -) external payable returns (bytes); +) external nonpayable returns (bytes); ``` _Reacted on received notification with `typeId`._ @@ -106,10 +108,12 @@ _Reacted on received notification with `typeId`._ #### Parameters -| Name | Type | Description | -| -------- | :-------: | ---------------------------------------------- | -| `typeId` | `bytes32` | Unique identifier for a specific notification. | -| `_1` | `bytes` | - | +| Name | Type | Description | +| ---------- | :-------: | ---------------------------------------------- | +| `notifier` | `address` | - | +| `_1` | `uint256` | - | +| `typeId` | `bytes32` | Unique identifier for a specific notification. | +| `_3` | `bytes` | - | #### Returns @@ -219,41 +223,6 @@ Calls `bytes4(keccak256(setDataBatch(bytes32[],bytes[])))` without checking for
-## Events - -### UniversalReceiver - -:::note References - -- Specification details: [**LSP-1-UniversalReceiver**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-1-UniversalReceiver.md#universalreceiver) -- Solidity implementation: [`LSP1UniversalReceiverDelegateUP.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP1UniversalReceiver/LSP1UniversalReceiverDelegateUP/LSP1UniversalReceiverDelegateUP.sol) -- Event signature: `UniversalReceiver(address,uint256,bytes32,bytes,bytes)` -- Event topic hash: `0x9c3ba68eb5742b8e3961aea0afc7371a71bf433c8a67a831803b64c064a178c2` - -::: - -```solidity -event UniversalReceiver(address indexed from, uint256 indexed value, bytes32 indexed typeId, bytes receivedData, bytes returnedValue); -``` - -\*Address `from` called the `universalReceiver(...)` function while sending `value` LYX. Notification type (typeId): `typeId` - -- Data received: `receivedData`.\* - -Emitted when the [`universalReceiver`](#universalreceiver) function was called with a specific `typeId` and some `receivedData` s - -#### Parameters - -| 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. | -| `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. | - -
- ## Errors ### CannotRegisterEOAsAsAssets @@ -282,24 +251,3 @@ Reverts when EOA calls the [`universalReceiver(..)`](#universalreceiver) functio | `caller` | `address` | The address of the EOA |
- -### NativeTokensNotAccepted - -:::note References - -- Specification details: [**LSP-1-UniversalReceiver**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-1-UniversalReceiver.md#nativetokensnotaccepted) -- Solidity implementation: [`LSP1UniversalReceiverDelegateUP.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP1UniversalReceiver/LSP1UniversalReceiverDelegateUP/LSP1UniversalReceiverDelegateUP.sol) -- Error signature: `NativeTokensNotAccepted()` -- Error hash: `0x114b721a` - -::: - -```solidity -error NativeTokensNotAccepted(); -``` - -_Cannot send native tokens to [`universalReceiver(...)`](#universalreceiver) function of the delegated contract._ - -Reverts when the [`universalReceiver`](#universalreceiver) function in the LSP1 Universal Receiver Delegate contract is called while sending some native tokens along the call (`msg.value` different than `0`) - -
diff --git a/docs/contracts/LSP1UniversalReceiver/LSP1UniversalReceiverDelegateVault/LSP1UniversalReceiverDelegateVault.md b/docs/contracts/LSP1UniversalReceiver/LSP1UniversalReceiverDelegateVault/LSP1UniversalReceiverDelegateVault.md index ddedeaae3..3e50d0f82 100644 --- a/docs/contracts/LSP1UniversalReceiver/LSP1UniversalReceiverDelegateVault/LSP1UniversalReceiverDelegateVault.md +++ b/docs/contracts/LSP1UniversalReceiver/LSP1UniversalReceiverDelegateVault/LSP1UniversalReceiverDelegateVault.md @@ -56,14 +56,14 @@ See [`IERC165-supportsInterface`](#ierc165-supportsinterface).
-### universalReceiver +### universalReceiverDelegate :::note References -- Specification details: [**LSP-1-UniversalReceiver**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-1-UniversalReceiver.md#universalreceiver) +- Specification details: [**LSP-1-UniversalReceiver**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-1-UniversalReceiver.md#universalreceiverdelegate) - Solidity implementation: [`LSP1UniversalReceiverDelegateVault.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP1UniversalReceiver/LSP1UniversalReceiverDelegateVault/LSP1UniversalReceiverDelegateVault.sol) -- Function signature: `universalReceiver(bytes32,)` -- Function selector: `0x534e72c8` +- Function signature: `universalReceiverDelegate(address,uint256,bytes32,bytes)` +- Function selector: `0xa245bbda` ::: @@ -75,10 +75,12 @@ See [`IERC165-supportsInterface`](#ierc165-supportsinterface). ::: ```solidity -function universalReceiver( +function universalReceiverDelegate( + address notifier, + uint256, bytes32 typeId, bytes -) external payable returns (bytes); +) external nonpayable returns (bytes); ``` _Reacted on received notification with `typeId`._ @@ -95,10 +97,12 @@ Handles two cases: Writes the received [LSP-7-DigitalAsset] or [LSP-8-Identifiab #### Parameters -| Name | Type | Description | -| -------- | :-------: | ---------------------------------------------- | -| `typeId` | `bytes32` | Unique identifier for a specific notification. | -| `_1` | `bytes` | - | +| Name | Type | Description | +| ---------- | :-------: | ---------------------------------------------- | +| `notifier` | `address` | - | +| `_1` | `uint256` | - | +| `typeId` | `bytes32` | Unique identifier for a specific notification. | +| `_3` | `bytes` | - | #### Returns @@ -176,41 +180,6 @@ Calls `bytes4(keccak256(setDataBatch(bytes32[],bytes[])))` without checking for
-## Events - -### UniversalReceiver - -:::note References - -- Specification details: [**LSP-1-UniversalReceiver**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-1-UniversalReceiver.md#universalreceiver) -- Solidity implementation: [`LSP1UniversalReceiverDelegateVault.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP1UniversalReceiver/LSP1UniversalReceiverDelegateVault/LSP1UniversalReceiverDelegateVault.sol) -- Event signature: `UniversalReceiver(address,uint256,bytes32,bytes,bytes)` -- Event topic hash: `0x9c3ba68eb5742b8e3961aea0afc7371a71bf433c8a67a831803b64c064a178c2` - -::: - -```solidity -event UniversalReceiver(address indexed from, uint256 indexed value, bytes32 indexed typeId, bytes receivedData, bytes returnedValue); -``` - -\*Address `from` called the `universalReceiver(...)` function while sending `value` LYX. Notification type (typeId): `typeId` - -- Data received: `receivedData`.\* - -Emitted when the [`universalReceiver`](#universalreceiver) function was called with a specific `typeId` and some `receivedData` s - -#### Parameters - -| 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. | -| `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. | - -
- ## Errors ### CannotRegisterEOAsAsAssets @@ -239,24 +208,3 @@ Reverts when EOA calls the [`universalReceiver(..)`](#universalreceiver) functio | `caller` | `address` | The address of the EOA |
- -### NativeTokensNotAccepted - -:::note References - -- Specification details: [**LSP-1-UniversalReceiver**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-1-UniversalReceiver.md#nativetokensnotaccepted) -- Solidity implementation: [`LSP1UniversalReceiverDelegateVault.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP1UniversalReceiver/LSP1UniversalReceiverDelegateVault/LSP1UniversalReceiverDelegateVault.sol) -- Error signature: `NativeTokensNotAccepted()` -- Error hash: `0x114b721a` - -::: - -```solidity -error NativeTokensNotAccepted(); -``` - -_Cannot send native tokens to [`universalReceiver(...)`](#universalreceiver) function of the delegated contract._ - -Reverts when the [`universalReceiver`](#universalreceiver) function in the LSP1 Universal Receiver Delegate contract is called while sending some native tokens along the call (`msg.value` different than `0`) - -
diff --git a/docs/contracts/LSP20CallVerification/LSP20CallVerification.md b/docs/contracts/LSP20CallVerification/LSP20CallVerification.md index 18ec93c78..c34f97ff7 100644 --- a/docs/contracts/LSP20CallVerification/LSP20CallVerification.md +++ b/docs/contracts/LSP20CallVerification/LSP20CallVerification.md @@ -16,7 +16,7 @@ > Implementation of a contract calling the verification functions according to LSP20 - Call Verification standard. -Module to be inherited used to verify the execution of functions according to a verifier address. Verification can happen before or after execution based on a magicValue. +Module to be inherited used to verify the execution of functions according to a verifier address. Verification can happen before or after execution based on a returnedStatus. ## Internal Methods @@ -33,8 +33,8 @@ function _verifyCall( ``` Calls [`lsp20VerifyCall`](#lsp20verifycall) function on the logicVerifier. -Reverts in case the value returned does not match the magic value (lsp20VerifyCall selector) -Returns whether a verification after the execution should happen based on the last byte of the magicValue +Reverts in case the value returned does not match the success value (lsp20VerifyCall selector) +Returns whether a verification after the execution should happen based on the last byte of the returnedStatus
@@ -48,7 +48,7 @@ function _verifyCallResult( ``` Calls [`lsp20VerifyCallResult`](#lsp20verifycallresult) function on the logicVerifier. -Reverts in case the value returned does not match the magic value (lsp20VerifyCallResult selector) +Reverts in case the value returned does not match the success value (lsp20VerifyCallResult selector)
diff --git a/docs/contracts/LSP23LinkedContractsDeployment/IPostDeploymentModule.md b/docs/contracts/LSP23LinkedContractsFactory/IPostDeploymentModule.md similarity index 80% rename from docs/contracts/LSP23LinkedContractsDeployment/IPostDeploymentModule.md rename to docs/contracts/LSP23LinkedContractsFactory/IPostDeploymentModule.md index dbb425ddc..c9efd3a70 100644 --- a/docs/contracts/LSP23LinkedContractsDeployment/IPostDeploymentModule.md +++ b/docs/contracts/LSP23LinkedContractsFactory/IPostDeploymentModule.md @@ -5,12 +5,12 @@ :::info Standard Specifications -[`LSP-23-LinkedContractsDeployment`](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-23-LinkedContractsDeployment.md) +[`LSP-23-LinkedContractsFactory`](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-23-LinkedContractsFactory.md) ::: :::info Solidity implementation -[`IPostDeploymentModule.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP23LinkedContractsDeployment/IPostDeploymentModule.sol) +[`IPostDeploymentModule.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP23LinkedContractsFactory/IPostDeploymentModule.sol) ::: @@ -23,8 +23,8 @@ When marked as 'public', a method can be called both externally and internally, :::note References -- Specification details: [**LSP-23-LinkedContractsDeployment**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-23-LinkedContractsDeployment.md#executepostdeployment) -- Solidity implementation: [`IPostDeploymentModule.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP23LinkedContractsDeployment/IPostDeploymentModule.sol) +- Specification details: [**LSP-23-LinkedContractsFactory**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-23-LinkedContractsFactory.md#executepostdeployment) +- Solidity implementation: [`IPostDeploymentModule.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP23LinkedContractsFactory/IPostDeploymentModule.sol) - Function signature: `executePostDeployment(address,address,bytes)` - Function selector: `0x28c4d14e` diff --git a/docs/contracts/LSP23LinkedContractsDeployment/LSP23LinkedContractsFactory.md b/docs/contracts/LSP23LinkedContractsFactory/LSP23LinkedContractsFactory.md similarity index 61% rename from docs/contracts/LSP23LinkedContractsDeployment/LSP23LinkedContractsFactory.md rename to docs/contracts/LSP23LinkedContractsFactory/LSP23LinkedContractsFactory.md index f95f1949f..1eab86124 100644 --- a/docs/contracts/LSP23LinkedContractsDeployment/LSP23LinkedContractsFactory.md +++ b/docs/contracts/LSP23LinkedContractsFactory/LSP23LinkedContractsFactory.md @@ -5,12 +5,12 @@ :::info Standard Specifications -[`LSP-23-LinkedContractsDeployment`](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-23-LinkedContractsDeployment.md) +[`LSP-23-LinkedContractsFactory`](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-23-LinkedContractsFactory.md) ::: :::info Solidity implementation -[`LSP23LinkedContractsFactory.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP23LinkedContractsDeployment/LSP23LinkedContractsFactory.sol) +[`LSP23LinkedContractsFactory.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP23LinkedContractsFactory/LSP23LinkedContractsFactory.sol) ::: @@ -23,8 +23,8 @@ When marked as 'public', a method can be called both externally and internally, :::note References -- Specification details: [**LSP-23-LinkedContractsDeployment**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-23-LinkedContractsDeployment.md#computeaddresses) -- Solidity implementation: [`LSP23LinkedContractsFactory.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP23LinkedContractsDeployment/LSP23LinkedContractsFactory.sol) +- Specification details: [**LSP-23-LinkedContractsFactory**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-23-LinkedContractsFactory.md#computeaddresses) +- Solidity implementation: [`LSP23LinkedContractsFactory.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP23LinkedContractsFactory/LSP23LinkedContractsFactory.sol) - Function signature: `computeAddresses(ILSP23LinkedContractsFactory.PrimaryContractDeployment,ILSP23LinkedContractsFactory.SecondaryContractDeployment,address,bytes)` - Function selector: `0xdecfb0b9` @@ -42,21 +42,23 @@ function computeAddresses( returns (address primaryContractAddress, address secondaryContractAddress); ``` +Computes the addresses of a primary contract and a secondary linked contract + #### Parameters -| Name | Type | Description | -| ------------------------------ | :--------------------------------------------------------: | ----------- | -| `primaryContractDeployment` | `ILSP23LinkedContractsFactory.PrimaryContractDeployment` | - | -| `secondaryContractDeployment` | `ILSP23LinkedContractsFactory.SecondaryContractDeployment` | - | -| `postDeploymentModule` | `address` | - | -| `postDeploymentModuleCalldata` | `bytes` | - | +| Name | Type | Description | +| ------------------------------ | :--------------------------------------------------------: | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `primaryContractDeployment` | `ILSP23LinkedContractsFactory.PrimaryContractDeployment` | Contains the needed parameter to deploy the primary contract. (`salt`, `fundingAmount`, `creationBytecode`) | +| `secondaryContractDeployment` | `ILSP23LinkedContractsFactory.SecondaryContractDeployment` | Contains the needed parameter to deploy the secondary contract. (`fundingAmount`, `creationBytecode`, `addPrimaryContractAddress`, `extraConstructorParams`) | +| `postDeploymentModule` | `address` | The optional module to be executed after deployment | +| `postDeploymentModuleCalldata` | `bytes` | The data to be passed to the post deployment module | #### Returns -| Name | Type | Description | -| -------------------------- | :-------: | ----------- | -| `primaryContractAddress` | `address` | - | -| `secondaryContractAddress` | `address` | - | +| Name | Type | Description | +| -------------------------- | :-------: | ----------------------------------------------- | +| `primaryContractAddress` | `address` | The address of the deployed primary contract. | +| `secondaryContractAddress` | `address` | The address of the deployed secondary contract. |
@@ -64,8 +66,8 @@ function computeAddresses( :::note References -- Specification details: [**LSP-23-LinkedContractsDeployment**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-23-LinkedContractsDeployment.md#computeerc1167addresses) -- Solidity implementation: [`LSP23LinkedContractsFactory.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP23LinkedContractsDeployment/LSP23LinkedContractsFactory.sol) +- Specification details: [**LSP-23-LinkedContractsFactory**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-23-LinkedContractsFactory.md#computeerc1167addresses) +- Solidity implementation: [`LSP23LinkedContractsFactory.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP23LinkedContractsFactory/LSP23LinkedContractsFactory.sol) - Function signature: `computeERC1167Addresses(ILSP23LinkedContractsFactory.PrimaryContractDeploymentInit,ILSP23LinkedContractsFactory.SecondaryContractDeploymentInit,address,bytes)` - Function selector: `0x8da85898` @@ -83,21 +85,23 @@ function computeERC1167Addresses( returns (address primaryContractAddress, address secondaryContractAddress); ``` +Computes the addresses of a primary and a secondary linked contracts ERC1167 proxies to be created + #### Parameters -| Name | Type | Description | -| --------------------------------- | :------------------------------------------------------------: | ----------- | -| `primaryContractDeploymentInit` | `ILSP23LinkedContractsFactory.PrimaryContractDeploymentInit` | - | -| `secondaryContractDeploymentInit` | `ILSP23LinkedContractsFactory.SecondaryContractDeploymentInit` | - | -| `postDeploymentModule` | `address` | - | -| `postDeploymentModuleCalldata` | `bytes` | - | +| Name | Type | Description | +| --------------------------------- | :------------------------------------------------------------: | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `primaryContractDeploymentInit` | `ILSP23LinkedContractsFactory.PrimaryContractDeploymentInit` | Contains the needed parameters to deploy a primary proxy contract. (`salt`, `fundingAmount`, `implementationContract`, `initializationCalldata`) | +| `secondaryContractDeploymentInit` | `ILSP23LinkedContractsFactory.SecondaryContractDeploymentInit` | Contains the needed parameters to deploy the secondary proxy contract. (`fundingAmount`, `implementationContract`, `initializationCalldata`, `addPrimaryContractAddress`, `extraInitializationParams`) | +| `postDeploymentModule` | `address` | The optional module to be executed after deployment. | +| `postDeploymentModuleCalldata` | `bytes` | The data to be passed to the post deployment module. | #### Returns -| Name | Type | Description | -| -------------------------- | :-------: | ----------- | -| `primaryContractAddress` | `address` | - | -| `secondaryContractAddress` | `address` | - | +| Name | Type | Description | +| -------------------------- | :-------: | ---------------------------------------------------- | +| `primaryContractAddress` | `address` | The address of the deployed primary contract proxy | +| `secondaryContractAddress` | `address` | The address of the deployed secondary contract proxy |
@@ -105,8 +109,8 @@ function computeERC1167Addresses( :::note References -- Specification details: [**LSP-23-LinkedContractsDeployment**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-23-LinkedContractsDeployment.md#deploycontracts) -- Solidity implementation: [`LSP23LinkedContractsFactory.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP23LinkedContractsDeployment/LSP23LinkedContractsFactory.sol) +- Specification details: [**LSP-23-LinkedContractsFactory**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-23-LinkedContractsFactory.md#deploycontracts) +- Solidity implementation: [`LSP23LinkedContractsFactory.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP23LinkedContractsFactory/LSP23LinkedContractsFactory.sol) - Function signature: `deployContracts(ILSP23LinkedContractsFactory.PrimaryContractDeployment,ILSP23LinkedContractsFactory.SecondaryContractDeployment,address,bytes)` - Function selector: `0xf830c0ab` @@ -124,21 +128,25 @@ function deployContracts( returns (address primaryContractAddress, address secondaryContractAddress); ``` +_Contracts deployed. Contract Address: `primaryContractAddress`. Primary Contract Address: `primaryContractAddress`_ + +Deploys a primary and a secondary linked contract. + #### Parameters -| Name | Type | Description | -| ------------------------------ | :--------------------------------------------------------: | ----------- | -| `primaryContractDeployment` | `ILSP23LinkedContractsFactory.PrimaryContractDeployment` | - | -| `secondaryContractDeployment` | `ILSP23LinkedContractsFactory.SecondaryContractDeployment` | - | -| `postDeploymentModule` | `address` | - | -| `postDeploymentModuleCalldata` | `bytes` | - | +| Name | Type | Description | +| ------------------------------ | :--------------------------------------------------------: | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `primaryContractDeployment` | `ILSP23LinkedContractsFactory.PrimaryContractDeployment` | Contains the needed parameter to deploy a contract. (`salt`, `fundingAmount`, `creationBytecode`) | +| `secondaryContractDeployment` | `ILSP23LinkedContractsFactory.SecondaryContractDeployment` | Contains the needed parameter to deploy the secondary contract. (`fundingAmount`, `creationBytecode`, `addPrimaryContractAddress`, `extraConstructorParams`) | +| `postDeploymentModule` | `address` | The optional module to be executed after deployment | +| `postDeploymentModuleCalldata` | `bytes` | The data to be passed to the post deployment module | #### Returns -| Name | Type | Description | -| -------------------------- | :-------: | ----------- | -| `primaryContractAddress` | `address` | - | -| `secondaryContractAddress` | `address` | - | +| Name | Type | Description | +| -------------------------- | :-------: | -------------------------------------- | +| `primaryContractAddress` | `address` | The address of the primary contract. | +| `secondaryContractAddress` | `address` | The address of the secondary contract. |
@@ -146,8 +154,8 @@ function deployContracts( :::note References -- Specification details: [**LSP-23-LinkedContractsDeployment**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-23-LinkedContractsDeployment.md#deployerc1167proxies) -- Solidity implementation: [`LSP23LinkedContractsFactory.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP23LinkedContractsDeployment/LSP23LinkedContractsFactory.sol) +- Specification details: [**LSP-23-LinkedContractsFactory**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-23-LinkedContractsFactory.md#deployerc1167proxies) +- Solidity implementation: [`LSP23LinkedContractsFactory.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP23LinkedContractsFactory/LSP23LinkedContractsFactory.sol) - Function signature: `deployERC1167Proxies(ILSP23LinkedContractsFactory.PrimaryContractDeploymentInit,ILSP23LinkedContractsFactory.SecondaryContractDeploymentInit,address,bytes)` - Function selector: `0x17c042c4` @@ -165,21 +173,25 @@ function deployERC1167Proxies( returns (address primaryContractAddress, address secondaryContractAddress); ``` +_Contract proxies deployed. Primary Proxy Address: `primaryContractAddress`. Secondary Contract Proxy Address: `secondaryContractAddress`_ + +Deploys ERC1167 proxies of a primary contract and a secondary linked contract + #### Parameters -| Name | Type | Description | -| --------------------------------- | :------------------------------------------------------------: | ----------- | -| `primaryContractDeploymentInit` | `ILSP23LinkedContractsFactory.PrimaryContractDeploymentInit` | - | -| `secondaryContractDeploymentInit` | `ILSP23LinkedContractsFactory.SecondaryContractDeploymentInit` | - | -| `postDeploymentModule` | `address` | - | -| `postDeploymentModuleCalldata` | `bytes` | - | +| Name | Type | Description | +| --------------------------------- | :------------------------------------------------------------: | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `primaryContractDeploymentInit` | `ILSP23LinkedContractsFactory.PrimaryContractDeploymentInit` | Contains the needed parameters to deploy a proxy contract. (`salt`, `fundingAmount`, `implementationContract`, `initializationCalldata`) | +| `secondaryContractDeploymentInit` | `ILSP23LinkedContractsFactory.SecondaryContractDeploymentInit` | Contains the needed parameters to deploy the secondary proxy contract. (`fundingAmount`, `implementationContract`, `initializationCalldata`, `addPrimaryContractAddress`, `extraInitializationParams`) | +| `postDeploymentModule` | `address` | The optional module to be executed after deployment. | +| `postDeploymentModuleCalldata` | `bytes` | The data to be passed to the post deployment module. | #### Returns -| Name | Type | Description | -| -------------------------- | :-------: | ----------- | -| `primaryContractAddress` | `address` | - | -| `secondaryContractAddress` | `address` | - | +| Name | Type | Description | +| -------------------------- | :-------: | ---------------------------------------------------- | +| `primaryContractAddress` | `address` | The address of the deployed primary contract proxy | +| `secondaryContractAddress` | `address` | The address of the deployed secondary contract proxy |
@@ -243,8 +255,8 @@ function _generatePrimaryContractProxySalt(struct ILSP23LinkedContractsFactory.P :::note References -- Specification details: [**LSP-23-LinkedContractsDeployment**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-23-LinkedContractsDeployment.md#deployedcontracts) -- Solidity implementation: [`LSP23LinkedContractsFactory.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP23LinkedContractsDeployment/LSP23LinkedContractsFactory.sol) +- Specification details: [**LSP-23-LinkedContractsFactory**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-23-LinkedContractsFactory.md#deployedcontracts) +- Solidity implementation: [`LSP23LinkedContractsFactory.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP23LinkedContractsFactory/LSP23LinkedContractsFactory.sol) - Event signature: `DeployedContracts(address,address,ILSP23LinkedContractsFactory.PrimaryContractDeployment,ILSP23LinkedContractsFactory.SecondaryContractDeployment,address,bytes)` - Event topic hash: `0x1ea27dabd8fd1508e844ab51c2fd3d9081f2684346857f9187da6d4a1aa7d3e6` @@ -273,8 +285,8 @@ Emitted when a primary and secondary contract are deployed. :::note References -- Specification details: [**LSP-23-LinkedContractsDeployment**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-23-LinkedContractsDeployment.md#deployederc1167proxies) -- Solidity implementation: [`LSP23LinkedContractsFactory.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP23LinkedContractsDeployment/LSP23LinkedContractsFactory.sol) +- Specification details: [**LSP-23-LinkedContractsFactory**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-23-LinkedContractsFactory.md#deployederc1167proxies) +- Solidity implementation: [`LSP23LinkedContractsFactory.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP23LinkedContractsFactory/LSP23LinkedContractsFactory.sol) - Event signature: `DeployedERC1167Proxies(address,address,ILSP23LinkedContractsFactory.PrimaryContractDeploymentInit,ILSP23LinkedContractsFactory.SecondaryContractDeploymentInit,address,bytes)` - Event topic hash: `0xb03dbe7a02c063899f863d542410b5b038c8f537045be3a26e7144e0074e1c7b` @@ -305,8 +317,8 @@ Emitted when proxies of a primary and secondary contract are deployed. :::note References -- Specification details: [**LSP-23-LinkedContractsDeployment**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-23-LinkedContractsDeployment.md#invalidvaluesum) -- Solidity implementation: [`LSP23LinkedContractsFactory.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP23LinkedContractsDeployment/LSP23LinkedContractsFactory.sol) +- Specification details: [**LSP-23-LinkedContractsFactory**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-23-LinkedContractsFactory.md#invalidvaluesum) +- Solidity implementation: [`LSP23LinkedContractsFactory.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP23LinkedContractsFactory/LSP23LinkedContractsFactory.sol) - Error signature: `InvalidValueSum()` - Error hash: `0x2fd9ca91` @@ -326,8 +338,8 @@ Reverts when the `msg.value` sent is not equal to the sum of value used for the :::note References -- Specification details: [**LSP-23-LinkedContractsDeployment**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-23-LinkedContractsDeployment.md#primarycontractproxyinitfailureerror) -- Solidity implementation: [`LSP23LinkedContractsFactory.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP23LinkedContractsDeployment/LSP23LinkedContractsFactory.sol) +- Specification details: [**LSP-23-LinkedContractsFactory**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-23-LinkedContractsFactory.md#primarycontractproxyinitfailureerror) +- Solidity implementation: [`LSP23LinkedContractsFactory.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP23LinkedContractsFactory/LSP23LinkedContractsFactory.sol) - Error signature: `PrimaryContractProxyInitFailureError(bytes)` - Error hash: `0x4364b6ee` @@ -353,8 +365,8 @@ Reverts when the deployment & intialization of the contract has failed. :::note References -- Specification details: [**LSP-23-LinkedContractsDeployment**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-23-LinkedContractsDeployment.md#secondarycontractproxyinitfailureerror) -- Solidity implementation: [`LSP23LinkedContractsFactory.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP23LinkedContractsDeployment/LSP23LinkedContractsFactory.sol) +- Specification details: [**LSP-23-LinkedContractsFactory**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-23-LinkedContractsFactory.md#secondarycontractproxyinitfailureerror) +- Solidity implementation: [`LSP23LinkedContractsFactory.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP23LinkedContractsFactory/LSP23LinkedContractsFactory.sol) - Error signature: `SecondaryContractProxyInitFailureError(bytes)` - Error hash: `0x9654a854` diff --git a/docs/contracts/LSP4DigitalAssetMetadata/LSP4Compatibility.md b/docs/contracts/LSP4DigitalAssetMetadata/LSP4Compatibility.md deleted file mode 100644 index 1f8e99615..000000000 --- a/docs/contracts/LSP4DigitalAssetMetadata/LSP4Compatibility.md +++ /dev/null @@ -1,539 +0,0 @@ - - - -# LSP4Compatibility - -:::info Standard Specifications - -[`LSP-4-DigitalAssetMetadata`](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-4-DigitalAssetMetadata.md) - -::: -:::info Solidity implementation - -[`LSP4Compatibility.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP4DigitalAssetMetadata/LSP4Compatibility.sol) - -::: - -> LSP4Compatibility - -LSP4 extension, for compatibility with clients & tools that expect ERC20/721. - -## Public Methods - -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. - -### getData - -:::note References - -- Specification details: [**LSP-4-DigitalAssetMetadata**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-4-DigitalAssetMetadata.md#getdata) -- Solidity implementation: [`LSP4Compatibility.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP4DigitalAssetMetadata/LSP4Compatibility.sol) -- Function signature: `getData(bytes32)` -- Function selector: `0x54f6127f` - -::: - -```solidity -function getData(bytes32 dataKey) external view returns (bytes dataValue); -``` - -_Reading the ERC725Y storage for data key `dataKey` returned the following value: `dataValue`._ - -Get in the ERC725Y storage the bytes data stored at a specific data key `dataKey`. - -#### Parameters - -| Name | Type | Description | -| --------- | :-------: | --------------------------------------------- | -| `dataKey` | `bytes32` | The data key for which to retrieve the value. | - -#### Returns - -| Name | Type | Description | -| ----------- | :-----: | ---------------------------------------------------- | -| `dataValue` | `bytes` | The bytes value stored under the specified data key. | - -
- -### getDataBatch - -:::note References - -- Specification details: [**LSP-4-DigitalAssetMetadata**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-4-DigitalAssetMetadata.md#getdatabatch) -- Solidity implementation: [`LSP4Compatibility.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP4DigitalAssetMetadata/LSP4Compatibility.sol) -- Function signature: `getDataBatch(bytes32[])` -- Function selector: `0xdedff9c6` - -::: - -```solidity -function getDataBatch( - bytes32[] dataKeys -) external view returns (bytes[] dataValues); -``` - -_Reading the ERC725Y storage for data keys `dataKeys` returned the following values: `dataValues`._ - -Get in the ERC725Y storage the bytes data stored at multiple data keys `dataKeys`. - -#### Parameters - -| Name | Type | Description | -| ---------- | :---------: | ------------------------------------------ | -| `dataKeys` | `bytes32[]` | The array of keys which values to retrieve | - -#### Returns - -| Name | Type | Description | -| ------------ | :-------: | ----------------------------------------- | -| `dataValues` | `bytes[]` | The array of data stored at multiple keys | - -
- -### name - -:::note References - -- Specification details: [**LSP-4-DigitalAssetMetadata**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-4-DigitalAssetMetadata.md#name) -- Solidity implementation: [`LSP4Compatibility.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP4DigitalAssetMetadata/LSP4Compatibility.sol) -- Function signature: `name()` -- Function selector: `0x06fdde03` - -::: - -```solidity -function name() external view returns (string); -``` - -Returns the name of the token. - -#### Returns - -| Name | Type | Description | -| ---- | :------: | --------------------- | -| `0` | `string` | The name of the token | - -
- -### owner - -:::note References - -- Specification details: [**LSP-4-DigitalAssetMetadata**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-4-DigitalAssetMetadata.md#owner) -- Solidity implementation: [`LSP4Compatibility.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP4DigitalAssetMetadata/LSP4Compatibility.sol) -- Function signature: `owner()` -- Function selector: `0x8da5cb5b` - -::: - -```solidity -function owner() external view returns (address); -``` - -Returns the address of the current owner. - -#### Returns - -| Name | Type | Description | -| ---- | :-------: | ----------- | -| `0` | `address` | - | - -
- -### renounceOwnership - -:::note References - -- Specification details: [**LSP-4-DigitalAssetMetadata**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-4-DigitalAssetMetadata.md#renounceownership) -- Solidity implementation: [`LSP4Compatibility.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP4DigitalAssetMetadata/LSP4Compatibility.sol) -- Function signature: `renounceOwnership()` -- Function selector: `0x715018a6` - -::: - -```solidity -function renounceOwnership() external nonpayable; -``` - -Leaves the contract without owner. It will not be possible to call `onlyOwner` functions anymore. Can only be called by the current owner. NOTE: Renouncing ownership will leave the contract without an owner, thereby removing any functionality that is only available to the owner. - -
- -### setData - -:::note References - -- Specification details: [**LSP-4-DigitalAssetMetadata**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-4-DigitalAssetMetadata.md#setdata) -- Solidity implementation: [`LSP4Compatibility.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP4DigitalAssetMetadata/LSP4Compatibility.sol) -- Function signature: `setData(bytes32,bytes)` -- Function selector: `0x7f23690c` - -::: - -:::caution Warning - -**Note for developers:** despite the fact that this function is set as `payable`, if the function is not intended to receive value (= native tokens), **an additional check should be implemented to ensure that `msg.value` sent was equal to 0**. - -::: - -```solidity -function setData(bytes32 dataKey, bytes dataValue) external payable; -``` - -_Setting the following data key value pair in the ERC725Y storage. Data key: `dataKey`, data value: `dataValue`._ - -Sets a single bytes value `dataValue` in the ERC725Y storage for a specific data key `dataKey`. The function is marked as payable to enable flexibility on child contracts. For instance to implement a fee mechanism for setting specific data. - -
- -**Requirements:** - -- SHOULD only be callable by the [`owner`](#owner). - -
- -
- -**Emitted events:** - -- [`DataChanged`](#datachanged) event. - -
- -#### Parameters - -| Name | Type | Description | -| ----------- | :-------: | ------------------------------------------ | -| `dataKey` | `bytes32` | The data key for which to set a new value. | -| `dataValue` | `bytes` | The new bytes value to set. | - -
- -### setDataBatch - -:::note References - -- Specification details: [**LSP-4-DigitalAssetMetadata**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-4-DigitalAssetMetadata.md#setdatabatch) -- Solidity implementation: [`LSP4Compatibility.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP4DigitalAssetMetadata/LSP4Compatibility.sol) -- Function signature: `setDataBatch(bytes32[],bytes[])` -- Function selector: `0x97902421` - -::: - -:::caution Warning - -**Note for developers:** despite the fact that this function is set as `payable`, if the function is not intended to receive value (= native tokens), **an additional check should be implemented to ensure that `msg.value` sent was equal to 0**. - -::: - -```solidity -function setDataBatch(bytes32[] dataKeys, bytes[] dataValues) external payable; -``` - -_Setting the following data key value pairs in the ERC725Y storage. Data keys: `dataKeys`, data values: `dataValues`._ - -Batch data setting function that behaves the same as [`setData`](#setdata) but allowing to set multiple data key/value pairs in the ERC725Y storage in the same transaction. - -
- -**Requirements:** - -- SHOULD only be callable by the [`owner`](#owner) of the contract. - -
- -
- -**Emitted events:** - -- [`DataChanged`](#datachanged) event **for each data key/value pair set**. - -
- -#### Parameters - -| Name | Type | Description | -| ------------ | :---------: | ---------------------------------------------------- | -| `dataKeys` | `bytes32[]` | An array of data keys to set bytes values for. | -| `dataValues` | `bytes[]` | An array of bytes values to set for each `dataKeys`. | - -
- -### supportsInterface - -:::note References - -- Specification details: [**LSP-4-DigitalAssetMetadata**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-4-DigitalAssetMetadata.md#supportsinterface) -- Solidity implementation: [`LSP4Compatibility.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP4DigitalAssetMetadata/LSP4Compatibility.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` | - | - -
- -### symbol - -:::note References - -- Specification details: [**LSP-4-DigitalAssetMetadata**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-4-DigitalAssetMetadata.md#symbol) -- Solidity implementation: [`LSP4Compatibility.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP4DigitalAssetMetadata/LSP4Compatibility.sol) -- Function signature: `symbol()` -- Function selector: `0x95d89b41` - -::: - -```solidity -function symbol() external view returns (string); -``` - -Returns the symbol of the token, usually a shorter version of the name. - -#### Returns - -| Name | Type | Description | -| ---- | :------: | ----------------------- | -| `0` | `string` | The symbol of the token | - -
- -### transferOwnership - -:::note References - -- Specification details: [**LSP-4-DigitalAssetMetadata**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-4-DigitalAssetMetadata.md#transferownership) -- Solidity implementation: [`LSP4Compatibility.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP4DigitalAssetMetadata/LSP4Compatibility.sol) -- Function signature: `transferOwnership(address)` -- Function selector: `0xf2fde38b` - -::: - -```solidity -function transferOwnership(address newOwner) external nonpayable; -``` - -Transfers ownership of the contract to a new account (`newOwner`). Can only be called by the current owner. - -#### Parameters - -| Name | Type | Description | -| ---------- | :-------: | ----------- | -| `newOwner` | `address` | - | - -
- -## 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. - -### \_checkOwner - -```solidity -function _checkOwner() internal view; -``` - -Throws if the sender is not the owner. - -
- -### \_setOwner - -```solidity -function _setOwner(address newOwner) internal nonpayable; -``` - -Changes the owner if `newOwner` and oldOwner are different -This pattern is useful in inheritance. - -
- -### \_getData - -```solidity -function _getData(bytes32 dataKey) internal view returns (bytes dataValue); -``` - -Read the value stored under a specific `dataKey` inside the underlying ERC725Y storage, -represented as a mapping of `bytes32` data keys mapped to their `bytes` data values. - -```solidity -mapping(bytes32 => bytes) _store -``` - -#### Parameters - -| Name | Type | Description | -| --------- | :-------: | ----------------------------------------------------------------------- | -| `dataKey` | `bytes32` | A bytes32 data key to read the associated `bytes` value from the store. | - -#### Returns - -| Name | Type | Description | -| ----------- | :-----: | ----------------------------------------------------------------------------- | -| `dataValue` | `bytes` | The `bytes` value associated with the given `dataKey` in the ERC725Y storage. | - -
- -### \_setData - -```solidity -function _setData(bytes32 dataKey, bytes dataValue) internal nonpayable; -``` - -Write a `dataValue` to the underlying ERC725Y storage, represented as a mapping of -`bytes32` data keys mapped to their `bytes` data values. - -```solidity -mapping(bytes32 => bytes) _store -``` - -
- -**Emitted events:** - -- [`DataChanged`](#datachanged) event emitted after a successful `setData` call. - -
- -#### Parameters - -| Name | Type | Description | -| ----------- | :-------: | ------------------------------------------------------------------------------- | -| `dataKey` | `bytes32` | A bytes32 data key to write the associated `bytes` value to the store. | -| `dataValue` | `bytes` | The `bytes` value to associate with the given `dataKey` in the ERC725Y storage. | - -
- -## Events - -### DataChanged - -:::note References - -- Specification details: [**LSP-4-DigitalAssetMetadata**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-4-DigitalAssetMetadata.md#datachanged) -- Solidity implementation: [`LSP4Compatibility.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP4DigitalAssetMetadata/LSP4Compatibility.sol) -- Event signature: `DataChanged(bytes32,bytes)` -- Event topic hash: `0xece574603820d07bc9b91f2a932baadf4628aabcb8afba49776529c14a6104b2` - -::: - -```solidity -event DataChanged(bytes32 indexed dataKey, bytes dataValue); -``` - -_The following data key/value pair has been changed in the ERC725Y storage: Data key: `dataKey`, data value: `dataValue`._ - -Emitted when data at a specific `dataKey` was changed to a new value `dataValue`. - -#### Parameters - -| Name | Type | Description | -| ----------------------- | :-------: | -------------------------------------------- | -| `dataKey` **`indexed`** | `bytes32` | The data key for which a bytes value is set. | -| `dataValue` | `bytes` | The value to set for the given data key. | - -
- -### OwnershipTransferred - -:::note References - -- Specification details: [**LSP-4-DigitalAssetMetadata**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-4-DigitalAssetMetadata.md#ownershiptransferred) -- Solidity implementation: [`LSP4Compatibility.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP4DigitalAssetMetadata/LSP4Compatibility.sol) -- Event signature: `OwnershipTransferred(address,address)` -- Event topic hash: `0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0` - -::: - -```solidity -event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); -``` - -#### Parameters - -| Name | Type | Description | -| ----------------------------- | :-------: | ----------- | -| `previousOwner` **`indexed`** | `address` | - | -| `newOwner` **`indexed`** | `address` | - | - -
- -## Errors - -### ERC725Y_DataKeysValuesEmptyArray - -:::note References - -- Specification details: [**LSP-4-DigitalAssetMetadata**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-4-DigitalAssetMetadata.md#erc725y_datakeysvaluesemptyarray) -- Solidity implementation: [`LSP4Compatibility.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP4DigitalAssetMetadata/LSP4Compatibility.sol) -- Error signature: `ERC725Y_DataKeysValuesEmptyArray()` -- Error hash: `0x97da5f95` - -::: - -```solidity -error ERC725Y_DataKeysValuesEmptyArray(); -``` - -Reverts when one of the array parameter provided to [`setDataBatch`](#setdatabatch) function is an empty array. - -
- -### ERC725Y_DataKeysValuesLengthMismatch - -:::note References - -- Specification details: [**LSP-4-DigitalAssetMetadata**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-4-DigitalAssetMetadata.md#erc725y_datakeysvalueslengthmismatch) -- Solidity implementation: [`LSP4Compatibility.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP4DigitalAssetMetadata/LSP4Compatibility.sol) -- Error signature: `ERC725Y_DataKeysValuesLengthMismatch()` -- Error hash: `0x3bcc8979` - -::: - -```solidity -error ERC725Y_DataKeysValuesLengthMismatch(); -``` - -Reverts when there is not the same number of elements in the `datakeys` and `dataValues` array parameters provided when calling the [`setDataBatch`](#setdatabatch) function. - -
- -### ERC725Y_MsgValueDisallowed - -:::note References - -- Specification details: [**LSP-4-DigitalAssetMetadata**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-4-DigitalAssetMetadata.md#erc725y_msgvaluedisallowed) -- Solidity implementation: [`LSP4Compatibility.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP4DigitalAssetMetadata/LSP4Compatibility.sol) -- Error signature: `ERC725Y_MsgValueDisallowed()` -- Error hash: `0xf36ba737` - -::: - -```solidity -error ERC725Y_MsgValueDisallowed(); -``` - -Reverts when sending value to the [`setData`](#setdata) or [`setDataBatch`](#setdatabatch) function. - -
diff --git a/docs/contracts/LSP4DigitalAssetMetadata/LSP4DigitalAssetMetadata.md b/docs/contracts/LSP4DigitalAssetMetadata/LSP4DigitalAssetMetadata.md index 0f5c8b1bf..15121dc6b 100644 --- a/docs/contracts/LSP4DigitalAssetMetadata/LSP4DigitalAssetMetadata.md +++ b/docs/contracts/LSP4DigitalAssetMetadata/LSP4DigitalAssetMetadata.md @@ -505,3 +505,47 @@ error LSP4TokenSymbolNotEditable(); Reverts when trying to edit the data key `LSP4TokenSymbol` after the digital asset contract has been deployed. The `LSP4TokenSymbol` data key is located inside the ERC725Y Data key-value store of the digital asset contract. It can be set only once inside the constructor/initializer when the digital asset contract is being deployed.
+ +### OwnableCallerNotTheOwner + +:::note References + +- Specification details: [**LSP-4-DigitalAssetMetadata**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-4-DigitalAssetMetadata.md#ownablecallernottheowner) +- Solidity implementation: [`LSP4DigitalAssetMetadata.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP4DigitalAssetMetadata/LSP4DigitalAssetMetadata.sol) +- Error signature: `OwnableCallerNotTheOwner(address)` +- Error hash: `0xbf1169c5` + +::: + +```solidity +error OwnableCallerNotTheOwner(address callerAddress); +``` + +Reverts when only the owner is allowed to call the function. + +#### Parameters + +| Name | Type | Description | +| --------------- | :-------: | ---------------------------------------- | +| `callerAddress` | `address` | The address that tried to make the call. | + +
+ +### OwnableCannotSetZeroAddressAsOwner + +:::note References + +- Specification details: [**LSP-4-DigitalAssetMetadata**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-4-DigitalAssetMetadata.md#ownablecannotsetzeroaddressasowner) +- Solidity implementation: [`LSP4DigitalAssetMetadata.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP4DigitalAssetMetadata/LSP4DigitalAssetMetadata.sol) +- Error signature: `OwnableCannotSetZeroAddressAsOwner()` +- Error hash: `0x1ad8836c` + +::: + +```solidity +error OwnableCannotSetZeroAddressAsOwner(); +``` + +Reverts when trying to set `address(0)` as the contract owner when deploying the contract, initializing it or transferring ownership of the contract. + +
diff --git a/docs/contracts/LSP6KeyManager/LSP6KeyManager.md b/docs/contracts/LSP6KeyManager/LSP6KeyManager.md index f48c54779..a1b0b76df 100644 --- a/docs/contracts/LSP6KeyManager/LSP6KeyManager.md +++ b/docs/contracts/LSP6KeyManager/LSP6KeyManager.md @@ -36,7 +36,7 @@ When marked as 'public', a method can be called both externally and internally, constructor(address target_); ``` -_Deploying a LSP6KeyManager linked to the contract at address `target_`.\_ +_Deploying a LSP6KeyManager linked to the contract at address `target_`._ Deploy a Key Manager and set the `target_` address in the contract storage, making this Key Manager linked to this `target_` contract. @@ -306,10 +306,10 @@ Get the nonce for a specific `from` address that can be used for signing relay t function isValidSignature( bytes32 dataHash, bytes signature -) external view returns (bytes4 magicValue); +) external view returns (bytes4 returnedStatus); ``` -Checks if a signature was signed by a controller that has the permission `SIGN`. If the signer is a controller with the permission `SIGN`, it will return the ERC1271 magic value. +Checks if a signature was signed by a controller that has the permission `SIGN`. If the signer is a controller with the permission `SIGN`, it will return the ERC1271 success value. #### Parameters @@ -320,9 +320,9 @@ Checks if a signature was signed by a controller that has the permission `SIGN`. #### Returns -| Name | Type | Description | -| ------------ | :------: | ---------------------------------------------------- | -| `magicValue` | `bytes4` | `0x1626ba7e` on success, or `0xffffffff` on failure. | +| Name | Type | Description | +| ---------------- | :------: | ---------------------------------------------------- | +| `returnedStatus` | `bytes4` | `0x1626ba7e` on success, or `0xffffffff` on failure. |
@@ -332,26 +332,39 @@ Checks if a signature was signed by a controller that has the permission `SIGN`. - Specification details: [**LSP-6-KeyManager**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-6-KeyManager.md#lsp20verifycall) - Solidity implementation: [`LSP6KeyManager.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP6KeyManager/LSP6KeyManager.sol) -- Function signature: `lsp20VerifyCall(address,uint256,bytes)` -- Function selector: `0x9bf04b11` +- Function signature: `lsp20VerifyCall(address,address,address,uint256,bytes)` +- Function selector: `0xde928f14` + +::: + +:::tip Hint + +This function can call by any other address than the {`target`}. This allows to verify permissions in a _"read-only"_ manner. Anyone can call this function to verify if the `caller` has the right permissions to perform the abi-encoded function call `data` on the {`target`} contract (while sending `msgValue` alongside the call). If the permissions have been verified successfully and `caller` is authorized, one of the following two LSP20 success value will be returned: + +- `0x1a238000`: LSP20 success value **without** post verification (last byte is `0x00`). +- `0x1a238001`: LSP20 success value **with** post-verification (last byte is `0x01`). ::: ```solidity function lsp20VerifyCall( + address, + address targetContract, address caller, uint256 msgValue, - bytes data + bytes callData ) external nonpayable returns (bytes4); ``` #### Parameters -| Name | Type | Description | -| ---------- | :-------: | ----------------------------------------------------- | -| `caller` | `address` | The address who called the function on the msg.sender | -| `msgValue` | `uint256` | - | -| `data` | `bytes` | - | +| Name | Type | Description | +| ---------------- | :-------: | ------------------------------------------------------------- | +| `_0` | `address` | - | +| `targetContract` | `address` | - | +| `caller` | `address` | The address who called the function on the `target` contract. | +| `msgValue` | `uint256` | - | +| `callData` | `bytes` | The calldata sent by the caller to the msg.sender | #### Returns @@ -365,10 +378,10 @@ function lsp20VerifyCall( :::note References -- Specification details: [**LSP-6-KeyManager**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-6-KeyManager.md#,)) +- Specification details: [**LSP-6-KeyManager**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-6-KeyManager.md#lsp20verifycallresult) - Solidity implementation: [`LSP6KeyManager.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP6KeyManager/LSP6KeyManager.sol) -- Function signature: `,)` -- Function selector: `0x9f47dbd3` +- Function signature: `lsp20VerifyCallResult(bytes32,bytes)` +- Function selector: `0xd3fc45d3` ::: @@ -605,22 +618,22 @@ function _getPermissionToSetAllowedCalls( ) internal view returns (bytes32); ``` -retrieve the permission required to set some AllowedCalls for a controller. +Retrieve the permission required to set some AllowedCalls for a controller. #### Parameters | Name | Type | Description | | ---------------------------------------- | :-------: | ------------------------------------------------------------------------------------------- | -| `controlledContract` | `address` | the address of the ERC725Y contract where the data key is verified. | -| `dataKey` | `bytes32` | `AddressPermissions:AllowedCalls:`. | -| `dataValue` | `bytes` | the updated value for the `dataKey`. MUST be a bytes28[CompactBytesArray] of Allowed Calls. | +| `controlledContract` | `address` | The address of the ERC725Y contract from which to fetch the value of `dataKey`. | +| `dataKey` | `bytes32` | A data key ion the format `AddressPermissions:AllowedCalls:`. | +| `dataValue` | `bytes` | The updated value for the `dataKey`. MUST be a bytes32[CompactBytesArray] of Allowed Calls. | | `hasBothAddControllerAndEditPermissions` | `bool` | - | #### Returns -| Name | Type | Description | -| ---- | :-------: | --------------------------------- | -| `0` | `bytes32` | either ADD or CHANGE PERMISSIONS. | +| Name | Type | Description | +| ---- | :-------: | ------------------------------- | +| `0` | `bytes32` | Either ADD or EDIT PERMISSIONS. |
@@ -635,22 +648,22 @@ function _getPermissionToSetAllowedERC725YDataKeys( ) internal view returns (bytes32); ``` -retrieve the permission required to set some Allowed ERC725Y Data Keys for a controller. +Retrieve the permission required to set some Allowed ERC725Y Data Keys for a controller. #### Parameters | Name | Type | Description | | ---------------------------------------- | :-------: | ----------------------------------------------------------------------------------------------------- | -| `controlledContract` | `address` | the address of the ERC725Y contract where the data key is verified. | -| `dataKey` | `bytes32` | or `AddressPermissions:AllowedERC725YDataKeys:`. | -| `dataValue` | `bytes` | the updated value for the `dataKey`. MUST be a bytes[CompactBytesArray] of Allowed ERC725Y Data Keys. | +| `controlledContract` | `address` | the address of the ERC725Y contract from which to fetch the value of `dataKey`. | +| `dataKey` | `bytes32` | A data key in the format `AddressPermissions:AllowedERC725YDataKeys:`. | +| `dataValue` | `bytes` | The updated value for the `dataKey`. MUST be a bytes[CompactBytesArray] of Allowed ERC725Y Data Keys. | | `hasBothAddControllerAndEditPermissions` | `bool` | - | #### Returns -| Name | Type | Description | -| ---- | :-------: | --------------------------------- | -| `0` | `bytes32` | either ADD or CHANGE PERMISSIONS. | +| Name | Type | Description | +| ---- | :-------: | ------------------------------- | +| `0` | `bytes32` | Either ADD or EDIT PERMISSIONS. |
@@ -932,6 +945,17 @@ function _isAllowedCallType(
+### \_verifyExecuteRelayCallPermission + +```solidity +function _verifyExecuteRelayCallPermission( + address controllerAddress, + bytes32 controllerPermissions +) internal pure; +``` + +
+ ### \_verifyOwnershipPermissions ```solidity @@ -1115,6 +1139,7 @@ and conform to the signature format according to the LSP25 standard. ```solidity function _executePayload( + address targetContract, uint256 msgValue, bytes payload ) internal nonpayable returns (bytes); @@ -1124,10 +1149,11 @@ _Execute the `payload` passed to `execute(...)` or `executeRelayCall(...)`_ #### Parameters -| Name | Type | Description | -| ---------- | :-------: | ------------------------------------------------------------------ | -| `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} contract. | #### Returns @@ -1141,8 +1167,9 @@ _Execute the `payload` passed to `execute(...)` or `executeRelayCall(...)`_ ```solidity function _verifyPermissions( + address targetContract, address from, - uint256 msgValue, + bool isRelayedCall, bytes payload ) internal view; ``` @@ -1151,21 +1178,12 @@ Verify if the `from` address is allowed to execute the `payload` on the [`target #### Parameters -| Name | Type | Description | -| ---------- | :-------: | ------------------------------------------------------------------- | -| `from` | `address` | Either the caller of {execute} or the signer of {executeRelayCall}. | -| `msgValue` | `uint256` | - | -| `payload` | `bytes` | The abi-encoded function call to execute on the {target} contract. | - -
- -### \_setupLSP6ReentrancyGuard - -```solidity -function _setupLSP6ReentrancyGuard() internal nonpayable; -``` - -Initialise \_reentrancyStatus to \_NOT_ENTERED. +| 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. |
@@ -1173,9 +1191,10 @@ Initialise \_reentrancyStatus to \_NOT_ENTERED. ```solidity function _nonReentrantBefore( + address targetContract, bool isSetData, address from -) internal nonpayable returns (bool isReentrantCall); +) internal nonpayable returns (bool reentrancyStatus); ``` Update the status from `_NON_ENTERED` to `_ENTERED` and checks if @@ -1187,10 +1206,10 @@ Used in the beginning of the `nonReentrant` modifier, before the method executio ### \_nonReentrantAfter ```solidity -function _nonReentrantAfter() internal nonpayable; +function _nonReentrantAfter(address targetContract) internal nonpayable; ``` -Resets the status to `_NOT_ENTERED` +Resets the status to `false` Used in the end of the `nonReentrant` modifier after the method execution is terminated
@@ -1291,27 +1310,6 @@ Reverts when calling the KeyManager through `execute(uint256,address,uint256,byt
-### CannotSendValueToSetData - -:::note References - -- Specification details: [**LSP-6-KeyManager**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-6-KeyManager.md#cannotsendvaluetosetdata) -- Solidity implementation: [`LSP6KeyManager.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP6KeyManager/LSP6KeyManager.sol) -- Error signature: `CannotSendValueToSetData()` -- Error hash: `0x59a529fc` - -::: - -```solidity -error CannotSendValueToSetData(); -``` - -_Cannot sent native tokens while setting data._ - -Reverts when calling the `setData(byte32,bytes)` or `setData(bytes32[],bytes[]) functions on the linked [`target`](#target) while sending value. - -
- ### DelegateCallDisallowedViaKeyManager :::note References @@ -1405,10 +1403,12 @@ Reverts when trying to call a function on the linked [`target`](#target), that i - `execute(uint256,address,uint256,bytes)` (ERC725X) -- `transferOwnership(address)` +- `transferOwnership(address)` (LSP14) - `acceptOwnership()` (LSP14) +- `renounceOwnership()` (LSP14) + #### Parameters | Name | Type | Description | diff --git a/docs/contracts/LSP7DigitalAsset/LSP7DigitalAsset.md b/docs/contracts/LSP7DigitalAsset/LSP7DigitalAsset.md index 6c62e8c45..047f5ab1a 100644 --- a/docs/contracts/LSP7DigitalAsset/LSP7DigitalAsset.md +++ b/docs/contracts/LSP7DigitalAsset/LSP7DigitalAsset.md @@ -58,6 +58,25 @@ This function is executed when:
+### receive + +:::note References + +- Specification details: [**LSP-7-DigitalAsset**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-7-DigitalAsset.md#receive) +- Solidity implementation: [`LSP7DigitalAsset.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP7DigitalAsset/LSP7DigitalAsset.sol) + +::: + +```solidity +receive() external payable; +``` + +_LSP7 contract cannot receive native tokens._ + +Reverts whenever someone tries to send native tokens to a LSP7 contract. + +
+ ### authorizeOperator :::note References @@ -206,12 +225,12 @@ This is a non-standard function, not part of the LSP7 standard interface. It has ```solidity function decreaseAllowance( address operator, - uint256 substractedAmount, + uint256 subtractedAmount, bytes operatorNotificationData ) external nonpayable; ``` -_Decrease the allowance of `operator` by -`substractedAmount`_ +_Decrease the allowance of `operator` by -`subtractedAmount`_ Atomically decreases the allowance granted to `operator` by the caller. This is an alternative approach to [`authorizeOperator`](#authorizeoperator) that can be used as a mitigation for the double spending allowance problem. @@ -220,7 +239,7 @@ Atomically decreases the allowance granted to `operator` by the caller. This is **Requirements:** - `operator` cannot be the zero address. -- `operator` must have allowance for the caller of at least `substractedAmount`. +- `operator` must have allowance for the caller of at least `subtractedAmount`. @@ -229,7 +248,7 @@ Atomically decreases the allowance granted to `operator` by the caller. This is **Emitted events:** - [`AuthorizedOperator`](#authorizedoperator) event indicating the updated allowance after decreasing it. -- [`RevokeOperator`](#revokeoperator) event if `substractedAmount` is the full allowance, indicating `operator` does not have any alauthorizedAmountForlowance left for `msg.sender`. +- [`RevokeOperator`](#revokeoperator) event if `subtractedAmount` is the full allowance, indicating `operator` does not have any alauthorizedAmountForlowance left for `msg.sender`. @@ -237,8 +256,8 @@ Atomically decreases the allowance granted to `operator` by the caller. This is | Name | Type | Description | | -------------------------- | :-------: | ------------------------------------------------------ | -| `operator` | `address` | the operator to decrease allowance for `msg.sender` | -| `substractedAmount` | `uint256` | the amount to decrease by in the operator's allowance. | +| `operator` | `address` | The operator to decrease allowance for `msg.sender` | +| `subtractedAmount` | `uint256` | The amount to decrease by in the operator's allowance. | | `operatorNotificationData` | `bytes` | - |
@@ -392,8 +411,8 @@ Atomically increases the allowance granted to `operator` by the caller. This is | Name | Type | Description | | -------------------------- | :-------: | ----------------------------------------------------------------------- | -| `operator` | `address` | the operator to increase the allowance for `msg.sender` | -| `addedAmount` | `uint256` | the additional amount to add on top of the current operator's allowance | +| `operator` | `address` | The operator to increase the allowance for `msg.sender` | +| `addedAmount` | `uint256` | The additional amount to add on top of the current operator's allowance | | `operatorNotificationData` | `bytes` | - |
@@ -643,7 +662,7 @@ function transfer( address from, address to, uint256 amount, - bool allowNonLSP1Recipient, + bool force, bytes data ) external nonpayable; ``` @@ -652,13 +671,13 @@ Transfers an `amount` of tokens from the `from` address to the `to` address and #### Parameters -| Name | Type | Description | -| ----------------------- | :-------: | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `from` | `address` | The sender address. | -| `to` | `address` | The recipient address. | -| `amount` | `uint256` | The amount of tokens to transfer. | -| `allowNonLSP1Recipient` | `bool` | When set to `true`, the `to` address CAN be any address. When set to `false`, the `to` address MUST be a contract that supports the LSP1 UniversalReceiver standard. | -| `data` | `bytes` | Any additional data the caller wants included in the emitted event, and sent in the hooks of the `from` and `to` addresses. | +| Name | Type | Description | +| -------- | :-------: | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `from` | `address` | The sender address. | +| `to` | `address` | The recipient address. | +| `amount` | `uint256` | The amount of tokens to transfer. | +| `force` | `bool` | When set to `true`, the `to` address CAN be any address. When set to `false`, the `to` address MUST be a contract that supports the LSP1 UniversalReceiver standard. | +| `data` | `bytes` | Any additional data the caller wants included in the emitted event, and sent in the hooks of the `from` and `to` addresses. |
@@ -678,7 +697,7 @@ function transferBatch( address[] from, address[] to, uint256[] amount, - bool[] allowNonLSP1Recipient, + bool[] force, bytes[] data ) external nonpayable; ``` @@ -687,13 +706,13 @@ Same as [`transfer(...)`](#`transfer) but transfer multiple tokens based on the #### Parameters -| Name | Type | Description | -| ----------------------- | :---------: | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `from` | `address[]` | An array of sending addresses. | -| `to` | `address[]` | An array of receiving addresses. | -| `amount` | `uint256[]` | An array of amount of tokens to transfer for each `from -> to` transfer. | -| `allowNonLSP1Recipient` | `bool[]` | For each transfer, when set to `true`, the `to` address CAN be any address. When set to `false`, the `to` address MUST be a contract that supports the LSP1 UniversalReceiver standard. | -| `data` | `bytes[]` | An array of additional data the caller wants included in the emitted event, and sent in the hooks to `from` and `to` addresses. | +| Name | Type | Description | +| -------- | :---------: | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `from` | `address[]` | An array of sending addresses. | +| `to` | `address[]` | An array of receiving addresses. | +| `amount` | `uint256[]` | An array of amount of tokens to transfer for each `from -> to` transfer. | +| `force` | `bool[]` | For each transfer, when set to `true`, the `to` address CAN be any address. When set to `false`, the `to` address MUST be a contract that supports the LSP1 UniversalReceiver standard. | +| `data` | `bytes[]` | An array of additional data the caller wants included in the emitted event, and sent in the hooks to `from` and `to` addresses. |
@@ -792,7 +811,7 @@ Save gas by emitting the [`DataChanged`](#datachanged) event with only the first function _updateOperator( address tokenOwner, address operator, - uint256 amount, + uint256 allowance, bytes operatorNotificationData ) internal nonpayable; ``` @@ -801,15 +820,34 @@ Changes token `amount` the `operator` has access to from `tokenOwner` tokens. If the amount is zero the operator is removed from the list of operators, otherwise he is added to the list of operators. If the amount is zero then the operator is being revoked, otherwise the operator amount is being modified. +#### Parameters + +| Name | Type | Description | +| -------------------------- | :-------: | -------------------------------------------------------------------------------------- | +| `tokenOwner` | `address` | The address that will give `operator` an allowance for on its balance. | +| `operator` | `address` | The address to grant an allowance to spend. | +| `allowance` | `uint256` | The maximum amount of token that `operator` can spend from the `tokenOwner`'s balance. | +| `operatorNotificationData` | `bytes` | - | +
### \_mint +:::info + +Any logic in the: + +- {\_beforeTokenTransfer} function will run before updating the balances. + +- {\_afterTokenTransfer} function will run after updating the balances, **but before notifying the recipient via LSP1**. + +::: + ```solidity function _mint( address to, uint256 amount, - bool allowNonLSP1Recipient, + bool force, bytes data ) internal nonpayable; ``` @@ -826,17 +864,27 @@ 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. | -| `allowNonLSP1Recipient` | `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} event, and sent in the LSP1 hook to the `to` address. |
### \_burn +:::info + +Any logic in the: + +- {\_beforeTokenTransfer} function will run before updating the balances. + +- {\_afterTokenTransfer} function will run after updating the balances, **but before notifying the sender via LSP1**. + +::: + :::tip Hint In dApps, you can know which address is burning tokens by listening for the `Transfer` event and filter with the zero address as `to`. @@ -851,7 +899,6 @@ Burns (= destroys) `amount` of tokens, decrease the `from` balance. This is done Both the sender and recipient will be notified of the token transfer through the LSP1 [`universalReceiver`](#universalreceiver) function, if they are contracts that support the LSP1 interface. Their `universalReceiver` function will receive all the parameters in the calldata packed encoded. -Any logic in the [`_beforeTokenTransfer`](#_beforetokentransfer) function will run before updating the balances.
@@ -865,20 +912,52 @@ Any logic in the [`_beforeTokenTransfer`](#_beforetokentransfer) function will r | Name | Type | Description | | -------- | :-------: | ------------------------------------------------------------------------------------------------------------------------- | -| `from` | `address` | the address to burn tokens from its balance. | -| `amount` | `uint256` | the amount of tokens to burn. | +| `from` | `address` | The address to burn tokens from its balance. | +| `amount` | `uint256` | The amount of tokens to burn. | | `data` | `bytes` | Additional data the caller wants included in the emitted event, and sent in the LSP1 hook to the `from` and `to` address. |
+### \_spendAllowance + +```solidity +function _spendAllowance( + address operator, + address tokenOwner, + uint256 amountToSpend +) internal nonpayable; +``` + +Spend `amountToSpend` from the `operator`'s authorized on behalf of the `tokenOwner`. + +#### Parameters + +| Name | Type | Description | +| --------------- | :-------: | ------------------------------------------------------------------- | +| `operator` | `address` | The address of the operator to decrease the allowance of. | +| `tokenOwner` | `address` | The address that granted an allowance on its balance to `operator`. | +| `amountToSpend` | `uint256` | The amount of tokens to substract in allowance of `operator`. | + +
+ ### \_transfer +:::info + +Any logic in the: + +- {\_beforeTokenTransfer} function will run before updating the balances. + +- {\_afterTokenTransfer} function will run after updating the balances, **but before notifying the sender/recipient via LSP1**. + +::: + ```solidity function _transfer( address from, address to, uint256 amount, - bool allowNonLSP1Recipient, + bool force, bytes data ) internal nonpayable; ``` @@ -888,7 +967,6 @@ of `to` by `+amount`. Both the sender and recipient will be notified of the token transfer through the LSP1 [`universalReceiver`](#universalreceiver) function, if they are contracts that support the LSP1 interface. Their `universalReceiver` function will receive all the parameters in the calldata packed encoded. -Any logic in the [`_beforeTokenTransfer`](#_beforetokentransfer) function will run before updating the balances.
@@ -900,13 +978,13 @@ Any logic in the [`_beforeTokenTransfer`](#_beforetokentransfer) function will r #### Parameters -| Name | Type | Description | -| ----------------------- | :-------: | ------------------------------------------------------------------------------------------------------------------------- | -| `from` | `address` | the address to decrease the balance. | -| `to` | `address` | the address to increase the balance. | -| `amount` | `uint256` | the amount of tokens to transfer from `from` to `to`. | -| `allowNonLSP1Recipient` | `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 event, and sent in the LSP1 hook to the `from` and `to` address. | +| Name | Type | Description | +| -------- | :-------: | ------------------------------------------------------------------------------------------------------------------------- | +| `from` | `address` | The address to decrease the balance. | +| `to` | `address` | The address to increase the balance. | +| `amount` | `uint256` | The amount of tokens to transfer from `from` to `to`. | +| `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 event, and sent in the LSP1 hook to the `from` and `to` address. |
@@ -916,7 +994,8 @@ Any logic in the [`_beforeTokenTransfer`](#_beforetokentransfer) function will r function _beforeTokenTransfer( address from, address to, - uint256 amount + uint256 amount, + bytes data ) internal nonpayable; ``` @@ -925,11 +1004,37 @@ Allows to run custom logic before updating balances and notifiying sender/recipi #### Parameters -| Name | Type | Description | -| -------- | :-------: | ------------------------------- | -| `from` | `address` | The sender address | -| `to` | `address` | The recipient address | -| `amount` | `uint256` | The amount of token to transfer | +| Name | Type | Description | +| -------- | :-------: | ------------------------------------ | +| `from` | `address` | The sender address | +| `to` | `address` | The recipient address | +| `amount` | `uint256` | The amount of token to transfer | +| `data` | `bytes` | The data sent alongside the transfer | + +
+ +### \_afterTokenTransfer + +```solidity +function _afterTokenTransfer( + address from, + address to, + uint256 amount, + bytes data +) internal nonpayable; +``` + +Hook that is called after any token transfer, including minting and burning. +Allows to run custom logic after updating balances, but **before notifiying sender/recipient** by overriding this function. + +#### Parameters + +| Name | Type | Description | +| -------- | :-------: | ------------------------------------ | +| `from` | `address` | The sender address | +| `to` | `address` | The recipient address | +| `amount` | `uint256` | The amount of token to transfer | +| `data` | `bytes` | The data sent alongside the transfer |
@@ -979,26 +1084,26 @@ If `from` is an EOA or a contract that does not support the LSP1 interface, noth ```solidity function _notifyTokenReceiver( address to, - bool allowNonLSP1Recipient, + bool force, bytes lsp1Data ) internal nonpayable; ``` Attempt to notify the token receiver `to` about the `amount` tokens being received. This is done by calling its [`universalReceiver`](#universalreceiver) function with the `_TYPEID_LSP7_TOKENSRECIPIENT` as typeId, if `to` is a contract that supports the LSP1 interface. -If `to` is is an EOA or a contract that does not support the LSP1 interface, the behaviour will depend on the `allowNonLSP1Recipient` boolean flag. +If `to` is is an EOA or a contract that does not support the LSP1 interface, the behaviour will depend on the `force` boolean flag. -- if `allowNonLSP1Recipient` is set to `true`, nothing will happen and no notification will be sent. +- if `force` is set to `true`, nothing will happen and no notification will be sent. -- if `allowNonLSP1Recipient` is set to `false, the transaction will revert. +- if `force` is set to `false, the transaction will revert. #### Parameters -| Name | Type | Description | -| ----------------------- | :-------: | --------------------------------------------------------------------------------------------------- | -| `to` | `address` | The address to call the {universalReceiver} function on. | -| `allowNonLSP1Recipient` | `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. | +| Name | Type | Description | +| ---------- | :-------: | --------------------------------------------------------------------------------------------------- | +| `to` | `address` | The address to call the {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. |
@@ -1034,6 +1139,13 @@ Returns the extension address stored under the following data key: ### \_fallbackLSP17Extendable +:::info + +The LSP7 Token contract should not hold any native tokens. Any native tokens received by the contract +will be forwarded to the extension address mapped to the selector from `msg.sig`. + +::: + ```solidity function _fallbackLSP17Extendable( bytes callData @@ -1047,9 +1159,6 @@ Reverts if there is no extension for the function being called. If there is an extension for the function selector being called, it calls the extension with the CALL opcode, passing the [`msg.data`](#msg.data) appended with the 20 bytes of the [`msg.sender`](#msg.sender) and 32 bytes of the [`msg.value`](#msg.value) -Because the function uses assembly [`return()/revert()`](#return) to terminate the call, it cannot be -called before other codes in fallback(). -Otherwise, the codes after \_fallbackLSP17Extendable() may never be reached.
@@ -1070,14 +1179,14 @@ Otherwise, the codes after \_fallbackLSP17Extendable() may never be reached. event AuthorizedOperator(address indexed operator, address indexed tokenOwner, uint256 indexed amount, bytes operatorNotificationData); ``` -Emitted when `tokenOwner` enables `operator` to transfer or burn the `tokenId`. +Emitted when `tokenOwner` enables `operator` for `amount` tokens. #### Parameters | Name | Type | Description | | -------------------------- | :-------: | ----------------------------------------------------------------------- | -| `operator` **`indexed`** | `address` | The address authorized as an operator. | -| `tokenOwner` **`indexed`** | `address` | The owner of the `tokenId`. | +| `operator` **`indexed`** | `address` | The address authorized as an operator | +| `tokenOwner` **`indexed`** | `address` | The token owner | | `amount` **`indexed`** | `uint256` | The amount of tokens `operator` address has access to from `tokenOwner` | | `operatorNotificationData` | `bytes` | The data to notify the operator about via LSP1. | @@ -1150,15 +1259,15 @@ event OwnershipTransferred(address indexed previousOwner, address indexed newOwn event RevokedOperator(address indexed operator, address indexed tokenOwner, bytes operatorNotificationData); ``` -Emitted when `tokenOwner` disables `operator` to transfer or burn `tokenId` on its behalf. +Emitted when `tokenOwner` disables `operator` for `amount` tokens and set its [`authorizedAmountFor(...)`](#`authorizedamountfor) to `0`. #### Parameters -| Name | Type | Description | -| -------------------------- | :-------: | --------------------------------------------------------------- | -| `operator` **`indexed`** | `address` | The address revoked from the operator array ({getOperatorsOf}). | -| `tokenOwner` **`indexed`** | `address` | The owner of the `tokenId`. | -| `operatorNotificationData` | `bytes` | The data to notify the operator about via LSP1. | +| Name | Type | Description | +| -------------------------- | :-------: | ----------------------------------------------- | +| `operator` **`indexed`** | `address` | The address revoked from operating | +| `tokenOwner` **`indexed`** | `address` | The token owner | +| `operatorNotificationData` | `bytes` | The data to notify the operator about via LSP1. |
@@ -1174,21 +1283,21 @@ Emitted when `tokenOwner` disables `operator` to transfer or burn `tokenId` on i ::: ```solidity -event Transfer(address indexed operator, address indexed from, address indexed to, uint256 amount, bool allowNonLSP1Recipient, bytes data); +event Transfer(address indexed operator, address indexed from, address indexed to, uint256 amount, bool force, bytes data); ``` -Emitted when `tokenId` token is transferred from the `from` to the `to` address. +Emitted when the `from` transferred successfully `amount` of tokens to `to`. #### Parameters -| Name | Type | Description | -| ------------------------ | :-------: | ---------------------------------------------------------------------------------------------------------------------------------- | -| `operator` **`indexed`** | `address` | The address of operator that sent the `tokenId` | -| `from` **`indexed`** | `address` | The previous owner of the `tokenId` | -| `to` **`indexed`** | `address` | The new owner of `tokenId` | -| `amount` | `uint256` | The amount of tokens transferred. | -| `allowNonLSP1Recipient` | `bool` | If the token transfer enforces the `to` recipient address to be a contract that implements the LSP1 standard or not. | -| `data` | `bytes` | Any additional data the caller included by the caller during the transfer, and sent in the hooks to the `from` and `to` addresses. | +| Name | Type | Description | +| ------------------------ | :-------: | ---------------------------------------------------------------------------------------------------------------------------- | +| `operator` **`indexed`** | `address` | The address of the operator that executed the transfer. | +| `from` **`indexed`** | `address` | The address which tokens were sent from (balance decreased by `-amount`). | +| `to` **`indexed`** | `address` | The address that received the tokens (balance increased by `+amount`). | +| `amount` | `uint256` | The amount of tokens transferred. | +| `force` | `bool` | if the transferred enforced the `to` recipient address to be a contract that implements the LSP1 standard or not. | +| `data` | `bytes` | Any additional data included by the caller during the transfer, and sent in the LSP1 hooks to the `from` and `to` addresses. |
@@ -1521,7 +1630,7 @@ error LSP7NotifyTokenReceiverContractMissingLSP1Interface( ); ``` -reverts if the `tokenReceiver` does not implement LSP1 when minting or transferring tokens with `bool allowNonLSP1Recipient` set as `false`. +reverts if the `tokenReceiver` does not implement LSP1 when minting or transferring tokens with `bool force` set as `false`. #### Parameters @@ -1546,7 +1655,7 @@ reverts if the `tokenReceiver` does not implement LSP1 when minting or transferr error LSP7NotifyTokenReceiverIsEOA(address tokenReceiver); ``` -reverts if the `tokenReceiver` is an EOA when minting or transferring tokens with `bool allowNonLSP1Recipient` set as `false`. +reverts if the `tokenReceiver` is an EOA when minting or transferring tokens with `bool force` set as `false`. #### Parameters @@ -1556,6 +1665,27 @@ reverts if the `tokenReceiver` is an EOA when minting or transferring tokens wit
+### LSP7TokenContractCannotHoldValue + +:::note References + +- Specification details: [**LSP-7-DigitalAsset**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-7-DigitalAsset.md#lsp7tokencontractcannotholdvalue) +- Solidity implementation: [`LSP7DigitalAsset.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP7DigitalAsset/LSP7DigitalAsset.sol) +- Error signature: `LSP7TokenContractCannotHoldValue()` +- Error hash: `0x388f5adc` + +::: + +```solidity +error LSP7TokenContractCannotHoldValue(); +``` + +_LSP7 contract cannot receive native tokens._ + +Error occurs when sending native tokens to the LSP7 contract without sending any data. E.g. Sending value without passing a bytes4 function selector to call a LSP17 Extension. + +
+ ### LSP7TokenOwnerCannotBeOperator :::note References @@ -1599,3 +1729,47 @@ reverts when there is no extension for the function selector being called with | `functionSelector` | `bytes4` | - |
+ +### OwnableCallerNotTheOwner + +:::note References + +- Specification details: [**LSP-7-DigitalAsset**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-7-DigitalAsset.md#ownablecallernottheowner) +- Solidity implementation: [`LSP7DigitalAsset.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP7DigitalAsset/LSP7DigitalAsset.sol) +- Error signature: `OwnableCallerNotTheOwner(address)` +- Error hash: `0xbf1169c5` + +::: + +```solidity +error OwnableCallerNotTheOwner(address callerAddress); +``` + +Reverts when only the owner is allowed to call the function. + +#### Parameters + +| Name | Type | Description | +| --------------- | :-------: | ---------------------------------------- | +| `callerAddress` | `address` | The address that tried to make the call. | + +
+ +### OwnableCannotSetZeroAddressAsOwner + +:::note References + +- Specification details: [**LSP-7-DigitalAsset**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-7-DigitalAsset.md#ownablecannotsetzeroaddressasowner) +- Solidity implementation: [`LSP7DigitalAsset.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP7DigitalAsset/LSP7DigitalAsset.sol) +- Error signature: `OwnableCannotSetZeroAddressAsOwner()` +- Error hash: `0x1ad8836c` + +::: + +```solidity +error OwnableCannotSetZeroAddressAsOwner(); +``` + +Reverts when trying to set `address(0)` as the contract owner when deploying the contract, initializing it or transferring ownership of the contract. + +
diff --git a/docs/contracts/LSP7DigitalAsset/extensions/LSP7Burnable.md b/docs/contracts/LSP7DigitalAsset/extensions/LSP7Burnable.md index 7322b5e9e..f0c7d2b96 100644 --- a/docs/contracts/LSP7DigitalAsset/extensions/LSP7Burnable.md +++ b/docs/contracts/LSP7DigitalAsset/extensions/LSP7Burnable.md @@ -56,6 +56,25 @@ This function is executed when:
+### receive + +:::note References + +- Specification details: [**LSP-7-DigitalAsset**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-7-DigitalAsset.md#receive) +- Solidity implementation: [`LSP7Burnable.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP7DigitalAsset/extensions/LSP7Burnable.sol) + +::: + +```solidity +receive() external payable; +``` + +_LSP7 contract cannot receive native tokens._ + +Reverts whenever someone tries to send native tokens to a LSP7 contract. + +
+ ### authorizeOperator :::note References @@ -231,12 +250,12 @@ This is a non-standard function, not part of the LSP7 standard interface. It has ```solidity function decreaseAllowance( address operator, - uint256 substractedAmount, + uint256 subtractedAmount, bytes operatorNotificationData ) external nonpayable; ``` -_Decrease the allowance of `operator` by -`substractedAmount`_ +_Decrease the allowance of `operator` by -`subtractedAmount`_ Atomically decreases the allowance granted to `operator` by the caller. This is an alternative approach to [`authorizeOperator`](#authorizeoperator) that can be used as a mitigation for the double spending allowance problem. @@ -245,7 +264,7 @@ Atomically decreases the allowance granted to `operator` by the caller. This is **Requirements:** - `operator` cannot be the zero address. -- `operator` must have allowance for the caller of at least `substractedAmount`. +- `operator` must have allowance for the caller of at least `subtractedAmount`.
@@ -254,7 +273,7 @@ Atomically decreases the allowance granted to `operator` by the caller. This is **Emitted events:** - [`AuthorizedOperator`](#authorizedoperator) event indicating the updated allowance after decreasing it. -- [`RevokeOperator`](#revokeoperator) event if `substractedAmount` is the full allowance, indicating `operator` does not have any alauthorizedAmountForlowance left for `msg.sender`. +- [`RevokeOperator`](#revokeoperator) event if `subtractedAmount` is the full allowance, indicating `operator` does not have any alauthorizedAmountForlowance left for `msg.sender`.
@@ -262,8 +281,8 @@ Atomically decreases the allowance granted to `operator` by the caller. This is | Name | Type | Description | | -------------------------- | :-------: | ------------------------------------------------------ | -| `operator` | `address` | the operator to decrease allowance for `msg.sender` | -| `substractedAmount` | `uint256` | the amount to decrease by in the operator's allowance. | +| `operator` | `address` | The operator to decrease allowance for `msg.sender` | +| `subtractedAmount` | `uint256` | The amount to decrease by in the operator's allowance. | | `operatorNotificationData` | `bytes` | - |
@@ -417,8 +436,8 @@ Atomically increases the allowance granted to `operator` by the caller. This is | Name | Type | Description | | -------------------------- | :-------: | ----------------------------------------------------------------------- | -| `operator` | `address` | the operator to increase the allowance for `msg.sender` | -| `addedAmount` | `uint256` | the additional amount to add on top of the current operator's allowance | +| `operator` | `address` | The operator to increase the allowance for `msg.sender` | +| `addedAmount` | `uint256` | The additional amount to add on top of the current operator's allowance | | `operatorNotificationData` | `bytes` | - |
@@ -668,7 +687,7 @@ function transfer( address from, address to, uint256 amount, - bool allowNonLSP1Recipient, + bool force, bytes data ) external nonpayable; ``` @@ -677,13 +696,13 @@ Transfers an `amount` of tokens from the `from` address to the `to` address and #### Parameters -| Name | Type | Description | -| ----------------------- | :-------: | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `from` | `address` | The sender address. | -| `to` | `address` | The recipient address. | -| `amount` | `uint256` | The amount of tokens to transfer. | -| `allowNonLSP1Recipient` | `bool` | When set to `true`, the `to` address CAN be any address. When set to `false`, the `to` address MUST be a contract that supports the LSP1 UniversalReceiver standard. | -| `data` | `bytes` | Any additional data the caller wants included in the emitted event, and sent in the hooks of the `from` and `to` addresses. | +| Name | Type | Description | +| -------- | :-------: | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `from` | `address` | The sender address. | +| `to` | `address` | The recipient address. | +| `amount` | `uint256` | The amount of tokens to transfer. | +| `force` | `bool` | When set to `true`, the `to` address CAN be any address. When set to `false`, the `to` address MUST be a contract that supports the LSP1 UniversalReceiver standard. | +| `data` | `bytes` | Any additional data the caller wants included in the emitted event, and sent in the hooks of the `from` and `to` addresses. |
@@ -703,7 +722,7 @@ function transferBatch( address[] from, address[] to, uint256[] amount, - bool[] allowNonLSP1Recipient, + bool[] force, bytes[] data ) external nonpayable; ``` @@ -712,13 +731,13 @@ Same as [`transfer(...)`](#`transfer) but transfer multiple tokens based on the #### Parameters -| Name | Type | Description | -| ----------------------- | :---------: | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `from` | `address[]` | An array of sending addresses. | -| `to` | `address[]` | An array of receiving addresses. | -| `amount` | `uint256[]` | An array of amount of tokens to transfer for each `from -> to` transfer. | -| `allowNonLSP1Recipient` | `bool[]` | For each transfer, when set to `true`, the `to` address CAN be any address. When set to `false`, the `to` address MUST be a contract that supports the LSP1 UniversalReceiver standard. | -| `data` | `bytes[]` | An array of additional data the caller wants included in the emitted event, and sent in the hooks to `from` and `to` addresses. | +| Name | Type | Description | +| -------- | :---------: | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `from` | `address[]` | An array of sending addresses. | +| `to` | `address[]` | An array of receiving addresses. | +| `amount` | `uint256[]` | An array of amount of tokens to transfer for each `from -> to` transfer. | +| `force` | `bool[]` | For each transfer, when set to `true`, the `to` address CAN be any address. When set to `false`, the `to` address MUST be a contract that supports the LSP1 UniversalReceiver standard. | +| `data` | `bytes[]` | An array of additional data the caller wants included in the emitted event, and sent in the hooks to `from` and `to` addresses. |
@@ -817,7 +836,7 @@ Save gas by emitting the [`DataChanged`](#datachanged) event with only the first function _updateOperator( address tokenOwner, address operator, - uint256 amount, + uint256 allowance, bytes operatorNotificationData ) internal nonpayable; ``` @@ -826,15 +845,34 @@ Changes token `amount` the `operator` has access to from `tokenOwner` tokens. If the amount is zero the operator is removed from the list of operators, otherwise he is added to the list of operators. If the amount is zero then the operator is being revoked, otherwise the operator amount is being modified. +#### Parameters + +| Name | Type | Description | +| -------------------------- | :-------: | -------------------------------------------------------------------------------------- | +| `tokenOwner` | `address` | The address that will give `operator` an allowance for on its balance. | +| `operator` | `address` | The address to grant an allowance to spend. | +| `allowance` | `uint256` | The maximum amount of token that `operator` can spend from the `tokenOwner`'s balance. | +| `operatorNotificationData` | `bytes` | - | +
### \_mint +:::info + +Any logic in the: + +- {\_beforeTokenTransfer} function will run before updating the balances. + +- {\_afterTokenTransfer} function will run after updating the balances, **but before notifying the recipient via LSP1**. + +::: + ```solidity function _mint( address to, uint256 amount, - bool allowNonLSP1Recipient, + bool force, bytes data ) internal nonpayable; ``` @@ -851,17 +889,27 @@ 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. | -| `allowNonLSP1Recipient` | `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} event, and sent in the LSP1 hook to the `to` address. |
### \_burn +:::info + +Any logic in the: + +- {\_beforeTokenTransfer} function will run before updating the balances. + +- {\_afterTokenTransfer} function will run after updating the balances, **but before notifying the sender via LSP1**. + +::: + :::tip Hint In dApps, you can know which address is burning tokens by listening for the `Transfer` event and filter with the zero address as `to`. @@ -876,7 +924,6 @@ Burns (= destroys) `amount` of tokens, decrease the `from` balance. This is done Both the sender and recipient will be notified of the token transfer through the LSP1 [`universalReceiver`](#universalreceiver) function, if they are contracts that support the LSP1 interface. Their `universalReceiver` function will receive all the parameters in the calldata packed encoded. -Any logic in the [`_beforeTokenTransfer`](#_beforetokentransfer) function will run before updating the balances.
@@ -890,20 +937,52 @@ Any logic in the [`_beforeTokenTransfer`](#_beforetokentransfer) function will r | Name | Type | Description | | -------- | :-------: | ------------------------------------------------------------------------------------------------------------------------- | -| `from` | `address` | the address to burn tokens from its balance. | -| `amount` | `uint256` | the amount of tokens to burn. | +| `from` | `address` | The address to burn tokens from its balance. | +| `amount` | `uint256` | The amount of tokens to burn. | | `data` | `bytes` | Additional data the caller wants included in the emitted event, and sent in the LSP1 hook to the `from` and `to` address. |
+### \_spendAllowance + +```solidity +function _spendAllowance( + address operator, + address tokenOwner, + uint256 amountToSpend +) internal nonpayable; +``` + +Spend `amountToSpend` from the `operator`'s authorized on behalf of the `tokenOwner`. + +#### Parameters + +| Name | Type | Description | +| --------------- | :-------: | ------------------------------------------------------------------- | +| `operator` | `address` | The address of the operator to decrease the allowance of. | +| `tokenOwner` | `address` | The address that granted an allowance on its balance to `operator`. | +| `amountToSpend` | `uint256` | The amount of tokens to substract in allowance of `operator`. | + +
+ ### \_transfer +:::info + +Any logic in the: + +- {\_beforeTokenTransfer} function will run before updating the balances. + +- {\_afterTokenTransfer} function will run after updating the balances, **but before notifying the sender/recipient via LSP1**. + +::: + ```solidity function _transfer( address from, address to, uint256 amount, - bool allowNonLSP1Recipient, + bool force, bytes data ) internal nonpayable; ``` @@ -913,7 +992,6 @@ of `to` by `+amount`. Both the sender and recipient will be notified of the token transfer through the LSP1 [`universalReceiver`](#universalreceiver) function, if they are contracts that support the LSP1 interface. Their `universalReceiver` function will receive all the parameters in the calldata packed encoded. -Any logic in the [`_beforeTokenTransfer`](#_beforetokentransfer) function will run before updating the balances.
@@ -925,13 +1003,13 @@ Any logic in the [`_beforeTokenTransfer`](#_beforetokentransfer) function will r #### Parameters -| Name | Type | Description | -| ----------------------- | :-------: | ------------------------------------------------------------------------------------------------------------------------- | -| `from` | `address` | the address to decrease the balance. | -| `to` | `address` | the address to increase the balance. | -| `amount` | `uint256` | the amount of tokens to transfer from `from` to `to`. | -| `allowNonLSP1Recipient` | `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 event, and sent in the LSP1 hook to the `from` and `to` address. | +| Name | Type | Description | +| -------- | :-------: | ------------------------------------------------------------------------------------------------------------------------- | +| `from` | `address` | The address to decrease the balance. | +| `to` | `address` | The address to increase the balance. | +| `amount` | `uint256` | The amount of tokens to transfer from `from` to `to`. | +| `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 event, and sent in the LSP1 hook to the `from` and `to` address. |
@@ -941,7 +1019,8 @@ Any logic in the [`_beforeTokenTransfer`](#_beforetokentransfer) function will r function _beforeTokenTransfer( address from, address to, - uint256 amount + uint256 amount, + bytes data ) internal nonpayable; ``` @@ -950,11 +1029,37 @@ Allows to run custom logic before updating balances and notifiying sender/recipi #### Parameters -| Name | Type | Description | -| -------- | :-------: | ------------------------------- | -| `from` | `address` | The sender address | -| `to` | `address` | The recipient address | -| `amount` | `uint256` | The amount of token to transfer | +| Name | Type | Description | +| -------- | :-------: | ------------------------------------ | +| `from` | `address` | The sender address | +| `to` | `address` | The recipient address | +| `amount` | `uint256` | The amount of token to transfer | +| `data` | `bytes` | The data sent alongside the transfer | + +
+ +### \_afterTokenTransfer + +```solidity +function _afterTokenTransfer( + address from, + address to, + uint256 amount, + bytes data +) internal nonpayable; +``` + +Hook that is called after any token transfer, including minting and burning. +Allows to run custom logic after updating balances, but **before notifiying sender/recipient** by overriding this function. + +#### Parameters + +| Name | Type | Description | +| -------- | :-------: | ------------------------------------ | +| `from` | `address` | The sender address | +| `to` | `address` | The recipient address | +| `amount` | `uint256` | The amount of token to transfer | +| `data` | `bytes` | The data sent alongside the transfer |
@@ -1004,26 +1109,26 @@ If `from` is an EOA or a contract that does not support the LSP1 interface, noth ```solidity function _notifyTokenReceiver( address to, - bool allowNonLSP1Recipient, + bool force, bytes lsp1Data ) internal nonpayable; ``` Attempt to notify the token receiver `to` about the `amount` tokens being received. This is done by calling its [`universalReceiver`](#universalreceiver) function with the `_TYPEID_LSP7_TOKENSRECIPIENT` as typeId, if `to` is a contract that supports the LSP1 interface. -If `to` is is an EOA or a contract that does not support the LSP1 interface, the behaviour will depend on the `allowNonLSP1Recipient` boolean flag. +If `to` is is an EOA or a contract that does not support the LSP1 interface, the behaviour will depend on the `force` boolean flag. -- if `allowNonLSP1Recipient` is set to `true`, nothing will happen and no notification will be sent. +- if `force` is set to `true`, nothing will happen and no notification will be sent. -- if `allowNonLSP1Recipient` is set to `false, the transaction will revert. +- if `force` is set to `false, the transaction will revert. #### Parameters -| Name | Type | Description | -| ----------------------- | :-------: | --------------------------------------------------------------------------------------------------- | -| `to` | `address` | The address to call the {universalReceiver} function on. | -| `allowNonLSP1Recipient` | `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. | +| Name | Type | Description | +| ---------- | :-------: | --------------------------------------------------------------------------------------------------- | +| `to` | `address` | The address to call the {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. |
@@ -1059,6 +1164,13 @@ Returns the extension address stored under the following data key: ### \_fallbackLSP17Extendable +:::info + +The LSP7 Token contract should not hold any native tokens. Any native tokens received by the contract +will be forwarded to the extension address mapped to the selector from `msg.sig`. + +::: + ```solidity function _fallbackLSP17Extendable( bytes callData @@ -1072,9 +1184,6 @@ Reverts if there is no extension for the function being called. If there is an extension for the function selector being called, it calls the extension with the CALL opcode, passing the [`msg.data`](#msg.data) appended with the 20 bytes of the [`msg.sender`](#msg.sender) and 32 bytes of the [`msg.value`](#msg.value) -Because the function uses assembly [`return()/revert()`](#return) to terminate the call, it cannot be -called before other codes in fallback(). -Otherwise, the codes after \_fallbackLSP17Extendable() may never be reached.
@@ -1095,14 +1204,14 @@ Otherwise, the codes after \_fallbackLSP17Extendable() may never be reached. event AuthorizedOperator(address indexed operator, address indexed tokenOwner, uint256 indexed amount, bytes operatorNotificationData); ``` -Emitted when `tokenOwner` enables `operator` to transfer or burn the `tokenId`. +Emitted when `tokenOwner` enables `operator` for `amount` tokens. #### Parameters | Name | Type | Description | | -------------------------- | :-------: | ----------------------------------------------------------------------- | -| `operator` **`indexed`** | `address` | The address authorized as an operator. | -| `tokenOwner` **`indexed`** | `address` | The owner of the `tokenId`. | +| `operator` **`indexed`** | `address` | The address authorized as an operator | +| `tokenOwner` **`indexed`** | `address` | The token owner | | `amount` **`indexed`** | `uint256` | The amount of tokens `operator` address has access to from `tokenOwner` | | `operatorNotificationData` | `bytes` | The data to notify the operator about via LSP1. | @@ -1175,15 +1284,15 @@ event OwnershipTransferred(address indexed previousOwner, address indexed newOwn event RevokedOperator(address indexed operator, address indexed tokenOwner, bytes operatorNotificationData); ``` -Emitted when `tokenOwner` disables `operator` to transfer or burn `tokenId` on its behalf. +Emitted when `tokenOwner` disables `operator` for `amount` tokens and set its [`authorizedAmountFor(...)`](#`authorizedamountfor) to `0`. #### Parameters -| Name | Type | Description | -| -------------------------- | :-------: | --------------------------------------------------------------- | -| `operator` **`indexed`** | `address` | The address revoked from the operator array ({getOperatorsOf}). | -| `tokenOwner` **`indexed`** | `address` | The owner of the `tokenId`. | -| `operatorNotificationData` | `bytes` | The data to notify the operator about via LSP1. | +| Name | Type | Description | +| -------------------------- | :-------: | ----------------------------------------------- | +| `operator` **`indexed`** | `address` | The address revoked from operating | +| `tokenOwner` **`indexed`** | `address` | The token owner | +| `operatorNotificationData` | `bytes` | The data to notify the operator about via LSP1. |
@@ -1199,21 +1308,21 @@ Emitted when `tokenOwner` disables `operator` to transfer or burn `tokenId` on i ::: ```solidity -event Transfer(address indexed operator, address indexed from, address indexed to, uint256 amount, bool allowNonLSP1Recipient, bytes data); +event Transfer(address indexed operator, address indexed from, address indexed to, uint256 amount, bool force, bytes data); ``` -Emitted when `tokenId` token is transferred from the `from` to the `to` address. +Emitted when the `from` transferred successfully `amount` of tokens to `to`. #### Parameters -| Name | Type | Description | -| ------------------------ | :-------: | ---------------------------------------------------------------------------------------------------------------------------------- | -| `operator` **`indexed`** | `address` | The address of operator that sent the `tokenId` | -| `from` **`indexed`** | `address` | The previous owner of the `tokenId` | -| `to` **`indexed`** | `address` | The new owner of `tokenId` | -| `amount` | `uint256` | The amount of tokens transferred. | -| `allowNonLSP1Recipient` | `bool` | If the token transfer enforces the `to` recipient address to be a contract that implements the LSP1 standard or not. | -| `data` | `bytes` | Any additional data the caller included by the caller during the transfer, and sent in the hooks to the `from` and `to` addresses. | +| Name | Type | Description | +| ------------------------ | :-------: | ---------------------------------------------------------------------------------------------------------------------------- | +| `operator` **`indexed`** | `address` | The address of the operator that executed the transfer. | +| `from` **`indexed`** | `address` | The address which tokens were sent from (balance decreased by `-amount`). | +| `to` **`indexed`** | `address` | The address that received the tokens (balance increased by `+amount`). | +| `amount` | `uint256` | The amount of tokens transferred. | +| `force` | `bool` | if the transferred enforced the `to` recipient address to be a contract that implements the LSP1 standard or not. | +| `data` | `bytes` | Any additional data included by the caller during the transfer, and sent in the LSP1 hooks to the `from` and `to` addresses. |
@@ -1546,7 +1655,7 @@ error LSP7NotifyTokenReceiverContractMissingLSP1Interface( ); ``` -reverts if the `tokenReceiver` does not implement LSP1 when minting or transferring tokens with `bool allowNonLSP1Recipient` set as `false`. +reverts if the `tokenReceiver` does not implement LSP1 when minting or transferring tokens with `bool force` set as `false`. #### Parameters @@ -1571,7 +1680,7 @@ reverts if the `tokenReceiver` does not implement LSP1 when minting or transferr error LSP7NotifyTokenReceiverIsEOA(address tokenReceiver); ``` -reverts if the `tokenReceiver` is an EOA when minting or transferring tokens with `bool allowNonLSP1Recipient` set as `false`. +reverts if the `tokenReceiver` is an EOA when minting or transferring tokens with `bool force` set as `false`. #### Parameters @@ -1581,6 +1690,27 @@ reverts if the `tokenReceiver` is an EOA when minting or transferring tokens wit
+### LSP7TokenContractCannotHoldValue + +:::note References + +- Specification details: [**LSP-7-DigitalAsset**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-7-DigitalAsset.md#lsp7tokencontractcannotholdvalue) +- Solidity implementation: [`LSP7Burnable.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP7DigitalAsset/extensions/LSP7Burnable.sol) +- Error signature: `LSP7TokenContractCannotHoldValue()` +- Error hash: `0x388f5adc` + +::: + +```solidity +error LSP7TokenContractCannotHoldValue(); +``` + +_LSP7 contract cannot receive native tokens._ + +Error occurs when sending native tokens to the LSP7 contract without sending any data. E.g. Sending value without passing a bytes4 function selector to call a LSP17 Extension. + +
+ ### LSP7TokenOwnerCannotBeOperator :::note References @@ -1624,3 +1754,47 @@ reverts when there is no extension for the function selector being called with | `functionSelector` | `bytes4` | - |
+ +### OwnableCallerNotTheOwner + +:::note References + +- Specification details: [**LSP-7-DigitalAsset**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-7-DigitalAsset.md#ownablecallernottheowner) +- Solidity implementation: [`LSP7Burnable.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP7DigitalAsset/extensions/LSP7Burnable.sol) +- Error signature: `OwnableCallerNotTheOwner(address)` +- Error hash: `0xbf1169c5` + +::: + +```solidity +error OwnableCallerNotTheOwner(address callerAddress); +``` + +Reverts when only the owner is allowed to call the function. + +#### Parameters + +| Name | Type | Description | +| --------------- | :-------: | ---------------------------------------- | +| `callerAddress` | `address` | The address that tried to make the call. | + +
+ +### OwnableCannotSetZeroAddressAsOwner + +:::note References + +- Specification details: [**LSP-7-DigitalAsset**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-7-DigitalAsset.md#ownablecannotsetzeroaddressasowner) +- Solidity implementation: [`LSP7Burnable.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP7DigitalAsset/extensions/LSP7Burnable.sol) +- Error signature: `OwnableCannotSetZeroAddressAsOwner()` +- Error hash: `0x1ad8836c` + +::: + +```solidity +error OwnableCannotSetZeroAddressAsOwner(); +``` + +Reverts when trying to set `address(0)` as the contract owner when deploying the contract, initializing it or transferring ownership of the contract. + +
diff --git a/docs/contracts/LSP7DigitalAsset/extensions/LSP7CappedSupply.md b/docs/contracts/LSP7DigitalAsset/extensions/LSP7CappedSupply.md index 6d94cff7f..028cf0a49 100644 --- a/docs/contracts/LSP7DigitalAsset/extensions/LSP7CappedSupply.md +++ b/docs/contracts/LSP7DigitalAsset/extensions/LSP7CappedSupply.md @@ -56,6 +56,25 @@ This function is executed when:
+### receive + +:::note References + +- Specification details: [**LSP-7-DigitalAsset**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-7-DigitalAsset.md#receive) +- Solidity implementation: [`LSP7CappedSupply.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP7DigitalAsset/extensions/LSP7CappedSupply.sol) + +::: + +```solidity +receive() external payable; +``` + +_LSP7 contract cannot receive native tokens._ + +Reverts whenever someone tries to send native tokens to a LSP7 contract. + +
+ ### authorizeOperator :::note References @@ -204,12 +223,12 @@ This is a non-standard function, not part of the LSP7 standard interface. It has ```solidity function decreaseAllowance( address operator, - uint256 substractedAmount, + uint256 subtractedAmount, bytes operatorNotificationData ) external nonpayable; ``` -_Decrease the allowance of `operator` by -`substractedAmount`_ +_Decrease the allowance of `operator` by -`subtractedAmount`_ Atomically decreases the allowance granted to `operator` by the caller. This is an alternative approach to [`authorizeOperator`](#authorizeoperator) that can be used as a mitigation for the double spending allowance problem. @@ -218,7 +237,7 @@ Atomically decreases the allowance granted to `operator` by the caller. This is **Requirements:** - `operator` cannot be the zero address. -- `operator` must have allowance for the caller of at least `substractedAmount`. +- `operator` must have allowance for the caller of at least `subtractedAmount`.
@@ -227,7 +246,7 @@ Atomically decreases the allowance granted to `operator` by the caller. This is **Emitted events:** - [`AuthorizedOperator`](#authorizedoperator) event indicating the updated allowance after decreasing it. -- [`RevokeOperator`](#revokeoperator) event if `substractedAmount` is the full allowance, indicating `operator` does not have any alauthorizedAmountForlowance left for `msg.sender`. +- [`RevokeOperator`](#revokeoperator) event if `subtractedAmount` is the full allowance, indicating `operator` does not have any alauthorizedAmountForlowance left for `msg.sender`.
@@ -235,8 +254,8 @@ Atomically decreases the allowance granted to `operator` by the caller. This is | Name | Type | Description | | -------------------------- | :-------: | ------------------------------------------------------ | -| `operator` | `address` | the operator to decrease allowance for `msg.sender` | -| `substractedAmount` | `uint256` | the amount to decrease by in the operator's allowance. | +| `operator` | `address` | The operator to decrease allowance for `msg.sender` | +| `subtractedAmount` | `uint256` | The amount to decrease by in the operator's allowance. | | `operatorNotificationData` | `bytes` | - |
@@ -390,8 +409,8 @@ Atomically increases the allowance granted to `operator` by the caller. This is | Name | Type | Description | | -------------------------- | :-------: | ----------------------------------------------------------------------- | -| `operator` | `address` | the operator to increase the allowance for `msg.sender` | -| `addedAmount` | `uint256` | the additional amount to add on top of the current operator's allowance | +| `operator` | `address` | The operator to increase the allowance for `msg.sender` | +| `addedAmount` | `uint256` | The additional amount to add on top of the current operator's allowance | | `operatorNotificationData` | `bytes` | - |
@@ -615,7 +634,7 @@ Returns true if this contract implements the interface defined by `interfaceId`. function tokenSupplyCap() external view returns (uint256); ``` -_The maximum supply amount of tokens allowed to exist is `_tokenSupplyCap`._ +_The maximum supply amount of tokens allowed to exist is `_TOKEN_SUPPLY_CAP`._ Get the maximum number of tokens that can exist to circulate. Once [`totalSupply`](#totalsupply) reaches reaches [`totalSuuplyCap`](#totalsuuplycap), it is not possible to mint more tokens. @@ -668,7 +687,7 @@ function transfer( address from, address to, uint256 amount, - bool allowNonLSP1Recipient, + bool force, bytes data ) external nonpayable; ``` @@ -677,13 +696,13 @@ Transfers an `amount` of tokens from the `from` address to the `to` address and #### Parameters -| Name | Type | Description | -| ----------------------- | :-------: | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `from` | `address` | The sender address. | -| `to` | `address` | The recipient address. | -| `amount` | `uint256` | The amount of tokens to transfer. | -| `allowNonLSP1Recipient` | `bool` | When set to `true`, the `to` address CAN be any address. When set to `false`, the `to` address MUST be a contract that supports the LSP1 UniversalReceiver standard. | -| `data` | `bytes` | Any additional data the caller wants included in the emitted event, and sent in the hooks of the `from` and `to` addresses. | +| Name | Type | Description | +| -------- | :-------: | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `from` | `address` | The sender address. | +| `to` | `address` | The recipient address. | +| `amount` | `uint256` | The amount of tokens to transfer. | +| `force` | `bool` | When set to `true`, the `to` address CAN be any address. When set to `false`, the `to` address MUST be a contract that supports the LSP1 UniversalReceiver standard. | +| `data` | `bytes` | Any additional data the caller wants included in the emitted event, and sent in the hooks of the `from` and `to` addresses. |
@@ -703,7 +722,7 @@ function transferBatch( address[] from, address[] to, uint256[] amount, - bool[] allowNonLSP1Recipient, + bool[] force, bytes[] data ) external nonpayable; ``` @@ -712,13 +731,13 @@ Same as [`transfer(...)`](#`transfer) but transfer multiple tokens based on the #### Parameters -| Name | Type | Description | -| ----------------------- | :---------: | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `from` | `address[]` | An array of sending addresses. | -| `to` | `address[]` | An array of receiving addresses. | -| `amount` | `uint256[]` | An array of amount of tokens to transfer for each `from -> to` transfer. | -| `allowNonLSP1Recipient` | `bool[]` | For each transfer, when set to `true`, the `to` address CAN be any address. When set to `false`, the `to` address MUST be a contract that supports the LSP1 UniversalReceiver standard. | -| `data` | `bytes[]` | An array of additional data the caller wants included in the emitted event, and sent in the hooks to `from` and `to` addresses. | +| Name | Type | Description | +| -------- | :---------: | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `from` | `address[]` | An array of sending addresses. | +| `to` | `address[]` | An array of receiving addresses. | +| `amount` | `uint256[]` | An array of amount of tokens to transfer for each `from -> to` transfer. | +| `force` | `bool[]` | For each transfer, when set to `true`, the `to` address CAN be any address. When set to `false`, the `to` address MUST be a contract that supports the LSP1 UniversalReceiver standard. | +| `data` | `bytes[]` | An array of additional data the caller wants included in the emitted event, and sent in the hooks to `from` and `to` addresses. |
@@ -817,7 +836,7 @@ Save gas by emitting the [`DataChanged`](#datachanged) event with only the first function _updateOperator( address tokenOwner, address operator, - uint256 amount, + uint256 allowance, bytes operatorNotificationData ) internal nonpayable; ``` @@ -826,6 +845,15 @@ Changes token `amount` the `operator` has access to from `tokenOwner` tokens. If the amount is zero the operator is removed from the list of operators, otherwise he is added to the list of operators. If the amount is zero then the operator is being revoked, otherwise the operator amount is being modified. +#### Parameters + +| Name | Type | Description | +| -------------------------- | :-------: | -------------------------------------------------------------------------------------- | +| `tokenOwner` | `address` | The address that will give `operator` an allowance for on its balance. | +| `operator` | `address` | The address to grant an allowance to spend. | +| `allowance` | `uint256` | The maximum amount of token that `operator` can spend from the `tokenOwner`'s balance. | +| `operatorNotificationData` | `bytes` | - | +
### \_mint @@ -834,7 +862,7 @@ If the amount is zero then the operator is being revoked, otherwise the operator function _mint( address to, uint256 amount, - bool allowNonLSP1Recipient, + bool force, bytes data ) internal nonpayable; ``` @@ -846,6 +874,16 @@ after `amount` of tokens have been minted. ### \_burn +:::info + +Any logic in the: + +- {\_beforeTokenTransfer} function will run before updating the balances. + +- {\_afterTokenTransfer} function will run after updating the balances, **but before notifying the sender via LSP1**. + +::: + :::tip Hint In dApps, you can know which address is burning tokens by listening for the `Transfer` event and filter with the zero address as `to`. @@ -860,7 +898,6 @@ Burns (= destroys) `amount` of tokens, decrease the `from` balance. This is done Both the sender and recipient will be notified of the token transfer through the LSP1 [`universalReceiver`](#universalreceiver) function, if they are contracts that support the LSP1 interface. Their `universalReceiver` function will receive all the parameters in the calldata packed encoded. -Any logic in the [`_beforeTokenTransfer`](#_beforetokentransfer) function will run before updating the balances.
@@ -874,20 +911,52 @@ Any logic in the [`_beforeTokenTransfer`](#_beforetokentransfer) function will r | Name | Type | Description | | -------- | :-------: | ------------------------------------------------------------------------------------------------------------------------- | -| `from` | `address` | the address to burn tokens from its balance. | -| `amount` | `uint256` | the amount of tokens to burn. | +| `from` | `address` | The address to burn tokens from its balance. | +| `amount` | `uint256` | The amount of tokens to burn. | | `data` | `bytes` | Additional data the caller wants included in the emitted event, and sent in the LSP1 hook to the `from` and `to` address. |
+### \_spendAllowance + +```solidity +function _spendAllowance( + address operator, + address tokenOwner, + uint256 amountToSpend +) internal nonpayable; +``` + +Spend `amountToSpend` from the `operator`'s authorized on behalf of the `tokenOwner`. + +#### Parameters + +| Name | Type | Description | +| --------------- | :-------: | ------------------------------------------------------------------- | +| `operator` | `address` | The address of the operator to decrease the allowance of. | +| `tokenOwner` | `address` | The address that granted an allowance on its balance to `operator`. | +| `amountToSpend` | `uint256` | The amount of tokens to substract in allowance of `operator`. | + +
+ ### \_transfer +:::info + +Any logic in the: + +- {\_beforeTokenTransfer} function will run before updating the balances. + +- {\_afterTokenTransfer} function will run after updating the balances, **but before notifying the sender/recipient via LSP1**. + +::: + ```solidity function _transfer( address from, address to, uint256 amount, - bool allowNonLSP1Recipient, + bool force, bytes data ) internal nonpayable; ``` @@ -897,7 +966,6 @@ of `to` by `+amount`. Both the sender and recipient will be notified of the token transfer through the LSP1 [`universalReceiver`](#universalreceiver) function, if they are contracts that support the LSP1 interface. Their `universalReceiver` function will receive all the parameters in the calldata packed encoded. -Any logic in the [`_beforeTokenTransfer`](#_beforetokentransfer) function will run before updating the balances.
@@ -909,13 +977,13 @@ Any logic in the [`_beforeTokenTransfer`](#_beforetokentransfer) function will r #### Parameters -| Name | Type | Description | -| ----------------------- | :-------: | ------------------------------------------------------------------------------------------------------------------------- | -| `from` | `address` | the address to decrease the balance. | -| `to` | `address` | the address to increase the balance. | -| `amount` | `uint256` | the amount of tokens to transfer from `from` to `to`. | -| `allowNonLSP1Recipient` | `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 event, and sent in the LSP1 hook to the `from` and `to` address. | +| Name | Type | Description | +| -------- | :-------: | ------------------------------------------------------------------------------------------------------------------------- | +| `from` | `address` | The address to decrease the balance. | +| `to` | `address` | The address to increase the balance. | +| `amount` | `uint256` | The amount of tokens to transfer from `from` to `to`. | +| `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 event, and sent in the LSP1 hook to the `from` and `to` address. |
@@ -925,7 +993,8 @@ Any logic in the [`_beforeTokenTransfer`](#_beforetokentransfer) function will r function _beforeTokenTransfer( address from, address to, - uint256 amount + uint256 amount, + bytes data ) internal nonpayable; ``` @@ -934,11 +1003,37 @@ Allows to run custom logic before updating balances and notifiying sender/recipi #### Parameters -| Name | Type | Description | -| -------- | :-------: | ------------------------------- | -| `from` | `address` | The sender address | -| `to` | `address` | The recipient address | -| `amount` | `uint256` | The amount of token to transfer | +| Name | Type | Description | +| -------- | :-------: | ------------------------------------ | +| `from` | `address` | The sender address | +| `to` | `address` | The recipient address | +| `amount` | `uint256` | The amount of token to transfer | +| `data` | `bytes` | The data sent alongside the transfer | + +
+ +### \_afterTokenTransfer + +```solidity +function _afterTokenTransfer( + address from, + address to, + uint256 amount, + bytes data +) internal nonpayable; +``` + +Hook that is called after any token transfer, including minting and burning. +Allows to run custom logic after updating balances, but **before notifiying sender/recipient** by overriding this function. + +#### Parameters + +| Name | Type | Description | +| -------- | :-------: | ------------------------------------ | +| `from` | `address` | The sender address | +| `to` | `address` | The recipient address | +| `amount` | `uint256` | The amount of token to transfer | +| `data` | `bytes` | The data sent alongside the transfer |
@@ -988,26 +1083,26 @@ If `from` is an EOA or a contract that does not support the LSP1 interface, noth ```solidity function _notifyTokenReceiver( address to, - bool allowNonLSP1Recipient, + bool force, bytes lsp1Data ) internal nonpayable; ``` Attempt to notify the token receiver `to` about the `amount` tokens being received. This is done by calling its [`universalReceiver`](#universalreceiver) function with the `_TYPEID_LSP7_TOKENSRECIPIENT` as typeId, if `to` is a contract that supports the LSP1 interface. -If `to` is is an EOA or a contract that does not support the LSP1 interface, the behaviour will depend on the `allowNonLSP1Recipient` boolean flag. +If `to` is is an EOA or a contract that does not support the LSP1 interface, the behaviour will depend on the `force` boolean flag. -- if `allowNonLSP1Recipient` is set to `true`, nothing will happen and no notification will be sent. +- if `force` is set to `true`, nothing will happen and no notification will be sent. -- if `allowNonLSP1Recipient` is set to `false, the transaction will revert. +- if `force` is set to `false, the transaction will revert. #### Parameters -| Name | Type | Description | -| ----------------------- | :-------: | --------------------------------------------------------------------------------------------------- | -| `to` | `address` | The address to call the {universalReceiver} function on. | -| `allowNonLSP1Recipient` | `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. | +| Name | Type | Description | +| ---------- | :-------: | --------------------------------------------------------------------------------------------------- | +| `to` | `address` | The address to call the {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. |
@@ -1043,6 +1138,13 @@ Returns the extension address stored under the following data key: ### \_fallbackLSP17Extendable +:::info + +The LSP7 Token contract should not hold any native tokens. Any native tokens received by the contract +will be forwarded to the extension address mapped to the selector from `msg.sig`. + +::: + ```solidity function _fallbackLSP17Extendable( bytes callData @@ -1056,9 +1158,6 @@ Reverts if there is no extension for the function being called. If there is an extension for the function selector being called, it calls the extension with the CALL opcode, passing the [`msg.data`](#msg.data) appended with the 20 bytes of the [`msg.sender`](#msg.sender) and 32 bytes of the [`msg.value`](#msg.value) -Because the function uses assembly [`return()/revert()`](#return) to terminate the call, it cannot be -called before other codes in fallback(). -Otherwise, the codes after \_fallbackLSP17Extendable() may never be reached.
@@ -1079,14 +1178,14 @@ Otherwise, the codes after \_fallbackLSP17Extendable() may never be reached. event AuthorizedOperator(address indexed operator, address indexed tokenOwner, uint256 indexed amount, bytes operatorNotificationData); ``` -Emitted when `tokenOwner` enables `operator` to transfer or burn the `tokenId`. +Emitted when `tokenOwner` enables `operator` for `amount` tokens. #### Parameters | Name | Type | Description | | -------------------------- | :-------: | ----------------------------------------------------------------------- | -| `operator` **`indexed`** | `address` | The address authorized as an operator. | -| `tokenOwner` **`indexed`** | `address` | The owner of the `tokenId`. | +| `operator` **`indexed`** | `address` | The address authorized as an operator | +| `tokenOwner` **`indexed`** | `address` | The token owner | | `amount` **`indexed`** | `uint256` | The amount of tokens `operator` address has access to from `tokenOwner` | | `operatorNotificationData` | `bytes` | The data to notify the operator about via LSP1. | @@ -1159,15 +1258,15 @@ event OwnershipTransferred(address indexed previousOwner, address indexed newOwn event RevokedOperator(address indexed operator, address indexed tokenOwner, bytes operatorNotificationData); ``` -Emitted when `tokenOwner` disables `operator` to transfer or burn `tokenId` on its behalf. +Emitted when `tokenOwner` disables `operator` for `amount` tokens and set its [`authorizedAmountFor(...)`](#`authorizedamountfor) to `0`. #### Parameters -| Name | Type | Description | -| -------------------------- | :-------: | --------------------------------------------------------------- | -| `operator` **`indexed`** | `address` | The address revoked from the operator array ({getOperatorsOf}). | -| `tokenOwner` **`indexed`** | `address` | The owner of the `tokenId`. | -| `operatorNotificationData` | `bytes` | The data to notify the operator about via LSP1. | +| Name | Type | Description | +| -------------------------- | :-------: | ----------------------------------------------- | +| `operator` **`indexed`** | `address` | The address revoked from operating | +| `tokenOwner` **`indexed`** | `address` | The token owner | +| `operatorNotificationData` | `bytes` | The data to notify the operator about via LSP1. |
@@ -1183,21 +1282,21 @@ Emitted when `tokenOwner` disables `operator` to transfer or burn `tokenId` on i ::: ```solidity -event Transfer(address indexed operator, address indexed from, address indexed to, uint256 amount, bool allowNonLSP1Recipient, bytes data); +event Transfer(address indexed operator, address indexed from, address indexed to, uint256 amount, bool force, bytes data); ``` -Emitted when `tokenId` token is transferred from the `from` to the `to` address. +Emitted when the `from` transferred successfully `amount` of tokens to `to`. #### Parameters -| Name | Type | Description | -| ------------------------ | :-------: | ---------------------------------------------------------------------------------------------------------------------------------- | -| `operator` **`indexed`** | `address` | The address of operator that sent the `tokenId` | -| `from` **`indexed`** | `address` | The previous owner of the `tokenId` | -| `to` **`indexed`** | `address` | The new owner of `tokenId` | -| `amount` | `uint256` | The amount of tokens transferred. | -| `allowNonLSP1Recipient` | `bool` | If the token transfer enforces the `to` recipient address to be a contract that implements the LSP1 standard or not. | -| `data` | `bytes` | Any additional data the caller included by the caller during the transfer, and sent in the hooks to the `from` and `to` addresses. | +| Name | Type | Description | +| ------------------------ | :-------: | ---------------------------------------------------------------------------------------------------------------------------- | +| `operator` **`indexed`** | `address` | The address of the operator that executed the transfer. | +| `from` **`indexed`** | `address` | The address which tokens were sent from (balance decreased by `-amount`). | +| `to` **`indexed`** | `address` | The address that received the tokens (balance increased by `+amount`). | +| `amount` | `uint256` | The amount of tokens transferred. | +| `force` | `bool` | if the transferred enforced the `to` recipient address to be a contract that implements the LSP1 standard or not. | +| `data` | `bytes` | Any additional data included by the caller during the transfer, and sent in the LSP1 hooks to the `from` and `to` addresses. |
@@ -1572,7 +1671,7 @@ error LSP7NotifyTokenReceiverContractMissingLSP1Interface( ); ``` -reverts if the `tokenReceiver` does not implement LSP1 when minting or transferring tokens with `bool allowNonLSP1Recipient` set as `false`. +reverts if the `tokenReceiver` does not implement LSP1 when minting or transferring tokens with `bool force` set as `false`. #### Parameters @@ -1597,7 +1696,7 @@ reverts if the `tokenReceiver` does not implement LSP1 when minting or transferr error LSP7NotifyTokenReceiverIsEOA(address tokenReceiver); ``` -reverts if the `tokenReceiver` is an EOA when minting or transferring tokens with `bool allowNonLSP1Recipient` set as `false`. +reverts if the `tokenReceiver` is an EOA when minting or transferring tokens with `bool force` set as `false`. #### Parameters @@ -1607,6 +1706,27 @@ reverts if the `tokenReceiver` is an EOA when minting or transferring tokens wit
+### LSP7TokenContractCannotHoldValue + +:::note References + +- Specification details: [**LSP-7-DigitalAsset**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-7-DigitalAsset.md#lsp7tokencontractcannotholdvalue) +- Solidity implementation: [`LSP7CappedSupply.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP7DigitalAsset/extensions/LSP7CappedSupply.sol) +- Error signature: `LSP7TokenContractCannotHoldValue()` +- Error hash: `0x388f5adc` + +::: + +```solidity +error LSP7TokenContractCannotHoldValue(); +``` + +_LSP7 contract cannot receive native tokens._ + +Error occurs when sending native tokens to the LSP7 contract without sending any data. E.g. Sending value without passing a bytes4 function selector to call a LSP17 Extension. + +
+ ### LSP7TokenOwnerCannotBeOperator :::note References @@ -1650,3 +1770,47 @@ reverts when there is no extension for the function selector being called with | `functionSelector` | `bytes4` | - |
+ +### OwnableCallerNotTheOwner + +:::note References + +- Specification details: [**LSP-7-DigitalAsset**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-7-DigitalAsset.md#ownablecallernottheowner) +- Solidity implementation: [`LSP7CappedSupply.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP7DigitalAsset/extensions/LSP7CappedSupply.sol) +- Error signature: `OwnableCallerNotTheOwner(address)` +- Error hash: `0xbf1169c5` + +::: + +```solidity +error OwnableCallerNotTheOwner(address callerAddress); +``` + +Reverts when only the owner is allowed to call the function. + +#### Parameters + +| Name | Type | Description | +| --------------- | :-------: | ---------------------------------------- | +| `callerAddress` | `address` | The address that tried to make the call. | + +
+ +### OwnableCannotSetZeroAddressAsOwner + +:::note References + +- Specification details: [**LSP-7-DigitalAsset**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-7-DigitalAsset.md#ownablecannotsetzeroaddressasowner) +- Solidity implementation: [`LSP7CappedSupply.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP7DigitalAsset/extensions/LSP7CappedSupply.sol) +- Error signature: `OwnableCannotSetZeroAddressAsOwner()` +- Error hash: `0x1ad8836c` + +::: + +```solidity +error OwnableCannotSetZeroAddressAsOwner(); +``` + +Reverts when trying to set `address(0)` as the contract owner when deploying the contract, initializing it or transferring ownership of the contract. + +
diff --git a/docs/contracts/LSP7DigitalAsset/extensions/LSP7CompatibleERC20.md b/docs/contracts/LSP7DigitalAsset/extensions/LSP7CompatibleERC20.md index 1a9e37e4f..3792dc399 100644 --- a/docs/contracts/LSP7DigitalAsset/extensions/LSP7CompatibleERC20.md +++ b/docs/contracts/LSP7DigitalAsset/extensions/LSP7CompatibleERC20.md @@ -56,6 +56,25 @@ This function is executed when:
+### receive + +:::note References + +- Specification details: [**LSP-7-DigitalAsset**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-7-DigitalAsset.md#receive) +- Solidity implementation: [`LSP7CompatibleERC20.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP7DigitalAsset/extensions/LSP7CompatibleERC20.sol) + +::: + +```solidity +receive() external payable; +``` + +_LSP7 contract cannot receive native tokens._ + +Reverts whenever someone tries to send native tokens to a LSP7 contract. + +
+ ### allowance :::note References @@ -74,18 +93,20 @@ function allowance( ) external view returns (uint256); ``` +Function to get operator allowance allowed to spend on behalf of `tokenOwner` from the ERC20 standard interface. + #### Parameters -| Name | Type | Description | -| ------------ | :-------: | ----------- | -| `tokenOwner` | `address` | - | -| `operator` | `address` | - | +| Name | Type | Description | +| ------------ | :-------: | ---------------------------------------- | +| `tokenOwner` | `address` | The address of the token owner | +| `operator` | `address` | The address approved by the `tokenOwner` | #### Returns -| Name | Type | Description | -| ---- | :-------: | ----------- | -| `0` | `uint256` | - | +| Name | Type | Description | +| ---- | :-------: | ------------------------------------------------- | +| `0` | `uint256` | The amount `operator` is approved by `tokenOwner` |
@@ -107,18 +128,20 @@ function approve( ) external nonpayable returns (bool); ``` +Approval function from th ERC20 standard interface. + #### Parameters -| Name | Type | Description | -| ---------- | :-------: | ----------- | -| `operator` | `address` | - | -| `amount` | `uint256` | - | +| Name | Type | Description | +| ---------- | :-------: | ----------------------------------- | +| `operator` | `address` | The address to approve for `amount` | +| `amount` | `uint256` | The amount to approve. | #### Returns -| Name | Type | Description | -| ---- | :----: | ----------- | -| `0` | `bool` | - | +| Name | Type | Description | +| ---- | :----: | ------------------------------ | +| `0` | `bool` | `true` on successful approval. |
@@ -270,12 +293,12 @@ This is a non-standard function, not part of the LSP7 standard interface. It has ```solidity function decreaseAllowance( address operator, - uint256 substractedAmount, + uint256 subtractedAmount, bytes operatorNotificationData ) external nonpayable; ``` -_Decrease the allowance of `operator` by -`substractedAmount`_ +_Decrease the allowance of `operator` by -`subtractedAmount`_ Atomically decreases the allowance granted to `operator` by the caller. This is an alternative approach to [`authorizeOperator`](#authorizeoperator) that can be used as a mitigation for the double spending allowance problem. @@ -284,7 +307,7 @@ Atomically decreases the allowance granted to `operator` by the caller. This is **Requirements:** - `operator` cannot be the zero address. -- `operator` must have allowance for the caller of at least `substractedAmount`. +- `operator` must have allowance for the caller of at least `subtractedAmount`.
@@ -293,7 +316,7 @@ Atomically decreases the allowance granted to `operator` by the caller. This is **Emitted events:** - [`AuthorizedOperator`](#authorizedoperator) event indicating the updated allowance after decreasing it. -- [`RevokeOperator`](#revokeoperator) event if `substractedAmount` is the full allowance, indicating `operator` does not have any alauthorizedAmountForlowance left for `msg.sender`. +- [`RevokeOperator`](#revokeoperator) event if `subtractedAmount` is the full allowance, indicating `operator` does not have any alauthorizedAmountForlowance left for `msg.sender`.
@@ -301,8 +324,8 @@ Atomically decreases the allowance granted to `operator` by the caller. This is | Name | Type | Description | | -------------------------- | :-------: | ------------------------------------------------------ | -| `operator` | `address` | the operator to decrease allowance for `msg.sender` | -| `substractedAmount` | `uint256` | the amount to decrease by in the operator's allowance. | +| `operator` | `address` | The operator to decrease allowance for `msg.sender` | +| `subtractedAmount` | `uint256` | The amount to decrease by in the operator's allowance. | | `operatorNotificationData` | `bytes` | - |
@@ -456,8 +479,8 @@ Atomically increases the allowance granted to `operator` by the caller. This is | Name | Type | Description | | -------------------------- | :-------: | ----------------------------------------------------------------------- | -| `operator` | `address` | the operator to increase the allowance for `msg.sender` | -| `addedAmount` | `uint256` | the additional amount to add on top of the current operator's allowance | +| `operator` | `address` | The operator to increase the allowance for `msg.sender` | +| `addedAmount` | `uint256` | The additional amount to add on top of the current operator's allowance | | `operatorNotificationData` | `bytes` | - |
@@ -477,7 +500,7 @@ Atomically increases the allowance granted to `operator` by the caller. This is function name() external view returns (string); ``` -Returns the name of the token. +Returns the name of the token. For compatibility with clients & tools that expect ERC20. #### Returns @@ -706,7 +729,7 @@ Returns true if this contract implements the interface defined by `interfaceId`. function symbol() external view returns (string); ``` -Returns the symbol of the token, usually a shorter version of the name. +Returns the symbol of the token, usually a shorter version of the name. For compatibility with clients & tools that expect ERC20. #### Returns @@ -757,7 +780,7 @@ function transfer( address from, address to, uint256 amount, - bool allowNonLSP1Recipient, + bool force, bytes data ) external nonpayable; ``` @@ -766,13 +789,13 @@ Transfers an `amount` of tokens from the `from` address to the `to` address and #### Parameters -| Name | Type | Description | -| ----------------------- | :-------: | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `from` | `address` | The sender address. | -| `to` | `address` | The recipient address. | -| `amount` | `uint256` | The amount of tokens to transfer. | -| `allowNonLSP1Recipient` | `bool` | When set to `true`, the `to` address CAN be any address. When set to `false`, the `to` address MUST be a contract that supports the LSP1 UniversalReceiver standard. | -| `data` | `bytes` | Any additional data the caller wants included in the emitted event, and sent in the hooks of the `from` and `to` addresses. | +| Name | Type | Description | +| -------- | :-------: | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `from` | `address` | The sender address. | +| `to` | `address` | The recipient address. | +| `amount` | `uint256` | The amount of tokens to transfer. | +| `force` | `bool` | When set to `true`, the `to` address CAN be any address. When set to `false`, the `to` address MUST be a contract that supports the LSP1 UniversalReceiver standard. | +| `data` | `bytes` | Any additional data the caller wants included in the emitted event, and sent in the hooks of the `from` and `to` addresses. |
@@ -789,7 +812,7 @@ Transfers an `amount` of tokens from the `from` address to the `to` address and :::info -This function uses the `allowNonLSP1Recipient` parameter as `true` so that EOA and any contract can receive tokens. +This function uses the `force` parameter as `true` so that EOA and any contract can receive tokens. ::: @@ -800,18 +823,20 @@ function transfer( ) external nonpayable returns (bool); ``` +Transfer function from the ERC20 standard interface. + #### Parameters -| Name | Type | Description | -| -------- | :-------: | ----------- | -| `to` | `address` | - | -| `amount` | `uint256` | - | +| Name | Type | Description | +| -------- | :-------: | --------------------------------- | +| `to` | `address` | The address receiving tokens. | +| `amount` | `uint256` | The amount of tokens to transfer. | #### Returns -| Name | Type | Description | -| ---- | :----: | ----------- | -| `0` | `bool` | - | +| Name | Type | Description | +| ---- | :----: | ------------------------------ | +| `0` | `bool` | `true` on successful transfer. |
@@ -831,7 +856,7 @@ function transferBatch( address[] from, address[] to, uint256[] amount, - bool[] allowNonLSP1Recipient, + bool[] force, bytes[] data ) external nonpayable; ``` @@ -840,13 +865,13 @@ Same as [`transfer(...)`](#`transfer) but transfer multiple tokens based on the #### Parameters -| Name | Type | Description | -| ----------------------- | :---------: | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `from` | `address[]` | An array of sending addresses. | -| `to` | `address[]` | An array of receiving addresses. | -| `amount` | `uint256[]` | An array of amount of tokens to transfer for each `from -> to` transfer. | -| `allowNonLSP1Recipient` | `bool[]` | For each transfer, when set to `true`, the `to` address CAN be any address. When set to `false`, the `to` address MUST be a contract that supports the LSP1 UniversalReceiver standard. | -| `data` | `bytes[]` | An array of additional data the caller wants included in the emitted event, and sent in the hooks to `from` and `to` addresses. | +| Name | Type | Description | +| -------- | :---------: | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `from` | `address[]` | An array of sending addresses. | +| `to` | `address[]` | An array of receiving addresses. | +| `amount` | `uint256[]` | An array of amount of tokens to transfer for each `from -> to` transfer. | +| `force` | `bool[]` | For each transfer, when set to `true`, the `to` address CAN be any address. When set to `false`, the `to` address MUST be a contract that supports the LSP1 UniversalReceiver standard. | +| `data` | `bytes[]` | An array of additional data the caller wants included in the emitted event, and sent in the hooks to `from` and `to` addresses. |
@@ -863,7 +888,7 @@ Same as [`transfer(...)`](#`transfer) but transfer multiple tokens based on the :::info -This function uses the `allowNonLSP1Recipient` parameter as `true` so that EOA and any contract can receive tokens. +This function uses the `force` parameter as `true` so that EOA and any contract can receive tokens. ::: @@ -875,19 +900,21 @@ function transferFrom( ) external nonpayable returns (bool); ``` +Transfer functions for operators from the ERC20 standard interface. + #### Parameters -| Name | Type | Description | -| -------- | :-------: | ----------- | -| `from` | `address` | - | -| `to` | `address` | - | -| `amount` | `uint256` | - | +| Name | Type | Description | +| -------- | :-------: | --------------------------------- | +| `from` | `address` | The address sending tokens. | +| `to` | `address` | The address receiving tokens. | +| `amount` | `uint256` | The amount of tokens to transfer. | #### Returns -| Name | Type | Description | -| ---- | :----: | ----------- | -| `0` | `bool` | - | +| Name | Type | Description | +| ---- | :----: | ------------------------------ | +| `0` | `bool` | `true` on successful transfer. |
@@ -973,9 +1000,11 @@ mapping(bytes32 => bytes) _store ### \_setData ```solidity -function _setData(bytes32 key, bytes value) internal nonpayable; +function _setData(bytes32 dataKey, bytes dataValue) internal nonpayable; ``` +Save gas by emitting the [`DataChanged`](#datachanged) event with only the first 256 bytes of dataValue +
### \_updateOperator @@ -997,7 +1026,7 @@ function _updateOperator( function _mint( address to, uint256 amount, - bool allowNonLSP1Recipient, + bool force, bytes data ) internal nonpayable; ``` @@ -1012,6 +1041,28 @@ function _burn(address from, uint256 amount, bytes data) internal nonpayable;
+### \_spendAllowance + +```solidity +function _spendAllowance( + address operator, + address tokenOwner, + uint256 amountToSpend +) internal nonpayable; +``` + +Spend `amountToSpend` from the `operator`'s authorized on behalf of the `tokenOwner`. + +#### Parameters + +| Name | Type | Description | +| --------------- | :-------: | ------------------------------------------------------------------- | +| `operator` | `address` | The address of the operator to decrease the allowance of. | +| `tokenOwner` | `address` | The address that granted an allowance on its balance to `operator`. | +| `amountToSpend` | `uint256` | The amount of tokens to substract in allowance of `operator`. | + +
+ ### \_transfer ```solidity @@ -1019,7 +1070,7 @@ function _transfer( address from, address to, uint256 amount, - bool allowNonLSP1Recipient, + bool force, bytes data ) internal nonpayable; ``` @@ -1032,7 +1083,8 @@ function _transfer( function _beforeTokenTransfer( address from, address to, - uint256 amount + uint256 amount, + bytes data ) internal nonpayable; ``` @@ -1041,11 +1093,37 @@ Allows to run custom logic before updating balances and notifiying sender/recipi #### Parameters -| Name | Type | Description | -| -------- | :-------: | ------------------------------- | -| `from` | `address` | The sender address | -| `to` | `address` | The recipient address | -| `amount` | `uint256` | The amount of token to transfer | +| Name | Type | Description | +| -------- | :-------: | ------------------------------------ | +| `from` | `address` | The sender address | +| `to` | `address` | The recipient address | +| `amount` | `uint256` | The amount of token to transfer | +| `data` | `bytes` | The data sent alongside the transfer | + +
+ +### \_afterTokenTransfer + +```solidity +function _afterTokenTransfer( + address from, + address to, + uint256 amount, + bytes data +) internal nonpayable; +``` + +Hook that is called after any token transfer, including minting and burning. +Allows to run custom logic after updating balances, but **before notifiying sender/recipient** by overriding this function. + +#### Parameters + +| Name | Type | Description | +| -------- | :-------: | ------------------------------------ | +| `from` | `address` | The sender address | +| `to` | `address` | The recipient address | +| `amount` | `uint256` | The amount of token to transfer | +| `data` | `bytes` | The data sent alongside the transfer |
@@ -1095,26 +1173,26 @@ If `from` is an EOA or a contract that does not support the LSP1 interface, noth ```solidity function _notifyTokenReceiver( address to, - bool allowNonLSP1Recipient, + bool force, bytes lsp1Data ) internal nonpayable; ``` Attempt to notify the token receiver `to` about the `amount` tokens being received. This is done by calling its [`universalReceiver`](#universalreceiver) function with the `_TYPEID_LSP7_TOKENSRECIPIENT` as typeId, if `to` is a contract that supports the LSP1 interface. -If `to` is is an EOA or a contract that does not support the LSP1 interface, the behaviour will depend on the `allowNonLSP1Recipient` boolean flag. +If `to` is is an EOA or a contract that does not support the LSP1 interface, the behaviour will depend on the `force` boolean flag. -- if `allowNonLSP1Recipient` is set to `true`, nothing will happen and no notification will be sent. +- if `force` is set to `true`, nothing will happen and no notification will be sent. -- if `allowNonLSP1Recipient` is set to `false, the transaction will revert. +- if `force` is set to `false, the transaction will revert. #### Parameters -| Name | Type | Description | -| ----------------------- | :-------: | --------------------------------------------------------------------------------------------------- | -| `to` | `address` | The address to call the {universalReceiver} function on. | -| `allowNonLSP1Recipient` | `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. | +| Name | Type | Description | +| ---------- | :-------: | --------------------------------------------------------------------------------------------------- | +| `to` | `address` | The address to call the {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. |
@@ -1150,6 +1228,13 @@ Returns the extension address stored under the following data key: ### \_fallbackLSP17Extendable +:::info + +The LSP7 Token contract should not hold any native tokens. Any native tokens received by the contract +will be forwarded to the extension address mapped to the selector from `msg.sig`. + +::: + ```solidity function _fallbackLSP17Extendable( bytes callData @@ -1163,9 +1248,6 @@ Reverts if there is no extension for the function being called. If there is an extension for the function selector being called, it calls the extension with the CALL opcode, passing the [`msg.data`](#msg.data) appended with the 20 bytes of the [`msg.sender`](#msg.sender) and 32 bytes of the [`msg.value`](#msg.value) -Because the function uses assembly [`return()/revert()`](#return) to terminate the call, it cannot be -called before other codes in fallback(). -Otherwise, the codes after \_fallbackLSP17Extendable() may never be reached.
@@ -1186,15 +1268,15 @@ Otherwise, the codes after \_fallbackLSP17Extendable() may never be reached. event Approval(address indexed owner, address indexed spender, uint256 value); ``` -ERC721 `Approval` event emitted when `owner` enables `operator` for `tokenId`. To provide compatibility with indexing ERC721 events. +Emitted when the allowance of a `spender` for an `owner` is set by a call to [`approve`](#approve). `value` is the new allowance. #### Parameters -| Name | Type | Description | -| ----------------------- | :-------: | ------------------------------------------ | -| `owner` **`indexed`** | `address` | The address of the owner of the `tokenId`. | -| `spender` **`indexed`** | `address` | - | -| `value` | `uint256` | - | +| Name | Type | Description | +| ----------------------- | :-------: | ----------- | +| `owner` **`indexed`** | `address` | - | +| `spender` **`indexed`** | `address` | - | +| `value` | `uint256` | - |
@@ -1213,14 +1295,14 @@ ERC721 `Approval` event emitted when `owner` enables `operator` for `tokenId`. T event AuthorizedOperator(address indexed operator, address indexed tokenOwner, uint256 indexed amount, bytes operatorNotificationData); ``` -Emitted when `tokenOwner` enables `operator` to transfer or burn the `tokenId`. +Emitted when `tokenOwner` enables `operator` for `amount` tokens. #### Parameters | Name | Type | Description | | -------------------------- | :-------: | ----------------------------------------------------------------------- | -| `operator` **`indexed`** | `address` | The address authorized as an operator. | -| `tokenOwner` **`indexed`** | `address` | The owner of the `tokenId`. | +| `operator` **`indexed`** | `address` | The address authorized as an operator | +| `tokenOwner` **`indexed`** | `address` | The token owner | | `amount` **`indexed`** | `uint256` | The amount of tokens `operator` address has access to from `tokenOwner` | | `operatorNotificationData` | `bytes` | The data to notify the operator about via LSP1. | @@ -1293,15 +1375,15 @@ event OwnershipTransferred(address indexed previousOwner, address indexed newOwn event RevokedOperator(address indexed operator, address indexed tokenOwner, bytes operatorNotificationData); ``` -Emitted when `tokenOwner` disables `operator` to transfer or burn `tokenId` on its behalf. +Emitted when `tokenOwner` disables `operator` for `amount` tokens and set its [`authorizedAmountFor(...)`](#`authorizedamountfor) to `0`. #### Parameters -| Name | Type | Description | -| -------------------------- | :-------: | --------------------------------------------------------------- | -| `operator` **`indexed`** | `address` | The address revoked from the operator array ({getOperatorsOf}). | -| `tokenOwner` **`indexed`** | `address` | The owner of the `tokenId`. | -| `operatorNotificationData` | `bytes` | The data to notify the operator about via LSP1. | +| Name | Type | Description | +| -------------------------- | :-------: | ----------------------------------------------- | +| `operator` **`indexed`** | `address` | The address revoked from operating | +| `tokenOwner` **`indexed`** | `address` | The token owner | +| `operatorNotificationData` | `bytes` | The data to notify the operator about via LSP1. |
@@ -1317,21 +1399,48 @@ Emitted when `tokenOwner` disables `operator` to transfer or burn `tokenId` on i ::: ```solidity -event Transfer(address indexed operator, address indexed from, address indexed to, uint256 amount, bool allowNonLSP1Recipient, bytes data); +event Transfer(address indexed operator, address indexed from, address indexed to, uint256 amount, bool force, bytes data); +``` + +Emitted when the `from` transferred successfully `amount` of tokens to `to`. + +#### Parameters + +| Name | Type | Description | +| ------------------------ | :-------: | ---------------------------------------------------------------------------------------------------------------------------- | +| `operator` **`indexed`** | `address` | The address of the operator that executed the transfer. | +| `from` **`indexed`** | `address` | The address which tokens were sent from (balance decreased by `-amount`). | +| `to` **`indexed`** | `address` | The address that received the tokens (balance increased by `+amount`). | +| `amount` | `uint256` | The amount of tokens transferred. | +| `force` | `bool` | if the transferred enforced the `to` recipient address to be a contract that implements the LSP1 standard or not. | +| `data` | `bytes` | Any additional data included by the caller during the transfer, and sent in the LSP1 hooks to the `from` and `to` addresses. | + +
+ +### Transfer + +:::note References + +- Specification details: [**LSP-7-DigitalAsset**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-7-DigitalAsset.md#transfer) +- Solidity implementation: [`LSP7CompatibleERC20.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP7DigitalAsset/extensions/LSP7CompatibleERC20.sol) +- Event signature: `Transfer(address,address,uint256)` +- Event topic hash: `0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef` + +::: + +```solidity +event Transfer(address indexed from, address indexed to, uint256 value); ``` -Emitted when `tokenId` token is transferred from the `from` to the `to` address. +Emitted when `value` tokens are moved from one account (`from`) to another (`to`). Note that `value` may be zero. #### Parameters -| Name | Type | Description | -| ------------------------ | :-------: | ---------------------------------------------------------------------------------------------------------------------------------- | -| `operator` **`indexed`** | `address` | The address of operator that sent the `tokenId` | -| `from` **`indexed`** | `address` | The previous owner of the `tokenId` | -| `to` **`indexed`** | `address` | The new owner of `tokenId` | -| `amount` | `uint256` | The amount of tokens transferred. | -| `allowNonLSP1Recipient` | `bool` | If the token transfer enforces the `to` recipient address to be a contract that implements the LSP1 standard or not. | -| `data` | `bytes` | Any additional data the caller included by the caller during the transfer, and sent in the hooks to the `from` and `to` addresses. | +| Name | Type | Description | +| -------------------- | :-------: | ----------- | +| `from` **`indexed`** | `address` | - | +| `to` **`indexed`** | `address` | - | +| `value` | `uint256` | - |
@@ -1664,7 +1773,7 @@ error LSP7NotifyTokenReceiverContractMissingLSP1Interface( ); ``` -reverts if the `tokenReceiver` does not implement LSP1 when minting or transferring tokens with `bool allowNonLSP1Recipient` set as `false`. +reverts if the `tokenReceiver` does not implement LSP1 when minting or transferring tokens with `bool force` set as `false`. #### Parameters @@ -1689,7 +1798,7 @@ reverts if the `tokenReceiver` does not implement LSP1 when minting or transferr error LSP7NotifyTokenReceiverIsEOA(address tokenReceiver); ``` -reverts if the `tokenReceiver` is an EOA when minting or transferring tokens with `bool allowNonLSP1Recipient` set as `false`. +reverts if the `tokenReceiver` is an EOA when minting or transferring tokens with `bool force` set as `false`. #### Parameters @@ -1699,6 +1808,27 @@ reverts if the `tokenReceiver` is an EOA when minting or transferring tokens wit
+### LSP7TokenContractCannotHoldValue + +:::note References + +- Specification details: [**LSP-7-DigitalAsset**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-7-DigitalAsset.md#lsp7tokencontractcannotholdvalue) +- Solidity implementation: [`LSP7CompatibleERC20.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP7DigitalAsset/extensions/LSP7CompatibleERC20.sol) +- Error signature: `LSP7TokenContractCannotHoldValue()` +- Error hash: `0x388f5adc` + +::: + +```solidity +error LSP7TokenContractCannotHoldValue(); +``` + +_LSP7 contract cannot receive native tokens._ + +Error occurs when sending native tokens to the LSP7 contract without sending any data. E.g. Sending value without passing a bytes4 function selector to call a LSP17 Extension. + +
+ ### LSP7TokenOwnerCannotBeOperator :::note References @@ -1742,3 +1872,47 @@ reverts when there is no extension for the function selector being called with | `functionSelector` | `bytes4` | - |
+ +### OwnableCallerNotTheOwner + +:::note References + +- Specification details: [**LSP-7-DigitalAsset**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-7-DigitalAsset.md#ownablecallernottheowner) +- Solidity implementation: [`LSP7CompatibleERC20.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP7DigitalAsset/extensions/LSP7CompatibleERC20.sol) +- Error signature: `OwnableCallerNotTheOwner(address)` +- Error hash: `0xbf1169c5` + +::: + +```solidity +error OwnableCallerNotTheOwner(address callerAddress); +``` + +Reverts when only the owner is allowed to call the function. + +#### Parameters + +| Name | Type | Description | +| --------------- | :-------: | ---------------------------------------- | +| `callerAddress` | `address` | The address that tried to make the call. | + +
+ +### OwnableCannotSetZeroAddressAsOwner + +:::note References + +- Specification details: [**LSP-7-DigitalAsset**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-7-DigitalAsset.md#ownablecannotsetzeroaddressasowner) +- Solidity implementation: [`LSP7CompatibleERC20.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP7DigitalAsset/extensions/LSP7CompatibleERC20.sol) +- Error signature: `OwnableCannotSetZeroAddressAsOwner()` +- Error hash: `0x1ad8836c` + +::: + +```solidity +error OwnableCannotSetZeroAddressAsOwner(); +``` + +Reverts when trying to set `address(0)` as the contract owner when deploying the contract, initializing it or transferring ownership of the contract. + +
diff --git a/docs/contracts/LSP7DigitalAsset/presets/LSP7CompatibleERC20Mintable.md b/docs/contracts/LSP7DigitalAsset/presets/LSP7CompatibleERC20Mintable.md index 7562efafa..63ac7291f 100644 --- a/docs/contracts/LSP7DigitalAsset/presets/LSP7CompatibleERC20Mintable.md +++ b/docs/contracts/LSP7DigitalAsset/presets/LSP7CompatibleERC20Mintable.md @@ -34,7 +34,7 @@ When marked as 'public', a method can be called both externally and internally, constructor(string name_, string symbol_, address newOwner_); ``` -_Deploying a `LSP7CompatibleERC20Mintable` token contract with: token name = `name_`, token symbol = `symbol*`, and address `newOwner*` as the token contract owner.\_ +_Deploying a `LSP7CompatibleERC20Mintable` token contract with: token name = `name_`, token symbol = `symbol_`, and address `newOwner_` as the token contract owner._ #### Parameters @@ -61,6 +61,21 @@ fallback() external payable;
+### receive + +:::note References + +- Specification details: [**LSP-7-DigitalAsset**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-7-DigitalAsset.md#receive) +- Solidity implementation: [`LSP7CompatibleERC20Mintable.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP7DigitalAsset/presets/LSP7CompatibleERC20Mintable.sol) + +::: + +```solidity +receive() external payable; +``` + +
+ ### allowance :::note References @@ -79,18 +94,20 @@ function allowance( ) external view returns (uint256); ``` +Function to get operator allowance allowed to spend on behalf of `tokenOwner` from the ERC20 standard interface. + #### Parameters -| Name | Type | Description | -| ------------ | :-------: | ----------- | -| `tokenOwner` | `address` | - | -| `operator` | `address` | - | +| Name | Type | Description | +| ------------ | :-------: | ---------------------------------------- | +| `tokenOwner` | `address` | The address of the token owner | +| `operator` | `address` | The address approved by the `tokenOwner` | #### Returns -| Name | Type | Description | -| ---- | :-------: | ----------- | -| `0` | `uint256` | - | +| Name | Type | Description | +| ---- | :-------: | ------------------------------------------------- | +| `0` | `uint256` | The amount `operator` is approved by `tokenOwner` |
@@ -112,18 +129,20 @@ function approve( ) external nonpayable returns (bool); ``` +Approval function from th ERC20 standard interface. + #### Parameters -| Name | Type | Description | -| ---------- | :-------: | ----------- | -| `operator` | `address` | - | -| `amount` | `uint256` | - | +| Name | Type | Description | +| ---------- | :-------: | ----------------------------------- | +| `operator` | `address` | The address to approve for `amount` | +| `amount` | `uint256` | The amount to approve. | #### Returns -| Name | Type | Description | -| ---- | :----: | ----------- | -| `0` | `bool` | - | +| Name | Type | Description | +| ---- | :----: | ------------------------------ | +| `0` | `bool` | `true` on successful approval. |
@@ -275,12 +294,12 @@ This is a non-standard function, not part of the LSP7 standard interface. It has ```solidity function decreaseAllowance( address operator, - uint256 substractedAmount, + uint256 subtractedAmount, bytes operatorNotificationData ) external nonpayable; ``` -_Decrease the allowance of `operator` by -`substractedAmount`_ +_Decrease the allowance of `operator` by -`subtractedAmount`_ Atomically decreases the allowance granted to `operator` by the caller. This is an alternative approach to [`authorizeOperator`](#authorizeoperator) that can be used as a mitigation for the double spending allowance problem. @@ -289,7 +308,7 @@ Atomically decreases the allowance granted to `operator` by the caller. This is **Requirements:** - `operator` cannot be the zero address. -- `operator` must have allowance for the caller of at least `substractedAmount`. +- `operator` must have allowance for the caller of at least `subtractedAmount`. @@ -298,7 +317,7 @@ Atomically decreases the allowance granted to `operator` by the caller. This is **Emitted events:** - [`AuthorizedOperator`](#authorizedoperator) event indicating the updated allowance after decreasing it. -- [`RevokeOperator`](#revokeoperator) event if `substractedAmount` is the full allowance, indicating `operator` does not have any alauthorizedAmountForlowance left for `msg.sender`. +- [`RevokeOperator`](#revokeoperator) event if `subtractedAmount` is the full allowance, indicating `operator` does not have any alauthorizedAmountForlowance left for `msg.sender`. @@ -306,8 +325,8 @@ Atomically decreases the allowance granted to `operator` by the caller. This is | Name | Type | Description | | -------------------------- | :-------: | ------------------------------------------------------ | -| `operator` | `address` | the operator to decrease allowance for `msg.sender` | -| `substractedAmount` | `uint256` | the amount to decrease by in the operator's allowance. | +| `operator` | `address` | The operator to decrease allowance for `msg.sender` | +| `subtractedAmount` | `uint256` | The amount to decrease by in the operator's allowance. | | `operatorNotificationData` | `bytes` | - |
@@ -461,8 +480,8 @@ Atomically increases the allowance granted to `operator` by the caller. This is | Name | Type | Description | | -------------------------- | :-------: | ----------------------------------------------------------------------- | -| `operator` | `address` | the operator to increase the allowance for `msg.sender` | -| `addedAmount` | `uint256` | the additional amount to add on top of the current operator's allowance | +| `operator` | `address` | The operator to increase the allowance for `msg.sender` | +| `addedAmount` | `uint256` | The additional amount to add on top of the current operator's allowance | | `operatorNotificationData` | `bytes` | - |
@@ -482,7 +501,7 @@ Atomically increases the allowance granted to `operator` by the caller. This is function mint( address to, uint256 amount, - bool allowNonLSP1Recipient, + bool force, bytes data ) external nonpayable; ``` @@ -491,12 +510,12 @@ Public [`_mint`](#_mint) function only callable by the [`owner`](#owner). #### Parameters -| Name | Type | Description | -| ----------------------- | :-------: | ----------- | -| `to` | `address` | - | -| `amount` | `uint256` | - | -| `allowNonLSP1Recipient` | `bool` | - | -| `data` | `bytes` | - | +| Name | Type | Description | +| -------- | :-------: | ----------- | +| `to` | `address` | - | +| `amount` | `uint256` | - | +| `force` | `bool` | - | +| `data` | `bytes` | - |
@@ -515,7 +534,7 @@ Public [`_mint`](#_mint) function only callable by the [`owner`](#owner). function name() external view returns (string); ``` -Returns the name of the token. +Returns the name of the token. For compatibility with clients & tools that expect ERC20. #### Returns @@ -744,7 +763,7 @@ Returns true if this contract implements the interface defined by `interfaceId`. function symbol() external view returns (string); ``` -Returns the symbol of the token, usually a shorter version of the name. +Returns the symbol of the token, usually a shorter version of the name. For compatibility with clients & tools that expect ERC20. #### Returns @@ -795,7 +814,7 @@ function transfer( address from, address to, uint256 amount, - bool allowNonLSP1Recipient, + bool force, bytes data ) external nonpayable; ``` @@ -804,13 +823,13 @@ Transfers an `amount` of tokens from the `from` address to the `to` address and #### Parameters -| Name | Type | Description | -| ----------------------- | :-------: | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `from` | `address` | The sender address. | -| `to` | `address` | The recipient address. | -| `amount` | `uint256` | The amount of tokens to transfer. | -| `allowNonLSP1Recipient` | `bool` | When set to `true`, the `to` address CAN be any address. When set to `false`, the `to` address MUST be a contract that supports the LSP1 UniversalReceiver standard. | -| `data` | `bytes` | Any additional data the caller wants included in the emitted event, and sent in the hooks of the `from` and `to` addresses. | +| Name | Type | Description | +| -------- | :-------: | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `from` | `address` | The sender address. | +| `to` | `address` | The recipient address. | +| `amount` | `uint256` | The amount of tokens to transfer. | +| `force` | `bool` | When set to `true`, the `to` address CAN be any address. When set to `false`, the `to` address MUST be a contract that supports the LSP1 UniversalReceiver standard. | +| `data` | `bytes` | Any additional data the caller wants included in the emitted event, and sent in the hooks of the `from` and `to` addresses. |
@@ -827,7 +846,7 @@ Transfers an `amount` of tokens from the `from` address to the `to` address and :::info -This function uses the `allowNonLSP1Recipient` parameter as `true` so that EOA and any contract can receive tokens. +This function uses the `force` parameter as `true` so that EOA and any contract can receive tokens. ::: @@ -838,18 +857,20 @@ function transfer( ) external nonpayable returns (bool); ``` +Transfer function from the ERC20 standard interface. + #### Parameters -| Name | Type | Description | -| -------- | :-------: | ----------- | -| `to` | `address` | - | -| `amount` | `uint256` | - | +| Name | Type | Description | +| -------- | :-------: | --------------------------------- | +| `to` | `address` | The address receiving tokens. | +| `amount` | `uint256` | The amount of tokens to transfer. | #### Returns -| Name | Type | Description | -| ---- | :----: | ----------- | -| `0` | `bool` | - | +| Name | Type | Description | +| ---- | :----: | ------------------------------ | +| `0` | `bool` | `true` on successful transfer. |
@@ -869,7 +890,7 @@ function transferBatch( address[] from, address[] to, uint256[] amount, - bool[] allowNonLSP1Recipient, + bool[] force, bytes[] data ) external nonpayable; ``` @@ -878,13 +899,13 @@ Same as [`transfer(...)`](#`transfer) but transfer multiple tokens based on the #### Parameters -| Name | Type | Description | -| ----------------------- | :---------: | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `from` | `address[]` | An array of sending addresses. | -| `to` | `address[]` | An array of receiving addresses. | -| `amount` | `uint256[]` | An array of amount of tokens to transfer for each `from -> to` transfer. | -| `allowNonLSP1Recipient` | `bool[]` | For each transfer, when set to `true`, the `to` address CAN be any address. When set to `false`, the `to` address MUST be a contract that supports the LSP1 UniversalReceiver standard. | -| `data` | `bytes[]` | An array of additional data the caller wants included in the emitted event, and sent in the hooks to `from` and `to` addresses. | +| Name | Type | Description | +| -------- | :---------: | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `from` | `address[]` | An array of sending addresses. | +| `to` | `address[]` | An array of receiving addresses. | +| `amount` | `uint256[]` | An array of amount of tokens to transfer for each `from -> to` transfer. | +| `force` | `bool[]` | For each transfer, when set to `true`, the `to` address CAN be any address. When set to `false`, the `to` address MUST be a contract that supports the LSP1 UniversalReceiver standard. | +| `data` | `bytes[]` | An array of additional data the caller wants included in the emitted event, and sent in the hooks to `from` and `to` addresses. |
@@ -901,7 +922,7 @@ Same as [`transfer(...)`](#`transfer) but transfer multiple tokens based on the :::info -This function uses the `allowNonLSP1Recipient` parameter as `true` so that EOA and any contract can receive tokens. +This function uses the `force` parameter as `true` so that EOA and any contract can receive tokens. ::: @@ -913,19 +934,21 @@ function transferFrom( ) external nonpayable returns (bool); ``` +Transfer functions for operators from the ERC20 standard interface. + #### Parameters -| Name | Type | Description | -| -------- | :-------: | ----------- | -| `from` | `address` | - | -| `to` | `address` | - | -| `amount` | `uint256` | - | +| Name | Type | Description | +| -------- | :-------: | --------------------------------- | +| `from` | `address` | The address sending tokens. | +| `to` | `address` | The address receiving tokens. | +| `amount` | `uint256` | The amount of tokens to transfer. | #### Returns -| Name | Type | Description | -| ---- | :----: | ----------- | -| `0` | `bool` | - | +| Name | Type | Description | +| ---- | :----: | ------------------------------ | +| `0` | `bool` | `true` on successful transfer. |
@@ -1011,9 +1034,11 @@ mapping(bytes32 => bytes) _store ### \_setData ```solidity -function _setData(bytes32 key, bytes value) internal nonpayable; +function _setData(bytes32 dataKey, bytes dataValue) internal nonpayable; ``` +Save gas by emitting the [`DataChanged`](#datachanged) event with only the first 256 bytes of dataValue +
### \_updateOperator @@ -1035,7 +1060,7 @@ function _updateOperator( function _mint( address to, uint256 amount, - bool allowNonLSP1Recipient, + bool force, bytes data ) internal nonpayable; ``` @@ -1050,6 +1075,28 @@ function _burn(address from, uint256 amount, bytes data) internal nonpayable;
+### \_spendAllowance + +```solidity +function _spendAllowance( + address operator, + address tokenOwner, + uint256 amountToSpend +) internal nonpayable; +``` + +Spend `amountToSpend` from the `operator`'s authorized on behalf of the `tokenOwner`. + +#### Parameters + +| Name | Type | Description | +| --------------- | :-------: | ------------------------------------------------------------------- | +| `operator` | `address` | The address of the operator to decrease the allowance of. | +| `tokenOwner` | `address` | The address that granted an allowance on its balance to `operator`. | +| `amountToSpend` | `uint256` | The amount of tokens to substract in allowance of `operator`. | + +
+ ### \_transfer ```solidity @@ -1057,7 +1104,7 @@ function _transfer( address from, address to, uint256 amount, - bool allowNonLSP1Recipient, + bool force, bytes data ) internal nonpayable; ``` @@ -1070,7 +1117,8 @@ function _transfer( function _beforeTokenTransfer( address from, address to, - uint256 amount + uint256 amount, + bytes data ) internal nonpayable; ``` @@ -1079,11 +1127,37 @@ Allows to run custom logic before updating balances and notifiying sender/recipi #### Parameters -| Name | Type | Description | -| -------- | :-------: | ------------------------------- | -| `from` | `address` | The sender address | -| `to` | `address` | The recipient address | -| `amount` | `uint256` | The amount of token to transfer | +| Name | Type | Description | +| -------- | :-------: | ------------------------------------ | +| `from` | `address` | The sender address | +| `to` | `address` | The recipient address | +| `amount` | `uint256` | The amount of token to transfer | +| `data` | `bytes` | The data sent alongside the transfer | + +
+ +### \_afterTokenTransfer + +```solidity +function _afterTokenTransfer( + address from, + address to, + uint256 amount, + bytes data +) internal nonpayable; +``` + +Hook that is called after any token transfer, including minting and burning. +Allows to run custom logic after updating balances, but **before notifiying sender/recipient** by overriding this function. + +#### Parameters + +| Name | Type | Description | +| -------- | :-------: | ------------------------------------ | +| `from` | `address` | The sender address | +| `to` | `address` | The recipient address | +| `amount` | `uint256` | The amount of token to transfer | +| `data` | `bytes` | The data sent alongside the transfer |
@@ -1133,26 +1207,26 @@ If `from` is an EOA or a contract that does not support the LSP1 interface, noth ```solidity function _notifyTokenReceiver( address to, - bool allowNonLSP1Recipient, + bool force, bytes lsp1Data ) internal nonpayable; ``` Attempt to notify the token receiver `to` about the `amount` tokens being received. This is done by calling its [`universalReceiver`](#universalreceiver) function with the `_TYPEID_LSP7_TOKENSRECIPIENT` as typeId, if `to` is a contract that supports the LSP1 interface. -If `to` is is an EOA or a contract that does not support the LSP1 interface, the behaviour will depend on the `allowNonLSP1Recipient` boolean flag. +If `to` is is an EOA or a contract that does not support the LSP1 interface, the behaviour will depend on the `force` boolean flag. -- if `allowNonLSP1Recipient` is set to `true`, nothing will happen and no notification will be sent. +- if `force` is set to `true`, nothing will happen and no notification will be sent. -- if `allowNonLSP1Recipient` is set to `false, the transaction will revert. +- if `force` is set to `false, the transaction will revert. #### Parameters -| Name | Type | Description | -| ----------------------- | :-------: | --------------------------------------------------------------------------------------------------- | -| `to` | `address` | The address to call the {universalReceiver} function on. | -| `allowNonLSP1Recipient` | `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. | +| Name | Type | Description | +| ---------- | :-------: | --------------------------------------------------------------------------------------------------- | +| `to` | `address` | The address to call the {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. |
@@ -1188,6 +1262,13 @@ Returns the extension address stored under the following data key: ### \_fallbackLSP17Extendable +:::info + +The LSP7 Token contract should not hold any native tokens. Any native tokens received by the contract +will be forwarded to the extension address mapped to the selector from `msg.sig`. + +::: + ```solidity function _fallbackLSP17Extendable( bytes callData @@ -1201,9 +1282,6 @@ Reverts if there is no extension for the function being called. If there is an extension for the function selector being called, it calls the extension with the CALL opcode, passing the [`msg.data`](#msg.data) appended with the 20 bytes of the [`msg.sender`](#msg.sender) and 32 bytes of the [`msg.value`](#msg.value) -Because the function uses assembly [`return()/revert()`](#return) to terminate the call, it cannot be -called before other codes in fallback(). -Otherwise, the codes after \_fallbackLSP17Extendable() may never be reached.
@@ -1224,15 +1302,15 @@ Otherwise, the codes after \_fallbackLSP17Extendable() may never be reached. event Approval(address indexed owner, address indexed spender, uint256 value); ``` -ERC721 `Approval` event emitted when `owner` enables `operator` for `tokenId`. To provide compatibility with indexing ERC721 events. +Emitted when the allowance of a `spender` for an `owner` is set by a call to [`approve`](#approve). `value` is the new allowance. #### Parameters -| Name | Type | Description | -| ----------------------- | :-------: | ------------------------------------------ | -| `owner` **`indexed`** | `address` | The address of the owner of the `tokenId`. | -| `spender` **`indexed`** | `address` | - | -| `value` | `uint256` | - | +| Name | Type | Description | +| ----------------------- | :-------: | ----------- | +| `owner` **`indexed`** | `address` | - | +| `spender` **`indexed`** | `address` | - | +| `value` | `uint256` | - |
@@ -1251,14 +1329,14 @@ ERC721 `Approval` event emitted when `owner` enables `operator` for `tokenId`. T event AuthorizedOperator(address indexed operator, address indexed tokenOwner, uint256 indexed amount, bytes operatorNotificationData); ``` -Emitted when `tokenOwner` enables `operator` to transfer or burn the `tokenId`. +Emitted when `tokenOwner` enables `operator` for `amount` tokens. #### Parameters | Name | Type | Description | | -------------------------- | :-------: | ----------------------------------------------------------------------- | -| `operator` **`indexed`** | `address` | The address authorized as an operator. | -| `tokenOwner` **`indexed`** | `address` | The owner of the `tokenId`. | +| `operator` **`indexed`** | `address` | The address authorized as an operator | +| `tokenOwner` **`indexed`** | `address` | The token owner | | `amount` **`indexed`** | `uint256` | The amount of tokens `operator` address has access to from `tokenOwner` | | `operatorNotificationData` | `bytes` | The data to notify the operator about via LSP1. | @@ -1331,15 +1409,15 @@ event OwnershipTransferred(address indexed previousOwner, address indexed newOwn event RevokedOperator(address indexed operator, address indexed tokenOwner, bytes operatorNotificationData); ``` -Emitted when `tokenOwner` disables `operator` to transfer or burn `tokenId` on its behalf. +Emitted when `tokenOwner` disables `operator` for `amount` tokens and set its [`authorizedAmountFor(...)`](#`authorizedamountfor) to `0`. #### Parameters -| Name | Type | Description | -| -------------------------- | :-------: | --------------------------------------------------------------- | -| `operator` **`indexed`** | `address` | The address revoked from the operator array ({getOperatorsOf}). | -| `tokenOwner` **`indexed`** | `address` | The owner of the `tokenId`. | -| `operatorNotificationData` | `bytes` | The data to notify the operator about via LSP1. | +| Name | Type | Description | +| -------------------------- | :-------: | ----------------------------------------------- | +| `operator` **`indexed`** | `address` | The address revoked from operating | +| `tokenOwner` **`indexed`** | `address` | The token owner | +| `operatorNotificationData` | `bytes` | The data to notify the operator about via LSP1. |
@@ -1355,21 +1433,48 @@ Emitted when `tokenOwner` disables `operator` to transfer or burn `tokenId` on i ::: ```solidity -event Transfer(address indexed operator, address indexed from, address indexed to, uint256 amount, bool allowNonLSP1Recipient, bytes data); +event Transfer(address indexed operator, address indexed from, address indexed to, uint256 amount, bool force, bytes data); ``` -Emitted when `tokenId` token is transferred from the `from` to the `to` address. +Emitted when the `from` transferred successfully `amount` of tokens to `to`. #### Parameters -| Name | Type | Description | -| ------------------------ | :-------: | ---------------------------------------------------------------------------------------------------------------------------------- | -| `operator` **`indexed`** | `address` | The address of operator that sent the `tokenId` | -| `from` **`indexed`** | `address` | The previous owner of the `tokenId` | -| `to` **`indexed`** | `address` | The new owner of `tokenId` | -| `amount` | `uint256` | The amount of tokens transferred. | -| `allowNonLSP1Recipient` | `bool` | If the token transfer enforces the `to` recipient address to be a contract that implements the LSP1 standard or not. | -| `data` | `bytes` | Any additional data the caller included by the caller during the transfer, and sent in the hooks to the `from` and `to` addresses. | +| Name | Type | Description | +| ------------------------ | :-------: | ---------------------------------------------------------------------------------------------------------------------------- | +| `operator` **`indexed`** | `address` | The address of the operator that executed the transfer. | +| `from` **`indexed`** | `address` | The address which tokens were sent from (balance decreased by `-amount`). | +| `to` **`indexed`** | `address` | The address that received the tokens (balance increased by `+amount`). | +| `amount` | `uint256` | The amount of tokens transferred. | +| `force` | `bool` | if the transferred enforced the `to` recipient address to be a contract that implements the LSP1 standard or not. | +| `data` | `bytes` | Any additional data included by the caller during the transfer, and sent in the LSP1 hooks to the `from` and `to` addresses. | + +
+ +### Transfer + +:::note References + +- Specification details: [**LSP-7-DigitalAsset**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-7-DigitalAsset.md#transfer) +- Solidity implementation: [`LSP7CompatibleERC20Mintable.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP7DigitalAsset/presets/LSP7CompatibleERC20Mintable.sol) +- Event signature: `Transfer(address,address,uint256)` +- Event topic hash: `0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef` + +::: + +```solidity +event Transfer(address indexed from, address indexed to, uint256 value); +``` + +Emitted when `value` tokens are moved from one account (`from`) to another (`to`). Note that `value` may be zero. + +#### Parameters + +| Name | Type | Description | +| -------------------- | :-------: | ----------- | +| `from` **`indexed`** | `address` | - | +| `to` **`indexed`** | `address` | - | +| `value` | `uint256` | - |
@@ -1702,7 +1807,7 @@ error LSP7NotifyTokenReceiverContractMissingLSP1Interface( ); ``` -reverts if the `tokenReceiver` does not implement LSP1 when minting or transferring tokens with `bool allowNonLSP1Recipient` set as `false`. +reverts if the `tokenReceiver` does not implement LSP1 when minting or transferring tokens with `bool force` set as `false`. #### Parameters @@ -1727,7 +1832,7 @@ reverts if the `tokenReceiver` does not implement LSP1 when minting or transferr error LSP7NotifyTokenReceiverIsEOA(address tokenReceiver); ``` -reverts if the `tokenReceiver` is an EOA when minting or transferring tokens with `bool allowNonLSP1Recipient` set as `false`. +reverts if the `tokenReceiver` is an EOA when minting or transferring tokens with `bool force` set as `false`. #### Parameters @@ -1737,6 +1842,27 @@ reverts if the `tokenReceiver` is an EOA when minting or transferring tokens wit
+### LSP7TokenContractCannotHoldValue + +:::note References + +- Specification details: [**LSP-7-DigitalAsset**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-7-DigitalAsset.md#lsp7tokencontractcannotholdvalue) +- Solidity implementation: [`LSP7CompatibleERC20Mintable.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP7DigitalAsset/presets/LSP7CompatibleERC20Mintable.sol) +- Error signature: `LSP7TokenContractCannotHoldValue()` +- Error hash: `0x388f5adc` + +::: + +```solidity +error LSP7TokenContractCannotHoldValue(); +``` + +_LSP7 contract cannot receive native tokens._ + +Error occurs when sending native tokens to the LSP7 contract without sending any data. E.g. Sending value without passing a bytes4 function selector to call a LSP17 Extension. + +
+ ### LSP7TokenOwnerCannotBeOperator :::note References @@ -1780,3 +1906,47 @@ reverts when there is no extension for the function selector being called with | `functionSelector` | `bytes4` | - |
+ +### OwnableCallerNotTheOwner + +:::note References + +- Specification details: [**LSP-7-DigitalAsset**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-7-DigitalAsset.md#ownablecallernottheowner) +- Solidity implementation: [`LSP7CompatibleERC20Mintable.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP7DigitalAsset/presets/LSP7CompatibleERC20Mintable.sol) +- Error signature: `OwnableCallerNotTheOwner(address)` +- Error hash: `0xbf1169c5` + +::: + +```solidity +error OwnableCallerNotTheOwner(address callerAddress); +``` + +Reverts when only the owner is allowed to call the function. + +#### Parameters + +| Name | Type | Description | +| --------------- | :-------: | ---------------------------------------- | +| `callerAddress` | `address` | The address that tried to make the call. | + +
+ +### OwnableCannotSetZeroAddressAsOwner + +:::note References + +- Specification details: [**LSP-7-DigitalAsset**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-7-DigitalAsset.md#ownablecannotsetzeroaddressasowner) +- Solidity implementation: [`LSP7CompatibleERC20Mintable.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP7DigitalAsset/presets/LSP7CompatibleERC20Mintable.sol) +- Error signature: `OwnableCannotSetZeroAddressAsOwner()` +- Error hash: `0x1ad8836c` + +::: + +```solidity +error OwnableCannotSetZeroAddressAsOwner(); +``` + +Reverts when trying to set `address(0)` as the contract owner when deploying the contract, initializing it or transferring ownership of the contract. + +
diff --git a/docs/contracts/LSP7DigitalAsset/presets/LSP7Mintable.md b/docs/contracts/LSP7DigitalAsset/presets/LSP7Mintable.md index 8deb38829..a178eff9e 100644 --- a/docs/contracts/LSP7DigitalAsset/presets/LSP7Mintable.md +++ b/docs/contracts/LSP7DigitalAsset/presets/LSP7Mintable.md @@ -39,7 +39,7 @@ constructor( ); ``` -_Deploying a `LSP7Mintable` token contract with: token name = `name_`, token symbol = `symbol*`, and address `newOwner*` as the token contract owner.\_ +_Deploying a `LSP7Mintable` token contract with: token name = `name_`, token symbol = `symbol_`, and address `newOwner_` as the token contract owner._ #### Parameters @@ -87,6 +87,25 @@ This function is executed when:
+### receive + +:::note References + +- Specification details: [**LSP-7-DigitalAsset**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-7-DigitalAsset.md#receive) +- Solidity implementation: [`LSP7Mintable.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP7DigitalAsset/presets/LSP7Mintable.sol) + +::: + +```solidity +receive() external payable; +``` + +_LSP7 contract cannot receive native tokens._ + +Reverts whenever someone tries to send native tokens to a LSP7 contract. + +
+ ### authorizeOperator :::note References @@ -235,12 +254,12 @@ This is a non-standard function, not part of the LSP7 standard interface. It has ```solidity function decreaseAllowance( address operator, - uint256 substractedAmount, + uint256 subtractedAmount, bytes operatorNotificationData ) external nonpayable; ``` -_Decrease the allowance of `operator` by -`substractedAmount`_ +_Decrease the allowance of `operator` by -`subtractedAmount`_ Atomically decreases the allowance granted to `operator` by the caller. This is an alternative approach to [`authorizeOperator`](#authorizeoperator) that can be used as a mitigation for the double spending allowance problem. @@ -249,7 +268,7 @@ Atomically decreases the allowance granted to `operator` by the caller. This is **Requirements:** - `operator` cannot be the zero address. -- `operator` must have allowance for the caller of at least `substractedAmount`. +- `operator` must have allowance for the caller of at least `subtractedAmount`. @@ -258,7 +277,7 @@ Atomically decreases the allowance granted to `operator` by the caller. This is **Emitted events:** - [`AuthorizedOperator`](#authorizedoperator) event indicating the updated allowance after decreasing it. -- [`RevokeOperator`](#revokeoperator) event if `substractedAmount` is the full allowance, indicating `operator` does not have any alauthorizedAmountForlowance left for `msg.sender`. +- [`RevokeOperator`](#revokeoperator) event if `subtractedAmount` is the full allowance, indicating `operator` does not have any alauthorizedAmountForlowance left for `msg.sender`. @@ -266,8 +285,8 @@ Atomically decreases the allowance granted to `operator` by the caller. This is | Name | Type | Description | | -------------------------- | :-------: | ------------------------------------------------------ | -| `operator` | `address` | the operator to decrease allowance for `msg.sender` | -| `substractedAmount` | `uint256` | the amount to decrease by in the operator's allowance. | +| `operator` | `address` | The operator to decrease allowance for `msg.sender` | +| `subtractedAmount` | `uint256` | The amount to decrease by in the operator's allowance. | | `operatorNotificationData` | `bytes` | - |
@@ -421,8 +440,8 @@ Atomically increases the allowance granted to `operator` by the caller. This is | Name | Type | Description | | -------------------------- | :-------: | ----------------------------------------------------------------------- | -| `operator` | `address` | the operator to increase the allowance for `msg.sender` | -| `addedAmount` | `uint256` | the additional amount to add on top of the current operator's allowance | +| `operator` | `address` | The operator to increase the allowance for `msg.sender` | +| `addedAmount` | `uint256` | The additional amount to add on top of the current operator's allowance | | `operatorNotificationData` | `bytes` | - |
@@ -442,7 +461,7 @@ Atomically increases the allowance granted to `operator` by the caller. This is function mint( address to, uint256 amount, - bool allowNonLSP1Recipient, + bool force, bytes data ) external nonpayable; ``` @@ -451,12 +470,12 @@ Public [`_mint`](#_mint) function only callable by the [`owner`](#owner). #### Parameters -| Name | Type | Description | -| ----------------------- | :-------: | ----------- | -| `to` | `address` | - | -| `amount` | `uint256` | - | -| `allowNonLSP1Recipient` | `bool` | - | -| `data` | `bytes` | - | +| Name | Type | Description | +| -------- | :-------: | ----------- | +| `to` | `address` | - | +| `amount` | `uint256` | - | +| `force` | `bool` | - | +| `data` | `bytes` | - |
@@ -705,7 +724,7 @@ function transfer( address from, address to, uint256 amount, - bool allowNonLSP1Recipient, + bool force, bytes data ) external nonpayable; ``` @@ -714,13 +733,13 @@ Transfers an `amount` of tokens from the `from` address to the `to` address and #### Parameters -| Name | Type | Description | -| ----------------------- | :-------: | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `from` | `address` | The sender address. | -| `to` | `address` | The recipient address. | -| `amount` | `uint256` | The amount of tokens to transfer. | -| `allowNonLSP1Recipient` | `bool` | When set to `true`, the `to` address CAN be any address. When set to `false`, the `to` address MUST be a contract that supports the LSP1 UniversalReceiver standard. | -| `data` | `bytes` | Any additional data the caller wants included in the emitted event, and sent in the hooks of the `from` and `to` addresses. | +| Name | Type | Description | +| -------- | :-------: | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `from` | `address` | The sender address. | +| `to` | `address` | The recipient address. | +| `amount` | `uint256` | The amount of tokens to transfer. | +| `force` | `bool` | When set to `true`, the `to` address CAN be any address. When set to `false`, the `to` address MUST be a contract that supports the LSP1 UniversalReceiver standard. | +| `data` | `bytes` | Any additional data the caller wants included in the emitted event, and sent in the hooks of the `from` and `to` addresses. |
@@ -740,7 +759,7 @@ function transferBatch( address[] from, address[] to, uint256[] amount, - bool[] allowNonLSP1Recipient, + bool[] force, bytes[] data ) external nonpayable; ``` @@ -749,13 +768,13 @@ Same as [`transfer(...)`](#`transfer) but transfer multiple tokens based on the #### Parameters -| Name | Type | Description | -| ----------------------- | :---------: | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `from` | `address[]` | An array of sending addresses. | -| `to` | `address[]` | An array of receiving addresses. | -| `amount` | `uint256[]` | An array of amount of tokens to transfer for each `from -> to` transfer. | -| `allowNonLSP1Recipient` | `bool[]` | For each transfer, when set to `true`, the `to` address CAN be any address. When set to `false`, the `to` address MUST be a contract that supports the LSP1 UniversalReceiver standard. | -| `data` | `bytes[]` | An array of additional data the caller wants included in the emitted event, and sent in the hooks to `from` and `to` addresses. | +| Name | Type | Description | +| -------- | :---------: | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `from` | `address[]` | An array of sending addresses. | +| `to` | `address[]` | An array of receiving addresses. | +| `amount` | `uint256[]` | An array of amount of tokens to transfer for each `from -> to` transfer. | +| `force` | `bool[]` | For each transfer, when set to `true`, the `to` address CAN be any address. When set to `false`, the `to` address MUST be a contract that supports the LSP1 UniversalReceiver standard. | +| `data` | `bytes[]` | An array of additional data the caller wants included in the emitted event, and sent in the hooks to `from` and `to` addresses. |
@@ -854,7 +873,7 @@ Save gas by emitting the [`DataChanged`](#datachanged) event with only the first function _updateOperator( address tokenOwner, address operator, - uint256 amount, + uint256 allowance, bytes operatorNotificationData ) internal nonpayable; ``` @@ -863,15 +882,34 @@ Changes token `amount` the `operator` has access to from `tokenOwner` tokens. If the amount is zero the operator is removed from the list of operators, otherwise he is added to the list of operators. If the amount is zero then the operator is being revoked, otherwise the operator amount is being modified. +#### Parameters + +| Name | Type | Description | +| -------------------------- | :-------: | -------------------------------------------------------------------------------------- | +| `tokenOwner` | `address` | The address that will give `operator` an allowance for on its balance. | +| `operator` | `address` | The address to grant an allowance to spend. | +| `allowance` | `uint256` | The maximum amount of token that `operator` can spend from the `tokenOwner`'s balance. | +| `operatorNotificationData` | `bytes` | - | +
### \_mint +:::info + +Any logic in the: + +- {\_beforeTokenTransfer} function will run before updating the balances. + +- {\_afterTokenTransfer} function will run after updating the balances, **but before notifying the recipient via LSP1**. + +::: + ```solidity function _mint( address to, uint256 amount, - bool allowNonLSP1Recipient, + bool force, bytes data ) internal nonpayable; ``` @@ -888,17 +926,27 @@ 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. | -| `allowNonLSP1Recipient` | `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} event, and sent in the LSP1 hook to the `to` address. |
### \_burn +:::info + +Any logic in the: + +- {\_beforeTokenTransfer} function will run before updating the balances. + +- {\_afterTokenTransfer} function will run after updating the balances, **but before notifying the sender via LSP1**. + +::: + :::tip Hint In dApps, you can know which address is burning tokens by listening for the `Transfer` event and filter with the zero address as `to`. @@ -913,7 +961,6 @@ Burns (= destroys) `amount` of tokens, decrease the `from` balance. This is done Both the sender and recipient will be notified of the token transfer through the LSP1 [`universalReceiver`](#universalreceiver) function, if they are contracts that support the LSP1 interface. Their `universalReceiver` function will receive all the parameters in the calldata packed encoded. -Any logic in the [`_beforeTokenTransfer`](#_beforetokentransfer) function will run before updating the balances.
@@ -927,20 +974,52 @@ Any logic in the [`_beforeTokenTransfer`](#_beforetokentransfer) function will r | Name | Type | Description | | -------- | :-------: | ------------------------------------------------------------------------------------------------------------------------- | -| `from` | `address` | the address to burn tokens from its balance. | -| `amount` | `uint256` | the amount of tokens to burn. | +| `from` | `address` | The address to burn tokens from its balance. | +| `amount` | `uint256` | The amount of tokens to burn. | | `data` | `bytes` | Additional data the caller wants included in the emitted event, and sent in the LSP1 hook to the `from` and `to` address. |
+### \_spendAllowance + +```solidity +function _spendAllowance( + address operator, + address tokenOwner, + uint256 amountToSpend +) internal nonpayable; +``` + +Spend `amountToSpend` from the `operator`'s authorized on behalf of the `tokenOwner`. + +#### Parameters + +| Name | Type | Description | +| --------------- | :-------: | ------------------------------------------------------------------- | +| `operator` | `address` | The address of the operator to decrease the allowance of. | +| `tokenOwner` | `address` | The address that granted an allowance on its balance to `operator`. | +| `amountToSpend` | `uint256` | The amount of tokens to substract in allowance of `operator`. | + +
+ ### \_transfer +:::info + +Any logic in the: + +- {\_beforeTokenTransfer} function will run before updating the balances. + +- {\_afterTokenTransfer} function will run after updating the balances, **but before notifying the sender/recipient via LSP1**. + +::: + ```solidity function _transfer( address from, address to, uint256 amount, - bool allowNonLSP1Recipient, + bool force, bytes data ) internal nonpayable; ``` @@ -950,7 +1029,6 @@ of `to` by `+amount`. Both the sender and recipient will be notified of the token transfer through the LSP1 [`universalReceiver`](#universalreceiver) function, if they are contracts that support the LSP1 interface. Their `universalReceiver` function will receive all the parameters in the calldata packed encoded. -Any logic in the [`_beforeTokenTransfer`](#_beforetokentransfer) function will run before updating the balances.
@@ -962,13 +1040,13 @@ Any logic in the [`_beforeTokenTransfer`](#_beforetokentransfer) function will r #### Parameters -| Name | Type | Description | -| ----------------------- | :-------: | ------------------------------------------------------------------------------------------------------------------------- | -| `from` | `address` | the address to decrease the balance. | -| `to` | `address` | the address to increase the balance. | -| `amount` | `uint256` | the amount of tokens to transfer from `from` to `to`. | -| `allowNonLSP1Recipient` | `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 event, and sent in the LSP1 hook to the `from` and `to` address. | +| Name | Type | Description | +| -------- | :-------: | ------------------------------------------------------------------------------------------------------------------------- | +| `from` | `address` | The address to decrease the balance. | +| `to` | `address` | The address to increase the balance. | +| `amount` | `uint256` | The amount of tokens to transfer from `from` to `to`. | +| `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 event, and sent in the LSP1 hook to the `from` and `to` address. |
@@ -978,7 +1056,8 @@ Any logic in the [`_beforeTokenTransfer`](#_beforetokentransfer) function will r function _beforeTokenTransfer( address from, address to, - uint256 amount + uint256 amount, + bytes data ) internal nonpayable; ``` @@ -987,11 +1066,37 @@ Allows to run custom logic before updating balances and notifiying sender/recipi #### Parameters -| Name | Type | Description | -| -------- | :-------: | ------------------------------- | -| `from` | `address` | The sender address | -| `to` | `address` | The recipient address | -| `amount` | `uint256` | The amount of token to transfer | +| Name | Type | Description | +| -------- | :-------: | ------------------------------------ | +| `from` | `address` | The sender address | +| `to` | `address` | The recipient address | +| `amount` | `uint256` | The amount of token to transfer | +| `data` | `bytes` | The data sent alongside the transfer | + +
+ +### \_afterTokenTransfer + +```solidity +function _afterTokenTransfer( + address from, + address to, + uint256 amount, + bytes data +) internal nonpayable; +``` + +Hook that is called after any token transfer, including minting and burning. +Allows to run custom logic after updating balances, but **before notifiying sender/recipient** by overriding this function. + +#### Parameters + +| Name | Type | Description | +| -------- | :-------: | ------------------------------------ | +| `from` | `address` | The sender address | +| `to` | `address` | The recipient address | +| `amount` | `uint256` | The amount of token to transfer | +| `data` | `bytes` | The data sent alongside the transfer |
@@ -1041,26 +1146,26 @@ If `from` is an EOA or a contract that does not support the LSP1 interface, noth ```solidity function _notifyTokenReceiver( address to, - bool allowNonLSP1Recipient, + bool force, bytes lsp1Data ) internal nonpayable; ``` Attempt to notify the token receiver `to` about the `amount` tokens being received. This is done by calling its [`universalReceiver`](#universalreceiver) function with the `_TYPEID_LSP7_TOKENSRECIPIENT` as typeId, if `to` is a contract that supports the LSP1 interface. -If `to` is is an EOA or a contract that does not support the LSP1 interface, the behaviour will depend on the `allowNonLSP1Recipient` boolean flag. +If `to` is is an EOA or a contract that does not support the LSP1 interface, the behaviour will depend on the `force` boolean flag. -- if `allowNonLSP1Recipient` is set to `true`, nothing will happen and no notification will be sent. +- if `force` is set to `true`, nothing will happen and no notification will be sent. -- if `allowNonLSP1Recipient` is set to `false, the transaction will revert. +- if `force` is set to `false, the transaction will revert. #### Parameters -| Name | Type | Description | -| ----------------------- | :-------: | --------------------------------------------------------------------------------------------------- | -| `to` | `address` | The address to call the {universalReceiver} function on. | -| `allowNonLSP1Recipient` | `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. | +| Name | Type | Description | +| ---------- | :-------: | --------------------------------------------------------------------------------------------------- | +| `to` | `address` | The address to call the {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. |
@@ -1096,6 +1201,13 @@ Returns the extension address stored under the following data key: ### \_fallbackLSP17Extendable +:::info + +The LSP7 Token contract should not hold any native tokens. Any native tokens received by the contract +will be forwarded to the extension address mapped to the selector from `msg.sig`. + +::: + ```solidity function _fallbackLSP17Extendable( bytes callData @@ -1109,9 +1221,6 @@ Reverts if there is no extension for the function being called. If there is an extension for the function selector being called, it calls the extension with the CALL opcode, passing the [`msg.data`](#msg.data) appended with the 20 bytes of the [`msg.sender`](#msg.sender) and 32 bytes of the [`msg.value`](#msg.value) -Because the function uses assembly [`return()/revert()`](#return) to terminate the call, it cannot be -called before other codes in fallback(). -Otherwise, the codes after \_fallbackLSP17Extendable() may never be reached.
@@ -1132,14 +1241,14 @@ Otherwise, the codes after \_fallbackLSP17Extendable() may never be reached. event AuthorizedOperator(address indexed operator, address indexed tokenOwner, uint256 indexed amount, bytes operatorNotificationData); ``` -Emitted when `tokenOwner` enables `operator` to transfer or burn the `tokenId`. +Emitted when `tokenOwner` enables `operator` for `amount` tokens. #### Parameters | Name | Type | Description | | -------------------------- | :-------: | ----------------------------------------------------------------------- | -| `operator` **`indexed`** | `address` | The address authorized as an operator. | -| `tokenOwner` **`indexed`** | `address` | The owner of the `tokenId`. | +| `operator` **`indexed`** | `address` | The address authorized as an operator | +| `tokenOwner` **`indexed`** | `address` | The token owner | | `amount` **`indexed`** | `uint256` | The amount of tokens `operator` address has access to from `tokenOwner` | | `operatorNotificationData` | `bytes` | The data to notify the operator about via LSP1. | @@ -1212,15 +1321,15 @@ event OwnershipTransferred(address indexed previousOwner, address indexed newOwn event RevokedOperator(address indexed operator, address indexed tokenOwner, bytes operatorNotificationData); ``` -Emitted when `tokenOwner` disables `operator` to transfer or burn `tokenId` on its behalf. +Emitted when `tokenOwner` disables `operator` for `amount` tokens and set its [`authorizedAmountFor(...)`](#`authorizedamountfor) to `0`. #### Parameters -| Name | Type | Description | -| -------------------------- | :-------: | --------------------------------------------------------------- | -| `operator` **`indexed`** | `address` | The address revoked from the operator array ({getOperatorsOf}). | -| `tokenOwner` **`indexed`** | `address` | The owner of the `tokenId`. | -| `operatorNotificationData` | `bytes` | The data to notify the operator about via LSP1. | +| Name | Type | Description | +| -------------------------- | :-------: | ----------------------------------------------- | +| `operator` **`indexed`** | `address` | The address revoked from operating | +| `tokenOwner` **`indexed`** | `address` | The token owner | +| `operatorNotificationData` | `bytes` | The data to notify the operator about via LSP1. |
@@ -1236,21 +1345,21 @@ Emitted when `tokenOwner` disables `operator` to transfer or burn `tokenId` on i ::: ```solidity -event Transfer(address indexed operator, address indexed from, address indexed to, uint256 amount, bool allowNonLSP1Recipient, bytes data); +event Transfer(address indexed operator, address indexed from, address indexed to, uint256 amount, bool force, bytes data); ``` -Emitted when `tokenId` token is transferred from the `from` to the `to` address. +Emitted when the `from` transferred successfully `amount` of tokens to `to`. #### Parameters -| Name | Type | Description | -| ------------------------ | :-------: | ---------------------------------------------------------------------------------------------------------------------------------- | -| `operator` **`indexed`** | `address` | The address of operator that sent the `tokenId` | -| `from` **`indexed`** | `address` | The previous owner of the `tokenId` | -| `to` **`indexed`** | `address` | The new owner of `tokenId` | -| `amount` | `uint256` | The amount of tokens transferred. | -| `allowNonLSP1Recipient` | `bool` | If the token transfer enforces the `to` recipient address to be a contract that implements the LSP1 standard or not. | -| `data` | `bytes` | Any additional data the caller included by the caller during the transfer, and sent in the hooks to the `from` and `to` addresses. | +| Name | Type | Description | +| ------------------------ | :-------: | ---------------------------------------------------------------------------------------------------------------------------- | +| `operator` **`indexed`** | `address` | The address of the operator that executed the transfer. | +| `from` **`indexed`** | `address` | The address which tokens were sent from (balance decreased by `-amount`). | +| `to` **`indexed`** | `address` | The address that received the tokens (balance increased by `+amount`). | +| `amount` | `uint256` | The amount of tokens transferred. | +| `force` | `bool` | if the transferred enforced the `to` recipient address to be a contract that implements the LSP1 standard or not. | +| `data` | `bytes` | Any additional data included by the caller during the transfer, and sent in the LSP1 hooks to the `from` and `to` addresses. |
@@ -1583,7 +1692,7 @@ error LSP7NotifyTokenReceiverContractMissingLSP1Interface( ); ``` -reverts if the `tokenReceiver` does not implement LSP1 when minting or transferring tokens with `bool allowNonLSP1Recipient` set as `false`. +reverts if the `tokenReceiver` does not implement LSP1 when minting or transferring tokens with `bool force` set as `false`. #### Parameters @@ -1608,7 +1717,7 @@ reverts if the `tokenReceiver` does not implement LSP1 when minting or transferr error LSP7NotifyTokenReceiverIsEOA(address tokenReceiver); ``` -reverts if the `tokenReceiver` is an EOA when minting or transferring tokens with `bool allowNonLSP1Recipient` set as `false`. +reverts if the `tokenReceiver` is an EOA when minting or transferring tokens with `bool force` set as `false`. #### Parameters @@ -1618,6 +1727,27 @@ reverts if the `tokenReceiver` is an EOA when minting or transferring tokens wit
+### LSP7TokenContractCannotHoldValue + +:::note References + +- Specification details: [**LSP-7-DigitalAsset**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-7-DigitalAsset.md#lsp7tokencontractcannotholdvalue) +- Solidity implementation: [`LSP7Mintable.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP7DigitalAsset/presets/LSP7Mintable.sol) +- Error signature: `LSP7TokenContractCannotHoldValue()` +- Error hash: `0x388f5adc` + +::: + +```solidity +error LSP7TokenContractCannotHoldValue(); +``` + +_LSP7 contract cannot receive native tokens._ + +Error occurs when sending native tokens to the LSP7 contract without sending any data. E.g. Sending value without passing a bytes4 function selector to call a LSP17 Extension. + +
+ ### LSP7TokenOwnerCannotBeOperator :::note References @@ -1661,3 +1791,47 @@ reverts when there is no extension for the function selector being called with | `functionSelector` | `bytes4` | - |
+ +### OwnableCallerNotTheOwner + +:::note References + +- Specification details: [**LSP-7-DigitalAsset**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-7-DigitalAsset.md#ownablecallernottheowner) +- Solidity implementation: [`LSP7Mintable.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP7DigitalAsset/presets/LSP7Mintable.sol) +- Error signature: `OwnableCallerNotTheOwner(address)` +- Error hash: `0xbf1169c5` + +::: + +```solidity +error OwnableCallerNotTheOwner(address callerAddress); +``` + +Reverts when only the owner is allowed to call the function. + +#### Parameters + +| Name | Type | Description | +| --------------- | :-------: | ---------------------------------------- | +| `callerAddress` | `address` | The address that tried to make the call. | + +
+ +### OwnableCannotSetZeroAddressAsOwner + +:::note References + +- Specification details: [**LSP-7-DigitalAsset**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-7-DigitalAsset.md#ownablecannotsetzeroaddressasowner) +- Solidity implementation: [`LSP7Mintable.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP7DigitalAsset/presets/LSP7Mintable.sol) +- Error signature: `OwnableCannotSetZeroAddressAsOwner()` +- Error hash: `0x1ad8836c` + +::: + +```solidity +error OwnableCannotSetZeroAddressAsOwner(); +``` + +Reverts when trying to set `address(0)` as the contract owner when deploying the contract, initializing it or transferring ownership of the contract. + +
diff --git a/docs/contracts/LSP8IdentifiableDigitalAsset/LSP8IdentifiableDigitalAsset.md b/docs/contracts/LSP8IdentifiableDigitalAsset/LSP8IdentifiableDigitalAsset.md index 53def3084..33a2ba6d7 100644 --- a/docs/contracts/LSP8IdentifiableDigitalAsset/LSP8IdentifiableDigitalAsset.md +++ b/docs/contracts/LSP8IdentifiableDigitalAsset/LSP8IdentifiableDigitalAsset.md @@ -58,6 +58,25 @@ This function is executed when:
+### receive + +:::note References + +- Specification details: [**LSP-8-IdentifiableDigitalAsset**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-8-IdentifiableDigitalAsset.md#receive) +- Solidity implementation: [`LSP8IdentifiableDigitalAsset.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP8IdentifiableDigitalAsset/LSP8IdentifiableDigitalAsset.sol) + +::: + +```solidity +receive() external payable; +``` + +_LSP8 contract cannot receive native tokens._ + +Reverts whenever someone tries to send native tokens to a LSP8 contract. + +
+ ### authorizeOperator :::note References @@ -563,22 +582,22 @@ function transfer( address from, address to, bytes32 tokenId, - bool allowNonLSP1Recipient, + bool force, bytes data ) external nonpayable; ``` -Transfer a given `tokenId` token from the `from` address to the `to` address. If operators are set for a specific `tokenId`, all the operators are revoked after the tokenId have been transferred. The `allowNonLSP1Recipient` parameter MUST be set to `true` when transferring tokens to Externally Owned Accounts (EOAs) or contracts that do not implement the LSP1 standard. +Transfer a given `tokenId` token from the `from` address to the `to` address. If operators are set for a specific `tokenId`, all the operators are revoked after the tokenId have been transferred. The `force` parameter MUST be set to `true` when transferring tokens to Externally Owned Accounts (EOAs) or contracts that do not implement the LSP1 standard. #### Parameters -| Name | Type | Description | -| ----------------------- | :-------: | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `from` | `address` | The address that owns the given `tokenId`. | -| `to` | `address` | The address that will receive the `tokenId`. | -| `tokenId` | `bytes32` | The token ID to transfer. | -| `allowNonLSP1Recipient` | `bool` | When set to `true`, the `to` address CAN be any addres. When set to `false`, the `to` address MUST be a contract that supports the LSP1 UniversalReceiver standard. | -| `data` | `bytes` | Any additional data the caller wants included in the emitted event, and sent in the hooks of the `from` and `to` addresses. | +| Name | Type | Description | +| --------- | :-------: | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `from` | `address` | The address that owns the given `tokenId`. | +| `to` | `address` | The address that will receive the `tokenId`. | +| `tokenId` | `bytes32` | The token ID to transfer. | +| `force` | `bool` | When set to `true`, the `to` address CAN be any addres. When set to `false`, the `to` address MUST be a contract that supports the LSP1 UniversalReceiver standard. | +| `data` | `bytes` | Any additional data the caller wants included in the emitted event, and sent in the hooks of the `from` and `to` addresses. |
@@ -598,7 +617,7 @@ function transferBatch( address[] from, address[] to, bytes32[] tokenId, - bool[] allowNonLSP1Recipient, + bool[] force, bytes[] data ) external nonpayable; ``` @@ -607,13 +626,13 @@ Transfers multiple tokens at once based on the arrays of `from`, `to` and `token #### Parameters -| Name | Type | Description | -| ----------------------- | :---------: | ----------------------------------------------------------------------------------------------------------------------------------------- | -| `from` | `address[]` | An array of sending addresses. | -| `to` | `address[]` | An array of recipient addresses. | -| `tokenId` | `bytes32[]` | An array of token IDs to transfer. | -| `allowNonLSP1Recipient` | `bool[]` | When set to `true`, `to` may be any address. When set to `false`, `to` must be a contract that supports the LSP1 standard and not revert. | -| `data` | `bytes[]` | Any additional data the caller wants included in the emitted event, and sent in the hooks to the `from` and `to` addresses. | +| Name | Type | Description | +| --------- | :---------: | ----------------------------------------------------------------------------------------------------------------------------------------- | +| `from` | `address[]` | An array of sending addresses. | +| `to` | `address[]` | An array of recipient addresses. | +| `tokenId` | `bytes32[]` | An array of token IDs to transfer. | +| `force` | `bool[]` | When set to `true`, `to` may be any address. When set to `false`, `to` must be a contract that supports the LSP1 standard and not revert. | +| `data` | `bytes[]` | Any additional data the caller wants included in the emitted event, and sent in the hooks to the `from` and `to` addresses. |
@@ -702,7 +721,8 @@ mapping(bytes32 => bytes) _store function _setData(bytes32 dataKey, bytes dataValue) internal nonpayable; ``` -Save gas by emitting the [`DataChanged`](#datachanged) event with only the first 256 bytes of dataValue +The ERC725Y data key `_LSP8_TOKENID_TYPE_KEY` cannot be changed +once the identifiable digital asset contract has been deployed.
@@ -783,11 +803,21 @@ When `tokenId` does not exist then revert with an error. ### \_mint +:::info + +Any logic in the: + +- {\_beforeTokenTransfer} function will run before updating the balances and ownership of `tokenId`s. + +- {\_afterTokenTransfer} function will run after updating the balances and ownership of `tokenId`s, **but before notifying the recipient via LSP1**. + +::: + ```solidity function _mint( address to, bytes32 tokenId, - bool allowNonLSP1Recipient, + bool force, bytes data ) internal nonpayable; ``` @@ -804,17 +834,27 @@ Create `tokenId` by minting it and transfers it to `to`. #### Parameters -| Name | Type | Description | -| ----------------------- | :-------: | -------------------------------------------------------------------------------------------------------------------------- | -| `to` | `address` | @param tokenId The token ID to create (= mint). | -| `tokenId` | `bytes32` | The token ID to create (= mint). | -| `allowNonLSP1Recipient` | `bool` | When set to `true`, `to` may be any address. When set to `false`, `to` must be a contract that supports the LSP1 standard. | -| `data` | `bytes` | Any additional data the caller wants included in the emitted event, and sent in the hook of the `to` address. | +| Name | Type | Description | +| --------- | :-------: | -------------------------------------------------------------------------------------------------------------------------- | +| `to` | `address` | @param tokenId The token ID to create (= mint). | +| `tokenId` | `bytes32` | The token ID to create (= mint). | +| `force` | `bool` | When set to `true`, `to` may be any address. When set to `false`, `to` must be a contract that supports the LSP1 standard. | +| `data` | `bytes` | Any additional data the caller wants included in the emitted event, and sent in the hook of the `to` address. |
### \_burn +:::info + +Any logic in the: + +- {\_beforeTokenTransfer} function will run before updating the balances and ownership of `tokenId`s. + +- {\_afterTokenTransfer} function will run after updating the balances and ownership of `tokenId`s, **but before notifying the sender via LSP1**. + +::: + :::tip Hint In dApps, you can know which addresses are burning tokens by listening for the `Transfer` event and filter with the zero address as `to`. @@ -830,7 +870,6 @@ This will also clear all the operators allowed to transfer the `tokenId`. The owner of the `tokenId` will be notified about the `tokenId` being transferred through its LSP1 [`universalReceiver`](#universalreceiver) function, if it is a contract that supports the LSP1 interface. Its [`universalReceiver`](#universalreceiver) function will receive all the parameters in the calldata packed encoded. -Any logic in the [`_beforeTokenTransfer`](#_beforetokentransfer) function will run before burning `tokenId` and updating the balances.
@@ -851,6 +890,16 @@ Any logic in the [`_beforeTokenTransfer`](#_beforetokentransfer) function will r ### \_transfer +:::info + +Any logic in the: + +- {\_beforeTokenTransfer} function will run before updating the balances and ownership of `tokenId`s. + +- {\_afterTokenTransfer} function will run after updating the balances and ownership of `tokenId`s, **but before notifying the sender/recipient via LSP1**. + +::: + :::danger This internal function does not check if the sender is authorized or not to operate on the `tokenId`. @@ -862,7 +911,7 @@ function _transfer( address from, address to, bytes32 tokenId, - bool allowNonLSP1Recipient, + bool force, bytes data ) internal nonpayable; ``` @@ -871,7 +920,6 @@ Change the owner of the `tokenId` from `from` to `to`. Both the sender and recipient will be notified of the `tokenId` being transferred through their LSP1 [`universalReceiver`](#universalreceiver) function, if they are contracts that support the LSP1 interface. Their `universalReceiver` function will receive all the parameters in the calldata packed encoded. -Any logic in the [`_beforeTokenTransfer`](#_beforetokentransfer) function will run before changing the owner of `tokenId`.
@@ -883,13 +931,13 @@ Any logic in the [`_beforeTokenTransfer`](#_beforetokentransfer) function will r #### Parameters -| Name | Type | Description | -| ----------------------- | :-------: | -------------------------------------------------------------------------------------------------------------------------- | -| `from` | `address` | The sender address. | -| `to` | `address` | @param tokenId The token to transfer. | -| `tokenId` | `bytes32` | The token to transfer. | -| `allowNonLSP1Recipient` | `bool` | When set to `true`, `to` may be any address. When set to `false`, `to` must be a contract that supports the LSP1 standard. | -| `data` | `bytes` | Additional data the caller wants included in the emitted event, and sent in the hooks to `from` and `to` addresses. | +| Name | Type | Description | +| --------- | :-------: | -------------------------------------------------------------------------------------------------------------------------- | +| `from` | `address` | The sender address. | +| `to` | `address` | @param tokenId The token to transfer. | +| `tokenId` | `bytes32` | The token to transfer. | +| `force` | `bool` | When set to `true`, `to` may be any address. When set to `false`, `to` must be a contract that supports the LSP1 standard. | +| `data` | `bytes` | Additional data the caller wants included in the emitted event, and sent in the hooks to `from` and `to` addresses. |
@@ -899,13 +947,38 @@ Any logic in the [`_beforeTokenTransfer`](#_beforetokentransfer) function will r function _beforeTokenTransfer( address from, address to, - bytes32 tokenId + bytes32 tokenId, + bytes data ) internal nonpayable; ``` Hook that is called before any token transfer, including minting and burning. +Allows to run custom logic before updating balances and notifiying sender/recipient by overriding this function. + +#### Parameters + +| Name | Type | Description | +| --------- | :-------: | -------------------------------------- | +| `from` | `address` | The sender address | +| `to` | `address` | @param tokenId The tokenId to transfer | +| `tokenId` | `bytes32` | The tokenId to transfer | +| `data` | `bytes` | The data sent alongside the transfer | -- Allows to run custom logic before updating balances and notifiying sender/recipient by overriding this function. +
+ +### \_afterTokenTransfer + +```solidity +function _afterTokenTransfer( + address from, + address to, + bytes32 tokenId, + bytes data +) internal nonpayable; +``` + +Hook that is called after any token transfer, including minting and burning. +Allows to run custom logic after updating balances, but **before notifiying sender/recipient via LSP1** by overriding this function. #### Parameters @@ -914,6 +987,7 @@ Hook that is called before any token transfer, including minting and burning. | `from` | `address` | The sender address | | `to` | `address` | @param tokenId The tokenId to transfer | | `tokenId` | `bytes32` | The tokenId to transfer | +| `data` | `bytes` | The data sent alongside the transfer |
@@ -955,13 +1029,13 @@ LSP1 interface. ```solidity function _notifyTokenReceiver( address to, - bool allowNonLSP1Recipient, + bool force, bytes lsp1Data ) internal nonpayable; ``` An attempt is made to notify the token receiver about the `tokenId` changing owners -using LSP1 interface. When allowNonLSP1Recipient is FALSE the token receiver MUST support LSP1. +using LSP1 interface. When force is FALSE the token receiver MUST support LSP1. The receiver may revert when the token being sent is not wanted.
@@ -998,6 +1072,13 @@ Returns the extension address stored under the following data key: ### \_fallbackLSP17Extendable +:::info + +The LSP8 Token contract should not hold any native tokens. Any native tokens received by the contract +will be forwarded to the extension address mapped to the selector from `msg.sig`. + +::: + ```solidity function _fallbackLSP17Extendable( bytes callData @@ -1011,9 +1092,6 @@ Reverts if there is no extension for the function being called. If there is an extension for the function selector being called, it calls the extension with the CALL opcode, passing the [`msg.data`](#msg.data) appended with the 20 bytes of the [`msg.sender`](#msg.sender) and 32 bytes of the [`msg.value`](#msg.value) -Because the function uses assembly [`return()/revert()`](#return) to terminate the call, it cannot be -called before other codes in fallback(). -Otherwise, the codes after \_fallbackLSP17Extendable() may never be reached.
@@ -1139,7 +1217,7 @@ Emitted when `tokenOwner` disables `operator` to transfer or burn `tokenId` on i ::: ```solidity -event Transfer(address operator, address indexed from, address indexed to, bytes32 indexed tokenId, bool allowNonLSP1Recipient, bytes data); +event Transfer(address operator, address indexed from, address indexed to, bytes32 indexed tokenId, bool force, bytes data); ``` Emitted when `tokenId` token is transferred from the `from` to the `to` address. @@ -1152,7 +1230,7 @@ Emitted when `tokenId` token is transferred from the `from` to the `to` address. | `from` **`indexed`** | `address` | The previous owner of the `tokenId` | | `to` **`indexed`** | `address` | The new owner of `tokenId` | | `tokenId` **`indexed`** | `bytes32` | The tokenId that was transferred | -| `allowNonLSP1Recipient` | `bool` | If the token transfer enforces the `to` recipient address to be a contract that implements the LSP1 standard or not. | +| `force` | `bool` | If the token transfer enforces the `to` recipient address to be a contract that implements the LSP1 standard or not. | | `data` | `bytes` | Any additional data the caller included by the caller during the transfer, and sent in the hooks to the `from` and `to` addresses. |
@@ -1319,7 +1397,7 @@ Reverts when trying to edit the data key `LSP4TokenSymbol` after the digital ass error LSP8CannotSendToAddressZero(); ``` -reverts when trying to send token to the zero address. +Reverts when trying to send token to the zero address.
@@ -1338,7 +1416,7 @@ reverts when trying to send token to the zero address. error LSP8CannotSendToSelf(); ``` -reverts when specifying the same address for `from` and `to` in a token transfer. +Reverts when specifying the same address for `from` and `to` in a token transfer.
@@ -1357,7 +1435,7 @@ reverts when specifying the same address for `from` and `to` in a token transfer error LSP8CannotUseAddressZeroAsOperator(); ``` -reverts when trying to set the zero address as an operator. +Reverts when trying to set the zero address as an operator.
@@ -1376,7 +1454,7 @@ reverts when trying to set the zero address as an operator. error LSP8InvalidTransferBatch(); ``` -reverts when the parameters used for `transferBatch` have different lengths. +Reverts when the parameters used for `transferBatch` have different lengths.
@@ -1395,7 +1473,7 @@ reverts when the parameters used for `transferBatch` have different lengths. error LSP8NonExistentTokenId(bytes32 tokenId); ``` -reverts when `tokenId` has not been minted. +Reverts when `tokenId` has not been minted. #### Parameters @@ -1420,7 +1498,7 @@ reverts when `tokenId` has not been minted. error LSP8NonExistingOperator(address operator, bytes32 tokenId); ``` -reverts when `operator` is not an operator for the `tokenId`. +Reverts when `operator` is not an operator for the `tokenId`. #### Parameters @@ -1446,7 +1524,7 @@ reverts when `operator` is not an operator for the `tokenId`. error LSP8NotTokenOperator(bytes32 tokenId, address caller); ``` -reverts when `caller` is not an allowed operator for `tokenId`. +Reverts when `caller` is not an allowed operator for `tokenId`. #### Parameters @@ -1472,7 +1550,7 @@ reverts when `caller` is not an allowed operator for `tokenId`. error LSP8NotTokenOwner(address tokenOwner, bytes32 tokenId, address caller); ``` -reverts when `caller` is not the `tokenOwner` of the `tokenId`. +Reverts when `caller` is not the `tokenOwner` of the `tokenId`. #### Parameters @@ -1501,7 +1579,7 @@ error LSP8NotifyTokenReceiverContractMissingLSP1Interface( ); ``` -reverts if the `tokenReceiver` does not implement LSP1 when minting or transferring tokens with `bool allowNonLSP1Recipient` set as `false`. +Reverts if the `tokenReceiver` does not implement LSP1 when minting or transferring tokens with `bool force` set as `false`. #### Parameters @@ -1526,7 +1604,7 @@ reverts if the `tokenReceiver` does not implement LSP1 when minting or transferr error LSP8NotifyTokenReceiverIsEOA(address tokenReceiver); ``` -reverts if the `tokenReceiver` is an EOA when minting or transferring tokens with `bool allowNonLSP1Recipient` set as `false`. +Reverts if the `tokenReceiver` is an EOA when minting or transferring tokens with `bool force` set as `false`. #### Parameters @@ -1551,7 +1629,7 @@ reverts if the `tokenReceiver` is an EOA when minting or transferring tokens wit error LSP8OperatorAlreadyAuthorized(address operator, bytes32 tokenId); ``` -reverts when `operator` is already authorized for the `tokenId`. +Reverts when `operator` is already authorized for the `tokenId`. #### Parameters @@ -1562,6 +1640,46 @@ reverts when `operator` is already authorized for the `tokenId`.
+### LSP8TokenContractCannotHoldValue + +:::note References + +- Specification details: [**LSP-8-IdentifiableDigitalAsset**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-8-IdentifiableDigitalAsset.md#lsp8tokencontractcannotholdvalue) +- Solidity implementation: [`LSP8IdentifiableDigitalAsset.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP8IdentifiableDigitalAsset/LSP8IdentifiableDigitalAsset.sol) +- Error signature: `LSP8TokenContractCannotHoldValue()` +- Error hash: `0x61f49442` + +::: + +```solidity +error LSP8TokenContractCannotHoldValue(); +``` + +_LSP8 contract cannot receive native tokens._ + +Error occurs when sending native tokens to the LSP8 contract without sending any data. E.g. Sending value without passing a bytes4 function selector to call a LSP17 Extension. + +
+ +### LSP8TokenIdTypeNotEditable + +:::note References + +- Specification details: [**LSP-8-IdentifiableDigitalAsset**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-8-IdentifiableDigitalAsset.md#lsp8tokenidtypenoteditable) +- Solidity implementation: [`LSP8IdentifiableDigitalAsset.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP8IdentifiableDigitalAsset/LSP8IdentifiableDigitalAsset.sol) +- Error signature: `LSP8TokenIdTypeNotEditable()` +- Error hash: `0x53bc1122` + +::: + +```solidity +error LSP8TokenIdTypeNotEditable(); +``` + +Reverts when trying to edit the data key `LSP8TokenIdType` after the identifiable digital asset contract has been deployed. The `LSP8TokenIdType` data key is located inside the ERC725Y Data key-value store of the identifiable digital asset contract. It can be set only once inside the constructor/initializer when the identifiable digital asset contract is being deployed. + +
+ ### LSP8TokenOwnerCannotBeOperator :::note References @@ -1577,7 +1695,7 @@ reverts when `operator` is already authorized for the `tokenId`. error LSP8TokenOwnerCannotBeOperator(); ``` -reverts when trying to authorize or revoke the token's owner as an operator. +Reverts when trying to authorize or revoke the token's owner as an operator.
@@ -1605,3 +1723,47 @@ reverts when there is no extension for the function selector being called with | `functionSelector` | `bytes4` | - |
+ +### OwnableCallerNotTheOwner + +:::note References + +- Specification details: [**LSP-8-IdentifiableDigitalAsset**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-8-IdentifiableDigitalAsset.md#ownablecallernottheowner) +- Solidity implementation: [`LSP8IdentifiableDigitalAsset.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP8IdentifiableDigitalAsset/LSP8IdentifiableDigitalAsset.sol) +- Error signature: `OwnableCallerNotTheOwner(address)` +- Error hash: `0xbf1169c5` + +::: + +```solidity +error OwnableCallerNotTheOwner(address callerAddress); +``` + +Reverts when only the owner is allowed to call the function. + +#### Parameters + +| Name | Type | Description | +| --------------- | :-------: | ---------------------------------------- | +| `callerAddress` | `address` | The address that tried to make the call. | + +
+ +### OwnableCannotSetZeroAddressAsOwner + +:::note References + +- Specification details: [**LSP-8-IdentifiableDigitalAsset**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-8-IdentifiableDigitalAsset.md#ownablecannotsetzeroaddressasowner) +- Solidity implementation: [`LSP8IdentifiableDigitalAsset.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP8IdentifiableDigitalAsset/LSP8IdentifiableDigitalAsset.sol) +- Error signature: `OwnableCannotSetZeroAddressAsOwner()` +- Error hash: `0x1ad8836c` + +::: + +```solidity +error OwnableCannotSetZeroAddressAsOwner(); +``` + +Reverts when trying to set `address(0)` as the contract owner when deploying the contract, initializing it or transferring ownership of the contract. + +
diff --git a/docs/contracts/LSP8IdentifiableDigitalAsset/extensions/LSP8Burnable.md b/docs/contracts/LSP8IdentifiableDigitalAsset/extensions/LSP8Burnable.md index 3b57aa369..181bbd5b7 100644 --- a/docs/contracts/LSP8IdentifiableDigitalAsset/extensions/LSP8Burnable.md +++ b/docs/contracts/LSP8IdentifiableDigitalAsset/extensions/LSP8Burnable.md @@ -56,6 +56,25 @@ This function is executed when:
+### receive + +:::note References + +- Specification details: [**LSP-8-IdentifiableDigitalAsset**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-8-IdentifiableDigitalAsset.md#receive) +- Solidity implementation: [`LSP8Burnable.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP8IdentifiableDigitalAsset/extensions/LSP8Burnable.sol) + +::: + +```solidity +receive() external payable; +``` + +_LSP8 contract cannot receive native tokens._ + +Reverts whenever someone tries to send native tokens to a LSP8 contract. + +
+ ### authorizeOperator :::note References @@ -589,22 +608,22 @@ function transfer( address from, address to, bytes32 tokenId, - bool allowNonLSP1Recipient, + bool force, bytes data ) external nonpayable; ``` -Transfer a given `tokenId` token from the `from` address to the `to` address. If operators are set for a specific `tokenId`, all the operators are revoked after the tokenId have been transferred. The `allowNonLSP1Recipient` parameter MUST be set to `true` when transferring tokens to Externally Owned Accounts (EOAs) or contracts that do not implement the LSP1 standard. +Transfer a given `tokenId` token from the `from` address to the `to` address. If operators are set for a specific `tokenId`, all the operators are revoked after the tokenId have been transferred. The `force` parameter MUST be set to `true` when transferring tokens to Externally Owned Accounts (EOAs) or contracts that do not implement the LSP1 standard. #### Parameters -| Name | Type | Description | -| ----------------------- | :-------: | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `from` | `address` | The address that owns the given `tokenId`. | -| `to` | `address` | The address that will receive the `tokenId`. | -| `tokenId` | `bytes32` | The token ID to transfer. | -| `allowNonLSP1Recipient` | `bool` | When set to `true`, the `to` address CAN be any addres. When set to `false`, the `to` address MUST be a contract that supports the LSP1 UniversalReceiver standard. | -| `data` | `bytes` | Any additional data the caller wants included in the emitted event, and sent in the hooks of the `from` and `to` addresses. | +| Name | Type | Description | +| --------- | :-------: | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `from` | `address` | The address that owns the given `tokenId`. | +| `to` | `address` | The address that will receive the `tokenId`. | +| `tokenId` | `bytes32` | The token ID to transfer. | +| `force` | `bool` | When set to `true`, the `to` address CAN be any addres. When set to `false`, the `to` address MUST be a contract that supports the LSP1 UniversalReceiver standard. | +| `data` | `bytes` | Any additional data the caller wants included in the emitted event, and sent in the hooks of the `from` and `to` addresses. |
@@ -624,7 +643,7 @@ function transferBatch( address[] from, address[] to, bytes32[] tokenId, - bool[] allowNonLSP1Recipient, + bool[] force, bytes[] data ) external nonpayable; ``` @@ -633,13 +652,13 @@ Transfers multiple tokens at once based on the arrays of `from`, `to` and `token #### Parameters -| Name | Type | Description | -| ----------------------- | :---------: | ----------------------------------------------------------------------------------------------------------------------------------------- | -| `from` | `address[]` | An array of sending addresses. | -| `to` | `address[]` | An array of recipient addresses. | -| `tokenId` | `bytes32[]` | An array of token IDs to transfer. | -| `allowNonLSP1Recipient` | `bool[]` | When set to `true`, `to` may be any address. When set to `false`, `to` must be a contract that supports the LSP1 standard and not revert. | -| `data` | `bytes[]` | Any additional data the caller wants included in the emitted event, and sent in the hooks to the `from` and `to` addresses. | +| Name | Type | Description | +| --------- | :---------: | ----------------------------------------------------------------------------------------------------------------------------------------- | +| `from` | `address[]` | An array of sending addresses. | +| `to` | `address[]` | An array of recipient addresses. | +| `tokenId` | `bytes32[]` | An array of token IDs to transfer. | +| `force` | `bool[]` | When set to `true`, `to` may be any address. When set to `false`, `to` must be a contract that supports the LSP1 standard and not revert. | +| `data` | `bytes[]` | Any additional data the caller wants included in the emitted event, and sent in the hooks to the `from` and `to` addresses. |
@@ -728,7 +747,8 @@ mapping(bytes32 => bytes) _store function _setData(bytes32 dataKey, bytes dataValue) internal nonpayable; ``` -Save gas by emitting the [`DataChanged`](#datachanged) event with only the first 256 bytes of dataValue +The ERC725Y data key `_LSP8_TOKENID_TYPE_KEY` cannot be changed +once the identifiable digital asset contract has been deployed.
@@ -809,11 +829,21 @@ When `tokenId` does not exist then revert with an error. ### \_mint +:::info + +Any logic in the: + +- {\_beforeTokenTransfer} function will run before updating the balances and ownership of `tokenId`s. + +- {\_afterTokenTransfer} function will run after updating the balances and ownership of `tokenId`s, **but before notifying the recipient via LSP1**. + +::: + ```solidity function _mint( address to, bytes32 tokenId, - bool allowNonLSP1Recipient, + bool force, bytes data ) internal nonpayable; ``` @@ -830,17 +860,27 @@ Create `tokenId` by minting it and transfers it to `to`. #### Parameters -| Name | Type | Description | -| ----------------------- | :-------: | -------------------------------------------------------------------------------------------------------------------------- | -| `to` | `address` | @param tokenId The token ID to create (= mint). | -| `tokenId` | `bytes32` | The token ID to create (= mint). | -| `allowNonLSP1Recipient` | `bool` | When set to `true`, `to` may be any address. When set to `false`, `to` must be a contract that supports the LSP1 standard. | -| `data` | `bytes` | Any additional data the caller wants included in the emitted event, and sent in the hook of the `to` address. | +| Name | Type | Description | +| --------- | :-------: | -------------------------------------------------------------------------------------------------------------------------- | +| `to` | `address` | @param tokenId The token ID to create (= mint). | +| `tokenId` | `bytes32` | The token ID to create (= mint). | +| `force` | `bool` | When set to `true`, `to` may be any address. When set to `false`, `to` must be a contract that supports the LSP1 standard. | +| `data` | `bytes` | Any additional data the caller wants included in the emitted event, and sent in the hook of the `to` address. |
### \_burn +:::info + +Any logic in the: + +- {\_beforeTokenTransfer} function will run before updating the balances and ownership of `tokenId`s. + +- {\_afterTokenTransfer} function will run after updating the balances and ownership of `tokenId`s, **but before notifying the sender via LSP1**. + +::: + :::tip Hint In dApps, you can know which addresses are burning tokens by listening for the `Transfer` event and filter with the zero address as `to`. @@ -856,7 +896,6 @@ This will also clear all the operators allowed to transfer the `tokenId`. The owner of the `tokenId` will be notified about the `tokenId` being transferred through its LSP1 [`universalReceiver`](#universalreceiver) function, if it is a contract that supports the LSP1 interface. Its [`universalReceiver`](#universalreceiver) function will receive all the parameters in the calldata packed encoded. -Any logic in the [`_beforeTokenTransfer`](#_beforetokentransfer) function will run before burning `tokenId` and updating the balances.
@@ -877,6 +916,16 @@ Any logic in the [`_beforeTokenTransfer`](#_beforetokentransfer) function will r ### \_transfer +:::info + +Any logic in the: + +- {\_beforeTokenTransfer} function will run before updating the balances and ownership of `tokenId`s. + +- {\_afterTokenTransfer} function will run after updating the balances and ownership of `tokenId`s, **but before notifying the sender/recipient via LSP1**. + +::: + :::danger This internal function does not check if the sender is authorized or not to operate on the `tokenId`. @@ -888,7 +937,7 @@ function _transfer( address from, address to, bytes32 tokenId, - bool allowNonLSP1Recipient, + bool force, bytes data ) internal nonpayable; ``` @@ -897,7 +946,6 @@ Change the owner of the `tokenId` from `from` to `to`. Both the sender and recipient will be notified of the `tokenId` being transferred through their LSP1 [`universalReceiver`](#universalreceiver) function, if they are contracts that support the LSP1 interface. Their `universalReceiver` function will receive all the parameters in the calldata packed encoded. -Any logic in the [`_beforeTokenTransfer`](#_beforetokentransfer) function will run before changing the owner of `tokenId`.
@@ -909,13 +957,13 @@ Any logic in the [`_beforeTokenTransfer`](#_beforetokentransfer) function will r #### Parameters -| Name | Type | Description | -| ----------------------- | :-------: | -------------------------------------------------------------------------------------------------------------------------- | -| `from` | `address` | The sender address. | -| `to` | `address` | @param tokenId The token to transfer. | -| `tokenId` | `bytes32` | The token to transfer. | -| `allowNonLSP1Recipient` | `bool` | When set to `true`, `to` may be any address. When set to `false`, `to` must be a contract that supports the LSP1 standard. | -| `data` | `bytes` | Additional data the caller wants included in the emitted event, and sent in the hooks to `from` and `to` addresses. | +| Name | Type | Description | +| --------- | :-------: | -------------------------------------------------------------------------------------------------------------------------- | +| `from` | `address` | The sender address. | +| `to` | `address` | @param tokenId The token to transfer. | +| `tokenId` | `bytes32` | The token to transfer. | +| `force` | `bool` | When set to `true`, `to` may be any address. When set to `false`, `to` must be a contract that supports the LSP1 standard. | +| `data` | `bytes` | Additional data the caller wants included in the emitted event, and sent in the hooks to `from` and `to` addresses. |
@@ -925,13 +973,38 @@ Any logic in the [`_beforeTokenTransfer`](#_beforetokentransfer) function will r function _beforeTokenTransfer( address from, address to, - bytes32 tokenId + bytes32 tokenId, + bytes data ) internal nonpayable; ``` Hook that is called before any token transfer, including minting and burning. +Allows to run custom logic before updating balances and notifiying sender/recipient by overriding this function. + +#### Parameters + +| Name | Type | Description | +| --------- | :-------: | -------------------------------------- | +| `from` | `address` | The sender address | +| `to` | `address` | @param tokenId The tokenId to transfer | +| `tokenId` | `bytes32` | The tokenId to transfer | +| `data` | `bytes` | The data sent alongside the transfer | -- Allows to run custom logic before updating balances and notifiying sender/recipient by overriding this function. +
+ +### \_afterTokenTransfer + +```solidity +function _afterTokenTransfer( + address from, + address to, + bytes32 tokenId, + bytes data +) internal nonpayable; +``` + +Hook that is called after any token transfer, including minting and burning. +Allows to run custom logic after updating balances, but **before notifiying sender/recipient via LSP1** by overriding this function. #### Parameters @@ -940,6 +1013,7 @@ Hook that is called before any token transfer, including minting and burning. | `from` | `address` | The sender address | | `to` | `address` | @param tokenId The tokenId to transfer | | `tokenId` | `bytes32` | The tokenId to transfer | +| `data` | `bytes` | The data sent alongside the transfer |
@@ -981,13 +1055,13 @@ LSP1 interface. ```solidity function _notifyTokenReceiver( address to, - bool allowNonLSP1Recipient, + bool force, bytes lsp1Data ) internal nonpayable; ``` An attempt is made to notify the token receiver about the `tokenId` changing owners -using LSP1 interface. When allowNonLSP1Recipient is FALSE the token receiver MUST support LSP1. +using LSP1 interface. When force is FALSE the token receiver MUST support LSP1. The receiver may revert when the token being sent is not wanted.
@@ -1024,6 +1098,13 @@ Returns the extension address stored under the following data key: ### \_fallbackLSP17Extendable +:::info + +The LSP8 Token contract should not hold any native tokens. Any native tokens received by the contract +will be forwarded to the extension address mapped to the selector from `msg.sig`. + +::: + ```solidity function _fallbackLSP17Extendable( bytes callData @@ -1037,9 +1118,6 @@ Reverts if there is no extension for the function being called. If there is an extension for the function selector being called, it calls the extension with the CALL opcode, passing the [`msg.data`](#msg.data) appended with the 20 bytes of the [`msg.sender`](#msg.sender) and 32 bytes of the [`msg.value`](#msg.value) -Because the function uses assembly [`return()/revert()`](#return) to terminate the call, it cannot be -called before other codes in fallback(). -Otherwise, the codes after \_fallbackLSP17Extendable() may never be reached.
@@ -1165,7 +1243,7 @@ Emitted when `tokenOwner` disables `operator` to transfer or burn `tokenId` on i ::: ```solidity -event Transfer(address operator, address indexed from, address indexed to, bytes32 indexed tokenId, bool allowNonLSP1Recipient, bytes data); +event Transfer(address operator, address indexed from, address indexed to, bytes32 indexed tokenId, bool force, bytes data); ``` Emitted when `tokenId` token is transferred from the `from` to the `to` address. @@ -1178,7 +1256,7 @@ Emitted when `tokenId` token is transferred from the `from` to the `to` address. | `from` **`indexed`** | `address` | The previous owner of the `tokenId` | | `to` **`indexed`** | `address` | The new owner of `tokenId` | | `tokenId` **`indexed`** | `bytes32` | The tokenId that was transferred | -| `allowNonLSP1Recipient` | `bool` | If the token transfer enforces the `to` recipient address to be a contract that implements the LSP1 standard or not. | +| `force` | `bool` | If the token transfer enforces the `to` recipient address to be a contract that implements the LSP1 standard or not. | | `data` | `bytes` | Any additional data the caller included by the caller during the transfer, and sent in the hooks to the `from` and `to` addresses. |
@@ -1345,7 +1423,7 @@ Reverts when trying to edit the data key `LSP4TokenSymbol` after the digital ass error LSP8CannotSendToAddressZero(); ``` -reverts when trying to send token to the zero address. +Reverts when trying to send token to the zero address.
@@ -1364,7 +1442,7 @@ reverts when trying to send token to the zero address. error LSP8CannotSendToSelf(); ``` -reverts when specifying the same address for `from` and `to` in a token transfer. +Reverts when specifying the same address for `from` and `to` in a token transfer.
@@ -1383,7 +1461,7 @@ reverts when specifying the same address for `from` and `to` in a token transfer error LSP8CannotUseAddressZeroAsOperator(); ``` -reverts when trying to set the zero address as an operator. +Reverts when trying to set the zero address as an operator.
@@ -1402,7 +1480,7 @@ reverts when trying to set the zero address as an operator. error LSP8InvalidTransferBatch(); ``` -reverts when the parameters used for `transferBatch` have different lengths. +Reverts when the parameters used for `transferBatch` have different lengths.
@@ -1421,7 +1499,7 @@ reverts when the parameters used for `transferBatch` have different lengths. error LSP8NonExistentTokenId(bytes32 tokenId); ``` -reverts when `tokenId` has not been minted. +Reverts when `tokenId` has not been minted. #### Parameters @@ -1446,7 +1524,7 @@ reverts when `tokenId` has not been minted. error LSP8NonExistingOperator(address operator, bytes32 tokenId); ``` -reverts when `operator` is not an operator for the `tokenId`. +Reverts when `operator` is not an operator for the `tokenId`. #### Parameters @@ -1472,7 +1550,7 @@ reverts when `operator` is not an operator for the `tokenId`. error LSP8NotTokenOperator(bytes32 tokenId, address caller); ``` -reverts when `caller` is not an allowed operator for `tokenId`. +Reverts when `caller` is not an allowed operator for `tokenId`. #### Parameters @@ -1498,7 +1576,7 @@ reverts when `caller` is not an allowed operator for `tokenId`. error LSP8NotTokenOwner(address tokenOwner, bytes32 tokenId, address caller); ``` -reverts when `caller` is not the `tokenOwner` of the `tokenId`. +Reverts when `caller` is not the `tokenOwner` of the `tokenId`. #### Parameters @@ -1527,7 +1605,7 @@ error LSP8NotifyTokenReceiverContractMissingLSP1Interface( ); ``` -reverts if the `tokenReceiver` does not implement LSP1 when minting or transferring tokens with `bool allowNonLSP1Recipient` set as `false`. +Reverts if the `tokenReceiver` does not implement LSP1 when minting or transferring tokens with `bool force` set as `false`. #### Parameters @@ -1552,7 +1630,7 @@ reverts if the `tokenReceiver` does not implement LSP1 when minting or transferr error LSP8NotifyTokenReceiverIsEOA(address tokenReceiver); ``` -reverts if the `tokenReceiver` is an EOA when minting or transferring tokens with `bool allowNonLSP1Recipient` set as `false`. +Reverts if the `tokenReceiver` is an EOA when minting or transferring tokens with `bool force` set as `false`. #### Parameters @@ -1577,7 +1655,7 @@ reverts if the `tokenReceiver` is an EOA when minting or transferring tokens wit error LSP8OperatorAlreadyAuthorized(address operator, bytes32 tokenId); ``` -reverts when `operator` is already authorized for the `tokenId`. +Reverts when `operator` is already authorized for the `tokenId`. #### Parameters @@ -1588,6 +1666,46 @@ reverts when `operator` is already authorized for the `tokenId`.
+### LSP8TokenContractCannotHoldValue + +:::note References + +- Specification details: [**LSP-8-IdentifiableDigitalAsset**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-8-IdentifiableDigitalAsset.md#lsp8tokencontractcannotholdvalue) +- Solidity implementation: [`LSP8Burnable.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP8IdentifiableDigitalAsset/extensions/LSP8Burnable.sol) +- Error signature: `LSP8TokenContractCannotHoldValue()` +- Error hash: `0x61f49442` + +::: + +```solidity +error LSP8TokenContractCannotHoldValue(); +``` + +_LSP8 contract cannot receive native tokens._ + +Error occurs when sending native tokens to the LSP8 contract without sending any data. E.g. Sending value without passing a bytes4 function selector to call a LSP17 Extension. + +
+ +### LSP8TokenIdTypeNotEditable + +:::note References + +- Specification details: [**LSP-8-IdentifiableDigitalAsset**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-8-IdentifiableDigitalAsset.md#lsp8tokenidtypenoteditable) +- Solidity implementation: [`LSP8Burnable.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP8IdentifiableDigitalAsset/extensions/LSP8Burnable.sol) +- Error signature: `LSP8TokenIdTypeNotEditable()` +- Error hash: `0x53bc1122` + +::: + +```solidity +error LSP8TokenIdTypeNotEditable(); +``` + +Reverts when trying to edit the data key `LSP8TokenIdType` after the identifiable digital asset contract has been deployed. The `LSP8TokenIdType` data key is located inside the ERC725Y Data key-value store of the identifiable digital asset contract. It can be set only once inside the constructor/initializer when the identifiable digital asset contract is being deployed. + +
+ ### LSP8TokenOwnerCannotBeOperator :::note References @@ -1603,7 +1721,7 @@ reverts when `operator` is already authorized for the `tokenId`. error LSP8TokenOwnerCannotBeOperator(); ``` -reverts when trying to authorize or revoke the token's owner as an operator. +Reverts when trying to authorize or revoke the token's owner as an operator.
@@ -1631,3 +1749,47 @@ reverts when there is no extension for the function selector being called with | `functionSelector` | `bytes4` | - |
+ +### OwnableCallerNotTheOwner + +:::note References + +- Specification details: [**LSP-8-IdentifiableDigitalAsset**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-8-IdentifiableDigitalAsset.md#ownablecallernottheowner) +- Solidity implementation: [`LSP8Burnable.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP8IdentifiableDigitalAsset/extensions/LSP8Burnable.sol) +- Error signature: `OwnableCallerNotTheOwner(address)` +- Error hash: `0xbf1169c5` + +::: + +```solidity +error OwnableCallerNotTheOwner(address callerAddress); +``` + +Reverts when only the owner is allowed to call the function. + +#### Parameters + +| Name | Type | Description | +| --------------- | :-------: | ---------------------------------------- | +| `callerAddress` | `address` | The address that tried to make the call. | + +
+ +### OwnableCannotSetZeroAddressAsOwner + +:::note References + +- Specification details: [**LSP-8-IdentifiableDigitalAsset**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-8-IdentifiableDigitalAsset.md#ownablecannotsetzeroaddressasowner) +- Solidity implementation: [`LSP8Burnable.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP8IdentifiableDigitalAsset/extensions/LSP8Burnable.sol) +- Error signature: `OwnableCannotSetZeroAddressAsOwner()` +- Error hash: `0x1ad8836c` + +::: + +```solidity +error OwnableCannotSetZeroAddressAsOwner(); +``` + +Reverts when trying to set `address(0)` as the contract owner when deploying the contract, initializing it or transferring ownership of the contract. + +
diff --git a/docs/contracts/LSP8IdentifiableDigitalAsset/extensions/LSP8CappedSupply.md b/docs/contracts/LSP8IdentifiableDigitalAsset/extensions/LSP8CappedSupply.md index a69370888..53a5b5f12 100644 --- a/docs/contracts/LSP8IdentifiableDigitalAsset/extensions/LSP8CappedSupply.md +++ b/docs/contracts/LSP8IdentifiableDigitalAsset/extensions/LSP8CappedSupply.md @@ -56,6 +56,25 @@ This function is executed when:
+### receive + +:::note References + +- Specification details: [**LSP-8-IdentifiableDigitalAsset**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-8-IdentifiableDigitalAsset.md#receive) +- Solidity implementation: [`LSP8CappedSupply.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP8IdentifiableDigitalAsset/extensions/LSP8CappedSupply.sol) + +::: + +```solidity +receive() external payable; +``` + +_LSP8 contract cannot receive native tokens._ + +Reverts whenever someone tries to send native tokens to a LSP8 contract. + +
+ ### authorizeOperator :::note References @@ -535,7 +554,7 @@ Returns the list of `tokenIds` for the `tokenOwner` address. function tokenSupplyCap() external view returns (uint256); ``` -_The maximum supply amount of tokens allowed to exist is `_tokenSupplyCap`._ +_The maximum supply amount of tokens allowed to exist is `_TOKEN_SUPPLY_CAP`._ Get the maximum number of tokens that can exist to circulate. Once [`totalSupply`](#totalsupply) reaches reaches [`totalSuuplyCap`](#totalsuuplycap), it is not possible to mint more tokens. @@ -588,22 +607,22 @@ function transfer( address from, address to, bytes32 tokenId, - bool allowNonLSP1Recipient, + bool force, bytes data ) external nonpayable; ``` -Transfer a given `tokenId` token from the `from` address to the `to` address. If operators are set for a specific `tokenId`, all the operators are revoked after the tokenId have been transferred. The `allowNonLSP1Recipient` parameter MUST be set to `true` when transferring tokens to Externally Owned Accounts (EOAs) or contracts that do not implement the LSP1 standard. +Transfer a given `tokenId` token from the `from` address to the `to` address. If operators are set for a specific `tokenId`, all the operators are revoked after the tokenId have been transferred. The `force` parameter MUST be set to `true` when transferring tokens to Externally Owned Accounts (EOAs) or contracts that do not implement the LSP1 standard. #### Parameters -| Name | Type | Description | -| ----------------------- | :-------: | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `from` | `address` | The address that owns the given `tokenId`. | -| `to` | `address` | The address that will receive the `tokenId`. | -| `tokenId` | `bytes32` | The token ID to transfer. | -| `allowNonLSP1Recipient` | `bool` | When set to `true`, the `to` address CAN be any addres. When set to `false`, the `to` address MUST be a contract that supports the LSP1 UniversalReceiver standard. | -| `data` | `bytes` | Any additional data the caller wants included in the emitted event, and sent in the hooks of the `from` and `to` addresses. | +| Name | Type | Description | +| --------- | :-------: | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `from` | `address` | The address that owns the given `tokenId`. | +| `to` | `address` | The address that will receive the `tokenId`. | +| `tokenId` | `bytes32` | The token ID to transfer. | +| `force` | `bool` | When set to `true`, the `to` address CAN be any addres. When set to `false`, the `to` address MUST be a contract that supports the LSP1 UniversalReceiver standard. | +| `data` | `bytes` | Any additional data the caller wants included in the emitted event, and sent in the hooks of the `from` and `to` addresses. |
@@ -623,7 +642,7 @@ function transferBatch( address[] from, address[] to, bytes32[] tokenId, - bool[] allowNonLSP1Recipient, + bool[] force, bytes[] data ) external nonpayable; ``` @@ -632,13 +651,13 @@ Transfers multiple tokens at once based on the arrays of `from`, `to` and `token #### Parameters -| Name | Type | Description | -| ----------------------- | :---------: | ----------------------------------------------------------------------------------------------------------------------------------------- | -| `from` | `address[]` | An array of sending addresses. | -| `to` | `address[]` | An array of recipient addresses. | -| `tokenId` | `bytes32[]` | An array of token IDs to transfer. | -| `allowNonLSP1Recipient` | `bool[]` | When set to `true`, `to` may be any address. When set to `false`, `to` must be a contract that supports the LSP1 standard and not revert. | -| `data` | `bytes[]` | Any additional data the caller wants included in the emitted event, and sent in the hooks to the `from` and `to` addresses. | +| Name | Type | Description | +| --------- | :---------: | ----------------------------------------------------------------------------------------------------------------------------------------- | +| `from` | `address[]` | An array of sending addresses. | +| `to` | `address[]` | An array of recipient addresses. | +| `tokenId` | `bytes32[]` | An array of token IDs to transfer. | +| `force` | `bool[]` | When set to `true`, `to` may be any address. When set to `false`, `to` must be a contract that supports the LSP1 standard and not revert. | +| `data` | `bytes[]` | Any additional data the caller wants included in the emitted event, and sent in the hooks to the `from` and `to` addresses. |
@@ -727,7 +746,8 @@ mapping(bytes32 => bytes) _store function _setData(bytes32 dataKey, bytes dataValue) internal nonpayable; ``` -Save gas by emitting the [`DataChanged`](#datachanged) event with only the first 256 bytes of dataValue +The ERC725Y data key `_LSP8_TOKENID_TYPE_KEY` cannot be changed +once the identifiable digital asset contract has been deployed.
@@ -812,7 +832,7 @@ When `tokenId` does not exist then revert with an error. function _mint( address to, bytes32 tokenId, - bool allowNonLSP1Recipient, + bool force, bytes data ) internal nonpayable; ``` @@ -825,6 +845,16 @@ after a new `tokenId` has of tokens have been minted. ### \_burn +:::info + +Any logic in the: + +- {\_beforeTokenTransfer} function will run before updating the balances and ownership of `tokenId`s. + +- {\_afterTokenTransfer} function will run after updating the balances and ownership of `tokenId`s, **but before notifying the sender via LSP1**. + +::: + :::tip Hint In dApps, you can know which addresses are burning tokens by listening for the `Transfer` event and filter with the zero address as `to`. @@ -840,7 +870,6 @@ This will also clear all the operators allowed to transfer the `tokenId`. The owner of the `tokenId` will be notified about the `tokenId` being transferred through its LSP1 [`universalReceiver`](#universalreceiver) function, if it is a contract that supports the LSP1 interface. Its [`universalReceiver`](#universalreceiver) function will receive all the parameters in the calldata packed encoded. -Any logic in the [`_beforeTokenTransfer`](#_beforetokentransfer) function will run before burning `tokenId` and updating the balances.
@@ -861,6 +890,16 @@ Any logic in the [`_beforeTokenTransfer`](#_beforetokentransfer) function will r ### \_transfer +:::info + +Any logic in the: + +- {\_beforeTokenTransfer} function will run before updating the balances and ownership of `tokenId`s. + +- {\_afterTokenTransfer} function will run after updating the balances and ownership of `tokenId`s, **but before notifying the sender/recipient via LSP1**. + +::: + :::danger This internal function does not check if the sender is authorized or not to operate on the `tokenId`. @@ -872,7 +911,7 @@ function _transfer( address from, address to, bytes32 tokenId, - bool allowNonLSP1Recipient, + bool force, bytes data ) internal nonpayable; ``` @@ -881,7 +920,6 @@ Change the owner of the `tokenId` from `from` to `to`. Both the sender and recipient will be notified of the `tokenId` being transferred through their LSP1 [`universalReceiver`](#universalreceiver) function, if they are contracts that support the LSP1 interface. Their `universalReceiver` function will receive all the parameters in the calldata packed encoded. -Any logic in the [`_beforeTokenTransfer`](#_beforetokentransfer) function will run before changing the owner of `tokenId`.
@@ -893,13 +931,13 @@ Any logic in the [`_beforeTokenTransfer`](#_beforetokentransfer) function will r #### Parameters -| Name | Type | Description | -| ----------------------- | :-------: | -------------------------------------------------------------------------------------------------------------------------- | -| `from` | `address` | The sender address. | -| `to` | `address` | @param tokenId The token to transfer. | -| `tokenId` | `bytes32` | The token to transfer. | -| `allowNonLSP1Recipient` | `bool` | When set to `true`, `to` may be any address. When set to `false`, `to` must be a contract that supports the LSP1 standard. | -| `data` | `bytes` | Additional data the caller wants included in the emitted event, and sent in the hooks to `from` and `to` addresses. | +| Name | Type | Description | +| --------- | :-------: | -------------------------------------------------------------------------------------------------------------------------- | +| `from` | `address` | The sender address. | +| `to` | `address` | @param tokenId The token to transfer. | +| `tokenId` | `bytes32` | The token to transfer. | +| `force` | `bool` | When set to `true`, `to` may be any address. When set to `false`, `to` must be a contract that supports the LSP1 standard. | +| `data` | `bytes` | Additional data the caller wants included in the emitted event, and sent in the hooks to `from` and `to` addresses. |
@@ -909,13 +947,38 @@ Any logic in the [`_beforeTokenTransfer`](#_beforetokentransfer) function will r function _beforeTokenTransfer( address from, address to, - bytes32 tokenId + bytes32 tokenId, + bytes data ) internal nonpayable; ``` Hook that is called before any token transfer, including minting and burning. +Allows to run custom logic before updating balances and notifiying sender/recipient by overriding this function. + +#### Parameters + +| Name | Type | Description | +| --------- | :-------: | -------------------------------------- | +| `from` | `address` | The sender address | +| `to` | `address` | @param tokenId The tokenId to transfer | +| `tokenId` | `bytes32` | The tokenId to transfer | +| `data` | `bytes` | The data sent alongside the transfer | -- Allows to run custom logic before updating balances and notifiying sender/recipient by overriding this function. +
+ +### \_afterTokenTransfer + +```solidity +function _afterTokenTransfer( + address from, + address to, + bytes32 tokenId, + bytes data +) internal nonpayable; +``` + +Hook that is called after any token transfer, including minting and burning. +Allows to run custom logic after updating balances, but **before notifiying sender/recipient via LSP1** by overriding this function. #### Parameters @@ -924,6 +987,7 @@ Hook that is called before any token transfer, including minting and burning. | `from` | `address` | The sender address | | `to` | `address` | @param tokenId The tokenId to transfer | | `tokenId` | `bytes32` | The tokenId to transfer | +| `data` | `bytes` | The data sent alongside the transfer |
@@ -965,13 +1029,13 @@ LSP1 interface. ```solidity function _notifyTokenReceiver( address to, - bool allowNonLSP1Recipient, + bool force, bytes lsp1Data ) internal nonpayable; ``` An attempt is made to notify the token receiver about the `tokenId` changing owners -using LSP1 interface. When allowNonLSP1Recipient is FALSE the token receiver MUST support LSP1. +using LSP1 interface. When force is FALSE the token receiver MUST support LSP1. The receiver may revert when the token being sent is not wanted.
@@ -1008,6 +1072,13 @@ Returns the extension address stored under the following data key: ### \_fallbackLSP17Extendable +:::info + +The LSP8 Token contract should not hold any native tokens. Any native tokens received by the contract +will be forwarded to the extension address mapped to the selector from `msg.sig`. + +::: + ```solidity function _fallbackLSP17Extendable( bytes callData @@ -1021,9 +1092,6 @@ Reverts if there is no extension for the function being called. If there is an extension for the function selector being called, it calls the extension with the CALL opcode, passing the [`msg.data`](#msg.data) appended with the 20 bytes of the [`msg.sender`](#msg.sender) and 32 bytes of the [`msg.value`](#msg.value) -Because the function uses assembly [`return()/revert()`](#return) to terminate the call, it cannot be -called before other codes in fallback(). -Otherwise, the codes after \_fallbackLSP17Extendable() may never be reached.
@@ -1149,7 +1217,7 @@ Emitted when `tokenOwner` disables `operator` to transfer or burn `tokenId` on i ::: ```solidity -event Transfer(address operator, address indexed from, address indexed to, bytes32 indexed tokenId, bool allowNonLSP1Recipient, bytes data); +event Transfer(address operator, address indexed from, address indexed to, bytes32 indexed tokenId, bool force, bytes data); ``` Emitted when `tokenId` token is transferred from the `from` to the `to` address. @@ -1162,7 +1230,7 @@ Emitted when `tokenId` token is transferred from the `from` to the `to` address. | `from` **`indexed`** | `address` | The previous owner of the `tokenId` | | `to` **`indexed`** | `address` | The new owner of `tokenId` | | `tokenId` **`indexed`** | `bytes32` | The tokenId that was transferred | -| `allowNonLSP1Recipient` | `bool` | If the token transfer enforces the `to` recipient address to be a contract that implements the LSP1 standard or not. | +| `force` | `bool` | If the token transfer enforces the `to` recipient address to be a contract that implements the LSP1 standard or not. | | `data` | `bytes` | Any additional data the caller included by the caller during the transfer, and sent in the hooks to the `from` and `to` addresses. |
@@ -1329,7 +1397,7 @@ Reverts when trying to edit the data key `LSP4TokenSymbol` after the digital ass error LSP8CannotSendToAddressZero(); ``` -reverts when trying to send token to the zero address. +Reverts when trying to send token to the zero address.
@@ -1348,7 +1416,7 @@ reverts when trying to send token to the zero address. error LSP8CannotSendToSelf(); ``` -reverts when specifying the same address for `from` and `to` in a token transfer. +Reverts when specifying the same address for `from` and `to` in a token transfer.
@@ -1367,7 +1435,7 @@ reverts when specifying the same address for `from` and `to` in a token transfer error LSP8CannotUseAddressZeroAsOperator(); ``` -reverts when trying to set the zero address as an operator. +Reverts when trying to set the zero address as an operator.
@@ -1428,7 +1496,7 @@ Reverts when setting `0` for the [`tokenSupplyCap`](#tokensupplycap). The max to error LSP8InvalidTransferBatch(); ``` -reverts when the parameters used for `transferBatch` have different lengths. +Reverts when the parameters used for `transferBatch` have different lengths.
@@ -1447,7 +1515,7 @@ reverts when the parameters used for `transferBatch` have different lengths. error LSP8NonExistentTokenId(bytes32 tokenId); ``` -reverts when `tokenId` has not been minted. +Reverts when `tokenId` has not been minted. #### Parameters @@ -1472,7 +1540,7 @@ reverts when `tokenId` has not been minted. error LSP8NonExistingOperator(address operator, bytes32 tokenId); ``` -reverts when `operator` is not an operator for the `tokenId`. +Reverts when `operator` is not an operator for the `tokenId`. #### Parameters @@ -1498,7 +1566,7 @@ reverts when `operator` is not an operator for the `tokenId`. error LSP8NotTokenOperator(bytes32 tokenId, address caller); ``` -reverts when `caller` is not an allowed operator for `tokenId`. +Reverts when `caller` is not an allowed operator for `tokenId`. #### Parameters @@ -1524,7 +1592,7 @@ reverts when `caller` is not an allowed operator for `tokenId`. error LSP8NotTokenOwner(address tokenOwner, bytes32 tokenId, address caller); ``` -reverts when `caller` is not the `tokenOwner` of the `tokenId`. +Reverts when `caller` is not the `tokenOwner` of the `tokenId`. #### Parameters @@ -1553,7 +1621,7 @@ error LSP8NotifyTokenReceiverContractMissingLSP1Interface( ); ``` -reverts if the `tokenReceiver` does not implement LSP1 when minting or transferring tokens with `bool allowNonLSP1Recipient` set as `false`. +Reverts if the `tokenReceiver` does not implement LSP1 when minting or transferring tokens with `bool force` set as `false`. #### Parameters @@ -1578,7 +1646,7 @@ reverts if the `tokenReceiver` does not implement LSP1 when minting or transferr error LSP8NotifyTokenReceiverIsEOA(address tokenReceiver); ``` -reverts if the `tokenReceiver` is an EOA when minting or transferring tokens with `bool allowNonLSP1Recipient` set as `false`. +Reverts if the `tokenReceiver` is an EOA when minting or transferring tokens with `bool force` set as `false`. #### Parameters @@ -1603,7 +1671,7 @@ reverts if the `tokenReceiver` is an EOA when minting or transferring tokens wit error LSP8OperatorAlreadyAuthorized(address operator, bytes32 tokenId); ``` -reverts when `operator` is already authorized for the `tokenId`. +Reverts when `operator` is already authorized for the `tokenId`. #### Parameters @@ -1614,6 +1682,46 @@ reverts when `operator` is already authorized for the `tokenId`.
+### LSP8TokenContractCannotHoldValue + +:::note References + +- Specification details: [**LSP-8-IdentifiableDigitalAsset**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-8-IdentifiableDigitalAsset.md#lsp8tokencontractcannotholdvalue) +- Solidity implementation: [`LSP8CappedSupply.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP8IdentifiableDigitalAsset/extensions/LSP8CappedSupply.sol) +- Error signature: `LSP8TokenContractCannotHoldValue()` +- Error hash: `0x61f49442` + +::: + +```solidity +error LSP8TokenContractCannotHoldValue(); +``` + +_LSP8 contract cannot receive native tokens._ + +Error occurs when sending native tokens to the LSP8 contract without sending any data. E.g. Sending value without passing a bytes4 function selector to call a LSP17 Extension. + +
+ +### LSP8TokenIdTypeNotEditable + +:::note References + +- Specification details: [**LSP-8-IdentifiableDigitalAsset**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-8-IdentifiableDigitalAsset.md#lsp8tokenidtypenoteditable) +- Solidity implementation: [`LSP8CappedSupply.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP8IdentifiableDigitalAsset/extensions/LSP8CappedSupply.sol) +- Error signature: `LSP8TokenIdTypeNotEditable()` +- Error hash: `0x53bc1122` + +::: + +```solidity +error LSP8TokenIdTypeNotEditable(); +``` + +Reverts when trying to edit the data key `LSP8TokenIdType` after the identifiable digital asset contract has been deployed. The `LSP8TokenIdType` data key is located inside the ERC725Y Data key-value store of the identifiable digital asset contract. It can be set only once inside the constructor/initializer when the identifiable digital asset contract is being deployed. + +
+ ### LSP8TokenOwnerCannotBeOperator :::note References @@ -1629,7 +1737,7 @@ reverts when `operator` is already authorized for the `tokenId`. error LSP8TokenOwnerCannotBeOperator(); ``` -reverts when trying to authorize or revoke the token's owner as an operator. +Reverts when trying to authorize or revoke the token's owner as an operator.
@@ -1657,3 +1765,47 @@ reverts when there is no extension for the function selector being called with | `functionSelector` | `bytes4` | - |
+ +### OwnableCallerNotTheOwner + +:::note References + +- Specification details: [**LSP-8-IdentifiableDigitalAsset**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-8-IdentifiableDigitalAsset.md#ownablecallernottheowner) +- Solidity implementation: [`LSP8CappedSupply.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP8IdentifiableDigitalAsset/extensions/LSP8CappedSupply.sol) +- Error signature: `OwnableCallerNotTheOwner(address)` +- Error hash: `0xbf1169c5` + +::: + +```solidity +error OwnableCallerNotTheOwner(address callerAddress); +``` + +Reverts when only the owner is allowed to call the function. + +#### Parameters + +| Name | Type | Description | +| --------------- | :-------: | ---------------------------------------- | +| `callerAddress` | `address` | The address that tried to make the call. | + +
+ +### OwnableCannotSetZeroAddressAsOwner + +:::note References + +- Specification details: [**LSP-8-IdentifiableDigitalAsset**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-8-IdentifiableDigitalAsset.md#ownablecannotsetzeroaddressasowner) +- Solidity implementation: [`LSP8CappedSupply.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP8IdentifiableDigitalAsset/extensions/LSP8CappedSupply.sol) +- Error signature: `OwnableCannotSetZeroAddressAsOwner()` +- Error hash: `0x1ad8836c` + +::: + +```solidity +error OwnableCannotSetZeroAddressAsOwner(); +``` + +Reverts when trying to set `address(0)` as the contract owner when deploying the contract, initializing it or transferring ownership of the contract. + +
diff --git a/docs/contracts/LSP8IdentifiableDigitalAsset/extensions/LSP8CompatibleERC721.md b/docs/contracts/LSP8IdentifiableDigitalAsset/extensions/LSP8CompatibleERC721.md index 119d2a718..d82124210 100644 --- a/docs/contracts/LSP8IdentifiableDigitalAsset/extensions/LSP8CompatibleERC721.md +++ b/docs/contracts/LSP8IdentifiableDigitalAsset/extensions/LSP8CompatibleERC721.md @@ -56,6 +56,25 @@ This function is executed when:
+### receive + +:::note References + +- Specification details: [**LSP-8-IdentifiableDigitalAsset**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-8-IdentifiableDigitalAsset.md#receive) +- Solidity implementation: [`LSP8CompatibleERC721.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP8IdentifiableDigitalAsset/extensions/LSP8CompatibleERC721.sol) + +::: + +```solidity +receive() external payable; +``` + +_LSP8 contract cannot receive native tokens._ + +Reverts whenever someone tries to send native tokens to a LSP8 contract. + +
+ ### approve :::note References @@ -71,7 +90,7 @@ This function is executed when: function approve(address operator, uint256 tokenId) external nonpayable; ``` -_Calling `approve` function on `ILSP8CompatibleERC721` contract. Approving operator at address `operator` to transfer tokenId `tokenId` on behalf of its owner._ +_Calling `approve` function to approve operator at address `operator` to transfer tokenId `tokenId` on behalf of its owner._ Approval function compatible with ERC721 `approve(address,uint256)`. @@ -305,18 +324,22 @@ function isApprovedForAll( ) external view returns (bool); ``` +_Checking if address `operator` is approved to transfer any tokenId owned by address `owner`._ + +Compatible with ERC721 isApprovedForAll. + #### Parameters -| Name | Type | Description | -| ------------ | :-------: | ----------- | -| `tokenOwner` | `address` | - | -| `operator` | `address` | - | +| Name | Type | Description | +| ------------ | :-------: | -------------------------------- | +| `tokenOwner` | `address` | The tokenOwner address to query. | +| `operator` | `address` | The operator address to query. | #### Returns -| Name | Type | Description | -| ---- | :----: | ----------- | -| `0` | `bool` | - | +| Name | Type | Description | +| ---- | :----: | --------------------------------------------------------------------------- | +| `0` | `bool` | Returns if the `operator` is allowed to manage all of the assets of `owner` |
@@ -370,7 +393,7 @@ Returns whether `operator` address is an operator for a given `tokenId`. function name() external view returns (string); ``` -Returns the name of the token. +Returns the name of the token. For compatibility with clients & tools that expect ERC721. #### Returns @@ -501,7 +524,7 @@ Remove access of `operator` for a given `tokenId`, disallowing it to transfer `t :::info -This function sets the `allowNonLSP1Recipient` parameter to `true` so that EOAs and any contract can receive the `tokenId`. +This function sets the `force` parameter to `true` so that EOAs and any contract can receive the `tokenId`. ::: @@ -513,7 +536,7 @@ function safeTransferFrom( ) external nonpayable; ``` -_Calling `safeTransferFrom` function on `ILSP8CompatibleERC721` contract. Transferring tokenId `tokenId` from address `from` to address `to`._ +_Calling `safeTransferFrom` function to transfer tokenId `tokenId` from address `from` to address `to`._ Safe Transfer function without optional data from the ERC721 standard interface. @@ -540,7 +563,7 @@ Safe Transfer function without optional data from the ERC721 standard interface. :::info -This function sets the `allowNonLSP1Recipient` parameter to `true` so that EOAs and any contract can receive the `tokenId`. +This function sets the `force` parameter to `true` so that EOAs and any contract can receive the `tokenId`. ::: @@ -553,7 +576,7 @@ function safeTransferFrom( ) external nonpayable; ``` -_Calling `safeTransferFrom` function with `data` on `ILSP8CompatibleERC721` contract. Transferring tokenId `tokenId` from address `from` to address `to`._ +_Calling `safeTransferFrom` function to transfer tokenId `tokenId` from address `from` to address `to`._ Safe Transfer function with optional data from the ERC721 standard interface. @@ -583,14 +606,24 @@ Safe Transfer function with optional data from the ERC721 standard interface. function setApprovalForAll(address operator, bool approved) external nonpayable; ``` -See [`_setApprovalForAll`](#_setapprovalforall) +_Setting the "approval for all" status of operator `_operator` to `_approved` to allow it to transfer any tokenIds on behalf of `msg.sender`._ + +Enable or disable approval for a third party ("operator") to manage all of `msg.sender`'s assets. The contract MUST allow multiple operators per owner. See [`_setApprovalForAll`](#_setapprovalforall) + +
+ +**Emitted events:** + +- [`ApprovalForAll`](#approvalforall) event + +
#### Parameters -| Name | Type | Description | -| ---------- | :-------: | ----------- | -| `operator` | `address` | - | -| `approved` | `bool` | - | +| Name | Type | Description | +| ---------- | :-------: | ----------------------------------------------------------- | +| `operator` | `address` | Address to add to the set of authorized operators. | +| `approved` | `bool` | True if the operator is approved, false to revoke approval. |
@@ -740,7 +773,7 @@ Returns true if this contract implements the interface defined by `interfaceId`. function symbol() external view returns (string); ``` -Returns the symbol of the token, usually a shorter version of the name. +Returns the symbol of the token, usually a shorter version of the name. For compatibility with clients & tools that expect ERC721. #### Returns @@ -816,10 +849,10 @@ Returns the list of `tokenIds` for the `tokenOwner` address. :::note References -- Specification details: [**LSP-8-IdentifiableDigitalAsset**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-8-IdentifiableDigitalAsset.md#)) +- Specification details: [**LSP-8-IdentifiableDigitalAsset**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-8-IdentifiableDigitalAsset.md#tokenuri) - Solidity implementation: [`LSP8CompatibleERC721.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP8IdentifiableDigitalAsset/extensions/LSP8CompatibleERC721.sol) -- Function signature: `)` -- Function selector: `0x59d76dc3` +- Function signature: `tokenURI(uint256)` +- Function selector: `0xc87b56dd` ::: @@ -827,6 +860,10 @@ Returns the list of `tokenIds` for the `tokenOwner` address. function tokenURI(uint256) external view returns (string); ``` +_Retrieving the token URI of tokenId `tokenId`._ + +Compatible with ERC721Metadata tokenURI. Retrieve the tokenURI for a specific `tokenId`. + #### Parameters | Name | Type | Description | @@ -835,9 +872,9 @@ function tokenURI(uint256) external view returns (string); #### Returns -| Name | Type | Description | -| ---- | :------: | ----------- | -| `0` | `string` | - | +| Name | Type | Description | +| ---- | :------: | -------------- | +| `0` | `string` | The token URI. |
@@ -882,22 +919,22 @@ function transfer( address from, address to, bytes32 tokenId, - bool allowNonLSP1Recipient, + bool force, bytes data ) external nonpayable; ``` -Transfer a given `tokenId` token from the `from` address to the `to` address. If operators are set for a specific `tokenId`, all the operators are revoked after the tokenId have been transferred. The `allowNonLSP1Recipient` parameter MUST be set to `true` when transferring tokens to Externally Owned Accounts (EOAs) or contracts that do not implement the LSP1 standard. +Transfer a given `tokenId` token from the `from` address to the `to` address. If operators are set for a specific `tokenId`, all the operators are revoked after the tokenId have been transferred. The `force` parameter MUST be set to `true` when transferring tokens to Externally Owned Accounts (EOAs) or contracts that do not implement the LSP1 standard. #### Parameters -| Name | Type | Description | -| ----------------------- | :-------: | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `from` | `address` | The address that owns the given `tokenId`. | -| `to` | `address` | The address that will receive the `tokenId`. | -| `tokenId` | `bytes32` | The token ID to transfer. | -| `allowNonLSP1Recipient` | `bool` | When set to `true`, the `to` address CAN be any addres. When set to `false`, the `to` address MUST be a contract that supports the LSP1 UniversalReceiver standard. | -| `data` | `bytes` | Any additional data the caller wants included in the emitted event, and sent in the hooks of the `from` and `to` addresses. | +| Name | Type | Description | +| --------- | :-------: | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `from` | `address` | The address that owns the given `tokenId`. | +| `to` | `address` | The address that will receive the `tokenId`. | +| `tokenId` | `bytes32` | The token ID to transfer. | +| `force` | `bool` | When set to `true`, the `to` address CAN be any addres. When set to `false`, the `to` address MUST be a contract that supports the LSP1 UniversalReceiver standard. | +| `data` | `bytes` | Any additional data the caller wants included in the emitted event, and sent in the hooks of the `from` and `to` addresses. |
@@ -917,7 +954,7 @@ function transferBatch( address[] from, address[] to, bytes32[] tokenId, - bool[] allowNonLSP1Recipient, + bool[] force, bytes[] data ) external nonpayable; ``` @@ -926,13 +963,13 @@ Transfers multiple tokens at once based on the arrays of `from`, `to` and `token #### Parameters -| Name | Type | Description | -| ----------------------- | :---------: | ----------------------------------------------------------------------------------------------------------------------------------------- | -| `from` | `address[]` | An array of sending addresses. | -| `to` | `address[]` | An array of recipient addresses. | -| `tokenId` | `bytes32[]` | An array of token IDs to transfer. | -| `allowNonLSP1Recipient` | `bool[]` | When set to `true`, `to` may be any address. When set to `false`, `to` must be a contract that supports the LSP1 standard and not revert. | -| `data` | `bytes[]` | Any additional data the caller wants included in the emitted event, and sent in the hooks to the `from` and `to` addresses. | +| Name | Type | Description | +| --------- | :---------: | ----------------------------------------------------------------------------------------------------------------------------------------- | +| `from` | `address[]` | An array of sending addresses. | +| `to` | `address[]` | An array of recipient addresses. | +| `tokenId` | `bytes32[]` | An array of token IDs to transfer. | +| `force` | `bool[]` | When set to `true`, `to` may be any address. When set to `false`, `to` must be a contract that supports the LSP1 standard and not revert. | +| `data` | `bytes[]` | Any additional data the caller wants included in the emitted event, and sent in the hooks to the `from` and `to` addresses. |
@@ -949,7 +986,7 @@ Transfers multiple tokens at once based on the arrays of `from`, `to` and `token :::info -This function sets the `allowNonLSP1Recipient` parameter to `true` so that EOAs and any contract can receive the `tokenId`. +This function sets the `force` parameter to `true` so that EOAs and any contract can receive the `tokenId`. ::: @@ -961,7 +998,7 @@ function transferFrom( ) external nonpayable; ``` -_Calling `transferFrom` function on `ILSP8CompatibleERC721` contract. Transferring tokenId `tokenId` from address `from` to address `to`._ +_Calling `transferFrom` function to transfer tokenId `tokenId` from address `from` to address `to`._ Transfer functions from the ERC721 standard interface. @@ -1057,9 +1094,12 @@ mapping(bytes32 => bytes) _store ### \_setData ```solidity -function _setData(bytes32 key, bytes value) internal nonpayable; +function _setData(bytes32 dataKey, bytes dataValue) internal nonpayable; ``` +The ERC725Y data key `_LSP8_TOKENID_TYPE_KEY` cannot be changed +once the identifiable digital asset contract has been deployed. +
### \_isOperatorOrOwner @@ -1143,7 +1183,7 @@ When `tokenId` does not exist then revert with an error. function _mint( address to, bytes32 tokenId, - bool allowNonLSP1Recipient, + bool force, bytes data ) internal nonpayable; ``` @@ -1165,7 +1205,7 @@ function _transfer( address from, address to, bytes32 tokenId, - bool allowNonLSP1Recipient, + bool force, bytes data ) internal nonpayable; ``` @@ -1178,13 +1218,38 @@ function _transfer( function _beforeTokenTransfer( address from, address to, - bytes32 tokenId + bytes32 tokenId, + bytes data ) internal nonpayable; ``` Hook that is called before any token transfer, including minting and burning. +Allows to run custom logic before updating balances and notifiying sender/recipient by overriding this function. + +#### Parameters + +| Name | Type | Description | +| --------- | :-------: | -------------------------------------- | +| `from` | `address` | The sender address | +| `to` | `address` | @param tokenId The tokenId to transfer | +| `tokenId` | `bytes32` | The tokenId to transfer | +| `data` | `bytes` | The data sent alongside the transfer | + +
-- Allows to run custom logic before updating balances and notifiying sender/recipient by overriding this function. +### \_afterTokenTransfer + +```solidity +function _afterTokenTransfer( + address from, + address to, + bytes32 tokenId, + bytes data +) internal nonpayable; +``` + +Hook that is called after any token transfer, including minting and burning. +Allows to run custom logic after updating balances, but **before notifiying sender/recipient via LSP1** by overriding this function. #### Parameters @@ -1193,6 +1258,7 @@ Hook that is called before any token transfer, including minting and burning. | `from` | `address` | The sender address | | `to` | `address` | @param tokenId The tokenId to transfer | | `tokenId` | `bytes32` | The tokenId to transfer | +| `data` | `bytes` | The data sent alongside the transfer |
@@ -1234,13 +1300,13 @@ LSP1 interface. ```solidity function _notifyTokenReceiver( address to, - bool allowNonLSP1Recipient, + bool force, bytes lsp1Data ) internal nonpayable; ``` An attempt is made to notify the token receiver about the `tokenId` changing owners -using LSP1 interface. When allowNonLSP1Recipient is FALSE the token receiver MUST support LSP1. +using LSP1 interface. When force is FALSE the token receiver MUST support LSP1. The receiver may revert when the token being sent is not wanted.
@@ -1277,6 +1343,13 @@ Returns the extension address stored under the following data key: ### \_fallbackLSP17Extendable +:::info + +The LSP8 Token contract should not hold any native tokens. Any native tokens received by the contract +will be forwarded to the extension address mapped to the selector from `msg.sig`. + +::: + ```solidity function _fallbackLSP17Extendable( bytes callData @@ -1290,9 +1363,6 @@ Reverts if there is no extension for the function being called. If there is an extension for the function selector being called, it calls the extension with the CALL opcode, passing the [`msg.data`](#msg.data) appended with the 20 bytes of the [`msg.sender`](#msg.sender) and 32 bytes of the [`msg.value`](#msg.value) -Because the function uses assembly [`return()/revert()`](#return) to terminate the call, it cannot be -called before other codes in fallback(). -Otherwise, the codes after \_fallbackLSP17Extendable() may never be reached.
@@ -1323,7 +1393,7 @@ function _setApprovalForAll( ) internal nonpayable; ``` -Approve `operator` to operate on all tokens of `tokensOwner` +Approve `operator` to operate on all tokens of `tokensOwner`.
@@ -1349,20 +1419,18 @@ Approve `operator` to operate on all tokens of `tokensOwner` ::: ```solidity -event Approval(address indexed owner, address indexed operator, uint256 indexed tokenId); +event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId); ``` -_ERC721 `Approval` compatible event emitted. Successfully approved operator `operator` to operate on tokenId `tokenId` on behalf of token owner `owner`._ - -ERC721 `Approval` event emitted when `owner` enables `operator` for `tokenId`. To provide compatibility with indexing ERC721 events. +Emitted when the allowance of a `spender` for an `owner` is set by a call to [`approve`](#approve). `value` is the new allowance. #### Parameters -| Name | Type | Description | -| ------------------------ | :-------: | ------------------------------------------ | -| `owner` **`indexed`** | `address` | The address of the owner of the `tokenId`. | -| `operator` **`indexed`** | `address` | The address set as operator. | -| `tokenId` **`indexed`** | `uint256` | The approved tokenId. | +| Name | Type | Description | +| ------------------------ | :-------: | ----------- | +| `owner` **`indexed`** | `address` | - | +| `approved` **`indexed`** | `address` | - | +| `tokenId` **`indexed`** | `uint256` | - |
@@ -1381,17 +1449,15 @@ ERC721 `Approval` event emitted when `owner` enables `operator` for `tokenId`. T event ApprovalForAll(address indexed owner, address indexed operator, bool approved); ``` -_ERC721 `ApprovalForAll` compatible event emitted. Successfully set "approved for all" status to `approved` for operator `operator` for token owner `owner`._ - -ERC721 `ApprovalForAll` event emitted when an `operator` is enabled or disabled for an owner to transfer any of its tokenIds. The operator can manage all NFTs of the owner. +Emitted when `account` grants or revokes permission to `operator` to transfer their tokens, according to `approved`. #### Parameters -| Name | Type | Description | -| ------------------------ | :-------: | ---------------------------------------------- | -| `owner` **`indexed`** | `address` | The address of the owner of tokenIds. | -| `operator` **`indexed`** | `address` | The address set as operator. | -| `approved` | `bool` | If `operator` is approved for all NFTs or not. | +| Name | Type | Description | +| ------------------------ | :-------: | ----------- | +| `owner` **`indexed`** | `address` | - | +| `operator` **`indexed`** | `address` | - | +| `approved` | `bool` | - |
@@ -1515,11 +1581,9 @@ Emitted when `tokenOwner` disables `operator` to transfer or burn `tokenId` on i ::: ```solidity -event Transfer(address operator, address indexed from, address indexed to, bytes32 indexed tokenId, bool allowNonLSP1Recipient, bytes data); +event Transfer(address operator, address indexed from, address indexed to, bytes32 indexed tokenId, bool force, bytes data); ``` -_ERC721 `Transfer` compatible event emitted. Successfully transferred tokenId `tokenId` from `from` to `to`._ - Emitted when `tokenId` token is transferred from the `from` to the `to` address. #### Parameters @@ -1530,11 +1594,38 @@ Emitted when `tokenId` token is transferred from the `from` to the `to` address. | `from` **`indexed`** | `address` | The previous owner of the `tokenId` | | `to` **`indexed`** | `address` | The new owner of `tokenId` | | `tokenId` **`indexed`** | `bytes32` | The tokenId that was transferred | -| `allowNonLSP1Recipient` | `bool` | If the token transfer enforces the `to` recipient address to be a contract that implements the LSP1 standard or not. | +| `force` | `bool` | If the token transfer enforces the `to` recipient address to be a contract that implements the LSP1 standard or not. | | `data` | `bytes` | Any additional data the caller included by the caller during the transfer, and sent in the hooks to the `from` and `to` addresses. |
+### Transfer + +:::note References + +- Specification details: [**LSP-8-IdentifiableDigitalAsset**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-8-IdentifiableDigitalAsset.md#transfer) +- Solidity implementation: [`LSP8CompatibleERC721.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP8IdentifiableDigitalAsset/extensions/LSP8CompatibleERC721.sol) +- Event signature: `Transfer(address,address,uint256)` +- Event topic hash: `0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef` + +::: + +```solidity +event Transfer(address indexed from, address indexed to, uint256 indexed tokenId); +``` + +Emitted when `value` tokens are moved from one account (`from`) to another (`to`). Note that `value` may be zero. + +#### Parameters + +| Name | Type | Description | +| ----------------------- | :-------: | ----------- | +| `from` **`indexed`** | `address` | - | +| `to` **`indexed`** | `address` | - | +| `tokenId` **`indexed`** | `uint256` | - | + +
+ ## Errors ### ERC725Y_DataKeysValuesEmptyArray @@ -1697,7 +1788,7 @@ Reverts when trying to edit the data key `LSP4TokenSymbol` after the digital ass error LSP8CannotSendToAddressZero(); ``` -reverts when trying to send token to the zero address. +Reverts when trying to send token to the zero address.
@@ -1716,7 +1807,7 @@ reverts when trying to send token to the zero address. error LSP8CannotSendToSelf(); ``` -reverts when specifying the same address for `from` and `to` in a token transfer. +Reverts when specifying the same address for `from` and `to` in a token transfer.
@@ -1735,7 +1826,7 @@ reverts when specifying the same address for `from` and `to` in a token transfer error LSP8CannotUseAddressZeroAsOperator(); ``` -reverts when trying to set the zero address as an operator. +Reverts when trying to set the zero address as an operator.
@@ -1754,7 +1845,7 @@ reverts when trying to set the zero address as an operator. error LSP8InvalidTransferBatch(); ``` -reverts when the parameters used for `transferBatch` have different lengths. +Reverts when the parameters used for `transferBatch` have different lengths.
@@ -1773,7 +1864,7 @@ reverts when the parameters used for `transferBatch` have different lengths. error LSP8NonExistentTokenId(bytes32 tokenId); ``` -reverts when `tokenId` has not been minted. +Reverts when `tokenId` has not been minted. #### Parameters @@ -1798,7 +1889,7 @@ reverts when `tokenId` has not been minted. error LSP8NonExistingOperator(address operator, bytes32 tokenId); ``` -reverts when `operator` is not an operator for the `tokenId`. +Reverts when `operator` is not an operator for the `tokenId`. #### Parameters @@ -1824,7 +1915,7 @@ reverts when `operator` is not an operator for the `tokenId`. error LSP8NotTokenOperator(bytes32 tokenId, address caller); ``` -reverts when `caller` is not an allowed operator for `tokenId`. +Reverts when `caller` is not an allowed operator for `tokenId`. #### Parameters @@ -1850,7 +1941,7 @@ reverts when `caller` is not an allowed operator for `tokenId`. error LSP8NotTokenOwner(address tokenOwner, bytes32 tokenId, address caller); ``` -reverts when `caller` is not the `tokenOwner` of the `tokenId`. +Reverts when `caller` is not the `tokenOwner` of the `tokenId`. #### Parameters @@ -1879,7 +1970,7 @@ error LSP8NotifyTokenReceiverContractMissingLSP1Interface( ); ``` -reverts if the `tokenReceiver` does not implement LSP1 when minting or transferring tokens with `bool allowNonLSP1Recipient` set as `false`. +Reverts if the `tokenReceiver` does not implement LSP1 when minting or transferring tokens with `bool force` set as `false`. #### Parameters @@ -1904,7 +1995,7 @@ reverts if the `tokenReceiver` does not implement LSP1 when minting or transferr error LSP8NotifyTokenReceiverIsEOA(address tokenReceiver); ``` -reverts if the `tokenReceiver` is an EOA when minting or transferring tokens with `bool allowNonLSP1Recipient` set as `false`. +Reverts if the `tokenReceiver` is an EOA when minting or transferring tokens with `bool force` set as `false`. #### Parameters @@ -1929,7 +2020,7 @@ reverts if the `tokenReceiver` is an EOA when minting or transferring tokens wit error LSP8OperatorAlreadyAuthorized(address operator, bytes32 tokenId); ``` -reverts when `operator` is already authorized for the `tokenId`. +Reverts when `operator` is already authorized for the `tokenId`. #### Parameters @@ -1940,6 +2031,46 @@ reverts when `operator` is already authorized for the `tokenId`.
+### LSP8TokenContractCannotHoldValue + +:::note References + +- Specification details: [**LSP-8-IdentifiableDigitalAsset**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-8-IdentifiableDigitalAsset.md#lsp8tokencontractcannotholdvalue) +- Solidity implementation: [`LSP8CompatibleERC721.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP8IdentifiableDigitalAsset/extensions/LSP8CompatibleERC721.sol) +- Error signature: `LSP8TokenContractCannotHoldValue()` +- Error hash: `0x61f49442` + +::: + +```solidity +error LSP8TokenContractCannotHoldValue(); +``` + +_LSP8 contract cannot receive native tokens._ + +Error occurs when sending native tokens to the LSP8 contract without sending any data. E.g. Sending value without passing a bytes4 function selector to call a LSP17 Extension. + +
+ +### LSP8TokenIdTypeNotEditable + +:::note References + +- Specification details: [**LSP-8-IdentifiableDigitalAsset**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-8-IdentifiableDigitalAsset.md#lsp8tokenidtypenoteditable) +- Solidity implementation: [`LSP8CompatibleERC721.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP8IdentifiableDigitalAsset/extensions/LSP8CompatibleERC721.sol) +- Error signature: `LSP8TokenIdTypeNotEditable()` +- Error hash: `0x53bc1122` + +::: + +```solidity +error LSP8TokenIdTypeNotEditable(); +``` + +Reverts when trying to edit the data key `LSP8TokenIdType` after the identifiable digital asset contract has been deployed. The `LSP8TokenIdType` data key is located inside the ERC725Y Data key-value store of the identifiable digital asset contract. It can be set only once inside the constructor/initializer when the identifiable digital asset contract is being deployed. + +
+ ### LSP8TokenOwnerCannotBeOperator :::note References @@ -1955,7 +2086,7 @@ reverts when `operator` is already authorized for the `tokenId`. error LSP8TokenOwnerCannotBeOperator(); ``` -reverts when trying to authorize or revoke the token's owner as an operator. +Reverts when trying to authorize or revoke the token's owner as an operator.
@@ -1983,3 +2114,47 @@ reverts when there is no extension for the function selector being called with | `functionSelector` | `bytes4` | - |
+ +### OwnableCallerNotTheOwner + +:::note References + +- Specification details: [**LSP-8-IdentifiableDigitalAsset**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-8-IdentifiableDigitalAsset.md#ownablecallernottheowner) +- Solidity implementation: [`LSP8CompatibleERC721.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP8IdentifiableDigitalAsset/extensions/LSP8CompatibleERC721.sol) +- Error signature: `OwnableCallerNotTheOwner(address)` +- Error hash: `0xbf1169c5` + +::: + +```solidity +error OwnableCallerNotTheOwner(address callerAddress); +``` + +Reverts when only the owner is allowed to call the function. + +#### Parameters + +| Name | Type | Description | +| --------------- | :-------: | ---------------------------------------- | +| `callerAddress` | `address` | The address that tried to make the call. | + +
+ +### OwnableCannotSetZeroAddressAsOwner + +:::note References + +- Specification details: [**LSP-8-IdentifiableDigitalAsset**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-8-IdentifiableDigitalAsset.md#ownablecannotsetzeroaddressasowner) +- Solidity implementation: [`LSP8CompatibleERC721.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP8IdentifiableDigitalAsset/extensions/LSP8CompatibleERC721.sol) +- Error signature: `OwnableCannotSetZeroAddressAsOwner()` +- Error hash: `0x1ad8836c` + +::: + +```solidity +error OwnableCannotSetZeroAddressAsOwner(); +``` + +Reverts when trying to set `address(0)` as the contract owner when deploying the contract, initializing it or transferring ownership of the contract. + +
diff --git a/docs/contracts/LSP8IdentifiableDigitalAsset/extensions/LSP8Enumerable.md b/docs/contracts/LSP8IdentifiableDigitalAsset/extensions/LSP8Enumerable.md index 46edaa81d..eea20e1d9 100644 --- a/docs/contracts/LSP8IdentifiableDigitalAsset/extensions/LSP8Enumerable.md +++ b/docs/contracts/LSP8IdentifiableDigitalAsset/extensions/LSP8Enumerable.md @@ -56,6 +56,25 @@ This function is executed when:
+### receive + +:::note References + +- Specification details: [**LSP-8-IdentifiableDigitalAsset**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-8-IdentifiableDigitalAsset.md#receive) +- Solidity implementation: [`LSP8Enumerable.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP8IdentifiableDigitalAsset/extensions/LSP8Enumerable.sol) + +::: + +```solidity +receive() external payable; +``` + +_LSP8 contract cannot receive native tokens._ + +Reverts whenever someone tries to send native tokens to a LSP8 contract. + +
+ ### authorizeOperator :::note References @@ -594,22 +613,22 @@ function transfer( address from, address to, bytes32 tokenId, - bool allowNonLSP1Recipient, + bool force, bytes data ) external nonpayable; ``` -Transfer a given `tokenId` token from the `from` address to the `to` address. If operators are set for a specific `tokenId`, all the operators are revoked after the tokenId have been transferred. The `allowNonLSP1Recipient` parameter MUST be set to `true` when transferring tokens to Externally Owned Accounts (EOAs) or contracts that do not implement the LSP1 standard. +Transfer a given `tokenId` token from the `from` address to the `to` address. If operators are set for a specific `tokenId`, all the operators are revoked after the tokenId have been transferred. The `force` parameter MUST be set to `true` when transferring tokens to Externally Owned Accounts (EOAs) or contracts that do not implement the LSP1 standard. #### Parameters -| Name | Type | Description | -| ----------------------- | :-------: | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `from` | `address` | The address that owns the given `tokenId`. | -| `to` | `address` | The address that will receive the `tokenId`. | -| `tokenId` | `bytes32` | The token ID to transfer. | -| `allowNonLSP1Recipient` | `bool` | When set to `true`, the `to` address CAN be any addres. When set to `false`, the `to` address MUST be a contract that supports the LSP1 UniversalReceiver standard. | -| `data` | `bytes` | Any additional data the caller wants included in the emitted event, and sent in the hooks of the `from` and `to` addresses. | +| Name | Type | Description | +| --------- | :-------: | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `from` | `address` | The address that owns the given `tokenId`. | +| `to` | `address` | The address that will receive the `tokenId`. | +| `tokenId` | `bytes32` | The token ID to transfer. | +| `force` | `bool` | When set to `true`, the `to` address CAN be any addres. When set to `false`, the `to` address MUST be a contract that supports the LSP1 UniversalReceiver standard. | +| `data` | `bytes` | Any additional data the caller wants included in the emitted event, and sent in the hooks of the `from` and `to` addresses. |
@@ -629,7 +648,7 @@ function transferBatch( address[] from, address[] to, bytes32[] tokenId, - bool[] allowNonLSP1Recipient, + bool[] force, bytes[] data ) external nonpayable; ``` @@ -638,13 +657,13 @@ Transfers multiple tokens at once based on the arrays of `from`, `to` and `token #### Parameters -| Name | Type | Description | -| ----------------------- | :---------: | ----------------------------------------------------------------------------------------------------------------------------------------- | -| `from` | `address[]` | An array of sending addresses. | -| `to` | `address[]` | An array of recipient addresses. | -| `tokenId` | `bytes32[]` | An array of token IDs to transfer. | -| `allowNonLSP1Recipient` | `bool[]` | When set to `true`, `to` may be any address. When set to `false`, `to` must be a contract that supports the LSP1 standard and not revert. | -| `data` | `bytes[]` | Any additional data the caller wants included in the emitted event, and sent in the hooks to the `from` and `to` addresses. | +| Name | Type | Description | +| --------- | :---------: | ----------------------------------------------------------------------------------------------------------------------------------------- | +| `from` | `address[]` | An array of sending addresses. | +| `to` | `address[]` | An array of recipient addresses. | +| `tokenId` | `bytes32[]` | An array of token IDs to transfer. | +| `force` | `bool[]` | When set to `true`, `to` may be any address. When set to `false`, `to` must be a contract that supports the LSP1 standard and not revert. | +| `data` | `bytes[]` | Any additional data the caller wants included in the emitted event, and sent in the hooks to the `from` and `to` addresses. |
@@ -733,7 +752,8 @@ mapping(bytes32 => bytes) _store function _setData(bytes32 dataKey, bytes dataValue) internal nonpayable; ``` -Save gas by emitting the [`DataChanged`](#datachanged) event with only the first 256 bytes of dataValue +The ERC725Y data key `_LSP8_TOKENID_TYPE_KEY` cannot be changed +once the identifiable digital asset contract has been deployed.
@@ -814,11 +834,21 @@ When `tokenId` does not exist then revert with an error. ### \_mint +:::info + +Any logic in the: + +- {\_beforeTokenTransfer} function will run before updating the balances and ownership of `tokenId`s. + +- {\_afterTokenTransfer} function will run after updating the balances and ownership of `tokenId`s, **but before notifying the recipient via LSP1**. + +::: + ```solidity function _mint( address to, bytes32 tokenId, - bool allowNonLSP1Recipient, + bool force, bytes data ) internal nonpayable; ``` @@ -835,17 +865,27 @@ Create `tokenId` by minting it and transfers it to `to`. #### Parameters -| Name | Type | Description | -| ----------------------- | :-------: | -------------------------------------------------------------------------------------------------------------------------- | -| `to` | `address` | @param tokenId The token ID to create (= mint). | -| `tokenId` | `bytes32` | The token ID to create (= mint). | -| `allowNonLSP1Recipient` | `bool` | When set to `true`, `to` may be any address. When set to `false`, `to` must be a contract that supports the LSP1 standard. | -| `data` | `bytes` | Any additional data the caller wants included in the emitted event, and sent in the hook of the `to` address. | +| Name | Type | Description | +| --------- | :-------: | -------------------------------------------------------------------------------------------------------------------------- | +| `to` | `address` | @param tokenId The token ID to create (= mint). | +| `tokenId` | `bytes32` | The token ID to create (= mint). | +| `force` | `bool` | When set to `true`, `to` may be any address. When set to `false`, `to` must be a contract that supports the LSP1 standard. | +| `data` | `bytes` | Any additional data the caller wants included in the emitted event, and sent in the hook of the `to` address. |
### \_burn +:::info + +Any logic in the: + +- {\_beforeTokenTransfer} function will run before updating the balances and ownership of `tokenId`s. + +- {\_afterTokenTransfer} function will run after updating the balances and ownership of `tokenId`s, **but before notifying the sender via LSP1**. + +::: + :::tip Hint In dApps, you can know which addresses are burning tokens by listening for the `Transfer` event and filter with the zero address as `to`. @@ -861,7 +901,6 @@ This will also clear all the operators allowed to transfer the `tokenId`. The owner of the `tokenId` will be notified about the `tokenId` being transferred through its LSP1 [`universalReceiver`](#universalreceiver) function, if it is a contract that supports the LSP1 interface. Its [`universalReceiver`](#universalreceiver) function will receive all the parameters in the calldata packed encoded. -Any logic in the [`_beforeTokenTransfer`](#_beforetokentransfer) function will run before burning `tokenId` and updating the balances.
@@ -882,6 +921,16 @@ Any logic in the [`_beforeTokenTransfer`](#_beforetokentransfer) function will r ### \_transfer +:::info + +Any logic in the: + +- {\_beforeTokenTransfer} function will run before updating the balances and ownership of `tokenId`s. + +- {\_afterTokenTransfer} function will run after updating the balances and ownership of `tokenId`s, **but before notifying the sender/recipient via LSP1**. + +::: + :::danger This internal function does not check if the sender is authorized or not to operate on the `tokenId`. @@ -893,7 +942,7 @@ function _transfer( address from, address to, bytes32 tokenId, - bool allowNonLSP1Recipient, + bool force, bytes data ) internal nonpayable; ``` @@ -902,7 +951,6 @@ Change the owner of the `tokenId` from `from` to `to`. Both the sender and recipient will be notified of the `tokenId` being transferred through their LSP1 [`universalReceiver`](#universalreceiver) function, if they are contracts that support the LSP1 interface. Their `universalReceiver` function will receive all the parameters in the calldata packed encoded. -Any logic in the [`_beforeTokenTransfer`](#_beforetokentransfer) function will run before changing the owner of `tokenId`.
@@ -914,13 +962,13 @@ Any logic in the [`_beforeTokenTransfer`](#_beforetokentransfer) function will r #### Parameters -| Name | Type | Description | -| ----------------------- | :-------: | -------------------------------------------------------------------------------------------------------------------------- | -| `from` | `address` | The sender address. | -| `to` | `address` | @param tokenId The token to transfer. | -| `tokenId` | `bytes32` | The token to transfer. | -| `allowNonLSP1Recipient` | `bool` | When set to `true`, `to` may be any address. When set to `false`, `to` must be a contract that supports the LSP1 standard. | -| `data` | `bytes` | Additional data the caller wants included in the emitted event, and sent in the hooks to `from` and `to` addresses. | +| Name | Type | Description | +| --------- | :-------: | -------------------------------------------------------------------------------------------------------------------------- | +| `from` | `address` | The sender address. | +| `to` | `address` | @param tokenId The token to transfer. | +| `tokenId` | `bytes32` | The token to transfer. | +| `force` | `bool` | When set to `true`, `to` may be any address. When set to `false`, `to` must be a contract that supports the LSP1 standard. | +| `data` | `bytes` | Additional data the caller wants included in the emitted event, and sent in the hooks to `from` and `to` addresses. |
@@ -930,7 +978,8 @@ Any logic in the [`_beforeTokenTransfer`](#_beforetokentransfer) function will r function _beforeTokenTransfer( address from, address to, - bytes32 tokenId + bytes32 tokenId, + bytes data ) internal nonpayable; ``` @@ -941,6 +990,32 @@ function _beforeTokenTransfer( | `from` | `address` | The address sending the `tokenId` (`address(0)` when `tokenId` is being minted). | | `to` | `address` | @param tokenId The bytes32 identifier of the token being transferred. | | `tokenId` | `bytes32` | The bytes32 identifier of the token being transferred. | +| `data` | `bytes` | The data sent alongside the the token transfer. | + +
+ +### \_afterTokenTransfer + +```solidity +function _afterTokenTransfer( + address from, + address to, + bytes32 tokenId, + bytes data +) internal nonpayable; +``` + +Hook that is called after any token transfer, including minting and burning. +Allows to run custom logic after updating balances, but **before notifiying sender/recipient via LSP1** by overriding this function. + +#### Parameters + +| Name | Type | Description | +| --------- | :-------: | -------------------------------------- | +| `from` | `address` | The sender address | +| `to` | `address` | @param tokenId The tokenId to transfer | +| `tokenId` | `bytes32` | The tokenId to transfer | +| `data` | `bytes` | The data sent alongside the transfer |
@@ -982,13 +1057,13 @@ LSP1 interface. ```solidity function _notifyTokenReceiver( address to, - bool allowNonLSP1Recipient, + bool force, bytes lsp1Data ) internal nonpayable; ``` An attempt is made to notify the token receiver about the `tokenId` changing owners -using LSP1 interface. When allowNonLSP1Recipient is FALSE the token receiver MUST support LSP1. +using LSP1 interface. When force is FALSE the token receiver MUST support LSP1. The receiver may revert when the token being sent is not wanted.
@@ -1025,6 +1100,13 @@ Returns the extension address stored under the following data key: ### \_fallbackLSP17Extendable +:::info + +The LSP8 Token contract should not hold any native tokens. Any native tokens received by the contract +will be forwarded to the extension address mapped to the selector from `msg.sig`. + +::: + ```solidity function _fallbackLSP17Extendable( bytes callData @@ -1038,9 +1120,6 @@ Reverts if there is no extension for the function being called. If there is an extension for the function selector being called, it calls the extension with the CALL opcode, passing the [`msg.data`](#msg.data) appended with the 20 bytes of the [`msg.sender`](#msg.sender) and 32 bytes of the [`msg.value`](#msg.value) -Because the function uses assembly [`return()/revert()`](#return) to terminate the call, it cannot be -called before other codes in fallback(). -Otherwise, the codes after \_fallbackLSP17Extendable() may never be reached.
@@ -1166,7 +1245,7 @@ Emitted when `tokenOwner` disables `operator` to transfer or burn `tokenId` on i ::: ```solidity -event Transfer(address operator, address indexed from, address indexed to, bytes32 indexed tokenId, bool allowNonLSP1Recipient, bytes data); +event Transfer(address operator, address indexed from, address indexed to, bytes32 indexed tokenId, bool force, bytes data); ``` Emitted when `tokenId` token is transferred from the `from` to the `to` address. @@ -1179,7 +1258,7 @@ Emitted when `tokenId` token is transferred from the `from` to the `to` address. | `from` **`indexed`** | `address` | The previous owner of the `tokenId` | | `to` **`indexed`** | `address` | The new owner of `tokenId` | | `tokenId` **`indexed`** | `bytes32` | The tokenId that was transferred | -| `allowNonLSP1Recipient` | `bool` | If the token transfer enforces the `to` recipient address to be a contract that implements the LSP1 standard or not. | +| `force` | `bool` | If the token transfer enforces the `to` recipient address to be a contract that implements the LSP1 standard or not. | | `data` | `bytes` | Any additional data the caller included by the caller during the transfer, and sent in the hooks to the `from` and `to` addresses. |
@@ -1346,7 +1425,7 @@ Reverts when trying to edit the data key `LSP4TokenSymbol` after the digital ass error LSP8CannotSendToAddressZero(); ``` -reverts when trying to send token to the zero address. +Reverts when trying to send token to the zero address.
@@ -1365,7 +1444,7 @@ reverts when trying to send token to the zero address. error LSP8CannotSendToSelf(); ``` -reverts when specifying the same address for `from` and `to` in a token transfer. +Reverts when specifying the same address for `from` and `to` in a token transfer.
@@ -1384,7 +1463,7 @@ reverts when specifying the same address for `from` and `to` in a token transfer error LSP8CannotUseAddressZeroAsOperator(); ``` -reverts when trying to set the zero address as an operator. +Reverts when trying to set the zero address as an operator.
@@ -1403,7 +1482,7 @@ reverts when trying to set the zero address as an operator. error LSP8InvalidTransferBatch(); ``` -reverts when the parameters used for `transferBatch` have different lengths. +Reverts when the parameters used for `transferBatch` have different lengths.
@@ -1422,7 +1501,7 @@ reverts when the parameters used for `transferBatch` have different lengths. error LSP8NonExistentTokenId(bytes32 tokenId); ``` -reverts when `tokenId` has not been minted. +Reverts when `tokenId` has not been minted. #### Parameters @@ -1447,7 +1526,7 @@ reverts when `tokenId` has not been minted. error LSP8NonExistingOperator(address operator, bytes32 tokenId); ``` -reverts when `operator` is not an operator for the `tokenId`. +Reverts when `operator` is not an operator for the `tokenId`. #### Parameters @@ -1473,7 +1552,7 @@ reverts when `operator` is not an operator for the `tokenId`. error LSP8NotTokenOperator(bytes32 tokenId, address caller); ``` -reverts when `caller` is not an allowed operator for `tokenId`. +Reverts when `caller` is not an allowed operator for `tokenId`. #### Parameters @@ -1499,7 +1578,7 @@ reverts when `caller` is not an allowed operator for `tokenId`. error LSP8NotTokenOwner(address tokenOwner, bytes32 tokenId, address caller); ``` -reverts when `caller` is not the `tokenOwner` of the `tokenId`. +Reverts when `caller` is not the `tokenOwner` of the `tokenId`. #### Parameters @@ -1528,7 +1607,7 @@ error LSP8NotifyTokenReceiverContractMissingLSP1Interface( ); ``` -reverts if the `tokenReceiver` does not implement LSP1 when minting or transferring tokens with `bool allowNonLSP1Recipient` set as `false`. +Reverts if the `tokenReceiver` does not implement LSP1 when minting or transferring tokens with `bool force` set as `false`. #### Parameters @@ -1553,7 +1632,7 @@ reverts if the `tokenReceiver` does not implement LSP1 when minting or transferr error LSP8NotifyTokenReceiverIsEOA(address tokenReceiver); ``` -reverts if the `tokenReceiver` is an EOA when minting or transferring tokens with `bool allowNonLSP1Recipient` set as `false`. +Reverts if the `tokenReceiver` is an EOA when minting or transferring tokens with `bool force` set as `false`. #### Parameters @@ -1578,7 +1657,7 @@ reverts if the `tokenReceiver` is an EOA when minting or transferring tokens wit error LSP8OperatorAlreadyAuthorized(address operator, bytes32 tokenId); ``` -reverts when `operator` is already authorized for the `tokenId`. +Reverts when `operator` is already authorized for the `tokenId`. #### Parameters @@ -1589,6 +1668,46 @@ reverts when `operator` is already authorized for the `tokenId`.
+### LSP8TokenContractCannotHoldValue + +:::note References + +- Specification details: [**LSP-8-IdentifiableDigitalAsset**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-8-IdentifiableDigitalAsset.md#lsp8tokencontractcannotholdvalue) +- Solidity implementation: [`LSP8Enumerable.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP8IdentifiableDigitalAsset/extensions/LSP8Enumerable.sol) +- Error signature: `LSP8TokenContractCannotHoldValue()` +- Error hash: `0x61f49442` + +::: + +```solidity +error LSP8TokenContractCannotHoldValue(); +``` + +_LSP8 contract cannot receive native tokens._ + +Error occurs when sending native tokens to the LSP8 contract without sending any data. E.g. Sending value without passing a bytes4 function selector to call a LSP17 Extension. + +
+ +### LSP8TokenIdTypeNotEditable + +:::note References + +- Specification details: [**LSP-8-IdentifiableDigitalAsset**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-8-IdentifiableDigitalAsset.md#lsp8tokenidtypenoteditable) +- Solidity implementation: [`LSP8Enumerable.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP8IdentifiableDigitalAsset/extensions/LSP8Enumerable.sol) +- Error signature: `LSP8TokenIdTypeNotEditable()` +- Error hash: `0x53bc1122` + +::: + +```solidity +error LSP8TokenIdTypeNotEditable(); +``` + +Reverts when trying to edit the data key `LSP8TokenIdType` after the identifiable digital asset contract has been deployed. The `LSP8TokenIdType` data key is located inside the ERC725Y Data key-value store of the identifiable digital asset contract. It can be set only once inside the constructor/initializer when the identifiable digital asset contract is being deployed. + +
+ ### LSP8TokenOwnerCannotBeOperator :::note References @@ -1604,7 +1723,7 @@ reverts when `operator` is already authorized for the `tokenId`. error LSP8TokenOwnerCannotBeOperator(); ``` -reverts when trying to authorize or revoke the token's owner as an operator. +Reverts when trying to authorize or revoke the token's owner as an operator.
@@ -1632,3 +1751,47 @@ reverts when there is no extension for the function selector being called with | `functionSelector` | `bytes4` | - |
+ +### OwnableCallerNotTheOwner + +:::note References + +- Specification details: [**LSP-8-IdentifiableDigitalAsset**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-8-IdentifiableDigitalAsset.md#ownablecallernottheowner) +- Solidity implementation: [`LSP8Enumerable.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP8IdentifiableDigitalAsset/extensions/LSP8Enumerable.sol) +- Error signature: `OwnableCallerNotTheOwner(address)` +- Error hash: `0xbf1169c5` + +::: + +```solidity +error OwnableCallerNotTheOwner(address callerAddress); +``` + +Reverts when only the owner is allowed to call the function. + +#### Parameters + +| Name | Type | Description | +| --------------- | :-------: | ---------------------------------------- | +| `callerAddress` | `address` | The address that tried to make the call. | + +
+ +### OwnableCannotSetZeroAddressAsOwner + +:::note References + +- Specification details: [**LSP-8-IdentifiableDigitalAsset**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-8-IdentifiableDigitalAsset.md#ownablecannotsetzeroaddressasowner) +- Solidity implementation: [`LSP8Enumerable.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP8IdentifiableDigitalAsset/extensions/LSP8Enumerable.sol) +- Error signature: `OwnableCannotSetZeroAddressAsOwner()` +- Error hash: `0x1ad8836c` + +::: + +```solidity +error OwnableCannotSetZeroAddressAsOwner(); +``` + +Reverts when trying to set `address(0)` as the contract owner when deploying the contract, initializing it or transferring ownership of the contract. + +
diff --git a/docs/contracts/LSP8IdentifiableDigitalAsset/presets/LSP8CompatibleERC721Mintable.md b/docs/contracts/LSP8IdentifiableDigitalAsset/presets/LSP8CompatibleERC721Mintable.md index a9c5b10aa..232dbd9d5 100644 --- a/docs/contracts/LSP8IdentifiableDigitalAsset/presets/LSP8CompatibleERC721Mintable.md +++ b/docs/contracts/LSP8IdentifiableDigitalAsset/presets/LSP8CompatibleERC721Mintable.md @@ -31,18 +31,24 @@ When marked as 'public', a method can be called both externally and internally, ::: ```solidity -constructor(string name_, string symbol_, address newOwner_); +constructor( + string name_, + string symbol_, + address newOwner_, + uint256 tokenIdType_ +); ``` -_Deploying a `LSP8CompatibleERC721Mintable` token contract with: token name = `name_`, token symbol = `symbol*`, and address `newOwner*` as the token contract owner.\_ +_Deploying a `LSP8CompatibleERC721Mintable` token contract with: token name = `name_`, token symbol = `symbol_`, and address `newOwner_` as the token contract owner._ #### Parameters -| Name | Type | Description | -| ----------- | :-------: | -------------------------------- | -| `name_` | `string` | The name of the token. | -| `symbol_` | `string` | The symbol of the token. | -| `newOwner_` | `address` | The owner of the token contract. | +| Name | Type | Description | +| -------------- | :-------: | -------------------------------- | +| `name_` | `string` | The name of the token. | +| `symbol_` | `string` | The symbol of the token. | +| `newOwner_` | `address` | The owner of the token contract. | +| `tokenIdType_` | `uint256` | - |
@@ -61,6 +67,21 @@ fallback() external payable;
+### receive + +:::note References + +- Specification details: [**LSP-8-IdentifiableDigitalAsset**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-8-IdentifiableDigitalAsset.md#receive) +- Solidity implementation: [`LSP8CompatibleERC721Mintable.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP8IdentifiableDigitalAsset/presets/LSP8CompatibleERC721Mintable.sol) + +::: + +```solidity +receive() external payable; +``` + +
+ ### approve :::note References @@ -76,7 +97,7 @@ fallback() external payable; function approve(address operator, uint256 tokenId) external nonpayable; ``` -_Calling `approve` function on `ILSP8CompatibleERC721` contract. Approving operator at address `operator` to transfer tokenId `tokenId` on behalf of its owner._ +_Calling `approve` function to approve operator at address `operator` to transfer tokenId `tokenId` on behalf of its owner._ Approval function compatible with ERC721 `approve(address,uint256)`. @@ -310,18 +331,22 @@ function isApprovedForAll( ) external view returns (bool); ``` +_Checking if address `operator` is approved to transfer any tokenId owned by address `owner`._ + +Compatible with ERC721 isApprovedForAll. + #### Parameters -| Name | Type | Description | -| ------------ | :-------: | ----------- | -| `tokenOwner` | `address` | - | -| `operator` | `address` | - | +| Name | Type | Description | +| ------------ | :-------: | -------------------------------- | +| `tokenOwner` | `address` | The tokenOwner address to query. | +| `operator` | `address` | The operator address to query. | #### Returns -| Name | Type | Description | -| ---- | :----: | ----------- | -| `0` | `bool` | - | +| Name | Type | Description | +| ---- | :----: | --------------------------------------------------------------------------- | +| `0` | `bool` | Returns if the `operator` is allowed to manage all of the assets of `owner` |
@@ -375,23 +400,23 @@ Returns whether `operator` address is an operator for a given `tokenId`. function mint( address to, bytes32 tokenId, - bool allowNonLSP1Recipient, + bool force, bytes data ) external nonpayable; ``` -_Minting tokenId `tokenId` for address `to` with the additional data `data` (Note: allow non-LSP1 recipient is set to `allowNonLSP1Recipient`)._ +_Minting tokenId `tokenId` for address `to` with the additional data `data` (Note: allow non-LSP1 recipient is set to `force`)._ Public [`_mint`](#_mint) function only callable by the [`owner`](#owner). #### Parameters -| Name | Type | Description | -| ----------------------- | :-------: | ------------------------------------------------------------------------------------------------------------------------------ | -| `to` | `address` | The address that will receive the minted `tokenId`. | -| `tokenId` | `bytes32` | The tokenId to mint. | -| `allowNonLSP1Recipient` | `bool` | Set to `false` to ensure that you are minting for a recipient that implements LSP1, `false` otherwise for forcing the minting. | -| `data` | `bytes` | Any addition data to be sent alongside the minting. | +| Name | Type | Description | +| --------- | :-------: | ------------------------------------------------------------------------------------------------------------------------------ | +| `to` | `address` | The address that will receive the minted `tokenId`. | +| `tokenId` | `bytes32` | The tokenId to mint. | +| `force` | `bool` | Set to `false` to ensure that you are minting for a recipient that implements LSP1, `false` otherwise for forcing the minting. | +| `data` | `bytes` | Any addition data to be sent alongside the minting. |
@@ -410,7 +435,7 @@ Public [`_mint`](#_mint) function only callable by the [`owner`](#owner). function name() external view returns (string); ``` -Returns the name of the token. +Returns the name of the token. For compatibility with clients & tools that expect ERC721. #### Returns @@ -541,7 +566,7 @@ Remove access of `operator` for a given `tokenId`, disallowing it to transfer `t :::info -This function sets the `allowNonLSP1Recipient` parameter to `true` so that EOAs and any contract can receive the `tokenId`. +This function sets the `force` parameter to `true` so that EOAs and any contract can receive the `tokenId`. ::: @@ -553,7 +578,7 @@ function safeTransferFrom( ) external nonpayable; ``` -_Calling `safeTransferFrom` function on `ILSP8CompatibleERC721` contract. Transferring tokenId `tokenId` from address `from` to address `to`._ +_Calling `safeTransferFrom` function to transfer tokenId `tokenId` from address `from` to address `to`._ Safe Transfer function without optional data from the ERC721 standard interface. @@ -580,7 +605,7 @@ Safe Transfer function without optional data from the ERC721 standard interface. :::info -This function sets the `allowNonLSP1Recipient` parameter to `true` so that EOAs and any contract can receive the `tokenId`. +This function sets the `force` parameter to `true` so that EOAs and any contract can receive the `tokenId`. ::: @@ -593,7 +618,7 @@ function safeTransferFrom( ) external nonpayable; ``` -_Calling `safeTransferFrom` function with `data` on `ILSP8CompatibleERC721` contract. Transferring tokenId `tokenId` from address `from` to address `to`._ +_Calling `safeTransferFrom` function to transfer tokenId `tokenId` from address `from` to address `to`._ Safe Transfer function with optional data from the ERC721 standard interface. @@ -623,14 +648,24 @@ Safe Transfer function with optional data from the ERC721 standard interface. function setApprovalForAll(address operator, bool approved) external nonpayable; ``` -See [`_setApprovalForAll`](#_setapprovalforall) +_Setting the "approval for all" status of operator `_operator` to `_approved` to allow it to transfer any tokenIds on behalf of `msg.sender`._ + +Enable or disable approval for a third party ("operator") to manage all of `msg.sender`'s assets. The contract MUST allow multiple operators per owner. See [`_setApprovalForAll`](#_setapprovalforall) + +
+ +**Emitted events:** + +- [`ApprovalForAll`](#approvalforall) event + +
#### Parameters -| Name | Type | Description | -| ---------- | :-------: | ----------- | -| `operator` | `address` | - | -| `approved` | `bool` | - | +| Name | Type | Description | +| ---------- | :-------: | ----------------------------------------------------------- | +| `operator` | `address` | Address to add to the set of authorized operators. | +| `approved` | `bool` | True if the operator is approved, false to revoke approval. |
@@ -780,7 +815,7 @@ Returns true if this contract implements the interface defined by `interfaceId`. function symbol() external view returns (string); ``` -Returns the symbol of the token, usually a shorter version of the name. +Returns the symbol of the token, usually a shorter version of the name. For compatibility with clients & tools that expect ERC721. #### Returns @@ -856,10 +891,10 @@ Returns the list of `tokenIds` for the `tokenOwner` address. :::note References -- Specification details: [**LSP-8-IdentifiableDigitalAsset**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-8-IdentifiableDigitalAsset.md#)) +- Specification details: [**LSP-8-IdentifiableDigitalAsset**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-8-IdentifiableDigitalAsset.md#tokenuri) - Solidity implementation: [`LSP8CompatibleERC721Mintable.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP8IdentifiableDigitalAsset/presets/LSP8CompatibleERC721Mintable.sol) -- Function signature: `)` -- Function selector: `0x59d76dc3` +- Function signature: `tokenURI(uint256)` +- Function selector: `0xc87b56dd` ::: @@ -867,6 +902,10 @@ Returns the list of `tokenIds` for the `tokenOwner` address. function tokenURI(uint256) external view returns (string); ``` +_Retrieving the token URI of tokenId `tokenId`._ + +Compatible with ERC721Metadata tokenURI. Retrieve the tokenURI for a specific `tokenId`. + #### Parameters | Name | Type | Description | @@ -875,9 +914,9 @@ function tokenURI(uint256) external view returns (string); #### Returns -| Name | Type | Description | -| ---- | :------: | ----------- | -| `0` | `string` | - | +| Name | Type | Description | +| ---- | :------: | -------------- | +| `0` | `string` | The token URI. |
@@ -922,22 +961,22 @@ function transfer( address from, address to, bytes32 tokenId, - bool allowNonLSP1Recipient, + bool force, bytes data ) external nonpayable; ``` -Transfer a given `tokenId` token from the `from` address to the `to` address. If operators are set for a specific `tokenId`, all the operators are revoked after the tokenId have been transferred. The `allowNonLSP1Recipient` parameter MUST be set to `true` when transferring tokens to Externally Owned Accounts (EOAs) or contracts that do not implement the LSP1 standard. +Transfer a given `tokenId` token from the `from` address to the `to` address. If operators are set for a specific `tokenId`, all the operators are revoked after the tokenId have been transferred. The `force` parameter MUST be set to `true` when transferring tokens to Externally Owned Accounts (EOAs) or contracts that do not implement the LSP1 standard. #### Parameters -| Name | Type | Description | -| ----------------------- | :-------: | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `from` | `address` | The address that owns the given `tokenId`. | -| `to` | `address` | The address that will receive the `tokenId`. | -| `tokenId` | `bytes32` | The token ID to transfer. | -| `allowNonLSP1Recipient` | `bool` | When set to `true`, the `to` address CAN be any addres. When set to `false`, the `to` address MUST be a contract that supports the LSP1 UniversalReceiver standard. | -| `data` | `bytes` | Any additional data the caller wants included in the emitted event, and sent in the hooks of the `from` and `to` addresses. | +| Name | Type | Description | +| --------- | :-------: | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `from` | `address` | The address that owns the given `tokenId`. | +| `to` | `address` | The address that will receive the `tokenId`. | +| `tokenId` | `bytes32` | The token ID to transfer. | +| `force` | `bool` | When set to `true`, the `to` address CAN be any addres. When set to `false`, the `to` address MUST be a contract that supports the LSP1 UniversalReceiver standard. | +| `data` | `bytes` | Any additional data the caller wants included in the emitted event, and sent in the hooks of the `from` and `to` addresses. |
@@ -957,7 +996,7 @@ function transferBatch( address[] from, address[] to, bytes32[] tokenId, - bool[] allowNonLSP1Recipient, + bool[] force, bytes[] data ) external nonpayable; ``` @@ -966,13 +1005,13 @@ Transfers multiple tokens at once based on the arrays of `from`, `to` and `token #### Parameters -| Name | Type | Description | -| ----------------------- | :---------: | ----------------------------------------------------------------------------------------------------------------------------------------- | -| `from` | `address[]` | An array of sending addresses. | -| `to` | `address[]` | An array of recipient addresses. | -| `tokenId` | `bytes32[]` | An array of token IDs to transfer. | -| `allowNonLSP1Recipient` | `bool[]` | When set to `true`, `to` may be any address. When set to `false`, `to` must be a contract that supports the LSP1 standard and not revert. | -| `data` | `bytes[]` | Any additional data the caller wants included in the emitted event, and sent in the hooks to the `from` and `to` addresses. | +| Name | Type | Description | +| --------- | :---------: | ----------------------------------------------------------------------------------------------------------------------------------------- | +| `from` | `address[]` | An array of sending addresses. | +| `to` | `address[]` | An array of recipient addresses. | +| `tokenId` | `bytes32[]` | An array of token IDs to transfer. | +| `force` | `bool[]` | When set to `true`, `to` may be any address. When set to `false`, `to` must be a contract that supports the LSP1 standard and not revert. | +| `data` | `bytes[]` | Any additional data the caller wants included in the emitted event, and sent in the hooks to the `from` and `to` addresses. |
@@ -989,7 +1028,7 @@ Transfers multiple tokens at once based on the arrays of `from`, `to` and `token :::info -This function sets the `allowNonLSP1Recipient` parameter to `true` so that EOAs and any contract can receive the `tokenId`. +This function sets the `force` parameter to `true` so that EOAs and any contract can receive the `tokenId`. ::: @@ -1001,7 +1040,7 @@ function transferFrom( ) external nonpayable; ``` -_Calling `transferFrom` function on `ILSP8CompatibleERC721` contract. Transferring tokenId `tokenId` from address `from` to address `to`._ +_Calling `transferFrom` function to transfer tokenId `tokenId` from address `from` to address `to`._ Transfer functions from the ERC721 standard interface. @@ -1097,9 +1136,12 @@ mapping(bytes32 => bytes) _store ### \_setData ```solidity -function _setData(bytes32 key, bytes value) internal nonpayable; +function _setData(bytes32 dataKey, bytes dataValue) internal nonpayable; ``` +The ERC725Y data key `_LSP8_TOKENID_TYPE_KEY` cannot be changed +once the identifiable digital asset contract has been deployed. +
### \_isOperatorOrOwner @@ -1183,7 +1225,7 @@ When `tokenId` does not exist then revert with an error. function _mint( address to, bytes32 tokenId, - bool allowNonLSP1Recipient, + bool force, bytes data ) internal nonpayable; ``` @@ -1205,7 +1247,7 @@ function _transfer( address from, address to, bytes32 tokenId, - bool allowNonLSP1Recipient, + bool force, bytes data ) internal nonpayable; ``` @@ -1218,13 +1260,38 @@ function _transfer( function _beforeTokenTransfer( address from, address to, - bytes32 tokenId + bytes32 tokenId, + bytes data ) internal nonpayable; ``` Hook that is called before any token transfer, including minting and burning. +Allows to run custom logic before updating balances and notifiying sender/recipient by overriding this function. + +#### Parameters + +| Name | Type | Description | +| --------- | :-------: | -------------------------------------- | +| `from` | `address` | The sender address | +| `to` | `address` | @param tokenId The tokenId to transfer | +| `tokenId` | `bytes32` | The tokenId to transfer | +| `data` | `bytes` | The data sent alongside the transfer | -- Allows to run custom logic before updating balances and notifiying sender/recipient by overriding this function. +
+ +### \_afterTokenTransfer + +```solidity +function _afterTokenTransfer( + address from, + address to, + bytes32 tokenId, + bytes data +) internal nonpayable; +``` + +Hook that is called after any token transfer, including minting and burning. +Allows to run custom logic after updating balances, but **before notifiying sender/recipient via LSP1** by overriding this function. #### Parameters @@ -1233,6 +1300,7 @@ Hook that is called before any token transfer, including minting and burning. | `from` | `address` | The sender address | | `to` | `address` | @param tokenId The tokenId to transfer | | `tokenId` | `bytes32` | The tokenId to transfer | +| `data` | `bytes` | The data sent alongside the transfer |
@@ -1274,13 +1342,13 @@ LSP1 interface. ```solidity function _notifyTokenReceiver( address to, - bool allowNonLSP1Recipient, + bool force, bytes lsp1Data ) internal nonpayable; ``` An attempt is made to notify the token receiver about the `tokenId` changing owners -using LSP1 interface. When allowNonLSP1Recipient is FALSE the token receiver MUST support LSP1. +using LSP1 interface. When force is FALSE the token receiver MUST support LSP1. The receiver may revert when the token being sent is not wanted.
@@ -1317,6 +1385,13 @@ Returns the extension address stored under the following data key: ### \_fallbackLSP17Extendable +:::info + +The LSP8 Token contract should not hold any native tokens. Any native tokens received by the contract +will be forwarded to the extension address mapped to the selector from `msg.sig`. + +::: + ```solidity function _fallbackLSP17Extendable( bytes callData @@ -1330,9 +1405,6 @@ Reverts if there is no extension for the function being called. If there is an extension for the function selector being called, it calls the extension with the CALL opcode, passing the [`msg.data`](#msg.data) appended with the 20 bytes of the [`msg.sender`](#msg.sender) and 32 bytes of the [`msg.value`](#msg.value) -Because the function uses assembly [`return()/revert()`](#return) to terminate the call, it cannot be -called before other codes in fallback(). -Otherwise, the codes after \_fallbackLSP17Extendable() may never be reached.
@@ -1363,7 +1435,7 @@ function _setApprovalForAll( ) internal nonpayable; ``` -Approve `operator` to operate on all tokens of `tokensOwner` +Approve `operator` to operate on all tokens of `tokensOwner`.
@@ -1389,20 +1461,18 @@ Approve `operator` to operate on all tokens of `tokensOwner` ::: ```solidity -event Approval(address indexed owner, address indexed operator, uint256 indexed tokenId); +event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId); ``` -_ERC721 `Approval` compatible event emitted. Successfully approved operator `operator` to operate on tokenId `tokenId` on behalf of token owner `owner`._ - -ERC721 `Approval` event emitted when `owner` enables `operator` for `tokenId`. To provide compatibility with indexing ERC721 events. +Emitted when the allowance of a `spender` for an `owner` is set by a call to [`approve`](#approve). `value` is the new allowance. #### Parameters -| Name | Type | Description | -| ------------------------ | :-------: | ------------------------------------------ | -| `owner` **`indexed`** | `address` | The address of the owner of the `tokenId`. | -| `operator` **`indexed`** | `address` | The address set as operator. | -| `tokenId` **`indexed`** | `uint256` | The approved tokenId. | +| Name | Type | Description | +| ------------------------ | :-------: | ----------- | +| `owner` **`indexed`** | `address` | - | +| `approved` **`indexed`** | `address` | - | +| `tokenId` **`indexed`** | `uint256` | - |
@@ -1421,17 +1491,15 @@ ERC721 `Approval` event emitted when `owner` enables `operator` for `tokenId`. T event ApprovalForAll(address indexed owner, address indexed operator, bool approved); ``` -_ERC721 `ApprovalForAll` compatible event emitted. Successfully set "approved for all" status to `approved` for operator `operator` for token owner `owner`._ - -ERC721 `ApprovalForAll` event emitted when an `operator` is enabled or disabled for an owner to transfer any of its tokenIds. The operator can manage all NFTs of the owner. +Emitted when `account` grants or revokes permission to `operator` to transfer their tokens, according to `approved`. #### Parameters -| Name | Type | Description | -| ------------------------ | :-------: | ---------------------------------------------- | -| `owner` **`indexed`** | `address` | The address of the owner of tokenIds. | -| `operator` **`indexed`** | `address` | The address set as operator. | -| `approved` | `bool` | If `operator` is approved for all NFTs or not. | +| Name | Type | Description | +| ------------------------ | :-------: | ----------- | +| `owner` **`indexed`** | `address` | - | +| `operator` **`indexed`** | `address` | - | +| `approved` | `bool` | - |
@@ -1555,11 +1623,9 @@ Emitted when `tokenOwner` disables `operator` to transfer or burn `tokenId` on i ::: ```solidity -event Transfer(address operator, address indexed from, address indexed to, bytes32 indexed tokenId, bool allowNonLSP1Recipient, bytes data); +event Transfer(address operator, address indexed from, address indexed to, bytes32 indexed tokenId, bool force, bytes data); ``` -_ERC721 `Transfer` compatible event emitted. Successfully transferred tokenId `tokenId` from `from` to `to`._ - Emitted when `tokenId` token is transferred from the `from` to the `to` address. #### Parameters @@ -1570,11 +1636,38 @@ Emitted when `tokenId` token is transferred from the `from` to the `to` address. | `from` **`indexed`** | `address` | The previous owner of the `tokenId` | | `to` **`indexed`** | `address` | The new owner of `tokenId` | | `tokenId` **`indexed`** | `bytes32` | The tokenId that was transferred | -| `allowNonLSP1Recipient` | `bool` | If the token transfer enforces the `to` recipient address to be a contract that implements the LSP1 standard or not. | +| `force` | `bool` | If the token transfer enforces the `to` recipient address to be a contract that implements the LSP1 standard or not. | | `data` | `bytes` | Any additional data the caller included by the caller during the transfer, and sent in the hooks to the `from` and `to` addresses. |
+### Transfer + +:::note References + +- Specification details: [**LSP-8-IdentifiableDigitalAsset**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-8-IdentifiableDigitalAsset.md#transfer) +- Solidity implementation: [`LSP8CompatibleERC721Mintable.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP8IdentifiableDigitalAsset/presets/LSP8CompatibleERC721Mintable.sol) +- Event signature: `Transfer(address,address,uint256)` +- Event topic hash: `0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef` + +::: + +```solidity +event Transfer(address indexed from, address indexed to, uint256 indexed tokenId); +``` + +Emitted when `value` tokens are moved from one account (`from`) to another (`to`). Note that `value` may be zero. + +#### Parameters + +| Name | Type | Description | +| ----------------------- | :-------: | ----------- | +| `from` **`indexed`** | `address` | - | +| `to` **`indexed`** | `address` | - | +| `tokenId` **`indexed`** | `uint256` | - | + +
+ ## Errors ### ERC725Y_DataKeysValuesEmptyArray @@ -1737,7 +1830,7 @@ Reverts when trying to edit the data key `LSP4TokenSymbol` after the digital ass error LSP8CannotSendToAddressZero(); ``` -reverts when trying to send token to the zero address. +Reverts when trying to send token to the zero address.
@@ -1756,7 +1849,7 @@ reverts when trying to send token to the zero address. error LSP8CannotSendToSelf(); ``` -reverts when specifying the same address for `from` and `to` in a token transfer. +Reverts when specifying the same address for `from` and `to` in a token transfer.
@@ -1775,7 +1868,7 @@ reverts when specifying the same address for `from` and `to` in a token transfer error LSP8CannotUseAddressZeroAsOperator(); ``` -reverts when trying to set the zero address as an operator. +Reverts when trying to set the zero address as an operator.
@@ -1794,7 +1887,7 @@ reverts when trying to set the zero address as an operator. error LSP8InvalidTransferBatch(); ``` -reverts when the parameters used for `transferBatch` have different lengths. +Reverts when the parameters used for `transferBatch` have different lengths.
@@ -1813,7 +1906,7 @@ reverts when the parameters used for `transferBatch` have different lengths. error LSP8NonExistentTokenId(bytes32 tokenId); ``` -reverts when `tokenId` has not been minted. +Reverts when `tokenId` has not been minted. #### Parameters @@ -1838,7 +1931,7 @@ reverts when `tokenId` has not been minted. error LSP8NonExistingOperator(address operator, bytes32 tokenId); ``` -reverts when `operator` is not an operator for the `tokenId`. +Reverts when `operator` is not an operator for the `tokenId`. #### Parameters @@ -1864,7 +1957,7 @@ reverts when `operator` is not an operator for the `tokenId`. error LSP8NotTokenOperator(bytes32 tokenId, address caller); ``` -reverts when `caller` is not an allowed operator for `tokenId`. +Reverts when `caller` is not an allowed operator for `tokenId`. #### Parameters @@ -1890,7 +1983,7 @@ reverts when `caller` is not an allowed operator for `tokenId`. error LSP8NotTokenOwner(address tokenOwner, bytes32 tokenId, address caller); ``` -reverts when `caller` is not the `tokenOwner` of the `tokenId`. +Reverts when `caller` is not the `tokenOwner` of the `tokenId`. #### Parameters @@ -1919,7 +2012,7 @@ error LSP8NotifyTokenReceiverContractMissingLSP1Interface( ); ``` -reverts if the `tokenReceiver` does not implement LSP1 when minting or transferring tokens with `bool allowNonLSP1Recipient` set as `false`. +Reverts if the `tokenReceiver` does not implement LSP1 when minting or transferring tokens with `bool force` set as `false`. #### Parameters @@ -1944,7 +2037,7 @@ reverts if the `tokenReceiver` does not implement LSP1 when minting or transferr error LSP8NotifyTokenReceiverIsEOA(address tokenReceiver); ``` -reverts if the `tokenReceiver` is an EOA when minting or transferring tokens with `bool allowNonLSP1Recipient` set as `false`. +Reverts if the `tokenReceiver` is an EOA when minting or transferring tokens with `bool force` set as `false`. #### Parameters @@ -1969,7 +2062,7 @@ reverts if the `tokenReceiver` is an EOA when minting or transferring tokens wit error LSP8OperatorAlreadyAuthorized(address operator, bytes32 tokenId); ``` -reverts when `operator` is already authorized for the `tokenId`. +Reverts when `operator` is already authorized for the `tokenId`. #### Parameters @@ -1980,6 +2073,27 @@ reverts when `operator` is already authorized for the `tokenId`.
+### LSP8TokenContractCannotHoldValue + +:::note References + +- Specification details: [**LSP-8-IdentifiableDigitalAsset**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-8-IdentifiableDigitalAsset.md#lsp8tokencontractcannotholdvalue) +- Solidity implementation: [`LSP8CompatibleERC721Mintable.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP8IdentifiableDigitalAsset/presets/LSP8CompatibleERC721Mintable.sol) +- Error signature: `LSP8TokenContractCannotHoldValue()` +- Error hash: `0x61f49442` + +::: + +```solidity +error LSP8TokenContractCannotHoldValue(); +``` + +_LSP8 contract cannot receive native tokens._ + +Error occurs when sending native tokens to the LSP8 contract without sending any data. E.g. Sending value without passing a bytes4 function selector to call a LSP17 Extension. + +
+ ### LSP8TokenIdAlreadyMinted :::note References @@ -1995,7 +2109,7 @@ reverts when `operator` is already authorized for the `tokenId`. error LSP8TokenIdAlreadyMinted(bytes32 tokenId); ``` -reverts when `tokenId` has already been minted. +Reverts when `tokenId` has already been minted. #### Parameters @@ -2005,6 +2119,25 @@ reverts when `tokenId` has already been minted.
+### LSP8TokenIdTypeNotEditable + +:::note References + +- Specification details: [**LSP-8-IdentifiableDigitalAsset**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-8-IdentifiableDigitalAsset.md#lsp8tokenidtypenoteditable) +- Solidity implementation: [`LSP8CompatibleERC721Mintable.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP8IdentifiableDigitalAsset/presets/LSP8CompatibleERC721Mintable.sol) +- Error signature: `LSP8TokenIdTypeNotEditable()` +- Error hash: `0x53bc1122` + +::: + +```solidity +error LSP8TokenIdTypeNotEditable(); +``` + +Reverts when trying to edit the data key `LSP8TokenIdType` after the identifiable digital asset contract has been deployed. The `LSP8TokenIdType` data key is located inside the ERC725Y Data key-value store of the identifiable digital asset contract. It can be set only once inside the constructor/initializer when the identifiable digital asset contract is being deployed. + +
+ ### LSP8TokenOwnerCannotBeOperator :::note References @@ -2020,7 +2153,7 @@ reverts when `tokenId` has already been minted. error LSP8TokenOwnerCannotBeOperator(); ``` -reverts when trying to authorize or revoke the token's owner as an operator. +Reverts when trying to authorize or revoke the token's owner as an operator.
@@ -2048,3 +2181,47 @@ reverts when there is no extension for the function selector being called with | `functionSelector` | `bytes4` | - |
+ +### OwnableCallerNotTheOwner + +:::note References + +- Specification details: [**LSP-8-IdentifiableDigitalAsset**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-8-IdentifiableDigitalAsset.md#ownablecallernottheowner) +- Solidity implementation: [`LSP8CompatibleERC721Mintable.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP8IdentifiableDigitalAsset/presets/LSP8CompatibleERC721Mintable.sol) +- Error signature: `OwnableCallerNotTheOwner(address)` +- Error hash: `0xbf1169c5` + +::: + +```solidity +error OwnableCallerNotTheOwner(address callerAddress); +``` + +Reverts when only the owner is allowed to call the function. + +#### Parameters + +| Name | Type | Description | +| --------------- | :-------: | ---------------------------------------- | +| `callerAddress` | `address` | The address that tried to make the call. | + +
+ +### OwnableCannotSetZeroAddressAsOwner + +:::note References + +- Specification details: [**LSP-8-IdentifiableDigitalAsset**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-8-IdentifiableDigitalAsset.md#ownablecannotsetzeroaddressasowner) +- Solidity implementation: [`LSP8CompatibleERC721Mintable.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP8IdentifiableDigitalAsset/presets/LSP8CompatibleERC721Mintable.sol) +- Error signature: `OwnableCannotSetZeroAddressAsOwner()` +- Error hash: `0x1ad8836c` + +::: + +```solidity +error OwnableCannotSetZeroAddressAsOwner(); +``` + +Reverts when trying to set `address(0)` as the contract owner when deploying the contract, initializing it or transferring ownership of the contract. + +
diff --git a/docs/contracts/LSP8IdentifiableDigitalAsset/presets/LSP8Mintable.md b/docs/contracts/LSP8IdentifiableDigitalAsset/presets/LSP8Mintable.md index 3c58a84fa..8f61176f7 100644 --- a/docs/contracts/LSP8IdentifiableDigitalAsset/presets/LSP8Mintable.md +++ b/docs/contracts/LSP8IdentifiableDigitalAsset/presets/LSP8Mintable.md @@ -31,18 +31,24 @@ When marked as 'public', a method can be called both externally and internally, ::: ```solidity -constructor(string name_, string symbol_, address newOwner_); +constructor( + string name_, + string symbol_, + address newOwner_, + uint256 tokenIdType_ +); ``` -_Deploying a `LSP8Mintable` token contract with: token name = `name_`, token symbol = `symbol*`, and address `newOwner*` as the token contract owner.\_ +_Deploying a `LSP8Mintable` token contract with: token name = `name_`, token symbol = `symbol_`, and address `newOwner_` as the token contract owner._ #### Parameters -| Name | Type | Description | -| ----------- | :-------: | -------------------------------- | -| `name_` | `string` | The name of the token. | -| `symbol_` | `string` | The symbol of the token. | -| `newOwner_` | `address` | The owner of the token contract. | +| Name | Type | Description | +| -------------- | :-------: | -------------------------------- | +| `name_` | `string` | The name of the token. | +| `symbol_` | `string` | The symbol of the token. | +| `newOwner_` | `address` | The owner of the token contract. | +| `tokenIdType_` | `uint256` | - |
@@ -81,6 +87,25 @@ This function is executed when:
+### receive + +:::note References + +- Specification details: [**LSP-8-IdentifiableDigitalAsset**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-8-IdentifiableDigitalAsset.md#receive) +- Solidity implementation: [`LSP8Mintable.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP8IdentifiableDigitalAsset/presets/LSP8Mintable.sol) + +::: + +```solidity +receive() external payable; +``` + +_LSP8 contract cannot receive native tokens._ + +Reverts whenever someone tries to send native tokens to a LSP8 contract. + +
+ ### authorizeOperator :::note References @@ -292,23 +317,23 @@ Returns whether `operator` address is an operator for a given `tokenId`. function mint( address to, bytes32 tokenId, - bool allowNonLSP1Recipient, + bool force, bytes data ) external nonpayable; ``` -_Minting tokenId `tokenId` for address `to` with the additional data `data` (Note: allow non-LSP1 recipient is set to `allowNonLSP1Recipient`)._ +_Minting tokenId `tokenId` for address `to` with the additional data `data` (Note: allow non-LSP1 recipient is set to `force`)._ Public [`_mint`](#_mint) function only callable by the [`owner`](#owner). #### Parameters -| Name | Type | Description | -| ----------------------- | :-------: | ------------------------------------------------------------------------------------------------------------------------------ | -| `to` | `address` | The address that will receive the minted `tokenId`. | -| `tokenId` | `bytes32` | The tokenId to mint. | -| `allowNonLSP1Recipient` | `bool` | Set to `false` to ensure that you are minting for a recipient that implements LSP1, `false` otherwise for forcing the minting. | -| `data` | `bytes` | Any addition data to be sent alongside the minting. | +| Name | Type | Description | +| --------- | :-------: | ------------------------------------------------------------------------------------------------------------------------------ | +| `to` | `address` | The address that will receive the minted `tokenId`. | +| `tokenId` | `bytes32` | The tokenId to mint. | +| `force` | `bool` | Set to `false` to ensure that you are minting for a recipient that implements LSP1, `false` otherwise for forcing the minting. | +| `data` | `bytes` | Any addition data to be sent alongside the minting. |
@@ -621,22 +646,22 @@ function transfer( address from, address to, bytes32 tokenId, - bool allowNonLSP1Recipient, + bool force, bytes data ) external nonpayable; ``` -Transfer a given `tokenId` token from the `from` address to the `to` address. If operators are set for a specific `tokenId`, all the operators are revoked after the tokenId have been transferred. The `allowNonLSP1Recipient` parameter MUST be set to `true` when transferring tokens to Externally Owned Accounts (EOAs) or contracts that do not implement the LSP1 standard. +Transfer a given `tokenId` token from the `from` address to the `to` address. If operators are set for a specific `tokenId`, all the operators are revoked after the tokenId have been transferred. The `force` parameter MUST be set to `true` when transferring tokens to Externally Owned Accounts (EOAs) or contracts that do not implement the LSP1 standard. #### Parameters -| Name | Type | Description | -| ----------------------- | :-------: | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `from` | `address` | The address that owns the given `tokenId`. | -| `to` | `address` | The address that will receive the `tokenId`. | -| `tokenId` | `bytes32` | The token ID to transfer. | -| `allowNonLSP1Recipient` | `bool` | When set to `true`, the `to` address CAN be any addres. When set to `false`, the `to` address MUST be a contract that supports the LSP1 UniversalReceiver standard. | -| `data` | `bytes` | Any additional data the caller wants included in the emitted event, and sent in the hooks of the `from` and `to` addresses. | +| Name | Type | Description | +| --------- | :-------: | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `from` | `address` | The address that owns the given `tokenId`. | +| `to` | `address` | The address that will receive the `tokenId`. | +| `tokenId` | `bytes32` | The token ID to transfer. | +| `force` | `bool` | When set to `true`, the `to` address CAN be any addres. When set to `false`, the `to` address MUST be a contract that supports the LSP1 UniversalReceiver standard. | +| `data` | `bytes` | Any additional data the caller wants included in the emitted event, and sent in the hooks of the `from` and `to` addresses. |
@@ -656,7 +681,7 @@ function transferBatch( address[] from, address[] to, bytes32[] tokenId, - bool[] allowNonLSP1Recipient, + bool[] force, bytes[] data ) external nonpayable; ``` @@ -665,13 +690,13 @@ Transfers multiple tokens at once based on the arrays of `from`, `to` and `token #### Parameters -| Name | Type | Description | -| ----------------------- | :---------: | ----------------------------------------------------------------------------------------------------------------------------------------- | -| `from` | `address[]` | An array of sending addresses. | -| `to` | `address[]` | An array of recipient addresses. | -| `tokenId` | `bytes32[]` | An array of token IDs to transfer. | -| `allowNonLSP1Recipient` | `bool[]` | When set to `true`, `to` may be any address. When set to `false`, `to` must be a contract that supports the LSP1 standard and not revert. | -| `data` | `bytes[]` | Any additional data the caller wants included in the emitted event, and sent in the hooks to the `from` and `to` addresses. | +| Name | Type | Description | +| --------- | :---------: | ----------------------------------------------------------------------------------------------------------------------------------------- | +| `from` | `address[]` | An array of sending addresses. | +| `to` | `address[]` | An array of recipient addresses. | +| `tokenId` | `bytes32[]` | An array of token IDs to transfer. | +| `force` | `bool[]` | When set to `true`, `to` may be any address. When set to `false`, `to` must be a contract that supports the LSP1 standard and not revert. | +| `data` | `bytes[]` | Any additional data the caller wants included in the emitted event, and sent in the hooks to the `from` and `to` addresses. |
@@ -760,7 +785,8 @@ mapping(bytes32 => bytes) _store function _setData(bytes32 dataKey, bytes dataValue) internal nonpayable; ``` -Save gas by emitting the [`DataChanged`](#datachanged) event with only the first 256 bytes of dataValue +The ERC725Y data key `_LSP8_TOKENID_TYPE_KEY` cannot be changed +once the identifiable digital asset contract has been deployed.
@@ -841,11 +867,21 @@ When `tokenId` does not exist then revert with an error. ### \_mint +:::info + +Any logic in the: + +- {\_beforeTokenTransfer} function will run before updating the balances and ownership of `tokenId`s. + +- {\_afterTokenTransfer} function will run after updating the balances and ownership of `tokenId`s, **but before notifying the recipient via LSP1**. + +::: + ```solidity function _mint( address to, bytes32 tokenId, - bool allowNonLSP1Recipient, + bool force, bytes data ) internal nonpayable; ``` @@ -862,17 +898,27 @@ Create `tokenId` by minting it and transfers it to `to`. #### Parameters -| Name | Type | Description | -| ----------------------- | :-------: | -------------------------------------------------------------------------------------------------------------------------- | -| `to` | `address` | @param tokenId The token ID to create (= mint). | -| `tokenId` | `bytes32` | The token ID to create (= mint). | -| `allowNonLSP1Recipient` | `bool` | When set to `true`, `to` may be any address. When set to `false`, `to` must be a contract that supports the LSP1 standard. | -| `data` | `bytes` | Any additional data the caller wants included in the emitted event, and sent in the hook of the `to` address. | +| Name | Type | Description | +| --------- | :-------: | -------------------------------------------------------------------------------------------------------------------------- | +| `to` | `address` | @param tokenId The token ID to create (= mint). | +| `tokenId` | `bytes32` | The token ID to create (= mint). | +| `force` | `bool` | When set to `true`, `to` may be any address. When set to `false`, `to` must be a contract that supports the LSP1 standard. | +| `data` | `bytes` | Any additional data the caller wants included in the emitted event, and sent in the hook of the `to` address. |
### \_burn +:::info + +Any logic in the: + +- {\_beforeTokenTransfer} function will run before updating the balances and ownership of `tokenId`s. + +- {\_afterTokenTransfer} function will run after updating the balances and ownership of `tokenId`s, **but before notifying the sender via LSP1**. + +::: + :::tip Hint In dApps, you can know which addresses are burning tokens by listening for the `Transfer` event and filter with the zero address as `to`. @@ -888,7 +934,6 @@ This will also clear all the operators allowed to transfer the `tokenId`. The owner of the `tokenId` will be notified about the `tokenId` being transferred through its LSP1 [`universalReceiver`](#universalreceiver) function, if it is a contract that supports the LSP1 interface. Its [`universalReceiver`](#universalreceiver) function will receive all the parameters in the calldata packed encoded. -Any logic in the [`_beforeTokenTransfer`](#_beforetokentransfer) function will run before burning `tokenId` and updating the balances.
@@ -909,6 +954,16 @@ Any logic in the [`_beforeTokenTransfer`](#_beforetokentransfer) function will r ### \_transfer +:::info + +Any logic in the: + +- {\_beforeTokenTransfer} function will run before updating the balances and ownership of `tokenId`s. + +- {\_afterTokenTransfer} function will run after updating the balances and ownership of `tokenId`s, **but before notifying the sender/recipient via LSP1**. + +::: + :::danger This internal function does not check if the sender is authorized or not to operate on the `tokenId`. @@ -920,7 +975,7 @@ function _transfer( address from, address to, bytes32 tokenId, - bool allowNonLSP1Recipient, + bool force, bytes data ) internal nonpayable; ``` @@ -929,7 +984,6 @@ Change the owner of the `tokenId` from `from` to `to`. Both the sender and recipient will be notified of the `tokenId` being transferred through their LSP1 [`universalReceiver`](#universalreceiver) function, if they are contracts that support the LSP1 interface. Their `universalReceiver` function will receive all the parameters in the calldata packed encoded. -Any logic in the [`_beforeTokenTransfer`](#_beforetokentransfer) function will run before changing the owner of `tokenId`.
@@ -941,13 +995,13 @@ Any logic in the [`_beforeTokenTransfer`](#_beforetokentransfer) function will r #### Parameters -| Name | Type | Description | -| ----------------------- | :-------: | -------------------------------------------------------------------------------------------------------------------------- | -| `from` | `address` | The sender address. | -| `to` | `address` | @param tokenId The token to transfer. | -| `tokenId` | `bytes32` | The token to transfer. | -| `allowNonLSP1Recipient` | `bool` | When set to `true`, `to` may be any address. When set to `false`, `to` must be a contract that supports the LSP1 standard. | -| `data` | `bytes` | Additional data the caller wants included in the emitted event, and sent in the hooks to `from` and `to` addresses. | +| Name | Type | Description | +| --------- | :-------: | -------------------------------------------------------------------------------------------------------------------------- | +| `from` | `address` | The sender address. | +| `to` | `address` | @param tokenId The token to transfer. | +| `tokenId` | `bytes32` | The token to transfer. | +| `force` | `bool` | When set to `true`, `to` may be any address. When set to `false`, `to` must be a contract that supports the LSP1 standard. | +| `data` | `bytes` | Additional data the caller wants included in the emitted event, and sent in the hooks to `from` and `to` addresses. |
@@ -957,13 +1011,38 @@ Any logic in the [`_beforeTokenTransfer`](#_beforetokentransfer) function will r function _beforeTokenTransfer( address from, address to, - bytes32 tokenId + bytes32 tokenId, + bytes data ) internal nonpayable; ``` Hook that is called before any token transfer, including minting and burning. +Allows to run custom logic before updating balances and notifiying sender/recipient by overriding this function. -- Allows to run custom logic before updating balances and notifiying sender/recipient by overriding this function. +#### Parameters + +| Name | Type | Description | +| --------- | :-------: | -------------------------------------- | +| `from` | `address` | The sender address | +| `to` | `address` | @param tokenId The tokenId to transfer | +| `tokenId` | `bytes32` | The tokenId to transfer | +| `data` | `bytes` | The data sent alongside the transfer | + +
+ +### \_afterTokenTransfer + +```solidity +function _afterTokenTransfer( + address from, + address to, + bytes32 tokenId, + bytes data +) internal nonpayable; +``` + +Hook that is called after any token transfer, including minting and burning. +Allows to run custom logic after updating balances, but **before notifiying sender/recipient via LSP1** by overriding this function. #### Parameters @@ -972,6 +1051,7 @@ Hook that is called before any token transfer, including minting and burning. | `from` | `address` | The sender address | | `to` | `address` | @param tokenId The tokenId to transfer | | `tokenId` | `bytes32` | The tokenId to transfer | +| `data` | `bytes` | The data sent alongside the transfer |
@@ -1013,13 +1093,13 @@ LSP1 interface. ```solidity function _notifyTokenReceiver( address to, - bool allowNonLSP1Recipient, + bool force, bytes lsp1Data ) internal nonpayable; ``` An attempt is made to notify the token receiver about the `tokenId` changing owners -using LSP1 interface. When allowNonLSP1Recipient is FALSE the token receiver MUST support LSP1. +using LSP1 interface. When force is FALSE the token receiver MUST support LSP1. The receiver may revert when the token being sent is not wanted.
@@ -1056,6 +1136,13 @@ Returns the extension address stored under the following data key: ### \_fallbackLSP17Extendable +:::info + +The LSP8 Token contract should not hold any native tokens. Any native tokens received by the contract +will be forwarded to the extension address mapped to the selector from `msg.sig`. + +::: + ```solidity function _fallbackLSP17Extendable( bytes callData @@ -1069,9 +1156,6 @@ Reverts if there is no extension for the function being called. If there is an extension for the function selector being called, it calls the extension with the CALL opcode, passing the [`msg.data`](#msg.data) appended with the 20 bytes of the [`msg.sender`](#msg.sender) and 32 bytes of the [`msg.value`](#msg.value) -Because the function uses assembly [`return()/revert()`](#return) to terminate the call, it cannot be -called before other codes in fallback(). -Otherwise, the codes after \_fallbackLSP17Extendable() may never be reached.
@@ -1197,7 +1281,7 @@ Emitted when `tokenOwner` disables `operator` to transfer or burn `tokenId` on i ::: ```solidity -event Transfer(address operator, address indexed from, address indexed to, bytes32 indexed tokenId, bool allowNonLSP1Recipient, bytes data); +event Transfer(address operator, address indexed from, address indexed to, bytes32 indexed tokenId, bool force, bytes data); ``` Emitted when `tokenId` token is transferred from the `from` to the `to` address. @@ -1210,7 +1294,7 @@ Emitted when `tokenId` token is transferred from the `from` to the `to` address. | `from` **`indexed`** | `address` | The previous owner of the `tokenId` | | `to` **`indexed`** | `address` | The new owner of `tokenId` | | `tokenId` **`indexed`** | `bytes32` | The tokenId that was transferred | -| `allowNonLSP1Recipient` | `bool` | If the token transfer enforces the `to` recipient address to be a contract that implements the LSP1 standard or not. | +| `force` | `bool` | If the token transfer enforces the `to` recipient address to be a contract that implements the LSP1 standard or not. | | `data` | `bytes` | Any additional data the caller included by the caller during the transfer, and sent in the hooks to the `from` and `to` addresses. |
@@ -1377,7 +1461,7 @@ Reverts when trying to edit the data key `LSP4TokenSymbol` after the digital ass error LSP8CannotSendToAddressZero(); ``` -reverts when trying to send token to the zero address. +Reverts when trying to send token to the zero address.
@@ -1396,7 +1480,7 @@ reverts when trying to send token to the zero address. error LSP8CannotSendToSelf(); ``` -reverts when specifying the same address for `from` and `to` in a token transfer. +Reverts when specifying the same address for `from` and `to` in a token transfer.
@@ -1415,7 +1499,7 @@ reverts when specifying the same address for `from` and `to` in a token transfer error LSP8CannotUseAddressZeroAsOperator(); ``` -reverts when trying to set the zero address as an operator. +Reverts when trying to set the zero address as an operator.
@@ -1434,7 +1518,7 @@ reverts when trying to set the zero address as an operator. error LSP8InvalidTransferBatch(); ``` -reverts when the parameters used for `transferBatch` have different lengths. +Reverts when the parameters used for `transferBatch` have different lengths.
@@ -1453,7 +1537,7 @@ reverts when the parameters used for `transferBatch` have different lengths. error LSP8NonExistentTokenId(bytes32 tokenId); ``` -reverts when `tokenId` has not been minted. +Reverts when `tokenId` has not been minted. #### Parameters @@ -1478,7 +1562,7 @@ reverts when `tokenId` has not been minted. error LSP8NonExistingOperator(address operator, bytes32 tokenId); ``` -reverts when `operator` is not an operator for the `tokenId`. +Reverts when `operator` is not an operator for the `tokenId`. #### Parameters @@ -1504,7 +1588,7 @@ reverts when `operator` is not an operator for the `tokenId`. error LSP8NotTokenOperator(bytes32 tokenId, address caller); ``` -reverts when `caller` is not an allowed operator for `tokenId`. +Reverts when `caller` is not an allowed operator for `tokenId`. #### Parameters @@ -1530,7 +1614,7 @@ reverts when `caller` is not an allowed operator for `tokenId`. error LSP8NotTokenOwner(address tokenOwner, bytes32 tokenId, address caller); ``` -reverts when `caller` is not the `tokenOwner` of the `tokenId`. +Reverts when `caller` is not the `tokenOwner` of the `tokenId`. #### Parameters @@ -1559,7 +1643,7 @@ error LSP8NotifyTokenReceiverContractMissingLSP1Interface( ); ``` -reverts if the `tokenReceiver` does not implement LSP1 when minting or transferring tokens with `bool allowNonLSP1Recipient` set as `false`. +Reverts if the `tokenReceiver` does not implement LSP1 when minting or transferring tokens with `bool force` set as `false`. #### Parameters @@ -1584,7 +1668,7 @@ reverts if the `tokenReceiver` does not implement LSP1 when minting or transferr error LSP8NotifyTokenReceiverIsEOA(address tokenReceiver); ``` -reverts if the `tokenReceiver` is an EOA when minting or transferring tokens with `bool allowNonLSP1Recipient` set as `false`. +Reverts if the `tokenReceiver` is an EOA when minting or transferring tokens with `bool force` set as `false`. #### Parameters @@ -1609,7 +1693,7 @@ reverts if the `tokenReceiver` is an EOA when minting or transferring tokens wit error LSP8OperatorAlreadyAuthorized(address operator, bytes32 tokenId); ``` -reverts when `operator` is already authorized for the `tokenId`. +Reverts when `operator` is already authorized for the `tokenId`. #### Parameters @@ -1620,6 +1704,27 @@ reverts when `operator` is already authorized for the `tokenId`.
+### LSP8TokenContractCannotHoldValue + +:::note References + +- Specification details: [**LSP-8-IdentifiableDigitalAsset**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-8-IdentifiableDigitalAsset.md#lsp8tokencontractcannotholdvalue) +- Solidity implementation: [`LSP8Mintable.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP8IdentifiableDigitalAsset/presets/LSP8Mintable.sol) +- Error signature: `LSP8TokenContractCannotHoldValue()` +- Error hash: `0x61f49442` + +::: + +```solidity +error LSP8TokenContractCannotHoldValue(); +``` + +_LSP8 contract cannot receive native tokens._ + +Error occurs when sending native tokens to the LSP8 contract without sending any data. E.g. Sending value without passing a bytes4 function selector to call a LSP17 Extension. + +
+ ### LSP8TokenIdAlreadyMinted :::note References @@ -1635,7 +1740,7 @@ reverts when `operator` is already authorized for the `tokenId`. error LSP8TokenIdAlreadyMinted(bytes32 tokenId); ``` -reverts when `tokenId` has already been minted. +Reverts when `tokenId` has already been minted. #### Parameters @@ -1645,6 +1750,25 @@ reverts when `tokenId` has already been minted.
+### LSP8TokenIdTypeNotEditable + +:::note References + +- Specification details: [**LSP-8-IdentifiableDigitalAsset**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-8-IdentifiableDigitalAsset.md#lsp8tokenidtypenoteditable) +- Solidity implementation: [`LSP8Mintable.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP8IdentifiableDigitalAsset/presets/LSP8Mintable.sol) +- Error signature: `LSP8TokenIdTypeNotEditable()` +- Error hash: `0x53bc1122` + +::: + +```solidity +error LSP8TokenIdTypeNotEditable(); +``` + +Reverts when trying to edit the data key `LSP8TokenIdType` after the identifiable digital asset contract has been deployed. The `LSP8TokenIdType` data key is located inside the ERC725Y Data key-value store of the identifiable digital asset contract. It can be set only once inside the constructor/initializer when the identifiable digital asset contract is being deployed. + +
+ ### LSP8TokenOwnerCannotBeOperator :::note References @@ -1660,7 +1784,7 @@ reverts when `tokenId` has already been minted. error LSP8TokenOwnerCannotBeOperator(); ``` -reverts when trying to authorize or revoke the token's owner as an operator. +Reverts when trying to authorize or revoke the token's owner as an operator.
@@ -1688,3 +1812,47 @@ reverts when there is no extension for the function selector being called with | `functionSelector` | `bytes4` | - |
+ +### OwnableCallerNotTheOwner + +:::note References + +- Specification details: [**LSP-8-IdentifiableDigitalAsset**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-8-IdentifiableDigitalAsset.md#ownablecallernottheowner) +- Solidity implementation: [`LSP8Mintable.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP8IdentifiableDigitalAsset/presets/LSP8Mintable.sol) +- Error signature: `OwnableCallerNotTheOwner(address)` +- Error hash: `0xbf1169c5` + +::: + +```solidity +error OwnableCallerNotTheOwner(address callerAddress); +``` + +Reverts when only the owner is allowed to call the function. + +#### Parameters + +| Name | Type | Description | +| --------------- | :-------: | ---------------------------------------- | +| `callerAddress` | `address` | The address that tried to make the call. | + +
+ +### OwnableCannotSetZeroAddressAsOwner + +:::note References + +- Specification details: [**LSP-8-IdentifiableDigitalAsset**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-8-IdentifiableDigitalAsset.md#ownablecannotsetzeroaddressasowner) +- Solidity implementation: [`LSP8Mintable.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP8IdentifiableDigitalAsset/presets/LSP8Mintable.sol) +- Error signature: `OwnableCannotSetZeroAddressAsOwner()` +- Error hash: `0x1ad8836c` + +::: + +```solidity +error OwnableCannotSetZeroAddressAsOwner(); +``` + +Reverts when trying to set `address(0)` as the contract owner when deploying the contract, initializing it or transferring ownership of the contract. + +
diff --git a/docs/contracts/LSP9Vault/LSP9Vault.md b/docs/contracts/LSP9Vault/LSP9Vault.md index c836e0130..0ad2d5255 100644 --- a/docs/contracts/LSP9Vault/LSP9Vault.md +++ b/docs/contracts/LSP9Vault/LSP9Vault.md @@ -202,7 +202,7 @@ _`msg.sender` is accepting ownership of contract: `address(this)`._ Transfer ownership of the contract from the current [`owner()`](#owner) to the [`pendingOwner()`](#pendingowner). Once this function is called: -- The current [`owner()`](#owner) will loose access to the functions restricted to the [`owner()`](#owner) only. +- The current [`owner()`](#owner) will lose access to the functions restricted to the [`owner()`](#owner) only. - The [`pendingOwner()`](#pendingowner) will gain access to the functions restricted to the [`owner()`](#owner) only. @@ -909,6 +909,12 @@ Perform low-level staticcall (operation type = 3) ### \_executeDelegateCall +:::caution Warning + +The `msg.value` should not be trusted for any method called with `operationType`: `DELEGATECALL` (4). + +::: + ```solidity function _executeDelegateCall( address target, @@ -1113,6 +1119,19 @@ Returns the extension address stored under the following data key: ### \_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) +); +``` + +::: + ```solidity function _fallbackLSP17Extendable( bytes callData @@ -1120,9 +1139,11 @@ function _fallbackLSP17Extendable( ``` Forwards the call to an extension mapped to a function selector. -Calls [`_getExtension`](#_getextension) to get the address of the extension mapped to the function selector being called on the account. If there is no extension, the `address(0)` will be returned. -Reverts if there is no extension for the function being called, except for the bytes4(0) function selector, which passes even if there is no extension for it. -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` +Calls [`_getExtension`](#_getextension) to get the address of the extension mapped to the function selector being +called on the account. If there is no extension, the `address(0)` will be returned. +Reverts if there is no extension for the function being called, except for the `bytes4(0)` function selector, which passes even if there is no extension for it. +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`](#msg.sender) and 32 bytes of the `msg.value`.
@@ -1339,7 +1360,7 @@ event UniversalReceiver(address indexed from, uint256 indexed value, bytes32 ind - Data received: `receivedData`.\* -Emitted when the [`universalReceiver`](#universalreceiver) function was called with a specific `typeId` and some `receivedData` s +Emitted when the [`universalReceiver`](#universalreceiver) function was called with a specific `typeId` and some `receivedData` #### Parameters @@ -1383,27 +1404,6 @@ Emitted when receiving native tokens. ## Errors -### CannotTransferOwnershipToSelf - -:::note References - -- Specification details: [**LSP-9-Vault**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-9-Vault.md#cannottransferownershiptoself) -- Solidity implementation: [`LSP9Vault.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP9Vault/LSP9Vault.sol) -- Error signature: `CannotTransferOwnershipToSelf()` -- Error hash: `0x43b248cd` - -::: - -```solidity -error CannotTransferOwnershipToSelf(); -``` - -_Cannot transfer ownership to the address of the contract itself._ - -Reverts when trying to transfer ownership to the `address(this)`. - -
- ### ERC725X_ContractDeploymentFailed :::note References @@ -1607,6 +1607,52 @@ Reverts when sending value to the [`setData`](#setdata) or [`setDataBatch`](#set
+### LSP14CallerNotPendingOwner + +:::note References + +- Specification details: [**LSP-9-Vault**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-9-Vault.md#lsp14callernotpendingowner) +- Solidity implementation: [`LSP9Vault.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP9Vault/LSP9Vault.sol) +- Error signature: `LSP14CallerNotPendingOwner(address)` +- Error hash: `0x451e4528` + +::: + +```solidity +error LSP14CallerNotPendingOwner(address caller); +``` + +Reverts when the `caller` that is trying to accept ownership of the contract is not the pending owner. + +#### Parameters + +| Name | Type | Description | +| -------- | :-------: | ------------------------------------------- | +| `caller` | `address` | The address that tried to accept ownership. | + +
+ +### LSP14CannotTransferOwnershipToSelf + +:::note References + +- Specification details: [**LSP-9-Vault**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-9-Vault.md#lsp14cannottransferownershiptoself) +- Solidity implementation: [`LSP9Vault.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP9Vault/LSP9Vault.sol) +- Error signature: `LSP14CannotTransferOwnershipToSelf()` +- Error hash: `0xe052a6f8` + +::: + +```solidity +error LSP14CannotTransferOwnershipToSelf(); +``` + +_Cannot transfer ownership to the address of the contract itself._ + +Reverts when trying to transfer ownership to the `address(this)`. + +
+ ### LSP14MustAcceptOwnershipInSeparateTransaction :::note References @@ -1628,6 +1674,37 @@ Reverts when pending owner accept ownership in the same transaction of transferr
+### LSP14NotInRenounceOwnershipInterval + +:::note References + +- Specification details: [**LSP-9-Vault**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-9-Vault.md#lsp14notinrenounceownershipinterval) +- Solidity implementation: [`LSP9Vault.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP9Vault/LSP9Vault.sol) +- Error signature: `LSP14NotInRenounceOwnershipInterval(uint256,uint256)` +- Error hash: `0x1b080942` + +::: + +```solidity +error LSP14NotInRenounceOwnershipInterval( + uint256 renounceOwnershipStart, + uint256 renounceOwnershipEnd +); +``` + +_Cannot confirm ownership renouncement yet. The ownership renouncement is allowed from: `renounceOwnershipStart` until: `renounceOwnershipEnd`._ + +Reverts when trying to renounce ownership before the initial confirmation delay. + +#### Parameters + +| Name | Type | Description | +| ------------------------ | :-------: | ----------------------------------------------------------------------- | +| `renounceOwnershipStart` | `uint256` | The start timestamp when one can confirm the renouncement of ownership. | +| `renounceOwnershipEnd` | `uint256` | The end timestamp when one can confirm the renouncement of ownership. | + +
+ ### LSP1DelegateNotAllowedToSetDataKey :::note References @@ -1680,33 +1757,27 @@ reverts when there is no extension for the function selector being called with
-### NotInRenounceOwnershipInterval +### OwnableCallerNotTheOwner :::note References -- Specification details: [**LSP-9-Vault**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-9-Vault.md#notinrenounceownershipinterval) +- Specification details: [**LSP-9-Vault**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-9-Vault.md#ownablecallernottheowner) - Solidity implementation: [`LSP9Vault.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP9Vault/LSP9Vault.sol) -- Error signature: `NotInRenounceOwnershipInterval(uint256,uint256)` -- Error hash: `0x8b9bf507` +- Error signature: `OwnableCallerNotTheOwner(address)` +- Error hash: `0xbf1169c5` ::: ```solidity -error NotInRenounceOwnershipInterval( - uint256 renounceOwnershipStart, - uint256 renounceOwnershipEnd -); +error OwnableCallerNotTheOwner(address callerAddress); ``` -_Cannot confirm ownership renouncement yet. The ownership renouncement is allowed from: `renounceOwnershipStart` until: `renounceOwnershipEnd`._ - -Reverts when trying to renounce ownership before the initial confirmation delay. +Reverts when only the owner is allowed to call the function. #### Parameters -| Name | Type | Description | -| ------------------------ | :-------: | ----------------------------------------------------------------------- | -| `renounceOwnershipStart` | `uint256` | The start timestamp when one can confirm the renouncement of ownership. | -| `renounceOwnershipEnd` | `uint256` | The end timestamp when one can confirm the renouncement of ownership. | +| Name | Type | Description | +| --------------- | :-------: | ---------------------------------------- | +| `callerAddress` | `address` | The address that tried to make the call. |
diff --git a/docs/contracts/UniversalProfile.md b/docs/contracts/UniversalProfile.md index ed5fcdbe8..180c43ddc 100644 --- a/docs/contracts/UniversalProfile.md +++ b/docs/contracts/UniversalProfile.md @@ -159,7 +159,7 @@ _`msg.sender` is accepting ownership of contract: `address(this)`._ Transfer ownership of the contract from the current [`owner()`](#owner) to the [`pendingOwner()`](#pendingowner). Once this function is called: -- The current [`owner()`](#owner) will loose access to the functions restricted to the [`owner()`](#owner) only. +- The current [`owner()`](#owner) will lose access to the functions restricted to the [`owner()`](#owner) only. - The [`pendingOwner()`](#pendingowner) will gain access to the functions restricted to the [`owner()`](#owner) only. @@ -293,6 +293,12 @@ Generic executor function to: ::: +:::caution Warning + +- The `msg.value` should not be trusted for any method called within the batch with `operationType`: `DELEGATECALL` (4). + +::: + ```solidity function executeBatch( uint256[] operationsType, @@ -428,7 +434,7 @@ Get in the ERC725Y storage the bytes data stored at multiple data keys `dataKeys function isValidSignature( bytes32 dataHash, bytes signature -) external view returns (bytes4 magicValue); +) external view returns (bytes4 returnedStatus); ``` _Achieves the goal of [EIP-1271] by validating signatures of smart contracts according to their own logic._ @@ -437,15 +443,15 @@ Handles two cases: 1. If the owner is an EOA, recovers an address from the hash and the signature provided: -- Returns the `magicValue` if the address recovered is the same as the owner, indicating that it was a valid signature. +- Returns the `_ERC1271_SUCCESSVALUE` if the address recovered is the same as the owner, indicating that it was a valid signature. -- If the address is different, it returns the fail value indicating that the signature is not valid. +- If the address is different, it returns the `_ERC1271_FAILVALUE` indicating that the signature is not valid. 2. If the owner is a smart contract, it forwards the call of [`isValidSignature()`](#isvalidsignature) to the owner contract: -- If the contract fails or returns the fail value, the [`isValidSignature()`](#isvalidsignature) on the account returns the fail value, indicating that the signature is not valid. +- If the contract fails or returns the `_ERC1271_FAILVALUE`, the [`isValidSignature()`](#isvalidsignature) on the account returns the `_ERC1271_FAILVALUE`, indicating that the signature is not valid. -- If the [`isValidSignature()`](#isvalidsignature) on the owner returned the `magicValue`, the [`isValidSignature()`](#isvalidsignature) on the account returns the `magicValue`, indicating that it's a valid signature. +- If the [`isValidSignature()`](#isvalidsignature) on the owner returned the `_ERC1271_SUCCESSVALUE`, the [`isValidSignature()`](#isvalidsignature) on the account returns the `_ERC1271_SUCCESSVALUE`, indicating that it's a valid signature. #### Parameters @@ -456,9 +462,9 @@ Handles two cases: #### Returns -| Name | Type | Description | -| ------------ | :------: | ----------------------------------------------------------------- | -| `magicValue` | `bytes4` | A `bytes4` value that indicates if the signature is valid or not. | +| Name | Type | Description | +| ---------------- | :------: | ----------------------------------------------------------------- | +| `returnedStatus` | `bytes4` | A `bytes4` value that indicates if the signature is valid or not. |
@@ -531,7 +537,7 @@ The address that ownership of the contract is transferred to. This address may u :::danger -Leaves the contract without an owner. Once ownership of the contract has been renounced, any functions that are restricted to be called by the owner will be permanently inaccessible, making these functions not callable anymore and unusable. +Leaves the contract without an owner. Once ownership of the contract has been renounced, any functions that are restricted to be called by the owner or an address allowed by the owner will be permanently inaccessible, making these functions not callable anymore and unusable. ::: @@ -888,6 +894,12 @@ Perform low-level staticcall (operation type = 3) ### \_executeDelegateCall +:::caution Warning + +The `msg.value` should not be trusted for any method called with `operationType`: `DELEGATECALL` (4). + +::: + ```solidity function _executeDelegateCall( address target, @@ -1092,6 +1104,19 @@ Returns the extension address stored under the following data key: ### \_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) +); +``` + +::: + ```solidity function _fallbackLSP17Extendable( bytes callData @@ -1099,9 +1124,11 @@ function _fallbackLSP17Extendable( ``` Forwards the call to an extension mapped to a function selector. -Calls [`_getExtension`](#_getextension) to get the address of the extension mapped to the function selector being called on the account. If there is no extension, the `address(0)` will be returned. -Reverts if there is no extension for the function being called, except for the bytes4(0) function selector, which passes even if there is no extension for it. -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` +Calls [`_getExtension`](#_getextension) to get the address of the extension mapped to the function selector being +called on the account. If there is no extension, the `address(0)` will be returned. +Reverts if there is no extension for the function being called, except for the `bytes4(0)` function selector, which passes even if there is no extension for it. +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`](#msg.sender) and 32 bytes of the `msg.value`.
@@ -1114,8 +1141,8 @@ function _verifyCall( ``` Calls [`lsp20VerifyCall`](#lsp20verifycall) function on the logicVerifier. -Reverts in case the value returned does not match the magic value (lsp20VerifyCall selector) -Returns whether a verification after the execution should happen based on the last byte of the magicValue +Reverts in case the value returned does not match the success value (lsp20VerifyCall selector) +Returns whether a verification after the execution should happen based on the last byte of the returnedStatus
@@ -1129,7 +1156,7 @@ function _verifyCallResult( ``` Calls [`lsp20VerifyCallResult`](#lsp20verifycallresult) function on the logicVerifier. -Reverts in case the value returned does not match the magic value (lsp20VerifyCallResult selector) +Reverts in case the value returned does not match the success value (lsp20VerifyCallResult selector)
@@ -1359,7 +1386,7 @@ event UniversalReceiver(address indexed from, uint256 indexed value, bytes32 ind - Data received: `receivedData`.\* -Emitted when the [`universalReceiver`](#universalreceiver) function was called with a specific `typeId` and some `receivedData` s +Emitted when the [`universalReceiver`](#universalreceiver) function was called with a specific `typeId` and some `receivedData` #### Parameters @@ -1403,27 +1430,6 @@ Emitted when receiving native tokens. ## Errors -### CannotTransferOwnershipToSelf - -:::note References - -- Specification details: [**UniversalProfile**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-3-UniversalProfile-Metadata.md#cannottransferownershiptoself) -- Solidity implementation: [`UniversalProfile.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/UniversalProfile.sol) -- Error signature: `CannotTransferOwnershipToSelf()` -- Error hash: `0x43b248cd` - -::: - -```solidity -error CannotTransferOwnershipToSelf(); -``` - -_Cannot transfer ownership to the address of the contract itself._ - -Reverts when trying to transfer ownership to the `address(this)`. - -
- ### ERC725X_ContractDeploymentFailed :::note References @@ -1608,6 +1614,25 @@ Reverts when the `operationTypeProvided` is none of the default operation types
+### ERC725Y_DataKeysValuesEmptyArray + +:::note References + +- Specification details: [**UniversalProfile**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-3-UniversalProfile-Metadata.md#erc725y_datakeysvaluesemptyarray) +- Solidity implementation: [`UniversalProfile.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/UniversalProfile.sol) +- Error signature: `ERC725Y_DataKeysValuesEmptyArray()` +- Error hash: `0x97da5f95` + +::: + +```solidity +error ERC725Y_DataKeysValuesEmptyArray(); +``` + +Reverts when one of the array parameter provided to [`setDataBatch`](#setdatabatch) function is an empty array. + +
+ ### ERC725Y_DataKeysValuesLengthMismatch :::note References @@ -1627,6 +1652,52 @@ Reverts when there is not the same number of elements in the `datakeys` and `dat
+### LSP14CallerNotPendingOwner + +:::note References + +- Specification details: [**UniversalProfile**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-3-UniversalProfile-Metadata.md#lsp14callernotpendingowner) +- Solidity implementation: [`UniversalProfile.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/UniversalProfile.sol) +- Error signature: `LSP14CallerNotPendingOwner(address)` +- Error hash: `0x451e4528` + +::: + +```solidity +error LSP14CallerNotPendingOwner(address caller); +``` + +Reverts when the `caller` that is trying to accept ownership of the contract is not the pending owner. + +#### Parameters + +| Name | Type | Description | +| -------- | :-------: | ------------------------------------------- | +| `caller` | `address` | The address that tried to accept ownership. | + +
+ +### LSP14CannotTransferOwnershipToSelf + +:::note References + +- Specification details: [**UniversalProfile**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-3-UniversalProfile-Metadata.md#lsp14cannottransferownershiptoself) +- Solidity implementation: [`UniversalProfile.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/UniversalProfile.sol) +- Error signature: `LSP14CannotTransferOwnershipToSelf()` +- Error hash: `0xe052a6f8` + +::: + +```solidity +error LSP14CannotTransferOwnershipToSelf(); +``` + +_Cannot transfer ownership to the address of the contract itself._ + +Reverts when trying to transfer ownership to the `address(this)`. + +
+ ### LSP14MustAcceptOwnershipInSeparateTransaction :::note References @@ -1648,47 +1719,53 @@ Reverts when pending owner accept ownership in the same transaction of transferr
-### LSP20CallingVerifierFailed +### LSP14NotInRenounceOwnershipInterval :::note References -- Specification details: [**UniversalProfile**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-3-UniversalProfile-Metadata.md#lsp20callingverifierfailed) +- Specification details: [**UniversalProfile**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-3-UniversalProfile-Metadata.md#lsp14notinrenounceownershipinterval) - Solidity implementation: [`UniversalProfile.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/UniversalProfile.sol) -- Error signature: `LSP20CallingVerifierFailed(bool)` -- Error hash: `0x8c6a8ae3` +- Error signature: `LSP14NotInRenounceOwnershipInterval(uint256,uint256)` +- Error hash: `0x1b080942` ::: ```solidity -error LSP20CallingVerifierFailed(bool postCall); +error LSP14NotInRenounceOwnershipInterval( + uint256 renounceOwnershipStart, + uint256 renounceOwnershipEnd +); ``` -reverts when the call to the owner fail with no revert reason +_Cannot confirm ownership renouncement yet. The ownership renouncement is allowed from: `renounceOwnershipStart` until: `renounceOwnershipEnd`._ + +Reverts when trying to renounce ownership before the initial confirmation delay. #### Parameters -| Name | Type | Description | -| ---------- | :----: | ---------------------------------------------------- | -| `postCall` | `bool` | True if the execution call was done, False otherwise | +| Name | Type | Description | +| ------------------------ | :-------: | ----------------------------------------------------------------------- | +| `renounceOwnershipStart` | `uint256` | The start timestamp when one can confirm the renouncement of ownership. | +| `renounceOwnershipEnd` | `uint256` | The end timestamp when one can confirm the renouncement of ownership. |
-### LSP20InvalidMagicValue +### LSP20CallVerificationFailed :::note References -- Specification details: [**UniversalProfile**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-3-UniversalProfile-Metadata.md#lsp20invalidmagicvalue) +- Specification details: [**UniversalProfile**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-3-UniversalProfile-Metadata.md#lsp20callverificationfailed) - Solidity implementation: [`UniversalProfile.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/UniversalProfile.sol) -- Error signature: `LSP20InvalidMagicValue(bool,bytes)` -- Error hash: `0xd088ec40` +- Error signature: `LSP20CallVerificationFailed(bool,bytes)` +- Error hash: `0x00c28d0f` ::: ```solidity -error LSP20InvalidMagicValue(bool postCall, bytes returnedData); +error LSP20CallVerificationFailed(bool postCall, bytes returnedData); ``` -reverts when the call to the owner does not return the magic value +reverts when the call to the owner does not return the LSP20 success value #### Parameters @@ -1699,58 +1776,77 @@ reverts when the call to the owner does not return the magic value
-### NoExtensionFoundForFunctionSelector +### LSP20CallingVerifierFailed :::note References -- Specification details: [**UniversalProfile**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-3-UniversalProfile-Metadata.md#noextensionfoundforfunctionselector) +- Specification details: [**UniversalProfile**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-3-UniversalProfile-Metadata.md#lsp20callingverifierfailed) - Solidity implementation: [`UniversalProfile.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/UniversalProfile.sol) -- Error signature: `NoExtensionFoundForFunctionSelector(bytes4)` -- Error hash: `0xbb370b2b` +- Error signature: `LSP20CallingVerifierFailed(bool)` +- Error hash: `0x8c6a8ae3` ::: ```solidity -error NoExtensionFoundForFunctionSelector(bytes4 functionSelector); +error LSP20CallingVerifierFailed(bool postCall); ``` -reverts when there is no extension for the function selector being called with +reverts when the call to the owner fail with no revert reason #### Parameters -| Name | Type | Description | -| ------------------ | :------: | ----------- | -| `functionSelector` | `bytes4` | - | +| Name | Type | Description | +| ---------- | :----: | ---------------------------------------------------- | +| `postCall` | `bool` | True if the execution call was done, False otherwise |
-### NotInRenounceOwnershipInterval +### LSP20EOACannotVerifyCall :::note References -- Specification details: [**UniversalProfile**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-3-UniversalProfile-Metadata.md#notinrenounceownershipinterval) +- Specification details: [**UniversalProfile**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-3-UniversalProfile-Metadata.md#lsp20eoacannotverifycall) - Solidity implementation: [`UniversalProfile.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/UniversalProfile.sol) -- Error signature: `NotInRenounceOwnershipInterval(uint256,uint256)` -- Error hash: `0x8b9bf507` +- Error signature: `LSP20EOACannotVerifyCall(address)` +- Error hash: `0x0c392301` ::: ```solidity -error NotInRenounceOwnershipInterval( - uint256 renounceOwnershipStart, - uint256 renounceOwnershipEnd -); +error LSP20EOACannotVerifyCall(address logicVerifier); ``` -_Cannot confirm ownership renouncement yet. The ownership renouncement is allowed from: `renounceOwnershipStart` until: `renounceOwnershipEnd`._ +Reverts when the logic verifier is an Externally Owned Account (EOA) that cannot return the LSP20 success value. -Reverts when trying to renounce ownership before the initial confirmation delay. +#### Parameters + +| Name | Type | Description | +| --------------- | :-------: | --------------------------------- | +| `logicVerifier` | `address` | The address of the logic verifier | + +
+ +### NoExtensionFoundForFunctionSelector + +:::note References + +- Specification details: [**UniversalProfile**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-3-UniversalProfile-Metadata.md#noextensionfoundforfunctionselector) +- Solidity implementation: [`UniversalProfile.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/UniversalProfile.sol) +- Error signature: `NoExtensionFoundForFunctionSelector(bytes4)` +- Error hash: `0xbb370b2b` + +::: + +```solidity +error NoExtensionFoundForFunctionSelector(bytes4 functionSelector); +``` + +reverts when there is no extension for the function selector being called with #### Parameters -| Name | Type | Description | -| ------------------------ | :-------: | ----------------------------------------------------------------------- | -| `renounceOwnershipStart` | `uint256` | The start timestamp when one can confirm the renouncement of ownership. | -| `renounceOwnershipEnd` | `uint256` | The end timestamp when one can confirm the renouncement of ownership. | +| Name | Type | Description | +| ------------------ | :------: | ----------- | +| `functionSelector` | `bytes4` | - |
diff --git a/docs/libraries/LSP1UniversalReceiver/LSP1Utils.md b/docs/libraries/LSP1UniversalReceiver/LSP1Utils.md index ff5954fab..c116ad6c8 100644 --- a/docs/libraries/LSP1UniversalReceiver/LSP1Utils.md +++ b/docs/libraries/LSP1UniversalReceiver/LSP1Utils.md @@ -47,39 +47,6 @@ supports the LSP1 interface.
-### callUniversalReceiverWithCallerInfos - -```solidity -function callUniversalReceiverWithCallerInfos( - address universalReceiverDelegate, - bytes32 typeId, - bytes receivedData, - address msgSender, - uint256 msgValue -) internal nonpayable returns (bytes); -``` - -Call a LSP1UniversalReceiverDelegate contract at `universalReceiverDelegate` address and append `msgSender` and `msgValue` -as additional informations in the calldata. - -#### Parameters - -| Name | Type | Description | -| --------------------------- | :-------: | ------------------------------------------------------------------------------------------------- | -| `universalReceiverDelegate` | `address` | The address of the LSP1UniversalReceiverDelegate to delegate the `universalReceiver` function to. | -| `typeId` | `bytes32` | A `bytes32` typeId. | -| `receivedData` | `bytes` | The data sent initially to the `universalReceiver` function. | -| `msgSender` | `address` | The address that initially called the `universalReceiver` function. | -| `msgValue` | `uint256` | The amount of native token received initially by the `universalReceiver` function. | - -#### Returns - -| Name | Type | Description | -| ---- | :-----: | ---------------------------------------------------------------- | -| `0` | `bytes` | The data returned by the LSP1UniversalReceiverDelegate contract. | - -
- ### getLSP1DelegateValue ```solidity diff --git a/docs/libraries/LSP2ERC725YJSONSchema/LSP2Utils.md b/docs/libraries/LSP2ERC725YJSONSchema/LSP2Utils.md index 516377228..b6a5f5b8e 100644 --- a/docs/libraries/LSP2ERC725YJSONSchema/LSP2Utils.md +++ b/docs/libraries/LSP2ERC725YJSONSchema/LSP2Utils.md @@ -339,72 +339,6 @@ Generate a ASSETURL value content.
-### isEncodedArray - -```solidity -function isEncodedArray(bytes data) internal pure returns (bool); -``` - -Verify if `data` is an abi-encoded array. - -#### Parameters - -| Name | Type | Description | -| ------ | :-----: | -------------------------- | -| `data` | `bytes` | The bytes value to verify. | - -#### Returns - -| Name | Type | Description | -| ---- | :----: | ------------------------------------------------------------------------ | -| `0` | `bool` | `true` if the `data` represents an abi-encoded array, `false` otherwise. | - -
- -### isEncodedArrayOfAddresses - -```solidity -function isEncodedArrayOfAddresses(bytes data) internal pure returns (bool); -``` - -Verify if `data` is an abi-encoded array of addresses (`address[]`) encoded according to the ABI specs. - -#### Parameters - -| Name | Type | Description | -| ------ | :-----: | -------------------------- | -| `data` | `bytes` | The bytes value to verify. | - -#### Returns - -| Name | Type | Description | -| ---- | :----: | ------------------------------------------------------------------------------------- | -| `0` | `bool` | `true` if the `data` represents an abi-encoded array of addresses, `false` otherwise. | - -
- -### isBytes4EncodedArray - -```solidity -function isBytes4EncodedArray(bytes data) internal pure returns (bool); -``` - -Verify if `data` is an abi-array of `bytes4` values (`bytes4[]`) encoded according to the ABI specs. - -#### Parameters - -| Name | Type | Description | -| ------ | :-----: | -------------------------- | -| `data` | `bytes` | The bytes value to verify. | - -#### Returns - -| Name | Type | Description | -| ---- | :----: | ------------------------------------------------------------------------------------ | -| `0` | `bool` | `true` if the `data` represents an abi-encoded array of `bytes4`, `false` otherwise. | - -
- ### isCompactBytesArray ```solidity diff --git a/docs/libraries/LSP6KeyManager/LSP6Utils.md b/docs/libraries/LSP6KeyManager/LSP6Utils.md index 5789174b7..5da90cae4 100644 --- a/docs/libraries/LSP6KeyManager/LSP6Utils.md +++ b/docs/libraries/LSP6KeyManager/LSP6Utils.md @@ -26,6 +26,13 @@ Internal functions cannot be called externally, whether from other smart contrac ### getPermissionsFor +:::info + +If the raw value fetched from the ERC725Y storage of `target` is not 32 bytes long, this is considered +like _"no permissions are set"_ and will return 32 x `0x00` bytes as `bytes32(0)`. + +::: + ```solidity function getPermissionsFor(contract IERC725Y target, address caller) internal view returns (bytes32); ``` diff --git a/dodoc/config.ts b/dodoc/config.ts index 942b40a47..02cb032b2 100644 --- a/dodoc/config.ts +++ b/dodoc/config.ts @@ -15,13 +15,14 @@ export const dodocConfig = { 'contracts/LSP16UniversalFactory/LSP16UniversalFactory.sol', 'contracts/LSP17ContractExtension/LSP17Extendable.sol', 'contracts/LSP17ContractExtension/LSP17Extension.sol', + 'contracts/LSP17Extensions/Extension4337.sol', + 'contracts/LSP17Extensions/OnERC721ReceivedExtension.sol', 'contracts/LSP20CallVerification/LSP20CallVerification.sol', - 'contracts/LSP23LinkedContractsDeployment/LSP23LinkedContractsFactory.sol', - 'contracts/LSP23LinkedContractsDeployment/IPostDeploymentModule.sol', + 'contracts/LSP23LinkedContractsFactory/LSP23LinkedContractsFactory.sol', + 'contracts/LSP23LinkedContractsFactory/IPostDeploymentModule.sol', 'contracts/LSP25ExecuteRelayCall/LSP25MultiChannelNonce.sol', // tokens - 'contracts/LSP4DigitalAssetMetadata/LSP4Compatibility.sol', 'contracts/LSP4DigitalAssetMetadata/LSP4DigitalAssetMetadata.sol', 'contracts/LSP7DigitalAsset/LSP7DigitalAsset.sol', 'contracts/LSP7DigitalAsset/extensions/LSP7Burnable.sol', @@ -192,21 +193,36 @@ const formatTextWithLists = (textToFormat: string) => { return formatedText; }; +const removeParameterNames = (content: string) => { + return content + .split(',') + .map((elem) => { + const trimmedElem = elem.trim(); + + if (trimmedElem.includes(' ')) { + return trimmedElem.substring(0, elem.trim().indexOf(' ')); + } else { + return trimmedElem; + } + }) + .toString(); +}; + const formatCode = (code: string, type: string) => { - let formatedCode = code + let formattedCode = code .substring(0, code.indexOf(')') + 1) .replace(`${type.toLowerCase()}`, '') .trim(); - if (!formatedCode.endsWith('()')) { - formatedCode = - formatedCode - .split(',') - .map((elem) => elem.trim().substring(0, elem.trim().indexOf(' '))) - .toString() + ')'; + if (!formattedCode.endsWith('()')) { + const start = `${formattedCode.split('(')[0]}(`; + const end = ')'; + const middle = formattedCode.replace(start, '').replace(end, ''); + + formattedCode = start + removeParameterNames(middle) + end; } - return formatedCode; + return formattedCode; }; const formatBulletPointsWithTitle = (textToFormat: string, title: string) => { diff --git a/dodoc/template.sqrl b/dodoc/template.sqrl index ae9ec093a..b71d43195 100644 --- a/dodoc/template.sqrl +++ b/dodoc/template.sqrl @@ -233,7 +233,7 @@ Internal functions cannot be called externally, whether from other smart contrac {{@foreach(it.events) => key, val}} -### {{key}} +### {{key.split('(')[0]}} :::note References{{'\n\n'}}{{@generateAdditionalEventInfo(it.name, val.code) /}}{{'\n\n'}}::: @@ -275,7 +275,7 @@ Internal functions cannot be called externally, whether from other smart contrac {{@foreach(it.errors) => key, val}} -### {{key}} +### {{key.split('(')[0]}} :::note References{{'\n\n'}}{{@generateAdditionalErrorInfo(it.name, val.code) /}}{{'\n\n'}}::: diff --git a/hardhat.config.ts b/hardhat.config.ts index 78aeec4d1..d621968aa 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -18,7 +18,10 @@ import '@nomicfoundation/hardhat-toolbox'; import 'hardhat-packager'; import 'hardhat-contract-sizer'; import 'hardhat-deploy'; + +// custom built hardhat plugins for CI import './scripts/ci/docs-generate'; +import './scripts/ci/gas_benchmark'; // Typescript types for web3.js import '@nomiclabs/hardhat-web3'; @@ -137,7 +140,6 @@ const config: HardhatUserConfig = { 'LSP11BasicSocialRecoveryInit', // ERC Compatible tokens // ------------------ - 'LSP4Compatibility', 'LSP7CompatibleERC20', 'LSP7CompatibleERC20InitAbstract', 'LSP7CompatibleERC20Mintable', diff --git a/package-lock.json b/package-lock.json index 0587a8d80..595bc0b9f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,21 +1,22 @@ { "name": "@lukso/lsp-smart-contracts", - "version": "0.11.0-rc.1", + "version": "0.12.0-rc.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@lukso/lsp-smart-contracts", - "version": "0.11.0-rc.1", + "version": "0.12.0-rc.0", "license": "Apache-2.0", "dependencies": { - "@erc725/smart-contracts": "^5.2.0", + "@account-abstraction/contracts": "^0.6.0", + "@erc725/smart-contracts": "^6.0.0", "@openzeppelin/contracts": "^4.9.2", "@openzeppelin/contracts-upgradeable": "^4.9.2", "solidity-bytes-utils": "0.8.0" }, "devDependencies": { - "@b00ste/hardhat-dodoc": "^0.3.11", + "@b00ste/hardhat-dodoc": "^0.3.15", "@defi-wonderland/smock": "^2.3.4", "@erc725/erc725.js": "0.17.2", "@lukso/eip191-signer.js": "^0.2.2", @@ -46,7 +47,7 @@ "prettier": "^2.8.8", "prettier-plugin-solidity": "^1.1.3", "rollup-plugin-esbuild": "^5.0.0", - "solhint": "^3.3.6", + "solhint": "^3.6.2", "standard-version": "^9.3.1", "ts-node": "^10.2.0", "typechain": "^8.0.0", @@ -57,6 +58,11 @@ "web3": "^1.5.2" } }, + "node_modules/@account-abstraction/contracts": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@account-abstraction/contracts/-/contracts-0.6.0.tgz", + "integrity": "sha512-8ooRJuR7XzohMDM4MV34I12Ci2bmxfE9+cixakRL7lA4BAwJKQ3ahvd8FbJa9kiwkUPCUNtj+/zxDQWYYalLMQ==" + }, "node_modules/@ampproject/remapping": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz", @@ -71,9 +77,9 @@ } }, "node_modules/@b00ste/hardhat-dodoc": { - "version": "0.3.11", - "resolved": "https://registry.npmjs.org/@b00ste/hardhat-dodoc/-/hardhat-dodoc-0.3.11.tgz", - "integrity": "sha512-GzH7yOJyux47ShCGxsGDeu3phm5iDOAAM+Vj/c8hm8oH5LT5uJ+RpvMVN+U7nNvRebJPnM2J6yc5hkRarHgRDQ==", + "version": "0.3.15", + "resolved": "https://registry.npmjs.org/@b00ste/hardhat-dodoc/-/hardhat-dodoc-0.3.15.tgz", + "integrity": "sha512-3aGhCRr09oe0meHxoE1xxk0aXbTe0XHcTbyeNEuaNBgCvN+ESAtsi/VUCL3IG9kWEgOgeXeEfHlPTGRjNx2dmg==", "dev": true, "dependencies": { "squirrelly": "^8.0.8" @@ -618,9 +624,9 @@ } }, "node_modules/@erc725/smart-contracts": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@erc725/smart-contracts/-/smart-contracts-5.2.0.tgz", - "integrity": "sha512-ML7eXO2l6GO847CKGTbSO7MxpfVpmZvVPA/4KutYwVkaZmPxB05WC2flPxUilUz7ws0S7xgyt50sPHLA1ffojA==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@erc725/smart-contracts/-/smart-contracts-6.0.0.tgz", + "integrity": "sha512-6okutGGL9xbg/MSgAof2FU1UcSNE/z3p9TORlROVGaM3gi1A6FQQ7fDqtBYkPtvHureX8yS9gP7xPt3PRbP43Q==", "dependencies": { "@openzeppelin/contracts": "^4.9.3", "@openzeppelin/contracts-upgradeable": "^4.9.3", @@ -1043,43 +1049,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@eslint/eslintrc/node_modules/ignore": { - "version": "5.2.4", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", - "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", - "dev": true, - "peer": true, - "engines": { - "node": ">= 4" - } - }, - "node_modules/@eslint/eslintrc/node_modules/import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "dev": true, - "peer": true, - "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@eslint/eslintrc/node_modules/resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, - "peer": true, - "engines": { - "node": ">=4" - } - }, "node_modules/@eslint/eslintrc/node_modules/type-fest": { "version": "0.20.2", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", @@ -3246,15 +3215,6 @@ } } }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { - "version": "5.2.4", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", - "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", - "dev": true, - "engines": { - "node": ">= 4" - } - }, "node_modules/@typescript-eslint/parser": { "version": "5.60.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.60.0.tgz", @@ -3387,15 +3347,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/ignore": { - "version": "5.2.4", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", - "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", - "dev": true, - "engines": { - "node": ">= 4" - } - }, "node_modules/@typescript-eslint/utils": { "version": "5.60.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.60.0.tgz", @@ -3587,6 +3538,7 @@ "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", "dev": true, + "peer": true, "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } @@ -4078,10 +4030,13 @@ } }, "node_modules/antlr4": { - "version": "4.7.1", - "resolved": "https://registry.npmjs.org/antlr4/-/antlr4-4.7.1.tgz", - "integrity": "sha512-haHyTW7Y9joE5MVs37P2lNYfU2RWBLfcRDD8OWldcdZm5TiCE91B5Xl1oWSwiDUSd4rlExpt2pu1fksYQjRBYQ==", - "dev": true + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/antlr4/-/antlr4-4.13.1.tgz", + "integrity": "sha512-kiXTspaRYvnIArgE97z5YVVf/cDVQABr3abFRR6mE7yesLMkgu4ujuyV/sgxafQ8wgve0DJQUJ38Z8tkgA2izA==", + "dev": true, + "engines": { + "node": ">=16" + } }, "node_modules/antlr4ts": { "version": "0.5.0-alpha.4", @@ -4296,7 +4251,6 @@ "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", "dev": true, - "peer": true, "engines": { "node": ">=8" } @@ -5015,39 +4969,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/caller-callsite": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/caller-callsite/-/caller-callsite-2.0.0.tgz", - "integrity": "sha512-JuG3qI4QOftFsZyOn1qq87fq5grLIyk1JYd5lJmdA+fG7aQ9pA/i3JIJGcO3q0MrRcHlOt1U+ZeHW8Dq9axALQ==", - "dev": true, - "dependencies": { - "callsites": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/caller-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-2.0.0.tgz", - "integrity": "sha512-MCL3sf6nCSXOwCTzvPKhN18TU7AHTvdtam8DAogxcrJ8Rjfbbg7Lgng64H9Iy+vUV6VGFClN/TyxBkAebLRR4A==", - "dev": true, - "dependencies": { - "caller-callsite": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/callsites": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz", - "integrity": "sha512-ksWePWBloaWPxJYQ8TL0JHvtci6G5QTKwQ95RcWAa/lzoAKuAOflGdAK92hpHXjkwb8zLxoLNUoNYZgVsaJzvQ==", - "dev": true, - "engines": { - "node": ">=4" - } - }, "node_modules/camelcase": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", @@ -5373,18 +5294,6 @@ "node": ">=6" } }, - "node_modules/cli-cursor": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", - "integrity": "sha512-8lgKz8LmCRYZZQDpRyT2m5rKJ08TnU4tR9FFFW2rxpxR1FzWi4PQ/NfyODchAatHaUgnSPVcx/R5w6NuTBzFiw==", - "dev": true, - "dependencies": { - "restore-cursor": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/cli-table3": { "version": "0.6.2", "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.2.tgz", @@ -5400,12 +5309,6 @@ "@colors/colors": "1.5.0" } }, - "node_modules/cli-width": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.1.tgz", - "integrity": "sha512-GRMWDxpOB6Dgk2E5Uo+3eEBvtOOlimMmpbFiKuLFnQzYDavtLFY3K5ona41jgN/WdRZtG7utuVSVTL4HbZHGkw==", - "dev": true - }, "node_modules/cliui": { "version": "7.0.4", "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", @@ -6775,53 +6678,56 @@ } }, "node_modules/cosmiconfig": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.2.1.tgz", - "integrity": "sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA==", + "version": "8.3.5", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.5.tgz", + "integrity": "sha512-A5Xry3xfS96wy2qbiLkQLAg4JUrR2wvfybxj6yqLmrUfMAvhS3MZxIP2oQn0grgYIvJqzpeTEWu4vK0t+12NNw==", "dev": true, "dependencies": { - "import-fresh": "^2.0.0", - "is-directory": "^0.3.1", - "js-yaml": "^3.13.1", - "parse-json": "^4.0.0" + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0", + "path-type": "^4.0.0" }, "engines": { - "node": ">=4" + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, - "node_modules/cosmiconfig/node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "node_modules/cosmiconfig/node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", "dev": true, "dependencies": { - "sprintf-js": "~1.0.2" - } - }, - "node_modules/cosmiconfig/node_modules/esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true, - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" }, "engines": { - "node": ">=4" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/cosmiconfig/node_modules/js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "node_modules/cosmiconfig/node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", "dev": true, - "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" + "engines": { + "node": ">=8" } }, "node_modules/crc-32": { @@ -7102,7 +7008,8 @@ "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true + "dev": true, + "peer": true }, "node_modules/defer-to-connect": { "version": "1.1.3", @@ -7181,15 +7088,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/del/node_modules/ignore": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", - "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", - "dev": true, - "engines": { - "node": ">= 4" - } - }, "node_modules/del/node_modules/rimraf": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", @@ -7346,6 +7244,7 @@ "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", "dev": true, + "peer": true, "dependencies": { "esutils": "^2.0.2" }, @@ -8007,27 +7906,6 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, - "node_modules/eslint-utils": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.4.3.tgz", - "integrity": "sha512-fbBN5W2xdY45KulGXmLHZ3c3FHfVYmKg0IrAKGOkT/464PQsx2UeIzfz1RmEci+KLm1bBaAzZAh8+/E+XAeZ8Q==", - "dev": true, - "dependencies": { - "eslint-visitor-keys": "^1.1.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/eslint-utils/node_modules/eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", - "dev": true, - "engines": { - "node": ">=4" - } - }, "node_modules/eslint-visitor-keys": { "version": "3.4.0", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.0.tgz", @@ -8177,33 +8055,6 @@ "node": ">=8" } }, - "node_modules/eslint/node_modules/ignore": { - "version": "5.2.4", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", - "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", - "dev": true, - "peer": true, - "engines": { - "node": ">= 4" - } - }, - "node_modules/eslint/node_modules/import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "dev": true, - "peer": true, - "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/eslint/node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -8272,16 +8123,6 @@ "node": ">=8" } }, - "node_modules/eslint/node_modules/resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, - "peer": true, - "engines": { - "node": ">=4" - } - }, "node_modules/eslint/node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -8384,6 +8225,7 @@ "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", "dev": true, + "peer": true, "dependencies": { "estraverse": "^5.1.0" }, @@ -8423,6 +8265,7 @@ "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", "dev": true, + "peer": true, "engines": { "node": ">=0.10.0" } @@ -10631,7 +10474,8 @@ "version": "2.0.6", "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true + "dev": true, + "peer": true }, "node_modules/fast-safe-stringify": { "version": "2.1.1", @@ -10647,18 +10491,6 @@ "reusify": "^1.0.4" } }, - "node_modules/figures": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", - "integrity": "sha512-Oa2M9atig69ZkfwiApY8F2Yy+tzMbazyvqv21R0NsSC8floSOC09BbT1ITWAdoMGQvJ/aZnR1KMwdx9tvHnTNA==", - "dev": true, - "dependencies": { - "escape-string-regexp": "^1.0.5" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/file-entry-cache": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", @@ -11875,6 +11707,7 @@ "version": "11.12.0", "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "peer": true, "engines": { "node": ">=4" } @@ -11899,16 +11732,6 @@ "node": ">=8" } }, - "node_modules/globby/node_modules/ignore": { - "version": "5.2.4", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", - "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", - "dev": true, - "peer": true, - "engines": { - "node": ">= 4" - } - }, "node_modules/globrex": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/globrex/-/globrex-0.1.2.tgz", @@ -13091,9 +12914,9 @@ ] }, "node_modules/ignore": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", - "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", + "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", "dev": true, "engines": { "node": ">= 4" @@ -13111,14 +12934,26 @@ "dev": true }, "node_modules/import-fresh": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz", - "integrity": "sha512-eZ5H8rcgYazHbKC3PG4ClHNykCSxtAhxSSEM+2mb+7evD2CKF5V7c0dNum7AdpDh0ZdICwZY9sRSn8f+KH96sg==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", "dev": true, "dependencies": { - "caller-path": "^2.0.0", - "resolve-from": "^3.0.0" + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-fresh/node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, "engines": { "node": ">=4" } @@ -13137,6 +12972,7 @@ "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", "dev": true, + "peer": true, "engines": { "node": ">=0.8.19" } @@ -13171,103 +13007,6 @@ "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", "dev": true }, - "node_modules/inquirer": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.5.2.tgz", - "integrity": "sha512-cntlB5ghuB0iuO65Ovoi8ogLHiWGs/5yNrtUcKjFhSSiVeAIVpD7koaSU9RM8mpXw5YDi9RdYXGQMaOURB7ycQ==", - "dev": true, - "dependencies": { - "ansi-escapes": "^3.2.0", - "chalk": "^2.4.2", - "cli-cursor": "^2.1.0", - "cli-width": "^2.0.0", - "external-editor": "^3.0.3", - "figures": "^2.0.0", - "lodash": "^4.17.12", - "mute-stream": "0.0.7", - "run-async": "^2.2.0", - "rxjs": "^6.4.0", - "string-width": "^2.1.0", - "strip-ansi": "^5.1.0", - "through": "^2.3.6" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/inquirer/node_modules/ansi-escapes": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz", - "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/inquirer/node_modules/ansi-regex": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", - "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/inquirer/node_modules/is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/inquirer/node_modules/string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", - "dev": true, - "dependencies": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/inquirer/node_modules/string-width/node_modules/ansi-regex": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.1.tgz", - "integrity": "sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/inquirer/node_modules/string-width/node_modules/strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha512-4XaJ2zQdCzROZDivEVIDPkcQn8LMFSa8kj8Gxb/Lnwzv9A8VctNZ+lfivC/sV3ivW8ElJTERXZoPBRrZKkNKow==", - "dev": true, - "dependencies": { - "ansi-regex": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/inquirer/node_modules/strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "dependencies": { - "ansi-regex": "^4.1.0" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/internal-slot": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.3.tgz", @@ -13482,15 +13221,6 @@ "node": ">=0.10.0" } }, - "node_modules/is-directory": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/is-directory/-/is-directory-0.3.1.tgz", - "integrity": "sha512-yVChGzahRFvbkscn2MlwGismPO12i9+znNruC5gVEntG3qu0xQMzsGg/JFbrsqDOHtHFPci+V5aP5T9I+yeKqw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/is-extendable": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", @@ -14070,7 +13800,8 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "dev": true + "dev": true, + "peer": true }, "node_modules/json-stringify-safe": { "version": "5.0.1", @@ -14414,8 +14145,7 @@ "version": "4.4.2", "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", "integrity": "sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==", - "dev": true, - "peer": true + "dev": true }, "node_modules/log-symbols": { "version": "4.1.0", @@ -15080,15 +14810,6 @@ "node": ">= 0.6" } }, - "node_modules/mimic-fn": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", - "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", - "dev": true, - "engines": { - "node": ">=4" - } - }, "node_modules/mimic-response": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", @@ -15654,12 +15375,6 @@ "imul": "^1.0.0" } }, - "node_modules/mute-stream": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz", - "integrity": "sha512-r65nCZhrbXXb6dXOACihYApHw2Q6pV0M3V0PSxd74N0+D8nzAdEAITq2oAjA1jVnKI+tGvEBUpqiMh0+rW6zDQ==", - "dev": true - }, "node_modules/nan": { "version": "2.16.0", "resolved": "https://registry.npmjs.org/nan/-/nan-2.16.0.tgz", @@ -15751,7 +15466,8 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true + "dev": true, + "peer": true }, "node_modules/natural-compare-lite": { "version": "1.4.0", @@ -16186,18 +15902,6 @@ "wrappy": "1" } }, - "node_modules/onetime": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", - "integrity": "sha512-oyyPpiMaKARvvcgip+JV+7zci5L8D1W9RZIz2l1o08AM3pfspitVWnPt3mzHcBPp12oYMTy0pqrFs/C+m3EwsQ==", - "dev": true, - "dependencies": { - "mimic-fn": "^1.0.0" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/optionator": { "version": "0.9.1", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", @@ -16434,12 +16138,6 @@ "node": ">=0.10.0" } }, - "node_modules/path-is-inside": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", - "integrity": "sha512-DUWJr3+ULp4zXmol/SZkFf3JGsS9/SIv+Y3Rt93/UjPpDpklB5f1er4O3POIbUuUJ3FXgqte2Q7SrU6zAqwk8w==", - "dev": true - }, "node_modules/path-key": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", @@ -16704,15 +16402,6 @@ "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" }, - "node_modules/progress": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", - "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", - "dev": true, - "engines": { - "node": ">=0.4.0" - } - }, "node_modules/promise": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/promise/-/promise-8.1.0.tgz", @@ -17127,15 +16816,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/regexpp": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-2.0.1.tgz", - "integrity": "sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw==", - "dev": true, - "engines": { - "node": ">=6.5.0" - } - }, "node_modules/registry-auth-token": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-4.2.2.tgz", @@ -17368,19 +17048,6 @@ "lowercase-keys": "^1.0.0" } }, - "node_modules/restore-cursor": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", - "integrity": "sha512-6IzJLuGi4+R14vwagDHX+JrXmPVtPpn4mffDJ1UdR7/Edm87fl6yi8mMBIVvFtJaNTUvjughmW4hwLhRG7gC1Q==", - "dev": true, - "dependencies": { - "onetime": "^2.0.0", - "signal-exit": "^3.0.2" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/ret": { "version": "0.1.15", "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", @@ -17749,9 +17416,9 @@ } }, "node_modules/semver": { - "version": "7.5.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.1.tgz", - "integrity": "sha512-Wvss5ivl8TMRZXXESstBA4uR5iXgEN/VC5/sOcuXdVLzcdkz4HWetIoRfG5gb5X+ij/G9rw9YoGn3QoQ8OCSpw==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -18030,7 +17697,6 @@ "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", "dev": true, - "peer": true, "dependencies": { "ansi-styles": "^4.0.0", "astral-regex": "^2.0.0", @@ -18048,7 +17714,6 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, - "peer": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -18064,7 +17729,6 @@ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, - "peer": true, "dependencies": { "color-name": "~1.1.4" }, @@ -18076,8 +17740,7 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "peer": true + "dev": true }, "node_modules/snapdragon": { "version": "0.8.2", @@ -18271,436 +17934,162 @@ } }, "node_modules/solhint": { - "version": "3.3.7", - "resolved": "https://registry.npmjs.org/solhint/-/solhint-3.3.7.tgz", - "integrity": "sha512-NjjjVmXI3ehKkb3aNtRJWw55SUVJ8HMKKodwe0HnejA+k0d2kmhw7jvpa+MCTbcEgt8IWSwx0Hu6aCo/iYOZzQ==", + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/solhint/-/solhint-3.6.2.tgz", + "integrity": "sha512-85EeLbmkcPwD+3JR7aEMKsVC9YrRSxd4qkXuMzrlf7+z2Eqdfm1wHWq1ffTuo5aDhoZxp2I9yF3QkxZOxOL7aQ==", "dev": true, "dependencies": { - "@solidity-parser/parser": "^0.14.1", - "ajv": "^6.6.1", - "antlr4": "4.7.1", - "ast-parents": "0.0.1", - "chalk": "^2.4.2", - "commander": "2.18.0", - "cosmiconfig": "^5.0.7", - "eslint": "^5.6.0", - "fast-diff": "^1.1.2", - "glob": "^7.1.3", - "ignore": "^4.0.6", - "js-yaml": "^3.12.0", - "lodash": "^4.17.11", - "semver": "^6.3.0" + "@solidity-parser/parser": "^0.16.0", + "ajv": "^6.12.6", + "antlr4": "^4.11.0", + "ast-parents": "^0.0.1", + "chalk": "^4.1.2", + "commander": "^10.0.0", + "cosmiconfig": "^8.0.0", + "fast-diff": "^1.2.0", + "glob": "^8.0.3", + "ignore": "^5.2.4", + "js-yaml": "^4.1.0", + "lodash": "^4.17.21", + "pluralize": "^8.0.0", + "semver": "^7.5.2", + "strip-ansi": "^6.0.1", + "table": "^6.8.1", + "text-table": "^0.2.0" }, "bin": { "solhint": "solhint.js" }, "optionalDependencies": { - "prettier": "^1.14.3" - } - }, - "node_modules/solhint/node_modules/acorn": { - "version": "6.4.2", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.2.tgz", - "integrity": "sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ==", - "dev": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/solhint/node_modules/ansi-regex": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.1.tgz", - "integrity": "sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/solhint/node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "dependencies": { - "sprintf-js": "~1.0.2" - } - }, - "node_modules/solhint/node_modules/astral-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", - "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/solhint/node_modules/commander": { - "version": "2.18.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.18.0.tgz", - "integrity": "sha512-6CYPa+JP2ftfRU2qkDK+UTVeQYosOg/2GbcjIcKPHfinyOLPVGXu/ovN86RP49Re5ndJK1N0kuiidFFuepc4ZQ==", - "dev": true - }, - "node_modules/solhint/node_modules/emoji-regex": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", - "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", - "dev": true - }, - "node_modules/solhint/node_modules/eslint": { - "version": "5.16.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-5.16.0.tgz", - "integrity": "sha512-S3Rz11i7c8AA5JPv7xAH+dOyq/Cu/VXHiHXBPOU1k/JAM5dXqQPt3qcrhpHSorXmrpu2g0gkIBVXAqCpzfoZIg==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.0.0", - "ajv": "^6.9.1", - "chalk": "^2.1.0", - "cross-spawn": "^6.0.5", - "debug": "^4.0.1", - "doctrine": "^3.0.0", - "eslint-scope": "^4.0.3", - "eslint-utils": "^1.3.1", - "eslint-visitor-keys": "^1.0.0", - "espree": "^5.0.1", - "esquery": "^1.0.1", - "esutils": "^2.0.2", - "file-entry-cache": "^5.0.1", - "functional-red-black-tree": "^1.0.1", - "glob": "^7.1.2", - "globals": "^11.7.0", - "ignore": "^4.0.6", - "import-fresh": "^3.0.0", - "imurmurhash": "^0.1.4", - "inquirer": "^6.2.2", - "js-yaml": "^3.13.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.3.0", - "lodash": "^4.17.11", - "minimatch": "^3.0.4", - "mkdirp": "^0.5.1", - "natural-compare": "^1.4.0", - "optionator": "^0.8.2", - "path-is-inside": "^1.0.2", - "progress": "^2.0.0", - "regexpp": "^2.0.1", - "semver": "^5.5.1", - "strip-ansi": "^4.0.0", - "strip-json-comments": "^2.0.1", - "table": "^5.2.3", - "text-table": "^0.2.0" - }, - "bin": { - "eslint": "bin/eslint.js" - }, - "engines": { - "node": "^6.14.0 || ^8.10.0 || >=9.10.0" + "prettier": "^2.8.3" } }, - "node_modules/solhint/node_modules/eslint-scope": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.3.tgz", - "integrity": "sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg==", + "node_modules/solhint/node_modules/@solidity-parser/parser": { + "version": "0.16.1", + "resolved": "https://registry.npmjs.org/@solidity-parser/parser/-/parser-0.16.1.tgz", + "integrity": "sha512-PdhRFNhbTtu3x8Axm0uYpqOy/lODYQK+MlYSgqIsq2L8SFYEHJPHNUiOTAJbDGzNjjr1/n9AcIayxafR/fWmYw==", "dev": true, "dependencies": { - "esrecurse": "^4.1.0", - "estraverse": "^4.1.1" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/solhint/node_modules/eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/solhint/node_modules/eslint/node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true, - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/solhint/node_modules/espree": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-5.0.1.tgz", - "integrity": "sha512-qWAZcWh4XE/RwzLJejfcofscgMc9CamR6Tn1+XRXNzrvUSSbiAjGOI/fggztjIi7y9VLPqnICMIPiGyr8JaZ0A==", - "dev": true, - "dependencies": { - "acorn": "^6.0.7", - "acorn-jsx": "^5.0.0", - "eslint-visitor-keys": "^1.0.0" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/solhint/node_modules/esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true, - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=4" + "antlr4ts": "^0.5.0-alpha.4" } }, - "node_modules/solhint/node_modules/estraverse": { + "node_modules/solhint/node_modules/ansi-styles": { "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/solhint/node_modules/file-entry-cache": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-5.0.1.tgz", - "integrity": "sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g==", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "dependencies": { - "flat-cache": "^2.0.1" + "color-convert": "^2.0.1" }, "engines": { - "node": ">=4" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/solhint/node_modules/flat-cache": { + "node_modules/solhint/node_modules/brace-expansion": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-2.0.1.tgz", - "integrity": "sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA==", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", "dev": true, "dependencies": { - "flatted": "^2.0.0", - "rimraf": "2.6.3", - "write": "1.0.3" - }, - "engines": { - "node": ">=4" + "balanced-match": "^1.0.0" } }, - "node_modules/solhint/node_modules/flatted": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.2.tgz", - "integrity": "sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA==", - "dev": true - }, - "node_modules/solhint/node_modules/import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "node_modules/solhint/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" }, "engines": { - "node": ">=6" + "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/solhint/node_modules/is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/solhint/node_modules/js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", - "dev": true, - "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/solhint/node_modules/levn": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", - "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==", - "dev": true, - "dependencies": { - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2" - }, - "engines": { - "node": ">= 0.8.0" + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/solhint/node_modules/optionator": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", - "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", + "node_modules/solhint/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, "dependencies": { - "deep-is": "~0.1.3", - "fast-levenshtein": "~2.0.6", - "levn": "~0.3.0", - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2", - "word-wrap": "~1.2.3" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/solhint/node_modules/prelude-ls": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==", - "dev": true, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/solhint/node_modules/prettier": { - "version": "1.19.1", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-1.19.1.tgz", - "integrity": "sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew==", - "dev": true, - "optional": true, - "bin": { - "prettier": "bin-prettier.js" + "color-name": "~1.1.4" }, "engines": { - "node": ">=4" - } - }, - "node_modules/solhint/node_modules/resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, - "engines": { - "node": ">=4" + "node": ">=7.0.0" } }, - "node_modules/solhint/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } + "node_modules/solhint/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true }, - "node_modules/solhint/node_modules/slice-ansi": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz", - "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==", + "node_modules/solhint/node_modules/commander": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", + "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", "dev": true, - "dependencies": { - "ansi-styles": "^3.2.0", - "astral-regex": "^1.0.0", - "is-fullwidth-code-point": "^2.0.0" - }, "engines": { - "node": ">=6" + "node": ">=14" } }, - "node_modules/solhint/node_modules/string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "node_modules/solhint/node_modules/glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", "dev": true, "dependencies": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" }, "engines": { - "node": ">=6" - } - }, - "node_modules/solhint/node_modules/string-width/node_modules/ansi-regex": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", - "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/solhint/node_modules/string-width/node_modules/strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "dependencies": { - "ansi-regex": "^4.1.0" + "node": ">=12" }, - "engines": { - "node": ">=6" + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/solhint/node_modules/strip-ansi": { + "node_modules/solhint/node_modules/has-flag": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha512-4XaJ2zQdCzROZDivEVIDPkcQn8LMFSa8kj8Gxb/Lnwzv9A8VctNZ+lfivC/sV3ivW8ElJTERXZoPBRrZKkNKow==", - "dev": true, - "dependencies": { - "ansi-regex": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/solhint/node_modules/strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, "engines": { - "node": ">=0.10.0" + "node": ">=8" } }, - "node_modules/solhint/node_modules/table": { - "version": "5.4.6", - "resolved": "https://registry.npmjs.org/table/-/table-5.4.6.tgz", - "integrity": "sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug==", + "node_modules/solhint/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", "dev": true, "dependencies": { - "ajv": "^6.10.2", - "lodash": "^4.17.14", - "slice-ansi": "^2.1.0", - "string-width": "^3.0.0" + "brace-expansion": "^2.0.1" }, "engines": { - "node": ">=6.0.0" + "node": ">=10" } }, - "node_modules/solhint/node_modules/type-check": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", - "integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==", + "node_modules/solhint/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "dependencies": { - "prelude-ls": "~1.1.2" + "has-flag": "^4.0.0" }, "engines": { - "node": ">= 0.8.0" + "node": ">=8" } }, "node_modules/solidity-bytes-utils": { @@ -20232,11 +19621,10 @@ } }, "node_modules/table": { - "version": "6.8.0", - "resolved": "https://registry.npmjs.org/table/-/table-6.8.0.tgz", - "integrity": "sha512-s/fitrbVeEyHKFa7mFdkuQMWlH1Wgw/yEXMt5xACT4ZpzWFluehAxRtUUQKPuWhaLAWhFcVx6w3oC8VKaUfPGA==", + "version": "6.8.1", + "resolved": "https://registry.npmjs.org/table/-/table-6.8.1.tgz", + "integrity": "sha512-Y4X9zqrCftUhMeH2EptSSERdVKt/nEdijTOacGD/97EKjhQ/Qs8RTlEGABSJNNN8lac9kheH+af7yAkEWlgneA==", "dev": true, - "peer": true, "dependencies": { "ajv": "^8.0.1", "lodash.truncate": "^4.4.2", @@ -20286,7 +19674,6 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", "dev": true, - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "json-schema-traverse": "^1.0.0", @@ -20302,8 +19689,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true, - "peer": true + "dev": true }, "node_modules/tar": { "version": "4.4.19", @@ -22605,6 +21991,7 @@ "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", "dev": true, + "peer": true, "engines": { "node": ">=0.10.0" } @@ -22699,18 +22086,6 @@ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "dev": true }, - "node_modules/write": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/write/-/write-1.0.3.tgz", - "integrity": "sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig==", - "dev": true, - "dependencies": { - "mkdirp": "^0.5.1" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/ws": { "version": "7.4.6", "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.6.tgz", @@ -22926,6 +22301,11 @@ } }, "dependencies": { + "@account-abstraction/contracts": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@account-abstraction/contracts/-/contracts-0.6.0.tgz", + "integrity": "sha512-8ooRJuR7XzohMDM4MV34I12Ci2bmxfE9+cixakRL7lA4BAwJKQ3ahvd8FbJa9kiwkUPCUNtj+/zxDQWYYalLMQ==" + }, "@ampproject/remapping": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz", @@ -22937,9 +22317,9 @@ } }, "@b00ste/hardhat-dodoc": { - "version": "0.3.11", - "resolved": "https://registry.npmjs.org/@b00ste/hardhat-dodoc/-/hardhat-dodoc-0.3.11.tgz", - "integrity": "sha512-GzH7yOJyux47ShCGxsGDeu3phm5iDOAAM+Vj/c8hm8oH5LT5uJ+RpvMVN+U7nNvRebJPnM2J6yc5hkRarHgRDQ==", + "version": "0.3.15", + "resolved": "https://registry.npmjs.org/@b00ste/hardhat-dodoc/-/hardhat-dodoc-0.3.15.tgz", + "integrity": "sha512-3aGhCRr09oe0meHxoE1xxk0aXbTe0XHcTbyeNEuaNBgCvN+ESAtsi/VUCL3IG9kWEgOgeXeEfHlPTGRjNx2dmg==", "dev": true, "requires": { "squirrelly": "^8.0.8" @@ -23362,9 +22742,9 @@ } }, "@erc725/smart-contracts": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@erc725/smart-contracts/-/smart-contracts-5.2.0.tgz", - "integrity": "sha512-ML7eXO2l6GO847CKGTbSO7MxpfVpmZvVPA/4KutYwVkaZmPxB05WC2flPxUilUz7ws0S7xgyt50sPHLA1ffojA==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@erc725/smart-contracts/-/smart-contracts-6.0.0.tgz", + "integrity": "sha512-6okutGGL9xbg/MSgAof2FU1UcSNE/z3p9TORlROVGaM3gi1A6FQQ7fDqtBYkPtvHureX8yS9gP7xPt3PRbP43Q==", "requires": { "@openzeppelin/contracts": "^4.9.3", "@openzeppelin/contracts-upgradeable": "^4.9.3", @@ -23568,31 +22948,6 @@ "type-fest": "^0.20.2" } }, - "ignore": { - "version": "5.2.4", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", - "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", - "dev": true, - "peer": true - }, - "import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "dev": true, - "peer": true, - "requires": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - } - }, - "resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, - "peer": true - }, "type-fest": { "version": "0.20.2", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", @@ -25157,14 +24512,6 @@ "natural-compare-lite": "^1.4.0", "semver": "^7.3.7", "tsutils": "^3.21.0" - }, - "dependencies": { - "ignore": { - "version": "5.2.4", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", - "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", - "dev": true - } } }, "@typescript-eslint/parser": { @@ -25236,12 +24583,6 @@ "merge2": "^1.4.1", "slash": "^3.0.0" } - }, - "ignore": { - "version": "5.2.4", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", - "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", - "dev": true } } }, @@ -25371,6 +24712,7 @@ "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", "dev": true, + "peer": true, "requires": {} }, "acorn-walk": { @@ -25735,9 +25077,9 @@ } }, "antlr4": { - "version": "4.7.1", - "resolved": "https://registry.npmjs.org/antlr4/-/antlr4-4.7.1.tgz", - "integrity": "sha512-haHyTW7Y9joE5MVs37P2lNYfU2RWBLfcRDD8OWldcdZm5TiCE91B5Xl1oWSwiDUSd4rlExpt2pu1fksYQjRBYQ==", + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/antlr4/-/antlr4-4.13.1.tgz", + "integrity": "sha512-kiXTspaRYvnIArgE97z5YVVf/cDVQABr3abFRR6mE7yesLMkgu4ujuyV/sgxafQ8wgve0DJQUJ38Z8tkgA2izA==", "dev": true }, "antlr4ts": { @@ -25914,8 +25256,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", - "dev": true, - "peer": true + "dev": true }, "async": { "version": "2.6.4", @@ -26496,30 +25837,6 @@ "get-intrinsic": "^1.0.2" } }, - "caller-callsite": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/caller-callsite/-/caller-callsite-2.0.0.tgz", - "integrity": "sha512-JuG3qI4QOftFsZyOn1qq87fq5grLIyk1JYd5lJmdA+fG7aQ9pA/i3JIJGcO3q0MrRcHlOt1U+ZeHW8Dq9axALQ==", - "dev": true, - "requires": { - "callsites": "^2.0.0" - } - }, - "caller-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-2.0.0.tgz", - "integrity": "sha512-MCL3sf6nCSXOwCTzvPKhN18TU7AHTvdtam8DAogxcrJ8Rjfbbg7Lgng64H9Iy+vUV6VGFClN/TyxBkAebLRR4A==", - "dev": true, - "requires": { - "caller-callsite": "^2.0.0" - } - }, - "callsites": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz", - "integrity": "sha512-ksWePWBloaWPxJYQ8TL0JHvtci6G5QTKwQ95RcWAa/lzoAKuAOflGdAK92hpHXjkwb8zLxoLNUoNYZgVsaJzvQ==", - "dev": true - }, "camelcase": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", @@ -26777,15 +26094,6 @@ "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", "dev": true }, - "cli-cursor": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", - "integrity": "sha512-8lgKz8LmCRYZZQDpRyT2m5rKJ08TnU4tR9FFFW2rxpxR1FzWi4PQ/NfyODchAatHaUgnSPVcx/R5w6NuTBzFiw==", - "dev": true, - "requires": { - "restore-cursor": "^2.0.0" - } - }, "cli-table3": { "version": "0.6.2", "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.2.tgz", @@ -26796,12 +26104,6 @@ "string-width": "^4.2.0" } }, - "cli-width": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.1.tgz", - "integrity": "sha512-GRMWDxpOB6Dgk2E5Uo+3eEBvtOOlimMmpbFiKuLFnQzYDavtLFY3K5ona41jgN/WdRZtG7utuVSVTL4HbZHGkw==", - "dev": true - }, "cliui": { "version": "7.0.4", "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", @@ -27870,41 +27172,34 @@ } }, "cosmiconfig": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.2.1.tgz", - "integrity": "sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA==", + "version": "8.3.5", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.5.tgz", + "integrity": "sha512-A5Xry3xfS96wy2qbiLkQLAg4JUrR2wvfybxj6yqLmrUfMAvhS3MZxIP2oQn0grgYIvJqzpeTEWu4vK0t+12NNw==", "dev": true, "requires": { - "import-fresh": "^2.0.0", - "is-directory": "^0.3.1", - "js-yaml": "^3.13.1", - "parse-json": "^4.0.0" + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0", + "path-type": "^4.0.0" }, "dependencies": { - "argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", "dev": true, "requires": { - "sprintf-js": "~1.0.2" + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" } }, - "esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", "dev": true - }, - "js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", - "dev": true, - "requires": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - } } } }, @@ -28133,7 +27428,8 @@ "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true + "dev": true, + "peer": true }, "defer-to-connect": { "version": "1.1.3", @@ -28191,12 +27487,6 @@ "slash": "^3.0.0" } }, - "ignore": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", - "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", - "dev": true - }, "rimraf": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", @@ -28321,6 +27611,7 @@ "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", "dev": true, + "peer": true, "requires": { "esutils": "^2.0.2" } @@ -28909,24 +28200,6 @@ "dev": true, "peer": true }, - "ignore": { - "version": "5.2.4", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", - "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", - "dev": true, - "peer": true - }, - "import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "dev": true, - "peer": true, - "requires": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - } - }, "locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -28971,13 +28244,6 @@ "dev": true, "peer": true }, - "resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, - "peer": true - }, "shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -29051,23 +28317,6 @@ "estraverse": "^5.2.0" } }, - "eslint-utils": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.4.3.tgz", - "integrity": "sha512-fbBN5W2xdY45KulGXmLHZ3c3FHfVYmKg0IrAKGOkT/464PQsx2UeIzfz1RmEci+KLm1bBaAzZAh8+/E+XAeZ8Q==", - "dev": true, - "requires": { - "eslint-visitor-keys": "^1.1.0" - }, - "dependencies": { - "eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", - "dev": true - } - } - }, "eslint-visitor-keys": { "version": "3.4.0", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.0.tgz", @@ -29098,6 +28347,7 @@ "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", "dev": true, + "peer": true, "requires": { "estraverse": "^5.1.0" } @@ -29127,7 +28377,8 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true + "dev": true, + "peer": true }, "etag": { "version": "1.8.1", @@ -31089,7 +30340,8 @@ "version": "2.0.6", "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true + "dev": true, + "peer": true }, "fast-safe-stringify": { "version": "2.1.1", @@ -31105,15 +30357,6 @@ "reusify": "^1.0.4" } }, - "figures": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", - "integrity": "sha512-Oa2M9atig69ZkfwiApY8F2Yy+tzMbazyvqv21R0NsSC8floSOC09BbT1ITWAdoMGQvJ/aZnR1KMwdx9tvHnTNA==", - "dev": true, - "requires": { - "escape-string-regexp": "^1.0.5" - } - }, "file-entry-cache": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", @@ -32057,7 +31300,8 @@ "globals": { "version": "11.12.0", "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==" + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "peer": true }, "globby": { "version": "10.0.2", @@ -32074,15 +31318,6 @@ "ignore": "^5.1.1", "merge2": "^1.2.3", "slash": "^3.0.0" - }, - "dependencies": { - "ignore": { - "version": "5.2.4", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", - "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", - "dev": true, - "peer": true - } } }, "globrex": { @@ -32983,9 +32218,9 @@ "dev": true }, "ignore": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", - "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", + "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", "dev": true }, "immediate": { @@ -33000,13 +32235,21 @@ "dev": true }, "import-fresh": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz", - "integrity": "sha512-eZ5H8rcgYazHbKC3PG4ClHNykCSxtAhxSSEM+2mb+7evD2CKF5V7c0dNum7AdpDh0ZdICwZY9sRSn8f+KH96sg==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", "dev": true, "requires": { - "caller-path": "^2.0.0", - "resolve-from": "^3.0.0" + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "dependencies": { + "resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true + } } }, "imul": { @@ -33019,7 +32262,8 @@ "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true + "dev": true, + "peer": true }, "indent-string": { "version": "4.0.0", @@ -33048,83 +32292,6 @@ "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", "dev": true }, - "inquirer": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.5.2.tgz", - "integrity": "sha512-cntlB5ghuB0iuO65Ovoi8ogLHiWGs/5yNrtUcKjFhSSiVeAIVpD7koaSU9RM8mpXw5YDi9RdYXGQMaOURB7ycQ==", - "dev": true, - "requires": { - "ansi-escapes": "^3.2.0", - "chalk": "^2.4.2", - "cli-cursor": "^2.1.0", - "cli-width": "^2.0.0", - "external-editor": "^3.0.3", - "figures": "^2.0.0", - "lodash": "^4.17.12", - "mute-stream": "0.0.7", - "run-async": "^2.2.0", - "rxjs": "^6.4.0", - "string-width": "^2.1.0", - "strip-ansi": "^5.1.0", - "through": "^2.3.6" - }, - "dependencies": { - "ansi-escapes": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz", - "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==", - "dev": true - }, - "ansi-regex": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", - "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", - "dev": true - }, - "string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", - "dev": true, - "requires": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.1.tgz", - "integrity": "sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==", - "dev": true - }, - "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha512-4XaJ2zQdCzROZDivEVIDPkcQn8LMFSa8kj8Gxb/Lnwzv9A8VctNZ+lfivC/sV3ivW8ElJTERXZoPBRrZKkNKow==", - "dev": true, - "requires": { - "ansi-regex": "^3.0.0" - } - } - } - }, - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "requires": { - "ansi-regex": "^4.1.0" - } - } - } - }, "internal-slot": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.3.tgz", @@ -33285,12 +32452,6 @@ } } }, - "is-directory": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/is-directory/-/is-directory-0.3.1.tgz", - "integrity": "sha512-yVChGzahRFvbkscn2MlwGismPO12i9+znNruC5gVEntG3qu0xQMzsGg/JFbrsqDOHtHFPci+V5aP5T9I+yeKqw==", - "dev": true - }, "is-extendable": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", @@ -33713,7 +32874,8 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "dev": true + "dev": true, + "peer": true }, "json-stringify-safe": { "version": "5.0.1", @@ -33977,8 +33139,7 @@ "version": "4.4.2", "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", "integrity": "sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==", - "dev": true, - "peer": true + "dev": true }, "log-symbols": { "version": "4.1.0", @@ -34499,12 +33660,6 @@ "mime-db": "1.52.0" } }, - "mimic-fn": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", - "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", - "dev": true - }, "mimic-response": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", @@ -34937,12 +34092,6 @@ "imul": "^1.0.0" } }, - "mute-stream": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz", - "integrity": "sha512-r65nCZhrbXXb6dXOACihYApHw2Q6pV0M3V0PSxd74N0+D8nzAdEAITq2oAjA1jVnKI+tGvEBUpqiMh0+rW6zDQ==", - "dev": true - }, "nan": { "version": "2.16.0", "resolved": "https://registry.npmjs.org/nan/-/nan-2.16.0.tgz", @@ -35018,7 +34167,8 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true + "dev": true, + "peer": true }, "natural-compare-lite": { "version": "1.4.0", @@ -35359,15 +34509,6 @@ "wrappy": "1" } }, - "onetime": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", - "integrity": "sha512-oyyPpiMaKARvvcgip+JV+7zci5L8D1W9RZIz2l1o08AM3pfspitVWnPt3mzHcBPp12oYMTy0pqrFs/C+m3EwsQ==", - "dev": true, - "requires": { - "mimic-fn": "^1.0.0" - } - }, "optionator": { "version": "0.9.1", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", @@ -35551,12 +34692,6 @@ "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", "dev": true }, - "path-is-inside": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", - "integrity": "sha512-DUWJr3+ULp4zXmol/SZkFf3JGsS9/SIv+Y3Rt93/UjPpDpklB5f1er4O3POIbUuUJ3FXgqte2Q7SrU6zAqwk8w==", - "dev": true - }, "path-key": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", @@ -35731,12 +34866,6 @@ "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" }, - "progress": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", - "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", - "dev": true - }, "promise": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/promise/-/promise-8.1.0.tgz", @@ -36069,12 +35198,6 @@ "functions-have-names": "^1.2.2" } }, - "regexpp": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-2.0.1.tgz", - "integrity": "sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw==", - "dev": true - }, "registry-auth-token": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-4.2.2.tgz", @@ -36251,16 +35374,6 @@ "lowercase-keys": "^1.0.0" } }, - "restore-cursor": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", - "integrity": "sha512-6IzJLuGi4+R14vwagDHX+JrXmPVtPpn4mffDJ1UdR7/Edm87fl6yi8mMBIVvFtJaNTUvjughmW4hwLhRG7gC1Q==", - "dev": true, - "requires": { - "onetime": "^2.0.0", - "signal-exit": "^3.0.2" - } - }, "ret": { "version": "0.1.15", "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", @@ -36524,9 +35637,9 @@ "integrity": "sha512-O4OZEaNtkMd/K0i6js9SL+gqy0ZCBMgUvlSqHKi4IBdjhe7wB8pwztUk1BbZ1fmrvpwFrPbHzqd2w5pTcJH6LA==" }, "semver": { - "version": "7.5.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.1.tgz", - "integrity": "sha512-Wvss5ivl8TMRZXXESstBA4uR5iXgEN/VC5/sOcuXdVLzcdkz4HWetIoRfG5gb5X+ij/G9rw9YoGn3QoQ8OCSpw==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, "requires": { "lru-cache": "^6.0.0" @@ -36749,7 +35862,6 @@ "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", "dev": true, - "peer": true, "requires": { "ansi-styles": "^4.0.0", "astral-regex": "^2.0.0", @@ -36761,7 +35873,6 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, - "peer": true, "requires": { "color-convert": "^2.0.1" } @@ -36771,7 +35882,6 @@ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, - "peer": true, "requires": { "color-name": "~1.1.4" } @@ -36780,8 +35890,7 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "peer": true + "dev": true } } }, @@ -36949,332 +36058,124 @@ } }, "solhint": { - "version": "3.3.7", - "resolved": "https://registry.npmjs.org/solhint/-/solhint-3.3.7.tgz", - "integrity": "sha512-NjjjVmXI3ehKkb3aNtRJWw55SUVJ8HMKKodwe0HnejA+k0d2kmhw7jvpa+MCTbcEgt8IWSwx0Hu6aCo/iYOZzQ==", + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/solhint/-/solhint-3.6.2.tgz", + "integrity": "sha512-85EeLbmkcPwD+3JR7aEMKsVC9YrRSxd4qkXuMzrlf7+z2Eqdfm1wHWq1ffTuo5aDhoZxp2I9yF3QkxZOxOL7aQ==", "dev": true, "requires": { - "@solidity-parser/parser": "^0.14.1", - "ajv": "^6.6.1", - "antlr4": "4.7.1", - "ast-parents": "0.0.1", - "chalk": "^2.4.2", - "commander": "2.18.0", - "cosmiconfig": "^5.0.7", - "eslint": "^5.6.0", - "fast-diff": "^1.1.2", - "glob": "^7.1.3", - "ignore": "^4.0.6", - "js-yaml": "^3.12.0", - "lodash": "^4.17.11", - "prettier": "^1.14.3", - "semver": "^6.3.0" + "@solidity-parser/parser": "^0.16.0", + "ajv": "^6.12.6", + "antlr4": "^4.11.0", + "ast-parents": "^0.0.1", + "chalk": "^4.1.2", + "commander": "^10.0.0", + "cosmiconfig": "^8.0.0", + "fast-diff": "^1.2.0", + "glob": "^8.0.3", + "ignore": "^5.2.4", + "js-yaml": "^4.1.0", + "lodash": "^4.17.21", + "pluralize": "^8.0.0", + "prettier": "^2.8.3", + "semver": "^7.5.2", + "strip-ansi": "^6.0.1", + "table": "^6.8.1", + "text-table": "^0.2.0" }, "dependencies": { - "acorn": { - "version": "6.4.2", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.2.tgz", - "integrity": "sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ==", - "dev": true - }, - "ansi-regex": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.1.tgz", - "integrity": "sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==", - "dev": true - }, - "argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "requires": { - "sprintf-js": "~1.0.2" - } - }, - "astral-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", - "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", - "dev": true - }, - "commander": { - "version": "2.18.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.18.0.tgz", - "integrity": "sha512-6CYPa+JP2ftfRU2qkDK+UTVeQYosOg/2GbcjIcKPHfinyOLPVGXu/ovN86RP49Re5ndJK1N0kuiidFFuepc4ZQ==", - "dev": true - }, - "emoji-regex": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", - "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", - "dev": true - }, - "eslint": { - "version": "5.16.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-5.16.0.tgz", - "integrity": "sha512-S3Rz11i7c8AA5JPv7xAH+dOyq/Cu/VXHiHXBPOU1k/JAM5dXqQPt3qcrhpHSorXmrpu2g0gkIBVXAqCpzfoZIg==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.0.0", - "ajv": "^6.9.1", - "chalk": "^2.1.0", - "cross-spawn": "^6.0.5", - "debug": "^4.0.1", - "doctrine": "^3.0.0", - "eslint-scope": "^4.0.3", - "eslint-utils": "^1.3.1", - "eslint-visitor-keys": "^1.0.0", - "espree": "^5.0.1", - "esquery": "^1.0.1", - "esutils": "^2.0.2", - "file-entry-cache": "^5.0.1", - "functional-red-black-tree": "^1.0.1", - "glob": "^7.1.2", - "globals": "^11.7.0", - "ignore": "^4.0.6", - "import-fresh": "^3.0.0", - "imurmurhash": "^0.1.4", - "inquirer": "^6.2.2", - "js-yaml": "^3.13.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.3.0", - "lodash": "^4.17.11", - "minimatch": "^3.0.4", - "mkdirp": "^0.5.1", - "natural-compare": "^1.4.0", - "optionator": "^0.8.2", - "path-is-inside": "^1.0.2", - "progress": "^2.0.0", - "regexpp": "^2.0.1", - "semver": "^5.5.1", - "strip-ansi": "^4.0.0", - "strip-json-comments": "^2.0.1", - "table": "^5.2.3", - "text-table": "^0.2.0" - }, - "dependencies": { - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true - } - } - }, - "eslint-scope": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.3.tgz", - "integrity": "sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg==", - "dev": true, - "requires": { - "esrecurse": "^4.1.0", - "estraverse": "^4.1.1" - } - }, - "eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", - "dev": true - }, - "espree": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-5.0.1.tgz", - "integrity": "sha512-qWAZcWh4XE/RwzLJejfcofscgMc9CamR6Tn1+XRXNzrvUSSbiAjGOI/fggztjIi7y9VLPqnICMIPiGyr8JaZ0A==", + "@solidity-parser/parser": { + "version": "0.16.1", + "resolved": "https://registry.npmjs.org/@solidity-parser/parser/-/parser-0.16.1.tgz", + "integrity": "sha512-PdhRFNhbTtu3x8Axm0uYpqOy/lODYQK+MlYSgqIsq2L8SFYEHJPHNUiOTAJbDGzNjjr1/n9AcIayxafR/fWmYw==", "dev": true, "requires": { - "acorn": "^6.0.7", - "acorn-jsx": "^5.0.0", - "eslint-visitor-keys": "^1.0.0" + "antlr4ts": "^0.5.0-alpha.4" } }, - "esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true - }, - "estraverse": { + "ansi-styles": { "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true - }, - "file-entry-cache": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-5.0.1.tgz", - "integrity": "sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g==", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "requires": { - "flat-cache": "^2.0.1" + "color-convert": "^2.0.1" } }, - "flat-cache": { + "brace-expansion": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-2.0.1.tgz", - "integrity": "sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA==", - "dev": true, - "requires": { - "flatted": "^2.0.0", - "rimraf": "2.6.3", - "write": "1.0.3" - } - }, - "flatted": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.2.tgz", - "integrity": "sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA==", - "dev": true - }, - "import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "dev": true, - "requires": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - } - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", - "dev": true - }, - "js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", "dev": true, "requires": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" + "balanced-match": "^1.0.0" } }, - "levn": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", - "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==", + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, "requires": { - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" } }, - "optionator": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", - "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, "requires": { - "deep-is": "~0.1.3", - "fast-levenshtein": "~2.0.6", - "levn": "~0.3.0", - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2", - "word-wrap": "~1.2.3" + "color-name": "~1.1.4" } }, - "prelude-ls": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==", - "dev": true - }, - "prettier": { - "version": "1.19.1", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-1.19.1.tgz", - "integrity": "sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew==", - "dev": true, - "optional": true - }, - "resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "commander": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", + "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", "dev": true }, - "slice-ansi": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz", - "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.0", - "astral-regex": "^1.0.0", - "is-fullwidth-code-point": "^2.0.0" - } - }, - "string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", "dev": true, "requires": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" - }, - "dependencies": { - "ansi-regex": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", - "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", - "dev": true - }, - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "requires": { - "ansi-regex": "^4.1.0" - } - } + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" } }, - "strip-ansi": { + "has-flag": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha512-4XaJ2zQdCzROZDivEVIDPkcQn8LMFSa8kj8Gxb/Lnwzv9A8VctNZ+lfivC/sV3ivW8ElJTERXZoPBRrZKkNKow==", - "dev": true, - "requires": { - "ansi-regex": "^3.0.0" - } - }, - "strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, - "table": { - "version": "5.4.6", - "resolved": "https://registry.npmjs.org/table/-/table-5.4.6.tgz", - "integrity": "sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug==", + "minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", "dev": true, "requires": { - "ajv": "^6.10.2", - "lodash": "^4.17.14", - "slice-ansi": "^2.1.0", - "string-width": "^3.0.0" + "brace-expansion": "^2.0.1" } }, - "type-check": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", - "integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==", + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "requires": { - "prelude-ls": "~1.1.2" + "has-flag": "^4.0.0" } } } @@ -38474,11 +37375,10 @@ } }, "table": { - "version": "6.8.0", - "resolved": "https://registry.npmjs.org/table/-/table-6.8.0.tgz", - "integrity": "sha512-s/fitrbVeEyHKFa7mFdkuQMWlH1Wgw/yEXMt5xACT4ZpzWFluehAxRtUUQKPuWhaLAWhFcVx6w3oC8VKaUfPGA==", + "version": "6.8.1", + "resolved": "https://registry.npmjs.org/table/-/table-6.8.1.tgz", + "integrity": "sha512-Y4X9zqrCftUhMeH2EptSSERdVKt/nEdijTOacGD/97EKjhQ/Qs8RTlEGABSJNNN8lac9kheH+af7yAkEWlgneA==", "dev": true, - "peer": true, "requires": { "ajv": "^8.0.1", "lodash.truncate": "^4.4.2", @@ -38492,7 +37392,6 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", "dev": true, - "peer": true, "requires": { "fast-deep-equal": "^3.1.1", "json-schema-traverse": "^1.0.0", @@ -38504,8 +37403,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true, - "peer": true + "dev": true } } }, @@ -40267,7 +39165,8 @@ "version": "1.2.3", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", - "dev": true + "dev": true, + "peer": true }, "wordwrap": { "version": "1.0.0", @@ -40342,15 +39241,6 @@ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "dev": true }, - "write": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/write/-/write-1.0.3.tgz", - "integrity": "sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig==", - "dev": true, - "requires": { - "mkdirp": "^0.5.1" - } - }, "ws": { "version": "7.4.6", "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.6.tgz", diff --git a/package.json b/package.json index c8700f37c..7cd471ae7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@lukso/lsp-smart-contracts", - "version": "0.11.1", + "version": "0.12.0-rc.0", "description": "The reference implementation for universal profiles smart contracts", "directories": { "test": "test" @@ -51,7 +51,6 @@ "test:upinit": "hardhat test --no-compile tests/UniversalProfileInit.test.ts", "test:lsp1": "hardhat test --no-compile tests/LSP1UniversalReceiver/*.test.ts", "test:lsp2": "hardhat test --no-compile tests/LSP2ERC725YJSONSchema/LSP2UtilsLibrary.test.ts", - "test:lsp4": "hardhat test --no-compile tests/LSP4DigitalAssetMetadata/LSP4Compatibility.test.ts", "test:lsp6": "hardhat test --no-compile tests/LSP6KeyManager/LSP6KeyManager.test.ts", "test:lsp6init": "hardhat test --no-compile tests/LSP6KeyManager/LSP6KeyManagerInit.test.ts", "test:lsp7": "hardhat test --no-compile tests/LSP7DigitalAsset/standard/*.test.ts", @@ -63,6 +62,7 @@ "test:lsp11": "hardhat test --no-compile tests/LSP11BasicSocialRecovery/LSP11BasicSocialRecovery.test.ts", "test:lsp11init": "hardhat test --no-compile tests/LSP11BasicSocialRecovery/LSP11BasicSocialRecoveryInit.test.ts", "test:lsp17": "hardhat test --no-compile tests/LSP17ContractExtension/LSP17Extendable.test.ts", + "test:lsp17extensions": "hardhat test --no-compile tests/LSP17Extensions/**/*.test.ts", "test:lsp20": "hardhat test --no-compile tests/LSP20CallVerification/LSP6/LSP20WithLSP6.test.ts", "test:lsp20init": "hardhat test --no-compile tests/LSP20CallVerification/LSP6/LSP20WithLSP6Init.test.ts", "test:lsp23": "hardhat test --no-compile tests/LSP23LinkedContractsDeployment/LSP23LinkedContractsDeployment.test.ts", @@ -73,7 +73,8 @@ "test:foundry": "forge test --no-match-test Skip -vvv --gas-report > gasreport.ansi", "test:importRequire": "npm run build:js && ./tests/importRequire.sh", "clean": "hardhat clean && rm -rf module common", - "build": "hardhat dodoc && ts-node scripts/interfaceIds.ts && prettier -w ./docs ", + "build": "hardhat compile", + "build:docs": "hardhat dodoc && ts-node scripts/interfaceIds.ts && prettier -w ./docs", "build:js": "vite build && dts-bundle-generator --config dtsconfig.json", "package": "hardhat prepare-package", "release": "run-s clean build package && standard-version", @@ -101,13 +102,14 @@ }, "homepage": "https://github.com/lukso-network/lsp-smart-contracts#readme", "dependencies": { - "@erc725/smart-contracts": "^5.2.0", + "@account-abstraction/contracts": "^0.6.0", + "@erc725/smart-contracts": "^6.0.0", "@openzeppelin/contracts": "^4.9.2", "@openzeppelin/contracts-upgradeable": "^4.9.2", "solidity-bytes-utils": "0.8.0" }, "devDependencies": { - "@b00ste/hardhat-dodoc": "^0.3.11", + "@b00ste/hardhat-dodoc": "^0.3.15", "@defi-wonderland/smock": "^2.3.4", "@erc725/erc725.js": "0.17.2", "@lukso/eip191-signer.js": "^0.2.2", @@ -138,7 +140,7 @@ "prettier": "^2.8.8", "prettier-plugin-solidity": "^1.1.3", "rollup-plugin-esbuild": "^5.0.0", - "solhint": "^3.3.6", + "solhint": "^3.6.2", "standard-version": "^9.3.1", "ts-node": "^10.2.0", "typechain": "^8.0.0", diff --git a/scripts/ci/gas_benchmark.ts b/scripts/ci/gas_benchmark.ts new file mode 100644 index 000000000..f7b0a9542 --- /dev/null +++ b/scripts/ci/gas_benchmark.ts @@ -0,0 +1,226 @@ +import fs from 'fs'; +import { task } from 'hardhat/config'; +import { Align, getMarkdownTable, Row } from 'markdown-table-ts'; + +task('gas-benchmark', 'Benchmark gas usage of the smart contracts based on predefined scenarios') + .addParam( + 'compare', + 'The `.json` file that contains the gas costs of the currently compiled contracts (e.g: current working branch)', + ) + .addParam( + 'against', + 'The `.json` file that contains the gas costs to compare against (e.g: the `develop` branch)', + ) + .setAction(async function (args) { + const currentBenchmark = JSON.parse(fs.readFileSync(args.compare, 'utf8')); + const baseBenchmark = JSON.parse(fs.readFileSync(args.against, 'utf8')); + + const deploymentCosts: Row[] = []; + + const casesEOAExecute: Row[] = []; + const casesEOASetData: Row[] = []; + const casesEOATokens: Row[] = []; + + const casesKeyManagerExecute: Row[] = []; + const casesKeyManagerSetData: Row[] = []; + + const formatNumber = (value: number) => { + return value.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ','); + }; + + const displayGasDiff = (gasDiff: number) => { + let emoji = ''; + + if (gasDiff > 0) { + emoji = '๐Ÿ“ˆโŒ'; + } + + if (gasDiff < 0) { + emoji = '๐Ÿ“‰โœ…'; + } + + return `${formatNumber(gasDiff)} ${emoji}`; + }; + + // Deployment costs + for (const [key, value] of Object.entries(currentBenchmark['deployment_costs'])) { + const gasCost: any = value; + const gasDiff = gasCost - baseBenchmark['deployment_costs'][key]; + + deploymentCosts.push([key, value + ` (${displayGasDiff(gasDiff)})`]); + } + + const generatedDeploymentCostsTable = getMarkdownTable({ + table: { + head: ['Deployed contracts', 'โ›ฝ Deployment cost'], + body: deploymentCosts, + }, + alignment: [Align.Left], + }); + + // EOA - execute + for (const [key, value] of Object.entries( + currentBenchmark['runtime_costs']['EOA_owner']['execute'], + )) { + const gasDiff = + value['gas_cost'] - baseBenchmark['runtime_costs']['EOA_owner']['execute'][key]['gas_cost']; + + casesEOAExecute.push([ + value['description'], + value['gas_cost'] + ` (${displayGasDiff(gasDiff)})`, + ]); + } + + const generatedEOAExecuteTable = getMarkdownTable({ + table: { + head: ['`execute` scenarios - UP owned by ๐Ÿ”‘ EOA', 'โ›ฝ Gas Usage'], + body: casesEOAExecute, + }, + alignment: [Align.Left], + }); + + // EOA - setData + for (const [key, value] of Object.entries( + currentBenchmark['runtime_costs']['EOA_owner']['setData'], + )) { + const gasDiff = + value['gas_cost'] - baseBenchmark['runtime_costs']['EOA_owner']['setData'][key]['gas_cost']; + + casesEOASetData.push([ + value['description'], + value['gas_cost'] + ` (${displayGasDiff(gasDiff)})`, + ]); + } + + const generatedEOASetDataTable = getMarkdownTable({ + table: { + head: ['`setData` scenarios - UP owned by ๐Ÿ”‘ EOA', 'โ›ฝ Gas Usage'], + body: casesEOASetData, + }, + alignment: [Align.Left], + }); + + // EOA - Tokens + for (const [key, value] of Object.entries( + currentBenchmark['runtime_costs']['EOA_owner']['tokens'], + )) { + const gasDiff = + value['gas_cost'] - baseBenchmark['runtime_costs']['EOA_owner']['tokens'][key]['gas_cost']; + + casesEOATokens.push([ + value['description'], + value['gas_cost'] + ` (${displayGasDiff(gasDiff)})`, + ]); + } + + const generatedEOATokensTable = getMarkdownTable({ + table: { + head: ['`Tokens` scenarios - UP owned by ๐Ÿ”‘ EOA', 'โ›ฝ Gas Usage'], + body: casesEOATokens, + }, + alignment: [Align.Left], + }); + + // Key Manager - execute + for (const [key, value] of Object.entries( + currentBenchmark['runtime_costs']['KeyManager_owner']['execute'], + )) { + const gasDiffMainController = + value['main_controller'] - + baseBenchmark['runtime_costs']['KeyManager_owner']['execute'][key]['main_controller']; + + const gasDiffRestrictedController = + value['restricted_controller'] - + baseBenchmark['runtime_costs']['KeyManager_owner']['execute'][key]['restricted_controller']; + + casesKeyManagerExecute.push([ + value['description'], + value['main_controller'] + ` (${displayGasDiff(gasDiffMainController)})`, + value['restricted_controller'] + ` (${displayGasDiff(gasDiffRestrictedController)})`, + ]); + } + + const generatedKeyManagerExecuteTable = getMarkdownTable({ + table: { + head: ['`execute` scenarios', '๐Ÿ‘‘ main controller', '๐Ÿ›ƒ restricted controller'], + body: casesKeyManagerExecute, + }, + alignment: [Align.Left], + }); + + // Key Manager - setData + for (const [key, value] of Object.entries( + currentBenchmark['runtime_costs']['KeyManager_owner']['setData'], + )) { + const gasDiffMainController = + value['main_controller'] - + baseBenchmark['runtime_costs']['KeyManager_owner']['setData'][key]['main_controller']; + + const gasDiffRestrictedController = + value['restricted_controller'] - + baseBenchmark['runtime_costs']['KeyManager_owner']['setData'][key]['restricted_controller']; + + casesKeyManagerSetData.push([ + value['description'], + value['main_controller'] + ` (${displayGasDiff(gasDiffMainController)})`, + value['restricted_controller'] + ` (${displayGasDiff(gasDiffRestrictedController)})`, + ]); + } + + const generatedKeyManagerSetDataTable = getMarkdownTable({ + table: { + head: ['`setData` scenarios', '๐Ÿ‘‘ main controller', '๐Ÿ›ƒ restricted controller'], + body: casesKeyManagerSetData, + }, + alignment: [Align.Left], + }); + + const markdownContent = ` +๐Ÿ‘‹ Hello +โ›ฝ I am the Gas Bot Reporter. I keep track of the gas costs of common interactions using Universal Profiles ๐Ÿ†™ ! +๐Ÿ“Š Here is a summary of the gas cost with the code introduced by this PR. + +## โ›ฝ๐Ÿ“Š Gas Benchmark Report + +### Deployment Costs + +${generatedDeploymentCostsTable} + +### Runtime Costs + +
+UniversalProfile owned by an ๐Ÿ”‘ EOA + +### ๐Ÿ”€ \`execute\` scenarios + +${generatedEOAExecuteTable} + +### ๐Ÿ—„๏ธ \`setData\` scenarios + +${generatedEOASetDataTable} + +### ๐Ÿ—„๏ธ \`Tokens\` scenarios + +${generatedEOATokensTable} + +
+ +
+UniversalProfile owned by a ๐Ÿ”’๐Ÿ“„ LSP6KeyManager + +### ๐Ÿ”€ \`execute\` scenarios + +${generatedKeyManagerExecuteTable} + +### ๐Ÿ—„๏ธ \`setData\` scenarios + +${generatedKeyManagerSetDataTable} + +
+ + `; + + const file = 'gas_benchmark.md'; + + fs.writeFileSync(file, markdownContent, 'utf8'); + }); diff --git a/scripts/ci/gas_benchmark_template.json b/scripts/ci/gas_benchmark_template.json new file mode 100644 index 000000000..6c7d794c6 --- /dev/null +++ b/scripts/ci/gas_benchmark_template.json @@ -0,0 +1,204 @@ +{ + "deployment_costs": { + "UniversalProfile": "", + "KeyManager": "", + "LSP1DelegateUP": "", + "LSP7Mintable": "", + "LSP8Mintable": "" + }, + "runtime_costs": { + "EOA_owner": { + "execute": { + "case_1": { + "description": "Transfer 1 LYX to an EOA without data", + "gas_cost": "" + }, + "case_2": { + "description": "Transfer 1 LYX to a UP without data", + "gas_cost": "" + }, + "case_3": { + "description": "Transfer 1 LYX to an EOA with 256 bytes of data", + "gas_cost": "" + }, + "case_4": { + "description": "Transfer 1 LYX to a UP with 256 bytes of data", + "gas_cost": "" + }, + "case_5": { + "description": "Transfer 0.1 LYX to 3x EOA without data", + "gas_cost": "" + }, + "case_6": { + "description": "Transfer 0.1 LYX to 3x UP without data", + "gas_cost": "" + }, + "case_7": { + "description": "Transfer 0.1 LYX to 3x EOA with 256 bytes of data", + "gas_cost": "" + }, + "case_8": { + "description": "Transfer 0.1 LYX to 3x UPs with 256 bytes of data", + "gas_cost": "" + } + }, + "setData": { + "case_1": { + "description": "Set a 20 bytes long value", + "gas_cost": "" + }, + "case_2": { + "description": "Set a 60 bytes long value", + "gas_cost": "" + }, + "case_3": { + "description": "Set a 160 bytes long value", + "gas_cost": "" + }, + "case_4": { + "description": "Set a 300 bytes long value", + "gas_cost": "" + }, + "case_5": { + "description": "Set a 600 bytes long value", + "gas_cost": "" + }, + "case_6": { + "description": "Change the value of a data key already set", + "gas_cost": "" + }, + "case_7": { + "description": "Remove the value of a data key already set", + "gas_cost": "" + }, + "case_8": { + "description": "Set 2 data keys of 20 bytes long value", + "gas_cost": "" + }, + "case_9": { + "description": "Set 2 data keys of 100 bytes long value", + "gas_cost": "" + }, + "case_10": { + "description": "Set 3 data keys of 20 bytes long value", + "gas_cost": "" + }, + "case_11": { + "description": "Change the value of three data keys already set of 20 bytes long value", + "gas_cost": "" + }, + "case_12": { + "description": "Remove the value of three data keys already set", + "gas_cost": "" + } + }, + "tokens": { + "case_1": { + "description": "Minting a LSP7Token to a UP (No Delegate) from an EOA", + "gas_cost": "" + }, + "case_2": { + "description": "Minting a LSP7Token to an EOA from an EOA", + "gas_cost": "" + }, + "case_3": { + "description": "Transferring an LSP7Token from a UP to another UP (No Delegate)", + "gas_cost": "" + }, + "case_4": { + "description": "Minting a LSP8Token to a UP (No Delegate) from an EOA ", + "gas_cost": "" + }, + "case_5": { + "description": "Minting a LSP8Token to an EOA from an EOA ", + "gas_cost": "" + }, + "case_6": { + "description": "Transferring an LSP8Token from a UP to another UP (No Delegate)", + "gas_cost": "" + } + } + }, + "KeyManager_owner": { + "execute": { + "case_1": { + "description": "LYX transfer --> to an EOA", + "main_controller": "", + "restricted_controller": "" + }, + "case_2": { + "description": "LYX transfer --> to a UP", + "main_controller": "", + "restricted_controller": "" + }, + "case_3": { + "description": "LSP7 token transfer --> to an EOA", + "main_controller": "", + "restricted_controller": "" + }, + "case_4": { + "description": "LSP7 token transfer --> to a UP", + "main_controller": "", + "restricted_controller": "" + }, + "case_5": { + "description": "LSP8 NFT transfer --> to an EOA", + "main_controller": "", + "restricted_controller": "" + }, + "case_6": { + "description": "LSP8 NFT transfer --> to a UP", + "main_controller": "", + "restricted_controller": "" + } + }, + "setData": { + "case_1": { + "description": "Update Profile details (LSP3Profile Metadata)", + "main_controller": "", + "restricted_controller": "" + }, + "case_2": { + "description": "Add a new controller with permission to `SET_DATA` + 3x allowed data keys:
`AddressPermissions[]`
+ `AddressPermissions[index]`
+ `AddressPermissions:Permissions:`
+ `AddressPermissions:AllowedERC725YDataKeys: 1. decrease `AddressPermissions[]` Array length
2. remove the controller address at `AddressPermissions[index]`
3. set \"0x\" for the controller permissions under AddressPermissions:Permissions:", + "main_controller": "", + "restricted_controller": "" + }, + "case_5": { + "description": "Write 5x new LSP12 Issued Assets", + "main_controller": "", + "restricted_controller": "" + }, + "case_6": { + "description": "Update 3x data keys (first 3)", + "main_controller": "", + "restricted_controller": "" + }, + "case_7": { + "description": "Update 3x data keys (middle 3)", + "main_controller": "", + "restricted_controller": "" + }, + "case_8": { + "description": "Update 3x data keys (last 3)", + "main_controller": "", + "restricted_controller": "" + }, + "case_9": { + "description": "Set 2 x new data keys + add 3x new controllers", + "main_controller": "", + "restricted_controller": "" + } + } + } + } +} diff --git a/scripts/interfaceIds.ts b/scripts/interfaceIds.ts index c1a4b41b7..fb6c79a1a 100644 --- a/scripts/interfaceIds.ts +++ b/scripts/interfaceIds.ts @@ -11,7 +11,15 @@ const ercInterfaceDescriptions = { ERC725Y: 'General Data key-value store.', }; -const excludedInterfaces = ['ERC20', 'ERC223', 'ERC721', 'ERC721Metadata', 'ERC777', 'ERC1155']; +const excludedInterfaces = [ + 'ERC20', + 'ERC20Metadata', + 'ERC223', + 'ERC721', + 'ERC721Metadata', + 'ERC777', + 'ERC1155', +]; async function main() { const interfaces = Object.entries(INTERFACE_IDS); @@ -31,8 +39,9 @@ async function main() { } else { const lspInterface = `I${contract}`; - // adjust the source path for LSP20 and LSP17 contracts + // adjust the source path for LSP1Delegate, LSP20 and LSP17 contracts const folders = { + LSP1UniversalReceiverDelegate: 'LSP1UniversalReceiver', LSP20CallVerifier: 'LSP20CallVerification', LSP17Extendable: 'LSP17ContractExtension', LSP17Extension: 'LSP17ContractExtension', @@ -40,7 +49,11 @@ async function main() { let folder; - if (contract === 'LSP20CallVerifier' || contract.startsWith('LSP17')) { + if ( + contract === 'LSP1UniversalReceiverDelegate' || + contract.startsWith('LSP17') || + contract === 'LSP20CallVerifier' + ) { folder = folders[contract]; } else { folder = contract; diff --git a/tests/Benchmark.test.ts b/tests/Benchmark.test.ts index 1d4cdc8a9..302f0a722 100644 --- a/tests/Benchmark.test.ts +++ b/tests/Benchmark.test.ts @@ -2,9 +2,10 @@ import fs from 'fs'; import { ethers } from 'hardhat'; import { expect } from 'chai'; import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'; -import { Align, getMarkdownTable, Row } from 'markdown-table-ts'; import { + LSP1UniversalReceiverDelegateUP, + LSP1UniversalReceiverDelegateUP__factory, LSP6KeyManager__factory, LSP7Mintable, LSP7Mintable__factory, @@ -15,21 +16,26 @@ import { } from '../types'; import { - ALL_PERMISSIONS, ERC725YDataKeys, INTERFACE_IDS, OPERATION_TYPES, PERMISSIONS, CALLTYPE, + LSP8_TOKEN_ID_TYPES, } from '../constants'; import { LSP6TestContext } from './utils/context'; import { setupKeyManager, setupProfileWithKeyManagerWithURD } from './utils/fixtures'; -import { combineAllowedCalls, combinePermissions, encodeCompactBytesArray } from './utils/helpers'; +import { + abiCoder, + combineAllowedCalls, + combinePermissions, + encodeCompactBytesArray, +} from './utils/helpers'; import { BigNumber } from 'ethers'; export type UniversalProfileContext = { accounts: SignerWithAddress[]; - owner: SignerWithAddress; + mainController: SignerWithAddress; universalProfile: UniversalProfile; initialFunding?: BigNumber; }; @@ -40,45 +46,113 @@ function generateRandomData(length) { const buildLSP6TestContext = async (initialFunding?: BigNumber): Promise => { const accounts = await ethers.getSigners(); - const owner = accounts[0]; - - const universalProfile = await new UniversalProfile__factory(owner).deploy(owner.address, { - value: initialFunding, - }); - const keyManager = await new LSP6KeyManager__factory(owner).deploy(universalProfile.address); - - return { accounts, owner, universalProfile, keyManager }; + const mainController = accounts[0]; + + const universalProfile = await new UniversalProfile__factory(mainController).deploy( + mainController.address, + { + value: initialFunding, + }, + ); + const keyManager = await new LSP6KeyManager__factory(mainController).deploy( + universalProfile.address, + ); + + return { accounts, mainController, universalProfile, keyManager }; }; const buildUniversalProfileContext = async ( initialFunding?: BigNumber, ): Promise => { const accounts = await ethers.getSigners(); - const owner = accounts[0]; + const mainController = accounts[0]; - const universalProfile = await new UniversalProfile__factory(owner).deploy(owner.address, { - value: initialFunding, - }); + const universalProfile = await new UniversalProfile__factory(mainController).deploy( + mainController.address, + { + value: initialFunding, + }, + ); - return { accounts, owner, universalProfile }; + return { accounts, mainController, universalProfile }; }; -let UniversalProfileSetDataTable; -let UniversalProfileExecuteTable; -let UniversalProfileTokensTable; +describe('โ›ฝ๐Ÿ“Š Gas Benchmark', () => { + let gasBenchmark; -let mainControllerExecuteTable; -let restrictedControllerExecuteTable; + before('setup benchmark file', async () => { + gasBenchmark = JSON.parse(fs.readFileSync('./scripts/ci/gas_benchmark_template.json', 'utf8')); + }); -let mainControllerSetDataTable; -let restrictedControllerSetDataTable; + after(async () => { + fs.writeFileSync('./gas_benchmark_result.json', JSON.stringify(gasBenchmark, null, 2)); + }); + + describe('Deployment costs', () => { + it('deploy contracts + save deployment costs', async () => { + const accounts = await ethers.getSigners(); + + // Universal Profile + const universalProfile = await new UniversalProfile__factory(accounts[0]).deploy( + accounts[0].address, + ); + + const universalProfileDeployTransaction = universalProfile.deployTransaction; + const universalProfileDeploymentReceipt = await universalProfileDeployTransaction.wait(); + + gasBenchmark['deployment_costs']['UniversalProfile'] = + universalProfileDeploymentReceipt.gasUsed.toNumber(); + + // Key Manager + const keyManager = await new LSP6KeyManager__factory(accounts[0]).deploy( + universalProfile.address, + ); + + const keyManagerDeployTransaction = keyManager.deployTransaction; + const keyManagerDeploymentReceipt = await keyManagerDeployTransaction?.wait(); + + gasBenchmark['deployment_costs']['KeyManager'] = + keyManagerDeploymentReceipt?.gasUsed.toNumber(); + + // LSP1 Delegate + const lsp1Delegate = await new LSP1UniversalReceiverDelegateUP__factory(accounts[0]).deploy(); + + const lsp1DelegateDeployTransaction = lsp1Delegate.deployTransaction; + const lsp1DelegateDeploymentReceipt = await lsp1DelegateDeployTransaction.wait(); + + gasBenchmark['deployment_costs']['LSP1DelegateUP'] = + lsp1DelegateDeploymentReceipt.gasUsed.toNumber(); + + // LSP7 Token (Mintable preset) + const lsp7Mintable = await new LSP7Mintable__factory(accounts[0]).deploy( + 'Token', + 'MTKN', + accounts[0].address, + false, + ); + + const lsp7DeployTransaction = lsp7Mintable.deployTransaction; + const lsp7DeploymentReceipt = await lsp7DeployTransaction.wait(); + + gasBenchmark['deployment_costs']['LSP7Mintable'] = lsp7DeploymentReceipt.gasUsed.toNumber(); + + // LSP8 NFT (Mintable preset) + const lsp8Mintable = await new LSP8Mintable__factory(accounts[0]).deploy( + 'My NFT', + 'MNFT', + accounts[0].address, + LSP8_TOKEN_ID_TYPES.NUMBER, + ); + + const lsp8DeployTransaction = lsp8Mintable.deployTransaction; + const lsp8DeploymentReceipt = await lsp8DeployTransaction.wait(); + + gasBenchmark['deployment_costs']['LSP8Mintable'] = lsp8DeploymentReceipt.gasUsed.toNumber(); + }); + }); -describe('โ›ฝ๐Ÿ“Š Gas Benchmark', () => { describe('UniversalProfile', () => { let context: UniversalProfileContext; - const executeUP: Row[] = []; - const setDataUP: Row[] = []; - const tokensUP: Row[] = []; describe('execute', () => { describe('execute Single', () => { @@ -88,7 +162,7 @@ describe('โ›ฝ๐Ÿ“Š Gas Benchmark', () => { it('Transfer 1 LYX to an EOA without data', async () => { const tx = await context.universalProfile - .connect(context.owner) + .connect(context.mainController) .execute( OPERATION_TYPES.CALL, context.accounts[1].address, @@ -98,15 +172,13 @@ describe('โ›ฝ๐Ÿ“Š Gas Benchmark', () => { const receipt = await tx.wait(); - executeUP.push([ - 'Transfer 1 LYX to an EOA without data', - receipt.gasUsed.toNumber().toString(), - ]); + gasBenchmark['runtime_costs']['EOA_owner']['execute']['case_1']['gas_cost'] = + receipt.gasUsed.toNumber(); }); it('Transfer 1 LYX to a UP without data', async () => { const tx = await context.universalProfile - .connect(context.owner) + .connect(context.mainController) .execute( OPERATION_TYPES.CALL, context.universalProfile.address, @@ -116,15 +188,13 @@ describe('โ›ฝ๐Ÿ“Š Gas Benchmark', () => { const receipt = await tx.wait(); - executeUP.push([ - 'Transfer 1 LYX to a UP without data', - receipt.gasUsed.toNumber().toString(), - ]); + gasBenchmark['runtime_costs']['EOA_owner']['execute']['case_2']['gas_cost'] = + receipt.gasUsed.toNumber(); }); it('Transfer 1 LYX to an EOA with 256 bytes of data', async () => { const tx = await context.universalProfile - .connect(context.owner) + .connect(context.mainController) .execute( OPERATION_TYPES.CALL, context.accounts[1].address, @@ -134,15 +204,13 @@ describe('โ›ฝ๐Ÿ“Š Gas Benchmark', () => { const receipt = await tx.wait(); - executeUP.push([ - 'Transfer 1 LYX to an EOA with 256 bytes of data', - receipt.gasUsed.toNumber().toString(), - ]); + gasBenchmark['runtime_costs']['EOA_owner']['execute']['case_3']['gas_cost'] = + receipt.gasUsed.toNumber(); }); it('Transfer 1 LYX to a UP with 256 bytes of data', async () => { const tx = await context.universalProfile - .connect(context.owner) + .connect(context.mainController) .execute( OPERATION_TYPES.CALL, context.universalProfile.address, @@ -152,35 +220,33 @@ describe('โ›ฝ๐Ÿ“Š Gas Benchmark', () => { const receipt = await tx.wait(); - executeUP.push([ - 'Transfer 1 LYX to a UP with 256 bytes of data', - receipt.gasUsed.toNumber().toString(), - ]); + gasBenchmark['runtime_costs']['EOA_owner']['execute']['case_4']['gas_cost'] = + receipt.gasUsed.toNumber(); }); }); describe('execute Array', () => { - let universalProfile1, universalProfile2, universalProfile3; + let universalProfile1: UniversalProfile, universalProfile2, universalProfile3; before(async () => { context = await buildUniversalProfileContext(ethers.utils.parseEther('50')); - universalProfile1 = await new UniversalProfile__factory(context.owner).deploy( + universalProfile1 = await new UniversalProfile__factory(context.mainController).deploy( context.accounts[2].address, ); - universalProfile2 = await new UniversalProfile__factory(context.owner).deploy( + universalProfile2 = await new UniversalProfile__factory(context.mainController).deploy( context.accounts[3].address, ); - universalProfile3 = await new UniversalProfile__factory(context.owner).deploy( + universalProfile3 = await new UniversalProfile__factory(context.mainController).deploy( context.accounts[4].address, ); }); it('Transfer 0.1 LYX to 3x EOA without data', async () => { const tx = await context.universalProfile - .connect(context.owner) + .connect(context.mainController) .executeBatch( [OPERATION_TYPES.CALL, OPERATION_TYPES.CALL, OPERATION_TYPES.CALL], [ @@ -198,15 +264,13 @@ describe('โ›ฝ๐Ÿ“Š Gas Benchmark', () => { const receipt = await tx.wait(); - executeUP.push([ - 'Transfer 0.1 LYX to 3x EOA without data', - receipt.gasUsed.toNumber().toString(), - ]); + gasBenchmark['runtime_costs']['EOA_owner']['execute']['case_5']['gas_cost'] = + receipt.gasUsed.toNumber(); }); it('Transfer 0.1 LYX to 3x UP without data', async () => { const tx = await context.universalProfile - .connect(context.owner) + .connect(context.mainController) .executeBatch( [OPERATION_TYPES.CALL, OPERATION_TYPES.CALL, OPERATION_TYPES.CALL], [universalProfile1.address, universalProfile2.address, universalProfile3.address], @@ -220,15 +284,13 @@ describe('โ›ฝ๐Ÿ“Š Gas Benchmark', () => { const receipt = await tx.wait(); - executeUP.push([ - 'Transfer 0.1 LYX to 3x UP without data', - receipt.gasUsed.toNumber().toString(), - ]); + gasBenchmark['runtime_costs']['EOA_owner']['execute']['case_6']['gas_cost'] = + receipt.gasUsed.toNumber(); }); it('Transfer 0.1 LYX to 3x EOA with 256 bytes of data', async () => { const tx = await context.universalProfile - .connect(context.owner) + .connect(context.mainController) .executeBatch( [OPERATION_TYPES.CALL, OPERATION_TYPES.CALL, OPERATION_TYPES.CALL], [ @@ -246,10 +308,8 @@ describe('โ›ฝ๐Ÿ“Š Gas Benchmark', () => { const receipt = await tx.wait(); - executeUP.push([ - 'Transfer 0.1 LYX to 3x EOA with 256 bytes of data', - receipt.gasUsed.toNumber().toString(), - ]); + gasBenchmark['runtime_costs']['EOA_owner']['execute']['case_7']['gas_cost'] = + receipt.gasUsed.toNumber(); }); it('Transfer 0.1 LYX to 3x UP with 256 bytes of data', async () => { @@ -259,7 +319,7 @@ describe('โ›ฝ๐Ÿ“Š Gas Benchmark', () => { ]); const tx = await context.universalProfile - .connect(context.owner) + .connect(context.mainController) .executeBatch( [OPERATION_TYPES.CALL, OPERATION_TYPES.CALL, OPERATION_TYPES.CALL], [universalProfile1.address, universalProfile2.address, universalProfile3.address], @@ -273,20 +333,8 @@ describe('โ›ฝ๐Ÿ“Š Gas Benchmark', () => { const receipt = await tx.wait(); - executeUP.push([ - 'Transfer 0.1 LYX to 3x EOA with 256 bytes of data', - receipt.gasUsed.toNumber().toString(), - ]); - }); - }); - - after(async () => { - UniversalProfileExecuteTable = getMarkdownTable({ - table: { - head: ['`execute` scenarios - ๐Ÿ‘‘ UP Owner', 'โ›ฝ Gas Usage'], - body: executeUP, - }, - alignment: [Align.Left, Align.Center], + gasBenchmark['runtime_costs']['EOA_owner']['execute']['case_8']['gas_cost'] = + receipt.gasUsed.toNumber(); }); }); }); @@ -305,7 +353,8 @@ describe('โ›ฝ๐Ÿ“Š Gas Benchmark', () => { const receipt = await tx.wait(); - setDataUP.push(['Set a 20 bytes long value', receipt.gasUsed.toNumber().toString()]); + gasBenchmark['runtime_costs']['EOA_owner']['setData']['case_1']['gas_cost'] = + receipt.gasUsed.toNumber(); }); it('Set a 60 bytes long value', async () => { @@ -316,7 +365,8 @@ describe('โ›ฝ๐Ÿ“Š Gas Benchmark', () => { const receipt = await tx.wait(); - setDataUP.push(['Set a 60 bytes long value', receipt.gasUsed.toNumber().toString()]); + gasBenchmark['runtime_costs']['EOA_owner']['setData']['case_2']['gas_cost'] = + receipt.gasUsed.toNumber(); }); it('Set a 160 bytes long value', async () => { @@ -327,7 +377,8 @@ describe('โ›ฝ๐Ÿ“Š Gas Benchmark', () => { const receipt = await tx.wait(); - setDataUP.push(['Set a 160 bytes long value', receipt.gasUsed.toNumber().toString()]); + gasBenchmark['runtime_costs']['EOA_owner']['setData']['case_3']['gas_cost'] = + receipt.gasUsed.toNumber(); }); it('Set a 300 bytes long value', async () => { @@ -338,7 +389,8 @@ describe('โ›ฝ๐Ÿ“Š Gas Benchmark', () => { const receipt = await tx.wait(); - setDataUP.push(['Set a 300 bytes long value', receipt.gasUsed.toNumber().toString()]); + gasBenchmark['runtime_costs']['EOA_owner']['setData']['case_4']['gas_cost'] = + receipt.gasUsed.toNumber(); }); it('Set a 600 bytes long value', async () => { @@ -349,7 +401,8 @@ describe('โ›ฝ๐Ÿ“Š Gas Benchmark', () => { const receipt = await tx.wait(); - setDataUP.push(['Set a 600 bytes long value', receipt.gasUsed.toNumber().toString()]); + gasBenchmark['runtime_costs']['EOA_owner']['setData']['case_5']['gas_cost'] = + receipt.gasUsed.toNumber(); }); it('Change the value of a data key already set', async () => { @@ -363,10 +416,8 @@ describe('โ›ฝ๐Ÿ“Š Gas Benchmark', () => { const receipt = await tx.wait(); - setDataUP.push([ - 'Change the value of a data key already set', - receipt.gasUsed.toNumber().toString(), - ]); + gasBenchmark['runtime_costs']['EOA_owner']['setData']['case_6']['gas_cost'] = + receipt.gasUsed.toNumber(); }); it('Remove the value of a data key already set', async () => { @@ -379,10 +430,8 @@ describe('โ›ฝ๐Ÿ“Š Gas Benchmark', () => { const receipt = await tx.wait(); - setDataUP.push([ - 'Remove the value of a data key already set', - receipt.gasUsed.toNumber().toString(), - ]); + gasBenchmark['runtime_costs']['EOA_owner']['setData']['case_7']['gas_cost'] = + receipt.gasUsed.toNumber(); }); }); @@ -402,10 +451,8 @@ describe('โ›ฝ๐Ÿ“Š Gas Benchmark', () => { const receipt = await tx.wait(); - setDataUP.push([ - 'Set 2 data keys of 20 bytes long value', - receipt.gasUsed.toNumber().toString(), - ]); + gasBenchmark['runtime_costs']['EOA_owner']['setData']['case_8']['gas_cost'] = + receipt.gasUsed.toNumber(); }); it('Set 2 data keys of 100 bytes long value', async () => { @@ -419,10 +466,8 @@ describe('โ›ฝ๐Ÿ“Š Gas Benchmark', () => { const receipt = await tx.wait(); - setDataUP.push([ - 'Set 2 data keys of 100 bytes long value', - receipt.gasUsed.toNumber().toString(), - ]); + gasBenchmark['runtime_costs']['EOA_owner']['setData']['case_9']['gas_cost'] = + receipt.gasUsed.toNumber(); }); it('Set 3 data keys of 20 bytes long value', async () => { @@ -442,10 +487,8 @@ describe('โ›ฝ๐Ÿ“Š Gas Benchmark', () => { const receipt = await tx.wait(); - setDataUP.push([ - 'Set 3 data keys of 20 bytes long value', - receipt.gasUsed.toNumber().toString(), - ]); + gasBenchmark['runtime_costs']['EOA_owner']['setData']['case_10']['gas_cost'] = + receipt.gasUsed.toNumber(); }); it('Change the value of three data keys already set of 20 bytes long value', async () => { @@ -467,10 +510,8 @@ describe('โ›ฝ๐Ÿ“Š Gas Benchmark', () => { const receipt = await tx.wait(); - setDataUP.push([ - 'Change the value of three data keys already set of 20 bytes long value', - receipt.gasUsed.toNumber().toString(), - ]); + gasBenchmark['runtime_costs']['EOA_owner']['setData']['case_11']['gas_cost'] = + receipt.gasUsed.toNumber(); }); it('Remove the value of three data keys already set', async () => { @@ -492,20 +533,8 @@ describe('โ›ฝ๐Ÿ“Š Gas Benchmark', () => { const receipt = await tx.wait(); - setDataUP.push([ - 'Remove the value of three data keys already set', - receipt.gasUsed.toNumber().toString(), - ]); - }); - }); - - after(async () => { - UniversalProfileSetDataTable = getMarkdownTable({ - table: { - head: ['`setData` scenarios - ๐Ÿ‘‘ UP Owner', 'โ›ฝ Gas Usage'], - body: setDataUP, - }, - alignment: [Align.Left, Align.Center], + gasBenchmark['runtime_costs']['EOA_owner']['setData']['case_12']['gas_cost'] = + receipt.gasUsed.toNumber(); }); }); }); @@ -518,21 +547,22 @@ describe('โ›ฝ๐Ÿ“Š Gas Benchmark', () => { before(async () => { context = await buildUniversalProfileContext(ethers.utils.parseEther('50')); // deploy a LSP7 token - lsp7Token = await new LSP7Mintable__factory(context.owner).deploy( + lsp7Token = await new LSP7Mintable__factory(context.mainController).deploy( 'Token', 'MTKN', - context.owner.address, + context.mainController.address, false, ); - // deploy a LSP7 token - lsp8Token = await new LSP8Mintable__factory(context.owner).deploy( - 'Token', - 'MTKN', - context.owner.address, + // deploy a LSP8 token + lsp8Token = await new LSP8Mintable__factory(context.mainController).deploy( + 'My NFT', + 'MNFT', + context.mainController.address, + LSP8_TOKEN_ID_TYPES.UNIQUE_ID, ); - universalProfile1 = await new UniversalProfile__factory(context.owner).deploy( + universalProfile1 = await new UniversalProfile__factory(context.mainController).deploy( context.accounts[2].address, ); }); @@ -543,10 +573,8 @@ describe('โ›ฝ๐Ÿ“Š Gas Benchmark', () => { const receipt = await tx.wait(); - tokensUP.push([ - 'Minting a LSP7Token to a UP (No Delegate) from an EOA', - receipt.gasUsed.toNumber().toString(), - ]); + gasBenchmark['runtime_costs']['EOA_owner']['tokens']['case_1']['gas_cost'] = + receipt.gasUsed.toNumber(); }); it('when minting LSP7Token to a EOA without data', async () => { @@ -554,10 +582,8 @@ describe('โ›ฝ๐Ÿ“Š Gas Benchmark', () => { const receipt = await tx.wait(); - tokensUP.push([ - 'Minting a LSP7Token to an EOA from an EOA', - receipt.gasUsed.toNumber().toString(), - ]); + gasBenchmark['runtime_costs']['EOA_owner']['tokens']['case_2']['gas_cost'] = + receipt.gasUsed.toNumber(); }); it('when transferring LSP7Token from a UP to a UP without data', async () => { @@ -570,15 +596,13 @@ describe('โ›ฝ๐Ÿ“Š Gas Benchmark', () => { ]); const tx = await context.universalProfile - .connect(context.owner) + .connect(context.mainController) .execute(OPERATION_TYPES.CALL, lsp7Token.address, 0, lsp7TransferPayload); const receipt = await tx.wait(); - tokensUP.push([ - 'Transferring an LSP7Token from a UP to another UP (No Delegate)', - receipt.gasUsed.toNumber().toString(), - ]); + gasBenchmark['runtime_costs']['EOA_owner']['tokens']['case_3']['gas_cost'] = + receipt.gasUsed.toNumber(); }); }); @@ -600,10 +624,8 @@ describe('โ›ฝ๐Ÿ“Š Gas Benchmark', () => { const receipt = await tx.wait(); - tokensUP.push([ - 'Minting a LSP8Token to a UP (No Delegate) from an EOA', - receipt.gasUsed.toNumber().toString(), - ]); + gasBenchmark['runtime_costs']['EOA_owner']['tokens']['case_4']['gas_cost'] = + receipt.gasUsed.toNumber(); }); it('when minting LSP8Token to a EOA without data', async () => { @@ -611,10 +633,8 @@ describe('โ›ฝ๐Ÿ“Š Gas Benchmark', () => { const receipt = await tx.wait(); - tokensUP.push([ - 'Minting a LSP8Token to an EOA from an EOA', - receipt.gasUsed.toNumber().toString(), - ]); + gasBenchmark['runtime_costs']['EOA_owner']['tokens']['case_5']['gas_cost'] = + receipt.gasUsed.toNumber(); }); it('when transferring LSP8Token from a UP to a UP without data', async () => { @@ -627,25 +647,13 @@ describe('โ›ฝ๐Ÿ“Š Gas Benchmark', () => { ]); const tx = await context.universalProfile - .connect(context.owner) + .connect(context.mainController) .execute(OPERATION_TYPES.CALL, lsp8Token.address, 0, lsp8TransferPayload); const receipt = await tx.wait(); - tokensUP.push([ - 'Transferring an LSP8Token from a UP to another UP (No Delegate)', - receipt.gasUsed.toNumber().toString(), - ]); - }); - }); - - after(async () => { - UniversalProfileTokensTable = getMarkdownTable({ - table: { - head: ['`Tokens` scenarios - ๐Ÿ‘‘ UP Owner', 'โ›ฝ Gas Usage'], - body: tokensUP, - }, - alignment: [Align.Left, Align.Center], + gasBenchmark['runtime_costs']['EOA_owner']['tokens']['case_6']['gas_cost'] = + receipt.gasUsed.toNumber(); }); }); }); @@ -654,8 +662,6 @@ describe('โ›ฝ๐Ÿ“Š Gas Benchmark', () => { describe('KeyManager', () => { describe('`execute(...)` via Key Manager', () => { describe('main controller (this browser extension)', () => { - const casesExecuteMainController: Row[] = []; - let context: LSP6TestContext; let recipientEOA: SignerWithAddress; @@ -679,23 +685,33 @@ describe('โ›ฝ๐Ÿ“Š Gas Benchmark', () => { const deployedContracts = await setupProfileWithKeyManagerWithURD(context.accounts[2]); aliceUP = deployedContracts[0] as UniversalProfile; - // the function `setupKeyManager` gives ALL PERMISSIONS - // to the owner as the first data key - await setupKeyManager(context, [], []); + const lsp1Delegate: LSP1UniversalReceiverDelegateUP = + await new LSP1UniversalReceiverDelegateUP__factory(context.accounts[0]).deploy(); + + // the function `setupKeyManager` gives ALL PERMISSIONS to the owner as the first data key + // We also setup the following: + // - LSP1 Delegate (for registering LSP7 tokens + LSP8 NFTs) + // - LSP3Profile metadata (to test for updates) + await setupKeyManager( + context, + [ERC725YDataKeys.LSP1.LSP1UniversalReceiverDelegate], + [lsp1Delegate.address], + ); // deploy a LSP7 token - lsp7MetaCoin = await new LSP7Mintable__factory(context.owner).deploy( + lsp7MetaCoin = await new LSP7Mintable__factory(context.mainController).deploy( 'MetaCoin', 'MTC', - context.owner.address, + context.mainController.address, false, ); // deploy a LSP8 NFT - lsp8MetaNFT = await new LSP8Mintable__factory(context.owner).deploy( + lsp8MetaNFT = await new LSP8Mintable__factory(context.mainController).deploy( 'MetaNFT', 'MNF', - context.owner.address, + context.mainController.address, + LSP8_TOKEN_ID_TYPES.UNIQUE_ID, ); // mint some tokens to the UP @@ -711,33 +727,31 @@ describe('โ›ฝ๐Ÿ“Š Gas Benchmark', () => { const lyxAmount = ethers.utils.parseEther('3'); // prettier-ignore - const tx = await context.universalProfile.connect(context.owner).execute(OPERATION_TYPES.CALL, recipientEOA.address, lyxAmount, "0x"); + const tx = await context.universalProfile.connect(context.mainController).execute(OPERATION_TYPES.CALL, recipientEOA.address, lyxAmount, "0x"); const receipt = await tx.wait(); - casesExecuteMainController.push([ - 'transfer LYX to an EOA', - receipt.gasUsed.toNumber().toString(), - ]); + gasBenchmark['runtime_costs']['KeyManager_owner']['execute']['case_1'][ + 'main_controller' + ] = receipt.gasUsed.toNumber(); }); it('transfers some LYXes to a UP', async () => { const lyxAmount = ethers.utils.parseEther('3'); // prettier-ignore - const tx = await context.universalProfile.connect(context.owner).execute(OPERATION_TYPES.CALL, aliceUP.address, lyxAmount, "0x"); + const tx = await context.universalProfile.connect(context.mainController).execute(OPERATION_TYPES.CALL, aliceUP.address, lyxAmount, "0x"); const receipt = await tx.wait(); - casesExecuteMainController.push([ - 'transfer LYX to a UP', - receipt.gasUsed.toNumber().toString(), - ]); + gasBenchmark['runtime_costs']['KeyManager_owner']['execute']['case_2'][ + 'main_controller' + ] = receipt.gasUsed.toNumber(); }); it('transfers some tokens (LSP7) to an EOA (no data)', async () => { const tokenAmount = 100; // prettier-ignore - const tx = await context.universalProfile.connect(context.owner).execute( + const tx = await context.universalProfile.connect(context.mainController).execute( OPERATION_TYPES.CALL, lsp7MetaCoin.address, 0, @@ -751,17 +765,16 @@ describe('โ›ฝ๐Ÿ“Š Gas Benchmark', () => { ); const receipt = await tx.wait(); - casesExecuteMainController.push([ - 'transfer tokens (LSP7) to an EOA (no data)', - receipt.gasUsed.toNumber().toString(), - ]); + gasBenchmark['runtime_costs']['KeyManager_owner']['execute']['case_3'][ + 'main_controller' + ] = receipt.gasUsed.toNumber(); }); it('transfer some tokens (LSP7) to a UP (no data)', async () => { const tokenAmount = 100; // prettier-ignore - const tx = await context.universalProfile.connect(context.owner).execute( + const tx = await context.universalProfile.connect(context.mainController).execute( OPERATION_TYPES.CALL, lsp7MetaCoin.address, 0, @@ -775,17 +788,16 @@ describe('โ›ฝ๐Ÿ“Š Gas Benchmark', () => { ); const receipt = await tx.wait(); - casesExecuteMainController.push([ - 'transfer tokens (LSP7) to a UP (no data)', - receipt.gasUsed.toNumber().toString(), - ]); + gasBenchmark['runtime_costs']['KeyManager_owner']['execute']['case_4'][ + 'main_controller' + ] = receipt.gasUsed.toNumber(); }); it('transfer a NFT (LSP8) to a EOA (no data)', async () => { const nftId = nftList[0]; // prettier-ignore - const tx = await context.universalProfile.connect(context.owner).execute( + const tx = await context.universalProfile.connect(context.mainController).execute( OPERATION_TYPES.CALL, lsp8MetaNFT.address, 0, @@ -799,17 +811,16 @@ describe('โ›ฝ๐Ÿ“Š Gas Benchmark', () => { ); const receipt = await tx.wait(); - casesExecuteMainController.push([ - 'transfer a NFT (LSP8) to a EOA (no data)', - receipt.gasUsed.toNumber().toString(), - ]); + gasBenchmark['runtime_costs']['KeyManager_owner']['execute']['case_5'][ + 'main_controller' + ] = receipt.gasUsed.toNumber(); }); it('transfer a NFT (LSP8) to a UP (no data)', async () => { const nftId = nftList[1]; // prettier-ignore - const tx = await context.universalProfile.connect(context.owner).execute( + const tx = await context.universalProfile.connect(context.mainController).execute( OPERATION_TYPES.CALL, lsp8MetaNFT.address, 0, @@ -823,25 +834,13 @@ describe('โ›ฝ๐Ÿ“Š Gas Benchmark', () => { ); const receipt = await tx.wait(); - casesExecuteMainController.push([ - 'transfer a NFT (LSP8) to a UP (no data)', - receipt.gasUsed.toNumber().toString(), - ]); - }); - - after(async () => { - mainControllerExecuteTable = getMarkdownTable({ - table: { - head: ['`execute` scenarios - ๐Ÿ‘‘ main controller', 'โ›ฝ Gas Usage'], - body: casesExecuteMainController, - }, - alignment: [Align.Left, Align.Center], - }); + gasBenchmark['runtime_costs']['KeyManager_owner']['execute']['case_6'][ + 'main_controller' + ] = receipt.gasUsed.toNumber(); }); }); describe('controllers with some restrictions', () => { - const casesExecuteRestrictedController: Row[] = []; let context: LSP6TestContext; let recipientEOA: SignerWithAddress; @@ -876,6 +875,7 @@ describe('โ›ฝ๐Ÿ“Š Gas Benchmark', () => { recipientEOA = context.accounts[1]; + // UP receiving LYX, Tokens and NFT transfers const deployedContracts = await setupProfileWithKeyManagerWithURD(context.accounts[2]); aliceUP = deployedContracts[0] as UniversalProfile; @@ -886,17 +886,17 @@ describe('โ›ฝ๐Ÿ“Š Gas Benchmark', () => { // LSP7 token transfer scenarios canTransferTwoTokens = context.accounts[3]; - lsp7MetaCoin = await new LSP7Mintable__factory(context.owner).deploy( + lsp7MetaCoin = await new LSP7Mintable__factory(context.mainController).deploy( 'MetaCoin', 'MTC', - context.owner.address, + context.mainController.address, false, ); - lsp7LyxDai = await new LSP7Mintable__factory(context.owner).deploy( + lsp7LyxDai = await new LSP7Mintable__factory(context.mainController).deploy( 'LyxDai', 'LDAI', - context.owner.address, + context.mainController.address, false, ); @@ -907,16 +907,18 @@ describe('โ›ฝ๐Ÿ“Š Gas Benchmark', () => { // LSP8 NFT transfer scenarios canTransferTwoNFTs = context.accounts[4]; - lsp8MetaNFT = await new LSP8Mintable__factory(context.owner).deploy( + lsp8MetaNFT = await new LSP8Mintable__factory(context.mainController).deploy( 'MetaNFT', 'MNF', - context.owner.address, + context.mainController.address, + LSP8_TOKEN_ID_TYPES.UNIQUE_ID, ); - lsp8LyxPunks = await new LSP8Mintable__factory(context.owner).deploy( + lsp8LyxPunks = await new LSP8Mintable__factory(context.mainController).deploy( 'LyxPunks', 'LPK', - context.owner.address, + context.mainController.address, + LSP8_TOKEN_ID_TYPES.UNIQUE_ID, ); [ @@ -929,10 +931,15 @@ describe('โ›ฝ๐Ÿ“Š Gas Benchmark', () => { }); }); + const lsp1Delegate = await new LSP1UniversalReceiverDelegateUP__factory( + context.accounts[0], + ).deploy(); + // prettier-ignore await setupKeyManager( context, [ + ERC725YDataKeys.LSP1.LSP1UniversalReceiverDelegate, ERC725YDataKeys.LSP6["AddressPermissions:Permissions"] + canTransferValueToOneAddress.address.substring(2), ERC725YDataKeys.LSP6["AddressPermissions:Permissions"] + canTransferTwoTokens.address.substring(2), ERC725YDataKeys.LSP6["AddressPermissions:Permissions"] + canTransferTwoNFTs.address.substring(2), @@ -941,10 +948,11 @@ describe('โ›ฝ๐Ÿ“Š Gas Benchmark', () => { ERC725YDataKeys.LSP6["AddressPermissions:AllowedCalls"] + canTransferTwoNFTs.address.substring(2), ], [ + lsp1Delegate.address, PERMISSIONS.TRANSFERVALUE, PERMISSIONS.CALL, PERMISSIONS.CALL, - combineAllowedCalls([CALLTYPE.VALUE], [allowedAddressToTransferValue], ["0xffffffff"], ["0xffffffff"]), + combineAllowedCalls([CALLTYPE.VALUE, CALLTYPE.VALUE], [allowedAddressToTransferValue, aliceUP.address], ["0xffffffff", "0xffffffff"], ["0xffffffff", "0xffffffff"]), combineAllowedCalls( [CALLTYPE.CALL, CALLTYPE.CALL], [lsp7MetaCoin.address, lsp7LyxDai.address], @@ -961,7 +969,7 @@ describe('โ›ฝ๐Ÿ“Š Gas Benchmark', () => { ) }); - it('transfer some LYXes to an EOA - restricted to 1 x allowed address only (TRANSFERVALUE + 1x AllowedCalls)', async () => { + it('transfer some LYXes to an EOA - restricted to 2 x allowed address only (TRANSFERVALUE + 2x AllowedCalls)', async () => { const lyxAmount = 10; const tx = await context.universalProfile @@ -969,10 +977,23 @@ describe('โ›ฝ๐Ÿ“Š Gas Benchmark', () => { .execute(OPERATION_TYPES.CALL, allowedAddressToTransferValue, lyxAmount, '0x'); const receipt = await tx.wait(); - casesExecuteRestrictedController.push([ - 'transfer some LYXes to an EOA - restricted to 1 x allowed address only (TRANSFERVALUE + 1x AllowedCalls)', - receipt.gasUsed.toNumber().toString(), - ]); + gasBenchmark['runtime_costs']['KeyManager_owner']['execute']['case_1'][ + 'restricted_controller' + ] = receipt.gasUsed.toNumber(); + }); + + it('transfer some LYXes to a UP - restricted to 2 x allowed address only (an EOA + a UP) (TRANSFERVALUE + 2x AllowedCalls)', async () => { + // ... + const lyxAmount = 10; + + const tx = await context.universalProfile + .connect(canTransferValueToOneAddress) + .execute(OPERATION_TYPES.CALL, aliceUP.address, lyxAmount, '0x'); + const receipt = await tx.wait(); + + gasBenchmark['runtime_costs']['KeyManager_owner']['execute']['case_2'][ + 'restricted_controller' + ] = receipt.gasUsed.toNumber(); }); it('transfers some tokens (LSP7) to an EOA - restricted to LSP7 + 2x allowed contracts only (CALL + 2x AllowedCalls) (no data)', async () => { @@ -993,10 +1014,9 @@ describe('โ›ฝ๐Ÿ“Š Gas Benchmark', () => { ); const receipt = await tx.wait(); - casesExecuteRestrictedController.push([ - 'transfers some tokens (LSP7) to an EOA - restricted to LSP7 + 2x allowed contracts only (CALL + 2x AllowedCalls) (no data)', - receipt.gasUsed.toNumber().toString(), - ]); + gasBenchmark['runtime_costs']['KeyManager_owner']['execute']['case_3'][ + 'restricted_controller' + ] = receipt.gasUsed.toNumber(); }); it('transfers some tokens (LSP7) to an other UP - restricted to LSP7 + 2x allowed contracts only (CALL + 2x AllowedCalls) (no data)', async () => { @@ -1017,10 +1037,9 @@ describe('โ›ฝ๐Ÿ“Š Gas Benchmark', () => { ); const receipt = await tx.wait(); - casesExecuteRestrictedController.push([ - 'transfers some tokens (LSP7) to an other UP - restricted to LSP7 + 2x allowed contracts only (CALL + 2x AllowedCalls) (no data)', - receipt.gasUsed.toNumber().toString(), - ]); + gasBenchmark['runtime_costs']['KeyManager_owner']['execute']['case_4'][ + 'restricted_controller' + ] = receipt.gasUsed.toNumber(); }); it('transfers a NFT (LSP8) to an EOA - restricted to LSP8 + 2x allowed contracts only (CALL + 2x AllowedCalls) (no data)', async () => { @@ -1041,10 +1060,9 @@ describe('โ›ฝ๐Ÿ“Š Gas Benchmark', () => { ); const receipt = await tx.wait(); - casesExecuteRestrictedController.push([ - 'transfers a NFT (LSP8) to an EOA - restricted to LSP8 + 2x allowed contracts only (CALL + 2x AllowedCalls) (no data)', - receipt.gasUsed.toNumber().toString(), - ]); + gasBenchmark['runtime_costs']['KeyManager_owner']['execute']['case_5'][ + 'restricted_controller' + ] = receipt.gasUsed.toNumber(); }); it('transfers a NFT (LSP8) to an other UP - restricted to LSP8 + 2x allowed contracts only (CALL + 2x AllowedCalls) (no data)', async () => { @@ -1065,20 +1083,9 @@ describe('โ›ฝ๐Ÿ“Š Gas Benchmark', () => { ); const receipt = await tx.wait(); - casesExecuteRestrictedController.push([ - 'transfers a NFT (LSP8) to an other UP - restricted to LSP8 + 2x allowed contracts only (CALL + 2x AllowedCalls) (no data)', - receipt.gasUsed.toNumber().toString(), - ]); - }); - - after(async () => { - restrictedControllerExecuteTable = getMarkdownTable({ - table: { - head: ['`execute` scenarios - ๐Ÿ›ƒ restricted controller', 'โ›ฝ Gas Usage'], - body: casesExecuteRestrictedController, - }, - alignment: [Align.Left, Align.Center], - }); + gasBenchmark['runtime_costs']['KeyManager_owner']['execute']['case_6'][ + 'restricted_controller' + ] = receipt.gasUsed.toNumber(); }); }); }); @@ -1086,8 +1093,7 @@ describe('โ›ฝ๐Ÿ“Š Gas Benchmark', () => { describe('`setData(...)` via Key Manager', () => { let context: LSP6TestContext; - let controllerCanSetData: SignerWithAddress, - controllerCanSetDataAndAddController: SignerWithAddress; + let controllerToAddEditAndRemove: SignerWithAddress; const allowedERC725YDataKeys = [ ethers.utils.keccak256(ethers.utils.toUtf8Bytes('key1')), @@ -1102,66 +1108,60 @@ describe('โ›ฝ๐Ÿ“Š Gas Benchmark', () => { ethers.utils.keccak256(ethers.utils.toUtf8Bytes('key10')), ]; + // Fictional scenario of a NFT Marketplace dApp + const nftMarketplaceDataKeys = [ + ethers.utils.keccak256(ethers.utils.toUtf8Bytes('NFT Marketplace dApp - settings')), + ethers.utils.keccak256(ethers.utils.toUtf8Bytes('NFT Marketplace dApp - followers')), + ethers.utils.keccak256(ethers.utils.toUtf8Bytes('NFT Marketplace dApp - rewards')), + ]; + before(async () => { context = await buildLSP6TestContext(); - controllerCanSetData = context.accounts[1]; - controllerCanSetDataAndAddController = context.accounts[2]; + controllerToAddEditAndRemove = context.accounts[1]; // prettier-ignore const permissionKeys = [ - ERC725YDataKeys.LSP6["AddressPermissions:Permissions"] + context.owner.address.substring(2), - ERC725YDataKeys.LSP6["AddressPermissions:Permissions"] + controllerCanSetData.address.substring(2), - ERC725YDataKeys.LSP6["AddressPermissions:AllowedERC725YDataKeys"] + controllerCanSetData.address.substring(2), - ERC725YDataKeys.LSP6["AddressPermissions:Permissions"] + controllerCanSetDataAndAddController.address.substring(2), - ERC725YDataKeys.LSP6["AddressPermissions:AllowedERC725YDataKeys"] + controllerCanSetDataAndAddController.address.substring(2), + ERC725YDataKeys.LSP3.LSP3Profile, ERC725YDataKeys.LSP6["AddressPermissions[]"].length, ERC725YDataKeys.LSP6["AddressPermissions[]"].index + "00000000000000000000000000000000", - ERC725YDataKeys.LSP6["AddressPermissions[]"].index + "00000000000000000000000000000001", - ERC725YDataKeys.LSP6["AddressPermissions[]"].index + "00000000000000000000000000000002", ]; - // // prettier-ignore const permissionValues = [ - ALL_PERMISSIONS, - PERMISSIONS.SETDATA, - encodeCompactBytesArray(allowedERC725YDataKeys), - combinePermissions(PERMISSIONS.SETDATA, PERMISSIONS.ADDCONTROLLER), - encodeCompactBytesArray(allowedERC725YDataKeys), + // Set some JSONURL for LSP3Profile metadata to test gas cost of updating your profile details + '0x6f357c6a70546a2accab18748420b63c63b5af4cf710848ae83afc0c51dd8ad17fb5e8b3697066733a2f2f516d65637247656a555156587057347a53393438704e76636e51724a314b69416f4d36626466725663575a736e35', ethers.utils.hexZeroPad(ethers.BigNumber.from(3).toHexString(), 16), - context.owner.address, - controllerCanSetData.address, - controllerCanSetDataAndAddController.address, + context.mainController.address, ]; + // The `context.mainController` is given `ALL_PERMISSIONS` as the first data key through `setupKeyManager` method. await setupKeyManager(context, permissionKeys, permissionValues); }); describe('main controller (this browser extension) has SUPER_SETDATA ', () => { - const benchmarkCasesSetDataMainController: Row[] = []; - - it('updates profile details (LSP3Profile metadata)', async () => { + it('Update profile details (LSP3Profile metadata)', async () => { const dataKey = ERC725YDataKeys.LSP3['LSP3Profile']; const dataValue = '0x6f357c6a820464ddfac1bec070cc14a8daf04129871d458f2ca94368aae8391311af6361696670733a2f2f516d597231564a4c776572673670456f73636468564775676f3339706136727963455a4c6a7452504466573834554178'; const tx = await context.universalProfile - .connect(context.owner) + .connect(context.mainController) .setData(dataKey, dataValue); + const receipt = await tx.wait(); - benchmarkCasesSetDataMainController.push([ - 'updates profile details (LSP3Profile metadata)', - receipt.gasUsed.toNumber().toString(), - ]); + gasBenchmark['runtime_costs']['KeyManager_owner']['setData']['case_1'][ + 'main_controller' + ] = receipt.gasUsed.toNumber(); }); - it(`give permissions to a controller - 1. increase AddressPermissions[] array length - 2. put the controller address at AddressPermissions[index] - 3. give the controller the permission SETDATA under AddressPermissions:Permissions: + it(`Give permissions to a controller + 1. increase \`AddressPermissions[]\` array length + 2. put the controller address at \`AddressPermissions[index]\` + 3. give the controller the permission \`SETDATA\` under \`AddressPermissions:Permissions:\` + 4. allow the controller to set 3x specific ERC725Y data keys under \`AddressPermissions:AllowedERC725YDataKeys:\` `, async () => { - const newController = context.accounts[3]; + const newController = controllerToAddEditAndRemove; const AddressPermissionsArrayLength = await context.universalProfile['getData(bytes32)']( ERC725YDataKeys.LSP6['AddressPermissions[]'].length, @@ -1172,6 +1172,7 @@ describe('โ›ฝ๐Ÿ“Š Gas Benchmark', () => { ERC725YDataKeys.LSP6["AddressPermissions[]"].length, ERC725YDataKeys.LSP6["AddressPermissions[]"].index + ethers.utils.hexZeroPad(ethers.utils.hexStripZeros(AddressPermissionsArrayLength), 16).substring(2), ERC725YDataKeys.LSP6["AddressPermissions:Permissions"] + newController.address.substring(2), + ERC725YDataKeys.LSP6["AddressPermissions:AllowedERC725YDataKeys"] + newController.address.substring(2), ]; // prettier-ignore @@ -1179,91 +1180,55 @@ describe('โ›ฝ๐Ÿ“Š Gas Benchmark', () => { ethers.utils.hexZeroPad(ethers.BigNumber.from(AddressPermissionsArrayLength).add(1).toHexString(), 16), newController.address, combinePermissions(PERMISSIONS.SETDATA), + encodeCompactBytesArray([ + ERC725YDataKeys.LSP3.LSP3Profile, + ERC725YDataKeys.LSP12['LSP12IssuedAssets[]'].index, + ERC725YDataKeys.LSP12['LSP12IssuedAssetsMap'], + ]) ]; const tx = await context.universalProfile - .connect(context.owner) + .connect(context.mainController) .setDataBatch(dataKeys, dataValues); const receipt = await tx.wait(); expect(await context.universalProfile.getDataBatch(dataKeys)).to.deep.equal(dataValues); - benchmarkCasesSetDataMainController.push([ - 'give permissions to a controller (AddressPermissions[] + AddressPermissions[index] + AddressPermissions:Permissions:)', - receipt.gasUsed.toNumber().toString(), - ]); + gasBenchmark['runtime_costs']['KeyManager_owner']['setData']['case_2'][ + 'main_controller' + ] = receipt.gasUsed.toNumber(); }); - it('restrict a controller to some specific ERC725Y Data Keys', async () => { - const controllerToEdit = context.accounts[3]; + it('Update permissions of previous controller. Allow it now to `SUPER_SETDATA`', async () => { + const controllerToEdit = controllerToAddEditAndRemove; - const allowedDataKeys = [ - ethers.utils.hexlify(ethers.utils.toUtf8Bytes('Allowed ERC725Y Data Key 1')), - ethers.utils.hexlify(ethers.utils.toUtf8Bytes('Allowed ERC725Y Data Key 2')), - ethers.utils.hexlify(ethers.utils.toUtf8Bytes('Allowed ERC725Y Data Key 3')), - ]; - - // prettier-ignore const dataKey = - ERC725YDataKeys.LSP6["AddressPermissions:AllowedERC725YDataKeys"] + controllerToEdit.address.substring(2) - - // prettier-ignore - const dataValue = encodeCompactBytesArray([allowedDataKeys[0], allowedDataKeys[1], allowedDataKeys[2]]) - - const tx = await context.universalProfile - .connect(context.owner) - .setData(dataKey, dataValue); - - const receipt = await tx.wait(); - - expect(await context.universalProfile.getData(dataKey)).to.equal(dataValue); - - benchmarkCasesSetDataMainController.push([ - 'restrict a controller to some specific ERC725Y Data Keys', - receipt.gasUsed.toNumber().toString(), - ]); - }); - - it('restrict a controller to interact only with 3x specific addresses', async () => { - const controllerToEdit = context.accounts[3]; - - const allowedAddresses = [ - context.accounts[4].address, - context.accounts[5].address, - context.accounts[6].address, - ]; + ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + + controllerToEdit.address.substring(2); - // prettier-ignore - const dataKey = ERC725YDataKeys.LSP6["AddressPermissions:AllowedCalls"] + controllerToEdit.address.substring(2) - - const dataValue = combineAllowedCalls( - [CALLTYPE.CALL, CALLTYPE.CALL, CALLTYPE.CALL], - [allowedAddresses[0], allowedAddresses[1], allowedAddresses[2]], - ['0xffffffff', '0xffffffff', '0xffffffff'], - ['0xffffffff', '0xffffffff', '0xffffffff'], - ); + const dataValue = combinePermissions(PERMISSIONS.SETDATA, PERMISSIONS.SUPER_SETDATA); const tx = await context.universalProfile - .connect(context.owner) + .connect(context.mainController) .setData(dataKey, dataValue); const receipt = await tx.wait(); expect(await context.universalProfile.getData(dataKey)).to.equal(dataValue); - benchmarkCasesSetDataMainController.push([ - 'restrict a controller to interact only with 3x specific addresses', - receipt.gasUsed.toNumber().toString(), - ]); + gasBenchmark['runtime_costs']['KeyManager_owner']['setData']['case_3'][ + 'main_controller' + ] = receipt.gasUsed.toNumber(); }); - it(`remove a controller (its permissions + its address from the AddressPermissions[] array) - 1. decrease AddressPermissions[] array length - 2. remove the controller address at AddressPermissions[index] - 3. set "0x" for the controller permissions under AddressPermissions:Permissions: + it(`Remove a controller + 1. decrease \`AddressPermissions[]\` array length + 2. remove the controller address at \`AddressPermissions[index]\` + 3. set \`0x\` for the controller permissions under \`AddressPermissions:Permissions:\` + 4. remove the Allowed ERC725Y Data Keys previously set for the controller under \`AddressPermissions:AllowedERC725YDataKeys:\` `, async () => { - const newController = context.accounts[3]; + const newController = controllerToAddEditAndRemove; const AddressPermissionsArrayLength = await context.universalProfile['getData(bytes32)']( ERC725YDataKeys.LSP6['AddressPermissions[]'].length, @@ -1274,6 +1239,7 @@ describe('โ›ฝ๐Ÿ“Š Gas Benchmark', () => { ERC725YDataKeys.LSP6["AddressPermissions[]"].length, ERC725YDataKeys.LSP6["AddressPermissions[]"].index + ethers.utils.hexZeroPad(ethers.utils.hexStripZeros(AddressPermissionsArrayLength), 16).substring(2), ERC725YDataKeys.LSP6["AddressPermissions:Permissions"] + newController.address.substring(2), + ERC725YDataKeys.LSP6["AddressPermissions:AllowedERC725YDataKeys"] + newController.address.substring(2), ]; // prettier-ignore @@ -1281,21 +1247,21 @@ describe('โ›ฝ๐Ÿ“Š Gas Benchmark', () => { ethers.utils.hexZeroPad(ethers.BigNumber.from(AddressPermissionsArrayLength).sub(1).toHexString(), 16), "0x", "0x", + "0x", ]; const tx = await context.universalProfile - .connect(context.owner) + .connect(context.mainController) .setDataBatch(dataKeys, dataValues); const receipt = await tx.wait(); - benchmarkCasesSetDataMainController.push([ - 'remove a controller (its permissions + its address from the AddressPermissions[] array)', - receipt.gasUsed.toNumber().toString(), - ]); + gasBenchmark['runtime_costs']['KeyManager_owner']['setData']['case_4'][ + 'main_controller' + ] = receipt.gasUsed.toNumber(); }); - it('write 5x LSP12 Issued Assets', async () => { + it('Write 5x new LSP12 Issued Assets', async () => { // prettier-ignore const issuedAssetsDataKeys = [ ERC725YDataKeys.LSP12["LSP12IssuedAssets[]"].length, @@ -1318,7 +1284,7 @@ describe('โ›ฝ๐Ÿ“Š Gas Benchmark', () => { ]; const tx = await context.universalProfile - .connect(context.owner) + .connect(context.mainController) .setDataBatch(issuedAssetsDataKeys, issuedAssetsDataValues); const receipt = await tx.wait(); @@ -1327,196 +1293,492 @@ describe('โ›ฝ๐Ÿ“Š Gas Benchmark', () => { issuedAssetsDataValues, ); - benchmarkCasesSetDataMainController.push([ - 'write 5x LSP12 Issued Assets', - receipt.gasUsed.toNumber().toString(), - ]); + gasBenchmark['runtime_costs']['KeyManager_owner']['setData']['case_5'][ + 'main_controller' + ] = receipt.gasUsed.toNumber(); }); - after(async () => { - mainControllerSetDataTable = getMarkdownTable({ - table: { - head: ['`setData` scenarios - ๐Ÿ‘‘ main controller', 'โ›ฝ Gas Usage'], - body: benchmarkCasesSetDataMainController, - }, - alignment: [Align.Left, Align.Center], - }); - }); - }); - - describe('a controller (EOA) can SETDATA, ADDCONTROLLER and on 10x AllowedERC725YKeys', () => { - const benchmarkCasesSetDataRestrictedController: Row[] = []; - - it('`setData(bytes32,bytes)` -> updates 1x data key', async () => { + it('Updates 1x data key', async () => { const dataKey = allowedERC725YDataKeys[5]; const dataValue = '0xaabbccdd'; const tx = await context.universalProfile - .connect(controllerCanSetData) + .connect(context.mainController) .setData(dataKey, dataValue); + const receipt = await tx.wait(); - benchmarkCasesSetDataRestrictedController.push([ - '`setData(bytes32,bytes)` -> updates 1x data key', - receipt.gasUsed.toNumber().toString(), - ]); + gasBenchmark['runtime_costs']['KeyManager_owner']['setData']['case_5'][ + 'main_controller' + ] = receipt.gasUsed.toNumber(); }); - it('`setData(bytes32[],bytes[])` -> updates 3x data keys (first x3)', async () => { + it('Updates 3x data keys (first x3)', async () => { const dataKeys = allowedERC725YDataKeys.slice(0, 3); const dataValues = ['0xaabbccdd', '0xaabbccdd', '0xaabbccdd']; const tx = await context.universalProfile - .connect(controllerCanSetData) + .connect(context.mainController) .setDataBatch(dataKeys, dataValues); + const receipt = await tx.wait(); - benchmarkCasesSetDataRestrictedController.push([ - '`setData(bytes32[],bytes[])` -> updates 3x data keys (first x3)', - receipt.gasUsed.toNumber().toString(), - ]); + gasBenchmark['runtime_costs']['KeyManager_owner']['setData']['case_6'][ + 'main_controller' + ] = receipt.gasUsed.toNumber(); }); - it('`setData(bytes32[],bytes[])` -> updates 3x data keys (middle x3)', async () => { + it('Update 3x data keys (middle x3)', async () => { const dataKeys = allowedERC725YDataKeys.slice(3, 6); const dataValues = ['0xaabbccdd', '0xaabbccdd', '0xaabbccdd']; const tx = await context.universalProfile - .connect(controllerCanSetData) + .connect(context.mainController) .setDataBatch(dataKeys, dataValues); + const receipt = await tx.wait(); - benchmarkCasesSetDataRestrictedController.push([ - '`setData(bytes32[],bytes[])` -> updates 3x data keys (middle x3)', - receipt.gasUsed.toNumber().toString(), - ]); + gasBenchmark['runtime_costs']['KeyManager_owner']['setData']['case_7'][ + 'main_controller' + ] = receipt.gasUsed.toNumber(); }); - it('`setData(bytes32[],bytes[])` -> updates 3x data keys (last x3)', async () => { + it('Update 3x data keys (last x3)', async () => { const dataKeys = allowedERC725YDataKeys.slice(7, 10); const dataValues = ['0xaabbccdd', '0xaabbccdd', '0xaabbccdd']; const tx = await context.universalProfile - .connect(controllerCanSetData) + .connect(context.mainController) .setDataBatch(dataKeys, dataValues); + const receipt = await tx.wait(); - benchmarkCasesSetDataRestrictedController.push([ - '`setData(bytes32[],bytes[])` -> updates 3x data keys (last x3)', - receipt.gasUsed.toNumber().toString(), - ]); + gasBenchmark['runtime_costs']['KeyManager_owner']['setData']['case_8'][ + 'main_controller' + ] = receipt.gasUsed.toNumber(); }); - it('`setData(bytes32[],bytes[])` -> updates 2x data keys + add 3x new controllers (including setting the array length + indexes under AddressPermissions[index])', async () => { + it('Set 2x data keys + add 3x new controllers (including setting the array length + indexes under AddressPermissions[index]) - 12 data keys in total', async () => { + const addressPermissionsArrayLength = ethers.BigNumber.from( + await context.universalProfile.getData( + ERC725YDataKeys.LSP6['AddressPermissions[]'].length, + ), + ).toNumber(); + + const newArrayLengthUint128Hex = ethers.utils.hexZeroPad( + ethers.BigNumber.from(addressPermissionsArrayLength).add(3).toHexString(), + 16, + ); + + // example of a dApp that set some logic + const compactBytesArrayAllowedERC725YDataKeys = encodeCompactBytesArray([ + ...nftMarketplaceDataKeys, + ERC725YDataKeys.LSP12['LSP12IssuedAssets[]'].index, + ERC725YDataKeys.LSP12['LSP12IssuedAssetsMap'], + ]); + const dataKeys = [ - allowedERC725YDataKeys[0], - allowedERC725YDataKeys[1], + nftMarketplaceDataKeys[0], // set the settings and followers to 0 to start (rewards are set later) + nftMarketplaceDataKeys[1], + ERC725YDataKeys.LSP6['AddressPermissions[]'].length, + ERC725YDataKeys.LSP6['AddressPermissions[]'].index + + `0000000000000000000000000000000${addressPermissionsArrayLength}`, + ERC725YDataKeys.LSP6['AddressPermissions[]'].index + + `0000000000000000000000000000000${addressPermissionsArrayLength + 1}`, + ERC725YDataKeys.LSP6['AddressPermissions[]'].index + + `0000000000000000000000000000000${addressPermissionsArrayLength + 2}`, ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + context.accounts[3].address.substring(2), ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + context.accounts[4].address.substring(2), ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + context.accounts[5].address.substring(2), + ERC725YDataKeys.LSP6['AddressPermissions:AllowedERC725YDataKeys'] + + context.accounts[3].address.substring(2), + ERC725YDataKeys.LSP6['AddressPermissions:AllowedERC725YDataKeys'] + + context.accounts[4].address.substring(2), + ERC725YDataKeys.LSP6['AddressPermissions:AllowedERC725YDataKeys'] + + context.accounts[5].address.substring(2), ]; const dataValues = [ - '0xaabbccdd', - '0xaabbccdd', + // user settings + ethers.utils.hexlify(ethers.utils.toUtf8Bytes('Some default user settings to start')), + // followers count starts at 0 + abiCoder.encode(['uint256'], [0]), + newArrayLengthUint128Hex, + context.accounts[3].address, + context.accounts[4].address, + context.accounts[5].address, PERMISSIONS.SETDATA, PERMISSIONS.SETDATA, PERMISSIONS.SETDATA, + compactBytesArrayAllowedERC725YDataKeys, + compactBytesArrayAllowedERC725YDataKeys, + compactBytesArrayAllowedERC725YDataKeys, ]; const tx = await context.universalProfile - .connect(controllerCanSetDataAndAddController) + .connect(context.mainController) .setDataBatch(dataKeys, dataValues); + const receipt = await tx.wait(); - benchmarkCasesSetDataRestrictedController.push([ - '`setData(bytes32[],bytes[])` -> updates 2x data keys + add 3x new controllers (including setting the array length + indexes under AddressPermissions[index])', - receipt.gasUsed.toNumber().toString(), - ]); + gasBenchmark['runtime_costs']['KeyManager_owner']['setData']['case_9'][ + 'main_controller' + ] = receipt.gasUsed.toNumber(); }); + }); - after(async () => { - restrictedControllerSetDataTable = getMarkdownTable({ - table: { - head: ['`setData` scenarios - ๐Ÿ›ƒ restricted controller', 'โ›ฝ Gas Usage'], - body: benchmarkCasesSetDataRestrictedController, - }, - alignment: [Align.Left, Align.Center], - }); + describe('restricted controllers', () => { + let controllercanSetTwoDataKeys: SignerWithAddress, + controllerCanAddControllers: SignerWithAddress, + controllerCanEditPermissions: SignerWithAddress, + controllerCanSetTenDataKeys: SignerWithAddress, + controllerCanSetDataAndAddControllers: SignerWithAddress; + + before(async () => { + context = await buildLSP6TestContext(); + + controllercanSetTwoDataKeys = context.accounts[1]; + controllerCanAddControllers = context.accounts[2]; + controllerCanEditPermissions = context.accounts[3]; + controllerCanSetTenDataKeys = context.accounts[4]; + controllerCanSetDataAndAddControllers = context.accounts[5]; + + controllerToAddEditAndRemove = context.accounts[6]; + + // prettier-ignore + const permissionKeys = [ + ERC725YDataKeys.LSP3.LSP3Profile, + ERC725YDataKeys.LSP6["AddressPermissions[]"].length, + ERC725YDataKeys.LSP6["AddressPermissions[]"].index + "00000000000000000000000000000000", + ERC725YDataKeys.LSP6["AddressPermissions:Permissions"] + controllercanSetTwoDataKeys.address.substring(2), + ERC725YDataKeys.LSP6["AddressPermissions:AllowedERC725YDataKeys"] + controllercanSetTwoDataKeys.address.substring(2), + ERC725YDataKeys.LSP6["AddressPermissions:Permissions"] + controllerCanAddControllers.address.substring(2), + ERC725YDataKeys.LSP6["AddressPermissions:Permissions"] + controllerCanEditPermissions.address.substring(2), + ERC725YDataKeys.LSP6["AddressPermissions:Permissions"] + controllerCanSetTenDataKeys.address.substring(2), + ERC725YDataKeys.LSP6["AddressPermissions:AllowedERC725YDataKeys"] + controllerCanSetTenDataKeys.address.substring(2), + ERC725YDataKeys.LSP6["AddressPermissions:Permissions"] + controllerCanSetDataAndAddControllers.address.substring(2), + ERC725YDataKeys.LSP6["AddressPermissions:AllowedERC725YDataKeys"] + controllerCanSetDataAndAddControllers.address.substring(2), + ]; + + const permissionValues = [ + // Set some JSONURL for LSP3Profile metadata to test gas cost of updating your profile details + '0x6f357c6a70546a2accab18748420b63c63b5af4cf710848ae83afc0c51dd8ad17fb5e8b3697066733a2f2f516d65637247656a555156587057347a53393438704e76636e51724a314b69416f4d36626466725663575a736e35', + ethers.utils.hexZeroPad(ethers.BigNumber.from(6).toHexString(), 16), + context.mainController.address, + PERMISSIONS.SETDATA, + encodeCompactBytesArray([ + ERC725YDataKeys.LSP3.LSP3Profile, + ERC725YDataKeys.LSP12['LSP12IssuedAssets[]'].index, + ]), + PERMISSIONS.ADDCONTROLLER, + PERMISSIONS.EDITPERMISSIONS, + PERMISSIONS.SETDATA, + encodeCompactBytesArray(allowedERC725YDataKeys), + combinePermissions(PERMISSIONS.SETDATA, PERMISSIONS.ADDCONTROLLER), + encodeCompactBytesArray([ + ...nftMarketplaceDataKeys, + ERC725YDataKeys.LSP3.LSP3Profile, + ERC725YDataKeys.LSP12['LSP12IssuedAssets[]'].index, + ]), + ]; + + // The `context.mainController` is given `ALL_PERMISSIONS` as the first data key through `setupKeyManager` method. + await setupKeyManager(context, permissionKeys, permissionValues); }); - }); - }); - }); - after(async () => { - const markdown = ` -๐Ÿ‘‹ Hello -โ›ฝ I am the Gas Bot Reporter. I keep track of the gas costs of common interactions using Universal Profiles ๐Ÿ†™ ! -๐Ÿ“Š Here is a summary of the gas cost with the code introduced by this PR. + it('Update profile details (LSP3Profile metadata)', async () => { + const dataKey = ERC725YDataKeys.LSP3['LSP3Profile']; + const dataValue = + '0x6f357c6a820464ddfac1bec070cc14a8daf04129871d458f2ca94368aae8391311af6361696670733a2f2f516d597231564a4c776572673670456f73636468564775676f3339706136727963455a4c6a7452504466573834554178'; -
-โ›ฝ๐Ÿ“Š See Gas Benchmark report of Using UniversalProfile owned by an EOA + const tx = await context.universalProfile + .connect(controllercanSetTwoDataKeys) + .setData(dataKey, dataValue); -### ๐Ÿ”€ \`execute\` scenarios + const receipt = await tx.wait(); -${UniversalProfileExecuteTable} + gasBenchmark['runtime_costs']['KeyManager_owner']['setData']['case_1'][ + 'restricted_controller' + ] = receipt.gasUsed.toNumber(); + }); -### ๐Ÿ—„๏ธ \`setData\` scenarios + it(`Give permissions to a controller + 1. increase \`AddressPermissions[]\` array length + 2. put the controller address at \`AddressPermissions[index]\` + 3. give the controller the permission \`SETDATA\` under \`AddressPermissions:Permissions:\` + 4. allow the controller to set 3x specific ERC725Y data keys under \`AddressPermissions:AllowedERC725YDataKeys:\` + `, async () => { + const newController = controllerToAddEditAndRemove; -${UniversalProfileSetDataTable} + const AddressPermissionsArrayLength = await context.universalProfile['getData(bytes32)']( + ERC725YDataKeys.LSP6['AddressPermissions[]'].length, + ); -### ๐Ÿ—„๏ธ \`Tokens\` scenarios + // prettier-ignore + const dataKeys = [ + ERC725YDataKeys.LSP6["AddressPermissions[]"].length, + ERC725YDataKeys.LSP6["AddressPermissions[]"].index + ethers.utils.hexZeroPad(ethers.utils.hexStripZeros(AddressPermissionsArrayLength), 16).substring(2), + ERC725YDataKeys.LSP6["AddressPermissions:Permissions"] + newController.address.substring(2), + ERC725YDataKeys.LSP6["AddressPermissions:AllowedERC725YDataKeys"] + newController.address.substring(2), + ]; -${UniversalProfileTokensTable} + // prettier-ignore + const dataValues = [ + ethers.utils.hexZeroPad(ethers.BigNumber.from(AddressPermissionsArrayLength).add(1).toHexString(), 16), + newController.address, + combinePermissions(PERMISSIONS.SETDATA), + encodeCompactBytesArray([ + ERC725YDataKeys.LSP3.LSP3Profile, + ERC725YDataKeys.LSP12['LSP12IssuedAssets[]'].index, + ERC725YDataKeys.LSP12['LSP12IssuedAssetsMap'], + ]) + ]; + const tx = await context.universalProfile + .connect(controllerCanAddControllers) + .setDataBatch(dataKeys, dataValues); + const receipt = await tx.wait(); -## ๐Ÿ“ Notes + expect(await context.universalProfile.getDataBatch(dataKeys)).to.deep.equal(dataValues); -- The \`execute\` and \`setData\` scenarios are executed on a fresh UniversalProfile smart contract, deployed as standard contracts (not as proxy behind a base contract implementation). + gasBenchmark['runtime_costs']['KeyManager_owner']['setData']['case_2'][ + 'restricted_controller' + ] = receipt.gasUsed.toNumber(); + }); + it('Update permissions of previous controller. Allow it now to `SUPER_SETDATA`', async () => { + const controllerToEdit = controllerToAddEditAndRemove; -
+ const dataKey = + ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + + controllerToEdit.address.substring(2); + const dataValue = combinePermissions(PERMISSIONS.SETDATA, PERMISSIONS.SUPER_SETDATA); -
-โ›ฝ๐Ÿ“Š See Gas Benchmark report of Using UniversalProfile owned by an LSP6KeyManager + const tx = await context.universalProfile + .connect(controllerCanEditPermissions) + .setData(dataKey, dataValue); -This document contains the gas usage for common interactions and scenarios when using UniversalProfile smart contracts. + const receipt = await tx.wait(); -### ๐Ÿ”€ \`execute\` scenarios + expect(await context.universalProfile.getData(dataKey)).to.equal(dataValue); -#### ๐Ÿ‘‘ unrestricted controller + gasBenchmark['runtime_costs']['KeyManager_owner']['setData']['case_3'][ + 'restricted_controller' + ] = receipt.gasUsed.toNumber(); + }); -${mainControllerExecuteTable} + it(`Remove a controller + 1. decrease \`AddressPermissions[]\` array length + 2. remove the controller address at \`AddressPermissions[index]\` + 3. set \`0x\` for the controller permissions under \`AddressPermissions:Permissions:\` + 4. remove the Allowed ERC725Y Data Keys previously set for the controller under \`AddressPermissions:AllowedERC725YDataKeys:\` + `, async () => { + const newController = controllerToAddEditAndRemove; -#### ๐Ÿ›ƒ restricted controller + const AddressPermissionsArrayLength = await context.universalProfile['getData(bytes32)']( + ERC725YDataKeys.LSP6['AddressPermissions[]'].length, + ); -${restrictedControllerExecuteTable} + // prettier-ignore + const dataKeys = [ + ERC725YDataKeys.LSP6["AddressPermissions[]"].length, + ERC725YDataKeys.LSP6["AddressPermissions[]"].index + ethers.utils.hexZeroPad(ethers.BigNumber.from(AddressPermissionsArrayLength).sub(1).toHexString(), 16).substring(2), + ERC725YDataKeys.LSP6["AddressPermissions:Permissions"] + newController.address.substring(2), + ERC725YDataKeys.LSP6["AddressPermissions:AllowedERC725YDataKeys"] + newController.address.substring(2), + ]; -### ๐Ÿ—„๏ธ \`setData\` scenarios + // prettier-ignore + const dataValues = [ + ethers.utils.hexZeroPad(ethers.BigNumber.from(AddressPermissionsArrayLength).sub(1).toHexString(), 16), + "0x", + "0x", + "0x", + ]; -#### ๐Ÿ‘‘ unrestricted controller + const tx = await context.universalProfile + .connect(controllerCanEditPermissions) + .setDataBatch(dataKeys, dataValues); -${mainControllerSetDataTable} + const receipt = await tx.wait(); -#### ๐Ÿ›ƒ restricted controller + gasBenchmark['runtime_costs']['KeyManager_owner']['setData']['case_4'][ + 'restricted_controller' + ] = receipt.gasUsed.toNumber(); + }); -${restrictedControllerSetDataTable} + it('Write 5x new LSP12 Issued Assets', async () => { + // prettier-ignore + const issuedAssetsDataKeys = [ + ERC725YDataKeys.LSP12["LSP12IssuedAssets[]"].length, + ERC725YDataKeys.LSP12["LSP12IssuedAssets[]"].index + "00000000000000000000000000000000", + ERC725YDataKeys.LSP12["LSP12IssuedAssets[]"].index + "00000000000000000000000000000001", + ERC725YDataKeys.LSP12["LSP12IssuedAssets[]"].index + "00000000000000000000000000000002", + ERC725YDataKeys.LSP12["LSP12IssuedAssets[]"].index + "00000000000000000000000000000003", + ERC725YDataKeys.LSP12["LSP12IssuedAssets[]"].index + "00000000000000000000000000000004", + ]; + // these are just random placeholder values + // they should be replaced with actual token contract address + const issuedAssetsDataValues = [ + '0x0000000000000000000000000000000000000000000000000000000000000005', + context.accounts[5].address, + context.accounts[6].address, + context.accounts[7].address, + context.accounts[8].address, + context.accounts[9].address, + ]; -## ๐Ÿ“ Notes + const tx = await context.universalProfile + .connect(controllercanSetTwoDataKeys) + .setDataBatch(issuedAssetsDataKeys, issuedAssetsDataValues); -- The \`execute\` and \`setData\` scenarios are executed on a fresh UniversalProfile and LSP6KeyManager smart contracts, deployed as standard contracts (not as proxy behind a base contract implementation). + const receipt = await tx.wait(); + + expect(await context.universalProfile.getDataBatch(issuedAssetsDataKeys)).to.deep.equal( + issuedAssetsDataValues, + ); + + gasBenchmark['runtime_costs']['KeyManager_owner']['setData']['case_5'][ + 'restricted_controller' + ] = receipt.gasUsed.toNumber(); + }); + + it('Updates 1x data key', async () => { + const dataKey = allowedERC725YDataKeys[5]; + const dataValue = '0xaabbccdd'; + + const tx = await context.universalProfile + .connect(controllerCanSetTenDataKeys) + .setData(dataKey, dataValue); + + const receipt = await tx.wait(); + + gasBenchmark['runtime_costs']['KeyManager_owner']['setData']['case_5'][ + 'restricted_controller' + ] = receipt.gasUsed.toNumber(); + }); + + it('Updates 3x data keys (first x3)', async () => { + const dataKeys = allowedERC725YDataKeys.slice(0, 3); + const dataValues = ['0xaabbccdd', '0xaabbccdd', '0xaabbccdd']; + + const tx = await context.universalProfile + .connect(controllerCanSetTenDataKeys) + .setDataBatch(dataKeys, dataValues); + + const receipt = await tx.wait(); + + gasBenchmark['runtime_costs']['KeyManager_owner']['setData']['case_6'][ + 'restricted_controller' + ] = receipt.gasUsed.toNumber(); + }); + + it('Update 3x data keys (middle x3)', async () => { + const dataKeys = allowedERC725YDataKeys.slice(3, 6); + const dataValues = ['0xaabbccdd', '0xaabbccdd', '0xaabbccdd']; + + const tx = await context.universalProfile + .connect(controllerCanSetTenDataKeys) + .setDataBatch(dataKeys, dataValues); + + const receipt = await tx.wait(); + + gasBenchmark['runtime_costs']['KeyManager_owner']['setData']['case_7'][ + 'restricted_controller' + ] = receipt.gasUsed.toNumber(); + }); + it('Update 3x data keys (last x3)', async () => { + const dataKeys = allowedERC725YDataKeys.slice(7, 10); + const dataValues = ['0xaabbccdd', '0xaabbccdd', '0xaabbccdd']; + + const tx = await context.universalProfile + .connect(controllerCanSetTenDataKeys) + .setDataBatch(dataKeys, dataValues); -
-`; - const file = 'benchmark.md'; + const receipt = await tx.wait(); + + gasBenchmark['runtime_costs']['KeyManager_owner']['setData']['case_8'][ + 'restricted_controller' + ] = receipt.gasUsed.toNumber(); + }); - fs.writeFileSync(file, markdown); + it('Set 2x data keys + add 3x new controllers (including setting the array length + indexes under AddressPermissions[index]) - 12 data keys in total', async () => { + const addressPermissionsArrayLength = ethers.BigNumber.from( + await context.universalProfile.getData( + ERC725YDataKeys.LSP6['AddressPermissions[]'].length, + ), + ).toNumber(); + + const newArrayLengthUint128Hex = ethers.utils.hexZeroPad( + ethers.BigNumber.from(addressPermissionsArrayLength).add(3).toHexString(), + 16, + ); + + // example of a dApp that set some logic + const compactBytesArrayAllowedERC725YDataKeys = encodeCompactBytesArray([ + ...nftMarketplaceDataKeys, + ERC725YDataKeys.LSP12['LSP12IssuedAssets[]'].index, + ERC725YDataKeys.LSP12['LSP12IssuedAssetsMap'], + ]); + + const dataKeys = [ + nftMarketplaceDataKeys[0], // set the settings and followers to 0 to start (rewards are set later) + nftMarketplaceDataKeys[1], + ERC725YDataKeys.LSP6['AddressPermissions[]'].length, + ERC725YDataKeys.LSP6['AddressPermissions[]'].index + + `0000000000000000000000000000000${addressPermissionsArrayLength}`, + ERC725YDataKeys.LSP6['AddressPermissions[]'].index + + `0000000000000000000000000000000${addressPermissionsArrayLength + 1}`, + ERC725YDataKeys.LSP6['AddressPermissions[]'].index + + `0000000000000000000000000000000${addressPermissionsArrayLength + 2}`, + ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + + context.accounts[7].address.substring(2), + ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + + context.accounts[8].address.substring(2), + ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + + context.accounts[9].address.substring(2), + ERC725YDataKeys.LSP6['AddressPermissions:AllowedERC725YDataKeys'] + + context.accounts[7].address.substring(2), + ERC725YDataKeys.LSP6['AddressPermissions:AllowedERC725YDataKeys'] + + context.accounts[8].address.substring(2), + ERC725YDataKeys.LSP6['AddressPermissions:AllowedERC725YDataKeys'] + + context.accounts[9].address.substring(2), + ]; + + const dataValues = [ + // user settings + ethers.utils.hexlify(ethers.utils.toUtf8Bytes('Some default user settings to start')), + // followers count starts at 0 + abiCoder.encode(['uint256'], [0]), + newArrayLengthUint128Hex, + context.accounts[7].address, + context.accounts[8].address, + context.accounts[9].address, + PERMISSIONS.SETDATA, + PERMISSIONS.SETDATA, + PERMISSIONS.SETDATA, + compactBytesArrayAllowedERC725YDataKeys, + compactBytesArrayAllowedERC725YDataKeys, + compactBytesArrayAllowedERC725YDataKeys, + ]; + + const tx = await context.universalProfile + .connect(controllerCanSetDataAndAddControllers) + .setDataBatch(dataKeys, dataValues); + + const receipt = await tx.wait(); + + gasBenchmark['runtime_costs']['KeyManager_owner']['setData']['case_9'][ + 'restricted_controller' + ] = receipt.gasUsed.toNumber(); + }); + }); + }); }); }); diff --git a/tests/LSP11BasicSocialRecovery/LSP11BasicSocialRecovery.behaviour.ts b/tests/LSP11BasicSocialRecovery/LSP11BasicSocialRecovery.behaviour.ts index c492e2de0..674f0b028 100644 --- a/tests/LSP11BasicSocialRecovery/LSP11BasicSocialRecovery.behaviour.ts +++ b/tests/LSP11BasicSocialRecovery/LSP11BasicSocialRecovery.behaviour.ts @@ -79,7 +79,10 @@ export const shouldBehaveLikeLSP11 = (buildContext: () => Promise { @@ -280,7 +283,10 @@ export const shouldBehaveLikeLSP11 = (buildContext: () => Promise { @@ -322,7 +328,10 @@ export const shouldBehaveLikeLSP11 = (buildContext: () => Promise { @@ -365,7 +374,10 @@ export const shouldBehaveLikeLSP11 = (buildContext: () => Promise { @@ -408,7 +420,10 @@ export const shouldBehaveLikeLSP11 = (buildContext: () => Promise { diff --git a/tests/LSP14Ownable2Step/LSP14Ownable2Step.behaviour.ts b/tests/LSP14Ownable2Step/LSP14Ownable2Step.behaviour.ts index c3e55b646..d317c2926 100644 --- a/tests/LSP14Ownable2Step/LSP14Ownable2Step.behaviour.ts +++ b/tests/LSP14Ownable2Step/LSP14Ownable2Step.behaviour.ts @@ -19,7 +19,7 @@ export type LSP14TestContext = { accounts: SignerWithAddress[]; contract: LSP9Vault; deployParams: { owner: SignerWithAddress }; - onlyOwnerRevertString: string; + onlyOwnerCustomError: string; }; export const shouldBehaveLikeLSP14 = ( @@ -75,7 +75,7 @@ export const shouldBehaveLikeLSP14 = ( context.contract .connect(context.deployParams.owner) .transferOwnership(context.contract.address), - ).to.be.revertedWithCustomError(context.contract, 'CannotTransferOwnershipToSelf'); + ).to.be.revertedWithCustomError(context.contract, 'LSP14CannotTransferOwnershipToSelf'); }); describe('it should still be allowed to call onlyOwner functions', () => { @@ -145,7 +145,7 @@ export const shouldBehaveLikeLSP14 = ( await expect( context.contract.connect(randomAddress).transferOwnership(randomAddress.address), - ).to.be.revertedWith('Ownable: caller is not the owner'); + ).to.be.revertedWithCustomError(context.contract, 'OwnableCallerNotTheOwner'); }); }); @@ -153,7 +153,7 @@ export const shouldBehaveLikeLSP14 = ( it('should revert when caller is not the pending owner', async () => { await expect( context.contract.connect(context.accounts[2]).acceptOwnership(), - ).to.be.revertedWith('LSP14: caller is not the pendingOwner'); + ).to.be.revertedWithCustomError(context.contract, 'LSP14CallerNotPendingOwner'); }); describe('when caller is the pending owner', () => { @@ -197,7 +197,7 @@ export const shouldBehaveLikeLSP14 = ( await expect( context.contract.connect(previousOwner).setData(key, value), - ).to.be.revertedWith(context.onlyOwnerRevertString); + ).to.be.revertedWith(context.onlyOwnerCustomError); }); it('should revert when calling `execute(...)`', async () => { @@ -208,13 +208,13 @@ export const shouldBehaveLikeLSP14 = ( context.contract .connect(previousOwner) .execute(OPERATION_TYPES.CALL, recipient.address, amount, '0x'), - ).to.be.revertedWith('Ownable: caller is not the owner'); + ).to.be.revertedWithCustomError(context.contract, 'OwnableCallerNotTheOwner'); }); it('should revert when calling `renounceOwnership(...)`', async () => { await expect( context.contract.connect(previousOwner).renounceOwnership(), - ).to.be.revertedWith('Ownable: caller is not the owner'); + ).to.be.revertedWithCustomError(context.contract, 'OwnableCallerNotTheOwner'); }); }); @@ -254,7 +254,10 @@ export const shouldBehaveLikeLSP14 = ( it('should revert with custom message', async () => { const tx = context.contract.connect(context.accounts[5]).renounceOwnership(); - await expect(tx).to.be.revertedWith('Ownable: caller is not the owner'); + await expect(tx).to.be.revertedWithCustomError( + context.contract, + 'OwnableCallerNotTheOwner', + ); }); }); @@ -288,6 +291,8 @@ export const shouldBehaveLikeLSP14 = ( await artifacts.getBuildInfo('contracts/LSP9Vault/LSP9Vault.sol:LSP9Vault') )?.output.contracts[ 'contracts/LSP9Vault/LSP9Vault.sol' + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore ].LSP9Vault.storageLayout.storage.filter((elem) => { if (elem.label === '_renounceOwnershipStartedAt') return elem; })[0].slot, @@ -357,7 +362,7 @@ export const shouldBehaveLikeLSP14 = ( await network.provider.send('hardhat_mine', [ethers.utils.hexValue(98)]); await expect(context.contract.connect(context.deployParams.owner).renounceOwnership()) - .to.be.revertedWithCustomError(context.contract, 'NotInRenounceOwnershipInterval') + .to.be.revertedWithCustomError(context.contract, 'LSP14NotInRenounceOwnershipInterval') .withArgs( renounceOwnershipOnceReceipt.blockNumber + 200, renounceOwnershipOnceReceipt.blockNumber + 400, @@ -375,6 +380,8 @@ export const shouldBehaveLikeLSP14 = ( await artifacts.getBuildInfo('contracts/LSP9Vault/LSP9Vault.sol:LSP9Vault') )?.output.contracts[ 'contracts/LSP9Vault/LSP9Vault.sol' + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore ].LSP9Vault.storageLayout.storage.filter((elem) => { if (elem.label === '_renounceOwnershipStartedAt') return elem; })[0].slot, @@ -440,6 +447,8 @@ export const shouldBehaveLikeLSP14 = ( await artifacts.getBuildInfo('contracts/LSP9Vault/LSP9Vault.sol:LSP9Vault') )?.output.contracts[ 'contracts/LSP9Vault/LSP9Vault.sol' + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore ].LSP9Vault.storageLayout.storage.filter((elem) => { if (elem.label === '_renounceOwnershipStartedAt') return elem; })[0].slot, @@ -471,7 +480,7 @@ export const shouldBehaveLikeLSP14 = ( context.contract .connect(context.deployParams.owner) .execute(OPERATION_TYPES.CALL, recipient, amount, '0x'), - ).to.be.revertedWith('Ownable: caller is not the owner'); + ).to.be.revertedWithCustomError(context.contract, 'OwnableCallerNotTheOwner'); }); }); }); @@ -504,9 +513,9 @@ export const shouldBehaveLikeLSP14 = ( }); it('previous pendingOwner should not be able to call acceptOwnership(...) anymore', async () => { - await expect(context.contract.connect(newOwner).acceptOwnership()).to.be.revertedWith( - 'LSP14: caller is not the pendingOwner', - ); + await expect( + context.contract.connect(newOwner).acceptOwnership(), + ).to.be.revertedWithCustomError(context.contract, 'LSP14CallerNotPendingOwner'); }); }); }); @@ -527,6 +536,8 @@ export const shouldBehaveLikeLSP14 = ( await artifacts.getBuildInfo('contracts/LSP9Vault/LSP9Vault.sol:LSP9Vault') )?.output.contracts[ 'contracts/LSP9Vault/LSP9Vault.sol' + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore ].LSP9Vault.storageLayout.storage.filter((elem) => { if (elem.label === '_renounceOwnershipStartedAt') return elem; })[0].slot, diff --git a/tests/LSP17ContractExtension/LSP17ExtendableTokens.behaviour.ts b/tests/LSP17ContractExtension/LSP17ExtendableTokens.behaviour.ts index 9838fb6d3..e6e20a87d 100644 --- a/tests/LSP17ContractExtension/LSP17ExtendableTokens.behaviour.ts +++ b/tests/LSP17ContractExtension/LSP17ExtendableTokens.behaviour.ts @@ -1,5 +1,4 @@ import { expect } from 'chai'; -import { ethers } from 'hardhat'; import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'; import { FakeContract, smock } from '@defi-wonderland/smock'; @@ -18,12 +17,6 @@ import { TransferExtension, ReenterAccountExtension__factory, ReenterAccountExtension, - OnERC721ReceivedExtension, - OnERC721ReceivedExtension__factory, - RequireCallbackToken, - RequireCallbackToken__factory, - RevertFallbackExtension, - RevertFallbackExtension__factory, BuyExtension, BuyExtension__factory, } from '../../types'; @@ -43,7 +36,6 @@ export type LSP17TestContext = { export const shouldBehaveLikeLSP17 = (buildContext: () => Promise) => { let context: LSP17TestContext; let notExistingFunctionSignature, - onERC721ReceivedFunctionSelector, checkMsgVariableFunctionSelector, nameFunctionSelector, ageFunctionSelector, @@ -63,7 +55,6 @@ export const shouldBehaveLikeLSP17 = (buildContext: () => Promise Promise Promise Promise { - describe('when making a call without any value', () => { - it('should revert', async () => { - await expect( - context.accounts[0].sendTransaction({ - to: context.contract.address, - }), - ).to.be.revertedWithCustomError(context.contract, 'InvalidFunctionSelector'); - }); - }); - - describe('when making a call with sending value', () => { - it('should revert', async () => { - const amountSent = 200; - await expect( - context.accounts[0].sendTransaction({ - to: context.contract.address, - value: amountSent, - }), - ).to.be.revertedWithCustomError(context.contract, 'InvalidFunctionSelector'); - }); - }); - }); - describe('when calling the contract with calldata', () => { describe("when calling method that doesn't exist", () => { describe('when there is no extension for the function called', () => { @@ -652,8 +611,6 @@ export const shouldBehaveLikeLSP17 = (buildContext: () => Promise { describe('when calling with a payload of length less than 4bytes', () => { - let revertFallbackExtension: RevertFallbackExtension; - it('should revert', async () => { await expect( context.accounts[0].sendTransaction({ diff --git a/tests/LSP17Extensions/Extension4337/4337.test.ts b/tests/LSP17Extensions/Extension4337/4337.test.ts new file mode 100644 index 000000000..3a9f9ff68 --- /dev/null +++ b/tests/LSP17Extensions/Extension4337/4337.test.ts @@ -0,0 +1,219 @@ +import { ethers } from 'hardhat'; +import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'; +import { Signer } from 'ethers'; +import { EntryPoint__factory, EntryPoint } from '@account-abstraction/contracts'; + +import { BytesLike, parseEther } from 'ethers/lib/utils'; +import { expect } from 'chai'; +import { + Extension4337__factory, + LSP6KeyManager, + LSP6KeyManager__factory, + UniversalProfile, + UniversalProfile__factory, +} from '../../../types'; +import { deployEntryPoint, getBalance, isDeployed } from '../helpers/utils'; +import { ALL_PERMISSIONS, ERC725YDataKeys, OPERATION_TYPES } from '../../../constants'; +import { combinePermissions } from '../../utils/helpers'; +import { fillAndSign } from '../helpers/UserOp'; + +describe('4337', function () { + let bundler: SignerWithAddress; + let deployer: Signer; + let universalProfile: UniversalProfile; + let universalProfileAddress: string; + let keyManager: LSP6KeyManager; + let entryPoint: EntryPoint; + let controllerWith4337Permission: SignerWithAddress; + let controllerWithout4337Permission: SignerWithAddress; + let controllerWithOnly4337Permission: SignerWithAddress; + let transferCallData: string; + const Permission4337: BytesLike = + '0x0000000000000000000000000000000000000000000000000000000000800000'; + const amountToTransfer = 1; + + before('setup', async function () { + const provider = ethers.provider; + deployer = provider.getSigner(); + const deployerAddress = await deployer.getAddress(); + + [ + bundler, + controllerWith4337Permission, + controllerWithout4337Permission, + controllerWithOnly4337Permission, + ] = await ethers.getSigners(); + + universalProfile = await new UniversalProfile__factory(deployer).deploy( + await deployer.getAddress(), + { value: parseEther('1') }, + ); + universalProfileAddress = universalProfile.address; + + keyManager = await new LSP6KeyManager__factory(deployer).deploy(universalProfile.address); + + // transfer ownership to keyManager + await universalProfile.transferOwnership(keyManager.address); + + const dataKey = + ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + deployerAddress.slice(2); + + await universalProfile.setData(dataKey, ALL_PERMISSIONS); + + const acceptOwnershipBytes = universalProfile.interface.encodeFunctionData('acceptOwnership'); + await keyManager.execute(acceptOwnershipBytes); + expect(await universalProfile.owner()).to.eq(keyManager.address); + + // deploy entrypoint + entryPoint = await deployEntryPoint(); + expect(await isDeployed(entryPoint.address)).to.eq(true); + + // give all permissions to entrypoint + const dataKeyEntryPointPermissions = + ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + entryPoint.address.slice(2); + await universalProfile.setData(dataKeyEntryPointPermissions, ALL_PERMISSIONS); + + // deploy extension and attach it to universalProfile + const extension4337 = await new Extension4337__factory(deployer).deploy(entryPoint.address); + const validateUserOpSigHash = extension4337.interface.getSighash('validateUserOp'); + + const extensionDataKey = + ERC725YDataKeys.LSP17.LSP17ExtensionPrefix + + validateUserOpSigHash.slice(2) + + '00000000000000000000000000000000'; + + await universalProfile.setData(extensionDataKey, extension4337.address); + + // give permissions to controllers + const dataKeyWithPermission4337 = + ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + + controllerWith4337Permission.address.slice(2); + await universalProfile.setData( + dataKeyWithPermission4337, + combinePermissions(ALL_PERMISSIONS, Permission4337), + ); + + const dataKeyWithoutPermission4337 = + ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + + controllerWithout4337Permission.address.slice(2); + await universalProfile.setData(dataKeyWithoutPermission4337, ALL_PERMISSIONS); + + const dataKeyWithOnlyPermission4337 = + ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + + controllerWithOnly4337Permission.address.slice(2); + + await universalProfile.setData(dataKeyWithOnlyPermission4337, Permission4337); + + // execute calldata + transferCallData = universalProfile.interface.encodeFunctionData('execute', [ + OPERATION_TYPES.CALL, + ethers.constants.AddressZero, + amountToTransfer, + '0x1234', + ]); + + // stake on entrypoint + const stakeAmount = parseEther('1'); + await entryPoint.depositTo(universalProfileAddress, { value: stakeAmount }); + }); + + it('should pass', async function () { + const addressZeroBalanceBefore = await getBalance(ethers.constants.AddressZero); + + const userOperation = await fillAndSign( + { + sender: universalProfileAddress, + callData: transferCallData, + }, + controllerWith4337Permission, + entryPoint, + ); + + await entryPoint.handleOps([userOperation], bundler.address); + + const addressZeroBalanceAfter = await getBalance(ethers.constants.AddressZero); + + expect(addressZeroBalanceAfter - addressZeroBalanceBefore).to.eq(amountToTransfer); + }); + + it('should fail when calling from wrong entrypoint', async function () { + const anotherEntryPoint = await new EntryPoint__factory(deployer).deploy(); + + const userOperation = await fillAndSign( + { + sender: universalProfileAddress, + callData: transferCallData, + }, + controllerWith4337Permission, + entryPoint, + ); + + const opIndex = 0; //index into the array of ops to the failed one (in simulateValidation, this is always zero). + + await expect(anotherEntryPoint.handleOps([userOperation], bundler.address)) + .to.be.revertedWithCustomError(entryPoint, 'FailedOp') + .withArgs(opIndex, 'AA23 reverted: Only EntryPoint contract can call this'); + }); + + it('should fail when controller does not have 4337 permission', async function () { + const anotherEntryPoint = await new EntryPoint__factory(deployer).deploy(); + + const userOperation = await fillAndSign( + { + sender: universalProfileAddress, + callData: transferCallData, + }, + controllerWithout4337Permission, + entryPoint, + ); + + await expect( + anotherEntryPoint.handleOps([userOperation], bundler.address), + ).to.be.revertedWithCustomError(entryPoint, 'FailedOp'); + }); + + it('should fail when controller only has 4337 permission', async function () { + const userOperation = await fillAndSign( + { + sender: universalProfileAddress, + callData: transferCallData, + }, + controllerWithOnly4337Permission, + entryPoint, + ); + await expect( + entryPoint.handleOps([userOperation], bundler.address), + ).to.be.revertedWithCustomError(entryPoint, 'FailedOp'); + }); + + it('should fail on invalid userop', async function () { + let op = await fillAndSign( + { + sender: universalProfileAddress, + callData: transferCallData, + nonce: 1234, + }, + controllerWith4337Permission, + entryPoint, + ); + + await expect(entryPoint.handleOps([op], bundler.address)) + .to.revertedWithCustomError(entryPoint, 'FailedOp') + .withArgs(0, 'AA25 invalid account nonce'); + + op = await fillAndSign( + { + sender: universalProfileAddress, + callData: transferCallData, + }, + controllerWith4337Permission, + entryPoint, + ); + + // invalidate the signature + op.callGasLimit = 1; + await expect(entryPoint.handleOps([op], bundler.address)) + .to.revertedWithCustomError(entryPoint, 'FailedOp') + .withArgs(0, 'AA24 signature error'); + }); +}); diff --git a/tests/LSP17Extensions/helpers/Create2Factory.ts b/tests/LSP17Extensions/helpers/Create2Factory.ts new file mode 100644 index 000000000..abf7a43bd --- /dev/null +++ b/tests/LSP17Extensions/helpers/Create2Factory.ts @@ -0,0 +1,124 @@ +// from: https://github.com/Arachnid/deterministic-deployment-proxy +import { BigNumber, BigNumberish, ethers, Signer } from 'ethers'; +import { arrayify, hexConcat, hexlify, hexZeroPad, keccak256 } from 'ethers/lib/utils'; +import { Provider } from '@ethersproject/providers'; +import { TransactionRequest } from '@ethersproject/abstract-provider'; + +export class Create2Factory { + factoryDeployed = false; + + // from: https://github.com/Arachnid/deterministic-deployment-proxy + static readonly contractAddress = '0x4e59b44847b379578588920ca78fbf26c0b4956c'; + static readonly factoryTx = + '0xf8a58085174876e800830186a08080b853604580600e600039806000f350fe7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe03601600081602082378035828234f58015156039578182fd5b8082525050506014600cf31ba02222222222222222222222222222222222222222222222222222222222222222a02222222222222222222222222222222222222222222222222222222222222222'; + static readonly factoryDeployer = '0x3fab184622dc19b6109349b94811493bf2a45362'; + static readonly deploymentGasPrice = 100e9; + static readonly deploymentGasLimit = 100000; + static readonly factoryDeploymentFee = ( + Create2Factory.deploymentGasPrice * Create2Factory.deploymentGasLimit + ).toString(); + + constructor( + readonly provider: Provider, + readonly signer = (provider as ethers.providers.JsonRpcProvider).getSigner(), + ) {} + + /** + * deploy a contract using our deterministic deployer. + * The deployer is deployed (unless it is already deployed) + * NOTE: this transaction will fail if already deployed. use getDeployedAddress to check it first. + * @param initCode delpoyment code. can be a hex string or factory.getDeploymentTransaction(..) + * @param salt specific salt for deployment + * @param gasLimit gas limit or 'estimate' to use estimateGas. by default, calculate gas based on data size. + */ + async deploy( + initCode: string | TransactionRequest, + salt: BigNumberish = 0, + gasLimit?: BigNumberish | 'estimate', + ): Promise { + await this.deployFactory(); + if (typeof initCode !== 'string') { + initCode = (initCode as TransactionRequest).data.toString(); + } + + const addr = Create2Factory.getDeployedAddress(initCode, salt); + if ((await this.provider.getCode(addr).then((code) => code.length)) > 2) { + return addr; + } + + const deployTx = { + to: Create2Factory.contractAddress, + data: this.getDeployTransactionCallData(initCode, salt), + }; + if (gasLimit === 'estimate') { + gasLimit = await this.signer.estimateGas(deployTx); + } + + if (gasLimit === undefined) { + gasLimit = + arrayify(initCode) + .map((x) => (x === 0 ? 4 : 16)) + .reduce((sum, x) => sum + x) + + (200 * initCode.length) / 2 + // actual is usually somewhat smaller (only deposited code, not entire constructor) + 6 * Math.ceil(initCode.length / 64) + // hash price. very minor compared to deposit costs + 32000 + + 21000; + + // deployer requires some extra gas + gasLimit = Math.floor((gasLimit * 64) / 63); + } + + const ret = await this.signer.sendTransaction({ ...deployTx, gasLimit }); + await ret.wait(); + if ((await this.provider.getCode(addr).then((code) => code.length)) === 2) { + throw new Error('failed to deploy'); + } + return addr; + } + + getDeployTransactionCallData(initCode: string, salt: BigNumberish = 0): string { + const saltBytes32 = hexZeroPad(hexlify(salt), 32); + return hexConcat([saltBytes32, initCode]); + } + + /** + * return the deployed address of this code. + * (the deployed address to be used by deploy() + * @param initCode + * @param salt + */ + static getDeployedAddress(initCode: string, salt: BigNumberish): string { + const saltBytes32 = hexZeroPad(hexlify(salt), 32); + return ( + '0x' + + keccak256( + hexConcat(['0xff', Create2Factory.contractAddress, saltBytes32, keccak256(initCode)]), + ).slice(-40) + ); + } + + // deploy the factory, if not already deployed. + async deployFactory(signer?: Signer): Promise { + if (await this._isFactoryDeployed()) { + return; + } + await (signer ?? this.signer).sendTransaction({ + to: Create2Factory.factoryDeployer, + value: BigNumber.from(Create2Factory.factoryDeploymentFee), + }); + await this.provider.sendTransaction(Create2Factory.factoryTx); + if (!(await this._isFactoryDeployed())) { + throw new Error('fatal: failed to deploy deterministic deployer'); + } + } + + async _isFactoryDeployed(): Promise { + if (!this.factoryDeployed) { + const deployed = await this.provider.getCode(Create2Factory.contractAddress); + if (deployed.length > 2) { + this.factoryDeployed = true; + } + } + return this.factoryDeployed; + } +} diff --git a/tests/LSP17Extensions/helpers/UserOp.ts b/tests/LSP17Extensions/helpers/UserOp.ts new file mode 100644 index 000000000..cb8b3141e --- /dev/null +++ b/tests/LSP17Extensions/helpers/UserOp.ts @@ -0,0 +1,251 @@ +import { arrayify, defaultAbiCoder, hexDataSlice, keccak256 } from 'ethers/lib/utils'; +import { BigNumber, Wallet } from 'ethers'; +import { AddressZero, callDataCost } from './utils'; +import { ecsign, toRpcSig, keccak256 as keccak256_buffer } from 'ethereumjs-util'; +import { Create2Factory } from './Create2Factory'; +import { EntryPoint } from '@account-abstraction/contracts'; +import { ethers } from 'ethers'; +import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'; +import * as typ from './solidityTypes'; + +export interface UserOperation { + sender: typ.address; + nonce: typ.uint256; + initCode: typ.bytes; + callData: typ.bytes; + callGasLimit: typ.uint256; + verificationGasLimit: typ.uint256; + preVerificationGas: typ.uint256; + maxFeePerGas: typ.uint256; + maxPriorityFeePerGas: typ.uint256; + paymasterAndData: typ.bytes; + signature: typ.bytes; +} + +export function packUserOp(op: UserOperation, forSignature = true): string { + if (forSignature) { + // Encoding the UserOperation object fields into a single string for signature + return defaultAbiCoder.encode( + [ + 'address', + 'uint256', + 'bytes32', + 'bytes32', + 'uint256', + 'uint256', + 'uint256', + 'uint256', + 'uint256', + 'bytes32', + ], + [ + op.sender, + op.nonce, + keccak256(op.initCode), + keccak256(op.callData), + op.callGasLimit, + op.verificationGasLimit, + op.preVerificationGas, + op.maxFeePerGas, + op.maxPriorityFeePerGas, + keccak256(op.paymasterAndData), + ], + ); + } else { + // Encoding the UserOperation object fields into a single string including the signature + return defaultAbiCoder.encode( + [ + 'address', + 'uint256', + 'bytes', + 'bytes', + 'uint256', + 'uint256', + 'uint256', + 'uint256', + 'uint256', + 'bytes', + 'bytes', + ], + [ + op.sender, + op.nonce, + op.initCode, + op.callData, + op.callGasLimit, + op.verificationGasLimit, + op.preVerificationGas, + op.maxFeePerGas, + op.maxPriorityFeePerGas, + op.paymasterAndData, + op.signature, + ], + ); + } +} + +export function getUserOpHash(op: UserOperation, entryPoint: string, chainId: number): string { + const userOpHash = keccak256(packUserOp(op, true)); + // Encoding the UserOperation hash, entryPoint address, and chainId for final hash computation + const enc = defaultAbiCoder.encode( + ['bytes32', 'address', 'uint256'], + [userOpHash, entryPoint, chainId], + ); + return keccak256(enc); +} + +export const DefaultsForUserOp: UserOperation = { + sender: AddressZero, + nonce: 0, + initCode: '0x', + callData: '0x', + callGasLimit: 0, + verificationGasLimit: 250000, + preVerificationGas: 21000, + maxFeePerGas: 0, + maxPriorityFeePerGas: 1e9, + paymasterAndData: '0x', + signature: '0x', +}; + +export function signUserOp( + op: UserOperation, + signer: Wallet, + entryPoint: string, + chainId: number, +): UserOperation { + const message = getUserOpHash(op, entryPoint, chainId); + const msg1 = Buffer.concat([ + Buffer.from('\x19Ethereum Signed Message:\n32', 'ascii'), + Buffer.from(arrayify(message)), + ]); + + const sig = ecsign(keccak256_buffer(msg1), Buffer.from(arrayify(signer.privateKey))); + // that's equivalent of: await signer.signMessage(message); + // (but without "async" + const signedMessage1 = toRpcSig(sig.v, sig.r, sig.s); + return { + ...op, + signature: signedMessage1, + }; +} + +export function fillUserOpDefaults( + op: Partial, + defaults = DefaultsForUserOp, +): UserOperation { + const partial: any = { ...op }; + + for (const key in partial) { + if (partial[key] == null) { + delete partial[key]; + } + } + const filled = { ...defaults, ...partial }; + return filled; +} + +// helper to fill structure: +// - default callGasLimit to estimate call from entryPoint to account +// if there is initCode: +// - calculate sender by eth_call the deployment code +// - default verificationGasLimit estimateGas of deployment code plus default 100000 +// no initCode: +// - update nonce from account.getNonce() +// entryPoint param is only required to fill in "sender address when specifying "initCode" +// nonce: assume contract as "getNonce()" function, and fill in. +// sender - only in case of construction: fill sender from initCode. +// callGasLimit: VERY crude estimation (by estimating call to account, and add rough entryPoint overhead +// verificationGasLimit: hard-code default at 100k. should add "create2" cost +export async function fillUserOp( + op: Partial, + signer: SignerWithAddress, + entryPoint?: EntryPoint, +): Promise { + const op1 = { ...op }; + const provider = entryPoint?.provider; + if (op.initCode != null) { + const initAddr = hexDataSlice(op1.initCode, 0, 20); + const initCallData = hexDataSlice(op1.initCode, 20); + if (op1.nonce == null) op1.nonce = 0; + if (op1.sender == null) { + if (initAddr.toLowerCase() === Create2Factory.contractAddress.toLowerCase()) { + const ctr = hexDataSlice(initCallData, 32); + const salt = hexDataSlice(initCallData, 0, 32); + op1.sender = Create2Factory.getDeployedAddress(ctr, salt); + } else { + if (provider == null) throw new Error('no entrypoint/provider'); + op1.sender = await entryPoint.callStatic + .getSenderAddress(op1.initCode) + .catch((e) => e.errorArgs.sender); + } + } + if (op1.verificationGasLimit == null) { + if (provider == null) throw new Error('no entrypoint/provider'); + const initEstimate = await provider.estimateGas({ + from: entryPoint?.address, + to: initAddr, + data: initCallData, + gasLimit: 10e6, + }); + op1.verificationGasLimit = BigNumber.from(DefaultsForUserOp.verificationGasLimit).add( + initEstimate, + ); + } + } + if (op1.nonce == null) { + if (provider == null) throw new Error('must have entryPoint to autofill nonce'); + + const signerKeyAsUint192 = ethers.BigNumber.from(signer.address).toHexString(); + + try { + op1.nonce = await entryPoint.getNonce(op1.sender, signerKeyAsUint192); + } catch { + op1.nonce = 0; + } + } + if (op1.callGasLimit == null && op.callData != null) { + if (provider == null) throw new Error('must have entryPoint for callGasLimit estimate'); + const gasEtimated = await provider.estimateGas({ + from: entryPoint?.address, + to: op1.sender, + data: op1.callData, + }); + + op1.callGasLimit = gasEtimated; + } + if (op1.maxFeePerGas == null) { + if (provider == null) throw new Error('must have entryPoint to autofill maxFeePerGas'); + const block = await provider.getBlock('latest'); + op1.maxFeePerGas = block.baseFeePerGas.add( + op1.maxPriorityFeePerGas ?? DefaultsForUserOp.maxPriorityFeePerGas, + ); + } + + if (op1.maxPriorityFeePerGas == null) { + op1.maxPriorityFeePerGas = DefaultsForUserOp.maxPriorityFeePerGas; + } + const op2 = fillUserOpDefaults(op1); + + if (op2.preVerificationGas.toString() === '0') { + op2.preVerificationGas = callDataCost(packUserOp(op2, false)); + } + return op2; +} + +export async function fillAndSign( + op: Partial, + signer: SignerWithAddress, + entryPoint?: EntryPoint, +): Promise { + const provider = entryPoint?.provider; + const op2 = await fillUserOp(op, signer, entryPoint); + + const chainId = await provider.getNetwork().then((net) => net.chainId); + const message = arrayify(getUserOpHash(op2, entryPoint.address, chainId)); + + return { + ...op2, + signature: await signer.signMessage(message), + }; +} diff --git a/tests/LSP17Extensions/helpers/solidityTypes.ts b/tests/LSP17Extensions/helpers/solidityTypes.ts new file mode 100644 index 000000000..5663a0166 --- /dev/null +++ b/tests/LSP17Extensions/helpers/solidityTypes.ts @@ -0,0 +1,10 @@ +// define the same export types as used by export typechain/ethers +import { BigNumberish } from 'ethers'; +import { BytesLike } from '@ethersproject/bytes'; + +export type address = string; +export type uint256 = BigNumberish; +export type uint = BigNumberish; +export type uint48 = BigNumberish; +export type bytes = BytesLike; +export type bytes32 = BytesLike; diff --git a/tests/LSP17Extensions/helpers/utils.ts b/tests/LSP17Extensions/helpers/utils.ts new file mode 100644 index 000000000..10c338d83 --- /dev/null +++ b/tests/LSP17Extensions/helpers/utils.ts @@ -0,0 +1,32 @@ +import { ethers } from 'hardhat'; +import { Create2Factory } from './Create2Factory'; +import { EntryPoint__factory, EntryPoint } from '@account-abstraction/contracts'; + +export const AddressZero = ethers.constants.AddressZero; + +export function callDataCost(data: string): number { + return ethers.utils + .arrayify(data) + .map((x) => (x === 0 ? 4 : 16)) + .reduce((sum, x) => sum + x); +} + +export async function deployEntryPoint(provider = ethers.provider): Promise { + const create2factory = new Create2Factory(provider); + const addr = await create2factory.deploy( + EntryPoint__factory.bytecode, + 0, + process.env.COVERAGE != null ? 20e6 : 8e6, + ); + return EntryPoint__factory.connect(addr, provider.getSigner()); +} + +export async function getBalance(address: string): Promise { + const balance = await ethers.provider.getBalance(address); + return parseInt(balance.toString()); +} + +export async function isDeployed(addr: string): Promise { + const code = await ethers.provider.getCode(addr); + return code.length > 2; +} diff --git a/tests/LSP1UniversalReceiver/LSP1UniversalReceiverDelegateUP.behaviour.ts b/tests/LSP1UniversalReceiver/LSP1UniversalReceiverDelegateUP.behaviour.ts index adf8bc19c..2fdfdfb82 100644 --- a/tests/LSP1UniversalReceiver/LSP1UniversalReceiverDelegateUP.behaviour.ts +++ b/tests/LSP1UniversalReceiver/LSP1UniversalReceiverDelegateUP.behaviour.ts @@ -30,7 +30,13 @@ import { import { ARRAY_LENGTH, LSP1_HOOK_PLACEHOLDER, abiCoder } from '../utils/helpers'; // constants -import { ERC725YDataKeys, INTERFACE_IDS, OPERATION_TYPES, LSP1_TYPE_IDS } from '../../constants'; +import { + ERC725YDataKeys, + INTERFACE_IDS, + OPERATION_TYPES, + LSP1_TYPE_IDS, + LSP8_TOKEN_ID_TYPES, +} from '../../constants'; // fixtures import { callPayload, getLSP5MapAndArrayKeysValue, setupKeyManager } from '../utils/fixtures'; @@ -1120,7 +1126,7 @@ export const shouldBehaveLikeLSP1Delegate = ( before(async () => { const setDataPayload = context.universalProfile1.interface.encodeFunctionData('setData', [ lsp5ArrayLengthDataKey, - lsp5ArrayLengthDataValue, + lsp5ArrayLengthDataValue.toHexString(), ]); await context.lsp6KeyManager1.connect(context.accounts.owner1).execute(setDataPayload); @@ -1167,7 +1173,7 @@ export const shouldBehaveLikeLSP1Delegate = ( before(async () => { const setDataPayload = context.universalProfile1.interface.encodeFunctionData('setData', [ lsp5ArrayLengthDataKey, - lsp5ArrayLengthDataValue, + lsp5ArrayLengthDataValue.toHexString(), ]); await context.lsp6KeyManager1.connect(context.accounts.owner1).execute(setDataPayload); @@ -1743,18 +1749,21 @@ export const shouldBehaveLikeLSP1Delegate = ( 'TokenAlpha', 'TA', context.accounts.random.address, + LSP8_TOKEN_ID_TYPES.UNIQUE_ID, ); lsp8TokenB = await new LSP8Tester__factory(context.accounts.random).deploy( 'TokenBeta', 'TB', context.accounts.random.address, + LSP8_TOKEN_ID_TYPES.UNIQUE_ID, ); lsp8TokenC = await new LSP8Tester__factory(context.accounts.random).deploy( 'TokenGamma', 'TA', context.accounts.random.address, + LSP8_TOKEN_ID_TYPES.UNIQUE_ID, ); }); @@ -2776,7 +2785,7 @@ export const shouldBehaveLikeLSP1Delegate = ( testContext = { accounts: signerAddresses, - owner: profileOwner, + mainController: profileOwner, universalProfile: deployedUniversalProfile, keyManager: deployedKeyManager, }; @@ -2794,7 +2803,9 @@ export const shouldBehaveLikeLSP1Delegate = ( [ERC725YDataKeys.LSP1.LSP1UniversalReceiverDelegate, lsp1Delegate.address], ); - await testContext.keyManager.connect(testContext.owner).execute(setLSP1DelegatePayload); + await testContext.keyManager + .connect(testContext.mainController) + .execute(setLSP1DelegatePayload); }); it('check that the LSP9Vault address is not set under LSP10', async () => { @@ -2839,7 +2850,7 @@ export const shouldBehaveLikeLSP1Delegate = ( [OPERATION_TYPES.CALL, vault.address, 0, transferOwnershipPayload], ); - await testContext.keyManager.connect(testContext.owner).execute(executePayload); + await testContext.keyManager.connect(testContext.mainController).execute(executePayload); // check that the new vault owner is the pending owner expect(await vault.pendingOwner()).to.equal(newVaultOwner.address); @@ -3069,6 +3080,7 @@ export const shouldBehaveLikeLSP1Delegate = ( 'MyToken', 'MTK', context.universalProfile1.address, + LSP8_TOKEN_ID_TYPES.NUMBER, ); // Mint token for UP1 await LSP8.mint(context.universalProfile1.address, '0x' + '0'.repeat(64), true, '0x'); @@ -3155,22 +3167,4 @@ export const shouldInitializeLikeLSP1Delegate = ( ); }); }); - describe('edge cases', () => { - describe('when sending value to universalReceiver(...)', () => { - it('should revert with custom error', async () => { - // value of 1 ethers - const value = ethers.utils.parseEther('1'); - await expect( - context.lsp1universalReceiverDelegateUP.universalReceiver( - LSP1_TYPE_IDS.LSP7Tokens_RecipientNotification, - '0x', - { value }, - ), - ).to.be.revertedWithCustomError( - context.lsp1universalReceiverDelegateUP, - 'NativeTokensNotAccepted', - ); - }); - }); - }); }; diff --git a/tests/LSP1UniversalReceiver/LSP1UniversalReceiverDelegateVault.behaviour.ts b/tests/LSP1UniversalReceiver/LSP1UniversalReceiverDelegateVault.behaviour.ts index 866dab1d6..727852c19 100644 --- a/tests/LSP1UniversalReceiver/LSP1UniversalReceiverDelegateVault.behaviour.ts +++ b/tests/LSP1UniversalReceiver/LSP1UniversalReceiverDelegateVault.behaviour.ts @@ -19,7 +19,13 @@ import { import { ARRAY_LENGTH, LSP1_HOOK_PLACEHOLDER, abiCoder } from '../utils/helpers'; // constants -import { ERC725YDataKeys, INTERFACE_IDS, OPERATION_TYPES, LSP1_TYPE_IDS } from '../../constants'; +import { + ERC725YDataKeys, + INTERFACE_IDS, + OPERATION_TYPES, + LSP1_TYPE_IDS, + LSP8_TOKEN_ID_TYPES, +} from '../../constants'; import { callPayload, getLSP5MapAndArrayKeysValue } from '../utils/fixtures'; import { BigNumber, BytesLike, Transaction } from 'ethers'; @@ -74,9 +80,9 @@ export const shouldBehaveLikeLSP1Delegate = (buildContext: () => Promise { + it('should support LSP1Delegate interface', async () => { const result = await context.lsp1universalReceiverDelegateVault.supportsInterface( - INTERFACE_IDS.LSP1UniversalReceiver, + INTERFACE_IDS.LSP1UniversalReceiverDelegate, ); expect(result).to.be.true; }); @@ -1151,18 +1157,21 @@ export const shouldBehaveLikeLSP1Delegate = (buildContext: () => Promise Promise Promise Promise Promise Promise Promise Promise Promise { - let ownerContract: FallbackReturnMagicValue; + describe('that implement the fallback that return the success value', () => { + let ownerContract: FallbackReturnSuccessValue; before('deploying a new owner', async () => { - ownerContract = await new FallbackReturnMagicValue__factory( + ownerContract = await new FallbackReturnSuccessValue__factory( context.deployParams.owner, ).deploy(); @@ -307,10 +307,10 @@ export const shouldBehaveLikeLSP20 = (buildContext: () => Promise { - let ownerContract: FirstCallReturnExpandedInvalidValue; + let ownerContract: FirstCallReturnExpandedFailValue; before('deploying a new owner', async () => { - ownerContract = await new FirstCallReturnExpandedInvalidValue__factory( + ownerContract = await new FirstCallReturnExpandedFailValue__factory( context.deployParams.owner, ).deploy(); @@ -335,15 +335,15 @@ export const shouldBehaveLikeLSP20 = (buildContext: () => Promise { - let ownerContract: FirstCallReturnInvalidMagicValue; + describe("that implements verifyCall but doesn't return success value", () => { + let ownerContract: FirstCallReturnFailValue; before('deploying a new owner', async () => { - ownerContract = await new FirstCallReturnInvalidMagicValue__factory( + ownerContract = await new FirstCallReturnFailValue__factory( context.deployParams.owner, ).deploy(); @@ -367,23 +367,23 @@ export const shouldBehaveLikeLSP20 = (buildContext: () => Promise { - let firstCallReturnMagicValueContract: FakeContract; + describe("that implements verifyCall that returns a valid success value but doesn't invoke verifyCallResult", () => { + let firstCallReturnSuccessValueContract: FakeContract; let newUniversalProfile: UniversalProfile; before(async () => { - firstCallReturnMagicValueContract = await smock.fake(ILSP20CallVerifier__factory.abi); - firstCallReturnMagicValueContract.lsp20VerifyCall.returns( - LSP20_MAGIC_VALUES.VERIFY_CALL.NO_POST_VERIFICATION, + firstCallReturnSuccessValueContract = await smock.fake(ILSP20CallVerifier__factory.abi); + firstCallReturnSuccessValueContract.lsp20VerifyCall.returns( + LSP20_SUCCESS_VALUES.VERIFY_CALL.NO_POST_VERIFICATION, ); newUniversalProfile = await new UniversalProfile__factory(context.accounts[0]).deploy( - firstCallReturnMagicValueContract.address, + firstCallReturnSuccessValueContract.address, ); }); @@ -400,21 +400,21 @@ export const shouldBehaveLikeLSP20 = (buildContext: () => Promise { - let firstCallReturnMagicValueContract: FakeContract; + describe('that implements verifyCall that returns a valid success value with additional data after the first 32 bytes', () => { + let firstCallReturnSuccessValueContract: FakeContract; let newUniversalProfile: UniversalProfile; before(async () => { - firstCallReturnMagicValueContract = await smock.fake(ILSP20CallVerifier__factory.abi); - firstCallReturnMagicValueContract.lsp20VerifyCall.returns( - LSP20_MAGIC_VALUES.VERIFY_CALL.NO_POST_VERIFICATION + + firstCallReturnSuccessValueContract = await smock.fake(ILSP20CallVerifier__factory.abi); + firstCallReturnSuccessValueContract.lsp20VerifyCall.returns( + LSP20_SUCCESS_VALUES.VERIFY_CALL.NO_POST_VERIFICATION + '0'.repeat(56) + '0xcafecafecafecafecafecafecafecafecafecafe' + '0'.repeat(24), ); newUniversalProfile = await new UniversalProfile__factory(context.accounts[0]).deploy( - firstCallReturnMagicValueContract.address, + firstCallReturnSuccessValueContract.address, ); }); @@ -433,21 +433,21 @@ export const shouldBehaveLikeLSP20 = (buildContext: () => Promise { - let bothCallReturnMagicValueContract: FakeContract; + describe('that implements verifyCall and verifyCallResult and both return success value', () => { + let bothCallReturnSuccessValueContract: FakeContract; let newUniversalProfile: UniversalProfile; before(async () => { - bothCallReturnMagicValueContract = await smock.fake(ILSP20CallVerifier__factory.abi); - bothCallReturnMagicValueContract.lsp20VerifyCall.returns( - LSP20_MAGIC_VALUES.VERIFY_CALL.WITH_POST_VERIFICATION, + bothCallReturnSuccessValueContract = await smock.fake(ILSP20CallVerifier__factory.abi); + bothCallReturnSuccessValueContract.lsp20VerifyCall.returns( + LSP20_SUCCESS_VALUES.VERIFY_CALL.WITH_POST_VERIFICATION, ); - bothCallReturnMagicValueContract.lsp20VerifyCallResult.returns( - LSP20_MAGIC_VALUES.VERIFY_CALL_RESULT, + bothCallReturnSuccessValueContract.lsp20VerifyCallResult.returns( + LSP20_SUCCESS_VALUES.VERIFY_CALL_RESULT, ); newUniversalProfile = await new UniversalProfile__factory(context.accounts[0]).deploy( - bothCallReturnMagicValueContract.address, + bothCallReturnSuccessValueContract.address, ); }); @@ -464,27 +464,27 @@ export const shouldBehaveLikeLSP20 = (buildContext: () => Promise { - let bothCallReturnMagicValueContract: FakeContract; + describe('that implements verifyCall and verifyCallResult and both return success value plus additional data', () => { + let bothCallReturnSuccessValueContract: FakeContract; let newUniversalProfile: UniversalProfile; before(async () => { - bothCallReturnMagicValueContract = await smock.fake(ILSP20CallVerifier__factory.abi); - bothCallReturnMagicValueContract.lsp20VerifyCall.returns( - LSP20_MAGIC_VALUES.VERIFY_CALL.WITH_POST_VERIFICATION + + bothCallReturnSuccessValueContract = await smock.fake(ILSP20CallVerifier__factory.abi); + bothCallReturnSuccessValueContract.lsp20VerifyCall.returns( + LSP20_SUCCESS_VALUES.VERIFY_CALL.WITH_POST_VERIFICATION + '0'.repeat(56) + '0xcafecafecafecafecafecafecafecafecafecafe' + '0'.repeat(24), ); - bothCallReturnMagicValueContract.lsp20VerifyCallResult.returns( - LSP20_MAGIC_VALUES.VERIFY_CALL_RESULT + + bothCallReturnSuccessValueContract.lsp20VerifyCallResult.returns( + LSP20_SUCCESS_VALUES.VERIFY_CALL_RESULT + '0'.repeat(56) + '0xcafecafecafecafecafecafecafecafecafecafe' + '0'.repeat(24), ); newUniversalProfile = await new UniversalProfile__factory(context.accounts[0]).deploy( - bothCallReturnMagicValueContract.address, + bothCallReturnSuccessValueContract.address, ); }); @@ -503,14 +503,14 @@ export const shouldBehaveLikeLSP20 = (buildContext: () => Promise { + describe('that implements verifyCallResult but return fail value', () => { let secondCallReturnFailureContract: FakeContract; let newUniversalProfile: UniversalProfile; before(async () => { secondCallReturnFailureContract = await smock.fake(ILSP20CallVerifier__factory.abi); secondCallReturnFailureContract.lsp20VerifyCall.returns( - LSP20_MAGIC_VALUES.VERIFY_CALL.WITH_POST_VERIFICATION, + LSP20_SUCCESS_VALUES.VERIFY_CALL.WITH_POST_VERIFICATION, ); secondCallReturnFailureContract.lsp20VerifyCallResult.returns('0x00000000'); @@ -525,23 +525,23 @@ export const shouldBehaveLikeLSP20 = (buildContext: () => Promise { + describe('that implements verifyCallResult but return an expanded success value', () => { let secondCallReturnExpandedValueContract: FakeContract; let newUniversalProfile: UniversalProfile; before(async () => { secondCallReturnExpandedValueContract = await smock.fake(ILSP20CallVerifier__factory.abi); secondCallReturnExpandedValueContract.lsp20VerifyCall.returns( - LSP20_MAGIC_VALUES.VERIFY_CALL.WITH_POST_VERIFICATION, + LSP20_SUCCESS_VALUES.VERIFY_CALL.WITH_POST_VERIFICATION, ); secondCallReturnExpandedValueContract.lsp20VerifyCallResult.returns( ethers.utils.solidityPack( ['bytes4', 'bytes28'], - [LSP20_MAGIC_VALUES.VERIFY_CALL_RESULT, '0x' + '0'.repeat(56)], + [LSP20_SUCCESS_VALUES.VERIFY_CALL_RESULT, '0x' + '0'.repeat(56)], ), ); diff --git a/tests/LSP20CallVerification/LSP20WithLSP14.behaviour.ts b/tests/LSP20CallVerification/LSP20WithLSP14.behaviour.ts index 46942cc58..860389cd7 100644 --- a/tests/LSP20CallVerification/LSP20WithLSP14.behaviour.ts +++ b/tests/LSP20CallVerification/LSP20WithLSP14.behaviour.ts @@ -19,7 +19,7 @@ export type LSP14CombinedWithLSP20TestContext = { accounts: SignerWithAddress[]; contract: LSP0ERC725Account; deployParams: { owner: SignerWithAddress }; - onlyOwnerRevertString: string; + onlyOwnerCustomError: string; }; export const shouldBehaveLikeLSP14WithLSP20 = ( @@ -75,7 +75,7 @@ export const shouldBehaveLikeLSP14WithLSP20 = ( context.contract .connect(context.deployParams.owner) .transferOwnership(context.contract.address), - ).to.be.revertedWithCustomError(context.contract, 'CannotTransferOwnershipToSelf'); + ).to.be.revertedWithCustomError(context.contract, 'LSP14CannotTransferOwnershipToSelf'); }); describe('it should still be allowed to call onlyOwner functions', () => { @@ -144,16 +144,17 @@ export const shouldBehaveLikeLSP14WithLSP20 = ( const randomAddress = context.accounts[2]; await expect(context.contract.connect(randomAddress).transferOwnership(randomAddress.address)) - .to.be.revertedWithCustomError(context.contract, 'LSP20InvalidMagicValue') - .withArgs(false, '0x'); + .to.be.revertedWithCustomError(context.contract, 'LSP20EOACannotVerifyCall') + .withArgs(context.deployParams.owner.address); }); }); describe('when calling acceptOwnership(...)', () => { it('should revert when caller is not the pending owner', async () => { - await expect( - context.contract.connect(context.accounts[2]).acceptOwnership(), - ).to.be.revertedWith('LSP14: caller is not the pendingOwner'); + const pendingOwner = await context.contract.pendingOwner(); + await expect(context.contract.connect(context.accounts[2]).acceptOwnership()) + .to.be.revertedWithCustomError(context.contract, 'LSP20EOACannotVerifyCall') + .withArgs(pendingOwner); }); describe('when caller is the pending owner', () => { @@ -196,8 +197,8 @@ export const shouldBehaveLikeLSP14WithLSP20 = ( const value = '0xabcd'; await expect(context.contract.connect(previousOwner).setData(key, value)) - .to.be.to.be.revertedWithCustomError(context.contract, 'LSP20InvalidMagicValue') - .withArgs(false, '0x'); + .to.be.to.be.revertedWithCustomError(context.contract, 'LSP20EOACannotVerifyCall') + .withArgs(newOwner.address); }); it('should revert when calling `execute(...)`', async () => { @@ -209,14 +210,14 @@ export const shouldBehaveLikeLSP14WithLSP20 = ( .connect(previousOwner) .execute(OPERATION_TYPES.CALL, recipient.address, amount, '0x'), ) - .to.be.revertedWithCustomError(context.contract, 'LSP20InvalidMagicValue') - .withArgs(false, '0x'); + .to.be.revertedWithCustomError(context.contract, 'LSP20EOACannotVerifyCall') + .withArgs(newOwner.address); }); it('should revert when calling `renounceOwnership(...)`', async () => { await expect(context.contract.connect(previousOwner).renounceOwnership()) - .to.be.revertedWithCustomError(context.contract, 'LSP20InvalidMagicValue') - .withArgs(false, '0x'); + .to.be.revertedWithCustomError(context.contract, 'LSP20EOACannotVerifyCall') + .withArgs(newOwner.address); }); }); @@ -257,8 +258,8 @@ export const shouldBehaveLikeLSP14WithLSP20 = ( const tx = context.contract.connect(context.accounts[5]).renounceOwnership(); await expect(tx) - .to.be.revertedWithCustomError(context.contract, 'LSP20InvalidMagicValue') - .withArgs(false, '0x'); + .to.be.revertedWithCustomError(context.contract, 'LSP20EOACannotVerifyCall') + .withArgs(newOwner.address); }); }); @@ -294,6 +295,8 @@ export const shouldBehaveLikeLSP14WithLSP20 = ( ) )?.output.contracts[ 'contracts/LSP0ERC725Account/LSP0ERC725Account.sol' + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore ].LSP0ERC725Account.storageLayout.storage.filter((elem) => { if (elem.label === '_renounceOwnershipStartedAt') return elem; })[0].slot, @@ -363,7 +366,7 @@ export const shouldBehaveLikeLSP14WithLSP20 = ( await network.provider.send('hardhat_mine', [ethers.utils.hexValue(98)]); await expect(context.contract.connect(context.deployParams.owner).renounceOwnership()) - .to.be.revertedWithCustomError(context.contract, 'NotInRenounceOwnershipInterval') + .to.be.revertedWithCustomError(context.contract, 'LSP14NotInRenounceOwnershipInterval') .withArgs( renounceOwnershipOnceReceipt.blockNumber + 200, renounceOwnershipOnceReceipt.blockNumber + 400, @@ -383,6 +386,8 @@ export const shouldBehaveLikeLSP14WithLSP20 = ( ) )?.output.contracts[ 'contracts/LSP0ERC725Account/LSP0ERC725Account.sol' + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore ].LSP0ERC725Account.storageLayout.storage.filter((elem) => { if (elem.label === '_renounceOwnershipStartedAt') return elem; })[0].slot, @@ -449,6 +454,8 @@ export const shouldBehaveLikeLSP14WithLSP20 = ( ) )?.output.contracts[ 'contracts/LSP0ERC725Account/LSP0ERC725Account.sol' + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore ].LSP0ERC725Account.storageLayout.storage.filter((elem) => { if (elem.label === '_renounceOwnershipStartedAt') return elem; })[0].slot, @@ -468,8 +475,8 @@ export const shouldBehaveLikeLSP14WithLSP20 = ( const value = ethers.utils.hexlify(ethers.utils.toUtf8Bytes('Random Value')); await expect(context.contract.connect(context.deployParams.owner).setData(key, value)) - .to.be.revertedWithCustomError(context.contract, 'LSP20InvalidMagicValue') - .withArgs(false, '0x'); + .to.be.revertedWithCustomError(context.contract, 'LSP20EOACannotVerifyCall') + .withArgs(ethers.constants.AddressZero); }); it('transfer LYX via `execute(...)`', async () => { @@ -481,8 +488,8 @@ export const shouldBehaveLikeLSP14WithLSP20 = ( .connect(context.deployParams.owner) .execute(OPERATION_TYPES.CALL, recipient, amount, '0x'), ) - .to.be.revertedWithCustomError(context.contract, 'LSP20InvalidMagicValue') - .withArgs(false, '0x'); + .to.be.revertedWithCustomError(context.contract, 'LSP20EOACannotVerifyCall') + .withArgs(ethers.constants.AddressZero); }); }); }); @@ -515,9 +522,10 @@ export const shouldBehaveLikeLSP14WithLSP20 = ( }); it('previous pendingOwner should not be able to call acceptOwnership(...) anymore', async () => { - await expect(context.contract.connect(newOwner).acceptOwnership()).to.be.revertedWith( - 'LSP14: caller is not the pendingOwner', - ); + const pendingOwner = await context.contract.pendingOwner(); + await expect(context.contract.connect(newOwner).acceptOwnership()) + .to.be.revertedWithCustomError(context.contract, 'LSP20EOACannotVerifyCall') + .withArgs(pendingOwner); }); }); }); @@ -540,6 +548,8 @@ export const shouldBehaveLikeLSP14WithLSP20 = ( ) )?.output.contracts[ 'contracts/LSP0ERC725Account/LSP0ERC725Account.sol' + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore ].LSP0ERC725Account.storageLayout.storage.filter((elem) => { if (elem.label === '_renounceOwnershipStartedAt') return elem; })[0].slot, diff --git a/tests/LSP20CallVerification/LSP6/Admin/PermissionChangeAddExtensions.test.ts b/tests/LSP20CallVerification/LSP6/Admin/PermissionChangeAddExtensions.test.ts index 75363c66e..7b96c5875 100644 --- a/tests/LSP20CallVerification/LSP6/Admin/PermissionChangeAddExtensions.test.ts +++ b/tests/LSP20CallVerification/LSP6/Admin/PermissionChangeAddExtensions.test.ts @@ -80,7 +80,8 @@ export const shouldBehaveLikePermissionChangeOrAddExtensions = ( canOnlyCall = context.accounts[6]; let permissionKeys = [ - ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + context.owner.address.substring(2), + ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + + context.mainController.address.substring(2), ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + canAddAndChangeExtensions.address.substring(2), ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + @@ -126,7 +127,7 @@ export const shouldBehaveLikePermissionChangeOrAddExtensions = ( permissionArrayValues = [ ethers.utils.hexZeroPad(ethers.utils.hexlify(7), 16), - context.owner.address, + context.mainController.address, canAddAndChangeExtensions.address, canOnlyAddExtensions.address, canOnlyChangeExtensions.address, @@ -150,7 +151,7 @@ export const shouldBehaveLikePermissionChangeOrAddExtensions = ( }; await context.universalProfile - .connect(context.owner) + .connect(context.mainController) .setData(payloadParam.dataKey, payloadParam.dataValue); const result = await context.universalProfile.getData(payloadParam.dataKey); @@ -164,7 +165,7 @@ export const shouldBehaveLikePermissionChangeOrAddExtensions = ( }; await context.universalProfile - .connect(context.owner) + .connect(context.mainController) .setData(payloadParam.dataKey, payloadParam.dataValue); const result = await context.universalProfile.getData(payloadParam.dataKey); @@ -178,7 +179,7 @@ export const shouldBehaveLikePermissionChangeOrAddExtensions = ( }; await context.universalProfile - .connect(context.owner) + .connect(context.mainController) .setData(payloadParam.dataKey, payloadParam.dataValue); const result = await context.universalProfile.getData(payloadParam.dataKey); @@ -348,7 +349,7 @@ export const shouldBehaveLikePermissionChangeOrAddExtensions = ( }; await context.universalProfile - .connect(context.owner) + .connect(context.mainController) .setData(payloadParam.dataKey, payloadParam.dataValue); }); it('should NOT be allowed to ADD another ExtensionHandler key', async () => { @@ -407,7 +408,7 @@ export const shouldBehaveLikePermissionChangeOrAddExtensions = ( }; await context.universalProfile - .connect(context.owner) + .connect(context.mainController) .setData(payloadParam.dataKey, payloadParam.dataValue); }); @@ -467,7 +468,7 @@ export const shouldBehaveLikePermissionChangeOrAddExtensions = ( }; await context.universalProfile - .connect(context.owner) + .connect(context.mainController) .setData(payloadParam.dataKey, payloadParam.dataValue); }); @@ -535,7 +536,7 @@ export const shouldBehaveLikePermissionChangeOrAddExtensions = ( }; await context.universalProfile - .connect(context.owner) + .connect(context.mainController) .setDataBatch(payloadParam.dataKeys, payloadParam.dataValues); const result = await context.universalProfile.getDataBatch(payloadParam.dataKeys); @@ -558,7 +559,7 @@ export const shouldBehaveLikePermissionChangeOrAddExtensions = ( }; await context.universalProfile - .connect(context.owner) + .connect(context.mainController) .setDataBatch(payloadParam.dataKeys, payloadParam.dataValues); const result = await context.universalProfile.getDataBatch(payloadParam.dataKeys); @@ -577,7 +578,7 @@ export const shouldBehaveLikePermissionChangeOrAddExtensions = ( }; await context.universalProfile - .connect(context.owner) + .connect(context.mainController) .setDataBatch(payloadParam.dataKeys, payloadParam.dataValues); const result = await context.universalProfile.getDataBatch(payloadParam.dataKeys); @@ -661,7 +662,7 @@ export const shouldBehaveLikePermissionChangeOrAddExtensions = ( }; await context.universalProfile - .connect(context.owner) + .connect(context.mainController) .setDataBatch(payloadParam.dataKeys, payloadParam.dataValues); }); describe('when adding multiple ExtensionHandler keys', () => { @@ -936,7 +937,7 @@ export const shouldBehaveLikePermissionChangeOrAddExtensions = ( }; await context.universalProfile - .connect(context.owner) + .connect(context.mainController) .setDataBatch(payloadParam.dataKeys, payloadParam.dataValues); }); describe('When adding ExtensionHandler key and one of his allowedERC725Y Data Key', () => { diff --git a/tests/LSP20CallVerification/LSP6/Admin/PermissionChangeAddURD.test.ts b/tests/LSP20CallVerification/LSP6/Admin/PermissionChangeAddURD.test.ts index 367d81cfb..d0dde441b 100644 --- a/tests/LSP20CallVerification/LSP6/Admin/PermissionChangeAddURD.test.ts +++ b/tests/LSP20CallVerification/LSP6/Admin/PermissionChangeAddURD.test.ts @@ -78,7 +78,8 @@ export const shouldBehaveLikePermissionChangeOrAddURD = ( canOnlyCall = context.accounts[6]; let permissionKeys = [ - ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + context.owner.address.substring(2), + ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + + context.mainController.address.substring(2), ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + canAddAndChangeUniversalReceiverDelegate.address.substring(2), ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + @@ -128,7 +129,7 @@ export const shouldBehaveLikePermissionChangeOrAddURD = ( permissionArrayValues = [ ethers.utils.hexZeroPad(ethers.utils.hexlify(7), 16), - context.owner.address, + context.mainController.address, canAddAndChangeUniversalReceiverDelegate.address, canOnlyAddUniversalReceiverDelegate.address, canOnlyChangeUniversalReceiverDelegate.address, @@ -152,7 +153,7 @@ export const shouldBehaveLikePermissionChangeOrAddURD = ( }; await context.universalProfile - .connect(context.owner) + .connect(context.mainController) .setData(payloadParam.dataKey, payloadParam.dataValue); const result = await context.universalProfile.getData(payloadParam.dataKey); @@ -166,7 +167,7 @@ export const shouldBehaveLikePermissionChangeOrAddURD = ( }; await context.universalProfile - .connect(context.owner) + .connect(context.mainController) .setData(payloadParam.dataKey, payloadParam.dataValue); const result = await context.universalProfile.getData(payloadParam.dataKey); @@ -180,7 +181,7 @@ export const shouldBehaveLikePermissionChangeOrAddURD = ( }; await context.universalProfile - .connect(context.owner) + .connect(context.mainController) .setData(payloadParam.dataKey, payloadParam.dataValue); const result = await context.universalProfile.getData(payloadParam.dataKey); @@ -362,7 +363,7 @@ export const shouldBehaveLikePermissionChangeOrAddURD = ( }; await context.universalProfile - .connect(context.owner) + .connect(context.mainController) .setData(payloadParam.dataKey, payloadParam.dataValue); }); it('should NOT be allowed to ADD another UniversalReceiverDelegate key', async () => { @@ -421,7 +422,7 @@ export const shouldBehaveLikePermissionChangeOrAddURD = ( }; await context.universalProfile - .connect(context.owner) + .connect(context.mainController) .setData(payloadParam.dataKey, payloadParam.dataValue); }); @@ -481,7 +482,7 @@ export const shouldBehaveLikePermissionChangeOrAddURD = ( }; await context.universalProfile - .connect(context.owner) + .connect(context.mainController) .setData(payloadParam.dataKey, payloadParam.dataValue); }); @@ -549,7 +550,7 @@ export const shouldBehaveLikePermissionChangeOrAddURD = ( }; await context.universalProfile - .connect(context.owner) + .connect(context.mainController) .setDataBatch(payloadParam.dataKeys, payloadParam.dataValues); const result = await context.universalProfile.getDataBatch(payloadParam.dataKeys); @@ -572,7 +573,7 @@ export const shouldBehaveLikePermissionChangeOrAddURD = ( }; await context.universalProfile - .connect(context.owner) + .connect(context.mainController) .setDataBatch(payloadParam.dataKeys, payloadParam.dataValues); const result = await context.universalProfile.getDataBatch(payloadParam.dataKeys); @@ -591,7 +592,7 @@ export const shouldBehaveLikePermissionChangeOrAddURD = ( }; await context.universalProfile - .connect(context.owner) + .connect(context.mainController) .setDataBatch(payloadParam.dataKeys, payloadParam.dataValues); const result = await context.universalProfile.getDataBatch(payloadParam.dataKeys); @@ -678,7 +679,7 @@ export const shouldBehaveLikePermissionChangeOrAddURD = ( }; await context.universalProfile - .connect(context.owner) + .connect(context.mainController) .setDataBatch(payloadParam.dataKeys, payloadParam.dataValues); }); describe('when adding multiple UniversalReceiverDelegate keys', () => { @@ -981,7 +982,7 @@ export const shouldBehaveLikePermissionChangeOrAddURD = ( }; await context.universalProfile - .connect(context.owner) + .connect(context.mainController) .setDataBatch(payloadParam.dataKeys, payloadParam.dataValues); }); describe('When adding UniversalReceiverDelegate key and one of his allowedERC725Y Data Key', () => { diff --git a/tests/LSP20CallVerification/LSP6/Admin/PermissionChangeOwner.test.ts b/tests/LSP20CallVerification/LSP6/Admin/PermissionChangeOwner.test.ts index da889fa28..961062800 100644 --- a/tests/LSP20CallVerification/LSP6/Admin/PermissionChangeOwner.test.ts +++ b/tests/LSP20CallVerification/LSP6/Admin/PermissionChangeOwner.test.ts @@ -1,6 +1,6 @@ import { expect } from 'chai'; -import { ethers } from 'hardhat'; -import { BigNumber } from 'ethers'; +import { ethers, network } from 'hardhat'; +import { BigNumber, ContractTransaction } from 'ethers'; import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'; // constants @@ -33,7 +33,7 @@ export const shouldBehaveLikePermissionChangeOwner = ( canChangeOwner = context.accounts[1]; cannotChangeOwner = context.accounts[2]; - newKeyManager = await new LSP6KeyManager__factory(context.owner).deploy( + newKeyManager = await new LSP6KeyManager__factory(context.mainController).deploy( context.universalProfile.address, ); @@ -54,7 +54,10 @@ export const shouldBehaveLikePermissionChangeOwner = ( context.universalProfile .connect(canChangeOwner) .transferOwnership(context.universalProfile.address), - ).to.be.revertedWithCustomError(context.universalProfile, 'CannotTransferOwnershipToSelf'); + ).to.be.revertedWithCustomError( + context.universalProfile, + 'LSP14CannotTransferOwnershipToSelf', + ); }); }); @@ -74,13 +77,13 @@ export const shouldBehaveLikePermissionChangeOwner = ( describe('when caller has ALL PERMISSIONS', () => { before('`transferOwnership(...)` to new Key Manager', async () => { await context.universalProfile - .connect(context.owner) + .connect(context.mainController) .transferOwnership(newKeyManager.address); }); after('reset ownership', async () => { await context.universalProfile - .connect(context.owner) + .connect(context.mainController) .transferOwnership(ethers.constants.AddressZero); }); @@ -93,7 +96,7 @@ export const shouldBehaveLikePermissionChangeOwner = ( const ownerBefore = await context.universalProfile.owner(); await context.universalProfile - .connect(context.owner) + .connect(context.mainController) .transferOwnership(newKeyManager.address); const ownerAfter = await context.universalProfile.owner(); @@ -107,7 +110,7 @@ export const shouldBehaveLikePermissionChangeOwner = ( const key = '0xcafecafecafecafecafecafecafecafecafecafecafecafecafecafecafecafe'; const value = '0xabcd'; - await context.universalProfile.connect(context.owner).setData(key, value); + await context.universalProfile.connect(context.mainController).setData(key, value); const result = await context.universalProfile.getData(key); expect(result).to.equal(value); @@ -121,7 +124,7 @@ export const shouldBehaveLikePermissionChangeOwner = ( const accountBalanceBefore = await provider.getBalance(context.universalProfile.address); await context.universalProfile - .connect(context.owner) + .connect(context.mainController) .execute(OPERATION_TYPES.CALL, recipient.address, amount, '0x'); const recipientBalanceAfter = await provider.getBalance(recipient.address); @@ -139,7 +142,7 @@ export const shouldBehaveLikePermissionChangeOwner = ( const overridenPendingOwner = ethers.Wallet.createRandom().address; await context.universalProfile - .connect(context.owner) + .connect(context.mainController) .transferOwnership(overridenPendingOwner); const pendingOwner = await context.universalProfile.pendingOwner(); @@ -156,7 +159,7 @@ export const shouldBehaveLikePermissionChangeOwner = ( after('reset ownership', async () => { await context.universalProfile - .connect(context.owner) + .connect(context.mainController) .transferOwnership(ethers.constants.AddressZero); }); @@ -179,9 +182,9 @@ export const shouldBehaveLikePermissionChangeOwner = ( }); it('should override the pendingOwner when transferOwnership(...) is called twice', async () => { - const overridenPendingOwner = await new LSP6KeyManager__factory(context.owner).deploy( - context.universalProfile.address, - ); + const overridenPendingOwner = await new LSP6KeyManager__factory( + context.mainController, + ).deploy(context.universalProfile.address); await context.universalProfile .connect(canChangeOwner) @@ -199,38 +202,49 @@ export const shouldBehaveLikePermissionChangeOwner = ( context.universalProfile.address, ); + const pendignOwner = await context.universalProfile.pendingOwner(); + const payload = context.universalProfile.interface.getSighash('acceptOwnership'); - await expect(notPendingKeyManager.connect(context.owner).execute(payload)).to.be.revertedWith( - 'LSP14: caller is not the pendingOwner', - ); + await expect(notPendingKeyManager.connect(context.mainController).execute(payload)) + .to.be.revertedWithCustomError(context.universalProfile, 'LSP20EOACannotVerifyCall') + .withArgs(pendignOwner); }); }); - describe('when calling acceptOwnership(...) via the pending new KeyManager', () => { + describe('when calling acceptOwnership(...) directly on the contract', () => { let pendingOwner: string; - before(async () => { - await context.universalProfile - .connect(context.owner) - .transferOwnership(newKeyManager.address); + describe('when pending owner is a new Key Manager', () => { + before(async () => { + await context.universalProfile + .connect(context.mainController) + .transferOwnership(newKeyManager.address); - pendingOwner = await context.universalProfile.pendingOwner(); + pendingOwner = await context.universalProfile.pendingOwner(); + }); - const acceptOwnershipPayload = - context.universalProfile.interface.getSighash('acceptOwnership'); + it('should not let you accept ownership if controller does not have permission `CHANGEOWNER`', async () => { + await expect(context.universalProfile.connect(cannotChangeOwner).acceptOwnership()) + .to.be.revertedWithCustomError(newKeyManager, 'NotAuthorised') + .withArgs(cannotChangeOwner.address, 'TRANSFEROWNERSHIP'); + }); - await newKeyManager.connect(context.owner).execute(acceptOwnershipPayload); - }); + it('should let you accept ownership if controller has permission', async () => { + await context.universalProfile.connect(canChangeOwner).acceptOwnership(); - it("should have change the account's owner to the pendingOwner (= pending KeyManager)", async () => { - const updatedOwner = await context.universalProfile.owner(); - expect(updatedOwner).to.equal(pendingOwner); - }); + expect(await context.universalProfile.owner()).to.equal(newKeyManager.address); + }); - it('should have cleared the pendingOwner after transfering ownership', async () => { - const newPendingOwner = await context.universalProfile.pendingOwner(); - expect(newPendingOwner).to.equal(ethers.constants.AddressZero); + it("should have change the account's owner to the pendingOwner (= pending KeyManager)", async () => { + const updatedOwner = await context.universalProfile.owner(); + expect(updatedOwner).to.equal(pendingOwner); + }); + + it('should have cleared the pendingOwner after transfering ownership', async () => { + const newPendingOwner = await context.universalProfile.pendingOwner(); + expect(newPendingOwner).to.equal(ethers.constants.AddressZero); + }); }); }); @@ -251,7 +265,7 @@ export const shouldBehaveLikePermissionChangeOwner = ( value, ]); - await expect(oldKeyManager.connect(context.owner).execute(payload)) + await expect(oldKeyManager.connect(context.mainController).execute(payload)) .to.be.revertedWithCustomError(newKeyManager, 'NoPermissionsSet') .withArgs(oldKeyManager.address); }); @@ -267,7 +281,7 @@ export const shouldBehaveLikePermissionChangeOwner = ( '0x', ]); - await expect(oldKeyManager.connect(context.owner).execute(payload)) + await expect(oldKeyManager.connect(context.mainController).execute(payload)) .to.be.revertedWithCustomError(newKeyManager, 'NoPermissionsSet') .withArgs(oldKeyManager.address); }); @@ -283,7 +297,7 @@ export const shouldBehaveLikePermissionChangeOwner = ( value, ]); - await newKeyManager.connect(context.owner).execute(payload); + await newKeyManager.connect(context.mainController).execute(payload); const result = await context.universalProfile.getData(key); expect(result).to.equal(value); @@ -303,7 +317,7 @@ export const shouldBehaveLikePermissionChangeOwner = ( '0x', ]); - await newKeyManager.connect(context.owner).execute(payload); + await newKeyManager.connect(context.mainController).execute(payload); const recipientBalanceAfter = await provider.getBalance(recipient.address); const accountBalanceAfter = await provider.getBalance(context.universalProfile.address); @@ -318,12 +332,48 @@ export const shouldBehaveLikePermissionChangeOwner = ( }); describe('when calling `renounceOwnership(...)`', () => { - it('should revert even if caller has ALL PERMISSIONS`', async () => { - const payload = context.universalProfile.interface.getSighash('renounceOwnership'); + describe('caller has ALL PERMISSIONS`', async () => { + let renounceOwnershipFirstTx: ContractTransaction; + let renounceOwnershipSecondTx: ContractTransaction; + + before(async () => { + // 1st call + renounceOwnershipFirstTx = await context.universalProfile + .connect(context.mainController) + .renounceOwnership(); + + // mine 200 blocks + await network.provider.send('hardhat_mine', [ethers.utils.hexValue(200)]); + + // 2nd call + renounceOwnershipSecondTx = await context.universalProfile + .connect(context.mainController) + .renounceOwnership(); + }); - await expect(context.universalProfile.connect(context.owner).renounceOwnership()) - .to.be.revertedWithCustomError(context.keyManager, 'InvalidERC725Function') - .withArgs(payload); + it('should emit `RenounceOwnershipStarted` on first call', async () => { + await expect(renounceOwnershipFirstTx).to.emit( + context.universalProfile, + 'RenounceOwnershipStarted', + ); + }); + + it('should emit `OwnershipRenounced` on second call', async () => { + await expect(renounceOwnershipSecondTx).to.emit( + context.universalProfile, + 'OwnershipRenounced', + ); + }); + + it('should clear the `pendingOwner` and set it to `AddressZero`', async () => { + expect(await context.universalProfile.pendingOwner()).to.equal( + ethers.constants.AddressZero, + ); + }); + + it('should update the owner to `AddressZero`', async () => { + expect(await context.universalProfile.owner()).to.equal(ethers.constants.AddressZero); + }); }); }); }; diff --git a/tests/LSP20CallVerification/LSP6/Interactions/AllowedAddresses.test.ts b/tests/LSP20CallVerification/LSP6/Interactions/AllowedAddresses.test.ts index 09c059816..fb76b459d 100644 --- a/tests/LSP20CallVerification/LSP6/Interactions/AllowedAddresses.test.ts +++ b/tests/LSP20CallVerification/LSP6/Interactions/AllowedAddresses.test.ts @@ -52,7 +52,8 @@ export const shouldBehaveLikeAllowedAddresses = (buildContext: () => Promise Promise Promise Promise Promise Promise Promise Promise { it('ERC1271', async () => { const sampleHash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes('Sample Message')); - const sampleSignature = await context.owner.signMessage('Sample Message'); + const sampleSignature = await context.mainController.signMessage('Sample Message'); const payload = signatureValidatorContract.interface.encodeFunctionData( 'isValidSignature', @@ -125,18 +126,18 @@ export const shouldBehaveLikeAllowedStandards = (buildContext: () => Promise { const key = ethers.utils.keccak256(ethers.utils.toUtf8Bytes('Key')); const value = '0xcafecafecafecafe'; - await context.universalProfile.connect(context.owner).setData(key, value); + await context.universalProfile.connect(context.mainController).setData(key, value); const result = await context.universalProfile.callStatic['getData(bytes32)'](key); expect(result).to.equal(value); @@ -162,7 +163,7 @@ export const shouldBehaveLikeAllowedStandards = (buildContext: () => Promise Promise) targetContract = await new TargetContract__factory(context.accounts[0]).deploy(); const permissionsKeys = [ - ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + context.owner.address.substring(2), + ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + + context.mainController.address.substring(2), ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + addressCanMakeCall.address.substring(2), ]; @@ -42,7 +43,7 @@ export const otherTestScenarios = (buildContext: () => Promise) await expect( context.universalProfile - .connect(context.owner) + .connect(context.mainController) .execute(INVALID_OPERATION_TYPE, targetContract.address, 0, targetPayload), ).to.be.revertedWithCustomError(context.universalProfile, 'ERC725X_UnknownOperationType'); }); diff --git a/tests/LSP20CallVerification/LSP6/Interactions/PermissionCall.test.ts b/tests/LSP20CallVerification/LSP6/Interactions/PermissionCall.test.ts index 1287fc62a..a2aa9b93f 100644 --- a/tests/LSP20CallVerification/LSP6/Interactions/PermissionCall.test.ts +++ b/tests/LSP20CallVerification/LSP6/Interactions/PermissionCall.test.ts @@ -99,11 +99,11 @@ export const shouldBehaveLikePermissionCall = ( ); const permissionsValues = [ - PERMISSIONS.SIGN, - PERMISSIONS.SIGN, - PERMISSIONS.CALL, - PERMISSIONS.CALL, - PERMISSIONS.SUPER_CALL, + combinePermissions(PERMISSIONS.SIGN, PERMISSIONS.EXECUTE_RELAY_CALL), + combinePermissions(PERMISSIONS.SIGN, PERMISSIONS.EXECUTE_RELAY_CALL), + combinePermissions(PERMISSIONS.CALL, PERMISSIONS.EXECUTE_RELAY_CALL), + combinePermissions(PERMISSIONS.CALL, PERMISSIONS.EXECUTE_RELAY_CALL), + combinePermissions(PERMISSIONS.SUPER_CALL, PERMISSIONS.EXECUTE_RELAY_CALL), allowedCallsValues, allowedCallsValues, ]; @@ -330,7 +330,8 @@ export const shouldBehaveLikePermissionCall = ( targetContract = await new TargetContract__factory(context.accounts[0]).deploy(); const permissionKeys = [ - ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + context.owner.address.substring(2), + ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + + context.mainController.address.substring(2), ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + addressCanMakeCallNoAllowedCalls.address.substring(2), ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + @@ -343,9 +344,9 @@ export const shouldBehaveLikePermissionCall = ( const permissionsValues = [ ALL_PERMISSIONS, - PERMISSIONS.CALL, - PERMISSIONS.CALL, - PERMISSIONS.SETDATA, + combinePermissions(PERMISSIONS.CALL, PERMISSIONS.EXECUTE_RELAY_CALL), + combinePermissions(PERMISSIONS.CALL, PERMISSIONS.EXECUTE_RELAY_CALL), + combinePermissions(PERMISSIONS.SETDATA, PERMISSIONS.EXECUTE_RELAY_CALL), combineAllowedCalls( [CALLTYPE.CALL], [targetContract.address], @@ -365,7 +366,7 @@ export const shouldBehaveLikePermissionCall = ( const targetPayload = targetContract.interface.encodeFunctionData('setName', [argument]); await context.universalProfile - .connect(context.owner) + .connect(context.mainController) .execute(OPERATION_TYPES.CALL, targetContract.address, 0, targetPayload); const result = await targetContract.callStatic.getName(); @@ -379,7 +380,7 @@ export const shouldBehaveLikePermissionCall = ( const targetContractPayload = targetContract.interface.encodeFunctionData('getName'); const result = await context.universalProfile - .connect(context.owner) + .connect(context.mainController) .callStatic.execute( OPERATION_TYPES.CALL, targetContract.address, @@ -397,7 +398,7 @@ export const shouldBehaveLikePermissionCall = ( const targetContractPayload = targetContract.interface.encodeFunctionData('getNumber'); const result = await context.universalProfile - .connect(context.owner) + .connect(context.mainController) .callStatic.execute( OPERATION_TYPES.CALL, targetContract.address, @@ -486,7 +487,8 @@ export const shouldBehaveLikePermissionCall = ( context = await buildContext(); const permissionKeys = [ - ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + context.owner.address.substring(2), + ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + + context.mainController.address.substring(2), ]; const permissionValues = [ALL_PERMISSIONS]; @@ -497,7 +499,13 @@ export const shouldBehaveLikePermissionCall = ( it('Should revert when caller calls the KeyManager through execute', async () => { const lsp20VerifyCallPayload = context.keyManager.interface.encodeFunctionData( 'lsp20VerifyCall', - [context.accounts[2].address, 0, '0xaabbccdd'], // random arguments + [ + context.accounts[2].address, + context.keyManager.address, + context.accounts[2].address, + 0, + '0xaabbccdd', + ], // random arguments ); await expect( diff --git a/tests/LSP20CallVerification/LSP6/Interactions/PermissionDelegateCall.test.ts b/tests/LSP20CallVerification/LSP6/Interactions/PermissionDelegateCall.test.ts index a67fd8d97..c4b5f09ec 100644 --- a/tests/LSP20CallVerification/LSP6/Interactions/PermissionDelegateCall.test.ts +++ b/tests/LSP20CallVerification/LSP6/Interactions/PermissionDelegateCall.test.ts @@ -35,12 +35,13 @@ export const shouldBehaveLikePermissionDelegateCall = ( addressCanDelegateCall = context.accounts[1]; addressCannotDelegateCall = context.accounts[2]; - erc725YDelegateCallContract = await new ERC725YDelegateCall__factory(context.owner).deploy( - context.universalProfile.address, - ); + erc725YDelegateCallContract = await new ERC725YDelegateCall__factory( + context.mainController, + ).deploy(context.universalProfile.address); const permissionKeys = [ - ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + context.owner.address.substring(2), + ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + + context.mainController.address.substring(2), ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + addressCanDelegateCall.address.substring(2), ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + @@ -70,7 +71,7 @@ export const shouldBehaveLikePermissionDelegateCall = ( await expect( context.universalProfile - .connect(context.owner) + .connect(context.mainController) .execute( OPERATION_TYPES.DELEGATECALL, erc725YDelegateCallContract.address, diff --git a/tests/LSP20CallVerification/LSP6/Interactions/PermissionDeploy.test.ts b/tests/LSP20CallVerification/LSP6/Interactions/PermissionDeploy.test.ts index 6d03d4306..f7232c6d3 100644 --- a/tests/LSP20CallVerification/LSP6/Interactions/PermissionDeploy.test.ts +++ b/tests/LSP20CallVerification/LSP6/Interactions/PermissionDeploy.test.ts @@ -29,7 +29,8 @@ export const shouldBehaveLikePermissionDeploy = (buildContext: () => Promise Promise Promise context.universalProfile - .connect(context.owner) + .connect(context.mainController) .execute(OPERATION_TYPES.CALL, recipient.address, amount, data), ).to.changeEtherBalances( [context.universalProfile.address, recipient.address], @@ -207,7 +208,7 @@ export const shouldBehaveLikePermissionTransferValue = ( const initialBalanceRecipient = await provider.getBalance(recipient.address); await context.universalProfile - .connect(context.owner) + .connect(context.mainController) .execute(OPERATION_TYPES.CALL, recipient.address, ethers.utils.parseEther('3'), data); const newBalanceUP = await provider.getBalance(context.universalProfile.address); @@ -396,7 +397,8 @@ export const shouldBehaveLikePermissionTransferValue = ( ); const permissionKeys = [ - ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + context.owner.address.substring(2), + ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + + context.mainController.address.substring(2), ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + contractCanTransferValue.address.substring(2), ERC725YDataKeys.LSP6['AddressPermissions:AllowedCalls'] + diff --git a/tests/LSP20CallVerification/LSP6/Interactions/Security.test.ts b/tests/LSP20CallVerification/LSP6/Interactions/Security.test.ts index ede08ed98..8b39ce1a5 100644 --- a/tests/LSP20CallVerification/LSP6/Interactions/Security.test.ts +++ b/tests/LSP20CallVerification/LSP6/Interactions/Security.test.ts @@ -60,7 +60,8 @@ export const testSecurityScenarios = (buildContext: () => Promise Promise Promise { const lsp20VerifyCallPayload = context.keyManager.interface.encodeFunctionData( 'lsp20VerifyCall', - [context.accounts[2].address, 0, '0xaabbccdd'], // random arguments + [ + context.accounts[2].address, + context.keyManager.address, + context.accounts[2].address, + 0, + '0xaabbccdd', + ], // random arguments ); await expect( context.universalProfile - .connect(context.owner) + .connect(context.mainController) .execute(OPERATION_TYPES.CALL, context.keyManager.address, 0, lsp20VerifyCallPayload), ).to.be.revertedWithCustomError(context.keyManager, 'CallingKeyManagerNotAllowed'); }); @@ -141,7 +148,7 @@ export const testSecurityScenarios = (buildContext: () => Promise Promise { it('should allow the URD to use `setData(..)` through the LSP6', async () => { const universalReceiverDelegateDataUpdater = - await new UniversalReceiverDelegateDataUpdater__factory(context.owner).deploy(); + await new UniversalReceiverDelegateDataUpdater__factory(context.mainController).deploy(); const randomHardcodedKey = ethers.utils.keccak256( ethers.utils.toUtf8Bytes('some random data key'), @@ -180,13 +187,18 @@ export const testSecurityScenarios = (buildContext: () => Promise Promise Promise Promise Promise Promise Promise { if (elem.label === '_reentrancyStatus') return elem; })[0].slot, diff --git a/tests/LSP20CallVerification/LSP6/LSP20WithLSP6.test.ts b/tests/LSP20CallVerification/LSP6/LSP20WithLSP6.test.ts index e871cd9e1..62b8aa8dc 100644 --- a/tests/LSP20CallVerification/LSP6/LSP20WithLSP6.test.ts +++ b/tests/LSP20CallVerification/LSP6/LSP20WithLSP6.test.ts @@ -10,15 +10,20 @@ import { shouldBehaveLikeLSP6 } from './LSP20WithLSP6.behaviour'; describe('LSP20 + LSP6 with constructor', () => { const buildTestContext = async (initialFunding?: BigNumber): Promise => { const accounts = await ethers.getSigners(); - const owner = accounts[0]; + const mainController = accounts[0]; - const universalProfile = await new UniversalProfile__factory(owner).deploy(owner.address, { - value: initialFunding, - }); + const universalProfile = await new UniversalProfile__factory(mainController).deploy( + mainController.address, + { + value: initialFunding, + }, + ); - const keyManager = await new LSP6KeyManager__factory(owner).deploy(universalProfile.address); + const keyManager = await new LSP6KeyManager__factory(mainController).deploy( + universalProfile.address, + ); - return { accounts, owner, universalProfile, keyManager, initialFunding }; + return { accounts, mainController, universalProfile, keyManager, initialFunding }; }; describe('when testing deployed contract', () => { diff --git a/tests/LSP20CallVerification/LSP6/LSP20WithLSP6Init.test.ts b/tests/LSP20CallVerification/LSP6/LSP20WithLSP6Init.test.ts index 4db9f0ddc..5d9729e17 100644 --- a/tests/LSP20CallVerification/LSP6/LSP20WithLSP6Init.test.ts +++ b/tests/LSP20CallVerification/LSP6/LSP20WithLSP6Init.test.ts @@ -12,21 +12,21 @@ import { shouldBehaveLikeLSP6 } from './LSP20WithLSP6.behaviour'; describe('LSP20 Init + LSP6 Init with proxy', () => { const buildProxyTestContext = async (initialFunding?: BigNumber): Promise => { const accounts = await ethers.getSigners(); - const owner = accounts[0]; + const mainController = accounts[0]; - const baseUP = await new UniversalProfileInit__factory(owner).deploy(); - const upProxy = await deployProxy(baseUP.address, owner); + const baseUP = await new UniversalProfileInit__factory(mainController).deploy(); + const upProxy = await deployProxy(baseUP.address, mainController); const universalProfile = await baseUP.attach(upProxy); - const baseKM = await new LSP6KeyManagerInit__factory(owner).deploy(); - const kmProxy = await deployProxy(baseKM.address, owner); + const baseKM = await new LSP6KeyManagerInit__factory(mainController).deploy(); + const kmProxy = await deployProxy(baseKM.address, mainController); const keyManager = await baseKM.attach(kmProxy); - return { accounts, owner, universalProfile, keyManager, initialFunding }; + return { accounts, mainController, universalProfile, keyManager, initialFunding }; }; const initializeProxy = async (context: LSP6TestContext) => { - await context.universalProfile['initialize(address)'](context.owner.address, { + await context.universalProfile['initialize(address)'](context.mainController.address, { value: context.initialFunding, }); diff --git a/tests/LSP20CallVerification/LSP6/SetData/AllowedERC725YDataKeys.test.ts b/tests/LSP20CallVerification/LSP6/SetData/AllowedERC725YDataKeys.test.ts index 5a70b2299..96b4735ce 100644 --- a/tests/LSP20CallVerification/LSP6/SetData/AllowedERC725YDataKeys.test.ts +++ b/tests/LSP20CallVerification/LSP6/SetData/AllowedERC725YDataKeys.test.ts @@ -38,7 +38,8 @@ export const shouldBehaveLikeAllowedERC725YDataKeys = ( controllerCanSetManyKeys = context.accounts[2]; const permissionKeys = [ - ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + context.owner.address.substring(2), + ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + + context.mainController.address.substring(2), ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + controllerCanSetOneKey.address.substring(2), ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + @@ -653,7 +654,7 @@ export const shouldBehaveLikeAllowedERC725YDataKeys = ( const key = ethers.utils.hexlify(ethers.utils.randomBytes(32)); const value = ethers.utils.hexlify(ethers.utils.toUtf8Bytes('Some data')); - await context.universalProfile.connect(context.owner).setData(key, value); + await context.universalProfile.connect(context.mainController).setData(key, value); const result = await context.universalProfile.getData(key); expect(result).to.equal(value); @@ -673,7 +674,7 @@ export const shouldBehaveLikeAllowedERC725YDataKeys = ( ethers.utils.hexlify(ethers.utils.toUtf8Bytes('Some data 3')), ]; - await context.universalProfile.connect(context.owner).setDataBatch(keys, values); + await context.universalProfile.connect(context.mainController).setDataBatch(keys, values); const result = await context.universalProfile.getDataBatch(keys); @@ -704,7 +705,8 @@ export const shouldBehaveLikeAllowedERC725YDataKeys = ( controllerCanSetMappingKeys = context.accounts[1]; const permissionKeys = [ - ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + context.owner.address.substring(2), + ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + + context.mainController.address.substring(2), ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + controllerCanSetMappingKeys.address.substring(2), ERC725YDataKeys.LSP6['AddressPermissions:AllowedERC725YDataKeys'] + @@ -897,7 +899,7 @@ export const shouldBehaveLikeAllowedERC725YDataKeys = ( ); await context.universalProfile - .connect(context.owner) + .connect(context.mainController) .setData(randomMappingKey, randomMappingValue); const result = await context.universalProfile.getData(randomMappingKey); @@ -919,7 +921,7 @@ export const shouldBehaveLikeAllowedERC725YDataKeys = ( ]; await context.universalProfile - .connect(context.owner) + .connect(context.mainController) .setDataBatch(randomMappingKeys, randomMappingValues); const result = await context.universalProfile.getDataBatch(randomMappingKeys); @@ -952,7 +954,8 @@ export const shouldBehaveLikeAllowedERC725YDataKeys = ( controllerCanSetArrayKeys = context.accounts[1]; const permissionKeys = [ - ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + context.owner.address.substring(2), + ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + + context.mainController.address.substring(2), ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + controllerCanSetArrayKeys.address.substring(2), ERC725YDataKeys.LSP6['AddressPermissions:AllowedERC725YDataKeys'] + @@ -1101,7 +1104,7 @@ export const shouldBehaveLikeAllowedERC725YDataKeys = ( const permissionKeys = [ ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + - context.owner.address.substring(2), + context.mainController.address.substring(2), ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + controllerCanSetSomeKeys.address.substring(2), ERC725YDataKeys.LSP6['AddressPermissions:AllowedERC725YDataKeys'] + @@ -1228,7 +1231,7 @@ export const shouldBehaveLikeAllowedERC725YDataKeys = ( const permissionKeys = [ ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + - context.owner.address.substring(2), + context.mainController.address.substring(2), ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + controllerCanSetSomeKeys.address.substring(2), ERC725YDataKeys.LSP6['AddressPermissions:AllowedERC725YDataKeys'] + @@ -1373,7 +1376,8 @@ export const shouldBehaveLikeAllowedERC725YDataKeys = ( controllerCanSetSomeKeys = context.accounts[1]; const permissionKeys = [ - ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + context.owner.address.substring(2), + ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + + context.mainController.address.substring(2), ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + controllerCanSetSomeKeys.address.substring(2), ERC725YDataKeys.LSP6['AddressPermissions:AllowedERC725YDataKeys'] + diff --git a/tests/LSP20CallVerification/LSP6/SetData/PermissionSetData.test.ts b/tests/LSP20CallVerification/LSP6/SetData/PermissionSetData.test.ts index c9eb4d23e..d1ea42ea4 100644 --- a/tests/LSP20CallVerification/LSP6/SetData/PermissionSetData.test.ts +++ b/tests/LSP20CallVerification/LSP6/SetData/PermissionSetData.test.ts @@ -64,7 +64,8 @@ export const shouldBehaveLikePermissionSetData = (buildContext: () => Promise Promise Promise Promise Promise Promise { context = await buildContext(); - contractCanSetData = await new ExecutorLSP20__factory(context.owner).deploy( + contractCanSetData = await new ExecutorLSP20__factory(context.mainController).deploy( context.universalProfile.address, ); const permissionKeys = [ - ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + context.owner.address.substring(2), + ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + + context.mainController.address.substring(2), ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + contractCanSetData.address.substring(2), ERC725YDataKeys.LSP6['AddressPermissions:AllowedERC725YDataKeys'] + diff --git a/tests/LSP20CallVerification/LSP6/SetPermissions/PermissionChangeAddController.test.ts b/tests/LSP20CallVerification/LSP6/SetPermissions/PermissionChangeAddController.test.ts index 73c05fbe6..2f0eb16b0 100644 --- a/tests/LSP20CallVerification/LSP6/SetPermissions/PermissionChangeAddController.test.ts +++ b/tests/LSP20CallVerification/LSP6/SetPermissions/PermissionChangeAddController.test.ts @@ -18,7 +18,7 @@ async function setupPermissions( permissionValues: string[], ) { await context.universalProfile - .connect(context.owner) + .connect(context.mainController) .setDataBatch(permissionsKeys, permissionValues); } @@ -27,7 +27,7 @@ async function setupPermissions( */ async function resetPermissions(context: LSP6TestContext, permissionsKeys: string[]) { await context.universalProfile - .connect(context.owner) + .connect(context.mainController) .setDataBatch(permissionsKeys, Array(permissionsKeys.length).fill('0x')); } @@ -47,7 +47,10 @@ export const shouldBehaveLikePermissionChangeOrAddController = ( await setupKeyManager( context, - [ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + context.owner.address.substring(2)], + [ + ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + + context.mainController.address.substring(2), + ], [ALL_PERMISSIONS], ); }); @@ -106,7 +109,7 @@ export const shouldBehaveLikePermissionChangeOrAddController = ( permissionArrayValues = [ ethers.utils.hexZeroPad(ethers.utils.hexlify(6), 16), - context.owner.address, + context.mainController.address, canOnlyAddController.address, canOnlyEditPermissions.address, canOnlySetData.address, @@ -143,7 +146,9 @@ export const shouldBehaveLikePermissionChangeOrAddController = ( ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + newController.address.substr(2); - await context.universalProfile.connect(context.owner).setData(key, PERMISSIONS.SETDATA); + await context.universalProfile + .connect(context.mainController) + .setData(key, PERMISSIONS.SETDATA); // prettier-ignore const result = await context.universalProfile.getData(key); @@ -157,7 +162,7 @@ export const shouldBehaveLikePermissionChangeOrAddController = ( const value = PERMISSIONS.SETDATA; - await context.universalProfile.connect(context.owner).setData(key, value); + await context.universalProfile.connect(context.mainController).setData(key, value); // prettier-ignore const result = await context.universalProfile.getData(key); @@ -174,7 +179,7 @@ export const shouldBehaveLikePermissionChangeOrAddController = ( const value = ethers.utils.hexZeroPad(ethers.utils.hexlify(newLength), 16); - await context.universalProfile.connect(context.owner).setData(key, value); + await context.universalProfile.connect(context.mainController).setData(key, value); // prettier-ignore const result = await context.universalProfile.getData(key); @@ -190,7 +195,7 @@ export const shouldBehaveLikePermissionChangeOrAddController = ( const value = ethers.utils.hexZeroPad(ethers.utils.hexlify(newLength), 16); - await context.universalProfile.connect(context.owner).setData(key, value); + await context.universalProfile.connect(context.mainController).setData(key, value); // prettier-ignore const result = await context.universalProfile.getData(key); @@ -205,7 +210,7 @@ export const shouldBehaveLikePermissionChangeOrAddController = ( '00000000000000000000000000000006'; const value = ethers.Wallet.createRandom().address.toLowerCase(); - await context.universalProfile.connect(context.owner).setData(key, value); + await context.universalProfile.connect(context.mainController).setData(key, value); const result = await context.universalProfile.getData(key); expect(result).to.equal(value); @@ -219,7 +224,9 @@ export const shouldBehaveLikePermissionChangeOrAddController = ( // set some random bytes under AddressPermissions[7] - await expect(context.universalProfile.connect(context.owner).setData(key, randomValue)) + await expect( + context.universalProfile.connect(context.mainController).setData(key, randomValue), + ) .to.be.revertedWithCustomError(context.keyManager, 'InvalidDataValuesForDataKeys') .withArgs(key, randomValue); }); @@ -232,7 +239,9 @@ export const shouldBehaveLikePermissionChangeOrAddController = ( // set some random bytes under AddressPermissions[7] - await expect(context.universalProfile.connect(context.owner).setData(key, randomValue)) + await expect( + context.universalProfile.connect(context.mainController).setData(key, randomValue), + ) .to.be.revertedWithCustomError(context.keyManager, 'InvalidDataValuesForDataKeys') .withArgs(key, randomValue); }); @@ -248,7 +257,7 @@ export const shouldBehaveLikePermissionChangeOrAddController = ( const value = randomWallet.address; - await context.universalProfile.connect(context.owner).setData(key, value); + await context.universalProfile.connect(context.mainController).setData(key, value); // prettier-ignore const result = await context.universalProfile.getData(key); @@ -263,7 +272,9 @@ export const shouldBehaveLikePermissionChangeOrAddController = ( // set some random bytes under AddressPermissions[7] - await expect(context.universalProfile.connect(context.owner).setData(key, randomValue)) + await expect( + context.universalProfile.connect(context.mainController).setData(key, randomValue), + ) .to.be.revertedWithCustomError(context.keyManager, 'InvalidDataValuesForDataKeys') .withArgs(key, randomValue); }); @@ -276,7 +287,9 @@ export const shouldBehaveLikePermissionChangeOrAddController = ( // set some random bytes under AddressPermissions[7] - await expect(context.universalProfile.connect(context.owner).setData(key, randomValue)) + await expect( + context.universalProfile.connect(context.mainController).setData(key, randomValue), + ) .to.be.revertedWithCustomError(context.keyManager, 'InvalidDataValuesForDataKeys') .withArgs(key, randomValue); }); @@ -290,7 +303,7 @@ export const shouldBehaveLikePermissionChangeOrAddController = ( const value = '0x'; - await context.universalProfile.connect(context.owner).setData(key, value); + await context.universalProfile.connect(context.mainController).setData(key, value); // prettier-ignore const result = await context.universalProfile.getData(key); @@ -309,7 +322,9 @@ export const shouldBehaveLikePermissionChangeOrAddController = ( // the value does not matter in the case of the test here const value = '0x0000000000000000000000000000000000000000000000000000000000000008'; - await expect(context.universalProfile.connect(context.owner).setData(key, value)) + await expect( + context.universalProfile.connect(context.mainController).setData(key, value), + ) .to.be.revertedWithCustomError(context.keyManager, 'NotRecognisedPermissionKey') .withArgs(key.toLowerCase()); }); @@ -906,7 +921,7 @@ export const shouldBehaveLikePermissionChangeOrAddController = ( PERMISSIONS.SETDATA, ]; - await context.universalProfile.connect(context.owner).setDataBatch(keys, values); + await context.universalProfile.connect(context.mainController).setDataBatch(keys, values); // prettier-ignore const fetchedResult = await context.universalProfile.getDataBatch(keys); @@ -930,7 +945,7 @@ export const shouldBehaveLikePermissionChangeOrAddController = ( combinePermissions(PERMISSIONS.SETDATA, PERMISSIONS.TRANSFERVALUE), ]; - await context.universalProfile.connect(context.owner).setDataBatch(keys, values); + await context.universalProfile.connect(context.mainController).setDataBatch(keys, values); // prettier-ignore const fetchedResult = await context.universalProfile.getDataBatch(keys); @@ -956,7 +971,7 @@ export const shouldBehaveLikePermissionChangeOrAddController = ( combinePermissions(PERMISSIONS.SETDATA, PERMISSIONS.TRANSFERVALUE), ]; - await context.universalProfile.connect(context.owner).setDataBatch(keys, values); + await context.universalProfile.connect(context.mainController).setDataBatch(keys, values); // prettier-ignore const fetchedResult = await context.universalProfile.getDataBatch(keys); diff --git a/tests/LSP20CallVerification/LSP6/SetPermissions/SetAllowedCalls.test.ts b/tests/LSP20CallVerification/LSP6/SetPermissions/SetAllowedCalls.test.ts index 233cb7f17..7a77517f0 100644 --- a/tests/LSP20CallVerification/LSP6/SetPermissions/SetAllowedCalls.test.ts +++ b/tests/LSP20CallVerification/LSP6/SetPermissions/SetAllowedCalls.test.ts @@ -18,9 +18,10 @@ export const shouldBehaveLikeSetAllowedCalls = (buildContext: () => Promise { let canOnlyAddController: SignerWithAddress, canOnlyEditPermissions: SignerWithAddress; - let beneficiary: SignerWithAddress; - let invalidBytes: SignerWithAddress; - let noBytes: SignerWithAddress; + let beneficiaryWithPermissions: SignerWithAddress, + beneficiaryNoPermissions: SignerWithAddress, + invalidBytes: SignerWithAddress, + noBytes: SignerWithAddress; before(async () => { context = await buildContext(); @@ -28,18 +29,20 @@ export const shouldBehaveLikeSetAllowedCalls = (buildContext: () => Promise Promise Promise Promise { - it('should revert and not be allowed to clear the list of allowed calls for an address', async () => { - const dataKey = - ERC725YDataKeys.LSP6['AddressPermissions:AllowedCalls'] + - beneficiary.address.substring(2); - const dataValue = '0x'; + describe('when controller / beneficiary has some permissions', () => { + it('should revert and not be allowed to clear the list of allowed calls for an address', async () => { + const dataKey = + ERC725YDataKeys.LSP6['AddressPermissions:AllowedCalls'] + + beneficiaryWithPermissions.address.substring(2); + const dataValue = '0x'; - await expect( - context.universalProfile.connect(canOnlyAddController).setData(dataKey, dataValue), - ) - .to.be.revertedWithCustomError(context.keyManager, 'NotAuthorised') - .withArgs(canOnlyAddController.address, 'EDITPERMISSIONS'); + await expect( + context.universalProfile.connect(canOnlyAddController).setData(dataKey, dataValue), + ) + .to.be.revertedWithCustomError(context.keyManager, 'NotAuthorised') + .withArgs(canOnlyAddController.address, 'EDITPERMISSIONS'); + }); + }); + + describe('when controller / beneficiary has no permissions', () => { + it('should pass and set the list of allowed calls for an address', async () => { + const dataKey = + ERC725YDataKeys.LSP6['AddressPermissions:AllowedCalls'] + + beneficiaryNoPermissions.address.substring(2); + const dataValue = '0x'; + + await context.universalProfile.connect(canOnlyAddController).setData(dataKey, dataValue); + + expect(await context.universalProfile.getData(dataKey)).to.equal(dataValue); + }); }); }); - describe('when caller has CHANGE permission', () => { - it('should allow to clear the list of allowed calls for an address', async () => { - const dataKey = - ERC725YDataKeys.LSP6['AddressPermissions:AllowedCalls'] + - beneficiary.address.substring(2); - const dataValue = '0x'; + describe('when caller has EDIT permission', () => { + describe('when controller / beneficiary has some permissions', () => { + it('should allow to clear the list of allowed calls for an address', async () => { + const dataKey = + ERC725YDataKeys.LSP6['AddressPermissions:AllowedCalls'] + + beneficiaryWithPermissions.address.substring(2); + const dataValue = '0x'; + + await context.universalProfile + .connect(canOnlyEditPermissions) + .setData(dataKey, dataValue); - await context.universalProfile.connect(canOnlyEditPermissions).setData(dataKey, dataValue); + const result = await context.universalProfile.getData(dataKey); + expect(result).to.equal(dataValue); + }); + }); + + describe('when controller / beneficiary has no permissions', () => { + it("should revert with error `NotAuthorised('EDITPERMISSIONS')` when trying to clear the list of allowed calls for an address", async () => { + const dataKey = + ERC725YDataKeys.LSP6['AddressPermissions:AllowedCalls'] + + beneficiaryNoPermissions.address.substring(2); + const dataValue = '0x'; - const result = await context.universalProfile.getData(dataKey); - expect(result).to.equal(dataValue); + await expect( + context.universalProfile.connect(canOnlyEditPermissions).setData(dataKey, dataValue), + ) + .to.be.revertedWithCustomError(context.keyManager, 'NotAuthorised') + .withArgs(canOnlyEditPermissions.address, 'ADDCONTROLLER'); + }); }); }); }); @@ -115,14 +167,16 @@ export const shouldBehaveLikeSetAllowedCalls = (buildContext: () => Promise Promise Promise Promise Promise Promise { - it('should fail when trying to edit existing allowed functions for an address', async () => { - const key = - ERC725YDataKeys.LSP6['AddressPermissions:AllowedCalls'] + - beneficiary.address.substring(2); - - const value = combineAllowedCalls( - // allow beneficiary to make a CALL to only function selectors 0xcafecafe and 0xf00df00d - [CALLTYPE.CALL, CALLTYPE.CALL], - [ - '0xffffffffffffffffffffffffffffffffffffffff', - '0xffffffffffffffffffffffffffffffffffffffff', - ], - ['0xffffffff', '0xffffffff'], - ['0xcafecafe', '0xf00df00d'], - ); - - await expect(context.universalProfile.connect(canOnlyAddController).setData(key, value)) - .to.be.revertedWithCustomError(context.keyManager, 'NotAuthorised') - .withArgs(canOnlyAddController.address, 'EDITPERMISSIONS'); - }); - - it('should fail with NotAuthorised -> when beneficiary address had an invalid bytes28[CompactBytesArray] initially', async () => { - const key = - ERC725YDataKeys.LSP6['AddressPermissions:AllowedCalls'] + - invalidBeneficiary.address.substring(2); - - const value = combineAllowedCalls( - // allow beneficiary to make a CALL to only function selectors 0xcafecafe and 0xf00df00d - [CALLTYPE.CALL, CALLTYPE.CALL], - [ - '0xffffffffffffffffffffffffffffffffffffffff', - '0xffffffffffffffffffffffffffffffffffffffff', - ], - ['0xffffffff', '0xffffffff'], - ['0xcafecafe', '0xf00df00d'], - ); - - await expect(context.universalProfile.connect(canOnlyAddController).setData(key, value)) - .to.be.revertedWithCustomError(context.keyManager, 'NotAuthorised') - .withArgs(canOnlyAddController.address, 'EDITPERMISSIONS'); - }); - - // Even if the controller had some 00 bytes set as allowed calls, it is not considered as it does not have any allowed calls set - // but rather that its allowed calls are "disabled" - describe('when beneficiary (= controller) had 00 bytes set initially as allowed calls (e.g: allowed calls disabled)', () => { - it('should fail with NotAuthorised -> when beneficiary had 32 x 0 bytes set initially as allowed calls', async () => { + describe('when controller to edit Allowed Calls for has some permissions set', () => { + it('should fail when trying to edit existing allowed functions for an address', async () => { const key = ERC725YDataKeys.LSP6['AddressPermissions:AllowedCalls'] + - zero32Bytes.address.substring(2); + beneficiary.address.substring(2); const value = combineAllowedCalls( - [CALLTYPE.VALUE, CALLTYPE.VALUE], - ['0xffffffff', '0xffffffff'], - ['0xcafecafe', '0xca11ca11'], + // allow beneficiary to make a CALL to only function selectors 0xcafecafe and 0xf00df00d + [CALLTYPE.CALL, CALLTYPE.CALL], [ '0xffffffffffffffffffffffffffffffffffffffff', '0xffffffffffffffffffffffffffffffffffffffff', ], + ['0xffffffff', '0xffffffff'], + ['0xcafecafe', '0xf00df00d'], ); await expect(context.universalProfile.connect(canOnlyAddController).setData(key, value)) @@ -556,49 +579,95 @@ export const shouldBehaveLikeSetAllowedCalls = (buildContext: () => Promise when beneficiary had 40 x 0 bytes set initially as allowed calls', async () => { + it('should fail with NotAuthorised -> when beneficiary address had an invalid bytes28[CompactBytesArray] initially', async () => { const key = ERC725YDataKeys.LSP6['AddressPermissions:AllowedCalls'] + - zero40Bytes.address.substring(2); + invalidBeneficiary.address.substring(2); const value = combineAllowedCalls( + // allow beneficiary to make a CALL to only function selectors 0xcafecafe and 0xf00df00d [CALLTYPE.CALL, CALLTYPE.CALL], [ '0xffffffffffffffffffffffffffffffffffffffff', '0xffffffffffffffffffffffffffffffffffffffff', ], ['0xffffffff', '0xffffffff'], - ['0xcafecafe', '0xca11ca11'], + ['0xcafecafe', '0xf00df00d'], ); await expect(context.universalProfile.connect(canOnlyAddController).setData(key, value)) .to.be.revertedWithCustomError(context.keyManager, 'NotAuthorised') .withArgs(canOnlyAddController.address, 'EDITPERMISSIONS'); }); - }); - it('should pass when beneficiary had no values set under AddressPermissions:AllowedCalls:... + setting a valid bytes28[CompactBytesArray]', async () => { - const newController = ethers.Wallet.createRandom(); + // Even if the controller had some 00 bytes set as allowed calls, it is not considered as it does not have any allowed calls set + // but rather that its allowed calls are "disabled" + describe('when beneficiary (= controller) had 00 bytes set initially as allowed calls (e.g: allowed calls disabled)', () => { + it('should fail with NotAuthorised -> when beneficiary had 32 x 0 bytes set initially as allowed calls', async () => { + const key = + ERC725YDataKeys.LSP6['AddressPermissions:AllowedCalls'] + + zero32Bytes.address.substring(2); + + const value = combineAllowedCalls( + [CALLTYPE.VALUE, CALLTYPE.VALUE], + ['0xffffffff', '0xffffffff'], + ['0xcafecafe', '0xca11ca11'], + [ + '0xffffffffffffffffffffffffffffffffffffffff', + '0xffffffffffffffffffffffffffffffffffffffff', + ], + ); + + await expect(context.universalProfile.connect(canOnlyAddController).setData(key, value)) + .to.be.revertedWithCustomError(context.keyManager, 'NotAuthorised') + .withArgs(canOnlyAddController.address, 'EDITPERMISSIONS'); + }); + + it('should fail with NotAuthorised -> when beneficiary had 40 x 0 bytes set initially as allowed calls', async () => { + const key = + ERC725YDataKeys.LSP6['AddressPermissions:AllowedCalls'] + + zero40Bytes.address.substring(2); + + const value = combineAllowedCalls( + [CALLTYPE.CALL, CALLTYPE.CALL], + [ + '0xffffffffffffffffffffffffffffffffffffffff', + '0xffffffffffffffffffffffffffffffffffffffff', + ], + ['0xffffffff', '0xffffffff'], + ['0xcafecafe', '0xca11ca11'], + ); + + await expect(context.universalProfile.connect(canOnlyAddController).setData(key, value)) + .to.be.revertedWithCustomError(context.keyManager, 'NotAuthorised') + .withArgs(canOnlyAddController.address, 'EDITPERMISSIONS'); + }); + }); - const key = - ERC725YDataKeys.LSP6['AddressPermissions:AllowedCalls'] + newController.address.substr(2); + it('should pass when beneficiary had no values set under AddressPermissions:AllowedCalls:... + setting a valid bytes28[CompactBytesArray]', async () => { + const newController = ethers.Wallet.createRandom(); - const value = combineAllowedCalls( - // allow beneficiary to CALL only function selectors 0xcafecafe and 0xf00df00d - [CALLTYPE.CALL, CALLTYPE.CALL], - [ - '0xffffffffffffffffffffffffffffffffffffffff', - '0xffffffffffffffffffffffffffffffffffffffff', - ], - ['0xffffffff', '0xffffffff'], - ['0xcafecafe', '0xf00df00d'], - ); + const key = + ERC725YDataKeys.LSP6['AddressPermissions:AllowedCalls'] + + newController.address.substr(2); - await context.universalProfile.connect(canOnlyAddController).setData(key, value); + const value = combineAllowedCalls( + // allow beneficiary to CALL only function selectors 0xcafecafe and 0xf00df00d + [CALLTYPE.CALL, CALLTYPE.CALL], + [ + '0xffffffffffffffffffffffffffffffffffffffff', + '0xffffffffffffffffffffffffffffffffffffffff', + ], + ['0xffffffff', '0xffffffff'], + ['0xcafecafe', '0xf00df00d'], + ); - // prettier-ignore - const result = await context.universalProfile.getData(key); - expect(result).to.equal(value); + await context.universalProfile.connect(canOnlyAddController).setData(key, value); + + // prettier-ignore + const result = await context.universalProfile.getData(key); + expect(result).to.equal(value); + }); }); describe('when setting an invalid bytes28[CompactBytesArray] for a new beneficiary', () => { @@ -633,83 +702,37 @@ export const shouldBehaveLikeSetAllowedCalls = (buildContext: () => Promise { - it('should fail when beneficiary had no values set under AddressPermissions:AllowedCalls:...', async () => { - const newController = ethers.Wallet.createRandom(); - - const key = - ERC725YDataKeys.LSP6['AddressPermissions:AllowedCalls'] + newController.address.substr(2); - - const value = combineAllowedCalls( - // allow beneficiary to CALL only function selectors 0xcafecafe and 0xbeefbeef - [CALLTYPE.CALL, CALLTYPE.CALL], - [ - '0xffffffffffffffffffffffffffffffffffffffff', - '0xffffffffffffffffffffffffffffffffffffffff', - ], - ['0xffffffff', '0xffffffff'], - ['0xcafecafe', '0xbeefbeef'], - ); - - await expect(context.universalProfile.connect(canOnlyEditPermissions).setData(key, value)) - .to.be.revertedWithCustomError(context.keyManager, 'NotAuthorised') - .withArgs(canOnlyEditPermissions.address, 'ADDCONTROLLER'); - }); - - it('should pass when trying to edit existing allowed bytes4 selectors under ANY:ANY:', async () => { - const key = - ERC725YDataKeys.LSP6['AddressPermissions:AllowedCalls'] + - beneficiary.address.substring(2); - - const value = combineAllowedCalls( - // allow beneficiary to CALL only function selectors 0xcafecafe and 0xbeefbeef - [CALLTYPE.CALL, CALLTYPE.CALL], - [ - '0xffffffffffffffffffffffffffffffffffffffff', - '0xffffffffffffffffffffffffffffffffffffffff', - ], - ['0xffffffff', '0xffffffff'], - ['0xcafecafe', '0xbeefbeef'], - ); - - await context.universalProfile.connect(canOnlyEditPermissions).setData(key, value); - - // prettier-ignore - const result = await context.universalProfile.getData(key); - expect(result).to.equal(value); - }); - - it('should pass when address had an invalid bytes28[CompactBytesArray] initially', async () => { - const key = - ERC725YDataKeys.LSP6['AddressPermissions:AllowedCalls'] + - invalidBeneficiary.address.substring(2); + describe('when controller to edit Allowed Calls for has some permissions set', () => { + it('should fail when beneficiary had no values set under AddressPermissions:AllowedCalls:...', async () => { + const newController = ethers.Wallet.createRandom(); - const value = combineAllowedCalls( - // allow beneficiary to CALL only function selectors 0xcafecafe and 0xbeefbeef - [CALLTYPE.CALL, CALLTYPE.CALL], - [ - '0xffffffffffffffffffffffffffffffffffffffff', - '0xffffffffffffffffffffffffffffffffffffffff', - ], - ['0xffffffff', '0xffffffff'], - ['0xcafecafe', '0xbeefbeef'], - ); + const key = + ERC725YDataKeys.LSP6['AddressPermissions:AllowedCalls'] + + newController.address.substr(2); - await context.universalProfile.connect(canOnlyEditPermissions).setData(key, value); + const value = combineAllowedCalls( + // allow beneficiary to CALL only function selectors 0xcafecafe and 0xbeefbeef + [CALLTYPE.CALL, CALLTYPE.CALL], + [ + '0xffffffffffffffffffffffffffffffffffffffff', + '0xffffffffffffffffffffffffffffffffffffffff', + ], + ['0xffffffff', '0xffffffff'], + ['0xcafecafe', '0xbeefbeef'], + ); - // prettier-ignore - const result = await context.universalProfile.getData(key); - expect(result).to.equal(value); - }); + await expect(context.universalProfile.connect(canOnlyEditPermissions).setData(key, value)) + .to.be.revertedWithCustomError(context.keyManager, 'NotAuthorised') + .withArgs(canOnlyEditPermissions.address, 'ADDCONTROLLER'); + }); - // Even if the controller had some 00 bytes set as allowed calls, it is not considered as it does not have any allowed calls set - // but rather that its allowed calls are "disabled" - describe('when beneficiary (= controller) had 00 bytes set initially as allowed calls (e.g: allowed calls disabled)', () => { - it('should pass when address had 32 x 0 bytes set initially as allowed calls', async () => { + it('should pass when trying to edit existing allowed bytes4 selectors under ANY:ANY:', async () => { const key = ERC725YDataKeys.LSP6['AddressPermissions:AllowedCalls'] + - zero32Bytes.address.substring(2); + beneficiary.address.substring(2); const value = combineAllowedCalls( + // allow beneficiary to CALL only function selectors 0xcafecafe and 0xbeefbeef [CALLTYPE.CALL, CALLTYPE.CALL], [ '0xffffffffffffffffffffffffffffffffffffffff', @@ -726,12 +749,13 @@ export const shouldBehaveLikeSetAllowedCalls = (buildContext: () => Promise { + it('should pass when address had an invalid bytes28[CompactBytesArray] initially', async () => { const key = ERC725YDataKeys.LSP6['AddressPermissions:AllowedCalls'] + - zero40Bytes.address.substring(2); + invalidBeneficiary.address.substring(2); const value = combineAllowedCalls( + // allow beneficiary to CALL only function selectors 0xcafecafe and 0xbeefbeef [CALLTYPE.CALL, CALLTYPE.CALL], [ '0xffffffffffffffffffffffffffffffffffffffff', @@ -747,6 +771,52 @@ export const shouldBehaveLikeSetAllowedCalls = (buildContext: () => Promise { + it('should pass when address had 32 x 0 bytes set initially as allowed calls', async () => { + const key = + ERC725YDataKeys.LSP6['AddressPermissions:AllowedCalls'] + + zero32Bytes.address.substring(2); + + const value = combineAllowedCalls( + [CALLTYPE.CALL, CALLTYPE.CALL], + [ + '0xffffffffffffffffffffffffffffffffffffffff', + '0xffffffffffffffffffffffffffffffffffffffff', + ], + ['0xffffffff', '0xffffffff'], + ['0xcafecafe', '0xbeefbeef'], + ); + + await context.universalProfile.connect(canOnlyEditPermissions).setData(key, value); + + // prettier-ignore + const result = await context.universalProfile.getData(key); + expect(result).to.equal(value); + }); + + it('should pass when address had 40 x 0 bytes set initially as allowed functions', async () => { + const key = + ERC725YDataKeys.LSP6['AddressPermissions:AllowedCalls'] + + zero40Bytes.address.substring(2); + + const value = combineAllowedCalls( + [CALLTYPE.CALL, CALLTYPE.CALL], + [ + '0xffffffffffffffffffffffffffffffffffffffff', + '0xffffffffffffffffffffffffffffffffffffffff', + ], + ['0xffffffff', '0xffffffff'], + ['0xcafecafe', '0xbeefbeef'], + ); + + await context.universalProfile.connect(canOnlyEditPermissions).setData(key, value); + + // prettier-ignore + const result = await context.universalProfile.getData(key); + expect(result).to.equal(value); + }); + }); }); describe('when changing the list of selectors in allowed calls from existing ANY:ANY: to an invalid value', () => { @@ -796,14 +866,16 @@ export const shouldBehaveLikeSetAllowedCalls = (buildContext: () => Promise Promise Promise { - it('should fail when trying to edit existing allowed standards for an address', async () => { - const key = - ERC725YDataKeys.LSP6['AddressPermissions:AllowedCalls'] + - beneficiary.address.substring(2); - - const value = combineAllowedCalls( - [CALLTYPE.CALL, CALLTYPE.CALL, CALLTYPE.CALL, CALLTYPE.CALL], - [ - '0xffffffffffffffffffffffffffffffffffffffff', - '0xffffffffffffffffffffffffffffffffffffffff', - '0xffffffffffffffffffffffffffffffffffffffff', - '0xffffffffffffffffffffffffffffffffffffffff', - ], - [ - INTERFACE_IDS.LSP7DigitalAsset, - INTERFACE_IDS.ERC20, - // add NFT standards (new LSP8 + old ERC721) - // in the list of allowed calls for the beneficiary controller - // (in addition to token contracts LSP7 + ERC20) - INTERFACE_IDS.LSP8IdentifiableDigitalAsset, - INTERFACE_IDS.ERC721, - ], - ['0xffffffff', '0xffffffff', '0xffffffff', '0xffffffff'], - ); - - await expect(context.universalProfile.connect(canOnlyAddController).setData(key, value)) - .to.be.revertedWithCustomError(context.keyManager, 'NotAuthorised') - .withArgs(canOnlyAddController.address, 'EDITPERMISSIONS'); - }); - - it('should fail with NotAuthorised -> when beneficiary address had an invalid bytes28[CompactBytesArray] initially', async () => { - const key = - ERC725YDataKeys.LSP6['AddressPermissions:AllowedCalls'] + - invalidBeneficiary.address.substring(2); - - const value = combineAllowedCalls( - [CALLTYPE.CALL, CALLTYPE.CALL, CALLTYPE.CALL, CALLTYPE.CALL], - [ - '0xffffffffffffffffffffffffffffffffffffffff', - '0xffffffffffffffffffffffffffffffffffffffff', - '0xffffffffffffffffffffffffffffffffffffffff', - '0xffffffffffffffffffffffffffffffffffffffff', - ], - [ - INTERFACE_IDS.LSP7DigitalAsset, - INTERFACE_IDS.ERC20, - // add NFT standards (new LSP8 + old ERC721) - // in the list of allowed calls for the beneficiary controller - // (in addition to token standards LSP7 + ERC20) - INTERFACE_IDS.LSP8IdentifiableDigitalAsset, - INTERFACE_IDS.ERC721, - ], - ['0xffffffff', '0xffffffff', '0xffffffff', '0xffffffff'], - ); - - await expect(context.universalProfile.connect(canOnlyAddController).setData(key, value)) - .to.be.revertedWithCustomError(context.keyManager, 'NotAuthorised') - .withArgs(canOnlyAddController.address, 'EDITPERMISSIONS'); - }); - - // Even if the controller had some 00 bytes set as allowed calls, it is not considered as it does not have any allowed calls set - // but rather that its allowed calls are "disabled" - describe('when beneficiary (= controller) had 00 bytes set initially as allowed calls (e.g: allowed calls disabled)', () => { - it('should fail with NotAuthorised -> when beneficiary had 32 x 0 bytes set initially as allowed calls', async () => { + describe('when controller / beneficiary has some allowed calls set', () => { + it('should fail when trying to edit existing allowed standards for an address', async () => { const key = ERC725YDataKeys.LSP6['AddressPermissions:AllowedCalls'] + - zero32Bytes.address.substring(2); + beneficiary.address.substring(2); const value = combineAllowedCalls( [CALLTYPE.CALL, CALLTYPE.CALL, CALLTYPE.CALL, CALLTYPE.CALL], @@ -910,6 +924,9 @@ export const shouldBehaveLikeSetAllowedCalls = (buildContext: () => Promise Promise when beneficiary had 40 x 0 bytes set initially as allowed calls', async () => { + it('should fail with NotAuthorised -> when beneficiary address had an invalid bytes28[CompactBytesArray] initially', async () => { const key = ERC725YDataKeys.LSP6['AddressPermissions:AllowedCalls'] + - zero40Bytes.address.substring(2); + invalidBeneficiary.address.substring(2); const value = combineAllowedCalls( [CALLTYPE.CALL, CALLTYPE.CALL, CALLTYPE.CALL, CALLTYPE.CALL], @@ -937,6 +954,9 @@ export const shouldBehaveLikeSetAllowedCalls = (buildContext: () => Promise Promise { + it('should fail with NotAuthorised -> when beneficiary had 32 x 0 bytes set initially as allowed calls', async () => { + const key = + ERC725YDataKeys.LSP6['AddressPermissions:AllowedCalls'] + + zero32Bytes.address.substring(2); + + const value = combineAllowedCalls( + [CALLTYPE.CALL, CALLTYPE.CALL, CALLTYPE.CALL, CALLTYPE.CALL], + [ + '0xffffffffffffffffffffffffffffffffffffffff', + '0xffffffffffffffffffffffffffffffffffffffff', + '0xffffffffffffffffffffffffffffffffffffffff', + '0xffffffffffffffffffffffffffffffffffffffff', + ], + [ + INTERFACE_IDS.LSP7DigitalAsset, + INTERFACE_IDS.ERC20, + INTERFACE_IDS.LSP8IdentifiableDigitalAsset, + INTERFACE_IDS.ERC721, + ], + ['0xffffffff', '0xffffffff', '0xffffffff', '0xffffffff'], + ); + + await expect(context.universalProfile.connect(canOnlyAddController).setData(key, value)) + .to.be.revertedWithCustomError(context.keyManager, 'NotAuthorised') + .withArgs(canOnlyAddController.address, 'EDITPERMISSIONS'); + }); + + it('should fail with NotAuthorised -> when beneficiary had 40 x 0 bytes set initially as allowed calls', async () => { + const key = + ERC725YDataKeys.LSP6['AddressPermissions:AllowedCalls'] + + zero40Bytes.address.substring(2); + + const value = combineAllowedCalls( + [CALLTYPE.CALL, CALLTYPE.CALL, CALLTYPE.CALL, CALLTYPE.CALL], + [ + '0xffffffffffffffffffffffffffffffffffffffff', + '0xffffffffffffffffffffffffffffffffffffffff', + '0xffffffffffffffffffffffffffffffffffffffff', + '0xffffffffffffffffffffffffffffffffffffffff', + ], + [ + INTERFACE_IDS.LSP7DigitalAsset, + INTERFACE_IDS.ERC20, + INTERFACE_IDS.LSP8IdentifiableDigitalAsset, + INTERFACE_IDS.ERC721, + ], + ['0xffffffff', '0xffffffff', '0xffffffff', '0xffffffff'], + ); + + await expect(context.universalProfile.connect(canOnlyAddController).setData(key, value)) + .to.be.revertedWithCustomError(context.keyManager, 'NotAuthorised') + .withArgs(canOnlyAddController.address, 'EDITPERMISSIONS'); + }); + }); }); it('should pass when beneficiary had no values set under AddressPermissions:AllowedCalls:... + setting a valid bytes28[CompactBytesArray]', async () => { @@ -1014,91 +1092,13 @@ export const shouldBehaveLikeSetAllowedCalls = (buildContext: () => Promise { - it('should fail when beneficiary had no values set under AddressPermissions:AllowedCalls:...', async () => { - const newController = ethers.Wallet.createRandom(); - - const key = - ERC725YDataKeys.LSP6['AddressPermissions:AllowedCalls'] + newController.address.substr(2); - - const value = combineAllowedCalls( - [CALLTYPE.CALL, CALLTYPE.CALL], - [ - '0xffffffffffffffffffffffffffffffffffffffff', - '0xffffffffffffffffffffffffffffffffffffffff', - ], - // try to add in the list of allowed calls for the beneficiary controller - // the rights to CALL any LSP7 or ERC20 token contract - // (NB: just the AllowedCalls, not the permission CALL) - [INTERFACE_IDS.LSP7DigitalAsset, INTERFACE_IDS.ERC20], - ['0xffffffff', '0xffffffff'], - ); - - await expect(context.universalProfile.connect(canOnlyEditPermissions).setData(key, value)) - .to.be.revertedWithCustomError(context.keyManager, 'NotAuthorised') - .withArgs(canOnlyEditPermissions.address, 'ADDCONTROLLER'); - }); - - it('should pass when trying to edit existing allowed standards for an address', async () => { - const key = - ERC725YDataKeys.LSP6['AddressPermissions:AllowedCalls'] + - beneficiary.address.substring(2); - - const value = combineAllowedCalls( - [CALLTYPE.CALL, CALLTYPE.CALL, CALLTYPE.CALL, CALLTYPE.CALL], - [ - '0xffffffffffffffffffffffffffffffffffffffff', - '0xffffffffffffffffffffffffffffffffffffffff', - '0xffffffffffffffffffffffffffffffffffffffff', - '0xffffffffffffffffffffffffffffffffffffffff', - ], - [ - INTERFACE_IDS.LSP7DigitalAsset, - INTERFACE_IDS.ERC20, - // add NFT standards (new LSP8 + old ERC721) - // in the list of allowed calls for the beneficiary controller - // (in addition to token standards LSP7 + ERC20) - INTERFACE_IDS.LSP8IdentifiableDigitalAsset, - INTERFACE_IDS.ERC721, - ], - ['0xffffffff', '0xffffffff', '0xffffffff', '0xffffffff'], - ); - - await context.universalProfile.connect(canOnlyEditPermissions).setData(key, value); - - // prettier-ignore - const result = await context.universalProfile.getData(key); - expect(result).to.equal(value); - }); - - it('should pass when address had an invalid bytes28[CompactBytesArray] initially', async () => { - const key = - ERC725YDataKeys.LSP6['AddressPermissions:AllowedCalls'] + - invalidBeneficiary.address.substring(2); - - const value = combineAllowedCalls( - [CALLTYPE.CALL, CALLTYPE.CALL], - [ - '0xffffffffffffffffffffffffffffffffffffffff', - '0xffffffffffffffffffffffffffffffffffffffff', - ], - [INTERFACE_IDS.LSP7DigitalAsset, INTERFACE_IDS.ERC20], - ['0xffffffff', '0xffffffff'], - ); - - await context.universalProfile.connect(canOnlyEditPermissions).setData(key, value); - - // prettier-ignore - const result = await context.universalProfile.getData(key); - expect(result).to.equal(value); - }); + describe('when controller / beneificary had some allowed calls set', () => { + it('should fail when beneficiary had no values set under AddressPermissions:AllowedCalls:...', async () => { + const newController = ethers.Wallet.createRandom(); - // Even if the controller had some 00 bytes set as allowed calls, it is not considered as it does not have any allowed calls set - // but rather that its allowed calls are "disabled" - describe('when beneficiary (= controller) had 00 bytes set initially as allowed calls (e.g: allowed calls disabled)', () => { - it('should pass when address had 32 x 0 bytes set initially as allowed calls', async () => { const key = ERC725YDataKeys.LSP6['AddressPermissions:AllowedCalls'] + - zero32Bytes.address.substring(2); + newController.address.substr(2); const value = combineAllowedCalls( [CALLTYPE.CALL, CALLTYPE.CALL], @@ -1106,10 +1106,43 @@ export const shouldBehaveLikeSetAllowedCalls = (buildContext: () => Promise { + const key = + ERC725YDataKeys.LSP6['AddressPermissions:AllowedCalls'] + + beneficiary.address.substring(2); + + const value = combineAllowedCalls( + [CALLTYPE.CALL, CALLTYPE.CALL, CALLTYPE.CALL, CALLTYPE.CALL], + [ + '0xffffffffffffffffffffffffffffffffffffffff', + '0xffffffffffffffffffffffffffffffffffffffff', + '0xffffffffffffffffffffffffffffffffffffffff', + '0xffffffffffffffffffffffffffffffffffffffff', + ], + [ + INTERFACE_IDS.LSP7DigitalAsset, + INTERFACE_IDS.ERC20, + // add NFT standards (new LSP8 + old ERC721) + // in the list of allowed calls for the beneficiary controller + // (in addition to token standards LSP7 + ERC20) + INTERFACE_IDS.LSP8IdentifiableDigitalAsset, + INTERFACE_IDS.ERC721, + ], + ['0xffffffff', '0xffffffff', '0xffffffff', '0xffffffff'], + ); + await context.universalProfile.connect(canOnlyEditPermissions).setData(key, value); // prettier-ignore @@ -1117,10 +1150,10 @@ export const shouldBehaveLikeSetAllowedCalls = (buildContext: () => Promise { + it('should pass when address had an invalid bytes28[CompactBytesArray] initially', async () => { const key = ERC725YDataKeys.LSP6['AddressPermissions:AllowedCalls'] + - zero40Bytes.address.substring(2); + invalidBeneficiary.address.substring(2); const value = combineAllowedCalls( [CALLTYPE.CALL, CALLTYPE.CALL], @@ -1138,6 +1171,52 @@ export const shouldBehaveLikeSetAllowedCalls = (buildContext: () => Promise { + it('should pass when address had 32 x 0 bytes set initially as allowed calls', async () => { + const key = + ERC725YDataKeys.LSP6['AddressPermissions:AllowedCalls'] + + zero32Bytes.address.substring(2); + + const value = combineAllowedCalls( + [CALLTYPE.CALL, CALLTYPE.CALL], + [ + '0xffffffffffffffffffffffffffffffffffffffff', + '0xffffffffffffffffffffffffffffffffffffffff', + ], + [INTERFACE_IDS.LSP7DigitalAsset, INTERFACE_IDS.ERC20], + ['0xffffffff', '0xffffffff'], + ); + + await context.universalProfile.connect(canOnlyEditPermissions).setData(key, value); + + // prettier-ignore + const result = await context.universalProfile.getData(key); + expect(result).to.equal(value); + }); + + it('should pass when address had 40 x 0 bytes set initially as allowed calls', async () => { + const key = + ERC725YDataKeys.LSP6['AddressPermissions:AllowedCalls'] + + zero40Bytes.address.substring(2); + + const value = combineAllowedCalls( + [CALLTYPE.CALL, CALLTYPE.CALL], + [ + '0xffffffffffffffffffffffffffffffffffffffff', + '0xffffffffffffffffffffffffffffffffffffffff', + ], + [INTERFACE_IDS.LSP7DigitalAsset, INTERFACE_IDS.ERC20], + ['0xffffffff', '0xffffffff'], + ); + + await context.universalProfile.connect(canOnlyEditPermissions).setData(key, value); + + // prettier-ignore + const result = await context.universalProfile.getData(key); + expect(result).to.equal(value); + }); + }); }); describe('when changing the list of interface IDs in allowed calls :ANY:ANY to an invalid value', () => { diff --git a/tests/LSP20CallVerification/LSP6/SetPermissions/SetAllowedERC725YDataKeys.test.ts b/tests/LSP20CallVerification/LSP6/SetPermissions/SetAllowedERC725YDataKeys.test.ts index 5bc43bc5b..cfbe6cc53 100644 --- a/tests/LSP20CallVerification/LSP6/SetPermissions/SetAllowedERC725YDataKeys.test.ts +++ b/tests/LSP20CallVerification/LSP6/SetPermissions/SetAllowedERC725YDataKeys.test.ts @@ -36,24 +36,27 @@ export const shouldBehaveLikeSetAllowedERC725YDataKeys = ( zero32Bytes = context.accounts[5]; zero40Bytes = context.accounts[6]; + // prettier-ignore const permissionKeys = [ - ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + - canOnlyAddController.address.substring(2), - ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + - canOnlyEditPermissions.address.substring(2), - ERC725YDataKeys.LSP6['AddressPermissions:AllowedERC725YDataKeys'] + - beneficiary.address.substring(2), - ERC725YDataKeys.LSP6['AddressPermissions:AllowedERC725YDataKeys'] + - invalidBeneficiary.address.substring(2), - ERC725YDataKeys.LSP6['AddressPermissions:AllowedERC725YDataKeys'] + - zero32Bytes.address.substring(2), - ERC725YDataKeys.LSP6['AddressPermissions:AllowedERC725YDataKeys'] + - zero40Bytes.address.substring(2), + ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + canOnlyAddController.address.substring(2), + ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + canOnlyEditPermissions.address.substring(2), + ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + beneficiary.address.substring(2), + ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + invalidBeneficiary.address.substring(2), + ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + zero32Bytes.address.substring(2), + ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + zero40Bytes.address.substring(2), + ERC725YDataKeys.LSP6['AddressPermissions:AllowedERC725YDataKeys'] + beneficiary.address.substring(2), + ERC725YDataKeys.LSP6['AddressPermissions:AllowedERC725YDataKeys'] + invalidBeneficiary.address.substring(2), + ERC725YDataKeys.LSP6['AddressPermissions:AllowedERC725YDataKeys'] + zero32Bytes.address.substring(2), + ERC725YDataKeys.LSP6['AddressPermissions:AllowedERC725YDataKeys'] + zero40Bytes.address.substring(2), ]; const permissionValues = [ PERMISSIONS.ADDCONTROLLER, PERMISSIONS.EDITPERMISSIONS, + PERMISSIONS.SETDATA, + PERMISSIONS.SETDATA, + PERMISSIONS.SETDATA, + PERMISSIONS.SETDATA, encodeCompactBytesArray([ ERC725YDataKeys.LSP3['LSP3Profile'], // prettier-ignore @@ -68,205 +71,211 @@ export const shouldBehaveLikeSetAllowedERC725YDataKeys = ( }); describe('when caller has ADDCONTROLLER', () => { - describe('when beneficiary had some ERC725Y data keys set under AddressPermissions:AllowedERC725YDataKeys:...', () => { - it('should fail when adding an extra allowed ERC725Y data key', async () => { - const key = - ERC725YDataKeys.LSP6['AddressPermissions:AllowedERC725YDataKeys'] + - beneficiary.address.substring(2); - - const value = encodeCompactBytesArray([ - ERC725YDataKeys.LSP3['LSP3Profile'], - // prettier-ignore - ethers.utils.keccak256(ethers.utils.toUtf8Bytes("Some Custom Profile Data Key")), - // prettier-ignore - ethers.utils.keccak256(ethers.utils.toUtf8Bytes("Another Custom Data Key")), - ]); - - await expect(context.universalProfile.connect(canOnlyAddController).setData(key, value)) - .to.be.revertedWithCustomError(context.keyManager, 'NotAuthorised') - .withArgs(canOnlyAddController.address, 'EDITPERMISSIONS'); - }); - - it('should fail when removing an allowed ERC725Y data key', async () => { - const key = - ERC725YDataKeys.LSP6['AddressPermissions:AllowedERC725YDataKeys'] + - beneficiary.address.substring(2); - - const value = encodeCompactBytesArray([ERC725YDataKeys.LSP3['LSP3Profile']]); - - await expect(context.universalProfile.connect(canOnlyAddController).setData(key, value)) - .to.be.revertedWithCustomError(context.keyManager, 'NotAuthorised') - .withArgs(canOnlyAddController.address, 'EDITPERMISSIONS'); - }); - - it('should fail when trying to clear the CompactedBytesArray completely', async () => { - const key = - ERC725YDataKeys.LSP6['AddressPermissions:AllowedERC725YDataKeys'] + - beneficiary.address.substring(2); - - const value = '0x'; - - await expect(context.universalProfile.connect(canOnlyAddController).setData(key, value)) - .to.be.revertedWithCustomError(context.keyManager, 'NotAuthorised') - .withArgs(canOnlyAddController.address, 'EDITPERMISSIONS'); + describe('when controller / beneficiary had some permissions set', () => { + describe('when beneficiary had some ERC725Y data keys set under AddressPermissions:AllowedERC725YDataKeys:...', () => { + it('should fail when adding an extra allowed ERC725Y data key', async () => { + const key = + ERC725YDataKeys.LSP6['AddressPermissions:AllowedERC725YDataKeys'] + + beneficiary.address.substring(2); + + const value = encodeCompactBytesArray([ + ERC725YDataKeys.LSP3['LSP3Profile'], + // prettier-ignore + ethers.utils.keccak256(ethers.utils.toUtf8Bytes("Some Custom Profile Data Key")), + // prettier-ignore + ethers.utils.keccak256(ethers.utils.toUtf8Bytes("Another Custom Data Key")), + ]); + + await expect(context.universalProfile.connect(canOnlyAddController).setData(key, value)) + .to.be.revertedWithCustomError(context.keyManager, 'NotAuthorised') + .withArgs(canOnlyAddController.address, 'EDITPERMISSIONS'); + }); + + it('should fail when removing an allowed ERC725Y data key', async () => { + const key = + ERC725YDataKeys.LSP6['AddressPermissions:AllowedERC725YDataKeys'] + + beneficiary.address.substring(2); + + const value = encodeCompactBytesArray([ERC725YDataKeys.LSP3['LSP3Profile']]); + + await expect(context.universalProfile.connect(canOnlyAddController).setData(key, value)) + .to.be.revertedWithCustomError(context.keyManager, 'NotAuthorised') + .withArgs(canOnlyAddController.address, 'EDITPERMISSIONS'); + }); + + it('should fail when trying to clear the CompactedBytesArray completely', async () => { + const key = + ERC725YDataKeys.LSP6['AddressPermissions:AllowedERC725YDataKeys'] + + beneficiary.address.substring(2); + + const value = '0x'; + + await expect(context.universalProfile.connect(canOnlyAddController).setData(key, value)) + .to.be.revertedWithCustomError(context.keyManager, 'NotAuthorised') + .withArgs(canOnlyAddController.address, 'EDITPERMISSIONS'); + }); + + it('should fail when setting an invalid CompactedBytesArray', async () => { + const key = + ERC725YDataKeys.LSP6['AddressPermissions:AllowedERC725YDataKeys'] + + beneficiary.address.substring(2); + + const value = '0xbadbadbadbad'; + + await expect( + context.universalProfile.connect(canOnlyAddController).setData(key, value), + ).to.be.revertedWithCustomError( + context.keyManager, + 'InvalidEncodedAllowedERC725YDataKeys', + ); + }); }); - it('should fail when setting an invalid CompactedBytesArray', async () => { - const key = - ERC725YDataKeys.LSP6['AddressPermissions:AllowedERC725YDataKeys'] + - beneficiary.address.substring(2); - - const value = '0xbadbadbadbad'; + describe('when beneficiary had no ERC725Y data keys set under AddressPermissions:AllowedERC725YDataKeys:...', () => { + it('should pass when setting a valid CompactedBytesArray', async () => { + const newController = ethers.Wallet.createRandom(); - await expect( - context.universalProfile.connect(canOnlyAddController).setData(key, value), - ).to.be.revertedWithCustomError( - context.keyManager, - 'InvalidEncodedAllowedERC725YDataKeys', - ); - }); - }); + const key = + ERC725YDataKeys.LSP6['AddressPermissions:AllowedERC725YDataKeys'] + + newController.address.substr(2); - describe('when beneficiary had no ERC725Y data keys set under AddressPermissions:AllowedERC725YDataKeys:...', () => { - it('should pass when setting a valid CompactedBytesArray', async () => { - const newController = ethers.Wallet.createRandom(); + const value = encodeCompactBytesArray([ + // prettier-ignore + ethers.utils.keccak256(ethers.utils.toUtf8Bytes("My Custom Profile Key 1")), + // prettier-ignore + ethers.utils.keccak256(ethers.utils.toUtf8Bytes("My Custom Profile Key 2")), + ]); - const key = - ERC725YDataKeys.LSP6['AddressPermissions:AllowedERC725YDataKeys'] + - newController.address.substr(2); + await context.universalProfile.connect(canOnlyAddController).setData(key, value); - const value = encodeCompactBytesArray([ - // prettier-ignore - ethers.utils.keccak256(ethers.utils.toUtf8Bytes("My Custom Profile Key 1")), // prettier-ignore - ethers.utils.keccak256(ethers.utils.toUtf8Bytes("My Custom Profile Key 2")), - ]); - - await context.universalProfile.connect(canOnlyAddController).setData(key, value); - - // prettier-ignore - const result = await context.universalProfile.getData(key); - expect(result).to.equal(value); - }); - - it('should fail when setting an invalid CompactedBytesArray (random bytes)', async () => { - const newController = ethers.Wallet.createRandom(); - - const key = - ERC725YDataKeys.LSP6['AddressPermissions:AllowedERC725YDataKeys'] + - newController.address.substr(2); - - const value = '0xbadbadbadbad'; - - await expect( - context.universalProfile.connect(canOnlyAddController).setData(key, value), - ).to.be.revertedWithCustomError( - context.keyManager, - 'InvalidEncodedAllowedERC725YDataKeys', - ); + const result = await context.universalProfile.getData(key); + expect(result).to.equal(value); + }); + + it('should fail when setting an invalid CompactedBytesArray (random bytes)', async () => { + const newController = ethers.Wallet.createRandom(); + + const key = + ERC725YDataKeys.LSP6['AddressPermissions:AllowedERC725YDataKeys'] + + newController.address.substr(2); + + const value = '0xbadbadbadbad'; + + await expect( + context.universalProfile.connect(canOnlyAddController).setData(key, value), + ).to.be.revertedWithCustomError( + context.keyManager, + 'InvalidEncodedAllowedERC725YDataKeys', + ); + }); }); }); }); describe('when caller has EDITPERMISSIONS', () => { - describe('when beneficiary had some ERC725Y data keys set under AddressPermissions:AllowedERC725YDataKeys:...', () => { - it('should pass when adding an extra allowed ERC725Y data key', async () => { - const key = - ERC725YDataKeys.LSP6['AddressPermissions:AllowedERC725YDataKeys'] + - beneficiary.address.substring(2); - - const value = encodeCompactBytesArray([ - ERC725YDataKeys.LSP3['LSP3Profile'], - // prettier-ignore - ethers.utils.keccak256(ethers.utils.toUtf8Bytes("Some Custom Profile Data Key")), - // prettier-ignore - ethers.utils.keccak256(ethers.utils.toUtf8Bytes("Another Custom Data Key")), - ]); - - await context.universalProfile.connect(canOnlyEditPermissions).setData(key, value); - - // prettier-ignore - const result = await context.universalProfile.getData(key); - expect(result).to.equal(value); - }); - - it('should pass when removing an allowed ERC725Y data key', async () => { - const key = - ERC725YDataKeys.LSP6['AddressPermissions:AllowedERC725YDataKeys'] + - beneficiary.address.substring(2); - - const value = encodeCompactBytesArray([ERC725YDataKeys.LSP3['LSP3Profile']]); - - await context.universalProfile.connect(canOnlyEditPermissions).setData(key, value); - - // prettier-ignore - const result = await context.universalProfile.getData(key); - expect(result).to.equal(value); - }); - - it('should pass when trying to clear the CompactedBytesArray completely', async () => { - const key = - ERC725YDataKeys.LSP6['AddressPermissions:AllowedERC725YDataKeys'] + - beneficiary.address.substring(2); + describe('when controller / beneficiary had some permissions set', () => { + describe('when beneficiary had some ERC725Y data keys set under AddressPermissions:AllowedERC725YDataKeys:...', () => { + it('should pass when adding an extra allowed ERC725Y data key', async () => { + const key = + ERC725YDataKeys.LSP6['AddressPermissions:AllowedERC725YDataKeys'] + + beneficiary.address.substring(2); + + const value = encodeCompactBytesArray([ + ERC725YDataKeys.LSP3['LSP3Profile'], + // prettier-ignore + ethers.utils.keccak256(ethers.utils.toUtf8Bytes("Some Custom Profile Data Key")), + // prettier-ignore + ethers.utils.keccak256(ethers.utils.toUtf8Bytes("Another Custom Data Key")), + ]); + + await context.universalProfile.connect(canOnlyEditPermissions).setData(key, value); - const value = '0x'; - - await context.universalProfile.connect(canOnlyEditPermissions).setData(key, value); + // prettier-ignore + const result = await context.universalProfile.getData(key); + expect(result).to.equal(value); + }); - // prettier-ignore - const result = await context.universalProfile.getData(key); - expect(result).to.equal(value); - }); + it('should pass when removing an allowed ERC725Y data key', async () => { + const key = + ERC725YDataKeys.LSP6['AddressPermissions:AllowedERC725YDataKeys'] + + beneficiary.address.substring(2); - it('should fail when setting an invalid CompactedBytesArray', async () => { - const key = - ERC725YDataKeys.LSP6['AddressPermissions:AllowedERC725YDataKeys'] + - beneficiary.address.substring(2); + const value = encodeCompactBytesArray([ERC725YDataKeys.LSP3['LSP3Profile']]); - const value = '0xbadbadbadbad'; + await context.universalProfile.connect(canOnlyEditPermissions).setData(key, value); - await expect( - context.universalProfile.connect(canOnlyEditPermissions).setData(key, value), - ).to.be.revertedWithCustomError( - context.keyManager, - 'InvalidEncodedAllowedERC725YDataKeys', - ); - }); - }); + // prettier-ignore + const result = await context.universalProfile.getData(key); + expect(result).to.equal(value); + }); - describe('when beneficiary had no ERC725Y data keys set under AddressPermissions:AllowedERC725YDataKeys:...', () => { - it('should fail and not authorize to add a list of allowed ERC725Y data keys (not authorised)', async () => { - const newController = ethers.Wallet.createRandom(); + it('should pass when trying to clear the CompactedBytesArray completely', async () => { + const key = + ERC725YDataKeys.LSP6['AddressPermissions:AllowedERC725YDataKeys'] + + beneficiary.address.substring(2); - const key = - ERC725YDataKeys.LSP6['AddressPermissions:AllowedERC725YDataKeys'] + - newController.address.substr(2); + const value = '0x'; - const value = encodeCompactBytesArray([ - ethers.utils.keccak256(ethers.utils.toUtf8Bytes('My Custom Key 1')), - ethers.utils.keccak256(ethers.utils.toUtf8Bytes('My Custom Key 2')), - ]); + await context.universalProfile.connect(canOnlyEditPermissions).setData(key, value); - await expect(context.universalProfile.connect(canOnlyEditPermissions).setData(key, value)) - .to.be.revertedWithCustomError(context.keyManager, 'NotAuthorised') - .withArgs(canOnlyEditPermissions.address, 'ADDCONTROLLER'); + // prettier-ignore + const result = await context.universalProfile.getData(key); + expect(result).to.equal(value); + }); + + it('should fail when setting an invalid CompactedBytesArray', async () => { + const key = + ERC725YDataKeys.LSP6['AddressPermissions:AllowedERC725YDataKeys'] + + beneficiary.address.substring(2); + + const value = '0xbadbadbadbad'; + + await expect( + context.universalProfile.connect(canOnlyEditPermissions).setData(key, value), + ).to.be.revertedWithCustomError( + context.keyManager, + 'InvalidEncodedAllowedERC725YDataKeys', + ); + }); }); - it('should fail when setting an invalid CompactedBytesArray', async () => { - const newController = ethers.Wallet.createRandom(); - - const key = - ERC725YDataKeys.LSP6['AddressPermissions:AllowedERC725YDataKeys'] + - newController.address.substr(2); - - const value = '0xbadbadbadbad'; - - await expect( - context.universalProfile.connect(canOnlyEditPermissions).setData(key, value), - ).to.be.revertedWithCustomError( - context.keyManager, - 'InvalidEncodedAllowedERC725YDataKeys', - ); + describe('when beneficiary had no ERC725Y data keys set under AddressPermissions:AllowedERC725YDataKeys:...', () => { + it('should fail and not authorize to add a list of allowed ERC725Y data keys (not authorised)', async () => { + const newController = ethers.Wallet.createRandom(); + + const key = + ERC725YDataKeys.LSP6['AddressPermissions:AllowedERC725YDataKeys'] + + newController.address.substr(2); + + const value = encodeCompactBytesArray([ + ethers.utils.keccak256(ethers.utils.toUtf8Bytes('My Custom Key 1')), + ethers.utils.keccak256(ethers.utils.toUtf8Bytes('My Custom Key 2')), + ]); + + await expect( + context.universalProfile.connect(canOnlyEditPermissions).setData(key, value), + ) + .to.be.revertedWithCustomError(context.keyManager, 'NotAuthorised') + .withArgs(canOnlyEditPermissions.address, 'ADDCONTROLLER'); + }); + + it('should fail when setting an invalid CompactedBytesArray', async () => { + const newController = ethers.Wallet.createRandom(); + + const key = + ERC725YDataKeys.LSP6['AddressPermissions:AllowedERC725YDataKeys'] + + newController.address.substr(2); + + const value = '0xbadbadbadbad'; + + await expect( + context.universalProfile.connect(canOnlyEditPermissions).setData(key, value), + ).to.be.revertedWithCustomError( + context.keyManager, + 'InvalidEncodedAllowedERC725YDataKeys', + ); + }); }); }); }); diff --git a/tests/LSP23LinkedContractsDeployment/LSP23LinkedContractsDeployment.test.ts b/tests/LSP23LinkedContractsDeployment/LSP23LinkedContractsDeployment.test.ts index 4dfa7a6ff..b94465f54 100644 --- a/tests/LSP23LinkedContractsDeployment/LSP23LinkedContractsDeployment.test.ts +++ b/tests/LSP23LinkedContractsDeployment/LSP23LinkedContractsDeployment.test.ts @@ -2,10 +2,11 @@ import { ethers } from 'hardhat'; import { expect } from 'chai'; import { - LSP23LinkedContractsFactory, + ILSP23LinkedContractsFactory, + KeyManagerWithExtraParams, LSP6KeyManager, UniversalProfile, -} from '../../typechain-types'; +} from '../../types'; import { ERC725YDataKeys } from '../../constants'; import { calculateProxiesAddresses, @@ -35,16 +36,16 @@ describe('UniversalProfileDeployer', function () { const salt = ethers.utils.randomBytes(32); - const primaryContractDeployment: LSP23LinkedContractsFactory.PrimaryContractDeploymentStruct = + const primaryContractDeployment: ILSP23LinkedContractsFactory.PrimaryContractDeploymentStruct = { salt, fundingAmount: 0, creationBytecode: universalProfileCreationCode, }; - const secondaryContractDeployment: LSP23LinkedContractsFactory.SecondaryContractDeploymentInitStruct = + const secondaryContractDeployment: ILSP23LinkedContractsFactory.SecondaryContractDeploymentStruct = { - fundingAmount: 0, + fundingAmount: ethers.BigNumber.from(0), creationBytecode: keyManagerBytecode, addPrimaryContractAddress: true, extraConstructorParams: '0x', @@ -125,8 +126,10 @@ describe('UniversalProfileDeployer', function () { expect(upContract).to.equal(expectedUpAddress); expect(keyManagerContract).to.equal(expectedKeyManagerAddress); - const keyManagerInstance: LSP6KeyManager = KeyManagerFactory.attach(keyManagerContract); - const universalProfileInstance: UniversalProfile = UniversalProfileFactory.attach(upContract); + const keyManagerInstance = KeyManagerFactory.attach(keyManagerContract) as LSP6KeyManager; + const universalProfileInstance = UniversalProfileFactory.attach( + upContract, + ) as UniversalProfile; // CHECK that the UP is owned by the KeyManager contract expect(await universalProfileInstance.owner()).to.equal(keyManagerContract); @@ -154,14 +157,14 @@ describe('UniversalProfileDeployer', function () { const salt = ethers.utils.randomBytes(32); - const primaryContractDeployment: LSP23LinkedContractsFactory.PrimaryContractDeploymentStruct = + const primaryContractDeployment: ILSP23LinkedContractsFactory.PrimaryContractDeploymentStruct = { salt, fundingAmount: universalProfileFundAmount, creationBytecode: universalProfileCreationCode, }; - const secondaryContractDeployment: LSP23LinkedContractsFactory.SecondaryContractDeploymentInitStruct = + const secondaryContractDeployment: ILSP23LinkedContractsFactory.SecondaryContractDeploymentStruct = { fundingAmount: 0, creationBytecode: keyManagerBytecode, @@ -253,14 +256,14 @@ describe('UniversalProfileDeployer', function () { const salt = ethers.utils.randomBytes(32); - const primaryContractDeployment: LSP23LinkedContractsFactory.PrimaryContractDeploymentStruct = + const primaryContractDeployment: ILSP23LinkedContractsFactory.PrimaryContractDeploymentStruct = { salt, fundingAmount: universalProfileFundAmount, creationBytecode: universalProfileCreationCode, }; - const secondaryContractDeployment: LSP23LinkedContractsFactory.SecondaryContractDeploymentInitStruct = + const secondaryContractDeployment: ILSP23LinkedContractsFactory.SecondaryContractDeploymentStruct = { fundingAmount: 0, creationBytecode: keyManagerBytecode, @@ -341,7 +344,7 @@ describe('UniversalProfileDeployer', function () { const salt = ethers.utils.randomBytes(32); - const primaryContractDeployment: LSP23LinkedContractsFactory.PrimaryContractDeploymentStruct = + const primaryContractDeployment: ILSP23LinkedContractsFactory.PrimaryContractDeploymentStruct = { salt, fundingAmount: 0, @@ -364,7 +367,7 @@ describe('UniversalProfileDeployer', function () { keyManagerBytecode = keyManagerBytecode + secondaryContractFirstParam.slice(2); - const secondaryContractDeployment: LSP23LinkedContractsFactory.SecondaryContractDeploymentInitStruct = + const secondaryContractDeployment: ILSP23LinkedContractsFactory.SecondaryContractDeploymentStruct = { fundingAmount: 0, creationBytecode: keyManagerBytecode, @@ -447,8 +450,12 @@ describe('UniversalProfileDeployer', function () { expect(upContract).to.equal(expectedUpAddress); expect(keyManagerContract).to.equal(expectedKeyManagerAddress); - const keyManagerInstance: LSP6KeyManager = KeyManagerFactory.attach(keyManagerContract); - const universalProfileInstance: UniversalProfile = UniversalProfileFactory.attach(upContract); + const keyManagerInstance = KeyManagerFactory.attach( + keyManagerContract, + ) as KeyManagerWithExtraParams; + const universalProfileInstance = UniversalProfileFactory.attach( + upContract, + ) as UniversalProfile; // CHECK that the UP is owned by the KeyManager contract expect(await universalProfileInstance.owner()).to.equal(keyManagerContract); @@ -456,8 +463,8 @@ describe('UniversalProfileDeployer', function () { // CHECK that the `target()` of the KeyManager contract is the UP contract expect(await keyManagerInstance.target()).to.equal(upContract); - expect(await keyManagerInstance.firstParam()).to.deep.equal(firstAddress); - expect(await keyManagerInstance.lastParam()).to.deep.equal(lastAddress); + expect(await keyManagerInstance.FIRST_PARAM()).to.deep.equal(firstAddress); + expect(await keyManagerInstance.LAST_PARAM()).to.deep.equal(lastAddress); }); }); describe('for proxies deployment', function () { @@ -475,7 +482,7 @@ describe('UniversalProfileDeployer', function () { const salt = ethers.utils.randomBytes(32); - const primaryContractDeploymentInit: LSP23LinkedContractsFactory.PrimaryContractDeploymentInitStruct = + const primaryContractDeploymentInit: ILSP23LinkedContractsFactory.PrimaryContractDeploymentInitStruct = { salt, fundingAmount: 0, @@ -485,7 +492,7 @@ describe('UniversalProfileDeployer', function () { ]), }; - const secondaryContractDeploymentInit: LSP23LinkedContractsFactory.SecondaryContractDeploymentInitStruct = + const secondaryContractDeploymentInit: ILSP23LinkedContractsFactory.SecondaryContractDeploymentInitStruct = { fundingAmount: 0, implementationContract: keyManagerInit.address, @@ -605,7 +612,7 @@ describe('UniversalProfileDeployer', function () { const salt = ethers.utils.randomBytes(32); - const primaryContractDeploymentInit: LSP23LinkedContractsFactory.PrimaryContractDeploymentInitStruct = + const primaryContractDeploymentInit: ILSP23LinkedContractsFactory.PrimaryContractDeploymentInitStruct = { salt, fundingAmount: primaryFundingAmount, @@ -615,7 +622,7 @@ describe('UniversalProfileDeployer', function () { ]), }; - const secondaryContractDeploymentInit: LSP23LinkedContractsFactory.SecondaryContractDeploymentInitStruct = + const secondaryContractDeploymentInit: ILSP23LinkedContractsFactory.SecondaryContractDeploymentInitStruct = { fundingAmount: secondaryFundingAmount, implementationContract: keyManagerInit.address, @@ -672,7 +679,7 @@ describe('UniversalProfileDeployer', function () { const primaryFundingAmount = ethers.utils.parseEther('1'); const secondaryFundingAmount = ethers.utils.parseEther('0'); // key manager does not accept funds - const primaryContractDeploymentInit: LSP23LinkedContractsFactory.PrimaryContractDeploymentInitStruct = + const primaryContractDeploymentInit: ILSP23LinkedContractsFactory.PrimaryContractDeploymentInitStruct = { salt, fundingAmount: primaryFundingAmount, @@ -682,7 +689,7 @@ describe('UniversalProfileDeployer', function () { ]), }; - const secondaryContractDeploymentInit: LSP23LinkedContractsFactory.SecondaryContractDeploymentInitStruct = + const secondaryContractDeploymentInit: ILSP23LinkedContractsFactory.SecondaryContractDeploymentInitStruct = { fundingAmount: secondaryFundingAmount, implementationContract: keyManagerInit.address, @@ -779,12 +786,8 @@ describe('UniversalProfileDeployer', function () { expect(secondaryAddress).to.not.equal(ethers.constants.AddressZero); }); it('should deploy proxies with correct initialization calldata (with secondary contract contains extraParams)', async function () { - const { - LSP23LinkedContractsFactory, - upInitPostDeploymentModule, - universalProfileInit, - keyManagerInit, - } = await deployImplementationContracts(); + const { LSP23LinkedContractsFactory, upInitPostDeploymentModule, universalProfileInit } = + await deployImplementationContracts(); const KeyManagerWithExtraParamsFactory = await ethers.getContractFactory( 'KeyManagerInitWithExtraParams', diff --git a/tests/LSP23LinkedContractsDeployment/helpers.ts b/tests/LSP23LinkedContractsDeployment/helpers.ts index 1c48b9e09..2622b5ab3 100644 --- a/tests/LSP23LinkedContractsDeployment/helpers.ts +++ b/tests/LSP23LinkedContractsDeployment/helpers.ts @@ -1,13 +1,14 @@ import { ethers } from 'hardhat'; import { BytesLike } from 'ethers'; +import { PromiseOrValue } from '../../types/common'; export async function calculateProxiesAddresses( - salt: BytesLike, - primaryImplementationContractAddress: string, - secondaryImplementationContractAddress: string, - secondaryContractInitializationCalldata: BytesLike, - secondaryContractAddControlledContractAddress: boolean, - secondaryContractExtraInitializationParams: BytesLike, + salt: PromiseOrValue, + primaryImplementationContractAddress: PromiseOrValue, + secondaryImplementationContractAddress: PromiseOrValue, + secondaryContractInitializationCalldata: PromiseOrValue, + secondaryContractAddControlledContractAddress: PromiseOrValue, + secondaryContractExtraInitializationParams: PromiseOrValue, upPostDeploymentModuleAddress: string, postDeploymentCalldata: BytesLike, linkedContractsFactoryAddress: string, @@ -32,7 +33,7 @@ export async function calculateProxiesAddresses( generatedSalt, ethers.utils.keccak256( '0x3d602d80600a3d3981f3363d3d373d3d3d363d73' + - primaryImplementationContractAddress.slice(2) + + (primaryImplementationContractAddress as string).slice(2) + '5af43d82803e903d91602b57fd5bf3', ), ); @@ -42,7 +43,7 @@ export async function calculateProxiesAddresses( ethers.utils.keccak256(expectedPrimaryContractAddress), ethers.utils.keccak256( '0x3d602d80600a3d3981f3363d3d373d3d3d363d73' + - secondaryImplementationContractAddress.slice(2) + + (secondaryImplementationContractAddress as string).slice(2) + '5af43d82803e903d91602b57fd5bf3', ), ); diff --git a/tests/LSP2ERC725YJSONSchema/LSP2UtilsLibrary.test.ts b/tests/LSP2ERC725YJSONSchema/LSP2UtilsLibrary.test.ts index 316107c5a..51da0874f 100644 --- a/tests/LSP2ERC725YJSONSchema/LSP2UtilsLibrary.test.ts +++ b/tests/LSP2ERC725YJSONSchema/LSP2UtilsLibrary.test.ts @@ -3,7 +3,7 @@ import { ethers } from 'hardhat'; import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'; import { LSP2UtilsLibraryTester, LSP2UtilsLibraryTester__factory } from '../../types'; -import { abiCoder, encodeCompactBytesArray } from '../utils/helpers'; +import { encodeCompactBytesArray } from '../utils/helpers'; describe('LSP2Utils', () => { let accounts: SignerWithAddress[]; @@ -14,169 +14,6 @@ describe('LSP2Utils', () => { lsp2Utils = await new LSP2UtilsLibraryTester__factory(accounts[0]).deploy(); }); - describe('isEncodedArray(...)', () => { - describe('testing different zero bytes 00 of various length', () => { - it('should return false for 1 x empty zero bytes', async () => { - const data = '0x00'; - const result = await lsp2Utils.isEncodedArray(data); - expect(result).to.be.false; - }); - - it('should return false for 10 x empty zero bytes', async () => { - const data = '0x00000000000000000000'; - const result = await lsp2Utils.isEncodedArray(data); - expect(result).to.be.false; - }); - - it('should return false for 20 x empty zero bytes', async () => { - const data = '0x0000000000000000000000000000000000000000'; - const result = await lsp2Utils.isEncodedArray(data); - expect(result).to.be.false; - }); - - it('should return false for 30 x empty zero bytes', async () => { - const data = '0x000000000000000000000000000000000000000000000000000000000000'; - const result = await lsp2Utils.isEncodedArray(data); - expect(result).to.be.false; - }); - - it('should return true for 32 x empty zero bytes', async () => { - const data = '0x0000000000000000000000000000000000000000000000000000000000000000'; - const result = await lsp2Utils.isEncodedArray(data); - expect(result).to.be.true; - }); - - it('should return true for 40 x empty zero bytes', async () => { - const data = - '0x00000000000000000000000000000000000000000000000000000000000000000000000000000000'; - const result = await lsp2Utils.isEncodedArray(data); - expect(result).to.be.true; - }); - - it('should return true for 64 x empty zero bytes', async () => { - const data = '0x' + '00'.repeat(64); - const result = await lsp2Utils.isEncodedArray(data); - expect(result).to.be.true; - }); - - it('should return true for 100 x empty zero bytes', async () => { - const data = '0x' + '00'.repeat(100); - const result = await lsp2Utils.isEncodedArray(data); - expect(result).to.be.true; - }); - }); - - describe('testing various non-zero bytes input', () => { - describe('when 32 bytes', () => { - it('should return false (`uint256` data = 32)', async () => { - const data = abiCoder.encode(['uint256'], [32]); - const result = await lsp2Utils.isEncodedArray(data); - expect(result).to.be.false; - }); - - it('should return false (`uint256` data = 12345)', async () => { - const data = abiCoder.encode(['uint256'], [12345]); - const result = await lsp2Utils.isEncodedArray(data); - expect(result).to.be.false; - }); - - it('should return false (`bytes32` data = 0xcafecafecafecafe...)', async () => { - const data = abiCoder.encode( - ['bytes32'], - ['0xcafecafecafecafecafecafecafecafecafecafecafecafecafecafecafecafe'], - ); - const result = await lsp2Utils.isEncodedArray(data); - expect(result).to.be.false; - }); - }); - - describe('when less than 32 bytes', () => { - it('should return false with 4x random bytes', async () => { - const data = '0xaabbccdd'; - const result = await lsp2Utils.isEncodedArray(data); - expect(result).to.be.false; - }); - - it('should return false with 16x random bytes', async () => { - const data = '0x1234567890abcdef1234567890abcdef'; - const result = await lsp2Utils.isEncodedArray(data); - expect(result).to.be.false; - }); - }); - - describe('when abi-encoded array, with length = 0', () => { - it('should return true with 64 bytes -> offset = 0x20, length = 0 (null)', async () => { - const data = - '0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000'; - const result = await lsp2Utils.isEncodedArray(data); - expect(result).to.be.true; - }); - - it('should return true with 64 bytes -> offset = 0x20, length = 0 (null) + 10x extra zero bytes', async () => { - const data = - '0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000'; - const result = await lsp2Utils.isEncodedArray(data); - expect(result).to.be.true; - }); - - it('should return true with 64 bytes -> offset = 0x20, length = 0 (null) + 10x extra random bytes', async () => { - const data = - '0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000aaaabbbbccccddddeeee'; - const result = await lsp2Utils.isEncodedArray(data); - expect(result).to.be.true; - }); - }); - - describe('when abi-encoded array, with length = 1', () => { - it('should return true with 1x array element - offset = 0x20, length = 1', async () => { - const data = - '0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000a0Ee7A142d267C1f36714E4a8F75612F20a79720'; - const result = await lsp2Utils.isEncodedArray(data); - expect(result).to.be.true; - }); - - it('should return true with 1x array element - offset = 0x20, length = 1, +5 custom bytes in the end', async () => { - const data = - '0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000a0Ee7A142d267C1f36714E4a8F75612F20a79720ffffffffff'; - const result = await lsp2Utils.isEncodedArray(data); - expect(result).to.be.true; - }); - - it('should return true with 1x array element - offset = 0x25 (+ 5 custom bytes in between), length = 1', async () => { - const data = - '0x0000000000000000000000000000000000000000000000000000000000000025ffffffffff0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000a0Ee7A142d267C1f36714E4a8F75612F20a79720'; - const result = await lsp2Utils.isEncodedArray(data); - expect(result).to.be.true; - }); - }); - - describe('when not correctly abi-encoded array, with length = 1', () => { - it('should return false with 1x array element - offset = 0x20, length = 1, but 31 bytes only', async () => { - const data = - '0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000a0Ee7A142d267C1f36714E4a8F75612F20a797'; - const result = await lsp2Utils.isEncodedArray(data); - expect(result).to.be.false; - }); - - it('should return false with 1x array element - offset = 0x20, length = 1, but 30 bytes only', async () => { - const data = - '0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000a0Ee7A142d267C1f36714E4a8F75612F20a7'; - const result = await lsp2Utils.isEncodedArray(data); - expect(result).to.be.false; - }); - }); - - describe('when correctly abi-encoded array, but the length does not match the number of elements', () => { - it('should return false when 1x array element, but length = 2', async () => { - const data = - '0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000cafecafecafecafecafecafecafecafecafecafe'; - const result = await lsp2Utils.isEncodedArray(data); - expect(result).to.be.false; - }); - }); - }); - }); - describe('`isCompactBytesArray(...)`', () => { it('should return true with `0x` (equivalent to `[]`)', async () => { const data = '0x'; diff --git a/tests/LSP4DigitalAssetMetadata/LSP4Compatibility.test.ts b/tests/LSP4DigitalAssetMetadata/LSP4Compatibility.test.ts deleted file mode 100644 index fc3ced43e..000000000 --- a/tests/LSP4DigitalAssetMetadata/LSP4Compatibility.test.ts +++ /dev/null @@ -1,77 +0,0 @@ -import { expect } from 'chai'; -import { ethers } from 'hardhat'; -import { LSP4CompatibilityTester, LSP4CompatibilityTester__factory } from '../../types'; - -import type { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'; - -type LSP4CompatibilityTestAccounts = { - owner: SignerWithAddress; -}; - -const getNamedAccounts = async () => { - const [owner] = await ethers.getSigners(); - return { owner }; -}; - -type LSP4CompatibilityTestContext = { - accounts: LSP4CompatibilityTestAccounts; - lsp4Compatibility: LSP4CompatibilityTester; - deployParams: { - name: string; - symbol: string; - newOwner: string; - }; -}; - -describe('LSP4Compatibility', () => { - const buildTestContext = async (): Promise => { - const accounts = await getNamedAccounts(); - const deployParams = { - name: 'Much compat', - symbol: 'WOW', - newOwner: accounts.owner.address, - }; - - const lsp4Compatibility = await new LSP4CompatibilityTester__factory(accounts.owner).deploy( - deployParams.name, - deployParams.symbol, - deployParams.newOwner, - ); - - return { accounts, lsp4Compatibility, deployParams }; - }; - - describe('when using LSP4Compatibility', () => { - let context: LSP4CompatibilityTestContext; - - before(async () => { - context = await buildTestContext(); - }); - - it('should allow reading name', async () => { - // using compatibility getter -> returns(string) - const nameAsString = await context.lsp4Compatibility.name(); - expect(nameAsString).to.equal(context.deployParams.name); - - // using getData -> returns(bytes) - const nameAsBytes = await context.lsp4Compatibility.getData( - ethers.utils.keccak256(ethers.utils.toUtf8Bytes('LSP4TokenName')), - ); - - expect(ethers.utils.toUtf8String(nameAsBytes)).to.equal(context.deployParams.name); - }); - - it('should allow reading symbol', async () => { - // using compatibility getter -> returns(string) - const symbolAsString = await context.lsp4Compatibility.symbol(); - expect(symbolAsString).to.equal(context.deployParams.symbol); - - // using getData -> returns(bytes) - const symbolAsBytes = await context.lsp4Compatibility.getData( - ethers.utils.keccak256(ethers.utils.toUtf8Bytes('LSP4TokenSymbol')), - ); - - expect(ethers.utils.toUtf8String(symbolAsBytes)).to.equal(context.deployParams.symbol); - }); - }); -}); diff --git a/tests/LSP4DigitalAssetMetadata/LSP4DigitalAssetMetadata.behaviour.ts b/tests/LSP4DigitalAssetMetadata/LSP4DigitalAssetMetadata.behaviour.ts index d56ea1e7a..dd552e53b 100644 --- a/tests/LSP4DigitalAssetMetadata/LSP4DigitalAssetMetadata.behaviour.ts +++ b/tests/LSP4DigitalAssetMetadata/LSP4DigitalAssetMetadata.behaviour.ts @@ -24,6 +24,32 @@ export const shouldBehaveLikeLSP4DigitalAssetMetadata = ( }); describe('when setting data on ERC725Y storage', () => { + describe('when sending value while setting data', () => { + it('should revert with `setData`', async () => { + const msgValue = ethers.utils.parseEther('2'); + const key = ethers.utils.keccak256(ethers.utils.toUtf8Bytes('My Key')); + const value = ethers.utils.hexlify(ethers.utils.randomBytes(256)); + + await expect( + context.contract + .connect(context.deployParams.owner) + .setData(key, value, { value: msgValue }), + ).to.be.revertedWithCustomError(context.contract, 'ERC725Y_MsgValueDisallowed'); + }); + + it('should revert with `setDataBatch`', async () => { + const msgValue = ethers.utils.parseEther('2'); + const key = [ethers.utils.keccak256(ethers.utils.toUtf8Bytes('My Key'))]; + const value = [ethers.utils.hexlify(ethers.utils.randomBytes(256))]; + + await expect( + context.contract + .connect(context.deployParams.owner) + .setDataBatch(key, value, { value: msgValue }), + ).to.be.revertedWithCustomError(context.contract, 'ERC725Y_MsgValueDisallowed'); + }); + }); + it('should revert when trying to edit Token Name', async () => { const key = ERC725YDataKeys.LSP4['LSP4TokenName']; const value = ethers.utils.hexlify(ethers.utils.toUtf8Bytes('Overriden Token Name')); diff --git a/tests/LSP6KeyManager/Admin/PermissionChangeAddExtensions.test.ts b/tests/LSP6KeyManager/Admin/PermissionChangeAddExtensions.test.ts index dad51e43a..5cb9a7ab2 100644 --- a/tests/LSP6KeyManager/Admin/PermissionChangeAddExtensions.test.ts +++ b/tests/LSP6KeyManager/Admin/PermissionChangeAddExtensions.test.ts @@ -80,7 +80,8 @@ export const shouldBehaveLikePermissionChangeOrAddExtensions = ( canOnlyCall = context.accounts[6]; let permissionKeys = [ - ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + context.owner.address.substring(2), + ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + + context.mainController.address.substring(2), ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + canAddAndChangeExtensions.address.substring(2), ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + @@ -126,7 +127,7 @@ export const shouldBehaveLikePermissionChangeOrAddExtensions = ( permissionArrayValues = [ ethers.utils.hexZeroPad(ethers.utils.hexlify(7), 16), - context.owner.address, + context.mainController.address, canAddAndChangeExtensions.address, canOnlyAddExtensions.address, canOnlyChangeExtensions.address, @@ -154,7 +155,7 @@ export const shouldBehaveLikePermissionChangeOrAddExtensions = ( payloadParam.dataValue, ]); - await context.keyManager.connect(context.owner).execute(payload); + await context.keyManager.connect(context.mainController).execute(payload); const result = await context.universalProfile.getData(payloadParam.dataKey); expect(result).to.equal(payloadParam.dataValue); @@ -171,7 +172,7 @@ export const shouldBehaveLikePermissionChangeOrAddExtensions = ( payloadParam.dataValue, ]); - await context.keyManager.connect(context.owner).execute(payload); + await context.keyManager.connect(context.mainController).execute(payload); const result = await context.universalProfile.getData(payloadParam.dataKey); expect(result).to.equal(payloadParam.dataValue); @@ -188,7 +189,7 @@ export const shouldBehaveLikePermissionChangeOrAddExtensions = ( payloadParam.dataValue, ]); - await context.keyManager.connect(context.owner).execute(payload); + await context.keyManager.connect(context.mainController).execute(payload); const result = await context.universalProfile.getData(payloadParam.dataKey); expect(result).to.equal(payloadParam.dataValue); @@ -212,7 +213,7 @@ export const shouldBehaveLikePermissionChangeOrAddExtensions = ( ]); await expect( - context.keyManager.connect(context.owner).execute(payload), + context.keyManager.connect(context.mainController).execute(payload), ).to.be.revertedWithCustomError( context.keyManager, 'KeyManagerCannotBeSetAsExtensionForLSP20Functions', @@ -237,7 +238,7 @@ export const shouldBehaveLikePermissionChangeOrAddExtensions = ( ]); await expect( - context.keyManager.connect(context.owner).execute(payload), + context.keyManager.connect(context.mainController).execute(payload), ).to.be.revertedWithCustomError( context.keyManager, 'KeyManagerCannotBeSetAsExtensionForLSP20Functions', @@ -268,7 +269,7 @@ export const shouldBehaveLikePermissionChangeOrAddExtensions = ( payloadParam.dataValues, ]); - await context.keyManager.connect(context.owner).execute(payload); + await context.keyManager.connect(context.mainController).execute(payload); const result = await context.universalProfile.getDataBatch(payloadParam.dataKeys); expect(result).to.deep.equal(payloadParam.dataValues); @@ -463,7 +464,7 @@ export const shouldBehaveLikePermissionChangeOrAddExtensions = ( payloadParam.dataValue, ]); - await context.keyManager.connect(context.owner).execute(payload); + await context.keyManager.connect(context.mainController).execute(payload); }); it('should NOT be allowed to ADD another ExtensionHandler key', async () => { const payloadParam = { @@ -528,7 +529,7 @@ export const shouldBehaveLikePermissionChangeOrAddExtensions = ( payloadParam.dataValue, ]); - await context.keyManager.connect(context.owner).execute(payload); + await context.keyManager.connect(context.mainController).execute(payload); }); it('should NOT be allowed to ADD another ExtensionHandler key even when ExtensionHandler key is allowed in AllowedERC725YDataKey', async () => { @@ -594,7 +595,7 @@ export const shouldBehaveLikePermissionChangeOrAddExtensions = ( payloadParam.dataValue, ]); - await context.keyManager.connect(context.owner).execute(payload); + await context.keyManager.connect(context.mainController).execute(payload); }); it('should NOT be allowed to ADD another ExtensionHandler key', async () => { @@ -668,7 +669,7 @@ export const shouldBehaveLikePermissionChangeOrAddExtensions = ( payloadParam.dataValues, ]); - await context.keyManager.connect(context.owner).execute(payload); + await context.keyManager.connect(context.mainController).execute(payload); const result = await context.universalProfile.getDataBatch(payloadParam.dataKeys); @@ -694,7 +695,7 @@ export const shouldBehaveLikePermissionChangeOrAddExtensions = ( payloadParam.dataValues, ]); - await context.keyManager.connect(context.owner).execute(payload); + await context.keyManager.connect(context.mainController).execute(payload); const result = await context.universalProfile.getDataBatch(payloadParam.dataKeys); @@ -716,7 +717,7 @@ export const shouldBehaveLikePermissionChangeOrAddExtensions = ( payloadParam.dataValues, ]); - await context.keyManager.connect(context.owner).execute(payload); + await context.keyManager.connect(context.mainController).execute(payload); const result = await context.universalProfile.getDataBatch(payloadParam.dataKeys); @@ -808,7 +809,7 @@ export const shouldBehaveLikePermissionChangeOrAddExtensions = ( payloadParam.dataValues, ]); - await context.keyManager.connect(context.owner).execute(payload); + await context.keyManager.connect(context.mainController).execute(payload); }); describe('when adding multiple ExtensionHandler keys', () => { it('should pass', async () => { @@ -1105,7 +1106,7 @@ export const shouldBehaveLikePermissionChangeOrAddExtensions = ( payloadParam.dataValues, ]); - await context.keyManager.connect(context.owner).execute(payload); + await context.keyManager.connect(context.mainController).execute(payload); }); describe('When adding ExtensionHandler key and one of his allowedERC725Y Data Key', () => { it('should pass', async () => { diff --git a/tests/LSP6KeyManager/Admin/PermissionChangeAddURD.test.ts b/tests/LSP6KeyManager/Admin/PermissionChangeAddURD.test.ts index d2ebf63da..8474edacf 100644 --- a/tests/LSP6KeyManager/Admin/PermissionChangeAddURD.test.ts +++ b/tests/LSP6KeyManager/Admin/PermissionChangeAddURD.test.ts @@ -78,7 +78,8 @@ export const shouldBehaveLikePermissionChangeOrAddURD = ( canOnlyCall = context.accounts[6]; let permissionKeys = [ - ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + context.owner.address.substring(2), + ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + + context.mainController.address.substring(2), ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + canAddAndChangeUniversalReceiverDelegate.address.substring(2), ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + @@ -128,7 +129,7 @@ export const shouldBehaveLikePermissionChangeOrAddURD = ( permissionArrayValues = [ ethers.utils.hexZeroPad(ethers.utils.hexlify(7), 16), - context.owner.address, + context.mainController.address, canAddAndChangeUniversalReceiverDelegate.address, canOnlyAddUniversalReceiverDelegate.address, canOnlyChangeUniversalReceiverDelegate.address, @@ -156,7 +157,7 @@ export const shouldBehaveLikePermissionChangeOrAddURD = ( payloadParam.dataValue, ]); - await context.keyManager.connect(context.owner).execute(payload); + await context.keyManager.connect(context.mainController).execute(payload); const result = await context.universalProfile.getData(payloadParam.dataKey); expect(result).to.equal(payloadParam.dataValue); @@ -173,7 +174,7 @@ export const shouldBehaveLikePermissionChangeOrAddURD = ( payloadParam.dataValue, ]); - await context.keyManager.connect(context.owner).execute(payload); + await context.keyManager.connect(context.mainController).execute(payload); const result = await context.universalProfile.getData(payloadParam.dataKey); expect(result).to.equal(payloadParam.dataValue); @@ -190,7 +191,7 @@ export const shouldBehaveLikePermissionChangeOrAddURD = ( payloadParam.dataValue, ]); - await context.keyManager.connect(context.owner).execute(payload); + await context.keyManager.connect(context.mainController).execute(payload); const result = await context.universalProfile.getData(payloadParam.dataKey); expect(result).to.equal(payloadParam.dataValue); @@ -411,7 +412,7 @@ export const shouldBehaveLikePermissionChangeOrAddURD = ( payloadParam.dataValue, ]); - await context.keyManager.connect(context.owner).execute(payload); + await context.keyManager.connect(context.mainController).execute(payload); }); it('should NOT be allowed to ADD another UniversalReceiverDelegate key', async () => { const payloadParam = { @@ -476,7 +477,7 @@ export const shouldBehaveLikePermissionChangeOrAddURD = ( payloadParam.dataValue, ]); - await context.keyManager.connect(context.owner).execute(payload); + await context.keyManager.connect(context.mainController).execute(payload); }); it('should NOT be allowed to ADD another UniversalReceiverDelegate key even when UniversalReceiverDelegate key is allowed in AllowedERC725YDataKey', async () => { @@ -542,7 +543,7 @@ export const shouldBehaveLikePermissionChangeOrAddURD = ( payloadParam.dataValue, ]); - await context.keyManager.connect(context.owner).execute(payload); + await context.keyManager.connect(context.mainController).execute(payload); }); it('should NOT be allowed to ADD another UniversalReceiverDelegate key', async () => { @@ -616,7 +617,7 @@ export const shouldBehaveLikePermissionChangeOrAddURD = ( payloadParam.dataValues, ]); - await context.keyManager.connect(context.owner).execute(payload); + await context.keyManager.connect(context.mainController).execute(payload); const result = await context.universalProfile.getDataBatch(payloadParam.dataKeys); @@ -642,7 +643,7 @@ export const shouldBehaveLikePermissionChangeOrAddURD = ( payloadParam.dataValues, ]); - await context.keyManager.connect(context.owner).execute(payload); + await context.keyManager.connect(context.mainController).execute(payload); const result = await context.universalProfile.getDataBatch(payloadParam.dataKeys); @@ -664,7 +665,7 @@ export const shouldBehaveLikePermissionChangeOrAddURD = ( payloadParam.dataValues, ]); - await context.keyManager.connect(context.owner).execute(payload); + await context.keyManager.connect(context.mainController).execute(payload); const result = await context.universalProfile.getDataBatch(payloadParam.dataKeys); @@ -765,7 +766,7 @@ export const shouldBehaveLikePermissionChangeOrAddURD = ( payloadParam.dataValues, ]); - await context.keyManager.connect(context.owner).execute(payload); + await context.keyManager.connect(context.mainController).execute(payload); }); describe('when adding multiple UniversalReceiverDelegate keys', () => { it('should pass', async () => { @@ -1106,7 +1107,7 @@ export const shouldBehaveLikePermissionChangeOrAddURD = ( payloadParam.dataValues, ]); - await context.keyManager.connect(context.owner).execute(payload); + await context.keyManager.connect(context.mainController).execute(payload); }); describe('When adding UniversalReceiverDelegate key and one of his allowedERC725Y Data Key', () => { it('should pass', async () => { diff --git a/tests/LSP6KeyManager/Admin/PermissionChangeOwner.test.ts b/tests/LSP6KeyManager/Admin/PermissionChangeOwner.test.ts index bbd3852fc..4922faf24 100644 --- a/tests/LSP6KeyManager/Admin/PermissionChangeOwner.test.ts +++ b/tests/LSP6KeyManager/Admin/PermissionChangeOwner.test.ts @@ -1,6 +1,6 @@ import { expect } from 'chai'; -import { ethers } from 'hardhat'; -import { BigNumber } from 'ethers'; +import { ethers, network } from 'hardhat'; +import { BigNumber, ContractTransaction } from 'ethers'; import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'; // constants @@ -35,7 +35,7 @@ export const shouldBehaveLikePermissionChangeOwner = ( canChangeOwner = context.accounts[1]; cannotChangeOwner = context.accounts[2]; - newKeyManager = await new LSP6KeyManager__factory(context.owner).deploy( + newKeyManager = await new LSP6KeyManager__factory(context.mainController).deploy( context.universalProfile.address, ); @@ -69,7 +69,10 @@ export const shouldBehaveLikePermissionChangeOwner = ( await expect( context.keyManager.connect(canChangeOwner).execute(transferOwnershipPayload), - ).to.be.revertedWithCustomError(context.universalProfile, 'CannotTransferOwnershipToSelf'); + ).to.be.revertedWithCustomError( + context.universalProfile, + 'LSP14CannotTransferOwnershipToSelf', + ); }); }); @@ -86,11 +89,11 @@ export const shouldBehaveLikePermissionChangeOwner = ( describe('when caller has ALL PERMISSIONS', () => { before('`transferOwnership(...)` to new Key Manager', async () => { - await context.keyManager.connect(context.owner).execute(transferOwnershipPayload); + await context.keyManager.connect(context.mainController).execute(transferOwnershipPayload); }); after('reset ownership', async () => { - await context.keyManager.connect(context.owner).execute(resetOwnershipPayload); + await context.keyManager.connect(context.mainController).execute(resetOwnershipPayload); }); it('should have set newKeyManager as pendingOwner', async () => { @@ -106,7 +109,7 @@ export const shouldBehaveLikePermissionChangeOwner = ( [newKeyManager.address], ); - await context.keyManager.connect(context.owner).execute(transferOwnershipPayload); + await context.keyManager.connect(context.mainController).execute(transferOwnershipPayload); const ownerAfter = await context.universalProfile.owner(); @@ -124,7 +127,7 @@ export const shouldBehaveLikePermissionChangeOwner = ( value, ]); - await context.keyManager.connect(context.owner).execute(payload); + await context.keyManager.connect(context.mainController).execute(payload); const result = await context.universalProfile.getData(key); expect(result).to.equal(value); @@ -142,7 +145,7 @@ export const shouldBehaveLikePermissionChangeOwner = ( ]); await expect( - context.keyManager.connect(context.owner).execute(payload), + context.keyManager.connect(context.mainController).execute(payload), ).to.changeEtherBalances([context.universalProfile, recipient], [`-${amount}`, amount]); }); }); @@ -151,7 +154,7 @@ export const shouldBehaveLikePermissionChangeOwner = ( const overridenPendingOwner = ethers.Wallet.createRandom().address; await context.keyManager - .connect(context.owner) + .connect(context.mainController) .execute( context.universalProfile.interface.encodeFunctionData('transferOwnership', [ overridenPendingOwner, @@ -170,7 +173,7 @@ export const shouldBehaveLikePermissionChangeOwner = ( }); after('reset ownership', async () => { - await context.keyManager.connect(context.owner).execute(resetOwnershipPayload); + await context.keyManager.connect(context.mainController).execute(resetOwnershipPayload); }); it('should have set newKeyManager as pendingOwner', async () => { @@ -195,9 +198,9 @@ export const shouldBehaveLikePermissionChangeOwner = ( }); it('should override the pendingOwner when transferOwnership(...) is called twice', async () => { - const overridenPendingOwner = await new LSP6KeyManager__factory(context.owner).deploy( - context.universalProfile.address, - ); + const overridenPendingOwner = await new LSP6KeyManager__factory( + context.mainController, + ).deploy(context.universalProfile.address); await context.keyManager .connect(canChangeOwner) @@ -215,7 +218,7 @@ export const shouldBehaveLikePermissionChangeOwner = ( describe('when calling acceptOwnership(...) from a KeyManager that is not the pendingOwner', () => { before('`transferOwnership(...)` to new Key Manager', async () => { - await context.keyManager.connect(context.owner).execute(transferOwnershipPayload); + await context.keyManager.connect(context.mainController).execute(transferOwnershipPayload); }); it('should revert', async () => { @@ -225,9 +228,9 @@ export const shouldBehaveLikePermissionChangeOwner = ( const payload = context.universalProfile.interface.getSighash('acceptOwnership'); - await expect(notPendingKeyManager.connect(context.owner).execute(payload)).to.be.revertedWith( - 'LSP14: caller is not the pendingOwner', - ); + await expect(notPendingKeyManager.connect(context.mainController).execute(payload)) + .to.be.revertedWithCustomError(context.keyManager, 'NoPermissionsSet') + .withArgs(notPendingKeyManager.address); }); }); @@ -236,7 +239,7 @@ export const shouldBehaveLikePermissionChangeOwner = ( before('`transferOwnership(...)` to new Key Manager', async () => { await context.universalProfile - .connect(context.owner) + .connect(context.mainController) .transferOwnership(newKeyManager.address); pendingOwner = await context.universalProfile.pendingOwner(); @@ -244,7 +247,7 @@ export const shouldBehaveLikePermissionChangeOwner = ( const acceptOwnershipPayload = context.universalProfile.interface.getSighash('acceptOwnership'); - await newKeyManager.connect(context.owner).execute(acceptOwnershipPayload); + await newKeyManager.connect(context.mainController).execute(acceptOwnershipPayload); }); it("should have change the account's owner to the pendingOwner (= pending KeyManager)", async () => { @@ -276,7 +279,7 @@ export const shouldBehaveLikePermissionChangeOwner = ( value, ]); - await expect(oldKeyManager.connect(context.owner).execute(payload)) + await expect(oldKeyManager.connect(context.mainController).execute(payload)) .to.be.revertedWithCustomError(newKeyManager, 'NoPermissionsSet') .withArgs(oldKeyManager.address); }); @@ -292,7 +295,7 @@ export const shouldBehaveLikePermissionChangeOwner = ( '0x', ]); - await expect(oldKeyManager.connect(context.owner).execute(payload)) + await expect(oldKeyManager.connect(context.mainController).execute(payload)) .to.be.revertedWithCustomError(newKeyManager, 'NoPermissionsSet') .withArgs(oldKeyManager.address); }); @@ -308,7 +311,7 @@ export const shouldBehaveLikePermissionChangeOwner = ( value, ]); - await newKeyManager.connect(context.owner).execute(payload); + await newKeyManager.connect(context.mainController).execute(payload); const result = await context.universalProfile.getData(key); expect(result).to.equal(value); @@ -325,56 +328,149 @@ export const shouldBehaveLikePermissionChangeOwner = ( '0x', ]); - await expect(newKeyManager.connect(context.owner).execute(payload)).to.changeEtherBalances( - [recipient, context.universalProfile], - [amount, `-${amount}`], - ); + await expect( + newKeyManager.connect(context.mainController).execute(payload), + ).to.changeEtherBalances([recipient, context.universalProfile], [amount, `-${amount}`]); }); }); }); describe('when calling `renounceOwnership(...)` via the KeyManager', () => { describe('when caller has ALL PERMISSIONS', () => { - it('should revert via `execute(...)`', async () => { - const payload = context.universalProfile.interface.getSighash('renounceOwnership'); + describe('using `execute(...)`', () => { + let renounceOwnershipFirstTx: ContractTransaction; + let renounceOwnershipSecondTx: ContractTransaction; + + before(async () => { + const payload = context.universalProfile.interface.getSighash('renounceOwnership'); + + // 1st call + renounceOwnershipFirstTx = await newKeyManager + .connect(context.mainController) + .execute(payload); + + // mine 200 blocks + await network.provider.send('hardhat_mine', [ethers.utils.hexValue(200)]); + + // 2nd call + renounceOwnershipSecondTx = await newKeyManager + .connect(context.mainController) + .execute(payload); + }); - await expect(context.keyManager.connect(context.owner).execute(payload)) - .to.be.revertedWithCustomError(context.keyManager, 'InvalidERC725Function') - .withArgs(payload); + it('should emit `RenounceOwnershipStarted` on first call', async () => { + await expect(renounceOwnershipFirstTx).to.emit( + context.universalProfile, + 'RenounceOwnershipStarted', + ); + }); + + it('should emit `OwnershipRenounced` on second call', async () => { + await expect(renounceOwnershipSecondTx).to.emit( + context.universalProfile, + 'OwnershipRenounced', + ); + }); + + it('should clear the `pendingOwner` and set it to `AddressZero`', async () => { + expect(await context.universalProfile.pendingOwner()).to.equal( + ethers.constants.AddressZero, + ); + }); + + it('should update the owner to `AddressZero`', async () => { + expect(await context.universalProfile.owner()).to.equal(ethers.constants.AddressZero); + }); }); - it('should revert via `executeRelayCall()`', async () => { - const HARDHAT_CHAINID = 31337; - const valueToSend = 0; + describe('using `executeRelayCall()`', () => { + let renounceOwnershipFirstTx: ContractTransaction; + let renounceOwnershipSecondTx: ContractTransaction; + + before(async () => { + // Build new context as `renounceOwnership()` was used in the previous context + // ------ Build new context ------ + context = await buildContext(); + await setupKeyManager(context, [], []); + // ------------------------------- + + // ------ General variables for relay call ------ + const payload = context.universalProfile.interface.getSighash('renounceOwnership'); + const eip191Signer = new EIP191Signer(); + const HARDHAT_CHAINID = 31337; + const validityTimestamps = 0; + const valueToSend = 0; + // ---------------------------------------------- + + // ------ 1st call ------ + const firstNonce = await context.keyManager.getNonce(context.mainController.address, 0); + + const firstEncodedMessage = ethers.utils.solidityPack( + ['uint256', 'uint256', 'uint256', 'uint256', 'uint256', 'bytes'], + [LSP25_VERSION, HARDHAT_CHAINID, firstNonce, validityTimestamps, valueToSend, payload], + ); - const nonce = await context.keyManager.getNonce(context.owner.address, 0); + const firstSignature = await eip191Signer.signDataWithIntendedValidator( + context.keyManager.address, + firstEncodedMessage, + LOCAL_PRIVATE_KEYS.ACCOUNT0, + ).signature; - const validityTimestamps = 0; + renounceOwnershipFirstTx = await context.keyManager + .connect(context.mainController) + .executeRelayCall(firstSignature, firstNonce, validityTimestamps, payload, { + value: valueToSend, + }); + // ---------------------- - const payload = context.universalProfile.interface.getSighash('renounceOwnership'); + // mine 200 blocks + await network.provider.send('hardhat_mine', [ethers.utils.hexValue(200)]); - const encodedMessage = ethers.utils.solidityPack( - ['uint256', 'uint256', 'uint256', 'uint256', 'uint256', 'bytes'], - [LSP25_VERSION, HARDHAT_CHAINID, nonce, validityTimestamps, valueToSend, payload], - ); + // ------ 2nd call ------ + const secondNonce = await context.keyManager.getNonce(context.mainController.address, 0); - const eip191Signer = new EIP191Signer(); + const secondEncodedMessage = ethers.utils.solidityPack( + ['uint256', 'uint256', 'uint256', 'uint256', 'uint256', 'bytes'], + [LSP25_VERSION, HARDHAT_CHAINID, secondNonce, validityTimestamps, valueToSend, payload], + ); - const { signature } = await eip191Signer.signDataWithIntendedValidator( - context.keyManager.address, - encodedMessage, - LOCAL_PRIVATE_KEYS.ACCOUNT0, - ); + const secondSignature = await eip191Signer.signDataWithIntendedValidator( + context.keyManager.address, + secondEncodedMessage, + LOCAL_PRIVATE_KEYS.ACCOUNT0, + ).signature; - await expect( - context.keyManager - .connect(context.owner) - .executeRelayCall(signature, nonce, validityTimestamps, payload, { + renounceOwnershipSecondTx = await context.keyManager + .connect(context.mainController) + .executeRelayCall(secondSignature, secondNonce, validityTimestamps, payload, { value: valueToSend, - }), - ) - .to.be.revertedWithCustomError(context.keyManager, 'InvalidERC725Function') - .withArgs(payload); + }); + // ---------------------- + }); + + it('should emit `RenounceOwnershipStarted` on first call', async () => { + await expect(renounceOwnershipFirstTx).to.emit( + context.universalProfile, + 'RenounceOwnershipStarted', + ); + }); + + it('should emit `OwnershipRenounced` on second call', async () => { + await expect(renounceOwnershipSecondTx).to.emit( + context.universalProfile, + 'OwnershipRenounced', + ); + }); + + it('should clear the `pendingOwner` and set it to `AddressZero`', async () => { + expect(await context.universalProfile.pendingOwner()).to.equal( + ethers.constants.AddressZero, + ); + }); + + it('should update the owner to `AddressZero`', async () => { + expect(await context.universalProfile.owner()).to.equal(ethers.constants.AddressZero); + }); }); }); }); diff --git a/tests/LSP6KeyManager/Admin/PermissionSign.test.ts b/tests/LSP6KeyManager/Admin/PermissionSign.test.ts index 202197a16..c3e517d78 100644 --- a/tests/LSP6KeyManager/Admin/PermissionSign.test.ts +++ b/tests/LSP6KeyManager/Admin/PermissionSign.test.ts @@ -26,7 +26,8 @@ export const shouldBehaveLikePermissionSign = (buildContext: () => Promise Promise { @@ -59,7 +60,7 @@ export const shouldBehaveLikePermissionSign = (buildContext: () => Promise Promise { @@ -86,7 +87,7 @@ export const shouldBehaveLikePermissionSign = (buildContext: () => Promise Promise Promise Promise Promise) => { let context: LSP6TestContext; @@ -55,8 +56,8 @@ export const shouldBehaveLikeAllowedFunctions = (buildContext: () => Promise Promise Promise Promise Promise Promise Promise Promise { it('ERC1271', async () => { const sampleHash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes('Sample Message')); - const sampleSignature = await context.owner.signMessage('Sample Message'); + const sampleSignature = await context.mainController.signMessage('Sample Message'); const payload = signatureValidatorContract.interface.encodeFunctionData( 'isValidSignature', @@ -136,9 +137,11 @@ export const shouldBehaveLikeAllowedStandards = (buildContext: () => Promise { @@ -150,7 +153,7 @@ export const shouldBehaveLikeAllowedStandards = (buildContext: () => Promise Promise { - it('should revert with Key Manager error `CannotSendValueToSetData` when sending value while setting data', async () => { - const amountToFund = ethers.utils.parseEther('2'); + it('should pass when sending value while setting data', async () => { + const msgValues = [ethers.utils.parseEther('2'), ethers.utils.parseEther('2')]; + const totalMsgValue = msgValues.reduce((accumulator, currentValue) => + accumulator.add(currentValue), + ); const dataKeys = [ ethers.utils.keccak256(ethers.utils.toUtf8Bytes('key1')), @@ -434,10 +442,6 @@ export const shouldBehaveLikeBatchExecute = ( ]; const dataValues = ['0xaaaaaaaa', '0xbbbbbbbb']; - const keyManagerBalanceBefore = await ethers.provider.getBalance( - context.keyManager.address, - ); - const firstSetDataPayload = context.universalProfile.interface.encodeFunctionData( 'setData', [dataKeys[0], dataValues[0]], @@ -448,25 +452,15 @@ export const shouldBehaveLikeBatchExecute = ( [dataKeys[1], dataValues[1]], ); - // this error occurs when calling `setData(...)` with msg.value, - // since these functions on ERC725Y are not payable await expect( context.keyManager - .connect(context.owner) - .executeBatch([1, 1], [firstSetDataPayload, secondSetDataPayload], { - value: amountToFund, + .connect(context.mainController) + .executeBatch(msgValues, [firstSetDataPayload, secondSetDataPayload], { + value: totalMsgValue, }), - ).to.be.revertedWithCustomError(context.keyManager, 'CannotSendValueToSetData'); + ).to.changeEtherBalances([context.universalProfile.address], [totalMsgValue]); - const keyManagerBalanceAfter = await ethers.provider.getBalance( - context.keyManager.address, - ); - - expect(keyManagerBalanceAfter).to.equal(keyManagerBalanceBefore); - - // the Key Manager must not hold any funds and must always forward any funds sent to it. - // it's balance must always be 0 after any execution - expect(await provider.getBalance(context.keyManager.address)).to.equal(0); + expect(await context.universalProfile.getDataBatch(dataKeys)).to.deep.equal(dataValues); }); }); }); @@ -496,13 +490,11 @@ export const shouldBehaveLikeBatchExecute = ( ); await expect( - context.keyManager.connect(context.owner).executeBatch(msgValues, payloads, { + context.keyManager.connect(context.mainController).executeBatch(msgValues, payloads, { value: totalValues, }), - ).to.changeEtherBalances( - [context.universalProfile.address, recipient], - [0, msgValues[1]], - ); + ).to.changeEtherBalances([context.universalProfile.address, recipient], msgValues); + expect(await context.universalProfile.getData(dataKey)).to.equal(dataValue); }); }); @@ -530,11 +522,19 @@ export const shouldBehaveLikeBatchExecute = ( accumulator.add(currentValue), ); + await context.keyManager + .connect(context.mainController) + .executeBatch(msgValues, payloads, { + value: totalValues, + }); + await expect( - context.keyManager.connect(context.owner).executeBatch(msgValues, payloads, { + context.keyManager.connect(context.mainController).executeBatch(msgValues, payloads, { value: totalValues, }), - ).to.be.revertedWithCustomError(context.keyManager, 'CannotSendValueToSetData'); + ).to.changeEtherBalances([context.universalProfile.address, recipient], msgValues); + + expect(await context.universalProfile.getData(dataKey)).to.equal(dataValue); }); }); }); @@ -587,7 +587,7 @@ export const shouldBehaveLikeBatchExecute = ( ]; await expect( - context.keyManager.connect(context.owner).executeBatch(values, payloads, { + context.keyManager.connect(context.mainController).executeBatch(values, payloads, { value: msgValue, }), ) @@ -643,7 +643,7 @@ export const shouldBehaveLikeBatchExecute = ( ]; await expect( - context.keyManager.connect(context.owner).executeBatch(values, payloads, { + context.keyManager.connect(context.mainController).executeBatch(values, payloads, { value: msgValue, }), ) @@ -696,7 +696,7 @@ export const shouldBehaveLikeBatchExecute = ( ]; const tx = await context.keyManager - .connect(context.owner) + .connect(context.mainController) .executeBatch(values, payloads, { value: totalValues, }); @@ -739,7 +739,7 @@ export const shouldBehaveLikeBatchExecute = ( await expect( context.keyManager - .connect(context.owner) + .connect(context.mainController) .executeBatch( [0, 0, 0], [failingTransferPayload, firstTransferPayload, secondTransferPayload], @@ -775,7 +775,7 @@ export const shouldBehaveLikeBatchExecute = ( await expect( context.keyManager - .connect(context.owner) + .connect(context.mainController) .executeBatch( [0, 0, 0], [firstTransferPayload, secondTransferPayload, failingTransferPayload], diff --git a/tests/LSP6KeyManager/Interactions/InvalidExecutePayloads.test.ts b/tests/LSP6KeyManager/Interactions/InvalidExecutePayloads.test.ts index c387e279b..a3ea3a31a 100644 --- a/tests/LSP6KeyManager/Interactions/InvalidExecutePayloads.test.ts +++ b/tests/LSP6KeyManager/Interactions/InvalidExecutePayloads.test.ts @@ -24,7 +24,8 @@ export const testInvalidExecutePayloads = (buildContext: () => Promise Promise { const lsp20VerifyCallPayload = context.keyManager.interface.encodeFunctionData( 'lsp20VerifyCall', - [context.accounts[2].address, 0, '0xaabbccdd'], // random arguments + [ + context.accounts[2].address, + context.keyManager.address, + context.accounts[2].address, + 0, + '0xaabbccdd', + ], // random arguments ); const executePayload = context.universalProfile.interface.encodeFunctionData('execute', [ @@ -1034,7 +1042,7 @@ export const shouldBehaveLikePermissionCall = ( ]); await expect( - context.keyManager.connect(context.owner).execute(executePayload), + context.keyManager.connect(context.mainController).execute(executePayload), ).to.be.revertedWithCustomError(context.keyManager, 'CallingKeyManagerNotAllowed'); }); @@ -1102,12 +1110,16 @@ export const shouldBehaveLikePermissionCall = ( const permissionValues = [ // permissions - PERMISSIONS.TRANSFERVALUE, - combinePermissions(PERMISSIONS.TRANSFERVALUE, PERMISSIONS.CALL), - PERMISSIONS.CALL, - PERMISSIONS.SIGN, - PERMISSIONS.SUPER_CALL, - PERMISSIONS.SUPER_TRANSFERVALUE, + combinePermissions(PERMISSIONS.TRANSFERVALUE, PERMISSIONS.EXECUTE_RELAY_CALL), + combinePermissions( + PERMISSIONS.TRANSFERVALUE, + PERMISSIONS.CALL, + PERMISSIONS.EXECUTE_RELAY_CALL, + ), + combinePermissions(PERMISSIONS.EXECUTE_RELAY_CALL, PERMISSIONS.CALL), + combinePermissions(PERMISSIONS.SIGN, PERMISSIONS.EXECUTE_RELAY_CALL), + combinePermissions(PERMISSIONS.SUPER_CALL, PERMISSIONS.EXECUTE_RELAY_CALL), + combinePermissions(PERMISSIONS.SUPER_TRANSFERVALUE, PERMISSIONS.EXECUTE_RELAY_CALL), // allowed calls, allowedCall, allowedCall, @@ -1267,9 +1279,9 @@ export const shouldBehaveLikePermissionCall = ( const permissionValues = [ // permissions - PERMISSIONS.CALL, - PERMISSIONS.SUPER_CALL, - PERMISSIONS.SIGN, + combinePermissions(PERMISSIONS.CALL, PERMISSIONS.EXECUTE_RELAY_CALL), + combinePermissions(PERMISSIONS.SUPER_CALL, PERMISSIONS.EXECUTE_RELAY_CALL), + combinePermissions(PERMISSIONS.SIGN, PERMISSIONS.EXECUTE_RELAY_CALL), // allowed calls, allowedCall, ]; diff --git a/tests/LSP6KeyManager/Interactions/PermissionDelegateCall.test.ts b/tests/LSP6KeyManager/Interactions/PermissionDelegateCall.test.ts index e2369d6e8..0230085a2 100644 --- a/tests/LSP6KeyManager/Interactions/PermissionDelegateCall.test.ts +++ b/tests/LSP6KeyManager/Interactions/PermissionDelegateCall.test.ts @@ -35,12 +35,13 @@ export const shouldBehaveLikePermissionDelegateCall = ( addressCanDelegateCall = context.accounts[1]; addressCannotDelegateCall = context.accounts[2]; - erc725YDelegateCallContract = await new ERC725YDelegateCall__factory(context.owner).deploy( - context.universalProfile.address, - ); + erc725YDelegateCallContract = await new ERC725YDelegateCall__factory( + context.mainController, + ).deploy(context.universalProfile.address); const permissionKeys = [ - ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + context.owner.address.substring(2), + ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + + context.mainController.address.substring(2), ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + addressCanDelegateCall.address.substring(2), ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + @@ -76,7 +77,7 @@ export const shouldBehaveLikePermissionDelegateCall = ( ]); await expect( - context.keyManager.connect(context.owner).execute(executePayload), + context.keyManager.connect(context.mainController).execute(executePayload), ).to.be.revertedWithCustomError(context.keyManager, 'DelegateCallDisallowedViaKeyManager'); }); diff --git a/tests/LSP6KeyManager/Interactions/PermissionDeploy.test.ts b/tests/LSP6KeyManager/Interactions/PermissionDeploy.test.ts index b83f8f804..86d8e4118 100644 --- a/tests/LSP6KeyManager/Interactions/PermissionDeploy.test.ts +++ b/tests/LSP6KeyManager/Interactions/PermissionDeploy.test.ts @@ -41,7 +41,8 @@ export const shouldBehaveLikePermissionDeploy = ( addressCannotDeploy = context.accounts[4]; const permissionKeys = [ - ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + context.owner.address.substring(2), + ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + + context.mainController.address.substring(2), ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + addressCanDeploy.address.substring(2), ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + @@ -54,10 +55,18 @@ export const shouldBehaveLikePermissionDeploy = ( const permissionsValues = [ ALL_PERMISSIONS, - PERMISSIONS.DEPLOY, - combinePermissions(PERMISSIONS.DEPLOY, PERMISSIONS.TRANSFERVALUE), - combinePermissions(PERMISSIONS.DEPLOY, PERMISSIONS.SUPER_TRANSFERVALUE), - PERMISSIONS.CALL, + combinePermissions(PERMISSIONS.DEPLOY, PERMISSIONS.EXECUTE_RELAY_CALL), + combinePermissions( + PERMISSIONS.DEPLOY, + PERMISSIONS.TRANSFERVALUE, + PERMISSIONS.EXECUTE_RELAY_CALL, + ), + combinePermissions( + PERMISSIONS.DEPLOY, + PERMISSIONS.SUPER_TRANSFERVALUE, + PERMISSIONS.EXECUTE_RELAY_CALL, + ), + combinePermissions(PERMISSIONS.CALL, PERMISSIONS.EXECUTE_RELAY_CALL), ]; await setupKeyManager(context, permissionKeys, permissionsValues); @@ -77,10 +86,10 @@ export const shouldBehaveLikePermissionDeploy = ( // do first a callstatic to retrieve the address of the contract expected to be deployed // so we can check it against the address emitted in the ContractCreated event const expectedContractAddress = await context.keyManager - .connect(context.owner) + .connect(context.mainController) .callStatic.execute(payload); - await expect(context.keyManager.connect(context.owner).execute(payload)) + await expect(context.keyManager.connect(context.mainController).execute(payload)) .to.emit(context.universalProfile, 'ContractCreated') .withArgs( OPERATION_TYPES.CREATE, @@ -92,7 +101,7 @@ export const shouldBehaveLikePermissionDeploy = ( it('should be allowed to deploy + fund a contract with CREATE', async () => { // deploy a UP from another UP and check that the new UP is funded + its owner was set - const initialUpOwner = context.owner.address; + const initialUpOwner = context.mainController.address; // generate the init code that contains the constructor args with the initial UP owner const upDeploymentTx = new UniversalProfile__factory( @@ -112,10 +121,10 @@ export const shouldBehaveLikePermissionDeploy = ( // do first a callstatic to retrieve the address of the contract expected to be deployed // so we can check it against the address emitted in the ContractCreated event const expectedContractAddress = await context.keyManager - .connect(context.owner) + .connect(context.mainController) .callStatic.execute(payload); - await expect(context.keyManager.connect(context.owner).execute(payload)) + await expect(context.keyManager.connect(context.mainController).execute(payload)) .to.emit(context.universalProfile, 'ContractCreated') .withArgs( OPERATION_TYPES.CREATE, @@ -151,14 +160,14 @@ export const shouldBehaveLikePermissionDeploy = ( contractBytecodeToDeploy, ).toLowerCase(); - await expect(context.keyManager.connect(context.owner).execute(payload)) + await expect(context.keyManager.connect(context.mainController).execute(payload)) .to.emit(context.universalProfile, 'ContractCreated') .withArgs(OPERATION_TYPES.CREATE2, ethers.utils.getAddress(preComputedAddress), 0, salt); }); it('should be allowed to deploy + fund a contract with CREATE2', async () => { // deploy a UP from another UP and check that the new UP is funded + its owner was set - const initialUpOwner = context.owner.address; + const initialUpOwner = context.mainController.address; // generate the init code that contains the constructor args with the initial UP owner const upDeploymentTx = new UniversalProfile__factory( @@ -183,7 +192,7 @@ export const shouldBehaveLikePermissionDeploy = ( contractBytecodeToDeploy, ).toLowerCase(); - await expect(context.keyManager.connect(context.owner).execute(payload)) + await expect(context.keyManager.connect(context.mainController).execute(payload)) .to.emit(context.universalProfile, 'ContractCreated') .withArgs( OPERATION_TYPES.CREATE2, @@ -229,7 +238,7 @@ export const shouldBehaveLikePermissionDeploy = ( it('should revert with error `NotAuthorised(SUPER_TRANSFERVALUE)` when trying to deploy + fund a contract with CREATE', async () => { // deploy a UP from another UP and check that the new UP is funded + its owner was set - const initialUpOwner = context.owner.address; + const initialUpOwner = context.mainController.address; // generate the init code that contains the constructor args with the initial UP owner const upDeploymentTx = new UniversalProfile__factory( @@ -275,7 +284,7 @@ export const shouldBehaveLikePermissionDeploy = ( it('should revert with error `NotAuthorised(SUPER_TRANSFERVALUE)` when trying to deploy + fund a contract with CREATE2', async () => { // deploy a UP from another UP and check that the new UP is funded + its owner was set - const initialUpOwner = context.owner.address; + const initialUpOwner = context.mainController.address; // generate the init code that contains the constructor args with the initial UP owner const upDeploymentTx = new UniversalProfile__factory( @@ -329,7 +338,7 @@ export const shouldBehaveLikePermissionDeploy = ( it('should revert with error `NotAuthorised(SUPER_TRANSFERVALUE)` when trying to deploy + fund a contract with CREATE', async () => { // deploy a UP from another UP and check that the new UP is funded + its owner was set - const initialUpOwner = context.owner.address; + const initialUpOwner = context.mainController.address; // generate the init code that contains the constructor args with the initial UP owner const upDeploymentTx = new UniversalProfile__factory( @@ -375,7 +384,7 @@ export const shouldBehaveLikePermissionDeploy = ( it('should revert with error `NotAuthorised(SUPER_TRANSFERVALUE)` when trying to deploy + fund a contract with CREATE2', async () => { // deploy a UP from another UP and check that the new UP is funded + its owner was set - const initialUpOwner = context.owner.address; + const initialUpOwner = context.mainController.address; // generate the init code that contains the constructor args with the initial UP owner const upDeploymentTx = new UniversalProfile__factory( @@ -431,7 +440,7 @@ export const shouldBehaveLikePermissionDeploy = ( it('should be allowed to deploy + fund a contract with CREATE', async () => { // deploy a UP from another UP and check that the new UP is funded + its owner was set - const initialUpOwner = context.owner.address; + const initialUpOwner = context.mainController.address; // generate the init code that contains the constructor args with the initial UP owner const upDeploymentTx = new UniversalProfile__factory( @@ -501,7 +510,7 @@ export const shouldBehaveLikePermissionDeploy = ( it('should be allowed to deploy + fund a contract with CREATE2', async () => { // deploy a UP from another UP and check that the new UP is funded + its owner was set - const initialUpOwner = context.owner.address; + const initialUpOwner = context.mainController.address; // generate the init code that contains the constructor args with the initial UP owner const upDeploymentTx = new UniversalProfile__factory( diff --git a/tests/LSP6KeyManager/Interactions/PermissionStaticCall.test.ts b/tests/LSP6KeyManager/Interactions/PermissionStaticCall.test.ts index eab6894a9..4c5e1ff8e 100644 --- a/tests/LSP6KeyManager/Interactions/PermissionStaticCall.test.ts +++ b/tests/LSP6KeyManager/Interactions/PermissionStaticCall.test.ts @@ -62,7 +62,8 @@ export const shouldBehaveLikePermissionStaticCall = ( ).deploy(); const permissionKeys = [ - ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + context.owner.address.substring(2), + ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + + context.mainController.address.substring(2), ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + addressCanMakeStaticCall.address.substring(2), ERC725YDataKeys.LSP6['AddressPermissions:AllowedCalls'] + @@ -107,7 +108,7 @@ export const shouldBehaveLikePermissionStaticCall = ( ]); const result = await context.keyManager - .connect(context.owner) + .connect(context.mainController) .callStatic.execute(executePayload); const [decodedResult] = abiCoder.decode(['string'], result); @@ -162,7 +163,7 @@ export const shouldBehaveLikePermissionStaticCall = ( describe('when calling `isValidSignature(bytes32,bytes)` on a contract', () => { it('should pass and return data when `value` param is 0', async () => { const message = 'some message to sign'; - const signature = await context.owner.signMessage(message); + const signature = await context.mainController.signMessage(message); const messageHash = ethers.utils.hashMessage(message); const erc1271ContractPayload = signatureValidator.interface.encodeFunctionData( @@ -182,14 +183,14 @@ export const shouldBehaveLikePermissionStaticCall = ( .callStatic.execute(executePayload); const [decodedResult] = abiCoder.decode(['bytes4'], result); - expect(decodedResult).to.equal(ERC1271_VALUES.MAGIC_VALUE); + expect(decodedResult).to.equal(ERC1271_VALUES.SUCCESS_VALUE); }); it('should revert with error `ERC725X_MsgValueDisallowedInStaticCall` if `value` param is not 0', async () => { const lyxAmount = ethers.utils.parseEther('3'); const message = 'some message to sign'; - const signature = await context.owner.signMessage(message); + const signature = await context.mainController.signMessage(message); const messageHash = ethers.utils.hashMessage(message); const erc1271ContractPayload = signatureValidator.interface.encodeFunctionData( @@ -219,8 +220,8 @@ export const shouldBehaveLikePermissionStaticCall = ( const onERC721Payload = onERC721ReceivedContract.interface.encodeFunctionData( 'onERC721Received', [ - context.owner.address, - context.owner.address, + context.mainController.address, + context.mainController.address, 1, ethers.utils.toUtf8Bytes('some data'), ], @@ -251,7 +252,12 @@ export const shouldBehaveLikePermissionStaticCall = ( // the params are not relevant for this test and just used as placeholders. const onERC721Payload = onERC721ReceivedContract.interface.encodeFunctionData( 'onERC721Received', - [context.owner.address, context.owner.address, 1, ethers.utils.toUtf8Bytes('some data')], + [ + context.mainController.address, + context.mainController.address, + 1, + ethers.utils.toUtf8Bytes('some data'), + ], ); const executePayload = context.universalProfile.interface.encodeFunctionData('execute', [ diff --git a/tests/LSP6KeyManager/Interactions/PermissionTransferValue.test.ts b/tests/LSP6KeyManager/Interactions/PermissionTransferValue.test.ts index 2b8db6f30..c59d65538 100644 --- a/tests/LSP6KeyManager/Interactions/PermissionTransferValue.test.ts +++ b/tests/LSP6KeyManager/Interactions/PermissionTransferValue.test.ts @@ -91,7 +91,8 @@ export const shouldBehaveLikePermissionTransferValue = ( ).to.equal(graffitiExtension.address); const permissionsKeys = [ - ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + context.owner.address.substring(2), + ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + + context.mainController.address.substring(2), ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + canTransferValue.address.substring(2), ERC725YDataKeys.LSP6['AddressPermissions:AllowedCalls'] + @@ -149,7 +150,7 @@ export const shouldBehaveLikePermissionTransferValue = ( * @see https://hardhat.org/hardhat-chai-matchers/docs/reference#.changeetherbalances */ await expect( - context.keyManager.connect(context.owner).execute(transferPayload), + context.keyManager.connect(context.mainController).execute(transferPayload), ).to.changeEtherBalances( [context.universalProfile.address, recipient.address], [`-${amount}`, amount], @@ -231,7 +232,7 @@ export const shouldBehaveLikePermissionTransferValue = ( data, ]); - await context.keyManager.connect(context.owner).execute(transferPayload); + await context.keyManager.connect(context.mainController).execute(transferPayload); const newBalanceUP = await provider.getBalance(context.universalProfile.address); expect(newBalanceUP).to.be.lt(initialBalanceUP); @@ -312,10 +313,12 @@ export const shouldBehaveLikePermissionTransferValue = ( const initialBalanceUP = await provider.getBalance(context.universalProfile.address); const initialBalanceRecipient = await provider.getBalance(recipient.address); - const transferPayload = universalProfileInterface.encodeFunctionData( - 'execute(uint256,address,uint256,bytes)', - [OPERATION_TYPES.CALL, recipient.address, ethers.utils.parseEther('3'), data], - ); + const transferPayload = universalProfileInterface.encodeFunctionData('execute', [ + OPERATION_TYPES.CALL, + recipient.address, + ethers.utils.parseEther('3'), + data, + ]); await expect( context.keyManager.connect(canTransferValue)['execute(bytes)'](transferPayload), @@ -334,10 +337,12 @@ export const shouldBehaveLikePermissionTransferValue = ( it('should pass when caller has permission TRANSFERVALUE + CALL', async () => { const amount = ethers.utils.parseEther('3'); - const transferPayload = universalProfileInterface.encodeFunctionData( - 'execute(uint256,address,uint256,bytes)', - [OPERATION_TYPES.CALL, recipient.address, amount, data], - ); + const transferPayload = universalProfileInterface.encodeFunctionData('execute', [ + OPERATION_TYPES.CALL, + recipient.address, + amount, + data, + ]); await expect(() => context.keyManager @@ -380,7 +385,7 @@ export const shouldBehaveLikePermissionTransferValue = ( ); // ethereum signed message prefix - const signature = await context.owner.signMessage(encodedMessage); + const signature = await context.mainController.signMessage(encodedMessage); await expect( context.keyManager.executeRelayCall( @@ -430,7 +435,7 @@ export const shouldBehaveLikePermissionTransferValue = ( await expect( context.keyManager - .connect(context.owner) + .connect(context.mainController) .executeRelayCall(signature, 0, validityTimestamps, executeRelayCallPayload, { value: valueToSend, }), @@ -450,10 +455,12 @@ export const shouldBehaveLikePermissionTransferValue = ( const initialBalanceUP = await provider.getBalance(context.universalProfile.address); const initialBalanceRecipient = await provider.getBalance(recipientUP.address); - const transferPayload = universalProfileInterface.encodeFunctionData( - 'execute(uint256,address,uint256,bytes)', - [OPERATION_TYPES.CALL, recipientUP.address, ethers.utils.parseEther('3'), data], - ); + const transferPayload = universalProfileInterface.encodeFunctionData('execute', [ + OPERATION_TYPES.CALL, + recipientUP.address, + ethers.utils.parseEther('3'), + data, + ]); await expect( context.keyManager.connect(canTransferValue)['execute(bytes)'](transferPayload), @@ -519,7 +526,8 @@ export const shouldBehaveLikePermissionTransferValue = ( ); const permissionKeys = [ - ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + context.owner.address.substring(2), + ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + + context.mainController.address.substring(2), ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + contractCanTransferValue.address.substring(2), ERC725YDataKeys.LSP6['AddressPermissions:AllowedCalls'] + diff --git a/tests/LSP6KeyManager/LSP6ControlledToken.test.ts b/tests/LSP6KeyManager/LSP6ControlledToken.test.ts index 77f0c7626..19b98fa31 100644 --- a/tests/LSP6KeyManager/LSP6ControlledToken.test.ts +++ b/tests/LSP6KeyManager/LSP6ControlledToken.test.ts @@ -20,7 +20,7 @@ export type LSP6ControlledToken = { accounts: SignerWithAddress[]; token: LSP7Mintable | LSP8Mintable; keyManager: LSP6KeyManager; - owner: SignerWithAddress; + mainController: SignerWithAddress; }; const buildContext = async () => { @@ -50,7 +50,7 @@ const buildContext = async () => { accounts, token: lsp7, keyManager, - owner: accounts[0], + mainController: accounts[0], }; }; @@ -71,7 +71,7 @@ const addControllerWithPermission = async ( const payload = context.token.interface.encodeFunctionData('setDataBatch', [keys, values]); - await context.keyManager.connect(context.owner).execute(payload); + await context.keyManager.connect(context.mainController).execute(payload); }; describe('When deploying LSP7 with LSP6 as owner', () => { @@ -107,10 +107,11 @@ describe('When deploying LSP7 with LSP6 as owner', () => { const keys = [ ERC725YDataKeys.LSP6['AddressPermissions[]'].length, ERC725YDataKeys.LSP6['AddressPermissions[]'].index + '0'.repeat(31) + '0', - ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + context.owner.address.substring(2), + ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + + context.mainController.address.substring(2), ]; - const values = [ARRAY_LENGTH.ONE, context.owner.address, ALL_PERMISSIONS]; + const values = [ARRAY_LENGTH.ONE, context.mainController.address, ALL_PERMISSIONS]; expect(await context.token.getDataBatch(keys)).to.deep.equal(values); }); @@ -119,37 +120,41 @@ describe('When deploying LSP7 with LSP6 as owner', () => { it('should revert because function does not exist on LSP6', async () => { const LSP7 = context.token as LSP7Mintable; const mintPayload = LSP7.interface.encodeFunctionData('mint', [ - context.owner.address, + context.mainController.address, 1, true, '0x', ]); - await expect(context.keyManager.connect(context.owner).execute(mintPayload)) + await expect(context.keyManager.connect(context.mainController).execute(mintPayload)) .to.be.revertedWithCustomError(context.keyManager, 'InvalidERC725Function') .withArgs(mintPayload.substring(0, 10)); }); }); describe('using renounceOwnership(..) in LSP7 through LSP6', () => { - it('should revert', async () => { + it('should pass', async () => { const renounceOwnershipPayload = context.token.interface.encodeFunctionData('renounceOwnership'); - await expect(context.keyManager.connect(context.owner).execute(renounceOwnershipPayload)) - .to.be.revertedWithCustomError(context.keyManager, 'InvalidERC725Function') - .withArgs(renounceOwnershipPayload); + await context.keyManager.connect(context.mainController).execute(renounceOwnershipPayload); + + expect(await context.token.owner()).to.equal(ethers.constants.AddressZero); }); }); describe('using transferOwnership(..) in LSP7 through LSP6', () => { + before("previous token contract's ownership was renounced, build new context", async () => { + context = await buildContext(); + }); + it('should change the owner of LSP7 contract', async () => { const LSP7 = context.token as LSP7Mintable; const transferOwnershipPayload = LSP7.interface.encodeFunctionData('transferOwnership', [ newOwner.address, ]); - await context.keyManager.connect(context.owner).execute(transferOwnershipPayload); + await context.keyManager.connect(context.mainController).execute(transferOwnershipPayload); expect(await context.token.owner()).to.equal(newOwner.address); }); @@ -159,9 +164,9 @@ describe('When deploying LSP7 with LSP6 as owner', () => { const value = ethers.utils.keccak256(ethers.utils.toUtf8Bytes('SecondRandomString')); const payload = context.token.interface.encodeFunctionData('setData', [key, value]); - await expect(context.keyManager.connect(context.owner).execute(payload)).to.be.revertedWith( - 'Ownable: caller is not the owner', - ); + await expect( + context.keyManager.connect(context.mainController).execute(payload), + ).to.be.revertedWithCustomError(context.token, 'OwnableCallerNotTheOwner'); }); it('should allow the new owner to call setData(..)', async () => { @@ -173,16 +178,16 @@ describe('When deploying LSP7 with LSP6 as owner', () => { expect(await context.token.getData(key)).to.equal(value); }); - it("`mint(..)` -> should revert with 'caller is not the owner' error.", async () => { + it("`mint(..)` -> should revert with 'InvalidERC725Function' error.", async () => { const LSP7 = context.token as LSP7Mintable; const mintPayload = LSP7.interface.encodeFunctionData('mint', [ - context.owner.address, + context.mainController.address, 1, true, '0x', ]); - await expect(context.keyManager.connect(context.owner).execute(mintPayload)) + await expect(context.keyManager.connect(context.mainController).execute(mintPayload)) .to.be.revertedWithCustomError(context.keyManager, 'InvalidERC725Function') .withArgs(mintPayload.substring(0, 10)); }); @@ -190,9 +195,9 @@ describe('When deploying LSP7 with LSP6 as owner', () => { it('should allow the new owner to call mint(..)', async () => { const LSP7 = context.token as LSP7Mintable; - await LSP7.connect(newOwner).mint(context.owner.address, 1, true, '0x'); + await LSP7.connect(newOwner).mint(context.mainController.address, 1, true, '0x'); - expect(await LSP7.balanceOf(context.owner.address)).to.equal(1); + expect(await LSP7.balanceOf(context.mainController.address)).to.equal(1); }); it("`transferOwnership(..)` -> should revert with 'caller is not the owner' error.", async () => { @@ -202,8 +207,8 @@ describe('When deploying LSP7 with LSP6 as owner', () => { ); await expect( - context.keyManager.connect(context.owner).execute(transferOwnershipPayload), - ).to.be.revertedWith('Ownable: caller is not the owner'); + context.keyManager.connect(context.mainController).execute(transferOwnershipPayload), + ).to.be.revertedWithCustomError(context.token, 'OwnableCallerNotTheOwner'); }); it('should allow the new owner to call transferOwnership(..)', async () => { @@ -216,9 +221,9 @@ describe('When deploying LSP7 with LSP6 as owner', () => { const renounceOwnershipPayload = context.token.interface.encodeFunctionData('renounceOwnership'); - await expect(context.keyManager.connect(context.owner).execute(renounceOwnershipPayload)) - .to.be.revertedWithCustomError(context.keyManager, 'InvalidERC725Function') - .withArgs(renounceOwnershipPayload); + await expect( + context.keyManager.connect(context.mainController).execute(renounceOwnershipPayload), + ).to.be.revertedWithCustomError(context.token, 'OwnableCallerNotTheOwner'); }); it('should allow the new owner to call renounceOwnership(..)', async () => { @@ -251,13 +256,13 @@ describe('When deploying LSP7 with LSP6 as owner', () => { ERC725YDataKeys.LSP6['AddressPermissions[]'].index + '0'.repeat(31) + '0', ERC725YDataKeys.LSP6['AddressPermissions[]'].index + '0'.repeat(31) + '1', ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + - context.owner.address.substring(2), + context.mainController.address.substring(2), ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + addressCanChangeOwner.address.substring(2), ]; const values = [ ARRAY_LENGTH.TWO, - context.owner.address, + context.mainController.address, addressCanChangeOwner.address, ALL_PERMISSIONS, PERMISSIONS.CHANGEOWNER, @@ -316,7 +321,7 @@ describe('When deploying LSP7 with LSP6 as owner', () => { ERC725YDataKeys.LSP6['AddressPermissions[]'].index + '0'.repeat(31) + '1', ERC725YDataKeys.LSP6['AddressPermissions[]'].index + '0'.repeat(31) + '2', ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + - context.owner.address.substring(2), + context.mainController.address.substring(2), ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + addressCanChangeOwner.address.substring(2), ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + @@ -324,7 +329,7 @@ describe('When deploying LSP7 with LSP6 as owner', () => { ]; const values = [ ARRAY_LENGTH.THREE, - context.owner.address, + context.mainController.address, addressCanChangeOwner.address, addressCanEditPermissions.address, ALL_PERMISSIONS, @@ -338,7 +343,7 @@ describe('When deploying LSP7 with LSP6 as owner', () => { it("should revert if caller doesn't have EDITPERMISSIONS permission", async () => { const key = ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + - context.owner.address.substring(2); + context.mainController.address.substring(2); const value = PERMISSIONS.CALL; const payload = context.token.interface.encodeFunctionData('setData', [key, value]); @@ -350,7 +355,7 @@ describe('When deploying LSP7 with LSP6 as owner', () => { it('should change ALL_PERMISSIONS to CALL permission of the address', async () => { const key = ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + - context.owner.address.substring(2); + context.mainController.address.substring(2); const value = PERMISSIONS.CALL; const payload = context.token.interface.encodeFunctionData('setData', [key, value]); @@ -362,7 +367,7 @@ describe('When deploying LSP7 with LSP6 as owner', () => { it('should add back ALL_PERMISSIONS of the address', async () => { const key = ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + - context.owner.address.substring(2); + context.mainController.address.substring(2); const value = ALL_PERMISSIONS; const payload = context.token.interface.encodeFunctionData('setData', [key, value]); @@ -392,7 +397,7 @@ describe('When deploying LSP7 with LSP6 as owner', () => { ERC725YDataKeys.LSP6['AddressPermissions[]'].index + '0'.repeat(31) + '2', ERC725YDataKeys.LSP6['AddressPermissions[]'].index + '0'.repeat(31) + '3', ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + - context.owner.address.substring(2), + context.mainController.address.substring(2), ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + addressCanChangeOwner.address.substring(2), ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + @@ -402,7 +407,7 @@ describe('When deploying LSP7 with LSP6 as owner', () => { ]; const values = [ ARRAY_LENGTH.FOUR, - context.owner.address, + context.mainController.address, addressCanChangeOwner.address, addressCanEditPermissions.address, addressCanAddController.address, @@ -446,7 +451,7 @@ describe('When deploying LSP7 with LSP6 as owner', () => { const value = '0x'; const payload = context.token.interface.encodeFunctionData('setData', [key, value]); - await context.keyManager.connect(context.owner).execute(payload); + await context.keyManager.connect(context.mainController).execute(payload); }); }); @@ -481,7 +486,7 @@ describe('When deploying LSP7 with LSP6 as owner', () => { ERC725YDataKeys.LSP6['AddressPermissions[]'].index + '0'.repeat(31) + '3', ERC725YDataKeys.LSP6['AddressPermissions[]'].index + '0'.repeat(31) + '4', ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + - context.owner.address.substring(2), + context.mainController.address.substring(2), ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + addressCanChangeOwner.address.substring(2), ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + @@ -493,7 +498,7 @@ describe('When deploying LSP7 with LSP6 as owner', () => { ]; const values = [ ARRAY_LENGTH.FIVE, - context.owner.address, + context.mainController.address, addressCanChangeOwner.address, addressCanEditPermissions.address, addressCanAddController.address, @@ -525,7 +530,7 @@ describe('When deploying LSP7 with LSP6 as owner', () => { const value = encodeCompactBytesArray([firstRandomSringKey.substring(0, 34)]); const payload = context.token.interface.encodeFunctionData('setData', [key, value]); - await context.keyManager.connect(context.owner).execute(payload); + await context.keyManager.connect(context.mainController).execute(payload); expect(await context.token.getData(key)).to.equal(value); }); @@ -588,14 +593,14 @@ describe('When deploying LSP7 with LSP6 as owner', () => { describe('when trying to call execute(..) function on LSP7 through LSP6', () => { it('should revert because function does not exist on LSP7', async () => { // deploying a dummy token contract with public mint function - const newTokenContract = await new LSP7Tester__factory(context.owner).deploy( + const newTokenContract = await new LSP7Tester__factory(context.mainController).deploy( 'NewTokenName', 'NewTokenSymbol', - context.owner.address, + context.mainController.address, ); // creating a payload to mint tokens in the new contract const mintPayload = newTokenContract.interface.encodeFunctionData('mint', [ - context.owner.address, + context.mainController.address, 1000, true, '0x', @@ -609,7 +614,7 @@ describe('When deploying LSP7 with LSP6 as owner', () => { ]); await expect( - context.keyManager.connect(context.owner).execute(payload), + context.keyManager.connect(context.mainController).execute(payload), ).to.be.revertedWithCustomError(newTokenContract, 'NoExtensionFoundForFunctionSelector'); }); }); @@ -636,7 +641,7 @@ describe('When deploying LSP7 with LSP6 as owner', () => { ERC725YDataKeys.LSP6['AddressPermissions[]'].index + '0'.repeat(31) + '4', ERC725YDataKeys.LSP6['AddressPermissions[]'].index + '0'.repeat(31) + '5', ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + - context.owner.address.substring(2), + context.mainController.address.substring(2), ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + addressCanChangeOwner.address.substring(2), ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + @@ -650,7 +655,7 @@ describe('When deploying LSP7 with LSP6 as owner', () => { ]; const values = [ ARRAY_LENGTH.SIX, - context.owner.address, + context.mainController.address, addressCanChangeOwner.address, addressCanEditPermissions.address, addressCanAddController.address, @@ -672,7 +677,7 @@ describe('When deploying LSP7 with LSP6 as owner', () => { const signature = await addressCanSign.signMessage('Some random message'); const validityOfTheSig = await context.keyManager.isValidSignature(dataHash, signature); - expect(validityOfTheSig).to.equal(ERC1271_VALUES.MAGIC_VALUE); + expect(validityOfTheSig).to.equal(ERC1271_VALUES.SUCCESS_VALUE); }); it('should not be allowed to sign messages for the token contract', async () => { diff --git a/tests/LSP6KeyManager/LSP6KeyManager.test.ts b/tests/LSP6KeyManager/LSP6KeyManager.test.ts index f2adeee39..d01ea7192 100644 --- a/tests/LSP6KeyManager/LSP6KeyManager.test.ts +++ b/tests/LSP6KeyManager/LSP6KeyManager.test.ts @@ -18,15 +18,20 @@ import { describe('LSP6KeyManager with constructor', () => { const buildTestContext = async (initialFunding?: BigNumber): Promise => { const accounts = await ethers.getSigners(); - const owner = accounts[0]; + const mainController = accounts[0]; - const universalProfile = await new UniversalProfile__factory(owner).deploy(owner.address, { - value: initialFunding, - }); + const universalProfile = await new UniversalProfile__factory(mainController).deploy( + mainController.address, + { + value: initialFunding, + }, + ); - const keyManager = await new LSP6KeyManager__factory(owner).deploy(universalProfile.address); + const keyManager = await new LSP6KeyManager__factory(mainController).deploy( + universalProfile.address, + ); - return { accounts, owner, universalProfile, keyManager, initialFunding }; + return { accounts, mainController, universalProfile, keyManager, initialFunding }; }; describe('when deploying the contract', () => { @@ -42,14 +47,16 @@ describe('LSP6KeyManager with constructor', () => { describe('testing internal functions', () => { testLSP6InternalFunctions(async () => { const accounts = await ethers.getSigners(); - const owner = accounts[0]; + const mainController = accounts[0]; - const universalProfile = await new UniversalProfile__factory(owner).deploy(owner.address); - const keyManagerInternalTester = await new KeyManagerInternalTester__factory(owner).deploy( - universalProfile.address, + const universalProfile = await new UniversalProfile__factory(mainController).deploy( + mainController.address, ); + const keyManagerInternalTester = await new KeyManagerInternalTester__factory( + mainController, + ).deploy(universalProfile.address); - return { owner, accounts, universalProfile, keyManagerInternalTester }; + return { mainController, accounts, universalProfile, keyManagerInternalTester }; }); }); }); diff --git a/tests/LSP6KeyManager/LSP6KeyManagerInit.test.ts b/tests/LSP6KeyManager/LSP6KeyManagerInit.test.ts index e2b46c20f..5d1869cb9 100644 --- a/tests/LSP6KeyManager/LSP6KeyManagerInit.test.ts +++ b/tests/LSP6KeyManager/LSP6KeyManagerInit.test.ts @@ -11,21 +11,21 @@ describe('LSP6KeyManager with proxy', () => { const buildProxyTestContext = async (initialFunding?: BigNumber): Promise => { const accounts = await ethers.getSigners(); - const owner = accounts[0]; + const mainController = accounts[0]; - const baseUP = await new UniversalProfileInit__factory(owner).deploy(); - const upProxy = await deployProxy(baseUP.address, owner); + const baseUP = await new UniversalProfileInit__factory(mainController).deploy(); + const upProxy = await deployProxy(baseUP.address, mainController); const universalProfile = await baseUP.attach(upProxy); - const baseKM = await new LSP6KeyManagerInit__factory(owner).deploy(); - const kmProxy = await deployProxy(baseKM.address, owner); + const baseKM = await new LSP6KeyManagerInit__factory(mainController).deploy(); + const kmProxy = await deployProxy(baseKM.address, mainController); const keyManager = await baseKM.attach(kmProxy); - return { accounts, owner, universalProfile, keyManager, initialFunding }; + return { accounts, mainController, universalProfile, keyManager, initialFunding }; }; const initializeProxies = async (context: LSP6TestContext) => { - await context.universalProfile['initialize(address)'](context.owner.address, { + await context.universalProfile['initialize(address)'](context.mainController.address, { value: context.initialFunding, }); diff --git a/tests/LSP6KeyManager/Relay/ExecuteRelayCall.test.ts b/tests/LSP6KeyManager/Relay/ExecuteRelayCall.test.ts index bf3efda48..a3c49da70 100644 --- a/tests/LSP6KeyManager/Relay/ExecuteRelayCall.test.ts +++ b/tests/LSP6KeyManager/Relay/ExecuteRelayCall.test.ts @@ -45,7 +45,9 @@ export const shouldBehaveLikeExecuteRelayCall = ( let signer: SignerWithAddress, relayer: SignerWithAddress, random: SignerWithAddress, - signerNoAllowedCalls: SignerWithAddress; + signerNoAllowedCalls: SignerWithAddress, + signerWithoutExecuteRelayCall: SignerWithAddress; + const signerPrivateKey = LOCAL_PRIVATE_KEYS.ACCOUNT1; let targetContract: TargetContract; @@ -57,20 +59,35 @@ export const shouldBehaveLikeExecuteRelayCall = ( relayer = context.accounts[2]; signerNoAllowedCalls = context.accounts[3]; random = context.accounts[4]; + signerWithoutExecuteRelayCall = context.accounts[5]; targetContract = await new TargetContract__factory(context.accounts[0]).deploy(); const permissionKeys = [ - ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + context.owner.address.substring(2), + ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + + context.mainController.address.substring(2), ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + signer.address.substring(2), ERC725YDataKeys.LSP6['AddressPermissions:AllowedCalls'] + signer.address.substring(2), ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + signerNoAllowedCalls.address.substring(2), + ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + + signerWithoutExecuteRelayCall.address.substring(2), ]; + const allPermissionsWithoutExecuteRelayCall = ethers.utils.hexZeroPad( + BigNumber.from(ALL_PERMISSIONS) + .sub(BigNumber.from(PERMISSIONS.EXECUTE_RELAY_CALL)) + .toHexString(), + 32, + ); + const permissionsValues = [ ALL_PERMISSIONS, - combinePermissions(PERMISSIONS.CALL, PERMISSIONS.TRANSFERVALUE), + combinePermissions( + PERMISSIONS.CALL, + PERMISSIONS.TRANSFERVALUE, + PERMISSIONS.EXECUTE_RELAY_CALL, + ), combineAllowedCalls( [ combineCallTypes(CALLTYPE.VALUE, CALLTYPE.CALL), @@ -80,12 +97,74 @@ export const shouldBehaveLikeExecuteRelayCall = ( ['0xffffffff', '0xffffffff'], ['0xffffffff', '0xffffffff'], ), - combinePermissions(PERMISSIONS.CALL, PERMISSIONS.TRANSFERVALUE), + combinePermissions( + PERMISSIONS.CALL, + PERMISSIONS.TRANSFERVALUE, + PERMISSIONS.EXECUTE_RELAY_CALL, + ), + allPermissionsWithoutExecuteRelayCall, ]; 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( + 'execute', + [OPERATION_TYPES.CALL, random.address, 0, '0x'], + ); + + const latestNonce = await context.keyManager.callStatic.getNonce( + signerWithoutExecuteRelayCall.address, + 0, + ); + + const validityTimestamps = 0; + + const signedMessageParams = { + lsp25Version: LSP25_VERSION, + chainId: 31337, // HARDHAT_CHAINID + nonce: latestNonce, + validityTimestamps, + msgValue: 0, + payload: executeRelayCallPayload, + }; + + const encodedMessage = ethers.utils.solidityPack( + ['uint256', 'uint256', 'uint256', 'uint256', 'uint256', 'bytes'], + [ + signedMessageParams.lsp25Version, + signedMessageParams.chainId, + signedMessageParams.nonce, + signedMessageParams.validityTimestamps, + signedMessageParams.msgValue, + signedMessageParams.payload, + ], + ); + + const eip191Signer = new EIP191Signer(); + const { signature } = await eip191Signer.signDataWithIntendedValidator( + context.keyManager.address, + encodedMessage, + LOCAL_PRIVATE_KEYS.ACCOUNT5, + ); + + await expect( + context.keyManager + .connect(relayer) + .executeRelayCall( + signature, + signedMessageParams.nonce, + signedMessageParams.validityTimestamps, + signedMessageParams.payload, + { value: 0 }, + ), + ) + .to.be.revertedWithCustomError(context.keyManager, 'NotAuthorised') + .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', () => { @@ -727,11 +806,12 @@ export const shouldBehaveLikeExecuteRelayCall = ( startingTimestamp, endingTimestamp, ); + const randomNumber = 12345; const calldata = context.universalProfile.interface.encodeFunctionData('execute', [ - 0, + OPERATION_TYPES.CALL, targetContract.address, 0, - targetContract.interface.encodeFunctionData('setNumber', [nonce]), + targetContract.interface.encodeFunctionData('setNumber', [randomNumber]), ]); const value = 0; const signature = await signLSP6ExecuteRelayCall( @@ -747,7 +827,7 @@ export const shouldBehaveLikeExecuteRelayCall = ( .connect(relayer) .executeRelayCall(signature, nonce, validityTimestamps, calldata); - expect(await targetContract.getNumber()).to.equal(nonce); + expect(await targetContract.getNumber()).to.equal(randomNumber); }); }); @@ -825,11 +905,12 @@ export const shouldBehaveLikeExecuteRelayCall = ( startingTimestamp, endingTimestamp, ); + const randomNumber = 12345; const calldata = context.universalProfile.interface.encodeFunctionData('execute', [ - 0, + OPERATION_TYPES.CALL, targetContract.address, 0, - targetContract.interface.encodeFunctionData('setNumber', [nonce]), + targetContract.interface.encodeFunctionData('setNumber', [randomNumber]), ]); const value = 0; const signature = await signLSP6ExecuteRelayCall( @@ -845,7 +926,7 @@ export const shouldBehaveLikeExecuteRelayCall = ( .connect(relayer) .executeRelayCall(signature, nonce, validityTimestamps, calldata); - expect(await targetContract.getNumber()).to.equal(nonce); + expect(await targetContract.getNumber()).to.equal(randomNumber); }); }); @@ -862,11 +943,12 @@ export const shouldBehaveLikeExecuteRelayCall = ( startingTimestamp, endingTimestamp, ); + const randomNumber = 12345; const calldata = context.universalProfile.interface.encodeFunctionData('execute', [ - 0, + OPERATION_TYPES.CALL, targetContract.address, 0, - targetContract.interface.encodeFunctionData('setNumber', [nonce]), + targetContract.interface.encodeFunctionData('setNumber', [randomNumber]), ]); const value = 0; const signature = await signLSP6ExecuteRelayCall( @@ -884,7 +966,7 @@ export const shouldBehaveLikeExecuteRelayCall = ( .connect(relayer) .executeRelayCall(signature, nonce, validityTimestamps, calldata); - expect(await targetContract.getNumber()).to.equal(nonce); + expect(await targetContract.getNumber()).to.equal(randomNumber); }); }); }); @@ -964,11 +1046,12 @@ export const shouldBehaveLikeExecuteRelayCall = ( startingTimestamp, endingTimestamp, ); + const randomNumber = 12345; const calldata = context.universalProfile.interface.encodeFunctionData('execute', [ - 0, + OPERATION_TYPES.CALL, targetContract.address, 0, - targetContract.interface.encodeFunctionData('setNumber', [nonce]), + targetContract.interface.encodeFunctionData('setNumber', [randomNumber]), ]); const value = 0; const signature = await signLSP6ExecuteRelayCall( @@ -986,7 +1069,7 @@ export const shouldBehaveLikeExecuteRelayCall = ( .connect(relayer) .executeRelayCall(signature, nonce, validityTimestamps, calldata); - expect(await targetContract.getNumber()).to.equal(nonce); + expect(await targetContract.getNumber()).to.equal(randomNumber); }); }); }); @@ -995,11 +1078,12 @@ export const shouldBehaveLikeExecuteRelayCall = ( it('passes', async () => { const nonce = await context.keyManager.callStatic.getNonce(signer.address, 14); const validityTimestamps = 0; + const randomNumber = 12345; const calldata = context.universalProfile.interface.encodeFunctionData('execute', [ - 0, + OPERATION_TYPES.CALL, targetContract.address, 0, - targetContract.interface.encodeFunctionData('setNumber', [nonce]), + targetContract.interface.encodeFunctionData('setNumber', [randomNumber]), ]); const value = 0; const signature = await signLSP6ExecuteRelayCall( @@ -1015,7 +1099,83 @@ export const shouldBehaveLikeExecuteRelayCall = ( .connect(relayer) .executeRelayCall(signature, nonce, validityTimestamps, calldata); - expect(await targetContract.getNumber()).to.equal(nonce); + expect(await targetContract.getNumber()).to.equal(randomNumber); + }); + }); + + describe('when `endingTimestamp == 0`', () => { + describe('`startingTimestamp` < now', () => { + it('passes', async () => { + const now = await time.latest(); + + const startingTimestamp = now - 100; + const endingTimestamp = 0; + + const nonce = await context.keyManager.callStatic.getNonce(signer.address, 14); + const validityTimestamps = createValidityTimestamps( + startingTimestamp, + endingTimestamp, + ); + const randomNumber = 12345; + const calldata = context.universalProfile.interface.encodeFunctionData('execute', [ + OPERATION_TYPES.CALL, + targetContract.address, + 0, + targetContract.interface.encodeFunctionData('setNumber', [randomNumber]), + ]); + const value = 0; + const signature = await signLSP6ExecuteRelayCall( + context.keyManager, + nonce.toString(), + validityTimestamps, + signerPrivateKey, + value, + calldata, + ); + + await context.keyManager + .connect(relayer) + .executeRelayCall(signature, nonce, validityTimestamps, calldata); + + expect(await targetContract.getNumber()).to.equal(randomNumber); + }); + }); + + describe('`startingTimestamp` > now', () => { + it('reverts', async () => { + const now = await time.latest(); + + const startingTimestamp = now + 100; + const endingTimestamp = 0; + + const nonce = await context.keyManager.callStatic.getNonce(signer.address, 14); + const validityTimestamps = createValidityTimestamps( + startingTimestamp, + endingTimestamp, + ); + const randomNumber = 12345; + const calldata = context.universalProfile.interface.encodeFunctionData('execute', [ + OPERATION_TYPES.CALL, + targetContract.address, + 0, + targetContract.interface.encodeFunctionData('setNumber', [randomNumber]), + ]); + const value = 0; + const signature = await signLSP6ExecuteRelayCall( + context.keyManager, + nonce.toString(), + validityTimestamps, + signerPrivateKey, + value, + calldata, + ); + + await expect( + context.keyManager + .connect(relayer) + .executeRelayCall(signature, nonce, validityTimestamps, calldata), + ).to.be.revertedWithCustomError(context.keyManager, 'RelayCallBeforeStartTime'); + }); }); }); }); @@ -1045,7 +1205,8 @@ export const shouldBehaveLikeExecuteRelayCall = ( ); const permissionKeys = [ - ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + context.owner.address.substring(2), + ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + + context.mainController.address.substring(2), ]; const permissionsValues = [ALL_PERMISSIONS]; @@ -1063,7 +1224,7 @@ export const shouldBehaveLikeExecuteRelayCall = ( await expect( context.keyManager - .connect(context.owner) + .connect(context.mainController) .executeRelayCallBatch(signatures, nonces, validityTimestamps, values, payloads), ).to.be.revertedWithCustomError( context.keyManager, @@ -1082,7 +1243,7 @@ export const shouldBehaveLikeExecuteRelayCall = ( '0x', ]); - const ownerNonce = await context.keyManager.getNonce(context.owner.address, 0); + const ownerNonce = await context.keyManager.getNonce(context.mainController.address, 0); const validityTimestamps = 0; @@ -1133,7 +1294,7 @@ export const shouldBehaveLikeExecuteRelayCall = ( // the incorrectly recovered address (as explained above) await expect( context.keyManager - .connect(context.owner) + .connect(context.mainController) .executeRelayCallBatch( [transferLyxSignature, transferLyxSignature], [ownerNonce, ownerNonce.add(1)], @@ -1160,7 +1321,7 @@ export const shouldBehaveLikeExecuteRelayCall = ( ERC725YDataKeys.LSP6['AddressPermissions:AllowedCalls'] + minter.address.substring(2), ], [ - PERMISSIONS.CALL, + combinePermissions(PERMISSIONS.CALL, PERMISSIONS.EXECUTE_RELAY_CALL), combineAllowedCalls( [CALLTYPE.CALL], [tokenContract.address], @@ -1171,7 +1332,7 @@ export const shouldBehaveLikeExecuteRelayCall = ( ], ); - const ownerNonce = await context.keyManager.getNonce(context.owner.address, 0); + const ownerNonce = await context.keyManager.getNonce(context.mainController.address, 0); const ownerGivePermissionsSignature = await signLSP6ExecuteRelayCall( context.keyManager, @@ -1230,7 +1391,7 @@ export const shouldBehaveLikeExecuteRelayCall = ( ); await context.keyManager - .connect(context.owner) + .connect(context.mainController) .executeRelayCallBatch( [ownerGivePermissionsSignature, minterMintSignature, ownerRemovePermissionsSignature], [ownerNonce, minterNonce, newOwnerNonce], @@ -1297,7 +1458,7 @@ export const shouldBehaveLikeExecuteRelayCall = ( [OPERATION_TYPES.CALL, thirdRecipient, transferAmounts[2], '0x'], ); - const ownerNonce = await context.keyManager.getNonce(context.owner.address, 0); + const ownerNonce = await context.keyManager.getNonce(context.mainController.address, 0); const validityTimestamps = 0; @@ -1328,7 +1489,7 @@ export const shouldBehaveLikeExecuteRelayCall = ( await expect( context.keyManager - .connect(context.owner) + .connect(context.mainController) .executeRelayCallBatch( [firstTransferLyxSignature, secondTransferLyxSignature, thirdTransferLyxSignature], [ownerNonce, ownerNonce.add(1), ownerNonce.add(2)], @@ -1383,7 +1544,7 @@ export const shouldBehaveLikeExecuteRelayCall = ( [OPERATION_TYPES.CALL, thirdRecipient, transferAmounts[2], '0x'], ); - const ownerNonce = await context.keyManager.getNonce(context.owner.address, 0); + const ownerNonce = await context.keyManager.getNonce(context.mainController.address, 0); const validityTimestamps = 0; @@ -1414,7 +1575,7 @@ export const shouldBehaveLikeExecuteRelayCall = ( await expect( context.keyManager - .connect(context.owner) + .connect(context.mainController) .executeRelayCallBatch( [firstTransferLyxSignature, secondTransferLyxSignature, thirdTransferLyxSignature], [ownerNonce, ownerNonce.add(1), ownerNonce.add(2)], @@ -1466,7 +1627,7 @@ export const shouldBehaveLikeExecuteRelayCall = ( [OPERATION_TYPES.CALL, thirdRecipient, transferAmounts[2], '0x'], ); - const ownerNonce = await context.keyManager.getNonce(context.owner.address, 0); + const ownerNonce = await context.keyManager.getNonce(context.mainController.address, 0); const validityTimestamps = 0; @@ -1496,7 +1657,7 @@ export const shouldBehaveLikeExecuteRelayCall = ( ); const tx = await context.keyManager - .connect(context.owner) + .connect(context.mainController) .executeRelayCallBatch( [firstTransferLyxSignature, secondTransferLyxSignature, thirdTransferLyxSignature], [ownerNonce, ownerNonce.add(1), ownerNonce.add(2)], @@ -1541,7 +1702,7 @@ export const shouldBehaveLikeExecuteRelayCall = ( [OPERATION_TYPES.CALL, randomRecipient, validAmount, '0x'], ); - const ownerNonce = await context.keyManager.getNonce(context.owner.address, 0); + const ownerNonce = await context.keyManager.getNonce(context.mainController.address, 0); const nonces = [ownerNonce, ownerNonce.add(1), ownerNonce.add(2)]; @@ -1579,7 +1740,7 @@ export const shouldBehaveLikeExecuteRelayCall = ( await expect( context.keyManager - .connect(context.owner) + .connect(context.mainController) .executeRelayCallBatch( signatures, nonces, @@ -1616,7 +1777,7 @@ export const shouldBehaveLikeExecuteRelayCall = ( [OPERATION_TYPES.CALL, randomRecipient, validAmount, '0x'], ); - const ownerNonce = await context.keyManager.getNonce(context.owner.address, 0); + const ownerNonce = await context.keyManager.getNonce(context.mainController.address, 0); const nonces = [ownerNonce, ownerNonce.add(1), ownerNonce.add(2)]; const values = [0, 0, 0]; @@ -1655,7 +1816,7 @@ export const shouldBehaveLikeExecuteRelayCall = ( await expect( context.keyManager - .connect(context.owner) + .connect(context.mainController) .executeRelayCallBatch( signatures, nonces, diff --git a/tests/LSP6KeyManager/Relay/MultiChannelNonce.test.ts b/tests/LSP6KeyManager/Relay/MultiChannelNonce.test.ts index 228cc35bf..cc4cf1050 100644 --- a/tests/LSP6KeyManager/Relay/MultiChannelNonce.test.ts +++ b/tests/LSP6KeyManager/Relay/MultiChannelNonce.test.ts @@ -18,7 +18,7 @@ import { // setup import { LSP6TestContext } from '../../utils/context'; import { setupKeyManager } from '../../utils/fixtures'; -import { LOCAL_PRIVATE_KEYS, combineAllowedCalls } from '../../utils/helpers'; +import { LOCAL_PRIVATE_KEYS, combineAllowedCalls, combinePermissions } from '../../utils/helpers'; export const shouldBehaveLikeMultiChannelNonce = (buildContext: () => Promise) => { let context: LSP6TestContext; @@ -35,14 +35,15 @@ export const shouldBehaveLikeMultiChannelNonce = (buildContext: () => Promise Promise Promise Promise { - it('should revert with Key Manager error `CannotSendValueToSetData`', async () => { - const key = ethers.utils.keccak256(ethers.utils.toUtf8Bytes('My Key')); + it('should pass', async () => { + const key = ethers.utils.keccak256(ethers.utils.toUtf8Bytes('My First Key')); const value = ethers.utils.hexlify(ethers.utils.toUtf8Bytes('Hello Lukso!!!')); const payload = context.universalProfile.interface.encodeFunctionData('setData', [ @@ -171,7 +172,9 @@ export const shouldBehaveLikePermissionSetData = (buildContext: () => Promise Promise Promise Promise Promise { - it('should revert with Key Manager error `CannotSendValueToSetData`', async () => { + it('should pass', async () => { const keys = [ ethers.utils.keccak256(ethers.utils.toUtf8Bytes('My First Key')), ethers.utils.keccak256(ethers.utils.toUtf8Bytes('My Second Key')), @@ -527,8 +530,10 @@ export const shouldBehaveLikePermissionSetData = (buildContext: () => Promise Promise { context = await buildContext(); - contractCanSetData = await new Executor__factory(context.owner).deploy( + contractCanSetData = await new Executor__factory(context.mainController).deploy( context.universalProfile.address, context.keyManager.address, ); const permissionKeys = [ - ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + context.owner.address.substring(2), + ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + + context.mainController.address.substring(2), ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + contractCanSetData.address.substring(2), ERC725YDataKeys.LSP6['AddressPermissions:AllowedERC725YDataKeys'] + diff --git a/tests/LSP6KeyManager/SetPermissions/PermissionChangeAddController.test.ts b/tests/LSP6KeyManager/SetPermissions/PermissionChangeAddController.test.ts index aac7e4dcb..4551588f7 100644 --- a/tests/LSP6KeyManager/SetPermissions/PermissionChangeAddController.test.ts +++ b/tests/LSP6KeyManager/SetPermissions/PermissionChangeAddController.test.ts @@ -22,7 +22,7 @@ async function setupPermissions( permissionValues, ]); - await context.keyManager.connect(context.owner).execute(setupPayload); + await context.keyManager.connect(context.mainController).execute(setupPayload); } /** @@ -34,7 +34,7 @@ async function resetPermissions(context: LSP6TestContext, permissionsKeys: strin Array(permissionsKeys.length).fill('0x'), ]); - await context.keyManager.connect(context.owner).execute(teardownPayload); + await context.keyManager.connect(context.mainController).execute(teardownPayload); } export const shouldBehaveLikePermissionChangeOrAddController = ( @@ -48,13 +48,98 @@ export const shouldBehaveLikePermissionChangeOrAddController = ( let permissionArrayKeys: string[] = []; let permissionArrayValues: string[] = []; + // addresses with not 32 bytes long permissions value set + // used to check that the caller editing the permissions value for these controllers requires the permission ADDCONTROLLER, + const callerHasAllPermissionsTestCase = { + addressWith16BytesHexPermissionsLength: '', + addressWith40BytesHexPermsissionsLength: '', + }; + + const callerHasAddControllerTestCase = { + addressWith16BytesHexPermissionsLength: '', + addressWith40BytesHexPermsissionsLength: '', + }; + + const callerHasEditPermissionsTestCase = { + addressWith16BytesHexPermissionsLength: '', + addressWith40BytesHexPermsissionsLength: '', + }; + + const callerHasSetDataTestCase = { + addressWith16BytesHexPermissionsLength: '', + addressWith40BytesHexPermsissionsLength: '', + }; + before('setup', async () => { context = await buildContext(); + callerHasAllPermissionsTestCase.addressWith16BytesHexPermissionsLength = + ethers.Wallet.createRandom().address.toLowerCase(); + + callerHasAllPermissionsTestCase.addressWith40BytesHexPermsissionsLength = + ethers.Wallet.createRandom().address.toLowerCase(); + + callerHasAddControllerTestCase.addressWith16BytesHexPermissionsLength = + ethers.Wallet.createRandom().address.toLowerCase(); + + callerHasAddControllerTestCase.addressWith40BytesHexPermsissionsLength = + ethers.Wallet.createRandom().address.toLowerCase(); + + callerHasEditPermissionsTestCase.addressWith16BytesHexPermissionsLength = + ethers.Wallet.createRandom().address.toLowerCase(); + + callerHasEditPermissionsTestCase.addressWith40BytesHexPermsissionsLength = + ethers.Wallet.createRandom().address.toLowerCase(); + + callerHasSetDataTestCase.addressWith16BytesHexPermissionsLength = + ethers.Wallet.createRandom().address.toLowerCase(); + + callerHasSetDataTestCase.addressWith40BytesHexPermsissionsLength = + ethers.Wallet.createRandom().address.toLowerCase(); + + const firstSetupPermissionsKeys = [ + ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + + callerHasAllPermissionsTestCase.addressWith16BytesHexPermissionsLength.substring(2), + ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + + callerHasAllPermissionsTestCase.addressWith40BytesHexPermsissionsLength.substring(2), + ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + + callerHasAddControllerTestCase.addressWith16BytesHexPermissionsLength.substring(2), + ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + + callerHasAddControllerTestCase.addressWith40BytesHexPermsissionsLength.substring(2), + ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + + callerHasEditPermissionsTestCase.addressWith16BytesHexPermissionsLength.substring(2), + ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + + callerHasEditPermissionsTestCase.addressWith40BytesHexPermsissionsLength.substring(2), + ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + + callerHasSetDataTestCase.addressWith16BytesHexPermissionsLength.substring(2), + ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + + callerHasSetDataTestCase.addressWith40BytesHexPermsissionsLength.substring(2), + ]; + + // We need to setup these first from the start, as the setup and teardown in the tests reset the permissions via the Key Manager, + // as the Key Manager will revert with custom error `InvalidDataValuesForDataKeys(AddressPermissions:Permissions:, invalidPermissionValue)` + const firstSetupPermissionsValues = [ + // 16 bytes long hex string = not 32 bytes long = equivalent to No Permissions Set + '0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', + // 40 bytes long hex string = not 32 bytes long = equivalent to No Permissions Set + '0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', + // same for other controllers (just repeated) + '0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', + '0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', + '0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', + '0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', + '0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', + '0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', + ]; + await setupKeyManager( context, - [ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + context.owner.address.substring(2)], - [ALL_PERMISSIONS], + [ + ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + + context.mainController.address.substring(2), + ...firstSetupPermissionsKeys, + ], + [ALL_PERMISSIONS, ...firstSetupPermissionsValues], ); }); @@ -100,7 +185,7 @@ export const shouldBehaveLikePermissionChangeOrAddController = ( PERMISSIONS.SETDATA, // placeholder permission PERMISSIONS.TRANSFERVALUE, - // 0x0000... = similar to empty, or 'no permissions set' + // `bytes32(0)` = similar to empty, or 'no permissions set' '0x0000000000000000000000000000000000000000000000000000000000000000', ]; @@ -116,7 +201,7 @@ export const shouldBehaveLikePermissionChangeOrAddController = ( permissionArrayValues = [ ethers.utils.hexZeroPad(ethers.utils.hexlify(6), 16), - context.owner.address, + context.mainController.address, canOnlyAddController.address, canOnlyEditPermissions.address, canOnlySetData.address, @@ -159,7 +244,7 @@ export const shouldBehaveLikePermissionChangeOrAddController = ( PERMISSIONS.SETDATA, ]); - await context.keyManager.connect(context.owner).execute(payload); + await context.keyManager.connect(context.mainController).execute(payload); // prettier-ignore const result = await context.universalProfile.getData(key); @@ -178,13 +263,43 @@ export const shouldBehaveLikePermissionChangeOrAddController = ( value, ]); - await context.keyManager.connect(context.owner).execute(payload); + await context.keyManager.connect(context.mainController).execute(payload); // prettier-ignore const result = await context.universalProfile.getData(key); expect(result).to.equal(value); }); + it('should be allowed to ADD a new controller if this address has a 16 bytes long bytes value already set under its permission', async () => { + const key = + ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + + callerHasAllPermissionsTestCase.addressWith16BytesHexPermissionsLength.substring(2); + const value = PERMISSIONS.SETDATA; + + const payload = context.universalProfile.interface.encodeFunctionData('setData', [ + key, + value, + ]); + + await context.keyManager.connect(context.mainController).execute(payload); + expect(await context.universalProfile.getData(key)).to.equal(value); + }); + + it('should be allowed to ADD a new controller if this address has a 40 bytes long bytes value already set under its permission', async () => { + const key = + ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + + callerHasAllPermissionsTestCase.addressWith40BytesHexPermsissionsLength.substring(2); + const value = PERMISSIONS.SETDATA; + + const payload = context.universalProfile.interface.encodeFunctionData('setData', [ + key, + value, + ]); + + await context.keyManager.connect(context.mainController).execute(payload); + expect(await context.universalProfile.getData(key)).to.equal(value); + }); + describe('when editing `AddressPermissions[]` array length', () => { it('should be allowed to increment the length', async () => { const key = ERC725YDataKeys.LSP6['AddressPermissions[]'].length; @@ -200,7 +315,7 @@ export const shouldBehaveLikePermissionChangeOrAddController = ( value, ]); - await context.keyManager.connect(context.owner).execute(payload); + await context.keyManager.connect(context.mainController).execute(payload); // prettier-ignore const result = await context.universalProfile.getData(key); @@ -221,7 +336,7 @@ export const shouldBehaveLikePermissionChangeOrAddController = ( value, ]); - await context.keyManager.connect(context.owner).execute(payload); + await context.keyManager.connect(context.mainController).execute(payload); // prettier-ignore const result = await context.universalProfile.getData(key); @@ -239,7 +354,7 @@ export const shouldBehaveLikePermissionChangeOrAddController = ( value, ]); - await context.keyManager.connect(context.owner).execute(payload); + await context.keyManager.connect(context.mainController).execute(payload); const result = await context.universalProfile.getData(key); expect(result).to.equal(value); @@ -255,7 +370,7 @@ export const shouldBehaveLikePermissionChangeOrAddController = ( randomValue, ]); - await expect(context.keyManager.connect(context.owner).execute(setupPayload)) + await expect(context.keyManager.connect(context.mainController).execute(setupPayload)) .to.be.revertedWithCustomError(context.keyManager, 'InvalidDataValuesForDataKeys') .withArgs(key, randomValue); }); @@ -270,7 +385,7 @@ export const shouldBehaveLikePermissionChangeOrAddController = ( randomValue, ]); - await expect(context.keyManager.connect(context.owner).execute(setupPayload)) + await expect(context.keyManager.connect(context.mainController).execute(setupPayload)) .to.be.revertedWithCustomError(context.keyManager, 'InvalidDataValuesForDataKeys') .withArgs(key, randomValue); }); @@ -289,7 +404,7 @@ export const shouldBehaveLikePermissionChangeOrAddController = ( value, ]); - await context.keyManager.connect(context.owner).execute(payload); + await context.keyManager.connect(context.mainController).execute(payload); // prettier-ignore const result = await context.universalProfile.getData(key); @@ -306,7 +421,7 @@ export const shouldBehaveLikePermissionChangeOrAddController = ( randomValue, ]); - await expect(context.keyManager.connect(context.owner).execute(setupPayload)) + await expect(context.keyManager.connect(context.mainController).execute(setupPayload)) .to.be.revertedWithCustomError(context.keyManager, 'InvalidDataValuesForDataKeys') .withArgs(key, randomValue); }); @@ -321,7 +436,7 @@ export const shouldBehaveLikePermissionChangeOrAddController = ( randomValue, ]); - await expect(context.keyManager.connect(context.owner).execute(setupPayload)) + await expect(context.keyManager.connect(context.mainController).execute(setupPayload)) .to.be.revertedWithCustomError(context.keyManager, 'InvalidDataValuesForDataKeys') .withArgs(key, randomValue); }); @@ -338,7 +453,7 @@ export const shouldBehaveLikePermissionChangeOrAddController = ( value, ]); - await context.keyManager.connect(context.owner).execute(payload); + await context.keyManager.connect(context.mainController).execute(payload); // prettier-ignore const result = await context.universalProfile.getData(key); @@ -362,7 +477,7 @@ export const shouldBehaveLikePermissionChangeOrAddController = ( value, ]); - await expect(context.keyManager.connect(context.owner).execute(payload)) + await expect(context.keyManager.connect(context.mainController).execute(payload)) .to.be.revertedWithCustomError(context.keyManager, 'NotRecognisedPermissionKey') .withArgs(key.toLowerCase()); }); @@ -429,6 +544,36 @@ export const shouldBehaveLikePermissionChangeOrAddController = ( .withArgs(canOnlyAddController.address, 'EDITPERMISSIONS'); }); + it('should be allowed to ADD a new controller if this address has a 16 bytes long bytes value already set under its permission', async () => { + const key = + ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + + callerHasAddControllerTestCase.addressWith16BytesHexPermissionsLength.substring(2); + const value = PERMISSIONS.SETDATA; + + const payload = context.universalProfile.interface.encodeFunctionData('setData', [ + key, + value, + ]); + + await context.keyManager.connect(canOnlyAddController).execute(payload); + expect(await context.universalProfile.getData(key)).to.equal(value); + }); + + it('should be allowed to ADD a new controller if this address has a 40 bytes long bytes value already set under its permission', async () => { + const key = + ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + + callerHasAddControllerTestCase.addressWith40BytesHexPermsissionsLength.substring(2); + const value = PERMISSIONS.SETDATA; + + const payload = context.universalProfile.interface.encodeFunctionData('setData', [ + key, + value, + ]); + + await context.keyManager.connect(canOnlyAddController).execute(payload); + expect(await context.universalProfile.getData(key)).to.equal(value); + }); + describe('when editing `AddressPermissions[]` array length', () => { it('should be allowed to increment the length', async () => { const key = ERC725YDataKeys.LSP6['AddressPermissions[]'].length; @@ -599,7 +744,7 @@ export const shouldBehaveLikePermissionChangeOrAddController = ( ]); }); - it('should not be allowed to ADD a permission', async () => { + it('should not be allowed to ADD a new controller', async () => { const newController = ethers.Wallet.createRandom(); const key = @@ -618,7 +763,7 @@ export const shouldBehaveLikePermissionChangeOrAddController = ( .withArgs(canOnlyEditPermissions.address, 'ADDCONTROLLER'); }); - it('should not be allowed to set (= ADD) a permission for an address that has 32 x 0 bytes (0x0000...0000) as permission value', async () => { + it('should not be allowed to ADD a new controller if this address had 32 x 0 bytes (0x0000...0000) already as permission value', async () => { const key = ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + addressWithZeroHexPermissions.address.substring(2); @@ -634,7 +779,40 @@ export const shouldBehaveLikePermissionChangeOrAddController = ( .withArgs(canOnlyEditPermissions.address, 'ADDCONTROLLER'); }); - it('should be allowed to CHANGE a permission', async () => { + it('should not be allowed to ADD a new controller if this address has a 16 bytes long bytes value already set under its permission', async () => { + const key = + ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + + callerHasEditPermissionsTestCase.addressWith16BytesHexPermissionsLength.substring(2); + + const value = PERMISSIONS.SETDATA; + + const payload = context.universalProfile.interface.encodeFunctionData('setData', [ + key, + value, + ]); + + await expect(context.keyManager.connect(canOnlyEditPermissions).execute(payload)) + .to.be.revertedWithCustomError(context.keyManager, 'NotAuthorised') + .withArgs(canOnlyEditPermissions.address, 'ADDCONTROLLER'); + }); + + it('should not be allowed to ADD a new controller if this address has a 40 bytes long bytes value already set under its permission', async () => { + const key = + ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + + callerHasEditPermissionsTestCase.addressWith40BytesHexPermsissionsLength.substring(2); + const value = PERMISSIONS.SETDATA; + + const payload = context.universalProfile.interface.encodeFunctionData('setData', [ + key, + value, + ]); + + await expect(context.keyManager.connect(canOnlyEditPermissions).execute(payload)) + .to.be.revertedWithCustomError(context.keyManager, 'NotAuthorised') + .withArgs(canOnlyEditPermissions.address, 'ADDCONTROLLER'); + }); + + it('should be allowed to EDIT the existing permissions of a controller', async () => { const key = ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + addressToEditPermissions.address.substring(2); @@ -860,6 +1038,39 @@ export const shouldBehaveLikePermissionChangeOrAddController = ( .withArgs(canOnlySetData.address, 'ADDCONTROLLER'); }); + it('should not be allowed to ADD a new controller if this address has a 16 bytes long bytes value already set under its permission', async () => { + const key = + ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + + callerHasSetDataTestCase.addressWith16BytesHexPermissionsLength.substring(2); + + const value = PERMISSIONS.SETDATA; + + const payload = context.universalProfile.interface.encodeFunctionData('setData', [ + key, + value, + ]); + + await expect(context.keyManager.connect(canOnlySetData).execute(payload)) + .to.be.revertedWithCustomError(context.keyManager, 'NotAuthorised') + .withArgs(canOnlySetData.address, 'ADDCONTROLLER'); + }); + + it('should not be allowed to ADD a new controller if this address has a 40 bytes long bytes value already set under its permission', async () => { + const key = + ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + + callerHasSetDataTestCase.addressWith40BytesHexPermsissionsLength.substring(2); + const value = PERMISSIONS.SETDATA; + + const payload = context.universalProfile.interface.encodeFunctionData('setData', [ + key, + value, + ]); + + await expect(context.keyManager.connect(canOnlySetData).execute(payload)) + .to.be.revertedWithCustomError(context.keyManager, 'NotAuthorised') + .withArgs(canOnlySetData.address, 'ADDCONTROLLER'); + }); + it('should not be allowed to EDIT a permission', async () => { const key = ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + @@ -1075,7 +1286,7 @@ export const shouldBehaveLikePermissionChangeOrAddController = ( values, ]); - await context.keyManager.connect(context.owner).execute(payload); + await context.keyManager.connect(context.mainController).execute(payload); // prettier-ignore const fetchedResult = await context.universalProfile.getDataBatch(keys); @@ -1104,7 +1315,7 @@ export const shouldBehaveLikePermissionChangeOrAddController = ( values, ]); - await context.keyManager.connect(context.owner).execute(payload); + await context.keyManager.connect(context.mainController).execute(payload); // prettier-ignore const fetchedResult = await context.universalProfile.getDataBatch(keys); @@ -1135,7 +1346,7 @@ export const shouldBehaveLikePermissionChangeOrAddController = ( values, ]); - await context.keyManager.connect(context.owner).execute(payload); + await context.keyManager.connect(context.mainController).execute(payload); // prettier-ignore const fetchedResult = await context.universalProfile.getDataBatch(keys); diff --git a/tests/LSP6KeyManager/SetPermissions/SetAllowedCalls.test.ts b/tests/LSP6KeyManager/SetPermissions/SetAllowedCalls.test.ts index 160d31382..3ad8fbfa2 100644 --- a/tests/LSP6KeyManager/SetPermissions/SetAllowedCalls.test.ts +++ b/tests/LSP6KeyManager/SetPermissions/SetAllowedCalls.test.ts @@ -20,9 +20,10 @@ export const shouldBehaveLikeSettingAllowedCalls = ( describe('deleting AllowedCalls', () => { let canOnlyAddController: SignerWithAddress, canOnlyEditPermissions: SignerWithAddress; - let beneficiary: SignerWithAddress; - let invalidBytes: SignerWithAddress; - let noBytes: SignerWithAddress; + let beneficiaryWithPermissions: SignerWithAddress, + beneficiaryNoPermissions: SignerWithAddress, + invalidBytes: SignerWithAddress, + noBytes: SignerWithAddress; before(async () => { context = await buildContext(); @@ -30,18 +31,20 @@ export const shouldBehaveLikeSettingAllowedCalls = ( canOnlyAddController = context.accounts[1]; canOnlyEditPermissions = context.accounts[2]; - beneficiary = context.accounts[3]; - invalidBytes = context.accounts[4]; - noBytes = context.accounts[5]; + beneficiaryWithPermissions = context.accounts[3]; + beneficiaryNoPermissions = context.accounts[4]; + invalidBytes = context.accounts[5]; + noBytes = context.accounts[6]; + // prettier-ignore const permissionKeys = [ - ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + - canOnlyAddController.address.substring(2), - ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + - canOnlyEditPermissions.address.substring(2), + ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + canOnlyAddController.address.substring(2), + ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + canOnlyEditPermissions.address.substring(2), + ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + beneficiaryWithPermissions.address.substring(2), ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + invalidBytes.address.substring(2), ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + noBytes.address.substring(2), - ERC725YDataKeys.LSP6['AddressPermissions:AllowedCalls'] + beneficiary.address.substring(2), + ERC725YDataKeys.LSP6['AddressPermissions:AllowedCalls'] + beneficiaryWithPermissions.address.substring(2), + ERC725YDataKeys.LSP6['AddressPermissions:AllowedCalls'] + beneficiaryNoPermissions.address.substring(2), ERC725YDataKeys.LSP6['AddressPermissions:AllowedCalls'] + invalidBytes.address.substring(2), ERC725YDataKeys.LSP6['AddressPermissions:AllowedCalls'] + noBytes.address.substring(2), ]; @@ -51,6 +54,19 @@ export const shouldBehaveLikeSettingAllowedCalls = ( PERMISSIONS.EDITPERMISSIONS, PERMISSIONS.CALL, PERMISSIONS.CALL, + PERMISSIONS.CALL, + // beneficiaryWithPermissions + combineAllowedCalls( + // allow the beneficiary to transfer value to addresses 0xcafe... and 0xbeef... + [CALLTYPE.VALUE, CALLTYPE.VALUE], + [ + '0xcafecafecafecafecafecafecafecafecafecafe', + '0xbeefbeefbeefbeefbeefbeefbeefbeefbeefbeef', + ], + ['0xffffffff', '0xffffffff'], + ['0xffffffff', '0xffffffff'], + ), + // beneficiaryNoPermissions combineAllowedCalls( // allow the beneficiary to transfer value to addresses 0xcafe... and 0xbeef... [CALLTYPE.VALUE, CALLTYPE.VALUE], @@ -61,47 +77,88 @@ export const shouldBehaveLikeSettingAllowedCalls = ( ['0xffffffff', '0xffffffff'], ['0xffffffff', '0xffffffff'], ), + // invalidBytes '0xbadbadbadbad', + // noBytes '0x', ]; await setupKeyManager(context, permissionKeys, permissionValues); }); - describe('when caller has ADD permission', () => { - it('should revert and not be allowed to clear the list of allowed calls for an address', async () => { - const dataKey = - ERC725YDataKeys.LSP6['AddressPermissions:AllowedCalls'] + - beneficiary.address.substring(2); - const dataValue = '0x'; + describe('when caller has permission ADD_CONTROLLER', () => { + describe('when controller in `AddressPermissions:AllowedCalls:` has some permissions', () => { + it('should revert and not be allowed to clear the list of allowed calls', async () => { + const dataKey = + ERC725YDataKeys.LSP6['AddressPermissions:AllowedCalls'] + + beneficiaryWithPermissions.address.substring(2); + const dataValue = '0x'; - const setDataPayload = context.universalProfile.interface.encodeFunctionData('setData', [ - dataKey, - dataValue, - ]); + const setDataPayload = context.universalProfile.interface.encodeFunctionData('setData', [ + dataKey, + dataValue, + ]); - await expect(context.keyManager.connect(canOnlyAddController).execute(setDataPayload)) - .to.be.revertedWithCustomError(context.keyManager, 'NotAuthorised') - .withArgs(canOnlyAddController.address, 'EDITPERMISSIONS'); + await expect(context.keyManager.connect(canOnlyAddController).execute(setDataPayload)) + .to.be.revertedWithCustomError(context.keyManager, 'NotAuthorised') + .withArgs(canOnlyAddController.address, 'EDITPERMISSIONS'); + }); + }); + + describe('when controller in `AddressPermissions:AllowedCalls:` has no permissions', () => { + it('should pass and clear the list of allowed calls', async () => { + const dataKey = + ERC725YDataKeys.LSP6['AddressPermissions:AllowedCalls'] + + beneficiaryNoPermissions.address.substring(2); + const dataValue = '0x'; + + const setDataPayload = context.universalProfile.interface.encodeFunctionData('setData', [ + dataKey, + dataValue, + ]); + + await context.keyManager.connect(canOnlyAddController).execute(setDataPayload); + + expect(await context.universalProfile.getData(dataKey)).to.equal(dataValue); + }); }); }); - describe('when caller has CHANGE permission', () => { - it('should allow to clear the list of allowed calls for an address', async () => { - const dataKey = - ERC725YDataKeys.LSP6['AddressPermissions:AllowedCalls'] + - beneficiary.address.substring(2); - const dataValue = '0x'; + describe('when caller has EDIT_PERMISSIONS', () => { + describe('when controller in `AddressPermissions:AllowedCalls:` has some permissions', () => { + it('should allow to clear the list of allowed calls', async () => { + const dataKey = + ERC725YDataKeys.LSP6['AddressPermissions:AllowedCalls'] + + beneficiaryWithPermissions.address.substring(2); + const dataValue = '0x'; - const setDataPayload = context.universalProfile.interface.encodeFunctionData('setData', [ - dataKey, - dataValue, - ]); + const setDataPayload = context.universalProfile.interface.encodeFunctionData('setData', [ + dataKey, + dataValue, + ]); + + await context.keyManager.connect(canOnlyEditPermissions).execute(setDataPayload); + + expect(await context.universalProfile.getData(dataKey)).to.equal(dataValue); + }); + }); + + describe('when controller in `AddressPermissions:AllowedCalls:` has no permissions', () => { + it('should revert and not allow to clear the list of allowed calls', async () => { + const dataKey = + ERC725YDataKeys.LSP6['AddressPermissions:AllowedCalls'] + + beneficiaryNoPermissions.address.substring(2); + const dataValue = '0x'; - await context.keyManager.connect(canOnlyEditPermissions).execute(setDataPayload); + const setDataPayload = context.universalProfile.interface.encodeFunctionData('setData', [ + dataKey, + dataValue, + ]); - const result = await context.universalProfile.getData(dataKey); - expect(result).to.equal(dataValue); + await expect(context.keyManager.connect(canOnlyEditPermissions).execute(setDataPayload)) + .to.be.revertedWithCustomError(context.keyManager, 'NotAuthorised') + .withArgs(canOnlyEditPermissions.address, 'ADDCONTROLLER'); + }); }); }); }); @@ -125,14 +182,16 @@ export const shouldBehaveLikeSettingAllowedCalls = ( zero32Bytes = context.accounts[5]; zero40Bytes = context.accounts[6]; + // prettier-ignore const permissionKeys = [ - ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + - canOnlyAddController.address.substring(2), - ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + - canOnlyEditPermissions.address.substring(2), + ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + canOnlyAddController.address.substring(2), + ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + canOnlyEditPermissions.address.substring(2), + ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + beneficiary.address.substring(2), + ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + invalidBeneficiary.address.substring(2), + ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + zero32Bytes.address.substring(2), + ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + zero40Bytes.address.substring(2), ERC725YDataKeys.LSP6['AddressPermissions:AllowedCalls'] + beneficiary.address.substring(2), - ERC725YDataKeys.LSP6['AddressPermissions:AllowedCalls'] + - invalidBeneficiary.address.substring(2), + ERC725YDataKeys.LSP6['AddressPermissions:AllowedCalls'] + invalidBeneficiary.address.substring(2), ERC725YDataKeys.LSP6['AddressPermissions:AllowedCalls'] + zero32Bytes.address.substring(2), ERC725YDataKeys.LSP6['AddressPermissions:AllowedCalls'] + zero40Bytes.address.substring(2), ]; @@ -140,6 +199,10 @@ export const shouldBehaveLikeSettingAllowedCalls = ( const permissionValues = [ PERMISSIONS.ADDCONTROLLER, PERMISSIONS.EDITPERMISSIONS, + PERMISSIONS.CALL, + PERMISSIONS.CALL, + PERMISSIONS.CALL, + PERMISSIONS.CALL, combineAllowedCalls( // allow the beneficiary to transfer value to addresses 0xcafe... and 0xbeef... [CALLTYPE.VALUE, CALLTYPE.VALUE], @@ -161,71 +224,17 @@ export const shouldBehaveLikeSettingAllowedCalls = ( }); describe('when caller has permission ADDCONTROLLER', () => { - it('should fail when trying to edit existing allowed addresses for an address', async () => { - const key = - ERC725YDataKeys.LSP6['AddressPermissions:AllowedCalls'] + - beneficiary.address.substring(2); - - const value = combineAllowedCalls( - [CALLTYPE.VALUE, CALLTYPE.VALUE], - [ - '0xcafecafecafecafecafecafecafecafecafecafe', - '0xca11ca11ca11ca11ca11ca11ca11ca11ca11ca11', - ], - ['0xffffffff', '0xffffffff'], - ['0xffffffff', '0xffffffff'], - ); - - const payload = context.universalProfile.interface.encodeFunctionData('setData', [ - key, - value, - ]); - - await expect(context.keyManager.connect(canOnlyAddController).execute(payload)) - .to.be.revertedWithCustomError(context.keyManager, 'NotAuthorised') - .withArgs(canOnlyAddController.address, 'EDITPERMISSIONS'); - }); - - it('should fail with NotAuthorised -> when beneficiary address had an invalid bytes28[CompatBytesArray]', async () => { - const key = - ERC725YDataKeys.LSP6['AddressPermissions:AllowedCalls'] + - invalidBeneficiary.address.substring(2); - - // try to set for the invalidBeneficiary some allowed calls - // that allow it to transfer value to addresses 0xcafe... and 0xbeef... - const value = combineAllowedCalls( - [CALLTYPE.VALUE, CALLTYPE.VALUE], - [ - '0xcafecafecafecafecafecafecafecafecafecafe', - '0xbeefbeefbeefbeefbeefbeefbeefbeefbeefbeef', - ], - ['0xffffffff', '0xffffffff'], - ['0xffffffff', '0xffffffff'], - ); - - const payload = context.universalProfile.interface.encodeFunctionData('setData', [ - key, - value, - ]); - - await expect(context.keyManager.connect(canOnlyAddController).execute(payload)) - .to.be.revertedWithCustomError(context.keyManager, 'NotAuthorised') - .withArgs(canOnlyAddController.address, 'EDITPERMISSIONS'); - }); - - // even if the controller had some 00 bytes set as allowed calls, it is not considered as it does not have any allowed calls set - // but rather that its allowed calls are "disabled" - describe('when beneficiary (= controller) had 00 bytes set initially as allowed calls (e.g: allowed calls disabled)', () => { - it('should fail with NotAuthorised -> when beneficiary had 32 x 0 bytes set initially as allowed calls', async () => { + describe('when the controller to edit the AllowedCalls for has some permissions', () => { + it('should fail when trying to edit existing allowed addresses for an address', async () => { const key = ERC725YDataKeys.LSP6['AddressPermissions:AllowedCalls'] + - zero32Bytes.address.substring(2); + beneficiary.address.substring(2); const value = combineAllowedCalls( [CALLTYPE.VALUE, CALLTYPE.VALUE], [ '0xcafecafecafecafecafecafecafecafecafecafe', - '0xbeefbeefbeefbeefbeefbeefbeefbeefbeefbeef', + '0xca11ca11ca11ca11ca11ca11ca11ca11ca11ca11', ], ['0xffffffff', '0xffffffff'], ['0xffffffff', '0xffffffff'], @@ -241,11 +250,13 @@ export const shouldBehaveLikeSettingAllowedCalls = ( .withArgs(canOnlyAddController.address, 'EDITPERMISSIONS'); }); - it('should fail with NotAuthorised -> when beneficiary had 40 x 0 bytes set initially as allowed calls', async () => { + it('should fail with NotAuthorised -> when beneficiary address had an invalid bytes32[CompactBytesArray]', async () => { const key = ERC725YDataKeys.LSP6['AddressPermissions:AllowedCalls'] + - zero40Bytes.address.substring(2); + invalidBeneficiary.address.substring(2); + // try to set for the invalidBeneficiary some allowed calls + // that allow it to transfer value to addresses 0xcafe... and 0xbeef... const value = combineAllowedCalls( [CALLTYPE.VALUE, CALLTYPE.VALUE], [ @@ -265,36 +276,89 @@ export const shouldBehaveLikeSettingAllowedCalls = ( .to.be.revertedWithCustomError(context.keyManager, 'NotAuthorised') .withArgs(canOnlyAddController.address, 'EDITPERMISSIONS'); }); - }); - it('should pass when beneficiary had no values set under AddressPermissions:AllowedCalls:... + setting a valid bytes28[CompactBytesArray]', async () => { - const newController = ethers.Wallet.createRandom(); + describe('when beneficiary (= controller) had 00 bytes set initially as allowed calls (e.g: allowed calls disabled)', () => { + it('should fail with NotAuthorised -> when beneficiary had 32 x 0 bytes set initially as allowed calls', async () => { + const key = + ERC725YDataKeys.LSP6['AddressPermissions:AllowedCalls'] + + zero32Bytes.address.substring(2); + + const value = combineAllowedCalls( + [CALLTYPE.VALUE, CALLTYPE.VALUE], + [ + '0xcafecafecafecafecafecafecafecafecafecafe', + '0xbeefbeefbeefbeefbeefbeefbeefbeefbeefbeef', + ], + ['0xffffffff', '0xffffffff'], + ['0xffffffff', '0xffffffff'], + ); + + const payload = context.universalProfile.interface.encodeFunctionData('setData', [ + key, + value, + ]); + + await expect(context.keyManager.connect(canOnlyAddController).execute(payload)) + .to.be.revertedWithCustomError(context.keyManager, 'NotAuthorised') + .withArgs(canOnlyAddController.address, 'EDITPERMISSIONS'); + }); + + it('should fail with NotAuthorised -> when beneficiary had 40 x 0 bytes set initially as allowed calls', async () => { + const key = + ERC725YDataKeys.LSP6['AddressPermissions:AllowedCalls'] + + zero40Bytes.address.substring(2); + + const value = combineAllowedCalls( + [CALLTYPE.VALUE, CALLTYPE.VALUE], + [ + '0xcafecafecafecafecafecafecafecafecafecafe', + '0xbeefbeefbeefbeefbeefbeefbeefbeefbeefbeef', + ], + ['0xffffffff', '0xffffffff'], + ['0xffffffff', '0xffffffff'], + ); + + const payload = context.universalProfile.interface.encodeFunctionData('setData', [ + key, + value, + ]); + + await expect(context.keyManager.connect(canOnlyAddController).execute(payload)) + .to.be.revertedWithCustomError(context.keyManager, 'NotAuthorised') + .withArgs(canOnlyAddController.address, 'EDITPERMISSIONS'); + }); + }); - const key = - ERC725YDataKeys.LSP6['AddressPermissions:AllowedCalls'] + newController.address.substr(2); + it('should pass when beneficiary had no values set under AddressPermissions:AllowedCalls:... + setting a valid bytes28[CompactBytesArray]', async () => { + const newController = ethers.Wallet.createRandom(); - // set for the newController some allowed calls - // that allow it to transfer value to addresses 0xcafe... and 0xbeef... - const value = combineAllowedCalls( - [CALLTYPE.VALUE, CALLTYPE.VALUE], - [ - '0xcafecafecafecafecafecafecafecafecafecafe', - '0xbeefbeefbeefbeefbeefbeefbeefbeefbeefbeef', - ], - ['0xffffffff', '0xffffffff'], - ['0xffffffff', '0xffffffff'], - ); + const key = + ERC725YDataKeys.LSP6['AddressPermissions:AllowedCalls'] + + newController.address.substr(2); - const payload = context.universalProfile.interface.encodeFunctionData('setData', [ - key, - value, - ]); + // set for the newController some allowed calls + // that allow it to transfer value to addresses 0xcafe... and 0xbeef... + const value = combineAllowedCalls( + [CALLTYPE.VALUE, CALLTYPE.VALUE], + [ + '0xcafecafecafecafecafecafecafecafecafecafe', + '0xbeefbeefbeefbeefbeefbeefbeefbeefbeefbeef', + ], + ['0xffffffff', '0xffffffff'], + ['0xffffffff', '0xffffffff'], + ); - await context.keyManager.connect(canOnlyAddController).execute(payload); + const payload = context.universalProfile.interface.encodeFunctionData('setData', [ + key, + value, + ]); - // prettier-ignore - const result = await context.universalProfile.getData(key); - expect(result).to.equal(value); + await context.keyManager.connect(canOnlyAddController).execute(payload); + + // prettier-ignore + const result = await context.universalProfile.getData(key); + expect(result).to.equal(value); + }); }); describe('when setting an invalid bytes28[CompactBytesArray] for a new beneficiary', () => { @@ -540,14 +604,16 @@ export const shouldBehaveLikeSettingAllowedCalls = ( zero32Bytes = context.accounts[5]; zero40Bytes = context.accounts[6]; + // prettier-ignore const permissionKeys = [ - ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + - canOnlyAddController.address.substring(2), - ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + - canOnlyEditPermissions.address.substring(2), + ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + canOnlyAddController.address.substring(2), + ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + canOnlyEditPermissions.address.substring(2), + ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + beneficiary.address.substring(2), + ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + invalidBeneficiary.address.substring(2), + ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + zero32Bytes.address.substring(2), + ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + zero40Bytes.address.substring(2), ERC725YDataKeys.LSP6['AddressPermissions:AllowedCalls'] + beneficiary.address.substring(2), - ERC725YDataKeys.LSP6['AddressPermissions:AllowedCalls'] + - invalidBeneficiary.address.substring(2), + ERC725YDataKeys.LSP6['AddressPermissions:AllowedCalls'] + invalidBeneficiary.address.substring(2), ERC725YDataKeys.LSP6['AddressPermissions:AllowedCalls'] + zero32Bytes.address.substring(2), ERC725YDataKeys.LSP6['AddressPermissions:AllowedCalls'] + zero40Bytes.address.substring(2), ]; @@ -555,6 +621,10 @@ export const shouldBehaveLikeSettingAllowedCalls = ( const permissionValues = [ PERMISSIONS.ADDCONTROLLER, PERMISSIONS.EDITPERMISSIONS, + PERMISSIONS.CALL, + PERMISSIONS.CALL, + PERMISSIONS.CALL, + PERMISSIONS.CALL, combineAllowedCalls( [CALLTYPE.CALL, CALLTYPE.CALL], [ @@ -573,74 +643,21 @@ export const shouldBehaveLikeSettingAllowedCalls = ( }); describe('when caller has permission ADDCONTROLLER', () => { - it('should fail when trying to edit existing allowed functions for an address', async () => { - const key = - ERC725YDataKeys.LSP6['AddressPermissions:AllowedCalls'] + - beneficiary.address.substring(2); - - const value = combineAllowedCalls( - // allow beneficiary to make a CALL to only function selectors 0xcafecafe and 0xf00df00d - [CALLTYPE.CALL, CALLTYPE.CALL], - [ - '0xffffffffffffffffffffffffffffffffffffffff', - '0xffffffffffffffffffffffffffffffffffffffff', - ], - ['0xffffffff', '0xffffffff'], - ['0xcafecafe', '0xf00df00d'], - ); - - const payload = context.universalProfile.interface.encodeFunctionData('setData', [ - key, - value, - ]); - - await expect(context.keyManager.connect(canOnlyAddController).execute(payload)) - .to.be.revertedWithCustomError(context.keyManager, 'NotAuthorised') - .withArgs(canOnlyAddController.address, 'EDITPERMISSIONS'); - }); - - it('should fail with NotAuthorised -> when beneficiary address had an invalid bytes28[CompactBytesArray] initially', async () => { - const key = - ERC725YDataKeys.LSP6['AddressPermissions:AllowedCalls'] + - invalidBeneficiary.address.substring(2); - - const value = combineAllowedCalls( - // allow beneficiary to make a CALL to only function selectors 0xcafecafe and 0xf00df00d - [CALLTYPE.CALL, CALLTYPE.CALL], - [ - '0xffffffffffffffffffffffffffffffffffffffff', - '0xffffffffffffffffffffffffffffffffffffffff', - ], - ['0xffffffff', '0xffffffff'], - ['0xcafecafe', '0xf00df00d'], - ); - - const payload = context.universalProfile.interface.encodeFunctionData('setData', [ - key, - value, - ]); - - await expect(context.keyManager.connect(canOnlyAddController).execute(payload)) - .to.be.revertedWithCustomError(context.keyManager, 'NotAuthorised') - .withArgs(canOnlyAddController.address, 'EDITPERMISSIONS'); - }); - - // even if the controller had some 00 bytes set as allowed calls, it is not considered as it does not have any allowed calls set - // but rather that its allowed calls are "disabled" - describe('when beneficiary (= controller) had 00 bytes set initially as allowed calls (e.g: allowed calls disabled)', () => { - it('should fail with NotAuthorised -> when beneficiary had 32 x 0 bytes set initially as allowed calls', async () => { + describe('when controller to edit Allowed Calls for has some permissions set', () => { + it('should fail when trying to edit existing allowed functions for an address', async () => { const key = ERC725YDataKeys.LSP6['AddressPermissions:AllowedCalls'] + - zero32Bytes.address.substring(2); + beneficiary.address.substring(2); const value = combineAllowedCalls( + // allow beneficiary to make a CALL to only function selectors 0xcafecafe and 0xf00df00d [CALLTYPE.CALL, CALLTYPE.CALL], [ '0xffffffffffffffffffffffffffffffffffffffff', '0xffffffffffffffffffffffffffffffffffffffff', ], ['0xffffffff', '0xffffffff'], - ['0xcafecafe', '0xca11ca11'], + ['0xcafecafe', '0xf00df00d'], ); const payload = context.universalProfile.interface.encodeFunctionData('setData', [ @@ -653,19 +670,20 @@ export const shouldBehaveLikeSettingAllowedCalls = ( .withArgs(canOnlyAddController.address, 'EDITPERMISSIONS'); }); - it('should fail with NotAuthorised -> when beneficiary had 40 x 0 bytes set initially as allowed calls', async () => { + it('should fail with NotAuthorised -> when beneficiary address had an invalid bytes28[CompactBytesArray] initially', async () => { const key = ERC725YDataKeys.LSP6['AddressPermissions:AllowedCalls'] + - zero40Bytes.address.substring(2); + invalidBeneficiary.address.substring(2); const value = combineAllowedCalls( + // allow beneficiary to make a CALL to only function selectors 0xcafecafe and 0xf00df00d [CALLTYPE.CALL, CALLTYPE.CALL], [ '0xffffffffffffffffffffffffffffffffffffffff', '0xffffffffffffffffffffffffffffffffffffffff', ], ['0xffffffff', '0xffffffff'], - ['0xcafecafe', '0xca11ca11'], + ['0xcafecafe', '0xf00df00d'], ); const payload = context.universalProfile.interface.encodeFunctionData('setData', [ @@ -677,35 +695,90 @@ export const shouldBehaveLikeSettingAllowedCalls = ( .to.be.revertedWithCustomError(context.keyManager, 'NotAuthorised') .withArgs(canOnlyAddController.address, 'EDITPERMISSIONS'); }); - }); - it('should pass when beneficiary had no values set under AddressPermissions:AllowedCalls:... + setting a valid bytes28[CompactBytesArray]', async () => { - const newController = ethers.Wallet.createRandom(); + // even if the controller had some 00 bytes set as allowed calls, it is not considered as it does not have any allowed calls set + // but rather that its allowed calls are "disabled" + describe('when beneficiary (= controller) had 00 bytes set initially as allowed calls (e.g: allowed calls disabled)', () => { + it('should fail with NotAuthorised -> when beneficiary had 32 x 0 bytes set initially as allowed calls', async () => { + const key = + ERC725YDataKeys.LSP6['AddressPermissions:AllowedCalls'] + + zero32Bytes.address.substring(2); + + const value = combineAllowedCalls( + [CALLTYPE.CALL, CALLTYPE.CALL], + [ + '0xffffffffffffffffffffffffffffffffffffffff', + '0xffffffffffffffffffffffffffffffffffffffff', + ], + ['0xffffffff', '0xffffffff'], + ['0xcafecafe', '0xca11ca11'], + ); + + const payload = context.universalProfile.interface.encodeFunctionData('setData', [ + key, + value, + ]); + + await expect(context.keyManager.connect(canOnlyAddController).execute(payload)) + .to.be.revertedWithCustomError(context.keyManager, 'NotAuthorised') + .withArgs(canOnlyAddController.address, 'EDITPERMISSIONS'); + }); + + it('should fail with NotAuthorised -> when beneficiary had 40 x 0 bytes set initially as allowed calls', async () => { + const key = + ERC725YDataKeys.LSP6['AddressPermissions:AllowedCalls'] + + zero40Bytes.address.substring(2); + + const value = combineAllowedCalls( + [CALLTYPE.CALL, CALLTYPE.CALL], + [ + '0xffffffffffffffffffffffffffffffffffffffff', + '0xffffffffffffffffffffffffffffffffffffffff', + ], + ['0xffffffff', '0xffffffff'], + ['0xcafecafe', '0xca11ca11'], + ); + + const payload = context.universalProfile.interface.encodeFunctionData('setData', [ + key, + value, + ]); + + await expect(context.keyManager.connect(canOnlyAddController).execute(payload)) + .to.be.revertedWithCustomError(context.keyManager, 'NotAuthorised') + .withArgs(canOnlyAddController.address, 'EDITPERMISSIONS'); + }); + }); - const key = - ERC725YDataKeys.LSP6['AddressPermissions:AllowedCalls'] + newController.address.substr(2); + it('should pass when beneficiary had no values set under AddressPermissions:AllowedCalls:... + setting a valid bytes28[CompactBytesArray]', async () => { + const newController = ethers.Wallet.createRandom(); - const value = combineAllowedCalls( - // allow beneficiary to CALL only function selectors 0xcafecafe and 0xf00df00d - [CALLTYPE.CALL, CALLTYPE.CALL], - [ - '0xffffffffffffffffffffffffffffffffffffffff', - '0xffffffffffffffffffffffffffffffffffffffff', - ], - ['0xffffffff', '0xffffffff'], - ['0xcafecafe', '0xf00df00d'], - ); + const key = + ERC725YDataKeys.LSP6['AddressPermissions:AllowedCalls'] + + newController.address.substr(2); - const payload = context.universalProfile.interface.encodeFunctionData('setData', [ - key, - value, - ]); + const value = combineAllowedCalls( + // allow beneficiary to CALL only function selectors 0xcafecafe and 0xf00df00d + [CALLTYPE.CALL, CALLTYPE.CALL], + [ + '0xffffffffffffffffffffffffffffffffffffffff', + '0xffffffffffffffffffffffffffffffffffffffff', + ], + ['0xffffffff', '0xffffffff'], + ['0xcafecafe', '0xf00df00d'], + ); - await context.keyManager.connect(canOnlyAddController).execute(payload); + const payload = context.universalProfile.interface.encodeFunctionData('setData', [ + key, + value, + ]); - // prettier-ignore - const result = await context.universalProfile.getData(key); - expect(result).to.equal(value); + await context.keyManager.connect(canOnlyAddController).execute(payload); + + // prettier-ignore + const result = await context.universalProfile.getData(key); + expect(result).to.equal(value); + }); }); describe('when setting an invalid bytes28[CompactBytesArray] for a new beneficiary', () => { @@ -750,97 +823,42 @@ export const shouldBehaveLikeSettingAllowedCalls = ( }); describe('when caller has EDITPERMISSIONS', () => { - it('should fail when beneficiary had no values set under AddressPermissions:AllowedCalls:...', async () => { - const newController = ethers.Wallet.createRandom(); - - const key = - ERC725YDataKeys.LSP6['AddressPermissions:AllowedCalls'] + newController.address.substr(2); + describe('when controller to edit Allowed Calls for has some permissions set', () => { + it('should fail when beneficiary had no values set under AddressPermissions:AllowedCalls:...', async () => { + const newController = ethers.Wallet.createRandom(); - const value = combineAllowedCalls( - // allow beneficiary to CALL only function selectors 0xcafecafe and 0xbeefbeef - [CALLTYPE.CALL, CALLTYPE.CALL], - [ - '0xffffffffffffffffffffffffffffffffffffffff', - '0xffffffffffffffffffffffffffffffffffffffff', - ], - ['0xffffffff', '0xffffffff'], - ['0xcafecafe', '0xbeefbeef'], - ); + const key = + ERC725YDataKeys.LSP6['AddressPermissions:AllowedCalls'] + + newController.address.substr(2); - const payload = context.universalProfile.interface.encodeFunctionData('setData', [ - key, - value, - ]); - - await expect(context.keyManager.connect(canOnlyEditPermissions).execute(payload)) - .to.be.revertedWithCustomError(context.keyManager, 'NotAuthorised') - .withArgs(canOnlyEditPermissions.address, 'ADDCONTROLLER'); - }); - - it('should pass when trying to edit existing allowed bytes4 selectors under ANY:ANY:', async () => { - const key = - ERC725YDataKeys.LSP6['AddressPermissions:AllowedCalls'] + - beneficiary.address.substring(2); - - const value = combineAllowedCalls( - // allow beneficiary to CALL only function selectors 0xcafecafe and 0xbeefbeef - [CALLTYPE.CALL, CALLTYPE.CALL], - [ - '0xffffffffffffffffffffffffffffffffffffffff', - '0xffffffffffffffffffffffffffffffffffffffff', - ], - ['0xffffffff', '0xffffffff'], - ['0xcafecafe', '0xbeefbeef'], - ); - - const payload = context.universalProfile.interface.encodeFunctionData('setData', [ - key, - value, - ]); - - await context.keyManager.connect(canOnlyEditPermissions).execute(payload); - - // prettier-ignore - const result = await context.universalProfile.getData(key); - expect(result).to.equal(value); - }); - - it('should pass when address had an invalid bytes28[CompactBytesArray] initially', async () => { - const key = - ERC725YDataKeys.LSP6['AddressPermissions:AllowedCalls'] + - invalidBeneficiary.address.substring(2); - - const value = combineAllowedCalls( - // allow beneficiary to CALL only function selectors 0xcafecafe and 0xbeefbeef - [CALLTYPE.CALL, CALLTYPE.CALL], - [ - '0xffffffffffffffffffffffffffffffffffffffff', - '0xffffffffffffffffffffffffffffffffffffffff', - ], - ['0xffffffff', '0xffffffff'], - ['0xcafecafe', '0xbeefbeef'], - ); - - const payload = context.universalProfile.interface.encodeFunctionData('setData', [ - key, - value, - ]); + const value = combineAllowedCalls( + // allow beneficiary to CALL only function selectors 0xcafecafe and 0xbeefbeef + [CALLTYPE.CALL, CALLTYPE.CALL], + [ + '0xffffffffffffffffffffffffffffffffffffffff', + '0xffffffffffffffffffffffffffffffffffffffff', + ], + ['0xffffffff', '0xffffffff'], + ['0xcafecafe', '0xbeefbeef'], + ); - await context.keyManager.connect(canOnlyEditPermissions).execute(payload); + const payload = context.universalProfile.interface.encodeFunctionData('setData', [ + key, + value, + ]); - const result = await context.universalProfile.getData(key); - expect(result).to.equal(value); - }); + await expect(context.keyManager.connect(canOnlyEditPermissions).execute(payload)) + .to.be.revertedWithCustomError(context.keyManager, 'NotAuthorised') + .withArgs(canOnlyEditPermissions.address, 'ADDCONTROLLER'); + }); - // even if the controller had some 00 bytes set as allowed calls, it is not considered as it does not have any allowed calls set - // but rather that its allowed calls are "disabled" - describe('when beneficiary (= controller) had 00 bytes set initially as allowed calls (e.g: allowed calls disabled)', () => { - it('should pass when address had 32 x 0 bytes set initially as allowed calls', async () => { + it('should pass when trying to edit existing allowed bytes4 selectors under ANY:ANY:', async () => { const key = ERC725YDataKeys.LSP6['AddressPermissions:AllowedCalls'] + - zero32Bytes.address.substring(2); + beneficiary.address.substring(2); const value = combineAllowedCalls( + // allow beneficiary to CALL only function selectors 0xcafecafe and 0xbeefbeef [CALLTYPE.CALL, CALLTYPE.CALL], [ '0xffffffffffffffffffffffffffffffffffffffff', @@ -857,16 +875,18 @@ export const shouldBehaveLikeSettingAllowedCalls = ( await context.keyManager.connect(canOnlyEditPermissions).execute(payload); + // prettier-ignore const result = await context.universalProfile.getData(key); expect(result).to.equal(value); }); - it('should pass when address had 40 x 0 bytes set initially as allowed functions', async () => { + it('should pass when address had an invalid bytes28[CompactBytesArray] initially', async () => { const key = ERC725YDataKeys.LSP6['AddressPermissions:AllowedCalls'] + - zero40Bytes.address.substring(2); + invalidBeneficiary.address.substring(2); const value = combineAllowedCalls( + // allow beneficiary to CALL only function selectors 0xcafecafe and 0xbeefbeef [CALLTYPE.CALL, CALLTYPE.CALL], [ '0xffffffffffffffffffffffffffffffffffffffff', @@ -883,10 +903,64 @@ export const shouldBehaveLikeSettingAllowedCalls = ( await context.keyManager.connect(canOnlyEditPermissions).execute(payload); - // prettier-ignore const result = await context.universalProfile.getData(key); expect(result).to.equal(value); }); + + describe('when beneficiary (= controller) had 00 bytes set initially as allowed calls (e.g: allowed calls disabled)', () => { + it('should pass when address had 32 x 0 bytes set initially as allowed calls', async () => { + const key = + ERC725YDataKeys.LSP6['AddressPermissions:AllowedCalls'] + + zero32Bytes.address.substring(2); + + const value = combineAllowedCalls( + [CALLTYPE.CALL, CALLTYPE.CALL], + [ + '0xffffffffffffffffffffffffffffffffffffffff', + '0xffffffffffffffffffffffffffffffffffffffff', + ], + ['0xffffffff', '0xffffffff'], + ['0xcafecafe', '0xbeefbeef'], + ); + + const payload = context.universalProfile.interface.encodeFunctionData('setData', [ + key, + value, + ]); + + await context.keyManager.connect(canOnlyEditPermissions).execute(payload); + + const result = await context.universalProfile.getData(key); + expect(result).to.equal(value); + }); + + it('should pass when address had 40 x 0 bytes set initially as allowed functions', async () => { + const key = + ERC725YDataKeys.LSP6['AddressPermissions:AllowedCalls'] + + zero40Bytes.address.substring(2); + + const value = combineAllowedCalls( + [CALLTYPE.CALL, CALLTYPE.CALL], + [ + '0xffffffffffffffffffffffffffffffffffffffff', + '0xffffffffffffffffffffffffffffffffffffffff', + ], + ['0xffffffff', '0xffffffff'], + ['0xcafecafe', '0xbeefbeef'], + ); + + const payload = context.universalProfile.interface.encodeFunctionData('setData', [ + key, + value, + ]); + + await context.keyManager.connect(canOnlyEditPermissions).execute(payload); + + // prettier-ignore + const result = await context.universalProfile.getData(key); + expect(result).to.equal(value); + }); + }); }); describe('when changing the list of selectors in allowed calls from existing ANY:ANY: to an invalid value', () => { @@ -946,14 +1020,16 @@ export const shouldBehaveLikeSettingAllowedCalls = ( zero32Bytes = context.accounts[5]; zero40Bytes = context.accounts[6]; + // prettier-ignore const permissionKeys = [ - ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + - canOnlyAddController.address.substring(2), - ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + - canOnlyEditPermissions.address.substring(2), + ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + canOnlyAddController.address.substring(2), + ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + canOnlyEditPermissions.address.substring(2), + ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + beneficiary.address.substring(2), + ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + invalidBeneficiary.address.substring(2), + ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + zero32Bytes.address.substring(2), + ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + zero40Bytes.address.substring(2), ERC725YDataKeys.LSP6['AddressPermissions:AllowedCalls'] + beneficiary.address.substring(2), - ERC725YDataKeys.LSP6['AddressPermissions:AllowedCalls'] + - invalidBeneficiary.address.substring(2), + ERC725YDataKeys.LSP6['AddressPermissions:AllowedCalls'] + invalidBeneficiary.address.substring(2), ERC725YDataKeys.LSP6['AddressPermissions:AllowedCalls'] + zero32Bytes.address.substring(2), ERC725YDataKeys.LSP6['AddressPermissions:AllowedCalls'] + zero40Bytes.address.substring(2), ]; @@ -961,6 +1037,10 @@ export const shouldBehaveLikeSettingAllowedCalls = ( const permissionValues = [ PERMISSIONS.ADDCONTROLLER, PERMISSIONS.EDITPERMISSIONS, + PERMISSIONS.CALL, + PERMISSIONS.CALL, + PERMISSIONS.CALL, + PERMISSIONS.CALL, combineAllowedCalls( // allow beneficiary controller to CALL any functions // on any LSP7 or ERC20 contracts @@ -981,83 +1061,11 @@ export const shouldBehaveLikeSettingAllowedCalls = ( }); describe('when caller has ADDCONTROLLER', () => { - it('should fail when trying to edit existing allowed standards for an address', async () => { - const key = - ERC725YDataKeys.LSP6['AddressPermissions:AllowedCalls'] + - beneficiary.address.substring(2); - - const value = combineAllowedCalls( - [CALLTYPE.CALL, CALLTYPE.CALL, CALLTYPE.CALL, CALLTYPE.CALL], - [ - '0xffffffffffffffffffffffffffffffffffffffff', - '0xffffffffffffffffffffffffffffffffffffffff', - '0xffffffffffffffffffffffffffffffffffffffff', - '0xffffffffffffffffffffffffffffffffffffffff', - ], - [ - INTERFACE_IDS.LSP7DigitalAsset, - INTERFACE_IDS.ERC20, - // add NFT standards (new LSP8 + old ERC721) - // in the list of allowed calls for the beneficiary controller - // (in addition to token contracts LSP7 + ERC20) - INTERFACE_IDS.LSP8IdentifiableDigitalAsset, - INTERFACE_IDS.ERC721, - ], - ['0xffffffff', '0xffffffff', '0xffffffff', '0xffffffff'], - ); - - const payload = context.universalProfile.interface.encodeFunctionData('setData', [ - key, - value, - ]); - - await expect(context.keyManager.connect(canOnlyAddController).execute(payload)) - .to.be.revertedWithCustomError(context.keyManager, 'NotAuthorised') - .withArgs(canOnlyAddController.address, 'EDITPERMISSIONS'); - }); - - it('should fail with NotAuthorised -> when beneficiary address had an invalid bytes28[CompactBytesArray] initially', async () => { - const key = - ERC725YDataKeys.LSP6['AddressPermissions:AllowedCalls'] + - invalidBeneficiary.address.substring(2); - - const value = combineAllowedCalls( - [CALLTYPE.CALL, CALLTYPE.CALL, CALLTYPE.CALL, CALLTYPE.CALL], - [ - '0xffffffffffffffffffffffffffffffffffffffff', - '0xffffffffffffffffffffffffffffffffffffffff', - '0xffffffffffffffffffffffffffffffffffffffff', - '0xffffffffffffffffffffffffffffffffffffffff', - ], - [ - INTERFACE_IDS.LSP7DigitalAsset, - INTERFACE_IDS.ERC20, - // add NFT standards (new LSP8 + old ERC721) - // in the list of allowed calls for the beneficiary controller - // (in addition to token standards LSP7 + ERC20) - INTERFACE_IDS.LSP8IdentifiableDigitalAsset, - INTERFACE_IDS.ERC721, - ], - ['0xffffffff', '0xffffffff', '0xffffffff', '0xffffffff'], - ); - - const payload = context.universalProfile.interface.encodeFunctionData('setData', [ - key, - value, - ]); - - await expect(context.keyManager.connect(canOnlyAddController).execute(payload)) - .to.be.revertedWithCustomError(context.keyManager, 'NotAuthorised') - .withArgs(canOnlyAddController.address, 'EDITPERMISSIONS'); - }); - - // even if the controller had some 00 bytes set as allowed calls, it is not considered as it does not have any allowed calls set - // but rather that its allowed calls are "disabled" - describe('when beneficiary (= controller) had 00 bytes set initially as allowed calls (e.g: allowed calls disabled)', () => { - it('should fail with NotAuthorised -> when beneficiary had 32 x 0 bytes set initially as allowed calls', async () => { + describe('when controller to edit Allowed Calls for has some permissions', () => { + it('should fail when trying to edit existing allowed standards for an address', async () => { const key = ERC725YDataKeys.LSP6['AddressPermissions:AllowedCalls'] + - zero32Bytes.address.substring(2); + beneficiary.address.substring(2); const value = combineAllowedCalls( [CALLTYPE.CALL, CALLTYPE.CALL, CALLTYPE.CALL, CALLTYPE.CALL], @@ -1070,6 +1078,9 @@ export const shouldBehaveLikeSettingAllowedCalls = ( [ INTERFACE_IDS.LSP7DigitalAsset, INTERFACE_IDS.ERC20, + // add NFT standards (new LSP8 + old ERC721) + // in the list of allowed calls for the beneficiary controller + // (in addition to token contracts LSP7 + ERC20) INTERFACE_IDS.LSP8IdentifiableDigitalAsset, INTERFACE_IDS.ERC721, ], @@ -1086,10 +1097,10 @@ export const shouldBehaveLikeSettingAllowedCalls = ( .withArgs(canOnlyAddController.address, 'EDITPERMISSIONS'); }); - it('should fail with NotAuthorised -> when beneficiary had 40 x 0 bytes set initially as allowed calls', async () => { + it('should fail with NotAuthorised -> when beneficiary address had an invalid bytes28[CompactBytesArray] initially', async () => { const key = ERC725YDataKeys.LSP6['AddressPermissions:AllowedCalls'] + - zero40Bytes.address.substring(2); + invalidBeneficiary.address.substring(2); const value = combineAllowedCalls( [CALLTYPE.CALL, CALLTYPE.CALL, CALLTYPE.CALL, CALLTYPE.CALL], @@ -1102,6 +1113,9 @@ export const shouldBehaveLikeSettingAllowedCalls = ( [ INTERFACE_IDS.LSP7DigitalAsset, INTERFACE_IDS.ERC20, + // add NFT standards (new LSP8 + old ERC721) + // in the list of allowed calls for the beneficiary controller + // (in addition to token standards LSP7 + ERC20) INTERFACE_IDS.LSP8IdentifiableDigitalAsset, INTERFACE_IDS.ERC721, ], @@ -1117,44 +1131,113 @@ export const shouldBehaveLikeSettingAllowedCalls = ( .to.be.revertedWithCustomError(context.keyManager, 'NotAuthorised') .withArgs(canOnlyAddController.address, 'EDITPERMISSIONS'); }); - }); - it('should pass when beneficiary had no values set under AddressPermissions:AllowedCalls:... + setting a valid bytes28[CompactBytesArray]', async () => { - const newController = ethers.Wallet.createRandom(); + // even if the controller had some 00 bytes set as allowed calls, it is not considered as it does not have any allowed calls set + // but rather that its allowed calls are "disabled" + describe('when beneficiary (= controller) had 00 bytes set initially as allowed calls (e.g: allowed calls disabled)', () => { + it('should fail with NotAuthorised -> when beneficiary had 32 x 0 bytes set initially as allowed calls', async () => { + const key = + ERC725YDataKeys.LSP6['AddressPermissions:AllowedCalls'] + + zero32Bytes.address.substring(2); + + const value = combineAllowedCalls( + [CALLTYPE.CALL, CALLTYPE.CALL, CALLTYPE.CALL, CALLTYPE.CALL], + [ + '0xffffffffffffffffffffffffffffffffffffffff', + '0xffffffffffffffffffffffffffffffffffffffff', + '0xffffffffffffffffffffffffffffffffffffffff', + '0xffffffffffffffffffffffffffffffffffffffff', + ], + [ + INTERFACE_IDS.LSP7DigitalAsset, + INTERFACE_IDS.ERC20, + INTERFACE_IDS.LSP8IdentifiableDigitalAsset, + INTERFACE_IDS.ERC721, + ], + ['0xffffffff', '0xffffffff', '0xffffffff', '0xffffffff'], + ); + + const payload = context.universalProfile.interface.encodeFunctionData('setData', [ + key, + value, + ]); + + await expect(context.keyManager.connect(canOnlyAddController).execute(payload)) + .to.be.revertedWithCustomError(context.keyManager, 'NotAuthorised') + .withArgs(canOnlyAddController.address, 'EDITPERMISSIONS'); + }); + + it('should fail with NotAuthorised -> when beneficiary had 40 x 0 bytes set initially as allowed calls', async () => { + const key = + ERC725YDataKeys.LSP6['AddressPermissions:AllowedCalls'] + + zero40Bytes.address.substring(2); + + const value = combineAllowedCalls( + [CALLTYPE.CALL, CALLTYPE.CALL, CALLTYPE.CALL, CALLTYPE.CALL], + [ + '0xffffffffffffffffffffffffffffffffffffffff', + '0xffffffffffffffffffffffffffffffffffffffff', + '0xffffffffffffffffffffffffffffffffffffffff', + '0xffffffffffffffffffffffffffffffffffffffff', + ], + [ + INTERFACE_IDS.LSP7DigitalAsset, + INTERFACE_IDS.ERC20, + INTERFACE_IDS.LSP8IdentifiableDigitalAsset, + INTERFACE_IDS.ERC721, + ], + ['0xffffffff', '0xffffffff', '0xffffffff', '0xffffffff'], + ); + + const payload = context.universalProfile.interface.encodeFunctionData('setData', [ + key, + value, + ]); + + await expect(context.keyManager.connect(canOnlyAddController).execute(payload)) + .to.be.revertedWithCustomError(context.keyManager, 'NotAuthorised') + .withArgs(canOnlyAddController.address, 'EDITPERMISSIONS'); + }); + }); - const key = - ERC725YDataKeys.LSP6['AddressPermissions:AllowedCalls'] + newController.address.substr(2); + it('should pass when beneficiary had no values set under AddressPermissions:AllowedCalls:... + setting a valid bytes28[CompactBytesArray]', async () => { + const newController = ethers.Wallet.createRandom(); - const value = combineAllowedCalls( - [CALLTYPE.CALL, CALLTYPE.CALL, CALLTYPE.CALL, CALLTYPE.CALL], - [ - '0xffffffffffffffffffffffffffffffffffffffff', - '0xffffffffffffffffffffffffffffffffffffffff', - '0xffffffffffffffffffffffffffffffffffffffff', - '0xffffffffffffffffffffffffffffffffffffffff', - ], - [ - INTERFACE_IDS.LSP7DigitalAsset, - INTERFACE_IDS.ERC20, - // add NFT standards (new LSP8 + old ERC721) - // in the list of allowed calls for the beneficiary controller - // (in addition to token standards LSP7 + ERC20) - INTERFACE_IDS.LSP8IdentifiableDigitalAsset, - INTERFACE_IDS.ERC721, - ], - ['0xffffffff', '0xffffffff', '0xffffffff', '0xffffffff'], - ); + const key = + ERC725YDataKeys.LSP6['AddressPermissions:AllowedCalls'] + + newController.address.substr(2); - const payload = context.universalProfile.interface.encodeFunctionData('setData', [ - key, - value, - ]); + const value = combineAllowedCalls( + [CALLTYPE.CALL, CALLTYPE.CALL, CALLTYPE.CALL, CALLTYPE.CALL], + [ + '0xffffffffffffffffffffffffffffffffffffffff', + '0xffffffffffffffffffffffffffffffffffffffff', + '0xffffffffffffffffffffffffffffffffffffffff', + '0xffffffffffffffffffffffffffffffffffffffff', + ], + [ + INTERFACE_IDS.LSP7DigitalAsset, + INTERFACE_IDS.ERC20, + // add NFT standards (new LSP8 + old ERC721) + // in the list of allowed calls for the beneficiary controller + // (in addition to token standards LSP7 + ERC20) + INTERFACE_IDS.LSP8IdentifiableDigitalAsset, + INTERFACE_IDS.ERC721, + ], + ['0xffffffff', '0xffffffff', '0xffffffff', '0xffffffff'], + ); - await context.keyManager.connect(canOnlyAddController).execute(payload); + const payload = context.universalProfile.interface.encodeFunctionData('setData', [ + key, + value, + ]); - // prettier-ignore - const result = await context.universalProfile.getData(key); - expect(result).to.equal(value); + await context.keyManager.connect(canOnlyAddController).execute(payload); + + // prettier-ignore + const result = await context.universalProfile.getData(key); + expect(result).to.equal(value); + }); }); describe('when setting an invalid bytes28[CompactBytesArray] of allowed calls for a new beneficiary', () => { @@ -1199,114 +1282,60 @@ export const shouldBehaveLikeSettingAllowedCalls = ( }); describe('when caller has EDITPERMISSIONS', () => { - it('should fail when beneficiary had no values set under AddressPermissions:AllowedCalls:...', async () => { - const newController = ethers.Wallet.createRandom(); - - const key = - ERC725YDataKeys.LSP6['AddressPermissions:AllowedCalls'] + newController.address.substr(2); - - const value = combineAllowedCalls( - [CALLTYPE.CALL, CALLTYPE.CALL], - [ - '0xffffffffffffffffffffffffffffffffffffffff', - '0xffffffffffffffffffffffffffffffffffffffff', - ], - // try to add in the list of allowed calls for the beneficiary controller - // the rights to CALL any LSP7 or ERC20 token contract - // (NB: just the AllowedCalls, not the permission CALL) - [INTERFACE_IDS.LSP7DigitalAsset, INTERFACE_IDS.ERC20], - ['0xffffffff', '0xffffffff'], - ); - - const payload = context.universalProfile.interface.encodeFunctionData('setData', [ - key, - value, - ]); - - await expect(context.keyManager.connect(canOnlyEditPermissions).execute(payload)) - .to.be.revertedWithCustomError(context.keyManager, 'NotAuthorised') - .withArgs(canOnlyEditPermissions.address, 'ADDCONTROLLER'); - }); - - it('should pass when trying to edit existing allowed standards for an address', async () => { - const key = - ERC725YDataKeys.LSP6['AddressPermissions:AllowedCalls'] + - beneficiary.address.substring(2); - - const value = combineAllowedCalls( - [CALLTYPE.CALL, CALLTYPE.CALL, CALLTYPE.CALL, CALLTYPE.CALL], - [ - '0xffffffffffffffffffffffffffffffffffffffff', - '0xffffffffffffffffffffffffffffffffffffffff', - '0xffffffffffffffffffffffffffffffffffffffff', - '0xffffffffffffffffffffffffffffffffffffffff', - ], - [ - INTERFACE_IDS.LSP7DigitalAsset, - INTERFACE_IDS.ERC20, - // add NFT standards (new LSP8 + old ERC721) - // in the list of allowed calls for the beneficiary controller - // (in addition to token standards LSP7 + ERC20) - INTERFACE_IDS.LSP8IdentifiableDigitalAsset, - INTERFACE_IDS.ERC721, - ], - ['0xffffffff', '0xffffffff', '0xffffffff', '0xffffffff'], - ); - - const payload = context.universalProfile.interface.encodeFunctionData('setData', [ - key, - value, - ]); - - await context.keyManager.connect(canOnlyEditPermissions).execute(payload); - - // prettier-ignore - const result = await context.universalProfile.getData(key); - expect(result).to.equal(value); - }); - - it('should pass when address had an invalid bytes28[CompactBytesArray] initially', async () => { - const key = - ERC725YDataKeys.LSP6['AddressPermissions:AllowedCalls'] + - invalidBeneficiary.address.substring(2); + describe('when controller to edit Allowed Calls for has some permissions', () => { + it('should fail when beneficiary had no values set under AddressPermissions:AllowedCalls:...', async () => { + const newController = ethers.Wallet.createRandom(); - const value = combineAllowedCalls( - [CALLTYPE.CALL, CALLTYPE.CALL], - [ - '0xffffffffffffffffffffffffffffffffffffffff', - '0xffffffffffffffffffffffffffffffffffffffff', - ], - [INTERFACE_IDS.LSP7DigitalAsset, INTERFACE_IDS.ERC20], - ['0xffffffff', '0xffffffff'], - ); + const key = + ERC725YDataKeys.LSP6['AddressPermissions:AllowedCalls'] + + newController.address.substr(2); - const payload = context.universalProfile.interface.encodeFunctionData('setData', [ - key, - value, - ]); + const value = combineAllowedCalls( + [CALLTYPE.CALL, CALLTYPE.CALL], + [ + '0xffffffffffffffffffffffffffffffffffffffff', + '0xffffffffffffffffffffffffffffffffffffffff', + ], + // try to add in the list of allowed calls for the beneficiary controller + // the rights to CALL any LSP7 or ERC20 token contract + // (NB: just the AllowedCalls, not the permission CALL) + [INTERFACE_IDS.LSP7DigitalAsset, INTERFACE_IDS.ERC20], + ['0xffffffff', '0xffffffff'], + ); - await context.keyManager.connect(canOnlyEditPermissions).execute(payload); + const payload = context.universalProfile.interface.encodeFunctionData('setData', [ + key, + value, + ]); - const result = await context.universalProfile.getData(key); - expect(result).to.equal(value); - }); + await expect(context.keyManager.connect(canOnlyEditPermissions).execute(payload)) + .to.be.revertedWithCustomError(context.keyManager, 'NotAuthorised') + .withArgs(canOnlyEditPermissions.address, 'ADDCONTROLLER'); + }); - // even if the controller had some 00 bytes set as allowed calls, it is not considered as it does not have any allowed calls set - // but rather that its allowed calls are "disabled" - describe('when beneficiary (= controller) had 00 bytes set initially as allowed calls (e.g: allowed calls disabled)', () => { - it('should pass when address had 32 x 0 bytes set initially as allowed calls', async () => { + it('should pass when trying to edit existing allowed standards for an address', async () => { const key = ERC725YDataKeys.LSP6['AddressPermissions:AllowedCalls'] + - zero32Bytes.address.substring(2); + beneficiary.address.substring(2); const value = combineAllowedCalls( - [CALLTYPE.CALL, CALLTYPE.CALL], + [CALLTYPE.CALL, CALLTYPE.CALL, CALLTYPE.CALL, CALLTYPE.CALL], [ '0xffffffffffffffffffffffffffffffffffffffff', '0xffffffffffffffffffffffffffffffffffffffff', + '0xffffffffffffffffffffffffffffffffffffffff', + '0xffffffffffffffffffffffffffffffffffffffff', ], - [INTERFACE_IDS.LSP7DigitalAsset, INTERFACE_IDS.ERC20], - ['0xffffffff', '0xffffffff'], + [ + INTERFACE_IDS.LSP7DigitalAsset, + INTERFACE_IDS.ERC20, + // add NFT standards (new LSP8 + old ERC721) + // in the list of allowed calls for the beneficiary controller + // (in addition to token standards LSP7 + ERC20) + INTERFACE_IDS.LSP8IdentifiableDigitalAsset, + INTERFACE_IDS.ERC721, + ], + ['0xffffffff', '0xffffffff', '0xffffffff', '0xffffffff'], ); const payload = context.universalProfile.interface.encodeFunctionData('setData', [ @@ -1316,14 +1345,15 @@ export const shouldBehaveLikeSettingAllowedCalls = ( await context.keyManager.connect(canOnlyEditPermissions).execute(payload); + // prettier-ignore const result = await context.universalProfile.getData(key); expect(result).to.equal(value); }); - it('should pass when address had 40 x 0 bytes set initially as allowed calls', async () => { + it('should pass when address had an invalid bytes28[CompactBytesArray] initially', async () => { const key = ERC725YDataKeys.LSP6['AddressPermissions:AllowedCalls'] + - zero40Bytes.address.substring(2); + invalidBeneficiary.address.substring(2); const value = combineAllowedCalls( [CALLTYPE.CALL, CALLTYPE.CALL], @@ -1342,10 +1372,66 @@ export const shouldBehaveLikeSettingAllowedCalls = ( await context.keyManager.connect(canOnlyEditPermissions).execute(payload); - // prettier-ignore const result = await context.universalProfile.getData(key); expect(result).to.equal(value); }); + + // even if the controller had some 00 bytes set as allowed calls, it is not considered as it does not have any allowed calls set + // but rather that its allowed calls are "disabled" + describe('when beneficiary (= controller) had 00 bytes set initially as allowed calls (e.g: allowed calls disabled)', () => { + it('should pass when address had 32 x 0 bytes set initially as allowed calls', async () => { + const key = + ERC725YDataKeys.LSP6['AddressPermissions:AllowedCalls'] + + zero32Bytes.address.substring(2); + + const value = combineAllowedCalls( + [CALLTYPE.CALL, CALLTYPE.CALL], + [ + '0xffffffffffffffffffffffffffffffffffffffff', + '0xffffffffffffffffffffffffffffffffffffffff', + ], + [INTERFACE_IDS.LSP7DigitalAsset, INTERFACE_IDS.ERC20], + ['0xffffffff', '0xffffffff'], + ); + + const payload = context.universalProfile.interface.encodeFunctionData('setData', [ + key, + value, + ]); + + await context.keyManager.connect(canOnlyEditPermissions).execute(payload); + + const result = await context.universalProfile.getData(key); + expect(result).to.equal(value); + }); + + it('should pass when address had 40 x 0 bytes set initially as allowed calls', async () => { + const key = + ERC725YDataKeys.LSP6['AddressPermissions:AllowedCalls'] + + zero40Bytes.address.substring(2); + + const value = combineAllowedCalls( + [CALLTYPE.CALL, CALLTYPE.CALL], + [ + '0xffffffffffffffffffffffffffffffffffffffff', + '0xffffffffffffffffffffffffffffffffffffffff', + ], + [INTERFACE_IDS.LSP7DigitalAsset, INTERFACE_IDS.ERC20], + ['0xffffffff', '0xffffffff'], + ); + + const payload = context.universalProfile.interface.encodeFunctionData('setData', [ + key, + value, + ]); + + await context.keyManager.connect(canOnlyEditPermissions).execute(payload); + + // prettier-ignore + const result = await context.universalProfile.getData(key); + expect(result).to.equal(value); + }); + }); }); describe('when changing the list of interface IDs in allowed calls :ANY:ANY to an invalid value', () => { diff --git a/tests/LSP6KeyManager/SetPermissions/SetAllowedERC725YDataKeys.test.ts b/tests/LSP6KeyManager/SetPermissions/SetAllowedERC725YDataKeys.test.ts index 848987cb8..f39dcccad 100644 --- a/tests/LSP6KeyManager/SetPermissions/SetAllowedERC725YDataKeys.test.ts +++ b/tests/LSP6KeyManager/SetPermissions/SetAllowedERC725YDataKeys.test.ts @@ -18,7 +18,8 @@ export const shouldBehaveLikeSetAllowedERC725YDataKeys = ( describe('setting Allowed ERC725YDataKeys', () => { let canOnlyAddController: SignerWithAddress, canOnlyEditPermissions: SignerWithAddress; - let beneficiary: SignerWithAddress, + let beneficiaryWithPermissions: SignerWithAddress, + beneficiaryNoPermissions: SignerWithAddress, invalidBeneficiary: SignerWithAddress, zero32Bytes: SignerWithAddress, zero40Bytes: SignerWithAddress; @@ -29,34 +30,43 @@ export const shouldBehaveLikeSetAllowedERC725YDataKeys = ( canOnlyAddController = context.accounts[1]; canOnlyEditPermissions = context.accounts[2]; - beneficiary = context.accounts[3]; - invalidBeneficiary = context.accounts[4]; - zero32Bytes = context.accounts[5]; - zero40Bytes = context.accounts[6]; + beneficiaryWithPermissions = context.accounts[3]; + beneficiaryNoPermissions = context.accounts[4]; + invalidBeneficiary = context.accounts[5]; + zero32Bytes = context.accounts[6]; + zero40Bytes = context.accounts[7]; + // prettier-ignore const permissionKeys = [ - ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + - canOnlyAddController.address.substring(2), - ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + - canOnlyEditPermissions.address.substring(2), - ERC725YDataKeys.LSP6['AddressPermissions:AllowedERC725YDataKeys'] + - beneficiary.address.substring(2), - ERC725YDataKeys.LSP6['AddressPermissions:AllowedERC725YDataKeys'] + - invalidBeneficiary.address.substring(2), - ERC725YDataKeys.LSP6['AddressPermissions:AllowedERC725YDataKeys'] + - zero32Bytes.address.substring(2), - ERC725YDataKeys.LSP6['AddressPermissions:AllowedERC725YDataKeys'] + - zero40Bytes.address.substring(2), + ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + canOnlyAddController.address.substring(2), + ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + canOnlyEditPermissions.address.substring(2), + ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + beneficiaryWithPermissions.address.substring(2), + ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + invalidBeneficiary.address.substring(2), + ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + zero32Bytes.address.substring(2), + ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + zero40Bytes.address.substring(2), + ERC725YDataKeys.LSP6['AddressPermissions:AllowedERC725YDataKeys'] + beneficiaryWithPermissions.address.substring(2), + ERC725YDataKeys.LSP6['AddressPermissions:AllowedERC725YDataKeys'] + beneficiaryNoPermissions.address.substring(2), + ERC725YDataKeys.LSP6['AddressPermissions:AllowedERC725YDataKeys'] + invalidBeneficiary.address.substring(2), + ERC725YDataKeys.LSP6['AddressPermissions:AllowedERC725YDataKeys'] + zero32Bytes.address.substring(2), + ERC725YDataKeys.LSP6['AddressPermissions:AllowedERC725YDataKeys'] + zero40Bytes.address.substring(2), ]; const permissionValues = [ PERMISSIONS.ADDCONTROLLER, PERMISSIONS.EDITPERMISSIONS, + PERMISSIONS.SETDATA, + PERMISSIONS.SETDATA, + PERMISSIONS.SETDATA, + PERMISSIONS.SETDATA, encodeCompactBytesArray([ ERC725YDataKeys.LSP3['LSP3Profile'], // prettier-ignore ethers.utils.keccak256(ethers.utils.toUtf8Bytes("Some Custom Profile Data Key")), ]), + encodeCompactBytesArray([ + ethers.utils.hexlify(ethers.utils.randomBytes(32)), + ethers.utils.hexlify(ethers.utils.randomBytes(32)), + ]), '0x11223344', '0x0000000000000000000000000000000000000000000000000000000000000000', '0x00000000000000000000000000000000000000000000000000000000000000000000000000000000', @@ -66,98 +76,149 @@ export const shouldBehaveLikeSetAllowedERC725YDataKeys = ( }); describe('when caller has ADDCONTROLLER', () => { - describe('when beneficiary had some ERC725Y data keys set under AddressPermissions:AllowedERC725YDataKeys:...', () => { - it('should fail when adding an extra allowed ERC725Y data key', async () => { - const key = - ERC725YDataKeys.LSP6['AddressPermissions:AllowedERC725YDataKeys'] + - beneficiary.address.substring(2); - - const value = encodeCompactBytesArray([ - ERC725YDataKeys.LSP3['LSP3Profile'], - // prettier-ignore - ethers.utils.keccak256(ethers.utils.toUtf8Bytes("Some Custom Profile Data Key")), - // prettier-ignore - ethers.utils.keccak256(ethers.utils.toUtf8Bytes("Another Custom Data Key")), - ]); - - const payload = context.universalProfile.interface.encodeFunctionData('setData', [ - key, - value, - ]); - - await expect(context.keyManager.connect(canOnlyAddController).execute(payload)) - .to.be.revertedWithCustomError(context.keyManager, 'NotAuthorised') - .withArgs(canOnlyAddController.address, 'EDITPERMISSIONS'); - }); - - it('should fail when removing an allowed ERC725Y data key', async () => { - const key = - ERC725YDataKeys.LSP6['AddressPermissions:AllowedERC725YDataKeys'] + - beneficiary.address.substring(2); - - const value = encodeCompactBytesArray([ERC725YDataKeys.LSP3['LSP3Profile']]); - - const payload = context.universalProfile.interface.encodeFunctionData('setData', [ - key, - value, - ]); - - await expect(context.keyManager.connect(canOnlyAddController).execute(payload)) - .to.be.revertedWithCustomError(context.keyManager, 'NotAuthorised') - .withArgs(canOnlyAddController.address, 'EDITPERMISSIONS'); + describe('when controller / beneficiary had some permissions set', () => { + describe('when beneficiary had some ERC725Y data keys set under AddressPermissions:AllowedERC725YDataKeys:...', () => { + it('should fail when adding an extra allowed ERC725Y data key', async () => { + const key = + ERC725YDataKeys.LSP6['AddressPermissions:AllowedERC725YDataKeys'] + + beneficiaryWithPermissions.address.substring(2); + + const value = encodeCompactBytesArray([ + ERC725YDataKeys.LSP3['LSP3Profile'], + // prettier-ignore + ethers.utils.keccak256(ethers.utils.toUtf8Bytes("Some Custom Profile Data Key")), + // prettier-ignore + ethers.utils.keccak256(ethers.utils.toUtf8Bytes("Another Custom Data Key")), + ]); + + const payload = context.universalProfile.interface.encodeFunctionData('setData', [ + key, + value, + ]); + + await expect(context.keyManager.connect(canOnlyAddController).execute(payload)) + .to.be.revertedWithCustomError(context.keyManager, 'NotAuthorised') + .withArgs(canOnlyAddController.address, 'EDITPERMISSIONS'); + }); + + it('should fail when removing an allowed ERC725Y data key', async () => { + const key = + ERC725YDataKeys.LSP6['AddressPermissions:AllowedERC725YDataKeys'] + + beneficiaryWithPermissions.address.substring(2); + + const value = encodeCompactBytesArray([ERC725YDataKeys.LSP3['LSP3Profile']]); + + const payload = context.universalProfile.interface.encodeFunctionData('setData', [ + key, + value, + ]); + + await expect(context.keyManager.connect(canOnlyAddController).execute(payload)) + .to.be.revertedWithCustomError(context.keyManager, 'NotAuthorised') + .withArgs(canOnlyAddController.address, 'EDITPERMISSIONS'); + }); + + it('should fail when trying to clear the CompactedBytesArray completely', async () => { + const key = + ERC725YDataKeys.LSP6['AddressPermissions:AllowedERC725YDataKeys'] + + beneficiaryWithPermissions.address.substring(2); + + const value = '0x'; + + const payload = context.universalProfile.interface.encodeFunctionData('setData', [ + key, + value, + ]); + + await expect(context.keyManager.connect(canOnlyAddController).execute(payload)) + .to.be.revertedWithCustomError(context.keyManager, 'NotAuthorised') + .withArgs(canOnlyAddController.address, 'EDITPERMISSIONS'); + }); + + it('should fail when setting an invalid CompactedBytesArray', async () => { + const key = + ERC725YDataKeys.LSP6['AddressPermissions:AllowedERC725YDataKeys'] + + beneficiaryWithPermissions.address.substring(2); + + const value = '0xbadbadbadbad'; + + const payload = context.universalProfile.interface.encodeFunctionData('setData', [ + key, + value, + ]); + + await expect(context.keyManager.connect(canOnlyAddController).execute(payload)) + .to.be.revertedWithCustomError( + context.keyManager, + 'InvalidEncodedAllowedERC725YDataKeys', + ) + .withArgs(value, "couldn't VALIDATE the data value"); + }); }); - it('should fail when trying to clear the CompactedBytesArray completely', async () => { - const key = - ERC725YDataKeys.LSP6['AddressPermissions:AllowedERC725YDataKeys'] + - beneficiary.address.substring(2); - - const value = '0x'; - - const payload = context.universalProfile.interface.encodeFunctionData('setData', [ - key, - value, - ]); + describe('when beneficiary had no ERC725Y data keys set under AddressPermissions:AllowedERC725YDataKeys:...', () => { + it('should pass when setting a valid CompactedBytesArray', async () => { + const newController = ethers.Wallet.createRandom(); - await expect(context.keyManager.connect(canOnlyAddController).execute(payload)) - .to.be.revertedWithCustomError(context.keyManager, 'NotAuthorised') - .withArgs(canOnlyAddController.address, 'EDITPERMISSIONS'); - }); + const key = + ERC725YDataKeys.LSP6['AddressPermissions:AllowedERC725YDataKeys'] + + newController.address.substr(2); - it('should fail when setting an invalid CompactedBytesArray', async () => { - const key = - ERC725YDataKeys.LSP6['AddressPermissions:AllowedERC725YDataKeys'] + - beneficiary.address.substring(2); + const value = encodeCompactBytesArray([ + // prettier-ignore + ethers.utils.keccak256(ethers.utils.toUtf8Bytes("My Custom Profile Key 1")), + // prettier-ignore + ethers.utils.keccak256(ethers.utils.toUtf8Bytes("My Custom Profile Key 2")), + ]); - const value = '0xbadbadbadbad'; + const payload = context.universalProfile.interface.encodeFunctionData('setData', [ + key, + value, + ]); - const payload = context.universalProfile.interface.encodeFunctionData('setData', [ - key, - value, - ]); + await context.keyManager.connect(canOnlyAddController).execute(payload); - await expect(context.keyManager.connect(canOnlyAddController).execute(payload)) - .to.be.revertedWithCustomError( - context.keyManager, - 'InvalidEncodedAllowedERC725YDataKeys', - ) - .withArgs(value, "couldn't VALIDATE the data value"); + // prettier-ignore + const result = await context.universalProfile.getData(key); + expect(result).to.equal(value); + }); + + it('should fail when setting an invalid CompactedBytesArray (random bytes)', async () => { + const newController = ethers.Wallet.createRandom(); + + const key = + ERC725YDataKeys.LSP6['AddressPermissions:AllowedERC725YDataKeys'] + + newController.address.substr(2); + + const value = '0xbadbadbadbad'; + + const payload = context.universalProfile.interface.encodeFunctionData('setData', [ + key, + value, + ]); + + await expect(context.keyManager.connect(canOnlyAddController).execute(payload)) + .to.be.revertedWithCustomError( + context.keyManager, + 'InvalidEncodedAllowedERC725YDataKeys', + ) + .withArgs(value, "couldn't VALIDATE the data value"); + }); }); }); - describe('when beneficiary had no ERC725Y data keys set under AddressPermissions:AllowedERC725YDataKeys:...', () => { - it('should pass when setting a valid CompactedBytesArray', async () => { - const newController = ethers.Wallet.createRandom(); - + describe('when controller / beneficiary had no permissions set', () => { + it('should pass and edit the list of Allowed ERC725Y Data Keys', async () => { const key = ERC725YDataKeys.LSP6['AddressPermissions:AllowedERC725YDataKeys'] + - newController.address.substr(2); + beneficiaryNoPermissions.address.substring(2); const value = encodeCompactBytesArray([ + ERC725YDataKeys.LSP3['LSP3Profile'], // prettier-ignore - ethers.utils.keccak256(ethers.utils.toUtf8Bytes("My Custom Profile Key 1")), + ethers.utils.keccak256(ethers.utils.toUtf8Bytes("Some Custom Profile Data Key")), // prettier-ignore - ethers.utils.keccak256(ethers.utils.toUtf8Bytes("My Custom Profile Key 2")), + ethers.utils.keccak256(ethers.utils.toUtf8Bytes("Another Custom Data Key")), ]); const payload = context.universalProfile.interface.encodeFunctionData('setData', [ @@ -167,132 +228,157 @@ export const shouldBehaveLikeSetAllowedERC725YDataKeys = ( await context.keyManager.connect(canOnlyAddController).execute(payload); - // prettier-ignore - const result = await context.universalProfile.getData(key); - expect(result).to.equal(value); - }); - - it('should fail when setting an invalid CompactedBytesArray (random bytes)', async () => { - const newController = ethers.Wallet.createRandom(); - - const key = - ERC725YDataKeys.LSP6['AddressPermissions:AllowedERC725YDataKeys'] + - newController.address.substr(2); - - const value = '0xbadbadbadbad'; - - const payload = context.universalProfile.interface.encodeFunctionData('setData', [ - key, - value, - ]); - - await expect(context.keyManager.connect(canOnlyAddController).execute(payload)) - .to.be.revertedWithCustomError( - context.keyManager, - 'InvalidEncodedAllowedERC725YDataKeys', - ) - .withArgs(value, "couldn't VALIDATE the data value"); + expect(await context.universalProfile.getData(key)).to.equal(value); }); }); }); describe('when caller has EDITPERMISSIONS', () => { - describe('when beneficiary had some ERC725Y data keys set under AddressPermissions:AllowedERC725YDataKeys:...', () => { - it('should pass when adding an extra allowed ERC725Y data key', async () => { - const key = - ERC725YDataKeys.LSP6['AddressPermissions:AllowedERC725YDataKeys'] + - beneficiary.address.substring(2); + describe('when controller / beneficiary had some permissions set', () => { + describe('when beneficiary had some ERC725Y data keys set under AddressPermissions:AllowedERC725YDataKeys:...', () => { + it('should pass when adding an extra allowed ERC725Y data key', async () => { + const key = + ERC725YDataKeys.LSP6['AddressPermissions:AllowedERC725YDataKeys'] + + beneficiaryWithPermissions.address.substring(2); + + const value = encodeCompactBytesArray([ + ERC725YDataKeys.LSP3['LSP3Profile'], + // prettier-ignore + ethers.utils.keccak256(ethers.utils.toUtf8Bytes("Some Custom Profile Data Key")), + // prettier-ignore + ethers.utils.keccak256(ethers.utils.toUtf8Bytes("Another Custom Data Key")), + ]); + + const payload = context.universalProfile.interface.encodeFunctionData('setData', [ + key, + value, + ]); + + await context.keyManager.connect(canOnlyEditPermissions).execute(payload); - const value = encodeCompactBytesArray([ - ERC725YDataKeys.LSP3['LSP3Profile'], - // prettier-ignore - ethers.utils.keccak256(ethers.utils.toUtf8Bytes("Some Custom Profile Data Key")), // prettier-ignore - ethers.utils.keccak256(ethers.utils.toUtf8Bytes("Another Custom Data Key")), - ]); - - const payload = context.universalProfile.interface.encodeFunctionData('setData', [ - key, - value, - ]); - - await context.keyManager.connect(canOnlyEditPermissions).execute(payload); - - // prettier-ignore - const result = await context.universalProfile.getData(key); - expect(result).to.equal(value); - }); + const result = await context.universalProfile.getData(key); + expect(result).to.equal(value); + }); - it('should pass when removing an allowed ERC725Y data key', async () => { - const key = - ERC725YDataKeys.LSP6['AddressPermissions:AllowedERC725YDataKeys'] + - beneficiary.address.substring(2); + it('should pass when removing an allowed ERC725Y data key', async () => { + const key = + ERC725YDataKeys.LSP6['AddressPermissions:AllowedERC725YDataKeys'] + + beneficiaryWithPermissions.address.substring(2); - const value = encodeCompactBytesArray([ERC725YDataKeys.LSP3['LSP3Profile']]); + const value = encodeCompactBytesArray([ERC725YDataKeys.LSP3['LSP3Profile']]); - const payload = context.universalProfile.interface.encodeFunctionData('setData', [ - key, - value, - ]); + const payload = context.universalProfile.interface.encodeFunctionData('setData', [ + key, + value, + ]); - await context.keyManager.connect(canOnlyEditPermissions).execute(payload); + await context.keyManager.connect(canOnlyEditPermissions).execute(payload); - // prettier-ignore - const result = await context.universalProfile.getData(key); - expect(result).to.equal(value); - }); + // prettier-ignore + const result = await context.universalProfile.getData(key); + expect(result).to.equal(value); + }); - it('should pass when trying to clear the CompactedBytesArray completely', async () => { - const key = - ERC725YDataKeys.LSP6['AddressPermissions:AllowedERC725YDataKeys'] + - beneficiary.address.substring(2); + it('should pass when trying to clear the CompactedBytesArray completely', async () => { + const key = + ERC725YDataKeys.LSP6['AddressPermissions:AllowedERC725YDataKeys'] + + beneficiaryWithPermissions.address.substring(2); - const value = '0x'; + const value = '0x'; - const payload = context.universalProfile.interface.encodeFunctionData('setData', [ - key, - value, - ]); + const payload = context.universalProfile.interface.encodeFunctionData('setData', [ + key, + value, + ]); - await context.keyManager.connect(canOnlyEditPermissions).execute(payload); + await context.keyManager.connect(canOnlyEditPermissions).execute(payload); - // prettier-ignore - const result = await context.universalProfile.getData(key); - expect(result).to.equal(value); + // prettier-ignore + const result = await context.universalProfile.getData(key); + expect(result).to.equal(value); + }); + + it('should fail when setting an invalid CompactedBytesArray', async () => { + const key = + ERC725YDataKeys.LSP6['AddressPermissions:AllowedERC725YDataKeys'] + + beneficiaryWithPermissions.address.substring(2); + + const value = '0xbadbadbadbad'; + + const payload = context.universalProfile.interface.encodeFunctionData('setData', [ + key, + value, + ]); + + await expect(context.keyManager.connect(canOnlyEditPermissions).execute(payload)) + .to.be.revertedWithCustomError( + context.keyManager, + 'InvalidEncodedAllowedERC725YDataKeys', + ) + .withArgs(value, "couldn't VALIDATE the data value"); + }); }); - it('should fail when setting an invalid CompactedBytesArray', async () => { - const key = - ERC725YDataKeys.LSP6['AddressPermissions:AllowedERC725YDataKeys'] + - beneficiary.address.substring(2); - - const value = '0xbadbadbadbad'; - - const payload = context.universalProfile.interface.encodeFunctionData('setData', [ - key, - value, - ]); - - await expect(context.keyManager.connect(canOnlyEditPermissions).execute(payload)) - .to.be.revertedWithCustomError( - context.keyManager, - 'InvalidEncodedAllowedERC725YDataKeys', - ) - .withArgs(value, "couldn't VALIDATE the data value"); + describe('when beneficiary had no ERC725Y data keys set under AddressPermissions:AllowedERC725YDataKeys:...', () => { + it('should fail and not authorize to add a list of allowed ERC725Y data keys (not authorised)', async () => { + const newController = ethers.Wallet.createRandom(); + + const key = + ERC725YDataKeys.LSP6['AddressPermissions:AllowedERC725YDataKeys'] + + newController.address.substr(2); + + const value = encodeCompactBytesArray([ + ethers.utils.keccak256(ethers.utils.toUtf8Bytes('My Custom Key 1')), + ethers.utils.keccak256(ethers.utils.toUtf8Bytes('My Custom Key 2')), + ]); + + const payload = context.universalProfile.interface.encodeFunctionData('setData', [ + key, + value, + ]); + + await expect(context.keyManager.connect(canOnlyEditPermissions).execute(payload)) + .to.be.revertedWithCustomError(context.keyManager, 'NotAuthorised') + .withArgs(canOnlyEditPermissions.address, 'ADDCONTROLLER'); + }); + + it('should fail when setting an invalid CompactedBytesArray', async () => { + const newController = ethers.Wallet.createRandom(); + + const key = + ERC725YDataKeys.LSP6['AddressPermissions:AllowedERC725YDataKeys'] + + newController.address.substr(2); + + const value = '0xbadbadbadbad'; + + const payload = context.universalProfile.interface.encodeFunctionData('setData', [ + key, + value, + ]); + + await expect(context.keyManager.connect(canOnlyEditPermissions).execute(payload)) + .to.be.revertedWithCustomError( + context.keyManager, + 'InvalidEncodedAllowedERC725YDataKeys', + ) + .withArgs(value, "couldn't VALIDATE the data value"); + }); }); }); - describe('when beneficiary had no ERC725Y data keys set under AddressPermissions:AllowedERC725YDataKeys:...', () => { - it('should fail and not authorize to add a list of allowed ERC725Y data keys (not authorised)', async () => { - const newController = ethers.Wallet.createRandom(); - + describe('when controller / beneficiary had no permissions set', () => { + it("should revert with error `NotAuthorised('ADDCONTROLLER')` when trying to add a list of allowed ERC725Y data keys", async () => { const key = ERC725YDataKeys.LSP6['AddressPermissions:AllowedERC725YDataKeys'] + - newController.address.substr(2); + beneficiaryNoPermissions.address.substring(2); const value = encodeCompactBytesArray([ - ethers.utils.keccak256(ethers.utils.toUtf8Bytes('My Custom Key 1')), - ethers.utils.keccak256(ethers.utils.toUtf8Bytes('My Custom Key 2')), + ERC725YDataKeys.LSP3['LSP3Profile'], + // prettier-ignore + ethers.utils.keccak256(ethers.utils.toUtf8Bytes("Some Custom Profile Data Key")), + // prettier-ignore + ethers.utils.keccak256(ethers.utils.toUtf8Bytes("Another Custom Data Key")), ]); const payload = context.universalProfile.interface.encodeFunctionData('setData', [ @@ -304,28 +390,6 @@ export const shouldBehaveLikeSetAllowedERC725YDataKeys = ( .to.be.revertedWithCustomError(context.keyManager, 'NotAuthorised') .withArgs(canOnlyEditPermissions.address, 'ADDCONTROLLER'); }); - - it('should fail when setting an invalid CompactedBytesArray', async () => { - const newController = ethers.Wallet.createRandom(); - - const key = - ERC725YDataKeys.LSP6['AddressPermissions:AllowedERC725YDataKeys'] + - newController.address.substr(2); - - const value = '0xbadbadbadbad'; - - const payload = context.universalProfile.interface.encodeFunctionData('setData', [ - key, - value, - ]); - - await expect(context.keyManager.connect(canOnlyEditPermissions).execute(payload)) - .to.be.revertedWithCustomError( - context.keyManager, - 'InvalidEncodedAllowedERC725YDataKeys', - ) - .withArgs(value, "couldn't VALIDATE the data value"); - }); }); }); }); diff --git a/tests/LSP6KeyManager/internals/AllowedCalls.internal.ts b/tests/LSP6KeyManager/internals/AllowedCalls.internal.ts index 26b6f5017..de2e1b21c 100644 --- a/tests/LSP6KeyManager/internals/AllowedCalls.internal.ts +++ b/tests/LSP6KeyManager/internals/AllowedCalls.internal.ts @@ -29,7 +29,7 @@ async function teardownKeyManagerHelper( Array(permissionsKeys.length).fill('0x'), ]); - await context.keyManagerInternalTester.connect(context.owner).execute(teardownPayload); + await context.keyManagerInternalTester.connect(context.mainController).execute(teardownPayload); } export const testAllowedCallsInternals = ( @@ -157,7 +157,8 @@ export const testAllowedCallsInternals = ( ); const permissionsKeys = [ - ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + context.owner.address.substring(2), + ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + + context.mainController.address.substring(2), ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + canCallOnlyTwoAddresses.address.substring(2), ERC725YDataKeys.LSP6['AddressPermissions:AllowedCalls'] + @@ -184,13 +185,13 @@ export const testAllowedCallsInternals = ( it('should return no bytes when no allowed calls were set', async () => { const bytesResult = await context.keyManagerInternalTester.getAllowedCallsFor( - context.owner.address, + context.mainController.address, ); expect(bytesResult).to.equal('0x'); const resultFromAccount = await context.universalProfile['getData(bytes32)']( ERC725YDataKeys.LSP6['AddressPermissions:AllowedCalls'] + - context.owner.address.substring(2), + context.mainController.address.substring(2), ); expect(resultFromAccount).to.equal('0x'); }); @@ -333,7 +334,7 @@ export const testAllowedCallsInternals = ( permissionValues, ]); - await context.keyManagerInternalTester.connect(context.owner).execute(setup); + await context.keyManagerInternalTester.connect(context.mainController).execute(setup); }); after('reset permissions', async () => { @@ -444,7 +445,7 @@ export const testAllowedCallsInternals = ( permissionValues, ]); - await context.keyManagerInternalTester.connect(context.owner).execute(setup); + await context.keyManagerInternalTester.connect(context.mainController).execute(setup); }); after('reset permissions', async () => { @@ -555,7 +556,7 @@ export const testAllowedCallsInternals = ( permissionValues, ]); - await context.keyManagerInternalTester.connect(context.owner).execute(setup); + await context.keyManagerInternalTester.connect(context.mainController).execute(setup); }); after('reset permissions', async () => { @@ -674,7 +675,8 @@ export const testAllowedCallsInternals = ( ]; const permissionKeys = [ - ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + context.owner.address.substring(2), + ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + + context.mainController.address.substring(2), ...Object.values(controllers).map( (controller) => ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + @@ -775,7 +777,8 @@ export const testAllowedCallsInternals = ( context = await buildContext(); const permissionKeys = [ - ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + context.owner.address.substring(2), + ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + + context.mainController.address.substring(2), ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + context.accounts[1].address.substring(2), ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + @@ -837,7 +840,8 @@ export const testAllowedCallsInternals = ( anyAllowedCalls = context.accounts[1]; const permissionsKeys = [ - ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + context.owner.address.substring(2), + ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + + context.mainController.address.substring(2), ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + anyAllowedCalls.address.substring(2), ERC725YDataKeys.LSP6['AddressPermissions:AllowedCalls'] + diff --git a/tests/LSP6KeyManager/internals/Execute.internal.ts b/tests/LSP6KeyManager/internals/Execute.internal.ts index 066cbcd8b..df5ebc240 100644 --- a/tests/LSP6KeyManager/internals/Execute.internal.ts +++ b/tests/LSP6KeyManager/internals/Execute.internal.ts @@ -14,7 +14,8 @@ export const testExecuteInternals = (buildContext: () => Promise Promise { + it('should revert if the address param is not left padded with 12 x `00` bytes', async () => { const executeParameters = { operationType: OPERATION_TYPES.CALL, to: context.accounts[3].address, @@ -68,11 +72,14 @@ export const testExecuteInternals = (buildContext: () => Promise { expect( - await context.keyManagerInternalTester.getPermissionsFor(context.owner.address), + await context.keyManagerInternalTester.getPermissionsFor(context.mainController.address), ).to.equal(ALL_PERMISSIONS); // ALL_PERMISSIONS = "0xffff..." }); @@ -125,7 +126,8 @@ export const testReadingPermissionsInternals = ( addressCanSetData = context.accounts[1]; const permissionKeys = [ - ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + context.owner.address.substring(2), + ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + + context.mainController.address.substring(2), ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + addressCanSetData.address.substring(2), ]; @@ -164,7 +166,8 @@ export const testReadingPermissionsInternals = ( fourthBeneficiary = context.accounts[4]; let permissionKeys = [ - ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + context.owner.address.substring(2), + ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + + context.mainController.address.substring(2), ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + firstBeneficiary.address.substring(2), ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + @@ -196,7 +199,7 @@ export const testReadingPermissionsInternals = ( // set AddressPermissions array values permissionArrayValues = [ '0x05', - context.owner.address, + context.mainController.address, firstBeneficiary.address, secondBeneficiary.address, thirdBeneficiary.address, diff --git a/tests/LSP6KeyManager/internals/SetData.internal.ts b/tests/LSP6KeyManager/internals/SetData.internal.ts index 4d56d2f13..b252504bf 100644 --- a/tests/LSP6KeyManager/internals/SetData.internal.ts +++ b/tests/LSP6KeyManager/internals/SetData.internal.ts @@ -13,7 +13,8 @@ export const testSetDataInternals = (buildContext: () => Promise Promise Promise Promise { describe('when `to` is an EOA', () => { - it('should allow transfering the tokens with `allowNonLSP1Recipient` param = true', async () => { + it('should allow transfering the tokens with `force` param = true', async () => { const txParams = { operator: context.accounts.owner, from: context.accounts.owner.address, to: context.accounts.tokenReceiver.address, amount: transferAmount, - allowNonLSP1Recipient: true, + force: true, data: expectedData, }; @@ -829,20 +829,20 @@ export const shouldBehaveLikeLSP7CompatibleERC20 = ( txParams.from, txParams.to, txParams.amount, - txParams.allowNonLSP1Recipient, + txParams.force, txParams.data, ), expectedData, ); }); - it('should NOT allow transfering the tokens with `allowNonLSP1Recipient` param = false', async () => { + it('should NOT allow transfering the tokens with `force` param = false', async () => { const txParams = { operator: context.accounts.owner, from: context.accounts.owner.address, to: context.accounts.tokenReceiver.address, amount: transferAmount, - allowNonLSP1Recipient: false, + force: false, data: expectedData, }; @@ -856,7 +856,7 @@ export const shouldBehaveLikeLSP7CompatibleERC20 = ( txParams.from, txParams.to, txParams.amount, - txParams.allowNonLSP1Recipient, + txParams.force, txParams.data, ), ) @@ -874,13 +874,13 @@ export const shouldBehaveLikeLSP7CompatibleERC20 = ( describe('when `to` is a contract', () => { describe('when receiving contract supports LSP1', () => { - it('should allow transfering the tokens with `allowNonLSP1Recipient` param = true', async () => { + it('should allow transfering the tokens with `force` param = true', async () => { const txParams = { operator: context.accounts.owner, from: context.accounts.owner.address, to: deployedContracts.tokenReceiverWithLSP1.address, amount: transferAmount, - allowNonLSP1Recipient: true, + force: true, data: expectedData, }; @@ -893,20 +893,20 @@ export const shouldBehaveLikeLSP7CompatibleERC20 = ( txParams.from, txParams.to, txParams.amount, - txParams.allowNonLSP1Recipient, + txParams.force, txParams.data, ), expectedData, ); }); - it('should allow transfering the tokens with `allowNonLSP1Recipient` param = false', async () => { + it('should allow transfering the tokens with `force` param = false', async () => { const txParams = { operator: context.accounts.owner, from: context.accounts.owner.address, to: deployedContracts.tokenReceiverWithLSP1.address, amount: transferAmount, - allowNonLSP1Recipient: false, + force: false, data: expectedData, }; @@ -919,7 +919,7 @@ export const shouldBehaveLikeLSP7CompatibleERC20 = ( txParams.from, txParams.to, txParams.amount, - txParams.allowNonLSP1Recipient, + txParams.force, txParams.data, ), expectedData, @@ -928,13 +928,13 @@ export const shouldBehaveLikeLSP7CompatibleERC20 = ( }); describe('when receiving contract does not support LSP1', () => { - it('should allow transfering the tokens with `allowNonLSP1Recipient` param = true', async () => { + it('should allow transfering the tokens with `force` param = true', async () => { const txParams = { operator: context.accounts.owner, from: context.accounts.owner.address, to: deployedContracts.tokenReceiverWithoutLSP1.address, amount: transferAmount, - allowNonLSP1Recipient: true, + force: true, data: expectedData, }; @@ -947,20 +947,20 @@ export const shouldBehaveLikeLSP7CompatibleERC20 = ( txParams.from, txParams.to, txParams.amount, - txParams.allowNonLSP1Recipient, + txParams.force, txParams.data, ), expectedData, ); }); - it('should NOT allow transfering the tokens with `allowNonLSP1Recipient` param = false', async () => { + it('should NOT allow transfering the tokens with `force` param = false', async () => { const txParams = { operator: context.accounts.owner, from: context.accounts.owner.address, to: deployedContracts.tokenReceiverWithoutLSP1.address, amount: transferAmount, - allowNonLSP1Recipient: false, + force: false, data: expectedData, }; @@ -974,7 +974,7 @@ export const shouldBehaveLikeLSP7CompatibleERC20 = ( txParams.from, txParams.to, txParams.amount, - txParams.allowNonLSP1Recipient, + txParams.force, txParams.data, ), ) @@ -994,13 +994,13 @@ export const shouldBehaveLikeLSP7CompatibleERC20 = ( describe('when caller (msg.sender) is an operator (= Not the `from` address)', () => { describe('when `to` is an EOA', () => { - it('should allow transfering the tokens with `allowNonLSP1Recipient` param = true', async () => { + it('should allow transfering the tokens with `force` param = true', async () => { const txParams = { operator: context.accounts.operator, from: context.accounts.owner.address, to: context.accounts.tokenReceiver.address, amount: transferAmount, - allowNonLSP1Recipient: true, + force: true, data: expectedData, }; @@ -1013,20 +1013,20 @@ export const shouldBehaveLikeLSP7CompatibleERC20 = ( txParams.from, txParams.to, txParams.amount, - txParams.allowNonLSP1Recipient, + txParams.force, txParams.data, ), expectedData, ); }); - it('should NOT allow transfering the tokens with `allowNonLSP1Recipient` param = false', async () => { + it('should NOT allow transfering the tokens with `force` param = false', async () => { const txParams = { operator: context.accounts.operator, from: context.accounts.owner.address, to: context.accounts.tokenReceiver.address, amount: transferAmount, - allowNonLSP1Recipient: false, + force: false, data: expectedData, }; @@ -1040,7 +1040,7 @@ export const shouldBehaveLikeLSP7CompatibleERC20 = ( txParams.from, txParams.to, txParams.amount, - txParams.allowNonLSP1Recipient, + txParams.force, txParams.data, ), ) @@ -1058,13 +1058,13 @@ export const shouldBehaveLikeLSP7CompatibleERC20 = ( describe('when `to` is a contract', () => { describe('when receiving contract supports LSP1', () => { - it('should allow transfering the tokens with `allowNonLSP1Recipient` param = true', async () => { + it('should allow transfering the tokens with `force` param = true', async () => { const txParams = { operator: context.accounts.operator, from: context.accounts.owner.address, to: deployedContracts.tokenReceiverWithLSP1.address, amount: transferAmount, - allowNonLSP1Recipient: true, + force: true, data: expectedData, }; @@ -1077,20 +1077,20 @@ export const shouldBehaveLikeLSP7CompatibleERC20 = ( txParams.from, txParams.to, txParams.amount, - txParams.allowNonLSP1Recipient, + txParams.force, txParams.data, ), expectedData, ); }); - it('should allow transfering the tokens with `allowNonLSP1Recipient` param = false', async () => { + it('should allow transfering the tokens with `force` param = false', async () => { const txParams = { operator: context.accounts.operator, from: context.accounts.owner.address, to: deployedContracts.tokenReceiverWithLSP1.address, amount: transferAmount, - allowNonLSP1Recipient: false, + force: false, data: expectedData, }; @@ -1103,7 +1103,7 @@ export const shouldBehaveLikeLSP7CompatibleERC20 = ( txParams.from, txParams.to, txParams.amount, - txParams.allowNonLSP1Recipient, + txParams.force, txParams.data, ), expectedData, @@ -1112,13 +1112,13 @@ export const shouldBehaveLikeLSP7CompatibleERC20 = ( }); describe('when receiving contract does not support LSP1', () => { - it('should allow transfering the tokens with `allowNonLSP1Recipient` param = true', async () => { + it('should allow transfering the tokens with `force` param = true', async () => { const txParams = { operator: context.accounts.operator, from: context.accounts.owner.address, to: deployedContracts.tokenReceiverWithoutLSP1.address, amount: transferAmount, - allowNonLSP1Recipient: true, + force: true, data: expectedData, }; @@ -1131,20 +1131,20 @@ export const shouldBehaveLikeLSP7CompatibleERC20 = ( txParams.from, txParams.to, txParams.amount, - txParams.allowNonLSP1Recipient, + txParams.force, txParams.data, ), expectedData, ); }); - it('should NOT allow transfering the tokens with `allowNonLSP1Recipient` param = false', async () => { + it('should NOT allow transfering the tokens with `force` param = false', async () => { const txParams = { operator: context.accounts.operator, from: context.accounts.owner.address, to: deployedContracts.tokenReceiverWithoutLSP1.address, amount: transferAmount, - allowNonLSP1Recipient: false, + force: false, data: expectedData, }; @@ -1158,7 +1158,7 @@ export const shouldBehaveLikeLSP7CompatibleERC20 = ( txParams.from, txParams.to, txParams.amount, - txParams.allowNonLSP1Recipient, + txParams.force, txParams.data, ), ) @@ -1189,7 +1189,7 @@ export const shouldBehaveLikeLSP7CompatibleERC20 = ( from: context.accounts.owner.address, to: deployedContracts.tokenReceiverWithoutLSP1.address, amount: ownerBalance.add(1), - allowNonLSP1Recipient: true, + force: true, data: expectedData, }; @@ -1203,7 +1203,7 @@ export const shouldBehaveLikeLSP7CompatibleERC20 = ( txParams.from, txParams.to, txParams.amount, - txParams.allowNonLSP1Recipient, + txParams.force, txParams.data, ), ) @@ -1230,7 +1230,7 @@ export const shouldBehaveLikeLSP7CompatibleERC20 = ( from: context.accounts.owner.address, to: deployedContracts.tokenReceiverWithoutLSP1.address, amount: ownerBalance.add(1), - allowNonLSP1Recipient: true, + force: true, data: expectedData, }; @@ -1244,7 +1244,7 @@ export const shouldBehaveLikeLSP7CompatibleERC20 = ( txParams.from, txParams.to, txParams.amount, - txParams.allowNonLSP1Recipient, + txParams.force, txParams.data, ), ) @@ -1285,8 +1285,18 @@ export const shouldInitializeLikeLSP7CompatibleERC20 = ( }); describe('when the contract was initialized', () => { - it('should have registered its ERC165 interface', async () => { - expect(await context.lsp7CompatibleERC20.supportsInterface(INTERFACE_IDS.LSP7DigitalAsset)); + it('should support ERC20 interface', async () => { + expect(await context.lsp7CompatibleERC20.supportsInterface(INTERFACE_IDS.ERC20)).to.be.true; + }); + + it('should support ERC20Metadata interface', async () => { + expect(await context.lsp7CompatibleERC20.supportsInterface(INTERFACE_IDS.ERC20Metadata)).to.be + .true; + }); + + it('should support LSP7 interface', async () => { + expect(await context.lsp7CompatibleERC20.supportsInterface(INTERFACE_IDS.LSP7DigitalAsset)).to + .be.true; }); it('should have set expected entries with ERC725Y.setData', async () => { @@ -1318,5 +1328,33 @@ export const shouldInitializeLikeLSP7CompatibleERC20 = ( .withArgs(symbolKey, expectedSymbolValue); expect(await context.lsp7CompatibleERC20.getData(symbolKey)).to.equal(expectedSymbolValue); }); + + describe('when using the functions from IERC20Metadata', () => { + it('should allow reading `name()`', async () => { + // using compatibility getter -> returns(string) + const nameAsString = await context.lsp7CompatibleERC20.name(); + expect(nameAsString).to.equal(context.deployParams.name); + + // using getData -> returns(bytes) + const nameAsBytes = await context.lsp7CompatibleERC20.getData( + ethers.utils.keccak256(ethers.utils.toUtf8Bytes('LSP4TokenName')), + ); + + expect(ethers.utils.toUtf8String(nameAsBytes)).to.equal(context.deployParams.name); + }); + + it('should allow reading `symbol()`', async () => { + // using compatibility getter -> returns(string) + const symbolAsString = await context.lsp7CompatibleERC20.symbol(); + expect(symbolAsString).to.equal(context.deployParams.symbol); + + // using getData -> returns(bytes) + const symbolAsBytes = await context.lsp7CompatibleERC20.getData( + ethers.utils.keccak256(ethers.utils.toUtf8Bytes('LSP4TokenSymbol')), + ); + + expect(ethers.utils.toUtf8String(symbolAsBytes)).to.equal(context.deployParams.symbol); + }); + }); }); }; diff --git a/tests/LSP7DigitalAsset/LSP7DigitalAsset.behaviour.ts b/tests/LSP7DigitalAsset/LSP7DigitalAsset.behaviour.ts index e6f17645a..0d41e3c03 100644 --- a/tests/LSP7DigitalAsset/LSP7DigitalAsset.behaviour.ts +++ b/tests/LSP7DigitalAsset/LSP7DigitalAsset.behaviour.ts @@ -79,35 +79,35 @@ export const shouldBehaveLikeLSP7 = (buildContext: () => Promise { describe('when `amount == 0`', () => { - it('should revert if `allowNonLSP1Recipient == false`', async () => { + it('should revert if `force == false`', async () => { const txParams = { to: context.accounts.anotherTokenReceiver.address, amount: 0, - allowNonLSP1Recipient: false, + force: false, data: '0x', }; await expect( context.lsp7 .connect(context.accounts.anyone) - .mint(txParams.to, txParams.amount, txParams.allowNonLSP1Recipient, txParams.data), + .mint(txParams.to, txParams.amount, txParams.force, txParams.data), ) .to.be.revertedWithCustomError(context.lsp7, 'LSP7NotifyTokenReceiverIsEOA') .withArgs(txParams.to); }); - it('should pass if `allowNonLSP1Recipient == true`', async () => { + it('should pass if `force == true`', async () => { const txParams = { to: context.accounts.anotherTokenReceiver.address, amount: 0, - allowNonLSP1Recipient: true, + force: true, data: '0x', }; await expect( context.lsp7 .connect(context.accounts.anyone) - .mint(txParams.to, txParams.amount, txParams.allowNonLSP1Recipient, txParams.data), + .mint(txParams.to, txParams.amount, txParams.force, txParams.data), ) .to.emit(context.lsp7, 'Transfer') .withArgs( @@ -115,7 +115,7 @@ export const shouldBehaveLikeLSP7 = (buildContext: () => Promise Promise Promise Promise Promise { // pre-conditions @@ -846,12 +836,10 @@ export const shouldBehaveLikeLSP7 = (buildContext: () => Promise Promise { - const allowNonLSP1Recipient = true; + describe('when using force=true', () => { + const force = true; const data = ethers.utils.hexlify( - ethers.utils.toUtf8Bytes('doing a transfer with allowNonLSP1Recipient'), + ethers.utils.toUtf8Bytes('doing a transfer with force'), ); describe('when `to` is an EOA', () => { @@ -902,7 +890,7 @@ export const shouldBehaveLikeLSP7 = (buildContext: () => Promise Promise Promise Promise Promise Promise { - const allowNonLSP1Recipient = false; + describe('when force=false', () => { + const force = false; const data = ethers.utils.hexlify( - ethers.utils.toUtf8Bytes('doing a transfer without allowNonLSP1Recipient'), + ethers.utils.toUtf8Bytes('doing a transfer without force'), ); describe('when `to` is an EOA', () => { @@ -988,7 +976,7 @@ export const shouldBehaveLikeLSP7 = (buildContext: () => Promise Promise Promise Promise Promise Promise Promise Promise Promise Promise { - it('should revert with `allowNonLSP1Recipient == false`', async () => { + it('should revert with `force == false`', async () => { const caller = context.accounts.anyone; const txParams = { from: context.accounts.anyone.address, to: context.accounts.anotherTokenReceiver.address, amount: ethers.BigNumber.from(0), - allowNonLSP1Recipient: false, + force: false, data: '0x', }; @@ -1168,7 +1156,7 @@ export const shouldBehaveLikeLSP7 = (buildContext: () => Promise Promise { + it('should pass with `force == true`', async () => { const caller = context.accounts.anyone; const txParams = { from: context.accounts.anyone.address, to: context.accounts.anotherTokenReceiver.address, amount: ethers.BigNumber.from(0), - allowNonLSP1Recipient: true, + force: true, data: '0x', }; @@ -1198,7 +1186,7 @@ export const shouldBehaveLikeLSP7 = (buildContext: () => Promise Promise Promise Promise Promise { // pre-conditions @@ -1309,7 +1297,7 @@ export const shouldBehaveLikeLSP7 = (buildContext: () => Promise { @@ -1320,7 +1308,7 @@ export const shouldBehaveLikeLSP7 = (buildContext: () => Promise Promise { if (expectedError.args.length > 0) await expect( - context.lsp7 - .connect(operator) - .transferBatch(from, to, amount, allowNonLSP1Recipient, data), + context.lsp7.connect(operator).transferBatch(from, to, amount, force, data), ) .to.be.revertedWithCustomError(context.lsp7, expectedError.error) .withArgs(...expectedError.args); else await expect( - context.lsp7 - .connect(operator) - .transferBatch(from, to, amount, allowNonLSP1Recipient, data), + context.lsp7.connect(operator).transferBatch(from, to, amount, force, data), ).to.be.revertedWithCustomError(context.lsp7, expectedError.error); }; @@ -1388,9 +1372,9 @@ export const shouldBehaveLikeLSP7 = (buildContext: () => Promise { + describe('when force=true', () => { const data = ethers.utils.hexlify( - ethers.utils.toUtf8Bytes('doing a transfer with allowNonLSP1Recipient'), + ethers.utils.toUtf8Bytes('doing a transfer with force'), ); describe('when `to` is an EOA', () => { @@ -1400,7 +1384,7 @@ export const shouldBehaveLikeLSP7 = (buildContext: () => Promise Promise Promise Promise Promise { + describe('when force=false', () => { const data = ethers.utils.hexlify( - ethers.utils.toUtf8Bytes('doing a transfer without allowNonLSP1Recipient'), + ethers.utils.toUtf8Bytes('doing a transfer without force'), ); describe('when `to` is an EOA', () => { @@ -1501,7 +1485,7 @@ export const shouldBehaveLikeLSP7 = (buildContext: () => Promise Promise Promise Promise { + describe('when force is mixed(true/false) respectively', () => { const data = ethers.utils.hexlify( - ethers.utils.toUtf8Bytes('doing a transfer without allowNonLSP1Recipient'), + ethers.utils.toUtf8Bytes('doing a transfer without force'), ); describe('when `to` is an EOA', () => { @@ -1568,7 +1552,7 @@ export const shouldBehaveLikeLSP7 = (buildContext: () => Promise Promise Promise { - it('should pass regardless of allowNonLSP1Recipient params', async () => { + it('should pass regardless of force params', async () => { const txParams = { from: [context.accounts.owner.address, context.accounts.owner.address], to: [ @@ -1612,7 +1596,7 @@ export const shouldBehaveLikeLSP7 = (buildContext: () => Promise Promise Promise Promise Promise Promise Promise { describe('when using address(0) as `from` address', () => { it('should revert', async () => { @@ -1800,7 +1785,14 @@ export const shouldBehaveLikeLSP7 = (buildContext: () => Promise Promise { + const operator = context.accounts.operator; + const amount = operatorAllowance; + + const operatorsList = await context.lsp7.getOperatorsOf(context.accounts.owner.address); + expect(operatorsList).to.include(operator.address); + + await context.lsp7.connect(operator).burn(context.accounts.owner.address, amount, '0x'); + + const updatedOperatorList = await context.lsp7.getOperatorsOf( + context.accounts.owner.address, + ); + expect(updatedOperatorList.length).to.equal(operatorsList.length - 1); + expect(updatedOperatorList).to.not.include(operator.address); + }); + it('token owner balance should have decreased', async () => { const operator = context.accounts.operator; const amount = operatorAllowance; @@ -1997,6 +2005,20 @@ export const shouldBehaveLikeLSP7 = (buildContext: () => Promise { + const operator = context.accounts.operator; + const amount = operatorAllowance; + + const operatorsList = await context.lsp7.getOperatorsOf(context.accounts.owner.address); + expect(operatorsList).to.include(operator.address); + + await expect( + context.lsp7.connect(operator).burn(context.accounts.owner.address, amount, '0x'), + ) + .to.emit(context.lsp7, 'RevokedOperator') + .withArgs(operator.address, context.accounts.owner.address, '0x'); + }); }); describe('when burning part of its allowance', () => { @@ -2020,6 +2042,18 @@ export const shouldBehaveLikeLSP7 = (buildContext: () => Promise { + const amount = 10; + assert.isBelow(amount, operatorAllowance); + + const operator = context.accounts.operator; + + await context.lsp7.connect(operator).burn(context.accounts.owner.address, amount, '0x'); + + const operatorsList = await context.lsp7.getOperatorsOf(context.accounts.owner.address); + expect(operatorsList).to.include(operator.address); + }); + it('token owner balance should have decreased', async () => { const amount = 10; assert.isBelow(amount, operatorAllowance); @@ -2065,6 +2099,24 @@ export const shouldBehaveLikeLSP7 = (buildContext: () => Promise { + const amount = 10; + assert.isBelow(amount, operatorAllowance); + + const operator = context.accounts.operator; + + await expect( + context.lsp7.connect(operator).burn(context.accounts.owner.address, amount, '0x'), + ) + .to.emit(context.lsp7, 'AuthorizedOperator') + .withArgs( + operator.address, + context.accounts.owner.address, + operatorAllowance - amount, + '0x', + ); + }); }); describe('when burning more than its allowance', () => { @@ -2108,7 +2160,7 @@ export const shouldBehaveLikeLSP7 = (buildContext: () => Promise { @@ -2120,21 +2172,21 @@ export const shouldBehaveLikeLSP7 = (buildContext: () => Promise { - await expect(context.lsp7.connect(oldOwner).renounceOwnership()).to.be.revertedWith( - 'Ownable: caller is not the owner', - ); + await expect( + context.lsp7.connect(oldOwner).renounceOwnership(), + ).to.be.revertedWithCustomError(context.lsp7, 'OwnableCallerNotTheOwner'); }); it('old owner should not be allowed to use `setData(..)`', async () => { const key = ethers.utils.keccak256(ethers.utils.toUtf8Bytes('key')); const value = ethers.utils.keccak256(ethers.utils.toUtf8Bytes('value')); - await expect(context.lsp7.connect(oldOwner).setData(key, value)).to.be.revertedWith( - 'Ownable: caller is not the owner', - ); + await expect( + context.lsp7.connect(oldOwner).setData(key, value), + ).to.be.revertedWithCustomError(context.lsp7, 'OwnableCallerNotTheOwner'); }); it('new owner should be allowed to use `transferOwnership(..)`', async () => { @@ -2160,6 +2212,32 @@ export const shouldBehaveLikeLSP7 = (buildContext: () => Promise { + describe('when making a call without any value', () => { + it('should revert', async () => { + await expect( + context.accounts.anyone.sendTransaction({ + to: context.lsp7.address, + }), + ) + .to.be.revertedWithCustomError(context.lsp7, 'InvalidFunctionSelector') + .withArgs('0x00000000'); + }); + }); + + describe('when making a call with sending value', () => { + it('should revert', async () => { + const amountSent = 200; + await expect( + context.accounts.anyone.sendTransaction({ + to: context.lsp7.address, + value: amountSent, + }), + ).to.be.revertedWithCustomError(context.lsp7, 'LSP7TokenContractCannotHoldValue'); + }); + }); + }); }; export type LSP7InitializeTestContext = { diff --git a/tests/LSP7DigitalAsset/LSP7Mintable.behaviour.ts b/tests/LSP7DigitalAsset/LSP7Mintable.behaviour.ts index bdb94bb58..c1e94a27e 100644 --- a/tests/LSP7DigitalAsset/LSP7Mintable.behaviour.ts +++ b/tests/LSP7DigitalAsset/LSP7Mintable.behaviour.ts @@ -54,7 +54,7 @@ export const shouldBehaveLikeLSP7Mintable = ( await context.lsp7Mintable.mint( context.accounts.tokenReceiver.address, amountToMint, - true, // beneficiary is an EOA, so we need to allowNonLSP1Recipient minting + true, // beneficiary is an EOA, so we need to force minting '0x', ); @@ -82,7 +82,7 @@ export const shouldBehaveLikeLSP7Mintable = ( await expect( context.lsp7Mintable.connect(nonOwner).mint(nonOwner.address, amountToMint, true, '0x'), - ).to.be.revertedWith('Ownable: caller is not the owner'); + ).to.be.revertedWithCustomError(context.lsp7Mintable, 'OwnableCallerNotTheOwner'); }); }); diff --git a/tests/LSP7DigitalAsset/proxy/LSP7DigitalAssetInit.test.ts b/tests/LSP7DigitalAsset/proxy/LSP7DigitalAssetInit.test.ts index 84a364410..fc3c599d4 100644 --- a/tests/LSP7DigitalAsset/proxy/LSP7DigitalAssetInit.test.ts +++ b/tests/LSP7DigitalAsset/proxy/LSP7DigitalAssetInit.test.ts @@ -79,7 +79,7 @@ describe('LSP7DigitalAssetInit with proxy', () => { ethers.constants.AddressZero, false, ), - ).to.be.revertedWith('Ownable: new owner is the zero address'); + ).to.be.revertedWithCustomError(context.lsp7, 'OwnableCannotSetZeroAddressAsOwner'); }); describe('when initializing the contract', () => { diff --git a/tests/LSP7DigitalAsset/standard/LSP7DigitalAsset.test.ts b/tests/LSP7DigitalAsset/standard/LSP7DigitalAsset.test.ts index 94869a442..8447ea11b 100644 --- a/tests/LSP7DigitalAsset/standard/LSP7DigitalAsset.test.ts +++ b/tests/LSP7DigitalAsset/standard/LSP7DigitalAsset.test.ts @@ -86,13 +86,11 @@ describe('LSP7DigitalAsset with constructor', () => { newOwner: ethers.constants.AddressZero, }; + const contractToDeploy = new LSP7Tester__factory(accounts[0]); + await expect( - new LSP7Tester__factory(accounts[0]).deploy( - deployParams.name, - deployParams.symbol, - deployParams.newOwner, - ), - ).to.be.revertedWith('Ownable: new owner is the zero address'); + contractToDeploy.deploy(deployParams.name, deployParams.symbol, deployParams.newOwner), + ).to.be.revertedWithCustomError(contractToDeploy, 'OwnableCannotSetZeroAddressAsOwner'); }); describe('once the contract was deployed', () => { diff --git a/tests/LSP8IdentifiableDigitalAsset/LSP8CappedSupply.behaviour.ts b/tests/LSP8IdentifiableDigitalAsset/LSP8CappedSupply.behaviour.ts index 5d68d9645..24cb4ceca 100644 --- a/tests/LSP8IdentifiableDigitalAsset/LSP8CappedSupply.behaviour.ts +++ b/tests/LSP8IdentifiableDigitalAsset/LSP8CappedSupply.behaviour.ts @@ -22,6 +22,7 @@ export type LSP8CappedSupplyTestContext = { name: string; symbol: string; newOwner: string; + tokenIdType: number; tokenSupplyCap: BigNumber; }; }; diff --git a/tests/LSP8IdentifiableDigitalAsset/LSP8CompatibleERC721.behaviour.ts b/tests/LSP8IdentifiableDigitalAsset/LSP8CompatibleERC721.behaviour.ts index c2eb483e5..5fc815c1b 100644 --- a/tests/LSP8IdentifiableDigitalAsset/LSP8CompatibleERC721.behaviour.ts +++ b/tests/LSP8IdentifiableDigitalAsset/LSP8CompatibleERC721.behaviour.ts @@ -49,6 +49,7 @@ type LSP8CompatibleERC721DeployParams = { symbol: string; newOwner: string; lsp4MetadataValue: string; + tokenIdType: number; }; export type LSP8CompatibleERC721TestContext = { @@ -74,6 +75,22 @@ export const shouldBehaveLikeLSP8CompatibleERC721 = ( context = await buildContext(); }); + describe('when setting data', () => { + it('should not allow to update the `LSP8TokenIdType` after deployment', async () => { + await expect( + context.lsp8CompatibleERC721.setData(ERC725YDataKeys.LSP8.LSP8TokenIdType, '0xdeadbeef'), + ).to.be.revertedWithCustomError(context.lsp8CompatibleERC721, 'LSP8TokenIdTypeNotEditable'); + }); + }); + + describe('when setting data', () => { + it('should not allow to update the `LSP8TokenIdType` after deployment', async () => { + await expect( + context.lsp8CompatibleERC721.setData(ERC725YDataKeys.LSP8.LSP8TokenIdType, '0xdeadbeef'), + ).to.be.revertedWithCustomError(context.lsp8CompatibleERC721, 'LSP8TokenIdTypeNotEditable'); + }); + }); + describe('when checking supported ERC165 interfaces', () => { it('should support ERC721', async () => { expect(await context.lsp8CompatibleERC721.supportsInterface(INTERFACE_IDS.ERC721)).to.equal( @@ -731,7 +748,7 @@ export const shouldBehaveLikeLSP8CompatibleERC721 = ( const transferSuccessScenario = async ( { operator, from, to, tokenId, data }: TransferTxParams, transferFn: string, - allowNonLSP1Recipient: boolean, + force: boolean, expectedData: string, ) => { // pre-conditions @@ -749,14 +766,7 @@ export const shouldBehaveLikeLSP8CompatibleERC721 = ( context.lsp8CompatibleERC721, 'Transfer(address,address,address,bytes32,bool,bytes)', ) - .withArgs( - operator, - from, - to, - tokenIdAsBytes32(tokenId), - allowNonLSP1Recipient, - expectedData, - ); + .withArgs(operator, from, to, tokenIdAsBytes32(tokenId), force, expectedData); await expect(tx) .to.emit(context.lsp8CompatibleERC721, 'Transfer(address,address,uint256)') @@ -773,7 +783,7 @@ export const shouldBehaveLikeLSP8CompatibleERC721 = ( describe('transferFrom', () => { const transferFn = 'transferFrom'; - const allowNonLSP1Recipient = true; + const force = true; const expectedData = ethers.utils.hexlify(ethers.utils.toUtf8Bytes('')); describe('when the from address is the tokenId owner', () => { @@ -786,12 +796,7 @@ export const shouldBehaveLikeLSP8CompatibleERC721 = ( tokenId: mintedTokenId, }; - await transferSuccessScenario( - txParams, - transferFn, - allowNonLSP1Recipient, - expectedData, - ); + await transferSuccessScenario(txParams, transferFn, force, expectedData); }); }); @@ -805,12 +810,7 @@ export const shouldBehaveLikeLSP8CompatibleERC721 = ( tokenId: mintedTokenId, }; - await transferSuccessScenario( - txParams, - transferFn, - allowNonLSP1Recipient, - expectedData, - ); + await transferSuccessScenario(txParams, transferFn, force, expectedData); }); }); @@ -823,12 +823,7 @@ export const shouldBehaveLikeLSP8CompatibleERC721 = ( tokenId: mintedTokenId, }; - await transferSuccessScenario( - txParams, - transferFn, - allowNonLSP1Recipient, - expectedData, - ); + await transferSuccessScenario(txParams, transferFn, force, expectedData); }); }); }); @@ -1275,14 +1270,21 @@ export const shouldInitializeLikeLSP8CompatibleERC721 = ( }); describe('when the contract was initialized', () => { - it('should have registered its ERC165 interface', async () => { + it('should support ERC721 interface', async () => { + expect(await context.lsp8CompatibleERC721.supportsInterface(INTERFACE_IDS.ERC721)).to.be.true; + }); + + it('should support ERC721Metadata interface', async () => { + expect(await context.lsp8CompatibleERC721.supportsInterface(INTERFACE_IDS.ERC721Metadata)).to + .be.true; + }); + + it('should support LSP8 interface', async () => { expect( await context.lsp8CompatibleERC721.supportsInterface( INTERFACE_IDS.LSP8IdentifiableDigitalAsset, ), - ); - expect(await context.lsp8CompatibleERC721.supportsInterface(INTERFACE_IDS.ERC721)); - expect(await context.lsp8CompatibleERC721.supportsInterface(INTERFACE_IDS.ERC721Metadata)); + ).to.be.true; }); it('should have set expected entries with ERC725Y.setData', async () => { @@ -1314,5 +1316,33 @@ export const shouldInitializeLikeLSP8CompatibleERC721 = ( .withArgs(symbolKey, expectedSymbolValue); expect(await context.lsp8CompatibleERC721.getData(symbolKey)).to.equal(expectedSymbolValue); }); + + describe('when using the functions from IERC721Metadata', () => { + it('should allow reading `name()`', async () => { + // using compatibility getter -> returns(string) + const nameAsString = await context.lsp8CompatibleERC721.name(); + expect(nameAsString).to.equal(context.deployParams.name); + + // using getData -> returns(bytes) + const nameAsBytes = await context.lsp8CompatibleERC721.getData( + ethers.utils.keccak256(ethers.utils.toUtf8Bytes('LSP4TokenName')), + ); + + expect(ethers.utils.toUtf8String(nameAsBytes)).to.equal(context.deployParams.name); + }); + + it('should allow reading `symbol()`', async () => { + // using compatibility getter -> returns(string) + const symbolAsString = await context.lsp8CompatibleERC721.symbol(); + expect(symbolAsString).to.equal(context.deployParams.symbol); + + // using getData -> returns(bytes) + const symbolAsBytes = await context.lsp8CompatibleERC721.getData( + ethers.utils.keccak256(ethers.utils.toUtf8Bytes('LSP4TokenSymbol')), + ); + + expect(ethers.utils.toUtf8String(symbolAsBytes)).to.equal(context.deployParams.symbol); + }); + }); }); }; diff --git a/tests/LSP8IdentifiableDigitalAsset/LSP8Enumerable.behaviour.ts b/tests/LSP8IdentifiableDigitalAsset/LSP8Enumerable.behaviour.ts index 5c567be7f..0be64bb05 100644 --- a/tests/LSP8IdentifiableDigitalAsset/LSP8Enumerable.behaviour.ts +++ b/tests/LSP8IdentifiableDigitalAsset/LSP8Enumerable.behaviour.ts @@ -18,6 +18,7 @@ export type LSP8EnumerableDeployParams = { name: string; symbol: string; newOwner: string; + tokenIdType: number; }; export type LSP8EnumerableTestContext = { diff --git a/tests/LSP8IdentifiableDigitalAsset/LSP8IdentifiableDigitalAsset.behaviour.ts b/tests/LSP8IdentifiableDigitalAsset/LSP8IdentifiableDigitalAsset.behaviour.ts index 05be3dd17..b0508f91f 100644 --- a/tests/LSP8IdentifiableDigitalAsset/LSP8IdentifiableDigitalAsset.behaviour.ts +++ b/tests/LSP8IdentifiableDigitalAsset/LSP8IdentifiableDigitalAsset.behaviour.ts @@ -42,6 +42,7 @@ export type LSP8DeployParams = { name: string; symbol: string; newOwner: string; + tokenIdType: number; }; export type LSP8TestContext = { @@ -58,12 +59,30 @@ export type ExpectedError = { const mintedTokenId = tokenIdAsBytes32(10); const neverMintedTokenId = tokenIdAsBytes32(1010110); -export const shouldBehaveLikeLSP8 = (buildContext: () => Promise) => { +export const shouldBehaveLikeLSP8 = ( + buildContext: (nftType: number) => Promise, +) => { let context: LSP8TestContext; let expectedTotalSupply = 0; before(async () => { - context = await buildContext(); + context = await buildContext(0); + }); + + describe('when setting data', () => { + it('should not allow to update the `LSP8TokenIdType` after deployment', async () => { + await expect( + context.lsp8.setData(ERC725YDataKeys.LSP8.LSP8TokenIdType, '0xdeadbeef'), + ).to.be.revertedWithCustomError(context.lsp8, 'LSP8TokenIdTypeNotEditable'); + }); + }); + + describe('when setting data', () => { + it('should not allow to update the `LSP8TokenIdType` after deployment', async () => { + await expect( + context.lsp8.setData(ERC725YDataKeys.LSP8.LSP8TokenIdType, '0xdeadbeef'), + ).to.be.revertedWithCustomError(context.lsp8, 'LSP8TokenIdTypeNotEditable'); + }); }); describe('when minting tokens', () => { @@ -83,17 +102,12 @@ export const shouldBehaveLikeLSP8 = (buildContext: () => Promise Promise Promise Promise { - context = await buildContext(); + context = await buildContext(0); // mint a tokenId await context.lsp8.mint( @@ -664,12 +668,12 @@ export const shouldBehaveLikeLSP8 = (buildContext: () => Promise { // pre-conditions @@ -692,12 +696,10 @@ export const shouldBehaveLikeLSP8 = (buildContext: () => Promise Promise { - const allowNonLSP1Recipient = true; + describe('when using force=true', () => { + const force = true; const data = ethers.utils.hexlify( - ethers.utils.toUtf8Bytes('doing a transfer with allowNonLSP1Recipient'), + ethers.utils.toUtf8Bytes('doing a transfer with force'), ); describe('when `to` is an EOA', () => { @@ -750,7 +752,7 @@ export const shouldBehaveLikeLSP8 = (buildContext: () => Promise Promise Promise Promise Promise Promise Promise Promise { - const allowNonLSP1Recipient = false; + describe('when force=false', () => { + const force = false; const data = ethers.utils.hexlify( - ethers.utils.toUtf8Bytes('doing a transfer without allowNonLSP1Recipient'), + ethers.utils.toUtf8Bytes('doing a transfer without force'), ); describe('when `to` is an EOA', () => { @@ -860,7 +862,7 @@ export const shouldBehaveLikeLSP8 = (buildContext: () => Promise Promise Promise Promise Promise Promise Promise Promise Promise Promise Promise Promise Promise Promise { // pre-conditions @@ -1107,7 +1109,7 @@ export const shouldBehaveLikeLSP8 = (buildContext: () => Promise { @@ -1118,7 +1120,7 @@ export const shouldBehaveLikeLSP8 = (buildContext: () => Promise Promise { @@ -1164,18 +1166,12 @@ export const shouldBehaveLikeLSP8 = (buildContext: () => Promise 0) - await expect( - context.lsp8 - .connect(operator) - .transferBatch(from, to, tokenId, allowNonLSP1Recipient, data), - ) + await expect(context.lsp8.connect(operator).transferBatch(from, to, tokenId, force, data)) .to.be.revertedWithCustomError(context.lsp8, expectedError.error) .withArgs(...expectedError.args); else await expect( - context.lsp8 - .connect(operator) - .transferBatch(from, to, tokenId, allowNonLSP1Recipient, data), + context.lsp8.connect(operator).transferBatch(from, to, tokenId, force, data), ).to.be.revertedWithCustomError(context.lsp8, expectedError.error); }; @@ -1186,9 +1182,9 @@ export const shouldBehaveLikeLSP8 = (buildContext: () => Promise { + describe('when force=true', () => { const data = ethers.utils.hexlify( - ethers.utils.toUtf8Bytes('doing a transfer with allowNonLSP1Recipient'), + ethers.utils.toUtf8Bytes('doing a transfer with force'), ); describe('when `to` is an EOA', () => { @@ -1198,7 +1194,7 @@ export const shouldBehaveLikeLSP8 = (buildContext: () => Promise Promise Promise Promise Promise { + describe('when force=false', () => { const data = ethers.utils.hexlify( - ethers.utils.toUtf8Bytes('doing a transfer without allowNonLSP1Recipient'), + ethers.utils.toUtf8Bytes('doing a transfer without force'), ); describe('when `to` is an EOA', () => { @@ -1297,7 +1293,7 @@ export const shouldBehaveLikeLSP8 = (buildContext: () => Promise Promise Promise Promise { + describe('when force is mixed(true/false) respectively', () => { const data = ethers.utils.hexlify( - ethers.utils.toUtf8Bytes('doing a transfer without allowNonLSP1Recipient'), + ethers.utils.toUtf8Bytes('doing a transfer without force'), ); describe('when `to` is an EOA', () => { @@ -1364,7 +1360,7 @@ export const shouldBehaveLikeLSP8 = (buildContext: () => Promise Promise Promise { - it('should pass regardless of allowNonLSP1Recipient params', async () => { + it('should pass regardless of force params', async () => { const txParams = { from: [context.accounts.owner.address, context.accounts.owner.address], to: [ @@ -1408,7 +1404,7 @@ export const shouldBehaveLikeLSP8 = (buildContext: () => Promise Promise Promise Promise Promise Promise { beforeEach(async () => { - context = await buildContext(); + context = await buildContext(0); await context.lsp8.mint( context.accounts.owner.address, @@ -1720,7 +1716,7 @@ export const shouldBehaveLikeLSP8 = (buildContext: () => Promise { - context = await buildContext(); + context = await buildContext(0); oldOwner = context.accounts.owner; newOwner = context.accounts.anyone; }); @@ -1729,7 +1725,7 @@ export const shouldBehaveLikeLSP8 = (buildContext: () => Promise { @@ -1739,7 +1735,7 @@ export const shouldBehaveLikeLSP8 = (buildContext: () => Promise { beforeEach(async () => { - context = await buildContext(); + context = await buildContext(0); await context.lsp8.connect(oldOwner).transferOwnership(newOwner.address); }); @@ -1748,21 +1744,21 @@ export const shouldBehaveLikeLSP8 = (buildContext: () => Promise { - await expect(context.lsp8.connect(oldOwner).renounceOwnership()).to.be.revertedWith( - 'Ownable: caller is not the owner', - ); + await expect( + context.lsp8.connect(oldOwner).renounceOwnership(), + ).to.be.revertedWithCustomError(context.lsp8, 'OwnableCallerNotTheOwner'); }); it('old owner should not be allowed to use `setData(..)`', async () => { const key = ethers.utils.keccak256(ethers.utils.toUtf8Bytes('key')); const value = ethers.utils.keccak256(ethers.utils.toUtf8Bytes('value')); - await expect(context.lsp8.connect(oldOwner).setData(key, value)).to.be.revertedWith( - 'Ownable: caller is not the owner', - ); + await expect( + context.lsp8.connect(oldOwner).setData(key, value), + ).to.be.revertedWithCustomError(context.lsp8, 'OwnableCallerNotTheOwner'); }); it('new owner should be allowed to use `transferOwnership(..)`', async () => { @@ -1788,6 +1784,32 @@ export const shouldBehaveLikeLSP8 = (buildContext: () => Promise { + describe('when making a call without any value', () => { + it('should revert', async () => { + await expect( + context.accounts.anyone.sendTransaction({ + to: context.lsp8.address, + }), + ) + .to.be.revertedWithCustomError(context.lsp8, 'InvalidFunctionSelector') + .withArgs('0x00000000'); + }); + }); + + describe('when making a call with sending value', () => { + it('should revert', async () => { + const amountSent = 200; + await expect( + context.accounts.anyone.sendTransaction({ + to: context.lsp8.address, + value: amountSent, + }), + ).to.be.revertedWithCustomError(context.lsp8, 'LSP8TokenContractCannotHoldValue'); + }); + }); + }); }; export type LSP8InitializeTestContext = { @@ -1850,6 +1872,16 @@ export const shouldInitializeLikeLSP8 = ( .to.emit(context.lsp8, 'DataChanged') .withArgs(symbolKey, expectedSymbolValue); expect(await context.lsp8.getData(symbolKey)).to.equal(expectedSymbolValue); + + const lsp8TokenIdTypeDataKey = ERC725YDataKeys.LSP8['LSP8TokenIdType']; + const expectedTokenIdDataValue = abiCoder.encode( + ['uint256'], + [context.deployParams.tokenIdType], + ); + await expect(context.initializeTransaction) + .to.emit(context.lsp8, 'DataChanged') + .withArgs(lsp8TokenIdTypeDataKey, expectedTokenIdDataValue); + expect(await context.lsp8.getData(lsp8TokenIdTypeDataKey)).to.equal(expectedTokenIdDataValue); }); }); }; diff --git a/tests/LSP8IdentifiableDigitalAsset/LSP8Mintable.behaviour.ts b/tests/LSP8IdentifiableDigitalAsset/LSP8Mintable.behaviour.ts index 0d5c5151a..da558a2ca 100644 --- a/tests/LSP8IdentifiableDigitalAsset/LSP8Mintable.behaviour.ts +++ b/tests/LSP8IdentifiableDigitalAsset/LSP8Mintable.behaviour.ts @@ -28,6 +28,7 @@ export type LSP8MintableDeployParams = { name: string; symbol: string; newOwner: string; + tokenIdType: number; }; export type LSP8MintableTestContext = { @@ -54,7 +55,7 @@ export const shouldBehaveLikeLSP8Mintable = ( await context.lsp8Mintable.mint( context.accounts.tokenReceiver.address, randomTokenId, - true, // beneficiary is an EOA, so we need to allowNonLSP1Recipient minting + true, // beneficiary is an EOA, so we need to force minting '0x', ); @@ -82,7 +83,7 @@ export const shouldBehaveLikeLSP8Mintable = ( context.lsp8Mintable .connect(nonOwner) .mint(context.accounts.tokenReceiver.address, randomTokenId, true, '0x'), - ).to.be.revertedWith('Ownable: caller is not the owner'); + ).to.be.revertedWithCustomError(context.lsp8Mintable, 'OwnableCallerNotTheOwner'); }); }); diff --git a/tests/LSP8IdentifiableDigitalAsset/proxy/LSP8BurnableInit.test.ts b/tests/LSP8IdentifiableDigitalAsset/proxy/LSP8BurnableInit.test.ts index 208769948..f6d1cf7db 100644 --- a/tests/LSP8IdentifiableDigitalAsset/proxy/LSP8BurnableInit.test.ts +++ b/tests/LSP8IdentifiableDigitalAsset/proxy/LSP8BurnableInit.test.ts @@ -7,6 +7,7 @@ import { LSP8BurnableInitTester, LSP8BurnableInitTester__factory } from '../../. import { shouldInitializeLikeLSP8 } from '../LSP8IdentifiableDigitalAsset.behaviour'; import { deployProxy } from '../../utils/fixtures'; +import { LSP8_TOKEN_ID_TYPES } from '../../../constants'; type LSP8BurnableInitTestContext = { accounts: SignerWithAddress[]; @@ -15,6 +16,7 @@ type LSP8BurnableInitTestContext = { name: string; symbol: string; newOwner: string; + tokenIdType: number; }; }; @@ -25,6 +27,7 @@ describe('LSP8BurnableInit with proxy', () => { name: 'LSP8 Burnable - deployed with constructor', symbol: 'BRN', newOwner: accounts[0].address, + tokenIdType: LSP8_TOKEN_ID_TYPES.NUMBER, }; const lsp8BurnableImplementation = await new LSP8BurnableInitTester__factory( @@ -41,6 +44,7 @@ describe('LSP8BurnableInit with proxy', () => { context.deployParams.name, context.deployParams.symbol, context.deployParams.newOwner, + context.deployParams.tokenIdType, ); }; diff --git a/tests/LSP8IdentifiableDigitalAsset/proxy/LSP8CappedSupplyInit.test.ts b/tests/LSP8IdentifiableDigitalAsset/proxy/LSP8CappedSupplyInit.test.ts index 18dfb8c16..b1091b1b4 100644 --- a/tests/LSP8IdentifiableDigitalAsset/proxy/LSP8CappedSupplyInit.test.ts +++ b/tests/LSP8IdentifiableDigitalAsset/proxy/LSP8CappedSupplyInit.test.ts @@ -10,6 +10,7 @@ import { } from '../LSP8CappedSupply.behaviour'; import { deployProxy } from '../../utils/fixtures'; +import { LSP8_TOKEN_ID_TYPES } from '../../../constants'; describe('LSP8CappedSupplyInit with proxy', () => { const buildTestContext = async () => { @@ -18,6 +19,7 @@ describe('LSP8CappedSupplyInit with proxy', () => { name: 'LSP8 capped supply - deployed with proxy', symbol: 'CAP', newOwner: accounts.owner.address, + tokenIdType: LSP8_TOKEN_ID_TYPES.NUMBER, tokenSupplyCap: ethers.BigNumber.from('2'), }; const lsp8CappedSupplyInit = await new LSP8CappedSupplyInitTester__factory( @@ -30,10 +32,11 @@ describe('LSP8CappedSupplyInit with proxy', () => { }; const initializeProxy = async (context: LSP8CappedSupplyTestContext) => { - return context.lsp8CappedSupply['initialize(string,string,address,uint256)']( + return context.lsp8CappedSupply['initialize(string,string,address,uint256,uint256)']( context.deployParams.name, context.deployParams.symbol, context.deployParams.newOwner, + context.deployParams.tokenIdType, context.deployParams.tokenSupplyCap, ); }; diff --git a/tests/LSP8IdentifiableDigitalAsset/proxy/LSP8CompatibleERC721Init.test.ts b/tests/LSP8IdentifiableDigitalAsset/proxy/LSP8CompatibleERC721Init.test.ts index fc0229f59..cf444cca8 100644 --- a/tests/LSP8IdentifiableDigitalAsset/proxy/LSP8CompatibleERC721Init.test.ts +++ b/tests/LSP8IdentifiableDigitalAsset/proxy/LSP8CompatibleERC721Init.test.ts @@ -14,6 +14,7 @@ import { } from '../LSP8CompatibleERC721.behaviour'; import { deployProxy } from '../../utils/fixtures'; +import { LSP8_TOKEN_ID_TYPES } from '../../../constants'; describe('LSP8CompatibleERC721Init with proxy', () => { const buildTestContext = async (): Promise => { @@ -31,6 +32,7 @@ describe('LSP8CompatibleERC721Init with proxy', () => { name: 'LSP8 - deployed with constructor', symbol: 'NFT', newOwner: accounts.owner.address, + tokenIdType: LSP8_TOKEN_ID_TYPES.NUMBER, lsp4MetadataValue, }; @@ -47,10 +49,11 @@ describe('LSP8CompatibleERC721Init with proxy', () => { }; const initializeProxy = async (context: LSP8CompatibleERC721TestContext) => { - return context.lsp8CompatibleERC721['initialize(string,string,address,bytes)']( + return context.lsp8CompatibleERC721['initialize(string,string,address,uint256,bytes)']( context.deployParams.name, context.deployParams.symbol, context.deployParams.newOwner, + context.deployParams.tokenIdType, context.deployParams.lsp4MetadataValue, ); }; @@ -65,10 +68,11 @@ describe('LSP8CompatibleERC721Init with proxy', () => { const randomCaller = accounts[1]; await expect( - lsp8CompatibilityForERC721TesterInit['initialize(string,string,address,bytes)']( + lsp8CompatibilityForERC721TesterInit['initialize(string,string,address,uint256,bytes)']( 'XXXXXXXXXXX', 'XXX', randomCaller.address, + 0, '0x', ), ).to.be.revertedWith('Initializable: contract is already initialized'); @@ -84,11 +88,7 @@ describe('LSP8CompatibleERC721Init with proxy', () => { const randomCaller = accounts[1]; await expect( - lsp8CompatibleERC721MintableInit['initialize(string,string,address)']( - 'XXXXXXXXXXX', - 'XXX', - randomCaller.address, - ), + lsp8CompatibleERC721MintableInit.initialize('XXXXXXXXXXX', 'XXX', randomCaller.address, 0), ).to.be.revertedWith('Initializable: contract is already initialized'); }); }); diff --git a/tests/LSP8IdentifiableDigitalAsset/proxy/LSP8EnumerableInit.test.ts b/tests/LSP8IdentifiableDigitalAsset/proxy/LSP8EnumerableInit.test.ts index ba3ed1bc8..7aae65422 100644 --- a/tests/LSP8IdentifiableDigitalAsset/proxy/LSP8EnumerableInit.test.ts +++ b/tests/LSP8IdentifiableDigitalAsset/proxy/LSP8EnumerableInit.test.ts @@ -9,6 +9,7 @@ import { } from '../LSP8Enumerable.behaviour'; import { deployProxy } from '../../utils/fixtures'; +import { LSP8_TOKEN_ID_TYPES } from '../../../constants'; describe('LSP8EnumerableInit with proxy', () => { const buildTestContext = async () => { @@ -17,6 +18,7 @@ describe('LSP8EnumerableInit with proxy', () => { name: 'LSP8 Enumerable - deployed with proxy', symbol: 'LSP8 NMRBL', newOwner: accounts.owner.address, + tokenIdType: LSP8_TOKEN_ID_TYPES.NUMBER, }; const LSP8EnumerableInit: LSP8EnumerableInitTester = @@ -29,10 +31,11 @@ describe('LSP8EnumerableInit with proxy', () => { }; const initializeProxy = async (context: LSP8EnumerableTestContext) => { - return context.lsp8Enumerable['initialize(string,string,address)']( + return context.lsp8Enumerable['initialize(string,string,address,uint256)']( context.deployParams.name, context.deployParams.symbol, context.deployParams.newOwner, + context.deployParams.tokenIdType, ); }; diff --git a/tests/LSP8IdentifiableDigitalAsset/proxy/LSP8IdentifiableDigitalAssetInit.test.ts b/tests/LSP8IdentifiableDigitalAsset/proxy/LSP8IdentifiableDigitalAssetInit.test.ts index faaceab15..d016e2167 100644 --- a/tests/LSP8IdentifiableDigitalAsset/proxy/LSP8IdentifiableDigitalAssetInit.test.ts +++ b/tests/LSP8IdentifiableDigitalAsset/proxy/LSP8IdentifiableDigitalAssetInit.test.ts @@ -18,12 +18,13 @@ import { import { deployProxy } from '../../utils/fixtures'; describe('LSP8IdentifiableDigitalAssetInit with proxy', () => { - const buildTestContext = async (): Promise => { + const buildTestContext = async (nftType: number): Promise => { const accounts = await getNamedAccounts(); const deployParams = { name: 'LSP8 - deployed with constructor', symbol: 'NFT', newOwner: accounts.owner.address, + tokenIdType: nftType, }; const lsp8TesterInit = await new LSP8InitTester__factory(accounts.owner).deploy(); @@ -35,7 +36,7 @@ describe('LSP8IdentifiableDigitalAssetInit with proxy', () => { const buildLSP4DigitalAssetMetadataTestContext = async (): Promise => { - const { lsp8 } = await buildTestContext(); + const { lsp8 } = await buildTestContext(0); const accounts = await ethers.getSigners(); const deployParams = { @@ -50,10 +51,11 @@ describe('LSP8IdentifiableDigitalAssetInit with proxy', () => { }; const initializeProxy = async (context: LSP8TestContext) => { - return context.lsp8['initialize(string,string,address)']( + return context.lsp8['initialize(string,string,address,uint256)']( context.deployParams.name, context.deployParams.symbol, context.deployParams.newOwner, + context.deployParams.tokenIdType, ); }; @@ -61,17 +63,18 @@ describe('LSP8IdentifiableDigitalAssetInit with proxy', () => { let context: LSP8TestContext; before(async () => { - context = await buildTestContext(); + context = await buildTestContext(0); }); it('should revert when initializing with address(0) as owner', async () => { await expect( - context.lsp8['initialize(string,string,address)']( + context.lsp8['initialize(string,string,address,uint256)']( context.deployParams.name, context.deployParams.symbol, ethers.constants.AddressZero, + 0, ), - ).to.be.revertedWith('Ownable: new owner is the zero address'); + ).to.be.revertedWithCustomError(context.lsp8, 'OwnableCannotSetZeroAddressAsOwner'); }); describe('when initializing the contract', () => { @@ -100,17 +103,18 @@ describe('LSP8IdentifiableDigitalAssetInit with proxy', () => { shouldBehaveLikeLSP4DigitalAssetMetadata(async () => { const lsp4Context = await buildLSP4DigitalAssetMetadataTestContext(); - await lsp4Context.contract['initialize(string,string,address)']( + await lsp4Context.contract['initialize(string,string,address,uint256)']( 'LSP8 - deployed with proxy', 'NFT', lsp4Context.deployParams.owner.address, + 0, ); return lsp4Context; }); shouldBehaveLikeLSP8(() => - buildTestContext().then(async (context) => { + buildTestContext(0).then(async (context) => { await initializeProxy(context); return context; diff --git a/tests/LSP8IdentifiableDigitalAsset/proxy/LSP8MintableInit.test.ts b/tests/LSP8IdentifiableDigitalAsset/proxy/LSP8MintableInit.test.ts index 96cd82060..aa8062b55 100644 --- a/tests/LSP8IdentifiableDigitalAsset/proxy/LSP8MintableInit.test.ts +++ b/tests/LSP8IdentifiableDigitalAsset/proxy/LSP8MintableInit.test.ts @@ -10,6 +10,7 @@ import { } from '../LSP8Mintable.behaviour'; import { deployProxy } from '../../utils/fixtures'; +import { LSP8_TOKEN_ID_TYPES } from '../../../constants'; describe('LSP8MintableInit with proxy', () => { const buildTestContext = async () => { @@ -18,6 +19,7 @@ describe('LSP8MintableInit with proxy', () => { name: 'LSP8 Mintable - deployed with proxy', symbol: 'MNTBL', newOwner: accounts.owner.address, + tokenIdType: LSP8_TOKEN_ID_TYPES.NUMBER, }; const LSP8MintableInit: LSP8MintableInit = await new LSP8MintableInit__factory( @@ -31,10 +33,11 @@ describe('LSP8MintableInit with proxy', () => { }; const initializeProxy = async (context: LSP8MintableTestContext) => { - return context.lsp8Mintable['initialize(string,string,address)']( + return context.lsp8Mintable['initialize(string,string,address,uint256)']( context.deployParams.name, context.deployParams.symbol, context.deployParams.newOwner, + context.deployParams.tokenIdType, ); }; @@ -47,10 +50,11 @@ describe('LSP8MintableInit with proxy', () => { const randomCaller = accounts[1]; await expect( - lsp8Mintable['initialize(string,string,address)']( + lsp8Mintable['initialize(string,string,address,uint256)']( 'XXXXXXXXXXX', 'XXX', randomCaller.address, + 0, ), ).to.be.revertedWith('Initializable: contract is already initialized'); }); diff --git a/tests/LSP8IdentifiableDigitalAsset/standard/LSP8Burnable.test.ts b/tests/LSP8IdentifiableDigitalAsset/standard/LSP8Burnable.test.ts index e15e676f8..4170d7959 100644 --- a/tests/LSP8IdentifiableDigitalAsset/standard/LSP8Burnable.test.ts +++ b/tests/LSP8IdentifiableDigitalAsset/standard/LSP8Burnable.test.ts @@ -4,6 +4,7 @@ import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'; import { LSP8BurnableTester, LSP8BurnableTester__factory } from '../../../types'; import { shouldInitializeLikeLSP8 } from '../LSP8IdentifiableDigitalAsset.behaviour'; +import { LSP8_TOKEN_ID_TYPES } from '../../../constants'; type LSP8BurnableTestContext = { accounts: SignerWithAddress[]; @@ -12,6 +13,7 @@ type LSP8BurnableTestContext = { name: string; symbol: string; newOwner: string; + tokenIdType: number; }; }; @@ -22,12 +24,14 @@ describe('LSP8Burnable with constructor', () => { name: 'LSP8 Burnable - deployed with constructor', symbol: 'BRN', newOwner: accounts[0].address, + tokenIdType: LSP8_TOKEN_ID_TYPES.NUMBER, }; const lsp8Burnable = await new LSP8BurnableTester__factory(accounts[0]).deploy( deployParams.name, deployParams.symbol, deployParams.newOwner, + deployParams.tokenIdType, ); return { accounts, lsp8Burnable, deployParams }; diff --git a/tests/LSP8IdentifiableDigitalAsset/standard/LSP8CappedSupply.test.ts b/tests/LSP8IdentifiableDigitalAsset/standard/LSP8CappedSupply.test.ts index c92f74a7d..9d3c85e41 100644 --- a/tests/LSP8IdentifiableDigitalAsset/standard/LSP8CappedSupply.test.ts +++ b/tests/LSP8IdentifiableDigitalAsset/standard/LSP8CappedSupply.test.ts @@ -8,6 +8,7 @@ import { LSP8CappedSupplyTestContext, getNamedAccounts, } from '../LSP8CappedSupply.behaviour'; +import { LSP8_TOKEN_ID_TYPES } from '../../../constants'; describe('LSP8CappedSupply with constructor', () => { const buildTestContext = async () => { @@ -16,12 +17,14 @@ describe('LSP8CappedSupply with constructor', () => { name: 'LSP8 capped supply - deployed with constructor', symbol: 'CAP', newOwner: accounts.owner.address, + tokenIdType: LSP8_TOKEN_ID_TYPES.NUMBER, tokenSupplyCap: ethers.BigNumber.from('2'), }; const lsp8CappedSupply = await new LSP8CappedSupplyTester__factory(accounts.owner).deploy( deployParams.name, deployParams.symbol, deployParams.newOwner, + deployParams.tokenIdType, deployParams.tokenSupplyCap, ); diff --git a/tests/LSP8IdentifiableDigitalAsset/standard/LSP8CompatibleERC721.test.ts b/tests/LSP8IdentifiableDigitalAsset/standard/LSP8CompatibleERC721.test.ts index f24298b47..e55499f8f 100644 --- a/tests/LSP8IdentifiableDigitalAsset/standard/LSP8CompatibleERC721.test.ts +++ b/tests/LSP8IdentifiableDigitalAsset/standard/LSP8CompatibleERC721.test.ts @@ -8,6 +8,7 @@ import { shouldInitializeLikeLSP8CompatibleERC721, LSP8CompatibleERC721TestContext, } from '../LSP8CompatibleERC721.behaviour'; +import { LSP8_TOKEN_ID_TYPES } from '../../../constants'; describe('LSP8CompatibleERC721 with constructor', () => { const buildTestContext = async (): Promise => { @@ -25,6 +26,7 @@ describe('LSP8CompatibleERC721 with constructor', () => { name: 'Compat for ERC721', symbol: 'NFT', newOwner: accounts.owner.address, + tokenIdType: LSP8_TOKEN_ID_TYPES.NUMBER, lsp4MetadataValue, }; @@ -34,6 +36,7 @@ describe('LSP8CompatibleERC721 with constructor', () => { deployParams.name, deployParams.symbol, deployParams.newOwner, + deployParams.tokenIdType, deployParams.lsp4MetadataValue, ); diff --git a/tests/LSP8IdentifiableDigitalAsset/standard/LSP8Enumerable.test.ts b/tests/LSP8IdentifiableDigitalAsset/standard/LSP8Enumerable.test.ts index ff35676d4..e78b97336 100644 --- a/tests/LSP8IdentifiableDigitalAsset/standard/LSP8Enumerable.test.ts +++ b/tests/LSP8IdentifiableDigitalAsset/standard/LSP8Enumerable.test.ts @@ -6,6 +6,7 @@ import { LSP8EnumerableTestContext, getNamedAccounts, } from '../LSP8Enumerable.behaviour'; +import { LSP8_TOKEN_ID_TYPES } from '../../../constants'; describe('LSP8Enumerable with constructor', () => { const buildTestContext = async () => { @@ -15,11 +16,17 @@ describe('LSP8Enumerable with constructor', () => { name: 'LSP8 Enumerable - deployed with constructor', symbol: 'LSP8 NMRBL', newOwner: accounts.owner.address, + tokenIdType: LSP8_TOKEN_ID_TYPES.NUMBER, }; const lsp8Enumerable: LSP8EnumerableTester = await new LSP8EnumerableTester__factory( accounts.owner, - ).deploy(deployParams.name, deployParams.symbol, deployParams.newOwner); + ).deploy( + deployParams.name, + deployParams.symbol, + deployParams.newOwner, + deployParams.tokenIdType, + ); return { accounts, lsp8Enumerable, deployParams }; }; diff --git a/tests/LSP8IdentifiableDigitalAsset/standard/LSP8IdentifiableDigitalAsset.test.ts b/tests/LSP8IdentifiableDigitalAsset/standard/LSP8IdentifiableDigitalAsset.test.ts index e1263a0e2..3d5d14d07 100644 --- a/tests/LSP8IdentifiableDigitalAsset/standard/LSP8IdentifiableDigitalAsset.test.ts +++ b/tests/LSP8IdentifiableDigitalAsset/standard/LSP8IdentifiableDigitalAsset.test.ts @@ -19,19 +19,22 @@ import { LS4DigitalAssetMetadataTestContext, shouldBehaveLikeLSP4DigitalAssetMetadata, } from '../../LSP4DigitalAssetMetadata/LSP4DigitalAssetMetadata.behaviour'; +import { LSP8_TOKEN_ID_TYPES } from '../../../constants'; describe('LSP8IdentifiableDigitalAsset with constructor', () => { - const buildTestContext = async (): Promise => { + const buildTestContext = async (nftType: number): Promise => { const accounts = await getNamedAccounts(); const deployParams = { name: 'LSP8 - deployed with constructor', symbol: 'NFT', newOwner: accounts.owner.address, + tokenIdType: nftType, }; const lsp8 = await new LSP8Tester__factory(accounts.owner).deploy( deployParams.name, deployParams.symbol, deployParams.newOwner, + deployParams.tokenIdType, ); return { accounts, lsp8, deployParams }; @@ -39,7 +42,7 @@ describe('LSP8IdentifiableDigitalAsset with constructor', () => { const buildLSP4DigitalAssetMetadataTestContext = async (): Promise => { - const { lsp8 } = await buildTestContext(); + const { lsp8 } = await buildTestContext(LSP8_TOKEN_ID_TYPES.NUMBER); const accounts = await ethers.getSigners(); const deployParams = { @@ -60,11 +63,13 @@ describe('LSP8IdentifiableDigitalAsset with constructor', () => { name: 'LSP8 - deployed with constructor', symbol: 'NFT', owner: accounts[0], + tokenIdType: LSP8_TOKEN_ID_TYPES.NUMBER, }; const contract = await new LSP8Tester__factory(accounts[0]).deploy( deployParams.name, deployParams.symbol, deployParams.owner.address, + deployParams.tokenIdType, ); return { accounts, contract, deployParams }; @@ -80,20 +85,23 @@ describe('LSP8IdentifiableDigitalAsset with constructor', () => { newOwner: ethers.constants.AddressZero, }; + const contractToDeploy = new LSP8Tester__factory(accounts[0]); + await expect( - new LSP8Tester__factory(accounts[0]).deploy( + contractToDeploy.deploy( deployParams.name, deployParams.symbol, ethers.constants.AddressZero, + LSP8_TOKEN_ID_TYPES.NUMBER, ), - ).to.be.revertedWith('Ownable: new owner is the zero address'); + ).to.be.revertedWithCustomError(contractToDeploy, 'OwnableCannotSetZeroAddressAsOwner'); }); describe('once the contract was deployed', () => { let context: LSP8TestContext; before(async () => { - context = await buildTestContext(); + context = await buildTestContext(0); }); shouldInitializeLikeLSP8(async () => { diff --git a/tests/LSP8IdentifiableDigitalAsset/standard/LSP8Mintable.test.ts b/tests/LSP8IdentifiableDigitalAsset/standard/LSP8Mintable.test.ts index 81409c007..79b77d849 100644 --- a/tests/LSP8IdentifiableDigitalAsset/standard/LSP8Mintable.test.ts +++ b/tests/LSP8IdentifiableDigitalAsset/standard/LSP8Mintable.test.ts @@ -1,3 +1,4 @@ +import { LSP8_TOKEN_ID_TYPES } from '../../../constants'; import { LSP8Mintable, LSP8Mintable__factory } from '../../../types'; import { shouldInitializeLikeLSP8 } from '../LSP8IdentifiableDigitalAsset.behaviour'; @@ -15,12 +16,14 @@ describe('LSP8Mintable with constructor', () => { name: 'LSP8 Mintable - deployed with constructor', symbol: 'LSP8 MNTBL', newOwner: accounts.owner.address, + tokenIdType: LSP8_TOKEN_ID_TYPES.NUMBER, }; const lsp8Mintable: LSP8Mintable = await new LSP8Mintable__factory(accounts.owner).deploy( deployParams.name, deployParams.symbol, deployParams.newOwner, + deployParams.tokenIdType, ); return { accounts, lsp8Mintable, deployParams }; diff --git a/tests/LSP9Vault/LSP9Vault.behaviour.ts b/tests/LSP9Vault/LSP9Vault.behaviour.ts index 3a0c93981..771ee6b56 100644 --- a/tests/LSP9Vault/LSP9Vault.behaviour.ts +++ b/tests/LSP9Vault/LSP9Vault.behaviour.ts @@ -104,7 +104,7 @@ export const shouldBehaveLikeLSP9 = ( await expect( lsp1UniversalReceiverDelegateVaultSetter .connect(context.accounts.anyone) - .universalReceiver(context.lsp9Vault.address, dataKey, dataValue), + .universalReceiverDelegate(context.lsp9Vault.address, dataKey, dataValue), ).to.be.revertedWith('Only Owner or reentered Universal Receiver Delegate allowed'); }); diff --git a/tests/LSP9Vault/LSP9Vault.test.ts b/tests/LSP9Vault/LSP9Vault.test.ts index ad2f28ed5..98d70c3a2 100644 --- a/tests/LSP9Vault/LSP9Vault.test.ts +++ b/tests/LSP9Vault/LSP9Vault.test.ts @@ -59,13 +59,13 @@ describe('LSP9Vault with constructor', () => { value: initialFunding, }); - const onlyOwnerRevertString = 'Only Owner or reentered Universal Receiver Delegate allowed'; + const onlyOwnerCustomError = 'Only Owner or reentered Universal Receiver Delegate allowed'; return { accounts, contract: lsp9Vault, deployParams, - onlyOwnerRevertString, + onlyOwnerCustomError, }; }; diff --git a/tests/LSP9Vault/LSP9VaultInit.test.ts b/tests/LSP9Vault/LSP9VaultInit.test.ts index 7eaab9ee6..4e7fc2560 100644 --- a/tests/LSP9Vault/LSP9VaultInit.test.ts +++ b/tests/LSP9Vault/LSP9VaultInit.test.ts @@ -126,13 +126,13 @@ describe('LSP9VaultInit with proxy', () => { const accounts = await ethers.getSigners(); await initializeProxy(context); - const onlyOwnerRevertString = 'Only Owner or reentered Universal Receiver Delegate allowed'; + const onlyOwnerCustomError = 'Only Owner or reentered Universal Receiver Delegate allowed'; return { accounts: accounts, contract: context.lsp9Vault, deployParams: { owner: context.accounts.owner }, - onlyOwnerRevertString, + onlyOwnerCustomError, }; }); diff --git a/tests/Mocks/ERC165Interfaces.test.ts b/tests/Mocks/ERC165Interfaces.test.ts index 07bb2be05..87d68d7b9 100644 --- a/tests/Mocks/ERC165Interfaces.test.ts +++ b/tests/Mocks/ERC165Interfaces.test.ts @@ -109,6 +109,11 @@ describe('Calculate ERC interfaces', () => { expect(result).to.equal(INTERFACE_IDS.ERC20); }); + it('ERC20Metadata', async () => { + const result = await contract.calculateInterfaceERC20Metadata(); + expect(result).to.equal(INTERFACE_IDS.ERC20Metadata); + }); + it('ERC223', async () => { const result = await contract.calculateInterfaceERC223(); expect(result).to.equal(INTERFACE_IDS.ERC223); diff --git a/tests/Mocks/KeyManagerExecutionCosts.test.ts b/tests/Mocks/KeyManagerExecutionCosts.test.ts index e40c3feb7..995e14bcc 100644 --- a/tests/Mocks/KeyManagerExecutionCosts.test.ts +++ b/tests/Mocks/KeyManagerExecutionCosts.test.ts @@ -26,12 +26,16 @@ describe('Key Manager gas cost interactions', () => { describe('when using LSP6KeyManager with constructor', () => { const buildLSP6TestContext = async (): Promise => { const accounts = await ethers.getSigners(); - const owner = accounts[0]; + const mainController = accounts[0]; - const universalProfile = await new UniversalProfile__factory(owner).deploy(owner.address); - const keyManager = await new LSP6KeyManager__factory(owner).deploy(universalProfile.address); + const universalProfile = await new UniversalProfile__factory(mainController).deploy( + mainController.address, + ); + const keyManager = await new LSP6KeyManager__factory(mainController).deploy( + universalProfile.address, + ); - return { accounts, owner, universalProfile, keyManager }; + return { accounts, mainController, universalProfile, keyManager }; }; describe('after deploying the contract', () => { @@ -54,7 +58,7 @@ describe('Key Manager gas cost interactions', () => { const permissionKeys = [ ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + - context.owner.address.substring(2), + context.mainController.address.substring(2), ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + restrictedToOneAddress.address.substring(2), ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + @@ -85,7 +89,7 @@ describe('Key Manager gas cost interactions', () => { await setupKeyManager(context, permissionKeys, permissionValues); - await context.owner.sendTransaction({ + await context.mainController.sendTransaction({ to: context.universalProfile.address, value: ethers.utils.parseEther('10'), }); @@ -107,7 +111,9 @@ describe('Key Manager gas cost interactions', () => { ], ); - const tx = await context.keyManager.connect(context.owner).execute(transferLyxPayload); + const tx = await context.keyManager + .connect(context.mainController) + .execute(transferLyxPayload); const receipt = await tx.wait(); diff --git a/tests/Reentrancy/LSP20/reentrancyHelpers.ts b/tests/Reentrancy/LSP20/reentrancyHelpers.ts index 1c6598c84..13495a86a 100644 --- a/tests/Reentrancy/LSP20/reentrancyHelpers.ts +++ b/tests/Reentrancy/LSP20/reentrancyHelpers.ts @@ -71,7 +71,7 @@ export const transferValueTestCases = { NotAuthorised: [ { permissionsText: 'NO Permissions', - permissions: '0x', + permissions: PERMISSIONS.EXECUTE_RELAY_CALL, allowedCalls: false, missingPermission: 'REENTRANCY', }, @@ -83,38 +83,46 @@ export const transferValueTestCases = { }, { permissionsText: 'REENTRANCY', - permissions: PERMISSIONS.REENTRANCY, + permissions: combinePermissions(PERMISSIONS.EXECUTE_RELAY_CALL, PERMISSIONS.REENTRANCY), allowedCalls: false, missingPermission: 'TRANSFERVALUE', }, { permissionsText: 'REENTRANCY', - permissions: PERMISSIONS.REENTRANCY, + permissions: combinePermissions(PERMISSIONS.EXECUTE_RELAY_CALL, PERMISSIONS.REENTRANCY), allowedCalls: true, missingPermission: 'TRANSFERVALUE', }, { permissionsText: 'TRANSFERVALUE', - permissions: PERMISSIONS.TRANSFERVALUE, + permissions: combinePermissions(PERMISSIONS.EXECUTE_RELAY_CALL, PERMISSIONS.TRANSFERVALUE), allowedCalls: false, missingPermission: 'REENTRANCY', }, { permissionsText: 'TRANSFERVALUE', - permissions: PERMISSIONS.TRANSFERVALUE, + permissions: combinePermissions(PERMISSIONS.EXECUTE_RELAY_CALL, PERMISSIONS.TRANSFERVALUE), allowedCalls: true, missingPermission: 'REENTRANCY', }, ], NoCallsAllowed: { permissionsText: 'REENTRANCY, TRANSFERVALUE', - permissions: combinePermissions(PERMISSIONS.REENTRANCY, PERMISSIONS.TRANSFERVALUE), + permissions: combinePermissions( + PERMISSIONS.REENTRANCY, + PERMISSIONS.TRANSFERVALUE, + PERMISSIONS.EXECUTE_RELAY_CALL, + ), allowedCalls: false, missingPermission: '', }, ValidCase: { permissionsText: 'REENTRANCY, TRANSFERVALUE', - permissions: combinePermissions(PERMISSIONS.REENTRANCY, PERMISSIONS.TRANSFERVALUE), + permissions: combinePermissions( + PERMISSIONS.REENTRANCY, + PERMISSIONS.TRANSFERVALUE, + PERMISSIONS.EXECUTE_RELAY_CALL, + ), allowedCalls: true, missingPermission: '', }, @@ -124,7 +132,7 @@ export const setDataTestCases = { NotAuthorised: [ { permissionsText: 'NO Permissions', - permissions: '0x', + permissions: PERMISSIONS.EXECUTE_RELAY_CALL, allowedERC725YDataKeys: false, missingPermission: 'REENTRANCY', }, @@ -136,38 +144,46 @@ export const setDataTestCases = { }, { permissionsText: 'REENTRANCY', - permissions: PERMISSIONS.REENTRANCY, + permissions: combinePermissions(PERMISSIONS.EXECUTE_RELAY_CALL, PERMISSIONS.REENTRANCY), allowedERC725YDataKeys: false, missingPermission: 'SETDATA', }, { permissionsText: 'REENTRANCY', - permissions: PERMISSIONS.REENTRANCY, + permissions: combinePermissions(PERMISSIONS.EXECUTE_RELAY_CALL, PERMISSIONS.REENTRANCY), allowedERC725YDataKeys: true, missingPermission: 'SETDATA', }, { permissionsText: 'SETDATA', - permissions: PERMISSIONS.SETDATA, + permissions: combinePermissions(PERMISSIONS.EXECUTE_RELAY_CALL, PERMISSIONS.SETDATA), allowedERC725YDataKeys: false, missingPermission: 'REENTRANCY', }, { permissionsText: 'SETDATA', - permissions: PERMISSIONS.SETDATA, + permissions: combinePermissions(PERMISSIONS.EXECUTE_RELAY_CALL, PERMISSIONS.SETDATA), allowedERC725YDataKeys: true, missingPermission: 'REENTRANCY', }, ], NoERC725YDataKeysAllowed: { permissionsText: 'REENTRANCY, SETDATA', - permissions: combinePermissions(PERMISSIONS.REENTRANCY, PERMISSIONS.SETDATA), + permissions: combinePermissions( + PERMISSIONS.REENTRANCY, + PERMISSIONS.SETDATA, + PERMISSIONS.EXECUTE_RELAY_CALL, + ), allowedERC725YDataKeys: false, missingPermission: '', }, ValidCase: { permissionsText: 'REENTRANCY, SETDATA', - permissions: combinePermissions(PERMISSIONS.REENTRANCY, PERMISSIONS.SETDATA), + permissions: combinePermissions( + PERMISSIONS.REENTRANCY, + PERMISSIONS.SETDATA, + PERMISSIONS.EXECUTE_RELAY_CALL, + ), allowedERC725YDataKeys: true, missingPermission: '', }, @@ -177,7 +193,7 @@ export const addPermissionsTestCases = { NotAuthorised: [ { permissionsText: 'NO Permissions', - permissions: '0x', + permissions: PERMISSIONS.EXECUTE_RELAY_CALL, missingPermission: 'REENTRANCY', }, { @@ -187,18 +203,22 @@ export const addPermissionsTestCases = { }, { permissionsText: 'REENTRANCY', - permissions: PERMISSIONS.REENTRANCY, + permissions: combinePermissions(PERMISSIONS.EXECUTE_RELAY_CALL, PERMISSIONS.REENTRANCY), missingPermission: 'ADDCONTROLLER', }, { permissionsText: 'ADDCONTROLLER', - permissions: PERMISSIONS.ADDCONTROLLER, + permissions: combinePermissions(PERMISSIONS.EXECUTE_RELAY_CALL, PERMISSIONS.ADDCONTROLLER), missingPermission: 'REENTRANCY', }, ], ValidCase: { permissionsText: 'REENTRANCY, ADDCONTROLLER', - permissions: combinePermissions(PERMISSIONS.REENTRANCY, PERMISSIONS.ADDCONTROLLER), + permissions: combinePermissions( + PERMISSIONS.REENTRANCY, + PERMISSIONS.ADDCONTROLLER, + PERMISSIONS.EXECUTE_RELAY_CALL, + ), missingPermission: '', }, }; @@ -207,7 +227,7 @@ export const editPermissionsTestCases = { NotAuthorised: [ { permissionsText: 'NO Permissions', - permissions: '0x', + permissions: PERMISSIONS.EXECUTE_RELAY_CALL, missingPermission: 'REENTRANCY', }, { @@ -217,18 +237,22 @@ export const editPermissionsTestCases = { }, { permissionsText: 'REENTRANCY', - permissions: PERMISSIONS.REENTRANCY, + permissions: combinePermissions(PERMISSIONS.EXECUTE_RELAY_CALL, PERMISSIONS.REENTRANCY), missingPermission: 'EDITPERMISSIONS', }, { permissionsText: 'EDITPERMISSIONS', - permissions: PERMISSIONS.EDITPERMISSIONS, + permissions: combinePermissions(PERMISSIONS.EXECUTE_RELAY_CALL, PERMISSIONS.EDITPERMISSIONS), missingPermission: 'REENTRANCY', }, ], ValidCase: { permissionsText: 'REENTRANCY, EDITPERMISSIONS', - permissions: combinePermissions(PERMISSIONS.REENTRANCY, PERMISSIONS.EDITPERMISSIONS), + permissions: combinePermissions( + PERMISSIONS.REENTRANCY, + PERMISSIONS.EDITPERMISSIONS, + PERMISSIONS.EXECUTE_RELAY_CALL, + ), missingPermission: '', }, }; @@ -237,7 +261,7 @@ export const addUniversalReceiverDelegateTestCases = { NotAuthorised: [ { permissionsText: 'NO Permissions', - permissions: '0x', + permissions: PERMISSIONS.EXECUTE_RELAY_CALL, missingPermission: 'REENTRANCY', }, { @@ -247,12 +271,15 @@ export const addUniversalReceiverDelegateTestCases = { }, { permissionsText: 'REENTRANCY', - permissions: PERMISSIONS.REENTRANCY, + permissions: combinePermissions(PERMISSIONS.EXECUTE_RELAY_CALL, PERMISSIONS.REENTRANCY), missingPermission: 'ADDUNIVERSALRECEIVERDELEGATE', }, { permissionsText: 'ADDUNIVERSALRECEIVERDELEGATE', - permissions: PERMISSIONS.ADDUNIVERSALRECEIVERDELEGATE, + permissions: combinePermissions( + PERMISSIONS.EXECUTE_RELAY_CALL, + PERMISSIONS.ADDUNIVERSALRECEIVERDELEGATE, + ), missingPermission: 'REENTRANCY', }, ], @@ -261,6 +288,7 @@ export const addUniversalReceiverDelegateTestCases = { permissions: combinePermissions( PERMISSIONS.REENTRANCY, PERMISSIONS.ADDUNIVERSALRECEIVERDELEGATE, + PERMISSIONS.EXECUTE_RELAY_CALL, ), missingPermission: '', }, @@ -270,7 +298,7 @@ export const changeUniversalReceiverDelegateTestCases = { NotAuthorised: [ { permissionsText: 'NO Permissions', - permissions: '0x', + permissions: PERMISSIONS.EXECUTE_RELAY_CALL, missingPermission: 'REENTRANCY', }, { @@ -280,12 +308,15 @@ export const changeUniversalReceiverDelegateTestCases = { }, { permissionsText: 'REENTRANCY', - permissions: PERMISSIONS.REENTRANCY, + permissions: combinePermissions(PERMISSIONS.EXECUTE_RELAY_CALL, PERMISSIONS.REENTRANCY), missingPermission: 'CHANGEUNIVERSALRECEIVERDELEGATE', }, { permissionsText: 'CHANGEUNIVERSALRECEIVERDELEGATE', - permissions: PERMISSIONS.CHANGEUNIVERSALRECEIVERDELEGATE, + permissions: combinePermissions( + PERMISSIONS.EXECUTE_RELAY_CALL, + PERMISSIONS.CHANGEUNIVERSALRECEIVERDELEGATE, + ), missingPermission: 'REENTRANCY', }, ], @@ -294,6 +325,7 @@ export const changeUniversalReceiverDelegateTestCases = { permissions: combinePermissions( PERMISSIONS.REENTRANCY, PERMISSIONS.CHANGEUNIVERSALRECEIVERDELEGATE, + PERMISSIONS.EXECUTE_RELAY_CALL, ), missingPermission: '', }, @@ -630,5 +662,5 @@ export const loadTestCase = async ( 'setDataBatch', [permissionKeys, permissionValues], ); - await context.keyManager.connect(context.owner).execute(permissionsPayload); + await context.keyManager.connect(context.mainController).execute(permissionsPayload); }; diff --git a/tests/Reentrancy/LSP6/LSP6Reentrancy.test.ts b/tests/Reentrancy/LSP6/LSP6Reentrancy.test.ts index 0ec0943fe..6b66a6080 100644 --- a/tests/Reentrancy/LSP6/LSP6Reentrancy.test.ts +++ b/tests/Reentrancy/LSP6/LSP6Reentrancy.test.ts @@ -68,14 +68,19 @@ export const shouldBehaveLikeLSP6ReentrancyScenarios = ( ); const permissionKeys = [ - ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + context.owner.address.substring(2), + ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + + context.mainController.address.substring(2), ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + signer.address.substring(2), ERC725YDataKeys.LSP6['AddressPermissions:AllowedCalls'] + signer.address.substring(2), ]; const permissionValues = [ ALL_PERMISSIONS, - combinePermissions(PERMISSIONS.CALL, PERMISSIONS.TRANSFERVALUE), + combinePermissions( + PERMISSIONS.CALL, + PERMISSIONS.TRANSFERVALUE, + PERMISSIONS.EXECUTE_RELAY_CALL, + ), combineAllowedCalls( // TODO: test reentrancy against the bits for the allowed calls [ @@ -91,7 +96,7 @@ export const shouldBehaveLikeLSP6ReentrancyScenarios = ( await setupKeyManager(context, permissionKeys, permissionValues); // Fund Universal Profile with some LYXe - await context.owner.sendTransaction({ + await context.mainController.sendTransaction({ to: context.universalProfile.address, value: ethers.utils.parseEther('10'), }); @@ -122,7 +127,7 @@ export const shouldBehaveLikeLSP6ReentrancyScenarios = ( // send LYX to malicious contract // at this point, the malicious contract receive function try to drain funds by re-entering the KeyManager // this should not be possible since it does not have the permission `REENTRANCY` - await expect(context.keyManager.connect(context.owner).execute(transferPayload)) + await expect(context.keyManager.connect(context.mainController).execute(transferPayload)) .to.be.revertedWithCustomError(context.keyManager, 'NotAuthorised') .withArgs(maliciousContract.address, 'REENTRANCY'); @@ -203,13 +208,13 @@ export const shouldBehaveLikeLSP6ReentrancyScenarios = ( await maliciousContract.loadPayload(executePayload); - await expect(context.keyManager.connect(context.owner).execute(transferPayload)) + await expect(context.keyManager.connect(context.mainController).execute(transferPayload)) .to.be.revertedWithCustomError(context.keyManager, 'NotAuthorised') .withArgs(maliciousContract.address, 'REENTRANCY'); }); it('should pass when reentered by URD and the URD has REENTRANCY permission', async () => { - const URDDummy = await new Reentrancy__factory(context.owner).deploy( + const URDDummy = await new Reentrancy__factory(context.mainController).deploy( context.keyManager.address, ); @@ -236,7 +241,7 @@ export const shouldBehaveLikeLSP6ReentrancyScenarios = ( ], ); - await context.keyManager.connect(context.owner).execute(setDataPayload); + await context.keyManager.connect(context.mainController).execute(setDataPayload); const transferPayload = context.universalProfile.interface.encodeFunctionData('execute', [ OPERATION_TYPES.CALL, @@ -254,7 +259,7 @@ export const shouldBehaveLikeLSP6ReentrancyScenarios = ( const initialAccountBalance = await provider.getBalance(context.universalProfile.address); const initialAttackerContractBalance = await provider.getBalance(maliciousContract.address); - await context.keyManager.connect(context.owner).execute(transferPayload); + await context.keyManager.connect(context.mainController).execute(transferPayload); const newAccountBalance = await provider.getBalance(context.universalProfile.address); const newAttackerContractBalance = await provider.getBalance(URDDummy.address); @@ -267,7 +272,7 @@ export const shouldBehaveLikeLSP6ReentrancyScenarios = ( it('should allow the URD to use `setData(..)` through the LSP6', async () => { const universalReceiverDelegateDataUpdater = - await new UniversalReceiverDelegateDataUpdater__factory(context.owner).deploy(); + await new UniversalReceiverDelegateDataUpdater__factory(context.mainController).deploy(); const randomHardcodedKey = ethers.utils.keccak256( ethers.utils.toUtf8Bytes('some random data key'), @@ -294,13 +299,18 @@ export const shouldBehaveLikeLSP6ReentrancyScenarios = ( ], ); - await context.keyManager.connect(context.owner).execute(setDataPayload); + await context.keyManager.connect(context.mainController).execute(setDataPayload); const universalReceiverDelegatePayload = - universalReceiverDelegateDataUpdater.interface.encodeFunctionData('universalReceiver', [ - LSP1_TYPE_IDS.LSP7Tokens_SenderNotification, - '0xcafecafecafecafe', - ]); + universalReceiverDelegateDataUpdater.interface.encodeFunctionData( + 'universalReceiverDelegate', + [ + ethers.constants.AddressZero, + 0, + LSP1_TYPE_IDS.LSP7Tokens_SenderNotification, + '0xcafecafecafecafe', + ], + ); const executePayload = context.universalProfile.interface.encodeFunctionData('execute', [ OPERATION_TYPES.CALL, @@ -309,7 +319,7 @@ export const shouldBehaveLikeLSP6ReentrancyScenarios = ( universalReceiverDelegatePayload, ]); - await context.keyManager.connect(context.owner).execute(executePayload); + await context.keyManager.connect(context.mainController).execute(executePayload); expect(await context.universalProfile.getData(randomHardcodedKey)).to.equal( randomHardcodedValue, @@ -332,7 +342,7 @@ export const shouldBehaveLikeLSP6ReentrancyScenarios = ( const permissionKeys = [ ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + - context.owner.address.substring(2), + context.mainController.address.substring(2), ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + firstReentrant.address.substring(2), ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + @@ -355,12 +365,14 @@ export const shouldBehaveLikeLSP6ReentrancyScenarios = ( const firstTargetSelector = firstReentrant.interface.encodeFunctionData('firstTarget'); - const payload = context.universalProfile.interface.encodeFunctionData( - 'execute(uint256,address,uint256,bytes)', - [OPERATION_TYPES.CALL, firstReentrant.address, 0, firstTargetSelector], - ); + const payload = context.universalProfile.interface.encodeFunctionData('execute', [ + OPERATION_TYPES.CALL, + firstReentrant.address, + 0, + firstTargetSelector, + ]); - await expect(context.keyManager.connect(context.owner).execute(payload)) + await expect(context.keyManager.connect(context.mainController).execute(payload)) .to.be.revertedWithCustomError(context.keyManager, 'NotAuthorised') .withArgs(secondReentrant.address, 'REENTRANCY'); }); @@ -384,12 +396,14 @@ export const shouldBehaveLikeLSP6ReentrancyScenarios = ( const firstTargetSelector = firstReentrant.interface.encodeFunctionData('firstTarget'); - const payload = context.universalProfile.interface.encodeFunctionData( - 'execute(uint256,address,uint256,bytes)', - [OPERATION_TYPES.CALL, firstReentrant.address, 0, firstTargetSelector], - ); + const payload = context.universalProfile.interface.encodeFunctionData('execute', [ + OPERATION_TYPES.CALL, + firstReentrant.address, + 0, + firstTargetSelector, + ]); - await context.keyManager.connect(context.owner).execute(payload); + await context.keyManager.connect(context.mainController).execute(payload); const result = await context.universalProfile['getData(bytes32)']( ethers.constants.HashZero, diff --git a/tests/Reentrancy/LSP6/SingleExecuteRelayCallToSingleExecute.test.ts b/tests/Reentrancy/LSP6/SingleExecuteRelayCallToSingleExecute.test.ts index 70728e772..848bcd970 100644 --- a/tests/Reentrancy/LSP6/SingleExecuteRelayCallToSingleExecute.test.ts +++ b/tests/Reentrancy/LSP6/SingleExecuteRelayCallToSingleExecute.test.ts @@ -2,7 +2,7 @@ import { expect } from 'chai'; import { ethers } from 'hardhat'; //types -import { BigNumber, BytesLike } from 'ethers'; +import { BigNumber } from 'ethers'; // constants import { ERC725YDataKeys } from '../../../constants'; @@ -25,6 +25,7 @@ import { generateRelayCall, generateExecutePayload, loadTestCase, + RelayCallParams, } from './reentrancyHelpers'; export const testSingleExecuteRelayCallToSingleExecute = ( @@ -40,12 +41,7 @@ export const testSingleExecuteRelayCallToSingleExecute = ( }); describe('when reentering and transferring value', () => { - let relayCallParams: { - signature: BytesLike; - nonce: BigNumber; - validityTimestamps: BytesLike; - payload: BytesLike; - }; + let relayCallParams: RelayCallParams; before(async () => { const executePayload = generateExecutePayload( context.keyManager.address, @@ -145,12 +141,7 @@ export const testSingleExecuteRelayCallToSingleExecute = ( }); describe('when reentering and setting data', () => { - let relayCallParams: { - signature: BytesLike; - nonce: BigNumber; - validityTimestamps: BytesLike; - payload: BytesLike; - }; + let relayCallParams: RelayCallParams; before(async () => { const executePayload = generateExecutePayload( context.keyManager.address, @@ -241,12 +232,7 @@ export const testSingleExecuteRelayCallToSingleExecute = ( }); describe('when reentering and adding permissions', () => { - let relayCallParams: { - signature: BytesLike; - nonce: BigNumber; - validityTimestamps: BytesLike; - payload: BytesLike; - }; + let relayCallParams: RelayCallParams; before(async () => { const executePayload = generateExecutePayload( context.keyManager.address, @@ -317,12 +303,7 @@ export const testSingleExecuteRelayCallToSingleExecute = ( }); describe('when reentering and changing permissions', () => { - let relayCallParams: { - signature: BytesLike; - nonce: BigNumber; - validityTimestamps: BytesLike; - payload: BytesLike; - }; + let relayCallParams: RelayCallParams; before(async () => { const executePayload = generateExecutePayload( context.keyManager.address, @@ -392,12 +373,7 @@ export const testSingleExecuteRelayCallToSingleExecute = ( }); describe('when reentering and adding URD', () => { - let relayCallParams: { - signature: BytesLike; - nonce: BigNumber; - validityTimestamps: BytesLike; - payload: BytesLike; - }; + let relayCallParams: RelayCallParams; before(async () => { const executePayload = generateExecutePayload( context.keyManager.address, @@ -468,12 +444,7 @@ export const testSingleExecuteRelayCallToSingleExecute = ( }); describe('when reentering and changing URD', () => { - let relayCallParams: { - signature: BytesLike; - nonce: BigNumber; - validityTimestamps: BytesLike; - payload: BytesLike; - }; + let relayCallParams: RelayCallParams; before(async () => { const executePayload = generateExecutePayload( context.keyManager.address, diff --git a/tests/Reentrancy/LSP6/SingleExecuteRelayCallToSingleExecuteRelayCall.test.ts b/tests/Reentrancy/LSP6/SingleExecuteRelayCallToSingleExecuteRelayCall.test.ts index 50d3b66b6..35d4b41ed 100644 --- a/tests/Reentrancy/LSP6/SingleExecuteRelayCallToSingleExecuteRelayCall.test.ts +++ b/tests/Reentrancy/LSP6/SingleExecuteRelayCallToSingleExecuteRelayCall.test.ts @@ -26,6 +26,7 @@ import { generateRelayCall, generateSingleRelayPayload, loadTestCase, + RelayCallParams, } from './reentrancyHelpers'; export const testSingleExecuteRelayCallToSingleExecuteRelayCall = ( @@ -53,12 +54,7 @@ export const testSingleExecuteRelayCallToSingleExecuteRelayCall = ( }); describe('when reentering and transferring value', () => { - let relayCallParams: { - signature: BytesLike; - nonce: BigNumber; - validityTimestamps: BytesLike; - payload: BytesLike; - }; + let relayCallParams: RelayCallParams; before(async () => { relayCallParams = await generateRelayCall( context.keyManager, @@ -162,12 +158,7 @@ export const testSingleExecuteRelayCallToSingleExecuteRelayCall = ( }); describe('when reentering and setting data', () => { - let relayCallParams: { - signature: BytesLike; - nonce: BigNumber; - validityTimestamps: BytesLike; - payload: BytesLike; - }; + let relayCallParams: RelayCallParams; before(async () => { relayCallParams = await generateRelayCall( context.keyManager, @@ -262,12 +253,7 @@ export const testSingleExecuteRelayCallToSingleExecuteRelayCall = ( }); describe('when reentering and adding permissions', () => { - let relayCallParams: { - signature: BytesLike; - nonce: BigNumber; - validityTimestamps: BytesLike; - payload: BytesLike; - }; + let relayCallParams: RelayCallParams; before(async () => { relayCallParams = await generateRelayCall( context.keyManager, @@ -342,12 +328,7 @@ export const testSingleExecuteRelayCallToSingleExecuteRelayCall = ( }); describe('when reentering and changing permissions', () => { - let relayCallParams: { - signature: BytesLike; - nonce: BigNumber; - validityTimestamps: BytesLike; - payload: BytesLike; - }; + let relayCallParams: RelayCallParams; before(async () => { relayCallParams = await generateRelayCall( context.keyManager, @@ -421,12 +402,7 @@ export const testSingleExecuteRelayCallToSingleExecuteRelayCall = ( }); describe('when reentering and adding URD', () => { - let relayCallParams: { - signature: BytesLike; - nonce: BigNumber; - validityTimestamps: BytesLike; - payload: BytesLike; - }; + let relayCallParams: RelayCallParams; before(async () => { relayCallParams = await generateRelayCall( context.keyManager, @@ -501,12 +477,7 @@ export const testSingleExecuteRelayCallToSingleExecuteRelayCall = ( }); describe('when reentering and changing URD', () => { - let relayCallParams: { - signature: BytesLike; - nonce: BigNumber; - validityTimestamps: BytesLike; - payload: BytesLike; - }; + let relayCallParams: RelayCallParams; before(async () => { relayCallParams = await generateRelayCall( context.keyManager, diff --git a/tests/Reentrancy/LSP6/reentrancyHelpers.ts b/tests/Reentrancy/LSP6/reentrancyHelpers.ts index 7f6fa70e5..b32efb594 100644 --- a/tests/Reentrancy/LSP6/reentrancyHelpers.ts +++ b/tests/Reentrancy/LSP6/reentrancyHelpers.ts @@ -67,11 +67,18 @@ export type ReentrancyContext = { randomLSP1TypeId: string; }; +export type RelayCallParams = { + signature: BytesLike; + nonce: BigNumber; + validityTimestamps: number | BytesLike; + payload: BytesLike; +}; + export const transferValueTestCases = { NotAuthorised: [ { permissionsText: 'NO Permissions', - permissions: '0x', + permissions: PERMISSIONS.EXECUTE_RELAY_CALL, allowedCalls: false, missingPermission: 'REENTRANCY', }, @@ -83,38 +90,46 @@ export const transferValueTestCases = { }, { permissionsText: 'REENTRANCY', - permissions: PERMISSIONS.REENTRANCY, + permissions: combinePermissions(PERMISSIONS.EXECUTE_RELAY_CALL, PERMISSIONS.REENTRANCY), allowedCalls: false, missingPermission: 'TRANSFERVALUE', }, { permissionsText: 'REENTRANCY', - permissions: PERMISSIONS.REENTRANCY, + permissions: combinePermissions(PERMISSIONS.EXECUTE_RELAY_CALL, PERMISSIONS.REENTRANCY), allowedCalls: true, missingPermission: 'TRANSFERVALUE', }, { permissionsText: 'TRANSFERVALUE', - permissions: PERMISSIONS.TRANSFERVALUE, + permissions: combinePermissions(PERMISSIONS.EXECUTE_RELAY_CALL, PERMISSIONS.TRANSFERVALUE), allowedCalls: false, missingPermission: 'REENTRANCY', }, { permissionsText: 'TRANSFERVALUE', - permissions: PERMISSIONS.TRANSFERVALUE, + permissions: combinePermissions(PERMISSIONS.EXECUTE_RELAY_CALL, PERMISSIONS.TRANSFERVALUE), allowedCalls: true, missingPermission: 'REENTRANCY', }, ], NoCallsAllowed: { permissionsText: 'REENTRANCY, TRANSFERVALUE', - permissions: combinePermissions(PERMISSIONS.REENTRANCY, PERMISSIONS.TRANSFERVALUE), + permissions: combinePermissions( + PERMISSIONS.REENTRANCY, + PERMISSIONS.TRANSFERVALUE, + PERMISSIONS.EXECUTE_RELAY_CALL, + ), allowedCalls: false, missingPermission: '', }, ValidCase: { permissionsText: 'REENTRANCY, TRANSFERVALUE', - permissions: combinePermissions(PERMISSIONS.REENTRANCY, PERMISSIONS.TRANSFERVALUE), + permissions: combinePermissions( + PERMISSIONS.REENTRANCY, + PERMISSIONS.TRANSFERVALUE, + PERMISSIONS.EXECUTE_RELAY_CALL, + ), allowedCalls: true, missingPermission: '', }, @@ -124,7 +139,7 @@ export const setDataTestCases = { NotAuthorised: [ { permissionsText: 'NO Permissions', - permissions: '0x', + permissions: PERMISSIONS.EXECUTE_RELAY_CALL, allowedERC725YDataKeys: false, missingPermission: 'REENTRANCY', }, @@ -136,38 +151,46 @@ export const setDataTestCases = { }, { permissionsText: 'REENTRANCY', - permissions: PERMISSIONS.REENTRANCY, + permissions: combinePermissions(PERMISSIONS.EXECUTE_RELAY_CALL, PERMISSIONS.REENTRANCY), allowedERC725YDataKeys: false, missingPermission: 'SETDATA', }, { permissionsText: 'REENTRANCY', - permissions: PERMISSIONS.REENTRANCY, + permissions: combinePermissions(PERMISSIONS.EXECUTE_RELAY_CALL, PERMISSIONS.REENTRANCY), allowedERC725YDataKeys: true, missingPermission: 'SETDATA', }, { permissionsText: 'SETDATA', - permissions: PERMISSIONS.SETDATA, + permissions: combinePermissions(PERMISSIONS.EXECUTE_RELAY_CALL, PERMISSIONS.SETDATA), allowedERC725YDataKeys: false, missingPermission: 'REENTRANCY', }, { permissionsText: 'SETDATA', - permissions: PERMISSIONS.SETDATA, + permissions: combinePermissions(PERMISSIONS.EXECUTE_RELAY_CALL, PERMISSIONS.SETDATA), allowedERC725YDataKeys: true, missingPermission: 'REENTRANCY', }, ], NoERC725YDataKeysAllowed: { permissionsText: 'REENTRANCY, SETDATA', - permissions: combinePermissions(PERMISSIONS.REENTRANCY, PERMISSIONS.SETDATA), + permissions: combinePermissions( + PERMISSIONS.REENTRANCY, + PERMISSIONS.SETDATA, + PERMISSIONS.EXECUTE_RELAY_CALL, + ), allowedERC725YDataKeys: false, missingPermission: '', }, ValidCase: { permissionsText: 'REENTRANCY, SETDATA', - permissions: combinePermissions(PERMISSIONS.REENTRANCY, PERMISSIONS.SETDATA), + permissions: combinePermissions( + PERMISSIONS.REENTRANCY, + PERMISSIONS.SETDATA, + PERMISSIONS.EXECUTE_RELAY_CALL, + ), allowedERC725YDataKeys: true, missingPermission: '', }, @@ -177,7 +200,7 @@ export const addPermissionsTestCases = { NotAuthorised: [ { permissionsText: 'NO Permissions', - permissions: '0x', + permissions: PERMISSIONS.EXECUTE_RELAY_CALL, missingPermission: 'REENTRANCY', }, { @@ -187,18 +210,22 @@ export const addPermissionsTestCases = { }, { permissionsText: 'REENTRANCY', - permissions: PERMISSIONS.REENTRANCY, + permissions: combinePermissions(PERMISSIONS.EXECUTE_RELAY_CALL, PERMISSIONS.REENTRANCY), missingPermission: 'ADDCONTROLLER', }, { permissionsText: 'ADDCONTROLLER', - permissions: PERMISSIONS.ADDCONTROLLER, + permissions: combinePermissions(PERMISSIONS.EXECUTE_RELAY_CALL, PERMISSIONS.ADDCONTROLLER), missingPermission: 'REENTRANCY', }, ], ValidCase: { permissionsText: 'REENTRANCY, ADDCONTROLLER', - permissions: combinePermissions(PERMISSIONS.REENTRANCY, PERMISSIONS.ADDCONTROLLER), + permissions: combinePermissions( + PERMISSIONS.REENTRANCY, + PERMISSIONS.ADDCONTROLLER, + PERMISSIONS.EXECUTE_RELAY_CALL, + ), missingPermission: '', }, }; @@ -207,7 +234,7 @@ export const editPermissionsTestCases = { NotAuthorised: [ { permissionsText: 'NO Permissions', - permissions: '0x', + permissions: PERMISSIONS.EXECUTE_RELAY_CALL, missingPermission: 'REENTRANCY', }, { @@ -217,18 +244,22 @@ export const editPermissionsTestCases = { }, { permissionsText: 'REENTRANCY', - permissions: PERMISSIONS.REENTRANCY, + permissions: combinePermissions(PERMISSIONS.EXECUTE_RELAY_CALL, PERMISSIONS.REENTRANCY), missingPermission: 'EDITPERMISSIONS', }, { permissionsText: 'EDITPERMISSIONS', - permissions: PERMISSIONS.EDITPERMISSIONS, + permissions: combinePermissions(PERMISSIONS.EXECUTE_RELAY_CALL, PERMISSIONS.EDITPERMISSIONS), missingPermission: 'REENTRANCY', }, ], ValidCase: { permissionsText: 'REENTRANCY, EDITPERMISSIONS', - permissions: combinePermissions(PERMISSIONS.REENTRANCY, PERMISSIONS.EDITPERMISSIONS), + permissions: combinePermissions( + PERMISSIONS.REENTRANCY, + PERMISSIONS.EDITPERMISSIONS, + PERMISSIONS.EXECUTE_RELAY_CALL, + ), missingPermission: '', }, }; @@ -237,7 +268,7 @@ export const addUniversalReceiverDelegateTestCases = { NotAuthorised: [ { permissionsText: 'NO Permissions', - permissions: '0x', + permissions: PERMISSIONS.EXECUTE_RELAY_CALL, missingPermission: 'REENTRANCY', }, { @@ -247,12 +278,15 @@ export const addUniversalReceiverDelegateTestCases = { }, { permissionsText: 'REENTRANCY', - permissions: PERMISSIONS.REENTRANCY, + permissions: combinePermissions(PERMISSIONS.EXECUTE_RELAY_CALL, PERMISSIONS.REENTRANCY), missingPermission: 'ADDUNIVERSALRECEIVERDELEGATE', }, { permissionsText: 'ADDUNIVERSALRECEIVERDELEGATE', - permissions: PERMISSIONS.ADDUNIVERSALRECEIVERDELEGATE, + permissions: combinePermissions( + PERMISSIONS.EXECUTE_RELAY_CALL, + PERMISSIONS.ADDUNIVERSALRECEIVERDELEGATE, + ), missingPermission: 'REENTRANCY', }, ], @@ -261,6 +295,7 @@ export const addUniversalReceiverDelegateTestCases = { permissions: combinePermissions( PERMISSIONS.REENTRANCY, PERMISSIONS.ADDUNIVERSALRECEIVERDELEGATE, + PERMISSIONS.EXECUTE_RELAY_CALL, ), missingPermission: '', }, @@ -270,7 +305,7 @@ export const changeUniversalReceiverDelegateTestCases = { NotAuthorised: [ { permissionsText: 'NO Permissions', - permissions: '0x', + permissions: PERMISSIONS.EXECUTE_RELAY_CALL, missingPermission: 'REENTRANCY', }, { @@ -280,12 +315,15 @@ export const changeUniversalReceiverDelegateTestCases = { }, { permissionsText: 'REENTRANCY', - permissions: PERMISSIONS.REENTRANCY, + permissions: combinePermissions(PERMISSIONS.EXECUTE_RELAY_CALL, PERMISSIONS.REENTRANCY), missingPermission: 'CHANGEUNIVERSALRECEIVERDELEGATE', }, { permissionsText: 'CHANGEUNIVERSALRECEIVERDELEGATE', - permissions: PERMISSIONS.CHANGEUNIVERSALRECEIVERDELEGATE, + permissions: combinePermissions( + PERMISSIONS.EXECUTE_RELAY_CALL, + PERMISSIONS.CHANGEUNIVERSALRECEIVERDELEGATE, + ), missingPermission: 'REENTRANCY', }, ], @@ -294,6 +332,7 @@ export const changeUniversalReceiverDelegateTestCases = { permissions: combinePermissions( PERMISSIONS.REENTRANCY, PERMISSIONS.CHANGEUNIVERSALRECEIVERDELEGATE, + PERMISSIONS.EXECUTE_RELAY_CALL, ), missingPermission: '', }, @@ -327,7 +366,7 @@ export const buildReentrancyContext = async (context: LSP6TestContext) => { const permissionValues = [ ALL_PERMISSIONS, - PERMISSIONS.CALL, + combinePermissions(PERMISSIONS.EXECUTE_RELAY_CALL, PERMISSIONS.CALL), combineAllowedCalls( // allow controller to call the 3 x addresses listed below [CALLTYPE.CALL, CALLTYPE.CALL, CALLTYPE.CALL], @@ -335,7 +374,7 @@ export const buildReentrancyContext = async (context: LSP6TestContext) => { ['0xffffffff', '0xffffffff', '0xffffffff'], ['0xffffffff', '0xffffffff', '0xffffffff'], ), - PERMISSIONS.CALL, + combinePermissions(PERMISSIONS.EXECUTE_RELAY_CALL, PERMISSIONS.CALL), combineAllowedCalls( // allow controller to call the 3 x addresses listed below [CALLTYPE.CALL, CALLTYPE.CALL, CALLTYPE.CALL], @@ -382,12 +421,7 @@ export const generateRelayCall = async ( payload.toString(), ); - const relayCallContext: { - signature: BytesLike; - nonce: BigNumber; - validityTimestamps: BytesLike | number; - payload: BytesLike; - } = { + const relayCallContext: RelayCallParams = { signature, nonce, validityTimestamps, @@ -630,5 +664,5 @@ export const loadTestCase = async ( 'setDataBatch', [permissionKeys, permissionValues], ); - await context.keyManager.connect(context.owner).execute(permissionsPayload); + await context.keyManager.connect(context.mainController).execute(permissionsPayload); }; diff --git a/tests/Reentrancy/Reentrancy.test.ts b/tests/Reentrancy/Reentrancy.test.ts index 70e6530ec..9ab0f7cb0 100644 --- a/tests/Reentrancy/Reentrancy.test.ts +++ b/tests/Reentrancy/Reentrancy.test.ts @@ -11,15 +11,20 @@ import { shouldBehaveLikeLSP20WithLSP6ReentrancyScenarios } from './LSP20/LSP20W describe('Reentrancy scenarios with constructor', () => { const buildTestContext = async (initialFunding?: BigNumber): Promise => { const accounts = await ethers.getSigners(); - const owner = accounts[0]; + const mainController = accounts[0]; - const universalProfile = await new UniversalProfile__factory(owner).deploy(owner.address, { - value: initialFunding, - }); + const universalProfile = await new UniversalProfile__factory(mainController).deploy( + mainController.address, + { + value: initialFunding, + }, + ); - const keyManager = await new LSP6KeyManager__factory(owner).deploy(universalProfile.address); + const keyManager = await new LSP6KeyManager__factory(mainController).deploy( + universalProfile.address, + ); - return { accounts, owner, universalProfile, keyManager, initialFunding }; + return { accounts, mainController, universalProfile, keyManager, initialFunding }; }; describe('when testing Reentrancy scenarios for LSP6', () => { diff --git a/tests/Reentrancy/ReentrancyInit.test.ts b/tests/Reentrancy/ReentrancyInit.test.ts index 0645d1819..2a3c0a33e 100644 --- a/tests/Reentrancy/ReentrancyInit.test.ts +++ b/tests/Reentrancy/ReentrancyInit.test.ts @@ -12,21 +12,21 @@ import { shouldBehaveLikeLSP20WithLSP6ReentrancyScenarios } from './LSP20/LSP20W describe('Reentrancy scenarios with proxy', () => { const buildProxyTestContext = async (initialFunding?: BigNumber): Promise => { const accounts = await ethers.getSigners(); - const owner = accounts[0]; + const mainController = accounts[0]; - const baseUP = await new UniversalProfileInit__factory(owner).deploy(); - const upProxy = await deployProxy(baseUP.address, owner); + const baseUP = await new UniversalProfileInit__factory(mainController).deploy(); + const upProxy = await deployProxy(baseUP.address, mainController); const universalProfile = await baseUP.attach(upProxy); - const baseKM = await new LSP6KeyManagerInit__factory(owner).deploy(); - const kmProxy = await deployProxy(baseKM.address, owner); + const baseKM = await new LSP6KeyManagerInit__factory(mainController).deploy(); + const kmProxy = await deployProxy(baseKM.address, mainController); const keyManager = await baseKM.attach(kmProxy); - return { accounts, owner, universalProfile, keyManager, initialFunding }; + return { accounts, mainController, universalProfile, keyManager, initialFunding }; }; const initializeProxies = async (context: LSP6TestContext) => { - await context.universalProfile['initialize(address)'](context.owner.address, { + await context.universalProfile['initialize(address)'](context.mainController.address, { value: context.initialFunding, }); diff --git a/tests/UniversalProfile.behaviour.ts b/tests/UniversalProfile.behaviour.ts index 4f0fedaaa..758a33550 100644 --- a/tests/UniversalProfile.behaviour.ts +++ b/tests/UniversalProfile.behaviour.ts @@ -48,7 +48,7 @@ export const shouldBehaveLikeLSP3 = ( const signature = await signer.signMessage(dataToSign); const result = await context.universalProfile.isValidSignature(messageHash, signature); - expect(result).to.equal(ERC1271_VALUES.MAGIC_VALUE); + expect(result).to.equal(ERC1271_VALUES.SUCCESS_VALUE); }); it('should return fail value when verifying signature from non-owner', async () => { @@ -133,6 +133,15 @@ export const shouldBehaveLikeLSP3 = ( '0xdaea594e385fc724449e3118b2db7e86dfba1826', ]; + it('should fail when passing empty arrays of data keys / values', async () => { + const keys = []; + const values = []; + + await expect( + context.universalProfile.setDataBatch(keys, values), + ).to.be.revertedWithCustomError(context.universalProfile, 'ERC725Y_DataKeysValuesEmptyArray'); + }); + it('should set the 3 x keys for a basic UP setup => `LSP3Profile`, `LSP12IssuedAssets[]` and `LSP1UniversalReceiverDelegate`', async () => { const keys = [ ERC725YDataKeys.LSP3.LSP3Profile, @@ -431,8 +440,8 @@ export const shouldBehaveLikeLSP3 = ( await expect( context.universalProfile.connect(context.accounts[4]).batchCalls([setDataPayload]), ) - .to.be.revertedWithCustomError(context.universalProfile, 'LSP20InvalidMagicValue') - .withArgs(false, '0x'); + .to.be.revertedWithCustomError(context.universalProfile, 'LSP20EOACannotVerifyCall') + .withArgs(context.deployParams.owner.address); }); }); diff --git a/tests/UniversalProfile.test.ts b/tests/UniversalProfile.test.ts index 01dcc7eab..f22bef997 100644 --- a/tests/UniversalProfile.test.ts +++ b/tests/UniversalProfile.test.ts @@ -72,9 +72,9 @@ describe('UniversalProfile with constructor', () => { { value: initialFunding }, ); - const onlyOwnerRevertString = 'Ownable: caller is not the owner'; + const onlyOwnerCustomError = 'OwnableCallerNotTheOwner'; - return { accounts, contract, deployParams, onlyOwnerRevertString }; + return { accounts, contract, deployParams, onlyOwnerCustomError }; }; const buildLSP17TestContext = async (): Promise => { diff --git a/tests/UniversalProfileInit.test.ts b/tests/UniversalProfileInit.test.ts index 5b73d1fcf..a0fbc0ef1 100644 --- a/tests/UniversalProfileInit.test.ts +++ b/tests/UniversalProfileInit.test.ts @@ -86,13 +86,13 @@ describe('UniversalProfileInit with proxy', () => { const universalProfile = universalProfileInit.attach(universalProfileProxy); - const onlyOwnerRevertString = 'Ownable: caller is not the owner'; + const onlyOwnerCustomError = 'OwnableCallerNotTheOwner'; return { accounts, contract: universalProfile, deployParams, - onlyOwnerRevertString, + onlyOwnerCustomError, }; }; diff --git a/tests/foundry/GasTests/LSP6s/LSP6ExecuteRC.sol b/tests/foundry/GasTests/LSP6s/LSP6ExecuteRC.sol index 55ef7c7d3..805044d9c 100644 --- a/tests/foundry/GasTests/LSP6s/LSP6ExecuteRC.sol +++ b/tests/foundry/GasTests/LSP6s/LSP6ExecuteRC.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: UNLICENSED +// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.13; import "../../../../contracts/LSP6KeyManager/LSP6KeyManager.sol"; diff --git a/tests/foundry/GasTests/LSP6s/LSP6ExecuteUC.sol b/tests/foundry/GasTests/LSP6s/LSP6ExecuteUC.sol index b72f24920..d1fc01615 100644 --- a/tests/foundry/GasTests/LSP6s/LSP6ExecuteUC.sol +++ b/tests/foundry/GasTests/LSP6s/LSP6ExecuteUC.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: UNLICENSED +// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.13; import "forge-std/Test.sol"; diff --git a/tests/foundry/GasTests/LSP6s/LSP6SetDataRC.sol b/tests/foundry/GasTests/LSP6s/LSP6SetDataRC.sol index 2d325e159..f4f7aaa9d 100644 --- a/tests/foundry/GasTests/LSP6s/LSP6SetDataRC.sol +++ b/tests/foundry/GasTests/LSP6s/LSP6SetDataRC.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: UNLICENSED +// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.13; import "forge-std/Test.sol"; diff --git a/tests/foundry/GasTests/LSP6s/LSP6SetDataUC.sol b/tests/foundry/GasTests/LSP6s/LSP6SetDataUC.sol index adba58552..9687c69f6 100644 --- a/tests/foundry/GasTests/LSP6s/LSP6SetDataUC.sol +++ b/tests/foundry/GasTests/LSP6s/LSP6SetDataUC.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: UNLICENSED +// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.13; import "forge-std/Test.sol"; diff --git a/tests/foundry/GasTests/UniversalProfileTestsHelper.sol b/tests/foundry/GasTests/UniversalProfileTestsHelper.sol index 874331271..3cf8d39e2 100644 --- a/tests/foundry/GasTests/UniversalProfileTestsHelper.sol +++ b/tests/foundry/GasTests/UniversalProfileTestsHelper.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: UNLICENSED +// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.13; import "forge-std/Test.sol"; diff --git a/tests/foundry/GasTests/execute/RestrictedController.sol b/tests/foundry/GasTests/execute/RestrictedController.sol index a8c0afbcf..a7bf58cf0 100644 --- a/tests/foundry/GasTests/execute/RestrictedController.sol +++ b/tests/foundry/GasTests/execute/RestrictedController.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: UNLICENSED +// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.13; import "../LSP6s/LSP6ExecuteRC.sol"; @@ -19,6 +19,9 @@ import { _PERMISSION_CALL, _PERMISSION_TRANSFERVALUE } from "../../../../contracts/LSP6KeyManager/LSP6Constants.sol"; +import { + _LSP8_TOKENID_TYPE_NUMBER +} from "../../../../contracts/LSP8IdentifiableDigitalAsset/LSP8Constants.sol"; import "../UniversalProfileTestsHelper.sol"; contract ExecuteRestrictedController is UniversalProfileTestsHelper { @@ -438,7 +441,8 @@ contract ExecuteRestrictedController is UniversalProfileTestsHelper { indentifiableDigitalAsset = new LSP8Tester( "TestLSP8", "TSTLSP8", - digitalAssetsOwner + digitalAssetsOwner, + _LSP8_TOKENID_TYPE_NUMBER ); bytes32 tokenID = bytes32(uint256(1)); diff --git a/tests/foundry/GasTests/execute/UnrestrictedController.sol b/tests/foundry/GasTests/execute/UnrestrictedController.sol index 29f9874ab..49ca3b07a 100644 --- a/tests/foundry/GasTests/execute/UnrestrictedController.sol +++ b/tests/foundry/GasTests/execute/UnrestrictedController.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: UNLICENSED +// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.13; import "../LSP6s/LSP6ExecuteUC.sol"; @@ -17,6 +17,9 @@ import { _PERMISSION_REENTRANCY, _PERMISSION_SUPER_TRANSFERVALUE } from "../../../../contracts/LSP6KeyManager/LSP6Constants.sol"; +import { + _LSP8_TOKENID_TYPE_NUMBER +} from "../../../../contracts/LSP8IdentifiableDigitalAsset/LSP8Constants.sol"; import "../UniversalProfileTestsHelper.sol"; contract ExecuteUnrestrictedController is UniversalProfileTestsHelper { @@ -260,7 +263,8 @@ contract ExecuteUnrestrictedController is UniversalProfileTestsHelper { indentifiableDigitalAsset = new LSP8Tester( "TestLSP8", "TSTLSP8", - digitalAssetsOwner + digitalAssetsOwner, + _LSP8_TOKENID_TYPE_NUMBER ); bytes32 tokenID = bytes32(uint256(1)); diff --git a/tests/foundry/LSP11BasicSocialRecovery/LSP11BasicSocialRecovery.t.sol b/tests/foundry/LSP11BasicSocialRecovery/LSP11BasicSocialRecovery.t.sol index 16edbc3c5..1e37dea63 100644 --- a/tests/foundry/LSP11BasicSocialRecovery/LSP11BasicSocialRecovery.t.sol +++ b/tests/foundry/LSP11BasicSocialRecovery/LSP11BasicSocialRecovery.t.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: UNLICENSED +// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.13; import "forge-std/Test.sol"; diff --git a/tests/foundry/LSP11BasicSocialRecovery/LSP11Mock.sol b/tests/foundry/LSP11BasicSocialRecovery/LSP11Mock.sol index d1d35b1c6..02414f79f 100644 --- a/tests/foundry/LSP11BasicSocialRecovery/LSP11Mock.sol +++ b/tests/foundry/LSP11BasicSocialRecovery/LSP11Mock.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: UNLICENSED +// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.13; import "../../../contracts/LSP11BasicSocialRecovery/LSP11BasicSocialRecovery.sol"; diff --git a/tests/foundry/LSP14Ownable2Step/TwoStepOwnership.sol b/tests/foundry/LSP14Ownable2Step/TwoStepOwnership.sol index d5e6486a2..c1e152a47 100644 --- a/tests/foundry/LSP14Ownable2Step/TwoStepOwnership.sol +++ b/tests/foundry/LSP14Ownable2Step/TwoStepOwnership.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: UNLICENSED +// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.13; import "forge-std/Test.sol"; diff --git a/tests/foundry/LSP14Ownable2Step/TwoStepRenounceOwnership.sol b/tests/foundry/LSP14Ownable2Step/TwoStepRenounceOwnership.sol index 0a00f8b4b..1936d494d 100644 --- a/tests/foundry/LSP14Ownable2Step/TwoStepRenounceOwnership.sol +++ b/tests/foundry/LSP14Ownable2Step/TwoStepRenounceOwnership.sol @@ -1,10 +1,14 @@ -// SPDX-License-Identifier: UNLICENSED +// 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 Implementation { // _pendingOwner is at slot 3 for LSP0ERC725Account bytes32[3] __gap; @@ -51,7 +55,12 @@ contract TwoStepRenounceOwnershipTest is Test { // Call acceptOwnership() to regain ownership should fail // as pendingOwner should be deleted on the second call of renounceOwnership again - vm.expectRevert("LSP14: caller is not the pendingOwner"); + vm.expectRevert( + abi.encodeWithSelector( + LSP20EOACannotVerifyCall.selector, + address(0) + ) + ); account.acceptOwnership(); } } diff --git a/tests/foundry/LSP16UniversalFactory/LSP16UniversalProfile.t.sol b/tests/foundry/LSP16UniversalFactory/LSP16UniversalProfile.t.sol index 9371afd89..48d4c397b 100644 --- a/tests/foundry/LSP16UniversalFactory/LSP16UniversalProfile.t.sol +++ b/tests/foundry/LSP16UniversalFactory/LSP16UniversalProfile.t.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: UNLICENSED +// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.13; import "forge-std/Test.sol"; diff --git a/tests/foundry/LSP2ERC725YJSONSchema/LSP2Utils.t.sol b/tests/foundry/LSP2ERC725YJSONSchema/LSP2Utils.t.sol index 0e217913e..677e6b409 100644 --- a/tests/foundry/LSP2ERC725YJSONSchema/LSP2Utils.t.sol +++ b/tests/foundry/LSP2ERC725YJSONSchema/LSP2Utils.t.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: UNLICENSED +// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.13; import "forge-std/Test.sol"; diff --git a/tests/foundry/LSP6KeyManager/LSP6SetDataTest.t.sol b/tests/foundry/LSP6KeyManager/LSP6SetDataTest.t.sol index 75c862208..729b284e3 100644 --- a/tests/foundry/LSP6KeyManager/LSP6SetDataTest.t.sol +++ b/tests/foundry/LSP6KeyManager/LSP6SetDataTest.t.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: UNLICENSED +// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.4; import "forge-std/Test.sol"; @@ -102,7 +102,13 @@ contract LSP6SetDataTest is Test { keyManager.execute(callData); // CHECK the LSP20 verification function reverts as well - keyManager.lsp20VerifyCall(malicious, 0, functionArgs); + keyManager.lsp20VerifyCall( + malicious, + address(universalProfile), + malicious, + 0, + functionArgs + ); // CHECK it reverts when calling directly the Universal Profile universalProfile.setData(dataKey, dataValue); @@ -222,7 +228,13 @@ contract LSP6SetDataTest is Test { keyManager.execute(callData); // CHECK the LSP20 verification function reverts as well - keyManager.lsp20VerifyCall(malicious, 0, functionArgs); + keyManager.lsp20VerifyCall( + malicious, + address(universalProfile), + malicious, + 0, + functionArgs + ); // CHECK it reverts when calling directly the Universal Profile universalProfile.setData(dataKey, dataValue); diff --git a/tests/foundry/LSP6KeyManager/LSP6Utils.t.sol b/tests/foundry/LSP6KeyManager/LSP6Utils.t.sol index 568a4f8af..2affc1f25 100644 --- a/tests/foundry/LSP6KeyManager/LSP6Utils.t.sol +++ b/tests/foundry/LSP6KeyManager/LSP6Utils.t.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: UNLICENSED +// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.13; import "forge-std/Test.sol"; diff --git a/tests/utils/context.ts b/tests/utils/context.ts index 266d1856c..7433a2ea6 100644 --- a/tests/utils/context.ts +++ b/tests/utils/context.ts @@ -4,7 +4,7 @@ import { KeyManagerInternalTester, LSP6KeyManager, UniversalProfile } from '../. export type LSP6TestContext = { accounts: SignerWithAddress[]; - owner: SignerWithAddress; + mainController: SignerWithAddress; universalProfile: UniversalProfile; keyManager: LSP6KeyManager; initialFunding?: BigNumber; @@ -12,7 +12,7 @@ export type LSP6TestContext = { export type LSP6InternalsTestContext = { accounts: SignerWithAddress[]; - owner: SignerWithAddress; + mainController: SignerWithAddress; universalProfile: UniversalProfile; keyManagerInternalTester: KeyManagerInternalTester; }; diff --git a/tests/utils/fixtures.ts b/tests/utils/fixtures.ts index 8b9934130..772b146d4 100644 --- a/tests/utils/fixtures.ts +++ b/tests/utils/fixtures.ts @@ -49,24 +49,25 @@ export async function setupKeyManager( _dataKeys: string[], _dataValues: string[], ) { - await _context.universalProfile.connect(_context.owner).setDataBatch( + await _context.universalProfile.connect(_context.mainController).setDataBatch( [ - // required to set owner permission so that it can acceptOwnership(...) via the KeyManager - // otherwise, the KeyManager will flag the calling owner as not having the permission CHANGEOWNER + // required to set main controller permission so that it can acceptOwnership(...) via the KeyManager + // otherwise, the KeyManager will flag the calling main controller as not having the permission CHANGEOWNER // when trying to setup the KeyManager - ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + _context.owner.address.substring(2), + ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + + _context.mainController.address.substring(2), ..._dataKeys, ], [ALL_PERMISSIONS, ..._dataValues], ); await _context.universalProfile - .connect(_context.owner) + .connect(_context.mainController) .transferOwnership(_context.keyManager.address); const payload = _context.universalProfile.interface.getSighash('acceptOwnership'); - await _context.keyManager.connect(_context.owner).execute(payload); + await _context.keyManager.connect(_context.mainController).execute(payload); } export async function setupKeyManagerHelper( @@ -75,23 +76,23 @@ export async function setupKeyManagerHelper( _permissionsValues: string[], ) { await _context.universalProfile - .connect(_context.owner) + .connect(_context.mainController) .setDataBatch( [ ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + - _context.owner.address.substring(2), + _context.mainController.address.substring(2), ..._permissionsKeys, ], [ALL_PERMISSIONS, ..._permissionsValues], ); await _context.universalProfile - .connect(_context.owner) + .connect(_context.mainController) .transferOwnership(_context.keyManagerInternalTester.address); const payload = _context.universalProfile.interface.getSighash('acceptOwnership'); - await _context.keyManagerInternalTester.connect(_context.owner).execute(payload); + await _context.keyManagerInternalTester.connect(_context.mainController).execute(payload); } /** diff --git a/tsconfig.json b/tsconfig.json index a38d1f94b..9364e1eff 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -10,14 +10,6 @@ "skipLibCheck": true, "lib": ["ES2019", "es2019.array"] }, - "include": ["./src", "./test", "hardhat"], - "exclude": [ - "node_modules", - "dist", - "build", - "coverage", - "artifacts", - "cache" - ], + "include": ["./tests", "./deploy", "hardhat"], "files": ["./hardhat.config.ts"] } diff --git a/tsconfig.module.json b/tsconfig.module.json index 83997ab57..db1b7f1fd 100644 --- a/tsconfig.module.json +++ b/tsconfig.module.json @@ -3,7 +3,6 @@ "compilerOptions": { "module": "esNext", "target": "esNext", - "outDir": "module", "declaration": true, "skipLibCheck": false },