From 62b6053e87cb1b2a2f6c4cc0e43831da97a6fe36 Mon Sep 17 00:00:00 2001
From: Bushstar <bushsolo@gmail.com>
Date: Thu, 13 Jul 2023 10:35:42 +0100
Subject: [PATCH 1/4] Feature gate transferdomain

---
 src/chainparams.cpp                         |  1 +
 src/init.cpp                                |  1 +
 src/masternodes/errors.h                    | 16 ++++
 src/masternodes/govvariables/attributes.cpp | 87 +++++++++++++++++----
 src/masternodes/govvariables/attributes.h   | 13 +++
 src/masternodes/mn_checks.cpp               | 39 ++++++++-
 src/masternodes/mn_checks.h                 |  1 +
 test/functional/feature_evm.py              | 31 +++++++-
 8 files changed, 169 insertions(+), 20 deletions(-)

diff --git a/src/chainparams.cpp b/src/chainparams.cpp
index fb4db3b9ec..6bf772123c 100644
--- a/src/chainparams.cpp
+++ b/src/chainparams.cpp
@@ -1358,6 +1358,7 @@ void SetupCommonArgActivationParams(Consensus::Params &consensus) {
     UpdateHeightValidation("Changi Intermediate", "-changiintermediateheight", consensus.ChangiIntermediateHeight);
     UpdateHeightValidation("Changi Intermediate2", "-changiintermediate2height", consensus.ChangiIntermediateHeight2);
     UpdateHeightValidation("Changi Intermediate3", "-changiintermediate3height", consensus.ChangiIntermediateHeight3);
+    UpdateHeightValidation("Changi Intermediate4", "-changiintermediate4height", consensus.ChangiIntermediateHeight4);
 
     if (gArgs.GetBoolArg("-simulatemainnet", false)) {
         consensus.pos.nTargetTimespan = 5 * 60; // 5 min == 10 blocks
diff --git a/src/init.cpp b/src/init.cpp
index 79302546c3..e684b35371 100644
--- a/src/init.cpp
+++ b/src/init.cpp
@@ -518,6 +518,7 @@ void SetupServerArgs()
     gArgs.AddArg("-changiintermediateheight", "Changi Intermediate fork activation height (regtest only)", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::CHAINPARAMS);
     gArgs.AddArg("-changiintermediate2height", "Changi Intermediate2 fork activation height (regtest only)", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::CHAINPARAMS);
     gArgs.AddArg("-changiintermediate3height", "Changi Intermediate3 fork activation height (regtest only)", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::CHAINPARAMS);
+    gArgs.AddArg("-changiintermediate4height", "Changi Intermediate4 fork activation height (regtest only)", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::CHAINPARAMS);
     gArgs.AddArg("-jellyfish_regtest", "Configure the regtest network for jellyfish testing", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::OPTIONS);
     gArgs.AddArg("-regtest-skip-loan-collateral-validation", "Skip loan collateral check for jellyfish testing", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::OPTIONS);
     gArgs.AddArg("-regtest-minttoken-simulate-mainnet", "Simulate mainnet for minttokens on regtest -  default behavior on regtest is to allow anyone to mint mintable tokens for ease of testing", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::OPTIONS);
diff --git a/src/masternodes/errors.h b/src/masternodes/errors.h
index 1c9d1e4cf0..bd26a246bb 100644
--- a/src/masternodes/errors.h
+++ b/src/masternodes/errors.h
@@ -262,6 +262,10 @@ class DeFiErrors {
         return Res::Err("Unsupported key for Governance Proposal section - {%d}", type);
     }
 
+    static Res GovVarVariableUnsupportedTransferType(const unsigned char type) {
+        return Res::Err("Unsupported key for Transfer Domain {%d}", type);
+    }
+
     static Res GovVarVariableUnsupportedParamType() {
         return Res::Err("Unsupported Param ID");
     }
@@ -410,6 +414,10 @@ class DeFiErrors {
         return Res::Err("tx must have at least one input from account owner");
     }
 
+    static Res TransferDomainNotEnabled() {
+        return Res::Err("Cannot create tx, transfer domain is not enabled");
+    }
+
     static Res TransferDomainEVMNotEnabled() {
         return Res::Err("Cannot create tx, EVM is not enabled");
     }
@@ -426,6 +434,14 @@ class DeFiErrors {
         return Res::Err("For transferdomain, only DFI token is currently supported");
     }
 
+    static Res TransferDomainEVMDVMNotEnabled() {
+        return Res::Err("EVM to DVM is not currently enabled");
+    }
+
+    static Res TransferDomainDVMEVMNotEnabled() {
+        return Res::Err("DVM to EVM is not currently enabled");
+    }
+
     static Res TransferDomainETHSourceAddress() {
         return Res::Err("Src address must be an ETH address in case of \"EVM\" domain");
     }
diff --git a/src/masternodes/govvariables/attributes.cpp b/src/masternodes/govvariables/attributes.cpp
index 325bce1d0e..6aa2269f3b 100644
--- a/src/masternodes/govvariables/attributes.cpp
+++ b/src/masternodes/govvariables/attributes.cpp
@@ -51,27 +51,29 @@ const std::map<uint8_t, std::string> &ATTRIBUTES::displayVersions() {
 
 const std::map<std::string, uint8_t> &ATTRIBUTES::allowedTypes() {
     static const std::map<std::string, uint8_t> types{
-        {"locks",      AttributeTypes::Locks     },
-        {"oracles",    AttributeTypes::Oracles   },
-        {"params",     AttributeTypes::Param     },
-        {"poolpairs",  AttributeTypes::Poolpairs },
-        {"token",      AttributeTypes::Token     },
-        {"gov",        AttributeTypes::Governance},
-        {"consortium", AttributeTypes::Consortium},
+        {"locks",          AttributeTypes::Locks     },
+        {"oracles",        AttributeTypes::Oracles   },
+        {"params",         AttributeTypes::Param     },
+        {"poolpairs",      AttributeTypes::Poolpairs },
+        {"token",          AttributeTypes::Token     },
+        {"gov",            AttributeTypes::Governance},
+        {"consortium",     AttributeTypes::Consortium},
+        {"transferdomain", AttributeTypes::Transfer  },
     };
     return types;
 }
 
 const std::map<uint8_t, std::string> &ATTRIBUTES::displayTypes() {
     static const std::map<uint8_t, std::string> types{
-        {AttributeTypes::Live,       "live"      },
-        {AttributeTypes::Locks,      "locks"     },
-        {AttributeTypes::Oracles,    "oracles"   },
-        {AttributeTypes::Param,      "params"    },
-        {AttributeTypes::Poolpairs,  "poolpairs" },
-        {AttributeTypes::Token,      "token"     },
-        {AttributeTypes::Governance, "gov"       },
-        {AttributeTypes::Consortium, "consortium"},
+        {AttributeTypes::Live,       "live"          },
+        {AttributeTypes::Locks,      "locks"         },
+        {AttributeTypes::Oracles,    "oracles"       },
+        {AttributeTypes::Param,      "params"        },
+        {AttributeTypes::Poolpairs,  "poolpairs"     },
+        {AttributeTypes::Token,      "token"         },
+        {AttributeTypes::Governance, "gov"           },
+        {AttributeTypes::Consortium, "consortium"    },
+        {AttributeTypes::Transfer,   "transferdomain"},
     };
     return types;
 }
@@ -142,6 +144,20 @@ const std::map<uint8_t, std::string> &ATTRIBUTES::displayGovernanceIDs() {
     return params;
 }
 
+const std::map<std::string, uint8_t> &ATTRIBUTES::allowedTransferIDs() {
+    static const std::map<std::string, uint8_t> params{
+            {"edges", TransferIDs::Edges},
+    };
+    return params;
+}
+
+const std::map<uint8_t, std::string> &ATTRIBUTES::displayTransferIDs() {
+    static const std::map<uint8_t, std::string> params{
+            {TransferIDs::Edges, "edges"},
+    };
+    return params;
+}
+
 const std::map<uint8_t, std::map<std::string, uint8_t>> &ATTRIBUTES::allowedKeys() {
     static const std::map<uint8_t, std::map<std::string, uint8_t>> keys{
         {AttributeTypes::Token,
@@ -197,6 +213,7 @@ const std::map<uint8_t, std::map<std::string, uint8_t>> &ATTRIBUTES::allowedKeys
              {"emission-unused-fund", DFIPKeys::EmissionUnusedFund},
              {"mint-tokens-to-address", DFIPKeys::MintTokens},
              {"allow-dusd-loops", DFIPKeys::AllowDUSDLoops},
+             {"transferdomain", DFIPKeys::TransferDomain},
          }},
         {AttributeTypes::Governance,
          {
@@ -213,6 +230,11 @@ const std::map<uint8_t, std::map<std::string, uint8_t>> &ATTRIBUTES::allowedKeys
              {"voting_period", GovernanceKeys::VotingPeriod},
              {"cfp_max_cycles", GovernanceKeys::CFPMaxCycles},
          }},
+        {AttributeTypes::Transfer,
+         {
+            {"evm-dvm", TransferKeys::EVM_DVM},
+            {"dvm-evm", TransferKeys::DVM_EVM},
+         }},
     };
     return keys;
 }
@@ -275,6 +297,7 @@ const std::map<uint8_t, std::map<uint8_t, std::string>> &ATTRIBUTES::displayKeys
              {DFIPKeys::EmissionUnusedFund, "emission-unused-fund"},
              {DFIPKeys::MintTokens, "mint-tokens-to-address"},
              {DFIPKeys::AllowDUSDLoops, "allow-dusd-loops"},
+             {DFIPKeys::TransferDomain, "transferdomain"},
          }},
         {AttributeTypes::Live,
          {
@@ -310,6 +333,11 @@ const std::map<uint8_t, std::map<uint8_t, std::string>> &ATTRIBUTES::displayKeys
              {GovernanceKeys::VotingPeriod, "voting_period"},
              {GovernanceKeys::CFPMaxCycles, "cfp_max_cycles"},
          }},
