tip: 937
title: Stepwise Closure of Bancor Trading
author: jeremyzpg628@gmail.com
discussions-to: https://github.com/tronprotocol/tips/issues/937
status: Draft
type: Standards Track
category: Core
created: 2026-09-22
Simple Summary
Introduce a new on-chain parameter CLOSE_EXCHANGE to retire the built-in Bancor trading feature (ExchangeCreate / ExchangeInject / ExchangeTransaction / ExchangeWithdraw) in irreversible, stepwise levels.
Abstract
This proposal introduces a new on-chain parameter CLOSE_EXCHANGE to retire the built-in Bancor trading feature in irreversible steps.
The Bancor trading feature consists of four distinct operation contracts:
- ExchangeCreate: pays the creation fee to create a trading pair, injects the initial pool funds, and sets the initial exchange ratio of the pair
- ExchangeInject: the pool creator injects one token into the pool and receives the other token in proportion to the pool ratio
- ExchangeTransaction: trades against a trading pair, computing the receivable amount of the other token with the Bancor formula; rejected since v4.8.0.1 by a hardcoded consensus gate (the reserved parameter 98 of TIP-836 can re-enable it)
- ExchangeWithdraw: the pool creator withdraws one token from the pool and pays the other token in proportion
CLOSE_EXCHANGE has three values, the initial value is 0, and it can only be raised one way and never lowered:
- Value 0: default state, nothing is closed
- Value 1: closes ExchangeCreate / ExchangeInject / ExchangeTransaction
- Value 2: on top of value 1, additionally closes ExchangeWithdraw
Motivation
- ExchangeTransaction has been rejected since v4.8.0.1 by a hardcoded consensus gate due to floating-point precision issues; parameter 98
ALLOW_HARDEN_EXCHANGE_CALCULATION, which controls the re-enable of this contract, has never been touched by any proposal, and there is no schedule on mainnet to enable it.
- The pair-creation contract ExchangeCreate is functional but long dormant: 186 trading pairs, the latest created on 2024-04-19; a 20-month transaction scan over all 107 existing non-zero-pool creators shows 0 calls of the four Bancor contract types; approximately 940,286 TRX are locked in the pools, controlled solely by the pair creators.
- The ecosystem has migrated to various on-chain exchanges; the idle write entry means ongoing maintenance/testing cost and revival/regression risk.
- The graded one-way close allows pair creators to withdraw funds while the new proposal parameter is at value 1, with the committee proposal deciding whether to move up to value 2.
Specification
Parameter values and behavior matrix
- Value 0 (default): All four Exchange contracts (Create/Inject/Transaction/Withdraw) can be broadcast, packed and executed normally.
- Value 1 (partial closure): ExchangeCreate, ExchangeInject, ExchangeTransaction are rejected consistently at the three entrances — broadcast, packing, block validation. ExchangeWithdraw is unaffected, creators can still withdraw pool assets.
- Value 2 (terminal state): On top of value 1, ExchangeWithdraw is additionally rejected (cumulative semantics); all four contracts are closed.
| Value |
ExchangeCreate |
ExchangeInject |
ExchangeTransaction |
ExchangeWithdraw |
| 0 |
unchanged (varies with parameter 98) |
accepted |
accepted |
accepted |
| 1 |
rejected |
rejected |
rejected |
accepted |
| 2 |
rejected |
rejected |
rejected |
rejected |
This parameter does not affect the availability of the read-only query APIs (getexchangebyid, listexchanges).
Value constraints and restrictions on other parameters
The CLOSE_EXCHANGE parameter requires a fork gate, rejection of repeated same-value proposals, only +1 steps with the value in [1,2], and a single-parameter constraint (a CLOSE_EXCHANGE proposal must contain only this one parameter); moreover, once this parameter has been set to 1 or 2, EXCHANGE_CREATE_FEE and ALLOW_HARDEN_EXCHANGE_CALCULATION should no longer be modified.
The +1 stepping is implemented in the validator of ProposalUtil.java:
case CLOSE_EXCHANGE: {
if (!forkController.pass(ForkBlockVersionEnum.VERSION_CLOSE_EXCHANGE)) {
throw new ContractValidateException("Bad chain parameter id [CLOSE_EXCHANGE]");
}
int current = dynamicPropertiesStore.getCloseExchange();
// Irreversible by design: CLOSE_EXCHANGE only advances one level at a time.
if (value == current) {
throw new ContractValidateException(
"[CLOSE_EXCHANGE] has been set to " + value + ", no need to propose again");
}
if (current == 2) {
throw new ContractValidateException(
"[CLOSE_EXCHANGE] has reached its terminal value 2; no further change is allowed");
}
if (value != current + 1 || value < 1 || value > 2) {
throw new ContractValidateException(
"This value[CLOSE_EXCHANGE] must be " + (current + 1) + " and within [1,2]");
}
break;
}
Rationale
The transaction-close gate can be implemented in two ways; the actuator validate layer (Option 2 in the Implementation section) is preferred: errors surface at build time, the change is confined to the four actuators, and it follows the precedent of proposals 44/35. The Manager three-entrance option (Option 1) is the same approach as the existing ExchangeTransaction rejection and is therefore kept as a reference; the two are equivalent at the consensus layer and differ in interception timing and change location.
Implementation
Option 1: Manager three-entrance gating (similar to parameter 98)
Rejection at the three transaction entrances in Manager, with a single predicate isClosedExchange + closedExchangeLevelRequired shared by all three, aligned at the same layer as the existing ExchangeTransaction rejection logic:
- Broadcast (pushTransaction): API response returned to the user; the contract name and level vary with the rejected contract and the current value:
Contract validate error : ExchangeCreateContract is rejected by exchange close level 1
- Packing (SR generateBlock) silently skips:
// generateBlock: isClosedExchange(tx) -> continue
- Block validation (processBlock) throws the same rejection message:
ExchangeWithdrawContract is rejected by exchange close level 2
The exception is thrown during block validation; a block containing transactions of closed contracts will be rejected.
Option 2: actuator validate-layer gating (similar to parameters 35/44)
Add the level check to the validate() of each Bancor-trading actuator: ExchangeCreate / ExchangeInject / ExchangeTransaction are rejected at level >= 1, ExchangeWithdraw at level = 2; the error message is the same as in Option 1:
// ExchangeCreateActuator.validate()
if (chainBaseManager.getDynamicPropertiesStore().getCloseExchange() >= 1) {
throw new ContractValidateException("ExchangeCreateContract is rejected by exchange close level 1");
}
actuator.validate() runs on all three consensus paths — broadcast, packing, block validation (processTransaction -> TransactionTrace -> RuntimeImpl) — so consensus coverage is complete.
The build phase is also covered: Wallet.createTransactionCapsule pre-runs actuator.validate(), so users see the rejection when constructing the transaction instead of waiting until broadcast.
Proposal parameter 44 (parameter: ALLOW_MARKET_TRANSACTION, actuators: MarketSellAssetActuator / MarketCancelOrderActuator) and proposal parameter 35 (parameter: FORBID_TRANSFER_TO_CONTRACT, actuators: TransferActuator / TransferAssetActuator) both use this pattern.
Test Cases
- Proposal validation: before the fork gate, any
CLOSE_EXCHANGE proposal is rejected with Bad chain parameter id [CLOSE_EXCHANGE]; only +1 steps within [1,2] are accepted (0 -> 1, 1 -> 2); same-value, skipped, out-of-range, and downgrade proposals are rejected with the documented error messages; the terminal value 2 rejects any further change; a CLOSE_EXCHANGE proposal must contain only this single parameter.
- Level x contract matrix: at level 1, ExchangeCreate/ExchangeInject/ExchangeTransaction are rejected with
<ContractType> is rejected by exchange close level 1 while ExchangeWithdraw stays open; at level 2 all four are rejected. Each rejection is asserted identically at transaction build time, broadcast, block packing (the closed transaction is skipped), and block validation (the block is rejected).
- The legacy parameter-98 rejection of ExchangeTransaction is preserved and cannot bypass the close gate (with harden mode enabled it is still rejected at level >= 1).
- Persistence and boundaries: the level defaults to 0, applies from the activating maintenance block onward, and survives node restart; read-only APIs
getexchangebyid/listexchanges and the chain parameter getCloseExchange remain available at every level.
- Funds safety: at level 1 creators can still withdraw pool funds via ExchangeWithdraw; blocks containing only open contracts are accepted at every level.
Backwards Compatibility
This proposal can only be submitted through the proposal mechanism; after the parameter has been changed, modifications to EXCHANGE_CREATE_FEE and ALLOW_HARDEN_EXCHANGE_CALCULATION will be restricted.
Copyright
Copyright and related rights waived via CC0.
Simple Summary
Introduce a new on-chain parameter
CLOSE_EXCHANGEto retire the built-in Bancor trading feature (ExchangeCreate / ExchangeInject / ExchangeTransaction / ExchangeWithdraw) in irreversible, stepwise levels.Abstract
This proposal introduces a new on-chain parameter
CLOSE_EXCHANGEto retire the built-in Bancor trading feature in irreversible steps.The Bancor trading feature consists of four distinct operation contracts:
CLOSE_EXCHANGEhas three values, the initial value is 0, and it can only be raised one way and never lowered:Motivation
ALLOW_HARDEN_EXCHANGE_CALCULATION, which controls the re-enable of this contract, has never been touched by any proposal, and there is no schedule on mainnet to enable it.Specification
Parameter values and behavior matrix
This parameter does not affect the availability of the read-only query APIs (
getexchangebyid,listexchanges).Value constraints and restrictions on other parameters
The
CLOSE_EXCHANGEparameter requires a fork gate, rejection of repeated same-value proposals, only +1 steps with the value in [1,2], and a single-parameter constraint (a CLOSE_EXCHANGE proposal must contain only this one parameter); moreover, once this parameter has been set to 1 or 2,EXCHANGE_CREATE_FEEandALLOW_HARDEN_EXCHANGE_CALCULATIONshould no longer be modified.The +1 stepping is implemented in the validator of
ProposalUtil.java:Rationale
The transaction-close gate can be implemented in two ways; the actuator validate layer (Option 2 in the Implementation section) is preferred: errors surface at build time, the change is confined to the four actuators, and it follows the precedent of proposals 44/35. The Manager three-entrance option (Option 1) is the same approach as the existing ExchangeTransaction rejection and is therefore kept as a reference; the two are equivalent at the consensus layer and differ in interception timing and change location.
Implementation
Option 1: Manager three-entrance gating (similar to parameter 98)
Rejection at the three transaction entrances in Manager, with a single predicate
isClosedExchange+closedExchangeLevelRequiredshared by all three, aligned at the same layer as the existing ExchangeTransaction rejection logic:The exception is thrown during block validation; a block containing transactions of closed contracts will be rejected.
Option 2: actuator validate-layer gating (similar to parameters 35/44)
Add the level check to the
validate()of each Bancor-trading actuator: ExchangeCreate / ExchangeInject / ExchangeTransaction are rejected at level >= 1, ExchangeWithdraw at level = 2; the error message is the same as in Option 1:actuator.validate()runs on all three consensus paths — broadcast, packing, block validation (processTransaction -> TransactionTrace -> RuntimeImpl) — so consensus coverage is complete.The build phase is also covered:
Wallet.createTransactionCapsulepre-runsactuator.validate(), so users see the rejection when constructing the transaction instead of waiting until broadcast.Proposal parameter 44 (parameter:
ALLOW_MARKET_TRANSACTION, actuators:MarketSellAssetActuator/MarketCancelOrderActuator) and proposal parameter 35 (parameter:FORBID_TRANSFER_TO_CONTRACT, actuators:TransferActuator/TransferAssetActuator) both use this pattern.Test Cases
CLOSE_EXCHANGEproposal is rejected withBad chain parameter id [CLOSE_EXCHANGE]; only+1steps within [1,2] are accepted (0 -> 1,1 -> 2); same-value, skipped, out-of-range, and downgrade proposals are rejected with the documented error messages; the terminal value 2 rejects any further change; aCLOSE_EXCHANGEproposal must contain only this single parameter.<ContractType> is rejected by exchange close level 1while ExchangeWithdraw stays open; at level 2 all four are rejected. Each rejection is asserted identically at transaction build time, broadcast, block packing (the closed transaction is skipped), and block validation (the block is rejected).getexchangebyid/listexchangesand the chain parametergetCloseExchangeremain available at every level.Backwards Compatibility
This proposal can only be submitted through the proposal mechanism; after the parameter has been changed, modifications to
EXCHANGE_CREATE_FEEandALLOW_HARDEN_EXCHANGE_CALCULATIONwill be restricted.Copyright
Copyright and related rights waived via CC0.