Skip to content

Commit

Permalink
feat: resolve aens name to contract address when calling contract (#1710
Browse files Browse the repository at this point in the history
)
  • Loading branch information
yaboiishere committed Apr 4, 2024
1 parent f220f48 commit 65575cb
Show file tree
Hide file tree
Showing 12 changed files with 369 additions and 28 deletions.
34 changes: 30 additions & 4 deletions lib/ae_mdw/contract.ex
Expand Up @@ -3,6 +3,8 @@ defmodule AeMdw.Contract do
AE smart contracts type (signatures) and previous calls information based on direct chain info.
"""

alias AeMdw.Blocks
alias AeMdw.Db.Name
alias AeMdw.EtsCache
alias AeMdw.Log
alias AeMdw.Node
Expand Down Expand Up @@ -57,6 +59,8 @@ defmodule AeMdw.Contract do
@type fun_arg_res_or_error :: fun_arg_res() | {:error, any()}
@type local_idx :: non_neg_integer()
@type code() :: binary()
@type name_pubkey :: pubkey()
@type contract_pubkey :: pubkey()
@opaque aecontract() :: tuple()
@typep pubkey :: DBN.pubkey()
@typep tx :: Node.tx()
Expand Down Expand Up @@ -213,13 +217,14 @@ defmodule AeMdw.Contract do
|> call_rec_from_id(contract_pk, block_hash)
end

@spec call_tx_info(tx(), DBN.pubkey(), block_hash()) :: {fun_arg_res_or_error(), call()}
def call_tx_info(tx_rec, contract_pk, block_hash) do
@spec call_tx_info(tx(), DBN.pubkey(), DBN.pubkey(), block_hash()) ::
{fun_arg_res_or_error(), call()}
def call_tx_info(tx_rec, contract_pk, contract_or_name_pk, block_hash) do
{:ok, {type_info, _compiler_vsn, _source_hash}} = get_info(contract_pk)
call_id = :aect_call_tx.call_id(tx_rec)
call_data = :aect_call_tx.call_data(tx_rec)

case :aec_chain.get_contract_call(contract_pk, call_id, block_hash) do
case :aec_chain.get_contract_call(contract_or_name_pk, call_id, block_hash) do
{:ok, call} ->
case :aect_call.return_type(call) do
:ok ->
Expand All @@ -242,7 +247,7 @@ defmodule AeMdw.Contract do

{:error, reason} ->
Log.error(
"call_tx_info error reason=#{inspect(reason)} params=#{inspect([contract_pk, call_id, block_hash])}"
"call_tx_info error reason=#{inspect(reason)} params=#{inspect([contract_or_name_pk, call_id, block_hash])}"
)

{{:error, reason}, nil}
Expand Down Expand Up @@ -346,6 +351,27 @@ defmodule AeMdw.Contract do
Enum.group_by(get_events(micro_block), fn {_event_name, %{tx_hash: tx_hash}} -> tx_hash end)
end

@spec maybe_resolve_contract_pk(name_pubkey() | contract_pubkey(), Blocks.block_hash()) ::
contract_pubkey()
def maybe_resolve_contract_pk(contract_or_name_pk, block_hash) do
contract_or_name_pk
|> case do
<<217, 52, 92, _rest::binary>> = name_pk ->
block_hash
|> Name.ptr_resolve(name_pk, "contract_pubkey")
|> case do
{:ok, contract_pk} ->
contract_pk

{:error, reason} ->
raise "Contract not resolved: #{inspect(contract_or_name_pk)} with reason #{inspect(reason)}"
end

contract_pk ->
contract_pk
end
end

#
# Private functions
#
Expand Down
19 changes: 15 additions & 4 deletions lib/ae_mdw/db/format.ex
Expand Up @@ -125,10 +125,16 @@ defmodule AeMdw.Db.Format do
end

defp custom_raw_data(state, :contract_call_tx, tx, tx_rec, signed_tx, block_hash) do
contract_pk = tx_rec |> :aect_call_tx.contract_id() |> Db.id_pubkey()
contract_or_name_pk =
tx_rec
|> :aect_call_tx.contract_id()
|> Db.id_pubkey()

contract_pk = Contract.maybe_resolve_contract_pk(contract_or_name_pk, block_hash)

txi = tx.tx_index
fun_arg_res = AeMdw.Db.Contract.call_fun_arg_res(state, contract_pk, txi)
call_info = format_call_info(signed_tx, contract_pk, block_hash, txi)
call_info = format_call_info(signed_tx, contract_or_name_pk, block_hash, txi)
call_details = Map.merge(call_info, fun_arg_res)
update_in(tx, [:tx], &Map.merge(&1, call_details))
end
Expand Down Expand Up @@ -406,7 +412,12 @@ defmodule AeMdw.Db.Format do
end

defp custom_encode(state, :contract_call_tx, tx, tx_rec, signed_tx, txi, block_hash) do
contract_pk = tx_rec |> :aect_call_tx.contract_id() |> Db.id_pubkey()
contract_or_name_pk =
tx_rec
|> :aect_call_tx.contract_id()
|> Db.id_pubkey()

contract_pk = Contract.maybe_resolve_contract_pk(contract_or_name_pk, block_hash)

fun_arg_res =
state
Expand All @@ -415,7 +426,7 @@ defmodule AeMdw.Db.Format do

call_details =
signed_tx
|> format_call_info(contract_pk, block_hash, txi)
|> format_call_info(contract_or_name_pk, block_hash, txi)
|> Map.merge(fun_arg_res)

aexn_type = DbContract.get_aexn_type(state, contract_pk)
Expand Down
16 changes: 12 additions & 4 deletions lib/ae_mdw/db/mutations/write_fields_mutation.ex
Expand Up @@ -70,14 +70,22 @@ defmodule AeMdw.Db.WriteFieldsMutation do
end)
end

defp resolve_pubkey(state, id, :spend_tx, :recipient_id, block_index) do
defp resolve_pubkey(state, id, type, field, block_index)
when type == :spend_tx and field == :recipient_id
when type == :contract_call_tx and field == :contract_id do
pointee =
case type do
:spend_tx -> "account_pubkey"
:contract_call_tx -> "contract_pubkey"
end

with {:name, name_hash} <- :aeser_id.specialize(id),
{:ok, account_pk} <- Name.ptr_resolve(state, block_index, name_hash) do
account_pk
{:ok, contract_pk} <- Name.ptr_resolve(state, block_index, name_hash, pointee) do
contract_pk
else
{:error, :name_revoked} ->
Log.warn(
"Revoked name used on spend! id: #{inspect(id)}, block_index: #{inspect(block_index)}"
"Revoked name used on contract call! id: #{inspect(id)}, block_index: #{inspect(block_index)}"
)

Name.last_update_pointee_pubkey(state, id)
Expand Down
26 changes: 21 additions & 5 deletions lib/ae_mdw/db/name.ex
Expand Up @@ -55,12 +55,23 @@ defmodule AeMdw.Db.Name do
plain_name
end

@spec ptr_resolve(state(), Blocks.block_index(), binary()) ::
@spec ptr_resolve(state(), Blocks.block_index(), binary(), binary()) ::
{:ok, binary()} | {:error, :name_revoked}
def ptr_resolve(state, block_index, name_hash) do
with {:ok, account_id} <-
:aens.resolve_hash("account_pubkey", name_hash, ns_tree!(state, block_index)) do
{:ok, Validate.id(account_id)}
def ptr_resolve(state, block_index, name_hash, pointer_key) do
with {:ok, id} <-
:aens.resolve_hash(pointer_key, name_hash, ns_tree!(state, block_index)),
{_type, specialized_id} <- :aeser_id.specialize(id) do
{:ok, specialized_id}
end
end

@spec ptr_resolve(Blocks.block_hash(), binary(), binary()) ::
{:ok, binary()} | {:error, :name_revoked}
def ptr_resolve(block_hash, name_hash, pointer_key) do
with {:ok, id} <-
:aens.resolve_hash(pointer_key, name_hash, ns_tree!(block_hash)),
{_type, specialized_id} <- :aeser_id.specialize(id) do
{:ok, specialized_id}
end
end

Expand Down Expand Up @@ -310,6 +321,11 @@ defmodule AeMdw.Db.Name do
state
|> State.fetch!(Model.Block, block_index)
|> Model.block(:hash)
|> ns_tree!()
end

defp ns_tree!(block_hash) do
block_hash
|> :aec_db.get_block_state()
|> :aec_trees.ns()
end
Expand Down
11 changes: 9 additions & 2 deletions lib/ae_mdw/db/sync/transaction.ex
Expand Up @@ -167,12 +167,19 @@ defmodule AeMdw.Db.Sync.Transaction do
tx_events: tx_events,
tx_hash: tx_hash
}) do
contract_pk =
contract_or_name_pk =
tx
|> :aect_call_tx.contract_id()
|> Db.id_pubkey()

{fun_arg_res, call_rec} = Contract.call_tx_info(tx, contract_pk, block_hash)
contract_pk =
Contract.maybe_resolve_contract_pk(
contract_or_name_pk,
block_hash
)

{fun_arg_res, call_rec} =
Contract.call_tx_info(tx, contract_pk, contract_or_name_pk, block_hash)

events_mutations =
SyncContract.events_mutations(
Expand Down
80 changes: 79 additions & 1 deletion test/ae_mdw/contract_test.exs
Expand Up @@ -88,7 +88,85 @@ defmodule AeMdw.ContractTest do
get_contract_call: fn ^contract_pk, ^call_id, ^block_hash -> {:ok, call} end
]}
] do
assert {:error, ^call} = Contract.call_tx_info(call_tx, contract_pk, block_hash)
assert {:error, ^call} =
Contract.call_tx_info(call_tx, contract_pk, contract_pk, block_hash)
end
end
end

describe "maybe_resolve_contract_pk/2" do
setup do
contract_pk = <<1::256>>

name_pk =
<<217, 52, 92, 99, 90, 59, 250, 112, 123, 114, 18, 135, 95, 95, 205, 71, 74, 160, 14, 22,
47, 119, 229, 124, 220, 96, 81, 40, 117, 5, 23, 138>>

fake_mb = "mh_2XGZtE8jxUs2NymKHsytkaLLrk6KY2t2w1FjJxtAUqYZn8Wsdd"

success_mock =
{:aens, [],
[
resolve_hash: fn "contract_pubkey", ^name_pk, _block ->
{:ok, {:id, :contract, contract_pk}}
end
]}

fail_mock =
{:aens, [],
[
resolve_hash: fn "contract_pubkey", ^name_pk, _block ->
{:error, :name_not_resolved}
end
]}

common_mocks = [
{:aec_db, [],
[
get_block_state: fn _block_hash -> {:ok, %{}} end
]},
{:aec_trees, [],
[
ns: fn _ns_tree -> {:ok, %{}} end
]}
]

%{
contract_pk: contract_pk,
name_pk: name_pk,
fake_mb: fake_mb,
success_mocks: [success_mock | common_mocks],
fail_mocks: [fail_mock | common_mocks]
}
end

test "it returns contract_pk when name contains contract_pubkey pointer", %{
contract_pk: contract_pk,
name_pk: name_pk,
fake_mb: fake_mb,
success_mocks: success_mocks
} do
with_mocks(success_mocks) do
assert ^contract_pk = Contract.maybe_resolve_contract_pk(name_pk, fake_mb)
end
end

test "it returns contract_pk when contract_pk is passed", %{
contract_pk: contract_pk,
fake_mb: fake_mb
} do
assert ^contract_pk = Contract.maybe_resolve_contract_pk(contract_pk, fake_mb)
end

test "it returns :error when contract isn't resolved", %{
name_pk: name_pk,
fake_mb: fake_mb,
fail_mocks: fail_mocks
} do
with_mocks fail_mocks do
assert_raise RuntimeError,
"Contract not resolved: #{inspect(name_pk)} with reason :name_not_resolved",
fn -> Contract.maybe_resolve_contract_pk(name_pk, fake_mb) end
end
end
end
Expand Down
41 changes: 41 additions & 0 deletions test/ae_mdw/db/contract_call_mutation_test.exs
Expand Up @@ -2,11 +2,13 @@ defmodule AeMdw.Db.ContractCallMutationTest do
use AeMdw.Db.MutationCase, async: false

alias AeMdw.Aex9
alias AeMdw.Contract
alias AeMdw.Db.ContractCallMutation
alias AeMdw.Db.Model
alias AeMdw.Db.MemStore
alias AeMdw.Db.NullStore
alias AeMdw.Db.State
alias AeMdw.EtsCache
alias AeMdw.Stats
alias AeMdw.Validate

Expand All @@ -19,6 +21,45 @@ defmodule AeMdw.Db.ContractCallMutationTest do
@burn_caller_pk <<234, 90, 164, 101, 3, 211, 169, 40, 246, 51, 6, 203, 132, 12, 34, 114, 203,
201, 104, 124, 76, 144, 134, 158, 55, 106, 213, 160, 170, 64, 59, 72>>

describe "calling a contract" do
test "it create call contract mutation", %{store: store} do
contract_pk =
<<108, 159, 218, 252, 142, 182, 31, 215, 107, 90, 189, 201, 108, 136, 21, 96, 45, 160,
108, 218, 130, 229, 90, 80, 44, 238, 94, 180, 157, 190, 40, 100>>

call_txi = 1_000_002
assert {account_pk, mutation} = contract_call_mutation("mint", call_txi, contract_pk)
assert %ContractCallMutation{txi: ^call_txi, contract_pk: ^contract_pk} = mutation

account_pk = :aeser_api_encoder.encode(:account_pubkey, account_pk)

ct_info =
{{}, "6.1.0",
<<2, 103, 8, 248, 133, 237, 74, 109, 39, 26, 123, 35, 66, 194, 182, 216, 94, 206, 24,
202, 187, 242, 135, 194, 61, 67, 179, 242, 70, 76, 63, 199>>}

EtsCache.put(Contract, contract_pk, ct_info)

store =
store
|> Store.put(
Model.Field,
Model.field(index: {:contract_create_tx, nil, contract_pk, call_txi - 1})
)
|> change_store([mutation])

create_txi = call_txi - 1

args = [
%{type: :address, value: account_pk},
%{type: :int, value: 70_000_000_000_000_000_000}
]

assert {:ok, Model.contract_call(index: {^create_txi, ^call_txi}, fun: "mint", args: ^args)} =
Store.get(store, Model.ContractCall, {call_txi - 1, call_txi})
end
end

describe "aex9 presence" do
test "add aex9 presence after a mint", %{store: store} do
contract_pk =
Expand Down
4 changes: 2 additions & 2 deletions test/ae_mdw/db/mutations/write_fields_mutation_test.exs
Expand Up @@ -54,9 +54,9 @@ defmodule AeMdw.Db.WriteFieldsMuationTest do
mutation = WriteFieldsMutation.new(:spend_tx, spend_tx, block_index, txi)

with_mocks [
{Name, [],
{Name, [:passthrough],
[
ptr_resolve: fn _store, _block_index, _name_hash ->
ptr_resolve: fn _store, _block_index, _name_hash, "account_pubkey" ->
{:ok, Validate.id!(@fake_account2)}
end
]}
Expand Down
2 changes: 1 addition & 1 deletion test/ae_mdw/db/sync/transaction_test.exs
Expand Up @@ -145,7 +145,7 @@ defmodule AeMdw.Db.Sync.TransactionTest do
with_mocks [
{Contract, [:passthrough],
[
call_tx_info: fn _tx, ^contract_pk, _block_hash ->
call_tx_info: fn _tx, ^contract_pk, ^contract_pk, _block_hash ->
{
fun_args_res("create_pair"),
call_rec("create_pair")
Expand Down
2 changes: 1 addition & 1 deletion test/ae_mdw_web/controllers/activities_controller_test.exs
Expand Up @@ -1393,7 +1393,7 @@ defmodule AeMdwWeb.ActivitiesControllerTest do
end,
get_aexn_type: fn _state, ^contract_pk -> :aex9 end
]},
{Contract, [],
{Contract, [:passthrough],
call_rec: fn _signed_tx, ^contract_pk, _block_hash -> {:error, :nocallrec} end},
{:aec_db, [], [get_header: fn _block_hash -> :header end]},
{:aetx_sign, [],
Expand Down

0 comments on commit 65575cb

Please sign in to comment.