-
-
Save kamescg/eb0aa9812db4597301e36be8a3d2799d to your computer and use it in GitHub Desktop.
DistrictERC20StreamPaymentsEnforcer.sol
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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