defmodule Soroban.Contract.BumpFootprintExpiration do
@moduledoc """
`BumpFootprintExpiration` implementation to bump a contract.
"""
alias Soroban.Contract.RPCCalls
alias Soroban.RPC.SendTransactionResponse
alias Stellar.Horizon.Accounts
alias Stellar.TxBuild.{
Account,
BumpFootprintExpiration,
LedgerFootprint,
LedgerKey,
SCAddress,
SCVal,
SequenceNumber,
Signature,
SorobanResources,
SorobanTransactionData
}
@type durability :: :persistent | :temporary
@type data_key :: String.t()
@type keys :: list({durability(), data_key()})
@type error :: {:error, atom()}
@type contract_address :: String.t()
@type wasm_id :: String.t()
@type secret_key :: String.t()
@type ledgers_to_bump :: non_neg_integer()
@type send_response :: {:ok, SendTransactionResponse.t()}
@type bump_footprint_validation :: {:ok, BumpFootprintExpiration.t()} | error()
@type soroban_data :: SorobanTransactionData.t()
@spec bump_contract(
contract_address :: contract_address(),
secret_key :: secret_key(),
ledgers_to_bump :: ledgers_to_bump()
) :: send_response()
def bump_contract(contract_address, secret_key, ledgers_to_bump) do
with {public_key, _secret} = keypair <- Stellar.KeyPair.from_secret_seed(secret_key),
{:ok, seq_num} <- Accounts.fetch_next_sequence_number(public_key),
{:ok, bump_footprint_op} <- create_bump_footprint_op(ledgers_to_bump),
%SequenceNumber{} = sequence_number <- SequenceNumber.new(seq_num),
%SorobanTransactionData{} = soroban_data <- create_soroban_data(contract_address),
%Account{} = source_account <- Account.new(public_key),
%Signature{} = signature <- Signature.new(keypair) do
bump_footprint_op
|> RPCCalls.simulate(source_account, sequence_number, soroban_data)
|> RPCCalls.send_transaction(
source_account,
sequence_number,
signature,
bump_footprint_op
)
end
end
@spec bump_contract_wasm(
wasm_id :: wasm_id(),
secret_key :: secret_key(),
ledgers_to_bump :: ledgers_to_bump()
) :: send_response()
def bump_contract_wasm(wasm_id, secret_key, ledgers_to_bump) do
with {public_key, _secret} = keypair <- Stellar.KeyPair.from_secret_seed(secret_key),
{:ok, seq_num} <- Accounts.fetch_next_sequence_number(public_key),
{:ok, bump_footprint_op} <- create_bump_footprint_op(ledgers_to_bump),
%SequenceNumber{} = sequence_number <- SequenceNumber.new(seq_num),
%SorobanTransactionData{} = soroban_data <- create_wasm_soroban_data(wasm_id),
%Account{} = source_account <- Account.new(public_key),
%Signature{} = signature <- Signature.new(keypair) do
bump_footprint_op
|> RPCCalls.simulate(source_account, sequence_number, soroban_data)
|> RPCCalls.send_transaction(
source_account,
sequence_number,
signature,
bump_footprint_op
)
end
end
@spec bump_contract_keys(
contract_address :: contract_address(),
secret_key :: secret_key(),
ledgers_to_bump :: ledgers_to_bump(),
keys :: keys()
) :: send_response()
def bump_contract_keys(contract_address, secret_key, ledgers_to_bump, keys) do
with {public_key, _secret} = keypair <- Stellar.KeyPair.from_secret_seed(secret_key),
{:ok, seq_num} <- Accounts.fetch_next_sequence_number(public_key),
{:ok, bump_footprint_op} <- create_bump_footprint_op(ledgers_to_bump),
%SequenceNumber{} = sequence_number <- SequenceNumber.new(seq_num),
%SorobanTransactionData{} = soroban_data <- create_soroban_data(contract_address, keys),
%Account{} = source_account <- Account.new(public_key),
%Signature{} = signature <- Signature.new(keypair) do
bump_footprint_op
|> RPCCalls.simulate(source_account, sequence_number, soroban_data)
|> RPCCalls.send_transaction(
source_account,
sequence_number,
signature,
bump_footprint_op
)
end
end
@spec create_soroban_data(contract_address :: contract_address()) :: soroban_data()
defp create_soroban_data(contract_address, keys \\ []) do
with %SCAddress{} = contract_sc_address <- SCAddress.new(contract_address),
[%LedgerKey{} | _] = contract_data <- create_keys(contract_sc_address, keys),
%LedgerFootprint{} = footprint <- LedgerFootprint.new(read_only: contract_data) do
[
footprint: footprint,
instructions: 0,
read_bytes: 0,
write_bytes: 0,
extended_meta_data_size_bytes: 0
]
|> SorobanResources.new()
|> (&SorobanTransactionData.new(resources: &1, refundable_fee: 0)).()
end
end
@spec create_wasm_soroban_data(wasm_id :: wasm_id()) :: soroban_data()
defp create_wasm_soroban_data(wasm_id) do
hash = Base.decode16!(wasm_id, case: :lower)
contract_code = LedgerKey.new({:contract_code, [hash: hash, body_type: :data_entry]})
footprint = LedgerFootprint.new(read_only: [contract_code])
[
footprint: footprint,
instructions: 0,
read_bytes: 0,
write_bytes: 0,
extended_meta_data_size_bytes: 0
]
|> SorobanResources.new()
|> (&SorobanTransactionData.new(resources: &1, refundable_fee: 0)).()
end
@spec create_bump_footprint_op(ledgers_to_bump :: ledgers_to_bump()) ::
bump_footprint_validation()
defp create_bump_footprint_op(ledgers_to_bump)
when is_integer(ledgers_to_bump) and ledgers_to_bump > 0,
do: {:ok, BumpFootprintExpiration.new(ledgers_to_expire: ledgers_to_bump)}
defp create_bump_footprint_op(_ledgers_to_bump), do: {:error, :invalid_ledger_to_bump}
@spec create_keys(contract_address :: SCAddress.t(), keys :: keys()) ::
list(LedgerKey.t()) | error()
defp create_keys(contract_sc_address, []) do
key = SCVal.new(ledger_key_contract_instance: nil)
[
LedgerKey.new(
{:contract_data,
[
contract: contract_sc_address,
key: key,
durability: :persistent,
body_type: :data_entry
]}
)
]
end
defp create_keys(contract_sc_address, keys) when is_list(keys) do
Enum.map(keys, fn {durability, value} ->
data_key = SCVal.new(symbol: value)
key = SCVal.new(vec: [data_key])
LedgerKey.new(
{:contract_data,
[
contract: contract_sc_address,
key: key,
durability: durability,
body_type: :data_entry
]}
)
end)
end
defp create_keys(_contract_sc_address, _keys), do: {:error, :invalid_keys}
end