Proposal: Create new transactions with KT1 accounts

Rework of my proposition.

:: 'p %parameters : mutez %fees : contract 'p : 'S -> operation : 'S NEW_TRANSACTION
where   contract 'p is the type of an entrypoint of the current contract

The instruction NEW_TRANSACTION pushes a new operation on the stack. This operation add an order that will be executed after the end of the current transaction. The order is not executed if the transaction failed.

The order originates a new transaction which correspond to a TRANSFER_TOKENS of 0 tokens to SELF with the specified %parameters (and the specified entry_point). The %fees parameter permits to pay the fees and storage for the new transaction.

This instruction and the corresponding operation can never fail but they are reverted if the current transaction failed.

The new transaction order is sent to the gossip network exactly like if it were originated by an implicit account. It means that you don’t know when and if the transaction will be included in a block.

Scheduling

The order is called after every other instructions only if the current transaction don’t fail.

Like every originated transaction on the gossip network you don’t know if it will be executed before or after every other competing transactions.

About tickets

The tickets (see MR) should be kept along the transaction chain.

About total gas limit

Should there be a limit of the total number of gas spent on the whole chains of transactions? I don’t find any reason but there could be.

DDOS

Is there a risk of DDOS induced? One transaction can send n new transactions to the network.

On how this proposal answers actual problems

Refunding to a list of contracts problem

We can read in the Tezos Developer Documentation: Michelson Anti-Patterns one of the current limit of Tezos that can disappear with this new instruction.

One common pattern in contracts is to refund a group of people’s funds at once. This is problematic if you accepted arbitrary contracts as a malicious user can do cause various issues for you.

Possible issues:

  • One contract swallows all the gas through a series of callbacks
  • One contract writes transactions until the block is full
  • Reentrancy bugs. Michelson intentionally makes these difficult to write, but it is still possible if you try.
  • A contract calls the FAIL instruction, stopping all computation.

Solution with NEW_TRANSACTION

Create an entry_point to pull the funds individually. In the “refund_everyone” entry_point iterates through the list of receiver and use the NEW_TRANSACTION instruction to trigger the individual refunds in separate transactions.

This way if one of the transfer fails you can still trigger the others. The failed transfers can then be triggered manually.

This solution prevents the “gas spending”, “block full” and “Fail computation” problems.

Example

import smartpy as sp

class RefundContract(sp.Contract):
    def __init__(self):
        self.init(funds = sp.map())

    @sp.entry_point
    def add_fund(self, params):
        sp.if self.data.funds.contains(params.receiver):
            self.data.funds[params.receiver] += sp.amount
        sp.else:
            self.data.funds[params.receiver] = sp.amount

    def send_refund(self, receiver):
        sp.transfer(sp.unit, self.data.funds[receiver], sp.contract(sp.TUnit, receiver).open_some())
        self.data.funds[receiver] = sp.mutez(0)

    @sp.entry_point
    def refund(self, params):
        sp.if sp.sender == sp.to_address(sp.self):
            self.send_refund(params.receiver)
        sp.else:
            self.send_refund(sp.sender)

    @sp.entry_point
    def refund_everyone(self, params):
        # Note that you must take into account the fact that you will pay fee apart
    	sp.verify(sp.mutez(params.fee * sp.len(self.data.funds.keys())) == sp.amount)
        
        sp.for receiver in self.data.funds.keys():
        	sp.new_transaction(arg=sp.record(receiver=receiver),  fee=sp.mutez(params.fee) , destination=sp.self_entry_point('refund'))
            """
            	sp.new_transaction represents the smartpy implementation of the proposal
            	It's a bit like doing:
            		sp.transfer(arg=sp.record(receiver=receiver), amount=sp.mutez(0), destination=sp.self_entry_point('refund'))
            	# except your transfer is sent in a new transaction after everything else passed.
           		# As a result if one transfer fails your "refund_everyone" transaction don't fail.
            	# However a pass of refund_everyone don't indicate if your transfers passed.
            """
            

On chain fee relay

One feature I’d like to see in Tezos would be to pay the fee (storage and block inclusion competition) for someone else or to ask for someone else to pay my fees.

This proposal permits that.

The sender still has to pay the fee for the relay demand but that fee could be much reduced compared to the one for the new transaction that will be done by the relay.

In the previous example, if the receiver wants to add a lot of things to the storage because of your refund, they can do it in a NEW_TRANSACTION so you don’t pay the fee for this.


Increase the interactivity possibilities

In the current situation implicit accounts are the only one able to create new transaction. It means that only humans or off chain codes can prolong the interactivity beyond the gas limit.

With this proposal you can start on a fresh new gas limit counter with a NEW_TRANSACTION.