Skip to content

Commit

Permalink
Fixes and tests
Browse files Browse the repository at this point in the history
  • Loading branch information
vimageDE committed Dec 18, 2024
1 parent 74e33e8 commit 79acc44
Show file tree
Hide file tree
Showing 5 changed files with 785 additions and 41 deletions.
63 changes: 31 additions & 32 deletions src/examples/allocator/SimpleAllocator.sol
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@ import { ResetPeriod } from "src/lib/IdLib.sol";

contract SimpleAllocator is ISimpleAllocator {

address private immutable _COMPACT_CONTRACT;
address private immutable _ARBITER;
uint256 private immutable _MIN_WITHDRAWAL_DELAY;
uint256 private immutable _MAX_WITHDRAWAL_DELAY;
address public immutable COMPACT_CONTRACT;
address public immutable ARBITER;
uint256 public immutable MIN_WITHDRAWAL_DELAY;
uint256 public immutable MAX_WITHDRAWAL_DELAY;

/// @dev mapping of tokenHash to the expiration of the lock
mapping(bytes32 tokenHash => uint256 expiration) private _claim;
Expand All @@ -27,10 +27,12 @@ contract SimpleAllocator is ISimpleAllocator {
mapping(bytes32 digest => bytes32 tokenHash) private _sponsor;

constructor(address compactContract_, address arbiter_, uint256 minWithdrawalDelay_, uint256 maxWithdrawalDelay_) {
_COMPACT_CONTRACT = compactContract_;
_ARBITER = arbiter_;
_MIN_WITHDRAWAL_DELAY = minWithdrawalDelay_;
_MAX_WITHDRAWAL_DELAY = maxWithdrawalDelay_;
COMPACT_CONTRACT = compactContract_;
ARBITER = arbiter_;
MIN_WITHDRAWAL_DELAY = minWithdrawalDelay_;
MAX_WITHDRAWAL_DELAY = maxWithdrawalDelay_;

ITheCompact(COMPACT_CONTRACT).__registerAllocator(address(this), "");
}

/// @inheritdoc ISimpleAllocator
Expand All @@ -40,47 +42,43 @@ contract SimpleAllocator is ISimpleAllocator {
revert InvalidCaller(msg.sender, compact_.sponsor);
}
bytes32 tokenHash = _getTokenHash(compact_.id, msg.sender);
// Check if the claim is already active
if (_claim[tokenHash] > block.timestamp && !ITheCompact(_COMPACT_CONTRACT).hasConsumedAllocatorNonce(_nonce[tokenHash], address(this))) {
revert ClaimActive(compact_.sponsor);
}
// Check no lock is active for this sponsor
if (_claim[tokenHash] > block.timestamp) {
// Check no lock is already active for this sponsor
if (_claim[tokenHash] > block.timestamp && !ITheCompact(COMPACT_CONTRACT).hasConsumedAllocatorNonce(_nonce[tokenHash], address(this))) {
revert ClaimActive(compact_.sponsor);
}
// Check arbiter is valid
if (compact_.arbiter != _ARBITER) {
if (compact_.arbiter != ARBITER) {
revert InvalidArbiter(compact_.arbiter);
}
// Check expiration is not too soon or too late
if (compact_.expires < block.timestamp + _MIN_WITHDRAWAL_DELAY || compact_.expires > block.timestamp + _MAX_WITHDRAWAL_DELAY) {
if (compact_.expires < block.timestamp + MIN_WITHDRAWAL_DELAY || compact_.expires > block.timestamp + MAX_WITHDRAWAL_DELAY) {
revert InvalidExpiration(compact_.expires);
}
// Check expiration is not longer then the tokens forced withdrawal time
(,, ResetPeriod resetPeriod, ) = ITheCompact(_COMPACT_CONTRACT).getLockDetails(compact_.id);
(,, ResetPeriod resetPeriod, ) = ITheCompact(COMPACT_CONTRACT).getLockDetails(compact_.id);
if(compact_.expires > block.timestamp + _resetPeriodToSeconds(resetPeriod) ){
revert ForceWithdrawalAvailable(compact_.expires, block.timestamp + _resetPeriodToSeconds(resetPeriod));
}
// Check expiration is not past an active force withdrawal
(, uint256 forcedWithdrawalExpiration) = ITheCompact(_COMPACT_CONTRACT).getForcedWithdrawalStatus(compact_.sponsor, compact_.id);
(, uint256 forcedWithdrawalExpiration) = ITheCompact(COMPACT_CONTRACT).getForcedWithdrawalStatus(compact_.sponsor, compact_.id);
if(forcedWithdrawalExpiration != 0 && forcedWithdrawalExpiration < compact_.expires) {
revert ForceWithdrawalAvailable(compact_.expires, forcedWithdrawalExpiration);
}
// Check nonce is not yet consumed
if (ITheCompact(_COMPACT_CONTRACT).hasConsumedAllocatorNonce(compact_.nonce, address(this))) {
if (ITheCompact(COMPACT_CONTRACT).hasConsumedAllocatorNonce(compact_.nonce, address(this))) {
revert NonceAlreadyConsumed(compact_.nonce);
}

uint256 balance = ERC6909(_COMPACT_CONTRACT).balanceOf(msg.sender, compact_.id);
uint256 balance = ERC6909(COMPACT_CONTRACT).balanceOf(msg.sender, compact_.id);
// Check balance is enough
if (balance < compact_.amount) {
revert InsufficientBalance(msg.sender, compact_.id);
revert InsufficientBalance(msg.sender, compact_.id, balance, compact_.amount);
}

bytes32 digest = keccak256(
abi.encodePacked(
bytes2(0x1901),
ITheCompact(_COMPACT_CONTRACT).DOMAIN_SEPARATOR(),
ITheCompact(COMPACT_CONTRACT).DOMAIN_SEPARATOR(),
keccak256(
abi.encode(
keccak256("Compact(address arbiter,address sponsor,uint256 nonce,uint256 expires,uint256 id,uint256 amount)"),
Expand All @@ -97,32 +95,32 @@ contract SimpleAllocator is ISimpleAllocator {

_claim[tokenHash] = compact_.expires;
_amount[tokenHash] = compact_.amount;
_nonce[tokenHash] = compact_.nonce;
_sponsor[digest] = tokenHash;
_nonce[digest] = compact_.nonce;

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);
if (msg.sender != COMPACT_CONTRACT) {
revert InvalidCaller(msg.sender, COMPACT_CONTRACT);
}
// For a transfer, the sponsor is the arbiter
if (operator_ != from_) {
revert InvalidCaller(operator_, from_);
}
uint256 balance = ERC6909(_COMPACT_CONTRACT).balanceOf(from_, id_);
uint256 balance = ERC6909(COMPACT_CONTRACT).balanceOf(from_, id_);
// Check unlocked balance
bytes32 tokenHash = _getTokenHash(id_, from_);

uint256 fullAmount = amount_;
if(_claim[tokenHash] > block.timestamp) {
// Lock is still active, add the locked amount if the nonce has not yet been consumed
fullAmount += ITheCompact(_COMPACT_CONTRACT).hasConsumedAllocatorNonce(_nonce[tokenHash], address(this)) ? 0 : _amount[tokenHash];
fullAmount += ITheCompact(COMPACT_CONTRACT).hasConsumedAllocatorNonce(_nonce[tokenHash], address(this)) ? 0 : _amount[tokenHash];
}
if( balance < fullAmount) {
revert InsufficientBalance(from_, id_);
revert InsufficientBalance(from_, id_, balance, fullAmount);
}

return 0x1a808f91;
Expand All @@ -144,7 +142,7 @@ contract SimpleAllocator is 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) {
if (expires <= block.timestamp || ITheCompact(COMPACT_CONTRACT).hasConsumedAllocatorNonce(_nonce[tokenHash], address(this))) {
return (0, 0);
}

Expand All @@ -154,14 +152,14 @@ contract SimpleAllocator is ISimpleAllocator {
/// @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) {
if (compact_.arbiter != ARBITER) {
revert InvalidArbiter(compact_.arbiter);
}
bytes32 tokenHash = _getTokenHash(compact_.id, compact_.sponsor);
bytes32 digest = keccak256(
abi.encodePacked(
bytes2(0x1901),
ITheCompact(_COMPACT_CONTRACT).DOMAIN_SEPARATOR(),
ITheCompact(COMPACT_CONTRACT).DOMAIN_SEPARATOR(),
keccak256(
abi.encode(
keccak256("Compact(address arbiter,address sponsor,uint256 nonce,uint256 expires,uint256 id,uint256 amount)"),
Expand All @@ -176,7 +174,8 @@ contract SimpleAllocator is ISimpleAllocator {
)
);
uint256 expires = _claim[tokenHash];
return (_sponsor[digest] == tokenHash && expires > block.timestamp && !ITheCompact(_COMPACT_CONTRACT).hasConsumedAllocatorNonce(_nonce[tokenHash], address(this)), expires);
bool active = _sponsor[digest] == tokenHash && expires > block.timestamp && !ITheCompact(COMPACT_CONTRACT).hasConsumedAllocatorNonce(_nonce[tokenHash], address(this));
return (active, active ? expires : 0);
}

function _getTokenHash(uint256 id_, address sponsor_) internal pure returns (bytes32) {
Expand Down
4 changes: 2 additions & 2 deletions src/interfaces/ISimpleAllocator.sol
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ interface ISimpleAllocator is IAllocator {
error NonceAlreadyConsumed(uint256 nonce);

/// @notice Thrown if the sponsor does not have enough balance to lock the amount
error InsufficientBalance(address sponsor, uint256 id);
error InsufficientBalance(address sponsor, uint256 id, uint256 balance, uint256 expectedBalance);

/// @notice Thrown if the provided expiration is not valid
error InvalidExpiration(uint256 expires);
Expand All @@ -38,7 +38,7 @@ interface ISimpleAllocator is IAllocator {
/// @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);
event Locked(address indexed sponsor, uint256 indexed 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
Expand Down
57 changes: 55 additions & 2 deletions src/test/TheCompactMock.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,36 +6,52 @@ import { IAllocator } from "src/interfaces/IAllocator.sol";
import { ERC6909 } from "lib/solady/src/tokens/ERC6909.sol";
import { ERC20 } from "lib/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol";
import { IdLib } from "src/lib/IdLib.sol";
import { ForcedWithdrawalStatus } from "src/types/ForcedWithdrawalStatus.sol";
import { ResetPeriod } from "src/types/ResetPeriod.sol";
import { Scope } from "src/types/Scope.sol";
import { console2 } from "forge-std/console2.sol";


contract TheCompactMock is ERC6909 {
using IdLib for uint96;
using IdLib for uint256;
using IdLib for address;

// Mock Variables
uint32 private constant DEFAULT_RESET_PERIOD = 60;
ResetPeriod private constant DEFAULT_RESET_PERIOD_TYPE = ResetPeriod.OneMinute;
Scope private constant DEFAULT_SCOPE = Scope.Multichain;
address private DEFAULT_ALLOCATOR;

// Mock State
mapping(uint256 id => address token) public tokens;
mapping(uint256 nonce => bool consumed) public consumedNonces;
mapping(address allocator => bool registered) public registeredAllocators;
mapping(address user => uint256 availableAt) public forcedWithdrawalStatus;

function __registerAllocator(address allocator, bytes calldata) external returns (uint96) {
registeredAllocators[allocator] = true;
DEFAULT_ALLOCATOR = allocator;
return 0;
}

function deposit(address token, uint256 amount, address allocator) external {
ERC20(token).transferFrom(msg.sender, address(this), amount);
uint256 id = _getTokenId(token, allocator);
tokens[id] = token;
_mint(msg.sender, id, amount);
}

function transfer(address from, address to, uint256 amount, address token, address allocator) external {
uint256 id = _getTokenId(token, allocator);
IAllocator(allocator).attest(msg.sender, from, to, id, amount);
_transfer(msg.sender, from, to, id, amount);
_transfer(address(0), from, to, id, amount);
}

function claim(address from, address to, address token, uint256 amount, address allocator, bytes calldata signature) external {
uint256 id = _getTokenId(token, allocator);
IAllocator(allocator).isValidSignature(keccak256(abi.encode(from, id, amount)), signature);
_transfer(msg.sender, from, to, id, amount);
_transfer(address(0), from, to, id, amount);
}

function withdraw(address token, uint256 amount, address allocator) external {
Expand All @@ -52,10 +68,47 @@ contract TheCompactMock is ERC6909 {
return true;
}

function hasConsumedAllocatorNonce(uint256 nonce, address) external view returns (bool) {
return consumedNonces[nonce];
}

function getLockDetails(uint256 id) external view returns (address, address, ResetPeriod, Scope) {
return (tokens[id], DEFAULT_ALLOCATOR, DEFAULT_RESET_PERIOD_TYPE, DEFAULT_SCOPE);
}

function enableForceWithdrawal(uint256) external returns (uint256) {
forcedWithdrawalStatus[msg.sender] = block.timestamp + DEFAULT_RESET_PERIOD;
return block.timestamp + DEFAULT_RESET_PERIOD;
}

function disableForceWithdrawal(uint256) external returns (bool) {
forcedWithdrawalStatus[msg.sender] = 0;
return true;
}

function getForcedWithdrawalStatus(address sponsor, uint256) external view returns (ForcedWithdrawalStatus, uint256) {
uint256 expires = forcedWithdrawalStatus[sponsor];
return (expires == 0 ? ForcedWithdrawalStatus.Disabled : ForcedWithdrawalStatus.Enabled, expires);
}

function getTokenId(address token, address allocator) external pure returns (uint256) {
return _getTokenId(token, allocator);
}

function DOMAIN_SEPARATOR() public view returns (bytes32) {
return
keccak256(
abi.encode(
// keccak256('EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)')
0x8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f,
keccak256("The Compact"),
keccak256("0"),
block.chainid,
address(this)
)
);
}

function name(
uint256 // id
) public view virtual override returns (string memory) {
Expand Down
10 changes: 5 additions & 5 deletions test/ServerAllocator.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ abstract contract CreateHash is Test {
// return keccak256(
// abi.encodePacked(
// "\x19\x01", // backslash is needed to escape the character
// _domainSeperator(verifyingContract),
// _domainSeparator(verifyingContract),
// keccak256(abi.encode(keccak256(bytes(ALLOCATOR_TYPE)), data.hash))
// )
// );
Expand All @@ -71,7 +71,7 @@ abstract contract CreateHash is Test {
return keccak256(
abi.encodePacked(
"\x19\x01", // backslash is needed to escape the character
_domainSeperator(verifyingContract),
_domainSeparator(verifyingContract),
keccak256(abi.encode(COMPACT_TYPEHASH, data.arbiter, data.sponsor, data.nonce, data.expires, data.id, data.amount))
)
);
Expand All @@ -81,7 +81,7 @@ abstract contract CreateHash is Test {
return keccak256(
abi.encodePacked(
"\x19\x01", // backslash is needed to escape the character
_domainSeperator(verifyingContract),
_domainSeparator(verifyingContract),
keccak256(abi.encode(keccak256(bytes(REGISTER_ATTESTATION_TYPE)), data.signer, data.attestationHash, data.expiration, data.nonce))
)
);
Expand All @@ -92,13 +92,13 @@ abstract contract CreateHash is Test {
return keccak256(
abi.encodePacked(
"\x19\x01", // backslash is needed to escape the character
_domainSeperator(verifyingContract),
_domainSeparator(verifyingContract),
keccak256(abi.encode(keccak256(bytes(NONCE_CONSUMPTION_TYPE)), data.signer, data.nonces, data.attestations))
)
);
}

function _domainSeperator(address verifyingContract) internal view returns (bytes32) {
function _domainSeparator(address verifyingContract) internal view returns (bytes32) {
return keccak256(abi.encode(keccak256(bytes(EIP712_DOMAIN_TYPE)), keccak256(bytes(name)), keccak256(bytes(version)), block.chainid, verifyingContract));
}

Expand Down
Loading

0 comments on commit 79acc44

Please sign in to comment.