Skip to content

Commit c669f68

Browse files
committed
Merge branch 'Testing' of github.com:lambdaclass/ethereum_war_game_tooling into Testing
2 parents 386a90f + bf0abe8 commit c669f68

File tree

14 files changed

+900
-7
lines changed

14 files changed

+900
-7
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
cache/
22
out/
3+
.local/

README.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,26 @@ You can change any of these config values at runtime by using the functions expo
1717

1818
The default config assumes you're running the local ethereum network this repo provides.
1919

20+
If you want to use infura-type host this are the setps you must follow:
21+
22+
First set your infura api key
23+
24+
```
25+
Context.set_infura_api_key("your_infura_api_key")
26+
```
27+
28+
Then set the your etherscan api key
29+
30+
```
31+
Context.set_etherscan_api_key("your_eth_scan_api_key")
32+
```
33+
34+
And finally set the name of the chain you want to use, for now this are the supported chains: eht mainnet, rinkeby and ropsten.
35+
36+
```
37+
EthClient.set_chain("chain_name")
38+
```
39+
2040
### Interacting with smart contracts
2141

2242
Currently there are three functions in the `EthClient` module that form the main API:

geth_nodes/docker-compose.yml renamed to docker-compose.yml

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,14 @@ services:
1414
--verbosity 5 --allow-insecure-unlock --fakepow --http.api eth,web3,personal,admin
1515
--unlock 0xafb72ccaeb7e22c8a7640f605824b0898424b3da
1616
--password /root/private_network/password
17+
--http.vhosts=*
1718
hostname: ethereum
1819
image: ethereum/client-go:stable
1920
ports:
2021
- 30304:30304
2122
- 8545:8545
2223
volumes:
23-
- ./private_network:/root/private_network
24+
- ./geth_nodes/private_network:/root/private_network
2425

2526
ethereum_node2:
2627
command: --datadir /root/private_network/node2 --networkid 1234 --port 30305
@@ -30,10 +31,40 @@ services:
3031
--verbosity 5 --allow-insecure-unlock --fakepow --http.api eth,web3,personal,admin
3132
--unlock 0x77b648683cde1d69544ed6f4f7204e8d51c324db
3233
--password /root/private_network/password
34+
--http.vhosts=*
3335
hostname: ethereum
3436
image: ethereum/client-go:stable
3537
ports:
3638
- 30305:30305
3739
- 8546:8546
3840
volumes:
39-
- ./private_network:/root/private_network
41+
- ./geth_nodes/private_network:/root/private_network
42+
43+
# ----------------------------------------------------------------------------
44+
# Wargame Tooling Client
45+
# ----------------------------------------------------------------------------
46+
47+
eth_client:
48+
build: ./eth_client/
49+
stdin_open: true # docker run -i
50+
tty: true # docker run -t
51+
ports:
52+
- 43487:43487
53+
volumes:
54+
- ./eth_client/:/app/
55+
56+
# ----------------------------------------------------------------------------
57+
# Livebook Frontend
58+
# ----------------------------------------------------------------------------
59+
60+
livebook:
61+
image: livebook/livebook
62+
ports:
63+
- 8080:8080
64+
- 8081:8081
65+
volumes:
66+
- ./livebooks/:/home/livebook/
67+
environment:
68+
- LIVEBOOK_HOME=/home/livebook/
69+
- LIVEBOOK_TOKEN_ENABLED=false
70+
- LIVEBOOK_DEFAULT_RUNTIME=attached:eth_client@eth_client:mycookie

eth_client/.iex.exs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1+
alias EthClient
12
alias EthClient.Context
23
alias EthClient.Contract
34

45
bin_path = "../contracts/src/bin/Storage.bin"
56
abi_path = "../contracts/src/bin/Storage.abi"
67

78
# Uncomment and fill in for Rinkeby
8-
# Context.set_rpc_host("your_rinkeby_rpc_host_here")
9-
# Context.set_chain_id(4)
10-
# Context.set_etherscan_api_key("your_api_key_here")
9+
Context.set_infura_api_key("your_infura_api_key")
10+
Context.set_etherscan_api_key("your_eth_scan_api_key")
11+
EthClient.set_chain("rinkeby")

