Definition: [
"autonomous agent",
{
"init": "{
if (trigger.output[[asset!=base]].asset != 'none')
bounce('foreign coins');
$fee = 1000;
$min_stake = 1e6;
$coef = 1.5;
$period_length = 3600;
// $period_length = 3*24*3600;
$exchange = trigger.data.exchange;
$remove_wallet_id = trigger.data.remove_wallet_id;
$add_wallet_id = trigger.data.add_wallet_id;
$forced_key = trigger.data.operation_id;
if ($remove_wallet_id AND $add_wallet_id)
bounce('cannot remove and add');
$wallet_id = $remove_wallet_id ? $remove_wallet_id : $add_wallet_id;
$pair = 'p_' || sha256($exchange || '=' || $wallet_id ); // long keys not allowed
}",
"messages": {
"cases": [
{
"if": "{$forced_key OR $exchange AND $wallet_id }",
"init": "{
if ($forced_key AND !var[$forced_key])
bounce("unknown operation");
if ($forced_key AND var[$forced_key] != 'committed')
bounce('this operation is not committed');
if ($forced_key){
$key = $forced_key;
}
else {
$num = var[$pair || '_number'] otherwise 0;
$old_key = 'k_' || sha256($exchange || '=' || $wallet_id ) || '_' || ($num);
if ((!$num OR var[$old_key] == 'committed') AND !trigger.data.commit)
$key = 'k_' || sha256($exchange || '=' || $wallet_id ) || '_' || ($num + 1);
else
$key = $old_key;
}
}",
"messages": {
"cases": [
{
"if": "{
if (trigger.output[[asset=base]] < $min_stake)
return false;
if ($forced_key)
bounce("you can specify operation id only for withdrawal");
$amount = trigger.output[[asset=base]] - $fee;
$outcome = $add_wallet_id ? 'in' : 'out';
$bInitialStake = !var[$key];
if ($bInitialStake){
if($remove_wallet_id AND (!var[$pair ||"_committed_outcome"] OR var[$pair||"_committed_outcome"] == $outcome))
bounce("this wallet id is not active");
if($add_wallet_id AND var[$pair||"_committed_outcome"] AND $outcome == var[$pair||"_committed_outcome"])
bounce("committed outcome for this pair is already " || $outcome);
$pool_id = trigger.data.pool_id;
if (!$pool_id)
bounce('please specify a reward pool id');
$number_of_rewards = var['pool_' || $pool_id || '_number_of_rewards'];
if (!$number_of_rewards)
bounce("pool " || $pool_id || " doesn't exist or is empty");
$pool_feed_name = var['pool_' || $pool_id || '_feed_name'];
if ($pool_feed_name AND $pool_feed_name != $exchange)
bounce("pool " || $pool_id || " is for exchange " || $pool_feed_name || " only");
return true;
}
// else expect a counterstake
if (timestamp - var[$key || '_countdown_start'] > $period_length)
bounce('challenging period expired');
$current_outcome = var[$key || '_outcome'];
if ($current_outcome == $outcome)
bounce('staking on the current outcome is not allowed');
$stake_on_current_outcome = var[$key || '_total_staked_on_' || $current_outcome];
$stake_on_proposed_outcome = var[$key || '_total_staked_on_' || $outcome];
$required_to_challenge = round($coef * $stake_on_current_outcome);
$would_override_current_outcome = ($stake_on_proposed_outcome + $amount >= $required_to_challenge);
if ($would_override_current_outcome)
$excess = $stake_on_proposed_outcome + $amount - $required_to_challenge;
true
}",
"messages": [
{
"if": "{$excess}",
"app": "payment",
"payload": {
"asset": "base",
"outputs": [
{
"address": "{trigger.address}",
"amount": "{$excess}"
}
]
}
},
{
"app": "state",
"state": "{
if ($bInitialStake){
var[$pair || '_exchange'] = $exchange;
var[$pair || '_wallet_id'] = $wallet_id;
var[$pair || '_number'] += 1;
var[$key] = 'onreview';
var[$key || '_initial_outcome'] = $outcome;
var[$key || '_initial_reporter'] = trigger.address;
var['pool_' || $pool_id || '_number_of_rewards'] -= 1;
var[$key || '_pool_id'] = $pool_id; // we'll need it if the feed is not accepted and the reward needs to be returned to the pool
response['pool_id'] = $pool_id;
response['expected_reward'] = var['pool_' || $pool_id || '_reward_amount'];
}
if ($bInitialStake OR $would_override_current_outcome){
var[$key || '_countdown_start'] = timestamp;
var[$key || '_outcome'] = $outcome;
response['countdown_started'] = true;
response['outcome'] = $outcome;
}
else
response['outcome'] = $current_outcome;
if (!$bInitialStake){
response['operation_id'] = $key;
}
$accepted_amount = $amount - $excess;
var[$key || '_total_staked'] += $accepted_amount;
var[$key || '_total_staked_on_' || $outcome] += $accepted_amount;
var[$key || '_total_staked_on_' || $outcome || '_by_' || trigger.address] += $accepted_amount;
response['staked_on_in'] = var[$key || '_total_staked_on_in'];
response['staked_on_out'] = var[$key || '_total_staked_on_out'] otherwise 0;
response['your_stake'] = var[$key || '_total_staked_on_' || $outcome || '_by_' || trigger.address];
}"
}
]
},
{
"if": "{
if (!trigger.data.commit)
return false;
if ($forced_key)
bounce("you can specify challenge id only for withdrawal");
if (!var[$key])
bounce('unknown feed');
if (var[$key] == 'committed')
bounce('already committed');
if (timestamp - var[$key || '_countdown_start'] <= $period_length)
bounce('challenge period is still running');
$pool_id = var[$key || '_pool_id'];
$outcome = var[$key || '_outcome'];
// immediately pay to the initial reporter. Other stakers (if any) will have to manually request withdrawals
$address = var[$key || '_initial_reporter'];
$initial_reporter_stake = var[$key || '_total_staked_on_' || $outcome || '_by_' || $address];
if ($initial_reporter_stake){
$reward = var['pool_' || $pool_id || '_reward_amount'];
$total_winning_stake = var[$key || '_total_staked_on_' || $outcome];
$total_stake = var[$key || '_total_staked'];
$amount = round($initial_reporter_stake / $total_winning_stake * $total_stake);
$full_amount = $amount + $reward;
}
true
}",
"messages": [
{
"if": "{$outcome == 'in'}",
"app": "data_feed",
"payload": {
"{$exchange}": "{$wallet_id}"
}
},
{
"if": "{$outcome == 'out'}",
"app": "data_feed",
"payload": {
"{$exchange}": "{"-"||$wallet_id}"
}
},
{
"if": "{$initial_reporter_stake}",
"app": "payment",
"payload": {
"asset": "base",
"outputs": [
{
"address": "{$address}",
"amount": "{$full_amount}"
}
]
}
},
{
"app": "state",
"state": "{
var[$key] = 'committed';
var[$pair||"_committed_outcome"] = $outcome;
if ($initial_reporter_stake){
var[$key || '_paid_out_' || $address] = 1;
var[$key || '_total_staked_on_' || $outcome || '_by_' || $address] = false;
}
else // return the reward to the pool
var['pool_' || $pool_id || '_number_of_rewards'] += 1;
response['message'] = "committed";
}"
}
]
},
{
"if": "{
if (!trigger.data.withdraw)
return false;
if (!var[$key])
bounce('unknown feed');
if (var[$key] != 'committed')
bounce('not committed yet');
$address = trigger.data.address otherwise trigger.address; // withdrawal can be triggered by anybody
if (var[$key || '_paid_out_' || $address])
bounce("you were already paid");
$outcome = var[$key || '_outcome'];
$my_stake = var[$key || '_total_staked_on_' || $outcome || '_by_' || $address];
if (!$my_stake)
bounce("you didn't stake on the winning outcome");
$total_winning_stake = var[$key || '_total_staked_on_' || $outcome];
$total_stake = var[$key || '_total_staked'];
$amount = round($my_stake / $total_winning_stake * $total_stake);
true
}",
"messages": [
{
"app": "payment",
"payload": {
"asset": "base",
"outputs": [
{
"address": "{$address}",
"amount": "{$amount}"
}
]
}
},
{
"app": "state",
"state": "{
var[$key || '_total_staked_on_' || $outcome || '_by_' || $address] = false;
var[$key || '_paid_out_' || $address] = 1;
response['message'] = "paid " || $amount || " bytes";
}"
}
]
}
]
}
},
{
"if": "{trigger.data.reward_amount AND trigger.data.number_of_rewards}",
"init": "{
$expected_amount = trigger.data.reward_amount * trigger.data.number_of_rewards;
if (trigger.output[[asset=base]] != $expected_amount)
bounce('wrong amount received, expected: ' || $expected_amount);
}",
"messages": [
{
"app": "state",
"state": "{
var['pool_id'] += 1;
$pool_id = var['pool_id'];
var['pool_' || $pool_id || '_sponsor'] = trigger.address;
var['pool_' || $pool_id || '_reward_amount'] = trigger.data.reward_amount;
var['pool_' || $pool_id || '_number_of_rewards'] = trigger.data.number_of_rewards;
if ($exchange) // the pool can be for one feed name only or general
var['pool_' || $pool_id || '_exchange'] = $exchange;
response['message'] = "created a reward pool " || $pool_id || " of " || trigger.output[[asset=base]] || " bytes";
}"
}
]
},
{
"if": "{trigger.data.withdraw_pool AND trigger.data.pool_id}",
"init": "{
$sponsor = var['pool_' || $pool_id || '_sponsor'];
if (!$sponsor)
bounce('no such pool: ' || $pool_id);
if ($sponsor != trigger.address)
bounce('not your pool: ' || $pool_id);
$number_of_rewards = var['pool_' || $pool_id || '_number_of_rewards']; // can be less than the initial number as some rewards might be already consumed or locked
if (!$number_of_rewards)
bounce('pool ' || $pool_id || ' is already empty');
$reward_amount = var['pool_' || $pool_id || '_reward_amount'];
}",
"messages": [
{
"app": "payment",
"payload": {
"asset": "base",
"outputs": [
{
"address": "{trigger.address}",
"amount": "{$number_of_rewards * $reward_amount}"
}
]
}
},
{
"app": "state",
"state": "{
var['pool_' || $pool_id || '_number_of_rewards'] = false;
response['message'] = "destroyed reward pool " || $pool_id;
}"
}
]
}
]
}
}
]