From 0242f8c14e37fe25b2f5df61f17a0a936fa56933 Mon Sep 17 00:00:00 2001 From: Jonathan Jove Date: Thu, 9 Jun 2022 09:43:25 -0500 Subject: [PATCH 01/16] Change rs-stellar-contract-host to point to a commit on stellar/rs-stellar-contract-env --- Cargo.lock | 6 +++--- src/rust/Cargo.toml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 04b3d397bb..c7c1a01a79 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -214,7 +214,7 @@ checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] name = "stellar-contract-env-common" version = "0.0.0" -source = "git+https://github.com/stellar/rs-stellar-contract-env?rev=9a783c26cbc022e0351538a3e9582a58510bc694#9a783c26cbc022e0351538a3e9582a58510bc694" +source = "git+https://github.com/stellar/rs-stellar-contract-env?rev=03ddc72fea37bd5bafd2566a0fd20e088e6d8381#03ddc72fea37bd5bafd2566a0fd20e088e6d8381" dependencies = [ "static_assertions", "stellar-xdr", @@ -224,7 +224,7 @@ dependencies = [ [[package]] name = "stellar-contract-env-host" version = "0.0.0" -source = "git+https://github.com/stellar/rs-stellar-contract-env?rev=9a783c26cbc022e0351538a3e9582a58510bc694#9a783c26cbc022e0351538a3e9582a58510bc694" +source = "git+https://github.com/stellar/rs-stellar-contract-env?rev=03ddc72fea37bd5bafd2566a0fd20e088e6d8381#03ddc72fea37bd5bafd2566a0fd20e088e6d8381" dependencies = [ "im-rc", "num-bigint", @@ -250,7 +250,7 @@ dependencies = [ [[package]] name = "stellar-xdr" version = "0.0.0" -source = "git+https://github.com/stellar/rs-stellar-xdr?rev=2a8b24c2978303612c49afcf005c1d35c592c97c#2a8b24c2978303612c49afcf005c1d35c592c97c" +source = "git+https://github.com/jonjove/rs-stellar-xdr?rev=72ea8fc0c01d27e9cb5a697ee73a9d3db9d8a09b#72ea8fc0c01d27e9cb5a697ee73a9d3db9d8a09b" dependencies = [ "base64", ] diff --git a/src/rust/Cargo.toml b/src/rust/Cargo.toml index 62019acb5e..7fdb296299 100644 --- a/src/rust/Cargo.toml +++ b/src/rust/Cargo.toml @@ -12,6 +12,6 @@ log = "0.4.17" cxx = "1.0" im-rc = "15.0.0" base64 = "0.13.0" -stellar-contract-env-host = { git = "https://github.com/stellar/rs-stellar-contract-env", rev = "9a783c26cbc022e0351538a3e9582a58510bc694", features = [ +stellar-contract-env-host = { git = "https://github.com/stellar/rs-stellar-contract-env", rev = "03ddc72fea37bd5bafd2566a0fd20e088e6d8381", features = [ "vm", ] } From 024f078e51bd08877f07e6b4571b28b8bda1d8d9 Mon Sep 17 00:00:00 2001 From: Jonathan Jove Date: Tue, 14 Jun 2022 12:11:02 -0500 Subject: [PATCH 02/16] Fix linker error in loadContractData caused by signature mismatch --- src/transactions/TransactionUtils.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/transactions/TransactionUtils.h b/src/transactions/TransactionUtils.h index 66189f00fd..92ace7d82a 100644 --- a/src/transactions/TransactionUtils.h +++ b/src/transactions/TransactionUtils.h @@ -140,7 +140,7 @@ LedgerTxnEntry loadLiquidityPool(AbstractLedgerTxn& ltx, PoolID const& poolID); #ifdef ENABLE_NEXT_PROTOCOL_VERSION_UNSAFE_FOR_PRODUCTION LedgerTxnEntry loadContractData(AbstractLedgerTxn& ltx, Hash const& contractID, - SCVal dataKey); + SCVal const& dataKey); #endif void acquireLiabilities(AbstractLedgerTxn& ltx, LedgerTxnHeader const& header, From 1070a231d87ce6720785ee57846f563eaf61de33 Mon Sep 17 00:00:00 2001 From: Jonathan Jove Date: Fri, 10 Jun 2022 16:32:11 -0500 Subject: [PATCH 03/16] CAP-52 XDR --- src/protocol-next/xdr/Stellar-transaction.x | 45 ++++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/src/protocol-next/xdr/Stellar-transaction.x b/src/protocol-next/xdr/Stellar-transaction.x index 74d282b8c8..7e915593e1 100644 --- a/src/protocol-next/xdr/Stellar-transaction.x +++ b/src/protocol-next/xdr/Stellar-transaction.x @@ -2,6 +2,7 @@ // under the Apache License, Version 2.0. See the COPYING file at the root // of this distribution or at http://www.apache.org/licenses/LICENSE-2.0 +%#include "xdr/Stellar-contract.h" %#include "xdr/Stellar-ledger-entries.h" namespace stellar @@ -64,7 +65,8 @@ enum OperationType CLAWBACK_CLAIMABLE_BALANCE = 20, SET_TRUST_LINE_FLAGS = 21, LIQUIDITY_POOL_DEPOSIT = 22, - LIQUIDITY_POOL_WITHDRAW = 23 + LIQUIDITY_POOL_WITHDRAW = 23, + INVOKE_HOST_FUNCTION = 24 }; /* CreateAccount @@ -472,6 +474,24 @@ struct LiquidityPoolWithdrawOp int64 minAmountB; // minimum amount of second asset to withdraw }; +enum HostFunction +{ + HOST_FN_CALL = 0, + HOST_FN_CREATE_CONTRACT = 1 +}; + +struct InvokeHostFunctionOp +{ + // The host function to invoke + HostFunction function; + + // Parameters to the host function + SCVec parameters; + + // The footprint for this invocation + LedgerFootprint footprint; +}; + /* An operation is the lowest unit of work that a transaction does */ struct Operation { @@ -530,6 +550,8 @@ struct Operation LiquidityPoolDepositOp liquidityPoolDepositOp; case LIQUIDITY_POOL_WITHDRAW: LiquidityPoolWithdrawOp liquidityPoolWithdrawOp; + case INVOKE_HOST_FUNCTION: + InvokeHostFunctionOp invokeHostFunctionOp; } body; }; @@ -1595,6 +1617,25 @@ case LIQUIDITY_POOL_WITHDRAW_UNDER_MINIMUM: void; }; +enum InvokeHostFunctionResultCode +{ + // codes considered as "success" for the operation + INVOKE_HOST_FUNCTION_SUCCESS = 0, + + // codes considered as "failure" for the operation + INVOKE_HOST_FUNCTION_MALFORMED = -1, + INVOKE_HOST_FUNCTION_TRAPPED = -2 +}; + +union InvokeHostFunctionResult switch (InvokeHostFunctionResultCode code) +{ +case INVOKE_HOST_FUNCTION_SUCCESS: + void; +case INVOKE_HOST_FUNCTION_MALFORMED: +case INVOKE_HOST_FUNCTION_TRAPPED: + void; +}; + /* High level Operation Result */ enum OperationResultCode { @@ -1661,6 +1702,8 @@ case opINNER: LiquidityPoolDepositResult liquidityPoolDepositResult; case LIQUIDITY_POOL_WITHDRAW: LiquidityPoolWithdrawResult liquidityPoolWithdrawResult; + case INVOKE_HOST_FUNCTION: + InvokeHostFunctionResult invokeHostFunctionResult; } tr; case opBAD_AUTH: From a27de993d08d6eb2aa678b95b77251277f4b3017 Mon Sep 17 00:00:00 2001 From: Jonathan Jove Date: Tue, 14 Jun 2022 14:02:00 -0500 Subject: [PATCH 04/16] Remove ContractUtils and ContractTests --- src/transactions/contracts/ContractUtils.cpp | 64 ----------- src/transactions/contracts/ContractUtils.h | 22 ---- src/transactions/test/ContractTests.cpp | 115 ------------------- 3 files changed, 201 deletions(-) delete mode 100644 src/transactions/contracts/ContractUtils.cpp delete mode 100644 src/transactions/contracts/ContractUtils.h delete mode 100644 src/transactions/test/ContractTests.cpp diff --git a/src/transactions/contracts/ContractUtils.cpp b/src/transactions/contracts/ContractUtils.cpp deleted file mode 100644 index 3d1d8d605b..0000000000 --- a/src/transactions/contracts/ContractUtils.cpp +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright 2022 Stellar Development Foundation and contributors. Licensed -// under the Apache License, Version 2.0. See the COPYING file at the root -// of this distribution or at http://www.apache.org/licenses/LICENSE-2.0 - -#ifdef ENABLE_NEXT_PROTOCOL_VERSION_UNSAFE_FOR_PRODUCTION - -#include "transactions/contracts/ContractUtils.h" -#include "crypto/Hex.h" -#include "rust/RustBridge.h" -#include "rust/RustVecXdrMarshal.h" -#include "util/Logging.h" -#include "util/XDROperators.h" -#include "util/types.h" -#include "xdr/Stellar-contract.h" -#include "xdr/Stellar-transaction.h" - -#include - -namespace stellar -{ - -template -XDRBuf -toXDRBuf(T const& t) -{ - return XDRBuf{ - std::make_unique>(xdr::xdr_to_opaque(t))}; -} - -// Invoke a contract with a given function name and args vector, where -// ledgerEntries is a vector of buffers each of which contains a ledger entry in -// the footprint of the contract. The keys in footprint and ledgerEntries -// must be the same, and ledgerEntries must contain an entry -// -// { -// contract_id, -// key: ScVal::Static(ScStatic::LedgerKeyContractCodeWasm) -// val: ScVal::Object(Some(ScObject::Binary(blob))) -// } -// -// which contains the WASM blob that will be run. -SCVal -invokeContract(Hash const& contract_id, std::string const& funcName, - SCVec const& args, LedgerFootprint const& footprint, - std::vector>> ledgerEntries) -{ - ZoneScoped; - rust::Vec xdrBufs; - xdrBufs.reserve(ledgerEntries.size()); - for (auto& p : ledgerEntries) - { - xdrBufs.push_back(XDRBuf{std::move(p)}); - } - rust::Vec retBuf = rust_bridge::invoke_contract( - toXDRBuf(contract_id), funcName, toXDRBuf(args), toXDRBuf(footprint), - xdrBufs); - SCVal ret; - xdr::xdr_from_opaque(retBuf, ret); - return ret; -} - -} - -#endif diff --git a/src/transactions/contracts/ContractUtils.h b/src/transactions/contracts/ContractUtils.h deleted file mode 100644 index 42c1c7200d..0000000000 --- a/src/transactions/contracts/ContractUtils.h +++ /dev/null @@ -1,22 +0,0 @@ -#pragma once - -// Copyright 2022 Stellar Development Foundation and contributors. Licensed -// under the Apache License, Version 2.0. See the COPYING file at the root -// of this distribution or at http://www.apache.org/licenses/LICENSE-2.0 - -#ifdef ENABLE_NEXT_PROTOCOL_VERSION_UNSAFE_FOR_PRODUCTION - -#include "xdr/Stellar-contract.h" -#include "xdr/Stellar-transaction.h" -#include -#include -#include - -namespace stellar -{ -SCVal invokeContract( - Hash const& contract_id, std::string const& funcName, SCVec const& args, - LedgerFootprint const& footprint, - std::vector>> ledgerEntries); -} -#endif diff --git a/src/transactions/test/ContractTests.cpp b/src/transactions/test/ContractTests.cpp deleted file mode 100644 index eb4ef30f27..0000000000 --- a/src/transactions/test/ContractTests.cpp +++ /dev/null @@ -1,115 +0,0 @@ -// Copyright 2022 Stellar Development Foundation and contributors. Licensed -// under the Apache License, Version 2.0. See the COPYING file at the root -// of this distribution or at http://www.apache.org/licenses/LICENSE-2.0 - -#ifdef ENABLE_NEXT_PROTOCOL_VERSION_UNSAFE_FOR_PRODUCTION - -#include "crypto/SecretKey.h" -#include "ledger/LedgerTxn.h" -#include "lib/catch.hpp" -#include "rust/RustBridge.h" -#include "test/test.h" -#include "xdr/Stellar-ledger-entries.h" -#include -#include -#include -#include -#include - -#include "transactions/contracts/ContractUtils.h" -#include "xdr/Stellar-contract.h" - -using namespace stellar; - -// This is an example WASM from the SDK that unpacks two SCV_I32 arguments, adds -// them with an overflow check, and re-packs them as an SCV_I32 if successful. -// -// To regenerate, check out the SDK, install a nightly toolchain with -// the rust-src component (to enable the 'tiny' build) using the following: -// -// $ rustup component add rust-src --toolchain nightly -// -// then do: -// -// $ make tiny -// $ xxd -i target/wasm32-unknown-unknown/release/example_add_i32.wasm - -std::vector add_i32_wasm{ - 0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, 0x01, 0x07, 0x01, 0x60, - 0x02, 0x7e, 0x7e, 0x01, 0x7e, 0x03, 0x02, 0x01, 0x00, 0x05, 0x03, 0x01, - 0x00, 0x10, 0x06, 0x11, 0x02, 0x7f, 0x00, 0x41, 0x80, 0x80, 0xc0, 0x00, - 0x0b, 0x7f, 0x00, 0x41, 0x80, 0x80, 0xc0, 0x00, 0x0b, 0x07, 0x2b, 0x04, - 0x06, 0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x02, 0x00, 0x03, 0x61, 0x64, - 0x64, 0x00, 0x00, 0x0a, 0x5f, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x5f, 0x65, - 0x6e, 0x64, 0x03, 0x00, 0x0b, 0x5f, 0x5f, 0x68, 0x65, 0x61, 0x70, 0x5f, - 0x62, 0x61, 0x73, 0x65, 0x03, 0x01, 0x0a, 0x47, 0x01, 0x45, 0x01, 0x02, - 0x7f, 0x02, 0x40, 0x20, 0x00, 0x42, 0x0f, 0x83, 0x42, 0x03, 0x52, 0x20, - 0x01, 0x42, 0x0f, 0x83, 0x42, 0x03, 0x52, 0x72, 0x45, 0x04, 0x40, 0x20, - 0x01, 0x42, 0x04, 0x88, 0xa7, 0x22, 0x02, 0x41, 0x00, 0x48, 0x20, 0x02, - 0x20, 0x00, 0x42, 0x04, 0x88, 0xa7, 0x22, 0x03, 0x6a, 0x22, 0x02, 0x20, - 0x03, 0x48, 0x73, 0x45, 0x0d, 0x01, 0x0b, 0x00, 0x0b, 0x20, 0x02, 0xad, - 0x42, 0x04, 0x86, 0x42, 0x03, 0x84, 0x0b}; - -TEST_CASE("WASM test", "[wasm]") -{ - SCVal x, y; - - x.type(SCV_I32); - x.i32() = 5; - - y.type(SCV_I32); - y.i32() = 7; - - SCVec args{x, y}; - - namespace ch = std::chrono; - using clock = ch::high_resolution_clock; - using usec = ch::microseconds; - - Hash contract_id = HashUtils::pseudoRandomForTesting(); - - LedgerFootprint footprint; - LedgerEntry wasm_le; - LedgerKey wasm_lk; - SCVal wasm_key; - SCVal wasm_val; - - wasm_key.type(SCValType::SCV_STATIC); - wasm_key.ic() = SCStatic::SCS_LEDGER_KEY_CONTRACT_CODE_WASM; - - wasm_val.type(SCValType::SCV_OBJECT); - wasm_val.obj().activate(); - wasm_val.obj()->type(SCO_BINARY); - wasm_val.obj()->bin().assign(add_i32_wasm.begin(), add_i32_wasm.end()); - - wasm_lk.type(CONTRACT_DATA); - wasm_lk.contractData().contractID = contract_id; - wasm_lk.contractData().key = wasm_key; - - wasm_le.data.type(CONTRACT_DATA); - wasm_le.data.contractData().contractID = contract_id; - wasm_le.data.contractData().key = wasm_key; - wasm_le.data.contractData().val = wasm_val; - - footprint.readOnly.emplace_back(wasm_lk); - - std::unique_ptr> wasm_xdr_ptr = - std::make_unique>(xdr::xdr_to_opaque(wasm_le)); - std::vector>> xdr_buffers; - xdr_buffers.emplace_back(std::move(wasm_xdr_ptr)); - - auto begin = clock::now(); - auto res = invokeContract(contract_id, "add", args, footprint, - std::move(xdr_buffers)); - auto end = clock::now(); - - auto us = ch::duration_cast(end - begin); - - REQUIRE(res.type() == SCV_I32); - REQUIRE(res.i32() == x.i32() + y.i32()); - - LOG_INFO(DEFAULT_LOG, "calculated {} + {} = {} in {} usecs", x.i32(), - y.i32(), res.i32(), us.count()); -} - -#endif \ No newline at end of file From 353d99eb26382e727048a3d4ed32fab28f209eeb Mon Sep 17 00:00:00 2001 From: Jonathan Jove Date: Tue, 14 Jun 2022 15:25:07 -0500 Subject: [PATCH 05/16] Remove invoke_contract --- src/rust/src/contract.rs | 70 ++-------------------------------------- src/rust/src/lib.rs | 8 ----- 2 files changed, 3 insertions(+), 75 deletions(-) diff --git a/src/rust/src/contract.rs b/src/rust/src/contract.rs index f7ea91473a..7d1eb32efb 100644 --- a/src/rust/src/contract.rs +++ b/src/rust/src/contract.rs @@ -6,17 +6,16 @@ use crate::{log::partition::TX, rust_bridge::XDRBuf}; use log::info; use std::io::Cursor; -use cxx::CxxString; use im_rc::OrdMap; use std::error::Error; use stellar_contract_env_host::{ storage::{self, AccessType, Key, Storage}, xdr, xdr::{ - ContractDataEntry, Hash, LedgerEntry, LedgerEntryData, LedgerKey, ReadXdr, ScObject, - ScStatic, ScVal, ScVec, WriteXdr, + ContractDataEntry, LedgerEntry, LedgerEntryData, LedgerKey, ReadXdr, ScVal, ScVec, + WriteXdr, }, - Host, HostError, Vm, + Host, HostError, }; /// Helper for [`build_storage_footprint_from_xdr`] that inserts a copy of some @@ -95,66 +94,3 @@ fn build_storage_map_from_xdr_ledger_entries( } Ok(map) } - -/// Looks up an [`ScObject`] un the provided map under the ledger key -/// [`ScStatic::LedgerKeyContractCodeWasm`] and returns a copy of its binary -/// data, which should be WASM bytecode. -fn extract_contract_wasm_from_storage_map( - contract_id: &Hash, - map: &OrdMap>, -) -> Result, HostError> { - let wasm_key = Key { - contract_id: contract_id.clone(), - key: ScVal::Static(ScStatic::LedgerKeyContractCodeWasm), - }; - let wasm = match map.get(&wasm_key) { - Some(Some(ScVal::Object(Some(ScObject::Binary(blob))))) => blob.clone(), - Some(_) => { - return Err(HostError::General( - "unexpected value type for LEDGER_KEY_CONTRACT_CODE_WASM", - ) - .into()) - } - None => { - return Err( - HostError::General("missing value for LEDGER_KEY_CONTRACT_CODE_WASM").into(), - ) - } - }; - Ok(wasm.to_vec()) -} - -/// Deserializes an [`xdr::Hash`] contract ID, an [`ScVec`] XDR object of -/// arguments, an [`xdr::LedgerFootprint`] and a sequence of [`xdr::LedgerEntry`] -/// entries containing all the data the contract intends to read. Then loads -/// some WASM bytecode out of the provided ledger entries (keyed under -/// [`ScStatic::LedgerKeyContractCodeWasm`]), instantiates a [`Host`] and [`Vm`] -/// with the provided WASM, invokes the requested function in the WASM, and -/// serializes an [`xdr::ScVal`] back into a return value. -pub(crate) fn invoke_contract( - contract_id: &XDRBuf, - func: &CxxString, - args: &XDRBuf, - footprint: &XDRBuf, - ledger_entries: &Vec, -) -> Result, Box> { - let contract_id = Hash::read_xdr(&mut Cursor::new(contract_id.data.as_slice()))?; - let arg_scvals = ScVec::read_xdr(&mut Cursor::new(args.data.as_slice()))?; - - let footprint = build_storage_footprint_from_xdr(footprint)?; - let map = build_storage_map_from_xdr_ledger_entries(&footprint, ledger_entries)?; - let wasm = extract_contract_wasm_from_storage_map(&contract_id, &map)?; - - let func_str = func.to_str()?; - - let storage = Storage::with_enforcing_footprint_and_map(footprint, map); - let mut host = Host::with_storage(storage); - let vm = Vm::new(&host, contract_id, wasm.as_slice())?; - - info!(target: TX, "Invoking contract function '{}'", func); - let res = vm.invoke_function(&mut host, func_str, &arg_scvals)?; - - let mut ret_xdr_buf: Vec = Vec::new(); - res.write_xdr(&mut Cursor::new(&mut ret_xdr_buf))?; - Ok(ret_xdr_buf) -} diff --git a/src/rust/src/lib.rs b/src/rust/src/lib.rs index e3ad479857..f05cbd94e2 100644 --- a/src/rust/src/lib.rs +++ b/src/rust/src/lib.rs @@ -35,13 +35,6 @@ mod rust_bridge { extern "Rust" { fn to_base64(b: &CxxVector, mut s: Pin<&mut CxxString>); fn from_base64(s: &CxxString, mut b: Pin<&mut CxxVector>); - fn invoke_contract( - contract_id: &XDRBuf, - func: &CxxString, - args: &XDRBuf, - footprint: &XDRBuf, - ledger_entries: &Vec, - ) -> Result>; fn init_logging(maxLevel: LogLevel) -> Result<()>; } @@ -64,7 +57,6 @@ mod b64; use b64::{from_base64, to_base64}; mod contract; -use contract::invoke_contract; mod log; use crate::log::init_logging; From e06bba77dce9a6adbc89d1b573728cfc7ea8b3f4 Mon Sep 17 00:00:00 2001 From: Jonathan Jove Date: Mon, 13 Jun 2022 11:24:46 -0500 Subject: [PATCH 06/16] Update machinery for storage that maps LedgerKey to LedgerEntry --- src/rust/src/contract.rs | 68 +++++++++++++++++++--------------------- 1 file changed, 33 insertions(+), 35 deletions(-) diff --git a/src/rust/src/contract.rs b/src/rust/src/contract.rs index 7d1eb32efb..d94fcc752e 100644 --- a/src/rust/src/contract.rs +++ b/src/rust/src/contract.rs @@ -9,11 +9,11 @@ use std::io::Cursor; use im_rc::OrdMap; use std::error::Error; use stellar_contract_env_host::{ - storage::{self, AccessType, Key, Storage}, + storage::{self, AccessType, Storage}, xdr, xdr::{ - ContractDataEntry, LedgerEntry, LedgerEntryData, LedgerKey, ReadXdr, ScVal, ScVec, - WriteXdr, + LedgerEntry, LedgerEntryData, LedgerKey, LedgerKeyAccount, + LedgerKeyContractData, LedgerKeyTrustLine, ReadXdr, ScVec, WriteXdr, }, Host, HostError, }; @@ -21,19 +21,17 @@ use stellar_contract_env_host::{ /// Helper for [`build_storage_footprint_from_xdr`] that inserts a copy of some /// [`AccessType`] `ty` into a [`storage::Footprint`] for every [`LedgerKey`] in /// `keys`. -fn populate_access_map_with_contract_data_keys( - access: &mut OrdMap, +fn populate_access_map( + access: &mut OrdMap, keys: Vec, ty: AccessType, ) -> Result<(), HostError> { for lk in keys { match lk { - LedgerKey::ContractData(xdr::LedgerKeyContractData { contract_id, key }) => { - let sk = Key { contract_id, key }; - access.insert(sk, ty.clone()); - } - _ => return Err(HostError::General("unexpected ledger key type").into()), - } + LedgerKey::Account(_) | LedgerKey::Trustline(_) | LedgerKey::ContractData(_) => (), + _ => return Err(HostError::General("unexpected ledger entry type")), + }; + access.insert(lk, ty.clone()); } Ok(()) } @@ -48,19 +46,28 @@ fn build_storage_footprint_from_xdr(footprint: &XDRBuf) -> Result Result { + match &le.data { + LedgerEntryData::Account(a) => Ok(LedgerKey::Account(LedgerKeyAccount { + account_id: a.account_id.clone(), + })), + LedgerEntryData::Trustline(tl) => Ok(LedgerKey::Trustline(LedgerKeyTrustLine { + account_id: tl.account_id.clone(), + asset: tl.asset.clone(), + })), + LedgerEntryData::ContractData(cd) => Ok(LedgerKey::ContractData(LedgerKeyContractData { + contract_id: cd.contract_id.clone(), + key: cd.key.clone(), + })), + _ => Err(HostError::General("unexpected ledger key")), + } +} + /// Deserializes a sequence of [`xdr::LedgerEntry`] structures from a vector of /// [`XDRBuf`] buffers, inserts them into an [`OrdMap`] with entries /// additionally keyed by the provided contract ID, checks that the entries @@ -68,24 +75,15 @@ fn build_storage_footprint_from_xdr(footprint: &XDRBuf) -> Result, -) -> Result>, HostError> { +) -> Result>, HostError> { let mut map = OrdMap::new(); for buf in ledger_entries { let le = LedgerEntry::read_xdr(&mut Cursor::new(buf.data.as_slice()))?; - match le.data { - LedgerEntryData::ContractData(ContractDataEntry { - key, - val, - contract_id, - }) => { - let sk = Key { contract_id, key }; - if !footprint.0.contains_key(&sk) { - return Err(HostError::General("ledger entry not found in footprint").into()); - } - map.insert(sk.clone(), Some(val)); - } - _ => return Err(HostError::General("unexpected ledger entry type").into()), + let key = ledger_entry_to_ledger_key(&le)?; + if !footprint.0.contains_key(&key) { + return Err(HostError::General("ledger entry not found in footprint").into()); } + map.insert(key, Some(le)); } for k in footprint.0.keys() { if !map.contains_key(k) { From 7fdc19e7d9f43f471433aa1ef0ba03e88f83dfe7 Mon Sep 17 00:00:00 2001 From: Jonathan Jove Date: Mon, 13 Jun 2022 11:37:42 -0500 Subject: [PATCH 07/16] Allow storage map to be built with deleted entries --- src/rust/src/contract.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rust/src/contract.rs b/src/rust/src/contract.rs index d94fcc752e..06cad135d3 100644 --- a/src/rust/src/contract.rs +++ b/src/rust/src/contract.rs @@ -87,7 +87,7 @@ fn build_storage_map_from_xdr_ledger_entries( } for k in footprint.0.keys() { if !map.contains_key(k) { - return Err(HostError::General("ledger entry not found for footprint entry").into()); + map.insert(k.clone(), None); } } Ok(map) From 413a0c83f7a84b6950137039d8d1bc747d38d380 Mon Sep 17 00:00:00 2001 From: Jonathan Jove Date: Tue, 14 Jun 2022 15:33:54 -0500 Subject: [PATCH 08/16] Add machinery to get modified ledger entries back --- src/rust/src/contract.rs | 28 +++++++++++++++++++++++++++- src/rust/src/lib.rs | 4 ++++ 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/src/rust/src/contract.rs b/src/rust/src/contract.rs index 06cad135d3..209e77f571 100644 --- a/src/rust/src/contract.rs +++ b/src/rust/src/contract.rs @@ -2,7 +2,10 @@ // under the Apache License, Version 2.0. See the COPYING file at the root // of this distribution or at http://www.apache.org/licenses/LICENSE-2.0 -use crate::{log::partition::TX, rust_bridge::XDRBuf}; +use crate::{ + log::partition::TX, + rust_bridge::{Bytes, XDRBuf}, +}; use log::info; use std::io::Cursor; @@ -92,3 +95,26 @@ fn build_storage_map_from_xdr_ledger_entries( } Ok(map) } + +/// Iterates over the storage map and serializes the read-write ledger entries +/// back to XDR. +fn build_xdr_ledger_entries_from_storage_map( + footprint: &storage::Footprint, + storage_map: &OrdMap>, +) -> Result, HostError> { + let mut res = Vec::new(); + for (lk, ole) in storage_map { + let mut xdr_buf: Vec = Vec::new(); + match footprint.0.get(lk) { + Some(AccessType::ReadOnly) => (), + Some(AccessType::ReadWrite) => { + if let Some(le) = ole { + le.write_xdr(&mut Cursor::new(&mut xdr_buf))?; + res.push(Bytes { vec: xdr_buf }); + } + } + None => return Err(HostError::General("ledger entry not in footprint")), + } + } + Ok(res) +} diff --git a/src/rust/src/lib.rs b/src/rust/src/lib.rs index f05cbd94e2..977d7cfbc6 100644 --- a/src/rust/src/lib.rs +++ b/src/rust/src/lib.rs @@ -17,6 +17,10 @@ mod rust_bridge { data: UniquePtr>, } + struct Bytes { + vec: Vec, + } + // LogLevel declares to cxx.rs a shared type that both Rust and C+++ will // understand. #[namespace = "stellar"] From 2bd45b0d96f8127204c663f3fad136e2d551e94a Mon Sep 17 00:00:00 2001 From: Jonathan Jove Date: Fri, 10 Jun 2022 16:35:32 -0500 Subject: [PATCH 09/16] Implement invoke_host_function --- src/rust/src/contract.rs | 42 +++++++++++++++++++++++++++++++++++++++- src/rust/src/lib.rs | 7 +++++++ 2 files changed, 48 insertions(+), 1 deletion(-) diff --git a/src/rust/src/contract.rs b/src/rust/src/contract.rs index 209e77f571..4bbae05dfb 100644 --- a/src/rust/src/contract.rs +++ b/src/rust/src/contract.rs @@ -15,7 +15,7 @@ use stellar_contract_env_host::{ storage::{self, AccessType, Storage}, xdr, xdr::{ - LedgerEntry, LedgerEntryData, LedgerKey, LedgerKeyAccount, + HostFunction, LedgerEntry, LedgerEntryData, LedgerKey, LedgerKeyAccount, LedgerKeyContractData, LedgerKeyTrustLine, ReadXdr, ScVec, WriteXdr, }, Host, HostError, @@ -118,3 +118,43 @@ fn build_xdr_ledger_entries_from_storage_map( } Ok(res) } + +/// Deserializes an [`xdr::HostFunction`] host function identifier, an [`xdr::ScVec`] XDR object of +/// arguments, an [`xdr::Footprint`] and a sequence of [`xdr::LedgerEntry`] entries containing all +/// the data the invocation intends to read. Then calls the host function with the specified +/// arguments, discards the [`xdr::ScVal`] return value, and returns the [`ReadWrite`] ledger +/// entries in serialized form. Ledger entries not returned have been deleted. +pub(crate) fn invoke_host_function( + hf_buf: &XDRBuf, + args_buf: &XDRBuf, + footprint_buf: &XDRBuf, + ledger_entries: &Vec, +) -> Result, Box> { + let hf = HostFunction::read_xdr(&mut Cursor::new(hf_buf.data.as_slice()))?; + let args = ScVec::read_xdr(&mut Cursor::new(args_buf.data.as_slice()))?; + + let footprint = build_storage_footprint_from_xdr(footprint_buf)?; + let map = build_storage_map_from_xdr_ledger_entries(&footprint, ledger_entries)?; + + let storage = Storage::with_enforcing_footprint_and_map(footprint, map); + let mut host = Host::with_storage(storage); + + match hf { + HostFunction::Call => { + info!(target: TX, "Invoking host function 'Call'"); + host.invoke_function(hf, args)?; + } + HostFunction::CreateContract => { + info!(target: TX, "Invoking host function 'CreateContract'"); + todo!(); + } + }; + + let storage = host + .try_recover_storage() + .map_err(|_h| HostError::General("could not get storage from host"))?; + Ok(build_xdr_ledger_entries_from_storage_map( + &storage.footprint, + &storage.map, + )?) +} diff --git a/src/rust/src/lib.rs b/src/rust/src/lib.rs index 977d7cfbc6..82eec01115 100644 --- a/src/rust/src/lib.rs +++ b/src/rust/src/lib.rs @@ -39,6 +39,12 @@ mod rust_bridge { extern "Rust" { fn to_base64(b: &CxxVector, mut s: Pin<&mut CxxString>); fn from_base64(s: &CxxString, mut b: Pin<&mut CxxVector>); + fn invoke_host_function( + hf_buf: &XDRBuf, + args: &XDRBuf, + footprint: &XDRBuf, + ledger_entries: &Vec, + ) -> Result>; fn init_logging(maxLevel: LogLevel) -> Result<()>; } @@ -61,6 +67,7 @@ mod b64; use b64::{from_base64, to_base64}; mod contract; +use contract::invoke_host_function; mod log; use crate::log::init_logging; From 890e9375f1793a085012ad21818b3880713a55c6 Mon Sep 17 00:00:00 2001 From: Jonathan Jove Date: Tue, 14 Jun 2022 14:12:40 -0500 Subject: [PATCH 10/16] Implement InvokeHostFunctionOpFrame --- .../InvokeHostFunctionOpFrame.cpp | 132 ++++++++++++++++++ src/transactions/InvokeHostFunctionOpFrame.h | 41 ++++++ src/transactions/OperationFrame.cpp | 3 + 3 files changed, 176 insertions(+) create mode 100644 src/transactions/InvokeHostFunctionOpFrame.cpp create mode 100644 src/transactions/InvokeHostFunctionOpFrame.h diff --git a/src/transactions/InvokeHostFunctionOpFrame.cpp b/src/transactions/InvokeHostFunctionOpFrame.cpp new file mode 100644 index 0000000000..6e16e8dc7f --- /dev/null +++ b/src/transactions/InvokeHostFunctionOpFrame.cpp @@ -0,0 +1,132 @@ +// Copyright 2022 Stellar Development Foundation and contributors. Licensed +// under the Apache License, Version 2.0. See the COPYING file at the root +// of this distribution or at http://www.apache.org/licenses/LICENSE-2.0 + +// clang-format off +// This needs to be included first +#include "rust/RustVecXdrMarshal.h" +// clang-format on +#include "transactions/InvokeHostFunctionOpFrame.h" +#include "ledger/LedgerTxn.h" +#include "ledger/LedgerTxnEntry.h" +#include "rust/RustBridge.h" + +namespace stellar +{ + +template +XDRBuf +toXDRBuf(T const& t) +{ + return XDRBuf{ + std::make_unique>(xdr::xdr_to_opaque(t))}; +} + +InvokeHostFunctionOpFrame::InvokeHostFunctionOpFrame(Operation const& op, + OperationResult& res, + TransactionFrame& parentTx) + : OperationFrame(op, res, parentTx) + , mInvokeHostFunction(mOperation.body.invokeHostFunctionOp()) +{ +} + +bool +InvokeHostFunctionOpFrame::isOpSupported(LedgerHeader const& header) const +{ + return header.ledgerVersion >= 20; +} + +bool +InvokeHostFunctionOpFrame::doApply(AbstractLedgerTxn& ltx) +{ + // Get the entries for the footprint + rust::Vec ledgerEntryXdrBufs; + ledgerEntryXdrBufs.reserve(mInvokeHostFunction.footprint.readOnly.size() + + mInvokeHostFunction.footprint.readWrite.size()); + for (auto const& lk : mInvokeHostFunction.footprint.readOnly) + { + // Load without record for readOnly to avoid writing them later + auto ltxe = ltx.loadWithoutRecord(lk); + if (ltxe) + { + ledgerEntryXdrBufs.emplace_back(toXDRBuf(ltxe.current())); + } + } + for (auto const& lk : mInvokeHostFunction.footprint.readWrite) + { + auto ltxe = ltx.load(lk); + if (ltxe) + { + ledgerEntryXdrBufs.emplace_back(toXDRBuf(ltxe.current())); + } + } + + rust::Vec retBufs; + try + { + retBufs = rust_bridge::invoke_host_function( + toXDRBuf(mInvokeHostFunction.function), + toXDRBuf(mInvokeHostFunction.parameters), + toXDRBuf(mInvokeHostFunction.footprint), ledgerEntryXdrBufs); + } + catch (std::exception& e) + { + innerResult().code(INVOKE_HOST_FUNCTION_TRAPPED); + return false; + } + + // Create or update every entry returned + std::unordered_set keys; + for (auto const& buf : retBufs) + { + LedgerEntry le; + xdr::xdr_from_opaque(buf.vec, le); + auto lk = LedgerEntryKey(le); + + auto ltxe = ltx.load(lk); + if (ltxe) + { + ltxe.current() = le; + } + else + { + ltx.create(le); + } + + keys.emplace(std::move(lk)); + } + + // Erase every entry not returned + for (auto const& lk : mInvokeHostFunction.footprint.readWrite) + { + if (keys.find(lk) == keys.end()) + { + auto ltxe = ltx.load(lk); + if (ltxe) + { + ltx.erase(lk); + } + } + } + + innerResult().code(INVOKE_HOST_FUNCTION_SUCCESS); + return true; +} + +bool +InvokeHostFunctionOpFrame::doCheckValid(uint32_t ledgerVersion) +{ + if (mParentTx.getNumOperations() > 1) + { + innerResult().code(INVOKE_HOST_FUNCTION_MALFORMED); + return false; + } + return true; +} + +void +InvokeHostFunctionOpFrame::insertLedgerKeysToPrefetch( + UnorderedSet& keys) const +{ +} +} diff --git a/src/transactions/InvokeHostFunctionOpFrame.h b/src/transactions/InvokeHostFunctionOpFrame.h new file mode 100644 index 0000000000..3345b77160 --- /dev/null +++ b/src/transactions/InvokeHostFunctionOpFrame.h @@ -0,0 +1,41 @@ +#pragma once + +// Copyright 2022 Stellar Development Foundation and contributors. Licensed +// under the Apache License, Version 2.0. See the COPYING file at the root +// of this distribution or at http://www.apache.org/licenses/LICENSE-2.0 + +#include "transactions/OperationFrame.h" + +namespace stellar +{ +class AbstractLedgerTxn; + +class InvokeHostFunctionOpFrame : public OperationFrame +{ + InvokeHostFunctionResult& + innerResult() + { + return mResult.tr().invokeHostFunctionResult(); + } + + InvokeHostFunctionOp const& mInvokeHostFunction; + + public: + InvokeHostFunctionOpFrame(Operation const& op, OperationResult& res, + TransactionFrame& parentTx); + + bool isOpSupported(LedgerHeader const& header) const override; + + bool doApply(AbstractLedgerTxn& ltx) override; + bool doCheckValid(uint32_t ledgerVersion) override; + + void + insertLedgerKeysToPrefetch(UnorderedSet& keys) const override; + + static InvokeHostFunctionResultCode + getInnerCode(OperationResult const& res) + { + return res.tr().invokeHostFunctionResult().code(); + } +}; +} diff --git a/src/transactions/OperationFrame.cpp b/src/transactions/OperationFrame.cpp index cc77947079..20a0679726 100644 --- a/src/transactions/OperationFrame.cpp +++ b/src/transactions/OperationFrame.cpp @@ -15,6 +15,7 @@ #include "transactions/CreatePassiveSellOfferOpFrame.h" #include "transactions/EndSponsoringFutureReservesOpFrame.h" #include "transactions/InflationOpFrame.h" +#include "transactions/InvokeHostFunctionOpFrame.h" #include "transactions/LiquidityPoolDepositOpFrame.h" #include "transactions/LiquidityPoolWithdrawOpFrame.h" #include "transactions/ManageBuyOfferOpFrame.h" @@ -113,6 +114,8 @@ OperationFrame::makeHelper(Operation const& op, OperationResult& res, return std::make_shared(op, res, tx); case LIQUIDITY_POOL_WITHDRAW: return std::make_shared(op, res, tx); + case INVOKE_HOST_FUNCTION: + return std::make_shared(op, res, tx); default: ostringstream err; err << "Unknown Tx type: " << op.body.type(); From d89bbbc887ecd1dc3aaea02f332b1522bf9a7412 Mon Sep 17 00:00:00 2001 From: Jonathan Jove Date: Tue, 14 Jun 2022 14:12:54 -0500 Subject: [PATCH 11/16] Add basic tests for InvokeHostFunctionOpFrame --- .../test/InvokeHostFunctionTests.cpp | 288 ++++++++++++++++++ 1 file changed, 288 insertions(+) create mode 100644 src/transactions/test/InvokeHostFunctionTests.cpp diff --git a/src/transactions/test/InvokeHostFunctionTests.cpp b/src/transactions/test/InvokeHostFunctionTests.cpp new file mode 100644 index 0000000000..dbe72089ed --- /dev/null +++ b/src/transactions/test/InvokeHostFunctionTests.cpp @@ -0,0 +1,288 @@ +// Copyright 2022 Stellar Development Foundation and contributors. Licensed +// under the Apache License, Version 2.0. See the COPYING file at the root +// of this distribution or at http://www.apache.org/licenses/LICENSE-2.0 + +#ifdef ENABLE_NEXT_PROTOCOL_VERSION_UNSAFE_FOR_PRODUCTION + +#include "crypto/SecretKey.h" +#include "ledger/LedgerTxn.h" +#include "lib/catch.hpp" +#include "main/Application.h" +#include "rust/RustBridge.h" +#include "test/TestAccount.h" +#include "test/TestUtils.h" +#include "test/TxTests.h" +#include "test/test.h" +#include "transactions/TransactionUtils.h" +#include "xdr/Stellar-contract.h" +#include "xdr/Stellar-ledger-entries.h" +#include +#include +#include +#include +#include + +using namespace stellar; +using namespace stellar::txtest; + +// This is an example WASM from the SDK that unpacks two SCV_I32 arguments, adds +// them with an overflow check, and re-packs them as an SCV_I32 if successful. +// +// To regenerate, check out the SDK, install a nightly toolchain with +// the rust-src component (to enable the 'tiny' build) using the following: +// +// $ rustup component add rust-src --toolchain nightly +// +// then do: +// +// $ make tiny +// $ xxd -i target/wasm32-unknown-unknown/release/example_add_i32.wasm +std::vector addI32Wasm{ + 0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, 0x01, 0x07, 0x01, 0x60, + 0x02, 0x7e, 0x7e, 0x01, 0x7e, 0x03, 0x02, 0x01, 0x00, 0x05, 0x03, 0x01, + 0x00, 0x10, 0x06, 0x11, 0x02, 0x7f, 0x00, 0x41, 0x80, 0x80, 0xc0, 0x00, + 0x0b, 0x7f, 0x00, 0x41, 0x80, 0x80, 0xc0, 0x00, 0x0b, 0x07, 0x2b, 0x04, + 0x06, 0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x02, 0x00, 0x03, 0x61, 0x64, + 0x64, 0x00, 0x00, 0x0a, 0x5f, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x5f, 0x65, + 0x6e, 0x64, 0x03, 0x00, 0x0b, 0x5f, 0x5f, 0x68, 0x65, 0x61, 0x70, 0x5f, + 0x62, 0x61, 0x73, 0x65, 0x03, 0x01, 0x0a, 0x47, 0x01, 0x45, 0x01, 0x02, + 0x7f, 0x02, 0x40, 0x20, 0x00, 0x42, 0x0f, 0x83, 0x42, 0x03, 0x52, 0x20, + 0x01, 0x42, 0x0f, 0x83, 0x42, 0x03, 0x52, 0x72, 0x45, 0x04, 0x40, 0x20, + 0x01, 0x42, 0x04, 0x88, 0xa7, 0x22, 0x02, 0x41, 0x00, 0x48, 0x20, 0x02, + 0x20, 0x00, 0x42, 0x04, 0x88, 0xa7, 0x22, 0x03, 0x6a, 0x22, 0x02, 0x20, + 0x03, 0x48, 0x73, 0x45, 0x0d, 0x01, 0x0b, 0x00, 0x0b, 0x20, 0x02, 0xad, + 0x42, 0x04, 0x86, 0x42, 0x03, 0x84, 0x0b}; + +// This is an example WASM from the SDK that can put and delete arbitrary +// contract data. +std::vector contractDataWasm{ + 0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, 0x01, 0x0c, 0x02, 0x60, + 0x02, 0x7e, 0x7e, 0x01, 0x7e, 0x60, 0x01, 0x7e, 0x01, 0x7e, 0x02, 0x0f, + 0x02, 0x01, 0x6c, 0x02, 0x24, 0x32, 0x00, 0x00, 0x01, 0x6c, 0x02, 0x24, + 0x35, 0x00, 0x01, 0x03, 0x03, 0x02, 0x00, 0x01, 0x05, 0x03, 0x01, 0x00, + 0x10, 0x06, 0x19, 0x03, 0x7f, 0x01, 0x41, 0x80, 0x80, 0xc0, 0x00, 0x0b, + 0x7f, 0x00, 0x41, 0x80, 0x80, 0xc0, 0x00, 0x0b, 0x7f, 0x00, 0x41, 0x80, + 0x80, 0xc0, 0x00, 0x0b, 0x07, 0x31, 0x05, 0x06, 0x6d, 0x65, 0x6d, 0x6f, + 0x72, 0x79, 0x02, 0x00, 0x03, 0x70, 0x75, 0x74, 0x00, 0x02, 0x03, 0x64, + 0x65, 0x6c, 0x00, 0x03, 0x0a, 0x5f, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x5f, + 0x65, 0x6e, 0x64, 0x03, 0x01, 0x0b, 0x5f, 0x5f, 0x68, 0x65, 0x61, 0x70, + 0x5f, 0x62, 0x61, 0x73, 0x65, 0x03, 0x02, 0x0a, 0x35, 0x02, 0x1a, 0x01, + 0x01, 0x7f, 0x23, 0x00, 0x41, 0x10, 0x6b, 0x22, 0x02, 0x24, 0x00, 0x20, + 0x00, 0x20, 0x01, 0x10, 0x00, 0x20, 0x02, 0x41, 0x10, 0x6a, 0x24, 0x00, + 0x0b, 0x18, 0x01, 0x01, 0x7f, 0x23, 0x00, 0x41, 0x10, 0x6b, 0x22, 0x01, + 0x24, 0x00, 0x20, 0x00, 0x10, 0x01, 0x20, 0x01, 0x41, 0x10, 0x6a, 0x24, + 0x00, 0x0b}; + +template +SCVal +makeBinary(T begin, T end) +{ + SCVal val(SCValType::SCV_OBJECT); + val.obj().activate().type(SCO_BINARY); + val.obj()->bin().assign(begin, end); + return val; +} + +static SCVal +makeI32(int32_t i32) +{ + SCVal val(SCV_I32); + val.i32() = i32; + return val; +} + +static SCVal +makeSymbol(std::string const& str) +{ + SCVal val(SCV_SYMBOL); + val.sym().assign(str.begin(), str.end()); + return val; +} + +template +static LedgerKey +deployContract(AbstractLedgerTxn& ltx, T begin, T end) +{ + SCVal wasmKey(SCValType::SCV_STATIC); + wasmKey.ic() = SCStatic::SCS_LEDGER_KEY_CONTRACT_CODE_WASM; + + LedgerEntry le; + le.data.type(CONTRACT_DATA); + le.data.contractData().contractID = HashUtils::pseudoRandomForTesting(); + le.data.contractData().key = wasmKey; + le.data.contractData().val = makeBinary(begin, end); + + ltx.create(le); + return LedgerEntryKey(le); +} + +template +static LedgerKey +deployContract(Application& app, T begin, T end) +{ + LedgerTxn ltx(app.getLedgerTxnRoot()); + auto contract = deployContract(ltx, begin, end); + ltx.commit(); + return contract; +} + +TEST_CASE("invoke host function", "[tx][contract]") +{ + VirtualClock clock; + auto app = createTestApplication(clock, getTestConfig()); + auto root = TestAccount::createRoot(*app); + + SECTION("add i32") + { + auto contract = + deployContract(*app, addI32Wasm.begin(), addI32Wasm.end()); + auto const& contractID = contract.contractData().contractID; + + { + Operation op; + op.body.type(INVOKE_HOST_FUNCTION); + auto& ihf = op.body.invokeHostFunctionOp(); + ihf.function = HOST_FN_CALL; + ihf.parameters.emplace_back( + makeBinary(contractID.begin(), contractID.end())); + ihf.parameters.emplace_back(makeSymbol("add")); + ihf.parameters.emplace_back(makeI32(7)); + ihf.parameters.emplace_back(makeI32(16)); + ihf.footprint.readOnly = {contract}; + + auto tx = + transactionFrameFromOps(app->getNetworkID(), root, {op}, {}); + LedgerTxn ltx(app->getLedgerTxnRoot()); + TransactionMeta txm(2); + REQUIRE(tx->checkValid(ltx, 0, 0, 0)); + REQUIRE(tx->apply(*app, ltx, txm)); + ltx.commit(); + } + } + + SECTION("contract data") + { + auto contract = deployContract(*app, contractDataWasm.begin(), + contractDataWasm.end()); + auto const& contractID = contract.contractData().contractID; + + auto checkContractData = [&](SCVal const& key, SCVal const* val) { + LedgerTxn ltx(app->getLedgerTxnRoot()); + auto ltxe = loadContractData(ltx, contractID, key); + if (val) + { + REQUIRE(ltxe); + REQUIRE(ltxe.current().data.contractData().val == *val); + } + else + { + REQUIRE(!ltxe); + } + }; + + auto putWithFootprint = [&](std::string const& key, + std::string const& val, + xdr::xvector const& readOnly, + xdr::xvector const& readWrite, + bool success) { + auto keySymbol = makeSymbol(key); + auto valSymbol = makeSymbol(val); + + Operation op; + op.body.type(INVOKE_HOST_FUNCTION); + auto& ihf = op.body.invokeHostFunctionOp(); + ihf.function = HOST_FN_CALL; + ihf.parameters.emplace_back( + makeBinary(contractID.begin(), contractID.end())); + ihf.parameters.emplace_back(makeSymbol("put")); + ihf.parameters.emplace_back(keySymbol); + ihf.parameters.emplace_back(valSymbol); + ihf.footprint.readOnly = readOnly; + ihf.footprint.readWrite = readWrite; + + auto tx = + transactionFrameFromOps(app->getNetworkID(), root, {op}, {}); + LedgerTxn ltx(app->getLedgerTxnRoot()); + TransactionMeta txm(2); + REQUIRE(tx->checkValid(ltx, 0, 0, 0)); + if (success) + { + REQUIRE(tx->apply(*app, ltx, txm)); + ltx.commit(); + checkContractData(keySymbol, &valSymbol); + } + else + { + REQUIRE(!tx->apply(*app, ltx, txm)); + ltx.commit(); + } + }; + + auto put = [&](std::string const& key, std::string const& val) { + putWithFootprint(key, val, {contract}, + {contractDataKey(contractID, makeSymbol(key))}, + true); + }; + + auto delWithFootprint = [&](std::string const& key, + xdr::xvector const& readOnly, + xdr::xvector const& readWrite, + bool success) { + auto keySymbol = makeSymbol(key); + + Operation op; + op.body.type(INVOKE_HOST_FUNCTION); + auto& ihf = op.body.invokeHostFunctionOp(); + ihf.function = HOST_FN_CALL; + ihf.parameters.emplace_back( + makeBinary(contractID.begin(), contractID.end())); + ihf.parameters.emplace_back(makeSymbol("del")); + ihf.parameters.emplace_back(keySymbol); + ihf.footprint.readOnly = readOnly; + ihf.footprint.readWrite = readWrite; + + auto tx = + transactionFrameFromOps(app->getNetworkID(), root, {op}, {}); + LedgerTxn ltx(app->getLedgerTxnRoot()); + TransactionMeta txm(2); + REQUIRE(tx->checkValid(ltx, 0, 0, 0)); + if (success) + { + REQUIRE(tx->apply(*app, ltx, txm)); + ltx.commit(); + checkContractData(keySymbol, nullptr); + } + else + { + REQUIRE(!tx->apply(*app, ltx, txm)); + ltx.commit(); + } + }; + + auto del = [&](std::string const& key) { + delWithFootprint(key, {contract}, + {contractDataKey(contractID, makeSymbol(key))}, + true); + }; + + put("key1", "val1a"); + put("key2", "val2a"); + + // Failure: contract data isn't in footprint + putWithFootprint("key1", "val1b", {contract}, {}, false); + delWithFootprint("key1", {contract}, {}, false); + + // Failure: contract data is read only + auto cdk = contractDataKey(contractID, makeSymbol("key2")); + putWithFootprint("key2", "val2b", {contract, cdk}, {}, false); + delWithFootprint("key2", {contract, cdk}, {}, false); + + put("key1", "val1c"); + put("key2", "val2c"); + + del("key1"); + del("key2"); + } +} + +#endif From 05d2df4dac00c1c1adb41a0f4e0a045d85e1d171 Mon Sep 17 00:00:00 2001 From: Jonathan Jove Date: Wed, 15 Jun 2022 10:18:47 -0500 Subject: [PATCH 12/16] Add tests for incorrect parameter number --- .../test/InvokeHostFunctionTests.cpp | 38 +++++++++++++++---- 1 file changed, 30 insertions(+), 8 deletions(-) diff --git a/src/transactions/test/InvokeHostFunctionTests.cpp b/src/transactions/test/InvokeHostFunctionTests.cpp index dbe72089ed..6a0fa8c3f3 100644 --- a/src/transactions/test/InvokeHostFunctionTests.cpp +++ b/src/transactions/test/InvokeHostFunctionTests.cpp @@ -138,16 +138,12 @@ TEST_CASE("invoke host function", "[tx][contract]") deployContract(*app, addI32Wasm.begin(), addI32Wasm.end()); auto const& contractID = contract.contractData().contractID; - { + auto call = [&](SCVec const& parameters, bool success) { Operation op; op.body.type(INVOKE_HOST_FUNCTION); auto& ihf = op.body.invokeHostFunctionOp(); ihf.function = HOST_FN_CALL; - ihf.parameters.emplace_back( - makeBinary(contractID.begin(), contractID.end())); - ihf.parameters.emplace_back(makeSymbol("add")); - ihf.parameters.emplace_back(makeI32(7)); - ihf.parameters.emplace_back(makeI32(16)); + ihf.parameters = parameters; ihf.footprint.readOnly = {contract}; auto tx = @@ -155,9 +151,35 @@ TEST_CASE("invoke host function", "[tx][contract]") LedgerTxn ltx(app->getLedgerTxnRoot()); TransactionMeta txm(2); REQUIRE(tx->checkValid(ltx, 0, 0, 0)); - REQUIRE(tx->apply(*app, ltx, txm)); + if (success) + { + REQUIRE(tx->apply(*app, ltx, txm)); + } + else + { + REQUIRE(!tx->apply(*app, ltx, txm)); + } ltx.commit(); - } + }; + + auto scContractID = makeBinary(contractID.begin(), contractID.end()); + auto scFunc = makeSymbol("add"); + auto sc7 = makeI32(7); + auto sc16 = makeI32(16); + + // Too few parameters for call + call({}, false); + call({scContractID}, false); + + // To few parameters for "add" + call({scContractID, scFunc}, false); + call({scContractID, scFunc, sc7}, false); + + // Correct function call + call({scContractID, scFunc, sc7, sc16}, true); + + // Too many parameters for "add" + call({scContractID, scFunc, sc7, sc16, makeI32(0)}, false); } SECTION("contract data") From 467f7bf8755cc69f79e7438fc4016527e5242da0 Mon Sep 17 00:00:00 2001 From: Jonathan Jove Date: Wed, 15 Jun 2022 10:54:42 -0500 Subject: [PATCH 13/16] Add conditional compilation to InvokeHostFunctionOpFrame and OperationFrame --- src/transactions/InvokeHostFunctionOpFrame.cpp | 3 +++ src/transactions/InvokeHostFunctionOpFrame.h | 2 ++ src/transactions/OperationFrame.cpp | 2 ++ 3 files changed, 7 insertions(+) diff --git a/src/transactions/InvokeHostFunctionOpFrame.cpp b/src/transactions/InvokeHostFunctionOpFrame.cpp index 6e16e8dc7f..356828a1d6 100644 --- a/src/transactions/InvokeHostFunctionOpFrame.cpp +++ b/src/transactions/InvokeHostFunctionOpFrame.cpp @@ -2,6 +2,8 @@ // under the Apache License, Version 2.0. See the COPYING file at the root // of this distribution or at http://www.apache.org/licenses/LICENSE-2.0 +#ifdef ENABLE_NEXT_PROTOCOL_VERSION_UNSAFE_FOR_PRODUCTION + // clang-format off // This needs to be included first #include "rust/RustVecXdrMarshal.h" @@ -130,3 +132,4 @@ InvokeHostFunctionOpFrame::insertLedgerKeysToPrefetch( { } } +#endif // ENABLE_NEXT_PROTOCOL_VERSION_UNSAFE_FOR_PRODUCTION diff --git a/src/transactions/InvokeHostFunctionOpFrame.h b/src/transactions/InvokeHostFunctionOpFrame.h index 3345b77160..b0ffa1df68 100644 --- a/src/transactions/InvokeHostFunctionOpFrame.h +++ b/src/transactions/InvokeHostFunctionOpFrame.h @@ -4,6 +4,7 @@ // under the Apache License, Version 2.0. See the COPYING file at the root // of this distribution or at http://www.apache.org/licenses/LICENSE-2.0 +#ifdef ENABLE_NEXT_PROTOCOL_VERSION_UNSAFE_FOR_PRODUCTION #include "transactions/OperationFrame.h" namespace stellar @@ -39,3 +40,4 @@ class InvokeHostFunctionOpFrame : public OperationFrame } }; } +#endif // ENABLE_NEXT_PROTOCOL_VERSION_UNSAFE_FOR_PRODUCTION diff --git a/src/transactions/OperationFrame.cpp b/src/transactions/OperationFrame.cpp index 20a0679726..e15291c2f3 100644 --- a/src/transactions/OperationFrame.cpp +++ b/src/transactions/OperationFrame.cpp @@ -114,8 +114,10 @@ OperationFrame::makeHelper(Operation const& op, OperationResult& res, return std::make_shared(op, res, tx); case LIQUIDITY_POOL_WITHDRAW: return std::make_shared(op, res, tx); +#ifdef ENABLE_NEXT_PROTOCOL_VERSION_UNSAFE_FOR_PRODUCTION case INVOKE_HOST_FUNCTION: return std::make_shared(op, res, tx); +#endif default: ostringstream err; err << "Unknown Tx type: " << op.body.type(); From 49766ff6971f6db935aee3f432bae68ab285cf44 Mon Sep 17 00:00:00 2001 From: Jonathan Jove Date: Wed, 15 Jun 2022 11:57:46 -0500 Subject: [PATCH 14/16] Change InvokeHostFunctionOpFrame to low threshold --- src/transactions/InvokeHostFunctionOpFrame.cpp | 6 ++++++ src/transactions/InvokeHostFunctionOpFrame.h | 2 ++ 2 files changed, 8 insertions(+) diff --git a/src/transactions/InvokeHostFunctionOpFrame.cpp b/src/transactions/InvokeHostFunctionOpFrame.cpp index 356828a1d6..3302d01211 100644 --- a/src/transactions/InvokeHostFunctionOpFrame.cpp +++ b/src/transactions/InvokeHostFunctionOpFrame.cpp @@ -32,6 +32,12 @@ InvokeHostFunctionOpFrame::InvokeHostFunctionOpFrame(Operation const& op, { } +ThresholdLevel +InvokeHostFunctionOpFrame::getThresholdLevel() const +{ + return ThresholdLevel::LOW; +} + bool InvokeHostFunctionOpFrame::isOpSupported(LedgerHeader const& header) const { diff --git a/src/transactions/InvokeHostFunctionOpFrame.h b/src/transactions/InvokeHostFunctionOpFrame.h index b0ffa1df68..5f4f51ff5f 100644 --- a/src/transactions/InvokeHostFunctionOpFrame.h +++ b/src/transactions/InvokeHostFunctionOpFrame.h @@ -25,6 +25,8 @@ class InvokeHostFunctionOpFrame : public OperationFrame InvokeHostFunctionOpFrame(Operation const& op, OperationResult& res, TransactionFrame& parentTx); + ThresholdLevel getThresholdLevel() const override; + bool isOpSupported(LedgerHeader const& header) const override; bool doApply(AbstractLedgerTxn& ltx) override; From a68b53eb524c2bc20c628324eed578a598c23245 Mon Sep 17 00:00:00 2001 From: Jonathan Jove Date: Fri, 17 Jun 2022 10:48:45 -0500 Subject: [PATCH 15/16] Use head of stellar/rs-stellar-contract-env for stellar-contract-env-host --- Cargo.lock | 6 +++--- src/rust/Cargo.toml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c7c1a01a79..31534ad3d9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -214,7 +214,7 @@ checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] name = "stellar-contract-env-common" version = "0.0.0" -source = "git+https://github.com/stellar/rs-stellar-contract-env?rev=03ddc72fea37bd5bafd2566a0fd20e088e6d8381#03ddc72fea37bd5bafd2566a0fd20e088e6d8381" +source = "git+https://github.com/stellar/rs-stellar-contract-env?rev=7c6958bd460e00bc6378b23a133b9ab4736b56c2#7c6958bd460e00bc6378b23a133b9ab4736b56c2" dependencies = [ "static_assertions", "stellar-xdr", @@ -224,7 +224,7 @@ dependencies = [ [[package]] name = "stellar-contract-env-host" version = "0.0.0" -source = "git+https://github.com/stellar/rs-stellar-contract-env?rev=03ddc72fea37bd5bafd2566a0fd20e088e6d8381#03ddc72fea37bd5bafd2566a0fd20e088e6d8381" +source = "git+https://github.com/stellar/rs-stellar-contract-env?rev=7c6958bd460e00bc6378b23a133b9ab4736b56c2#7c6958bd460e00bc6378b23a133b9ab4736b56c2" dependencies = [ "im-rc", "num-bigint", @@ -250,7 +250,7 @@ dependencies = [ [[package]] name = "stellar-xdr" version = "0.0.0" -source = "git+https://github.com/jonjove/rs-stellar-xdr?rev=72ea8fc0c01d27e9cb5a697ee73a9d3db9d8a09b#72ea8fc0c01d27e9cb5a697ee73a9d3db9d8a09b" +source = "git+https://github.com/stellar/rs-stellar-xdr?rev=b2d367f04706af7f21a7a6a7abb4920e18dacadb#b2d367f04706af7f21a7a6a7abb4920e18dacadb" dependencies = [ "base64", ] diff --git a/src/rust/Cargo.toml b/src/rust/Cargo.toml index 7fdb296299..91dd8d3217 100644 --- a/src/rust/Cargo.toml +++ b/src/rust/Cargo.toml @@ -12,6 +12,6 @@ log = "0.4.17" cxx = "1.0" im-rc = "15.0.0" base64 = "0.13.0" -stellar-contract-env-host = { git = "https://github.com/stellar/rs-stellar-contract-env", rev = "03ddc72fea37bd5bafd2566a0fd20e088e6d8381", features = [ +stellar-contract-env-host = { git = "https://github.com/stellar/rs-stellar-contract-env", rev = "7c6958bd460e00bc6378b23a133b9ab4736b56c2", features = [ "vm", ] } From b1d22f7c98a653f25baad2475540edcacbf4e325 Mon Sep 17 00:00:00 2001 From: Jonathan Jove Date: Fri, 17 Jun 2022 14:08:38 -0500 Subject: [PATCH 16/16] Fix contractDataWasm that was broken by https://github.com/stellar/rs-stellar-contract-env/pull/110 --- src/transactions/test/InvokeHostFunctionTests.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/transactions/test/InvokeHostFunctionTests.cpp b/src/transactions/test/InvokeHostFunctionTests.cpp index 6a0fa8c3f3..9f3fe09dad 100644 --- a/src/transactions/test/InvokeHostFunctionTests.cpp +++ b/src/transactions/test/InvokeHostFunctionTests.cpp @@ -58,8 +58,8 @@ std::vector addI32Wasm{ std::vector contractDataWasm{ 0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, 0x01, 0x0c, 0x02, 0x60, 0x02, 0x7e, 0x7e, 0x01, 0x7e, 0x60, 0x01, 0x7e, 0x01, 0x7e, 0x02, 0x0f, - 0x02, 0x01, 0x6c, 0x02, 0x24, 0x32, 0x00, 0x00, 0x01, 0x6c, 0x02, 0x24, - 0x35, 0x00, 0x01, 0x03, 0x03, 0x02, 0x00, 0x01, 0x05, 0x03, 0x01, 0x00, + 0x02, 0x01, 0x6c, 0x02, 0x24, 0x5f, 0x00, 0x00, 0x01, 0x6c, 0x02, 0x24, + 0x32, 0x00, 0x01, 0x03, 0x03, 0x02, 0x00, 0x01, 0x05, 0x03, 0x01, 0x00, 0x10, 0x06, 0x19, 0x03, 0x7f, 0x01, 0x41, 0x80, 0x80, 0xc0, 0x00, 0x0b, 0x7f, 0x00, 0x41, 0x80, 0x80, 0xc0, 0x00, 0x0b, 0x7f, 0x00, 0x41, 0x80, 0x80, 0xc0, 0x00, 0x0b, 0x07, 0x31, 0x05, 0x06, 0x6d, 0x65, 0x6d, 0x6f,