From 36054e7ac5c22c8320a0b6aa297eb9a9924d4c13 Mon Sep 17 00:00:00 2001
From: liuxincheng <lxcmyf@qq.com>
Date: Fri, 2 Jun 2023 10:49:06 +0800
Subject: [PATCH] feat(freezeV2): optimize delegate resource lock period

---
 .../actuator/DelegateResourceActuator.java    |  70 +++-
 .../org/tron/core/utils/ProposalUtil.java     |  19 +-
 .../org/tron/core/utils/TransactionUtil.java  |   7 +-
 .../core/store/DynamicPropertiesStore.java    |  31 +-
 .../common/parameter/CommonParameter.java     |   4 +
 .../java/org/tron/core/config/Parameter.java  |   1 +
 .../src/main/java/org/tron/core/Wallet.java   |   5 +
 .../tron/core/consensus/ProposalService.java  |   7 +
 .../http/DelegateResourceServlet.java         |   4 +-
 .../test/java/org/tron/core/WalletTest.java   |  17 +-
 .../DelegateResourceActuatorTest.java         | 383 ++++++++++++++----
 .../actuator/utils/TransactionUtilTest.java   | 119 ++++--
 .../core/contract/balance_contract.proto      |   1 +
 13 files changed, 511 insertions(+), 157 deletions(-)

diff --git a/actuator/src/main/java/org/tron/core/actuator/DelegateResourceActuator.java b/actuator/src/main/java/org/tron/core/actuator/DelegateResourceActuator.java
index f91c147e3ee..d7dfcd5da14 100755
--- a/actuator/src/main/java/org/tron/core/actuator/DelegateResourceActuator.java
+++ b/actuator/src/main/java/org/tron/core/actuator/DelegateResourceActuator.java
@@ -2,7 +2,11 @@
 
 import static org.tron.core.actuator.ActuatorConstant.NOT_EXIST_STR;
 import static org.tron.core.config.Parameter.ChainConstant.DELEGATE_PERIOD;
+import static org.tron.core.config.Parameter.ChainConstant.MAX_BLOCK_NUM_DELEGATE_PERIOD;
 import static org.tron.core.config.Parameter.ChainConstant.TRX_PRECISION;
+import static org.tron.protos.contract.Common.ResourceCode;
+import static org.tron.protos.contract.Common.ResourceCode.BANDWIDTH;
+import static org.tron.protos.contract.Common.ResourceCode.ENERGY;
 
 import com.google.protobuf.ByteString;
 import com.google.protobuf.InvalidProtocolBufferException;
