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

Adding the recipient to the ERC20BalanceGteEnforcer terms #38

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
{
"transactions": [
{
"hash": "0x387b6ff19251fc406bdbd7b8ba93f1e3dfa2eb5da95a91d36b8f39c245e64ba5",
"transactionType": "CREATE2",
"contractName": "ERC20BalanceGteEnforcer",
"contractAddress": "0xa9fa48158e454274cb13ed59034cd644fb11ee8a",
"function": null,
"arguments": null,
"transaction": {
"from": "0xb0403b32f54d0bd752113f4009e8b534c6669f44",
"to": "0x4e59b44847b379578588920ca78fbf26c0b4956c",
"gas": "0x9e687",
"value": "0x0",
"input": "0x3000000000000000000000000000000000000000000000000000000000000000608060405234801561001057600080fd5b50610785806100206000396000f3fe608060405234801561001057600080fd5b50600436106100625760003560e01c806332a16f4e146100675780638678d6ef1461009f578063a145832a146100c0578063b5e54492146100d5578063b99deb0e146100f5578063d3eddcc51461012e575b600080fd5b61008a6100753660046104ce565b60016020526000908152604090205460ff1681565b60405190151581526020015b60405180910390f35b6100b26100ad366004610503565b610141565b604051908152602001610096565b6100d36100ce366004610588565b610156565b005b6100b26100e33660046104ce565b60006020819052908152604090205481565b610108610103366004610655565b610296565b604080516001600160a01b03948516815293909216602084015290820152606001610096565b6100d361013c366004610588565b610359565b600061014e84848461048b565b949350505050565b6000806101638c8c610296565b5091509150600061017533838861048b565b60008181526001602052604090205490915060ff16156101ef5760405162461bcd60e51b815260206004820152602a60248201527f455243323042616c616e6365477465456e666f726365723a656e666f726365726044820152690b5a5ccb5b1bd8dad95960b21b60648201526084015b60405180910390fd5b6000818152600160208190526040808320805460ff1916909217909155516370a0823160e01b81526001600160a01b0385811660048301528416906370a0823190602401602060405180830381865afa158015610250573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906102749190610697565b6000928352602083905260409092209190915550505050505050505050505050565b60008080604884146102ff5760405162461bcd60e51b815260206004820152602c60248201527f455243323042616c616e6365477465456e666f726365723a696e76616c69642d60448201526b0e8cae4dae65ad8cadccee8d60a31b60648201526084016101e6565b61030d6014600086886106b0565b610316916106da565b60601c92506103296028601486886106b0565b610332916106da565b60601c915061034484602881886106b0565b61034d9161070f565b60001c90509250925092565b60008060006103688d8d610296565b925092509250600061037b33848961048b565b600081815260016020526040808220805460ff19169055516370a0823160e01b81526001600160a01b03878116600483015292935090918516906370a0823190602401602060405180830381865afa1580156103db573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906103ff9190610697565b60008381526020819052604090205490915061041c90849061072e565b81101561047a5760405162461bcd60e51b815260206004820152602660248201527f455243323042616c616e6365477465456e666f726365723a62616c616e63652d6044820152651b9bdd0b59dd60d21b60648201526084016101e6565b505050505050505050505050505050565b604080516001600160a01b039485166020808301919091529390941684820152606080850192909252805180850390920182526080909301909252815191012090565b6000602082840312156104e057600080fd5b5035919050565b80356001600160a01b03811681146104fe57600080fd5b919050565b60008060006060848603121561051857600080fd5b610521846104e7565b925061052f602085016104e7565b9150604084013590509250925092565b60008083601f84011261055157600080fd5b50813567ffffffffffffffff81111561056957600080fd5b60208301915083602082850101111561058157600080fd5b9250929050565b60008060008060008060008060008060e08b8d0312156105a757600080fd5b8a3567ffffffffffffffff808211156105bf57600080fd5b6105cb8e838f0161053f565b909c509a5060208d01359150808211156105e457600080fd5b6105f08e838f0161053f565b909a50985060408d0135975060608d013591508082111561061057600080fd5b5061061d8d828e0161053f565b90965094505060808b0135925061063660a08c016104e7565b915061064460c08c016104e7565b90509295989b9194979a5092959850565b6000806020838503121561066857600080fd5b823567ffffffffffffffff81111561067f57600080fd5b61068b8582860161053f565b90969095509350505050565b6000602082840312156106a957600080fd5b5051919050565b600080858511156106c057600080fd5b838611156106cd57600080fd5b5050820193919092039150565b6bffffffffffffffffffffffff1981358181169160148510156107075780818660140360031b1b83161692505b505092915050565b8035602083101561072857600019602084900360031b1b165b92915050565b8082018082111561072857634e487b7160e01b600052601160045260246000fdfea26469706673582212201dc7f4975efe32dc10c9bd8cbe7300e9a7e01f0dad5e7e54ec0ba5c7ebaf437464736f6c63430008170033",
"nonce": "0x3c",
"chainId": "0xe705"
},
"additionalContracts": [],
"isFixedGasLimit": false
}
],
"receipts": [
{
"status": "0x1",
"cumulativeGasUsed": "0x72af4",
"logs": [],
"logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
"type": "0x2",
"transactionHash": "0x387b6ff19251fc406bdbd7b8ba93f1e3dfa2eb5da95a91d36b8f39c245e64ba5",
"transactionIndex": "0x0",
"blockHash": "0x170da69ce9df63eec4187aff105b8288464bb00f81a967e2b41bb13401492bc5",
"blockNumber": "0x6d6927",
"gasUsed": "0x72af4",
"effectiveGasPrice": "0x6673ae1",
"from": "0xb0403b32f54d0bd752113f4009e8b534c6669f44",
"to": "0x4e59b44847b379578588920ca78fbf26c0b4956c",
"contractAddress": null
}
],
"libraries": [],
"pending": [],
"returns": {},
"timestamp": 1734402606,
"chain": 59141,
"commit": "e894780"
}
180 changes: 15 additions & 165 deletions broadcast/DeployEnvironmentSetUp.s.sol/59141/run-latest.json

