Skip to content

Commit 5a678c0

Browse files
Create Tokenomics README.md (#32)
* Create Tokenomics README.md * Create placeholder.txt * Add files via upload * Update ETH_Gas_Data_Infura.py Added async/parallel processing updates and error handling to all 3 scripts, making them 50-100x more efficient. Chainstack is currently the main version of the script for block data scraping, with the other 2 (Infura and Etherscan) are additional tools for use with chains besides Ethereum or as backups. * Update ETH_Gas_Data_Etherscan.py Same as previous update * Update ETH_Gas_Data_Chainstack.py Same updates as the last two scripts, this is the current main version between the three. Aync/concurrency and error handling updates, as well as additional columns to assist in GNOT fee simulation. * Update README.md Added a description of the revised/improved script functionality
1 parent ee194f6 commit 5a678c0

File tree

5 files changed

+234
-0
lines changed

5 files changed

+234
-0
lines changed

tokenomics/README.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# Tokenomics
2+
A central location to store all tokenomic scripts, data, and documentation that doesn't fall under other repos or directories.
3+
4+
## GNOT Fee Equation & Simulation
5+
6+
[Fee Equation Spreadsheet](https://docs.google.com/spreadsheets/d/16LdahdcIRMHNQXULCSeO90PGbHTbZbuefwDVbDeKpWk/edit?usp=sharing)
7+
8+
In order to test variants of the GNOT/Gno.land fee equation, a number of scripts, simulations, and files have been created to aid in the process. Async/concurrency has been added to reduce request time from a week to roughly 4 hours per chain.
9+
10+
- A series of scripts and queries utilizing Etherscan, Infura, and a Chainstack node that pull block data from ETH and similar smart contract platforms/L1s before exporting to a CSV.
11+
- Fee data will be used to weight the GNOT fee simulations, resulting in a more realistic distribution. Wei Ethereum data is then converted to Gwei to better align with GNOT notation.
12+
- Gas limit, gas used, the percentage used out of the max, median wei price, and median gwei price are all scrapped and added to a CSV. The percentage of gas used each block will be used to weight the GNOT fee equation's network congestion metric during simulations.
13+
14+
- Once the base Gno.land fee equation is finalized, a Monte Carlo simulation will be created to test a number of Gno.land network conditions. This includes testing against exploits such as spam attacks, various levels of network congestion, etc. Individual parameters within the fee equation such as CPU cycles required, bytes stored, and the threshold at which fee cost begins increasing expotentially (to combat exploits) will also be tested.
15+
16+
If for any reason the Monte Carlo simulation does not provide adoquete insight, a seperate Cartesian Product simulation may be created to brute force additional results. By virtue of testing every possible parameter input against every other parameter input, a Cartesian Product sim can further substantiate any findings as necessary (trading efficiency for thoroughness).
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
#!/usr/bin/env python
2+
# coding: utf-8
3+
4+
# In[1]:
5+
6+
7+
import nest_asyncio
8+
nest_asyncio.apply()
9+
import asyncio
10+
import aiohttp
11+
import pandas as pd
12+
13+
CONCURRENCY_LIMIT = 50 # Adjust this to set the desired concurrency level
14+
semaphore = asyncio.Semaphore(CONCURRENCY_LIMIT)
15+
16+
async def fetch_gas_data_for_block(session, rpc_url, block_num):
17+
async with semaphore: # Use the semaphore to limit concurrency
18+
headers = {"Content-Type": "application/json"}
19+
payload = {
20+
"jsonrpc": "2.0",
21+
"id": 1,
22+
"method": "eth_getBlockByNumber",
23+
"params": [hex(block_num), True]
24+
}
25+
26+
async with session.post(rpc_url, headers=headers, json=payload) as response:
27+
if response.headers.get("Content-Type") != "application/json":
28+
print(f"Unexpected content type for block {block_num}: {response.headers.get('Content-Type')}")
29+
return {
30+
"timestamp": None,
31+
"block_gas_limit": None,
32+
"block_gas_used": None,
33+
"median_gas_price": None,
34+
"error": f"Unexpected content type: {response.headers.get('Content-Type')}"
35+
}
36+
37+
data = await response.json()
38+
if data["result"]:
39+
block_gas_limit = int(data["result"]["gasLimit"], 16)
40+
block_gas_used = int(data["result"]["gasUsed"], 16)
41+
timestamp = int(data["result"]["timestamp"], 16)
42+
43+
tx_gas_prices = [int(tx["gasPrice"], 16) for tx in data["result"]["transactions"]]
44+
median_gas_price = sorted(tx_gas_prices)[len(tx_gas_prices) // 2] if tx_gas_prices else 0
45+
46+
return {
47+
"timestamp": timestamp,
48+
"block_gas_limit": block_gas_limit,
49+
"block_gas_used": block_gas_used,
50+
"median_gas_price": median_gas_price,
51+
"error": None
52+
}
53+
else:
54+
return {
55+
"timestamp": None,
56+
"block_gas_limit": None,
57+
"block_gas_used": None,
58+
"median_gas_price": None,
59+
"error": "No result in response"
60+
}
61+
62+
async def fetch_gas_data_chainstack(rpc_url, start_block, end_block):
63+
tasks = []
64+
async with aiohttp.ClientSession() as session:
65+
for block_num in range(start_block, end_block + 1):
66+
task = fetch_gas_data_for_block(session, rpc_url, block_num)
67+
tasks.append(task)
68+
return await asyncio.gather(*tasks)
69+
70+
RPC_URL = "ADD_RPC_URL"
71+
START_BLOCK = 15537393
72+
END_BLOCK = 17971893
73+
74+
gas_data = asyncio.run(fetch_gas_data_chainstack(RPC_URL, START_BLOCK, END_BLOCK))
75+
df = pd.DataFrame(gas_data)
76+
df['median_gas_price_gwei'] = df['median_gas_price'] / 1e9
77+
df['percentage_used'] = df['block_gas_used'] / df['block_gas_limit']
78+
df = df[['timestamp', 'block_gas_limit', 'block_gas_used', 'percentage_used', 'median_gas_price', 'median_gas_price_gwei', 'error']]
79+
df.to_csv('ETH_historic_gas_data_chainstack.csv', index=False)
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
#!/usr/bin/env python
2+
# coding: utf-8
3+
4+
# In[ ]:
5+
6+
7+
import nest_asyncio
8+
nest_asyncio.apply()
9+
import asyncio
10+
import aiohttp
11+
import pandas as pd
12+
13+
CONCURRENCY_LIMIT = 50
14+
semaphore = asyncio.Semaphore(CONCURRENCY_LIMIT)
15+
16+
async def fetch_gas_data_for_block(session, api_key, block_num):
17+
base_url = "https://api.etherscan.io/api"
18+
params = {
19+
"module": "proxy",
20+
"action": "eth_getBlockByNumber",
21+
"tag": hex(block_num),
22+
"boolean": "true",
23+
"apikey": api_key
24+
}
25+
26+
async with session.get(base_url, params=params) as response:
27+
data = await response.json()
28+
if data["result"]:
29+
block_gas_limit = int(data["result"]["gasLimit"], 16)
30+
block_gas_used = int(data["result"]["gasUsed"], 16)
31+
timestamp = int(data["result"]["timestamp"], 16)
32+
33+
tx_gas_prices = [int(tx["gasPrice"], 16) for tx in data["result"]["transactions"]]
34+
median_gas_price = sorted(tx_gas_prices)[len(tx_gas_prices) // 2] if tx_gas_prices else 0
35+
36+
return {
37+
"timestamp": timestamp,
38+
"block_gas_limit": block_gas_limit,
39+
"block_gas_used": block_gas_used,
40+
"median_gas_price": median_gas_price,
41+
"error": None
42+
}
43+
else:
44+
return {
45+
"timestamp": None,
46+
"block_gas_limit": None,
47+
"block_gas_used": None,
48+
"median_gas_price": None,
49+
"error": "No result in response"
50+
}
51+
52+
async def fetch_gas_data_etherscan(api_key, start_block, end_block):
53+
tasks = []
54+
async with aiohttp.ClientSession() as session:
55+
for block_num in range(start_block, end_block + 1):
56+
task = fetch_gas_data_for_block(session, api_key, block_num)
57+
tasks.append(task)
58+
return await asyncio.gather(*tasks)
59+
60+
API_KEY = "ETHERSCAN_API_KEY"
61+
START_BLOCK = 15537393
62+
END_BLOCK = 17971893
63+
64+
gas_data = asyncio.run(fetch_gas_data_etherscan(API_KEY, START_BLOCK, END_BLOCK))
65+
df = pd.DataFrame(gas_data)
66+
df['median_gas_price_gwei'] = df['median_gas_price'] / 1e9
67+
df['percentage_used'] = df['block_gas_used'] / df['block_gas_limit']
68+
df = df[['timestamp', 'block_gas_limit', 'block_gas_used', 'percentage_used', 'median_gas_price', 'median_gas_price_gwei', 'error']]
69+
df.to_csv('ETH_historic_gas_data_etherscan.csv', index=False)
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
#!/usr/bin/env python
2+
# coding: utf-8
3+
4+
# In[ ]:
5+
6+
7+
import nest_asyncio
8+
nest_asyncio.apply()
9+
import asyncio
10+
import aiohttp
11+
import pandas as pd
12+
13+
CONCURRENCY_LIMIT = 50
14+
semaphore = asyncio.Semaphore(CONCURRENCY_LIMIT)
15+
16+
async def fetch_gas_data_for_block(session, api_key, block_num):
17+
base_url = "https://mainnet.infura.io/v3/" + api_key
18+
headers = {"Content-Type": "application/json"}
19+
payload = {
20+
"jsonrpc": "2.0",
21+
"id": 1,
22+
"method": "eth_getBlockByNumber",
23+
"params": [hex(block_num), True]
24+
}
25+
26+
async with session.post(base_url, headers=headers, json=payload) as response:
27+
data = await response.json()
28+
if data["result"]:
29+
block_gas_limit = int(data["result"]["gasLimit"], 16)
30+
block_gas_used = int(data["result"]["gasUsed"], 16)
31+
timestamp = int(data["result"]["timestamp"], 16)
32+
33+
tx_gas_prices = [int(tx["gasPrice"], 16) for tx in data["result"]["transactions"]]
34+
median_gas_price = sorted(tx_gas_prices)[len(tx_gas_prices) // 2] if tx_gas_prices else 0
35+
36+
return {
37+
"timestamp": timestamp,
38+
"block_gas_limit": block_gas_limit,
39+
"block_gas_used": block_gas_used,
40+
"median_gas_price": median_gas_price,
41+
"error": None
42+
}
43+
else:
44+
return {
45+
"timestamp": None,
46+
"block_gas_limit": None,
47+
"block_gas_used": None,
48+
"median_gas_price": None,
49+
"error": "No result in response"
50+
}
51+
52+
async def fetch_gas_data_infura(api_key, start_block, end_block):
53+
tasks = []
54+
async with aiohttp.ClientSession() as session:
55+
for block_num in range(start_block, end_block + 1):
56+
task = fetch_gas_data_for_block(session, api_key, block_num)
57+
tasks.append(task)
58+
return await asyncio.gather(*tasks)
59+
60+
API_KEY = "INFURA_API_KEY"
61+
START_BLOCK = 15537393
62+
END_BLOCK = 17971893
63+
64+
gas_data = asyncio.run(fetch_gas_data_infura(API_KEY, START_BLOCK, END_BLOCK))
65+
df = pd.DataFrame(gas_data)
66+
df['median_gas_price_gwei'] = df['median_gas_price'] / 1e9
67+
df['percentage_used'] = df['block_gas_used'] / df['block_gas_limit']
68+
df = df[['timestamp', 'block_gas_limit', 'block_gas_used', 'percentage_used', 'median_gas_price', 'median_gas_price_gwei', 'error']]
69+
df.to_csv('ETH_historic_gas_data_infura.csv', index=False)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
GNOT Fee scripts & simulations

0 commit comments

Comments
 (0)