diff --git a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/CreateAccountAcceptanceTest.java b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/CreateAccountAcceptanceTest.java index c38f81d59a..514fd220d5 100644 --- a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/CreateAccountAcceptanceTest.java +++ b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/CreateAccountAcceptanceTest.java @@ -12,10 +12,9 @@ */ package tech.pegasys.pantheon.tests.acceptance; -import static org.web3j.utils.Convert.Unit.ETHER; - import tech.pegasys.pantheon.tests.acceptance.dsl.AcceptanceTestBase; import tech.pegasys.pantheon.tests.acceptance.dsl.account.Account; +import tech.pegasys.pantheon.tests.acceptance.dsl.blockchain.Amount; import tech.pegasys.pantheon.tests.acceptance.dsl.node.Node; import org.junit.Before; @@ -27,14 +26,17 @@ public class CreateAccountAcceptanceTest extends AcceptanceTestBase { @Before public void setUp() throws Exception { - minerNode = pantheon.createMinerNode("node1"); - cluster.start(minerNode, pantheon.createArchiveNode("node2")); + minerNode = pantheon.createMinerNode("minerNode"); + cluster.start(minerNode, pantheon.createArchiveNode("archiveNode")); } @Test public void shouldCreateAnAccount() { - final Account account = accounts.createAccount("a-new-account"); - minerNode.execute(transactions.createTransfer(account, 20)); - cluster.verify(account.balanceEquals("20", ETHER)); + final Account account = accounts.createAccount("account-one"); + final Amount balance = Amount.ether(20); + + minerNode.execute(transactions.createTransfer(account, balance)); + + cluster.verify(account.balanceEquals(balance)); } } diff --git a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/account/Account.java b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/account/Account.java index 1fb54b020e..2ab5293a62 100644 --- a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/account/Account.java +++ b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/account/Account.java @@ -16,6 +16,7 @@ import tech.pegasys.pantheon.crypto.SECP256K1.PrivateKey; import tech.pegasys.pantheon.ethereum.core.Address; import tech.pegasys.pantheon.ethereum.core.Hash; +import tech.pegasys.pantheon.tests.acceptance.dsl.blockchain.Amount; import tech.pegasys.pantheon.tests.acceptance.dsl.condition.Condition; import tech.pegasys.pantheon.tests.acceptance.dsl.condition.account.ExpectAccountBalance; import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.eth.EthTransactions; @@ -69,4 +70,9 @@ public Condition balanceEquals(final String expectedBalance, final Unit balanceU public Condition balanceEquals(final int expectedBalance) { return balanceEquals(String.valueOf(expectedBalance), Unit.ETHER); } + + public Condition balanceEquals(final Amount expectedBalance) { + return new ExpectAccountBalance( + eth, this, expectedBalance.getValue(), expectedBalance.getUnit()); + } } diff --git a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/blockchain/Amount.java b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/blockchain/Amount.java new file mode 100644 index 0000000000..47be6e08b6 --- /dev/null +++ b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/blockchain/Amount.java @@ -0,0 +1,72 @@ +/* + * Copyright 2019 ConsenSys AG. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package tech.pegasys.pantheon.tests.acceptance.dsl.blockchain; + +import static org.web3j.utils.Convert.Unit.ETHER; +import static org.web3j.utils.Convert.Unit.WEI; + +import java.math.BigInteger; + +import org.web3j.utils.Convert; +import org.web3j.utils.Convert.Unit; + +public class Amount { + + private String value; + private Unit unit; + + private Amount(final long value, final Unit unit) { + this.value = String.valueOf(value); + this.unit = unit; + } + + private Amount(final BigInteger value, final Unit unit) { + this.value = value.toString(); + this.unit = unit; + } + + public String getValue() { + return value; + } + + public Unit getUnit() { + return unit; + } + + public Amount subtract(final Amount subtracting) { + + final Unit denominator; + if (unit.getWeiFactor().compareTo(subtracting.unit.getWeiFactor()) == -1) { + denominator = unit; + } else { + denominator = subtracting.unit; + } + + final BigInteger result = + Convert.fromWei( + Convert.toWei(value, unit) + .subtract(Convert.toWei(subtracting.value, subtracting.unit)), + denominator) + .toBigInteger(); + + return new Amount(result, denominator); + } + + public static Amount ether(final long value) { + return new Amount(value, ETHER); + } + + public static Amount wei(final BigInteger value) { + return new Amount(value, WEI); + } +} diff --git a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/transaction/Transactions.java b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/transaction/Transactions.java index 01533a9e29..aa273dfc3c 100644 --- a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/transaction/Transactions.java +++ b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/transaction/Transactions.java @@ -14,7 +14,9 @@ import tech.pegasys.pantheon.tests.acceptance.dsl.account.Account; import tech.pegasys.pantheon.tests.acceptance.dsl.account.Accounts; +import tech.pegasys.pantheon.tests.acceptance.dsl.blockchain.Amount; import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.account.TransferTransaction; +import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.account.TransferTransactionBuilder; import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.account.TransferTransactionSet; import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.eea.EeaGetTransactionReceiptTransaction; import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.eea.EeaSendRawTransactionTransaction; @@ -33,7 +35,6 @@ import java.util.List; import org.web3j.tx.Contract; -import org.web3j.utils.Convert.Unit; public class Transactions { @@ -47,14 +48,46 @@ public TransferTransaction createTransfer(final Account recipient, final int amo return createTransfer(accounts.getPrimaryBenefactor(), recipient, amount); } + public TransferTransaction createTransfer(final Account recipient, final Amount amount) { + return createTransfer(accounts.getPrimaryBenefactor(), recipient, amount); + } + public TransferTransaction createTransfer( final Account sender, final Account recipient, final int amount) { - return new TransferTransaction(sender, recipient, String.valueOf(amount), Unit.ETHER); + return new TransferTransactionBuilder() + .sender(sender) + .recipient(recipient) + .amount(Amount.ether(amount)) + .build(); + } + + public TransferTransaction createTransfer( + final Account sender, final Account recipient, final Amount amount, final Amount gasPrice) { + return new TransferTransactionBuilder() + .sender(sender) + .recipient(recipient) + .amount(amount) + .gasPrice(gasPrice) + .build(); + } + + public TransferTransaction createTransfer( + final Account sender, final Account recipient, final Amount amount) { + return new TransferTransactionBuilder() + .sender(sender) + .recipient(recipient) + .amount(amount) + .build(); } public TransferTransaction createTransfer( final Account sender, final Account recipient, final int amount, final BigInteger nonce) { - return new TransferTransaction(sender, recipient, String.valueOf(amount), Unit.ETHER, nonce); + return new TransferTransactionBuilder() + .sender(sender) + .recipient(recipient) + .amount(Amount.ether(amount)) + .nonce(nonce) + .build(); } public EeaSendRawTransactionTransaction createPrivateRawTransaction( @@ -65,9 +98,14 @@ public EeaSendRawTransactionTransaction createPrivateRawTransaction( public TransferTransactionSet createIncrementalTransfers( final Account sender, final Account recipient, final int etherAmount) { final List transfers = new ArrayList<>(); + final TransferTransactionBuilder transferOneEther = + new TransferTransactionBuilder() + .sender(sender) + .recipient(recipient) + .amount(Amount.ether(1)); for (int i = 1; i <= etherAmount; i++) { - transfers.add(new TransferTransaction(sender, recipient, "1", Unit.ETHER)); + transfers.add(transferOneEther.build()); } return new TransferTransactionSet(transfers); diff --git a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/transaction/account/TransferTransaction.java b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/transaction/account/TransferTransaction.java index 191b226e4d..12d0d4c2b8 100644 --- a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/transaction/account/TransferTransaction.java +++ b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/transaction/account/TransferTransaction.java @@ -16,6 +16,7 @@ import tech.pegasys.pantheon.ethereum.core.Hash; import tech.pegasys.pantheon.tests.acceptance.dsl.account.Account; +import tech.pegasys.pantheon.tests.acceptance.dsl.blockchain.Amount; import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.JsonRequestFactories; import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.Transaction; @@ -30,35 +31,31 @@ public class TransferTransaction implements Transaction { + /** Price for each for each GAS units in this transaction (wei). */ private static final BigInteger MINIMUM_GAS_PRICE = BigInteger.valueOf(1000); - private static final BigInteger TRANSFER_GAS_COST = BigInteger.valueOf(21000); + + /** Number of GAS units that the transaction will cost. */ + private static final BigInteger INTRINSIC_GAS = BigInteger.valueOf(21000); private final Account sender; private final Account recipient; - private final String amount; - private final Unit unit; - private final Optional nonce; - - public TransferTransaction( - final Account sender, final Account recipient, final String amount, final Unit unit) { - this(sender, recipient, amount, unit, null); - } + private final String transferAmount; + private final Unit transferUnit; + private final BigInteger gasPrice; + private final BigInteger nonce; public TransferTransaction( final Account sender, final Account recipient, - final String amount, - final Unit unit, + final Amount transferAmount, + final Amount gasPrice, final BigInteger nonce) { this.sender = sender; this.recipient = recipient; - this.amount = amount; - this.unit = unit; - if (nonce != null) { - this.nonce = Optional.of(nonce); - } else { - this.nonce = Optional.empty(); - } + this.transferAmount = transferAmount.getValue(); + this.transferUnit = transferAmount.getUnit(); + this.gasPrice = gasPrice == null ? MINIMUM_GAS_PRICE : convertGasPriceToWei(gasPrice); + this.nonce = nonce; } @Override @@ -72,15 +69,39 @@ public Hash execute(final JsonRequestFactories node) { } } + public Amount executionCost() { + return Amount.wei(INTRINSIC_GAS.multiply(gasPrice)); + } + public String signedTransactionData() { + final Optional nonce = getNonce(); + final RawTransaction transaction = RawTransaction.createEtherTransaction( nonce.orElse(nonce.orElseGet(sender::getNextNonce)), - MINIMUM_GAS_PRICE, - TRANSFER_GAS_COST, + gasPrice, + INTRINSIC_GAS, recipient.getAddress(), - Convert.toWei(amount, unit).toBigIntegerExact()); + Convert.toWei(transferAmount, transferUnit).toBigIntegerExact()); return toHexString(TransactionEncoder.signMessage(transaction, sender.web3jCredentials())); } + + private Optional getNonce() { + return nonce == null ? Optional.empty() : Optional.of(nonce); + } + + private BigInteger convertGasPriceToWei(final Amount unconverted) { + final BigInteger price = + Convert.toWei(unconverted.getValue(), unconverted.getUnit()).toBigInteger(); + + if (MINIMUM_GAS_PRICE.compareTo(price) == 1) { + throw new IllegalArgumentException( + String.format( + "Gas price: %s WEI, is below the accepted minimum: %s WEI", + price, MINIMUM_GAS_PRICE)); + } + + return price; + } } diff --git a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/transaction/account/TransferTransactionBuilder.java b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/transaction/account/TransferTransactionBuilder.java new file mode 100644 index 0000000000..2c2984554a --- /dev/null +++ b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/transaction/account/TransferTransactionBuilder.java @@ -0,0 +1,72 @@ +/* + * Copyright 2019 ConsenSys AG. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package tech.pegasys.pantheon.tests.acceptance.dsl.transaction.account; + +import tech.pegasys.pantheon.tests.acceptance.dsl.account.Account; +import tech.pegasys.pantheon.tests.acceptance.dsl.blockchain.Amount; + +import java.math.BigInteger; + +public class TransferTransactionBuilder { + + private Account sender; + private Account recipient; + private Amount transferAmount; + private Amount gasPrice; + private BigInteger nonce; + + public TransferTransactionBuilder sender(final Account sender) { + this.sender = sender; + validateSender(); + return this; + } + + public TransferTransactionBuilder recipient(final Account recipient) { + this.recipient = recipient; + return this; + } + + public TransferTransactionBuilder amount(final Amount transferAmount) { + this.transferAmount = transferAmount; + validateTransferAmount(); + return this; + } + + public TransferTransactionBuilder nonce(final BigInteger nonce) { + this.nonce = nonce; + return this; + } + + public TransferTransactionBuilder gasPrice(final Amount gasPrice) { + this.gasPrice = gasPrice; + return this; + } + + public TransferTransaction build() { + validateSender(); + validateTransferAmount(); + return new TransferTransaction(sender, recipient, transferAmount, gasPrice, nonce); + } + + private void validateSender() { + if (sender == null) { + throw new IllegalArgumentException("NULL sender is not allowed."); + } + } + + private void validateTransferAmount() { + if (transferAmount == null) { + throw new IllegalArgumentException("NULL transferAmount is not allowed."); + } + } +}