Skip to content

Commit

Permalink
Re #253: Add transfer() and send() functionality to BlockchainRuntime
Browse files Browse the repository at this point in the history
Added the functionality transfer, similar to call, except with no data field.
Transfer is available to contract and to account.
Added tests to verify the transfer of balance (including zero and negatives to behave as expected).
Added a test that calls internal transaction for the transfer.
  • Loading branch information
aionWilliam committed Feb 5, 2019
1 parent 3650f5b commit a0931bc
Show file tree
Hide file tree
Showing 6 changed files with 243 additions and 0 deletions.
34 changes: 34 additions & 0 deletions org.aion.avm.core/src/org/aion/avm/core/BlockchainRuntimeImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,40 @@ public Result avm_call(Address targetAddress, org.aion.avm.shadow.java.math.BigI
return runInternalCall(internalTx);
}

@Override
public Result avm_transfer(Address targetAddress, org.aion.avm.shadow.java.math.BigInteger value, long energyLimit) throws IllegalArgumentException {
org.aion.vm.api.interfaces.Address internalSender = (ctx.getTransactionKind() == Type.CREATE.toInt()) ? ctx.getContractAddress() : ctx.getDestinationAddress();

java.math.BigInteger underlyingValue = value.getUnderlying();
require(targetAddress != null, "Destination can't be NULL");
require(underlyingValue.compareTo(java.math.BigInteger.ZERO) >= 0 , "Value can't be negative");
require(underlyingValue.compareTo(kernel.getBalance(internalSender)) <= 0, "Insufficient balance");
require(energyLimit >= 0, "Energy limit can't be negative");

if (ctx.getTransactionStackDepth() == 10) {
throw new CallDepthLimitExceededException("Internal call depth cannot be more than 10");
}

AvmAddress target = AvmAddress.wrap(targetAddress.unwrap());
if (!kernel.destinationAddressIsSafeForThisVM(target)) {
throw new IllegalArgumentException("Attempt to execute code using a foreign virtual machine");
}

// construct the internal transaction
InternalTransaction internalTx = new InternalTransaction(Transaction.Type.CALL,
internalSender,
target,
this.kernel.getNonce(internalSender),
underlyingValue,
new byte[0],
restrictEnergyLimit(energyLimit),
ctx.getTransactionEnergyPrice());

// Call the common run helper.
return runInternalCall(internalTx);
}


