// SPDX-License-Identifier: AGPLv3 pragma solidity 0.7.6; pragma experimental ABIEncoderV2; import { ISuperfluidGovernance, ISuperfluid, ISuperfluidToken, ISuperApp, SuperAppDefinitions, ContextDefinitions } from "../interfaces/superfluid/ISuperfluid.sol"; import { ISuperfluidToken } from "../interfaces/superfluid/ISuperfluidToken.sol"; import { SignedSafeMath } from "@openzeppelin/contracts/math/SignedSafeMath.sol"; import { SafeCast } from "@openzeppelin/contracts/utils/SafeCast.sol"; /** * @dev Helper library for building super agreement */ library AgreementLibrary { using SignedSafeMath for int256; using SafeCast for uint256; using SafeCast for int256; /************************************************************************** * Context helpers *************************************************************************/ /** * @dev Authorize the msg.sender to access token agreement storage * * NOTE: * - msg.sender must be the expected host contract. * - it should revert on unauthorized access. */ function authorizeTokenAccess(ISuperfluidToken token, bytes memory ctx) internal view returns (ISuperfluid.Context memory) { require(token.getHost() == msg.sender, "AgreementLibrary: unauthroized host"); // ruleid: superfluid-ctx-injection return ISuperfluid(msg.sender).decodeCtx(ctx); } /************************************************************************** * Agreement callback helpers *************************************************************************/ struct CallbackInputs { ISuperfluidToken token; address account; bytes32 agreementId; bytes agreementData; uint256 appAllowanceGranted; int256 appAllowanceUsed; uint256 noopBit; } function createCallbackInputs( ISuperfluidToken token, address account, bytes32 agreementId, bytes memory agreementData ) internal pure returns (CallbackInputs memory inputs) { inputs.token = token; inputs.account = account; inputs.agreementId = agreementId; inputs.agreementData = agreementData; } function callAppBeforeCallback( CallbackInputs memory inputs, bytes memory ctx ) internal returns(bytes memory cbdata) { bool isSuperApp; bool isJailed; uint256 noopMask; (isSuperApp, isJailed, noopMask) = ISuperfluid(msg.sender).getAppManifest(ISuperApp(inputs.account)); if (isSuperApp && !isJailed) { bytes memory appCtx = _pushCallbackStack(ctx, inputs); if ((noopMask & inputs.noopBit) == 0) { bytes memory callData = abi.encodeWithSelector( _selectorFromNoopBit(inputs.noopBit), inputs.token, address(this) /* agreementClass */, inputs.agreementId, inputs.agreementData, new bytes(0) // placeholder ctx ); cbdata = ISuperfluid(msg.sender).callAppBeforeCallback( ISuperApp(inputs.account), callData, inputs.noopBit == SuperAppDefinitions.BEFORE_AGREEMENT_TERMINATED_NOOP, appCtx); } _popCallbackStack(ctx, 0); } } function callAppAfterCallback( CallbackInputs memory inputs, bytes memory cbdata, bytes memory ctx ) internal returns (ISuperfluid.Context memory appContext, bytes memory newCtx) { bool isSuperApp; bool isJailed; uint256 noopMask; (isSuperApp, isJailed, noopMask) = ISuperfluid(msg.sender).getAppManifest(ISuperApp(inputs.account)); if (isSuperApp && !isJailed) { newCtx = _pushCallbackStack(ctx, inputs); if ((noopMask & inputs.noopBit) == 0) { bytes memory callData = abi.encodeWithSelector( _selectorFromNoopBit(inputs.noopBit), inputs.token, address(this) /* agreementClass */, inputs.agreementId, inputs.agreementData, cbdata, new bytes(0) // placeholder ctx ); newCtx = ISuperfluid(msg.sender).callAppAfterCallback( ISuperApp(inputs.account), callData, inputs.noopBit == SuperAppDefinitions.AFTER_AGREEMENT_TERMINATED_NOOP, newCtx); appContext = ISuperfluid(msg.sender).decodeCtx(newCtx); // adjust allowance used to the range [appAllowanceWanted..appAllowanceGranted] appContext.appAllowanceUsed = max(0, min( inputs.appAllowanceGranted.toInt256(), max(appContext.appAllowanceWanted.toInt256(), appContext.appAllowanceUsed))); } newCtx = _popCallbackStack(ctx, appContext.appAllowanceUsed); } } function _selectorFromNoopBit(uint256 noopBit) private pure returns (bytes4 selector) { if (noopBit == SuperAppDefinitions.BEFORE_AGREEMENT_CREATED_NOOP) { return ISuperApp.beforeAgreementCreated.selector; } else if (noopBit == SuperAppDefinitions.BEFORE_AGREEMENT_UPDATED_NOOP) { return ISuperApp.beforeAgreementUpdated.selector; } else if (noopBit == SuperAppDefinitions.BEFORE_AGREEMENT_TERMINATED_NOOP) { return ISuperApp.beforeAgreementTerminated.selector; } else if (noopBit == SuperAppDefinitions.AFTER_AGREEMENT_CREATED_NOOP) { return ISuperApp.afterAgreementCreated.selector; } else if (noopBit == SuperAppDefinitions.AFTER_AGREEMENT_UPDATED_NOOP) { return ISuperApp.afterAgreementUpdated.selector; } else /* if (noopBit == SuperAppDefinitions.AFTER_AGREEMENT_TERMINATED_NOOP) */ { return ISuperApp.afterAgreementTerminated.selector; } } function _pushCallbackStack( bytes memory ctx, CallbackInputs memory inputs ) private returns (bytes memory appCtx) { // app allowance params stack PUSH // pass app allowance and current allowance used to the app, appCtx = ISuperfluid(msg.sender).appCallbackPush( ctx, ISuperApp(inputs.account), inputs.appAllowanceGranted, inputs.appAllowanceUsed, inputs.token); } function _popCallbackStack( bytes memory ctx, int256 appAllowanceUsedDelta ) private returns (bytes memory newCtx) { // app allowance params stack POP return ISuperfluid(msg.sender).appCallbackPop(ctx, appAllowanceUsedDelta); } /************************************************************************** * Misc *************************************************************************/ function max(int256 a, int256 b) internal pure returns (int256) { return a > b ? a : b; } function min(int256 a, int256 b) internal pure returns (int256) { return a > b ? b : a; } }