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 +- -**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. | - -
- -**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`. | - -
- -**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. | - -
@@ -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. |@@ -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` | - |
+### \_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. |@@ -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`.
@@ -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`.
@@ -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. |@@ -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` | - |
+### \_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. |@@ -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`.
@@ -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`.
@@ -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. |@@ -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` | - |
+### \_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. |@@ -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`.
@@ -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`.
@@ -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} + +++ + `; + + 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:UniversalProfile owned by a ๐๐ LSP6KeyManager
+ +### ๐ \`execute\` scenarios + +${generatedKeyManagerExecuteTable} + +### ๐๏ธ \`setData\` scenarios + +${generatedKeyManagerSetDataTable} + +
`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'; - -+ 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 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 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โฝ๐ 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); - { @@ -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