Skip to content

Commit 7909340

Browse files
authored
Merge pull request #109 from dapp-protocols/issue-88
Resolve #88
2 parents 7c2c7e8 + 887b129 commit 7909340

File tree

9 files changed

+188
-0
lines changed

9 files changed

+188
-0
lines changed

libraries/eosiolib/capi/eosio/action.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,16 @@ uint64_t publication_time( void );
168168
__attribute__((eosio_wasm_import))
169169
capi_name current_receiver( void );
170170

171+
/**
172+
* Get the hash of the code currently published on the given account
173+
* @param account Name of the account to hash the code of
174+
* @param struct_version Version specifying the desired format of the returned struct
175+
* @param result_buffer Buffer wherein the result should be written
176+
* @param buffer_size Size in bytes of result_buffer
177+
*/
178+
__attribute__((eosio_wasm_import))
179+
uint32_t get_code_hash( uint64_t account, uint32_t struct_version, char* result_buffer, size_t buffer_size );
180+
171181
/**
172182
* Set the action return value which will be included in the action_receipt
173183
* @brief Set the action return value

libraries/eosiolib/contracts/eosio/action.hpp

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#include "../../core/eosio/serialize.hpp"
1010
#include "../../core/eosio/datastream.hpp"
1111
#include "../../core/eosio/name.hpp"
12+
#include "../../core/eosio/fixed_bytes.hpp"
1213
#include "../../core/eosio/ignore.hpp"
1314
#include "../../core/eosio/time.hpp"
1415

@@ -48,9 +49,23 @@ namespace eosio {
4849

4950
__attribute__((eosio_wasm_import))
5051
uint64_t current_receiver();
52+
53+
__attribute__((eosio_wasm_import))
54+
uint32_t get_code_hash( uint64_t account, uint32_t struct_version, char* result_buffer, size_t buffer_size );
5155
}
5256
};
5357

58+
struct code_hash_result {
59+
unsigned_int struct_version;
60+
uint64_t code_sequence;
61+
checksum256 code_hash;
62+
uint8_t vm_type;
63+
uint8_t vm_version;
64+
65+
CDT_REFLECT(struct_version, code_sequence, code_hash, vm_type, vm_version);
66+
EOSLIB_SERIALIZE(code_hash_result, (struct_version)(code_sequence)(code_hash)(vm_type)(vm_version));
67+
};
68+
5469
/**
5570
* @defgroup action Action
5671
* @ingroup contracts
@@ -146,6 +161,50 @@ namespace eosio {
146161
return name{internal_use_do_not_use::current_receiver()};
147162
}
148163

164+
/**
165+
* Get the hash of the code currently published on the given account
166+
* @param account Name of the account to hash the code of
167+
* @param full_result Optional: If a full result struct is desired, a pointer to the struct to populate
168+
* @return The SHA256 hash of the specified account's code
169+
*/
170+
inline checksum256 get_code_hash( name account, code_hash_result* full_result = nullptr ) {
171+
if (full_result == nullptr)
172+
full_result = (code_hash_result*)alloca(sizeof(code_hash_result));
173+
constexpr size_t max_stack_buffer_size = 50;
174+
175+
// Packed size of this struct will virtually always be less than the struct size; always less after padding
176+
auto struct_buffer_size = sizeof(code_hash_result);
177+
char* struct_buffer = (char*)alloca(struct_buffer_size);
178+
179+
using VersionType = decltype(code_hash_result::struct_version);
180+
const VersionType STRUCT_VERSION = 0;
181+
auto response_size =
182+
internal_use_do_not_use::get_code_hash(account.value, STRUCT_VERSION, struct_buffer, struct_buffer_size);
183+
// Safety check: in this case, response size should never exceed our buffer, but just in case...
184+
bool buffer_on_heap = false;
185+
if (response_size > struct_buffer_size) {
186+
// Slow path: allocate an adequate buffer and try again
187+
// No need to deallocate struct_buffer since it was alloca'd
188+
if (response_size > max_stack_buffer_size) {
189+
struct_buffer = (char*)malloc(response_size);
190+
buffer_on_heap = true;
191+
} else {
192+
struct_buffer = (char*)alloca(response_size);
193+
}
194+
internal_use_do_not_use::get_code_hash(account.value, STRUCT_VERSION, struct_buffer, struct_buffer_size);
195+
}
196+
197+
check(unpack<VersionType>(struct_buffer, struct_buffer_size) == STRUCT_VERSION,
198+
"Hypervisor returned unexpected code hash struct version");
199+
unpack(*full_result, struct_buffer, struct_buffer_size);
200+
201+
// If struct_buffer is heap allocated, we must free it
202+
if (buffer_on_heap)
203+
free(struct_buffer);
204+
205+
return full_result->code_hash;
206+
}
207+
149208
/**
150209
* Copy up to length bytes of current action data to the specified location
151210
*

tests/integration/contracts.hpp.in

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,5 +22,10 @@ namespace eosio::testing {
2222

2323
static std::vector<uint8_t> crypto_primitives_test_wasm() { return read_wasm("${CMAKE_BINARY_DIR}/../unit/test_contracts/crypto_primitives_tests.wasm"); }
2424
static std::vector<char> crypto_primitives_test_abi() { return read_abi("${CMAKE_BINARY_DIR}/../unit/test_contracts/crypto_primitives_tests.abi"); }
25+
26+
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"); }
27+
static std::vector<char> get_code_hash_write_test_abi() { return read_abi("${CMAKE_BINARY_DIR}/../unit/test_contracts/get_code_hash_write.abi"); }
28+
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"); }
29+
static std::vector<char> get_code_hash_read_test_abi() { return read_abi("${CMAKE_BINARY_DIR}/../unit/test_contracts/get_code_hash_read.abi"); }
2530
};
2631
} //ns eosio::testing
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
#include <boost/test/unit_test.hpp>
2+
#include <eosio/testing/tester.hpp>
3+
#include <eosio/chain/abi_serializer.hpp>
4+
5+
#include <Runtime/Runtime.h>
6+
7+
#include <fc/variant_object.hpp>
8+
9+
#include <contracts.hpp>
10+
11+
using namespace eosio;
12+
using namespace eosio::testing;
13+
using namespace eosio::chain;
14+
using namespace fc;
15+
16+
using mvo = fc::mutable_variant_object;
17+
18+
struct code_hash {
19+
uint64_t id;
20+
fc::sha256 hash;
21+
uint64_t primary_key() const { return id; }
22+
};
23+
FC_REFLECT(code_hash, (id)(hash))
24+
25+
BOOST_AUTO_TEST_SUITE(get_code_hash_tests_suite)
26+
27+
BOOST_FIXTURE_TEST_CASE( get_code_hash_tests, tester ) try {
28+
create_accounts( { "test"_n } );
29+
produce_block();
30+
31+
set_code( "test"_n, contracts::get_code_hash_write_test_wasm() );
32+
set_abi( "test"_n, contracts::get_code_hash_write_test_abi().data() );
33+
34+
produce_blocks();
35+
push_action("test"_n, "theaction"_n, "test"_n, mvo());
36+
code_hash entry;
37+
get_table_entry(entry, "test"_n, "test"_n, "code.hash"_n, 0);
38+
wdump((entry.hash));
39+
40+
set_code( "test"_n, contracts::get_code_hash_read_test_wasm() );
41+
produce_blocks();
42+
43+
push_action("test"_n, "theaction"_n, "test"_n, mvo());
44+
} FC_LOG_AND_RETHROW()
45+
46+
BOOST_AUTO_TEST_SUITE_END()

tests/unit/test_contracts/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ add_contract(explicit_nested_tests explicit_nested_tests explicit_nested_tests.c
77
add_contract(transfer_contract transfer_contract transfer.cpp)
88
add_contract(minimal_tests minimal_tests minimal_tests.cpp)
99
add_contract(crypto_primitives_tests crypto_primitives_tests crypto_primitives_tests.cpp)
10+
add_contract(get_code_hash_tests get_code_hash_write get_code_hash_write.cpp)
11+
add_contract(get_code_hash_tests get_code_hash_read get_code_hash_read.cpp)
1012
add_contract(capi_tests capi_tests capi/capi.c capi/action.c capi/chain.c capi/crypto.c capi/db.c capi/permission.c
1113
capi/print.c capi/privileged.c capi/system.c capi/transaction.c)
1214

tests/unit/test_contracts/capi/action.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,6 @@ void test_action( void ) {
1313
send_context_free_inline(NULL, 0);
1414
publication_time();
1515
current_receiver();
16+
get_code_hash(0, 0, NULL, 0);
1617
set_action_return_value(NULL, 0);
1718
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
#include <eosio/eosio.hpp>
2+
#include <eosio/action.hpp>
3+
#include <eosio/name.hpp>
4+
5+
#include "get_code_hash_table.hpp"
6+
7+
class [[eosio::contract]] get_code_hash_tests : public contract {
8+
public:
9+
using contract::contract;
10+
11+
using hash_table = multi_index<name("code.hash"), code_hash>;
12+
13+
// Read the old code's hash from database and verify new code's hash differs
14+
[[eosio::action]]
15+
void theaction() {
16+
require_auth(get_self());
17+
hash_table hashes(get_self(), get_self().value);
18+
19+
auto hash = get_code_hash(get_self());
20+
check(hash != checksum256(), "Code hash should not be null");
21+
22+
auto record = hashes.get(0, "Unable to find recorded hash");
23+
check(hash != record.hash, "Code hash has not changed");
24+
eosio::print("Old hash: ", record.hash, "; new hash: ", hash);
25+
}
26+
};
27+
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
#pragma once
2+
3+
using namespace eosio;
4+
5+
TABLE code_hash {
6+
uint64_t id;
7+
checksum256 hash;
8+
uint64_t primary_key() const { return id; }
9+
};
10+
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
#include <eosio/eosio.hpp>
2+
#include <eosio/action.hpp>
3+
#include <eosio/name.hpp>
4+
5+
#include "get_code_hash_table.hpp"
6+
7+
class [[eosio::contract]] get_code_hash_tests : public contract {
8+
public:
9+
using contract::contract;
10+
11+
using hash_table = multi_index<name("code.hash"), code_hash>;
12+
13+
// Write this code's hash to database
14+
[[eosio::action]]
15+
void theaction() {
16+
require_auth(get_self());
17+
hash_table hashes(get_self(), get_self().value);
18+
19+
auto hash = get_code_hash(get_self());
20+
check(hash != checksum256(), "Code hash should not be null");
21+
22+
hashes.emplace(get_self(), [&hash](auto& t) {
23+
t.id = 0;
24+
t.hash = hash;
25+
});
26+
}
27+
};
28+

0 commit comments

Comments
 (0)