Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Resolve #88 #109

Merged
merged 3 commits into from
Mar 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions libraries/eosiolib/capi/eosio/action.h
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,16 @@ uint64_t publication_time( void );
__attribute__((eosio_wasm_import))
capi_name current_receiver( void );

/**
* Get the hash of the code currently published on the given account
* @param account Name of the account to hash the code of
* @param struct_version Version specifying the desired format of the returned struct
* @param result_buffer Buffer wherein the result should be written
* @param buffer_size Size in bytes of result_buffer
*/
__attribute__((eosio_wasm_import))
uint32_t get_code_hash( uint64_t account, uint32_t struct_version, char* result_buffer, size_t buffer_size );

/**
* Set the action return value which will be included in the action_receipt
* @brief Set the action return value
Expand Down
59 changes: 59 additions & 0 deletions libraries/eosiolib/contracts/eosio/action.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include "../../core/eosio/serialize.hpp"
#include "../../core/eosio/datastream.hpp"
#include "../../core/eosio/name.hpp"
#include "../../core/eosio/fixed_bytes.hpp"
#include "../../core/eosio/ignore.hpp"
#include "../../core/eosio/time.hpp"

Expand Down Expand Up @@ -52,9 +53,23 @@ namespace eosio {

__attribute__((eosio_wasm_import))
uint64_t current_receiver();

__attribute__((eosio_wasm_import))
uint32_t get_code_hash( uint64_t account, uint32_t struct_version, char* result_buffer, size_t buffer_size );
}
};

struct code_hash_result {
unsigned_int struct_version;
uint64_t code_sequence;
checksum256 code_hash;
uint8_t vm_type;
uint8_t vm_version;

CDT_REFLECT(struct_version, code_sequence, code_hash, vm_type, vm_version);
EOSLIB_SERIALIZE(code_hash_result, (struct_version)(code_sequence)(code_hash)(vm_type)(vm_version));
};

/**
* @defgroup action Action
* @ingroup contracts
Expand Down Expand Up @@ -150,6 +165,50 @@ namespace eosio {
return name{internal_use_do_not_use::current_receiver()};
}

/**
* Get the hash of the code currently published on the given account
* @param account Name of the account to hash the code of
* @param full_result Optional: If a full result struct is desired, a pointer to the struct to populate
* @return The SHA256 hash of the specified account's code
*/
inline checksum256 get_code_hash( name account, code_hash_result* full_result = nullptr ) {
if (full_result == nullptr)
full_result = (code_hash_result*)alloca(sizeof(code_hash_result));
constexpr size_t max_stack_buffer_size = 50;

// Packed size of this struct will virtually always be less than the struct size; always less after padding
auto struct_buffer_size = sizeof(code_hash_result);
char* struct_buffer = (char*)alloca(struct_buffer_size);

using VersionType = decltype(code_hash_result::struct_version);
const VersionType STRUCT_VERSION = 0;
auto response_size =
internal_use_do_not_use::get_code_hash(account.value, STRUCT_VERSION, struct_buffer, struct_buffer_size);
// Safety check: in this case, response size should never exceed our buffer, but just in case...
bool buffer_on_heap = false;
if (response_size > struct_buffer_size) {
// Slow path: allocate an adequate buffer and try again
// No need to deallocate struct_buffer since it was alloca'd
if (response_size > max_stack_buffer_size) {
struct_buffer = (char*)malloc(response_size);
buffer_on_heap = true;
} else {
struct_buffer = (char*)alloca(response_size);
}
internal_use_do_not_use::get_code_hash(account.value, STRUCT_VERSION, struct_buffer, struct_buffer_size);
}

check(unpack<VersionType>(struct_buffer, struct_buffer_size) == STRUCT_VERSION,
"Hypervisor returned unexpected code hash struct version");
unpack(*full_result, struct_buffer, struct_buffer_size);

// If struct_buffer is heap allocated, we must free it
if (buffer_on_heap)
free(struct_buffer);

return full_result->code_hash;
}

