Skip to content

Instantly share code, notes, and snippets.

@SamWilsn

SamWilsn/DSA.md Secret

Last active January 24, 2020 17:35
Show Gist options
  • Save SamWilsn/369de587ac7373c8f77ed26079531671 to your computer and use it in GitHub Desktop.
Save SamWilsn/369de587ac7373c8f77ed26079531671 to your computer and use it in GitHub Desktop.

Arbitrary State Access

Arbitrary state access is a read or write to a storage slot specified by another storage slot.

Example

This example is a loose approximation of an auction contract. Each new highest bidder locks in Ether as their bid, and the previous bidder is refunded.

refund_addr: address
refund_amount: wei_value

@public
@payable
def bid(new_refund_addr: address):
    assert msg.value > self.refund_amount
    
    send(self.refund_addr, self.refund_amount)
    
    self.refund_addr = new_refund_addr
    self.refund_amount = msg.value

Explanation

To show how this is DSA, imagine a scenario given the two following transactions {and their witnesses}:

  • bid('0xB', 4) {refund_addr == 0xA && refund_amount == 3 && (0xA).balance == 2}
  • bid('0xC', 5) {refund_addr == 0xA && refund_amount == 3 && (0xA).balance == 2}

If bid('0xB', 4) is included in the block first, the witnesses included by bid('0xC', 5) will be missing the state at 0xB.

Branched State Access

A branched state access is a read or write to a storage slot selected from a list known ahead of time.

Example

troz: int128
poit: int128
narf: int128

@public
def add_value(value: int128):
    if self.troz < 5:
        self.troz = self.poit + value
    else:
        self.troz = self.narf + value

Explanation

To show how this is DSA, imagine a scenario given the two following transactions {and their witnesses}:

  • add_value(2) { troz == 3 && poit == 5 }
  • add_value(3) { troz == 3 && poit == 5 }

If add_value(2) is included in the block first, the witnesses included by add_value(3) will be missing the state for narf.

Unlike Arbitrary State Access, it is possible to create a witness that is guaranteed to be sufficient by unconditionally adding poit and narf:

  • add_value(2) { troz == 3 && poit == 5 && narf == 11 }
  • add_value(3) { troz == 3 && poit == 5 && narf == 11 }

Predicted State Access

Example

previous_addr: address

@public
@payable
def dweep():
    send(self.previous_addr, msg.value)
    self.previous_addr = msg.sender

While dweep contains DSA, a transaction cannot be invalidated by a preceding transaction in the same block.

Regardless of whether dweep()[value=11,sender=0xB] is included before dweep()[value=23,sender=0xC] or vise versa, every state access will have a value (provided by the preceding transaction.)

Explanation

Consider these transactions {and their witnesses}:

  • dweep()[value=11,sender=0xB] { previous_addr = 0xA && (0xB).balance = 13 }
  • dweep()[value=23,sender=0xC] { previous_addr = 0xA && (0xC).balance = 29 }

If dweep()[value=11,sender=0xB] is included first, the state accesses for dweep()[value=23,sender=0xC] look like:

  • (0xC).balance == 29 is included in dweep()[value=23,sender=0xC]'s witness.
  • previous_addr == 0xB is known after dweep()[value=11,sender=0xB] is executed.
  • (0xB).balance == 2 is known after dweep()[value=11,sender=0xB] is executed.

On the other hand, if dweep(23) is included first, the state accesses for dweep(11) look like:

  • (0xB).balance == 13 is included in dweep()[value=11,sender=0xB]'s witness.
  • previous_addr == 0xC is known after dweep()[value=23,sender=0xC] is executed.
  • (0xC).balance == 6 is known after dweep()[value=23,sender=0xC] is executed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment