mirror of
https://github.com/ton-blockchain/ton
synced 2025-03-09 15:40:10 +00:00
GETEXTRABALANCE instruction, changes in reserve
This commit is contained in:
parent
cf50b4b5da
commit
2b182186de
7 changed files with 143 additions and 14 deletions
|
@ -398,6 +398,7 @@ struct SizeLimitsConfig {
|
||||||
td::uint32 max_acc_public_libraries = 256;
|
td::uint32 max_acc_public_libraries = 256;
|
||||||
td::uint32 defer_out_queue_size_limit = 256;
|
td::uint32 defer_out_queue_size_limit = 256;
|
||||||
td::uint32 max_msg_extra_currencies = 2;
|
td::uint32 max_msg_extra_currencies = 2;
|
||||||
|
td::uint32 max_reserve_extra_currencies = 2;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct CatchainValidatorsConfig {
|
struct CatchainValidatorsConfig {
|
||||||
|
|
|
@ -2823,6 +2823,20 @@ int Transaction::try_action_reserve_currency(vm::CellSlice& cs, ActionPhase& ap,
|
||||||
}
|
}
|
||||||
int mode = rec.mode;
|
int mode = rec.mode;
|
||||||
LOG(INFO) << "in try_action_reserve_currency(" << mode << ")";
|
LOG(INFO) << "in try_action_reserve_currency(" << mode << ")";
|
||||||
|
if (cfg.extra_currency_v2) {
|
||||||
|
CurrencyCollection value;
|
||||||
|
if (!value.unpack(rec.currency)) {
|
||||||
|
LOG(DEBUG) << "invalid value in action_reserve_currency";
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (!CurrencyCollection::remove_zero_extra_currencies(value.extra, cfg.size_limits.max_reserve_extra_currencies)) {
|
||||||
|
LOG(DEBUG) << "invalid extra currencies in action_reserve_currency: too many currencies (max "
|
||||||
|
<< cfg.size_limits.max_reserve_extra_currencies << ")";
|
||||||
|
// Dict should be valid, since it was checked in t_OutListNode.validate_ref, so error here means limit exceeded
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
rec.currency = value.pack();
|
||||||
|
}
|
||||||
CurrencyCollection reserve, newc;
|
CurrencyCollection reserve, newc;
|
||||||
if (!reserve.validate_unpack(std::move(rec.currency))) {
|
if (!reserve.validate_unpack(std::move(rec.currency))) {
|
||||||
LOG(DEBUG) << "cannot parse currency field in action_reserve_currency";
|
LOG(DEBUG) << "cannot parse currency field in action_reserve_currency";
|
||||||
|
@ -2832,9 +2846,17 @@ int Transaction::try_action_reserve_currency(vm::CellSlice& cs, ActionPhase& ap,
|
||||||
<< ", balance=" << ap.remaining_balance.to_str() << ", original balance=" << original_balance.to_str();
|
<< ", balance=" << ap.remaining_balance.to_str() << ", original balance=" << original_balance.to_str();
|
||||||
if (mode & 4) {
|
if (mode & 4) {
|
||||||
if (mode & 8) {
|
if (mode & 8) {
|
||||||
reserve = original_balance - reserve;
|
if (cfg.extra_currency_v2) {
|
||||||
|
reserve.grams = original_balance.grams - reserve.grams;
|
||||||
|
} else {
|
||||||
|
reserve = original_balance - reserve;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
reserve += original_balance;
|
if (cfg.extra_currency_v2) {
|
||||||
|
reserve.grams += original_balance.grams;
|
||||||
|
} else {
|
||||||
|
reserve += original_balance;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else if (mode & 8) {
|
} else if (mode & 8) {
|
||||||
LOG(DEBUG) << "invalid reserve mode " << mode;
|
LOG(DEBUG) << "invalid reserve mode " << mode;
|
||||||
|
@ -2847,7 +2869,7 @@ int Transaction::try_action_reserve_currency(vm::CellSlice& cs, ActionPhase& ap,
|
||||||
if (mode & 2) {
|
if (mode & 2) {
|
||||||
if (cfg.reserve_extra_enabled) {
|
if (cfg.reserve_extra_enabled) {
|
||||||
if (!reserve.clamp(ap.remaining_balance)) {
|
if (!reserve.clamp(ap.remaining_balance)) {
|
||||||
LOG(DEBUG) << "failed to clamp reserve amount" << mode;
|
LOG(DEBUG) << "failed to clamp reserve amount " << mode;
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -2868,7 +2890,11 @@ int Transaction::try_action_reserve_currency(vm::CellSlice& cs, ActionPhase& ap,
|
||||||
newc.grams = ap.remaining_balance.grams - reserve.grams;
|
newc.grams = ap.remaining_balance.grams - reserve.grams;
|
||||||
if (mode & 1) {
|
if (mode & 1) {
|
||||||
// leave only res_grams, reserve everything else
|
// leave only res_grams, reserve everything else
|
||||||
std::swap(newc, reserve);
|
if (cfg.extra_currency_v2) {
|
||||||
|
std::swap(newc.grams, reserve.grams);
|
||||||
|
} else {
|
||||||
|
std::swap(newc, reserve);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// set remaining_balance to new_grams and new_extra
|
// set remaining_balance to new_grams and new_extra
|
||||||
ap.remaining_balance = std::move(newc);
|
ap.remaining_balance = std::move(newc);
|
||||||
|
|
|
@ -1327,6 +1327,8 @@ x{F840} @Defop GETGLOBVAR
|
||||||
x{F860} @Defop SETGLOBVAR
|
x{F860} @Defop SETGLOBVAR
|
||||||
{ dup 1 31 @rangechk <b x{F87_} s, swap 5 u, @addopb } : SETGLOB
|
{ dup 1 31 @rangechk <b x{F87_} s, swap 5 u, @addopb } : SETGLOB
|
||||||
|
|
||||||
|
x{F880} @Defop GETEXTRABALANCE
|
||||||
|
|
||||||
x{F900} @Defop HASHCU
|
x{F900} @Defop HASHCU
|
||||||
x{F901} @Defop HASHSU
|
x{F901} @Defop HASHSU
|
||||||
x{F902} @Defop SHA256U
|
x{F902} @Defop SHA256U
|
||||||
|
|
|
@ -358,6 +358,75 @@ int exec_get_forward_fee_simple(VmState* st) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int exec_get_extra_currency_balance(VmState* st) {
|
||||||
|
VM_LOG(st) << "execute GETEXTRABALANCE";
|
||||||
|
Stack& stack = st->get_stack();
|
||||||
|
auto id = (td::uint32)stack.pop_long_range((1LL << 32) - 1);
|
||||||
|
|
||||||
|
auto tuple = st->get_c7();
|
||||||
|
tuple = tuple_index(tuple, 0).as_tuple_range(255);
|
||||||
|
if (tuple.is_null()) {
|
||||||
|
throw VmError{Excno::type_chk, "intermediate value is not a tuple"};
|
||||||
|
}
|
||||||
|
tuple = tuple_index(tuple, 7).as_tuple_range(255); // Balance
|
||||||
|
if (tuple.is_null()) {
|
||||||
|
throw VmError{Excno::type_chk, "intermediate value is not a tuple"};
|
||||||
|
}
|
||||||
|
auto dict_root = tuple_index(tuple, 1);
|
||||||
|
if (!dict_root.is_cell() && !dict_root.is_null()) {
|
||||||
|
throw VmError{Excno::type_chk, "intermediate value is not cell or null"};
|
||||||
|
}
|
||||||
|
|
||||||
|
class LocalVmState : public VmStateInterface {
|
||||||
|
public:
|
||||||
|
explicit LocalVmState(VmState* st) : st_(st) {
|
||||||
|
}
|
||||||
|
~LocalVmState() override = default;
|
||||||
|
|
||||||
|
Ref<Cell> load_library(td::ConstBitPtr hash) override {
|
||||||
|
return st_->load_library(hash);
|
||||||
|
}
|
||||||
|
void register_cell_load(const CellHash& cell_hash) override {
|
||||||
|
auto new_cell = st_->register_cell_load_free(cell_hash);
|
||||||
|
consume_gas(new_cell ? VmState::cell_load_gas_price : VmState::cell_reload_gas_price);
|
||||||
|
}
|
||||||
|
void register_cell_create() override {
|
||||||
|
// Not expected in this operation
|
||||||
|
}
|
||||||
|
int get_global_version() const override {
|
||||||
|
return st_->get_global_version();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
VmState* st_;
|
||||||
|
long long remaining = VmState::get_extra_balance_cheap_max_gas_price;
|
||||||
|
|
||||||
|
void consume_gas(long long gas) {
|
||||||
|
long long consumed = std::min(gas, remaining);
|
||||||
|
st_->consume_gas(consumed);
|
||||||
|
remaining -= consumed;
|
||||||
|
if (remaining == 0) {
|
||||||
|
st_->consume_free_gas(gas - consumed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
bool cheap = st->register_get_extra_balance_call();
|
||||||
|
LocalVmState local_vm_state{st};
|
||||||
|
VmStateInterface::Guard guard{cheap ? local_vm_state : *st};
|
||||||
|
|
||||||
|
Dictionary dict{dict_root.as_cell(), 32};
|
||||||
|
Ref<CellSlice> cs = dict.lookup(td::BitArray<32>(id));
|
||||||
|
if (cs.is_null()) {
|
||||||
|
stack.push_smallint(0);
|
||||||
|
} else {
|
||||||
|
td::RefInt256 x;
|
||||||
|
util::load_var_integer_q(cs.write(), x, /* len_bits = */ 5, /* sgnd = */ false, /* quiet = */ false);
|
||||||
|
stack.push_int(std::move(x));
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
void register_ton_config_ops(OpcodeTable& cp0) {
|
void register_ton_config_ops(OpcodeTable& cp0) {
|
||||||
using namespace std::placeholders;
|
using namespace std::placeholders;
|
||||||
cp0.insert(OpcodeInstr::mkfixedrange(0xf820, 0xf823, 16, 4, instr::dump_1c("GETPARAM "), exec_get_var_param))
|
cp0.insert(OpcodeInstr::mkfixedrange(0xf820, 0xf823, 16, 4, instr::dump_1c("GETPARAM "), exec_get_var_param))
|
||||||
|
@ -391,7 +460,8 @@ void register_ton_config_ops(OpcodeTable& cp0) {
|
||||||
.insert(OpcodeInstr::mksimple(0xf840, 16, "GETGLOBVAR", exec_get_global_var))
|
.insert(OpcodeInstr::mksimple(0xf840, 16, "GETGLOBVAR", exec_get_global_var))
|
||||||
.insert(OpcodeInstr::mkfixedrange(0xf841, 0xf860, 16, 5, instr::dump_1c_and(31, "GETGLOB "), exec_get_global))
|
.insert(OpcodeInstr::mkfixedrange(0xf841, 0xf860, 16, 5, instr::dump_1c_and(31, "GETGLOB "), exec_get_global))
|
||||||
.insert(OpcodeInstr::mksimple(0xf860, 16, "SETGLOBVAR", exec_set_global_var))
|
.insert(OpcodeInstr::mksimple(0xf860, 16, "SETGLOBVAR", exec_set_global_var))
|
||||||
.insert(OpcodeInstr::mkfixedrange(0xf861, 0xf880, 16, 5, instr::dump_1c_and(31, "SETGLOB "), exec_set_global));
|
.insert(OpcodeInstr::mkfixedrange(0xf861, 0xf880, 16, 5, instr::dump_1c_and(31, "SETGLOB "), exec_set_global))
|
||||||
|
.insert(OpcodeInstr::mksimple(0xf880, 16, "GETEXTRABALANCE", exec_get_extra_currency_balance)->require_version(10));
|
||||||
}
|
}
|
||||||
|
|
||||||
static constexpr int randseed_idx = 6;
|
static constexpr int randseed_idx = 6;
|
||||||
|
|
|
@ -656,12 +656,12 @@ bool VmState::register_library_collection(Ref<Cell> lib) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void VmState::register_cell_load(const CellHash& cell_hash) {
|
void VmState::register_cell_load(const CellHash& cell_hash) {
|
||||||
if (cell_load_gas_price == cell_reload_gas_price) {
|
auto new_cell = loaded_cells.insert(cell_hash).second; // check whether this is the first time this cell is loaded
|
||||||
consume_gas(cell_load_gas_price);
|
consume_gas(new_cell ? cell_load_gas_price : cell_reload_gas_price);
|
||||||
} else {
|
}
|
||||||
auto ok = loaded_cells.insert(cell_hash); // check whether this is the first time this cell is loaded
|
|
||||||
consume_gas(ok.second ? cell_load_gas_price : cell_reload_gas_price);
|
bool VmState::register_cell_load_free(const CellHash& cell_hash) {
|
||||||
}
|
return loaded_cells.insert(cell_hash).second;
|
||||||
}
|
}
|
||||||
|
|
||||||
void VmState::register_cell_create() {
|
void VmState::register_cell_create() {
|
||||||
|
@ -715,10 +715,13 @@ void VmState::run_child_vm(VmState&& new_state, bool return_data, bool return_ac
|
||||||
if (!isolate_gas) {
|
if (!isolate_gas) {
|
||||||
new_state.loaded_cells = std::move(loaded_cells);
|
new_state.loaded_cells = std::move(loaded_cells);
|
||||||
} else {
|
} else {
|
||||||
consume_gas(std::min<long long>(chksgn_counter, chksgn_free_count) * chksgn_gas_price);
|
consume_gas(free_gas_consumed);
|
||||||
chksgn_counter = 0;
|
chksgn_counter = 0;
|
||||||
|
get_extra_balance_counter = 0;
|
||||||
}
|
}
|
||||||
new_state.chksgn_counter = chksgn_counter;
|
new_state.chksgn_counter = chksgn_counter;
|
||||||
|
new_state.free_gas_consumed = free_gas_consumed;
|
||||||
|
new_state.get_extra_balance_counter = get_extra_balance_counter;
|
||||||
|
|
||||||
auto new_parent = std::make_unique<ParentVmState>();
|
auto new_parent = std::make_unique<ParentVmState>();
|
||||||
new_parent->return_data = return_data;
|
new_parent->return_data = return_data;
|
||||||
|
@ -743,6 +746,8 @@ void VmState::restore_parent_vm(int res) {
|
||||||
loaded_cells = std::move(child_state.loaded_cells);
|
loaded_cells = std::move(child_state.loaded_cells);
|
||||||
}
|
}
|
||||||
chksgn_counter = child_state.chksgn_counter;
|
chksgn_counter = child_state.chksgn_counter;
|
||||||
|
get_extra_balance_counter = child_state.get_extra_balance_counter;
|
||||||
|
free_gas_consumed = child_state.free_gas_consumed;
|
||||||
VM_LOG(this) << "Child VM finished. res: " << res << ", steps: " << child_state.steps
|
VM_LOG(this) << "Child VM finished. res: " << res << ", steps: " << child_state.steps
|
||||||
<< ", gas: " << child_state.gas_consumed();
|
<< ", gas: " << child_state.gas_consumed();
|
||||||
|
|
||||||
|
|
|
@ -103,6 +103,8 @@ class VmState final : public VmStateInterface {
|
||||||
td::uint16 max_data_depth = 512; // Default value
|
td::uint16 max_data_depth = 512; // Default value
|
||||||
int global_version{0};
|
int global_version{0};
|
||||||
size_t chksgn_counter = 0;
|
size_t chksgn_counter = 0;
|
||||||
|
size_t get_extra_balance_counter = 0;
|
||||||
|
long long free_gas_consumed = 0;
|
||||||
std::unique_ptr<ParentVmState> parent = nullptr;
|
std::unique_ptr<ParentVmState> parent = nullptr;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
@ -161,7 +163,10 @@ class VmState final : public VmStateInterface {
|
||||||
bls_g2_multiexp_coef2_gas_price = 22840,
|
bls_g2_multiexp_coef2_gas_price = 22840,
|
||||||
|
|
||||||
bls_pairing_base_gas_price = 20000,
|
bls_pairing_base_gas_price = 20000,
|
||||||
bls_pairing_element_gas_price = 11800
|
bls_pairing_element_gas_price = 11800,
|
||||||
|
|
||||||
|
get_extra_balance_cheap_count = 5,
|
||||||
|
get_extra_balance_cheap_max_gas_price = 200
|
||||||
};
|
};
|
||||||
VmState();
|
VmState();
|
||||||
VmState(Ref<CellSlice> _code, int global_version, Ref<Stack> _stack, const GasLimits& _gas, int flags = 0, Ref<Cell> _data = {},
|
VmState(Ref<CellSlice> _code, int global_version, Ref<Stack> _stack, const GasLimits& _gas, int flags = 0, Ref<Cell> _data = {},
|
||||||
|
@ -214,6 +219,9 @@ class VmState final : public VmStateInterface {
|
||||||
consume_stack_gas((unsigned)stk->depth());
|
consume_stack_gas((unsigned)stk->depth());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
void consume_free_gas(long long amount) {
|
||||||
|
free_gas_consumed += amount;
|
||||||
|
}
|
||||||
GasLimits get_gas_limits() const {
|
GasLimits get_gas_limits() const {
|
||||||
return gas;
|
return gas;
|
||||||
}
|
}
|
||||||
|
@ -226,6 +234,7 @@ class VmState final : public VmStateInterface {
|
||||||
Ref<Cell> load_library(
|
Ref<Cell> load_library(
|
||||||
td::ConstBitPtr hash) override; // may throw a dictionary exception; returns nullptr if library is not found
|
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_load(const CellHash& cell_hash) override;
|
||||||
|
bool register_cell_load_free(const CellHash& cell_hash);
|
||||||
void register_cell_create() override;
|
void register_cell_create() override;
|
||||||
bool init_cp(int new_cp);
|
bool init_cp(int new_cp);
|
||||||
bool set_cp(int new_cp);
|
bool set_cp(int new_cp);
|
||||||
|
@ -420,9 +429,15 @@ class VmState final : public VmStateInterface {
|
||||||
++chksgn_counter;
|
++chksgn_counter;
|
||||||
if (chksgn_counter > chksgn_free_count) {
|
if (chksgn_counter > chksgn_free_count) {
|
||||||
consume_gas(chksgn_gas_price);
|
consume_gas(chksgn_gas_price);
|
||||||
|
} else {
|
||||||
|
consume_free_gas(chksgn_gas_price);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
bool register_get_extra_balance_call() {
|
||||||
|
++get_extra_balance_counter;
|
||||||
|
return get_extra_balance_counter <= get_extra_balance_cheap_count;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void init_cregs(bool same_c3 = false, bool push_0 = true);
|
void init_cregs(bool same_c3 = false, bool push_0 = true);
|
||||||
|
|
|
@ -3,7 +3,7 @@ Global version is a parameter specified in `ConfigParam 8` ([block.tlb](https://
|
||||||
Various features are enabled depending on the global version.
|
Various features are enabled depending on the global version.
|
||||||
|
|
||||||
## Version 4
|
## Version 4
|
||||||
New features of version 4 are desctibed in detail in [the documentation](https://docs.ton.org/v3/documentation/tvm/changelog/tvm-upgrade-2023-07).
|
New features of version 4 are described in detail in [the documentation](https://docs.ton.org/v3/documentation/tvm/changelog/tvm-upgrade-2023-07).
|
||||||
|
|
||||||
### New TVM instructions
|
### New TVM instructions
|
||||||
* `PREVMCBLOCKS`, `PREVKEYBLOCK`
|
* `PREVMCBLOCKS`, `PREVKEYBLOCK`
|
||||||
|
@ -152,7 +152,17 @@ Example: if the last masterchain block seqno is `19071` then the list contains b
|
||||||
- `SENDMSG` does not check the number of extra currencies.
|
- `SENDMSG` does not check the number of extra currencies.
|
||||||
- Extra currency dictionary is not counted in the account size and does not affect storage fees.
|
- Extra currency dictionary is not counted in the account size and does not affect storage fees.
|
||||||
- Accounts with already existing extra currencies will get their sizes recomputed without EC only after modifying `AccountState`.
|
- Accounts with already existing extra currencies will get their sizes recomputed without EC only after modifying `AccountState`.
|
||||||
|
- Reserve modes `+1`, `+4` and `+8` ("reserve all except", "add original balance" and "negate amount") now only affect TONs, but not extra currencies.
|
||||||
|
- One reserve action can have at most 2 different extra currencies. Amount of an extra currency in the dictionary can be zero.
|
||||||
|
|
||||||
### TVM changes
|
### TVM changes
|
||||||
- `SENDMSG` calculates messages size and fees without extra currencies, uses new +64 and +128 mode behavior.
|
- `SENDMSG` calculates messages size and fees without extra currencies, uses new +64 and +128 mode behavior.
|
||||||
- `SENDMSG` does not check the number of extra currencies.
|
- `SENDMSG` does not check the number of extra currencies.
|
||||||
|
- New instruction `GETEXTRABALANCE` (`id - amount`). Takes id of the extra currency (integer in range `0..2^32-1`), returns the amount of this extra currency on the account balance.
|
||||||
|
- This is equivalent to taking the extra currency dictionary (`BALANCE SECOND`), loading value (`UDICTGET`) and parsing it (`LDVARUINT32`). If `id` is not present in the dictionary, `0` is returned.
|
||||||
|
- `GETEXTRABALANCE` has special gas cost that allows writing gas-efficient code with predictable gas usage even if there are a lot of different extra currencies.
|
||||||
|
- The full gas cost of `GETEXTRABALANCE` is `26` (normal instruction cost) plus gas for loading cells (up to `3300` if the dictionary has maximum depth).
|
||||||
|
- However, the first `5` executions of `GETEXTRABALANCE` cost at most `26+200` gas units. All subsequent executions cost the full price.
|
||||||
|
- `RUNVM` interacts with this instructions in the following way:
|
||||||
|
- Without "isolate gas" mode, the child VM shares `GETEXTRABALANCE` counter with the parent vm.
|
||||||
|
- With "isolate gas" mode, in the beginning of `RUNVM` the parent VM spends full gas for all already executed `GETEXTRABALANCE` and resets the counter.
|
Loading…
Add table
Add a link
Reference in a new issue