/**
* Copy up to length bytes of current action data to the specified location
*
Expand Down
5 changes: 5 additions & 0 deletions tests/integration/contracts.hpp.in
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,10 @@ namespace eosio::testing {

static std::vector<uint8_t> crypto_primitives_test_wasm() { return read_wasm("${CMAKE_BINARY_DIR}/../unit/test_contracts/crypto_primitives_tests.wasm"); }
static std::vector<char> crypto_primitives_test_abi() { return read_abi("${CMAKE_BINARY_DIR}/../unit/test_contracts/crypto_primitives_tests.abi"); }

static std::vector<uint8_t> get_code_hash_write_test_wasm() { return read_wasm("${CMAKE_BINARY_DIR}/../unit/test_contracts/get_code_hash_write.wasm"); }
static std::vector<char> get_code_hash_write_test_abi() { return read_abi("${CMAKE_BINARY_DIR}/../unit/test_contracts/get_code_hash_write.abi"); }
static std::vector<uint8_t> get_code_hash_read_test_wasm() { return read_wasm("${CMAKE_BINARY_DIR}/../unit/test_contracts/get_code_hash_read.wasm"); }
static std::vector<char> get_code_hash_read_test_abi() { return read_abi("${CMAKE_BINARY_DIR}/../unit/test_contracts/get_code_hash_read.abi"); }
};
} //ns eosio::testing
46 changes: 46 additions & 0 deletions tests/integration/get_code_hash_tests.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
#include <boost/test/unit_test.hpp>
#include <eosio/testing/tester.hpp>
#include <eosio/chain/abi_serializer.hpp>

#include <Runtime/Runtime.h>

#include <fc/variant_object.hpp>

#include <contracts.hpp>

using namespace eosio;
using namespace eosio::testing;
using namespace eosio::chain;
using namespace fc;

using mvo = fc::mutable_variant_object;

struct code_hash {
uint64_t id;
fc::sha256 hash;
uint64_t primary_key() const { return id; }
};
FC_REFLECT(code_hash, (id)(hash))

BOOST_AUTO_TEST_SUITE(get_code_hash_tests_suite)

BOOST_FIXTURE_TEST_CASE( get_code_hash_tests, tester ) try {
create_accounts( { "test"_n } );
produce_block();

set_code( "test"_n, contracts::get_code_hash_write_test_wasm() );
set_abi( "test"_n, contracts::get_code_hash_write_test_abi().data() );

produce_blocks();
push_action("test"_n, "theaction"_n, "test"_n, mvo());
code_hash entry;
get_table_entry(entry, "test"_n, "test"_n, "code.hash"_n, 0);
wdump((entry.hash));

set_code( "test"_n, contracts::get_code_hash_read_test_wasm() );
produce_blocks();

push_action("test"_n, "theaction"_n, "test"_n, mvo());
} FC_LOG_AND_RETHROW()

BOOST_AUTO_TEST_SUITE_END()
2 changes: 2 additions & 0 deletions tests/unit/test_contracts/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ add_contract(explicit_nested_tests explicit_nested_tests explicit_nested_tests.c
add_contract(transfer_contract transfer_contract transfer.cpp)
add_contract(minimal_tests minimal_tests minimal_tests.cpp)
add_contract(crypto_primitives_tests crypto_primitives_tests crypto_primitives_tests.cpp)
add_contract(get_code_hash_tests get_code_hash_write get_code_hash_write.cpp)
add_contract(get_code_hash_tests get_code_hash_read get_code_hash_read.cpp)
add_contract(capi_tests capi_tests capi/capi.c capi/action.c capi/chain.c capi/crypto.c capi/db.c capi/permission.c
capi/print.c capi/privileged.c capi/system.c capi/transaction.c)

Expand Down
1 change: 1 addition & 0 deletions tests/unit/test_contracts/capi/action.c
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,6 @@ void test_action( void ) {
send_context_free_inline(NULL, 0);
publication_time();
current_receiver();
get_code_hash(0, 0, NULL, 0);
set_action_return_value(NULL, 0);
}
27 changes: 27 additions & 0 deletions tests/unit/test_contracts/get_code_hash_read.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#include <eosio/eosio.hpp>
#include <eosio/action.hpp>
#include <eosio/name.hpp>

#include "get_code_hash_table.hpp"

class [[eosio::contract]] get_code_hash_tests : public contract {
public:
using contract::contract;

using hash_table = multi_index<name("code.hash"), code_hash>;

// Read the old code's hash from database and verify new code's hash differs
[[eosio::action]]
void theaction() {
require_auth(get_self());
hash_table hashes(get_self(), get_self().value);

auto hash = get_code_hash(get_self());
check(hash != checksum256(), "Code hash should not be null");

auto record = hashes.get(0, "Unable to find recorded hash");
check(hash != record.hash, "Code hash has not changed");
eosio::print("Old hash: ", record.hash, "; new hash: ", hash);
}
};

10 changes: 10 additions & 0 deletions tests/unit/test_contracts/get_code_hash_table.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#pragma once

using namespace eosio;

TABLE code_hash {
uint64_t id;
checksum256 hash;
uint64_t primary_key() const { return id; }
};

28 changes: 28 additions & 0 deletions tests/unit/test_contracts/get_code_hash_write.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#include <eosio/eosio.hpp>
#include <eosio/action.hpp>
#include <eosio/name.hpp>

#include "get_code_hash_table.hpp"

class [[eosio::contract]] get_code_hash_tests : public contract {
public:
using contract::contract;

using hash_table = multi_index<name("code.hash"), code_hash>;

// Write this code's hash to database
[[eosio::action]]
void theaction() {
require_auth(get_self());
hash_table hashes(get_self(), get_self().value);

auto hash = get_code_hash(get_self());
check(hash != checksum256(), "Code hash should not be null");

hashes.emplace(get_self(), [&hash](auto& t) {
t.id = 0;
t.hash = hash;
});
}
};