This website is no longer actively supported. Please see the Ripple Developer Center for up-to-date documentation and other resources.

Contracts

From Ripple Wiki
Jump to: navigation, search

NOTE: This page details how smart contracts functionality could be built in to the Ripple network. There are currently no plans to implement this approach. Instead, Codius is being developed as a smart contracts solution for Ripple as well as any other cryptocurrency or web service.

Intro

Contracts are a flexible way to support lots of different transaction types and instruments. We anticipate there will be many uses which we haven't thought of.

To make a contract you:

  • Pay a fee
    • The amount of the fee depends of the length of the contract expiration
  • Pay a contract bond
    • the issuer gets this bond back when the contract is no longer in the ledger


Here are some example use cases (bold ones are worked out):

  • Subscriptions
  • Time Release Escrow coins and then have them time release
  • XRP Auction Auction for XRP that escrows all the bids
  • Bonds (I don't think there isn't much reason for us to support these. They require the bond holder to trust the issuer so they might as well just work it out outside of our system.)
  • Bond Auction
  • Nickname exchange offer
  • Kickstarter/Assurance contract transaction only completes if it reaches a certain balance
  • Escrow
  • M of N accounts?
  • Private box
  • Multi-stage transaction
  • Invoices (Someone remind me what the point of these are?)
  • Auction


Contracts act much like accounts. They can send and receive XRP create ripple lines, orders, other contracts, etc.

When a contract is the destination for a send, the amount sent is added to the contract's balance and the contract's fund code is executed.

Contract Data

Contracts have the following data. The issuer is always set to the account that created the contract. The issuer must fund the escrow balance initially. All the other fields can be set by the issuer.

  • Issuer (AccountID)
  • Owner (AccountID) since only the owner can authorize a transfer of the contract
  • Issuer Bond Amount (We need to store it since it can change. It goes back to the issuer when the bond is removed from the ledger)
  • Escrow Balance (In XRP. Is set when the contract is created)
  • Ripple Balance
    • issuer/currency/amount.
    • Set when the contract is created.
    • If amount is non-zero it is taken from the issuer
    • Can only accept funding from this Issuer and Currency.
  • Create Code <executed only once when the contract is created>
  • Fund Code <executed when someone sends money to this contract>
  • Accept Code <executed when we receive an accept transaction. it can contain random variables. it also allows the contract to send from the Acceptors account.>
    • We don't need this if we can add arbitrary parameters to a normal transaction and some way for the sender to agree to the contract explicitly
  • Remove Code <executed if the contract is removed prematurely by the issuer>
  • variable data fields
  • Expiration Date?

Ledger_Format#Contract_Root_Node

Language

Foundational Ops

  • pushInt (The next 4 bytes are pushed on the stack as an int)
  • pushFloat (the next 4 bytes are pushed on the stack as a float)
  • pushuint160 (The next 20 bytes are pushed on the stack as a uint160)
  • pushPath (The next N bytes are pushed on the stack as a path)
  • pushBool (The next byte is pushed onto the stack as a bool)
  • pushError (The next byte is pushed onto the stack as an Error)
  • pop (Take off the top piece of data on the stack)
  • jump (The next 4 bytes are the offset to jump.)
  • jumpIf (The next 4 bytes are the offset to jump. If the top of the stack is true jump to this new location)
  • >, < , == , !=  : (a,b) -> bool
  •  %, *, \, +, - : (a,b) -> int or float
  • AND(p1,p2) -> int or bool
  • OR(p1,p2) -> int or bool
  • NOT(p1) -> int or bool

