fix: vyper contracts re-verificaiton (#10053)

* Add test case for publishing re-verified solidity contract

* Add (failing) test case for publishing re-verified vyper contract

* fix: allow for vyper contracts re-verification

* Remove commented line
mf-only-health-webapp
Rim Rakhimov 6 months ago committed by GitHub
parent b377ba5588
commit 621024c046
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 40
      apps/explorer/lib/explorer/smart_contract/vyper/publisher.ex
  2. 40
      apps/explorer/test/explorer/smart_contract/solidity/publisher_test.exs
  3. 80
      apps/explorer/test/explorer/smart_contract/vyper/publisher_test.exs
  4. 42
      apps/explorer/test/support/factory.ex

@ -123,10 +123,46 @@ defmodule Explorer.SmartContract.Vyper.Publisher do
end
def publish_smart_contract(address_hash, params, abi) do
Logger.info("Publish successfully verified Vyper smart-contract #{address_hash} into the DB")
attrs = address_hash |> attributes(params, abi)
SmartContract.create_smart_contract(attrs, attrs.external_libraries, attrs.secondary_sources)
create_or_update_smart_contract(address_hash, attrs)
end
@doc """
Creates or updates a smart contract record based on its verification status.
This function first checks if a smart contract associated with the provided address hash
is already verified. If verified, it updates the existing smart contract record with the
new attributes provided, such as external libraries and secondary sources. During the update,
the contract methods are also updated: existing methods are preserved, and any new methods
from the provided ABI are added to ensure the contract's integrity and completeness.
If the smart contract is not verified, it creates a new record in the database with the
provided attributes, setting it up for verification. In this case, all contract methods
from the ABI are freshly inserted as part of the new smart contract creation.
## Parameters
- `address_hash`: The hash of the address for the smart contract.
- `attrs`: A map containing attributes such as external libraries and secondary sources.
## Returns
- `{:ok, Explorer.Chain.SmartContract.t()}`: Successfully created or updated smart
contract.
- `{:error, data}`: on failure, returning `Ecto.Changeset.t()` or, if any issues
happen during setting the address as verified, an error message.
"""
@spec create_or_update_smart_contract(binary() | Explorer.Chain.Hash.t(), %{
:secondary_sources => list(),
optional(any()) => any()
}) :: {:error, Ecto.Changeset.t() | String.t()} | {:ok, Explorer.Chain.SmartContract.t()}
def create_or_update_smart_contract(address_hash, attrs) do
Logger.info("Publish successfully verified Vyper smart-contract #{address_hash} into the DB")
if SmartContract.verified?(address_hash) do
SmartContract.update_smart_contract(attrs, attrs.external_libraries, attrs.secondary_sources)
else
SmartContract.create_smart_contract(attrs, attrs.external_libraries, attrs.secondary_sources)
end
end
defp unverified_smart_contract(address_hash, params, error, error_message, verification_with_files? \\ false) do

@ -193,5 +193,45 @@ defmodule Explorer.SmartContract.Solidity.PublisherTest do
response = Publisher.publish(contract_address.hash, params, external_libraries_form_params)
assert {:ok, %SmartContract{} = _smart_contract} = response
end
test "allows to re-verify solidity contracts" do
contract_code_info = Factory.contract_code_info_modern_compiler()
contract_address = insert(:contract_address, contract_code: contract_code_info.bytecode)
:transaction
|> insert(created_contract_address_hash: contract_address.hash, input: contract_code_info.tx_input)
|> with_block(status: :ok)
valid_attrs = %{
"contract_source_code" => contract_code_info.source_code,
"compiler_version" => contract_code_info.version,
"name" => contract_code_info.name,
"optimization" => contract_code_info.optimized
}
response = Publisher.publish(contract_address.hash, valid_attrs)
assert {:ok, %SmartContract{}} = response
updated_name = "AnotherContractName"
updated_contract_source_code =
String.replace(
valid_attrs["contract_source_code"],
"contract #{valid_attrs["name"]}",
"contract #{updated_name}"
)
valid_attrs =
valid_attrs
|> Map.put("name", updated_name)
|> Map.put("contract_source_code", updated_contract_source_code)
response = Publisher.publish(contract_address.hash, valid_attrs)
assert {:ok, %SmartContract{} = smart_contract} = response
assert smart_contract.name == valid_attrs["name"]
assert smart_contract.contract_source_code == valid_attrs["contract_source_code"]
end
end
end

@ -0,0 +1,80 @@
defmodule Explorer.SmartContract.Vyper.PublisherTest do
use ExUnit.Case, async: true
use Explorer.DataCase
doctest Explorer.SmartContract.Vyper.Publisher
@moduletag timeout: :infinity
alias Explorer.Chain.{ContractMethod, SmartContract}
alias Explorer.{Factory, Repo}
alias Explorer.SmartContract.Vyper.Publisher
setup do
configuration = Application.get_env(:explorer, Explorer.SmartContract.RustVerifierInterfaceBehaviour)
Application.put_env(:explorer, Explorer.SmartContract.RustVerifierInterfaceBehaviour, enabled: false)
on_exit(fn ->
Application.put_env(:explorer, Explorer.SmartContract.RustVerifierInterfaceBehaviour, configuration)
end)
end
describe "publish/2" do
test "with valid data creates a smart_contract" do
contract_code_info = Factory.contract_code_info_vyper()
contract_address = insert(:contract_address, contract_code: contract_code_info.bytecode)
:transaction
|> insert(created_contract_address_hash: contract_address.hash, input: contract_code_info.tx_input)
|> with_block(status: :ok)
valid_attrs = %{
"contract_source_code" => contract_code_info.source_code,
"compiler_version" => contract_code_info.version,
"name" => contract_code_info.name
}
response = Publisher.publish(contract_address.hash, valid_attrs)
assert {:ok, %SmartContract{} = smart_contract} = response
assert smart_contract.address_hash == contract_address.hash
assert smart_contract.name == valid_attrs["name"]
assert smart_contract.compiler_version == valid_attrs["compiler_version"]
assert smart_contract.contract_source_code == valid_attrs["contract_source_code"]
assert is_nil(smart_contract.constructor_arguments)
assert smart_contract.abi == contract_code_info.abi
end
test "allows to re-verify vyper contracts" do
contract_code_info = Factory.contract_code_info_vyper()
contract_address = insert(:contract_address, contract_code: contract_code_info.bytecode)
:transaction
|> insert(created_contract_address_hash: contract_address.hash, input: contract_code_info.tx_input)
|> with_block(status: :ok)
valid_attrs = %{
"contract_source_code" => contract_code_info.source_code,
"compiler_version" => contract_code_info.version,
"name" => contract_code_info.name
}
response = Publisher.publish(contract_address.hash, valid_attrs)
assert {:ok, %SmartContract{}} = response
updated_name = "AnotherContractName"
valid_attrs =
valid_attrs
|> Map.put("name", updated_name)
response = Publisher.publish(contract_address.hash, valid_attrs)
assert {:ok, %SmartContract{} = smart_contract} = response
assert smart_contract.name == valid_attrs["name"]
end
end
end

@ -451,6 +451,48 @@ defmodule Explorer.Factory do
}
end
def contract_code_info_vyper do
%{
bytecode:
"0x5f3560e01c60026001821660011b61005b01601e395f51565b63158ef93e81186100535734610057575f5460405260206040f3610053565b633fa4f245811861005357346100575760015460405260206040f35b5f5ffd5b5f80fd00180037",
tx_input:
"0x3461001c57607b6001555f5f5561005f61002060003961005f6000f35b5f80fd5f3560e01c60026001821660011b61005b01601e395f51565b63158ef93e81186100535734610057575f5460405260206040f3610053565b633fa4f245811861005357346100575760015460405260206040f35b5f5ffd5b5f80fd0018003784185f810400a16576797065728300030a0013",
name: "SimpleContract",
source_code: """
initialized: public(bool)
value: public(uint256)
@external
def __init__():
self.value = 123
self.initialized = False
""",
abi: [
%{
"inputs" => [],
"outputs" => [],
"stateMutability" => "nonpayable",
"type" => "constructor"
},
%{
"inputs" => [],
"name" => "initialized",
"outputs" => [%{"name" => "", "type" => "bool"}],
"stateMutability" => "view",
"type" => "function"
},
%{
"inputs" => [],
"name" => "value",
"outputs" => [%{"name" => "", "type" => "uint256"}],
"stateMutability" => "view",
"type" => "function"
}
],
version: "v0.3.10"
}
end
def address_hash do
{:ok, address_hash} =
"address_hash"

Loading…
Cancel
Save