From 906c409075372ba0f8c25bcea46a9cab379241e6 Mon Sep 17 00:00:00 2001 From: liuxincheng Date: Thu, 18 Jan 2024 21:56:31 +0800 Subject: [PATCH] feat(withdraw): support withdrawing rewards to specified address --- .../actuator/WithdrawBalanceToActuator.java | 171 ++++++++ .../org/tron/core/services/RpcApiService.java | 7 + .../services/http/FullNodeHttpApiService.java | 3 + .../http/HttpSelfFormatFieldName.java | 3 + .../http/WithdrawBalanceToServlet.java | 48 +++ .../core/services/jsonrpc/JsonRpcApiUtil.java | 3 + .../WithdrawBalanceToActuatorTest.java | 393 ++++++++++++++++++ protocol/src/main/protos/api/api.proto | 3 + protocol/src/main/protos/core/Tron.proto | 2 + .../core/contract/balance_contract.proto | 5 + 10 files changed, 638 insertions(+) create mode 100755 actuator/src/main/java/org/tron/core/actuator/WithdrawBalanceToActuator.java create mode 100644 framework/src/main/java/org/tron/core/services/http/WithdrawBalanceToServlet.java create mode 100644 framework/src/test/java/org/tron/core/actuator/WithdrawBalanceToActuatorTest.java diff --git a/actuator/src/main/java/org/tron/core/actuator/WithdrawBalanceToActuator.java b/actuator/src/main/java/org/tron/core/actuator/WithdrawBalanceToActuator.java new file mode 100755 index 00000000000..d934340fc6f --- /dev/null +++ b/actuator/src/main/java/org/tron/core/actuator/WithdrawBalanceToActuator.java @@ -0,0 +1,171 @@ +package org.tron.core.actuator; + +import static org.tron.core.actuator.ActuatorConstant.ACCOUNT_EXCEPTION_STR; +import static org.tron.core.actuator.ActuatorConstant.NOT_EXIST_STR; +import static org.tron.core.config.Parameter.ChainConstant.FROZEN_PERIOD; + +import com.google.common.math.LongMath; +import com.google.protobuf.ByteString; +import com.google.protobuf.InvalidProtocolBufferException; +import java.util.Arrays; +import java.util.Objects; +import lombok.extern.slf4j.Slf4j; +import org.tron.common.parameter.CommonParameter; +import org.tron.common.utils.DecodeUtil; +import org.tron.common.utils.StringUtil; +import org.tron.core.capsule.AccountCapsule; +import org.tron.core.capsule.TransactionResultCapsule; +import org.tron.core.exception.ContractExeException; +import org.tron.core.exception.ContractValidateException; +import org.tron.core.service.MortgageService; +import org.tron.core.store.AccountStore; +import org.tron.core.store.DynamicPropertiesStore; +import org.tron.protos.Protocol.Transaction.Contract.ContractType; +import org.tron.protos.Protocol.Transaction.Result.code; +import org.tron.protos.contract.BalanceContract.WithdrawBalanceToContract; + +@Slf4j(topic = "actuator") +public class WithdrawBalanceToActuator extends AbstractActuator { + + public WithdrawBalanceToActuator() { + super(ContractType.WithdrawBalanceToContract, WithdrawBalanceToContract.class); + } + + @Override + public boolean execute(Object result) throws ContractExeException { + TransactionResultCapsule ret = (TransactionResultCapsule) result; + if (Objects.isNull(ret)) { + throw new RuntimeException(ActuatorConstant.TX_RESULT_NULL); + } + + long fee = calcFee(); + final WithdrawBalanceToContract withdrawBalanceToContract; + AccountStore accountStore = chainBaseManager.getAccountStore(); + DynamicPropertiesStore dynamicStore = chainBaseManager.getDynamicPropertiesStore(); + MortgageService mortgageService = chainBaseManager.getMortgageService(); + try { + withdrawBalanceToContract = any.unpack(WithdrawBalanceToContract.class); + } catch (InvalidProtocolBufferException e) { + logger.debug(e.getMessage(), e); + ret.setStatus(fee, code.FAILED); + throw new ContractExeException(e.getMessage()); + } + + mortgageService.withdrawReward(withdrawBalanceToContract.getOwnerAddress() + .toByteArray()); + + AccountCapsule ownerCapsule = accountStore. + get(withdrawBalanceToContract.getOwnerAddress().toByteArray()); + + AccountCapsule receiverCapsule = accountStore. + get(withdrawBalanceToContract.getReceiverAddress().toByteArray()); + long receiverBalance = receiverCapsule.getBalance(); + long allowance = ownerCapsule.getAllowance(); + + long now = dynamicStore.getLatestBlockHeaderTimestamp(); + ownerCapsule.setInstance(ownerCapsule.getInstance().toBuilder() + .setAllowance(0L) + .setLatestWithdrawTime(now) + .build()); + receiverCapsule.setInstance(receiverCapsule.getInstance().toBuilder() + .setBalance(receiverBalance + allowance) + .setLatestWithdrawToTime(now) + .build()); + accountStore.put(ownerCapsule.createDbKey(), ownerCapsule); + accountStore.put(receiverCapsule.createDbKey(), receiverCapsule); + ret.setWithdrawAmount(allowance); + ret.setStatus(fee, code.SUCESS); + + return true; + } + + @Override + public boolean validate() throws ContractValidateException { + if (this.any == null) { + throw new ContractValidateException(ActuatorConstant.CONTRACT_NOT_EXIST); + } + if (chainBaseManager == null) { + throw new ContractValidateException(ActuatorConstant.STORE_NOT_EXIST); + } + AccountStore accountStore = chainBaseManager.getAccountStore(); + DynamicPropertiesStore dynamicStore = chainBaseManager.getDynamicPropertiesStore(); + MortgageService mortgageService = chainBaseManager.getMortgageService(); + if (!this.any.is(WithdrawBalanceToContract.class)) { + throw new ContractValidateException( + "contract type error, expected type [WithdrawBalanceToContract], real type[" + any + .getClass() + "]"); + } + final WithdrawBalanceToContract withdrawBalanceToContract; + try { + withdrawBalanceToContract = this.any.unpack(WithdrawBalanceToContract.class); + } catch (InvalidProtocolBufferException e) { + logger.debug(e.getMessage(), e); + throw new ContractValidateException(e.getMessage()); + } + byte[] ownerAddress = withdrawBalanceToContract.getOwnerAddress().toByteArray(); + if (!DecodeUtil.addressValid(ownerAddress)) { + throw new ContractValidateException("Invalid owner address"); + } + + AccountCapsule accountCapsule = accountStore.get(ownerAddress); + String readableOwnerAddress = StringUtil.createReadableString(ownerAddress); + if (accountCapsule == null) { + throw new ContractValidateException( + ACCOUNT_EXCEPTION_STR + readableOwnerAddress + NOT_EXIST_STR); + } + + byte[] receiverAddress = withdrawBalanceToContract.getReceiverAddress().toByteArray(); + if (!DecodeUtil.addressValid(receiverAddress)) { + throw new ContractValidateException("Invalid receiver address"); + } + + AccountCapsule receiverCapsule = accountStore.get(receiverAddress); + if (receiverCapsule == null) { + String readableReceiverAddress = StringUtil.createReadableString(receiverAddress); + throw new ContractValidateException( + ACCOUNT_EXCEPTION_STR + readableReceiverAddress + NOT_EXIST_STR); + } + + boolean isGP = CommonParameter.getInstance() + .getGenesisBlock().getWitnesses().stream().anyMatch(witness -> + Arrays.equals(ownerAddress, witness.getAddress())); + if (isGP) { + throw new ContractValidateException( + ACCOUNT_EXCEPTION_STR + readableOwnerAddress + + "] is a guard representative and is not allowed to withdraw Balance"); + } + + long latestWithdrawTime = accountCapsule.getLatestWithdrawTime(); + long now = dynamicStore.getLatestBlockHeaderTimestamp(); + long witnessAllowanceFrozenTime = dynamicStore.getWitnessAllowanceFrozenTime() * FROZEN_PERIOD; + + if (now - latestWithdrawTime < witnessAllowanceFrozenTime) { + throw new ContractValidateException("The last withdraw time is " + + latestWithdrawTime + ", less than 24 hours"); + } + + if (accountCapsule.getAllowance() <= 0 && + mortgageService.queryReward(ownerAddress) <= 0) { + throw new ContractValidateException("witnessAccount does not have any reward"); + } + try { + LongMath.checkedAdd(accountCapsule.getBalance(), accountCapsule.getAllowance()); + } catch (ArithmeticException e) { + logger.debug(e.getMessage(), e); + throw new ContractValidateException(e.getMessage()); + } + + return true; + } + + @Override + public ByteString getOwnerAddress() throws InvalidProtocolBufferException { + return any.unpack(WithdrawBalanceToContract.class).getOwnerAddress(); + } + + @Override + public long calcFee() { + return 0; + } + +} diff --git a/framework/src/main/java/org/tron/core/services/RpcApiService.java b/framework/src/main/java/org/tron/core/services/RpcApiService.java index c5077facf6f..a6722985f50 100755 --- a/framework/src/main/java/org/tron/core/services/RpcApiService.java +++ b/framework/src/main/java/org/tron/core/services/RpcApiService.java @@ -137,6 +137,7 @@ import org.tron.protos.contract.BalanceContract.UnDelegateResourceContract; import org.tron.protos.contract.BalanceContract.UnfreezeBalanceContract; import org.tron.protos.contract.BalanceContract.WithdrawBalanceContract; +import org.tron.protos.contract.BalanceContract.WithdrawBalanceToContract; import org.tron.protos.contract.BalanceContract.WithdrawExpireUnfreezeContract; import org.tron.protos.contract.ExchangeContract.ExchangeCreateContract; import org.tron.protos.contract.ExchangeContract.ExchangeInjectContract; @@ -1443,6 +1444,12 @@ public void withdrawBalance2(WithdrawBalanceContract request, createTransactionExtention(request, ContractType.WithdrawBalanceContract, responseObserver); } + @Override + public void withdrawBalanceTo(WithdrawBalanceToContract request, + StreamObserver responseObserver) { + createTransactionExtention(request, ContractType.WithdrawBalanceToContract, responseObserver); + } + @Override public void withdrawExpireUnfreeze(WithdrawExpireUnfreezeContract request, StreamObserver responseObserver) { diff --git a/framework/src/main/java/org/tron/core/services/http/FullNodeHttpApiService.java b/framework/src/main/java/org/tron/core/services/http/FullNodeHttpApiService.java index 55e6e07b5ec..d4a1312c7b8 100644 --- a/framework/src/main/java/org/tron/core/services/http/FullNodeHttpApiService.java +++ b/framework/src/main/java/org/tron/core/services/http/FullNodeHttpApiService.java @@ -55,6 +55,8 @@ public class FullNodeHttpApiService extends HttpService { @Autowired private WithdrawBalanceServlet withdrawBalanceServlet; @Autowired + private WithdrawBalanceToServlet withdrawBalanceToServlet; + @Autowired private UpdateAssetServlet updateAssetServlet; @Autowired private ListNodesServlet listNodesServlet; @@ -328,6 +330,7 @@ public void start() { context.addServlet(new ServletHolder(unFreezeBalanceServlet), "/wallet/unfreezebalance"); context.addServlet(new ServletHolder(unFreezeAssetServlet), "/wallet/unfreezeasset"); context.addServlet(new ServletHolder(withdrawBalanceServlet), "/wallet/withdrawbalance"); + context.addServlet(new ServletHolder(withdrawBalanceToServlet), "/wallet/withdrawbalanceTo"); context.addServlet(new ServletHolder(updateAssetServlet), "/wallet/updateasset"); context.addServlet(new ServletHolder(listNodesServlet), "/wallet/listnodes"); context.addServlet( diff --git a/framework/src/main/java/org/tron/core/services/http/HttpSelfFormatFieldName.java b/framework/src/main/java/org/tron/core/services/http/HttpSelfFormatFieldName.java index c551259fb3e..49410376573 100644 --- a/framework/src/main/java/org/tron/core/services/http/HttpSelfFormatFieldName.java +++ b/framework/src/main/java/org/tron/core/services/http/HttpSelfFormatFieldName.java @@ -98,6 +98,9 @@ public class HttpSelfFormatFieldName { AddressFieldNameMap.put("protocol.UnfreezeAssetContract.owner_address", 1); //WithdrawBalanceContract AddressFieldNameMap.put("protocol.WithdrawBalanceContract.owner_address", 1); + //WithdrawBalanceToContract + AddressFieldNameMap.put("protocol.WithdrawBalanceToContract.owner_address", 1); + AddressFieldNameMap.put("protocol.WithdrawBalanceToContract.receiver_address", 1); //UpdateAssetContract AddressFieldNameMap.put("protocol.UpdateAssetContract.owner_address", 1); //ProposalCreateContract diff --git a/framework/src/main/java/org/tron/core/services/http/WithdrawBalanceToServlet.java b/framework/src/main/java/org/tron/core/services/http/WithdrawBalanceToServlet.java new file mode 100644 index 00000000000..bf0c4280388 --- /dev/null +++ b/framework/src/main/java/org/tron/core/services/http/WithdrawBalanceToServlet.java @@ -0,0 +1,48 @@ +package org.tron.core.services.http; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; +import java.util.stream.Collectors; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.tron.core.Wallet; +import org.tron.protos.Protocol.Transaction; +import org.tron.protos.Protocol.Transaction.Contract.ContractType; +import org.tron.protos.contract.BalanceContract.WithdrawBalanceToContract; + + +@Component +@Slf4j(topic = "API") +public class WithdrawBalanceToServlet extends RateLimiterServlet { + + @Autowired + private Wallet wallet; + + @Override + protected void doGet(HttpServletRequest request, HttpServletResponse response) { + + } + + @Override + protected void doPost(HttpServletRequest request, HttpServletResponse response) { + try { + String contract = request.getReader().lines() + .collect(Collectors.joining(System.lineSeparator())); + Util.checkBodySize(contract); + boolean visible = Util.getVisiblePost(contract); + WithdrawBalanceToContract.Builder build = WithdrawBalanceToContract.newBuilder(); + JsonFormat.merge(contract, build, visible); + Transaction tx = wallet + .createTransactionCapsule(build.build(), ContractType.WithdrawBalanceToContract) + .getInstance(); + JSONObject jsonObject = JSON.parseObject(contract); + tx = Util.setTransactionPermissionId(jsonObject, tx); + response.getWriter().println(Util.printCreateTransaction(tx, visible)); + } catch (Exception e) { + Util.processError(e, response); + } + } +} diff --git a/framework/src/main/java/org/tron/core/services/jsonrpc/JsonRpcApiUtil.java b/framework/src/main/java/org/tron/core/services/jsonrpc/JsonRpcApiUtil.java index 4efc5994c0d..9d5135861b4 100644 --- a/framework/src/main/java/org/tron/core/services/jsonrpc/JsonRpcApiUtil.java +++ b/framework/src/main/java/org/tron/core/services/jsonrpc/JsonRpcApiUtil.java @@ -214,6 +214,7 @@ public static long getTransactionAmount(Transaction.Contract contract, String ha switch (contract.getType()) { case UnfreezeBalanceContract: case WithdrawBalanceContract: + case WithdrawBalanceToContract: case WithdrawExpireUnfreezeContract: case UnfreezeBalanceV2Contract: case CancelAllUnfreezeV2Contract: @@ -291,6 +292,7 @@ public static long getTransactionAmount(Transaction.Contract contract, String ha break; case UnfreezeBalanceContract: case WithdrawBalanceContract: + case WithdrawBalanceToContract: case WithdrawExpireUnfreezeContract: case UnfreezeBalanceV2Contract: case CancelAllUnfreezeV2Contract: @@ -322,6 +324,7 @@ public static long getAmountFromTransactionInfo(String hash, ContractType contra amount = transactionInfo.getUnfreezeAmount(); break; case WithdrawBalanceContract: + case WithdrawBalanceToContract: amount = transactionInfo.getWithdrawAmount(); break; case ExchangeInjectContract: diff --git a/framework/src/test/java/org/tron/core/actuator/WithdrawBalanceToActuatorTest.java b/framework/src/test/java/org/tron/core/actuator/WithdrawBalanceToActuatorTest.java new file mode 100644 index 00000000000..8f45c657ff4 --- /dev/null +++ b/framework/src/test/java/org/tron/core/actuator/WithdrawBalanceToActuatorTest.java @@ -0,0 +1,393 @@ +package org.tron.core.actuator; + +import static junit.framework.TestCase.fail; + +import com.google.protobuf.Any; +import com.google.protobuf.ByteString; +import lombok.extern.slf4j.Slf4j; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.tron.common.BaseTest; +import org.tron.common.args.Witness; +import org.tron.common.utils.ByteArray; +import org.tron.common.utils.StringUtil; +import org.tron.core.Constant; +import org.tron.core.Wallet; +import org.tron.core.capsule.AccountCapsule; +import org.tron.core.capsule.TransactionResultCapsule; +import org.tron.core.capsule.WitnessCapsule; +import org.tron.core.config.args.Args; +import org.tron.core.exception.BalanceInsufficientException; +import org.tron.core.exception.ContractExeException; +import org.tron.core.exception.ContractValidateException; +import org.tron.protos.Protocol; +import org.tron.protos.Protocol.AccountType; +import org.tron.protos.Protocol.Transaction.Result.code; +import org.tron.protos.contract.AssetIssueContractOuterClass; +import org.tron.protos.contract.BalanceContract.WithdrawBalanceToContract; + +@Slf4j +public class WithdrawBalanceToActuatorTest extends BaseTest { + + private static final String OWNER_ADDRESS; + private static final String OWNER_ADDRESS_INVALID = "aaaa"; + private static final String OWNER_ACCOUNT_INVALID; + private static final String RECEIVER_ADDRESS; + private static final String RECEIVER_ADDRESS_INVALID = "bbbb"; + private static final String RECEIVER_ACCOUNT_INVALID; + private static final long initBalance = 10_000_000_000L; + private static final long allowance = 32_000_000L; + + static { + Args.setParam(new String[]{"--output-directory", dbPath()}, Constant.TEST_CONF); + OWNER_ADDRESS = Wallet.getAddressPreFixString() + "548794500882809695a8a687866e76d4271a1abc"; + OWNER_ACCOUNT_INVALID = + Wallet.getAddressPreFixString() + "548794500882809695a8a687866e76d4271a3456"; + RECEIVER_ADDRESS = Wallet.getAddressPreFixString() + "abd4b9367799eaa3197fecb144eb71de1e049150"; + RECEIVER_ACCOUNT_INVALID = + Wallet.getAddressPreFixString() + "548794500882809695a8a687866e76d4271a3457"; + } + + /** + * create temp Capsule test need. + */ + @Before + public void createAccountCapsule() { + AccountCapsule ownerCapsule = new AccountCapsule(ByteString.copyFromUtf8("owner"), + ByteString.copyFrom(ByteArray.fromHexString(OWNER_ADDRESS)), AccountType.Normal, + initBalance); + dbManager.getAccountStore().put(ownerCapsule.createDbKey(), ownerCapsule); + + AccountCapsule receiverCapsule = + new AccountCapsule( + ByteString.copyFromUtf8("receiver"), + ByteString.copyFrom(ByteArray.fromHexString(RECEIVER_ADDRESS)), + Protocol.AccountType.Normal, + 0L); + dbManager.getAccountStore().put(receiverCapsule.getAddress().toByteArray(), receiverCapsule); + } + + private Any getContract(String ownerAddress, String receiverAddress) { + return Any.pack(WithdrawBalanceToContract.newBuilder() + .setOwnerAddress(ByteString.copyFrom(ByteArray.fromHexString(ownerAddress))) + .setReceiverAddress(ByteString.copyFrom(ByteArray.fromHexString(receiverAddress))).build()); + } + + @Test + public void testWithdrawBalanceTo() { + long now = System.currentTimeMillis(); + dbManager.getDynamicPropertiesStore().saveLatestBlockHeaderTimestamp(now); + byte[] address = ByteArray.fromHexString(OWNER_ADDRESS); + try { + dbManager.getMortgageService() + .adjustAllowance(dbManager.getAccountStore(), address, allowance); + } catch (BalanceInsufficientException e) { + fail("BalanceInsufficientException"); + } + AccountCapsule accountCapsule = dbManager.getAccountStore().get(address); + Assert.assertEquals(initBalance, accountCapsule.getBalance()); + Assert.assertEquals(allowance, accountCapsule.getAllowance()); + Assert.assertEquals(0, accountCapsule.getLatestWithdrawTime()); + + WitnessCapsule witnessCapsule = new WitnessCapsule(ByteString.copyFrom(address), 100, + "http://baidu.com"); + dbManager.getWitnessStore().put(address, witnessCapsule); + + WithdrawBalanceToActuator actuator = new WithdrawBalanceToActuator(); + actuator.setChainBaseManager(dbManager.getChainBaseManager()) + .setAny(getContract(OWNER_ADDRESS, RECEIVER_ADDRESS)); + + TransactionResultCapsule ret = new TransactionResultCapsule(); + + try { + actuator.validate(); + actuator.execute(ret); + Assert.assertEquals(code.SUCESS, ret.getInstance().getRet()); + AccountCapsule owner = dbManager.getAccountStore() + .get(ByteArray.fromHexString(OWNER_ADDRESS)); + AccountCapsule receiver = dbManager.getAccountStore() + .get(ByteArray.fromHexString(RECEIVER_ADDRESS)); + + Assert.assertEquals(initBalance, owner.getBalance()); + Assert.assertEquals(0, owner.getAllowance()); + Assert.assertEquals(allowance, receiver.getBalance()); + Assert.assertNotEquals(owner.getLatestWithdrawTime(), 0); + } catch (ContractValidateException | ContractExeException e) { + Assert.fail(); + } + } + + + @Test + public void invalidOwnerAddress() { + WithdrawBalanceToActuator actuator = new WithdrawBalanceToActuator(); + actuator.setChainBaseManager(dbManager.getChainBaseManager()) + .setAny(getContract(OWNER_ADDRESS_INVALID, RECEIVER_ADDRESS)); + + TransactionResultCapsule ret = new TransactionResultCapsule(); + + try { + actuator.validate(); + actuator.execute(ret); + fail("cannot run here."); + + } catch (ContractValidateException e) { + Assert.assertTrue(true); + + Assert.assertEquals("Invalid owner address", e.getMessage()); + + } catch (ContractExeException e) { + Assert.assertTrue(true); + } + + } + + @Test + public void invalidReceiverAddress() { + WithdrawBalanceToActuator actuator = new WithdrawBalanceToActuator(); + actuator.setChainBaseManager(dbManager.getChainBaseManager()) + .setAny(getContract(OWNER_ADDRESS, RECEIVER_ADDRESS_INVALID)); + + TransactionResultCapsule ret = new TransactionResultCapsule(); + + try { + actuator.validate(); + actuator.execute(ret); + fail("cannot run here."); + + } catch (ContractValidateException e) { + Assert.assertTrue(true); + + Assert.assertEquals("Invalid receiver address", e.getMessage()); + + } catch (ContractExeException e) { + Assert.assertTrue(true); + } + + } + + @Test + public void invalidOwnerAccount() { + WithdrawBalanceToActuator actuator = new WithdrawBalanceToActuator(); + actuator.setChainBaseManager(dbManager.getChainBaseManager()) + .setAny(getContract(OWNER_ACCOUNT_INVALID, RECEIVER_ADDRESS)); + + TransactionResultCapsule ret = new TransactionResultCapsule(); + + try { + actuator.validate(); + actuator.execute(ret); + fail("cannot run here."); + } catch (ContractValidateException e) { + Assert.assertTrue(true); + Assert.assertEquals("Account[" + OWNER_ACCOUNT_INVALID + "] not exists", e.getMessage()); + } catch (ContractExeException e) { + Assert.fail(); + } + } + + @Test + public void invalidReceiverAccount() { + WithdrawBalanceToActuator actuator = new WithdrawBalanceToActuator(); + actuator.setChainBaseManager(dbManager.getChainBaseManager()) + .setAny(getContract(OWNER_ADDRESS, RECEIVER_ACCOUNT_INVALID)); + + TransactionResultCapsule ret = new TransactionResultCapsule(); + + try { + actuator.validate(); + actuator.execute(ret); + fail("cannot run here."); + } catch (ContractValidateException e) { + Assert.assertTrue(true); + Assert.assertEquals("Account[" + RECEIVER_ACCOUNT_INVALID + "] not exists", e.getMessage()); + } catch (ContractExeException e) { + Assert.fail(); + } + } + + @Test + public void notWitness() { + WithdrawBalanceToActuator actuator = new WithdrawBalanceToActuator(); + actuator.setChainBaseManager(dbManager.getChainBaseManager()) + .setAny(getContract(OWNER_ADDRESS, RECEIVER_ADDRESS)); + + TransactionResultCapsule ret = new TransactionResultCapsule(); + + try { + actuator.validate(); + actuator.execute(ret); + fail("cannot run here."); + + } catch (ContractValidateException e) { + Assert.assertTrue(true); + } catch (ContractExeException e) { + Assert.fail(); + } + } + + @Test + public void noAllowance() { + long now = System.currentTimeMillis(); + dbManager.getDynamicPropertiesStore().saveLatestBlockHeaderTimestamp(now); + + byte[] address = ByteArray.fromHexString(OWNER_ADDRESS); + + AccountCapsule accountCapsule = dbManager.getAccountStore().get(address); + Assert.assertEquals(0, accountCapsule.getAllowance()); + + WitnessCapsule witnessCapsule = new WitnessCapsule(ByteString.copyFrom(address), 100, + "http://baidu.com"); + dbManager.getWitnessStore().put(address, witnessCapsule); + + WithdrawBalanceToActuator actuator = new WithdrawBalanceToActuator(); + actuator.setChainBaseManager(dbManager.getChainBaseManager()) + .setAny(getContract(OWNER_ADDRESS, RECEIVER_ADDRESS)); + + TransactionResultCapsule ret = new TransactionResultCapsule(); + + try { + actuator.validate(); + actuator.execute(ret); + fail("cannot run here."); + + } catch (ContractValidateException e) { + Assert.assertTrue(true); + Assert.assertEquals("witnessAccount does not have any reward", e.getMessage()); + } catch (ContractExeException e) { + Assert.fail(); + } + } + + @Test + public void isGR() { + Witness w = Args.getInstance().getGenesisBlock().getWitnesses().get(0); + byte[] address = w.getAddress(); + AccountCapsule grCapsule = new AccountCapsule(ByteString.copyFromUtf8("gr"), + ByteString.copyFrom(address), AccountType.Normal, initBalance); + dbManager.getAccountStore().put(grCapsule.createDbKey(), grCapsule); + long now = System.currentTimeMillis(); + dbManager.getDynamicPropertiesStore().saveLatestBlockHeaderTimestamp(now); + + try { + dbManager.getMortgageService() + .adjustAllowance(dbManager.getAccountStore(), address, allowance); + } catch (BalanceInsufficientException e) { + fail("BalanceInsufficientException"); + } + AccountCapsule accountCapsule = dbManager.getAccountStore().get(address); + Assert.assertEquals(allowance, accountCapsule.getAllowance()); + + WitnessCapsule witnessCapsule = new WitnessCapsule(ByteString.copyFrom(address), 100, + "http://google.com"); + + dbManager.getAccountStore().put(address, accountCapsule); + dbManager.getWitnessStore().put(address, witnessCapsule); + + WithdrawBalanceToActuator actuator = new WithdrawBalanceToActuator(); + actuator.setChainBaseManager(dbManager.getChainBaseManager()) + .setAny(getContract(ByteArray.toHexString(address), RECEIVER_ADDRESS)); + + TransactionResultCapsule ret = new TransactionResultCapsule(); + Assert.assertTrue(dbManager.getWitnessStore().has(address)); + + try { + actuator.validate(); + actuator.execute(ret); + fail("cannot run here."); + + } catch (ContractValidateException e) { + String readableOwnerAddress = StringUtil.createReadableString(address); + Assert.assertTrue(true); + Assert.assertEquals("Account[" + readableOwnerAddress + + "] is a guard representative and is not allowed to withdraw Balance", e.getMessage()); + } catch (ContractExeException e) { + Assert.fail(); + } + } + + @Test + public void notTimeToWithdraw() { + long now = System.currentTimeMillis(); + dbManager.getDynamicPropertiesStore().saveLatestBlockHeaderTimestamp(now); + + byte[] address = ByteArray.fromHexString(OWNER_ADDRESS); + try { + dbManager.getMortgageService() + .adjustAllowance(dbManager.getAccountStore(), address, allowance); + } catch (BalanceInsufficientException e) { + fail("BalanceInsufficientException"); + } + AccountCapsule accountCapsule = dbManager.getAccountStore().get(address); + accountCapsule.setLatestWithdrawTime(now); + Assert.assertEquals(allowance, accountCapsule.getAllowance()); + Assert.assertEquals(accountCapsule.getLatestWithdrawTime(), now); + + WitnessCapsule witnessCapsule = new WitnessCapsule(ByteString.copyFrom(address), 100, + "http://baidu.com"); + + dbManager.getAccountStore().put(address, accountCapsule); + dbManager.getWitnessStore().put(address, witnessCapsule); + + WithdrawBalanceToActuator actuator = new WithdrawBalanceToActuator(); + actuator.setChainBaseManager(dbManager.getChainBaseManager()) + .setAny(getContract(OWNER_ADDRESS, RECEIVER_ADDRESS)); + + TransactionResultCapsule ret = new TransactionResultCapsule(); + + try { + actuator.validate(); + actuator.execute(ret); + fail("cannot run here."); + + } catch (ContractValidateException e) { + Assert.assertTrue(true); + Assert + .assertEquals("The last withdraw time is " + now + ", less than 24 hours", + e.getMessage()); + } catch (ContractExeException e) { + Assert.fail(); + } + } + + @Test + public void commonErrorCheck() { + + WithdrawBalanceToActuator actuator = new WithdrawBalanceToActuator(); + ActuatorTest actuatorTest = new ActuatorTest(actuator, dbManager); + actuatorTest.noContract(); + + Any invalidContractTypes = Any.pack(AssetIssueContractOuterClass.AssetIssueContract.newBuilder() + .build()); + actuatorTest.setInvalidContract(invalidContractTypes); + actuatorTest.setInvalidContractTypeMsg("contract type error", + "contract type error, expected type [WithdrawBalanceToContract], real type["); + actuatorTest.invalidContractType(); + + long now = System.currentTimeMillis(); + dbManager.getDynamicPropertiesStore().saveLatestBlockHeaderTimestamp(now); + byte[] address = ByteArray.fromHexString(OWNER_ADDRESS); + try { + dbManager.getMortgageService() + .adjustAllowance(dbManager.getAccountStore(), address, allowance); + } catch (BalanceInsufficientException e) { + fail("BalanceInsufficientException"); + } + AccountCapsule accountCapsule = dbManager.getAccountStore().get(address); + Assert.assertEquals(allowance, accountCapsule.getAllowance()); + Assert.assertEquals(0, accountCapsule.getLatestWithdrawTime()); + + WitnessCapsule witnessCapsule = new WitnessCapsule(ByteString.copyFrom(address), 100, + "http://google.com"); + dbManager.getWitnessStore().put(address, witnessCapsule); + + actuatorTest.setContract(getContract(OWNER_ADDRESS, RECEIVER_ADDRESS)); + actuatorTest.nullTransationResult(); + + actuatorTest.setNullDBManagerMsg("No account store or dynamic store!"); + actuatorTest.nullDBManger(); + } + +} + diff --git a/protocol/src/main/protos/api/api.proto b/protocol/src/main/protos/api/api.proto index 2505fa48d6f..39855637a23 100644 --- a/protocol/src/main/protos/api/api.proto +++ b/protocol/src/main/protos/api/api.proto @@ -272,6 +272,9 @@ service Wallet { rpc WithdrawBalance2 (WithdrawBalanceContract) returns (TransactionExtention) { } + rpc WithdrawBalanceTo (WithdrawBalanceToContract) returns (TransactionExtention) { + } + rpc WithdrawExpireUnfreeze (WithdrawExpireUnfreezeContract) returns (TransactionExtention) { } diff --git a/protocol/src/main/protos/core/Tron.proto b/protocol/src/main/protos/core/Tron.proto index 41ef968d907..4cc945e80f3 100644 --- a/protocol/src/main/protos/core/Tron.proto +++ b/protocol/src/main/protos/core/Tron.proto @@ -238,6 +238,7 @@ message Account { int64 delegated_frozenV2_balance_for_bandwidth = 36; int64 acquired_delegated_frozenV2_balance_for_bandwidth = 37; + int64 latest_withdraw_to_time = 0x26; } message Key { @@ -377,6 +378,7 @@ message Transaction { DelegateResourceContract = 57; UnDelegateResourceContract = 58; CancelAllUnfreezeV2Contract = 59; + WithdrawBalanceToContract = 60; } ContractType type = 1; google.protobuf.Any parameter = 2; diff --git a/protocol/src/main/protos/core/contract/balance_contract.proto b/protocol/src/main/protos/core/contract/balance_contract.proto index ea1c96270d6..c57169a0f69 100644 --- a/protocol/src/main/protos/core/contract/balance_contract.proto +++ b/protocol/src/main/protos/core/contract/balance_contract.proto @@ -29,6 +29,11 @@ message WithdrawBalanceContract { bytes owner_address = 1; } +message WithdrawBalanceToContract { + bytes owner_address = 1; + bytes receiver_address = 2; +} + message TransferContract { bytes owner_address = 1; bytes to_address = 2;