Skip to content

fix: preserve direct-transfer semantics for MTS seize - #1

Closed
maxim-inj wants to merge 2 commits into
injective-mtsfrom
injective-mts-direct-seize-hook-suppression
Closed

maxim-inj wants to merge 2 commits into
injective-mtsfrom
injective-mts-direct-seize-hook-suppression

Conversation

@maxim-inj

@maxim-inj maxim-inj commented Aug 5, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

  • Keep seize implemented as the existing direct BANK.transfer between source and issuer wallets.
  • Expose the transient suppressPostTransferRecord state through isBankTransferHookSuppressed() for the Bank precompile to read during the in-flight call.
  • Extend the real-node MTS suite with restricted issuer-wallet checks plus partial and full seize assertions.
  • Verify supply, balances, mirrored investor balances, wallet tracking, and investor counters.

Notes

No burn/mint workaround is included, and there is no storage-layout change. One of the alternative workarounds was to replace transfer with mint+burn but it changes semantics.

Behavior

Ordinary EVM transfers and Cosmos bank sends to the restricted issuer wallet continue to be rejected. Only the contract-originated Bank transfer inside an already-authorized seize observes the transient suppression state; the flag resets before the transaction completes.

Dependency

Requires InjectiveLabs/injective-core#2919 for active-StateDB suppression handling in the Bank precompile and matching permissions hooks.

Verification

  • npx hardhat test test/dstoken-injective-mts.test.ts --network injectiveLocal: 1 passing against a real local patched injectived node, including partial and full seize
  • regulated-token seize regression selection: 12 passing

Keep seize as a direct BANK.transfer after validateSeize. Expose the transient suppression state so Injective Core can read in-flight contract storage before entering the matching permissions hooks.

The getter does not alter transfer semantics or storage layout; ordinary EVM and Cosmos transfers continue through compliance.
Extend the real-node Hardhat acceptance test with a restricted issuer wallet. Confirm ordinary EVM and Cosmos transfers remain rejected, then verify partial and full seize operations preserve supply, balances, wallet tracking, and investor counters.
@coderabbitai

coderabbitai Bot commented Aug 5, 2026 •

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The token contract now exposes post-transfer hook suppression status. The Injective MTS integration test adds issuer-wallet registration, blocked transfer checks, and partial and full seizure accounting checks.

Changes

MTS transfer controls

Layer / File(s) Summary
Expose transfer hook suppression state
contracts/token/DSTokenMTS.sol
Adds isBankTransferHookSuppressed(), which returns the current suppressPostTransferRecord flag.
Validate issuer-wallet seizure behavior
test/dstoken-injective-mts.test.ts
Adds the issuer wallet signer and wallet manager, registers the issuer wallet, checks blocked EVM and bank transfers, and verifies partial and full seizure balances, supply, wallet counts, and investor counts.

Estimated code review effort: 2 (Simple) | ~10 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly identifies the main change: preserving direct-transfer semantics for MTS seize operations.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch injective-mts-direct-seize-hook-suppression

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
test/dstoken-injective-mts.test.ts (1)

263-264: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Assert the EVM restriction, not only a revert.

to.be.reverted accepts any failure. It does not prove that the issuer-wallet restriction caused the EVM transfer to fail. After confirming the compliance error is stable, match it with revertedWith or the appropriate custom-error matcher. Otherwise, assert that both balances remain unchanged.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/dstoken-injective-mts.test.ts` around lines 263 - 264, Update the
issuer-wallet transfer assertion in the relevant test to match the specific
compliance error using revertedWith or the appropriate custom-error matcher,
rather than accepting any revert. Preserve the existing bankSend
compliance-error assertion and verify the issuer wallet restriction is the cause
of the EVM transfer failure.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@test/dstoken-injective-mts.test.ts`:
- Around line 268-284: Update every numeric expected value in the assertions
surrounding dsToken.totalSupply, balanceOf, balanceOfInvestor, walletCount, and
complianceService.getTotalInvestorsCount to bigint literals by adding the n
suffix, including all 14 values before and after seize.

---

Nitpick comments:
In `@test/dstoken-injective-mts.test.ts`:
- Around line 263-264: Update the issuer-wallet transfer assertion in the
relevant test to match the specific compliance error using revertedWith or the
appropriate custom-error matcher, rather than accepting any revert. Preserve the
existing bankSend compliance-error assertion and verify the issuer wallet
restriction is the cause of the EVM transfer failure.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 4f37c776-26fc-4dd7-9236-d82bed4b5bce

📥 Commits

Reviewing files that changed from the base of the PR and between baba336 and 1efa06a.

📒 Files selected for processing (2)
  • contracts/token/DSTokenMTS.sol
  • test/dstoken-injective-mts.test.ts

Comment on lines +268 to +284
expect(await dsToken.totalSupply()).to.equal(500);
expect(await dsToken.balanceOf(holder.address)).to.equal(275);
expect(await dsToken.balanceOf(issuerWallet.address)).to.equal(100);
expect(await dsToken.balanceOfInvestor(INVESTORS.INVESTOR_ID.INVESTOR_ID_1)).to.equal(275);
expect(await dsToken.walletCount()).to.equal(3);
expect(await complianceService.getTotalInvestorsCount()).to.equal(2);

await (await dsToken.seize(holder.address, issuerWallet.address, 275, 'test full seize')).wait();

expect(await dsToken.totalSupply()).to.equal(500);
expect(await dsToken.balanceOf(holder.address)).to.equal(0);
expect(await dsToken.balanceOf(owner.address)).to.equal(125);
expect(await dsToken.balanceOf(issuerWallet.address)).to.equal(375);
expect(await dsToken.balanceOfInvestor(INVESTORS.INVESTOR_ID.INVESTOR_ID_1)).to.equal(0);
expect(await dsToken.balanceOfInvestor(INVESTORS.INVESTOR_ID.INVESTOR_ID_2)).to.equal(125);
expect(await dsToken.walletCount()).to.equal(2);
expect(await complianceService.getTotalInvestorsCount()).to.equal(1);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail
rg -n '"ethers"\s*:' --glob 'package*.json' --glob '*lock*'
rg -n 'to\.equal\([0-9]+' test/dstoken-injective-mts.test.ts

Repository: InjectiveLabs/dstoken

Length of output: 159


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail
printf '%s\n' '--- candidate files ---'
git ls-files | rg '(^|/)(package[^/]*\.json|.*lock.*|dstoken-injective-mts\.test\.ts)$' || true
printf '%s\n' '--- test file matches ---'
test_file="$(git ls-files | rg '(^|/)dstoken-injective-mts\.test\.ts$' | head -n 1)"
printf 'test_file=%s\n' "$test_file"
rg -n -C 3 'to\.equal\([0-9]+|totalSupply|walletCount|getTotalInvestorsCount|balanceOfInvestor' "$test_file" || true
printf '%s\n' '--- dependency declarations ---'
while IFS= read -r f; do
  printf '\n[%s]\n' "$f"
  rg -n -C 2 '"ethers"\s*:' "$f" || true
done < <(git ls-files | rg '(^|/)(package[^/]*\.json|.*lock.*)$')

Repository: InjectiveLabs/dstoken

Length of output: 3828


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail
printf '%s\n' '--- package.json ethers declaration ---'
rg -n -C 3 '"ethers"\s*:' package.json
printf '%s\n' '--- package-lock ethers entries ---'
rg -n -C 3 '"node_modules/ethers"|"ethers"\s*:' package-lock.json | head -n 80
printf '%s\n' '--- pnpm-lock ethers entries ---'
rg -n -C 3 '(^|/| )ethers(@|:)|ethers:' pnpm-lock.yaml | head -n 80
printf '%s\n' '--- numeric equality assertions in the test ---'
rg -n 'to\.equal\([0-9]+[n]?\)' test/dstoken-injective-mts.test.ts
printf '%s\n' '--- return types for asserted contract methods ---'
rg -n -C 4 'function (totalSupply|balanceOf|balanceOfInvestor|walletCount|getTotalInvestorsCount)|totalSupply\(|balanceOfInvestor\(|walletCount\(|getTotalInvestorsCount\(' contracts interfaces src test | head -n 240

