diff --git a/packages/ethereum-contracts/CHANGELOG.md b/packages/ethereum-contracts/CHANGELOG.md index adabe798ef..14538d950f 100644 --- a/packages/ethereum-contracts/CHANGELOG.md +++ b/packages/ethereum-contracts/CHANGELOG.md @@ -9,6 +9,16 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm - ISuperfuidPool self-transfer is not allowed +### 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. + ### Changed - fix a few types and build warnings diff --git a/packages/ethereum-contracts/contracts/interfaces/superfluid/Definitions.sol b/packages/ethereum-contracts/contracts/interfaces/superfluid/Definitions.sol index f9687901a1..7a29d7e775 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 * @@ -212,6 +230,41 @@ 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; + /** + * @dev DMZForwarder.forward2771Call batch operation type + * + * Call spec: + * forward2771Call( + * target, + * 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 ef62a0e93c..30d7e95084 100644 --- a/packages/ethereum-contracts/contracts/interfaces/superfluid/ISuperToken.sol +++ b/packages/ethereum-contracts/contracts/interfaces/superfluid/ISuperToken.sol @@ -578,6 +578,28 @@ 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 to The account to receive upgraded tokens + * @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 downgraded 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/interfaces/superfluid/ISuperfluid.sol b/packages/ethereum-contracts/contracts/interfaces/superfluid/ISuperfluid.sol index b6abefb953..cf660e9a10 100644 --- a/packages/ethereum-contracts/contracts/interfaces/superfluid/ISuperfluid.sol +++ b/packages/ethereum-contracts/contracts/interfaces/superfluid/ISuperfluid.sol @@ -622,14 +622,31 @@ 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`). + * 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/mocks/SuperfluidMock.sol b/packages/ethereum-contracts/contracts/mocks/SuperfluidMock.sol index 12d68d7bf9..d516c7c338 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 { } @@ -129,9 +129,8 @@ contract SuperfluidUpgradabilityTester is Superfluid { contract SuperfluidMock is Superfluid { - - constructor(bool nonUpgradable, bool appWhiteListingEnabled) - Superfluid(nonUpgradable, appWhiteListingEnabled) + constructor(bool nonUpgradable, bool appWhiteListingEnabled, address dmzForwarder) + Superfluid(nonUpgradable, appWhiteListingEnabled, dmzForwarder) // solhint-disable-next-line no-empty-blocks { } @@ -173,5 +172,4 @@ contract SuperfluidMock is Superfluid { { _jailApp(app, 6942); } - } 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..a4fbedf23d 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 * @@ -95,9 +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 = DMZForwarder(dmzForwarderAddress); } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -312,7 +316,7 @@ contract Superfluid is //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Superfluid Upgradeable Beacon //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - + /// @inheritdoc ISuperfluid function updatePoolBeaconLogic(address newLogic) external override onlyGovernance { GeneralDistributionAgreementV1 gda = GeneralDistributionAgreementV1( @@ -794,12 +798,11 @@ contract Superfluid is **************************************************************************/ function _batchCall( - address msgSender, + address payable msgSender, Operation[] calldata operations ) internal { - bool valueForwarded = false; for (uint256 i = 0; i < operations.length; ++i) { uint32 operationType = operations[i].operationType; if (operationType == BatchOperation.OPERATION_TYPE_ERC20_APPROVE) { @@ -847,6 +850,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( @@ -854,20 +869,42 @@ 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), - 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{value: address(this).balance}( + operations[i].target, + operations[i].data); + if (!success) { + CallUtils.revertFromReturnedData(returnData); + } + } else if (operationType == BatchOperation.OPERATION_TYPE_ERC2771_FORWARD_CALL) { + (bool success, bytes memory returnData) = + DMZ_FORWARDER.forward2771Call{value: address(this).balance}( + operations[i].target, + msgSender, + operations[i].data); + if (!success) { + CallUtils.revertFromReturnedData(returnData); + } } else { 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. + msgSender.transfer(address(this).balance); } } @@ -877,12 +914,12 @@ contract Superfluid is ) external override payable { - _batchCall(msg.sender, operations); + _batchCall(payable(msg.sender), operations); } /// @dev ISuperfluid.forwardBatchCall implementation function forwardBatchCall(Operation[] calldata operations) - external override + external override payable { _batchCall(_getTransactionSigner(), operations); } @@ -899,7 +936,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 new file mode 100644 index 0000000000..5c36c8ea7e --- /dev/null +++ b/packages/ethereum-contracts/contracts/utils/DMZForwarder.sol @@ -0,0 +1,50 @@ +// 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 + * alongside Superfluid specific batch operations. + * 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) + { + // solhint-disable-next-line avoid-low-level-calls + (success, returnData) = target.call{value: msg.value}(data); + } + + /** + * @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) + { + // 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 1843a22e7b..943e1f0e45 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,18 @@ library SuperfluidGovDeployerLibrary { } } +library SuperfluidDMZForwarderDeployerLibrary { + // After deploying, you may want to transfer ownership to the host + 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); } } 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 + ), }, } ); 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/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; } 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..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; @@ -120,6 +120,7 @@ describe("SuperTokenV1Library.GDA", function () { ); const receipt = await createPoolTxn.wait(); const poolAddress = getPoolAddressFromReceipt(receipt); + const poolContract = await ethers.getContractAt( "SuperfluidPool", poolAddress diff --git a/packages/ethereum-contracts/test/contracts/superfluid/Superfluid.test.ts b/packages/ethereum-contracts/test/contracts/superfluid/Superfluid.test.ts index 98fe04baf7..4711f77427 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, @@ -662,6 +664,7 @@ describe("Superfluid Host Contract", function () { }); }); + // disabled due to contract size limit describe("#5 Context Utilities", () => { it("#5.1 test replacePlaceholderCtx with testCtxFuncX", async () => { const testCtxFunc = async ( @@ -694,6 +697,7 @@ describe("Superfluid Host Contract", function () { ); } + // disabled code because contract size limit hit // more complicated ABI await testCtxFunc( "ctxFunc2", @@ -701,8 +705,8 @@ describe("Superfluid Host Contract", function () { governance.address, t.contracts.ida.address, ethers.utils.hexZeroPad("0x2020", 32), - "0x" /* agreementData */, - "0x" /* cbdata */, + "0x", // agreementData + "0x", // cbdata ], "0x" + "dead".repeat(20) ); @@ -712,8 +716,8 @@ 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) ); @@ -2697,7 +2701,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( 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..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,17 +5,99 @@ 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 { ISuperfluidToken } from "../../../contracts/interfaces/superfluid/ISuperfluidToken.sol"; +import { IGeneralDistributionAgreementV1, ISuperfluidPool, PoolConfig } from "../../../contracts/interfaces/agreements/gdav1/IGeneralDistributionAgreementV1.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"; +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 TestContract { + error SomeError(); + error IncorrectPayment(); + + bool public stateChanged; + + function permissionlessFn() public returns (bool) { + stateChanged = true; + return true; + } + + // accept native coins + receive() external payable {} + + function pay(uint256 expectedAmount) external payable { + if (msg.value != expectedAmount) revert IncorrectPayment(); + } + + function doRevert() external pure { + revert SomeError(); + } +} + +// A mock for an external contract that uses ERC-2771 +contract TestContract2771 is TestContract, Ownable, BaseRelayRecipient { + error NotOwner(); + + // Expects the msgSender to be encoded in calldata as specified by ERC-2771. + // Will revert if relayed for anybody but the contract owner. + function privilegedFn() public returns (bool) { + if (_getTransactionSigner() != owner()) revert NotOwner(); + stateChanged = true; + 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 + 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(Superfluid host) { + _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; + 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)); @@ -208,4 +290,342 @@ 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"); + } + + 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"); + } + + function testSimpleForwardCall() public { + DMZForwarder forwarder = new DMZForwarder(); + TestContract testContract = new TestContract(); + + (bool success, bytes memory returnValue) = forwarder.forwardCall( + address(testContract), + abi.encodeCall(testContract.permissionlessFn, ()) + ); + // decoded return value + bool retVal = abi.decode(returnValue, (bool)); + assertTrue(success, "DMZForwarder: call failed"); + assertEq(retVal, true, "DMZForwarder: unexpected return value"); + } + + function testSimpleForwardCallBatchCall() public { + TestContract testContract = new TestContract(); + + 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.permissionlessFn, ()) + }); + sf.host.batchCall(ops); + assertEq(testContract.stateChanged(), true, "TestContract: unexpected state"); + } + + function testSimpleForwardCallBatchCallRevert() public { + TestContract testContract = new TestContract(); + + 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(TestContract.SomeError.selector); + sf.host.batchCall(ops); + } + + function test2771ForwardCall() public { + DMZForwarder forwarder = new DMZForwarder(); + + TestContract2771 testContract = new TestContract2771(); + // alice has privileged access to the testContract + testContract.transferOwnership(alice); + + // we relay a call for alice + (bool success, bytes memory returnValue) = forwarder.forward2771Call( + address(testContract), + alice, + abi.encodeCall(testContract.privilegedFn, ()) + ); + // decoded return value + bool retVal = abi.decode(returnValue, (bool)); + assertTrue(success, "DMZForwarder: call failed"); + 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.privilegedFn, ()) + ); + 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.privilegedFn, ()) + ); + vm.stopPrank(); + } + + function test2771ForwardCallBatchCall() public { + TestContract2771Checked testContract = new TestContract2771Checked(sf.host); + testContract.transferOwnership(alice); + + 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.privilegedFn, ()) + }); + + // 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.stateChanged(), true, "TestContract: unexpected state"); + vm.stopPrank(); + } + + function testSimpleForwardCallBatchCallWithValue() 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, ()) + }); + + // 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 testSimpleForwardCallBatchCallWithValueUsingReceiveFn() 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"); + } + + function testSimpleForwardCallBatchCallWithValueUnsupportedOpsOrder() 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 test2771ForwardCallBatchCallWithValue() 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_ERC2771_FORWARD_CALL, + target: address(testContract), + data: abi.encodeCall(testContract.privilegedPay, (amount)) + }); + ops[1] = ISuperfluid.Operation({ + operationType: BatchOperation.OPERATION_TYPE_ERC2771_FORWARD_CALL, + target: address(testContract), + data: abi.encodeCall(testContract.permissionlessFn, ()) + }); + + 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(); + } + + 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 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, ) = 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?"); + } } 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)); } 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"],