Rate Limits

How to implement rate limits?

Interactive Simulation

Initial state:

Edit state changes:

Current state changes:

Implementation

const RATE_LIMIT = hourlyRateLimit;
const RATE_LIMIT_K2 = hourlyRateLimitK2;

const state = {
  valuesRaw: {
    carbon:        1_100, // tCO2eq
    kvcmAlloc:   200_000, // kVCM
    k2Alloc:     200_000, // K2
    kvcmTotal: 1_000_000, // kVCM
    k2Total:   1_000_000, // K2
  },
  snapshots: {
    carbon:    { value:     1_100, time: 0, rate: RATE_LIMIT },
    kvcmAlloc: { value:   200_000, time: 0, rate: RATE_LIMIT },
    k2Alloc:   { value:   200_000, time: 0, rate: RATE_LIMIT_K2 },
    kvcmTotal: { value: 1_000_000, time: 0, rate: RATE_LIMIT },
    k2Total:   { value: 1_000_000, time: 0, rate: RATE_LIMIT_K2 },
  },
  time: 0,                // hours
};
const carbonDelta = +100; // tCO2eq
const time = 24;          // hours

function executeSwap(state, carbonDelta, time) {
  const { valuesRaw, snapshots } = state;
  const valuesEffective = computeEffectiveValues(valuesRaw, snapshots, time);
  const kvcmDelta = computePriceSwap(valuesEffective, carbonDelta);
  const stateNew = {
    valuesRaw: {
      ...valuesRaw,
      carbon: valuesRaw.carbon + carbonDelta,
      kvcmTotal: valuesRaw.kvcmTotal + kvcmDelta,
    },
    snapshots,
    time,
  };
  return stateNew;
}

function executeRetire(state, carbonDelta, time) {
  const { valuesRaw, snapshots } = state;
  const valuesEffective = computeEffectiveValues(valuesRaw, snapshots, time);
  const kvcmDelta = computePriceRetire(valuesRaw, carbonDelta);
  const stateNew = {
    valuesRaw: {
      ...valuesRaw,
      carbon: valuesRaw.carbon + carbonDelta,
      kvcmTotal: valuesRaw.kvcmTotal + kvcmDelta,
    },
    snapshots: {
      ...snapshots,
      carbon: maybeUpdatedSnapshot(
        snapshots.carbon,
        valuesEffective.carbon,
        valuesRaw.carbon,
        time,
      ),
      kvcmTotal: maybeUpdatedSnapshot(
        snapshots.kvcmTotal,
        valuesEffective.kvcmTotal,
        valuesRaw.kvcmTotal,
        time,
      ),
    },
    time,
  };
  return stateNew;
}

function executeChange(state, name, delta, time) {
  if (name === "carbon") {
    throw new Error("Use `executeSwap` or `executeRetire` to change `carbon`");
  }
  const { valuesRaw, snapshots } = state;
  const valueEffective = computeEffectiveValue(
    name,
    valuesRaw,
    snapshots,
    time,
  );
  const stateNew = {
    valuesRaw: { ...valuesRaw, [name]: valuesRaw[name] + delta },
    snapshots: {
      ...snapshots,
      [name]: maybeUpdatedSnapshot(
        snapshots[name],
        valueEffective,
        valuesRaw[name],
        time,
      ),
    },
    time,
  };
  return stateNew;
}

function computeEffectiveValues(valuesRaw, snapshots, time) {
  const valuesEffective = {};
  for (const name in valuesRaw) {
    valuesEffective[name] = computeEffectiveValue(
      name,
      valuesRaw,
      snapshots,
      time,
    );
  }
  return valuesEffective;
}

function computeEffectiveValue(name, valuesRaw, snapshots, time) {
  let valueEffective;
  if (name === "carbon" || name === "kvcmTotal" || name === "k2Total") {
    valueEffective = rateLimitedInv(valuesRaw[name], snapshots[name], time);
  } else {
    valueEffective = rateLimited(valuesRaw[name], snapshots[name], time);
  }
  return valueEffective;
}

function rateLimited(valueRaw, snapshot, time) {
  const timeDelta = time - snapshot.time;
  // Can use fast and inaccurate exp implementation here to reduce cost
  const valueLimit = snapshot.value / Math.exp(-snapshot.rate * timeDelta);
  const valueEffective = Math.min(valueRaw, valueLimit);
  return valueEffective;
}

function rateLimitedInv(valueRaw, snapshot, time) {
  const timeDelta = time - snapshot.time;
  // Can use fast and inaccurate exp implementation here to reduce cost
  const valueLimit = snapshot.value * Math.exp(-snapshot.rate * timeDelta);
  const valueEffective = Math.max(valueRaw, valueLimit);
  return valueEffective;
}

function maybeUpdatedSnapshot(snapshot, valueEffective, valueRaw, time) {
  if (valueEffective === valueRaw) {
    return { value: valueRaw, time: time, rate: snapshot.rate };
  }
  return snapshot;
}