Skip to content

Commit

Permalink
feat!: Notify Operator via LSP1 in LSP7 and LSP8 (#700)
Browse files Browse the repository at this point in the history
* feat!: notify operator via LSP1 in LSP7 and LSP8

* test: adjust old tests according to new functions

* chore: run prettier

* refactor!: calculate new interfaceid for LSP7 and LSP8

* chore: change parameter name

* test: add more tests to new hooks

* chore: resolve linter warnings

* chore: resolve failing tests

* chore: add missing natspec

* chore: resolve failing tests

* docs: update with latest docs

* chore: resolve failing tests

* chore: resolve solhint

* chore: remove unused vars
  • Loading branch information
YamenMerhi authored Sep 6, 2023
1 parent 87375c7 commit f090fd7
Show file tree
Hide file tree
Showing 43 changed files with 4,403 additions and 534 deletions.
12 changes: 10 additions & 2 deletions constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ export const INTERFACE_IDS = {
LSP0ERC725Account: '0x24871b3d',
LSP1UniversalReceiver: '0x6bb56a14',
LSP6KeyManager: '0x66918867',
LSP7DigitalAsset: '0xc6b21b81',
LSP8IdentifiableDigitalAsset: '0xa9ac26fe',
LSP7DigitalAsset: '0x05519512',
LSP8IdentifiableDigitalAsset: '0x1ae9ba1f',
LSP9Vault: '0x28af17e6',
LSP11BasicSocialRecovery: '0x049a28f1',
LSP14Ownable2Step: '0x94be5999',
Expand Down Expand Up @@ -282,6 +282,10 @@ export const LSP1_TYPE_IDS = {
LSP7Tokens_RecipientNotification:
'0x20804611b3e2ea21c480dc465142210acf4a2485947541770ec1fb87dee4a55c',

// keccak256('LSP7Tokens_OperatorNotification')
LSP7Tokens_OperatorNotification:
'0x386072cc5a58e61263b434c722725f21031cd06e7c552cfaa06db5de8a320dbc',

// keccak256('LSP8Tokens_SenderNotification')
LSP8Tokens_SenderNotification:
'0xb23eae7e6d1564b295b4c3e3be402d9a2f0776c57bdf365903496f6fa481ab00',
Expand All @@ -290,6 +294,10 @@ export const LSP1_TYPE_IDS = {
LSP8Tokens_RecipientNotification:
'0x0b084a55ebf70fd3c06fd755269dac2212c4d3f0f4d09079780bfa50c1b2984d',

// keccak256('LSP8Tokens_OperatorNotification')
LSP8Tokens_OperatorNotification:
'0x8a1c15a8799f71b547e08e2bcb2e85257e81b0a07eee2ce6712549eef1f00970',

// keccak256('LSP9OwnershipTransferStarted')
LSP9OwnershipTransferStarted:
'0xaefd43f45fed1bcd8992f23c803b6f4ec45cf6b62b0d404d565f290a471e763f',
Expand Down
24 changes: 20 additions & 4 deletions contracts/LSP7DigitalAsset/ILSP7DigitalAsset.sol
Original file line number Diff line number Diff line change
Expand Up @@ -37,19 +37,26 @@ interface ILSP7DigitalAsset is IERC165, IERC725Y {
* @param operator The address authorized as an operator
* @param tokenOwner The token owner
* @param amount The amount of tokens `operator` address has access to from `tokenOwner`
* @param operatorNotificationData The data to notify the operator about via LSP1.
*/
event AuthorizedOperator(
address indexed operator,
address indexed tokenOwner,
uint256 indexed amount
uint256 indexed amount,
bytes operatorNotificationData
);

/**
* @dev Emitted when `tokenOwner` disables `operator` for `amount` tokens and set its {`authorizedAmountFor(...)`} to `0`.
* @param operator The address revoked from operating
* @param tokenOwner The token owner
* @param operatorNotificationData The data to notify the operator about via LSP1.
*/
event RevokedOperator(address indexed operator, address indexed tokenOwner);
event RevokedOperator(
address indexed operator,
address indexed tokenOwner,
bytes operatorNotificationData
);

// --- Token queries

Expand Down Expand Up @@ -97,28 +104,37 @@ interface ILSP7DigitalAsset is IERC165, IERC725Y {
*
* @param operator The address to authorize as an operator.
* @param amount The allowance amount of tokens operator has access to.
* @param operatorNotificationData The data to notify the operator about via LSP1.
*
* @custom:requirements
* - `operator` cannot be the zero address.
*
* @custom:events {AuthorizedOperator} when allowance is given to a new operator or
* an existing operator's allowance is updated.
*/
function authorizeOperator(address operator, uint256 amount) external;
function authorizeOperator(
address operator,
uint256 amount,
bytes memory operatorNotificationData
) external;

/**
* @dev Removes the `operator` address as an operator of callers tokens, disallowing it to send any amount of tokens
* on behalf of the token owner (the caller of the function `msg.sender`). See also {authorizedAmountFor}.
*
* @param operator The address to revoke as an operator.
* @param operatorNotificationData The data to notify the operator about via LSP1.
*
* @custom:requirements
* - `operator` cannot be calling address.
* - `operator` cannot be the zero address.
*
* @custom:events {RevokedOperator} event with address of the operator being revoked for the caller (token holder).
*/
function revokeOperator(address operator) external;
function revokeOperator(
address operator,
bytes memory operatorNotificationData
) external;

/**
* @dev Get the amount of tokens `operator` address has access to from `tokenOwner`.
Expand Down
5 changes: 4 additions & 1 deletion contracts/LSP7DigitalAsset/LSP7Constants.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
pragma solidity ^0.8.4;

// --- ERC165 interface ids
bytes4 constant _INTERFACEID_LSP7 = 0xc6b21b81;
bytes4 constant _INTERFACEID_LSP7 = 0x05519512;

// --- Token Hooks

Expand All @@ -11,3 +11,6 @@ bytes32 constant _TYPEID_LSP7_TOKENSSENDER = 0x429ac7a06903dbc9c13dfcb3c9d11df81

// keccak256('LSP7Tokens_RecipientNotification')
bytes32 constant _TYPEID_LSP7_TOKENSRECIPIENT = 0x20804611b3e2ea21c480dc465142210acf4a2485947541770ec1fb87dee4a55c;

// keccak256('LSP7Tokens_OperatorNotification')
bytes32 constant _TYPEID_LSP7_TOKENOPERATOR = 0x386072cc5a58e61263b434c722725f21031cd06e7c552cfaa06db5de8a320dbc;
1 change: 1 addition & 0 deletions contracts/LSP7DigitalAsset/LSP7DigitalAsset.sol
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ abstract contract LSP7DigitalAsset is

// fallback function

// solhint-disable no-complex-fallback
/**
* @notice The `fallback` function was called with the following amount of native tokens: `msg.value`; and the following calldata: `callData`.
*
Expand Down
103 changes: 91 additions & 12 deletions contracts/LSP7DigitalAsset/LSP7DigitalAssetCore.sol
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import "./LSP7Errors.sol";
// constants
import {_INTERFACEID_LSP1} from "../LSP1UniversalReceiver/LSP1Constants.sol";
import {
_TYPEID_LSP7_TOKENOPERATOR,
_TYPEID_LSP7_TOKENSSENDER,
_TYPEID_LSP7_TOKENSRECIPIENT
} from "./LSP7Constants.sol";
Expand Down Expand Up @@ -97,16 +98,34 @@ abstract contract LSP7DigitalAssetCore is ILSP7DigitalAsset {
*/
function authorizeOperator(
address operator,
uint256 amount
uint256 amount,
bytes memory operatorNotificationData
) public virtual {
_updateOperator(msg.sender, operator, amount);
_updateOperator(msg.sender, operator, amount, operatorNotificationData);

bytes memory lsp1Data = abi.encode(
msg.sender,
amount,
operatorNotificationData
);
_notifyTokenOperator(operator, lsp1Data);
}

/**
* @inheritdoc ILSP7DigitalAsset
*/
function revokeOperator(address operator) public virtual {
_updateOperator(msg.sender, operator, 0);
function revokeOperator(
address operator,
bytes memory operatorNotificationData
) public virtual {
_updateOperator(msg.sender, operator, 0, operatorNotificationData);

bytes memory lsp1Data = abi.encode(
msg.sender,
0,
operatorNotificationData
);
_notifyTokenOperator(operator, lsp1Data);
}

/**
Expand Down Expand Up @@ -158,7 +177,7 @@ abstract contract LSP7DigitalAssetCore is ILSP7DigitalAsset {
);
}

_updateOperator(from, operator, operatorAmount - amount);
_updateOperator(from, operator, operatorAmount - amount, "");
}

_transfer(from, to, amount, allowNonLSP1Recipient, data);
Expand Down Expand Up @@ -222,13 +241,24 @@ abstract contract LSP7DigitalAssetCore is ILSP7DigitalAsset {
*/
function increaseAllowance(
address operator,
uint256 addedAmount
uint256 addedAmount,
bytes memory operatorNotificationData
) public virtual {
uint256 newAllowance = authorizedAmountFor(operator, msg.sender) +
addedAmount;
_updateOperator(
msg.sender,
operator,
authorizedAmountFor(operator, msg.sender) + addedAmount
newAllowance,
operatorNotificationData
);

bytes memory lsp1Data = abi.encode(
msg.sender,
newAllowance,
operatorNotificationData
);
_notifyTokenOperator(operator, lsp1Data);
}

/**
Expand Down Expand Up @@ -256,20 +286,31 @@ abstract contract LSP7DigitalAssetCore is ILSP7DigitalAsset {
*/
function decreaseAllowance(
address operator,
uint256 substractedAmount
uint256 substractedAmount,
bytes memory operatorNotificationData
) public virtual {
uint256 currentAllowance = authorizedAmountFor(operator, msg.sender);
if (currentAllowance < substractedAmount) {
revert LSP7DecreasedAllowanceBelowZero();
}

uint256 newAllowance;
unchecked {
newAllowance = currentAllowance - substractedAmount;
_updateOperator(
msg.sender,
operator,
currentAllowance - substractedAmount
newAllowance,
operatorNotificationData
);
}

bytes memory lsp1Data = abi.encode(
msg.sender,
newAllowance,
operatorNotificationData
);
_notifyTokenOperator(operator, lsp1Data);
}

/**
Expand All @@ -288,7 +329,8 @@ abstract contract LSP7DigitalAssetCore is ILSP7DigitalAsset {
function _updateOperator(
address tokenOwner,
address operator,
uint256 amount
uint256 amount,
bytes memory operatorNotificationData
) internal virtual {
if (operator == address(0)) {
revert LSP7CannotUseAddressZeroAsOperator();
Expand All @@ -302,10 +344,19 @@ abstract contract LSP7DigitalAssetCore is ILSP7DigitalAsset {

if (amount != 0) {
_operators[tokenOwner].add(operator);
emit AuthorizedOperator(operator, tokenOwner, amount);
emit AuthorizedOperator(
operator,
tokenOwner,
amount,
operatorNotificationData
);
} else {
_operators[tokenOwner].remove(operator);
emit RevokedOperator(operator, tokenOwner);
emit RevokedOperator(
operator,
tokenOwner,
operatorNotificationData
);
}
}

Expand Down Expand Up @@ -488,6 +539,34 @@ abstract contract LSP7DigitalAssetCore is ILSP7DigitalAsset {
uint256 amount
) internal virtual {}

/**
* @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 {
if (
ERC165Checker.supportsERC165InterfaceUnchecked(
operator,
_INTERFACEID_LSP1
)
) {
operator.call(
abi.encodeWithSelector(
ILSP1UniversalReceiver.universalReceiver.selector,
_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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ abstract contract LSP7DigitalAssetInitAbstract is

// fallback function

// solhint-disable no-complex-fallback
/**
* @notice The `fallback` function was called with the following amount of native tokens: `msg.value`; and the following calldata: `callData`.
*
Expand Down
12 changes: 9 additions & 3 deletions contracts/LSP7DigitalAsset/extensions/LSP7CompatibleERC20.sol
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ abstract contract LSP7CompatibleERC20 is
address operator,
uint256 amount
) public virtual returns (bool) {
authorizeOperator(operator, amount);
authorizeOperator(operator, amount, "");
return true;
}

Expand Down Expand Up @@ -109,9 +109,15 @@ abstract contract LSP7CompatibleERC20 is
function _updateOperator(
address tokenOwner,
address operator,
uint256 amount
uint256 amount,
bytes memory operatorNotificationData
) internal virtual override {
super._updateOperator(tokenOwner, operator, amount);
super._updateOperator(
tokenOwner,
operator,
amount,
operatorNotificationData
);
emit Approval(tokenOwner, operator, amount);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ abstract contract LSP7CompatibleERC20InitAbstract is
address operator,
uint256 amount
) public virtual returns (bool) {
authorizeOperator(operator, amount);
authorizeOperator(operator, amount, "");
return true;
}

Expand Down Expand Up @@ -116,9 +116,15 @@ abstract contract LSP7CompatibleERC20InitAbstract is
function _updateOperator(
address tokenOwner,
address operator,
uint256 amount
uint256 amount,
bytes memory operatorNotificationData
) internal virtual override {
super._updateOperator(tokenOwner, operator, amount);
super._updateOperator(
tokenOwner,
operator,
amount,
operatorNotificationData
);
emit Approval(tokenOwner, operator, amount);
}

Expand Down
Loading

0 comments on commit f090fd7

Please sign in to comment.