Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions examples/token-lab/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
node_modules/
out/
out-trc10/
txgen.json
txgen-trc20.json
txgen-trc10.json
report*.txt
104 changes: 104 additions & 0 deletions examples/token-lab/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
# token-lab — TRC20 and TRC10 on a private chain

Two recipes that stand up a private chain, mint a token, drive transfers
through it with `txgen`, and assert the receivers hold exactly what was sent.

```bash
npm install # once — tronweb, for signing
export SR_PRIVATE_KEY=da146374a75310b9666e834ee4ad0866d6f4035967bfc76217c5a495fff9f0d0

trond recipe run --file examples/token-lab/trc20.yaml --allow-host-exec \
--param lab_dir=examples/token-lab --param sender_key=$SR_PRIVATE_KEY

trond recipe run --file examples/token-lab/trc10.yaml --allow-host-exec \
--param lab_dir=examples/token-lab --param sender_key=$SR_PRIVATE_KEY
```

Around 8–12 seconds each once the java-tron image is pulled and
`node_modules` exists. First run on a fresh machine is a few minutes, almost
all of it the 678 MB image.

`--allow-host-exec` is required because minting, loading and verifying all
happen outside trond. The recipes are refused outright under
`--require-private`, which is correct: a host step names no node, so the
gate cannot vouch for it.

## The part worth reading: chain parameters

**A private chain will happily deploy a contract that can never execute, and
happily hold a TRC10 that nothing can transfer.** Both failures look like
broken tooling rather than a mis-seeded chain, and both cost real time to
diagnose. `network.yaml` sets four things because of it:

| parameter | without it |
|---|---|
| `vm.supportConstant` | read-only contract calls are refused with "this node does not support constant" |
| `committee.allowTvmConstantinople`<br>`committee.allowTvmSolidity059`<br>`committee.allowTvmIstanbul` | the deploy returns `contractRet: SUCCESS`, and then **every call returns empty with no energy used** — it reads as a broken contract, not a chain missing its TVM upgrades |
| `committee.allowSameTokenName` | `transferasset` wants the token **name**, not its numeric id. Every tool that sends the id — txgen included — gets `No asset!`, as though the asset was never issued |

`allowCreationOfContracts` is already 1 in the shipped private template,
which is what makes the TVM case so confusing: deployment works, execution
does not.

**These seed the dynamic property store at genesis.** Changing them on a
running chain does nothing — the chain has to be recreated. `trond` will
restart the node for you when the config changes, and the restart is real,
but the already-seeded properties do not move.

`getchainparameters` is the way to check rather than reason:

```bash
curl -s -X POST http://127.0.0.1:8390/wallet/getchainparameters \
| python3 -c "import sys,json;[print(p['key'],'=',p.get('value','(unset)')) \
for p in json.load(sys.stdin)['chainParameter'] if 'Tvm' in p['key'] or 'TokenName' in p['key']]"
```

## Layout

```
network.yaml the chain, and the four parameters above
trc20.yaml / trc10.yaml the recipes
contract/TestToken.sol minimal TRC20 — transfer + balanceOf, what txgen drives
contract/abi.json compiled artifacts, committed so no solc is needed
contract/bytecode.hex
scripts/deploy.js TRC20 deploy (prints JSON -> {{ steps.deploy.address_hex }})
scripts/issue-asset.js TRC10 issuance (prints JSON -> {{ steps.issue.trc10_id }})
scripts/verify-*.js balance assertions
scripts/expect.js computes tx_count * amount for the verifier
txgen-*.tmpl.json txgen configs, filled in by the load step
```

The artifacts are committed rather than compiled on demand, so running this
needs no solc. If you change `TestToken.sol`, recompile with **evmVersion
`istanbul`** — solc 0.8.20+ emits `PUSH0`, which java-tron's TVM does not
implement:

```bash
npx solc@0.8.18 --optimize --evm-version istanbul --abi --bin contract/TestToken.sol
```

## How the two differ

The shape is identical — create, await, mint, load, verify — and only two
steps differ:

- **Minting.** TRC20 deploys a contract; TRC10 issues an asset. An account
may issue only one asset, so `issue-asset.js` reuses an existing one
rather than failing, because recipes get re-run.
- **Verification.** A TRC20 balance sits behind a contract call; a TRC10
balance sits on the account in `assetV2`.

Adding a third token type is those two steps and nothing else.

Two encoding traps if you write your own scripts against the HTTP API:
`getaccount` returns `asset_issued_name` **hex-encoded** but
`asset_issued_ID` as a **plain decimal string** — hex-decoding the id yields
bytes that still look like a value (`"1000001"` becomes `0x10 0x00 0x00`),
so the mistake survives as plausible data instead of erroring. And
`createassetissue` wants `name`, `abbr`, `description` and `url` hex-encoded.

## The key

`SR_PRIVATE_KEY` above is java-tron's published private-net key. It already
appears in `AGENTS.md` and the shipped `private_net_config.conf`; it is not a
secret and controls nothing outside a local chain.
35 changes: 35 additions & 0 deletions examples/token-lab/contract/TestToken.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.18;

