blockchain v0.1.5 Blockchain.Account

Represents the state of an account, as defined in Section 4 of the Yellow Paper.

Link to this section Summary

Functions

Simple helper function to adjust wei in an account. Wei may be positive (to add wei) or negative (to remove it). This function will raise if we attempt to reduce wei in an account to less than zero

Even simpler helper function to adjust wei in an account negatively. Wei may be positive (to subtract wei) or negative (to add it). This function will raise if we attempt to reduce wei in an account to less than zero

Completely removes an account from the world state. This is used, for instance, after a suicide. This is defined from Eq.(77) and Eq.(78) in the Yellow Paper

Decodes an account from an RLP encodable structure. This is defined as Eq.(10) p in the Yellow Paper (reversed)

Loads an account from an address, as defined in Eq.(9), Eq.(10), Eq.(11) and Eq.(12) of the Yellow Paper

Helper function to load multiple accounts

Returns the machine code associated with the account at the given address. This will return nil if the contract has no associated code (i.e. it is a simple account)

Gets a value from storage root of an account. See Section 4.1 under storageRoot from the Yellow Paper

Simple helper function to increment a nonce value

Checks whether or not an account is a non-contract account. This is defined in the latter part of Section 4.1 of the Yellow Paper

Stores an account at a given address. This function handles serializing the account, encoding it to RLP and placing into the given state trie

Puts code into a given account. Note, this will handle the aspect that we need to store the code_hash outside of the contract itself and only store the KEC of the code_hash

Stores a value in the storage root of an account. This is defined in Section 4.1 under storageRoot in the Yellow Paper

Encodes an account such that it can be represented in RLP encoding. This is defined as Eq.(10) p in the Yellow Paper

