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 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..62ffa95dc2 --- /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/console2.sol"; + +/// @title VestingSchedulerTests +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 = 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 = ""; + + uint8 constant numOfTesters = 5; + + constructor() FoundrySuperfluidTester(numOfTesters) { + vestingScheduler = new VestingSchedulerV2(sf.host); + } + + /// SETUP AND HELPERS + function setUp() public virtual override { + vm.setEnv(TOKEN_TYPE_ENV_KEY, "WRAPPER_SUPER_TOKEN"); + 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; + uint256 testerId; + 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) 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), sender, a.receiver) + .flowRate == 0 + ); + + vm.assume(a.receiver != address(0) && 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( + sender, + a.receiver, + a.startDate, + a.cliffDate, + a.flowRate, + a.cliffAmount, + a.endDate + ); + } else if (t == TestActionType.TA_UPDATE_SCHEDULE) { + _test_updateVestingSchedule( + sender, + a.receiver, + a.startDate, + a.cliffDate, + a.flowRate, + a.cliffAmount, + a.endDate, + a.newEndDate + ); + } else if (t == TestActionType.TA_DELETE_SCHEDULE) { + _test_deleteVestingSchedule( + sender, + a.receiver, + a.startDate, + a.cliffDate, + a.flowRate, + a.cliffAmount, + a.endDate + ); + } else if (t == TestActionType.TA_EXECUTE_SCHEDULE) { + _test_executeCliffAndFlow( + sender, + a.receiver, + a.startDate, + a.cliffDate, + a.flowRate, + a.cliffAmount, + a.endDate + ); + } else if (t == TestActionType.TA_TERMINATE_SCHEDULE) { + _test_executeEndVesting( + 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(); + } +} \ No newline at end of file diff --git a/packages/automation-contracts/scheduler/test/VestingSchedulerV2.t.sol b/packages/automation-contracts/scheduler/test/VestingSchedulerV2.t.sol index 1dd9086899..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 } 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"; @@ -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) { @@ -276,7 +277,37 @@ 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( + 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 +1400,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 +2273,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, @@ -2280,4 +2377,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); + } } diff --git a/packages/ethereum-contracts/CHANGELOG.md b/packages/ethereum-contracts/CHANGELOG.md index eaf48fb06a..61135dd11a 100644 --- a/packages/ethereum-contracts/CHANGELOG.md +++ b/packages/ethereum-contracts/CHANGELOG.md @@ -8,6 +8,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ### Changed * `IUserDefinedMacro`: added a method `postCheck()` which allows to verify state changes after running the macro. +* `SuperfluidFrameworkDeployer` now also deploys and `MacroForwarder` and enables it as trusted forwarder. ### Fixed diff --git a/packages/ethereum-contracts/contracts/utils/SuperfluidFrameworkDeploymentSteps.sol b/packages/ethereum-contracts/contracts/utils/SuperfluidFrameworkDeploymentSteps.sol index 7d15408dd4..a8c769b773 100644 --- a/packages/ethereum-contracts/contracts/utils/SuperfluidFrameworkDeploymentSteps.sol +++ b/packages/ethereum-contracts/contracts/utils/SuperfluidFrameworkDeploymentSteps.sol @@ -33,6 +33,7 @@ import { CFAv1Library } from "../apps/CFAv1Library.sol"; import { IDAv1Library } from "../apps/IDAv1Library.sol"; import { IResolver } from "../interfaces/utils/IResolver.sol"; import { DMZForwarder } from "../utils/DMZForwarder.sol"; +import { MacroForwarder } from "../utils/MacroForwarder.sol"; /// @title Superfluid Framework Deployment Steps /// @author Superfluid @@ -66,6 +67,7 @@ contract SuperfluidFrameworkDeploymentSteps { CFAv1Forwarder cfaV1Forwarder; IDAv1Forwarder idaV1Forwarder; GDAv1Forwarder gdaV1Forwarder; + MacroForwarder macroForwarder; BatchLiquidator batchLiquidator; TOGA toga; } @@ -92,6 +94,7 @@ contract SuperfluidFrameworkDeploymentSteps { CFAv1Forwarder internal cfaV1Forwarder; IDAv1Forwarder internal idaV1Forwarder; GDAv1Forwarder internal gdaV1Forwarder; + MacroForwarder internal macroForwarder; // Other Peripheral Contracts TestResolver internal testResolver; @@ -121,6 +124,7 @@ contract SuperfluidFrameworkDeploymentSteps { cfaV1Forwarder: cfaV1Forwarder, idaV1Forwarder: idaV1Forwarder, gdaV1Forwarder: gdaV1Forwarder, + macroForwarder: macroForwarder, batchLiquidator: batchLiquidator, toga: toga }); @@ -224,6 +228,10 @@ contract SuperfluidFrameworkDeploymentSteps { // Deploy GDAv1Forwarder gdaV1Forwarder = GDAv1ForwarderDeployerLibrary.deploy(host); testGovernance.enableTrustedForwarder(host, ISuperfluidToken(address(0)), address(gdaV1Forwarder)); + + // Deploy MacroForwarder + macroForwarder = new MacroForwarder(host); + testGovernance.enableTrustedForwarder(host, ISuperfluidToken(address(0)), address(macroForwarder)); } else if (step == 5) {// PERIPHERAL CONTRACTS: SuperToken Logic and SuperTokenFactory Logic // Deploy canonical SuperToken logic contract superTokenLogic = SuperToken(SuperTokenDeployerLibrary.deploy( diff --git a/packages/ethereum-contracts/test/foundry/SuperfluidFrameworkDeployer.t.sol b/packages/ethereum-contracts/test/foundry/SuperfluidFrameworkDeployer.t.sol index 250b0367f8..abbfc3c1c9 100644 --- a/packages/ethereum-contracts/test/foundry/SuperfluidFrameworkDeployer.t.sol +++ b/packages/ethereum-contracts/test/foundry/SuperfluidFrameworkDeployer.t.sol @@ -21,6 +21,7 @@ contract SuperfluidFrameworkDeployerTest is FoundrySuperfluidTester { assertTrue(address(sf.cfaV1Forwarder) != address(0), "SFDeployer: cfaV1Forwarder not deployed"); assertTrue(address(sf.idaV1Forwarder) != address(0), "SFDeployer: idaV1Forwarder not deployed"); assertTrue(address(sf.gdaV1Forwarder) != address(0), "SFDeployer: gdaV1Forwarder not deployed"); + assertTrue(address(sf.macroForwarder) != address(0), "SFDeployer: macroForwarder not deployed"); assertTrue(address(sf.batchLiquidator) != address(0), "SFDeployer: batchLiquidator not deployed"); } diff --git a/packages/ethereum-contracts/test/foundry/utils/MacroForwarder.t.sol b/packages/ethereum-contracts/test/foundry/utils/MacroForwarder.t.sol index ac72df5219..f4b5d05cda 100644 --- a/packages/ethereum-contracts/test/foundry/utils/MacroForwarder.t.sol +++ b/packages/ethereum-contracts/test/foundry/utils/MacroForwarder.t.sol @@ -74,7 +74,7 @@ contract GoodMacro is IUserDefinedMacro { contract MultiFlowDeleteMacro is IUserDefinedMacro { error InsufficientReward(); - function buildBatchOperations(ISuperfluid host, bytes memory params, address msgSender) external override view + function buildBatchOperations(ISuperfluid host, bytes memory params, address /*msgSender*/) external override view returns (ISuperfluid.Operation[] memory operations) { IConstantFlowAgreementV1 cfa = IConstantFlowAgreementV1(address(host.getAgreementClass( @@ -82,7 +82,7 @@ contract MultiFlowDeleteMacro is IUserDefinedMacro { ))); // parse params - (ISuperToken token, address sender, address[] memory receivers, uint256 minBalanceAfter) = + (ISuperToken token, address sender, address[] memory receivers,) = abi.decode(params, (ISuperToken, address, address[], uint256)); // construct batch operations @@ -110,7 +110,7 @@ contract MultiFlowDeleteMacro is IUserDefinedMacro { return abi.encode(superToken, sender, receivers, minBalanceAfter); } - function postCheck(ISuperfluid host, bytes memory params, address msgSender) external view { + function postCheck(ISuperfluid /*host*/, bytes memory params, address msgSender) external view { // parse params (ISuperToken superToken,,, uint256 minBalanceAfter) = abi.decode(params, (ISuperToken, address, address[], uint256)); @@ -172,29 +172,19 @@ contract StatefulMacro is IUserDefinedMacro { // ============== Test Contract ============== contract MacroForwarderTest is FoundrySuperfluidTester { - MacroForwarder internal macroForwarder; - constructor() FoundrySuperfluidTester(5) { } - function setUp() public override { - super.setUp(); - macroForwarder = new MacroForwarder(sf.host); - vm.startPrank(address(sf.governance.owner())); - sf.governance.enableTrustedForwarder(sf.host, ISuperToken(address(0)), address(macroForwarder)); - vm.stopPrank(); - } - function testDummyMacro() external { NaugthyMacro m = new NaugthyMacro(false /* not naughty */); - macroForwarder.runMacro(IUserDefinedMacro(address(m)), new bytes(0)); + sf.macroForwarder.runMacro(IUserDefinedMacro(address(m)), new bytes(0)); } function testNaugtyMacro() external { NaugthyMacro m = new NaugthyMacro(true /* naughty */); vm.expectRevert(); // Note: need to cast the naughty macro - macroForwarder.runMacro(IUserDefinedMacro(address(m)), new bytes(0)); + sf.macroForwarder.runMacro(IUserDefinedMacro(address(m)), new bytes(0)); } function testGoodMacro() external { @@ -205,7 +195,7 @@ contract MacroForwarderTest is FoundrySuperfluidTester { vm.startPrank(admin); // NOTE! This is different from abi.encode(superToken, int96(42), [bob, carol]), // which is a fixed array: address[2]. - macroForwarder.runMacro(m, abi.encode(superToken, int96(42), recipients)); + sf.macroForwarder.runMacro(m, abi.encode(superToken, int96(42), recipients)); assertEq(sf.cfa.getNetFlow(superToken, bob), 42); assertEq(sf.cfa.getNetFlow(superToken, carol), 42); vm.stopPrank(); @@ -219,7 +209,7 @@ contract MacroForwarderTest is FoundrySuperfluidTester { vm.startPrank(admin); // NOTE! This is different from abi.encode(superToken, int96(42), [bob, carol]), // which is a fixed array: address[2]. - macroForwarder.runMacro(m, m.getParams(superToken, int96(42), recipients)); + sf.macroForwarder.runMacro(m, m.getParams(superToken, int96(42), recipients)); assertEq(sf.cfa.getNetFlow(superToken, bob), 42); assertEq(sf.cfa.getNetFlow(superToken, carol), 42); vm.stopPrank(); @@ -231,10 +221,10 @@ contract MacroForwarderTest is FoundrySuperfluidTester { recipients[1] = carol; StatefulMacro m = new StatefulMacro(); m.setConfig(StatefulMacro.Config( - macroForwarder, superToken, 42, recipients, dan + sf.macroForwarder, superToken, 42, recipients, dan )); vm.startPrank(admin); - macroForwarder.runMacro(m, new bytes(0)); + sf.macroForwarder.runMacro(m, new bytes(0)); assertEq(sf.cfa.getNetFlow(superToken, bob), 42); assertEq(sf.cfa.getNetFlow(superToken, carol), 42); vm.stopPrank(); @@ -254,7 +244,7 @@ contract MacroForwarderTest is FoundrySuperfluidTester { superToken.createFlow(recipients[i], 42); } // now batch-delete them - macroForwarder.runMacro(m, m.getParams(superToken, sender, recipients, 0)); + sf.macroForwarder.runMacro(m, m.getParams(superToken, sender, recipients, 0)); for (uint i = 0; i < recipients.length; ++i) { assertEq(sf.cfa.getNetFlow(superToken, recipients[i]), 0); @@ -284,9 +274,9 @@ contract MacroForwarderTest is FoundrySuperfluidTester { uint256 danBalanceBefore = superToken.balanceOf(dan); // unreasonable reward expectation: post check fails vm.expectRevert(MultiFlowDeleteMacro.InsufficientReward.selector); - macroForwarder.runMacro(m, abi.encode(superToken, alice, recipients, danBalanceBefore + 1e24)); + sf.macroForwarder.runMacro(m, abi.encode(superToken, alice, recipients, danBalanceBefore + 1e24)); // reasonable reward expectation: post check passes - macroForwarder.runMacro(m, abi.encode(superToken, alice, recipients, danBalanceBefore + (uint256(uint96(flowRate)) * 600))); + sf.macroForwarder.runMacro(m, abi.encode(superToken, alice, recipients, danBalanceBefore + (uint256(uint96(flowRate)) * 600))); } } \ No newline at end of file