提交 9a47bc60 编写于 作者: D Daniel Larimer 提交者: GitHub

Merge pull request #67 from brianjohnson5972/master

Limiting WASM exectuion time
add_subdirectory(currency)
add_subdirectory(exchange)
add_subdirectory(infinite)
#add_subdirectory(social)
file(GLOB SOURCE_FILES "*.cpp")
add_wast_target(infinite "${SOURCE_FILES}" "${CMAKE_SOURCE_DIR}/contracts" ${CMAKE_CURRENT_SOURCE_DIR})
#include <infinite/infinite.hpp> /// defines transfer struct (abi)
namespace infinite {
using namespace eos;
/// When storing accounts, check for empty balance and remove account
void storeAccount( AccountName account, const Account& a ) {
if( a.isEmpty() ) {
/// value, scope
Accounts::remove( a, account );
} else {
/// value, scope
Accounts::store( a, account );
}
}
void apply_currency_transfer( const infinite::Transfer& transfer ) {
requireNotice( transfer.to, transfer.from );
requireAuth( transfer.from );
auto from = getAccount( transfer.from );
auto to = getAccount( transfer.to );
while (from.balance > infinite::CurrencyTokens())
{
from.balance -= transfer.quantity; /// token subtraction has underflow assertion
to.balance += transfer.quantity; /// token addition has overflow assertion
}
storeAccount( transfer.from, from );
storeAccount( transfer.to, to );
}
} // namespace infinite
using namespace infinite;
extern "C" {
void init() {
storeAccount( N(currency), Account( CurrencyTokens(1000ll*1000ll*1000ll) ) );
}
/// The apply method implements the dispatch of events to this contract
void apply( uint64_t code, uint64_t action ) {
if( code == N(currency) ) {
if( action == N(transfer) )
infinite::apply_currency_transfer( currentMessage< infinite::Transfer >() );
}
}
}
#include <eoslib/eos.hpp>
#include <eoslib/token.hpp>
#include <eoslib/db.hpp>
namespace infinite {
typedef eos::token<uint64_t,N(currency)> CurrencyTokens;
/**
* Transfer requires that the sender and receiver be the first two
* accounts notified and that the sender has provided authorization.
*/
struct Transfer {
AccountName from;
AccountName to;
CurrencyTokens quantity;
};
/**
* @brief row in Account table stored within each scope
*/
struct Account {
Account( CurrencyTokens b = CurrencyTokens() ):balance(b){}
/**
* The key is constant because there is only one record per scope/currency/accounts
*/
const uint64_t key = N(account);
CurrencyTokens balance;
bool isEmpty()const { return balance.quantity == 0; }
};
static_assert( sizeof(Account) == sizeof(uint64_t)+sizeof(CurrencyTokens), "unexpected packing" );
using Accounts = Table<N(currency),N(currency),N(account),Account,uint64_t>;
/**
* Accounts information for owner is stored:
*
* owner/infinite/account/account -> Account
*
* This API is made available for 3rd parties wanting read access to
* the users balance. If the account doesn't exist a default constructed
* account will be returned.
*/
inline Account getAccount( AccountName owner ) {
Account account;
/// scope, record
Accounts::get( account, owner );
return account;
}
} /// namespace infinite
......@@ -49,6 +49,7 @@ namespace eos { namespace chain {
FC_DECLARE_DERIVED_EXCEPTION( insufficient_fee, eos::chain::transaction_exception, 3030007, "insufficient fee" )
FC_DECLARE_DERIVED_EXCEPTION( tx_missing_scope, eos::chain::transaction_exception, 3030008, "missing required scope" )
FC_DECLARE_DERIVED_EXCEPTION( tx_missing_recipient, eos::chain::transaction_exception, 3030009, "missing required recipient" )
FC_DECLARE_DERIVED_EXCEPTION( checktime_exceeded, eos::chain::transaction_exception, 3030010, "allotted processing time was exceeded" )
FC_DECLARE_DERIVED_EXCEPTION( invalid_pts_address, eos::chain::utility_exception, 3060001, "invalid pts address" )
FC_DECLARE_DERIVED_EXCEPTION( insufficient_feeds, eos::chain::chain_exception, 37006, "insufficient feeds" )
......
#pragma once
#include <eos/chain/exceptions.hpp>
#include <eos/chain/message.hpp>
#include <eos/chain/message_handling_contexts.hpp>
#include <Runtime/Runtime.h>
......@@ -34,6 +35,8 @@ class wasm_interface {
void validate( message_validate_context& c );
void precondition( precondition_validate_context& c );
int64_t current_execution_time();
apply_context* current_apply_context = nullptr;
message_validate_context* current_validate_context = nullptr;
precondition_validate_context* current_precondition_context = nullptr;
......@@ -56,7 +59,7 @@ class wasm_interface {
map<AccountName, ModuleState> instances;
fc::time_point checktimeStart;
wasm_interface();
};
......
......@@ -10,6 +10,7 @@
#include "IR/Validate.h"
#include <eos/chain/key_value_object.hpp>
#include <eos/chain/account_object.hpp>
#include <chrono>
namespace eos { namespace chain {
using namespace IR;
......@@ -18,6 +19,20 @@ namespace eos { namespace chain {
wasm_interface::wasm_interface() {
}
#ifdef NDEBUG
const int CHECKTIME_LIMIT = 2000;
#else
const int CHECKTIME_LIMIT = 12000;
#endif
DEFINE_INTRINSIC_FUNCTION0(env,checktime,checktime,none) {
auto dur = wasm_interface::get().current_execution_time();
if (dur > CHECKTIME_LIMIT) {
wlog("checktime called ${d}", ("d", dur));
throw checktime_exceeded();
}
}
DEFINE_INTRINSIC_FUNCTION2(env,multeq_i128,multeq_i128,none,i32,self,i32,other) {
auto& wasm = wasm_interface::get();
auto mem = wasm.current_memory;
......@@ -337,6 +352,11 @@ DEFINE_INTRINSIC_FUNCTION1(env,free,free,none,i32,ptr) {
}
};
int64_t wasm_interface::current_execution_time()
{
return (fc::time_point::now() - checktimeStart).count();
}
char* wasm_interface::vm_allocate( int bytes ) {
FunctionInstance* alloc_function = asFunctionNullable(getInstanceExport(current_module,"alloc"));
......@@ -345,6 +365,8 @@ DEFINE_INTRINSIC_FUNCTION1(env,free,free,none,i32,ptr) {
std::vector<Value> invokeArgs(1);
invokeArgs[0] = U32(bytes);
checktimeStart = fc::time_point::now();
auto result = Runtime::invokeFunction(alloc_function,invokeArgs);
return &memoryRef<char>( current_memory, result.i32 );
......@@ -380,6 +402,8 @@ DEFINE_INTRINSIC_FUNCTION1(env,free,free,none,i32,ptr) {
memset( memstart + state.mem_end, 0, ((1<<16) - state.mem_end) );
memcpy( memstart, state.init_memory.data(), state.mem_end);
checktimeStart = fc::time_point::now();
Runtime::invokeFunction(call,args);
} catch( const Runtime::Exception& e ) {
edump((std::string(describeExceptionCause(e.cause))));
......@@ -400,6 +424,8 @@ DEFINE_INTRINSIC_FUNCTION1(env,free,free,none,i32,ptr) {
return; /// if not found then it is a no-op
}
checktimeStart = fc::time_point::now();
const FunctionType* functionType = getFunctionType(apply);
FC_ASSERT( functionType->parameters.size() == 0 );
......@@ -481,7 +507,7 @@ DEFINE_INTRINSIC_FUNCTION1(env,free,free,none,i32,ptr) {
wlog( "LOADING CODE" );
auto start = fc::time_point::now();
Serialization::MemoryInputStream stream((const U8*)recipient.code.data(),recipient.code.size());
WASM::serialize(stream,*state.module);
WASM::serializeWithInjection(stream,*state.module);
RootResolver rootResolver;
LinkResult linkResult = linkModule(*state.module,rootResolver);
......
......@@ -46,7 +46,7 @@ class ProducerVotesObject : public chainbase::object<chain::producer_votes_objec
void updateVotes(types::ShareType deltaVotes, types::UInt128 currentRaceTime);
/// @brief Get the number of votes this producer has received
types::ShareType getVotes() const { return race.speed; }
pair<types::ShareType,id_type> getVoteOrder()const { return std::tie( race.speed, id ); }
pair<types::ShareType,id_type> getVoteOrder()const { return { race.speed, id }; }
/**
* These fields are used for the producer scheduling algorithm which uses a virtual race to ensure that runner-up
......@@ -103,7 +103,7 @@ class ProducerVotesObject : public chainbase::object<chain::producer_votes_objec
types::UInt128 projectedRaceFinishTime() const { return race.projectedFinishTime; }
typedef std::pair<types::UInt128,id_type> rft_order_type;
rft_order_type projectedRaceFinishTimeOrder() const { return std::tie(race.projectedFinishTime,id); }
rft_order_type projectedRaceFinishTimeOrder() const { return {race.projectedFinishTime,id}; }
};
/**
......
......@@ -12,5 +12,6 @@ namespace Serialization { struct InputStream; struct OutputStream; }
namespace WASM
{
WEBASSEMBLY_API void serialize(Serialization::InputStream& stream,IR::Module& module);
WEBASSEMBLY_API void serializeWithInjection(Serialization::InputStream& stream,IR::Module& module);
WEBASSEMBLY_API void serialize(Serialization::OutputStream& stream,const IR::Module& module);
}
......@@ -7,6 +7,8 @@
#include "IR/Types.h"
#include "IR/Validate.h"
#include <iostream>
using namespace Serialization;
static void throwIfNotValidUTF8(const std::string& string)
......@@ -174,6 +176,106 @@ namespace IR
}
}
using namespace IR;
class ChecktimeInjection
{
public:
ChecktimeInjection()
: typeSlot(-1)
{}
void addCall(const Module& module, Serialization::OutputStream& inByteStream)
{
OpcodeAndImm<CallImm>* encodedOperator = (OpcodeAndImm<CallImm>*)inByteStream.advance(sizeof(OpcodeAndImm<CallImm>));
encodedOperator->opcode = Opcode::call;
// checktime will be the last defined import
encodedOperator->imm.functionIndex = checktimeIndex(module);
}
static U32 checktimeIndex(const Module& module)
{
return module.functions.imports.size() - 1;
}
void setTypeSlot(const Module& module, ResultType returnType, const std::vector<ValueType>& parameterTypes)
{
if (returnType == ResultType::none && !parameterTypes.size() )
typeSlot = module.types.size() - 1;
}
void addTypeSlot(Module& module)
{
if (typeSlot < 0)
{
// add a type for void func(void)
typeSlot = module.types.size();
module.types.push_back(FunctionType::get(ResultType::none));
}
}
void addImport(Module& module)
{
const U32 functionTypeIndex = typeSlot;
module.functions.imports.push_back({{functionTypeIndex},std::move(u8"env"),std::move(u8"checktime")});
}
void conditionallyAddCall(Opcode opcode, const ControlStructureImm& imm, const Module& module, Serialization::OutputStream& inByteStream)
{
switch(opcode)
{
case Opcode::loop:
case Opcode::block:
addCall(module, inByteStream);
default:
break;
};
}
template<typename Imm>
void conditionallyAddCall(Opcode , const Imm& , const Module& , Serialization::OutputStream& )
{
}
void adjustExportIndex(Module& module)
{
// all function exports need to have their index increased to account for inserted definition
for (auto& exportDef : module.exports)
{
if (exportDef.kind == ObjectKind::function)
++exportDef.index;
}
}
void adjustCallIndex(const Module& module, CallImm& imm)
{
if (imm.functionIndex >= checktimeIndex(module))
++imm.functionIndex;
}
template<typename Imm>
void adjustCallIndex(const Module& , Imm& )
{
}
private:
int typeSlot;
};
struct NoOpInjection
{
void addCall(const Module& , Serialization::OutputStream& ) {}
void setTypeSlot(const Module& , ResultType , const std::vector<ValueType>& ) {}
void addTypeSlot(Module& ) {}
void addImport(Module& ) {}
template<typename Imm>
void conditionallyAddCall(Opcode , const Imm& , const Module& , Serialization::OutputStream& ) {}
void adjustExportIndex(Module& ) {}
template<typename Imm>
void adjustCallIndex(const Module& , Imm& ) {}
};
namespace WASM
{
using namespace IR;
......@@ -382,10 +484,10 @@ namespace WASM
{
serializeConstant(stream,"expected user section (section ID 0)",(U8)SectionType::user);
ArrayOutputStream sectionStream;
serialize(sectionStream,userSection.name);
Serialization::serialize(sectionStream,userSection.name);
serializeBytes(sectionStream,userSection.data.data(),userSection.data.size());
std::vector<U8> sectionBytes = sectionStream.getBytes();
serialize(stream,sectionBytes);
Serialization::serialize(stream,sectionBytes);
}
void serialize(InputStream& stream,UserSection& userSection)
......@@ -395,10 +497,10 @@ namespace WASM
serializeVarUInt32(stream,numSectionBytes);
MemoryInputStream sectionStream(stream.advance(numSectionBytes),numSectionBytes);
serialize(sectionStream,userSection.name);
Serialization::serialize(sectionStream,userSection.name);
throwIfNotValidUTF8(userSection.name);
userSection.data.resize(sectionStream.capacity());
serializeBytes(sectionStream,userSection.data.data(),userSection.data.size());
Serialization::serializeBytes(sectionStream,userSection.data.data(),userSection.data.size());
assert(!sectionStream.capacity());
}
......@@ -441,6 +543,11 @@ namespace WASM
FunctionDef& functionDef;
};
template<typename Injection>
struct WasmSerializationImpl
{
Injection injection;
void serializeFunctionBody(OutputStream& sectionStream,Module& module,FunctionDef& functionDef)
{
ArrayOutputStream bodyStream;
......@@ -497,6 +604,9 @@ namespace WASM
// Deserialize the function code, validate it, and re-encode it in the IR format.
ArrayOutputStream irCodeByteStream;
injection.addCall(module, irCodeByteStream);
OperatorEncoderStream irEncoderStream(irCodeByteStream);
CodeValidationStream codeValidationStream(module,functionDef);
while(bodyStream.capacity())
......@@ -510,8 +620,10 @@ namespace WASM
{ \
Imm imm; \
serialize(bodyStream,imm,functionDef); \
injection.adjustCallIndex(module, imm); \
codeValidationStream.name(imm); \
irEncoderStream.name(imm); \
injection.conditionallyAddCall(opcode, imm, module, irCodeByteStream); \
break; \
}
ENUM_NONFLOAT_OPERATORS(VISIT_OPCODE)
......@@ -532,9 +644,10 @@ namespace WASM
template<typename Stream>
void serializeTypeSection(Stream& moduleStream,Module& module)
{
serializeSection(moduleStream,SectionType::type,[&module](Stream& sectionStream)
Injection& localInjection = injection;
serializeSection(moduleStream,SectionType::type,[&module,&localInjection](Stream& sectionStream)
{
serializeArray(sectionStream,module.types,[](Stream& stream,const FunctionType*& functionType)
serializeArray(sectionStream,module.types,[&module,&localInjection](Stream& stream,const FunctionType*& functionType)
{
serializeConstant(stream,"function type tag",U8(0x60));
if(Stream::isInput)
......@@ -544,6 +657,7 @@ namespace WASM
serialize(stream,parameterTypes);
serialize(stream,returnType);
functionType = FunctionType::get(returnType,parameterTypes);
localInjection.setTypeSlot(module, returnType, parameterTypes);
}
else
{
......@@ -552,11 +666,17 @@ namespace WASM
}
});
});
if(Stream::isInput)
{
injection.addTypeSlot(module);
}
}
template<typename Stream>
void serializeImportSection(Stream& moduleStream,Module& module)
{
serializeSection(moduleStream,SectionType::import,[&module](Stream& sectionStream)
Injection& localInjection = injection;
serializeSection(moduleStream,SectionType::import,[&module,&localInjection](Stream& sectionStream)
{
Uptr size = module.functions.imports.size()
+ module.tables.imports.size()
......@@ -612,6 +732,7 @@ namespace WASM
default: throw FatalSerializationException("invalid ObjectKind");
}
}
localInjection.addImport(module);
}
else
{
......@@ -716,6 +837,8 @@ namespace WASM
{
serialize(sectionStream,module.exports);
});
injection.adjustExportIndex(module);
}
template<typename Stream>
......@@ -739,7 +862,7 @@ namespace WASM
template<typename Stream>
void serializeCodeSection(Stream& moduleStream,Module& module)
{
serializeSection(moduleStream,SectionType::functionDefinitions,[&module](Stream& sectionStream)
serializeSection(moduleStream,SectionType::functionDefinitions,[&module,this](Stream& sectionStream)
{
Uptr numFunctionBodies = module.functions.defs.size();
serializeVarUInt32(sectionStream,numFunctionBodies);
......@@ -817,14 +940,23 @@ namespace WASM
};
};
}
};
void serialize(Serialization::InputStream& stream,Module& module)
{
serializeModule(stream,module);
WasmSerializationImpl<NoOpInjection> impl;
impl.serializeModule(stream,module);
IR::validateDefinitions(module);
}
void serializeWithInjection(Serialization::InputStream& stream,Module& module)
{
WasmSerializationImpl<ChecktimeInjection> impl;
impl.serializeModule(stream,module);
IR::validateDefinitions(module);
}
void serialize(Serialization::OutputStream& stream,const Module& module)
{
serializeModule(stream,const_cast<Module&>(module));
WasmSerializationImpl<NoOpInjection> impl;
impl.serializeModule(stream,const_cast<Module&>(module));
}
}
......@@ -29,6 +29,7 @@
#include <eos/chain/account_object.hpp>
#include <eos/chain/key_value_object.hpp>
#include <eos/chain/block_summary_object.hpp>
#include <eos/chain/wasm_interface.hpp>
#include <eos/utilities/tempdir.hpp>
......@@ -45,6 +46,7 @@
#include <currency/currency.wast.hpp>
#include <exchange/exchange.wast.hpp>
#include <infinite/infinite.wast.hpp>
using namespace eos;
using namespace chain;
......@@ -522,8 +524,8 @@ R"(
(export "uint64_unpack" (func $uint64_unpack))
(export "String_unpack" (func $String_unpack))
(export "Transfer_unpack" (func $Transfer_unpack))
(export "onInit" (func $onInit))
(export "onApply_Transfer_simplecoin" (func $onApply_Transfer_simplecoin))
(export "init" (func $init))
(export "apply_simplecoin_transfer" (func $apply_simplecoin_transfer))
(func $malloc (param $0 i32) (result i32)
(local $1 i32)
(i32.store offset=8208
......@@ -834,7 +836,7 @@ R"(
)
)
)
(func $onInit
(func $init
(call $assert
(i32.const 1)
(i32.const 8240)
......@@ -862,7 +864,7 @@ R"(
(i32.const 8)
)
)
(func $onApply_Transfer_simplecoin
(func $apply_simplecoin_transfer
(local $0 i32)
(local $1 i32)
(local $2 i64)
......@@ -1124,4 +1126,54 @@ R"(
}
} FC_LOG_AND_RETHROW() }
//Test account script float rejection
BOOST_FIXTURE_TEST_CASE(create_script_w_loop, testing_fixture)
{ try {
Make_Blockchain(chain);
chain.produce_blocks(10);
Make_Account(chain, currency);
chain.produce_blocks(1);
types::setcode handler;
handler.account = "currency";
auto wasm = assemble_wast( infinite_wast );
handler.code.resize(wasm.size());
memcpy( handler.code.data(), wasm.data(), wasm.size() );
{
eos::chain::SignedTransaction trx;
trx.scope = {"currency"};
trx.messages.resize(1);
trx.messages[0].code = config::EosContractName;
trx.setMessage(0, "setcode", handler);
trx.expiration = chain.head_block_time() + 100;
trx.set_reference_block(chain.head_block_id());
chain.push_transaction(trx);
chain.produce_blocks(1);
}
{
eos::chain::SignedTransaction trx;
trx.scope = sort_names({"currency","inita"});
trx.emplaceMessage("currency",
vector<types::AccountPermission>{ {"currency","active"} },
"transfer", types::transfer{"currency", "inita", 1});
trx.expiration = chain.head_block_time() + 100;
trx.set_reference_block(chain.head_block_id());
try
{
wlog("starting long transaction");
chain.push_transaction(trx);
BOOST_FAIL("transaction should have failed with checktime_exceeded");
}
catch (const eos::chain::checktime_exceeded& check)
{
wlog("checktime_exceeded caught");
}
}
} FC_LOG_AND_RETHROW() }
BOOST_AUTO_TEST_SUITE_END()
extern "C" {
typedef long long uint64_t;
typedef unsigned int uint32_t;
typedef uint64_t AccountName;
int load( const void* keyptr, int keylen, void* valueptr, int valuelen );
void store( const void* keyptr, int keylen, const void* valueptr, int valuelen );
int readMessage( void* dest, int destsize );
int remove( const void* key, int keyLength );
void printi( uint64_t );
void print( const char* str );
void assert( int test, const char* message );
void* memcpy( void* dest, const void* src, uint32_t size );
uint64_t name_to_int64( const char* name );
bool loopControl(int i);
/*
void* malloc( unsigned int size ) {
static char dynamic_memory[1024*8];
static int start = 0;
int old_start = start;
start += 8*((size+7)/8);
assert( start < sizeof(dynamic_memory), "out of memory" );
return &dynamic_memory[old_start];
}
*/
}
template<typename Key, typename Value>
int load( const Key& key, Value& v ) { return load( &key, sizeof(Key), &v, sizeof(Value) ); }
template<typename Key, typename Value>
void store( const Key& key, const Value& v ) { store( &key, sizeof(key), &v, sizeof(v) ); }
template<typename Key>
void remove( const Key& key ) { remove( &key, sizeof(key) ); }
template<typename Message>
void readMessage( Message& m ) { readMessage( &m, sizeof(Message) ); }
/// END BUILT IN LIBRARY.... everything below this is "user contract"
extern "C" {
struct Transfer {
uint64_t from;
uint64_t to;
uint64_t amount;
char memo[];
};
static_assert( sizeof(Transfer) == 3*sizeof(uint64_t), "unexpected padding" );
struct Balance {
uint64_t balance;
};
void init() {
static Balance initial = { uint64_t(10)*1000*1000ll*1000ll };
static AccountName simplecoin;
simplecoin = name_to_int64( "simplecoin" );
print( "on_init called with "); printi( initial.balance ); print( "\n");
store( simplecoin, initial );
}
void apply_simplecoin_transfer() {
static Transfer message;
static Balance from_balance;
static Balance to_balance;
to_balance.balance = 0;
readMessage( message );
load( message.from, from_balance );
load( message.to, to_balance );
assert( from_balance.balance >= message.amount, "insufficient funds" );
int i = 0;
bool cont = true;
while (cont) {
cont = loopControl(i++);
}
from_balance.balance -= message.amount;
to_balance.balance += message.amount;
if( from_balance.balance )
store( message.from, from_balance );
else
remove( message.from );
store( message.to, to_balance );
}
} // extern "C"
const char* wast_apply = R"====((module
(type $FUNCSIG$ji (func (param i32) (result i64)))
(type $FUNCSIG$vi (func (param i32)))
(type $FUNCSIG$vj (func (param i64)))
(type $FUNCSIG$vii (func (param i32 i32)))
(type $FUNCSIG$ii (func (param i32) (result i32)))
(type $FUNCSIG$viiii (func (param i32 i32 i32 i32)))
(type $FUNCSIG$iii (func (param i32 i32) (result i32)))
(type $FUNCSIG$iiiii (func (param i32 i32 i32 i32) (result i32)))
(import "env" "assert" (func $assert (param i32 i32)))
(import "env" "load" (func $load (param i32 i32 i32 i32) (result i32)))
(import "env" "loopControl" (func $loopControl (param i32) (result i32)))
(import "env" "name_to_int64" (func $name_to_int64 (param i32) (result i64)))
(import "env" "print" (func $print (param i32)))
(import "env" "printi" (func $printi (param i64)))
(import "env" "readMessage" (func $readMessage (param i32 i32) (result i32)))
(import "env" "remove" (func $remove (param i32 i32) (result i32)))
(import "env" "store" (func $store (param i32 i32 i32 i32)))
(table 0 anyfunc)
(memory $0 1)
(data (i32.const 16) "\00\e4\0bT\02\00\00\00")
(data (i32.const 32) "simplecoin\00")
(data (i32.const 48) "on_init called with \00")
(data (i32.const 80) "\n\00")
(data (i32.const 128) "insufficient funds\00")
(export "memory" (memory $0))
(export "init" (func $init))
(export "apply_simplecoin_transfer" (func $apply_simplecoin_transfer))
(func $init
(i64.store offset=24
(i32.const 0)
(call $name_to_int64
(i32.const 32)
)
)
(call $print
(i32.const 48)
)
(call $printi
(i64.load offset=16
(i32.const 0)
)
)
(call $print
(i32.const 80)
)
(call $store
(i32.const 24)
(i32.const 8)
(i32.const 16)
(i32.const 8)
)
)
(func $apply_simplecoin_transfer
(local $0 i32)
(local $1 i64)
(local $2 i64)
(local $3 i32)
(set_local $3
(i32.const 0)
)
(i64.store offset=120
(i32.const 0)
(i64.const 0)
)
(drop
(call $readMessage
(i32.const 88)
(i32.const 24)
)
)
(drop
(call $load
(i32.const 88)
(i32.const 8)
(i32.const 112)
(i32.const 8)
)
)
(drop
(call $load
(i32.const 96)
(i32.const 8)
(i32.const 120)
(i32.const 8)
)
)
(call $assert
(i64.ge_s
(i64.load offset=112
(i32.const 0)
)
(i64.load offset=104
(i32.const 0)
)
)
(i32.const 128)
)
(loop $label$0
(set_local $0
(call $loopControl
(get_local $3)
)
)
(set_local $3
(i32.add
(get_local $3)
(i32.const 1)
)
)
(br_if $label$0
(get_local $0)
)
)
(i64.store offset=112
(i32.const 0)
(tee_local $2
(i64.sub
(i64.load offset=112
(i32.const 0)
)
(tee_local $1
(i64.load offset=104
(i32.const 0)
)
)
)
)
)
(i64.store offset=120
(i32.const 0)
(i64.add
(get_local $1)
(i64.load offset=120
(i32.const 0)
)
)
)
(block $label$1
(block $label$2
(br_if $label$2
(i64.eqz
(get_local $2)
)
)
(call $store
(i32.const 88)
(i32.const 8)
(i32.const 112)
(i32.const 8)
)
(br $label$1)
)
(drop
(call $remove
(i32.const 88)
(i32.const 8)
)
)
)
(call $store
(i32.const 96)
(i32.const 8)
(i32.const 120)
(i32.const 8)
)
)
))====";
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册