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-370: polygon gas cost estimation #9430

Merged
merged 3 commits into from
Sep 4, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
115 changes: 75 additions & 40 deletions app/assets/v2/js/cart-ethereum-polygon.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
const bulkCheckoutAddressPolygon = '0x3E2849E2A489C8fE47F52847c42aF2E8A82B9973';
const bulkCheckoutAddressPolygon = appCart.$refs.cart.network === 'mainnet'
? '0xb99080b9407436eBb2b8Fe56D45fFA47E9bb8877'
: '0x3E2849E2A489C8fE47F52847c42aF2E8A82B9973';

function objectMap(object, mapFn) {
return Object.keys(object).reduce(function(result, key) {
Expand All @@ -18,11 +20,10 @@ Vue.component('grantsCartEthereumPolygon', {

data: function() {
return {
network: 'testnet',
polygon: {
showModal: false, // true to show modal to user, false to hide
checkoutStatus: 'not-started', // options are 'not-started', 'pending', and 'complete'
estimatedGasCost: '700000'
estimatedGasCost: 650000
},

cart: {
Expand Down Expand Up @@ -60,7 +61,7 @@ Vue.component('grantsCartEthereumPolygon', {
*/
supportedTokens() {
const mainnetTokens = [ 'DAI', 'ETH', 'USDT', 'USDC', 'PAN', 'BNB', 'UNI', 'CELO', 'MASK', 'MATIC' ];
const testnetTokens = [ 'MATIC', 'ETH', 'DAI' ];
const testnetTokens = [ 'DAI', 'ETH', 'USDT', 'USDC', 'UNI', 'MATIC' ];

return appCart.$refs.cart.network === 'mainnet' ? mainnetTokens : testnetTokens;
},
Expand Down Expand Up @@ -216,7 +217,7 @@ Vue.component('grantsCartEthereumPolygon', {
// This error code indicates that the chain has not been added to MetaMask
if (switchError.code === 4902) {
let networkText = network === 'rinkeby' || network === 'goerli' ||
network === 'ropsten' || network === 'kovan' ? 'testnet' : network;
network === 'ropsten' || network === 'kovan' ? 'testnet' : network;

try {
await ethereum.request({
Expand All @@ -241,7 +242,6 @@ Vue.component('grantsCartEthereumPolygon', {
console.error(switchError);
}
}
this.network = getDataChains(ethereum.networkVersion, 'chainId')[0] && getDataChains(ethereum.networkVersion, 'chainId')[0].network;
},

// Send a batch transfer based on donation inputs
Expand Down Expand Up @@ -280,7 +280,6 @@ Vue.component('grantsCartEthereumPolygon', {
}

await this.setupPolygon();
appCart.$refs.cart.userSwitchedToPolygon = true;

// Token approvals and balance checks from bulk checkout contract
// (just checks data, does not execute approvals)
Expand Down Expand Up @@ -344,6 +343,28 @@ Vue.component('grantsCartEthereumPolygon', {
return;
}

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) {
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);
Expand All @@ -353,12 +374,10 @@ Vue.component('grantsCartEthereumPolygon', {
if (isAllDai) {
if (donationCurrencies.length === 1) {
// Special case since we overestimate here otherwise
return 70000;
return gasLimit + 65000;
}
// TODO: find a suitable curve using
// https://github.com/mds1/Gitcoin-Checkout-Gas-Analysis
// return 27500 * donationCurrencies.length + 125000;
return 10000 * donationCurrencies.length + 70000;
return gasLimit + 10000 * donationCurrencies.length + 45000;
}

/**
Expand All @@ -367,14 +386,11 @@ Vue.component('grantsCartEthereumPolygon', {
* 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
*/
const gasLimit = this.donationInputs.reduce((accumulator, currentValue) => {
const tokenAddr = currentValue.token?.toLowerCase();
gasLimit += this.donationInputs.reduce((accumulator, currentValue) => {
// const tokenAddr = currentValue.token?.toLowerCase();

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

} else if (tokenAddr === '0x960b236A07cf122663c4303350609A66A7B288C0'.toLowerCase()) {
return accumulator + 170000; // ANT donation gas estimate
return accumulator + 25000; // MATIC donation gas estimate
}

return accumulator + 70000; // generic token donation gas estimate
Expand Down Expand Up @@ -412,32 +428,51 @@ Vue.component('grantsCartEthereumPolygon', {
requiredAmounts[tokenSymbol].isBalanceSufficient = true; // initialize sufficiency result
const tokenDetails = this.getTokenByName(tokenSymbol);

if (tokenDetails.name === 'MATIC') {
const userMaticBalance = toBigNumber(await web3.eth.getBalance(userAddress));
const userMaticBalance = toBigNumber(await web3.eth.getBalance(userAddress));
const tokenIsMatic = tokenDetails.name === 'MATIC';

if (userMaticBalance.lt(requiredAmounts[tokenSymbol].amount)) {
// User MATIC balance is too small compared to selected donation amounts
requiredAmounts[tokenSymbol].isBalanceSufficient = false;
requiredAmounts[tokenSymbol].amount = (
requiredAmounts[tokenSymbol].amount - userMaticBalance
) / 10 ** tokenDetails.decimals;
isBalanceSufficient = false;
}
} else {
const tokenContract = new web3.eth.Contract(token_abi, tokenDetails.addr);
// Check user token balance against required amount
const userTokenBalance = toBigNumber(await tokenContract.methods
.balanceOf(userAddress)
.call({ from: userAddress }));

if (userTokenBalance.lt(requiredAmounts[tokenSymbol].amount)) {
requiredAmounts[tokenSymbol].isBalanceSufficient = false;
requiredAmounts[tokenSymbol].amount = (
requiredAmounts[tokenSymbol].amount - userTokenBalance
) / 10 ** tokenDetails.decimals;
isBalanceSufficient = false;
// Check user matic balance against required amount
if (userMaticBalance.lt(requiredAmounts[tokenSymbol].amount) && tokenIsMatic) {
requiredAmounts[tokenSymbol].isBalanceSufficient = false;
requiredAmounts[tokenSymbol].amount = (
requiredAmounts[tokenSymbol].amount - userMaticBalance
) / 10 ** tokenDetails.decimals;
isBalanceSufficient = false;
}

// Check if user has enough MATIC to cover gas costs
const gasFeeInWei = web3.utils.toWei(
(this.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
};
}
}

// Check user token balance against required amount
const tokenContract = new web3.eth.Contract(token_abi, tokenDetails.addr);
const userTokenBalance = toBigNumber(await tokenContract.methods
.balanceOf(userAddress)
.call({ from: userAddress }));

if (userTokenBalance.lt(requiredAmounts[tokenSymbol].amount)) {
requiredAmounts[tokenSymbol].isBalanceSufficient = false;
requiredAmounts[tokenSymbol].amount = (
requiredAmounts[tokenSymbol].amount - userTokenBalance
) / 10 ** tokenDetails.decimals;
isBalanceSufficient = false;
}
}

// Return result and required amounts
Expand Down
13 changes: 7 additions & 6 deletions app/assets/v2/js/cart.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,13 @@ let appCart;
document.addEventListener('dataWalletReady', async function(e) {
appCart.$refs['cart'].network = networkName;
appCart.$refs['cart'].networkId = String(Number(web3.eth.currentProvider.chainId));
if (!appCart.$refs.cart.userSwitchedToPolygon) {
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);
}
Expand Down Expand Up @@ -55,7 +56,7 @@ Vue.component('grants-cart', {
{ text: 'Wallet address', value: 'address' },
{ text: 'Transaction Hash', value: 'txid' }
],
userSwitchedToPolygon: false,
autoSwitchNetwork: true,
chainId: '',
networkId: '',
network: 'mainnet',
Expand Down Expand Up @@ -403,7 +404,7 @@ Vue.component('grants-cart', {
const percentSavings = savingsInGas / estimateL1 * 100;
const savingsInPercent = percentSavings > 99 ? 99 : Math.round(percentSavings); // max value of 99%

return { name: name, savingsInGas, savingsInPercent };
return { name, savingsInGas, savingsInPercent };
};

zkSyncComparisonResult = compareWithL2(estimateZkSync, 'zkSync');
Expand Down Expand Up @@ -1175,10 +1176,10 @@ Vue.component('grants-cart', {
// Therefore we get the value of denomination and token_address using the below logic instead of
// using tokenDetails.addr
switch (tokenName) {
case 'ETH':
case 'ETH' && checkoutType !== 'eth_polygon':
tokenAddress = '0x0000000000000000000000000000000000000000';
break;
case 'MATIC':
case 'MATIC' && checkoutType === 'eth_polygon':
tokenAddress = '0x0000000000000000000000000000000000001010';
break;
default:
Expand Down Expand Up @@ -1246,7 +1247,7 @@ Vue.component('grants-cart', {
// Something went wrong, so we use the manual ingestion process instead
console.error(err);
console.log('Standard contribution ingestion failed, falling back to manual ingestion');
await this.postToDatabaseManualIngestion(txHash, userAddress);
await this.postToDatabaseManualIngestion(txHash, userAddress, checkoutType);
}
},

Expand Down
4 changes: 2 additions & 2 deletions app/grants/templates/grants/cart-vue.html
Original file line number Diff line number Diff line change
Expand Up @@ -157,10 +157,10 @@ <h1 class="text-left text-black mt-5 mb-3">Grants Cart</h1>
{% bundle merge_js file qrcode %}
<script src="qrcode.min.js" base-dir="/node_modules/qrcodejs/"></script>
{% endbundle %}
<script src="{% static "v2/js/cart-ethereum-zksync.js" %}"></script>
<script src="{% static "v2/js/cart-ethereum-polygon.js" %}"></script>
<script src="{% static "v2/js/grants/create-collection-modal.js" %}"></script>
<script src="{% static "v2/js/cart.js" %}"></script>
<script src="{% static "v2/js/cart-ethereum-zksync.js" %}"></script>
<script src="{% static "v2/js/cart-ethereum-polygon.js" %}"></script>
<script src="{% static "v2/js/abi.js" %}"></script>
<script src="{% static "v2/js/pages/shared_bounty_mutation_estimate_gas.js" %}"></script>
<script src="{% static "v2/js/why-this-matters-modal.js" %}"></script>
Expand Down
2 changes: 1 addition & 1 deletion app/grants/templates/grants/cart/eth.html
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ <h4 class="col gc-font-base text-dark p-0">Summary</h4>
<li>You are contributing [[donationsToGrantsString]]</li>
<li>You are additionally contributing [[donationsToGitcoinString]] to the Gitcoin Maintainer Grant</li>
<li>
Note: The exact checkout flow will depend on whether you use standard checkout or zkSync.
Note: The exact checkout flow will depend on whether you use Standard Checkout, Polygon, or zkSync.
<a href="https://github.com/gitcoinco/web/blob/master/docs/GRANTS.md" target="_blank">Read how this works</a>.
</li>
</ul>
Expand Down