+        {AttributeTypes::Transfer,
+         {
+            {TransferKeys::EVM_DVM, "evm-dvm"},
+            {TransferKeys::DVM_EVM, "dvm-evm"},
+         }},
     };
     return keys;
 }
@@ -604,6 +632,7 @@ const std::map<uint8_t, std::map<uint8_t, std::function<ResVal<CAttributeValue>(
                  {DFIPKeys::EmissionUnusedFund, VerifyBool},
                  {DFIPKeys::MintTokens, VerifyBool},
                  {DFIPKeys::AllowDUSDLoops, VerifyBool},
+                 {DFIPKeys::TransferDomain, VerifyBool},
              }},
             {AttributeTypes::Locks,
              {
@@ -628,6 +657,11 @@ const std::map<uint8_t, std::map<uint8_t, std::function<ResVal<CAttributeValue>(
                  {GovernanceKeys::VotingPeriod, VerifyUInt32},
                  {GovernanceKeys::CFPMaxCycles, VerifyUInt32},
              }},
+            {AttributeTypes::Transfer,
+             {
+                {TransferKeys::EVM_DVM, VerifyBool},
+                {TransferKeys::DVM_EVM, VerifyBool},
+             }},
     };
     return parsers;
 }
@@ -747,6 +781,12 @@ Res ATTRIBUTES::ProcessVariable(const std::string &key,
             return DeFiErrors::GovVarVariableInvalidKey("governance", allowedGovernanceIDs());
         }
         typeId = id->second;
