diff --git a/contracts/modules/CoreActions.sol b/contracts/modules/CoreActions.sol new file mode 100644 index 00000000..708adc9e --- /dev/null +++ b/contracts/modules/CoreActions.sol @@ -0,0 +1,328 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.16; + +import { ICoreActions } from "@modules/interfaces/ICoreActions.sol"; +import { IAddressAliasRegistry } from "@modules/interfaces/IAddressAliasRegistry.sol"; +import { EnumerableMap } from "openzeppelin/utils/structs/EnumerableMap.sol"; +import { EIP712 } from "solady/utils/EIP712.sol"; +import { LibBitmap } from "solady/utils/LibBitmap.sol"; +import { SignatureCheckerLib } from "solady/utils/SignatureCheckerLib.sol"; +import { LibZip } from "solady/utils/LibZip.sol"; +import { LibMulticaller } from "multicaller/LibMulticaller.sol"; +import { LibOps } from "@core/utils/LibOps.sol"; + +/** + * @title CoreActions + * @dev The registry for social coreActions. + */ +contract CoreActions is ICoreActions, EIP712 { + using LibBitmap for LibBitmap.Bitmap; + using EnumerableMap for EnumerableMap.AddressToUintMap; + + // ============================================================= + // CONSTANTS + // ============================================================= + + /** + * @dev For EIP-712 signature digest calculation. + */ + bytes32 public constant CORE_ACTION_REGISTRATIONS_TYPEHASH = + // prettier-ignore + keccak256( + "CoreActionRegistrations(" + "uint256 coreActionType," + "address[] targets," + "address[][] actors," + "uint256[][] timestamps," + "uint256 nonce" + ")" + ); + + // ============================================================= + // IMMUTABLES + // ============================================================= + + /** + * @dev The address alias registry. + */ + address public immutable addressAliasRegistry; + + // ============================================================= + // STORAGE + // ============================================================= + + /** + * @dev Mapping of `platform` => `coreActionType` => `target` => `actor` => `timestamp`. + */ + mapping(address => mapping(uint256 => mapping(address => EnumerableMap.AddressToUintMap))) internal _coreActions; + + /** + * @dev For storing the invalidated nonces. + */ + mapping(address => LibBitmap.Bitmap) internal _invalidatedNonces; + + /** + * @dev A mapping of `platform` => `platformSigner`. + */ + mapping(address => address) public platformSigner; + + // ============================================================= + // CONSTRUCTOR + // ============================================================= + + constructor(address addressAliasRegistry_) payable { + addressAliasRegistry = addressAliasRegistry_; + } + + // ============================================================= + // PUBLIC / EXTERNAL WRITE FUNCTIONS + // ============================================================= + + /** + * @inheritdoc ICoreActions + */ + function register(CoreActionRegistrations calldata r) + external + returns (address[] memory targetAliases, address[][] memory actorAliases) + { + uint256 n = r.targets.length; + address[] memory resolvedTargets; + address[][] memory resolvedActors = new address[][](n); + actorAliases = new address[][](n); + + // Check input array lengths and resolve aliases. + unchecked { + if (n != r.actors.length) revert ArrayLengthsMismatch(); + if (n != r.timestamps.length) revert ArrayLengthsMismatch(); + + IAddressAliasRegistry registry = IAddressAliasRegistry(addressAliasRegistry); + (resolvedTargets, targetAliases) = registry.resolveAndRegister(r.targets); + + for (uint256 i; i != n; ++i) { + if (r.actors[i].length != r.timestamps[i].length) revert ArrayLengthsMismatch(); + (resolvedActors[i], actorAliases[i]) = registry.resolveAndRegister(actorAliases[i]); + } + } + + // Check the signature and invalidate the nonce. + unchecked { + bytes32 digest = _hashTypedData( + keccak256( + abi.encode( + CORE_ACTION_REGISTRATIONS_TYPEHASH, + r.coreActionType, // uint256 + _hashOf(resolvedTargets), // address[] + _hashOf(resolvedActors), // address[][] + _hashOf(r.timestamps), // uint256[][] + r.nonce // uint256 + ) + ) + ); + + address signer = platformSigner[r.platform]; + if (!SignatureCheckerLib.isValidSignatureNowCalldata(signer, digest, r.signature)) + revert InvalidSignature(); + if (!_invalidatedNonces[signer].toggle(r.nonce)) revert InvalidSignature(); + + uint256[] memory nonces = new uint256[](1); + nonces[0] = r.nonce; + emit NoncesInvalidated(signer, nonces); + } + + // Store and emit events. + unchecked { + for (uint256 i; i != r.targets.length; ++i) { + address target = r.targets[i]; + EnumerableMap.AddressToUintMap storage m = _coreActions[r.platform][r.coreActionType][target]; + for (uint256 j; j != r.actors[i].length; ++j) { + address actor = r.actors[i][j]; + if (!m.contains(actor)) { + uint256 timestamp = r.timestamps[i][j]; + m.set(actor, timestamp); + emit Interacted(r.platform, r.coreActionType, target, actor, timestamp); + } + } + } + } + } + + /** + * @inheritdoc ICoreActions + */ + function invalidateNonces(uint256[] calldata nonces) external { + unchecked { + address sender = LibMulticaller.sender(); + LibBitmap.Bitmap storage s = _invalidatedNonces[sender]; + for (uint256 i; i != nonces.length; ++i) { + s.set(nonces[i]); + } + emit NoncesInvalidated(sender, nonces); + } + } + + /** + * @inheritdoc ICoreActions + */ + function setPlatformSigner(address signer) public { + address sender = LibMulticaller.senderOrSigner(); + platformSigner[sender] = signer; + emit PlatformSignerSet(sender, signer); + } + + // Misc functions: + // --------------- + + /** + * @dev For calldata compression. + */ + fallback() external payable { + LibZip.cdFallback(); + } + + /** + * @dev For calldata compression. + */ + receive() external payable { + LibZip.cdFallback(); + } + + // ============================================================= + // PUBLIC / EXTERNAL VIEW FUNCTIONS + // ============================================================= + + /** + * @inheritdoc ICoreActions + */ + function noncesInvalidated(address signer, uint256[] calldata nonces) public view returns (bool[] memory result) { + unchecked { + result = new bool[](nonces.length); + LibBitmap.Bitmap storage s = _invalidatedNonces[signer]; + for (uint256 i; i != nonces.length; ++i) { + result[i] = s.get(nonces[i]); + } + } + } + + /** + * @inheritdoc ICoreActions + */ + function getCoreActionTimestamp( + address platform, + uint256 coreActionType, + address target, + address actor + ) public view returns (uint256) { + return _coreActions[platform][coreActionType][target].get(actor); + } + + /** + * @inheritdoc ICoreActions + */ + function numCoreActions( + address platform, + uint256 coreActionType, + address target + ) public view returns (uint256) { + return _coreActions[platform][coreActionType][target].length(); + } + + /** + * @inheritdoc ICoreActions + */ + function getCoreActions( + address platform, + uint256 coreActionType, + address target + ) public view returns (address[] memory actors, uint256[] memory timestamps) { + EnumerableMap.AddressToUintMap storage m = _coreActions[platform][coreActionType][target]; + return getCoreActionsIn(platform, coreActionType, target, 0, m.length()); + } + + /** + * @inheritdoc ICoreActions + */ + function getCoreActionsIn( + address platform, + uint256 coreActionType, + address target, + uint256 start, + uint256 stop + ) public view returns (address[] memory actors, uint256[] memory timestamps) { + EnumerableMap.AddressToUintMap storage m = _coreActions[platform][coreActionType][target]; + unchecked { + uint256 l = stop - start; + uint256 n = m.length(); + if (LibOps.or(start > stop, stop > n)) revert InvalidQueryRange(); + actors = new address[](l); + timestamps = new uint256[](l); + for (uint256 i; i != l; ++i) { + (actors[i], timestamps[i]) = m.at(start + i); + } + } + } + + // ============================================================= + // INTERNAL / PRIVATE HELPERS + // ============================================================= + + /** + * @dev Override for EIP-712. + * @return name_ The EIP-712 name. + * @return version_ The EIP-712 version. + */ + function _domainNameAndVersion() + internal + pure + virtual + override + returns (string memory name_, string memory version_) + { + name_ = "CoreActions"; + version_ = "1"; + } + + /** + * @dev Returns the hash of `a`. + */ + function _hashOf(address[] memory a) internal pure freeTempMemory returns (bytes32) { + return keccak256(abi.encodePacked(a)); + } + + /** + * @dev Returns the hash of `a`. + */ + function _hashOf(address[][] memory a) internal pure freeTempMemory returns (bytes32) { + uint256 n = a.length; + bytes32[] memory encoded = new bytes32[](n); + for (uint256 i = 0; i != n; ++i) { + encoded[i] = keccak256(abi.encodePacked(a[i])); + } + return keccak256(abi.encodePacked(encoded)); + } + + /** + * @dev Returns the hash of `a`. + */ + function _hashOf(uint256[][] calldata a) internal pure freeTempMemory returns (bytes32) { + uint256 n = a.length; + bytes32[] memory encoded = new bytes32[](n); + for (uint256 i = 0; i != n; ++i) { + encoded[i] = keccak256(abi.encodePacked(a[i])); + } + return keccak256(abi.encodePacked(encoded)); + } + + /** + * @dev Frees all memory allocated within the scope of the function. + */ + modifier freeTempMemory() { + bytes32 m; + assembly { + m := mload(0x40) + } + _; + assembly { + mstore(0x40, m) + } + } +} diff --git a/contracts/modules/interfaces/ICoreActions.sol b/contracts/modules/interfaces/ICoreActions.sol new file mode 100644 index 00000000..77a0e237 --- /dev/null +++ b/contracts/modules/interfaces/ICoreActions.sol @@ -0,0 +1,201 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.16; + +/** + * @title CoreActions + * @dev The registry for social core actions. + */ +interface ICoreActions { + // ============================================================= + // STRUCTS + // ============================================================= + + /** + * @dev A struct containing the arguments for registering core actions. + */ + struct CoreActionRegistrations { + // The platform. + address platform; + // The core action type. + uint256 coreActionType; + // The list of targets. + address[] targets; + // The list of lists of timestamps. Must have the same dimensions as `actors`. + address[][] actors; + // The list of lists of timestamps. Must have the same dimensions as `actors`. + uint256[][] timestamps; + // The nonce of the signature (per platform's signer). + uint256 nonce; + // A signature by the current `platform` signer to authorize registration. + bytes signature; + } + + // ============================================================= + // EVENTS + // ============================================================= + + /** + * @dev Emitted when `actor` performs a core action with `target`, + * of `coreActionType`, on `platform`, at `timestamp`. + * @param platform The platform address. + * @param coreActionType The core action type. + * @param target The core action target. + * @param actor The core action actor. + * @param timestamp The core action timestamp. + */ + event Interacted( + address indexed platform, + uint256 coreActionType, + address indexed target, + address indexed actor, + uint256 timestamp + ); + + /** + * @dev Emitted when the `nonces` of `signer` are invalidated. + * @param signer The signer of the nonces. + * @param nonces The nonces. + */ + event NoncesInvalidated(address indexed signer, uint256[] nonces); + + /** + * @dev Emitted when the signer for a platform is set. + * @param platform The platform address. + * @param signer The signer for the platform. + */ + event PlatformSignerSet(address indexed platform, address signer); + + // ============================================================= + // ERRORS + // ============================================================= + + /** + * @dev The signature is invalid. + */ + error InvalidSignature(); + + /** + * @dev The length of the input arrays must be the same. + */ + error ArrayLengthsMismatch(); + + /** + * @dev The query range exceeds the bounds. + */ + error InvalidQueryRange(); + + // ============================================================= + // PUBLIC / EXTERNAL WRITE FUNCTIONS + // ============================================================= + + /** + * @dev Registers a batch of core actions. + * @param r The core actions to register. + * @return targetAliases A list of aliases corresponding to `targets`. + * @return actorAliases A list of aliases corresponding to `actors`. + */ + function register(CoreActionRegistrations memory r) + external + returns (address[] memory targetAliases, address[][] memory actorAliases); + + /** + * @dev Allows the platform to set their signer. + * @param signer The signer for the platform. + */ + function setPlatformSigner(address signer) external; + + /** + * @dev Invalidates the nonces for the `msg.sender`. + * @param nonces The nonces. + */ + function invalidateNonces(uint256[] calldata nonces) external; + + // ============================================================= + // PUBLIC / EXTERNAL VIEW FUNCTIONS + // ============================================================= + + /** + * @dev Returns the CoreActionRegistrations struct's EIP-712 typehash. + * @return The constant value. + */ + function CORE_ACTION_REGISTRATIONS_TYPEHASH() external pure returns (bytes32); + + /** + * @dev Returns the configured signer for `platform`. + * @param platform The platform. + * @return The configured value. + */ + function platformSigner(address platform) external view returns (address); + + /** + * @dev Returns whether each of the `nonces` of `signer` has been invalidated. + * @param signer The signer of the signature. + * @param nonces An array of nonces. + * @return A bool array representing whether each nonce has been invalidated. + */ + function noncesInvalidated(address signer, uint256[] calldata nonces) external view returns (bool[] memory); + + /** + * @dev Returns the core action timestamp of `actor` on target`, + * of `coreActionType`, on `platform`. + * @param platform The platform. + * @param coreActionType The core action type. + * @param target The core action target. + * @param actor The actor + * @return The amped timestamp value. + */ + function getCoreActionTimestamp( + address platform, + uint256 coreActionType, + address target, + address actor + ) external view returns (uint256); + + /** + * @dev Returns the number of core actions on `target`, + * of `coreActionType` on `platform`. + * @param platform The platform. + * @param coreActionType The core action type. + * @param target The core action target. + * @return The latest value. + */ + function numCoreActions( + address platform, + uint256 coreActionType, + address target + ) external view returns (uint256); + + /** + * @dev Returns the list of `actors` and `timestamps` + * for coreActions on `target`, of `coreActionType`, on `platform`. + * @param platform The platform. + * @param coreActionType The core action type. + * @param target The core action target. + * @return actors The actors for the coreActions. + * @return timestamps The timestamps of the coreActions. + */ + function getCoreActions( + address platform, + uint256 coreActionType, + address target + ) external view returns (address[] memory actors, uint256[] memory timestamps); + + /** + * @dev Returns the list of `actors` and `timestamps` + * for coreActions on `target`, of `coreActionType`, on `platform`. + * @param platform The platform. + * @param coreActionType The core action type. + * @param target The core action target. + * @param start The start index of the range. + * @param stop The end index of the range (exclusive). + * @return actors The actors for the coreActions. + * @return timestamps The timestamps of the coreActions. + */ + function getCoreActionsIn( + address platform, + uint256 coreActionType, + address target, + uint256 start, + uint256 stop + ) external view returns (address[] memory actors, uint256[] memory timestamps); +}