From 72de9d7d41a39214e457894cf50830b8f9dec279 Mon Sep 17 00:00:00 2001 From: Kaspar Kallas Date: Thu, 8 Aug 2024 14:15:54 +0300 Subject: [PATCH 1/6] no-op to create pr --- packages/automation-contracts/scheduler/README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/automation-contracts/scheduler/README.md b/packages/automation-contracts/scheduler/README.md index 9eea2417da..3c5a895cef 100644 --- a/packages/automation-contracts/scheduler/README.md +++ b/packages/automation-contracts/scheduler/README.md @@ -8,8 +8,7 @@ If you have an intended end date and/or start date for a stream, instead of havi ## Vesting Scheduler -The Vesting Scheduler allows you to schedule the vesting of tokens to a receiver account. The Vesting Scheduler does not hold the tokens, rather it simply uses permissions to move them for you - +The Vesting Scheduler allows you to schedule the vesting of tokens to a receiver account. The Vesting Scheduler does not hold the tokens, rather it simply uses permissions to move them for you. ## Getting Started From 3f1c6aa50ba1051ac953491c9d31572bb097eeff Mon Sep 17 00:00:00 2001 From: Kaspar Kallas Date: Thu, 8 Aug 2024 14:25:15 +0300 Subject: [PATCH 2/6] additional vesting scheduler tests by 0xPilou --- .../VestingSchedulerV2.StatefulFuzz.t.sol | 433 ++++++++++++++++++ .../scheduler/test/VestingSchedulerV2.t.sol | 92 ++++ 2 files changed, 525 insertions(+) create mode 100644 packages/automation-contracts/scheduler/test/VestingSchedulerV2.StatefulFuzz.t.sol diff --git a/packages/automation-contracts/scheduler/test/VestingSchedulerV2.StatefulFuzz.t.sol b/packages/automation-contracts/scheduler/test/VestingSchedulerV2.StatefulFuzz.t.sol new file mode 100644 index 0000000000..a65731e723 --- /dev/null +++ b/packages/automation-contracts/scheduler/test/VestingSchedulerV2.StatefulFuzz.t.sol @@ -0,0 +1,433 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.0; + +import {ISuperToken} from "@superfluid-finance/ethereum-contracts/contracts/interfaces/superfluid/ISuperToken.sol"; +import {FlowOperatorDefinitions} from "@superfluid-finance/ethereum-contracts/contracts/interfaces/superfluid/ISuperfluid.sol"; +import {IVestingSchedulerV2} from "./../contracts/interface/IVestingSchedulerV2.sol"; +import {VestingSchedulerV2} from "./../contracts/VestingSchedulerV2.sol"; +import {FoundrySuperfluidTester} from "@superfluid-finance/ethereum-contracts/test/foundry/FoundrySuperfluidTester.sol"; +import {SuperTokenV1Library} from "@superfluid-finance/ethereum-contracts/contracts/apps/SuperTokenV1Library.sol"; +import "forge-std/console.sol"; + +/// @title VestingSchedulerTests +/// @notice Look at me , I am the captain now - Elvijs +contract VestingSchedulerV2StatefulFuzzTests is FoundrySuperfluidTester { + using SuperTokenV1Library for ISuperToken; + + /// @dev This is required by solidity for using the SuperTokenV1Library in the tester + VestingSchedulerV2 public vestingScheduler; + + /// @dev Constants for Testing + uint256 constant MIN_CLIFF_AMOUNT = 1; + uint256 constant MAX_CLIFF_AMOUNT = 1_000e18; + int96 constant MIN_FLOW_RATE = 1; + int96 constant MAX_FLOW_RATE = 1_000e18; + bytes constant EMPTY_CTX = ""; + + constructor() FoundrySuperfluidTester(0) { + vestingScheduler = new VestingSchedulerV2(sf.host); + } + + /// SETUP AND HELPERS + function setUp() public virtual override { + super.setUp(); + } + + enum TestActionType { + TA_CREATE_SCHEDULE, + TA_UPDATE_SCHEDULE, + TA_DELETE_SCHEDULE, + TA_EXECUTE_SCHEDULE, + TA_TERMINATE_SCHEDULE + // TA_CREATE_CLAIMABLE_SCHEDULE, + } + + struct Actions { + uint8 actionCode; + address sender; + address receiver; + uint32 startDate; + uint32 cliffDate; + int96 flowRate; + uint256 cliffAmount; + uint32 endDate; + uint32 newEndDate; + } + + function toActionType( + uint8 actionCode, + uint8 maxAction + ) internal pure returns (TestActionType) { + return TestActionType(actionCode % maxAction); + } + + function test_StatefulFuzz(Actions[10] calldata actions) public { + for (uint256 i = 0; i < actions.length; i++) { + Actions memory a = actions[i]; + TestActionType t = toActionType(a.actionCode, 5); + vm.assume( + vestingScheduler + .getVestingSchedule( + address(superToken), + a.sender, + a.receiver + ) + .flowRate == 0 + ); + vm.assume( + a.sender != address(0) && + a.receiver != address(0) && + a.sender != a.receiver + ); + a.flowRate = int96(bound(a.flowRate, 1, 1000e18)); + a.startDate = uint32( + bound( + a.startDate, + uint32(block.timestamp), + uint32(block.timestamp + 3650 days) + ) + ); + a.endDate = uint32( + bound( + a.endDate, + a.startDate + vestingScheduler.MIN_VESTING_DURATION() + 1, + a.startDate + uint32(3650 days) + ) + ); + if (a.cliffDate == 0) { + a.cliffAmount = 0; + vm.assume(a.startDate < a.endDate); + vm.assume( + a.startDate + vestingScheduler.START_DATE_VALID_AFTER() < + a.endDate - vestingScheduler.END_DATE_VALID_BEFORE() + ); + } else { + a.cliffAmount = bound(a.cliffAmount, 1e18, 1000e18); + a.cliffDate = uint32( + bound(a.cliffDate, a.startDate, a.endDate - 1) + ); + vm.assume( + a.endDate - a.cliffDate > + vestingScheduler.MIN_VESTING_DURATION() + ); + vm.assume( + a.cliffDate + vestingScheduler.START_DATE_VALID_AFTER() < + a.endDate - vestingScheduler.END_DATE_VALID_BEFORE() + ); + } + + if (t == TestActionType.TA_CREATE_SCHEDULE) { + _test_createVestingSchedule( + a.sender, + a.receiver, + a.startDate, + a.cliffDate, + a.flowRate, + a.cliffAmount, + a.endDate + ); + } else if (t == TestActionType.TA_UPDATE_SCHEDULE) { + _test_updateVestingSchedule( + a.sender, + a.receiver, + a.startDate, + a.cliffDate, + a.flowRate, + a.cliffAmount, + a.endDate, + a.newEndDate + ); + } else if (t == TestActionType.TA_DELETE_SCHEDULE) { + _test_deleteVestingSchedule( + a.sender, + a.receiver, + a.startDate, + a.cliffDate, + a.flowRate, + a.cliffAmount, + a.endDate + ); + } else if (t == TestActionType.TA_EXECUTE_SCHEDULE) { + _test_executeCliffAndFlow( + a.sender, + a.receiver, + a.startDate, + a.cliffDate, + a.flowRate, + a.cliffAmount, + a.endDate + ); + } else if (t == TestActionType.TA_TERMINATE_SCHEDULE) { + _test_executeEndVesting( + a.sender, + a.receiver, + a.startDate, + a.cliffDate, + a.flowRate, + a.cliffAmount, + a.endDate + ); + } else assert(false); + } + } + + function _test_createVestingSchedule( + address sender, + address receiver, + uint32 startDate, + uint32 cliffDate, + int96 flowRate, + uint256 cliffAmount, + uint32 endDate + ) internal { + _createVestingSchedule( + sender, + receiver, + startDate, + cliffDate, + flowRate, + cliffAmount, + endDate + ); + + //assert storage data + VestingSchedulerV2.VestingSchedule memory schedule = vestingScheduler + .getVestingSchedule(address(superToken), sender, receiver); + + uint32 cliffAndFlowDate = cliffDate == 0 ? startDate : cliffDate; + assertTrue( + schedule.cliffAndFlowDate == cliffAndFlowDate, + "schedule.cliffAndFlowDate" + ); + assertTrue(schedule.endDate == endDate, "schedule.endDate"); + assertTrue(schedule.flowRate == flowRate, "schedule.flowRate"); + assertTrue(schedule.cliffAmount == cliffAmount, "schedule.cliffAmount"); + assertTrue( + schedule.claimValidityDate == 0, + "schedule.claimValidityDate" + ); + } + + function _test_executeCliffAndFlow( + address sender, + address receiver, + uint32 startDate, + uint32 cliffDate, + int96 flowRate, + uint256 cliffAmount, + uint32 endDate + ) internal { + _createVestingSchedule( + sender, + receiver, + startDate, + cliffDate, + flowRate, + cliffAmount, + endDate + ); + + _provisionSender(sender); + _arrangeAllowances(sender, flowRate); + + // Set the time to 1 second after the cliff and flow date + vm.warp(cliffDate == 0 ? startDate + 1 : cliffDate + 1); + + bool success = vestingScheduler.executeCliffAndFlow( + superToken, + sender, + receiver + ); + + assertTrue(success, "executeCliffAndFlow should return true"); + + //assert storage data + VestingSchedulerV2.VestingSchedule memory schedule = vestingScheduler + .getVestingSchedule(address(superToken), sender, receiver); + assertTrue(schedule.cliffAndFlowDate == 0, "schedule.cliffAndFlowDate"); + assertTrue(schedule.cliffAmount == 0, "schedule.cliffAmount"); + } + + function _test_updateVestingSchedule( + address sender, + address receiver, + uint32 startDate, + uint32 cliffDate, + int96 flowRate, + uint256 cliffAmount, + uint32 endDate, + uint32 newEndDate + ) internal { + _createVestingSchedule( + sender, + receiver, + startDate, + cliffDate, + flowRate, + cliffAmount, + endDate + ); + + _provisionSender(sender); + _arrangeAllowances(sender, flowRate); + + // Set the time to 1 second after the cliff and flow date + vm.warp(cliffDate == 0 ? startDate + 1 : cliffDate + 1); + vestingScheduler.executeCliffAndFlow(superToken, sender, receiver); + + vm.assume(newEndDate > block.timestamp); + vm.prank(sender); + vestingScheduler.updateVestingSchedule( + superToken, + receiver, + newEndDate, + EMPTY_CTX + ); + + //assert storage data + VestingSchedulerV2.VestingSchedule memory schedule = vestingScheduler + .getVestingSchedule(address(superToken), sender, receiver); + assertTrue(schedule.endDate == newEndDate, "schedule.cliffAndFlowDate"); + assertTrue(schedule.remainderAmount == 0, "schedule.remainderAmount"); + } + + function _test_executeEndVesting( + address sender, + address receiver, + uint32 startDate, + uint32 cliffDate, + int96 flowRate, + uint256 cliffAmount, + uint32 endDate + ) internal { + _createVestingSchedule( + sender, + receiver, + startDate, + cliffDate, + flowRate, + cliffAmount, + endDate + ); + + _provisionSender(sender); + _arrangeAllowances(sender, flowRate); + + // Set the time to 1 second after the cliff and flow date + vm.warp(cliffDate == 0 ? startDate + 1 : cliffDate + 1); + + vestingScheduler.executeCliffAndFlow(superToken, sender, receiver); + + vm.warp(endDate - vestingScheduler.END_DATE_VALID_BEFORE() + 1); + + bool success = vestingScheduler.executeEndVesting( + superToken, + sender, + receiver + ); + assertTrue(success, "executeEndVesting should return true"); + + //assert storage data + VestingSchedulerV2.VestingSchedule memory schedule = vestingScheduler + .getVestingSchedule(address(superToken), sender, receiver); + assertTrue(schedule.cliffAndFlowDate == 0, "schedule.cliffAndFlowDate"); + assertTrue(schedule.endDate == 0, "schedule.endDate"); + assertTrue(schedule.flowRate == 0, "schedule.flowRate"); + assertTrue(schedule.cliffAmount == 0, "schedule.cliffAmount"); + assertTrue( + schedule.claimValidityDate == 0, + "schedule.claimValidityDate" + ); + assertTrue(schedule.remainderAmount == 0, "schedule.remainderAmount"); + } + + function _test_deleteVestingSchedule( + address sender, + address receiver, + uint32 startDate, + uint32 cliffDate, + int96 flowRate, + uint256 cliffAmount, + uint32 endDate + ) internal { + _createVestingSchedule( + sender, + receiver, + startDate, + cliffDate, + flowRate, + cliffAmount, + endDate + ); + + vm.prank(sender); + vestingScheduler.deleteVestingSchedule(superToken, receiver, EMPTY_CTX); + + //assert storage data + VestingSchedulerV2.VestingSchedule memory schedule = vestingScheduler + .getVestingSchedule(address(superToken), sender, receiver); + assertTrue(schedule.cliffAndFlowDate == 0, "schedule.cliffAndFlowDate"); + assertTrue(schedule.endDate == 0, "schedule.endDate"); + assertTrue(schedule.flowRate == 0, "schedule.flowRate"); + assertTrue(schedule.cliffAmount == 0, "schedule.cliffAmount"); + assertTrue( + schedule.claimValidityDate == 0, + "schedule.claimValidityDate" + ); + assertTrue(schedule.remainderAmount == 0, "schedule.remainderAmount"); + } + + function _createVestingSchedule( + address sender, + address receiver, + uint32 startDate, + uint32 cliffDate, + int96 flowRate, + uint256 cliffAmount, + uint32 endDate + ) private { + vm.prank(sender); + vestingScheduler.createVestingSchedule( + superToken, + receiver, + startDate, + cliffDate, + flowRate, + cliffAmount, + endDate, + 0, + EMPTY_CTX + ); + } + + function _provisionSender(address sender) private { + token.mint(sender, INIT_TOKEN_BALANCE); + vm.startPrank(sender); + token.approve(address(superToken), INIT_TOKEN_BALANCE); + superToken.upgrade(INIT_TOKEN_BALANCE); + vm.stopPrank(); + } + + function _arrangeAllowances(address sender, int96 flowRate) private { + vm.startPrank(sender); + // ## Superfluid ACL allowance and permissions + sf.host.callAgreement( + sf.cfa, + abi.encodeCall( + sf.cfa.updateFlowOperatorPermissions, + ( + superToken, + address(vestingScheduler), + FlowOperatorDefinitions.AUTHORIZE_FULL_CONTROL, + flowRate, + new bytes(0) + ) + ), + new bytes(0) + ); + + // ## ERC-20 allowance for cliff and compensation transfers + superToken.approve(address(vestingScheduler), type(uint256).max); + vm.stopPrank(); + } +} diff --git a/packages/automation-contracts/scheduler/test/VestingSchedulerV2.t.sol b/packages/automation-contracts/scheduler/test/VestingSchedulerV2.t.sol index 1dd9086899..9494550db1 100644 --- a/packages/automation-contracts/scheduler/test/VestingSchedulerV2.t.sol +++ b/packages/automation-contracts/scheduler/test/VestingSchedulerV2.t.sol @@ -277,6 +277,32 @@ contract VestingSchedulerV2Tests is FoundrySuperfluidTester { /// TESTS function testCreateVestingSchedule() public { + vm.expectEmit(true, true, true, true); + emit VestingScheduleCreated( + superToken, alice, bob, START_DATE, CLIFF_DATE, FLOW_RATE, END_DATE, CLIFF_TRANSFER_AMOUNT, 0, 0); + + vm.startPrank(alice); + vestingScheduler.createVestingSchedule( + superToken, + bob, + START_DATE, + CLIFF_DATE, + FLOW_RATE, + CLIFF_TRANSFER_AMOUNT, + END_DATE, + EMPTY_CTX + ); + vm.stopPrank(); + + //assert storage data + VestingSchedulerV2.VestingSchedule memory schedule = vestingScheduler.getVestingSchedule(address(superToken), alice, bob); + assertTrue(schedule.cliffAndFlowDate == CLIFF_DATE , "schedule.cliffAndFlowDate"); + assertTrue(schedule.endDate == END_DATE , "schedule.endDate"); + assertTrue(schedule.flowRate == FLOW_RATE , "schedule.flowRate"); + assertTrue(schedule.cliffAmount == CLIFF_TRANSFER_AMOUNT , "schedule.cliffAmount"); + } + + function test_createVestingSchedule_v1_overload() public { vm.expectEmit(true, true, true, true); emit VestingScheduleCreated( superToken, alice, bob, START_DATE, CLIFF_DATE, FLOW_RATE, END_DATE, CLIFF_TRANSFER_AMOUNT, 0, 0); @@ -1369,6 +1395,66 @@ contract VestingSchedulerV2Tests is FoundrySuperfluidTester { vm.warp(type(uint32).max); assertEq($.afterSenderBalance, superToken.balanceOf(alice), "After the schedule has ended, the sender's balance should never change."); } + + function test_createAndExecuteVestingScheduleFromAmountAndDuration(uint256 _totalAmount, uint32 _totalDuration) public { + + _totalDuration = SafeCast.toUint32(bound(_totalDuration, uint32(7 days), uint32(365 days))); + _totalAmount = bound(_totalAmount, 1 ether, 100 ether); + + int96 flowRate = SafeCast.toInt96( + SafeCast.toInt256(_totalAmount / _totalDuration) + ); + + uint96 remainderAmount = SafeCast.toUint96( + _totalAmount - (SafeCast.toUint256(flowRate) * _totalDuration) + ); + + _setACL_AUTHORIZE_FULL_CONTROL(alice, flowRate); + + vm.startPrank(alice); + superToken.increaseAllowance(address(vestingScheduler), type(uint256).max); + + vm.expectEmit(true, true, true, true); + emit VestingScheduleCreated( + superToken, alice, bob, uint32(block.timestamp), 0, flowRate, uint32(block.timestamp) + _totalDuration, 0, 0, remainderAmount); + + vm.expectEmit(true, true, true, true); + emit VestingCliffAndFlowExecuted(superToken, alice, bob, uint32(block.timestamp), flowRate, 0, 0); + + vestingScheduler.createAndExecuteVestingScheduleFromAmountAndDuration(superToken, bob, _totalAmount, _totalDuration, EMPTY_CTX); + + vm.stopPrank(); + } + + function test_createAndExecuteVestingScheduleFromAmountAndDuration_noCtx(uint256 _totalAmount, uint32 _totalDuration) public { + _totalDuration = SafeCast.toUint32(bound(_totalDuration, uint32(7 days), uint32(365 days))); + _totalAmount = bound(_totalAmount, 1 ether, 100 ether); + + int96 flowRate = SafeCast.toInt96( + SafeCast.toInt256(_totalAmount / _totalDuration) + ); + + uint96 remainderAmount = SafeCast.toUint96( + _totalAmount - (SafeCast.toUint256(flowRate) * _totalDuration) + ); + + _setACL_AUTHORIZE_FULL_CONTROL(alice, flowRate); + + vm.startPrank(alice); + superToken.increaseAllowance(address(vestingScheduler), type(uint256).max); + + vm.expectEmit(true, true, true, true); + emit VestingScheduleCreated( + superToken, alice, bob, uint32(block.timestamp), 0, flowRate, uint32(block.timestamp) + _totalDuration, 0, 0, remainderAmount); + + vm.expectEmit(true, true, true, true); + emit VestingCliffAndFlowExecuted(superToken, alice, bob, uint32(block.timestamp), flowRate, 0, 0); + + vestingScheduler.createAndExecuteVestingScheduleFromAmountAndDuration(superToken, bob, _totalAmount, _totalDuration); + + vm.stopPrank(); + } + function test_createClaimableVestingSchedule() public { @@ -2182,6 +2268,12 @@ contract VestingSchedulerV2Tests is FoundrySuperfluidTester { testAssertScheduleDoesNotExist(address(superToken), alice, bob); } + function test_executeEndVesting_scheduleNotClaimed() public { + _createClaimableVestingScheduleWithDefaultData(alice, bob); + vm.expectRevert(IVestingSchedulerV2.ScheduleNotClaimed.selector); + vestingScheduler.executeEndVesting(superToken, alice, bob); + } + function test_getMaximumNeededTokenAllowance_with_claim_should_end_with_zero_if_extreme_ranges_are_used( uint256 totalAmount, uint32 totalDuration, From a6a7a26465ef440bb46d1ee6ab465f542c2a0380 Mon Sep 17 00:00:00 2001 From: Kaspar Kallas Date: Fri, 13 Sep 2024 11:24:28 +0300 Subject: [PATCH 3/6] force super token type --- .../scheduler/test/VestingSchedulerV2.StatefulFuzz.t.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/automation-contracts/scheduler/test/VestingSchedulerV2.StatefulFuzz.t.sol b/packages/automation-contracts/scheduler/test/VestingSchedulerV2.StatefulFuzz.t.sol index a65731e723..ab77e3a6ab 100644 --- a/packages/automation-contracts/scheduler/test/VestingSchedulerV2.StatefulFuzz.t.sol +++ b/packages/automation-contracts/scheduler/test/VestingSchedulerV2.StatefulFuzz.t.sol @@ -10,7 +10,6 @@ import {SuperTokenV1Library} from "@superfluid-finance/ethereum-contracts/contra import "forge-std/console.sol"; /// @title VestingSchedulerTests -/// @notice Look at me , I am the captain now - Elvijs contract VestingSchedulerV2StatefulFuzzTests is FoundrySuperfluidTester { using SuperTokenV1Library for ISuperToken; @@ -30,6 +29,7 @@ contract VestingSchedulerV2StatefulFuzzTests is FoundrySuperfluidTester { /// SETUP AND HELPERS function setUp() public virtual override { + vm.setEnv(TOKEN_TYPE_ENV_KEY, "WRAPPER_SUPER_TOKEN"); super.setUp(); } From 711a7eae210806b4ddfb863c03e8efb2b135d682 Mon Sep 17 00:00:00 2001 From: Kaspar Kallas Date: Mon, 16 Sep 2024 17:06:45 +0300 Subject: [PATCH 4/6] test _getSender --- .../scheduler/test/VestingSchedulerV2.t.sol | 61 ++++++++++++++++++- 1 file changed, 60 insertions(+), 1 deletion(-) diff --git a/packages/automation-contracts/scheduler/test/VestingSchedulerV2.t.sol b/packages/automation-contracts/scheduler/test/VestingSchedulerV2.t.sol index 9494550db1..c5794ce4a7 100644 --- a/packages/automation-contracts/scheduler/test/VestingSchedulerV2.t.sol +++ b/packages/automation-contracts/scheduler/test/VestingSchedulerV2.t.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.0; import { ISuperToken } from "@superfluid-finance/ethereum-contracts/contracts/interfaces/superfluid/ISuperToken.sol"; -import { FlowOperatorDefinitions } from "@superfluid-finance/ethereum-contracts/contracts/interfaces/superfluid/ISuperfluid.sol"; +import { FlowOperatorDefinitions, ISuperfluid, BatchOperation } from "@superfluid-finance/ethereum-contracts/contracts/interfaces/superfluid/ISuperfluid.sol"; import { IVestingSchedulerV2 } from "./../contracts/interface/IVestingSchedulerV2.sol"; import { VestingSchedulerV2 } from "./../contracts/VestingSchedulerV2.sol"; import { FoundrySuperfluidTester } from "@superfluid-finance/ethereum-contracts/test/foundry/FoundrySuperfluidTester.sol"; @@ -91,6 +91,7 @@ contract VestingSchedulerV2Tests is FoundrySuperfluidTester { uint32 immutable CLAIM_VALIDITY_DATE = uint32(BLOCK_TIMESTAMP + 15 days); uint32 immutable END_DATE = uint32(BLOCK_TIMESTAMP + 20 days); bytes constant EMPTY_CTX = ""; + bytes constant NON_EMPTY_CTX = abi.encode(alice); uint256 internal _expectedTotalSupply = 0; constructor() FoundrySuperfluidTester(3) { @@ -2372,4 +2373,62 @@ contract VestingSchedulerV2Tests is FoundrySuperfluidTester { testAssertScheduleDoesNotExist(address(superToken), alice, bob); } + + function test_getSender_throws_when_invalid_host() public { + vm.expectRevert(IVestingSchedulerV2.HostInvalid.selector); + + vm.startPrank(alice); + vestingScheduler.createVestingSchedule( + superToken, + bob, + START_DATE, + CLIFF_DATE, + FLOW_RATE, + CLIFF_TRANSFER_AMOUNT, + END_DATE, + 0, + NON_EMPTY_CTX + ); + vm.stopPrank(); + } + + function test_getSender_works_in_a_batch_call() public { + // Create a vesting schedule to update with a batch call that uses the context + vm.startPrank(alice); + vestingScheduler.createVestingSchedule( + superToken, + bob, + START_DATE, + CLIFF_DATE, + FLOW_RATE, + CLIFF_TRANSFER_AMOUNT, + END_DATE, + 0, + EMPTY_CTX + ); + _arrangeAllowances(alice, FLOW_RATE); + vm.stopPrank(); + + vm.warp(CLIFF_DATE != 0 ? CLIFF_DATE : START_DATE); + vestingScheduler.executeCliffAndFlow(superToken, alice, bob); + + uint32 newEndDate = type(uint32).max - 1234; + + // Setting up a batch call. Superfluid Protocol will replace the emtpy context with data about the sender. That's where the sender is retrieved from. + ISuperfluid.Operation[] memory ops = new ISuperfluid.Operation[](1); + ops[0] = ISuperfluid.Operation({ + operationType: BatchOperation.OPERATION_TYPE_SUPERFLUID_CALL_APP_ACTION, + target: address(vestingScheduler), + data: abi.encodeCall(vestingScheduler.updateVestingSchedule, (superToken, bob, newEndDate, EMPTY_CTX)) + }); + + // Act + vm.prank(alice); + sf.host.batchCall(ops); + vm.stopPrank(); + + // Assert + IVestingSchedulerV2.VestingSchedule memory schedule = vestingScheduler.getVestingSchedule(address(superToken), alice, bob); + assertEq(schedule.endDate, newEndDate); + } } From c1ef0704275d4bb4ff48388e1e95ddb8dac165c1 Mon Sep 17 00:00:00 2001 From: Kaspar Kallas Date: Mon, 16 Sep 2024 18:27:33 +0300 Subject: [PATCH 5/6] test whether superapp --- .../scheduler/test/VestingSchedulerV2.t.sol | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/automation-contracts/scheduler/test/VestingSchedulerV2.t.sol b/packages/automation-contracts/scheduler/test/VestingSchedulerV2.t.sol index c5794ce4a7..888f58d7df 100644 --- a/packages/automation-contracts/scheduler/test/VestingSchedulerV2.t.sol +++ b/packages/automation-contracts/scheduler/test/VestingSchedulerV2.t.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.0; import { ISuperToken } from "@superfluid-finance/ethereum-contracts/contracts/interfaces/superfluid/ISuperToken.sol"; -import { FlowOperatorDefinitions, ISuperfluid, BatchOperation } from "@superfluid-finance/ethereum-contracts/contracts/interfaces/superfluid/ISuperfluid.sol"; +import { FlowOperatorDefinitions, ISuperfluid, BatchOperation, ISuperApp } from "@superfluid-finance/ethereum-contracts/contracts/interfaces/superfluid/ISuperfluid.sol"; import { IVestingSchedulerV2 } from "./../contracts/interface/IVestingSchedulerV2.sol"; import { VestingSchedulerV2 } from "./../contracts/VestingSchedulerV2.sol"; import { FoundrySuperfluidTester } from "@superfluid-finance/ethereum-contracts/test/foundry/FoundrySuperfluidTester.sol"; @@ -277,6 +277,10 @@ contract VestingSchedulerV2Tests is FoundrySuperfluidTester { /// TESTS + function test_vesting_scheduler_is_superapp() public { + assertTrue(sf.host.isApp(ISuperApp(address(new VestingSchedulerV2(sf.host))))); + } + function testCreateVestingSchedule() public { vm.expectEmit(true, true, true, true); emit VestingScheduleCreated( From e893a9613e866fb4b912f31c8d3d757808c4db96 Mon Sep 17 00:00:00 2001 From: Kaspar Kallas Date: Tue, 17 Sep 2024 14:23:16 +0300 Subject: [PATCH 6/6] fix fuzz tests (by Pierre) --- .../VestingSchedulerV2.StatefulFuzz.t.sol | 42 +++++++++---------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/packages/automation-contracts/scheduler/test/VestingSchedulerV2.StatefulFuzz.t.sol b/packages/automation-contracts/scheduler/test/VestingSchedulerV2.StatefulFuzz.t.sol index ab77e3a6ab..62ffa95dc2 100644 --- a/packages/automation-contracts/scheduler/test/VestingSchedulerV2.StatefulFuzz.t.sol +++ b/packages/automation-contracts/scheduler/test/VestingSchedulerV2.StatefulFuzz.t.sol @@ -7,7 +7,7 @@ import {IVestingSchedulerV2} from "./../contracts/interface/IVestingSchedulerV2. import {VestingSchedulerV2} from "./../contracts/VestingSchedulerV2.sol"; import {FoundrySuperfluidTester} from "@superfluid-finance/ethereum-contracts/test/foundry/FoundrySuperfluidTester.sol"; import {SuperTokenV1Library} from "@superfluid-finance/ethereum-contracts/contracts/apps/SuperTokenV1Library.sol"; -import "forge-std/console.sol"; +import "forge-std/console2.sol"; /// @title VestingSchedulerTests contract VestingSchedulerV2StatefulFuzzTests is FoundrySuperfluidTester { @@ -17,13 +17,15 @@ contract VestingSchedulerV2StatefulFuzzTests is FoundrySuperfluidTester { VestingSchedulerV2 public vestingScheduler; /// @dev Constants for Testing - uint256 constant MIN_CLIFF_AMOUNT = 1; + uint256 constant MIN_CLIFF_AMOUNT = 2; uint256 constant MAX_CLIFF_AMOUNT = 1_000e18; int96 constant MIN_FLOW_RATE = 1; int96 constant MAX_FLOW_RATE = 1_000e18; bytes constant EMPTY_CTX = ""; - constructor() FoundrySuperfluidTester(0) { + uint8 constant numOfTesters = 5; + + constructor() FoundrySuperfluidTester(numOfTesters) { vestingScheduler = new VestingSchedulerV2(sf.host); } @@ -44,7 +46,7 @@ contract VestingSchedulerV2StatefulFuzzTests is FoundrySuperfluidTester { struct Actions { uint8 actionCode; - address sender; + uint256 testerId; address receiver; uint32 startDate; uint32 cliffDate; @@ -61,24 +63,22 @@ contract VestingSchedulerV2StatefulFuzzTests is FoundrySuperfluidTester { return TestActionType(actionCode % maxAction); } - function test_StatefulFuzz(Actions[10] calldata actions) public { + function test_StatefulFuzz(Actions[10] calldata actions) external { for (uint256 i = 0; i < actions.length; i++) { + console2.log("ITERATION ID : %d", i); Actions memory a = actions[i]; TestActionType t = toActionType(a.actionCode, 5); + + a.testerId = bound(a.testerId, 0, 4); + address sender = TEST_ACCOUNTS[a.testerId]; + vm.assume( vestingScheduler - .getVestingSchedule( - address(superToken), - a.sender, - a.receiver - ) + .getVestingSchedule(address(superToken), sender, a.receiver) .flowRate == 0 ); - vm.assume( - a.sender != address(0) && - a.receiver != address(0) && - a.sender != a.receiver - ); + + vm.assume(a.receiver != address(0) && sender != a.receiver); a.flowRate = int96(bound(a.flowRate, 1, 1000e18)); a.startDate = uint32( bound( @@ -118,7 +118,7 @@ contract VestingSchedulerV2StatefulFuzzTests is FoundrySuperfluidTester { if (t == TestActionType.TA_CREATE_SCHEDULE) { _test_createVestingSchedule( - a.sender, + sender, a.receiver, a.startDate, a.cliffDate, @@ -128,7 +128,7 @@ contract VestingSchedulerV2StatefulFuzzTests is FoundrySuperfluidTester { ); } else if (t == TestActionType.TA_UPDATE_SCHEDULE) { _test_updateVestingSchedule( - a.sender, + sender, a.receiver, a.startDate, a.cliffDate, @@ -139,7 +139,7 @@ contract VestingSchedulerV2StatefulFuzzTests is FoundrySuperfluidTester { ); } else if (t == TestActionType.TA_DELETE_SCHEDULE) { _test_deleteVestingSchedule( - a.sender, + sender, a.receiver, a.startDate, a.cliffDate, @@ -149,7 +149,7 @@ contract VestingSchedulerV2StatefulFuzzTests is FoundrySuperfluidTester { ); } else if (t == TestActionType.TA_EXECUTE_SCHEDULE) { _test_executeCliffAndFlow( - a.sender, + sender, a.receiver, a.startDate, a.cliffDate, @@ -159,7 +159,7 @@ contract VestingSchedulerV2StatefulFuzzTests is FoundrySuperfluidTester { ); } else if (t == TestActionType.TA_TERMINATE_SCHEDULE) { _test_executeEndVesting( - a.sender, + sender, a.receiver, a.startDate, a.cliffDate, @@ -430,4 +430,4 @@ contract VestingSchedulerV2StatefulFuzzTests is FoundrySuperfluidTester { superToken.approve(address(vestingScheduler), type(uint256).max); vm.stopPrank(); } -} +} \ No newline at end of file