Snippets

chromawallet Change pubkey cross concept

Created by Riccardo Sibani last modified
enum account_type {
  single_key_account,
  multi_sig_account
}

object import_state {
   mutable dev: boolean = true;
}

// TOREMOVE all the class and put it in one object
class _this_blockchain {
  blockchain: blockchain;
  chain_id: integer;
}

class blockchain {
  key blockchain_rid: byte_array;
  chain_id: integer; // TOREMOVE
}

class account {
  key id: byte_array;
  mutable account_type;
  mutable args: byte_array;
  mother_chain: blockchain;

  chain_id: integer; // TOREMOVE
}

class master_account {
  key master: account, controlled: account;

  chain_id: integer;
}

record single_key_account_args { pubkey; }
record multi_sig_account_args { signatures_required: integer; pubkeys: list<pubkey>;}

class asset { key name, chain_id: integer; } //TOREMOVE

class balance { 
  key account, asset;
  mutable amount: integer;  
  
  chain_id: integer; // TOREMOVE
}

class locked_tokens {
  key asset, blockchain;
  mutable amount: integer;

  chain_id: integer; // TOREMOVE
}

class payment_history_entry (log) {
    index account, asset, block_height: integer;
    delta: integer;
    chain_id: integer; // TOREMOVE

    // helps to locate exact position of payment entry in transaction
    op_index: integer;
    is_input: boolean;
    entry_index: integer;
}

class crosschain_history_entry (log) {
  payment_history_entry;
  index blockchain; 
  to: pubkey;
}

record account_descriptor {
  account_type;
  args: GTXValue;
}

operation init(blockchain_rid: byte_array, chain_id: integer) {
    require((_this_blockchain@*{.chain_id == chain_id} limit 1).size() == 0);
    val bc = create blockchain(blockchain_rid, chain_id);
    create _this_blockchain(bc, chain_id);
}

function tkl_register_account (account_descriptor, chain_id: integer, id: byte_array) { // TODO remove id, should be description hash
    create account (
       .id = id, // Replace this with account_descriptor.hash()
       account_type = account_descriptor.account_type,
       args = account_descriptor.args.toBytes(),
       mother_chain = _this_blockchain@{chain_id}.blockchain,

       chain_id // TOREMOVE
    );
}

function tkl_change_auth(old_account: account, new_account_type: account_type, new_args: GTXValue, chain_id: integer) {
  tkl_require_auth(old_account);
  // Question, should we perform checks here?
  update account@{.id == old_account.id, .chain_id == chain_id}(.account_type = new_account_type, .args = new_args.toBytes());
}

function tkl_require_auth (account) {
   val t = account.account_type;
   // TODO: switch or when statement
   if (t == account_type.multi_sig_account) {
     val multi_sign_req = multi_sig_account_args.fromBytes(account.args);
     var num_sign = multi_sign_req.signatures_required;
     for(s in multi_sign_req.pubkeys) {
       if(is_signer(s)) {
         num_sign -= 1;
         //if(num_sign < 1) {breaks};
       }
     }
     require(num_sign<1);
   } else if (t == account_type.single_key_account) {
        require(
            is_signer(single_key_account_args.fromBytes(account.args).pubkey)
        );
   }
}

operation register_account(account_type_: text, args: GTXValue, chain_id: integer, id: byte_array) {
  if(account_type_ == 'single_key_account') {
    tkl_register_account(account_descriptor(account_type.single_key_account, args), chain_id, id);
  } else  if (account_type_ == 'multi_sig_account'  ){
    tkl_register_account(account_descriptor(account_type.multi_sig_account, args), chain_id, id);
  } else {
    require(false);
  }
}

record ft3_entry {
   account_id: byte_array;
   asset_name: name;
   amount: integer;
   extra: map<text, GTXValue>;
   chain_id: integer; // TOREMOVE
}

operation ft3_transfer(inputs: list<ft3_entry>, outputs: list<ft3_entry>) {
  val sum_inputs = map<asset, integer>();
   var idx = 0;
   for (i in inputs) {
      val asset = asset @ { i.asset_name, .chain_id == i.chain_id };
      val account = account@{.id == i.account_id, .chain_id == i.chain_id};
      tkl_require_auth( account );
      require( i.amount >= 0, "Amount should be positive." );
      require( balance @ { account, asset, .chain_id == i.chain_id }.amount >= i.amount, "Your balance is too low");
      sum_inputs[asset] = if (asset in sum_inputs) sum_inputs[asset] + i.amount else i.amount;
      
      // We are sure that the balance is already registered, otherwise the account doesn't have money and should fail
      update balance@{ account, asset, .chain_id == i.chain_id} (.amount -= i.amount);

      create payment_history_entry (
        .chain_id = i.chain_id,
        account,
        asset,
        .block_height = op_context.transaction.block.block_height,
        .delta = -i.amount,

        .op_index = 0, //op_context ???
        .is_input = true,
        .entry_index = idx
      );
      idx += 1;
   }

   idx = 0;
   for (o in outputs) {
      val target_account = account @ { .id == o.account_id, .chain_id == o.chain_id };
      val asset = asset @ { o.asset_name, .chain_id == o.chain_id  };
      require( sum_inputs[asset] >= o.amount );
      sum_inputs[asset] -= o.amount;
      require(sum_inputs[asset] >= 0);
      if (balance @? {target_account, asset, .chain_id == o.chain_id } == null) {
          create balance ( target_account, asset, .amount = o.amount, .chain_id = o.chain_id);
      } else {
        update balance @ { target_account, asset, .chain_id == o.chain_id } (amount += o.amount);
      }
      create payment_history_entry (
        .chain_id = o.chain_id,
        .account = target_account,
        asset,
        .block_height = op_context.transaction.block.block_height,
        .delta = o.amount,

        .op_index = 500, // something from op_context,
        .is_input = true,
        .entry_index = idx
      );
      idx += 1;
   } 
}

record xc_ft_entry {
    chain_id: integer; // TOREMOVE
    from: account;
    blockchain;
    asset;
    to: pubkey;
    amount: integer; 
}

operation add_blockchain(blockchain_rid: byte_array, chain_id: integer) {
    create blockchain(blockchain_rid, chain_id);
}

operation xc_init_chroma_deposit(chain_id: integer, from_id: byte_array, to_blockchain_rid: byte_array, asset_name: text, to: pubkey, amount: integer) {
    xc_init_ft3_deposit(
        xc_ft_entry(chain_id, 
            account@{.id == from_id, chain_id}, 
            blockchain@{to_blockchain_rid, chain_id}, 
            asset@{asset_name, chain_id}, 
            to, 
            amount)
    );   
}

function xc_init_ft3_deposit(xc_ft_entry) { // TOREMOVE
  val account = xc_ft_entry.from;
  val asset = xc_ft_entry.asset;
  tkl_require_auth(account);
  // Sender has control over the pubkey that will receive money 
  require(is_signer(xc_ft_entry.to));
  
  require(xc_ft_entry.amount > 0);
  val u_balance = balance@{account, asset, .chain_id == xc_ft_entry.chain_id}.amount;
  require(u_balance >= xc_ft_entry.amount);

  //TODO if balance doesn't exist, create it
  update balance@{account, asset, .chain_id == xc_ft_entry.chain_id}(.amount = u_balance - xc_ft_entry.amount);

    create payment_history_entry(
      .chain_id = xc_ft_entry.chain_id,
      account,
      asset,
      .block_height = op_context.transaction.block.block_height,
      .delta = -xc_ft_entry.amount,
      .is_input = true,

      .op_index = 0, // TODO
      .entry_index = 0 // decauple this from payment_history_entry to something optional
    );
  create crosschain_history_entry (
    payment_history_entry@{.chain_id == xc_ft_entry.chain_id, account, asset, .block_height == op_context.transaction.block.block_height, .delta == -xc_ft_entry.amount}, // this stuff might not be deterministic (same account, chain_id, asset, amount etc. There are 2 payment_history entry which are the same)
    .blockchain = xc_ft_entry.blockchain,
    .to = xc_ft_entry.to
  );
  
  val account_locked_tokens = create_locked_tokens_if_not_exists(asset, xc_ft_entry.blockchain, xc_ft_entry.chain_id);
  account_locked_tokens.amount += xc_ft_entry.amount; 
}


operation xc_apply_dapp_deposit(chain_id: integer, from_id: byte_array, to_blockchain_rid: byte_array, asset_name: text, to: pubkey, amount: integer,      from_blockchain_rid: byte_array, this_chain_id: integer){ // TODO REMOVE from_blockchain_rid
    xc_apply_ft3_deposit(
        xc_ft_entry(
            chain_id, 
            account@{.id == from_id, .chain_id == this_chain_id}, 
            blockchain@{.blockchain_rid == to_blockchain_rid}, 
            asset@{.name == asset_name, .chain_id == this_chain_id}, 
            to, 
            amount
        ),
        blockchain@{.blockchain_rid == from_blockchain_rid},
        this_chain_id
    );
}

