Skip to content

Commit

Permalink
new API integration - multi transfer function support
Browse files Browse the repository at this point in the history
  • Loading branch information
sasurobert committed Jul 14, 2021
1 parent 2de8612 commit 18697d3
Show file tree
Hide file tree
Showing 9 changed files with 176 additions and 95 deletions.
68 changes: 49 additions & 19 deletions arwen/contexts/output.go
Original file line number Diff line number Diff line change
Expand Up @@ -305,11 +305,13 @@ func (context *outputContext) Transfer(destination []byte, sender []byte, gasLim
func (context *outputContext) TransferESDT(
destination []byte,
sender []byte,
tokenIdentifier []byte,
nonce uint64,
value *big.Int,
transfers []*vmcommon.ESDTTransfer,
callInput *vmcommon.ContractCallInput,
) (uint64, error) {
if len(transfers) == 0 {
return 0, arwen.ErrTransferValueOnESDTCall
}

isSmartContract := context.host.Blockchain().IsSmartContract(destination)
sameShard := context.host.AreInSameShard(sender, destination)
callType := vmcommon.DirectCall
Expand All @@ -318,7 +320,7 @@ func (context *outputContext) TransferESDT(
callType = vmcommon.ESDTTransferAndExecute
}

vmOutput, gasConsumedByTransfer, err := context.host.ExecuteESDTTransfer(destination, sender, tokenIdentifier, nonce, value, callType)
vmOutput, gasConsumedByTransfer, err := context.host.ExecuteESDTTransfer(destination, sender, transfers, callType)
if err != nil {
return 0, err
}
Expand Down Expand Up @@ -349,25 +351,12 @@ func (context *outputContext) TransferESDT(
Value: big.NewInt(0),
GasLimit: gasRemaining,
GasLocked: 0,
Data: []byte(vmcommon.BuiltInFunctionESDTTransfer + "@" + hex.EncodeToString(tokenIdentifier) + "@" + hex.EncodeToString(value.Bytes())),
Data: []byte{},
CallType: vmcommon.DirectCall,
SenderAddress: sender,
}

if nonce > 0 {
nonceAsBytes := big.NewInt(0).SetUint64(nonce).Bytes()
outputTransfer.Data = []byte(vmcommon.BuiltInFunctionESDTNFTTransfer + "@" + hex.EncodeToString(tokenIdentifier) +
"@" + hex.EncodeToString(nonceAsBytes) + "@" + hex.EncodeToString(value.Bytes()))
if sameShard {
outputTransfer.Data = append(outputTransfer.Data, []byte("@"+hex.EncodeToString(destination))...)
} else {
outTransfer, ok := vmOutput.OutputAccounts[string(destination)]
if ok && len(outTransfer.OutputTransfers) == 1 {
outputTransfer.Data = outTransfer.OutputTransfers[0].Data
}
}

}
outputTransfer.Data = context.getOutputTransferDataFromESDTTransfer(transfers, vmOutput, sameShard, destination)

if sameShard {
outputTransfer.GasLimit = 0
Expand All @@ -387,6 +376,47 @@ func (context *outputContext) TransferESDT(
return gasRemaining, nil
}

func (context *outputContext) getOutputTransferDataFromESDTTransfer(
transfers []*vmcommon.ESDTTransfer,
vmOutput *vmcommon.VMOutput,
sameShard bool,
destination []byte,
) []byte {

if len(transfers) == 1 && transfers[0].ESDTTokenNonce == 0 {
return []byte(vmcommon.BuiltInFunctionESDTTransfer + "@" + hex.EncodeToString(transfers[0].ESDTTokenName) + "@" + hex.EncodeToString(transfers[0].ESDTValue.Bytes()))
}

if len(transfers) == 1 {
data := []byte(vmcommon.BuiltInFunctionESDTNFTTransfer + "@" + hex.EncodeToString(transfers[0].ESDTTokenName) +
"@" + hex.EncodeToString(big.NewInt(0).SetUint64(transfers[0].ESDTTokenNonce).Bytes()) + "@" + hex.EncodeToString(transfers[0].ESDTValue.Bytes()))
if sameShard {
data = append(data, []byte("@"+hex.EncodeToString(destination))...)
return data
}

outTransfer, ok := vmOutput.OutputAccounts[string(destination)]
if ok && len(outTransfer.OutputTransfers) == 1 {
data = outTransfer.OutputTransfers[0].Data
}
return data
}

if !sameShard {
outTransfer, ok := vmOutput.OutputAccounts[string(destination)]
if ok && len(outTransfer.OutputTransfers) == 1 {
return outTransfer.OutputTransfers[0].Data
}
}

data := vmcommon.BuiltInFunctionMultiESDTNFTTransfer + "@" + hex.EncodeToString(destination) + "@" + hex.EncodeToString(big.NewInt(int64(len(transfers))).Bytes())
for _, transfer := range transfers {
data += "@" + hex.EncodeToString(transfer.ESDTTokenName) + "@" + hex.EncodeToString(big.NewInt(0).SetUint64(transfer.ESDTTokenNonce).Bytes()) + "@" + hex.EncodeToString(transfer.ESDTValue.Bytes())
}

return []byte(data)
}

func (context *outputContext) hasSufficientBalance(address []byte, value *big.Int) bool {
senderBalance := context.host.Blockchain().GetBalanceBigInt(address)
return senderBalance.Cmp(value) >= 0
Expand Down
129 changes: 81 additions & 48 deletions arwen/elrondapi/elrondei.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ package elrondapi
// extern void v1_3_getExternalBalance(void *context, int32_t addressOffset, int32_t resultOffset);
// extern int32_t v1_3_blockHash(void *context, long long nonce, int32_t resultOffset);
// extern int32_t v1_3_transferValue(void *context, int32_t dstOffset, int32_t valueOffset, int32_t dataOffset, int32_t length);
// extern int32_t v1_3_transferESDT(void *context, int32_t dstOffset, int32_t tokenIDOffset, int32_t tokenIdLen, int32_t valueOffset, long long gasLimit, int32_t dataOffset, int32_t length);
// extern int32_t v1_3_transferESDTExecute(void *context, int32_t dstOffset, int32_t tokenIDOffset, int32_t tokenIdLen, int32_t valueOffset, long long gasLimit, int32_t functionOffset, int32_t functionLength, int32_t numArguments, int32_t argumentsLengthOffset, int32_t dataOffset);
// extern int32_t v1_3_transferESDTNFTExecute(void *context, int32_t dstOffset, int32_t tokenIDOffset, int32_t tokenIdLen, int32_t valueOffset, long long nonce, long long gasLimit, int32_t functionOffset, int32_t functionLength, int32_t numArguments, int32_t argumentsLengthOffset, int32_t dataOffset);
// extern int32_t v1_3_multiTransferESDTNFTExecute(void *context, int32_t dstOffset, int32_t numTokenTransfers, int32_t tokenTransfersArgsLengthOffset, int32_t tokenTransferDataOffset, long long gasLimit, int32_t functionOffset, int32_t functionLength, int32_t numArguments, int32_t argumentsLengthOffset, int32_t dataOffset);
// extern int32_t v1_3_transferValueExecute(void *context, int32_t dstOffset, int32_t valueOffset, long long gasLimit, int32_t functionOffset, int32_t functionLength, int32_t numArguments, int32_t argumentsLengthOffset, int32_t dataOffset);
// extern int32_t v1_3_getArgumentLength(void *context, int32_t id);
// extern int32_t v1_3_getArgument(void *context, int32_t id, int32_t argOffset);
Expand All @@ -37,7 +37,7 @@ package elrondapi
// extern int32_t v1_3_getESDTTokenNameByIndex(void *context, int32_t resultOffset, int32_t index);
// extern long long v1_3_getESDTTokenNonceByIndex(void *context, int32_t index);
// extern int32_t v1_3_getESDTTokenTypeByIndex(void *context, int32_t index);
// extern int32_t v1_3_getCallValueTokenNamByIndexe(void *context, int32_t callValueOffset, int32_t tokenNameOffset, int32_t index);
// extern int32_t v1_3_getCallValueTokenNameByIndex(void *context, int32_t callValueOffset, int32_t tokenNameOffset, int32_t index);
// extern long long v1_3_getCurrentESDTNFTNonce(void *context, int32_t addressOffset, int32_t tokenIDOffset, int32_t tokenIDLen);
// extern void v1_3_writeLog(void *context, int32_t pointer, int32_t length, int32_t topicPtr, int32_t numTopics);
// extern void v1_3_writeEventLog(void *context, int32_t numTopics, int32_t topicLengthsOffset, int32_t topicOffset, int32_t dataOffset, int32_t dataLength);
Expand Down Expand Up @@ -164,7 +164,7 @@ func ElrondEIImports() (*wasmer.Imports, error) {
return nil, err
}

imports, err = imports.Append("transferESDT", v1_3_transferESDT, C.v1_3_transferESDT)
imports, err = imports.Append("multiTransferESDTNFTExecute", v1_3_multiTransferESDTNFTExecute, C.v1_3_multiTransferESDTNFTExecute)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -1097,30 +1097,6 @@ func makeCrossShardCallFromInput(vmInput *vmcommon.ContractCallInput) string {
return txData
}

//export v1_3_transferESDT
func v1_3_transferESDT(
context unsafe.Pointer,
destOffset int32,
tokenIDOffset int32,
tokenIDLen int32,
valueOffset int32,
gasLimit int64,
dataOffset int32,
length int32,
) int32 {
host := arwen.GetVMHost(context)
metering := host.Metering()

gasToUse := metering.GasSchedule().ElrondAPICost.TransferValue
metering.UseGas(gasToUse)

gasToUse = math.MulUint64(metering.GasSchedule().BaseOperationCost.PersistPerByte, uint64(length))
metering.UseGas(gasToUse)
logEEI.Warn("transferESDT() is deprecated")
// this is only for backward compatibility - function deprecated
return 1
}

//export v1_3_transferESDTExecute
func v1_3_transferESDTExecute(
context unsafe.Pointer,
Expand Down Expand Up @@ -1170,6 +1146,68 @@ func v1_3_transferESDTNFTExecute(
dataOffset)
}

//export v1_3_multiTransferESDTNFTExecute
func v1_3_multiTransferESDTNFTExecute(
context unsafe.Pointer,
destOffset int32,
numTokenTransfers int32,
tokenTransfersArgsLengthOffset int32,
tokenTransferDataOffset int32,
gasLimit int64,
functionOffset int32,
functionLength int32,
numArguments int32,
argumentsLengthOffset int32,
dataOffset int32,
) int32 {
host := arwen.GetVMHost(context)
runtime := host.Runtime()
metering := host.Metering()

callArgs, err := extractIndirectContractCallArgumentsWithoutValue(
host, destOffset, functionOffset, functionLength, numArguments, argumentsLengthOffset, dataOffset)
if arwen.WithFaultAndHost(host, err, runtime.ElrondAPIErrorShouldFailExecution()) {
return 1
}

gasToUse := math.MulUint64(metering.GasSchedule().BaseOperationCost.DataCopyPerByte, uint64(callArgs.actualLen))
metering.UseGas(gasToUse)

transferArgs, actualLen, err := getArgumentsFromMemory(
host,
numTokenTransfers*parsers.ArgsPerTransfer,
tokenTransfersArgsLengthOffset,
tokenTransferDataOffset,
)

gasToUse = math.MulUint64(metering.GasSchedule().BaseOperationCost.DataCopyPerByte, uint64(actualLen))
metering.UseGas(gasToUse)

transfers := make([]*vmcommon.ESDTTransfer, numTokenTransfers)
for i := int32(0); i < numTokenTransfers; i++ {
tokenStartIndex := i * parsers.ArgsPerTransfer
transfer := &vmcommon.ESDTTransfer{
ESDTTokenName: transferArgs[tokenStartIndex],
ESDTTokenNonce: big.NewInt(0).SetBytes(transferArgs[tokenStartIndex+1]).Uint64(),
ESDTValue: big.NewInt(0).SetBytes(transferArgs[tokenStartIndex+2]),
ESDTTokenType: uint32(vmcommon.Fungible),
}
if transfer.ESDTTokenNonce > 0 {
transfer.ESDTTokenType = uint32(vmcommon.NonFungible)
}
transfers[i] = transfer
}

return TransferESDTNFTExecuteWithTypedArgs(
host,
callArgs.dest,
transfers,
gasLimit,
callArgs.function,
callArgs.args,
)
}

// TransferESDTNFTExecuteWithHost contains only memory reading of arguments
func TransferESDTNFTExecuteWithHost(
host arwen.VMHost,
Expand Down Expand Up @@ -1202,12 +1240,19 @@ func TransferESDTNFTExecuteWithHost(
gasToUse := math.MulUint64(metering.GasSchedule().BaseOperationCost.DataCopyPerByte, uint64(callArgs.actualLen))
metering.UseGas(gasToUse)

transfer := &vmcommon.ESDTTransfer{
ESDTValue: callArgs.value,
ESDTTokenName: tokenIdentifier,
ESDTTokenNonce: uint64(nonce),
ESDTTokenType: uint32(vmcommon.Fungible),
}
if nonce > 0 {
transfer.ESDTTokenType = uint32(vmcommon.NonFungible)
}
return TransferESDTNFTExecuteWithTypedArgs(
host,
callArgs.value,
tokenIdentifier,
callArgs.dest,
nonce,
[]*vmcommon.ESDTTransfer{transfer},
gasLimit,
callArgs.function,
callArgs.args,
Expand All @@ -1217,10 +1262,8 @@ func TransferESDTNFTExecuteWithHost(
// TransferESDTNFTExecuteWithTypedArgs defines the actual transfer ESDT execute logic
func TransferESDTNFTExecuteWithTypedArgs(
host arwen.VMHost,
esdtValue *big.Int,
esdtTokenName []byte,
dest []byte,
nonce int64,
transfers []*vmcommon.ESDTTransfer,
gasLimit int64,
function []byte,
data [][]byte,
Expand All @@ -1233,7 +1276,7 @@ func TransferESDTNFTExecuteWithTypedArgs(

output := host.Output()

gasToUse := metering.GasSchedule().ElrondAPICost.TransferValue
gasToUse := metering.GasSchedule().ElrondAPICost.TransferValue * uint64(len(transfers))
metering.UseGas(gasToUse)

sender := runtime.GetSCAddress()
Expand All @@ -1255,22 +1298,12 @@ func TransferESDTNFTExecuteWithTypedArgs(
return 1
}

esdtTokenType := vmcommon.Fungible
if nonce > 0 {
esdtTokenType = vmcommon.NonFungible
}
contractCallInput.ESDTTransfers = make([]*vmcommon.ESDTTransfer, 1)
contractCallInput.ESDTTransfers[0] = &vmcommon.ESDTTransfer{
ESDTValue: esdtValue,
ESDTTokenName: esdtTokenName,
ESDTTokenType: uint32(esdtTokenType),
ESDTTokenNonce: uint64(nonce),
}
contractCallInput.ESDTTransfers = transfers
}

snapshotBeforeTransfer := host.Blockchain().GetSnapshot()

gasLimitForExec, executeErr := output.TransferESDT(dest, sender, esdtTokenName, uint64(nonce), esdtValue, contractCallInput)
gasLimitForExec, executeErr := output.TransferESDT(dest, sender, transfers, contractCallInput)
if arwen.WithFaultAndHost(host, executeErr, runtime.ElrondAPIErrorShouldFailExecution()) {
return 1
}
Expand Down Expand Up @@ -2920,7 +2953,7 @@ func createContract(
code []byte,
codeMetadata []byte,
host arwen.VMHost,
runtime arwen.RuntimeContext,
_ arwen.RuntimeContext,
) ([]byte, error) {
contractCreate := &vmcommon.ContractCreateInput{
VMInput: vmcommon.VMInput{
Expand Down Expand Up @@ -3007,7 +3040,7 @@ func prepareIndirectContractCallInput(
destination []byte,
function []byte,
data [][]byte,
gasToUse uint64,
_ uint64,
syncExecutionRequired bool,
) (*vmcommon.ContractCallInput, error) {
runtime := host.Runtime()
Expand Down
34 changes: 25 additions & 9 deletions arwen/host/execution.go
Original file line number Diff line number Diff line change
Expand Up @@ -656,8 +656,12 @@ func (host *vmHost) callSCMethodIndirect() error {
}

// ExecuteESDTTransfer calls the process built in function with the given transfer for ESDT/ESDTNFT if nonce > 0
// there are no NFTs with nonce == 0
func (host *vmHost) ExecuteESDTTransfer(destination []byte, sender []byte, tokenIdentifier []byte, nonce uint64, value *big.Int, callType vmcommon.CallType) (*vmcommon.VMOutput, uint64, error) {
// there are no NFTs with nonce == 0, it will call multi transfer if multiple tokens are sent
func (host *vmHost) ExecuteESDTTransfer(destination []byte, sender []byte, transfers []*vmcommon.ESDTTransfer, callType vmcommon.CallType) (*vmcommon.VMOutput, uint64, error) {
if len(transfers) == 0 {
return nil, 0, arwen.ErrFailedTransfer
}

_, _, metering, _, runtime, _ := host.GetContexts()

esdtTransferInput := &vmcommon.ContractCallInput{
Expand All @@ -675,18 +679,30 @@ func (host *vmHost) ExecuteESDTTransfer(destination []byte, sender []byte, token
AllowInitFunction: false,
}

if nonce > 0 {
esdtTransferInput.Function = vmcommon.BuiltInFunctionESDTNFTTransfer
esdtTransferInput.RecipientAddr = esdtTransferInput.CallerAddr
nonceAsBytes := big.NewInt(0).SetUint64(nonce).Bytes()
esdtTransferInput.Arguments = append(esdtTransferInput.Arguments, tokenIdentifier, nonceAsBytes, value.Bytes(), destination)
if len(transfers) == 1 {
if transfers[0].ESDTTokenNonce > 0 {
esdtTransferInput.Function = vmcommon.BuiltInFunctionESDTNFTTransfer
esdtTransferInput.RecipientAddr = esdtTransferInput.CallerAddr
nonceAsBytes := big.NewInt(0).SetUint64(transfers[0].ESDTTokenNonce).Bytes()
esdtTransferInput.Arguments = append(esdtTransferInput.Arguments, transfers[0].ESDTTokenName, nonceAsBytes, transfers[0].ESDTValue.Bytes(), destination)
} else {
esdtTransferInput.Arguments = append(esdtTransferInput.Arguments, transfers[0].ESDTTokenName, transfers[0].ESDTValue.Bytes())
}
} else {
esdtTransferInput.Arguments = append(esdtTransferInput.Arguments, tokenIdentifier, value.Bytes())
esdtTransferInput.Function = vmcommon.BuiltInFunctionMultiESDTNFTTransfer
esdtTransferInput.RecipientAddr = esdtTransferInput.CallerAddr
esdtTransferInput.Arguments = append(esdtTransferInput.Arguments, destination, big.NewInt(int64(len(transfers))).Bytes())
for _, transfer := range transfers {
nonceAsBytes := big.NewInt(0).SetUint64(transfer.ESDTTokenNonce).Bytes()
esdtTransferInput.Arguments = append(esdtTransferInput.Arguments, transfer.ESDTTokenName, nonceAsBytes, transfer.ESDTValue.Bytes())
}
}

vmOutput, err := host.Blockchain().ProcessBuiltInFunction(esdtTransferInput)
log.Trace("ESDT transfer", "sender", sender, "dest", destination)
log.Trace("ESDT transfer", "token", tokenIdentifier, "value", value)
for _, transfer := range transfers {
log.Trace("ESDT transfer", "token", transfer.ESDTTokenName, "nonce", transfer.ESDTTokenNonce, "value", transfer.ESDTValue)
}
if err != nil {
log.Trace("ESDT transfer", "error", err)
return vmOutput, esdtTransferInput.GasProvided, err
Expand Down
4 changes: 2 additions & 2 deletions arwen/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ type VMHost interface {
IsArwenV3Enabled() bool
IsESDTFunctionsEnabled() bool

ExecuteESDTTransfer(destination []byte, sender []byte, tokenIdentifier []byte, nonce uint64, value *big.Int, callType vmcommon.CallType) (*vmcommon.VMOutput, uint64, error)
ExecuteESDTTransfer(destination []byte, sender []byte, esdtTransfers []*vmcommon.ESDTTransfer, callType vmcommon.CallType) (*vmcommon.VMOutput, uint64, error)
CreateNewContract(input *vmcommon.ContractCreateInput) ([]byte, error)
ExecuteOnSameContext(input *vmcommon.ContractCallInput) (*AsyncContextInfo, error)
ExecuteOnDestContext(input *vmcommon.ContractCallInput) (*vmcommon.VMOutput, *AsyncContextInfo, error)
Expand Down Expand Up @@ -180,7 +180,7 @@ type OutputContext interface {
WriteLog(address []byte, topics [][]byte, data []byte)
TransferValueOnly(destination []byte, sender []byte, value *big.Int, checkPayable bool) error
Transfer(destination []byte, sender []byte, gasLimit uint64, gasLocked uint64, value *big.Int, input []byte, callType vmcommon.CallType) error
TransferESDT(destination []byte, sender []byte, tokenIdentifier []byte, nonce uint64, value *big.Int, callInput *vmcommon.ContractCallInput) (uint64, error)
TransferESDT(destination []byte, sender []byte, transfers []*vmcommon.ESDTTransfer, callInput *vmcommon.ContractCallInput) (uint64, error)
SelfDestruct(address []byte, beneficiary []byte)
GetRefund() uint64
SetRefund(refund uint64)
Expand Down
2 changes: 1 addition & 1 deletion mock/context/outputContextMock.go
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ func (o *OutputContextMock) Transfer(_ []byte, _ []byte, _ uint64, _ uint64, _ *
}

// TransferESDT mocked method
func (o *OutputContextMock) TransferESDT(_ []byte, _ []byte, _ []byte, _ uint64, _ *big.Int, _ *vmcommon.ContractCallInput) (uint64, error) {
func (o *OutputContextMock) TransferESDT(_ []byte, _ []byte, _ []*vmcommon.ESDTTransfer, _ *vmcommon.ContractCallInput) (uint64, error) {
return 0, nil
}

Expand Down
Loading

0 comments on commit 18697d3

Please sign in to comment.