Skip to content

Commit

Permalink
chore: added redeemer validation to payment enforcer
Browse files Browse the repository at this point in the history
  • Loading branch information
hanzel98 committed Aug 29, 2024
1 parent b68ce99 commit 096870c
Show file tree
Hide file tree
Showing 2 changed files with 90 additions and 62 deletions.
5 changes: 4 additions & 1 deletion src/enforcers/NativeTokenPaymentEnforcer.sol
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,11 @@ contract NativeTokenPaymentEnforcer is CaveatEnforcer {

Delegation[] memory delegations_ = abi.decode(_args, (Delegation[]));

require(delegations_[0].delegator == _redeemer, "NativeTokenPaymentEnforcer:invalid-redeemer");

uint256 delegationsLength = delegations_.length;
// Assign the delegation hash as the args to the args equality enforcer.
for (uint256 x = 0; x < delegations_.length; ++x) {
for (uint256 x = 0; x < delegationsLength; ++x) {
Delegation memory delegation_ = delegations_[x];
for (uint256 i = 0; i < delegation_.caveats.length; ++i) {
if (delegation_.caveats[i].enforcer == argsEqualityCheckEnforcer) {
Expand Down
147 changes: 86 additions & 61 deletions test/enforcers/NativeTokenPaymentEnforcer.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -86,11 +86,16 @@ contract NativeTokenPaymentEnforcerTest is CaveatEnforcerBaseTest {
vm.startPrank(address(delegationManager));
vm.expectEmit(true, true, true, true, address(nativeTokenPaymentEnforcer));
emit NativeTokenPaymentEnforcer.ValidatedPayment(
address(delegationManager), delegationHash_, recipient_, address(users.alice.deleGator), address(0), 1 ether
address(delegationManager),
delegationHash_,
recipient_,
address(users.alice.deleGator),
address(users.bob.deleGator),
1 ether
);

nativeTokenPaymentEnforcer.afterHook(
terms_, args_, mode_, executionCallData_, delegationHash_, address(users.alice.deleGator), address(0)
terms_, args_, mode_, executionCallData_, delegationHash_, address(users.alice.deleGator), address(users.bob.deleGator)
);
}

Expand Down Expand Up @@ -137,11 +142,16 @@ contract NativeTokenPaymentEnforcerTest is CaveatEnforcerBaseTest {
vm.startPrank(address(delegationManager));
vm.expectEmit(true, true, true, true, address(nativeTokenPaymentEnforcer));
emit NativeTokenPaymentEnforcer.ValidatedPayment(
address(delegationManager), delegationHash_, recipient_, address(users.alice.deleGator), address(0), 1 ether
address(delegationManager),
delegationHash_,
recipient_,
address(users.alice.deleGator),
address(users.bob.deleGator),
1 ether
);

nativeTokenPaymentEnforcer.afterHook(
terms_, args_, mode_, executionCallData_, delegationHash_, address(users.alice.deleGator), address(0)
terms_, args_, mode_, executionCallData_, delegationHash_, address(users.alice.deleGator), address(users.bob.deleGator)
);
}

Expand Down Expand Up @@ -210,7 +220,7 @@ contract NativeTokenPaymentEnforcerTest is CaveatEnforcerBaseTest {
ExecutionLib.encodeSingle(execution_.target, execution_.value, execution_.callData),
delegationHash_,
address(users.alice.deleGator),
address(0)
address(users.bob.deleGator)
);
}

Expand Down Expand Up @@ -259,7 +269,9 @@ contract NativeTokenPaymentEnforcerTest is CaveatEnforcerBaseTest {

vm.startPrank(mockDelegationManager_);
vm.expectRevert("NativeTokenPaymentEnforcer:payment-not-received");
nativeTokenPaymentEnforcer.afterHook(terms_, args_, mode_, executionCallData_, delegationHash_, address(0), address(0));
nativeTokenPaymentEnforcer.afterHook(
terms_, args_, mode_, executionCallData_, delegationHash_, address(0), address(users.bob.deleGator)
);
}

// Should FAIL if the sender is different from the delegation manager.
Expand Down Expand Up @@ -326,26 +338,24 @@ contract NativeTokenPaymentEnforcerTest is CaveatEnforcerBaseTest {
assertEq(address(users.alice.deleGator).balance, aliceBalanceBefore_ + 1 ether);
}

function test_delegationFrontRunning() public {
address recipient_ = address(users.alice.deleGator);
function test_delegationFrontRunningWithRedeemer() public {
// The original terms indicating to send 1 ether to Alice as the payment for Bob
bytes memory originalPaymentTerms_ = abi.encodePacked(recipient_, uint256(1 ether));
bytes memory originalPaymentTerms_ = abi.encodePacked(address(users.alice.deleGator), uint256(1 ether));

(, Delegation memory originalPaidDelegation_) = _getExampleDelegation(originalPaymentTerms_, hex"");
(bytes32 originalDelegationHash_, Delegation memory originalPaidDelegation_) =
_getExampleDelegation(originalPaymentTerms_, hex"");
Delegation[] memory originalPaidDelegations_ = new Delegation[](1);
originalPaidDelegations_[0] = originalPaidDelegation_;

// The malicious terms indicating to send 1 ether to Alice as the payment for Carol
bytes memory maliciousPaymentTerms_ = abi.encodePacked(recipient_, uint256(1 ether));
(, Delegation memory maliciousPaidDelegation_) = _getMaliciousDelegation(maliciousPaymentTerms_, hex"");
(bytes32 maliciousDelegationHash_, Delegation memory maliciousPaidDelegation_) =
_getMaliciousDelegation(originalPaymentTerms_, hex"");
Delegation[] memory maliciousPaidDelegations_ = new Delegation[](1);
maliciousPaidDelegations_[0] = maliciousPaidDelegation_;

// Create the Allowance enforcer (No args enforcer comparison, prone to front-running)
uint256 allowance_ = 1 ether;
bytes memory allowanceTerms_ = abi.encode(allowance_);
// Create the Allowance enforcer (No args enforcer comparison)
Caveat[] memory caveats_ = new Caveat[](1);
caveats_[0] = Caveat({ args: hex"", enforcer: address(nativeTokenTransferAmountEnforcer), terms: allowanceTerms_ });
caveats_[0] =
Caveat({ args: hex"", enforcer: address(nativeTokenTransferAmountEnforcer), terms: abi.encode(uint256(1 ether)) });

// Create payment delegation from Bob to NativeTokenPaymentEnforcer
// This delegation is public any one could front-running it
Expand All @@ -361,62 +371,55 @@ contract NativeTokenPaymentEnforcerTest is CaveatEnforcerBaseTest {
});
paymentDelegations_[0] = signDelegation(users.bob, paymentDelegations_[0]);

// The args contain the payment delegation to redeem
bytes memory argsWithBobPayment_ = abi.encode(paymentDelegations_);

Execution memory execution_ = Execution({
target: address(aliceDeleGatorCounter),
value: 0,
callData: abi.encodeWithSelector(Counter.increment.selector)
});

uint256 aliceBalanceBefore_ = address(users.alice.deleGator).balance;
vm.startPrank(address(delegationManager));

assertEq(aliceDeleGatorCounter.count(), 0);
bytes memory executionCallData_ =
ExecutionLib.encodeSingle(address(aliceDeleGatorCounter), 0, abi.encodeWithSelector(Counter.increment.selector));

// Pass the delegation payment in the args.
maliciousPaidDelegations_[0].caveats[0].args = argsWithBobPayment_;
invokeDelegation_UserOp(users.carol, maliciousPaidDelegations_, execution_);
assertEq(aliceDeleGatorCounter.count(), 1);

// Alice received the payment
assertEq(address(users.alice.deleGator).balance, aliceBalanceBefore_ + 1 ether);
// The redeemer is Carol and not Bob who is the delegator of the payment delegations
vm.expectRevert("NativeTokenPaymentEnforcer:invalid-redeemer");
nativeTokenPaymentEnforcer.afterHook(
originalPaymentTerms_,
abi.encode(paymentDelegations_), // argsWithBobPayment_
ModeLib.encodeSimpleSingle(),
executionCallData_,
maliciousDelegationHash_,
maliciousPaidDelegation_.delegator,
maliciousPaidDelegation_.delegate
);

// Pass the delegation payment in the args.
originalPaidDelegations_[0].caveats[0].args = argsWithBobPayment_;
invokeDelegation_UserOp(users.bob, originalPaidDelegations_, execution_);
// The execution did not work because the allowance has already been used.
assertEq(aliceDeleGatorCounter.count(), 1);
// The redeemer is Bob who is the delegator of the payment delegations
nativeTokenPaymentEnforcer.afterHook(
originalPaymentTerms_,
abi.encode(paymentDelegations_), // argsWithBobPayment_
ModeLib.encodeSimpleSingle(),
executionCallData_,
originalDelegationHash_,
originalPaidDelegation_.delegator,
originalPaidDelegation_.delegate
);
}

function test_delegationArgsEnforcerPreventFrontRunning() public {
function test_delegationPreventFrontRunningWithArgs() public {
// The original terms indicating to send 1 ether to Alice as the payment for Bob
address recipient_ = address(users.alice.deleGator);
bytes memory originalPaymentTerms_ = abi.encodePacked(recipient_, uint256(1 ether));

(bytes32 originalPaidDelegationHash_, Delegation memory originalPaidDelegation_) =
_getExampleDelegation(originalPaymentTerms_, hex"");
Delegation[] memory originalPaidDelegations_ = new Delegation[](1);
originalPaidDelegations_[0] = originalPaidDelegation_;

// The malicious terms indicating to send 1 ether to Alice as the payment for Carol
bytes memory maliciousPaymentTerms_ = abi.encodePacked(recipient_, uint256(1 ether));

(, Delegation memory maliciousPaidDelegation_) = _getMaliciousDelegation(maliciousPaymentTerms_, hex"");
Delegation[] memory maliciousPaidDelegations_ = new Delegation[](1);
maliciousPaidDelegations_[0] = maliciousPaidDelegation_;
(bytes32 openPaidDelegationHash_, Delegation memory openPaidDelegation_) = _getOpenDelegation(originalPaymentTerms_, hex"");
Delegation[] memory openPaidDelegations_ = new Delegation[](1);
openPaidDelegations_[0] = openPaidDelegation_;

// Create the Allowance enforcer (Combined with args enforcer, prevent front-running)
uint256 allowance_ = 1 ether;
bytes memory allowanceTerms_ = abi.encode(allowance_);
bytes memory argsTerms_ = abi.encode(originalPaidDelegationHash_);
bytes memory argsTerms_ = abi.encode(openPaidDelegationHash_);

Caveat[] memory caveats_ = new Caveat[](2);
caveats_[0] = Caveat({ args: hex"", enforcer: address(nativeTokenTransferAmountEnforcer), terms: allowanceTerms_ });
caveats_[1] = Caveat({ args: hex"", enforcer: address(argsEqualityCheckEnforcer), terms: argsTerms_ });

// Create payment delegation from Bob to NativeTokenPaymentEnforcer
// This delegation is public any one could front-running it but it is protected with the args enforcer
// This delegation is protected with the args enforcer
// Even though Bob created this delegation to pay for his Alice delegation, Carol tries to use it to pay for her.
Delegation[] memory paymentDelegations_ = new Delegation[](1);
paymentDelegations_[0] = Delegation({
Expand All @@ -443,17 +446,16 @@ contract NativeTokenPaymentEnforcerTest is CaveatEnforcerBaseTest {
assertEq(aliceDeleGatorCounter.count(), 0);

// Pass the delegation payment in the args.
maliciousPaidDelegations_[0].caveats[0].args = argsWithBobPayment_;
invokeDelegation_UserOp(users.carol, maliciousPaidDelegations_, execution_);
// The execution did not work because the allowance fails due to the invalid args.
openPaidDelegations_[0].caveats[0].args = argsWithBobPayment_;

invokeDelegation_UserOp(users.carol, openPaidDelegations_, execution_);
// The execution did not work because the redeemer is not Carol.
assertEq(aliceDeleGatorCounter.count(), 0);
// Alice did not receive the payment
assertEq(address(users.alice.deleGator).balance, aliceBalanceBefore_);

// Pass the delegation payment in the args.
originalPaidDelegations_[0].caveats[0].args = argsWithBobPayment_;
invokeDelegation_UserOp(users.bob, originalPaidDelegations_, execution_);
// The execution works well with a proper args
invokeDelegation_UserOp(users.bob, openPaidDelegations_, execution_);
// The execution works well with a proper args and using Bob as redeemer
assertEq(aliceDeleGatorCounter.count(), 1);
// Alice received the payment
assertEq(address(users.alice.deleGator).balance, aliceBalanceBefore_ + 1 ether);
Expand Down Expand Up @@ -482,6 +484,29 @@ contract NativeTokenPaymentEnforcerTest is CaveatEnforcerBaseTest {
delegationHash_ = EncoderLib._getDelegationHash(delegation_);
}

function _getOpenDelegation(
bytes memory inputTerms_,
bytes memory args_
)
internal
view
returns (bytes32 delegationHash_, Delegation memory delegation_)
{
Caveat[] memory caveats_ = new Caveat[](1);
caveats_[0] = Caveat({ args: args_, enforcer: address(nativeTokenPaymentEnforcer), terms: inputTerms_ });

delegation_ = Delegation({
delegate: delegationManager.ANY_DELEGATE(),
delegator: address(users.alice.deleGator),
authority: ROOT_AUTHORITY,
caveats: caveats_,
salt: 0,
signature: hex""
});
delegation_ = signDelegation(users.alice, delegation_);
delegationHash_ = EncoderLib._getDelegationHash(delegation_);
}

function _getMaliciousDelegation(
bytes memory inputTerms_,
bytes memory args_
Expand Down

0 comments on commit 096870c

Please sign in to comment.