Repository: InjectiveLabs/dstoken

Length of output: 28384


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail
python3 - <<'PY'
import json
import re
from pathlib import Path

package = json.loads(Path("package.json").read_text())
lock = json.loads(Path("package-lock.json").read_text())
test = Path("test/dstoken-injective-mts.test.ts").read_text()
source = Path("contracts/token/IDSToken.sol").read_text()

declared = package["devDependencies"]["ethers"]
locked = lock["packages"]["node_modules/ethers"]["version"]
block = "\n".join(test.splitlines()[267:284])
assertions = re.findall(r"\.to\.equal\((\d+)\)", block)
uint256_methods = {
    name: bool(re.search(
        rf"function\s+{name}\b[\s\S]*?returns\s*\(\s*uint256",
        source,
    ))
    for name in ("walletCount", "balanceOfInvestor")
}
print({
    "declared_ethers": declared,
    "locked_ethers": locked,
    "bare_numeric_assertions_in_lines_268_284": len(assertions),
    "asserted_values": assertions,
    "IDSToken_uint256_methods": uint256_methods,
})
assert locked.startswith("6.")
assert len(assertions) == 16
assert all(uint256_methods.values())
PY

Repository: InjectiveLabs/dstoken

Length of output: 539


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail
printf '%s\n' '--- asserted function declarations ---'
rg -n -U 'function (totalSupply|balanceOf|balanceOfInvestor|walletCount|getTotalInvestorsCount)\b[\s\S]{0,180}?returns\s*\(\s*uint256' contracts
printf '%s\n' '--- corrected read-only invariant check ---'
python3 - <<'PY'
import json
import re
from pathlib import Path

package = json.loads(Path("package.json").read_text())
lock = json.loads(Path("package-lock.json").read_text())
test_lines = Path("test/dstoken-injective-mts.test.ts").read_text().splitlines()
block = "\n".join(test_lines[267:284])
values = re.findall(r"\.to\.equal\((\d+)\)", block)

print("declared ethers:", package["devDependencies"]["ethers"])
print("locked ethers:", lock["packages"]["node_modules/ethers"]["version"])
print("bare numeric assertions at lines 268-284:", len(values))
print("values:", values)

assert lock["packages"]["node_modules/ethers"]["version"].startswith("6.")
assert len(values) == 14
PY

Repository: InjectiveLabs/dstoken

Length of output: 2191


Use bigint values in the numeric assertions. Ethers 6 returns these uint256 values as bigint, so update all 14 expected values in lines 268–284 with the n suffix.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/dstoken-injective-mts.test.ts` around lines 268 - 284, Update every
numeric expected value in the assertions surrounding dsToken.totalSupply,
balanceOf, balanceOfInvestor, walletCount, and
complianceService.getTotalInvestorsCount to bigint literals by adding the n
suffix, including all 14 values before and after seize.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR preserves the intended “direct Bank.transfer” semantics for seize in the Injective MTS integration by exposing the in-flight suppression flag to the Bank precompile and expanding the real-node integration test coverage around restricted issuer wallets and seize behavior.

Changes:

  • Expose suppressPostTransferRecord via isBankTransferHookSuppressed() so the Bank precompile can read the transient suppression state during an authorized in-flight call.
  • Extend the Injective MTS integration test to validate issuer-wallet restrictions and to assert partial/full seize outcomes across supply, balances, wallet tracking, and investor counters.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.

File Description
contracts/token/DSTokenMTS.sol Adds an external view accessor for the transient suppression flag used during Bank-module transfers (for precompile integration).
test/dstoken-injective-mts.test.ts Adds issuer-wallet restriction checks and partial/full seize assertions in the Injective MTS real-node suite.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@maxim-inj

Copy link
Copy Markdown
Collaborator Author

@maxim-inj maxim-inj closed this Sep 28, 2026
@maxim-inj
maxim-inj deleted the injective-mts-direct-seize-hook-suppression branch September 28, 2026 15:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants