Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: add operator to the transfer notification functions in LSP7 and LSP8 #795

Merged
merged 9 commits into from
Nov 24, 2023

Conversation

YamenMerhi
Copy link
Member

@YamenMerhi YamenMerhi commented Nov 6, 2023

What does this PR introduce?

♻️ Refactor

  • add internal notification functions in LSP7 and LSP8 to be able to override them easily
  • Add operator as part of the lsp1Data in a case of notifying using LSP1 for LSP7 and LSP8

PR Checklist

  • Wrote Tests
  • Wrote & Generated Documentation (readme/natspec/dodoc)
  • Ran npm run lint && npm run lint:solidity (solhint)
  • Ran npm run format (prettier)
  • Ran npm run build
  • Ran npm run test

Copy link
Contributor

github-actions bot commented Nov 6, 2023

Changes to gas cost

Generated at commit: 233926862fcafba777ce4398516c28d03cc8ee6c, compared to commit: 6594f678c362bf26882023aa1a108bfd8188924d

🧾 Summary (10% most significant diffs)

Contract Method Avg (+/-) %
LSP6ExecuteUnrestrictedController transferTokensToRandomEOA +397 ❌ +0.54%
LSP6ExecuteRestrictedController transferTokensToRandomEOA +397 ❌ +0.53%

Full diff report 👇
Contract Deployment Cost (+/-) Method Min (+/-) % Avg (+/-) % Median (+/-) % Max (+/-) % # Calls (+/-)
LSP6ExecuteUnrestrictedController 3,047,114 (0) transferNFTToRandomEOA
transferNFTToRandomUP
transferTokensToRandomEOA
transferTokensToRandomUP
142,150 (+396)
248,150 (+774)
74,150 (+397)
205,094 (+968)
+0.28%
+0.31%
+0.54%
+0.47%
142,150 (+396)
248,150 (+774)
74,150 (+397)
205,094 (+968)
+0.28%
+0.31%
+0.54%
+0.47%
142,150 (+396)
248,150 (+774)
74,150 (+397)
205,094 (+968)
+0.28%
+0.31%
+0.54%
+0.47%
142,150 (+396)
248,150 (+774)
74,150 (+397)
205,094 (+968)
+0.28%
+0.31%
+0.54%
+0.47%
1 (0)
1 (0)
1 (0)
1 (0)
LSP6ExecuteRestrictedController 3,047,114 (0) transferNFTToRandomEOA
transferNFTToRandomUP
transferTokensToRandomEOA
transferTokensToRandomUP
143,406 (+396)
249,406 (+774)
75,406 (+397)
206,664 (+968)
+0.28%
+0.31%
+0.53%
+0.47%
143,406 (+396)
249,406 (+774)
75,406 (+397)
206,664 (+968)
+0.28%
+0.31%
+0.53%
+0.47%
143,406 (+396)
249,406 (+774)
75,406 (+397)
206,664 (+968)
+0.28%
+0.31%
+0.53%
+0.47%
143,406 (+396)
249,406 (+774)
75,406 (+397)
206,664 (+968)
+0.28%
+0.31%
+0.53%
+0.47%
1 (0)
1 (0)
1 (0)
1 (0)

Copy link
Contributor

github-actions bot commented Nov 6, 2023

👋 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

Deployed contracts ⛽ Deployment cost
UniversalProfile 3185468 (0 )
KeyManager 3659015 (0 )
LSP1DelegateUP 1637510 (0 )
LSP7Mintable 2347118 (13,995 📈❌)
LSP8Mintable 2446360 (23,290 📈❌)

Runtime Costs

UniversalProfile owned by an 🔑 EOA

🔀 execute scenarios

execute scenarios - UP owned by 🔑 EOA ⛽ Gas Usage
Transfer 1 LYX to an EOA without data 37560 (0 )
Transfer 1 LYX to a UP without data 46253 (0 )
Transfer 1 LYX to an EOA with 256 bytes of data 42233 (24 📈❌)
Transfer 1 LYX to a UP with 256 bytes of data 57162 (-24 📉✅)
Transfer 0.1 LYX to 3x EOA without data 70862 (0 )
Transfer 0.1 LYX to 3x UP without data 104453 (0 )
Transfer 0.1 LYX to 3x EOA with 256 bytes of data 84874 (60 📈❌)
Transfer 0.1 LYX to 3x UPs with 256 bytes of data 137209 (0 )

🗄️ setData scenarios

setData scenarios - UP owned by 🔑 EOA ⛽ Gas Usage
Set a 20 bytes long value 49951 (0 )
Set a 60 bytes long value 95273 (0 )
Set a 160 bytes long value 164433 (-12 📉✅)
Set a 300 bytes long value 279644 (-12 📉✅)
Set a 600 bytes long value 484128 (-12 📉✅)
Change the value of a data key already set 32839 (12 📈❌)
Remove the value of a data key already set 27313 (0 )
Set 2 data keys of 20 bytes long value 78500 (0 )
Set 2 data keys of 100 bytes long value 260652 (0 )
Set 3 data keys of 20 bytes long value 105206 (-12 📉✅)
Change the value of three data keys already set of 20 bytes long value 45518 (0 )
Remove the value of three data keys already set 41397 (0 )

🗄️ Tokens scenarios

Tokens scenarios - UP owned by 🔑 EOA ⛽ Gas Usage
Minting a LSP7Token to a UP (No Delegate) from an EOA 92680 (0 )
Minting a LSP7Token to an EOA from an EOA 59355 (0 )
Transferring an LSP7Token from a UP to another UP (No Delegate) 101508 (-48 📉✅)
Minting a LSP8Token to a UP (No Delegate) from an EOA 159571 (0 )
Minting a LSP8Token to an EOA from an EOA 126247 (0 )
Transferring an LSP8Token from a UP to another UP (No Delegate) 150202 (-47 📉✅)
UniversalProfile owned by a 🔒📄 LSP6KeyManager

🔀 execute scenarios

execute scenarios 👑 main controller 🛃 restricted controller
LYX transfer --> to an EOA 64356 (0 ) 75306 (0 )
LYX transfer --> to a UP 78489 (0 ) 93386 (0 )
LSP7 token transfer --> to an EOA 116305 (-48 📉✅) 131055 (-48 📉✅)
LSP7 token transfer --> to a UP 249535 (-48 📉✅) 264285 (-48 📉✅)
LSP8 NFT transfer --> to an EOA 180414 (-47 📉✅) 195141 (-47 📉✅)
LSP8 NFT transfer --> to a UP 296868 (-47 📉✅) 311595 (-47 📉✅)

🗄️ setData scenarios

setData scenarios 👑 main controller 🛃 restricted controller
Update Profile details (LSP3Profile Metadata) 67294 (0 ) 77316 (0 )
Add a new controller with permission to SET_DATA + 3x allowed data keys:
AddressPermissions[]
+ AddressPermissions[index]
+ AddressPermissions:Permissions:<controller>
+ AddressPermissions:AllowedERC725YDataKeys:<controller)
209636 (0 ) 219793 (0 )
Update permissions of previous controller. Allow it now to SUPER_SETDATA 52322 (0 ) 55328 (0 )
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:
78861 (0 ) 90160 (0 )
Write 5x new LSP12 Issued Assets 66989 (0 ) 101616 (0 )
Update 3x data keys (first 3) 125501 (0 ) 159605 (0 )
Update 3x data keys (middle 3) 105589 (0 ) 143759 (0 )
Update 3x data keys (last 3) 125501 (0 ) 169092 (0 )
Set 2 x new data keys + add 3x new controllers 810481 (0 ) 872281 (0 )

@CJ42
Copy link
Member

CJ42 commented Nov 23, 2023

This change make sense to me, as anyone wanting to override any custom logic for notifying can then just override these internal functions ✅ , instead of overriding the whole _mint, _burn and _transfer functions ❌ which could lead re-write the whole core logic incorrectly.

One alternative I want to propose is to still call the LSP1Utils library function inside these _notifyTokenOperator and _notifyTokenSender functions. As follow:

    /**
     * @dev Attempt to notify the operator `operator` about the `amount` tokens being authorized with.
     * This is done by calling its {universalReceiver} function with the `_TYPEID_LSP7_TOKENOPERATOR` as typeId, if `operator` is a contract that supports the LSP1 interface.
     * If `operator` is an EOA or a contract that does not support the LSP1 interface, nothing will happen and no notification will be sent.
     
     * @param operator The address to call the {universalReceiver} function on.                                                                                                                                                                                   
     * @param lsp1Data the data to be sent to the `operator` address in the `universalReceiver` call.
     */
    function _notifyTokenOperator(
        address operator,
        bytes memory lsp1Data
    ) internal virtual {
        operator.notifyUniversalReceiver(_TYPEID_LSP7_TOKENOPERATOR, lsp1Data);
    }

    /**
     * @dev Attempt to notify the token sender `from` about the `amount` of tokens being transferred.
     * This is done by calling its {universalReceiver} function with the `_TYPEID_LSP7_TOKENSSENDER` as typeId, if `from` is a contract that supports the LSP1 interface.
     * If `from` is an EOA or a contract that does not support the LSP1 interface, nothing will happen and no notification will be sent.
     
     * @param from The address to call the {universalReceiver} function on.                                                                                                                                                                                   
     * @param lsp1Data the data to be sent to the `from` address in the `universalReceiver` call.
     */
    function _notifyTokenSender(
        address from,
        bytes memory lsp1Data
    ) internal virtual {
        from.notifyUniversalReceiver(_TYPEID_LSP7_TOKENSSENDER, lsp1Data);
    }

Benefits:

  • Reduce deployment cost, which is important for the size of the token contracts, to keep the size to not grow too much, so that more functionalities for implementations can be added and not reach the max bytecode size allowed.
  • Still allow to override these functions.

Drawbacks:

  • costs a bit more gas, but minimal (+28 gas), compared to @YamenMerhi solution which actually saves gas (-48 gas, still minimal)
  • if someone need to override to add some custom data for instance, if adding the extra data before the library call, if the from does not support the LSP1 interface, gas is wasted preparing this extra data. For instance:
    // custom overriden notification function
    function _notifyTokenSender(
        address from,
        bytes memory lsp1Data
    ) internal override {
        lsp1Data = abi.encodePacked(lsp1Data, "some extra information like the msg.value received: ", msg.value);
        from.notifyUniversalReceiver(_TYPEID_LSP7_TOKENSSENDER, lsp1Data);
    }

In this code snippet above, preparing the data cost gas because some manipulation in memory is occuring. If the sender from does not supports LSP1 interface, this memory manipulation is unecessary and the gas is wasted.
By keeping the duplicate code (WET code), we can move this extra data preparation inside the if block check for the supportsInterface as follow:

    // this will not waste the gas to prepare the extra data
    function _notifyTokenSender(
        address from,
        bytes memory lsp1Data
    ) internal override {
        
        if (
            ERC165Checker.supportsERC165InterfaceUnchecked(
                from,
                _INTERFACEID_LSP1
            )
        ) {
            lsp1Data = abi.encodePacked(lsp1Data, "some extra information like the msg.value received: ", msg.value);
            ILSP1(from).universalReceiver(_TYPEID_LSP7_TOKENSSENDER, lsp1Data);
        }
    }

My conclusion: I would upvote my proposal, so that we have a solution in between @YamenMerhi PR (which is needed to easily override and customize the behaviour), and my recommendation, to keep contract size in control and it does not grow too much. For the examples I gave above, this is probably something that developers should be aware in each specific token contract implementation.

Gas cost of alternative solution proposed

👋 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

Deployed contracts ⛽ Deployment cost
UniversalProfile 3185468 (0 )
KeyManager 3659015 (0 )
LSP1DelegateUP 1637510 (0 )
LSP7Mintable 2327728 (-5,395 📉✅)
LSP8Mintable 2426970 (3,900 📈❌)

Runtime Costs

UniversalProfile owned by an 🔑 EOA

🔀 execute scenarios

execute scenarios - UP owned by 🔑 EOA ⛽ Gas Usage
Transfer 1 LYX to an EOA without data 37560 (0 )
Transfer 1 LYX to a UP without data 46253 (0 )
Transfer 1 LYX to an EOA with 256 bytes of data 42233 (0 )
Transfer 1 LYX to a UP with 256 bytes of data 57126 (-60 📉✅)
Transfer 0.1 LYX to 3x EOA without data 70862 (0 )
Transfer 0.1 LYX to 3x UP without data 104453 (0 )
Transfer 0.1 LYX to 3x EOA with 256 bytes of data 84838 (0 )
Transfer 0.1 LYX to 3x UPs with 256 bytes of data 137173 (-72 📉✅)

🗄️ setData scenarios

setData scenarios - UP owned by 🔑 EOA ⛽ Gas Usage
Set a 20 bytes long value 49951 (0 )
Set a 60 bytes long value 95261 (0 )
Set a 160 bytes long value 164421 (-24 📉✅)
Set a 300 bytes long value 279668 (0 )
Set a 600 bytes long value 484116 (12 📈❌)
Change the value of a data key already set 32839 (0 )
Remove the value of a data key already set 27313 (0 )
Set 2 data keys of 20 bytes long value 78500 (12 📈❌)
Set 2 data keys of 100 bytes long value 260652 (-12 📉✅)
Set 3 data keys of 20 bytes long value 105218 (24 📈❌)
Change the value of three data keys already set of 20 bytes long value 45506 (-12 📉✅)
Remove the value of three data keys already set 41397 (0 )

🗄️ Tokens scenarios

Tokens scenarios - UP owned by 🔑 EOA ⛽ Gas Usage
Minting a LSP7Token to a UP (No Delegate) from an EOA 92680 (0 )
Minting a LSP7Token to an EOA from an EOA 59355 (0 )
Transferring an LSP7Token from a UP to another UP (No Delegate) 101584 (28 📈❌)
Minting a LSP8Token to a UP (No Delegate) from an EOA 159571 (0 )
Minting a LSP8Token to an EOA from an EOA 126247 (0 )
Transferring an LSP8Token from a UP to another UP (No Delegate) 150277 (28 📈❌)
UniversalProfile owned by a 🔒📄 LSP6KeyManager

🔀 execute scenarios

execute scenarios 👑 main controller 🛃 restricted controller
LYX transfer --> to an EOA 64356 (0 ) 75306 (0 )
LYX transfer --> to a UP 78489 (0 ) 93386 (0 )
LSP7 token transfer --> to an EOA 116381 (28 📈❌) 131131 (28 📈❌)
LSP7 token transfer --> to a UP 249611 (28 📈❌) 264361 (28 📈❌)
LSP8 NFT transfer --> to an EOA 180489 (28 📈❌) 195216 (28 📈❌)
LSP8 NFT transfer --> to a UP 296943 (28 📈❌) 311670 (28 📈❌)

🗄️ setData scenarios

setData scenarios 👑 main controller 🛃 restricted controller
Update Profile details (LSP3Profile Metadata) 67294 (0 ) 77316 (0 )
Add a new controller with permission to SET_DATA + 3x allowed data keys:
AddressPermissions[]
+ AddressPermissions[index]
+ AddressPermissions:Permissions:<controller>
+ AddressPermissions:AllowedERC725YDataKeys:<controller)
209636 (0 ) 219793 (0 )
Update permissions of previous controller. Allow it now to SUPER_SETDATA 52322 (0 ) 55328 (0 )
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:
78861 (0 ) 90160 (0 )
Write 5x new LSP12 Issued Assets 66989 (0 ) 101616 (0 )
Update 3x data keys (first 3) 125501 (0 ) 159605 (0 )
Update 3x data keys (middle 3) 105589 (0 ) 143759 (0 )
Update 3x data keys (last 3) 125501 (0 ) 169092 (0 )
Set 2 x new data keys + add 3x new controllers 810481 (0 ) 872281 (0 )

@CJ42
Copy link
Member

CJ42 commented Nov 23, 2023

Also depending on the solution we choose, we will have to remove the using LSP1Utils for address statements:

@YamenMerhi YamenMerhi changed the title refactor: add internal notification functions in LSP7 and LSP8 refactor: add operator to the transfer notification functions in LSP7 and LSP8 Nov 24, 2023
@CJ42 CJ42 merged commit b10080f into develop Nov 24, 2023
25 checks passed
@CJ42 CJ42 deleted the lsp1/token-hooks branch November 24, 2023 14:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

3 participants