@@ -46,8 +50,10 @@ public boolean execute(Object result) throws ContractExeException {
     long fee = calcFee();
     final DelegateResourceContract delegateResourceContract;
     AccountStore accountStore = chainBaseManager.getAccountStore();
+    byte[] ownerAddress;
     try {
-      delegateResourceContract = any.unpack(DelegateResourceContract.class);
+      delegateResourceContract = this.any.unpack(DelegateResourceContract.class);
+      ownerAddress = getOwnerAddress().toByteArray();
     } catch (InvalidProtocolBufferException e) {
       logger.debug(e.getMessage(), e);
       ret.setStatus(fee, code.FAILED);
@@ -59,21 +65,21 @@ public boolean execute(Object result) throws ContractExeException {
 
     long delegateBalance = delegateResourceContract.getBalance();
     boolean lock = delegateResourceContract.getLock();
-    byte[] ownerAddress = delegateResourceContract.getOwnerAddress().toByteArray();
+    long lockPeriod = delegateResourceContract.getLockPeriod();
     byte[] receiverAddress = delegateResourceContract.getReceiverAddress().toByteArray();
 
     // delegate resource to receiver
     switch (delegateResourceContract.getResource()) {
       case BANDWIDTH:
         delegateResource(ownerAddress, receiverAddress, true,
-            delegateBalance, lock);
+            delegateBalance, lock, lockPeriod);
 
         ownerCapsule.addDelegatedFrozenV2BalanceForBandwidth(delegateBalance);
         ownerCapsule.addFrozenBalanceForBandwidthV2(-delegateBalance);
         break;
       case ENERGY:
         delegateResource(ownerAddress, receiverAddress, false,
-            delegateBalance, lock);
+            delegateBalance, lock, lockPeriod);
 
         ownerCapsule.addDelegatedFrozenV2BalanceForEnergy(delegateBalance);
         ownerCapsule.addFrozenBalanceForEnergyV2(-delegateBalance);
@@ -100,6 +106,7 @@ public boolean validate() throws ContractValidateException {
     }
     AccountStore accountStore = chainBaseManager.getAccountStore();
     DynamicPropertiesStore dynamicStore = chainBaseManager.getDynamicPropertiesStore();
+    DelegatedResourceStore delegatedResourceStore = chainBaseManager.getDelegatedResourceStore();
     if (!any.is(DelegateResourceContract.class)) {
       throw new ContractValidateException(
           "contract type error,expected type [DelegateResourceContract],real type["
@@ -116,13 +123,14 @@ public boolean validate() throws ContractValidateException {
     }
 
     final DelegateResourceContract delegateResourceContract;
+    byte[] ownerAddress;
     try {
       delegateResourceContract = this.any.unpack(DelegateResourceContract.class);
+      ownerAddress = getOwnerAddress().toByteArray();
     } catch (InvalidProtocolBufferException e) {
       logger.debug(e.getMessage(), e);
       throw new ContractValidateException(e.getMessage());
     }
-    byte[] ownerAddress = delegateResourceContract.getOwnerAddress().toByteArray();
     if (!DecodeUtil.addressValid(ownerAddress)) {
       throw new ContractValidateException("Invalid address");
     }
@@ -210,6 +218,36 @@ public boolean validate() throws ContractValidateException {
               + readableOwnerAddress + NOT_EXIST_STR);
     }
 
+    boolean lock = delegateResourceContract.getLock();
+    if (lock && dynamicStore.supportAllowOptimizeLockDelegateResource()) {
+      long lockPeriod = delegateResourceContract.getLockPeriod();
+      if (lockPeriod < 0 || lockPeriod > MAX_BLOCK_NUM_DELEGATE_PERIOD) {
+        throw new ContractValidateException(
+            "The lock period of delegate resource cannot be less than 0 and cannot exceed 1 year!");
+      }
+
+      byte[] key = DelegatedResourceCapsule.createDbKeyV2(ownerAddress, receiverAddress, true);
+      DelegatedResourceCapsule delegatedResourceCapsule = delegatedResourceStore.get(key);
+      long now = dynamicStore.getLatestBlockHeaderTimestamp();
+      if (delegatedResourceCapsule != null) {
+        switch (delegateResourceContract.getResource()) {
+          case BANDWIDTH: {
+            validRemainTime(BANDWIDTH, lockPeriod,
+                delegatedResourceCapsule.getExpireTimeForBandwidth(), now);
+          }
+          break;
+          case ENERGY: {
+            validRemainTime(ENERGY, lockPeriod,
+                delegatedResourceCapsule.getExpireTimeForEnergy(), now);
+          }
+          break;
+          default:
+            throw new ContractValidateException(
+                "ResourceCode error, valid ResourceCode[BANDWIDTH、ENERGY]");
+        }
+      }
+    }
+
     if (receiverCapsule.getType() == AccountType.Contract) {
       throw new ContractValidateException(
           "Do not allow delegate resources to contract addresses");
@@ -218,6 +256,17 @@ public boolean validate() throws ContractValidateException {
     return true;
   }
 
+  private void validRemainTime(ResourceCode resourceCode, long lockPeriod, long expireTime,
+      long now) throws ContractValidateException {
+    long remainTime = expireTime - now;
+    if (lockPeriod * 3 * 1000 < remainTime) {
+      throw new ContractValidateException(
+          "The lock period for " + resourceCode.name() + " this time cannot be less than the "
+              + "remaining time[" + remainTime + "s] of the last lock period for "
+              + resourceCode.name() + "!");
+    }
+  }
+
   @Override
   public ByteString getOwnerAddress() throws InvalidProtocolBufferException {
     return any.unpack(DelegateResourceContract.class).getOwnerAddress();
@@ -229,7 +278,7 @@ public long calcFee() {
   }
 
   private void delegateResource(byte[] ownerAddress, byte[] receiverAddress, boolean isBandwidth,
-                                long balance, boolean lock) {
+                                long balance, boolean lock, long lockPeriod) {
     AccountStore accountStore = chainBaseManager.getAccountStore();
     DynamicPropertiesStore dynamicPropertiesStore = chainBaseManager.getDynamicPropertiesStore();
     DelegatedResourceStore delegatedResourceStore = chainBaseManager.getDelegatedResourceStore();
@@ -241,12 +290,15 @@ private void delegateResource(byte[] ownerAddress, byte[] receiverAddress, boole
     delegatedResourceStore.unLockExpireResource(ownerAddress, receiverAddress, now);
 
     //modify DelegatedResourceStore
-    byte[] key;
     long expireTime = 0;
     if (lock) {
-      expireTime = now + DELEGATE_PERIOD;
+      if (dynamicPropertiesStore.supportAllowOptimizeLockDelegateResource()) {
+        expireTime = now + (lockPeriod == 0 ? DELEGATE_PERIOD : lockPeriod * 3 * 1000);
+      } else {
+        expireTime = now + DELEGATE_PERIOD;
+      }
     }
-    key = DelegatedResourceCapsule.createDbKeyV2(ownerAddress, receiverAddress, lock);
+    byte[] key = DelegatedResourceCapsule.createDbKeyV2(ownerAddress, receiverAddress, lock);
     DelegatedResourceCapsule delegatedResourceCapsule = delegatedResourceStore.get(key);
     if (delegatedResourceCapsule == null) {
       delegatedResourceCapsule = new DelegatedResourceCapsule(ByteString.copyFrom(ownerAddress),
diff --git a/actuator/src/main/java/org/tron/core/utils/ProposalUtil.java b/actuator/src/main/java/org/tron/core/utils/ProposalUtil.java
index 9ab68cc1925..0fdb9234bad 100644
--- a/actuator/src/main/java/org/tron/core/utils/ProposalUtil.java
+++ b/actuator/src/main/java/org/tron/core/utils/ProposalUtil.java
@@ -692,6 +692,22 @@ public static void validator(DynamicPropertiesStore dynamicPropertiesStore,
         }
         break;
       }
+      case ALLOW_OPTIMIZE_LOCK_DELEGATE_RESOURCE: {
+        if (!forkController.pass(ForkBlockVersionEnum.VERSION_4_7_2)) {
+          throw new ContractValidateException(
+              "Bad chain parameter id [ALLOW_OPTIMIZE_LOCK_DELEGATE_RESOURCE]");
+        }
+        if (value != 1) {
+          throw new ContractValidateException(
+              "This value[ALLOW_OPTIMIZE_LOCK_DELEGATE_RESOURCE] is only allowed to be 1");
+        }
+        if (dynamicPropertiesStore.getUnfreezeDelayDays() == 0) {
+          throw new ContractValidateException(
+              "[UNFREEZE_DELAY_DAYS] proposal must be approved "
+                  + "before [ALLOW_OPTIMIZE_LOCK_DELEGATE_RESOURCE] can be proposed");
+        }
+        break;
+      }
       default:
         break;
     }
@@ -765,7 +781,8 @@ public enum ProposalType {         // current value, value range
     DYNAMIC_ENERGY_THRESHOLD(73), // 0, [0, LONG]
     DYNAMIC_ENERGY_INCREASE_FACTOR(74), // 0, [0, 10_000]
     DYNAMIC_ENERGY_MAX_FACTOR(75), // 0, [0, 100_000]
-    ALLOW_TVM_SHANGHAI(76); // 0, 1
+    ALLOW_TVM_SHANGHAI(76), // 0, 1
+    ALLOW_OPTIMIZE_LOCK_DELEGATE_RESOURCE(78); // 0, 1
 
     private long code;
 
diff --git a/actuator/src/main/java/org/tron/core/utils/TransactionUtil.java b/actuator/src/main/java/org/tron/core/utils/TransactionUtil.java
index e5cffa49790..e8fe963f89b 100644
--- a/actuator/src/main/java/org/tron/core/utils/TransactionUtil.java
+++ b/actuator/src/main/java/org/tron/core/utils/TransactionUtil.java
@@ -17,6 +17,7 @@
 
 import static org.tron.common.crypto.Hash.sha3omit12;
 import static org.tron.core.config.Parameter.ChainConstant.DELEGATE_COST_BASE_SIZE;
+import static org.tron.core.config.Parameter.ChainConstant.MAX_BLOCK_NUM_DELEGATE_PERIOD;
 import static org.tron.core.config.Parameter.ChainConstant.TRX_PRECISION;
 
 import com.google.common.base.CaseFormat;
@@ -279,11 +280,11 @@ public static long consumeBandWidthSize(
   }
 
 
-  public static long estimateConsumeBandWidthSize(
-          final AccountCapsule ownerCapsule,
-          ChainBaseManager chainBaseManager) {
+  public static long estimateConsumeBandWidthSize(final AccountCapsule ownerCapsule,
+      ChainBaseManager chainBaseManager) {
     DelegateResourceContract.Builder builder = DelegateResourceContract.newBuilder()
                     .setLock(true)
+                    .setLockPeriod(MAX_BLOCK_NUM_DELEGATE_PERIOD)
                     .setBalance(ownerCapsule.getFrozenV2BalanceForBandwidth());
     TransactionCapsule fakeTransactionCapsule = new TransactionCapsule(builder.build()
             , ContractType.DelegateResourceContract);
diff --git a/chainbase/src/main/java/org/tron/core/store/DynamicPropertiesStore.java b/chainbase/src/main/java/org/tron/core/store/DynamicPropertiesStore.java
index 001282a0de8..c797c113c77 100644
--- a/chainbase/src/main/java/org/tron/core/store/DynamicPropertiesStore.java
+++ b/chainbase/src/main/java/org/tron/core/store/DynamicPropertiesStore.java
@@ -14,7 +14,6 @@
 import org.tron.common.utils.ByteArray;
 import org.tron.common.utils.Sha256Hash;
 import org.tron.core.capsule.BytesCapsule;
-import org.tron.core.config.Parameter;
 import org.tron.core.config.Parameter.ChainConstant;
 import org.tron.core.db.TronStoreWithRevoking;
 import org.tron.core.exception.BadItemException;
@@ -208,6 +207,9 @@ public class DynamicPropertiesStore extends TronStoreWithRevoking<BytesCapsule>
 
   private static final byte[] ALLOW_TVM_SHANGHAI = "ALLOW_TVM_SHANGHAI".getBytes();
 
+  private static final byte[] ALLOW_OPTIMIZE_LOCK_DELEGATE_RESOURCE =
+      "ALLOW_OPTIMIZE_LOCK_DELEGATE_RESOURCE".getBytes();
+
   @Autowired
   private DynamicPropertiesStore(@Value("properties") String dbName) {
     super(dbName);
@@ -2192,7 +2194,7 @@ public long getNextMaintenanceTime() {
   }
 
   public long getMaintenanceSkipSlots() {
-    return Parameter.ChainConstant.MAINTENANCE_SKIP_SLOTS;
+    return ChainConstant.MAINTENANCE_SKIP_SLOTS;
   }
 
   public void saveNextMaintenanceTime(long nextMaintenanceTime) {
@@ -2218,6 +2220,9 @@ public void updateNextMaintenanceTime(long blockTime) {
 
   //The unit is trx
   public void addTotalNetWeight(long amount) {
+    if (amount == 0) {
+      return;
+    }
     long totalNetWeight = getTotalNetWeight();
     totalNetWeight += amount;
     if (allowNewReward()) {
@@ -2228,6 +2233,9 @@ public void addTotalNetWeight(long amount) {
 
   //The unit is trx
   public void addTotalEnergyWeight(long amount) {
+    if (amount == 0) {
+      return;
+    }
     long totalEnergyWeight = getTotalEnergyWeight();
     totalEnergyWeight += amount;
     if (allowNewReward()) {
@@ -2238,6 +2246,9 @@ public void addTotalEnergyWeight(long amount) {
 
   //The unit is trx
   public void addTotalTronPowerWeight(long amount) {
+    if (amount == 0) {
+      return;
+    }
     long totalWeight = getTotalTronPowerWeight();
     totalWeight += amount;
     if (allowNewReward()) {
@@ -2769,6 +2780,22 @@ public long getAllowTvmShangHai() {
         .orElse(CommonParameter.getInstance().getAllowTvmShangHai());
   }
 
+  public void saveAllowOptimizeLockDelegateResource(long allowOptimizeLockDelegateResource) {
+    this.put(DynamicPropertiesStore.ALLOW_OPTIMIZE_LOCK_DELEGATE_RESOURCE,
+        new BytesCapsule(ByteArray.fromLong(allowOptimizeLockDelegateResource)));
+  }
+
+  public long getAllowOptimizeLockDelegateResource() {
+    return Optional.ofNullable(getUnchecked(ALLOW_OPTIMIZE_LOCK_DELEGATE_RESOURCE))
+        .map(BytesCapsule::getData)
+        .map(ByteArray::toLong)
+        .orElse(CommonParameter.getInstance().getAllowOptimizeLockDelegateResource());
+  }
+
+  public boolean supportAllowOptimizeLockDelegateResource() {
+    return getAllowOptimizeLockDelegateResource() == 1L && getUnfreezeDelayDays() > 0;
+  }
+
   private static class DynamicResourceProperties {
 
     private static final byte[] ONE_DAY_NET_LIMIT = "ONE_DAY_NET_LIMIT".getBytes();
diff --git a/common/src/main/java/org/tron/common/parameter/CommonParameter.java b/common/src/main/java/org/tron/common/parameter/CommonParameter.java
index ef89e01925a..71836c64a40 100644
--- a/common/src/main/java/org/tron/common/parameter/CommonParameter.java
+++ b/common/src/main/java/org/tron/common/parameter/CommonParameter.java
@@ -651,6 +651,10 @@ public class CommonParameter {
   @Setter
   public long allowTvmShangHai;
 
+  @Getter
+  @Setter
+  public long allowOptimizeLockDelegateResource;
+
   private static double calcMaxTimeRatio() {
     //return max(2.0, min(5.0, 5 * 4.0 / max(Runtime.getRuntime().availableProcessors(), 1)));
     return 5.0;
diff --git a/common/src/main/java/org/tron/core/config/Parameter.java b/common/src/main/java/org/tron/core/config/Parameter.java
index 5a170577b99..822a79896cc 100644
--- a/common/src/main/java/org/tron/core/config/Parameter.java
+++ b/common/src/main/java/org/tron/core/config/Parameter.java
@@ -74,6 +74,7 @@ public class ChainConstant {
     public static final int BLOCK_VERSION = 28;
     public static final long FROZEN_PERIOD = 86_400_000L;
     public static final long DELEGATE_PERIOD = 3 * 86_400_000L;
+    public static final long MAX_BLOCK_NUM_DELEGATE_PERIOD = 10512000L;
     public static final long TRX_PRECISION = 1000_000L;
     public static final long DELEGATE_COST_BASE_SIZE = 275L;
   }
diff --git a/framework/src/main/java/org/tron/core/Wallet.java b/framework/src/main/java/org/tron/core/Wallet.java
index a836b15d59d..c5989e19266 100755
--- a/framework/src/main/java/org/tron/core/Wallet.java
+++ b/framework/src/main/java/org/tron/core/Wallet.java
@@ -1325,6 +1325,11 @@ public Protocol.ChainParameters getChainParameters() {
         .setValue(dbManager.getDynamicPropertiesStore().getAllowTvmShangHai())
         .build());
 
+    builder.addChainParameter(Protocol.ChainParameters.ChainParameter.newBuilder()
+        .setKey("getAllowOptimizeLockDelegateResource")
+        .setValue(dbManager.getDynamicPropertiesStore().getAllowOptimizeLockDelegateResource())
+        .build());
+
     return builder.build();
   }
 
diff --git a/framework/src/main/java/org/tron/core/consensus/ProposalService.java b/framework/src/main/java/org/tron/core/consensus/ProposalService.java
index 4ca90eae640..f376de4917c 100644
--- a/framework/src/main/java/org/tron/core/consensus/ProposalService.java
+++ b/framework/src/main/java/org/tron/core/consensus/ProposalService.java
@@ -343,6 +343,13 @@ public static boolean process(Manager manager, ProposalCapsule proposalCapsule)
           manager.getDynamicPropertiesStore().saveAllowTvmShangHai(entry.getValue());
           break;
         }
+        case ALLOW_OPTIMIZE_LOCK_DELEGATE_RESOURCE: {
+          if (manager.getDynamicPropertiesStore().getAllowOptimizeLockDelegateResource() == 0) {
+            manager.getDynamicPropertiesStore()
+                .saveAllowOptimizeLockDelegateResource(entry.getValue());
+          }
+          break;
+        }
         default:
           find = false;
           break;
diff --git a/framework/src/main/java/org/tron/core/services/http/DelegateResourceServlet.java b/framework/src/main/java/org/tron/core/services/http/DelegateResourceServlet.java
index 221cc34cf7c..00994238988 100644
--- a/framework/src/main/java/org/tron/core/services/http/DelegateResourceServlet.java
+++ b/framework/src/main/java/org/tron/core/services/http/DelegateResourceServlet.java
@@ -1,5 +1,6 @@
 package org.tron.core.services.http;
 
+import com.alibaba.fastjson.JSON;
 import com.alibaba.fastjson.JSONObject;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
@@ -18,6 +19,7 @@ public class DelegateResourceServlet extends RateLimiterServlet {
   @Autowired
   private Wallet wallet;
 
+  @Override
   protected void doPost(HttpServletRequest request, HttpServletResponse response) {
     try {
       PostParams params = PostParams.getPostParams(request);
@@ -26,7 +28,7 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response)
       Transaction tx = wallet
           .createTransactionCapsule(build.build(), ContractType.DelegateResourceContract)
           .getInstance();
-      JSONObject jsonObject = JSONObject.parseObject(params.getParams());
+      JSONObject jsonObject = JSON.parseObject(params.getParams());
       tx = Util.setTransactionPermissionId(jsonObject, tx);
       response.getWriter().println(Util.printCreateTransaction(tx, params.isVisible()));
     } catch (Exception e) {
diff --git a/framework/src/test/java/org/tron/core/WalletTest.java b/framework/src/test/java/org/tron/core/WalletTest.java
index 88fa7b6f8f9..b487a0f8421 100644
--- a/framework/src/test/java/org/tron/core/WalletTest.java
+++ b/framework/src/test/java/org/tron/core/WalletTest.java
@@ -363,6 +363,7 @@ public void testGetEcKey() {
   public void ss() {
     for (int i = 0; i < 4; i++) {
       ECKey ecKey = new ECKey(Utils.getRandom());
+      assertNotNull(ecKey);
       System.out.println(i + 1);
       System.out.println("privateKey:" + ByteArray.toHexString(ecKey.getPrivKeyBytes()));
       System.out.println("publicKey:" + ByteArray.toHexString(ecKey.getPubKey()));
@@ -483,14 +484,14 @@ public void getPaginatedAssetIssueList() {
     AssetIssueList assetList1 = wallet.getAssetIssueList(0, 100);
     assertEquals("get Asset1", assetList1.getAssetIssue(0).getName(), Asset1.getName());
     try {
-      assetList1.getAssetIssue(1);
+      assertNotNull(assetList1.getAssetIssue(1));
     } catch (Exception e) {
       Assert.assertTrue("AssetIssueList1 size should be 1", true);
     }
 
     AssetIssueList assetList2 = wallet.getAssetIssueList(0, 0);
     try {
-      assetList2.getAssetIssue(0);
+      assertNotNull(assetList2.getAssetIssue(0));
     } catch (Exception e) {
       Assert.assertTrue("AssetIssueList2 size should be 0", true);
     }
@@ -615,12 +616,8 @@ public void testGetDelegatedResource() {
           delegatedResourceList.getDelegatedResource(0).getFrozenBalanceForBandwidth());
       Assert.assertEquals(0L,
           delegatedResourceList.getDelegatedResource(0).getExpireTimeForBandwidth());
-    } catch (ContractValidateException e) {
-      Assert.assertFalse(e instanceof ContractValidateException);
-    } catch (ContractExeException e) {
-      Assert.assertFalse(e instanceof ContractExeException);
     } catch (Exception e) {
-      Assert.assertEquals(false, true);
+      Assert.fail();
     }
   }
 
@@ -794,7 +791,7 @@ public void testGetCanDelegatedMaxSizeBandWidth() {
     GrpcAPI.CanDelegatedMaxSizeResponseMessage message = wallet.getCanDelegatedMaxSize(
         ByteString.copyFrom(ByteArray.fromHexString(OWNER_ADDRESS)),
         BANDWIDTH.getNumber());
-    Assert.assertEquals(initBalance - 280L, message.getMaxSize());
+    Assert.assertEquals(initBalance - 285L, message.getMaxSize());
 
   }
 
@@ -1054,13 +1051,13 @@ public void testEstimateEnergyOutOfTime() {
   @Test
   public void testListNodes() {
     try {
-      GrpcAPI.NodeList nodeList = wallet.listNodes();
+      wallet.listNodes();
     } catch (Exception e) {
       Assert.assertTrue(e instanceof NullPointerException);
     }
     Args.getInstance().setP2pDisable(true);
     GrpcAPI.NodeList nodeList = wallet.listNodes();
-    Assert.assertTrue(nodeList.getNodesList().size() == 0);
+    assertEquals(0, nodeList.getNodesList().size());
   }
 }
 
diff --git a/framework/src/test/java/org/tron/core/actuator/DelegateResourceActuatorTest.java b/framework/src/test/java/org/tron/core/actuator/DelegateResourceActuatorTest.java
index afbe6895bb9..a232729d3de 100644
--- a/framework/src/test/java/org/tron/core/actuator/DelegateResourceActuatorTest.java
+++ b/framework/src/test/java/org/tron/core/actuator/DelegateResourceActuatorTest.java
@@ -1,6 +1,13 @@
 package org.tron.core.actuator;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 import static org.tron.core.config.Parameter.ChainConstant.TRX_PRECISION;
+import static org.tron.protos.contract.Common.ResourceCode.BANDWIDTH;
+import static org.tron.protos.contract.Common.ResourceCode.ENERGY;
+import static org.tron.protos.contract.Common.ResourceCode.TRON_POWER;
 
 import com.google.protobuf.Any;
 import com.google.protobuf.ByteString;
@@ -23,8 +30,8 @@
 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;
 import org.tron.protos.contract.BalanceContract.DelegateResourceContract;
-import org.tron.protos.contract.Common.ResourceCode;
 
 @Slf4j
 public class DelegateResourceActuatorTest extends BaseTest {
@@ -108,18 +115,40 @@ private Any getLockedDelegateContractForBandwidth(String ownerAddress, String re
             .setOwnerAddress(ByteString.copyFrom(ByteArray.fromHexString(ownerAddress)))
             .setReceiverAddress(ByteString.copyFrom(ByteArray.fromHexString(receiveAddress)))
             .setBalance(unfreezeBalance)
-            .setResource(ResourceCode.BANDWIDTH)
+            .setResource(BANDWIDTH)
             .setLock(lock)
             .build());
   }
 
+  private Any getOptimizeLockedDelegateContractForBandwidth(long unfreezeBalance, long lockPeriod) {
+    return Any.pack(DelegateResourceContract.newBuilder()
+        .setOwnerAddress(ByteString.copyFrom(ByteArray.fromHexString(OWNER_ADDRESS)))
+        .setReceiverAddress(ByteString.copyFrom(ByteArray.fromHexString(RECEIVER_ADDRESS)))
+        .setBalance(unfreezeBalance)
+        .setResource(BANDWIDTH)
+        .setLock(true)
+        .setLockPeriod(lockPeriod)
+        .build());
+  }
+
+  private Any getOptimizeLockedDelegateContractForEnergy(long unfreezeBalance, long lockPeriod) {
+    return Any.pack(DelegateResourceContract.newBuilder()
+        .setOwnerAddress(ByteString.copyFrom(ByteArray.fromHexString(OWNER_ADDRESS)))
+        .setReceiverAddress(ByteString.copyFrom(ByteArray.fromHexString(RECEIVER_ADDRESS)))
+        .setBalance(unfreezeBalance)
+        .setResource(ENERGY)
+        .setLock(true)
+        .setLockPeriod(lockPeriod)
+        .build());
+  }
+
   private Any getDelegateContractForCpu(long unfreezeBalance) {
     return Any.pack(
         DelegateResourceContract.newBuilder()
             .setOwnerAddress(ByteString.copyFrom(ByteArray.fromHexString(OWNER_ADDRESS)))
             .setReceiverAddress(ByteString.copyFrom(ByteArray.fromHexString(RECEIVER_ADDRESS)))
             .setBalance(unfreezeBalance)
-            .setResource(ResourceCode.ENERGY)
+            .setResource(ENERGY)
             .build());
   }
 
@@ -129,7 +158,7 @@ private Any getDelegateContractForTronPower(long unfreezeBalance) {
             .setOwnerAddress(ByteString.copyFrom(ByteArray.fromHexString(OWNER_ADDRESS)))
             .setReceiverAddress(ByteString.copyFrom(ByteArray.fromHexString(RECEIVER_ADDRESS)))
             .setBalance(unfreezeBalance)
-            .setResource(ResourceCode.TRON_POWER)
+            .setResource(TRON_POWER)
             .build());
   }
 
@@ -147,12 +176,12 @@ public void testDelegateResourceWithNoFreeze() {
     try {
       actuator.validate();
       actuator.execute(ret);
-      Assert.fail("cannot run here.");
+      fail("cannot run here.");
     } catch (ContractValidateException e) {
-      Assert.assertEquals("delegateBalance must be less than available FreezeBandwidthV2 balance",
+      assertEquals("delegateBalance must be less than available FreezeBandwidthV2 balance",
           e.getMessage());
     } catch (ContractExeException e) {
-      Assert.fail();
+      fail();
     }
 
     actuator.setChainBaseManager(dbManager.getChainBaseManager()).setAny(
@@ -160,13 +189,13 @@ public void testDelegateResourceWithNoFreeze() {
     try {
       actuator.validate();
       actuator.execute(ret);
-      Assert.fail("cannot run here.");
+      fail("cannot run here.");
     } catch (ContractValidateException e) {
-      Assert.assertEquals(
+      assertEquals(
           "delegateBalance must be less than available FreezeEnergyV2 balance",
           e.getMessage());
     } catch (ContractExeException e) {
-      Assert.fail(e.getMessage());
+      fail(e.getMessage());
     }
   }
 
@@ -190,16 +219,15 @@ public void testDelegateBandwidthWithUsage() {
     try {
       actuator.validate();
       actuator.execute(ret);
-      Assert.fail("cannot run here.");
+      fail("cannot run here.");
     } catch (ContractValidateException e) {
-      Assert.assertEquals("delegateBalance must be less than available FreezeBandwidthV2 balance",
+      assertEquals("delegateBalance must be less than available FreezeBandwidthV2 balance",
           e.getMessage());
     } catch (ContractExeException e) {
-      Assert.fail(e.getMessage());
+      fail(e.getMessage());
     }
   }
 
-
   @Test
   public void testDelegateCpuWithUsage() {
     freezeCpuForOwner();
@@ -220,13 +248,13 @@ public void testDelegateCpuWithUsage() {
     try {
       actuator.validate();
       actuator.execute(ret);
-      Assert.fail("cannot run here.");
+      fail("cannot run here.");
     } catch (ContractValidateException e) {
-      Assert.assertEquals(
+      assertEquals(
           "delegateBalance must be less than available FreezeEnergyV2 balance",
           e.getMessage());
     } catch (ContractExeException e) {
-      Assert.fail(e.getMessage());
+      fail(e.getMessage());
     }
   }
 
@@ -252,9 +280,9 @@ public void testDelegateResourceWithContractAddress() {
       actuator.validate();
       actuator.execute(ret);
     } catch (ContractValidateException e) {
-      Assert.assertEquals("Do not allow delegate resources to contract addresses", e.getMessage());
+      assertEquals("Do not allow delegate resources to contract addresses", e.getMessage());
     } catch (ContractExeException e) {
-      Assert.fail(e.getMessage());
+      fail(e.getMessage());
     }
   }
 
@@ -271,9 +299,9 @@ public void testDelegateResourceToSelf() {
       actuator.validate();
       actuator.execute(ret);
     } catch (ContractValidateException e) {
-      Assert.assertEquals("receiverAddress must not be the same as ownerAddress", e.getMessage());
+      assertEquals("receiverAddress must not be the same as ownerAddress", e.getMessage());
     } catch (ContractExeException e) {
-      Assert.fail(e.getMessage());
+      fail(e.getMessage());
     }
   }
 
@@ -292,48 +320,48 @@ public void testDelegateResourceForBandwidth() {
     try {
       actuator.validate();
       actuator.execute(ret);
-      Assert.assertEquals(code.SUCESS, ret.getInstance().getRet());
+      assertEquals(code.SUCESS, ret.getInstance().getRet());
       AccountCapsule ownerCapsule =
           dbManager.getAccountStore().get(owner);
 
-      Assert.assertEquals(delegateBalance, ownerCapsule.getDelegatedFrozenV2BalanceForBandwidth());
-      Assert.assertEquals(initBalance - delegateBalance,
+      assertEquals(delegateBalance, ownerCapsule.getDelegatedFrozenV2BalanceForBandwidth());
+      assertEquals(initBalance - delegateBalance,
           ownerCapsule.getFrozenV2BalanceForBandwidth());
-      Assert.assertEquals(initBalance, ownerCapsule.getTronPower());
+      assertEquals(initBalance, ownerCapsule.getTronPower());
 
       AccountCapsule receiverCapsule =
           dbManager.getAccountStore().get(receiver);
-      Assert.assertEquals(delegateBalance,
+      assertEquals(delegateBalance,
           receiverCapsule.getAcquiredDelegatedFrozenV2BalanceForBandwidth());
-      Assert.assertEquals(0L, receiverCapsule.getAcquiredDelegatedFrozenV2BalanceForEnergy());
-      Assert.assertEquals(0L, receiverCapsule.getTronPower());
+      assertEquals(0L, receiverCapsule.getAcquiredDelegatedFrozenV2BalanceForEnergy());
+      assertEquals(0L, receiverCapsule.getTronPower());
 
       DelegatedResourceCapsule delegatedResourceCapsule = dbManager.getDelegatedResourceStore()
           .get(DelegatedResourceCapsule
               .createDbKeyV2(ByteArray.fromHexString(OWNER_ADDRESS),
                   ByteArray.fromHexString(RECEIVER_ADDRESS), false));
 
-      Assert.assertEquals(delegateBalance, delegatedResourceCapsule.getFrozenBalanceForBandwidth());
+      assertEquals(delegateBalance, delegatedResourceCapsule.getFrozenBalanceForBandwidth());
       long totalNetWeightAfter = dbManager.getDynamicPropertiesStore().getTotalNetWeight();
-      Assert.assertEquals(totalNetWeightBefore, totalNetWeightAfter);
+      assertEquals(totalNetWeightBefore, totalNetWeightAfter);
 
       //check DelegatedResourceAccountIndex
       DelegatedResourceAccountIndexCapsule ownerIndexCapsule = dbManager
           .getDelegatedResourceAccountIndexStore().getV2Index(owner);
-      Assert.assertEquals(0, ownerIndexCapsule.getFromAccountsList().size());
-      Assert.assertEquals(1, ownerIndexCapsule.getToAccountsList().size());
-      Assert.assertTrue(ownerIndexCapsule.getToAccountsList()
+      assertEquals(0, ownerIndexCapsule.getFromAccountsList().size());
+      assertEquals(1, ownerIndexCapsule.getToAccountsList().size());
+      assertTrue(ownerIndexCapsule.getToAccountsList()
           .contains(ByteString.copyFrom(ByteArray.fromHexString(RECEIVER_ADDRESS))));
 
       DelegatedResourceAccountIndexCapsule receiveCapsule = dbManager
           .getDelegatedResourceAccountIndexStore().getV2Index(receiver);
-      Assert.assertEquals(0, receiveCapsule.getToAccountsList().size());
-      Assert.assertEquals(1, receiveCapsule.getFromAccountsList().size());
-      Assert.assertTrue(receiveCapsule.getFromAccountsList()
+      assertEquals(0, receiveCapsule.getToAccountsList().size());
+      assertEquals(1, receiveCapsule.getFromAccountsList().size());
+      assertTrue(receiveCapsule.getFromAccountsList()
           .contains(ByteString.copyFrom(ByteArray.fromHexString(OWNER_ADDRESS))));
 
     } catch (ContractValidateException | ContractExeException e) {
-      Assert.fail(e.getMessage());
+      fail(e.getMessage());
     }
   }
 
@@ -353,22 +381,23 @@ public void testLockedDelegateResourceForBandwidth() {
     try {
       actuator.validate();
       actuator.execute(ret);
-      Assert.assertEquals(code.SUCESS, ret.getInstance().getRet());
+      assertEquals(code.SUCESS, ret.getInstance().getRet());
       AccountCapsule ownerCapsule =
               dbManager.getAccountStore().get(owner);
 
-      Assert.assertEquals(delegateBalance, ownerCapsule.getDelegatedFrozenV2BalanceForBandwidth());
-      Assert.assertEquals(initBalance - delegateBalance,
+      assertEquals(delegateBalance, ownerCapsule.getDelegatedFrozenV2BalanceForBandwidth());
+      assertEquals(initBalance - delegateBalance,
               ownerCapsule.getFrozenV2BalanceForBandwidth());
-      Assert.assertEquals(initBalance, ownerCapsule.getTronPower());
+      assertEquals(initBalance, ownerCapsule.getTronPower());
 
       AccountCapsule receiverCapsule =
               dbManager.getAccountStore().get(receiver);
-      Assert.assertEquals(delegateBalance,
+      assertEquals(delegateBalance,
               receiverCapsule.getAcquiredDelegatedFrozenV2BalanceForBandwidth());
-      Assert.assertEquals(0L, receiverCapsule.getAcquiredDelegatedFrozenV2BalanceForEnergy());
-      Assert.assertEquals(0L, receiverCapsule.getTronPower());
+      assertEquals(0L, receiverCapsule.getAcquiredDelegatedFrozenV2BalanceForEnergy());
+      assertEquals(0L, receiverCapsule.getTronPower());
 
+      //check DelegatedResource
       DelegatedResourceCapsule delegatedResourceCapsule = dbManager.getDelegatedResourceStore()
               .get(DelegatedResourceCapsule
                       .createDbKeyV2(ByteArray.fromHexString(OWNER_ADDRESS),
@@ -380,30 +409,181 @@ public void testLockedDelegateResourceForBandwidth() {
       Assert.assertNull(delegatedResourceCapsule);
       Assert.assertNotNull(lockedResourceCapsule);
       Assert.assertNotEquals(0, lockedResourceCapsule.getExpireTimeForBandwidth());
-      Assert.assertEquals(delegateBalance, lockedResourceCapsule.getFrozenBalanceForBandwidth());
+      assertEquals(delegateBalance, lockedResourceCapsule.getFrozenBalanceForBandwidth());
       long totalNetWeightAfter = dbManager.getDynamicPropertiesStore().getTotalNetWeight();
-      Assert.assertEquals(totalNetWeightBefore, totalNetWeightAfter);
+      assertEquals(totalNetWeightBefore, totalNetWeightAfter);
 
       //check DelegatedResourceAccountIndex
       DelegatedResourceAccountIndexCapsule ownerIndexCapsule = dbManager
               .getDelegatedResourceAccountIndexStore().getV2Index(owner);
-      Assert.assertEquals(0, ownerIndexCapsule.getFromAccountsList().size());
-      Assert.assertEquals(1, ownerIndexCapsule.getToAccountsList().size());
-      Assert.assertTrue(ownerIndexCapsule.getToAccountsList()
+      assertEquals(0, ownerIndexCapsule.getFromAccountsList().size());
+      assertEquals(1, ownerIndexCapsule.getToAccountsList().size());
+      assertTrue(ownerIndexCapsule.getToAccountsList()
               .contains(ByteString.copyFrom(ByteArray.fromHexString(RECEIVER_ADDRESS))));
 
       DelegatedResourceAccountIndexCapsule receiveCapsule = dbManager
               .getDelegatedResourceAccountIndexStore().getV2Index(receiver);
-      Assert.assertEquals(0, receiveCapsule.getToAccountsList().size());
-      Assert.assertEquals(1, receiveCapsule.getFromAccountsList().size());
-      Assert.assertTrue(receiveCapsule.getFromAccountsList()
+      assertEquals(0, receiveCapsule.getToAccountsList().size());
+      assertEquals(1, receiveCapsule.getFromAccountsList().size());
+      assertTrue(receiveCapsule.getFromAccountsList()
               .contains(ByteString.copyFrom(ByteArray.fromHexString(OWNER_ADDRESS))));
 
     } catch (ContractValidateException | ContractExeException e) {
-      Assert.fail(e.getMessage());
+      fail(e.getMessage());
     }
   }
 
+  @Test
+  public void testOptimizeLockedDelegateResourceForBandwidthWrongLockPeriod1() {
+    dbManager.getDynamicPropertiesStore().saveAllowOptimizeLockDelegateResource(1);
+    freezeBandwidthForOwner();
+    long delegateBalance = 1_000_000_000L;
+    DelegateResourceActuator actuator = new DelegateResourceActuator();
+    actuator.setChainBaseManager(dbManager.getChainBaseManager()).setAny(
+        getOptimizeLockedDelegateContractForBandwidth(
+            delegateBalance, 370 * 24 * 3600));
+    assertThrows("The lock period of delegate resources cannot exceed 1 year!",
+        ContractValidateException.class, actuator::validate);
+  }
+
+  @Test
+  public void testOptimizeLockedDelegateResourceForBandwidthWrongLockPeriod2() {
+    dbManager.getDynamicPropertiesStore().saveAllowOptimizeLockDelegateResource(1);
+    freezeBandwidthForOwner();
+    long delegateBalance = 1_000_000_000L;
+    DelegateResourceActuator actuator = new DelegateResourceActuator();
+    actuator.setChainBaseManager(dbManager.getChainBaseManager()).setAny(
+        getOptimizeLockedDelegateContractForBandwidth(
+            delegateBalance, 60));
+
+    TransactionResultCapsule ret = new TransactionResultCapsule();
+    try {
+      actuator.validate();
+      actuator.execute(ret);
+      assertEquals(code.SUCESS, ret.getInstance().getRet());
+    } catch (ContractValidateException | ContractExeException e) {
+      fail(e.getMessage());
+    }
+
+    DelegateResourceActuator actuator1 = new DelegateResourceActuator();
+    actuator1.setChainBaseManager(dbManager.getChainBaseManager()).setAny(
+        getOptimizeLockedDelegateContractForBandwidth(
+            delegateBalance, 30));
+    assertThrows("The lock period for bandwidth this time cannot be less than the remaining"
+            + " time[60000s] of the last lock period for bandwidth!",
+        ContractValidateException.class, actuator1::validate);
+  }
+
+  @Test
+  public void testOptimizeLockedDelegateResourceForBandwidth() {
+    dbManager.getDynamicPropertiesStore().saveAllowOptimizeLockDelegateResource(1);
+    dbManager.getDynamicPropertiesStore().saveLatestBlockHeaderTimestamp(50_000L);
+    freezeBandwidthForOwner();
+    long delegateBalance = 1_000_000_000L;
+    DelegateResourceActuator actuator = new DelegateResourceActuator();
+    actuator.setChainBaseManager(dbManager.getChainBaseManager()).setAny(
+        getOptimizeLockedDelegateContractForBandwidth(
+            delegateBalance, 60));
+
+    TransactionResultCapsule ret = new TransactionResultCapsule();
+    try {
+      actuator.validate();
+      actuator.execute(ret);
+      assertEquals(code.SUCESS, ret.getInstance().getRet());
+    } catch (ContractValidateException | ContractExeException e) {
+      fail(e.getMessage());
+    }
+
+    DelegateResourceActuator actuator1 = new DelegateResourceActuator();
+    actuator1.setChainBaseManager(dbManager.getChainBaseManager()).setAny(
+        getOptimizeLockedDelegateContractForBandwidth(
+            delegateBalance, 60));
+
+    TransactionResultCapsule ret1 = new TransactionResultCapsule();
+    try {
+      actuator1.validate();
+      actuator1.execute(ret1);
+      assertEquals(code.SUCESS, ret1.getInstance().getRet());
+    } catch (ContractValidateException | ContractExeException e) {
+      fail(e.getMessage());
+    }
+    DelegatedResourceCapsule lockedResourceCapsule = dbManager.getDelegatedResourceStore()
+        .get(DelegatedResourceCapsule
+            .createDbKeyV2(ByteArray.fromHexString(OWNER_ADDRESS),
+                ByteArray.fromHexString(RECEIVER_ADDRESS), true));
+    long expireTimeForBandwidth = lockedResourceCapsule.getExpireTimeForBandwidth();
+    assertEquals(50_000L + 60 * 3 * 1000, expireTimeForBandwidth);
+    assertTrue(expireTimeForBandwidth > 60_000);
+  }
+
+  @Test
+  public void testOptimizeLockedDelegateResourceForEnergy() {
+    dbManager.getDynamicPropertiesStore().saveAllowOptimizeLockDelegateResource(1);
+    dbManager.getDynamicPropertiesStore().saveLatestBlockHeaderTimestamp(50_000L);
+    freezeCpuForOwner();
+    long delegateBalance = 1_000_000_000L;
+    DelegateResourceActuator actuator = new DelegateResourceActuator();
+    actuator.setChainBaseManager(dbManager.getChainBaseManager()).setAny(
+        getOptimizeLockedDelegateContractForEnergy(
+            delegateBalance, 60));
+
+    TransactionResultCapsule ret = new TransactionResultCapsule();
+    try {
+      actuator.validate();
+      actuator.execute(ret);
+      assertEquals(code.SUCESS, ret.getInstance().getRet());
+    } catch (ContractValidateException | ContractExeException e) {
+      fail(e.getMessage());
+    }
+
+    DelegateResourceActuator actuator1 = new DelegateResourceActuator();
+    actuator1.setChainBaseManager(dbManager.getChainBaseManager()).setAny(
+        getOptimizeLockedDelegateContractForEnergy(
+            delegateBalance, 60));
+
+    TransactionResultCapsule ret1 = new TransactionResultCapsule();
+    try {
+      actuator1.validate();
+      actuator1.execute(ret1);
+      assertEquals(code.SUCESS, ret1.getInstance().getRet());
+    } catch (ContractValidateException | ContractExeException e) {
+      fail(e.getMessage());
+    }
+    DelegatedResourceCapsule lockedResourceCapsule = dbManager.getDelegatedResourceStore()
+        .get(DelegatedResourceCapsule
+            .createDbKeyV2(ByteArray.fromHexString(OWNER_ADDRESS),
+                ByteArray.fromHexString(RECEIVER_ADDRESS), true));
+    assertTrue(lockedResourceCapsule.getExpireTimeForEnergy() > 60_000);
+  }
+
+  @Test
+  public void testOptimizeLockedDelegateResourceForEnergyWrongLockPeriod2() {
+    dbManager.getDynamicPropertiesStore().saveAllowOptimizeLockDelegateResource(1);
+    freezeCpuForOwner();
+    long delegateBalance = 1_000_000_000L;
+    DelegateResourceActuator actuator = new DelegateResourceActuator();
+    actuator.setChainBaseManager(dbManager.getChainBaseManager()).setAny(
+        getOptimizeLockedDelegateContractForEnergy(
+            delegateBalance, 60));
+
+    TransactionResultCapsule ret = new TransactionResultCapsule();
+    try {
+      actuator.validate();
+      actuator.execute(ret);
+      assertEquals(code.SUCESS, ret.getInstance().getRet());
+    } catch (ContractValidateException | ContractExeException e) {
+      fail(e.getMessage());
+    }
+
+    DelegateResourceActuator actuator1 = new DelegateResourceActuator();
+    actuator1.setChainBaseManager(dbManager.getChainBaseManager()).setAny(
+        getOptimizeLockedDelegateContractForEnergy(
+            delegateBalance, 30));
+    assertThrows("The lock period for energy this time cannot be less than the remaining"
+            + " time[60000s] of the last lock period for energy!",
+        ContractValidateException.class, actuator1::validate);
+  }
+
   @Test
   public void testDelegateResourceForCpu() {
     freezeCpuForOwner();
@@ -419,53 +599,50 @@ public void testDelegateResourceForCpu() {
     try {
       actuator.validate();
       actuator.execute(ret);
-      Assert.assertEquals(code.SUCESS, ret.getInstance().getRet());
+      assertEquals(code.SUCESS, ret.getInstance().getRet());
       AccountCapsule ownerCapsule =
           dbManager.getAccountStore().get(owner);
 
-      Assert.assertEquals(initBalance, ownerCapsule.getBalance());
-      Assert.assertEquals(0L, ownerCapsule.getFrozenBalance());
-      Assert.assertEquals(0L, ownerCapsule.getDelegatedFrozenV2BalanceForBandwidth());
-      Assert.assertEquals(delegateBalance, ownerCapsule.getDelegatedFrozenV2BalanceForEnergy());
-      Assert.assertEquals(initBalance, ownerCapsule.getTronPower());
+      assertEquals(initBalance, ownerCapsule.getBalance());
+      assertEquals(0L, ownerCapsule.getFrozenBalance());
+      assertEquals(0L, ownerCapsule.getDelegatedFrozenV2BalanceForBandwidth());
+      assertEquals(delegateBalance, ownerCapsule.getDelegatedFrozenV2BalanceForEnergy());
+      assertEquals(initBalance, ownerCapsule.getTronPower());
 
       AccountCapsule receiverCapsule =
           dbManager.getAccountStore().get(receiver);
-      Assert.assertEquals(0L, receiverCapsule.getAcquiredDelegatedFrozenV2BalanceForBandwidth());
-      Assert.assertEquals(delegateBalance,
+      assertEquals(0L, receiverCapsule.getAcquiredDelegatedFrozenV2BalanceForBandwidth());
+      assertEquals(delegateBalance,
           receiverCapsule.getAcquiredDelegatedFrozenV2BalanceForEnergy());
-      Assert.assertEquals(0L, receiverCapsule.getTronPower());
+      assertEquals(0L, receiverCapsule.getTronPower());
 
       DelegatedResourceCapsule delegatedResourceCapsule = dbManager.getDelegatedResourceStore()
           .get(DelegatedResourceCapsule.createDbKeyV2(owner, receiver, false));
 
-      Assert.assertEquals(0L, delegatedResourceCapsule.getFrozenBalanceForBandwidth());
-      Assert.assertEquals(delegateBalance, delegatedResourceCapsule.getFrozenBalanceForEnergy());
+      assertEquals(0L, delegatedResourceCapsule.getFrozenBalanceForBandwidth());
+      assertEquals(delegateBalance, delegatedResourceCapsule.getFrozenBalanceForEnergy());
 
       long totalEnergyWeightAfter = dbManager.getDynamicPropertiesStore().getTotalEnergyWeight();
-      Assert.assertEquals(totalEnergyWeightBefore, totalEnergyWeightAfter);
+      assertEquals(totalEnergyWeightBefore, totalEnergyWeightAfter);
 
       //check DelegatedResourceAccountIndex
       DelegatedResourceAccountIndexCapsule ownerIndexCapsule = dbManager
           .getDelegatedResourceAccountIndexStore().getV2Index(owner);
-      Assert
-          .assertEquals(0, ownerIndexCapsule.getFromAccountsList().size());
-      Assert.assertEquals(1, ownerIndexCapsule.getToAccountsList().size());
-      Assert.assertTrue(ownerIndexCapsule.getToAccountsList()
+      assertEquals(0, ownerIndexCapsule.getFromAccountsList().size());
+      assertEquals(1, ownerIndexCapsule.getToAccountsList().size());
+      assertTrue(ownerIndexCapsule.getToAccountsList()
           .contains(ByteString.copyFrom(receiver)));
 
       DelegatedResourceAccountIndexCapsule receiverIndexCapsule = dbManager
           .getDelegatedResourceAccountIndexStore().getV2Index(receiver);
-      Assert
-          .assertEquals(0, receiverIndexCapsule.getToAccountsList().size());
-      Assert
-          .assertEquals(1,
+      assertEquals(0, receiverIndexCapsule.getToAccountsList().size());
+      assertEquals(1,
               receiverIndexCapsule.getFromAccountsList().size());
-      Assert.assertTrue(receiverIndexCapsule.getFromAccountsList()
+      assertTrue(receiverIndexCapsule.getFromAccountsList()
           .contains(ByteString.copyFrom(owner)));
 
     } catch (ContractValidateException | ContractExeException e) {
-      Assert.fail(e.getMessage());
+      fail(e.getMessage());
     }
   }
 
@@ -480,11 +657,11 @@ public void delegateLessThanZero() {
     try {
       actuator.validate();
       actuator.execute(ret);
-      Assert.fail("cannot run here.");
+      fail("cannot run here.");
     } catch (ContractValidateException e) {
-      Assert.assertEquals("delegateBalance must be more than 1TRX", e.getMessage());
+      assertEquals("delegateBalance must be more than 1TRX", e.getMessage());
     } catch (ContractExeException e) {
-      Assert.fail(e.getMessage());
+      fail(e.getMessage());
     }
   }
 
@@ -498,12 +675,12 @@ public void delegateTronPower() {
     try {
       actuator.validate();
       actuator.execute(ret);
-      Assert.fail("cannot run here.");
+      fail("cannot run here.");
     } catch (ContractValidateException e) {
-      Assert.assertEquals("ResourceCode error, valid ResourceCode[BANDWIDTH、ENERGY]",
+      assertEquals("ResourceCode error, valid ResourceCode[BANDWIDTH、ENERGY]",
           e.getMessage());
     } catch (ContractExeException e) {
-      Assert.fail(e.getMessage());
+      fail(e.getMessage());
     }
   }
 
@@ -518,12 +695,12 @@ public void delegateMoreThanBalance() {
     try {
       actuator.validate();
       actuator.execute(ret);
-      Assert.fail("cannot run here.");
+      fail("cannot run here.");
     } catch (ContractValidateException e) {
-      Assert.assertEquals("delegateBalance must be less than available FreezeBandwidthV2 balance",
+      assertEquals("delegateBalance must be less than available FreezeBandwidthV2 balance",
           e.getMessage());
     } catch (ContractExeException e) {
-      Assert.fail(e.getMessage());
+      fail(e.getMessage());
     }
   }
 
@@ -538,15 +715,25 @@ public void invalidOwnerAddress() {
     try {
       actuator.validate();
       actuator.execute(ret);
-      Assert.fail("cannot run here.");
+      fail("cannot run here.");
 
     } catch (ContractValidateException e) {
-      Assert.assertEquals("Invalid address", e.getMessage());
+      assertEquals("Invalid address", e.getMessage());
     } catch (ContractExeException e) {
-      Assert.fail(e.getMessage());
+      fail(e.getMessage());
     }
   }
 
+  @Test
+  public void invalidReceiverAddress() {
+    freezeBandwidthForOwner();
+    DelegateResourceActuator actuator = new DelegateResourceActuator();
+    actuator.setChainBaseManager(dbManager.getChainBaseManager())
+        .setAny(getDelegateContractForBandwidth(
+            OWNER_ADDRESS, OWNER_ADDRESS_INVALID, 1_000_000_000L));
+    assertThrows("Invalid receiverAddress", ContractValidateException.class, actuator::validate);
+  }
+
   @Test
   public void invalidOwnerAccount() {
     DelegateResourceActuator actuator = new DelegateResourceActuator();
@@ -558,12 +745,12 @@ public void invalidOwnerAccount() {
     try {
       actuator.validate();
       actuator.execute(ret);
-      Assert.fail("cannot run here.");
+      fail("cannot run here.");
     } catch (ContractValidateException e) {
-      Assert.assertEquals("Account[" + OWNER_ACCOUNT_INVALID + "] not exists",
+      assertEquals("Account[" + OWNER_ACCOUNT_INVALID + "] not exists",
           e.getMessage());
     } catch (ContractExeException e) {
-      Assert.fail(e.getMessage());
+      fail(e.getMessage());
     }
   }
 
@@ -588,4 +775,20 @@ public void commonErrorCheck() {
     actuatorTest.setNullDBManagerMsg("No account store or dynamic store!");
     actuatorTest.nullDBManger();
   }
+
+  @Test
+  public void testErrorContract() {
+    DelegateResourceActuator actuator = new DelegateResourceActuator();
+    actuator.setChainBaseManager(dbManager.getChainBaseManager()).setAny(getErrorContract());
+    assertThrows(
+        "contract type error, expected type [DelegateResourceContract], "
+            + "real type[WithdrawExpireUnfreezeContract]",
+        ContractValidateException.class, actuator::validate);
+  }
+
+  private Any getErrorContract() {
+    return Any.pack(BalanceContract.WithdrawExpireUnfreezeContract.newBuilder().setOwnerAddress(
+        ByteString.copyFrom(ByteArray.fromHexString(OWNER_ADDRESS))).build()
+    );
+  }
 }
diff --git a/framework/src/test/java/org/tron/core/actuator/utils/TransactionUtilTest.java b/framework/src/test/java/org/tron/core/actuator/utils/TransactionUtilTest.java
index ad8846743e5..6bee877318a 100644
--- a/framework/src/test/java/org/tron/core/actuator/utils/TransactionUtilTest.java
+++ b/framework/src/test/java/org/tron/core/actuator/utils/TransactionUtilTest.java
@@ -1,22 +1,35 @@
 package org.tron.core.actuator.utils;
 
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.tron.core.capsule.utils.TransactionUtil.isNumber;
+import static org.tron.core.utils.TransactionUtil.validAccountId;
+import static org.tron.core.utils.TransactionUtil.validAccountName;
+import static org.tron.core.utils.TransactionUtil.validAssetName;
+import static org.tron.core.utils.TransactionUtil.validTokenAbbrName;
 
+import com.google.protobuf.ByteString;
 import java.nio.charset.StandardCharsets;
 import lombok.extern.slf4j.Slf4j;
+import org.junit.Before;
 import org.junit.BeforeClass;
 import org.junit.Test;
 import org.tron.common.BaseTest;
+import org.tron.common.utils.ByteArray;
 import org.tron.core.Constant;
+import org.tron.core.Wallet;
+import org.tron.core.capsule.AccountCapsule;
 import org.tron.core.config.args.Args;
 import org.tron.core.utils.TransactionUtil;
+import org.tron.protos.Protocol.AccountType;
 
 
 @Slf4j(topic = "capsule")
 public class TransactionUtilTest extends BaseTest {
 
+  private static String OWNER_ADDRESS;
+
   /**
    * Init .
    */
@@ -24,81 +37,96 @@ public class TransactionUtilTest extends BaseTest {
   public static void init() {
     dbPath = "output_transactionUtil_test";
     Args.setParam(new String[]{"--output-directory", dbPath}, Constant.TEST_CONF);
+    OWNER_ADDRESS = Wallet.getAddressPreFixString() + "548794500882809695a8a687866e76d4271a1abc";
+
+  }
+
+  @Before
+  public void setUp() {
+    byte[] owner = ByteArray.fromHexString(OWNER_ADDRESS);
+    AccountCapsule ownerCapsule =
+        new AccountCapsule(
+            ByteString.copyFromUtf8("owner"),
+            ByteString.copyFrom(owner),
+            AccountType.Normal,
+            10_000_000_000L);
+    ownerCapsule.setFrozenForBandwidth(1000000L, 1000000L);
+    dbManager.getAccountStore().put(ownerCapsule.getAddress().toByteArray(), ownerCapsule);
   }
 
   @Test
   public void validAccountNameCheck() {
-    String account = "";
-    assertTrue(TransactionUtil.validAccountName(account.getBytes(StandardCharsets.UTF_8)));
+    StringBuilder account = new StringBuilder();
+    assertTrue(validAccountName(account.toString().getBytes(StandardCharsets.UTF_8)));
     for (int i = 0; i < 200; i++) {
-      account += (char) ('a' + (i % 26));
+      account.append((char) ('a' + (i % 26)));
     }
-    assertTrue(TransactionUtil.validAccountName(account.getBytes(StandardCharsets.UTF_8)));
-    account += 'z';
-    assertFalse(TransactionUtil.validAccountName(account.getBytes(StandardCharsets.UTF_8)));
+    assertTrue(validAccountName(account.toString().getBytes(StandardCharsets.UTF_8)));
+    account.append('z');
+    assertFalse(validAccountName(account.toString().getBytes(StandardCharsets.UTF_8)));
 
   }
 
   @Test
   public void validAccountIdCheck() {
-    String accountId = "";
-    assertFalse(TransactionUtil.validAccountId(accountId.getBytes(StandardCharsets.UTF_8)));
+    StringBuilder accountId = new StringBuilder();
+    assertFalse(validAccountId(accountId.toString().getBytes(StandardCharsets.UTF_8)));
     for (int i = 0; i < 7; i++) {
-      accountId += (char) ('a' + (i % 26));
+      accountId.append((char) ('a' + (i % 26)));
     }
-    assertFalse(TransactionUtil.validAccountId(accountId.getBytes(StandardCharsets.UTF_8)));
+    assertFalse(validAccountId(accountId.toString().getBytes(StandardCharsets.UTF_8)));
     for (int i = 0; i < 26; i++) {
-      accountId += (char) ('a' + (i % 26));
+      accountId.append((char) ('a' + (i % 26)));
     }
-    assertFalse(TransactionUtil.validAccountId(accountId.getBytes(StandardCharsets.UTF_8)));
-    accountId = "ab  cdefghij";
-    assertFalse(TransactionUtil.validAccountId(accountId.getBytes(StandardCharsets.UTF_8)));
-    accountId = (char) 128 + "abcdefjijk" + (char) 129;
-    assertFalse(TransactionUtil.validAccountId(accountId.getBytes(StandardCharsets.UTF_8)));
-    accountId = "";
+    assertFalse(validAccountId(accountId.toString().getBytes(StandardCharsets.UTF_8)));
+    accountId = new StringBuilder("ab  cdefghij");
+    assertFalse(validAccountId(accountId.toString().getBytes(StandardCharsets.UTF_8)));
+    accountId = new StringBuilder((char) 128 + "abcdefjijk" + (char) 129);
+    assertFalse(validAccountId(accountId.toString().getBytes(StandardCharsets.UTF_8)));
+    accountId = new StringBuilder();
     for (int i = 0; i < 30; i++) {
-      accountId += (char) ('a' + (i % 26));
+      accountId.append((char) ('a' + (i % 26)));
     }
-    assertTrue(TransactionUtil.validAccountId(accountId.getBytes(StandardCharsets.UTF_8)));
+    assertTrue(validAccountId(accountId.toString().getBytes(StandardCharsets.UTF_8)));
 
   }
 
   @Test
   public void validAssetNameCheck() {
-    String assetName = "";
-    assertFalse(TransactionUtil.validAssetName(assetName.getBytes(StandardCharsets.UTF_8)));
+    StringBuilder assetName = new StringBuilder();
+    assertFalse(validAssetName(assetName.toString().getBytes(StandardCharsets.UTF_8)));
     for (int i = 0; i < 33; i++) {
-      assetName += (char) ('a' + (i % 26));
+      assetName.append((char) ('a' + (i % 26)));
     }
-    assertFalse(TransactionUtil.validAssetName(assetName.getBytes(StandardCharsets.UTF_8)));
-    assetName = "ab  cdefghij";
-    assertFalse(TransactionUtil.validAssetName(assetName.getBytes(StandardCharsets.UTF_8)));
-    assetName = (char) 128 + "abcdefjijk" + (char) 129;
-    assertFalse(TransactionUtil.validAssetName(assetName.getBytes(StandardCharsets.UTF_8)));
-    assetName = "";
+    assertFalse(validAssetName(assetName.toString().getBytes(StandardCharsets.UTF_8)));
+    assetName = new StringBuilder("ab  cdefghij");
+    assertFalse(validAssetName(assetName.toString().getBytes(StandardCharsets.UTF_8)));
+    assetName = new StringBuilder((char) 128 + "abcdefjijk" + (char) 129);
+    assertFalse(validAssetName(assetName.toString().getBytes(StandardCharsets.UTF_8)));
+    assetName = new StringBuilder();
     for (int i = 0; i < 20; i++) {
-      assetName += (char) ('a' + (i % 26));
+      assetName.append((char) ('a' + (i % 26)));
     }
-    assertTrue(TransactionUtil.validAssetName(assetName.getBytes(StandardCharsets.UTF_8)));
+    assertTrue(validAssetName(assetName.toString().getBytes(StandardCharsets.UTF_8)));
   }
 
   @Test
   public void validTokenAbbrNameCheck() {
-    String abbrName = "";
-    assertFalse(TransactionUtil.validTokenAbbrName(abbrName.getBytes(StandardCharsets.UTF_8)));
+    StringBuilder abbrName = new StringBuilder();
+    assertFalse(validTokenAbbrName(abbrName.toString().getBytes(StandardCharsets.UTF_8)));
     for (int i = 0; i < 6; i++) {
-      abbrName += (char) ('a' + (i % 26));
+      abbrName.append((char) ('a' + (i % 26)));
     }
-    assertFalse(TransactionUtil.validTokenAbbrName(abbrName.getBytes(StandardCharsets.UTF_8)));
-    abbrName = "a bd";
-    assertFalse(TransactionUtil.validTokenAbbrName(abbrName.getBytes(StandardCharsets.UTF_8)));
-    abbrName = "a" + (char) 129 + 'f';
-    assertFalse(TransactionUtil.validTokenAbbrName(abbrName.getBytes(StandardCharsets.UTF_8)));
-    abbrName = "";
+    assertFalse(validTokenAbbrName(abbrName.toString().getBytes(StandardCharsets.UTF_8)));
+    abbrName = new StringBuilder("a bd");
+    assertFalse(validTokenAbbrName(abbrName.toString().getBytes(StandardCharsets.UTF_8)));
+    abbrName = new StringBuilder("a" + (char) 129 + 'f');
+    assertFalse(validTokenAbbrName(abbrName.toString().getBytes(StandardCharsets.UTF_8)));
+    abbrName = new StringBuilder();
     for (int i = 0; i < 5; i++) {
-      abbrName += (char) ('a' + (i % 26));
+      abbrName.append((char) ('a' + (i % 26)));
     }
-    assertTrue(TransactionUtil.validTokenAbbrName(abbrName.getBytes(StandardCharsets.UTF_8)));
+    assertTrue(validTokenAbbrName(abbrName.toString().getBytes(StandardCharsets.UTF_8)));
   }
 
   @Test
@@ -114,4 +142,13 @@ public void isNumberCheck() {
     assertTrue(isNumber(number.getBytes(StandardCharsets.UTF_8)));
   }
 
+  @Test
+  public void testEstimateConsumeBandWidthSize() {
+    AccountCapsule ownerCapsule =
+        dbManager.getAccountStore().get(ByteArray.fromHexString(OWNER_ADDRESS));
+    long estimateConsumeBandWidthSize = TransactionUtil.estimateConsumeBandWidthSize(ownerCapsule,
+        dbManager.getChainBaseManager());
+    assertEquals(278L, estimateConsumeBandWidthSize);
+  }
+
 }
diff --git a/protocol/src/main/protos/core/contract/balance_contract.proto b/protocol/src/main/protos/core/contract/balance_contract.proto
index 90a2dfaa48d..9c0bded1cbc 100644
--- a/protocol/src/main/protos/core/contract/balance_contract.proto
+++ b/protocol/src/main/protos/core/contract/balance_contract.proto
@@ -103,6 +103,7 @@ message DelegateResourceContract {
   int64 balance = 3;
   bytes receiver_address = 4;
   bool  lock = 5;
+  int64 lock_period = 6;
 }
 
 message UnDelegateResourceContract {