Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

GITC-394: polygon tx validation #9475

Merged
19 changes: 12 additions & 7 deletions app/dashboard/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -284,7 +284,7 @@ def ipfs_cat_requests(key):
return None, 500


def get_web3(network, sockets=False):
def get_web3(network, sockets=False, is_polygon=False):
chibie marked this conversation as resolved.
Show resolved Hide resolved
"""Get a Web3 session for the provided network.

Attributes:
Expand All @@ -298,8 +298,13 @@ def get_web3(network, sockets=False):
web3.main.Web3: A web3 instance for the provided network.

"""
if network in ['mainnet', 'rinkeby', 'ropsten']:
if sockets:
if network in ['mainnet', 'rinkeby', 'ropsten', 'testnet']:
if network == 'mainnet' and is_polygon:
network = 'polygon-mainnet'
elif network == 'testnet':
zlsgh marked this conversation as resolved.
Show resolved Hide resolved
network = 'polygon-mumbai'

if sockets and not is_polygon: # polygon doesn't yet have socket support in infura
if settings.INFURA_USE_V3:
provider = WebsocketProvider(f'wss://{network}.infura.io/ws/v3/{settings.INFURA_V3_PROJECT_ID}')
else:
Expand Down Expand Up @@ -925,12 +930,12 @@ def is_valid_eth_address(eth_address):
return (bool(re.match(r"^0x[a-zA-Z0-9]{40}$", eth_address)) or eth_address == "0x0")


def get_tx_status(txid, network, created_on):
status, timestamp, tx = get_tx_status_and_details(txid, network, created_on)
def get_tx_status(txid, network, created_on, is_polygon=False):
status, timestamp, tx = get_tx_status_and_details(txid, network, created_on, is_polygon=is_polygon)
return status, timestamp


def get_tx_status_and_details(txid, network, created_on):
def get_tx_status_and_details(txid, network, created_on, is_polygon=False):
from django.utils import timezone

import pytz
Expand All @@ -944,7 +949,7 @@ def get_tx_status_and_details(txid, network, created_on):
if txid == 'override':
return 'success', None #overridden by admin
try:
web3 = get_web3(network)
web3 = get_web3(network, is_polygon=is_polygon)
tx = web3.eth.getTransactionReceipt(txid)
if not tx:
drop_dead_date = created_on + timezone.timedelta(days=DROPPED_DAYS)
Expand Down
34 changes: 22 additions & 12 deletions app/economy/tx.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,7 @@
from django.conf import settings
from django.utils import timezone

import requests
from dashboard.abi import erc20_abi
from dashboard.utils import get_tx_status, get_web3
from economy.models import Token
from web3 import Web3
from web3.exceptions import BadFunctionCallOutput
Expand Down Expand Up @@ -107,39 +105,51 @@ def parse_token_amount(token_symbol, amount, network):
parsed_amount = int(amount * 10 ** decimals)
return parsed_amount

def check_for_replaced_tx(tx_hash, network):
def check_for_replaced_tx(tx_hash, network, datetime=None, is_polygon=False):
"""
Get status of the provided transaction hash, and look for a replacement transaction hash. If a
replacement exists, return the status and hash of the new transaction
"""
status, timestamp = get_tx_status(tx_hash, network, timezone.now())
from dashboard.utils import get_tx_status

if not datetime:
datetime = timezone.now()

status, timestamp = get_tx_status(tx_hash, network, datetime, is_polygon=is_polygon)
if status in ['pending', 'dropped', 'unknown', '']:
new_tx = getReplacedTX(tx_hash)
if new_tx:
tx_hash = new_tx
status, timestamp = get_tx_status(tx_hash, network, timezone.now())
status, timestamp = get_tx_status(tx_hash, network, datetime)

return tx_hash, status, timestamp


def grants_transaction_validator(contribution, w3):
def grants_transaction_validator(contribution, w3, is_polygon=False):
"""
This function is used to validate contributions sent on L1 through the BulkCheckout contract.
This function is used to validate contributions sent on L1 & Polygon L2 through the BulkCheckout contract.
This contract can be found here:
- On GitHub: https://github.com/gitcoinco/BulkTransactions/blob/master/contracts/BulkCheckout.sol
- On mainnet: https://etherscan.io/address/0x7d655c57f71464b6f83811c55d84009cd9f5221c
- On Polygon mainnet: https://polygonscan.com/address/0xb99080b9407436eBb2b8Fe56D45fFA47E9bb8877
- On Polygon testnet: https://mumbai.polygonscan.com/address/0x3E2849E2A489C8fE47F52847c42aF2E8A82B9973

To facilitate testing on Rinkeby, we pass in a web3 instance instead of using the mainnet
To facilitate testing on Rinkeby and Mumbai, we pass in a web3 instance instead of using the mainnet
instance defined at the top of this file
"""

