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
t() :: %Blockchain.Account{balance: EVM.Wei.t, code_hash: MerklePatriciaTree.Trie.key, nonce: integer, storage_root: EVM.trie_root}
Link to this section Functions
add_wei(EVM.state, EVM.address, EVM.Wei.t) :: EVM.state
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
dec_wei(EVM.state, EVM.address, EVM.Wei.t) :: EVM.state
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}
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
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{}
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
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
]
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>>}
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
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
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
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>>]
put_code(EVM.state, EVM.address, EVM.MachineCode.t) :: EVM.state
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>>
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}
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>>
]
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"}
transfer!(EVM.state, EVM.address, EVM.address, EVM.Wei.t) :: EVM.state
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}}
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}