Skip to content

Commit

Permalink
EVM: Fix create token (#2686)
Browse files Browse the repository at this point in the history
* Add UTF-8 encoding validity check, limit dst20 token name length

* fmt cpp

* Add longer string test

* Fix rust lint

* Disable clippy on ffi

* Specify exact clippy warn

* Init vec with reserved namespace size

* Fix throw error on max byte size check

* Add genesis block token name tests

* Remove unnecessary substr op

* Fix createtoken rpc token name max length

* Initial dev to port utf8 check on rust side

* Fixes to use vec

* Fix rust ffi for creating dst20 tokens

* Rust lint

* Fmt cpp

* Revert token validation pipeline

* Add unit test for rust UTF-8 encoding validity test

* Fix rust lint

* Switch to 31 limit

* Fix dst20 test

* Remove clippy ignore

* Fix merge error

* Expose rust str utf8 check with ffi

* Fix rust lint

* Remove import

* Remove import

* Add next upgrade fork height on tests

* Disable lint checks on dst20 migration tokens

* Fix rust lint

---------

Co-authored-by: Jouzo <[email protected]>
  • Loading branch information
sieniven and Jouzo authored Nov 24, 2023
1 parent cd29ac3 commit d4c4725
Show file tree
Hide file tree
Showing 46 changed files with 432 additions and 54 deletions.
3 changes: 2 additions & 1 deletion lib/ain-cpp-imports/src/bridge.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,8 @@ pub mod ffi {
fn getEthSyncStatus() -> [i64; 2];
fn getAttributeValues(mnview_ptr: usize) -> Attributes;
fn CppLogPrintf(message: String);
fn getDST20Tokens(mnview_ptr: usize) -> Vec<DST20Token>;
#[allow(clippy::ptr_arg)]
fn getDST20Tokens(mnview_ptr: usize, tokens: &mut Vec<DST20Token>) -> bool;
fn getClientVersion() -> String;
fn getNumCores() -> i32;
fn getCORSAllowedOrigin() -> String;
Expand Down
10 changes: 6 additions & 4 deletions lib/ain-cpp-imports/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,8 @@ mod ffi {
// Just the logs are skipped.
}

pub fn getDST20Tokens(_mnview_ptr: usize) -> Vec<DST20Token> {
#[allow(clippy::ptr_arg)]
pub fn getDST20Tokens(_mnview_ptr: usize, _tokens: &mut Vec<DST20Token>) -> bool {
unimplemented!("{}", UNIMPL_MSG)
}
pub fn getClientVersion() -> String {
Expand Down Expand Up @@ -238,9 +239,10 @@ pub fn log_print(message: &str) {
ffi::CppLogPrintf(message.to_owned());
}

/// Fetches all DST20 tokens in view.
pub fn get_dst20_tokens(mnview_ptr: usize) -> Vec<ffi::DST20Token> {
ffi::getDST20Tokens(mnview_ptr)
/// Fetches all DST20 tokens in view, returns the result of the migration
#[allow(clippy::ptr_arg)]
pub fn get_dst20_tokens(mnview_ptr: usize, tokens: &mut Vec<ffi::DST20Token>) -> bool {
ffi::getDST20Tokens(mnview_ptr, tokens)
}

/// Returns the number of CPU cores available to the node.
Expand Down
7 changes: 6 additions & 1 deletion lib/ain-evm/src/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -411,8 +411,13 @@ fn get_default_successful_receipt() -> ReceiptV3 {
}

pub fn get_dst20_migration_txs(mnview_ptr: usize) -> Result<Vec<ExecuteTx>> {
let mut tokens = vec![];
let mut txs = Vec::new();
for token in ain_cpp_imports::get_dst20_tokens(mnview_ptr) {
if !ain_cpp_imports::get_dst20_tokens(mnview_ptr, &mut tokens) {
return Err(format_err!("DST20 token migration failed, invalid token name.").into());
}

for token in tokens {
let address = ain_contracts::dst20_address_from_token_id(token.id)?;
trace!(
"[get_dst20_migration_txs] Deploying to address {:#?}",
Expand Down
55 changes: 55 additions & 0 deletions lib/ain-evm/tests/utf8_string_test.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
#[cfg(test)]
use std::str;

#[test]
fn check_for_valid_utf8_strings() {
let test1 = "abcdefghijklmnopqrstuvwxyz1234567890~_= ^+%]{}";
let test2 = "abcdeàèéìòù";
let test3 = "😁 Beaming Face With Smiling Eyes";
let test4 = "Slightly Smiling Face 🙂";
let test5 = "🤣🤣🤣 Rolling on the Floor Laughing";
let test6 = "🤩🤩🤩 Star-🤩Struck 🤩🤩";
let test7 = "Left till here away at to whom past. Feelings laughing at no wondered repeated provided finished. \
It acceptance thoroughly my advantages everything as. Are projecting inquietude affronting preference saw who. \
Marry of am do avoid ample as. Old disposal followed she ignorant desirous two has. Called played entire roused \
though for one too. He into walk roof made tall cold he. Feelings way likewise addition wandered contempt bed \
indulged.";

let test1_bytes = test1.as_bytes();
let test2_bytes = test2.as_bytes();
let test3_bytes = test3.as_bytes();
let test4_bytes = test4.as_bytes();
let test5_bytes = test5.as_bytes();
let test6_bytes = test6.as_bytes();
let test7_bytes = test7.as_bytes();

assert_eq!(str::from_utf8(test1_bytes), Ok(test1));
assert_eq!(str::from_utf8(test2_bytes), Ok(test2));
assert_eq!(str::from_utf8(test3_bytes), Ok(test3));
assert_eq!(str::from_utf8(test4_bytes), Ok(test4));
assert_eq!(str::from_utf8(test5_bytes), Ok(test5));
assert_eq!(str::from_utf8(test6_bytes), Ok(test6));
assert_eq!(str::from_utf8(test7_bytes), Ok(test7));
}

#[test]
fn check_for_invalid_utf8_strings() {
let smiling_face = "😁".as_bytes();
let laughing_face = "🤣".as_bytes();
let star_struck_face = "🤩".as_bytes();

let mut test1 = smiling_face[0..1].to_vec();
test1.extend_from_slice(" Beaming Face With Smiling Eyes".as_bytes());

let mut test2 = laughing_face[0..3].to_vec();
test2.extend_from_slice(&laughing_face[0..2]);
test2.extend_from_slice(&laughing_face[0..1]);
test2.extend_from_slice(" Rolling on the Floor Laughing".as_bytes());

let mut test3 = star_struck_face[0..1].to_vec();
test3.extend_from_slice("🤩🤩 Star-🤩Struck 🤩🤩".as_bytes());

assert!(str::from_utf8(test1.as_slice()).is_err());
assert!(str::from_utf8(test2.as_slice()).is_err());
assert!(str::from_utf8(test3.as_slice()).is_err());
}
12 changes: 5 additions & 7 deletions lib/ain-rs-exports/src/evm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -726,19 +726,17 @@ fn evm_try_get_tx_by_hash(tx_hash: &str) -> Result<ffi::EVMTransaction> {
fn evm_try_unsafe_create_dst20(
template: &mut BlockTemplateWrapper,
native_hash: &str,
name: &str,
symbol: &str,
token_id: u64,
token: ffi::DST20TokenInfo,
) -> Result<()> {
let native_hash = XHash::from(native_hash);
let address = ain_contracts::dst20_address_from_token_id(token_id)?;
let address = ain_contracts::dst20_address_from_token_id(token.id)?;
debug!("Deploying to address {:#?}", address);

let system_tx = ExecuteTx::SystemTx(SystemTx::DeployContract(DeployContractData {
name: String::from(name),
symbol: String::from(symbol),
name: token.name,
symbol: token.symbol,
address,
token_id,
token_id: token.id,
}));

unsafe {
Expand Down
59 changes: 35 additions & 24 deletions lib/ain-rs-exports/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
mod core;
mod evm;
mod prelude;
mod util;

use ain_evm::blocktemplate::BlockTemplate;

use crate::{core::*, evm::*};
use crate::{core::*, evm::*, util::*};

pub struct BlockTemplateWrapper(Option<BlockTemplate>);

Expand All @@ -22,6 +23,32 @@ impl BlockTemplateWrapper {

#[cxx::bridge]
pub mod ffi {
// ========= FFI ==========
pub struct CrossBoundaryResult {
pub ok: bool,
pub reason: String,
}

// ========= Util ==========
extern "Rust" {
fn rs_try_from_utf8(result: &mut CrossBoundaryResult, string: &'static [u8]) -> String;
}

// ========= Core ==========
extern "Rust" {
fn ain_rs_preinit(result: &mut CrossBoundaryResult);
fn ain_rs_init_logging(result: &mut CrossBoundaryResult);
fn ain_rs_init_core_services(result: &mut CrossBoundaryResult);
fn ain_rs_wipe_evm_folder(result: &mut CrossBoundaryResult);
fn ain_rs_stop_core_services(result: &mut CrossBoundaryResult);

// Networking
fn ain_rs_init_network_json_rpc_service(result: &mut CrossBoundaryResult, addr: &str);
fn ain_rs_init_network_grpc_service(result: &mut CrossBoundaryResult, addr: &str);
fn ain_rs_init_network_subscriptions_service(result: &mut CrossBoundaryResult, addr: &str);
fn ain_rs_stop_network_services(result: &mut CrossBoundaryResult);
}

// ========== Block ==========
#[derive(Default)]
pub struct EVMBlockHeader {
Expand Down Expand Up @@ -74,26 +101,6 @@ pub mod ffi {
pub key_id: u32,
}

// ========= Core ==========
pub struct CrossBoundaryResult {
pub ok: bool,
pub reason: String,
}

extern "Rust" {
fn ain_rs_preinit(result: &mut CrossBoundaryResult);
fn ain_rs_init_logging(result: &mut CrossBoundaryResult);
fn ain_rs_init_core_services(result: &mut CrossBoundaryResult);
fn ain_rs_wipe_evm_folder(result: &mut CrossBoundaryResult);
fn ain_rs_stop_core_services(result: &mut CrossBoundaryResult);

// Networking
fn ain_rs_init_network_json_rpc_service(result: &mut CrossBoundaryResult, addr: &str);
fn ain_rs_init_network_grpc_service(result: &mut CrossBoundaryResult, addr: &str);
fn ain_rs_init_network_subscriptions_service(result: &mut CrossBoundaryResult, addr: &str);
fn ain_rs_stop_network_services(result: &mut CrossBoundaryResult);
}

// ========== EVM ==========

pub struct CreateTransactionContext<'a> {
Expand Down Expand Up @@ -129,6 +136,12 @@ pub mod ffi {
pub token_id: u32,
}

pub struct DST20TokenInfo {
pub id: u64,
pub name: String,
pub symbol: String,
}

#[derive(Default)]
pub struct CreateTxResult {
pub tx: Vec<u8>,
Expand Down Expand Up @@ -291,9 +304,7 @@ pub mod ffi {
result: &mut CrossBoundaryResult,
block_template: &mut BlockTemplateWrapper,
native_hash: &str,
name: &str,
symbol: &str,
token_id: u64,
token: DST20TokenInfo,
);

fn evm_try_unsafe_bridge_dst20(
Expand Down
13 changes: 13 additions & 0 deletions lib/ain-rs-exports/src/util.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
use std::str;

use ain_evm::Result;
use ain_macros::ffi_fallible;

use crate::{ffi, prelude::*};

/// Validates a slice of bytes is valid UTF-8 and converts the bytes to a rust string slice.
#[ffi_fallible]
pub fn rs_try_from_utf8(string: &'static [u8]) -> Result<String> {
let string = str::from_utf8(string).map_err(|_| "Error interpreting bytes, invalid UTF-8")?;
Ok(string.to_string())
}
1 change: 1 addition & 0 deletions src/Makefile.test.include
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@ DEFI_TESTS =\
test/txvalidation_tests.cpp \
test/txvalidationcache_tests.cpp \
test/uint256_tests.cpp \
test/utf8_string_tests.cpp \
test/util_tests.cpp \
test/validation_block_tests.cpp \
test/versionbits_tests.cpp \
Expand Down
2 changes: 1 addition & 1 deletion src/dfi/consensus/tokens.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ Res CTokensConsensus::operator()(const CCreateTokenMessage &obj) const {
}
}

auto tokenId = mnview.CreateToken(token, static_cast<int>(height) < consensus.DF2BayfrontHeight, &blockCtx);
auto tokenId = mnview.CreateToken(token, static_cast<int>(height), &blockCtx);
return tokenId;
}

Expand Down
3 changes: 2 additions & 1 deletion src/dfi/rpc_tokens.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ UniValue createtoken(const JSONRPCRequest &request) {
{"name",
RPCArg::Type::STR,
RPCArg::Optional::OMITTED,
"Token's name (optional), no longer than " + std::to_string(CToken::MAX_TOKEN_NAME_LENGTH)},
"Token's name (optional), no longer than " +
std::to_string(CToken::POST_METACHAIN_TOKEN_NAME_BYTE_SIZE)},
{"isDAT",
RPCArg::Type::BOOL,
RPCArg::Optional::OMITTED,
Expand Down
32 changes: 25 additions & 7 deletions src/dfi/tokens.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,7 @@ Res CTokensView::CreateDFIToken() {
return Res::Ok();
}

ResVal<DCT_ID> CTokensView::CreateToken(const CTokensView::CTokenImpl &token,
bool isPreBayfront,
BlockContext *blockCtx) {
ResVal<DCT_ID> CTokensView::CreateToken(const CTokensView::CTokenImpl &token, int height, BlockContext *blockCtx) {
if (GetTokenByCreationTx(token.creationTx)) {
return Res::Err("token with creation tx %s already exists!", token.creationTx.ToString());
}
Expand All @@ -93,7 +91,7 @@ ResVal<DCT_ID> CTokensView::CreateToken(const CTokensView::CTokenImpl &token,
},
id);
if (id == DCT_ID_START) {
if (isPreBayfront) {
if (height < Params().GetConsensus().DF2BayfrontHeight) {
return Res::Err("Critical fault: trying to create DCT_ID same as DCT_ID_START for Foundation owner\n");
}

Expand All @@ -107,12 +105,32 @@ ResVal<DCT_ID> CTokensView::CreateToken(const CTokensView::CTokenImpl &token,
const auto &evmTemplate = blockCtx->GetEVMTemplate();
if (shouldCreateDst20 && evmTemplate) {
CrossBoundaryResult result;
rust::string token_name{};
rust::string token_symbol{};
if (height >= Params().GetConsensus().DF23UpgradeHeight) {
if (token.name.size() > CToken::POST_METACHAIN_TOKEN_NAME_BYTE_SIZE) {
return Res::Err("Error creating DST20 token, token name is larger than max bytes\n");
}
token_name = rs_try_from_utf8(result, ffi_from_string_to_slice(token.name));
if (!result.ok) {
return Res::Err("Error creating DST20 token, token name not valid UTF-8\n");
}
token_symbol = rs_try_from_utf8(result, ffi_from_string_to_slice(token.symbol));
if (!result.ok) {
return Res::Err("Error creating DST20 token, token symbol not valid UTF-8\n");
}
} else {
token_name = rust::string(token.name);
token_symbol = rust::string(token.symbol);
}
evm_try_unsafe_create_dst20(result,
evmTemplate->GetTemplate(),
token.creationTx.GetHex(),
rust::string(token.name.c_str()),
rust::string(token.symbol.c_str()),
id.v);
DST20TokenInfo{
id.v,
token_name,
token_symbol,
});
if (!result.ok) {
return Res::Err("Error creating DST20 token: %s", result.reason);
}
Expand Down
3 changes: 2 additions & 1 deletion src/dfi/tokens.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ class CToken {
static const uint8_t MAX_TOKEN_NAME_LENGTH = 128;
static const uint8_t MAX_TOKEN_SYMBOL_LENGTH = 8;
static const uint8_t MAX_TOKEN_POOLPAIR_LENGTH = 16;
static const uint8_t POST_METACHAIN_TOKEN_NAME_BYTE_SIZE = 30;
enum class TokenFlags : uint8_t {
None = 0,
Mintable = 0x01,
Expand Down Expand Up @@ -197,7 +198,7 @@ class CTokensView : public virtual CStorageView {
DCT_ID const &start = DCT_ID{0});

Res CreateDFIToken();
ResVal<DCT_ID> CreateToken(const CTokenImpl &token, bool isPreBayfront = false, BlockContext *blockCtx = nullptr);
ResVal<DCT_ID> CreateToken(const CTokenImpl &token, int height, BlockContext *blockCtx = nullptr);
Res UpdateToken(const CTokenImpl &newToken, bool isPreBayfront = false, const bool tokenSplitUpdate = false);

Res BayfrontFlagsCleanup();
Expand Down
26 changes: 22 additions & 4 deletions src/ffi/ffiexports.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#include <dfi/govvariables/attributes.h>
#include <dfi/mn_rpc.h>
#include <ffi/ffiexports.h>
#include <ffi/ffihelpers.h>
#include <httprpc.h>
#include <key_io.h>
#include <logging.h>
Expand Down Expand Up @@ -313,22 +314,39 @@ uint32_t getEthMaxResponseByteSize() {
return max_response_size_mb * 1024 * 1024;
}

rust::vec<DST20Token> getDST20Tokens(std::size_t mnview_ptr) {
bool getDST20Tokens(std::size_t mnview_ptr, rust::vec<DST20Token> &tokens) {
LOCK(cs_main);

rust::vec<DST20Token> tokens;
bool res = true;
CCustomCSView *cache = reinterpret_cast<CCustomCSView *>(static_cast<uintptr_t>(mnview_ptr));
cache->ForEachToken(
[&](DCT_ID const &id, CTokensView::CTokenImpl token) {
if (!token.IsDAT() || token.IsPoolShare()) {
return true;
}
if (token.name.size() > CToken::POST_METACHAIN_TOKEN_NAME_BYTE_SIZE) {
res = false;
return false;
}

tokens.push_back({id.v, token.name, token.symbol});
CrossBoundaryResult result;
auto token_name = rs_try_from_utf8(result, ffi_from_string_to_slice(token.name));
if (!result.ok) {
LogPrintf("Error migrating DST20 token, token name not valid UTF-8\n");
res = false;
return false;
}
auto token_symbol = rs_try_from_utf8(result, ffi_from_string_to_slice(token.symbol));
if (!result.ok) {
LogPrintf("Error migrating DST20 token, token symbol not valid UTF-8\n");
res = false;
return false;
}
tokens.push_back({id.v, token_name, token_symbol});
return true;
},
DCT_ID{1}); // start from non-DFI
return tokens;
return res;
}

int32_t getNumCores() {
Expand Down
Loading

0 comments on commit d4c4725

Please sign in to comment.