[
"autonomous agent",
{
"doc_url": "https://ostable.org/bonded-stablecoin.json",
"getters": "{
$get_reserve = ($s1, $s2) => {
$r = $s1^params.m * $s2^params.n;
$r
};
$get_p2 = ($s1, $s2) => {
// $p2 = ($get_reserve($s1, $s2 + 0.001*$s2) - $get_reserve($s1, $s2))/(0.001*$s2); // derivative
$p2 = $s1^params.m * params.n * $s2^(params.n-1); // derivative
$p2
};
$get_oracles = () => {
$oracles = var['oracles'];
if ($oracles)
return $oracles;
$initial_oracles = [];
if (params.oracle1 AND params.feed_name1)
$initial_oracles[] = {oracle: params.oracle1, feed_name: params.feed_name1, op: params.op1 OTHERWISE '*'};
if (params.oracle2 AND params.feed_name2)
$initial_oracles[] = {oracle: params.oracle2, feed_name: params.feed_name2, op: params.op2 OTHERWISE '*'};
if (params.oracle3 AND params.feed_name3)
$initial_oracles[] = {oracle: params.oracle3, feed_name: params.feed_name3, op: params.op3 OTHERWISE '*'};
$initial_oracles
};
// leverage
// 0: track the oracle price, e.g. USD
// 1: track the reserve price, e.g. GBYTE
// 2: take 2x long position in the reserve asset relative to the oracle price
// -1: take short position in the reserve asset relative to the oracle price
// -2: take 2x short position in the reserve asset relative to the oracle price
// fractional leverage is also ok
$get_leverage = () => params.leverage OTHERWISE 0;
$get_fee_multiplier = () => var['fee_multiplier'] OTHERWISE params.fee_multiplier OTHERWISE 5;
$get_initial_interest_rate = () => exists(params.interest_rate) ? params.interest_rate : 0.1; // 10%
$get_interest_rate = () => {
$interest_rate_var = var['interest_rate'];
exists($interest_rate_var) ? $interest_rate_var : $get_initial_interest_rate()
};
// how much is moved each time
$get_moved_capacity_share = () => var['moved_capacity_share'] OTHERWISE params.moved_capacity_share OTHERWISE 0.1;
// timeout between movements of capacity from slow to fast pool
$get_move_capacity_timeout = () => var['move_capacity_timeout'] OTHERWISE params.move_capacity_timeout OTHERWISE 2*3600;
// what part of slow capacity is moved to SF
$get_sf_capacity_share = () => {
$sf_capacity_share_var = var['sf_capacity_share'];
exists($sf_capacity_share_var) ? $sf_capacity_share_var : (params.sf_capacity_share OTHERWISE 0)
};
$get_slow_capacity_share = () => {
$slow_capacity_share_var = var['slow_capacity_share'];
if (exists($slow_capacity_share_var))
$slow_capacity_share = $slow_capacity_share_var;
else if (exists(params.slow_capacity_share))
$slow_capacity_share = params.slow_capacity_share;
else
$slow_capacity_share = 0.5;
$slow_capacity_share
};
$get_growth_factor = () => {
$interest_rate = $get_interest_rate();
$term = (timestamp - var['rate_update_ts']) / (360 * 24 * 3600); // in years
$growth_factor = var['growth_factor'] * (1 + $interest_rate)^$term;
$growth_factor
};
$get_fee = ($avg_reserve, $old_distance, $new_distance) => {
$fee_multiplier = $get_fee_multiplier();
// capacity = fee_multiplier * reserve * distance^2
$fee = ceil($fee_multiplier * $avg_reserve * ($new_distance - $old_distance) * ($new_distance + $old_distance));
// $fee = ceil($fee_multiplier * $avg_reserve * ($new_distance - $old_distance));
// $fee = ceil($abs_reserve_delta * ($new_distance + $old_distance) / 2 * $fee_multiplier);
$fee
};
$get_oracle_price = () => {
$oracles = $get_oracles();
$oracle_price = reduce($oracles, 3, ($price, $oracle_info) => {
if (!exists($price))
return false;
$df = data_feed[[oracles=$oracle_info.oracle, feed_name=$oracle_info.feed_name, ifnone=false]];
if (!exists($df))
return false;
($oracle_info.op == '*') ? $price * $df : $price / $df
}, 1);
$oracle_price
};
$get_target_p2 = () => {
$oracle_price = $get_oracle_price();
if (!exists($oracle_price))
return false;
$target_p2 = $oracle_price^($get_leverage() - 1) * $get_growth_factor();
$target_p2
};
$get_turnover = ($reserve_payout, $tokens1, $tokens2, $p2) => {
// positive numbers are outputs, negative amounts are inputs
$reserve_turnover = abs($reserve_payout);
if ($tokens1 >= 0 AND $tokens2 >= 0 OR $tokens1 <= 0 AND $tokens2 <= 0)
return $reserve_turnover;
$token2_turnover = abs($tokens2) * $p2 * 10^(params.reserve_asset_decimals - params.decimals2);
if ($tokens2 >= 0 AND $reserve_payout >= 0 OR $tokens2 <= 0 AND $reserve_payout <= 0)
return $token2_turnover + $reserve_turnover;
$token2_turnover
};
$get_distance = ($p2, $target_p2) => (exists($p2) AND exists($target_p2)) ? abs($p2 - $target_p2) / min($p2, $target_p2) : 0;
$get_exchange_result = ($tokens1, $tokens2) => {
$slow_capacity_share = $get_slow_capacity_share();
$fast_capacity_share = 1 - $slow_capacity_share;
$initial_p2 = var['p2'];
$target_p2 = $get_target_p2();
$distance = $get_distance($initial_p2, $target_p2);
$reserve = var['reserve'];
if (!$reserve AND ($tokens1 <= 0 OR $tokens2 <= 0))
bounce("initial mint must be with both tokens");
$new_supply1 = var['supply1'] + $tokens1;
$new_supply2 = var['supply2'] + $tokens2;
$s1 = $new_supply1 / 10^params.decimals1;
$s2 = $new_supply2 / 10^params.decimals2;
$r = $get_reserve($s1, $s2);
$p2 = $get_p2($s1, $s2);
$new_reserve = ceil($r * 10^params.reserve_asset_decimals);
$reserve_delta = $new_reserve - $reserve; // can be negative
if ($tokens1 >= 0 AND $tokens2 >= 0 AND $reserve_delta < 0)
bounce("issuing tokens while the reserve decreases?");
if ($tokens1 <= 0 AND $tokens2 <= 0 AND $reserve_delta > 0)
bounce("burning tokens while the reserve increases?");
$new_distance = $get_distance($p2, $target_p2);
$avg_reserve = ($reserve + $new_reserve) / 2;
$fast_capacity = var['fast_capacity'];
if ($distance == 0 AND $new_distance == 0){
$fee = 0;
$reward = 0;
$reserve_needed = $reserve_delta;
}
else if ($new_distance >= $distance){ // going away from the target price - pay a fee
$reward = 0;
$regular_fee = $get_fee($avg_reserve, $distance, $new_distance);
$new_fast_capacity = $fast_capacity + $regular_fee * $fast_capacity_share;
$distance_share = 1 - $distance/$new_distance;
// reward that would be paid for returning the price back to $initial_p2
$reverse_reward = $distance_share * $new_fast_capacity;
if ($regular_fee >= $reverse_reward)
$fee = $regular_fee;
else
$fee = ceil($distance_share / (1 - $distance_share * $fast_capacity_share) * $fast_capacity);
$reserve_needed = $reserve_delta + $fee; // negative for payouts
}
else { // going towards the target price - get a reward
$fee = 0;
$regular_reward = floor((1 - $new_distance/$distance) * $fast_capacity);
if (params.capped_reward){
// if the reward would be greater than the fee for the reverse transaction, cap the reward by the fee
$reverse_fee = $get_fee($avg_reserve, $new_distance, $distance);
$reward = min($regular_reward, $reverse_fee);
}
else
$reward = $regular_reward;
$reserve_needed = $reserve_delta - $reward; // negative for payouts
}
$turnover = $get_turnover(-$reserve_delta, $tokens1, $tokens2, $p2);
$fee_percent = $fee/$turnover*100;
{
reserve_needed: $reserve_needed,
reserve_delta: $reserve_delta,
fee: $fee,
regular_fee: $regular_fee,
reward: $reward,
initial_p2: $initial_p2,
p2: $p2,
target_p2: $target_p2,
new_distance: $new_distance,
turnover: $turnover,
fee_percent: $fee_percent,
slow_capacity_share: $slow_capacity_share,
}
};
}",
"init": "{
$fund_base_aa = '5WOTEURNL2XGGKD2FGM5HEES4NKVCBCR';
$decision_engine_base_aa = params.decision_engine_base_aa OTHERWISE '625UKTER5WR5JQPQYS7CU4ST2EXFUCDG';
$governance_base_aa = 'LXHUYEV6IHBCTGMFNSWRBBU7DGR3JTIY';
$aa2aa_bytes = 2000;
$de_bytes = 3000;
// peg
$allow_oracle_change = params.allow_oracle_change;
// curve
if (!exists(params.m) OR !exists(params.n))
bounce("curve not defined");
// fee and capacitor
$threshold_distance = var['threshold_distance'] OTHERWISE params.threshold_distance OTHERWISE 0.01; // 1% deviation from the peg
// reserve
$reserve_asset = params.reserve_asset OTHERWISE 'base';
if (!exists(params.reserve_asset_decimals))
bounce('no reserve_asset_decimals');
$min_contribution = ($reserve_asset == 'base') ? 99999 : 0;
$network_fee = ($reserve_asset == 'base') ? 4000 : 0; // for fees and pinging the DE
// tokens
if (!exists(params.decimals1))
bounce('no decimals1');
if (!exists(params.decimals2))
bounce('no decimals2');
$asset1 = var['asset1'];
$asset2 = var['asset2'];
$ready = $asset1 AND $asset2;
if ($ready)
$lost_peg_ts = var['lost_peg_ts'];
$governance_aa = var['governance_aa'];
$fund_aa = var['fund_aa'];
}",
"messages": {
"cases": [
{
"if": "{ trigger.data.define AND (!$asset1 OR !$asset2) }",
"init": "{
$step = !$asset1 ? 1 : 2;
if ($step == 1){
$fund_aa_definition = [
'autonomous agent',
{
base_aa: $fund_base_aa,
params: {
curve_aa: this_address
}
}
];
$fund_aa_address = chash160($fund_aa_definition);
$decision_engine_aa_definition = [
'autonomous agent',
{
base_aa: $decision_engine_base_aa,
params: {
curve_aa: this_address,
below_peg_threshold: params.below_peg_threshold,
below_peg_timeout: params.below_peg_timeout,
min_reserve_delta: params.min_reserve_delta,
}
}
];
$decision_engine_aa_address = chash160($decision_engine_aa_definition);
}
}",
"messages": [
{
"app": "asset",
"payload": {
"is_private": false,
"is_transferrable": true,
"auto_destroy": false,
"fixed_denominations": false,
"issued_by_definer_only": true,
"cosigned_by_definer": false,
"spender_attested": false
}
},
{
"if": "{trigger.data.factory AND $step == 1}",
"app": "data",
"payload": {
"define": 1,
"factory": "{trigger.data.factory}"
}
},
{
"if": "{trigger.data.factory AND $step == 1}",
"app": "payment",
"payload": {
"asset": "base",
"outputs": [
{
"address": "{$fund_aa_address}",
"amount": 6000
}
]
}
},
{
"if": "{trigger.data.factory AND $step == 2}",
"app": "data",
"payload": {
"asset1": "{$asset1}"
}
},
{
"if": "{trigger.data.factory AND $step == 2}",
"app": "payment",
"payload": {
"asset": "base",
"outputs": [
{
"address": "{trigger.data.factory}",
"amount": 500
}
]
}
},
{
"if": "{$step == 1}",
"app": "definition",
"payload": {
"definition": "{$fund_aa_definition}"
}
},
{
"if": "{$step == 1}",
"app": "definition",
"payload": {
"definition": "{$decision_engine_aa_definition}"
}
},
{
"if": "{$step == 2}",
"app": "definition",
"payload": {
"definition": [
"autonomous agent",
{
"base_aa": "{$governance_base_aa}",
"params": {
"curve_aa": "{this_address}",
"regular_challenging_period": "{params.regular_challenging_period}",
"important_challenging_period": "{params.important_challenging_period}",
"freeze_period": "{params.freeze_period}",
"proposal_min_support": "{params.proposal_min_support}"
}
}
]
}
},
{
"app": "state",
"state": "{
var['asset' || $step] = response_unit;
response['asset' || $step] = response_unit;
if ($step == 1){
var['fund_aa'] = $fund_aa_address;
var['decision_engine_aa'] = $decision_engine_aa_address;
}
if ($step == 2){
var['governance_aa'] = unit[response_unit].messages[[.app='definition']].payload.address;
var['rate_update_ts'] = timestamp;
var['growth_factor'] = 1;
var['interest_rate'] = $get_initial_interest_rate();
}
}"
}
]
},
{
"if": "{ $ready AND trigger.data.move_capacity }",
"init": "{
$initial_p2 = var['p2'];
$target_p2 = $get_target_p2();
$distance = $get_distance($initial_p2, $target_p2);
if ($lost_peg_ts){
if ($distance > $threshold_distance AND timestamp > $lost_peg_ts + $get_move_capacity_timeout()){
$moved_amount = floor($get_moved_capacity_share() * var['slow_capacity']);
$sf_amount = floor($get_sf_capacity_share() * $moved_amount);
$fast_amount = $moved_amount - $sf_amount;
$bLostNow = true; // restart the countdown to the next movement
response['amount'] = $moved_amount;
}
else if ($distance <= $threshold_distance)
$bRestored = true;
}
else if ($distance > $threshold_distance)
$bLostNow = true;
}",
"messages": [
{
"if": "{$sf_amount}",
"app": "payment",
"payload": {
"asset": "{$reserve_asset}",
"outputs": [
{
"address": "{$fund_aa}",
"amount": "{$sf_amount}"
}
]
}
},
{
"app": "state",
"state": "{
if ($moved_amount)
var['slow_capacity'] -= $moved_amount;
if ($fast_amount)
var['fast_capacity'] += $fast_amount;
if ($bRestored OR $bLostNow)
var['lost_peg_ts'] = $bLostNow ? timestamp : false;
}"
}
]
},
{
"if": "{ $ready AND trigger.address == $governance_aa AND trigger.data.name }",
"init": "{
$name = trigger.data.name;
}",
"messages": [
{
"app": "state",
"state": "{
if ($name == 'oracles' AND !$allow_oracle_change)
bounce("changing the oracle is not allowed");
if ($name == 'interest_rate'){
var['growth_factor'] = $get_growth_factor();
var['rate_update_ts'] = timestamp;
}
if (starts_with($name, 'deposits.')){
$short_name = substring($name, length('deposits.'));
$deposit_params = var['deposit_params'] OTHERWISE {};
$deposit_params[$short_name] = trigger.data.value;
var['deposit_params'] = $deposit_params;
}
else
var[$name] = trigger.data.value;
}"
}
]
},
{
"if": "{ $ready AND (trigger.output[[asset=$reserve_asset]] > $min_contribution AND (trigger.data.tokens1 OR trigger.data.tokens2) OR trigger.output[[asset=$asset1]] > 0 OR trigger.output[[asset=$asset2]] > 0) }",
"init": "{
if (trigger.data.tokens1_to AND !is_valid_address(trigger.data.tokens1_to))
bounce("bad tokens1_to address");
if (trigger.data.tokens2_to AND !is_valid_address(trigger.data.tokens2_to))
bounce("bad tokens2_to address");
if (trigger.data.reserve_to AND !is_valid_address(trigger.data.reserve_to))
bounce("bad reserve_to address");
if (trigger.data.to AND !is_valid_address(trigger.data.to))
bounce("bad to address");
$tokens1_to = trigger.data.tokens1_to OTHERWISE trigger.data.to OTHERWISE trigger.address;
$tokens2_to = trigger.data.tokens2_to OTHERWISE trigger.data.to OTHERWISE trigger.address;
$reserve_to = trigger.data.reserve_to OTHERWISE trigger.data.to OTHERWISE trigger.address;
if (trigger.data.tokens1 AND (!is_integer(trigger.data.tokens1) OR trigger.data.tokens1 <= 0))
bounce("invalid number of tokens1");
if (trigger.data.tokens2 AND (!is_integer(trigger.data.tokens2) OR trigger.data.tokens2 <= 0))
bounce("invalid number of tokens2");
if (trigger.data.tokens1 AND trigger.output[[asset=$asset1]] > 0)
bounce("both tokens1 param and amount");
if (trigger.data.tokens2 AND trigger.output[[asset=$asset2]] > 0)
bounce("both tokens2 param and amount");
$tokens1 = trigger.data.tokens1 OTHERWISE -trigger.output[[asset=$asset1]];
$tokens2 = trigger.data.tokens2 OTHERWISE -trigger.output[[asset=$asset2]];
$bFund = (trigger.address == $fund_aa);
$bNotifyDE = (!$bFund OR trigger.data.notifyDE);
if ($tokens1 AND !$bFund)
bounce("only stability fund is allowed to transact in T1");
$tokens1_to_aa = $tokens1 > 0 AND $tokens1_to != trigger.address AND is_aa($tokens1_to);
$tokens2_to_aa = $tokens2 > 0 AND $tokens2_to != trigger.address AND is_aa($tokens2_to);
$full_network_fee = $network_fee + ($reserve_asset == 'base' ? ($tokens1_to_aa ? $aa2aa_bytes : 0) + ($tokens2_to_aa ? $aa2aa_bytes : 0) : 0);
$reserve_asset_amount = trigger.output[[asset=$reserve_asset]] - $full_network_fee; // subtract a fee to compensate for network fees
$reserve = var['reserve'];
if (!$reserve AND ($tokens1 <= 0 OR $tokens2 <= 0))
bounce("initial mint must be with both tokens");
$res = $get_exchange_result($tokens1, $tokens2);
$reserve_needed = $res.reserve_needed;
$reserve_delta = $res.reserve_delta;
$fee = $res.fee;
$regular_fee = $res.regular_fee;
$reward = $res.reward;
$p2 = $res.p2;
$target_p2 = $res.target_p2;
$new_distance = $res.new_distance;
$turnover = $res.turnover;
$fee_percent = $res.fee_percent;
$slow_capacity_share = $res.slow_capacity_share;
response['p2'] = $p2;
response['target_p2'] = $target_p2;
response['new_distance'] = $new_distance;
response['fee'] = $fee;
response['reward'] = $reward;
// response['term'] = (timestamp - var['rate_update_ts']) / (360 * 24 * 3600);
response['growth_factor'] = $get_growth_factor();
response['turnover'] = $turnover;
if ($reserve_delta > 0 AND $reserve_needed > $reserve_asset_amount){
$currency = ($reserve_asset == 'base') ? 'bytes' : 'reserve tokens';
bounce("expected " || ($reserve_needed + $full_network_fee) || " " || $currency || ", received " || ($reserve_asset_amount + $full_network_fee) /*|| ", growth factor " || $growth_factor*/ || ", p2 " || $p2 || ", target p2 " || $target_p2 || ", new distance " || $new_distance);
}
$payout = $reserve_asset_amount - $reserve_needed; // it is the change if reserve_delta>0
if ($payout < 0)
bounce("unexpected payout < 0");
if ($payout > 0 AND trigger.data.min_reserve_tokens AND $payout < trigger.data.min_reserve_tokens)
bounce("payout would be only " || $payout);
if (trigger.data.max_fee_percent AND $fee_percent > trigger.data.max_fee_percent)
bounce("fee would be " || $fee_percent || '%');
$reserve_to_aa = $payout > 0 AND $reserve_to != trigger.address AND is_aa($reserve_to);
$decision_engine_aa = var['decision_engine_aa'];
$data_payload = {};
if ($tokens1_to_aa OR $tokens2_to_aa OR $reserve_to_aa)
$data_payload.to = trigger.address;
if ($bNotifyDE)
$data_payload.tx = {
tokens2: $tokens2,
res: $res,
};
}",
"messages": [
{
"if": "{$tokens1 > 0}",
"app": "payment",
"payload": {
"asset": "{$asset1}",
"outputs": [
{
"address": "{$tokens1_to}",
"amount": "{ $tokens1 }"
}
]
}
},
{
"if": "{$tokens2 > 0}",
"app": "payment",
"payload": {
"asset": "{$asset2}",
"outputs": [
{
"address": "{$tokens2_to}",
"amount": "{ $tokens2 }"
}
]
}
},
{
"app": "payment",
"payload": {
"asset": "{$reserve_asset}",
"outputs": [
{
"address": "{$reserve_to}",
"amount": "{ $payout }"
},
{
"address": "{$tokens1_to}",
"amount": "{$aa2aa_bytes}",
"if": "{ $reserve_asset == 'base' AND $tokens1_to_aa}"
},
{
"address": "{$tokens2_to}",
"amount": "{$aa2aa_bytes}",
"if": "{ $reserve_asset == 'base' AND $tokens2_to_aa}"
},
{
"address": "{$decision_engine_aa}",
"amount": "{$de_bytes}",
"if": "{ $reserve_asset == 'base' AND $bNotifyDE}"
}
]
}
},
{
"if": "{ $reserve_asset != 'base' }",
"app": "payment",
"payload": {
"asset": "base",
"outputs": [
{
"address": "{$reserve_to}",
"amount": "{$aa2aa_bytes}",
"if": "{$reserve_to_aa}"
},
{
"address": "{$tokens1_to}",
"amount": "{$aa2aa_bytes}",
"if": "{$tokens1_to_aa}"
},
{
"address": "{$tokens2_to}",
"amount": "{$aa2aa_bytes}",
"if": "{$tokens2_to_aa}"
},
{
"address": "{$decision_engine_aa}",
"amount": "{$de_bytes}",
"if": "{$bNotifyDE}"
}
]
}
},
{
"if": "{ length($data_payload) > 0 }",
"app": "data",
"payload": "{$data_payload}"
},
{
"app": "state",
"state": "{
var['p2'] = $p2;
var['supply1'] += $tokens1;
var['supply2'] += $tokens2;
var['reserve'] += $reserve_delta;
if ($fee){
$fee_to_slow_capacitor = floor($slow_capacity_share * $fee);
$fee_to_fast_capacitor = $fee - $fee_to_slow_capacitor;
var['slow_capacity'] += $fee_to_slow_capacitor;
var['fast_capacity'] += $fee_to_fast_capacitor;
response['fee%'] = round($fee_percent, 4) || '%';
}
if ($reward){
var['fast_capacity'] -= $reward;
response['reward%'] = round($reward / $turnover * 100, 4) || '%';
}
if ($new_distance > $threshold_distance AND !$lost_peg_ts)
$bLostNow = true;
if ($new_distance <= $threshold_distance AND $lost_peg_ts)
$bRestored = true;
if ($bRestored OR $bLostNow)
var['lost_peg_ts'] = $bLostNow ? timestamp : false;
}"
}
]
}
]
}
}
]