Helper function for transferring eth for one account to another. This handles the fact that a new account may be shadow-created if it receives eth. See Section 8, Eq.(100), Eq.(101), Eq.(102, Eq.(103), and Eq.(104) of the Yellow Paper

Performs transfer but raises instead of returning if an error occurs

Gets and updates an account based on a given input function fun. Account passed to fun will be blank instead of nil if account doesn’t exist

Link to this section Types

Link to this type t()
t() :: %Blockchain.Account{balance: EVM.Wei.t, code_hash: MerklePatriciaTree.Trie.key, nonce: integer, storage_root: EVM.trie_root}

Link to this section Functions

Link to this function add_wei(state, address, delta_wei)

Simple helper function to adjust wei in an account. Wei may be positive (to add wei) or negative (to remove it). This function will raise if we attempt to reduce wei in an account to less than zero.

Examples

iex> state = MerklePatriciaTree.Trie.new(MerklePatriciaTree.Test.random_ets_db())
...>   |> Blockchain.Account.put_account(<<0x01::160>>, %Blockchain.Account{balance: 10})
iex> state
...> |> Blockchain.Account.add_wei(<<0x01::160>>, 13)
...> |> Blockchain.Account.get_account(<<0x01::160>>)
%Blockchain.Account{balance: 23}

iex> state = MerklePatriciaTree.Trie.new(MerklePatriciaTree.Test.random_ets_db())
...>   |> Blockchain.Account.put_account(<<0x01::160>>, %Blockchain.Account{balance: 10})
iex> state
...> |> Blockchain.Account.add_wei(<<0x01::160>>, -3)
...> |> Blockchain.Account.get_account(<<0x01::160>>)
%Blockchain.Account{balance: 7}

iex> state = MerklePatriciaTree.Trie.new(MerklePatriciaTree.Test.random_ets_db())
...>   |> Blockchain.Account.put_account(<<0x01::160>>, %Blockchain.Account{balance: 10})
iex> state
...> |> Blockchain.Account.add_wei(<<0x01::160>>, -13)
...> |> Blockchain.Account.get_account(<<0x01::160>>)
** (RuntimeError) wei reduced to less than zero
Link to this function dec_wei(state, address, delta_wei)

Even simpler helper function to adjust wei in an account negatively. Wei may be positive (to subtract wei) or negative (to add it). This function will raise if we attempt to reduce wei in an account to less than zero.

Examples

iex> state = MerklePatriciaTree.Trie.new(MerklePatriciaTree.Test.random_ets_db())
...>   |> Blockchain.Account.put_account(<<0x01::160>>, %Blockchain.Account{balance: 10})
iex> state
...> |> Blockchain.Account.dec_wei(<<0x01::160>>, 3)
...> |> Blockchain.Account.get_account(<<0x01::160>>)
%Blockchain.Account{balance: 7}

iex> state = MerklePatriciaTree.Trie.new(MerklePatriciaTree.Test.random_ets_db())
...>   |> Blockchain.Account.put_account(<<0x01::160>>, %Blockchain.Account{balance: 10})
iex> state
...> |> Blockchain.Account.dec_wei(<<0x01::160>>, 13)
...> |> Blockchain.Account.get_account(<<0x01::160>>)
** (RuntimeError) wei reduced to less than zero

iex> state = MerklePatriciaTree.Trie.new(MerklePatriciaTree.Test.random_ets_db())
...>   |> Blockchain.Account.put_account(<<0x01::160>>, %Blockchain.Account{balance: 10})
iex> state
...> |> Blockchain.Account.dec_wei(<<0x01::160>>, -3)
...> |> Blockchain.Account.get_account(<<0x01::160>>)
%Blockchain.Account{balance: 13}
Link to this function del_account(state, address)
del_account(EVM.state, EVM.address) :: EVM.state

Completely removes an account from the world state. This is used, for instance, after a suicide. This is defined from Eq.(77) and Eq.(78) in the Yellow Paper.

Examples

iex> MerklePatriciaTree.Trie.new(MerklePatriciaTree.Test.random_ets_db())
...>   |> Blockchain.Account.put_account(<<0x01::160>>, %Blockchain.Account{balance: 10})
...>   |> Blockchain.Account.del_account(<<0x01::160>>)
...>   |> Blockchain.Account.get_account(<<0x01::160>>)
nil

iex> MerklePatriciaTree.Trie.new(MerklePatriciaTree.Test.random_ets_db())
...>   |> Blockchain.Account.del_account(<<0x01::160>>)
...>   |> Blockchain.Account.get_account(<<0x01::160>>)
nil
Link to this function deserialize(rlp)
deserialize(ExRLP.t) :: t

Decodes an account from an RLP encodable structure. This is defined as Eq.(10) p in the Yellow Paper (reversed).

Examples

iex> Blockchain.Account.deserialize([<<5>>, <<10>>, <<0x00, 0x01>>, <<0x01, 0x02>>])
%Blockchain.Account{nonce: 5, balance: 10, storage_root: <<0x00, 0x01>>, code_hash: <<0x01, 0x02>>}

iex> Blockchain.Account.deserialize([<<0>>, <<0>>, <<86, 232, 31, 23, 27, 204, 85, 166, 255, 131, 69, 230, 146, 192, 248, 110, 91, 72, 224, 27, 153, 108, 173, 192, 1, 98, 47, 181, 227, 99, 180, 33>>, <<197, 210, 70, 1, 134, 247, 35, 60, 146, 126, 125, 178, 220, 199, 3, 192, 229, 0, 182, 83, 202, 130, 39, 59, 123, 250, 216, 4, 93, 133, 164, 112>>])
%Blockchain.Account{}
Link to this function get_account(state, address)
get_account(EVM.state, EVM.address) :: t | nil

Loads an account from an address, as defined in Eq.(9), Eq.(10), Eq.(11) and Eq.(12) of the Yellow Paper.

Examples

iex> MerklePatriciaTree.Trie.new(MerklePatriciaTree.Test.random_ets_db())
...> |> MerklePatriciaTree.Trie.update(<<0x01::160>> |> BitHelper.kec, ExRLP.encode([5, 6, <<1>>, <<2>>]))
...> |> Blockchain.Account.get_account(<<0x01::160>>)
%Blockchain.Account{nonce: 5, balance: 6, storage_root: <<0x01>>, code_hash: <<0x02>>}

iex> MerklePatriciaTree.Trie.new(MerklePatriciaTree.Test.random_ets_db())
...> |> MerklePatriciaTree.Trie.update(<<0x01::160>> |> BitHelper.kec, <<>>)
...> |> Blockchain.Account.get_account(<<0x01::160>>)
nil

iex> MerklePatriciaTree.Trie.new(MerklePatriciaTree.Test.random_ets_db())
...> |> Blockchain.Account.get_account(<<0x01::160>>)
nil
Link to this function get_accounts(state, addresses)
get_accounts(EVM.state, [EVM.address]) :: [t | nil]

Helper function to load multiple accounts.

Examples

iex> state = MerklePatriciaTree.Trie.update(MerklePatriciaTree.Trie.new(MerklePatriciaTree.Test.random_ets_db()), <<0x01::160>> |> BitHelper.kec, ExRLP.encode([5, 6, <<1>>, <<2>>]))
iex> Blockchain.Account.get_accounts(state, [<<0x01::160>>, <<0x02::160>>])
[
  %Blockchain.Account{nonce: 5, balance: 6, storage_root: <<0x01>>, code_hash: <<0x02>>},
  nil
]
Link to this function get_machine_code(state, contract_address)
get_machine_code(EVM.state, EVM.address) ::
  {:ok, binary} |
  :not_found

Returns the machine code associated with the account at the given address. This will return nil if the contract has no associated code (i.e. it is a simple account).

We may return :not_found, indicating that we were not able to find the given code hash in the state trie.

Alternatively, we will return {:ok, machine_code} where machine_code may be the empty string <<>>.

Note from Yellow Paper: > “it is assumed that the client will have stored the pair (KEC(I_b), I_b)

 at some point prior in order to make the determinatio of Ib feasible"

Examples

iex> MerklePatriciaTree.Trie.new(MerklePatriciaTree.Test.random_ets_db())
...> |> Blockchain.Account.get_machine_code(<<0x01::160>>)
{:ok, <<>>}

iex> MerklePatriciaTree.Trie.new(MerklePatriciaTree.Test.random_ets_db())
...> |> Blockchain.Account.put_account(<<0x01::160>>, %Blockchain.Account{code_hash: <<555>>})
...> |> Blockchain.Account.get_machine_code(<<0x01::160>>)
:not_found

iex> MerklePatriciaTree.Trie.new(MerklePatriciaTree.Test.random_ets_db())
...> |> Blockchain.Account.put_code(<<0x01::160>>, <<1, 2, 3>>)
...> |> Blockchain.Account.get_machine_code(<<0x01::160>>)
{:ok, <<1, 2, 3>>}
Link to this function get_storage(state, address, key)
get_storage(EVM.state, EVM.address, integer) ::
  {:ok, integer} |
  :account_not_found |
  :key_not_found

Gets a value from storage root of an account. See Section 4.1 under storageRoot from the Yellow Paper.

Examples

iex> state = MerklePatriciaTree.Trie.new(MerklePatriciaTree.Test.random_ets_db())
iex> updated_state = Blockchain.Account.put_storage(state, <<01::160>>, 5, 9)
iex> Blockchain.Account.get_storage(updated_state, <<01::160>>, 5)
{:ok, 9}

iex> state = MerklePatriciaTree.Trie.new(MerklePatriciaTree.Test.random_ets_db())
iex> Blockchain.Account.get_storage(state, <<02::160>>, 5)
:account_not_found

iex> state = MerklePatriciaTree.Trie.new(MerklePatriciaTree.Test.random_ets_db())
iex> updated_state = Blockchain.Account.put_storage(state, <<01::160>>, 5, 9)
iex> Blockchain.Account.get_storage(updated_state, <<01::160>>, 55)
:key_not_found
Link to this function increment_nonce(state, address, return_accounts \\ false)
increment_nonce(EVM.state, EVM.address, boolean) ::
  EVM.state |
  {EVM.state, t, t}

Simple helper function to increment a nonce value.

iex> state = MerklePatriciaTree.Trie.new(MerklePatriciaTree.Test.random_ets_db())
...>   |> Blockchain.Account.put_account(<<0x01::160>>, %Blockchain.Account{nonce: 10})
iex> state
...> |> Blockchain.Account.increment_nonce(<<0x01::160>>)
...> |> Blockchain.Account.get_account(<<0x01::160>>)
%Blockchain.Account{nonce: 11}

iex> state = MerklePatriciaTree.Trie.new(MerklePatriciaTree.Test.random_ets_db())
...>   |> Blockchain.Account.put_account(<<0x01::160>>, %Blockchain.Account{nonce: 10})
iex> { _state, before_acct, after_acct } = Blockchain.Account.increment_nonce(state, <<0x01::160>>, true)
iex> before_acct.nonce
10
iex> after_acct.nonce
11
Link to this function is_simple_account?(acct)
is_simple_account?(t) :: boolean

Checks whether or not an account is a non-contract account. This is defined in the latter part of Section 4.1 of the Yellow Paper.

Examples

iex> Blockchain.Account.is_simple_account?(%Blockchain.Account{})
true

iex> Blockchain.Account.is_simple_account?(%Blockchain.Account{code_hash: <<0x01, 0x02>>})
false

iex> Blockchain.Account.is_simple_account?(%Blockchain.Account{code_hash: <<197, 210, 70, 1, 134, 247, 35, 60, 146, 126, 125, 178, 220, 199, 3, 192, 229, 0, 182, 83, 202, 130, 39, 59, 123, 250, 216, 4, 93, 133, 164, 112>>})
true
Link to this function put_account(state, address, account)
put_account(EVM.state, EVM.address, t) :: EVM.state

Stores an account at a given address. This function handles serializing the account, encoding it to RLP and placing into the given state trie.

Examples

iex> state = Blockchain.Account.put_account(MerklePatriciaTree.Trie.new(MerklePatriciaTree.Test.random_ets_db()), <<0x01::160>>, %Blockchain.Account{nonce: 5, balance: 6, storage_root: <<0x01>>, code_hash: <<0x02>>})
iex> MerklePatriciaTree.Trie.get(state, <<0x01::160>> |> BitHelper.kec) |> ExRLP.decode
[<<5>>, <<6>>, <<0x01>>, <<0x02>>]
Link to this function put_code(state, contract_address, machine_code)

Puts code into a given account. Note, this will handle the aspect that we need to store the code_hash outside of the contract itself and only store the KEC of the code_hash.

This is defined in Eq.(98) and address in Section 4.1 under codeHash in the Yellow Paper.

Not sure if this is correct, but I’m going to store the code_hash in state, as well as the link to it in the Account object itself.

TODO: Verify the above ^^^ is accurate, as it’s not spelled out

  in the Yellow Paper directly.

Examples

iex> state = MerklePatriciaTree.Trie.new(MerklePatriciaTree.Test.random_ets_db())
...> |> Blockchain.Account.put_code(<<0x01::160>>, <<1, 2, 3>>)
iex> Blockchain.Account.get_account(state, <<0x01::160>>)
%Blockchain.Account{code_hash: <<241, 136, 94, 218, 84, 183, 160, 83, 49, 140, 212, 30,
                                  32, 147, 34, 13, 171, 21, 214, 83, 129, 177, 21, 122, 54, 51, 168,
                                  59, 253, 92, 146, 57>>}
iex> MerklePatriciaTree.Trie.get(state, BitHelper.kec(<<1, 2, 3>>))
<<1, 2, 3>>
Link to this function put_storage(state, address, key, value)
put_storage(EVM.state, EVM.address, integer, integer) :: t

Stores a value in the storage root of an account. This is defined in Section 4.1 under storageRoot in the Yellow Paper.

Examples

iex> state = MerklePatriciaTree.Trie.new(MerklePatriciaTree.Test.random_ets_db())
iex> updated_state = Blockchain.Account.put_storage(state, <<01::160>>, 5, 9)
iex> Blockchain.Account.get_storage(updated_state, <<01::160>>, 5)
{:ok, 9}
Link to this function serialize(account)
serialize(t) :: ExRLP.t

Encodes an account such that it can be represented in RLP encoding. This is defined as Eq.(10) p in the Yellow Paper.

Examples

iex> Blockchain.Account.serialize(%Blockchain.Account{nonce: 5, balance: 10, storage_root: <<0x00, 0x01>>, code_hash: <<0x01, 0x02>>})
[5, 10, <<0x00, 0x01>>, <<0x01, 0x02>>]

iex> Blockchain.Account.serialize(%Blockchain.Account{})
[
  0,
  0,
  <<86, 232, 31, 23, 27, 204, 85, 166, 255, 131, 69, 230, 146, 192, 248, 110, 91, 72, 224, 27, 153, 108, 173, 192, 1, 98, 47, 181, 227, 99, 180, 33>>,
  <<197, 210, 70, 1, 134, 247, 35, 60, 146, 126, 125, 178, 220, 199, 3, 192, 229, 0, 182, 83, 202, 130, 39, 59, 123, 250, 216, 4, 93, 133, 164, 112>>
]
Link to this function transfer(state, from, to, wei)
transfer(EVM.state, EVM.address, EVM.address, EVM.Wei.t) ::
  {:ok, EVM.state} |
  {:error, String.t}

Helper function for transferring eth for one account to another. This handles the fact that a new account may be shadow-created if it receives eth. See Section 8, Eq.(100), Eq.(101), Eq.(102, Eq.(103), and Eq.(104) of the Yellow Paper.

The Yellow Paper assumes this function will always succeed (as the checks occur before this function is called), but we’ll check just in case this function is not properly called. The only case will be if the sending account is nil or has an insufficient balance, but we add a few extra checks just in case.

Note: transferring value to an empty account still adds value to said account,

  even though it's effectively a zombie.

Examples

iex> state = MerklePatriciaTree.Trie.new(MerklePatriciaTree.Test.random_ets_db())
...>   |> Blockchain.Account.put_account(<<0x01::160>>, %Blockchain.Account{balance: 10})
...>   |> Blockchain.Account.put_account(<<0x02::160>>, %Blockchain.Account{balance: 5})
iex> {:ok, state} = Blockchain.Account.transfer(state, <<0x01::160>>, <<0x02::160>>, 3)
iex> {Blockchain.Account.get_account(state, <<0x01::160>>), Blockchain.Account.get_account(state, <<0x02::160>>)}
{%Blockchain.Account{balance: 7}, %Blockchain.Account{balance: 8}}

iex> state = MerklePatriciaTree.Trie.new(MerklePatriciaTree.Test.random_ets_db())
...>   |> Blockchain.Account.put_account(<<0x01::160>>, %Blockchain.Account{balance: 10})
iex> {:ok, state} = Blockchain.Account.transfer(state, <<0x01::160>>, <<0x02::160>>, 3)
iex> {Blockchain.Account.get_account(state, <<0x01::160>>), Blockchain.Account.get_account(state, <<0x02::160>>)}
{%Blockchain.Account{balance: 7}, %Blockchain.Account{balance: 3}}

iex> state = MerklePatriciaTree.Trie.new(MerklePatriciaTree.Test.random_ets_db())
...>   |> Blockchain.Account.put_account(<<0x01::160>>, %Blockchain.Account{balance: 10})
iex> Blockchain.Account.transfer(state, <<0x01::160>>, <<0x02::160>>, 12)
{:error, "sender account insufficient wei"}

iex> Blockchain.Account.transfer(MerklePatriciaTree.Trie.new(MerklePatriciaTree.Test.random_ets_db()), <<0x01::160>>, <<0x02::160>>, -3)
{:error, "wei transfer cannot be negative"}
Link to this function transfer!(state, from, to, wei)

Performs transfer but raises instead of returning if an error occurs.

Examples

iex> state = MerklePatriciaTree.Trie.new(MerklePatriciaTree.Test.random_ets_db())
...>   |> Blockchain.Account.put_account(<<0x01::160>>, %Blockchain.Account{balance: 10})
...>   |> Blockchain.Account.put_account(<<0x02::160>>, %Blockchain.Account{balance: 5})
iex> state = Blockchain.Account.transfer!(state, <<0x01::160>>, <<0x02::160>>, 3)
iex> {Blockchain.Account.get_account(state, <<0x01::160>>), Blockchain.Account.get_account(state, <<0x02::160>>)}
{%Blockchain.Account{balance: 7}, %Blockchain.Account{balance: 8}}
Link to this function update_account(state, address, fun, return_accounts \\ false)
update_account(EVM.state, EVM.address, (t -> t), boolean) ::
  EVM.state |
  {EVM.state, t, t}

Gets and updates an account based on a given input function fun. Account passed to fun will be blank instead of nil if account doesn’t exist.

Examples

iex> MerklePatriciaTree.Trie.new(MerklePatriciaTree.Test.random_ets_db())
...>   |> Blockchain.Account.put_account(<<0x01::160>>, %Blockchain.Account{balance: 10})
...>   |> Blockchain.Account.update_account(<<0x01::160>>, fn (acc) -> %{acc | balance: acc.balance + 5} end)
...>   |> Blockchain.Account.get_account(<<0x01::160>>)
%Blockchain.Account{balance: 15}

iex> {_state, before_acct, after_acct} = MerklePatriciaTree.Trie.new(MerklePatriciaTree.Test.random_ets_db())
...>   |> Blockchain.Account.put_account(<<0x01::160>>, %Blockchain.Account{balance: 10})
...>   |> Blockchain.Account.update_account(<<0x01::160>>, fn (acc) -> %{acc | balance: acc.balance + 5} end, true)
iex> before_acct.balance
10
iex> after_acct.balance
15

iex> MerklePatriciaTree.Trie.new(MerklePatriciaTree.Test.random_ets_db())
...>   |> Blockchain.Account.update_account(<<0x01::160>>, fn (acc) -> %{acc | nonce: acc.nonce + 1} end)
...>   |> Blockchain.Account.get_account(<<0x01::160>>)
%Blockchain.Account{nonce: 1}