Skip to content

Commit

Permalink
polygon finale (#9464)
Browse files Browse the repository at this point in the history
* begin

* bella ciao

* remove staff flag

* update

* review fixes @mds1

* restore staff flag

* make it cleaner

* GITC-394: polygon tx validation (#9475)

* update admin + checkout type

* update get_web3

* handle polygon tx validation

* fix travis

* check fix

* get_tx_status fix import

* bug fix get_web3

* pass polygon boolean

* set infura id env for polygon

* check fix

* try maticvigil

* revert to infura

* use chain over is_polygon

* refac
  • Loading branch information
chibie authored Sep 22, 2021
1 parent c09a3f2 commit ed8c62c
Show file tree
Hide file tree
Showing 7 changed files with 176 additions and 95 deletions.
125 changes: 101 additions & 24 deletions app/assets/v2/js/cart-ethereum-polygon.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,3 @@
const bulkCheckoutAddressPolygon = appCart.$refs.cart.network === 'mainnet'
? '0xb99080b9407436eBb2b8Fe56D45fFA47E9bb8877'
: '0x3E2849E2A489C8fE47F52847c42aF2E8A82B9973';

function objectMap(object, mapFn) {
return Object.keys(object).reduce(function(result, key) {
result[key] = mapFn(object[key]);
Expand Down Expand Up @@ -138,6 +134,12 @@ Vue.component('grantsCartEthereumPolygon', {
this.polygon.checkoutStatus = 'depositing';
},

getBulkCheckoutAddress() {
return appCart.$refs.cart.network === 'mainnet'
? '0xb99080b9407436eBb2b8Fe56D45fFA47E9bb8877'
: '0x3E2849E2A489C8fE47F52847c42aF2E8A82B9973';
},

handleError(e) {
appCart.$refs.cart.handleError(e);
},
Expand Down Expand Up @@ -238,6 +240,8 @@ Vue.component('grantsCartEthereumPolygon', {
}
} else if (switchError.code === 4001) {
throw new Error('Please connect MetaMask to Polygon network.');
} else if (switchError.code === -32002) {
throw new Error('Please respond to a pending MetaMask request.');
} else {
console.error(switchError);
}
Expand All @@ -246,6 +250,8 @@ Vue.component('grantsCartEthereumPolygon', {

// Send a batch transfer based on donation inputs
async checkoutWithPolygon() {
const bulkCheckoutAddressPolygon = this.getBulkCheckoutAddress();

try {

if (typeof ga !== 'undefined') {
Expand Down Expand Up @@ -311,6 +317,8 @@ Vue.component('grantsCartEthereumPolygon', {
},

async sendDonationTx(userAddress) {
const bulkCheckoutAddressPolygon = this.getBulkCheckoutAddress();

// Get our donation inputs
const bulkTransaction = new web3.eth.Contract(bulkCheckoutAbi, bulkCheckoutAddressPolygon);
const donationInputsFiltered = this.getDonationInputs();
Expand All @@ -334,11 +342,78 @@ Vue.component('grantsCartEthereumPolygon', {

// Estimates the total gas cost of a polygon checkout and sends it to cart.js
async estimateGasCost() {
// The below heuristics are used instead of `estimateGas()` so we can send the donation
// transaction before the approval txs are confirmed, because if the approval txs
// are not confirmed then estimateGas will fail.
/**
* The below heuristics are used instead of `estimateGas()` so we can send the donation
* transaction before the approval txs are confirmed, because if the approval txs
* are not confirmed then estimateGas will fail.
*/

let networkId = appCart.$refs.cart.networkId;

if (networkId !== '80001' && networkId !== '137' && appCart.$refs.cart.chainId !== '1' || this.cart.unsupportedTokens.length > 0) {
return;
}

return 70000;
let gasLimit = 0;

// If user has enough balance within Polygon, cost equals the minimum amount
let { isBalanceSufficient, requiredAmounts } = await this.hasEnoughBalanceInPolygon();

if (!isBalanceSufficient) {
// If we're here, user needs at least one L1 deposit, so let's calculate the total cost
requiredAmounts = objectMap(requiredAmounts, value => {
if (value.isBalanceSufficient == false) {
return value.amount;
}
});

for (const tokenSymbol in requiredAmounts) {
/**
* The below estimates were got by analyzing gas usages for deposit transactions
* on the RootChainManagerProxy contract. View the link below,
* https://goerli.etherscan.io/address/0xbbd7cbfa79faee899eaf900f13c9065bf03b1a74
*/
if (tokenSymbol === 'ETH') {
gasLimit += 94659; // add ~94.66k gas for ETH deposits
} else {
gasLimit += 103000; // add 103k gas for token deposits
}
}
}

// If we have a cart where all donations are in Dai, we use a linear regression to
// estimate gas costs based on real checkout transaction data, and add a 50% margin
const donationCurrencies = this.donationInputs.map(donation => donation.token);
const daiAddress = this.getTokenByName('DAI')?.addr;
const isAllDai = donationCurrencies.every((addr) => addr === daiAddress);

if (isAllDai) {
if (donationCurrencies.length === 1) {
// Special case since we overestimate here otherwise
return gasLimit + 65000;
}
// The Below curve found by running script with the repo https://github.com/mds1/Gitcoin-Checkout-Gas-Analysis.
// View the chart here -> https://chart-studio.plotly.com/~chibie/1/
return gasLimit + 10000 * donationCurrencies.length + 80000;
}

/**
* Otherwise, based on contract tests, we use the more conservative heuristic below to get
* a gas estimate. The estimates used here are based on testing the cost of a single
* donation (i.e. one item in the cart). Because gas prices go down with batched
* transactions, whereas this assumes they're constant, this gives us a conservative estimate
*/
gasLimit += this.donationInputs.reduce((accumulator, currentValue) => {
// const tokenAddr = currentValue.token?.toLowerCase();

if (currentValue.token === MATIC_ADDRESS) {
return accumulator + 25000; // MATIC donation gas estimate
}

return accumulator + 70000; // generic token donation gas estimate
}, 0);

return gasLimit;
},

// Returns true if user has enough balance within Polygon to avoid L1 deposit, false otherwise
Expand Down Expand Up @@ -383,22 +458,24 @@ Vue.component('grantsCartEthereumPolygon', {
}

// Check if user has enough MATIC to cover gas costs
const gasFeeInWei = web3.utils.toWei(
(this.polygon.estimatedGasCost * 2).toString(), 'gwei' // using 2 gwei as gas price
);

if (userMaticBalance.lt(gasFeeInWei)) {
let requiredAmount = parseFloat(Number(
web3.utils.fromWei((gasFeeInWei - userMaticBalance).toString(), 'ether')
).toFixed(5));

if (requiredAmounts['MATIC']) {
requiredAmounts['MATIC'].amount += requiredAmount;
} else {
requiredAmounts['MATIC'] = {
amount: requiredAmount,
isBalanceSufficient: false
};
if (this.polygon.estimatedGasCost) {
const gasFeeInWei = web3.utils.toWei(
(this.polygon.estimatedGasCost * 2).toString(), 'gwei' // using 2 gwei as gas price
);

if (userMaticBalance.lt(gasFeeInWei)) {
let requiredAmount = parseFloat(Number(
web3.utils.fromWei((gasFeeInWei - userMaticBalance).toString(), 'ether')
).toFixed(5));

if (requiredAmounts['MATIC']) {
requiredAmounts['MATIC'].amount += requiredAmount;
} else {
requiredAmounts['MATIC'] = {
amount: requiredAmount,
isBalanceSufficient: false
};
}
}
}

Expand Down
40 changes: 10 additions & 30 deletions app/assets/v2/js/cart.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,7 @@ let appCart;

document.addEventListener('dataWalletReady', async function(e) {
appCart.$refs['cart'].network = networkName;
appCart.$refs['cart'].sourceNetwork = networkName;
appCart.$refs['cart'].networkId = String(Number(web3.eth.currentProvider.chainId));
if (appCart.$refs.cart.autoSwitchNetwork) {
try {
await ethereum.request({
method: 'wallet_switchEthereumChain',
params: [{ chainId: networkName == 'mainnet' ? '0x1' : '0x4' }]
}); // mainnet or rinkeby
appCart.$refs.cart.autoSwitchNetwork = false;
} catch (e) {
console.log(e);
}
}
}, false);

// needWalletConnection();
Expand Down Expand Up @@ -57,12 +45,10 @@ Vue.component('grants-cart', {
{ text: 'Wallet address', value: 'address' },
{ text: 'Transaction Hash', value: 'txid' }
],
autoSwitchNetwork: true,
checkoutRecommendationIsCompleted: false,
standardCheckoutInitiated: false,
chainId: '',
networkId: '',
network: 'mainnet',
sourceNetwork: 'mainnet',
tabSelected: 'ETH',
tabIndex: null,
currentTokens: [], // list of all available tokens
Expand Down Expand Up @@ -392,24 +378,19 @@ Vue.component('grants-cart', {
const estimateZkSync = Number(this.zkSyncEstimatedGasCost); // zkSync gas cost estimate
const estimatePolygon = Number(this.polygonEstimatedGasCost); // polygon gas cost estimate

const exit = (recommendation) => {
this.checkoutRecommendationIsCompleted = true;
return recommendation;
};

const compareWithL2 = (estimateL2, name) => {
if (estimateL1 < estimateL2) {
const savingsInGas = estimateL2 - estimateL1;
const savingsInPercent = Math.round(savingsInGas / estimateL2 * 100);

return exit({ name: 'Standard checkout', savingsInGas, savingsInPercent });
return { name: 'Standard checkout', savingsInGas, savingsInPercent };
}

const savingsInGas = estimateL1 - estimateL2;
const percentSavings = savingsInGas / estimateL1 * 100;
const savingsInPercent = percentSavings > 99 ? 99 : Math.round(percentSavings); // max value of 99%

return exit({ name, savingsInGas, savingsInPercent });
return { name, savingsInGas, savingsInPercent };
};

zkSyncComparisonResult = compareWithL2(estimateZkSync, 'zkSync');
Expand All @@ -418,12 +399,12 @@ Vue.component('grants-cart', {
polygonSavings = polygonComparisonResult.name === 'Polygon' ? polygonComparisonResult.savingsInPercent : 0;

if (zkSyncSavings > polygonSavings) {
return exit(zkSyncComparisonResult);
return zkSyncComparisonResult;
} else if (zkSyncSavings < polygonSavings) {
return exit(polygonComparisonResult);
return polygonComparisonResult;
}

return exit(zkSyncComparisonResult); // recommendation will be standard checkout
return zkSyncComparisonResult; // recommendation will be standard checkout
},

isHarmonyExtInstalled() {
Expand Down Expand Up @@ -809,8 +790,6 @@ Vue.component('grants-cart', {
* @param {String} name Token name, e.g. ETH or DAI
*/
getTokenByName(name, isPolygon = false) {
let token;

if (name === 'ETH' && !isPolygon) {
return {
addr: ETH_ADDRESS,
Expand All @@ -836,9 +815,7 @@ Vue.component('grants-cart', {
return token;
}

token = this.filterByChainId.filter(token => token.name === name)[0];

return token;
return this.filterByChainId.filter(token => token.name === name)[0];
},

async applyAmountToAllGrants(grant) {
Expand Down Expand Up @@ -1060,6 +1037,8 @@ Vue.component('grants-cart', {

// Standard L1 checkout flow
async standardCheckout() {
this.standardCheckoutInitiated = true;

try {
// Setup -----------------------------------------------------------------------------------
this.isCheckoutOngoing = true;
Expand All @@ -1086,6 +1065,7 @@ Vue.component('grants-cart', {
} catch (err) {
this.handleError(err);
}
this.standardCheckoutInitiated = false;
},

/**
Expand Down
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, chain='std'):
"""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 chain == 'polygon':
network = 'polygon-mainnet'
elif network == 'testnet':
network = 'polygon-mumbai'

if sockets and chain != '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, chain='std'):
status, timestamp, tx = get_tx_status_and_details(txid, network, created_on, chain=chain)
return status, timestamp


def get_tx_status_and_details(txid, network, created_on):
def get_tx_status_and_details(txid, network, created_on, chain='std'):
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, chain=chain)
tx = web3.eth.getTransactionReceipt(txid)
if not tx:
drop_dead_date = created_on + timezone.timedelta(days=DROPPED_DAYS)
Expand Down
Loading

0 comments on commit ed8c62c

Please sign in to comment.