Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Soroban: Add extendTtl Builtin Method #1709

Merged
Merged
44 changes: 44 additions & 0 deletions docs/language/builtins.rst
Original file line number Diff line number Diff line change
Expand Up @@ -666,3 +666,47 @@ Assuming `arg1` is 512 and `arg2` is 196, the output to the log will be ``foo en
When formatting integers in to decimals, types larger than 64 bits require expensive division.
Be mindful this will increase the gas cost. Larger values will incur a higher gas cost.
Alternatively, use a hexadecimal ``{:x}`` format specifier to reduce the cost.


extendTtl(uint32 threshold, uint32 extend_to)
+++++++++++++++++++++++++++++++++++++++++++++++++++++++

The ``extendTtl()`` method allows extending the time-to-live (TTL) of a contract storage entry.

If the entry's TTL is below threshold ledgers, this function updates ``live_until_ledger_seq`` such that TTL equals ``extend_to``. The TTL is defined as:

.. math::

TTL = live_until_ledger_seq - current_ledger


.. note:: This method is only available on the Soroban target

.. code-block:: solidity

/// Extends the TTL for the `count` persistent key to 5000 ledgers
/// if the current TTL is smaller than 1000 ledgers
function extend_ttl() public view returns (int64) {
return count.extendTtl(1000, 5000);
}



For more details on managing contract data TTLs in Soroban, refer to the docs for `TTL <https://developers.stellar.org/docs/build/smart-contracts/getting-started/storing-data#managing-contract-data-ttls-with-extend_ttl>`_.

extendInstanceTtl(uint32 threshold, uint32 extend_to)
+++++++++++++++++++++++++++++++++++++++++++++++++++++++

The extendInstanceTtl() function extends the time-to-live (TTL) of contract instance storage.

If the TTL for the current contract instance and code (if applicable) is below threshold ledgers, this function extends ``live_until_ledger_seq`` such that TTL equals ``extend_to``.

.. note:: This is a global function, not a method, and is only available on the Soroban target

.. code-block:: solidity

