Skip to content

Commit

Permalink
Merge branch 'develop' into fix/userdocs-errors
Browse files Browse the repository at this point in the history
  • Loading branch information
richtera authored Aug 23, 2023
2 parents 999956d + e938d86 commit 84b1df9
Show file tree
Hide file tree
Showing 23 changed files with 1,890 additions and 1,406 deletions.
315 changes: 135 additions & 180 deletions contracts/LSP10ReceivedVaults/LSP10Utils.sol

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,16 @@ import {LSP5Utils} from "../../LSP5ReceivedAssets/LSP5Utils.sol";

// constants
import "../LSP1Constants.sol";
import {
_TYPEID_LSP7_TOKENSSENDER,
_TYPEID_LSP7_TOKENSRECIPIENT,
_INTERFACEID_LSP7
} from "../../LSP7DigitalAsset/LSP7Constants.sol";
import {
_TYPEID_LSP8_TOKENSSENDER,
_TYPEID_LSP8_TOKENSRECIPIENT,
_INTERFACEID_LSP8
} from "../../LSP8IdentifiableDigitalAsset/LSP8Constants.sol";
import "../../LSP9Vault/LSP9Constants.sol";

// errors
Expand All @@ -43,101 +53,142 @@ contract LSP1UniversalReceiverDelegateVault is ERC165, ILSP1UniversalReceiver {
* @notice Reacted on received notification with `typeId`.
*
* @custom:requirements Cannot accept native tokens.
* @custom:info
* - If some issues occured with generating the `dataKeys` or `dataValues` the `returnedMessage` will be an error message, otherwise it will be empty.
* - If an error occured when trying to use `setDataBatch(dataKeys,dataValues)`, it will return the raw error data back to the caller.
*
* @param typeId Unique identifier for a specific notification.
* @return result The result of the reaction for `typeId`.
* @return The result of the reaction for `typeId`.
*/
function universalReceiver(
bytes32 typeId,
bytes memory /* data */
) public payable virtual returns (bytes memory result) {
if (msg.value != 0) revert NativeTokensNotAccepted();
// This contract acts like a UniversalReceiverDelegate of a Vault where we append the
// address and the value, sent to the universalReceiver function of the LSP9, to the msg.data
// Check https://github.com/lukso-network/LIPs/blob/main/LSPs/LSP-9-Vault.md#universalreceiver
) public payable virtual returns (bytes memory) {
// CHECK that we did not send any native tokens to the LSP1 Delegate, as it cannot transfer them back.
if (msg.value != 0) {
revert NativeTokensNotAccepted();
}

address notifier = address(bytes20(msg.data[msg.data.length - 52:]));

(
bool invalid,
bytes10 mapPrefix,
bytes4 interfaceID,
bool isReceiving
) = LSP1Utils.getTransferDetails(typeId);
// The notifier is supposed to be either the LSP7 or LSP8 contract
// If it's EOA we revert to avoid registering the EOA as asset (spam protection)
// solhint-disable avoid-tx-origin
if (notifier == tx.origin) {
revert CannotRegisterEOAsAsAssets(notifier);
}

if (invalid || interfaceID == _INTERFACEID_LSP9)
return "LSP1: typeId out of scope";
if (typeId == _TYPEID_LSP7_TOKENSSENDER) {
return _tokenSender(notifier);
}

// solhint-disable avoid-tx-origin
if (notifier == tx.origin) revert CannotRegisterEOAsAsAssets(notifier);

bytes32 notifierMapKey = LSP2Utils.generateMappingKey(
mapPrefix,
bytes20(notifier)
);
bytes memory notifierMapValue = IERC725Y(msg.sender).getData(
notifierMapKey
);

bytes32[] memory dataKeys;
bytes[] memory dataValues;

if (isReceiving) {
// if the map value is already set, then do nothing
if (bytes20(notifierMapValue) != bytes20(0))
return "URD: asset received is already registered";

// CHECK balance only when the Token contract is already deployed,
// not when tokens are being transferred on deployment through the `constructor`
if (notifier.code.length > 0) {
// if the amount sent is 0, then do not update the keys
uint256 balance = ILSP7DigitalAsset(notifier).balanceOf(
msg.sender
);
if (balance == 0) return "LSP1: balance not updated";
if (typeId == _TYPEID_LSP7_TOKENSRECIPIENT) {
return _tokenRecipient(notifier, _INTERFACEID_LSP7);
}

if (typeId == _TYPEID_LSP8_TOKENSSENDER) {
return _tokenSender(notifier);
}

if (typeId == _TYPEID_LSP8_TOKENSRECIPIENT) {
return _tokenRecipient(notifier, _INTERFACEID_LSP8);
}

return "LSP1: typeId out of scope";
}

/**
* @dev Handler for LSP7 and LSP8 token sender type id.
*
* @custom:info
* - Tries to generate LSP5 data key/value pairs for removing asset from the ERC725Y storage.
* - Tries to use `setDataBatch(bytes32[],bytes[])` if generated proper LSP5 data key/value pairs.
* - Does not revert. But returns an error message. Use off-chain lib to get even more info.
*
* @param notifier The LSP7 or LSP8 token address.
*/
function _tokenSender(address notifier) internal returns (bytes memory) {
// if the amount sent is not the full balance, then do not update the keys
try ILSP7DigitalAsset(notifier).balanceOf(msg.sender) returns (
uint256 balance
) {
if (balance != 0) {
return "LSP1: full balance is not sent";
}
} catch {
return "LSP1: `balanceOf(address)` function not found";
}

(bytes32[] memory dataKeys, bytes[] memory dataValues) = LSP5Utils
.generateSentAssetKeys(msg.sender, notifier);

(dataKeys, dataValues) = LSP5Utils.generateReceivedAssetKeys(
msg.sender,
notifier,
notifierMapKey,
interfaceID
);

IERC725Y(msg.sender).setDataBatch(dataKeys, dataValues);
} else {
// if there is no map value for the asset to remove, then do nothing
if (bytes20(notifierMapValue) == bytes20(0))
return "LSP1: asset sent is not registered";

// if it's a token transfer (LSP7/LSP8)
uint256 balance = ILSP7DigitalAsset(notifier).balanceOf(msg.sender);
if (balance != 0) return "LSP1: full balance is not sent";

// if the value under the `LSP5ReceivedAssetsMap:<asset-address>`
// is not a valid tuple as `(bytes4,uint128)`
if (notifierMapValue.length < 20)
return "LSP1: asset data corrupted";

// Identify where the asset is located in the `LSP5ReceivedAssets[]` Array
// by extracting the index from the tuple value `(bytes4,uint128)`
// fetched under the LSP5ReceivedAssetsMap/LSP10VaultsMap data key
uint128 assetIndex = uint128(uint160(bytes20(notifierMapValue)));

(dataKeys, dataValues) = LSP5Utils.generateSentAssetKeys(
msg.sender,
notifierMapKey,
assetIndex
);

/**
* `generateSentAssetKeys(...)` returns empty arrays in the following cases:
* - the index returned from the data key `notifierMapKey` is bigger than
* the length of the `LSP5ReceivedAssets[]`, meaning, index is out of bounds.
*/
if (dataKeys.length == 0 && dataValues.length == 0)
return "LSP1: asset data corrupted";

IERC725Y(msg.sender).setDataBatch(dataKeys, dataValues);
// `generateSentAssetKeys(...)` returns empty arrays when encountering errors
if (dataKeys.length == 0 && dataValues.length == 0) {
return "LSP5: Error generating data key/value pairs";
}

// Set the LSP5 generated data keys on the account
return _setDataBatchWithoutReverting(dataKeys, dataValues);
}

/**
* @dev Handler for LSP7 and LSP8 token recipient type id.
*
* @custom:info
* - Tries to generate LSP5 data key/value pairs for adding asset to the ERC725Y storage.
* - Tries to use `setDataBatch(bytes32[],bytes[])` if generated proper LSP5 data key/value pairs.
* - Does not revert. But returns an error message. Use off-chain lib to get even more info.
*
* @param notifier The LSP7 or LSP8 token address.
* @param interfaceId The LSP7 or LSP8 interface id.
*/
function _tokenRecipient(
address notifier,
bytes4 interfaceId
) internal returns (bytes memory) {
// CHECK balance only when the Token contract is already deployed,
// not when tokens are being transferred on deployment through the `constructor`
if (notifier.code.length > 0) {
// if the amount sent is 0, then do not update the keys
try ILSP7DigitalAsset(notifier).balanceOf(msg.sender) returns (
uint256 balance
) {
if (balance == 0) {
return "LSP1: balance is zero";
}
} catch {
return "LSP1: `balanceOf(address)` function not found";
}
}

(bytes32[] memory dataKeys, bytes[] memory dataValues) = LSP5Utils
.generateReceivedAssetKeys(msg.sender, notifier, interfaceId);

// `generateReceivedAssetKeys(...)` returns empty arrays when encountering errors
if (dataKeys.length == 0 && dataValues.length == 0) {
return "LSP5: Error generating data key/value pairs";
}

// Set the LSP5 generated data keys on the account
return _setDataBatchWithoutReverting(dataKeys, dataValues);
}

/**
* @dev Calls `bytes4(keccak256(setDataBatch(bytes32[],bytes[])))` without checking for `bool succes`, but it returns all the data back.
*
* @custom:info If an the low-level transaction revert, the returned data will be forwarded. Th contract that uses this function can use the `Address` library to revert with the revert reason.
*
* @param dataKeys Data Keys to be set.
* @param dataValues Data Values to be set.
*/
function _setDataBatchWithoutReverting(
bytes32[] memory dataKeys,
bytes[] memory dataValues
) internal returns (bytes memory) {
try IERC725Y(msg.sender).setDataBatch(dataKeys, dataValues) {
return "";
} catch (bytes memory errorData) {
return errorData;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,14 @@
pragma solidity ^0.8.4;

interface IPostDeploymentModule {
/**
* @dev Executes post-deployment logic for the primary and secondary contracts.
* @notice This function can be used to perform any additional setup or configuration after the primary and secondary contracts have been deployed.
*
* @param primaryContract The address of the deployed primary contract.
* @param secondaryContract The address of the deployed secondary contract.
* @param calldataToPostDeploymentModule Calldata to be passed for the post-deployment execution.
*/
function executePostDeployment(
address primaryContract,
address secondaryContract,
Expand Down
126 changes: 126 additions & 0 deletions contracts/LSP2ERC725YJSONSchema/LSP2Utils.sol
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.4;

// interfaces
import {
IERC725Y
} from "@erc725/smart-contracts/contracts/interfaces/IERC725Y.sol";

// libraries
import {BytesLib} from "solidity-bytes-utils/contracts/BytesLib.sol";

Expand Down Expand Up @@ -415,4 +420,125 @@ library LSP2Utils {
if (pointer == compactBytesArray.length) return true;
return false;
}

/**
* @dev Validates if the bytes `arrayLength` are exactly 16 bytes long, and are of the exact size of an LSP2 Array length value
*
* @param arrayLength Plain bytes that should be validated.
*
* @return `true` if the value is 16 bytes long, `false` otherwise.
*/
function isValidLSP2ArrayLengthValue(
bytes memory arrayLength
) internal pure returns (bool) {
if (arrayLength.length == 16) {
return true;
}
return false;
}

/**
* @dev Generates Data Key/Value pairs for removing the last element from an LSP2 Array and a mapping Data Key.
*
* @param arrayKey The Data Key of Key Type Array.
* @param newArrayLength The new Array Length for the `arrayKey`.
* @param removedElementIndexKey The Data Key of Key Type Array Index for the removed element.
* @param removedElementMapKey The Data Key of a mapping to be removed.
*/
function removeLastElementFromArrayAndMap(
bytes32 arrayKey,
uint128 newArrayLength,
bytes32 removedElementIndexKey,
bytes32 removedElementMapKey
)
internal
pure
returns (bytes32[] memory dataKeys, bytes[] memory dataValues)
{
dataKeys = new bytes32[](3);
dataValues = new bytes[](3);

// store the number of received assets decremented by 1
dataKeys[0] = arrayKey;
dataValues[0] = abi.encodePacked(newArrayLength);

// remove the data value for the map key of the element
dataKeys[1] = removedElementMapKey;
dataValues[1] = "";

// remove the data value for the map key of the element
dataKeys[2] = removedElementIndexKey;
dataValues[2] = "";
}

/**
* @dev Generates Data Key/Value pairs for removing an element from an LSP2 Array and a mapping Data Key.
*
* @custom:info The function assumes that the Data Value stored under the mapping Data Key is of length 20 where the last 16 bytes are the index of the element in the array.
*
* @param ERC725YContract The ERC725Y contract.
* @param arrayKey The Data Key of Key Type Array.
* @param newArrayLength The new Array Length for the `arrayKey`.
* @param removedElementIndexKey The Data Key of Key Type Array Index for the removed element.
* @param removedElementIndex the index of the removed element.
* @param removedElementMapKey The Data Key of a mapping to be removed.
*/
function removeElementFromArrayAndMap(
IERC725Y ERC725YContract,

Check warning on line 487 in contracts/LSP2ERC725YJSONSchema/LSP2Utils.sol

View workflow job for this annotation

GitHub Actions / build

Variable name must be in mixedCase
bytes32 arrayKey,
uint128 newArrayLength,
bytes32 removedElementIndexKey,
uint128 removedElementIndex,
bytes32 removedElementMapKey
)
internal
view
returns (bytes32[] memory dataKeys, bytes[] memory dataValues)
{
dataKeys = new bytes32[](5);
dataValues = new bytes[](5);

// store the number of received assets decremented by 1
dataKeys[0] = arrayKey;
dataValues[0] = abi.encodePacked(newArrayLength);

// remove the data value for the map key of the element
dataKeys[1] = removedElementMapKey;
dataValues[1] = "";

// Generate the key of the last element in the array
bytes32 lastElementIndexKey = LSP2Utils.generateArrayElementKeyAtIndex(
arrayKey,
newArrayLength
);

// Get the data value from the key of the last element in the array
bytes20 lastElementIndexValue = bytes20(
ERC725YContract.getData(lastElementIndexKey)
);

// Set data value of the last element instead of the element from the array that will be removed
dataKeys[2] = removedElementIndexKey;
dataValues[2] = bytes.concat(lastElementIndexValue);

// Remove the data value for the swapped array element
dataKeys[3] = lastElementIndexKey;
dataValues[3] = "";

// Generate mapping key for the swapped array element
bytes32 lastElementMapKey = LSP2Utils.generateMappingKey(
bytes10(removedElementMapKey),
lastElementIndexValue
);

// Generate the mapping value for the swapped array element
bytes memory lastElementMapValue = abi.encodePacked(
bytes4(ERC725YContract.getData(lastElementMapKey)),
removedElementIndex
);

// Update the map value of the swapped array element to the new index
dataKeys[4] = lastElementMapKey;
dataValues[4] = lastElementMapValue;
}
}
Loading

0 comments on commit 84b1df9

Please sign in to comment.