+    } else if (type == AttributeTypes::Transfer) {
+        auto id = allowedTransferIDs().find(keys[2]);
+        if (id == allowedTransferIDs().end()) {
+            return DeFiErrors::GovVarVariableInvalidKey("transferdomain", allowedTransferIDs());
+        }
+        typeId = id->second;
     } else {
         auto id = VerifyInt32(keys[2]);
         if (!id) {
@@ -815,7 +855,7 @@ Res ATTRIBUTES::ProcessVariable(const std::string &key,
                     typeKey != DFIPKeys::ConsortiumEnabled && typeKey != DFIPKeys::CFPPayout &&
                     typeKey != DFIPKeys::EmissionUnusedFund && typeKey != DFIPKeys::MintTokens &&
                     typeKey != DFIPKeys::EVMEnabled && typeKey != DFIPKeys::ICXEnabled &&
-                    typeKey != DFIPKeys::AllowDUSDLoops) {
+                    typeKey != DFIPKeys::AllowDUSDLoops && typeKey != DFIPKeys::TransferDomain) {
                     return DeFiErrors::GovVarVariableUnsupportedFeatureType(typeKey);
                 }
             } else if (typeId == ParamIDs::Foundation) {
@@ -837,6 +877,13 @@ Res ATTRIBUTES::ProcessVariable(const std::string &key,
             } else {
                 return DeFiErrors::GovVarVariableUnsupportedGovType();
             }
+        } else if (type == AttributeTypes::Transfer) {
+            if (typeId == TransferIDs::Edges) {
+                if (typeKey != TransferKeys::DVM_EVM && typeKey != TransferKeys::EVM_DVM)
+                    return DeFiErrors::GovVarVariableUnsupportedTransferType(typeKey);
+            } else {
+                return DeFiErrors::GovVarVariableUnsupportedGovType();
+            }
         }
 
         attrV0 = CDataStructureV0{type, typeId, typeKey};
@@ -1145,6 +1192,8 @@ UniValue ATTRIBUTES::ExportFiltered(GovVarsFilter filter, const std::string &pre
                 id = displayOracleIDs().at(attrV0->typeId);
             } else if (attrV0->type == AttributeTypes::Governance) {
                 id = displayGovernanceIDs().at(attrV0->typeId);
+            }  else if (attrV0->type == AttributeTypes::Transfer) {
+                id = displayTransferIDs().at(attrV0->typeId);
             } else {
                 id = KeyBuilder(attrV0->typeId);
             }
@@ -1556,6 +1605,12 @@ Res ATTRIBUTES::Validate(const CCustomCSView &view) const {
                 }
                 break;
 
+            case AttributeTypes::Transfer:
+                if (view.GetLastHeight() < Params().GetConsensus().NextNetworkUpgradeHeight) {
+                    return Res::Err("Cannot be set before NextNetworkUpgrade");
+                }
+                break;
+
             default:
                 return Res::Err("Unrecognised type (%d)", attrV0->type);
         }
diff --git a/src/masternodes/govvariables/attributes.h b/src/masternodes/govvariables/attributes.h
index 7a1963767b..973eb925cb 100644
--- a/src/masternodes/govvariables/attributes.h
+++ b/src/masternodes/govvariables/attributes.h
@@ -23,6 +23,7 @@ enum AttributeTypes : uint8_t {
     Locks      = 'L',
     Governance = 'g',
     Consortium = 'c',
+    Transfer   = 'b',
 };
 
 enum ParamIDs : uint8_t {
@@ -46,6 +47,10 @@ enum GovernanceIDs : uint8_t {
     Proposals = 'b',
 };
 
+enum TransferIDs : uint8_t {
+    Edges    = 'a',
+};
+
 enum EconomyKeys : uint8_t {
     PaybackDFITokens          = 'a',
     PaybackTokens             = 'b',
@@ -89,6 +94,7 @@ enum DFIPKeys : uint8_t {
     EVMEnabled           = 'u',
     ICXEnabled           = 'v',
     AllowDUSDLoops       = 'w',
+    TransferDomain       = 'x',
 };
 
 enum GovernanceKeys : uint8_t {
@@ -138,6 +144,11 @@ enum PoolKeys : uint8_t {
     TokenBFeeDir = 'd',
 };
 
+enum TransferKeys : uint8_t {
+    DVM_EVM = 'a',
+    EVM_DVM = 'b',
+};
+
 struct CDataStructureV0 {
     uint8_t type;
     uint32_t typeId;
@@ -424,6 +435,7 @@ class ATTRIBUTES : public GovVariable, public AutoRegistrator<GovVariable, ATTRI
     static const std::map<uint8_t, std::string> &displayOracleIDs();
     static const std::map<uint8_t, std::string> &displayConsortiumIDs();
     static const std::map<uint8_t, std::string> &displayGovernanceIDs();
+    static const std::map<uint8_t, std::string> &displayTransferIDs();
     static const std::map<uint8_t, std::map<uint8_t, std::string>> &displayKeys();
 
     Res RefundFuturesContracts(CCustomCSView &mnview,
@@ -447,6 +459,7 @@ class ATTRIBUTES : public GovVariable, public AutoRegistrator<GovVariable, ATTRI
     static const std::map<std::string, uint8_t> &allowedOracleIDs();
     static const std::map<std::string, uint8_t> &allowedConsortiumIDs();
     static const std::map<std::string, uint8_t> &allowedGovernanceIDs();
+    static const std::map<std::string, uint8_t> &allowedTransferIDs();
     static const std::map<uint8_t, std::map<std::string, uint8_t>> &allowedKeys();
     static const std::map<uint8_t, std::map<uint8_t, std::function<ResVal<CAttributeValue>(const std::string &)>>>
         &parseValue();
diff --git a/src/masternodes/mn_checks.cpp b/src/masternodes/mn_checks.cpp
index 1b19725ee2..9da7317f08 100644
--- a/src/masternodes/mn_checks.cpp
+++ b/src/masternodes/mn_checks.cpp
@@ -4019,10 +4019,12 @@ static Res ValidateTransferDomainScripts(const CScript &srcScript, const CScript
 }
 
 Res ValidateTransferDomainEdge(const CTransaction &tx,
+                                   const CCustomCSView &mnview,
                                    uint32_t height,
                                    const CCoinsViewCache &coins,
                                    const Consensus::Params &consensus,
-                                   CTransferDomainItem src, CTransferDomainItem dst) {
+                                   CTransferDomainItem src,
+                                   CTransferDomainItem dst) {
 
     // TODO: Remove code branch on stable.
     if (height < static_cast<uint32_t>(consensus.ChangiIntermediateHeight3)) {
@@ -4039,7 +4041,19 @@ Res ValidateTransferDomainEdge(const CTransaction &tx,
     if (src.amount.nTokenId != DCT_ID{0} || dst.amount.nTokenId != DCT_ID{0})
         return DeFiErrors::TransferDomainIncorrectToken();
 
+    CDataStructureV0 evm_dvm{AttributeTypes::Transfer, TransferIDs::Edges, TransferKeys::EVM_DVM};
+    CDataStructureV0 dvm_evm{AttributeTypes::Transfer, TransferIDs::Edges, TransferKeys::DVM_EVM};
+
+    const auto attributes = mnview.GetAttributes();
+    assert(attributes);
+
     if (src.domain == static_cast<uint8_t>(VMDomain::DVM) && dst.domain == static_cast<uint8_t>(VMDomain::EVM)) {
+        if (height >= static_cast<uint32_t>(consensus.ChangiIntermediateHeight4)) {
+            if (!attributes->GetValue(dvm_evm, false)) {
+                return DeFiErrors::TransferDomainDVMEVMNotEnabled();
+            }
+        }
+
         // DVM to EVM
         auto res = ValidateTransferDomainScripts(src.address, dst.address, VMDomainEdge::DVMToEVM);
         if (!res) return res;
@@ -4047,6 +4061,12 @@ Res ValidateTransferDomainEdge(const CTransaction &tx,
         return HasAuth(tx, coins, src.address);
 
     } else if (src.domain == static_cast<uint8_t>(VMDomain::EVM) && dst.domain == static_cast<uint8_t>(VMDomain::DVM)) {
+        if (height >= static_cast<uint32_t>(consensus.ChangiIntermediateHeight4)) {
+            if (!attributes->GetValue(evm_dvm, false)) {
+                return DeFiErrors::TransferDomainEVMDVMNotEnabled();
+            }
+        }
+
         // EVM to DVM
         auto res = ValidateTransferDomainScripts(src.address, dst.address, VMDomainEdge::EVMToDVM);
         if (!res) return res;
@@ -4069,6 +4089,10 @@ Res ValidateTransferDomain(const CTransaction &tx,
         return DeFiErrors::TransferDomainEVMNotEnabled();
     }
 
+    if (!IsTransferDomainEnabled(height, mnview, consensus)) {
+        return DeFiErrors::TransferDomainNotEnabled();
+    }
+
     if (height >= static_cast<uint32_t>(consensus.ChangiIntermediateHeight4)) {
         if (obj.transfers.size() < 1) {
             return DeFiErrors::TransferDomainInvalid();
@@ -4076,7 +4100,7 @@ Res ValidateTransferDomain(const CTransaction &tx,
     }
 
     for (const auto &[src, dst] : obj.transfers) {
-        auto res = ValidateTransferDomainEdge(tx, height, coins, consensus, src, dst);
+        auto res = ValidateTransferDomainEdge(tx, mnview, height, coins, consensus, src, dst);
         if (!res) return res;
     }
 
@@ -5088,3 +5112,14 @@ bool IsEVMEnabled(const int height, const CCustomCSView &view, const Consensus::
     assert(attributes);
     return attributes->GetValue(enabledKey, false);
 }
+
+bool IsTransferDomainEnabled(const int height, const CCustomCSView &view, const Consensus::Params &consensus) {
+    if (height < consensus.NextNetworkUpgradeHeight) {
+        return false;
+    }
+
+    const CDataStructureV0 enabledKey{AttributeTypes::Param, ParamIDs::Feature, DFIPKeys::TransferDomain};
+    auto attributes = view.GetAttributes();
+    assert(attributes);
+    return attributes->GetValue(enabledKey, false);
+}
diff --git a/src/masternodes/mn_checks.h b/src/masternodes/mn_checks.h
index 1e59fede46..127629daeb 100644
--- a/src/masternodes/mn_checks.h
+++ b/src/masternodes/mn_checks.h
@@ -523,6 +523,7 @@ bool IsTestNetwork();
 bool IsMainNetwork();
 bool IsICXEnabled(const int height, const CCustomCSView &view, const Consensus::Params &consensus);
 bool IsEVMEnabled(const int height, const CCustomCSView &view, const Consensus::Params &consensus);
+bool IsTransferDomainEnabled(const int height, const CCustomCSView &view, const Consensus::Params &consensus);
 Res HasAuth(const CTransaction &tx, const CCoinsViewCache &coins, const CScript &auth, AuthStrategy strategy = AuthStrategy::DirectPubKeyMatch);
 Res ValidateTransferDomain(const CTransaction &tx,
                                    uint32_t height,
diff --git a/test/functional/feature_evm.py b/test/functional/feature_evm.py
index 51d2663bf0..4499f65aa4 100755
--- a/test/functional/feature_evm.py
+++ b/test/functional/feature_evm.py
@@ -21,8 +21,8 @@ def set_test_params(self):
         self.num_nodes = 2
         self.setup_clean_chain = True
         self.extra_args = [
-            ['-txordering=2', '-dummypos=0', '-txnotokens=0', '-amkheight=50', '-bayfrontheight=51', '-eunosheight=80', '-fortcanningheight=82', '-fortcanninghillheight=84', '-fortcanningroadheight=86', '-fortcanningcrunchheight=88', '-fortcanningspringheight=90', '-fortcanninggreatworldheight=94', '-fortcanningepilogueheight=96', '-grandcentralheight=101', '-nextnetworkupgradeheight=105', '-changiintermediateheight=105', '-changiintermediate3height=105', '-subsidytest=1', '-txindex=1'],
-            ['-txordering=2', '-dummypos=0', '-txnotokens=0', '-amkheight=50', '-bayfrontheight=51', '-eunosheight=80', '-fortcanningheight=82', '-fortcanninghillheight=84', '-fortcanningroadheight=86', '-fortcanningcrunchheight=88', '-fortcanningspringheight=90', '-fortcanninggreatworldheight=94', '-fortcanningepilogueheight=96', '-grandcentralheight=101', '-nextnetworkupgradeheight=105', '-changiintermediateheight=105', '-changiintermediate3height=105', '-subsidytest=1', '-txindex=1']
+            ['-txordering=2', '-dummypos=0', '-txnotokens=0', '-amkheight=50', '-bayfrontheight=51', '-eunosheight=80', '-fortcanningheight=82', '-fortcanninghillheight=84', '-fortcanningroadheight=86', '-fortcanningcrunchheight=88', '-fortcanningspringheight=90', '-fortcanninggreatworldheight=94', '-fortcanningepilogueheight=96', '-grandcentralheight=101', '-nextnetworkupgradeheight=105', '-changiintermediateheight=105', '-changiintermediate3height=105', '-changiintermediate4height=105', '-subsidytest=1', '-txindex=1'],
+            ['-txordering=2', '-dummypos=0', '-txnotokens=0', '-amkheight=50', '-bayfrontheight=51', '-eunosheight=80', '-fortcanningheight=82', '-fortcanninghillheight=84', '-fortcanningroadheight=86', '-fortcanningcrunchheight=88', '-fortcanningspringheight=90', '-fortcanninggreatworldheight=94', '-fortcanningepilogueheight=96', '-grandcentralheight=101', '-nextnetworkupgradeheight=105', '-changiintermediateheight=105', '-changiintermediate3height=105', '-changiintermediate4height=105', '-subsidytest=1', '-txindex=1']
         ]
 
     def test_tx_without_chainid(self, node, keypair, web3):
@@ -86,10 +86,28 @@ def run_test(self):
         # Move to fork height
         self.nodes[0].generate(4)
 
+        # Check error before EVM enabled
+        assert_raises_rpc_error(-32600, "Cannot create tx, EVM is not enabled", self.nodes[0].evmtx, eth_address, 0, 21, 21000, to_address, 0.1)
+        assert_raises_rpc_error(-32600, "Cannot create tx, EVM is not enabled", self.nodes[0].transferdomain, [{"src": {"address":address, "amount":"100@DFI", "domain": 2}, "dst":{"address":eth_address, "amount":"100@DFI", "domain": 3}}])
+
         # Activate EVM
         self.nodes[0].setgov({"ATTRIBUTES": {'v0/params/feature/evm': 'true'}})
         self.nodes[0].generate(1)
 
+        # Check error before transferdomain enabled
+        assert_raises_rpc_error(-32600, "Cannot create tx, transfer domain is not enabled", self.nodes[0].transferdomain, [{"src": {"address":address, "amount":"100@DFI", "domain": 2}, "dst":{"address":eth_address, "amount":"100@DFI", "domain": 3}}])
+
+        # Activate transferdomain
+        self.nodes[0].setgov({"ATTRIBUTES": {'v0/params/feature/transferdomain': 'true'}})
+        self.nodes[0].generate(1)
+
+        # Check error before transferdomain DVM to EVM is enabled
+        assert_raises_rpc_error(-32600, "DVM to EVM is not currently enabled", self.nodes[0].transferdomain, [{"src": {"address":address, "amount":"100@DFI", "domain": 2}, "dst":{"address":eth_address, "amount":"100@DFI", "domain": 3}}])
+
+        # Activate transferdomain DVM to EVM
+        self.nodes[0].setgov({"ATTRIBUTES": {'v0/transferdomain/edges/dvm-evm': 'true'}})
+        self.nodes[0].generate(1)
+
         # Check transferdomain without DFI balance before DFI address is funded
         assert_raises_rpc_error(-32600, "amount 0.00000000 is less than 100.00000000", self.nodes[0].transferdomain, [{"src": {"address":address, "amount":"100@DFI", "domain": 2}, "dst":{"address":eth_address, "amount":"100@DFI", "domain": 3}}])
 
@@ -140,6 +158,15 @@ def run_test(self):
         assert_raises_rpc_error(-32600, "For transferdomain, only DFI token is currently supported", self.nodes[0].transferdomain, [{"src": {"address":address, "amount":"100@DFI", "domain": 2}, "dst":{"address":eth_address, "amount":"100@BTC", "domain": 3}}])
         assert_raises_rpc_error(-32600, "Excess data set, maximum allow is 0", self.nodes[0].transferdomain, [{"src": {"address":address, "amount":"100@DFI", "domain": 2, "data": "1"}, "dst":{"address":eth_address, "amount":"100@DFI", "domain": 3}}])
         assert_raises_rpc_error(-32600, "Excess data set, maximum allow is 0", self.nodes[0].transferdomain, [{"src": {"address":address, "amount":"100@DFI", "domain": 2}, "dst":{"address":eth_address, "amount":"100@DFI", "domain": 3, "data": "1"}}])
+
+        # Check error before transferdomain DVM to EVM is enabled
+        assert_raises_rpc_error(-32600, "EVM to DVM is not currently enabled", self.nodes[0].transferdomain, [{"src": {"address":address, "amount":"100@DFI", "domain": 3}, "dst":{"address":eth_address, "amount":"100@DFI", "domain": 2}}])
+
+        # Activate transferdomain DVM to EVM
+        self.nodes[0].setgov({"ATTRIBUTES": {'v0/transferdomain/edges/evm-dvm': 'true'}})
+        self.nodes[0].generate(1)
+
+        # Test not enough balance for EVM to DVM transfer
         assert_raises_rpc_error(-32600, "Not enough balance in " + eth_address + " to cover \"EVM\" domain transfer", self.nodes[0].transferdomain, [{"src": {"address":eth_address, "amount":"100@DFI", "domain": 3}, "dst":{"address":address, "amount":"100@DFI", "domain": 2}}])
 
         # Transfer 100 DFI from DVM to EVM

From a96fd2210e9b45493a0d8d14fe50764bbb59f221 Mon Sep 17 00:00:00 2001
From: Bushstar <bushsolo@gmail.com>
Date: Thu, 13 Jul 2023 10:43:54 +0100
Subject: [PATCH 2/4] Remove gas used check

---
 test/functional/feature_evm.py | 3 ---
 1 file changed, 3 deletions(-)

diff --git a/test/functional/feature_evm.py b/test/functional/feature_evm.py
index 4499f65aa4..76b36f9968 100755
--- a/test/functional/feature_evm.py
+++ b/test/functional/feature_evm.py
@@ -400,7 +400,6 @@ def run_test(self):
         # Check EVM blockhash
         eth_block = self.nodes[0].eth_getBlockByNumber('latest')
         eth_hash = eth_block['hash'][2:]
-        eth_fee = eth_block['gasUsed'][2:]
         block = self.nodes[0].getblock(self.nodes[0].getblockhash(self.nodes[0].getblockcount()))
         raw_tx = self.nodes[0].getrawtransaction(block['tx'][0], 1)
         block_hash = raw_tx['vout'][1]['scriptPubKey']['hex'][20:84]
@@ -409,8 +408,6 @@ def run_test(self):
         # Check EVM miner fee
         opreturn_fee_amount = raw_tx['vout'][1]['scriptPubKey']['hex'][84:]
         opreturn_fee_sats = Decimal(int(opreturn_fee_amount[2:4] + opreturn_fee_amount[0:2], 16)) / 100000000
-        eth_fee_sats = Decimal(int(Decimal(int(eth_fee, 16)) / 10)) / 100000000
-        assert_equal(opreturn_fee_sats, eth_fee_sats)
         assert_equal(opreturn_fee_sats, miner_fee)
 
         # Test rollback of EVM TX

From 03389edc2098882990fa1a0a404b7f7f501fff4b Mon Sep 17 00:00:00 2001
From: Bushstar <bushsolo@gmail.com>
Date: Fri, 14 Jul 2023 06:47:50 +0100
Subject: [PATCH 3/4] Add TransferDomainLiveConfig

---
 src/masternodes/mn_checks.cpp | 25 +++++++++++++++----------
 1 file changed, 15 insertions(+), 10 deletions(-)

diff --git a/src/masternodes/mn_checks.cpp b/src/masternodes/mn_checks.cpp
index 9da7317f08..2f953bdb37 100644
--- a/src/masternodes/mn_checks.cpp
+++ b/src/masternodes/mn_checks.cpp
@@ -4018,8 +4018,13 @@ static Res ValidateTransferDomainScripts(const CScript &srcScript, const CScript
     return DeFiErrors::TransferDomainUnknownEdge();
 }
 
+struct TransferDomainLiveConfig {
+    bool dvmToEvm;
+    bool evmTodvm;
+};
+
 Res ValidateTransferDomainEdge(const CTransaction &tx,
-                                   const CCustomCSView &mnview,
+                                   const TransferDomainLiveConfig &transferdomainConfig,
                                    uint32_t height,
                                    const CCoinsViewCache &coins,
                                    const Consensus::Params &consensus,
@@ -4041,15 +4046,9 @@ Res ValidateTransferDomainEdge(const CTransaction &tx,
     if (src.amount.nTokenId != DCT_ID{0} || dst.amount.nTokenId != DCT_ID{0})
         return DeFiErrors::TransferDomainIncorrectToken();
 
-    CDataStructureV0 evm_dvm{AttributeTypes::Transfer, TransferIDs::Edges, TransferKeys::EVM_DVM};
-    CDataStructureV0 dvm_evm{AttributeTypes::Transfer, TransferIDs::Edges, TransferKeys::DVM_EVM};
-
-    const auto attributes = mnview.GetAttributes();
-    assert(attributes);
-
     if (src.domain == static_cast<uint8_t>(VMDomain::DVM) && dst.domain == static_cast<uint8_t>(VMDomain::EVM)) {
         if (height >= static_cast<uint32_t>(consensus.ChangiIntermediateHeight4)) {
-            if (!attributes->GetValue(dvm_evm, false)) {
+            if (!transferdomainConfig.dvmToEvm) {
                 return DeFiErrors::TransferDomainDVMEVMNotEnabled();
             }
         }
@@ -4062,7 +4061,7 @@ Res ValidateTransferDomainEdge(const CTransaction &tx,
 
     } else if (src.domain == static_cast<uint8_t>(VMDomain::EVM) && dst.domain == static_cast<uint8_t>(VMDomain::DVM)) {
         if (height >= static_cast<uint32_t>(consensus.ChangiIntermediateHeight4)) {
-            if (!attributes->GetValue(evm_dvm, false)) {
+            if (!transferdomainConfig.evmTodvm) {
                 return DeFiErrors::TransferDomainEVMDVMNotEnabled();
             }
         }
@@ -4099,8 +4098,14 @@ Res ValidateTransferDomain(const CTransaction &tx,
         }
     }
 
+    CDataStructureV0 evm_dvm{AttributeTypes::Transfer, TransferIDs::Edges, TransferKeys::EVM_DVM};
+    CDataStructureV0 dvm_evm{AttributeTypes::Transfer, TransferIDs::Edges, TransferKeys::DVM_EVM};
+    const auto attributes = mnview.GetAttributes();
+    assert(attributes);
+    TransferDomainLiveConfig transferdomainConfig{attributes->GetValue(dvm_evm, false), attributes->GetValue(evm_dvm, false)};
+
     for (const auto &[src, dst] : obj.transfers) {
-        auto res = ValidateTransferDomainEdge(tx, mnview, height, coins, consensus, src, dst);
+        auto res = ValidateTransferDomainEdge(tx, transferdomainConfig, height, coins, consensus, src, dst);
         if (!res) return res;
     }
 

From df8d00d6b33acfe9cd9d091466f862ece45ff179 Mon Sep 17 00:00:00 2001
From: Bushstar <bushsolo@gmail.com>
Date: Fri, 14 Jul 2023 08:18:23 +0100
Subject: [PATCH 4/4] Change value names. Auto enable on Changi fork.

---
 src/masternodes/govvariables/attributes.cpp |  4 ++--
 src/masternodes/validation.cpp              | 20 ++++++++++++++++++++
 test/functional/feature_evm.py              |  4 ++--
 3 files changed, 24 insertions(+), 4 deletions(-)

diff --git a/src/masternodes/govvariables/attributes.cpp b/src/masternodes/govvariables/attributes.cpp
index 6aa2269f3b..236b1c4f7f 100644
--- a/src/masternodes/govvariables/attributes.cpp
+++ b/src/masternodes/govvariables/attributes.cpp
@@ -146,14 +146,14 @@ const std::map<uint8_t, std::string> &ATTRIBUTES::displayGovernanceIDs() {
 
 const std::map<std::string, uint8_t> &ATTRIBUTES::allowedTransferIDs() {
     static const std::map<std::string, uint8_t> params{
-            {"edges", TransferIDs::Edges},
+            {"allowed", TransferIDs::Edges},
     };
     return params;
 }
 
 const std::map<uint8_t, std::string> &ATTRIBUTES::displayTransferIDs() {
     static const std::map<uint8_t, std::string> params{
-            {TransferIDs::Edges, "edges"},
+            {TransferIDs::Edges, "allowed"},
     };
     return params;
 }
diff --git a/src/masternodes/validation.cpp b/src/masternodes/validation.cpp
index 140335b7fa..925041b667 100644
--- a/src/masternodes/validation.cpp
+++ b/src/masternodes/validation.cpp
@@ -2447,6 +2447,23 @@ static void ProcessEVMQueue(const CBlock &block, const CBlockIndex *pindex, CCus
     cache.AddBalance(minerAddress, {DCT_ID{}, static_cast<CAmount>(blockResult.miner_fee / CAMOUNT_TO_GWEI)});
 }
 
+static void ProcessChangiIntermediate4(const CBlockIndex* pindex, CCustomCSView& cache, const CChainParams& chainparams) {
+    if (pindex->nHeight != chainparams.GetConsensus().ChangiIntermediateHeight4 || IsRegtestNetwork()) {
+        return;
+    }
+
+    auto attributes = cache.GetAttributes();
+    assert(attributes);
+
+    CDataStructureV0 evm_dvm{AttributeTypes::Transfer, TransferIDs::Edges, TransferKeys::EVM_DVM};
+    CDataStructureV0 dvm_evm{AttributeTypes::Transfer, TransferIDs::Edges, TransferKeys::DVM_EVM};
+
+    attributes->SetValue(evm_dvm, true);
+    attributes->SetValue(dvm_evm, true);
+
+    cache.SetVariable(*attributes);
+}
+
 void ProcessDeFiEvent(const CBlock &block, const CBlockIndex* pindex, CCustomCSView& mnview, const CCoinsViewCache& view, const CChainParams& chainparams, const CreationTxs &creationTxs, const uint64_t evmContext, std::array<uint8_t, 20>& beneficiary) {
     CCustomCSView cache(mnview);
 
@@ -2504,6 +2521,9 @@ void ProcessDeFiEvent(const CBlock &block, const CBlockIndex* pindex, CCustomCSV
     // Execute EVM Queue
     ProcessEVMQueue(block, pindex, cache, chainparams, evmContext, beneficiary);
 
+    // Execute ChangiIntermediate4 Events. Delete when removing Changi forks
+    ProcessChangiIntermediate4(pindex, cache, chainparams);
+
     // construct undo
     auto& flushable = cache.GetStorage();
     auto undo = CUndo::Construct(mnview.GetStorage(), flushable.GetRaw());
diff --git a/test/functional/feature_evm.py b/test/functional/feature_evm.py
index 76b36f9968..f79e5e7c4a 100755
--- a/test/functional/feature_evm.py
+++ b/test/functional/feature_evm.py
@@ -105,7 +105,7 @@ def run_test(self):
         assert_raises_rpc_error(-32600, "DVM to EVM is not currently enabled", self.nodes[0].transferdomain, [{"src": {"address":address, "amount":"100@DFI", "domain": 2}, "dst":{"address":eth_address, "amount":"100@DFI", "domain": 3}}])
 
         # Activate transferdomain DVM to EVM
-        self.nodes[0].setgov({"ATTRIBUTES": {'v0/transferdomain/edges/dvm-evm': 'true'}})
+        self.nodes[0].setgov({"ATTRIBUTES": {'v0/transferdomain/allowed/dvm-evm': 'true'}})
         self.nodes[0].generate(1)
 
         # Check transferdomain without DFI balance before DFI address is funded
@@ -163,7 +163,7 @@ def run_test(self):
         assert_raises_rpc_error(-32600, "EVM to DVM is not currently enabled", self.nodes[0].transferdomain, [{"src": {"address":address, "amount":"100@DFI", "domain": 3}, "dst":{"address":eth_address, "amount":"100@DFI", "domain": 2}}])
 
         # Activate transferdomain DVM to EVM
-        self.nodes[0].setgov({"ATTRIBUTES": {'v0/transferdomain/edges/evm-dvm': 'true'}})
+        self.nodes[0].setgov({"ATTRIBUTES": {'v0/transferdomain/allowed/evm-dvm': 'true'}})
         self.nodes[0].generate(1)
 
         # Test not enough balance for EVM to DVM transfer