/// Extends the TTL for the contract instance storage to 10000 ledgers
/// if the current TTL is smaller than 2000 ledgers
function extendInstanceTtl() public view returns (int64) {
return extendInstanceTtl(2000, 10000);
}
38 changes: 38 additions & 0 deletions src/codegen/expression.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ use crate::sema::{
expression::ResolveTo,
};
use crate::Target;
use core::panic;
use num_bigint::{BigInt, Sign};
use num_traits::{FromPrimitive, One, ToPrimitive, Zero};
use solang_parser::pt::{self, CodeLocation, Loc};
Expand Down Expand Up @@ -2327,6 +2328,43 @@ fn expr_builtin(

code(loc, *contract_no, ns, opt)
}
ast::Builtin::ExtendTtl => {
let mut arguments: Vec<Expression> = args
.iter()
.map(|v| expression(v, cfg, contract_no, func, ns, vartab, opt))
.collect();

// var_no is the first argument of the builtin
let var_no = match arguments[0].clone() {
Expression::NumberLiteral { value, .. } => value,
_ => panic!("First argument of extendTtl() must be a number literal"),
}
.to_usize()
.expect("Unable to convert var_no to usize");
let var = ns.contracts[contract_no].variables.get(var_no).unwrap();
let storage_type_usize = match var
.storage_type
.clone()
.expect("Unable to get storage type") {
solang_parser::pt::StorageType::Temporary(_) => 0,
solang_parser::pt::StorageType::Persistent(_) => 1,
solang_parser::pt::StorageType::Instance(_) => panic!("Calling extendTtl() on instance storage is not allowed. Use `extendInstanceTtl()` instead."),
};

// append the storage type to the arguments
arguments.push(Expression::NumberLiteral {
loc: *loc,
ty: Type::Uint(32),
value: BigInt::from(storage_type_usize),
});

Expression::Builtin {
loc: *loc,
tys: tys.to_vec(),
kind: (&builtin).into(),
args: arguments,
}
}
_ => {
let arguments: Vec<Expression> = args
.iter()
Expand Down
8 changes: 8 additions & 0 deletions src/codegen/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,8 @@ impl From<inkwell::OptimizationLevel> for OptimizationLevel {
pub enum HostFunctions {
PutContractData,
GetContractData,
ExtendContractDataTtl,
ExtendCurrentContractInstanceAndCodeTtl,
LogFromLinearMemory,
SymbolNewFromLinearMemory,
VectorNew,
Expand All @@ -111,6 +113,8 @@ impl HostFunctions {
match self {
HostFunctions::PutContractData => "l._",
HostFunctions::GetContractData => "l.1",
HostFunctions::ExtendContractDataTtl => "l.7",
HostFunctions::ExtendCurrentContractInstanceAndCodeTtl => "l.8",
HostFunctions::LogFromLinearMemory => "x._",
HostFunctions::SymbolNewFromLinearMemory => "b.j",
HostFunctions::VectorNew => "v._",
Expand Down Expand Up @@ -1794,6 +1798,8 @@ pub enum Builtin {
WriteUint256LE,
WriteBytes,
Concat,
ExtendTtl,
ExtendInstanceTtl,
}

impl From<&ast::Builtin> for Builtin {
Expand Down Expand Up @@ -1856,6 +1862,8 @@ impl From<&ast::Builtin> for Builtin {
ast::Builtin::PrevRandao => Builtin::PrevRandao,
ast::Builtin::ContractCode => Builtin::ContractCode,
ast::Builtin::StringConcat | ast::Builtin::BytesConcat => Builtin::Concat,
ast::Builtin::ExtendTtl => Builtin::ExtendTtl,
ast::Builtin::ExtendInstanceTtl => Builtin::ExtendInstanceTtl,
_ => panic!("Builtin should not be in the cfg"),
}
}
Expand Down
4 changes: 4 additions & 0 deletions src/codegen/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ fn test_builtin_conversion() {
ast::Builtin::WriteUint256LE,
ast::Builtin::WriteString,
ast::Builtin::WriteBytes,
ast::Builtin::ExtendTtl,
ast::Builtin::ExtendInstanceTtl,
];

let output: Vec<codegen::Builtin> = vec![
Expand Down Expand Up @@ -115,6 +117,8 @@ fn test_builtin_conversion() {
codegen::Builtin::WriteUint256LE,
codegen::Builtin::WriteBytes,
codegen::Builtin::WriteBytes,
codegen::Builtin::ExtendTtl,
codegen::Builtin::ExtendInstanceTtl,
];

for (i, item) in input.iter().enumerate() {
Expand Down
15 changes: 15 additions & 0 deletions src/emit/soroban/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,19 @@ impl HostFunctions {
.context
.i64_type()
.fn_type(&[ty.into(), ty.into()], false),
// https://github.com/stellar/stellar-protocol/blob/2fdc77302715bc4a31a784aef1a797d466965024/core/cap-0046-03.md#ledger-host-functions-mod-l
// ;; If the entry's TTL is below `threshold` ledgers, extend `live_until_ledger_seq` such that TTL == `extend_to`, where TTL is defined as live_until_ledger_seq - current ledger.
// (func $extend_contract_data_ttl (param $k_val i64) (param $t_storage_type i64) (param $threshold_u32_val i64) (param $extend_to_u32_val i64) (result i64))
HostFunctions::ExtendContractDataTtl => bin
.context
.i64_type()
.fn_type(&[ty.into(), ty.into(), ty.into(), ty.into()], false),
// ;; If the TTL for the current contract instance and code (if applicable) is below `threshold` ledgers, extend `live_until_ledger_seq` such that TTL == `extend_to`, where TTL is defined as live_until_ledger_seq - current ledger.
// (func $extend_current_contract_instance_and_code_ttl (param $threshold_u32_val i64) (param $extend_to_u32_val i64) (result i64))
HostFunctions::ExtendCurrentContractInstanceAndCodeTtl => bin
.context
.i64_type()
.fn_type(&[ty.into(), ty.into()], false),
HostFunctions::LogFromLinearMemory => bin
.context
.i64_type()
Expand Down Expand Up @@ -279,6 +292,8 @@ impl SorobanTarget {
let host_functions = [
HostFunctions::PutContractData,
HostFunctions::GetContractData,
HostFunctions::ExtendContractDataTtl,
HostFunctions::ExtendCurrentContractInstanceAndCodeTtl,
HostFunctions::LogFromLinearMemory,
HostFunctions::SymbolNewFromLinearMemory,
HostFunctions::VectorNew,
Expand Down
151 changes: 150 additions & 1 deletion src/emit/soroban/target.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// SPDX-License-Identifier: Apache-2.0

use crate::codegen::cfg::HashTy;
use crate::codegen::Builtin;
use crate::codegen::Expression;
use crate::emit::binary::Binary;
use crate::emit::soroban::{HostFunctions, SorobanTarget};
Expand All @@ -19,6 +20,8 @@ use inkwell::values::{

use solang_parser::pt::{Loc, StorageType};

use num_traits::ToPrimitive;

use std::collections::HashMap;

// TODO: Implement TargetRuntime for SorobanTarget.
Expand Down Expand Up @@ -460,7 +463,153 @@ impl<'a> TargetRuntime<'a> for SorobanTarget {
function: FunctionValue<'b>,
ns: &Namespace,
) -> BasicValueEnum<'b> {
unimplemented!()
emit_context!(bin);

match expr {
Expression::Builtin {
kind: Builtin::ExtendTtl,
args,
..
} => {
// Get arguments
// (func $extend_contract_data_ttl (param $k_val i64) (param $t_storage_type i64) (param $threshold_u32_val i64) (param $extend_to_u32_val i64) (result i64))
assert_eq!(args.len(), 4, "extendTtl expects 4 arguments");
// SAFETY: We already checked that the length of args is 4 so it is safe to unwrap here
let slot_no = match args.first().unwrap() {
Expression::NumberLiteral { value, .. } => value,
_ => panic!(
"Expected slot_no to be of type Expression::NumberLiteral. Actual: {:?}",
args.get(1).unwrap()
),
}
.to_u64()
.unwrap();
let threshold = match args.get(1).unwrap() {
Expression::NumberLiteral { value, .. } => value,
_ => panic!(
"Expected threshold to be of type Expression::NumberLiteral. Actual: {:?}",
args.get(1).unwrap()
),
}
.to_u64()
.unwrap();
let extend_to = match args.get(2).unwrap() {
Expression::NumberLiteral { value, .. } => value,
_ => panic!(
"Expected extend_to to be of type Expression::NumberLiteral. Actual: {:?}",
args.get(2).unwrap()
),
}
.to_u64()
.unwrap();
let storage_type = match args.get(3).unwrap() {
Expression::NumberLiteral { value, .. } => value,
_ => panic!(
"Expected storage_type to be of type Expression::NumberLiteral. Actual: {:?}",
args.get(3).unwrap()
),
}
.to_u64()
.unwrap();

// Encode the values (threshold and extend_to)
// See: https://github.com/stellar/stellar-protocol/blob/master/core/cap-0046-01.md#tag-values
let threshold_u32_val = (threshold << 32) + 4;
let extend_to_u32_val = (extend_to << 32) + 4;

// Call the function
let function_name = HostFunctions::ExtendContractDataTtl.name();
let function_value = bin.module.get_function(function_name).unwrap();

let value = bin
.builder
.build_call(
function_value,
&[
bin.context.i64_type().const_int(slot_no, false).into(),
bin.context.i64_type().const_int(storage_type, false).into(),
bin.context
.i64_type()
.const_int(threshold_u32_val, false)
.into(),
bin.context
.i64_type()
.const_int(extend_to_u32_val, false)
.into(),
],
function_name,
)
.unwrap()
.try_as_basic_value()
.left()
.unwrap()
.into_int_value();

value.into()
}
Expression::Builtin {
kind: Builtin::ExtendInstanceTtl,
args,
..
} => {
// Get arguments
// (func $extend_contract_data_ttl (param $k_val i64) (param $t_storage_type i64) (param $threshold_u32_val i64) (param $extend_to_u32_val i64) (result i64))
assert_eq!(args.len(), 2, "extendTtl expects 2 arguments");
// SAFETY: We already checked that the length of args is 2 so it is safe to unwrap here
let threshold = match args.first().unwrap() {
Expression::NumberLiteral { value, .. } => value,
_ => panic!(
"Expected threshold to be of type Expression::NumberLiteral. Actual: {:?}",
args.get(1).unwrap()
),
}
.to_u64()
.unwrap();
let extend_to = match args.get(1).unwrap() {
Expression::NumberLiteral { value, .. } => value,
_ => panic!(
"Expected extend_to to be of type Expression::NumberLiteral. Actual: {:?}",
args.get(2).unwrap()
),
}
.to_u64()
.unwrap();

// Encode the values (threshold and extend_to)
// See: https://github.com/stellar/stellar-protocol/blob/master/core/cap-0046-01.md#tag-values
let threshold_u32_val = (threshold << 32) + 4;
let extend_to_u32_val = (extend_to << 32) + 4;

// Call the function
let function_name = HostFunctions::ExtendCurrentContractInstanceAndCodeTtl.name();
let function_value = bin.module.get_function(function_name).unwrap();

let value = bin
.builder
.build_call(
function_value,
&[
bin.context
.i64_type()
.const_int(threshold_u32_val, false)
.into(),
bin.context
.i64_type()
.const_int(extend_to_u32_val, false)
.into(),
],
function_name,
)
.unwrap()
.try_as_basic_value()
.left()
.unwrap()
.into_int_value();

value.into()
}
_ => unimplemented!("unsupported builtin"),
}
}

/// Return the return data from an external call (either revert error or return values)
Expand Down
2 changes: 2 additions & 0 deletions src/sema/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1779,6 +1779,8 @@ pub enum Builtin {
TypeInterfaceId,
TypeRuntimeCode,
TypeCreatorCode,
ExtendTtl,
ExtendInstanceTtl,
}

#[derive(PartialEq, Eq, Clone, Debug)]
Expand Down
Loading
Loading