Functions

  • bool sendXRP(FromID, ToID, Amount)
    • Send coins between accounts.
    • returns if it failed or succeeded
    • FromID can be escrow, the issuer or the acceptor
    • Rolled back in a block
  • bool send(FromID, ToID, SrcCurrency, DstCurrency, SendMax, Amount, Path)
    • The path is probably sent in by an accept transaction
    • returns if it failed or succeeded
    • FromID can be escrow, the issuer or the acceptor
    • Rolled back in a block
  • void addApproval()
    • The accepting funder is added to the list of accounts this contract can always act on the behalf of.
  • bool removeContract()
    • remove this contract from the ledger.
    • All escrowed coins and the bonded coins are returned to the Issuer
    • Rolled back in a block
  • void startBlock() (The next 4 bytes are the offset to jump to if there is a failure)
  • bool endBlock()
    • All transactions in the block either all fail or all succeed.
    • if we have a failed transaction we jump to the offset set in startBlock
    • endBlock pushes if it failed or succeeded onto the stack
    • blocks can't be nested
  • bool changeContractOwner( NewOwnerID )
    • Change the owner of this contract
    • Rolled back in a block
  • void stop() stop executing script
  • void setData(index,data)
    • Rolled back in a block
  • data getData(index)
  • int getNumData() returns the number of data elements
    • These are used to store arbitrary data for later use by the contract
    • Each contract can only hold X pieces of data (or X bytes)
  • void setRegister(index,data)
    • Rolled back in a block
  • data getRegister(index)
    • This is used as temporary storage for the script
    • local variables essentially
  • bool fee(src,amount)
    • Pay a fee of amount
    • src can be, escrow, issuer, or acceptor
    • Rolled back in a block
  • void cancelTransaction()
    • Causes the transaction to fail
  • void stopRemove()
    • Only valid in the remove code block. Stops the contract from being removed
    • Rolled back in a block
  • bool createLine(AccountID,Currency,Amount)
    • Extends this much credit to the AccountID
    • This must be done before the contract can accept IOUs
    • Rolled back in a block

data access

  • data getAcceptData(index)
    • This is how the code accesses the data sent in the accept transaction
  • int ledgerNum() (returns the current ledger sequence number)
  • int ledgerTime() (returns the current ledger time stamp)
  • float randFloat()
    • returns a random number between 0 and 1
    • this number is deterministically created based on the hash of the collection of transactions or something
  • int getBalance() (returns the amount of XRP held by the contract)
  • int getNumLines() ?
  • int getLineBalance(lineIndex) ?
  • uint160 getLineCurrency(lineIndex) ?
  • uint160 getLineIssuer(lineIndex) ?
  • uint160 getContractID()
  • uint160 getOwnerID()
  • uint160 getIssuerID()
  • uint160 getAcceptorID(index)
    • 0 is always the current acceptor. 1 - N are the acceptors that have approved in the past.
    • the return will be 0 if there are no more acceptors at that index or above
  • other Owner, Issuer, Acceptor data we might need?
    • Seq Num
    • Balance

Later

  • createOffer
  • createContract
  • changeCode(newCode) (Could be crazy. allows them to dynamically update the contract code)
  • bool checkSig(Public Key,Fields,Signature)
    • check if the signature of Fields was created by the private key of public key.
    • I thought we would need this for private boxes but we don't


Example Contracts

Time release coins

Coins are locked and only release at a certain rate. Escrow Balance is set at creation

  • S is the start time
  • E is the original escrow amount
  • R is the release rate
  • Fund
sendXRP(getContractID(),getOwnerID(), E-(ledgerTime()-S)*R);
  • Remove
fee(getContractID(),getXRPEscrowed());

Subscription