@Override
public Result avm_create(org.aion.avm.shadow.java.math.BigInteger value, ByteArray data, long energyLimit) {
org.aion.vm.api.interfaces.Address internalSender = (ctx.getTransactionKind() == Type.CREATE.toInt()) ? ctx.getContractAddress() : ctx.getDestinationAddress();
Expand Down
156 changes: 156 additions & 0 deletions org.aion.avm.core/test/org/aion/avm/core/TransferTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
package org.aion.avm.core;

import org.aion.avm.api.ABIEncoder;
import org.aion.avm.api.Address;
import org.aion.avm.core.bitcoin.Transaction;
import org.aion.avm.core.testHashes.HashTestTargetClass;
import org.aion.avm.core.util.AvmRule;
import org.aion.avm.core.util.HashUtils;
import org.aion.avm.core.util.Helpers;
import org.aion.avm.core.util.TestingHelper;
import org.aion.kernel.AvmTransactionResult;
import org.aion.kernel.KernelInterfaceImpl;
import org.aion.vm.api.interfaces.TransactionContext;
import org.aion.vm.api.interfaces.TransactionResult;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.spongycastle.util.encoders.Hex;

import java.math.BigInteger;

public class TransferTest {
@Rule
public AvmRule avmRule = new AvmRule(false);

private long energyLimit = 10_000_000L;
private long energyPrice = 1L;

private org.aion.vm.api.interfaces.Address deployer = KernelInterfaceImpl.PREMINED_ADDRESS;
private org.aion.vm.api.interfaces.Address dappAddress;

private final String methodName = "doTransfer";
private final long dappInitialBalance = 10000;

@Before
public void setup() {
byte[] txData = avmRule.getDappBytes(TransferTestTarget.class, null);
dappAddress = avmRule.deploy(deployer, BigInteger.ZERO, txData, energyLimit, energyPrice).getDappAddress();

//send some balance to the dapp
avmRule.kernel.adjustBalance(dappAddress, BigInteger.valueOf(dappInitialBalance));
}

@Test
public void testTransferNormal() {
long energyLimit = 5000000;
long transferBalance = 500;

// receiver address
org.aion.vm.api.interfaces.Address receiverAddress = Helpers.randomAddress();
avmRule.kernel.createAccount(receiverAddress);
byte[] to = receiverAddress.toBytes();

// Call contract
byte[] txData = ABIEncoder.encodeMethodArguments(methodName, to, transferBalance, energyLimit);
TransactionResult txResult = avmRule.call(deployer, dappAddress, BigInteger.ZERO, txData, this.energyLimit, this.energyPrice).getTransactionResult();
Assert.assertEquals(AvmTransactionResult.Code.SUCCESS, txResult.getResultCode());

// check balance
BigInteger contractBalance = avmRule.kernel.getBalance(dappAddress);
System.out.println("contract balance is: " + contractBalance);
Assert.assertEquals(dappInitialBalance - transferBalance, contractBalance.intValue());

BigInteger receiverBalance = avmRule.kernel.getBalance(receiverAddress);
System.out.println("receiver balance is: " + receiverBalance);
Assert.assertEquals(transferBalance, receiverBalance.intValue());
}

@Test
public void testTransferZero() {
long energyLimit = 5000000;
long transferBalance = 0;

// receiver address
org.aion.vm.api.interfaces.Address receiverAddress = Helpers.randomAddress();
avmRule.kernel.createAccount(receiverAddress);
byte[] to = receiverAddress.toBytes();

// Call contract
byte[] txData = ABIEncoder.encodeMethodArguments(methodName, to, transferBalance, energyLimit);
TransactionResult txResult = avmRule.call(deployer, dappAddress, BigInteger.ZERO, txData, this.energyLimit, this.energyPrice).getTransactionResult();
Assert.assertEquals(AvmTransactionResult.Code.SUCCESS, txResult.getResultCode());

// check balance
BigInteger contractBalance = avmRule.kernel.getBalance(dappAddress);
System.out.println("contract balance is: " + contractBalance);
Assert.assertEquals(dappInitialBalance - transferBalance, contractBalance.intValue());

BigInteger receiverBalance = avmRule.kernel.getBalance(receiverAddress);
System.out.println("receiver balance is: " + receiverBalance);
Assert.assertEquals(transferBalance, receiverBalance.intValue());
}

@Test
public void testTransferNegative() {
long energyLimit = 5000000;
long transferBalance = -10;

// receiver address
org.aion.vm.api.interfaces.Address receiverAddress = Helpers.randomAddress();
avmRule.kernel.createAccount(receiverAddress);
byte[] to = receiverAddress.toBytes();

// Call contract
byte[] txData = ABIEncoder.encodeMethodArguments(methodName, to, transferBalance, energyLimit);
TransactionResult txResult = avmRule.call(deployer, dappAddress, BigInteger.ZERO, txData, this.energyLimit, this.energyPrice).getTransactionResult();
Assert.assertEquals(AvmTransactionResult.Code.FAILED_EXCEPTION, txResult.getResultCode());

// check balance
BigInteger contractBalance = avmRule.kernel.getBalance(dappAddress);
System.out.println("contract balance is: " + contractBalance);
Assert.assertEquals(dappInitialBalance, contractBalance.intValue());

BigInteger receiverBalance = avmRule.kernel.getBalance(receiverAddress);
System.out.println("receiver balance is: " + receiverBalance);
Assert.assertEquals(0, receiverBalance.intValue());
}

@Test
public void testTransferInternal() {
// call a dapp that calls another dapp to transfer value
long energyLimit = 5000000;
long transferBalance = 1000;
String methodNameOuter = "callAnotherDappToTransfer";

// deploy 2nd dapp
org.aion.vm.api.interfaces.Address dapp2Address;
byte[] deployData = avmRule.getDappBytes(TransferTestTarget.class, null);
dapp2Address = avmRule.deploy(deployer, BigInteger.ZERO, deployData, energyLimit, energyPrice).getDappAddress();
avmRule.kernel.adjustBalance(dapp2Address, BigInteger.valueOf(dappInitialBalance));

// generate receiver address
org.aion.vm.api.interfaces.Address receiverAddress = Helpers.randomAddress();
avmRule.kernel.createAccount(receiverAddress);

// Call contract
byte[] txData2 = ABIEncoder.encodeMethodArguments(methodName, receiverAddress.toBytes(), transferBalance, energyLimit);
byte[] txData = ABIEncoder.encodeMethodArguments(methodNameOuter, dapp2Address.toBytes(), txData2, energyLimit);

TransactionResult txResult = avmRule.call(deployer, dappAddress, BigInteger.ZERO, txData, this.energyLimit, this.energyPrice).getTransactionResult();
Assert.assertEquals(AvmTransactionResult.Code.SUCCESS, txResult.getResultCode());

// check balance
BigInteger contractBalance = avmRule.kernel.getBalance(dappAddress);
BigInteger contract2Balance = avmRule.kernel.getBalance(dapp2Address);
BigInteger receiverBalance = avmRule.kernel.getBalance(receiverAddress);

System.out.println("contract balance is: " + contractBalance);
Assert.assertEquals(dappInitialBalance, contractBalance.intValue());
System.out.println("contract2 balance is: " + contract2Balance);
Assert.assertEquals(dappInitialBalance - transferBalance, contract2Balance.intValue());
System.out.println("receiver balance is: " + receiverBalance);
Assert.assertEquals(transferBalance, receiverBalance.intValue());
}
}
36 changes: 36 additions & 0 deletions org.aion.avm.core/test/org/aion/avm/core/TransferTestTarget.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package org.aion.avm.core;

import org.aion.avm.api.ABIDecoder;
import org.aion.avm.api.Address;
import org.aion.avm.api.BlockchainRuntime;

import java.math.BigInteger;

public class TransferTestTarget {

public void doTransfer(byte[] to, long balance, long energyLimit){
Address receiverAddress = new Address(to);
BlockchainRuntime.transfer(receiverAddress, BigInteger.valueOf(balance), energyLimit);
}

public void callAnotherDappToTransfer(byte[] to, byte[] args, long energyLimit){
Address receiverAddress = new Address(to);
BlockchainRuntime.call(receiverAddress, BigInteger.ZERO, args, energyLimit);
}

private static org.aion.avm.core.TransferTestTarget testTarget;

/**
* Initialization code executed once at the Dapp deployment.
*/
static {
testTarget = new org.aion.avm.core.TransferTestTarget();
}

/**
* Entry point at a transaction call.
*/
public static byte[] main() {
return ABIDecoder.decodeAndRunWithObject(testTarget, BlockchainRuntime.getData());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,12 @@ public Result avm_call(Address targetAddress, BigInteger value, ByteArray payloa
return new Result(true, payload);
}

@Override
public Result avm_transfer(Address targetAddress, BigInteger value, long energyLimit) throws IllegalArgumentException {
// We will just bounce back the input, so that the caller can see "something".
return new Result(true, new ByteArray(value.avm_intValue()));
}

@Override
public Result avm_create(BigInteger value, ByteArray data, long energyLimit) {
return new Result(true, null);
Expand Down
9 changes: 9 additions & 0 deletions org.aion.avm.rt/src/org/aion/avm/api/BlockchainRuntime.java
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,11 @@ public static Result avm_call(Address targetAddress, BigInteger value, ByteArray
return blockchainRuntime.avm_call(targetAddress, value, data, energyLimit);
}

public static Result avm_transfer(Address targetAddress, BigInteger value, long energyLimit) {
IInstrumentation.attachedThreadInstrumentation.get().chargeEnergy(RuntimeMethodFeeSchedule.BlockchainRuntime_avm_call);
return blockchainRuntime.avm_transfer(targetAddress, value, energyLimit);
}

public static Result avm_create(BigInteger value, ByteArray data, long energyLimit) {
IInstrumentation.attachedThreadInstrumentation.get().chargeEnergy(RuntimeMethodFeeSchedule.BlockchainRuntime_avm_create);
return blockchainRuntime.avm_create(value, data, energyLimit);
Expand Down Expand Up @@ -257,6 +262,10 @@ public static Result call(Address targetAddress, java.math.BigInteger value, byt
return avm_call(targetAddress, new BigInteger(value), new ByteArray(data), energyLimit);
}

public static Result transfer(Address targetAddress, java.math.BigInteger value, long energyLimit) {
return avm_transfer(targetAddress, new BigInteger(value), energyLimit);
}

public static Result create(java.math.BigInteger value, byte[] data, long energyLimit) {
return avm_create(new BigInteger(value), new ByteArray(data), energyLimit);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,8 @@ public interface IBlockchainRuntime {
*/
Result avm_call(Address targetAddress, BigInteger value, ByteArray data, long energyLimit) throws IllegalArgumentException;

Result avm_transfer(Address targetAddress, BigInteger value, long energyLimit) throws IllegalArgumentException;

Result avm_create(BigInteger value, ByteArray data, long energyLimit) throws IllegalArgumentException;

/**
Expand Down

0 comments on commit a0931bc

Please sign in to comment.