From c163b35847e4b92b01cefb830670045e811c03aa Mon Sep 17 00:00:00 2001 From: didi Date: Mon, 24 Jun 2024 17:49:24 +0200 Subject: [PATCH 01/35] added batch call operations for upgradeTo and downgradeTo --- .../interfaces/superfluid/Definitions.sol | 18 +++++ .../interfaces/superfluid/ISuperToken.sol | 21 ++++++ .../contracts/superfluid/SuperToken.sol | 14 ++++ .../contracts/superfluid/Superfluid.sol | 14 +++- .../superfluid/Superfluid.BatchCall.t.sol | 75 +++++++++++++++++++ 5 files changed, 141 insertions(+), 1 deletion(-) diff --git a/packages/ethereum-contracts/contracts/interfaces/superfluid/Definitions.sol b/packages/ethereum-contracts/contracts/interfaces/superfluid/Definitions.sol index f9687901a1..554348fd25 100644 --- a/packages/ethereum-contracts/contracts/interfaces/superfluid/Definitions.sol +++ b/packages/ethereum-contracts/contracts/interfaces/superfluid/Definitions.sol @@ -192,6 +192,24 @@ library BatchOperation { * ) */ uint32 constant internal OPERATION_TYPE_SUPERTOKEN_DOWNGRADE = 2 + 100; + /** + * @dev SuperToken.upgradeTo batch operation type + * + * Call spec: + * ISuperToken(target).operationUpgradeTo( + * abi.decode(data, (address to, uint256 amount) + * ) + */ + uint32 constant internal OPERATION_TYPE_SUPERTOKEN_UPGRADE_TO = 3 + 100; + /** + * @dev SuperToken.downgradeTo batch operation type + * + * Call spec: + * ISuperToken(target).operationDowngradeTo( + * abi.decode(data, (address to, uint256 amount) + * ) + */ + uint32 constant internal OPERATION_TYPE_SUPERTOKEN_DOWNGRADE_TO = 4 + 100; /** * @dev Superfluid.callAgreement batch operation type * diff --git a/packages/ethereum-contracts/contracts/interfaces/superfluid/ISuperToken.sol b/packages/ethereum-contracts/contracts/interfaces/superfluid/ISuperToken.sol index ef62a0e93c..43d609b279 100644 --- a/packages/ethereum-contracts/contracts/interfaces/superfluid/ISuperToken.sol +++ b/packages/ethereum-contracts/contracts/interfaces/superfluid/ISuperToken.sol @@ -578,6 +578,27 @@ interface ISuperToken is ISuperfluidToken, IERC20Metadata, IERC777 { */ function operationDowngrade(address account, uint256 amount) external; + /** + * @dev Upgrade ERC20 to SuperToken by host contract and transfer immediately. + * @param account The account to be changed. + * @param amount Number of tokens to be upgraded (in 18 decimals) + * + * @custom:modifiers + * - onlyHost + */ + function operationUpgradeTo(address account, address to, uint256 amount) external; + + /** + * @dev Downgrade ERC20 to SuperToken by host contract and transfer immediately. + * @param account The account to be changed. + * @param to The account to receive upgraded tokens + * @param amount Number of tokens to be downgraded (in 18 decimals) + * + * @custom:modifiers + * - onlyHost + */ + function operationDowngradeTo(address account, address to, uint256 amount) external; + // Flow NFT events /** * @dev Constant Outflow NFT proxy created event diff --git a/packages/ethereum-contracts/contracts/superfluid/SuperToken.sol b/packages/ethereum-contracts/contracts/superfluid/SuperToken.sol index d1aaee5b55..b2eeee0717 100644 --- a/packages/ethereum-contracts/contracts/superfluid/SuperToken.sol +++ b/packages/ethereum-contracts/contracts/superfluid/SuperToken.sol @@ -882,6 +882,20 @@ contract SuperToken is _downgrade(msg.sender, account, account, amount, "", ""); } + function operationUpgradeTo(address account, address to, uint256 amount) + external virtual override + onlyHost + { + _upgrade(msg.sender, account, to, amount, "", ""); + } + + function operationDowngradeTo(address account, address to, uint256 amount) + external virtual override + onlyHost + { + _downgrade(msg.sender, account, to, amount, "", ""); + } + /************************************************************************** * Modifiers *************************************************************************/ diff --git a/packages/ethereum-contracts/contracts/superfluid/Superfluid.sol b/packages/ethereum-contracts/contracts/superfluid/Superfluid.sol index 29ef49be95..d0cb6108c5 100644 --- a/packages/ethereum-contracts/contracts/superfluid/Superfluid.sol +++ b/packages/ethereum-contracts/contracts/superfluid/Superfluid.sol @@ -312,7 +312,7 @@ contract Superfluid is //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Superfluid Upgradeable Beacon //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - + /// @inheritdoc ISuperfluid function updatePoolBeaconLogic(address newLogic) external override onlyGovernance { GeneralDistributionAgreementV1 gda = GeneralDistributionAgreementV1( @@ -847,6 +847,18 @@ contract Superfluid is ISuperToken(operations[i].target).operationDowngrade( msgSender, abi.decode(operations[i].data, (uint256))); // amount + } else if (operationType == BatchOperation.OPERATION_TYPE_SUPERTOKEN_UPGRADE_TO) { + (address to, uint256 amount) = abi.decode(operations[i].data, (address, uint256)); + ISuperToken(operations[i].target).operationUpgradeTo( + msgSender, + to, + amount); + } else if (operationType == BatchOperation.OPERATION_TYPE_SUPERTOKEN_DOWNGRADE_TO) { + (address to, uint256 amount) = abi.decode(operations[i].data, (address, uint256)); + ISuperToken(operations[i].target).operationDowngradeTo( + msgSender, + to, + amount); } else if (operationType == BatchOperation.OPERATION_TYPE_SUPERFLUID_CALL_AGREEMENT) { (bytes memory callData, bytes memory userData) = abi.decode(operations[i].data, (bytes, bytes)); _callAgreement( diff --git a/packages/ethereum-contracts/test/foundry/superfluid/Superfluid.BatchCall.t.sol b/packages/ethereum-contracts/test/foundry/superfluid/Superfluid.BatchCall.t.sol index 3c1a17fa71..2a08f56ef5 100644 --- a/packages/ethereum-contracts/test/foundry/superfluid/Superfluid.BatchCall.t.sol +++ b/packages/ethereum-contracts/test/foundry/superfluid/Superfluid.BatchCall.t.sol @@ -208,4 +208,79 @@ contract SuperfluidBatchCallTest is FoundrySuperfluidTester { vm.expectRevert("CallUtils: target revert()"); sf.host.batchCall{value: 42}(ops); } + + function testRevertIfOperationUpgradeToIsNotCalledByHost(address notHost) public { + vm.assume(notHost != address(sf.host)); + + vm.expectRevert(ISuperfluidToken.SF_TOKEN_ONLY_HOST.selector); + vm.prank(notHost); + superToken.operationUpgradeTo(alice, bob, 100); + } + + function testUpgradeTo(uint256 amount) public { + vm.assume(amount < type(uint64).max); + + vm.prank(alice); + token.approve(address(superToken), amount); + + uint256 bobBalanceBefore = superToken.balanceOf(bob); + vm.prank(address(sf.host)); + superToken.operationUpgradeTo(alice, bob, amount); + uint256 bobBalanceAfter = superToken.balanceOf(bob); + assertEq(bobBalanceAfter, bobBalanceBefore + amount, "Bob has unexpected final balance"); + } + + function testUpgradeToBatchCall(uint256 amount) public { + vm.assume(amount < type(uint64).max); + + vm.prank(alice); + token.approve(address(superToken), amount); + + ISuperfluid.Operation[] memory ops = new ISuperfluid.Operation[](1); + uint256 bobBalanceBefore = superToken.balanceOf(bob); + ops[0] = ISuperfluid.Operation({ + operationType: BatchOperation.OPERATION_TYPE_SUPERTOKEN_UPGRADE_TO, + target: address(superToken), + data: abi.encode(bob, amount) + }); + vm.prank(alice); + sf.host.batchCall(ops); + uint256 bobBalanceAfter = superToken.balanceOf(bob); + assertEq(bobBalanceAfter, bobBalanceBefore + amount, "Bob has unexpected final balance"); + } + + function testRevertIfOperationDowngradeToIsNotCalledByHost(address notHost) public { + vm.assume(notHost != address(sf.host)); + + vm.expectRevert(ISuperfluidToken.SF_TOKEN_ONLY_HOST.selector); + vm.prank(notHost); + superToken.operationDowngradeTo(alice, bob, 100); + } + + function testDowngradeTo(uint256 amount) public { + vm.assume(amount < type(uint64).max); + + uint256 bobBalanceBefore = token.balanceOf(bob); + vm.prank(address(sf.host)); + superToken.operationDowngradeTo(alice, bob, amount); + uint256 bobBalanceAfter = token.balanceOf(bob); + assertEq(bobBalanceAfter, bobBalanceBefore + amount, "Bob has unexpected final balance"); + } + + function testDowngradeToBatchCall(uint256 amount) public { + vm.assume(amount < type(uint64).max); + + ISuperfluid.Operation[] memory ops = new ISuperfluid.Operation[](1); + uint256 bobBalanceBefore = token.balanceOf(bob); + ops[0] = ISuperfluid.Operation({ + operationType: BatchOperation.OPERATION_TYPE_SUPERTOKEN_DOWNGRADE_TO, + target: address(superToken), + data: abi.encode(bob, amount) + }); + vm.prank(alice); + sf.host.batchCall(ops); + uint256 bobBalanceAfter = token.balanceOf(bob); + assertEq(bobBalanceAfter, bobBalanceBefore + amount, "Bob has unexpected final balance"); + } + } From 947699e846e96c42967ce8c525c2706473638cee Mon Sep 17 00:00:00 2001 From: didi Date: Mon, 24 Jun 2024 18:34:30 +0200 Subject: [PATCH 02/35] added test case for connecting a pool via batch call --- .../superfluid/Superfluid.BatchCall.t.sol | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/packages/ethereum-contracts/test/foundry/superfluid/Superfluid.BatchCall.t.sol b/packages/ethereum-contracts/test/foundry/superfluid/Superfluid.BatchCall.t.sol index 2a08f56ef5..ae43ed17ce 100644 --- a/packages/ethereum-contracts/test/foundry/superfluid/Superfluid.BatchCall.t.sol +++ b/packages/ethereum-contracts/test/foundry/superfluid/Superfluid.BatchCall.t.sol @@ -6,6 +6,7 @@ import { stdError } from "forge-std/Test.sol"; import { BatchOperation, ISuperfluid, Superfluid } from "../../../contracts/superfluid/Superfluid.sol"; import { SuperToken } from "../../../contracts/superfluid/SuperToken.sol"; import { IConstantFlowAgreementV1 } from "../../../contracts/interfaces/agreements/IConstantFlowAgreementV1.sol"; +import { IGeneralDistributionAgreementV1, ISuperfluidPool, PoolConfig } from "../../../contracts/interfaces/agreements/gdav1/IGeneralDistributionAgreementV1.sol"; import { ISuperfluidToken } from "../../../contracts/interfaces/superfluid/ISuperfluidToken.sol"; import { FoundrySuperfluidTester } from "../FoundrySuperfluidTester.sol"; import { SuperTokenV1Library } from "../../../contracts/apps/SuperTokenV1Library.sol"; @@ -283,4 +284,22 @@ contract SuperfluidBatchCallTest is FoundrySuperfluidTester { assertEq(bobBalanceAfter, bobBalanceBefore + amount, "Bob has unexpected final balance"); } + function testCallAgreementConnectPoolBatchCall() public { + PoolConfig memory config = PoolConfig({ transferabilityForUnitsOwner: true, distributionFromAnyAddress: false }); + ISuperfluidPool pool = _helperCreatePool(superToken, alice, alice, false, config); + + ISuperfluid.Operation[] memory ops = new ISuperfluid.Operation[](1); + bytes memory connectPoolCallData = + abi.encodeCall(IGeneralDistributionAgreementV1.connectPool, (pool, new bytes(0))); + ops[0] = ISuperfluid.Operation({ + operationType: BatchOperation.OPERATION_TYPE_SUPERFLUID_CALL_AGREEMENT, + target: address(sf.gda), + data: abi.encode(connectPoolCallData, new bytes(0)) + }); + + vm.prank(alice); + sf.host.batchCall(ops); + + assertTrue(sf.gda.isMemberConnected(pool, alice), "Alice: Pool is not connected"); + } } From 84bd23eb4df13d93e58c9a9e539e447d36c0e79d Mon Sep 17 00:00:00 2001 From: didi Date: Mon, 24 Jun 2024 21:33:36 +0200 Subject: [PATCH 03/35] added OPERATION_TYPE_SIMPLE_FORWARD_CALL --- .../interfaces/superfluid/Definitions.sol | 10 +++ .../contracts/superfluid/Superfluid.sol | 10 +++ .../contracts/utils/DMZForwarder.sol | 19 +++++ .../superfluid/Superfluid.BatchCall.t.sol | 69 +++++++++++++++++++ 4 files changed, 108 insertions(+) create mode 100644 packages/ethereum-contracts/contracts/utils/DMZForwarder.sol diff --git a/packages/ethereum-contracts/contracts/interfaces/superfluid/Definitions.sol b/packages/ethereum-contracts/contracts/interfaces/superfluid/Definitions.sol index 554348fd25..b23f41c5e8 100644 --- a/packages/ethereum-contracts/contracts/interfaces/superfluid/Definitions.sol +++ b/packages/ethereum-contracts/contracts/interfaces/superfluid/Definitions.sol @@ -230,6 +230,16 @@ library BatchOperation { * ) */ uint32 constant internal OPERATION_TYPE_SUPERFLUID_CALL_APP_ACTION = 2 + 200; + /** + * @dev DMZForwarder.forwardCall batch operation type + * + * Call spec: + * forwardCall( + * target, + * data + * ) + */ + uint32 constant internal OPERATION_TYPE_SIMPLE_FORWARD_CALL = 1 + 300; } /** diff --git a/packages/ethereum-contracts/contracts/superfluid/Superfluid.sol b/packages/ethereum-contracts/contracts/superfluid/Superfluid.sol index d0cb6108c5..69c2d115b9 100644 --- a/packages/ethereum-contracts/contracts/superfluid/Superfluid.sol +++ b/packages/ethereum-contracts/contracts/superfluid/Superfluid.sol @@ -23,6 +23,7 @@ import { GeneralDistributionAgreementV1 } from "../agreements/gdav1/GeneralDistr import { SuperfluidUpgradeableBeacon } from "../upgradability/SuperfluidUpgradeableBeacon.sol"; import { CallUtils } from "../libs/CallUtils.sol"; import { BaseRelayRecipient } from "../libs/BaseRelayRecipient.sol"; +import { DMZForwarder } from "../utils/DMZForwarder.sol"; /** * @dev The Superfluid host implementation. @@ -51,6 +52,8 @@ contract Superfluid is // solhint-disable-next-line var-name-mixedcase bool immutable public APP_WHITE_LISTING_ENABLED; + DMZForwarder immutable public DMZ_FORWARDER; + /** * @dev Maximum number of level of apps can be composed together * @@ -98,6 +101,7 @@ contract Superfluid is constructor(bool nonUpgradable, bool appWhiteListingEnabled) { NON_UPGRADABLE_DEPLOYMENT = nonUpgradable; APP_WHITE_LISTING_ENABLED = appWhiteListingEnabled; + DMZ_FORWARDER = new DMZForwarder(); } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -873,6 +877,12 @@ contract Superfluid is valueForwarded ? 0 : msg.value, operations[i].data); valueForwarded = true; + } else if (operationType == BatchOperation.OPERATION_TYPE_SIMPLE_FORWARD_CALL) { + (bool success, bytes memory returnData) = + DMZ_FORWARDER.forwardCall(operations[i].target, operations[i].data); + if (!success) { + CallUtils.revertFromReturnedData(returnData); + } } else { revert HOST_UNKNOWN_BATCH_CALL_OPERATION_TYPE(); } diff --git a/packages/ethereum-contracts/contracts/utils/DMZForwarder.sol b/packages/ethereum-contracts/contracts/utils/DMZForwarder.sol new file mode 100644 index 0000000000..61364db0c5 --- /dev/null +++ b/packages/ethereum-contracts/contracts/utils/DMZForwarder.sol @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: AGPLv3 +pragma solidity 0.8.23; + +/** + * @title DMZForwarder + * @dev The purpose of this contract is to make arbitrary contract calls batchable. + * We want those to be routed through this dedicated contract because having the host, + * as msg.sender would be too dangerous. + * This contract shall not have any special permissions. + */ +contract DMZForwarder { + function forwardCall(address target, bytes memory data) + external payable + returns(bool success, bytes memory returnData) + { + // solhint-disable-next-line avoid-low-level-calls + (success, returnData) = target.call(data); + } +} \ No newline at end of file diff --git a/packages/ethereum-contracts/test/foundry/superfluid/Superfluid.BatchCall.t.sol b/packages/ethereum-contracts/test/foundry/superfluid/Superfluid.BatchCall.t.sol index ae43ed17ce..623eab4c5c 100644 --- a/packages/ethereum-contracts/test/foundry/superfluid/Superfluid.BatchCall.t.sol +++ b/packages/ethereum-contracts/test/foundry/superfluid/Superfluid.BatchCall.t.sol @@ -11,6 +11,27 @@ import { ISuperfluidToken } from "../../../contracts/interfaces/superfluid/ISupe import { FoundrySuperfluidTester } from "../FoundrySuperfluidTester.sol"; import { SuperTokenV1Library } from "../../../contracts/apps/SuperTokenV1Library.sol"; import { SuperAppMock } from "../../../contracts/mocks/SuperAppMocks.sol"; +import { DMZForwarder } from "../../../contracts/utils/DMZForwarder.sol"; + +// A mock for an arbitrary external contract +contract MockExtContract { + uint256 public val; + + error SomeError(); + + constructor(uint256 initialVal) { + val = initialVal; + } + + function setVal(uint256 val_) external returns (uint256 prevVal) { + prevVal = val; + val = val_; + } + + function doRevert() external { + revert SomeError(); + } +} contract SuperfluidBatchCallTest is FoundrySuperfluidTester { using SuperTokenV1Library for SuperToken; @@ -302,4 +323,52 @@ contract SuperfluidBatchCallTest is FoundrySuperfluidTester { assertTrue(sf.gda.isMemberConnected(pool, alice), "Alice: Pool is not connected"); } + + function testDMZForwarder() public { + DMZForwarder forwarder = new DMZForwarder(); + uint256 initialVal = 1; + MockExtContract testContract = new MockExtContract(initialVal); + + uint256 newVal = 42; + (bool success, bytes memory returnValue) = forwarder.forwardCall( + address(testContract), + abi.encodeCall(testContract.setVal, (newVal)) + ); + // decoded return value + uint256 oldVal = abi.decode(returnValue, (uint256)); + assertTrue(success, "DMZForwarder: call failed"); + assertEq(testContract.val(), newVal, "TestContract: unexpected test contract state"); + assertEq(oldVal, initialVal, "DMZForwarder: unexpected return value"); + } + + function testDMZForwarderBatchCall() public { + uint256 initialVal = 1; + MockExtContract testContract = new MockExtContract(initialVal); + + uint256 newVal = 42; + ISuperfluid.Operation[] memory ops = new ISuperfluid.Operation[](1); + ops[0] = ISuperfluid.Operation({ + operationType: BatchOperation.OPERATION_TYPE_SIMPLE_FORWARD_CALL, + target: address(testContract), + data: abi.encodeCall(testContract.setVal, (newVal)) + }); + sf.host.batchCall(ops); + assertEq(testContract.val(), newVal, "TestContract: unexpected test contract state"); + } + + function testDMZForwarderRevertInBatchCall() public { + uint256 initialVal = 1; + MockExtContract testContract = new MockExtContract(initialVal); + + uint256 newVal = 42; + ISuperfluid.Operation[] memory ops = new ISuperfluid.Operation[](1); + ops[0] = ISuperfluid.Operation({ + operationType: BatchOperation.OPERATION_TYPE_SIMPLE_FORWARD_CALL, + target: address(testContract), + data: abi.encodeCall(testContract.doRevert, ()) + }); + + vm.expectRevert(MockExtContract.SomeError.selector); + sf.host.batchCall(ops); + } } From 1f31d3c0c3c41b0f35556a72c2a8a1779e03210c Mon Sep 17 00:00:00 2001 From: didi Date: Tue, 25 Jun 2024 16:43:28 +0200 Subject: [PATCH 04/35] added OPERATION_TYPE_ERC2771_FORWARD_CALL --- .../interfaces/superfluid/Definitions.sol | 11 ++ .../contracts/superfluid/Superfluid.sol | 8 +- .../contracts/utils/DMZForwarder.sol | 26 +++- .../test/foundry/FoundrySuperfluidTester.sol | 1 - .../superfluid/Superfluid.BatchCall.t.sol | 123 ++++++++++++++++-- 5 files changed, 153 insertions(+), 16 deletions(-) diff --git a/packages/ethereum-contracts/contracts/interfaces/superfluid/Definitions.sol b/packages/ethereum-contracts/contracts/interfaces/superfluid/Definitions.sol index b23f41c5e8..5601d5a045 100644 --- a/packages/ethereum-contracts/contracts/interfaces/superfluid/Definitions.sol +++ b/packages/ethereum-contracts/contracts/interfaces/superfluid/Definitions.sol @@ -240,6 +240,17 @@ library BatchOperation { * ) */ uint32 constant internal OPERATION_TYPE_SIMPLE_FORWARD_CALL = 1 + 300; + /** + * @dev DMZForwarder.forward2771Call batch operation type + * + * Call spec: + * forward2771Call( + * target, + * msg.sender, + * data + * ) + */ + uint32 constant internal OPERATION_TYPE_ERC2771_FORWARD_CALL = 2 + 300; } /** diff --git a/packages/ethereum-contracts/contracts/superfluid/Superfluid.sol b/packages/ethereum-contracts/contracts/superfluid/Superfluid.sol index 69c2d115b9..907d923426 100644 --- a/packages/ethereum-contracts/contracts/superfluid/Superfluid.sol +++ b/packages/ethereum-contracts/contracts/superfluid/Superfluid.sol @@ -883,6 +883,12 @@ contract Superfluid is if (!success) { CallUtils.revertFromReturnedData(returnData); } + } else if (operationType == BatchOperation.OPERATION_TYPE_ERC2771_FORWARD_CALL) { + (bool success, bytes memory returnData) = + DMZ_FORWARDER.forward2771Call(operations[i].target, msgSender, operations[i].data); + if (!success) { + CallUtils.revertFromReturnedData(returnData); + } } else { revert HOST_UNKNOWN_BATCH_CALL_OPERATION_TYPE(); } @@ -921,7 +927,7 @@ contract Superfluid is ) != 0; } - /// @dev IRelayRecipient.isTrustedForwarder implementation + /// @dev IRelayRecipient.versionRecipient implementation function versionRecipient() external override pure returns (string memory) diff --git a/packages/ethereum-contracts/contracts/utils/DMZForwarder.sol b/packages/ethereum-contracts/contracts/utils/DMZForwarder.sol index 61364db0c5..1fab2b2844 100644 --- a/packages/ethereum-contracts/contracts/utils/DMZForwarder.sol +++ b/packages/ethereum-contracts/contracts/utils/DMZForwarder.sol @@ -1,19 +1,33 @@ // SPDX-License-Identifier: AGPLv3 pragma solidity 0.8.23; +import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; + /** * @title DMZForwarder - * @dev The purpose of this contract is to make arbitrary contract calls batchable. - * We want those to be routed through this dedicated contract because having the host, - * as msg.sender would be too dangerous. - * This contract shall not have any special permissions. + * @dev The purpose of this contract is to make arbitrary contract calls batchable + * alongside Superfluid specific batch operations. + * We route the callse through this dedicated contract in order to not have msg.sender set + * to the host contract, for security reasons. + * Forwarded calls can optionally use ERC-2771 to preserve the original msg.sender. */ -contract DMZForwarder { +contract DMZForwarder is Ownable { function forwardCall(address target, bytes memory data) - external payable + external returns(bool success, bytes memory returnData) { // solhint-disable-next-line avoid-low-level-calls (success, returnData) = target.call(data); } + + // Forwards a call using ERC-2771. + // That means the original msg.sender (provided by the host) is passed along in a trusted way. + // A recipient contract needs to trust this contract (trusted forwarder). + function forward2771Call(address target, address msgSender, bytes memory data) + external onlyOwner + returns(bool success, bytes memory returnData) + { + // solhint-disable-next-line avoid-low-level-calls + (success, returnData) = target.call(abi.encodePacked(data, msgSender)); + } } \ No newline at end of file diff --git a/packages/ethereum-contracts/test/foundry/FoundrySuperfluidTester.sol b/packages/ethereum-contracts/test/foundry/FoundrySuperfluidTester.sol index ce0477ddea..9dd1946818 100644 --- a/packages/ethereum-contracts/test/foundry/FoundrySuperfluidTester.sol +++ b/packages/ethereum-contracts/test/foundry/FoundrySuperfluidTester.sol @@ -1606,7 +1606,6 @@ contract FoundrySuperfluidTester is Test { PoolUnitData memory poolUnitDataBefore = _helperGetPoolUnitsData(pool_); - (int256 claimableBalance,) = pool_.getClaimableNow(member_); (int256 balanceBefore,,,) = poolSuperToken.realtimeBalanceOfNow(member_); { _updateMemberUnits(pool_, poolSuperToken, caller_, member_, newUnits_, useBools_); diff --git a/packages/ethereum-contracts/test/foundry/superfluid/Superfluid.BatchCall.t.sol b/packages/ethereum-contracts/test/foundry/superfluid/Superfluid.BatchCall.t.sol index 623eab4c5c..a472236945 100644 --- a/packages/ethereum-contracts/test/foundry/superfluid/Superfluid.BatchCall.t.sol +++ b/packages/ethereum-contracts/test/foundry/superfluid/Superfluid.BatchCall.t.sol @@ -12,9 +12,11 @@ import { FoundrySuperfluidTester } from "../FoundrySuperfluidTester.sol"; import { SuperTokenV1Library } from "../../../contracts/apps/SuperTokenV1Library.sol"; import { SuperAppMock } from "../../../contracts/mocks/SuperAppMocks.sol"; import { DMZForwarder } from "../../../contracts/utils/DMZForwarder.sol"; +import { Ownable } from '@openzeppelin/contracts/access/Ownable.sol'; +import { BaseRelayRecipient } from "../../../contracts/libs/BaseRelayRecipient.sol"; // A mock for an arbitrary external contract -contract MockExtContract { +contract TestContract { uint256 public val; error SomeError(); @@ -23,16 +25,56 @@ contract MockExtContract { val = initialVal; } - function setVal(uint256 val_) external returns (uint256 prevVal) { + function setVal(uint256 val_) public virtual returns (uint256 prevVal) { prevVal = val; val = val_; } - function doRevert() external { + function doRevert() external pure { revert SomeError(); } } +// A mock for an external contract that uses ERC-2771 +contract TestContract2771 is TestContract, Ownable, BaseRelayRecipient { + error NotOwner(); + + constructor(uint256 initialVal) TestContract(initialVal) { } + + // Expects the msgSender to be encoded in calldata as specified by ERC-2771. + // Will revert if relayed for anybody but the contract owner. + function setVal(uint256 val_) public override returns (uint256 prevVal) { + if (_getTransactionSigner() != owner()) revert NotOwner(); + return super.setVal(val_); + } + + /// @dev BaseRelayRecipient.isTrustedForwarder implementation + function isTrustedForwarder(address /*forwarder*/) public view virtual override returns(bool) { + // we don't enforce any restrictions for this test + return true; + } + + /// @dev IRelayRecipient.versionRecipient implementation + function versionRecipient() external override pure returns (string memory) { + return "v1"; + } +} + +// Same as TestContract2771, but only trusts the host's DMZForwarder +contract TestContract2771Checked is TestContract2771 { + Superfluid internal _host; + + constructor(uint256 initialVal, Superfluid host) TestContract2771(initialVal) { + _host = host; + } + + /// @dev BaseRelayRecipient.isTrustedForwarder implementation + function isTrustedForwarder(address forwarder) public view override returns(bool) { + // TODO: shall we add this to ISuperfluid and recommend as general pattern? + return forwarder == address(_host.DMZ_FORWARDER()); + } +} + contract SuperfluidBatchCallTest is FoundrySuperfluidTester { using SuperTokenV1Library for SuperToken; @@ -327,7 +369,7 @@ contract SuperfluidBatchCallTest is FoundrySuperfluidTester { function testDMZForwarder() public { DMZForwarder forwarder = new DMZForwarder(); uint256 initialVal = 1; - MockExtContract testContract = new MockExtContract(initialVal); + TestContract testContract = new TestContract(initialVal); uint256 newVal = 42; (bool success, bytes memory returnValue) = forwarder.forwardCall( @@ -343,7 +385,7 @@ contract SuperfluidBatchCallTest is FoundrySuperfluidTester { function testDMZForwarderBatchCall() public { uint256 initialVal = 1; - MockExtContract testContract = new MockExtContract(initialVal); + TestContract testContract = new TestContract(initialVal); uint256 newVal = 42; ISuperfluid.Operation[] memory ops = new ISuperfluid.Operation[](1); @@ -358,9 +400,8 @@ contract SuperfluidBatchCallTest is FoundrySuperfluidTester { function testDMZForwarderRevertInBatchCall() public { uint256 initialVal = 1; - MockExtContract testContract = new MockExtContract(initialVal); + TestContract testContract = new TestContract(initialVal); - uint256 newVal = 42; ISuperfluid.Operation[] memory ops = new ISuperfluid.Operation[](1); ops[0] = ISuperfluid.Operation({ operationType: BatchOperation.OPERATION_TYPE_SIMPLE_FORWARD_CALL, @@ -368,7 +409,73 @@ contract SuperfluidBatchCallTest is FoundrySuperfluidTester { data: abi.encodeCall(testContract.doRevert, ()) }); - vm.expectRevert(MockExtContract.SomeError.selector); + vm.expectRevert(TestContract.SomeError.selector); sf.host.batchCall(ops); } + + function testDMZForwarder2771() public { + DMZForwarder forwarder = new DMZForwarder(); + uint256 initialVal = 1; + + TestContract2771 testContract = new TestContract2771(initialVal); + // only alice shall be allowed to change val + testContract.transferOwnership(alice); + + uint256 newVal = 42; + // we relay a call for alice + (bool success, bytes memory returnValue) = forwarder.forward2771Call( + address(testContract), + alice, + abi.encodeCall(testContract.setVal, (newVal)) + ); + // decoded return value + uint256 oldVal = abi.decode(returnValue, (uint256)); + assertTrue(success, "DMZForwarder: call failed"); + assertEq(testContract.val(), newVal, "TestContract: unexpected test contract state"); + assertEq(oldVal, initialVal, "DMZForwarder: unexpected return value"); + + // if relaying for bob, it should fail + (success,) = forwarder.forward2771Call( + address(testContract), + bob, + abi.encodeCall(testContract.setVal, (newVal)) + ); + assertFalse(success, "DMZForwarder: call should have failed"); + + // only the owner of the forwarder shall be allowed to relay + vm.startPrank(eve); + vm.expectRevert("Ownable: caller is not the owner"); + forwarder.forward2771Call( + address(testContract), + alice, + abi.encodeCall(testContract.setVal, (newVal)) + ); + vm.stopPrank(); + } + + function testDMZForwarder2771BatchCall() public { + uint256 initialVal = 1; + TestContract2771Checked testContract = new TestContract2771Checked(initialVal, sf.host); + testContract.transferOwnership(alice); + + uint256 newVal = 42; + ISuperfluid.Operation[] memory ops = new ISuperfluid.Operation[](1); + ops[0] = ISuperfluid.Operation({ + operationType: BatchOperation.OPERATION_TYPE_ERC2771_FORWARD_CALL, + target: address(testContract), + data: abi.encodeCall(testContract.setVal, (newVal)) + }); + + // should fail if called by bob (not the owner of testContract) + vm.startPrank(bob); + vm.expectRevert(TestContract2771.NotOwner.selector); + sf.host.batchCall(ops); + vm.stopPrank(); + + // should succeed if called by alice + vm.startPrank(alice); + sf.host.batchCall(ops); + assertEq(testContract.val(), newVal, "TestContract: unexpected test contract state"); + vm.stopPrank(); + } } From b9582cd3ac307737999296b0430f00be426f8177 Mon Sep 17 00:00:00 2001 From: didi Date: Thu, 27 Jun 2024 13:57:50 +0200 Subject: [PATCH 05/35] forward native tokens with the first generic external call --- .../contracts/superfluid/Superfluid.sol | 14 +- .../contracts/utils/DMZForwarder.sol | 9 +- .../superfluid/Superfluid.BatchCall.t.sol | 140 +++++++++++++----- 3 files changed, 112 insertions(+), 51 deletions(-) diff --git a/packages/ethereum-contracts/contracts/superfluid/Superfluid.sol b/packages/ethereum-contracts/contracts/superfluid/Superfluid.sol index 907d923426..eb80013bb3 100644 --- a/packages/ethereum-contracts/contracts/superfluid/Superfluid.sol +++ b/packages/ethereum-contracts/contracts/superfluid/Superfluid.sol @@ -803,7 +803,6 @@ contract Superfluid is ) internal { - bool valueForwarded = false; for (uint256 i = 0; i < operations.length; ++i) { uint32 operationType = operations[i].operationType; if (operationType == BatchOperation.OPERATION_TYPE_ERC20_APPROVE) { @@ -874,12 +873,13 @@ contract Superfluid is _callAppAction( msgSender, ISuperApp(operations[i].target), - valueForwarded ? 0 : msg.value, + address(this).balance, operations[i].data); - valueForwarded = true; } else if (operationType == BatchOperation.OPERATION_TYPE_SIMPLE_FORWARD_CALL) { (bool success, bytes memory returnData) = - DMZ_FORWARDER.forwardCall(operations[i].target, operations[i].data); + DMZ_FORWARDER.forwardCall{value: address(this).balance}( + operations[i].target, + operations[i].data); if (!success) { CallUtils.revertFromReturnedData(returnData); } @@ -893,9 +893,9 @@ contract Superfluid is revert HOST_UNKNOWN_BATCH_CALL_OPERATION_TYPE(); } } - if (msg.value != 0 && !valueForwarded) { - // return ETH provided if not forwarded - payable(msg.sender).transfer(msg.value); + if (address(this).balance != 0) { + // return any native tokens left to the sender. + payable(msg.sender).transfer(address(this).balance); } } diff --git a/packages/ethereum-contracts/contracts/utils/DMZForwarder.sol b/packages/ethereum-contracts/contracts/utils/DMZForwarder.sol index 1fab2b2844..ea65655976 100644 --- a/packages/ethereum-contracts/contracts/utils/DMZForwarder.sol +++ b/packages/ethereum-contracts/contracts/utils/DMZForwarder.sol @@ -10,24 +10,25 @@ import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; * We route the callse through this dedicated contract in order to not have msg.sender set * to the host contract, for security reasons. * Forwarded calls can optionally use ERC-2771 to preserve the original msg.sender. + * If native tokens (msg.value) are provided, they are forwarded as well. */ contract DMZForwarder is Ownable { function forwardCall(address target, bytes memory data) - external + external payable returns(bool success, bytes memory returnData) { // solhint-disable-next-line avoid-low-level-calls - (success, returnData) = target.call(data); + (success, returnData) = target.call{value: msg.value}(data); } // Forwards a call using ERC-2771. // That means the original msg.sender (provided by the host) is passed along in a trusted way. // A recipient contract needs to trust this contract (trusted forwarder). function forward2771Call(address target, address msgSender, bytes memory data) - external onlyOwner + external payable onlyOwner returns(bool success, bytes memory returnData) { // solhint-disable-next-line avoid-low-level-calls - (success, returnData) = target.call(abi.encodePacked(data, msgSender)); + (success, returnData) = target.call{value: msg.value}(abi.encodePacked(data, msgSender)); } } \ No newline at end of file diff --git a/packages/ethereum-contracts/test/foundry/superfluid/Superfluid.BatchCall.t.sol b/packages/ethereum-contracts/test/foundry/superfluid/Superfluid.BatchCall.t.sol index a472236945..5abc96d7be 100644 --- a/packages/ethereum-contracts/test/foundry/superfluid/Superfluid.BatchCall.t.sol +++ b/packages/ethereum-contracts/test/foundry/superfluid/Superfluid.BatchCall.t.sol @@ -17,17 +17,21 @@ import { BaseRelayRecipient } from "../../../contracts/libs/BaseRelayRecipient.s // A mock for an arbitrary external contract contract TestContract { - uint256 public val; - error SomeError(); + error IncorrectPayment(); + + bool public stateChanged; - constructor(uint256 initialVal) { - val = initialVal; + function permissionlessFn() public returns (bool) { + stateChanged = true; + return true; } - function setVal(uint256 val_) public virtual returns (uint256 prevVal) { - prevVal = val; - val = val_; + // accept native coins + receive() external payable {} + + function pay(uint256 expectedAmount) external payable { + if (msg.value != expectedAmount) revert IncorrectPayment(); } function doRevert() external pure { @@ -39,13 +43,12 @@ contract TestContract { contract TestContract2771 is TestContract, Ownable, BaseRelayRecipient { error NotOwner(); - constructor(uint256 initialVal) TestContract(initialVal) { } - // Expects the msgSender to be encoded in calldata as specified by ERC-2771. // Will revert if relayed for anybody but the contract owner. - function setVal(uint256 val_) public override returns (uint256 prevVal) { + function privilegedFn() public returns (bool) { if (_getTransactionSigner() != owner()) revert NotOwner(); - return super.setVal(val_); + stateChanged = true; + return true; } /// @dev BaseRelayRecipient.isTrustedForwarder implementation @@ -64,7 +67,7 @@ contract TestContract2771 is TestContract, Ownable, BaseRelayRecipient { contract TestContract2771Checked is TestContract2771 { Superfluid internal _host; - constructor(uint256 initialVal, Superfluid host) TestContract2771(initialVal) { + constructor(Superfluid host) { _host = host; } @@ -75,6 +78,7 @@ contract TestContract2771Checked is TestContract2771 { } } + contract SuperfluidBatchCallTest is FoundrySuperfluidTester { using SuperTokenV1Library for SuperToken; @@ -368,39 +372,33 @@ contract SuperfluidBatchCallTest is FoundrySuperfluidTester { function testDMZForwarder() public { DMZForwarder forwarder = new DMZForwarder(); - uint256 initialVal = 1; - TestContract testContract = new TestContract(initialVal); + TestContract testContract = new TestContract(); - uint256 newVal = 42; (bool success, bytes memory returnValue) = forwarder.forwardCall( address(testContract), - abi.encodeCall(testContract.setVal, (newVal)) + abi.encodeCall(testContract.permissionlessFn, ()) ); // decoded return value - uint256 oldVal = abi.decode(returnValue, (uint256)); + bool retVal = abi.decode(returnValue, (bool)); assertTrue(success, "DMZForwarder: call failed"); - assertEq(testContract.val(), newVal, "TestContract: unexpected test contract state"); - assertEq(oldVal, initialVal, "DMZForwarder: unexpected return value"); + assertEq(retVal, true, "DMZForwarder: unexpected return value"); } function testDMZForwarderBatchCall() public { - uint256 initialVal = 1; - TestContract testContract = new TestContract(initialVal); + TestContract testContract = new TestContract(); - uint256 newVal = 42; ISuperfluid.Operation[] memory ops = new ISuperfluid.Operation[](1); ops[0] = ISuperfluid.Operation({ operationType: BatchOperation.OPERATION_TYPE_SIMPLE_FORWARD_CALL, target: address(testContract), - data: abi.encodeCall(testContract.setVal, (newVal)) + data: abi.encodeCall(testContract.permissionlessFn, ()) }); sf.host.batchCall(ops); - assertEq(testContract.val(), newVal, "TestContract: unexpected test contract state"); + assertEq(testContract.stateChanged(), true, "TestContract: unexpected state"); } function testDMZForwarderRevertInBatchCall() public { - uint256 initialVal = 1; - TestContract testContract = new TestContract(initialVal); + TestContract testContract = new TestContract(); ISuperfluid.Operation[] memory ops = new ISuperfluid.Operation[](1); ops[0] = ISuperfluid.Operation({ @@ -415,10 +413,9 @@ contract SuperfluidBatchCallTest is FoundrySuperfluidTester { function testDMZForwarder2771() public { DMZForwarder forwarder = new DMZForwarder(); - uint256 initialVal = 1; - TestContract2771 testContract = new TestContract2771(initialVal); - // only alice shall be allowed to change val + TestContract2771 testContract = new TestContract2771(); + // alice has privileged access to the testContract testContract.transferOwnership(alice); uint256 newVal = 42; @@ -426,19 +423,19 @@ contract SuperfluidBatchCallTest is FoundrySuperfluidTester { (bool success, bytes memory returnValue) = forwarder.forward2771Call( address(testContract), alice, - abi.encodeCall(testContract.setVal, (newVal)) + abi.encodeCall(testContract.privilegedFn, ()) ); // decoded return value - uint256 oldVal = abi.decode(returnValue, (uint256)); + bool retVal = abi.decode(returnValue, (bool)); assertTrue(success, "DMZForwarder: call failed"); - assertEq(testContract.val(), newVal, "TestContract: unexpected test contract state"); - assertEq(oldVal, initialVal, "DMZForwarder: unexpected return value"); + assertEq(testContract.stateChanged(), true, "TestContract: unexpected state"); + assertEq(retVal, true, "DMZForwarder: unexpected return value"); // if relaying for bob, it should fail (success,) = forwarder.forward2771Call( address(testContract), bob, - abi.encodeCall(testContract.setVal, (newVal)) + abi.encodeCall(testContract.privilegedFn, ()) ); assertFalse(success, "DMZForwarder: call should have failed"); @@ -448,22 +445,20 @@ contract SuperfluidBatchCallTest is FoundrySuperfluidTester { forwarder.forward2771Call( address(testContract), alice, - abi.encodeCall(testContract.setVal, (newVal)) + abi.encodeCall(testContract.privilegedFn, ()) ); vm.stopPrank(); } function testDMZForwarder2771BatchCall() public { - uint256 initialVal = 1; - TestContract2771Checked testContract = new TestContract2771Checked(initialVal, sf.host); + TestContract2771Checked testContract = new TestContract2771Checked(sf.host); testContract.transferOwnership(alice); - uint256 newVal = 42; ISuperfluid.Operation[] memory ops = new ISuperfluid.Operation[](1); ops[0] = ISuperfluid.Operation({ operationType: BatchOperation.OPERATION_TYPE_ERC2771_FORWARD_CALL, target: address(testContract), - data: abi.encodeCall(testContract.setVal, (newVal)) + data: abi.encodeCall(testContract.privilegedFn, ()) }); // should fail if called by bob (not the owner of testContract) @@ -475,7 +470,72 @@ contract SuperfluidBatchCallTest is FoundrySuperfluidTester { // should succeed if called by alice vm.startPrank(alice); sf.host.batchCall(ops); - assertEq(testContract.val(), newVal, "TestContract: unexpected test contract state"); + assertEq(testContract.stateChanged(), true, "TestContract: unexpected state"); vm.stopPrank(); } + + function testDMZForwarderBatchCallWithValue() public { + TestContract testContract = new TestContract(); + + uint256 amount = 42; + ISuperfluid.Operation[] memory ops = new ISuperfluid.Operation[](2); + ops[0] = ISuperfluid.Operation({ + operationType: BatchOperation.OPERATION_TYPE_SIMPLE_FORWARD_CALL, + target: address(testContract), + data: abi.encodeCall(testContract.pay, (amount)) + }); + ops[1] = ISuperfluid.Operation({ + operationType: BatchOperation.OPERATION_TYPE_SIMPLE_FORWARD_CALL, + target: address(testContract), + data: abi.encodeCall(testContract.permissionlessFn, ()) + }); + + // the first operation shall forward the value, the second shall not (and thus succeed) + sf.host.batchCall{value: amount}(ops); + assertEq(address(testContract).balance, amount, "TestContract: unexpected balance"); + } + + function testDMZForwarderBatchCallWithValueUnsupportedOrder() public { + TestContract testContract = new TestContract(); + + uint256 amount = 42; + ISuperfluid.Operation[] memory ops = new ISuperfluid.Operation[](2); + ops[0] = ISuperfluid.Operation({ + operationType: BatchOperation.OPERATION_TYPE_SIMPLE_FORWARD_CALL, + target: address(testContract), + data: abi.encodeCall(testContract.permissionlessFn, ()) + }); + ops[1] = ISuperfluid.Operation({ + operationType: BatchOperation.OPERATION_TYPE_SIMPLE_FORWARD_CALL, + target: address(testContract), + data: abi.encodeCall(testContract.pay, (amount)) + }); + + // This fails because the native tokens are forwarded to the first operation, + // which calls a non-payable function and thus reverts + vm.expectRevert(); + sf.host.batchCall{value: amount}(ops); + } + + function testDMZForwarder2771BatchCallWithValueUsingReceiveFn() public { + TestContract2771Checked testContract = new TestContract2771Checked(sf.host); + + uint256 amount = 42; + ISuperfluid.Operation[] memory ops = new ISuperfluid.Operation[](2); + ops[0] = ISuperfluid.Operation({ + operationType: BatchOperation.OPERATION_TYPE_SIMPLE_FORWARD_CALL, + target: address(testContract), + data: "" + }); + ops[1] = ISuperfluid.Operation({ + operationType: BatchOperation.OPERATION_TYPE_SIMPLE_FORWARD_CALL, + target: address(testContract), + data: abi.encodeCall(testContract.permissionlessFn, ()) + }); + + // This shall work because we first forward native tokens to the contract, + // then call the contract's setVal function + sf.host.batchCall{value: amount}(ops); + assertEq(address(testContract).balance, amount, "TestContract: unexpected test contract balance"); + } } From a1ae6ad7645d5be4503d763b830ed730f649a98a Mon Sep 17 00:00:00 2001 From: didi Date: Thu, 27 Jun 2024 15:30:20 +0200 Subject: [PATCH 06/35] fix contract size --- .../contracts/mocks/SuperfluidMock.sol | 4 ++-- .../contracts/superfluid/Superfluid.sol | 4 ++-- .../SuperfluidFrameworkDeploymentSteps.sol | 18 ++++++++++++++---- 3 files changed, 18 insertions(+), 8 deletions(-) diff --git a/packages/ethereum-contracts/contracts/mocks/SuperfluidMock.sol b/packages/ethereum-contracts/contracts/mocks/SuperfluidMock.sol index 12d68d7bf9..2cb7dcce7a 100644 --- a/packages/ethereum-contracts/contracts/mocks/SuperfluidMock.sol +++ b/packages/ethereum-contracts/contracts/mocks/SuperfluidMock.sol @@ -11,7 +11,7 @@ import { CallUtils } from "../libs/CallUtils.sol"; contract SuperfluidUpgradabilityTester is Superfluid { - constructor() Superfluid(false, false) + constructor() Superfluid(false, false, address(0)) // solhint-disable-next-line no-empty-blocks { } @@ -131,7 +131,7 @@ contract SuperfluidMock is Superfluid { constructor(bool nonUpgradable, bool appWhiteListingEnabled) - Superfluid(nonUpgradable, appWhiteListingEnabled) + Superfluid(nonUpgradable, appWhiteListingEnabled, address(0)) // solhint-disable-next-line no-empty-blocks { } diff --git a/packages/ethereum-contracts/contracts/superfluid/Superfluid.sol b/packages/ethereum-contracts/contracts/superfluid/Superfluid.sol index eb80013bb3..cf52f454e4 100644 --- a/packages/ethereum-contracts/contracts/superfluid/Superfluid.sol +++ b/packages/ethereum-contracts/contracts/superfluid/Superfluid.sol @@ -98,10 +98,10 @@ contract Superfluid is /// function in its respective mock contract to ensure that it doesn't break anything or lead to unexpected /// behaviors/layout when upgrading - constructor(bool nonUpgradable, bool appWhiteListingEnabled) { + constructor(bool nonUpgradable, bool appWhiteListingEnabled, address dmzForwarderAddress) { NON_UPGRADABLE_DEPLOYMENT = nonUpgradable; APP_WHITE_LISTING_ENABLED = appWhiteListingEnabled; - DMZ_FORWARDER = new DMZForwarder(); + DMZ_FORWARDER = DMZForwarder(dmzForwarderAddress); } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/packages/ethereum-contracts/contracts/utils/SuperfluidFrameworkDeploymentSteps.sol b/packages/ethereum-contracts/contracts/utils/SuperfluidFrameworkDeploymentSteps.sol index 1843a22e7b..023d48e3d2 100644 --- a/packages/ethereum-contracts/contracts/utils/SuperfluidFrameworkDeploymentSteps.sol +++ b/packages/ethereum-contracts/contracts/utils/SuperfluidFrameworkDeploymentSteps.sol @@ -34,6 +34,7 @@ import { TOGA } from "./TOGA.sol"; 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"; /// @title Superfluid Framework Deployment Steps /// @author Superfluid @@ -151,11 +152,12 @@ contract SuperfluidFrameworkDeploymentSteps { if (step == 0) { // CORE CONTRACT: TestGovernance // Deploy TestGovernance, a Superfluid Governance for testing purpose. It needs initialization later. testGovernance = SuperfluidGovDeployerLibrary.deployTestGovernance(); - SuperfluidGovDeployerLibrary.transferOwnership(testGovernance, address(this)); } else if (step == 1) { // CORE CONTRACT: Superfluid (Host) + DMZForwarder dmzForwarder = SuperfluidDMZForwarderDeployerLibrary.deploy(); // Deploy Host and initialize the test governance. - host = SuperfluidHostDeployerLibrary.deploy(true, false); + host = SuperfluidHostDeployerLibrary.deploy(true, false, address(dmzForwarder)); + dmzForwarder.transferOwnership(address(host)); host.initialize(testGovernance); @@ -347,9 +349,17 @@ library SuperfluidGovDeployerLibrary { } } +library SuperfluidDMZForwarderDeployerLibrary { + function deploy() external returns (DMZForwarder) { + return new DMZForwarder(); + } +} + library SuperfluidHostDeployerLibrary { - function deploy(bool _nonUpgradable, bool _appWhiteListingEnabled) external returns (Superfluid) { - return new Superfluid(_nonUpgradable, _appWhiteListingEnabled); + function deploy(bool _nonUpgradable, bool _appWhiteListingEnabled, address dmzForwarderAddress) + external returns (Superfluid) + { + return new Superfluid(_nonUpgradable, _appWhiteListingEnabled, dmzForwarderAddress); } } From 81526770b8083e77dc4d6abc2484924631c8167b Mon Sep 17 00:00:00 2001 From: didi Date: Thu, 27 Jun 2024 17:07:44 +0200 Subject: [PATCH 07/35] appease linter --- .../contracts/apps/SuperTokenV1Library.sol | 7 ++++--- .../agreements/gdav1/GeneralDistributionAgreement.t.sol | 8 ++++---- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/packages/ethereum-contracts/contracts/apps/SuperTokenV1Library.sol b/packages/ethereum-contracts/contracts/apps/SuperTokenV1Library.sol index 286a65e093..264d42d382 100644 --- a/packages/ethereum-contracts/contracts/apps/SuperTokenV1Library.sol +++ b/packages/ethereum-contracts/contracts/apps/SuperTokenV1Library.sol @@ -837,7 +837,7 @@ library SuperTokenV1Library { * @dev get flow info of a distributor to a pool for given token * @param token The token used in flow * @param distributor The sitributor of the flow - * @param pool The GDA pool + * @param pool The GDA pool * @return lastUpdated Timestamp of flow creation or last flowrate change * @return flowRate The flow rate * @return deposit The amount of deposit the flow @@ -922,7 +922,7 @@ library SuperTokenV1Library { deposit += cfaDeposit; owedDeposit += cfaOwedDeposit; } - + { (uint256 lastUpdatedGDA, int96 gdaNetFlowRate, uint256 gdaDeposit) = gda.getAccountFlowInfo(token, account); @@ -964,10 +964,11 @@ library SuperTokenV1Library { function getGDANetFlowInfo(ISuperToken token, address account) internal view - returns (uint256 lastUpdated, int96 flowRate, uint256 deposit, uint256 /* owedDeposit unused */) + returns (uint256 lastUpdated, int96 flowRate, uint256 deposit, uint256 owedDeposit) { (, IGeneralDistributionAgreementV1 gda) = _getHostAndGDA(token); (lastUpdated, flowRate, deposit) = gda.getAccountFlowInfo(token, account); + owedDeposit = 0; // not used in GDA } /** diff --git a/packages/ethereum-contracts/test/foundry/agreements/gdav1/GeneralDistributionAgreement.t.sol b/packages/ethereum-contracts/test/foundry/agreements/gdav1/GeneralDistributionAgreement.t.sol index 12e2ff9aee..c95b0c52ec 100644 --- a/packages/ethereum-contracts/test/foundry/agreements/gdav1/GeneralDistributionAgreement.t.sol +++ b/packages/ethereum-contracts/test/foundry/agreements/gdav1/GeneralDistributionAgreement.t.sol @@ -559,9 +559,9 @@ contract GeneralDistributionAgreementV1IntegrationTest is FoundrySuperfluidTeste for (uint256 i = 0; i < members.length; ++i) { if (sf.gda.isPool(superToken, members[i]) || members[i] == address(0)) continue; - uint128 memberUnits = pool.getUnits(members[i]); + uint128 memberIUnits = pool.getUnits(members[i]); - assertEq(perUnitDistributionAmount * memberUnits, pool.getTotalAmountReceivedByMember(members[i])); + assertEq(perUnitDistributionAmount * memberIUnits, pool.getTotalAmountReceivedByMember(members[i])); } } @@ -594,9 +594,9 @@ contract GeneralDistributionAgreementV1IntegrationTest is FoundrySuperfluidTeste for (uint256 i = 0; i < members.length; ++i) { if (sf.gda.isPool(superToken, members[i]) || members[i] == address(0)) continue; - uint128 memberUnits = pool.getUnits(members[i]); + uint128 memberIUnits = pool.getUnits(members[i]); - assertEq(perUnitDistributionAmount * memberUnits, pool.getTotalAmountReceivedByMember(members[i])); + assertEq(perUnitDistributionAmount * memberIUnits, pool.getTotalAmountReceivedByMember(members[i])); } } From b78a0d3cd9da54db14ec3c4b58c8a0dafaddcc82 Mon Sep 17 00:00:00 2001 From: didi Date: Thu, 27 Jun 2024 17:17:40 +0200 Subject: [PATCH 08/35] more comments, fixed value forwarding via 2771 --- .../interfaces/superfluid/Definitions.sol | 16 ++++++- .../interfaces/superfluid/ISuperToken.sol | 3 +- .../interfaces/superfluid/ISuperfluid.sol | 17 +++++++- .../contracts/superfluid/Superfluid.sol | 13 +++++- .../superfluid/Superfluid.BatchCall.t.sol | 43 ++++++++++++++++--- 5 files changed, 80 insertions(+), 12 deletions(-) diff --git a/packages/ethereum-contracts/contracts/interfaces/superfluid/Definitions.sol b/packages/ethereum-contracts/contracts/interfaces/superfluid/Definitions.sol index 5601d5a045..7a29d7e775 100644 --- a/packages/ethereum-contracts/contracts/interfaces/superfluid/Definitions.sol +++ b/packages/ethereum-contracts/contracts/interfaces/superfluid/Definitions.sol @@ -246,9 +246,23 @@ library BatchOperation { * Call spec: * forward2771Call( * target, - * msg.sender, + * msgSender, * data * ) + * + * NOTE: In the context of this operation, the `DZMForwarder` contract acts as the + * _trusted forwarder_ which must be trusted by the _recipient contract_ (operation target). + * It shall do so by dynamically looking up the DMZForwarder used by the host, like this: + * + * function isTrustedForwarder(address forwarder) public view returns(bool) { + * return forwarder == address(host.DMZ_FORWARDER()); + * } + * + * If used in the context of a `forwardBatchCall`, we effectively have a chaining/nesting + * of ERC-2771 calls where the host acts as _recipient contract_ of the enveloping 2771 call + * and the DMZForwarder acts as the _trusted forwarder_ of the nested 2771 call(s). + * That's why `msgSender` could be either the actual `msg.sender` (if using `batchCall`) + * or the relayed sender address (if using `forwardBatchCall`). */ uint32 constant internal OPERATION_TYPE_ERC2771_FORWARD_CALL = 2 + 300; } diff --git a/packages/ethereum-contracts/contracts/interfaces/superfluid/ISuperToken.sol b/packages/ethereum-contracts/contracts/interfaces/superfluid/ISuperToken.sol index 43d609b279..30d7e95084 100644 --- a/packages/ethereum-contracts/contracts/interfaces/superfluid/ISuperToken.sol +++ b/packages/ethereum-contracts/contracts/interfaces/superfluid/ISuperToken.sol @@ -581,6 +581,7 @@ interface ISuperToken is ISuperfluidToken, IERC20Metadata, IERC777 { /** * @dev Upgrade ERC20 to SuperToken by host contract and transfer immediately. * @param account The account to be changed. + * @param to The account to receive upgraded tokens * @param amount Number of tokens to be upgraded (in 18 decimals) * * @custom:modifiers @@ -591,7 +592,7 @@ interface ISuperToken is ISuperfluidToken, IERC20Metadata, IERC777 { /** * @dev Downgrade ERC20 to SuperToken by host contract and transfer immediately. * @param account The account to be changed. - * @param to The account to receive upgraded tokens + * @param to The account to receive downgraded tokens * @param amount Number of tokens to be downgraded (in 18 decimals) * * @custom:modifiers diff --git a/packages/ethereum-contracts/contracts/interfaces/superfluid/ISuperfluid.sol b/packages/ethereum-contracts/contracts/interfaces/superfluid/ISuperfluid.sol index b6abefb953..8d158f4c59 100644 --- a/packages/ethereum-contracts/contracts/interfaces/superfluid/ISuperfluid.sol +++ b/packages/ethereum-contracts/contracts/interfaces/superfluid/ISuperfluid.sol @@ -622,12 +622,27 @@ interface ISuperfluid { /** * @dev Batch call function * @param operations Array of batch operations + * + * NOTE: `batchCall` is `payable, because there's limited support for sending + * native tokens to batch operation targets. + * If value is > 0, the whole amount is sent to the first operation matching any of: + * - OPERATION_TYPE_SUPERFLUID_CALL_APP_ACTION + * - OPERATION_TYPE_SIMPLE_FORWARD_CALL + * - OPERATION_TYPE_ERC2771_FORWARD_CALL + * If the first such operation does not allow receiving native tokens, + * the transaction will revert. + * It's currently not possible to send native tokens to multiple operations, or to + * any but the first operation of one of the above mentioned types. + * If no such operation is included, the native tokens will be sent back to the sender. */ function batchCall(Operation[] calldata operations) external payable; /** - * @dev Batch call function for trusted forwarders (EIP-2771) + * @dev Batch call function with EIP-2771 encoded msgSender * @param operations Array of batch operations + * + * NOTE: This can be called only by contracts recognized as _trusted forwarder_ + * by the host contract (see `Superfluid.isTrustedForwarder`) */ function forwardBatchCall(Operation[] calldata operations) external; diff --git a/packages/ethereum-contracts/contracts/superfluid/Superfluid.sol b/packages/ethereum-contracts/contracts/superfluid/Superfluid.sol index cf52f454e4..1de0d3eca1 100644 --- a/packages/ethereum-contracts/contracts/superfluid/Superfluid.sol +++ b/packages/ethereum-contracts/contracts/superfluid/Superfluid.sol @@ -869,7 +869,13 @@ contract Superfluid is ISuperAgreement(operations[i].target), callData, userData); - } else if (operationType == BatchOperation.OPERATION_TYPE_SUPERFLUID_CALL_APP_ACTION) { + } + // The following operations for call proxies allow forwarding of native tokens. + // we use `address(this).balance` instead of `msg.value`, because the latter ist not + // updated after forwarding to the first operation, while `balance` is. + // The initial balance is equal to `msg.value` because there's no other path + // for the contract to receive native tokens. + else if (operationType == BatchOperation.OPERATION_TYPE_SUPERFLUID_CALL_APP_ACTION) { _callAppAction( msgSender, ISuperApp(operations[i].target), @@ -885,7 +891,10 @@ contract Superfluid is } } else if (operationType == BatchOperation.OPERATION_TYPE_ERC2771_FORWARD_CALL) { (bool success, bytes memory returnData) = - DMZ_FORWARDER.forward2771Call(operations[i].target, msgSender, operations[i].data); + DMZ_FORWARDER.forward2771Call{value: address(this).balance}( + operations[i].target, + msgSender, + operations[i].data); if (!success) { CallUtils.revertFromReturnedData(returnData); } diff --git a/packages/ethereum-contracts/test/foundry/superfluid/Superfluid.BatchCall.t.sol b/packages/ethereum-contracts/test/foundry/superfluid/Superfluid.BatchCall.t.sol index 5abc96d7be..2dc904cbae 100644 --- a/packages/ethereum-contracts/test/foundry/superfluid/Superfluid.BatchCall.t.sol +++ b/packages/ethereum-contracts/test/foundry/superfluid/Superfluid.BatchCall.t.sol @@ -51,6 +51,12 @@ contract TestContract2771 is TestContract, Ownable, BaseRelayRecipient { return true; } + // this can be used to check correct association of the payment + function privilegedPay(uint256 expectedAmount) external payable { + if (_getTransactionSigner() != owner()) revert NotOwner(); + if (msg.value != expectedAmount) revert IncorrectPayment(); + } + /// @dev BaseRelayRecipient.isTrustedForwarder implementation function isTrustedForwarder(address /*forwarder*/) public view virtual override returns(bool) { // we don't enforce any restrictions for this test @@ -418,7 +424,6 @@ contract SuperfluidBatchCallTest is FoundrySuperfluidTester { // alice has privileged access to the testContract testContract.transferOwnership(alice); - uint256 newVal = 42; // we relay a call for alice (bool success, bytes memory returnValue) = forwarder.forward2771Call( address(testContract), @@ -490,6 +495,28 @@ contract SuperfluidBatchCallTest is FoundrySuperfluidTester { data: abi.encodeCall(testContract.permissionlessFn, ()) }); + // This shall work because we first forward native tokens to the contract, + // then call the non-payable function + sf.host.batchCall{value: amount}(ops); + assertEq(address(testContract).balance, amount, "TestContract: unexpected balance"); + } + + function testDMZForwarderBatchCallWithValueUsingReceiveFn() public { + TestContract testContract = new TestContract(); + + uint256 amount = 42; + ISuperfluid.Operation[] memory ops = new ISuperfluid.Operation[](2); + ops[0] = ISuperfluid.Operation({ + operationType: BatchOperation.OPERATION_TYPE_SIMPLE_FORWARD_CALL, + target: address(testContract), + data: "" + }); + ops[1] = ISuperfluid.Operation({ + operationType: BatchOperation.OPERATION_TYPE_SIMPLE_FORWARD_CALL, + target: address(testContract), + data: abi.encodeCall(testContract.permissionlessFn, ()) + }); + // the first operation shall forward the value, the second shall not (and thus succeed) sf.host.batchCall{value: amount}(ops); assertEq(address(testContract).balance, amount, "TestContract: unexpected balance"); @@ -517,25 +544,27 @@ contract SuperfluidBatchCallTest is FoundrySuperfluidTester { sf.host.batchCall{value: amount}(ops); } - function testDMZForwarder2771BatchCallWithValueUsingReceiveFn() public { + function testDMZForwarder2771BatchCallWithValue() public { TestContract2771Checked testContract = new TestContract2771Checked(sf.host); + testContract.transferOwnership(alice); uint256 amount = 42; ISuperfluid.Operation[] memory ops = new ISuperfluid.Operation[](2); ops[0] = ISuperfluid.Operation({ - operationType: BatchOperation.OPERATION_TYPE_SIMPLE_FORWARD_CALL, + operationType: BatchOperation.OPERATION_TYPE_ERC2771_FORWARD_CALL, target: address(testContract), - data: "" + data: abi.encodeCall(testContract.privilegedPay, (amount)) }); ops[1] = ISuperfluid.Operation({ - operationType: BatchOperation.OPERATION_TYPE_SIMPLE_FORWARD_CALL, + operationType: BatchOperation.OPERATION_TYPE_ERC2771_FORWARD_CALL, target: address(testContract), data: abi.encodeCall(testContract.permissionlessFn, ()) }); - // This shall work because we first forward native tokens to the contract, - // then call the contract's setVal function + vm.deal(alice, 1 ether); + vm.startPrank(alice); sf.host.batchCall{value: amount}(ops); assertEq(address(testContract).balance, amount, "TestContract: unexpected test contract balance"); + vm.stopPrank(); } } From 839072c5a3cff247205edcff6b2ad758e375e04a Mon Sep 17 00:00:00 2001 From: didi Date: Thu, 27 Jun 2024 18:14:03 +0200 Subject: [PATCH 09/35] more verbose documentation --- .../contracts/utils/DMZForwarder.sol | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/packages/ethereum-contracts/contracts/utils/DMZForwarder.sol b/packages/ethereum-contracts/contracts/utils/DMZForwarder.sol index ea65655976..3b10b72d7a 100644 --- a/packages/ethereum-contracts/contracts/utils/DMZForwarder.sol +++ b/packages/ethereum-contracts/contracts/utils/DMZForwarder.sol @@ -7,12 +7,17 @@ import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; * @title DMZForwarder * @dev The purpose of this contract is to make arbitrary contract calls batchable * alongside Superfluid specific batch operations. - * We route the callse through this dedicated contract in order to not have msg.sender set + * We route the calls through this dedicated contract in order to not have msg.sender set * to the host contract, for security reasons. * Forwarded calls can optionally use ERC-2771 to preserve the original msg.sender. * If native tokens (msg.value) are provided, they are forwarded as well. */ contract DMZForwarder is Ownable { + /** + * @dev Forwards a call for which msg.sender doesn't matter + * @param target The target contract to call + * @param data The call data + */ function forwardCall(address target, bytes memory data) external payable returns(bool success, bytes memory returnData) @@ -21,9 +26,12 @@ contract DMZForwarder is Ownable { (success, returnData) = target.call{value: msg.value}(data); } - // Forwards a call using ERC-2771. - // That means the original msg.sender (provided by the host) is passed along in a trusted way. - // A recipient contract needs to trust this contract (trusted forwarder). + /** + * @dev Forwards a call passing along the original msg.sender encoded as specified in ERC-2771. + * @param target The target contract to call + * @param msgSender The original msg.sender passed along by the trusted contract owner + * @param data The call data + */ function forward2771Call(address target, address msgSender, bytes memory data) external payable onlyOwner returns(bool success, bytes memory returnData) From 1391b1f008da5e750b9ca55fc1d0eee7b46e3282 Mon Sep 17 00:00:00 2001 From: didi Date: Thu, 27 Jun 2024 18:18:03 +0200 Subject: [PATCH 10/35] fix hotfuzz init --- packages/hot-fuzz/hot-fuzz | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/hot-fuzz/hot-fuzz b/packages/hot-fuzz/hot-fuzz index 922e2a33d6..2aafee1a4d 100755 --- a/packages/hot-fuzz/hot-fuzz +++ b/packages/hot-fuzz/hot-fuzz @@ -43,7 +43,7 @@ cryticArgs: [ "--compile-force-framework=${CRYTIC_COMPILE_FRAMEWORK}", "--foundry-out-directory=${FOUNDRY_ROOT}/${FOUNDRY_OUT:-out}", # "--export-dir=${PROJECT_DIR}/crytic-export", TODO unfortunately this doesn't work - "--compile-libraries=(CFAv1ForwarderDeployerLibrary,0xf01),(GDAv1ForwarderDeployerLibrary,0xf02),(IDAv1ForwarderDeployerLibrary,0xf03),(ProxyDeployerLibrary,0xf04),(SlotsBitmapLibrary,0xf05),(SuperfluidCFAv1DeployerLibrary,0xf06),(SuperfluidFlowNFTLogicDeployerLibrary,0xf07),(SuperfluidGDAv1DeployerLibrary,0xf08),(SuperfluidGovDeployerLibrary,0xf09),(SuperfluidHostDeployerLibrary,0xf0a),(SuperfluidIDAv1DeployerLibrary,0xf0b),(SuperfluidPeripheryDeployerLibrary,0xf0c),(SuperfluidPoolDeployerLibrary,0xf0d),(SuperfluidPoolLogicDeployerLibrary,0xf0e),(SuperfluidPoolNFTLogicDeployerLibrary,0xf0f),(SuperTokenDeployerLibrary,0xf10),(SuperTokenFactoryDeployerLibrary,0xf11),(TokenDeployerLibrary,0xf12)" + "--compile-libraries=(CFAv1ForwarderDeployerLibrary,0xf01),(GDAv1ForwarderDeployerLibrary,0xf02),(IDAv1ForwarderDeployerLibrary,0xf03),(ProxyDeployerLibrary,0xf04),(SlotsBitmapLibrary,0xf05),(SuperfluidCFAv1DeployerLibrary,0xf06),(SuperfluidFlowNFTLogicDeployerLibrary,0xf07),(SuperfluidGDAv1DeployerLibrary,0xf08),(SuperfluidGovDeployerLibrary,0xf09),(SuperfluidHostDeployerLibrary,0xf0a),(SuperfluidIDAv1DeployerLibrary,0xf0b),(SuperfluidPeripheryDeployerLibrary,0xf0c),(SuperfluidPoolDeployerLibrary,0xf0d),(SuperfluidPoolLogicDeployerLibrary,0xf0e),(SuperfluidPoolNFTLogicDeployerLibrary,0xf0f),(SuperTokenDeployerLibrary,0xf10),(SuperTokenFactoryDeployerLibrary,0xf11),(TokenDeployerLibrary,0xf12),(SuperfluidDMZForwarderDeployerLibrary,0xf13)" ] deployContracts: [ ["0xf01", "CFAv1ForwarderDeployerLibrary"], @@ -64,6 +64,7 @@ deployContracts: [ ["0xf10", "SuperTokenDeployerLibrary"], ["0xf11", "SuperTokenFactoryDeployerLibrary"], ["0xf12", "TokenDeployerLibrary"], +["0xf13", "SuperfluidDMZForwarderDeployerLibrary"], ] deployBytecodes: [ ["0x1820a4B7618BdE71Dce8cdc73aAB6C95905faD24", "608060405234801561001057600080fd5b506109c5806100206000396000f3fe608060405234801561001057600080fd5b50600436106100a5576000357c010000000000000000000000000000000000000000000000000000000090048063a41e7d5111610078578063a41e7d51146101d4578063aabbb8ca1461020a578063b705676514610236578063f712f3e814610280576100a5565b806329965a1d146100aa5780633d584063146100e25780635df8122f1461012457806365ba36c114610152575b600080fd5b6100e0600480360360608110156100c057600080fd5b50600160a060020a038135811691602081013591604090910135166102b6565b005b610108600480360360208110156100f857600080fd5b5035600160a060020a0316610570565b60408051600160a060020a039092168252519081900360200190f35b6100e06004803603604081101561013a57600080fd5b50600160a060020a03813581169160200135166105bc565b6101c26004803603602081101561016857600080fd5b81019060208101813564010000000081111561018357600080fd5b82018360208201111561019557600080fd5b803590602001918460018302840111640100000000831117156101b757600080fd5b5090925090506106b3565b60408051918252519081900360200190f35b6100e0600480360360408110156101ea57600080fd5b508035600160a060020a03169060200135600160e060020a0319166106ee565b6101086004803603604081101561022057600080fd5b50600160a060020a038135169060200135610778565b61026c6004803603604081101561024c57600080fd5b508035600160a060020a03169060200135600160e060020a0319166107ef565b604080519115158252519081900360200190f35b61026c6004803603604081101561029657600080fd5b508035600160a060020a03169060200135600160e060020a0319166108aa565b6000600160a060020a038416156102cd57836102cf565b335b9050336102db82610570565b600160a060020a031614610339576040805160e560020a62461bcd02815260206004820152600f60248201527f4e6f7420746865206d616e616765720000000000000000000000000000000000604482015290519081900360640190fd5b6103428361092a565b15610397576040805160e560020a62461bcd02815260206004820152601a60248201527f4d757374206e6f7420626520616e204552433136352068617368000000000000604482015290519081900360640190fd5b600160a060020a038216158015906103b85750600160a060020a0382163314155b156104ff5760405160200180807f455243313832305f4143434550545f4d4147494300000000000000000000000081525060140190506040516020818303038152906040528051906020012082600160a060020a031663249cb3fa85846040518363ffffffff167c01000000000000000000000000000000000000000000000000000000000281526004018083815260200182600160a060020a0316600160a060020a031681526020019250505060206040518083038186803b15801561047e57600080fd5b505afa158015610492573d6000803e3d6000fd5b505050506040513d60208110156104a857600080fd5b5051146104ff576040805160e560020a62461bcd02815260206004820181905260248201527f446f6573206e6f7420696d706c656d656e742074686520696e74657266616365604482015290519081900360640190fd5b600160a060020a03818116600081815260208181526040808320888452909152808220805473ffffffffffffffffffffffffffffffffffffffff19169487169485179055518692917f93baa6efbd2244243bfee6ce4cfdd1d04fc4c0e9a786abd3a41313bd352db15391a450505050565b600160a060020a03818116600090815260016020526040812054909116151561059a5750806105b7565b50600160a060020a03808216600090815260016020526040902054165b919050565b336105c683610570565b600160a060020a031614610624576040805160e560020a62461bcd02815260206004820152600f60248201527f4e6f7420746865206d616e616765720000000000000000000000000000000000604482015290519081900360640190fd5b81600160a060020a031681600160a060020a0316146106435780610646565b60005b600160a060020a03838116600081815260016020526040808220805473ffffffffffffffffffffffffffffffffffffffff19169585169590951790945592519184169290917f605c2dbf762e5f7d60a546d42e7205dcb1b011ebc62a61736a57c9089d3a43509190a35050565b600082826040516020018083838082843780830192505050925050506040516020818303038152906040528051906020012090505b92915050565b6106f882826107ef565b610703576000610705565b815b600160a060020a03928316600081815260208181526040808320600160e060020a031996909616808452958252808320805473ffffffffffffffffffffffffffffffffffffffff19169590971694909417909555908152600284528181209281529190925220805460ff19166001179055565b600080600160a060020a038416156107905783610792565b335b905061079d8361092a565b156107c357826107ad82826108aa565b6107b85760006107ba565b815b925050506106e8565b600160a060020a0390811660009081526020818152604080832086845290915290205416905092915050565b6000808061081d857f01ffc9a70000000000000000000000000000000000000000000000000000000061094c565b909250905081158061082d575080155b1561083d576000925050506106e8565b61084f85600160e060020a031961094c565b909250905081158061086057508015155b15610870576000925050506106e8565b61087a858561094c565b909250905060018214801561088f5750806001145b1561089f576001925050506106e8565b506000949350505050565b600160a060020a0382166000908152600260209081526040808320600160e060020a03198516845290915281205460ff1615156108f2576108eb83836107ef565b90506106e8565b50600160a060020a03808316600081815260208181526040808320600160e060020a0319871684529091529020549091161492915050565b7bffffffffffffffffffffffffffffffffffffffffffffffffffffffff161590565b6040517f01ffc9a7000000000000000000000000000000000000000000000000000000008082526004820183905260009182919060208160248189617530fa90519096909550935050505056fea165627a7a72305820377f4a2d4301ede9949f163f319021a6e9c687c292a5e2b2c4734c126b524e6c0029"], From 23048b0858248fe3b9fd72b27491802728060dd3 Mon Sep 17 00:00:00 2001 From: didi Date: Thu, 27 Jun 2024 18:51:42 +0200 Subject: [PATCH 11/35] add dmzfwd to deploy script --- .../ops-scripts/deploy-framework.js | 37 ++++++++++++++++++- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/packages/ethereum-contracts/ops-scripts/deploy-framework.js b/packages/ethereum-contracts/ops-scripts/deploy-framework.js index 6da6a6aedf..fe2b89e681 100644 --- a/packages/ethereum-contracts/ops-scripts/deploy-framework.js +++ b/packages/ethereum-contracts/ops-scripts/deploy-framework.js @@ -228,6 +228,7 @@ module.exports = eval(`(${S.toString()})({skipArgv: true})`)(async function ( "PoolAdminNFT", "PoolMemberNFT", "IAccessControlEnumerable", + "DMZForwarder", ]; const mockContracts = [ "SuperfluidMock", @@ -268,6 +269,7 @@ module.exports = eval(`(${S.toString()})({skipArgv: true})`)(async function ( PoolAdminNFT, PoolMemberNFT, IAccessControlEnumerable, + DMZForwarder, } = await SuperfluidSDK.loadContracts({ ...extractWeb3Options(options), additionalContracts: contracts.concat(useMocks ? mockContracts : []), @@ -348,11 +350,14 @@ module.exports = eval(`(${S.toString()})({skipArgv: true})`)(async function ( `Superfluid.${protocolReleaseVersion}`, async (contractAddress) => !(await hasCode(web3, contractAddress)), async () => { + const dmzForwarder = await web3tx(DMZForwarder.new, "DMZForwarder.new")(); + output += `DMZ_FORWARDER=${dmzForwarder.address}\n`; + let superfluidAddress; const superfluidLogic = await web3tx( SuperfluidLogic.new, "SuperfluidLogic.new" - )(nonUpgradable, appWhiteListing); + )(nonUpgradable, appWhiteListing, dmzForwarder.address); console.log( `Superfluid new code address ${superfluidLogic.address}` ); @@ -372,6 +377,10 @@ module.exports = eval(`(${S.toString()})({skipArgv: true})`)(async function ( superfluidAddress = superfluidLogic.address; } const superfluid = await Superfluid.at(superfluidAddress); + await web3tx( + dmzForwarder.transferOwnership, + "dmzForwarder.transferOwnership" + )(superfluid.address); await web3tx( superfluid.initialize, "Superfluid.initialize" @@ -796,6 +805,30 @@ module.exports = eval(`(${S.toString()})({skipArgv: true})`)(async function ( throw new Error("Superfluid is not upgradable"); } + async function getPrevDMZForwarderAddr() { + console.log("Getting DMZForwarder address..."); + try { + return await superfluid.DMZ_FORWARDER(); + } catch (err) { + return ZERO_ADDRESS; // fallback + } + } + + const dmzForwarderAddress = await deployContractIfCodeChanged( + web3, + DMZForwarder, + await getPrevDMZForwarderAddr(), + async () => { + const dmzForwarder = await web3tx(DMZForwarder.new, "DMZForwarder.new")(); + await web3tx( + dmzForwarder.transferOwnership, + "dmzForwarder.transferOwnership" + )(superfluid.address); + output += `DMZ_FORWARDER=${dmzForwarder.address}\n`; + return dmzForwarder.address; + } + ); + // deploy new superfluid host logic superfluidNewLogicAddress = await deployContractIfCodeChanged( web3, @@ -808,7 +841,7 @@ module.exports = eval(`(${S.toString()})({skipArgv: true})`)(async function ( const superfluidLogic = await web3tx( SuperfluidLogic.new, "SuperfluidLogic.new" - )(nonUpgradable, appWhiteListing); + )(nonUpgradable, appWhiteListing, dmzForwarderAddress); output += `SUPERFLUID_HOST_LOGIC=${superfluidLogic.address}\n`; return superfluidLogic.address; } From 48279c1593c4e73651f0c7dabb48daf6156f1240 Mon Sep 17 00:00:00 2001 From: didi Date: Thu, 27 Jun 2024 22:17:38 +0200 Subject: [PATCH 12/35] adjust mock constructor --- .../ethereum-contracts/contracts/mocks/SuperfluidMock.sol | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/ethereum-contracts/contracts/mocks/SuperfluidMock.sol b/packages/ethereum-contracts/contracts/mocks/SuperfluidMock.sol index 2cb7dcce7a..8501dcaa27 100644 --- a/packages/ethereum-contracts/contracts/mocks/SuperfluidMock.sol +++ b/packages/ethereum-contracts/contracts/mocks/SuperfluidMock.sol @@ -129,9 +129,8 @@ contract SuperfluidUpgradabilityTester is Superfluid { contract SuperfluidMock is Superfluid { - - constructor(bool nonUpgradable, bool appWhiteListingEnabled) - Superfluid(nonUpgradable, appWhiteListingEnabled, address(0)) + constructor(bool nonUpgradable, bool appWhiteListingEnabled, address dmzForwarder) + Superfluid(nonUpgradable, appWhiteListingEnabled, dmzForwarder) // solhint-disable-next-line no-empty-blocks { } From 71b0158cf421a53f6cea56677dea9e452127ebd5 Mon Sep 17 00:00:00 2001 From: didi Date: Thu, 27 Jun 2024 22:19:09 +0200 Subject: [PATCH 13/35] disable code to stay inside contract size limit --- .../ethereum-contracts/contracts/mocks/SuperfluidMock.sol | 5 ++++- .../test/contracts/superfluid/Superfluid.test.ts | 3 ++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/ethereum-contracts/contracts/mocks/SuperfluidMock.sol b/packages/ethereum-contracts/contracts/mocks/SuperfluidMock.sol index 8501dcaa27..12981a7dd4 100644 --- a/packages/ethereum-contracts/contracts/mocks/SuperfluidMock.sol +++ b/packages/ethereum-contracts/contracts/mocks/SuperfluidMock.sol @@ -6,7 +6,7 @@ import { ISuperApp } from "../superfluid/Superfluid.sol"; -import { CallUtils } from "../libs/CallUtils.sol"; +//import { CallUtils } from "../libs/CallUtils.sol"; contract SuperfluidUpgradabilityTester is Superfluid { @@ -135,6 +135,8 @@ contract SuperfluidMock is Superfluid { { } + // disabled code because contract size limit hit + /* function ctxFunc1(uint256 n, bytes calldata ctx) external pure returns (uint256, bytes memory) @@ -166,6 +168,7 @@ contract SuperfluidMock is Superfluid { if (success) return returnedData; else CallUtils.revertFromReturnedData(returnedData); } + */ function jailApp(ISuperApp app) external diff --git a/packages/ethereum-contracts/test/contracts/superfluid/Superfluid.test.ts b/packages/ethereum-contracts/test/contracts/superfluid/Superfluid.test.ts index 98fe04baf7..cd5f697bcc 100644 --- a/packages/ethereum-contracts/test/contracts/superfluid/Superfluid.test.ts +++ b/packages/ethereum-contracts/test/contracts/superfluid/Superfluid.test.ts @@ -662,7 +662,8 @@ describe("Superfluid Host Contract", function () { }); }); - describe("#5 Context Utilities", () => { + // disabled due to contract size limit + describe.skip("#5 Context Utilities", () => { it("#5.1 test replacePlaceholderCtx with testCtxFuncX", async () => { const testCtxFunc = async ( ctxFuncX: string, From 30aed68ac52a0187bdcc50d5e4048b1806af152e Mon Sep 17 00:00:00 2001 From: didi Date: Thu, 27 Jun 2024 22:35:15 +0200 Subject: [PATCH 14/35] this is insanity --- .../dev-scripts/deploy-test-framework.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/packages/ethereum-contracts/dev-scripts/deploy-test-framework.js b/packages/ethereum-contracts/dev-scripts/deploy-test-framework.js index 5e60f6ace9..41d12ae832 100644 --- a/packages/ethereum-contracts/dev-scripts/deploy-test-framework.js +++ b/packages/ethereum-contracts/dev-scripts/deploy-test-framework.js @@ -20,6 +20,7 @@ const SuperTokenFactoryDeployerLibraryArtifact = require("@superfluid-finance/et const SuperfluidFrameworkDeployerArtifact = require("@superfluid-finance/ethereum-contracts/build/hardhat/contracts/utils/SuperfluidFrameworkDeployer.sol/SuperfluidFrameworkDeployer.json"); const SlotsBitmapLibraryArtifact = require("@superfluid-finance/ethereum-contracts/build/hardhat/contracts/libs/SlotsBitmapLibrary.sol/SlotsBitmapLibrary.json"); const TokenDeployerLibraryArtifact = require("@superfluid-finance/ethereum-contracts/build/hardhat/contracts/utils/SuperfluidFrameworkDeploymentSteps.sol/TokenDeployerLibrary.json"); +const SuperfluidDMZForwarderDeployerLibraryArtifact = require("@superfluid-finance/ethereum-contracts/build/hardhat/contracts/utils/SuperfluidFrameworkDeploymentSteps.sol/SuperfluidDMZForwarderDeployerLibrary.json"); const ERC1820Registry = require("../dev-scripts/artifacts/ERC1820Registry.json"); @@ -238,6 +239,12 @@ const _deployTestFramework = async (provider, signer) => { TokenDeployerLibraryArtifact, signer ); + const SuperfluidDMZForwarderDeployerLibrary = + await _getFactoryAndReturnDeployedContract( + "SuperfluidDMZForwarderDeployerLibrary", + SuperfluidDMZForwarderDeployerLibraryArtifact, + signer + ); const sfDeployer = await _getFactoryAndReturnDeployedContract( "SuperfluidFrameworkDeployer", @@ -289,6 +296,9 @@ const _deployTestFramework = async (provider, signer) => { SuperTokenFactoryDeployerLibrary ), TokenDeployerLibrary: getContractAddress(TokenDeployerLibrary), + SuperfluidDMZForwarderDeployerLibrary: getContractAddress( + SuperfluidDMZForwarderDeployerLibrary + ), }, } ); From 9ec13fbadb3109d68d060b70353e9d5605fbac49 Mon Sep 17 00:00:00 2001 From: didi Date: Fri, 28 Jun 2024 09:57:09 +0200 Subject: [PATCH 15/35] fix test --- .../test/contracts/superfluid/Superfluid.test.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/ethereum-contracts/test/contracts/superfluid/Superfluid.test.ts b/packages/ethereum-contracts/test/contracts/superfluid/Superfluid.test.ts index cd5f697bcc..8c78de51e6 100644 --- a/packages/ethereum-contracts/test/contracts/superfluid/Superfluid.test.ts +++ b/packages/ethereum-contracts/test/contracts/superfluid/Superfluid.test.ts @@ -112,11 +112,13 @@ describe("Superfluid Host Contract", function () { await ethers.getContractFactory("SuperfluidMock"); const mock1 = await sfMockFactory.deploy( false /* nonUpgradable */, - false /* appWhiteListingEnabled */ + false /* appWhiteListingEnabled */, + ZERO_ADDRESS /* dmzForwader */ ); const mock2 = await sfMockFactory.deploy( true /* nonUpgradable */, - false /* appWhiteListingEnabled */ + false /* appWhiteListingEnabled */, + ZERO_ADDRESS /* dmzForwader */ ); await governance.updateContracts( superfluid.address, @@ -2698,7 +2700,8 @@ describe("Superfluid Host Contract", function () { await ethers.getContractFactory("SuperfluidMock"); const mock1 = await mock1Factory.deploy( false /* nonUpgradable */, - false /* appWhiteListingEnabled */ + false /* appWhiteListingEnabled */, + ZERO_ADDRESS /* dmzForwader */ ); await expectCustomError( governance.updateContracts( From 39dfb017e8dbf7fd32b18e1a2aca4307fdca4362 Mon Sep 17 00:00:00 2001 From: didi Date: Fri, 28 Jun 2024 10:28:19 +0200 Subject: [PATCH 16/35] fix deployment test --- .../ethereum-contracts/test/ops-scripts/deployment.test.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/ethereum-contracts/test/ops-scripts/deployment.test.js b/packages/ethereum-contracts/test/ops-scripts/deployment.test.js index f718d5a168..9bbb62b166 100644 --- a/packages/ethereum-contracts/test/ops-scripts/deployment.test.js +++ b/packages/ethereum-contracts/test/ops-scripts/deployment.test.js @@ -124,7 +124,8 @@ contract("Embedded deployment scripts", (accounts) => { // with constructor param const a1 = await web3tx(Superfluid.new, "Superfluid.new 1")( true, // nonUpgradable - false // appWhiteListingEnabled + false, // appWhiteListingEnabled + ZERO_ADDRESS // dmzForwader ); assert.isFalse(await codeChanged(web3, Superfluid, a1.address)); } From 34c5bb5a404d585615b27eb9324b0a4807e6ddd6 Mon Sep 17 00:00:00 2001 From: didi Date: Fri, 28 Jun 2024 12:06:07 +0200 Subject: [PATCH 17/35] disable only part of the replacePlaceholderCtx test --- .../contracts/mocks/SuperfluidMock.sol | 9 ++++----- .../test/contracts/superfluid/Superfluid.test.ts | 13 ++++++++----- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/packages/ethereum-contracts/contracts/mocks/SuperfluidMock.sol b/packages/ethereum-contracts/contracts/mocks/SuperfluidMock.sol index 12981a7dd4..46f74c83ae 100644 --- a/packages/ethereum-contracts/contracts/mocks/SuperfluidMock.sol +++ b/packages/ethereum-contracts/contracts/mocks/SuperfluidMock.sol @@ -6,7 +6,7 @@ import { ISuperApp } from "../superfluid/Superfluid.sol"; -//import { CallUtils } from "../libs/CallUtils.sol"; +import { CallUtils } from "../libs/CallUtils.sol"; contract SuperfluidUpgradabilityTester is Superfluid { @@ -135,8 +135,6 @@ contract SuperfluidMock is Superfluid { { } - // disabled code because contract size limit hit - /* function ctxFunc1(uint256 n, bytes calldata ctx) external pure returns (uint256, bytes memory) @@ -144,7 +142,9 @@ contract SuperfluidMock is Superfluid { return (n, ctx); } + // disabled code because contract size limit hit // same ABI to afterAgreementCreated + /* function ctxFunc2( address superToken, address agreementClass, @@ -157,6 +157,7 @@ contract SuperfluidMock is Superfluid { { return (superToken, agreementClass, agreementId, agreementData, cbdata, ctx); } + */ function testCtxFuncX(bytes calldata dataWithPlaceHolderCtx, bytes calldata ctx) external view @@ -168,12 +169,10 @@ contract SuperfluidMock is Superfluid { if (success) return returnedData; else CallUtils.revertFromReturnedData(returnedData); } - */ function jailApp(ISuperApp app) external { _jailApp(app, 6942); } - } diff --git a/packages/ethereum-contracts/test/contracts/superfluid/Superfluid.test.ts b/packages/ethereum-contracts/test/contracts/superfluid/Superfluid.test.ts index 8c78de51e6..dd3c1cd033 100644 --- a/packages/ethereum-contracts/test/contracts/superfluid/Superfluid.test.ts +++ b/packages/ethereum-contracts/test/contracts/superfluid/Superfluid.test.ts @@ -665,7 +665,7 @@ describe("Superfluid Host Contract", function () { }); // disabled due to contract size limit - describe.skip("#5 Context Utilities", () => { + describe("#5 Context Utilities", () => { it("#5.1 test replacePlaceholderCtx with testCtxFuncX", async () => { const testCtxFunc = async ( ctxFuncX: string, @@ -697,15 +697,17 @@ describe("Superfluid Host Contract", function () { ); } + // disabled code because contract size limit hit // more complicated ABI + /* await testCtxFunc( "ctxFunc2", [ governance.address, t.contracts.ida.address, ethers.utils.hexZeroPad("0x2020", 32), - "0x" /* agreementData */, - "0x" /* cbdata */, + "0x", // agreementData + "0x", // cbdata ], "0x" + "dead".repeat(20) ); @@ -715,11 +717,12 @@ describe("Superfluid Host Contract", function () { governance.address, t.contracts.ida.address, ethers.utils.hexZeroPad("0x2020", 32), - "0xdead" /* agreementData */, - "0xbeef" /* cbdata */, + "0xdead", // agreementData + "0xbeef", // cbdata ], "0x" + "faec".repeat(20) ); + */ // error case await expectCustomError( From 804734d0c3973c6d9b3e8c56bf63143b25c75d48 Mon Sep 17 00:00:00 2001 From: didi Date: Fri, 28 Jun 2024 12:39:25 +0200 Subject: [PATCH 18/35] revert unrelated change --- .../ethereum-contracts/contracts/apps/SuperTokenV1Library.sol | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/ethereum-contracts/contracts/apps/SuperTokenV1Library.sol b/packages/ethereum-contracts/contracts/apps/SuperTokenV1Library.sol index 264d42d382..81ea159f50 100644 --- a/packages/ethereum-contracts/contracts/apps/SuperTokenV1Library.sol +++ b/packages/ethereum-contracts/contracts/apps/SuperTokenV1Library.sol @@ -964,11 +964,10 @@ library SuperTokenV1Library { function getGDANetFlowInfo(ISuperToken token, address account) internal view - returns (uint256 lastUpdated, int96 flowRate, uint256 deposit, uint256 owedDeposit) + returns (uint256 lastUpdated, int96 flowRate, uint256 deposit, uint256 /* owedDeposit unused */) { (, IGeneralDistributionAgreementV1 gda) = _getHostAndGDA(token); (lastUpdated, flowRate, deposit) = gda.getAccountFlowInfo(token, account); - owedDeposit = 0; // not used in GDA } /** From 5772a075dfe56713a9d36cfae24b03317e342d68 Mon Sep 17 00:00:00 2001 From: didi Date: Fri, 28 Jun 2024 15:57:19 +0200 Subject: [PATCH 19/35] try failing test only --- .../test/contracts/apps/SuperTokenV1Library.GDA.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ethereum-contracts/test/contracts/apps/SuperTokenV1Library.GDA.test.ts b/packages/ethereum-contracts/test/contracts/apps/SuperTokenV1Library.GDA.test.ts index 68ddfff3e3..97dea6518f 100644 --- a/packages/ethereum-contracts/test/contracts/apps/SuperTokenV1Library.GDA.test.ts +++ b/packages/ethereum-contracts/test/contracts/apps/SuperTokenV1Library.GDA.test.ts @@ -109,7 +109,7 @@ describe("SuperTokenV1Library.GDA", function () { t.afterEachTestCaseBenchmark(); }); - it("#1.1 Should be able to create pool", async () => { + it.only("#1.1 Should be able to create pool", async () => { const createPoolTxn = await superTokenLibraryGDAMock.createPoolTest( superToken.address, alice, From ea4d3a313a1c5d7143104e0ffba3b06220afb507 Mon Sep 17 00:00:00 2001 From: didi Date: Fri, 28 Jun 2024 18:05:25 +0200 Subject: [PATCH 20/35] fixed the flakiness source and undid the undo --- .../contracts/apps/SuperTokenV1Library.sol | 3 ++- .../test/contracts/apps/SuperTokenV1Library.GDA.test.ts | 9 +++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/packages/ethereum-contracts/contracts/apps/SuperTokenV1Library.sol b/packages/ethereum-contracts/contracts/apps/SuperTokenV1Library.sol index 81ea159f50..b389f658f6 100644 --- a/packages/ethereum-contracts/contracts/apps/SuperTokenV1Library.sol +++ b/packages/ethereum-contracts/contracts/apps/SuperTokenV1Library.sol @@ -964,10 +964,11 @@ library SuperTokenV1Library { function getGDANetFlowInfo(ISuperToken token, address account) internal view - returns (uint256 lastUpdated, int96 flowRate, uint256 deposit, uint256 /* owedDeposit unused */) + returns (uint256 lastUpdated, int96 flowRate, uint256 deposit, uint256 owedDeposit) { (, IGeneralDistributionAgreementV1 gda) = _getHostAndGDA(token); (lastUpdated, flowRate, deposit) = gda.getAccountFlowInfo(token, account); + owedDeposit = 0; // unused in GDA } /** diff --git a/packages/ethereum-contracts/test/contracts/apps/SuperTokenV1Library.GDA.test.ts b/packages/ethereum-contracts/test/contracts/apps/SuperTokenV1Library.GDA.test.ts index 97dea6518f..122fcd5b89 100644 --- a/packages/ethereum-contracts/test/contracts/apps/SuperTokenV1Library.GDA.test.ts +++ b/packages/ethereum-contracts/test/contracts/apps/SuperTokenV1Library.GDA.test.ts @@ -73,9 +73,9 @@ describe("SuperTokenV1Library.GDA", function () { const event = receipt.events?.find((x) => x.topics.includes(POOL_CREATED_TOPIC) ); - return ethers.utils.hexStripZeros( - event ? event.data : ethers.constants.AddressZero - ); + return event + ? `0x${event.data.substring(event.data.length - 40)}` + : ethers.constants.AddressZero; }; let alice: string, bob: string; @@ -109,7 +109,7 @@ describe("SuperTokenV1Library.GDA", function () { t.afterEachTestCaseBenchmark(); }); - it.only("#1.1 Should be able to create pool", async () => { + it("#1.1 Should be able to create pool", async () => { const createPoolTxn = await superTokenLibraryGDAMock.createPoolTest( superToken.address, alice, @@ -120,6 +120,7 @@ describe("SuperTokenV1Library.GDA", function () { ); const receipt = await createPoolTxn.wait(); const poolAddress = getPoolAddressFromReceipt(receipt); + const poolContract = await ethers.getContractAt( "SuperfluidPool", poolAddress From bb6fda4b10130b28699476d2053b0afec4666221 Mon Sep 17 00:00:00 2001 From: didi Date: Fri, 28 Jun 2024 18:21:46 +0200 Subject: [PATCH 21/35] updated CHANGELOG --- packages/ethereum-contracts/CHANGELOG.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/packages/ethereum-contracts/CHANGELOG.md b/packages/ethereum-contracts/CHANGELOG.md index 76bd6f606e..986ecc7732 100644 --- a/packages/ethereum-contracts/CHANGELOG.md +++ b/packages/ethereum-contracts/CHANGELOG.md @@ -3,6 +3,17 @@ All notable changes to the ethereum-contracts will be documented in this file. This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [unreleased] + +### Added + +- `batchCall` now supports 4 additional operation types: + - `OPERATION_TYPE_SUPERTOKEN_UPGRADE_TO` + - `OPERATION_TYPE_SUPERTOKEN_DOWNGRADE_TO` + - `OPERATION_TYPE_SIMPLE_FORWARD_CALL` + - `OPERATION_TYPE_ERC2771_FORWARD_CALL` + The latter 2 allow to add arbitrary contract calls to batch call. + ## [v1.9.1] - 2024-03-19 ### Breaking From 08a17fd06d721f6b586b4b7133c05c8902de77d6 Mon Sep 17 00:00:00 2001 From: didi Date: Mon, 1 Jul 2024 15:27:38 +0200 Subject: [PATCH 22/35] allow to withdraw native tokens stuck in DMZForwarder --- .../contracts/superfluid/Superfluid.sol | 6 +- .../contracts/utils/DMZForwarder.sol | 8 +++ .../SuperfluidFrameworkDeploymentSteps.sol | 1 + .../superfluid/Superfluid.BatchCall.t.sol | 56 ++++++++++++++++--- 4 files changed, 59 insertions(+), 12 deletions(-) diff --git a/packages/ethereum-contracts/contracts/superfluid/Superfluid.sol b/packages/ethereum-contracts/contracts/superfluid/Superfluid.sol index 1de0d3eca1..66d7dbe6ea 100644 --- a/packages/ethereum-contracts/contracts/superfluid/Superfluid.sol +++ b/packages/ethereum-contracts/contracts/superfluid/Superfluid.sol @@ -798,7 +798,7 @@ contract Superfluid is **************************************************************************/ function _batchCall( - address msgSender, + address payable msgSender, Operation[] calldata operations ) internal @@ -904,7 +904,7 @@ contract Superfluid is } if (address(this).balance != 0) { // return any native tokens left to the sender. - payable(msg.sender).transfer(address(this).balance); + msgSender.transfer(address(this).balance); } } @@ -914,7 +914,7 @@ contract Superfluid is ) external override payable { - _batchCall(msg.sender, operations); + _batchCall(payable(msg.sender), operations); } /// @dev ISuperfluid.forwardBatchCall implementation diff --git a/packages/ethereum-contracts/contracts/utils/DMZForwarder.sol b/packages/ethereum-contracts/contracts/utils/DMZForwarder.sol index 3b10b72d7a..5c36c8ea7e 100644 --- a/packages/ethereum-contracts/contracts/utils/DMZForwarder.sol +++ b/packages/ethereum-contracts/contracts/utils/DMZForwarder.sol @@ -39,4 +39,12 @@ contract DMZForwarder is Ownable { // solhint-disable-next-line avoid-low-level-calls (success, returnData) = target.call{value: msg.value}(abi.encodePacked(data, msgSender)); } + + /** + * @dev Allows to withdraw native tokens (ETH) which got stuck in this contract. + * This could happen if a call fails, but the caller doesn't revert the tx. + */ + function withdrawLostNativeTokens(address payable receiver) external onlyOwner { + receiver.transfer(address(this).balance); + } } \ No newline at end of file diff --git a/packages/ethereum-contracts/contracts/utils/SuperfluidFrameworkDeploymentSteps.sol b/packages/ethereum-contracts/contracts/utils/SuperfluidFrameworkDeploymentSteps.sol index 023d48e3d2..943e1f0e45 100644 --- a/packages/ethereum-contracts/contracts/utils/SuperfluidFrameworkDeploymentSteps.sol +++ b/packages/ethereum-contracts/contracts/utils/SuperfluidFrameworkDeploymentSteps.sol @@ -350,6 +350,7 @@ library SuperfluidGovDeployerLibrary { } library SuperfluidDMZForwarderDeployerLibrary { + // After deploying, you may want to transfer ownership to the host function deploy() external returns (DMZForwarder) { return new DMZForwarder(); } diff --git a/packages/ethereum-contracts/test/foundry/superfluid/Superfluid.BatchCall.t.sol b/packages/ethereum-contracts/test/foundry/superfluid/Superfluid.BatchCall.t.sol index 2dc904cbae..6f9ddabfb0 100644 --- a/packages/ethereum-contracts/test/foundry/superfluid/Superfluid.BatchCall.t.sol +++ b/packages/ethereum-contracts/test/foundry/superfluid/Superfluid.BatchCall.t.sol @@ -376,7 +376,7 @@ contract SuperfluidBatchCallTest is FoundrySuperfluidTester { assertTrue(sf.gda.isMemberConnected(pool, alice), "Alice: Pool is not connected"); } - function testDMZForwarder() public { + function testSimpleForwardCall() public { DMZForwarder forwarder = new DMZForwarder(); TestContract testContract = new TestContract(); @@ -390,7 +390,7 @@ contract SuperfluidBatchCallTest is FoundrySuperfluidTester { assertEq(retVal, true, "DMZForwarder: unexpected return value"); } - function testDMZForwarderBatchCall() public { + function testSimpleForwardCallBatchCall() public { TestContract testContract = new TestContract(); ISuperfluid.Operation[] memory ops = new ISuperfluid.Operation[](1); @@ -403,7 +403,7 @@ contract SuperfluidBatchCallTest is FoundrySuperfluidTester { assertEq(testContract.stateChanged(), true, "TestContract: unexpected state"); } - function testDMZForwarderRevertInBatchCall() public { + function testSimpleForwardCallBatchCallRevert() public { TestContract testContract = new TestContract(); ISuperfluid.Operation[] memory ops = new ISuperfluid.Operation[](1); @@ -417,7 +417,7 @@ contract SuperfluidBatchCallTest is FoundrySuperfluidTester { sf.host.batchCall(ops); } - function testDMZForwarder2771() public { + function test2771ForwardCall() public { DMZForwarder forwarder = new DMZForwarder(); TestContract2771 testContract = new TestContract2771(); @@ -455,7 +455,7 @@ contract SuperfluidBatchCallTest is FoundrySuperfluidTester { vm.stopPrank(); } - function testDMZForwarder2771BatchCall() public { + function test2771ForwardCallBatchCall() public { TestContract2771Checked testContract = new TestContract2771Checked(sf.host); testContract.transferOwnership(alice); @@ -479,7 +479,7 @@ contract SuperfluidBatchCallTest is FoundrySuperfluidTester { vm.stopPrank(); } - function testDMZForwarderBatchCallWithValue() public { + function testSimpleForwardCallBatchCallWithValue() public { TestContract testContract = new TestContract(); uint256 amount = 42; @@ -501,7 +501,7 @@ contract SuperfluidBatchCallTest is FoundrySuperfluidTester { assertEq(address(testContract).balance, amount, "TestContract: unexpected balance"); } - function testDMZForwarderBatchCallWithValueUsingReceiveFn() public { + function testSimpleForwardCallBatchCallWithValueUsingReceiveFn() public { TestContract testContract = new TestContract(); uint256 amount = 42; @@ -522,7 +522,7 @@ contract SuperfluidBatchCallTest is FoundrySuperfluidTester { assertEq(address(testContract).balance, amount, "TestContract: unexpected balance"); } - function testDMZForwarderBatchCallWithValueUnsupportedOrder() public { + function testSimpleForwardCallBatchCallWithValueUnsupportedOpsOrder() public { TestContract testContract = new TestContract(); uint256 amount = 42; @@ -544,7 +544,7 @@ contract SuperfluidBatchCallTest is FoundrySuperfluidTester { sf.host.batchCall{value: amount}(ops); } - function testDMZForwarder2771BatchCallWithValue() public { + function test2771ForwardCallBatchCallWithValue() public { TestContract2771Checked testContract = new TestContract2771Checked(sf.host); testContract.transferOwnership(alice); @@ -567,4 +567,42 @@ contract SuperfluidBatchCallTest is FoundrySuperfluidTester { assertEq(address(testContract).balance, amount, "TestContract: unexpected test contract balance"); vm.stopPrank(); } + + function testRefundFromBatchCall() public { + uint256 amount = 42; + address sender = alice; + + vm.deal(sender, 1 ether); + uint256 senderBalanceBefore = sender.balance; + vm.startPrank(sender); + sf.host.batchCall{value: amount}(new ISuperfluid.Operation[](0)); + vm.stopPrank(); + // no operation "consumed" the native tokens: we expect full refund + assertEq(sender.balance, senderBalanceBefore, "batchCall sender: unexpected balance"); + assertEq(address(sf.host).balance, 0, "batchCall host: native tokens left"); + } + + function testWithdrawLostNativeTokensFromDMZForwarder() public { + uint256 amount = 42; + + DMZForwarder forwarder = new DMZForwarder(); + + // failing call which causes `amount` to get stuck in the forwarder contract + (bool success, bytes memory returnValue) = forwarder.forwardCall{value: amount}( + address(sf.host), new bytes(0x1)); + + assertFalse(success, "DMZForwarder: call should have failed"); + assertEq(address(forwarder).balance, amount, "DMZForwarder: unexpected balance"); + + // eve isn't allowed to withdraw + vm.startPrank(eve); + vm.expectRevert("Ownable: caller is not the owner"); + forwarder.withdrawLostNativeTokens(payable(bob)); + vm.stopPrank(); + + // but we can withdraw + forwarder.withdrawLostNativeTokens(payable(bob)); + assertEq(address(forwarder).balance, 0, "DMZForwarder: balance still not 0"); + assertEq(bob.balance, amount, "DMZForwarder: where did the money go?"); + } } From b511690f4b1a3de9dd87b592a3a5365bcaaf4d92 Mon Sep 17 00:00:00 2001 From: didi Date: Mon, 1 Jul 2024 15:49:35 +0200 Subject: [PATCH 23/35] made host.forwardBatchCall payable too --- .../interfaces/superfluid/ISuperfluid.sol | 6 ++-- .../contracts/superfluid/Superfluid.sol | 2 +- .../superfluid/Superfluid.BatchCall.t.sol | 29 +++++++++++++++++-- 3 files changed, 31 insertions(+), 6 deletions(-) diff --git a/packages/ethereum-contracts/contracts/interfaces/superfluid/ISuperfluid.sol b/packages/ethereum-contracts/contracts/interfaces/superfluid/ISuperfluid.sol index 8d158f4c59..cf660e9a10 100644 --- a/packages/ethereum-contracts/contracts/interfaces/superfluid/ISuperfluid.sol +++ b/packages/ethereum-contracts/contracts/interfaces/superfluid/ISuperfluid.sol @@ -642,9 +642,11 @@ interface ISuperfluid { * @param operations Array of batch operations * * NOTE: This can be called only by contracts recognized as _trusted forwarder_ - * by the host contract (see `Superfluid.isTrustedForwarder`) + * by the host contract (see `Superfluid.isTrustedForwarder`). + * If native tokens are passed along, the same rules as for `batchCall` apply, + * with an optional refund going to the encoded msgSender. */ - function forwardBatchCall(Operation[] calldata operations) external; + function forwardBatchCall(Operation[] calldata operations) external payable; /************************************************************************** * Function modifiers for access control and parameter validations diff --git a/packages/ethereum-contracts/contracts/superfluid/Superfluid.sol b/packages/ethereum-contracts/contracts/superfluid/Superfluid.sol index 66d7dbe6ea..a4fbedf23d 100644 --- a/packages/ethereum-contracts/contracts/superfluid/Superfluid.sol +++ b/packages/ethereum-contracts/contracts/superfluid/Superfluid.sol @@ -919,7 +919,7 @@ contract Superfluid is /// @dev ISuperfluid.forwardBatchCall implementation function forwardBatchCall(Operation[] calldata operations) - external override + external override payable { _batchCall(_getTransactionSigner(), operations); } diff --git a/packages/ethereum-contracts/test/foundry/superfluid/Superfluid.BatchCall.t.sol b/packages/ethereum-contracts/test/foundry/superfluid/Superfluid.BatchCall.t.sol index 6f9ddabfb0..6571cf6814 100644 --- a/packages/ethereum-contracts/test/foundry/superfluid/Superfluid.BatchCall.t.sol +++ b/packages/ethereum-contracts/test/foundry/superfluid/Superfluid.BatchCall.t.sol @@ -5,9 +5,8 @@ import { stdError } from "forge-std/Test.sol"; import { BatchOperation, ISuperfluid, Superfluid } from "../../../contracts/superfluid/Superfluid.sol"; import { SuperToken } from "../../../contracts/superfluid/SuperToken.sol"; -import { IConstantFlowAgreementV1 } from "../../../contracts/interfaces/agreements/IConstantFlowAgreementV1.sol"; import { IGeneralDistributionAgreementV1, ISuperfluidPool, PoolConfig } from "../../../contracts/interfaces/agreements/gdav1/IGeneralDistributionAgreementV1.sol"; -import { ISuperfluidToken } from "../../../contracts/interfaces/superfluid/ISuperfluidToken.sol"; +import { IConstantFlowAgreementV1, ISuperToken, ISuperfluidToken } from "../../../contracts/interfaces/superfluid/ISuperfluid.sol"; import { FoundrySuperfluidTester } from "../FoundrySuperfluidTester.sol"; import { SuperTokenV1Library } from "../../../contracts/apps/SuperTokenV1Library.sol"; import { SuperAppMock } from "../../../contracts/mocks/SuperAppMocks.sol"; @@ -88,8 +87,17 @@ contract TestContract2771Checked is TestContract2771 { contract SuperfluidBatchCallTest is FoundrySuperfluidTester { using SuperTokenV1Library for SuperToken; + address someTrustedForwarder = address(0x1a1c); + constructor() FoundrySuperfluidTester(3) { } + function setUp() public override { + super.setUp(); + vm.startPrank(address(sf.governance.owner())); + sf.governance.enableTrustedForwarder(sf.host, ISuperToken(address(0)), someTrustedForwarder); + vm.stopPrank(); + } + function testRevertIfOperationIncreaseAllowanceIsNotCalledByHost(address notHost) public { vm.assume(notHost != address(sf.host)); @@ -582,13 +590,28 @@ contract SuperfluidBatchCallTest is FoundrySuperfluidTester { assertEq(address(sf.host).balance, 0, "batchCall host: native tokens left"); } + function testRefundFromForwardBatchCall() public { + uint256 amount = 42; + + vm.deal(someTrustedForwarder, 1 ether); + vm.startPrank(someTrustedForwarder); + bytes memory data = abi.encodeCall(sf.host.forwardBatchCall, (new ISuperfluid.Operation[](0))); + // bob is 2771-encoded as msgSender + (bool success, ) = address(sf.host).call{value: amount}(abi.encodePacked(data, bob)); + vm.stopPrank(); + // no operation "consumed" the native tokens: we expect full refund to bob + assertTrue(success, "forwardBatchCall: call failed"); + assertEq(bob.balance, amount, "batchCall msgSender: unexpected balance"); + assertEq(address(sf.host).balance, 0, "batchCall host: native tokens left"); + } + function testWithdrawLostNativeTokensFromDMZForwarder() public { uint256 amount = 42; DMZForwarder forwarder = new DMZForwarder(); // failing call which causes `amount` to get stuck in the forwarder contract - (bool success, bytes memory returnValue) = forwarder.forwardCall{value: amount}( + (bool success, ) = forwarder.forwardCall{value: amount}( address(sf.host), new bytes(0x1)); assertFalse(success, "DMZForwarder: call should have failed"); From 3f4c8e3f8cd6dcb526b682d296f54979e8eeb604 Mon Sep 17 00:00:00 2001 From: "Miao, ZhiCheng" Date: Mon, 1 Jul 2024 19:05:49 +0300 Subject: [PATCH 24/35] rename to GeneralDistributionAgreementV1.prop.t.sol --- ...ementV1.prop.sol => GeneralDistributionAgreementV1.prop.t.sol} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename packages/ethereum-contracts/test/foundry/agreements/gdav1/{GeneralDistributionAgreementV1.prop.sol => GeneralDistributionAgreementV1.prop.t.sol} (100%) diff --git a/packages/ethereum-contracts/test/foundry/agreements/gdav1/GeneralDistributionAgreementV1.prop.sol b/packages/ethereum-contracts/test/foundry/agreements/gdav1/GeneralDistributionAgreementV1.prop.t.sol similarity index 100% rename from packages/ethereum-contracts/test/foundry/agreements/gdav1/GeneralDistributionAgreementV1.prop.sol rename to packages/ethereum-contracts/test/foundry/agreements/gdav1/GeneralDistributionAgreementV1.prop.t.sol From 913d39dbeeaa52160188389ad886d11b3c3a24ee Mon Sep 17 00:00:00 2001 From: "Miao, ZhiCheng" Date: Mon, 1 Jul 2024 20:11:52 +0300 Subject: [PATCH 25/35] update foundry --- flake.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/flake.lock b/flake.lock index e01a8e212a..83f4a97425 100644 --- a/flake.lock +++ b/flake.lock @@ -28,11 +28,11 @@ ] }, "locked": { - "lastModified": 1707037862, - "narHash": "sha256-jCNrmFDx+neh7Uz0Q2kmqz19Yyz8OxnGoZpzd2w3SME=", + "lastModified": 1717405880, + "narHash": "sha256-qcXXOnRSl0sGKm7JknntBU4su8/342YKZvjklHsIl+Q=", "owner": "shazow", "repo": "foundry.nix", - "rev": "03b8af1efb00c51dceaac92462dc77b1b57683e0", + "rev": "708c0df1e36b5185a727a3c517a5100e46392792", "type": "github" }, "original": { From 43176b0124b1b88d65f5a41cb6618b6857db467f Mon Sep 17 00:00:00 2001 From: "Miao, ZhiCheng" Date: Mon, 1 Jul 2024 21:27:08 +0300 Subject: [PATCH 26/35] use assembly destructor to have different error code --- .../contracts/mocks/SuperfluidDestructorMock.sol | 2 +- packages/ethereum-contracts/foundry.toml | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/ethereum-contracts/contracts/mocks/SuperfluidDestructorMock.sol b/packages/ethereum-contracts/contracts/mocks/SuperfluidDestructorMock.sol index f9a1f97109..fb2f3dbe8f 100644 --- a/packages/ethereum-contracts/contracts/mocks/SuperfluidDestructorMock.sol +++ b/packages/ethereum-contracts/contracts/mocks/SuperfluidDestructorMock.sol @@ -8,7 +8,7 @@ contract SuperfluidDestructorMock { fallback() external { // this == impl in this call - selfdestruct(payable(0)); + assembly { selfdestruct(0) } } } diff --git a/packages/ethereum-contracts/foundry.toml b/packages/ethereum-contracts/foundry.toml index 55b6f5502f..052a997e02 100644 --- a/packages/ethereum-contracts/foundry.toml +++ b/packages/ethereum-contracts/foundry.toml @@ -1,10 +1,12 @@ [profile.default] root = '../..' src = 'packages/ethereum-contracts/contracts' -test = 'packages/ethereum-contracts/test/foundry/' +test = 'packages/ethereum-contracts/test/foundry' solc_version = "0.8.23" -ignored_error_codes = [5159] # selfdestruct in contracts/mocks/SuperfluidDestructorMock.sol -# deny_warnings = true +#deny_warnings = true +ignored_error_codes = [ + 1699 # assembly { selfdestruct } in contracts/mocks/SuperfluidDestructorMock.sol +] # keep in sync with truffle-config.js evm_version = 'paris' remappings = [ From 0cd9fc3d4532653c90aced66b4b70f607e3dfb5f Mon Sep 17 00:00:00 2001 From: "Miao, ZhiCheng" Date: Mon, 1 Jul 2024 21:28:01 +0300 Subject: [PATCH 27/35] uncomment out test code --- .../ethereum-contracts/contracts/mocks/SuperfluidMock.sol | 3 --- packages/ethereum-contracts/hardhat.config.ts | 5 ++--- packages/ethereum-contracts/package.json | 3 ++- .../test/contracts/superfluid/Superfluid.test.ts | 2 -- 4 files changed, 4 insertions(+), 9 deletions(-) diff --git a/packages/ethereum-contracts/contracts/mocks/SuperfluidMock.sol b/packages/ethereum-contracts/contracts/mocks/SuperfluidMock.sol index 46f74c83ae..d516c7c338 100644 --- a/packages/ethereum-contracts/contracts/mocks/SuperfluidMock.sol +++ b/packages/ethereum-contracts/contracts/mocks/SuperfluidMock.sol @@ -142,9 +142,7 @@ contract SuperfluidMock is Superfluid { return (n, ctx); } - // disabled code because contract size limit hit // same ABI to afterAgreementCreated - /* function ctxFunc2( address superToken, address agreementClass, @@ -157,7 +155,6 @@ contract SuperfluidMock is Superfluid { { return (superToken, agreementClass, agreementId, agreementData, cbdata, ctx); } - */ function testCtxFuncX(bytes calldata dataWithPlaceHolderCtx, bytes calldata ctx) external view diff --git a/packages/ethereum-contracts/hardhat.config.ts b/packages/ethereum-contracts/hardhat.config.ts index 6dcb708c69..d1453421b5 100644 --- a/packages/ethereum-contracts/hardhat.config.ts +++ b/packages/ethereum-contracts/hardhat.config.ts @@ -151,9 +151,8 @@ const config: HardhatUserConfig = { url: process.env.SCROLL_MAINNET_PROVIDER_URL || "", }, hardhat: { - // Fixing an issue that parallel coverage test is not working for unkown reason. - // Ref: https://github.com/NomicFoundation/hardhat/issues/4310 - allowUnlimitedContractSize: process.env.IS_COVERAGE_TEST ? true : undefined, + // We defer the contract size limit test to foundry. + allowUnlimitedContractSize: true, }, }, mocha: { diff --git a/packages/ethereum-contracts/package.json b/packages/ethereum-contracts/package.json index 63dd9f3dfe..52e4ece8e2 100644 --- a/packages/ethereum-contracts/package.json +++ b/packages/ethereum-contracts/package.json @@ -13,11 +13,12 @@ "/contracts/**/*", "!/contracts/mocks/*", "/build/truffle/*.json", - "/build/hardhat/**/*", "!/build/truffle/*Mock*.json", "!/build/truffle/*Tester*.json", "!/build/truffle/*Anvil.json", "!/build/truffle/*Properties.json", + "/build/hardhat/**/*", + "!/build/hardhat/contracts/mocks/**/*", "/build/contracts-sizes.txt", "/build/bundled-abi.js", "/build/bundled-abi.json", diff --git a/packages/ethereum-contracts/test/contracts/superfluid/Superfluid.test.ts b/packages/ethereum-contracts/test/contracts/superfluid/Superfluid.test.ts index dd3c1cd033..4711f77427 100644 --- a/packages/ethereum-contracts/test/contracts/superfluid/Superfluid.test.ts +++ b/packages/ethereum-contracts/test/contracts/superfluid/Superfluid.test.ts @@ -699,7 +699,6 @@ describe("Superfluid Host Contract", function () { // disabled code because contract size limit hit // more complicated ABI - /* await testCtxFunc( "ctxFunc2", [ @@ -722,7 +721,6 @@ describe("Superfluid Host Contract", function () { ], "0x" + "faec".repeat(20) ); - */ // error case await expectCustomError( From 08ffbc3ebbe44749880b39ed4f45c266050fe039 Mon Sep 17 00:00:00 2001 From: "Miao, ZhiCheng" Date: Mon, 1 Jul 2024 21:44:21 +0300 Subject: [PATCH 28/35] update foundry version --- flake.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/flake.lock b/flake.lock index e01a8e212a..83f4a97425 100644 --- a/flake.lock +++ b/flake.lock @@ -28,11 +28,11 @@ ] }, "locked": { - "lastModified": 1707037862, - "narHash": "sha256-jCNrmFDx+neh7Uz0Q2kmqz19Yyz8OxnGoZpzd2w3SME=", + "lastModified": 1717405880, + "narHash": "sha256-qcXXOnRSl0sGKm7JknntBU4su8/342YKZvjklHsIl+Q=", "owner": "shazow", "repo": "foundry.nix", - "rev": "03b8af1efb00c51dceaac92462dc77b1b57683e0", + "rev": "708c0df1e36b5185a727a3c517a5100e46392792", "type": "github" }, "original": { From 77dae62948a2e7192fda26451362bc47edecdfe0 Mon Sep 17 00:00:00 2001 From: "Miao, ZhiCheng" Date: Mon, 1 Jul 2024 21:45:09 +0300 Subject: [PATCH 29/35] [ETHEREUM-CONTRACTS]: typo fixes --- .../agreements/gdav1/GeneralDistributionAgreement.t.sol | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/ethereum-contracts/test/foundry/agreements/gdav1/GeneralDistributionAgreement.t.sol b/packages/ethereum-contracts/test/foundry/agreements/gdav1/GeneralDistributionAgreement.t.sol index 12e2ff9aee..c95b0c52ec 100644 --- a/packages/ethereum-contracts/test/foundry/agreements/gdav1/GeneralDistributionAgreement.t.sol +++ b/packages/ethereum-contracts/test/foundry/agreements/gdav1/GeneralDistributionAgreement.t.sol @@ -559,9 +559,9 @@ contract GeneralDistributionAgreementV1IntegrationTest is FoundrySuperfluidTeste for (uint256 i = 0; i < members.length; ++i) { if (sf.gda.isPool(superToken, members[i]) || members[i] == address(0)) continue; - uint128 memberUnits = pool.getUnits(members[i]); + uint128 memberIUnits = pool.getUnits(members[i]); - assertEq(perUnitDistributionAmount * memberUnits, pool.getTotalAmountReceivedByMember(members[i])); + assertEq(perUnitDistributionAmount * memberIUnits, pool.getTotalAmountReceivedByMember(members[i])); } } @@ -594,9 +594,9 @@ contract GeneralDistributionAgreementV1IntegrationTest is FoundrySuperfluidTeste for (uint256 i = 0; i < members.length; ++i) { if (sf.gda.isPool(superToken, members[i]) || members[i] == address(0)) continue; - uint128 memberUnits = pool.getUnits(members[i]); + uint128 memberIUnits = pool.getUnits(members[i]); - assertEq(perUnitDistributionAmount * memberUnits, pool.getTotalAmountReceivedByMember(members[i])); + assertEq(perUnitDistributionAmount * memberIUnits, pool.getTotalAmountReceivedByMember(members[i])); } } From 5d7011b8a6b925583c0917acefc556d2dc4fb557 Mon Sep 17 00:00:00 2001 From: "Miao, ZhiCheng" Date: Mon, 1 Jul 2024 21:45:37 +0300 Subject: [PATCH 30/35] [ETHEREUM-CONTRACTS] rename prop.sol to prop.t.sol --- ...lowAgreementV1.prop.sol => ConstantFlowAgreementV1.prop.t.sol} | 0 ...ementV1.prop.sol => GeneralDistributionAgreementV1.prop.t.sol} | 0 .../{AgreementLibrary.prop.sol => AgreementLibrary.prop.t.sol} | 0 ...{SlotsBitmapLibrary.prop.sol => SlotsBitmapLibrary.prop.t.sol} | 0 .../superfluid/{FlowNFTBase.prop.sol => FlowNFTBase.prop.t.sol} | 0 .../{SuperfluidPool.prop.sol => SuperfluidPool.prop.t.sol} | 0 ...lowAgreementV1.prop.sol => ConstantFlowAgreementV1.prop.t.sol} | 0 7 files changed, 0 insertions(+), 0 deletions(-) rename packages/ethereum-contracts/test/foundry/agreements/{ConstantFlowAgreementV1.prop.sol => ConstantFlowAgreementV1.prop.t.sol} (100%) rename packages/ethereum-contracts/test/foundry/agreements/gdav1/{GeneralDistributionAgreementV1.prop.sol => GeneralDistributionAgreementV1.prop.t.sol} (100%) rename packages/ethereum-contracts/test/foundry/libs/{AgreementLibrary.prop.sol => AgreementLibrary.prop.t.sol} (100%) rename packages/ethereum-contracts/test/foundry/libs/{SlotsBitmapLibrary.prop.sol => SlotsBitmapLibrary.prop.t.sol} (100%) rename packages/ethereum-contracts/test/foundry/superfluid/{FlowNFTBase.prop.sol => FlowNFTBase.prop.t.sol} (100%) rename packages/ethereum-contracts/test/foundry/superfluid/{SuperfluidPool.prop.sol => SuperfluidPool.prop.t.sol} (100%) rename packages/hot-fuzz/contracts/superfluid-tests/{ConstantFlowAgreementV1.prop.sol => ConstantFlowAgreementV1.prop.t.sol} (100%) diff --git a/packages/ethereum-contracts/test/foundry/agreements/ConstantFlowAgreementV1.prop.sol b/packages/ethereum-contracts/test/foundry/agreements/ConstantFlowAgreementV1.prop.t.sol similarity index 100% rename from packages/ethereum-contracts/test/foundry/agreements/ConstantFlowAgreementV1.prop.sol rename to packages/ethereum-contracts/test/foundry/agreements/ConstantFlowAgreementV1.prop.t.sol diff --git a/packages/ethereum-contracts/test/foundry/agreements/gdav1/GeneralDistributionAgreementV1.prop.sol b/packages/ethereum-contracts/test/foundry/agreements/gdav1/GeneralDistributionAgreementV1.prop.t.sol similarity index 100% rename from packages/ethereum-contracts/test/foundry/agreements/gdav1/GeneralDistributionAgreementV1.prop.sol rename to packages/ethereum-contracts/test/foundry/agreements/gdav1/GeneralDistributionAgreementV1.prop.t.sol diff --git a/packages/ethereum-contracts/test/foundry/libs/AgreementLibrary.prop.sol b/packages/ethereum-contracts/test/foundry/libs/AgreementLibrary.prop.t.sol similarity index 100% rename from packages/ethereum-contracts/test/foundry/libs/AgreementLibrary.prop.sol rename to packages/ethereum-contracts/test/foundry/libs/AgreementLibrary.prop.t.sol diff --git a/packages/ethereum-contracts/test/foundry/libs/SlotsBitmapLibrary.prop.sol b/packages/ethereum-contracts/test/foundry/libs/SlotsBitmapLibrary.prop.t.sol similarity index 100% rename from packages/ethereum-contracts/test/foundry/libs/SlotsBitmapLibrary.prop.sol rename to packages/ethereum-contracts/test/foundry/libs/SlotsBitmapLibrary.prop.t.sol diff --git a/packages/ethereum-contracts/test/foundry/superfluid/FlowNFTBase.prop.sol b/packages/ethereum-contracts/test/foundry/superfluid/FlowNFTBase.prop.t.sol similarity index 100% rename from packages/ethereum-contracts/test/foundry/superfluid/FlowNFTBase.prop.sol rename to packages/ethereum-contracts/test/foundry/superfluid/FlowNFTBase.prop.t.sol diff --git a/packages/ethereum-contracts/test/foundry/superfluid/SuperfluidPool.prop.sol b/packages/ethereum-contracts/test/foundry/superfluid/SuperfluidPool.prop.t.sol similarity index 100% rename from packages/ethereum-contracts/test/foundry/superfluid/SuperfluidPool.prop.sol rename to packages/ethereum-contracts/test/foundry/superfluid/SuperfluidPool.prop.t.sol diff --git a/packages/hot-fuzz/contracts/superfluid-tests/ConstantFlowAgreementV1.prop.sol b/packages/hot-fuzz/contracts/superfluid-tests/ConstantFlowAgreementV1.prop.t.sol similarity index 100% rename from packages/hot-fuzz/contracts/superfluid-tests/ConstantFlowAgreementV1.prop.sol rename to packages/hot-fuzz/contracts/superfluid-tests/ConstantFlowAgreementV1.prop.t.sol From fb2654a5d1420a9d5cfde84df3d45e5de05ab01b Mon Sep 17 00:00:00 2001 From: "Miao, ZhiCheng" Date: Mon, 1 Jul 2024 21:49:16 +0300 Subject: [PATCH 31/35] fix build warning --- .../contracts/apps/SuperTokenV1Library.sol | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/ethereum-contracts/contracts/apps/SuperTokenV1Library.sol b/packages/ethereum-contracts/contracts/apps/SuperTokenV1Library.sol index 286a65e093..b389f658f6 100644 --- a/packages/ethereum-contracts/contracts/apps/SuperTokenV1Library.sol +++ b/packages/ethereum-contracts/contracts/apps/SuperTokenV1Library.sol @@ -837,7 +837,7 @@ library SuperTokenV1Library { * @dev get flow info of a distributor to a pool for given token * @param token The token used in flow * @param distributor The sitributor of the flow - * @param pool The GDA pool + * @param pool The GDA pool * @return lastUpdated Timestamp of flow creation or last flowrate change * @return flowRate The flow rate * @return deposit The amount of deposit the flow @@ -922,7 +922,7 @@ library SuperTokenV1Library { deposit += cfaDeposit; owedDeposit += cfaOwedDeposit; } - + { (uint256 lastUpdatedGDA, int96 gdaNetFlowRate, uint256 gdaDeposit) = gda.getAccountFlowInfo(token, account); @@ -964,10 +964,11 @@ library SuperTokenV1Library { function getGDANetFlowInfo(ISuperToken token, address account) internal view - returns (uint256 lastUpdated, int96 flowRate, uint256 deposit, uint256 /* owedDeposit unused */) + returns (uint256 lastUpdated, int96 flowRate, uint256 deposit, uint256 owedDeposit) { (, IGeneralDistributionAgreementV1 gda) = _getHostAndGDA(token); (lastUpdated, flowRate, deposit) = gda.getAccountFlowInfo(token, account); + owedDeposit = 0; // unused in GDA } /** From 4833be0e78f1bd47f01f87c376279496d29f9834 Mon Sep 17 00:00:00 2001 From: "Miao, ZhiCheng" Date: Mon, 1 Jul 2024 21:49:46 +0300 Subject: [PATCH 32/35] ignore mocks from hardhat build --- packages/ethereum-contracts/package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/ethereum-contracts/package.json b/packages/ethereum-contracts/package.json index 63dd9f3dfe..52e4ece8e2 100644 --- a/packages/ethereum-contracts/package.json +++ b/packages/ethereum-contracts/package.json @@ -13,11 +13,12 @@ "/contracts/**/*", "!/contracts/mocks/*", "/build/truffle/*.json", - "/build/hardhat/**/*", "!/build/truffle/*Mock*.json", "!/build/truffle/*Tester*.json", "!/build/truffle/*Anvil.json", "!/build/truffle/*Properties.json", + "/build/hardhat/**/*", + "!/build/hardhat/contracts/mocks/**/*", "/build/contracts-sizes.txt", "/build/bundled-abi.js", "/build/bundled-abi.json", From d8b9e1752522061c837b66224c3cfecb2b6b7e82 Mon Sep 17 00:00:00 2001 From: "Miao, ZhiCheng" Date: Mon, 1 Jul 2024 21:50:40 +0300 Subject: [PATCH 33/35] use assembly selfdestruct for now --- .../contracts/mocks/SuperfluidDestructorMock.sol | 2 +- packages/ethereum-contracts/foundry.toml | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/ethereum-contracts/contracts/mocks/SuperfluidDestructorMock.sol b/packages/ethereum-contracts/contracts/mocks/SuperfluidDestructorMock.sol index f9a1f97109..fb2f3dbe8f 100644 --- a/packages/ethereum-contracts/contracts/mocks/SuperfluidDestructorMock.sol +++ b/packages/ethereum-contracts/contracts/mocks/SuperfluidDestructorMock.sol @@ -8,7 +8,7 @@ contract SuperfluidDestructorMock { fallback() external { // this == impl in this call - selfdestruct(payable(0)); + assembly { selfdestruct(0) } } } diff --git a/packages/ethereum-contracts/foundry.toml b/packages/ethereum-contracts/foundry.toml index 55b6f5502f..052a997e02 100644 --- a/packages/ethereum-contracts/foundry.toml +++ b/packages/ethereum-contracts/foundry.toml @@ -1,10 +1,12 @@ [profile.default] root = '../..' src = 'packages/ethereum-contracts/contracts' -test = 'packages/ethereum-contracts/test/foundry/' +test = 'packages/ethereum-contracts/test/foundry' solc_version = "0.8.23" -ignored_error_codes = [5159] # selfdestruct in contracts/mocks/SuperfluidDestructorMock.sol -# deny_warnings = true +#deny_warnings = true +ignored_error_codes = [ + 1699 # assembly { selfdestruct } in contracts/mocks/SuperfluidDestructorMock.sol +] # keep in sync with truffle-config.js evm_version = 'paris' remappings = [ From 287e1c2c5a98096396e65acc6152a34ff10280df Mon Sep 17 00:00:00 2001 From: "Miao, ZhiCheng" Date: Mon, 1 Jul 2024 22:00:42 +0300 Subject: [PATCH 34/35] fix build warning --- .../ethereum-contracts/test/foundry/FoundrySuperfluidTester.sol | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/ethereum-contracts/test/foundry/FoundrySuperfluidTester.sol b/packages/ethereum-contracts/test/foundry/FoundrySuperfluidTester.sol index ce0477ddea..9dd1946818 100644 --- a/packages/ethereum-contracts/test/foundry/FoundrySuperfluidTester.sol +++ b/packages/ethereum-contracts/test/foundry/FoundrySuperfluidTester.sol @@ -1606,7 +1606,6 @@ contract FoundrySuperfluidTester is Test { PoolUnitData memory poolUnitDataBefore = _helperGetPoolUnitsData(pool_); - (int256 claimableBalance,) = pool_.getClaimableNow(member_); (int256 balanceBefore,,,) = poolSuperToken.realtimeBalanceOfNow(member_); { _updateMemberUnits(pool_, poolSuperToken, caller_, member_, newUnits_, useBools_); From 5fceaf1d5fbf740e48d3463a55695edd20d45216 Mon Sep 17 00:00:00 2001 From: "Miao, ZhiCheng" Date: Mon, 1 Jul 2024 22:07:20 +0300 Subject: [PATCH 35/35] update CHANGELOG --- packages/ethereum-contracts/CHANGELOG.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/packages/ethereum-contracts/CHANGELOG.md b/packages/ethereum-contracts/CHANGELOG.md index 76bd6f606e..d1b5f4420c 100644 --- a/packages/ethereum-contracts/CHANGELOG.md +++ b/packages/ethereum-contracts/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to the ethereum-contracts will be documented in this file. This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [unreleased] + +### Changed + +- fix a few types and build warnings +- rename '.prop.sol' to '.prop.t.sol' +- upgrade flake lock input: foundry + ## [v1.9.1] - 2024-03-19 ### Breaking