//function xc_apply_ft3_deposit(proof<xc_ft_entry>) {
//  val xc_ft_entry = verify_proof(proof);
function xc_apply_ft3_deposit(xc_ft_entry, from_blockchain_rid: blockchain, this_chain_id: integer) { // TODO REMOVE from_blockchain_rid
  // val = verify_proof(proof);

  //require(op_context.transaction.hash() == proof.tx_rid); // TODO UNCOMMENT

  // check if the account exists in dapp, if not create it
  // TOREMOVE TODO this code can be greatly simplified without chain_id
  val account = xc_ft_entry.from;
  val blockchain = blockchain@{blockchain == xc_ft_entry.blockchain, this_chain_id};
  val asset = asset@{asset == xc_ft_entry.asset, this_chain_id};
  val sender_account = create_account_if_not_exists(account.id, account.account_type, account.args, account.mother_chain, this_chain_id);
   
  val locked_tokens = create_locked_tokens_if_not_exists(asset, blockchain, this_chain_id);
  locked_tokens.amount += xc_ft_entry.amount;
  

  // check if receiver accounts exists or needs to be created
  val receiver_account = create_account_if_not_exists(xc_ft_entry.to, account_type.single_key_account, single_key_account_args(xc_ft_entry.to).toBytes(), _this_blockchain@{this_chain_id}.blockchain, this_chain_id);
  val receiver_balance = create_balance_if_not_exists(receiver_account, asset, this_chain_id);
  receiver_balance.amount += xc_ft_entry.amount;

  require(is_signer(xc_ft_entry.to));
  val master_account = create_master_account_if_not_exists(sender_account, receiver_account, this_chain_id);
}


operation change_pubkey (account_id: byte_array, blockchain_rid: byte_array, old_pubkey: pubkey, new_pubkey: pubkey, chain_id: integer) {
  val from_blockchain = blockchain@{blockchain_rid};
  val owner_account = account@{.id == account_id, .mother_chain == from_blockchain, .chain_id == chain_id};
  val controlled_account = account@{.account_type == account_type.single_key_account, .args == single_key_account_args(old_pubkey).toBytes()};
  tkl_require_auth(owner_account);

  // if account has access to pubkey, then change
  val is_master = master_account@?{.master == owner_account, .controlled == controlled_account, .chain_id == chain_id};
  require(is_master!= null);
  
  controlled_account.args = single_key_account_args(new_pubkey).toBytes();
}


function create_master_account_if_not_exists(master: account, controlled: account, chain_id: integer): master_account {
  val master_account_exists = master_account@?{.master == master, .controlled == controlled, chain_id};
  if(master_account_exists == null) {
    return create master_account(.master = master, .controlled = controlled, chain_id);
  } 
  return master_account_exists!!;
}

function create_locked_tokens_if_not_exists(asset, blockchain, chain_id: integer): locked_tokens {
  val locked_tokens_exists = locked_tokens@?{asset, blockchain, .chain_id == chain_id};
  if( locked_tokens_exists == null) {
    return create locked_tokens(
      chain_id = chain_id, 
      asset = asset, 
      blockchain = blockchain, 
      amount = 0
    );
  }
  return locked_tokens_exists!!;
}

function create_balance_if_not_exists(account, asset, chain_id: integer): balance {
  val balance_exists = balance@?{asset, account, .chain_id == chain_id};

  if(balance_exists == null) {
    return create balance(account, asset, amount = 0, .chain_id = chain_id);
  }
  return balance_exists!!;
}

function create_account_if_not_exists(id: byte_array, selected_account_type: account_type, args: byte_array, mother_chain: blockchain, chain_id: integer): account {
  var account_exists = account@?{
    .account_type == selected_account_type, 
    .args == args,
    .mother_chain == _this_blockchain@{chain_id}.blockchain,

    .chain_id == chain_id
  };

  if(account_exists == null) {
    return  create account (
      id = id,
      account_type = selected_account_type, 
      args = args,
      mother_chain = _this_blockchain@{chain_id}.blockchain,

      chain_id = chain_id
    );
  }

  return account_exists!!;
}

Comments (0)

HTTPS SSH

You can clone a snippet to your computer for local editing. Learn more.