# Get bulk checkout contract instance
bulk_checkout_contract = w3.eth.contract(address=bulk_checkout_address, abi=bulk_checkout_abi)

# Get specific info about this contribution that we use later
tx_hash = contribution.split_tx_id
network = contribution.subscription.network

if network == 'mainnet' and is_polygon:
bulk_checkout_address = '0xb99080b9407436eBb2b8Fe56D45fFA47E9bb8877'
elif network == 'testnet':
bulk_checkout_address = '0x3E2849E2A489C8fE47F52847c42aF2E8A82B9973'

# Get bulk checkout contract instance
bulk_checkout_contract = w3.eth.contract(address=bulk_checkout_address, abi=bulk_checkout_abi)

# Response that calling function uses to set fields on Contribution. Set the defaults here
response = {
# We set `passed` to `True` if matching transfer is found for this contribution. The
Expand Down Expand Up @@ -168,7 +178,7 @@ def grants_transaction_validator(contribution, w3):
return response

# Check for dropped and replaced txn
tx_hash, status, timestamp = check_for_replaced_tx(tx_hash, network)
tx_hash, status, _ = check_for_replaced_tx(tx_hash, network, is_polygon=is_polygon)

# If transaction was successful, continue to validate it
if status == 'success':
Expand Down
28 changes: 15 additions & 13 deletions app/grants/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
import requests
from django_extensions.db.fields import AutoSlugField
from economy.models import SuperModel
from economy.tx import check_for_replaced_tx
from economy.utils import ConversionRateNotFoundError, convert_amount
from gas.utils import eth_usd_conv_rate, recommend_min_gas_price_to_confirm_in_time
from grants.utils import generate_collection_thumbnail, get_upload_filename, is_grant_team_member
Expand Down Expand Up @@ -1681,6 +1682,7 @@ class Contribution(SuperModel):
CHECKOUT_TYPES = [
('eth_std', 'eth_std'),
('eth_zksync', 'eth_zksync'),
('eth_polygon', 'eth_polygon'),
('zcash_std', 'zcash_std'),
('celo_std', 'celo_std'),
('zil_std', 'zil_std'),
Expand Down Expand Up @@ -1767,6 +1769,9 @@ def blockexplorer_url_txid(self):
def blockexplorer_url_helper(self, tx_id):
if self.checkout_type == 'eth_zksync':
return f'https://zkscan.io/explorer/transactions/{tx_id.replace("sync-tx:", "")}'
if self.checkout_type == 'eth_polygon':
network_sub = f"mumbai." if self.subscription and self.subscription.network != 'mainnet' else ''
return f'https://{network_sub}polygonscan.com/tx/{tx_id}'
if self.checkout_type == 'eth_std':
network_sub = f"{self.subscription.network}." if self.subscription and self.subscription.network != 'mainnet' else ''
return f'https://{network_sub}etherscan.io/tx/{tx_id}'
Expand Down Expand Up @@ -1808,17 +1813,16 @@ def leave_gitcoinbot_comment_for_status(self, status):
"comment":comment,
"is_edited":True,
}
);
)
except Exception as e:
print(e)


def update_tx_status(self):
"""Updates tx status for Ethereum contributions."""
try:
from dashboard.utils import get_web3
from economy.tx import grants_transaction_validator
from dashboard.utils import get_tx_status
from economy.tx import getReplacedTX

# If `tx_override` is True, we don't run the validator for this contribution
if self.tx_override:
Expand Down Expand Up @@ -1849,20 +1853,18 @@ def update_tx_status(self):
self.tx_cleared = True
self.validator_comment = "zkSync checkout. Success" if self.success else f"zkSync Checkout. {tx_data['fail_reason']}"

elif self.checkout_type == 'eth_std':
# Standard L1 checkout using the BulkCheckout contract
elif self.checkout_type == 'eth_std' or self.checkout_type == 'eth_polygon':
# Standard L1 and Polygon L2 checkout using the BulkCheckout contract

is_polygon = True if self.checkout_type == 'eth_polygon' else False

# Prepare web3 provider
PROVIDER = "wss://" + network + ".infura.io/ws/v3/" + settings.INFURA_V3_PROJECT_ID
w3 = Web3(Web3.WebsocketProvider(PROVIDER))
w3 = get_web3(network, is_polygon=is_polygon)

# Handle dropped/replaced transactions
split_tx_status, _ = get_tx_status(self.split_tx_id, self.subscription.network, self.created_on)
if split_tx_status in ['pending', 'dropped', 'unknown', '']:
new_tx = getReplacedTX(self.split_tx_id)
if new_tx:
self.split_tx_id = new_tx
split_tx_status, _ = get_tx_status(self.split_tx_id, self.subscription.network, self.created_on)
_, split_tx_status, _ = check_for_replaced_tx(
self.split_tx_id, network, self.created_on, is_polygon=is_polygon
)

# Handle pending txns
if split_tx_status in ['pending']:
Expand Down