Skip to content

Commit

Permalink
Add resovled uco and token movements in the contract's constants (#1035)
Browse files Browse the repository at this point in the history
* Add resovled uco and token movements in the contract's constants

* fix token_movements & add unit test

---------

Co-authored-by: Bastien CHAMAGNE <bastien@chamagne.fr>
  • Loading branch information
samuelmanzanera and bchamagne committed May 19, 2023
1 parent 87cf087 commit 67218a2
Show file tree
Hide file tree
Showing 3 changed files with 192 additions and 36 deletions.
114 changes: 85 additions & 29 deletions lib/archethic/contracts/contract/constants.ex
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ defmodule Archethic.Contracts.ContractConstants do
alias Archethic.TransactionChain.TransactionData.UCOLedger
alias Archethic.TransactionChain.TransactionData.UCOLedger.Transfer, as: UCOTransfer
alias Archethic.TransactionChain.Transaction.ValidationStamp
alias Archethic.TransactionChain.Transaction.ValidationStamp.LedgerOperations

alias Archethic.TransactionChain.Transaction.ValidationStamp.LedgerOperations.TransactionMovement

alias Archethic.Utils

@doc """
Expand Down Expand Up @@ -64,6 +68,20 @@ defmodule Archethic.Contracts.ContractConstants do
Enum.reduce(uco_transfers, %{}, fn %UCOTransfer{to: to, amount: amount}, acc ->
Map.update(acc, to, amount, &(&1 + amount))
end),
"uco_movements" =>
case validation_stamp do
nil ->
[]

%ValidationStamp{
ledger_operations: %LedgerOperations{transaction_movements: transaction_movements}
} ->
transaction_movements
|> Enum.filter(&(&1.type == :UCO))
|> Enum.reduce(%{}, fn %TransactionMovement{to: to, amount: amount}, acc ->
Map.update(acc, to, amount, &(&1 + amount))
end)
end,
"token_transfers" =>
Enum.reduce(token_transfers, %{}, fn %TokenTransfer{
to: to,
Expand All @@ -80,6 +98,31 @@ defmodule Archethic.Contracts.ContractConstants do

Map.update(acc, to, [token_transfer], &[token_transfer | &1])
end),
"token_movements" =>
case validation_stamp do
nil ->
[]

%ValidationStamp{
ledger_operations: %LedgerOperations{transaction_movements: transaction_movements}
} ->
transaction_movements
|> Enum.filter(&match?({:token, _, _}, &1.type))
|> Enum.reduce(%{}, fn %TransactionMovement{
to: to,
amount: amount,
type: {:token, token_address, token_id}
},
acc ->
token_transfer = %{
"amount" => amount,
"token_address" => token_address,
"token_id" => token_id
}

Map.update(acc, to, [token_transfer], &[token_transfer | &1])
end)
end,
"timestamp" =>
case validation_stamp do
# Happens during the transaction validation
Expand Down Expand Up @@ -187,28 +230,35 @@ defmodule Archethic.Contracts.ContractConstants do
apply_not_nil(constants, "recipients", fn recipients ->
Enum.map(recipients, &Base.encode16/1)
end),
"uco_transfers" =>
apply_not_nil(constants, "uco_transfers", fn transfers ->
transfers
|> Enum.map(fn {to, amount} ->
{Base.encode16(to), amount}
end)
|> Enum.into(%{})
end),
"uco_transfers" => apply_not_nil(constants, "uco_transfers", &uco_movements_to_string/1),
"uco_movements" => apply_not_nil(constants, "uco_movements", &uco_movements_to_string/1),
"token_transfers" =>
apply_not_nil(constants, "token_transfers", fn transfers ->
transfers
|> Enum.map(fn {to, transfers} ->
{Base.encode16(to),
Enum.map(transfers, fn transfer ->
Map.update!(transfer, "token_address", &Base.encode16/1)
end)}
end)
end),
apply_not_nil(constants, "token_transfers", &token_movements_to_string/1),
"token_movements" =>
apply_not_nil(constants, "token_movements", &token_movements_to_string/1),
"timestamp" => Map.get(constants, "timestamp")
}
end

defp uco_movements_to_string(transfers) do
transfers
|> Enum.map(fn {to, amount} ->
{Base.encode16(to), amount}
end)
|> Enum.into(%{})
end

defp token_movements_to_string(transfers) do
transfers
|> Enum.map(fn {to, transfers} ->
{Base.encode16(to),
Enum.map(transfers, fn transfer ->
Map.update!(transfer, "token_address", &Base.encode16/1)
end)}
end)
|> Enum.into(%{})
end

@doc """
Apply a function on all transactions of the constants map
"""
Expand Down Expand Up @@ -249,20 +299,26 @@ defmodule Archethic.Contracts.ContractConstants do
@spec cast_transaction_amount_to_float(map()) :: map()
def cast_transaction_amount_to_float(transaction) do
transaction
|> Map.update!("uco_transfers", fn uco_transfers ->
uco_transfers
|> Enum.map(fn {address, amount} ->
{address, Utils.from_bigint(amount)}
end)
|> Enum.into(%{})
|> Map.update!("uco_transfers", &cast_uco_movements_to_float/1)
|> Map.update!("uco_movements", &cast_uco_movements_to_float/1)
|> Map.update!("token_transfers", &cast_token_movements_to_float/1)
|> Map.update!("token_movements", &cast_token_movements_to_float/1)
end

defp cast_uco_movements_to_float(movements) do
movements
|> Enum.map(fn {address, amount} ->
{address, Utils.from_bigint(amount)}
end)
|> Map.update!("token_transfers", fn token_transfers ->
token_transfers
|> Enum.map(fn {address, token_transfer} ->
{address, Enum.map(token_transfer, &convert_token_transfer_amount_to_bigint/1)}
end)
|> Enum.into(%{})
|> Enum.into(%{})
end

defp cast_token_movements_to_float(movements) do
movements
|> Enum.map(fn {address, token_transfer} ->
{address, Enum.map(token_transfer, &convert_token_transfer_amount_to_bigint/1)}
end)
|> Enum.into(%{})
end

defp convert_token_transfer_amount_to_bigint(token_transfer) do
Expand Down
104 changes: 98 additions & 6 deletions test/archethic/contracts/contract/constants_test.exs
Original file line number Diff line number Diff line change
@@ -1,18 +1,110 @@
defmodule Archethic.Contracts.ContractConstantsTest do
use ArchethicCase

import ArchethicCase

alias Archethic.TransactionFactory
alias Archethic.TransactionChain.Transaction
alias Archethic.TransactionChain.TransactionData.Ledger
alias Archethic.TransactionChain.TransactionData.UCOLedger
alias Archethic.TransactionChain.TransactionData.UCOLedger.Transfer, as: UcoTransfer
alias Archethic.TransactionChain.TransactionData.TokenLedger
alias Archethic.TransactionChain.TransactionData.TokenLedger.Transfer, as: TokenTransfer
alias Archethic.TransactionChain.Transaction.ValidationStamp.LedgerOperations

alias Archethic.TransactionChain.Transaction.ValidationStamp.LedgerOperations.TransactionMovement

alias Archethic.Contracts.ContractConstants
alias Archethic.Utils

test "from_transaction/1 should return a map" do
tx = TransactionFactory.create_valid_transaction()
describe "from_transaction/1" do
test "should return a map" do
tx = TransactionFactory.create_valid_transaction()

constant =
tx
|> ContractConstants.from_transaction()

assert %{"type" => "transfer"} = constant
end

test "should return both uco transfer & movements" do
token_address = random_address()

uco_movement_address = random_address()
uco_movement_amount = Utils.to_bigint(2)
token_movement_address = random_address()
token_movement_amount = Utils.to_bigint(7)
uco_input_address = random_address()
uco_input_amount = Utils.to_bigint(5)
token_input_address = random_address()
token_input_amount = Utils.to_bigint(8)

ledger = %Ledger{
uco: %UCOLedger{
transfers: [
%UcoTransfer{
to: uco_input_address,
amount: uco_input_amount
}
]
},
token: %TokenLedger{
transfers: [
%TokenTransfer{
to: token_input_address,
amount: token_input_amount,
token_address: token_address,
token_id: 1
}
]
}
}

ledger_op = %LedgerOperations{
fee: Utils.to_bigint(1.337),
transaction_movements: [
%TransactionMovement{
to: uco_movement_address,
amount: uco_movement_amount,
type: :UCO
},
%TransactionMovement{
to: token_movement_address,
amount: token_movement_amount,
type: {:token, token_address, 2}
}
]
}

# This won't produce a cryptographically valid transaction
# because we override some fields after the validation stamp has been set.
# But it's fine for testing purposes
constant =
TransactionFactory.create_valid_transaction([], ledger: ledger)
|> put_in([Access.key!(:validation_stamp), Access.key!(:ledger_operations)], ledger_op)
|> ContractConstants.from_transaction()

assert %{
"uco_movements" => uco_movements,
"token_movements" => token_movements,
"uco_transfers" => uco_transfers,
"token_transfers" => token_transfers
} = constant

assert ^uco_movement_amount = uco_movements[uco_movement_address]
assert ^uco_input_amount = uco_transfers[uco_input_address]

constant =
tx
|> ContractConstants.from_transaction()
[token_movement_at_address] = token_movements[token_movement_address]
assert ^token_movement_amount = token_movement_at_address["amount"]
assert ^token_address = token_movement_at_address["token_address"]
assert 2 = token_movement_at_address["token_id"]

assert %{"type" => "transfer"} = constant
[token_transfers_at_address] = token_transfers[token_input_address]
assert ^token_input_amount = token_transfers_at_address["amount"]
assert ^token_address = token_transfers_at_address["token_address"]
assert 1 = token_transfers_at_address["token_id"]
end
end

test "to_transaction/1 should return a transaction" do
Expand Down
10 changes: 9 additions & 1 deletion test/support/transaction_factory.ex
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ defmodule Archethic.TransactionFactory do
alias Archethic.TransactionChain.Transaction.ValidationStamp.LedgerOperations.TransactionMovement

alias Archethic.TransactionChain.TransactionData
alias Archethic.TransactionChain.TransactionData.Ledger

def create_valid_transaction(
inputs \\ [],
Expand All @@ -27,11 +28,18 @@ defmodule Archethic.TransactionFactory do
index = Keyword.get(opts, :index, 0)
content = Keyword.get(opts, :content, "")
code = Keyword.get(opts, :code, "")
ledger = Keyword.get(opts, :ledger, %Ledger{})

timestamp =
Keyword.get(opts, :timestamp, DateTime.utc_now()) |> DateTime.truncate(:millisecond)

tx = Transaction.new(type, %TransactionData{content: content, code: code}, seed, index)
tx =
Transaction.new(
type,
%TransactionData{content: content, code: code, ledger: ledger},
seed,
index
)

ledger_operations =
%LedgerOperations{
Expand Down

0 comments on commit 67218a2

Please sign in to comment.