From 4739f1ef354726343f3ff022e63d08cda65484b4 Mon Sep 17 00:00:00 2001 From: 0xdavinchee <0xdavinchee@gmail.com> Date: Mon, 14 Nov 2022 15:53:12 +0200 Subject: [PATCH] [ETHEREUM-CONTRACTS] 1.4.3 Release (#1158) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [JS-SDK] 再见 JS-SDK (#1072) * Don't wrap SubgraphClient's errors with SFError (#1075) * [SDK-CORE] 0.5.6 Patch Fix (#1074) * fix subgraphAPIEndpoint for sdk-core * Update constants.ts * third time is the charm * Update 5_subgraph.test.ts * proper env variable name * Update yarn.lock * address comments * isArbGoerli * Get Nix Pilled (#1073) * use nix flake to lock build toolchains such as yarn, foundry, ghc, etc. * added nodejs 16 to dependencies * add flakes/whitehat * add flakes/whitehat * devShells * remove haskell tools * [SDK-CORE] 0.5.6 Patch Fix (#1074) * fix subgraphAPIEndpoint for sdk-core * Update constants.ts * third time is the charm * Update 5_subgraph.test.ts * proper env variable name * Update yarn.lock * address comments * isArbGoerli * updated contributing * typo Co-authored-by: 0xdavinchee <0xdavinchee@gmail.com> * The Great TypeScript Refactor (#1069) * TS WIP - testsuites * tsconfig revamp * convert testsuites files into TS * fix up test:contracts:hardhat accordingly * TS WIP - Typechain * add typechain for ethereum-contracts * TS WIP - UUPS and CallUtils * convert UUPS test and CallUtils test file to typescript * TS WIP - Utility Contract Tests * convert utility contract tests to TypeScript * remove file extension in all-contracts.ts * add some helpers to helpers.js * TS WIP - CFAv1Forwarder Tests * convert CFAv1Forwarder contract tests to TypeScript * TS WIP - Library Tests * convert CFAv1Library and IDAv1Library contract tests to TypeScript * TS WIP - Scenarios Tests * convert scenarios tests to typescript * TS WIP - Scenarios Tests * convert scenarios tests to typescript * TS WIP - SuperfluidGoverannceII tests * convert SuperfluidGovernanceII contract tests to TypeScript * TS WIP - Custom Tokens Tests * convert custom token contract tests to TypeScript * TS WIP - Superfluid Core Tests * convert superfluid core tests to typescript * use import instead of require in testsuite files * TS WIP - Clean up commit pollution * undo some of the variable renaming * TS WIP - Clean up commit pollution (cont.) * undo use of ethers over web3 * TS WIP - Clean up commit pollution (cont.) * TS WIP - Token behavior files * some cleanup * mainly converted token behavior files * TS WIP - CFA/IDA behavior files * converted behavior files for CFA and IDA * TS WIP - Convert peripheries * convert CFADataModel, MFASupport, AgreementHelper, helpers, expectRevert to typescript * TS WIP - Convert TestEnvironment * convert TestEnvironment to typescript * fix MFASupport import * fix TestEnvironment imports * remove module.exports from AgreementHelper * TS WIP - Deprecate JS-SDK remove js-sdk from: - pr-artifact creation - canary tests + coverage - publish-pr-packages - publish-release-packages - codecov flag - root package.json - check-changeset.sh * TS WIP - Add JS-SDK tests * add tests that exist in js-sdk to sdk-core as part of deprecation * TS WIP - Packages * bump hardhat version * lower js-sdk version * TS WIP - Refactor Cleanup * remove most explicit any's + add actual type * separation of types from actual files in certain places where it is bloated * BN refactor (passing in BigNumber to almost all funcs) * .eslintrc.ts.json added to lint typescript test files * lint fix files * general type fixes * remove parallel flag * bump js-sdk version * cleanup * fix naming * bump solidity-coverage version * fix dead links * Split cfa and ida test files * split CFA and IDA test files into: callback vs non callback and mfa for cfa * consistent file naming * Bump terser in /packages/sdk-redux-examples/sdk-redux-react-typecript (#1078) Bumps [terser](https://github.com/terser/terser) from 4.8.0 to 4.8.1. - [Release notes](https://github.com/terser/terser/releases) - [Changelog](https://github.com/terser/terser/blob/master/CHANGELOG.md) - [Commits](https://github.com/terser/terser/compare/v4.8.0...v4.8.1) --- updated-dependencies: - dependency-name: terser dependency-type: indirect ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * [ETHEREUM-CONTRACTS] _isPatricianPeriod Patch (#1080) * fix tests expect (#1082) * this test just needs to know something is undefined * in the workflow it lets us know which property is undefined, but locally it gives the original message: "Cannot read properties of undefined" * further refactoring cleanup (#1081) * use BigNumber almost everywhere * use expect more * remove js-sdk usage as much as possible * remove web3 usage as much as possible * [ETHEREUM-CONTRACTS] backport 1.4.1 fixes (#1084) * [ETHEREUM-CONTRACTS] backport 1.4.1 fixes - deployment scripts improvements - [SECURITY] CFA: check flow sender instead of msg sender in order to cover ACL use Co-authored-by: 0xdavinchee <0xdavinchee@gmail.com> * fix build fix build and add in patch fix test * remove js and js-sdk * fix imports * purge js-sdk from readme.md * just remove codecov badge Co-authored-by: Didi Co-authored-by: 0xdavinchee <0xdavinchee@gmail.com> * [JS-SDK] Half undo deprecation (#1098) * Undo js-sdk deprecation * half deprecate js-sdk * remove JS-SDK from .github/workflows * remove js-sdk from root level files * fix undefined variable errors * add JS-SDK test parity back in for SDK-Core * tidy up README's * remove build-abi-js.sh from bash files * Update ci.feature.yml * build! * [ETHEREUM-CONTRACTS] CFAv1Forwarder Fixes (#1094) * CFAV1Forwarder Fixes * make _host and _cfa state variables immutable - these should not * fix `_deleteFlow` logic * fix natspec * return true from _forwardBatchCall * add comment about EIP 2771 * add CFAv1Forwarder tests * add CFAv1Forwarder to deploy-framework + resolver * require artifacts from hardhat * pain * use truffle build not hh artifacts * hard code fix for now * :-1: * undo js-sdk revert * add CFAv1Forwader to js-sdk * CFAv1Forwarder to js-sdk * do this in a separate branch * remove `protocolReleaseVersion` * Fix links to the examples repo (#1100) * [ETHEREUM-CONTRACTS] CFA Hooks (#1099) * Fix subgraph testing: copy upstream docker compose file (#1109) * [ETHEREUM-CONTRACTS] SuperfluidFrameworkDeployer Additions (#1104) * Now SDK-CORE are testing using this deployer instead the web3-version of the deployer * Update of the nix flake tooling (#1108) * [ETHEREUM-CONTRACTS/SDK-CORE/SUBGRAPH] Use more Typechain capabilities (#1113) Provide end-consumer access to typechain files via ethereum-contracts as the origin source and also exported via sdk-core no more abi file imports, using typechain generated factories all around fix up lint scripts add typechain build pipeline in ethereum-contracts Co-authored-by: Daniel * [ETHEREUM-CONTRACTS/SDK-CORE] CFAv1 Forwarder Integration (#1118) * Agreement Forwarder Integration WIP * Split supertoken tests into separate files * Add CFAv1Forwarder to SuperfluidFrameworkDeployer + set in resolver and enable as trusted forwarder * Integrate CFAv1Forwarder in SDK-Core * tests to ensure that forwarder is being used and works as expected * validateOperationShouldUseCallAgreement checks that we are using agreement forwarder * Update CHANGELOG.md * SDK-Core cleanup Co-Authored-By: tokdaniel <7677603+tokdaniel@users.noreply.github.com> * Fix them hardcoded addresses! * two step fix * Subgraph test setup cleanup * delete test-subgraph.template, it is a duplicate * rename prepare-local: prepare-manifest-local * update ganache.json * create runDeployContractsAndToken.ts to deploy framework and tokens and create new ganache.json dynamically * no more hardcoded addresses in addresses.template.ts! * helpers.ts modified to read ganache.json file to retrieve resolver address to setup framework * must build sdk-core * Fixes * Make it properly dynamic by deploying and preparing local files prior to actual subgraph deployment * fix up package.json given the above * fix up readme * build sdk-core before deploy contracts * do not redeploy * missed the other reusable workflow * chainId unddefined? * sender is not needed here Co-authored-by: tokdaniel <7677603+tokdaniel@users.noreply.github.com> * [ETHEREUM-CONTRACTS/SDK-CORE] Ethereum-Contracts 1.4.2 / SDK-Core 0.5.7 (#1119) * bump sdk-core version * update metadata for satsuma endpoint * bump eth-contracts + changelog * [SUBGRAPH] graph-node issue resolved by The Graph team (#1130) pinned to 0.28.1: https://github.com/graphprotocol/graph-node/issues/4034 * [SUBGRAPH] Revert Changes + Cleanup (#1131) * Update Codeowners (#1132) * Update Codeowners - adding 0xdavinchee as co-codeowner of /packages/ethereum-contracts/ - adding @kasparkallas to js-sdk too as SDK co-owners. * [SDK-CORE] Gas Multiplier (#1128) * gas multiplier * fix where we handle the gasLimit * handle the gas limit modification one level deeper to not break other SDK-Core functionality * address review comments * [CI/SUBGRAPH] Workflow + Script for deploying to Satsuma endpoint (#1125) * modify deploy-subgraph.yml - modify workflow for deploy-subgraph to support new deploy-to-satsuma.sh bash script * ci workflow cleanup * Update handler.deploy-subgraph.yml * address review comments * [ETHEREUM-CONTRACTS] Hardcoded Hook Gas Limit (#1129) * add option for CFA hook to deploy script * hardcode `CFA_HOOK_GAS_LIMIT` * test case added * fix deployment test * handle the magical 1/64 case * blow up in catch * fix deployment script * simplification: make hook gas limit a constant * fix flakey foundry invariant test * the clipped deposits need to be bounded appropriately * no mentions * comment cleanup Co-authored-by: didi * [SDK-CORE] Load w/ metadata (#1127) * sdk-core load w/ metadata * install graphql * changelog + ethers mentioned * address comments * no more EMPTY_NETWORK_DATA * Yellowpaper 1 - Denotational Semantics of General Payment Primitives, and Its Payment System (#1105) * [CI] Changelog reminder handler (#1133) * create changelog reminder handler * changlog reminder handler * cleanup of check-changeset.sh * forgot to checkout monorepo * bad syntax Co-authored-by: Miao ZhiCheng * CFA forwarder deployment related changes (#1117) * improved tooling for same-address deployment of CFA forwarder * ISSUE #1089 - Create2 Hybrid SuperTokenFactory (#1115) * Contracts WIP * `createCanonicalERC20Wrapper` and `computeWrapperSuperTokenAddress` implemented * both functions added to `ISuperTokenFactory` * canonical creation tests * tests that compute works as expected * tests create2 works as expected * tests that we can only create canonical token once * tests that we can create canonical erc20 wrappers for multiple tokens * initialize list * implementation for list initialization added * tests added for list initialization * fix flakey foundry invariant test * the clipped deposits need to be bounded appropriately * address review comments * rename computeWrapperSuperTokenAddress to computeCanonicalERC20WrapperAddress * fix up comments * add new custom error code * add getCanonicalERC20Wrapper function * Address Review Comments * Add warning about reordering storage layouts in all contracts which have upgradability * Add note comments about adding tests to validateStorageLayout if proxy contract storage layout is touched * Fix up tests given review comment logic changes * typo fix * address review comments * add to readme * remove "our" from vocabulary when referring to canonical erc20 wrapper list * use ownable over SueprfluidGovernanceII * remove InitializeData from ISuperTokenFactory.sol * Remove SuperfluidErrors Library (#1142) * Localize custom errors to individual contracts * Modify tests accordingly * Retain the name so that it is easy to debug which contract is throwing the error * No more use of error codes as this is redundant given the above * Comments with the hashed custom error name for quick debugging * [SUBGRAPH] Event Entity Base Property Initialization Refactor (#1143) * simple refactoring * utilize entity.set in initializeEventEntity to cut down on code and duplication * no upcast - must pass entity * bad copy paste * set non null account entity! * review comments addressed! * [ETHEREUM-CONTRACTS/JS-SDK] Contracts scripts typings + JS-SDK cleanup (#1144) * Map deposit from Subgraph to Stream (#1145) Co-authored-by: Miao ZhiCheng * Yellowpaper v1.0: errata and grammatic fixes after reviews (#1148) * [SDK-CORE/SDK-REDUX] Fix reference docs Cloudfront bucket ID & use single "@dev" for latest dev-build (#1146) * Use new cloudfront distribution ID * Use single dev-version instead of many specific dev-versions for ref docs * Update sdk-redux dependencies and bump version (#1147) * Bump apollo-server-core from 3.10.0 to 3.11.0 (#1149) Bumps [apollo-server-core](https://github.com/apollographql/apollo-server/tree/HEAD/packages/apollo-server-core) from 3.10.0 to 3.11.0. - [Release notes](https://github.com/apollographql/apollo-server/releases) - [Changelog](https://github.com/apollographql/apollo-server/blob/apollo-server-core@3.11.0/CHANGELOG.md) - [Commits](https://github.com/apollographql/apollo-server/commits/apollo-server-core@3.11.0/packages/apollo-server-core) --- updated-dependencies: - dependency-name: apollo-server-core dependency-type: indirect ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * [SDK-CORE] Mainnet Support | TODO: Reinstall @superfluid-finance/metadata (#1139) * add mainnet to relevant workflow files (#1141) Co-authored-by: Didi * [SUBGRAPH] Mainnet Support (#1140) Co-authored-by: Didi * TOGAv3: use transfer and eliminate custodian - signed (#1150) * minor cleanups * remove .ts script file which broke the build * undo yarn.lock * fix tests Signed-off-by: dependabot[bot] Co-authored-by: Kaspar Kallas Co-authored-by: Miao ZhiCheng Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Didi Co-authored-by: sffn3va <114768934+sffn3va@users.noreply.github.com> Co-authored-by: tokdaniel <7677603+tokdaniel@users.noreply.github.com> Co-authored-by: Didi --- .editorconfig | 3 + ...subgraph-on-previous-sdk-core-versions.yml | 8 - .github/workflows/ci.canary.yml | 8 +- .../workflows/handler.changelog-reminder.yml | 44 + .github/workflows/handler.deploy-subgraph.yml | 22 +- .../workflows/handler.deploy-to-mainnet.yml | 3 +- .../workflows/handler.list-super-token.yml | 14 +- .../handler.publish-release-packages.yml | 8 +- .../handler.run-ethereum-contracts-script.yml | 14 +- .../handler.update-evm-contracts-docs.yml | 2 +- CODEOWNERS | 8 +- flake.lock | 12 +- flake.nix | 1 + packages/ethereum-contracts/.gitignore | 4 +- packages/ethereum-contracts/CHANGELOG.md | 10 + .../contracts/agreements/AgreementBase.sol | 6 +- .../agreements/ConstantFlowAgreementV1.sol | 61 +- .../InstantDistributionAgreementV1.sol | 29 +- .../gov/SuperfluidGovernanceBase.sol | 11 +- .../agreements/IConstantFlowAgreementV1.sol | 36 +- .../IInstantDistributionAgreementV1.sol | 12 +- .../interfaces/superfluid/Definitions.sol | 90 -- .../interfaces/superfluid/ISuperToken.sol | 18 +- .../superfluid/ISuperTokenFactory.sol | 58 +- .../interfaces/superfluid/ISuperfluid.sol | 46 +- .../superfluid/ISuperfluidGovernance.sol | 6 +- .../superfluid/ISuperfluidToken.sol | 12 +- .../contracts/mocks/SuperTokenFactoryMock.sol | 4 +- .../contracts/superfluid/SuperToken.sol | 24 +- .../superfluid/SuperTokenFactory.sol | 165 ++- .../contracts/superfluid/Superfluid.sol | 45 +- .../contracts/superfluid/SuperfluidToken.sol | 14 +- .../contracts/utils/TOGA.sol | 44 +- .../contracts/utils/TokenCustodian.sol | 55 - packages/ethereum-contracts/package.json | 9 +- .../scripts/deploy-aux-contracts.js | 6 +- .../scripts/deploy-framework.js | 38 +- .../scripts/deploy-test-framework.js | 78 + .../scripts/deploy-test-framework.ts | 84 -- .../scripts/gov-set-trusted-forwarder.js | 3 + packages/ethereum-contracts/scripts/index.js | 72 + .../scripts/resolver-set-key-value.js | 69 + .../tasks/deploy-cfa-forwarder.sh | 41 + .../tasks/etherscan-verify-framework.sh | 9 +- .../ConstantFlowAgreementV1-CFAHook.test.ts | 2 - .../ConstantFlowAgreementV1-Callback.test.ts | 3 +- .../ConstantFlowAgreementV1-MFA.test.ts | 6 +- ...nstantFlowAgreementV1-Non-Callback.test.ts | 36 +- ...ntDistributionAgreementV1-Callback.test.ts | 3 +- ...stributionAgreementV1-Non-Callback.test.ts | 57 +- .../gov/SuperfluidGovernanceII.test.ts | 3 +- .../contracts/scenarios/scenarios.test.ts | 6 +- .../contracts/superfluid/ERC20.behavior.ts | 39 +- .../contracts/superfluid/ERC777.behavior.ts | 56 +- .../superfluid/SuperToken.ERC20.test.ts | 9 +- .../superfluid/SuperToken.ERC777.test.ts | 4 +- .../superfluid/SuperToken.NonStandard.test.ts | 36 +- .../superfluid/SuperTokenFactory.test.ts | 6 +- .../contracts/superfluid/Superfluid.test.ts | 69 +- .../superfluid/SuperfluidToken.test.ts | 18 +- .../test/contracts/tokens/SETH.test.ts | 3 +- .../test/contracts/utils/TOGA.test.ts | 219 +-- .../ConstantFlowAgreementV1.prop.sol | 2 + packages/ethereum-contracts/test/types.ts | 41 - packages/ethereum-contracts/truffle-config.js | 1 - packages/ethereum-contracts/tsconfig.json | 1 - .../ethereum-contracts/tsconfig.scripts.json | 24 + .../tsconfig.typechain.json | 3 +- packages/hot-fuzz/package.json | 2 +- .../src/ConstantFlowAgreementV1Helper.d.ts | 6 +- .../InstantDistributionAgreementV1Helper.d.ts | 18 +- .../InstantDistributionAgreementV1Helper.js | 2 +- packages/js-sdk/src/User.d.ts | 8 +- packages/js-sdk/src/batchCall.d.ts | 2 +- packages/js-sdk/src/loadContracts.d.ts | 3 +- .../js-sdk/test/Framework.subgraph.test.js | 4 +- ...stantDistributionAgreementV1Helper.test.js | 8 +- packages/sdk-core/CHANGELOG.md | 54 +- packages/sdk-core/README.md | 7 + packages/sdk-core/package.json | 4 +- packages/sdk-core/src/Framework.ts | 102 +- packages/sdk-core/src/Operation.ts | 32 +- packages/sdk-core/src/constants.ts | 16 +- packages/sdk-core/src/interfaces.ts | 7 - .../src/subgraph/entities/stream/stream.ts | 2 + .../subgraph/entities/stream/streams.graphql | 1 + packages/sdk-core/src/subgraph/schema.graphql | 3 + packages/sdk-core/src/types.ts | 17 + packages/sdk-core/test/2_operation.test.ts | 67 +- packages/sdk-redux/CHANGELOG.md | 3 + packages/sdk-redux/package.json | 10 +- packages/spec-haskell/Makefile | 50 +- packages/spec-haskell/cabal.project.freeze | 59 - .../spec-haskell/packages/core/src-internal | 1 + .../core/src-internal/Lens/Internal.hs | 29 - .../core/src/Money/Systems/Communism.lhs | 57 - .../Common.hs} | 2 +- .../ConstantFlowDistributionAgreement.hs | 26 +- .../InstantDistributionAgreement.hs | 28 +- .../ProportionalDistributionIndex.hs | 27 +- .../{ => Universal}/ConstantFlowAgreement.hs | 14 +- .../{ => Universal}/DecayingFlowAgreement.hs | 18 +- .../InstantTransferAgreement.hs | 14 +- .../{ => Universal}/MinterAgreement.hs | 14 +- .../{Indexes => }/UniversalIndex.hs | 10 +- .../Systems/Superfluid/Concepts/Agreement.hs | 87 +- .../Superfluid/Concepts/MonetaryUnitData.hs | 17 +- .../src/Money/Systems/Superfluid/CoreTypes.hs | 18 +- .../Superfluid/CoreTypes/RealTimeBalance.hs | 3 +- .../Superfluid/CoreTypes/TypedValue.hs | 11 +- .../Money/Systems/Superfluid/MonetaryUnit.hs | 16 +- .../MonetaryUnitData/ConstantFlow.hs | 18 +- .../MonetaryUnitData/DecayingFlow.hs | 6 +- .../MonetaryUnitData/InstantValue.hs | 6 +- .../MonetaryUnitData/MintedValue.hs | 6 +- .../SubSystems/BufferBasedSolvency.hs | 4 +- .../src/Money/Systems/Superfluid/Token.hs | 20 +- .../core/src/Money/Theory/Distribution.lhs | 54 - .../src/Money/Theory/FinancialContract.lhs | 54 + .../src/Money/Theory/MoneyDistribution.lhs | 93 ++ .../core/src/Money/Theory/MoneyMedium.lhs | 73 + .../Theory/PaymentExecutionEnvironment.lhs | 172 +++ .../src/Money/Theory/PaymentPrimitives.lhs | 104 ++ .../core/superfluid-protocol-spec-core.cabal | 25 +- .../ConstantFlowDistributionAgreement_prop.hs | 22 +- .../Money/Systems/Superfluid/TestTypes.hs | 25 +- .../Superfluid/Instances/Simple/System.hs | 20 +- .../Superfluid/Instances/Simple/Types.hs | 47 +- .../Superfluid/ConstantFlowAgreement_test.hs | 10 +- .../Superfluid/DecayingFlowAgreement_test.hs | 12 +- packages/spec-haskell/utils/lhs2tex.sh | 21 +- packages/spec-haskell/yellowpaper/Biblio.bib | 195 +++ packages/spec-haskell/yellowpaper/Paper.tex | 154 -- packages/spec-haskell/yellowpaper/Paper1.tex | 1265 +++++++++++++++++ .../agreement-contract-omega-function.png | Bin 0 -> 152530 bytes .../assets/agreement-data-structures.png | Bin 0 -> 127258 bytes .../assets/discrete-money-distribution.png | Bin 0 -> 14863 bytes .../assets/money-distribution-with-ctx.png | Bin 0 -> 13836 bytes .../spec-haskell/yellowpaper/preamble.tex | 67 + packages/subgraph/config/arbitrum-goerli.json | 3 +- packages/subgraph/config/arbitrum-one.json | 3 +- packages/subgraph/config/avalanche-c.json | 3 +- packages/subgraph/config/avalanche-fuji.json | 3 +- packages/subgraph/config/bsc-mainnet.json | 3 +- packages/subgraph/config/eth-mainnet.json | 10 + packages/subgraph/config/goerli.json | 3 +- packages/subgraph/config/matic.json | 3 +- packages/subgraph/config/mumbai.json | 3 +- packages/subgraph/config/optimism-goerli.json | 3 +- .../subgraph/config/optimism-mainnet.json | 3 +- packages/subgraph/config/xdai.json | 3 +- packages/subgraph/networks.json | 1 + packages/subgraph/package.json | 5 +- packages/subgraph/src/addresses.template.ts | 52 +- packages/subgraph/src/mappings/cfav1.ts | 32 +- packages/subgraph/src/mappings/host.ts | 99 +- packages/subgraph/src/mappings/idav1.ts | 167 +-- packages/subgraph/src/mappings/resolver.ts | 57 +- packages/subgraph/src/mappings/superToken.ts | 126 +- .../src/mappings/superTokenFactory.ts | 49 +- .../src/mappings/superfluidGovernance.ts | 80 +- packages/subgraph/src/utils.ts | 30 +- .../subgraph/tasks/deploy-all-networks.sh | 2 - packages/subgraph/tasks/deploy-to-network.sh | 2 +- packages/subgraph/tasks/deploy-to-satsuma.sh | 22 + packages/subgraph/truffle-config.js | 31 - tasks/check-changeset.sh | 11 +- yarn.lock | 175 ++- 168 files changed, 3989 insertions(+), 2300 deletions(-) create mode 100644 .github/workflows/handler.changelog-reminder.yml delete mode 100644 packages/ethereum-contracts/contracts/utils/TokenCustodian.sol create mode 100644 packages/ethereum-contracts/scripts/deploy-test-framework.js delete mode 100644 packages/ethereum-contracts/scripts/deploy-test-framework.ts create mode 100644 packages/ethereum-contracts/scripts/index.js create mode 100644 packages/ethereum-contracts/scripts/resolver-set-key-value.js create mode 100755 packages/ethereum-contracts/tasks/deploy-cfa-forwarder.sh create mode 100644 packages/ethereum-contracts/tsconfig.scripts.json delete mode 100644 packages/spec-haskell/cabal.project.freeze create mode 120000 packages/spec-haskell/packages/core/src-internal delete mode 100644 packages/spec-haskell/packages/core/src-internal/Lens/Internal.hs delete mode 100644 packages/spec-haskell/packages/core/src/Money/Systems/Communism.lhs rename packages/spec-haskell/packages/core/src/Money/Systems/Superfluid/Agreements/{Indexes/ProportionalDistributionCommon.hs => ProportionalDistribution/Common.hs} (87%) rename packages/spec-haskell/packages/core/src/Money/Systems/Superfluid/Agreements/{ => ProportionalDistribution}/ConstantFlowDistributionAgreement.hs (88%) rename packages/spec-haskell/packages/core/src/Money/Systems/Superfluid/Agreements/{ => ProportionalDistribution}/InstantDistributionAgreement.hs (84%) rename packages/spec-haskell/packages/core/src/Money/Systems/Superfluid/Agreements/{Indexes => }/ProportionalDistributionIndex.hs (80%) rename packages/spec-haskell/packages/core/src/Money/Systems/Superfluid/Agreements/{ => Universal}/ConstantFlowAgreement.hs (84%) rename packages/spec-haskell/packages/core/src/Money/Systems/Superfluid/Agreements/{ => Universal}/DecayingFlowAgreement.hs (83%) rename packages/spec-haskell/packages/core/src/Money/Systems/Superfluid/Agreements/{ => Universal}/InstantTransferAgreement.hs (81%) rename packages/spec-haskell/packages/core/src/Money/Systems/Superfluid/Agreements/{ => Universal}/MinterAgreement.hs (84%) rename packages/spec-haskell/packages/core/src/Money/Systems/Superfluid/Agreements/{Indexes => }/UniversalIndex.hs (72%) delete mode 100644 packages/spec-haskell/packages/core/src/Money/Theory/Distribution.lhs create mode 100644 packages/spec-haskell/packages/core/src/Money/Theory/FinancialContract.lhs create mode 100644 packages/spec-haskell/packages/core/src/Money/Theory/MoneyDistribution.lhs create mode 100644 packages/spec-haskell/packages/core/src/Money/Theory/MoneyMedium.lhs create mode 100644 packages/spec-haskell/packages/core/src/Money/Theory/PaymentExecutionEnvironment.lhs create mode 100644 packages/spec-haskell/packages/core/src/Money/Theory/PaymentPrimitives.lhs delete mode 100644 packages/spec-haskell/yellowpaper/Paper.tex create mode 100644 packages/spec-haskell/yellowpaper/Paper1.tex create mode 100644 packages/spec-haskell/yellowpaper/assets/agreement-contract-omega-function.png create mode 100644 packages/spec-haskell/yellowpaper/assets/agreement-data-structures.png create mode 100644 packages/spec-haskell/yellowpaper/assets/discrete-money-distribution.png create mode 100644 packages/spec-haskell/yellowpaper/assets/money-distribution-with-ctx.png create mode 100644 packages/spec-haskell/yellowpaper/preamble.tex create mode 100644 packages/subgraph/config/eth-mainnet.json create mode 100644 packages/subgraph/tasks/deploy-to-satsuma.sh delete mode 100644 packages/subgraph/truffle-config.js diff --git a/.editorconfig b/.editorconfig index c0a1063f18..e72d831105 100644 --- a/.editorconfig +++ b/.editorconfig @@ -42,3 +42,6 @@ indent_size = 2 [*.yaml] indent_size = 2 + +[*.nix] +indent_style = 2 diff --git a/.github/workflows/call.test-subgraph-on-previous-sdk-core-versions.yml b/.github/workflows/call.test-subgraph-on-previous-sdk-core-versions.yml index e625ef2913..4be4608c8d 100644 --- a/.github/workflows/call.test-subgraph-on-previous-sdk-core-versions.yml +++ b/.github/workflows/call.test-subgraph-on-previous-sdk-core-versions.yml @@ -58,14 +58,6 @@ jobs: run: yarn prepare-local working-directory: ./packages/subgraph - - name: "Deploy Framework and Tokens" - run: npx hardhat run scripts/runDeployContractsAndToken.ts --network localhost - working-directory: ./packages/subgraph - - - name: "Prepare files for local testing" - run: yarn prepare-local - working-directory: ./packages/subgraph - - name: "Run setup-graph-node" run: | chmod +x ./tasks/setup-graph-node.sh diff --git a/.github/workflows/ci.canary.yml b/.github/workflows/ci.canary.yml index 2a5428c362..8538742e48 100644 --- a/.github/workflows/ci.canary.yml +++ b/.github/workflows/ci.canary.yml @@ -244,8 +244,8 @@ jobs: aws_region: eu-west-2 aws_access_key_id: ${{ secrets.SITE_DEPLOYER_AWS_ACCESS_KEY_ID }} aws_secret_access_key: ${{ secrets.SITE_DEPLOYER_AWS_SECRET_ACCESS_KEY }} - s3_uri: ${{ format('{0}sdk-core@{1}', secrets.SITE_DEPLOYER_AWS_S3_DOCS_URI, steps.sdk-versions.outputs.SDK_CORE_VERSION) }} - cloudfront_distribution_id: E3SV855CTC9UJO + s3_uri: ${{ format('{0}sdk-core@dev', secrets.SITE_DEPLOYER_AWS_S3_DOCS_URI) }} + cloudfront_distribution_id: E3JEO5R14CT8IH - name: Upload sdk-redux HTML documentation uses: ./build-scripts/s3cloudfront-hosting/actions/sync @@ -254,8 +254,8 @@ jobs: aws_region: eu-west-2 aws_access_key_id: ${{ secrets.SITE_DEPLOYER_AWS_ACCESS_KEY_ID }} aws_secret_access_key: ${{ secrets.SITE_DEPLOYER_AWS_SECRET_ACCESS_KEY }} - s3_uri: ${{ format('{0}sdk-redux@{1}', secrets.SITE_DEPLOYER_AWS_S3_DOCS_URI, steps.sdk-versions.outputs.SDK_REDUX_VERSION) }} - cloudfront_distribution_id: E3SV855CTC9UJO + s3_uri: ${{ format('{0}sdk-redux@dev', secrets.SITE_DEPLOYER_AWS_S3_DOCS_URI) }} + cloudfront_distribution_id: E3JEO5R14CT8IH upgrade-contracts: name: Upgrade ethereum-contracts on goerli testnet (protocol release version "test") diff --git a/.github/workflows/handler.changelog-reminder.yml b/.github/workflows/handler.changelog-reminder.yml new file mode 100644 index 0000000000..7e0f47db16 --- /dev/null +++ b/.github/workflows/handler.changelog-reminder.yml @@ -0,0 +1,44 @@ +name: Changelog Reminder + +on: + pull_request: + types: [opened] + +jobs: + check: + name: Check which packages have been modified + + runs-on: ubuntu-latest + + outputs: + build_ethereum_contracts: ${{ env.BUILD_ETHEREUM_CONTRACTS }} + build_sdk_core: ${{ env.BUILD_SDK_CORE }} + build_sdk_redux: ${{ env.BUILD_SDK_REDUX }} + build_spec_haskell: ${{ env.BUILD_SPEC_HASKELL }} + + steps: + - uses: actions/checkout@v3 + + - name: Check changeset + run: tasks/check-changeset.sh ${{ github.sha }} dev + + create-reminder: + name: Create Changelog reminder in PR discussion + + runs-on: ubuntu-latest + + needs: [check] + if: needs.check.outputs.build_ethereum_contracts || needs.check.outputs.build_sdk_core || needs.check.outputs.build_sdk_redux || needs.check.outputs.build_spec_haskell + + steps: + - name: Create Reminder + uses: peter-evans/create-or-update-comment@v2 + with: + issue-number: ${{ github.event.pull_request.number }} + body: | + ## Changelog Reminder + + Reminder to update the CHANGELOG.md for any of the modified packages in this PR. + - [ ] CHANGELOG.md modified + - [ ] Double check before merge + reactions: white_check_mark \ No newline at end of file diff --git a/.github/workflows/handler.deploy-subgraph.yml b/.github/workflows/handler.deploy-subgraph.yml index 467b14f72f..237a982888 100644 --- a/.github/workflows/handler.deploy-subgraph.yml +++ b/.github/workflows/handler.deploy-subgraph.yml @@ -8,10 +8,20 @@ on: release_branch: description: 'Release branch (feature/dev/v1...)' required: true + type: string default: 'feature' + deploy_to_satsuma_endpoint: + required: true + type: boolean + description: "Explicitly declare whether you want to deploy to Satsuma's endpoint." network: required: false + type: string description: 'Network to deploy to (matic/xdai/kovan...)' + satsuma_version_label: + required: false + type: string + description: 'Version label for Satsuma deployment, we are not using this for hosted deployments (format: v0.0.1)' jobs: deploy-subgraph: @@ -58,8 +68,16 @@ jobs: run: "yarn codegen" working-directory: ${{ env.subgraph-working-directory }} - - name: "Deploy to endpoint" + - name: "Deploy to Satsuma endpoint" + if: inputs.deploy_to_satsuma_endpoint == true + run: "yarn deploy:to-satsuma ${{ github.event.inputs.satsuma_version_label }} ${{ github.event.inputs.network }}" + working-directory: ${{ env.subgraph-working-directory }} + env: + SATSUMA_DEPLOY_KEY: ${{ secrets.SATSUMA_DEPLOY_KEY }} + + - name: "Deploy to Hosted Subgraph Superfluid endpoint" + if: inputs.deploy_to_satsuma_endpoint == false run: "yarn deploy ${{ github.event.inputs.release_branch }} ${{ github.event.inputs.network }}" working-directory: ${{ env.subgraph-working-directory }} env: - THEGRAPH_ACCESS_TOKEN: ${{ secrets.THEGRAPH_ACCESS_TOKEN }} + THE_GRAPH_ACCESS_TOKEN: ${{ secrets.THE_GRAPH_ACCESS_TOKEN }} diff --git a/.github/workflows/handler.deploy-to-mainnet.yml b/.github/workflows/handler.deploy-to-mainnet.yml index 800437354c..2b14afe353 100644 --- a/.github/workflows/handler.deploy-to-mainnet.yml +++ b/.github/workflows/handler.deploy-to-mainnet.yml @@ -27,6 +27,7 @@ jobs: # testnet web3 providers (leaving for testing) AVALANCHE_FUJI_PROVIDER_URL: ${{ secrets.AVALANCHE_FUJI_PROVIDER_URL }} # mainnet web3 providers + ETH_MAINNET_PROVIDER_URL: ${{ secrets.ETH_MAINNET_PROVIDER_URL }} XDAI_MAINNET_PROVIDER_URL: ${{ secrets.XDAI_MAINNET_PROVIDER_URL }} POLYGON_MAINNET_PROVIDER_URL: ${{ secrets.POLYGON_MAINNET_PROVIDER_URL }} OPTIMISM_MAINNET_PROVIDER_URL: ${{ secrets.OPTIMISM_MAINNET_PROVIDER_URL }} @@ -71,4 +72,4 @@ jobs: aws_access_key_id: ${{ secrets.SITE_DEPLOYER_AWS_ACCESS_KEY_ID }} aws_secret_access_key: ${{ secrets.SITE_DEPLOYER_AWS_SECRET_ACCESS_KEY }} s3_uri: ${{ format('{0}{1}-contract-addrs@{2}', secrets.SITE_DEPLOYER_AWS_S3_DOCS_URI, github.event.inputs.network, github.run_id) }} - cloudfront_distribution_id: E3SV855CTC9UJO \ No newline at end of file + cloudfront_distribution_id: E3JEO5R14CT8IH diff --git a/.github/workflows/handler.list-super-token.yml b/.github/workflows/handler.list-super-token.yml index ed669b7983..0d847f942b 100644 --- a/.github/workflows/handler.list-super-token.yml +++ b/.github/workflows/handler.list-super-token.yml @@ -59,14 +59,16 @@ jobs: env: DEFAULT_MNEMONIC: ${{ secrets.BUILD_AGENT_MNEMONIC }} # network web3 providers - ETH_GOERLI_PROVIDER_URL: ${{ secrets.ETH_GOERLI_PROVIDER_URL }} - POLYGON_MUMBAI_PROVIDER_URL: ${{ secrets.POLYGON_MUMBAI_PROVIDER_URL }} + ETH_MAINNET_PROVIDER_URL: ${{ secrets.ETH_MAINNET_PROVIDER_URL }} XDAI_MAINNET_PROVIDER_URL: ${{ secrets.XDAI_MAINNET_PROVIDER_URL }} POLYGON_MAINNET_PROVIDER_URL: ${{ secrets.POLYGON_MAINNET_PROVIDER_URL }} - OPTIMISM_GOERLI_PROVIDER_URL: ${{ secrets.OPTIMISM_GOERLI_PROVIDER_URL }} - ARBITRUM_GOERLI_PROVIDER_URL: ${{ secrets.ARBITRUM_GOERLI_PROVIDER_URL }} - AVALANCHE_FUJI_PROVIDER_URL: ${{ secrets.AVALANCHE_FUJI_PROVIDER_URL }} OPTIMISM_MAINNET_PROVIDER_URL: ${{ secrets.OPTIMISM_MAINNET_PROVIDER_URL }} ARBITRUM_ONE_PROVIDER_URL: ${{ secrets.ARBITRUM_ONE_PROVIDER_URL }} - AVALANCHE_C_PROVIDER_URL: ${{ secrets.AVALANCHE_C_PROVIDER_URL }} BSC_MAINNET_PROVIDER_URL: ${{ secrets.BSC_MAINNET_PROVIDER_URL }} + AVALANCHE_C_PROVIDER_URL: ${{ secrets.AVALANCHE_C_PROVIDER_URL }} + + OPTIMISM_GOERLI_PROVIDER_URL: ${{ secrets.OPTIMISM_GOERLI_PROVIDER_URL }} + ARBITRUM_GOERLI_PROVIDER_URL: ${{ secrets.ARBITRUM_GOERLI_PROVIDER_URL }} + ETH_GOERLI_PROVIDER_URL: ${{ secrets.ETH_GOERLI_PROVIDER_URL }} + POLYGON_MUMBAI_PROVIDER_URL: ${{ secrets.POLYGON_MUMBAI_PROVIDER_URL }} + AVALANCHE_FUJI_PROVIDER_URL: ${{ secrets.AVALANCHE_FUJI_PROVIDER_URL }} diff --git a/.github/workflows/handler.publish-release-packages.yml b/.github/workflows/handler.publish-release-packages.yml index 7eea732a84..7a26626c79 100644 --- a/.github/workflows/handler.publish-release-packages.yml +++ b/.github/workflows/handler.publish-release-packages.yml @@ -79,7 +79,7 @@ jobs: aws_access_key_id: ${{ secrets.SITE_DEPLOYER_AWS_ACCESS_KEY_ID }} aws_secret_access_key: ${{ secrets.SITE_DEPLOYER_AWS_SECRET_ACCESS_KEY }} s3_uri: ${{ format('{0}sdk-core@{1}', secrets.SITE_DEPLOYER_AWS_S3_DOCS_URI, steps.publish-sdk-core.outputs.PUBLISHED_VERSION) }} - cloudfront_distribution_id: E3SV855CTC9UJO + cloudfront_distribution_id: E3JEO5R14CT8IH - name: Upload sdk-core latest documentation redirect if: env.PUBLISH_SDK_CORE == 1 @@ -90,7 +90,7 @@ jobs: aws_access_key_id: ${{ secrets.SITE_DEPLOYER_AWS_ACCESS_KEY_ID }} aws_secret_access_key: ${{ secrets.SITE_DEPLOYER_AWS_SECRET_ACCESS_KEY }} s3_uri: ${{ format('{0}sdk-core', secrets.SITE_DEPLOYER_AWS_S3_DOCS_URI) }} - cloudfront_distribution_id: E3SV855CTC9UJO + cloudfront_distribution_id: E3JEO5R14CT8IH - name: Publish sdk-redux package id: publish-sdk-redux @@ -120,7 +120,7 @@ jobs: aws_access_key_id: ${{ secrets.SITE_DEPLOYER_AWS_ACCESS_KEY_ID }} aws_secret_access_key: ${{ secrets.SITE_DEPLOYER_AWS_SECRET_ACCESS_KEY }} s3_uri: ${{ format('{0}sdk-redux@{1}', secrets.SITE_DEPLOYER_AWS_S3_DOCS_URI, steps.publish-sdk-redux.outputs.PUBLISHED_VERSION) }} - cloudfront_distribution_id: E3SV855CTC9UJO + cloudfront_distribution_id: E3JEO5R14CT8IH - name: Upload sdk-redux latest documentation redirect if: env.PUBLISH_SDK_REDUX == 1 @@ -131,4 +131,4 @@ jobs: aws_access_key_id: ${{ secrets.SITE_DEPLOYER_AWS_ACCESS_KEY_ID }} aws_secret_access_key: ${{ secrets.SITE_DEPLOYER_AWS_SECRET_ACCESS_KEY }} s3_uri: ${{ format('{0}sdk-redux', secrets.SITE_DEPLOYER_AWS_S3_DOCS_URI) }} - cloudfront_distribution_id: E3SV855CTC9UJO + cloudfront_distribution_id: E3JEO5R14CT8IH diff --git a/.github/workflows/handler.run-ethereum-contracts-script.yml b/.github/workflows/handler.run-ethereum-contracts-script.yml index 0ae26d5658..7229d5a35d 100644 --- a/.github/workflows/handler.run-ethereum-contracts-script.yml +++ b/.github/workflows/handler.run-ethereum-contracts-script.yml @@ -63,14 +63,16 @@ jobs: DEFAULT_MNEMONIC: ${{ secrets.BUILD_AGENT_MNEMONIC }} # network web3 providers - ETH_GOERLI_PROVIDER_URL: ${{ secrets.ETH_GOERLI_PROVIDER_URL }} - POLYGON_MUMBAI_PROVIDER_URL: ${{ secrets.POLYGON_MUMBAI_PROVIDER_URL }} + ETH_MAINNET_PROVIDER_URL: ${{ secrets.ETH_MAINNET_PROVIDER_URL }} XDAI_MAINNET_PROVIDER_URL: ${{ secrets.XDAI_MAINNET_PROVIDER_URL }} POLYGON_MAINNET_PROVIDER_URL: ${{ secrets.POLYGON_MAINNET_PROVIDER_URL }} - OPTIMISM_GOERLI_PROVIDER_URL: ${{ secrets.OPTIMISM_GOERLI_PROVIDER_URL }} - ARBITRUM_GOERLI_PROVIDER_URL: ${{ secrets.ARBITRUM_GOERLI_PROVIDER_URL }} - AVALANCHE_FUJI_PROVIDER_URL: ${{ secrets.AVALANCHE_FUJI_PROVIDER_URL }} OPTIMISM_MAINNET_PROVIDER_URL: ${{ secrets.OPTIMISM_MAINNET_PROVIDER_URL }} ARBITRUM_ONE_PROVIDER_URL: ${{ secrets.ARBITRUM_ONE_PROVIDER_URL }} - AVALANCHE_C_PROVIDER_URL: ${{ secrets.AVALANCHE_C_PROVIDER_URL }} BSC_MAINNET_PROVIDER_URL: ${{ secrets.BSC_MAINNET_PROVIDER_URL }} + AVALANCHE_C_PROVIDER_URL: ${{ secrets.AVALANCHE_C_PROVIDER_URL }} + + OPTIMISM_GOERLI_PROVIDER_URL: ${{ secrets.OPTIMISM_GOERLI_PROVIDER_URL }} + ARBITRUM_GOERLI_PROVIDER_URL: ${{ secrets.ARBITRUM_GOERLI_PROVIDER_URL }} + ETH_GOERLI_PROVIDER_URL: ${{ secrets.ETH_GOERLI_PROVIDER_URL }} + POLYGON_MUMBAI_PROVIDER_URL: ${{ secrets.POLYGON_MUMBAI_PROVIDER_URL }} + AVALANCHE_FUJI_PROVIDER_URL: ${{ secrets.AVALANCHE_FUJI_PROVIDER_URL }} diff --git a/.github/workflows/handler.update-evm-contracts-docs.yml b/.github/workflows/handler.update-evm-contracts-docs.yml index 5915d37eb6..bcfc17ffbb 100644 --- a/.github/workflows/handler.update-evm-contracts-docs.yml +++ b/.github/workflows/handler.update-evm-contracts-docs.yml @@ -54,7 +54,7 @@ jobs: # aws_access_key_id: ${{ secrets.SITE_DEPLOYER_AWS_ACCESS_KEY_ID }} # aws_secret_access_key: ${{ secrets.SITE_DEPLOYER_AWS_SECRET_ACCESS_KEY }} # s3_uri: ${{ format('{0}ethereum-contracts@{1}', secrets.SITE_DEPLOYER_AWS_S3_DOCS_URI, steps.sdk-versions.outputs.CONTRACTS_VERSION) }} - # cloudfront_distribution_id: E3SV855CTC9UJO + # cloudfront_distribution_id: E3JEO5R14CT8IH - name: Update the docs repo uses: dmnemec/copy_file_to_another_repo_action@main diff --git a/CODEOWNERS b/CODEOWNERS index c5a581c6f6..c48d7fa0ee 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -1,7 +1,7 @@ # REFERENCE: https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners -# By default no owner, so everyone in the FTE team can approve the pull request -* @superfluid-finance/fte +# By default no owner, so everyone in the protocal devs group can approve the pull request +* @superfluid-finance/protocol-devs # Workflow files are gatekept by MiaoZC /.github/ @hellwolf @@ -11,8 +11,8 @@ # Packages # /packages/spec-haskell/ @hellwolf -/packages/ethereum-contracts/ @hellwolf -/packages/js-sdk/ @hellwolf @0xdavinchee +/packages/ethereum-contracts/ @hellwolf @0xdavinchee +/packages/js-sdk/ @hellwolf @kasparkallas @0xdavinchee /packages/subgraph/ @0xdavinchee @kasparkallas @hellwolf /packages/sdk-core/ @0xdavinchee @kasparkallas @hellwolf /packages/sdk-redux/ @0xdavinchee @kasparkallas @hellwolf diff --git a/flake.lock b/flake.lock index 976c0fa262..4a92e87269 100644 --- a/flake.lock +++ b/flake.lock @@ -36,11 +36,11 @@ "nixpkgs": "nixpkgs" }, "locked": { - "lastModified": 1665047725, - "narHash": "sha256-X2zu8WQPrYj3pFJM7usPbcEOKH1oqE+0YBzu2sjMAUY=", + "lastModified": 1666171006, + "narHash": "sha256-gFoOk0AXWFbRps4SPlbnWMyizc1H9RJqo4n9sq0CME0=", "owner": "shazow", "repo": "foundry.nix", - "rev": "7b53a7d5ced2e6b6cd73846195d025844cffed50", + "rev": "9d22e1f9c97e36a173535093c46417ed532234de", "type": "github" }, "original": { @@ -67,11 +67,11 @@ }, "nixpkgs_2": { "locked": { - "lastModified": 1664904529, - "narHash": "sha256-mGlB/SQR4E9jb7fOOxCTJlwL6Mk1Dpyvi4UrOXL6C18=", + "lastModified": 1666154393, + "narHash": "sha256-G4jctSMlliZvG4zUrcas0O+t3qJHzM0moBpG93lcl44=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "b7a47253e0c8cb04c0a3f8ed3149e90229e62884", + "rev": "7b38cb118ce4ad36d19a8b021068633b57ce0d3f", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index e1a115f9f1..b8a78f8206 100644 --- a/flake.nix +++ b/flake.nix @@ -37,6 +37,7 @@ supportedGhcVersions = [ "924" ]; }) hlint + stylish-haskell gnuplot # sage math sage diff --git a/packages/ethereum-contracts/.gitignore b/packages/ethereum-contracts/.gitignore index b20d87d830..e5bdf99cca 100644 --- a/packages/ethereum-contracts/.gitignore +++ b/packages/ethereum-contracts/.gitignore @@ -5,4 +5,6 @@ /artifacts /cache /docs/api -/typechain-types \ No newline at end of file +/typechain-types +/scripts/**/*.d.ts +/scripts/**/*.d.ts.map diff --git a/packages/ethereum-contracts/CHANGELOG.md b/packages/ethereum-contracts/CHANGELOG.md index a69b7cf7cb..3300df0f88 100644 --- a/packages/ethereum-contracts/CHANGELOG.md +++ b/packages/ethereum-contracts/CHANGELOG.md @@ -5,6 +5,16 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ## Unreleased +## [v1.4.3] - 2022-11-14 +### Added + +- `createCanonicalERC20Wrapper` added for creating ERC20 Wrapper Super tokens which will be added to a canonical wrapper super token list based on naming convention and semi-upgradeability. This will be the recommended way of creating ERC20 Wrapper moving forward. (#1115) + - `name` naming convention: Super Token `name` will be `"Super ${underlyingToken.name}"` + - `symbol` naming convention: Super Token `symbol` will be `"${underlyingToken.symbol}x"` +- Hardhat `artifacts` included in npm package (#1144) +- Include declaration files in `types` folder for files in `scripts` in npm package (#1144) +- Remove custom error codes in favor of localized per contract custom errors (#1142) + ## [v.1.4.2] - 2022-10-13 ### Added diff --git a/packages/ethereum-contracts/contracts/agreements/AgreementBase.sol b/packages/ethereum-contracts/contracts/agreements/AgreementBase.sol index 132af7e2da..2427a9c815 100644 --- a/packages/ethereum-contracts/contracts/agreements/AgreementBase.sol +++ b/packages/ethereum-contracts/contracts/agreements/AgreementBase.sol @@ -3,7 +3,6 @@ pragma solidity 0.8.16; import { UUPSProxiable } from "../upgradability/UUPSProxiable.sol"; import { ISuperAgreement } from "../interfaces/superfluid/ISuperAgreement.sol"; -import { SuperfluidErrors } from "../interfaces/superfluid/Definitions.sol"; /** * @title Superfluid agreement base boilerplate contract @@ -15,6 +14,9 @@ abstract contract AgreementBase is { address immutable internal _host; + // Custom Erorrs + error AGREEMENT_BASE_ONLY_HOST(); // 0x1601d91e + constructor(address host) { _host = host; @@ -30,7 +32,7 @@ abstract contract AgreementBase is function updateCode(address newAddress) external override { - if (msg.sender != _host) revert SuperfluidErrors.ONLY_HOST(SuperfluidErrors.AGREEMENT_BASE_ONLY_HOST); + if (msg.sender != _host) revert AGREEMENT_BASE_ONLY_HOST(); return _updateCodeAddress(newAddress); } diff --git a/packages/ethereum-contracts/contracts/agreements/ConstantFlowAgreementV1.sol b/packages/ethereum-contracts/contracts/agreements/ConstantFlowAgreementV1.sol index bb99d9cce5..0451fae604 100644 --- a/packages/ethereum-contracts/contracts/agreements/ConstantFlowAgreementV1.sol +++ b/packages/ethereum-contracts/contracts/agreements/ConstantFlowAgreementV1.sol @@ -4,7 +4,6 @@ pragma solidity 0.8.16; import { IConstantFlowAgreementHook } from "../interfaces/agreements/IConstantFlowAgreementHook.sol"; import { IConstantFlowAgreementV1, - SuperfluidErrors, ISuperfluidToken } from "../interfaces/agreements/IConstantFlowAgreementV1.sol"; import { @@ -67,6 +66,10 @@ contract ConstantFlowAgreementV1 is IConstantFlowAgreementHook public immutable constantFlowAgreementHook; + // An arbitrarily chosen safety limit for the external calls to protect against out-of-gas grief exploits. + // solhint-disable-next-line var-name-mixedcase + uint64 constant public CFA_HOOK_GAS_LIMIT = 250000; + using SafeCast for uint256; using SafeCast for int256; @@ -92,7 +95,10 @@ contract ConstantFlowAgreementV1 is } // solhint-disable-next-line no-empty-blocks - constructor(ISuperfluid host, IConstantFlowAgreementHook _hookAddress) AgreementBase(address(host)) { + constructor( + ISuperfluid host, + IConstantFlowAgreementHook _hookAddress + ) AgreementBase(address(host)) { constantFlowAgreementHook = _hookAddress; } @@ -413,7 +419,7 @@ contract ConstantFlowAgreementV1 is returns(bytes32 flowId, FlowParams memory flowParams) { if (flowVars.receiver == address(0)) { - revert SuperfluidErrors.ZERO_ADDRESS(SuperfluidErrors.CFA_ZERO_ADDRESS_RECEIVER); + revert CFA_ZERO_ADDRESS_RECEIVER(); } flowId = _generateFlowId(flowVars.sender, flowVars.receiver); @@ -438,7 +444,7 @@ contract ConstantFlowAgreementV1 is (bytes32 flowId, FlowParams memory flowParams) = _createOrUpdateFlowCheck(flowVars, currentContext); (bool exist, FlowData memory oldFlowData) = _getAgreementData(flowVars.token, flowId); - if (exist) revert SuperfluidErrors.ALREADY_EXISTS(SuperfluidErrors.CFA_FLOW_ALREADY_EXISTS); + if (exist) revert CFA_FLOW_ALREADY_EXISTS(); if (ISuperfluid(msg.sender).isApp(ISuperApp(flowVars.receiver))) { newCtx = _changeFlowToApp( @@ -453,11 +459,9 @@ contract ConstantFlowAgreementV1 is _requireAvailableBalance(flowVars.token, flowVars.sender, currentContext); - // @note It is possible this silently fails due to out of gas reasons, and users should - // still be able to recreate the hook behavior. This logic should exist in the NFT contract though. - // This should be safe as we don't have any behavior/state changes in the catch block. if (address(constantFlowAgreementHook) != address(0)) { - try constantFlowAgreementHook.onCreate( + uint256 gasLeftBefore = gasleft(); + try constantFlowAgreementHook.onCreate{ gas: CFA_HOOK_GAS_LIMIT }( flowVars.token, IConstantFlowAgreementHook.CFAHookParams({ sender: flowParams.sender, @@ -467,7 +471,14 @@ contract ConstantFlowAgreementV1 is }) ) // solhint-disable-next-line no-empty-blocks - {} catch {} + {} catch { +// If the CFA hook actually runs out of gas, not just hitting the safety gas limit, we revert the whole transaction. +// This solves an issue where the gas estimaton didn't provide enough gas by default for the CFA hook to succeed. +// See https://medium.com/@wighawag/ethereum-the-concept-of-gas-and-its-dangers-28d0eb809bb2 + if (gasleft() <= gasLeftBefore / 63) { + revert CFA_HOOK_OUT_OF_GAS(); + } + } } } @@ -483,7 +494,7 @@ contract ConstantFlowAgreementV1 is { (, FlowParams memory flowParams) = _createOrUpdateFlowCheck(flowVars, currentContext); - if (!exist) revert SuperfluidErrors.DOES_NOT_EXIST(SuperfluidErrors.CFA_FLOW_DOES_NOT_EXIST); + if (!exist) revert CFA_FLOW_DOES_NOT_EXIST(); if (ISuperfluid(msg.sender).isApp(ISuperApp(flowVars.receiver))) { newCtx = _changeFlowToApp( @@ -500,8 +511,9 @@ contract ConstantFlowAgreementV1 is // @note See comment in _createFlow if (address(constantFlowAgreementHook) != address(0)) { + uint256 gasLeftBefore = gasleft(); // solhint-disable-next-line no-empty-blocks - try constantFlowAgreementHook.onUpdate( + try constantFlowAgreementHook.onUpdate{ gas: CFA_HOOK_GAS_LIMIT }( flowVars.token, IConstantFlowAgreementHook.CFAHookParams({ sender: flowParams.sender, @@ -511,7 +523,12 @@ contract ConstantFlowAgreementV1 is }), oldFlowData.flowRate // solhint-disable-next-line no-empty-blocks - ) {} catch {} + ) {} catch { + // @note See comment in onCreate + if (gasleft() <= gasLeftBefore / 63) { + revert CFA_HOOK_OUT_OF_GAS(); + } + } } } @@ -526,10 +543,10 @@ contract ConstantFlowAgreementV1 is { FlowParams memory flowParams; if (flowVars.sender == address(0)) { - revert SuperfluidErrors.ZERO_ADDRESS(SuperfluidErrors.CFA_ZERO_ADDRESS_SENDER); + revert CFA_ZERO_ADDRESS_SENDER(); } if (flowVars.receiver == address(0)) { - revert SuperfluidErrors.ZERO_ADDRESS(SuperfluidErrors.CFA_ZERO_ADDRESS_RECEIVER); + revert CFA_ZERO_ADDRESS_RECEIVER(); } flowParams.flowId = _generateFlowId(flowVars.sender, flowVars.receiver); flowParams.sender = flowVars.sender; @@ -538,7 +555,7 @@ contract ConstantFlowAgreementV1 is flowParams.flowRate = 0; flowParams.userData = currentContext.userData; (bool exist, FlowData memory oldFlowData) = _getAgreementData(flowVars.token, flowParams.flowId); - if (!exist) revert SuperfluidErrors.DOES_NOT_EXIST(SuperfluidErrors.CFA_FLOW_DOES_NOT_EXIST); + if (!exist) revert CFA_FLOW_DOES_NOT_EXIST(); (int256 availableBalance,,) = flowVars.token.realtimeBalanceOf(flowVars.sender, currentContext.timestamp); @@ -626,7 +643,8 @@ contract ConstantFlowAgreementV1 is // @note See comment in _createFlow if (address(constantFlowAgreementHook) != address(0)) { - try constantFlowAgreementHook.onDelete( + uint256 gasLeftBefore = gasleft(); + try constantFlowAgreementHook.onDelete{ gas: CFA_HOOK_GAS_LIMIT }( flowVars.token, IConstantFlowAgreementHook.CFAHookParams({ sender: flowParams.sender, @@ -636,7 +654,12 @@ contract ConstantFlowAgreementV1 is }), oldFlowData.flowRate // solhint-disable-next-line no-empty-blocks - ) {} catch {} + ) {} catch { + // @note See comment in onCreate + if (gasleft() <= gasLeftBefore / 63) { + revert CFA_HOOK_OUT_OF_GAS(); + } + } } } @@ -1140,7 +1163,7 @@ contract ConstantFlowAgreementV1 is userDamageAmount ); } else { - revert SuperfluidErrors.APP_RULE(SuperAppDefinitions.APP_RULE_NO_CRITICAL_RECEIVER_ACCOUNT); + revert ISuperfluid.APP_RULE(SuperAppDefinitions.APP_RULE_NO_CRITICAL_RECEIVER_ACCOUNT); } } } @@ -1284,7 +1307,7 @@ contract ConstantFlowAgreementV1 is currentContext.appCreditToken != token) { (int256 availableBalance,,) = token.realtimeBalanceOf(flowSender, currentContext.timestamp); if (availableBalance < 0) { - revert SuperfluidErrors.INSUFFICIENT_BALANCE(SuperfluidErrors.CFA_INSUFFICIENT_BALANCE); + revert CFA_INSUFFICIENT_BALANCE(); } } } diff --git a/packages/ethereum-contracts/contracts/agreements/InstantDistributionAgreementV1.sol b/packages/ethereum-contracts/contracts/agreements/InstantDistributionAgreementV1.sol index 9987078c32..4154deb5e7 100644 --- a/packages/ethereum-contracts/contracts/agreements/InstantDistributionAgreementV1.sol +++ b/packages/ethereum-contracts/contracts/agreements/InstantDistributionAgreementV1.sol @@ -5,8 +5,7 @@ import { SafeCast } from "@openzeppelin/contracts/utils/math/SafeCast.sol"; import { IInstantDistributionAgreementV1, - ISuperfluidToken, - SuperfluidErrors + ISuperfluidToken } from "../interfaces/agreements/IInstantDistributionAgreementV1.sol"; import { ISuperfluid, @@ -140,7 +139,7 @@ contract InstantDistributionAgreementV1 is address publisher = context.msgSender; bytes32 iId = _getPublisherId(publisher, indexId); if (_hasIndexData(token, iId)) { - revert SuperfluidErrors.ALREADY_EXISTS(SuperfluidErrors.IDA_INDEX_ALREADY_EXISTS); + revert IDA_INDEX_ALREADY_EXISTS(); } token.createAgreement(iId, _encodeIndexData(IndexData(0, 0, 0))); @@ -188,7 +187,7 @@ contract InstantDistributionAgreementV1 is { bytes32 iId = _getPublisherId(publisher, indexId); (bool exist, IndexData memory idata) = _getIndexData(token, iId); - if (!exist) revert SuperfluidErrors.DOES_NOT_EXIST(SuperfluidErrors.IDA_INDEX_DOES_NOT_EXIST); + if (!exist) revert IDA_INDEX_DOES_NOT_EXIST(); uint256 totalUnits = uint256(idata.totalUnitsApproved + idata.totalUnitsPending); uint128 indexDelta = (amount / totalUnits).toUint128(); @@ -279,7 +278,7 @@ contract InstantDistributionAgreementV1 is // check account solvency if (token.isAccountCriticalNow(publisher)) { - revert SuperfluidErrors.INSUFFICIENT_BALANCE(SuperfluidErrors.IDA_INSUFFICIENT_BALANCE); + revert IDA_INSUFFICIENT_BALANCE(); } } @@ -296,7 +295,7 @@ contract InstantDistributionAgreementV1 is bool exist; iId = _getPublisherId(publisher, indexId); (exist, idata) = _getIndexData(token, iId); - if (!exist) revert SuperfluidErrors.DOES_NOT_EXIST(SuperfluidErrors.IDA_INDEX_DOES_NOT_EXIST); + if (!exist) revert IDA_INDEX_DOES_NOT_EXIST(); } /************************************************************************** @@ -345,7 +344,7 @@ contract InstantDistributionAgreementV1 is if (vars.subscriptionExists) { // required condition check if (vars.sdata.subId != _UNALLOCATED_SUB_ID) { - revert SuperfluidErrors.ALREADY_EXISTS(SuperfluidErrors.IDA_SUBSCRIPTION_ALREADY_APPROVED); + revert IDA_SUBSCRIPTION_ALREADY_APPROVED(); } } @@ -433,7 +432,7 @@ contract InstantDistributionAgreementV1 is // should not revoke an pending(un-approved) subscription if (vars.sdata.subId == _UNALLOCATED_SUB_ID) { - revert SuperfluidErrors.DOES_NOT_EXIST(SuperfluidErrors.IDA_SUBSCRIPTION_IS_NOT_APPROVED); + revert IDA_SUBSCRIPTION_IS_NOT_APPROVED(); } cbStates = AgreementLibrary.createCallbackInputs( @@ -484,7 +483,7 @@ contract InstantDistributionAgreementV1 is returns(bytes memory newCtx) { if (subscriber == address(0)) { - revert SuperfluidErrors.ZERO_ADDRESS(SuperfluidErrors.IDA_ZERO_ADDRESS_SUBSCRIBER); + revert IDA_ZERO_ADDRESS_SUBSCRIBER(); } _SubscriptionOperationVars memory vars; AgreementLibrary.CallbackInputs memory cbStates; @@ -646,7 +645,7 @@ contract InstantDistributionAgreementV1 is (exist, sdata) = _getSubscriptionData(token, agreementId); if (!exist) { - revert SuperfluidErrors.DOES_NOT_EXIST(SuperfluidErrors.IDA_SUBSCRIPTION_DOES_NOT_EXIST); + revert IDA_SUBSCRIPTION_DOES_NOT_EXIST(); } publisher = sdata.publisher; @@ -713,7 +712,7 @@ contract InstantDistributionAgreementV1 is userData = context.userData; } if (subscriber == address(0)) { - revert SuperfluidErrors.ZERO_ADDRESS(SuperfluidErrors.IDA_ZERO_ADDRESS_SUBSCRIBER); + revert IDA_ZERO_ADDRESS_SUBSCRIBER(); } // only publisher can delete a subscription @@ -789,7 +788,7 @@ contract InstantDistributionAgreementV1 is { AgreementLibrary.authorizeTokenAccess(token, ctx); if (subscriber == address(0)) { - revert SuperfluidErrors.ZERO_ADDRESS(SuperfluidErrors.IDA_ZERO_ADDRESS_SUBSCRIBER); + revert IDA_ZERO_ADDRESS_SUBSCRIBER(); } _SubscriptionOperationVars memory vars; @@ -805,7 +804,7 @@ contract InstantDistributionAgreementV1 is // required condition check if (vars.sdata.subId != _UNALLOCATED_SUB_ID) { - revert SuperfluidErrors.ALREADY_EXISTS(SuperfluidErrors.IDA_SUBSCRIPTION_ALREADY_APPROVED); + revert IDA_SUBSCRIPTION_ALREADY_APPROVED(); } uint256 pendingDistribution = uint256(vars.idata.indexValue - vars.sdata.indexValue) @@ -862,11 +861,11 @@ contract InstantDistributionAgreementV1 is iId = _getPublisherId(publisher, indexId); sId = _getSubscriptionId(subscriber, iId); (indexExists, idata) = _getIndexData(token, iId); - if (!indexExists) revert SuperfluidErrors.DOES_NOT_EXIST(SuperfluidErrors.IDA_INDEX_DOES_NOT_EXIST); + if (!indexExists) revert IDA_INDEX_DOES_NOT_EXIST(); (subscriptionExists, sdata) = _getSubscriptionData(token, sId); if (requireSubscriptionExisting) { if (!subscriptionExists) { - revert SuperfluidErrors.DOES_NOT_EXIST(SuperfluidErrors.IDA_SUBSCRIPTION_DOES_NOT_EXIST); + revert IDA_SUBSCRIPTION_DOES_NOT_EXIST(); } // sanity check assert(sdata.publisher == publisher); diff --git a/packages/ethereum-contracts/contracts/gov/SuperfluidGovernanceBase.sol b/packages/ethereum-contracts/contracts/gov/SuperfluidGovernanceBase.sol index 0c53a9373f..97925caa91 100644 --- a/packages/ethereum-contracts/contracts/gov/SuperfluidGovernanceBase.sol +++ b/packages/ethereum-contracts/contracts/gov/SuperfluidGovernanceBase.sol @@ -8,7 +8,6 @@ import { ISuperToken, ISuperTokenFactory, ISuperfluidGovernance, - SuperfluidErrors, SuperfluidGovernanceConfigs } from "../interfaces/superfluid/ISuperfluid.sol"; @@ -26,8 +25,16 @@ abstract contract SuperfluidGovernanceBase is ISuperfluidGovernance uint256 value; } + /* WARNING: NEVER RE-ORDER VARIABLES! Including the base contracts. + Always double-check that new + variables are added APPEND-ONLY. Re-ordering variables can + permanently BREAK the deployed proxy contract. */ + // host => superToken => config mapping (address => mapping (address => mapping (bytes32 => Value))) internal _configs; + /// NOTE: Whenever modifying the storage layout here it is important to update the validateStorageLayout + /// function in its respective mock contract to ensure that it doesn't break anything or lead to unexpected + /// behaviors/layout when upgrading /************************************************************************** /* ISuperfluidGovernance interface @@ -515,7 +522,7 @@ abstract contract SuperfluidGovernanceBase is ISuperfluidGovernance uint256 cs; // solhint-disable-next-line no-inline-assembly assembly { cs := extcodesize(factory) } - if (cs == 0) revert SuperfluidErrors.MUST_BE_CONTRACT(SuperfluidErrors.SF_GOV_MUST_BE_CONTRACT); + if (cs == 0) revert SF_GOV_MUST_BE_CONTRACT(); } _setConfig( host, ISuperfluidToken(address(0)), diff --git a/packages/ethereum-contracts/contracts/interfaces/agreements/IConstantFlowAgreementV1.sol b/packages/ethereum-contracts/contracts/interfaces/agreements/IConstantFlowAgreementV1.sol index 46d06324b9..0eaa324673 100644 --- a/packages/ethereum-contracts/contracts/interfaces/agreements/IConstantFlowAgreementV1.sol +++ b/packages/ethereum-contracts/contracts/interfaces/agreements/IConstantFlowAgreementV1.sol @@ -3,7 +3,6 @@ pragma solidity >= 0.8.4; import { ISuperAgreement } from "../superfluid/ISuperAgreement.sol"; import { ISuperfluidToken } from "../superfluid/ISuperfluidToken.sol"; -import { SuperfluidErrors } from "../superfluid/Definitions.sol"; /** * @title Constant Flow Agreement interface @@ -14,21 +13,26 @@ abstract contract IConstantFlowAgreementV1 is ISuperAgreement { /************************************************************************** * Errors *************************************************************************/ - error CFA_ACL_NO_SENDER_CREATE(); - error CFA_ACL_NO_SENDER_UPDATE(); - error CFA_ACL_OPERATOR_NO_CREATE_PERMISSIONS(); - error CFA_ACL_OPERATOR_NO_UPDATE_PERMISSIONS(); - error CFA_ACL_OPERATOR_NO_DELETE_PERMISSIONS(); - error CFA_ACL_FLOW_RATE_ALLOWANCE_EXCEEDED(); - error CFA_ACL_UNCLEAN_PERMISSIONS(); - error CFA_ACL_NO_SENDER_FLOW_OPERATOR(); - error CFA_ACL_NO_NEGATIVE_ALLOWANCE(); - - error CFA_DEPOSIT_TOO_BIG(); - error CFA_FLOW_RATE_TOO_BIG(); - error CFA_NON_CRITICAL_SENDER(); - error CFA_INVALID_FLOW_RATE(); - error CFA_NO_SELF_FLOW(); + error CFA_ACL_NO_SENDER_CREATE(); // 0x4b993136 + error CFA_ACL_NO_SENDER_UPDATE(); // 0xedfa0d3b + error CFA_ACL_OPERATOR_NO_CREATE_PERMISSIONS(); // 0xa3eab6ac + error CFA_ACL_OPERATOR_NO_UPDATE_PERMISSIONS(); // 0xac434b5f + error CFA_ACL_OPERATOR_NO_DELETE_PERMISSIONS(); // 0xe30f1bff + error CFA_ACL_FLOW_RATE_ALLOWANCE_EXCEEDED(); // 0xa0645c1f + error CFA_ACL_UNCLEAN_PERMISSIONS(); // 0x7939d66c + error CFA_ACL_NO_SENDER_FLOW_OPERATOR(); // 0xb0ed394d + error CFA_ACL_NO_NEGATIVE_ALLOWANCE(); // 0x86e0377d + error CFA_FLOW_ALREADY_EXISTS(); // 0x801b6863 + error CFA_FLOW_DOES_NOT_EXIST(); // 0x5a32bf24 + error CFA_INSUFFICIENT_BALANCE(); // 0xea76c9b3 + error CFA_ZERO_ADDRESS_SENDER(); // 0x1ce9b067 + error CFA_ZERO_ADDRESS_RECEIVER(); // 0x78e02b2a + error CFA_HOOK_OUT_OF_GAS(); // 0x9f76430b + error CFA_DEPOSIT_TOO_BIG(); // 0x752c2b9c + error CFA_FLOW_RATE_TOO_BIG(); // 0x0c9c55c1 + error CFA_NON_CRITICAL_SENDER(); // 0xce11b5d1 + error CFA_INVALID_FLOW_RATE(); // 0x91acad16 + error CFA_NO_SELF_FLOW(); // 0xa47338ef /// @dev ISuperAgreement.agreementType implementation function agreementType() external override pure returns (bytes32) { diff --git a/packages/ethereum-contracts/contracts/interfaces/agreements/IInstantDistributionAgreementV1.sol b/packages/ethereum-contracts/contracts/interfaces/agreements/IInstantDistributionAgreementV1.sol index 35e5f48cc6..6031308713 100644 --- a/packages/ethereum-contracts/contracts/interfaces/agreements/IInstantDistributionAgreementV1.sol +++ b/packages/ethereum-contracts/contracts/interfaces/agreements/IInstantDistributionAgreementV1.sol @@ -3,7 +3,6 @@ pragma solidity >= 0.8.4; import { ISuperAgreement } from "../superfluid/ISuperAgreement.sol"; import { ISuperfluidToken } from "../superfluid/ISuperfluidToken.sol"; -import { SuperfluidErrors } from "../superfluid/Definitions.sol"; /** @@ -39,8 +38,15 @@ abstract contract IInstantDistributionAgreementV1 is ISuperAgreement { /************************************************************************** * Errors *************************************************************************/ - error IDA_INDEX_SHOULD_GROW(); // index value should grow - error IDA_OPERATION_NOT_ALLOWED(); // operation not allowed + error IDA_INDEX_SHOULD_GROW(); // 0xcfdca725 + error IDA_OPERATION_NOT_ALLOWED(); // 0x92da6d17 + error IDA_INDEX_ALREADY_EXISTS(); // 0x5c02a517 + error IDA_INDEX_DOES_NOT_EXIST(); // 0xedeaa63b + error IDA_SUBSCRIPTION_DOES_NOT_EXIST(); // 0xb6c8c980 + error IDA_SUBSCRIPTION_ALREADY_APPROVED(); // 0x3eb2f849 + error IDA_SUBSCRIPTION_IS_NOT_APPROVED(); // 0x37412573 + error IDA_INSUFFICIENT_BALANCE(); // 0x16e759bb + error IDA_ZERO_ADDRESS_SUBSCRIBER(); // 0xc90a4674 /// @dev ISuperAgreement.agreementType implementation function agreementType() external override pure returns (bytes32) { diff --git a/packages/ethereum-contracts/contracts/interfaces/superfluid/Definitions.sol b/packages/ethereum-contracts/contracts/interfaces/superfluid/Definitions.sol index 75631cfbeb..8905be59d4 100644 --- a/packages/ethereum-contracts/contracts/interfaces/superfluid/Definitions.sol +++ b/packages/ethereum-contracts/contracts/interfaces/superfluid/Definitions.sol @@ -227,93 +227,3 @@ library SuperfluidGovernanceConfigs { patricianPeriod = pppConfig & type(uint32).max; } } - -/** - * @title Superfluid Common Custom Errors and Error Codes - * @author Superfluid - */ -library SuperfluidErrors { - /************************************************************************** - / Shared Custom Errors - /**************************************************************************/ - error APP_RULE(uint256 _code); // uses SuperAppDefinitions' App Jail Reasons - - // The Error Code Reference refers to the types of errors within a range, - // e.g. ALREADY_EXISTS and DOES_NOT_EXIST error codes will live between - // 1000-1099 for Constant Flow Agreement error codes. - - // Error Code Reference - error ALREADY_EXISTS(uint256 _code); // 0 - 99 - error DOES_NOT_EXIST(uint256 _code); // 0 - 99 - error INSUFFICIENT_BALANCE(uint256 _code); // 100 - 199 - error MUST_BE_CONTRACT(uint256 _code); // 200 - 299 - error ONLY_LISTED_AGREEMENT(uint256 _code); // 300 - 399 - error ONLY_HOST(uint256 _code); // 400 - 499 - error ZERO_ADDRESS(uint256 _code); // 500 - 599 - - /************************************************************************** - / Error Codes - /**************************************************************************/ - // 1000 - 1999 | Constant Flow Agreement - uint256 constant internal CFA_FLOW_ALREADY_EXISTS = 1000; - uint256 constant internal CFA_FLOW_DOES_NOT_EXIST = 1001; - - uint256 constant internal CFA_INSUFFICIENT_BALANCE = 1100; - - uint256 constant internal CFA_ZERO_ADDRESS_SENDER = 1500; - uint256 constant internal CFA_ZERO_ADDRESS_RECEIVER = 1501; - - // 2000 - 2999 | Instant Distribution Agreement - uint256 constant internal IDA_INDEX_ALREADY_EXISTS = 2000; - uint256 constant internal IDA_INDEX_DOES_NOT_EXIST = 2001; - - uint256 constant internal IDA_SUBSCRIPTION_DOES_NOT_EXIST = 2002; - - uint256 constant internal IDA_SUBSCRIPTION_ALREADY_APPROVED = 2003; - uint256 constant internal IDA_SUBSCRIPTION_IS_NOT_APPROVED = 2004; - - uint256 constant internal IDA_INSUFFICIENT_BALANCE = 2100; - - uint256 constant internal IDA_ZERO_ADDRESS_SUBSCRIBER = 2500; - - // 3000 - 3999 | Host - uint256 constant internal HOST_AGREEMENT_ALREADY_REGISTERED = 3000; - uint256 constant internal HOST_AGREEMENT_IS_NOT_REGISTERED = 3001; - uint256 constant internal HOST_SUPER_APP_ALREADY_REGISTERED = 3002; - - uint256 constant internal HOST_MUST_BE_CONTRACT = 3200; - - uint256 constant internal HOST_ONLY_LISTED_AGREEMENT = 3300; - - // 4000 - 4999 | Superfluid Governance II - uint256 constant internal SF_GOV_MUST_BE_CONTRACT = 4200; - - // 5000 - 5999 | SuperfluidToken - uint256 constant internal SF_TOKEN_AGREEMENT_ALREADY_EXISTS = 5000; - uint256 constant internal SF_TOKEN_AGREEMENT_DOES_NOT_EXIST = 5001; - - uint256 constant internal SF_TOKEN_BURN_INSUFFICIENT_BALANCE = 5100; - uint256 constant internal SF_TOKEN_MOVE_INSUFFICIENT_BALANCE = 5101; - - uint256 constant internal SF_TOKEN_ONLY_LISTED_AGREEMENT = 5300; - - uint256 constant internal SF_TOKEN_ONLY_HOST = 5400; - - // 6000 - 6999 | SuperToken - uint256 constant internal SUPER_TOKEN_ONLY_HOST = 6400; - - uint256 constant internal SUPER_TOKEN_APPROVE_FROM_ZERO_ADDRESS = 6500; - uint256 constant internal SUPER_TOKEN_APPROVE_TO_ZERO_ADDRESS = 6501; - uint256 constant internal SUPER_TOKEN_BURN_FROM_ZERO_ADDRESS = 6502; - uint256 constant internal SUPER_TOKEN_MINT_TO_ZERO_ADDRESS = 6503; - uint256 constant internal SUPER_TOKEN_TRANSFER_FROM_ZERO_ADDRESS = 6504; - uint256 constant internal SUPER_TOKEN_TRANSFER_TO_ZERO_ADDRESS = 6505; - - // 7000 - 7999 | SuperToken Factory - uint256 constant internal SUPER_TOKEN_FACTORY_ONLY_HOST = 7400; - - uint256 constant internal SUPER_TOKEN_FACTORY_ZERO_ADDRESS = 7500; - - // 8000 - 8999 | Agreement Base - uint256 constant internal AGREEMENT_BASE_ONLY_HOST = 8400; -} \ No newline at end of file diff --git a/packages/ethereum-contracts/contracts/interfaces/superfluid/ISuperToken.sol b/packages/ethereum-contracts/contracts/interfaces/superfluid/ISuperToken.sol index 2bcdd76be6..254239ccd8 100644 --- a/packages/ethereum-contracts/contracts/interfaces/superfluid/ISuperToken.sol +++ b/packages/ethereum-contracts/contracts/interfaces/superfluid/ISuperToken.sol @@ -6,7 +6,6 @@ import { ISuperfluidToken } from "./ISuperfluidToken.sol"; import { TokenInfo } from "../tokens/TokenInfo.sol"; import { IERC777 } from "@openzeppelin/contracts/token/ERC777/IERC777.sol"; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import { SuperfluidErrors } from "./Definitions.sol"; /** * @title Super token (Superfluid Token + ERC20 + ERC777) interface @@ -17,11 +16,18 @@ interface ISuperToken is ISuperfluidToken, TokenInfo, IERC20, IERC777 { /************************************************************************** * Errors *************************************************************************/ - error SUPER_TOKEN_CALLER_IS_NOT_OPERATOR_FOR_HOLDER(); - error SUPER_TOKEN_NOT_ERC777_TOKENS_RECIPIENT(); - error SUPER_TOKEN_INFLATIONARY_DEFLATIONARY_NOT_SUPPORTED(); - error SUPER_TOKEN_NO_UNDERLYING_TOKEN(); - error SUPER_TOKEN_ONLY_SELF(); + error SUPER_TOKEN_CALLER_IS_NOT_OPERATOR_FOR_HOLDER(); // 0xf7f02227 + error SUPER_TOKEN_NOT_ERC777_TOKENS_RECIPIENT(); // 0xfe737d05 + error SUPER_TOKEN_INFLATIONARY_DEFLATIONARY_NOT_SUPPORTED(); // 0xe3e13698 + error SUPER_TOKEN_NO_UNDERLYING_TOKEN(); // 0xf79cf656 + error SUPER_TOKEN_ONLY_SELF(); // 0x7ffa6648 + error SUPER_TOKEN_ONLY_HOST(); // 0x98f73704 + error SUPER_TOKEN_APPROVE_FROM_ZERO_ADDRESS(); // 0x81638627 + error SUPER_TOKEN_APPROVE_TO_ZERO_ADDRESS(); // 0xdf070274 + error SUPER_TOKEN_BURN_FROM_ZERO_ADDRESS(); // 0xba2ab184 + error SUPER_TOKEN_MINT_TO_ZERO_ADDRESS(); // 0x0d243157 + error SUPER_TOKEN_TRANSFER_FROM_ZERO_ADDRESS(); // 0xeecd6c9b + error SUPER_TOKEN_TRANSFER_TO_ZERO_ADDRESS(); // 0xe219bd39 /** * @dev Initialize the contract diff --git a/packages/ethereum-contracts/contracts/interfaces/superfluid/ISuperTokenFactory.sol b/packages/ethereum-contracts/contracts/interfaces/superfluid/ISuperTokenFactory.sol index 4980efe87c..aa72ff601f 100644 --- a/packages/ethereum-contracts/contracts/interfaces/superfluid/ISuperTokenFactory.sol +++ b/packages/ethereum-contracts/contracts/interfaces/superfluid/ISuperTokenFactory.sol @@ -7,13 +7,22 @@ import { IERC20, ERC20WithTokenInfo } from "../tokens/ERC20WithTokenInfo.sol"; -import { SuperfluidErrors } from "./Definitions.sol"; /** * @title Super token factory interface * @author Superfluid */ interface ISuperTokenFactory { + + /************************************************************************** + * Errors + *************************************************************************/ + error SUPER_TOKEN_FACTORY_ALREADY_EXISTS(); // 0x91d67972 + error SUPER_TOKEN_FACTORY_DOES_NOT_EXIST(); // 0x872cac48 + error SUPER_TOKEN_FACTORY_UNINITIALIZED(); // 0x1b39b9b4 + error SUPER_TOKEN_FACTORY_ONLY_HOST(); // 0x478b8e83 + error SUPER_TOKEN_FACTORY_ZERO_ADDRESS(); // 0x305c9e82 + /** * @dev Get superfluid host contract address */ @@ -36,16 +45,17 @@ interface ISuperTokenFactory { /// Upgradable through `host.updateSuperTokenLogic` operation SEMI_UPGRADABLE, /// Always using the latest super token logic - FULL_UPGRADABE + FULL_UPGRADABLE } /** - * @dev Create new super token wrapper for the underlying ERC20 token + * @notice Create new super token wrapper for the underlying ERC20 token * @param underlyingToken Underlying ERC20 token * @param underlyingDecimals Underlying token decimals * @param upgradability Upgradability mode * @param name Super token name * @param symbol Super token symbol + * @return superToken The deployed and initialized wrapper super token */ function createERC20Wrapper( IERC20 underlyingToken, @@ -58,12 +68,12 @@ interface ISuperTokenFactory { returns (ISuperToken superToken); /** - * @dev Create new super token wrapper for the underlying ERC20 token with extra token info + * @notice Create new super token wrapper for the underlying ERC20 token with extra token info * @param underlyingToken Underlying ERC20 token * @param upgradability Upgradability mode * @param name Super token name * @param symbol Super token symbol - * + * @return superToken The deployed and initialized wrapper super token * NOTE: * - It assumes token provide the .decimals() function */ @@ -76,6 +86,44 @@ interface ISuperTokenFactory { external returns (ISuperToken superToken); + /** + * @notice Creates a wrapper super token AND sets it in the canonical list OR reverts if it already exists + * @dev salt for create2 is the keccak256 hash of abi.encode(address(_underlyingToken)) + * @param _underlyingToken Underlying ERC20 token + * @return ISuperToken the created supertoken + */ + function createCanonicalERC20Wrapper(ERC20WithTokenInfo _underlyingToken) + external + returns (ISuperToken); + + /** + * @notice Computes/Retrieves wrapper super token address given the underlying token address + * @dev We return from our canonical list if it already exists, otherwise we compute it + * @dev note that this function only computes addresses for SEMI_UPGRADABLE SuperTokens + * @param _underlyingToken Underlying ERC20 token address + * @return superTokenAddress Super token address + * @return isDeployed whether the super token is deployed AND set in the canonical mapping + */ + function computeCanonicalERC20WrapperAddress(address _underlyingToken) + external + view + returns (address superTokenAddress, bool isDeployed); + + /** + * @notice Gets the canonical ERC20 wrapper super token address given the underlying token address + * @dev We return the address if it exists and the zero address otherwise + * @param _underlyingTokenAddress Underlying ERC20 token address + * @return superTokenAddress Super token address + */ + function getCanonicalERC20Wrapper(address _underlyingTokenAddress) + external + view + returns (address superTokenAddress); + + /** + * @dev Creates a new custom super token + * @param customSuperTokenProxy address of the custom supertoken proxy + */ function initializeCustomSuperToken( address customSuperTokenProxy ) diff --git a/packages/ethereum-contracts/contracts/interfaces/superfluid/ISuperfluid.sol b/packages/ethereum-contracts/contracts/interfaces/superfluid/ISuperfluid.sol index 6ab5ae7a2b..770aa1f118 100644 --- a/packages/ethereum-contracts/contracts/interfaces/superfluid/ISuperfluid.sol +++ b/packages/ethereum-contracts/contracts/interfaces/superfluid/ISuperfluid.sol @@ -12,7 +12,6 @@ import { ContextDefinitions, FlowOperatorDefinitions, SuperAppDefinitions, - SuperfluidErrors, SuperfluidGovernanceConfigs } from "./Definitions.sol"; import { TokenInfo } from "../tokens/TokenInfo.sol"; @@ -35,27 +34,34 @@ interface ISuperfluid { * Errors *************************************************************************/ // Superfluid Custom Errors - error HOST_AGREEMENT_CALLBACK_IS_NOT_ACTION(); - error HOST_CANNOT_DOWNGRADE_TO_NON_UPGRADEABLE(); - error HOST_CALL_AGREEMENT_WITH_CTX_FROM_WRONG_ADDRESS(); - error HOST_CALL_APP_ACTION_WITH_CTX_FROM_WRONG_ADDRESS(); - error HOST_INVALID_CONFIG_WORD(); - error HOST_MAX_256_AGREEMENTS(); - error HOST_NON_UPGRADEABLE(); - error HOST_NON_ZERO_LENGTH_PLACEHOLDER_CTX(); - error HOST_ONLY_GOVERNANCE(); - error HOST_UNKNOWN_BATCH_CALL_OPERATION_TYPE(); + error HOST_AGREEMENT_CALLBACK_IS_NOT_ACTION(); // 0xef4295f6 + error HOST_CANNOT_DOWNGRADE_TO_NON_UPGRADEABLE(); // 0x474e7641 + error HOST_CALL_AGREEMENT_WITH_CTX_FROM_WRONG_ADDRESS(); // 0x0cd0ebc2 + error HOST_CALL_APP_ACTION_WITH_CTX_FROM_WRONG_ADDRESS(); // 0x473f7bd4 + error HOST_INVALID_CONFIG_WORD(); // 0xf4c802a4 + error HOST_MAX_256_AGREEMENTS(); // 0x7c281a78 + error HOST_NON_UPGRADEABLE(); // 0x14f72c9f + error HOST_NON_ZERO_LENGTH_PLACEHOLDER_CTX(); // 0x67e9985b + error HOST_ONLY_GOVERNANCE(); // 0xc5d22a4e + error HOST_UNKNOWN_BATCH_CALL_OPERATION_TYPE(); // 0xb4770115 + error HOST_AGREEMENT_ALREADY_REGISTERED(); // 0xdc9ddba8 + error HOST_AGREEMENT_IS_NOT_REGISTERED(); // 0x1c9e9bea + error HOST_MUST_BE_CONTRACT(); // 0xd4f6b30c + error HOST_ONLY_LISTED_AGREEMENT(); // 0x619c5359 // App Related Custom Errors - error HOST_INVALID_OR_EXPIRED_SUPER_APP_REGISTRATION_KEY(); - error HOST_NOT_A_SUPER_APP(); - error HOST_NO_APP_REGISTRATION_PERMISSIONS(); - error HOST_RECEIVER_IS_NOT_SUPER_APP(); - error HOST_SENDER_IS_NOT_SUPER_APP(); - error HOST_SOURCE_APP_NEEDS_HIGHER_APP_LEVEL(); - error HOST_SUPER_APP_IS_JAILED(); - error HOST_SUPER_APP_ALREADY_REGISTERED(); - error HOST_UNAUTHORIZED_SUPER_APP_FACTORY(); + // uses SuperAppDefinitions' App Jail Reasons as _code + error APP_RULE(uint256 _code); // 0xa85ba64f + + error HOST_INVALID_OR_EXPIRED_SUPER_APP_REGISTRATION_KEY(); // 0x19ab84d1 + error HOST_NOT_A_SUPER_APP(); // 0x163cbe43 + error HOST_NO_APP_REGISTRATION_PERMISSIONS(); // 0x5b93ebf0 + error HOST_RECEIVER_IS_NOT_SUPER_APP(); // 0x96aa315e + error HOST_SENDER_IS_NOT_SUPER_APP(); // 0xbacfdc40 + error HOST_SOURCE_APP_NEEDS_HIGHER_APP_LEVEL(); // 0x44725270 + error HOST_SUPER_APP_IS_JAILED(); // 0x02384b64 + error HOST_SUPER_APP_ALREADY_REGISTERED(); // 0x01b0a935 + error HOST_UNAUTHORIZED_SUPER_APP_FACTORY(); // 0x289533c5 /************************************************************************** * Time diff --git a/packages/ethereum-contracts/contracts/interfaces/superfluid/ISuperfluidGovernance.sol b/packages/ethereum-contracts/contracts/interfaces/superfluid/ISuperfluidGovernance.sol index 0edb18fa6b..50838d9d84 100644 --- a/packages/ethereum-contracts/contracts/interfaces/superfluid/ISuperfluidGovernance.sol +++ b/packages/ethereum-contracts/contracts/interfaces/superfluid/ISuperfluidGovernance.sol @@ -5,7 +5,6 @@ import { ISuperAgreement } from "./ISuperAgreement.sol"; import { ISuperToken } from "./ISuperToken.sol"; import { ISuperfluidToken } from "./ISuperfluidToken.sol"; import { ISuperfluid } from "./ISuperfluid.sol"; -import { SuperfluidErrors } from "./Definitions.sol"; /** @@ -17,8 +16,9 @@ interface ISuperfluidGovernance { /************************************************************************** * Errors *************************************************************************/ - error SF_GOV_ARRAYS_NOT_SAME_LENGTH(); - error SF_GOV_INVALID_LIQUIDATION_OR_PATRICIAN_PERIOD(); + error SF_GOV_ARRAYS_NOT_SAME_LENGTH(); // 0x27743aa6 + error SF_GOV_INVALID_LIQUIDATION_OR_PATRICIAN_PERIOD(); // 0xe171980a + error SF_GOV_MUST_BE_CONTRACT(); // 0x80dddd73 /** * @dev Replace the current governance with a new governance diff --git a/packages/ethereum-contracts/contracts/interfaces/superfluid/ISuperfluidToken.sol b/packages/ethereum-contracts/contracts/interfaces/superfluid/ISuperfluidToken.sol index 6c349be7a9..15a333a348 100644 --- a/packages/ethereum-contracts/contracts/interfaces/superfluid/ISuperfluidToken.sol +++ b/packages/ethereum-contracts/contracts/interfaces/superfluid/ISuperfluidToken.sol @@ -2,13 +2,23 @@ pragma solidity >= 0.8.4; import { ISuperAgreement } from "./ISuperAgreement.sol"; -import { SuperfluidErrors } from "./Definitions.sol"; /** * @title Superfluid token interface * @author Superfluid */ interface ISuperfluidToken { + + /************************************************************************** + * Errors + *************************************************************************/ + error SF_TOKEN_AGREEMENT_ALREADY_EXISTS(); // 0xf05521f6 + error SF_TOKEN_AGREEMENT_DOES_NOT_EXIST(); // 0xdae18809 + error SF_TOKEN_BURN_INSUFFICIENT_BALANCE(); // 0x10ecdf44 + error SF_TOKEN_MOVE_INSUFFICIENT_BALANCE(); // 0x2f4cb941 + error SF_TOKEN_ONLY_LISTED_AGREEMENT(); // 0xc9ff6644 + error SF_TOKEN_ONLY_HOST(); // 0xc51efddd + /************************************************************************** * Basic information *************************************************************************/ diff --git a/packages/ethereum-contracts/contracts/mocks/SuperTokenFactoryMock.sol b/packages/ethereum-contracts/contracts/mocks/SuperTokenFactoryMock.sol index 6a9a0d875a..a4253cd15b 100644 --- a/packages/ethereum-contracts/contracts/mocks/SuperTokenFactoryMock.sol +++ b/packages/ethereum-contracts/contracts/mocks/SuperTokenFactoryMock.sol @@ -26,6 +26,9 @@ contract SuperTokenFactoryStorageLayoutTester is SuperTokenFactoryBase { assembly { slot:= _superTokenLogic.slot offset := _superTokenLogic.offset } require (slot == 0 && offset == 2, "_superTokenLogic changed location"); + + assembly { slot := _canonicalWrapperSuperTokens.slot offset := _canonicalWrapperSuperTokens.offset } + require(slot == 1 && offset == 0, "_canonicalWrapperSuperTokens changed location"); } // dummy impl @@ -67,7 +70,6 @@ contract SuperTokenFactoryMock is SuperTokenFactoryBase { return _helper.create(host, 0); } - } contract SuperTokenFactoryMock42 is SuperTokenFactoryBase diff --git a/packages/ethereum-contracts/contracts/superfluid/SuperToken.sol b/packages/ethereum-contracts/contracts/superfluid/SuperToken.sol index 117ef726ec..507a30f47c 100644 --- a/packages/ethereum-contracts/contracts/superfluid/SuperToken.sol +++ b/packages/ethereum-contracts/contracts/superfluid/SuperToken.sol @@ -22,7 +22,6 @@ import { SafeCast } from "@openzeppelin/contracts/utils/math/SafeCast.sol"; import { IERC777Recipient } from "@openzeppelin/contracts/token/ERC777/IERC777Recipient.sol"; import { IERC777Sender } from "@openzeppelin/contracts/token/ERC777/IERC777Sender.sol"; import { Address } from "@openzeppelin/contracts/utils/Address.sol"; -import { SuperfluidErrors } from "../interfaces/superfluid/Definitions.sol"; /** * @title Superfluid's super token implementation @@ -68,6 +67,11 @@ contract SuperToken is // NOTE: for future compatibility, these are reserved solidity slots // The sub-class of SuperToken solidity slot will start after _reserve22 + + // NOTE: Whenever modifying the storage layout here it is important to update the validateStorageLayout + // function in its respective mock contract to ensure that it doesn't break anything or lead to unexpected + // behaviors/layout when upgrading + uint256 internal _reserve22; uint256 private _reserve23; uint256 private _reserve24; @@ -114,7 +118,7 @@ contract SuperToken is } function updateCode(address newAddress) external override { - if (msg.sender != address(_host)) revert SuperfluidErrors.ONLY_HOST(SuperfluidErrors.SUPER_TOKEN_ONLY_HOST); + if (msg.sender != address(_host)) revert SUPER_TOKEN_ONLY_HOST(); UUPSProxiable._updateCodeAddress(newAddress); } @@ -150,10 +154,10 @@ contract SuperToken is internal returns (bool) { if (holder == address(0)) { - revert SuperfluidErrors.ZERO_ADDRESS(SuperfluidErrors.SUPER_TOKEN_TRANSFER_FROM_ZERO_ADDRESS); + revert SUPER_TOKEN_TRANSFER_FROM_ZERO_ADDRESS(); } if (recipient == address(0)) { - revert SuperfluidErrors.ZERO_ADDRESS(SuperfluidErrors.SUPER_TOKEN_TRANSFER_TO_ZERO_ADDRESS); + revert SUPER_TOKEN_TRANSFER_TO_ZERO_ADDRESS(); } address operator = msg.sender; @@ -191,10 +195,10 @@ contract SuperToken is private { if (from == address(0)) { - revert SuperfluidErrors.ZERO_ADDRESS(SuperfluidErrors.SUPER_TOKEN_TRANSFER_FROM_ZERO_ADDRESS); + revert SUPER_TOKEN_TRANSFER_FROM_ZERO_ADDRESS(); } if (to == address(0)) { - revert SuperfluidErrors.ZERO_ADDRESS(SuperfluidErrors.SUPER_TOKEN_TRANSFER_TO_ZERO_ADDRESS); + revert SUPER_TOKEN_TRANSFER_TO_ZERO_ADDRESS(); } _callTokensToSend(operator, from, to, amount, userData, operatorData); @@ -248,7 +252,7 @@ contract SuperToken is internal { if (account == address(0)) { - revert SuperfluidErrors.ZERO_ADDRESS(SuperfluidErrors.SUPER_TOKEN_MINT_TO_ZERO_ADDRESS); + revert SUPER_TOKEN_MINT_TO_ZERO_ADDRESS(); } SuperfluidToken._mint(account, amount); @@ -276,7 +280,7 @@ contract SuperToken is internal { if (from == address(0)) { - revert SuperfluidErrors.ZERO_ADDRESS(SuperfluidErrors.SUPER_TOKEN_BURN_FROM_ZERO_ADDRESS); + revert SUPER_TOKEN_BURN_FROM_ZERO_ADDRESS(); } _callTokensToSend(operator, from, address(0), amount, userData, operatorData); @@ -304,10 +308,10 @@ contract SuperToken is internal { if (account == address(0)) { - revert SuperfluidErrors.ZERO_ADDRESS(SuperfluidErrors.SUPER_TOKEN_APPROVE_FROM_ZERO_ADDRESS); + revert SUPER_TOKEN_APPROVE_FROM_ZERO_ADDRESS(); } if (spender == address(0)) { - revert SuperfluidErrors.ZERO_ADDRESS(SuperfluidErrors.SUPER_TOKEN_APPROVE_TO_ZERO_ADDRESS); + revert SUPER_TOKEN_APPROVE_TO_ZERO_ADDRESS(); } _allowances[account][spender] = amount; diff --git a/packages/ethereum-contracts/contracts/superfluid/SuperTokenFactory.sol b/packages/ethereum-contracts/contracts/superfluid/SuperTokenFactory.sol index 1e997a31f3..dfdc1e249f 100644 --- a/packages/ethereum-contracts/contracts/superfluid/SuperTokenFactory.sol +++ b/packages/ethereum-contracts/contracts/superfluid/SuperTokenFactory.sol @@ -5,11 +5,11 @@ import { ISuperTokenFactory, ISuperToken, IERC20, - ERC20WithTokenInfo, - SuperfluidErrors + ERC20WithTokenInfo } from "../interfaces/superfluid/ISuperTokenFactory.sol"; import { ISuperfluid } from "../interfaces/superfluid/ISuperfluid.sol"; +import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; import { UUPSProxy } from "../upgradability/UUPSProxy.sol"; import { UUPSProxiable } from "../upgradability/UUPSProxiable.sol"; @@ -18,26 +18,43 @@ import { SuperToken } from "../superfluid/SuperToken.sol"; import { FullUpgradableSuperTokenProxy } from "./FullUpgradableSuperTokenProxy.sol"; -import { Address } from "@openzeppelin/contracts/utils/Address.sol"; -import { Create2 } from "@openzeppelin/contracts/utils/Create2.sol"; - - abstract contract SuperTokenFactoryBase is UUPSProxiable, ISuperTokenFactory { + struct InitializeData { + address underlyingToken; + address superToken; + } + + /* WARNING: NEVER RE-ORDER VARIABLES! Including the base contracts. + Always double-check that new + variables are added APPEND-ONLY. Re-ordering variables can + permanently BREAK the deployed proxy contract. */ ISuperfluid immutable internal _host; ISuperToken internal _superTokenLogic; + /// @notice A mapping from underlying token addresses to canonical wrapper super token addresses + /// @dev Reasoning: (1) provide backwards compatibility for existing listed wrapper super tokens + /// @dev (2) prevent address retrieval issues if we ever choose to modify the bytecode of the UUPSProxy contract + /// @dev NOTE: address(0) key points to the NativeAssetSuperToken on the network. + mapping(address => address) internal _canonicalWrapperSuperTokens; + + /// NOTE: Whenever modifying the storage layout here it is important to update the validateStorageLayout + /// function in its respective mock contract to ensure that it doesn't break anything or lead to unexpected + /// behaviors/layout when upgrading + + error SUPER_TOKEN_FACTORY_ONLY_GOVERNANCE_OWNER(); + constructor( ISuperfluid host ) { _host = host; } - /// @dev ISuperTokenFactory.getHost implementation + /// @inheritdoc ISuperTokenFactory function getHost() external view override(ISuperTokenFactory) @@ -49,6 +66,7 @@ abstract contract SuperTokenFactoryBase is /************************************************************************** * UUPSProxiable **************************************************************************/ + /// @inheritdoc ISuperTokenFactory function initialize() external override initializer // OpenZeppelin Initializable @@ -62,7 +80,7 @@ abstract contract SuperTokenFactoryBase is function updateCode(address newAddress) external override { if (msg.sender != address(_host)) { - revert SuperfluidErrors.ONLY_HOST(SuperfluidErrors.SUPER_TOKEN_FACTORY_ONLY_HOST); + revert SUPER_TOKEN_FACTORY_ONLY_HOST(); } _updateCodeAddress(newAddress); _updateSuperTokenLogic(); @@ -78,6 +96,7 @@ abstract contract SuperTokenFactoryBase is /************************************************************************** * ISuperTokenFactory **************************************************************************/ + /// @inheritdoc ISuperTokenFactory function getSuperTokenLogic() external view override returns (ISuperToken) @@ -87,6 +106,60 @@ abstract contract SuperTokenFactoryBase is function createSuperTokenLogic(ISuperfluid host) external virtual returns (address logic); + /// @inheritdoc ISuperTokenFactory + function createCanonicalERC20Wrapper(ERC20WithTokenInfo _underlyingToken) + external + returns (ISuperToken) + { + // we use this to check if we have initialized the _canonicalWrapperSuperTokens mapping + // @note we must set this during initialization + if (_canonicalWrapperSuperTokens[address(0)] == address(0)) { + revert SUPER_TOKEN_FACTORY_UNINITIALIZED(); + } + + address underlyingTokenAddress = address(_underlyingToken); + address canonicalSuperTokenAddress = _canonicalWrapperSuperTokens[ + underlyingTokenAddress + ]; + + // if the canonical super token address exists, revert with custom error + if (canonicalSuperTokenAddress != address(0)) { + revert SUPER_TOKEN_FACTORY_ALREADY_EXISTS(); + } + + // use create2 to deterministically create the proxy contract for the wrapper super token + bytes32 salt = keccak256(abi.encode(underlyingTokenAddress)); + UUPSProxy proxy = new UUPSProxy{ salt: salt }(); + + // NOTE: address(proxy) is equivalent to address(superToken) + _canonicalWrapperSuperTokens[underlyingTokenAddress] = address( + proxy + ); + + // set the implementation/logic contract address for the newly deployed proxy + proxy.initializeProxy(address(_superTokenLogic)); + + // cast it as the same type as the logic contract + ISuperToken superToken = ISuperToken(address(proxy)); + + // get underlying token info + uint8 underlyingDecimals = _underlyingToken.decimals(); + string memory underlyingName = _underlyingToken.name(); + string memory underlyingSymbol = _underlyingToken.symbol(); + // initialize the contract (proxy constructor) + superToken.initialize( + _underlyingToken, + underlyingDecimals, + string.concat("Super ", underlyingName), + string.concat(underlyingSymbol, "x") + ); + + emit SuperTokenCreated(superToken); + + return superToken; + } + + /// @inheritdoc ISuperTokenFactory function createERC20Wrapper( IERC20 underlyingToken, uint8 underlyingDecimals, @@ -98,7 +171,7 @@ abstract contract SuperTokenFactoryBase is returns (ISuperToken superToken) { if (address(underlyingToken) == address(0)) { - revert SuperfluidErrors.ZERO_ADDRESS(SuperfluidErrors.SUPER_TOKEN_FACTORY_ZERO_ADDRESS); + revert SUPER_TOKEN_FACTORY_ZERO_ADDRESS(); } if (upgradability == Upgradability.NON_UPGRADABLE) { @@ -108,7 +181,7 @@ abstract contract SuperTokenFactoryBase is // initialize the wrapper proxy.initializeProxy(address(_superTokenLogic)); superToken = ISuperToken(address(proxy)); - } else /* if (type == Upgradability.FULL_UPGRADABE) */ { + } else /* if (type == Upgradability.FULL_UPGRADABLE) */ { FullUpgradableSuperTokenProxy proxy = new FullUpgradableSuperTokenProxy(); proxy.initialize(); superToken = ISuperToken(address(proxy)); @@ -125,6 +198,7 @@ abstract contract SuperTokenFactoryBase is emit SuperTokenCreated(superToken); } + /// @inheritdoc ISuperTokenFactory function createERC20Wrapper( ERC20WithTokenInfo underlyingToken, Upgradability upgradability, @@ -143,6 +217,7 @@ abstract contract SuperTokenFactoryBase is ); } + /// @inheritdoc ISuperTokenFactory function initializeCustomSuperToken( address customSuperTokenProxy ) @@ -156,6 +231,71 @@ abstract contract SuperTokenFactoryBase is emit CustomSuperTokenCreated(ISuperToken(customSuperTokenProxy)); } + /// @inheritdoc ISuperTokenFactory + function computeCanonicalERC20WrapperAddress(address _underlyingToken) + external + view + returns (address superTokenAddress, bool isDeployed) + { + address existingAddress = _canonicalWrapperSuperTokens[ + _underlyingToken + ]; + + if (existingAddress != address(0)) { + superTokenAddress = existingAddress; + isDeployed = true; + } else { + bytes memory bytecode = type(UUPSProxy).creationCode; + superTokenAddress = address( + uint160( + uint256( + keccak256( + abi.encodePacked( + bytes1(0xff), + address(this), + keccak256(abi.encode(_underlyingToken)), + keccak256(bytecode) + ) + ) + ) + ) + ); + isDeployed = false; + } + } + + /// @inheritdoc ISuperTokenFactory + function getCanonicalERC20Wrapper(address _underlyingTokenAddress) + external + view + returns (address superTokenAddress) + { + superTokenAddress = _canonicalWrapperSuperTokens[ + _underlyingTokenAddress + ]; + } + + /// @notice Initializes list of canonical wrapper super tokens. + /// @dev Note that this should also be kind of a throwaway function which will be executed only once. + /// @param _data an array of canonical wrappper super tokens to be set + function initializeCanonicalWrapperSuperTokens( + InitializeData[] calldata _data + ) external virtual { + Ownable gov = Ownable(address(_host.getGovernance())); + if (msg.sender != gov.owner()) revert SUPER_TOKEN_FACTORY_ONLY_GOVERNANCE_OWNER(); + + // once the list has been set, it cannot be reset + // @note this means that we must set the 0 address (Native Asset Super Token) when we call this the first time + if (_canonicalWrapperSuperTokens[address(0)] != address(0)) { + revert SUPER_TOKEN_FACTORY_ALREADY_EXISTS(); + } + + // initialize mapping + for (uint256 i = 0; i < _data.length; i++) { + _canonicalWrapperSuperTokens[_data[i].underlyingToken] = _data[i] + .superToken; + } + } } // splitting this off because the contract is getting bigger @@ -170,6 +310,11 @@ contract SuperTokenFactoryHelper { contract SuperTokenFactory is SuperTokenFactoryBase { + /* WARNING: NEVER RE-ORDER VARIABLES! Including the base contracts. + Always double-check that new + variables are added APPEND-ONLY. Re-ordering variables can + permanently BREAK the deployed proxy contract. */ + SuperTokenFactoryHelper immutable private _helper; constructor( diff --git a/packages/ethereum-contracts/contracts/superfluid/Superfluid.sol b/packages/ethereum-contracts/contracts/superfluid/Superfluid.sol index b8a23ac484..2300865c5b 100644 --- a/packages/ethereum-contracts/contracts/superfluid/Superfluid.sol +++ b/packages/ethereum-contracts/contracts/superfluid/Superfluid.sol @@ -15,7 +15,6 @@ import { ContextDefinitions, BatchOperation, SuperfluidGovernanceConfigs, - SuperfluidErrors, ISuperfluidToken, ISuperToken, ISuperTokenFactory, @@ -90,6 +89,10 @@ contract Superfluid is /// @dev if app whitelisting is enabled, this is to make sure the keys are used only once mapping(bytes32 => bool) internal _appKeysUsedDeprecated; + /// NOTE: Whenever modifying the storage layout here it is important to update the validateStorageLayout + /// 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) { NON_UPGRADABLE_DEPLOYMENT = nonUpgradable; APP_WHITE_LISTING_ENABLED = appWhiteListingEnabled; @@ -147,7 +150,7 @@ contract Superfluid is function registerAgreementClass(ISuperAgreement agreementClassLogic) external onlyGovernance override { bytes32 agreementType = agreementClassLogic.agreementType(); if (_agreementClassIndices[agreementType] != 0) { - revert SuperfluidErrors.ALREADY_EXISTS(SuperfluidErrors.HOST_AGREEMENT_ALREADY_REGISTERED); + revert HOST_AGREEMENT_ALREADY_REGISTERED(); } if (_agreementClasses.length >= 256) revert HOST_MAX_256_AGREEMENTS(); ISuperAgreement agreementClass; @@ -170,7 +173,7 @@ contract Superfluid is bytes32 agreementType = agreementClassLogic.agreementType(); uint idx = _agreementClassIndices[agreementType]; if (idx == 0) { - revert SuperfluidErrors.DOES_NOT_EXIST(SuperfluidErrors.HOST_AGREEMENT_IS_NOT_REGISTERED); + revert HOST_AGREEMENT_IS_NOT_REGISTERED(); } UUPSProxiable proxiable = UUPSProxiable(address(_agreementClasses[idx - 1])); proxiable.updateCode(address(agreementClassLogic)); @@ -201,7 +204,7 @@ contract Superfluid is { uint idx = _agreementClassIndices[agreementType]; if (idx == 0) { - revert SuperfluidErrors.DOES_NOT_EXIST(SuperfluidErrors.HOST_AGREEMENT_IS_NOT_REGISTERED); + revert HOST_AGREEMENT_IS_NOT_REGISTERED(); } return ISuperAgreement(_agreementClasses[idx - 1]); } @@ -230,7 +233,7 @@ contract Superfluid is { uint idx = _agreementClassIndices[agreementType]; if (idx == 0) { - revert SuperfluidErrors.DOES_NOT_EXIST(SuperfluidErrors.HOST_AGREEMENT_IS_NOT_REGISTERED); + revert HOST_AGREEMENT_IS_NOT_REGISTERED(); } return bitmap | (1 << (idx - 1)); } @@ -241,7 +244,7 @@ contract Superfluid is { uint idx = _agreementClassIndices[agreementType]; if (idx == 0) { - revert SuperfluidErrors.DOES_NOT_EXIST(SuperfluidErrors.HOST_AGREEMENT_IS_NOT_REGISTERED); + revert HOST_AGREEMENT_IS_NOT_REGISTERED(); } return bitmap & ~(1 << (idx - 1)); } @@ -345,7 +348,7 @@ contract Superfluid is uint256 cs; // solhint-disable-next-line no-inline-assembly assembly { cs := extcodesize(caller()) } - if (cs == 0) revert SuperfluidErrors.MUST_BE_CONTRACT(SuperfluidErrors.HOST_MUST_BE_CONTRACT); + if (cs == 0) revert HOST_MUST_BE_CONTRACT(); } if (APP_WHITE_LISTING_ENABLED) { @@ -365,7 +368,7 @@ contract Superfluid is { // solhint-disable-next-line avoid-tx-origin if (msg.sender == tx.origin) { - revert SuperfluidErrors.APP_RULE(SuperAppDefinitions.APP_RULE_NO_REGISTRATION_FOR_EOA); + revert APP_RULE(SuperAppDefinitions.APP_RULE_NO_REGISTRATION_FOR_EOA); } if (checkIfInAppConstructor) { @@ -373,7 +376,7 @@ contract Superfluid is // solhint-disable-next-line no-inline-assembly assembly { cs := extcodesize(app) } if (cs != 0) { - revert SuperfluidErrors.APP_RULE(SuperAppDefinitions.APP_RULE_REGISTRATION_ONLY_IN_CONSTRUCTOR); + revert APP_RULE(SuperAppDefinitions.APP_RULE_REGISTRATION_ONLY_IN_CONSTRUCTOR); } } if ( @@ -468,7 +471,7 @@ contract Superfluid is cbdata = abi.decode(returnedData, (bytes)); } else { if (!isTermination) { - revert SuperfluidErrors.APP_RULE(SuperAppDefinitions.APP_RULE_CTX_IS_MALFORMATED); + revert APP_RULE(SuperAppDefinitions.APP_RULE_CTX_IS_MALFORMATED); } else { _jailApp(app, SuperAppDefinitions.APP_RULE_CTX_IS_MALFORMATED); } @@ -494,7 +497,7 @@ contract Superfluid is newCtx = abi.decode(returnedData, (bytes)); if (!_isCtxValid(newCtx)) { if (!isTermination) { - revert SuperfluidErrors.APP_RULE(SuperAppDefinitions.APP_RULE_CTX_IS_READONLY); + revert APP_RULE(SuperAppDefinitions.APP_RULE_CTX_IS_READONLY); } else { newCtx = ctx; _jailApp(app, SuperAppDefinitions.APP_RULE_CTX_IS_READONLY); @@ -502,7 +505,7 @@ contract Superfluid is } } else { if (!isTermination) { - revert SuperfluidErrors.APP_RULE(SuperAppDefinitions.APP_RULE_CTX_IS_MALFORMATED); + revert APP_RULE(SuperAppDefinitions.APP_RULE_CTX_IS_MALFORMATED); } else { newCtx = ctx; _jailApp(app, SuperAppDefinitions.APP_RULE_CTX_IS_MALFORMATED); @@ -530,7 +533,7 @@ contract Superfluid is // we use 1 instead of MAX_APP_CALLBACK_LEVEL because 1 captures what we are trying to enforce if (isApp(ISuperApp(context.msgSender)) && context.appCallbackLevel >= 1) { if (!_compositeApps[ISuperApp(context.msgSender)][app]) { - revert SuperfluidErrors.APP_RULE(SuperAppDefinitions.APP_RULE_COMPOSITE_APP_IS_NOT_WHITELISTED); + revert APP_RULE(SuperAppDefinitions.APP_RULE_COMPOSITE_APP_IS_NOT_WHITELISTED); } } context.appCallbackLevel++; @@ -665,7 +668,7 @@ contract Superfluid is (success, returnedData) = _callExternalWithReplacedCtx(address(app), callData, ctx); if (success) { ctx = abi.decode(returnedData, (bytes)); - if (!_isCtxValid(ctx)) revert SuperfluidErrors.APP_RULE(SuperAppDefinitions.APP_RULE_CTX_IS_READONLY); + if (!_isCtxValid(ctx)) revert APP_RULE(SuperAppDefinitions.APP_RULE_CTX_IS_READONLY); } else { CallUtils.revertFromReturnedData(returnedData); } @@ -742,7 +745,7 @@ contract Superfluid is (bool success, bytes memory returnedData) = _callExternalWithReplacedCtx(address(app), callData, newCtx); if (success) { (newCtx) = abi.decode(returnedData, (bytes)); - if (!_isCtxValid(newCtx)) revert SuperfluidErrors.APP_RULE(SuperAppDefinitions.APP_RULE_CTX_IS_READONLY); + if (!_isCtxValid(newCtx)) revert APP_RULE(SuperAppDefinitions.APP_RULE_CTX_IS_READONLY); // back to old msg.sender context = decodeCtx(newCtx); context.msgSender = oldSender; @@ -873,7 +876,7 @@ contract Superfluid is returns (bytes memory ctx) { if (context.appCallbackLevel > MAX_APP_CALLBACK_LEVEL) { - revert SuperfluidErrors.APP_RULE(SuperAppDefinitions.APP_RULE_MAX_APP_LEVEL_REACHED); + revert APP_RULE(SuperAppDefinitions.APP_RULE_MAX_APP_LEVEL_REACHED); } uint256 callInfo = ContextDefinitions.encodeCallInfo(context.appCallbackLevel, context.callType); uint256 creditIO = @@ -965,7 +968,7 @@ contract Superfluid is if (success) { if (returnedData.length == 0) { - revert SuperfluidErrors.APP_RULE(SuperAppDefinitions.APP_RULE_CTX_IS_MALFORMATED); + revert APP_RULE(SuperAppDefinitions.APP_RULE_CTX_IS_MALFORMATED); } } } @@ -1054,7 +1057,7 @@ contract Superfluid is } modifier requireValidCtx(bytes memory ctx) { - if (!_isCtxValid(ctx)) revert SuperfluidErrors.APP_RULE(SuperAppDefinitions.APP_RULE_CTX_IS_READONLY); + if (!_isCtxValid(ctx)) revert APP_RULE(SuperAppDefinitions.APP_RULE_CTX_IS_READONLY); _; } @@ -1064,13 +1067,13 @@ contract Superfluid is } modifier cleanCtx() { - if (_ctxStamp != 0) revert SuperfluidErrors.APP_RULE(SuperAppDefinitions.APP_RULE_CTX_IS_NOT_CLEAN); + if (_ctxStamp != 0) revert APP_RULE(SuperAppDefinitions.APP_RULE_CTX_IS_NOT_CLEAN); _; } modifier isAgreement(ISuperAgreement agreementClass) { if (!isAgreementClassListed(agreementClass)) { - revert SuperfluidErrors.ONLY_LISTED_AGREEMENT(SuperfluidErrors.HOST_ONLY_LISTED_AGREEMENT); + revert HOST_ONLY_LISTED_AGREEMENT(); } _; } @@ -1082,7 +1085,7 @@ contract Superfluid is modifier onlyAgreement() { if (!isAgreementClassListed(ISuperAgreement(msg.sender))) { - revert SuperfluidErrors.ONLY_LISTED_AGREEMENT(SuperfluidErrors.HOST_ONLY_LISTED_AGREEMENT); + revert HOST_ONLY_LISTED_AGREEMENT(); } _; } diff --git a/packages/ethereum-contracts/contracts/superfluid/SuperfluidToken.sol b/packages/ethereum-contracts/contracts/superfluid/SuperfluidToken.sol index 7049b02ec5..7042b79b5b 100644 --- a/packages/ethereum-contracts/contracts/superfluid/SuperfluidToken.sol +++ b/packages/ethereum-contracts/contracts/superfluid/SuperfluidToken.sol @@ -4,7 +4,7 @@ pragma solidity 0.8.16; import { ISuperfluid } from "../interfaces/superfluid/ISuperfluid.sol"; import { ISuperAgreement } from "../interfaces/superfluid/ISuperAgreement.sol"; import { ISuperfluidGovernance } from "../interfaces/superfluid/ISuperfluidGovernance.sol"; -import { ISuperfluidToken, SuperfluidErrors } from "../interfaces/superfluid/ISuperfluidToken.sol"; +import { ISuperfluidToken } from "../interfaces/superfluid/ISuperfluidToken.sol"; import { SafeCast } from "@openzeppelin/contracts/utils/math/SafeCast.sol"; import { EventsEmitter } from "../libs/EventsEmitter.sol"; @@ -197,7 +197,7 @@ abstract contract SuperfluidToken is ISuperfluidToken { (int256 availableBalance,,) = realtimeBalanceOf(account, _host.getNow()); if (availableBalance < amount.toInt256()) { - revert SuperfluidErrors.INSUFFICIENT_BALANCE(SuperfluidErrors.SF_TOKEN_BURN_INSUFFICIENT_BALANCE); + revert SF_TOKEN_BURN_INSUFFICIENT_BALANCE(); } _sharedSettledBalances[account] = _sharedSettledBalances[account] - amount.toInt256(); _totalSupply = _totalSupply - amount; @@ -212,7 +212,7 @@ abstract contract SuperfluidToken is ISuperfluidToken { (int256 availableBalance,,) = realtimeBalanceOf(from, _host.getNow()); if (availableBalance < amount) { - revert SuperfluidErrors.INSUFFICIENT_BALANCE(SuperfluidErrors.SF_TOKEN_MOVE_INSUFFICIENT_BALANCE); + revert SF_TOKEN_MOVE_INSUFFICIENT_BALANCE(); } _sharedSettledBalances[from] = _sharedSettledBalances[from] - amount; _sharedSettledBalances[to] = _sharedSettledBalances[to] + amount; @@ -237,7 +237,7 @@ abstract contract SuperfluidToken is ISuperfluidToken address agreementClass = msg.sender; bytes32 slot = keccak256(abi.encode("AgreementData", agreementClass, id)); if (FixedSizeData.hasData(slot, data.length)) { - revert SuperfluidErrors.ALREADY_EXISTS(SuperfluidErrors.SF_TOKEN_AGREEMENT_ALREADY_EXISTS); + revert SF_TOKEN_AGREEMENT_ALREADY_EXISTS(); } FixedSizeData.storeData(slot, data); emit AgreementCreated(agreementClass, id, data); @@ -279,7 +279,7 @@ abstract contract SuperfluidToken is ISuperfluidToken address agreementClass = msg.sender; bytes32 slot = keccak256(abi.encode("AgreementData", agreementClass, id)); if (!FixedSizeData.hasData(slot,dataLength)) { - revert SuperfluidErrors.DOES_NOT_EXIST(SuperfluidErrors.SF_TOKEN_AGREEMENT_DOES_NOT_EXIST); + revert SF_TOKEN_AGREEMENT_DOES_NOT_EXIST(); } FixedSizeData.eraseData(slot, dataLength); emit AgreementTerminated(msg.sender, id); @@ -379,14 +379,14 @@ abstract contract SuperfluidToken is ISuperfluidToken modifier onlyAgreement() { if (!_host.isAgreementClassListed(ISuperAgreement(msg.sender))) { - revert SuperfluidErrors.ONLY_LISTED_AGREEMENT(SuperfluidErrors.SF_TOKEN_ONLY_LISTED_AGREEMENT); + revert SF_TOKEN_ONLY_LISTED_AGREEMENT(); } _; } modifier onlyHost() { if (address(_host) != msg.sender) { - revert SuperfluidErrors.ONLY_HOST(SuperfluidErrors.SF_TOKEN_ONLY_HOST); + revert SF_TOKEN_ONLY_HOST(); } _; } diff --git a/packages/ethereum-contracts/contracts/utils/TOGA.sol b/packages/ethereum-contracts/contracts/utils/TOGA.sol index 7fdeba3daa..c5c162b268 100644 --- a/packages/ethereum-contracts/contracts/utils/TOGA.sol +++ b/packages/ethereum-contracts/contracts/utils/TOGA.sol @@ -13,8 +13,6 @@ import { IConstantFlowAgreementV1 } from "../interfaces/agreements/IConstantFlow import { IERC1820Registry } from "@openzeppelin/contracts/utils/introspection/IERC1820Registry.sol"; import { IERC777Recipient } from "@openzeppelin/contracts/token/ERC777/IERC777Recipient.sol"; -import { TokenCustodian } from "./TokenCustodian.sol"; - /** * @title TOGA: Transparent Ongoing Auction * @author Superfluid @@ -33,6 +31,10 @@ import { TokenCustodian } from "./TokenCustodian.sol"; * Funds accumulated there can be withdrawn from there at any time. * The current PIC can increase its bond by sending more funds using ERC777.send(). * + * changes in v3: + * Use ERC20.transfer() instead of ERC777.send() when outbid. + * This allows us to eliminate the custodian contract and related complexity. + * */ interface ITOGAv1 { /** @@ -116,7 +118,15 @@ interface ITOGAv2 is ITOGAv1 { event BondIncreased(ISuperToken indexed token, uint256 additionalBond); } -contract TOGA is ITOGAv2, IERC777Recipient { +interface ITOGAv3 is ITOGAv1 { + /** + * @dev Emitted if a PIC increases its bond + * @param additionalBond The additional amount added to the bond + */ + event BondIncreased(ISuperToken indexed token, uint256 additionalBond); +} + +contract TOGA is ITOGAv3, IERC777Recipient { using SafeCast for uint256; // lightweight struct packing an address and a bool (reentrancy guard) into 1 word @@ -129,12 +139,8 @@ contract TOGA is ITOGAv2, IERC777Recipient { IConstantFlowAgreementV1 internal immutable _cfa; uint256 public immutable minBondDuration; IERC1820Registry constant internal _ERC1820_REG = IERC1820Registry(0x1820a4B7618BdE71Dce8cdc73aAB6C95905faD24); - // solhint-disable-next-line var-name-mixedcase - uint64 constant internal ERC777_SEND_GAS_LIMIT = 3000000; - // takes custody of bonds which the TOGA fails to send back to an outbid PIC - TokenCustodian public custodian; - constructor(ISuperfluid host_, uint256 minBondDuration_, TokenCustodian custodian_) { + constructor(ISuperfluid host_, uint256 minBondDuration_) { _host = ISuperfluid(host_); minBondDuration = minBondDuration_; _cfa = IConstantFlowAgreementV1( @@ -144,12 +150,6 @@ contract TOGA is ITOGAv2, IERC777Recipient { _ERC1820_REG.setInterfaceImplementer(address(this), erc777TokensRecipientHash, address(this)); _ERC1820_REG.setInterfaceImplementer(address(this), keccak256("TOGAv1"), address(this)); _ERC1820_REG.setInterfaceImplementer(address(this), keccak256("TOGAv2"), address(this)); - - require( - _ERC1820_REG.getInterfaceImplementer(address(custodian_), erc777TokensRecipientHash) == address(custodian_), - "TOGA: invalid custodian" - ); - custodian = custodian_; } function getCurrentPIC(ISuperToken token) external view override returns(address pic) { @@ -243,10 +243,6 @@ contract TOGA is ITOGAv2, IERC777Recipient { emit ExitRateChanged(token, newExitRate); } - function withdrawFundsInCustody(ISuperToken token) external override { - custodian.flush(token, msg.sender); - } - // ============ internal ============ function _getCurrentPICBond(ISuperToken token) internal view returns(uint256 bond) { @@ -288,16 +284,8 @@ contract TOGA is ITOGAv2, IERC777Recipient { // if no PIC was set yet, rewards already accumulated become part of the bond of the first PIC if (currentPICAddr != address(0)) { - // send remaining bond to current PIC - // solhint-disable-next-line check-send-result - try token.send{gas: ERC777_SEND_GAS_LIMIT}(currentPICAddr, currentPICBond, "0x") - // solhint-disable-next-line no-empty-blocks - {} catch { - // if sending failed, move the remaining bond to a custody contract - // the current PIC can withdraw it in a separate tx anytime later - // solhint-disable-next-line check-send-result, multiple-sends - token.send(address(custodian), currentPICBond, abi.encode(currentPICAddr)); - } + // transfer remaining bond to current PIC + token.transfer(currentPICAddr, currentPICBond); } // set new PIC diff --git a/packages/ethereum-contracts/contracts/utils/TokenCustodian.sol b/packages/ethereum-contracts/contracts/utils/TokenCustodian.sol deleted file mode 100644 index 04adaf58e0..0000000000 --- a/packages/ethereum-contracts/contracts/utils/TokenCustodian.sol +++ /dev/null @@ -1,55 +0,0 @@ -// SPDX-License-Identifier: AGPLv3 -pragma solidity 0.8.16; - -import { IERC777 } from "@openzeppelin/contracts/token/ERC777/IERC777.sol"; -import { IERC1820Registry } from "@openzeppelin/contracts/utils/introspection/IERC1820Registry.sol"; -import { IERC777Recipient } from "@openzeppelin/contracts/token/ERC777/IERC777Recipient.sol"; - -/** - * @title Token custodian contract - * @author Superfluid - * @dev Contract which takes custody of funds which couldn't be sent to the designated recipient - */ -contract TokenCustodian is IERC777Recipient { - IERC1820Registry constant internal _ERC1820_REGISTRY = - IERC1820Registry(0x1820a4B7618BdE71Dce8cdc73aAB6C95905faD24); - mapping(IERC777 => mapping(address => uint256)) public balances; - - constructor() { - _ERC1820_REGISTRY.setInterfaceImplementer(address(this), keccak256("ERC777TokensRecipient"), address(this)); - } - - event CustodianDeposit(IERC777 indexed token, address recipient, uint256 amount); - event CustodianWithdrawal(IERC777 indexed token, address recipient, uint256 amount); - - // transfers tokens it has in custody for the given recipient to it - function flush(IERC777 token, address recipient) public { - uint256 amount = balances[token][recipient]; - if (amount > 0) { - balances[token][recipient] = 0; - // solhint-disable-next-line check-send-result - token.send(recipient, amount, "0x0"); - emit CustodianWithdrawal(token, recipient, amount); - } - } - - // ============ IERC777Recipient ============ - - // interface through which ERC777 tokens are deposited to this contract. - // requires userData to contain an abi-encoded address which designates the recipient for which the deposit is made. - function tokensReceived( - address /*operator*/, - address /*from*/, - address /*to*/, - uint256 amount, - bytes calldata userData, - bytes calldata /*operatorData*/ - ) override external { - IERC777 token = IERC777(msg.sender); - // note: if userData is not set or not decodable as address, this will revert - address recipient = abi.decode(userData, (address)); - balances[token][recipient] += amount; - emit CustodianDeposit(token, recipient, amount); - // note: anybody could call this function and store balance entries. We can just ignore that, does no harm. - } -} diff --git a/packages/ethereum-contracts/package.json b/packages/ethereum-contracts/package.json index 36bd25a16d..9f7539afe0 100644 --- a/packages/ethereum-contracts/package.json +++ b/packages/ethereum-contracts/package.json @@ -1,6 +1,6 @@ { "name": "@superfluid-finance/ethereum-contracts", - "version": "1.4.2", + "version": "1.4.3", "description": " Ethereum contracts implementation for the Superfluid Protocol", "homepage": "https://github.com/superfluid-finance/protocol-monorepo/tree/dev/packages/ethereum-contracts#readme", "repository": { @@ -19,10 +19,14 @@ "!/build/contracts/*Properties.json", "/build/contracts-sizes.txt", "/build/abi.js", + "artifacts/contracts/**/*.json", + "!/**/*.dbg.json", "scripts/**/*", "utils/**/*", "/build/typechain/**/*" ], + "main": "./scripts/index.js", + "typings": "./types/index.d.ts", "scripts": { "dev": "tasks/dev.sh", "run-hardhat": "IS_HARDHAT=true hardhat", @@ -34,6 +38,7 @@ "build:contracts-truffle": "yarn run-truffle compile", "build:contracts-hardhat": "yarn run-hardhat compile", "build:typechain": "tsc -p tsconfig.typechain.json", + "build:scripts": "tsc -p tsconfig.scripts.json", "build:contracts-size": "tasks/print-contract-sizes.sh | tee build/contracts-sizes.txt", "testenv:start": "test/testenv-ctl.sh start", "testenv:stop": "test/testenv-ctl.sh stop", @@ -83,6 +88,6 @@ "solidity-coverage": "0.8.0", "solidity-docgen": "^0.6.0-beta.25", "truffle-flattener": "^1.6.0", - "truffle-plugin-verify": "0.5.28" + "truffle-plugin-verify": "0.5.31" } } diff --git a/packages/ethereum-contracts/scripts/deploy-aux-contracts.js b/packages/ethereum-contracts/scripts/deploy-aux-contracts.js index 577a407c32..f373e6918d 100644 --- a/packages/ethereum-contracts/scripts/deploy-aux-contracts.js +++ b/packages/ethereum-contracts/scripts/deploy-aux-contracts.js @@ -9,7 +9,6 @@ const SuperfluidGovernanceIIProxy = artifacts.require( "SuperfluidGovernanceIIProxy" ); const SuperfluidGovernanceII = artifacts.require("SuperfluidGovernanceII"); -const TokenCustodian = artifacts.require("TokenCustodian"); const TOGA = artifacts.require("TOGA"); const BatchLiquidator = artifacts.require("BatchLiquidator"); @@ -74,13 +73,10 @@ module.exports = eval(`(${S.toString()})()`)(async function ( ); console.log("deploying solvency related contracts"); - const tokenCustodian = await TokenCustodian.new(); - console.log("TokenCustodian deployed at: ", tokenCustodian.address); const minBondDuration = process.env.TOGA_MIN_BOND_DURATION || 604800; const toga = await TOGA.new( sf.host.address, - minBondDuration, - tokenCustodian.address + minBondDuration ); console.log("TOGA deployed at:", toga.address); diff --git a/packages/ethereum-contracts/scripts/deploy-framework.js b/packages/ethereum-contracts/scripts/deploy-framework.js index 4490d866c7..24c8996019 100644 --- a/packages/ethereum-contracts/scripts/deploy-framework.js +++ b/packages/ethereum-contracts/scripts/deploy-framework.js @@ -2,6 +2,7 @@ const fs = require("fs"); const util = require("util"); const getConfig = require("./libs/getConfig"); const SuperfluidSDK = require("@superfluid-finance/js-sdk"); +const ethers = require("ethers"); const {web3tx} = require("@decentral.ee/web3-helpers"); const deployERC1820 = require("../scripts/deploy-erc1820"); @@ -98,6 +99,8 @@ async function deployContractIfCodeChanged( * (overriding env: RELEASE_VERSION) * @param {boolean} options.outputFile Name of file where to log addresses of newly deployed contracts * (overriding env: OUTPUT_FILE) + * @param {boolean} options.cfaHookContract Address of the contract to be set up as CFA hooks receiver + * (overriding env: CFA_HOOK_CONTRACT) * * Usage: npx truffle exec scripts/deploy-framework.js */ @@ -114,6 +117,7 @@ module.exports = eval(`(${S.toString()})({skipArgv: true})`)(async function ( appWhiteListing, protocolReleaseVersion, outputFile, + cfaHookContract } = options; resetSuperfluidFramework = options.resetSuperfluidFramework; @@ -124,6 +128,9 @@ module.exports = eval(`(${S.toString()})({skipArgv: true})`)(async function ( outputFile = outputFile || process.env.OUTPUT_FILE; console.log("output file: ", outputFile); + cfaHookContract = cfaHookContract || process.env.CFA_HOOK_CONTRACT; + console.log("CFA hook contract", cfaHookContract); + // string to build a list of newly deployed contracts, written to a file if "outputFile" option set let output = ""; @@ -358,7 +365,7 @@ module.exports = eval(`(${S.toString()})({skipArgv: true})`)(async function ( const deployCFAv1 = async () => { // @note Once we have the actual implementation for the hook contract, // we will need to deploy it and put it here instead of ZERO_ADDRESS - const hookContractAddress = ZERO_ADDRESS; + const hookContractAddress = cfaHookContract || ZERO_ADDRESS; console.log("CFA Hook Contract Address:", hookContractAddress); const agreement = await web3tx( @@ -444,17 +451,24 @@ module.exports = eval(`(${S.toString()})({skipArgv: true})`)(async function ( } } - // deploy CFAv1Forwarder - await deployAndRegisterContractIf( - CFAv1Forwarder, - "CFAv1Forwarder", - async (contractAddress) => contractAddress === ZERO_ADDRESS, - async () => { - const forwarder = await CFAv1Forwarder.new(superfluid.address); - output += `CFA_V1_FORWARDER=${forwarder.address}\n`; - return forwarder; - } - ); + // deploy CFAv1Forwarder for test deployments + // for other (permanent) deployments, it's not handled by this script + if (protocolReleaseVersion === "test") { + await deployAndRegisterContractIf( + CFAv1Forwarder, + "CFAv1Forwarder", + async (contractAddress) => contractAddress === ZERO_ADDRESS, + async () => { + const forwarder = await CFAv1Forwarder.new(superfluid.address); + output += `CFA_V1_FORWARDER=${forwarder.address}\n`; + await web3tx( + governance.enableTrustedForwarder, + "Governance set CFAv1Forwarder" + )(superfluid.address, ZERO_ADDRESS, forwarder.address); + return forwarder; + } + ); + } let superfluidNewLogicAddress = ZERO_ADDRESS; const agreementsToUpdate = []; diff --git a/packages/ethereum-contracts/scripts/deploy-test-framework.js b/packages/ethereum-contracts/scripts/deploy-test-framework.js new file mode 100644 index 0000000000..72e8e5ea66 --- /dev/null +++ b/packages/ethereum-contracts/scripts/deploy-test-framework.js @@ -0,0 +1,78 @@ +const {ethers} = require("hardhat"); + +const SlotsBitmapLibraryArtifact = require("@superfluid-finance/ethereum-contracts/artifacts/contracts/libs/SlotsBitmapLibrary.sol/SlotsBitmapLibrary.json"); +const SuperfluidFrameworkDeployerArtifact = require("@superfluid-finance/ethereum-contracts/artifacts/contracts/utils/SuperfluidFrameworkDeployer.sol/SuperfluidFrameworkDeployer.json"); + +const ERC1820Registry = require("./artifacts/ERC1820Registry.json"); + +const ERC1820_ADDRESS = "0x1820a4b7618bde71dce8cdc73aab6c95905fad24"; +const ERC1820_BIN = ERC1820Registry.bin; +const ERC1820_DEPLOYER = "0xa990077c3205cbDf861e17Fa532eeB069cE9fF96"; +const ERC1820_PAYLOAD = + "0xf90a388085174876e800830c35008080b909e5" + + ERC1820_BIN + + "1ba01820182018201820182018201820182018201820182018201820182018201820a01820182018201820182018201820182018201820182018201820182018201820"; + +async function deployERC1820(provider) { + console.log("Deploying ERC1820..."); + const code = await provider.send("eth_getCode", [ + ERC1820_ADDRESS, + "latest", + ]); + if (code === "0x") { + const [from] = await provider.send("eth_accounts", []); + + await provider.send("eth_sendTransaction", [ + { + from, + to: ERC1820_DEPLOYER, + value: "0x11c37937e080000", + }, + ]); + await provider.send("eth_sendRawTransaction", [ERC1820_PAYLOAD]); + + console.log("ERC1820 registry successfully deployed"); + } +} + +const _getFactoryAndReturnDeployedContract = async ( + contractName, + artifact, + signerOrOptions, + ...args +) => { + console.log(`Deploying ${contractName}...`); + const ContractFactory = await ethers.getContractFactoryFromArtifact( + artifact, + signerOrOptions + ); + const contract = await ContractFactory.deploy(...args); + await contract.deployed(); + console.log(`${contractName} Deployed At:`, contract.address); + return contract; +}; + +const deployTestFramework = async () => { + const signer = (await ethers.getSigners())[0]; + await deployERC1820(ethers.provider); + const SlotsBitmapLibrary = await _getFactoryAndReturnDeployedContract( + "SlotsBitmapLibrary", + SlotsBitmapLibraryArtifact, + signer + ); + const frameworkDeployer = await _getFactoryAndReturnDeployedContract( + "SuperfluidFrameworkDeployer", + SuperfluidFrameworkDeployerArtifact, + { + signer, + libraries: { + SlotsBitmapLibrary: SlotsBitmapLibrary.address, + }, + } + ); + return frameworkDeployer; +}; + +module.exports = { + deployTestFramework, +}; diff --git a/packages/ethereum-contracts/scripts/deploy-test-framework.ts b/packages/ethereum-contracts/scripts/deploy-test-framework.ts deleted file mode 100644 index d302a54f63..0000000000 --- a/packages/ethereum-contracts/scripts/deploy-test-framework.ts +++ /dev/null @@ -1,84 +0,0 @@ -import {Contract, ethers} from "ethers"; -import {ethers as hardhatEthers} from "hardhat"; -import {Artifact, FactoryOptions} from "hardhat/types"; - -import SlotsBitmapLibraryArtifact from "../artifacts/contracts/libs/SlotsBitmapLibrary.sol/SlotsBitmapLibrary.json"; -import SuperfluidFrameworkDeployerArtifact from "../artifacts/contracts/utils/SuperfluidFrameworkDeployer.sol/SuperfluidFrameworkDeployer.json"; -import { - SlotsBitmapLibrary, - SuperfluidFrameworkDeployer, -} from "../typechain-types"; -import ERC1820Registry from "./artifacts/ERC1820Registry.json"; - -const ERC1820_ADDRESS = "0x1820a4b7618bde71dce8cdc73aab6c95905fad24"; -const ERC1820_DEPLOYER = "0xa990077c3205cbDf861e17Fa532eeB069cE9fF96"; -const ERC1820_BIN = ERC1820Registry.bin; -const ERC1820_PAYLOAD = - "0xf90a388085174876e800830c35008080b909e5" + - ERC1820_BIN + - "1ba01820182018201820182018201820182018201820182018201820182018201820a01820182018201820182018201820182018201820182018201820182018201820"; - -export async function deployERC1820( - provider: ethers.providers.JsonRpcProvider -): Promise { - console.log("Deploying ERC1820..."); - const code = await provider.send("eth_getCode", [ - ERC1820_ADDRESS, - "latest", - ]); - if (code === "0x") { - const [from] = await provider.send("eth_accounts", []); - - const tx = await provider.send("eth_sendTransaction", [ - { - from, - to: ERC1820_DEPLOYER, - value: "0x11c37937e080000", - }, - ]); - - await provider.send("eth_sendRawTransaction", [ERC1820_PAYLOAD]); - - console.log("ERC1820 registry successfully deployed"); - } -} - -const _getFactoryAndReturnDeployedContract = async ( - contractName: string, - artifact: Artifact, - signerOrOptions?: ethers.Signer | FactoryOptions | undefined, - ...args: any[] -) => { - console.log(`Deploying ${contractName}...`); - const ContractFactory = await hardhatEthers.getContractFactoryFromArtifact( - artifact, - signerOrOptions - ); - const contract = (await ContractFactory.deploy(...args)) as T; - await contract.deployed(); - console.log(`${contractName} Deployed At:`, contract.address); - return contract; -}; - -export const deployTestFramework = async () => { - const signer = (await hardhatEthers.getSigners())[0]; - await deployERC1820(hardhatEthers.provider); - const SlotsBitmapLibrary = - await _getFactoryAndReturnDeployedContract( - "SlotsBitmapLibrary", - SlotsBitmapLibraryArtifact, - signer - ); - const frameworkDeployer = - await _getFactoryAndReturnDeployedContract( - "SuperfluidFrameworkDeployer", - SuperfluidFrameworkDeployerArtifact, - { - signer, - libraries: { - SlotsBitmapLibrary: SlotsBitmapLibrary.address, - }, - } - ); - return frameworkDeployer; -}; diff --git a/packages/ethereum-contracts/scripts/gov-set-trusted-forwarder.js b/packages/ethereum-contracts/scripts/gov-set-trusted-forwarder.js index 9424bad1e7..1de316b2a7 100644 --- a/packages/ethereum-contracts/scripts/gov-set-trusted-forwarder.js +++ b/packages/ethereum-contracts/scripts/gov-set-trusted-forwarder.js @@ -18,6 +18,9 @@ const { * use TOKEN ADDRESS 0x0000000000000000000000000000000000000000 to set for all tokens. * If ENABLE is 1, the forwarder is enabled; if ENABLE is 0, the forwarder is disabled. * + * ENV vars: + * GOVERNANCE_ADMIN_TYPE needs to be set to MULTISIG for networks with multisig owned governance + * * Make sure to only set forwarders which can be fully trusted! */ module.exports = eval(`(${S.toString()})()`)(async function ( diff --git a/packages/ethereum-contracts/scripts/index.js b/packages/ethereum-contracts/scripts/index.js new file mode 100644 index 0000000000..d42d16acb9 --- /dev/null +++ b/packages/ethereum-contracts/scripts/index.js @@ -0,0 +1,72 @@ +// Contract Deployment Scripts +const deployAuxContracts = require("./deploy-aux-contracts"); +const deployDeterministically = require("./deploy-deterministically"); +const deployFramework = require("./deploy-framework"); + +// Contract Deployment Scripts (Testing Only) +const deployErc1820 = require("./deploy-erc1820"); +const deployTestEnvironment = require("./deploy-test-environment"); +const deployTestFramework = require("./deploy-test-framework"); + +// SuperToken Deployment Scripts (Testing Only) +const deploySuperToken = require("./deploy-super-token"); +const deployTestToken = require("./deploy-test-token"); +const deployUnlistedPureSuperToken = require("./deploy-unlisted-pure-super-token"); +const deployUnlistedSuperToken = require("./deploy-unlisted-super-token"); + +// Governance Actions Scripts +const govCreateNewAppRegistrationKey = require("./gov-create-new-app-registration-key"); +const govCreateNewFactoryRegistration = require("./gov-create-new-factory-registration"); +const govSet3PsConfig = require("./gov-set-3Ps-config"); +const govSetRewardAddress = require("./gov-set-reward-address"); +const govSetTokenMinDeposit = require("./gov-set-token-min-deposit"); +const govSetTrustedForwarder = require("./gov-set-trusted-forwarder"); +const govTransferFrameworkOwnership = require("./gov-transfer-framework-ownership"); +const govUpgradeGovernance = require("./gov-upgrade-governance"); +const govUpgradeSuperTokenLogic = require("./gov-upgrade-super-token-logic"); + +// Info Scripts +const infoInspectAccount = require("./info-inspect-account"); +const infoListApps = require("./info-list-apps"); +const infoPrintContractAddresses = require("./info-print-contract-addresses"); +const infoScanDeployments = require("./info-scan-deployments"); +const infoShowProtocol = require("./info-show-protocol"); + +// Resolver Actions Scripts +const resolverListSuperToken = require("./resolver-list-super-token"); +const resolverRegisterToken = require("./resolver-register-token"); +const resolverResetDeployment = require("./resolver-reset-deployment"); +const resolverSetKeyValue = require("./resolver-set-key-value"); +const resolverUnlistSuperToken = require("./resolver-unlist-super-token"); + +module.exports = { + deployAuxContracts, + deployDeterministically, + deployErc1820, + deployFramework, + deploySuperToken, + deployTestEnvironment, + deployTestFramework, + deployTestToken, + deployUnlistedPureSuperToken, + deployUnlistedSuperToken, + govCreateNewAppRegistrationKey, + govCreateNewFactoryRegistration, + govSet3PsConfig, + govSetRewardAddress, + govSetTokenMinDeposit, + govSetTrustedForwarder, + govTransferFrameworkOwnership, + govUpgradeGovernance, + govUpgradeSuperTokenLogic, + infoInspectAccount, + infoListApps, + infoPrintContractAddresses, + infoScanDeployments, + infoShowProtocol, + resolverListSuperToken, + resolverRegisterToken, + resolverResetDeployment, + resolverSetKeyValue, + resolverUnlistSuperToken, +}; diff --git a/packages/ethereum-contracts/scripts/resolver-set-key-value.js b/packages/ethereum-contracts/scripts/resolver-set-key-value.js new file mode 100644 index 0000000000..76b01cbd8b --- /dev/null +++ b/packages/ethereum-contracts/scripts/resolver-set-key-value.js @@ -0,0 +1,69 @@ +const SuperfluidSDK = require("@superfluid-finance/js-sdk"); +const { + getScriptRunnerFactory: S, + ZERO_ADDRESS, + extractWeb3Options, + builtTruffleContractLoader, + setResolver, +} = require("./libs/common"); + +/** + * @dev Set a given value for a given key in resolver + * @param {Array} argv Overriding command line arguments + * @param {boolean} options.isTruffle Whether the script is used within native truffle framework + * @param {Web3} options.web3 Injected web3 instance + * @param {Address} options.from Address to deploy contracts from + * @param {boolean} options.protocolReleaseVersion Specify the protocol release version to be used + * + * Usage: npx truffle exec scripts/resolver-set-key-value.js : {KEY} {VALUE} + * + * ENV vars: + * ALLOW_UPDATE: only if set will existing values be overwritten + * RESOLVER_ADMIN_TYPE: needs to be set to MULTISIG for networks with multisig owner + */ +module.exports = eval(`(${S.toString()})()`)(async function ( + args, + options = {} +) { + console.log("======== Set key with value ========"); + let {protocolReleaseVersion} = options; + + if (args.length !== 2) { + throw new Error("Wrong number of arguments"); + } + const value = args.pop(); + const key = args.pop(); + + console.log("Key", key); + console.log("Value", value); + + console.log("protocol release version:", protocolReleaseVersion); + + const sf = new SuperfluidSDK.Framework({ + ...extractWeb3Options(options), + version: protocolReleaseVersion, + additionalContracts: [ + "Ownable", + "IMultiSigWallet", + "SuperfluidGovernanceBase", + "Resolver", + "IAccessControlEnumerable", + ], + contractLoader: builtTruffleContractLoader, + }); + await sf.initialize(); + + const resolver = await sf.contracts.Resolver.at(sf.resolver.address); + + const prevVal = await resolver.get.call(key); + if (prevVal !== ZERO_ADDRESS) { + console.log("Key already has a value", prevVal); + if (!process.env.ALLOW_UPDATE) { + throw new Error( + "ALLOW_UPDATE not set, can't override existing value" + ); + } + } + + await setResolver(sf, key, value); +}); diff --git a/packages/ethereum-contracts/tasks/deploy-cfa-forwarder.sh b/packages/ethereum-contracts/tasks/deploy-cfa-forwarder.sh new file mode 100755 index 0000000000..86fcf995fd --- /dev/null +++ b/packages/ethereum-contracts/tasks/deploy-cfa-forwarder.sh @@ -0,0 +1,41 @@ +#!/bin/bash -eux + +# Usage: +# tasks/deploy-cfa-forwarder.sh +# +# Example: +# tasks/deploy-cfa-forwarder.sh optimism-goerli 0xcfa132e353cb4e398080b9700609bb008eceb125 +# +# The invoking account needs to be (co-)owner of the resolver and governance +# +# important ENV vars: +# RELEASE_VERSION, DETERMINISTIC_DEPLOYER_PK, RESOLVER_ADMIN_TYPE, GOVERNANCE_ADMIN_TYPE +# +# You can use the npm package vanity-eth to get a deployer account for a given contract address: +# Example use: npx vanityeth -i cfa1 --contract +# +# Note that the value of DETERMINISTIC_DEPLOYER_PK needs to match the given contract-addr. +# The script will not check this, but fail (at contract verification) if not matching. +# +# For optimism the gas estimation doesn't work, requires setting EST_TX_COST. +# For polygon-mainnet, GAS_PRICE usually needs to be set. +# +# On some networks you may need to use override ENV vars for the deployment to succeed + +network=$1 +cfaAddr=$2 + +# deploy +npx truffle exec --network $network scripts/deploy-deterministically.js : CFAv1Forwarder + +# verify (give it a few seconds to pick up the code) +sleep 5 +npx truffle run --network $network verify CFAv1Forwarder@$cfaAddr + +# set resolver +ALLOW_UPDATE=1 npx truffle exec --network $network scripts/resolver-set-key-value.js : CFAv1Forwarder $cfaAddr + +# create gov action +npx truffle exec --network $network scripts/gov-set-trusted-forwarder.js : 0x0000000000000000000000000000000000000000 $cfaAddr 1 + +# TODO: on mainnets, the resolver entry should be set only after the gov action was signed & executed \ No newline at end of file diff --git a/packages/ethereum-contracts/tasks/etherscan-verify-framework.sh b/packages/ethereum-contracts/tasks/etherscan-verify-framework.sh index fe6deea9cf..7ce4c70e02 100755 --- a/packages/ethereum-contracts/tasks/etherscan-verify-framework.sh +++ b/packages/ethereum-contracts/tasks/etherscan-verify-framework.sh @@ -24,6 +24,7 @@ case $TRUFFLE_NETWORK in echo "$TRUFFLE_NETWORK is testnet" IS_TESTNET=1 ;; + eth-mainnet | \ polygon-mainnet | \ optimism-mainnet | \ arbitrum-one | \ @@ -64,10 +65,10 @@ if [ ! -z "$SUPERFLUID_HOST_PROXY" ]; then fi echo SUPERFLUID_GOVERNANCE -if [ ! -z "$IS_TESTNET" ];then - try_verify TestGovernance@${SUPERFLUID_GOVERNANCE} -else - if [ ! -z "$SUPERFLUID_GOVERNANCE" ]; then +if [ ! -z "$SUPERFLUID_GOVERNANCE" ]; then + if [ ! -z "$IS_TESTNET" ];then + try_verify TestGovernance@${SUPERFLUID_GOVERNANCE} + else try_verify SuperfluidGovernanceII@${SUPERFLUID_GOVERNANCE} --custom-proxy SuperfluidGovernanceIIProxy fi fi diff --git a/packages/ethereum-contracts/test/contracts/agreements/ConstantFlowAgreementV1-CFAHook.test.ts b/packages/ethereum-contracts/test/contracts/agreements/ConstantFlowAgreementV1-CFAHook.test.ts index 21e697c40b..6915271537 100644 --- a/packages/ethereum-contracts/test/contracts/agreements/ConstantFlowAgreementV1-CFAHook.test.ts +++ b/packages/ethereum-contracts/test/contracts/agreements/ConstantFlowAgreementV1-CFAHook.test.ts @@ -43,7 +43,6 @@ describe("CFAv1 | CFA Hook Mock Tests", function () { describe("#1 GoodCFAHookMock Tests", () => { before(async () => { - process.env.USE_GOOD_HOOK = "true"; await t.beforeTestSuite({ isTruffle: true, nAccounts: 5, @@ -211,7 +210,6 @@ describe("CFAv1 | CFA Hook Mock Tests", function () { describe("#2 BadCFAHookMock Tests", () => { before(async () => { - process.env.USE_GOOD_HOOK = ""; await t.beforeTestSuite({ isTruffle: true, nAccounts: 5, diff --git a/packages/ethereum-contracts/test/contracts/agreements/ConstantFlowAgreementV1-Callback.test.ts b/packages/ethereum-contracts/test/contracts/agreements/ConstantFlowAgreementV1-Callback.test.ts index dc818fed6d..aa4fea75ec 100644 --- a/packages/ethereum-contracts/test/contracts/agreements/ConstantFlowAgreementV1-Callback.test.ts +++ b/packages/ethereum-contracts/test/contracts/agreements/ConstantFlowAgreementV1-Callback.test.ts @@ -347,8 +347,7 @@ describe("CFAv1 | Callback Tests", function () { flowRate: FLOW_RATE1, }), cfa, - "INSUFFICIENT_BALANCE", - t.customErrorCode.CFA_INSUFFICIENT_BALANCE + "CFA_INSUFFICIENT_BALANCE" ); // fund the app with diff --git a/packages/ethereum-contracts/test/contracts/agreements/ConstantFlowAgreementV1-MFA.test.ts b/packages/ethereum-contracts/test/contracts/agreements/ConstantFlowAgreementV1-MFA.test.ts index e5541901fd..c690cb5f66 100644 --- a/packages/ethereum-contracts/test/contracts/agreements/ConstantFlowAgreementV1-MFA.test.ts +++ b/packages/ethereum-contracts/test/contracts/agreements/ConstantFlowAgreementV1-MFA.test.ts @@ -867,8 +867,7 @@ describe("CFAv1 | Multi Flow Super App Tests", function () { userData, }), cfa, - "INSUFFICIENT_BALANCE", - t.customErrorCode.CFA_INSUFFICIENT_BALANCE + "CFA_INSUFFICIENT_BALANCE" ); await timeTravelOnceAndVerifyAll(); @@ -1550,8 +1549,7 @@ describe("CFAv1 | Multi Flow Super App Tests", function () { signer: await ethers.getSigner(t.accounts[0]), }), cfa, - "INSUFFICIENT_BALANCE", - t.customErrorCode.CFA_INSUFFICIENT_BALANCE + "CFA_INSUFFICIENT_BALANCE" ); }); diff --git a/packages/ethereum-contracts/test/contracts/agreements/ConstantFlowAgreementV1-Non-Callback.test.ts b/packages/ethereum-contracts/test/contracts/agreements/ConstantFlowAgreementV1-Non-Callback.test.ts index 2102f5911e..5de35f321b 100644 --- a/packages/ethereum-contracts/test/contracts/agreements/ConstantFlowAgreementV1-Non-Callback.test.ts +++ b/packages/ethereum-contracts/test/contracts/agreements/ConstantFlowAgreementV1-Non-Callback.test.ts @@ -282,8 +282,7 @@ describe("CFAv1 | Non-Callback Tests", function () { flowRate: FLOW_RATE1, }), cfa, - "INSUFFICIENT_BALANCE", - t.customErrorCode.CFA_INSUFFICIENT_BALANCE + "CFA_INSUFFICIENT_BALANCE" ); }); @@ -348,8 +347,7 @@ describe("CFAv1 | Non-Callback Tests", function () { flowRate: FLOW_RATE1, }), cfa, - "ALREADY_EXISTS", - t.customErrorCode.CFA_FLOW_ALREADY_EXISTS + "CFA_FLOW_ALREADY_EXISTS" ); }); @@ -376,8 +374,7 @@ describe("CFAv1 | Non-Callback Tests", function () { flowRate: FLOW_RATE1, }), cfa, - "ZERO_ADDRESS", - t.customErrorCode.CFA_ZERO_ADDRESS_RECEIVER + "CFA_ZERO_ADDRESS_RECEIVER" ); }); }); @@ -469,8 +466,7 @@ describe("CFAv1 | Non-Callback Tests", function () { flowRate: FLOW_RATE1, }), cfa, - "DOES_NOT_EXIST", - t.customErrorCode.CFA_FLOW_DOES_NOT_EXIST + "CFA_FLOW_DOES_NOT_EXIST" ); }); @@ -500,8 +496,7 @@ describe("CFAv1 | Non-Callback Tests", function () { ), }), cfa, - "INSUFFICIENT_BALANCE", - t.customErrorCode.CFA_INSUFFICIENT_BALANCE + "CFA_INSUFFICIENT_BALANCE" ); }); @@ -528,8 +523,7 @@ describe("CFAv1 | Non-Callback Tests", function () { flowRate: FLOW_RATE1, }), cfa, - "ZERO_ADDRESS", - t.customErrorCode.CFA_ZERO_ADDRESS_RECEIVER + "CFA_ZERO_ADDRESS_RECEIVER" ); }); }); @@ -601,8 +595,7 @@ describe("CFAv1 | Non-Callback Tests", function () { receiver: t.aliases[agent], }), cfa, - "DOES_NOT_EXIST", - t.customErrorCode.CFA_FLOW_DOES_NOT_EXIST + "CFA_FLOW_DOES_NOT_EXIST" ); }); @@ -615,8 +608,7 @@ describe("CFAv1 | Non-Callback Tests", function () { receiver: ZERO_ADDRESS, }), cfa, - "ZERO_ADDRESS", - t.customErrorCode.CFA_ZERO_ADDRESS_RECEIVER + "CFA_ZERO_ADDRESS_RECEIVER" ); }); @@ -630,8 +622,7 @@ describe("CFAv1 | Non-Callback Tests", function () { signer: await ethers.getSigner(t.aliases[sender]), }), cfa, - "ZERO_ADDRESS", - t.customErrorCode.CFA_ZERO_ADDRESS_SENDER + "CFA_ZERO_ADDRESS_SENDER" ); }); }); @@ -678,8 +669,7 @@ describe("CFAv1 | Non-Callback Tests", function () { signer, }), cfa, - "ZERO_ADDRESS", - t.customErrorCode.CFA_ZERO_ADDRESS_SENDER + "CFA_ZERO_ADDRESS_SENDER" ); }); @@ -2528,8 +2518,7 @@ describe("CFAv1 | Non-Callback Tests", function () { receiver: t.aliases[receiver], }), cfa, - "INSUFFICIENT_BALANCE", - t.customErrorCode.CFA_INSUFFICIENT_BALANCE + "CFA_INSUFFICIENT_BALANCE" ); }); }); @@ -3772,8 +3761,7 @@ describe("CFAv1 | Non-Callback Tests", function () { "0x" ), cfa, - "INSUFFICIENT_BALANCE", - t.customErrorCode.CFA_INSUFFICIENT_BALANCE + "CFA_INSUFFICIENT_BALANCE" ); }); }); diff --git a/packages/ethereum-contracts/test/contracts/agreements/InstantDistributionAgreementV1-Callback.test.ts b/packages/ethereum-contracts/test/contracts/agreements/InstantDistributionAgreementV1-Callback.test.ts index 8533edd605..19b674e873 100644 --- a/packages/ethereum-contracts/test/contracts/agreements/InstantDistributionAgreementV1-Callback.test.ts +++ b/packages/ethereum-contracts/test/contracts/agreements/InstantDistributionAgreementV1-Callback.test.ts @@ -423,8 +423,7 @@ describe("IDAv1 | Callback Tests", function () { signer: aliceSigner, }), ida, - "DOES_NOT_EXIST", - t.customErrorCode.IDA_SUBSCRIPTION_DOES_NOT_EXIST + "IDA_SUBSCRIPTION_DOES_NOT_EXIST" ); }); }); diff --git a/packages/ethereum-contracts/test/contracts/agreements/InstantDistributionAgreementV1-Non-Callback.test.ts b/packages/ethereum-contracts/test/contracts/agreements/InstantDistributionAgreementV1-Non-Callback.test.ts index cc5a8e5895..d2094c8af6 100644 --- a/packages/ethereum-contracts/test/contracts/agreements/InstantDistributionAgreementV1-Non-Callback.test.ts +++ b/packages/ethereum-contracts/test/contracts/agreements/InstantDistributionAgreementV1-Non-Callback.test.ts @@ -108,8 +108,7 @@ describe("IDAv1 | Non-Callback Tests", function () { signer: aliceSigner, }), ida, - "ALREADY_EXISTS", - t.customErrorCode.IDA_INDEX_ALREADY_EXISTS + "IDA_INDEX_ALREADY_EXISTS" ); }); @@ -166,8 +165,7 @@ describe("IDAv1 | Non-Callback Tests", function () { signer: aliceSigner, }), ida, - "DOES_NOT_EXIST", - t.customErrorCode.IDA_INDEX_DOES_NOT_EXIST + "IDA_INDEX_DOES_NOT_EXIST" ); await expectCustomError( t.agreementHelper.callAgreement({ @@ -179,8 +177,7 @@ describe("IDAv1 | Non-Callback Tests", function () { signer: aliceSigner, }), ida, - "DOES_NOT_EXIST", - t.customErrorCode.IDA_INDEX_DOES_NOT_EXIST + "IDA_INDEX_DOES_NOT_EXIST" ); await expectCustomError( ida.calculateDistribution( @@ -190,8 +187,7 @@ describe("IDAv1 | Non-Callback Tests", function () { "42" ), ida, - "DOES_NOT_EXIST", - t.customErrorCode.IDA_INDEX_DOES_NOT_EXIST + "IDA_INDEX_DOES_NOT_EXIST" ); }); @@ -328,8 +324,7 @@ describe("IDAv1 | Non-Callback Tests", function () { signer: aliceSigner, }), ida, - "INSUFFICIENT_BALANCE", - t.customErrorCode.IDA_INSUFFICIENT_BALANCE + "IDA_INSUFFICIENT_BALANCE" ); }); @@ -356,8 +351,7 @@ describe("IDAv1 | Non-Callback Tests", function () { signer: aliceSigner, }), ida, - "ZERO_ADDRESS", - t.customErrorCode.IDA_ZERO_ADDRESS_SUBSCRIBER + "IDA_ZERO_ADDRESS_SUBSCRIBER" ); }); @@ -385,8 +379,7 @@ describe("IDAv1 | Non-Callback Tests", function () { signer: aliceSigner, }), ida, - "ZERO_ADDRESS", - t.customErrorCode.IDA_ZERO_ADDRESS_SUBSCRIBER + "IDA_ZERO_ADDRESS_SUBSCRIBER" ); }); @@ -451,8 +444,7 @@ describe("IDAv1 | Non-Callback Tests", function () { signer: bobSigner, }), ida, - "ALREADY_EXISTS", - t.customErrorCode.IDA_SUBSCRIPTION_ALREADY_APPROVED + "IDA_SUBSCRIPTION_ALREADY_APPROVED" ); }); @@ -580,8 +572,7 @@ describe("IDAv1 | Non-Callback Tests", function () { signer: aliceSigner, }), ida, - "DOES_NOT_EXIST", - t.customErrorCode.IDA_SUBSCRIPTION_DOES_NOT_EXIST + "IDA_SUBSCRIPTION_DOES_NOT_EXIST" ); }); @@ -772,8 +763,7 @@ describe("IDAv1 | Non-Callback Tests", function () { signer: bobSigner, }), ida, - "DOES_NOT_EXIST", - t.customErrorCode.IDA_INDEX_DOES_NOT_EXIST + "IDA_INDEX_DOES_NOT_EXIST" ); await expectCustomError( t.agreementHelper.callAgreement({ @@ -791,8 +781,7 @@ describe("IDAv1 | Non-Callback Tests", function () { signer: bobSigner, }), ida, - "DOES_NOT_EXIST", - t.customErrorCode.IDA_INDEX_DOES_NOT_EXIST + "IDA_INDEX_DOES_NOT_EXIST" ); await expectCustomError( ida.getSubscription( @@ -802,8 +791,7 @@ describe("IDAv1 | Non-Callback Tests", function () { bob ), ida, - "DOES_NOT_EXIST", - t.customErrorCode.IDA_INDEX_DOES_NOT_EXIST + "IDA_INDEX_DOES_NOT_EXIST" ); }); @@ -905,8 +893,7 @@ describe("IDAv1 | Non-Callback Tests", function () { signer: bobSigner, }), ida, - "DOES_NOT_EXIST", - t.customErrorCode.IDA_SUBSCRIPTION_IS_NOT_APPROVED + "IDA_SUBSCRIPTION_IS_NOT_APPROVED" ); }); @@ -927,8 +914,7 @@ describe("IDAv1 | Non-Callback Tests", function () { signer: bobSigner, }), ida, - "DOES_NOT_EXIST", - t.customErrorCode.IDA_SUBSCRIPTION_DOES_NOT_EXIST + "IDA_SUBSCRIPTION_DOES_NOT_EXIST" ); }); @@ -943,8 +929,7 @@ describe("IDAv1 | Non-Callback Tests", function () { signer: bobSigner, }), ida, - "DOES_NOT_EXIST", - t.customErrorCode.IDA_INDEX_DOES_NOT_EXIST + "IDA_INDEX_DOES_NOT_EXIST" ); }); @@ -1494,8 +1479,7 @@ describe("IDAv1 | Non-Callback Tests", function () { signer: bobSigner, }), ida, - "DOES_NOT_EXIST", - t.customErrorCode.IDA_SUBSCRIPTION_DOES_NOT_EXIST + "IDA_SUBSCRIPTION_DOES_NOT_EXIST" ); }); @@ -1513,8 +1497,7 @@ describe("IDAv1 | Non-Callback Tests", function () { signer: bobSigner, }), ida, - "DOES_NOT_EXIST", - t.customErrorCode.IDA_INDEX_DOES_NOT_EXIST + "IDA_INDEX_DOES_NOT_EXIST" ); }); @@ -1547,8 +1530,7 @@ describe("IDAv1 | Non-Callback Tests", function () { signer: bobSigner, }), ida, - "ALREADY_EXISTS", - t.customErrorCode.IDA_SUBSCRIPTION_ALREADY_APPROVED + "IDA_SUBSCRIPTION_ALREADY_APPROVED" ); }); @@ -1573,8 +1555,7 @@ describe("IDAv1 | Non-Callback Tests", function () { signer: bobSigner, }), ida, - "ZERO_ADDRESS", - t.customErrorCode.IDA_ZERO_ADDRESS_SUBSCRIBER + "IDA_ZERO_ADDRESS_SUBSCRIBER" ); }); }); diff --git a/packages/ethereum-contracts/test/contracts/gov/SuperfluidGovernanceII.test.ts b/packages/ethereum-contracts/test/contracts/gov/SuperfluidGovernanceII.test.ts index 0516fc9000..0a612145d9 100644 --- a/packages/ethereum-contracts/test/contracts/gov/SuperfluidGovernanceII.test.ts +++ b/packages/ethereum-contracts/test/contracts/gov/SuperfluidGovernanceII.test.ts @@ -600,8 +600,7 @@ describe("Superfluid Ownable Governance Contract", function () { .connect(aliceSigner) .authorizeAppFactory(superfluid.address, FAKE_ADDRESS1), governance, - "MUST_BE_CONTRACT", - t.customErrorCode.SF_GOV_MUST_BE_CONTRACT + "SF_GOV_MUST_BE_CONTRACT" ); assert.isFalse( diff --git a/packages/ethereum-contracts/test/contracts/scenarios/scenarios.test.ts b/packages/ethereum-contracts/test/contracts/scenarios/scenarios.test.ts index 2fad30d592..fac1c9ce9f 100644 --- a/packages/ethereum-contracts/test/contracts/scenarios/scenarios.test.ts +++ b/packages/ethereum-contracts/test/contracts/scenarios/scenarios.test.ts @@ -615,8 +615,7 @@ describe("Superfluid scenarios", function () { signer: await ethers.getSigner(alice), }), t.contracts.ida, - "INSUFFICIENT_BALANCE", - t.customErrorCode.IDA_INSUFFICIENT_BALANCE + "IDA_INSUFFICIENT_BALANCE" ); }); @@ -755,8 +754,7 @@ describe("Superfluid scenarios", function () { signer: await ethers.getSigner(alice), }), t.contracts.ida, - "INSUFFICIENT_BALANCE", - t.customErrorCode.IDA_INSUFFICIENT_BALANCE + "IDA_INSUFFICIENT_BALANCE" ); await agreementHelper.modifyFlow({ diff --git a/packages/ethereum-contracts/test/contracts/superfluid/ERC20.behavior.ts b/packages/ethereum-contracts/test/contracts/superfluid/ERC20.behavior.ts index 446142b054..3306f42b60 100644 --- a/packages/ethereum-contracts/test/contracts/superfluid/ERC20.behavior.ts +++ b/packages/ethereum-contracts/test/contracts/superfluid/ERC20.behavior.ts @@ -3,7 +3,6 @@ import {SignerWithAddress} from "@nomiclabs/hardhat-ethers/signers"; import {BigNumber, BigNumberish, ContractTransaction} from "ethers"; import {ethers, expect} from "hardhat"; -import TestEnvironment from "../../TestEnvironment"; import {expectCustomError, expectRevertedWith} from "../../utils/expectRevert"; import {toBN} from "../utils/helpers"; @@ -14,8 +13,7 @@ export function shouldBehaveLikeERC20( initialHolder: string; recipient: string; anotherAccount: string; - }, - t: TestEnvironment + } ) { let initialHolder: string, recipient: string, anotherAccount: string; @@ -65,8 +63,7 @@ export function shouldBehaveLikeERC20( ) { const signer = await ethers.getSigner(from); return this.token.connect(signer).transfer(to, value); - }, - t + } ); }); @@ -163,9 +160,7 @@ export function shouldBehaveLikeERC20( .connect(spenderSigner) .transferFrom(tokenOwner, to, amount), this.token, - "INSUFFICIENT_BALANCE", - t.customErrorCode - .SF_TOKEN_MOVE_INSUFFICIENT_BALANCE + "SF_TOKEN_MOVE_INSUFFICIENT_BALANCE" ); }); }); @@ -200,9 +195,7 @@ export function shouldBehaveLikeERC20( .connect(spenderSigner) .transferFrom(tokenOwner, to, amount), this.token, - "INSUFFICIENT_BALANCE", - t.customErrorCode - .SF_TOKEN_MOVE_INSUFFICIENT_BALANCE + "SF_TOKEN_MOVE_INSUFFICIENT_BALANCE" ); }); }); @@ -223,8 +216,7 @@ export function shouldBehaveLikeERC20( .connect(spenderSigner) .transferFrom(tokenOwner, to, amount), this.token, - "ZERO_ADDRESS", - t.customErrorCode.SUPER_TOKEN_TRANSFER_TO_ZERO_ADDRESS + "SUPER_TOKEN_TRANSFER_TO_ZERO_ADDRESS" ); }); }); @@ -245,8 +237,7 @@ export function shouldBehaveLikeERC20( .connect(spenderSigner) .transferFrom(tokenOwner, to, amount), this.token, - "ZERO_ADDRESS", - t.customErrorCode.SUPER_TOKEN_TRANSFER_FROM_ZERO_ADDRESS + "SUPER_TOKEN_TRANSFER_FROM_ZERO_ADDRESS" ); }); }); @@ -268,8 +259,7 @@ export function shouldBehaveLikeERC20( ) { const signer = await ethers.getSigner(owner); return this.token.connect(signer).approve(spender, amount); - }, - t + } ); }); } @@ -283,8 +273,7 @@ export function shouldBehaveLikeERC20Transfer( from: string, to: string, amount: BigNumber - ) => Promise, - t: TestEnvironment + ) => Promise ) { let from: string, to: string; @@ -299,8 +288,7 @@ export function shouldBehaveLikeERC20Transfer( await expectCustomError( transfer.call(this, from, to, amount), this.token, - "INSUFFICIENT_BALANCE", - t.customErrorCode.SF_TOKEN_MOVE_INSUFFICIENT_BALANCE + "SF_TOKEN_MOVE_INSUFFICIENT_BALANCE" ); }); }); @@ -360,8 +348,7 @@ export function shouldBehaveLikeERC20Transfer( balance ), this.token, - "ZERO_ADDRESS", - t.customErrorCode.SUPER_TOKEN_TRANSFER_TO_ZERO_ADDRESS + "SUPER_TOKEN_TRANSFER_TO_ZERO_ADDRESS" ); }); }); @@ -376,8 +363,7 @@ export function shouldBehaveLikeERC20Approve( owner: string, spender: string, amount: BigNumber - ) => Promise, - t: TestEnvironment + ) => Promise ) { let owner: string, spender: string; @@ -460,8 +446,7 @@ export function shouldBehaveLikeERC20Approve( await expectCustomError( approve.call(this, owner, ethers.constants.AddressZero, supply), this.token, - "ZERO_ADDRESS", - t.customErrorCode.SUPER_TOKEN_APPROVE_TO_ZERO_ADDRESS + "SUPER_TOKEN_APPROVE_TO_ZERO_ADDRESS" ); }); }); diff --git a/packages/ethereum-contracts/test/contracts/superfluid/ERC777.behavior.ts b/packages/ethereum-contracts/test/contracts/superfluid/ERC777.behavior.ts index 8926f33f75..9bbfbcf942 100644 --- a/packages/ethereum-contracts/test/contracts/superfluid/ERC777.behavior.ts +++ b/packages/ethereum-contracts/test/contracts/superfluid/ERC777.behavior.ts @@ -100,9 +100,7 @@ function _shouldBehaveLikeERC777DirectSend( .connect(holderSigner) .send(recipient, balance.addn(1).toString(), data), tokenContract, - "INSUFFICIENT_BALANCE", - this.testenv.customErrorCode - .SF_TOKEN_MOVE_INSUFFICIENT_BALANCE + "SF_TOKEN_MOVE_INSUFFICIENT_BALANCE" ); }); @@ -112,9 +110,7 @@ function _shouldBehaveLikeERC777DirectSend( .connect(holderSigner) .send(ZERO_ADDRESS, new BN("1").toString(), data), tokenContract, - "ZERO_ADDRESS", - this.testenv.customErrorCode - .SUPER_TOKEN_TRANSFER_TO_ZERO_ADDRESS + "SUPER_TOKEN_TRANSFER_TO_ZERO_ADDRESS" ); }); }); @@ -139,9 +135,7 @@ function _shouldBehaveLikeERC777DirectSend( .connect(holderSigner) .send(recipient, new BN("1").toString(), data), tokenContract, - "INSUFFICIENT_BALANCE", - this.testenv.customErrorCode - .SF_TOKEN_MOVE_INSUFFICIENT_BALANCE + "SF_TOKEN_MOVE_INSUFFICIENT_BALANCE" ); }); }); @@ -202,9 +196,7 @@ function _shouldBehaveLikeERC777OperatorSend( operatorData ), tokenContract, - "INSUFFICIENT_BALANCE", - this.testenv.customErrorCode - .SF_TOKEN_MOVE_INSUFFICIENT_BALANCE + "SF_TOKEN_MOVE_INSUFFICIENT_BALANCE" ); }); @@ -221,9 +213,7 @@ function _shouldBehaveLikeERC777OperatorSend( operatorData ), tokenContract, - "ZERO_ADDRESS", - this.testenv.customErrorCode - .SUPER_TOKEN_TRANSFER_TO_ZERO_ADDRESS + "SUPER_TOKEN_TRANSFER_TO_ZERO_ADDRESS" ); }); }); @@ -257,9 +247,7 @@ function _shouldBehaveLikeERC777OperatorSend( operatorData ), tokenContract, - "INSUFFICIENT_BALANCE", - this.testenv.customErrorCode - .SF_TOKEN_MOVE_INSUFFICIENT_BALANCE + "SF_TOKEN_MOVE_INSUFFICIENT_BALANCE" ); }); @@ -278,12 +266,8 @@ function _shouldBehaveLikeERC777OperatorSend( ), tokenContract, isDefaultOperator - ? "ZERO_ADDRESS" - : "SUPER_TOKEN_CALLER_IS_NOT_OPERATOR_FOR_HOLDER", - isDefaultOperator - ? this.testenv.customErrorCode - .SUPER_TOKEN_TRANSFER_FROM_ZERO_ADDRESS - : undefined + ? "SUPER_TOKEN_TRANSFER_FROM_ZERO_ADDRESS" + : "SUPER_TOKEN_CALLER_IS_NOT_OPERATOR_FOR_HOLDER" ); }); }); @@ -365,9 +349,7 @@ function _shouldBehaveLikeERC777DirectBurn( .connect(holderSigner) .burn(balance.addn(1).toString(), data), tokenContract, - "INSUFFICIENT_BALANCE", - this.testenv.customErrorCode - .SF_TOKEN_BURN_INSUFFICIENT_BALANCE + "SF_TOKEN_BURN_INSUFFICIENT_BALANCE" ); }); }); @@ -396,9 +378,7 @@ function _shouldBehaveLikeERC777DirectBurn( .connect(holderSigner) .burn(new BN("1").toString(), data), tokenContract, - "INSUFFICIENT_BALANCE", - this.testenv.customErrorCode - .SF_TOKEN_BURN_INSUFFICIENT_BALANCE + "SF_TOKEN_BURN_INSUFFICIENT_BALANCE" ); }); }); @@ -455,9 +435,7 @@ function _shouldBehaveLikeERC777OperatorBurn( operatorData ), tokenContract, - "INSUFFICIENT_BALANCE", - this.testenv.customErrorCode - .SF_TOKEN_BURN_INSUFFICIENT_BALANCE + "SF_TOKEN_BURN_INSUFFICIENT_BALANCE" ); }); }); @@ -493,9 +471,7 @@ function _shouldBehaveLikeERC777OperatorBurn( operatorData ), tokenContract, - "INSUFFICIENT_BALANCE", - this.testenv.customErrorCode - .SF_TOKEN_BURN_INSUFFICIENT_BALANCE + "SF_TOKEN_BURN_INSUFFICIENT_BALANCE" ); }); @@ -517,12 +493,8 @@ function _shouldBehaveLikeERC777OperatorBurn( ), tokenContract, isDefaultOperator - ? "ZERO_ADDRESS" - : "SUPER_TOKEN_CALLER_IS_NOT_OPERATOR_FOR_HOLDER", - isDefaultOperator - ? this.testenv.customErrorCode - .SUPER_TOKEN_BURN_FROM_ZERO_ADDRESS - : undefined + ? "SUPER_TOKEN_BURN_FROM_ZERO_ADDRESS" + : "SUPER_TOKEN_CALLER_IS_NOT_OPERATOR_FOR_HOLDER" ); }); }); diff --git a/packages/ethereum-contracts/test/contracts/superfluid/SuperToken.ERC20.test.ts b/packages/ethereum-contracts/test/contracts/superfluid/SuperToken.ERC20.test.ts index 38f8cbc877..c281c38c14 100644 --- a/packages/ethereum-contracts/test/contracts/superfluid/SuperToken.ERC20.test.ts +++ b/packages/ethereum-contracts/test/contracts/superfluid/SuperToken.ERC20.test.ts @@ -261,8 +261,7 @@ describe("SuperToken's ERC20 compliance", function () { .connect(aliceSigner) .increaseAllowance(spender, amount), this.token, - "ZERO_ADDRESS", - t.customErrorCode.SUPER_TOKEN_APPROVE_TO_ZERO_ADDRESS + "SUPER_TOKEN_APPROVE_TO_ZERO_ADDRESS" ); }); }); @@ -291,8 +290,7 @@ describe("SuperToken's ERC20 compliance", function () { initialSupply ), this.token, - "ZERO_ADDRESS", - t.customErrorCode.SUPER_TOKEN_TRANSFER_FROM_ZERO_ADDRESS + "SUPER_TOKEN_TRANSFER_FROM_ZERO_ADDRESS" ); }); }); @@ -326,8 +324,7 @@ describe("SuperToken's ERC20 compliance", function () { initialSupply ), this.token, - "ZERO_ADDRESS", - t.customErrorCode.SUPER_TOKEN_APPROVE_FROM_ZERO_ADDRESS + "SUPER_TOKEN_APPROVE_FROM_ZERO_ADDRESS" ); }); }); diff --git a/packages/ethereum-contracts/test/contracts/superfluid/SuperToken.ERC777.test.ts b/packages/ethereum-contracts/test/contracts/superfluid/SuperToken.ERC777.test.ts index ac61fc4c95..8bc78c86b8 100644 --- a/packages/ethereum-contracts/test/contracts/superfluid/SuperToken.ERC777.test.ts +++ b/packages/ethereum-contracts/test/contracts/superfluid/SuperToken.ERC777.test.ts @@ -541,9 +541,7 @@ describe("SuperToken's ERC777 implementation", function () { operatorData ), tokenContract, - "ZERO_ADDRESS", - t.customErrorCode - .SUPER_TOKEN_MINT_TO_ZERO_ADDRESS + "SUPER_TOKEN_MINT_TO_ZERO_ADDRESS" ); }); diff --git a/packages/ethereum-contracts/test/contracts/superfluid/SuperToken.NonStandard.test.ts b/packages/ethereum-contracts/test/contracts/superfluid/SuperToken.NonStandard.test.ts index 95add82a4b..33689b832c 100644 --- a/packages/ethereum-contracts/test/contracts/superfluid/SuperToken.NonStandard.test.ts +++ b/packages/ethereum-contracts/test/contracts/superfluid/SuperToken.NonStandard.test.ts @@ -79,8 +79,7 @@ describe("SuperToken's Non Standard Functions", function () { await expectCustomError( superToken.updateCode(ZERO_ADDRESS), superToken, - "ONLY_HOST", - t.customErrorCode.SUPER_TOKEN_ONLY_HOST + "SUPER_TOKEN_ONLY_HOST" ); }); @@ -208,8 +207,7 @@ describe("SuperToken's Non Standard Functions", function () { await expectCustomError( superToken.connect(aliceSigner).downgrade(toBN(1)), superToken, - "INSUFFICIENT_BALANCE", - t.customErrorCode.SF_TOKEN_BURN_INSUFFICIENT_BALANCE + "SF_TOKEN_BURN_INSUFFICIENT_BALANCE" ); }); @@ -621,8 +619,7 @@ describe("SuperToken's Non Standard Functions", function () { await expectCustomError( customToken.callSelfBurn(alice, 101, "0x"), superTokenContract, - "INSUFFICIENT_BALANCE", - t.customErrorCode.SF_TOKEN_BURN_INSUFFICIENT_BALANCE + "SF_TOKEN_BURN_INSUFFICIENT_BALANCE" ); await web3tx(customToken.callSelfBurn, "customToken.callSelfBurn")( @@ -660,8 +657,7 @@ describe("SuperToken's Non Standard Functions", function () { await expectCustomError( customToken.callSelfTransferFrom(bob, alice, alice, 100), superTokenContract, - "INSUFFICIENT_BALANCE", - t.customErrorCode.SF_TOKEN_MOVE_INSUFFICIENT_BALANCE + "SF_TOKEN_MOVE_INSUFFICIENT_BALANCE" ); // holder cannot be zero address @@ -673,16 +669,14 @@ describe("SuperToken's Non Standard Functions", function () { 100 ), superTokenContract, - "ZERO_ADDRESS", - t.customErrorCode.SUPER_TOKEN_TRANSFER_FROM_ZERO_ADDRESS + "SUPER_TOKEN_TRANSFER_FROM_ZERO_ADDRESS" ); // recipient cannot be zero address await expectCustomError( customToken.callSelfTransferFrom(alice, bob, ZERO_ADDRESS, 100), superTokenContract, - "ZERO_ADDRESS", - t.customErrorCode.SUPER_TOKEN_TRANSFER_TO_ZERO_ADDRESS + "SUPER_TOKEN_TRANSFER_TO_ZERO_ADDRESS" ); // alice approves bob to spend her tokens @@ -738,16 +732,14 @@ describe("SuperToken's Non Standard Functions", function () { await expectCustomError( customToken.callSelfApproveFor(ZERO_ADDRESS, bob, 100), superTokenContract, - "ZERO_ADDRESS", - t.customErrorCode.SUPER_TOKEN_APPROVE_FROM_ZERO_ADDRESS + "SUPER_TOKEN_APPROVE_FROM_ZERO_ADDRESS" ); // spender cannot be zero address await expectCustomError( customToken.callSelfApproveFor(alice, ZERO_ADDRESS, 100), superTokenContract, - "ZERO_ADDRESS", - t.customErrorCode.SUPER_TOKEN_APPROVE_TO_ZERO_ADDRESS + "SUPER_TOKEN_APPROVE_TO_ZERO_ADDRESS" ); // should be able to call selfApprove at will + make a selfTransferFrom @@ -805,26 +797,22 @@ describe("SuperToken's Non Standard Functions", function () { await expectCustomError( superToken.operationApprove(alice, bob, "0"), superToken, - "ONLY_HOST", - t.customErrorCode.SF_TOKEN_ONLY_HOST + "SF_TOKEN_ONLY_HOST" ); await expectCustomError( superToken.operationTransferFrom(alice, bob, admin, "0"), superToken, - "ONLY_HOST", - t.customErrorCode.SF_TOKEN_ONLY_HOST + "SF_TOKEN_ONLY_HOST" ); await expectCustomError( superToken.operationUpgrade(alice, "0"), superToken, - "ONLY_HOST", - t.customErrorCode.SF_TOKEN_ONLY_HOST + "SF_TOKEN_ONLY_HOST" ); await expectCustomError( superToken.operationDowngrade(alice, "0"), superToken, - "ONLY_HOST", - t.customErrorCode.SF_TOKEN_ONLY_HOST + "SF_TOKEN_ONLY_HOST" ); }); }); diff --git a/packages/ethereum-contracts/test/contracts/superfluid/SuperTokenFactory.test.ts b/packages/ethereum-contracts/test/contracts/superfluid/SuperTokenFactory.test.ts index 2076e66ac2..0910d05da5 100644 --- a/packages/ethereum-contracts/test/contracts/superfluid/SuperTokenFactory.test.ts +++ b/packages/ethereum-contracts/test/contracts/superfluid/SuperTokenFactory.test.ts @@ -80,8 +80,7 @@ describe("SuperTokenFactory Contract", function () { await expectCustomError( factory.updateCode(ZERO_ADDRESS), factory, - "ONLY_HOST", - t.customErrorCode.SUPER_TOKEN_FACTORY_ONLY_HOST + "SUPER_TOKEN_FACTORY_ONLY_HOST" ); }); @@ -278,8 +277,7 @@ describe("SuperTokenFactory Contract", function () { "createERC20Wrapper(address,uint8,uint8,string,string)" ](ZERO_ADDRESS, 18, 0, "name", "symbol"), factory, - "ZERO_ADDRESS", - t.customErrorCode.SUPER_TOKEN_FACTORY_ZERO_ADDRESS + "SUPER_TOKEN_FACTORY_ZERO_ADDRESS" ); }); }); diff --git a/packages/ethereum-contracts/test/contracts/superfluid/Superfluid.test.ts b/packages/ethereum-contracts/test/contracts/superfluid/Superfluid.test.ts index 559ff77b01..65fcb5c039 100644 --- a/packages/ethereum-contracts/test/contracts/superfluid/Superfluid.test.ts +++ b/packages/ethereum-contracts/test/contracts/superfluid/Superfluid.test.ts @@ -309,8 +309,7 @@ describe("Superfluid Host Contract", function () { await expectCustomError( t.contracts.ida.updateCode(ZERO_ADDRESS), t.contracts.ida, - "ONLY_HOST", - t.customErrorCode.AGREEMENT_BASE_ONLY_HOST + "AGREEMENT_BASE_ONLY_HOST" ); }); @@ -329,8 +328,7 @@ describe("Superfluid Host Contract", function () { mockA2.address ), superfluid, - "ALREADY_EXISTS", - t.customErrorCode.HOST_AGREEMENT_ALREADY_REGISTERED + "HOST_AGREEMENT_ALREADY_REGISTERED" ); }); @@ -387,27 +385,23 @@ describe("Superfluid Host Contract", function () { ZERO_ADDRESS ), superfluid, - "DOES_NOT_EXIST", - t.customErrorCode.HOST_AGREEMENT_IS_NOT_REGISTERED + "HOST_AGREEMENT_IS_NOT_REGISTERED" ); await expectCustomError( superfluid.getAgreementClass(typeA), superfluid, - "DOES_NOT_EXIST", - t.customErrorCode.HOST_AGREEMENT_IS_NOT_REGISTERED + "HOST_AGREEMENT_IS_NOT_REGISTERED" ); await expectCustomError( superfluid.addToAgreementClassesBitmap(0, typeA), superfluid, - "DOES_NOT_EXIST", - t.customErrorCode.HOST_AGREEMENT_IS_NOT_REGISTERED + "HOST_AGREEMENT_IS_NOT_REGISTERED" ); await expectCustomError( superfluid.removeFromAgreementClassesBitmap(0, typeA), superfluid, - "DOES_NOT_EXIST", - t.customErrorCode.HOST_AGREEMENT_IS_NOT_REGISTERED + "HOST_AGREEMENT_IS_NOT_REGISTERED" ); }); @@ -596,8 +590,7 @@ describe("Superfluid Host Contract", function () { await expectCustomError( superfluid.registerAppByFactory(ZERO_ADDRESS, 1), superfluid, - "MUST_BE_CONTRACT", - t.customErrorCode.HOST_MUST_BE_CONTRACT + "HOST_MUST_BE_CONTRACT" ); }); @@ -764,7 +757,7 @@ describe("Superfluid Host Contract", function () { }); it("#6.2 use agreement framework as an unregistered agreement", async () => { - const reason = "ONLY_LISTED_AGREEMENT"; + const reason = "HOST_ONLY_LISTED_AGREEMENT"; // call from an unregistered mock agreement const mock = await createAgreementMock( @@ -779,8 +772,7 @@ describe("Superfluid Host Contract", function () { "0x" ), superfluid, - reason, - t.customErrorCode.HOST_ONLY_LISTED_AGREEMENT + reason ); await expectCustomError( mock.tryCallAppAfterCallback( @@ -790,8 +782,7 @@ describe("Superfluid Host Contract", function () { "0x" ), superfluid, - reason, - t.customErrorCode.HOST_ONLY_LISTED_AGREEMENT + reason ); await expectCustomError( mock.tryAppCallbackPush( @@ -801,14 +792,12 @@ describe("Superfluid Host Contract", function () { "0x" ), superfluid, - reason, - t.customErrorCode.HOST_ONLY_LISTED_AGREEMENT + reason ); await expectCustomError( mock.tryAppCallbackPop(superfluid.address, "0x"), superfluid, - reason, - t.customErrorCode.HOST_ONLY_LISTED_AGREEMENT + reason ); await expectCustomError( mock.tryCtxUseCredit( @@ -817,8 +806,7 @@ describe("Superfluid Host Contract", function () { "0x" ), superfluid, - reason, - t.customErrorCode.HOST_ONLY_LISTED_AGREEMENT + reason ); await expectCustomError( mock.tryJailApp( @@ -828,13 +816,12 @@ describe("Superfluid Host Contract", function () { "0x" ), superfluid, - reason, - t.customErrorCode.HOST_ONLY_LISTED_AGREEMENT + reason ); }); it("#6.3 use agreement framework as an impersonating agreement", async () => { - const reason = "ONLY_LISTED_AGREEMENT"; + const reason = "HOST_ONLY_LISTED_AGREEMENT"; const mock = await createAgreementMock( await t.contracts.cfa.agreementType(), @@ -848,8 +835,7 @@ describe("Superfluid Host Contract", function () { "0x" ), superfluid, - reason, - t.customErrorCode.HOST_ONLY_LISTED_AGREEMENT + reason ); await expectCustomError( mock.tryCallAppAfterCallback( @@ -859,8 +845,7 @@ describe("Superfluid Host Contract", function () { "0x" ), superfluid, - reason, - t.customErrorCode.HOST_ONLY_LISTED_AGREEMENT + reason ); await expectCustomError( mock.tryAppCallbackPush( @@ -870,14 +855,12 @@ describe("Superfluid Host Contract", function () { "0x" ), superfluid, - reason, - t.customErrorCode.HOST_ONLY_LISTED_AGREEMENT + reason ); await expectCustomError( mock.tryAppCallbackPop(superfluid.address, "0x"), superfluid, - reason, - t.customErrorCode.HOST_ONLY_LISTED_AGREEMENT + reason ); await expectCustomError( mock.tryCtxUseCredit( @@ -886,8 +869,7 @@ describe("Superfluid Host Contract", function () { "0x" ), superfluid, - reason, - t.customErrorCode.HOST_ONLY_LISTED_AGREEMENT + reason ); await expectCustomError( mock.tryJailApp( @@ -897,8 +879,7 @@ describe("Superfluid Host Contract", function () { "0x" ), superfluid, - reason, - t.customErrorCode.HOST_ONLY_LISTED_AGREEMENT + reason ); }); @@ -1715,7 +1696,7 @@ describe("Superfluid Host Contract", function () { describe("#7 callAgreement", () => { it("#7.1 only listed agreement allowed", async () => { - const reason = "ONLY_LISTED_AGREEMENT"; + const reason = "HOST_ONLY_LISTED_AGREEMENT"; // call to an non agreement await expect(superfluid.callAgreement(alice, "0x", "0x")).to.be .reverted; @@ -1727,8 +1708,7 @@ describe("Superfluid Host Contract", function () { await expectCustomError( superfluid.callAgreement(mock.address, "0x", "0x"), superfluid, - reason, - t.customErrorCode.HOST_ONLY_LISTED_AGREEMENT + reason ); // call to an in personating mock agreement mock = await createAgreementMock( @@ -1738,8 +1718,7 @@ describe("Superfluid Host Contract", function () { await expectCustomError( superfluid.callAgreement(mock.address, "0x", "0x"), superfluid, - reason, - t.customErrorCode.HOST_ONLY_LISTED_AGREEMENT + reason ); }); diff --git a/packages/ethereum-contracts/test/contracts/superfluid/SuperfluidToken.test.ts b/packages/ethereum-contracts/test/contracts/superfluid/SuperfluidToken.test.ts index 752affedeb..43ee67effe 100644 --- a/packages/ethereum-contracts/test/contracts/superfluid/SuperfluidToken.test.ts +++ b/packages/ethereum-contracts/test/contracts/superfluid/SuperfluidToken.test.ts @@ -298,8 +298,7 @@ describe("SuperfluidToken implementation", function () { testData ), superToken, - "ALREADY_EXISTS", - t.customErrorCode.SF_TOKEN_AGREEMENT_ALREADY_EXISTS + "SF_TOKEN_AGREEMENT_ALREADY_EXISTS" ); // try overlapping data await expectCustomError( @@ -307,8 +306,7 @@ describe("SuperfluidToken implementation", function () { testData[0], ]), superToken, - "ALREADY_EXISTS", - t.customErrorCode.SF_TOKEN_AGREEMENT_ALREADY_EXISTS + "SF_TOKEN_AGREEMENT_ALREADY_EXISTS" ); await expectCustomError( acA.createAgreementFor(superToken.address, formattedData, [ @@ -316,8 +314,7 @@ describe("SuperfluidToken implementation", function () { ...testData, ]), superToken, - "ALREADY_EXISTS", - t.customErrorCode.SF_TOKEN_AGREEMENT_ALREADY_EXISTS + "SF_TOKEN_AGREEMENT_ALREADY_EXISTS" ); }); @@ -394,8 +391,7 @@ describe("SuperfluidToken implementation", function () { 2 ), superToken, - "DOES_NOT_EXIST", - t.customErrorCode.SF_TOKEN_AGREEMENT_DOES_NOT_EXIST + "SF_TOKEN_AGREEMENT_DOES_NOT_EXIST" ); }); }); @@ -495,8 +491,7 @@ describe("SuperfluidToken implementation", function () { await expectCustomError( acBad.settleBalanceFor(superToken.address, bob, "1"), superToken, - "ONLY_LISTED_AGREEMENT", - t.customErrorCode.SF_TOKEN_ONLY_LISTED_AGREEMENT + "SF_TOKEN_ONLY_LISTED_AGREEMENT" ); }); @@ -544,8 +539,7 @@ describe("SuperfluidToken implementation", function () { 0 ), superToken, - "ONLY_LISTED_AGREEMENT", - t.customErrorCode.SF_TOKEN_ONLY_LISTED_AGREEMENT + "SF_TOKEN_ONLY_LISTED_AGREEMENT" ); }); diff --git a/packages/ethereum-contracts/test/contracts/tokens/SETH.test.ts b/packages/ethereum-contracts/test/contracts/tokens/SETH.test.ts index 7fa60d0551..f57d80670d 100644 --- a/packages/ethereum-contracts/test/contracts/tokens/SETH.test.ts +++ b/packages/ethereum-contracts/test/contracts/tokens/SETH.test.ts @@ -126,8 +126,7 @@ describe("Super ETH (SETH) Contract", function () { .connect(aliceSigner) .downgradeToETH(toWad(1).addn(1).toString()), superTokenContract, - "INSUFFICIENT_BALANCE", - t.customErrorCode.SF_TOKEN_BURN_INSUFFICIENT_BALANCE + "SF_TOKEN_BURN_INSUFFICIENT_BALANCE" ); const aliceBalance1 = await web3.eth.getBalance(alice); diff --git a/packages/ethereum-contracts/test/contracts/utils/TOGA.test.ts b/packages/ethereum-contracts/test/contracts/utils/TOGA.test.ts index 27f70c242f..56298e935e 100644 --- a/packages/ethereum-contracts/test/contracts/utils/TOGA.test.ts +++ b/packages/ethereum-contracts/test/contracts/utils/TOGA.test.ts @@ -6,7 +6,6 @@ import { SuperfluidMock, SuperToken, TOGA, - TokenCustodian, } from "../../../typechain-types"; import {expectRevertedWith} from "../../utils/expectRevert"; @@ -29,7 +28,7 @@ describe("TOGA", function () { let superfluid: SuperfluidMock, erc1820: IERC1820Registry, cfa: ConstantFlowAgreementV1; - let toga: TOGA, custodian: TokenCustodian; + let toga: TOGA; let superToken: SuperToken; const MIN_BOND_DURATION = 3600 * 24 * 7; // 604800 s | 7 days const EXIT_RATE_1 = 1; // 1 wad per second @@ -53,16 +52,10 @@ describe("TOGA", function () { beforeEach(async function () { await t.beforeEachTestCase(); - const CustodianFactory = await ethers.getContractFactory( - "TokenCustodian" - ); - custodian = await CustodianFactory.deploy(); - console.log(`custodian deployed at: ${custodian.address}`); const TOGAFactory = await ethers.getContractFactory("TOGA"); toga = await TOGAFactory.deploy( superfluid.address, - MIN_BOND_DURATION, - custodian.address + MIN_BOND_DURATION ); console.log(`TOGA deployed at: ${toga.address}`); await t.upgradeBalance("alice", t.configs.INIT_BALANCE); @@ -169,9 +162,6 @@ describe("TOGA", function () { // ================================================================================ it("#0 contract setup", async () => { - const custodianAddr = await toga.custodian(); - assert.equal(custodianAddr, custodian.address); - const minBondDuration = await toga.minBondDuration(); assert.equal(minBondDuration.toNumber(), MIN_BOND_DURATION); }); @@ -653,209 +643,4 @@ describe("TOGA", function () { (await superToken2.balanceOf(bob)).toString() ); }); - - it("#16 reverting send() hook can't prevent a successful bid", async () => { - await t.upgradeBalance("bob", t.configs.INIT_BALANCE); - - const aliceRecipientHookFactory = await ethers.getContractFactory( - "ERC777RecipientReverting" - ); - const aliceRecipientHook = await aliceRecipientHookFactory.deploy(); - await erc1820 - .connect(await ethers.getSigner(alice)) - .setInterfaceImplementer( - alice, - web3.utils.soliditySha3("ERC777TokensRecipient")!, - aliceRecipientHook.address - ); - await sendPICBid(alice, superToken, BOND_AMOUNT_1E18, EXIT_RATE_1E3); - - await sendPICBid(bob, superToken, BOND_AMOUNT_2E18, EXIT_RATE_1E3); - assert.equal(await toga.getCurrentPIC(superToken.address), bob); - }); - - // this ensures an OOG in fallback transfer can't succeed - it("#17 reverting transfer to custodian reverts whole tx", async () => { - await t.upgradeBalance("bob", t.configs.INIT_BALANCE); - await t.upgradeBalance("carol", t.configs.INIT_BALANCE); - - // override to a toga which uses a reverting custodian contract - const revertingRecipientFactory = await ethers.getContractFactory( - "ERC777RecipientReverting" - ); - const revertingRecipient = await revertingRecipientFactory.deploy(); - const TOGAFactory = await ethers.getContractFactory("TOGA"); - toga = await TOGAFactory.deploy( - superfluid.address, - MIN_BOND_DURATION, - revertingRecipient.address - ); - - // alice becomes first PIC - await sendPICBid(alice, superToken, BOND_AMOUNT_1E18, EXIT_RATE_1E3); - assert.equal(await toga.getCurrentPIC(superToken.address), alice); - - // bob can outbid - await sendPICBid(bob, superToken, BOND_AMOUNT_2E18, EXIT_RATE_1E3); - assert.equal(await toga.getCurrentPIC(superToken.address), bob); - - await erc1820 - .connect(await ethers.getSigner(bob)) - .setInterfaceImplementer( - bob, - web3.utils.soliditySha3("ERC777TokensRecipient")!, - revertingRecipient.address - ); - - // now both the send() in the try and the send() in the catch fail - await expectRevertedWith( - sendPICBid(carol, superToken, BOND_AMOUNT_3E18, EXIT_RATE_1E3), - "they shall not pass" - ); - }); - - it("#18 previous PIC can't grief new bidder with gas draining send() hook [ @skip-on-coverage ]", async () => { - await t.upgradeBalance("bob", t.configs.INIT_BALANCE); - - await sendPICBid(alice, superToken, BOND_AMOUNT_1E18, EXIT_RATE_1E3); - - // alice becomes malicious and tries to prevent others from outbidding her - const ERC777RecipientDrainingGasFactory = - await ethers.getContractFactory("ERC777RecipientDrainingGas"); - const aliceRecipientHook = - await ERC777RecipientDrainingGasFactory.deploy(); - await erc1820 - .connect(await ethers.getSigner(alice)) - .setInterfaceImplementer( - alice, - web3.utils.soliditySha3("ERC777TokensRecipient")!, - aliceRecipientHook.address - ); - - // send hook has higher allowance than gas limit, causes the tx to fail - await expectRevertedWith( - sendPICBid(bob, superToken, BOND_AMOUNT_2E18, EXIT_RATE_1E3), - "CallUtils: target revert()" - ); - - // tx gets high enough gas limit for the send allowance not to make it fail - await expect( - sendPICBid( - bob, - superToken, - BOND_AMOUNT_2E18, - EXIT_RATE_1E3, - 4000000 - ) - ).to.emit(aliceRecipientHook, "DrainedGas"); - // @note we need to figure out a way of getting the receipt - // console.log(`gas used by tx: ${r1.receipt.gasUsed}`); - }); - - it("#19 funds in custody can be withdrawn by legitimate owner", async () => { - await t.upgradeBalance("bob", t.configs.INIT_BALANCE); - await t.upgradeBalance("carol", t.configs.INIT_BALANCE); - - const RevertingRecipientHookFactory = await ethers.getContractFactory( - "ERC777RecipientReverting" - ); - const revertingRecipientHook = - await RevertingRecipientHookFactory.deploy(); - await erc1820 - .connect(await ethers.getSigner(alice)) - .setInterfaceImplementer( - alice, - web3.utils.soliditySha3("ERC777TokensRecipient")!, - revertingRecipientHook.address - ); - await erc1820 - .connect(await ethers.getSigner(bob)) - .setInterfaceImplementer( - bob, - web3.utils.soliditySha3("ERC777TokensRecipient")!, - revertingRecipientHook.address - ); - - await sendPICBid(alice, superToken, BOND_AMOUNT_1E18, 0); - const alicePreOutbid1Bal = await superToken.balanceOf(alice); - const alicePreOutbid1Bond = ( - await toga.getCurrentPICInfo(superToken.address) - ).bond; - - await expect(sendPICBid(bob, superToken, BOND_AMOUNT_2E18, 0)) - .to.emit(custodian, "CustodianDeposit") - .withArgs(superToken.address, alice, BOND_AMOUNT_1E18.toString()); - assert.equal( - (await superToken.balanceOf(custodian.address)).toString(), - alicePreOutbid1Bond.toString() - ); - - const bobPreOutbid1Bal = await superToken.balanceOf(bob); - const bobPreOutbid1Bond = ( - await toga.getCurrentPICInfo(superToken.address) - ).bond; - - await expect(sendPICBid(carol, superToken, BOND_AMOUNT_3E18, 0)) - .to.emit(custodian, "CustodianDeposit") - .withArgs(superToken.address, bob, BOND_AMOUNT_2E18.toString()); - assert.equal( - (await superToken.balanceOf(custodian.address)).toString(), - alicePreOutbid1Bond.add(bobPreOutbid1Bond).toString() - ); - - // remove the reverting hook from both alice and bob, - // otherwise withdrawal from the custodian fails too - await erc1820 - .connect(await ethers.getSigner(alice)) - .setInterfaceImplementer( - alice, - web3.utils.soliditySha3("ERC777TokensRecipient")!, - ZERO_ADDRESS - ); - await erc1820 - .connect(await ethers.getSigner(bob)) - .setInterfaceImplementer( - bob, - web3.utils.soliditySha3("ERC777TokensRecipient")!, - ZERO_ADDRESS - ); - - await expect( - toga - .connect(await ethers.getSigner(alice)) - .withdrawFundsInCustody(superToken.address) - ) - .to.emit(custodian, "CustodianWithdrawal") - .withArgs(superToken.address, alice, BOND_AMOUNT_1E18.toString()); - assert.equal( - (await superToken.balanceOf(alice)).toString(), - alicePreOutbid1Bal.add(alicePreOutbid1Bond).toString() - ); - // withdrawing again shall have no effect - await toga - .connect(await ethers.getSigner(alice)) - .withdrawFundsInCustody(superToken.address); - assert.equal( - (await superToken.balanceOf(alice)).toString(), - alicePreOutbid1Bal.add(alicePreOutbid1Bond).toString() - ); - - await expect( - toga - .connect(await ethers.getSigner(bob)) - .withdrawFundsInCustody(superToken.address) - ) - .to.emit(custodian, "CustodianWithdrawal") - .withArgs(superToken.address, bob, BOND_AMOUNT_2E18.toString()); - assert.equal( - (await superToken.balanceOf(bob)).toString(), - bobPreOutbid1Bal.add(bobPreOutbid1Bond).toString() - ); - - // no funds left as all was withdrawn - assert.equal( - (await superToken.balanceOf(custodian.address)).toString(), - "0" - ); - }); }); diff --git a/packages/ethereum-contracts/test/foundry/agreements/ConstantFlowAgreementV1.prop.sol b/packages/ethereum-contracts/test/foundry/agreements/ConstantFlowAgreementV1.prop.sol index 93e3415af7..628893ddf8 100644 --- a/packages/ethereum-contracts/test/foundry/agreements/ConstantFlowAgreementV1.prop.sol +++ b/packages/ethereum-contracts/test/foundry/agreements/ConstantFlowAgreementV1.prop.sol @@ -118,6 +118,8 @@ contract ConstantFlowAgreementV1Properties is Test { vm.assume(type(uint256).max - depositB < depositA); uint256 clippedDepositA = cfa.clipDepositNumberRoundingUp(depositA); uint256 clippedDepositB = cfa.clipDepositNumberRoundingUp(depositB); + clippedDepositA = bound(clippedDepositA, 0, type(uint256).max - clippedDepositB); + clippedDepositB = bound(clippedDepositB, 0, type(uint256).max - clippedDepositA); uint256 summedDeposit = clippedDepositA + clippedDepositB; uint256 clippedSummedDeposit = cfa.clipDepositNumberRoundingUp(summedDeposit); assertTrue(summedDeposit == clippedSummedDeposit); diff --git a/packages/ethereum-contracts/test/types.ts b/packages/ethereum-contracts/test/types.ts index 07558ba22c..0ffe35ab00 100644 --- a/packages/ethereum-contracts/test/types.ts +++ b/packages/ethereum-contracts/test/types.ts @@ -127,47 +127,6 @@ export const CUSTOM_ERROR_CODES = { APP_RULE_COMPOSITE_APP_IS_NOT_WHITELISTED: 30, APP_RULE_COMPOSITE_APP_IS_JAILED: 31, APP_RULE_MAX_APP_LEVEL_REACHED: 40, - - CFA_FLOW_ALREADY_EXISTS: 1000, - CFA_FLOW_DOES_NOT_EXIST: 1001, - CFA_INSUFFICIENT_BALANCE: 1100, - CFA_ZERO_ADDRESS_SENDER: 1500, - CFA_ZERO_ADDRESS_RECEIVER: 1501, - - IDA_INDEX_ALREADY_EXISTS: 2000, - IDA_INDEX_DOES_NOT_EXIST: 2001, - IDA_SUBSCRIPTION_DOES_NOT_EXIST: 2002, - IDA_SUBSCRIPTION_ALREADY_APPROVED: 2003, - IDA_SUBSCRIPTION_IS_NOT_APPROVED: 2004, - IDA_INSUFFICIENT_BALANCE: 2100, - IDA_ZERO_ADDRESS_SUBSCRIBER: 2500, - - HOST_AGREEMENT_ALREADY_REGISTERED: 3000, - HOST_AGREEMENT_IS_NOT_REGISTERED: 3001, - HOST_SUPER_APP_ALREADY_REGISTERED: 3002, - HOST_MUST_BE_CONTRACT: 3200, - HOST_ONLY_LISTED_AGREEMENT: 3300, - - SF_GOV_MUST_BE_CONTRACT: 4200, - - SF_TOKEN_AGREEMENT_ALREADY_EXISTS: 5000, - SF_TOKEN_AGREEMENT_DOES_NOT_EXIST: 5001, - SF_TOKEN_BURN_INSUFFICIENT_BALANCE: 5100, - SF_TOKEN_MOVE_INSUFFICIENT_BALANCE: 5101, - SF_TOKEN_ONLY_LISTED_AGREEMENT: 5300, - SF_TOKEN_ONLY_HOST: 5400, - - SUPER_TOKEN_ONLY_HOST: 6400, - SUPER_TOKEN_APPROVE_FROM_ZERO_ADDRESS: 6500, - SUPER_TOKEN_APPROVE_TO_ZERO_ADDRESS: 6501, - SUPER_TOKEN_BURN_FROM_ZERO_ADDRESS: 6502, - SUPER_TOKEN_MINT_TO_ZERO_ADDRESS: 6503, - SUPER_TOKEN_TRANSFER_FROM_ZERO_ADDRESS: 6504, - SUPER_TOKEN_TRANSFER_TO_ZERO_ADDRESS: 6505, - SUPER_TOKEN_FACTORY_ONLY_HOST: 7400, - SUPER_TOKEN_FACTORY_ZERO_ADDRESS: 7500, - - AGREEMENT_BASE_ONLY_HOST: 8400, }; export type CustomErrorCodeType = typeof CUSTOM_ERROR_CODES; diff --git a/packages/ethereum-contracts/truffle-config.js b/packages/ethereum-contracts/truffle-config.js index ec07c2b35b..906bcf9781 100644 --- a/packages/ethereum-contracts/truffle-config.js +++ b/packages/ethereum-contracts/truffle-config.js @@ -129,7 +129,6 @@ function createNetworkDefaultConfiguration( const E = (module.exports = { plugins: [ //"truffle-security", - "solidity-coverage", "truffle-plugin-verify", ], /** diff --git a/packages/ethereum-contracts/tsconfig.json b/packages/ethereum-contracts/tsconfig.json index 9fb61885ff..1767969c8b 100644 --- a/packages/ethereum-contracts/tsconfig.json +++ b/packages/ethereum-contracts/tsconfig.json @@ -101,6 +101,5 @@ // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ "skipLibCheck": true /* Skip type checking all .d.ts files. */ }, - "files": ["hardhat.config.ts"], "include": ["testsuites", "test", "./typechain-types", "scripts"] } diff --git a/packages/ethereum-contracts/tsconfig.scripts.json b/packages/ethereum-contracts/tsconfig.scripts.json new file mode 100644 index 0000000000..1ae6f10086 --- /dev/null +++ b/packages/ethereum-contracts/tsconfig.scripts.json @@ -0,0 +1,24 @@ +{ + // Change this to match your project + "include": ["scripts/*.js"], + "compilerOptions": { + // Tells TypeScript to read JS files, as + // normally they are ignored as source files + "allowJs": true, + // Generate d.ts files + "declaration": true, + // This compiler run should + // only output d.ts files + "emitDeclarationOnly": true, + // Types should go into this directory. + // Removing this would place the .d.ts files + // next to the .js files + // "outDir": "types", + // go to js file when using IDE functions like + // "Go to Definition" in VSCode + "declarationMap": true, + // Emit additional JavaScript to ease support for importing CommonJS modules. + // This enables 'allowSyntheticDefaultImports' for type compatibility. + "esModuleInterop": true + } + } \ No newline at end of file diff --git a/packages/ethereum-contracts/tsconfig.typechain.json b/packages/ethereum-contracts/tsconfig.typechain.json index 30e1571f2d..538bf2c61b 100644 --- a/packages/ethereum-contracts/tsconfig.typechain.json +++ b/packages/ethereum-contracts/tsconfig.typechain.json @@ -1,15 +1,14 @@ { - "extends": "./tsconfig", "compilerOptions": { "target": "ES2019", "outDir": "build/typechain", "moduleResolution": "node", "module": "CommonJS", + "esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */, "declaration": true, "declarationMap": true, "sourceMap": true }, - "files": [], "include": ["typechain-types"], "exclude": ["node_modules/**"] } diff --git a/packages/hot-fuzz/package.json b/packages/hot-fuzz/package.json index 63044743f4..666a18a7be 100644 --- a/packages/hot-fuzz/package.json +++ b/packages/hot-fuzz/package.json @@ -24,7 +24,7 @@ "@superfluid-finance/ethereum-contracts": "1.2.2" }, "devDependencies": { - "@superfluid-finance/ethereum-contracts": "1.4.2" + "@superfluid-finance/ethereum-contracts": "1.4.3" }, "license": "AGPL-3.0", "bugs": { diff --git a/packages/js-sdk/src/ConstantFlowAgreementV1Helper.d.ts b/packages/js-sdk/src/ConstantFlowAgreementV1Helper.d.ts index 8cf34543a2..456ec56802 100644 --- a/packages/js-sdk/src/ConstantFlowAgreementV1Helper.d.ts +++ b/packages/js-sdk/src/ConstantFlowAgreementV1Helper.d.ts @@ -151,7 +151,7 @@ export declare class ConstantFlowAgreementV1Helper { * @param {tokenParam} superToken superToken for the flow * @param {addressParam} sender sender of the flow * @param {addressParam} receiver receiver of the flow - * @return {Promise} Informationo about the flow: + * @return {Promise} Information about the flow: * - timestamp, time when the flow was last updated * - flowRate, flow rate of the flow * - deposit, deposit of the flow @@ -176,7 +176,7 @@ export declare class ConstantFlowAgreementV1Helper { * @dev Get information of the net flow of an account * @param {tokenParam} superToken superToken for the flow * @param {addressParam} account the account for the query - * @return {Promise} Net flow rate of the account + * @return {Promise} Flow info of the account */ getAccountFlowInfo({ superToken, @@ -196,7 +196,7 @@ export declare class ConstantFlowAgreementV1Helper { * @dev List flows of the account * @param {tokenParam} superToken superToken for the flow * @param {addressParam} account the account for the query - * @return {Promise<[]>} + * @return {Promise} */ listFlows({ superToken, diff --git a/packages/js-sdk/src/InstantDistributionAgreementV1Helper.d.ts b/packages/js-sdk/src/InstantDistributionAgreementV1Helper.d.ts index 1634354b5a..b468027401 100644 --- a/packages/js-sdk/src/InstantDistributionAgreementV1Helper.d.ts +++ b/packages/js-sdk/src/InstantDistributionAgreementV1Helper.d.ts @@ -141,7 +141,7 @@ export interface ListIndicesOptions { publisher: string; } -export interface ListSubcribersOptions { +export interface ListSubscribersOptions { superToken: string; publisher: string; indexId: number; @@ -333,14 +333,14 @@ export declare class InstantDistributionAgreementV1Helper { * @param {addressParam} publisher Publisher of the index * @param {int} indexId ID of the index * @param {addressParam} subscriber Subscriber of the index - * @return {Promise} Subscription data + * @return {Promise} Subscription data */ getSubscription({ superToken, publisher, indexId, subscriber - }: GetSubscriptionOptions): Promise; + }: GetSubscriptionOptions): Promise; /** * @dev Claim distributions to a subscriber of the index by anyone. * @param {tokenParam} superToken SuperToken for the index @@ -370,7 +370,7 @@ export declare class InstantDistributionAgreementV1Helper { * @param {tokenParam} superToken SuperToken for the index * @param {addressParam} publisher Publisher of the index * @param {int} indexId ID of the index - * @return {Promise} Subscription data + * @return {Promise} Subscription data */ getIndex({ superToken, @@ -381,7 +381,7 @@ export declare class InstantDistributionAgreementV1Helper { * @dev List indices of a publisher * @param {tokenParam} superToken SuperToken for the index * @param {addressParam} publisher Publisher of the index - * @return {Promise} Subscription data + * @return {Promise} Subscription data */ listIndices({ superToken, @@ -392,19 +392,19 @@ export declare class InstantDistributionAgreementV1Helper { * @param {tokenParam} superToken SuperToken for the index * @param {addressParam} publisher Publisher of the index * @param {int} indexId ID of the index - * @return {Promise} Subscription data + * @return {Promise} Subscription data */ - listSubcribers({ + listSubscribers({ superToken, publisher, indexId - }: ListSubcribersOptions): Promise; + }: ListSubscribersOptions): Promise; /** * @dev List subscriptions of an account * @param {tokenParam} superToken SuperToken for the index * @param {addressParam} publisher Publisher of the index * @param {int} indexId ID of the index - * @return {Promise} Subscription data + * @return {Promise} Subscription data */ listSubscriptions({ superToken, diff --git a/packages/js-sdk/src/InstantDistributionAgreementV1Helper.js b/packages/js-sdk/src/InstantDistributionAgreementV1Helper.js index e75a6ffba9..47b58de1fd 100644 --- a/packages/js-sdk/src/InstantDistributionAgreementV1Helper.js +++ b/packages/js-sdk/src/InstantDistributionAgreementV1Helper.js @@ -519,7 +519,7 @@ module.exports = class InstantDistributionAgreementV1Helper { * @param {int} indexId ID of the index * @return {Promise} Subscription data */ - async listSubcribers({superToken, publisher, indexId}) { + async listSubscribers({superToken, publisher, indexId}) { const superTokenNorm = await this._sf.utils.normalizeTokenParam( superToken ); diff --git a/packages/js-sdk/src/User.d.ts b/packages/js-sdk/src/User.d.ts index 7f964c7b28..cef8575a72 100644 --- a/packages/js-sdk/src/User.d.ts +++ b/packages/js-sdk/src/User.d.ts @@ -1,8 +1,8 @@ import type { Framework } from './Framework'; import type BN from 'bn.js'; -import type { Transaction } from 'web3'; -import type { Flow } from './ConstantFlowAgreementV1Helper'; -import type { Subscription } from './InstantDistributionAgreementV1Helper'; +import type { Transaction } from 'web3-core'; +import type { FlowInfo, FlowList } from './ConstantFlowAgreementV1Helper'; +import type { SubscriptionData } from './InstantDistributionAgreementV1Helper'; // params options types export interface UserOptions { @@ -41,7 +41,7 @@ export interface UserDetails { netFlow: string // numeric string } ida: { - subscriptions: Array; + subscriptions: Array; } } diff --git a/packages/js-sdk/src/batchCall.d.ts b/packages/js-sdk/src/batchCall.d.ts index c04347c821..746117c332 100644 --- a/packages/js-sdk/src/batchCall.d.ts +++ b/packages/js-sdk/src/batchCall.d.ts @@ -54,7 +54,7 @@ type IDAMethodType = 'claim' | 'getIndex' | 'listIndices' | - 'listSubcribers' | + 'listSubscribers' | 'listSubscriptions'; interface SuperTokenIDAData { diff --git a/packages/js-sdk/src/loadContracts.d.ts b/packages/js-sdk/src/loadContracts.d.ts index 06eb39c61c..b088bdabe6 100644 --- a/packages/js-sdk/src/loadContracts.d.ts +++ b/packages/js-sdk/src/loadContracts.d.ts @@ -1,5 +1,6 @@ import type { Contract as Web3Contract } from "web3-eth-contract"; import TruffleContract from "@truffle/contract"; +import ethers, { Contract, ContractInterface } from "ethers"; import type Web3 from "web3"; type SuperfluidContractNames = @@ -27,7 +28,7 @@ export type SuperfluidContractObject = { export type SuperfluidContracts = Record export type LoadedContract = Web3Contract | TruffleContract.Contract -export type AbiContainer = Pick; +export type AbiContainer = Pick; export type ContractLoader = (name: string) => AbiContainer; declare function setTruffleContractDefaults(c: TruffleContract.Contract, networkId: number, from: string): void; diff --git a/packages/js-sdk/test/Framework.subgraph.test.js b/packages/js-sdk/test/Framework.subgraph.test.js index 9b323925d7..46945b9e89 100644 --- a/packages/js-sdk/test/Framework.subgraph.test.js +++ b/packages/js-sdk/test/Framework.subgraph.test.js @@ -79,8 +79,8 @@ describe("Framework subgraph (matic) support", function () { assert.equal(indexes[0], 0); }); - it("ida.listSubcribers", async () => { - const subscribers = await sf.ida.listSubcribers({ + it("ida.listSubscribers", async () => { + const subscribers = await sf.ida.listSubscribers({ superToken: sf.tokens.ETHx.address, publisher: "0x9BEf427fa1fF5269b824eeD9415F7622b81244f5", indexId: 0, diff --git a/packages/js-sdk/test/InstantDistributionAgreementV1Helper.test.js b/packages/js-sdk/test/InstantDistributionAgreementV1Helper.test.js index c0952e6f4b..a996331bc3 100644 --- a/packages/js-sdk/test/InstantDistributionAgreementV1Helper.test.js +++ b/packages/js-sdk/test/InstantDistributionAgreementV1Helper.test.js @@ -413,7 +413,7 @@ describe("InstantDistributionAgreementV1Helper class", function () { ); }); - it("listSubcribers", async () => { + it("listSubscribers", async () => { const units = toWad(100).toString(); const publisher = alice; await sf.ida.createIndex({ @@ -443,7 +443,7 @@ describe("InstantDistributionAgreementV1Helper class", function () { }); assert.deepEqual( - await sf.ida.listSubcribers({ + await sf.ida.listSubscribers({ superToken: superToken.address, publisher, indexId: 1, @@ -469,7 +469,7 @@ describe("InstantDistributionAgreementV1Helper class", function () { }); assert.deepEqual( - await sf.ida.listSubcribers({ + await sf.ida.listSubscribers({ superToken: superToken.address, publisher, indexId: 1, @@ -483,7 +483,7 @@ describe("InstantDistributionAgreementV1Helper class", function () { ); assert.deepEqual( - await sf.ida.listSubcribers({ + await sf.ida.listSubscribers({ superToken: superToken.address, publisher, indexId: 2, diff --git a/packages/sdk-core/CHANGELOG.md b/packages/sdk-core/CHANGELOG.md index 21aa9fd708..bdd35760c9 100644 --- a/packages/sdk-core/CHANGELOG.md +++ b/packages/sdk-core/CHANGELOG.md @@ -5,6 +5,35 @@ All notable changes to the SDK-core will be documented in this file. This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +### Added +- Map `deposit` to Stream when querying from Subgraph + +## [0.6.0] - 2022-11-02 + +### Added +- Support for mainnet + +### Changed +- Framework initialization for supported networks utilizes `@superfluid-finance/metadata`, but still uses the resolver for unsupported/local testing environments + - > NOTE: This will not create any changes when doing `Framework.create` and is just a minor optimization. +- All transactions executed via SDK-Core have a default multiplier (1.2x) applied to the provider estimated gas limit **unless** an ethers `Overrides` object is passed during creation of the `Operation`. +- There is also the option to pass in an explicit multiplier when executing transactions: `Operation.exec(signer, 1.32)`. + +## [0.5.7] - 2022-10-13 +### Breaking +- `CFAv1Forwarder` integrated into SDK-Core and will be the default way of calling CFA agreements and `sender` is now a required property. + - Migration: pass sender into the affected CFAv1 callAgreement functions - `create/update/deleteFlow`. + > NOTE: You must pass `shouldUseCallAgreement` explicitly as a property if you want to execute these calls via the Host. + +### Added +- typechain files consumed from `@superfluid-finance/ethereum-contracts` and exported from SDK-Core + +## [0.5.6] - 2022-09-07 +### Fixes +- Correct `subgraphAPIEndpoint` in `getResolverData` + +### Breaking +- Don't wrap `SubgraphClient` with `SFError` ## [0.5.7] - 2022-10-13 @@ -270,19 +299,18 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ## [0.1.0] - 2021-12-01 ### Added - -- Initial preview version of SDK-Core -- Features: - - New `Framework` initialization pattern - - Built with `ethers.js` and `TypeScript` from the ground up - - `Query` class which leverages the Subgraph for queries with simple filters - - New `Operation` class for executing transactions/batching transactions - - `ConstantFlowAgreementV1` and `InstantDistributionAgreementV1` helper classes with create, read, update and delete functionality - - New `SuperToken` class with `SuperToken` CRUD functionality and an underlying `Token` class with basic `ERC20` functionality - - New `BatchCall` class for creating and executing batch calls with supported `Operation's` - -# [Unreleased]: https://github.com/superfluid-finance/protocol-monorepo/compare/sdk-core%40v0.5.7...HEAD - +- Initial preview version of SDK-Core +- Features: + - New `Framework` initialization pattern + - Built with `ethers.js` and `TypeScript` from the ground up + - `Query` class which leverages the Subgraph for queries with simple filters + - New `Operation` class for executing transactions/batching transactions + - `ConstantFlowAgreementV1` and `InstantDistributionAgreementV1` helper classes with create, read, update and delete functionality + - New `SuperToken` class with `SuperToken` CRUD functionality and an underlying `Token` class with basic `ERC20` functionality + - New `BatchCall` class for creating and executing batch calls with supported `Operation's` + +[Unreleased]: https://github.com/superfluid-finance/protocol-monorepo/compare/sdk-core%40v0.6.0...HEAD +[0.6.0]: https://github.com/superfluid-finance/protocol-monorepo/compare/sdk-core%40v0.5.7...sdk-core%40v0.6.0 [0.5.7]: https://github.com/superfluid-finance/protocol-monorepo/compare/sdk-core%40v0.5.6...sdk-core%40v0.5.7 [0.5.6]: https://github.com/superfluid-finance/protocol-monorepo/compare/sdk-core%40v0.5.5...sdk-core%40v0.5.6 [0.5.5]: https://github.com/superfluid-finance/protocol-monorepo/compare/sdk-core%40v0.5.4...sdk-core%40v0.5.5 diff --git a/packages/sdk-core/README.md b/packages/sdk-core/README.md index a8335a41fb..528416c46d 100644 --- a/packages/sdk-core/README.md +++ b/packages/sdk-core/README.md @@ -50,6 +50,13 @@ SDK-Core is in early active development and can have breaking releases without w # Prerequisites +> NOTE: You need to have graphql and ethers installed as a dependency in order to use SDK-Core: + +```bash +yarn install graphql ethers +``` + + To get the package up and running you'll need to install the necessary dependencies and build the project: ```bash diff --git a/packages/sdk-core/package.json b/packages/sdk-core/package.json index 0ac266d891..bb00341a4c 100644 --- a/packages/sdk-core/package.json +++ b/packages/sdk-core/package.json @@ -1,6 +1,6 @@ { "name": "@superfluid-finance/sdk-core", - "version": "0.5.7", + "version": "0.6.0", "description": "SDK Core for building with Superfluid Protocol", "homepage": "https://github.com/superfluid-finance/protocol-monorepo/tree/dev/packages/sdk-core#readme", "repository": { @@ -55,7 +55,7 @@ "url": "https://github.com/superfluid-finance/protocol-monorepo/issues" }, "dependencies": { - "@superfluid-finance/ethereum-contracts": "1.4.2", + "@superfluid-finance/ethereum-contracts": "1.4.3", "@superfluid-finance/metadata": "git+https://github.com/superfluid-finance/metadata.git", "browserify": "^17.0.0", "graphql-request": "^4.3.0", diff --git a/packages/sdk-core/src/Framework.ts b/packages/sdk-core/src/Framework.ts index d67cce64fe..3b07cbc14c 100644 --- a/packages/sdk-core/src/Framework.ts +++ b/packages/sdk-core/src/Framework.ts @@ -2,6 +2,7 @@ import { HardhatEthersHelpers } from "@nomiclabs/hardhat-ethers/types"; import { Resolver, Resolver__factory, + Superfluid__factory, SuperfluidLoader__factory, } from "@superfluid-finance/ethereum-contracts/build/typechain"; import { ethers } from "ethers"; @@ -26,12 +27,7 @@ import { getSubgraphQueriesEndpoint, validateFrameworkConstructorOptions, } from "./frameworkHelpers"; -import { - IConfig, - IContracts, - IResolverData, - ISignerConstructorOptions, -} from "./interfaces"; +import { IConfig, IContracts, ISignerConstructorOptions } from "./interfaces"; import { isEthersProvider, isInjectedWeb3 } from "./utils"; type SupportedProvider = @@ -159,57 +155,75 @@ export default class Framework { } try { - const resolverData: IResolverData = chainIdToResolverDataMap.get( - chainId - ) || { - subgraphAPIEndpoint: "", - resolverAddress: "", - networkName: "", - nativeTokenSymbol: "", - }; + const networkData = chainIdToResolverDataMap.get(chainId); const resolverAddress = options.resolverAddress ? options.resolverAddress - : resolverData.resolverAddress; + : networkData + ? networkData.addresses.resolver + : ethers.constants.AddressZero; const resolver = Resolver__factory.connect( resolverAddress, provider ); - - const superfluidLoaderAddress = await resolver.get( - "SuperfluidLoader-v1" - ); - const cfaV1ForwarderAddress = await resolver.get("CFAv1Forwarder"); - const superfluidLoader = SuperfluidLoader__factory.connect( - superfluidLoaderAddress, - provider - ); - - const framework = await superfluidLoader.loadFramework( - releaseVersion - ); - const governanceAddress = await new Host( - framework.superfluid - ).contract - .connect(provider) - .getGovernance(); - - const settings: IFrameworkSettings = { + const baseSettings = { chainId, customSubgraphQueriesEndpoint, protocolReleaseVersion: options.protocolReleaseVersion || "v1", provider, networkName, - config: { - resolverAddress, - hostAddress: framework.superfluid, - cfaV1Address: framework.agreementCFAv1, - idaV1Address: framework.agreementIDAv1, - governanceAddress, - cfaV1ForwarderAddress, - }, }; - return new Framework(options, settings); + // supported networks scenario + if (networkData != null) { + const settings: IFrameworkSettings = { + ...baseSettings, + config: { + resolverAddress, + hostAddress: networkData.addresses.host, + cfaV1Address: networkData.addresses.cfaV1, + idaV1Address: networkData.addresses.idaV1, + governanceAddress: networkData.addresses.governance, + cfaV1ForwarderAddress: + networkData.addresses.cfaV1Forwarder, + }, + }; + + return new Framework(options, settings); + } else { + // unsupported networks scenario (e.g. local testing) + const superfluidLoaderAddress = await resolver.get( + "SuperfluidLoader-v1" + ); + const cfaV1ForwarderAddress = await resolver.get( + "CFAv1Forwarder" + ); + const superfluidLoader = SuperfluidLoader__factory.connect( + superfluidLoaderAddress, + provider + ); + + const framework = await superfluidLoader.loadFramework( + releaseVersion + ); + const governanceAddress = await Superfluid__factory.connect( + framework.superfluid, + provider + ).getGovernance(); + + const settings: IFrameworkSettings = { + ...baseSettings, + config: { + resolverAddress, + hostAddress: framework.superfluid, + cfaV1Address: framework.agreementCFAv1, + idaV1Address: framework.agreementIDAv1, + governanceAddress, + cfaV1ForwarderAddress, + }, + }; + + return new Framework(options, settings); + } } catch (err) { throw new SFError({ type: "FRAMEWORK_INITIALIZATION", diff --git a/packages/sdk-core/src/Operation.ts b/packages/sdk-core/src/Operation.ts index 95d47129ec..d4eff2f3d3 100644 --- a/packages/sdk-core/src/Operation.ts +++ b/packages/sdk-core/src/Operation.ts @@ -36,13 +36,16 @@ export default class Operation { * Executes the operation via the provided signer. * @description Populates all fields of the transaction, signs it and sends it to the network. * @param signer The signer of the transaction + * @param gasLimitMultiplier A multiplier to provide gasLimit buffer on top of the estimated gas limit (1.2x is the default) * @returns {ethers.providers.TransactionResponse} A TransactionResponse object which can be awaited */ exec = async ( - signer: ethers.Signer + signer: ethers.Signer, + gasLimitMultiplier = 1.2 ): Promise => { const populatedTransaction = await this.getPopulatedTransactionRequest( - signer + signer, + gasLimitMultiplier ); return await signer.sendTransaction(populatedTransaction); }; @@ -54,12 +57,33 @@ export default class Operation { * @returns {Promise} */ getPopulatedTransactionRequest = async ( - signer: ethers.Signer + signer: ethers.Signer, + gasLimitMultiplier = 1.2 ): Promise => { const txnToPopulate = this.forwarderPopulatedPromise ? await this.forwarderPopulatedPromise : await this.populateTransactionPromise; - return await signer.populateTransaction(txnToPopulate); + const signerPopulatedTransaction = await signer.populateTransaction( + txnToPopulate + ); + + // if gasLimit exists, an Overrides object has been passed or the user has explicitly set + // a gasLimit for their transaction prior to execution and so we keep it as is else we apply + // a specified or the default (1.2) multiplier on the gas limit. + return txnToPopulate.gasLimit + ? txnToPopulate + : { + ...signerPopulatedTransaction, + gasLimit: + // @note if gasLimit is null, this function will throw due to + // conversion to BigNumber, so we must round this number + // we can be more conservative by using Math.ceil instead of Math.round + Math.ceil( + Number( + signerPopulatedTransaction.gasLimit?.toString() + ) * gasLimitMultiplier + ), + }; }; /** * Signs the populated transaction via the provided signer (what you intend on sending to the network). diff --git a/packages/sdk-core/src/constants.ts b/packages/sdk-core/src/constants.ts index 7b50bc194b..e1b053f70c 100644 --- a/packages/sdk-core/src/constants.ts +++ b/packages/sdk-core/src/constants.ts @@ -1,7 +1,7 @@ import metadata from "@superfluid-finance/metadata"; import DefaultSubgraphReleaseTag from "./defaultSubgraphReleaseTag.json"; -import { IResolverData } from "./interfaces"; +import { NetworkData } from "./types"; /******* TIME CONSTANTS *******/ export const MONTHS_PER_YEAR = 12; @@ -32,15 +32,9 @@ const subgraphReleaseTag = (globalThis.process && globalThis.process.env.SUBGRAPH_RELEASE_TAG) || DefaultSubgraphReleaseTag.value; -const getResolverData = (chainId: number): IResolverData => { +const getAddressesData = (chainId: number): NetworkData | null => { const networkData = metadata.networks.find((x) => x.chainId === chainId); - if (!networkData) - return { - subgraphAPIEndpoint: "", - networkName: "", - resolverAddress: "", - nativeTokenSymbol: "", - }; + if (!networkData) return null; const hostedEndpoint = networkData.subgraphV1.hostedEndpoint; const subgraphAPIEndpoint = subgraphReleaseTag ? hostedEndpoint.replace("v1", subgraphReleaseTag) @@ -48,13 +42,13 @@ const getResolverData = (chainId: number): IResolverData => { return { subgraphAPIEndpoint, networkName: networkData.name, - resolverAddress: networkData.contractsV1.resolver, nativeTokenSymbol: networkData.nativeTokenSymbol, + addresses: networkData.contractsV1, }; }; export const chainIdToResolverDataMap = new Map( - metadata.networks.map((x) => [x.chainId, getResolverData(x.chainId)]) + metadata.networks.map((x) => [x.chainId, getAddressesData(x.chainId)]) ); export const networkNameToChainIdMap = new Map( diff --git a/packages/sdk-core/src/interfaces.ts b/packages/sdk-core/src/interfaces.ts index 442d9a47a8..ada4f70773 100644 --- a/packages/sdk-core/src/interfaces.ts +++ b/packages/sdk-core/src/interfaces.ts @@ -429,13 +429,6 @@ export interface ILightAccountTokenSnapshot extends IAggregateEntityBase { // Internal Interfaces -export interface IResolverData { - readonly subgraphAPIEndpoint: string; - readonly networkName: string; - readonly resolverAddress: string; - readonly nativeTokenSymbol: string; -} - export interface ISignerConstructorOptions { readonly web3Provider?: ethers.providers.Web3Provider; // Web3Provider (client side - metamask, web3modal) readonly provider?: ethers.providers.Provider; // Provider diff --git a/packages/sdk-core/src/subgraph/entities/stream/stream.ts b/packages/sdk-core/src/subgraph/entities/stream/stream.ts index dd49486c3c..cfb6cf1356 100644 --- a/packages/sdk-core/src/subgraph/entities/stream/stream.ts +++ b/packages/sdk-core/src/subgraph/entities/stream/stream.ts @@ -30,6 +30,7 @@ export interface Stream { sender: Address; token: Address; tokenSymbol: string; + deposit: BigNumber; } export type StreamListQuery = SubgraphListQuery; @@ -66,6 +67,7 @@ export class StreamQueryHandler extends SubgraphQueryHandler< token: x.token.id, tokenSymbol: x.token.symbol, sender: x.sender.id, + deposit: x.deposit, })); requestDocument = StreamsDocument; diff --git a/packages/sdk-core/src/subgraph/entities/stream/streams.graphql b/packages/sdk-core/src/subgraph/entities/stream/streams.graphql index 83bf455884..b115520c1b 100644 --- a/packages/sdk-core/src/subgraph/entities/stream/streams.graphql +++ b/packages/sdk-core/src/subgraph/entities/stream/streams.graphql @@ -24,5 +24,6 @@ query streams($first: Int = 10, $orderBy: Stream_orderBy = id, $orderDirection: } updatedAtBlockNumber updatedAtTimestamp + deposit } } diff --git a/packages/sdk-core/src/subgraph/schema.graphql b/packages/sdk-core/src/subgraph/schema.graphql index 8101009271..0be4362cf4 100644 --- a/packages/sdk-core/src/subgraph/schema.graphql +++ b/packages/sdk-core/src/subgraph/schema.graphql @@ -18,6 +18,9 @@ type _Block_ { """The block number""" number: Int! + + """Integer representation of the timestamp stored in blocks for the chain""" + timestamp: Int } """The type for the top-level _meta field""" diff --git a/packages/sdk-core/src/types.ts b/packages/sdk-core/src/types.ts index 92f85304cb..16704db8fa 100644 --- a/packages/sdk-core/src/types.ts +++ b/packages/sdk-core/src/types.ts @@ -3,3 +3,20 @@ export type FlowActionType = | 0 // CREATE | 1 // UPDATE | 2; // TERMINATE + +export type NetworkData = { + subgraphAPIEndpoint: string; + networkName: string; + nativeTokenSymbol: string; + addresses: { + resolver: string; + host: string; + governance: string; + cfaV1: string; + cfaV1Forwarder: string; + idaV1: string; + superTokenFactory: string; + superfluidLoader: string; + toga: string; + }; +}; diff --git a/packages/sdk-core/test/2_operation.test.ts b/packages/sdk-core/test/2_operation.test.ts index ba13cd7f81..26f0422e58 100644 --- a/packages/sdk-core/test/2_operation.test.ts +++ b/packages/sdk-core/test/2_operation.test.ts @@ -1,6 +1,6 @@ import { expect } from "chai"; import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; -import { Framework } from "../src/index"; +import { Framework, toBN } from "../src/index"; import { getPerSecondFlowRateByMonth } from "../src"; import { IConstantFlowAgreementV1__factory } from "@superfluid-finance/ethereum-contracts/build/typechain"; import Operation from "../src/Operation"; @@ -9,6 +9,7 @@ import { SuperAppTester } from "../typechain-types"; import { SuperAppTester__factory } from "../typechain-types"; const cfaInterface = IConstantFlowAgreementV1__factory.createInterface(); import { TestEnvironment, makeSuite } from "./TestEnvironment"; +import { ethers } from "ethers"; /** * Create a simple call app action (setVal) operation with the SuperAppTester contract. @@ -19,7 +20,8 @@ import { TestEnvironment, makeSuite } from "./TestEnvironment"; export const createCallAppActionOperation = async ( deployer: SignerWithAddress, framework: Framework, - val: number + val: number, + overrides?: ethers.Overrides ) => { const SuperAppTesterFactory = await hre.ethers.getContractFactory( "SuperAppTester", @@ -42,13 +44,30 @@ export const createCallAppActionOperation = async ( return { operation: framework.host.callAppAction( superAppTester.address, - callData + callData, + overrides ), superAppTester, }; }; makeSuite("Operation Tests", (testEnv: TestEnvironment) => { + describe("Revert cases", () => { + it("Should fail if gas limit used is far below estimation.", async () => { + const NEW_VAL = 69; + const { operation } = await createCallAppActionOperation( + testEnv.alice, + testEnv.sdkFramework, + NEW_VAL + ); + try { + await operation.exec(testEnv.alice, 0.25); + } catch (err) { + expect(err.message).to.not.be.undefined; + } + }); + }); + describe("Happy Path Tests", () => { it("Should be able to get transaction hash and it should be equal to transaction hash once executed", async () => { const revokeControlOp = @@ -116,10 +135,50 @@ makeSuite("Operation Tests", (testEnv: TestEnvironment) => { testEnv.sdkFramework, NEW_VAL ); - await operation.exec(testEnv.alice); + const txn = await operation.exec(testEnv.alice, 1); expect(await superAppTester.val()).to.equal(NEW_VAL.toString()); }); + it("Should be able to use arbitrary gas estimation limit", async () => { + const NEW_VAL = 69; + const { operation } = await createCallAppActionOperation( + testEnv.alice, + testEnv.sdkFramework, + NEW_VAL + ); + + // we compare the two update operations and not the first one + // because initial storage creation costs more than subsequent updates + const { operation: updateOp1 } = await createCallAppActionOperation( + testEnv.alice, + testEnv.sdkFramework, + 420 + ); + const { operation: updateOp2 } = await createCallAppActionOperation( + testEnv.alice, + testEnv.sdkFramework, + 365 + ); + await operation.exec(testEnv.alice); + const updateOpTxn1 = await updateOp1.exec(testEnv.alice, 1); + const updateOpTxn2 = await updateOp2.exec(testEnv.alice, 2); + expect(updateOpTxn1.gasLimit.mul(toBN(2))).to.equal( + updateOpTxn2.gasLimit + ); + }); + + it("Should not apply multiplier to Overrides gas limit", async () => { + const NEW_VAL = 69; + const { operation } = await createCallAppActionOperation( + testEnv.alice, + testEnv.sdkFramework, + NEW_VAL, + { gasLimit: 500000 } + ); + const txn = await operation.exec(testEnv.alice, 2); + expect(txn.gasLimit).to.equal("500000"); + }); + it("Should throw an error when trying to execute a transaction with faulty callData", async () => { const callData = cfaInterface.encodeFunctionData("createFlow", [ testEnv.wrapperSuperToken.address, diff --git a/packages/sdk-redux/CHANGELOG.md b/packages/sdk-redux/CHANGELOG.md index aa65d72531..88aba7d254 100644 --- a/packages/sdk-redux/CHANGELOG.md +++ b/packages/sdk-redux/CHANGELOG.md @@ -3,10 +3,13 @@ All notable changes to the SDK-redux will be documented in this file. ## [Unreleased] +## [0.4.0] - 2022-10-31 + ### Breaking - Pass in `signer` through mutation payload - Remove `setSignerForSdkRedux` - Serialized `transactionResponse` is now possibly undefined on `TrackedTransaction` when serialization fails +- Update `@reduxjs/toolkit` & `@superfluid-finance/sdk-core` dependencies ### Added - Query for transfer events diff --git a/packages/sdk-redux/package.json b/packages/sdk-redux/package.json index b7f6351124..c3e7386813 100644 --- a/packages/sdk-redux/package.json +++ b/packages/sdk-redux/package.json @@ -1,6 +1,6 @@ { "name": "@superfluid-finance/sdk-redux", - "version": "0.3.0", + "version": "0.4.0", "description": "SDK Redux for streamlined front-end application development with Superfluid Protocol", "homepage": "https://docs.superfluid.finance/", "repository": { @@ -37,17 +37,17 @@ "node": ">=12" }, "dependencies": { - "@types/promise-retry": "^1.1.3", "graphql-request": "^4.3.0", "lodash": "^4.17.21", "promise-retry": "^2.0.1" }, "devDependencies": { - "@reduxjs/toolkit": "^1.8.3" + "@types/promise-retry": "^1.1.3", + "@reduxjs/toolkit": "^1.8.6" }, "peerDependencies": { - "@reduxjs/toolkit": "^1.6.0 || ^1.7.0", - "@superfluid-finance/sdk-core": "~0.4.3" + "@reduxjs/toolkit": "^1.7.0 || 1.8.0", + "@superfluid-finance/sdk-core": "~0.5.6" }, "files": [ "dist/main", diff --git a/packages/spec-haskell/Makefile b/packages/spec-haskell/Makefile index 3de10c8818..82cef8c7d1 100644 --- a/packages/spec-haskell/Makefile +++ b/packages/spec-haskell/Makefile @@ -74,35 +74,49 @@ YELLOWPAPER_ROOT = $(DOCS_BUILDDIR)/yellowpaper $(YELLOWPAPER_ROOT)/tex: mkdir -p "$@" -$(YELLOWPAPER_ROOT)/tex/MoneyDistributionConcepts.tex: packages/core/src/Money/Distribution/Concepts.lhs - ./utils/lhs2tex.sh "$<" > "$@" -$(YELLOWPAPER_ROOT)/tex/Communism.tex: packages/core/src/Money/Distribution/Communism.lhs - ./utils/lhs2tex.sh "$<" > "$@" $(YELLOWPAPER_ROOT)/tex/lhsfmt.tex: echo '%include lhs2TeX.fmt' | lhs2TeX > "$@" + +.SECONDEXPANSION: +$(YELLOWPAPER_ROOT)/tex/%.lhs.tex: packages/core/src/Money/Theory/%.lhs + ./utils/lhs2tex.sh "$<" > "$@" + .SECONDEXPANSION: -$(YELLOWPAPER_ROOT)/tex/%: yellowpaper/$$(notdir $$*) +$(YELLOWPAPER_ROOT)/tex/%.tex: yellowpaper/$$(notdir $$*).tex cp "$<" "$@" -YELLOWPAPER_INPUTS = $(YELLOWPAPER_ROOT)/tex \ - $(YELLOWPAPER_ROOT)/tex/MoneyDistributionConcepts.tex \ - $(YELLOWPAPER_ROOT)/tex/Communism.tex \ - $(YELLOWPAPER_ROOT)/tex/lhsfmt.tex \ +.SECONDEXPANSION: +$(YELLOWPAPER_ROOT)/tex/%.bib: yellowpaper/$$(notdir $$*).bib + cp "$<" "$@" + +YELLOWPAPER1_FILENAME=semantic-money-yellowpaper1 +YELLOWPAPER1_INPUTS = $(YELLOWPAPER_ROOT)/tex \ + $(YELLOWPAPER_ROOT)/tex/MoneyDistribution.lhs.tex \ + $(YELLOWPAPER_ROOT)/tex/FinancialContract.lhs.tex \ + $(YELLOWPAPER_ROOT)/tex/PaymentExecutionEnvironment.lhs.tex \ + $(YELLOWPAPER_ROOT)/tex/MoneyMedium.lhs.tex \ + $(YELLOWPAPER_ROOT)/tex/PaymentPrimitives.lhs.tex \ $(YELLOWPAPER_ROOT)/tex/lhsfmt.tex \ + $(YELLOWPAPER_ROOT)/tex/preamble.tex \ $(YELLOWPAPER_ROOT)/tex/Biblio.bib \ - $(YELLOWPAPER_ROOT)/tex/Paper.tex -$(YELLOWPAPER_ROOT)/pdflatex/superfluid-yellowpaper.pdf: $(YELLOWPAPER_INPUTS) + $(YELLOWPAPER_ROOT)/tex/Paper1.tex +$(YELLOWPAPER_ROOT)/pdflatex/$(YELLOWPAPER1_FILENAME).pdf: $(YELLOWPAPER1_INPUTS) ls $(YELLOWPAPER_ROOT)/tex rm -rf $(YELLOWPAPER_ROOT)/pdflatex mkdir -p $(YELLOWPAPER_ROOT)/pdflatex - cd $(YELLOWPAPER_ROOT)/pdflatex; { \ - pdflatex -shell-escape -jobname=superfluid-yellowpaper ../tex/Paper.tex; \ - biber superfluid-yellowpaper.bcf; \ - pdflatex -shell-escape -jobname=superfluid-yellowpaper ../tex/Paper.tex; \ - } - -docs-yellowpaper: $(YELLOWPAPER_ROOT)/pdflatex/superfluid-yellowpaper.pdf + rm -rf $(YELLOWPAPER_ROOT)/assets + cp -r yellowpaper/assets $(YELLOWPAPER_ROOT)/assets + cp $(YELLOWPAPER_ROOT)/tex/preamble.tex $(YELLOWPAPER_ROOT)/pdflatex/ + cd $(YELLOWPAPER_ROOT)/pdflatex; \ + P=$(YELLOWPAPER1_FILENAME); \ + { \ + pdflatex -shell-escape -jobname=$$P ../tex/Paper1.tex && \ + biber $$P.bcf && \ + pdflatex -shell-escape -jobname=$$P ../tex/Paper1.tex; \ + } + +docs-yellowpapers: $(YELLOWPAPER_ROOT)/pdflatex/$(YELLOWPAPER1_FILENAME).pdf docs: docs-haddock docs-yellowpaper diff --git a/packages/spec-haskell/cabal.project.freeze b/packages/spec-haskell/cabal.project.freeze deleted file mode 100644 index 3c10681fc6..0000000000 --- a/packages/spec-haskell/cabal.project.freeze +++ /dev/null @@ -1,59 +0,0 @@ -active-repositories: hackage.haskell.org:merge -constraints: any.HUnit ==1.6.2.0, - any.QuickCheck ==2.14.2, - QuickCheck -old-random +templatehaskell, - any.ansi-terminal ==0.11.3, - ansi-terminal -example, - any.array ==0.5.4.0, - any.base ==4.16.3.0, - any.binary ==0.8.9.0, - any.bytestring ==0.11.3.1, - any.call-stack ==0.4.0, - any.clock ==0.8.3, - clock -llvm, - any.colour ==2.3.6, - any.containers ==0.6.5.1, - any.data-default ==0.7.1.1, - any.data-default-class ==0.1.2.0, - any.data-default-instances-containers ==0.0.1, - any.data-default-instances-dlist ==0.0.1, - any.data-default-instances-old-locale ==0.0.1, - any.deepseq ==1.4.6.1, - any.directory ==1.3.6.2, - any.dlist ==1.0, - dlist -werror, - any.exceptions ==0.10.4, - any.filepath ==1.4.2.2, - any.ghc ==9.2.4, - any.ghc-bignum ==1.2, - any.ghc-boot ==9.2.4, - any.ghc-boot-th ==9.2.4, - any.ghc-heap ==9.2.4, - any.ghc-prim ==0.8.0, - any.ghci ==9.2.4, - any.hpc ==0.6.1.0, - any.hspec ==2.10.0.1, - any.hspec-core ==2.10.0.1, - any.hspec-discover ==2.10.0.1, - any.hspec-expectations ==0.8.2, - any.math-extras ==0.1.1.0, - any.microlens ==0.4.13.0, - any.mtl ==2.2.2, - any.old-locale ==1.0.0.7, - any.pretty ==1.1.3.6, - any.primitive ==0.7.4.0, - any.process ==1.6.13.2, - any.quickcheck-io ==0.2.0, - any.random ==1.2.1.1, - any.rts ==1.0.2, - any.setenv ==0.1.1.3, - any.splitmix ==0.1.0.4, - splitmix -optimised-mixer, - any.stm ==2.5.0.2, - any.template-haskell ==2.18.0.0, - any.terminfo ==0.4.1.5, - any.tf-random ==0.5, - any.time ==1.11.1.1, - any.transformers ==0.5.6.2, - any.unix ==2.7.2.2 -index-state: hackage.haskell.org 2022-08-12T21:03:11Z diff --git a/packages/spec-haskell/packages/core/src-internal b/packages/spec-haskell/packages/core/src-internal new file mode 120000 index 0000000000..d76e8f02d5 --- /dev/null +++ b/packages/spec-haskell/packages/core/src-internal @@ -0,0 +1 @@ +../src-internal \ No newline at end of file diff --git a/packages/spec-haskell/packages/core/src-internal/Lens/Internal.hs b/packages/spec-haskell/packages/core/src-internal/Lens/Internal.hs deleted file mode 100644 index ee14a7122f..0000000000 --- a/packages/spec-haskell/packages/core/src-internal/Lens/Internal.hs +++ /dev/null @@ -1,29 +0,0 @@ -{-# LANGUAGE TemplateHaskell #-} - -module Lens.Internal - ( module Lens.Micro - , module Lens.Micro.Extras - , readOnlyLens - , field - ) where - -import Language.Haskell.TH -import Lens.Micro -import Lens.Micro.Extras - --- | A short hand for creating a read only lens -readOnlyLens :: (s -> a) -> Lens s t a b -readOnlyLens g = lens g (error "setting a read only lens") - --- | Make a lens from a field name. --- --- Example: @over $(field 'foo) (*2)@ --- NOTE: Copied from basic-lens -field :: Name -> Q Exp -field name = do - [|\f r -> - fmap - $(lamE - [varP (mkName "a")] - (recUpdE (varE (mkName "r")) [return (name, VarE (mkName "a"))])) - (f ($(varE name) r))|] diff --git a/packages/spec-haskell/packages/core/src/Money/Systems/Communism.lhs b/packages/spec-haskell/packages/core/src/Money/Systems/Communism.lhs deleted file mode 100644 index 1da889dd47..0000000000 --- a/packages/spec-haskell/packages/core/src/Money/Systems/Communism.lhs +++ /dev/null @@ -1,57 +0,0 @@ -Communism comes in various forms, and all have its own money distribution "system". - -\ignore{ -\begin{code} -module Money.Systems.Communism where - -import Money.Theory.Distribution -\end{code} -} - -\lhsparagraph{Utopian Communism} - -In its utopian form, there is no more scarcity, but the number of its members are finite. Hence the any meaningful monetary -unit would also come with unlimited value. - -\begin{code} -data Infinite a = Infinite | Only a - -data UtopianCommunism - -instance Distribution UtopianCommunism where - monetaryUnits = undefined - bearerOf = undefined - valueOf = undefined -\end{code} - -\lhsparagraph{Dystopian Communism} - -In its more likely form, there is simply nothing, and everyone owns nothing. - -\begin{code} -data DystopianCommunism - -instance Distribution DystopianCommunism where - monetaryUnits = undefined - bearerOf = undefined - valueOf = undefined -\end{code} - -\lhsparagraph{Autocratic Communism} - -In a more realistic form, the one that determines the value amount of each monetary unit is at the mercy of the side -effect of the monadic autocrat. - -\begin{code} -data AutocraticCommunism - -instance Distribution AutocraticCommunism where - monetaryUnits = undefined - bearerOf = undefined - valueOf = undefined -\end{code} - -\lhsparagraph{Communism Bad} - -In colcusion, as it has been demonstrated, all forms of communism are not workable as it's either neither functional nor -pure money redistribution mechanism. diff --git a/packages/spec-haskell/packages/core/src/Money/Systems/Superfluid/Agreements/Indexes/ProportionalDistributionCommon.hs b/packages/spec-haskell/packages/core/src/Money/Systems/Superfluid/Agreements/ProportionalDistribution/Common.hs similarity index 87% rename from packages/spec-haskell/packages/core/src/Money/Systems/Superfluid/Agreements/Indexes/ProportionalDistributionCommon.hs rename to packages/spec-haskell/packages/core/src/Money/Systems/Superfluid/Agreements/ProportionalDistribution/Common.hs index 53096e069a..7d2786fe34 100644 --- a/packages/spec-haskell/packages/core/src/Money/Systems/Superfluid/Agreements/Indexes/ProportionalDistributionCommon.hs +++ b/packages/spec-haskell/packages/core/src/Money/Systems/Superfluid/Agreements/ProportionalDistribution/Common.hs @@ -1,6 +1,6 @@ {-# LANGUAGE DeriveAnyClass #-} -module Money.Systems.Superfluid.Agreements.Indexes.ProportionalDistributionCommon where +module Money.Systems.Superfluid.Agreements.ProportionalDistribution.Common where import Data.Default import GHC.Generics diff --git a/packages/spec-haskell/packages/core/src/Money/Systems/Superfluid/Agreements/ConstantFlowDistributionAgreement.hs b/packages/spec-haskell/packages/core/src/Money/Systems/Superfluid/Agreements/ProportionalDistribution/ConstantFlowDistributionAgreement.hs similarity index 88% rename from packages/spec-haskell/packages/core/src/Money/Systems/Superfluid/Agreements/ConstantFlowDistributionAgreement.hs rename to packages/spec-haskell/packages/core/src/Money/Systems/Superfluid/Agreements/ProportionalDistribution/ConstantFlowDistributionAgreement.hs index 6de5ffda6b..371b5cf815 100644 --- a/packages/spec-haskell/packages/core/src/Money/Systems/Superfluid/Agreements/ConstantFlowDistributionAgreement.hs +++ b/packages/spec-haskell/packages/core/src/Money/Systems/Superfluid/Agreements/ProportionalDistribution/ConstantFlowDistributionAgreement.hs @@ -8,7 +8,7 @@ -- It is instant transferring over an proportional distribution index -- -- This module is typically imported using qualified name CFDA. -module Money.Systems.Superfluid.Agreements.ConstantFlowDistributionAgreement where +module Money.Systems.Superfluid.Agreements.ProportionalDistribution.ConstantFlowDistributionAgreement where import Data.Default import Data.Type.Any @@ -18,8 +18,8 @@ import Lens.Internal import Money.Systems.Superfluid.SystemTypes -- -import Money.Systems.Superfluid.Agreements.Indexes.ProportionalDistributionCommon -import qualified Money.Systems.Superfluid.MonetaryUnitData.ConstantFlow as CFMUD +import Money.Systems.Superfluid.Agreements.ProportionalDistribution.Common +import qualified Money.Systems.Superfluid.MonetaryUnitData.ConstantFlow as CFMUD -- * Contracts @@ -63,9 +63,9 @@ type SubscriberData sft = SubscriberContract sft type SubscriberMonetaryUnitData sft = CFMUD.MonetaryUnitData (SubscriberData sft) sft instance SuperfluidSystemTypes sft => CFMUD.MonetaryUnitLenses (PublisherData sft) sft where - settledAt = $(field 'pub_settled_at) - settledValue = $(field 'pub_settled_value) - netFlowRate = $(field 'pub_total_flow_rate) + settledAt = $(field 'pub_settled_at) + settledValue = $(field 'pub_settled_value) + netFlowRate = $(field 'pub_total_flow_rate) instance SuperfluidSystemTypes sft => CFMUD.MonetaryUnitLenses (SubscriberData sft) sft where settledAt = readOnlyLens @@ -75,7 +75,7 @@ instance SuperfluidSystemTypes sft => CFMUD.MonetaryUnitLenses (SubscriberData s netFlowRate = readOnlyLens (\(( DistributionContractBase { total_unit = tu } - , DistributionContract { dc_flow_rate = dcfr }), + , DistributionContract { dc_flow_rate = dcfr }), ( SubscriptionContractBase { sub_owned_unit = u } , _)) -> if tu /= 0 then floor $ fromIntegral dcfr * u / tu else 0) @@ -123,9 +123,6 @@ instance SuperfluidSystemTypes sft => AgreementContract (PublisherContract sft) , dc_flow_rate = dcfr } = dc - concatAgreementOperationOutput (PublisherOperationOutputF a) (PublisherOperationOutputF a') = - PublisherOperationOutputF (a <> a') - functorizeAgreementOperationOutput p = fmap (mkAny p) data AgreementOperation (PublisherContract sft) = UpdateDistributionFlowRate (SFT_MVAL sft) @@ -140,6 +137,9 @@ type PublisherOperationOutput sft = AgreementOperationOutputF (PublisherContract (PublisherMonetaryUnitData sft) instance SuperfluidSystemTypes sft => Default (PublisherOperationOutput sft) +instance SuperfluidSystemTypes sft => Monoid (PublisherOperationOutput sft) where mempty = def +instance SuperfluidSystemTypes sft => Semigroup (PublisherOperationOutput sft) where + PublisherOperationOutputF a <> PublisherOperationOutputF a' = PublisherOperationOutputF (a <> a') -- * Subscriber Operations @@ -178,9 +178,6 @@ instance SuperfluidSystemTypes sft => AgreementContract (SubscriberContract sft) , sc_settled_value_per_unit = svpu } = sc - concatAgreementOperationOutput (SubscriberOperationOutputF a) (SubscriberOperationOutputF a') = - SubscriberOperationOutputF (a <> a') - functorizeAgreementOperationOutput p = fmap (mkAny p) data AgreementOperation (SubscriberContract sft) = SettleSubscription @@ -195,3 +192,6 @@ type SubscriberOperationOutput sft = AgreementOperationOutputF (SubscriberContra (PublisherMonetaryUnitData sft) instance SuperfluidSystemTypes sft => Default (SubscriberOperationOutput sft) +instance SuperfluidSystemTypes sft => Monoid (SubscriberOperationOutput sft) where mempty = def +instance SuperfluidSystemTypes sft => Semigroup (SubscriberOperationOutput sft) where + SubscriberOperationOutputF a <> SubscriberOperationOutputF a' = SubscriberOperationOutputF (a <> a') diff --git a/packages/spec-haskell/packages/core/src/Money/Systems/Superfluid/Agreements/InstantDistributionAgreement.hs b/packages/spec-haskell/packages/core/src/Money/Systems/Superfluid/Agreements/ProportionalDistribution/InstantDistributionAgreement.hs similarity index 84% rename from packages/spec-haskell/packages/core/src/Money/Systems/Superfluid/Agreements/InstantDistributionAgreement.hs rename to packages/spec-haskell/packages/core/src/Money/Systems/Superfluid/Agreements/ProportionalDistribution/InstantDistributionAgreement.hs index fe9fdd3226..83ca8912dc 100644 --- a/packages/spec-haskell/packages/core/src/Money/Systems/Superfluid/Agreements/InstantDistributionAgreement.hs +++ b/packages/spec-haskell/packages/core/src/Money/Systems/Superfluid/Agreements/ProportionalDistribution/InstantDistributionAgreement.hs @@ -8,7 +8,7 @@ -- It is instant transferring over an proportional distribution index -- -- This module is typically imported using qualified name . -module Money.Systems.Superfluid.Agreements.InstantDistributionAgreement where +module Money.Systems.Superfluid.Agreements.ProportionalDistribution.InstantDistributionAgreement where import Data.Coerce import Data.Default @@ -18,8 +18,8 @@ import Lens.Internal import Money.Systems.Superfluid.SystemTypes -- -import Money.Systems.Superfluid.Agreements.Indexes.ProportionalDistributionCommon -import qualified Money.Systems.Superfluid.MonetaryUnitData.InstantValue as IVMUD +import Money.Systems.Superfluid.Agreements.ProportionalDistribution.Common +import qualified Money.Systems.Superfluid.MonetaryUnitData.InstantValue as IVMUD -- * Contracts @@ -91,23 +91,23 @@ instance SuperfluidSystemTypes sft => AgreementContract (PublisherContract sft) where DistributionContractBase { total_unit = tu} = dcBase DistributionContract { dc_value_per_unit = vpu } = dc - concatAgreementOperationOutput (PublisherOperationOutputF a) (PublisherOperationOutputF a') = - PublisherOperationOutputF (a <> a') - functorizeAgreementOperationOutput p = fmap (mkAny p) data AgreementOperation (PublisherContract sft) = Distribute (SFT_MVAL sft) - type AgreementOperationOutput (PublisherContract sft) = PublisherOperationOutputF sft + type AgreementOperationOutput (PublisherContract sft) = PublisherOperationOutput sft data AgreementOperationOutputF (PublisherContract sft) elem = PublisherOperationOutputF elem -- publisher deriving stock (Functor, Foldable, Traversable, Generic) -type PublisherOperationOutputF sft = AgreementOperationOutputF (PublisherContract sft) +type PublisherOperationOutput sft = AgreementOperationOutputF (PublisherContract sft) (PublisherMonetaryUnitData sft) -instance SuperfluidSystemTypes sft => Default (PublisherOperationOutputF sft) +instance SuperfluidSystemTypes sft => Default (PublisherOperationOutput sft) +instance SuperfluidSystemTypes sft => Monoid (PublisherOperationOutput sft) where mempty = def +instance SuperfluidSystemTypes sft => Semigroup (PublisherOperationOutput sft) where + PublisherOperationOutputF a <> PublisherOperationOutputF a' = PublisherOperationOutputF (a <> a') -- * Subscriber Operations @@ -133,17 +133,17 @@ instance SuperfluidSystemTypes sft => AgreementContract (SubscriberContract sft) , sc_settled_value_per_unit = svpu } = sc - concatAgreementOperationOutput _ a = a - functorizeAgreementOperationOutput _ _ = SubscriberOperationOutputF data AgreementOperation (SubscriberContract sft) = SettleSubscription - type AgreementOperationOutput (SubscriberContract sft) = SubscriberOperationOutputF sft + type AgreementOperationOutput (SubscriberContract sft) = SubscriberOperationOutput sft data AgreementOperationOutputF (SubscriberContract sft) _ = SubscriberOperationOutputF deriving stock (Functor, Foldable, Traversable, Generic) -type SubscriberOperationOutputF sft = AgreementOperationOutputF (SubscriberContract sft) () +type SubscriberOperationOutput sft = AgreementOperationOutputF (SubscriberContract sft) () -instance SuperfluidSystemTypes sft => Default (SubscriberOperationOutputF sft) +instance SuperfluidSystemTypes sft => Default (SubscriberOperationOutput sft) +instance SuperfluidSystemTypes sft => Monoid (SubscriberOperationOutput sft) where mempty = def +instance SuperfluidSystemTypes sft => Semigroup (SubscriberOperationOutput sft) where (<>) = const diff --git a/packages/spec-haskell/packages/core/src/Money/Systems/Superfluid/Agreements/Indexes/ProportionalDistributionIndex.hs b/packages/spec-haskell/packages/core/src/Money/Systems/Superfluid/Agreements/ProportionalDistributionIndex.hs similarity index 80% rename from packages/spec-haskell/packages/core/src/Money/Systems/Superfluid/Agreements/Indexes/ProportionalDistributionIndex.hs rename to packages/spec-haskell/packages/core/src/Money/Systems/Superfluid/Agreements/ProportionalDistributionIndex.hs index d107f55144..3a94f59e97 100644 --- a/packages/spec-haskell/packages/core/src/Money/Systems/Superfluid/Agreements/Indexes/ProportionalDistributionIndex.hs +++ b/packages/spec-haskell/packages/core/src/Money/Systems/Superfluid/Agreements/ProportionalDistributionIndex.hs @@ -3,7 +3,7 @@ {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TypeFamilies #-} -module Money.Systems.Superfluid.Agreements.Indexes.ProportionalDistributionIndex where +module Money.Systems.Superfluid.Agreements.ProportionalDistributionIndex where import Data.Default import Data.Type.Any @@ -12,9 +12,9 @@ import Lens.Internal import Money.Systems.Superfluid.SystemTypes -- -import qualified Money.Systems.Superfluid.Agreements.ConstantFlowDistributionAgreement as CFDA -import Money.Systems.Superfluid.Agreements.Indexes.ProportionalDistributionCommon -import qualified Money.Systems.Superfluid.Agreements.InstantDistributionAgreement as IDA +import Money.Systems.Superfluid.Agreements.ProportionalDistribution.Common +import qualified Money.Systems.Superfluid.Agreements.ProportionalDistribution.ConstantFlowDistributionAgreement as CFDA +import qualified Money.Systems.Superfluid.Agreements.ProportionalDistribution.InstantDistributionAgreement as IDA -- * Contracts @@ -100,21 +100,26 @@ instance SuperfluidSystemTypes sft => AgreementContract (SubscriberContract sft) , sub_settled_at = t' }}) - in (ac', cfdaMUDΔ) + in (ac', SubscriberOperationOutput cfdaMUDΔ) where DistributionContract { dc_base = DistributionContractBase { total_unit = tu }} = dc0 SubscriptionContract { sc_base = SubscriptionContractBase { sub_owned_unit = u }} = sc0 - concatAgreementOperationOutput cfda cfda' = cfda <> cfda' - - functorizeAgreementOperationOutput p cfda = SubscriberOperationOutputF - (mkAny p cfda) + functorizeAgreementOperationOutput p (SubscriberOperationOutput cfda) = + SubscriberOperationOutputF (mkAny p cfda) data AgreementOperation (SubscriberContract sft) = Subscribe (SFT_FLOAT sft) - type AgreementOperationOutput (SubscriberContract sft) = - (CFDA.PublisherMonetaryUnitData sft) + type AgreementOperationOutput (SubscriberContract sft) = SubscriberOperationOutput sft data AgreementOperationOutputF (SubscriberContract sft) elem = SubscriberOperationOutputF { subscription_output_cfda :: elem } deriving stock (Functor, Foldable, Traversable) + +data SubscriberOperationOutput sft = SubscriberOperationOutput + (CFDA.PublisherMonetaryUnitData sft) + deriving stock (Generic) + +instance SuperfluidSystemTypes sft => Default (SubscriberOperationOutput sft) +instance SuperfluidSystemTypes sft => Monoid (SubscriberOperationOutput sft) where mempty = def +instance SuperfluidSystemTypes sft => Semigroup (SubscriberOperationOutput sft) where (<>) = const diff --git a/packages/spec-haskell/packages/core/src/Money/Systems/Superfluid/Agreements/ConstantFlowAgreement.hs b/packages/spec-haskell/packages/core/src/Money/Systems/Superfluid/Agreements/Universal/ConstantFlowAgreement.hs similarity index 84% rename from packages/spec-haskell/packages/core/src/Money/Systems/Superfluid/Agreements/ConstantFlowAgreement.hs rename to packages/spec-haskell/packages/core/src/Money/Systems/Superfluid/Agreements/Universal/ConstantFlowAgreement.hs index d9af50f6b9..f26d95ea76 100644 --- a/packages/spec-haskell/packages/core/src/Money/Systems/Superfluid/Agreements/ConstantFlowAgreement.hs +++ b/packages/spec-haskell/packages/core/src/Money/Systems/Superfluid/Agreements/Universal/ConstantFlowAgreement.hs @@ -6,7 +6,7 @@ -- | Constant flow agreement. -- -- This module is typically imported using qualified name CFA. -module Money.Systems.Superfluid.Agreements.ConstantFlowAgreement where +module Money.Systems.Superfluid.Agreements.Universal.ConstantFlowAgreement where import Data.Default import Data.Type.Any @@ -70,20 +70,20 @@ instance SuperfluidSystemTypes sft => AgreementContract (ContractData sft) sft w & set CFMUD.netFlowRate flowRateΔ) in (ac', fmap CFMUD.MkMonetaryUnitData mudsΔ) - concatAgreementOperationOutput (OperationOutputF a b) (OperationOutputF a' b') = - OperationOutputF (a <> a') (b <> b') - functorizeAgreementOperationOutput p = fmap (mkAny p) data AgreementOperation (ContractData sft) = UpdateFlow (FlowRate sft) - type AgreementOperationOutput (ContractData sft) = OperationOutputF sft + type AgreementOperationOutput (ContractData sft) = OperationOutput sft data AgreementOperationOutputF (ContractData sft) a = OperationOutputF { flow_sender :: a , flow_receiver :: a } deriving stock (Functor, Foldable, Traversable, Generic) -type OperationOutputF sft = AgreementOperationOutputF (ContractData sft) (MonetaryUnitData sft) +type OperationOutput sft = AgreementOperationOutputF (ContractData sft) (MonetaryUnitData sft) -instance SuperfluidSystemTypes sft => Default (OperationOutputF sft) +instance SuperfluidSystemTypes sft => Default (OperationOutput sft) +instance SuperfluidSystemTypes sft => Monoid (OperationOutput sft) where mempty = def +instance SuperfluidSystemTypes sft => Semigroup (OperationOutput sft) where + OperationOutputF a b <> OperationOutputF a' b' = OperationOutputF (a <> a') (b <> b') diff --git a/packages/spec-haskell/packages/core/src/Money/Systems/Superfluid/Agreements/DecayingFlowAgreement.hs b/packages/spec-haskell/packages/core/src/Money/Systems/Superfluid/Agreements/Universal/DecayingFlowAgreement.hs similarity index 83% rename from packages/spec-haskell/packages/core/src/Money/Systems/Superfluid/Agreements/DecayingFlowAgreement.hs rename to packages/spec-haskell/packages/core/src/Money/Systems/Superfluid/Agreements/Universal/DecayingFlowAgreement.hs index 9a188d7945..23c4c9f308 100644 --- a/packages/spec-haskell/packages/core/src/Money/Systems/Superfluid/Agreements/DecayingFlowAgreement.hs +++ b/packages/spec-haskell/packages/core/src/Money/Systems/Superfluid/Agreements/Universal/DecayingFlowAgreement.hs @@ -6,7 +6,7 @@ -- | Decaying flow agreement. -- -- This module is typically imported using qualified name . -module Money.Systems.Superfluid.Agreements.DecayingFlowAgreement where +module Money.Systems.Superfluid.Agreements.Universal.DecayingFlowAgreement where import Data.Default import Data.Proxy @@ -61,8 +61,8 @@ instance SuperfluidSystemTypes sft => AgreementContract (ContractData sft) sft w θ_Δ = fromIntegral (θ - distribution_limit ac) ac' = ContractData { distribution_limit = θ - , flow_last_updated_at = t' - } + , flow_last_updated_at = t' + } mudsΔ = OperationOutputF (def & set DFMUD.settledAt t' & set DFMUD.αVal θ_Δ @@ -73,21 +73,21 @@ instance SuperfluidSystemTypes sft => AgreementContract (ContractData sft) sft w in (ac', fmap DFMUD.MkMonetaryUnitData mudsΔ) - concatAgreementOperationOutput (OperationOutputF a b) (OperationOutputF a' b') = - OperationOutputF (a <> a') (b <> b') - functorizeAgreementOperationOutput p = fmap (mkAny p) data AgreementOperation (ContractData sft) = UpdateDecayingFlow (DistributionLimit sft) - type AgreementOperationOutput (ContractData sft) = OperationOutputF sft + type AgreementOperationOutput (ContractData sft) = OperationOutput sft data AgreementOperationOutputF (ContractData sft) elem = OperationOutputF { flow_sender :: elem , flow_receiver :: elem } deriving stock (Functor, Foldable, Traversable, Generic) -type OperationOutputF sft = AgreementOperationOutputF (ContractData sft) (MonetaryUnitData sft) +type OperationOutput sft = AgreementOperationOutputF (ContractData sft) (MonetaryUnitData sft) -instance SuperfluidSystemTypes sft => Default (OperationOutputF sft) +instance SuperfluidSystemTypes sft => Default (OperationOutput sft) +instance SuperfluidSystemTypes sft => Monoid (OperationOutput sft) where mempty = def +instance SuperfluidSystemTypes sft => Semigroup (OperationOutput sft) where + OperationOutputF a b <> OperationOutputF a' b' = OperationOutputF (a <> a') (b <> b') diff --git a/packages/spec-haskell/packages/core/src/Money/Systems/Superfluid/Agreements/InstantTransferAgreement.hs b/packages/spec-haskell/packages/core/src/Money/Systems/Superfluid/Agreements/Universal/InstantTransferAgreement.hs similarity index 81% rename from packages/spec-haskell/packages/core/src/Money/Systems/Superfluid/Agreements/InstantTransferAgreement.hs rename to packages/spec-haskell/packages/core/src/Money/Systems/Superfluid/Agreements/Universal/InstantTransferAgreement.hs index 47f90a3eb7..bf5b4e3fe3 100644 --- a/packages/spec-haskell/packages/core/src/Money/Systems/Superfluid/Agreements/InstantTransferAgreement.hs +++ b/packages/spec-haskell/packages/core/src/Money/Systems/Superfluid/Agreements/Universal/InstantTransferAgreement.hs @@ -6,7 +6,7 @@ -- | Instant transferring agreement. -- -- This module is typically imported using qualified name ITA. -module Money.Systems.Superfluid.Agreements.InstantTransferAgreement where +module Money.Systems.Superfluid.Agreements.Universal.InstantTransferAgreement where import Data.Coerce import Data.Default @@ -50,20 +50,20 @@ instance SuperfluidSystemTypes sft => AgreementContract (ContractData sft) sft w (def & set IVMUD.untappedValue (coerce amount))) in (ac', mudsΔ) - concatAgreementOperationOutput (OperationOutputF a b) (OperationOutputF a' b') = - OperationOutputF (a <> a') (b <> b') - functorizeAgreementOperationOutput p = fmap (mkAny p) data AgreementOperation (ContractData sft) = Transfer (SFT_MVAL sft) - type AgreementOperationOutput (ContractData sft) = OperationOutputF sft + type AgreementOperationOutput (ContractData sft) = OperationOutput sft data AgreementOperationOutputF (ContractData sft) elem = OperationOutputF { transfer_from :: elem , transfer_to :: elem } deriving stock (Functor, Foldable, Traversable, Generic) -type OperationOutputF sft = AgreementOperationOutputF (ContractData sft) (MonetaryUnitData sft) +type OperationOutput sft = AgreementOperationOutputF (ContractData sft) (MonetaryUnitData sft) -instance SuperfluidSystemTypes sft => Default (OperationOutputF sft) +instance SuperfluidSystemTypes sft => Default (OperationOutput sft) +instance SuperfluidSystemTypes sft => Monoid (OperationOutput sft) where mempty = def +instance SuperfluidSystemTypes sft => Semigroup (OperationOutput sft) where + OperationOutputF a b <> OperationOutputF a' b' = OperationOutputF (a <> a') (b <> b') diff --git a/packages/spec-haskell/packages/core/src/Money/Systems/Superfluid/Agreements/MinterAgreement.hs b/packages/spec-haskell/packages/core/src/Money/Systems/Superfluid/Agreements/Universal/MinterAgreement.hs similarity index 84% rename from packages/spec-haskell/packages/core/src/Money/Systems/Superfluid/Agreements/MinterAgreement.hs rename to packages/spec-haskell/packages/core/src/Money/Systems/Superfluid/Agreements/Universal/MinterAgreement.hs index 08b436ff85..770e325441 100644 --- a/packages/spec-haskell/packages/core/src/Money/Systems/Superfluid/Agreements/MinterAgreement.hs +++ b/packages/spec-haskell/packages/core/src/Money/Systems/Superfluid/Agreements/Universal/MinterAgreement.hs @@ -6,7 +6,7 @@ -- | Instant transferring agreement. -- -- This module is typically imported using qualified name MINTA. -module Money.Systems.Superfluid.Agreements.MinterAgreement where +module Money.Systems.Superfluid.Agreements.Universal.MinterAgreement where import Data.Coerce (coerce) import Data.Default @@ -59,21 +59,21 @@ instance SuperfluidSystemTypes sft => AgreementContract (ContractData sft) sft w (def & set MVMUD.untappedValue (coerce (- amount)))) in (ac', mudsΔ) - concatAgreementOperationOutput (OperationOutputF a b) (OperationOutputF a' b') = - OperationOutputF (a <> a') (b <> b') - functorizeAgreementOperationOutput p = fmap (mkAny p) data AgreementOperation (ContractData sft) = Mint (SFT_MVAL sft) | Burn (SFT_MVAL sft) - type AgreementOperationOutput (ContractData sft) = OperationOutputF sft + type AgreementOperationOutput (ContractData sft) = OperationOutput sft data AgreementOperationOutputF (ContractData sft) elem = OperationOutputF { mint_from :: elem , mint_to :: elem } deriving stock (Functor, Foldable, Traversable, Generic) -type OperationOutputF sft = AgreementOperationOutputF (ContractData sft) (MonetaryUnitData sft) +type OperationOutput sft = AgreementOperationOutputF (ContractData sft) (MonetaryUnitData sft) -instance SuperfluidSystemTypes sft => Default (OperationOutputF sft) +instance SuperfluidSystemTypes sft => Default (OperationOutput sft) +instance SuperfluidSystemTypes sft => Monoid (OperationOutput sft) where mempty = def +instance SuperfluidSystemTypes sft => Semigroup (OperationOutput sft) where + OperationOutputF a b <> OperationOutputF a' b' = OperationOutputF (a <> a') (b <> b') diff --git a/packages/spec-haskell/packages/core/src/Money/Systems/Superfluid/Agreements/Indexes/UniversalIndex.hs b/packages/spec-haskell/packages/core/src/Money/Systems/Superfluid/Agreements/UniversalIndex.hs similarity index 72% rename from packages/spec-haskell/packages/core/src/Money/Systems/Superfluid/Agreements/Indexes/UniversalIndex.hs rename to packages/spec-haskell/packages/core/src/Money/Systems/Superfluid/Agreements/UniversalIndex.hs index 3ab39cc064..526ddc9fae 100644 --- a/packages/spec-haskell/packages/core/src/Money/Systems/Superfluid/Agreements/Indexes/UniversalIndex.hs +++ b/packages/spec-haskell/packages/core/src/Money/Systems/Superfluid/Agreements/UniversalIndex.hs @@ -1,7 +1,7 @@ {-# LANGUAGE DeriveAnyClass #-} {-# LANGUAGE TemplateHaskell #-} -module Money.Systems.Superfluid.Agreements.Indexes.UniversalIndex where +module Money.Systems.Superfluid.Agreements.UniversalIndex where import Data.Default import GHC.Generics @@ -9,10 +9,10 @@ import Lens.Internal import Money.Systems.Superfluid.SystemTypes -- -import qualified Money.Systems.Superfluid.Agreements.ConstantFlowAgreement as CFA -import qualified Money.Systems.Superfluid.Agreements.DecayingFlowAgreement as DFA -import qualified Money.Systems.Superfluid.Agreements.InstantTransferAgreement as ITA -import qualified Money.Systems.Superfluid.Agreements.MinterAgreement as MINTA +import qualified Money.Systems.Superfluid.Agreements.Universal.ConstantFlowAgreement as CFA +import qualified Money.Systems.Superfluid.Agreements.Universal.DecayingFlowAgreement as DFA +import qualified Money.Systems.Superfluid.Agreements.Universal.InstantTransferAgreement as ITA +import qualified Money.Systems.Superfluid.Agreements.Universal.MinterAgreement as MINTA -- | This is data that is universally available to the monetary unit. diff --git a/packages/spec-haskell/packages/core/src/Money/Systems/Superfluid/Concepts/Agreement.hs b/packages/spec-haskell/packages/core/src/Money/Systems/Superfluid/Concepts/Agreement.hs index 3c58140348..f73c504f6f 100644 --- a/packages/spec-haskell/packages/core/src/Money/Systems/Superfluid/Concepts/Agreement.hs +++ b/packages/spec-haskell/packages/core/src/Money/Systems/Superfluid/Concepts/Agreement.hs @@ -28,37 +28,36 @@ import Money.Systems.Superfluid.CoreTypes class ( SuperfluidCoreTypes sft , Default ac , MonetaryUnitDataClass ac sft - , Default (AgreementOperationOutput ac) , Traversable (AgreementOperationOutputF ac) -- <= Foldable Functor + , Monoid (AgreementOperationOutput ac) ) => AgreementContract ac sft | ac -> sft where -- | ω function - apply agreement operation ~ao~ (hear: ω) to the agreement operation data ~ac~ to get a tuple of: -- -- 1. An updated ~ac'~. -- - -- 2. A functorful delta of agreement monetary unit data ~mudsΔ~, which then can be monoid-appended to existing - -- ~mudΔ~. This is what can make an agreement scalable. + -- 2. A functorful delta of agreement monetary unit data ~mudsΔ~, which then can be appended to existing ~mudΔ~. + -- This is what can make an agreement scalable. applyAgreementOperation - :: ac -- ac - -> AgreementOperation ac -- ao - -> SFT_TS sft -- t - -> (ac, AgreementOperationOutput ac) -- (ac', mudsΔ) + :: forall t ao aoo. + -- indexed type aliases + ( t ~ SFT_TS sft + , ao ~ AgreementOperation ac + , aoo ~ AgreementOperationOutput ac + ) + => ac -> ao -> t -> (ac, aoo) -- | φ' function - functorize the existential semigroup monetary unit data of agreement operation output functorizeAgreementOperationOutput - :: forall muds f any_smud. - ( muds ~ AgreementOperationOutput ac - , f ~ AgreementOperationOutputF ac + :: forall any_smud muds f. + ( any_smud `IsAnyTypeOf` MPTC_Flip SemigroupMonetaryUnitData sft , MonetaryUnitDataClass any_smud sft - , any_smud `IsAnyTypeOf` MPTC_Flip SemigroupMonetaryUnitData sft + -- indexed type aliases + , muds ~ AgreementOperationOutput ac + , f ~ AgreementOperationOutputF ac ) - => Proxy any_smud -> muds -> f any_smud - - -- | κ function - concatenate agreement operation output, which can be functorized any time. - concatAgreementOperationOutput - :: forall muds. - muds ~ AgreementOperationOutput ac - => muds -> muds -> muds + => Proxy any_smud + -> muds -> f any_smud -- Note: though ~ac~ is injected, but it seems natural to have operation as associated data type instead. data family AgreementOperation ac :: Type @@ -67,7 +66,7 @@ class ( SuperfluidCoreTypes sft data family AgreementOperationOutputF ac :: Type -> Type -- Note: since ~ac~ is injected, hence this can be associated type alias. - type family AgreementOperationOutput ac = (muds :: Type) | muds -> ac + type family AgreementOperationOutput ac = (smuds :: Type) | smuds -> ac -- * Null Agreement @@ -79,7 +78,6 @@ instance SuperfluidCoreTypes sft => MonetaryUnitDataClass (NullAgreementContract instance SuperfluidCoreTypes sft => AgreementContract (NullAgreementContract sft) sft where applyAgreementOperation ac _ _ = (ac, NullAgreementOutoutF) functorizeAgreementOperationOutput _ _ = NullAgreementOutoutF - concatAgreementOperationOutput = const data AgreementOperation (NullAgreementContract sft) = NullAgreementOperation data AgreementOperationOutputF (NullAgreementContract sft) _ = NullAgreementOutoutF deriving stock (Functor, Foldable, Traversable) @@ -88,6 +86,8 @@ instance SuperfluidCoreTypes sft => AgreementContract (NullAgreementContract sft type NullAgreementOutout sft = AgreementOperationOutputF (NullAgreementContract sft) () instance SuperfluidCoreTypes sft => Default (NullAgreementOutout sft) where def = def +instance SuperfluidCoreTypes sft => Monoid (NullAgreementOutout sft) where mempty = def +instance Semigroup (NullAgreementOutout sft) where (<>) = const -- * Agreement Contract State @@ -100,51 +100,54 @@ data AnyAgreementContractState sft = forall ac. AgreementContract ac sft -- ===================================================================================================================== -- * Agreement Laws -ao_sum_contract_balance :: forall sft any_smud. +ao_sum_contract_balance :: forall sft any_smud t rtb any_acs. ( SuperfluidCoreTypes sft - , MonetaryUnitDataClass any_smud sft , any_smud `IsAnyTypeOf` MPTC_Flip SemigroupMonetaryUnitData sft + , MonetaryUnitDataClass any_smud sft + -- indexed type aliases + , t ~ SFT_TS sft + , rtb ~ SFT_RTB sft + , any_acs ~ AnyAgreementContractState sft ) => Proxy any_smud - -> AnyAgreementContractState sft - -> SFT_TS sft - -> SFT_RTB sft + -> any_acs -> t -> rtb ao_sum_contract_balance p (MkAnyAgreementContractState (ac, muds)) t = foldr (<>) (π₂ t ac) (fmap (π₁ t) (φ muds)) where φ = functorizeAgreementOperationOutput p π₁ = flip balanceProvided -- π function (flipped) for semigroup mud π₂ = flip balanceProvided -- π function (flipped) for contract mud -ao_go_single_op :: forall ac sft. +ao_go_single_op :: forall ac sft t ao aoo acs. ( SuperfluidCoreTypes sft , AgreementContract ac sft + -- indexed type aliases + , t ~ SFT_TS sft + , ao ~ AgreementOperation ac + , aoo ~ AgreementOperationOutput ac + , acs ~ (ac, aoo) ) - => AgreementContractState ac sft - -> AgreementOperation ac - -> SFT_TS sft - -> AgreementContractState ac sft + => acs -> ao -> t -> acs ao_go_single_op (ac, muds) ao t' = let (ac', mudsΔ) = ω ac ao t' - muds' = κ muds mudsΔ + muds' = muds <> mudsΔ in (ac', muds') where ω = applyAgreementOperation - κ = concatAgreementOperationOutput -ao_go_zero_sum_balance_after_single_op :: forall ac sft any_smud. +ao_go_zero_sum_balance_after_single_op :: forall ac sft any_smud t v ao aoo acs any_acs. ( SuperfluidCoreTypes sft , AgreementContract ac sft + , any_smud `IsAnyTypeOf` MPTC_Flip SemigroupMonetaryUnitData sft , MonetaryUnitDataClass any_smud sft - , any_smud - `IsAnyTypeOf` - MPTC_Flip SemigroupMonetaryUnitData sft + -- indexed type aliases + , t ~ SFT_TS sft + , v ~ SFT_MVAL sft + , ao ~ AgreementOperation ac + , aoo ~ AgreementOperationOutput ac + , acs ~ (ac, aoo) + , any_acs ~ AnyAgreementContractState sft ) => Proxy any_smud - -> (SFT_MVAL sft -> SFT_MVAL sft -> Bool) - -> AgreementContractState ac sft - -> AgreementOperation ac - -> [AnyAgreementContractState sft] - -> SFT_TS sft - -> (Bool, AgreementContractState ac sft) + -> (v -> v -> Bool) -> acs -> ao -> [any_acs] -> t -> (Bool, acs) ao_go_zero_sum_balance_after_single_op p mvalEq cs ao ocss t' = let cs' = ao_go_single_op cs ao t' in (σ cs `mvalEq` b && σ cs' `mvalEq` b, cs') diff --git a/packages/spec-haskell/packages/core/src/Money/Systems/Superfluid/Concepts/MonetaryUnitData.hs b/packages/spec-haskell/packages/core/src/Money/Systems/Superfluid/Concepts/MonetaryUnitData.hs index 7a1752f73d..da02f83005 100644 --- a/packages/spec-haskell/packages/core/src/Money/Systems/Superfluid/Concepts/MonetaryUnitData.hs +++ b/packages/spec-haskell/packages/core/src/Money/Systems/Superfluid/Concepts/MonetaryUnitData.hs @@ -1,4 +1,5 @@ {-# LANGUAGE FunctionalDependencies #-} +{-# LANGUAGE GADTs #-} module Money.Systems.Superfluid.Concepts.MonetaryUnitData ( MonetaryUnitDataClass (..) @@ -15,9 +16,12 @@ class ( SuperfluidCoreTypes sft ) => MonetaryUnitDataClass mud sft | mud -> sft where -- | π function - balance provided (hear: π) by the monetary unit data. balanceProvided - :: mud -- mud - -> SFT_TS sft -- t - -> SFT_RTB sft -- rtb + :: forall t rtb. + -- indexed type aliases + ( t ~ SFT_TS sft + , rtb ~ SFT_RTB sft + ) + => mud -> t -> rtb -- A default implementation that always returns zero rtb balanceProvided _ _ = mempty @@ -36,9 +40,12 @@ class ( MonetaryUnitDataClass smud sft -- * Semigroup Monetary Unit Data Laws -- | A semigroup binary operation should settle mud in a way that pi function output stay the same. -mud_prop_semigroup_settles_pi :: ( SuperfluidCoreTypes sft +mud_prop_semigroup_settles_pi :: forall sft mud t. + ( SuperfluidCoreTypes sft , SemigroupMonetaryUnitData mud sft + -- indexed type aliases + , t ~ SFT_TS sft ) - => mud -> mud -> SFT_TS sft -> Bool + => mud -> mud -> t -> Bool mud_prop_semigroup_settles_pi m m' t = π m t <> π m' t == π (m <> m') t where π = balanceProvided diff --git a/packages/spec-haskell/packages/core/src/Money/Systems/Superfluid/CoreTypes.hs b/packages/spec-haskell/packages/core/src/Money/Systems/Superfluid/CoreTypes.hs index 94706aa85f..e25f7d1b82 100644 --- a/packages/spec-haskell/packages/core/src/Money/Systems/Superfluid/CoreTypes.hs +++ b/packages/spec-haskell/packages/core/src/Money/Systems/Superfluid/CoreTypes.hs @@ -4,7 +4,6 @@ module Money.Systems.Superfluid.CoreTypes ( module Money.Systems.Superfluid.CoreTypes.TypedValue , module Money.Systems.Superfluid.CoreTypes.RealTimeBalance , SFTFloat - , Timestamp , RealTimeBalance (..) , SuperfluidCoreTypes (..) , SFT_RTB @@ -21,13 +20,6 @@ import Money.Systems.Superfluid.CoreTypes.TypedValue -- | Superfluid float type. TODO naming? class (Default fr, RealFloat fr) => SFTFloat fr --- | Timestamp type class. --- --- Notional conventions: --- * Type name: ts --- * SuperfluidTypes type indexer: SFT_TS -class (Default ts, Integral ts) => Timestamp ts - -- | Superfluid core types as associated type synonyms. -- -- Notional conventions: @@ -36,13 +28,13 @@ class (Default ts, Integral ts) => Timestamp ts -- Note: -- - Note the "6.4.9.7.1. Syntax of injectivity annotation" class ( SFTFloat (SFT_FLOAT sft) - , Typeable (SFT_MVAL sft), Value (SFT_MVAL sft) + , Typeable (SFT_MVAL sft), MonetaryValue (SFT_MVAL sft) , Timestamp (SFT_TS sft) , RealTimeBalance (SFT_RTB_F sft) (SFT_MVAL sft) ) => SuperfluidCoreTypes (sft :: Type) where - type family SFT_FLOAT sft = (sft_float :: Type) | sft_float -> sft - type family SFT_MVAL sft = (sft_mval :: Type) | sft_mval -> sft - type family SFT_TS sft = (sft_ts :: Type) | sft_ts -> sft - type family SFT_RTB_F sft = (sft_rtbF :: Type -> Type) | sft_rtbF -> sft + type family SFT_FLOAT sft = (float :: Type) | float -> sft + type family SFT_MVAL sft = (mval :: Type) | mval -> sft + type family SFT_TS sft = (t :: Type) | t -> sft + type family SFT_RTB_F sft = (rtbF :: Type -> Type) | rtbF -> sft type SFT_RTB sft = SFT_RTB_F sft (SFT_MVAL sft) diff --git a/packages/spec-haskell/packages/core/src/Money/Systems/Superfluid/CoreTypes/RealTimeBalance.hs b/packages/spec-haskell/packages/core/src/Money/Systems/Superfluid/CoreTypes/RealTimeBalance.hs index 324ef062fd..d6e9a6f8a6 100644 --- a/packages/spec-haskell/packages/core/src/Money/Systems/Superfluid/CoreTypes/RealTimeBalance.hs +++ b/packages/spec-haskell/packages/core/src/Money/Systems/Superfluid/CoreTypes/RealTimeBalance.hs @@ -27,12 +27,11 @@ import Money.Systems.Superfluid.CoreTypes.TypedValue -- [RTB's mappend commutativity] @x <> y@ = @y <> x@ -- [RTB's identity to and from typed values] @(typedValuesToRTB . typedValuesFromRTB) x@ = @x@ -- [RTB's conservation of net value] @(netValueOfRTB . valueToRTB . netValueOfRTB) v@ = @netValueOfRTB v@ -class ( Value v +class ( MonetaryValue v , Foldable rtbF , Monoid (rtbF v) , Eq (rtbF v) ) => RealTimeBalance rtbF v | rtbF -> v where - -- | Convert a single monetary value to a RTB value. valueToRTB :: Proxy rtbF -> v -> rtbF v diff --git a/packages/spec-haskell/packages/core/src/Money/Systems/Superfluid/CoreTypes/TypedValue.hs b/packages/spec-haskell/packages/core/src/Money/Systems/Superfluid/CoreTypes/TypedValue.hs index 5e0faa059e..2c58dbfcdf 100644 --- a/packages/spec-haskell/packages/core/src/Money/Systems/Superfluid/CoreTypes/TypedValue.hs +++ b/packages/spec-haskell/packages/core/src/Money/Systems/Superfluid/CoreTypes/TypedValue.hs @@ -14,7 +14,8 @@ -- readily to be used by any 'sub-system'. -- module Money.Systems.Superfluid.CoreTypes.TypedValue - ( Value + ( Timestamp + , MonetaryValue , TypedValue (..) , UntappedValue (..) , AnyTypedValue (..) @@ -26,14 +27,14 @@ import Data.Coerce (Coercible, coerce) import Data.Default (Default (..)) import Data.Typeable (Proxy (..), Typeable) -import Money.Theory.Distribution (Value) +import Money.Theory.MoneyDistribution (Timestamp, MonetaryValue) -- | Typed value is an otherwise unaccounted value ~v~ with a type. -- -- Notional conventions: -- * Type name: tv -class (Typeable tv, Value v, Coercible tv v) => TypedValue tv v | tv -> v where +class (Typeable tv, MonetaryValue mv, Coercible tv mv) => TypedValue tv mv | tv -> mv where -- | Get string representation of typed value tag. typedValueTag :: Proxy tv -> String @@ -43,8 +44,8 @@ class (Typeable tv, Value v, Coercible tv v) => TypedValue tv v | tv -> v where -- Notional conventions: -- * Term name: uval newtype UntappedValue v = MkUntappedValue v - deriving newtype (Default, Enum, Num, Eq, Ord, Real, Integral, Value) -instance (Typeable v, Value v) => TypedValue (UntappedValue v) v where typedValueTag _ = "_" + deriving newtype (Default, Enum, Num, Eq, Ord, Real, Integral, MonetaryValue) +instance (Typeable v, MonetaryValue v) => TypedValue (UntappedValue v) v where typedValueTag _ = "_" -- | Any typed value. -- diff --git a/packages/spec-haskell/packages/core/src/Money/Systems/Superfluid/MonetaryUnit.hs b/packages/spec-haskell/packages/core/src/Money/Systems/Superfluid/MonetaryUnit.hs index df840569e1..4b2028dcd8 100644 --- a/packages/spec-haskell/packages/core/src/Money/Systems/Superfluid/MonetaryUnit.hs +++ b/packages/spec-haskell/packages/core/src/Money/Systems/Superfluid/MonetaryUnit.hs @@ -7,15 +7,15 @@ import Lens.Internal import Money.Systems.Superfluid.SystemTypes -- -import qualified Money.Systems.Superfluid.Agreements.ConstantFlowAgreement as CFA -import qualified Money.Systems.Superfluid.Agreements.ConstantFlowDistributionAgreement as CFDA -import qualified Money.Systems.Superfluid.Agreements.DecayingFlowAgreement as DFA -import qualified Money.Systems.Superfluid.Agreements.InstantDistributionAgreement as IDA -import qualified Money.Systems.Superfluid.Agreements.InstantTransferAgreement as ITA -import qualified Money.Systems.Superfluid.Agreements.MinterAgreement as MINTA +import qualified Money.Systems.Superfluid.Agreements.ProportionalDistribution.ConstantFlowDistributionAgreement as CFDA +import qualified Money.Systems.Superfluid.Agreements.ProportionalDistribution.InstantDistributionAgreement as IDA +import qualified Money.Systems.Superfluid.Agreements.Universal.ConstantFlowAgreement as CFA +import qualified Money.Systems.Superfluid.Agreements.Universal.DecayingFlowAgreement as DFA +import qualified Money.Systems.Superfluid.Agreements.Universal.InstantTransferAgreement as ITA +import qualified Money.Systems.Superfluid.Agreements.Universal.MinterAgreement as MINTA -- -import qualified Money.Systems.Superfluid.Agreements.Indexes.ProportionalDistributionIndex as PDIDX -import qualified Money.Systems.Superfluid.Agreements.Indexes.UniversalIndex as UIDX +import qualified Money.Systems.Superfluid.Agreements.ProportionalDistributionIndex as PDIDX +import qualified Money.Systems.Superfluid.Agreements.UniversalIndex as UIDX -- | Monetary unit type class. diff --git a/packages/spec-haskell/packages/core/src/Money/Systems/Superfluid/MonetaryUnitData/ConstantFlow.hs b/packages/spec-haskell/packages/core/src/Money/Systems/Superfluid/MonetaryUnitData/ConstantFlow.hs index 52d2656cd8..52a145185b 100644 --- a/packages/spec-haskell/packages/core/src/Money/Systems/Superfluid/MonetaryUnitData/ConstantFlow.hs +++ b/packages/spec-haskell/packages/core/src/Money/Systems/Superfluid/MonetaryUnitData/ConstantFlow.hs @@ -13,22 +13,24 @@ import Lens.Internal import Money.Systems.Superfluid.SystemTypes -class (Default amuLs, SuperfluidSystemTypes sft) => MonetaryUnitLenses amuLs sft | amuLs -> sft where - settledAt :: Lens' amuLs (SFT_TS sft) - settledValue :: Lens' amuLs (UntappedValue (SFT_MVAL sft)) - netFlowRate :: Lens' amuLs (SFT_MVAL sft) +class ( Default amuLs + , SuperfluidSystemTypes sft + ) => MonetaryUnitLenses amuLs sft | amuLs -> sft where + settledAt :: Lens' amuLs (SFT_TS sft) + settledValue :: Lens' amuLs (UntappedValue (SFT_MVAL sft)) + netFlowRate :: Lens' amuLs (SFT_MVAL sft) type MonetaryUnitData :: Type -> Type -> Type newtype MonetaryUnitData amuLs sft = MkMonetaryUnitData { getMonetaryUnitLenses :: amuLs } deriving (Default) instance MonetaryUnitLenses amuLs sft => Semigroup (MonetaryUnitData amuLs sft) where - (<>) (MkMonetaryUnitData a) (MkMonetaryUnitData b) = + MkMonetaryUnitData a <> MkMonetaryUnitData b = let t = a^.settledAt t' = b^.settledAt settledΔ = MkUntappedValue $ a^.netFlowRate * fromIntegral (t' - t) - c = a & set settledAt t' - & over netFlowRate (+ b^.netFlowRate) - & over settledValue (+ (b^.settledValue + settledΔ)) + c = a & set settledAt t' + & over netFlowRate (+ b^.netFlowRate) + & over settledValue (+ (b^.settledValue + settledΔ)) in MkMonetaryUnitData c instance MonetaryUnitLenses amuLs sft => MonetaryUnitDataClass (MonetaryUnitData amuLs sft) sft where diff --git a/packages/spec-haskell/packages/core/src/Money/Systems/Superfluid/MonetaryUnitData/DecayingFlow.hs b/packages/spec-haskell/packages/core/src/Money/Systems/Superfluid/MonetaryUnitData/DecayingFlow.hs index f3c1571d07..355f3535a5 100644 --- a/packages/spec-haskell/packages/core/src/Money/Systems/Superfluid/MonetaryUnitData/DecayingFlow.hs +++ b/packages/spec-haskell/packages/core/src/Money/Systems/Superfluid/MonetaryUnitData/DecayingFlow.hs @@ -16,7 +16,9 @@ import Lens.Internal import Money.Systems.Superfluid.SystemTypes -class (Default amuLs, SuperfluidSystemTypes sft) => MonetaryUnitLenses amuLs sft | amuLs -> sft where +class ( Default amuLs + , SuperfluidSystemTypes sft + ) => MonetaryUnitLenses amuLs sft | amuLs -> sft where decayingFactor :: Lens' amuLs (SFT_FLOAT sft) settledAt :: Lens' amuLs (SFT_TS sft) αVal :: Lens' amuLs (SFT_FLOAT sft) @@ -34,7 +36,7 @@ instance MonetaryUnitLenses amuLs sft => Semigroup (MonetaryUnitData amuLs sft) -- } -- where { t_s = t_s, αVal = α, εVal = ε } = a -- { t_s = t_s', αVal = α', εVal = ε' } = b - (<>) (MkMonetaryUnitData a) (MkMonetaryUnitData b) = + MkMonetaryUnitData a <> MkMonetaryUnitData b = let c = a & set settledAt (b^.settledAt) & over αVal (\α -> α * exp (-λ * t_Δ) - ε') & over εVal (+ ε') diff --git a/packages/spec-haskell/packages/core/src/Money/Systems/Superfluid/MonetaryUnitData/InstantValue.hs b/packages/spec-haskell/packages/core/src/Money/Systems/Superfluid/MonetaryUnitData/InstantValue.hs index ec433980c2..5bdfba219c 100644 --- a/packages/spec-haskell/packages/core/src/Money/Systems/Superfluid/MonetaryUnitData/InstantValue.hs +++ b/packages/spec-haskell/packages/core/src/Money/Systems/Superfluid/MonetaryUnitData/InstantValue.hs @@ -15,14 +15,16 @@ import Money.Systems.Superfluid.SystemTypes -- * Monetary unit data -- -class (Default amuLs, SuperfluidSystemTypes sft) => MonetaryUnitLenses amuLs sft | amuLs -> sft where +class ( Default amuLs + , SuperfluidSystemTypes sft + ) => MonetaryUnitLenses amuLs sft | amuLs -> sft where untappedValue :: Lens' amuLs (UntappedValue (SFT_MVAL sft)) type MonetaryUnitData :: Type -> Type -> Type newtype MonetaryUnitData amuLs sft = MkMonetaryUnitData { getMonetaryUnitLenses :: amuLs } deriving (Default) instance MonetaryUnitLenses amuLs sft => Semigroup (MonetaryUnitData amuLs sft) where - (<>) (MkMonetaryUnitData a) (MkMonetaryUnitData b) = + MkMonetaryUnitData a <> MkMonetaryUnitData b = let c = a & over untappedValue (+ b^.untappedValue) in MkMonetaryUnitData c diff --git a/packages/spec-haskell/packages/core/src/Money/Systems/Superfluid/MonetaryUnitData/MintedValue.hs b/packages/spec-haskell/packages/core/src/Money/Systems/Superfluid/MonetaryUnitData/MintedValue.hs index 1ebd116221..fef041fd6f 100644 --- a/packages/spec-haskell/packages/core/src/Money/Systems/Superfluid/MonetaryUnitData/MintedValue.hs +++ b/packages/spec-haskell/packages/core/src/Money/Systems/Superfluid/MonetaryUnitData/MintedValue.hs @@ -18,8 +18,8 @@ import Money.Systems.Superfluid.SystemTypes -- newtype MintedValue v = MkMintedValue v - deriving newtype (Default, Enum, Num, Eq, Ord, Real, Integral, Value) -instance (Typeable v, Value v) => TypedValue (MintedValue v) v where typedValueTag _ = "b" + deriving newtype (Default, Enum, Num, Eq, Ord, Real, Integral, MonetaryValue) +instance (Typeable v, MonetaryValue v) => TypedValue (MintedValue v) v where typedValueTag _ = "b" -- * Monetary unit data -- @@ -32,7 +32,7 @@ newtype MonetaryUnitData amuLs sft = MkMonetaryUnitData { getMonetaryUnitLenses deriving (Default) instance MonetaryUnitLenses amuLs sft => Semigroup (MonetaryUnitData amuLs sft) where - (<>) (MkMonetaryUnitData a) (MkMonetaryUnitData b) = + MkMonetaryUnitData a <> MkMonetaryUnitData b = let c = a & over untappedValue (+ b^.untappedValue) & over mintedValue (+ b^.mintedValue) in MkMonetaryUnitData c diff --git a/packages/spec-haskell/packages/core/src/Money/Systems/Superfluid/SubSystems/BufferBasedSolvency.hs b/packages/spec-haskell/packages/core/src/Money/Systems/Superfluid/SubSystems/BufferBasedSolvency.hs index 1dddd6cc92..2de28155cf 100644 --- a/packages/spec-haskell/packages/core/src/Money/Systems/Superfluid/SubSystems/BufferBasedSolvency.hs +++ b/packages/spec-haskell/packages/core/src/Money/Systems/Superfluid/SubSystems/BufferBasedSolvency.hs @@ -11,5 +11,5 @@ import Money.Systems.Superfluid.CoreTypes newtype BufferValue v = MkBufferValue v - deriving newtype (Default, Enum, Num, Eq, Ord, Real, Integral, Value) -instance (Typeable v, Value v) => TypedValue (BufferValue v) v where typedValueTag _ = "b" + deriving newtype (Default, Enum, Num, Eq, Ord, Real, Integral, MonetaryValue) +instance (Typeable v, MonetaryValue v) => TypedValue (BufferValue v) v where typedValueTag _ = "b" diff --git a/packages/spec-haskell/packages/core/src/Money/Systems/Superfluid/Token.hs b/packages/spec-haskell/packages/core/src/Money/Systems/Superfluid/Token.hs index 9caea8939f..c00021e303 100644 --- a/packages/spec-haskell/packages/core/src/Money/Systems/Superfluid/Token.hs +++ b/packages/spec-haskell/packages/core/src/Money/Systems/Superfluid/Token.hs @@ -9,20 +9,20 @@ module Money.Systems.Superfluid.Token , Token (..) ) where -import Data.Foldable (toList) -import Data.Kind (Type) +import Data.Foldable (toList) +import Data.Kind (Type) import Lens.Internal import Money.Systems.Superfluid.SystemTypes -- -import qualified Money.Systems.Superfluid.Agreements.ConstantFlowAgreement as CFA -import qualified Money.Systems.Superfluid.Agreements.ConstantFlowDistributionAgreement as CFDA -import qualified Money.Systems.Superfluid.Agreements.DecayingFlowAgreement as DFA -import qualified Money.Systems.Superfluid.Agreements.InstantDistributionAgreement as IDA -import qualified Money.Systems.Superfluid.Agreements.InstantTransferAgreement as ITA -import qualified Money.Systems.Superfluid.Agreements.MinterAgreement as MINTA +import qualified Money.Systems.Superfluid.Agreements.ProportionalDistribution.ConstantFlowDistributionAgreement as CFDA +import qualified Money.Systems.Superfluid.Agreements.ProportionalDistribution.InstantDistributionAgreement as IDA +import qualified Money.Systems.Superfluid.Agreements.Universal.ConstantFlowAgreement as CFA +import qualified Money.Systems.Superfluid.Agreements.Universal.DecayingFlowAgreement as DFA +import qualified Money.Systems.Superfluid.Agreements.Universal.InstantTransferAgreement as ITA +import qualified Money.Systems.Superfluid.Agreements.Universal.MinterAgreement as MINTA -- -import qualified Money.Systems.Superfluid.Agreements.Indexes.ProportionalDistributionIndex as PDIDX +import qualified Money.Systems.Superfluid.Agreements.ProportionalDistributionIndex as PDIDX -- import Money.Systems.Superfluid.MonetaryUnit @@ -188,7 +188,7 @@ class ( Monad tk setContract (PDIDX.SubscriberOperationOutputF addr) (dc', sc') t = do overProportionalDistributionContract addr indexId (const dc') t overProportionalDistributionSubscription subscriber addr indexId (const sc') t - (t, aoAccounts, cfdaMUDΔ) <- eff_agreement_operation_base + (t, aoAccounts, PDIDX.SubscriberOperationOutput cfdaMUDΔ) <- eff_agreement_operation_base (PDIDX.SubscriberOperationOutputF publisher) (PDIDX.Subscribe unit) viewContract diff --git a/packages/spec-haskell/packages/core/src/Money/Theory/Distribution.lhs b/packages/spec-haskell/packages/core/src/Money/Theory/Distribution.lhs deleted file mode 100644 index 42510df0a3..0000000000 --- a/packages/spec-haskell/packages/core/src/Money/Theory/Distribution.lhs +++ /dev/null @@ -1,54 +0,0 @@ -An attempt of creating a unifying theory for money and its distribution was made by Buldas, Saarepera et al.\cite{buldas2021unifying} - -\begin{displayquote} -A useful observation about existing money schemes is that they all have some kind of monetary units that are physical or -digital representations of money. Examples are bills, coins, bank accounts, Bitcoin UTXOs, etc. -\end{displayquote} - -\ignore{ -\begin{code} -{-# LANGUAGE TypeFamilies #-} -module Money.Theory.Distribution where - -import Data.Default (Default) --- import Data.Kind (Type) -\end{code} -} - -\begin{code} --- | Value Class --- --- Naming conventions: --- --- * Type name: v -class (Integral v, Default v) => Value v -\end{code} - -\begin{code} -class Context ctx -\end{code} - -\begin{code} -class Bearer brr -\end{code} - -\begin{code} -class MonetaryUnit mu -\end{code} - -\begin{code} --- | Value Distribution -class Distribution d where - -- type DistributionValueType d :: Type - -- type DistributionMonetaryUnitType d :: Type - -- type DistributionBearerType d :: Type - -- type DistributionContextType d :: Type - - monetaryUnits :: MonetaryUnit mu => d -> [mu] - - valueOf :: (Context ctx, MonetaryUnit mu, Value v) => d -> (mu, ctx) -> v - - bearerOf :: (MonetaryUnit mu, Bearer brr) => d -> mu -> brr -\end{code} - -(WIP) diff --git a/packages/spec-haskell/packages/core/src/Money/Theory/FinancialContract.lhs b/packages/spec-haskell/packages/core/src/Money/Theory/FinancialContract.lhs new file mode 100644 index 0000000000..6e32b29104 --- /dev/null +++ b/packages/spec-haskell/packages/core/src/Money/Theory/FinancialContract.lhs @@ -0,0 +1,54 @@ +> -- -*- fill-column: 70; -*- + +\ignore{ +\begin{code} +{-# LANGUAGE FunctionalDependencies #-} +{-# LANGUAGE TypeFamilyDependencies #-} +module Money.Theory.FinancialContract + -- $intro + -- + ( FinancialContract (..) + ) where + +import Money.Theory.MoneyDistribution +\end{code} +} + +\begin{haddock} +\begin{code} +{- $intro + +A financial contract is the execution context for payment primitives, +including their execution conditions, timing\footnote{Timing is a type +of condition of which current system time is a factor}, and execution +order. + +Inspired by the \textit{technique of composing financial contracts} +demonstrated in \cite{peyton2000composing}, we define the type class +for financial contracts as follows: + +-} +\end{code} +\end{haddock} + +\begin{code} +-- | Composable financial contracts. +class MoneyDistribution md => FinancialContract fc md | fc -> md where + -- | Predicate of the execution condition of a financial contract. + fcPred :: ( ctx ~ MD_CTX md + , Timestamp t + ) + => fc -> (md, ctx) -> t -> Bool + + -- | Execute the payment primitives encoded in the financial + -- contract. + fcExec :: ( ctx ~ MD_CTX md + , Timestamp t + ) + => fc -> (md, ctx) -> t -> ((md, ctx), fc) +\end{code} + +We know that both $md$ and $ctx$ are constrained to be monoid, then +\textit{(md, ctx)} must be monoidal too. With this, it is possible to +build a combinatorial library of financial contracts that can be used +to construct more complex financial contracts. diff --git a/packages/spec-haskell/packages/core/src/Money/Theory/MoneyDistribution.lhs b/packages/spec-haskell/packages/core/src/Money/Theory/MoneyDistribution.lhs new file mode 100644 index 0000000000..51fdebc0cd --- /dev/null +++ b/packages/spec-haskell/packages/core/src/Money/Theory/MoneyDistribution.lhs @@ -0,0 +1,93 @@ +> -- -*- fill-column: 70; -*- + +\ignore{ +\begin{code} +{-# LANGUAGE TypeFamilyDependencies #-} +module Money.Theory.MoneyDistribution + -- $intro + -- + ( MonetaryValue + , Timestamp + , SharedContext + , Bearer + , MonetaryUnit + , MoneyDistribution (..) + ) where + +import Data.Default (Default) +import Data.Kind (Type) +import Data.Coerce ( Coercible ) +\end{code} +} + +\begin{haddock} +\begin{code} +{- $intro + +As the innermost layer of a modern payment system, money distribution +models how monetary value is distributed amongst bearers. + +-} +\end{code} +\end{haddock} + +\begin{code} +class (Integral ν, Default ν) => MonetaryValue ν +\end{code} + +\begin{code} +class (Integral t, Default t) => Timestamp t +\end{code} + +\begin{code} +class Monoid ctx => SharedContext ctx +\end{code} + +\begin{code} +class Bearer brr +\end{code} + +\begin{code} +class Eq u => MonetaryUnit u +\end{code} + +\begin{code} +-- | Money distribution functions and indexed types. +class ( MonetaryValue (MD_MVAL md) + , Timestamp (MD_TS md) + -- t & mval should have the same representational type + , Coercible (MD_TS md) (MD_MVAL md) + , MonetaryUnit (MD_MU md) + , Bearer (MD_BRR md) + , SharedContext (MD_CTX md) + , Monoid md + ) => MoneyDistribution md where + -- | Set of bearers. + bearers :: ( brr ~ MD_BRR md + ) + => md -> [brr] + + -- | Set of monetary units. + monetaryUnits :: mu ~ MD_MU md + => md -> [mu] + + -- | Money distribution β function. + bearerOf :: ( mu ~ MD_MU md + , brr ~ MD_BRR md + ) + => md -> mu -> brr + + -- | Money distribution ν function. + monetaryValueOf :: ( mv ~ MD_MVAL md + , mu ~ MD_MU md + , ctx ~ MD_CTX md + , t ~ MD_TS md + ) + => md -> mu -> ctx -> t -> mv + + type family MD_MVAL md = (mval :: Type) | mval -> md + type family MD_TS md = (t :: Type) | t -> md + type family MD_MU md = (mu :: Type) | mu -> md + type family MD_BRR md = (brr :: Type) | brr -> md + type family MD_CTX md = (ctx :: Type) | ctx -> md +\end{code} diff --git a/packages/spec-haskell/packages/core/src/Money/Theory/MoneyMedium.lhs b/packages/spec-haskell/packages/core/src/Money/Theory/MoneyMedium.lhs new file mode 100644 index 0000000000..99dd0c903e --- /dev/null +++ b/packages/spec-haskell/packages/core/src/Money/Theory/MoneyMedium.lhs @@ -0,0 +1,73 @@ +> -- -*- fill-column: 70; -*- + +\ignore{ +\begin{code} +{-# LANGUAGE TypeFamilyDependencies #-} +module Money.Theory.MoneyMedium + -- $intro + -- + ( NondetSeqMoneyToken (..) + , MoneyNote (..) + , NondetSeqMoneyNotes (..) + ) where + +import Money.Theory.MoneyDistribution +import Money.Theory.FinancialContract +import Money.Theory.PaymentExecutionEnvironment +\end{code} +} + +\begin{haddock} +\begin{code} +{- $intro + +Here are some toy models for non-deterministic sequential money tokens +and money notes: + +-} +\end{code} +\end{haddock} + +\begin{code} +type Address = String + +-- | Toy model for non-deterministic sequential money token. +class ( MoneyDistribution md + , FinancialContract fc md + , NondetSeqPaymentExecEnv tk md fc + ) => NondetSeqMoneyToken tk md fc where + -- | Customary interface for querying one's current account balance. + balanceOf :: mval ~ MD_MVAL md + => Address -> tk mval + + -- The rest would be just convenience interfaces for ~fcMInsert~ + +-- | A money note that is capable of encoding financial contract. +data MoneyNote md fc = ( MoneyDistribution md + , FinancialContract fc md + ) => FinancialContractNote md fc + | MoneytaryUnitNote md + +type NoteID = String + +-- | A toy model for non-deterministic sequential money notes execution +-- environment. +class ( MoneyDistribution md + , FinancialContract fc md + , NondetSeqPaymentExecEnv env md fc + ) => NondetSeqMoneyNotes env md fc where + -- | Find note by its ID. This should be used by ~fc~ to rehydrate + -- the its references to the notes. + findNote :: note ~ MoneyNote md fc + => NoteID -> env note + + -- | Customary interface for querying the note's current balance. + balanceIn :: ( mval ~ MD_MVAL md + , note ~ MoneyNote md fc + ) + => note -> env mval +\end{code} + +It may seem tiny semantic differences between tokens and notes +execution environment, but it is on purpose. Their main difference lies +mainly in their ``user experience'' implementations. diff --git a/packages/spec-haskell/packages/core/src/Money/Theory/PaymentExecutionEnvironment.lhs b/packages/spec-haskell/packages/core/src/Money/Theory/PaymentExecutionEnvironment.lhs new file mode 100644 index 0000000000..9d1457390e --- /dev/null +++ b/packages/spec-haskell/packages/core/src/Money/Theory/PaymentExecutionEnvironment.lhs @@ -0,0 +1,172 @@ +> -- -*- fill-column: 70; -*- + +\ignore{ +\begin{code} +{-# LANGUAGE FunctionalDependencies #-} +{-# LANGUAGE GADTs #-} +module Money.Theory.PaymentExecutionEnvironment + -- $intro + -- + ( NondetSeqPaymentExecEnv (..) + , TotallyOrderedFinancialContract + , PartiallyOrderedFinancialContract + , DetSeqPaymentExecEnv (..) + ) where + +import Money.Theory.MoneyDistribution +import Money.Theory.FinancialContract +\end{code} +} + +\begin{haddock} +\begin{code} +{- $intro + +Here are some models for the different payment execution environments. + +-} +\end{code} +\end{haddock} + +\paragraph{Non-deterministic Sequential Execution Environment} + +First, we define a model for a non-deterministic sequential payment +execution environment, which includes a set of all financial contracts +and a step-through function: + +\begin{code} +-- | Non-deterministic sequential payment execution environment. +class ( MoneyDistribution md + , FinancialContract fc md + , Monad env + ) => NondetSeqPaymentExecEnv env md fc | env -> md, env -> fc where + -- | Monadically update a financial contract in the execution + -- environment. + fcMUpdate :: fc -> env () + + -- | Monadically select one financial contract from the execution + -- environment. + fcMSelect :: ( ctx ~ MD_CTX md + , Timestamp t + ) + => t -> env (md, ctx, fc) + + -- | Step through the execution environment. + penvStepThrough :: ( ctx ~ MD_CTX md + , Timestamp t + ) + => t -> env (md, ctx) + -- Default implementation for the step through function. + penvStepThrough t = do + (md, ctx, fc) <- fcMSelect t + if fcPred fc (md, ctx) t + then do + let ((md', ctx'), fc') = fcExec fc (md, ctx) t + fcMUpdate fc' + -- (<>) operator is the binary operator for monoidal types. + return ((md, ctx) <> (md', ctx')) + else return (md, ctx) +\end{code} + +We do not assume that \textit{fcMSelect} yields a predicate that +evaluates to true; since it could be an input from the external +world. This won't work with any deterministic financial contract set. + +The environment is a Monad, where different side effects for +\textit{fcMSelect} can be encoded. However, arrows could be used +instead for a more generalized interface to +computation\cite{hughes2000generalising}. + +\paragraph{Parallel Execution} + +When the executions of payment primitives can be parallel, +resource-sharing problems arise in their data storage when updating +money distribution, context, and financial contract sets. + +To model the parallel execution, ones must first study the concurrent +control of the data storage system used +(\cite{bernstein1981concurrency}), while formalism of parallel +execution can be best done using Petri Nets +(\cite{petri1962kommunikation}, \cite{reisig2012petri})\footnote{Petri +Nets World, +https://www.informatik.uni-hamburg.de/TGI/PetriNets/index.php}. + +A model in Haskell will not be provided for now since it is out of the +scope of the paper. + +\paragraph{Deterministic Execution} + +To make the execution environment deterministic, stronger ordering +conditions must be provided to the financial contract type: + +\begin{code} +-- | Financial contract that can be totally ordered. +class ( MoneyDistribution md + , FinancialContract tofc md + , Ord tofc) + => TotallyOrderedFinancialContract tofc md + +-- | A partially ordered data type (incomplete definition). +class Poset a +-- omitting detailed interface of it. + +-- | Financial contract that can be partially ordered. +class ( MoneyDistribution md + , FinancialContract tofc md + , Poset tofc) + => PartiallyOrderedFinancialContract tofc md +\end{code} + +Total ordered financial contract could be used to model deterministic +sequential execution environment: + +\begin{code} +-- | Deterministic sequential payment execution environment. +class ( MoneyDistribution md + , TotallyOrderedFinancialContract tofc md + ) => DetSeqPaymentExecEnv env md tofc | env -> md, env -> tofc where + -- | Update a financial contract in the execution environment. + -- + -- Note: + -- + -- * In order to keep well-ordering properties, the complexity + -- of this function can be at least as bad as updating a + -- sorted data structure $O(log(n))$. + fcUpdate :: fc -> env -> env + + -- | Deterministically get the next financial contract + -- executable at a specific time. + fcNext :: ( ctx ~ MD_CTX md + , Timestamp t + ) + => env -> (md, ctx, tofc, t) + + -- | Update execution environment with new money distribution and + -- context. + penvUpdate :: ctx ~ MD_CTX md + => env -> (md, ctx) -> env + + -- | Deterministically step through the execution environment + penvDetStepThrough :: ( ctx ~ MD_CTX md + , Timestamp t + ) + => env -> (env, t) + -- Default implementation for the step through function. + penvDetStepThrough env = let + (md, ctx, fc, t) = fcNext env + ((md', ctx'), fc') = fcExec fc (md, ctx) t + -- assert: fcPred fc (md, ctx) t + in (penvUpdate + (fcUpdate fc' env) + ((md, ctx) <> (md', ctx')) + , t) +\end{code} + +The environment is no longer monadic; that is to say, it is now fully +deterministic. Instead, the monadic interactions with the external +world should use \textit{fcInsert} for adding new financial contracts +to the environment. + +A weaker condition, namely a poset (partially ordered) of financial +contracts, may enable deterministic parallel executions of +payments. However, model in Haskell will also not be provided for now. diff --git a/packages/spec-haskell/packages/core/src/Money/Theory/PaymentPrimitives.lhs b/packages/spec-haskell/packages/core/src/Money/Theory/PaymentPrimitives.lhs new file mode 100644 index 0000000000..28b9852b9a --- /dev/null +++ b/packages/spec-haskell/packages/core/src/Money/Theory/PaymentPrimitives.lhs @@ -0,0 +1,104 @@ +> -- -*- fill-column: 70; -*- + +\ignore{ +\begin{code} + +{-# LANGUAGE FunctionalDependencies #-} +{-# LANGUAGE GADTs #-} +module Money.Theory.PaymentPrimitives + -- $intro + -- + ( MoneyDistributionModel + , 𝓜 + , 𝓜' (..) + , sem𝓜 + ) where + +import Data.Coerce ( coerce ) + +import Money.Theory.MoneyDistribution +\end{code} +} + +\begin{haddock} +\begin{code} +{- $intro + +Here is the denotational semantics of payment primitives of modern +payment system. + +-} +\end{code} +\end{haddock} + +\begin{code} +-- | Type synonym for ⟦𝓜⟧. +type MoneyDistributionModel' md = forall ν t u. + ( MoneyDistribution md + , ν ~ MD_MVAL md + , t ~ MD_TS md + , u ~ MD_MU md + ) => u -> t -> ν + +-- | ⟦𝓜⟧ - methematical model of meaning in money distribution. +data MoneyDistributionModel md = MkMoneyDistributionModel + (MoneyDistributionModel' md) +\end{code} + +\begin{code} +-- | Semigroup class instance ⟦𝓜⟧. +instance ( MoneyDistribution md + ) => Semigroup (MoneyDistributionModel md) where + -- ⊕: monoid binary operator + (MkMoneyDistributionModel ma) <> (MkMoneyDistributionModel mb) = + MkMoneyDistributionModel (\u -> \t -> ma u t + mb u t) + +-- | Monoid class instance ⟦𝓜⟧. +instance ( MoneyDistribution md + ) => Monoid (MoneyDistributionModel md) where + -- ∅: monoid empty set + mempty = MkMoneyDistributionModel (\_ -> \_ -> 0) +\end{code} + +\begin{code} +-- | Index abstraction. +class Eq u => Index k u | k -> u where + ρ :: k -> u -> Double + +-- | Universal index. +data UniversalIndex u = MkUniversalIndex u +instance Eq u => Index (UniversalIndex u) u where + ρ (MkUniversalIndex u) u' = if u == u' then 1 else 0 +\end{code} + +\begin{code} +-- | 𝓜' - syntactic category using index abstraction. +data 𝓜' ν t u = + forall k1 k2. (Index k1 u, Index k2 u) => TransferI k1 k2 ν | + forall k1 k2. (Index k1 u, Index k2 u) => FlowI k1 k2 ν t + +-- | Type synonym for 𝓜' using type family. +type 𝓜 md = forall ν t u. + ( MoneyDistribution md + , ν ~ MD_MVAL md + , t ~ MD_TS md + , u ~ MD_MU md + ) => 𝓜' ν t u + +-- | ⟦.⟧ - semantic function of 𝓜. +sem :: MoneyDistribution md + => 𝓜 md -> MoneyDistributionModel' md +sem (TransferI ka kb amount) = \u -> \_ -> + let x = fromIntegral amount + in ceiling $ -x * ρ ka u + x * ρ kb u +sem (FlowI ka kb r t') = \u -> \t -> + let x = fromIntegral $ -r * coerce(t - t') + in ceiling $ -x * ρ ka u + x * ρ kb u +-- GHC 9.4.2 bug re non-exhaustive pattern matching? +sem _ = error "huh?" + +-- | ⟦.⟧ - semantic function of 𝓜. +sem𝓜 :: MoneyDistribution md + => 𝓜 md -> MoneyDistributionModel md +sem𝓜 s = MkMoneyDistributionModel (sem s) +\end{code} diff --git a/packages/spec-haskell/packages/core/superfluid-protocol-spec-core.cabal b/packages/spec-haskell/packages/core/superfluid-protocol-spec-core.cabal index 8dd72682f4..ff0f04a424 100644 --- a/packages/spec-haskell/packages/core/superfluid-protocol-spec-core.cabal +++ b/packages/spec-haskell/packages/core/superfluid-protocol-spec-core.cabal @@ -16,8 +16,11 @@ build-type: Simple library exposed-modules: Data.Type.Any - Money.Theory.Distribution - Money.Systems.Communism + Money.Theory.MoneyDistribution + Money.Theory.FinancialContract + Money.Theory.PaymentExecutionEnvironment + Money.Theory.MoneyMedium + Money.Theory.PaymentPrimitives Money.Systems.Superfluid.CoreTypes.TypedValue Money.Systems.Superfluid.CoreTypes.RealTimeBalance Money.Systems.Superfluid.CoreTypes @@ -28,15 +31,15 @@ library Money.Systems.Superfluid.MonetaryUnitData.InstantValue Money.Systems.Superfluid.MonetaryUnitData.ConstantFlow Money.Systems.Superfluid.MonetaryUnitData.DecayingFlow - Money.Systems.Superfluid.Agreements.MinterAgreement - Money.Systems.Superfluid.Agreements.InstantTransferAgreement - Money.Systems.Superfluid.Agreements.ConstantFlowAgreement - Money.Systems.Superfluid.Agreements.DecayingFlowAgreement - Money.Systems.Superfluid.Agreements.InstantDistributionAgreement - Money.Systems.Superfluid.Agreements.ConstantFlowDistributionAgreement - Money.Systems.Superfluid.Agreements.Indexes.UniversalIndex - Money.Systems.Superfluid.Agreements.Indexes.ProportionalDistributionCommon - Money.Systems.Superfluid.Agreements.Indexes.ProportionalDistributionIndex + Money.Systems.Superfluid.Agreements.Universal.MinterAgreement + Money.Systems.Superfluid.Agreements.Universal.InstantTransferAgreement + Money.Systems.Superfluid.Agreements.Universal.ConstantFlowAgreement + Money.Systems.Superfluid.Agreements.Universal.DecayingFlowAgreement + Money.Systems.Superfluid.Agreements.UniversalIndex + Money.Systems.Superfluid.Agreements.ProportionalDistribution.Common + Money.Systems.Superfluid.Agreements.ProportionalDistribution.InstantDistributionAgreement + Money.Systems.Superfluid.Agreements.ProportionalDistribution.ConstantFlowDistributionAgreement + Money.Systems.Superfluid.Agreements.ProportionalDistributionIndex Money.Systems.Superfluid.SubSystems.BufferBasedSolvency Money.Systems.Superfluid.MonetaryUnit Money.Systems.Superfluid.Token diff --git a/packages/spec-haskell/packages/core/test/Money/Systems/Superfluid/ConstantFlowDistributionAgreement_prop.hs b/packages/spec-haskell/packages/core/test/Money/Systems/Superfluid/ConstantFlowDistributionAgreement_prop.hs index 14ba6f55df..6231bf2f1d 100644 --- a/packages/spec-haskell/packages/core/test/Money/Systems/Superfluid/ConstantFlowDistributionAgreement_prop.hs +++ b/packages/spec-haskell/packages/core/test/Money/Systems/Superfluid/ConstantFlowDistributionAgreement_prop.hs @@ -10,9 +10,9 @@ import Lens.Internal import Test.Hspec import Test.QuickCheck -import qualified Money.Systems.Superfluid.Agreements.ConstantFlowDistributionAgreement as CFDA -import qualified Money.Systems.Superfluid.Agreements.Indexes.ProportionalDistributionIndex as PDIDX -import qualified Money.Systems.Superfluid.MonetaryUnitData.ConstantFlow as CFMUD +import qualified Money.Systems.Superfluid.Agreements.ProportionalDistribution.ConstantFlowDistributionAgreement as CFDA +import qualified Money.Systems.Superfluid.Agreements.ProportionalDistributionIndex as PDIDX +import qualified Money.Systems.Superfluid.MonetaryUnitData.ConstantFlow as CFMUD import Money.Systems.Superfluid.SystemTypes import Money.Systems.Superfluid.TestTypes @@ -65,13 +65,13 @@ ao_1pub2subs_zero_sum_balance t0 aos0 = go (getNonEmpty aos0) t0 (def, def) def where t' = t + tΔ go ((TO_1Pub2Subs (PubOp2 _), _) :_) _ _ _ _ = error "huh? there is no pub2" go ((TO_1Pub2Subs (SubOp1 ao), tΔ):aos) t (dcFull, pubMuds) scFull1 scFull2 = - let (isGood, ((dcFull', scFull1'), pubMudsΔ)) = single_op + let (isGood, ((dcFull', scFull1'), PDIDX.SubscriberOperationOutput pubMudsΔ)) = single_op t' (dcFull, pubMuds) scFull1 scFull2 ((dcFull, scFull1), def) ao in isGood && go aos t' (dcFull', pubMuds <> pubMudsΔ) scFull1' scFull2 where t' = t + tΔ go ((TO_1Pub2Subs (SubOp2 ao), tΔ):aos) t (dcFull, pubMuds) scFull1 scFull2 = - let (isGood, ((dcFull', scFull2'), pubMudsΔ)) = single_op + let (isGood, ((dcFull', scFull2'), PDIDX.SubscriberOperationOutput pubMudsΔ)) = single_op t' (dcFull, pubMuds) scFull1 scFull2 ((dcFull, scFull2), def) ao in isGood && go aos t' (dcFull', pubMuds <> pubMudsΔ) scFull1 scFull2' @@ -89,10 +89,10 @@ ao_1pub2subs_zero_sum_balance t0 aos0 = go (getNonEmpty aos0) t0 (def, def) def newtype TO_2Pubs1Sub = TO_2Pubs1Sub TestOperations deriving Show instance Arbitrary TO_2Pubs1Sub where - arbitrary = oneof [ -- (arbitrary :: Gen ()) <&> TO_2Pubs1Sub . Nop - -- , (arbitrary :: Gen T_CFDAPublisherOperation) <&> TO_2Pubs1Sub . PubOp1 - (arbitrary :: Gen T_CFDAPublisherOperation) <&> TO_2Pubs1Sub . PubOp2 - -- , (arbitrary :: Gen T_PDIDXSubscriberOperation) <&> TO_2Pubs1Sub . SubOp1 + arbitrary = oneof [ (arbitrary :: Gen ()) <&> TO_2Pubs1Sub . Nop + , (arbitrary :: Gen T_CFDAPublisherOperation) <&> TO_2Pubs1Sub . PubOp1 + , (arbitrary :: Gen T_CFDAPublisherOperation) <&> TO_2Pubs1Sub . PubOp2 + , (arbitrary :: Gen T_PDIDXSubscriberOperation) <&> TO_2Pubs1Sub . SubOp1 , (arbitrary :: Gen T_PDIDXSubscriberOperation) <&> TO_2Pubs1Sub . SubOp2 ] ao_2pubs1sub_zero_sum_balance :: T_Timestamp -> NonEmptyList (TO_2Pubs1Sub, T_Timestamp) -> Bool @@ -122,13 +122,13 @@ ao_2pubs1sub_zero_sum_balance t0 aos0 = go (getNonEmpty aos0) t0 (def, def) (def in isGood && go aos t' (dcFull1, pubMuds1) (dcFull2', pubMuds2') scFull1 scFull2 where t' = t + tΔ go ((TO_2Pubs1Sub (SubOp1 ao), tΔ):aos) t (dcFull1, pubMuds1) (dcFull2, pubMuds2) scFull1 scFull2 = - let (isGood, ((dcFull1', scFull1'), pubMuds1Δ)) = single_op + let (isGood, ((dcFull1', scFull1'), PDIDX.SubscriberOperationOutput pubMuds1Δ)) = single_op t' (dcFull1, pubMuds1) (dcFull2, pubMuds2) scFull1 scFull2 ((dcFull1, scFull1), def) ao in isGood && go aos t' (dcFull1', pubMuds1 <> pubMuds1Δ) (dcFull2, pubMuds2) scFull1' scFull2 where t' = t + tΔ go ((TO_2Pubs1Sub (SubOp2 ao), tΔ):aos) t (dcFull1, pubMuds1) (dcFull2, pubMuds2) scFull1 scFull2 = - let (isGood, ((dcFull2', scFull2'), pubMuds2Δ)) = single_op + let (isGood, ((dcFull2', scFull2'), PDIDX.SubscriberOperationOutput pubMuds2Δ)) = single_op t' (dcFull1, pubMuds1) (dcFull2, pubMuds2) scFull1 scFull2 ((dcFull2, scFull2), def) ao in isGood && go aos t' (dcFull1, pubMuds1) (dcFull2', pubMuds2 <> pubMuds2Δ) scFull1 scFull2' diff --git a/packages/spec-haskell/packages/core/test/Money/Systems/Superfluid/TestTypes.hs b/packages/spec-haskell/packages/core/test/Money/Systems/Superfluid/TestTypes.hs index 32b4239e51..37a714a4fa 100644 --- a/packages/spec-haskell/packages/core/test/Money/Systems/Superfluid/TestTypes.hs +++ b/packages/spec-haskell/packages/core/test/Money/Systems/Superfluid/TestTypes.hs @@ -7,22 +7,25 @@ module Money.Systems.Superfluid.TestTypes where import Control.Applicative import Data.Coerce import Data.Default -import Data.Functor ((<&>)) +import Data.Functor ((<&>)) import Data.Type.Any import Data.Typeable import GHC.Generics -import Math.Extras.Double (Tolerance, fuzzyEq) +import Math.Extras.Double + ( Tolerance + , fuzzyEq + ) import Test.QuickCheck import Money.Systems.Superfluid.SystemTypes -- -import qualified Money.Systems.Superfluid.Agreements.ConstantFlowAgreement as CFA -import qualified Money.Systems.Superfluid.Agreements.ConstantFlowDistributionAgreement as CFDA -import qualified Money.Systems.Superfluid.Agreements.Indexes.ProportionalDistributionIndex as PDIDX -import qualified Money.Systems.Superfluid.MonetaryUnitData.ConstantFlow as CFMUD -import qualified Money.Systems.Superfluid.MonetaryUnitData.MintedValue as MVMUD -import qualified Money.Systems.Superfluid.SubSystems.BufferBasedSolvency as BBS +import qualified Money.Systems.Superfluid.Agreements.ProportionalDistribution.ConstantFlowDistributionAgreement as CFDA +import qualified Money.Systems.Superfluid.Agreements.ProportionalDistributionIndex as PDIDX +import qualified Money.Systems.Superfluid.Agreements.Universal.ConstantFlowAgreement as CFA +import qualified Money.Systems.Superfluid.MonetaryUnitData.ConstantFlow as CFMUD +import qualified Money.Systems.Superfluid.MonetaryUnitData.MintedValue as MVMUD +import qualified Money.Systems.Superfluid.SubSystems.BufferBasedSolvency as BBS -- * Timestamp @@ -31,12 +34,12 @@ newtype T_Timestamp = T_Timestamp Integer deriving newtype (Enum, Eq, Ord, Num, Real, Integral, Default, Timestamp, Show) instance Arbitrary T_Timestamp where - arbitrary = arbitrary <&> abs <&> T_Timestamp + arbitrary = arbitrary <&> id <&> T_Timestamp -- * Value newtype T_MVal = T_MVal Integer - deriving newtype (Default, Eq, Enum, Real, Ord, Num, Integral, Value, Show, Arbitrary) + deriving newtype (Default, Eq, Enum, Real, Ord, Num, Integral, MonetaryValue, Show, Arbitrary) deriving instance Show (UntappedValue T_MVal) @@ -50,7 +53,7 @@ test_flowrate_per_mon_max :: Int test_flowrate_per_mon_max = floor (100e6 :: Double) fuzzy_tolerance :: Tolerance -fuzzy_tolerance = fromIntegral flowrate_base / 1e4 -- accurate to 4 decimals of $1 / month +fuzzy_tolerance = fromIntegral flowrate_base / 1e4 -- accurate to 4 decimals of $1 fuzzyEqMVal :: T_MVal -> T_MVal -> Bool fuzzyEqMVal a b = fuzzyEq fuzzy_tolerance (fromIntegral a) (fromIntegral b) diff --git a/packages/spec-haskell/packages/simple/src/Money/Systems/Superfluid/Instances/Simple/System.hs b/packages/spec-haskell/packages/simple/src/Money/Systems/Superfluid/Instances/Simple/System.hs index 9db4f84565..1a42804f68 100644 --- a/packages/spec-haskell/packages/simple/src/Money/Systems/Superfluid/Instances/Simple/System.hs +++ b/packages/spec-haskell/packages/simple/src/Money/Systems/Superfluid/Instances/Simple/System.hs @@ -32,24 +32,24 @@ module Money.Systems.Superfluid.Instances.Simple.System import Control.Monad.Trans.Class import Control.Monad.Trans.Reader import Control.Monad.Trans.State -import Data.Binary (Binary) -import Data.Char (isAlpha) +import Data.Binary (Binary) +import Data.Char (isAlpha) import Data.Default -import qualified Data.Map as M +import qualified Data.Map as M import Data.Maybe import Data.String import Data.Type.TaggedTypeable import Lens.Internal -import qualified Money.Systems.Superfluid.MonetaryUnitData.ConstantFlow as CFMUD -import qualified Money.Systems.Superfluid.MonetaryUnitData.DecayingFlow as DFMUD -import qualified Money.Systems.Superfluid.MonetaryUnitData.InstantValue as IVMUD -import qualified Money.Systems.Superfluid.MonetaryUnitData.MintedValue as MVMUD +import qualified Money.Systems.Superfluid.MonetaryUnitData.ConstantFlow as CFMUD +import qualified Money.Systems.Superfluid.MonetaryUnitData.DecayingFlow as DFMUD +import qualified Money.Systems.Superfluid.MonetaryUnitData.InstantValue as IVMUD +import qualified Money.Systems.Superfluid.MonetaryUnitData.MintedValue as MVMUD -- -import qualified Money.Systems.Superfluid.Agreements.Indexes.ProportionalDistributionIndex as PDIDX -import qualified Money.Systems.Superfluid.Agreements.Indexes.UniversalIndex as UIDX +import qualified Money.Systems.Superfluid.Agreements.ProportionalDistributionIndex as PDIDX +import qualified Money.Systems.Superfluid.Agreements.UniversalIndex as UIDX -- -import qualified Money.Systems.Superfluid.Token as SF +import qualified Money.Systems.Superfluid.Token as SF -- import Money.Systems.Superfluid.Instances.Simple.Types diff --git a/packages/spec-haskell/packages/simple/src/Money/Systems/Superfluid/Instances/Simple/Types.hs b/packages/spec-haskell/packages/simple/src/Money/Systems/Superfluid/Instances/Simple/Types.hs index 6a28f5fcf5..1956b2cbb4 100644 --- a/packages/spec-haskell/packages/simple/src/Money/Systems/Superfluid/Instances/Simple/Types.hs +++ b/packages/spec-haskell/packages/simple/src/Money/Systems/Superfluid/Instances/Simple/Types.hs @@ -38,41 +38,46 @@ module Money.Systems.Superfluid.Instances.Simple.Types , ProportionalDistributionIndexID ) where -import Control.Applicative (Applicative (..)) +import Control.Applicative + ( Applicative (..) + ) import Data.Binary import Data.Coerce import Data.Default -import Data.Foldable (toList) -import Data.List (intercalate) +import Data.Foldable (toList) +import Data.List + ( intercalate + ) import Data.Proxy import Data.Type.Any import Data.Type.TaggedTypeable -import GHC.Generics (Generic) +import GHC.Generics + ( Generic + ) import Lens.Internal -import Text.Printf (printf) +import Text.Printf (printf) import Money.Systems.Superfluid.SystemTypes -- -import qualified Money.Systems.Superfluid.MonetaryUnitData.ConstantFlow as CFMUD -import qualified Money.Systems.Superfluid.MonetaryUnitData.DecayingFlow as DFMUD -import qualified Money.Systems.Superfluid.MonetaryUnitData.InstantValue as IVMUD -import qualified Money.Systems.Superfluid.MonetaryUnitData.MintedValue as MVMUD +import qualified Money.Systems.Superfluid.MonetaryUnitData.ConstantFlow as CFMUD +import qualified Money.Systems.Superfluid.MonetaryUnitData.DecayingFlow as DFMUD +import qualified Money.Systems.Superfluid.MonetaryUnitData.InstantValue as IVMUD +import qualified Money.Systems.Superfluid.MonetaryUnitData.MintedValue as MVMUD -- -import qualified Money.Systems.Superfluid.Agreements.Indexes.ProportionalDistributionCommon as PDCOMMON - -import qualified Money.Systems.Superfluid.Agreements.ConstantFlowAgreement as CFA -import qualified Money.Systems.Superfluid.Agreements.ConstantFlowDistributionAgreement as CFDA -import qualified Money.Systems.Superfluid.Agreements.DecayingFlowAgreement as DFA -import qualified Money.Systems.Superfluid.Agreements.InstantDistributionAgreement as IDA -import qualified Money.Systems.Superfluid.Agreements.InstantTransferAgreement as ITA -import qualified Money.Systems.Superfluid.Agreements.MinterAgreement as MINTA +import qualified Money.Systems.Superfluid.Agreements.ProportionalDistribution.Common as PDCOMMON +import qualified Money.Systems.Superfluid.Agreements.ProportionalDistribution.ConstantFlowDistributionAgreement as CFDA +import qualified Money.Systems.Superfluid.Agreements.ProportionalDistribution.InstantDistributionAgreement as IDA +import qualified Money.Systems.Superfluid.Agreements.Universal.ConstantFlowAgreement as CFA +import qualified Money.Systems.Superfluid.Agreements.Universal.DecayingFlowAgreement as DFA +import qualified Money.Systems.Superfluid.Agreements.Universal.InstantTransferAgreement as ITA +import qualified Money.Systems.Superfluid.Agreements.Universal.MinterAgreement as MINTA -- -import qualified Money.Systems.Superfluid.Agreements.Indexes.ProportionalDistributionIndex as PDIDX -import qualified Money.Systems.Superfluid.Agreements.Indexes.UniversalIndex as UIDX +import qualified Money.Systems.Superfluid.Agreements.ProportionalDistributionIndex as PDIDX +import qualified Money.Systems.Superfluid.Agreements.UniversalIndex as UIDX -- -import qualified Money.Systems.Superfluid.SubSystems.BufferBasedSolvency as BBS +import qualified Money.Systems.Superfluid.SubSystems.BufferBasedSolvency as BBS -- ===================================================================================================================== @@ -84,7 +89,7 @@ import qualified Money.Systems.Superfluid.SubSystems.BufferBasedSolvency -- - Name is a monicker of MakerDAO v1 WAD type. newtype Wad = Wad Integer - deriving newtype (Default, Eq, Enum, Real, Ord, Num, Integral, Binary, Value) + deriving newtype (Default, Eq, Enum, Real, Ord, Num, Integral, Binary, MonetaryValue) toWad :: (RealFrac a) => a -> Wad toWad x = Wad (round $ x * (10 ^ (18::Integer))) diff --git a/packages/spec-haskell/packages/simple/test/Money/Systems/Superfluid/ConstantFlowAgreement_test.hs b/packages/spec-haskell/packages/simple/test/Money/Systems/Superfluid/ConstantFlowAgreement_test.hs index 280cef30d1..88f59a54aa 100644 --- a/packages/spec-haskell/packages/simple/test/Money/Systems/Superfluid/ConstantFlowAgreement_test.hs +++ b/packages/spec-haskell/packages/simple/test/Money/Systems/Superfluid/ConstantFlowAgreement_test.hs @@ -5,14 +5,14 @@ module Money.Systems.Superfluid.ConstantFlowAgreement_test (tests) where import Control.Monad.IO.Class import Lens.Micro -import Test.Hspec (HasCallStack) +import Test.Hspec (HasCallStack) import Test.HUnit -import qualified Money.Systems.Superfluid.Agreements.ConstantFlowAgreement as CFA -import qualified Money.Systems.Superfluid.Agreements.Indexes.UniversalIndex as UIDX -import qualified Money.Systems.Superfluid.MonetaryUnitData.ConstantFlow as CFMUD +import qualified Money.Systems.Superfluid.Agreements.Universal.ConstantFlowAgreement as CFA +import qualified Money.Systems.Superfluid.Agreements.UniversalIndex as UIDX +import qualified Money.Systems.Superfluid.MonetaryUnitData.ConstantFlow as CFMUD -- -import qualified Money.Systems.Superfluid.Instances.Simple.System as SF +import qualified Money.Systems.Superfluid.Instances.Simple.System as SF -- import Money.Systems.Superfluid.TokenTester diff --git a/packages/spec-haskell/packages/simple/test/Money/Systems/Superfluid/DecayingFlowAgreement_test.hs b/packages/spec-haskell/packages/simple/test/Money/Systems/Superfluid/DecayingFlowAgreement_test.hs index e06e8f1ad9..f5373fdd0a 100644 --- a/packages/spec-haskell/packages/simple/test/Money/Systems/Superfluid/DecayingFlowAgreement_test.hs +++ b/packages/spec-haskell/packages/simple/test/Money/Systems/Superfluid/DecayingFlowAgreement_test.hs @@ -5,15 +5,15 @@ module Money.Systems.Superfluid.DecayingFlowAgreement_test (tests) where import Control.Monad.IO.Class import Lens.Micro -import Math.Extras.Double (fuzzyEq) -import Test.Hspec (HasCallStack) +import Math.Extras.Double (fuzzyEq) +import Test.Hspec (HasCallStack) import Test.HUnit -import qualified Money.Systems.Superfluid.Agreements.DecayingFlowAgreement as DFA -import qualified Money.Systems.Superfluid.Agreements.Indexes.UniversalIndex as UIDX -import qualified Money.Systems.Superfluid.MonetaryUnitData.DecayingFlow as DFMUD +import qualified Money.Systems.Superfluid.Agreements.Universal.DecayingFlowAgreement as DFA +import qualified Money.Systems.Superfluid.Agreements.UniversalIndex as UIDX +import qualified Money.Systems.Superfluid.MonetaryUnitData.DecayingFlow as DFMUD -- -import qualified Money.Systems.Superfluid.Instances.Simple.System as SF +import qualified Money.Systems.Superfluid.Instances.Simple.System as SF -- import Money.Systems.Superfluid.TokenTester diff --git a/packages/spec-haskell/utils/lhs2tex.sh b/packages/spec-haskell/utils/lhs2tex.sh index 512e75ac20..6ec489793f 100755 --- a/packages/spec-haskell/utils/lhs2tex.sh +++ b/packages/spec-haskell/utils/lhs2tex.sh @@ -1,15 +1,20 @@ #!/bin/sh -cd "$(dirname "$0")"/.. - -LHS_FILE=$1 +LHS_FILE=$(readlink -f "$1") [ -f "$LHS_FILE" ] || exit 1 -TEX_FILE="$(basename $LHS_FILE)" +cd "$(dirname "$0")"/.. { cat $1 -} | \ - sed -e '/\s*--.*/d' | # quirk: fix for haskell comments - sed -e 's/\\begin{code}/\\begin{minted}{haskell}/g' -e 's/\\end{code}/\\end{minted}/g' | # quirk: fix for haskell code blocks - cat +} | sed "1 d" | # remove emacs mode-line + awk ' + /\\begin{haddock}/ { h = 1; next } + /\\end{haddock}/ { h = 0; next } + /\\begin{code}/ { if (h) next } + /\\end{code}/ { if (h) next } + /^{-/ { if (h) next } + /^-}/ { if (h) next } + /^--}/ { if (h) next } + { print $0 }' | # process haddock documentation + cat # nop, just to rhyme. diff --git a/packages/spec-haskell/yellowpaper/Biblio.bib b/packages/spec-haskell/yellowpaper/Biblio.bib index 415674fa80..93089b650f 100644 --- a/packages/spec-haskell/yellowpaper/Biblio.bib +++ b/packages/spec-haskell/yellowpaper/Biblio.bib @@ -5,6 +5,114 @@ @book{von2013theory publisher={Ludwig von Mises Institute} } +@incollection{friedman1989quantity, + title={Quantity theory of money}, + author={Friedman, Milton}, + booktitle={Money}, + pages={1--40}, + year={1989}, + publisher={Springer} +} + +@article{ammous2018can, + title={Can cryptocurrencies fulfil the functions of money?}, + author={Ammous, Saifedean}, + journal={The Quarterly Review of Economics and Finance}, + volume={70}, + pages={38--51}, + year={2018}, + publisher={Elsevier} +} + +@misc{hardle2020understanding, + title={Understanding cryptocurrencies}, + author={H{\"a}rdle, Wolfgang Karl and Harvey, Campbell R and Reule, Raphael CG}, + journal={Journal of Financial Econometrics}, + volume={18}, + number={2}, + pages={181--208}, + year={2020}, + publisher={Oxford University Press} +} + +@article{hudak1992report, + title={Report on the programming language Haskell: a non-strict, purely functional language version 1.2}, + author={Hudak, Paul and Peyton Jones, Simon and Wadler, Philip and Boutel, Brian and Fairbairn, Jon and Fasel, Joseph and Guzm{\'a}n, Mar{\'\i}a M and Hammond, Kevin and Hughes, John and Johnsson, Thomas and others}, + journal={ACM SigPlan notices}, + volume={27}, + number={5}, + pages={1--164}, + year={1992}, + publisher={ACM New York, NY, USA} +} + +@book{jones2003haskell, + title={Haskell 98 language and libraries: the revised report}, + author={Jones, Simon Peyton}, + year={2003}, + publisher={Cambridge University Press} +} + +@article{marlow2010haskell, + title={Haskell 2010 language report}, + author={Marlow, Simon and others}, + year={2010} +} + +@book{jones2003haskell, + title={Haskell 98 language and libraries: the revised report}, + author={Jones, Simon Peyton}, + year={2003}, + publisher={Cambridge University Press} +} + +@article{peyton2000composing, + title={Composing contracts: an adventure in financial engineering (functional pearl)}, + author={Peyton Jones, Simon and Eber, Jean-Marc and Seward, Julian}, + journal={ACM SIGPLAN Notices}, + volume={35}, + number={9}, + pages={280--292}, + year={2000}, + publisher={ACM New York, NY, USA} +} + +@article{hughes2000generalising, + title={Generalising monads to arrows}, + author={Hughes, John}, + journal={Science of computer programming}, + volume={37}, + number={1-3}, + pages={67--111}, + year={2000}, + publisher={Elsevier} +} + +@article{bernstein1981concurrency, + title={Concurrency control in distributed database systems}, + author={Bernstein, Philip A and Goodman, Nathan}, + journal={ACM Computing Surveys (CSUR)}, + volume={13}, + number={2}, + pages={185--221}, + year={1981}, + publisher={ACM New York, NY, USA} +} + +@article{petri1962kommunikation, + title={Kommunikation mit automaten}, + author={Petri, Carl Adam}, + year={1962} +} + +@book{reisig2012petri, + title={Petri nets: an introduction}, + author={Reisig, Wolfgang}, + volume={4}, + year={2012}, + publisher={Springer Science \& Business Media} +} + @article{buldas2021unifying, title={A Unifying Theory of Electronic Money and Payment Systems}, author={Buldas, Ahto and Saarepera, M{\"a}rt and Steiner, Jamie and Draheim, Dirk}, @@ -12,3 +120,90 @@ @article{buldas2021unifying volume={2021}, year={2021} } + +@inproceedings{chakravarty2020extended, + title={The extended UTXO model}, + author={Chakravarty, Manuel MT and Chapman, James and MacKenzie, Kenneth and Melkonian, Orestis and Peyton Jones, Michael and Wadler, Philip}, + booktitle={International Conference on Financial Cryptography and Data Security}, + pages={525--539}, + year={2020}, + organization={Springer} +} + +@inproceedings{elliott1997functional, + title={Functional reactive animation}, + author={Elliott, Conal and Hudak, Paul}, + booktitle={Proceedings of the second ACM SIGPLAN international conference on Functional programming}, + pages={263--273}, + year={1997} +} + +@inproceedings{wan2000functional, + title={Functional reactive programming from first principles}, + author={Wan, Zhanyong and Hudak, Paul}, + booktitle={Proceedings of the ACM SIGPLAN 2000 conference on Programming language design and implementation}, + pages={242--252}, + year={2000} +} + +@inproceedings{hudak2002arrows, + title={Arrows, robots, and functional reactive programming}, + author={Hudak, Paul and Courtney, Antony and Nilsson, Henrik and Peterson, John}, + booktitle={International School on Advanced Functional Programming}, + pages={159--187}, + year={2002}, + organization={Springer} +} + +@book{scott1971toward, + title={Toward a mathematical semantics for computer languages}, + author={Scott, Dana S and Strachey, Christopher}, + volume={1}, + year={1971}, + publisher={Oxford University Computing Laboratory, Programming Research Group Oxford} +} + +@TechReport{Elliott2009-type-class-morphisms-TR, + author = {Conal Elliott}, + title = {Denotational design with type class morphisms (extended version)}, + institution = {LambdaPix}, + url = {http://conal.net/papers/type-class-morphisms}, + month = {March}, + number = {2009-01}, + year = 2009 +} + +@article{wood2014ethereum, + title={Ethereum: A secure decentralised generalised transaction ledger}, + author={Wood, Gavin and others}, + journal={Ethereum project yellow paper}, + volume={151}, + number={2014}, + pages={1--32}, + year={2014} +} + +@article{clarke2020profunctor, + title={Profunctor optics, a categorical update}, + author={Clarke, Bryce and Elkins, Derek and Gibbons, Jeremy and Loregian, Fosco and Milewski, Bartosz and Pillmore, Emily and Rom{\'a}n, Mario}, + journal={arXiv preprint arXiv:2001.07488}, + year={2020} +} + +@article{wadler2015propositions, + title={Propositions as types}, + author={Wadler, Philip}, + journal={Communications of the ACM}, + volume={58}, + number={12}, + pages={75--84}, + year={2015}, + publisher={ACM New York, NY, USA} +} + +@book{needles2013principles, + title={Principles of accounting}, + author={Needles, Belverd E and Powers, Marian and Crosson, Susan V}, + year={2013}, + publisher={Cengage Learning} +} diff --git a/packages/spec-haskell/yellowpaper/Paper.tex b/packages/spec-haskell/yellowpaper/Paper.tex deleted file mode 100644 index 2f9dfed38c..0000000000 --- a/packages/spec-haskell/yellowpaper/Paper.tex +++ /dev/null @@ -1,154 +0,0 @@ -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% Document Preamble -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -\documentclass{article} - -%% Base Folder for Inputs -\makeatletter -\def\input@path{{../tex/}} -\makeatother - -%% Import Packages -\usepackage{fvextra} -\usepackage{csquotes} -\usepackage{xcolor} -\usepackage{minted} - -%% Literate Haskell Macros - -%%% include common formatting -\input{lhsfmt.tex} -%%% for ignoring some code -\long\def\ignore#1{} -%%% for creating a paragraph -\newcommand{\lhsparagraph}[1]{\paragraph{#1}\mbox{}\\} - -%% Document Meta Data - -\title{Superfluid Money: Enabling Generalized Schemes For Monetary Units} - -\author{ - Miao, ZhiCheng (hellwolf)\\ - Co-Founder, Superfluid Finance\\ - miao@superfluid.finance -} - -\usepackage{biblatex} -\addbibresource{../tex/Biblio.bib} - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% Document Body -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -\begin{document} - -\maketitle - -\begin{abstract} - \begin{center} - (Some Distant Lyric as Placeholder) - - Money, money, money - - Must be funny - - In the rich man's world - - Money, money, money - - Always sunny - - In the rich man's world - - \ - - Ah, all the things I could do - - If I had a little money - - It's a rich man's world - - It's a rich man's world - \end{center} -\end{abstract} - -\section{Introduction} - -It should be fair to say, every aspects of money are controversial: the nature of money, the value of money, money and -banking, and monetary reconstruction. \cite{von2013theory} - -But less has been challenged is the underlying theory of money schemes, or often called payment systems. - -This yellow paper aims to add a new controversy, by introducing the concept of context into the money schemes and -generalizing existing theory for a new unifying theory of money schemes in Part 1. - -In Part 2, we also put forth a realization and specification of a version of contextual money scheme known enabled by a -system called Superfluid money. - -The yellow paper also uses literate Haskell as a technique to embed concepts and specifications as compilable code for -better verifiability and reusability. - -(STILL WIP) - -\newpage -\part{Philosophy of Money} -\newpage - -\section{Nature of Money} - -\section{Money Schemes} - -\subsection{A New Unifying Theory} - -\input{MoneyDistributionConcepts.tex} - -\subsection{Communism} - -\input{Communism.tex} - -\subsection{Context free Schemes} - -Most of the existing money schemes are of this category, where a money unit completely disregards its context and hence -its liquidity value and bearer are static until the next money redistribution occurs. - -Here are the type definitions of these money schemes defined in \cite{buldas2021unifying}: - -TODO... - -\subsection{Contextual Schemes} - -In contract, under the contextual money schemes, the liquidity value and maybe even bearer of a money unit can be a -function of context too. - -TODO... - -\newpage -\part{Superfluid Money} -\newpage - -\section{Real Time Balance} - -\section{Sub Systems} - -\section{Agreement Sub System} - -\section{Type of Agreements} - -\subsection{Transferable Balance Agreement} - -\subsection{Constant Flow Agreement} - -\subsection{Distribution Agreement} - -\subsection{Decaying Flow Agreement} - -\section{Solvency Sub Systems} - -\subsection{Buffer Based Solvency Systems} - -\section{Type Of Money Units} - -\newpage - -\printbibliography{} - -\end{document} diff --git a/packages/spec-haskell/yellowpaper/Paper1.tex b/packages/spec-haskell/yellowpaper/Paper1.tex new file mode 100644 index 0000000000..d75e171778 --- /dev/null +++ b/packages/spec-haskell/yellowpaper/Paper1.tex @@ -0,0 +1,1265 @@ +%%% -- -*- fill-column: 100; -*- + +%% Include the preamble at choice during processing. +\input{preamble.tex} + +\begin{document} + +\title{Denotational Semantics of General Payment Primitives, and Its Payment System} + +\author{\\ + Miao, ZhiCheng\\ + Co-founder, Superfluid Finance\\ + miao@superfluid.finance +} + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Title Page +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +\maketitle + +\begin{abstract} +Payment systems in the information age are still modeled after their analog predecessors. While +electronic money payment systems do utilize computing technology and the Internet, this paper +presents a case that true modernization can be reached by (a) making payments happening continuously +over time, (b) involving more than two parties in payment if necessary, (c) having compositional +financial contracts. + +This paper first explores the foundation of modern payment systems, which consists of a money +distribution model, payment primitives, payment execution systems of financial contracts, and +different forms of money mediums. Then the paper uses \textit{denotational semantics} to formally +define payment primitives for modern payment systems. By the end, this paper includes an overview of +the \textit{Superfluid protocol}, a reference implementation of the payment primitives, and its +payment system. + +This paper is the first in the series of yellowpapers about modern payment systems dubbed ``semantic +money.'' + +\end{abstract} + +\begin{versionhistory} + \vhEntry{0.9}{2022-10-19}{MZC}{For reviews} + \vhEntry{1.0}{2022-10-31}{MZC}{First release} +\end{versionhistory} + +\part*{Introduction} +%%%%%%%%%%%%%%%%%%%% + +It should be fair to say every aspect of money is controversial: the nature of money, the value of +money, money and banking, and monetary reconstruction. Two major schools of thought about the theory +of money are the \textit{Austrian school} (\cite{von2013theory}) and the \textit{Chicago school} +(\cite{friedman1989quantity}). That was before the appearance of the Internet-era version of +monetary reconstruction, broadly defined as cryptocurrency, which challenges theories of money +further and demands their updates (\cite{ammous2018can} \cite{hardle2020understanding}). + +This yellow paper does not intend to address these controversies; instead, it focuses on the +function of money. According to Von Mises: + +\begin{displayquote} +The function of money is to facilitate the business of the market by acting as a common medium of +exchange. \footfullcite[][Chapter One, Chapter I, § 1, p1]{von2013theory} +\end{displayquote} + +How do different forms of money perform this function, especially in the information age, when we +increasingly use electronic forms of money? + +This paper adds a new controversy to money, presenting a foundation of what constitutes +a \textit{modern payment system} and a set of \textit{payment primitives} for the system to +challenge the preconceived notions of how money can perform its function of medium of exchange. + +In part \ref{part:foundation}, we shall first explore the foundation. Here we present a formal +definition of a payment system and its components. We then select a few relevant approaches used in +computer science useful for modeling and defining formal specifications for the payment system. + +One of the approaches is \textit{denotational semantics}, which is used in part \ref{part:gpp} of +the paper to define the \textit{general payment primitives}. Along with the denotational semantics, +the paper also includes a restatement\footnote{It is a borrowed term from common law: ``restatement +of the law''. In our case, the denotative mathematical laws.} of it in \textit{Haskell programming +language} (\cite{hudak1992report} \cite{jones2003haskell} \cite{marlow2010haskell}). + +In part \ref{part:sf}, we introduce a reference implementation of the general payment primitives and +its payment system called \textit{Superfluid Protocol}, along with its overview. + +The end of the paper also includes notes on possible further investigations. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\part{Foundation}\label{part:foundation} +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%%%%%%%%%%%%%%%%%%%%%%%% +\section{Payment System} +%%%%%%%%%%%%%%%%%%%%%%%% + +Here we present a definition of a payment system and its components. + +\paragraph{Payment system} + +It is solely defined by these components: + +\begin{itemize} + \item \textit{money distribution} models how monetary value is distributed amongst +bearers \footnote{(Banking \& Finance) a person who presents a note or bill for payment. - Collins +English Dictionary}, + + \item \textit{payment primitives} update money distribution, + + \item \textit{payment execution environment} performs payment primitives encoded + in \textit{financial contracts}, + + \item and \textit{forms of money medium} are the ``user interfaces'' of money for the bearers. +\end{itemize} + +\paragraph{Modernization} + +For its modern upgrade, the system should also have these properties: + +\begin{itemize} + \item Money can be distributed continuously over time, as opposed to being in discrete chunks. + + \item Payment primitives can involve more than two parties, as opposed to being only for a +sender and a receiver. + + \item Financial systems should be compositional. +\end{itemize} + +\subsection{Money Distribution} +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +A representation of money and its distribution proposed in ``a unifying theory'' +by \citeauthor{buldas2021unifying} involves the following components: + +\begin{itemize} + \item U is the set of monetary units. + + \item $\nu: U \rightarrow \mathbb{N}$ is the value function defining the value $\nu(u)$ of every +value unit u. The set $\mathbb{N}$ is the set of all natural numbers, but instead, we can use any +set of numerals that are totally ordered (\eg,\ integers, real numbers). + + \item $\beta: U \rightarrow \mathcal{B}$ is the bearer function defining the bearer $\beta(u)$ +of a unit. $\mathcal{B}$ is the set of possible bearers. The bearer is usually a legal construction +defining any type of legal entity, such as a person, a family, a company, a state institution, etc. +\end{itemize} + +This discrete nature of this money distribution model is schematically depicted +in figure \ref{fig:discrete-md}. + +\begin{figure}[h] + \centering + \includegraphics[width=0.5\textwidth]{../assets/discrete-money-distribution.png} + \caption{Schematic representation of discrete money distribution} + \label{fig:discrete-md} +\end{figure} + +\subsubsection{Adding Context $\gamma$} + +But the discrete nature of the model does not provide the necessary element for us to add the +desired properties to the payment system. So here we propose a modification that involves the usage +of \textit{context ($\gamma$)}: + +\begin{figure}[h] + \centering + \includegraphics[width=0.5\textwidth]{../assets/money-distribution-with-ctx.png} + \caption{Schematic representation of money distribution with context} + \label{fig:md-with-ctx} +\end{figure} + +Note that in this model, context can be updated independently, while value functions of the same +money distribution can produce different monetary values. + +\paragraph{Components of Context} + +Here are some possible components of context and how they work: + +\begin{itemize} + \item If time (t) is included in the context, then the monetary value of each monetary unit can +vary continuously over time. Time is part of the physical reality, hence not changeable by the +actors in the payment system. + + \item Any subset of monetary units can also have their value functions depending on the same +information in context. This could enable payment primitives that involve many parties. This set of +information in context is referred to as \textit{ctx :: SharedContext}; they can be changed over +time by the actors in the payment system. +\end{itemize} + +For the purpose of this paper, the model of context is: $\gamma = t \times ctx$. + +\subsubsection{Haskell Code Conventions Used} + +Before the first time this paper includes specification in Haskell, here are some highlights of +conventions in the code style. + +\paragraph{Language Extensions} + +The specification is written in Haskell with \textit{GHC2021 language set}\footnote{The complete set +of the extensions is enumerated in +\url{https://downloads.haskell.org/ghc/latest/docs/users_guide/exts/control.html\#extension-GHC2021}.}. + +Other notable extensions used in the paper are: $FunctionalDependencies$, $TypeFamilies$, +$TypeFamilyDependencies$, $GADTs$. + +\paragraph{Indexed Types and Type Equality} + +To make the specification free of specific choices of core data types in +implementation, \textit{FunctionalDependencies}, \textit{TypeFamilies}, +and \textit{TypeFamilyDependencies} are extensively used. As a consequence, the type signature can +look very cluttered. In order to address that, type quality operator $(\sim)$ is also used. Here is +an example snippets: + +\begin{code} + type (~) :: forall k. k -> k -> Constraint + + monetaryValue :: ( mv ~ MD_MVAL md + , mu ~ MD_MU md + , ctx ~ MD_CTX md + ) + => md -> (mu, ctx) -> mv +\end{code} + +The otherwise tedious type family synonyms ``\mintinline{haskell}{MD_XYZ md}'' are rewritten with $mv$ +$mu$ $ctx$ with the help of $(\sim)$ operator. + +\subsubsection{Haskell Definition Of Money Distribution} + +\input{MoneyDistribution.lhs.tex} + +\subsection{Payment Primitives} +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +A payment primitive is a data type with a generator function that produces payment primitive from +the shared context $ctx$, primitive specific argument $args$, and timestamp $t$, along with an update +of the shared context. Type signature \ref{eq:genPrim} is for the generator of payment +primitive \textit{a}: + +\begin{equation}\label{eq:genPrim} + genPrim_a :: ctx \rightarrow args_a \rightarrow t \rightarrow prim \times ctx +\end{equation} + +Payment primitive data then can be used to create a delta update of money +distribution\footnote{Specifically being monoidal, that is in short a set that has associative +binary operation and an identity element. See https://ncatlab.org/nlab/show/monoid.}: + +\begin{equation} + runPrim :: prim \rightarrow md +\end{equation} + +Loosely speaking, it is considered primitive, if it can not be broken down into other existing +primitives, which result in the same money distribution; additionally, primitives should be the only +constructs in a payment system that can update money distribution. + +Updates are \textit{monoidal} so that they can be incremental, and their parallel executions can be +modeled. + +The best-known primitive is an instant transfer of monetary value between one monetary unit to +another. The introduction of context enables more primitives to be defined, and this will be +discussed in part \ref{part:gpp}. + +\subsection{Payment Execution Environment} +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +The purpose of a payment execution environment is to perform the actual payment primitives, where +their computation interface, parallel evaluation strategies, and payment system solvency are defined. + +It is out of scope for this paper to survey in-depth the problem space of the operational semantics +of payment execution environments. Nonetheless, a simplified model and some potential extensions are +discussed briefly to place payment primitives in the big picture. + +\subsubsection{Composing Financial Contracts} + +\input{FinancialContract.lhs.tex} + +\subsubsection{Simplified Execution Environment Models} + +\input{PaymentExecutionEnvironment.lhs.tex} + +\subsection{Payment System Solvency} + +While money distribution does not assign meaning to the range of monetary values, negative values +can have special meanings in real-world applications. In the following analysis, we call any money +unit with a negative monetary value \textit{insolvent}. + +The detailed analysis of these solvency models is out of the scope of this paper. + +\paragraph{Buffer Based Solvency Treatment} + +In a non-deterministic execution environment, we cannot determine when any financial contract will +be executed. That means there is always a chance a monetary unit could reach negative monetary +value. + +To mitigate the uncertainty of execution time, we introduce a concept called \textit{buffer}. A +buffer has a monetary value set aside in a solvency conditional financial contract, such that if a +solvency condition arises, the buffer may be drawn to cover the loss introduced by the +non-deterministic timing of the execution. + +\paragraph{Deterministic Solvency Treatment} + +Since \textit{fcNext} returns what is the following financial contract executable at a specific +time, this deterministic property eliminates the need for the buffer. + +On the other hand, it introduces a different type of systemic risk: a kind of +denial-of-service. Because the complexity of $fcUpdate$ is $O(log(n))$, the system may not be able +to advance its system time until all the following executable contracts are executed. + +\subsection{Money Mediums} +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +\begin{displayquote} +A useful observation about existing money schemes is that they all have some kind of monetary units +that are physical or digital representations of money. Examples are bills, coins, bank accounts, +Bitcoin UTXOs, etc. \footfullcite[][P3]{buldas2021unifying} +\end{displayquote} + +We call them money mediums, and we further separate them into two big groups: + +\begin{itemize} + \item \textit{Token and its Accounts} - \eg,\ bank currency accounts. Each token is its own +centralized execution environment; bearers access their monetary value through their accounts and +execute financial contracts through the token. + + \item \textit{Note} - \eg,\ federal reserve notes, bills, coins and Bitcoin UTXOs, etc. The +execution environment is independent of the notes, but it needs notes to complete the execution of +financial contracts. +\end{itemize} + +One of the main differences is from the ``user interface'' perspective. A bearer expects to keep +many notes in hands while maybe only needing a few accounts for each token. Also, it is up to bearers +to keep track of all their notes, while tokens can keep track of most of the states for bearers; +hence notes are more decentralized and tokens are more centralized. Some also argue note-like model +is better for complex concurrent and distributed computing environment +\footfullcite[][P2]{chakravarty2020extended}. + +\subsubsection{Haskell Definition Of Money Mediums} + +\input{MoneyMedium.lhs.tex} + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\section{Relevant Approaches} +%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +The focus of this paper is to formally define a set of payment primitives that modernize our payment +systems. However, to prevent reinventing wheels, we should first discuss some approaches in computer +science that help tackle this challenge. + +\subsection{Functional Reactive Programming} +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +Recall that we want our modern payment system to handle money distribution continuously over time +and its financial contracts to be compositional. A very closely related software design paradigm +best known to address those needs is \textit{functional reactive programming (FRP)}. It was first +introduced by Conal Elliott \& Paul Hudak in solving multimedia animations +(\cite{elliott1997functional}). Later, Hudak also worked on \cite{hudak2002arrows} and +\cite{wan2000functional}, making FRP a more general framework for programming hybrid systems with +continuous behaviors in a high-level, declarative manner. + +After FRP got more adoption, it evolved into some variations that support discrete semantics and +some variations better suited for interactive systems. However, in this paper, we will stick to and +revisit the basic constructs of the original formulation used in \cite{elliott1997functional} from +which we will draw inspiration. + +\paragraph{Temporal Modeling and Behaviors} + +Values that vary over continuous time are called \textit{behaviors}. They are first-class values and +are built up compositionally. That is what we want in modern payment systems also. The semantic +function of $\alpha$-behaviors produces the value of type $\alpha$ of a behavior at a given time: + +\begin{equation} + at : Behavior_{\alpha} \rightarrow Time \rightarrow \alpha +\end{equation} + +\paragraph{Event Modeling} + +Like behaviors, \textit{events} are first-class values too. The semantic function of $\alpha$-event +describes the time and information associated with an \textit{occurrence} of the event: + +\begin{equation} + occ : Event_{\alpha} \rightarrow Time \times \alpha +\end{equation} + +In modern payment systems, the payment primitives executed in payment execution environments are +one type of event. More types of events can be read from the original paper \footfullcite[][section +2.3 Semantics of Events]{elliott1997functional}. + +\paragraph{Reactivity} + +They key to modeling the payment execution environment using FRP is the \textit{reactivity}, which +makes behaviors reactive. Specifically, the behavior \textit{b untilB e} exhibits b's behavior +until \textit{e} occurs, and then switches to a new behavior encoded in \textit{e}: + +\begin{equation} + \begin{split} + &untilB : Behavior_{\alpha} \rightarrow Event_{Behavior_{\alpha}} \rightarrow Behavior_{\alpha} \\ + &at\ [\![b\ until\ B]\!]\ t = if\ t\ \leq\ t_{e}\ then\ at\ [\![b]\!]\ t\ else\ at\ [\![b']\!]\ t \\ + &\qquad where (t_e, b') = occ[\![e]\!] + \end{split} +\end{equation} + +Note that $[\![.]\!]$ is the denotational semantics notation to be introduced later. + +In the context of modern payment systems, the occurrences of payment primitives (events) change the +behaviors of monetary units in terms of how much monetary value it has over continuous. The +building blocks of the financial contracts should be about modeling these events and their +reactivity declaratively. + +\subsection{Denotational Semantics} +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +We also want a formal and precise specification of payment primitives. The title of the paper +includes \textit{denotational semantics}: ``it is a \textit{compositional} style for precisely +specifying the meanings of languages, invented by Christopher Strachey and Dana Scott in the 1960s +(\cite{scott1971toward})'', and Conal Elliott proposed that denotational semantics can also be +applied to \textit{data types within a programming language} +\footfullcite[][section 2, denotational semantics and data types]{Elliott2009-type-class-morphisms-TR}. + +To create denotational semantics for each syntactic category $\mathcal{C}$, we should specify: + +\begin{itemize} +\item a mathematical model $[\![\mathcal{C}]\!]$ of meanings, and +\item a semantic function $[\![.]\!]_{\mathcal{C}} :: \mathcal{C} \rightarrow [\![\mathcal{C}]\!]$. +\end{itemize} + +The syntactic category we are interested in is \textit{payment primitives}, which we should treat as +FRP-style \textit{Behavior} data types. In the following chapters, it is also unambiguously referred +to in short as $[\![.]\!]$. Various $[\![.]\!]$ must be compositional, \ie,\ must be defined by +structural recursion. + +It is important to note that our purpose in using the denotational semantics is to give precise +meaning to payment primitives independent of their implementations (which deal with performance, +optimization, side effects, etc.). We will spend part \ref{part:gpp} exploring the denotational +semantics for general payment primitives in modern payment systems. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\part{General Payment Primitives}\label{part:gpp} +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +The set of payment primitives supported in a payment system is general if (a) the monetary value of +each monetary unit can vary continuously over time (b) monetary values can logically be shifted +between two or more monetary units. + +The extent of the generality of each payment system may vary. This paper introduces a set of payment +primitives that is general and serves as a starting point for the readers to explore the space of +modern payment systems. + +The specifications will be formally defined using denotational semantics, along with a restatement +in Haskell also applied as a constructive artifact friendly to computer environment\footnote{One may +argue for a restatement in \textit{Agda} instead, for it has a richer dependently-type system for +desirable constructive proofs. This matter will be dealt in the ``future investigations'' section of +this paper.}. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\section{Denotational Semantics} +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +Here is the convention for symbols used in the formulas: + +\begin{itemize} +\item \textbf{M} is the \textit{model for money distribution}. +\item \textbf{m} is for \textit{money distributions}. +\item \textbf{U} is the \textit{set of all money units} in money distribution. +\item \textbf{u} is for \textit{monetary units}. +\item $\boldsymbol{\nu}$ is for \textit{monetary values}. +\item \textbf{t} is for \textit{time}. +\end{itemize} + +\subsection{$\mathcal{M}$ - Syntax for Money Distribution} + +Recall in the definition of payment system previously, money distribution sits in the core of the +system, and that is the syntactic category $\mathcal{M}$ we will be dealing with: + +\begin{itemize} + \item The meaning of the mathematical \textit{model} $[\![\mathcal{M}]\!]$ is money distribution. + + \item Semantic function $[\![.]\!] :: \mathcal{M} \rightarrow [\![\mathcal{M}]\!] $ evaluates +the expression of money distribution, payment primitives, etc. +\end{itemize} + +\subsection{Money Distribution} +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +\paragraph{Model} + +This is the model for money distribution. + +\begin{equation}\label{md_model} + \begin{split} + [\![M\ u\ t\ \nu]\!] &= u \rightarrow t \rightarrow \nu \\ + [\![.]\!] &= M\ u\ t\ \nu \rightarrow (u \rightarrow t \rightarrow \nu) \\ + \end{split} +\end{equation} + +\paragraph{Monoidal} + +With this model, $\mathcal{M}$ is also monoidal, hence compositional. + +\begin{equation} + \begin{split} + [\![\emptyset]\!] &= \lambda u \rightarrow \lambda t\ \rightarrow \emptyset \\ + [\![m_a \oplus m_b]\!] &= \lambda u \rightarrow \lambda t\ \rightarrow + [\![m_a]\!]\ u\ t\ +\ [\![m_b]\!]\ u\ t \\ + \end{split} +\end{equation} + +Knowing that function application category $a \rightarrow b$ is also monoidal: + +\begin{equation} + \begin{split} + \emptyset &= \lambda a \rightarrow \emptyset \\ + f \oplus g &= \lambda a \rightarrow f\ a \oplus g\ a \\ + \end{split} +\end{equation} + +With some substitutions, we get the desired \textit{monoid homomorphism} property for $\mathcal{M}$. + +\begin{equation} + \begin{split} + [\![\emptyset]\!] &= \emptyset \\ + [\![m_a \oplus m_b]\!] &= [\![m_a]\!] \oplus [\![m_b]\!] \\ + \end{split} +\end{equation} + +\subsection{Payment Primitives} +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +A payment primitive \textit{prim} is a model that produces a monoidal money distribution, +semantically representing an ``update'' to a money distribution: + +\begin{equation} + [\![prim]\!] = [\![\Delta\ m]\!] +\end{equation} + +\paragraph{Law of Conservation of Value} + +First of all, money distribution must obey the \textit{law of conservation of value}: + +\begin{equation} + \forall t \in \mathbb{R} {\displaystyle \sum_{u \in U} [\![m]\!]\ u\ t = 0} +\end{equation} + +That is to say, at any given time, the sum of the monetary value of all monetary units shall always +equal zero. Zero is used to keep the semantical meaning of payment systems simple and elegant. In +its applications, a payment system may use some special monetary unit accounting for negative +monetary values to accommodate concepts such as mining, minting, money printing, etc. + +\paragraph{Restricted Money Distribution} + +To come up with such lawful money distribution, we can divide and conquer. In a payment system, if +we restrict that only payment primitives can provide ``updates'' to their money distribution so that +money distribution is only a result of a sequence of payment primitive updates; then, we can have +these axioms as the basis for the proof. + +\paragraph{Axiom A of Restricted Money Distributions} + +First, we must impose the law of conservation of value on payment primitives as an axiom in order to +prove inductively that such restricted money distribution satisfies the law of conservation of value +too. + +\begin{equation} + \begin{split} + &\textit{law of conservation of value for payment primitives} \\ + &\forall t \in \mathbb{R} ({\displaystyle \sum_{u \in U} [\![prim]\!]\ u\ t = 0}) \\ + \end{split} +\end{equation} + +\paragraph{Axiom B of Restricted Money Distributions} + +\begin{equation} + \begin{split} + &\textit{money distribution consists of updates from payment primitives} \\ + &[\![m]\!] = [\![prim_1]\!] \oplus [\![prim_2]\!] \oplus [\![prim_3]\!] \oplus \dotsb + \end{split} +\end{equation} + +\paragraph{Proof of Restricted Money Distributions} + +satisfying the law of conservation of value. + +\begin{equation} + \begin{split} + &\textbf{In addition to the axioms, given:} \\ + \\ + &\textit{(base case with empty money distribution is trivial)} \\ + &\forall t \in \mathbb{R} {\displaystyle \sum_{u \in U} [\![\emptyset]\!]\ u\ t = 0} \\ + \\ + &\textit{(monoid homomorphism)} \\ + &{\displaystyle \sum_{u \in U} [\![prim_1 \oplus prim_2]\!]\ u\ t} = + {\displaystyle \sum_{u \in U} [\![prim_2]\!]\ u\ t} \oplus + {\displaystyle \sum_{u \in U} [\![prim_2]\!]\ u\ t} \\ + \\ + &\textbf{We have:} \\ + &\forall t \in \mathbb{R}. \\ + &{\displaystyle \sum_{u \in U} [\![m]\!]\ u\ t} \\ + &\textit{(applying Axiom B)} \\ + = &{\displaystyle \sum_{u \in U} + ([\![prim_1]\!] \oplus + [\![prim_2]\!] \oplus + [\![prim_3]\!]\oplus \dotsb) + }\ u\ t + \\ + &\textit{(applying monoid homomorphism)} \\ + = &{\displaystyle \sum_{u \in U} [\![prim_1]\!]\ u\ t} \oplus + {\displaystyle \sum_{u \in U} [\![prim_2]\!]\ u\ t} \oplus + {\displaystyle \sum_{u \in U} [\![prim_3]\!]\ u\ t} \oplus \dotsb + \\ + &\textit{(applying Axiom A)} \\ + =&\ 0 + \\ + \blacksquare + \end{split} +\end{equation} + +\subsection{One-to-One Payment Primitives} +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +Here are the primitives involving a sender ($u_a$) and a receiver ($u_b$) (one-to-one payments). + +\paragraph{Transfer} + +Instant transferring of a fixed amount of monetary value $x$: + +\begin{equation} + \begin{split} + [\![transfer\ u_a\ u_b\ x]\!] &= + \lambda u \rightarrow \lambda t \rightarrow \\ + &\begin{cases} + -x & (u_a = u) \\ + x & (u_b = u) \\ + 0 & (otherwise) + \end{cases} + \end{split} +\end{equation} + +\paragraph{(Constant) Flow} + +Flowing of monetary value at a constant rate of $r$ at time $t'$: + +\begin{equation} + \begin{split} + [\![flow\ u_a\ u_b\ r\ t']\!] &= + \lambda u \rightarrow \lambda t \rightarrow \\ + &\begin{cases} + -r \cdot (t - t') & (u_a = u) \\ + r \cdot (t - t') & (u_b = u) \\ + 0 & (otherwise) + \end{cases} + \end{split} +\end{equation} + +\paragraph{Decaying Flow} + +Another way monetary value could flow is through an exponential decay +function\footnote{\url{https://en.wikipedia.org/wiki/Exponential_decay}.}. + +Symbolically, this process can be expressed by the following differential equation, where N is the +quantity and $\lambda$ is a positive rate called the exponential decay constant: + +\begin{equation} + {\displaystyle {\frac {dN}{dt}}=-\lambda N} +\end{equation} + +The solution to this equation (see derivation below) is: + +\begin{equation} + {\displaystyle N(t)=N_{0}e^{-\lambda t}} +\end{equation} + +But a more convenient semantics of it uses the parameter called ``distribution limit'' ($\theta$) +instead. In this formulation, a decaying flow distributes $\epsilon$ amount of monetary value at a +rate started at $\alpha$ and halving every time period of $\displaystyle \frac{ln(2)}{\lambda}$: + +\begin{equation} + \begin{split} + [\![decayingFlow\ u_a\ u_b\ \theta\ \lambda\ t']\!] &= + \lambda u \rightarrow \lambda t \rightarrow \\ + &\begin{cases} + {\displaystyle \alpha \cdot e^{-\lambda (t' - t)} - \theta} & (u_a = u) \\ + {\displaystyle -\alpha \cdot e^{-\lambda (t' - t)} + \theta} & (u_b = u) \\ + 0 & (otherwise) + \end{cases} + \end{split} +\end{equation} + +For the simplicity of the later discussion, this form of payment primitive is omitted. + +\subsection{Index Abstraction} +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +To make primitives support more than two parties, we introduce an abstraction called \textit{index}. + +An \textit{index} (we use symbol $k$ for them) has a function $\rho$ which produces a real number for +each monetary unit: + +\begin{equation} + \rho\ k :: u \rightarrow \mathbb{R} +\end{equation} + +To make it meaningful, it must satisfy the following law: + +\begin{equation} + \displaystyle \sum_{u \in U} \rho\ k\ u = 1 +\end{equation} + +Semantically, it represents a proportion of each money unit, and they must add up to 1. + +\subsection{Indexed Primitives} + +Let's generalize the one-to-one payment primitives using index abstraction. We use $I$ subscript for +the indexed versions of the primitives. + +\paragraph{Indexed Transfer} + +\begin{equation} + \begin{split} + [\![transfer_I\ k_a\ k_b\ x]\!] &= + \lambda u \rightarrow \lambda t \rightarrow \\ + &-x \cdot \rho\ k_a\ u + x \cdot \rho\ k_b\ u + \end{split} +\end{equation} + +\paragraph{Indexed (Constant) Flow} + +\begin{equation} + \begin{split} + [\![flow_I\ k_a\ k_b\ r\ t']\!] &= + \lambda u \rightarrow \lambda t \rightarrow \\ + &-r \cdot (t - t') \cdot \rho\ k_a\ u + r \cdot (t - t') \cdot \rho\ k_b\ u + \end{split} +\end{equation} + +It should be straightforward to prove that they also satisfy the \textit{axiom A of restricted money +distribution} thanks to the law of the index. + +\paragraph{Universal Index} + +Now one to one payment primitives can be redefined using \textit{universal index} ($ku_{any}$): + +\begin{equation} + \rho\ ku_{a} = \lambda u \rightarrow if\ u\ =\ u_a\ then\ 1\ else\ 0 +\end{equation} + +It means that it is an index \textit{universally} available for each monetary unit, and its +proportion is always 1 for that monetary unit and 0 for all others. + +\paragraph{Proportional Distribution Primitives} + +A special case of the indexed primitives is to fix the sender side to be an \textit{universal +index}. + +\subsection{Network Abstraction} + +An even more general abstraction is to model participants involved a payment +primitive \textit{network} (we use the symbol $w$ for them). + +It has the function $\rho$ as in \textit{index}, and it satisfies a different law: + +\begin{equation} + \displaystyle \sum_{u \in U} \rho\ w\ u = 0 +\end{equation} + +\subsection{Networked Primitives} + +Let's generalize the basic payment primitives using network abstraction. We use $N$ subscript for +the indexed versions of the primitives. + +\paragraph{Shift} + +We rename $transfer$ to $shift$ for networked instant payment primitive: + +\begin{equation} + \begin{split} + [\![shift_N\ w\ x]\!] &= + \lambda u \rightarrow \lambda t \rightarrow x \cdot \rho\ w\ u + \end{split} +\end{equation} + +\paragraph{Networked (Constant) Flow} + +\begin{equation} + \begin{split} + [\![flow_N\ w\ r\ t']\!] &= + \lambda u \rightarrow \lambda t \rightarrow r \cdot (t - t') \cdot \rho\ w\ u + \end{split} +\end{equation} + +\subsection{Generality vs. Optimization} + +The question now is, should we use the most general form of semantics to guide implementation? + +The answer is most likely a no. Not because it is not useful; after all mathematical formula is +about truth and perhaps also elegance in it, but because of we may miss optimization opportunities +needed in implementation when more specialized versions are used instead\footnote{We will not though +discuss the subjective aspects, \eg\ in software engineering principles such as YAGNI +(\url{https://en.wikipedia.org/wiki/You_aren't_gonna_need_it}), or user experience perspective of +the payment primitives.}. + +In part \ref{part:sf}, we will look into a reference implementation where these optimizations are +made. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\section{Restatement in Haskell} +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +\input{PaymentPrimitives.lhs.tex} + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\part{Superfluid Protocol - A Reference Implementation}\label{part:sf} +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +\textit{Superfluid protocol} (``the protocol'')\footnote{The code base is available +at \url{https://github.com/superfluid-finance/protocol-monorepo/}.} is the first implementation of +the \textit{denotational semantics of payment primitives} (though before it was formalized and named +so) on \textit{Ethereum Virtual Machine} (\cite{wood2014ethereum}). The first version of the protocol +is written in Solidity programming language \footnote{\url{https://soliditylang.org/} - About +Solidity programming language.}. + +To better serve as a reference implementation of the \textit{modern payment system} formalized by +this paper and its sequels, an implementation in Haskell was created. It aims to implement the full +specifications of (a) the denotational semantics of payment primitives, (b) compositional financial +contracts for the payment primitives, (c) token \& note model of money mediums, and their execution +environment. + +This paper covers the overview of the implementation with regard to (a). + +\section{Real Time Balance} + +For the purpose of compositional financial contracts, it is useful to separate monetary values that +bearers can use immediately (called \textit{untappedValue} in code) from ones that are set aside for +other financial purpose. The concept of \textit{real time balance} is thus created. \textit{Real +time balance} is a functorful\footnote{Bartosz Milewski has an excellent series on +functors: \url{https://bartoszmilewski.com/2015/01/20/functors/}, where he uses the term +``functorful'' to convey the idea of generalized container.} of monetary values. It can be converted +to a single monetary value. + +Since this is not the subject of this paper, it suffices to show its definition instead: + +\begin{code} +-- Note that we omit the definition of AnyTypedValue here, +-- since it is not relevant to the idea here. + +class ( MonetaryValue v + , Foldable rtbF + , Monoid (rtbF v) + , Eq (rtbF v) + ) => RealTimeBalance rtbF v | rtbF -> v where + + -- | Convert a single monetary value to a RTB value. + valueToRTB :: Proxy rtbF -> v -> rtbF v + + -- | Net monetary value of a RTB value. + netValueOfRTB :: rtbF v -> v + netValueOfRTB = foldr (+) def + + -- | Convert typed values to a RTB value. + typedValuesToRTB :: [AnyTypedValue v] -> rtbF v + + -- | Get typed values from a RTB value. + typedValuesFromRTB :: rtbF v -> [AnyTypedValue v] +\end{code} + +In the paper, we refer to the real time balance values as $rtb$, and its function $netValueOfRTB$ is +what matters the most here, it converts any $rtb$ to the \textit{monetary value} which is what the +model of money distribution needs. + +\section{Agreement Framework} + +The main tasks of the implementer of \textit{denotational payment primitives} are: + +\begin{itemize} + \item preserve the program correctness (and if possible, with proof of equivalence), + \item optimize for efficient computations, + \item and provide a software interface for it. +\end{itemize} + +The protocol introduces \textit{agreement framework} to address the optimization needs and to +provide a consistent software interface called ``agreement'' with agreement laws for reasoning about +the correctness. + +\subsection{Monetary Unit Data (MUD)} + +Recall that all monetary units have a monetary value function: $ \nu :: u \rightarrow \mathbb{N}$. + +Agreement framework defines a concept called \textit{monetary unit data}, and each monetary unit has +a set of them: + +\begin{code} +class ( SuperfluidCoreTypes sft + ) => MonetaryUnitDataClass mud sft | mud -> sft where + -- | π function - balance provided (hear: π) by the monetary unit data. + balanceProvided + :: forall t rtb. + -- indexed type aliases + ( t ~ SFT_TS sft + , rtb ~ SFT_RTB sft + ) + => mud -> t -> rtb + +-- | A semigroup constrained monetary unit data type class. +-- +-- Note: a. ~mud~ that doesn't have a binary function may also be referred +-- to as "non-scalable" ~mud~. +-- +-- a. What can make a ~mud~ "scalable" then is exactly when it is an +-- actual semigroup. Since a new state can be merged onto the +-- previous state to a new single state. It is still worth mentioning +-- that it is only a sufficient condition, since a monoid could still +-- "cheat" by linearly grow its data size on each binary operation. +class ( MonetaryUnitDataClass smud sft + , Semigroup smud + ) => SemigroupMonetaryUnitData smud sft +\end{code} + +The $\pi$ function produces \textit{real time balance} at a particular time for \textit{monetary +unit data}. How a monetary unit produces monetary value from its set of monetary unit data is +described in the ``hierarchy of agreements'' section. + +\subsection{Agreement Contract} + +Recall how a \textit{payment primitive} generator should look like: + +\begin{equation} + genPrim_a :: ctx \rightarrow args_a \rightarrow t \rightarrow prim \times ctx +\end{equation} + +Shared context $ctx$ is clearly the only data can be used by optimization, since it is updated each +time a primitive is generated. In the agreement framework, a data type \textit{agreement contract} +in the shared context is to fulfill this role: + +\begin{code} +-- | Agreement contract type class. +class ( SuperfluidCoreTypes sft + , Default ac + , MonetaryUnitDataClass ac sft + , Traversable (AgreementOperationOutputF ac) -- <= Foldable Functor + , Monoid (AgreementOperationOutput ac) + ) => AgreementContract ac sft | ac -> sft where + + -- | ω function - apply agreement operation ~ao~ (hear: ω) to the agreement + -- operation data ~ac~ to get a tuple of: + -- + -- 1. An updated ~ac'~. + -- + -- 2. A functorful delta of agreement monetary unit data ~mudsΔ~, which + -- then can be appended to existing ~mudΔ~. This is what can make an + -- agreement scalable. + applyAgreementOperation + :: forall t ao aoo. + -- indexed type aliases + ( t ~ SFT_TS sft + , ao ~ AgreementOperation ac + , aoo ~ AgreementOperationOutput ac + ) + => ac -> ao -> t -> (ac, aoo) + + -- | φ' function - functorize the existential semigroup monetary unit data + -- of agreement operation output + functorizeAgreementOperationOutput + :: forall any_smud muds f. + ( any_smud `IsAnyTypeOf` MPTC_Flip SemigroupMonetaryUnitData sft + , MonetaryUnitDataClass any_smud sft + -- indexed type aliases + , muds ~ AgreementOperationOutput ac + , f ~ AgreementOperationOutputF ac + ) + => Proxy any_smud + -> muds -> f any_smud + + data family AgreementOperation ac :: Type + data family AgreementOperationOutputF ac :: Type -> Type + type family AgreementOperationOutput ac = (smuds :: Type) | smuds -> ac +\end{code} + +The $\omega$ function ($applyAgreementOperation$) is the $genPrim$ in the agreement framework. It +takes in a agreement contract, an operation onto it and the current time; then it spits out an +update of the agreement contract and a functorful of new delta of monetary unit data. + +Figure \ref{fig:ac-omega} is a illustration of the $\omega$ function ``machinery''. + +\begin{figure}[H] + \centering + \includegraphics[width=1\textwidth]{../assets/agreement-contract-omega-function.png} + \caption{Agreement Contract $\omega$ Function} + \label{fig:ac-omega} +\end{figure} + +It also illustrates that it is expected the process should respect the \textit{law of conservation +of value} mandated by the \textit{restricted money distribution} model. + +It is also important to note that \textit{AgreementContract} itself is also +a \textit{MonetaryUnitDataClass}, but it is not a semigroup, hence it is used to replace the +previous agreement contract data. This has optimization implications, as in that if agreement +contracts do not produce zero balance then they must be included in the $\nu$ function, that can +make the $\nu$ function $O(N)$ to the number of agreement contracts a monetary unit is +associated with. + +It may have been self evident that agreement contract is a analogy to the fact that it encodes +``ongoing relationships'' like legal contracts do between bearers. + +\section{Lens Data Accessors} + +In order to support \textit{index abstraction} in the denotational payment primitives, Monetary unit +data are created with \textit{lens data accessors}\footfullcite{clarke2020profunctor}: + +\begin{code} +-- representation of lenses +data Lens a b s t = Lens { view :: s -> a, update :: (b, s) -> t } +-- the pro-functor version of it +type Lens s t a b = forall f. Functor f => (a -> f b) -> s -> f t +\end{code} + +The pro-functor version of lens might seem very obscure at first, but its data representation is +rather self-explanatory: a lens is simply a pair of getter ($view$) and setter ($update$) encoded in +the data. + +With the help of Lens, then we can create the payment primitives independent of the choices between +1-to-1, 1-to-N, etc. Here are how they are defined for each class of payment primitives: + +\section{Useful Agreements} + +\subsection{Instant Value MUD} + +This is for agreements where value is instantly transferred. + +\begin{code} +class ( Default amuLs + , SuperfluidSystemTypes sft + ) => MonetaryUnitLenses amuLs sft | amuLs -> sft where + untappedValue :: Lens' amuLs (UntappedValue (SFT_MVAL sft)) + +type MonetaryUnitData :: Type -> Type -> Type +newtype MonetaryUnitData amuLs sft = + MkMonetaryUnitData { getMonetaryUnitLenses :: amuLs } + deriving (Default) + +instance MonetaryUnitLenses amuLs sft + => Semigroup (MonetaryUnitData amuLs sft) where + MkMonetaryUnitData a <> MkMonetaryUnitData b = + let c = a & over untappedValue (+ b^.untappedValue) + in MkMonetaryUnitData c + +instance MonetaryUnitLenses amuLs sft + => MonetaryUnitDataClass (MonetaryUnitData amuLs sft) sft where + balanceProvided (MkMonetaryUnitData a) _ = + typedValuesToRTB [mkAnyTypedValue $ a^.untappedValue] +\end{code} + +Note that (a) lens \textbf{operator (\string^.)} is the view function (getter) of data, (b) +semigroup \textbf{operator ($<>$)} defines how two monetary unit data can be combined into one. + +\subsection{Constant Flow Agreement (CFA) MUD} + +A more interesting case is where monetary value is changing continuously over time at a constant +rate: + +\begin{code} +class ( Default amuLs + , SuperfluidSystemTypes sft + ) => MonetaryUnitLenses amuLs sft | amuLs -> sft where + settledAt :: Lens' amuLs (SFT_TS sft) + settledValue :: Lens' amuLs (UntappedValue (SFT_MVAL sft)) + netFlowRate :: Lens' amuLs (SFT_MVAL sft) + +type MonetaryUnitData :: Type -> Type -> Type +newtype MonetaryUnitData amuLs sft = + MkMonetaryUnitData { getMonetaryUnitLenses :: amuLs } + deriving (Default) + +instance MonetaryUnitLenses amuLs sft + => Semigroup (MonetaryUnitData amuLs sft) where + MkMonetaryUnitData a <> MkMonetaryUnitData b = + let t = a^.settledAt + t' = b^.settledAt + settledΔ = MkUntappedValue $ a^.netFlowRate * fromIntegral (t' - t) + c = a & set settledAt t' + & over netFlowRate (+ b^.netFlowRate) + & over settledValue (+ (b^.settledValue + settledΔ)) + in MkMonetaryUnitData c + +instance MonetaryUnitLenses amuLs sft + => MonetaryUnitDataClass (MonetaryUnitData amuLs sft) sft where + balanceProvided (MkMonetaryUnitData a) t = + let b = uval_s + coerce (fr * fromIntegral (t - t_s)) + in typedValuesToRTB [ mkAnyTypedValue b ] + where t_s = a^.settledAt + uval_s = a^.settledValue + fr = a^.netFlowRate +\end{code} + +Note the implementation of semigroup binary operator, this is where the optimization occurs: to use +$netFlowRate$ to fold monetary unit data into a single value. + +\subsection{Decaying Flow Agreement (CFA) MUD} + +\begin{code} +class ( Default amuLs + , SuperfluidSystemTypes sft + ) => MonetaryUnitLenses amuLs sft | amuLs -> sft where + decayingFactor :: Lens' amuLs (SFT_FLOAT sft) + settledAt :: Lens' amuLs (SFT_TS sft) + αVal :: Lens' amuLs (SFT_FLOAT sft) + εVal :: Lens' amuLs (SFT_FLOAT sft) + +type MonetaryUnitData :: Type -> Type -> Type +newtype MonetaryUnitData amuLs sft = + MkMonetaryUnitData { getMonetaryUnitLenses :: amuLs } + deriving (Default) + +instance MonetaryUnitLenses amuLs sft + => Semigroup (MonetaryUnitData amuLs sft) where + MkMonetaryUnitData a <> MkMonetaryUnitData b = + let c = a & set settledAt (b^.settledAt) + & over αVal (\α -> α * exp (-λ * t_Δ) - ε') + & over εVal (+ ε') + in MkMonetaryUnitData c + where ε' = b^.εVal + λ = b^.decayingFactor + t_Δ = fromIntegral (b^.settledAt - a^.settledAt) + +instance MonetaryUnitLenses amuLs sft + => MonetaryUnitDataClass (MonetaryUnitData amuLs sft) sft where + balanceProvided (MkMonetaryUnitData a) t = + let b = ceiling $ α * exp (-λ * t_Δ) + ε + in typedValuesToRTB [ (mkAnyTypedValue . MkUntappedValue) b ] + where t_s = a^.settledAt + α = a^.αVal + ε = a^.εVal + λ = a^.decayingFactor + t_Δ = fromIntegral (t - t_s) +\end{code} + +\subsection{Universal Index (UIDX)} + +An universal index then can be implemented trivially by representing lenses using the record syntax +directly. For example, for constant flow monetary unit data: + +\begin{code} +data MonetaryUnitLenses sft = MonetaryUnitLenses + { settled_at :: SFT_TS sft + , settled_value :: UntappedValue (SFT_MVAL sft) + , net_flow_rate :: SFT_MVAL sft + } deriving (Generic) + +deriving instance SuperfluidSystemTypes sft + => Default (MonetaryUnitLenses sft) + +-- | Monetary unit lenses for the universal index. +instance SuperfluidSystemTypes sft + => CFMUD.MonetaryUnitLenses (MonetaryUnitLenses sft) sft where + settledAt = $(field 'settled_at) + settledValue = $(field 'settled_value) + netFlowRate = $(field 'net_flow_rate) +\end{code} + +The $field$ is a template Haskell function to generate lens from record fields. + +\subsection{Proportional Distribution Index (PDIDX)} + +Proportional distribution index describes on-going \textit{subscription} agreement contracts between +one \textit{publisher} and many \textit{subscribers} to the publisher's \textit{distribution}. + +Its instant value variance is called \textit{``Instant Distribution Agreement (IDA)''}. + +Its constant flow variance is called \textit{``Constant Flow Distribution Agreement (CFDA)''}. + +\section{Hierarchy of Agreements} + +To finish, figure \ref{fig:ac-agreement-data-structures} is the illustration of data structures for +the denotational payment primitives implemented with agreement framework. + +\begin{figure}[H] + \centering + \includegraphics[width=0.9\textwidth]{../assets/agreement-data-structures.png} + \caption{Agreement Data Structures} + \label{fig:ac-agreement-data-structures} +\end{figure} + +Note that with \textit{Root MUD}, any monetary unit can traverse all its MUDs; and with \textit{Root +Contract} relations between monetary units can also be searched by a simple algorithm. + +This concludes the overview of the Superfluid protocol implementation. For more details, one should +refer to the code repository mentioned before. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\part{Notes on Future Investigations} +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +\section{Restatement in Agda, Correctness and Equivalence Proofs} + +One of the advantages of using Haskell language to write the reference implementation was its +industrial strength. Because of that, the implementation could be easily integrated into a +production code base without significant performance compromises. + +However, one major disadvantage is that it is not equipped with sufficient apparatus for program +correctness proofs. The best it can offer without using more experimental Haskell features is to +use \textit{QuickCheck}\footnote{\url{https://wiki.haskell.org/QuickCheck} - Haskell wiki page for +QuickCheck.} to test that the necessary laws are not violated using randomized test vectors. + +To amend this deficiency, \textit{Agda programming language} comes as a great candidate for a +different constructive restatement of the formal specification. + +It is based on the insight of the deep connection (equivalence/isomorphism) between logic and +dependently typed programming, often called ``the Curry–Howard correspondence'', as discovered and +developed during the 20th century (explained in \cite{wadler2015propositions} by Phillip Wadler as +``Propositions as Types''). Agda being a dependently typed functional programming language fully +embodies this insight, hence a good candidate for creating provable correct programs. + +To learn more about Agda, refer to these footnotes +\footnote{\url{https://plfa.github.io/} - Programming Language Foundations in Agda.} +\footnote{\url{https://wiki.portal.chalmers.se/agda/pmwiki.php} - Agda Wiki.} +\footnote{\url{https://wiki.portal.chalmers.se/agda/Main/Community} - Agda community page.}. + +\section{Future Papers on Modern Payment System} + +The next yellow paper will formalize how the denotational payment primitives can be stitched +together using a powerful combinator library pattern briefly described +in \cite{peyton2000composing}. + +The execution environments for these financial contracts are also fertile ground for new ideas, from +deterministic execution avoiding the usage of buffer, to using distributed ledger technology to +deep-embed these financial contracts in the consensus layer. + +\section{General Accounting Domains \& Real Time Finance} + +\begin{displayquote} +Accounting, also known as accountancy, is the measurement, processing, and communication of +financial and non-financial information about economic entities such as businesses and +corporations. \footfullcite{needles2013principles} +\end{displayquote} + +The \textit{real time balance} introduced in this paper can be seen as an instance in +the \textit{general accounting domain}, specifically for financial contracts in dealing with payment +primitives, which can be seen as a real time version of cash flow accounting. Along with balance +sheet accounting, and income statement accounting, the conversion between these instances of general +accounting domains are often called ``reconciliations''. + +More work can be done on how to create a simple and elegant automation system for reconciliations. + +\paragraph{Real Time Finance} + +\emph{ +We define the term \textbf{real time finance} to mean a financial system where its payment system is +modernized to handle money distribution continuously over time, its financial contracts are +compositional, and its general accounting domain is automated and processed in real time. } + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\part*{Conclusions} +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +This paper defines what constitutes a modern payment system, with its payment primitives formally +specified using denotational semantics and restated using Haskell programming language. Along with a +reference implementation of the specification, we want to make a case that marrying with the +progress of computer science, the way of money performs its function of medium of exchange can have +a modern upgrade. + +Absent any claim on whether a modern upgrade is needed; we invite the readers to ask ourselves a +question together: with electronic money increasingly being used, should we keep emulating the +function of money of its traditional quality, or could we rethink what it may be in the information +age? + +In future work, we will look into deeper other topics of real time finance and continue to refine +the methodology used to achieve \textit{simple and precise} specifications guiding correct and +efficient engineering. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\newpage +\printbibliography{} +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +\end{document} diff --git a/packages/spec-haskell/yellowpaper/assets/agreement-contract-omega-function.png b/packages/spec-haskell/yellowpaper/assets/agreement-contract-omega-function.png new file mode 100644 index 0000000000000000000000000000000000000000..e4be9a8144323c4743887e166d8aaf5a429a8362 GIT binary patch literal 152530 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�jRBIWlxr;;1nPAyks3K)^qgq2^40nmKi4b*`Gpb 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⋙!FfVI*(w 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%0DYc6 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 literal 0 HcmV?d00001 diff --git a/packages/spec-haskell/yellowpaper/preamble.tex b/packages/spec-haskell/yellowpaper/preamble.tex new file mode 100644 index 0000000000..f67e197c5a --- /dev/null +++ b/packages/spec-haskell/yellowpaper/preamble.tex @@ -0,0 +1,67 @@ +\documentclass[a4paper,10pt]{article} + +%% This document is processed in an isolated clean folder at the same level +\makeatletter +\def\input@path{{../tex/}} +\makeatother + +%% Page settings +%%% \usepackage[margin=0.5in,nomarginpar]{geometry} + +%%% Bibliography system setup using biblatex +\usepackage[ + backend=biber, + style=ieee, +]{biblatex} +\addbibresource{../tex/Biblio.bib} + +%% Packages +\usepackage[utf8]{inputenc} +\usepackage{hyperref} +\usepackage{fvextra} +\usepackage{csquotes} +\usepackage{xcolor} +\usepackage{minted} +\usepackage{amsfonts} +\usepackage{amsmath} +\usepackage{graphicx} +\usepackage{enumitem} +\usepackage{float} +\usepackage{vhistory} + +%% Formatting + +\renewcommand{\mkbegdispquote}[2]{\itshape} + +%% Required Literate Haskell (lhs) macros (code, spec) +%%% include common lhs formatting +\input{lhsfmt.tex} +%%% for code +\newminted[code]{haskell}{} +\newminted[spec]{haskell}{} + +%% Other macros +\long\def\ignore#1{} % for ignoring code +\newcommand{\eg}{\textit{e}.\textit{g}.} +\newcommand{\ie}{\textit{i}.\textit{e}.} +\newcommand{\etal}{\textit{et al}.} + +%% Used Unicodes in code +\DeclareUnicodeCharacter{03B1}{$\alpha$} +\DeclareUnicodeCharacter{03B2}{$\beta$} +\DeclareUnicodeCharacter{03B5}{$\epsilon$} +\DeclareUnicodeCharacter{03BB}{$\lambda$} +\DeclareUnicodeCharacter{03BD}{$\nu$} +\DeclareUnicodeCharacter{03C0}{$\pi$} +\DeclareUnicodeCharacter{03C1}{$\rho$} +\DeclareUnicodeCharacter{03C6}{$\phi$} +\DeclareUnicodeCharacter{03C9}{$\omega$} +\DeclareUnicodeCharacter{0394}{$\Delta$} + +\DeclareUnicodeCharacter{2205}{$\emptyset$} +\DeclareUnicodeCharacter{2295}{$\oplus$} + +\DeclareUnicodeCharacter{27E6}{$[\![$} +\DeclareUnicodeCharacter{27E7}{$]\!]$} + +\DeclareUnicodeCharacter{1D4DC}{$\mathcal{M}$} diff --git a/packages/subgraph/config/arbitrum-goerli.json b/packages/subgraph/config/arbitrum-goerli.json index 085198f6f4..428b8a27ef 100644 --- a/packages/subgraph/config/arbitrum-goerli.json +++ b/packages/subgraph/config/arbitrum-goerli.json @@ -5,5 +5,6 @@ "cfaAddress": "0xff48668fa670A85e55A7a822b352d5ccF3E7b18C", "idaAddress": "0x96215257F2FcbB00135578f766c0449d239bd92F", "superTokenFactoryAddress": "0x9aCc39d15e3f168c111a1D4F80271a9E526c9a9F", - "resolverV1Address": "0x21d4E9fbB9DB742E6ef4f29d189a7C18B0b59136" + "resolverV1Address": "0x21d4E9fbB9DB742E6ef4f29d189a7C18B0b59136", + "nativeAssetSuperTokenAddress": "0xE01F8743677Da897F4e7De9073b57Bf034FC2433" } \ No newline at end of file diff --git a/packages/subgraph/config/arbitrum-one.json b/packages/subgraph/config/arbitrum-one.json index ea2b199346..68a1bdf0cc 100644 --- a/packages/subgraph/config/arbitrum-one.json +++ b/packages/subgraph/config/arbitrum-one.json @@ -5,5 +5,6 @@ "cfaAddress": "0x731FdBB12944973B500518aea61942381d7e240D", "idaAddress": "0x2319C7e07EB063340D2a0E36709B0D65fda75986", "superTokenFactoryAddress": "0x1C21Ead77fd45C84a4c916Db7A6635D0C6FF09D6", - "resolverV1Address": "0x609b9d9d6Ee9C3200745A79B9d3398DBd63d509F" + "resolverV1Address": "0x609b9d9d6Ee9C3200745A79B9d3398DBd63d509F", + "nativeAssetSuperTokenAddress": "0xe6c8d111337d0052b9d88bf5d7d55b7f8385acd3" } diff --git a/packages/subgraph/config/avalanche-c.json b/packages/subgraph/config/avalanche-c.json index df4b0ee73f..d6515a63a2 100644 --- a/packages/subgraph/config/avalanche-c.json +++ b/packages/subgraph/config/avalanche-c.json @@ -5,5 +5,6 @@ "cfaAddress": "0x6946c5B38Ffea373b0a2340b4AEf0De8F6782e58", "idaAddress": "0x1fA9fFe8Db73F701454B195151Db4Abb18423cf2", "superTokenFactoryAddress": "0x464AADdBB2B80f3Cb666522EB7381bE610F638b4", - "resolverV1Address": "0x24a3F04F70B7f07B9673EadD3e146391BcfEa5c1" + "resolverV1Address": "0x24a3F04F70B7f07B9673EadD3e146391BcfEa5c1", + "nativeAssetSuperTokenAddress": "0xbe916845d8678b5d2f7ad79525a62d7c08abba7e" } \ No newline at end of file diff --git a/packages/subgraph/config/avalanche-fuji.json b/packages/subgraph/config/avalanche-fuji.json index eec63deeed..08fecc5ba3 100644 --- a/packages/subgraph/config/avalanche-fuji.json +++ b/packages/subgraph/config/avalanche-fuji.json @@ -5,5 +5,6 @@ "cfaAddress": "0xED74d30B8034152b0638CB03cc5c3c906dd1c482", "idaAddress": "0x997d745884F54a93E6662b055c5e6c09F688718b", "superTokenFactoryAddress": "0xA25dbEa94C5824892006b30a629213E7Bf238624", - "resolverV1Address": "0x141920741bC45b962B59c833cd849bA617F7ef38" + "resolverV1Address": "0x141920741bC45b962B59c833cd849bA617F7ef38", + "nativeAssetSuperTokenAddress": "0x5735c32c38f5af0fb04a7c77c832ba4d7abffec8" } \ No newline at end of file diff --git a/packages/subgraph/config/bsc-mainnet.json b/packages/subgraph/config/bsc-mainnet.json index c83bd502a2..24fefe9cfa 100644 --- a/packages/subgraph/config/bsc-mainnet.json +++ b/packages/subgraph/config/bsc-mainnet.json @@ -6,5 +6,6 @@ "idaAddress": "0x594ed9Cd773584B645aC1F5B11020d3b32cDF07d", "superTokenFactoryAddress": "0x8bde47397301F0Cd31b9000032fD517a39c946Eb", "superfluidGovernanceAddress": "0xee07D9fce4Cf2a891BC979E9d365929506C2982f", - "resolverV1Address": "0x69604aA4e9e8BF44A73C680997205Edb03A92E41" + "resolverV1Address": "0x69604aA4e9e8BF44A73C680997205Edb03A92E41", + "nativeAssetSuperTokenAddress": "0x529a4116f160c833c61311569d6b33dff41fd657" } \ No newline at end of file diff --git a/packages/subgraph/config/eth-mainnet.json b/packages/subgraph/config/eth-mainnet.json new file mode 100644 index 0000000000..97f28df431 --- /dev/null +++ b/packages/subgraph/config/eth-mainnet.json @@ -0,0 +1,10 @@ +{ + "network": "mainnet", + "hostStartBlock": 15870000, + "hostAddress": "0x4E583d9390082B65Bef884b629DFA426114CED6d", + "cfaAddress": "0x2844c1BBdA121E9E43105630b9C8310e5c72744b", + "idaAddress": "0xbCF9cfA8Da20B591790dF27DE65C1254Bf91563d", + "superTokenFactoryAddress": "0x0422689cc4087b6B7280e0a7e7F655200ec86Ae1", + "resolverV1Address": "0xeE4cD028f5fdaAdeA99f8fc38e8bA8A57c90Be53", + "nativeAssetSuperTokenAddress": "0xc22bea0be9872d8b7b3933cec70ece4d53a900da" +} diff --git a/packages/subgraph/config/goerli.json b/packages/subgraph/config/goerli.json index 35a871e580..2d133e9ccc 100644 --- a/packages/subgraph/config/goerli.json +++ b/packages/subgraph/config/goerli.json @@ -5,5 +5,6 @@ "cfaAddress": "0xEd6BcbF6907D4feEEe8a8875543249bEa9D308E8", "idaAddress": "0xfDdcdac21D64B639546f3Ce2868C7EF06036990c", "superTokenFactoryAddress": "0x94f26B4c8AD12B18c12f38E878618f7664bdcCE2", - "resolverV1Address": "0x3710AB3fDE2B61736B8BB0CE845D6c61F667a78E" + "resolverV1Address": "0x3710AB3fDE2B61736B8BB0CE845D6c61F667a78E", + "nativeAssetSuperTokenAddress": "0x5943f705abb6834cad767e6e4bb258bc48d9c947" } diff --git a/packages/subgraph/config/matic.json b/packages/subgraph/config/matic.json index e2aa043e85..b1dcee648f 100644 --- a/packages/subgraph/config/matic.json +++ b/packages/subgraph/config/matic.json @@ -5,5 +5,6 @@ "cfaAddress": "0x6EeE6060f715257b970700bc2656De21dEdF074C", "idaAddress": "0xB0aABBA4B2783A72C52956CDEF62d438ecA2d7a1", "superTokenFactoryAddress": "0x2C90719f25B10Fc5646c82DA3240C76Fa5BcCF34", - "resolverV1Address": "0xE0cc76334405EE8b39213E620587d815967af39C" + "resolverV1Address": "0xE0cc76334405EE8b39213E620587d815967af39C", + "nativeAssetSuperTokenAddress": "0x3ad736904e9e65189c3000c7dd2c8ac8bb7cd4e3" } diff --git a/packages/subgraph/config/mumbai.json b/packages/subgraph/config/mumbai.json index e220a544a9..d787c406cf 100644 --- a/packages/subgraph/config/mumbai.json +++ b/packages/subgraph/config/mumbai.json @@ -5,5 +5,6 @@ "cfaAddress": "0x49e565Ed1bdc17F3d220f72DF0857C26FA83F873", "idaAddress": "0x804348D4960a61f2d5F9ce9103027A3E849E09b8", "superTokenFactoryAddress": "0x200657E2f123761662567A1744f9ACAe50dF47E6", - "resolverV1Address": "0x8C54C83FbDe3C59e59dd6E324531FB93d4F504d3" + "resolverV1Address": "0x8C54C83FbDe3C59e59dd6E324531FB93d4F504d3", + "nativeAssetSuperTokenAddress": "0x96b82b65acf7072efeb00502f45757f254c2a0d4" } diff --git a/packages/subgraph/config/optimism-goerli.json b/packages/subgraph/config/optimism-goerli.json index d69192916e..925e97b531 100644 --- a/packages/subgraph/config/optimism-goerli.json +++ b/packages/subgraph/config/optimism-goerli.json @@ -5,5 +5,6 @@ "cfaAddress": "0xff48668fa670A85e55A7a822b352d5ccF3E7b18C", "idaAddress": "0x96215257F2FcbB00135578f766c0449d239bd92F", "superTokenFactoryAddress": "0x9aCc39d15e3f168c111a1D4F80271a9E526c9a9F", - "resolverV1Address": "0x21d4E9fbB9DB742E6ef4f29d189a7C18B0b59136" + "resolverV1Address": "0x21d4E9fbB9DB742E6ef4f29d189a7C18B0b59136", + "nativeAssetSuperTokenAddress": "0xE01F8743677Da897F4e7De9073b57Bf034FC2433" } \ No newline at end of file diff --git a/packages/subgraph/config/optimism-mainnet.json b/packages/subgraph/config/optimism-mainnet.json index 65b0cef1b0..aedd5cd6ab 100644 --- a/packages/subgraph/config/optimism-mainnet.json +++ b/packages/subgraph/config/optimism-mainnet.json @@ -5,5 +5,6 @@ "cfaAddress": "0x204C6f131bb7F258b2Ea1593f5309911d8E458eD", "idaAddress": "0xc4ce5118C3B20950ee288f086cb7FC166d222D4c", "superTokenFactoryAddress": "0x8276469A443D5C6B7146BED45e2abCaD3B6adad9", - "resolverV1Address": "0x743B5f46BC86caF41bE4956d9275721E0531B186" + "resolverV1Address": "0x743B5f46BC86caF41bE4956d9275721E0531B186", + "nativeAssetSuperTokenAddress": "0x4ac8bd1bdae47beef2d1c6aa62229509b962aa0d" } diff --git a/packages/subgraph/config/xdai.json b/packages/subgraph/config/xdai.json index cf62983390..5c895ddffe 100644 --- a/packages/subgraph/config/xdai.json +++ b/packages/subgraph/config/xdai.json @@ -5,5 +5,6 @@ "cfaAddress": "0xEbdA4ceF883A7B12c4E669Ebc58927FBa8447C7D", "idaAddress": "0x7888ac96F987Eb10E291F34851ae0266eF912081", "superTokenFactoryAddress": "0x23410e2659380784498509698ed70E414D384880", - "resolverV1Address": "0xD2009765189164b495c110D61e4D301729079911" + "resolverV1Address": "0xD2009765189164b495c110D61e4D301729079911", + "nativeAssetSuperTokenAddress": "0x59988e47a3503aafaa0368b9def095c818fdca01" } diff --git a/packages/subgraph/networks.json b/packages/subgraph/networks.json index 04f2958406..a17d223424 100644 --- a/packages/subgraph/networks.json +++ b/packages/subgraph/networks.json @@ -1,4 +1,5 @@ [ + "eth-mainnet", "matic", "xdai", "optimism-mainnet", diff --git a/packages/subgraph/package.json b/packages/subgraph/package.json index 51a428fe5b..d9fa7e4465 100644 --- a/packages/subgraph/package.json +++ b/packages/subgraph/package.json @@ -34,7 +34,7 @@ "build-and-deploy-local": "yarn codegen && yarn create-local && yarn deploy-local", "watch": "graph deploy superfluid-test --node http://localhost:8020/ --ipfs http://localhost:5001 --watch", "deploy": "chmod +x ./tasks/deploy.sh && ./tasks/deploy.sh", - "deploy-subgraph": "graph deploy $SUBGRAPH_NAME --node https://api.thegraph.com/deploy/ --ipfs https://api.thegraph.com/ipfs --access-token $THEGRAPH_ACCESS_TOKEN", + "deploy-subgraph": "graph deploy $SUBGRAPH_NAME --node https://api.thegraph.com/deploy/ --ipfs https://api.thegraph.com/ipfs --access-token $THE_GRAPH_ACCESS_TOKEN", "deploy:feature:matic": "yarn deploy-to-feature-network matic", "deploy:feature:goerli": "yarn deploy-to-feature-network goerli", "deploy-all-networks": "chmod +x ./tasks/deploy-all-networks.sh && ./tasks/deploy-all-networks.sh", @@ -45,6 +45,7 @@ "deploy:to-dev": "yarn deploy-to dev", "deploy:to-v1": "yarn deploy-to v1", "deploy:to-feature": "yarn deploy-to feature", + "deploy:to-satsuma": "chmod +x ./tasks/deploy-to-satsuma.sh && ./tasks/deploy-to-satsuma.sh", "lint": "run-s lint:*", "lint:js-eslint": "eslint . --max-warnings=0 --report-unused-disable-directives && echo '✔ Your .js files look good.'", "pre-commit": "if [ ! -z \"$(git status -s .)\" ];then run-s pre-commit:*;else true;fi", @@ -57,7 +58,7 @@ "dependencies": { "@graphprotocol/graph-cli": "0.29.0", "@graphprotocol/graph-ts": "0.27.0", - "@superfluid-finance/sdk-core": "0.5.7", + "@superfluid-finance/sdk-core": "0.6.0", "mustache": "^4.2.0" }, "devDependencies": { diff --git a/packages/subgraph/src/addresses.template.ts b/packages/subgraph/src/addresses.template.ts index d1cc7ec53f..ad6559454c 100644 --- a/packages/subgraph/src/addresses.template.ts +++ b/packages/subgraph/src/addresses.template.ts @@ -1,61 +1,17 @@ -import { Address, TypedMap } from "@graphprotocol/graph-ts"; +import { Address } from "@graphprotocol/graph-ts"; // This file is a template file which is used for getting the address // based on the network we set in the set-network package.json file. // We add a bit of complexity to the package.json, but remove much // more as a result. -// @note TODO: I am pretty sure we can remove {{network}} and just do -// return Address.fromString("{{hostAddress}}") export function getHostAddress(): Address { - const network = "{{network}}"; - const addresses: TypedMap = new TypedMap(); - addresses.set("mainnet", "{{hostAddress}}"); - addresses.set("goerli", "0x22ff293e14F1EC3A09B137e9e06084AFd63adDF9"); - addresses.set("matic", "0x3E14dC1b13c488a8d5D310918780c983bD5982E7"); - addresses.set("mumbai", "0xEB796bdb90fFA0f28255275e16936D25d3418603"); - addresses.set("xdai", "0x2dFe937cD98Ab92e59cF3139138f18c823a4efE7"); - addresses.set("fuji", "0xf04F2C525819691ed9ABD3D2B7109E1633795e68"); - addresses.set("arbitrum-goerli", "0xE40983C2476032A0915600b9472B3141aA5B5Ba9"); - addresses.set("optimism-goerli", "0xE40983C2476032A0915600b9472B3141aA5B5Ba9"); - addresses.set("avalanche", "0x60377C7016E4cdB03C87EF474896C11cB560752C"); - addresses.set("arbitrum-one", "0xCf8Acb4eF033efF16E8080aed4c7D5B9285D2192"); - addresses.set("optimism", "0x567c4B141ED61923967cA25Ef4906C8781069a10"); - addresses.set("bsc", "0xd1e2cFb6441680002Eb7A44223160aB9B67d7E6E"); - return Address.fromString(addresses.mustGet(network)); + return Address.fromString("{{hostAddress}}"); } export function getResolverAddress(): Address { - const network = "{{network}}"; - const addresses: TypedMap = new TypedMap(); - addresses.set("mainnet", "{{resolverV1Address}}"); - addresses.set("goerli", "0x3710AB3fDE2B61736B8BB0CE845D6c61F667a78E"); - addresses.set("matic", "0xE0cc76334405EE8b39213E620587d815967af39C"); - addresses.set("mumbai", "0x8C54C83FbDe3C59e59dd6E324531FB93d4F504d3"); - addresses.set("xdai", "0xD2009765189164b495c110D61e4D301729079911"); - addresses.set("fuji", "0x141920741bC45b962B59c833cd849bA617F7ef38"); - addresses.set("arbitrum-goerli", "0x21d4E9fbB9DB742E6ef4f29d189a7C18B0b59136"); - addresses.set("optimism-goerli", "0x21d4E9fbB9DB742E6ef4f29d189a7C18B0b59136"); - addresses.set("avalanche", "0x24a3F04F70B7f07B9673EadD3e146391BcfEa5c1"); - addresses.set("arbitrum-one", "0x609b9d9d6Ee9C3200745A79B9d3398DBd63d509F"); - addresses.set("optimism", "0x743B5f46BC86caF41bE4956d9275721E0531B186"); - addresses.set("bsc", "0x69604aA4e9e8BF44A73C680997205Edb03A92E41"); - return Address.fromString(addresses.mustGet(network)); + return Address.fromString("{{resolverV1Address}}"); } export function getNativeAssetSuperTokenAddress(): Address { - const network = "{{network}}"; - const addresses: TypedMap = new TypedMap(); - addresses.set("mainnet", "{{nativeAssetSuperTokenAddress}}"); - addresses.set("goerli", "0x5943f705abb6834cad767e6e4bb258bc48d9c947"); - addresses.set("matic", "0x3ad736904e9e65189c3000c7dd2c8ac8bb7cd4e3"); - addresses.set("mumbai", "0x96b82b65acf7072efeb00502f45757f254c2a0d4"); - addresses.set("xdai", "0x59988e47a3503aafaa0368b9def095c818fdca01"); - addresses.set("fuji", "0x5735c32c38f5af0fb04a7c77c832ba4d7abffec8"); - addresses.set("arbitrum-goerli", "0xE01F8743677Da897F4e7De9073b57Bf034FC2433"); - addresses.set("optimism-goerli", "0xE01F8743677Da897F4e7De9073b57Bf034FC2433"); - addresses.set("avalanche", "0xbe916845d8678b5d2f7ad79525a62d7c08abba7e"); - addresses.set("arbitrum-one", "0xe6c8d111337d0052b9d88bf5d7d55b7f8385acd3"); - addresses.set("optimism", "0x4ac8bd1bdae47beef2d1c6aa62229509b962aa0d"); - addresses.set("bsc", "0x529a4116f160c833c61311569d6b33dff41fd657"); - return Address.fromString(addresses.mustGet(network)); + return Address.fromString("{{nativeAssetSuperTokenAddress}}"); } diff --git a/packages/subgraph/src/mappings/cfav1.ts b/packages/subgraph/src/mappings/cfav1.ts index 391bfd7701..2479b99086 100644 --- a/packages/subgraph/src/mappings/cfav1.ts +++ b/packages/subgraph/src/mappings/cfav1.ts @@ -15,13 +15,13 @@ import { BIG_INT_ONE, BIG_INT_ZERO, bytesToAddress, - createEventID, getFlowOperatorID, - getOrder, getStreamPeriodID, MAX_FLOW_RATE, + initializeEventEntity, tokenHasValidHost, ZERO_ADDRESS, + createEventID, } from "../utils"; import { _createAccountTokenSnapshotLogEntity, @@ -385,19 +385,13 @@ function _createFlowUpdatedEntity( totalAmountStreamedUntilTimestamp: BigInt, deposit: BigInt ): FlowUpdatedEvent { - let ev = new FlowUpdatedEvent(createEventID("FlowUpdated", event)); - ev.transactionHash = event.transaction.hash; - ev.gasPrice = event.transaction.gasPrice; - ev.name = "FlowUpdated"; - ev.addresses = [ + const ev = new FlowUpdatedEvent(createEventID("FlowUpdated", event)); + initializeEventEntity(ev, event, [ event.params.token, event.params.sender, event.params.receiver, - ]; - ev.timestamp = event.block.timestamp; - ev.order = getOrder(event.block.number, event.logIndex); - ev.blockNumber = event.block.number; - ev.logIndex = event.logIndex; + ]); + ev.token = event.params.token; ev.sender = event.params.sender; ev.receiver = event.params.receiver; @@ -420,21 +414,15 @@ function _createFlowUpdatedEntity( function _createFlowOperatorUpdatedEventEntity( event: FlowOperatorUpdated ): FlowOperatorUpdatedEvent { - let ev = new FlowOperatorUpdatedEvent( + const ev = new FlowOperatorUpdatedEvent( createEventID("FlowOperatorUpdated", event) ); - ev.transactionHash = event.transaction.hash; - ev.gasPrice = event.transaction.gasPrice; - ev.name = "FlowOperatorUpdated"; - ev.addresses = [ + initializeEventEntity(ev, event, [ event.params.token, event.params.sender, event.params.flowOperator, - ]; - ev.timestamp = event.block.timestamp; - ev.blockNumber = event.block.number; - ev.logIndex = event.logIndex; - ev.order = getOrder(event.block.number, event.logIndex); + ]); + ev.token = event.params.token; ev.sender = event.params.sender; ev.permissions = event.params.permissions; diff --git a/packages/subgraph/src/mappings/host.ts b/packages/subgraph/src/mappings/host.ts index 0431fd8169..1901b3e3cc 100644 --- a/packages/subgraph/src/mappings/host.ts +++ b/packages/subgraph/src/mappings/host.ts @@ -17,25 +17,17 @@ import { SuperTokenFactoryUpdated, SuperTokenLogicUpdated, } from "../../generated/Host/ISuperfluid"; -import {createEventID, getOrder} from "../utils"; +import { createEventID, initializeEventEntity } from "../utils"; import { commitHash, configuration, branch } from "../meta.ignore"; import { ethereum } from "@graphprotocol/graph-ts"; import { SuperfluidGovernance } from "../../generated/templates"; export function handleGovernanceReplaced(event: GovernanceReplaced): void { - let ev = new GovernanceReplacedEvent( - createEventID("GovernanceReplaced", event) - ); - ev.transactionHash = event.transaction.hash; - ev.gasPrice = event.transaction.gasPrice; - ev.timestamp = event.block.timestamp; - ev.name = "GovernanceReplaced"; - ev.addresses = []; - ev.blockNumber = event.block.number; - ev.order = getOrder(event.block.number, event.logIndex); + const eventId = createEventID("GovernanceReplaced", event); + const ev = new GovernanceReplacedEvent(eventId); + initializeEventEntity(ev, event, []); ev.oldGovernance = event.params.oldGov; ev.newGovernance = event.params.newGov; - ev.logIndex = event.logIndex; ev.save(); // Create data source template for new Governance contract @@ -48,17 +40,9 @@ export function handleGovernanceReplaced(event: GovernanceReplaced): void { export function handleAgreementClassRegistered( event: AgreementClassRegistered ): void { - let ev = new AgreementClassRegisteredEvent( - createEventID("AgreementClassRegistered", event) - ); - ev.transactionHash = event.transaction.hash; - ev.gasPrice = event.transaction.gasPrice; - ev.timestamp = event.block.timestamp; - ev.name = "AgreementClassRegistered"; - ev.addresses = []; - ev.blockNumber = event.block.number; - ev.logIndex = event.logIndex; - ev.order = getOrder(event.block.number, event.logIndex); + const eventId = createEventID("AgreementClassRegistered", event); + const ev = new AgreementClassRegisteredEvent(eventId); + initializeEventEntity(ev, event, []); ev.agreementType = event.params.agreementType; ev.code = event.params.code; ev.save(); @@ -69,17 +53,10 @@ export function handleAgreementClassRegistered( export function handleAgreementClassUpdated( event: AgreementClassUpdated ): void { - let ev = new AgreementClassUpdatedEvent( - createEventID("AgreementClassUpdated", event) - ); - ev.transactionHash = event.transaction.hash; - ev.gasPrice = event.transaction.gasPrice; - ev.timestamp = event.block.timestamp; - ev.name = "AgreementClassUpdated"; - ev.addresses = []; - ev.blockNumber = event.block.number; - ev.logIndex = event.logIndex; - ev.order = getOrder(event.block.number, event.logIndex); + const eventId = createEventID("AgreementClassUpdated", event); + const ev = new AgreementClassUpdatedEvent(eventId); + initializeEventEntity(ev, event, []); + ev.agreementType = event.params.agreementType; ev.code = event.params.code; ev.save(); @@ -91,17 +68,10 @@ export function handleAgreementClassUpdated( export function handleSuperTokenFactoryUpdated( event: SuperTokenFactoryUpdated ): void { - let ev = new SuperTokenFactoryUpdatedEvent( - createEventID("SuperTokenFactoryUpdated", event) - ); - ev.transactionHash = event.transaction.hash; - ev.gasPrice = event.transaction.gasPrice; - ev.timestamp = event.block.timestamp; - ev.name = "SuperTokenFactoryUpdated"; - ev.addresses = []; - ev.blockNumber = event.block.number; - ev.logIndex = event.logIndex; - ev.order = getOrder(event.block.number, event.logIndex); + const eventId = createEventID("SuperTokenFactoryUpdated", event); + const ev = new SuperTokenFactoryUpdatedEvent(eventId); + initializeEventEntity(ev, event, []); + ev.newFactory = event.params.newFactory; ev.save(); } @@ -109,46 +79,27 @@ export function handleSuperTokenFactoryUpdated( export function handleSuperTokenLogicUpdated( event: SuperTokenLogicUpdated ): void { - let ev = new SuperTokenLogicUpdatedEvent( - createEventID("SuperTokenLogicUpdated", event) - ); - ev.transactionHash = event.transaction.hash; - ev.gasPrice = event.transaction.gasPrice; - ev.timestamp = event.block.timestamp; - ev.name = "SuperTokenLogicUpdated"; - ev.addresses = []; - ev.blockNumber = event.block.number; - ev.logIndex = event.logIndex; - ev.order = getOrder(event.block.number, event.logIndex); + const eventId = createEventID("SuperTokenLogicUpdated", event); + const ev = new SuperTokenLogicUpdatedEvent(eventId); + initializeEventEntity(ev, event, []); + ev.token = event.params.token; ev.code = event.params.code; ev.save(); } export function handleAppRegistered(event: AppRegistered): void { - let ev = new AppRegisteredEvent(createEventID("AppRegistered", event)); - ev.transactionHash = event.transaction.hash; - ev.gasPrice = event.transaction.gasPrice; - ev.timestamp = event.block.timestamp; - ev.name = "AppRegistered"; - ev.addresses = []; - ev.blockNumber = event.block.number; - ev.logIndex = event.logIndex; - ev.order = getOrder(event.block.number, event.logIndex); + const ev = new AppRegisteredEvent(createEventID("AppRegistered", event)); + initializeEventEntity(ev, event, []); + ev.app = event.params.app; ev.save(); } export function handleJail(event: Jail): void { - let ev = new JailEvent(createEventID("Jail", event)); - ev.transactionHash = event.transaction.hash; - ev.gasPrice = event.transaction.gasPrice; - ev.timestamp = event.block.timestamp; - ev.name = "Jail"; - ev.addresses = []; - ev.blockNumber = event.block.number; - ev.logIndex = event.logIndex; - ev.order = getOrder(event.block.number, event.logIndex); + const ev = new JailEvent(createEventID("Jail", event)); + initializeEventEntity(ev, event, []); + ev.app = event.params.app; ev.reason = event.params.reason; ev.save(); diff --git a/packages/subgraph/src/mappings/idav1.ts b/packages/subgraph/src/mappings/idav1.ts index b4b3bee84e..59f1b6da34 100644 --- a/packages/subgraph/src/mappings/idav1.ts +++ b/packages/subgraph/src/mappings/idav1.ts @@ -27,7 +27,7 @@ import { BIG_INT_ZERO, createEventID, getIndexID, - getOrder, + initializeEventEntity, subscriptionExists as subscriptionWithUnitsExists, tokenHasValidHost, } from "../utils"; @@ -631,15 +631,13 @@ function _createIndexCreatedEventEntity( event: IndexCreated, indexId: string ): void { - let ev = new IndexCreatedEvent(createEventID("IndexCreated", event)); - ev.transactionHash = event.transaction.hash; - ev.gasPrice = event.transaction.gasPrice; - ev.timestamp = event.block.timestamp; - ev.name = "IndexCreated"; - ev.addresses = [event.params.token, event.params.publisher]; - ev.blockNumber = event.block.number; - ev.logIndex = event.logIndex; - ev.order = getOrder(event.block.number, event.logIndex); + const eventId = createEventID("IndexCreated", event); + const ev = new IndexCreatedEvent(eventId); + initializeEventEntity(ev, event, [ + event.params.token, + event.params.publisher, + ]); + ev.token = event.params.token; ev.publisher = event.params.publisher; ev.indexId = event.params.indexId; @@ -652,21 +650,14 @@ function _createIndexDistributionClaimedEventEntity( event: IndexDistributionClaimed, indexId: string ): void { - let ev = new IndexDistributionClaimedEvent( - createEventID("IndexDistributionClaimed", event) - ); - ev.transactionHash = event.transaction.hash; - ev.gasPrice = event.transaction.gasPrice; - ev.timestamp = event.block.timestamp; - ev.name = "IndexDistributionClaimed"; - ev.addresses = [ + const eventId = createEventID("IndexDistributionClaimed", event); + const ev = new IndexDistributionClaimedEvent(eventId); + initializeEventEntity(ev, event, [ event.params.token, event.params.publisher, event.params.subscriber, - ]; - ev.blockNumber = event.block.number; - ev.logIndex = event.logIndex; - ev.order = getOrder(event.block.number, event.logIndex); + ]); + ev.token = event.params.token; ev.publisher = event.params.publisher; ev.indexId = event.params.indexId; @@ -680,15 +671,13 @@ function _createIndexUpdatedEventEntity( event: IndexUpdated, indexId: string ): void { - let ev = new IndexUpdatedEvent(createEventID("IndexUpdated", event)); - ev.transactionHash = event.transaction.hash; - ev.gasPrice = event.transaction.gasPrice; - ev.timestamp = event.block.timestamp; - ev.name = "IndexUpdated"; - ev.addresses = [event.params.token, event.params.publisher]; - ev.blockNumber = event.block.number; - ev.logIndex = event.logIndex; - ev.order = getOrder(event.block.number, event.logIndex); + const eventId = createEventID("IndexUpdated", event); + const ev = new IndexUpdatedEvent(eventId); + initializeEventEntity(ev, event, [ + event.params.token, + event.params.publisher, + ]); + ev.token = event.params.token; ev.publisher = event.params.publisher; ev.indexId = event.params.indexId; @@ -704,19 +693,14 @@ function _createIndexSubscribedEventEntity( event: IndexSubscribed, indexId: string ): void { - let ev = new IndexSubscribedEvent(createEventID("IndexSubscribed", event)); - ev.transactionHash = event.transaction.hash; - ev.gasPrice = event.transaction.gasPrice; - ev.timestamp = event.block.timestamp; - ev.name = "IndexSubscribed"; - ev.addresses = [ + const eventId = createEventID("IndexSubscribed", event); + const ev = new IndexSubscribedEvent(eventId); + initializeEventEntity(ev, event, [ event.params.token, event.params.publisher, event.params.subscriber, - ]; - ev.blockNumber = event.block.number; - ev.logIndex = event.logIndex; - ev.order = getOrder(event.block.number, event.logIndex); + ]); + ev.token = event.params.token; ev.publisher = event.params.publisher; ev.indexId = event.params.indexId; @@ -731,21 +715,14 @@ function _createIndexUnitsUpdatedEventEntity( indexId: string, oldUnits: BigInt ): void { - let ev = new IndexUnitsUpdatedEvent( - createEventID("IndexUnitsUpdated", event) - ); - ev.transactionHash = event.transaction.hash; - ev.gasPrice = event.transaction.gasPrice; - ev.timestamp = event.block.timestamp; - ev.name = "IndexUnitsUpdated"; - ev.addresses = [ + const eventId = createEventID("IndexUnitsUpdated", event); + const ev = new IndexUnitsUpdatedEvent(eventId); + initializeEventEntity(ev, event, [ event.params.token, event.params.publisher, event.params.subscriber, - ]; - ev.blockNumber = event.block.number; - ev.logIndex = event.logIndex; - ev.order = getOrder(event.block.number, event.logIndex); + ]); + ev.token = event.params.token; ev.subscriber = event.params.subscriber; ev.publisher = event.params.publisher; @@ -761,21 +738,14 @@ function _createIndexUnsubscribedEventEntity( event: IndexUnsubscribed, indexId: string ): void { - let ev = new IndexUnsubscribedEvent( - createEventID("IndexUnsubscribed", event) - ); - ev.transactionHash = event.transaction.hash; - ev.gasPrice = event.transaction.gasPrice; - ev.timestamp = event.block.timestamp; - ev.name = "IndexUnsubscribed"; - ev.addresses = [ + const eventId = createEventID("IndexUnsubscribed", event); + const ev = new IndexUnsubscribedEvent(eventId); + initializeEventEntity(ev, event, [ event.params.token, event.params.publisher, event.params.subscriber, - ]; - ev.blockNumber = event.block.number; - ev.logIndex = event.logIndex; - ev.order = getOrder(event.block.number, event.logIndex); + ]); + ev.token = event.params.token; ev.subscriber = event.params.subscriber; ev.publisher = event.params.publisher; @@ -789,21 +759,13 @@ function _createSubscriptionApprovedEventEntity( event: SubscriptionApproved, subscriptionId: string ): void { - let ev = new SubscriptionApprovedEvent( - createEventID("SubscriptionApproved", event) - ); - ev.transactionHash = event.transaction.hash; - ev.gasPrice = event.transaction.gasPrice; - ev.timestamp = event.block.timestamp; - ev.name = "SubscriptionApproved"; - ev.addresses = [ + const eventId = createEventID("SubscriptionApproved", event); + const ev = new SubscriptionApprovedEvent(eventId); + initializeEventEntity(ev, event, [ event.params.token, event.params.publisher, event.params.subscriber, - ]; - ev.blockNumber = event.block.number; - ev.logIndex = event.logIndex; - ev.order = getOrder(event.block.number, event.logIndex); + ]); ev.token = event.params.token; ev.subscriber = event.params.subscriber; ev.publisher = event.params.publisher; @@ -817,21 +779,14 @@ function _createSubscriptionDistributionClaimedEventEntity( event: SubscriptionDistributionClaimed, subscriptionId: string ): void { - let ev = new SubscriptionDistributionClaimedEvent( - createEventID("SubscriptionDistributionClaimed", event) - ); - ev.transactionHash = event.transaction.hash; - ev.gasPrice = event.transaction.gasPrice; - ev.timestamp = event.block.timestamp; - ev.name = "SubscriptionDistributionClaimed"; - ev.addresses = [ + const eventId = createEventID("SubscriptionDistributionClaimed", event); + const ev = new SubscriptionDistributionClaimedEvent(eventId); + initializeEventEntity(ev, event, [ event.params.token, event.params.publisher, event.params.subscriber, - ]; - ev.blockNumber = event.block.number; - ev.logIndex = event.logIndex; - ev.order = getOrder(event.block.number, event.logIndex); + ]); + ev.token = event.params.token; ev.subscriber = event.params.subscriber; ev.publisher = event.params.publisher; @@ -845,21 +800,14 @@ function _createSubscriptionRevokedEventEntity( event: SubscriptionRevoked, subscriptionId: string ): void { - let ev = new SubscriptionRevokedEvent( - createEventID("SubscriptionRevoked", event) - ); - ev.transactionHash = event.transaction.hash; - ev.gasPrice = event.transaction.gasPrice; - ev.timestamp = event.block.timestamp; - ev.name = "SubscriptionRevoked"; - ev.addresses = [ + const eventId = createEventID("SubscriptionRevoked", event); + const ev = new SubscriptionRevokedEvent(eventId); + initializeEventEntity(ev, event, [ event.params.token, event.params.publisher, event.params.subscriber, - ]; - ev.blockNumber = event.block.number; - ev.logIndex = event.logIndex; - ev.order = getOrder(event.block.number, event.logIndex); + ]); + ev.token = event.params.token; ev.subscriber = event.params.subscriber; ev.publisher = event.params.publisher; @@ -874,21 +822,14 @@ function _createSubscriptionUnitsUpdatedEventEntity( subscriptionId: string, oldUnits: BigInt ): void { - let ev = new SubscriptionUnitsUpdatedEvent( - createEventID("SubscriptionUnitsUpdated", event) - ); - ev.transactionHash = event.transaction.hash; - ev.gasPrice = event.transaction.gasPrice; - ev.timestamp = event.block.timestamp; - ev.name = "SubscriptionUnitsUpdated"; - ev.addresses = [ + const eventId = createEventID("SubscriptionUnitsUpdated", event); + const ev = new SubscriptionUnitsUpdatedEvent(eventId); + initializeEventEntity(ev, event, [ event.params.token, event.params.publisher, event.params.subscriber, - ]; - ev.blockNumber = event.block.number; - ev.logIndex = event.logIndex; - ev.order = getOrder(event.block.number, event.logIndex); + ]); + ev.token = event.params.token; ev.subscriber = event.params.subscriber; ev.publisher = event.params.publisher; diff --git a/packages/subgraph/src/mappings/resolver.ts b/packages/subgraph/src/mappings/resolver.ts index 52b8e26f4e..81575dcfbb 100644 --- a/packages/subgraph/src/mappings/resolver.ts +++ b/packages/subgraph/src/mappings/resolver.ts @@ -1,4 +1,4 @@ -import { Address, Bytes, ethereum } from "@graphprotocol/graph-ts"; +import { Bytes, ethereum } from "@graphprotocol/graph-ts"; import { RoleAdminChanged, RoleGranted, @@ -13,20 +13,13 @@ import { Token, } from "../../generated/schema"; import { getOrInitResolverEntry } from "../mappingHelpers"; -import { createEventID, getOrder, ZERO_ADDRESS } from "../utils"; +import { createEventID, initializeEventEntity, ZERO_ADDRESS } from "../utils"; export function handleRoleAdminChanged(event: RoleAdminChanged): void { - let ev = new RoleAdminChangedEvent( - createEventID("RoleAdminChanged", event) - ); - ev.transactionHash = event.transaction.hash; - ev.gasPrice = event.transaction.gasPrice; - ev.timestamp = event.block.timestamp; - ev.blockNumber = event.block.number; - ev.order = getOrder(event.block.number, event.logIndex); - ev.logIndex = event.logIndex; - ev.name = "RoleAdminChanged"; - ev.addresses = []; + const eventId = createEventID("RoleAdminChanged", event); + const ev = new RoleAdminChangedEvent(eventId); + initializeEventEntity(ev, event, []); + ev.role = event.params.role; ev.previousAdminRole = event.params.previousAdminRole; ev.newAdminRole = event.params.newAdminRole; @@ -34,30 +27,20 @@ export function handleRoleAdminChanged(event: RoleAdminChanged): void { } export function handleRoleGranted(event: RoleGranted): void { - let ev = new RoleGrantedEvent(createEventID("RoleGranted", event)); - ev.transactionHash = event.transaction.hash; - ev.gasPrice = event.transaction.gasPrice; - ev.timestamp = event.block.timestamp; - ev.blockNumber = event.block.number; - ev.logIndex = event.logIndex; - ev.order = getOrder(event.block.number, event.logIndex); - ev.name = "RoleGranted"; - ev.addresses = []; + const eventId = createEventID("RoleGranted", event); + const ev = new RoleGrantedEvent(eventId); + initializeEventEntity(ev, event, []); + ev.role = event.params.role; ev.account = event.params.account; ev.sender = event.params.sender; ev.save(); } export function handleRoleRevoked(event: RoleRevoked): void { - let ev = new RoleRevokedEvent(createEventID("RoleRevoked", event)); - ev.transactionHash = event.transaction.hash; - ev.gasPrice = event.transaction.gasPrice; - ev.timestamp = event.block.timestamp; - ev.blockNumber = event.block.number; - ev.logIndex = event.logIndex; - ev.order = getOrder(event.block.number, event.logIndex); - ev.name = "RoleRevoked"; - ev.addresses = []; + const eventId = createEventID("RoleRevoked", event); + const ev = new RoleRevokedEvent(eventId); + initializeEventEntity(ev, event, []); + ev.role = event.params.role; ev.account = event.params.account; ev.sender = event.params.sender; @@ -91,15 +74,9 @@ function _createSetEvent( target: Bytes, name: Bytes ): void { - const ev = new SetEvent(createEventID("Set", event)); - ev.transactionHash = event.transaction.hash; - ev.gasPrice = event.transaction.gasPrice; - ev.timestamp = event.block.timestamp; - ev.blockNumber = event.block.number; - ev.logIndex = event.logIndex; - ev.order = getOrder(event.block.number, event.logIndex); - ev.name = "Set"; - ev.addresses = [target]; + const eventId = createEventID("Set", event); + const ev = new SetEvent(eventId); + initializeEventEntity(ev, event, [target]); ev.hashedName = name; ev.target = target; diff --git a/packages/subgraph/src/mappings/superToken.ts b/packages/subgraph/src/mappings/superToken.ts index b3c614bf98..4f79e253a9 100644 --- a/packages/subgraph/src/mappings/superToken.ts +++ b/packages/subgraph/src/mappings/superToken.ts @@ -20,7 +20,7 @@ import { } from "../../generated/schema"; import { createEventID, - getOrder, + initializeEventEntity, tokenHasValidHost, ZERO_ADDRESS, } from "../utils"; @@ -285,22 +285,15 @@ function updateHOLEntitiesForLiquidation( function _createAgreementLiquidatedByEventEntity( event: AgreementLiquidatedBy ): void { - let ev = new AgreementLiquidatedByEvent( - createEventID("AgreementLiquidatedBy", event) - ); - ev.transactionHash = event.transaction.hash; - ev.gasPrice = event.transaction.gasPrice; - ev.timestamp = event.block.timestamp; - ev.name = "AgreementLiquidatedBy"; - ev.addresses = [ + const eventId = createEventID("AgreementLiquidatedBy", event); + const ev = new AgreementLiquidatedByEvent(eventId); + initializeEventEntity(ev, event, [ event.address, event.params.liquidatorAccount, event.params.penaltyAccount, event.params.bondAccount, - ]; - ev.blockNumber = event.block.number; - ev.logIndex = event.logIndex; - ev.order = getOrder(event.block.number, event.logIndex); + ]) as AgreementLiquidatedByEvent; + ev.token = event.address; ev.liquidatorAccount = event.params.liquidatorAccount; ev.agreementClass = event.params.agreementClass; @@ -315,22 +308,15 @@ function _createAgreementLiquidatedByEventEntity( function _createAgreementLiquidatedV2EventEntity( event: AgreementLiquidatedV2 ): void { - let ev = new AgreementLiquidatedV2Event( - createEventID("AgreementLiquidatedV2", event) - ); - ev.transactionHash = event.transaction.hash; - ev.gasPrice = event.transaction.gasPrice; - ev.timestamp = event.block.timestamp; - ev.name = "AgreementLiquidatedV2"; - ev.addresses = [ + const eventId = createEventID("AgreementLiquidatedV2", event); + const ev = new AgreementLiquidatedV2Event(eventId); + initializeEventEntity(ev, event, [ event.address, event.params.liquidatorAccount, event.params.targetAccount, event.params.rewardAmountReceiver, - ]; - ev.blockNumber = event.block.number; - ev.logIndex = event.logIndex; - ev.order = getOrder(event.block.number, event.logIndex); + ]); + ev.token = event.address; ev.liquidatorAccount = event.params.liquidatorAccount; ev.agreementClass = event.params.agreementClass; @@ -360,15 +346,10 @@ function _createAgreementLiquidatedV2EventEntity( } function _createBurnedEventEntity(event: Burned): void { - let ev = new BurnedEvent(createEventID("Burned", event)); - ev.transactionHash = event.transaction.hash; - ev.gasPrice = event.transaction.gasPrice; - ev.timestamp = event.block.timestamp; - ev.name = "Burned"; - ev.addresses = [event.address, event.params.from]; - ev.blockNumber = event.block.number; - ev.logIndex = event.logIndex; - ev.order = getOrder(event.block.number, event.logIndex); + const eventId = createEventID("Burned", event); + const ev = new BurnedEvent(eventId); + initializeEventEntity(ev, event, [event.address, event.params.from]); + ev.token = event.address; ev.operator = event.params.operator; ev.from = event.params.from; @@ -379,15 +360,14 @@ function _createBurnedEventEntity(event: Burned): void { } function _createMintedEventEntity(event: Minted): void { - let ev = new MintedEvent(createEventID("Minted", event)); - ev.transactionHash = event.transaction.hash; - ev.gasPrice = event.transaction.gasPrice; - ev.timestamp = event.block.timestamp; - ev.name = "Minted"; - ev.addresses = [event.address, event.params.operator, event.params.to]; - ev.blockNumber = event.block.number; - ev.logIndex = event.logIndex; - ev.order = getOrder(event.block.number, event.logIndex); + const eventId = createEventID("Minted", event); + const ev = new MintedEvent(eventId); + initializeEventEntity(ev, event, [ + event.address, + event.params.operator, + event.params.to, + ]); + ev.token = event.address; ev.operator = event.params.operator; ev.to = event.params.to; @@ -398,15 +378,13 @@ function _createMintedEventEntity(event: Minted): void { } function _createSentEventEntity(event: Sent): void { - let ev = new SentEvent(createEventID("Sent", event)); - ev.transactionHash = event.transaction.hash; - ev.gasPrice = event.transaction.gasPrice; - ev.timestamp = event.block.timestamp; - ev.name = "Sent"; - ev.addresses = [event.address, event.params.operator, event.params.to]; - ev.blockNumber = event.block.number; - ev.logIndex = event.logIndex; - ev.order = getOrder(event.block.number, event.logIndex); + const eventId = createEventID("Sent", event); + const ev = new SentEvent(eventId); + initializeEventEntity(ev, event, [ + event.address, + event.params.operator, + event.params.to, + ]); ev.amount = event.params.amount; ev.data = event.params.data; ev.token = event.address; @@ -418,51 +396,37 @@ function _createSentEventEntity(event: Sent): void { } function _createTokenUpgradedEventEntity(event: TokenUpgraded): void { - let ev = new TokenUpgradedEvent(createEventID("TokenUpgraded", event)); + const eventId = createEventID("TokenUpgraded", event); + const ev = new TokenUpgradedEvent(eventId); + initializeEventEntity(ev, event, [event.address, event.params.account]); + ev.account = event.params.account.toHex(); - ev.transactionHash = event.transaction.hash; - ev.gasPrice = event.transaction.gasPrice; - ev.timestamp = event.block.timestamp; - ev.name = "TokenUpgraded"; - ev.addresses = [event.address, event.params.account]; - ev.blockNumber = event.block.number; - ev.logIndex = event.logIndex; - ev.order = getOrder(event.block.number, event.logIndex); ev.token = event.address; ev.amount = event.params.amount; ev.save(); } function _createTokenDowngradedEventEntity(event: TokenDowngraded): void { - let ev = new TokenDowngradedEvent(createEventID("TokenDowngraded", event)); + const eventId = createEventID("TokenDowngraded", event); + const ev = new TokenDowngradedEvent(eventId); + initializeEventEntity(ev, event, [event.address, event.params.account]); ev.account = event.params.account.toHex(); - ev.transactionHash = event.transaction.hash; - ev.gasPrice = event.transaction.gasPrice; - ev.timestamp = event.block.timestamp; - ev.name = "TokenDowngraded"; - ev.addresses = [event.address, event.params.account]; - ev.blockNumber = event.block.number; - ev.logIndex = event.logIndex; - ev.order = getOrder(event.block.number, event.logIndex); ev.token = event.address; ev.amount = event.params.amount; ev.save(); } function _createTransferEventEntity(event: Transfer): void { - let ev = new TransferEvent(createEventID("Transfer", event)); - let value = event.params.value; - ev.transactionHash = event.transaction.hash; - ev.gasPrice = event.transaction.gasPrice; - ev.timestamp = event.block.timestamp; - ev.name = "Transfer"; - ev.addresses = [event.address, event.params.from, event.params.to]; - ev.blockNumber = event.block.number; - ev.logIndex = event.logIndex; - ev.order = getOrder(event.block.number, event.logIndex); + const eventId = createEventID("Transfer", event); + const ev = new TransferEvent(eventId); + initializeEventEntity(ev, event, [ + event.address, + event.params.from, + event.params.to, + ]); ev.from = event.params.from.toHex(); ev.to = event.params.to.toHex(); - ev.value = value; + ev.value = event.params.value; ev.token = event.address; ev.save(); } diff --git a/packages/subgraph/src/mappings/superTokenFactory.ts b/packages/subgraph/src/mappings/superTokenFactory.ts index 91873df0f3..d6f7880037 100644 --- a/packages/subgraph/src/mappings/superTokenFactory.ts +++ b/packages/subgraph/src/mappings/superTokenFactory.ts @@ -8,7 +8,11 @@ import { SuperTokenCreatedEvent, SuperTokenLogicCreatedEvent, } from "../../generated/schema"; -import { createEventID, getOrder, tokenHasValidHost } from "../utils"; +import { + createEventID, + initializeEventEntity, + tokenHasValidHost, +} from "../utils"; import { getOrInitSuperToken } from "../mappingHelpers"; import { getHostAddress } from "../addresses"; @@ -18,18 +22,10 @@ export function handleSuperTokenCreated(event: SuperTokenCreated): void { if (!hasValidHost) { return; } + const eventId = createEventID("SuperTokenCreated", event); + const ev = new SuperTokenCreatedEvent(eventId); + initializeEventEntity(ev, event, [event.params.token]); - let ev = new SuperTokenCreatedEvent( - createEventID("SuperTokenCreated", event) - ); - ev.transactionHash = event.transaction.hash; - ev.gasPrice = event.transaction.gasPrice; - ev.timestamp = event.block.timestamp; - ev.name = "SuperTokenCreated"; - ev.addresses = [event.params.token]; - ev.blockNumber = event.block.number; - ev.logIndex = event.logIndex; - ev.order = getOrder(event.block.number, event.logIndex); ev.token = event.params.token; ev.save(); @@ -44,18 +40,9 @@ export function handleCustomSuperTokenCreated( if (!hasValidHost) { return; } - - let ev = new CustomSuperTokenCreatedEvent( - createEventID("CustomSuperTokenCreated", event) - ); - ev.transactionHash = event.transaction.hash; - ev.gasPrice = event.transaction.gasPrice; - ev.timestamp = event.block.timestamp; - ev.name = "CustomSuperTokenCreated"; - ev.addresses = [event.params.token]; - ev.blockNumber = event.block.number; - ev.logIndex = event.logIndex; - ev.order = getOrder(event.block.number, event.logIndex); + const eventId = createEventID("CustomSuperTokenCreated", event); + const ev = new CustomSuperTokenCreatedEvent(eventId); + initializeEventEntity(ev, event, [event.params.token]); ev.token = event.params.token; ev.save(); @@ -70,18 +57,10 @@ export function handleSuperTokenLogicCreated( if (!hasValidHost) { return; } + const eventId = createEventID("SuperTokenLogicCreated", event); + const ev = new SuperTokenLogicCreatedEvent(eventId); + initializeEventEntity(ev, event, []); - let ev = new SuperTokenLogicCreatedEvent( - createEventID("SuperTokenLogicCreated", event) - ); - ev.transactionHash = event.transaction.hash; - ev.gasPrice = event.transaction.gasPrice; - ev.timestamp = event.block.timestamp; - ev.name = "SuperTokenLogicCreated"; - ev.addresses = []; - ev.blockNumber = event.block.number; - ev.logIndex = event.logIndex; - ev.order = getOrder(event.block.number, event.logIndex); ev.tokenLogic = event.params.tokenLogic; ev.save(); } diff --git a/packages/subgraph/src/mappings/superfluidGovernance.ts b/packages/subgraph/src/mappings/superfluidGovernance.ts index 6d2765e1ca..aec64da854 100644 --- a/packages/subgraph/src/mappings/superfluidGovernance.ts +++ b/packages/subgraph/src/mappings/superfluidGovernance.ts @@ -12,19 +12,13 @@ import { PPPConfigurationChangedEvent, TrustedForwarderChangedEvent, } from "../../generated/schema"; -import { createEventID, getOrder } from "../utils"; +import { createEventID, initializeEventEntity } from "../utils"; export function handleConfigChanged(event: ConfigChanged): void { - let ev = new ConfigChangedEvent(createEventID("ConfigChanged", event)); - ev.transactionHash = event.transaction.hash; - ev.gasPrice = event.transaction.gasPrice; - ev.timestamp = event.block.timestamp; - ev.name = "ConfigChanged"; - ev.addresses = []; - ev.governanceAddress = event.address; - ev.blockNumber = event.block.number; - ev.order = getOrder(event.block.number, event.logIndex); - ev.logIndex = event.logIndex; + const eventId = createEventID("ConfigChanged", event); + const ev = new ConfigChangedEvent(eventId); + initializeEventEntity(ev, event, []); + ev.host = event.params.host; ev.superToken = event.params.superToken; ev.key = event.params.key; @@ -34,18 +28,10 @@ export function handleConfigChanged(event: ConfigChanged): void { } export function handleRewardAddressChanged(event: RewardAddressChanged): void { - let ev = new RewardAddressChangedEvent( - createEventID("RewardAddressChanged", event) - ); - ev.transactionHash = event.transaction.hash; - ev.gasPrice = event.transaction.gasPrice; - ev.timestamp = event.block.timestamp; - ev.name = "RewardAddressChanged"; - ev.addresses = []; - ev.governanceAddress = event.address; - ev.blockNumber = event.block.number; - ev.logIndex = event.logIndex; - ev.order = getOrder(event.block.number, event.logIndex); + const eventId = createEventID("RewardAddressChanged", event); + const ev = new RewardAddressChangedEvent(eventId); + initializeEventEntity(ev, event, []); + ev.host = event.params.host; ev.superToken = event.params.superToken; ev.isKeySet = event.params.isKeySet; @@ -56,18 +42,10 @@ export function handleRewardAddressChanged(event: RewardAddressChanged): void { export function handleCFAv1LiquidationPeriodChanged( event: CFAv1LiquidationPeriodChanged ): void { - let ev = new CFAv1LiquidationPeriodChangedEvent( - createEventID("CFAv1LiquidationPeriodChanged", event) - ); - ev.transactionHash = event.transaction.hash; - ev.gasPrice = event.transaction.gasPrice; - ev.timestamp = event.block.timestamp; - ev.name = "CFAv1LiquidationPeriodChanged"; - ev.addresses = []; - ev.governanceAddress = event.address; - ev.blockNumber = event.block.number; - ev.logIndex = event.logIndex; - ev.order = getOrder(event.block.number, event.logIndex); + const eventId = createEventID("CFAv1LiquidationPeriodChanged", event); + const ev = new CFAv1LiquidationPeriodChangedEvent(eventId); + initializeEventEntity(ev, event, []); + ev.host = event.params.host; ev.superToken = event.params.superToken; ev.isKeySet = event.params.isKeySet; @@ -78,18 +56,10 @@ export function handleCFAv1LiquidationPeriodChanged( export function handlePPPConfigurationChanged( event: PPPConfigurationChanged ): void { - let ev = new PPPConfigurationChangedEvent( - createEventID("PPPConfigurationChanged", event) - ); - ev.transactionHash = event.transaction.hash; - ev.gasPrice = event.transaction.gasPrice; - ev.timestamp = event.block.timestamp; - ev.name = "TrustedForwarderChanged"; - ev.addresses = []; - ev.governanceAddress = event.address; - ev.blockNumber = event.block.number; - ev.logIndex = event.logIndex; - ev.order = getOrder(event.block.number, event.logIndex); + const eventId = createEventID("PPPConfigurationChanged", event); + const ev = new PPPConfigurationChangedEvent(eventId); + initializeEventEntity(ev, event, []); + ev.host = event.params.host; ev.superToken = event.params.superToken; ev.isKeySet = event.params.isKeySet; @@ -101,18 +71,10 @@ export function handlePPPConfigurationChanged( export function handleTrustedForwarderChanged( event: TrustedForwarderChanged ): void { - let ev = new TrustedForwarderChangedEvent( - createEventID("TrustedForwarderChanged", event) - ); - ev.transactionHash = event.transaction.hash; - ev.gasPrice = event.transaction.gasPrice; - ev.timestamp = event.block.timestamp; - ev.order = getOrder(event.block.number, event.logIndex); - ev.name = "TrustedForwarderChanged"; - ev.addresses = []; - ev.governanceAddress = event.address; - ev.blockNumber = event.block.number; - ev.logIndex = event.logIndex; + const eventId = createEventID("TrustedForwarderChanged", event); + const ev = new TrustedForwarderChangedEvent(eventId); + initializeEventEntity(ev, event, []); + ev.host = event.params.host; ev.superToken = event.params.superToken; ev.isKeySet = event.params.isKeySet; diff --git a/packages/subgraph/src/utils.ts b/packages/subgraph/src/utils.ts index 6b2866f459..0a8f6880e9 100644 --- a/packages/subgraph/src/utils.ts +++ b/packages/subgraph/src/utils.ts @@ -1,4 +1,4 @@ -import { Address, BigInt, Bytes, ethereum, log } from "@graphprotocol/graph-ts"; +import { Address, BigInt, Bytes, Entity, ethereum, log, Value } from "@graphprotocol/graph-ts"; import { ISuperToken as SuperToken } from "../generated/templates/SuperToken/ISuperToken"; import { Resolver } from "../generated/ResolverV1/Resolver"; import { @@ -42,6 +42,34 @@ export function createEventID( ); } +/** + * Initialize event and its base properties on Event interface. + * @param event the ethereum.Event object + * @param addresses the addresses array + * @returns Entity to be casted as original Event type + */ +export function initializeEventEntity( + entity: Entity, + event: ethereum.Event, + addresses: Bytes[] + ): Entity { + const idValue = entity.get("id"); + if (!idValue) return entity; + + const stringId = idValue.toString(); + const name = stringId.split("-")[0]; + + entity.set("blockNumber", Value.fromBigInt(event.block.number)); + entity.set("logIndex", Value.fromBigInt(event.logIndex)); + entity.set("order", Value.fromBigInt(getOrder(event.block.number, event.logIndex))); + entity.set("name", Value.fromString(name)); + entity.set("addresses", Value.fromBytesArray(addresses)); + entity.set("timestamp", Value.fromBigInt(event.block.timestamp)); + entity.set("transactionHash", Value.fromBytes(event.transaction.hash)); + entity.set("gasPrice", Value.fromBigInt(event.transaction.gasPrice)); + return entity; + } + /************************************************************************** * HOL entities util functions *************************************************************************/ diff --git a/packages/subgraph/tasks/deploy-all-networks.sh b/packages/subgraph/tasks/deploy-all-networks.sh index 97be53d93b..e90a51e5f7 100755 --- a/packages/subgraph/tasks/deploy-all-networks.sh +++ b/packages/subgraph/tasks/deploy-all-networks.sh @@ -1,8 +1,6 @@ #!/bin/bash JQ="../../node_modules/node-jq/bin/jq" -mustache="../../node_modules/mustache/bin/mustache" -graph="../../node_modules/@graphprotocol/graph-cli" NETWORKS=( $($JQ -r .[] ./networks.json) ) [ $? == 0 ] || exit 1 diff --git a/packages/subgraph/tasks/deploy-to-network.sh b/packages/subgraph/tasks/deploy-to-network.sh index 20c04589b4..215097419f 100644 --- a/packages/subgraph/tasks/deploy-to-network.sh +++ b/packages/subgraph/tasks/deploy-to-network.sh @@ -12,4 +12,4 @@ graph deploy \ superfluid-finance/protocol-$1-$2 \ --node https://api.thegraph.com/deploy/ \ --ipfs https://api.thegraph.com/ipfs \ - --access-token $THEGRAPH_ACCESS_TOKEN + --access-token $THE_GRAPH_ACCESS_TOKEN diff --git a/packages/subgraph/tasks/deploy-to-satsuma.sh b/packages/subgraph/tasks/deploy-to-satsuma.sh new file mode 100644 index 0000000000..f04c875315 --- /dev/null +++ b/packages/subgraph/tasks/deploy-to-satsuma.sh @@ -0,0 +1,22 @@ +#!/bin/bash +# $1 = the version label +# $2 = the network + +# From the Satsuma docs: +# https://docs.satsuma.xyz/subgraph-deploys +# cd + +# graph deploy \ +# --version-label \ +# --node http://app.satsuma.xyz/api/subgraphs/deploy \ +# --deploy-key + +mustache="../../node_modules/mustache/bin/mustache" +graph="../../node_modules/@graphprotocol/graph-cli" + +mustache config/$2.json subgraph.template.yaml > subgraph.yaml +mustache config/$2.json src/addresses.template.ts > src/addresses.ts +graph deploy $2 \ + --version-label $1 \ + --node https://app.satsuma.xyz/api/subgraphs/deploy \ + --deploy-key $SATSUMA_DEPLOY_KEY \ No newline at end of file diff --git a/packages/subgraph/truffle-config.js b/packages/subgraph/truffle-config.js deleted file mode 100644 index d3a85c02cf..0000000000 --- a/packages/subgraph/truffle-config.js +++ /dev/null @@ -1,31 +0,0 @@ -/** - * This truffle-config is only used for testing the subgraph. - * - * See the @superfluid-finance/ethereum-contracts package for a good example. - **/ -const path = require("path"); -module.exports = { - contracts_build_directory: path.join( - __dirname, - "../ethereum-contracts/build/contracts" - ), - compilers: { - solc: { - version: "0.7.6", - settings: { - optimizer: { - enabled: true, - runs: 200, - }, - }, - }, - }, - networks: { - ganache: { - host: "0.0.0.0", - network_id: "*", - port: 8545, - networkCheckTimeout: 10000, - }, - }, -}; diff --git a/tasks/check-changeset.sh b/tasks/check-changeset.sh index 9fb54ae2fa..1aa66c9cf3 100755 --- a/tasks/check-changeset.sh +++ b/tasks/check-changeset.sh @@ -20,6 +20,7 @@ function setBuildAll() { BUILD_ETHEREUM_CONTRACTS=1 BUILD_SDK_CORE=1 BUILD_SDK_REDUX=1 + BUILD_SPEC_HASKELL=1 BUILD_SUBGRAPH=1 echo Everything will be tested. } @@ -54,27 +55,21 @@ if ! [ -z "$GITHUB_ENV" ];then echo SDK-REDUX will be tested. fi # if subgraph package changed - if grep -E "^packages/subgraph/(subgraph.template.yaml|schema.graphql|config|scripts|src|test|hardhat.config.ts|package.json)" changed-files.list;then + if grep -E "^packages/subgraph/(subgraph.template.yaml|schema.graphql|config|scripts|src|tasks|test|hardhat.config.ts|package.json|docker-compose.yml)" changed-files.list;then BUILD_SUBGRAPH=1 echo Subgraph will be tested. fi - # if sdk-redux package changed + # if haskell spec package changed if grep -E "^packages/spec-haskell/(packages/|cabal.project)" changed-files.list;then BUILD_SPEC_HASKELL=1 echo SPEC-HASKELL will be tested. fi - # if any exapmle project changed - if grep -E "^examples/" changed-files.list;then - BUILD_EXAMPLES=1 - echo Examples will be tested. - fi echo "BUILD_ETHEREUM_CONTRACTS=${BUILD_ETHEREUM_CONTRACTS}" >> $GITHUB_ENV echo "BUILD_SDK_CORE=${BUILD_SDK_CORE}" >> $GITHUB_ENV echo "BUILD_SDK_REDUX=${BUILD_SDK_REDUX}" >> $GITHUB_ENV echo "BUILD_SUBGRAPH=${BUILD_SUBGRAPH}" >> $GITHUB_ENV echo "BUILD_SPEC_HASKELL=${BUILD_SPEC_HASKELL}" >> $GITHUB_ENV - echo "BUILD_EXAMPLES=${BUILD_EXAMPLES}" >> $GITHUB_ENV if [ "$BUILD_ETHEREUM_CONTRACTS" == 1 ] || [ "$BUILD_SDK_CORE" == 1 ] || [ "$BUILD_SDK_REDUX" == 1 ];then echo PR packages will be published. echo "PUBLISH_PR_ARTIFACT=1" >> $GITHUB_ENV diff --git a/yarn.lock b/yarn.lock index 5afeb24ede..d62e409e55 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10,10 +10,10 @@ "@jridgewell/gen-mapping" "^0.1.0" "@jridgewell/trace-mapping" "^0.3.9" -"@apollo/protobufjs@1.2.4": - version "1.2.4" - resolved "https://registry.yarnpkg.com/@apollo/protobufjs/-/protobufjs-1.2.4.tgz#d913e7627210ec5efd758ceeb751c776c68ba133" - integrity sha512-npVJ9NVU/pynj+SCU+fambvTneJDyCnif738DnZ7pCxdDtzeEz7WkpSIq5wNUmWm5Td55N+S2xfqZ+WP4hDLng== +"@apollo/protobufjs@1.2.6": + version "1.2.6" + resolved "https://registry.yarnpkg.com/@apollo/protobufjs/-/protobufjs-1.2.6.tgz#d601e65211e06ae1432bf5993a1a0105f2862f27" + integrity sha512-Wqo1oSHNUj/jxmsVp4iR3I480p6qdqHikn38lKrFhfzcDJ7lwd7Ck7cHRl4JE81tWNArl77xhnG/OkZhxKBYOw== dependencies: "@protobufjs/aspromise" "^1.1.2" "@protobufjs/base64" "^1.1.2" @@ -43,9 +43,9 @@ lru-cache "^7.10.1" "@apollo/utils.logger@^1.0.0": - version "1.0.0" - resolved "https://registry.yarnpkg.com/@apollo/utils.logger/-/utils.logger-1.0.0.tgz#6e3460a2250c2ef7c2c3b0be6b5e148a1596f12b" - integrity sha512-dx9XrjyisD2pOa+KsB5RcDbWIAdgC91gJfeyLCgy0ctJMjQe7yZK5kdWaWlaOoCeX0z6YI9iYlg7vMPyMpQF3Q== + version "1.0.1" + resolved "https://registry.yarnpkg.com/@apollo/utils.logger/-/utils.logger-1.0.1.tgz#aea0d1bb7ceb237f506c6bbf38f10a555b99a695" + integrity sha512-XdlzoY7fYNK4OIcvMD2G94RoFZbzTQaNP0jozmqqMudmaGo2I/2Jx71xlDJ801mWA/mbYRihyaw6KJii7k5RVA== "@apollo/utils.printwithreducedwhitespace@^1.1.0": version "1.1.0" @@ -1775,6 +1775,22 @@ "@graphql-tools/utils" "8.8.0" tslib "^2.4.0" +"@graphql-tools/merge@8.3.1": + version "8.3.1" + resolved "https://registry.yarnpkg.com/@graphql-tools/merge/-/merge-8.3.1.tgz#06121942ad28982a14635dbc87b5d488a041d722" + integrity sha512-BMm99mqdNZbEYeTPK3it9r9S6rsZsQKtlqJsSBknAclXq2pGEfOxjcIZi+kBSkHZKPKCRrYDd5vY0+rUmIHVLg== + dependencies: + "@graphql-tools/utils" "8.9.0" + tslib "^2.4.0" + +"@graphql-tools/merge@8.3.10": + version "8.3.10" + resolved "https://registry.yarnpkg.com/@graphql-tools/merge/-/merge-8.3.10.tgz#81f374bc1e8c81d45cb1003d8ed05f181b7e6bd5" + integrity sha512-/hSg69JwqEA+t01wQmMGKPuaJ9VJBSz6uAXhbNNrTBJu8bmXljw305NVXM49pCwDKFVUGtbTqYrBeLcfT3RoYw== + dependencies: + "@graphql-tools/utils" "9.0.1" + tslib "^2.4.0" + "@graphql-tools/merge@8.3.4": version "8.3.4" resolved "https://registry.yarnpkg.com/@graphql-tools/merge/-/merge-8.3.4.tgz#749f710d3a930512e6ca36e3bb053c12e22ef332" @@ -1784,12 +1800,12 @@ tslib "^2.4.0" "@graphql-tools/mock@^8.1.2": - version "8.7.0" - resolved "https://registry.yarnpkg.com/@graphql-tools/mock/-/mock-8.7.0.tgz#da286596ef05b84165e76cf6f608eeea4109a60c" - integrity sha512-K/hqP442mXAvW36v/3TmqFpNzRw14P86xlsJZod88OXwpDfb97X09z1QsaMcvSe8E7ijcKWLlTRk15/vDQSL2Q== + version "8.7.10" + resolved "https://registry.yarnpkg.com/@graphql-tools/mock/-/mock-8.7.10.tgz#1a277f29ba96b8111c063eb6f5899df441be786d" + integrity sha512-PuRGfk6TQger7EfE08yO3+QCAcZ6nYo3kyoEmTPc27w4yiqKCwZIyD8vegzl/EQphEourjaOhO149te6qNEUeQ== dependencies: - "@graphql-tools/schema" "8.5.0" - "@graphql-tools/utils" "8.8.0" + "@graphql-tools/schema" "9.0.8" + "@graphql-tools/utils" "9.0.1" fast-json-stable-stringify "^2.1.0" tslib "^2.4.0" @@ -1834,7 +1850,7 @@ "@graphql-tools/utils" "8.8.0" tslib "^2.4.0" -"@graphql-tools/schema@8.5.0", "@graphql-tools/schema@^8.0.0", "@graphql-tools/schema@^8.3.1": +"@graphql-tools/schema@8.5.0": version "8.5.0" resolved "https://registry.yarnpkg.com/@graphql-tools/schema/-/schema-8.5.0.tgz#0332b3a2e674d16e9bf8a58dfd47432449ce2368" integrity sha512-VeFtKjM3SA9/hCJJfr95aEdC3G0xIKM9z0Qdz4i+eC1g2fdZYnfWFt2ucW4IME+2TDd0enHlKzaV0qk2SLVUww== @@ -1844,7 +1860,7 @@ tslib "^2.4.0" value-or-promise "1.0.11" -"@graphql-tools/schema@9.0.2", "@graphql-tools/schema@^9.0.0": +"@graphql-tools/schema@9.0.2": version "9.0.2" resolved "https://registry.yarnpkg.com/@graphql-tools/schema/-/schema-9.0.2.tgz#f00dcd2600c25a2e8d69e3a4ab279ea150aab0c4" integrity sha512-FnBM1PMKQ6y8KlzeFocnEwcGA/IT++z4v+hvvwwXL+IUYDNqmrp9XYNklpQRb/KKSbTtKnQapCWNiVNex7jl+Q== @@ -1854,6 +1870,26 @@ tslib "^2.4.0" value-or-promise "1.0.11" +"@graphql-tools/schema@9.0.8", "@graphql-tools/schema@^9.0.0": + version "9.0.8" + resolved "https://registry.yarnpkg.com/@graphql-tools/schema/-/schema-9.0.8.tgz#df3119c8543e6dacf425998f83aa714e2ee86eb0" + integrity sha512-PnES7sNkhQ/FdPQhP7cup0OIzwzQh+nfjklilU7YJzE209ACIyEQtxoNCfvPW5eV6hc9bWsBQeI3Jm4mMtwxNA== + dependencies: + "@graphql-tools/merge" "8.3.10" + "@graphql-tools/utils" "9.0.1" + tslib "^2.4.0" + value-or-promise "1.0.11" + +"@graphql-tools/schema@^8.0.0", "@graphql-tools/schema@^8.3.1": + version "8.5.1" + resolved "https://registry.yarnpkg.com/@graphql-tools/schema/-/schema-8.5.1.tgz#c2f2ff1448380919a330312399c9471db2580b58" + integrity sha512-0Esilsh0P/qYcB5DKQpiKeQs/jevzIadNTaT0jeWklPMwNbT7yMX4EqZany7mbeRRlSRwMzNzL5olyFdffHBZg== + dependencies: + "@graphql-tools/merge" "8.3.1" + "@graphql-tools/utils" "8.9.0" + tslib "^2.4.0" + value-or-promise "1.0.11" + "@graphql-tools/url-loader@7.14.2", "@graphql-tools/url-loader@^7.13.2": version "7.14.2" resolved "https://registry.yarnpkg.com/@graphql-tools/url-loader/-/url-loader-7.14.2.tgz#170c909f07b35e29c4d27af7764516f1b31d9484" @@ -1910,6 +1946,20 @@ dependencies: tslib "^2.4.0" +"@graphql-tools/utils@8.9.0": + version "8.9.0" + resolved "https://registry.yarnpkg.com/@graphql-tools/utils/-/utils-8.9.0.tgz#c6aa5f651c9c99e1aca55510af21b56ec296cdb7" + integrity sha512-pjJIWH0XOVnYGXCqej8g/u/tsfV4LvLlj0eATKQu5zwnxd/TiTHq7Cg313qUPTFFHZ3PP5wJ15chYVtLDwaymg== + dependencies: + tslib "^2.4.0" + +"@graphql-tools/utils@9.0.1": + version "9.0.1" + resolved "https://registry.yarnpkg.com/@graphql-tools/utils/-/utils-9.0.1.tgz#04933b34c3435ef9add4f8bdfdf452040376f9d0" + integrity sha512-z6FimVa5E44bHKmqK0/uMp9hHvHo2Tkt9A5rlLb40ReD/8IFKehSXLzM4b2N1vcP7mSsbXIdDK9Aoc8jT/he1Q== + dependencies: + tslib "^2.4.0" + "@graphql-tools/wrap@8.5.0": version "8.5.0" resolved "https://registry.yarnpkg.com/@graphql-tools/wrap/-/wrap-8.5.0.tgz#ce1b0d775e1fc3a17404df566f4d2196d31c6e20" @@ -3517,10 +3567,10 @@ resolved "https://registry.yarnpkg.com/@redux-saga/types/-/types-1.1.0.tgz#0e81ce56b4883b4b2a3001ebe1ab298b84237204" integrity sha512-afmTuJrylUU/0OtqzaRkbyYFFNgCF73Bvel/sw90pvGrWIZ+vyoIJqA6eMSoA6+nb443kTmulmBtC9NerXboNg== -"@reduxjs/toolkit@^1.8.3": - version "1.8.3" - resolved "https://registry.yarnpkg.com/@reduxjs/toolkit/-/toolkit-1.8.3.tgz#9c6a9c497bde43a67618d37a4175a00ae12efeb2" - integrity sha512-lU/LDIfORmjBbyDLaqFN2JB9YmAT1BElET9y0ZszwhSBa5Ef3t6o5CrHupw5J1iOXwd+o92QfQZ8OJpwXvsssg== +"@reduxjs/toolkit@^1.8.6": + version "1.8.6" + resolved "https://registry.yarnpkg.com/@reduxjs/toolkit/-/toolkit-1.8.6.tgz#147fb7957befcdb75bc9c1230db63628e30e4332" + integrity sha512-4Ia/Loc6WLmdSOzi7k5ff7dLK8CgG2b8aqpLsCAJhazAzGdp//YBUSaj0ceW6a3kDBDNRrq5CRwyCS0wBiL1ig== dependencies: immer "^9.0.7" redux "^4.1.2" @@ -3690,10 +3740,10 @@ dependencies: antlr4ts "^0.5.0-alpha.4" -"@superfluid-finance/ethereum-contracts@1.4.1": - version "1.4.1" - resolved "https://registry.yarnpkg.com/@superfluid-finance/ethereum-contracts/-/ethereum-contracts-1.4.1.tgz#1ce38ca09de4c9fdf82857557d054c3b95e2012f" - integrity sha512-44wMl6v3pt+4+Q2zITUIeHFylS6iayM0ibjmtzhqJWrnC7BOuA39hoJWswZBrl0Tq+aESoOOXKR4v9jt5kYEbw== +"@superfluid-finance/ethereum-contracts@1.4.0": + version "1.4.0" + resolved "https://registry.yarnpkg.com/@superfluid-finance/ethereum-contracts/-/ethereum-contracts-1.4.0.tgz#942747bbb8b5ab752092553973285bb42bc47352" + integrity sha512-j552IAThuzdgnxsyQ9ujqsOUZl48PIv51wLARhiSv6l78RwdEnZd7U1/3UXyZuN8i0JARKExOetd9+SDaSRW8Q== dependencies: "@decentral.ee/web3-helpers" "0.5.3" "@openzeppelin/contracts" "4.7.3" @@ -3705,7 +3755,7 @@ "@superfluid-finance/metadata@git+https://github.com/superfluid-finance/metadata.git": version "1.1.0" - resolved "git+https://github.com/superfluid-finance/metadata.git#689edf7204836dbbfa8fd0bafce3080bb0271c03" + resolved "git+https://github.com/superfluid-finance/metadata.git#aa1f9158956355fa1224cdf973b2dc972f3e5703" "@szmarczak/http-timer@^1.1.2": version "1.1.2" @@ -5066,17 +5116,17 @@ apollo-datasource@^3.3.2: "@apollo/utils.keyvaluecache" "^1.0.1" apollo-server-env "^4.2.1" -apollo-reporting-protobuf@^3.3.1, apollo-reporting-protobuf@^3.3.2: - version "3.3.2" - resolved "https://registry.yarnpkg.com/apollo-reporting-protobuf/-/apollo-reporting-protobuf-3.3.2.tgz#2078c53d3140bc6221c6040c5326623e0c21c8d4" - integrity sha512-j1tx9tmkVdsLt1UPzBrvz90PdjAeKW157WxGn+aXlnnGfVjZLIRXX3x5t1NWtXvB7rVaAsLLILLtDHW382TSoQ== +apollo-reporting-protobuf@^3.3.1, apollo-reporting-protobuf@^3.3.3: + version "3.3.3" + resolved "https://registry.yarnpkg.com/apollo-reporting-protobuf/-/apollo-reporting-protobuf-3.3.3.tgz#df2b7ff73422cd682af3f1805d32301aefdd9e89" + integrity sha512-L3+DdClhLMaRZWVmMbBcwl4Ic77CnEBPXLW53F7hkYhkaZD88ivbCVB1w/x5gunO6ZHrdzhjq0FHmTsBvPo7aQ== dependencies: - "@apollo/protobufjs" "1.2.4" + "@apollo/protobufjs" "1.2.6" apollo-server-core@^3.10.0: - version "3.10.0" - resolved "https://registry.yarnpkg.com/apollo-server-core/-/apollo-server-core-3.10.0.tgz#6680b4eb4699829ed50d8a592721ee5e5e11e041" - integrity sha512-ln5drIk3oW/ycYhcYL9TvM7vRf7OZwJrgHWlnjnMakozBQIBSumdMi4pN001DhU9mVBWTfnmBv3CdcxJdGXIvA== + version "3.11.0" + resolved "https://registry.yarnpkg.com/apollo-server-core/-/apollo-server-core-3.11.0.tgz#dbbf4c03ac0fdd8774e03c1f4f0d1ea1448b743c" + integrity sha512-5iRlkbilXpQeY66/F2/t2oNO0YSqb+kFb5lyMUIqK9VLuBfI/hILQDa5H71ar7hhexKwoDzIDfSJRg5ASNmnQw== dependencies: "@apollo/utils.keyvaluecache" "^1.0.1" "@apollo/utils.logger" "^1.0.0" @@ -5087,18 +5137,19 @@ apollo-server-core@^3.10.0: "@graphql-tools/schema" "^8.0.0" "@josephg/resolvable" "^1.0.0" apollo-datasource "^3.3.2" - apollo-reporting-protobuf "^3.3.2" + apollo-reporting-protobuf "^3.3.3" apollo-server-env "^4.2.1" apollo-server-errors "^3.3.1" - apollo-server-plugin-base "^3.6.2" - apollo-server-types "^3.6.2" + apollo-server-plugin-base "^3.7.0" + apollo-server-types "^3.7.0" async-retry "^1.2.1" fast-json-stable-stringify "^2.1.0" graphql-tag "^2.11.0" loglevel "^1.6.8" lru-cache "^6.0.0" + node-abort-controller "^3.0.1" sha.js "^2.4.11" - uuid "^8.0.0" + uuid "^9.0.0" whatwg-mimetype "^3.0.0" apollo-server-env@^4.2.1: @@ -5130,21 +5181,21 @@ apollo-server-express@^3.10.0: cors "^2.8.5" parseurl "^1.3.3" -apollo-server-plugin-base@^3.6.2: - version "3.6.2" - resolved "https://registry.yarnpkg.com/apollo-server-plugin-base/-/apollo-server-plugin-base-3.6.2.tgz#f256e1f274c8fee0d7267b6944f402da71788fb3" - integrity sha512-erWXjLOO1u7fxQkbxJ2cwSO7p0tYzNied91I1SJ9tikXZ/2eZUyDyvrpI+4g70kOdEi+AmJ5Fo8ahEXKJ75zdg== +apollo-server-plugin-base@^3.7.0: + version "3.7.0" + resolved "https://registry.yarnpkg.com/apollo-server-plugin-base/-/apollo-server-plugin-base-3.7.0.tgz#b7170c2be0344d5f4382fea951f6d1dd274d6635" + integrity sha512-YRPjqFHvWK9eM4gN3D4ArrAtPY7Mb1FL+YoXXwq2GxdrsZSolnDYQkqZ6BhK11J8lUmAQpnpunK91IPZshWluA== dependencies: - apollo-server-types "^3.6.2" + apollo-server-types "^3.7.0" -apollo-server-types@^3.6.2: - version "3.6.2" - resolved "https://registry.yarnpkg.com/apollo-server-types/-/apollo-server-types-3.6.2.tgz#34bb0c335fcce3057cbdf72b3b63da182de6fc84" - integrity sha512-9Z54S7NB+qW1VV+kmiqwU2Q6jxWfX89HlSGCGOo3zrkrperh85LrzABgN9S92+qyeHYd72noMDg2aI039sF3dg== +apollo-server-types@^3.6.2, apollo-server-types@^3.7.0: + version "3.7.0" + resolved "https://registry.yarnpkg.com/apollo-server-types/-/apollo-server-types-3.7.0.tgz#5a6f6f05a3c2ed937ad339b91665248dad957733" + integrity sha512-Y2wx7eH/dqqYDdzt0KBJRbVKR10bLiup2aT8huoBbp/u3nbCN88jo1yW+FvlETeV+iKuoY3RiZDlHIvcDQ5/lA== dependencies: "@apollo/utils.keyvaluecache" "^1.0.1" "@apollo/utils.logger" "^1.0.0" - apollo-reporting-protobuf "^3.3.2" + apollo-reporting-protobuf "^3.3.3" apollo-server-env "^4.2.1" apollo-server@^3.6.3: @@ -12877,9 +12928,9 @@ lru-cache@^6.0.0: yallist "^4.0.0" lru-cache@^7.10.1, lru-cache@^7.4.4, lru-cache@^7.5.1, lru-cache@^7.7.1: - version "7.13.0" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-7.13.0.tgz#c8178692969fb680cad948db4aad54066590a65a" - integrity sha512-SNFKDOORR41fkWP3DXiIUvXvfzDRPg3bxD1+29iRyP2ZW+Njp2o6zhx9YkEpq1tbP0AEDNW2VBUedzDIxmNhdg== + version "7.14.1" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-7.14.1.tgz#8da8d2f5f59827edb388e63e459ac23d6d408fea" + integrity sha512-ysxwsnTKdAx96aTRdhDOCQfDgbHnt8SK0KY8SEjO0wHinhWOFTESbjVCMPbU1uGXg/ch4lifqx0wfjOawU2+WA== lru_map@^0.3.3: version "0.3.3" @@ -17924,10 +17975,10 @@ truffle-flattener@^1.6.0: mkdirp "^1.0.4" tsort "0.0.1" -truffle-plugin-verify@0.5.28: - version "0.5.28" - resolved "https://registry.yarnpkg.com/truffle-plugin-verify/-/truffle-plugin-verify-0.5.28.tgz#e0d3811c366e0625656ea97f7443ef897f004332" - integrity sha512-wmWJsJPq9Zj/FaJVXISwnL0mOGGKLgSCbOnwXzxmuv+0iRcUuccFAianYP4Ru/QjzsD2T6pQ7TUjVGLEcCkMtg== +truffle-plugin-verify@0.5.31: + version "0.5.31" + resolved "https://registry.yarnpkg.com/truffle-plugin-verify/-/truffle-plugin-verify-0.5.31.tgz#99c34fe6f5b41917831dd80b9b3b6f4fa26db542" + integrity sha512-hojMRl+43FwJuI8r+nLZcer3iDVT6+uyoQtJpXpru4QWS4v0knPlLZHqF0xPXavuuHss6cqTyBWO8FeI6cC6KQ== dependencies: axios "^0.26.1" cli-logger "^0.5.40" @@ -18044,11 +18095,16 @@ tslib@^1.8.1, tslib@^1.9.0, tslib@^1.9.3: resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== -tslib@^2.0.0, tslib@^2.0.3, tslib@^2.1.0, tslib@^2.3.0, tslib@^2.4.0, tslib@~2.4.0: +tslib@^2.0.0, tslib@^2.0.3, tslib@^2.3.0, tslib@~2.4.0: version "2.4.0" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.4.0.tgz#7cecaa7f073ce680a05847aa77be941098f36dc3" integrity sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ== +tslib@^2.1.0, tslib@^2.4.0: + version "2.4.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.4.1.tgz#0d0bfbaac2880b91e22df0768e55be9753a5b17e" + integrity sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA== + tsort@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/tsort/-/tsort-0.0.1.tgz#e2280f5e817f8bf4275657fd0f9aebd44f5a2786" @@ -18534,7 +18590,7 @@ uuid@3.3.2: resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131" integrity sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA== -uuid@8.3.2, uuid@^8.0.0, uuid@^8.3.2: +uuid@8.3.2, uuid@^8.3.2: version "8.3.2" resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== @@ -18544,6 +18600,11 @@ uuid@^3.0.1, uuid@^3.2.1, uuid@^3.3.2: resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== +uuid@^9.0.0: + version "9.0.0" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-9.0.0.tgz#592f550650024a38ceb0c562f2f6aa435761efb5" + integrity sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg== + v8-compile-cache-lib@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf" @@ -19618,9 +19679,9 @@ xmlhttprequest@1.8.0: integrity sha512-58Im/U0mlVBLM38NdZjHyhuMtCqa61469k2YP/AaPbvCoV9aQGUpbJBj1QRm2ytRiVQBD/fsw7L2bJGDVQswBA== xss@^1.0.8: - version "1.0.13" - resolved "https://registry.yarnpkg.com/xss/-/xss-1.0.13.tgz#6e48f616128b39f366dfadc57411e1eb5b341c6c" - integrity sha512-clu7dxTm1e8Mo5fz3n/oW3UCXBfV89xZ72jM8yzo1vR/pIS0w3sgB3XV2H8Vm6zfGnHL0FzvLJPJEBhd86/z4Q== + version "1.0.14" + resolved "https://registry.yarnpkg.com/xss/-/xss-1.0.14.tgz#4f3efbde75ad0d82e9921cc3c95e6590dd336694" + integrity sha512-og7TEJhXvn1a7kzZGQ7ETjdQVS2UfZyTlsEdDOqvQF7GoxNfY+0YLCzBy1kPdsDDx4QuNAonQPddpsn6Xl/7sw== dependencies: commander "^2.20.3" cssfilter "0.0.10"