{"id":29177887,"url":"https://github.com/gitcoinco/matching_contracts","last_synced_at":"2025-07-01T18:09:07.045Z","repository":{"id":43681593,"uuid":"318559523","full_name":"gitcoinco/matching_contracts","owner":"gitcoinco","description":null,"archived":false,"fork":false,"pushed_at":"2022-04-15T15:11:59.000Z","size":891,"stargazers_count":16,"open_issues_count":1,"forks_count":12,"subscribers_count":10,"default_branch":"main","last_synced_at":"2024-04-15T13:43:14.101Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/gitcoinco.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2020-12-04T15:37:44.000Z","updated_at":"2023-05-19T06:34:45.000Z","dependencies_parsed_at":"2022-09-21T13:00:41.437Z","dependency_job_id":null,"html_url":"https://github.com/gitcoinco/matching_contracts","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/gitcoinco/matching_contracts","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gitcoinco%2Fmatching_contracts","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gitcoinco%2Fmatching_contracts/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gitcoinco%2Fmatching_contracts/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gitcoinco%2Fmatching_contracts/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/gitcoinco","download_url":"https://codeload.github.com/gitcoinco/matching_contracts/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gitcoinco%2Fmatching_contracts/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":263013738,"owners_count":23399815,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-07-01T18:09:01.459Z","updated_at":"2025-07-01T18:09:07.026Z","avatar_url":"https://github.com/gitcoinco.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Gitcoin Matching Contracts\n\nNon-custodial match payouts for Gitcoin Grants.\n\n- [Gitcoin Matching Contracts](#gitcoin-matching-contracts)\n  - [About](#about)\n  - [Contract Design and Security](#contract-design-and-security)\n  - [Development](#development)\n    - [Contract Setup](#contract-setup)\n    - [Python Setup](#python-setup)\n    - [Test Workflow](#test-workflow)\n  - [Acknowledgements](#acknowledgements)\n\n## About\n\n\u003cimg width=\"900\" src=\"https://p200.p0.n0.cdn.getcloudapp.com/items/12uKrDq5/Screen%20Shot%202020-12-08%20at%208.41.45%20AM.png?source=viewer\u0026v=ecde302feb2463818307271ae4e22026\" alt=\"Architecture\"\u003e\n\nThis contract allows for non-custodial Gitcoin Grants match payouts. It works as follows:\n\n1. During a matching round, deploy a new instance of this contract\n2. Once the round is complete, Gitcoin computes the final match amounts earned by each grant\n3. Over the course of multiple transactions, the contract owner will set the payout mapping\n   stored in the `payouts` variable. This maps each grant receiving address to the match amount\n   owed, in the ERC20 token\n4. Once this mapping has been set for each grant, the contract owner calls `finalize()`. This\n   sets `finalized` to `true`, and at this point the payout mapping can no longer be updated.\n5. Funders review the payout mapping, and if they approve they transfer their funds to this\n   contract. This can be done with an ordinary transfer to this contract address.\n6. Once all funds have been transferred, the contract owner calls `enablePayouts` which lets\n   grant owners withdraw their match payments\n7. Grant owners can now call `claimMatchPayout()` to have their match payout sent to their address.\n   Anyone can call this method on behalf of a grant owner, which is useful if your Gitcoin\n   grants address cannot call contract methods.\n\n### Contract Deployments\n\n- round 8\n  - rinkeby : [0xf2354570bE2fB420832Fb7Ff6ff0AE0dF80CF2c6](https://rinkeby.etherscan.io/tx/0xf2354570bE2fB420832Fb7Ff6ff0AE0dF80CF2c6)\n  - mainnet : [0xf2354570bE2fB420832Fb7Ff6ff0AE0dF80CF2c6](https://etherscan.io/address/0xf2354570bE2fB420832Fb7Ff6ff0AE0dF80CF2c6)\n\n- round 9\n  - mainnet : [0x3342e3737732d879743f2682a3953a730ae4f47c](https://etherscan.io/address/0x3342e3737732d879743f2682a3953a730ae4f47c)\n\n- round 10\n  - rinkeby : [0x8B7E04872f4e3F12e6CEb7F25BF8C74813ad3e38](https://rinkeby.etherscan.io/address/0x8B7E04872f4e3F12e6CEb7F25BF8C74813ad3e38)\n  - mainnet : [0x3ebAFfe01513164e638480404c651E885cCA0AA4](https://etherscan.io/address/0x3ebAFfe01513164e638480404c651E885cCA0AA4)\n\n- round 11\n  - rinkeby : [0x77278BB93694827f3c60Cdf0275C9C58AED0BEbE](https://rinkeby.etherscan.io/address/0x77278BB93694827f3c60Cdf0275C9C58AED0BEbE)\n  - mainnet : [0x0EbD2E2130b73107d0C45fF2E16c93E7e2e10e3a](https://etherscan.io/address/0x0EbD2E2130b73107d0C45fF2E16c93E7e2e10e3a)\n\n- round 12\n  - mainnet : [0xAB8d71d59827dcc90fEDc5DDb97f87eFfB1B1A5B](https://etherscan.io/address/0xAB8d71d59827dcc90fEDc5DDb97f87eFfB1B1A5B)\n\n- round 13\n  - mainnet : [0xF63FD0739cB68651eFbD06BCcb23F1A1623D5520](https://etherscan.io/address/0xF63FD0739cB68651eFbD06BCcb23F1A1623D5520)\n  - UNI Round : [0x0019863771b57FBA997cF6602CB2dD572A43e977](https://etherscan.io/address/0x0019863771b57FBA997cF6602CB2dD572A43e977)\n  - OHM Round : [0x868CBca73915f842A70cD9584D80a57DB5E690C1](https://etherscan.io/address/0x868CBca73915f842A70cD9584D80a57DB5E690C1)\n\n## Contract Design and Security\n\nWhen designing and developing this contract, security was the number one goal. This led to keeping\nthings as simple as possible in many places, and as a result some aspects of the design may seem\ninefficient or suboptimal. This section will explain those design decisions. To start, let's\nreview the contract flow over its lifecycle as shown below:\n\n![image](https://user-images.githubusercontent.com/17163988/102834965-42d9bf80-43aa-11eb-9072-1d318de8ef66.png)\n\nNow lets review a few of the specific design decisions in this context.\n\nThe payout mapping: Match amounts are saved in a mapping called `payouts`, and because there are about 1000 grants that receive match payouts, it takes multiple transactions and a lot of gas to set this mapping. Using a Merkle distributor may feel like the cleaner way to do it, but we intentionally decided against that here. One reason is because it's more complex, and since this contract was not formally audited we wanted to keep it simple. Another reason is because if we set the payout mapping wrong, we can easily override it with additional calls to `setPayouts` without having to generate a new merkle root.\n\nFunding: All funds to be paid out are expected to come from the [Gitcoin Grants multisig](https://etherscan.io/address/0xde21f729137c5af1b01d73af1dc21effa2b8a0d6). We take advantage of the fact that we trust this funder to keep things simple. The contract is funded with an ordinary transfer of an ERC20 token to the `MatchPayouts` contract. If the funder makes a mistake during this transfer, they have the ability to withdraw funds using the `withdrawFunding` method, which lets only the funder withdraw any tokens from the contract. Notice how there are no restrictions on when or what token the funder can withdraw—they can withdraw any amount of any token at any time! In an adversarial environment, this would be a problem. But because we trust the funder, we enable this functionality as a safeguard so funds can be withdrawn at any time in case something goes wrong.\n\nClaiming Funds: Some grants use contract wallets and it may not be easy for them to call a method allowing them to claim funds. As a result, the `claimMatchPayout` method allows anyone to withdraw on behalf of a grant, and transfers the funds to that grant's receiving address. Additionally, because there is no `msg.sender` usage, and because all match payouts are in a single ERC20 token, there is no reentrancy risk to worry about here.\n\n## Development\n\nCreate a copy of `.env.example` and fill in the environment variables, the proceed to the following sections.\nIf you don't plan on deploying the contracts, you should be able to leave these with the defaults.\n\n### Contract Setup\n\n```sh\n# Install dependencies\n$ yarn install\n\n# Compile the smart contracts with Hardhat\n$ yarn compile\n\n# Generate TypeChain artifacts\n$ yarn typechain\n\n# Lint the Solidity code\n$ yarn lint:sol\n\n# Lint the TypeScript code\n$ yarn lint:ts\n\n# Run tests\n$ yarn test\n\n# Generate the code coverage report\n$ yarn coverage\n\n# Delete the smart contract artifacts, the coverage reports and the Hardhat cache\n$ yarn clean\n```\n\n### Python Setup\n\nFor the following section you'll need python. To get setup, follow the commands below:\n\n```sh\n# Create a new virtual environment in this directory\npython3 -m venv venv\n\n# Activate the virtual environment\nsource ./venv/bin/activate\n\n# Install dependencies\npip install -r requirements.txt\n```\n\n### Test Workflow\n\nThis section explains how to deploy a local instance of the contract and run Python scripts to\ncheck the contract state. The final versions of this script live in the\n[gitcoin/web](https://github.com/gitcoinco/web/) repo, but were developed here.\n\nFirst let's deploy the contracts locally.\n\n```sh\nyarn deploy:local\n```\n\nWait a few seconds for that to complete.\n\nNow we want to set a payout mapping and verify the results before finalizing it. We can do this with\nthe below command, which will set the payout mapping and save the mapping to `outputs/payouts.json`\nso we can verify it later\n\n```sh\nyarn sim:set-payouts\n```\n\nLet's compare the total value of the payouts mapping from the events to what we'd expect from\n`payouts.json`, and let's compare that to the ERC20 balance of the contract.\n\n```sh\n# Run the python script\nyarn sim:verify-payouts\n```\n\nAs expected, the contract does not have enough ERC20 token to cover all match payouts.\n\nRun `yarn sim:fund` to simulate the funder adding ERC20 token to the contract. Now run `yarn sim:verify-payouts`\nagain and it will show the contract has sufficient funds! At this point, the owner can call\n`enablePayouts` to let grant owners withdraw their match amounts.\n\n\n\n## Deploy Steps\n- update inputs-rinkeby.js and input-mainnet.js with needed params\n- update deploy.ts with the right config for mainnet and rinkeby \n- deploy the contract\n- verify the contract using hardhat and verify on etherscan\n\n\n### Verify Source Code after Deployment\n\nThe `hardhat-etherscan` plugin is installed and can be used to verify contract source code after it has been deployed. Documentation can be found here: \u003chttps://hardhat.org/plugins/nomiclabs-hardhat-etherscan.html\u003e\n\n## Acknowledgements\n\nThis project was built with Paul Berg's [solidity-template](https://github.com/paulrberg/solidity-template).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgitcoinco%2Fmatching_contracts","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgitcoinco%2Fmatching_contracts","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgitcoinco%2Fmatching_contracts/lists"}