From 9d26c19cfbb6a50d1765943843147de52a055e07 Mon Sep 17 00:00:00 2001 From: vimageDE Date: Mon, 16 Dec 2024 12:23:58 +0100 Subject: [PATCH] Interface and clean-up --- src/examples/allocator/SimpleAllocator.sol | 45 +++++----------- src/interfaces/ISimpleAllocator.sol | 61 ++++++++++++++++++++++ 2 files changed, 75 insertions(+), 31 deletions(-) create mode 100644 src/interfaces/ISimpleAllocator.sol diff --git a/src/examples/allocator/SimpleAllocator.sol b/src/examples/allocator/SimpleAllocator.sol index def35df..447f6ed 100644 --- a/src/examples/allocator/SimpleAllocator.sol +++ b/src/examples/allocator/SimpleAllocator.sol @@ -2,60 +2,38 @@ pragma solidity ^0.8.27; -import { ERC6909 } from "lib/solady/src/tokens/ERC6909.sol"; import { ERC6909 } from "lib/solady/src/tokens/ERC6909.sol"; import { IERC1271 } from "openzeppelin-contracts/contracts/interfaces/IERC1271.sol"; -import { Ownable } from "openzeppelin-contracts/contracts/access/Ownable.sol"; -import { Ownable2Step } from "openzeppelin-contracts/contracts/access/Ownable2Step.sol"; -import { ECDSA } from "openzeppelin-contracts/contracts/utils/cryptography/ECDSA.sol"; import { IAllocator } from "src/interfaces/IAllocator.sol"; -import { ITheCompactClaims } from "src/interfaces/ITheCompactClaims.sol"; import { ITheCompact } from "src/interfaces/ITheCompact.sol"; -import { BasicClaim } from "src/types/Claims.sol"; +import { ISimpleAllocator } from "src/interfaces/ISimpleAllocator.sol"; import { Compact } from "src/types/EIP712Types.sol"; import { ResetPeriod } from "src/lib/IdLib.sol"; -contract SimpleAllocator is Ownable2Step, IAllocator { - struct LockedAllocation { - uint216 amount; - uint40 expires; - } +contract SimpleAllocator is ISimpleAllocator { - // The slot holding the current active claim, transiently. bytes32(uint256(keccak256("ActiveClaim")) - 1) - uint256 private constant _ACTIVE_CLAIM_SLOT = 0x52878b5aadd152a1719f94d6380573e67df5b5f15153bef7af957f0c05d2a1bf; - // The slot holding the current active claim sponsor, transiently. bytes32(uint256(keccak256("ActiveClaimSponsor")) - 1) - uint256 private constant _ACTIVE_CLAIM_SPONSOR_SLOT = 0x5c0cba9a91a791e685f0a43b1ceba6e6670ab2d235795af4fe5350bca1423e19; address private immutable _COMPACT_CONTRACT; address private immutable _ARBITER; uint256 private immutable _MIN_WITHDRAWAL_DELAY; uint256 private immutable _MAX_WITHDRAWAL_DELAY; - // mapping(bytes32 tokenHash => LockedAllocation allocation) private _locked; - + /// @dev mapping of tokenHash to the expiration of the lock mapping(bytes32 tokenHash => uint256 expiration) private _claim; + /// @dev mapping of tokenHash to the amount of the lock mapping(bytes32 tokenHash => uint256 amount) private _amount; + /// @dev mapping of tokenHash to the nonce of the lock mapping(bytes32 tokenHash => uint256 nonce) private _nonce; + /// @dev mapping of the lock digest to the tokenHash of the lock mapping(bytes32 digest => bytes32 tokenHash) private _sponsor; - error ClaimActive(address sponsor); - error InvalidCaller(address caller, address expected); - error InvalidArbiter(address arbiter); - error NonceAlreadyConsumed(uint256 nonce); - error InsufficientBalance(address sponsor, uint256 id); - error InvalidExpiration(uint256 expires); - error ForceWithdrawalAvailable(uint256 expires, uint256 forcedWithdrawalExpiration); - error InvalidLock(bytes32 digest, uint256 expiration); - - event Locked(address sponsor, uint256 id, uint256 amount, uint256 expires); - - constructor(address compactContract_, address arbiter_, uint256 minWithdrawalDelay_, uint256 maxWithdrawalDelay_, address owner_) Ownable(owner_) { + constructor(address compactContract_, address arbiter_, uint256 minWithdrawalDelay_, uint256 maxWithdrawalDelay_) { _COMPACT_CONTRACT = compactContract_; _ARBITER = arbiter_; _MIN_WITHDRAWAL_DELAY = minWithdrawalDelay_; _MAX_WITHDRAWAL_DELAY = maxWithdrawalDelay_; } - /// @dev locks all tokens of a sponsor for an id + /// @inheritdoc ISimpleAllocator function lock(Compact calldata compact_) external { // Check msg.sender is sponsor if (msg.sender != compact_.sponsor) { @@ -125,6 +103,7 @@ contract SimpleAllocator is Ownable2Step, IAllocator { emit Locked(compact_.sponsor, compact_.id, compact_.amount, compact_.expires); } + /// @inheritdoc IAllocator function attest(address operator_, address from_, address, uint256 id_, uint256 amount_) external view returns (bytes4) { if (msg.sender != _COMPACT_CONTRACT) { revert InvalidCaller(msg.sender, _COMPACT_CONTRACT); @@ -143,8 +122,10 @@ contract SimpleAllocator is Ownable2Step, IAllocator { return 0x1a808f91; } + /// @inheritdoc IERC1271 /// @dev we trust the compact contract to check the nonce is not already consumed function isValidSignature(bytes32 hash, bytes calldata) external view returns (bytes4 magicValue) { + // The hash is the digest of the compact bytes32 tokenHash = _sponsor[hash]; if (tokenHash == bytes32(0) || _claim[tokenHash] <= block.timestamp) { revert InvalidLock(hash, _claim[tokenHash]); @@ -153,7 +134,8 @@ contract SimpleAllocator is Ownable2Step, IAllocator { return IERC1271.isValidSignature.selector; } - function checkTokensLocked(uint256 id_, address sponsor_) internal view returns (uint256 amount_, uint256 expires_) { + /// @inheritdoc ISimpleAllocator + function checkTokensLocked(uint256 id_, address sponsor_) external view returns (uint256 amount_, uint256 expires_) { bytes32 tokenHash = _getTokenHash(id_, sponsor_); uint256 expires = _claim[tokenHash]; if (expires <= block.timestamp) { @@ -163,6 +145,7 @@ contract SimpleAllocator is Ownable2Step, IAllocator { return (_amount[tokenHash], expires); } + /// @inheritdoc ISimpleAllocator function checkCompactLocked(Compact calldata compact_) external view returns (bool locked_, uint256 expires_) { // TODO: Check the force unlock time in the compact contract and adapt expires_ if needed if (compact_.arbiter != _ARBITER) { diff --git a/src/interfaces/ISimpleAllocator.sol b/src/interfaces/ISimpleAllocator.sol new file mode 100644 index 0000000..1f018fc --- /dev/null +++ b/src/interfaces/ISimpleAllocator.sol @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.27; + +import { IAllocator } from "src/interfaces/IAllocator.sol"; +import { Compact } from "src/types/EIP712Types.sol"; + + +interface ISimpleAllocator is IAllocator { + + /// @notice Thrown if a claim is already active + error ClaimActive(address sponsor); + + /// @notice Thrown if the caller is invalid + error InvalidCaller(address caller, address expected); + + /// @notice Thrown if the suggested arbiter is not the arbiter of the allocator + error InvalidArbiter(address arbiter); + + /// @notice Thrown if the nonce has already been consumed on the compact contract + error NonceAlreadyConsumed(uint256 nonce); + + /// @notice Thrown if the sponsor does not have enough balance to lock the amount + error InsufficientBalance(address sponsor, uint256 id); + + /// @notice Thrown if the provided expiration is not valid + error InvalidExpiration(uint256 expires); + + /// @notice Thrown if the expiration is longer then the tokens forced withdrawal time + error ForceWithdrawalAvailable(uint256 expires, uint256 forcedWithdrawalExpiration); + + /// @notice Thrown if the provided lock is not available or expired + /// @dev The expiration will be '0' if no lock is available + error InvalidLock(bytes32 digest, uint256 expiration); + + /// @notice Emitted when a lock is successfully created + /// @param sponsor The address of the sponsor + /// @param id The id of the token + /// @param amount The amount of the token that was available for locking (the full balance of the token will get locked) + /// @param expires The expiration of the lock + event Locked(address sponsor, uint256 id, uint256 amount, uint256 expires); + + /// @notice Locks the tokens of an id for a claim + /// @dev Locks all tokens of a sponsor for an id + /// @param compact_ The compact that contains the data about the lock + function lock(Compact calldata compact_) external; + + /// @notice Checks if the tokens of a sponsor for an id are locked + /// @param id_ The id of the token + /// @param sponsor_ The address of the sponsor + /// @return amount_ The amount of the token that was available for locking (the full balance of the token will get locked) + /// @return expires_ The expiration of the lock + function checkTokensLocked(uint256 id_, address sponsor_) external view returns (uint256 amount_, uint256 expires_); + + /// @notice Checks if the a lock for the compact exists and is active + /// @dev Also checks if the provided nonce has not yet been consumed on the compact contract + /// @param compact_ The compact that contains the data about the lock + /// @return locked_ Whether the compact is locked + /// @return expires_ The expiration of the lock + function checkCompactLocked(Compact calldata compact_) external view returns (bool locked_, uint256 expires_); +}