diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml
index d3cec6a7a12..b1ce025f86d 100644
--- a/.github/workflows/CI.yml
+++ b/.github/workflows/CI.yml
@@ -2,8 +2,8 @@ name: CI
on: [push]
jobs:
- build:
- name: Build
+ commonwealth:
+ name: Commonwealth Tests
runs-on: ubuntu-latest
defaults:
@@ -64,3 +64,47 @@ jobs:
with:
name: code-coverage-report
path: coverage
+
+ chain-events:
+ name: Chain Events Tests
+ runs-on: ubuntu-latest
+ defaults:
+ run:
+ working-directory: packages/chain-events
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v1
+
+ - name: Setup Node
+ uses: actions/setup-node@v1
+ with:
+ node-version: 14.x
+
+ - name: Get yarn cache directory path
+ id: yarn-cache-dir-path
+ run: echo "::set-output name=dir::$(yarn cache dir)"
+
+ - uses: actions/cache@v2
+ id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`)
+ with:
+ path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
+ key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
+ restore-keys: |
+ ${{ runner.os }}-yarn-
+
+ - name: Install dependencies
+ run: yarn
+
+ - name: Run unit tests
+ run: yarn unit-test
+
+ # TODO: fix integration tests -- they currently fail
+ # - name: Run integration tests
+ # run: yarn integration-test
+
+ # TODO: reintegrate code coverage once we get nyc working with hardhat
+
+ # TODO: fix linter and then reintegrate using current ts version
+ # - name: Run linter
+ # run: yarn lint
\ No newline at end of file
diff --git a/package.json b/package.json
index a4453cf64f8..404b6de7ada 100644
--- a/package.json
+++ b/package.json
@@ -15,6 +15,7 @@
"keywords": [],
"workspaces": [
"packages/common-common",
+ "packages/chain-events",
"packages/token-balance-cache",
"packages/commonwealth"
],
diff --git a/packages/chain-events/.eslintrc.json b/packages/chain-events/.eslintrc.json
new file mode 100644
index 00000000000..be2d25d1e12
--- /dev/null
+++ b/packages/chain-events/.eslintrc.json
@@ -0,0 +1,42 @@
+{
+ "extends": [
+ "airbnb-base",
+ "plugin:prettier/recommended",
+ "plugin:import/errors",
+ "plugin:import/warnings",
+ "plugin:import/typescript",
+ "plugin:@typescript-eslint/eslint-recommended",
+ "plugin:@typescript-eslint/recommended"
+ ],
+ "plugins": ["@typescript-eslint"],
+ "parser": "@typescript-eslint/parser",
+ "settings": {
+ "import/extensions": [".js", ".ts"],
+ "import/resolver": {
+ "node": {},
+ "webpack": {
+ "config": "webpack/webpack.common.js"
+ }
+ }
+ },
+ "ignorePatterns": ["contractTypes", "eth", "dist"],
+ "rules": {
+ "@typescript-eslint/interface-name-prefix": "off",
+ "import/prefer-default-export": 0,
+ "import/extensions": 0,
+ "import/no-cycle": 0,
+ "import/order": ["error", {
+ "newlines-between": "always"
+ }],
+ "max-classes-per-file": "off",
+ "no-await-in-loop": "off",
+ "no-import-cycles": "off",
+ "no-nested-ternary": "off",
+ "no-param-reassign": "off",
+ "no-plusplus": "off",
+ "no-restricted-syntax": "off",
+ "no-underscore-dangle": "off",
+ "no-useless-constructor": "off",
+ "class-methods-use-this": "off"
+ }
+}
diff --git a/packages/chain-events/.github/PULL_REQUEST_TEMPLATE.md b/packages/chain-events/.github/PULL_REQUEST_TEMPLATE.md
new file mode 100644
index 00000000000..1339edd3355
--- /dev/null
+++ b/packages/chain-events/.github/PULL_REQUEST_TEMPLATE.md
@@ -0,0 +1,16 @@
+
+
+## Description
+
+
+## Motivation and Context
+
+
+
+## How has this been tested?
+
+
+
+
+## Have proper tags been added (for bug, enhancement, breaking change)?
+- [ ] yes
diff --git a/packages/chain-events/.github/workflows/build.yml b/packages/chain-events/.github/workflows/build.yml
new file mode 100644
index 00000000000..d9a37c79790
--- /dev/null
+++ b/packages/chain-events/.github/workflows/build.yml
@@ -0,0 +1,41 @@
+name: Build
+on: [push]
+
+jobs:
+ build:
+ name: Build
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v1
+
+ - name: Setup Node
+ uses: actions/setup-node@v1
+ with:
+ node-version: 14.x
+
+ - name: Get yarn cache directory path
+ id: yarn-cache-dir-path
+ run: echo "::set-output name=dir::$(yarn cache dir)"
+
+ - uses: actions/cache@v2
+ id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`)
+ with:
+ path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
+ key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
+ restore-keys: |
+ ${{ runner.os }}-yarn-
+
+ - name: Install dependencies
+ run: yarn
+
+ - name: Run unit tests
+ run: yarn unit-test
+
+ - name: Run integration tests
+ run: yarn integration-test
+
+ # TODO: reintegrate code coverage once we get nyc working with hardhat
+
+ - name: Run linter
+ run: yarn lint
\ No newline at end of file
diff --git a/packages/chain-events/.gitignore b/packages/chain-events/.gitignore
new file mode 100644
index 00000000000..fb5822e01d3
--- /dev/null
+++ b/packages/chain-events/.gitignore
@@ -0,0 +1,16 @@
+.DS_Store
+/eth/build
+/eth/artifacts
+/eth/cache
+node_modules/
+.vscode
+yarn-error.log
+.nyc_output/
+coverage/
+
+.env
+*.tgz
+
+.yalc/
+yalc.lock
+/dist/
diff --git a/packages/chain-events/.mocharc.json b/packages/chain-events/.mocharc.json
new file mode 100644
index 00000000000..24e465ef92a
--- /dev/null
+++ b/packages/chain-events/.mocharc.json
@@ -0,0 +1,7 @@
+{
+ "extension": [".spec.ts"],
+ "package": "./package.json",
+ "timeout": 60000,
+ "require": [ "jsdom-global/register", "tsconfig-paths/register", "@babel/register", "source-map-support/register" ],
+ "exit": true
+}
\ No newline at end of file
diff --git a/packages/chain-events/.nvmrc b/packages/chain-events/.nvmrc
new file mode 100644
index 00000000000..ca3f1e5c83e
--- /dev/null
+++ b/packages/chain-events/.nvmrc
@@ -0,0 +1 @@
+v14
\ No newline at end of file
diff --git a/packages/chain-events/.nycrc b/packages/chain-events/.nycrc
new file mode 100644
index 00000000000..3807191c790
--- /dev/null
+++ b/packages/chain-events/.nycrc
@@ -0,0 +1,6 @@
+{
+ "extends": "@istanbuljs/nyc-config-typescript",
+ "all": false,
+ "reporter": "html",
+ "include": [ "src/" ]
+}
\ No newline at end of file
diff --git a/packages/chain-events/.prettierrc.json b/packages/chain-events/.prettierrc.json
new file mode 100644
index 00000000000..2bcad54ddde
--- /dev/null
+++ b/packages/chain-events/.prettierrc.json
@@ -0,0 +1,4 @@
+{
+ "arrowParens": "always",
+ "singleQuote": true
+}
diff --git a/packages/chain-events/.stylelintrc b/packages/chain-events/.stylelintrc
new file mode 100644
index 00000000000..cd3d7bbd71e
--- /dev/null
+++ b/packages/chain-events/.stylelintrc
@@ -0,0 +1,14 @@
+{
+ "ignoreFiles": "client/styles/lib/*",
+ "rules": {
+ "block-no-empty": null,
+ "color-no-invalid-hex": true,
+ "comment-empty-line-before": [ "always", {
+ "ignore": ["stylelint-commands", "after-comment"]
+ } ],
+ "declaration-colon-space-after": "always",
+ "indentation": 4,
+ "max-empty-lines": 2,
+ "unit-whitelist": ["px", "em", "rem", "%", "s", "vh", "deg", "ms"]
+ }
+}
\ No newline at end of file
diff --git a/packages/chain-events/LICENSE.txt b/packages/chain-events/LICENSE.txt
new file mode 100644
index 00000000000..96bd6edad70
--- /dev/null
+++ b/packages/chain-events/LICENSE.txt
@@ -0,0 +1,674 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc.
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users. We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors. You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights. Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received. You must make sure that they, too, receive
+or can get the source code. And you must show them these terms so they
+know their rights.
+
+ Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+ For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software. For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+ Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so. This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software. The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable. Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products. If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+ Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary. To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Use with the GNU Affero General Public License.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+
+ Copyright (C)
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+ Copyright (C)
+ This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+.
+
+ The GNU General Public License does not permit incorporating your program
+into proprietary programs. If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License. But first, please read
+.
\ No newline at end of file
diff --git a/packages/chain-events/README.md b/packages/chain-events/README.md
new file mode 100644
index 00000000000..e2ce1ddc646
--- /dev/null
+++ b/packages/chain-events/README.md
@@ -0,0 +1,304 @@
+# @commonwealth/chain-events
+
+"@commonwealth/chain-events" is a library for subscribing and processing synthetic blockchain events.
+
+## Installation
+
+Available on [npm](https://www.npmjs.com/package/@commonwealth/chain-events) and designed to work both in browser and with nodejs.
+
+```bash
+yarn add @commonwealth/chain-events
+```
+
+For developing on this project itself, first you must build the project to replicate the npm package structure (using the typescript compiler), and then you can install your local version via `yarn link`:
+
+```bash
+~/chain-events$ yarn build
+~/chain-events$ yarn link
+~/chain-events$ cd ~/project-name
+~/project-name$ yarn link @commonwealth/chain-events
+```
+
+Be sure to call `yarn unlink` once development has been completed and the new changes have been published.
+
+Please submit any enhancements or bug fixes as a Pull Request on the [project's github page](https://github.com/hicommonwealth/chain-events).
+
+## Development
+
+```
+npm install -g npm-install-peers
+```
+
+For using a local version of Chain Events in other projects, we recommend you use `yalc`, which functions as a local package repository for your `npm` libraries in development.
+
+To install `yalc`, run:
+
+```bash
+$ yarn global add yalc
+```
+
+Then, publish Chain Events to the `yalc` respository (which will first build the project):
+
+```bash
+~/chain-events$ yalc publish
+```
+
+Navigate to the project you want to test Chain Events inside, and use `yalc` to add it. This will update its `package.json` to point the "@commonwealth/chain-events" dependency to a local file.
+
+```bash
+~/commonwealth$ yalc add @commonwealth/chain-events
+~/commonwealth$ yarn
+```
+
+Any time you update Chain Events after publishing and adding, simply run the following to build and propagate a new update:
+
+```bash
+~/chain-events$ yalc publish --push
+```
+
+
+## Publishing
+
+First ensure you bump the package version in the [package.json](./package.json) file. Then build, and publish to the npm repository. A `--dry-run` is useful beforehand to ensure the version and file lists are correct.
+
+```bash
+~/chain-events$ yarn build
+~/chain-events$ npm publish [--tag ] --dry-run
+~/chain-events$ npm publish [--tag ]
+```
+
+## Publishing Types
+First navigate to [types](./types) then bump the package version and publish.
+
+```bash
+~/chain-events/types$ npm publish [--tag ] --dry-run
+~/chain-events/types$ npm publish [--tag ]
+```
+
+
+## Standalone Usage
+
+This package includes an "event listener" script located at [listener.ts](./scripts/listener.ts),
+which permits real-time listening for on-chain events, and can be used for testing a chain connection, pushing events to
+a queue, or/and running chain-events as a node.
+
+The following is an example usage, connecting to a local node running on edgeware mainnet:
+
+```bash
+~/chain-events$ yarn build
+~/chain-events$ yarn listen -n edgeware -u ws://localhost:9944
+```
+
+The full set of options is listed as, with only `-n` required:
+
+```
+Options:
+ --help Show help [boolean]
+ --version Show version number [boolean]
+ -z, --config Path to a config file to setup multiple [string]
+ listeners (see below)
+ -n, --network chain to listen on
+ [required] [choices: "edgeware", "edgeware-local", "edgeware-testnet",
+ "kusama", "kusama-local", "polkadot", "polkadot-local", "kulupu", "moloch",
+ "moloch-local"]
+ -u, --url node url [string]
+ -a, --archival run listener in archival mode [boolean]
+ -b, --startBlock when running in archival mode, which block [number]
+ should we start from
+ -s, --skipCatchup Whether to attempt to retrieve historical [boolean]
+ events not collected due to down-time
+ -c, --contractAddress eth contract address [string]
+ -q, --rabbitmq Publish messages to queue hosted on RabbitMQ [boolean]
+ -e, --eventNode Run chain-events as a node that allows [boolean]
+ interacting with listeners over http
+ (only updating substrate specs for now)
+```
+
+If the -z option is passed then only -q and -e can be used (all other options conflict with the config defined by -z)
+
+#### Environment Variables
+- NODE_ENV: dictates where a listener will get its initial spec. when NODE_ENV = "production"
+the listener gets its spec from commonwealth.im. Otherwise, the listener will get its spec from the commonwealth server
+hosted locally.
+
+#### Listener config file
+Must be a json file with the following format:
+```json
+[
+ {
+ "network": "Required (string) - The name of the network",
+ "url": "Optional (string) - Node url to connect to",
+ "archival": "Optional (boolean) - run listener in archival mode",
+ "startBlock": "Optional (number) - when running in archival mode, which block should we start from",
+ "skipCatchup": "Optional (boolean) - Whether to attempt to retrieve historical events not collected due to down-time",
+ "excludedEvents": "Optional (array of strings) - An array of EventKinds to ignore. Currently only relevant for the RabbitMQ producer."
+ }
+]
+```
+See manyListenerConfigEx.json for an example configuration
+
+
+## Library Usage
+The easiest usage of the package involves using the Listener class which initializes the various components. Do this
+for Substrate chains as follows:
+```typescript
+import { Listener as SubstrateListener } from "";
+
+// TODO: listener argument docs
+// create a listener instance
+const listener = new SubstrateListener();
+
+// initialize the listener
+await listener.init();
+
+// subscribe/listen to events on the specified chain
+await listener.subscribe();
+````
+
+The Listener classes have a variety functions that facilitate using the listener.
+
+##### Updating the substrate spec
+```typescript
+await listener.updateSpec({yourNewSpec})
+```
+
+##### Updating the url the listener should use
+```typescript
+await listener.updateUrl('yourNewUrl')
+```
+
+##### Changing the event handlers
+The event handlers are accessible through the `eventHandlers` property.
+The eventHandlers property is defined as follows:
+
+```
+eventHandlers: {
+ [handlerName: string]: {
+ "handler": IEventHandler,
+ "excludedEvents": SubstrateEvents[]
+ }
+}
+```
+Thus, to change an event handler, or the events that it ignores simply access it directly:
+```typescript
+// change the handler of "myEventHandler"
+listener.eventHandlers["myEventHandler"].handler = newHandler;
+```
+
+##### Changing the excluded events
+As described above you can change the events that a handler ignores either directly in the execution of the handler
+or by setting "excludedEvents" like so:
+```typescript
+// change the events "myEventHandler" excludes
+listener.eventHandlers["myEventHandler"].excludedEvents = ["someEventKind", "anotherEventKind"]
+```
+You can also exclude events from all handlers at one by changing the globalExcludedEvents property like so:
+```typescript
+listener.globalExcludedEvents = ["someEventKind", "anotherEventKind"]
+```
+
+### Provided Handlers
+##### RabbitMQ Producer
+##### HTTP Post Handler
+##### Single Event Handler
+
+### Custom Handlers
+A custom handler is necessary in many cases depending on what you are trying to build. Thankfully creating your own
+is very easy!
+
+Just extend the `IEventHandler` and implement the `handle` method:
+```typescript
+import {CWEvent, IEventHandler} from "chain-event-types"
+
+class ExampleEventHandler implements IEventHandler {
+ public async handle(event: CWEvent): Promise {
+ // your code goes here
+ }
+}
+```
+
+In order to use chain-event-types in your project you will need to install chain-event-types from
+'git+https://github.com/timolegros/chain-events.git#build.types' and have the following dev dependencies:
+- '@polkadot/types'
+- '@polkadot/api'
+
+
+The easiest usage of the package involves calling `subscribeEvents` directly, which initializes the various components automatically. Do this for Substrate as follows.
+
+```typescript
+import { spec } from '@edgeware/node-types';
+import { SubstrateEvents, CWEvent, IEventHandler } from '@commonwealth/chain-events';
+
+// This is an example event handler that processes events as they are emitted.
+// Add logic in the `handle()` method to take various actions based on the events.
+class ExampleEventHandler extends IEventHandler {
+ public async handle(event: CWEvent): Promise {
+ console.log(`Received event: ${JSON.stringify(event, null, 2)}`);
+ }
+}
+
+async function subscribe(url) {
+ // Populate with chain spec type overrides
+ const api = await SubstrateEvents.createApi(url, spec);
+
+ const handlers = [ new ExampleEventHandler() ];
+ const subscriber = await SubstrateEvents.subscribeEvents({
+ api,
+ chain: 'edgeware',
+ handlers,
+
+ // print more output
+ verbose: true,
+
+ // if set to false, will attempt to poll past events at setup time
+ skipCatchup: true,
+
+ // if not skipping catchup, this function should "discover" the most
+ // recently seen block, in order to limit how far back we attempt to "catch-up"
+ discoverReconnectRange: undefined,
+ });
+ return subscriber;
+}
+```
+
+Alternatively, the individual `Subscriber`, `Poller`, `StorageFetcher`, and `Processor` objects can be accessed directly on the `SubstrateEvents` object, and
+can be set up directly. For an example of this, see the initialization procedure in [subscribeFunc.ts](src/chains/substrate/subscribeFunc.ts).
+
+### Class Details
+
+The top level `@commonwealth/chain-events` import exposes various abstract types from the [interfaces.ts](./src/interfaces.ts) file, as well as "per-chain" modules, e.g. for Substrate, `SubstrateTypes` and `SubstrateEvents`, with the former containing interfaces and the latter containing classes and functions.
+
+The two main concepts used in the project are "ChainEvents" and "ChainEntities".
+* A "ChainEvent" represents a single event or extrinsic call performed on the chain, although it may be augmented with additional chain data at production time. ChainEvents are the main outputs generated by this project.
+* A "ChainEntity" represents a stateful object on chain, subject to one or more "ChainEvents" which manipulate its state. The most common usage of ChainEntity is to represent on-chain proposals, which may have a pre-voting phase, a voting phase, and a period post-voting before the proposal is marked completed, each phase transition represented by events that relate to the same object. **This project defines types and simple utilities for ChainEntities but does not provide any specific tools for managing them.**
+
+Each chain implements several abstract classes, described in [interfaces.ts](./src/interfaces.ts). The list for Substrate is as follows:
+
+* `Subscriber` exposes a `subscribe()` method, which listens to the chain via the API and constructs a synthetic `Block` type when events occur, containing necessary data for later processing.
+* `Poller` exposes a `poll()` method, which attempts to fetch a range of past blocks and returns an Array of synthetic `Block`s. This is used for "catching up" on past events.
+* `StorageFetcher` exposes a `fetch()` method, which queries chain storage and constructs "fake" `Block`s, that represent what the original events may have looked like. This is used to quickly catch up on stateful Chain Entities from chains that prune past blocks (as most do).
+* `Processor` exposes a `process()` method, which takes a synthetic `Block` and attempts to convert it into a `CWEvent` (aka a ChainEvent), by running it through various "filters", found in the [filters](src/chains/substrate/filters) directory. The primary filter types used are as follows:
+ * `ParseType` uses data from the chain to detect the ChainEvent kind of a `Block`. It is used to quickly filter out blocks that do not represent any kind of ChainEvent.
+ * `Enrich` uses the API to query additional data about a ChainEvent that did not appear in the original `Block`, and constructs the final `CWEvent` object. This is used because many "events" on chains provide only minimal info, which we may want to augment for application purposes.
+ * Two other filters exist, which are not used by the `Processor`, but may be useful in an application:
+ * `Title` takes a kind of ChainEvent and produces an object with a title and description, useful for enumerating a human-readable list of possible ChainEvents.
+ * `Label` takes a specific ChainEvent and produces an object with a heading, a label, and a linkUrl, useful for creating human-readable UIs around particular events. The `linkUrl` property in particular is currently specific to [Commonwealth](https://commonwealth.im/), but may in the future be generalized.
+
+Note that every item on this list may not be implemented for every chain (e.g. Moloch does not have a `Poller`), but the combination of these components provides the pieces to create a more usable application-usable event stream than what is exposed on the chain.
+
+### Usage as Commonwealth Chain-Events DB Node
+Running chain-events as a CW DB node lets us run a cluster of chain-events node each with multiple listeners without
+needing for each of them to be aware of each other or implementing load-balancing. This is achieved by having the chain
+events DB nodes poll the database for the information that is specific to them.
+
+####Environment Variables
+- `NUM_WORKERS`: The total number of chain-events DB nodes in the cluster. This is used to ensure even separation of
+ listeners among the different chain-events DB nodes.
+- `WORKER_NUMBER`: The unique number id that this chain-events DB node should have. Must be between 0 and NUM_WORKERS-1
+- `HANDLE_IDENTITY`: ("handle" || "publish" || null)
+ - handle: The node will directly update the database with identity data
+ - publish: The node will publish identity events to an identity queue
+ - null: The node will not query the identity cache
+
+- `NODE_ENV`: ("production" || "development") - optional
+- `DATABASE_URL`: The url of the database to connect to. If `NODE_ENV` = production this url is the default.
diff --git a/packages/chain-events/eth/contracts/AAVE/dependencies/open-zeppelin/Address.sol b/packages/chain-events/eth/contracts/AAVE/dependencies/open-zeppelin/Address.sol
new file mode 100644
index 00000000000..4787d9a6706
--- /dev/null
+++ b/packages/chain-events/eth/contracts/AAVE/dependencies/open-zeppelin/Address.sol
@@ -0,0 +1,60 @@
+pragma solidity 0.7.5;
+
+/**
+ * @dev Collection of functions related to the address type
+ */
+library Address {
+ /**
+ * @dev Returns true if `account` is a contract.
+ *
+ * [IMPORTANT]
+ * ====
+ * It is unsafe to assume that an address for which this function returns
+ * false is an externally-owned account (EOA) and not a contract.
+ *
+ * Among others, `isContract` will return false for the following
+ * types of addresses:
+ *
+ * - an externally-owned account
+ * - a contract in construction
+ * - an address where a contract will be created
+ * - an address where a contract lived, but was destroyed
+ * ====
+ */
+ function isContract(address account) internal view returns (bool) {
+ // According to EIP-1052, 0x0 is the value returned for not-yet created accounts
+ // and 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470 is returned
+ // for accounts without code, i.e. `keccak256('')`
+ bytes32 codehash;
+ bytes32 accountHash = 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470;
+ // solhint-disable-next-line no-inline-assembly
+ assembly {
+ codehash := extcodehash(account)
+ }
+ return (codehash != accountHash && codehash != 0x0);
+ }
+
+ /**
+ * @dev Replacement for Solidity's `transfer`: sends `amount` wei to
+ * `recipient`, forwarding all available gas and reverting on errors.
+ *
+ * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
+ * of certain opcodes, possibly making contracts go over the 2300 gas limit
+ * imposed by `transfer`, making them unable to receive funds via
+ * `transfer`. {sendValue} removes this limitation.
+ *
+ * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].
+ *
+ * IMPORTANT: because control is transferred to `recipient`, care must be
+ * taken to not create reentrancy vulnerabilities. Consider using
+ * {ReentrancyGuard} or the
+ * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
+ */
+ function sendValue(address payable recipient, uint256 amount) internal {
+ require(address(this).balance >= amount, 'Address: insufficient balance');
+
+ // solhint-disable-next-line avoid-low-level-calls, avoid-call-value
+ (bool success, ) = recipient.call{value: amount}('');
+ require(success, 'Address: unable to send value, recipient may have reverted');
+ }
+}
diff --git a/packages/chain-events/eth/contracts/AAVE/dependencies/open-zeppelin/BaseAdminUpgradeabilityProxy.sol b/packages/chain-events/eth/contracts/AAVE/dependencies/open-zeppelin/BaseAdminUpgradeabilityProxy.sol
new file mode 100644
index 00000000000..74c9f0ed5ba
--- /dev/null
+++ b/packages/chain-events/eth/contracts/AAVE/dependencies/open-zeppelin/BaseAdminUpgradeabilityProxy.sol
@@ -0,0 +1,125 @@
+pragma solidity 0.7.5;
+
+import './UpgradeabilityProxy.sol';
+
+/**
+ * @title BaseAdminUpgradeabilityProxy
+ * @dev This contract combines an upgradeability proxy with an authorization
+ * mechanism for administrative tasks.
+ * All external functions in this contract must be guarded by the
+ * `ifAdmin` modifier. See ethereum/solidity#3864 for a Solidity
+ * feature proposal that would enable this to be done automatically.
+ */
+contract BaseAdminUpgradeabilityProxy is BaseUpgradeabilityProxy {
+ /**
+ * @dev Emitted when the administration has been transferred.
+ * @param previousAdmin Address of the previous admin.
+ * @param newAdmin Address of the new admin.
+ */
+ event AdminChanged(address previousAdmin, address newAdmin);
+
+ /**
+ * @dev Storage slot with the admin of the contract.
+ * This is the keccak-256 hash of "eip1967.proxy.admin" subtracted by 1, and is
+ * validated in the constructor.
+ */
+
+ bytes32
+ internal constant ADMIN_SLOT = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103;
+
+ /**
+ * @dev Modifier to check whether the `msg.sender` is the admin.
+ * If it is, it will run the function. Otherwise, it will delegate the call
+ * to the implementation.
+ */
+ modifier ifAdmin() {
+ if (msg.sender == _admin()) {
+ _;
+ } else {
+ _fallback();
+ }
+ }
+
+ /**
+ * @return The address of the proxy admin.
+ */
+ function admin() external ifAdmin returns (address) {
+ return _admin();
+ }
+
+ /**
+ * @return The address of the implementation.
+ */
+ function implementation() external ifAdmin returns (address) {
+ return _implementation();
+ }
+
+ /**
+ * @dev Changes the admin of the proxy.
+ * Only the current admin can call this function.
+ * @param newAdmin Address to transfer proxy administration to.
+ */
+ function changeAdmin(address newAdmin) external ifAdmin {
+ require(newAdmin != address(0), 'Cannot change the admin of a proxy to the zero address');
+ emit AdminChanged(_admin(), newAdmin);
+ _setAdmin(newAdmin);
+ }
+
+ /**
+ * @dev Upgrade the backing implementation of the proxy.
+ * Only the admin can call this function.
+ * @param newImplementation Address of the new implementation.
+ */
+ function upgradeTo(address newImplementation) external ifAdmin {
+ _upgradeTo(newImplementation);
+ }
+
+ /**
+ * @dev Upgrade the backing implementation of the proxy and call a function
+ * on the new implementation.
+ * This is useful to initialize the proxied contract.
+ * @param newImplementation Address of the new implementation.
+ * @param data Data to send as msg.data in the low level call.
+ * It should include the signature and the parameters of the function to be called, as described in
+ * https://solidity.readthedocs.io/en/v0.4.24/abi-spec.html#function-selector-and-argument-encoding.
+ */
+ function upgradeToAndCall(address newImplementation, bytes calldata data)
+ external
+ payable
+ ifAdmin
+ {
+ _upgradeTo(newImplementation);
+ (bool success, ) = newImplementation.delegatecall(data);
+ require(success);
+ }
+
+ /**
+ * @return adm The admin slot.
+ */
+ function _admin() internal view returns (address adm) {
+ bytes32 slot = ADMIN_SLOT;
+ assembly {
+ adm := sload(slot)
+ }
+ }
+
+ /**
+ * @dev Sets the address of the proxy admin.
+ * @param newAdmin Address of the new proxy admin.
+ */
+ function _setAdmin(address newAdmin) internal {
+ bytes32 slot = ADMIN_SLOT;
+
+ assembly {
+ sstore(slot, newAdmin)
+ }
+ }
+
+ /**
+ * @dev Only fall back when the sender is not the admin.
+ */
+ function _willFallback() internal virtual override {
+ require(msg.sender != _admin(), 'Cannot call fallback function from the proxy admin');
+ super._willFallback();
+ }
+}
diff --git a/packages/chain-events/eth/contracts/AAVE/dependencies/open-zeppelin/BaseUpgradeabilityProxy.sol b/packages/chain-events/eth/contracts/AAVE/dependencies/open-zeppelin/BaseUpgradeabilityProxy.sol
new file mode 100644
index 00000000000..1ef3e757fd3
--- /dev/null
+++ b/packages/chain-events/eth/contracts/AAVE/dependencies/open-zeppelin/BaseUpgradeabilityProxy.sol
@@ -0,0 +1,63 @@
+pragma solidity 0.7.5;
+
+import './Proxy.sol';
+import './Address.sol';
+
+/**
+ * @title BaseUpgradeabilityProxy
+ * @dev This contract implements a proxy that allows to change the
+ * implementation address to which it will delegate.
+ * Such a change is called an implementation upgrade.
+ */
+contract BaseUpgradeabilityProxy is Proxy {
+ /**
+ * @dev Emitted when the implementation is upgraded.
+ * @param implementation Address of the new implementation.
+ */
+ event Upgraded(address indexed implementation);
+
+ /**
+ * @dev Storage slot with the address of the current implementation.
+ * This is the keccak-256 hash of "eip1967.proxy.implementation" subtracted by 1, and is
+ * validated in the constructor.
+ */
+ bytes32
+ internal constant IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
+
+ /**
+ * @dev Returns the current implementation.
+ * @return impl Address of the current implementation
+ */
+ function _implementation() internal override view returns (address impl) {
+ bytes32 slot = IMPLEMENTATION_SLOT;
+ assembly {
+ impl := sload(slot)
+ }
+ }
+
+ /**
+ * @dev Upgrades the proxy to a new implementation.
+ * @param newImplementation Address of the new implementation.
+ */
+ function _upgradeTo(address newImplementation) internal {
+ _setImplementation(newImplementation);
+ emit Upgraded(newImplementation);
+ }
+
+ /**
+ * @dev Sets the implementation address of the proxy.
+ * @param newImplementation Address of the new implementation.
+ */
+ function _setImplementation(address newImplementation) internal {
+ require(
+ Address.isContract(newImplementation),
+ 'Cannot set a proxy implementation to a non-contract address'
+ );
+
+ bytes32 slot = IMPLEMENTATION_SLOT;
+
+ assembly {
+ sstore(slot, newImplementation)
+ }
+ }
+}
diff --git a/packages/chain-events/eth/contracts/AAVE/dependencies/open-zeppelin/Context.sol b/packages/chain-events/eth/contracts/AAVE/dependencies/open-zeppelin/Context.sol
new file mode 100644
index 00000000000..19c265f4283
--- /dev/null
+++ b/packages/chain-events/eth/contracts/AAVE/dependencies/open-zeppelin/Context.sol
@@ -0,0 +1,23 @@
+// SPDX-License-Identifier: MIT
+pragma solidity 0.7.5;
+
+/*
+ * @dev Provides information about the current execution context, including the
+ * sender of the transaction and its data. While these are generally available
+ * via msg.sender and msg.data, they should not be accessed in such a direct
+ * manner, since when dealing with GSN meta-transactions the account sending and
+ * paying for execution may not be the actual sender (as far as an application
+ * is concerned).
+ *
+ * This contract is only required for intermediate, library-like contracts.
+ */
+abstract contract Context {
+ function _msgSender() internal view virtual returns (address payable) {
+ return msg.sender;
+ }
+
+ function _msgData() internal view virtual returns (bytes memory) {
+ this; // silence state mutability warning without generating bytecode - see https://github.com/ethereum/solidity/issues/2691
+ return msg.data;
+ }
+}
diff --git a/packages/chain-events/eth/contracts/AAVE/dependencies/open-zeppelin/InitializableAdminUpgradeabilityProxy.sol b/packages/chain-events/eth/contracts/AAVE/dependencies/open-zeppelin/InitializableAdminUpgradeabilityProxy.sol
new file mode 100644
index 00000000000..4a5fb1a2685
--- /dev/null
+++ b/packages/chain-events/eth/contracts/AAVE/dependencies/open-zeppelin/InitializableAdminUpgradeabilityProxy.sol
@@ -0,0 +1,42 @@
+// SPDX-License-Identifier: agpl-3.0
+pragma solidity 0.7.5;
+
+import './BaseAdminUpgradeabilityProxy.sol';
+import './InitializableUpgradeabilityProxy.sol';
+
+/**
+ * @title InitializableAdminUpgradeabilityProxy
+ * @dev Extends from BaseAdminUpgradeabilityProxy with an initializer for
+ * initializing the implementation, admin, and init data.
+ */
+contract InitializableAdminUpgradeabilityProxy is
+ BaseAdminUpgradeabilityProxy,
+ InitializableUpgradeabilityProxy
+{
+ /**
+ * Contract initializer.
+ * @param _logic address of the initial implementation.
+ * @param _admin Address of the proxy administrator.
+ * @param _data Data to send as msg.data to the implementation to initialize the proxied contract.
+ * It should include the signature and the parameters of the function to be called, as described in
+ * https://solidity.readthedocs.io/en/v0.4.24/abi-spec.html#function-selector-and-argument-encoding.
+ * This parameter is optional, if no data is given the initialization call to proxied contract will be skipped.
+ */
+ function initialize(
+ address _logic,
+ address _admin,
+ bytes memory _data
+ ) public payable {
+ require(_implementation() == address(0));
+ InitializableUpgradeabilityProxy.initialize(_logic, _data);
+ assert(ADMIN_SLOT == bytes32(uint256(keccak256('eip1967.proxy.admin')) - 1));
+ _setAdmin(_admin);
+ }
+
+ /**
+ * @dev Only fall back when the sender is not the admin.
+ */
+ function _willFallback() internal override(BaseAdminUpgradeabilityProxy, Proxy) {
+ BaseAdminUpgradeabilityProxy._willFallback();
+ }
+}
diff --git a/packages/chain-events/eth/contracts/AAVE/dependencies/open-zeppelin/InitializableUpgradeabilityProxy.sol b/packages/chain-events/eth/contracts/AAVE/dependencies/open-zeppelin/InitializableUpgradeabilityProxy.sol
new file mode 100644
index 00000000000..563f33f811b
--- /dev/null
+++ b/packages/chain-events/eth/contracts/AAVE/dependencies/open-zeppelin/InitializableUpgradeabilityProxy.sol
@@ -0,0 +1,29 @@
+// SPDX-License-Identifier: agpl-3.0
+pragma solidity 0.7.5;
+
+import './BaseUpgradeabilityProxy.sol';
+
+/**
+ * @title InitializableUpgradeabilityProxy
+ * @dev Extends BaseUpgradeabilityProxy with an initializer for initializing
+ * implementation and init data.
+ */
+contract InitializableUpgradeabilityProxy is BaseUpgradeabilityProxy {
+ /**
+ * @dev Contract initializer.
+ * @param _logic Address of the initial implementation.
+ * @param _data Data to send as msg.data to the implementation to initialize the proxied contract.
+ * It should include the signature and the parameters of the function to be called, as described in
+ * https://solidity.readthedocs.io/en/v0.4.24/abi-spec.html#function-selector-and-argument-encoding.
+ * This parameter is optional, if no data is given the initialization call to proxied contract will be skipped.
+ */
+ function initialize(address _logic, bytes memory _data) public payable {
+ require(_implementation() == address(0));
+ assert(IMPLEMENTATION_SLOT == bytes32(uint256(keccak256('eip1967.proxy.implementation')) - 1));
+ _setImplementation(_logic);
+ if (_data.length > 0) {
+ (bool success, ) = _logic.delegatecall(_data);
+ require(success);
+ }
+ }
+}
diff --git a/packages/chain-events/eth/contracts/AAVE/dependencies/open-zeppelin/Ownable.sol b/packages/chain-events/eth/contracts/AAVE/dependencies/open-zeppelin/Ownable.sol
new file mode 100644
index 00000000000..26e37f785fc
--- /dev/null
+++ b/packages/chain-events/eth/contracts/AAVE/dependencies/open-zeppelin/Ownable.sol
@@ -0,0 +1,68 @@
+// SPDX-License-Identifier: MIT
+pragma solidity 0.7.5;
+
+import './Context.sol';
+
+/**
+ * @dev Contract module which provides a basic access control mechanism, where
+ * there is an account (an owner) that can be granted exclusive access to
+ * specific functions.
+ *
+ * By default, the owner account will be the one that deploys the contract. This
+ * can later be changed with {transferOwnership}.
+ *
+ * This module is used through inheritance. It will make available the modifier
+ * `onlyOwner`, which can be applied to your functions to restrict their use to
+ * the owner.
+ */
+contract Ownable is Context {
+ address private _owner;
+
+ event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
+
+ /**
+ * @dev Initializes the contract setting the deployer as the initial owner.
+ */
+ constructor() {
+ address msgSender = _msgSender();
+ _owner = msgSender;
+ emit OwnershipTransferred(address(0), msgSender);
+ }
+
+ /**
+ * @dev Returns the address of the current owner.
+ */
+ function owner() public view returns (address) {
+ return _owner;
+ }
+
+ /**
+ * @dev Throws if called by any account other than the owner.
+ */
+ modifier onlyOwner() {
+ require(_owner == _msgSender(), 'Ownable: caller is not the owner');
+ _;
+ }
+
+ /**
+ * @dev Leaves the contract without owner. It will not be possible to call
+ * `onlyOwner` functions anymore. Can only be called by the current owner.
+ *
+ * NOTE: Renouncing ownership will leave the contract without an owner,
+ * thereby removing any functionality that is only available to the owner.
+ */
+ function renounceOwnership() public virtual onlyOwner {
+ emit OwnershipTransferred(_owner, address(0));
+ _owner = address(0);
+ }
+
+ /**
+ * @dev Transfers ownership of the contract to a new account (`newOwner`).
+ * Can only be called by the current owner.
+ */
+ function transferOwnership(address newOwner) public virtual onlyOwner {
+ require(newOwner != address(0), 'Ownable: new owner is the zero address');
+ emit OwnershipTransferred(_owner, newOwner);
+ _owner = newOwner;
+ }
+}
diff --git a/packages/chain-events/eth/contracts/AAVE/dependencies/open-zeppelin/Proxy.sol b/packages/chain-events/eth/contracts/AAVE/dependencies/open-zeppelin/Proxy.sol
new file mode 100644
index 00000000000..83b0bf6958c
--- /dev/null
+++ b/packages/chain-events/eth/contracts/AAVE/dependencies/open-zeppelin/Proxy.sol
@@ -0,0 +1,70 @@
+pragma solidity 0.7.5;
+
+/**
+ * @title Proxy
+ * @dev Implements delegation of calls to other contracts, with proper
+ * forwarding of return values and bubbling of failures.
+ * It defines a fallback function that delegates all calls to the address
+ * returned by the abstract _implementation() internal function.
+ */
+abstract contract Proxy {
+ /**
+ * @dev Fallback function.
+ * Implemented entirely in `_fallback`.
+ */
+ fallback() external payable {
+ _fallback();
+ }
+
+ /**
+ * @return The Address of the implementation.
+ */
+ function _implementation() internal virtual view returns (address);
+
+ /**
+ * @dev Delegates execution to an implementation contract.
+ * This is a low level function that doesn't return to its internal call site.
+ * It will return to the external caller whatever the implementation returns.
+ * @param implementation Address to delegate.
+ */
+ function _delegate(address implementation) internal {
+ assembly {
+ // Copy msg.data. We take full control of memory in this inline assembly
+ // block because it will not return to Solidity code. We overwrite the
+ // Solidity scratch pad at memory position 0.
+ calldatacopy(0, 0, calldatasize())
+
+ // Call the implementation.
+ // out and outsize are 0 because we don't know the size yet.
+ let result := delegatecall(gas(), implementation, 0, calldatasize(), 0, 0)
+
+ // Copy the returned data.
+ returndatacopy(0, 0, returndatasize())
+
+ switch result
+ // delegatecall returns 0 on error.
+ case 0 {
+ revert(0, returndatasize())
+ }
+ default {
+ return(0, returndatasize())
+ }
+ }
+ }
+
+ /**
+ * @dev Function that is run as the first thing in the fallback function.
+ * Can be redefined in derived contracts to add functionality.
+ * Redefinitions must call super._willFallback().
+ */
+ function _willFallback() internal virtual {}
+
+ /**
+ * @dev fallback implementation.
+ * Extracted to enable manual triggering.
+ */
+ function _fallback() internal {
+ _willFallback();
+ _delegate(_implementation());
+ }
+}
diff --git a/packages/chain-events/eth/contracts/AAVE/dependencies/open-zeppelin/SafeMath.sol b/packages/chain-events/eth/contracts/AAVE/dependencies/open-zeppelin/SafeMath.sol
new file mode 100644
index 00000000000..f044c25120e
--- /dev/null
+++ b/packages/chain-events/eth/contracts/AAVE/dependencies/open-zeppelin/SafeMath.sol
@@ -0,0 +1,163 @@
+// SPDX-License-Identifier: MIT
+pragma solidity 0.7.5;
+
+/**
+ * @dev Wrappers over Solidity's arithmetic operations with added overflow
+ * checks.
+ *
+ * Arithmetic operations in Solidity wrap on overflow. This can easily result
+ * in bugs, because programmers usually assume that an overflow raises an
+ * error, which is the standard behavior in high level programming languages.
+ * `SafeMath` restores this intuition by reverting the transaction when an
+ * operation overflows.
+ *
+ * Using this library instead of the unchecked operations eliminates an entire
+ * class of bugs, so it's recommended to use it always.
+ */
+library SafeMath {
+ /**
+ * @dev Returns the addition of two unsigned integers, reverting on
+ * overflow.
+ *
+ * Counterpart to Solidity's `+` operator.
+ *
+ * Requirements:
+ * - Addition cannot overflow.
+ */
+ function add(uint256 a, uint256 b) internal pure returns (uint256) {
+ uint256 c = a + b;
+ require(c >= a, 'SafeMath: addition overflow');
+
+ return c;
+ }
+
+ /**
+ * @dev Returns the subtraction of two unsigned integers, reverting on
+ * overflow (when the result is negative).
+ *
+ * Counterpart to Solidity's `-` operator.
+ *
+ * Requirements:
+ * - Subtraction cannot overflow.
+ */
+ function sub(uint256 a, uint256 b) internal pure returns (uint256) {
+ return sub(a, b, 'SafeMath: subtraction overflow');
+ }
+
+ /**
+ * @dev Returns the subtraction of two unsigned integers, reverting with custom message on
+ * overflow (when the result is negative).
+ *
+ * Counterpart to Solidity's `-` operator.
+ *
+ * Requirements:
+ * - Subtraction cannot overflow.
+ */
+ function sub(
+ uint256 a,
+ uint256 b,
+ string memory errorMessage
+ ) internal pure returns (uint256) {
+ require(b <= a, errorMessage);
+ uint256 c = a - b;
+
+ return c;
+ }
+
+ /**
+ * @dev Returns the multiplication of two unsigned integers, reverting on
+ * overflow.
+ *
+ * Counterpart to Solidity's `*` operator.
+ *
+ * Requirements:
+ * - Multiplication cannot overflow.
+ */
+ function mul(uint256 a, uint256 b) internal pure returns (uint256) {
+ // Gas optimization: this is cheaper than requiring 'a' not being zero, but the
+ // benefit is lost if 'b' is also tested.
+ // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
+ if (a == 0) {
+ return 0;
+ }
+
+ uint256 c = a * b;
+ require(c / a == b, 'SafeMath: multiplication overflow');
+
+ return c;
+ }
+
+ /**
+ * @dev Returns the integer division of two unsigned integers. Reverts on
+ * division by zero. The result is rounded towards zero.
+ *
+ * Counterpart to Solidity's `/` operator. Note: this function uses a
+ * `revert` opcode (which leaves remaining gas untouched) while Solidity
+ * uses an invalid opcode to revert (consuming all remaining gas).
+ *
+ * Requirements:
+ * - The divisor cannot be zero.
+ */
+ function div(uint256 a, uint256 b) internal pure returns (uint256) {
+ return div(a, b, 'SafeMath: division by zero');
+ }
+
+ /**
+ * @dev Returns the integer division of two unsigned integers. Reverts with custom message on
+ * division by zero. The result is rounded towards zero.
+ *
+ * Counterpart to Solidity's `/` operator. Note: this function uses a
+ * `revert` opcode (which leaves remaining gas untouched) while Solidity
+ * uses an invalid opcode to revert (consuming all remaining gas).
+ *
+ * Requirements:
+ * - The divisor cannot be zero.
+ */
+ function div(
+ uint256 a,
+ uint256 b,
+ string memory errorMessage
+ ) internal pure returns (uint256) {
+ // Solidity only automatically asserts when dividing by 0
+ require(b > 0, errorMessage);
+ uint256 c = a / b;
+ // assert(a == b * c + a % b); // There is no case in which this doesn't hold
+
+ return c;
+ }
+
+ /**
+ * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
+ * Reverts when dividing by zero.
+ *
+ * Counterpart to Solidity's `%` operator. This function uses a `revert`
+ * opcode (which leaves remaining gas untouched) while Solidity uses an
+ * invalid opcode to revert (consuming all remaining gas).
+ *
+ * Requirements:
+ * - The divisor cannot be zero.
+ */
+ function mod(uint256 a, uint256 b) internal pure returns (uint256) {
+ return mod(a, b, 'SafeMath: modulo by zero');
+ }
+
+ /**
+ * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
+ * Reverts with custom message when dividing by zero.
+ *
+ * Counterpart to Solidity's `%` operator. This function uses a `revert`
+ * opcode (which leaves remaining gas untouched) while Solidity uses an
+ * invalid opcode to revert (consuming all remaining gas).
+ *
+ * Requirements:
+ * - The divisor cannot be zero.
+ */
+ function mod(
+ uint256 a,
+ uint256 b,
+ string memory errorMessage
+ ) internal pure returns (uint256) {
+ require(b != 0, errorMessage);
+ return a % b;
+ }
+}
diff --git a/packages/chain-events/eth/contracts/AAVE/dependencies/open-zeppelin/UpgradeabilityProxy.sol b/packages/chain-events/eth/contracts/AAVE/dependencies/open-zeppelin/UpgradeabilityProxy.sol
new file mode 100644
index 00000000000..9ecc837e646
--- /dev/null
+++ b/packages/chain-events/eth/contracts/AAVE/dependencies/open-zeppelin/UpgradeabilityProxy.sol
@@ -0,0 +1,27 @@
+pragma solidity 0.7.5;
+
+import './BaseUpgradeabilityProxy.sol';
+
+/**
+ * @title UpgradeabilityProxy
+ * @dev Extends BaseUpgradeabilityProxy with a constructor for initializing
+ * implementation and init data.
+ */
+contract UpgradeabilityProxy is BaseUpgradeabilityProxy {
+ /**
+ * @dev Contract constructor.
+ * @param _logic Address of the initial implementation.
+ * @param _data Data to send as msg.data to the implementation to initialize the proxied contract.
+ * It should include the signature and the parameters of the function to be called, as described in
+ * https://solidity.readthedocs.io/en/v0.4.24/abi-spec.html#function-selector-and-argument-encoding.
+ * This parameter is optional, if no data is given the initialization call to proxied contract will be skipped.
+ */
+ constructor(address _logic, bytes memory _data) public payable {
+ assert(IMPLEMENTATION_SLOT == bytes32(uint256(keccak256('eip1967.proxy.implementation')) - 1));
+ _setImplementation(_logic);
+ if (_data.length > 0) {
+ (bool success, ) = _logic.delegatecall(_data);
+ require(success);
+ }
+ }
+}
diff --git a/packages/chain-events/eth/contracts/AAVE/governance/AaveGovernanceV2.sol b/packages/chain-events/eth/contracts/AAVE/governance/AaveGovernanceV2.sol
new file mode 100644
index 00000000000..60449d3ad51
--- /dev/null
+++ b/packages/chain-events/eth/contracts/AAVE/governance/AaveGovernanceV2.sol
@@ -0,0 +1,500 @@
+// SPDX-License-Identifier: agpl-3.0
+pragma solidity 0.7.5;
+pragma abicoder v2;
+
+import {IVotingStrategy} from '../interfaces/IVotingStrategy.sol';
+import {IExecutorWithTimelock} from '../interfaces/IExecutorWithTimelock.sol';
+import {IProposalValidator} from '../interfaces/IProposalValidator.sol';
+import {IGovernanceStrategy} from '../interfaces/IGovernanceStrategy.sol';
+import {IAaveGovernanceV2} from '../interfaces/IAaveGovernanceV2.sol';
+import {Ownable} from '../dependencies/open-zeppelin/Ownable.sol';
+import {SafeMath} from '../dependencies/open-zeppelin/SafeMath.sol';
+import {isContract, getChainId} from '../misc/Helpers.sol';
+
+/**
+ * @title Governance V2 contract
+ * @dev Main point of interaction with Aave protocol's governance
+ * - Create a Proposal
+ * - Cancel a Proposal
+ * - Queue a Proposal
+ * - Execute a Proposal
+ * - Submit Vote to a Proposal
+ * Proposal States : Pending => Active => Succeeded(/Failed) => Queued => Executed(/Expired)
+ * The transition to "Canceled" can appear in multiple states
+ * @author Aave
+ **/
+contract AaveGovernanceV2 is Ownable, IAaveGovernanceV2 {
+ using SafeMath for uint256;
+
+ address private _governanceStrategy;
+ uint256 private _votingDelay;
+
+ uint256 private _proposalsCount;
+ mapping(uint256 => Proposal) private _proposals;
+ mapping(address => bool) private _authorizedExecutors;
+
+ address private _guardian;
+
+ bytes32 public constant DOMAIN_TYPEHASH = keccak256(
+ 'EIP712Domain(string name,uint256 chainId,address verifyingContract)'
+ );
+ bytes32 public constant VOTE_EMITTED_TYPEHASH = keccak256('VoteEmitted(uint256 id,bool support)');
+ string public constant NAME = 'Aave Governance v2';
+
+ modifier onlyGuardian() {
+ require(msg.sender == _guardian, 'ONLY_BY_GUARDIAN');
+ _;
+ }
+
+ constructor(
+ address governanceStrategy,
+ uint256 votingDelay,
+ address guardian,
+ address[] memory executors
+ ) {
+ _setGovernanceStrategy(governanceStrategy);
+ _setVotingDelay(votingDelay);
+ _guardian = guardian;
+
+ authorizeExecutors(executors);
+ }
+
+ struct CreateVars {
+ uint256 startBlock;
+ uint256 endBlock;
+ uint256 previousProposalsCount;
+ }
+
+ /**
+ * @dev Creates a Proposal (needs to be validated by the Proposal Validator)
+ * @param executor The ExecutorWithTimelock contract that will execute the proposal
+ * @param targets list of contracts called by proposal's associated transactions
+ * @param values list of value in wei for each propoposal's associated transaction
+ * @param signatures list of function signatures (can be empty) to be used when created the callData
+ * @param calldatas list of calldatas: if associated signature empty, calldata ready, else calldata is arguments
+ * @param withDelegatecalls boolean, true = transaction delegatecalls the taget, else calls the target
+ * @param ipfsHash IPFS hash of the proposal
+ **/
+ function create(
+ IExecutorWithTimelock executor,
+ address[] memory targets,
+ uint256[] memory values,
+ string[] memory signatures,
+ bytes[] memory calldatas,
+ bool[] memory withDelegatecalls,
+ bytes32 ipfsHash
+ ) external override returns (uint256) {
+ require(targets.length != 0, 'INVALID_EMPTY_TARGETS');
+ require(
+ targets.length == values.length &&
+ targets.length == signatures.length &&
+ targets.length == calldatas.length &&
+ targets.length == withDelegatecalls.length,
+ 'INCONSISTENT_PARAMS_LENGTH'
+ );
+
+ require(isExecutorAuthorized(address(executor)), 'EXECUTOR_NOT_AUTHORIZED');
+
+ require(
+ IProposalValidator(address(executor)).validateCreatorOfProposal(
+ this,
+ msg.sender,
+ block.number - 1
+ ),
+ 'PROPOSITION_CREATION_INVALID'
+ );
+
+ CreateVars memory vars;
+
+ vars.startBlock = block.number.add(_votingDelay);
+ vars.endBlock = vars.startBlock.add(IProposalValidator(address(executor)).VOTING_DURATION());
+
+ vars.previousProposalsCount = _proposalsCount;
+
+ Proposal storage newProposal = _proposals[vars.previousProposalsCount];
+ newProposal.id = vars.previousProposalsCount;
+ newProposal.creator = msg.sender;
+ newProposal.executor = executor;
+ newProposal.targets = targets;
+ newProposal.values = values;
+ newProposal.signatures = signatures;
+ newProposal.calldatas = calldatas;
+ newProposal.withDelegatecalls = withDelegatecalls;
+ newProposal.startBlock = vars.startBlock;
+ newProposal.endBlock = vars.endBlock;
+ newProposal.strategy = _governanceStrategy;
+ newProposal.ipfsHash = ipfsHash;
+ _proposalsCount++;
+
+ emit ProposalCreated(
+ vars.previousProposalsCount,
+ msg.sender,
+ executor,
+ targets,
+ values,
+ signatures,
+ calldatas,
+ withDelegatecalls,
+ vars.startBlock,
+ vars.endBlock,
+ _governanceStrategy,
+ ipfsHash
+ );
+
+ return newProposal.id;
+ }
+
+ /**
+ * @dev Cancels a Proposal.
+ * - Callable by the _guardian with relaxed conditions, or by anybody if the conditions of
+ * cancellation on the executor are fulfilled
+ * @param proposalId id of the proposal
+ **/
+ function cancel(uint256 proposalId) external override {
+ ProposalState state = getProposalState(proposalId);
+ require(
+ state != ProposalState.Executed &&
+ state != ProposalState.Canceled &&
+ state != ProposalState.Expired,
+ 'ONLY_BEFORE_EXECUTED'
+ );
+
+ Proposal storage proposal = _proposals[proposalId];
+ require(
+ msg.sender == _guardian ||
+ IProposalValidator(address(proposal.executor)).validateProposalCancellation(
+ this,
+ proposal.creator,
+ block.number - 1
+ ),
+ 'PROPOSITION_CANCELLATION_INVALID'
+ );
+ proposal.canceled = true;
+ for (uint256 i = 0; i < proposal.targets.length; i++) {
+ proposal.executor.cancelTransaction(
+ proposal.targets[i],
+ proposal.values[i],
+ proposal.signatures[i],
+ proposal.calldatas[i],
+ proposal.executionTime,
+ proposal.withDelegatecalls[i]
+ );
+ }
+
+ emit ProposalCanceled(proposalId);
+ }
+
+ /**
+ * @dev Queue the proposal (If Proposal Succeeded)
+ * @param proposalId id of the proposal to queue
+ **/
+ function queue(uint256 proposalId) external override {
+ require(getProposalState(proposalId) == ProposalState.Succeeded, 'INVALID_STATE_FOR_QUEUE');
+ Proposal storage proposal = _proposals[proposalId];
+ uint256 executionTime = block.timestamp.add(proposal.executor.getDelay());
+ for (uint256 i = 0; i < proposal.targets.length; i++) {
+ _queueOrRevert(
+ proposal.executor,
+ proposal.targets[i],
+ proposal.values[i],
+ proposal.signatures[i],
+ proposal.calldatas[i],
+ executionTime,
+ proposal.withDelegatecalls[i]
+ );
+ }
+ proposal.executionTime = executionTime;
+
+ emit ProposalQueued(proposalId, executionTime, msg.sender);
+ }
+
+ /**
+ * @dev Execute the proposal (If Proposal Queued)
+ * @param proposalId id of the proposal to execute
+ **/
+ function execute(uint256 proposalId) external payable override {
+ require(getProposalState(proposalId) == ProposalState.Queued, 'ONLY_QUEUED_PROPOSALS');
+ Proposal storage proposal = _proposals[proposalId];
+ proposal.executed = true;
+ for (uint256 i = 0; i < proposal.targets.length; i++) {
+ proposal.executor.executeTransaction{value: proposal.values[i]}(
+ proposal.targets[i],
+ proposal.values[i],
+ proposal.signatures[i],
+ proposal.calldatas[i],
+ proposal.executionTime,
+ proposal.withDelegatecalls[i]
+ );
+ }
+ emit ProposalExecuted(proposalId, msg.sender);
+ }
+
+ /**
+ * @dev Function allowing msg.sender to vote for/against a proposal
+ * @param proposalId id of the proposal
+ * @param support boolean, true = vote for, false = vote against
+ **/
+ function submitVote(uint256 proposalId, bool support) external override {
+ return _submitVote(msg.sender, proposalId, support);
+ }
+
+ /**
+ * @dev Function to register the vote of user that has voted offchain via signature
+ * @param proposalId id of the proposal
+ * @param support boolean, true = vote for, false = vote against
+ * @param v v part of the voter signature
+ * @param r r part of the voter signature
+ * @param s s part of the voter signature
+ **/
+ function submitVoteBySignature(
+ uint256 proposalId,
+ bool support,
+ uint8 v,
+ bytes32 r,
+ bytes32 s
+ ) external override {
+ bytes32 digest = keccak256(
+ abi.encodePacked(
+ '\x19\x01',
+ keccak256(abi.encode(DOMAIN_TYPEHASH, keccak256(bytes(NAME)), getChainId(), address(this))),
+ keccak256(abi.encode(VOTE_EMITTED_TYPEHASH, proposalId, support))
+ )
+ );
+ address signer = ecrecover(digest, v, r, s);
+ require(signer != address(0), 'INVALID_SIGNATURE');
+ return _submitVote(signer, proposalId, support);
+ }
+
+ /**
+ * @dev Set new GovernanceStrategy
+ * Note: owner should be a timelocked executor, so needs to make a proposal
+ * @param governanceStrategy new Address of the GovernanceStrategy contract
+ **/
+ function setGovernanceStrategy(address governanceStrategy) external override onlyOwner {
+ _setGovernanceStrategy(governanceStrategy);
+ }
+
+ /**
+ * @dev Set new Voting Delay (delay before a newly created proposal can be voted on)
+ * Note: owner should be a timelocked executor, so needs to make a proposal
+ * @param votingDelay new voting delay in terms of blocks
+ **/
+ function setVotingDelay(uint256 votingDelay) external override onlyOwner {
+ _setVotingDelay(votingDelay);
+ }
+
+ /**
+ * @dev Add new addresses to the list of authorized executors
+ * @param executors list of new addresses to be authorized executors
+ **/
+ function authorizeExecutors(address[] memory executors) public override onlyOwner {
+ for (uint256 i = 0; i < executors.length; i++) {
+ _authorizeExecutor(executors[i]);
+ }
+ }
+
+ /**
+ * @dev Remove addresses to the list of authorized executors
+ * @param executors list of addresses to be removed as authorized executors
+ **/
+ function unauthorizeExecutors(address[] memory executors) public override onlyOwner {
+ for (uint256 i = 0; i < executors.length; i++) {
+ _unauthorizeExecutor(executors[i]);
+ }
+ }
+
+ /**
+ * @dev Let the guardian abdicate from its priviledged rights
+ **/
+ function __abdicate() external override onlyGuardian {
+ _guardian = address(0);
+ }
+
+ /**
+ * @dev Getter of the current GovernanceStrategy address
+ * @return The address of the current GovernanceStrategy contracts
+ **/
+ function getGovernanceStrategy() external view override returns (address) {
+ return _governanceStrategy;
+ }
+
+ /**
+ * @dev Getter of the current Voting Delay (delay before a created proposal can be voted on)
+ * Different from the voting duration
+ * @return The voting delay in number of blocks
+ **/
+ function getVotingDelay() external view override returns (uint256) {
+ return _votingDelay;
+ }
+
+ /**
+ * @dev Returns whether an address is an authorized executor
+ * @param executor address to evaluate as authorized executor
+ * @return true if authorized
+ **/
+ function isExecutorAuthorized(address executor) public view override returns (bool) {
+ return _authorizedExecutors[executor];
+ }
+
+ /**
+ * @dev Getter the address of the guardian, that can mainly cancel proposals
+ * @return The address of the guardian
+ **/
+ function getGuardian() external view override returns (address) {
+ return _guardian;
+ }
+
+ /**
+ * @dev Getter of the proposal count (the current number of proposals ever created)
+ * @return the proposal count
+ **/
+ function getProposalsCount() external view override returns (uint256) {
+ return _proposalsCount;
+ }
+
+ /**
+ * @dev Getter of a proposal by id
+ * @param proposalId id of the proposal to get
+ * @return the proposal as ProposalWithoutVotes memory object
+ **/
+ function getProposalById(uint256 proposalId)
+ external
+ view
+ override
+ returns (ProposalWithoutVotes memory)
+ {
+ Proposal storage proposal = _proposals[proposalId];
+ ProposalWithoutVotes memory proposalWithoutVotes = ProposalWithoutVotes({
+ id: proposal.id,
+ creator: proposal.creator,
+ executor: proposal.executor,
+ targets: proposal.targets,
+ values: proposal.values,
+ signatures: proposal.signatures,
+ calldatas: proposal.calldatas,
+ withDelegatecalls: proposal.withDelegatecalls,
+ startBlock: proposal.startBlock,
+ endBlock: proposal.endBlock,
+ executionTime: proposal.executionTime,
+ forVotes: proposal.forVotes,
+ againstVotes: proposal.againstVotes,
+ executed: proposal.executed,
+ canceled: proposal.canceled,
+ strategy: proposal.strategy,
+ ipfsHash: proposal.ipfsHash
+ });
+
+ return proposalWithoutVotes;
+ }
+
+ /**
+ * @dev Getter of the Vote of a voter about a proposal
+ * Note: Vote is a struct: ({bool support, uint248 votingPower})
+ * @param proposalId id of the proposal
+ * @param voter address of the voter
+ * @return The associated Vote memory object
+ **/
+ function getVoteOnProposal(uint256 proposalId, address voter)
+ external
+ view
+ override
+ returns (Vote memory)
+ {
+ return _proposals[proposalId].votes[voter];
+ }
+
+ /**
+ * @dev Get the current state of a proposal
+ * @param proposalId id of the proposal
+ * @return The current state if the proposal
+ **/
+ function getProposalState(uint256 proposalId) public view override returns (ProposalState) {
+ require(_proposalsCount >= proposalId, 'INVALID_PROPOSAL_ID');
+ Proposal storage proposal = _proposals[proposalId];
+ if (proposal.canceled) {
+ return ProposalState.Canceled;
+ } else if (block.number <= proposal.startBlock) {
+ return ProposalState.Pending;
+ } else if (block.number <= proposal.endBlock) {
+ return ProposalState.Active;
+ } else if (!IProposalValidator(address(proposal.executor)).isProposalPassed(this, proposalId)) {
+ return ProposalState.Failed;
+ } else if (proposal.executionTime == 0) {
+ return ProposalState.Succeeded;
+ } else if (proposal.executed) {
+ return ProposalState.Executed;
+ } else if (proposal.executor.isProposalOverGracePeriod(this, proposalId)) {
+ return ProposalState.Expired;
+ } else {
+ return ProposalState.Queued;
+ }
+ }
+
+ function _queueOrRevert(
+ IExecutorWithTimelock executor,
+ address target,
+ uint256 value,
+ string memory signature,
+ bytes memory callData,
+ uint256 executionTime,
+ bool withDelegatecall
+ ) internal {
+ require(
+ !executor.isActionQueued(
+ keccak256(abi.encode(target, value, signature, callData, executionTime, withDelegatecall))
+ ),
+ 'DUPLICATED_ACTION'
+ );
+ executor.queueTransaction(target, value, signature, callData, executionTime, withDelegatecall);
+ }
+
+ function _submitVote(
+ address voter,
+ uint256 proposalId,
+ bool support
+ ) internal {
+ require(getProposalState(proposalId) == ProposalState.Active, 'VOTING_CLOSED');
+ Proposal storage proposal = _proposals[proposalId];
+ Vote storage vote = proposal.votes[voter];
+
+ require(vote.votingPower == 0, 'VOTE_ALREADY_SUBMITTED');
+
+ uint256 votingPower = IVotingStrategy(proposal.strategy).getVotingPowerAt(
+ voter,
+ proposal.startBlock
+ );
+
+ if (support) {
+ proposal.forVotes = proposal.forVotes.add(votingPower);
+ } else {
+ proposal.againstVotes = proposal.againstVotes.add(votingPower);
+ }
+
+ vote.support = support;
+ vote.votingPower = uint248(votingPower);
+
+ emit VoteEmitted(proposalId, voter, support, votingPower);
+ }
+
+ function _setGovernanceStrategy(address governanceStrategy) internal {
+ _governanceStrategy = governanceStrategy;
+
+ emit GovernanceStrategyChanged(governanceStrategy, msg.sender);
+ }
+
+ function _setVotingDelay(uint256 votingDelay) internal {
+ _votingDelay = votingDelay;
+
+ emit VotingDelayChanged(votingDelay, msg.sender);
+ }
+
+ function _authorizeExecutor(address executor) internal {
+ _authorizedExecutors[executor] = true;
+ emit ExecutorAuthorized(executor);
+ }
+
+ function _unauthorizeExecutor(address executor) internal {
+ _authorizedExecutors[executor] = false;
+ emit ExecutorUnauthorized(executor);
+ }
+}
diff --git a/packages/chain-events/eth/contracts/AAVE/governance/Executor.sol b/packages/chain-events/eth/contracts/AAVE/governance/Executor.sol
new file mode 100644
index 00000000000..9aca4d65f99
--- /dev/null
+++ b/packages/chain-events/eth/contracts/AAVE/governance/Executor.sol
@@ -0,0 +1,31 @@
+// SPDX-License-Identifier: agpl-3.0
+pragma solidity 0.7.5;
+pragma abicoder v2;
+
+import {ExecutorWithTimelock} from './ExecutorWithTimelock.sol';
+import {ProposalValidator} from './ProposalValidator.sol';
+
+/**
+ * @title Time Locked, Validator, Executor Contract
+ * @dev Contract
+ * - Validate Proposal creations/ cancellation
+ * - Validate Vote Quorum and Vote success on proposal
+ * - Queue, Execute, Cancel, successful proposals' transactions.
+ * @author Aave
+ **/
+contract Executor is ExecutorWithTimelock, ProposalValidator {
+ constructor(
+ address admin,
+ uint256 delay,
+ uint256 gracePeriod,
+ uint256 minimumDelay,
+ uint256 maximumDelay,
+ uint256 propositionThreshold,
+ uint256 voteDuration,
+ uint256 voteDifferential,
+ uint256 minimumQuorum
+ )
+ ExecutorWithTimelock(admin, delay, gracePeriod, minimumDelay, maximumDelay)
+ ProposalValidator(propositionThreshold, voteDuration, voteDifferential, minimumQuorum)
+ {}
+}
diff --git a/packages/chain-events/eth/contracts/AAVE/governance/ExecutorWithTimelock.sol b/packages/chain-events/eth/contracts/AAVE/governance/ExecutorWithTimelock.sol
new file mode 100644
index 00000000000..29cdadf65e4
--- /dev/null
+++ b/packages/chain-events/eth/contracts/AAVE/governance/ExecutorWithTimelock.sol
@@ -0,0 +1,287 @@
+// SPDX-License-Identifier: agpl-3.0
+pragma solidity 0.7.5;
+pragma abicoder v2;
+
+import {IExecutorWithTimelock} from '../interfaces/IExecutorWithTimelock.sol';
+import {IAaveGovernanceV2} from '../interfaces/IAaveGovernanceV2.sol';
+import {SafeMath} from '../dependencies/open-zeppelin/SafeMath.sol';
+
+/**
+ * @title Time Locked Executor Contract, inherited by Aave Governance Executors
+ * @dev Contract that can queue, execute, cancel transactions voted by Governance
+ * Queued transactions can be executed after a delay and until
+ * Grace period is not over.
+ * @author Aave
+ **/
+contract ExecutorWithTimelock is IExecutorWithTimelock {
+ using SafeMath for uint256;
+
+ uint256 public immutable override GRACE_PERIOD;
+ uint256 public immutable override MINIMUM_DELAY;
+ uint256 public immutable override MAXIMUM_DELAY;
+
+ address private _admin;
+ address private _pendingAdmin;
+ uint256 private _delay;
+
+ mapping(bytes32 => bool) private _queuedTransactions;
+
+ /**
+ * @dev Constructor
+ * @param admin admin address, that can call the main functions, (Governance)
+ * @param delay minimum time between queueing and execution of proposal
+ * @param gracePeriod time after `delay` while a proposal can be executed
+ * @param minimumDelay lower threshold of `delay`, in seconds
+ * @param maximumDelay upper threhold of `delay`, in seconds
+ **/
+ constructor(
+ address admin,
+ uint256 delay,
+ uint256 gracePeriod,
+ uint256 minimumDelay,
+ uint256 maximumDelay
+ ) {
+ require(delay >= minimumDelay, 'DELAY_SHORTER_THAN_MINIMUM');
+ require(delay <= maximumDelay, 'DELAY_LONGER_THAN_MAXIMUM');
+ _delay = delay;
+ _admin = admin;
+
+ GRACE_PERIOD = gracePeriod;
+ MINIMUM_DELAY = minimumDelay;
+ MAXIMUM_DELAY = maximumDelay;
+
+ emit NewDelay(delay);
+ emit NewAdmin(admin);
+ }
+
+ modifier onlyAdmin() {
+ require(msg.sender == _admin, 'ONLY_BY_ADMIN');
+ _;
+ }
+
+ modifier onlyTimelock() {
+ require(msg.sender == address(this), 'ONLY_BY_THIS_TIMELOCK');
+ _;
+ }
+
+ modifier onlyPendingAdmin() {
+ require(msg.sender == _pendingAdmin, 'ONLY_BY_PENDING_ADMIN');
+ _;
+ }
+
+ /**
+ * @dev Set the delay
+ * @param delay delay between queue and execution of proposal
+ **/
+ function setDelay(uint256 delay) public onlyTimelock {
+ _validateDelay(delay);
+ _delay = delay;
+
+ emit NewDelay(delay);
+ }
+
+ /**
+ * @dev Function enabling pending admin to become admin
+ **/
+ function acceptAdmin() public onlyPendingAdmin {
+ _admin = msg.sender;
+ _pendingAdmin = address(0);
+
+ emit NewAdmin(msg.sender);
+ }
+
+ /**
+ * @dev Setting a new pending admin (that can then become admin)
+ * Can only be called by this executor (i.e via proposal)
+ * @param newPendingAdmin address of the new admin
+ **/
+ function setPendingAdmin(address newPendingAdmin) public onlyTimelock {
+ _pendingAdmin = newPendingAdmin;
+
+ emit NewPendingAdmin(newPendingAdmin);
+ }
+
+ /**
+ * @dev Function, called by Governance, that queue a transaction, returns action hash
+ * @param target smart contract target
+ * @param value wei value of the transaction
+ * @param signature function signature of the transaction
+ * @param data function arguments of the transaction or callData if signature empty
+ * @param executionTime time at which to execute the transaction
+ * @param withDelegatecall boolean, true = transaction delegatecalls the target, else calls the target
+ * @return the action Hash
+ **/
+ function queueTransaction(
+ address target,
+ uint256 value,
+ string memory signature,
+ bytes memory data,
+ uint256 executionTime,
+ bool withDelegatecall
+ ) public override onlyAdmin returns (bytes32) {
+ require(executionTime >= block.timestamp.add(_delay), 'EXECUTION_TIME_UNDERESTIMATED');
+
+ bytes32 actionHash = keccak256(
+ abi.encode(target, value, signature, data, executionTime, withDelegatecall)
+ );
+ _queuedTransactions[actionHash] = true;
+
+ emit QueuedAction(actionHash, target, value, signature, data, executionTime, withDelegatecall);
+ return actionHash;
+ }
+
+ /**
+ * @dev Function, called by Governance, that cancels a transaction, returns action hash
+ * @param target smart contract target
+ * @param value wei value of the transaction
+ * @param signature function signature of the transaction
+ * @param data function arguments of the transaction or callData if signature empty
+ * @param executionTime time at which to execute the transaction
+ * @param withDelegatecall boolean, true = transaction delegatecalls the target, else calls the target
+ * @return the action Hash of the canceled tx
+ **/
+ function cancelTransaction(
+ address target,
+ uint256 value,
+ string memory signature,
+ bytes memory data,
+ uint256 executionTime,
+ bool withDelegatecall
+ ) public override onlyAdmin returns (bytes32) {
+ bytes32 actionHash = keccak256(
+ abi.encode(target, value, signature, data, executionTime, withDelegatecall)
+ );
+ _queuedTransactions[actionHash] = false;
+
+ emit CancelledAction(
+ actionHash,
+ target,
+ value,
+ signature,
+ data,
+ executionTime,
+ withDelegatecall
+ );
+ return actionHash;
+ }
+
+ /**
+ * @dev Function, called by Governance, that cancels a transaction, returns the callData executed
+ * @param target smart contract target
+ * @param value wei value of the transaction
+ * @param signature function signature of the transaction
+ * @param data function arguments of the transaction or callData if signature empty
+ * @param executionTime time at which to execute the transaction
+ * @param withDelegatecall boolean, true = transaction delegatecalls the target, else calls the target
+ * @return the callData executed as memory bytes
+ **/
+ function executeTransaction(
+ address target,
+ uint256 value,
+ string memory signature,
+ bytes memory data,
+ uint256 executionTime,
+ bool withDelegatecall
+ ) public payable override onlyAdmin returns (bytes memory) {
+ bytes32 actionHash = keccak256(
+ abi.encode(target, value, signature, data, executionTime, withDelegatecall)
+ );
+ require(_queuedTransactions[actionHash], 'ACTION_NOT_QUEUED');
+ require(block.timestamp >= executionTime, 'TIMELOCK_NOT_FINISHED');
+ require(block.timestamp <= executionTime.add(GRACE_PERIOD), 'GRACE_PERIOD_FINISHED');
+
+ _queuedTransactions[actionHash] = false;
+
+ bytes memory callData;
+
+ if (bytes(signature).length == 0) {
+ callData = data;
+ } else {
+ callData = abi.encodePacked(bytes4(keccak256(bytes(signature))), data);
+ }
+
+ bool success;
+ bytes memory resultData;
+ if (withDelegatecall) {
+ require(msg.value >= value, "NOT_ENOUGH_MSG_VALUE");
+ // solium-disable-next-line security/no-call-value
+ (success, resultData) = target.delegatecall(callData);
+ } else {
+ // solium-disable-next-line security/no-call-value
+ (success, resultData) = target.call{value: value}(callData);
+ }
+
+ require(success, 'FAILED_ACTION_EXECUTION');
+
+ emit ExecutedAction(
+ actionHash,
+ target,
+ value,
+ signature,
+ data,
+ executionTime,
+ withDelegatecall,
+ resultData
+ );
+
+ return resultData;
+ }
+
+ /**
+ * @dev Getter of the current admin address (should be governance)
+ * @return The address of the current admin
+ **/
+ function getAdmin() external view override returns (address) {
+ return _admin;
+ }
+
+ /**
+ * @dev Getter of the current pending admin address
+ * @return The address of the pending admin
+ **/
+ function getPendingAdmin() external view override returns (address) {
+ return _pendingAdmin;
+ }
+
+ /**
+ * @dev Getter of the delay between queuing and execution
+ * @return The delay in seconds
+ **/
+ function getDelay() external view override returns (uint256) {
+ return _delay;
+ }
+
+ /**
+ * @dev Returns whether an action (via actionHash) is queued
+ * @param actionHash hash of the action to be checked
+ * keccak256(abi.encode(target, value, signature, data, executionTime, withDelegatecall))
+ * @return true if underlying action of actionHash is queued
+ **/
+ function isActionQueued(bytes32 actionHash) external view override returns (bool) {
+ return _queuedTransactions[actionHash];
+ }
+
+ /**
+ * @dev Checks whether a proposal is over its grace period
+ * @param governance Governance contract
+ * @param proposalId Id of the proposal against which to test
+ * @return true of proposal is over grace period
+ **/
+ function isProposalOverGracePeriod(IAaveGovernanceV2 governance, uint256 proposalId)
+ external
+ view
+ override
+ returns (bool)
+ {
+ IAaveGovernanceV2.ProposalWithoutVotes memory proposal = governance.getProposalById(proposalId);
+
+ return (block.timestamp > proposal.executionTime.add(GRACE_PERIOD));
+ }
+
+ function _validateDelay(uint256 delay) internal view {
+ require(delay >= MINIMUM_DELAY, 'DELAY_SHORTER_THAN_MINIMUM');
+ require(delay <= MAXIMUM_DELAY, 'DELAY_LONGER_THAN_MAXIMUM');
+ }
+
+ receive() external payable {}
+}
diff --git a/packages/chain-events/eth/contracts/AAVE/governance/GovernanceStrategy.sol b/packages/chain-events/eth/contracts/AAVE/governance/GovernanceStrategy.sol
new file mode 100644
index 00000000000..31fb4dd5d9f
--- /dev/null
+++ b/packages/chain-events/eth/contracts/AAVE/governance/GovernanceStrategy.sol
@@ -0,0 +1,95 @@
+// SPDX-License-Identifier: agpl-3.0
+pragma solidity 0.7.5;
+pragma abicoder v2;
+
+import {IGovernanceStrategy} from '../interfaces/IGovernanceStrategy.sol';
+import {IERC20} from '../interfaces/IERC20.sol';
+import {IGovernancePowerDelegationToken} from '../interfaces/IGovernancePowerDelegationToken.sol';
+
+/**
+ * @title Governance Strategy contract
+ * @dev Smart contract containing logic to measure users' relative power to propose and vote.
+ * User Power = User Power from Aave Token + User Power from stkAave Token.
+ * User Power from Token = Token Power + Token Power as Delegatee [- Token Power if user has delegated]
+ * Two wrapper functions linked to Aave Tokens's GovernancePowerDelegationERC20.sol implementation
+ * - getPropositionPowerAt: fetching a user Proposition Power at a specified block
+ * - getVotingPowerAt: fetching a user Voting Power at a specified block
+ * @author Aave
+ **/
+contract GovernanceStrategy is IGovernanceStrategy {
+ address public immutable AAVE;
+ address public immutable STK_AAVE;
+
+ /**
+ * @dev Constructor, register tokens used for Voting and Proposition Powers.
+ * @param aave The address of the AAVE Token contract.
+ * @param stkAave The address of the stkAAVE Token Contract
+ **/
+ constructor(address aave, address stkAave) {
+ AAVE = aave;
+ STK_AAVE = stkAave;
+ }
+
+ /**
+ * @dev Returns the total supply of Proposition Tokens Available for Governance
+ * = AAVE Available for governance + stkAAVE available
+ * The supply of AAVE staked in stkAAVE are not taken into account so:
+ * = (Supply of AAVE - AAVE in stkAAVE) + (Supply of stkAAVE)
+ * = Supply of AAVE, Since the supply of stkAAVE is equal to the number of AAVE staked
+ * @param blockNumber Blocknumber at which to evaluate
+ * @return total supply at blockNumber
+ **/
+ function getTotalPropositionSupplyAt(uint256 blockNumber) public view override returns (uint256) {
+ return IERC20(AAVE).totalSupplyAt(blockNumber);
+ }
+
+ /**
+ * @dev Returns the total supply of Outstanding Voting Tokens
+ * @param blockNumber Blocknumber at which to evaluate
+ * @return total supply at blockNumber
+ **/
+ function getTotalVotingSupplyAt(uint256 blockNumber) public view override returns (uint256) {
+ return getTotalPropositionSupplyAt(blockNumber);
+ }
+
+ /**
+ * @dev Returns the Proposition Power of a user at a specific block number.
+ * @param user Address of the user.
+ * @param blockNumber Blocknumber at which to fetch Proposition Power
+ * @return Power number
+ **/
+ function getPropositionPowerAt(address user, uint256 blockNumber)
+ public
+ view
+ override
+ returns (uint256)
+ {
+ return
+ _getPowerByTypeAt(user, blockNumber, IGovernancePowerDelegationToken.DelegationType.PROPOSITION_POWER);
+ }
+
+ /**
+ * @dev Returns the Vote Power of a user at a specific block number.
+ * @param user Address of the user.
+ * @param blockNumber Blocknumber at which to fetch Vote Power
+ * @return Vote number
+ **/
+ function getVotingPowerAt(address user, uint256 blockNumber)
+ public
+ view
+ override
+ returns (uint256)
+ {
+ return _getPowerByTypeAt(user, blockNumber, IGovernancePowerDelegationToken.DelegationType.VOTING_POWER);
+ }
+
+ function _getPowerByTypeAt(
+ address user,
+ uint256 blockNumber,
+ IGovernancePowerDelegationToken.DelegationType powerType
+ ) internal view returns (uint256) {
+ return
+ IGovernancePowerDelegationToken(AAVE).getPowerAtBlock(user, blockNumber, powerType) +
+ IGovernancePowerDelegationToken(STK_AAVE).getPowerAtBlock(user, blockNumber, powerType);
+ }
+}
diff --git a/packages/chain-events/eth/contracts/AAVE/governance/ProposalValidator.sol b/packages/chain-events/eth/contracts/AAVE/governance/ProposalValidator.sol
new file mode 100644
index 00000000000..41cfcb80d65
--- /dev/null
+++ b/packages/chain-events/eth/contracts/AAVE/governance/ProposalValidator.sol
@@ -0,0 +1,196 @@
+// SPDX-License-Identifier: agpl-3.0
+pragma solidity 0.7.5;
+pragma abicoder v2;
+
+import {IAaveGovernanceV2} from '../interfaces/IAaveGovernanceV2.sol';
+import {IGovernanceStrategy} from '../interfaces/IGovernanceStrategy.sol';
+import {IProposalValidator} from '../interfaces/IProposalValidator.sol';
+import {SafeMath} from '../dependencies/open-zeppelin/SafeMath.sol';
+
+/**
+ * @title Proposal Validator Contract, inherited by Aave Governance Executors
+ * @dev Validates/Invalidations propositions state modifications.
+ * Proposition Power functions: Validates proposition creations/ cancellation
+ * Voting Power functions: Validates success of propositions.
+ * @author Aave
+ **/
+contract ProposalValidator is IProposalValidator {
+ using SafeMath for uint256;
+
+ uint256 public immutable override PROPOSITION_THRESHOLD;
+ uint256 public immutable override VOTING_DURATION;
+ uint256 public immutable override VOTE_DIFFERENTIAL;
+ uint256 public immutable override MINIMUM_QUORUM;
+ uint256 public constant override ONE_HUNDRED_WITH_PRECISION = 10000; // Equivalent to 100%, but scaled for precision
+
+ /**
+ * @dev Constructor
+ * @param propositionThreshold minimum percentage of supply needed to submit a proposal
+ * - In ONE_HUNDRED_WITH_PRECISION units
+ * @param votingDuration duration in blocks of the voting period
+ * @param voteDifferential percentage of supply that `for` votes need to be over `against`
+ * in order for the proposal to pass
+ * - In ONE_HUNDRED_WITH_PRECISION units
+ * @param minimumQuorum minimum percentage of the supply in FOR-voting-power need for a proposal to pass
+ * - In ONE_HUNDRED_WITH_PRECISION units
+ **/
+ constructor(
+ uint256 propositionThreshold,
+ uint256 votingDuration,
+ uint256 voteDifferential,
+ uint256 minimumQuorum
+ ) {
+ PROPOSITION_THRESHOLD = propositionThreshold;
+ VOTING_DURATION = votingDuration;
+ VOTE_DIFFERENTIAL = voteDifferential;
+ MINIMUM_QUORUM = minimumQuorum;
+ }
+
+ /**
+ * @dev Called to validate a proposal (e.g when creating new proposal in Governance)
+ * @param governance Governance Contract
+ * @param user Address of the proposal creator
+ * @param blockNumber Block Number against which to make the test (e.g proposal creation block -1).
+ * @return boolean, true if can be created
+ **/
+ function validateCreatorOfProposal(
+ IAaveGovernanceV2 governance,
+ address user,
+ uint256 blockNumber
+ ) external view override returns (bool) {
+ return isPropositionPowerEnough(governance, user, blockNumber);
+ }
+
+ /**
+ * @dev Called to validate the cancellation of a proposal
+ * Needs to creator to have lost proposition power threashold
+ * @param governance Governance Contract
+ * @param user Address of the proposal creator
+ * @param blockNumber Block Number against which to make the test (e.g proposal creation block -1).
+ * @return boolean, true if can be cancelled
+ **/
+ function validateProposalCancellation(
+ IAaveGovernanceV2 governance,
+ address user,
+ uint256 blockNumber
+ ) external view override returns (bool) {
+ return !isPropositionPowerEnough(governance, user, blockNumber);
+ }
+
+ /**
+ * @dev Returns whether a user has enough Proposition Power to make a proposal.
+ * @param governance Governance Contract
+ * @param user Address of the user to be challenged.
+ * @param blockNumber Block Number against which to make the challenge.
+ * @return true if user has enough power
+ **/
+ function isPropositionPowerEnough(
+ IAaveGovernanceV2 governance,
+ address user,
+ uint256 blockNumber
+ ) public view override returns (bool) {
+ IGovernanceStrategy currentGovernanceStrategy = IGovernanceStrategy(
+ governance.getGovernanceStrategy()
+ );
+ return
+ currentGovernanceStrategy.getPropositionPowerAt(user, blockNumber) >=
+ getMinimumPropositionPowerNeeded(governance, blockNumber);
+ }
+
+ /**
+ * @dev Returns the minimum Proposition Power needed to create a proposition.
+ * @param governance Governance Contract
+ * @param blockNumber Blocknumber at which to evaluate
+ * @return minimum Proposition Power needed
+ **/
+ function getMinimumPropositionPowerNeeded(IAaveGovernanceV2 governance, uint256 blockNumber)
+ public
+ view
+ override
+ returns (uint256)
+ {
+ IGovernanceStrategy currentGovernanceStrategy = IGovernanceStrategy(
+ governance.getGovernanceStrategy()
+ );
+ return
+ currentGovernanceStrategy
+ .getTotalPropositionSupplyAt(blockNumber)
+ .mul(PROPOSITION_THRESHOLD)
+ .div(ONE_HUNDRED_WITH_PRECISION);
+ }
+
+ /**
+ * @dev Returns whether a proposal passed or not
+ * @param governance Governance Contract
+ * @param proposalId Id of the proposal to set
+ * @return true if proposal passed
+ **/
+ function isProposalPassed(IAaveGovernanceV2 governance, uint256 proposalId)
+ external
+ view
+ override
+ returns (bool)
+ {
+ return (isQuorumValid(governance, proposalId) &&
+ isVoteDifferentialValid(governance, proposalId));
+ }
+
+ /**
+ * @dev Calculates the minimum amount of Voting Power needed for a proposal to Pass
+ * @param votingSupply Total number of oustanding voting tokens
+ * @return voting power needed for a proposal to pass
+ **/
+ function getMinimumVotingPowerNeeded(uint256 votingSupply)
+ public
+ view
+ override
+ returns (uint256)
+ {
+ return votingSupply.mul(MINIMUM_QUORUM).div(ONE_HUNDRED_WITH_PRECISION);
+ }
+
+ /**
+ * @dev Check whether a proposal has reached quorum, ie has enough FOR-voting-power
+ * Here quorum is not to understand as number of votes reached, but number of for-votes reached
+ * @param governance Governance Contract
+ * @param proposalId Id of the proposal to verify
+ * @return voting power needed for a proposal to pass
+ **/
+ function isQuorumValid(IAaveGovernanceV2 governance, uint256 proposalId)
+ public
+ view
+ override
+ returns (bool)
+ {
+ IAaveGovernanceV2.ProposalWithoutVotes memory proposal = governance.getProposalById(proposalId);
+ uint256 votingSupply = IGovernanceStrategy(proposal.strategy).getTotalVotingSupplyAt(
+ proposal.startBlock
+ );
+
+ return proposal.forVotes >= getMinimumVotingPowerNeeded(votingSupply);
+ }
+
+ /**
+ * @dev Check whether a proposal has enough extra FOR-votes than AGAINST-votes
+ * FOR VOTES - AGAINST VOTES > VOTE_DIFFERENTIAL * voting supply
+ * @param governance Governance Contract
+ * @param proposalId Id of the proposal to verify
+ * @return true if enough For-Votes
+ **/
+ function isVoteDifferentialValid(IAaveGovernanceV2 governance, uint256 proposalId)
+ public
+ view
+ override
+ returns (bool)
+ {
+ IAaveGovernanceV2.ProposalWithoutVotes memory proposal = governance.getProposalById(proposalId);
+ uint256 votingSupply = IGovernanceStrategy(proposal.strategy).getTotalVotingSupplyAt(
+ proposal.startBlock
+ );
+
+ return (proposal.forVotes.mul(ONE_HUNDRED_WITH_PRECISION).div(votingSupply) >
+ proposal.againstVotes.mul(ONE_HUNDRED_WITH_PRECISION).div(votingSupply).add(
+ VOTE_DIFFERENTIAL
+ ));
+ }
+}
diff --git a/packages/chain-events/eth/contracts/AAVE/interfaces/IAaveGovernanceV2.sol b/packages/chain-events/eth/contracts/AAVE/interfaces/IAaveGovernanceV2.sol
new file mode 100644
index 00000000000..a4c8d86e8ee
--- /dev/null
+++ b/packages/chain-events/eth/contracts/AAVE/interfaces/IAaveGovernanceV2.sol
@@ -0,0 +1,270 @@
+// SPDX-License-Identifier: agpl-3.0
+pragma solidity 0.7.5;
+pragma abicoder v2;
+
+import {IExecutorWithTimelock} from './IExecutorWithTimelock.sol';
+
+interface IAaveGovernanceV2 {
+ enum ProposalState {Pending, Canceled, Active, Failed, Succeeded, Queued, Expired, Executed}
+
+ struct Vote {
+ bool support;
+ uint248 votingPower;
+ }
+
+ struct Proposal {
+ uint256 id;
+ address creator;
+ IExecutorWithTimelock executor;
+ address[] targets;
+ uint256[] values;
+ string[] signatures;
+ bytes[] calldatas;
+ bool[] withDelegatecalls;
+ uint256 startBlock;
+ uint256 endBlock;
+ uint256 executionTime;
+ uint256 forVotes;
+ uint256 againstVotes;
+ bool executed;
+ bool canceled;
+ address strategy;
+ bytes32 ipfsHash;
+ mapping(address => Vote) votes;
+ }
+
+ struct ProposalWithoutVotes {
+ uint256 id;
+ address creator;
+ IExecutorWithTimelock executor;
+ address[] targets;
+ uint256[] values;
+ string[] signatures;
+ bytes[] calldatas;
+ bool[] withDelegatecalls;
+ uint256 startBlock;
+ uint256 endBlock;
+ uint256 executionTime;
+ uint256 forVotes;
+ uint256 againstVotes;
+ bool executed;
+ bool canceled;
+ address strategy;
+ bytes32 ipfsHash;
+ }
+
+ /**
+ * @dev emitted when a new proposal is created
+ * @param id Id of the proposal
+ * @param creator address of the creator
+ * @param executor The ExecutorWithTimelock contract that will execute the proposal
+ * @param targets list of contracts called by proposal's associated transactions
+ * @param values list of value in wei for each propoposal's associated transaction
+ * @param signatures list of function signatures (can be empty) to be used when created the callData
+ * @param calldatas list of calldatas: if associated signature empty, calldata ready, else calldata is arguments
+ * @param withDelegatecalls boolean, true = transaction delegatecalls the taget, else calls the target
+ * @param startBlock block number when vote starts
+ * @param endBlock block number when vote ends
+ * @param strategy address of the governanceStrategy contract
+ * @param ipfsHash IPFS hash of the proposal
+ **/
+ event ProposalCreated(
+ uint256 id,
+ address indexed creator,
+ IExecutorWithTimelock indexed executor,
+ address[] targets,
+ uint256[] values,
+ string[] signatures,
+ bytes[] calldatas,
+ bool[] withDelegatecalls,
+ uint256 startBlock,
+ uint256 endBlock,
+ address strategy,
+ bytes32 ipfsHash
+ );
+
+ /**
+ * @dev emitted when a proposal is canceled
+ * @param id Id of the proposal
+ **/
+ event ProposalCanceled(uint256 id);
+
+ /**
+ * @dev emitted when a proposal is queued
+ * @param id Id of the proposal
+ * @param executionTime time when proposal underlying transactions can be executed
+ * @param initiatorQueueing address of the initiator of the queuing transaction
+ **/
+ event ProposalQueued(uint256 id, uint256 executionTime, address indexed initiatorQueueing);
+ /**
+ * @dev emitted when a proposal is executed
+ * @param id Id of the proposal
+ * @param initiatorExecution address of the initiator of the execution transaction
+ **/
+ event ProposalExecuted(uint256 id, address indexed initiatorExecution);
+ /**
+ * @dev emitted when a vote is registered
+ * @param id Id of the proposal
+ * @param voter address of the voter
+ * @param support boolean, true = vote for, false = vote against
+ * @param votingPower Power of the voter/vote
+ **/
+ event VoteEmitted(uint256 id, address indexed voter, bool support, uint256 votingPower);
+
+ event GovernanceStrategyChanged(address indexed newStrategy, address indexed initiatorChange);
+
+ event VotingDelayChanged(uint256 newVotingDelay, address indexed initiatorChange);
+
+ event ExecutorAuthorized(address executor);
+
+ event ExecutorUnauthorized(address executor);
+
+ /**
+ * @dev Creates a Proposal (needs Proposition Power of creator > Threshold)
+ * @param executor The ExecutorWithTimelock contract that will execute the proposal
+ * @param targets list of contracts called by proposal's associated transactions
+ * @param values list of value in wei for each propoposal's associated transaction
+ * @param signatures list of function signatures (can be empty) to be used when created the callData
+ * @param calldatas list of calldatas: if associated signature empty, calldata ready, else calldata is arguments
+ * @param withDelegatecalls if true, transaction delegatecalls the taget, else calls the target
+ * @param ipfsHash IPFS hash of the proposal
+ **/
+ function create(
+ IExecutorWithTimelock executor,
+ address[] memory targets,
+ uint256[] memory values,
+ string[] memory signatures,
+ bytes[] memory calldatas,
+ bool[] memory withDelegatecalls,
+ bytes32 ipfsHash
+ ) external returns (uint256);
+
+ /**
+ * @dev Cancels a Proposal,
+ * either at anytime by guardian
+ * or when proposal is Pending/Active and threshold no longer reached
+ * @param proposalId id of the proposal
+ **/
+ function cancel(uint256 proposalId) external;
+
+ /**
+ * @dev Queue the proposal (If Proposal Succeeded)
+ * @param proposalId id of the proposal to queue
+ **/
+ function queue(uint256 proposalId) external;
+
+ /**
+ * @dev Execute the proposal (If Proposal Queued)
+ * @param proposalId id of the proposal to execute
+ **/
+ function execute(uint256 proposalId) external payable;
+
+ /**
+ * @dev Function allowing msg.sender to vote for/against a proposal
+ * @param proposalId id of the proposal
+ * @param support boolean, true = vote for, false = vote against
+ **/
+ function submitVote(uint256 proposalId, bool support) external;
+
+ /**
+ * @dev Function to register the vote of user that has voted offchain via signature
+ * @param proposalId id of the proposal
+ * @param support boolean, true = vote for, false = vote against
+ * @param v v part of the voter signature
+ * @param r r part of the voter signature
+ * @param s s part of the voter signature
+ **/
+ function submitVoteBySignature(
+ uint256 proposalId,
+ bool support,
+ uint8 v,
+ bytes32 r,
+ bytes32 s
+ ) external;
+
+ /**
+ * @dev Set new GovernanceStrategy
+ * Note: owner should be a timelocked executor, so needs to make a proposal
+ * @param governanceStrategy new Address of the GovernanceStrategy contract
+ **/
+ function setGovernanceStrategy(address governanceStrategy) external;
+
+ /**
+ * @dev Set new Voting Delay (delay before a newly created proposal can be voted on)
+ * Note: owner should be a timelocked executor, so needs to make a proposal
+ * @param votingDelay new voting delay in seconds
+ **/
+ function setVotingDelay(uint256 votingDelay) external;
+
+ /**
+ * @dev Add new addresses to the list of authorized executors
+ * @param executors list of new addresses to be authorized executors
+ **/
+ function authorizeExecutors(address[] memory executors) external;
+
+ /**
+ * @dev Remove addresses to the list of authorized executors
+ * @param executors list of addresses to be removed as authorized executors
+ **/
+ function unauthorizeExecutors(address[] memory executors) external;
+
+ /**
+ * @dev Let the guardian abdicate from its priviledged rights
+ **/
+ function __abdicate() external;
+
+ /**
+ * @dev Getter of the current GovernanceStrategy address
+ * @return The address of the current GovernanceStrategy contracts
+ **/
+ function getGovernanceStrategy() external view returns (address);
+
+ /**
+ * @dev Getter of the current Voting Delay (delay before a created proposal can be voted on)
+ * Different from the voting duration
+ * @return The voting delay in seconds
+ **/
+ function getVotingDelay() external view returns (uint256);
+
+ /**
+ * @dev Returns whether an address is an authorized executor
+ * @param executor address to evaluate as authorized executor
+ * @return true if authorized
+ **/
+ function isExecutorAuthorized(address executor) external view returns (bool);
+
+ /**
+ * @dev Getter the address of the guardian, that can mainly cancel proposals
+ * @return The address of the guardian
+ **/
+ function getGuardian() external view returns (address);
+
+ /**
+ * @dev Getter of the proposal count (the current number of proposals ever created)
+ * @return the proposal count
+ **/
+ function getProposalsCount() external view returns (uint256);
+
+ /**
+ * @dev Getter of a proposal by id
+ * @param proposalId id of the proposal to get
+ * @return the proposal as ProposalWithoutVotes memory object
+ **/
+ function getProposalById(uint256 proposalId) external view returns (ProposalWithoutVotes memory);
+
+ /**
+ * @dev Getter of the Vote of a voter about a proposal
+ * Note: Vote is a struct: ({bool support, uint248 votingPower})
+ * @param proposalId id of the proposal
+ * @param voter address of the voter
+ * @return The associated Vote memory object
+ **/
+ function getVoteOnProposal(uint256 proposalId, address voter) external view returns (Vote memory);
+
+ /**
+ * @dev Get the current state of a proposal
+ * @param proposalId id of the proposal
+ * @return The current state if the proposal
+ **/
+ function getProposalState(uint256 proposalId) external view returns (ProposalState);
+}
diff --git a/packages/chain-events/eth/contracts/AAVE/interfaces/IERC20.sol b/packages/chain-events/eth/contracts/AAVE/interfaces/IERC20.sol
new file mode 100644
index 00000000000..40b56d02b60
--- /dev/null
+++ b/packages/chain-events/eth/contracts/AAVE/interfaces/IERC20.sol
@@ -0,0 +1,9 @@
+// SPDX-License-Identifier: agpl-3.0
+pragma solidity 0.7.5;
+pragma abicoder v2;
+
+interface IERC20 {
+ function totalSupplyAt(uint256 blockNumber) external view returns (uint256);
+
+ function balanceOf(address account) external view returns (uint256);
+}
diff --git a/packages/chain-events/eth/contracts/AAVE/interfaces/IExecutorWithTimelock.sol b/packages/chain-events/eth/contracts/AAVE/interfaces/IExecutorWithTimelock.sol
new file mode 100644
index 00000000000..b3be8d20cec
--- /dev/null
+++ b/packages/chain-events/eth/contracts/AAVE/interfaces/IExecutorWithTimelock.sol
@@ -0,0 +1,185 @@
+// SPDX-License-Identifier: agpl-3.0
+pragma solidity 0.7.5;
+pragma abicoder v2;
+
+import {IAaveGovernanceV2} from './IAaveGovernanceV2.sol';
+
+interface IExecutorWithTimelock {
+ /**
+ * @dev emitted when a new pending admin is set
+ * @param newPendingAdmin address of the new pending admin
+ **/
+ event NewPendingAdmin(address newPendingAdmin);
+
+ /**
+ * @dev emitted when a new admin is set
+ * @param newAdmin address of the new admin
+ **/
+ event NewAdmin(address newAdmin);
+
+ /**
+ * @dev emitted when a new delay (between queueing and execution) is set
+ * @param delay new delay
+ **/
+ event NewDelay(uint256 delay);
+
+ /**
+ * @dev emitted when a new (trans)action is Queued.
+ * @param actionHash hash of the action
+ * @param target address of the targeted contract
+ * @param value wei value of the transaction
+ * @param signature function signature of the transaction
+ * @param data function arguments of the transaction or callData if signature empty
+ * @param executionTime time at which to execute the transaction
+ * @param withDelegatecall boolean, true = transaction delegatecalls the target, else calls the target
+ **/
+ event QueuedAction(
+ bytes32 actionHash,
+ address indexed target,
+ uint256 value,
+ string signature,
+ bytes data,
+ uint256 executionTime,
+ bool withDelegatecall
+ );
+
+ /**
+ * @dev emitted when an action is Cancelled
+ * @param actionHash hash of the action
+ * @param target address of the targeted contract
+ * @param value wei value of the transaction
+ * @param signature function signature of the transaction
+ * @param data function arguments of the transaction or callData if signature empty
+ * @param executionTime time at which to execute the transaction
+ * @param withDelegatecall boolean, true = transaction delegatecalls the target, else calls the target
+ **/
+ event CancelledAction(
+ bytes32 actionHash,
+ address indexed target,
+ uint256 value,
+ string signature,
+ bytes data,
+ uint256 executionTime,
+ bool withDelegatecall
+ );
+
+ /**
+ * @dev emitted when an action is Cancelled
+ * @param actionHash hash of the action
+ * @param target address of the targeted contract
+ * @param value wei value of the transaction
+ * @param signature function signature of the transaction
+ * @param data function arguments of the transaction or callData if signature empty
+ * @param executionTime time at which to execute the transaction
+ * @param withDelegatecall boolean, true = transaction delegatecalls the target, else calls the target
+ * @param resultData the actual callData used on the target
+ **/
+ event ExecutedAction(
+ bytes32 actionHash,
+ address indexed target,
+ uint256 value,
+ string signature,
+ bytes data,
+ uint256 executionTime,
+ bool withDelegatecall,
+ bytes resultData
+ );
+ /**
+ * @dev Getter of the current admin address (should be governance)
+ * @return The address of the current admin
+ **/
+ function getAdmin() external view returns (address);
+ /**
+ * @dev Getter of the current pending admin address
+ * @return The address of the pending admin
+ **/
+ function getPendingAdmin() external view returns (address);
+ /**
+ * @dev Getter of the delay between queuing and execution
+ * @return The delay in seconds
+ **/
+ function getDelay() external view returns (uint256);
+ /**
+ * @dev Returns whether an action (via actionHash) is queued
+ * @param actionHash hash of the action to be checked
+ * keccak256(abi.encode(target, value, signature, data, executionTime, withDelegatecall))
+ * @return true if underlying action of actionHash is queued
+ **/
+ function isActionQueued(bytes32 actionHash) external view returns (bool);
+ /**
+ * @dev Checks whether a proposal is over its grace period
+ * @param governance Governance contract
+ * @param proposalId Id of the proposal against which to test
+ * @return true of proposal is over grace period
+ **/
+ function isProposalOverGracePeriod(IAaveGovernanceV2 governance, uint256 proposalId)
+ external
+ view
+ returns (bool);
+ /**
+ * @dev Getter of grace period constant
+ * @return grace period in seconds
+ **/
+ function GRACE_PERIOD() external view returns (uint256);
+ /**
+ * @dev Getter of minimum delay constant
+ * @return minimum delay in seconds
+ **/
+ function MINIMUM_DELAY() external view returns (uint256);
+ /**
+ * @dev Getter of maximum delay constant
+ * @return maximum delay in seconds
+ **/
+ function MAXIMUM_DELAY() external view returns (uint256);
+ /**
+ * @dev Function, called by Governance, that queue a transaction, returns action hash
+ * @param target smart contract target
+ * @param value wei value of the transaction
+ * @param signature function signature of the transaction
+ * @param data function arguments of the transaction or callData if signature empty
+ * @param executionTime time at which to execute the transaction
+ * @param withDelegatecall boolean, true = transaction delegatecalls the target, else calls the target
+ **/
+ function queueTransaction(
+ address target,
+ uint256 value,
+ string memory signature,
+ bytes memory data,
+ uint256 executionTime,
+ bool withDelegatecall
+ ) external returns (bytes32);
+ /**
+ * @dev Function, called by Governance, that cancels a transaction, returns the callData executed
+ * @param target smart contract target
+ * @param value wei value of the transaction
+ * @param signature function signature of the transaction
+ * @param data function arguments of the transaction or callData if signature empty
+ * @param executionTime time at which to execute the transaction
+ * @param withDelegatecall boolean, true = transaction delegatecalls the target, else calls the target
+ **/
+ function executeTransaction(
+ address target,
+ uint256 value,
+ string memory signature,
+ bytes memory data,
+ uint256 executionTime,
+ bool withDelegatecall
+ ) external payable returns (bytes memory);
+ /**
+ * @dev Function, called by Governance, that cancels a transaction, returns action hash
+ * @param target smart contract target
+ * @param value wei value of the transaction
+ * @param signature function signature of the transaction
+ * @param data function arguments of the transaction or callData if signature empty
+ * @param executionTime time at which to execute the transaction
+ * @param withDelegatecall boolean, true = transaction delegatecalls the target, else calls the target
+ **/
+ function cancelTransaction(
+ address target,
+ uint256 value,
+ string memory signature,
+ bytes memory data,
+ uint256 executionTime,
+ bool withDelegatecall
+ ) external returns (bytes32);
+}
diff --git a/packages/chain-events/eth/contracts/AAVE/interfaces/IGovernancePowerDelegationToken.sol b/packages/chain-events/eth/contracts/AAVE/interfaces/IGovernancePowerDelegationToken.sol
new file mode 100644
index 00000000000..59ebffa34bc
--- /dev/null
+++ b/packages/chain-events/eth/contracts/AAVE/interfaces/IGovernancePowerDelegationToken.sol
@@ -0,0 +1,18 @@
+// SPDX-License-Identifier: agpl-3.0
+pragma solidity 0.7.5;
+pragma abicoder v2;
+
+interface IGovernancePowerDelegationToken {
+ enum DelegationType {VOTING_POWER, PROPOSITION_POWER}
+ /**
+ * @dev get the power of a user at a specified block
+ * @param user address of the user
+ * @param blockNumber block number at which to get power
+ * @param delegationType delegation type (propose/vote)
+ **/
+ function getPowerAtBlock(
+ address user,
+ uint256 blockNumber,
+ DelegationType delegationType
+ ) external view returns (uint256);
+}
diff --git a/packages/chain-events/eth/contracts/AAVE/interfaces/IGovernanceStrategy.sol b/packages/chain-events/eth/contracts/AAVE/interfaces/IGovernanceStrategy.sol
new file mode 100644
index 00000000000..8c6ffee460a
--- /dev/null
+++ b/packages/chain-events/eth/contracts/AAVE/interfaces/IGovernanceStrategy.sol
@@ -0,0 +1,32 @@
+// SPDX-License-Identifier: agpl-3.0
+pragma solidity 0.7.5;
+pragma abicoder v2;
+
+interface IGovernanceStrategy {
+ /**
+ * @dev Returns the Proposition Power of a user at a specific block number.
+ * @param user Address of the user.
+ * @param blockNumber Blocknumber at which to fetch Proposition Power
+ * @return Power number
+ **/
+ function getPropositionPowerAt(address user, uint256 blockNumber) external view returns (uint256);
+ /**
+ * @dev Returns the total supply of Outstanding Proposition Tokens
+ * @param blockNumber Blocknumber at which to evaluate
+ * @return total supply at blockNumber
+ **/
+ function getTotalPropositionSupplyAt(uint256 blockNumber) external view returns (uint256);
+ /**
+ * @dev Returns the total supply of Outstanding Voting Tokens
+ * @param blockNumber Blocknumber at which to evaluate
+ * @return total supply at blockNumber
+ **/
+ function getTotalVotingSupplyAt(uint256 blockNumber) external view returns (uint256);
+ /**
+ * @dev Returns the Vote Power of a user at a specific block number.
+ * @param user Address of the user.
+ * @param blockNumber Blocknumber at which to fetch Vote Power
+ * @return Vote number
+ **/
+ function getVotingPowerAt(address user, uint256 blockNumber) external view returns (uint256);
+}
diff --git a/packages/chain-events/eth/contracts/AAVE/interfaces/IProposalValidator.sol b/packages/chain-events/eth/contracts/AAVE/interfaces/IProposalValidator.sol
new file mode 100644
index 00000000000..4b995e8b3dc
--- /dev/null
+++ b/packages/chain-events/eth/contracts/AAVE/interfaces/IProposalValidator.sol
@@ -0,0 +1,132 @@
+// SPDX-License-Identifier: agpl-3.0
+pragma solidity 0.7.5;
+pragma abicoder v2;
+
+import {IAaveGovernanceV2} from './IAaveGovernanceV2.sol';
+
+interface IProposalValidator {
+
+ /**
+ * @dev Called to validate a proposal (e.g when creating new proposal in Governance)
+ * @param governance Governance Contract
+ * @param user Address of the proposal creator
+ * @param blockNumber Block Number against which to make the test (e.g proposal creation block -1).
+ * @return boolean, true if can be created
+ **/
+ function validateCreatorOfProposal(
+ IAaveGovernanceV2 governance,
+ address user,
+ uint256 blockNumber
+ ) external view returns (bool);
+
+ /**
+ * @dev Called to validate the cancellation of a proposal
+ * @param governance Governance Contract
+ * @param user Address of the proposal creator
+ * @param blockNumber Block Number against which to make the test (e.g proposal creation block -1).
+ * @return boolean, true if can be cancelled
+ **/
+ function validateProposalCancellation(
+ IAaveGovernanceV2 governance,
+ address user,
+ uint256 blockNumber
+ ) external view returns (bool);
+
+ /**
+ * @dev Returns whether a user has enough Proposition Power to make a proposal.
+ * @param governance Governance Contract
+ * @param user Address of the user to be challenged.
+ * @param blockNumber Block Number against which to make the challenge.
+ * @return true if user has enough power
+ **/
+ function isPropositionPowerEnough(
+ IAaveGovernanceV2 governance,
+ address user,
+ uint256 blockNumber
+ ) external view returns (bool);
+
+ /**
+ * @dev Returns the minimum Proposition Power needed to create a proposition.
+ * @param governance Governance Contract
+ * @param blockNumber Blocknumber at which to evaluate
+ * @return minimum Proposition Power needed
+ **/
+ function getMinimumPropositionPowerNeeded(IAaveGovernanceV2 governance, uint256 blockNumber)
+ external
+ view
+ returns (uint256);
+
+ /**
+ * @dev Returns whether a proposal passed or not
+ * @param governance Governance Contract
+ * @param proposalId Id of the proposal to set
+ * @return true if proposal passed
+ **/
+ function isProposalPassed(IAaveGovernanceV2 governance, uint256 proposalId)
+ external
+ view
+ returns (bool);
+
+ /**
+ * @dev Check whether a proposal has reached quorum, ie has enough FOR-voting-power
+ * Here quorum is not to understand as number of votes reached, but number of for-votes reached
+ * @param governance Governance Contract
+ * @param proposalId Id of the proposal to verify
+ * @return voting power needed for a proposal to pass
+ **/
+ function isQuorumValid(IAaveGovernanceV2 governance, uint256 proposalId)
+ external
+ view
+ returns (bool);
+
+ /**
+ * @dev Check whether a proposal has enough extra FOR-votes than AGAINST-votes
+ * FOR VOTES - AGAINST VOTES > VOTE_DIFFERENTIAL * voting supply
+ * @param governance Governance Contract
+ * @param proposalId Id of the proposal to verify
+ * @return true if enough For-Votes
+ **/
+ function isVoteDifferentialValid(IAaveGovernanceV2 governance, uint256 proposalId)
+ external
+ view
+ returns (bool);
+
+ /**
+ * @dev Calculates the minimum amount of Voting Power needed for a proposal to Pass
+ * @param votingSupply Total number of oustanding voting tokens
+ * @return voting power needed for a proposal to pass
+ **/
+ function getMinimumVotingPowerNeeded(uint256 votingSupply) external view returns (uint256);
+
+ /**
+ * @dev Get proposition threshold constant value
+ * @return the proposition threshold value (100 <=> 1%)
+ **/
+ function PROPOSITION_THRESHOLD() external view returns (uint256);
+
+ /**
+ * @dev Get voting duration constant value
+ * @return the voting duration value in seconds
+ **/
+ function VOTING_DURATION() external view returns (uint256);
+
+ /**
+ * @dev Get the vote differential threshold constant value
+ * to compare with % of for votes/total supply - % of against votes/total supply
+ * @return the vote differential threshold value (100 <=> 1%)
+ **/
+ function VOTE_DIFFERENTIAL() external view returns (uint256);
+
+ /**
+ * @dev Get quorum threshold constant value
+ * to compare with % of for votes/total supply
+ * @return the quorum threshold value (100 <=> 1%)
+ **/
+ function MINIMUM_QUORUM() external view returns (uint256);
+
+ /**
+ * @dev precision helper: 100% = 10000
+ * @return one hundred percents with our chosen precision
+ **/
+ function ONE_HUNDRED_WITH_PRECISION() external view returns (uint256);
+}
diff --git a/packages/chain-events/eth/contracts/AAVE/interfaces/IVotingStrategy.sol b/packages/chain-events/eth/contracts/AAVE/interfaces/IVotingStrategy.sol
new file mode 100644
index 00000000000..1edb11ddebd
--- /dev/null
+++ b/packages/chain-events/eth/contracts/AAVE/interfaces/IVotingStrategy.sol
@@ -0,0 +1,7 @@
+// SPDX-License-Identifier: agpl-3.0
+pragma solidity 0.7.5;
+pragma abicoder v2;
+
+interface IVotingStrategy {
+ function getVotingPowerAt(address user, uint256 blockNumber) external view returns (uint256);
+}
diff --git a/packages/chain-events/eth/contracts/AAVE/misc/Helpers.sol b/packages/chain-events/eth/contracts/AAVE/misc/Helpers.sol
new file mode 100644
index 00000000000..dcd8fc627ae
--- /dev/null
+++ b/packages/chain-events/eth/contracts/AAVE/misc/Helpers.sol
@@ -0,0 +1,24 @@
+// SPDX-License-Identifier: agpl-3.0
+pragma solidity 0.7.5;
+pragma abicoder v2;
+
+function getChainId() pure returns (uint256) {
+ uint256 chainId;
+ assembly {
+ chainId := chainid()
+ }
+ return chainId;
+}
+
+function isContract(address account) view returns (bool) {
+ // According to EIP-1052, 0x0 is the value returned for not-yet created accounts
+ // and 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470 is returned
+ // for accounts without code, i.e. `keccak256('')`
+ bytes32 codehash;
+ bytes32 accountHash = 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470;
+ // solhint-disable-next-line no-inline-assembly
+ assembly {
+ codehash := extcodehash(account)
+ }
+ return (codehash != accountHash && codehash != 0x0);
+}
diff --git a/packages/chain-events/eth/contracts/AAVE/mocks/AaveTokenV1Mock.sol b/packages/chain-events/eth/contracts/AAVE/mocks/AaveTokenV1Mock.sol
new file mode 100644
index 00000000000..2aadb98ca27
--- /dev/null
+++ b/packages/chain-events/eth/contracts/AAVE/mocks/AaveTokenV1Mock.sol
@@ -0,0 +1,32 @@
+import {AaveToken} from '@aave/aave-token/contracts/token/AaveToken.sol';
+
+contract AaveTokenV1Mock is AaveToken {
+ /**
+ * @dev initializes the contract upon assignment to the InitializableAdminUpgradeabilityProxy
+ * @param minter the address of the LEND -> AAVE migration contract
+ */
+ function initialize(address minter) external initializer {
+ uint256 chainId;
+
+ //solium-disable-next-line
+ assembly {
+ chainId := chainid()
+ }
+
+ DOMAIN_SEPARATOR = keccak256(
+ abi.encode(
+ EIP712_DOMAIN,
+ keccak256(bytes(NAME)),
+ keccak256(EIP712_REVISION),
+ chainId,
+ address(this)
+ )
+ );
+ _name = NAME;
+ _symbol = SYMBOL;
+ _setupDecimals(DECIMALS);
+ // _aaveGovernance = aaveGovernance;
+ _mint(minter, MIGRATION_AMOUNT);
+ _mint(minter, DISTRIBUTION_AMOUNT);
+ }
+}
diff --git a/packages/chain-events/eth/contracts/AAVE/mocks/AaveTokenV2Mock.sol b/packages/chain-events/eth/contracts/AAVE/mocks/AaveTokenV2Mock.sol
new file mode 100644
index 00000000000..5c56367eac5
--- /dev/null
+++ b/packages/chain-events/eth/contracts/AAVE/mocks/AaveTokenV2Mock.sol
@@ -0,0 +1,7 @@
+import {AaveTokenV2} from '@aave/aave-token/contracts/token/AaveTokenV2.sol';
+
+contract AaveTokenV2Mock is AaveTokenV2 {
+ function mint(address minter, uint256 amount) external {
+ _mint(minter, amount);
+ }
+}
diff --git a/packages/chain-events/eth/contracts/AAVE/mocks/FlashAttacks.sol b/packages/chain-events/eth/contracts/AAVE/mocks/FlashAttacks.sol
new file mode 100644
index 00000000000..fe26ecd25b0
--- /dev/null
+++ b/packages/chain-events/eth/contracts/AAVE/mocks/FlashAttacks.sol
@@ -0,0 +1,49 @@
+// SPDX-License-Identifier: agpl-3.0
+pragma solidity 0.7.5;
+pragma abicoder v2;
+
+import {IAaveGovernanceV2} from '../interfaces/IAaveGovernanceV2.sol';
+import {IERC20} from './interfaces/IERC20.sol';
+import {IExecutorWithTimelock} from '../interfaces/IExecutorWithTimelock.sol';
+
+
+contract FlashAttacks {
+
+ IERC20 internal immutable TOKEN;
+ address internal immutable MINTER;
+ IAaveGovernanceV2 internal immutable GOV;
+
+ constructor(address _token, address _MINTER, address _governance) {
+ TOKEN = IERC20(_token);
+ MINTER = _MINTER;
+ GOV = IAaveGovernanceV2(_governance);
+ }
+
+ function flashVote(uint256 votePower, uint256 proposalId, bool support) external {
+ TOKEN.transferFrom(MINTER,address(this), votePower);
+ GOV.submitVote(proposalId, support);
+ TOKEN.transfer(MINTER, votePower);
+ }
+
+ function flashVotePermit(uint256 votePower, uint256 proposalId,
+ bool support,
+ uint8 v,
+ bytes32 r,
+ bytes32 s) external {
+ TOKEN.transferFrom(MINTER, address(this), votePower);
+ GOV.submitVoteBySignature(proposalId, support, v, r, s);
+ TOKEN.transfer(MINTER, votePower);
+ }
+
+ function flashProposal(uint256 proposalPower, IExecutorWithTimelock executor,
+ address[] memory targets,
+ uint256[] memory values,
+ string[] memory signatures,
+ bytes[] memory calldatas,
+ bool[] memory withDelegatecalls,
+ bytes32 ipfsHash) external {
+ TOKEN.transferFrom(MINTER, address(this),proposalPower);
+ GOV.create(executor, targets, values, signatures, calldatas, withDelegatecalls, ipfsHash);
+ TOKEN.transfer(MINTER, proposalPower);
+ }
+}
diff --git a/packages/chain-events/eth/contracts/AAVE/mocks/SelfdestructTransfer.sol b/packages/chain-events/eth/contracts/AAVE/mocks/SelfdestructTransfer.sol
new file mode 100644
index 00000000000..6fe8e0595b9
--- /dev/null
+++ b/packages/chain-events/eth/contracts/AAVE/mocks/SelfdestructTransfer.sol
@@ -0,0 +1,8 @@
+// SPDX-License-Identifier: agpl-3.0
+pragma solidity 0.6.12;
+
+contract SelfdestructTransfer {
+ function destroyAndTransfer(address payable to) external payable {
+ selfdestruct(to);
+ }
+}
diff --git a/packages/chain-events/eth/contracts/AAVE/mocks/interfaces/IERC20.sol b/packages/chain-events/eth/contracts/AAVE/mocks/interfaces/IERC20.sol
new file mode 100644
index 00000000000..e4619fc279b
--- /dev/null
+++ b/packages/chain-events/eth/contracts/AAVE/mocks/interfaces/IERC20.sol
@@ -0,0 +1,17 @@
+// SPDX-License-Identifier: agpl-3.0
+pragma solidity 0.7.5;
+pragma abicoder v2;
+
+interface IERC20 {
+ function totalSupplyAt(uint256 blockNumber) external view returns (uint256);
+
+ function balanceOf(address account) external view returns (uint256);
+
+ function transferFrom(
+ address sender,
+ address recipient,
+ uint256 amount
+ ) external returns (bool);
+
+ function transfer(address recipient, uint256 amount) external returns (bool);
+}
diff --git a/packages/chain-events/eth/contracts/Commonwealth/DataTypes.sol b/packages/chain-events/eth/contracts/Commonwealth/DataTypes.sol
new file mode 100644
index 00000000000..728a7a0407d
--- /dev/null
+++ b/packages/chain-events/eth/contracts/Commonwealth/DataTypes.sol
@@ -0,0 +1,36 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.0;
+
+////////////////////////////////////////////////////////////////////////////////////////////
+/// @title DataTypes
+////////////////////////////////////////////////////////////////////////////////////////////
+
+// this order of variables optimizes gas by using the least amount of 32 byte storage spaces as possible
+library DataTypes {
+ struct ProjectMetaData {
+ uint256 id;
+ bytes32 name;
+ bytes32 ipfsHash;
+ bytes32 url;
+ address creator;
+ }
+
+ struct ProtocolData {
+ // /// @notice The protocol fee percentage at time of project creation
+ uint8 fee;
+ // // @notice The address to send the protocol fee to
+ address feeTo;
+ }
+
+ struct ProjectData {
+ // /// @notice Minimum value for a project to be successful
+ uint256 threshold;
+
+ // /// @notice Deadline by which project must meet funding threshold
+ uint256 deadline; // uint24 max val = 6.9 years
+ // // @notice Address to which all funds will be withdrawn to if the project is funded
+ address beneficiary;
+ // // @notice The only token this project accepts for funding
+ address acceptedToken;
+ }
+}
diff --git a/packages/chain-events/eth/contracts/Commonwealth/ICuratedProject.sol b/packages/chain-events/eth/contracts/Commonwealth/ICuratedProject.sol
new file mode 100644
index 00000000000..6c907d2107c
--- /dev/null
+++ b/packages/chain-events/eth/contracts/Commonwealth/ICuratedProject.sol
@@ -0,0 +1,31 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.0;
+
+import "./IProjectBase.sol";
+
+interface ICuratedProject is IProjectBase {
+ event Curate(address indexed sender, address indexed token, uint256 amount);
+
+ function bToken() external view returns (address);
+
+ function cToken() external view returns (address);
+
+ function totalCuratorFunding() external view returns (uint256);
+
+ function curatorFee() external view returns (uint256);
+
+ function initialize(
+ DataTypes.ProjectMetaData memory _metaData,
+ DataTypes.ProjectData memory _projectData,
+ DataTypes.ProtocolData memory _protocolData,
+ uint256 _curatorFee,
+ address _bToken,
+ address _cToken
+ ) external returns (bool);
+
+ function curate(uint256 _amount) external returns (bool);
+
+ function curatorsWithdraw() external returns (bool);
+
+ function withdrawRemaining() external view returns (uint256);
+}
diff --git a/packages/chain-events/eth/contracts/Commonwealth/ICuratedProjectFactory.sol b/packages/chain-events/eth/contracts/Commonwealth/ICuratedProjectFactory.sol
new file mode 100644
index 00000000000..732c61abc99
--- /dev/null
+++ b/packages/chain-events/eth/contracts/Commonwealth/ICuratedProjectFactory.sol
@@ -0,0 +1,19 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.0;
+
+import "./IProjectBaseFactory.sol";
+
+interface ICuratedProjectFactory is IProjectBaseFactory {
+ function setCmnProjTokenImpl(address _cmnProjTokenImpl) external;
+
+ function createProject(
+ bytes32 _name,
+ bytes32 _ipfsHash,
+ bytes32 _url,
+ address _beneficiary,
+ address _acceptedToken,
+ uint256 _threshold,
+ uint256 _deadline,
+ uint256 _curatorFee
+ ) external returns (address);
+}
diff --git a/packages/chain-events/eth/contracts/Commonwealth/IProjectBase.sol b/packages/chain-events/eth/contracts/Commonwealth/IProjectBase.sol
new file mode 100644
index 00000000000..782b4bdf7fa
--- /dev/null
+++ b/packages/chain-events/eth/contracts/Commonwealth/IProjectBase.sol
@@ -0,0 +1,43 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.0;
+
+import {DataTypes} from './DataTypes.sol';
+
+interface IProjectBase {
+ event Back(address indexed sender, address indexed token, uint256 amount);
+ event Withdraw(address indexed sender, address indexed token, uint256 amount, bytes32 withdrawalType);
+ event Succeeded(uint256 timestamp, uint256 amount);
+ event Failed();
+ event ProjectDataChange(bytes32 name, bytes32 oldData, bytes32 newData);
+
+ ///////////////////////////////////////////
+ // Getters - view functions
+ //////////////////////////////////////////
+ function metaData() external view returns (DataTypes.ProjectMetaData memory);
+
+ function projectData() external view returns (DataTypes.ProjectData memory);
+
+ function protocolData() external view returns (DataTypes.ProtocolData memory);
+
+ function totalFunding() external view returns (uint256);
+
+ function lockedWithdraw() external view returns (bool);
+
+ function funded() external view returns (bool);
+
+ ///////////////////////////////////////////
+ // functions
+ //////////////////////////////////////////
+
+ function setName(bytes32 _name) external;
+
+ function setIpfsHash(bytes32 _ipfsHash) external;
+
+ function setUrl(bytes32 _url) external;
+
+ function back(uint256 _amount) external returns (bool);
+
+ function beneficiaryWithdraw() external returns (bool);
+
+ function backersWithdraw() external returns (bool);
+}
diff --git a/packages/chain-events/eth/contracts/Commonwealth/IProjectBaseFactory.sol b/packages/chain-events/eth/contracts/Commonwealth/IProjectBaseFactory.sol
new file mode 100644
index 00000000000..161a41ae6be
--- /dev/null
+++ b/packages/chain-events/eth/contracts/Commonwealth/IProjectBaseFactory.sol
@@ -0,0 +1,32 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.0;
+
+import './DataTypes.sol';
+
+interface IProjectBaseFactory {
+ event ProjectCreated(uint256 projectIndex, address projectAddress);
+ event ProtocolFeeToChange(address oldAddr, address newAddr);
+ event ProtocolFeeChange(uint8 oldFee, uint8 newFee);
+ event ProjectImplChange(address oldAddr, address newAddr);
+ event ProtocolTokenImplChange(address oldAddr, address newAddr);
+
+ function protocolData() external view returns (DataTypes.ProtocolData memory);
+
+ function owner() external view returns (address);
+
+ function projectImp() external view returns (address);
+
+ function projects(uint32 projectIndex) external view returns (address);
+
+ function isAcceptedToken(address token) external view returns (bool);
+
+ function numProjects() external view returns (uint32);
+
+ function addAcceptedTokens(address[] memory _tokens) external;
+
+ function setFeeTo(address _feeTo) external;
+
+ function setProtocolFee(uint8 _protocolFee) external;
+
+ function setProjectImpl(address _projectImpl) external;
+}
diff --git a/packages/chain-events/eth/contracts/Compound/ERC20VotesMock.sol b/packages/chain-events/eth/contracts/Compound/ERC20VotesMock.sol
new file mode 100644
index 00000000000..342824bc4c7
--- /dev/null
+++ b/packages/chain-events/eth/contracts/Compound/ERC20VotesMock.sol
@@ -0,0 +1,21 @@
+// SPDX-License-Identifier: MIT
+
+pragma solidity ^0.8.0;
+
+import "@openzeppelin/contracts-governance/token/ERC20/extensions/ERC20Votes.sol";
+
+contract ERC20VotesMock is ERC20Votes {
+ constructor(string memory name, string memory symbol) ERC20(name, symbol) ERC20Permit(name) {}
+
+ function mint(address account, uint256 amount) public {
+ _mint(account, amount);
+ }
+
+ function burn(address account, uint256 amount) public {
+ _burn(account, amount);
+ }
+
+ function getChainId() external view returns (uint256) {
+ return block.chainid;
+ }
+}
\ No newline at end of file
diff --git a/packages/chain-events/eth/contracts/Compound/FeiDao.sol b/packages/chain-events/eth/contracts/Compound/FeiDao.sol
new file mode 100644
index 00000000000..8c8c939bc76
--- /dev/null
+++ b/packages/chain-events/eth/contracts/Compound/FeiDao.sol
@@ -0,0 +1,206 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+pragma solidity ^0.8.4;
+
+import '@openzeppelin/contracts-governance/governance/compatibility/GovernorCompatibilityBravo.sol';
+import '@openzeppelin/contracts-governance/governance/extensions/GovernorTimelockCompound.sol';
+import '@openzeppelin/contracts-governance/governance/extensions/GovernorVotesComp.sol';
+import '@openzeppelin/contracts-governance/token/ERC20/extensions/ERC20VotesComp.sol';
+
+// Forked functionality from https://github.com/unlock-protocol/unlock/blob/master/smart-contracts/contracts/UnlockProtocolGovernor.sol
+
+contract FeiDAO is
+ GovernorCompatibilityBravo,
+ GovernorVotesComp,
+ GovernorTimelockCompound
+{
+ uint256 private _votingDelay = 1; // reduce voting delay to 1 block
+ uint256 private _votingPeriod = 13000; // extend voting period to 48h
+ uint256 private _quorum = 25_000_000e18;
+ uint256 private _proposalThreshold = 2_500_000e18;
+
+ address private _guardian;
+ uint256 private _eta;
+ address public constant BACKUP_GOVERNOR =
+ 0x4C895973334Af8E06fd6dA4f723Ac24A5f259e6B;
+ uint256 public constant ROLLBACK_DEADLINE = 1635724800; // Nov 1, 2021 midnight UTC
+
+ constructor(
+ ERC20VotesComp tribe,
+ ICompoundTimelock timelock,
+ address guardian
+ )
+ GovernorVotesComp(tribe)
+ GovernorTimelockCompound(timelock)
+ Governor('Fei DAO')
+ {
+ _guardian = guardian;
+ }
+
+ /*
+ * Events to track params changes
+ */
+ event QuorumUpdated(uint256 oldQuorum, uint256 newQuorum);
+ event VotingDelayUpdated(uint256 oldVotingDelay, uint256 newVotingDelay);
+ event VotingPeriodUpdated(uint256 oldVotingPeriod, uint256 newVotingPeriod);
+ event ProposalThresholdUpdated(
+ uint256 oldProposalThreshold,
+ uint256 newProposalThreshold
+ );
+ event RollbackQueued(uint256 eta);
+ event Rollback();
+
+ function votingDelay() public view override returns (uint256) {
+ return _votingDelay;
+ }
+
+ function votingPeriod() public view override returns (uint256) {
+ return _votingPeriod;
+ }
+
+ function quorum(uint256) public view override returns (uint256) {
+ return _quorum;
+ }
+
+ function proposalThreshold() public view override returns (uint256) {
+ return _proposalThreshold;
+ }
+
+ // governance setters
+ function setVotingDelay(uint256 newVotingDelay) public onlyGovernance {
+ uint256 oldVotingDelay = _votingDelay;
+ _votingDelay = newVotingDelay;
+ emit VotingDelayUpdated(oldVotingDelay, newVotingDelay);
+ }
+
+ function setVotingPeriod(uint256 newVotingPeriod) public onlyGovernance {
+ uint256 oldVotingPeriod = _votingPeriod;
+ _votingPeriod = newVotingPeriod;
+ emit VotingPeriodUpdated(oldVotingPeriod, newVotingPeriod);
+ }
+
+ function setQuorum(uint256 newQuorum) public onlyGovernance {
+ uint256 oldQuorum = _quorum;
+ _quorum = newQuorum;
+ emit QuorumUpdated(oldQuorum, newQuorum);
+ }
+
+ function setProposalThreshold(uint256 newProposalThreshold)
+ public
+ onlyGovernance
+ {
+ uint256 oldProposalThreshold = _proposalThreshold;
+ _proposalThreshold = newProposalThreshold;
+ emit ProposalThresholdUpdated(
+ oldProposalThreshold,
+ newProposalThreshold
+ );
+ }
+
+ /// @notice one-time option to roll back the DAO to old GovernorAlpha
+ /// @dev guardian-only, and expires after the deadline. This function is here as a fallback in case something goes wrong.
+ function __rollback(uint256 eta) external {
+ require(msg.sender == _guardian, 'FeiDAO: caller not guardian');
+ // Deleting guardian prevents multiple triggers of this function
+ _guardian = address(0);
+
+ require(eta <= ROLLBACK_DEADLINE, 'FeiDAO: rollback expired');
+ _eta = eta;
+
+ ICompoundTimelock _timelock = ICompoundTimelock(payable(timelock()));
+ _timelock.queueTransaction(
+ timelock(),
+ 0,
+ 'setPendingAdmin(address)',
+ abi.encode(BACKUP_GOVERNOR),
+ eta
+ );
+
+ emit RollbackQueued(eta);
+ }
+
+ /// @notice complete the rollback
+ function __executeRollback() external {
+ require(_eta <= block.timestamp, 'FeiDAO: too soon');
+ require(_guardian == address(0), 'FeiDAO: no queue');
+
+ ICompoundTimelock _timelock = ICompoundTimelock(payable(timelock()));
+ _timelock.executeTransaction(
+ timelock(),
+ 0,
+ 'setPendingAdmin(address)',
+ abi.encode(BACKUP_GOVERNOR),
+ _eta
+ );
+
+ emit Rollback();
+ }
+
+ // The following functions are overrides required by Solidity.
+ function getVotes(address account, uint256 blockNumber)
+ public
+ view
+ override(IGovernor, GovernorVotesComp)
+ returns (uint256)
+ {
+ return super.getVotes(account, blockNumber);
+ }
+
+ function state(uint256 proposalId)
+ public
+ view
+ override(IGovernor, Governor, GovernorTimelockCompound)
+ returns (ProposalState)
+ {
+ return super.state(proposalId);
+ }
+
+ function propose(
+ address[] memory targets,
+ uint256[] memory values,
+ bytes[] memory calldatas,
+ string memory description
+ )
+ public
+ override(IGovernor, Governor, GovernorCompatibilityBravo)
+ returns (uint256)
+ {
+ return super.propose(targets, values, calldatas, description);
+ }
+
+ function _execute(
+ uint256 proposalId,
+ address[] memory targets,
+ uint256[] memory values,
+ bytes[] memory calldatas,
+ bytes32 descriptionHash
+ ) internal override(Governor, GovernorTimelockCompound) {
+ super._execute(proposalId, targets, values, calldatas, descriptionHash);
+ }
+
+ function _cancel(
+ address[] memory targets,
+ uint256[] memory values,
+ bytes[] memory calldatas,
+ bytes32 descriptionHash
+ ) internal override(Governor, GovernorTimelockCompound) returns (uint256) {
+ return super._cancel(targets, values, calldatas, descriptionHash);
+ }
+
+ function _executor()
+ internal
+ view
+ override(Governor, GovernorTimelockCompound)
+ returns (address)
+ {
+ return super._executor();
+ }
+
+ function supportsInterface(bytes4 interfaceId)
+ public
+ view
+ override(IERC165, Governor, GovernorTimelockCompound)
+ returns (bool)
+ {
+ return super.supportsInterface(interfaceId);
+ }
+}
diff --git a/packages/chain-events/eth/contracts/Compound/GovernorAlpha.sol b/packages/chain-events/eth/contracts/Compound/GovernorAlpha.sol
new file mode 100644
index 00000000000..c131cc3489d
--- /dev/null
+++ b/packages/chain-events/eth/contracts/Compound/GovernorAlpha.sol
@@ -0,0 +1,547 @@
+pragma solidity >=0.4.21 <0.7.0;
+pragma experimental ABIEncoderV2;
+
+
+contract GovernorAlpha {
+ /// @notice The name of this contract
+ string public constant name = "Marlin Governor Alpha";
+
+ /// @notice The number of votes in support of a proposal required in order for a quorum to be reached and for a vote to succeed
+ function quorumVotes() public pure returns (uint256) {
+ return 8000e18;
+ } // 8000 MPond
+
+ /// @notice The number of votes required in order for a voter to become a proposer
+ function proposalThreshold() public pure returns (uint256) {
+ return 1e18;
+ } // 1 MPond
+
+ /// @notice The maximum number of actions that can be included in a proposal
+ function proposalMaxOperations() public pure returns (uint256) {
+ return 10;
+ } // 10 actions
+
+ /// @notice The delay before voting on a proposal may take place, once proposed
+ function votingDelay() public pure returns (uint256) {
+ return 1;
+ } // 1 block
+
+ /// @notice The duration of voting on a proposal, in blocks
+ function votingPeriod() public pure returns (uint256) {
+ return 17200;
+ } // ~3 days in blocks (assuming 15s blocks)
+
+ /// @notice The address of the MPond Protocol Timelock
+ TimelockInterface public timelock;
+
+ /// @notice The address of the MPond governance token
+ MPondInterface public MPond;
+
+ /// @notice The address of the Governor Guardian
+ address public guardian;
+
+ /// @notice The total number of proposals
+ uint256 public proposalCount;
+
+ struct Proposal {
+ /// @notice Unique id for looking up a proposal
+ uint256 id;
+ /// @notice Creator of the proposal
+ address proposer;
+ /// @notice The timestamp that the proposal will be available for execution, set once the vote succeeds
+ uint256 eta;
+ /// @notice the ordered list of target addresses for calls to be made
+ address[] targets;
+ /// @notice The ordered list of values (i.e. msg.value) to be passed to the calls to be made
+ uint256[] values;
+ /// @notice The ordered list of function signatures to be called
+ string[] signatures;
+ /// @notice The ordered list of calldata to be passed to each call
+ bytes[] calldatas;
+ /// @notice The block at which voting begins: holders must delegate their votes prior to this block
+ uint256 startBlock;
+ /// @notice The block at which voting ends: votes must be cast prior to this block
+ uint256 endBlock;
+ /// @notice Current number of votes in favor of this proposal
+ uint256 forVotes;
+ /// @notice Current number of votes in opposition to this proposal
+ uint256 againstVotes;
+ /// @notice Flag marking whether the proposal has been canceled
+ bool canceled;
+ /// @notice Flag marking whether the proposal has been executed
+ bool executed;
+ /// @notice Receipts of ballots for the entire set of voters
+ mapping(address => Receipt) receipts;
+ }
+
+ /// @notice Ballot receipt record for a voter
+ struct Receipt {
+ /// @notice Whether or not a vote has been cast
+ bool hasVoted;
+ /// @notice Whether or not the voter supports the proposal
+ bool support;
+ /// @notice The number of votes the voter had, which were cast
+ uint96 votes;
+ }
+
+ /// @notice Possible states that a proposal may be in
+ enum ProposalState {
+ Pending,
+ Active,
+ Canceled,
+ Defeated,
+ Succeeded,
+ Queued,
+ Expired,
+ Executed
+ }
+
+ /// @notice The official record of all proposals ever proposed
+ mapping(uint256 => Proposal) public proposals;
+
+ /// @notice The latest proposal for each proposer
+ mapping(address => uint256) public latestProposalIds;
+
+ /// @notice The EIP-712 typehash for the contract's domain
+ bytes32 public constant DOMAIN_TYPEHASH = keccak256(
+ "EIP712Domain(string name,uint256 chainId,address verifyingContract)"
+ );
+
+ /// @notice The EIP-712 typehash for the ballot struct used by the contract
+ bytes32 public constant BALLOT_TYPEHASH = keccak256(
+ "Ballot(uint256 proposalId,bool support)"
+ );
+
+ /// @notice An event emitted when a new proposal is created
+ event ProposalCreated(
+ uint256 id,
+ address proposer,
+ address[] targets,
+ uint256[] values,
+ string[] signatures,
+ bytes[] calldatas,
+ uint256 startBlock,
+ uint256 endBlock,
+ string description
+ );
+
+ /// @notice An event emitted when a vote has been cast on a proposal
+ event VoteCast(
+ address voter,
+ uint256 proposalId,
+ bool support,
+ uint256 votes
+ );
+
+ /// @notice An event emitted when a proposal has been canceled
+ event ProposalCanceled(uint256 id);
+
+ /// @notice An event emitted when a proposal has been queued in the Timelock
+ event ProposalQueued(uint256 id, uint256 eta);
+
+ /// @notice An event emitted when a proposal has been executed in the Timelock
+ event ProposalExecuted(uint256 id);
+
+ constructor(
+ address timelock_,
+ address MPond_,
+ address guardian_
+ ) public {
+ timelock = TimelockInterface(timelock_);
+ MPond = MPondInterface(MPond_);
+ guardian = guardian_;
+ }
+
+ function propose(
+ address[] memory targets,
+ uint256[] memory values,
+ string[] memory signatures,
+ bytes[] memory calldatas,
+ string memory description
+ ) public returns (uint256) {
+ require(
+ MPond.getPriorVotes(msg.sender, sub256(block.number, 1)) >
+ proposalThreshold(),
+ "GovernorAlpha::propose: proposer votes below proposal threshold"
+ );
+ require(
+ targets.length == values.length &&
+ targets.length == signatures.length &&
+ targets.length == calldatas.length,
+ "GovernorAlpha::propose: proposal function information arity mismatch"
+ );
+ require(
+ targets.length != 0,
+ "GovernorAlpha::propose: must provide actions"
+ );
+ require(
+ targets.length <= proposalMaxOperations(),
+ "GovernorAlpha::propose: too many actions"
+ );
+
+ uint256 latestProposalId = latestProposalIds[msg.sender];
+ if (latestProposalId != 0) {
+ ProposalState proposersLatestProposalState = state(
+ latestProposalId
+ );
+ require(
+ proposersLatestProposalState != ProposalState.Active,
+ "GovernorAlpha::propose: one live proposal per proposer, found an already active proposal"
+ );
+ require(
+ proposersLatestProposalState != ProposalState.Pending,
+ "GovernorAlpha::propose: one live proposal per proposer, found an already pending proposal"
+ );
+ }
+
+ uint256 startBlock = add256(block.number, votingDelay());
+ uint256 endBlock = add256(startBlock, votingPeriod());
+
+ proposalCount++;
+ Proposal memory newProposal = Proposal({
+ id: proposalCount,
+ proposer: msg.sender,
+ eta: 0,
+ targets: targets,
+ values: values,
+ signatures: signatures,
+ calldatas: calldatas,
+ startBlock: startBlock,
+ endBlock: endBlock,
+ forVotes: 0,
+ againstVotes: 0,
+ canceled: false,
+ executed: false
+ });
+
+ proposals[newProposal.id] = newProposal;
+ latestProposalIds[newProposal.proposer] = newProposal.id;
+
+ emit ProposalCreated(
+ newProposal.id,
+ msg.sender,
+ targets,
+ values,
+ signatures,
+ calldatas,
+ startBlock,
+ endBlock,
+ description
+ );
+ return newProposal.id;
+ }
+
+ function queue(uint256 proposalId) public {
+ require(
+ state(proposalId) == ProposalState.Succeeded,
+ "GovernorAlpha::queue: proposal can only be queued if it is succeeded"
+ );
+ Proposal storage proposal = proposals[proposalId];
+ uint256 eta = add256(block.timestamp, timelock.delay());
+ for (uint256 i = 0; i < proposal.targets.length; i++) {
+ _queueOrRevert(
+ proposal.targets[i],
+ proposal.values[i],
+ proposal.signatures[i],
+ proposal.calldatas[i],
+ eta
+ );
+ }
+ proposal.eta = eta;
+ emit ProposalQueued(proposalId, eta);
+ }
+
+ function _queueOrRevert(
+ address target,
+ uint256 value,
+ string memory signature,
+ bytes memory data,
+ uint256 eta
+ ) internal {
+ require(
+ !timelock.queuedTransactions(
+ keccak256(abi.encode(target, value, signature, data, eta))
+ ),
+ "GovernorAlpha::_queueOrRevert: proposal action already queued at eta"
+ );
+ timelock.queueTransaction(target, value, signature, data, eta);
+ }
+
+ function execute(uint256 proposalId) public payable {
+ require(
+ state(proposalId) == ProposalState.Queued,
+ "GovernorAlpha::execute: proposal can only be executed if it is queued"
+ );
+ Proposal storage proposal = proposals[proposalId];
+ proposal.executed = true;
+ for (uint256 i = 0; i < proposal.targets.length; i++) {
+ timelock.executeTransaction.value(proposal.values[i])(
+ proposal.targets[i],
+ proposal.values[i],
+ proposal.signatures[i],
+ proposal.calldatas[i],
+ proposal.eta
+ );
+ }
+ emit ProposalExecuted(proposalId);
+ }
+
+ function cancel(uint256 proposalId) public {
+ ProposalState state = state(proposalId);
+ require(
+ state != ProposalState.Executed,
+ "GovernorAlpha::cancel: cannot cancel executed proposal"
+ );
+
+ Proposal storage proposal = proposals[proposalId];
+ require(
+ msg.sender == guardian ||
+ MPond.getPriorVotes(
+ proposal.proposer,
+ sub256(block.number, 1)
+ ) <
+ proposalThreshold(),
+ "GovernorAlpha::cancel: proposer above threshold"
+ );
+
+ proposal.canceled = true;
+ for (uint256 i = 0; i < proposal.targets.length; i++) {
+ timelock.cancelTransaction(
+ proposal.targets[i],
+ proposal.values[i],
+ proposal.signatures[i],
+ proposal.calldatas[i],
+ proposal.eta
+ );
+ }
+
+ emit ProposalCanceled(proposalId);
+ }
+
+ function getActions(uint256 proposalId)
+ public
+ view
+ returns (
+ address[] memory targets,
+ uint256[] memory values,
+ string[] memory signatures,
+ bytes[] memory calldatas
+ )
+ {
+ Proposal storage p = proposals[proposalId];
+ return (p.targets, p.values, p.signatures, p.calldatas);
+ }
+
+ function getReceipt(uint256 proposalId, address voter)
+ public
+ view
+ returns (Receipt memory)
+ {
+ return proposals[proposalId].receipts[voter];
+ }
+
+ function state(uint256 proposalId) public view returns (ProposalState) {
+ require(
+ proposalCount >= proposalId && proposalId > 0,
+ "GovernorAlpha::state: invalid proposal id"
+ );
+ Proposal storage proposal = proposals[proposalId];
+ if (proposal.canceled) {
+ return ProposalState.Canceled;
+ } else if (block.number <= proposal.startBlock) {
+ return ProposalState.Pending;
+ } else if (block.number <= proposal.endBlock) {
+ return ProposalState.Active;
+ } else if (
+ proposal.forVotes <= proposal.againstVotes ||
+ proposal.forVotes < quorumVotes()
+ ) {
+ return ProposalState.Defeated;
+ } else if (proposal.eta == 0) {
+ return ProposalState.Succeeded;
+ } else if (proposal.executed) {
+ return ProposalState.Executed;
+ } else if (
+ block.timestamp >= add256(proposal.eta, timelock.GRACE_PERIOD())
+ ) {
+ return ProposalState.Expired;
+ } else {
+ return ProposalState.Queued;
+ }
+ }
+
+ function castVote(uint256 proposalId, bool support) public {
+ return _castVote(msg.sender, proposalId, support);
+ }
+
+ function castVoteBySig(
+ uint256 proposalId,
+ bool support,
+ uint8 v,
+ bytes32 r,
+ bytes32 s
+ ) public {
+ bytes32 domainSeparator = keccak256(
+ abi.encode(
+ DOMAIN_TYPEHASH,
+ keccak256(bytes(name)),
+ getChainId(),
+ address(this)
+ )
+ );
+ bytes32 structHash = keccak256(
+ abi.encode(BALLOT_TYPEHASH, proposalId, support)
+ );
+ bytes32 digest = keccak256(
+ abi.encodePacked("\x19\x01", domainSeparator, structHash)
+ );
+ address signatory = ecrecover(digest, v, r, s);
+ require(
+ signatory != address(0),
+ "GovernorAlpha::castVoteBySig: invalid signature"
+ );
+ return _castVote(signatory, proposalId, support);
+ }
+
+ function _castVote(
+ address voter,
+ uint256 proposalId,
+ bool support
+ ) internal {
+ require(
+ state(proposalId) == ProposalState.Active,
+ "GovernorAlpha::_castVote: voting is closed"
+ );
+ Proposal storage proposal = proposals[proposalId];
+ Receipt storage receipt = proposal.receipts[voter];
+ require(
+ receipt.hasVoted == false,
+ "GovernorAlpha::_castVote: voter already voted"
+ );
+ uint96 votes = MPond.getPriorVotes(voter, proposal.startBlock);
+
+ if (support) {
+ proposal.forVotes = add256(proposal.forVotes, votes);
+ } else {
+ proposal.againstVotes = add256(proposal.againstVotes, votes);
+ }
+
+ receipt.hasVoted = true;
+ receipt.support = support;
+ receipt.votes = votes;
+
+ emit VoteCast(voter, proposalId, support, votes);
+ }
+
+ function __acceptAdmin() public {
+ require(
+ msg.sender == guardian,
+ "GovernorAlpha::__acceptAdmin: sender must be gov guardian"
+ );
+ timelock.acceptAdmin();
+ }
+
+ function __abdicate() public {
+ require(
+ msg.sender == guardian,
+ "GovernorAlpha::__abdicate: sender must be gov guardian"
+ );
+ guardian = address(0);
+ }
+
+ function __queueSetTimelockPendingAdmin(
+ address newPendingAdmin,
+ uint256 eta
+ ) public {
+ require(
+ msg.sender == guardian,
+ "GovernorAlpha::__queueSetTimelockPendingAdmin: sender must be gov guardian"
+ );
+ timelock.queueTransaction(
+ address(timelock),
+ 0,
+ "setPendingAdmin(address)",
+ abi.encode(newPendingAdmin),
+ eta
+ );
+ }
+
+ function __executeSetTimelockPendingAdmin(
+ address newPendingAdmin,
+ uint256 eta
+ ) public {
+ require(
+ msg.sender == guardian,
+ "GovernorAlpha::__executeSetTimelockPendingAdmin: sender must be gov guardian"
+ );
+ timelock.executeTransaction(
+ address(timelock),
+ 0,
+ "setPendingAdmin(address)",
+ abi.encode(newPendingAdmin),
+ eta
+ );
+ }
+
+ function add256(uint256 a, uint256 b) internal pure returns (uint256) {
+ uint256 c = a + b;
+ require(c >= a, "addition overflow");
+ return c;
+ }
+
+ function sub256(uint256 a, uint256 b) internal pure returns (uint256) {
+ require(b <= a, "subtraction underflow");
+ return a - b;
+ }
+
+ function getChainId() internal pure returns (uint256) {
+ uint256 chainId;
+ assembly {
+ chainId := chainid()
+ }
+ return chainId;
+ }
+}
+
+
+interface TimelockInterface {
+ function delay() external view returns (uint256);
+
+ function GRACE_PERIOD() external view returns (uint256);
+
+ function acceptAdmin() external;
+
+ function queuedTransactions(bytes32 hash) external view returns (bool);
+
+ function queueTransaction(
+ address target,
+ uint256 value,
+ string calldata signature,
+ bytes calldata data,
+ uint256 eta
+ ) external returns (bytes32);
+
+ function cancelTransaction(
+ address target,
+ uint256 value,
+ string calldata signature,
+ bytes calldata data,
+ uint256 eta
+ ) external;
+
+ function executeTransaction(
+ address target,
+ uint256 value,
+ string calldata signature,
+ bytes calldata data,
+ uint256 eta
+ ) external payable returns (bytes memory);
+}
+
+
+interface MPondInterface {
+ function getPriorVotes(address account, uint256 blockNumber)
+ external
+ view
+ returns (uint96);
+}
diff --git a/packages/chain-events/eth/contracts/Compound/GovernorAlphaMock.sol b/packages/chain-events/eth/contracts/Compound/GovernorAlphaMock.sol
new file mode 100644
index 00000000000..79d74b48699
--- /dev/null
+++ b/packages/chain-events/eth/contracts/Compound/GovernorAlphaMock.sol
@@ -0,0 +1,547 @@
+pragma solidity >=0.4.21 <0.7.0;
+pragma experimental ABIEncoderV2;
+
+
+contract GovernorAlphaMock {
+ /// @notice The name of this contract
+ string public constant name = "Marlin Governor Alpha";
+
+ /// @notice The number of votes in support of a proposal required in order for a quorum to be reached and for a vote to succeed
+ function quorumVotes() public pure returns (uint256) {
+ return 2e18;
+ } // 2 MPond
+
+ /// @notice The number of votes required in order for a voter to become a proposer
+ function proposalThreshold() public pure returns (uint256) {
+ return 1e18;
+ } // 1 MPond
+
+ /// @notice The maximum number of actions that can be included in a proposal
+ function proposalMaxOperations() public pure returns (uint256) {
+ return 10;
+ } // 10 actions
+
+ /// @notice The delay before voting on a proposal may take place, once proposed
+ function votingDelay() public pure returns (uint256) {
+ return 1;
+ } // 1 block
+
+ /// @notice The duration of voting on a proposal, in blocks
+ function votingPeriod() public pure returns (uint256) {
+ return 12;
+ } // ~3 days in blocks (assuming 15s blocks)
+
+ /// @notice The address of the MPond Protocol Timelock
+ TimelockInterface public timelock;
+
+ /// @notice The address of the MPond governance token
+ MPondInterface public MPond;
+
+ /// @notice The address of the Governor Guardian
+ address public guardian;
+
+ /// @notice The total number of proposals
+ uint256 public proposalCount;
+
+ struct Proposal {
+ /// @notice Unique id for looking up a proposal
+ uint256 id;
+ /// @notice Creator of the proposal
+ address proposer;
+ /// @notice The timestamp that the proposal will be available for execution, set once the vote succeeds
+ uint256 eta;
+ /// @notice the ordered list of target addresses for calls to be made
+ address[] targets;
+ /// @notice The ordered list of values (i.e. msg.value) to be passed to the calls to be made
+ uint256[] values;
+ /// @notice The ordered list of function signatures to be called
+ string[] signatures;
+ /// @notice The ordered list of calldata to be passed to each call
+ bytes[] calldatas;
+ /// @notice The block at which voting begins: holders must delegate their votes prior to this block
+ uint256 startBlock;
+ /// @notice The block at which voting ends: votes must be cast prior to this block
+ uint256 endBlock;
+ /// @notice Current number of votes in favor of this proposal
+ uint256 forVotes;
+ /// @notice Current number of votes in opposition to this proposal
+ uint256 againstVotes;
+ /// @notice Flag marking whether the proposal has been canceled
+ bool canceled;
+ /// @notice Flag marking whether the proposal has been executed
+ bool executed;
+ /// @notice Receipts of ballots for the entire set of voters
+ mapping(address => Receipt) receipts;
+ }
+
+ /// @notice Ballot receipt record for a voter
+ struct Receipt {
+ /// @notice Whether or not a vote has been cast
+ bool hasVoted;
+ /// @notice Whether or not the voter supports the proposal
+ bool support;
+ /// @notice The number of votes the voter had, which were cast
+ uint96 votes;
+ }
+
+ /// @notice Possible states that a proposal may be in
+ enum ProposalState {
+ Pending,
+ Active,
+ Canceled,
+ Defeated,
+ Succeeded,
+ Queued,
+ Expired,
+ Executed
+ }
+
+ /// @notice The official record of all proposals ever proposed
+ mapping(uint256 => Proposal) public proposals;
+
+ /// @notice The latest proposal for each proposer
+ mapping(address => uint256) public latestProposalIds;
+
+ /// @notice The EIP-712 typehash for the contract's domain
+ bytes32 public constant DOMAIN_TYPEHASH = keccak256(
+ "EIP712Domain(string name,uint256 chainId,address verifyingContract)"
+ );
+
+ /// @notice The EIP-712 typehash for the ballot struct used by the contract
+ bytes32 public constant BALLOT_TYPEHASH = keccak256(
+ "Ballot(uint256 proposalId,bool support)"
+ );
+
+ /// @notice An event emitted when a new proposal is created
+ event ProposalCreated(
+ uint256 id,
+ address proposer,
+ address[] targets,
+ uint256[] values,
+ string[] signatures,
+ bytes[] calldatas,
+ uint256 startBlock,
+ uint256 endBlock,
+ string description
+ );
+
+ /// @notice An event emitted when a vote has been cast on a proposal
+ event VoteCast(
+ address voter,
+ uint256 proposalId,
+ bool support,
+ uint256 votes
+ );
+
+ /// @notice An event emitted when a proposal has been canceled
+ event ProposalCanceled(uint256 id);
+
+ /// @notice An event emitted when a proposal has been queued in the Timelock
+ event ProposalQueued(uint256 id, uint256 eta);
+
+ /// @notice An event emitted when a proposal has been executed in the Timelock
+ event ProposalExecuted(uint256 id);
+
+ constructor(
+ address timelock_,
+ address MPond_,
+ address guardian_
+ ) public {
+ timelock = TimelockInterface(timelock_);
+ MPond = MPondInterface(MPond_);
+ guardian = guardian_;
+ }
+
+ function propose(
+ address[] memory targets,
+ uint256[] memory values,
+ string[] memory signatures,
+ bytes[] memory calldatas,
+ string memory description
+ ) public returns (uint256) {
+ require(
+ MPond.getPriorVotes(msg.sender, sub256(block.number, 1)) >
+ proposalThreshold(),
+ "GovernorAlphaMock::propose: proposer votes below proposal threshold"
+ );
+ require(
+ targets.length == values.length &&
+ targets.length == signatures.length &&
+ targets.length == calldatas.length,
+ "GovernorAlphaMock::propose: proposal function information arity mismatch"
+ );
+ require(
+ targets.length != 0,
+ "GovernorAlphaMock::propose: must provide actions"
+ );
+ require(
+ targets.length <= proposalMaxOperations(),
+ "GovernorAlphaMock::propose: too many actions"
+ );
+
+ uint256 latestProposalId = latestProposalIds[msg.sender];
+ if (latestProposalId != 0) {
+ ProposalState proposersLatestProposalState = state(
+ latestProposalId
+ );
+ require(
+ proposersLatestProposalState != ProposalState.Active,
+ "GovernorAlphaMock::propose: one live proposal per proposer, found an already active proposal"
+ );
+ require(
+ proposersLatestProposalState != ProposalState.Pending,
+ "GovernorAlphaMock::propose: one live proposal per proposer, found an already pending proposal"
+ );
+ }
+
+ uint256 startBlock = add256(block.number, votingDelay());
+ uint256 endBlock = add256(startBlock, votingPeriod());
+
+ proposalCount++;
+ Proposal memory newProposal = Proposal({
+ id: proposalCount,
+ proposer: msg.sender,
+ eta: 0,
+ targets: targets,
+ values: values,
+ signatures: signatures,
+ calldatas: calldatas,
+ startBlock: startBlock,
+ endBlock: endBlock,
+ forVotes: 0,
+ againstVotes: 0,
+ canceled: false,
+ executed: false
+ });
+
+ proposals[newProposal.id] = newProposal;
+ latestProposalIds[newProposal.proposer] = newProposal.id;
+
+ emit ProposalCreated(
+ newProposal.id,
+ msg.sender,
+ targets,
+ values,
+ signatures,
+ calldatas,
+ startBlock,
+ endBlock,
+ description
+ );
+ return newProposal.id;
+ }
+
+ function queue(uint256 proposalId) public {
+ require(
+ state(proposalId) == ProposalState.Succeeded,
+ "GovernorAlphaMock::queue: proposal can only be queued if it is succeeded"
+ );
+ Proposal storage proposal = proposals[proposalId];
+ uint256 eta = add256(block.timestamp, timelock.delay());
+ for (uint256 i = 0; i < proposal.targets.length; i++) {
+ _queueOrRevert(
+ proposal.targets[i],
+ proposal.values[i],
+ proposal.signatures[i],
+ proposal.calldatas[i],
+ eta
+ );
+ }
+ proposal.eta = eta;
+ emit ProposalQueued(proposalId, eta);
+ }
+
+ function _queueOrRevert(
+ address target,
+ uint256 value,
+ string memory signature,
+ bytes memory data,
+ uint256 eta
+ ) internal {
+ require(
+ !timelock.queuedTransactions(
+ keccak256(abi.encode(target, value, signature, data, eta))
+ ),
+ "GovernorAlphaMock::_queueOrRevert: proposal action already queued at eta"
+ );
+ timelock.queueTransaction(target, value, signature, data, eta);
+ }
+
+ function execute(uint256 proposalId) public payable {
+ require(
+ state(proposalId) == ProposalState.Queued,
+ "GovernorAlphaMock::execute: proposal can only be executed if it is queued"
+ );
+ Proposal storage proposal = proposals[proposalId];
+ proposal.executed = true;
+ for (uint256 i = 0; i < proposal.targets.length; i++) {
+ timelock.executeTransaction.value(proposal.values[i])(
+ proposal.targets[i],
+ proposal.values[i],
+ proposal.signatures[i],
+ proposal.calldatas[i],
+ proposal.eta
+ );
+ }
+ emit ProposalExecuted(proposalId);
+ }
+
+ function cancel(uint256 proposalId) public {
+ ProposalState state = state(proposalId);
+ require(
+ state != ProposalState.Executed,
+ "GovernorAlphaMock::cancel: cannot cancel executed proposal"
+ );
+
+ Proposal storage proposal = proposals[proposalId];
+ require(
+ msg.sender == guardian ||
+ MPond.getPriorVotes(
+ proposal.proposer,
+ sub256(block.number, 1)
+ ) <
+ proposalThreshold(),
+ "GovernorAlphaMock::cancel: proposer above threshold"
+ );
+
+ proposal.canceled = true;
+ for (uint256 i = 0; i < proposal.targets.length; i++) {
+ timelock.cancelTransaction(
+ proposal.targets[i],
+ proposal.values[i],
+ proposal.signatures[i],
+ proposal.calldatas[i],
+ proposal.eta
+ );
+ }
+
+ emit ProposalCanceled(proposalId);
+ }
+
+ function getActions(uint256 proposalId)
+ public
+ view
+ returns (
+ address[] memory targets,
+ uint256[] memory values,
+ string[] memory signatures,
+ bytes[] memory calldatas
+ )
+ {
+ Proposal storage p = proposals[proposalId];
+ return (p.targets, p.values, p.signatures, p.calldatas);
+ }
+
+ function getReceipt(uint256 proposalId, address voter)
+ public
+ view
+ returns (Receipt memory)
+ {
+ return proposals[proposalId].receipts[voter];
+ }
+
+ function state(uint256 proposalId) public view returns (ProposalState) {
+ require(
+ proposalCount >= proposalId && proposalId > 0,
+ "GovernorAlphaMock::state: invalid proposal id"
+ );
+ Proposal storage proposal = proposals[proposalId];
+ if (proposal.canceled) {
+ return ProposalState.Canceled;
+ } else if (block.number <= proposal.startBlock) {
+ return ProposalState.Pending;
+ } else if (block.number <= proposal.endBlock) {
+ return ProposalState.Active;
+ } else if (
+ proposal.forVotes <= proposal.againstVotes ||
+ proposal.forVotes < quorumVotes()
+ ) {
+ return ProposalState.Defeated;
+ } else if (proposal.eta == 0) {
+ return ProposalState.Succeeded;
+ } else if (proposal.executed) {
+ return ProposalState.Executed;
+ } else if (
+ block.timestamp >= add256(proposal.eta, timelock.GRACE_PERIOD())
+ ) {
+ return ProposalState.Expired;
+ } else {
+ return ProposalState.Queued;
+ }
+ }
+
+ function castVote(uint256 proposalId, bool support) public {
+ return _castVote(msg.sender, proposalId, support);
+ }
+
+ function castVoteBySig(
+ uint256 proposalId,
+ bool support,
+ uint8 v,
+ bytes32 r,
+ bytes32 s
+ ) public {
+ bytes32 domainSeparator = keccak256(
+ abi.encode(
+ DOMAIN_TYPEHASH,
+ keccak256(bytes(name)),
+ getChainId(),
+ address(this)
+ )
+ );
+ bytes32 structHash = keccak256(
+ abi.encode(BALLOT_TYPEHASH, proposalId, support)
+ );
+ bytes32 digest = keccak256(
+ abi.encodePacked("\x19\x01", domainSeparator, structHash)
+ );
+ address signatory = ecrecover(digest, v, r, s);
+ require(
+ signatory != address(0),
+ "GovernorAlphaMock::castVoteBySig: invalid signature"
+ );
+ return _castVote(signatory, proposalId, support);
+ }
+
+ function _castVote(
+ address voter,
+ uint256 proposalId,
+ bool support
+ ) internal {
+ require(
+ state(proposalId) == ProposalState.Active,
+ "GovernorAlphaMock::_castVote: voting is closed"
+ );
+ Proposal storage proposal = proposals[proposalId];
+ Receipt storage receipt = proposal.receipts[voter];
+ require(
+ receipt.hasVoted == false,
+ "GovernorAlphaMock::_castVote: voter already voted"
+ );
+ uint96 votes = MPond.getPriorVotes(voter, proposal.startBlock);
+
+ if (support) {
+ proposal.forVotes = add256(proposal.forVotes, votes);
+ } else {
+ proposal.againstVotes = add256(proposal.againstVotes, votes);
+ }
+
+ receipt.hasVoted = true;
+ receipt.support = support;
+ receipt.votes = votes;
+
+ emit VoteCast(voter, proposalId, support, votes);
+ }
+
+ function __acceptAdmin() public {
+ require(
+ msg.sender == guardian,
+ "GovernorAlphaMock::__acceptAdmin: sender must be gov guardian"
+ );
+ timelock.acceptAdmin();
+ }
+
+ function __abdicate() public {
+ require(
+ msg.sender == guardian,
+ "GovernorAlphaMock::__abdicate: sender must be gov guardian"
+ );
+ guardian = address(0);
+ }
+
+ function __queueSetTimelockPendingAdmin(
+ address newPendingAdmin,
+ uint256 eta
+ ) public {
+ require(
+ msg.sender == guardian,
+ "GovernorAlphaMock::__queueSetTimelockPendingAdmin: sender must be gov guardian"
+ );
+ timelock.queueTransaction(
+ address(timelock),
+ 0,
+ "setPendingAdmin(address)",
+ abi.encode(newPendingAdmin),
+ eta
+ );
+ }
+
+ function __executeSetTimelockPendingAdmin(
+ address newPendingAdmin,
+ uint256 eta
+ ) public {
+ require(
+ msg.sender == guardian,
+ "GovernorAlphaMock::__executeSetTimelockPendingAdmin: sender must be gov guardian"
+ );
+ timelock.executeTransaction(
+ address(timelock),
+ 0,
+ "setPendingAdmin(address)",
+ abi.encode(newPendingAdmin),
+ eta
+ );
+ }
+
+ function add256(uint256 a, uint256 b) internal pure returns (uint256) {
+ uint256 c = a + b;
+ require(c >= a, "addition overflow");
+ return c;
+ }
+
+ function sub256(uint256 a, uint256 b) internal pure returns (uint256) {
+ require(b <= a, "subtraction underflow");
+ return a - b;
+ }
+
+ function getChainId() internal pure returns (uint256) {
+ uint256 chainId;
+ assembly {
+ chainId := chainid()
+ }
+ return chainId;
+ }
+}
+
+
+interface TimelockInterface {
+ function delay() external view returns (uint256);
+
+ function GRACE_PERIOD() external view returns (uint256);
+
+ function acceptAdmin() external;
+
+ function queuedTransactions(bytes32 hash) external view returns (bool);
+
+ function queueTransaction(
+ address target,
+ uint256 value,
+ string calldata signature,
+ bytes calldata data,
+ uint256 eta
+ ) external returns (bytes32);
+
+ function cancelTransaction(
+ address target,
+ uint256 value,
+ string calldata signature,
+ bytes calldata data,
+ uint256 eta
+ ) external;
+
+ function executeTransaction(
+ address target,
+ uint256 value,
+ string calldata signature,
+ bytes calldata data,
+ uint256 eta
+ ) external payable returns (bytes memory);
+}
+
+
+interface MPondInterface {
+ function getPriorVotes(address account, uint256 blockNumber)
+ external
+ view
+ returns (uint96);
+}
diff --git a/packages/chain-events/eth/contracts/Compound/GovernorBravoDelegate.sol b/packages/chain-events/eth/contracts/Compound/GovernorBravoDelegate.sol
new file mode 100644
index 00000000000..7da6b76dca7
--- /dev/null
+++ b/packages/chain-events/eth/contracts/Compound/GovernorBravoDelegate.sol
@@ -0,0 +1,380 @@
+pragma solidity ^0.5.16;
+pragma experimental ABIEncoderV2;
+
+import "./GovernorBravoInterfaces.sol";
+
+contract GovernorBravoDelegate is GovernorBravoDelegateStorageV1, GovernorBravoEvents {
+
+ /// @notice The name of this contract
+ string public constant name = "Compound Governor Bravo";
+
+ /// @notice The minimum setable proposal threshold
+ uint public constant MIN_PROPOSAL_THRESHOLD = 50000e18; // 50,000 Comp
+
+ /// @notice The maximum setable proposal threshold
+ uint public constant MAX_PROPOSAL_THRESHOLD = 100000e18; //100,000 Comp
+
+ /// @notice The minimum setable voting period
+ uint public constant MIN_VOTING_PERIOD = 5760; // About 24 hours
+
+ /// @notice The max setable voting period
+ uint public constant MAX_VOTING_PERIOD = 80640; // About 2 weeks
+
+ /// @notice The min setable voting delay
+ uint public constant MIN_VOTING_DELAY = 1;
+
+ /// @notice The max setable voting delay
+ uint public constant MAX_VOTING_DELAY = 40320; // About 1 week
+
+ /// @notice The number of votes in support of a proposal required in order for a quorum to be reached and for a vote to succeed
+ uint public constant quorumVotes = 400000e18; // 400,000 = 4% of Comp
+
+ /// @notice The maximum number of actions that can be included in a proposal
+ uint public constant proposalMaxOperations = 10; // 10 actions
+
+ /// @notice The EIP-712 typehash for the contract's domain
+ bytes32 public constant DOMAIN_TYPEHASH = keccak256("EIP712Domain(string name,uint256 chainId,address verifyingContract)");
+
+ /// @notice The EIP-712 typehash for the ballot struct used by the contract
+ bytes32 public constant BALLOT_TYPEHASH = keccak256("Ballot(uint256 proposalId,uint8 support)");
+
+ /**
+ * @notice Used to initialize the contract during delegator contructor
+ * @param timelock_ The address of the Timelock
+ * @param comp_ The address of the COMP token
+ * @param votingPeriod_ The initial voting period
+ * @param votingDelay_ The initial voting delay
+ * @param proposalThreshold_ The initial proposal threshold
+ */
+ function initialize(address timelock_, address comp_, uint votingPeriod_, uint votingDelay_, uint proposalThreshold_) public {
+ require(address(timelock) == address(0), "GovernorBravo::initialize: can only initialize once");
+ require(msg.sender == admin, "GovernorBravo::initialize: admin only");
+ require(timelock_ != address(0), "GovernorBravo::initialize: invalid timelock address");
+ require(comp_ != address(0), "GovernorBravo::initialize: invalid comp address");
+ require(votingPeriod_ >= MIN_VOTING_PERIOD && votingPeriod_ <= MAX_VOTING_PERIOD, "GovernorBravo::initialize: invalid voting period");
+ require(votingDelay_ >= MIN_VOTING_DELAY && votingDelay_ <= MAX_VOTING_DELAY, "GovernorBravo::initialize: invalid voting delay");
+ require(proposalThreshold_ >= MIN_PROPOSAL_THRESHOLD && proposalThreshold_ <= MAX_PROPOSAL_THRESHOLD, "GovernorBravo::initialize: invalid proposal threshold");
+
+ timelock = TimelockInterface(timelock_);
+ comp = CompInterface(comp_);
+ votingPeriod = votingPeriod_;
+ votingDelay = votingDelay_;
+ proposalThreshold = proposalThreshold_;
+ }
+
+ /**
+ * @notice Function used to propose a new proposal. Sender must have delegates above the proposal threshold
+ * @param targets Target addresses for proposal calls
+ * @param values Eth values for proposal calls
+ * @param signatures Function signatures for proposal calls
+ * @param calldatas Calldatas for proposal calls
+ * @param description String description of the proposal
+ * @return Proposal id of new proposal
+ */
+ function propose(address[] memory targets, uint[] memory values, string[] memory signatures, bytes[] memory calldatas, string memory description) public returns (uint) {
+ // Reject proposals before initiating as Governor
+ require(initialProposalId != 0, "GovernorBravo::propose: Governor Bravo not active");
+ require(comp.getPriorVotes(msg.sender, sub256(block.number, 1)) > proposalThreshold, "GovernorBravo::propose: proposer votes below proposal threshold");
+ require(targets.length == values.length && targets.length == signatures.length && targets.length == calldatas.length, "GovernorBravo::propose: proposal function information arity mismatch");
+ require(targets.length != 0, "GovernorBravo::propose: must provide actions");
+ require(targets.length <= proposalMaxOperations, "GovernorBravo::propose: too many actions");
+
+ uint latestProposalId = latestProposalIds[msg.sender];
+ if (latestProposalId != 0) {
+ ProposalState proposersLatestProposalState = state(latestProposalId);
+ require(proposersLatestProposalState != ProposalState.Active, "GovernorBravo::propose: one live proposal per proposer, found an already active proposal");
+ require(proposersLatestProposalState != ProposalState.Pending, "GovernorBravo::propose: one live proposal per proposer, found an already pending proposal");
+ }
+
+ uint startBlock = add256(block.number, votingDelay);
+ uint endBlock = add256(startBlock, votingPeriod);
+
+ proposalCount++;
+ Proposal memory newProposal = Proposal({
+ id: proposalCount,
+ proposer: msg.sender,
+ eta: 0,
+ targets: targets,
+ values: values,
+ signatures: signatures,
+ calldatas: calldatas,
+ startBlock: startBlock,
+ endBlock: endBlock,
+ forVotes: 0,
+ againstVotes: 0,
+ abstainVotes: 0,
+ canceled: false,
+ executed: false
+ });
+
+ proposals[newProposal.id] = newProposal;
+ latestProposalIds[newProposal.proposer] = newProposal.id;
+
+ emit ProposalCreated(newProposal.id, msg.sender, targets, values, signatures, calldatas, startBlock, endBlock, description);
+ return newProposal.id;
+ }
+
+ /**
+ * @notice Queues a proposal of state succeeded
+ * @param proposalId The id of the proposal to queue
+ */
+ function queue(uint proposalId) external {
+ require(state(proposalId) == ProposalState.Succeeded, "GovernorBravo::queue: proposal can only be queued if it is succeeded");
+ Proposal storage proposal = proposals[proposalId];
+ uint eta = add256(block.timestamp, timelock.delay());
+ for (uint i = 0; i < proposal.targets.length; i++) {
+ queueOrRevertInternal(proposal.targets[i], proposal.values[i], proposal.signatures[i], proposal.calldatas[i], eta);
+ }
+ proposal.eta = eta;
+ emit ProposalQueued(proposalId, eta);
+ }
+
+ function queueOrRevertInternal(address target, uint value, string memory signature, bytes memory data, uint eta) internal {
+ require(!timelock.queuedTransactions(keccak256(abi.encode(target, value, signature, data, eta))), "GovernorBravo::queueOrRevertInternal: identical proposal action already queued at eta");
+ timelock.queueTransaction(target, value, signature, data, eta);
+ }
+
+ /**
+ * @notice Executes a queued proposal if eta has passed
+ * @param proposalId The id of the proposal to execute
+ */
+ function execute(uint proposalId) external payable {
+ require(state(proposalId) == ProposalState.Queued, "GovernorBravo::execute: proposal can only be executed if it is queued");
+ Proposal storage proposal = proposals[proposalId];
+ proposal.executed = true;
+ for (uint i = 0; i < proposal.targets.length; i++) {
+ timelock.executeTransaction.value(proposal.values[i])(proposal.targets[i], proposal.values[i], proposal.signatures[i], proposal.calldatas[i], proposal.eta);
+ }
+ emit ProposalExecuted(proposalId);
+ }
+
+ /**
+ * @notice Cancels a proposal only if sender is the proposer, or proposer delegates dropped below proposal threshold
+ * @param proposalId The id of the proposal to cancel
+ */
+ function cancel(uint proposalId) external {
+ require(state(proposalId) != ProposalState.Executed, "GovernorBravo::cancel: cannot cancel executed proposal");
+
+ Proposal storage proposal = proposals[proposalId];
+ require(msg.sender == proposal.proposer || comp.getPriorVotes(proposal.proposer, sub256(block.number, 1)) < proposalThreshold, "GovernorBravo::cancel: proposer above threshold");
+
+ proposal.canceled = true;
+ for (uint i = 0; i < proposal.targets.length; i++) {
+ timelock.cancelTransaction(proposal.targets[i], proposal.values[i], proposal.signatures[i], proposal.calldatas[i], proposal.eta);
+ }
+
+ emit ProposalCanceled(proposalId);
+ }
+
+ /**
+ * @notice Gets actions of a proposal
+ * @param proposalId the id of the proposal
+ * @return Targets, values, signatures, and calldatas of the proposal actions
+ */
+ function getActions(uint proposalId) external view returns (address[] memory targets, uint[] memory values, string[] memory signatures, bytes[] memory calldatas) {
+ Proposal storage p = proposals[proposalId];
+ return (p.targets, p.values, p.signatures, p.calldatas);
+ }
+
+ /**
+ * @notice Gets the receipt for a voter on a given proposal
+ * @param proposalId the id of proposal
+ * @param voter The address of the voter
+ * @return The voting receipt
+ */
+ function getReceipt(uint proposalId, address voter) external view returns (Receipt memory) {
+ return proposals[proposalId].receipts[voter];
+ }
+
+ /**
+ * @notice Gets the state of a proposal
+ * @param proposalId The id of the proposal
+ * @return Proposal state
+ */
+ function state(uint proposalId) public view returns (ProposalState) {
+ require(proposalCount >= proposalId && proposalId > initialProposalId, "GovernorBravo::state: invalid proposal id");
+ Proposal storage proposal = proposals[proposalId];
+ if (proposal.canceled) {
+ return ProposalState.Canceled;
+ } else if (block.number <= proposal.startBlock) {
+ return ProposalState.Pending;
+ } else if (block.number <= proposal.endBlock) {
+ return ProposalState.Active;
+ } else if (proposal.forVotes <= proposal.againstVotes || proposal.forVotes < quorumVotes) {
+ return ProposalState.Defeated;
+ } else if (proposal.eta == 0) {
+ return ProposalState.Succeeded;
+ } else if (proposal.executed) {
+ return ProposalState.Executed;
+ } else if (block.timestamp >= add256(proposal.eta, timelock.GRACE_PERIOD())) {
+ return ProposalState.Expired;
+ } else {
+ return ProposalState.Queued;
+ }
+ }
+
+ /**
+ * @notice Cast a vote for a proposal
+ * @param proposalId The id of the proposal to vote on
+ * @param support The support value for the vote. 0=against, 1=for, 2=abstain
+ */
+ function castVote(uint proposalId, uint8 support) external {
+ emit VoteCast(msg.sender, proposalId, support, castVoteInternal(msg.sender, proposalId, support), "");
+ }
+
+ /**
+ * @notice Cast a vote for a proposal with a reason
+ * @param proposalId The id of the proposal to vote on
+ * @param support The support value for the vote. 0=against, 1=for, 2=abstain
+ * @param reason The reason given for the vote by the voter
+ */
+ function castVoteWithReason(uint proposalId, uint8 support, string calldata reason) external {
+ emit VoteCast(msg.sender, proposalId, support, castVoteInternal(msg.sender, proposalId, support), reason);
+ }
+
+ /**
+ * @notice Cast a vote for a proposal by signature
+ * @dev External function that accepts EIP-712 signatures for voting on proposals.
+ */
+ function castVoteBySig(uint proposalId, uint8 support, uint8 v, bytes32 r, bytes32 s) external {
+ bytes32 domainSeparator = keccak256(abi.encode(DOMAIN_TYPEHASH, keccak256(bytes(name)), getChainIdInternal(), address(this)));
+ bytes32 structHash = keccak256(abi.encode(BALLOT_TYPEHASH, proposalId, support));
+ bytes32 digest = keccak256(abi.encodePacked("\x19\x01", domainSeparator, structHash));
+ address signatory = ecrecover(digest, v, r, s);
+ require(signatory != address(0), "GovernorBravo::castVoteBySig: invalid signature");
+ emit VoteCast(signatory, proposalId, support, castVoteInternal(signatory, proposalId, support), "");
+ }
+
+ /**
+ * @notice Internal function that caries out voting logic
+ * @param voter The voter that is casting their vote
+ * @param proposalId The id of the proposal to vote on
+ * @param support The support value for the vote. 0=against, 1=for, 2=abstain
+ * @return The number of votes cast
+ */
+ function castVoteInternal(address voter, uint proposalId, uint8 support) internal returns (uint96) {
+ require(state(proposalId) == ProposalState.Active, "GovernorBravo::castVoteInternal: voting is closed");
+ require(support <= 2, "GovernorBravo::castVoteInternal: invalid vote type");
+ Proposal storage proposal = proposals[proposalId];
+ Receipt storage receipt = proposal.receipts[voter];
+ require(receipt.hasVoted == false, "GovernorBravo::castVoteInternal: voter already voted");
+ uint96 votes = comp.getPriorVotes(voter, proposal.startBlock);
+
+ if (support == 0) {
+ proposal.againstVotes = add256(proposal.againstVotes, votes);
+ } else if (support == 1) {
+ proposal.forVotes = add256(proposal.forVotes, votes);
+ } else if (support == 2) {
+ proposal.abstainVotes = add256(proposal.abstainVotes, votes);
+ }
+
+ receipt.hasVoted = true;
+ receipt.support = support;
+ receipt.votes = votes;
+
+ return votes;
+ }
+
+ /**
+ * @notice Admin function for setting the voting delay
+ * @param newVotingDelay new voting delay, in blocks
+ */
+ function _setVotingDelay(uint newVotingDelay) external {
+ require(msg.sender == admin, "GovernorBravo::_setVotingDelay: admin only");
+ require(newVotingDelay >= MIN_VOTING_DELAY && newVotingDelay <= MAX_VOTING_DELAY, "GovernorBravo::_setVotingDelay: invalid voting delay");
+ uint oldVotingDelay = votingDelay;
+ votingDelay = newVotingDelay;
+
+ emit VotingDelaySet(oldVotingDelay,votingDelay);
+ }
+
+ /**
+ * @notice Admin function for setting the voting period
+ * @param newVotingPeriod new voting period, in blocks
+ */
+ function _setVotingPeriod(uint newVotingPeriod) external {
+ require(msg.sender == admin, "GovernorBravo::_setVotingPeriod: admin only");
+ require(newVotingPeriod >= MIN_VOTING_PERIOD && newVotingPeriod <= MAX_VOTING_PERIOD, "GovernorBravo::_setVotingPeriod: invalid voting period");
+ uint oldVotingPeriod = votingPeriod;
+ votingPeriod = newVotingPeriod;
+
+ emit VotingPeriodSet(oldVotingPeriod, votingPeriod);
+ }
+
+ /**
+ * @notice Admin function for setting the proposal threshold
+ * @dev newProposalThreshold must be greater than the hardcoded min
+ * @param newProposalThreshold new proposal threshold
+ */
+ function _setProposalThreshold(uint newProposalThreshold) external {
+ require(msg.sender == admin, "GovernorBravo::_setProposalThreshold: admin only");
+ require(newProposalThreshold >= MIN_PROPOSAL_THRESHOLD && newProposalThreshold <= MAX_PROPOSAL_THRESHOLD, "GovernorBravo::_setProposalThreshold: invalid proposal threshold");
+ uint oldProposalThreshold = proposalThreshold;
+ proposalThreshold = newProposalThreshold;
+
+ emit ProposalThresholdSet(oldProposalThreshold, proposalThreshold);
+ }
+
+ // function setInitialProposalId() external {
+ // initialProposalId = 1;
+ // proposalCount = 1;
+ // }
+
+ /**
+ * @notice Begins transfer of admin rights. The newPendingAdmin must call `_acceptAdmin` to finalize the transfer.
+ * @dev Admin function to begin change of admin. The newPendingAdmin must call `_acceptAdmin` to finalize the transfer.
+ * @param newPendingAdmin New pending admin.
+ */
+ function _setPendingAdmin(address newPendingAdmin) external {
+ // Check caller = admin
+ require(msg.sender == admin, "GovernorBravo:_setPendingAdmin: admin only");
+
+ // Save current value, if any, for inclusion in log
+ address oldPendingAdmin = pendingAdmin;
+
+ // Store pendingAdmin with value newPendingAdmin
+ pendingAdmin = newPendingAdmin;
+
+ // Emit NewPendingAdmin(oldPendingAdmin, newPendingAdmin)
+ emit NewPendingAdmin(oldPendingAdmin, newPendingAdmin);
+ }
+
+ /**
+ * @notice Accepts transfer of admin rights. msg.sender must be pendingAdmin
+ * @dev Admin function for pending admin to accept role and update admin
+ */
+ function _acceptAdmin() external {
+ // Check caller is pendingAdmin and pendingAdmin ≠address(0)
+ require(msg.sender == pendingAdmin && msg.sender != address(0), "GovernorBravo:_acceptAdmin: pending admin only");
+
+ // Save current values for inclusion in log
+ address oldAdmin = admin;
+ address oldPendingAdmin = pendingAdmin;
+
+ // Store admin with value pendingAdmin
+ admin = pendingAdmin;
+
+ // Clear the pending value
+ pendingAdmin = address(0);
+
+ emit NewAdmin(oldAdmin, admin);
+ emit NewPendingAdmin(oldPendingAdmin, pendingAdmin);
+ }
+
+ function add256(uint256 a, uint256 b) internal pure returns (uint) {
+ uint c = a + b;
+ require(c >= a, "addition overflow");
+ return c;
+ }
+
+ function sub256(uint256 a, uint256 b) internal pure returns (uint) {
+ require(b <= a, "subtraction underflow");
+ return a - b;
+ }
+
+ function getChainIdInternal() internal pure returns (uint) {
+ uint chainId;
+ assembly { chainId := chainid() }
+ return chainId;
+ }
+}
\ No newline at end of file
diff --git a/packages/chain-events/eth/contracts/Compound/GovernorBravoDelegateMock.sol b/packages/chain-events/eth/contracts/Compound/GovernorBravoDelegateMock.sol
new file mode 100644
index 00000000000..9fb943d217a
--- /dev/null
+++ b/packages/chain-events/eth/contracts/Compound/GovernorBravoDelegateMock.sol
@@ -0,0 +1,382 @@
+pragma solidity ^0.5.16;
+pragma experimental ABIEncoderV2;
+
+import "./GovernorBravoInterfaces.sol";
+
+// TODO fill out modifications to this contract
+contract GovernorBravoDelegateMock is GovernorBravoDelegateStorageV1, GovernorBravoEvents {
+
+ /// @notice The name of this contract
+ string public constant name = "Compound Governor Bravo";
+
+ /// @notice The minimum setable proposal threshold
+ uint public constant MIN_PROPOSAL_THRESHOLD = 1; // 50,000 Comp
+
+ /// @notice The maximum setable proposal threshold
+ uint public constant MAX_PROPOSAL_THRESHOLD = 100000e18; //100,000 Comp
+
+ /// @notice The minimum setable voting period
+ uint public constant MIN_VOTING_PERIOD = 5760; // About 24 hours
+
+ /// @notice The max setable voting period
+ uint public constant MAX_VOTING_PERIOD = 80640; // About 2 weeks
+
+ /// @notice The min setable voting delay
+ uint public constant MIN_VOTING_DELAY = 1;
+
+ /// @notice The max setable voting delay
+ uint public constant MAX_VOTING_DELAY = 40320; // About 1 week
+
+ /// @notice The number of votes in support of a proposal required in order for a quorum to be reached and for a vote to succeed
+ uint public constant quorumVotes = 1; // Changed from 400k to 1
+
+ /// @notice The maximum number of actions that can be included in a proposal
+ uint public constant proposalMaxOperations = 10; // 10 actions
+
+ /// @notice The EIP-712 typehash for the contract's domain
+ bytes32 public constant DOMAIN_TYPEHASH = keccak256("EIP712Domain(string name,uint256 chainId,address verifyingContract)");
+
+ /// @notice The EIP-712 typehash for the ballot struct used by the contract
+ bytes32 public constant BALLOT_TYPEHASH = keccak256("Ballot(uint256 proposalId,uint8 support)");
+
+ /**
+ * @notice Used to initialize the contract during delegator contructor
+ * @param timelock_ The address of the Timelock
+ * @param comp_ The address of the COMP token
+ * @param votingPeriod_ The initial voting period
+ * @param votingDelay_ The initial voting delay
+ * @param proposalThreshold_ The initial proposal threshold
+ */
+ function initialize(address timelock_, address comp_, uint votingPeriod_, uint votingDelay_, uint proposalThreshold_) public {
+ require(address(timelock) == address(0), "GovernorBravo::initialize: can only initialize once");
+ require(msg.sender == admin, "GovernorBravo::initialize: admin only");
+ require(timelock_ != address(0), "GovernorBravo::initialize: invalid timelock address");
+ require(comp_ != address(0), "GovernorBravo::initialize: invalid comp address");
+ require(votingPeriod_ >= MIN_VOTING_PERIOD && votingPeriod_ <= MAX_VOTING_PERIOD, "GovernorBravo::initialize: invalid voting period");
+ require(votingDelay_ >= MIN_VOTING_DELAY && votingDelay_ <= MAX_VOTING_DELAY, "GovernorBravo::initialize: invalid voting delay");
+ require(proposalThreshold_ >= MIN_PROPOSAL_THRESHOLD && proposalThreshold_ <= MAX_PROPOSAL_THRESHOLD, "GovernorBravo::initialize: invalid proposal threshold");
+
+ timelock = TimelockInterface(timelock_);
+ comp = CompInterface(comp_);
+ votingPeriod = votingPeriod_;
+ votingDelay = votingDelay_;
+ proposalThreshold = proposalThreshold_;
+ }
+
+ /**
+ * @notice Function used to propose a new proposal. Sender must have delegates above the proposal threshold
+ * @param targets Target addresses for proposal calls
+ * @param values Eth values for proposal calls
+ * @param signatures Function signatures for proposal calls
+ * @param calldatas Calldatas for proposal calls
+ * @param description String description of the proposal
+ * @return Proposal id of new proposal
+ */
+ function propose(address[] memory targets, uint[] memory values, string[] memory signatures, bytes[] memory calldatas, string memory description) public returns (uint) {
+ // Reject proposals before initiating as Governor
+ require(address(comp) != address(0), "GovernorBravo::propose: Governor Bravo not initialized");
+ require(initialProposalId != 0, "GovernorBravo::propose: Governor Bravo not active");
+ require(comp.getPriorVotes(msg.sender, sub256(block.number, 1)) > proposalThreshold, "GovernorBravo::propose: proposer votes below proposal threshold");
+ require(targets.length == values.length && targets.length == signatures.length && targets.length == calldatas.length, "GovernorBravo::propose: proposal function information arity mismatch");
+ require(targets.length != 0, "GovernorBravo::propose: must provide actions");
+ require(targets.length <= proposalMaxOperations, "GovernorBravo::propose: too many actions");
+
+ uint latestProposalId = latestProposalIds[msg.sender];
+ if (latestProposalId != 0) {
+ ProposalState proposersLatestProposalState = state(latestProposalId);
+ require(proposersLatestProposalState != ProposalState.Active, "GovernorBravo::propose: one live proposal per proposer, found an already active proposal");
+ require(proposersLatestProposalState != ProposalState.Pending, "GovernorBravo::propose: one live proposal per proposer, found an already pending proposal");
+ }
+
+ uint startBlock = add256(block.number, votingDelay);
+ uint endBlock = add256(startBlock, votingPeriod);
+
+ proposalCount++;
+ Proposal memory newProposal = Proposal({
+ id: proposalCount,
+ proposer: msg.sender,
+ eta: 0,
+ targets: targets,
+ values: values,
+ signatures: signatures,
+ calldatas: calldatas,
+ startBlock: startBlock,
+ endBlock: endBlock,
+ forVotes: 0,
+ againstVotes: 0,
+ abstainVotes: 0,
+ canceled: false,
+ executed: false
+ });
+
+ proposals[newProposal.id] = newProposal;
+ latestProposalIds[newProposal.proposer] = newProposal.id;
+
+ emit ProposalCreated(newProposal.id, msg.sender, targets, values, signatures, calldatas, startBlock, endBlock, description);
+ return newProposal.id;
+ }
+
+ /**
+ * @notice Queues a proposal of state succeeded
+ * @param proposalId The id of the proposal to queue
+ */
+ function queue(uint proposalId) external {
+ require(state(proposalId) == ProposalState.Succeeded, "GovernorBravo::queue: proposal can only be queued if it is succeeded");
+ Proposal storage proposal = proposals[proposalId];
+ uint eta = add256(block.timestamp, timelock.delay());
+ for (uint i = 0; i < proposal.targets.length; i++) {
+ queueOrRevertInternal(proposal.targets[i], proposal.values[i], proposal.signatures[i], proposal.calldatas[i], eta);
+ }
+ proposal.eta = eta;
+ emit ProposalQueued(proposalId, eta);
+ }
+
+ function queueOrRevertInternal(address target, uint value, string memory signature, bytes memory data, uint eta) internal {
+ require(!timelock.queuedTransactions(keccak256(abi.encode(target, value, signature, data, eta))), "GovernorBravo::queueOrRevertInternal: identical proposal action already queued at eta");
+ timelock.queueTransaction(target, value, signature, data, eta);
+ }
+
+ /**
+ * @notice Executes a queued proposal if eta has passed
+ * @param proposalId The id of the proposal to execute
+ */
+ function execute(uint proposalId) external payable {
+ require(state(proposalId) == ProposalState.Queued, "GovernorBravo::execute: proposal can only be executed if it is queued");
+ Proposal storage proposal = proposals[proposalId];
+ proposal.executed = true;
+ for (uint i = 0; i < proposal.targets.length; i++) {
+ timelock.executeTransaction.value(proposal.values[i])(proposal.targets[i], proposal.values[i], proposal.signatures[i], proposal.calldatas[i], proposal.eta);
+ }
+ emit ProposalExecuted(proposalId);
+ }
+
+ /**
+ * @notice Cancels a proposal only if sender is the proposer, or proposer delegates dropped below proposal threshold
+ * @param proposalId The id of the proposal to cancel
+ */
+ function cancel(uint proposalId) external {
+ require(state(proposalId) != ProposalState.Executed, "GovernorBravo::cancel: cannot cancel executed proposal");
+
+ Proposal storage proposal = proposals[proposalId];
+ require(msg.sender == proposal.proposer || comp.getPriorVotes(proposal.proposer, sub256(block.number, 1)) < proposalThreshold, "GovernorBravo::cancel: proposer above threshold");
+
+ proposal.canceled = true;
+ for (uint i = 0; i < proposal.targets.length; i++) {
+ timelock.cancelTransaction(proposal.targets[i], proposal.values[i], proposal.signatures[i], proposal.calldatas[i], proposal.eta);
+ }
+
+ emit ProposalCanceled(proposalId);
+ }
+
+ /**
+ * @notice Gets actions of a proposal
+ * @param proposalId the id of the proposal
+ * @return Targets, values, signatures, and calldatas of the proposal actions
+ */
+ function getActions(uint proposalId) external view returns (address[] memory targets, uint[] memory values, string[] memory signatures, bytes[] memory calldatas) {
+ Proposal storage p = proposals[proposalId];
+ return (p.targets, p.values, p.signatures, p.calldatas);
+ }
+
+ /**
+ * @notice Gets the receipt for a voter on a given proposal
+ * @param proposalId the id of proposal
+ * @param voter The address of the voter
+ * @return The voting receipt
+ */
+ function getReceipt(uint proposalId, address voter) external view returns (Receipt memory) {
+ return proposals[proposalId].receipts[voter];
+ }
+
+ /**
+ * @notice Gets the state of a proposal
+ * @param proposalId The id of the proposal
+ * @return Proposal state
+ */
+ function state(uint proposalId) public view returns (ProposalState) {
+ require(proposalCount >= proposalId && proposalId > initialProposalId, "GovernorBravo::state: invalid proposal id");
+ Proposal storage proposal = proposals[proposalId];
+ if (proposal.canceled) {
+ return ProposalState.Canceled;
+ } else if (block.number <= proposal.startBlock) {
+ return ProposalState.Pending;
+ } else if (block.number <= proposal.endBlock) {
+ return ProposalState.Active;
+ } else if (proposal.forVotes <= proposal.againstVotes || proposal.forVotes < quorumVotes) {
+ return ProposalState.Defeated;
+ } else if (proposal.eta == 0) {
+ return ProposalState.Succeeded;
+ } else if (proposal.executed) {
+ return ProposalState.Executed;
+ } else if (block.timestamp >= add256(proposal.eta, timelock.GRACE_PERIOD())) {
+ return ProposalState.Expired;
+ } else {
+ return ProposalState.Queued;
+ }
+ }
+
+ /**
+ * @notice Cast a vote for a proposal
+ * @param proposalId The id of the proposal to vote on
+ * @param support The support value for the vote. 0=against, 1=for, 2=abstain
+ */
+ function castVote(uint proposalId, uint8 support) external {
+ emit VoteCast(msg.sender, proposalId, support, castVoteInternal(msg.sender, proposalId, support), "");
+ }
+
+ /**
+ * @notice Cast a vote for a proposal with a reason
+ * @param proposalId The id of the proposal to vote on
+ * @param support The support value for the vote. 0=against, 1=for, 2=abstain
+ * @param reason The reason given for the vote by the voter
+ */
+ function castVoteWithReason(uint proposalId, uint8 support, string calldata reason) external {
+ emit VoteCast(msg.sender, proposalId, support, castVoteInternal(msg.sender, proposalId, support), reason);
+ }
+
+ /**
+ * @notice Cast a vote for a proposal by signature
+ * @dev External function that accepts EIP-712 signatures for voting on proposals.
+ */
+ function castVoteBySig(uint proposalId, uint8 support, uint8 v, bytes32 r, bytes32 s) external {
+ bytes32 domainSeparator = keccak256(abi.encode(DOMAIN_TYPEHASH, keccak256(bytes(name)), getChainIdInternal(), address(this)));
+ bytes32 structHash = keccak256(abi.encode(BALLOT_TYPEHASH, proposalId, support));
+ bytes32 digest = keccak256(abi.encodePacked("\x19\x01", domainSeparator, structHash));
+ address signatory = ecrecover(digest, v, r, s);
+ require(signatory != address(0), "GovernorBravo::castVoteBySig: invalid signature");
+ emit VoteCast(signatory, proposalId, support, castVoteInternal(signatory, proposalId, support), "");
+ }
+
+ /**
+ * @notice Internal function that caries out voting logic
+ * @param voter The voter that is casting their vote
+ * @param proposalId The id of the proposal to vote on
+ * @param support The support value for the vote. 0=against, 1=for, 2=abstain
+ * @return The number of votes cast
+ */
+ function castVoteInternal(address voter, uint proposalId, uint8 support) internal returns (uint96) {
+ require(state(proposalId) == ProposalState.Active, "GovernorBravo::castVoteInternal: voting is closed");
+ require(support <= 2, "GovernorBravo::castVoteInternal: invalid vote type");
+ Proposal storage proposal = proposals[proposalId];
+ Receipt storage receipt = proposal.receipts[voter];
+ require(receipt.hasVoted == false, "GovernorBravo::castVoteInternal: voter already voted");
+ uint96 votes = comp.getPriorVotes(voter, proposal.startBlock);
+
+ if (support == 0) {
+ proposal.againstVotes = add256(proposal.againstVotes, votes);
+ } else if (support == 1) {
+ proposal.forVotes = add256(proposal.forVotes, votes);
+ } else if (support == 2) {
+ proposal.abstainVotes = add256(proposal.abstainVotes, votes);
+ }
+
+ receipt.hasVoted = true;
+ receipt.support = support;
+ receipt.votes = votes;
+
+ return votes;
+ }
+
+ /**
+ * @notice Admin function for setting the voting delay
+ * @param newVotingDelay new voting delay, in blocks
+ */
+ function _setVotingDelay(uint newVotingDelay) external {
+ require(msg.sender == admin, "GovernorBravo::_setVotingDelay: admin only");
+ require(newVotingDelay >= MIN_VOTING_DELAY && newVotingDelay <= MAX_VOTING_DELAY, "GovernorBravo::_setVotingDelay: invalid voting delay");
+ uint oldVotingDelay = votingDelay;
+ votingDelay = newVotingDelay;
+
+ emit VotingDelaySet(oldVotingDelay,votingDelay);
+ }
+
+ /**
+ * @notice Admin function for setting the voting period
+ * @param newVotingPeriod new voting period, in blocks
+ */
+ function _setVotingPeriod(uint newVotingPeriod) external {
+ require(msg.sender == admin, "GovernorBravo::_setVotingPeriod: admin only");
+ require(newVotingPeriod >= MIN_VOTING_PERIOD && newVotingPeriod <= MAX_VOTING_PERIOD, "GovernorBravo::_setVotingPeriod: invalid voting period");
+ uint oldVotingPeriod = votingPeriod;
+ votingPeriod = newVotingPeriod;
+
+ emit VotingPeriodSet(oldVotingPeriod, votingPeriod);
+ }
+
+ /**
+ * @notice Admin function for setting the proposal threshold
+ * @dev newProposalThreshold must be greater than the hardcoded min
+ * @param newProposalThreshold new proposal threshold
+ */
+ function _setProposalThreshold(uint newProposalThreshold) external {
+ require(msg.sender == admin, "GovernorBravo::_setProposalThreshold: admin only");
+ require(newProposalThreshold >= MIN_PROPOSAL_THRESHOLD && newProposalThreshold <= MAX_PROPOSAL_THRESHOLD, "GovernorBravo::_setProposalThreshold: invalid proposal threshold");
+ uint oldProposalThreshold = proposalThreshold;
+ proposalThreshold = newProposalThreshold;
+
+ emit ProposalThresholdSet(oldProposalThreshold, proposalThreshold);
+ }
+
+ function setInitialProposalId() external {
+ initialProposalId = 1;
+ proposalCount = 1;
+ }
+
+ /**
+ * @notice Begins transfer of admin rights. The newPendingAdmin must call `_acceptAdmin` to finalize the transfer.
+ * @dev Admin function to begin change of admin. The newPendingAdmin must call `_acceptAdmin` to finalize the transfer.
+ * @param newPendingAdmin New pending admin.
+ */
+ function _setPendingAdmin(address newPendingAdmin) external {
+ // Check caller = admin
+ require(msg.sender == admin, "GovernorBravo:_setPendingAdmin: admin only");
+
+ // Save current value, if any, for inclusion in log
+ address oldPendingAdmin = pendingAdmin;
+
+ // Store pendingAdmin with value newPendingAdmin
+ pendingAdmin = newPendingAdmin;
+
+ // Emit NewPendingAdmin(oldPendingAdmin, newPendingAdmin)
+ emit NewPendingAdmin(oldPendingAdmin, newPendingAdmin);
+ }
+
+ /**
+ * @notice Accepts transfer of admin rights. msg.sender must be pendingAdmin
+ * @dev Admin function for pending admin to accept role and update admin
+ */
+ function _acceptAdmin() external {
+ // Check caller is pendingAdmin and pendingAdmin ≠address(0)
+ require(msg.sender == pendingAdmin && msg.sender != address(0), "GovernorBravo:_acceptAdmin: pending admin only");
+
+ // Save current values for inclusion in log
+ address oldAdmin = admin;
+ address oldPendingAdmin = pendingAdmin;
+
+ // Store admin with value pendingAdmin
+ admin = pendingAdmin;
+
+ // Clear the pending value
+ pendingAdmin = address(0);
+
+ emit NewAdmin(oldAdmin, admin);
+ emit NewPendingAdmin(oldPendingAdmin, pendingAdmin);
+ }
+
+ function add256(uint256 a, uint256 b) internal pure returns (uint) {
+ uint c = a + b;
+ require(c >= a, "addition overflow");
+ return c;
+ }
+
+ function sub256(uint256 a, uint256 b) internal pure returns (uint) {
+ require(b <= a, "subtraction underflow");
+ return a - b;
+ }
+
+ function getChainIdInternal() internal pure returns (uint) {
+ uint chainId;
+ assembly { chainId := chainid() }
+ return chainId;
+ }
+}
\ No newline at end of file
diff --git a/packages/chain-events/eth/contracts/Compound/GovernorBravoDelegator.sol b/packages/chain-events/eth/contracts/Compound/GovernorBravoDelegator.sol
new file mode 100644
index 00000000000..a3f12377667
--- /dev/null
+++ b/packages/chain-events/eth/contracts/Compound/GovernorBravoDelegator.sol
@@ -0,0 +1,79 @@
+pragma solidity ^0.5.16;
+pragma experimental ABIEncoderV2;
+
+import "./GovernorBravoInterfaces.sol";
+
+contract GovernorBravoDelegator is GovernorBravoDelegatorStorage, GovernorBravoEvents {
+ constructor(
+ address timelock_,
+ address comp_,
+ address admin_,
+ address implementation_,
+ uint votingPeriod_,
+ uint votingDelay_,
+ uint proposalThreshold_) public {
+
+ // Admin set to msg.sender for initialization
+ admin = msg.sender;
+
+ delegateTo(implementation_, abi.encodeWithSignature("initialize(address,address,uint256,uint256,uint256)",
+ timelock_,
+ comp_,
+ votingPeriod_,
+ votingDelay_,
+ proposalThreshold_));
+
+ _setImplementation(implementation_);
+
+ admin = admin_;
+ }
+
+
+ /**
+ * @notice Called by the admin to update the implementation of the delegator
+ * @param implementation_ The address of the new implementation for delegation
+ */
+ function _setImplementation(address implementation_) public {
+ require(msg.sender == admin, "GovernorBravoDelegator::_setImplementation: admin only");
+ require(implementation_ != address(0), "GovernorBravoDelegator::_setImplementation: invalid implementation address");
+
+ address oldImplementation = implementation;
+ implementation = implementation_;
+
+ emit NewImplementation(oldImplementation, implementation);
+ }
+
+ /**
+ * @notice Internal method to delegate execution to another contract
+ * @dev It returns to the external caller whatever the implementation returns or forwards reverts
+ * @param callee The contract to delegatecall
+ * @param data The raw data to delegatecall
+ */
+ function delegateTo(address callee, bytes memory data) internal {
+ (bool success, bytes memory returnData) = callee.delegatecall(data);
+ assembly {
+ if eq(success, 0) {
+ revert(add(returnData, 0x20), returndatasize)
+ }
+ }
+ }
+
+ /**
+ * @dev Delegates execution to an implementation contract.
+ * It returns to the external caller whatever the implementation returns
+ * or forwards reverts.
+ */
+ function () external payable {
+ // delegate all other functions to current implementation
+ (bool success, ) = implementation.delegatecall(msg.data);
+
+ assembly {
+ let free_mem_ptr := mload(0x40)
+ returndatacopy(free_mem_ptr, 0, returndatasize)
+
+ switch success
+ case 0 { revert(free_mem_ptr, returndatasize) }
+ default { return(free_mem_ptr, returndatasize) }
+ }
+ }
+}
\ No newline at end of file
diff --git a/packages/chain-events/eth/contracts/Compound/GovernorBravoImmutable.sol b/packages/chain-events/eth/contracts/Compound/GovernorBravoImmutable.sol
new file mode 100644
index 00000000000..0d3db7fead9
--- /dev/null
+++ b/packages/chain-events/eth/contracts/Compound/GovernorBravoImmutable.sol
@@ -0,0 +1,37 @@
+pragma solidity ^0.5.16;
+pragma experimental ABIEncoderV2;
+
+import "./GovernorBravoDelegateMock.sol";
+
+contract GovernorBravoImmutable is GovernorBravoDelegateMock {
+
+ constructor(
+ address timelock_,
+ address comp_,
+ address admin_,
+ uint votingPeriod_,
+ uint votingDelay_,
+ uint proposalThreshold_) public {
+ admin = msg.sender;
+ initialize(timelock_, comp_, votingPeriod_, votingDelay_, proposalThreshold_);
+
+ admin = admin_;
+ }
+
+
+ function initialize(address timelock_, address comp_, uint votingPeriod_, uint votingDelay_, uint proposalThreshold_) public {
+ require(msg.sender == admin, "GovernorBravo::initialize: admin only");
+ require(address(timelock) == address(0), "GovernorBravo::initialize: can only initialize once");
+
+ timelock = TimelockInterface(timelock_);
+ comp = CompInterface(comp_);
+ votingPeriod = votingPeriod_;
+ votingDelay = votingDelay_;
+ proposalThreshold = proposalThreshold_;
+ }
+
+ function _initiate() public {
+ proposalCount = 1;
+ initialProposalId = 1;
+ }
+}
\ No newline at end of file
diff --git a/packages/chain-events/eth/contracts/Compound/GovernorBravoInterfaces.sol b/packages/chain-events/eth/contracts/Compound/GovernorBravoInterfaces.sol
new file mode 100644
index 00000000000..153e5af11c8
--- /dev/null
+++ b/packages/chain-events/eth/contracts/Compound/GovernorBravoInterfaces.sol
@@ -0,0 +1,184 @@
+pragma solidity ^0.5.16;
+pragma experimental ABIEncoderV2;
+
+
+contract GovernorBravoEvents {
+ /// @notice An event emitted when a new proposal is created
+ event ProposalCreated(uint id, address proposer, address[] targets, uint[] values, string[] signatures, bytes[] calldatas, uint startBlock, uint endBlock, string description);
+
+ /// @notice An event emitted when a vote has been cast on a proposal
+ /// @param voter The address which casted a vote
+ /// @param proposalId The proposal id which was voted on
+ /// @param support Support value for the vote. 0=against, 1=for, 2=abstain
+ /// @param votes Number of votes which were cast by the voter
+ /// @param reason The reason given for the vote by the voter
+ event VoteCast(address indexed voter, uint proposalId, uint8 support, uint votes, string reason);
+
+ /// @notice An event emitted when a proposal has been canceled
+ event ProposalCanceled(uint id);
+
+ /// @notice An event emitted when a proposal has been queued in the Timelock
+ event ProposalQueued(uint id, uint eta);
+
+ /// @notice An event emitted when a proposal has been executed in the Timelock
+ event ProposalExecuted(uint id);
+
+ /// @notice An event emitted when the voting delay is set
+ event VotingDelaySet(uint oldVotingDelay, uint newVotingDelay);
+
+ /// @notice An event emitted when the voting period is set
+ event VotingPeriodSet(uint oldVotingPeriod, uint newVotingPeriod);
+
+ /// @notice Emitted when implementation is changed
+ event NewImplementation(address oldImplementation, address newImplementation);
+
+ /// @notice Emitted when proposal threshold is set
+ event ProposalThresholdSet(uint oldProposalThreshold, uint newProposalThreshold);
+
+ /// @notice Emitted when pendingAdmin is changed
+ event NewPendingAdmin(address oldPendingAdmin, address newPendingAdmin);
+
+ /// @notice Emitted when pendingAdmin is accepted, which means admin is updated
+ event NewAdmin(address oldAdmin, address newAdmin);
+}
+
+contract GovernorBravoDelegatorStorage {
+ /// @notice Administrator for this contract
+ address public admin;
+
+ /// @notice Pending administrator for this contract
+ address public pendingAdmin;
+
+ /// @notice Active brains of Governor
+ address public implementation;
+}
+
+
+/**
+ * @title Storage for Governor Bravo Delegate
+ * @notice For future upgrades, do not change GovernorBravoDelegateStorageV1. Create a new
+ * contract which implements GovernorBravoDelegateStorageV1 and following the naming convention
+ * GovernorBravoDelegateStorageVX.
+ */
+contract GovernorBravoDelegateStorageV1 is GovernorBravoDelegatorStorage {
+
+ /// @notice The delay before voting on a proposal may take place, once proposed, in blocks
+ uint public votingDelay;
+
+ /// @notice The duration of voting on a proposal, in blocks
+ uint public votingPeriod;
+
+ /// @notice The number of votes required in order for a voter to become a proposer
+ uint public proposalThreshold;
+
+ /// @notice Initial proposal id set at become
+ uint public initialProposalId;
+
+ /// @notice The total number of proposals
+ uint public proposalCount;
+
+ /// @notice The address of the Compound Protocol Timelock
+ TimelockInterface public timelock;
+
+ /// @notice The address of the Compound governance token
+ CompInterface public comp;
+
+ /// @notice The official record of all proposals ever proposed
+ mapping (uint => Proposal) public proposals;
+
+ /// @notice The latest proposal for each proposer
+ mapping (address => uint) public latestProposalIds;
+
+
+ struct Proposal {
+ /// @notice Unique id for looking up a proposal
+ uint id;
+
+ /// @notice Creator of the proposal
+ address proposer;
+
+ /// @notice The timestamp that the proposal will be available for execution, set once the vote succeeds
+ uint eta;
+
+ /// @notice the ordered list of target addresses for calls to be made
+ address[] targets;
+
+ /// @notice The ordered list of values (i.e. msg.value) to be passed to the calls to be made
+ uint[] values;
+
+ /// @notice The ordered list of function signatures to be called
+ string[] signatures;
+
+ /// @notice The ordered list of calldata to be passed to each call
+ bytes[] calldatas;
+
+ /// @notice The block at which voting begins: holders must delegate their votes prior to this block
+ uint startBlock;
+
+ /// @notice The block at which voting ends: votes must be cast prior to this block
+ uint endBlock;
+
+ /// @notice Current number of votes in favor of this proposal
+ uint forVotes;
+
+ /// @notice Current number of votes in opposition to this proposal
+ uint againstVotes;
+
+ /// @notice Current number of votes for abstaining for this proposal
+ uint abstainVotes;
+
+ /// @notice Flag marking whether the proposal has been canceled
+ bool canceled;
+
+ /// @notice Flag marking whether the proposal has been executed
+ bool executed;
+
+ /// @notice Receipts of ballots for the entire set of voters
+ mapping (address => Receipt) receipts;
+ }
+
+ /// @notice Ballot receipt record for a voter
+ struct Receipt {
+ /// @notice Whether or not a vote has been cast
+ bool hasVoted;
+
+ /// @notice Whether or not the voter supports the proposal or abstains
+ uint8 support;
+
+ /// @notice The number of votes the voter had, which were cast
+ uint96 votes;
+ }
+
+ /// @notice Possible states that a proposal may be in
+ enum ProposalState {
+ Pending,
+ Active,
+ Canceled,
+ Defeated,
+ Succeeded,
+ Queued,
+ Expired,
+ Executed
+ }
+}
+
+interface TimelockInterface {
+ function delay() external view returns (uint);
+ function GRACE_PERIOD() external view returns (uint);
+ function acceptAdmin() external;
+ function queuedTransactions(bytes32 hash) external view returns (bool);
+ function queueTransaction(address target, uint value, string calldata signature, bytes calldata data, uint eta) external returns (bytes32);
+ function cancelTransaction(address target, uint value, string calldata signature, bytes calldata data, uint eta) external;
+ function executeTransaction(address target, uint value, string calldata signature, bytes calldata data, uint eta) external payable returns (bytes memory);
+}
+
+interface CompInterface {
+ function getPriorVotes(address account, uint blockNumber) external view returns (uint96);
+}
+
+/*
+interface GovernorAlpha {
+ /// @notice The total number of proposals
+ function proposalCount() external returns (uint);
+}
+*/
\ No newline at end of file
diff --git a/packages/chain-events/eth/contracts/Compound/GovernorMock.sol b/packages/chain-events/eth/contracts/Compound/GovernorMock.sol
new file mode 100644
index 00000000000..895ce730936
--- /dev/null
+++ b/packages/chain-events/eth/contracts/Compound/GovernorMock.sol
@@ -0,0 +1,128 @@
+// SPDX-License-Identifier: MIT
+
+pragma solidity ^0.8.0;
+
+import "@openzeppelin/contracts-governance/governance/extensions/GovernorTimelockControl.sol";
+import "@openzeppelin/contracts-governance/governance/extensions/GovernorCountingSimple.sol";
+import "@openzeppelin/contracts-governance/governance/extensions/GovernorVotesQuorumFraction.sol";
+import "@openzeppelin/contracts-governance/governance/extensions/GovernorProposalThreshold.sol";
+
+contract GovernorMock is GovernorTimelockControl, GovernorProposalThreshold, GovernorVotesQuorumFraction, GovernorCountingSimple {
+ uint256 immutable _votingDelay;
+ uint256 immutable _votingPeriod;
+ uint256 immutable _proposalThreshold;
+
+ constructor(
+ string memory name_,
+ ERC20Votes token_,
+ uint256 votingDelay_,
+ uint256 votingPeriod_,
+ TimelockController timelock_,
+ uint256 quorumNumerator_,
+ uint256 proposalThreshold_
+ )
+ Governor(name_)
+ GovernorTimelockControl(timelock_)
+ GovernorVotes(token_)
+ GovernorVotesQuorumFraction(quorumNumerator_)
+ {
+ _votingDelay = votingDelay_;
+ _votingPeriod = votingPeriod_;
+ _proposalThreshold = proposalThreshold_;
+ }
+
+ function supportsInterface(bytes4 interfaceId)
+ public
+ view
+ virtual
+ override(Governor, GovernorTimelockControl)
+ returns (bool)
+ {
+ return super.supportsInterface(interfaceId);
+ }
+
+ function votingDelay() public view override returns (uint256) {
+ return _votingDelay;
+ }
+
+ function votingPeriod() public view override returns (uint256) {
+ return _votingPeriod;
+ }
+
+ function quorum(uint256 blockNumber)
+ public
+ view
+ override(IGovernor, GovernorVotesQuorumFraction)
+ returns (uint256)
+ {
+ return super.quorum(blockNumber);
+ }
+
+ function cancel(
+ address[] memory targets,
+ uint256[] memory values,
+ bytes[] memory calldatas,
+ bytes32 descriptionHash
+ ) public returns (uint256 proposalId) {
+ return _cancel(targets, values, calldatas, descriptionHash);
+ }
+
+ /**
+ * Overriden functions
+ */
+ function proposalThreshold() public view virtual override returns (uint256) {
+ return _proposalThreshold;
+ }
+
+ function propose(
+ address[] memory targets,
+ uint256[] memory values,
+ bytes[] memory calldatas,
+ string memory description
+ ) public virtual override(IGovernor, Governor, GovernorProposalThreshold) returns (uint256) {
+ return super.propose(targets, values, calldatas, description);
+ }
+
+ function state(uint256 proposalId)
+ public
+ view
+ virtual
+ override(Governor, GovernorTimelockControl)
+ returns (ProposalState)
+ {
+ return super.state(proposalId);
+ }
+
+ function _execute(
+ uint256 proposalId,
+ address[] memory targets,
+ uint256[] memory values,
+ bytes[] memory calldatas,
+ bytes32 descriptionHash
+ ) internal virtual override(Governor, GovernorTimelockControl) {
+ super._execute(proposalId, targets, values, calldatas, descriptionHash);
+ }
+
+ function _cancel(
+ address[] memory targets,
+ uint256[] memory values,
+ bytes[] memory calldatas,
+ bytes32 descriptionHash
+ ) internal virtual override(Governor, GovernorTimelockControl) returns (uint256 proposalId) {
+ return super._cancel(targets, values, calldatas, descriptionHash);
+ }
+
+ function getVotes(address account, uint256 blockNumber)
+ public
+ view
+ virtual
+ override(IGovernor, GovernorVotes)
+ returns (uint256)
+ {
+ return super.getVotes(account, blockNumber);
+ }
+
+ function _executor() internal view virtual override(Governor, GovernorTimelockControl) returns (address) {
+ return super._executor();
+ }
+}
\ No newline at end of file
diff --git a/packages/chain-events/eth/contracts/Compound/MPond.sol b/packages/chain-events/eth/contracts/Compound/MPond.sol
new file mode 100644
index 00000000000..5d9db10860f
--- /dev/null
+++ b/packages/chain-events/eth/contracts/Compound/MPond.sol
@@ -0,0 +1,578 @@
+pragma solidity >=0.4.21 <0.7.0;
+pragma experimental ABIEncoderV2;
+
+
+contract MPond {
+ /// @notice EIP-20 token name for this token
+ string public constant name = "Marlin Governance Token";
+
+ /// @notice EIP-20 token symbol for this token
+ string public constant symbol = "MPOND";
+
+ /// @notice EIP-20 token decimals for this token
+ uint8 public constant decimals = 18;
+
+ /// @notice Total number of tokens in circulation
+ uint256 public constant totalSupply = 10000e18; // 10k MPond
+ uint256 public constant bridgeSupply = 7000e18; // 3k MPond
+ /// @notice Allowance amounts on behalf of others
+ mapping(address => mapping(address => uint96)) internal allowances;
+
+ /// @notice Official record of token balances for each account
+ mapping(address => uint96) internal balances;
+
+ /// @notice A record of each accounts delegate
+ mapping(address => mapping(address => uint96)) public delegates;
+
+ /// @notice A checkpoint for marking number of votes from a given block
+ struct Checkpoint {
+ uint32 fromBlock;
+ uint96 votes;
+ }
+
+ /// @notice A record of votes checkpoints for each account, by index
+ mapping(address => mapping(uint32 => Checkpoint)) public checkpoints;
+
+ /// @notice The number of checkpoints for each account
+ mapping(address => uint32) public numCheckpoints;
+
+ /// @notice The EIP-712 typehash for the contract's domain
+ bytes32 public constant DOMAIN_TYPEHASH = keccak256(
+ "EIP712Domain(string name,uint256 chainId,address verifyingContract)"
+ );
+
+ /// @notice The EIP-712 typehash for the delegation struct used by the contract
+ bytes32 public constant DELEGATION_TYPEHASH = keccak256(
+ "Delegation(address delegatee,uint256 nonce,uint256 expiry,uint96 amount)"
+ );
+
+ /// @notice The EIP-712 typehash for the delegation struct used by the contract
+ bytes32 public constant UNDELEGATION_TYPEHASH = keccak256(
+ "Unelegation(address delegatee,uint256 nonce,uint256 expiry,uint96 amount)"
+ );
+ /// @notice A record of states for signing / validating signatures
+ mapping(address => uint256) public nonces;
+
+ /// customized params
+ address public admin;
+ mapping(address => bool) public isWhiteListed;
+ bool public enableAllTranfers = true;
+
+ /// @notice An event thats emitted when an account changes its delegate
+ event DelegateChanged(
+ address indexed delegator,
+ address indexed fromDelegate,
+ address indexed toDelegate
+ );
+
+ /// @notice An event thats emitted when a delegate account's vote balance changes
+ event DelegateVotesChanged(
+ address indexed delegate,
+ uint256 previousBalance,
+ uint256 newBalance
+ );
+
+ /// @notice The standard EIP-20 transfer event
+ event Transfer(address indexed from, address indexed to, uint256 amount);
+
+ /// @notice The standard EIP-20 approval event
+ event Approval(
+ address indexed owner,
+ address indexed spender,
+ uint256 amount
+ );
+
+ /**
+ * @notice Construct a new Comp token
+ * @param account The initial account to grant all the tokens
+ */
+ constructor(address account, address bridge) public {
+ require(
+ account != bridge,
+ "Bridge and accoutn should not be the same address"
+ );
+ balances[bridge] = uint96(bridgeSupply);
+ delegates[bridge][address(0)] = uint96(bridgeSupply);
+ isWhiteListed[bridge] = true;
+ emit Transfer(address(0), bridge, bridgeSupply);
+
+ uint96 remainingSupply = sub96(
+ uint96(totalSupply),
+ uint96(bridgeSupply),
+ "Comp: Subtraction overflow in the constructor"
+ );
+ balances[account] = remainingSupply;
+ delegates[account][address(0)] = remainingSupply;
+ isWhiteListed[account] = true;
+ emit Transfer(address(0), account, uint256(remainingSupply));
+ }
+
+ function addWhiteListAddress(address _address) external returns (bool) {
+ require(msg.sender == admin, "Only admin can whitelist");
+ isWhiteListed[_address] = true;
+ return true;
+ }
+
+ function enableAllTransfers() external returns (bool) {
+ require(msg.sender == admin, "Only enable can enable all transfers");
+ enableAllTranfers = true;
+ return true;
+ }
+
+ function isWhiteListedTransfer(address _address1, address _address2)
+ public
+ view
+ returns (bool)
+ {
+ return
+ (isWhiteListed[_address1] || isWhiteListed[_address2]) ||
+ enableAllTranfers;
+ }
+
+ /**
+ * @notice Get the number of tokens `spender` is approved to spend on behalf of `account`
+ * @param account The address of the account holding the funds
+ * @param spender The address of the account spending the funds
+ * @return The number of tokens approved
+ */
+ function allowance(address account, address spender)
+ external
+ view
+ returns (uint256)
+ {
+ return allowances[account][spender];
+ }
+
+ /**
+ * @notice Approve `spender` to transfer up to `amount` from `src`
+ * @dev This will overwrite the approval amount for `spender`
+ * and is subject to issues noted [here](https://eips.ethereum.org/EIPS/eip-20#approve)
+ * @param spender The address of the account which may transfer tokens
+ * @param rawAmount The number of tokens that are approved (2^256-1 means infinite)
+ * @return Whether or not the approval succeeded
+ */
+ function approve(address spender, uint256 rawAmount)
+ external
+ returns (bool)
+ {
+ uint96 amount;
+ if (rawAmount == uint256(-1)) {
+ amount = uint96(-1);
+ } else {
+ amount = safe96(rawAmount, "Comp::approve: amount exceeds 96 bits");
+ }
+
+ allowances[msg.sender][spender] = amount;
+
+ emit Approval(msg.sender, spender, amount);
+ return true;
+ }
+
+ /**
+ * @notice Get the number of tokens held by the `account`
+ * @param account The address of the account to get the balance of
+ * @return The number of tokens held
+ */
+ function balanceOf(address account) external view returns (uint256) {
+ return balances[account];
+ }
+
+ /**
+ * @notice Transfer `amount` tokens from `msg.sender` to `dst`
+ * @param dst The address of the destination account
+ * @param rawAmount The number of tokens to transfer
+ * @return Whether or not the transfer succeeded
+ */
+ function transfer(address dst, uint256 rawAmount) external returns (bool) {
+ require(
+ isWhiteListedTransfer(msg.sender, dst),
+ "Atleast of the address (msg.sender or dst) should be whitelisted"
+ );
+ uint96 amount = safe96(
+ rawAmount,
+ "Comp::transfer: amount exceeds 96 bits"
+ );
+ _transferTokens(msg.sender, dst, amount);
+ return true;
+ }
+
+ /**
+ * @notice Transfer `amount` tokens from `src` to `dst`
+ * @param src The address of the source account
+ * @param dst The address of the destination account
+ * @param rawAmount The number of tokens to transfer
+ * @return Whether or not the transfer succeeded
+ */
+ function transferFrom(
+ address src,
+ address dst,
+ uint256 rawAmount
+ ) external returns (bool) {
+ require(
+ isWhiteListedTransfer(msg.sender, dst),
+ "Atleast of the address (src or dst) should be whitelisted"
+ );
+ address spender = msg.sender;
+ uint96 spenderAllowance = allowances[src][spender];
+ uint96 amount = safe96(
+ rawAmount,
+ "Comp::approve: amount exceeds 96 bits"
+ );
+
+ if (spender != src && spenderAllowance != uint96(-1)) {
+ uint96 newAllowance = sub96(
+ spenderAllowance,
+ amount,
+ "Comp::transferFrom: transfer amount exceeds spender allowance"
+ );
+ allowances[src][spender] = newAllowance;
+
+ emit Approval(src, spender, newAllowance);
+ }
+
+ _transferTokens(src, dst, amount);
+ return true;
+ }
+
+ /**
+ * @notice Delegate votes from `msg.sender` to `delegatee`
+ * @param delegatee The address to delegate votes to
+ */
+ function delegate(address delegatee, uint96 amount) public {
+ return _delegate(msg.sender, delegatee, amount);
+ }
+
+ function undelegate(address delegatee, uint96 amount) public {
+ return _undelegate(msg.sender, delegatee, amount);
+ }
+
+ /**
+ * @notice Delegates votes from signatory to `delegatee`
+ * @param delegatee The address to delegate votes to
+ * @param nonce The contract state required to match the signature
+ * @param expiry The time at which to expire the signature
+ * @param v The recovery byte of the signature
+ * @param r Half of the ECDSA signature pair
+ * @param s Half of the ECDSA signature pair
+ */
+ function delegateBySig(
+ address delegatee,
+ uint256 nonce,
+ uint256 expiry,
+ uint8 v,
+ bytes32 r,
+ bytes32 s,
+ uint96 amount
+ ) public {
+ bytes32 domainSeparator = keccak256(
+ abi.encode(
+ DOMAIN_TYPEHASH,
+ keccak256(bytes(name)),
+ getChainId(),
+ address(this)
+ )
+ );
+ bytes32 structHash = keccak256(
+ abi.encode(DELEGATION_TYPEHASH, delegatee, nonce, expiry, amount)
+ );
+ bytes32 digest = keccak256(
+ abi.encodePacked("\x19\x01", domainSeparator, structHash)
+ );
+ address signatory = ecrecover(digest, v, r, s);
+ require(
+ signatory != address(0),
+ "Comp::delegateBySig: invalid signature"
+ );
+ require(
+ nonce == nonces[signatory]++,
+ "Comp::delegateBySig: invalid nonce"
+ );
+ require(now <= expiry, "Comp::delegateBySig: signature expired");
+ return _delegate(signatory, delegatee, amount);
+ }
+
+ function undelegateBySig(
+ address delegatee,
+ uint256 nonce,
+ uint256 expiry,
+ uint8 v,
+ bytes32 r,
+ bytes32 s,
+ uint96 amount
+ ) public {
+ bytes32 domainSeparator = keccak256(
+ abi.encode(
+ DOMAIN_TYPEHASH,
+ keccak256(bytes(name)),
+ getChainId(),
+ address(this)
+ )
+ );
+ bytes32 structHash = keccak256(
+ abi.encode(UNDELEGATION_TYPEHASH, delegatee, nonce, expiry, amount)
+ );
+ bytes32 digest = keccak256(
+ abi.encodePacked("\x19\x01", domainSeparator, structHash)
+ );
+ address signatory = ecrecover(digest, v, r, s);
+ require(
+ signatory != address(0),
+ "Comp::undelegateBySig: invalid signature"
+ );
+ require(
+ nonce == nonces[signatory]++,
+ "Comp::undelegateBySig: invalid nonce"
+ );
+ require(now <= expiry, "Comp::undelegateBySig: signature expired");
+ return _undelegate(signatory, delegatee, amount);
+ }
+
+ /**
+ * @notice Gets the current votes balance for `account`
+ * @param account The address to get votes balance
+ * @return The number of current votes for `account`
+ */
+ function getCurrentVotes(address account) external view returns (uint96) {
+ uint32 nCheckpoints = numCheckpoints[account];
+ return
+ nCheckpoints > 0 ? checkpoints[account][nCheckpoints - 1].votes : 0;
+ }
+
+ /**
+ * @notice Determine the prior number of votes for an account as of a block number
+ * @dev Block number must be a finalized block or else this function will revert to prevent misinformation.
+ * @param account The address of the account to check
+ * @param blockNumber The block number to get the vote balance at
+ * @return The number of votes the account had as of the given block
+ */
+ function getPriorVotes(address account, uint256 blockNumber)
+ public
+ view
+ returns (uint96)
+ {
+ require(
+ blockNumber < block.number,
+ "Comp::getPriorVotes: not yet determined"
+ );
+
+ uint32 nCheckpoints = numCheckpoints[account];
+ if (nCheckpoints == 0) {
+ return 0;
+ }
+
+ // First check most recent balance
+ if (checkpoints[account][nCheckpoints - 1].fromBlock <= blockNumber) {
+ return checkpoints[account][nCheckpoints - 1].votes;
+ }
+
+ // Next check implicit zero balance
+ if (checkpoints[account][0].fromBlock > blockNumber) {
+ return 0;
+ }
+
+ uint32 lower = 0;
+ uint32 upper = nCheckpoints - 1;
+ while (upper > lower) {
+ uint32 center = upper - (upper - lower) / 2; // ceil, avoiding overflow
+ Checkpoint memory cp = checkpoints[account][center];
+ if (cp.fromBlock == blockNumber) {
+ return cp.votes;
+ } else if (cp.fromBlock < blockNumber) {
+ lower = center;
+ } else {
+ upper = center - 1;
+ }
+ }
+ return checkpoints[account][lower].votes;
+ }
+
+ function _delegate(
+ address delegator,
+ address delegatee,
+ uint96 amount
+ ) internal {
+ delegates[delegator][address(0)] = sub96(
+ delegates[delegator][address(0)],
+ amount,
+ "Comp: delegates underflow"
+ );
+ delegates[delegator][delegatee] = add96(
+ delegates[delegator][delegatee],
+ amount,
+ "Comp: delegates overflow"
+ );
+
+ emit DelegateChanged(delegator, address(0), delegatee);
+
+ _moveDelegates(address(0), delegatee, amount);
+ }
+
+ function _undelegate(
+ address delegator,
+ address delegatee,
+ uint96 amount
+ ) internal {
+ delegates[delegator][delegatee] = sub96(
+ delegates[delegator][delegatee],
+ amount,
+ "Comp: undelegates underflow"
+ );
+ delegates[delegator][address(0)] = add96(
+ delegates[delegator][address(0)],
+ amount,
+ "Comp: delegates underflow"
+ );
+ emit DelegateChanged(delegator, delegatee, address(0));
+ _moveDelegates(delegatee, address(0), amount);
+ }
+
+ function _transferTokens(
+ address src,
+ address dst,
+ uint96 amount
+ ) internal {
+ require(
+ src != address(0),
+ "Comp::_transferTokens: cannot transfer from the zero address"
+ );
+ require(
+ delegates[src][address(0)] >= amount,
+ "Comp: _transferTokens: undelegated amount should be greater than transfer amount"
+ );
+ require(
+ dst != address(0),
+ "Comp::_transferTokens: cannot transfer to the zero address"
+ );
+
+ balances[src] = sub96(
+ balances[src],
+ amount,
+ "Comp::_transferTokens: transfer amount exceeds balance"
+ );
+ delegates[src][address(0)] = sub96(
+ delegates[src][address(0)],
+ amount,
+ "Comp: _tranferTokens: undelegate subtraction error"
+ );
+
+ balances[dst] = add96(
+ balances[dst],
+ amount,
+ "Comp::_transferTokens: transfer amount overflows"
+ );
+ delegates[dst][address(0)] = add96(
+ delegates[dst][address(0)],
+ amount,
+ "Comp: _transferTokens: undelegate addition error"
+ );
+ emit Transfer(src, dst, amount);
+
+ // _moveDelegates(delegates[src], delegates[dst], amount);
+ }
+
+ function _moveDelegates(
+ address srcRep,
+ address dstRep,
+ uint96 amount
+ ) internal {
+ if (srcRep != dstRep && amount > 0) {
+ if (srcRep != address(0)) {
+ uint32 srcRepNum = numCheckpoints[srcRep];
+ uint96 srcRepOld = srcRepNum > 0
+ ? checkpoints[srcRep][srcRepNum - 1].votes
+ : 0;
+ uint96 srcRepNew = sub96(
+ srcRepOld,
+ amount,
+ "Comp::_moveVotes: vote amount underflows"
+ );
+ _writeCheckpoint(srcRep, srcRepNum, srcRepOld, srcRepNew);
+ }
+
+ if (dstRep != address(0)) {
+ uint32 dstRepNum = numCheckpoints[dstRep];
+ uint96 dstRepOld = dstRepNum > 0
+ ? checkpoints[dstRep][dstRepNum - 1].votes
+ : 0;
+ uint96 dstRepNew = add96(
+ dstRepOld,
+ amount,
+ "Comp::_moveVotes: vote amount overflows"
+ );
+ _writeCheckpoint(dstRep, dstRepNum, dstRepOld, dstRepNew);
+ }
+ }
+ }
+
+ function _writeCheckpoint(
+ address delegatee,
+ uint32 nCheckpoints,
+ uint96 oldVotes,
+ uint96 newVotes
+ ) internal {
+ uint32 blockNumber = safe32(
+ block.number,
+ "Comp::_writeCheckpoint: block number exceeds 32 bits"
+ );
+
+ if (
+ nCheckpoints > 0 &&
+ checkpoints[delegatee][nCheckpoints - 1].fromBlock == blockNumber
+ ) {
+ checkpoints[delegatee][nCheckpoints - 1].votes = newVotes;
+ } else {
+ checkpoints[delegatee][nCheckpoints] = Checkpoint(
+ blockNumber,
+ newVotes
+ );
+ numCheckpoints[delegatee] = nCheckpoints + 1;
+ }
+
+ emit DelegateVotesChanged(delegatee, oldVotes, newVotes);
+ }
+
+ function safe32(uint256 n, string memory errorMessage)
+ internal
+ pure
+ returns (uint32)
+ {
+ require(n < 2**32, errorMessage);
+ return uint32(n);
+ }
+
+ function safe96(uint256 n, string memory errorMessage)
+ internal
+ pure
+ returns (uint96)
+ {
+ require(n < 2**96, errorMessage);
+ return uint96(n);
+ }
+
+ function add96(
+ uint96 a,
+ uint96 b,
+ string memory errorMessage
+ ) internal pure returns (uint96) {
+ uint96 c = a + b;
+ require(c >= a, errorMessage);
+ return c;
+ }
+
+ function sub96(
+ uint96 a,
+ uint96 b,
+ string memory errorMessage
+ ) internal pure returns (uint96) {
+ require(b <= a, errorMessage);
+ return a - b;
+ }
+
+ function getChainId() internal pure returns (uint256) {
+ uint256 chainId;
+ assembly {
+ chainId := chainid()
+ }
+ return chainId;
+ }
+}
diff --git a/packages/chain-events/eth/contracts/Compound/Timelock.sol b/packages/chain-events/eth/contracts/Compound/Timelock.sol
new file mode 100644
index 00000000000..792258a45b2
--- /dev/null
+++ b/packages/chain-events/eth/contracts/Compound/Timelock.sol
@@ -0,0 +1,206 @@
+pragma solidity >=0.4.21 <0.7.0;
+
+import "@openzeppelin/contracts/math/SafeMath.sol";
+
+
+contract Timelock {
+ using SafeMath for uint256;
+
+ event NewAdmin(address indexed newAdmin);
+ event NewPendingAdmin(address indexed newPendingAdmin);
+ event NewDelay(uint256 indexed newDelay);
+ event CancelTransaction(
+ bytes32 indexed txHash,
+ address indexed target,
+ uint256 value,
+ string signature,
+ bytes data,
+ uint256 eta
+ );
+ event ExecuteTransaction(
+ bytes32 indexed txHash,
+ address indexed target,
+ uint256 value,
+ string signature,
+ bytes data,
+ uint256 eta
+ );
+ event QueueTransaction(
+ bytes32 indexed txHash,
+ address indexed target,
+ uint256 value,
+ string signature,
+ bytes data,
+ uint256 eta
+ );
+
+ uint256 public constant GRACE_PERIOD = 14 days;
+ uint256 public constant MINIMUM_DELAY = 2 days;
+ uint256 public constant MAXIMUM_DELAY = 30 days;
+
+ address public admin;
+ address public pendingAdmin;
+ uint256 public delay;
+
+ mapping(bytes32 => bool) public queuedTransactions;
+
+ constructor(address admin_, uint256 delay_) public {
+ require(
+ delay_ >= MINIMUM_DELAY,
+ "Timelock::constructor: Delay must exceed minimum delay."
+ );
+ require(
+ delay_ <= MAXIMUM_DELAY,
+ "Timelock::setDelay: Delay must not exceed maximum delay."
+ );
+
+ admin = admin_;
+ delay = delay_;
+ }
+
+ function() external payable {}
+
+ function setDelay(uint256 delay_) public {
+ require(
+ msg.sender == address(this),
+ "Timelock::setDelay: Call must come from Timelock."
+ );
+ require(
+ delay_ >= MINIMUM_DELAY,
+ "Timelock::setDelay: Delay must exceed minimum delay."
+ );
+ require(
+ delay_ <= MAXIMUM_DELAY,
+ "Timelock::setDelay: Delay must not exceed maximum delay."
+ );
+ delay = delay_;
+
+ emit NewDelay(delay);
+ }
+
+ function acceptAdmin() public {
+ require(
+ msg.sender == pendingAdmin,
+ "Timelock::acceptAdmin: Call must come from pendingAdmin."
+ );
+ admin = msg.sender;
+ pendingAdmin = address(0);
+
+ emit NewAdmin(admin);
+ }
+
+ function setPendingAdmin(address pendingAdmin_) public {
+ require(
+ msg.sender == address(this),
+ "Timelock::setPendingAdmin: Call must come from Timelock."
+ );
+ pendingAdmin = pendingAdmin_;
+
+ emit NewPendingAdmin(pendingAdmin);
+ }
+
+ function queueTransaction(
+ address target,
+ uint256 value,
+ string memory signature,
+ bytes memory data,
+ uint256 eta
+ ) public returns (bytes32) {
+ require(
+ msg.sender == admin,
+ "Timelock::queueTransaction: Call must come from admin."
+ );
+ require(
+ eta >= getBlockTimestamp().add(delay),
+ "Timelock::queueTransaction: Estimated execution block must satisfy delay."
+ );
+
+ bytes32 txHash = keccak256(
+ abi.encode(target, value, signature, data, eta)
+ );
+ queuedTransactions[txHash] = true;
+
+ emit QueueTransaction(txHash, target, value, signature, data, eta);
+ return txHash;
+ }
+
+ function cancelTransaction(
+ address target,
+ uint256 value,
+ string memory signature,
+ bytes memory data,
+ uint256 eta
+ ) public {
+ require(
+ msg.sender == admin,
+ "Timelock::cancelTransaction: Call must come from admin."
+ );
+
+ bytes32 txHash = keccak256(
+ abi.encode(target, value, signature, data, eta)
+ );
+ queuedTransactions[txHash] = false;
+
+ emit CancelTransaction(txHash, target, value, signature, data, eta);
+ }
+
+ function executeTransaction(
+ address target,
+ uint256 value,
+ string memory signature,
+ bytes memory data,
+ uint256 eta
+ ) public payable returns (bytes memory) {
+ require(
+ msg.sender == admin,
+ "Timelock::executeTransaction: Call must come from admin."
+ );
+
+ bytes32 txHash = keccak256(
+ abi.encode(target, value, signature, data, eta)
+ );
+ require(
+ queuedTransactions[txHash],
+ "Timelock::executeTransaction: Transaction hasn't been queued."
+ );
+ require(
+ getBlockTimestamp() >= eta,
+ "Timelock::executeTransaction: Transaction hasn't surpassed time lock."
+ );
+ require(
+ getBlockTimestamp() <= eta.add(GRACE_PERIOD),
+ "Timelock::executeTransaction: Transaction is stale."
+ );
+
+ queuedTransactions[txHash] = false;
+
+ bytes memory callData;
+
+ if (bytes(signature).length == 0) {
+ callData = data;
+ } else {
+ callData = abi.encodePacked(
+ bytes4(keccak256(bytes(signature))),
+ data
+ );
+ }
+
+ // solium-disable-next-line security/no-call-value
+ (bool success, bytes memory returnData) = target.call.value(value)(
+ callData
+ );
+ require(
+ success,
+ "Timelock::executeTransaction: Transaction execution reverted."
+ );
+
+ emit ExecuteTransaction(txHash, target, value, signature, data, eta);
+
+ return returnData;
+ }
+
+ function getBlockTimestamp() internal view returns (uint256) {
+ // solium-disable-next-line security/no-block-members
+ return block.timestamp;
+ }
+}
diff --git a/packages/chain-events/eth/contracts/Compound/TimelockMock.sol b/packages/chain-events/eth/contracts/Compound/TimelockMock.sol
new file mode 100644
index 00000000000..103d030c102
--- /dev/null
+++ b/packages/chain-events/eth/contracts/Compound/TimelockMock.sol
@@ -0,0 +1,211 @@
+pragma solidity >=0.4.21 <0.7.0;
+
+import "@openzeppelin/contracts/math/SafeMath.sol";
+
+
+contract TimelockMock {
+ using SafeMath for uint256;
+
+ event NewAdmin(address indexed newAdmin);
+ event NewPendingAdmin(address indexed newPendingAdmin);
+ event NewDelay(uint256 indexed newDelay);
+ event CancelTransaction(
+ bytes32 indexed txHash,
+ address indexed target,
+ uint256 value,
+ string signature,
+ bytes data,
+ uint256 eta
+ );
+ event ExecuteTransaction(
+ bytes32 indexed txHash,
+ address indexed target,
+ uint256 value,
+ string signature,
+ bytes data,
+ uint256 eta
+ );
+ event QueueTransaction(
+ bytes32 indexed txHash,
+ address indexed target,
+ uint256 value,
+ string signature,
+ bytes data,
+ uint256 eta
+ );
+
+ uint256 public constant GRACE_PERIOD = 14 minutes;
+ uint256 public constant MINIMUM_DELAY = 2 minutes;
+ uint256 public constant MAXIMUM_DELAY = 30 minutes;
+
+ address public admin;
+ address public pendingAdmin;
+ uint256 public delay;
+
+ mapping(bytes32 => bool) public queuedTransactions;
+
+ constructor(address admin_, uint256 delay_) public {
+ require(
+ delay_ >= MINIMUM_DELAY,
+ "TimelockMock::constructor: Delay must exceed minimum delay."
+ );
+ require(
+ delay_ <= MAXIMUM_DELAY,
+ "TimelockMock::setDelay: Delay must not exceed maximum delay."
+ );
+
+ admin = admin_;
+ delay = delay_;
+ }
+
+ function() external payable {}
+
+ function setDelay(uint256 delay_) public {
+ require(
+ msg.sender == address(this),
+ "TimelockMock::setDelay: Call must come from TimelockMock."
+ );
+ require(
+ delay_ >= MINIMUM_DELAY,
+ "TimelockMock::setDelay: Delay must exceed minimum delay."
+ );
+ require(
+ delay_ <= MAXIMUM_DELAY,
+ "TimelockMock::setDelay: Delay must not exceed maximum delay."
+ );
+ delay = delay_;
+
+ emit NewDelay(delay);
+ }
+
+ function acceptAdmin() public {
+ require(
+ msg.sender == pendingAdmin,
+ "TimelockMock::acceptAdmin: Call must come from pendingAdmin."
+ );
+ admin = msg.sender;
+ pendingAdmin = address(0);
+
+ emit NewAdmin(admin);
+ }
+
+ function setPendingAdmin(address pendingAdmin_) public {
+ require(
+ msg.sender == address(this),
+ "TimelockMock::setPendingAdmin: Call must come from TimelockMock."
+ );
+ pendingAdmin = pendingAdmin_;
+
+ emit NewPendingAdmin(pendingAdmin);
+ }
+
+ function queueTransaction(
+ address target,
+ uint256 value,
+ string memory signature,
+ bytes memory data,
+ uint256 eta
+ ) public returns (bytes32) {
+ // Remove this check for integration test purposes. We just want the events.
+ // require(
+ // msg.sender == admin,
+ // "TimelockMock::queueTransaction: Call must come from admin."
+ // );
+ require(
+ eta >= getBlockTimestamp().add(delay),
+ "TimelockMock::queueTransaction: Estimated execution block must satisfy delay."
+ );
+
+ bytes32 txHash = keccak256(
+ abi.encode(target, value, signature, data, eta)
+ );
+ queuedTransactions[txHash] = true;
+
+ emit QueueTransaction(txHash, target, value, signature, data, eta);
+ return txHash;
+ }
+
+ function cancelTransaction(
+ address target,
+ uint256 value,
+ string memory signature,
+ bytes memory data,
+ uint256 eta
+ ) public {
+ // Remove requirement for testing purposes.
+ // require(
+ // msg.sender == admin,
+ // "TimelockMock::cancelTransaction: Call must come from admin."
+ // );
+
+ bytes32 txHash = keccak256(
+ abi.encode(target, value, signature, data, eta)
+ );
+ queuedTransactions[txHash] = false;
+
+ emit CancelTransaction(txHash, target, value, signature, data, eta);
+ }
+
+ function executeTransaction(
+ address target,
+ uint256 value,
+ string memory signature,
+ bytes memory data,
+ uint256 eta
+ ) public payable returns (bytes memory) {
+
+ // Remove this check for integration test purposes. We just want the events.
+ // require(
+ // msg.sender == admin,
+ // "TimelockMock::executeTransaction: Call must come from admin."
+ // );
+
+ bytes32 txHash = keccak256(
+ abi.encode(target, value, signature, data, eta)
+ );
+ require(
+ queuedTransactions[txHash],
+ "TimelockMock::executeTransaction: Transaction hasn't been queued."
+ );
+ // Modify contract again for speeding up testing.
+ // require(
+ // getBlockTimestamp() >= eta,
+ // "TimelockMock::executeTransaction: Transaction hasn't surpassed time lock."
+ // );
+ require(
+ getBlockTimestamp() <= eta.add(GRACE_PERIOD),
+ "TimelockMock::executeTransaction: Transaction is stale."
+ );
+
+ queuedTransactions[txHash] = false;
+
+ bytes memory callData;
+
+ if (bytes(signature).length == 0) {
+ callData = data;
+ } else {
+ callData = abi.encodePacked(
+ bytes4(keccak256(bytes(signature))),
+ data
+ );
+ }
+
+ // solium-disable-next-line security/no-call-value
+ (bool success, bytes memory returnData) = target.call.value(value)(
+ callData
+ );
+ require(
+ success,
+ "TimelockMock::executeTransaction: Transaction execution reverted."
+ );
+
+ emit ExecuteTransaction(txHash, target, value, signature, data, eta);
+
+ return returnData;
+ }
+
+ function getBlockTimestamp() internal view returns (uint256) {
+ // solium-disable-next-line security/no-block-members
+ return block.timestamp;
+ }
+}
diff --git a/packages/chain-events/eth/contracts/Migrations.sol b/packages/chain-events/eth/contracts/Migrations.sol
new file mode 100644
index 00000000000..478d5f35efe
--- /dev/null
+++ b/packages/chain-events/eth/contracts/Migrations.sol
@@ -0,0 +1,23 @@
+pragma solidity >=0.4.21 <0.7.0;
+
+contract Migrations {
+ address public owner;
+ uint public last_completed_migration;
+
+ constructor() public {
+ owner = msg.sender;
+ }
+
+ modifier restricted() {
+ if (msg.sender == owner) _;
+ }
+
+ function setCompleted(uint completed) public restricted {
+ last_completed_migration = completed;
+ }
+
+ function upgrade(address new_address) public restricted {
+ Migrations upgraded = Migrations(new_address);
+ upgraded.setCompleted(last_completed_migration);
+ }
+}
\ No newline at end of file
diff --git a/packages/chain-events/eth/contracts/MolochV1/GuildBank.sol b/packages/chain-events/eth/contracts/MolochV1/GuildBank.sol
new file mode 100644
index 00000000000..e83bfd56a32
--- /dev/null
+++ b/packages/chain-events/eth/contracts/MolochV1/GuildBank.sol
@@ -0,0 +1,23 @@
+pragma solidity ^0.5.3;
+
+import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
+import "@openzeppelin/contracts/ownership/Ownable.sol";
+import "@openzeppelin/contracts/math/SafeMath.sol";
+
+contract GuildBank1 is Ownable {
+ using SafeMath for uint256;
+
+ IERC20 public approvedToken; // approved token contract reference
+
+ event Withdrawal(address indexed receiver, uint256 amount);
+
+ constructor(address approvedTokenAddress) public {
+ approvedToken = IERC20(approvedTokenAddress);
+ }
+
+ function withdraw(address receiver, uint256 shares, uint256 totalShares) public onlyOwner returns (bool) {
+ uint256 amount = approvedToken.balanceOf(address(this)).mul(shares).div(totalShares);
+ emit Withdrawal(receiver, amount);
+ return approvedToken.transfer(receiver, amount);
+ }
+}
\ No newline at end of file
diff --git a/packages/chain-events/eth/contracts/MolochV1/Moloch.sol b/packages/chain-events/eth/contracts/MolochV1/Moloch.sol
new file mode 100644
index 00000000000..7bca8012c2e
--- /dev/null
+++ b/packages/chain-events/eth/contracts/MolochV1/Moloch.sol
@@ -0,0 +1,414 @@
+pragma solidity ^0.5.3;
+
+import "@openzeppelin/contracts/math/SafeMath.sol";
+import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
+import "./GuildBank.sol";
+
+contract Moloch1 {
+ using SafeMath for uint256;
+
+ /***************
+ GLOBAL CONSTANTS
+ ***************/
+ uint256 public periodDuration; // default = 17280 = 4.8 hours in seconds (5 periods per day)
+ uint256 public votingPeriodLength; // default = 35 periods (7 days)
+ uint256 public gracePeriodLength; // default = 35 periods (7 days)
+ uint256 public abortWindow; // default = 5 periods (1 day)
+ uint256 public proposalDeposit; // default = 10 ETH (~$1,000 worth of ETH at contract deployment)
+ uint256 public dilutionBound; // default = 3 - maximum multiplier a YES voter will be obligated to pay in case of mass ragequit
+ uint256 public processingReward; // default = 0.1 - amount of ETH to give to whoever processes a proposal
+ uint256 public summoningTime; // needed to determine the current period
+
+ IERC20 public approvedToken; // approved token contract reference; default = wETH
+ GuildBank1 public guildBank; // guild bank contract reference
+
+ // HARD-CODED LIMITS
+ // These numbers are quite arbitrary; they are small enough to avoid overflows when doing calculations
+ // with periods or shares, yet big enough to not limit reasonable use cases.
+ uint256 constant MAX_VOTING_PERIOD_LENGTH = 10**18; // maximum length of voting period
+ uint256 constant MAX_GRACE_PERIOD_LENGTH = 10**18; // maximum length of grace period
+ uint256 constant MAX_DILUTION_BOUND = 10**18; // maximum dilution bound
+ uint256 constant MAX_NUMBER_OF_SHARES = 10**18; // maximum number of shares that can be minted
+
+ /***************
+ EVENTS
+ ***************/
+ event SubmitProposal(uint256 proposalIndex, address indexed delegateKey, address indexed memberAddress, address indexed applicant, uint256 tokenTribute, uint256 sharesRequested);
+ event SubmitVote(uint256 indexed proposalIndex, address indexed delegateKey, address indexed memberAddress, uint8 uintVote);
+ event ProcessProposal(uint256 indexed proposalIndex, address indexed applicant, address indexed memberAddress, uint256 tokenTribute, uint256 sharesRequested, bool didPass);
+ event Ragequit(address indexed memberAddress, uint256 sharesToBurn);
+ event Abort(uint256 indexed proposalIndex, address applicantAddress);
+ event UpdateDelegateKey(address indexed memberAddress, address newDelegateKey);
+ event SummonComplete(address indexed summoner, uint256 shares);
+
+ /******************
+ INTERNAL ACCOUNTING
+ ******************/
+ uint256 public totalShares = 0; // total shares across all members
+ uint256 public totalSharesRequested = 0; // total shares that have been requested in unprocessed proposals
+
+ enum Vote {
+ Null, // default value, counted as abstention
+ Yes,
+ No
+ }
+
+ struct Member {
+ address delegateKey; // the key responsible for submitting proposals and voting - defaults to member address unless updated
+ uint256 shares; // the # of shares assigned to this member
+ bool exists; // always true once a member has been created
+ uint256 highestIndexYesVote; // highest proposal index # on which the member voted YES
+ }
+
+ struct Proposal {
+ address proposer; // the member who submitted the proposal
+ address applicant; // the applicant who wishes to become a member - this key will be used for withdrawals
+ uint256 sharesRequested; // the # of shares the applicant is requesting
+ uint256 startingPeriod; // the period in which voting can start for this proposal
+ uint256 yesVotes; // the total number of YES votes for this proposal
+ uint256 noVotes; // the total number of NO votes for this proposal
+ bool processed; // true only if the proposal has been processed
+ bool didPass; // true only if the proposal passed
+ bool aborted; // true only if applicant calls "abort" fn before end of voting period
+ uint256 tokenTribute; // amount of tokens offered as tribute
+ string details; // proposal details - could be IPFS hash, plaintext, or JSON
+ uint256 maxTotalSharesAtYesVote; // the maximum # of total shares encountered at a yes vote on this proposal
+ mapping (address => Vote) votesByMember; // the votes on this proposal by each member
+ }
+
+ mapping (address => Member) public members;
+ mapping (address => address) public memberAddressByDelegateKey;
+ Proposal[] public proposalQueue;
+
+ /********
+ MODIFIERS
+ ********/
+ modifier onlyMember {
+ require(members[msg.sender].shares > 0, "Moloch::onlyMember - not a member");
+ _;
+ }
+
+ modifier onlyDelegate {
+ require(members[memberAddressByDelegateKey[msg.sender]].shares > 0, "Moloch::onlyDelegate - not a delegate");
+ _;
+ }
+
+ /********
+ FUNCTIONS
+ ********/
+ constructor(
+ address summoner,
+ address _approvedToken,
+ uint256 _periodDuration,
+ uint256 _votingPeriodLength,
+ uint256 _gracePeriodLength,
+ uint256 _abortWindow,
+ uint256 _proposalDeposit,
+ uint256 _dilutionBound,
+ uint256 _processingReward
+ ) public {
+ require(summoner != address(0), "Moloch::constructor - summoner cannot be 0");
+ require(_approvedToken != address(0), "Moloch::constructor - _approvedToken cannot be 0");
+ require(_periodDuration > 0, "Moloch::constructor - _periodDuration cannot be 0");
+ require(_votingPeriodLength > 0, "Moloch::constructor - _votingPeriodLength cannot be 0");
+ require(_votingPeriodLength <= MAX_VOTING_PERIOD_LENGTH, "Moloch::constructor - _votingPeriodLength exceeds limit");
+ require(_gracePeriodLength <= MAX_GRACE_PERIOD_LENGTH, "Moloch::constructor - _gracePeriodLength exceeds limit");
+ require(_abortWindow > 0, "Moloch::constructor - _abortWindow cannot be 0");
+ require(_abortWindow <= _votingPeriodLength, "Moloch::constructor - _abortWindow must be smaller than or equal to _votingPeriodLength");
+ require(_dilutionBound > 0, "Moloch::constructor - _dilutionBound cannot be 0");
+ require(_dilutionBound <= MAX_DILUTION_BOUND, "Moloch::constructor - _dilutionBound exceeds limit");
+ require(_proposalDeposit >= _processingReward, "Moloch::constructor - _proposalDeposit cannot be smaller than _processingReward");
+
+ approvedToken = IERC20(_approvedToken);
+
+ guildBank = new GuildBank1(_approvedToken);
+
+ periodDuration = _periodDuration;
+ votingPeriodLength = _votingPeriodLength;
+ gracePeriodLength = _gracePeriodLength;
+ abortWindow = _abortWindow;
+ proposalDeposit = _proposalDeposit;
+ dilutionBound = _dilutionBound;
+ processingReward = _processingReward;
+
+ summoningTime = now;
+
+ members[summoner] = Member(summoner, 1, true, 0);
+ memberAddressByDelegateKey[summoner] = summoner;
+ totalShares = 1;
+
+ emit SummonComplete(summoner, 1);
+ }
+
+ /*****************
+ PROPOSAL FUNCTIONS
+ *****************/
+
+ function submitProposal(
+ address applicant,
+ uint256 tokenTribute,
+ uint256 sharesRequested,
+ string memory details
+ )
+ public
+ onlyDelegate
+ {
+ require(applicant != address(0), "Moloch::submitProposal - applicant cannot be 0");
+
+ // Make sure we won't run into overflows when doing calculations with shares.
+ // Note that totalShares + totalSharesRequested + sharesRequested is an upper bound
+ // on the number of shares that can exist until this proposal has been processed.
+ require(totalShares.add(totalSharesRequested).add(sharesRequested) <= MAX_NUMBER_OF_SHARES, "Moloch::submitProposal - too many shares requested");
+
+ totalSharesRequested = totalSharesRequested.add(sharesRequested);
+
+ address memberAddress = memberAddressByDelegateKey[msg.sender];
+
+ // collect proposal deposit from proposer and store it in the Moloch until the proposal is processed
+ require(approvedToken.transferFrom(msg.sender, address(this), proposalDeposit), "Moloch::submitProposal - proposal deposit token transfer failed");
+
+ // collect tribute from applicant and store it in the Moloch until the proposal is processed
+ require(approvedToken.transferFrom(applicant, address(this), tokenTribute), "Moloch::submitProposal - tribute token transfer failed");
+
+ // compute startingPeriod for proposal
+ uint256 startingPeriod = max(
+ getCurrentPeriod(),
+ proposalQueue.length == 0 ? 0 : proposalQueue[proposalQueue.length.sub(1)].startingPeriod
+ ).add(1);
+
+ // create proposal ...
+ Proposal memory proposal = Proposal({
+ proposer: memberAddress,
+ applicant: applicant,
+ sharesRequested: sharesRequested,
+ startingPeriod: startingPeriod,
+ yesVotes: 0,
+ noVotes: 0,
+ processed: false,
+ didPass: false,
+ aborted: false,
+ tokenTribute: tokenTribute,
+ details: details,
+ maxTotalSharesAtYesVote: 0
+ });
+
+ // ... and append it to the queue
+ proposalQueue.push(proposal);
+
+ uint256 proposalIndex = proposalQueue.length.sub(1);
+ emit SubmitProposal(proposalIndex, msg.sender, memberAddress, applicant, tokenTribute, sharesRequested);
+ }
+
+ function submitVote(uint256 proposalIndex, uint8 uintVote) public onlyDelegate {
+ address memberAddress = memberAddressByDelegateKey[msg.sender];
+ Member storage member = members[memberAddress];
+
+ require(proposalIndex < proposalQueue.length, "Moloch::submitVote - proposal does not exist");
+ Proposal storage proposal = proposalQueue[proposalIndex];
+
+ require(uintVote < 3, "Moloch::submitVote - uintVote must be less than 3");
+ Vote vote = Vote(uintVote);
+
+ require(getCurrentPeriod() >= proposal.startingPeriod, "Moloch::submitVote - voting period has not started");
+ require(!hasVotingPeriodExpired(proposal.startingPeriod), "Moloch::submitVote - proposal voting period has expired");
+ require(proposal.votesByMember[memberAddress] == Vote.Null, "Moloch::submitVote - member has already voted on this proposal");
+ require(vote == Vote.Yes || vote == Vote.No, "Moloch::submitVote - vote must be either Yes or No");
+ require(!proposal.aborted, "Moloch::submitVote - proposal has been aborted");
+
+ // store vote
+ proposal.votesByMember[memberAddress] = vote;
+
+ // count vote
+ if (vote == Vote.Yes) {
+ proposal.yesVotes = proposal.yesVotes.add(member.shares);
+
+ // set highest index (latest) yes vote - must be processed for member to ragequit
+ if (proposalIndex > member.highestIndexYesVote) {
+ member.highestIndexYesVote = proposalIndex;
+ }
+
+ // set maximum of total shares encountered at a yes vote - used to bound dilution for yes voters
+ if (totalShares > proposal.maxTotalSharesAtYesVote) {
+ proposal.maxTotalSharesAtYesVote = totalShares;
+ }
+
+ } else if (vote == Vote.No) {
+ proposal.noVotes = proposal.noVotes.add(member.shares);
+ }
+
+ emit SubmitVote(proposalIndex, msg.sender, memberAddress, uintVote);
+ }
+
+ function processProposal(uint256 proposalIndex) public {
+ require(proposalIndex < proposalQueue.length, "Moloch::processProposal - proposal does not exist");
+ Proposal storage proposal = proposalQueue[proposalIndex];
+
+ require(getCurrentPeriod() >= proposal.startingPeriod.add(votingPeriodLength).add(gracePeriodLength), "Moloch::processProposal - proposal is not ready to be processed");
+ require(proposal.processed == false, "Moloch::processProposal - proposal has already been processed");
+ require(proposalIndex == 0 || proposalQueue[proposalIndex.sub(1)].processed, "Moloch::processProposal - previous proposal must be processed");
+
+ proposal.processed = true;
+ totalSharesRequested = totalSharesRequested.sub(proposal.sharesRequested);
+
+ bool didPass = proposal.yesVotes > proposal.noVotes;
+
+ // Make the proposal fail if the dilutionBound is exceeded
+ if (totalShares.mul(dilutionBound) < proposal.maxTotalSharesAtYesVote) {
+ didPass = false;
+ }
+
+ // PROPOSAL PASSED
+ if (didPass && !proposal.aborted) {
+
+ proposal.didPass = true;
+
+ // if the applicant is already a member, add to their existing shares
+ if (members[proposal.applicant].exists) {
+ members[proposal.applicant].shares = members[proposal.applicant].shares.add(proposal.sharesRequested);
+
+ // the applicant is a new member, create a new record for them
+ } else {
+ // if the applicant address is already taken by a member's delegateKey, reset it to their member address
+ if (members[memberAddressByDelegateKey[proposal.applicant]].exists) {
+ address memberToOverride = memberAddressByDelegateKey[proposal.applicant];
+ memberAddressByDelegateKey[memberToOverride] = memberToOverride;
+ members[memberToOverride].delegateKey = memberToOverride;
+ }
+
+ // use applicant address as delegateKey by default
+ members[proposal.applicant] = Member(proposal.applicant, proposal.sharesRequested, true, 0);
+ memberAddressByDelegateKey[proposal.applicant] = proposal.applicant;
+ }
+
+ // mint new shares
+ totalShares = totalShares.add(proposal.sharesRequested);
+
+ // transfer tokens to guild bank
+ require(
+ approvedToken.transfer(address(guildBank), proposal.tokenTribute),
+ "Moloch::processProposal - token transfer to guild bank failed"
+ );
+
+ // PROPOSAL FAILED OR ABORTED
+ } else {
+ // return all tokens to the applicant
+ require(
+ approvedToken.transfer(proposal.applicant, proposal.tokenTribute),
+ "Moloch::processProposal - failing vote token transfer failed"
+ );
+ }
+
+ // send msg.sender the processingReward
+ require(
+ approvedToken.transfer(msg.sender, processingReward),
+ "Moloch::processProposal - failed to send processing reward to msg.sender"
+ );
+
+ // return deposit to proposer (subtract processing reward)
+ require(
+ approvedToken.transfer(proposal.proposer, proposalDeposit.sub(processingReward)),
+ "Moloch::processProposal - failed to return proposal deposit to proposer"
+ );
+
+ emit ProcessProposal(
+ proposalIndex,
+ proposal.applicant,
+ proposal.proposer,
+ proposal.tokenTribute,
+ proposal.sharesRequested,
+ didPass
+ );
+ }
+
+ function ragequit(uint256 sharesToBurn) public onlyMember {
+ uint256 initialTotalShares = totalShares;
+
+ Member storage member = members[msg.sender];
+
+ require(member.shares >= sharesToBurn, "Moloch::ragequit - insufficient shares");
+
+ require(canRagequit(member.highestIndexYesVote), "Moloch::ragequit - cant ragequit until highest index proposal member voted YES on is processed");
+
+ // burn shares
+ member.shares = member.shares.sub(sharesToBurn);
+ totalShares = totalShares.sub(sharesToBurn);
+
+ // instruct guildBank to transfer fair share of tokens to the ragequitter
+ require(
+ guildBank.withdraw(msg.sender, sharesToBurn, initialTotalShares),
+ "Moloch::ragequit - withdrawal of tokens from guildBank failed"
+ );
+
+ emit Ragequit(msg.sender, sharesToBurn);
+ }
+
+ function abort(uint256 proposalIndex) public {
+ require(proposalIndex < proposalQueue.length, "Moloch::abort - proposal does not exist");
+ Proposal storage proposal = proposalQueue[proposalIndex];
+
+ require(msg.sender == proposal.applicant, "Moloch::abort - msg.sender must be applicant");
+ require(getCurrentPeriod() < proposal.startingPeriod.add(abortWindow), "Moloch::abort - abort window must not have passed");
+ require(!proposal.aborted, "Moloch::abort - proposal must not have already been aborted");
+
+ uint256 tokensToAbort = proposal.tokenTribute;
+ proposal.tokenTribute = 0;
+ proposal.aborted = true;
+
+ // return all tokens to the applicant
+ require(
+ approvedToken.transfer(proposal.applicant, tokensToAbort),
+ "Moloch::processProposal - failed to return tribute to applicant"
+ );
+
+ emit Abort(proposalIndex, msg.sender);
+ }
+
+ function updateDelegateKey(address newDelegateKey) public onlyMember {
+ require(newDelegateKey != address(0), "Moloch::updateDelegateKey - newDelegateKey cannot be 0");
+
+ // skip checks if member is setting the delegate key to their member address
+ if (newDelegateKey != msg.sender) {
+ require(!members[newDelegateKey].exists, "Moloch::updateDelegateKey - cant overwrite existing members");
+ require(!members[memberAddressByDelegateKey[newDelegateKey]].exists, "Moloch::updateDelegateKey - cant overwrite existing delegate keys");
+ }
+
+ Member storage member = members[msg.sender];
+ memberAddressByDelegateKey[member.delegateKey] = address(0);
+ memberAddressByDelegateKey[newDelegateKey] = msg.sender;
+ member.delegateKey = newDelegateKey;
+
+ emit UpdateDelegateKey(msg.sender, newDelegateKey);
+ }
+
+ /***************
+ GETTER FUNCTIONS
+ ***************/
+
+ function max(uint256 x, uint256 y) internal pure returns (uint256) {
+ return x >= y ? x : y;
+ }
+
+ function getCurrentPeriod() public view returns (uint256) {
+ return now.sub(summoningTime).div(periodDuration);
+ }
+
+ function getProposalQueueLength() public view returns (uint256) {
+ return proposalQueue.length;
+ }
+
+ // can only ragequit if the latest proposal you voted YES on has been processed
+ function canRagequit(uint256 highestIndexYesVote) public view returns (bool) {
+ require(highestIndexYesVote < proposalQueue.length, "Moloch::canRagequit - proposal does not exist");
+ return proposalQueue[highestIndexYesVote].processed;
+ }
+
+ function hasVotingPeriodExpired(uint256 startingPeriod) public view returns (bool) {
+ return getCurrentPeriod() >= startingPeriod.add(votingPeriodLength);
+ }
+
+ function getMemberProposalVote(address memberAddress, uint256 proposalIndex) public view returns (Vote) {
+ require(members[memberAddress].exists, "Moloch::getMemberProposalVote - member doesn't exist");
+ require(proposalIndex < proposalQueue.length, "Moloch::getMemberProposalVote - proposal doesn't exist");
+ return proposalQueue[proposalIndex].votesByMember[memberAddress];
+ }
+}
\ No newline at end of file
diff --git a/packages/chain-events/eth/contracts/MolochV1/Token.sol b/packages/chain-events/eth/contracts/MolochV1/Token.sol
new file mode 100644
index 00000000000..f15c025bb4a
--- /dev/null
+++ b/packages/chain-events/eth/contracts/MolochV1/Token.sol
@@ -0,0 +1,9 @@
+pragma solidity ^0.5.2;
+
+import "./oz/ERC20.sol";
+
+contract Token is ERC20 {
+ constructor(uint256 supply) public {
+ _mint(msg.sender, supply);
+ }
+}
\ No newline at end of file
diff --git a/packages/chain-events/eth/contracts/MolochV1/oz/ERC20.sol b/packages/chain-events/eth/contracts/MolochV1/oz/ERC20.sol
new file mode 100644
index 00000000000..aadf6b76912
--- /dev/null
+++ b/packages/chain-events/eth/contracts/MolochV1/oz/ERC20.sol
@@ -0,0 +1,190 @@
+pragma solidity ^0.5.2;
+
+import "./IERC20.sol";
+import "./SafeMath.sol";
+
+/**
+ * @title Standard ERC20 token
+ *
+ * @dev Implementation of the basic standard token.
+ * https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20.md
+ * Originally based on code by FirstBlood:
+ * https://github.com/Firstbloodio/token/blob/master/smart_contract/FirstBloodToken.sol
+ *
+ * This implementation emits additional Approval events, allowing applications to reconstruct the allowance status for
+ * all accounts just by listening to said events. Note that this isn't required by the specification, and other
+ * compliant implementations may not do it.
+ */
+contract ERC20 is IERC20 {
+ using SafeMath for uint256;
+
+ mapping (address => uint256) private _balances;
+
+ mapping (address => mapping (address => uint256)) private _allowed;
+
+ uint256 private _totalSupply;
+
+ /**
+ * @dev Total number of tokens in existence
+ */
+ function totalSupply() public view returns (uint256) {
+ return _totalSupply;
+ }
+
+ /**
+ * @dev Gets the balance of the specified address.
+ * @param owner The address to query the balance of.
+ * @return An uint256 representing the amount owned by the passed address.
+ */
+ function balanceOf(address owner) public view returns (uint256) {
+ return _balances[owner];
+ }
+
+ /**
+ * @dev Function to check the amount of tokens that an owner allowed to a spender.
+ * @param owner address The address which owns the funds.
+ * @param spender address The address which will spend the funds.
+ * @return A uint256 specifying the amount of tokens still available for the spender.
+ */
+ function allowance(address owner, address spender) public view returns (uint256) {
+ return _allowed[owner][spender];
+ }
+
+ /**
+ * @dev Transfer token for a specified address
+ * @param to The address to transfer to.
+ * @param value The amount to be transferred.
+ */
+ function transfer(address to, uint256 value) public returns (bool) {
+ _transfer(msg.sender, to, value);
+ return true;
+ }
+
+ /**
+ * @dev Approve the passed address to spend the specified amount of tokens on behalf of msg.sender.
+ * Beware that changing an allowance with this method brings the risk that someone may use both the old
+ * and the new allowance by unfortunate transaction ordering. One possible solution to mitigate this
+ * race condition is to first reduce the spender's allowance to 0 and set the desired value afterwards:
+ * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
+ * @param spender The address which will spend the funds.
+ * @param value The amount of tokens to be spent.
+ */
+ function approve(address spender, uint256 value) public returns (bool) {
+ _approve(msg.sender, spender, value);
+ return true;
+ }
+
+ /**
+ * @dev Transfer tokens from one address to another.
+ * Note that while this function emits an Approval event, this is not required as per the specification,
+ * and other compliant implementations may not emit the event.
+ * @param from address The address which you want to send tokens from
+ * @param to address The address which you want to transfer to
+ * @param value uint256 the amount of tokens to be transferred
+ */
+ function transferFrom(address from, address to, uint256 value) public returns (bool) {
+ _transfer(from, to, value);
+ _approve(from, msg.sender, _allowed[from][msg.sender].sub(value));
+ return true;
+ }
+
+ /**
+ * @dev Increase the amount of tokens that an owner allowed to a spender.
+ * approve should be called when allowed_[_spender] == 0. To increment
+ * allowed value is better to use this function to avoid 2 calls (and wait until
+ * the first transaction is mined)
+ * From MonolithDAO Token.sol
+ * Emits an Approval event.
+ * @param spender The address which will spend the funds.
+ * @param addedValue The amount of tokens to increase the allowance by.
+ */
+ function increaseAllowance(address spender, uint256 addedValue) public returns (bool) {
+ _approve(msg.sender, spender, _allowed[msg.sender][spender].add(addedValue));
+ return true;
+ }
+
+ /**
+ * @dev Decrease the amount of tokens that an owner allowed to a spender.
+ * approve should be called when allowed_[_spender] == 0. To decrement
+ * allowed value is better to use this function to avoid 2 calls (and wait until
+ * the first transaction is mined)
+ * From MonolithDAO Token.sol
+ * Emits an Approval event.
+ * @param spender The address which will spend the funds.
+ * @param subtractedValue The amount of tokens to decrease the allowance by.
+ */
+ function decreaseAllowance(address spender, uint256 subtractedValue) public returns (bool) {
+ _approve(msg.sender, spender, _allowed[msg.sender][spender].sub(subtractedValue));
+ return true;
+ }
+
+ /**
+ * @dev Transfer token for a specified addresses
+ * @param from The address to transfer from.
+ * @param to The address to transfer to.
+ * @param value The amount to be transferred.
+ */
+ function _transfer(address from, address to, uint256 value) internal {
+ require(to != address(0));
+
+ _balances[from] = _balances[from].sub(value);
+ _balances[to] = _balances[to].add(value);
+ emit Transfer(from, to, value);
+ }
+
+ /**
+ * @dev Internal function that mints an amount of the token and assigns it to
+ * an account. This encapsulates the modification of balances such that the
+ * proper events are emitted.
+ * @param account The account that will receive the created tokens.
+ * @param value The amount that will be created.
+ */
+ function _mint(address account, uint256 value) internal {
+ require(account != address(0));
+
+ _totalSupply = _totalSupply.add(value);
+ _balances[account] = _balances[account].add(value);
+ emit Transfer(address(0), account, value);
+ }
+
+ /**
+ * @dev Internal function that burns an amount of the token of a given
+ * account.
+ * @param account The account whose tokens will be burnt.
+ * @param value The amount that will be burnt.
+ */
+ function _burn(address account, uint256 value) internal {
+ require(account != address(0));
+
+ _totalSupply = _totalSupply.sub(value);
+ _balances[account] = _balances[account].sub(value);
+ emit Transfer(account, address(0), value);
+ }
+
+ /**
+ * @dev Approve an address to spend another addresses' tokens.
+ * @param owner The address that owns the tokens.
+ * @param spender The address that will spend the tokens.
+ * @param value The number of tokens that can be spent.
+ */
+ function _approve(address owner, address spender, uint256 value) internal {
+ require(spender != address(0));
+ require(owner != address(0));
+
+ _allowed[owner][spender] = value;
+ emit Approval(owner, spender, value);
+ }
+
+ /**
+ * @dev Internal function that burns an amount of the token of a given
+ * account, deducting from the sender's allowance for said account. Uses the
+ * internal burn function.
+ * Emits an Approval event (reflecting the reduced allowance).
+ * @param account The account whose tokens will be burnt.
+ * @param value The amount that will be burnt.
+ */
+ function _burnFrom(address account, uint256 value) internal {
+ _burn(account, value);
+ _approve(account, msg.sender, _allowed[account][msg.sender].sub(value));
+ }
+}
diff --git a/packages/chain-events/eth/contracts/MolochV1/oz/IERC20.sol b/packages/chain-events/eth/contracts/MolochV1/oz/IERC20.sol
new file mode 100644
index 00000000000..4d208c79660
--- /dev/null
+++ b/packages/chain-events/eth/contracts/MolochV1/oz/IERC20.sol
@@ -0,0 +1,23 @@
+pragma solidity ^0.5.2;
+
+/**
+ * @title ERC20 interface
+ * @dev see https://github.com/ethereum/EIPs/issues/20
+ */
+interface IERC20 {
+ function transfer(address to, uint256 value) external returns (bool);
+
+ function approve(address spender, uint256 value) external returns (bool);
+
+ function transferFrom(address from, address to, uint256 value) external returns (bool);
+
+ function totalSupply() external view returns (uint256);
+
+ function balanceOf(address who) external view returns (uint256);
+
+ function allowance(address owner, address spender) external view returns (uint256);
+
+ event Transfer(address indexed from, address indexed to, uint256 value);
+
+ event Approval(address indexed owner, address indexed spender, uint256 value);
+}
diff --git a/packages/chain-events/eth/contracts/MolochV1/oz/SafeMath.sol b/packages/chain-events/eth/contracts/MolochV1/oz/SafeMath.sol
new file mode 100644
index 00000000000..5dd4bb903d0
--- /dev/null
+++ b/packages/chain-events/eth/contracts/MolochV1/oz/SafeMath.sol
@@ -0,0 +1,65 @@
+pragma solidity ^0.5.2;
+
+/**
+ * @title SafeMath
+ * @dev Unsigned math operations with safety checks that revert on error
+ */
+library SafeMath {
+ /**
+ * @dev Multiplies two unsigned integers, reverts on overflow.
+ */
+ function mul(uint256 a, uint256 b) internal pure returns (uint256) {
+ // Gas optimization: this is cheaper than requiring 'a' not being zero, but the
+ // benefit is lost if 'b' is also tested.
+ // See: https://github.com/OpenZeppelin/openzeppelin-solidity/pull/522
+ if (a == 0) {
+ return 0;
+ }
+
+ uint256 c = a * b;
+ require(c / a == b);
+
+ return c;
+ }
+
+ /**
+ * @dev Integer division of two unsigned integers truncating the quotient, reverts on division by zero.
+ */
+ function div(uint256 a, uint256 b) internal pure returns (uint256) {
+ // Solidity only automatically asserts when dividing by 0
+ require(b > 0);
+ uint256 c = a / b;
+ // assert(a == b * c + a % b); // There is no case in which this doesn't hold
+
+ return c;
+ }
+
+ /**
+ * @dev Subtracts two unsigned integers, reverts on overflow (i.e. if subtrahend is greater than minuend).
+ */
+ function sub(uint256 a, uint256 b) internal pure returns (uint256) {
+ require(b <= a);
+ uint256 c = a - b;
+
+ return c;
+ }
+
+ /**
+ * @dev Adds two unsigned integers, reverts on overflow.
+ */
+ function add(uint256 a, uint256 b) internal pure returns (uint256) {
+ uint256 c = a + b;
+ require(c >= a);
+
+ return c;
+ }
+
+ /**
+ * @dev Divides two unsigned integers and returns the remainder (unsigned integer modulo),
+ * reverts when dividing by zero.
+ */
+ function mod(uint256 a, uint256 b) internal pure returns (uint256) {
+ require(b != 0);
+ return a % b;
+ }
+}
diff --git a/packages/chain-events/eth/contracts/MolochV2/GuildBank.sol b/packages/chain-events/eth/contracts/MolochV2/GuildBank.sol
new file mode 100644
index 00000000000..5b8204175bb
--- /dev/null
+++ b/packages/chain-events/eth/contracts/MolochV2/GuildBank.sol
@@ -0,0 +1,24 @@
+pragma solidity ^0.5.3;
+
+import "@openzeppelin/contracts/ownership/Ownable.sol";
+import "@openzeppelin/contracts/math/SafeMath.sol";
+import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
+
+contract GuildBank2 is Ownable {
+ using SafeMath for uint256;
+
+ event Withdrawal(address indexed receiver, address indexed tokenAddress, uint256 amount);
+
+ function withdraw(address receiver, uint256 shares, uint256 totalShares, IERC20[] memory _approvedTokens) public onlyOwner returns (bool) {
+ for (uint256 i=0; i < _approvedTokens.length; i++) {
+ uint256 amount = _approvedTokens[i].balanceOf(address(this)).mul(shares).div(totalShares);
+ emit Withdrawal(receiver, address(_approvedTokens[i]), amount);
+ return _approvedTokens[i].transfer(receiver, amount);
+ }
+ }
+
+ function withdrawToken(IERC20 token, address receiver, uint256 amount) public onlyOwner returns (bool) {
+ emit Withdrawal(receiver, address(token), amount);
+ return token.transfer(receiver, amount);
+ }
+}
\ No newline at end of file
diff --git a/packages/chain-events/eth/contracts/MolochV2/Helper.sol b/packages/chain-events/eth/contracts/MolochV2/Helper.sol
new file mode 100644
index 00000000000..a1f611bdd25
--- /dev/null
+++ b/packages/chain-events/eth/contracts/MolochV2/Helper.sol
@@ -0,0 +1,9 @@
+pragma solidity ^0.5.0;
+
+import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
+import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
+import "@openzeppelin/contracts/token/ERC777/ERC777.sol";
+
+contract Helper {
+ constructor() public {}
+}
\ No newline at end of file
diff --git a/packages/chain-events/eth/contracts/MolochV2/Moloch.sol b/packages/chain-events/eth/contracts/MolochV2/Moloch.sol
new file mode 100644
index 00000000000..efda2e81329
--- /dev/null
+++ b/packages/chain-events/eth/contracts/MolochV2/Moloch.sol
@@ -0,0 +1,590 @@
+pragma solidity ^0.5.3;
+
+import "@openzeppelin/contracts/math/SafeMath.sol";
+import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
+import "./GuildBank.sol";
+
+contract Moloch2 {
+ using SafeMath for uint256;
+
+ // ****************
+ // GLOBAL CONSTANTS
+ // ****************
+ uint256 public periodDuration; // default = 17280 = 4.8 hours in seconds (5 periods per day)
+ uint256 public votingPeriodLength; // default = 35 periods (7 days)
+ uint256 public gracePeriodLength; // default = 35 periods (7 days)
+ uint256 public emergencyExitWait; // default = 35 periods (7 days) - if proposal has not been processed after this time, its logic will be skipped
+ uint256 public proposalDeposit; // default = 10 ETH (~$1,000 worth of ETH at contract deployment)
+ uint256 public dilutionBound; // default = 3 - maximum multiplier a YES voter will be obligated to pay in case of mass ragequit
+ uint256 public processingReward; // default = 0.1 - amount of ETH to give to whoever processes a proposal
+ uint256 public summoningTime; // needed to determine the current period
+
+ IERC20 public depositToken; // reference to the deposit token
+ GuildBank2 public guildBank; // guild bank contract reference
+
+ // HARD-CODED LIMITS
+ // These numbers are quite arbitrary; they are small enough to avoid overflows when doing calculations
+ // with periods or shares, yet big enough to not limit reasonable use cases.
+ uint256 constant MAX_VOTING_PERIOD_LENGTH = 10**18; // maximum length of voting period
+ uint256 constant MAX_GRACE_PERIOD_LENGTH = 10**18; // maximum length of grace period
+ uint256 constant MAX_DILUTION_BOUND = 10**18; // maximum dilution bound
+ uint256 constant MAX_NUMBER_OF_SHARES = 10**18; // maximum number of shares that can be minted
+
+ // ***************
+ // EVENTS
+ // ***************
+ event SubmitProposal(uint256 proposalIndex, address indexed delegateKey, address indexed memberAddress, address indexed applicant, uint256 tributeOffered, uint256 sharesRequested);
+ event SubmitVote(uint256 indexed proposalIndex, address indexed delegateKey, address indexed memberAddress, uint8 uintVote);
+ event ProcessProposal(uint256 indexed proposalIndex, address indexed applicant, address indexed memberAddress, uint256 tributeOffered, uint256 sharesRequested, bool didPass);
+ event Ragequit(address indexed memberAddress, uint256 sharesToBurn);
+ event CancelProposal(uint256 indexed proposalIndex, address applicantAddress);
+ event UpdateDelegateKey(address indexed memberAddress, address newDelegateKey);
+ event SummonComplete(address indexed summoner, uint256 shares);
+
+ // *******************
+ // INTERNAL ACCOUNTING
+ // *******************
+ uint256 public proposalCount = 0; // total proposals submitted
+ uint256 public totalShares = 0; // total shares across all members
+ uint256 public totalSharesRequested = 0; // total shares that have been requested in unprocessed proposals
+
+ enum Vote {
+ Null, // default value, counted as abstention
+ Yes,
+ No
+ }
+
+ struct Member {
+ address delegateKey; // the key responsible for submitting proposals and voting - defaults to member address unless updated
+ uint256 shares; // the # of shares assigned to this member
+ bool exists; // always true once a member has been created
+ uint256 highestIndexYesVote; // highest proposal index # on which the member voted YES
+ }
+
+ struct Proposal {
+ address applicant; // the applicant who wishes to become a member - this key will be used for withdrawals
+ address proposer; // whoever submitted the proposal (can be non-member)
+ address sponsor; // the member who sponsored the proposal
+ uint256 sharesRequested; // the # of shares the applicant is requesting
+ uint256 tributeOffered; // amount of tokens offered as tribute
+ IERC20 tributeToken; // token being offered as tribute
+ uint256 paymentRequested; // the payments requested for each applicant
+ IERC20 paymentToken; // token to send payment in
+ uint256 startingPeriod; // the period in which voting can start for this proposal
+ uint256 yesVotes; // the total number of YES votes for this proposal
+ uint256 noVotes; // the total number of NO votes for this proposal
+ bool[6] flags; // [sponsored, processed, didPass, cancelled, whitelist, guildkick]
+ // 0. sponsored - true only if the proposal has been submitted by a member
+ // 1. processed - true only if the proposal has been processed
+ // 2. didPass - true only if the proposal passed
+ // 3. cancelled - true only if the proposer called cancelProposal before a member sponsored the proposal
+ // 4. whitelist - true only if this is a whitelist proposal, NOTE - tributeToken is target of whitelist
+ // 5. guildkick - true only if this is a guild kick proposal, NOTE - applicant is target of guild kick
+ string details; // proposal details - could be IPFS hash, plaintext, or JSON
+ uint256 maxTotalSharesAtYesVote; // the maximum # of total shares encountered at a yes vote on this proposal
+ mapping (address => Vote) votesByMember; // the votes on this proposal by each member
+ }
+
+ mapping (address => bool) public tokenWhitelist;
+ IERC20[] public approvedTokens;
+
+ mapping (address => bool) public proposedToWhitelist; // true if a token has been proposed to the whitelist (to avoid duplicate whitelist proposals)
+ mapping (address => bool) public proposedToKick; // true if a member has been proposed to be kicked (to avoid duplicate guild kick proposals)
+
+ mapping (address => Member) public members;
+ mapping (address => address) public memberAddressByDelegateKey;
+
+ // proposals by ID
+ mapping (uint256 => Proposal) public proposals;
+
+ // the queue of proposals (only store a reference by the proposal id)
+ uint256[] public proposalQueue;
+
+ // *********
+ // MODIFIERS
+ // *********
+ modifier onlyMember {
+ require(members[msg.sender].shares > 0, "Moloch::onlyMember - not a member");
+ _;
+ }
+
+ modifier onlyDelegate {
+ require(members[memberAddressByDelegateKey[msg.sender]].shares > 0, "Moloch::onlyDelegate - not a delegate");
+ _;
+ }
+
+ // *********
+ // FUNCTIONS
+ // *********
+ constructor(
+ address summoner,
+ address[] memory _approvedTokens,
+ uint256 _periodDuration,
+ uint256 _votingPeriodLength,
+ uint256 _gracePeriodLength,
+ uint256 _emergencyExitWait,
+ uint256 _proposalDeposit,
+ uint256 _dilutionBound,
+ uint256 _processingReward
+ ) public {
+ require(summoner != address(0), "Moloch::constructor - summoner cannot be 0");
+ require(_periodDuration > 0, "Moloch::constructor - _periodDuration cannot be 0");
+ require(_votingPeriodLength > 0, "Moloch::constructor - _votingPeriodLength cannot be 0");
+ require(_votingPeriodLength <= MAX_VOTING_PERIOD_LENGTH, "Moloch::constructor - _votingPeriodLength exceeds limit");
+ require(_gracePeriodLength <= MAX_GRACE_PERIOD_LENGTH, "Moloch::constructor - _gracePeriodLength exceeds limit");
+ require(_emergencyExitWait > 0, "Moloch::constructor - _emergencyExitWait cannot be 0");
+ require(_dilutionBound > 0, "Moloch::constructor - _dilutionBound cannot be 0");
+ require(_dilutionBound <= MAX_DILUTION_BOUND, "Moloch::constructor - _dilutionBound exceeds limit");
+ require(_approvedTokens.length > 0, "Moloch::constructor - need at least one approved token");
+ require(_proposalDeposit >= _processingReward, "Moloch::constructor - _proposalDeposit cannot be smaller than _processingReward");
+
+ // first approved token is the deposit token
+ depositToken = IERC20(_approvedTokens[0]);
+
+ for (uint256 i=0; i < _approvedTokens.length; i++) {
+ require(_approvedTokens[i] != address(0), "Moloch::constructor - _approvedToken cannot be 0");
+ require(!tokenWhitelist[_approvedTokens[i]], "Moloch::constructor - duplicate approved token");
+ tokenWhitelist[_approvedTokens[i]] = true;
+ approvedTokens.push(IERC20(_approvedTokens[i]));
+ }
+
+ guildBank = new GuildBank2();
+
+ periodDuration = _periodDuration;
+ votingPeriodLength = _votingPeriodLength;
+ gracePeriodLength = _gracePeriodLength;
+ emergencyExitWait = _emergencyExitWait;
+ proposalDeposit = _proposalDeposit;
+ dilutionBound = _dilutionBound;
+ processingReward = _processingReward;
+
+ summoningTime = now;
+
+ members[summoner] = Member(summoner, 1, true, 0);
+ memberAddressByDelegateKey[summoner] = summoner;
+ totalShares = 1;
+
+ emit SummonComplete(summoner, 1);
+ }
+
+ // ******************
+ // PROPOSAL FUNCTIONS
+ // ******************
+
+ function submitProposal(
+ address applicant,
+ uint256 sharesRequested,
+ uint256 tributeOffered,
+ address tributeToken,
+ uint256 paymentRequested,
+ address paymentToken,
+ string memory details
+ )
+ public
+ {
+ require(tokenWhitelist[tributeToken], "Moloch::submitProposal - tributeToken is not whitelisted");
+ require(tokenWhitelist[paymentToken], "Moloch::submitProposal - payment is not whitelisted");
+ require(applicant != address(0), "Moloch::submitProposal - applicant cannot be 0");
+
+ // collect tribute from applicant and store it in the Moloch until the proposal is processed
+ require(IERC20(tributeToken).transferFrom(msg.sender, address(this), tributeOffered), "Moloch::submitProposal - tribute token transfer failed");
+
+ bool[6] memory flags;
+
+ // create proposal...
+ Proposal memory proposal = Proposal({
+ applicant: applicant,
+ proposer: msg.sender,
+ sponsor: address(0),
+ sharesRequested: sharesRequested,
+ tributeOffered: tributeOffered,
+ tributeToken: IERC20(tributeToken),
+ paymentRequested: paymentRequested,
+ paymentToken: IERC20(paymentToken),
+ startingPeriod: 0,
+ yesVotes: 0,
+ noVotes: 0,
+ flags: flags,
+ details: details,
+ maxTotalSharesAtYesVote: 0
+ });
+
+ proposals[proposalCount] = proposal; // save proposal by its id
+ proposalCount += 1; // increment proposal counter
+
+ // uint256 proposalIndex = proposalQueue.length.sub(1);
+ // TODO emit SubmitProposal(proposalIndex, msg.sender, memberAddress, applicant, tributeOffered, sharesRequested);
+ }
+
+ function submitWhitelistProposal(address tokenToWhitelist, string memory details) public {
+ require(tokenToWhitelist != address(0), "Moloch::submitWhitelistProposal - must provide token address");
+ require(!tokenWhitelist[tokenToWhitelist], "Moloch::submitWhitelistProposal - can't already have whitelisted the token");
+
+ bool[6] memory flags;
+ flags[4] = true; // whitelist proposal = true
+
+ // create proposal ...
+ Proposal memory proposal = Proposal({
+ applicant: address(0),
+ proposer: msg.sender,
+ sponsor: address(0),
+ sharesRequested: 0,
+ tributeOffered: 0,
+ tributeToken: IERC20(tokenToWhitelist), // tributeToken = tokenToWhitelist
+ paymentRequested: 0,
+ paymentToken: IERC20(address(0)),
+ startingPeriod: 0,
+ yesVotes: 0,
+ noVotes: 0,
+ flags: flags,
+ details: details,
+ maxTotalSharesAtYesVote: 0
+ });
+
+
+ proposals[proposalCount] = proposal; // save proposal by its id
+ proposalCount += 1; // increment proposal counter
+
+ // uint256 proposalIndex = proposalQueue.length.sub(1);
+ // TODO emit SubmitProposal(proposalIndex, msg.sender, memberAddress, applicant, tributeOffered, sharesRequested);
+ }
+
+ function submitGuildKickProposal(address memberToKick, string memory details) public {
+ require(members[memberToKick].shares > 0, "Moloch::submitGuildKickProposal - member must have at least one share");
+
+ bool[6] memory flags;
+ flags[5] = true; // guild kick proposal = true
+
+ // create proposal ...
+ Proposal memory proposal = Proposal({
+ applicant: memberToKick, // applicant = memberToKick
+ proposer: msg.sender,
+ sponsor: address(0),
+ sharesRequested: 0,
+ tributeOffered: 0,
+ tributeToken: IERC20(address(0)),
+ paymentRequested: 0,
+ paymentToken: IERC20(address(0)),
+ startingPeriod: 0,
+ yesVotes: 0,
+ noVotes: 0,
+ flags: flags,
+ details: details,
+ maxTotalSharesAtYesVote: 0
+ });
+
+ proposals[proposalCount] = proposal; // save proposal by its id
+ proposalCount += 1; // increment proposal counter
+
+ // uint256 proposalIndex = proposalQueue.length.sub(1);
+ // TODO emit SubmitProposal(proposalIndex, msg.sender, memberAddress, applicant, tributeOffered, sharesRequested);
+ }
+
+ function sponsorProposal(uint256 proposalId) public onlyDelegate {
+ // collect proposal deposit from proposer and store it in the Moloch until the proposal is processed
+ require(depositToken.transferFrom(msg.sender, address(this), proposalDeposit), "Moloch::submitProposal - proposal deposit token transfer failed");
+
+ Proposal memory proposal = proposals[proposalId];
+
+ require(!proposal.flags[0], "Moloch::sponsorProposal - proposal has already been sponsored");
+ require(!proposal.flags[3], "Moloch::sponsorProposal - proposal has been cancelled");
+
+ // token whitelist proposal
+ if (proposal.flags[4]) {
+ require(!proposedToWhitelist[address(proposal.tributeToken)]); // already an active proposal to whitelist this token
+ proposedToWhitelist[address(proposal.tributeToken)] = true;
+
+ // gkick proposal
+ } else if (proposal.flags[5]) {
+ require(!proposedToKick[proposal.applicant]); // already an active proposal to kick this member
+ proposedToKick[proposal.applicant] = true;
+
+ // standard proposal
+ } else {
+ // Make sure we won't run into overflows when doing calculations with shares.
+ // Note that totalShares + totalSharesRequested + sharesRequested is an upper bound
+ // on the number of shares that can exist until this proposal has been processed.
+ require(totalShares.add(totalSharesRequested).add(proposal.sharesRequested) <= MAX_NUMBER_OF_SHARES, "Moloch::submitProposal - too many shares requested");
+ totalSharesRequested = totalSharesRequested.add(proposal.sharesRequested);
+ }
+
+ // compute startingPeriod for proposal
+ uint256 startingPeriod = max(
+ getCurrentPeriod(),
+ proposalQueue.length == 0 ? 0 : proposals[proposalQueue[proposalQueue.length.sub(1)]].startingPeriod
+ ).add(1);
+
+ proposal.startingPeriod = startingPeriod;
+
+ address memberAddress = memberAddressByDelegateKey[msg.sender];
+ proposal.sponsor = memberAddress;
+
+ // ... and append it to the queue by its id
+ proposalQueue.push(proposalId);
+
+ // uint256 proposalIndex = proposalQueue.length.sub(1);
+ // emit SponsorProposal(proposalId, proposalIndex, msg.sender, memberAddress, applicant, tributeOffered, sharesRequested);
+ }
+
+ function submitVote(uint256 proposalIndex, uint8 uintVote) public onlyDelegate {
+ address memberAddress = memberAddressByDelegateKey[msg.sender];
+ Member storage member = members[memberAddress];
+
+ require(proposalIndex < proposalQueue.length, "Moloch::submitVote - proposal does not exist");
+ Proposal storage proposal = proposals[proposalQueue[proposalIndex]];
+
+ require(uintVote < 3, "Moloch::submitVote - uintVote must be less than 3");
+ Vote vote = Vote(uintVote);
+
+ require(proposal.flags[0], "Moloch::submitVote - proposal has not been sponsored");
+ require(getCurrentPeriod() >= proposal.startingPeriod, "Moloch::submitVote - voting period has not started");
+ require(!hasVotingPeriodExpired(proposal.startingPeriod), "Moloch::submitVote - proposal voting period has expired");
+ require(proposal.votesByMember[memberAddress] == Vote.Null, "Moloch::submitVote - member has already voted on this proposal");
+ require(vote == Vote.Yes || vote == Vote.No, "Moloch::submitVote - vote must be either Yes or No");
+
+ // store vote
+ proposal.votesByMember[memberAddress] = vote;
+
+ // count vote
+ if (vote == Vote.Yes) {
+ proposal.yesVotes = proposal.yesVotes.add(member.shares);
+
+ // set highest index (latest) yes vote - must be processed for member to ragequit
+ if (proposalIndex > member.highestIndexYesVote) {
+ member.highestIndexYesVote = proposalIndex;
+ }
+
+ // set maximum of total shares encountered at a yes vote - used to bound dilution for yes voters
+ if (totalShares > proposal.maxTotalSharesAtYesVote) {
+ proposal.maxTotalSharesAtYesVote = totalShares;
+ }
+
+ } else if (vote == Vote.No) {
+ proposal.noVotes = proposal.noVotes.add(member.shares);
+ }
+
+ emit SubmitVote(proposalIndex, msg.sender, memberAddress, uintVote);
+ }
+
+ function processProposal(uint256 proposalIndex) public {
+ require(proposalIndex < proposalQueue.length, "Moloch::processProposal - proposal does not exist");
+ Proposal storage proposal = proposals[proposalQueue[proposalIndex]];
+
+ require(getCurrentPeriod() >= proposal.startingPeriod.add(votingPeriodLength).add(gracePeriodLength), "Moloch::processProposal - proposal is not ready to be processed");
+ require(proposal.flags[1] == false, "Moloch::processProposal - proposal has already been processed");
+ require(proposalIndex == 0 || proposals[proposalQueue[proposalIndex.sub(1)]].flags[1], "Moloch::processProposal - previous proposal must be processed");
+
+ proposal.flags[1] = true;
+ totalSharesRequested = totalSharesRequested.sub(proposal.sharesRequested);
+
+ bool didPass = proposal.yesVotes > proposal.noVotes;
+
+ // If emergencyExitWait has passed from when this proposal *should* have been able to be processed, skip all effects
+ bool emergencyProcessing = false;
+ if (getCurrentPeriod() >= proposal.startingPeriod.add(votingPeriodLength).add(gracePeriodLength).add(emergencyExitWait)) {
+ emergencyProcessing = true;
+ didPass = false;
+ }
+
+ // Make the proposal fail if the dilutionBound is exceeded
+ if (totalShares.mul(dilutionBound) < proposal.maxTotalSharesAtYesVote) {
+ didPass = false;
+ }
+
+ // Make sure there is enough tokens for payments, or auto-fail
+ if (proposal.paymentRequested >= proposal.paymentToken.balanceOf(address(guildBank))) {
+ didPass = false;
+ }
+
+ // PROPOSAL PASSED
+ if (didPass) {
+
+ proposal.flags[2] = true; // didPass = true
+
+ // whitelist proposal passed, add token to whitelist
+ if (proposal.flags[4]) {
+ tokenWhitelist[address(proposal.tributeToken)] = true;
+ approvedTokens.push(proposal.tributeToken);
+
+ // guild kick proposal passed, ragequit 100% of the member's shares
+ // NOTE - if any approvedToken is broken gkicks will fail and get stuck here (until emergency processing)
+ } else if (proposal.flags[5]) {
+ _ragequit(members[proposal.applicant].shares, approvedTokens);
+
+ // standard proposal passed, collect tribute, send payment, mint shares
+ } else {
+ // if the applicant is already a member, add to their existing shares
+ if (members[proposal.applicant].exists) {
+ members[proposal.applicant].shares = members[proposal.applicant].shares.add(proposal.sharesRequested);
+
+ // the applicant is a new member, create a new record for them
+ } else {
+ // if the applicant address is already taken by a member's delegateKey, reset it to their member address
+ if (members[memberAddressByDelegateKey[proposal.applicant]].exists) {
+ address memberToOverride = memberAddressByDelegateKey[proposal.applicant];
+ memberAddressByDelegateKey[memberToOverride] = memberToOverride;
+ members[memberToOverride].delegateKey = memberToOverride;
+ }
+
+ // use applicant address as delegateKey by default
+ members[proposal.applicant] = Member(proposal.applicant, proposal.sharesRequested, true, 0);
+ memberAddressByDelegateKey[proposal.applicant] = proposal.applicant;
+ }
+
+ // mint new shares
+ totalShares = totalShares.add(proposal.sharesRequested);
+
+ // transfer tribute tokens to guild bank
+ require(
+ proposal.tributeToken.transfer(address(guildBank), proposal.tributeOffered),
+ "Moloch::processProposal - token transfer to guild bank failed"
+ );
+
+ // transfer payment tokens to applicant
+ require(
+ guildBank.withdrawToken(proposal.paymentToken, proposal.applicant, proposal.paymentRequested),
+ "Moloch::processProposal - token payment to applicant failed"
+ );
+ }
+
+ // PROPOSAL FAILED
+ } else {
+ // Don't return applicant tokens if we are in emergency processing - likely the tokens are broken
+ if (!emergencyProcessing) {
+ // return all tokens to the proposer
+ require(
+ proposal.tributeToken.transfer(proposal.proposer, proposal.tributeOffered),
+ "Moloch::processProposal - failing vote token transfer failed"
+ );
+ }
+ }
+
+ // if token whitelist proposal, remove token from tokens proposed to whitelist
+ if (proposal.flags[4]) {
+ proposedToWhitelist[address(proposal.tributeToken)] = false;
+ }
+
+ // if guild kick proposal, remove member from list of members proposed to be kicked
+ if (proposal.flags[5]) {
+ proposedToKick[proposal.applicant] = false;
+ }
+
+ // send msg.sender the processingReward
+ require(
+ depositToken.transfer(msg.sender, processingReward),
+ "Moloch::processProposal - failed to send processing reward to msg.sender"
+ );
+
+ // return deposit to sponsor (subtract processing reward)
+ require(
+ depositToken.transfer(proposal.sponsor, proposalDeposit.sub(processingReward)),
+ "Moloch::processProposal - failed to return proposal deposit to sponsor"
+ );
+
+ // TODO emit ProcessProposal()
+ }
+
+ function ragequit(uint256 sharesToBurn) public onlyMember {
+ _ragequit(sharesToBurn, approvedTokens);
+ }
+
+ function safeRagequit(uint256 sharesToBurn, IERC20[] memory tokenList) public onlyMember {
+ // all tokens in tokenList must be in the tokenWhitelist
+ for (uint256 i=0; i < tokenList.length; i++) {
+ require(tokenWhitelist[address(tokenList[i])], "Moloch::safeRequit - token must be whitelisted");
+
+ // check token uniqueness - for every token address after the first, enforce ascending lexical order
+ if (i > 0) {
+ require(tokenList[i] > tokenList[i-1], "Moloch::safeRagequit - tokenList must be unique and in ascending order");
+ }
+ }
+
+ _ragequit(sharesToBurn, tokenList);
+ }
+
+ function _ragequit(uint256 sharesToBurn, IERC20[] memory _approvedTokens) internal {
+ uint256 initialTotalShares = totalShares;
+
+ Member storage member = members[msg.sender];
+
+ require(member.shares >= sharesToBurn, "Moloch::ragequit - insufficient shares");
+
+ require(canRagequit(member.highestIndexYesVote), "Moloch::ragequit - cant ragequit until highest index proposal member voted YES on is processed");
+
+ // burn shares
+ member.shares = member.shares.sub(sharesToBurn);
+ totalShares = totalShares.sub(sharesToBurn);
+
+ // instruct guildBank to transfer fair share of tokens to the ragequitter
+ require(
+ guildBank.withdraw(msg.sender, sharesToBurn, initialTotalShares, _approvedTokens),
+ "Moloch::ragequit - withdrawal of tokens from guildBank failed"
+ );
+
+ emit Ragequit(msg.sender, sharesToBurn);
+ }
+
+ function cancelProposal(uint256 proposalId) public {
+ Proposal storage proposal = proposals[proposalId];
+ require(!proposal.flags[0], "Moloch::cancelProposal - proposal has already been sponsored");
+ require(msg.sender == proposal.proposer, "Moloch::cancelProposal - only the proposer can cancel");
+
+ proposal.flags[3] = true; // cancelled
+
+ require(
+ proposal.tributeToken.transfer(proposal.proposer, proposal.tributeOffered),
+ "Moloch::processProposal - failed to return tribute to proposer"
+ );
+
+ emit CancelProposal(proposalId, msg.sender);
+ }
+
+ function updateDelegateKey(address newDelegateKey) public onlyMember {
+ require(newDelegateKey != address(0), "Moloch::updateDelegateKey - newDelegateKey cannot be 0");
+
+ // skip checks if member is setting the delegate key to their member address
+ if (newDelegateKey != msg.sender) {
+ require(!members[newDelegateKey].exists, "Moloch::updateDelegateKey - cant overwrite existing members");
+ require(!members[memberAddressByDelegateKey[newDelegateKey]].exists, "Moloch::updateDelegateKey - cant overwrite existing delegate keys");
+ }
+
+ Member storage member = members[msg.sender];
+ memberAddressByDelegateKey[member.delegateKey] = address(0);
+ memberAddressByDelegateKey[newDelegateKey] = msg.sender;
+ member.delegateKey = newDelegateKey;
+
+ emit UpdateDelegateKey(msg.sender, newDelegateKey);
+ }
+
+ // ****************
+ // GETTER FUNCTIONS
+ // ****************
+
+ function max(uint256 x, uint256 y) internal pure returns (uint256) {
+ return x >= y ? x : y;
+ }
+
+ function getCurrentPeriod() public view returns (uint256) {
+ return now.sub(summoningTime).div(periodDuration);
+ }
+
+ function getProposalQueueLength() public view returns (uint256) {
+ return proposalQueue.length;
+ }
+
+ // can only ragequit if the latest proposal you voted YES on has been processed
+ function canRagequit(uint256 highestIndexYesVote) public view returns (bool) {
+ require(highestIndexYesVote < proposalQueue.length, "Moloch::canRagequit - proposal does not exist");
+ return proposals[proposalQueue[highestIndexYesVote]].flags[1]; // processed
+ }
+
+ function hasVotingPeriodExpired(uint256 startingPeriod) public view returns (bool) {
+ return getCurrentPeriod() >= startingPeriod.add(votingPeriodLength);
+ }
+
+ function getMemberProposalVote(address memberAddress, uint256 proposalIndex) public view returns (Vote) {
+ require(members[memberAddress].exists, "Moloch::getMemberProposalVote - member doesn't exist");
+ require(proposalIndex < proposalQueue.length, "Moloch::getMemberProposalVote - proposal doesn't exist");
+ return proposals[proposalQueue[proposalIndex]].votesByMember[memberAddress];
+ }
+}
\ No newline at end of file
diff --git a/packages/chain-events/eth/hardhat.config.ts b/packages/chain-events/eth/hardhat.config.ts
new file mode 100644
index 00000000000..c11a75371cf
--- /dev/null
+++ b/packages/chain-events/eth/hardhat.config.ts
@@ -0,0 +1,66 @@
+// eslint-disable-next-line import/no-extraneous-dependencies
+import 'hardhat-typechain';
+import '@nomiclabs/hardhat-waffle';
+
+export default {
+ solidity: {
+ compilers: [
+ {
+ version: '0.8.4',
+ settings: {
+ optimizer: { enabled: true, runs: 200 },
+ evmVersion: 'istanbul',
+ },
+ },
+ {
+ version: '0.8.0',
+ settings: {
+ optimizer: { enabled: true, runs: 200 },
+ evmVersion: 'istanbul',
+ },
+ },
+ {
+ version: '0.7.5',
+ settings: {
+ optimizer: { enabled: true, runs: 200 },
+ evmVersion: 'istanbul',
+ },
+ },
+ {
+ version: '0.6.8',
+ settings: {
+ optimizer: { enabled: true, runs: 200 },
+ evmVersion: 'istanbul',
+ },
+ },
+ {
+ version: '0.6.12',
+ settings: {
+ optimizer: { enabled: true, runs: 200 },
+ evmVersion: 'istanbul',
+ },
+ },
+ {
+ version: '0.5.5',
+ settings: {
+ optimizer: { enabled: true, runs: 200 },
+ evmVersion: 'petersburg',
+ },
+ },
+ {
+ version: '0.5.16',
+ settings: {
+ optimizer: { enabled: true, runs: 200 },
+ evmVersion: 'istanbul',
+ },
+ },
+ ],
+ },
+ typechain: {
+ outDir: '../src/contractTypes',
+ target: 'ethers-v5',
+ },
+ mocha: {
+ timeout: 40000
+ },
+};
diff --git a/packages/chain-events/eth/migrations/1_initial_migration.js b/packages/chain-events/eth/migrations/1_initial_migration.js
new file mode 100644
index 00000000000..82c676e13bb
--- /dev/null
+++ b/packages/chain-events/eth/migrations/1_initial_migration.js
@@ -0,0 +1,6 @@
+const Migrations = artifacts.require('Migrations');
+
+// eslint-disable-next-line func-names
+module.exports = function (deployer) {
+ deployer.deploy(Migrations);
+};
diff --git a/packages/chain-events/eth/migrations/2_deploy_contracts.js b/packages/chain-events/eth/migrations/2_deploy_contracts.js
new file mode 100644
index 00000000000..978737eca20
--- /dev/null
+++ b/packages/chain-events/eth/migrations/2_deploy_contracts.js
@@ -0,0 +1,22 @@
+const MolochV2 = artifacts.require('Moloch2');
+const GuildBankV2 = artifacts.require('GuildBank2');
+const HelperV2 = artifacts.require('Helper');
+
+// eslint-disable-next-line func-names
+module.exports = function (deployer) {
+ deployer.deploy(MolochV2,
+ '0xcE7aa2D3C1F8B572B50238230f5D55A78dB86087', // Summoner
+ ['0xcE7aa2D3C1F8B572B50238230f5D55A78dB86087'], // approvedTokens
+ 17280, // _periodDuration
+ 35, // _votingPeriodLength
+ 35, // _gracePeriodLength
+ 70, // _abortWindow
+ '10000000', // _proposalDeposit
+ 3, // _diluationBound
+ '10000000', // _processingReward
+ // { gas: 25000 }
+ // eslint-disable-next-line function-paren-newline
+ );
+ deployer.deploy(GuildBankV2);
+ // deployer.deploy(Helper);
+};
diff --git a/packages/chain-events/eth/migrations/3_moloch_v1.js b/packages/chain-events/eth/migrations/3_moloch_v1.js
new file mode 100644
index 00000000000..a0ea866e1f8
--- /dev/null
+++ b/packages/chain-events/eth/migrations/3_moloch_v1.js
@@ -0,0 +1,30 @@
+const MolochV1 = artifacts.require('Moloch1');
+const GuildBankV1 = artifacts.require('GuildBank1');
+const Token = artifacts.require('Token');
+
+// eslint-disable-next-line func-names
+module.exports = async function (deployer, network, accounts) {
+ await deployer.deploy(Token, 10000);
+ const token = await Token.deployed();
+
+ const summoner = accounts[0];
+ const applicants = accounts.slice(5);
+
+ await deployer.deploy(MolochV1,
+ summoner,
+ token.address, // approvedTokens:
+ 60, // _periodDuration:
+ 2, // _votingPeriodLength:
+ 2, // _gracePeriodLength:
+ 1, // _abortWindow:
+ '3', // _proposalDeposit:
+ 3, // _diluationBound:
+ '3', // _processingReward:
+ // { gas: 25000 }
+ // eslint-disable-next-line function-paren-newline
+ );
+ const moloch = await MolochV1.deployed();
+
+ await deployer.deploy(GuildBankV1, summoner);
+ // deployer.deploy(Helper);
+};
diff --git a/packages/chain-events/eth/migrations/4_marlin_v2.js b/packages/chain-events/eth/migrations/4_marlin_v2.js
new file mode 100644
index 00000000000..59ef09b68b6
--- /dev/null
+++ b/packages/chain-events/eth/migrations/4_marlin_v2.js
@@ -0,0 +1,16 @@
+const MPond = artifacts.require('MPond');
+const GovernorAlpha = artifacts.require('GovernorAlpha');
+const Timelock = artifacts.require('Timelock');
+
+// eslint-disable-next-line func-names
+module.exports = async function (deployer, network, accounts) {
+ // Marlin Contracts
+ // accounts[0] is initial mpondGuardian, admin of timelock, and guardian of GovernorAlpha
+ const mpondGuardian = accounts[0]
+ await deployer.deploy(MPond, mpondGuardian, accounts[1]);
+ const mpond = await MPond.deployed();
+ await deployer.deploy(Timelock, mpondGuardian, 172800); // 172800 is 2 days in seconds, which is the minimum delay for the contract
+ const timelock = await Timelock.deployed();
+ await deployer.deploy(GovernorAlpha, timelock.address, mpond.address, mpondGuardian);
+ const governorAlpha = GovernorAlpha.deployed();
+};
\ No newline at end of file
diff --git a/packages/chain-events/eth/scripts/triggerEvents.js b/packages/chain-events/eth/scripts/triggerEvents.js
new file mode 100644
index 00000000000..95947e114af
--- /dev/null
+++ b/packages/chain-events/eth/scripts/triggerEvents.js
@@ -0,0 +1,59 @@
+const truffleContract = require('truffle-contract');
+const Web3 = require('web3');
+const provider = new Web3.providers.HttpProvider('http://localhost:9545');
+const web3 = new Web3(provider);
+
+const Moloch1Contract = truffleContract(require('../build/contracts/Moloch1.json'));
+Moloch1Contract.setProvider(provider);
+
+const TokenContract = truffleContract(require('../build/contracts/Token.json'));
+TokenContract.setProvider(web3.currentProvider);
+
+const summoner = '0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1';
+const applicant = '0xFFcf8FDEE72ac11b5c542428B35EEF5769C409f0';
+
+async function submitProposal(moloch1, token) {
+ await token.transfer(applicant, 10, { from: summoner });
+ await token.approve(moloch1.address, 5, { from: summoner });
+ await token.approve(moloch1.address, 5, { from: applicant });
+ await moloch1.submitProposal(applicant, 5, 5, 'hello', { from: summoner });
+ console.log('Proposal created!');
+}
+
+async function submitVote(moloch1, proposalIndex) {
+ await moloch1.submitVote(proposalIndex, 1, { from: summoner });
+ console.log('Vote submitted!');
+}
+
+async function processProposal(moloch1, proposalIndex) {
+ await moloch1.processProposal(proposalIndex, { from: summoner });
+ console.log('Proposal processed!');
+}
+
+async function abort(moloch1, proposalIndex) {
+ await moloch1.abortProposal(proposalIndex, { from: applicant });
+ console.log('Proposal aborted!');
+}
+
+async function ragequit(moloch1, sharesToBurn, who) {
+ await moloch1.ragequit(sharesToBurn, { from: who });
+ console.log('Ragequit!');
+}
+
+async function updateDelegateKey(moloch1, newKey, who) {
+ await moloch1.updateDelegateKey(newKey, { from: who });
+ console.log('Delegate key updated!');
+}
+
+// eslint-disable-next-line func-names
+module.exports = async function (callback) {
+ try {
+ const [ moloch1, token ] = await Promise.all([ Moloch1Contract.deployed(), TokenContract.deployed() ]);
+ await submitProposal(moloch1, token);
+ await submitVote(moloch1, 0);
+ console.log('Done!');
+ callback();
+ } catch (err) {
+ callback(err);
+ }
+};
diff --git a/packages/chain-events/eth/truffle-config.js b/packages/chain-events/eth/truffle-config.js
new file mode 100644
index 00000000000..06fef89e3fc
--- /dev/null
+++ b/packages/chain-events/eth/truffle-config.js
@@ -0,0 +1,70 @@
+/* eslint-disable import/no-extraneous-dependencies */
+/* eslint-disable quotes */
+/* eslint-disable no-multi-spaces */
+
+require('@babel/register');
+require('@babel/polyfill');
+
+// const HDWalletProvider = require('truffle-hdwallet-provider');
+// const infuraKey = "fj4jll3k.....";
+//
+// const fs = require('fs');
+// const mnemonic = fs.readFileSync(".secret").toString().trim();
+
+module.exports = {
+ /**
+ * Networks define how you connect to your ethereum client and let you set the
+ * defaults web3 uses to send transactions. If you don't specify one truffle
+ * will spin up a development blockchain for you on port 9545 when you
+ * run `develop` or `test`. You can ask a truffle command to use a specific
+ * network from the command line, e.g
+ *
+ * $ truffle test --network
+ */
+
+ networks: {
+ // Useful for testing. The `development` name is special - truffle uses it by default
+ // if it's defined here and no other network is specified at the command line.
+ // You should run a client (like ganache-cli, geth or parity) in a separate terminal
+ // tab if you use this network and you must also set the `host`, `port` and `network_id`
+ // options below to some value.
+ //
+ development: {
+ host: '127.0.0.1', // Localhost (default: none)
+ port: 9545, // Standard Ethereum port (default: none)
+ network_id: '*', // Any network (default: none)
+ gas: 8000000,
+ gasPrice: 2000, // 20 gwei (in wei) (default: 100 gwei)
+ },
+
+ // Another network with more advanced options...
+ // advanced: {
+ // port: 8777, // Custom port
+ // network_id: 1342, // Custom network
+ // gas: 8500000, // Gas sent with each transaction (default: ~6700000)
+ // gasPrice: 20000000000, // 20 gwei (in wei) (default: 100 gwei)
+ // from: , // Account to send txs from (default: accounts[0])
+ // websockets: true // Enable EventEmitter interface for web3 (default: false)
+ // },
+ },
+
+ // Set default mocha options here, use special reporters etc.
+ mocha: {
+ // timeout: 100000
+ },
+
+ // Configure your compilers
+ compilers: {
+ solc: {
+ // version: '0.5.1', // Fetch exact version from solc-bin (default: truffle's version)
+ // docker: true, // Use "0.5.1" you've installed locally with docker (default: false)
+ // settings: { // See the solidity docs for advice about optimization and evmVersion
+ // optimizer: {
+ // enabled: false,
+ // runs: 200
+ // },
+ // evmVersion: "byzantium"
+ // }
+ },
+ },
+};
diff --git a/packages/chain-events/manyListenerConfigEx.json b/packages/chain-events/manyListenerConfigEx.json
new file mode 100644
index 00000000000..123b958bb28
--- /dev/null
+++ b/packages/chain-events/manyListenerConfigEx.json
@@ -0,0 +1,13 @@
+[
+ {
+ "network": "kusama",
+ "url": "",
+ "archival": false,
+ "skipCatchup": true
+ },
+ {
+ "network": "polkadot",
+ "archival": false,
+ "skipCatchup": true
+ }
+]
diff --git a/packages/chain-events/package.json b/packages/chain-events/package.json
new file mode 100644
index 00000000000..82e4ded412e
--- /dev/null
+++ b/packages/chain-events/package.json
@@ -0,0 +1,103 @@
+{
+ "name": "chain-events",
+ "version": "0.13.3",
+ "description": "Listen to various chains for events.",
+ "license": "GPL-3.0",
+ "files": [
+ "src/"
+ ],
+ "main": "src/index.ts",
+ "types": "dist/index.d.ts",
+ "scripts": {
+ "build": "rimraf dist/ && tsc --project tsconfig.json && copyfiles -u 1 \"./src/**/*.d.ts\" \"dist\"",
+ "compile-contracts": "cd eth && npx hardhat compile",
+ "unit-test": "ts-mocha --config ./.mocharc.json ./test/unit/**/*.spec.ts",
+ "integration-test": "cd eth && hardhat test ../test/integration/*.spec.ts",
+ "lint": "eslint src/ test/",
+ "listen": "ts-node -T ./scripts/listener.ts",
+ "listenV2": "ts-node -T ./scripts/listenerV2.ts",
+ "listen-archival": "ts-node -T ./scripts/listener.ts -n edgeware-local -a true",
+ "scrape": "ts-node -T ./scripts/scraper.ts",
+ "ganache": "ganache-cli -m \"Alice\" -p 9545 -l 800000000 --allowUnlimitedContractSize",
+ "batch-poll": "ts-node -T ./scripts/batchPoller.ts",
+ "preyalcpublish": "yarn build"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "repository": {
+ "type": "git",
+ "url": "git+https://github.com/hicommonwealth/chain-events.git"
+ },
+ "bugs": {
+ "url": "https://github.com/hicommonwealth/chain-events/issues"
+ },
+ "homepage": "https://github.com/hicommonwealth/chain-events#readme",
+ "dependencies": {
+ "@ethersproject/abi": "^5.0.0",
+ "@ethersproject/bytes": "^5.0.0",
+ "@ethersproject/providers": "^5.0.0",
+ "@nomiclabs/hardhat-ethers": "^2.0.2",
+ "@polkadot/api": "6.0.5",
+ "@polkadot/api-derive": "6.0.5",
+ "@polkadot/types": "6.0.5",
+ "@polkadot/util": "7.4.1",
+ "bn.js": "^5.1.3",
+ "ethereum-block-by-date": "^1.4.0",
+ "ethers": "^5.0.0",
+ "lodash": "^4.17.21",
+ "moment": "^2.29.1",
+ "node-fetch": "^2.6.1",
+ "pg": "^8.6.0",
+ "pg-format": "^1.0.4",
+ "sleep-promise": "^8.0.1",
+ "typescript-logging": "^0.6.4",
+ "underscore": "^1.10.2",
+ "web3": "^1.3.1",
+ "web3-core": "^1.3.1",
+ "web3-utils": "^1.3.1"
+ },
+ "devDependencies": {
+ "@aave/aave-token": "^1.0.4",
+ "@babel/core": "^7.10.3",
+ "@babel/polyfill": "^7.10.1",
+ "@babel/register": "^7.10.3",
+ "@istanbuljs/nyc-config-typescript": "^0.1.3",
+ "@nomiclabs/hardhat-waffle": "^2.0.1",
+ "@openzeppelin/contracts": "^2.4.0",
+ "@openzeppelin/contracts-governance": "npm:@openzeppelin/contracts@^4.3.2",
+ "@typechain/ethers-v5": "^6.0.0",
+ "@types/bn.js": "^4.11.6",
+ "@types/chai": "^4.2.11",
+ "@types/mocha": "^7.0.2",
+ "@types/node": "^14.0.14",
+ "@types/underscore": "^1.10.1",
+ "@types/yargs": "^15.0.9",
+ "@typescript-eslint/eslint-plugin": "^4.22.0",
+ "@typescript-eslint/parser": "^4.22.0",
+ "chai": "^4.2.0",
+ "copyfiles": "^2.4.1",
+ "dotenv": "^10.0.0",
+ "eslint": "^7.14.0",
+ "eslint-config-airbnb-base": "^14.0.0",
+ "eslint-config-prettier": "^6.10.1",
+ "eslint-plugin-import": "^2.20.0",
+ "eslint-plugin-prettier": "^3.1.2",
+ "ganache-cli": "^6.9.1",
+ "hardhat": "^2.6.4",
+ "hardhat-typechain": "^0.3.5",
+ "install-peers-cli": "^2.2.0",
+ "jsdom": "^16.2.2",
+ "jsdom-global": "^3.0.2",
+ "mocha": "^8.2.1",
+ "nyc": "^15.1.0",
+ "prettier": "2.0.2",
+ "rimraf": "^3.0.2",
+ "ts-generator": "^0.1.1",
+ "ts-mocha": "^8.0.0",
+ "ts-node": "^8.10.2",
+ "typechain": "^4.0.1",
+ "typescript": "^4.3.4",
+ "yargs": "^16.1.0"
+ }
+}
diff --git a/packages/chain-events/scripts/batchPoller.ts b/packages/chain-events/scripts/batchPoller.ts
new file mode 100644
index 00000000000..2decb2f468d
--- /dev/null
+++ b/packages/chain-events/scripts/batchPoller.ts
@@ -0,0 +1,113 @@
+import { ApiPromise } from '@polkadot/api';
+import { LogGroupControlSettings } from 'typescript-logging';
+import {
+ chainSupportedBy, SubstrateEvents, IEventHandler, IDisconnectedRange, CWEvent, SubstrateTypes
+} from '../dist/index';
+import { factoryControl } from '../dist/logging';
+
+export async function batchQuery(
+ api: ApiPromise,
+ eventHandlers: IEventHandler[],
+ fullRange?: IDisconnectedRange,
+ aggregateFirst = false, // fetch all blocks before running processor function
+) {
+ // turn off debug logging for poller -- it's annoying
+ factoryControl.change({ group: 'all', logLevel: 'Info' } as LogGroupControlSettings);
+
+ // create range if not already set
+ const latestBlock = +(await api.derive.chain.bestNumber());
+ if (!fullRange) {
+ fullRange = {
+ startBlock: 0,
+ endBlock: latestBlock
+ };
+ } else if (!fullRange.endBlock) {
+ fullRange.endBlock = latestBlock;
+ }
+
+ const processBlocksFn = async (blocks: SubstrateTypes.Block[]) => {
+ // process all blocks
+ const processor = new SubstrateEvents.Processor(api);
+ for (const block of blocks) {
+ // retrieve events from block
+ const events = await processor.process(block);
+
+ // send all events through event-handlers in sequence
+ await Promise.all(events.map(async (event) => {
+ let prevResult = null;
+ for (const handler of eventHandlers) {
+ try {
+ // pass result of last handler into next one (chaining db events)
+ prevResult = await handler.handle(event, prevResult);
+ } catch (err) {
+ console.error(`Event handle failure: ${err.message}`);
+ break;
+ }
+ }
+ }));
+ }
+ }
+
+ // TODO: configure chunk size
+ const CHUNK_SIZE = 1000;
+
+ const poller = new SubstrateEvents.Poller(api);
+ const results = [];
+ // iterate over all blocks in chunks, from smallest to largest, and place in result array
+ for (let block = fullRange.startBlock + CHUNK_SIZE; block <= fullRange.endBlock; block += CHUNK_SIZE) {
+ try {
+ const chunk = await poller.poll({
+ startBlock: block - CHUNK_SIZE,
+ endBlock: Math.min(block, fullRange.endBlock)
+ }, CHUNK_SIZE);
+
+ // the final query will be smaller than CHUNK_SIZE, otherwise a shortened length means pruning took place
+ if (chunk.length < CHUNK_SIZE && block < fullRange.endBlock) {
+ throw new Error('Found pruned headers, must query archival node');
+ }
+ console.log(`Fetched blocks ${chunk[0].header.number} to ${chunk[CHUNK_SIZE - 1].header.number}.`);
+
+ if (aggregateFirst) {
+ // compile chunks into results
+ results.push(...chunk);
+ } else {
+ // process chunk immediately, and do not aggregate
+ await processBlocksFn(chunk);
+ }
+ } catch (err) {
+ console.error(`Failed to fetch blocks ${block - CHUNK_SIZE}-${block}: ${err.message}.`);
+ // TODO: exit if desired
+ }
+ }
+ if (aggregateFirst) {
+ await processBlocksFn(results);
+ }
+}
+
+class StandaloneEventHandler extends IEventHandler {
+ public async handle(event: CWEvent): Promise {
+ console.log(`Received event: ${JSON.stringify(event, null, 2)}`);
+ }
+}
+
+function main() {
+ const args = process.argv.slice(2);
+ const chain = args[0] || 'edgeware';
+ console.log(`Listening to events on ${chain}.`);
+
+ const networks = {
+ 'edgeware': 'ws://mainnet1.edgewa.re:9944',
+ 'edgeware-local': 'ws://localhost:9944',
+ 'edgeware-testnet': 'wss://beresheet1.edgewa.re',
+ };
+
+ const url = networks[chain];
+
+ if (!url) throw new Error(`no url for chain ${chain}`);
+ SubstrateEvents.createApi(url, {}).then(async (api) => {
+ await batchQuery(api, [ new StandaloneEventHandler() ]);
+ process.exit(0);
+ });
+}
+
+main();
diff --git a/packages/chain-events/scripts/listener.ts b/packages/chain-events/scripts/listener.ts
new file mode 100644
index 00000000000..d2a4dfe023c
--- /dev/null
+++ b/packages/chain-events/scripts/listener.ts
@@ -0,0 +1,258 @@
+/* eslint-disable import/no-extraneous-dependencies */
+/* eslint-disable no-console */
+
+import * as yargs from 'yargs';
+import fetch from 'node-fetch';
+import EthDater from 'ethereum-block-by-date';
+
+import {
+ IEventHandler,
+ CWEvent,
+ SubstrateEvents,
+ CompoundEvents,
+ MolochEvents,
+ AaveEvents,
+ Erc20Events,
+ SupportedNetwork,
+ Erc721Events,
+} from '../src/index';
+
+import { contracts, networkSpecs, networkUrls } from './listenerUtils';
+
+// eslint-disable-next-line @typescript-eslint/no-var-requires
+require('dotenv').config();
+
+const { argv } = yargs
+ .options({
+ network: {
+ alias: 'n',
+ choices: Object.values(SupportedNetwork),
+ demandOption: true,
+ description: 'network listener to use',
+ },
+ chain: {
+ alias: 'c',
+ type: 'string',
+ description: 'chain to listen on',
+ },
+ url: {
+ alias: 'u',
+ type: 'string',
+ description: 'node url',
+ },
+ contractAddress: {
+ alias: 'a',
+ type: 'string',
+ description: 'eth contract address',
+ },
+ archival: {
+ alias: 'A',
+ type: 'boolean',
+ description: 'run listener in archival mode or not',
+ },
+ startBlock: {
+ alias: 'b',
+ type: 'number',
+ description:
+ 'when running in archival mode, which block should we start from',
+ },
+ })
+ .check((data) => {
+ if (!data.url && !data.chain) {
+ if (data.network === SupportedNetwork.Substrate) {
+ throw new Error('Must pass either URL or chain name!');
+ } else {
+ // default to eth mainnet if not on substrate
+ data.chain = 'erc20';
+ }
+ }
+ if (!networkUrls[data.chain] && !data.url) {
+ throw new Error(`no URL found for ${data.chain}`);
+ }
+ if (
+ data.network !== SupportedNetwork.Substrate &&
+ data.network !== SupportedNetwork.ERC20 &&
+ data.network !== SupportedNetwork.ERC721 &&
+ !data.contractAddress &&
+ !contracts[data.chain]
+ ) {
+ throw new Error(`no contract found for ${data.chain}`);
+ }
+ if (
+ data.network === SupportedNetwork.Substrate &&
+ !networkSpecs[data.chain]
+ ) {
+ throw new Error(`no spec found for ${data.chain}`);
+ }
+ return true;
+ });
+
+const { archival } = argv;
+// if running in archival mode then which block shall we star from
+const startBlock: number = argv.startBlock ?? 0;
+const { network } = argv;
+const chain = argv.chain || 'dummy';
+const url = argv.url || networkUrls[chain];
+const spec = networkSpecs[chain];
+const contract = argv.contractAddress || contracts[chain];
+
+class StandaloneEventHandler extends IEventHandler {
+ // eslint-disable-next-line class-methods-use-this
+ public async handle(event: CWEvent): Promise {
+ console.log(`Received event: ${JSON.stringify(event, null, 2)}`);
+ return null;
+ }
+}
+const skipCatchup = false;
+const tokenListUrls = ['https://gateway.ipfs.io/ipns/tokens.uniswap.org'];
+const nftListUrls = [
+ 'https://raw.githubusercontent.com/jnaviask/collectible-lists/main/test/schema/bigexample.collectiblelist.json',
+];
+
+interface TokenListEntry {
+ chainId: number;
+ address: string;
+ name: string;
+ symbol: string;
+ standard?: string; // erc721
+ decimals?: number; // 18
+}
+
+async function getTokenList(tokenListUrl: string): Promise {
+ const data = await fetch(tokenListUrl)
+ .then((o) => o.json())
+ .catch((e) => {
+ console.error(e);
+ return [];
+ });
+ return data?.tokens?.filter((o) => o);
+}
+
+console.log(`Connecting to ${chain} on url ${url}...`);
+
+if (network === SupportedNetwork.Substrate) {
+ SubstrateEvents.createApi(url, spec as any).then(async (api) => {
+ const fetcher = new SubstrateEvents.StorageFetcher(api);
+ try {
+ const fetched = await fetcher.fetch();
+ console.log(fetched.map((f) => f.data));
+ } catch (err) {
+ console.log(err);
+ console.error(`Got error from fetcher: ${JSON.stringify(err, null, 2)}.`);
+ }
+ SubstrateEvents.subscribeEvents({
+ chain,
+ api,
+ handlers: [new StandaloneEventHandler()],
+ skipCatchup,
+ archival,
+ startBlock,
+ verbose: true,
+ enricherConfig: { balanceTransferThresholdPermill: 1_000 }, // 0.1% of total issuance
+ });
+ });
+} else if (network === SupportedNetwork.Moloch) {
+ const contractVersion = 1;
+ MolochEvents.createApi(url, contractVersion, contract).then(async (api) => {
+ const dater = new EthDater(api.provider);
+ const fetcher = new MolochEvents.StorageFetcher(
+ api,
+ contractVersion,
+ dater
+ );
+ try {
+ const fetched = await fetcher.fetch(
+ { startBlock: 11000000, maxResults: 3 },
+ true
+ );
+ // const fetched = await fetcher.fetchOne('132');
+ console.log(fetched.map((f) => f.data));
+ } catch (err) {
+ console.log(err);
+ console.error(`Got error from fetcher: ${JSON.stringify(err, null, 2)}.`);
+ }
+ MolochEvents.subscribeEvents({
+ chain,
+ api,
+ contractVersion,
+ handlers: [new StandaloneEventHandler()],
+ skipCatchup,
+ verbose: true,
+ });
+ });
+} else if (network === SupportedNetwork.Compound) {
+ CompoundEvents.createApi(url, contract).then(async (api) => {
+ const fetcher = new CompoundEvents.StorageFetcher(api);
+ try {
+ const fetched = await fetcher.fetch({
+ startBlock: 13353227,
+ maxResults: 1,
+ });
+ // const fetched = await fetcher.fetchOne('2');
+ console.log(fetched.map((f) => f.data));
+ } catch (err) {
+ console.log(err);
+ console.error(`Got error from fetcher: ${JSON.stringify(err, null, 2)}.`);
+ }
+ CompoundEvents.subscribeEvents({
+ chain,
+ api,
+ handlers: [new StandaloneEventHandler()],
+ skipCatchup,
+ verbose: true,
+ });
+ });
+} else if (network === SupportedNetwork.Aave) {
+ AaveEvents.createApi(url, contract).then(async (api) => {
+ const fetcher = new AaveEvents.StorageFetcher(api);
+ try {
+ const fetched = await fetcher.fetch({
+ startBlock: 13353227,
+ maxResults: 1,
+ });
+ // const fetched = await fetcher.fetchOne('10');
+ console.log(fetched.sort((a, b) => a.blockNumber - b.blockNumber));
+ } catch (err) {
+ console.log(err);
+ console.error(`Got error from fetcher: ${JSON.stringify(err, null, 2)}.`);
+ }
+ AaveEvents.subscribeEvents({
+ chain,
+ api,
+ handlers: [new StandaloneEventHandler()],
+ skipCatchup,
+ verbose: true,
+ });
+ });
+} else if (network === SupportedNetwork.ERC20) {
+ getTokenList(tokenListUrls[0]).then(async (tokens) => {
+ const validTokens = tokens.filter((t) => t.chainId === 1);
+ const tokenAddresses = validTokens.map((o) => o.address);
+ const tokenNames = validTokens.map((o) => o.name);
+ const api = await Erc20Events.createApi(url, tokenAddresses, tokenNames);
+ Erc20Events.subscribeEvents({
+ chain,
+ api,
+ handlers: [new StandaloneEventHandler()],
+ skipCatchup,
+ verbose: false,
+ enricherConfig: { balanceTransferThresholdPermill: 500_000 }, // 50% of total supply
+ });
+ });
+} else if (network === SupportedNetwork.ERC721) {
+ getTokenList(nftListUrls[0]).then(async (tokens) => {
+ const validTokens = tokens.filter(
+ (t) => t.chainId === 1 && t.standard === 'erc721'
+ );
+ const tokenAddresses = validTokens.map((o) => o.address);
+ const tokenNames = validTokens.map((o) => o.name);
+ const api = await Erc721Events.createApi(url, tokenAddresses, tokenNames);
+ Erc721Events.subscribeEvents({
+ chain,
+ api,
+ handlers: [new StandaloneEventHandler()],
+ skipCatchup,
+ verbose: false,
+ });
+ });
+}
diff --git a/packages/chain-events/scripts/listenerUtils.ts b/packages/chain-events/scripts/listenerUtils.ts
new file mode 100644
index 00000000000..b068e610bff
--- /dev/null
+++ b/packages/chain-events/scripts/listenerUtils.ts
@@ -0,0 +1,63 @@
+import type { RegisteredTypes } from '@polkadot/types/types';
+
+import { HydraDXSpec } from './specs/hydraDX';
+import { KulupuSpec } from './specs/kulupu';
+import { StafiSpec } from './specs/stafi';
+import { CloverSpec } from './specs/clover';
+import { EdgewareSpec } from './specs/edgeware';
+
+export const networkUrls = {
+ clover: 'wss://api.clover.finance',
+ hydradx: 'wss://rpc-01.snakenet.hydradx.io',
+ edgeware: 'ws://mainnet2.edgewa.re:9944',
+ 'edgeware-local': 'ws://localhost:9944',
+ 'edgeware-testnet': 'wss://beresheet1.edgewa.re',
+ kusama: 'wss://kusama-rpc.polkadot.io',
+ polkadot: 'wss://rpc.polkadot.io',
+ kulupu: 'ws://rpc.kulupu.corepaper.org/ws',
+ stafi: 'wss://scan-rpc.stafi.io/ws',
+
+ moloch: 'wss://eth-mainnet.alchemyapi.io/v2/cNC4XfxR7biwO2bfIO5aKcs9EMPxTQfr',
+ 'moloch-local': 'ws://127.0.0.1:9545',
+
+ marlin: 'wss://eth-mainnet.alchemyapi.io/v2/cNC4XfxR7biwO2bfIO5aKcs9EMPxTQfr',
+ 'marlin-local': 'ws://127.0.0.1:9545',
+ uniswap:
+ 'wss://eth-mainnet.alchemyapi.io/v2/cNC4XfxR7biwO2bfIO5aKcs9EMPxTQfr',
+ tribe: 'wss://eth-mainnet.alchemyapi.io/v2/cNC4XfxR7biwO2bfIO5aKcs9EMPxTQfr',
+
+ aave: 'wss://eth-mainnet.alchemyapi.io/v2/cNC4XfxR7biwO2bfIO5aKcs9EMPxTQfr',
+ 'aave-local': 'ws://127.0.0.1:9545',
+ 'dydx-ropsten':
+ 'wss://eth-ropsten.alchemyapi.io/v2/2xXT2xx5AvA3GFTev3j_nB9LzWdmxPk7',
+ dydx: 'wss://eth-mainnet.alchemyapi.io/v2/cNC4XfxR7biwO2bfIO5aKcs9EMPxTQfr',
+ frax: 'ws://localhost:8545',
+
+ erc20: 'wss://eth-mainnet.alchemyapi.io/v2/cNC4XfxR7biwO2bfIO5aKcs9EMPxTQfr',
+ 'eth-local': 'ws://127.0.0.1:8545',
+} as const;
+
+export const networkSpecs: { [chain: string]: RegisteredTypes } = {
+ clover: CloverSpec,
+ hydradx: HydraDXSpec,
+ kulupu: KulupuSpec,
+ edgeware: EdgewareSpec,
+ 'edgeware-local': EdgewareSpec,
+ 'edgeware-testnet': EdgewareSpec,
+ stafi: StafiSpec,
+ kusama: {},
+ polkadot: {},
+};
+
+export const contracts = {
+ moloch: '0x1fd169A4f5c59ACf79d0Fd5d91D1201EF1Bce9f1',
+ 'moloch-local': '0x9561C133DD8580860B6b7E504bC5Aa500f0f06a7',
+ marlin: '0x777992c2E4EDF704e49680468a9299C6679e37F6',
+ aave: '0xEC568fffba86c094cf06b22134B23074DFE2252c',
+ 'aave-local': '0xcf7ed3acca5a467e9e704c703e8d87f634fb0fc9',
+ 'dydx-ropsten': '0x6938240Ba19cB8a614444156244b658f650c8D5c',
+ dydx: '0x7E9B1672616FF6D6629Ef2879419aaE79A9018D2',
+ uniswap: '0xc4e172459f1e7939d522503b81afaac1014ce6f6',
+ frax: '0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0',
+ 'commonwealth-local': '0x7914a8b73E11432953d9cCda060018EA1d9DCde9',
+};
diff --git a/packages/chain-events/scripts/listenerV2.ts b/packages/chain-events/scripts/listenerV2.ts
new file mode 100644
index 00000000000..35331846790
--- /dev/null
+++ b/packages/chain-events/scripts/listenerV2.ts
@@ -0,0 +1,129 @@
+import * as yargs from 'yargs';
+
+import { createListener, LoggingHandler, SupportedNetwork } from '../src';
+
+import { networkUrls, contracts, networkSpecs } from './listenerUtils';
+
+require('dotenv').config();
+
+const { argv } = yargs.options({
+ network: {
+ alias: 'n',
+ choices: Object.values(SupportedNetwork),
+ demandOption: true,
+ description: 'network to listen on',
+ },
+ chain: {
+ alias: 'c',
+ type: 'string',
+ description: 'name of chain to listen on',
+ },
+ url: {
+ alias: 'u',
+ type: 'string',
+ description: 'node url',
+ },
+ contractAddress: {
+ alias: 'a',
+ type: 'string',
+ description: 'eth contract address',
+ },
+ tokenName: {
+ alias: 't',
+ type: 'string',
+ description:
+ 'Name of the token if network is erc20 and contractAddress is a erc20 token address',
+ },
+ reconnectSince: {
+ alias: 'R',
+ type: 'number',
+ description: 'Block number to query from',
+ },
+});
+
+const shortcuts = {
+ substrate: {
+ chain: 'edgeware',
+ network: SupportedNetwork.Substrate,
+ url: networkUrls.edgeware,
+ spec: networkSpecs.edgeware,
+ enricherConfig: {
+ balanceTransferThreshold: 500_000,
+ },
+ },
+ erc20: {
+ chain: 'erc20',
+ network: SupportedNetwork.ERC20,
+ url: networkUrls.erc20,
+ tokenAddresses: ['0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48'],
+ tokenNames: ['usd-coin'],
+ },
+ compound: {
+ chain: 'marlin',
+ network: SupportedNetwork.Compound,
+ url: networkUrls.marlin,
+ address: contracts.marlin,
+ },
+ aave: {
+ chain: 'dydx',
+ network: SupportedNetwork.Aave,
+ url: networkUrls.dydx,
+ address: contracts.dydx,
+ },
+ moloch: {
+ chain: 'moloch',
+ network: SupportedNetwork.Moloch,
+ url: networkUrls.moloch,
+ address: contracts.moloch,
+ },
+ commonwealth: {
+ chain: 'commonwealth',
+ network: SupportedNetwork.Commonwealth,
+ url: networkUrls['eth-local'],
+ address: contracts['commonwealth-local'],
+ },
+};
+
+async function main(): Promise {
+ let listener;
+ let sc;
+ if (argv.network && !argv.chain) sc = shortcuts[argv.network];
+ try {
+ listener = await createListener(
+ argv.chain || sc.chain || 'dummyChain',
+ argv.network || sc.network,
+ {
+ url: argv.url || sc.url || networkUrls[argv.chain],
+ address: argv.contractAddress || sc.address || contracts[argv.chain],
+ tokenAddresses: argv.contractAddress
+ ? [argv.contractAddress]
+ : sc.tokenAddresses,
+ tokenNames: argv.tokenName ? [argv.tokenName] : sc.tokenNames,
+ verbose: false,
+ spec: sc.spec || networkSpecs[argv.chain],
+ enricherConfig: sc.enricherConfig || {
+ balanceTransferThreshold: 500_000,
+ },
+ discoverReconnectRange: argv.reconnectSince
+ ? () => Promise.resolve({ startBlock: argv.reconnectSince })
+ : undefined,
+ }
+ );
+
+ listener.eventHandlers.logger = {
+ handler: new LoggingHandler(),
+ excludedEvents: [],
+ };
+
+ await listener.subscribe();
+ } catch (e) {
+ console.log(e);
+ }
+
+ return listener;
+}
+
+main().then((listener) => {
+ const temp = listener;
+ console.log('Subscribed...');
+});
diff --git a/packages/chain-events/scripts/scraper.ts b/packages/chain-events/scripts/scraper.ts
new file mode 100644
index 00000000000..e194cd6f665
--- /dev/null
+++ b/packages/chain-events/scripts/scraper.ts
@@ -0,0 +1,57 @@
+import { SubstrateEvents, SubstrateTypes } from '../dist/index';
+import { Registration } from '@polkadot/types/interfaces';
+import { Option } from '@polkadot/types';
+import { ParseType } from '../dist/substrate/filters/type_parser';
+import fs from 'fs';
+
+const args = process.argv.slice(2);
+const chain = args[0] || 'edgeware';
+console.log(`Listening to events on ${chain}.`);
+
+const networks = {
+ 'edgeware': 'ws://mainnet1.edgewa.re:9944',
+ 'edgeware-local': 'ws://localhost:9944',
+ 'edgeware-testnet': 'wss://beresheet1.edgewa.re',
+};
+
+const url = networks[chain];
+
+if (!url) throw new Error(`no url for chain ${chain}`);
+SubstrateEvents.createApi(url, {}).then(async (api) => {
+ const subscriber = new SubstrateEvents.Subscriber(api);
+ const identities = {};
+ const FINISH_BLOCK = 1000000;
+ subscriber.subscribe(async (block) => {
+ // go through events and add new identities
+ for (const { event } of block.events) {
+ const kind = ParseType(block.versionName, block.versionNumber, event.section, event.method);
+ if (kind === SubstrateTypes.EventKind.IdentitySet) {
+ // query the entire identity data
+ const who = event.data[0].toString();
+ const registrationOpt = await api.query.identity.identityOf