DeploySEALED
╲$●@$~╱◆╱▫~▫*##░◇╱!◇○!^^□?%╲▫□■^%╱█^■▒■□$&▓%◆?*~▒▓#○◆╱^╳█╳▪▒░~!◇
Transaction ID
Execution Fee
0.00000219 FLOWTransaction Summary
DeployContract deployment
Contract deployment
Script Arguments
0nameString
FlowRewardsModels
1codeString
import FlowRewards from 0xa45ead1cf1ca9eda
import FlowRewardsRegistry from 0xa45ead1cf1ca9eda
import Clock from 0xa45ead1cf1ca9eda
/// This contract defines reward boost and distribution models used in the FlowRewards contract
///
access(all) contract FlowRewardsModels {
/// Creates a new MultilinearBoostModel with the provided stages
///
/// @param stages: The stages defining the boost and time boundaries for boost. These should be contiguous and
/// ordered by start time - a condition enforced in resource init
///
/// @return The newly created MultilinearBoostModel
///
access(all) fun createBoostModel(stages: [Stage]): @{FlowRewards.BoostModel} {
return <- create MultilinearBoostModel(stages: stages)
}
access(all) fun createDistributionModel(start: UFix64, end: UFix64): @{FlowRewards.DistributionModel} {
return <- create LinearDistributionModel(start: start, end: end)
}
/// Each stage defines a start and end time and a boost applicable for that period
///
access(all) struct Stage {
/// The reward boost factor for this stage
access(all) let boost: UFix64
/// The start timestamp boundary for this stage
access(all) let start: UFix64
/// The end timestamp boundary for this stage
access(all) let end: UFix64
init(boost: UFix64, start: UFix64, end: UFix64) {
pre {
start < end: "Start must be before end"
}
self.boost = boost
self.start = start
self.end = end
}
/// Calculates the boost amount for a given lock amount amount over this stage
///
/// @param lockAmount: The lock amount amount to calculate boost amount against
/// @param start: The start time which the lock amount should begin boosting. If this exceeds the stage end, no
/// boost amount will be calculated
/// @param upTo: The end time up to which the lock amount should boost. If this precedes the stage start, no
/// boost amount will be calculated. If this exceeds the stage end, boost will be calculated up to the stage
// end
///
/// @return The boost amount for the lock amount over this stage
///
access(all) fun calculateBoostAmount(lockAmount: UFix64, start: UFix64, upTo: UFix64): UFix64 {
// Return early if defined bounds do not overlap with this stage
if upTo <= self.start || self.end <= start {
return 0.0
}
let boostStart = start < self.start ? self.start : start
let boostEnd = upTo < self.end ? upTo : self.end
if boostEnd <= boostStart {
return 0.0
}
let time = boostEnd - boostStart
let boostAmount = lockAmount * self.boost
let proportionalTime = time / 31_536_000.0
return boostAmount * proportionalTime
}
}
/// The MultilinearBoostModel defines a series of stages, each with fixed boosts and calculates rewards over those
/// stages
///
access(all) resource MultilinearBoostModel : FlowRewards.BoostModel {
/// The stages defining the long-term boost and time boundaries for boost calculation
access(all) let stages: [Stage]
init(stages: [Stage]) {
pre {
stages.length > 0: "Must have at least one stage"
}
for i, stage in stages {
if i < stages.length - 1 {
assert(stage.end == stages[i + 1].start, message: "Stages must be contiguous")
}
}
self.stages = stages
}
/// Returns the start time of the first stage
///
/// @return The start timestamp of the first stage
///
access(all) fun getBoostStart(): UFix64 {
return self.stages[0].start
}
/// Returns the end time of the last stage
///
/// @return The end timestamp of the last stage
///
access(all) fun getBoostEnd(): UFix64 {
return self.stages[self.stages.length - 1].end
}
/// Returns the boost factor for the model at the start of the boost period
///
/// @return The starting boost factor for the model
///
access(all) fun getStartingBoostFactor(): UFix64 {
return self.getBoostFactor(atTime: self.getBoostStart())
}
/// Calculates the boost factor for a lockup executed at the specified time
///
/// @param start: The time at which the lockup was executed
///
/// @return The boost factor for the lockup executed at the specified time
///
access(all) fun getBoostFactor(atTime: UFix64?): UFix64 {
let atTime = atTime ?? Clock.time()
let modelEnd = self.getBoostEnd()
// Cannot boost after model end, return 0.0
if modelEnd <= atTime {
return 0.0
}
let modelStart = self.getBoostStart()
// If the lockup occurred before the model start, boost starts at the model start
let boostStart = atTime <= modelStart ? modelStart : atTime
let amount = 1_000.0
var boostAmount = 0.0
boostAmount = self._calculateBoostAmount(amount: amount, start: boostStart, upTo: modelEnd)
// Determine the boost factor relative to the lock amount of 1_000.0
return boostAmount / amount
}
/// Calculates the boost amount for a given summary up to a specified time
///
/// @param summary: The reward summary to calculate boost amount for
/// @param upTo: The end time up to which to calculate boost. If nil, the current block timestamp is used. If
/// the specified time is before the model start, no boost amount will be calculated. If the specified time
/// is beyond the model end, the boost will be calculated up to the model end
///
/// @return The total boost amount for the summary up to the specified time
///
access(all) fun calculateBoostAmount(
summary: &{FlowRewardsRegistry.Summary},
upTo: UFix64?
): UFix64 {
let modelStart = self.getBoostStart()
let modelEnd = self.getBoostEnd()
var boostEnd = upTo ?? Clock.time()
// Return early if boost period has not started as specified
if boostEnd <= modelStart {
return 0.0
}
// Bound the boost end to the model end if requested threshold is beyond the model end
boostEnd = modelEnd < boostEnd ? modelEnd : boostEnd
var total = 0.0
for lockup in summary.lockups {
let whenLocked = lockup.timestamp
// If the lockup occurred after the model end, skip it
if modelEnd <= whenLocked { continue }
// If the lockup occurred before the model start, boost starts at the model start
let boostStart = modelStart >= whenLocked ? modelStart : whenLocked
total = total + self._calculateBoostAmount(amount: lockup.amount,start: boostStart, upTo: boostEnd)
}
return total
}
/// Calculates the boost for a given lock amount amount up to a specified time iterating over all stages
///
access(self) fun _calculateBoostAmount(amount: UFix64, start: UFix64, upTo: UFix64): UFix64 {
var total = 0.0
// Iterate over stages to calculate boost amount over each stage's boost
for i, stage in self.stages {
// Add the boost amount for this stage to the total
total = total + stage.calculateBoostAmount(
lockAmount: amount,
start: start,
upTo: upTo
)
if upTo <= stage.end {
break
}
}
return total
}
}
/// This resource defines a linear distribution model of locked + rewarded FLOW over a specified time period
///
access(all) resource LinearDistributionModel : FlowRewards.DistributionModel {
/// The start time of the distribution period
access(all) let start: UFix64
/// The end time of the distribution period
access(all) let end: UFix64
init(start: UFix64, end: UFix64) {
pre {
start < end: "Start must be before end"
}
self.start = start
self.end = end
}
/// Returns the time at which the distribtuion model starts distributing funds
///
/// @return The start time of the distribution period
///
access(all) fun getDistributionStart(): UFix64 {
return self.start
}
/// Returns the time at which the distribtuion model stops distributing funds
///
/// @return The end time of the distribution period
///
access(all) fun getDistributionEnd(): UFix64 {
return self.end
}
/// Returns the amount that can be distributed at a given time according to the distribution period defined in
/// this model at a linear rate
/// NOTE: Any implementing contracts should be defined in this contract account as the summary is passed by
/// reference
///
/// @param maxDistribution: The maximum amount of the same denomination that can be distributed
/// @param atTime: The time at which to calculate the distribution. If nil, the current block timestamp is used
/// If the specified time is before the model start, no distribution will be calculated. If the specified
/// time is beyond the model end, the distribution will be calculated up to the model end
///
/// @return The total amount of locked and/or rewarded FLOW that can be distributed at the specified time
///
access(all) fun calculateDistribution(
maxDistribution: UFix64,
atTime: UFix64?
): UFix64 {
let distributionStart = self.getDistributionStart()
let distributionEnd = self.getDistributionEnd()
var distributionTime = atTime ?? Clock.time()
// Return early if distribution period has not started as specified
if distributionTime < distributionStart {
return 0.0
} else if distributionTime >= distributionEnd {
// If the distribution period has ended, return the max distribution
return maxDistribution
}
// Calculate how far into the distribution period we are as a percentage at distributionTime
let distributionPeriod = distributionEnd - distributionStart
let timeElapsed = distributionTime - distributionStart
let distributionPercentage = timeElapsed / distributionPeriod
// Return the proportion of maxDistribution that can be distributed at this time
return maxDistribution * distributionPercentage
}
}
}
Cadence Script
1transaction(name: String, code: String ) {
2 prepare(signer: AuthAccount) {
3 signer.contracts.add(name: name, code: code.utf8 )
4 }
5 }