eth_client/Dockerfile

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
FROM elixir:1.13-alpine
2+
3+
RUN apk --no-cache add \
4+
cargo
5+
# inotify-tools\
6+
# git
7+
8+
RUN mkdir /app
9+
WORKDIR /app
10+
11+
RUN mix local.hex --force && mix local.rebar --force
12+
13+
COPY mix.lock mix.exs ./
14+
15+
# Get both dev and test env dependencies
16+
RUN MIX_ENV="dev" mix do deps.get --only dev, deps.compile
17+
# RUN MIX_ENV="test" mix do deps.get --only test, deps.compile
18+
19+
# Copy the source code to the docker image.
20+
COPY . .
21+
22+
# Pre-compile dev & test environments
23+
RUN MIX_ENV="dev" mix compile
24+
# RUN MIX_ENV="test" mix compile
25+
26+
ENTRYPOINT "./startup.sh"
27+
CMD iex -S mix
28+
# CMD iex --sname wargame_tools --erl "-config sys.config -setcookie mycookie" -S mix
29+

eth_client/lib/eth_client.ex

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,13 @@ defmodule EthClient do
1313
4 => "rinkeby."
1414
}
1515

16+
@supported_chains ["ropsten", "rinkeby", "mainnet"]
17+
18+
@chain_id_by_name %{"mainnet" => 1, "ropsten" => 3, "rinkeby" => 4}
19+
20+
@local_host_chain_id 1234
21+
@local_host_rpc "http://localhost:8545"
22+
1623
def deploy(bin_path) do
1724
{:ok, data} = File.read(bin_path)
1825
data = add_0x(data)
@@ -80,7 +87,7 @@ defmodule EthClient do
8087
wei_to_ether(balance)
8188
end
8289

83-
def invoke(method, arguments, amount) do
90+
def invoke(method, arguments, amount \\ 0) do
8491
data =
8592
ABI.encode(method, arguments)
8693
|> Base.encode16(case: :lower)
@@ -134,6 +141,21 @@ defmodule EthClient do
134141
end
135142
end
136143

144+
def set_chain(chain_name) when chain_name in @supported_chains do
145+
infura_api_key = Context.infura_api_key()
146+
Context.set_rpc_host("https://#{chain_name}.infura.io/v3/#{infura_api_key}")
147+
Context.set_chain_id(@chain_id_by_name[chain_name])
148+
end
149+
150+
def set_chain("local") do
151+
Context.set_rpc_host(@local_host_rpc)
152+
Context.set_chain_id(@local_host_chain_id)
153+
end
154+
155+
def set_chain(chain_name) do
156+
Logger.info("#{chain_name} is not a supported chain.")
157+
end
158+
137159
defp nonce(address) do
138160
{nonce, ""} =
139161
Rpc.get_transaction_count(address)

eth_client/lib/eth_client/context.ex

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,13 @@ defmodule EthClient.Context do
1717
def contract, do: get(:contract)
1818
def etherscan_api_key, do: get(:etherscan_api_key)
1919
def net_proxy, do: get(:net_proxy)
20+
def infura_api_key, do: get(:infura_api_key)
2021

2122
def set_rpc_host(new_host), do: set(:rpc_host, new_host)
2223
def set_chain_id(new_chain_id), do: set(:chain_id, new_chain_id)
2324
def set_user_account(new_user_account), do: set(:user_account, new_user_account)
2425
def set_net_proxy(proxy), do: set(:net_proxy, proxy)
26+
def set_infura_api_key(api_key), do: set(:infura_api_key, api_key)
2527

2628
def set_contract_address(new_address) do
2729
IEx.configure(default_prompt: "#{String.slice(new_address, 0..5)}>")

eth_client/lib/eth_client/contract.ex

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ defmodule EthClient.Contract do
22
@moduledoc false
33

44
alias EthClient.ABI
5+
alias EthClient.Contract.Opcodes
6+
alias EthClient.Rpc
57

68
defstruct [:address, :functions]
79

@@ -13,6 +15,17 @@ defmodule EthClient.Contract do
1315
end
1416
end
1517

18+
def to_opcodes do
19+
EthClient.Context.contract().address
20+
|> contract_to_opcodes()
21+
end
22+
23+
def contract_to_opcodes(address) when is_binary(address) do
24+
with {:ok, code} <- Rpc.get_code(address) do
25+
Opcodes.bytecode_to_opcodes(code)
26+
end
27+
end
28+
1629
defp parse_abi(abi), do: parse_abi(abi, %{})
1730

