Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Server based allocator #35

Merged
merged 4 commits into from
Dec 16, 2024
Merged
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
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
Loading