mirror of
https://github.com/ton-blockchain/ton
synced 2025-03-09 15:40:10 +00:00
updated tonlib
- updated tonlib - updated validator - updated documentation - first version of http over rldp proxy
This commit is contained in:
parent
53ec9684bd
commit
77842f9b63
128 changed files with 10555 additions and 2285 deletions
|
@ -14,15 +14,15 @@
|
|||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2017-2019 Telegram Systems LLP
|
||||
Copyright 2017-2020 Telegram Systems LLP
|
||||
*/
|
||||
#include <functional>
|
||||
#include "vm/arithops.h"
|
||||
#include "vm/log.h"
|
||||
#include "vm/opctable.h"
|
||||
#include "vm/stack.hpp"
|
||||
#include "vm/continuation.h"
|
||||
#include "vm/excno.hpp"
|
||||
#include "vm/vm.h"
|
||||
#include "common/bigint.hpp"
|
||||
#include "common/refint.h"
|
||||
|
||||
|
|
|
@ -14,16 +14,16 @@
|
|||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2017-2019 Telegram Systems LLP
|
||||
Copyright 2017-2020 Telegram Systems LLP
|
||||
*/
|
||||
#include <functional>
|
||||
#include "vm/cellops.h"
|
||||
#include "vm/log.h"
|
||||
#include "vm/opctable.h"
|
||||
#include "vm/stack.hpp"
|
||||
#include "vm/continuation.h"
|
||||
#include "vm/excno.hpp"
|
||||
#include "vm/vmstate.h"
|
||||
#include "vm/vm.h"
|
||||
#include "common/bigint.hpp"
|
||||
#include "common/refint.h"
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2017-2019 Telegram Systems LLP
|
||||
Copyright 2017-2020 Telegram Systems LLP
|
||||
*/
|
||||
#include "vm/cells/CellSlice.h"
|
||||
#include "vm/excno.hpp"
|
||||
|
@ -1026,7 +1026,7 @@ std::ostream& operator<<(std::ostream& os, Ref<CellSlice> cs_ref) {
|
|||
VirtualCell::LoadedCell load_cell_slice_impl(const Ref<Cell>& cell, bool* can_be_special) {
|
||||
auto* vm_state_interface = VmStateInterface::get();
|
||||
if (vm_state_interface) {
|
||||
vm_state_interface->register_cell_load();
|
||||
vm_state_interface->register_cell_load(cell->get_hash());
|
||||
}
|
||||
auto r_loaded_cell = cell->load_cell();
|
||||
if (r_loaded_cell.is_error()) {
|
||||
|
|
|
@ -1,3 +1,21 @@
|
|||
/*
|
||||
This file is part of TON Blockchain Library.
|
||||
|
||||
TON Blockchain Library is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
TON Blockchain Library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2019-2020 Telegram Systems LLP
|
||||
*/
|
||||
#include "CellString.h"
|
||||
#include "td/utils/misc.h"
|
||||
|
||||
|
@ -61,4 +79,79 @@ td::Result<td::string> CellString::load(CellSlice &cs, unsigned int top_bits) {
|
|||
CHECK(to.offs == (int)size);
|
||||
return res;
|
||||
}
|
||||
|
||||
td::Status CellText::store(CellBuilder &cb, td::Slice slice, unsigned int top_bits) {
|
||||
td::uint32 size = td::narrow_cast<td::uint32>(slice.size() * 8);
|
||||
return store(cb, td::BitSlice(slice.ubegin(), size), top_bits);
|
||||
}
|
||||
|
||||
td::Status CellText::store(CellBuilder &cb, td::BitSlice slice, unsigned int top_bits) {
|
||||
if (slice.size() > max_bytes * 8) {
|
||||
return td::Status::Error("String is too long (1)");
|
||||
}
|
||||
if (cb.remaining_bits() < 16) {
|
||||
return td::Status::Error("Not enough space in a builder");
|
||||
}
|
||||
if (top_bits < 16) {
|
||||
return td::Status::Error("Need at least 16 top bits");
|
||||
}
|
||||
if (slice.size() == 0) {
|
||||
cb.store_long(0, 8);
|
||||
return td::Status::OK();
|
||||
}
|
||||
unsigned int head = td::min(slice.size(), td::min(cb.remaining_bits(), top_bits) - 16) / 8 * 8;
|
||||
auto max_bits = vm::Cell::max_bits / 8 * 8;
|
||||
auto depth = 1 + (slice.size() - head + max_bits - 8 - 1) / (max_bits - 8);
|
||||
if (depth > max_chain_length) {
|
||||
return td::Status::Error("String is too long (2)");
|
||||
}
|
||||
cb.store_long(depth, 8);
|
||||
cb.store_long(head / 8, 8);
|
||||
cb.append_bitslice(slice.subslice(0, head));
|
||||
slice.advance(head);
|
||||
if (slice.size() == 0) {
|
||||
return td::Status::OK();
|
||||
}
|
||||
cb.store_ref(do_store(std::move(slice)));
|
||||
return td::Status::OK();
|
||||
}
|
||||
|
||||
td::Ref<vm::Cell> CellText::do_store(td::BitSlice slice) {
|
||||
vm::CellBuilder cb;
|
||||
unsigned int head = td::min(slice.size(), cb.remaining_bits() - 8) / 8 * 8;
|
||||
cb.store_long(head / 8, 8);
|
||||
cb.append_bitslice(slice.subslice(0, head));
|
||||
slice.advance(head);
|
||||
if (slice.size() != 0) {
|
||||
cb.store_ref(do_store(std::move(slice)));
|
||||
}
|
||||
return cb.finalize();
|
||||
}
|
||||
|
||||
template <class F>
|
||||
void CellText::for_each(F &&f, CellSlice cs) {
|
||||
auto depth = cs.fetch_ulong(8);
|
||||
|
||||
for (td::uint32 i = 0; i < depth; i++) {
|
||||
auto size = cs.fetch_ulong(8);
|
||||
f(cs.fetch_bits(td::narrow_cast<int>(size) * 8));
|
||||
if (i + 1 < depth) {
|
||||
cs = vm::load_cell_slice(cs.prefetch_ref());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
td::Result<td::string> CellText::load(CellSlice &cs) {
|
||||
unsigned int size = 0;
|
||||
for_each([&](auto slice) { size += slice.size(); }, cs);
|
||||
if (size % 8 != 0) {
|
||||
return td::Status::Error("Size is not divisible by 8");
|
||||
}
|
||||
std::string res(size / 8, 0);
|
||||
|
||||
td::BitPtr to(td::MutableSlice(res).ubegin());
|
||||
for_each([&](auto slice) { to.concat(slice); }, cs);
|
||||
CHECK(to.offs == (int)size);
|
||||
return res;
|
||||
}
|
||||
} // namespace vm
|
||||
|
|
|
@ -1,3 +1,21 @@
|
|||
/*
|
||||
This file is part of TON Blockchain Library.
|
||||
|
||||
TON Blockchain Library is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
TON Blockchain Library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2019-2020 Telegram Systems LLP
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "td/utils/Status.h"
|
||||
|
@ -13,10 +31,35 @@ class CellString {
|
|||
static td::Status store(CellBuilder &cb, td::Slice slice, unsigned int top_bits = Cell::max_bits);
|
||||
static td::Status store(CellBuilder &cb, td::BitSlice slice, unsigned int top_bits = Cell::max_bits);
|
||||
static td::Result<td::string> load(CellSlice &cs, unsigned int top_bits = Cell::max_bits);
|
||||
static td::Result<td::Ref<vm::Cell>> create(td::Slice slice, unsigned int top_bits = Cell::max_bits) {
|
||||
vm::CellBuilder cb;
|
||||
TRY_STATUS(store(cb, slice, top_bits));
|
||||
return cb.finalize();
|
||||
}
|
||||
|
||||
private:
|
||||
template <class F>
|
||||
static void for_each(F &&f, CellSlice &cs, unsigned int top_bits = Cell::max_bits);
|
||||
};
|
||||
|
||||
class CellText {
|
||||
public:
|
||||
static constexpr unsigned int max_bytes = 1024;
|
||||
static constexpr unsigned int max_chain_length = 16;
|
||||
|
||||
static td::Status store(CellBuilder &cb, td::Slice slice, unsigned int top_bits = Cell::max_bits);
|
||||
static td::Status store(CellBuilder &cb, td::BitSlice slice, unsigned int top_bits = Cell::max_bits);
|
||||
static td::Result<td::string> load(CellSlice &cs);
|
||||
static td::Result<td::Ref<vm::Cell>> create(td::Slice slice, unsigned int top_bits = Cell::max_bits) {
|
||||
vm::CellBuilder cb;
|
||||
TRY_STATUS(store(cb, slice, top_bits));
|
||||
return cb.finalize();
|
||||
}
|
||||
|
||||
private:
|
||||
template <class F>
|
||||
static void for_each(F &&f, CellSlice cs);
|
||||
static td::Ref<vm::Cell> do_store(td::BitSlice slice);
|
||||
};
|
||||
|
||||
} // namespace vm
|
||||
|
|
|
@ -14,12 +14,13 @@
|
|||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2017-2019 Telegram Systems LLP
|
||||
Copyright 2017-2020 Telegram Systems LLP
|
||||
*/
|
||||
#include "vm/dispatch.h"
|
||||
#include "vm/continuation.h"
|
||||
#include "vm/dict.h"
|
||||
#include "vm/log.h"
|
||||
#include "vm/vm.h"
|
||||
|
||||
namespace vm {
|
||||
|
||||
|
@ -625,562 +626,4 @@ void VmState::init_cregs(bool same_c3, bool push_0) {
|
|||
}
|
||||
}
|
||||
|
||||
VmState::VmState() : cp(-1), dispatch(&dummy_dispatch_table), quit0(true, 0), quit1(true, 1) {
|
||||
ensure_throw(init_cp(0));
|
||||
init_cregs();
|
||||
}
|
||||
|
||||
VmState::VmState(Ref<CellSlice> _code)
|
||||
: code(std::move(_code)), cp(-1), dispatch(&dummy_dispatch_table), quit0(true, 0), quit1(true, 1) {
|
||||
ensure_throw(init_cp(0));
|
||||
init_cregs();
|
||||
}
|
||||
|
||||
VmState::VmState(Ref<CellSlice> _code, Ref<Stack> _stack, int flags, Ref<Cell> _data, VmLog log,
|
||||
std::vector<Ref<Cell>> _libraries, Ref<Tuple> init_c7)
|
||||
: code(std::move(_code))
|
||||
, stack(std::move(_stack))
|
||||
, cp(-1)
|
||||
, dispatch(&dummy_dispatch_table)
|
||||
, quit0(true, 0)
|
||||
, quit1(true, 1)
|
||||
, log(log)
|
||||
, libraries(std::move(_libraries)) {
|
||||
ensure_throw(init_cp(0));
|
||||
set_c4(std::move(_data));
|
||||
if (init_c7.not_null()) {
|
||||
set_c7(std::move(init_c7));
|
||||
}
|
||||
init_cregs(flags & 1, flags & 2);
|
||||
}
|
||||
|
||||
VmState::VmState(Ref<CellSlice> _code, Ref<Stack> _stack, const GasLimits& gas, int flags, Ref<Cell> _data, VmLog log,
|
||||
std::vector<Ref<Cell>> _libraries, Ref<Tuple> init_c7)
|
||||
: code(std::move(_code))
|
||||
, stack(std::move(_stack))
|
||||
, cp(-1)
|
||||
, dispatch(&dummy_dispatch_table)
|
||||
, quit0(true, 0)
|
||||
, quit1(true, 1)
|
||||
, log(log)
|
||||
, gas(gas)
|
||||
, libraries(std::move(_libraries)) {
|
||||
ensure_throw(init_cp(0));
|
||||
set_c4(std::move(_data));
|
||||
if (init_c7.not_null()) {
|
||||
set_c7(std::move(init_c7));
|
||||
}
|
||||
init_cregs(flags & 1, flags & 2);
|
||||
}
|
||||
|
||||
Ref<CellSlice> VmState::convert_code_cell(Ref<Cell> code_cell) {
|
||||
if (code_cell.is_null()) {
|
||||
return {};
|
||||
}
|
||||
Ref<CellSlice> csr{true, NoVmOrd(), code_cell};
|
||||
if (csr->is_valid()) {
|
||||
return csr;
|
||||
}
|
||||
return load_cell_slice_ref(CellBuilder{}.store_ref(std::move(code_cell)).finalize());
|
||||
}
|
||||
|
||||
bool VmState::init_cp(int new_cp) {
|
||||
const DispatchTable* dt = DispatchTable::get_table(new_cp);
|
||||
if (dt) {
|
||||
cp = new_cp;
|
||||
dispatch = dt;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool VmState::set_cp(int new_cp) {
|
||||
return new_cp == cp || init_cp(new_cp);
|
||||
}
|
||||
|
||||
void VmState::force_cp(int new_cp) {
|
||||
if (!set_cp(new_cp)) {
|
||||
throw VmError{Excno::inv_opcode, "unsupported codepage"};
|
||||
}
|
||||
}
|
||||
|
||||
// simple call to a continuation cont
|
||||
int VmState::call(Ref<Continuation> cont) {
|
||||
const ControlData* cont_data = cont->get_cdata();
|
||||
if (cont_data) {
|
||||
if (cont_data->save.c[0].not_null()) {
|
||||
// call reduces to a jump
|
||||
return jump(std::move(cont));
|
||||
}
|
||||
if (cont_data->stack.not_null() || cont_data->nargs >= 0) {
|
||||
// if cont has non-empty stack or expects fixed number of arguments, call is not simple
|
||||
return call(std::move(cont), -1, -1);
|
||||
}
|
||||
// create return continuation, to be stored into new c0
|
||||
Ref<OrdCont> ret = Ref<OrdCont>{true, std::move(code), cp};
|
||||
ret.unique_write().get_cdata()->save.set_c0(std::move(cr.c[0]));
|
||||
cr.set_c0(
|
||||
std::move(ret)); // set c0 to its final value before switching to cont; notice that cont.save.c0 is not set
|
||||
return jump_to(std::move(cont));
|
||||
}
|
||||
// create return continuation, to be stored into new c0
|
||||
Ref<OrdCont> ret = Ref<OrdCont>{true, std::move(code), cp};
|
||||
ret.unique_write().get_cdata()->save.set_c0(std::move(cr.c[0]));
|
||||
// general implementation of a simple call
|
||||
cr.set_c0(std::move(ret)); // set c0 to its final value before switching to cont; notice that cont.save.c0 is not set
|
||||
return jump_to(std::move(cont));
|
||||
}
|
||||
|
||||
// call with parameters to continuation cont
|
||||
int VmState::call(Ref<Continuation> cont, int pass_args, int ret_args) {
|
||||
const ControlData* cont_data = cont->get_cdata();
|
||||
if (cont_data) {
|
||||
if (cont_data->save.c[0].not_null()) {
|
||||
// call reduces to a jump
|
||||
return jump(std::move(cont), pass_args);
|
||||
}
|
||||
int depth = stack->depth();
|
||||
if (pass_args > depth || cont_data->nargs > depth) {
|
||||
throw VmError{Excno::stk_und, "stack underflow while calling a continuation: not enough arguments on stack"};
|
||||
}
|
||||
if (cont_data->nargs > pass_args && pass_args >= 0) {
|
||||
throw VmError{Excno::stk_und,
|
||||
"stack underflow while calling a closure continuation: not enough arguments passed"};
|
||||
}
|
||||
auto old_c0 = std::move(cr.c[0]);
|
||||
// optimization(?): decrease refcnts of unused continuations in c[i] as early as possible
|
||||
preclear_cr(cont_data->save);
|
||||
// no exceptions should be thrown after this point
|
||||
int copy = cont_data->nargs, skip = 0;
|
||||
if (pass_args >= 0) {
|
||||
if (copy >= 0) {
|
||||
skip = pass_args - copy;
|
||||
} else {
|
||||
copy = pass_args;
|
||||
}
|
||||
}
|
||||
// copy=-1 : pass whole stack, else pass top `copy` elements, drop next `skip` elements.
|
||||
Ref<Stack> new_stk;
|
||||
if (cont_data->stack.not_null() && !cont_data->stack->is_empty()) {
|
||||
// `cont` already has a stack, create resulting stack from it
|
||||
if (copy < 0) {
|
||||
copy = stack->depth();
|
||||
}
|
||||
if (cont->is_unique()) {
|
||||
// optimization: avoid copying stack if we hold the only copy of `cont`
|
||||
new_stk = std::move(cont.unique_write().get_cdata()->stack);
|
||||
} else {
|
||||
new_stk = cont_data->stack;
|
||||
}
|
||||
new_stk.write().move_from_stack(get_stack(), copy);
|
||||
if (skip > 0) {
|
||||
get_stack().pop_many(skip);
|
||||
}
|
||||
} else if (copy >= 0) {
|
||||
new_stk = get_stack().split_top(copy, skip);
|
||||
} else {
|
||||
new_stk = std::move(stack);
|
||||
stack.clear();
|
||||
}
|
||||
// create return continuation using the remainder of current stack
|
||||
Ref<OrdCont> ret = Ref<OrdCont>{true, std::move(code), cp, std::move(stack), ret_args};
|
||||
ret.unique_write().get_cdata()->save.set_c0(std::move(old_c0));
|
||||
Ref<OrdCont> ord_cont = static_cast<Ref<OrdCont>>(cont);
|
||||
set_stack(std::move(new_stk));
|
||||
cr.set_c0(std::move(ret)); // ??? if codepage of code in ord_cont is unknown, will end up with incorrect c0
|
||||
return jump_to(std::move(cont));
|
||||
} else {
|
||||
// have no continuation data, situation is somewhat simpler
|
||||
int depth = stack->depth();
|
||||
if (pass_args > depth) {
|
||||
throw VmError{Excno::stk_und, "stack underflow while calling a continuation: not enough arguments on stack"};
|
||||
}
|
||||
// create new stack from the top `pass_args` elements of the current stack
|
||||
Ref<Stack> new_stk = (pass_args >= 0 ? get_stack().split_top(pass_args) : std::move(stack));
|
||||
// create return continuation using the remainder of the current stack
|
||||
Ref<OrdCont> ret = Ref<OrdCont>{true, std::move(code), cp, std::move(stack), ret_args};
|
||||
ret.unique_write().get_cdata()->save.set_c0(std::move(cr.c[0]));
|
||||
set_stack(std::move(new_stk));
|
||||
cr.set_c0(std::move(ret)); // ??? if codepage of code in ord_cont is unknown, will end up with incorrect c0
|
||||
return jump_to(std::move(cont));
|
||||
}
|
||||
}
|
||||
|
||||
// simple jump to continuation cont
|
||||
int VmState::jump(Ref<Continuation> cont) {
|
||||
const ControlData* cont_data = cont->get_cdata();
|
||||
if (cont_data && (cont_data->stack.not_null() || cont_data->nargs >= 0)) {
|
||||
// if cont has non-empty stack or expects fixed number of arguments, jump is not simple
|
||||
return jump(std::move(cont), -1);
|
||||
} else {
|
||||
return jump_to(std::move(cont));
|
||||
}
|
||||
}
|
||||
|
||||
// general jump to continuation cont
|
||||
int VmState::jump(Ref<Continuation> cont, int pass_args) {
|
||||
const ControlData* cont_data = cont->get_cdata();
|
||||
if (cont_data) {
|
||||
// first do the checks
|
||||
int depth = stack->depth();
|
||||
if (pass_args > depth || cont_data->nargs > depth) {
|
||||
throw VmError{Excno::stk_und, "stack underflow while jumping to a continuation: not enough arguments on stack"};
|
||||
}
|
||||
if (cont_data->nargs > pass_args && pass_args >= 0) {
|
||||
throw VmError{Excno::stk_und,
|
||||
"stack underflow while jumping to closure continuation: not enough arguments passed"};
|
||||
}
|
||||
// optimization(?): decrease refcnts of unused continuations in c[i] as early as possible
|
||||
preclear_cr(cont_data->save);
|
||||
// no exceptions should be thrown after this point
|
||||
int copy = cont_data->nargs;
|
||||
if (pass_args >= 0 && copy < 0) {
|
||||
copy = pass_args;
|
||||
}
|
||||
// copy=-1 : pass whole stack, else pass top `copy` elements, drop the remainder.
|
||||
if (cont_data->stack.not_null() && !cont_data->stack->is_empty()) {
|
||||
// `cont` already has a stack, create resulting stack from it
|
||||
if (copy < 0) {
|
||||
copy = get_stack().depth();
|
||||
}
|
||||
Ref<Stack> new_stk;
|
||||
if (cont->is_unique()) {
|
||||
// optimization: avoid copying the stack if we hold the only copy of `cont`
|
||||
new_stk = std::move(cont.unique_write().get_cdata()->stack);
|
||||
} else {
|
||||
new_stk = cont_data->stack;
|
||||
}
|
||||
new_stk.write().move_from_stack(get_stack(), copy);
|
||||
set_stack(std::move(new_stk));
|
||||
} else {
|
||||
if (copy >= 0) {
|
||||
get_stack().drop_bottom(stack->depth() - copy);
|
||||
}
|
||||
}
|
||||
return jump_to(std::move(cont));
|
||||
} else {
|
||||
// have no continuation data, situation is somewhat simpler
|
||||
if (pass_args >= 0) {
|
||||
int depth = get_stack().depth();
|
||||
if (pass_args > depth) {
|
||||
throw VmError{Excno::stk_und, "stack underflow while jumping to a continuation: not enough arguments on stack"};
|
||||
}
|
||||
get_stack().drop_bottom(depth - pass_args);
|
||||
}
|
||||
return jump_to(std::move(cont));
|
||||
}
|
||||
}
|
||||
|
||||
int VmState::ret() {
|
||||
Ref<Continuation> cont = quit0;
|
||||
cont.swap(cr.c[0]);
|
||||
return jump(std::move(cont));
|
||||
}
|
||||
|
||||
int VmState::ret(int ret_args) {
|
||||
Ref<Continuation> cont = quit0;
|
||||
cont.swap(cr.c[0]);
|
||||
return jump(std::move(cont), ret_args);
|
||||
}
|
||||
|
||||
int VmState::ret_alt() {
|
||||
Ref<Continuation> cont = quit1;
|
||||
cont.swap(cr.c[1]);
|
||||
return jump(std::move(cont));
|
||||
}
|
||||
|
||||
int VmState::ret_alt(int ret_args) {
|
||||
Ref<Continuation> cont = quit1;
|
||||
cont.swap(cr.c[1]);
|
||||
return jump(std::move(cont), ret_args);
|
||||
}
|
||||
|
||||
Ref<OrdCont> VmState::extract_cc(int save_cr, int stack_copy, int cc_args) {
|
||||
Ref<Stack> new_stk;
|
||||
if (stack_copy < 0 || stack_copy == stack->depth()) {
|
||||
new_stk = std::move(stack);
|
||||
stack.clear();
|
||||
} else if (stack_copy > 0) {
|
||||
stack->check_underflow(stack_copy);
|
||||
new_stk = get_stack().split_top(stack_copy);
|
||||
} else {
|
||||
new_stk = Ref<Stack>{true};
|
||||
}
|
||||
Ref<OrdCont> cc = Ref<OrdCont>{true, std::move(code), cp, std::move(stack), cc_args};
|
||||
stack = std::move(new_stk);
|
||||
if (save_cr & 7) {
|
||||
ControlData* cdata = cc.unique_write().get_cdata();
|
||||
if (save_cr & 1) {
|
||||
cdata->save.set_c0(std::move(cr.c[0]));
|
||||
cr.set_c0(quit0);
|
||||
}
|
||||
if (save_cr & 2) {
|
||||
cdata->save.set_c1(std::move(cr.c[1]));
|
||||
cr.set_c1(quit1);
|
||||
}
|
||||
if (save_cr & 4) {
|
||||
cdata->save.set_c2(std::move(cr.c[2]));
|
||||
// cr.set_c2(Ref<ExcQuitCont>{true});
|
||||
}
|
||||
}
|
||||
return cc;
|
||||
}
|
||||
|
||||
int VmState::throw_exception(int excno) {
|
||||
Stack& stack_ref = get_stack();
|
||||
stack_ref.clear();
|
||||
stack_ref.push_smallint(0);
|
||||
stack_ref.push_smallint(excno);
|
||||
code.clear();
|
||||
consume_gas(exception_gas_price);
|
||||
return jump(get_c2());
|
||||
}
|
||||
|
||||
int VmState::throw_exception(int excno, StackEntry&& arg) {
|
||||
Stack& stack_ref = get_stack();
|
||||
stack_ref.clear();
|
||||
stack_ref.push(std::move(arg));
|
||||
stack_ref.push_smallint(excno);
|
||||
code.clear();
|
||||
consume_gas(exception_gas_price);
|
||||
return jump(get_c2());
|
||||
}
|
||||
|
||||
void GasLimits::gas_exception() const {
|
||||
throw VmNoGas{};
|
||||
}
|
||||
|
||||
void GasLimits::set_limits(long long _max, long long _limit, long long _credit) {
|
||||
gas_max = _max;
|
||||
gas_limit = _limit;
|
||||
gas_credit = _credit;
|
||||
change_base(_limit + _credit);
|
||||
}
|
||||
|
||||
void GasLimits::change_limit(long long _limit) {
|
||||
_limit = std::min(std::max(_limit, 0LL), gas_max);
|
||||
gas_credit = 0;
|
||||
gas_limit = _limit;
|
||||
change_base(_limit);
|
||||
}
|
||||
|
||||
bool VmState::set_gas_limits(long long _max, long long _limit, long long _credit) {
|
||||
gas.set_limits(_max, _limit, _credit);
|
||||
return true;
|
||||
}
|
||||
|
||||
void VmState::change_gas_limit(long long new_limit) {
|
||||
VM_LOG(this) << "changing gas limit to " << std::min(new_limit, gas.gas_max);
|
||||
gas.change_limit(new_limit);
|
||||
}
|
||||
|
||||
int VmState::step() {
|
||||
assert(!code.is_null());
|
||||
//VM_LOG(st) << "stack:"; stack->dump(VM_LOG(st));
|
||||
//VM_LOG(st) << "; cr0.refcnt = " << get_c0()->get_refcnt() - 1 << std::endl;
|
||||
if (stack_trace) {
|
||||
stack->dump(std::cerr, 3);
|
||||
}
|
||||
++steps;
|
||||
if (code->size()) {
|
||||
return dispatch->dispatch(this, code.write());
|
||||
} else if (code->size_refs()) {
|
||||
VM_LOG(this) << "execute implicit JMPREF\n";
|
||||
Ref<Continuation> cont = Ref<OrdCont>{true, load_cell_slice_ref(code->prefetch_ref()), get_cp()};
|
||||
return jump(std::move(cont));
|
||||
} else {
|
||||
VM_LOG(this) << "execute implicit RET\n";
|
||||
return ret();
|
||||
}
|
||||
}
|
||||
|
||||
int VmState::run() {
|
||||
if (code.is_null()) {
|
||||
throw VmError{Excno::fatal, "cannot run an uninitialized VM"};
|
||||
}
|
||||
int res;
|
||||
Guard guard(this);
|
||||
do {
|
||||
// LOG(INFO) << "[BS] data cells: " << DataCell::get_total_data_cells();
|
||||
try {
|
||||
try {
|
||||
res = step();
|
||||
gas.check();
|
||||
} catch (vm::CellBuilder::CellWriteError) {
|
||||
throw VmError{Excno::cell_ov};
|
||||
} catch (vm::CellBuilder::CellCreateError) {
|
||||
throw VmError{Excno::cell_ov};
|
||||
} catch (vm::CellSlice::CellReadError) {
|
||||
throw VmError{Excno::cell_und};
|
||||
}
|
||||
} catch (const VmError& vme) {
|
||||
VM_LOG(this) << "handling exception code " << vme.get_errno() << ": " << vme.get_msg();
|
||||
try {
|
||||
// LOG(INFO) << "[EX] data cells: " << DataCell::get_total_data_cells();
|
||||
++steps;
|
||||
res = throw_exception(vme.get_errno());
|
||||
} catch (const VmError& vme2) {
|
||||
VM_LOG(this) << "exception " << vme2.get_errno() << " while handling exception: " << vme.get_msg();
|
||||
// LOG(INFO) << "[EXX] data cells: " << DataCell::get_total_data_cells();
|
||||
return ~vme2.get_errno();
|
||||
}
|
||||
} catch (VmNoGas vmoog) {
|
||||
++steps;
|
||||
VM_LOG(this) << "unhandled out-of-gas exception: gas consumed=" << gas.gas_consumed()
|
||||
<< ", limit=" << gas.gas_limit;
|
||||
get_stack().clear();
|
||||
get_stack().push_smallint(gas.gas_consumed());
|
||||
return vmoog.get_errno(); // no ~ for unhandled exceptions (to make their faking impossible)
|
||||
}
|
||||
} while (!res);
|
||||
// LOG(INFO) << "[EN] data cells: " << DataCell::get_total_data_cells();
|
||||
if ((res | 1) == -1) {
|
||||
commit();
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
ControlData* force_cdata(Ref<Continuation>& cont) {
|
||||
if (!cont->get_cdata()) {
|
||||
cont = Ref<ArgContExt>{true, cont};
|
||||
return cont.unique_write().get_cdata();
|
||||
} else {
|
||||
return cont.write().get_cdata();
|
||||
}
|
||||
}
|
||||
|
||||
ControlRegs* force_cregs(Ref<Continuation>& cont) {
|
||||
return &force_cdata(cont)->save;
|
||||
}
|
||||
|
||||
int run_vm_code(Ref<CellSlice> code, Ref<Stack>& stack, int flags, Ref<Cell>* data_ptr, VmLog log, long long* steps,
|
||||
GasLimits* gas_limits, std::vector<Ref<Cell>> libraries, Ref<Tuple> init_c7, Ref<Cell>* actions_ptr) {
|
||||
VmState vm{code,
|
||||
std::move(stack),
|
||||
gas_limits ? *gas_limits : GasLimits{},
|
||||
flags,
|
||||
data_ptr ? *data_ptr : Ref<Cell>{},
|
||||
log,
|
||||
std::move(libraries),
|
||||
std::move(init_c7)};
|
||||
int res = vm.run();
|
||||
stack = vm.get_stack_ref();
|
||||
if (vm.committed() && data_ptr) {
|
||||
*data_ptr = vm.get_committed_state().c4;
|
||||
}
|
||||
if (vm.committed() && actions_ptr) {
|
||||
*actions_ptr = vm.get_committed_state().c5;
|
||||
}
|
||||
if (steps) {
|
||||
*steps = vm.get_steps_count();
|
||||
}
|
||||
if (gas_limits) {
|
||||
*gas_limits = vm.get_gas_limits();
|
||||
LOG(INFO) << "steps: " << vm.get_steps_count() << " gas: used=" << gas_limits->gas_consumed()
|
||||
<< ", max=" << gas_limits->gas_max << ", limit=" << gas_limits->gas_limit
|
||||
<< ", credit=" << gas_limits->gas_credit;
|
||||
}
|
||||
if ((vm.get_log().log_mask & vm::VmLog::DumpStack) != 0) {
|
||||
VM_LOG(&vm) << "BEGIN_STACK_DUMP";
|
||||
for (int i = stack->depth(); i > 0; i--) {
|
||||
VM_LOG(&vm) << (*stack)[i - 1].to_string();
|
||||
}
|
||||
VM_LOG(&vm) << "END_STACK_DUMP";
|
||||
}
|
||||
|
||||
return ~res;
|
||||
}
|
||||
|
||||
int run_vm_code(Ref<CellSlice> code, Stack& stack, int flags, Ref<Cell>* data_ptr, VmLog log, long long* steps,
|
||||
GasLimits* gas_limits, std::vector<Ref<Cell>> libraries, Ref<Tuple> init_c7, Ref<Cell>* actions_ptr) {
|
||||
Ref<Stack> stk{true};
|
||||
stk.unique_write().set_contents(std::move(stack));
|
||||
stack.clear();
|
||||
int res = run_vm_code(code, stk, flags, data_ptr, log, steps, gas_limits, std::move(libraries), std::move(init_c7),
|
||||
actions_ptr);
|
||||
CHECK(stack.is_unique());
|
||||
if (stk.is_null()) {
|
||||
stack.clear();
|
||||
} else if (&(*stk) != &stack) {
|
||||
VmState* st = nullptr;
|
||||
if (stk->is_unique()) {
|
||||
VM_LOG(st) << "move resulting stack (" << stk->depth() << " entries)";
|
||||
stack.set_contents(std::move(stk.unique_write()));
|
||||
} else {
|
||||
VM_LOG(st) << "copying resulting stack (" << stk->depth() << " entries)";
|
||||
stack.set_contents(*stk);
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
// may throw a dictionary exception; returns nullptr if library is not found in context
|
||||
Ref<Cell> VmState::load_library(td::ConstBitPtr hash) {
|
||||
std::unique_ptr<VmStateInterface> tmp_ctx;
|
||||
// install temporary dummy vm state interface to prevent charging for cell load operations during library lookup
|
||||
VmStateInterface::Guard(tmp_ctx.get());
|
||||
for (const auto& lib_collection : libraries) {
|
||||
auto lib = lookup_library_in(hash, lib_collection);
|
||||
if (lib.not_null()) {
|
||||
return lib;
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
bool VmState::register_library_collection(Ref<Cell> lib) {
|
||||
if (lib.is_null()) {
|
||||
return true;
|
||||
}
|
||||
libraries.push_back(std::move(lib));
|
||||
return true;
|
||||
}
|
||||
|
||||
void VmState::register_cell_load() {
|
||||
consume_gas(cell_load_gas_price);
|
||||
}
|
||||
|
||||
void VmState::register_cell_create() {
|
||||
consume_gas(cell_create_gas_price);
|
||||
}
|
||||
|
||||
td::BitArray<256> VmState::get_state_hash() const {
|
||||
// TODO: implement properly, by serializing the stack etc, and computing the Merkle hash
|
||||
td::BitArray<256> res;
|
||||
res.clear();
|
||||
return res;
|
||||
}
|
||||
|
||||
td::BitArray<256> VmState::get_final_state_hash(int exit_code) const {
|
||||
// TODO: implement properly, by serializing the stack etc, and computing the Merkle hash
|
||||
td::BitArray<256> res;
|
||||
res.clear();
|
||||
return res;
|
||||
}
|
||||
|
||||
Ref<vm::Cell> lookup_library_in(td::ConstBitPtr key, vm::Dictionary& dict) {
|
||||
try {
|
||||
auto val = dict.lookup(key, 256);
|
||||
if (val.is_null() || !val->have_refs()) {
|
||||
return {};
|
||||
}
|
||||
auto root = val->prefetch_ref();
|
||||
if (root.not_null() && !root->get_hash().bits().compare(key, 256)) {
|
||||
return root;
|
||||
}
|
||||
return {};
|
||||
} catch (vm::VmError) {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
Ref<vm::Cell> lookup_library_in(td::ConstBitPtr key, Ref<vm::Cell> lib_root) {
|
||||
if (lib_root.is_null()) {
|
||||
return lib_root;
|
||||
}
|
||||
vm::Dictionary dict{std::move(lib_root), 256};
|
||||
return lookup_library_in(key, dict);
|
||||
}
|
||||
|
||||
} // namespace vm
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2017-2019 Telegram Systems LLP
|
||||
Copyright 2017-2020 Telegram Systems LLP
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
|
@ -371,289 +371,7 @@ class OrdCont : public Continuation {
|
|||
static Ref<OrdCont> deserialize(CellSlice& cs, int mode = 0);
|
||||
};
|
||||
|
||||
struct GasLimits {
|
||||
static constexpr long long infty = (1ULL << 63) - 1;
|
||||
long long gas_max, gas_limit, gas_credit, gas_remaining, gas_base;
|
||||
GasLimits() : gas_max(infty), gas_limit(infty), gas_credit(0), gas_remaining(infty), gas_base(infty) {
|
||||
}
|
||||
GasLimits(long long _limit, long long _max = infty, long long _credit = 0)
|
||||
: gas_max(_max)
|
||||
, gas_limit(_limit)
|
||||
, gas_credit(_credit)
|
||||
, gas_remaining(_limit + _credit)
|
||||
, gas_base(gas_remaining) {
|
||||
}
|
||||
long long gas_consumed() const {
|
||||
return gas_base - gas_remaining;
|
||||
}
|
||||
void set_limits(long long _max, long long _limit, long long _credit = 0);
|
||||
void change_base(long long _base) {
|
||||
gas_remaining += _base - gas_base;
|
||||
gas_base = _base;
|
||||
}
|
||||
void change_limit(long long _limit);
|
||||
void consume(long long amount) {
|
||||
// LOG(DEBUG) << "consume " << amount << " gas (" << gas_remaining << " remaining)";
|
||||
gas_remaining -= amount;
|
||||
}
|
||||
bool try_consume(long long amount) {
|
||||
// LOG(DEBUG) << "try consume " << amount << " gas (" << gas_remaining << " remaining)";
|
||||
return (gas_remaining -= amount) >= 0;
|
||||
}
|
||||
void gas_exception() const;
|
||||
void gas_exception(bool cond) const {
|
||||
if (!cond) {
|
||||
gas_exception();
|
||||
}
|
||||
}
|
||||
void consume_chk(long long amount) {
|
||||
gas_exception(try_consume(amount));
|
||||
}
|
||||
void check() const {
|
||||
gas_exception(gas_remaining >= 0);
|
||||
}
|
||||
bool final_ok() const {
|
||||
return gas_remaining >= gas_credit;
|
||||
}
|
||||
};
|
||||
|
||||
struct CommittedState {
|
||||
Ref<vm::Cell> c4, c5;
|
||||
bool committed{false};
|
||||
};
|
||||
|
||||
class VmState final : public VmStateInterface {
|
||||
Ref<CellSlice> code;
|
||||
Ref<Stack> stack;
|
||||
ControlRegs cr;
|
||||
CommittedState cstate;
|
||||
int cp;
|
||||
long long steps{0};
|
||||
const DispatchTable* dispatch;
|
||||
Ref<QuitCont> quit0, quit1;
|
||||
VmLog log;
|
||||
GasLimits gas;
|
||||
std::vector<Ref<Cell>> libraries;
|
||||
int stack_trace{0}, debug_off{0};
|
||||
|
||||
bool chksig_always_succeed{false};
|
||||
|
||||
public:
|
||||
static constexpr unsigned cell_load_gas_price = 100, cell_create_gas_price = 500, exception_gas_price = 50,
|
||||
tuple_entry_gas_price = 1;
|
||||
VmState();
|
||||
VmState(Ref<CellSlice> _code);
|
||||
VmState(Ref<CellSlice> _code, Ref<Stack> _stack, int flags = 0, Ref<Cell> _data = {}, VmLog log = {},
|
||||
std::vector<Ref<Cell>> _libraries = {}, Ref<Tuple> init_c7 = {});
|
||||
VmState(Ref<CellSlice> _code, Ref<Stack> _stack, const GasLimits& _gas, int flags = 0, Ref<Cell> _data = {},
|
||||
VmLog log = {}, std::vector<Ref<Cell>> _libraries = {}, Ref<Tuple> init_c7 = {});
|
||||
template <typename... Args>
|
||||
VmState(Ref<Cell> code_cell, Args&&... args)
|
||||
: VmState(convert_code_cell(std::move(code_cell)), std::forward<Args>(args)...) {
|
||||
}
|
||||
VmState(const VmState&) = delete;
|
||||
VmState(VmState&&) = delete;
|
||||
VmState& operator=(const VmState&) = delete;
|
||||
VmState& operator=(VmState&&) = delete;
|
||||
bool set_gas_limits(long long _max, long long _limit, long long _credit = 0);
|
||||
bool final_gas_ok() const {
|
||||
return gas.final_ok();
|
||||
}
|
||||
long long gas_consumed() const {
|
||||
return gas.gas_consumed();
|
||||
}
|
||||
bool committed() const {
|
||||
return cstate.committed;
|
||||
}
|
||||
const CommittedState& get_committed_state() const {
|
||||
return cstate;
|
||||
}
|
||||
void consume_gas(long long amount) {
|
||||
gas.consume(amount);
|
||||
}
|
||||
void consume_tuple_gas(unsigned tuple_len) {
|
||||
consume_gas(tuple_len * tuple_entry_gas_price);
|
||||
}
|
||||
void consume_tuple_gas(const Ref<vm::Tuple>& tup) {
|
||||
if (tup.not_null()) {
|
||||
consume_tuple_gas((unsigned)tup->size());
|
||||
}
|
||||
}
|
||||
GasLimits get_gas_limits() const {
|
||||
return gas;
|
||||
}
|
||||
void change_gas_limit(long long new_limit);
|
||||
template <typename... Args>
|
||||
void check_underflow(Args... args) {
|
||||
stack->check_underflow(args...);
|
||||
}
|
||||
bool register_library_collection(Ref<Cell> lib);
|
||||
Ref<Cell> load_library(
|
||||
td::ConstBitPtr hash) override; // may throw a dictionary exception; returns nullptr if library is not found
|
||||
void register_cell_load() override;
|
||||
void register_cell_create() override;
|
||||
bool init_cp(int new_cp);
|
||||
bool set_cp(int new_cp);
|
||||
void force_cp(int new_cp);
|
||||
int get_cp() const {
|
||||
return cp;
|
||||
}
|
||||
int incr_stack_trace(int v) {
|
||||
return stack_trace += v;
|
||||
}
|
||||
long long get_steps_count() const {
|
||||
return steps;
|
||||
}
|
||||
td::BitArray<256> get_state_hash() const;
|
||||
td::BitArray<256> get_final_state_hash(int exit_code) const;
|
||||
int step();
|
||||
int run();
|
||||
Stack& get_stack() {
|
||||
return stack.write();
|
||||
}
|
||||
const Stack& get_stack_const() const {
|
||||
return *stack;
|
||||
}
|
||||
Ref<Stack> get_stack_ref() const {
|
||||
return stack;
|
||||
}
|
||||
Ref<Continuation> get_c0() const {
|
||||
return cr.c[0];
|
||||
}
|
||||
Ref<Continuation> get_c1() const {
|
||||
return cr.c[1];
|
||||
}
|
||||
Ref<Continuation> get_c2() const {
|
||||
return cr.c[2];
|
||||
}
|
||||
Ref<Continuation> get_c3() const {
|
||||
return cr.c[3];
|
||||
}
|
||||
Ref<Cell> get_c4() const {
|
||||
return cr.d[0];
|
||||
}
|
||||
Ref<Tuple> get_c7() const {
|
||||
return cr.c7;
|
||||
}
|
||||
Ref<Continuation> get_c(unsigned idx) const {
|
||||
return cr.get_c(idx);
|
||||
}
|
||||
Ref<Cell> get_d(unsigned idx) const {
|
||||
return cr.get_d(idx);
|
||||
}
|
||||
StackEntry get(unsigned idx) const {
|
||||
return cr.get(idx);
|
||||
}
|
||||
const VmLog& get_log() const {
|
||||
return log;
|
||||
}
|
||||
void define_c0(Ref<Continuation> cont) {
|
||||
cr.define_c0(std::move(cont));
|
||||
}
|
||||
void set_c0(Ref<Continuation> cont) {
|
||||
cr.set_c0(std::move(cont));
|
||||
}
|
||||
void set_c1(Ref<Continuation> cont) {
|
||||
cr.set_c1(std::move(cont));
|
||||
}
|
||||
void set_c2(Ref<Continuation> cont) {
|
||||
cr.set_c2(std::move(cont));
|
||||
}
|
||||
bool set_c(unsigned idx, Ref<Continuation> val) {
|
||||
return cr.set_c(idx, std::move(val));
|
||||
}
|
||||
bool set_d(unsigned idx, Ref<Cell> val) {
|
||||
return cr.set_d(idx, std::move(val));
|
||||
}
|
||||
void set_c4(Ref<Cell> val) {
|
||||
cr.set_c4(std::move(val));
|
||||
}
|
||||
bool set_c7(Ref<Tuple> val) {
|
||||
return cr.set_c7(std::move(val));
|
||||
}
|
||||
bool set(unsigned idx, StackEntry val) {
|
||||
return cr.set(idx, std::move(val));
|
||||
}
|
||||
void set_stack(Ref<Stack> new_stk) {
|
||||
stack = std::move(new_stk);
|
||||
}
|
||||
Ref<Stack> swap_stack(Ref<Stack> new_stk) {
|
||||
stack.swap(new_stk);
|
||||
return new_stk;
|
||||
}
|
||||
void ensure_throw(bool cond) const {
|
||||
if (!cond) {
|
||||
fatal();
|
||||
}
|
||||
}
|
||||
void set_code(Ref<CellSlice> _code, int _cp) {
|
||||
code = std::move(_code);
|
||||
force_cp(_cp);
|
||||
}
|
||||
Ref<CellSlice> get_code() const {
|
||||
return code;
|
||||
}
|
||||
void push_code() {
|
||||
get_stack().push_cellslice(get_code());
|
||||
}
|
||||
void adjust_cr(const ControlRegs& save) {
|
||||
cr ^= save;
|
||||
}
|
||||
void adjust_cr(ControlRegs&& save) {
|
||||
cr ^= save;
|
||||
}
|
||||
void preclear_cr(const ControlRegs& save) {
|
||||
cr &= save;
|
||||
}
|
||||
int call(Ref<Continuation> cont);
|
||||
int call(Ref<Continuation> cont, int pass_args, int ret_args = -1);
|
||||
int jump(Ref<Continuation> cont);
|
||||
int jump(Ref<Continuation> cont, int pass_args);
|
||||
int ret();
|
||||
int ret(int ret_args);
|
||||
int ret_alt();
|
||||
int ret_alt(int ret_args);
|
||||
int repeat(Ref<Continuation> body, Ref<Continuation> after, long long count);
|
||||
int again(Ref<Continuation> body);
|
||||
int until(Ref<Continuation> body, Ref<Continuation> after);
|
||||
int loop_while(Ref<Continuation> cond, Ref<Continuation> body, Ref<Continuation> after);
|
||||
int throw_exception(int excno, StackEntry&& arg);
|
||||
int throw_exception(int excno);
|
||||
Ref<OrdCont> extract_cc(int save_cr = 1, int stack_copy = -1, int cc_args = -1);
|
||||
void fatal(void) const {
|
||||
throw VmFatal{};
|
||||
}
|
||||
int jump_to(Ref<Continuation> cont) {
|
||||
return cont->is_unique() ? cont.unique_write().jump_w(this) : cont->jump(this);
|
||||
}
|
||||
static Ref<CellSlice> convert_code_cell(Ref<Cell> code_cell);
|
||||
void commit() {
|
||||
cstate.c4 = cr.d[0];
|
||||
cstate.c5 = cr.d[1];
|
||||
cstate.committed = true;
|
||||
}
|
||||
|
||||
void set_chksig_always_succeed(bool flag) {
|
||||
chksig_always_succeed = flag;
|
||||
}
|
||||
bool get_chksig_always_succeed() const {
|
||||
return chksig_always_succeed;
|
||||
}
|
||||
|
||||
private:
|
||||
void init_cregs(bool same_c3 = false, bool push_0 = true);
|
||||
};
|
||||
|
||||
int run_vm_code(Ref<CellSlice> _code, Ref<Stack>& _stack, int flags = 0, Ref<Cell>* data_ptr = nullptr, VmLog log = {},
|
||||
long long* steps = nullptr, GasLimits* gas_limits = nullptr, std::vector<Ref<Cell>> libraries = {},
|
||||
Ref<Tuple> init_c7 = {}, Ref<Cell>* actions_ptr = nullptr);
|
||||
int run_vm_code(Ref<CellSlice> _code, Stack& _stack, int flags = 0, Ref<Cell>* data_ptr = nullptr, VmLog log = {},
|
||||
long long* steps = nullptr, GasLimits* gas_limits = nullptr, std::vector<Ref<Cell>> libraries = {},
|
||||
Ref<Tuple> init_c7 = {}, Ref<Cell>* actions_ptr = nullptr);
|
||||
|
||||
ControlData* force_cdata(Ref<Continuation>& cont);
|
||||
ControlRegs* force_cregs(Ref<Continuation>& cont);
|
||||
|
||||
Ref<vm::Cell> lookup_library_in(td::ConstBitPtr key, Ref<vm::Cell> lib_root);
|
||||
|
||||
} // namespace vm
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2017-2019 Telegram Systems LLP
|
||||
Copyright 2017-2020 Telegram Systems LLP
|
||||
*/
|
||||
#include <functional>
|
||||
#include "vm/contops.h"
|
||||
|
@ -24,6 +24,7 @@
|
|||
#include "vm/continuation.h"
|
||||
#include "vm/cellops.h"
|
||||
#include "vm/excno.hpp"
|
||||
#include "vm/vm.h"
|
||||
|
||||
namespace vm {
|
||||
|
||||
|
|
|
@ -14,15 +14,15 @@
|
|||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2017-2019 Telegram Systems LLP
|
||||
Copyright 2017-2020 Telegram Systems LLP
|
||||
*/
|
||||
#include <functional>
|
||||
#include "vm/debugops.h"
|
||||
#include "vm/log.h"
|
||||
#include "vm/opctable.h"
|
||||
#include "vm/stack.hpp"
|
||||
#include "vm/continuation.h"
|
||||
#include "vm/excno.hpp"
|
||||
#include "vm/vm.h"
|
||||
|
||||
namespace vm {
|
||||
|
||||
|
|
|
@ -20,8 +20,8 @@
|
|||
#include "vm/log.h"
|
||||
#include "vm/opctable.h"
|
||||
#include "vm/stack.hpp"
|
||||
#include "vm/continuation.h"
|
||||
#include "vm/excno.hpp"
|
||||
#include "vm/vm.h"
|
||||
#include "common/bigint.hpp"
|
||||
#include "common/refint.h"
|
||||
#include "vm/dictops.h"
|
||||
|
|
|
@ -14,14 +14,14 @@
|
|||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2017-2019 Telegram Systems LLP
|
||||
Copyright 2017-2020 Telegram Systems LLP
|
||||
*/
|
||||
#include <cassert>
|
||||
#include <iterator>
|
||||
#include "vm/opctable.h"
|
||||
#include "vm/cellslice.h"
|
||||
#include "vm/excno.hpp"
|
||||
#include "vm/continuation.h"
|
||||
#include "vm/vm.h"
|
||||
#include <iostream>
|
||||
#include <iomanip>
|
||||
#include <sstream>
|
||||
|
|
|
@ -14,14 +14,14 @@
|
|||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2017-2019 Telegram Systems LLP
|
||||
Copyright 2017-2020 Telegram Systems LLP
|
||||
*/
|
||||
#include "vm/log.h"
|
||||
#include "vm/stackops.h"
|
||||
#include "vm/opctable.h"
|
||||
#include "vm/stack.hpp"
|
||||
#include "vm/continuation.h"
|
||||
#include "vm/excno.hpp"
|
||||
#include "vm/vm.h"
|
||||
|
||||
namespace vm {
|
||||
|
||||
|
|
|
@ -14,15 +14,15 @@
|
|||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2017-2019 Telegram Systems LLP
|
||||
Copyright 2017-2020 Telegram Systems LLP
|
||||
*/
|
||||
#include <functional>
|
||||
#include "vm/tonops.h"
|
||||
#include "vm/log.h"
|
||||
#include "vm/opctable.h"
|
||||
#include "vm/stack.hpp"
|
||||
#include "vm/continuation.h"
|
||||
#include "vm/excno.hpp"
|
||||
#include "vm/vm.h"
|
||||
#include "vm/dict.h"
|
||||
#include "Ed25519.h"
|
||||
|
||||
|
@ -397,6 +397,83 @@ void register_ton_crypto_ops(OpcodeTable& cp0) {
|
|||
.insert(OpcodeInstr::mksimple(0xf911, 16, "CHKSIGNS", std::bind(exec_ed25519_check_signature, _1, true)));
|
||||
}
|
||||
|
||||
struct VmStorageStat {
|
||||
td::uint64 cells{0}, bits{0}, refs{0}, limit;
|
||||
td::HashSet<CellHash> visited;
|
||||
VmStorageStat(td::uint64 _limit) : limit(_limit) {
|
||||
}
|
||||
bool add_storage(Ref<Cell> cell);
|
||||
bool add_storage(const CellSlice& cs);
|
||||
bool check_visited(const CellHash& cell_hash) {
|
||||
return visited.insert(cell_hash).second;
|
||||
}
|
||||
bool check_visited(const Ref<Cell>& cell) {
|
||||
return check_visited(cell->get_hash());
|
||||
}
|
||||
};
|
||||
|
||||
bool VmStorageStat::add_storage(Ref<Cell> cell) {
|
||||
if (cell.is_null() || !check_visited(cell)) {
|
||||
return true;
|
||||
}
|
||||
if (cells >= limit) {
|
||||
return false;
|
||||
}
|
||||
++cells;
|
||||
bool special;
|
||||
auto cs = load_cell_slice_special(std::move(cell), special);
|
||||
return cs.is_valid() && add_storage(std::move(cs));
|
||||
}
|
||||
|
||||
bool VmStorageStat::add_storage(const CellSlice& cs) {
|
||||
bits += cs.size();
|
||||
refs += cs.size_refs();
|
||||
for (unsigned i = 0; i < cs.size_refs(); i++) {
|
||||
if (!add_storage(cs.prefetch_ref(i))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
int exec_compute_data_size(VmState* st, int mode) {
|
||||
VM_LOG(st) << (mode & 2 ? 'S' : 'C') << "DATASIZE" << (mode & 1 ? "Q" : "");
|
||||
Stack& stack = st->get_stack();
|
||||
stack.check_underflow(2);
|
||||
auto bound = stack.pop_int();
|
||||
Ref<Cell> cell;
|
||||
Ref<CellSlice> cs;
|
||||
if (mode & 2) {
|
||||
cs = stack.pop_cellslice();
|
||||
} else {
|
||||
cell = stack.pop_maybe_cell();
|
||||
}
|
||||
if (!bound->is_valid() || bound->sgn() < 0) {
|
||||
throw VmError{Excno::range_chk, "finite non-negative integer expected"};
|
||||
}
|
||||
VmStorageStat stat{bound->unsigned_fits_bits(63) ? bound->to_long() : (1ULL << 63) - 1};
|
||||
bool ok = (mode & 2 ? stat.add_storage(cs.write()) : stat.add_storage(std::move(cell)));
|
||||
if (ok) {
|
||||
stack.push_smallint(stat.cells);
|
||||
stack.push_smallint(stat.bits);
|
||||
stack.push_smallint(stat.refs);
|
||||
} else if (!(mode & 1)) {
|
||||
throw VmError{Excno::cell_ov, "scanned too many cells"};
|
||||
}
|
||||
if (mode & 1) {
|
||||
stack.push_bool(ok);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void register_ton_misc_ops(OpcodeTable& cp0) {
|
||||
using namespace std::placeholders;
|
||||
cp0.insert(OpcodeInstr::mksimple(0xf940, 16, "CDATASIZEQ", std::bind(exec_compute_data_size, _1, 1)))
|
||||
.insert(OpcodeInstr::mksimple(0xf941, 16, "CDATASIZE", std::bind(exec_compute_data_size, _1, 0)))
|
||||
.insert(OpcodeInstr::mksimple(0xf942, 16, "SDATASIZEQ", std::bind(exec_compute_data_size, _1, 3)))
|
||||
.insert(OpcodeInstr::mksimple(0xf943, 16, "SDATASIZE", std::bind(exec_compute_data_size, _1, 2)));
|
||||
}
|
||||
|
||||
int exec_load_var_integer(VmState* st, int len_bits, bool sgnd, bool quiet) {
|
||||
if (len_bits == 4 && !sgnd) {
|
||||
VM_LOG(st) << "execute LDGRAMS" << (quiet ? "Q" : "");
|
||||
|
@ -822,6 +899,7 @@ void register_ton_ops(OpcodeTable& cp0) {
|
|||
register_prng_ops(cp0);
|
||||
register_ton_config_ops(cp0);
|
||||
register_ton_crypto_ops(cp0);
|
||||
register_ton_misc_ops(cp0);
|
||||
register_ton_currency_address_ops(cp0);
|
||||
register_ton_message_ops(cp0);
|
||||
}
|
||||
|
|
|
@ -14,14 +14,14 @@
|
|||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2017-2019 Telegram Systems LLP
|
||||
Copyright 2017-2020 Telegram Systems LLP
|
||||
*/
|
||||
#include "vm/log.h"
|
||||
#include "vm/stackops.h"
|
||||
#include "vm/opctable.h"
|
||||
#include "vm/stack.hpp"
|
||||
#include "vm/continuation.h"
|
||||
#include "vm/excno.hpp"
|
||||
#include "vm/vm.h"
|
||||
|
||||
namespace vm {
|
||||
|
||||
|
|
593
crypto/vm/vm.cpp
Normal file
593
crypto/vm/vm.cpp
Normal file
|
@ -0,0 +1,593 @@
|
|||
/*
|
||||
This file is part of TON Blockchain Library.
|
||||
|
||||
TON Blockchain Library is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
TON Blockchain Library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2017-2020 Telegram Systems LLP
|
||||
*/
|
||||
#include "vm/dispatch.h"
|
||||
#include "vm/continuation.h"
|
||||
#include "vm/dict.h"
|
||||
#include "vm/log.h"
|
||||
#include "vm/vm.h"
|
||||
|
||||
namespace vm {
|
||||
|
||||
VmState::VmState() : cp(-1), dispatch(&dummy_dispatch_table), quit0(true, 0), quit1(true, 1) {
|
||||
ensure_throw(init_cp(0));
|
||||
init_cregs();
|
||||
}
|
||||
|
||||
VmState::VmState(Ref<CellSlice> _code)
|
||||
: code(std::move(_code)), cp(-1), dispatch(&dummy_dispatch_table), quit0(true, 0), quit1(true, 1) {
|
||||
ensure_throw(init_cp(0));
|
||||
init_cregs();
|
||||
}
|
||||
|
||||
VmState::VmState(Ref<CellSlice> _code, Ref<Stack> _stack, int flags, Ref<Cell> _data, VmLog log,
|
||||
std::vector<Ref<Cell>> _libraries, Ref<Tuple> init_c7)
|
||||
: code(std::move(_code))
|
||||
, stack(std::move(_stack))
|
||||
, cp(-1)
|
||||
, dispatch(&dummy_dispatch_table)
|
||||
, quit0(true, 0)
|
||||
, quit1(true, 1)
|
||||
, log(log)
|
||||
, libraries(std::move(_libraries)) {
|
||||
ensure_throw(init_cp(0));
|
||||
set_c4(std::move(_data));
|
||||
if (init_c7.not_null()) {
|
||||
set_c7(std::move(init_c7));
|
||||
}
|
||||
init_cregs(flags & 1, flags & 2);
|
||||
}
|
||||
|
||||
VmState::VmState(Ref<CellSlice> _code, Ref<Stack> _stack, const GasLimits& gas, int flags, Ref<Cell> _data, VmLog log,
|
||||
std::vector<Ref<Cell>> _libraries, Ref<Tuple> init_c7)
|
||||
: code(std::move(_code))
|
||||
, stack(std::move(_stack))
|
||||
, cp(-1)
|
||||
, dispatch(&dummy_dispatch_table)
|
||||
, quit0(true, 0)
|
||||
, quit1(true, 1)
|
||||
, log(log)
|
||||
, gas(gas)
|
||||
, libraries(std::move(_libraries)) {
|
||||
ensure_throw(init_cp(0));
|
||||
set_c4(std::move(_data));
|
||||
if (init_c7.not_null()) {
|
||||
set_c7(std::move(init_c7));
|
||||
}
|
||||
init_cregs(flags & 1, flags & 2);
|
||||
}
|
||||
|
||||
Ref<CellSlice> VmState::convert_code_cell(Ref<Cell> code_cell) {
|
||||
if (code_cell.is_null()) {
|
||||
return {};
|
||||
}
|
||||
Ref<CellSlice> csr{true, NoVmOrd(), code_cell};
|
||||
if (csr->is_valid()) {
|
||||
return csr;
|
||||
}
|
||||
return load_cell_slice_ref(CellBuilder{}.store_ref(std::move(code_cell)).finalize());
|
||||
}
|
||||
|
||||
bool VmState::init_cp(int new_cp) {
|
||||
const DispatchTable* dt = DispatchTable::get_table(new_cp);
|
||||
if (dt) {
|
||||
cp = new_cp;
|
||||
dispatch = dt;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool VmState::set_cp(int new_cp) {
|
||||
return new_cp == cp || init_cp(new_cp);
|
||||
}
|
||||
|
||||
void VmState::force_cp(int new_cp) {
|
||||
if (!set_cp(new_cp)) {
|
||||
throw VmError{Excno::inv_opcode, "unsupported codepage"};
|
||||
}
|
||||
}
|
||||
|
||||
// simple call to a continuation cont
|
||||
int VmState::call(Ref<Continuation> cont) {
|
||||
const ControlData* cont_data = cont->get_cdata();
|
||||
if (cont_data) {
|
||||
if (cont_data->save.c[0].not_null()) {
|
||||
// call reduces to a jump
|
||||
return jump(std::move(cont));
|
||||
}
|
||||
if (cont_data->stack.not_null() || cont_data->nargs >= 0) {
|
||||
// if cont has non-empty stack or expects fixed number of arguments, call is not simple
|
||||
return call(std::move(cont), -1, -1);
|
||||
}
|
||||
// create return continuation, to be stored into new c0
|
||||
Ref<OrdCont> ret = Ref<OrdCont>{true, std::move(code), cp};
|
||||
ret.unique_write().get_cdata()->save.set_c0(std::move(cr.c[0]));
|
||||
cr.set_c0(
|
||||
std::move(ret)); // set c0 to its final value before switching to cont; notice that cont.save.c0 is not set
|
||||
return jump_to(std::move(cont));
|
||||
}
|
||||
// create return continuation, to be stored into new c0
|
||||
Ref<OrdCont> ret = Ref<OrdCont>{true, std::move(code), cp};
|
||||
ret.unique_write().get_cdata()->save.set_c0(std::move(cr.c[0]));
|
||||
// general implementation of a simple call
|
||||
cr.set_c0(std::move(ret)); // set c0 to its final value before switching to cont; notice that cont.save.c0 is not set
|
||||
return jump_to(std::move(cont));
|
||||
}
|
||||
|
||||
// call with parameters to continuation cont
|
||||
int VmState::call(Ref<Continuation> cont, int pass_args, int ret_args) {
|
||||
const ControlData* cont_data = cont->get_cdata();
|
||||
if (cont_data) {
|
||||
if (cont_data->save.c[0].not_null()) {
|
||||
// call reduces to a jump
|
||||
return jump(std::move(cont), pass_args);
|
||||
}
|
||||
int depth = stack->depth();
|
||||
if (pass_args > depth || cont_data->nargs > depth) {
|
||||
throw VmError{Excno::stk_und, "stack underflow while calling a continuation: not enough arguments on stack"};
|
||||
}
|
||||
if (cont_data->nargs > pass_args && pass_args >= 0) {
|
||||
throw VmError{Excno::stk_und,
|
||||
"stack underflow while calling a closure continuation: not enough arguments passed"};
|
||||
}
|
||||
auto old_c0 = std::move(cr.c[0]);
|
||||
// optimization(?): decrease refcnts of unused continuations in c[i] as early as possible
|
||||
preclear_cr(cont_data->save);
|
||||
// no exceptions should be thrown after this point
|
||||
int copy = cont_data->nargs, skip = 0;
|
||||
if (pass_args >= 0) {
|
||||
if (copy >= 0) {
|
||||
skip = pass_args - copy;
|
||||
} else {
|
||||
copy = pass_args;
|
||||
}
|
||||
}
|
||||
// copy=-1 : pass whole stack, else pass top `copy` elements, drop next `skip` elements.
|
||||
Ref<Stack> new_stk;
|
||||
if (cont_data->stack.not_null() && !cont_data->stack->is_empty()) {
|
||||
// `cont` already has a stack, create resulting stack from it
|
||||
if (copy < 0) {
|
||||
copy = stack->depth();
|
||||
}
|
||||
if (cont->is_unique()) {
|
||||
// optimization: avoid copying stack if we hold the only copy of `cont`
|
||||
new_stk = std::move(cont.unique_write().get_cdata()->stack);
|
||||
} else {
|
||||
new_stk = cont_data->stack;
|
||||
}
|
||||
new_stk.write().move_from_stack(get_stack(), copy);
|
||||
if (skip > 0) {
|
||||
get_stack().pop_many(skip);
|
||||
}
|
||||
} else if (copy >= 0) {
|
||||
new_stk = get_stack().split_top(copy, skip);
|
||||
} else {
|
||||
new_stk = std::move(stack);
|
||||
stack.clear();
|
||||
}
|
||||
// create return continuation using the remainder of current stack
|
||||
Ref<OrdCont> ret = Ref<OrdCont>{true, std::move(code), cp, std::move(stack), ret_args};
|
||||
ret.unique_write().get_cdata()->save.set_c0(std::move(old_c0));
|
||||
Ref<OrdCont> ord_cont = static_cast<Ref<OrdCont>>(cont);
|
||||
set_stack(std::move(new_stk));
|
||||
cr.set_c0(std::move(ret)); // ??? if codepage of code in ord_cont is unknown, will end up with incorrect c0
|
||||
return jump_to(std::move(cont));
|
||||
} else {
|
||||
// have no continuation data, situation is somewhat simpler
|
||||
int depth = stack->depth();
|
||||
if (pass_args > depth) {
|
||||
throw VmError{Excno::stk_und, "stack underflow while calling a continuation: not enough arguments on stack"};
|
||||
}
|
||||
// create new stack from the top `pass_args` elements of the current stack
|
||||
Ref<Stack> new_stk = (pass_args >= 0 ? get_stack().split_top(pass_args) : std::move(stack));
|
||||
// create return continuation using the remainder of the current stack
|
||||
Ref<OrdCont> ret = Ref<OrdCont>{true, std::move(code), cp, std::move(stack), ret_args};
|
||||
ret.unique_write().get_cdata()->save.set_c0(std::move(cr.c[0]));
|
||||
set_stack(std::move(new_stk));
|
||||
cr.set_c0(std::move(ret)); // ??? if codepage of code in ord_cont is unknown, will end up with incorrect c0
|
||||
return jump_to(std::move(cont));
|
||||
}
|
||||
}
|
||||
|
||||
// simple jump to continuation cont
|
||||
int VmState::jump(Ref<Continuation> cont) {
|
||||
const ControlData* cont_data = cont->get_cdata();
|
||||
if (cont_data && (cont_data->stack.not_null() || cont_data->nargs >= 0)) {
|
||||
// if cont has non-empty stack or expects fixed number of arguments, jump is not simple
|
||||
return jump(std::move(cont), -1);
|
||||
} else {
|
||||
return jump_to(std::move(cont));
|
||||
}
|
||||
}
|
||||
|
||||
// general jump to continuation cont
|
||||
int VmState::jump(Ref<Continuation> cont, int pass_args) {
|
||||
const ControlData* cont_data = cont->get_cdata();
|
||||
if (cont_data) {
|
||||
// first do the checks
|
||||
int depth = stack->depth();
|
||||
if (pass_args > depth || cont_data->nargs > depth) {
|
||||
throw VmError{Excno::stk_und, "stack underflow while jumping to a continuation: not enough arguments on stack"};
|
||||
}
|
||||
if (cont_data->nargs > pass_args && pass_args >= 0) {
|
||||
throw VmError{Excno::stk_und,
|
||||
"stack underflow while jumping to closure continuation: not enough arguments passed"};
|
||||
}
|
||||
// optimization(?): decrease refcnts of unused continuations in c[i] as early as possible
|
||||
preclear_cr(cont_data->save);
|
||||
// no exceptions should be thrown after this point
|
||||
int copy = cont_data->nargs;
|
||||
if (pass_args >= 0 && copy < 0) {
|
||||
copy = pass_args;
|
||||
}
|
||||
// copy=-1 : pass whole stack, else pass top `copy` elements, drop the remainder.
|
||||
if (cont_data->stack.not_null() && !cont_data->stack->is_empty()) {
|
||||
// `cont` already has a stack, create resulting stack from it
|
||||
if (copy < 0) {
|
||||
copy = get_stack().depth();
|
||||
}
|
||||
Ref<Stack> new_stk;
|
||||
if (cont->is_unique()) {
|
||||
// optimization: avoid copying the stack if we hold the only copy of `cont`
|
||||
new_stk = std::move(cont.unique_write().get_cdata()->stack);
|
||||
} else {
|
||||
new_stk = cont_data->stack;
|
||||
}
|
||||
new_stk.write().move_from_stack(get_stack(), copy);
|
||||
set_stack(std::move(new_stk));
|
||||
} else {
|
||||
if (copy >= 0) {
|
||||
get_stack().drop_bottom(stack->depth() - copy);
|
||||
}
|
||||
}
|
||||
return jump_to(std::move(cont));
|
||||
} else {
|
||||
// have no continuation data, situation is somewhat simpler
|
||||
if (pass_args >= 0) {
|
||||
int depth = get_stack().depth();
|
||||
if (pass_args > depth) {
|
||||
throw VmError{Excno::stk_und, "stack underflow while jumping to a continuation: not enough arguments on stack"};
|
||||
}
|
||||
get_stack().drop_bottom(depth - pass_args);
|
||||
}
|
||||
return jump_to(std::move(cont));
|
||||
}
|
||||
}
|
||||
|
||||
int VmState::ret() {
|
||||
Ref<Continuation> cont = quit0;
|
||||
cont.swap(cr.c[0]);
|
||||
return jump(std::move(cont));
|
||||
}
|
||||
|
||||
int VmState::ret(int ret_args) {
|
||||
Ref<Continuation> cont = quit0;
|
||||
cont.swap(cr.c[0]);
|
||||
return jump(std::move(cont), ret_args);
|
||||
}
|
||||
|
||||
int VmState::ret_alt() {
|
||||
Ref<Continuation> cont = quit1;
|
||||
cont.swap(cr.c[1]);
|
||||
return jump(std::move(cont));
|
||||
}
|
||||
|
||||
int VmState::ret_alt(int ret_args) {
|
||||
Ref<Continuation> cont = quit1;
|
||||
cont.swap(cr.c[1]);
|
||||
return jump(std::move(cont), ret_args);
|
||||
}
|
||||
|
||||
Ref<OrdCont> VmState::extract_cc(int save_cr, int stack_copy, int cc_args) {
|
||||
Ref<Stack> new_stk;
|
||||
if (stack_copy < 0 || stack_copy == stack->depth()) {
|
||||
new_stk = std::move(stack);
|
||||
stack.clear();
|
||||
} else if (stack_copy > 0) {
|
||||
stack->check_underflow(stack_copy);
|
||||
new_stk = get_stack().split_top(stack_copy);
|
||||
} else {
|
||||
new_stk = Ref<Stack>{true};
|
||||
}
|
||||
Ref<OrdCont> cc = Ref<OrdCont>{true, std::move(code), cp, std::move(stack), cc_args};
|
||||
stack = std::move(new_stk);
|
||||
if (save_cr & 7) {
|
||||
ControlData* cdata = cc.unique_write().get_cdata();
|
||||
if (save_cr & 1) {
|
||||
cdata->save.set_c0(std::move(cr.c[0]));
|
||||
cr.set_c0(quit0);
|
||||
}
|
||||
if (save_cr & 2) {
|
||||
cdata->save.set_c1(std::move(cr.c[1]));
|
||||
cr.set_c1(quit1);
|
||||
}
|
||||
if (save_cr & 4) {
|
||||
cdata->save.set_c2(std::move(cr.c[2]));
|
||||
// cr.set_c2(Ref<ExcQuitCont>{true});
|
||||
}
|
||||
}
|
||||
return cc;
|
||||
}
|
||||
|
||||
int VmState::throw_exception(int excno) {
|
||||
Stack& stack_ref = get_stack();
|
||||
stack_ref.clear();
|
||||
stack_ref.push_smallint(0);
|
||||
stack_ref.push_smallint(excno);
|
||||
code.clear();
|
||||
consume_gas(exception_gas_price);
|
||||
return jump(get_c2());
|
||||
}
|
||||
|
||||
int VmState::throw_exception(int excno, StackEntry&& arg) {
|
||||
Stack& stack_ref = get_stack();
|
||||
stack_ref.clear();
|
||||
stack_ref.push(std::move(arg));
|
||||
stack_ref.push_smallint(excno);
|
||||
code.clear();
|
||||
consume_gas(exception_gas_price);
|
||||
return jump(get_c2());
|
||||
}
|
||||
|
||||
void GasLimits::gas_exception() const {
|
||||
throw VmNoGas{};
|
||||
}
|
||||
|
||||
void GasLimits::set_limits(long long _max, long long _limit, long long _credit) {
|
||||
gas_max = _max;
|
||||
gas_limit = _limit;
|
||||
gas_credit = _credit;
|
||||
change_base(_limit + _credit);
|
||||
}
|
||||
|
||||
void GasLimits::change_limit(long long _limit) {
|
||||
_limit = std::min(std::max(_limit, 0LL), gas_max);
|
||||
gas_credit = 0;
|
||||
gas_limit = _limit;
|
||||
change_base(_limit);
|
||||
}
|
||||
|
||||
bool VmState::set_gas_limits(long long _max, long long _limit, long long _credit) {
|
||||
gas.set_limits(_max, _limit, _credit);
|
||||
return true;
|
||||
}
|
||||
|
||||
void VmState::change_gas_limit(long long new_limit) {
|
||||
VM_LOG(this) << "changing gas limit to " << std::min(new_limit, gas.gas_max);
|
||||
gas.change_limit(new_limit);
|
||||
}
|
||||
|
||||
int VmState::step() {
|
||||
assert(!code.is_null());
|
||||
//VM_LOG(st) << "stack:"; stack->dump(VM_LOG(st));
|
||||
//VM_LOG(st) << "; cr0.refcnt = " << get_c0()->get_refcnt() - 1 << std::endl;
|
||||
if (stack_trace) {
|
||||
stack->dump(std::cerr, 3);
|
||||
}
|
||||
++steps;
|
||||
if (code->size()) {
|
||||
return dispatch->dispatch(this, code.write());
|
||||
} else if (code->size_refs()) {
|
||||
VM_LOG(this) << "execute implicit JMPREF\n";
|
||||
Ref<Continuation> cont = Ref<OrdCont>{true, load_cell_slice_ref(code->prefetch_ref()), get_cp()};
|
||||
return jump(std::move(cont));
|
||||
} else {
|
||||
VM_LOG(this) << "execute implicit RET\n";
|
||||
return ret();
|
||||
}
|
||||
}
|
||||
|
||||
int VmState::run() {
|
||||
if (code.is_null()) {
|
||||
throw VmError{Excno::fatal, "cannot run an uninitialized VM"};
|
||||
}
|
||||
int res;
|
||||
Guard guard(this);
|
||||
do {
|
||||
// LOG(INFO) << "[BS] data cells: " << DataCell::get_total_data_cells();
|
||||
try {
|
||||
try {
|
||||
res = step();
|
||||
gas.check();
|
||||
} catch (vm::CellBuilder::CellWriteError) {
|
||||
throw VmError{Excno::cell_ov};
|
||||
} catch (vm::CellBuilder::CellCreateError) {
|
||||
throw VmError{Excno::cell_ov};
|
||||
} catch (vm::CellSlice::CellReadError) {
|
||||
throw VmError{Excno::cell_und};
|
||||
}
|
||||
} catch (const VmError& vme) {
|
||||
VM_LOG(this) << "handling exception code " << vme.get_errno() << ": " << vme.get_msg();
|
||||
try {
|
||||
// LOG(INFO) << "[EX] data cells: " << DataCell::get_total_data_cells();
|
||||
++steps;
|
||||
res = throw_exception(vme.get_errno());
|
||||
} catch (const VmError& vme2) {
|
||||
VM_LOG(this) << "exception " << vme2.get_errno() << " while handling exception: " << vme.get_msg();
|
||||
// LOG(INFO) << "[EXX] data cells: " << DataCell::get_total_data_cells();
|
||||
return ~vme2.get_errno();
|
||||
}
|
||||
} catch (VmNoGas vmoog) {
|
||||
++steps;
|
||||
VM_LOG(this) << "unhandled out-of-gas exception: gas consumed=" << gas.gas_consumed()
|
||||
<< ", limit=" << gas.gas_limit;
|
||||
get_stack().clear();
|
||||
get_stack().push_smallint(gas.gas_consumed());
|
||||
return vmoog.get_errno(); // no ~ for unhandled exceptions (to make their faking impossible)
|
||||
}
|
||||
} while (!res);
|
||||
// LOG(INFO) << "[EN] data cells: " << DataCell::get_total_data_cells();
|
||||
if ((res | 1) == -1) {
|
||||
commit();
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
ControlData* force_cdata(Ref<Continuation>& cont) {
|
||||
if (!cont->get_cdata()) {
|
||||
cont = Ref<ArgContExt>{true, cont};
|
||||
return cont.unique_write().get_cdata();
|
||||
} else {
|
||||
return cont.write().get_cdata();
|
||||
}
|
||||
}
|
||||
|
||||
ControlRegs* force_cregs(Ref<Continuation>& cont) {
|
||||
return &force_cdata(cont)->save;
|
||||
}
|
||||
|
||||
int run_vm_code(Ref<CellSlice> code, Ref<Stack>& stack, int flags, Ref<Cell>* data_ptr, VmLog log, long long* steps,
|
||||
GasLimits* gas_limits, std::vector<Ref<Cell>> libraries, Ref<Tuple> init_c7, Ref<Cell>* actions_ptr) {
|
||||
VmState vm{code,
|
||||
std::move(stack),
|
||||
gas_limits ? *gas_limits : GasLimits{},
|
||||
flags,
|
||||
data_ptr ? *data_ptr : Ref<Cell>{},
|
||||
log,
|
||||
std::move(libraries),
|
||||
std::move(init_c7)};
|
||||
int res = vm.run();
|
||||
stack = vm.get_stack_ref();
|
||||
if (vm.committed() && data_ptr) {
|
||||
*data_ptr = vm.get_committed_state().c4;
|
||||
}
|
||||
if (vm.committed() && actions_ptr) {
|
||||
*actions_ptr = vm.get_committed_state().c5;
|
||||
}
|
||||
if (steps) {
|
||||
*steps = vm.get_steps_count();
|
||||
}
|
||||
if (gas_limits) {
|
||||
*gas_limits = vm.get_gas_limits();
|
||||
LOG(INFO) << "steps: " << vm.get_steps_count() << " gas: used=" << gas_limits->gas_consumed()
|
||||
<< ", max=" << gas_limits->gas_max << ", limit=" << gas_limits->gas_limit
|
||||
<< ", credit=" << gas_limits->gas_credit;
|
||||
}
|
||||
if ((vm.get_log().log_mask & vm::VmLog::DumpStack) != 0) {
|
||||
VM_LOG(&vm) << "BEGIN_STACK_DUMP";
|
||||
for (int i = stack->depth(); i > 0; i--) {
|
||||
VM_LOG(&vm) << (*stack)[i - 1].to_string();
|
||||
}
|
||||
VM_LOG(&vm) << "END_STACK_DUMP";
|
||||
}
|
||||
|
||||
return ~res;
|
||||
}
|
||||
|
||||
int run_vm_code(Ref<CellSlice> code, Stack& stack, int flags, Ref<Cell>* data_ptr, VmLog log, long long* steps,
|
||||
GasLimits* gas_limits, std::vector<Ref<Cell>> libraries, Ref<Tuple> init_c7, Ref<Cell>* actions_ptr) {
|
||||
Ref<Stack> stk{true};
|
||||
stk.unique_write().set_contents(std::move(stack));
|
||||
stack.clear();
|
||||
int res = run_vm_code(code, stk, flags, data_ptr, log, steps, gas_limits, std::move(libraries), std::move(init_c7),
|
||||
actions_ptr);
|
||||
CHECK(stack.is_unique());
|
||||
if (stk.is_null()) {
|
||||
stack.clear();
|
||||
} else if (&(*stk) != &stack) {
|
||||
VmState* st = nullptr;
|
||||
if (stk->is_unique()) {
|
||||
VM_LOG(st) << "move resulting stack (" << stk->depth() << " entries)";
|
||||
stack.set_contents(std::move(stk.unique_write()));
|
||||
} else {
|
||||
VM_LOG(st) << "copying resulting stack (" << stk->depth() << " entries)";
|
||||
stack.set_contents(*stk);
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
// may throw a dictionary exception; returns nullptr if library is not found in context
|
||||
Ref<Cell> VmState::load_library(td::ConstBitPtr hash) {
|
||||
std::unique_ptr<VmStateInterface> tmp_ctx;
|
||||
// install temporary dummy vm state interface to prevent charging for cell load operations during library lookup
|
||||
VmStateInterface::Guard(tmp_ctx.get());
|
||||
for (const auto& lib_collection : libraries) {
|
||||
auto lib = lookup_library_in(hash, lib_collection);
|
||||
if (lib.not_null()) {
|
||||
return lib;
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
bool VmState::register_library_collection(Ref<Cell> lib) {
|
||||
if (lib.is_null()) {
|
||||
return true;
|
||||
}
|
||||
libraries.push_back(std::move(lib));
|
||||
return true;
|
||||
}
|
||||
|
||||
void VmState::register_cell_load(const CellHash& cell_hash) {
|
||||
if (cell_load_gas_price == cell_reload_gas_price) {
|
||||
consume_gas(cell_load_gas_price);
|
||||
} else {
|
||||
auto ok = loaded_cells.insert(cell_hash); // check whether this is the first time this cell is loaded
|
||||
if (ok.second) {
|
||||
loaded_cells_count++;
|
||||
}
|
||||
consume_gas(ok.second ? cell_load_gas_price : cell_reload_gas_price);
|
||||
}
|
||||
}
|
||||
|
||||
void VmState::register_cell_create() {
|
||||
consume_gas(cell_create_gas_price);
|
||||
}
|
||||
|
||||
td::BitArray<256> VmState::get_state_hash() const {
|
||||
// TODO: implement properly, by serializing the stack etc, and computing the Merkle hash
|
||||
td::BitArray<256> res;
|
||||
res.clear();
|
||||
return res;
|
||||
}
|
||||
|
||||
td::BitArray<256> VmState::get_final_state_hash(int exit_code) const {
|
||||
// TODO: implement properly, by serializing the stack etc, and computing the Merkle hash
|
||||
td::BitArray<256> res;
|
||||
res.clear();
|
||||
return res;
|
||||
}
|
||||
|
||||
Ref<vm::Cell> lookup_library_in(td::ConstBitPtr key, vm::Dictionary& dict) {
|
||||
try {
|
||||
auto val = dict.lookup(key, 256);
|
||||
if (val.is_null() || !val->have_refs()) {
|
||||
return {};
|
||||
}
|
||||
auto root = val->prefetch_ref();
|
||||
if (root.not_null() && !root->get_hash().bits().compare(key, 256)) {
|
||||
return root;
|
||||
}
|
||||
return {};
|
||||
} catch (vm::VmError) {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
Ref<vm::Cell> lookup_library_in(td::ConstBitPtr key, Ref<vm::Cell> lib_root) {
|
||||
if (lib_root.is_null()) {
|
||||
return lib_root;
|
||||
}
|
||||
vm::Dictionary dict{std::move(lib_root), 256};
|
||||
return lookup_library_in(key, dict);
|
||||
}
|
||||
|
||||
} // namespace vm
|
320
crypto/vm/vm.h
Normal file
320
crypto/vm/vm.h
Normal file
|
@ -0,0 +1,320 @@
|
|||
/*
|
||||
This file is part of TON Blockchain Library.
|
||||
|
||||
TON Blockchain Library is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
TON Blockchain Library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2017-2020 Telegram Systems LLP
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "common/refcnt.hpp"
|
||||
#include "vm/cellslice.h"
|
||||
#include "vm/stack.hpp"
|
||||
#include "vm/vmstate.h"
|
||||
#include "vm/log.h"
|
||||
#include "vm/continuation.h"
|
||||
#include "td/utils/HashSet.h"
|
||||
|
||||
namespace vm {
|
||||
|
||||
using td::Ref;
|
||||
struct GasLimits {
|
||||
static constexpr long long infty = (1ULL << 63) - 1;
|
||||
long long gas_max, gas_limit, gas_credit, gas_remaining, gas_base;
|
||||
GasLimits() : gas_max(infty), gas_limit(infty), gas_credit(0), gas_remaining(infty), gas_base(infty) {
|
||||
}
|
||||
GasLimits(long long _limit, long long _max = infty, long long _credit = 0)
|
||||
: gas_max(_max)
|
||||
, gas_limit(_limit)
|
||||
, gas_credit(_credit)
|
||||
, gas_remaining(_limit + _credit)
|
||||
, gas_base(gas_remaining) {
|
||||
}
|
||||
long long gas_consumed() const {
|
||||
return gas_base - gas_remaining;
|
||||
}
|
||||
void set_limits(long long _max, long long _limit, long long _credit = 0);
|
||||
void change_base(long long _base) {
|
||||
gas_remaining += _base - gas_base;
|
||||
gas_base = _base;
|
||||
}
|
||||
void change_limit(long long _limit);
|
||||
void consume(long long amount) {
|
||||
// LOG(DEBUG) << "consume " << amount << " gas (" << gas_remaining << " remaining)";
|
||||
gas_remaining -= amount;
|
||||
}
|
||||
bool try_consume(long long amount) {
|
||||
// LOG(DEBUG) << "try consume " << amount << " gas (" << gas_remaining << " remaining)";
|
||||
return (gas_remaining -= amount) >= 0;
|
||||
}
|
||||
void gas_exception() const;
|
||||
void gas_exception(bool cond) const {
|
||||
if (!cond) {
|
||||
gas_exception();
|
||||
}
|
||||
}
|
||||
void consume_chk(long long amount) {
|
||||
gas_exception(try_consume(amount));
|
||||
}
|
||||
void check() const {
|
||||
gas_exception(gas_remaining >= 0);
|
||||
}
|
||||
bool final_ok() const {
|
||||
return gas_remaining >= gas_credit;
|
||||
}
|
||||
};
|
||||
|
||||
struct CommittedState {
|
||||
Ref<vm::Cell> c4, c5;
|
||||
bool committed{false};
|
||||
};
|
||||
|
||||
class VmState final : public VmStateInterface {
|
||||
Ref<CellSlice> code;
|
||||
Ref<Stack> stack;
|
||||
ControlRegs cr;
|
||||
CommittedState cstate;
|
||||
int cp;
|
||||
long long steps{0};
|
||||
const DispatchTable* dispatch;
|
||||
Ref<QuitCont> quit0, quit1;
|
||||
VmLog log;
|
||||
GasLimits gas;
|
||||
std::vector<Ref<Cell>> libraries;
|
||||
td::HashSet<CellHash> loaded_cells;
|
||||
td::int64 loaded_cells_count{0};
|
||||
int stack_trace{0}, debug_off{0};
|
||||
bool chksig_always_succeed{false};
|
||||
|
||||
public:
|
||||
enum {
|
||||
cell_load_gas_price = 100,
|
||||
cell_reload_gas_price = 25,
|
||||
cell_create_gas_price = 500,
|
||||
exception_gas_price = 50,
|
||||
tuple_entry_gas_price = 1
|
||||
};
|
||||
VmState();
|
||||
VmState(Ref<CellSlice> _code);
|
||||
VmState(Ref<CellSlice> _code, Ref<Stack> _stack, int flags = 0, Ref<Cell> _data = {}, VmLog log = {},
|
||||
std::vector<Ref<Cell>> _libraries = {}, Ref<Tuple> init_c7 = {});
|
||||
VmState(Ref<CellSlice> _code, Ref<Stack> _stack, const GasLimits& _gas, int flags = 0, Ref<Cell> _data = {},
|
||||
VmLog log = {}, std::vector<Ref<Cell>> _libraries = {}, Ref<Tuple> init_c7 = {});
|
||||
template <typename... Args>
|
||||
VmState(Ref<Cell> code_cell, Args&&... args)
|
||||
: VmState(convert_code_cell(std::move(code_cell)), std::forward<Args>(args)...) {
|
||||
}
|
||||
VmState(const VmState&) = delete;
|
||||
VmState(VmState&&) = delete;
|
||||
VmState& operator=(const VmState&) = delete;
|
||||
VmState& operator=(VmState&&) = delete;
|
||||
bool set_gas_limits(long long _max, long long _limit, long long _credit = 0);
|
||||
bool final_gas_ok() const {
|
||||
return gas.final_ok();
|
||||
}
|
||||
long long gas_consumed() const {
|
||||
return gas.gas_consumed();
|
||||
}
|
||||
bool committed() const {
|
||||
return cstate.committed;
|
||||
}
|
||||
const CommittedState& get_committed_state() const {
|
||||
return cstate;
|
||||
}
|
||||
void consume_gas(long long amount) {
|
||||
gas.consume(amount);
|
||||
}
|
||||
void consume_tuple_gas(unsigned tuple_len) {
|
||||
consume_gas(tuple_len * tuple_entry_gas_price);
|
||||
}
|
||||
void consume_tuple_gas(const Ref<vm::Tuple>& tup) {
|
||||
if (tup.not_null()) {
|
||||
consume_tuple_gas((unsigned)tup->size());
|
||||
}
|
||||
}
|
||||
GasLimits get_gas_limits() const {
|
||||
return gas;
|
||||
}
|
||||
void change_gas_limit(long long new_limit);
|
||||
template <typename... Args>
|
||||
void check_underflow(Args... args) {
|
||||
stack->check_underflow(args...);
|
||||
}
|
||||
bool register_library_collection(Ref<Cell> lib);
|
||||
Ref<Cell> load_library(
|
||||
td::ConstBitPtr hash) override; // may throw a dictionary exception; returns nullptr if library is not found
|
||||
void register_cell_load(const CellHash& cell_hash) override;
|
||||
void register_cell_create() override;
|
||||
bool init_cp(int new_cp);
|
||||
bool set_cp(int new_cp);
|
||||
void force_cp(int new_cp);
|
||||
int get_cp() const {
|
||||
return cp;
|
||||
}
|
||||
int incr_stack_trace(int v) {
|
||||
return stack_trace += v;
|
||||
}
|
||||
long long get_steps_count() const {
|
||||
return steps;
|
||||
}
|
||||
td::BitArray<256> get_state_hash() const;
|
||||
td::BitArray<256> get_final_state_hash(int exit_code) const;
|
||||
int step();
|
||||
int run();
|
||||
Stack& get_stack() {
|
||||
return stack.write();
|
||||
}
|
||||
const Stack& get_stack_const() const {
|
||||
return *stack;
|
||||
}
|
||||
Ref<Stack> get_stack_ref() const {
|
||||
return stack;
|
||||
}
|
||||
Ref<Continuation> get_c0() const {
|
||||
return cr.c[0];
|
||||
}
|
||||
Ref<Continuation> get_c1() const {
|
||||
return cr.c[1];
|
||||
}
|
||||
Ref<Continuation> get_c2() const {
|
||||
return cr.c[2];
|
||||
}
|
||||
Ref<Continuation> get_c3() const {
|
||||
return cr.c[3];
|
||||
}
|
||||
Ref<Cell> get_c4() const {
|
||||
return cr.d[0];
|
||||
}
|
||||
Ref<Tuple> get_c7() const {
|
||||
return cr.c7;
|
||||
}
|
||||
Ref<Continuation> get_c(unsigned idx) const {
|
||||
return cr.get_c(idx);
|
||||
}
|
||||
Ref<Cell> get_d(unsigned idx) const {
|
||||
return cr.get_d(idx);
|
||||
}
|
||||
StackEntry get(unsigned idx) const {
|
||||
return cr.get(idx);
|
||||
}
|
||||
const VmLog& get_log() const {
|
||||
return log;
|
||||
}
|
||||
void define_c0(Ref<Continuation> cont) {
|
||||
cr.define_c0(std::move(cont));
|
||||
}
|
||||
void set_c0(Ref<Continuation> cont) {
|
||||
cr.set_c0(std::move(cont));
|
||||
}
|
||||
void set_c1(Ref<Continuation> cont) {
|
||||
cr.set_c1(std::move(cont));
|
||||
}
|
||||
void set_c2(Ref<Continuation> cont) {
|
||||
cr.set_c2(std::move(cont));
|
||||
}
|
||||
bool set_c(unsigned idx, Ref<Continuation> val) {
|
||||
return cr.set_c(idx, std::move(val));
|
||||
}
|
||||
bool set_d(unsigned idx, Ref<Cell> val) {
|
||||
return cr.set_d(idx, std::move(val));
|
||||
}
|
||||
void set_c4(Ref<Cell> val) {
|
||||
cr.set_c4(std::move(val));
|
||||
}
|
||||
bool set_c7(Ref<Tuple> val) {
|
||||
return cr.set_c7(std::move(val));
|
||||
}
|
||||
bool set(unsigned idx, StackEntry val) {
|
||||
return cr.set(idx, std::move(val));
|
||||
}
|
||||
void set_stack(Ref<Stack> new_stk) {
|
||||
stack = std::move(new_stk);
|
||||
}
|
||||
Ref<Stack> swap_stack(Ref<Stack> new_stk) {
|
||||
stack.swap(new_stk);
|
||||
return new_stk;
|
||||
}
|
||||
void ensure_throw(bool cond) const {
|
||||
if (!cond) {
|
||||
fatal();
|
||||
}
|
||||
}
|
||||
void set_code(Ref<CellSlice> _code, int _cp) {
|
||||
code = std::move(_code);
|
||||
force_cp(_cp);
|
||||
}
|
||||
Ref<CellSlice> get_code() const {
|
||||
return code;
|
||||
}
|
||||
void push_code() {
|
||||
get_stack().push_cellslice(get_code());
|
||||
}
|
||||
void adjust_cr(const ControlRegs& save) {
|
||||
cr ^= save;
|
||||
}
|
||||
void adjust_cr(ControlRegs&& save) {
|
||||
cr ^= save;
|
||||
}
|
||||
void preclear_cr(const ControlRegs& save) {
|
||||
cr &= save;
|
||||
}
|
||||
int call(Ref<Continuation> cont);
|
||||
int call(Ref<Continuation> cont, int pass_args, int ret_args = -1);
|
||||
int jump(Ref<Continuation> cont);
|
||||
int jump(Ref<Continuation> cont, int pass_args);
|
||||
int ret();
|
||||
int ret(int ret_args);
|
||||
int ret_alt();
|
||||
int ret_alt(int ret_args);
|
||||
int repeat(Ref<Continuation> body, Ref<Continuation> after, long long count);
|
||||
int again(Ref<Continuation> body);
|
||||
int until(Ref<Continuation> body, Ref<Continuation> after);
|
||||
int loop_while(Ref<Continuation> cond, Ref<Continuation> body, Ref<Continuation> after);
|
||||
int throw_exception(int excno, StackEntry&& arg);
|
||||
int throw_exception(int excno);
|
||||
Ref<OrdCont> extract_cc(int save_cr = 1, int stack_copy = -1, int cc_args = -1);
|
||||
void fatal(void) const {
|
||||
throw VmFatal{};
|
||||
}
|
||||
int jump_to(Ref<Continuation> cont) {
|
||||
return cont->is_unique() ? cont.unique_write().jump_w(this) : cont->jump(this);
|
||||
}
|
||||
static Ref<CellSlice> convert_code_cell(Ref<Cell> code_cell);
|
||||
void commit() {
|
||||
cstate.c4 = cr.d[0];
|
||||
cstate.c5 = cr.d[1];
|
||||
cstate.committed = true;
|
||||
}
|
||||
|
||||
void set_chksig_always_succeed(bool flag) {
|
||||
chksig_always_succeed = flag;
|
||||
}
|
||||
bool get_chksig_always_succeed() const {
|
||||
return chksig_always_succeed;
|
||||
}
|
||||
|
||||
private:
|
||||
void init_cregs(bool same_c3 = false, bool push_0 = true);
|
||||
};
|
||||
|
||||
int run_vm_code(Ref<CellSlice> _code, Ref<Stack>& _stack, int flags = 0, Ref<Cell>* data_ptr = nullptr, VmLog log = {},
|
||||
long long* steps = nullptr, GasLimits* gas_limits = nullptr, std::vector<Ref<Cell>> libraries = {},
|
||||
Ref<Tuple> init_c7 = {}, Ref<Cell>* actions_ptr = nullptr);
|
||||
int run_vm_code(Ref<CellSlice> _code, Stack& _stack, int flags = 0, Ref<Cell>* data_ptr = nullptr, VmLog log = {},
|
||||
long long* steps = nullptr, GasLimits* gas_limits = nullptr, std::vector<Ref<Cell>> libraries = {},
|
||||
Ref<Tuple> init_c7 = {}, Ref<Cell>* actions_ptr = nullptr);
|
||||
|
||||
Ref<vm::Cell> lookup_library_in(td::ConstBitPtr key, Ref<vm::Cell> lib_root);
|
||||
|
||||
} // namespace vm
|
|
@ -14,7 +14,7 @@
|
|||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2017-2019 Telegram Systems LLP
|
||||
Copyright 2017-2020 Telegram Systems LLP
|
||||
*/
|
||||
#pragma once
|
||||
#include "common/refcnt.hpp"
|
||||
|
@ -32,7 +32,7 @@ class VmStateInterface : public td::Context<VmStateInterface> {
|
|||
td::ConstBitPtr hash) { // may throw a dictionary exception; returns nullptr if library is not found
|
||||
return {};
|
||||
}
|
||||
virtual void register_cell_load(){};
|
||||
virtual void register_cell_load(const CellHash& cell_hash){};
|
||||
virtual void register_cell_create(){};
|
||||
};
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue