Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
[submodule "lib/the-compact"]
path = lib/the-compact
url = https://github.com/Uniswap/the-compact
branch = v1
branch = utility-lib
[submodule "lib/tribunal"]
path = lib/tribunal
url = https://github.com/Uniswap/tribunal
3 changes: 2 additions & 1 deletion foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,11 @@ libs = ["lib"]
optimizer = true
optimizer_runs = 999999
via_ir = true
solc = "0.8.28"
solc = "0.8.30"
verbosity = 2
ffi = true
evm_version = "cancun"
bytecode_hash = "none"
fs_permissions = [
{ access = "read-write", path = ".forge-snapshots"},
{ access = "read", path = "script/" }
Expand Down
2 changes: 1 addition & 1 deletion lib/the-compact
Submodule the-compact updated 138 files
2 changes: 1 addition & 1 deletion snapshots/ERC7683Allocator_open.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
"open_simpleOrder": "166989"
"open_simpleOrder": "168841"
}
2 changes: 1 addition & 1 deletion snapshots/ERC7683Allocator_openFor.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
"openFor_simpleOrder_userHimself": "170429"
"openFor_simpleOrder_userHimself": "172266"
}
16 changes: 8 additions & 8 deletions snapshots/HybridAllocatorTest.json
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
{
"allocateAndRegister_erc20Token": "187680",
"allocateAndRegister_erc20Token_emptyAmountInput": "188590",
"allocateAndRegister_multipleTokens": "223610",
"allocateAndRegister_nativeToken": "139231",
"allocateAndRegister_nativeToken_emptyAmountInput": "139067",
"allocateAndRegister_second_erc20Token": "114886",
"allocateAndRegister_second_nativeToken": "104867",
"hybrid_execute_single": "171524"
"allocateAndRegister_erc20Token": "187659",
"allocateAndRegister_erc20Token_emptyAmountInput": "188569",
"allocateAndRegister_multipleTokens": "223595",
"allocateAndRegister_nativeToken": "139222",
"allocateAndRegister_nativeToken_emptyAmountInput": "139058",
"allocateAndRegister_second_erc20Token": "114865",
"allocateAndRegister_second_nativeToken": "104858",
"hybrid_execute_single": "174737"
}
14 changes: 7 additions & 7 deletions snapshots/OnChainAllocatorTest.json
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
{
"allocateFor_success_withRegistration": "132405",
"allocate_and_delete_expired_allocation": "64570",
"allocate_erc20": "127841",
"allocate_native": "127601",
"allocate_second_erc20": "95853",
"onchain_execute_double": "343161",
"onchain_execute_single": "216772"
"allocateFor_success_withRegistration": "134197",
"allocate_and_delete_expired_allocation": "66376",
"allocate_erc20": "129647",
"allocate_native": "129407",
"allocate_second_erc20": "97659",
"onchain_execute_double": "346191",
"onchain_execute_single": "219927"
}
5 changes: 2 additions & 3 deletions src/allocators/ERC7683Allocator.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ pragma solidity ^0.8.27;
import {IOriginSettler} from '../interfaces/ERC7683/IOriginSettler.sol';
import {IERC7683Allocator} from '../interfaces/IERC7683Allocator.sol';
import {OnChainAllocator} from './OnChainAllocator.sol';
import {AllocatorLib as AL} from './lib/AllocatorLib.sol';
import {ERC7683AllocatorLib as ERC7683AL} from './lib/ERC7683AllocatorLib.sol';

