GardenBlockchain

Separate token storage from transfer policy

An older presenter-contract experiment and the authority, state, and migration boundaries it introduces.

The earlier design split an ERC20 token from a separate presenter contract that decides how transfers proceed. The aim was to change policy without moving the token’s core balances into a new contract.

Component Responsibility in the source Authority to review
Token Balances, allowances, transfer hook Owner can replace the presenter
Presenter Receives tokens and forwards them Token-address check, owner controls and withdrawals
Maintenance state Pauses selected presenter operations Who can pause, resume, or mark an instance outdated

The token and interface

// SPDX-License-Identifier: MIT
pragma solidity 0.8.3;

import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "./interfaces/ITokenPresenter.sol";

/**
ERC20Token implementation
 */
contract ERC20Token is ERC20, Ownable {

  address public presenter;
  uint8 private _decimals;

  constructor(string memory name_, string memory symbol_, uint8 decimals_, uint256 initialSupply_) ERC20(name_, symbol_) {
    _decimals = decimals_;
    _mint(_msgSender(), initialSupply_ * 10 ** uint256(decimals_));
  }

  /**
  * @dev set the decimal
  */
  function decimals() override public view returns (uint8) {
    return _decimals;
  }

  /**
  * @dev set the presenter of the token to decide transfer functionality
  * @param _presenter address of presenter
  */
  function setPresenter(address _presenter) onlyOwner public {
    presenter = _presenter;
  }

  /**
  * @dev transfer the tokens, if presenter is not set, normal behaviour
  */
  function _transfer(address _from, address _to, uint256 _amount) internal override {
    // Transfer fund and responsibility to presenter
    if (presenter != address(0) && presenter != _msgSender()) {
      super._transfer(_from, presenter, _amount);
      ITokenPresenter(presenter).receiveTokens(_msgSender(), _from, _to, _amount);
    } else {
      super._transfer(_from, _to, _amount);
    }
  }

}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.3;

interface ITokenPresenter {
  function receiveTokens(address _trigger, address _from, address _to, uint256 _amount) external returns (bool);
}

The transfer hook first moves tokens into the presenter, then calls its interface. That creates a separate external-call and custody boundary. The presenter check, failure behavior, reentrancy assumptions, and owner authority need review as a complete system. Splitting contracts does not remove upgrade trust.

The source presenter and maintenance listings were flattened into single lines after an SPDX comment, making those text copies unsuitable as compilable Solidity. Their intended roles are described here instead of silently reconstructing code.

Arithmetic still needs context

uint internal constant RATE_NOMINATOR = 10000;uint tax = 100; // 1%uint taxAmount = (amount * tax) / RATE_NOMINATOR;

The example expresses a 1% tax using a denominator of 10,000. Integer division rounds down. Solidity 0.8.x checked arithmetic and older SafeMath-based sources have different arithmetic behavior; the old note’s advice should not be mixed across compiler generations.

Migration is a state transition

The following is the original conceptual sketch, not valid standalone Solidity:

ContractV1 is Maintainable {
 uint a = 1;

}

ContractV2 is Maintainable {
 uint a;
 restore(address contractV1) public onlyOwner ifMaintenance {
  a = ContractV1(contractV1).a;
 }
}

====
UPGRADE FLOW:
- ContractV1 -> enableMaintenance(true)
- ContractV2 -> enableMaintenance(true)
- ContractV2 -> restore(contract v1 address)
- ContractV2 -> enableMaintenance(false)
- ContractV1 -> enableOutdated(true)

A real migration needs access control, a defined source snapshot, bounded copying, progress tracking, and a decision about partially migrated state. Arrays of users may require pagination; exported data and reconstructed balances require reconciliation.

Proxy upgrades are a different arrangement: their storage compatibility and initialization rules depend on the chosen proxy design. A constructor is not a substitute for initializing proxy storage, and source verification does not universally prevent explorer read/write access. No contract was compiled, upgraded, migrated, or audited here.

← Back to the garden