From 4f4d796f39586a97848e41f76820663f6362f598 Mon Sep 17 00:00:00 2001 From: 0xdavinchee <0xdavinchee@gmail.com> Date: Tue, 14 Nov 2023 14:26:33 +0200 Subject: [PATCH] [ETHEREUM-CONTRACTS] App credit test (#1743) * add app credit sanity test * cleanup console.logs --- .../contracts/mocks/CrossStreamSuperApp.sol | 46 ++++++++++ .../test/foundry/FoundrySuperfluidTester.sol | 4 - .../foundry/apps/CrossStreamSuperApp.t.sol | 90 +++++++++++++++++++ 3 files changed, 136 insertions(+), 4 deletions(-) create mode 100644 packages/ethereum-contracts/contracts/mocks/CrossStreamSuperApp.sol create mode 100644 packages/ethereum-contracts/test/foundry/apps/CrossStreamSuperApp.t.sol diff --git a/packages/ethereum-contracts/contracts/mocks/CrossStreamSuperApp.sol b/packages/ethereum-contracts/contracts/mocks/CrossStreamSuperApp.sol new file mode 100644 index 0000000000..06cb22df2e --- /dev/null +++ b/packages/ethereum-contracts/contracts/mocks/CrossStreamSuperApp.sol @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: AGPLv3 +pragma solidity 0.8.19; + +import { ISuperfluid, ISuperToken } from "../interfaces/superfluid/ISuperfluid.sol"; +import { SuperAppBaseFlow } from "../apps/SuperAppBaseFlow.sol"; +import { SuperTokenV1Library } from "../apps/SuperTokenV1Library.sol"; + +using SuperTokenV1Library for ISuperToken; + +/// @title CrossStreamSuperApp +/// @author Superfluid +/// @dev A super app used for testing "cross-stream" flows in callbacks +/// and its behavior surrounding the internal protocol accounting. +/// That is, two senders sending a flow to the super app +contract CrossStreamSuperApp is SuperAppBaseFlow { + address public flowRecipient; + address public prevSender; + int96 public prevFlowRate; + + constructor(ISuperfluid host_, address z_) SuperAppBaseFlow(host_, true, true, true, "") { + flowRecipient = z_; + } + + function onFlowCreated(ISuperToken superToken, address sender, bytes calldata ctx) + internal + override + returns (bytes memory newCtx) + { + newCtx = ctx; + + // get incoming stream + int96 inFlowRate = superToken.getFlowRate(sender, address(this)); + + if (prevSender == address(0)) { + // first flow to super app creates a flow + newCtx = superToken.createFlowWithCtx(flowRecipient, inFlowRate, newCtx); + } else { + // subsequent flows to super app updates and deletes the flow + newCtx = superToken.updateFlowWithCtx(flowRecipient, inFlowRate, newCtx); + newCtx = superToken.deleteFlowWithCtx(prevSender, address(this), newCtx); + } + + prevSender = sender; + prevFlowRate = inFlowRate; + } +} diff --git a/packages/ethereum-contracts/test/foundry/FoundrySuperfluidTester.sol b/packages/ethereum-contracts/test/foundry/FoundrySuperfluidTester.sol index 12939650d1..6c4e805903 100644 --- a/packages/ethereum-contracts/test/foundry/FoundrySuperfluidTester.sol +++ b/packages/ethereum-contracts/test/foundry/FoundrySuperfluidTester.sol @@ -264,10 +264,6 @@ contract FoundrySuperfluidTester is Test { function _definitionLiquiditySumInvariant() internal view returns (bool) { int256 liquiditySum = _helperGetSuperTokenLiquiditySum(superToken); - console.log("_expectedTotalSupply"); - console.log(_expectedTotalSupply); - console.log("liquiditySum"); - console.logInt(liquiditySum); return int256(_expectedTotalSupply) == liquiditySum; } diff --git a/packages/ethereum-contracts/test/foundry/apps/CrossStreamSuperApp.t.sol b/packages/ethereum-contracts/test/foundry/apps/CrossStreamSuperApp.t.sol new file mode 100644 index 0000000000..f92aa8e053 --- /dev/null +++ b/packages/ethereum-contracts/test/foundry/apps/CrossStreamSuperApp.t.sol @@ -0,0 +1,90 @@ +// SPDX-License-Identifier: AGPLv3 +pragma solidity 0.8.19; + +import { CrossStreamSuperApp } from "../../../contracts/mocks/CrossStreamSuperApp.sol"; +import { SuperTokenV1Library } from "../../../contracts/apps/SuperTokenV1Library.sol"; +import { ISuperToken } from "../../../contracts/interfaces/superfluid/ISuperfluid.sol"; + +import { FoundrySuperfluidTester } from "../FoundrySuperfluidTester.sol"; + +import "forge-std/Test.sol"; + +using SuperTokenV1Library for ISuperToken; + +contract CrossStreamSuperAppTest is FoundrySuperfluidTester { + CrossStreamSuperApp public superApp; + + constructor() FoundrySuperfluidTester(5) { } + + function setUp() public override { + super.setUp(); + + superApp = new CrossStreamSuperApp(sf.host, bob); + _addAccount(address(superApp)); + } + + function testNoTokensMintedOrBurnedInCrossStreamSuperApp(int96 flowRate, uint64 blockTimestamp) public { + vm.assume(flowRate < 1e14); + // @note due to clipping, there is precision loss, therefore if the flow rate is too low + // tokens will be unrecoverable + vm.assume(flowRate > 2 ** 32 - 1); + int96 initialFlowRate = flowRate; + + uint256 balance = superToken.balanceOf(alice); + + uint256 amountOfTimeTillZero = balance / uint256(uint96(initialFlowRate)); + vm.assume(blockTimestamp < amountOfTimeTillZero); + + int256 rtbSumStart = _helperGetSuperTokenLiquiditySum(superToken); + + // send an initial deposit to the super app to allow the test to pass + uint256 deposit = sf.cfa.getDepositRequiredForFlowRate(superToken, initialFlowRate); + vm.startPrank(carol); + superToken.transfer(address(superApp), deposit); + vm.stopPrank(); + + // @note We are mainly concerned with _definitionAumGtEqRtbSumInvariant holding true: + // Assert the global invariants at every step. + _assertGlobalInvariants(); + + // create the first flow from alice -> super app -> bob + vm.startPrank(alice); + superToken.createFlow(address(superApp), initialFlowRate); + vm.stopPrank(); + + _assertGlobalInvariants(); + + uint256 bt = block.timestamp; + + vm.warp(bt + blockTimestamp); + + assertEq(initialFlowRate, superToken.getFlowRate(alice, address(superApp))); + + // create the second flow from dan -> super app -> bob + // this first: updates the flow from super app -> bob to equal to the new inflowRate (dan flow rateƄ) + // then we delete the flow from alice -> super app, super app executes this deletion + vm.startPrank(dan); + int96 updatedFlowRate = initialFlowRate * 2; + superToken.createFlow(address(superApp), updatedFlowRate); + vm.stopPrank(); + + _assertGlobalInvariants(); + + // finally we close the stream from dan => super app and super app => bob + vm.startPrank(dan); + superToken.deleteFlow(dan, address(superApp)); + vm.stopPrank(); + + _assertGlobalInvariants(); + + vm.startPrank(bob); + superToken.deleteFlow(address(superApp), bob); + vm.stopPrank(); + + _assertGlobalInvariants(); + + int256 rtbSumEnd = _helperGetSuperTokenLiquiditySum(superToken); + + assertEq(rtbSumStart, rtbSumEnd, "rtbSumStart != rtbSumEnd"); + } +}