/// Minimal TRC20 — the ERC20 surface txgen exercises: transfer + balanceOf.
contract TestToken {
string public constant name = "TrondTest";
string public constant symbol = "TTT";
uint8 public constant decimals = 6;
uint256 public totalSupply;
mapping(address => uint256) public balanceOf;
mapping(address => mapping(address => uint256)) public allowance;

event Transfer(address indexed from, address indexed to, uint256 value);
event Approval(address indexed owner, address indexed spender, uint256 value);

constructor(uint256 initialSupply) {
totalSupply = initialSupply;
balanceOf[msg.sender] = initialSupply;
emit Transfer(address(0), msg.sender, initialSupply);
}

function transfer(address to, uint256 value) external returns (bool) {
require(balanceOf[msg.sender] >= value, "insufficient");
balanceOf[msg.sender] -= value;
balanceOf[to] += value;
emit Transfer(msg.sender, to, value);
return true;
}

function approve(address spender, uint256 value) external returns (bool) {
allowance[msg.sender][spender] = value;
emit Approval(msg.sender, spender, value);
return true;
}
}
1 change: 1 addition & 0 deletions examples/token-lab/contract/abi.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[{"inputs":[{"internalType":"uint256","name":"initialSupply","type":"uint256"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"spender","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Transfer","type":"event"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"allowance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"}],"name":"approve","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"decimals","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"}],"name":"transfer","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"}]
1 change: 1 addition & 0 deletions examples/token-lab/contract/bytecode.hex
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
608060405234801561001057600080fd5b506040516104ee3803806104ee83398101604081905261002f9161007f565b600081815533808252600160209081526040808420859055518481529192917fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef910160405180910390a350610098565b60006020828403121561009157600080fd5b5051919050565b610447806100a76000396000f3fe608060405234801561001057600080fd5b50600436106100885760003560e01c806370a082311161005b57806370a082311461011f57806395d89b411461013f578063a9059cbb14610161578063dd62ed3e1461017457600080fd5b806306fdde031461008d578063095ea7b3146100cb57806318160ddd146100ee578063313ce56714610105575b600080fd5b6100b560405180604001604052806009815260200168151c9bdb9915195cdd60ba1b81525081565b6040516100c291906102ec565b60405180910390f35b6100de6100d9366004610356565b61019f565b60405190151581526020016100c2565b6100f760005481565b6040519081526020016100c2565b61010d600681565b60405160ff90911681526020016100c2565b6100f761012d366004610380565b60016020526000908152604090205481565b6100b56040518060400160405280600381526020016215151560ea1b81525081565b6100de61016f366004610356565b61020c565b6100f76101823660046103a2565b600260209081526000928352604080842090915290825290205481565b3360008181526002602090815260408083206001600160a01b038716808552925280832085905551919290917f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925906101fa9086815260200190565b60405180910390a35060015b92915050565b3360009081526001602052604081205482111561025e5760405162461bcd60e51b815260206004820152600c60248201526b1a5b9cdd59999a58da595b9d60a21b604482015260640160405180910390fd5b336000908152600160205260408120805484929061027d9084906103eb565b90915550506001600160a01b038316600090815260016020526040812080548492906102aa9084906103fe565b90915550506040518281526001600160a01b0384169033907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef906020016101fa565b600060208083528351808285015260005b81811015610319578581018301518582016040015282016102fd565b506000604082860101526040601f19601f8301168501019250505092915050565b80356001600160a01b038116811461035157600080fd5b919050565b6000806040838503121561036957600080fd5b6103728361033a565b946020939093013593505050565b60006020828403121561039257600080fd5b61039b8261033a565b9392505050565b600080604083850312156103b557600080fd5b6103be8361033a565b91506103cc6020840161033a565b90509250929050565b634e487b7160e01b600052601160045260246000fd5b81810381811115610206576102066103d5565b80820180821115610206576102066103d556fea2646970667358221220512e7203d05a3df377f1f2d7febaeac8a82e2b8ad2483c27989ad601ec32f32464736f6c63430008120033
25 changes: 25 additions & 0 deletions examples/token-lab/network.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
name: trc20-lab
target: {type: local, runtime: docker}
network: private
nodes:
- type: witness
version: latest
witness_key: {private_key_env: SR_PRIVATE_KEY}
resources: {memory: 4GB}
ports: {http: 8390, grpc: 50351, p2p: 19988}
config_overrides:
node.rpc.minEffectiveConnection: 0
# Contracts DEPLOY without these and then never execute: calls return
# empty with no energy used, which reads as a broken contract rather
# than a chain missing its TVM upgrades. These seed the dynamic
# properties at genesis, so they must be set before the first block.
vm.supportConstant: true
committee.allowTvmTransferTrc10: 1
committee.allowTvmConstantinople: 1
committee.allowTvmSolidity059: 1
committee.allowTvmIstanbul: 1
# TRC10 only. With this unset, transferasset's asset_name must be the
# token NAME; every tool that sends the numeric id — txgen included —
# gets "No asset!" and looks like it issued nothing. Mainnet has had
# this active for years, so a private chain without it is the odd one.
committee.allowSameTokenName: 1
Loading
Loading