Skip to content

TIP-937: Stepwise Closure of Bancor Trading #937

Description

@warku123
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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    • Status
      In Progress

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions