Skip to content

Instantly share code, notes, and snippets.

@kamescg
Created February 10, 2023 00:25
Show Gist options
  • Save kamescg/eb0aa9812db4597301e36be8a3d2799d to your computer and use it in GitHub Desktop.
Save kamescg/eb0aa9812db4597301e36be8a3d2799d to your computer and use it in GitHub Desktop.
DistrictERC20StreamPaymentsEnforcer.sol
contract DistrictERC20StreamPaymentsEnforcer is
CaveatEnforcer,
Delegatable("DistrictERC20PermitStreamingPaymentsEnforcer", "1")
{
using BytesLib for bytes;
mapping(bytes32 => bool) public isCanceled;
mapping(bytes32 => uint256) public totalWithdrawals;
function enforceCaveat(
bytes calldata terms,
Transaction calldata transaction,
bytes32 delegationHash
) public override returns (bool) {
require(!isCanceled[delegationHash], "enforcer:canceled-subscription");
require(bytes4(transaction.data[0:4]) == 0x4fd30ba3, "enforcer:invalid-method");
require(transaction.data.toAddress(16) == terms.toAddress(0), "enforcer:invalid-recipient");
// ensure allowed withdrawal limits
uint64 startStreamTimestamp = terms.toUint64(40);
uint64 endStreamTimestamp = terms.toUint64(48);
address verifier = terms.toAddress(20);
uint256 tokensPerSecond = IVerifier(verifier).getTokensPerSecond();
uint256 currentTimestamp = block.timestamp;
uint256 elapsedTime = currentTimestamp - startStreamTimestamp;
uint256 streamTotalTime = endStreamTimestamp - startStreamTimestamp;
if (elapsedTime > streamTotalTime) {
elapsedTime = streamTotalTime;
}
uint256 totalTokensStreamed = elapsedTime * tokensPerSecond;
uint256 tokensRequested = transaction.data.toUint256(36);
uint256 totalWithdrawal = totalWithdrawals[delegationHash];
require(totalWithdrawal + tokensRequested <= totalTokensStreamed, "enforcer:large-withdrawal");
totalWithdrawals[delegationHash] += tokensRequested;
return true;
}
function cancelSubscription(SignedDelegation calldata signedDelegation, bytes32 domainHash)
external
{
address signer = verifyExternalDelegationSignature(signedDelegation, domainHash);
address sender = _msgSender();
require(signer == sender, "DistrictERC20SubscriptionsEnforcer:no-cancel-permission");
bytes32 delegationHash = GET_SIGNEDDELEGATION_PACKETHASH(signedDelegation);
isCanceled[delegationHash] = true;
}
function verifyExternalDelegationSignature(
SignedDelegation memory signedDelegation,
bytes32 domainHash
) public view virtual returns (address) {
Delegation memory delegation = signedDelegation.delegation;
bytes32 sigHash = getExternalDelegationTypedDataHash(delegation, domainHash);
address recoveredSignatureSigner = recover(sigHash, signedDelegation.signature);
return recoveredSignatureSigner;
}
function getExternalDelegationTypedDataHash(Delegation memory delegation, bytes32 domainHash)
public
pure
returns (bytes32)
{
bytes32 digest = keccak256(
abi.encodePacked("\x19\x01", domainHash, GET_DELEGATION_PACKETHASH(delegation))
);
return digest;
}
function _msgSender() internal view override returns (address sender) {
if (msg.sender == address(this)) {
bytes memory array = msg.data;
uint256 index = msg.data.length;
assembly {
// Load the 32 bytes word from memory with the address on the lower 20 bytes, and mask those.
sender := and(mload(add(array, index)), 0xffffffffffffffffffffffffffffffffffffffff)
}
} else {
sender = msg.sender;
}
return sender;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment