Merge branch 'development_builders' of https://github.com/freeflowuniverse/herolib into development_builders
This commit is contained in:
48
examples/threefold/incatokens/main.v
Normal file
48
examples/threefold/incatokens/main.v
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
import freeflowuniverse.herolib.threefold.incatokens
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
// Create simulation
|
||||||
|
mut sim := incatokens.simulation_new(
|
||||||
|
name: 'inca_tge_v1'
|
||||||
|
total_supply: 10_000_000_000
|
||||||
|
)!
|
||||||
|
|
||||||
|
// Initialize with default rounds
|
||||||
|
sim.init_default_rounds()!
|
||||||
|
|
||||||
|
// Define scenarios
|
||||||
|
scenarios := {
|
||||||
|
'Low': {
|
||||||
|
'demands': [10_000_000.0, 10_000_000.0, 0.0],
|
||||||
|
'amm_trades': [0.0, 0.0, 0.0]
|
||||||
|
},
|
||||||
|
'Mid': {
|
||||||
|
'demands': [20_000_000.0, 20_000_000.0, 0.0],
|
||||||
|
'amm_trades': [0.0, 0.0, 0.0]
|
||||||
|
},
|
||||||
|
'High': {
|
||||||
|
'demands': [50_000_000.0, 100_000_000.0, 0.0],
|
||||||
|
'amm_trades': [0.0, 0.0, 0.0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run all scenarios
|
||||||
|
for name, params in scenarios {
|
||||||
|
sim.run_scenario(name, params['demands'], params['amm_trades'])!
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create vesting schedules
|
||||||
|
sim.create_vesting_schedules()!
|
||||||
|
|
||||||
|
// Generate charts
|
||||||
|
price_chart := sim.generate_price_chart()!
|
||||||
|
market_cap_chart := sim.generate_market_cap_chart()!
|
||||||
|
vesting_chart := sim.generate_vesting_chart()!
|
||||||
|
|
||||||
|
// Export results
|
||||||
|
sim.export_report('simulation_report.md')!
|
||||||
|
sim.export_csv('prices', 'price_data.csv')!
|
||||||
|
sim.export_csv('vesting', 'vesting_schedule.csv')!
|
||||||
|
|
||||||
|
println('Simulation complete!')
|
||||||
|
}
|
||||||
38
lib/threefold/incatokens/amm.v
Normal file
38
lib/threefold/incatokens/amm.v
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
module incatokens
|
||||||
|
|
||||||
|
import math
|
||||||
|
|
||||||
|
pub struct AMMPool {
|
||||||
|
pub mut:
|
||||||
|
tokens f64
|
||||||
|
usdc f64
|
||||||
|
k f64 // constant product
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn (mut pool AMMPool) add_liquidity(tokens f64, usdc f64) {
|
||||||
|
pool.tokens += tokens
|
||||||
|
pool.usdc += usdc
|
||||||
|
pool.k = pool.tokens * pool.usdc
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn (mut pool AMMPool) trade(usdc_amount f64) ! {
|
||||||
|
if pool.tokens <= 0 || pool.usdc <= 0 {
|
||||||
|
return error('AMM pool is empty')
|
||||||
|
}
|
||||||
|
|
||||||
|
pool.usdc += usdc_amount
|
||||||
|
|
||||||
|
if pool.usdc <= 0 {
|
||||||
|
return error('Insufficient USDC in pool')
|
||||||
|
}
|
||||||
|
|
||||||
|
// Maintain constant product
|
||||||
|
pool.tokens = pool.k / pool.usdc
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn (pool AMMPool) get_price() f64 {
|
||||||
|
if pool.tokens <= 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return pool.usdc / pool.tokens
|
||||||
|
}
|
||||||
54
lib/threefold/incatokens/auction.v
Normal file
54
lib/threefold/incatokens/auction.v
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
module incatokens
|
||||||
|
|
||||||
|
import math
|
||||||
|
pub struct AuctionResult {
|
||||||
|
pub mut:
|
||||||
|
tokens_sold f64
|
||||||
|
clearing_price f64
|
||||||
|
usd_raised f64
|
||||||
|
fully_subscribed bool
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn simulate_auction(config AuctionConfig) !AuctionResult {
|
||||||
|
demand := config.demand
|
||||||
|
min_price := config.min_price
|
||||||
|
token_supply := config.token_supply
|
||||||
|
|
||||||
|
if token_supply <= 0 {
|
||||||
|
return AuctionResult{
|
||||||
|
tokens_sold: 0
|
||||||
|
clearing_price: 0
|
||||||
|
usd_raised: 0
|
||||||
|
fully_subscribed: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
implied_avg := demand / token_supply
|
||||||
|
|
||||||
|
if implied_avg < min_price {
|
||||||
|
// Partial fill at floor price
|
||||||
|
tokens_sold := math.min(demand / min_price, token_supply)
|
||||||
|
return AuctionResult{
|
||||||
|
tokens_sold: tokens_sold
|
||||||
|
clearing_price: min_price
|
||||||
|
usd_raised: tokens_sold * min_price
|
||||||
|
fully_subscribed: tokens_sold >= token_supply
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Full subscription at market clearing price
|
||||||
|
return AuctionResult{
|
||||||
|
tokens_sold: token_supply
|
||||||
|
clearing_price: implied_avg
|
||||||
|
usd_raised: demand
|
||||||
|
fully_subscribed: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@[params]
|
||||||
|
pub struct AuctionConfig {
|
||||||
|
pub mut:
|
||||||
|
demand f64
|
||||||
|
min_price f64
|
||||||
|
token_supply f64
|
||||||
|
}
|
||||||
67
lib/threefold/incatokens/charts.v
Normal file
67
lib/threefold/incatokens/charts.v
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
module incatokens
|
||||||
|
|
||||||
|
import freeflowuniverse.herolib.biz.spreadsheet
|
||||||
|
import freeflowuniverse.herolib.web.echarts
|
||||||
|
|
||||||
|
// Generate price evolution chart
|
||||||
|
pub fn (sim Simulation) generate_price_chart() !echarts.EChartsOption {
|
||||||
|
mut rownames := []string{}
|
||||||
|
for name, _ in sim.scenarios {
|
||||||
|
rownames << 'scenario_${name}_price'
|
||||||
|
}
|
||||||
|
|
||||||
|
return sim.price_sheet.line_chart(
|
||||||
|
rowname: rownames.join(',')
|
||||||
|
period_type: .month
|
||||||
|
title: 'INCA Token Price Evolution'
|
||||||
|
title_sub: 'Price paths across different scenarios'
|
||||||
|
unit: .normal
|
||||||
|
)!
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate market cap chart
|
||||||
|
pub fn (sim Simulation) generate_market_cap_chart() !echarts.EChartsOption {
|
||||||
|
// Create market cap rows from price rows
|
||||||
|
mut mc_sheet := spreadsheet.sheet_new(
|
||||||
|
name: '${sim.name}_market_cap'
|
||||||
|
nrcol: sim.price_sheet.nrcol
|
||||||
|
curr: sim.currency
|
||||||
|
)!
|
||||||
|
|
||||||
|
for name, scenario in sim.scenarios {
|
||||||
|
mut mc_row := mc_sheet.row_new(
|
||||||
|
name: 'scenario_${name}_mc'
|
||||||
|
tags: 'scenario:${name} type:market_cap'
|
||||||
|
descr: 'Market cap for ${name} scenario'
|
||||||
|
)!
|
||||||
|
|
||||||
|
price_row := sim.price_sheet.row_get('scenario_${name}_price')!
|
||||||
|
for i, cell in price_row.cells {
|
||||||
|
mc_row.cells[i].val = cell.val * sim.total_supply
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return mc_sheet.bar_chart(
|
||||||
|
namefilter: mc_sheet.rows.keys()
|
||||||
|
period_type: .quarter
|
||||||
|
title: 'INCA Market Capitalization'
|
||||||
|
title_sub: 'Market cap evolution by quarter'
|
||||||
|
unit: .million
|
||||||
|
)!
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate vesting schedule chart
|
||||||
|
pub fn (mut sim Simulation) generate_vesting_chart() !echarts.EChartsOption {
|
||||||
|
if isnil(sim.vesting_sheet) {
|
||||||
|
sim.create_vesting_schedules()!
|
||||||
|
}
|
||||||
|
|
||||||
|
return sim.vesting_sheet.line_chart(
|
||||||
|
includefilter: ['type:vesting']
|
||||||
|
excludefilter: ['type:total_vesting']
|
||||||
|
period_type: .quarter
|
||||||
|
title: 'Token Vesting Schedule'
|
||||||
|
title_sub: 'Cumulative tokens unlocked over time'
|
||||||
|
unit: .million
|
||||||
|
)!
|
||||||
|
}
|
||||||
95
lib/threefold/incatokens/export.v
Normal file
95
lib/threefold/incatokens/export.v
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
module incatokens
|
||||||
|
|
||||||
|
import freeflowuniverse.herolib.core.pathlib
|
||||||
|
import freeflowuniverse.herolib.data.markdown
|
||||||
|
import os
|
||||||
|
|
||||||
|
// // Export simulation results to markdown report
|
||||||
|
// pub fn (sim &Simulation) export_report(path string) ! {
|
||||||
|
// content := sim.render_report()!
|
||||||
|
|
||||||
|
// // Write to file
|
||||||
|
// mut p := pathlib.get_file(
|
||||||
|
// path: path
|
||||||
|
// create: true
|
||||||
|
// delete: true
|
||||||
|
// )!
|
||||||
|
// p.write(content)!
|
||||||
|
// }
|
||||||
|
|
||||||
|
// Export data to CSV
|
||||||
|
pub fn (sim Simulation) export_csv(sheet_name string, path string) ! {
|
||||||
|
mut sheet := match sheet_name {
|
||||||
|
'prices' { sim.price_sheet }
|
||||||
|
'tokens' { sim.token_sheet }
|
||||||
|
'investments' { sim.investment_sheet }
|
||||||
|
'vesting' { sim.vesting_sheet }
|
||||||
|
else { return error('Unknown sheet: ${sheet_name}') }
|
||||||
|
}
|
||||||
|
|
||||||
|
sheet.export_csv(
|
||||||
|
path: path
|
||||||
|
separator: ','
|
||||||
|
include_empty: false
|
||||||
|
)!
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn (sim Simulation) generate_distribution_section() !string {
|
||||||
|
mut lines := []string{}
|
||||||
|
|
||||||
|
lines << '- **Total supply:** ${sim.total_supply} INCA'
|
||||||
|
lines << '- **Public (TGE):** ${(sim.public_pct * 100).str()}% (No lockup)'
|
||||||
|
lines << '- **Team:** ${(sim.team_pct * 100).str()}% (${sim.team_vesting.cliff_months}mo cliff, ${sim.team_vesting.vesting_months}mo vest)'
|
||||||
|
lines << '- **Treasury:** ${(sim.treasury_pct * 100).str()}% (${sim.treasury_vesting.cliff_months}mo cliff, ${sim.treasury_vesting.vesting_months}mo vest)'
|
||||||
|
lines << '- **Investors:** ${(sim.investor_pct * 100).str()}%'
|
||||||
|
lines << ''
|
||||||
|
lines << '### Investor Rounds & Vesting'
|
||||||
|
lines << '| Round | Allocation | Price (USD) | Vesting Schedule |'
|
||||||
|
lines << '|---|---|---|---|'
|
||||||
|
|
||||||
|
for round in sim.investor_rounds {
|
||||||
|
lines << '| **${round.name}** | ${(round.allocation_pct * 100).str()}% | \$${round.price} | ${round.vesting.cliff_months}mo cliff, ${round.vesting.vesting_months}mo linear vest |'
|
||||||
|
}
|
||||||
|
|
||||||
|
return lines.join('\n')
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn (sim Simulation) generate_scenario_section(scenario &Scenario) !string {
|
||||||
|
mut lines := []string{}
|
||||||
|
|
||||||
|
lines << '### ${scenario.name} Scenario'
|
||||||
|
lines << '**Parameters:**'
|
||||||
|
lines << '- **Auction Demand:** \$${scenario.demands.map(it.str()).join(', ')}'
|
||||||
|
lines << '- **AMM Net Trade:** \$${scenario.amm_trades.map(it.str()).join(', ')}'
|
||||||
|
lines << ''
|
||||||
|
lines << '**Results:**'
|
||||||
|
lines << '| Treasury Raised | Final Price | ${sim.investor_rounds.map("ROI " + it.name).join(" | ")} |'
|
||||||
|
lines << '|:---|:---|${sim.investor_rounds.map(":---").join("|")}|'
|
||||||
|
|
||||||
|
mut row := ['\$${(scenario.final_metrics.treasury_total / 1000000).str()}M', '\$${scenario.final_metrics.final_price}']
|
||||||
|
for round in sim.investor_rounds {
|
||||||
|
roi := scenario.final_metrics.investor_roi[round.name] or { 0.0 }
|
||||||
|
row << '${roi}x'
|
||||||
|
}
|
||||||
|
lines << '| ${row.join(' | ')} |'
|
||||||
|
|
||||||
|
return lines.join('\n')
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn (sim Simulation) generate_financial_summary() !string {
|
||||||
|
mut lines := []string{}
|
||||||
|
|
||||||
|
lines << '### Funds Raised for INCA COOP'
|
||||||
|
lines << '| Round | USD Raised |'
|
||||||
|
lines << '|---|---|'
|
||||||
|
|
||||||
|
mut total_raised := 0.0
|
||||||
|
for round in sim.investor_rounds {
|
||||||
|
raised := round.allocation_pct * sim.total_supply * round.price
|
||||||
|
total_raised += raised
|
||||||
|
lines << '| **${round.name}** | \$${raised} |'
|
||||||
|
}
|
||||||
|
lines << '| **Total** | **\$${total_raised}** |'
|
||||||
|
|
||||||
|
return lines.join('\n')
|
||||||
|
}
|
||||||
68
lib/threefold/incatokens/factory.v
Normal file
68
lib/threefold/incatokens/factory.v
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
module incatokens
|
||||||
|
|
||||||
|
import freeflowuniverse.herolib.core.texttools
|
||||||
|
import freeflowuniverse.herolib.biz.spreadsheet
|
||||||
|
|
||||||
|
__global (
|
||||||
|
simulations map[string]&Simulation
|
||||||
|
)
|
||||||
|
|
||||||
|
@[params]
|
||||||
|
pub struct SimulationNewArgs {
|
||||||
|
pub mut:
|
||||||
|
name string = 'default'
|
||||||
|
total_supply f64 = 10_000_000_000
|
||||||
|
public_pct f64 = 0.50
|
||||||
|
team_pct f64 = 0.15
|
||||||
|
treasury_pct f64 = 0.15
|
||||||
|
investor_pct f64 = 0.20
|
||||||
|
nrcol int = 60 // 60 months default
|
||||||
|
currency string = 'USD'
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn simulation_new(args SimulationNewArgs) !&Simulation {
|
||||||
|
name := texttools.name_fix(args.name)
|
||||||
|
|
||||||
|
// Initialize spreadsheets for tracking
|
||||||
|
price_sheet := spreadsheet.sheet_new(
|
||||||
|
name: '${name}_prices'
|
||||||
|
nrcol: args.nrcol
|
||||||
|
curr: args.currency
|
||||||
|
)!
|
||||||
|
|
||||||
|
token_sheet := spreadsheet.sheet_new(
|
||||||
|
name: '${name}_tokens'
|
||||||
|
nrcol: args.nrcol
|
||||||
|
curr: args.currency
|
||||||
|
)!
|
||||||
|
|
||||||
|
investment_sheet := spreadsheet.sheet_new(
|
||||||
|
name: '${name}_investments'
|
||||||
|
nrcol: args.nrcol
|
||||||
|
curr: args.currency
|
||||||
|
)!
|
||||||
|
|
||||||
|
mut sim := &Simulation{
|
||||||
|
name: name
|
||||||
|
total_supply: args.total_supply
|
||||||
|
public_pct: args.public_pct
|
||||||
|
team_pct: args.team_pct
|
||||||
|
treasury_pct: args.treasury_pct
|
||||||
|
investor_pct: args.investor_pct
|
||||||
|
currency: args.currency
|
||||||
|
price_sheet: price_sheet
|
||||||
|
token_sheet: token_sheet
|
||||||
|
investment_sheet: investment_sheet
|
||||||
|
vesting_sheet: unsafe { nil }
|
||||||
|
}
|
||||||
|
|
||||||
|
simulations[name] = sim
|
||||||
|
return sim
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn simulation_get(name string) !&Simulation {
|
||||||
|
name_fixed := texttools.name_fix(name)
|
||||||
|
return simulations[name_fixed] or {
|
||||||
|
return error('Simulation "${name_fixed}" not found')
|
||||||
|
}
|
||||||
|
}
|
||||||
81
lib/threefold/incatokens/incatokens_test.v
Normal file
81
lib/threefold/incatokens/incatokens_test.v
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
module incatokens
|
||||||
|
|
||||||
|
fn test_simulation_creation() {
|
||||||
|
mut sim := simulation_new(name: 'test_sim')!
|
||||||
|
sim.init_default_rounds()!
|
||||||
|
|
||||||
|
assert sim.name == 'test_sim'
|
||||||
|
assert sim.total_supply == 10_000_000_000
|
||||||
|
assert sim.investor_rounds.len == 3
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_scenario_execution() {
|
||||||
|
mut sim := simulation_new(name: 'test_sim')!
|
||||||
|
sim.init_default_rounds()!
|
||||||
|
|
||||||
|
// Run low scenario
|
||||||
|
low_scenario := sim.run_scenario(
|
||||||
|
'Low',
|
||||||
|
[10_000_000.0, 10_000_000.0, 0.0],
|
||||||
|
[0.0, 0.0, 0.0]
|
||||||
|
)!
|
||||||
|
|
||||||
|
assert low_scenario.name == 'Low'
|
||||||
|
assert low_scenario.final_metrics.treasury_total > 0
|
||||||
|
assert low_scenario.final_metrics.final_price > 0
|
||||||
|
|
||||||
|
// Check ROI is positive for all rounds
|
||||||
|
for round in sim.investor_rounds {
|
||||||
|
roi := low_scenario.final_metrics.investor_roi[round.name] or { 0.0 }
|
||||||
|
assert roi > 0 // Should have positive return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_vesting_schedules() {
|
||||||
|
mut sim := simulation_new(name: 'test_sim')!
|
||||||
|
sim.init_default_rounds()!
|
||||||
|
sim.create_vesting_schedules()!
|
||||||
|
|
||||||
|
// Check team vesting
|
||||||
|
team_row := sim.vesting_sheet.row_get('team_vesting')!
|
||||||
|
|
||||||
|
// Before cliff (month 11), should be 0
|
||||||
|
assert team_row.cells[11].val == 0
|
||||||
|
|
||||||
|
// After cliff starts (month 12), should have tokens
|
||||||
|
assert team_row.cells[12].val > 0
|
||||||
|
|
||||||
|
// After full vesting (month 48), should have all tokens
|
||||||
|
total_team_tokens := sim.total_supply * sim.team_pct
|
||||||
|
assert team_row.cells[48].val == total_team_tokens
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_export_functionality() {
|
||||||
|
mut sim := simulation_new(name: 'test_export')!
|
||||||
|
sim.init_default_rounds()!
|
||||||
|
|
||||||
|
// Run scenarios
|
||||||
|
sim.run_scenario('Low', [10_000_000.0, 10_000_000.0, 0.0], [0.0, 0.0, 0.0])!
|
||||||
|
sim.run_scenario('High', [50_000_000.0, 100_000_000.0, 0.0], [0.0, 0.0, 0.0])!
|
||||||
|
|
||||||
|
// Test CSV export
|
||||||
|
sim.export_csv('prices', '/tmp/test_prices.csv')!
|
||||||
|
|
||||||
|
// Test report generation
|
||||||
|
// sim.export_report('/tmp/test_report.md')!
|
||||||
|
}
|
||||||
|
// fn test_template_rendering() {
|
||||||
|
// mut sim := simulation_new(name: 'test_template')!
|
||||||
|
// sim.init_default_rounds()!
|
||||||
|
|
||||||
|
// // Run a scenario
|
||||||
|
// sim.run_scenario('Low', [10_000_000.0, 10_000_000.0, 0.0], [0.0, 0.0, 0.0])!
|
||||||
|
|
||||||
|
// // Render the report
|
||||||
|
// report := sim.render_report()!
|
||||||
|
|
||||||
|
// // Assert that the report is not empty and contains some expected text
|
||||||
|
// assert report.len > 0
|
||||||
|
// assert report.contains('# INCA TGE Simulation Report')
|
||||||
|
// assert report.contains('## 1) Token Distribution & Vesting')
|
||||||
|
// }
|
||||||
93
lib/threefold/incatokens/model.v
Normal file
93
lib/threefold/incatokens/model.v
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
module incatokens
|
||||||
|
|
||||||
|
import freeflowuniverse.herolib.biz.spreadsheet
|
||||||
|
import freeflowuniverse.herolib.data.ourtime
|
||||||
|
|
||||||
|
pub struct VestingSchedule {
|
||||||
|
pub mut:
|
||||||
|
cliff_months int
|
||||||
|
vesting_months int
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct InvestorRound {
|
||||||
|
pub mut:
|
||||||
|
name string
|
||||||
|
allocation_pct f64
|
||||||
|
price f64
|
||||||
|
vesting VestingSchedule
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum EpochType {
|
||||||
|
auction_only
|
||||||
|
hybrid
|
||||||
|
amm_only
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Epoch {
|
||||||
|
pub mut:
|
||||||
|
index int
|
||||||
|
type_ EpochType
|
||||||
|
start_month int
|
||||||
|
end_month int
|
||||||
|
auction_share f64
|
||||||
|
amm_share f64
|
||||||
|
tokens_allocated f64
|
||||||
|
tokens_spillover f64
|
||||||
|
auction_demand f64
|
||||||
|
amm_net_trade f64
|
||||||
|
final_price f64
|
||||||
|
treasury_raised f64
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Scenario {
|
||||||
|
pub mut:
|
||||||
|
name string
|
||||||
|
demands []f64
|
||||||
|
amm_trades []f64
|
||||||
|
epochs []Epoch
|
||||||
|
final_metrics ScenarioMetrics
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ScenarioMetrics {
|
||||||
|
pub mut:
|
||||||
|
treasury_total f64
|
||||||
|
final_price f64
|
||||||
|
investor_roi map[string]f64
|
||||||
|
market_cap_final f64
|
||||||
|
circulating_supply_final f64
|
||||||
|
}
|
||||||
|
|
||||||
|
@[heap]
|
||||||
|
pub struct Simulation {
|
||||||
|
pub mut:
|
||||||
|
name string
|
||||||
|
|
||||||
|
// Token distribution
|
||||||
|
total_supply f64
|
||||||
|
public_pct f64
|
||||||
|
team_pct f64
|
||||||
|
treasury_pct f64
|
||||||
|
investor_pct f64
|
||||||
|
|
||||||
|
// Configuration
|
||||||
|
currency string
|
||||||
|
epoch1_floor_uplift f64 = 1.20
|
||||||
|
epochn_floor_uplift f64 = 1.20
|
||||||
|
amm_liquidity_depth_factor f64 = 2.0
|
||||||
|
|
||||||
|
// Investor rounds
|
||||||
|
investor_rounds []InvestorRound
|
||||||
|
|
||||||
|
// Vesting schedules
|
||||||
|
team_vesting VestingSchedule
|
||||||
|
treasury_vesting VestingSchedule
|
||||||
|
|
||||||
|
// Tracking sheets
|
||||||
|
price_sheet &spreadsheet.Sheet
|
||||||
|
token_sheet &spreadsheet.Sheet
|
||||||
|
investment_sheet &spreadsheet.Sheet
|
||||||
|
vesting_sheet &spreadsheet.Sheet
|
||||||
|
|
||||||
|
// Scenarios
|
||||||
|
scenarios map[string]&Scenario
|
||||||
|
}
|
||||||
175
lib/threefold/incatokens/simulation.v
Normal file
175
lib/threefold/incatokens/simulation.v
Normal file
@@ -0,0 +1,175 @@
|
|||||||
|
module incatokens
|
||||||
|
|
||||||
|
import freeflowuniverse.herolib.biz.spreadsheet
|
||||||
|
|
||||||
|
// Initialize default investor rounds
|
||||||
|
pub fn (mut sim Simulation) init_default_rounds() ! {
|
||||||
|
sim.investor_rounds = [
|
||||||
|
InvestorRound{
|
||||||
|
name: 'R1'
|
||||||
|
allocation_pct: 0.05
|
||||||
|
price: 0.005
|
||||||
|
vesting: VestingSchedule{cliff_months: 6, vesting_months: 24}
|
||||||
|
},
|
||||||
|
InvestorRound{
|
||||||
|
name: 'R2'
|
||||||
|
allocation_pct: 0.075
|
||||||
|
price: 0.009
|
||||||
|
vesting: VestingSchedule{cliff_months: 6, vesting_months: 24}
|
||||||
|
},
|
||||||
|
InvestorRound{
|
||||||
|
name: 'R3'
|
||||||
|
allocation_pct: 0.075
|
||||||
|
price: 0.0106
|
||||||
|
vesting: VestingSchedule{cliff_months: 6, vesting_months: 24}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
sim.team_vesting = VestingSchedule{cliff_months: 12, vesting_months: 36}
|
||||||
|
sim.treasury_vesting = VestingSchedule{cliff_months: 12, vesting_months: 48}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run a scenario with given demands and AMM trades
|
||||||
|
pub fn (mut sim Simulation) run_scenario(name string, demands []f64, amm_trades []f64) !&Scenario {
|
||||||
|
mut scenario := &Scenario{
|
||||||
|
name: name
|
||||||
|
demands: demands
|
||||||
|
amm_trades: amm_trades
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize epochs
|
||||||
|
scenario.epochs = [
|
||||||
|
Epoch{index: 0, type_: .auction_only, start_month: 0, end_month: 3, auction_share: 1.0, amm_share: 0.0},
|
||||||
|
Epoch{index: 1, type_: .hybrid, start_month: 3, end_month: 6, auction_share: 0.5, amm_share: 0.5},
|
||||||
|
Epoch{index: 2, type_: .amm_only, start_month: 6, end_month: 12, auction_share: 0.0, amm_share: 1.0}
|
||||||
|
]
|
||||||
|
|
||||||
|
// Track in spreadsheet
|
||||||
|
mut price_row := sim.price_sheet.row_new(
|
||||||
|
name: 'scenario_${name}_price'
|
||||||
|
tags: 'scenario:${name} type:price'
|
||||||
|
descr: 'Token price evolution for ${name} scenario'
|
||||||
|
)!
|
||||||
|
|
||||||
|
mut treasury_row := sim.investment_sheet.row_new(
|
||||||
|
name: 'scenario_${name}_treasury'
|
||||||
|
tags: 'scenario:${name} type:treasury'
|
||||||
|
descr: 'Treasury raised for ${name} scenario'
|
||||||
|
aggregatetype: .sum
|
||||||
|
)!
|
||||||
|
|
||||||
|
// Calculate public tokens per epoch
|
||||||
|
total_public := sim.total_supply * sim.public_pct
|
||||||
|
tokens_per_epoch := total_public / 3.0
|
||||||
|
|
||||||
|
mut last_auction_price := sim.get_last_investor_price()
|
||||||
|
mut spillover := 0.0
|
||||||
|
mut treasury_total := 0.0
|
||||||
|
mut amm_pool := AMMPool{}
|
||||||
|
|
||||||
|
for mut epoch in scenario.epochs {
|
||||||
|
epoch.tokens_allocated = tokens_per_epoch + spillover
|
||||||
|
epoch.auction_demand = demands[epoch.index]
|
||||||
|
epoch.amm_net_trade = amm_trades[epoch.index]
|
||||||
|
|
||||||
|
// Run auction if applicable
|
||||||
|
if epoch.auction_share > 0 {
|
||||||
|
auction_tokens := epoch.tokens_allocated * epoch.auction_share
|
||||||
|
floor_price := sim.calculate_floor_price(epoch.index, last_auction_price)
|
||||||
|
|
||||||
|
auction_result := simulate_auction(
|
||||||
|
demand: epoch.auction_demand
|
||||||
|
min_price: floor_price
|
||||||
|
token_supply: auction_tokens
|
||||||
|
)!
|
||||||
|
|
||||||
|
epoch.treasury_raised = auction_result.usd_raised
|
||||||
|
treasury_total += auction_result.usd_raised
|
||||||
|
last_auction_price = auction_result.clearing_price
|
||||||
|
epoch.final_price = auction_result.clearing_price
|
||||||
|
spillover = auction_tokens - auction_result.tokens_sold
|
||||||
|
|
||||||
|
// Record in spreadsheet
|
||||||
|
treasury_row.cells[epoch.start_month].val = auction_result.usd_raised
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle AMM if applicable
|
||||||
|
if epoch.amm_share > 0 {
|
||||||
|
amm_tokens := epoch.tokens_allocated * epoch.amm_share + spillover
|
||||||
|
spillover = 0
|
||||||
|
|
||||||
|
// Seed AMM pool
|
||||||
|
amm_usdc_to_add := sim.amm_liquidity_depth_factor * epoch.treasury_raised
|
||||||
|
amm_pool.add_liquidity(amm_tokens, amm_usdc_to_add)
|
||||||
|
|
||||||
|
// Simulate trading
|
||||||
|
if epoch.amm_net_trade != 0 {
|
||||||
|
amm_pool.trade(epoch.amm_net_trade)!
|
||||||
|
}
|
||||||
|
|
||||||
|
epoch.final_price = amm_pool.get_price()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Record price in spreadsheet
|
||||||
|
for month in epoch.start_month .. epoch.end_month {
|
||||||
|
price_row.cells[month].val = epoch.final_price
|
||||||
|
}
|
||||||
|
|
||||||
|
epoch.tokens_spillover = spillover
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate final metrics
|
||||||
|
scenario.final_metrics = sim.calculate_metrics(scenario, treasury_total)!
|
||||||
|
|
||||||
|
sim.scenarios[name] = scenario
|
||||||
|
return scenario
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate metrics for scenario
|
||||||
|
fn (sim Simulation) calculate_metrics(scenario &Scenario, treasury_total f64) !ScenarioMetrics {
|
||||||
|
final_price := scenario.epochs.last().final_price
|
||||||
|
|
||||||
|
mut investor_roi := map[string]f64{}
|
||||||
|
for round in sim.investor_rounds {
|
||||||
|
investor_roi[round.name] = final_price / round.price
|
||||||
|
}
|
||||||
|
|
||||||
|
return ScenarioMetrics{
|
||||||
|
treasury_total: treasury_total
|
||||||
|
final_price: final_price
|
||||||
|
investor_roi: investor_roi
|
||||||
|
market_cap_final: final_price * sim.total_supply
|
||||||
|
circulating_supply_final: sim.calculate_circulating_supply(12) // at month 12
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (sim Simulation) get_last_investor_price() f64 {
|
||||||
|
mut max_price := 0.0
|
||||||
|
for round in sim.investor_rounds {
|
||||||
|
if round.price > max_price {
|
||||||
|
max_price = round.price
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return max_price
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (sim Simulation) calculate_floor_price(epoch_idx int, last_auction_price f64) f64 {
|
||||||
|
last_investor_price := sim.get_last_investor_price()
|
||||||
|
|
||||||
|
if epoch_idx == 0 {
|
||||||
|
return last_investor_price * sim.epoch1_floor_uplift
|
||||||
|
}
|
||||||
|
return last_auction_price * sim.epochn_floor_uplift
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (sim Simulation) calculate_circulating_supply(month int) f64 {
|
||||||
|
// Base circulation (non-public allocations)
|
||||||
|
investor_tokens := sim.investor_pct * sim.total_supply
|
||||||
|
team_tokens := sim.team_pct * sim.total_supply
|
||||||
|
treasury_tokens := sim.treasury_pct * sim.total_supply
|
||||||
|
|
||||||
|
// For simplicity, assume all public tokens are circulating after TGE
|
||||||
|
public_tokens := sim.public_pct * sim.total_supply
|
||||||
|
|
||||||
|
return investor_tokens + team_tokens + treasury_tokens + public_tokens
|
||||||
|
}
|
||||||
71
lib/threefold/incatokens/vesting.v
Normal file
71
lib/threefold/incatokens/vesting.v
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
module incatokens
|
||||||
|
|
||||||
|
import freeflowuniverse.herolib.biz.spreadsheet
|
||||||
|
|
||||||
|
// Create vesting schedule in spreadsheet
|
||||||
|
pub fn (mut sim Simulation) create_vesting_schedules() ! {
|
||||||
|
// Create vesting sheet
|
||||||
|
mut vesting_sheet := spreadsheet.sheet_new(
|
||||||
|
name: '${sim.name}_vesting'
|
||||||
|
nrcol: 60 // 60 months
|
||||||
|
curr: sim.currency
|
||||||
|
)!
|
||||||
|
|
||||||
|
// Team vesting
|
||||||
|
team_tokens := sim.total_supply * sim.team_pct
|
||||||
|
mut team_row := vesting_sheet.row_new(
|
||||||
|
name: 'team_vesting'
|
||||||
|
tags: 'category:team type:vesting'
|
||||||
|
descr: 'Team token vesting schedule'
|
||||||
|
)!
|
||||||
|
sim.apply_vesting_schedule(mut team_row, team_tokens, sim.team_vesting)!
|
||||||
|
|
||||||
|
// Treasury vesting
|
||||||
|
treasury_tokens := sim.total_supply * sim.treasury_pct
|
||||||
|
mut treasury_row := vesting_sheet.row_new(
|
||||||
|
name: 'treasury_vesting'
|
||||||
|
tags: 'category:treasury type:vesting'
|
||||||
|
descr: 'Treasury token vesting schedule'
|
||||||
|
)!
|
||||||
|
sim.apply_vesting_schedule(mut treasury_row, treasury_tokens, sim.treasury_vesting)!
|
||||||
|
|
||||||
|
// Investor rounds vesting
|
||||||
|
for round in sim.investor_rounds {
|
||||||
|
round_tokens := sim.total_supply * round.allocation_pct
|
||||||
|
mut round_row := vesting_sheet.row_new(
|
||||||
|
name: '${round.name}_vesting'
|
||||||
|
tags: 'category:investor round:${round.name} type:vesting'
|
||||||
|
descr: '${round.name} investor vesting schedule'
|
||||||
|
)!
|
||||||
|
sim.apply_vesting_schedule(mut round_row, round_tokens, round.vesting)!
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create total unlocked row
|
||||||
|
mut total_row := vesting_sheet.group2row(
|
||||||
|
name: 'total_unlocked'
|
||||||
|
include: ['type:vesting']
|
||||||
|
tags: 'summary type:total_vesting'
|
||||||
|
descr: 'Total tokens unlocked over time'
|
||||||
|
aggregatetype: .sum
|
||||||
|
)!
|
||||||
|
|
||||||
|
sim.vesting_sheet = vesting_sheet
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (sim Simulation) apply_vesting_schedule(mut row spreadsheet.Row, total_tokens f64, schedule VestingSchedule) ! {
|
||||||
|
monthly_unlock := total_tokens / f64(schedule.vesting_months)
|
||||||
|
|
||||||
|
for month in 0 .. row.sheet.nrcol {
|
||||||
|
if month < schedule.cliff_months {
|
||||||
|
// Before cliff, no tokens unlocked
|
||||||
|
row.cells[month].val = 0
|
||||||
|
} else if month < schedule.cliff_months + schedule.vesting_months {
|
||||||
|
// During vesting period
|
||||||
|
months_vested := month - schedule.cliff_months + 1
|
||||||
|
row.cells[month].val = monthly_unlock * f64(months_vested)
|
||||||
|
} else {
|
||||||
|
// After vesting complete
|
||||||
|
row.cells[month].val = total_tokens
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user