Skip to content

Commit

Permalink
Merge pull request #109 from dapp-protocols/issue-88
Browse files Browse the repository at this point in the history
Resolve #88
  • Loading branch information
spoonincode authored Mar 19, 2023
2 parents 7c2c7e8 + 887b129 commit 7909340
Show file tree
Hide file tree
Showing 9 changed files with 188 additions and 0 deletions.
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 @@ -9,6 +9,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 @@ -48,9 +49,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 @@ -146,6 +161,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;
});
}
};

0 comments on commit 7909340

Please sign in to comment.