Send A coins every N ledgers to someone.

  • Issuer (the subscriber's account ID)
  • Owner (the service's accountID)
  • Create:
setData(1,getLedgerTime());
  • Fund:
    if((getData(1)-getLedgerTime()) > N)
    {
        startBlock();
        setData(1, getData(1)+N );
        sendXRP(getIssuerID(),getOwnerID(),A);
        endBlock();
    }

Private Box

Someone can create a private box that some other account has the keys to. This box can be told to send by anyone with the keys. For private box, we can just have the transaction come from the box. In the transaction the source account would be the contract and the pub key would be the contract's pub key. The owner of the private box will get the box's private key by looking at the contract.

We don't actually have to do anything in the contract code. We just store the encrypted private key in the ledger node. We don't even do that in the case of a weblink.

We could add something that prevents the issuer from removing but really the recipient should move the contents when they get the box.

Escrowed Auction

X XRP is auctioned off for currency C M is the min bid. D is the Auction expiration

  • Issuer
  • Owner (current highest bidder)
  • Create:
SET_DATA(1,M);
SET_DATA(2,0);
  • Fund
if(Ledger_Date()>D)
{
   if(GET_DATA(2)) SEND(Escrow,GET_DATA(2),EscrowAmount());
   SEND(Escrow,IssuerID,RippleEscrowAmount());
   REMOVE_THIS();
}else
{
    if(Fund_Ripple_Amount() > GET_DATA(1))
    {
      if(GET_DATA(2)) SEND(Escrow,GET_DATA(2),RippleEscrowAmount());
      SET_DATA(1,Fund_Ripple_Amount());
      SET_DATA(2,FunderID());
    }
}
  • Remove
 if(GET_DATA(2)) SEND(Escrow,GET_DATA(2),RippleEscrowAmount());

Bond

I don't think there isn't much reason for us to support these. They require the bond holder to trust the issuer so they might as well just work it out outside of our system. These live in the ledger and can be transferred to other owners. Lender agrees to pay X XRP at Date D to the owner of the bond.

  • Issuer (the lender's accountID)
  • Owner (current bond holder accountID)
  • Fund:
if( getLedgerTime()>D)
{
    startBlock();
    sendXRP(getIssuerID(),getOwnerID(),X);
    removeContract();
    endBlock();
}
  • Remove:
if(! sendXRP(getIssuerID(),getOwnerID(),X) ) stopRemove();

Bond Transfer

Owner of bond C will sell it to whoever pays X XRP

  • Issuer (the current owner of the bond)
  • Accept:
if( accept(1) > X )
{ 
   block{
       transferContract(C,Triggerer);
       send_xns(Acceptor, Owner, X);
    }
}

Escrow

A wants to send to B. The transaction doesn't happen until approved by C. C can cancel the transaction. Transaction expires after time D

  • Issuer is A
  • Owner is B
  • Escrow balance is set by A when the contract is made
  • Accept:
if(AcceptorID==C)
{
    if(GET_ACCEPT_DATA(1))
    {
        block{
            SEND_XRP(Escrow,OwnerID,EscrowAmount);
            REMOVE_THIS();
        }
    }else
    {
        block{
            SEND_XRP(Escrow,IssuerID,EscrowAmount);
            REMOVE_THIS();
        }
    }
    REMOVE_THIS();
}
  • Remove:
if(LEDGER_DATE()>D)
{
    SEND_XRP(Escrow,Issuer,EscrowAmount);
}else
{
    STOP_REMOVE();
}

Multiple person transaction

If N people send to a particular account the money will be sent otherwise it will be refunded the people who did fund.


Kickstarter style

This has to live in the ledger. If contract raises P coins by date D, the coins are released otherwise they are all refunded to the funders.

  • Issuer
  • Owner (project leaders ID)
  • Trigger:
  • IF( AND(LEDGER_DATE()>D) ,
  • IF( ESCROW()>P, SEND(Escrow,RecipientID,EscrowAmount),
  • SET_REGISTER(1,2);
  • WHILE(GET_REGISTER(1)<GET_DATA(1), SEND(Escrow,GET_DATA(GET_REGISTER(1)*2,GET_DATA(GET_REGISTER(1)*2+1); SET_REGISTER(1,GET_REGISTER(1)+1) ); ,
  • SET_DATA(GET_DATA(1),GET_TRIGGERER()); SET_DATA(GET_DATA(1)+1,GET_TRIGGER_AMOUNT()); SET_DATA(1,GET_DATA(1)+1);
  • Remove
 return all escrowed money

This is also known as an assurance contract.

Lotto

If you fund this contract you are added to the lotto. Your chance is proportional to the amount of money everyone put in. Lotto pays out at time D

  • Issuer (project's accountID)
  • Fund:
if( Ledger_DATE() < D)
{
   SET_DATA(NUM_DATA(),FunderID);
   SET_DATA(NUM_DATA(),FundAmount);
}else
{
   SET_REGISTER(2,1);
   SET_REGISTER(1, RAND_FLOAT()*EscrowAmount());
   While(GET_REGISTER(1)>0)
   {
       SET_REGISTER(3, GET_DATA(GET_REGISTER(2)*2);
       SET_REGISTER(1, GET_REGISTER(1)-GET_DATA(GET_REGISTER(2)*2+1));
       SET_REGISTER(2, GET_REGISTER(2)+1);
   }
      SEND_XRP(Escrow,IssuerID,EscrowAmount*.01);
      SEND_XRP(Escrow,GET_REGISTER(3),EscrowAmount);
      REMOVE_THIS();
}
  • Fund RPN
  • Remove:
   STOP_REMOVE();

Penny Auction

Last account to send at least X coins to a particular address gets a prize. If no one sends for time T the auction ends. P is the price of the auction Z is the auction prize

  • Issuer (Who started the auction)
  • Owner (who will win the auction at expiration)
  • Create:
SET_DATA(1, LEDGER_DATE() );
  • Fund:
if( GET_DATA(1)+T < LEDGER_DATE() )
{
   if(FundAmount>P)
   {
        setOwner(FunderID);
        SET_DATA(1,LEDGER_DATE());
   }
   SEND(Escrow,IssuerID,FundAmount);
}else
{
   SEND(Escrow,OwnerID,EscrowAmount);
   REMOVE_THIS();
}
  • Remove:
STOP_REMOVE();



Nickname exchange offer

This doesn't have to live in the ledger. It can just be sent from Issuer to recipient and the recipient can sign it and introduce it into the network. One small issue is that if somehow the original owner regains control of this nick before the expiration date then this transaction can be replayed.

  • Issuer (the buyer's accountID)
  • Owner <empty>
  • Recipient (the seller's accountID)
  • Transfer Condition <empty>
  • Trigger Condition:
    • Outer Sig by Recipient
  • Action
    • Send N coins from (IssuerID -> RecipientID)
    • Transfer (nickname -> IssuerID)
  • Expire Condition
    • Ledger # > X



Issues

  • how do we handle this private box stuff?
    • We need a way to create a transaction to the contract that doesn't originate from an account.
  • What about contracts that just sit there?
    • We can make part of the escrow be a bond that is returned to the issuer when the contract is removed from the ledger.
    • There could still be contracts just sitting there?
    • Do we want to make the initial fee dependent on how long the contract is allowed to sit there?
  • Do we need to index the contracts by owner or issuer?
  • Do we need to be able to ripple to a contract?
    • I don't think we need to allow this
    • If you send to a contract it can have some result like funding some other contract
  • Someone could make a raping contract that takes all the senders money. Then trick people to sending to it.
    • We probably can't allow it to create sends from the Triggerer unless the triggerer explicitly accepts
    • We could make the trigger have to say how much they authorize. This only works for stamps what about transferring contracts or ripple
  • Can anyone remove a contract? The owner?
  • What do we do if there are data type mismatches? Or invalid parameters to functions?
  • What happens if someone sends in a currency this contract doesn't want?
    • Probably the currency it accepts should be in the description
    • The transaction fails before it hits the code

solved:

  • Do we need multiple types of triggers?
    • Probably not especially if we allow them to send 0 XRP
  • Do we need triggers that convey information besides what is in a send?
    • Ripple path
    • Some other random thing
  • Anyone can force a contract to expire since they can just send to it and make it do work thus burning through its escrow fee
    • Maybe the triggerer pays the fee?
      • This seems like it could get ugly if the script has to stop part way through
        • We will just have a global roll back if the script isn't funded enough

Breaking the System

Types of attacks:

  • Bugs in code
  • Creating a ton of work
    • this should be impossible since there is a fee for each operation
  • some logical error that allows a contract to pull coins from another account

Old

Contracts are stuck in the ledger and require a maintenance fee. This fee is base + some amount per operation. The contract specifies who is responsible for paying the fee. If the fee can't be paid the contract is removed.


Every function is just a tree. The serialized format just has a flattened version of this tree. The code is run through linearly executing everything it needs to execute. Every function has a code and an expected number of parameters ADD| FUNCTION|param|param|FUNCTION| ...

  • int getRippleEscrowed() (returns the amount still in escrow)
  • uint160 getRippleEscrowCurrency() (returns the amount still in escrow)
  • uint160 getRippleEscrowIssuer() (returns the amount still in escrow)


Operations:

  • >, < , == , !=
  •  %, *, \, +, -
  • AND(p1,p2)
  • OR(p1,p2)
  • NOT(p1)
  • IF(Condition, true code, false code)
  • WHILE(Condition,code)
  • bool SEND_XRP(FromID, ToID, Amount)
    • Send coins between accounts.
    • returns if it failed or succeeded
    • FromID can be the issuer or the account that Accepted the code
  • bool SEND(FromID, ToID, SrcCurrency, DstCurrency, SendMax, Amount, Path)
    • The path is probably sent in by an accept transaction
    • returns if it failed or succeeded
    • FromID can be the issuer or the account that Accepted the code
  • REMOVE_THIS()
    • remove this contract from the ledger.
    • All escrowed coins and the bonded coins are returned to the Issuer
  • STOP_REMOVE()
    • Only valid in the remove code block. Stops the contract from being removed
  • bool BLOCK( Code )
    • All functions in the block either all fail or all succeed.
    • returns if it failed or succeeded
  • bool SET_OWNER( OwnerID )
    • Change the owner of this contract
  • END() stop executing script
  • STORE_DATA(index,data)
  • GET_DATA(index,data)
  • NUM_DATA() returns the number of data elements
  • SET_REGISTER(index,data) not stored. Used for variables in the script
  • GET_REGISTER(index,data)
  • FEE(amount)
    • Pay a fee of amount