[
"autonomous agent",
{
"doc_url": "https://counterstake.org/bridge-import.json",
"getters": "{
// ratio of the initial stake to the amount claimed
$get_ratio = () => var['ratio'] OTHERWISE params.ratio OTHERWISE 1;
$get_min_stake = () => var['min_stake'] OTHERWISE params.min_stake OTHERWISE 0;
// the counterstake must be that larger than the current stake
$get_counterstake_coef = () => var['counterstake_coef'] OTHERWISE params.counterstake_coef OTHERWISE 1.5;
// the claimed tx must be at least that old
$get_min_tx_age = () => {
$min_tx_age = var['min_tx_age'];
if (exists($min_tx_age))
return $min_tx_age;
exists(params.min_tx_age) ? params.min_tx_age : 0
};
// which transfers are deemed large and trigger a different schedule of challenging periods
$get_large_threshold = () => var['large_threshold'] OTHERWISE params.large_threshold OTHERWISE 0;
$is_stake_large = ($stake) => {
$large_threshold = $get_large_threshold();
$large_threshold AND $stake >= $large_threshold
};
// challenging periods in hours
$get_challenging_periods = () => var['challenging_periods'] OTHERWISE params.challenging_periods OTHERWISE [3*24, 7*24, 30*24, 60*24];
$get_large_challenging_periods = () => var['large_challenging_periods'] OTHERWISE params.large_challenging_periods OTHERWISE [7*24, 30*24, 60*24, 90*24];
// returns the challenging period in seconds
$get_challenging_period = ($period_number, $bLarge) => {
$periods = $bLarge ? $get_large_challenging_periods() : $get_challenging_periods();
$last_number = length($periods) - 1;
round($periods[min($period_number, $last_number)] * 3600)
};
$get_min_price = () => var['min_price'] OTHERWISE params.min_price OTHERWISE 0;
$get_oracles = () => var['oracles'] OTHERWISE params.oracles;
// returns oracle price of foreign asset in terms of stake asset. The price is in display units (e.g. ETH/GBYTE, not wei/byte)
$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);
max($oracle_price, $get_min_price())
};
// returns oracle price in the smallest indivisible units of local assets (e.g. nanoeth/byte)
$get_oracle_price_in_pennies = () => {
$mul = 10^(params.stake_asset_decimals - params.asset_decimals);
$mul * $get_oracle_price()
};
$get_required_stake = ($amount) => max(ceil($get_oracle_price_in_pennies() * $amount * $get_ratio()), $get_min_stake());
}",
"init": "{
if (!params.home_network)
bounce("no home network");
if (!params.home_asset)
bounce("no home asset");
$is_valid_decimals = $decimals => is_integer($decimals) AND $decimals >= 0 AND $decimals <= 15;
if (!$is_valid_decimals(params.asset_decimals))
bounce("bad asset_decimals");
if (!$is_valid_decimals(params.stake_asset_decimals))
bounce("bad stake_asset_decimals");
$stake_asset = params.stake_asset OTHERWISE 'base';
if (!asset[$stake_asset].exists)
bounce("no such asset: " || $stake_asset);
$counterstake_coef = $get_counterstake_coef();
$fee = 2000;
$stake_asset_fee = ($stake_asset == 'base') ? $fee : 0;
$stake = trigger.output[[asset=$stake_asset]] - $stake_asset_fee;
$asset = var['asset'];
if (trigger.data.address AND !is_valid_address(trigger.data.address))
bounce("invalid address: " || trigger.data.address);
$governance_base_aa = 'KDHCTQOTKTO6MLYOCU6OCBI7KK72DV3P';
}",
"messages": {
"cases": [
{
"if": "{ trigger.data.define AND !$asset }",
"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}",
"app": "payment",
"payload": {
"asset": "base",
"outputs": [
{
"address": "{trigger.data.factory}",
"amount": 1000
}
]
}
},
{
"app": "definition",
"payload": {
"definition": [
"autonomous agent",
{
"base_aa": "{$governance_base_aa}",
"params": {
"import_aa": "{this_address}",
"challenging_period": "{params.governance_challenging_period OTHERWISE ''}",
"freeze_period": "{params.freeze_period OTHERWISE ''}"
}
}
]
}
},
{
"app": "state",
"state": "{
var['governance_aa'] = unit[response_unit].messages[[.app='definition']].payload.address;
var['asset'] = response_unit;
response['asset'] = response_unit;
}"
}
]
},
{
"if": "{ $asset AND trigger.address == var['governance_aa'] AND trigger.data.name }",
"init": "{
$name = trigger.data.name;
}",
"messages": [
{
"app": "state",
"state": "{
var[$name] = trigger.data.value;
}"
}
]
},
{
"if": "{ trigger.data.txid AND trigger.data.amount AND trigger.data.txts AND trigger.data.sender_address AND $stake > 0 }",
"init": "{
if (trigger.output[[asset=base]] < $fee)
bounce("should send at least " || $fee || " bytes");
$reward = trigger.data.reward OTHERWISE 0;
if ($reward >= trigger.data.amount)
bounce("reward too large");
require(is_integer(trigger.data.amount) AND trigger.data.amount > 0, "amount must be positive integer");
require(is_integer($reward), "reward must be integer");
// can claim and stake for somebody else by immediately paying their claimed amount minus the reward
$address = trigger.data.address OTHERWISE trigger.address;
if ($address != trigger.address AND $reward < 0)
bounce("the sender disallowed third-party claiming by setting a negative reward");
$paid_amount = ($address != trigger.address) ? trigger.data.amount - $reward : 0;
if (trigger.output[[asset=$asset]] != $paid_amount)
bounce("received wrong amount in image asset " || trigger.output[[asset=$asset]] || ", expected " || $paid_amount);
$required_stake = $get_required_stake(trigger.data.amount);
if ($stake < $required_stake)
bounce("received stake " || $stake || " is less than the required stake " || $required_stake);
if (timestamp < trigger.data.txts + $get_min_tx_age())
bounce("the tx timestamp is not old enough");
$str_data = trigger.data.data ? json_stringify(trigger.data.data) : '';
$claim_hash = sha256(trigger.data.sender_address || '_' || $address || '_' || trigger.data.txid || '_' || trigger.data.txts || '_' || trigger.data.amount || '_' || $reward || '_' || $str_data);
if (var['num_' || $claim_hash])
bounce("this transfer has already been claimed");
$is_large = $is_stake_large($stake);
$claim_num = var['claim_num'] + 1;
}",
"messages": [
{
"if": "{$paid_amount > 0}",
"app": "payment",
"payload": {
"asset": "{$asset}",
"outputs": [
{
"address": "{$address}",
"amount": "{$paid_amount}"
}
]
}
},
{
"if": "{$paid_amount > 0}",
"app": "data",
"payload": {
"claim_num": "{$claim_num}",
"sender_address": "{trigger.data.sender_address}",
"address": "{$address}",
"amount": "{trigger.data.amount}",
"data": "{trigger.data.data OTHERWISE ''}"
}
},
{
"app": "state",
"state": "{
var['claim_num'] = $claim_num;
var['num_' || $claim_hash] = $claim_num;
$claim = {
claim_hash: $claim_hash,
amount: trigger.data.amount, // in image asset
reward: $reward,
sender_address: trigger.data.sender_address,
address: $address, // might be a third party
claimant_address: trigger.address,
txid: trigger.data.txid,
txts: trigger.data.txts,
stakes: {
yes: $stake,
no: 0,
},
current_outcome: 'yes',
is_large: $is_large,
period_number: 0,
ts: timestamp,
expiry_ts: timestamp + $get_challenging_period(0, $is_large),
challenging_target: ceil($stake * $counterstake_coef),
};
if (trigger.data.data)
$claim.data = trigger.data.data;
var['o_' || $claim_num] = $claim;
var[$claim_num || '_yes_by_' || trigger.address] += $stake; // not by $address!
var['address_' || $address || '_' || $claim_num] = trigger.data.amount;
response['message'] = "challenging period expires in " || ($claim.expiry_ts-timestamp)/3600 || " hours";
response['new_claim_num'] = $claim_num;
}"
}
]
},
{
"if": "{ trigger.data.stake_on AND trigger.data.claim_num AND $stake > 0 }",
"init": "{
if (trigger.output[[asset=base]] < $fee)
bounce("should send at least " || $fee || " bytes");
$claim_num = trigger.data.claim_num;
$stake_on = trigger.data.stake_on;
if ($stake_on != 'yes' AND $stake_on != 'no')
bounce("bad stake_on: " || $stake_on);
$claim = var['o_' || $claim_num];
if (!$claim)
bounce("no such claim: " || $claim_num);
if (timestamp > $claim.expiry_ts)
bounce("the challenging period has expired");
if ($stake_on == $claim.current_outcome)
bounce("the outcome " || $stake_on || " is already current");
$stake_on_proposed_outcome = $claim.stakes[$stake_on] + $stake;
$excess = $stake_on_proposed_outcome - $claim.challenging_target;
$would_override_current_outcome = $excess >= 0;
$accepted_stake = $stake - ($excess >= 0 ? $excess : 0);
}",
"messages": [
{
"if": "{$excess > 0}",
"app": "payment",
"payload": {
"asset": "{$stake_asset}",
"outputs": [
{
"address": "{trigger.address}",
"amount": "{$excess}"
}
]
}
},
{
"app": "state",
"state": "{
$claim.stakes[$stake_on] = $claim.stakes[$stake_on] + $accepted_stake;
if ($would_override_current_outcome) {
$claim.period_number = $claim.period_number + 1;
$claim.current_outcome = $stake_on;
$claim.expiry_ts = timestamp + $get_challenging_period($claim.period_number, $claim.is_large);
$claim.challenging_target = ceil($claim.challenging_target * $counterstake_coef);
}
var['o_' || $claim_num] = $claim;
var[$claim_num || '_' || $stake_on || '_by_' || trigger.address] += $accepted_stake;
response['accepted_stake'] = $accepted_stake;
response['message'] = "current outcome " || ($would_override_current_outcome ? "became " : "stays ") || $claim.current_outcome || ". Total staked " || $claim.stakes.yes || " on yes, " || $claim.stakes.no || " on no. Expires in " || ($claim.expiry_ts-timestamp)/3600 || " hours.";
}"
}
]
},
{
"if": "{trigger.data.withdraw AND trigger.data.claim_num}",
"init": "{
$claim_num = trigger.data.claim_num;
$ongoing_claim = var['o_' || $claim_num];
if ($ongoing_claim) {
$claim = $ongoing_claim;
if (timestamp < $claim.expiry_ts)
bounce("challenging period is still ongoing");
}
else {
$claim = var['f_' || $claim_num];
if (!$claim)
bounce("no such claim: " || $claim_num);
}
$address = trigger.data.address OTHERWISE trigger.address; // withdrawal can be triggered by anybody
$outcome = $claim.current_outcome;
$is_winning_claimant = ($address == $claim.claimant_address AND $outcome == 'yes');
if ($is_winning_claimant AND $claim.issued)
bounce("already issued");
$my_stake = var[$claim_num || '_' || $outcome || '_by_' || $address];
if (!$my_stake AND !$is_winning_claimant)
bounce("you are not the recipient and you didn't stake on the winning outcome or you have already withdrawn");
}",
"messages": [
{
"if": "{$my_stake}",
"app": "payment",
"payload": {
"asset": "{$stake_asset}",
"outputs": [
{
"address": "{$address}",
"amount": "{floor($my_stake / $claim.stakes[$outcome] * ($claim.stakes.yes + $claim.stakes.no))}"
}
]
}
},
{
"if": "{$is_winning_claimant}",
"app": "payment",
"payload": {
"asset": "{$asset}",
"outputs": [
{
"address": "{$address}",
"amount": "{$claim.amount}"
}
]
}
},
{
"app": "data",
"payload": {
"claim_num": "{$claim_num}",
"sender_address": "{$claim.sender_address}",
"address": "{$claim.address}",
"amount": "{$claim.amount}",
"data": "{$claim.data OTHERWISE ''}"
}
},
{
"app": "state",
"state": "{
if ($is_winning_claimant)
$claim.issued = true;
if ($ongoing_claim) {
var['o_' || $claim_num] = false;
response['message'] = "finished claim " || $claim_num;
}
if ($is_winning_claimant OR $ongoing_claim)
var['f_' || $claim_num] = $claim;
var[$claim_num || '_' || $outcome || '_by_' || $address] = false;
}"
}
]
},
{
"if": "{trigger.data.home_address AND trigger.output[[asset=$asset]] > 0}",
"messages": [
{
"app": "state",
"state": "{
$amount = trigger.output[[asset=$asset]];
if (trigger.data.reward AND !is_integer(trigger.data.reward))
bounce("reward must be integer");
if (trigger.data.reward AND trigger.data.reward >= $amount)
bounce("the claim reward is bigger than your transfer");
response['home_address'] = trigger.data.home_address;
response['amount'] = $amount;
response['reward'] = trigger.data.reward OTHERWISE 0;
response['timestamp'] = timestamp;
if (trigger.data.data)
response['data'] = json_stringify(trigger.data.data);
response['message'] = "started repatriation";
}"
}
]
}
]
}
}
]