Skip to content

Commit

Permalink
Merge pull request #35 from Uniswap/server-based-allocator
Browse files Browse the repository at this point in the history
Server based allocator
  • Loading branch information
vimageDE authored Dec 16, 2024
2 parents 42bd879 + bcb19c9 commit 74e33e8
Show file tree
Hide file tree
Showing 4 changed files with 172 additions and 169 deletions.
131 changes: 66 additions & 65 deletions src/examples/allocator/ServerAllocator.sol
Original file line number Diff line number Diff line change
Expand Up @@ -15,23 +15,24 @@ import { IERC1271 } from "lib/openzeppelin-contracts/contracts/interfaces/IERC12
contract ServerAllocator is Ownable2Step, EIP712, IServerAllocator {
using ECDSA for bytes32;

// keccak256("Attest(address,address,address,uint256,uint256)")
// bytes4(keccak256("attest(address,address,address,uint256,uint256)")).
bytes4 private constant _ATTEST_SELECTOR = 0x1a808f91;

// keccak256("RegisterAttest(address signer,bytes32 attestHash,uint256 expiration,uint256 nonce)")
bytes32 private constant _ATTEST_TYPE_HASH = 0xaf2dfd3fe08723f490d203be627da2725f4ad38681e455221da2fc1a633bbb18;
// keccak256("RegisterAttestation(address signer,bytes32 attestationHash,uint256 expiration,uint256 nonce)")
bytes32 private constant _ATTESTATION_TYPE_HASH = 0x6017ed71e505719876ff40d1e87ed2a0a078883c87bd2902ea9988c117f7ca7f;

// keccak256("NonceConsumption(address signer,uint256[] nonces,bytes32[] attests)")
bytes32 private constant _NONCE_CONSUMPTION_TYPE_HASH = 0xb06793f900067653959d9bc53299ebf6b5aa5cf5f6c1a463305891a3db695f3c;
// keccak256("NonceConsumption(address signer,uint256[] nonces,bytes32[] attestations)")
bytes32 private constant _NONCE_CONSUMPTION_TYPE_HASH = 0x626e2c6c331510cafaa5cc323e6ac1e87f32c48cba2a61d81c86b50534f7cc91;

address private immutable _COMPACT_CONTRACT;

mapping(address => uint256) private _signers;
/// @dev mapping of a signer to their index (incremented to skip 0) in _activeSigners
mapping(address signer => uint256 index) private _signers;
address[] private _activeSigners;

mapping(bytes32 => uint256) private _attestExpirations;
mapping(bytes32 => uint256) private _attestCounts;
mapping(bytes32 => bool) private _attestSignatures;
mapping(bytes32 => uint256) private _attestationExpirations;
mapping(bytes32 => uint256) private _attestationCounts;
mapping(bytes32 => bool) private _attestationSignatures;

modifier isSigner(address signer_) {
if (!_containsSigner(signer_)) {
Expand Down Expand Up @@ -73,24 +74,24 @@ contract ServerAllocator is Ownable2Step, EIP712, IServerAllocator {
}

/// @inheritdoc IServerAllocator
function registerAttest(bytes32 attest_, uint256 expiration_) external isSigner(msg.sender) {
_registerAttest(attest_, expiration_);
function registerAttestation(bytes32 attestation_, uint256 expiration_) external isSigner(msg.sender) {
_registerAttestation(attestation_, expiration_);
}

/// @inheritdoc IServerAllocator
function registerAttestViaSignature(RegisterAttest calldata attest_, bytes calldata signature_) external {
bytes32 _attestWithNonce = keccak256(abi.encode(attest_.attestHash, attest_.expiration, attest_.nonce));
if (_attestSignatures[_attestWithNonce]) {
revert AlreadyUsedSig(attest_.attestHash, attest_.nonce);
function registerAttestationViaSignature(RegisterAttestation calldata attestation_, bytes calldata signature_) external {
bytes32 _attestationWithNonce = keccak256(abi.encode(attestation_.attestationHash, attestation_.expiration, attestation_.nonce));
if (_attestationSignatures[_attestationWithNonce]) {
revert AlreadyUsedSig(attestation_.attestationHash, attestation_.nonce);
}
address signer = _validateSignedAttest(attest_.signer, attest_.attestHash, attest_.expiration, attest_.nonce, signature_);
if (signer != attest_.signer || !_containsSigner(signer)) {
address signer = _validateSignedAttestation(attestation_.signer, attestation_.attestationHash, attestation_.expiration, attestation_.nonce, signature_);
if (signer != attestation_.signer || !_containsSigner(signer)) {
revert InvalidSignature(signature_, signer);
}

// Invalidate signature
_attestSignatures[_attestWithNonce] = true;
_registerAttest(attest_.attestHash, attest_.expiration);
_attestationSignatures[_attestationWithNonce] = true;
_registerAttestation(attestation_.attestationHash, attestation_.expiration);
}

/// @inheritdoc IAllocator
Expand All @@ -104,54 +105,54 @@ contract ServerAllocator is Ownable2Step, EIP712, IServerAllocator {
if (msg.sender != _COMPACT_CONTRACT) {
revert InvalidCaller(msg.sender, _COMPACT_CONTRACT);
}
bytes32 registeredAttest = keccak256(abi.encode(from_, id_, amount_));
uint256 count = _attestCounts[registeredAttest];
bytes32 registeredAttestation = keccak256(abi.encode(from_, id_, amount_));
uint256 count = _attestationCounts[registeredAttestation];

if (count == 0) {
revert UnregisteredAttest(registeredAttest);
revert UnregisteredAttestation(registeredAttestation);
}
for (uint256 i = count; i > 0; --i) {
bytes32 countedAttest = keccak256(abi.encode(registeredAttest, i));
if (_attestExpirations[countedAttest] >= block.timestamp) {
// Found a valid registered attest
bytes32 countedAttestation = keccak256(abi.encode(registeredAttestation, i));
if (_attestationExpirations[countedAttestation] >= block.timestamp) {
// Found a valid registered attestation
if (i == count) {
// Last attest, delete
delete _attestExpirations[countedAttest];
// Last attestation, delete
delete _attestationExpirations[countedAttestation];
} else {
// Shift attest and delete from the end
bytes32 lastAttest = keccak256(abi.encode(registeredAttest, count));
_attestExpirations[countedAttest] = _attestExpirations[lastAttest];
delete _attestExpirations[lastAttest];
// Shift attestation and delete from the end
bytes32 lastAttestation = keccak256(abi.encode(registeredAttestation, count));
_attestationExpirations[countedAttestation] = _attestationExpirations[lastAttestation];
delete _attestationExpirations[lastAttestation];
}
_attestCounts[registeredAttest] = --count;
_attestationCounts[registeredAttestation] = --count;

emit Attested(from_, id_, amount_);
emit AttestationConsumed(from_, id_, amount_);
return _ATTEST_SELECTOR;
}
}

revert ExpiredAttests(registeredAttest);
revert ExpiredAttestations(registeredAttestation);
}

/// @inheritdoc IServerAllocator
function consume(uint256[] calldata nonces_, bytes32[] calldata attests_) external isSigner(msg.sender) {
if (attests_.length != nonces_.length) {
function consume(uint256[] calldata nonces_, bytes32[] calldata attestations_) external isSigner(msg.sender) {
if (attestations_.length != nonces_.length) {
revert InvalidInput();
}
_consumeNonces(nonces_, attests_);
_consumeNonces(nonces_, attestations_);
}

/// @inheritdoc IServerAllocator
function consumeViaSignature(NonceConsumption calldata data_, bytes calldata signature_) external {
if (data_.attests.length != data_.nonces.length) {
if (data_.attestations.length != data_.nonces.length) {
revert InvalidInput();
}
address signer = _validateNonceConsumption(data_, signature_);
if (signer != data_.signer || !_containsSigner(signer)) {
// first check is optional, can be deleted for gas efficiency
revert InvalidSignature(signature_, signer);
}
_consumeNonces(data_.nonces, data_.attests);
_consumeNonces(data_.nonces, data_.attestations);
}

/// @inheritdoc IERC1271
Expand All @@ -174,59 +175,59 @@ contract ServerAllocator is Ownable2Step, EIP712, IServerAllocator {
}

/// @inheritdoc IServerAllocator
function checkAttestExpirations(bytes32 attest_) external view returns (uint256[] memory) {
return _checkAttestExpirations(attest_);
function checkAttestationExpirations(bytes32 attestation_) external view returns (uint256[] memory) {
return _checkAttestationExpirations(attestation_);
}

/// @inheritdoc IServerAllocator
function checkAttestExpirations(address sponsor_, uint256 id_, uint256 amount_) external view returns (uint256[] memory) {
return _checkAttestExpirations(keccak256(abi.encode(sponsor_, id_, amount_)));
function checkAttestationExpirations(address sponsor_, uint256 id_, uint256 amount_) external view returns (uint256[] memory) {
return _checkAttestationExpirations(keccak256(abi.encode(sponsor_, id_, amount_)));
}

/// @inheritdoc IServerAllocator
function getCompactContract() external view returns (address) {
return _COMPACT_CONTRACT;
}

function _registerAttest(bytes32 attest_, uint256 expiration_) internal {
function _registerAttestation(bytes32 attestation_, uint256 expiration_) internal {
if (expiration_ < block.timestamp) {
revert Expired(expiration_, block.timestamp);
}
uint256 count = ++_attestCounts[attest_];
bytes32 countedAttest = keccak256(abi.encode(attest_, count));
uint256 count = ++_attestationCounts[attestation_];
bytes32 countedAttestation = keccak256(abi.encode(attestation_, count));

_attestExpirations[countedAttest] = expiration_;
_attestationExpirations[countedAttestation] = expiration_;

emit AttestRegistered(attest_, expiration_);
emit AttestationRegistered(attestation_, expiration_);
}

/// Todo: This will lead to always the last registered hash being consumed.
function _consumeNonces(uint256[] calldata nonces_, bytes32[] calldata attests_) internal {
function _consumeNonces(uint256[] calldata nonces_, bytes32[] calldata attestations_) internal {
ITheCompact(_COMPACT_CONTRACT).consume(nonces_);
uint256 nonceLength = attests_.length;
uint256 nonceLength = attestations_.length;
for (uint256 i = 0; i < nonceLength; ++i) {
bytes32 hashToConsume = attests_[i];
bytes32 hashToConsume = attestations_[i];
if (hashToConsume != bytes32(0)) {
uint256 count = _attestCounts[attests_[i]];
uint256 count = _attestationCounts[attestations_[i]];
if (count != 0) {
// Consume the latest registered attest
delete _attestExpirations[
keccak256(abi.encode(attests_[i], count))
// Consume the latest registered attestation
delete _attestationExpirations[
keccak256(abi.encode(attestations_[i], count))
];
_attestCounts[attests_[i]] = --count;
_attestationCounts[attestations_[i]] = --count;
}
}
}
emit NoncesConsumed(nonces_);
}

function _validateSignedAttest(address signer_, bytes32 hash_, uint256 expiration_, uint256 nonce, bytes calldata signature_) internal view returns (address) {
bytes32 message = _hashAttest(signer_, hash_, expiration_, nonce);
function _validateSignedAttestation(address signer_, bytes32 hash_, uint256 expiration_, uint256 nonce, bytes calldata signature_) internal view returns (address) {
bytes32 message = _hashAttestation(signer_, hash_, expiration_, nonce);
return message.recover(signature_);
}

function _hashAttest(address signer_, bytes32 hash_, uint256 expiration_, uint256 nonce_) internal view returns (bytes32) {
return _hashTypedDataV4(keccak256(abi.encode(_ATTEST_TYPE_HASH, signer_, hash_, expiration_, nonce_)));
function _hashAttestation(address signer_, bytes32 hash_, uint256 expiration_, uint256 nonce_) internal view returns (bytes32) {
return _hashTypedDataV4(keccak256(abi.encode(_ATTESTATION_TYPE_HASH, signer_, hash_, expiration_, nonce_)));
}

function _validateSignedHash(bytes32 digest_, bytes calldata signature_) internal pure returns (address) {
Expand All @@ -239,21 +240,21 @@ contract ServerAllocator is Ownable2Step, EIP712, IServerAllocator {
}

function _hashNonceConsumption(NonceConsumption calldata data_) internal view returns (bytes32) {
return _hashTypedDataV4(keccak256(abi.encode(_NONCE_CONSUMPTION_TYPE_HASH, data_.signer, data_.nonces, data_.attests)));
return _hashTypedDataV4(keccak256(abi.encode(_NONCE_CONSUMPTION_TYPE_HASH, data_.signer, data_.nonces, data_.attestations)));
}

function _containsSigner(address signer_) internal view returns (bool) {
return _signers[signer_] != 0;
}

function _checkAttestExpirations(bytes32 attest_) internal view returns (uint256[] memory) {
uint256 count = _attestCounts[attest_];
function _checkAttestationExpirations(bytes32 attestation_) internal view returns (uint256[] memory) {
uint256 count = _attestationCounts[attestation_];
if (count == 0) {
revert UnregisteredAttest(attest_);
revert UnregisteredAttestation(attestation_);
}
uint256[] memory expirations = new uint256[](count);
for (uint256 i = count; i > 0; --i) {
expirations[i - 1] = _attestExpirations[keccak256(abi.encode(attest_, i))];
expirations[i - 1] = _attestationExpirations[keccak256(abi.encode(attestation_, i))];
}
return expirations;
}
Expand Down
Loading

0 comments on commit 74e33e8

Please sign in to comment.