contract/vm_callback.go
package contract
/*
#cgo CFLAGS: -I${SRCDIR}/../libtool/include/luajit-2.1
#cgo LDFLAGS: ${SRCDIR}/../libtool/lib/libluajit-5.1.a -lm
#include <stdlib.h>
#include <string.h>
#include "vm.h"
#include "bignum_module.h"
struct proof {
void *data;
size_t len;
};
#define RLP_TSTRING 0
#define RLP_TLIST 1
struct rlp_obj {
int rlp_obj_type;
void *data;
size_t size;
};
*/
import "C"
import (
"bytes"
"crypto/sha256"
"errors"
"fmt"
"math/big"
"strconv"
"strings"
"unsafe"
"github.com/aergoio/aergo-lib/log"
"github.com/aergoio/aergo/v2/cmd/aergoluac/util"
"github.com/aergoio/aergo/v2/contract/name"
"github.com/aergoio/aergo/v2/contract/system"
"github.com/aergoio/aergo/v2/internal/common"
"github.com/aergoio/aergo/v2/internal/enc/base58"
"github.com/aergoio/aergo/v2/internal/enc/hex"
"github.com/aergoio/aergo/v2/state"
"github.com/aergoio/aergo/v2/state/statedb"
"github.com/aergoio/aergo/v2/types"
"github.com/aergoio/aergo/v2/types/dbkey"
"github.com/btcsuite/btcd/btcec"
)
var (
mulAergo, mulGaer, zeroBig *big.Int
vmLogger = log.NewLogger("contract.vm")
)
const (
maxEventCnt = 50
maxEventNameSize = 64
maxEventArgSize = 4096
luaCallCountDeduc = 1000
)
func init() {
mulAergo = types.NewAmount(1, types.Aergo)
mulGaer = types.NewAmount(1, types.Gaer)
zeroBig = types.NewZeroAmount()
}
//export luaSetDB
func luaSetDB(L *LState, service C.int, key unsafe.Pointer, keyLen C.int, value *C.char) *C.char {
ctx := contexts[service]
if ctx == nil {
return C.CString("[System.LuaSetDB] contract state not found")
}
if ctx.isQuery == true || ctx.nestedView > 0 {
return C.CString("[System.LuaSetDB] set not permitted in query")
}
val := []byte(C.GoString(value))
if err := ctx.curContract.callState.ctrState.SetData(C.GoBytes(key, keyLen), val); err != nil {
return C.CString(err.Error())
}
if err := ctx.addUpdateSize(int64(types.HashIDLength + len(val))); err != nil {
C.luaL_setuncatchablerror(L)
return C.CString(err.Error())
}
if ctx.traceFile != nil {
_, _ = ctx.traceFile.WriteString("[Set]\n")
_, _ = ctx.traceFile.WriteString(fmt.Sprintf("Key=%s Len=%v byte=%v\n",
string(C.GoBytes(key, keyLen)), keyLen, C.GoBytes(key, keyLen)))
_, _ = ctx.traceFile.WriteString(fmt.Sprintf("Data=%s Len=%d byte=%v\n",
string(val), len(val), val))
}
return nil
}
//export luaGetDB
func luaGetDB(L *LState, service C.int, key unsafe.Pointer, keyLen C.int, blkno *C.char) (*C.char, *C.char) {
ctx := contexts[service]
if ctx == nil {
return nil, C.CString("[System.LuaGetDB] contract state not found")
}
if blkno != nil {
bigNo, _ := new(big.Int).SetString(strings.TrimSpace(C.GoString(blkno)), 10)
if bigNo == nil || bigNo.Sign() < 0 {
return nil, C.CString("[System.LuaGetDB] invalid blockheight value :" + C.GoString(blkno))
}
blkNo := bigNo.Uint64()
chainBlockHeight := ctx.blockInfo.No
if chainBlockHeight == 0 {
bestBlock, err := ctx.cdb.GetBestBlock()
if err != nil {
return nil, C.CString("[System.LuaGetDB] get best block error")
}
chainBlockHeight = bestBlock.GetHeader().GetBlockNo()
}
if blkNo < chainBlockHeight {
blk, err := ctx.cdb.GetBlockByNo(blkNo)
if err != nil {
return nil, C.CString(err.Error())
}
accountId := types.ToAccountID(ctx.curContract.contractId)
contractProof, err := ctx.bs.GetAccountAndProof(accountId[:], blk.GetHeader().GetBlocksRootHash(), false)
if err != nil {
return nil, C.CString("[System.LuaGetDB] failed to get snapshot state for account")
} else if contractProof.Inclusion {
trieKey := common.Hasher(C.GoBytes(key, keyLen))
varProof, err := ctx.bs.GetVarAndProof(trieKey, contractProof.GetState().GetStorageRoot(), false)
if err != nil {
return nil, C.CString("[System.LuaGetDB] failed to get snapshot state variable in contract")
}
if varProof.Inclusion {
if len(varProof.GetValue()) == 0 {
return nil, nil
}
return C.CString(string(varProof.GetValue())), nil
}
}
return nil, nil
}
}
data, err := ctx.curContract.callState.ctrState.GetData(C.GoBytes(key, keyLen))
if err != nil {
return nil, C.CString(err.Error())
}
if data == nil {
return nil, nil
}
return C.CString(string(data)), nil
}
//export luaDelDB
func luaDelDB(L *LState, service C.int, key unsafe.Pointer, keyLen C.int) *C.char {
ctx := contexts[service]
if ctx == nil {
return C.CString("[System.LuaDelDB] contract state not found")
}
if ctx.isQuery == true || ctx.nestedView > 0 {
return C.CString("[System.LuaDelDB] delete not permitted in query")
}
if err := ctx.curContract.callState.ctrState.DeleteData(C.GoBytes(key, keyLen)); err != nil {
return C.CString(err.Error())
}
if err := ctx.addUpdateSize(int64(32)); err != nil {
C.luaL_setuncatchablerror(L)
return C.CString(err.Error())
}
if ctx.traceFile != nil {
_, _ = ctx.traceFile.WriteString("[Del]\n")
_, _ = ctx.traceFile.WriteString(fmt.Sprintf("Key=%s Len=%v byte=%v\n",
string(C.GoBytes(key, keyLen)), keyLen, C.GoBytes(key, keyLen)))
}
return nil
}
func setInstCount(ctx *vmContext, parent *LState, child *LState) {
if !ctx.IsGasSystem() {
C.vm_setinstcount(parent, C.vm_instcount(child))
}
}
func setInstMinusCount(ctx *vmContext, L *LState, deduc C.int) {
if !ctx.IsGasSystem() {
C.vm_setinstcount(L, minusCallCount(ctx, C.vm_instcount(L), deduc))
}
}
func minusCallCount(ctx *vmContext, curCount, deduc C.int) C.int {
if ctx.IsGasSystem() {
return 0
}
remain := curCount - deduc
if remain <= 0 {
remain = 1
}
return remain
}
//export luaCallContract
func luaCallContract(L *LState, service C.int, contractId *C.char, fname *C.char, args *C.char,
amount *C.char, gas uint64) (C.int, *C.char) {
fnameStr := C.GoString(fname)
argsStr := C.GoString(args)
ctx := contexts[service]
if ctx == nil {
return -1, C.CString("[Contract.LuaCallContract] contract state not found")
}
// get the contract address
contractAddress := C.GoString(contractId)
cid, err := getAddressNameResolved(contractAddress, ctx.bs)
if err != nil {
return -1, C.CString("[Contract.LuaCallContract] invalid contractId: " + err.Error())
}
aid := types.ToAccountID(cid)
// read the amount for the contract call
amountBig, err := transformAmount(C.GoString(amount))
if err != nil {
return -1, C.CString("[Contract.LuaCallContract] invalid amount: " + err.Error())
}
// get the contract state
cs, err := getContractState(ctx, cid)
if err != nil {
return -1, C.CString("[Contract.LuaCallContract] getAccount error: " + err.Error())
}
// check if the contract exists
callee := getContract(cs.ctrState, ctx.bs)
if callee == nil {
return -1, C.CString("[Contract.LuaCallContract] cannot find contract " + C.GoString(contractId))
}
prevContractInfo := ctx.curContract
// read the arguments for the contract call
var ci types.CallInfo
ci.Name = fnameStr
err = getCallInfo(&ci.Args, []byte(argsStr), cid)
if err != nil {
return -1, C.CString("[Contract.LuaCallContract] invalid arguments: " + err.Error())
}
// get the remaining gas from the parent LState
ctx.refreshRemainingGas(L)
// create a new executor with the remaining gas on the child LState
ce := newExecutor(callee, cid, ctx, &ci, amountBig, false, false, cs.ctrState)
defer func() {
// close the executor, closes also the child LState
ce.close()
// set the remaining gas on the parent LState
ctx.setRemainingGas(L)
}()
if ce.err != nil {
return -1, C.CString("[Contract.LuaCallContract] newExecutor error: " + ce.err.Error())
}
// send the amount to the contract
senderState := prevContractInfo.callState.accState
receiverState := cs.accState
if amountBig.Cmp(zeroBig) > 0 {
if ctx.isQuery == true || ctx.nestedView > 0 {
return -1, C.CString("[Contract.LuaCallContract] send not permitted in query")
}
if r := sendBalance(senderState, receiverState, amountBig); r != nil {
return -1, r
}
}
seq, err := setRecoveryPoint(aid, ctx, senderState, cs, amountBig, false, false)
if ctx.traceFile != nil {
_, _ = ctx.traceFile.WriteString(fmt.Sprintf("[CALL Contract %v(%v) %v]\n",
contractAddress, aid.String(), fnameStr))
_, _ = ctx.traceFile.WriteString(fmt.Sprintf("snapshot set %d\n", seq))
_, _ = ctx.traceFile.WriteString(fmt.Sprintf("SendBalance: %s\n", amountBig.String()))
_, _ = ctx.traceFile.WriteString(fmt.Sprintf("After sender: %s receiver: %s\n",
senderState.Balance().String(), receiverState.Balance().String()))
}
if err != nil {
return -1, C.CString("[System.LuaCallContract] database error: " + err.Error())
}
// set the current contract info
ctx.curContract = newContractInfo(cs, prevContractInfo.contractId, cid,
receiverState.RP(), amountBig)
defer func() {
ctx.curContract = prevContractInfo
}()
// execute the contract call
defer setInstCount(ctx, L, ce.L)
ret := ce.call(minusCallCount(ctx, C.vm_instcount(L), luaCallCountDeduc), L)
// check if the contract call failed
if ce.err != nil {
err := clearRecovery(L, ctx, seq, true)
if err != nil {
return -1, C.CString("[Contract.LuaCallContract] recovery err: " + err.Error())
}
if ctx.traceFile != nil {
_, _ = ctx.traceFile.WriteString(fmt.Sprintf("recovery snapshot: %d\n", seq))
}
switch ceErr := ce.err.(type) {
case *VmTimeoutError:
return -1, C.CString(ceErr.Error())
default:
return -1, C.CString("[Contract.LuaCallContract] call err: " + ceErr.Error())
}
}
if seq == 1 {
err := clearRecovery(L, ctx, seq, false)
if err != nil {
return -1, C.CString("[Contract.LuaCallContract] recovery err: " + err.Error())
}
}
return ret, nil
}
//export luaDelegateCallContract
func luaDelegateCallContract(L *LState, service C.int, contractId *C.char,
fname *C.char, args *C.char, gas uint64) (C.int, *C.char) {
contractIdStr := C.GoString(contractId)
fnameStr := C.GoString(fname)
argsStr := C.GoString(args)
ctx := contexts[service]
if ctx == nil {
return -1, C.CString("[Contract.LuaDelegateCallContract] contract state not found")
}
// get the contract address
cid, err := getAddressNameResolved(contractIdStr, ctx.bs)
if err != nil {
return -1, C.CString("[Contract.LuaDelegateCallContract] invalid contractId: " + err.Error())
}
aid := types.ToAccountID(cid)
// get the contract state
contractState, err := getOnlyContractState(ctx, cid)
if err != nil {
return -1, C.CString("[Contract.LuaDelegateCallContract]getContractState error" + err.Error())
}
// check if the contract exists
contract := getContract(contractState, ctx.bs)
if contract == nil {
return -1, C.CString("[Contract.LuaDelegateCallContract] cannot find contract " + contractIdStr)
}
// read the arguments for the contract call
var ci types.CallInfo
ci.Name = fnameStr
err = getCallInfo(&ci.Args, []byte(argsStr), cid)
if err != nil {
return -1, C.CString("[Contract.LuaDelegateCallContract] invalid arguments: " + err.Error())
}
// get the remaining gas from the parent LState
ctx.refreshRemainingGas(L)
// create a new executor with the remaining gas on the child LState
ce := newExecutor(contract, cid, ctx, &ci, zeroBig, false, false, contractState)
defer func() {
// close the executor, closes also the child LState
ce.close()
// set the remaining gas on the parent LState
ctx.setRemainingGas(L)
}()
if ce.err != nil {
return -1, C.CString("[Contract.LuaDelegateCallContract] newExecutor error: " + ce.err.Error())
}
seq, err := setRecoveryPoint(aid, ctx, nil, ctx.curContract.callState, zeroBig, false, false)
if err != nil {
return -1, C.CString("[System.LuaDelegateCallContract] database error: " + err.Error())
}
if ctx.traceFile != nil {
_, _ = ctx.traceFile.WriteString(fmt.Sprintf("[DELEGATECALL Contract %v %v]\n", contractIdStr, fnameStr))
_, _ = ctx.traceFile.WriteString(fmt.Sprintf("snapshot set %d\n", seq))
}
// execute the contract call
defer setInstCount(ctx, L, ce.L)
ret := ce.call(minusCallCount(ctx, C.vm_instcount(L), luaCallCountDeduc), L)
// check if the contract call failed
if ce.err != nil {
err := clearRecovery(L, ctx, seq, true)
if err != nil {
return -1, C.CString("[Contract.LuaDelegateCallContract] recovery error: " + err.Error())
}
if ctx.traceFile != nil {
_, _ = ctx.traceFile.WriteString(fmt.Sprintf("recovery snapshot: %d\n", seq))
}
switch ceErr := ce.err.(type) {
case *VmTimeoutError:
return -1, C.CString(ceErr.Error())
default:
return -1, C.CString("[Contract.LuaDelegateCallContract] call error: " + ce.err.Error())
}
}
if seq == 1 {
err := clearRecovery(L, ctx, seq, false)
if err != nil {
return -1, C.CString("[Contract.LuaDelegateCallContract] recovery error: " + err.Error())
}
}
return ret, nil
}
func getAddressNameResolved(account string, bs *state.BlockState) ([]byte, error) {
accountLen := len(account)
if accountLen == types.EncodedAddressLength {
return types.DecodeAddress(account)
} else if accountLen == types.NameLength {
cid, err := name.Resolve(bs, []byte(account), false)
if err != nil {
return nil, err
}
if cid == nil {
return nil, errors.New("name not founded :" + account)
}
return cid, nil
}
return nil, errors.New("invalid account length:" + account)
}
//export luaSendAmount
func luaSendAmount(L *LState, service C.int, contractId *C.char, amount *C.char) *C.char {
ctx := contexts[service]
if ctx == nil {
return C.CString("[Contract.LuaSendAmount] contract state not found")
}
// read the amount to be sent
amountBig, err := transformAmount(C.GoString(amount))
if err != nil {
return C.CString("[Contract.LuaSendAmount] invalid amount: " + err.Error())
}
// cannot send amount in query
if (ctx.isQuery == true || ctx.nestedView > 0) && amountBig.Cmp(zeroBig) > 0 {
return C.CString("[Contract.LuaSendAmount] send not permitted in query")
}
// get the receiver account
cid, err := getAddressNameResolved(C.GoString(contractId), ctx.bs)
if err != nil {
return C.CString("[Contract.LuaSendAmount] invalid contractId: " + err.Error())
}
// get the receiver state
aid := types.ToAccountID(cid)
cs, err := getCallState(ctx, cid)
if err != nil {
return C.CString("[Contract.LuaSendAmount] getAccount error: " + err.Error())
}
// get the sender state
senderState := ctx.curContract.callState.accState
receiverState := cs.accState
// check if the receiver is a contract
if len(receiverState.CodeHash()) > 0 {
// get the contract state
if cs.ctrState == nil {
cs.ctrState, err = statedb.OpenContractState(cid, receiverState.State(), ctx.bs.StateDB)
if err != nil {
return C.CString("[Contract.LuaSendAmount] getContractState error: " + err.Error())
}
}
// set the function to be called
var ci types.CallInfo
ci.Name = "default"
// get the contract code
code := getContract(cs.ctrState, ctx.bs)
if code == nil {
return C.CString("[Contract.LuaSendAmount] cannot find contract:" + C.GoString(contractId))
}
// get the remaining gas from the parent LState
ctx.refreshRemainingGas(L)
// create a new executor with the remaining gas on the child LState
ce := newExecutor(code, cid, ctx, &ci, amountBig, false, false, cs.ctrState)
defer func() {
// close the executor, closes also the child LState
ce.close()
// set the remaining gas on the parent LState
ctx.setRemainingGas(L)
}()
if ce.err != nil {
return C.CString("[Contract.LuaSendAmount] newExecutor error: " + ce.err.Error())
}
// send the amount to the contract
if amountBig.Cmp(zeroBig) > 0 {
if r := sendBalance(senderState, receiverState, amountBig); r != nil {
return r
}
}
// create a recovery point
seq, err := setRecoveryPoint(aid, ctx, senderState, cs, amountBig, false, false)
if err != nil {
return C.CString("[System.LuaSendAmount] database error: " + err.Error())
}
// log some info
if ctx.traceFile != nil {
_, _ = ctx.traceFile.WriteString(
fmt.Sprintf("[Send Call default] %s(%s) : %s\n", types.EncodeAddress(cid), aid.String(), amountBig.String()))
_, _ = ctx.traceFile.WriteString(fmt.Sprintf("After sender: %s receiver: %s\n",
senderState.Balance().String(), receiverState.Balance().String()))
_, _ = ctx.traceFile.WriteString(fmt.Sprintf("snapshot set %d\n", seq))
}
// set the current contract info
prevContractInfo := ctx.curContract
ctx.curContract = newContractInfo(cs, prevContractInfo.contractId, cid,
receiverState.RP(), amountBig)
defer func() {
ctx.curContract = prevContractInfo
}()
// execute the contract call
defer setInstCount(ctx, L, ce.L)
ce.call(minusCallCount(ctx, C.vm_instcount(L), luaCallCountDeduc), L)
// check if the contract call failed
if ce.err != nil {
// recover to the previous state
err := clearRecovery(L, ctx, seq, true)
if err != nil {
return C.CString("[Contract.LuaSendAmount] recovery err: " + err.Error())
}
// log some info
if ctx.traceFile != nil {
_, _ = ctx.traceFile.WriteString(fmt.Sprintf("recovery snapshot: %d\n", seq))
}
// return the error message
return C.CString("[Contract.LuaSendAmount] call err: " + ce.err.Error())
}
if seq == 1 {
err := clearRecovery(L, ctx, seq, false)
if err != nil {
return C.CString("[Contract.LuaSendAmount] recovery err: " + err.Error())
}
}
// the transfer and contract call succeeded
return nil
}
// the receiver is not a contract, just send the amount
// if amount is zero, do nothing
if amountBig.Cmp(zeroBig) == 0 {
return nil
}
// send the amount to the receiver
if r := sendBalance(senderState, receiverState, amountBig); r != nil {
return r
}
// update the recovery point
if ctx.lastRecoveryEntry != nil {
_, _ = setRecoveryPoint(aid, ctx, senderState, cs, amountBig, true, false)
}
// log some info
if ctx.traceFile != nil {
_, _ = ctx.traceFile.WriteString(fmt.Sprintf("[Send] %s(%s) : %s\n",
types.EncodeAddress(cid), aid.String(), amountBig.String()))
_, _ = ctx.traceFile.WriteString(fmt.Sprintf("After sender: %s receiver: %s\n",
senderState.Balance().String(), receiverState.Balance().String()))
}
return nil
}
//export luaPrint
func luaPrint(L *LState, service C.int, args *C.char) {
ctx := contexts[service]
setInstMinusCount(ctx, L, 1000)
ctrLgr.Info().Str("Contract SystemPrint", types.EncodeAddress(ctx.curContract.contractId)).Msg(C.GoString(args))
}
//export luaSetRecoveryPoint
func luaSetRecoveryPoint(L *LState, service C.int) (C.int, *C.char) {
ctx := contexts[service]
if ctx == nil {
return -1, C.CString("[Contract.pcall] contract state not found")
}
if ctx.isQuery == true || ctx.nestedView > 0 {
return 0, nil
}
curContract := ctx.curContract
seq, err := setRecoveryPoint(types.ToAccountID(curContract.contractId), ctx, nil,
curContract.callState, zeroBig, false, false)
if err != nil {
return -1, C.CString("[Contract.pcall] database error: " + err.Error())
}
if ctx.traceFile != nil {
_, _ = ctx.traceFile.WriteString(fmt.Sprintf("[Pcall] snapshot set %d\n", seq))
}
return C.int(seq), nil
}
func clearRecovery(L *LState, ctx *vmContext, start int, error bool) error {
item := ctx.lastRecoveryEntry
for {
if error {
if item.recovery(ctx.bs) != nil {
return errors.New("database error")
}
}
if item.seq == start {
if error || item.prev == nil {
ctx.lastRecoveryEntry = item.prev
}
return nil
}
item = item.prev
if item == nil {
return errors.New("internal error")
}
}
}
//export luaClearRecovery
func luaClearRecovery(L *LState, service C.int, start int, error bool) *C.char {
ctx := contexts[service]
if ctx == nil {
return C.CString("[Contract.pcall] contract state not found")
}
err := clearRecovery(L, ctx, start, error)
if err != nil {
return C.CString(err.Error())
}
if ctx.traceFile != nil && error == true {
_, _ = ctx.traceFile.WriteString(fmt.Sprintf("pcall recovery snapshot : %d\n", start))
}
return nil
}
//export luaGetBalance
func luaGetBalance(L *LState, service C.int, contractId *C.char) (*C.char, *C.char) {
ctx := contexts[service]
if contractId == nil {
return C.CString(ctx.curContract.callState.ctrState.GetBalanceBigInt().String()), nil
}
cid, err := getAddressNameResolved(C.GoString(contractId), ctx.bs)
if err != nil {
return nil, C.CString("[Contract.LuaGetBalance] invalid contractId: " + err.Error())
}
aid := types.ToAccountID(cid)
cs := ctx.callState[aid]
if cs == nil {
bs := ctx.bs
as, err := bs.GetAccountState(aid)
if err != nil {
return nil, C.CString("[Contract.LuaGetBalance] getAccount error: " + err.Error())
}
return C.CString(as.GetBalanceBigInt().String()), nil
}
return C.CString(cs.accState.Balance().String()), nil
}
//export luaGetSender
func luaGetSender(L *LState, service C.int) *C.char {
ctx := contexts[service]
setInstMinusCount(ctx, L, 1000)
return C.CString(types.EncodeAddress(ctx.curContract.sender))
}
//export luaGetHash
func luaGetHash(L *LState, service C.int) *C.char {
ctx := contexts[service]
return C.CString(base58.Encode(ctx.txHash))
}
//export luaGetBlockNo
func luaGetBlockNo(L *LState, service C.int) C.lua_Integer {
ctx := contexts[service]
return C.lua_Integer(ctx.blockInfo.No)
}
//export luaGetTimeStamp
func luaGetTimeStamp(L *LState, service C.int) C.lua_Integer {
ctx := contexts[service]
return C.lua_Integer(ctx.blockInfo.Ts / 1e9)
}
//export luaGetContractId
func luaGetContractId(L *LState, service C.int) *C.char {
ctx := contexts[service]
setInstMinusCount(ctx, L, 1000)
return C.CString(types.EncodeAddress(ctx.curContract.contractId))
}
//export luaGetAmount
func luaGetAmount(L *LState, service C.int) *C.char {
ctx := contexts[service]
return C.CString(ctx.curContract.amount.String())
}
//export luaGetOrigin
func luaGetOrigin(L *LState, service C.int) *C.char {
ctx := contexts[service]
setInstMinusCount(ctx, L, 1000)
return C.CString(types.EncodeAddress(ctx.origin))
}
//export luaGetPrevBlockHash
func luaGetPrevBlockHash(L *LState, service C.int) *C.char {
ctx := contexts[service]
return C.CString(base58.Encode(ctx.blockInfo.PrevBlockHash))
}
//export luaGetDbHandle
func luaGetDbHandle(service C.int) *C.sqlite3 {
ctx := contexts[service]
curContract := ctx.curContract
cs := curContract.callState
if cs.tx != nil {
return cs.tx.getHandle()
}
var tx sqlTx
var err error
aid := types.ToAccountID(curContract.contractId)
if ctx.isQuery == true {
tx, err = beginReadOnly(aid.String(), curContract.rp)
} else {
tx, err = beginTx(aid.String(), curContract.rp)
}
if err != nil {
sqlLgr.Error().Err(err).Msg("Begin SQL Transaction")
return nil
}
if ctx.isQuery == false {
err = tx.savepoint()
if err != nil {
sqlLgr.Error().Err(err).Msg("Begin SQL Transaction")
return nil
}
}
cs.tx = tx
return cs.tx.getHandle()
}
func checkHexString(data string) bool {
if len(data) >= 2 && data[0] == '0' && (data[1] == 'x' || data[1] == 'X') {
return true
}
return false
}
//export luaCryptoSha256
func luaCryptoSha256(L *LState, arg unsafe.Pointer, argLen C.int) (*C.char, *C.char) {
data := C.GoBytes(arg, argLen)
if checkHexString(string(data)) {
dataStr := data[2:]
var err error
data, err = hex.Decode(string(dataStr))
if err != nil {
return nil, C.CString("[Contract.LuaCryptoSha256] hex decoding error: " + err.Error())
}
}
h := sha256.New()
h.Write(data)
resultHash := h.Sum(nil)
return C.CString("0x" + hex.Encode(resultHash)), nil
}
func decodeHex(hexStr string) ([]byte, error) {
if checkHexString(hexStr) {
hexStr = hexStr[2:]
}
return hex.Decode(hexStr)
}
//export luaECVerify
func luaECVerify(L *LState, service C.int, msg *C.char, sig *C.char, addr *C.char) (C.int, *C.char) {
bMsg, err := decodeHex(C.GoString(msg))
if err != nil {
return -1, C.CString("[Contract.LuaEcVerify] invalid message format: " + err.Error())
}
bSig, err := decodeHex(C.GoString(sig))
if err != nil {
return -1, C.CString("[Contract.LuaEcVerify] invalid signature format: " + err.Error())
}
ctx := contexts[service]
if ctx == nil {
return -1, C.CString("[Contract.LuaEcVerify]not found contract state")
}
setInstMinusCount(ctx, L, 10000)
var pubKey *btcec.PublicKey
var verifyResult bool
address := C.GoString(addr)
isAergo := len(address) == types.EncodedAddressLength
/*Aergo Address*/
if isAergo {
bAddress, err := types.DecodeAddress(address)
if err != nil {
return -1, C.CString("[Contract.LuaEcVerify] invalid aergo address: " + err.Error())
}
pubKey, err = btcec.ParsePubKey(bAddress, btcec.S256())
if err != nil {
return -1, C.CString("[Contract.LuaEcVerify] error parsing pubKey: " + err.Error())
}
}
// CompactSign
if len(bSig) == 65 {
// ethereum
if !isAergo {
btcsig := make([]byte, 65)
btcsig[0] = bSig[64] + 27
copy(btcsig[1:], bSig)
bSig = btcsig
}
pub, _, err := btcec.RecoverCompact(btcec.S256(), bSig, bMsg)
if err != nil {
return -1, C.CString("[Contract.LuaEcVerify] error recoverCompact: " + err.Error())
}
if pubKey != nil {
verifyResult = pubKey.IsEqual(pub)
} else {
bAddress, err := decodeHex(address)
if err != nil {
return -1, C.CString("[Contract.LuaEcVerify] invalid Ethereum address: " + err.Error())
}
bPub := pub.SerializeUncompressed()
h := sha256.New()
h.Write(bPub[1:])
signAddress := h.Sum(nil)[12:]
verifyResult = bytes.Equal(bAddress, signAddress)
}
} else {
sign, err := btcec.ParseSignature(bSig, btcec.S256())
if err != nil {
return -1, C.CString("[Contract.LuaEcVerify] error parsing signature: " + err.Error())
}
if pubKey == nil {
return -1, C.CString("[Contract.LuaEcVerify] error recovering pubKey")
}
verifyResult = sign.Verify(bMsg, pubKey)
}
if verifyResult {
return C.int(1), nil
}
return C.int(0), nil
}
func luaCryptoToBytes(data unsafe.Pointer, dataLen C.int) ([]byte, bool) {
var d []byte
b := C.GoBytes(data, dataLen)
isHex := checkHexString(string(b))
if isHex {
var err error
d, err = hex.Decode(string(b[2:]))
if err != nil {
isHex = false
}
}
if !isHex {
d = b
}
return d, isHex
}
func luaCryptoRlpToBytes(data unsafe.Pointer) rlpObject {
x := (*C.struct_rlp_obj)(data)
if x.rlp_obj_type == C.RLP_TSTRING {
b, _ := luaCryptoToBytes(x.data, C.int(x.size))
return rlpString(b)
}
var l rlpList
elems := (*[1 << 30]C.struct_rlp_obj)(unsafe.Pointer(x.data))[:C.int(x.size):C.int(x.size)]
for _, elem := range elems {
b, _ := luaCryptoToBytes(elem.data, C.int(elem.size))
l = append(l, rlpString(b))
}
return l
}
//export luaCryptoVerifyProof
func luaCryptoVerifyProof(
key unsafe.Pointer, keyLen C.int,
value unsafe.Pointer,
hash unsafe.Pointer, hashLen C.int,
proof unsafe.Pointer, nProof C.int,
) C.int {
k, _ := luaCryptoToBytes(key, keyLen)
v := luaCryptoRlpToBytes(value)
h, _ := luaCryptoToBytes(hash, hashLen)
cProof := (*[1 << 30]C.struct_proof)(proof)[:nProof:nProof]
bProof := make([][]byte, int(nProof))
for i, p := range cProof {
bProof[i], _ = luaCryptoToBytes(p.data, C.int(p.len))
}
if verifyEthStorageProof(k, v, h, bProof) {
return C.int(1)
}
return C.int(0)
}
//export luaCryptoKeccak256
func luaCryptoKeccak256(data unsafe.Pointer, dataLen C.int) (unsafe.Pointer, int) {
d, isHex := luaCryptoToBytes(data, dataLen)
h := keccak256(d)
if isHex {
hexb := []byte("0x" + hex.Encode(h))
return C.CBytes(hexb), len(hexb)
} else {
return C.CBytes(h), len(h)
}
}
// transformAmount processes the input string to calculate the total amount,
// taking into account the different units ("aergo", "gaer", "aer")
func transformAmount(amountStr string) (*big.Int, error) {
if len(amountStr) == 0 {
return zeroBig, nil
}
totalAmount := new(big.Int)
remainingStr := amountStr
// Define the units and corresponding multipliers
for _, data := range []struct {
unit string
multiplier *big.Int
}{
{"aergo", mulAergo},
{"gaer", mulGaer},
{"aer", zeroBig},
} {
idx := strings.Index(strings.ToLower(remainingStr), data.unit)
if idx != -1 {
// Extract the part before the unit
subStr := remainingStr[:idx]
// Parse and convert the amount
partialAmount, err := parseAndConvert(subStr, data.unit, data.multiplier, amountStr)
if err != nil {
return nil, err
}
// Add to the total amount
totalAmount.Add(totalAmount, partialAmount)
// Adjust the remaining string to process
remainingStr = remainingStr[idx+len(data.unit):]
}
}
// Process the rest of the string, if there is some
if len(remainingStr) > 0 {
partialAmount, err := parseAndConvert(remainingStr, "", zeroBig, amountStr)
if err != nil {
return nil, err
}
// Add to the total amount
totalAmount.Add(totalAmount, partialAmount)
}
return totalAmount, nil
}
// parseAndConvert is a helper function to parse the substring as a big integer
// and apply the necessary multiplier based on the unit.
func parseAndConvert(subStr, unit string, mulUnit *big.Int, amountStr string) (*big.Int, error) {
trimmedStr := strings.TrimSpace(subStr)
// Convert the trimmed string to a big integer
amountBig, valid := new(big.Int).SetString(trimmedStr, 10)
if !valid {
// Emits a backwards compatible error message
// the same as: dataType := len(unit) > 0 ? "BigNum" : "Integer"
dataType := map[bool]string{true: "BigNum", false: "Integer"}[len(unit) > 0]
return nil, errors.New("converting error for " + dataType + ": " + strings.TrimSpace(amountStr))
}
// Check for negative amounts
if amountBig.Cmp(zeroBig) < 0 {
return nil, errors.New("negative amount not allowed")
}
// Apply multiplier based on unit
if mulUnit != zeroBig {
amountBig.Mul(amountBig, mulUnit)
}
return amountBig, nil
}
//export luaDeployContract
func luaDeployContract(
L *LState,
service C.int,
contract *C.char,
args *C.char,
amount *C.char,
) (C.int, *C.char) {
argsStr := C.GoString(args)
contractStr := C.GoString(contract)
ctx := contexts[service]
if ctx == nil {
return -1, C.CString("[Contract.LuaDeployContract]not found contract state")
}
if ctx.isQuery == true || ctx.nestedView > 0 {
return -1, C.CString("[Contract.LuaDeployContract]send not permitted in query")
}
bs := ctx.bs
// contract code
var code []byte
// check if contract name or address is given
cid, err := getAddressNameResolved(contractStr, bs)
if err == nil {
// check if contract exists
contractState, err := getOnlyContractState(ctx, cid)
if err != nil {
return -1, C.CString("[Contract.LuaDeployContract]" + err.Error())
}
// read the contract code
code, err = contractState.GetCode()
if err != nil {
return -1, C.CString("[Contract.LuaDeployContract]" + err.Error())
} else if len(code) == 0 {
return -1, C.CString("[Contract.LuaDeployContract]: not found code")
}
}
// compile contract code if not found
if len(code) == 0 {
if ctx.blockInfo.ForkVersion >= 2 {
code, err = Compile(contractStr, L)
} else {
code, err = Compile(contractStr, nil)
}
if err != nil {
if C.luaL_hasuncatchablerror(L) != C.int(0) &&
C.ERR_BF_TIMEOUT == err.Error() {
return -1, C.CString(C.ERR_BF_TIMEOUT)
} else if err == ErrVmStart {
return -1, C.CString("[Contract.LuaDeployContract] get luaState error")
}
return -1, C.CString("[Contract.LuaDeployContract]compile error:" + err.Error())
}
}
err = ctx.addUpdateSize(int64(len(code)))
if err != nil {
return -1, C.CString("[Contract.LuaDeployContract]:" + err.Error())
}
// create account for the contract
prevContractInfo := ctx.curContract
creator := prevContractInfo.callState.accState
newContract, err := state.CreateAccountState(CreateContractID(prevContractInfo.contractId, creator.Nonce()), bs.StateDB)
if err != nil {
return -1, C.CString("[Contract.LuaDeployContract]:" + err.Error())
}
contractState, err := statedb.OpenContractState(newContract.ID(), newContract.State(), bs.StateDB)
if err != nil {
return -1, C.CString("[Contract.LuaDeployContract]:" + err.Error())
}
cs := &callState{isCallback: true, isDeploy: true, ctrState: contractState, accState: newContract}
ctx.callState[newContract.AccountID()] = cs
// read the amount transferred to the contract
amountBig, err := transformAmount(C.GoString(amount))
if err != nil {
return -1, C.CString("[Contract.LuaDeployContract]value not proper format:" + err.Error())
}
// read the arguments for the constructor call
var ci types.CallInfo
err = getCallInfo(&ci.Args, []byte(argsStr), newContract.ID())
if err != nil {
return -1, C.CString("[Contract.LuaDeployContract] invalid args:" + err.Error())
}
// send the amount to the contract
senderState := prevContractInfo.callState.accState
receiverState := cs.accState
if amountBig.Cmp(zeroBig) > 0 {
if rv := sendBalance(senderState, receiverState, amountBig); rv != nil {
return -1, rv
}
}
// create a recovery point
seq, err := setRecoveryPoint(newContract.AccountID(), ctx, senderState, cs, amountBig, false, true)
if err != nil {
return -1, C.CString("[System.LuaDeployContract] DB err:" + err.Error())
}
// log some info
if ctx.traceFile != nil {
_, _ = ctx.traceFile.WriteString(fmt.Sprintf("[DEPLOY] %s(%s)\n",
types.EncodeAddress(newContract.ID()), newContract.AccountID().String()))
_, _ = ctx.traceFile.WriteString(fmt.Sprintf("deploy snapshot set %d\n", seq))
_, _ = ctx.traceFile.WriteString(fmt.Sprintf("SendBalance : %s\n", amountBig.String()))
_, _ = ctx.traceFile.WriteString(fmt.Sprintf("After sender: %s receiver: %s\n",
senderState.Balance().String(), receiverState.Balance().String()))
}
// set the contract info
ctx.curContract = newContractInfo(cs, prevContractInfo.contractId, newContract.ID(),
receiverState.RP(), amountBig)
defer func() {
ctx.curContract = prevContractInfo
}()
runCode := util.LuaCode(code).ByteCode()
// save the contract code
err = contractState.SetCode(code)
if err != nil {
return -1, C.CString("[Contract.LuaDeployContract]:" + err.Error())
}
// save the contract creator
err = contractState.SetData(dbkey.CreatorMeta(), []byte(types.EncodeAddress(prevContractInfo.contractId)))
if err != nil {
return -1, C.CString("[Contract.LuaDeployContract]:" + err.Error())
}
// get the remaining gas from the parent LState
ctx.refreshRemainingGas(L)
// create a new executor with the remaining gas on the child LState
ce := newExecutor(runCode, newContract.ID(), ctx, &ci, amountBig, true, false, contractState)
defer func() {
// close the executor, which will close the child LState
ce.close()
// set the remaining gas on the parent LState
ctx.setRemainingGas(L)
}()
if ce.err != nil {
return -1, C.CString("[Contract.LuaDeployContract]newExecutor Error :" + ce.err.Error())
}
if ctx.blockInfo.ForkVersion < 2 {
// create a sql database for the contract
if db := luaGetDbHandle(ctx.service); db == nil {
return -1, C.CString("[System.LuaDeployContract] DB err: cannot open a database")
}
}
// increment the nonce of the creator
senderState.SetNonce(senderState.Nonce() + 1)
addr := C.CString(types.EncodeAddress(newContract.ID()))
ret := C.int(1)
if ce != nil {
// run the constructor
defer setInstCount(ce.ctx, L, ce.L)
ret += ce.call(minusCallCount(ctx, C.vm_instcount(L), luaCallCountDeduc), L)
// check if the execution was successful
if ce.err != nil {
// rollback the recovery point
err := clearRecovery(L, ctx, seq, true)
if err != nil {
return -1, C.CString("[Contract.LuaDeployContract] recovery error: " + err.Error())
}
// log some info
if ctx.traceFile != nil {
_, _ = ctx.traceFile.WriteString(fmt.Sprintf("recovery snapshot: %d\n", seq))
}
// return the error message
return -1, C.CString("[Contract.LuaDeployContract] call err:" + ce.err.Error())
}
}
if seq == 1 {
err := clearRecovery(L, ctx, seq, false)
if err != nil {
return -1, C.CString("[Contract.LuaDeployContract] recovery error: " + err.Error())
}
}
return ret, addr
}
//export isPublic
func isPublic() C.int {
if PubNet {
return C.int(1)
} else {
return C.int(0)
}
}
//export luaRandomInt
func luaRandomInt(min, max, service C.int) C.int {
ctx := contexts[service]
if ctx.seed == nil {
setRandomSeed(ctx)
}
return C.int(ctx.seed.Intn(int(max+C.int(1)-min)) + int(min))
}
//export luaEvent
func luaEvent(L *LState, service C.int, eventName *C.char, args *C.char) *C.char {
ctx := contexts[service]
if ctx.isQuery == true || ctx.nestedView > 0 {
return C.CString("[Contract.Event] event not permitted in query")
}
if ctx.eventCount >= maxEventCnt {
return C.CString(fmt.Sprintf("[Contract.Event] exceeded the maximum number of events(%d)", maxEventCnt))
}
if len(C.GoString(eventName)) > maxEventNameSize {
return C.CString(fmt.Sprintf("[Contract.Event] exceeded the maximum length of event name(%d)", maxEventNameSize))
}
if len(C.GoString(args)) > maxEventArgSize {
return C.CString(fmt.Sprintf("[Contract.Event] exceeded the maximum length of event args(%d)", maxEventArgSize))
}
ctx.events = append(
ctx.events,
&types.Event{
ContractAddress: ctx.curContract.contractId,
EventIdx: ctx.eventCount,
EventName: C.GoString(eventName),
JsonArgs: C.GoString(args),
},
)
ctx.eventCount++
return nil
}
//export luaIsContract
func luaIsContract(L *LState, service C.int, contractId *C.char) (C.int, *C.char) {
ctx := contexts[service]
if ctx == nil {
return -1, C.CString("[Contract.LuaIsContract] contract state not found")
}
cid, err := getAddressNameResolved(C.GoString(contractId), ctx.bs)
if err != nil {
return -1, C.CString("[Contract.LuaIsContract] invalid contractId: " + err.Error())
}
cs, err := getCallState(ctx, cid)
if err != nil {
return -1, C.CString("[Contract.LuaIsContract] getAccount error: " + err.Error())
}
return C.int(len(cs.accState.CodeHash())), nil
}
//export luaGovernance
func luaGovernance(L *LState, service C.int, gType C.char, arg *C.char) *C.char {
ctx := contexts[service]
if ctx == nil {
return C.CString("[Contract.LuaGovernance] contract state not found")
}
if ctx.isQuery == true || ctx.nestedView > 0 {
return C.CString("[Contract.LuaGovernance] governance not permitted in query")
}
var amountBig *big.Int
var payload []byte
switch gType {
case 'S', 'U':
var err error
amountBig, err = transformAmount(C.GoString(arg))
if err != nil {
return C.CString("[Contract.LuaGovernance] invalid amount: " + err.Error())
}
if gType == 'S' {
payload = []byte(fmt.Sprintf(`{"Name":"%s"}`, types.Opstake.Cmd()))
} else {
payload = []byte(fmt.Sprintf(`{"Name":"%s"}`, types.Opunstake.Cmd()))
}
case 'V':
amountBig = zeroBig
payload = []byte(fmt.Sprintf(`{"Name":"%s","Args":%s}`, types.OpvoteBP.Cmd(), C.GoString(arg)))
case 'D':
amountBig = zeroBig
payload = []byte(fmt.Sprintf(`{"Name":"%s","Args":%s}`, types.OpvoteDAO.Cmd(), C.GoString(arg)))
}
cid := []byte(types.AergoSystem)
aid := types.ToAccountID(cid)
scsState, err := getContractState(ctx, cid)
if err != nil {
return C.CString("[Contract.LuaGovernance] getAccount error: " + err.Error())
}
curContract := ctx.curContract
senderState := curContract.callState.accState
receiverState := scsState.accState
txBody := types.TxBody{
Amount: amountBig.Bytes(),
Payload: payload,
}
if ctx.blockInfo.ForkVersion >= 2 {
txBody.Account = curContract.contractId
}
err = types.ValidateSystemTx(&txBody)
if err != nil {
return C.CString("[Contract.LuaGovernance] error: " + err.Error())
}
seq, err := setRecoveryPoint(aid, ctx, senderState, scsState, zeroBig, false, false)
if err != nil {
return C.CString("[Contract.LuaGovernance] database error: " + err.Error())
}
events, err := system.ExecuteSystemTx(scsState.ctrState, &txBody, senderState, receiverState, ctx.blockInfo)
if err != nil {
rErr := clearRecovery(L, ctx, seq, true)
if rErr != nil {
return C.CString("[Contract.LuaGovernance] recovery error: " + rErr.Error())
}
return C.CString("[Contract.LuaGovernance] error: " + err.Error())
}
if seq == 1 {
err := clearRecovery(L, ctx, seq, false)
if err != nil {
return C.CString("[Contract.LuaGovernance] recovery error: " + err.Error())
}
}
ctx.eventCount += int32(len(events))
ctx.events = append(ctx.events, events...)
if ctx.lastRecoveryEntry != nil {
if gType == 'S' {
seq, _ = setRecoveryPoint(aid, ctx, senderState, scsState, amountBig, true, false)
if ctx.traceFile != nil {
_, _ = ctx.traceFile.WriteString(fmt.Sprintf("[GOVERNANCE]aid(%s)\n", aid.String()))
_, _ = ctx.traceFile.WriteString(fmt.Sprintf("snapshot set %d\n", seq))
_, _ = ctx.traceFile.WriteString(fmt.Sprintf("staking : %s\n", amountBig.String()))
_, _ = ctx.traceFile.WriteString(fmt.Sprintf("After sender: %s receiver: %s\n",
senderState.Balance().String(), receiverState.Balance().String()))
}
} else if gType == 'U' {
seq, _ = setRecoveryPoint(aid, ctx, receiverState, ctx.curContract.callState, amountBig, true, false)
if ctx.traceFile != nil {
_, _ = ctx.traceFile.WriteString(fmt.Sprintf("[GOVERNANCE]aid(%s)\n", aid.String()))
_, _ = ctx.traceFile.WriteString(fmt.Sprintf("snapshot set %d\n", seq))
_, _ = ctx.traceFile.WriteString(fmt.Sprintf("unstaking : %s\n", amountBig.String()))
_, _ = ctx.traceFile.WriteString(fmt.Sprintf("After sender: %s receiver: %s\n",
senderState.Balance().String(), receiverState.Balance().String()))
}
}
}
return nil
}
//export luaViewStart
func luaViewStart(service C.int) {
ctx := contexts[service]
ctx.nestedView++
}
//export luaViewEnd
func luaViewEnd(service C.int) {
ctx := contexts[service]
ctx.nestedView--
}
//export luaCheckView
func luaCheckView(service C.int) C.int {
ctx := contexts[service]
return C.int(ctx.nestedView)
}
// luaCheckTimeout checks whether the block creation timeout occurred.
//
//export luaCheckTimeout
func luaCheckTimeout(service C.int) C.int {
if service < BlockFactory {
// Originally, MaxVmService was used instead of maxContext. service
// value can be 2 and decremented by MaxVmService(=2) during VM loading.
// That means the value of service becomes zero after the latter
// adjustment.
//
// This make the VM check block timeout in a unwanted situation. If that
// happens during the chain service is connecting block, the block chain
// becomes out of sync.
service = service + C.int(maxContext)
}
if service != BlockFactory {
return 0
}
ctx := contexts[service]
select {
case <-ctx.execCtx.Done():
return 1
default:
return 0
}
}
//export luaIsFeeDelegation
func luaIsFeeDelegation(L *LState, service C.int) (C.int, *C.char) {
ctx := contexts[service]
if ctx == nil {
return -1, C.CString("[Contract.LuaIsContract] contract state not found")
}
if ctx.isFeeDelegation {
return 1, nil
}
return 0, nil
}
//export LuaGetDbHandleSnap
func LuaGetDbHandleSnap(service C.int, snap *C.char) *C.char {
stateSet := contexts[service]
curContract := stateSet.curContract
callState := curContract.callState
if stateSet.isQuery != true {
return C.CString("[Contract.LuaSetDbSnap] not permitted in transaction")
}
if callState.tx != nil {
return C.CString("[Contract.LuaSetDbSnap] transaction already started")
}
rp, err := strconv.ParseUint(C.GoString(snap), 10, 64)
if err != nil {
return C.CString("[Contract.LuaSetDbSnap] snapshot is not valid" + C.GoString(snap))
}
aid := types.ToAccountID(curContract.contractId)
tx, err := beginReadOnly(aid.String(), rp)
if err != nil {
return C.CString("Error Begin SQL Transaction")
}
callState.tx = tx
return nil
}
//export LuaGetDbSnapshot
func LuaGetDbSnapshot(service C.int) *C.char {
stateSet := contexts[service]
curContract := stateSet.curContract
return C.CString(strconv.FormatUint(curContract.rp, 10))
}
//export luaGetStaking
func luaGetStaking(service C.int, addr *C.char) (*C.char, C.lua_Integer, *C.char) {
var (
ctx *vmContext
scs, namescs *statedb.ContractState
err error
staking *types.Staking
)
ctx = contexts[service]
scs, err = statedb.GetSystemAccountState(ctx.bs.StateDB)
if err != nil {
return nil, 0, C.CString(err.Error())
}
namescs, err = statedb.GetNameAccountState(ctx.bs.StateDB)
if err != nil {
return nil, 0, C.CString(err.Error())
}
staking, err = system.GetStaking(scs, name.GetAddress(namescs, types.ToAddress(C.GoString(addr))))
if err != nil {
return nil, 0, C.CString(err.Error())
}
return C.CString(staking.GetAmountBigInt().String()), C.lua_Integer(staking.When), nil
}
func sendBalance(sender *state.AccountState, receiver *state.AccountState, amount *big.Int) *C.char {
if err := state.SendBalance(sender, receiver, amount); err != nil {
return C.CString("[Contract.sendBalance] insufficient balance: " +
sender.Balance().String() + " : " + amount.String())
}
return nil
}