Large diffs are not rendered by default.

32 changes: 17 additions & 15 deletions src/enforcers/ERC20BalanceGteEnforcer.sol
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ contract ERC20BalanceGteEnforcer is CaveatEnforcer {
/**
* @notice Generates the key that identifies the run. Produced by the hash of the values used.
* @param _caller Address of the sender calling the enforcer.
* @param _token Token being compared in the beforeHook and beforeHook.
* @param _token Token being compared in the beforeHook and afterHook.
* @param _delegationHash The hash of the delegation.
* @return The hash to be used as key of the mapping.
*/
Expand All @@ -37,63 +37,65 @@ contract ERC20BalanceGteEnforcer is CaveatEnforcer {

/**
* @notice This function caches the delegators ERC20 balance before the delegation is executed.
* @param _terms 52 packed bytes where: the first 20 bytes are the address of the token, the next 32 bytes
* are the amount the balance should be greater than
* @param _terms 72 packed bytes where: the first 20 bytes are the address of the recipient, the next 20 bytes
* are the address of the token, the next 32 bytes are the amount the balance should be greater than
*/
function beforeHook(
bytes calldata _terms,
bytes calldata,
ModeCode,
bytes calldata,
bytes32 _delegationHash,
address _delegator,
address,
address
)
public
override
{
(address token_,) = getTermsInfo(_terms);
(address recipient_, address token_,) = getTermsInfo(_terms);
bytes32 hashKey_ = _getHashKey(msg.sender, token_, _delegationHash);
require(!isLocked[hashKey_], "ERC20BalanceGteEnforcer:enforcer-is-locked");
isLocked[hashKey_] = true;
uint256 balance_ = IERC20(token_).balanceOf(_delegator);
uint256 balance_ = IERC20(token_).balanceOf(recipient_);
balanceCache[hashKey_] = balance_;
}

/**
* @notice This function enforces that the delegators ERC20 balance has increased by at least the amount provided.
* @param _terms 52 packed bytes where: the first 20 bytes are the address of the token, the next 32 bytes
* are the amount the balance should be greater than
* @param _terms 72 packed bytes where: the first 20 bytes are the address of the recipient, the next 20 bytes
* are the address of the token, the next 32 bytes are the amount the balance should be greater than
*/
function afterHook(
bytes calldata _terms,
bytes calldata,
ModeCode,
bytes calldata,
bytes32 _delegationHash,
address _delegator,
address,
address
)
public
override
{
(address token_, uint256 amount_) = getTermsInfo(_terms);
(address recipient_, address token_, uint256 amount_) = getTermsInfo(_terms);
bytes32 hashKey_ = _getHashKey(msg.sender, token_, _delegationHash);
delete isLocked[hashKey_];
uint256 balance_ = IERC20(token_).balanceOf(_delegator);
uint256 balance_ = IERC20(token_).balanceOf(recipient_);
require(balance_ >= balanceCache[hashKey_] + amount_, "ERC20BalanceGteEnforcer:balance-not-gt");
}

/**
* @notice Decodes the terms used in this CaveatEnforcer.
* @param _terms encoded data that is used during the execution hooks.
* @return recipient_ The address of the recipient.
* @return token_ The address of the token.
* @return amount_ The amount the balance should be greater than.
*/
function getTermsInfo(bytes calldata _terms) public pure returns (address token_, uint256 amount_) {
require(_terms.length == 52, "ERC20BalanceGteEnforcer:invalid-terms-length");
token_ = address(bytes20(_terms[:20]));
amount_ = uint256(bytes32(_terms[20:]));
function getTermsInfo(bytes calldata _terms) public pure returns (address recipient_, address token_, uint256 amount_) {
require(_terms.length == 72, "ERC20BalanceGteEnforcer:invalid-terms-length");
recipient_ = address(bytes20(_terms[:20]));
token_ = address(bytes20(_terms[20:40]));
amount_ = uint256(bytes32(_terms[40:]));
}

////////////////////////////// Internal Methods //////////////////////////////
Expand Down
67 changes: 54 additions & 13 deletions test/enforcers/ERC20BalanceGteEnforcer.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ contract ERC20BalanceGteEnforcerTest is CaveatEnforcerBaseTest {
BasicERC20 public token;
address delegator;
address delegate;
address recipient;
address dm;
Execution mintExecution;
bytes mintExecutionCallData;
Expand All @@ -28,6 +29,7 @@ contract ERC20BalanceGteEnforcerTest is CaveatEnforcerBaseTest {
super.setUp();
delegator = address(users.alice.deleGator);
delegate = address(users.bob.deleGator);
recipient = address(users.carol.deleGator);
dm = address(delegationManager);
enforcer = new ERC20BalanceGteEnforcer();
vm.label(address(enforcer), "ERC20 BalanceGte Enforcer");
Expand All @@ -42,48 +44,73 @@ contract ERC20BalanceGteEnforcerTest is CaveatEnforcerBaseTest {

// Validates the terms get decoded correctly
function test_decodedTheTerms() public {
bytes memory terms_ = abi.encodePacked(address(token), uint256(100));
bytes memory terms_ = abi.encodePacked(address(recipient), address(token), uint256(100));
uint256 amount_;
address token_;
(token_, amount_) = enforcer.getTermsInfo(terms_);
address recipient_;
(recipient_, token_, amount_) = enforcer.getTermsInfo(terms_);
assertEq(amount_, 100);
assertEq(token_, address(token));
assertEq(recipient_, address(recipient));
}

// Validates that a balance has increased at least the expected amount
function test_allow_ifBalanceIncreases() public {
// Expect it to increase by at least 100
bytes memory terms_ = abi.encodePacked(address(token), uint256(100));
bytes memory terms_ = abi.encodePacked(address(recipient), address(token), uint256(100));

// Increase by 100
vm.prank(dm);
enforcer.beforeHook(terms_, hex"", mode, mintExecutionCallData, bytes32(0), delegator, delegate);
vm.prank(delegator);
token.mint(delegator, 100);
token.mint(recipient, 100);
vm.prank(dm);
enforcer.afterHook(terms_, hex"", mode, mintExecutionCallData, bytes32(0), delegator, delegate);

// Increase by 1000
vm.prank(dm);
enforcer.beforeHook(terms_, hex"", mode, mintExecutionCallData, bytes32(0), delegator, delegate);
vm.prank(delegator);
token.mint(delegator, 1000);
token.mint(recipient, 1000);
vm.prank(dm);
enforcer.afterHook(terms_, hex"", mode, mintExecutionCallData, bytes32(0), delegator, delegate);
}

// Validates that the delegation can be reused with different recipients
function test_allow_reuseDelegationWithDifferentRecipients() public {
// Expect it to increase by at least 100
bytes memory terms1_ = abi.encodePacked(address(recipient), address(token), uint256(100));
bytes memory terms2_ = abi.encodePacked(address(delegator), address(token), uint256(100));

// Increase by 100, check for recipient
vm.prank(dm);
enforcer.beforeHook(terms1_, hex"", mode, mintExecutionCallData, bytes32(0), delegator, delegate);
vm.prank(delegator);
token.mint(recipient, 100);
vm.prank(dm);
enforcer.afterHook(terms1_, hex"", mode, mintExecutionCallData, bytes32(0), delegator, delegate);

// Increase by 100, check for delegator as recipient
vm.prank(dm);
enforcer.beforeHook(terms2_, hex"", mode, mintExecutionCallData, bytes32(0), delegator, delegate);
vm.prank(delegator);
token.mint(delegator, 100);
vm.prank(dm);
enforcer.afterHook(terms2_, hex"", mode, mintExecutionCallData, bytes32(0), delegator, delegate);
}

// ////////////////////// Errors //////////////////////

// Reverts if a balance hasn't increased by the set amount
function test_notAllow_insufficientIncrease() public {
// Expect it to increase by at least 100
bytes memory terms_ = abi.encodePacked(address(token), uint256(100));
bytes memory terms_ = abi.encodePacked(address(recipient), address(token), uint256(100));

// Increase by 10, expect revert
vm.prank(dm);
enforcer.beforeHook(terms_, hex"", mode, mintExecutionCallData, bytes32(0), delegator, delegate);
vm.prank(delegator);
token.mint(delegator, 10);
token.mint(recipient, 10);
vm.prank(dm);
vm.expectRevert(bytes("ERC20BalanceGteEnforcer:balance-not-gt"));
enforcer.afterHook(terms_, hex"", mode, mintExecutionCallData, bytes32(0), delegator, delegate);
Expand All @@ -92,7 +119,7 @@ contract ERC20BalanceGteEnforcerTest is CaveatEnforcerBaseTest {
// Reverts if a enforcer is locked
function test_notAllow_reenterALockedEnforcer() public {
// Expect it to increase by at least 100
bytes memory terms_ = abi.encodePacked(address(token), uint256(100));
bytes memory terms_ = abi.encodePacked(address(recipient), address(token), uint256(100));
bytes32 delegationHash_ = bytes32(uint256(99999999));

// Increase by 100
Expand All @@ -104,7 +131,7 @@ contract ERC20BalanceGteEnforcerTest is CaveatEnforcerBaseTest {
vm.expectRevert(bytes("ERC20BalanceGteEnforcer:enforcer-is-locked"));
enforcer.beforeHook(terms_, hex"", mode, mintExecutionCallData, delegationHash_, delegator, delegate);
vm.startPrank(delegator);
token.mint(delegator, 1000);
token.mint(recipient, 1000);
vm.startPrank(dm);

// Unlocks the enforcer
Expand All @@ -115,17 +142,31 @@ contract ERC20BalanceGteEnforcerTest is CaveatEnforcerBaseTest {
assertTrue(enforcer.isLocked(hashKey_));
}

// Reverts if the recipient didn't receive any tokens
function test_notAllow_noIncreaseToRecipient() public {
bytes memory terms_ = abi.encodePacked(address(recipient), address(token), uint256(100));

// BeforeHook should cache the recipient's balance
vm.prank(dm);
enforcer.beforeHook(terms_, hex"", mode, mintExecutionCallData, bytes32(0), delegator, delegate);

// No tokens are minted to the recipient, so balance shouldn't increase
vm.prank(dm);
vm.expectRevert(bytes("ERC20BalanceGteEnforcer:balance-not-gt"));
enforcer.afterHook(terms_, hex"", mode, mintExecutionCallData, bytes32(0), delegator, delegate);
}

// Validates the terms are well formed
function test_invalid_decodedTheTerms() public {
bytes memory terms_;

// Too small
terms_ = abi.encodePacked(address(token), uint8(100));
terms_ = abi.encodePacked(address(recipient), address(token), uint8(100));
vm.expectRevert(bytes("ERC20BalanceGteEnforcer:invalid-terms-length"));
enforcer.getTermsInfo(terms_);

// Too large
terms_ = abi.encodePacked(uint256(100), uint256(100));
terms_ = abi.encodePacked(address(recipient), address(token), uint256(100), uint256(100));
vm.expectRevert(bytes("ERC20BalanceGteEnforcer:invalid-terms-length"));
enforcer.getTermsInfo(terms_);
}
Expand All @@ -135,15 +176,15 @@ contract ERC20BalanceGteEnforcerTest is CaveatEnforcerBaseTest {
bytes memory terms_;

// Invalid token
terms_ = abi.encodePacked(address(0), uint256(100));
terms_ = abi.encodePacked(address(recipient), address(0), uint256(100));
vm.expectRevert();
enforcer.beforeHook(terms_, hex"", mode, mintExecutionCallData, bytes32(0), delegator, delegate);
}

// Validates that an invalid ID reverts
function test_notAllow_expectingOverflow() public {
// Expect balance to increase so much that the balance overflows
bytes memory terms_ = abi.encodePacked(address(token), type(uint256).max);
bytes memory terms_ = abi.encodePacked(address(recipient), address(token), type(uint256).max);

// Increase
vm.prank(dm);
Expand Down