1831
defp parse_abi([], acc), do: {:ok, acc}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
defmodule EthClient.Contract.Opcodes do
2+
alias EthClient.Rpc
3+
4+
@moduledoc """
5+
This module provides a function to turn any valid EVM byte code
6+
into opcodes and a function to retrieve a contract and turn it into
7+
its opcodes.
8+
"""
9+
def bytecode_to_opcodes(code) when is_binary(code) do
10+
parse_code(code, [])
11+
end
12+
13+
defp get_opcodes do
14+
opcodes_from_file!()
15+
|> parse_opcodes()
16+
end
17+
18+
defp opcodes_from_file! do
19+
"./opcodes.json"
20+
|> Path.expand()
21+
|> File.read!()
22+
end
23+
24+
defp parse_opcodes(codes) do
25+
codes
26+
|> Jason.decode!()
27+
|> filter_invalid()
28+
end
29+
30+
defp filter_invalid(code_list) do
31+
Enum.reduce(code_list, fn
32+
%{"Hex" => _hex, "Name" => name}, acc when name == "*invalid*" -> acc
33+
%{"Hex" => hex, "Name" => name}, acc -> Map.put(acc, hex, name)
34+
end)
35+
end
36+
37+
# First remove the leading 0x,
38+
# upcase to keep it consistent with the JSON.
39+
defp parse_code(<<"0x", rest::binary>>, []) do
40+
rest
41+
|> String.upcase()
42+
|> parse_code([], get_opcodes())
43+
end
44+
45+
# Opcodes are base16 numbers ranging from
46+
# 00 up to FF, they come inside a string,
47+
# so we match them every 2 characters and
48+
# check the instruction matching those two characters
49+
# Let's say we have FFAAFF, this function clause
50+
# would match like this:
51+
# opcode = "FF"
52+
# rest = "AAFF"
53+
# And FF matches with the "SELFDESTRUCT" instruction.
54+
defp parse_code(<<opcode::binary-size(2), rest::binary>>, acum, opcodes) do
55+
case Map.get(opcodes, opcode) do
56+
nil ->
57+
parse_code(rest, ["#{opcode} opcode is unknown" | acum], opcodes)
58+
59+
<<"PUSH", n::binary>> ->
60+
{arguments, rest} = fetch_arguments(rest, n, :push)
61+
parse_code(rest, ["PUSH 0x#{arguments}" | acum], opcodes)
62+
63+
instruction ->
64+
parse_code(rest, [instruction | acum], opcodes)
65+
end
66+
end
67+
68+
# When this matches, we have finished parsing the string.
69+
defp parse_code(_, acum, _) do
70+
acum
71+
|> Enum.reverse()
72+
|> Enum.with_index(fn string, index -> "[#{index}] " <> string end)
73+
|> Enum.join("\n")
74+
|> IO.puts()
75+
end
76+
77+
defp fetch_arguments(code, n, :push) when is_binary(n) do
78+
chars_to_fetch = String.to_integer(n) * 2
79+
<<arguments::binary-size(chars_to_fetch), rest::binary>> = code
80+
{arguments, rest}
81+
end
82+
end

eth_client/lib/eth_client/rpc.ex

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ defmodule EthClient.Rpc do
1717
def gas_price, do: send_request("eth_gasPrice", [])
1818
def get_transaction_by_hash(tx_hash), do: send_request("eth_getTransactionByHash", [tx_hash])
1919
def get_transaction_receipt(tx_hash), do: send_request("eth_getTransactionReceipt", [tx_hash])
20+
def get_code(contract), do: send_request("eth_getCode", [contract, "latest"])
2021
def call(call_map), do: send_request("eth_call", [call_map, "latest"])
2122

2223
def get_logs(log_map), do: send_request("eth_getLogs", [log_map])

eth_client/mix.exs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ defmodule EthClient.MixProject do
3131
{:rustler, "~> 0.25.0"},
3232
{:ex_rlp, "~> 0.5.4"},
3333
{:dialyxir, "~> 1.1", only: [:dev, :test], runtime: false},
34-
{:excoveralls, "~> 0.14.5"}
34+
{:excoveralls, "~> 0.14.5", only: [:test], runtime: false}
3535
]
3636
end
3737
end

0 commit comments

Comments
 (0)