Bonding Curve Implementation Issue: Price Calculation Overflow

Problem Description

I’m implementing a bonding curve for pass pricing in my protocol and encountering two issues:

  1. Arithmetic Overflow: The multiplication in the price calculation is causing overflow errors in Move
  • Price Suppression: When fixing the overflow, the resulting prices are too low

Current Implementation

Python Reference (Working as Expected)

def get_price(supply, amount):
    # Constants
    DEFAULT_WEIGHT_A = 30000000  # 0.3 * 10^8
    DEFAULT_WEIGHT_B = 20000000  # 0.2 * 10^8
    DEFAULT_WEIGHT_C = 2
    WAD = 100000000  # 10^8
    INITIAL_PRICE = 1 * WAD

    # Add adjustment factor to supply
    adjusted_supply = supply + DEFAULT_WEIGHT_C
    if adjusted_supply == 0:
        return INITIAL_PRICE

    # Calculate summations
    n1 = adjusted_supply - 1
    sum1 = (n1 * adjusted_supply * (2 * n1 + 1)) // 6
    
    n2 = n1 + amount
    sum2 = (n2 * (adjusted_supply + amount) * (2 * n2 + 1)) // 6
    
    # Calculate final price
    summation = DEFAULT_WEIGHT_A * (sum2 - sum1)
    price = ((DEFAULT_WEIGHT_B * summation) // WAD * INITIAL_PRICE) // WAD
    
    return max(price, INITIAL_PRICE)

MOVE Implementation (Causing Overflow)

public fun calculate_price(supply: u64, amount: u64, is_sell: bool): u64 acquires Config {
    let config = borrow_global<Config>(@podium);

    let adjusted_supply = supply + config.weight_c;
    if (adjusted_supply == 0) {
        return INITIAL_PRICE
    };

    let n1 = adjusted_supply - 1;
    let sum1 = (n1 * adjusted_supply * (2 * n1 + 1)) / 6;
    
    let n2 = n1 + amount;
    let sum2 = (n2 * (adjusted_supply + amount) * (2 * n2 + 1)) / 6;
    
    let summation = config.weight_a * (sum2 - sum1);
    let price = (config.weight_b * summation * INITIAL_PRICE) / (WAD * WAD);
    
    if (price < INITIAL_PRICE) {
        INITIAL_PRICE
    } else {
        price
    }
}

Expected Price Progression
First 10 passes: ~$1-15 range
First 50 passes: ~$15-75 range
First 100 passes: ~$75-200 range
Questions
What’s the best way to handle the multiplication overflow in Move while maintaining the desired price curve?
Should we be using different scaling factors or a different order of operations?
Are there any Move-specific patterns for handling large number calculations in bonding curves?
Additional Context
Using WAD = 100000000 (10^8) for decimal scaling
Initial price is set to 1 MOVE (100000000 in internal units)
The weights are currently:
weight_a = 30000000 (0.3 10^8)
weight_b = 20000000 (0.2 10^8)
weight_c = 2
Any insights on implementing this correctly in Move would be greatly appreciated!

2 Likes

I’ll take a look through the codebase and get back to you but just some initial thoughts:

looks like the summation can get very large very very quickly a u64 holds 2^64 - 1 and your current summation can blow through that before you do your division. This might need some rethinking - maybe if possible doing the division earlier?

2 Likes

This issue I had was that when I did the division earlier it seemed to suppress the price. I will try and find my prior attempt.

When performing multiplication with high values in Move, it’s safer to use type annotations for your variables. Ensure that both variables involved in the multiplication are of the same type (e.g., u64 multiplying u64) to avoid unexpected type errors or overflows.

1 Like
    let config = borrow_global<Config>(@podium);

    // Add adjustment factor to supply
    let adjusted_supply: u64 = supply + config.weight_c;
    if (adjusted_supply == 0) {
        return INITIAL_PRICE
    };

    // Calculate first summation in parts to prevent overflow
    let n1: u64 = adjusted_supply - 1;
    let sum1_part1: u64 = n1 * adjusted_supply;
    let sum1_part2: u64 = (2 * n1 + 1);
    let sum1: u64 = (sum1_part1 * sum1_part2) / 6;
    
    // Calculate second summation in parts
    let n2: u64 = n1 + amount;
    let sum2_part1: u64 = n2 * (adjusted_supply + amount);
    let sum2_part2: u64 = (2 * n2 + 1);
    let sum2: u64 = (sum2_part1 * sum2_part2) / 6;
    
    // Calculate price with intermediate scaling
    let summation_diff: u64 = sum2 - sum1;
    
    // Scale down early to prevent overflow
    let scaled_weight_a: u64 = config.weight_a / WAD;
    let scaled_weight_b: u64 = config.weight_b / WAD;
    
    let summation: u64 = scaled_weight_a * summation_diff;
    let price: u64 = scaled_weight_b * summation * INITIAL_PRICE;
    
    if (price < INITIAL_PRICE) {
        INITIAL_PRICE
    } else {
        price
    }
}

the above ended in failure because it was scaling prices down too fast.

Supply 0: Price = 0 MOVE
Supply 1000000: Price = 0 MOVE
Supply 500000: Price = 0 MOVE

I then tried to just reduce the values of the initial inputs to hopefully keep it in more control:

// Constants
const INPUT_SCALE: u64 = 1000; // 10^3 instead of 10^8
const WAD: u64 = 100000000; // 10^8 remains same for price

public fun calculate_price(supply: u64, amount: u64, is_sell: bool): u64 acquires Config {
    let config = borrow_global<Config>(@podium);

    // Add adjustment factor to supply
    let adjusted_supply: u64 = supply + config.weight_c;
    if (adjusted_supply == 0) {
        return INITIAL_PRICE
    };

    // Calculate first summation in parts to prevent overflow
    let n1: u64 = adjusted_supply - 1;
    
    // Scale down early to prevent overflow
    let scaled_n1: u64 = n1 / INPUT_SCALE;
    let scaled_supply: u64 = adjusted_supply / INPUT_SCALE;
    
    // Calculate first sum with scaled values
    let sum1: u64 = (scaled_n1 * scaled_supply * (2 * scaled_n1 + 1)) / 6;
    
    // Calculate second summation with scaled values
    let scaled_amount: u64 = amount / INPUT_SCALE;
    let n2: u64 = scaled_n1 + scaled_amount;
    let sum2: u64 = (n2 * (scaled_supply + scaled_amount) * (2 * n2 + 1)) / 6;
    
    // Calculate summation difference
    let summation_diff: u64 = sum2 - sum1;
    
    // Apply weights in parts with intermediate scaling
    let step1: u64 = (summation_diff * (config.weight_a / INPUT_SCALE)) / WAD;
    let step2: u64 = (step1 * (config.weight_b / INPUT_SCALE)) / WAD;
    
    // Scale up the final result
    let price: u64 = step2 * INITIAL_PRICE;
    
    debug::print(&string::utf8(b"Price calculation steps:"));
    debug::print(&string::utf8(b"Summation diff:"));
    debug::print(&summation_diff);
    debug::print(&string::utf8(b"Step 1:"));
    debug::print(&step1);
    debug::print(&string::utf8(b"Step 2:"));
    debug::print(&step2);
    debug::print(&string::utf8(b"Final price:"));
    debug::print(&price);

    if (price < INITIAL_PRICE) {
        INITIAL_PRICE
    } else {
        price
    }
}

this one seemed to pass my tests. However, the scale is off. I may be able to work with this though. These are the value outputs:

Supply 0: Price = 1.00 MOVE
Supply 1: Price = 2.00 MOVE
Supply 2: Price = 14.00 MOVE
Supply 3: Price = 1.70 MOVE (sell price)

This is what I expected:

Purchase 1: Price = 1.00 MOVE
Purchase 2: Price = 2.25 MOVE
Purchase 3: Price = 4.00 MOVE
Purchase 4: Price = 6.25 MOVE
Purchase 5: Price = 9.00 MOVE
Purchase 6: Price = 12.25 MOVE

These are the changes I made:


// Scale down early to prevent overflow
let scaled_n1: u64 = n1 / INPUT_SCALE;        // Using 10^3 instead of 10^8
let scaled_supply: u64 = adjusted_supply / INPUT_SCALE;

I am used a larger scale for the initial inputs to get the curve more in alignment.

then these are the middle calculations

// Calculate first sum with scaled values
let sum1: u64 = (scaled_n1 * scaled_supply * (2 * scaled_n1 + 1)) / 6;

// Calculate second summation with scaled values
let scaled_amount: u64 = amount / INPUT_SCALE;
let n2: u64 = scaled_n1 + scaled_amount;
let sum2: u64 = (n2 * (scaled_supply + scaled_amount) * (2 * n2 + 1)) / 6;
  • Used scaled values throughout the calculations

  • Break down multiplications into smaller steps

  • Divide early rather than letting numbers grow

Weight Application:

// Apply weights in parts with middle scaling
let step1: u64 = (summation_diff * (config.weight_a / INPUT_SCALE)) / WAD;
let step2: u64 = (step1 * (config.weight_b / INPUT_SCALE)) / WAD;
  • Scale down weights before multiplication

  • Apply WAD (10^8) scaling after multiplication

  • Break weight application into steps

1 Like