From a7dc6f1b81d81000cff4fe94ad221cbc9a70b93d Mon Sep 17 00:00:00 2001 From: Justin Kaseman Date: Wed, 6 Sep 2023 19:36:07 -0400 Subject: [PATCH] (test): Add ToS Allow List foundry tests (#10438) * (test): Add Functions Subscriptions foundry tests * (test): Add ToS Allow List foundry tests --- .../gas-snapshots/functions.gas-snapshot | 25 +- .../FunctionsTermsOfServiceAllowList.t.sol | 313 +++++++++++++++++- 2 files changed, 325 insertions(+), 13 deletions(-) diff --git a/contracts/gas-snapshots/functions.gas-snapshot b/contracts/gas-snapshots/functions.gas-snapshot index 56d8a4da7fd..00ebdc48dfd 100644 --- a/contracts/gas-snapshots/functions.gas-snapshot +++ b/contracts/gas-snapshots/functions.gas-snapshot @@ -29,7 +29,7 @@ FunctionsSubscriptions_CancelSubscription:test_CancelSubscription_RevertIfNotSub FunctionsSubscriptions_CancelSubscription:test_CancelSubscription_RevertIfPaused() (gas: 20103) FunctionsSubscriptions_CancelSubscription:test_CancelSubscription_RevertIfPendingRequests() (gas: 193421) FunctionsSubscriptions_CancelSubscription:test_CancelSubscription_Success() (gas: 67209) -FunctionsSubscriptions_Constructor:test_Constructor_Success() (gas: 7609) +FunctionsSubscriptions_Constructor:test_Constructor_Success() (gas: 7609) FunctionsSubscriptions_CreateSubscriptionWithConsumer:test_CreateSubscriptionWithConsumer_RevertIfNotAllowedSender() (gas: 28659) FunctionsSubscriptions_CreateSubscriptionWithConsumer:test_CreateSubscriptionWithConsumer_RevertIfPaused() (gas: 17970) FunctionsSubscriptions_CreateSubscriptionWithConsumer:test_CreateSubscriptionWithConsumer_Success() (gas: 371776) @@ -88,4 +88,25 @@ FunctionsSubscriptions_TimeoutRequests:test_TimeoutRequests_RevertInvalidRequest FunctionsSubscriptions_TimeoutRequests:test_TimeoutRequests_Success() (gas: 57781) FunctionsSubscriptions_createSubscription:test_CreateSubscription_RevertIfNotAllowedSender() (gas: 26368) FunctionsSubscriptions_createSubscription:test_CreateSubscription_RevertIfPaused() (gas: 15714) -FunctionsSubscriptions_createSubscription:test_CreateSubscription_Success() (gas: 152510) \ No newline at end of file +FunctionsSubscriptions_createSubscription:test_CreateSubscription_Success() (gas: 152510) +FunctionsTermsOfServiceAllowList_AcceptTermsOfService:test_AcceptTermsOfService_RevertIfAcceptorIsNotSender() (gas: 25855) +FunctionsTermsOfServiceAllowList_AcceptTermsOfService:test_AcceptTermsOfService_RevertIfBlockedSender() (gas: 44366) +FunctionsTermsOfServiceAllowList_AcceptTermsOfService:test_AcceptTermsOfService_RevertIfInvalidSigner() (gas: 23615) +FunctionsTermsOfServiceAllowList_AcceptTermsOfService:test_AcceptTermsOfService_RevertIfRecipientContractIsNotSender() (gas: 1469094) +FunctionsTermsOfServiceAllowList_AcceptTermsOfService:test_AcceptTermsOfService_RevertIfRecipientIsNotSender() (gas: 26021) +FunctionsTermsOfServiceAllowList_AcceptTermsOfService:test_AcceptTermsOfService_SuccessIfAcceptingForContract() (gas: 1549182) +FunctionsTermsOfServiceAllowList_AcceptTermsOfService:test_AcceptTermsOfService_SuccessIfAcceptingForSelf() (gas: 94702) +FunctionsTermsOfServiceAllowList_BlockSender:test_BlockSender_RevertIfNotOwner() (gas: 15469) +FunctionsTermsOfServiceAllowList_BlockSender:test_BlockSender_Success() (gas: 50442) +FunctionsTermsOfServiceAllowList_Constructor:test_Constructor_Success() (gas: 12187) +FunctionsTermsOfServiceAllowList_GetAllAllowedSenders:test_GetAllAllowedSenders_Success() (gas: 19243) +FunctionsTermsOfServiceAllowList_GetConfig:test_GetConfig_Success() (gas: 15773) +FunctionsTermsOfServiceAllowList_GetMessage:test_GetMessage_Success() (gas: 11592) +FunctionsTermsOfServiceAllowList_HasAccess:test_HasAccess_FalseWhenEnabled() (gas: 15931) +FunctionsTermsOfServiceAllowList_HasAccess:test_HasAccess_TrueWhenDisabled() (gas: 23445) +FunctionsTermsOfServiceAllowList_IsBlockedSender:test_IsBlockedSender_SuccessFalse() (gas: 15354) +FunctionsTermsOfServiceAllowList_IsBlockedSender:test_IsBlockedSender_SuccessTrue() (gas: 41957) +FunctionsTermsOfServiceAllowList_UnblockSender:test_UnblockSender_RevertIfNotOwner() (gas: 13525) +FunctionsTermsOfServiceAllowList_UnblockSender:test_UnblockSender_Success() (gas: 95208) +FunctionsTermsOfServiceAllowList_UpdateConfig:test_UpdateConfig_RevertIfNotOwner() (gas: 13736) +FunctionsTermsOfServiceAllowList_UpdateConfig:test_UpdateConfig_Success() (gas: 22082) diff --git a/contracts/src/v0.8/functions/tests/1_0_0/FunctionsTermsOfServiceAllowList.t.sol b/contracts/src/v0.8/functions/tests/1_0_0/FunctionsTermsOfServiceAllowList.t.sol index af6fa6dcca5..d4a18e207b4 100644 --- a/contracts/src/v0.8/functions/tests/1_0_0/FunctionsTermsOfServiceAllowList.t.sol +++ b/contracts/src/v0.8/functions/tests/1_0_0/FunctionsTermsOfServiceAllowList.t.sol @@ -1,52 +1,343 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.19; -/// @notice #constructor -contract FunctionsTermsOfServiceAllowList_Constructor { +import {TermsOfServiceAllowList} from "../../dev/1_0_0/accessControl/TermsOfServiceAllowList.sol"; +import {FunctionsClientTestHelper} from "./testhelpers/FunctionsClientTestHelper.sol"; + +import {FunctionsRoutesSetup, FunctionsOwnerAcceptTermsOfServiceSetup} from "./Setup.t.sol"; +/// @notice #constructor +contract FunctionsTermsOfServiceAllowList_Constructor is FunctionsRoutesSetup { + function test_Constructor_Success() public { + assertEq(s_termsOfServiceAllowList.typeAndVersion(), "Functions Terms of Service Allow List v1.0.0"); + assertEq(s_termsOfServiceAllowList.owner(), OWNER_ADDRESS); + } } /// @notice #getConfig -contract FunctionsTermsOfServiceAllowList_GetConfig { +contract FunctionsTermsOfServiceAllowList_GetConfig is FunctionsRoutesSetup { + function test_GetConfig_Success() public { + // Send as stranger + vm.stopPrank(); + vm.startPrank(STRANGER_ADDRESS); + TermsOfServiceAllowList.Config memory config = s_termsOfServiceAllowList.getConfig(); + assertEq(config.enabled, getTermsOfServiceConfig().enabled); + assertEq(config.signerPublicKey, getTermsOfServiceConfig().signerPublicKey); + } } /// @notice #updateConfig -contract FunctionsTermsOfServiceAllowList_UpdateConfig { +contract FunctionsTermsOfServiceAllowList_UpdateConfig is FunctionsRoutesSetup { + function test_UpdateConfig_RevertIfNotOwner() public { + // Send as stranger + vm.stopPrank(); + vm.startPrank(STRANGER_ADDRESS); + + vm.expectRevert("Only callable by owner"); + s_termsOfServiceAllowList.updateConfig( + TermsOfServiceAllowList.Config({enabled: true, signerPublicKey: STRANGER_ADDRESS}) + ); + } + + event ConfigUpdated(TermsOfServiceAllowList.Config config); + + function test_UpdateConfig_Success() public { + TermsOfServiceAllowList.Config memory configToSet = TermsOfServiceAllowList.Config({ + enabled: false, + signerPublicKey: TOS_SIGNER + }); + + // topic0 (function signature, always checked), NOT topic1 (false), NOT topic2 (false), NOT topic3 (false), and data (true). + bool checkTopic1 = false; + bool checkTopic2 = false; + bool checkTopic3 = false; + bool checkData = true; + vm.expectEmit(checkTopic1, checkTopic2, checkTopic3, checkData); + emit ConfigUpdated(configToSet); + + s_termsOfServiceAllowList.updateConfig(configToSet); + TermsOfServiceAllowList.Config memory config = s_termsOfServiceAllowList.getConfig(); + assertEq(config.enabled, configToSet.enabled); + assertEq(config.signerPublicKey, configToSet.signerPublicKey); + } } /// @notice #getMessage -contract FunctionsTermsOfServiceAllowList_GetMessage { +contract FunctionsTermsOfServiceAllowList_GetMessage is FunctionsRoutesSetup { + function test_GetMessage_Success() public { + // Send as stranger + vm.stopPrank(); + vm.startPrank(STRANGER_ADDRESS); + bytes32 message = s_termsOfServiceAllowList.getMessage(STRANGER_ADDRESS, STRANGER_ADDRESS); + + assertEq(message, keccak256(abi.encodePacked(STRANGER_ADDRESS, STRANGER_ADDRESS))); + } } /// @notice #acceptTermsOfService -contract FunctionsTermsOfServiceAllowList_AcceptTermsOfService { +contract FunctionsTermsOfServiceAllowList_AcceptTermsOfService is FunctionsRoutesSetup { + function test_AcceptTermsOfService_RevertIfBlockedSender() public { + s_termsOfServiceAllowList.blockSender(STRANGER_ADDRESS); + + // Send as stranger + vm.stopPrank(); + vm.startPrank(STRANGER_ADDRESS); + + bytes32 message = s_termsOfServiceAllowList.getMessage(STRANGER_ADDRESS, STRANGER_ADDRESS); + bytes32 prefixedMessage = keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", message)); + (uint8 v, bytes32 r, bytes32 s) = vm.sign(TOS_SIGNER_PRIVATE_KEY, prefixedMessage); + + vm.expectRevert(TermsOfServiceAllowList.RecipientIsBlocked.selector); + + s_termsOfServiceAllowList.acceptTermsOfService(STRANGER_ADDRESS, STRANGER_ADDRESS, r, s, v); + } + + function test_AcceptTermsOfService_RevertIfInvalidSigner() public { + // Send as stranger + vm.stopPrank(); + vm.startPrank(STRANGER_ADDRESS); + + bytes32 message = s_termsOfServiceAllowList.getMessage(STRANGER_ADDRESS, STRANGER_ADDRESS); + bytes32 prefixedMessage = keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", message)); + (uint8 v, bytes32 r, bytes32 s) = vm.sign(STRANGER_PRIVATE_KEY, prefixedMessage); + + vm.expectRevert(TermsOfServiceAllowList.InvalidSignature.selector); + + s_termsOfServiceAllowList.acceptTermsOfService(STRANGER_ADDRESS, STRANGER_ADDRESS, r, s, v); + } + + function test_AcceptTermsOfService_RevertIfRecipientIsNotSender() public { + // Send as stranger + vm.stopPrank(); + vm.startPrank(STRANGER_ADDRESS); + + bytes32 message = s_termsOfServiceAllowList.getMessage(OWNER_ADDRESS, STRANGER_ADDRESS); + bytes32 prefixedMessage = keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", message)); + (uint8 v, bytes32 r, bytes32 s) = vm.sign(TOS_SIGNER_PRIVATE_KEY, prefixedMessage); + + vm.expectRevert(TermsOfServiceAllowList.InvalidUsage.selector); + + s_termsOfServiceAllowList.acceptTermsOfService(OWNER_ADDRESS, STRANGER_ADDRESS, r, s, v); + } + + function test_AcceptTermsOfService_RevertIfAcceptorIsNotSender() public { + // Send as stranger + vm.stopPrank(); + vm.startPrank(STRANGER_ADDRESS); + + bytes32 message = s_termsOfServiceAllowList.getMessage(STRANGER_ADDRESS, OWNER_ADDRESS); + bytes32 prefixedMessage = keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", message)); + (uint8 v, bytes32 r, bytes32 s) = vm.sign(TOS_SIGNER_PRIVATE_KEY, prefixedMessage); + + vm.expectRevert(TermsOfServiceAllowList.InvalidUsage.selector); + + s_termsOfServiceAllowList.acceptTermsOfService(STRANGER_ADDRESS, OWNER_ADDRESS, r, s, v); + } + + function test_AcceptTermsOfService_RevertIfRecipientContractIsNotSender() public { + FunctionsClientTestHelper s_functionsClientHelper = new FunctionsClientTestHelper(address(s_functionsRouter)); + + // Send as externally owned account + vm.stopPrank(); + vm.startPrank(STRANGER_ADDRESS); + + // Attempt to accept for a contract account + bytes32 message = s_termsOfServiceAllowList.getMessage(STRANGER_ADDRESS, address(s_functionsClientHelper)); + bytes32 prefixedMessage = keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", message)); + (uint8 v, bytes32 r, bytes32 s) = vm.sign(TOS_SIGNER_PRIVATE_KEY, prefixedMessage); + + vm.expectRevert(TermsOfServiceAllowList.InvalidUsage.selector); + + s_termsOfServiceAllowList.acceptTermsOfService(STRANGER_ADDRESS, address(s_functionsClientHelper), r, s, v); + } + event AddedAccess(address user); + + function test_AcceptTermsOfService_SuccessIfAcceptingForSelf() public { + // Send as stranger + vm.stopPrank(); + vm.startPrank(STRANGER_ADDRESS); + + bytes32 message = s_termsOfServiceAllowList.getMessage(STRANGER_ADDRESS, STRANGER_ADDRESS); + bytes32 prefixedMessage = keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", message)); + (uint8 v, bytes32 r, bytes32 s) = vm.sign(TOS_SIGNER_PRIVATE_KEY, prefixedMessage); + + // topic0 (function signature, always checked), NOT topic1 (false), NOT topic2 (false), NOT topic3 (false), and data (true). + bool checkTopic1 = false; + bool checkTopic2 = false; + bool checkTopic3 = false; + bool checkData = true; + vm.expectEmit(checkTopic1, checkTopic2, checkTopic3, checkData); + emit AddedAccess(STRANGER_ADDRESS); + + s_termsOfServiceAllowList.acceptTermsOfService(STRANGER_ADDRESS, STRANGER_ADDRESS, r, s, v); + + assertEq(s_termsOfServiceAllowList.hasAccess(STRANGER_ADDRESS, new bytes(0)), true); + } + + function test_AcceptTermsOfService_SuccessIfAcceptingForContract() public { + FunctionsClientTestHelper s_functionsClientHelper = new FunctionsClientTestHelper(address(s_functionsRouter)); + + // Send as stranger + vm.stopPrank(); + vm.startPrank(STRANGER_ADDRESS); + + bytes32 message = s_termsOfServiceAllowList.getMessage(STRANGER_ADDRESS, address(s_functionsClientHelper)); + bytes32 prefixedMessage = keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", message)); + (uint8 v, bytes32 r, bytes32 s) = vm.sign(TOS_SIGNER_PRIVATE_KEY, prefixedMessage); + + // topic0 (function signature, always checked), NOT topic1 (false), NOT topic2 (false), NOT topic3 (false), and data (true). + bool checkTopic1 = false; + bool checkTopic2 = false; + bool checkTopic3 = false; + bool checkData = true; + vm.expectEmit(checkTopic1, checkTopic2, checkTopic3, checkData); + emit AddedAccess(address(s_functionsClientHelper)); + + s_functionsClientHelper.acceptTermsOfService(STRANGER_ADDRESS, address(s_functionsClientHelper), r, s, v); + + assertEq(s_termsOfServiceAllowList.hasAccess(address(s_functionsClientHelper), new bytes(0)), true); + } } /// @notice #getAllAllowedSenders -contract FunctionsTermsOfServiceAllowList_GetAllAllowedSenders { +contract FunctionsTermsOfServiceAllowList_GetAllAllowedSenders is FunctionsOwnerAcceptTermsOfServiceSetup { + function test_GetAllAllowedSenders_Success() public { + // Send as stranger + vm.stopPrank(); + vm.startPrank(STRANGER_ADDRESS); + + address[] memory expectedSenders = new address[](1); + expectedSenders[0] = OWNER_ADDRESS; + assertEq(s_termsOfServiceAllowList.getAllAllowedSenders(), expectedSenders); + } } /// @notice #hasAccess -contract FunctionsTermsOfServiceAllowList_HasAccess { +contract FunctionsTermsOfServiceAllowList_HasAccess is FunctionsRoutesSetup { + function test_HasAccess_FalseWhenEnabled() public { + // Send as stranger + vm.stopPrank(); + vm.startPrank(STRANGER_ADDRESS); + + // Check access of account that is not on the allow list + assertEq(s_termsOfServiceAllowList.hasAccess(STRANGER_ADDRESS, new bytes(0)), false); + } + + function test_HasAccess_TrueWhenDisabled() public { + // Disable allow list, which opens all access + s_termsOfServiceAllowList.updateConfig( + TermsOfServiceAllowList.Config({enabled: false, signerPublicKey: TOS_SIGNER}) + ); + + // Send as stranger + vm.stopPrank(); + vm.startPrank(STRANGER_ADDRESS); + // Check access of account that is not on the allow list + assertEq(s_termsOfServiceAllowList.hasAccess(STRANGER_ADDRESS, new bytes(0)), true); + } } /// @notice #isBlockedSender -contract FunctionsTermsOfServiceAllowList_IsBlockedSender { +contract FunctionsTermsOfServiceAllowList_IsBlockedSender is FunctionsRoutesSetup { + function test_IsBlockedSender_SuccessFalse() public { + // Send as stranger + vm.stopPrank(); + vm.startPrank(STRANGER_ADDRESS); + assertEq(s_termsOfServiceAllowList.isBlockedSender(STRANGER_ADDRESS), false); + } + + function test_IsBlockedSender_SuccessTrue() public { + // Block sender + s_termsOfServiceAllowList.blockSender(STRANGER_ADDRESS); + + // Send as stranger + vm.stopPrank(); + vm.startPrank(STRANGER_ADDRESS); + + assertEq(s_termsOfServiceAllowList.isBlockedSender(STRANGER_ADDRESS), true); + } } /// @notice #blockSender -contract FunctionsTermsOfServiceAllowList_BlockSender { +contract FunctionsTermsOfServiceAllowList_BlockSender is FunctionsRoutesSetup { + function test_BlockSender_RevertIfNotOwner() public { + // Send as stranger + vm.stopPrank(); + vm.startPrank(STRANGER_ADDRESS); + + vm.expectRevert("Only callable by owner"); + s_termsOfServiceAllowList.blockSender(OWNER_ADDRESS); + } + + event BlockedAccess(address user); + + function test_BlockSender_Success() public { + // topic0 (function signature, always checked), NOT topic1 (false), NOT topic2 (false), NOT topic3 (false), and data (true). + bool checkTopic1 = false; + bool checkTopic2 = false; + bool checkTopic3 = false; + bool checkData = true; + vm.expectEmit(checkTopic1, checkTopic2, checkTopic3, checkData); + emit BlockedAccess(STRANGER_ADDRESS); + + s_termsOfServiceAllowList.blockSender(STRANGER_ADDRESS); + assertEq(s_termsOfServiceAllowList.hasAccess(STRANGER_ADDRESS, new bytes(0)), false); + assertEq(s_termsOfServiceAllowList.isBlockedSender(STRANGER_ADDRESS), true); + // Account can no longer accept Terms of Service + bytes32 message = s_termsOfServiceAllowList.getMessage(STRANGER_ADDRESS, STRANGER_ADDRESS); + bytes32 prefixedMessage = keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", message)); + (uint8 v, bytes32 r, bytes32 s) = vm.sign(TOS_SIGNER_PRIVATE_KEY, prefixedMessage); + vm.expectRevert(TermsOfServiceAllowList.RecipientIsBlocked.selector); + s_termsOfServiceAllowList.acceptTermsOfService(STRANGER_ADDRESS, STRANGER_ADDRESS, r, s, v); + } } /// @notice #unblockSender -contract FunctionsTermsOfServiceAllowList_UnblockSender { +contract FunctionsTermsOfServiceAllowList_UnblockSender is FunctionsRoutesSetup { + function setUp() public virtual override { + FunctionsRoutesSetup.setUp(); + + s_termsOfServiceAllowList.blockSender(STRANGER_ADDRESS); + } + + function test_UnblockSender_RevertIfNotOwner() public { + // Send as stranger + vm.stopPrank(); + vm.startPrank(STRANGER_ADDRESS); + + vm.expectRevert("Only callable by owner"); + s_termsOfServiceAllowList.unblockSender(STRANGER_ADDRESS); + } + + event UnblockedAccess(address user); + + function test_UnblockSender_Success() public { + // topic0 (function signature, always checked), NOT topic1 (false), NOT topic2 (false), NOT topic3 (false), and data (true). + bool checkTopic1 = false; + bool checkTopic2 = false; + bool checkTopic3 = false; + bool checkData = true; + vm.expectEmit(checkTopic1, checkTopic2, checkTopic3, checkData); + emit UnblockedAccess(STRANGER_ADDRESS); + + s_termsOfServiceAllowList.unblockSender(STRANGER_ADDRESS); + + // Send as stranger + vm.stopPrank(); + vm.startPrank(STRANGER_ADDRESS); + // Account can now accept the Terms of Service + bytes32 message = s_termsOfServiceAllowList.getMessage(STRANGER_ADDRESS, STRANGER_ADDRESS); + bytes32 prefixedMessage = keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", message)); + (uint8 v, bytes32 r, bytes32 s) = vm.sign(TOS_SIGNER_PRIVATE_KEY, prefixedMessage); + s_termsOfServiceAllowList.acceptTermsOfService(STRANGER_ADDRESS, STRANGER_ADDRESS, r, s, v); + } }