dcgtc / dgrants Goto Github PK
View Code? Open in Web Editor NEWDecentralized Grant platform that implements quadratic funding.
License: GNU Affero General Public License v3.0
Decentralized Grant platform that implements quadratic funding.
License: GNU Affero General Public License v3.0
The current swapAndDonate
method supports:
We want to update this to support:
To do this efficiently, it probably makes sense to first execute a single swap to donationToken
, then transfer donationToken
s to N Grant payee addresses. The assumption here is that 1 swap and N transfers is going to be less expensive than N swaps.
The caller should provide a proportion for each grant receiving a donation. The proportions should be applied to the total donationToken
received after the swap.
This issue is a minimal spec for a smart contract implementing a single matching grant round.
owner
with special internal privileges (see below)startTime
timestamp that must in the future at the time of deploymentendTime
timestamp that must be after the startTimedonationToken
address that is assumed to be an ERC20 implementationregistry
address that refers to a GrantsRegistry instancemetaPtr
variable that is set at deploymentminContribution
variable that is set at deployment, representing the minimum matching contribution that can be made to a grant during the roundhasPaidOut
boolean variable that defaults to false, and toggles only when the owner calls the payout method (see below)endTime
, (including before the startTime
), the Round can accept matching pool donations in donationToken
via a special method. Internally, the method should use transferFrom
to take custody of the funds and emit an event.grantId
as a parameter, and verify that the Grant exists in the GrantRegistryamount
is greater than or equal to the minContribution
amounttransferFrom
to send the funds the Grant's payee
addressowner
should be able to transfer all matching pool funds to a payoutAddress
via a special method call. Only the owner should be able to call this method, and only after the endTime
has passed.Open Questions:
Notes:
We need to choose a license for the project. We would like to allow usage of the project and codebase by others but require that changes be made available to the public so that everyone can benefit from building on top of our work. We would also like to make sure that we reserve the right to change or update the license.
Currently, a GrantRound
has one donationToken
, which is used for both the matching pool contributions and individual grant contributions. We want to separate these such that the entity who creates the GrantRound
can specify a different token used for each case:
donationToken
for individual contributions
matchingToken
for the matching pool contributions
Both should be provided in the GrantRound
constructor. The GrantRoundManager
should continue to hold the donationToken
fixed for all Grants deployed through it, but should allow the matchingToken
to be passed as a parameter.
This PR is dependent on #61.
The in-progress implementation of GrantRound
defines a single token— selected by the Round owner at deployment— which is used for all contributions, including matching pool contributions, grant contributions, and the subsequent payout of matching contributions after the round is over.
For at least the case of grant contributions— and possibly for matching pool contributions as well— we want to support users being able to give in the token of their choice. There are at least two broad approaches for doing so.
One approach would be to allow the user to specify the token they're donating at the time of a contribution in the GrantRound
itself, and accept the token donation regardless of this address.
Another approach would be to retain a single accounting token for each GrantRound
, and instead swap the token for the accounting token before paying it to the Grant or adding it to the matching Pool. This could be done internally on the GrantRound
, using an external wrapper contract, or using a more advanced approach like a delegatecall
to a swapping strategy contract defined at deploy time (in order to allow alternative or upgradeable swapping strategies in future Rounds).
This issue is for discussing and weighing the tradeoffs involved in each approach before making a decision on how to implement it.
Currently it can be a bit jumpy of a UX when automatically connecting to your last used wallet
Right now on GrantRound
once the round end there is a method payoutGrants
which the GrantRound
owner invokes to transfer funds to another address
https://github.com/dcgtc/dgrants/blob/main/contracts/contracts/GrantRound.sol#L103-L107
How does this flow work post that?
This is my understanding as it stands.
MerkleDistributor
contract with :
approval_address
(currently gitcoin but later on DAO ) to validate and approve the distributionpayoutGrants
and transfer funds to the approved MerkleDistributor
contractMerkleDistributor
does a bulk transfer and transfer on the contractQ:
MerkleDistributor
contract, can this not be stored on the GrantRound contract ? (Are we avoiding it cause it would be expensive ? -> we are already storing the list of grants)The revert cases in the GrantRound
constructor should be explicitly tested.
Context from @phutchins in #2 (comment):
We may opt to replace Nightwind and Tailwind but we can leave them for now.
Our designer @scco is big on simple design without frameworks. If we decided not to use it, it may be more of a completely remove type of thing than a replace with something else so the effort might not be as bad as you think. Our direction right now is to build just enough UI to test/try the platform that we're building so we shouldn't spend time just yet worrying about making it pretty so if we were to wipe the ui in a few weeks and start over it shouldn't mater. I'm currently in between as I have not used Tailwind before and would like to give it a shot. I also would like for it to be relatively easy for people that are not our designer to make additions/changes so a framework might be useful. On the other hand they can make things more complex. I think we can decide this over the next week or two as @scco will be devoting more time to this project a few days from now.
A limitation that results from this is you can't donate WETH and ETH in the same transaction, which I think is an edge case that's ok if we don't support. Wonder if Uniswap V3 router would let you multicall an ETH->DAI and WETH->DAI swap in the same transaction. My guess it supports that via multicall where you deposit ETH->WETH in the router as the first call, then execute the swap as the next call.
-- @mds1
As described in #40, every contribution requires, at a minimum:
GrantRegistry
(2600 gas the first time, 100 gas subsequent times)payee
address fo the specified grant ID (2100 gas each time)This adds up quite a bit, and is very inefficient. To contribute to 100 grants at once, you're looking at (2600 + 100 * 99) + (2100 * 100) = 222,500 gas
, or about $16 at current prices just to read all payee
addresses. This is not counting token transfers, swaps, and other contract calls made as part of the donation transaction, and checkouts will likely become prohibitively expensive for many users.
I'd like to propose an alternate approach, which instead use the the payee
address itself as the grant's ID. There's two initial problems with this idea:
payee
address for multiple grantspayee
to block you from creating a grantThe solution proposed here is as follows:
id
owner
, and only the owner
of that contract can withdraw the funds (the owner can be anything, e.g. an EOA, a multisig, a timelock, a DAO, etc.)payee
address, which saves gasThis makes creating grants more expensive for grant owners, but makes contributions cheaper for donors
Maker's DSProxy and Gnosis Safe have both been around for a long time and are sufficiently battle-tested that they can be used as a drop-in solution for the contract wallets we need to deploy. This gives us 3 options for using the grant's payee
address as it's ID (using current gas prices for dollar amounts):
We don't need to pick one of those 3 options in this issue—this issue is focused on whether or not we should make this change—and if so we can finalize implementation as part of a separate issue
Here are the pros/cons of the two approaches that I can currently think of:
Using an explicit ID and payee
address as we currently do:
Using a contract wallet as the grantId
, where the wallet's owner can be whatever the user wants:
"Worst case" estimated cost savings of contract wallet:
222,5000 / 100 = 2,225
gas per contribution600,000 / 2,225 = 270
contributionsEven though the proposed idea has two X's and the current approach has 1, I think that:
Currently #17, uses Waffle Mock ERC20 framework to set return values for IERC20
functions called in the GrantRound
contract. Instead we'd like to introspect into the state of token transfers. This is to ensure that the methods implemented successfully send the tokens and balances are updated correctly; along with handling common revert edge cases.
Running yarn build
from the app folder fails with various TS errors.
yarn dev
yarn lint
yarn vite build
succeedsvue-tsc --noEmit
check in yarn build
@wildmolasses Curious to hear your inputs on the best way to handle this so those TS errors surface sooner, whether that's in the VSCode UI, the dev console, or during linting
The clr calculation would be done offline at the end of the round.
To allow dApp to calculate this. ducurve
is being built as node package and will exist as a subdirectory.
It will expose functions that will be invoked by dApp during / the end of the round. and the response will either return a single GrantsDistribution
or an array of GrantsDistribution
When will this package be used
during a round to show users
at the end of the round
The package allows for flexibility
All code is maintained within the dcurve repo (this will change as we'll have to account for diff hash / clr algo ).
Will update this structure
/src
/internal/fetch.ts # exports functions to fetch information about GrantRound and Contributions
/internal/main.ts # exports the core logic (will be supplied with a calc & hash command on construct)
/internal/calc/* # exports command pattern implementations of the clr algorithms
/internal/hash/* # exports command pattern implementations of the hashing functions
/types.ts # exports the types used in the project
/index.ts # exports the modules outlined above
/tests # tests are written here
/dist # packaged src which will be published to npm
You can check out the structure here: https://github.com/dcgtc/dgrants/tree/dcurve/dcurve
These functions would live inside /src/internal/main.ts
.
calculateCurrentDistribution
- calculates the distribution + hash based on contributions
calculatePredictedDistribution
- how the curve would vary if a grant were to get a contribution worth X tokens.
calculatePredictedDistributions
- same as calculatePredictedDistribution but returns an array of GrantsDistribution
as if you have 5 grants -> generates 5 distribution where each distribution one grant gets a contribution worth X tokens.
This would be a slightly expensive operation and would increase in time as number of grants increase. Useful to show data on the explorer page.
The response of any of these methods will be a distribution.
The actual struct can be found here
https://github.com/dcgtc/dgrants/blob/dcurve/dcurve/src/types.ts#L38
Making the GrantRoundManager
inherit from SwapRouter
(#74) would remove approvals and transfers that are currently needed.
Inheriting from SwapRouter means we'd become a custom v3 router, and users would have to trust (or verify by inspected verified source code) that we did not modify the router functionality in some malicious or unsafe way.
While running a dGrants round.
The dcurve
package should be able to fetch the trust bonus score of the contributor's address ( hosted on gitcoin ) while determining the distribution. Until DID
are generated and are issues NFT
/stored on-chain, a temporary solution here would be integrating an API
linear.ts
would we'd add the trust bonus scoreThis issue defines the routes and baseline functionality for the prototype UI's interaction with the GrantRegistry
contract implmented in #7
/dgrants
/dgrants/:id
where :id
is the integer ID of the Grant in the GrantRegistry/dgrants/new
Note: in the long term we will (obviously) not show the metaPtr bytes, but rather load, parse, and display the metadata from the storage location. This is a temporary requirement to demonstrate interaction with the Registry is working properly.
Picks up where #94 left off, and is dependent on a PR for that being merged
Currently, the GrantRound
contract is architected such that contributions are made to a Grant through the Round. An alternative, and perhaps superior approach, would be to have users make contributions through a ContributionRouter
contract.
Such a Router would:
Advantages to this approach include:
approve
a contract that can do the appropriate transferFrom
Possible downsides:
Other questions:
GrantRegistry
directly, as opposed to in a separate Router contract? What are the tradeoffs for each approach?GrantRound
before making the contribution, how would the Router approach impact this? Could/should swapping be implemented in the Router, or in another contract wrapping the Router?/dgrants/rounds/:address
where :address
is the hex address of a GrantRound
contract instanceaddMatchingFunds
method call txshasPaidOut
, show the contract address (and transaction hash?) where the payout was made to. Link to Etherescan./dgrants/rounds/factory/:address
where :address
is the hex address of a GrantRoundFactory
contract instance/dgrants/rounds
(updated with assumption that the Factory is a singleton)Additional notes/questions:
addMatchingFunds
method? Should we have a minimum matching pool contribution threshold?The following should be verified in the constructor. Recommend waiting for #21 to be completed first since there will likely be some merge conflicts, as the tests should be probably refactored here to use Waffle fixtures to simplify snapshots/reverts for going forwards/backwards in time
require(_donationToken.totalSupply() > 0, "GrantRound: Invalid token");
require(_startTime >= block.timestamp, "GrantRound: Start time has already passed");
require(_endTime > _startTime, "GrantRound: End time must be after start time");
require(_registry.grantCount() >= 0, "GrantRound: Invalid registry"); // verify this call doesn't revert
Also update existing test suite to use shared types, instead of inlined type or types in utils.ts
This is now needed because we fork mainnet for testing as of #61
Part of this issue should be investigating the best way to do this, such as:
main
?Context: #75
This issue is a spec for a proof of concept/prototype implementation of storing Metadata on IPFS via Fleek. It's meant to demonstrate the end-to-end functionality, without including all details, such as the full Grant Schema, the ability to edit, or viewing past edits of the metadata. All such functionality can be added in future tasks once this proof of concept is built out.
/dgrants/new
form to remove the Metadata URL field, and replace it with two new fields: Name & DescriptionGrantRound
via transaction
QmXnnyufdzAWL5CqZ2RnSNgPbvCc1ALT73s6epPrRnZ1Xy
, should the metaPtr be simply "QmXnnyufdzAWL5CqZ2RnSNgPbvCc1ALT73s6epPrRnZ1Xy"
or should it be something like "https://cloudflare-ipfs.com/ipfs/QmXnnyufdzAWL5CqZ2RnSNgPbvCc1ALT73s6epPrRnZ1Xy"
Update various places where Grant Metadata is displayed to actually load the data and show, specifically:
/dgrants
to show the Grant Name & (possibly shortened) Description in each Grant Card; remove the Grant ID and mtaptr URL/dgrants/:id
to show Grant Name & Description, remove metadata URL and Grant ID{
"name": "My Grant Name",
"description": "This is my Grant. There are many like it, but this one is mine."
}
import { create } from 'ipfs-http-client'
const ipfs = create({
url: "https://ipfs-api.dev.fleek.cool",
headers: {
Authorization: "v2 <apiKey>", // or Bearer <JWT> or public <AppKey>
},
});
We could use a style guide to give clear direction on what we expect in our codebase. I imagine that this could be pulled from the Gitcoin style guide.
[
{
grantId: 5,
contributionTokenAddress: "0x6b175474e89094c44da98b954eedeac495271d0f",
contributionAmount: "5000000000000000000",
},
{
grantId: 7,
contributionTokenAddress: "0xde30da39c46104798bb5aa3fe8b9e0e1f348163f",
contributionAmount: "1337000000000000000",
},
]
The dGrants system needs to a way to obtain sybil scores (a score based on risk for a donor with the intent of verifying that they are a unique individual) and utilize them when calculating quadratic matching for Grant Round payouts.
Separately we are driving an initiative to standardize and decentralize sybil score aggregation and exposure.
Grants Round 11 Scope
Gitcoin Holdings currently is an aggregator of sybil risk indicators and captures that data in a closed database. An initiative exists to export the sybil score aggregated by Gitcoin as a DID (Decentralized ID). In the short term (by Grants Round 11) the best approach may be to utilize this DID which would be attached to a users ETH account which they used to make their donation. This would make it such that a user could make a donation then subsequently verify their identity through the Gitcoin trust bonus process as long as they did this prior to the payout calculation event.
I imagine the blob of data that we would consume from this DID could look something like the following:
Lets discuss what this could look like and what we need in order a solution like this to work.
This was the subhraph i was meesing around with for the GrantRound and my assumption was these fields would be available on the contract.
enum GrantRoundState (
Setup "This is default state"
Active "This is when it's got all the funds and has started"
Pending "This is pending payment state"
Closed "Round is done"
)
type GrantRound @entity {
id: ID! "hash"
owner: Bytes!
name: String!
state: GrantRoundState!
amount: BigInt!
metadata: ID!
grants: [Grant!]! "do we store list of grant ID or pointer here"
startDate: String!
endDate: String!
}
Currently, tests use timeTravel
and setNextBlockTimestamp
to modify blocks. Instead waffle fixtures could be used to snapshot and revert changes made to the chain before each test
Once a GrantRound has ended, the payoutAdmin
would invoke payoutGrants to transfer funds into the GrantRoundPayout
contract.
Global variables
mapping(address => uint256) public payouts;
stores the address to amountFunctions
setPayout
to be able to upload the distribution of type PayoutFields
(this function should be callable multiple times if incase all payouts can't be set in 1 transaction )payoutGrants
which
payouts
=< amount in contractmapping(address => uint256) public payouts;
struct PayoutFields {
address recipient; // grant receiving address
uint256 amount; // match amount for that grant
}
We need to add a contributors guide to enable future contributions.
Currently, in the GrantRound
contract, there is one privileged role, called owner
, that has one specific right: calling payoutGrants
to move matching funds to the payout address. The new system should have:
payoutApprover
can call payoutGrants
metaUpdater
can call a new, setMetaPtr
method, which updated the metaPtr
methodNote: Open to new naming schemes.
For example, do we want something like this:
addMatchingFunds
to add matching funds to a roundTBD what to do with tokens transferred directly to the round
This issue is a spec for a proof of concept/prototype implementation of storing Metadata on IPFS via IPNS via the Fleek API. It's meant to demonstrate the end-to-end functionality, without including all details, such as the full Grant Schema or viewing past edits of the metadata.
/dgrants/new
form to remove the Metadata URL field, and replace it with two new fields: Name & Description/key/gen
Fleek endpoint (TBD: What should the name be?)/name/publish
/dgrants
to show the Grant Name & (possibly shortened) Description in each Grant Card; remove the Grant ID and mtaptr URL/dgrants/:id
to show Grant Name & Description, remove metadata URL and Grant ID{
"name": "My Grant Name",
"description": "This is my Grant. There are many like it, but this one is mine."
}
Open Questions:
In our .env files, the VITA_INFURA_API_KEY variable could be renamed to VITA_INFURA_PROJECT_ID (or VITA_INFURA_ID) to match the way that the value is named on Infura. For consistency, the INFURA_ID variable could be renamed to INFURA_PROJECT_ID (or left alone if we use VITA_INFURA_ID).
Stack trace in console below. I have not yet tried connecting a Gnosis Safe so unsure if the module works:
Uncaught (in promise) TypeError: _context.t2 is not a constructor
at _callee$ (gnosis-19b90e57.js:39)
at tryCatch (runtime.js:63)
at Generator.invoke [as _invoke] (runtime.js:293)
at Generator.next (runtime.js:118)
at asyncGeneratorStep (gnosis-19b90e57.js:7)
at _next (gnosis-19b90e57.js:9)
_callee$ @ gnosis-19b90e57.js:39
tryCatch @ runtime.js:63
invoke @ runtime.js:293
(anonymous) @ runtime.js:118
asyncGeneratorStep @ gnosis-19b90e57.js:7
_next @ gnosis-19b90e57.js:9
index.ts:213 Uncaught (in promise) Error: call revert exception (method="getAllGrants()", errorArgs=null, errorName=null, errorSignature=null, reason=null, code=CALL_EXCEPTION, version=abi/5.4.0)
Currently configured to ES2018, but would be nice to move to ES2020 for bigint support. Need to research browser support for ES2020 and decide if it's sufficient before going this route.
GrantRoundFactory
to GrantRoundManager
donationToken
to the Manager contract that is defined at deployment_donationToken
from createGrantRound
, instead using the immutable donationToken
when creating a Roundregistry
, an instance of GrantRegistry
to the Manager contract that is defined at deploymentrouter
, an instance of the Uniswap v3 Router contract that is defined at deploymentDonation
struct as follows:// Define inputs required for each contribution in a struct
struct Donation {
uint96 grantId; // grantId
uint256 amount; // amount of that token
address[] swapPath; // Uniswap router swap path
address[] rounds; // rounds they want the contribution to count for
}
swapAndDonate
, which:
Donation
struct as a parametergrantId
is validround
, validating it:
donationToken
defined by this ManagerswapPath[0]
defined in Donation
is the same as the Manager's donationToken
, uses transferFrom
to move the token to the Grant's payee
addresstransferFrom
to move amount
of the token swapPath[0]
to the Managerrouter
and swapPath
to swap swapPath[0]
for donationToken
transfer
to send the donationToken.balanceOf(this)
to the Grant's payee
addressgrantId
, swapPath[0]
token, amount
, and rounds
includeddonateToGrant
from the GrantRound
contract@scco to generate list of un-styled UI components that need to be implemented so that he can style them
While working on #14 I noticed there are no logout buttons. Minor issue but it seems like a nice to have.
Current implementation requires an existing pool for the pair. Determine how to do arbitrary "paths" between tokens for Uniswap v3. Implement this in current contracts, and/or alongside the bulk checkout functionality.
does yarn dev
expose a RPC url?
if so what is it?
looks like all dgrants are posted to a local RPC, but i dont see an address in the terminal when i run yarn app dev
, and i dont see in the docs how to run this
We would like to test dGrants on Avalanche and then contribute to the codebase as we test and find any issues.
Currently the dApp relies on Infura which only serves Ethereum.
We would like to add support for Avalanche so that our developers can test locally.
What would be the best approach to do this? I looked for contribution guideline, but couldn't find anything. Should we fork the code and then build those changes in a new repo or should we add multiple L1 support into this repo and submit PR's?
Set up GitHub Actions for CI. The full monorepo test suite should run. Preferably, the tests would run when a PR was opened and/or udpated.
This issue is dependent on #17
We will use the factory pattern to allow users to create new GrantRound
instance. The factory should:
GrantRound
and deploy one via a public method callGrantRounds
contract and testsQuestions:
GrantRound
constructor do any sanity checking of the address provided as the ERC20 token? For example, calling the symbol
method, which would cause revert if the address passed was not an ERC20 implementation.Once a GrantRound is complete, we calculate the distraction using the CLR pairwise matching algorithm
This lists out what each grant would get based on the
This calculation is also done during the round to let folks know
Personally, I'm not convinced that CLR calculation should be done onchain -> cause it's expensive.
But irrespective of which route we take we'd want a script that would for V1
DonationSent
event. Check hereAt the end of it , the dApp would have function which it would invoke to calculate this curve which returns the
Once this script is ready, the next steps would be deciding where to store this hash and the actual distribution curve once a round ends
cc @gdixon
Context here: #7 (comment)
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.