sruby – Simple, Secure and Solid Ruby: Run Ruby Contract / Transaction Scripts on the Blockchain / World Computer


(Gerald Bauer) #1

Hello,

   I've published a new red paper [1] that introduces the new sruby
version / flavor.

  What's sruby?

sruby - short for simple, secure & solid ruby - is a subset of mruby
that is a subset of "classic" ruby and lets you run contract /
transaction scripts on the blockchain (world computer).

Happy transacting with sruby. Questions and comments welcome. Cheers. Prost.

PS: Some sruby contract samples:

1) Hello, World! - Greeter

class Greeter < Contract

  def initialize( greeting )
    @owner    = msg.sender
    @greeting = greeting
  end

  def greet
    @greeting
  end

  def kill
    selfdestruct( msg.sender )  if msg.sender == @owner
  end
end  # class Greeter

2) Mint Your Own Money - Minimal Viable Token

class Token < Contract

  def initialize( initial_supply )
    @balance_of = Mapping.of( Address => Money )
    @balance_of[ msg.sender] = initial_supply
  end

  def transfer( to, value )
    assert( @balance_of[ msg.sender ] >= value )
    assert( @balance_of[ to ] + value >= @balance_of[ to ] )

    @balance_of[ msg.sender ] -= value
    @balance_of[ to ]         += value

    true
  end
end   # class MyToken

3) Win x65 000 - Roll the (Satoshi) Dice

class SatoshiDice < Contract

  Bet = Struct.new( :user, :block, :cap, :amount )

  ## Fee (Casino House Edge) is 1.9%, that is, 19 / 1000
  FEE_NUMERATOR   = 19
  FEE_DENOMINATOR = 1000

  MAXIMUM_CAP = 2**16   # 65_536 = 2^16 = 2 byte/16 bit
  MAXIMUM_BET = 100_000_000
  MINIMUM_BET = 100

  BetPlaced = Event.new( :id, :user, :cap, :amount )
  Roll      = Event.new( :id, :rolled )

  def initialize
    @owner   = msg.sender
    @counter = 0
    @bets    = Mapping.of( Integer => Bet )
  end

  def bet( cap )
     assert( cap >= 1 && cap <= MAXIMUM_CAP )
     assert( msg.value >= MINIMUM_BET && msg.value <= MAXIMUM_BET )

     @counter += 1
     @bets[@counter] = Bet.new( msg.sender, block.number+3, cap, msg.value )
     log BetPlaced.new( @counter, msg.sender, cap, msg.value )
  end

  def roll( id )
    bet = @bets[id]

    assert( msg.sender == bet.user )
    assert( block.number >= bet.block )
    assert( block.number <= bet.block + 255 )

    ## "provable" fair - random number depends on
    ##  - blockhash (of block in the future - t+3)
    ##  - nonce (that is, bet counter id)
    hex = sha256( "#{blockhash( bet.block )} #{id}" )
    ## get first 2 bytes (4 chars in hex string) and convert to integer number
    ##   results in a number between 0 and 65_535
    rolled = hex_to_i( hex[0,4] )

    if rolled < bet.cap
       payout = bet.amount * MAXIMUM_CAP / bet.cap
       fee = payout * FEE_NUMERATOR / FEE_DENOMINATOR
       payout -= fee

       msg.sender.transfer( payout )
    end

    log Roll.new( id, rolled )
    @bets.delete( id )
  end

  def fund
  end

  def kill
    assert( msg.sender == @owner )
    selfdestruct( @owner )
  end
end

[1] https://github.com/openblockchains/universum/blob/master/REDPAPER.md


(Gerald Bauer) #2

Hello,

    sruby - Simple, Smart, Secure, Safe, Solid & Sound (S6) Ruby now
has a new home [1].

    See the /s6ruby org(anization) at github. The new org incl.

     - the "Red Paper"
     - the Universum world computer runtime
     - sample contract scripts (in sruby, of course)
     - the Programming Crypto Contracts step-by-step book / guide
     - and more.

  Last but not least - thanks to Matz for Ruby and for the sruby tweet [2]!

Cheers. Prost.

[1] https://github.com/s6ruby
[2] https://twitter.com/yukihiro_matz/status/1092631315252310016

PS: To include some sruby code - here's a kick starter-like crowd
funder contract:

```

···

##############################
# Crowd Funder Contract

State = Enum.new( :fundraising, :expired_refund, :successful )

Contribution = Struct.new( :amount, :contributor )

FundingReceived = Event.new( :address, :amount, :current_total )
WinnerPaid = Event.new( :winner_address )

def initialize(
      time_in_hours_for_fundraising,
      campaign_url,
      fund_recipient,
      minimum_to_raise )

  @creator = msg.sender
  @fund_recipient = fund_recipient # note: creator may be
different than recipient
  @campaign_url = campaign_url
  @minimum_to_raise = minimum_to_raise # required to tip, else
everyone gets refund
  @raise_by = block.timestamp + (time_in_hours_for_fundraising
* 1.hour )

  @state = State.fundraising
  @total_raised = 0
  @complete_at = 0
  @contributions = Array.of( Contribution )
end

def pay_out
  assert @state.successful?

  @fund_recipient.transfer( this.balance )
  log WinnerPaid.new( @fund_recipient )
end

def check_if_funding_complete_or_expired
  if @total_raised > @minimum_to_raise
    @state = State.successful
    pay_out()
  elsif block.timestamp > @raise_by
    # note: backers can now collect refunds by calling refund(id)
    @state = State.expired_refund
    @complete_at = block.timestamp
  end
end

def contribute
  assert @state.fundraising?

  @contributions.push( Contribution.new( msg.value, msg.sender ))
  @total_raised += msg.value

  log FundingReceived.new( msg.sender, msg.value, @total_raised )

  check_if_funding_complete_or_expired()

  @contributions.size - 1 # return (contribution) id
end

def refund( id )
  assert @state.expired_refund?
  assert @contributions.size > id && id >= 0 && @contributions[id].amount != 0

  amount_to_refund = @contributions[id].amount
  @contributions[id].amount = 0

  @contributions[id].contributor.transfer( amount_to_refund )

  true
end

def kill
  assert msg.sender == @creator
  # wait 24 weeks after final contract state before allowing contract
destruction
  assert (state.expired_refund? || state.successful?) && @complete_at
+ 24.weeks < block.timestamp

  # note: creator gets all money that hasn't be claimed
  selfdestruct( msg.sender )
end
```