import {Tribunal} from '@uniswap/tribunal/Tribunal.sol';
Expand All @@ -30,8 +31,6 @@ import {BatchCompact, Lock} from '@uniswap/the-compact/types/EIP712Types.sol';
/// @dev Users can open orders for themselves or for others by providing a signature or the tokens directly.
/// @custom:security-contact security@uniswap.org
contract ERC7683Allocator is OnChainAllocator, IERC7683Allocator {
constructor(address compact) OnChainAllocator(compact) {}

/// @inheritdoc IOriginSettler
function openFor(GaslessCrossChainOrder calldata order, bytes calldata sponsorSignature, bytes calldata) external {
(
Expand Down Expand Up @@ -92,7 +91,7 @@ contract ERC7683Allocator is OnChainAllocator, IERC7683Allocator {
allocate(orderData.commitments, orderData.arbiter, expires, COMPACT_TYPEHASH_WITH_MANDATE, mandateHash);

// Ensure a registration exists before opening the order
if (!ITheCompact(COMPACT_CONTRACT).isRegistered(msg.sender, claimHash, COMPACT_TYPEHASH_WITH_MANDATE)) {
if (!ITheCompact(AL.THE_COMPACT).isRegistered(msg.sender, claimHash, COMPACT_TYPEHASH_WITH_MANDATE)) {
revert InvalidRegistration(msg.sender, claimHash);
}

Expand Down
41 changes: 18 additions & 23 deletions src/allocators/HybridAllocator.sol
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,11 @@ contract HybridAllocator is IHybridAllocator {
event SignerRemoved(address signer);
event SignerReplacementProposed(address oldSigner, address newSigner);
event SignerReplaced(address oldSigner, address newSigner);
event AllocatorInitialized(address compact, address initialSigner, uint96 allocatorId);
event AllocatorInitialized(address initialSigner, uint96 allocatorId);

/// @notice The unique identifier for this allocator within The Compact protocol
uint96 public immutable ALLOCATOR_ID;
uint256 private immutable _INITIAL_CHAIN_ID;
ITheCompact internal immutable _COMPACT;
bytes32 internal immutable _COMPACT_DOMAIN_SEPARATOR;

mapping(bytes32 claimHash => bool allocated) internal claims;
Expand All @@ -50,14 +49,13 @@ contract HybridAllocator is IHybridAllocator {
_;
}

constructor(address compact_, address signer_) {
constructor(address signer_) {
if (signer_ == address(0)) {
revert InvalidSigner();
}
_INITIAL_CHAIN_ID = block.chainid;
_COMPACT = ITheCompact(compact_);
_COMPACT_DOMAIN_SEPARATOR = _COMPACT.DOMAIN_SEPARATOR();
try _COMPACT.__registerAllocator(address(this), '') returns (uint96 allocatorId) {
_COMPACT_DOMAIN_SEPARATOR = ITheCompact(AL.THE_COMPACT).DOMAIN_SEPARATOR();
try ITheCompact(AL.THE_COMPACT).__registerAllocator(address(this), '') returns (uint96 allocatorId) {
ALLOCATOR_ID = allocatorId;
} catch {
// The Compact does not have a getter function for retrieving the status of allocator registration,
Expand All @@ -70,7 +68,7 @@ contract HybridAllocator is IHybridAllocator {
allocatorSlot := or(0x000044036fc77deaed2300000000000000000000000, allocatorId)
}

bytes32 registeredAllocator = Extsload(compact_).extsload(allocatorSlot);
bytes32 registeredAllocator = Extsload(AL.THE_COMPACT).extsload(allocatorSlot);

assembly ("memory-safe") {
if iszero(eq(registeredAllocator, address())) {
Expand All @@ -87,7 +85,7 @@ contract HybridAllocator is IHybridAllocator {
signers[signer_] = true;
signerCount++;

emit AllocatorInitialized(compact_, signer_, ALLOCATOR_ID);
emit AllocatorInitialized(signer_, ALLOCATOR_ID);
emit SignerAdded(signer_);
}

Expand Down Expand Up @@ -162,9 +160,9 @@ contract HybridAllocator is IHybridAllocator {
recipient = AL.getRecipient(recipient);
idsAndAmounts = _actualIdsAndAmounts(idsAndAmounts);

(bytes32 claimHash, uint256[] memory registeredAmounts) = _COMPACT.batchDepositAndRegisterFor{value: msg.value}(
recipient, idsAndAmounts, arbiter, ++nonces, expires, typehash, witness
);
(bytes32 claimHash, uint256[] memory registeredAmounts) = ITheCompact(AL.THE_COMPACT).batchDepositAndRegisterFor{
value: msg.value
}(recipient, idsAndAmounts, arbiter, ++nonces, expires, typehash, witness);

Lock[] memory commitments = new Lock[](idsAndAmounts.length);
for (uint256 i = 0; i < idsAndAmounts.length; i++) {
Expand Down Expand Up @@ -194,9 +192,7 @@ contract HybridAllocator is IHybridAllocator {
bytes calldata /* orderData */
) external returns (uint256 nonce) {
nonce = nonces + 1;
AL.prepareAllocation(
address(_COMPACT), nonce, recipient, idsAndAmounts, arbiter, expires, typehash, witness, ALLOCATOR_ID
);
AL.prepareAllocation(nonce, recipient, idsAndAmounts, arbiter, expires, typehash, witness, ALLOCATOR_ID);
}

/// @inheritdoc IOnChainAllocation
Expand All @@ -211,9 +207,8 @@ contract HybridAllocator is IHybridAllocator {
) external {
uint256 nonce = ++nonces;

(bytes32 claimHash, Lock[] memory commitments) = AL.executeAllocation(
address(_COMPACT), nonce, recipient, idsAndAmounts, arbiter, expires, typehash, witness
);
(bytes32 claimHash, Lock[] memory commitments) =
AL.executeAllocation(nonce, recipient, idsAndAmounts, arbiter, expires, typehash, witness);

// Allocate the claim
claims[claimHash] = true;
Expand All @@ -231,8 +226,8 @@ contract HybridAllocator is IHybridAllocator {
uint256[2][] calldata, /*idsAndAmounts*/
bytes calldata allocatorData_
) external virtual returns (bytes4) {
if (msg.sender != address(_COMPACT)) {
revert InvalidCaller(msg.sender, address(_COMPACT));
if (msg.sender != AL.THE_COMPACT) {
revert InvalidCaller(msg.sender, AL.THE_COMPACT);
}
// The compact will check the validity of the nonce and expiration

Expand All @@ -248,7 +243,7 @@ contract HybridAllocator is IHybridAllocator {
bytes32 digest = _deriveDigest(claimHash, _COMPACT_DOMAIN_SEPARATOR);
if (block.chainid != _INITIAL_CHAIN_ID) {
// If the chain was forked, we can not use the cached domain separator
digest = _deriveDigest(claimHash, _COMPACT.DOMAIN_SEPARATOR());
digest = _deriveDigest(claimHash, ITheCompact(AL.THE_COMPACT).DOMAIN_SEPARATOR());
}
if (!_checkSignature(digest, allocatorData_)) {
revert InvalidSignature();
Expand Down Expand Up @@ -276,7 +271,7 @@ contract HybridAllocator is IHybridAllocator {
bytes32 digest = _deriveDigest(claimHash, _COMPACT_DOMAIN_SEPARATOR);
if (block.chainid != _INITIAL_CHAIN_ID) {
// If the chain was forked, we can not use the cached domain separator
digest = _deriveDigest(claimHash, _COMPACT.DOMAIN_SEPARATOR());
digest = _deriveDigest(claimHash, ITheCompact(AL.THE_COMPACT).DOMAIN_SEPARATOR());
}
return _checkSignature(digest, allocatorData);
}
Expand Down Expand Up @@ -319,8 +314,8 @@ contract HybridAllocator is IHybridAllocator {
idsAndAmounts[idIndex][1] = IERC20(token).balanceOf(address(this));
}

if (IERC20(token).allowance(address(this), address(_COMPACT)) < idsAndAmounts[idIndex][1]) {
SafeTransferLib.safeApproveWithRetry(token, address(_COMPACT), type(uint256).max);
if (IERC20(token).allowance(address(this), AL.THE_COMPACT) < idsAndAmounts[idIndex][1]) {
SafeTransferLib.safeApproveWithRetry(token, AL.THE_COMPACT, type(uint256).max);
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/allocators/HybridERC7683.sol
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import {IOriginSettler} from 'src/interfaces/ERC7683/IOriginSettler.sol';
contract HybridERC7683 is HybridAllocator, IERC7683Allocator {
error OnlyDepositsAllowed();

constructor(address compact, address signer) HybridAllocator(compact, signer) {}
constructor(address signer) HybridAllocator(signer) {}

/// @inheritdoc IOriginSettler
function openFor(GaslessCrossChainOrder calldata order, bytes calldata sponsorSignature, bytes calldata) external {
Expand Down
44 changes: 22 additions & 22 deletions src/allocators/OnChainAllocator.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,23 +8,23 @@ import {AllocatorLib as AL} from './lib/AllocatorLib.sol';
import {IERC20} from '@openzeppelin/contracts/token/ERC20/IERC20.sol';
import {ERC6909} from '@solady/tokens/ERC6909.sol';
import {SafeTransferLib} from '@solady/utils/SafeTransferLib.sol';

import {IAllocator} from '@uniswap/the-compact/interfaces/IAllocator.sol';
import {IOnChainAllocation} from '@uniswap/the-compact/interfaces/IOnChainAllocation.sol';
import {ITheCompact} from '@uniswap/the-compact/interfaces/ITheCompact.sol';
import {Extsload} from '@uniswap/the-compact/lib/Extsload.sol';
import {IdLib} from '@uniswap/the-compact/lib/IdLib.sol';
import {Lock} from '@uniswap/the-compact/types/EIP712Types.sol';
import {Utility} from '@uniswap/the-compact/utility/Utility.sol';

/// @title OnChainAllocator
/// @notice Allocates tokens deposited into the compact.
/// @dev The contract ensures tokens can not be double spent by a user in a fully decentralized manner.
/// @dev Users can open orders for themselves or for others by providing a signature or the tokens directly.
/// @custom:security-contact security@uniswap.org
contract OnChainAllocator is IOnChainAllocator {
contract OnChainAllocator is IOnChainAllocator, Utility {
/// @notice The chain id at the time of deployment
uint256 private immutable _INITIAL_CHAIN_ID;
/// @notice The address of The Compact protocol contract for token management and claim registration
address public immutable COMPACT_CONTRACT;
/// @notice The EIP-712 domain separator for The Compact protocol, used for signature verification
bytes32 public immutable COMPACT_DOMAIN_SEPARATOR;
/// @notice The unique identifier for this allocator within The Compact protocol
Expand All @@ -37,17 +37,16 @@ contract OnChainAllocator is IOnChainAllocator {
mapping(address user => uint96 nonce) public nonces;

modifier onlyCompact() {
if (msg.sender != COMPACT_CONTRACT) {
revert InvalidCaller(msg.sender, COMPACT_CONTRACT);
if (msg.sender != AL.THE_COMPACT) {
revert InvalidCaller(msg.sender, AL.THE_COMPACT);
}
_;
}

constructor(address compactContract_) {
constructor() {
_INITIAL_CHAIN_ID = block.chainid;
COMPACT_CONTRACT = compactContract_;
COMPACT_DOMAIN_SEPARATOR = ITheCompact(COMPACT_CONTRACT).DOMAIN_SEPARATOR();
try ITheCompact(COMPACT_CONTRACT).__registerAllocator(address(this), '') returns (uint96 allocatorId) {
COMPACT_DOMAIN_SEPARATOR = ITheCompact(AL.THE_COMPACT).DOMAIN_SEPARATOR();
try ITheCompact(AL.THE_COMPACT).__registerAllocator(address(this), '') returns (uint96 allocatorId) {
ALLOCATOR_ID = allocatorId;
} catch {
// The Compact does not have a getter function for retrieving the status of allocator registration,
Expand All @@ -60,7 +59,7 @@ contract OnChainAllocator is IOnChainAllocator {
allocatorSlot := or(0x000044036fc77deaed2300000000000000000000000, allocatorId)
}

bytes32 registeredAllocator = Extsload(COMPACT_CONTRACT).extsload(allocatorSlot);
bytes32 registeredAllocator = Extsload(AL.THE_COMPACT).extsload(allocatorSlot);

assembly ("memory-safe") {
if iszero(eq(registeredAllocator, address())) {
Expand Down Expand Up @@ -104,7 +103,7 @@ contract OnChainAllocator is IOnChainAllocator {
bytes32 digest = keccak256(abi.encodePacked(bytes2(0x1901), COMPACT_DOMAIN_SEPARATOR, claimHash));
if (block.chainid != _INITIAL_CHAIN_ID) {
digest = keccak256(
abi.encodePacked(bytes2(0x1901), ITheCompact(COMPACT_CONTRACT).DOMAIN_SEPARATOR(), claimHash)
abi.encodePacked(bytes2(0x1901), ITheCompact(AL.THE_COMPACT).DOMAIN_SEPARATOR(), claimHash)
);
}
address signer_ = AL.recoverSigner(digest, signature);
Expand All @@ -113,7 +112,7 @@ contract OnChainAllocator is IOnChainAllocator {
}
} else {
// confirm the claim hash is registered on the compact
if (!ITheCompact(COMPACT_CONTRACT).isRegistered(sponsor, claimHash, typehash)) {
if (!ITheCompact(AL.THE_COMPACT).isRegistered(sponsor, claimHash, typehash)) {
revert InvalidRegistration(sponsor, claimHash);
}
}
Expand Down Expand Up @@ -198,8 +197,8 @@ contract OnChainAllocator is IOnChainAllocator {
idsAndAmounts[i][1] = amount;

// Approve the compact contract to spend the tokens.
if (IERC20(token).allowance(address(this), COMPACT_CONTRACT) < amount) {
SafeTransferLib.safeApproveWithRetry(token, COMPACT_CONTRACT, type(uint256).max);
if (IERC20(token).allowance(address(this), AL.THE_COMPACT) < amount) {
SafeTransferLib.safeApproveWithRetry(token, AL.THE_COMPACT, type(uint256).max);
}
}

Expand All @@ -209,7 +208,7 @@ contract OnChainAllocator is IOnChainAllocator {
}

// Deposit the tokens and register the claim in the compact
(claimHash, registeredAmounts) = ITheCompact(COMPACT_CONTRACT).batchDepositAndRegisterFor{value: msg.value}(
(claimHash, registeredAmounts) = ITheCompact(AL.THE_COMPACT).batchDepositAndRegisterFor{value: msg.value}(
recipient, idsAndAmounts, arbiter, nonce, expires, typehash, witness
);

Expand Down Expand Up @@ -257,9 +256,8 @@ contract OnChainAllocator is IOnChainAllocator {
}
uint32 expiration = uint32(expires);
nonce = _getNonce(msg.sender, recipient);
AL.prepareAllocation(
COMPACT_CONTRACT, nonce, recipient, idsAndAmounts, arbiter, expiration, typehash, witness, ALLOCATOR_ID
);

AL.prepareAllocation(nonce, recipient, idsAndAmounts, arbiter, expiration, typehash, witness, ALLOCATOR_ID);

return nonce;
}
Expand Down Expand Up @@ -296,7 +294,7 @@ contract OnChainAllocator is IOnChainAllocator {
bytes32 witness
) private returns (bytes32, Lock[] memory) {
(bytes32 claimHash, Lock[] memory commitments) =
AL.executeAllocation(COMPACT_CONTRACT, nonce, recipient, idsAndAmounts, arbiter, expires, typehash, witness);
AL.executeAllocation(nonce, recipient, idsAndAmounts, arbiter, expires, typehash, witness);

// Allocate the claim
for (uint256 i = 0; i < commitments.length; i++) {
Expand All @@ -321,7 +319,9 @@ contract OnChainAllocator is IOnChainAllocator {
/// @inheritdoc IAllocator
function attest(address, address from_, address, uint256 id_, uint256 amount_) external returns (bytes4) {
// Can be called by anyone, as this will only clean up expired allocations.
uint256 balance = ERC6909(COMPACT_CONTRACT).balanceOf(from_, id_);

// do not use the settled balance, since this will be called within the _beforeTokenTransfer hook of the compact.
uint256 balance = ERC6909(AL.THE_COMPACT).balanceOf(from_, id_);

// Check unlocked balance
bytes32 tokenHash = _getTokenHash(id_, from_);
Expand Down Expand Up @@ -447,7 +447,7 @@ contract OnChainAllocator is IOnChainAllocator {
}

// Ensure no forcedWithdrawal is active for the token id
(, uint256 forcedWithdrawal) = ITheCompact(COMPACT_CONTRACT).getForcedWithdrawalStatus(
(, uint256 forcedWithdrawal) = ITheCompact(AL.THE_COMPACT).getForcedWithdrawalStatus(
sponsor, AL.toId(commitment.lockTag, commitment.token)
);
if (forcedWithdrawal != 0 && forcedWithdrawal <= expires) {
Expand All @@ -460,7 +460,7 @@ contract OnChainAllocator is IOnChainAllocator {
function _checkBalance(address sponsor, Lock calldata commitment) private returns (bytes32 tokenHash) {
// Check the balance of the recipient is sufficient
tokenHash = _getTokenHash(commitment.lockTag, commitment.token, sponsor);
uint256 balance = ERC6909(COMPACT_CONTRACT).balanceOf(sponsor, AL.toId(commitment.lockTag, commitment.token));
uint256 balance = settledBalanceOf(sponsor, AL.toId(commitment.lockTag, commitment.token));
uint256 allocatedBalance = _allocatedBalance(tokenHash);
uint256 requiredBalance = allocatedBalance + commitment.amount;
if (requiredBalance > balance) {
Expand Down
Loading