Skip to content

Fix missing require_auth, add checked arithmetic and TTL policy - #1228

Merged
yusuftomilola merged 1 commit into
DistinctCodes:mainfrom
tolulopedd26:feat/sc-41-44-tests-auth-overflow-ttl
Jul 27, 2026
Merged

Fix missing require_auth, add checked arithmetic and TTL policy#1228
yusuftomilola merged 1 commit into
DistinctCodes:mainfrom
tolulopedd26:feat/sc-41-44-tests-auth-overflow-ttl

Conversation

@tolulopedd26

Copy link
Copy Markdown
Contributor

Closes #1205
Closes #1206
Closes #1207
Closes #1208

SC-42: authorization audit

Found and fixed a critical authentication gap in two crates. Ten entrypoints
took a `caller: Address`, compared it against a registrar allowlist, the asset
owner, the stored admin, or the approver set — and never called
`caller.require_auth()`. Because `caller` is chosen by whoever builds the
transaction, every one of those checks was satisfied by simply naming a
privileged address.

assetsup: register_asset, update_asset_metadata, transfer_asset_ownership,
retire_asset. transfer_asset_ownership was a direct asset-theft path: name the
current owner, receive the asset.

multisig-transfer: configure_approval_rule, create_transfer_request,
approve_transfer_request, reject_transfer_request, cancel_transfer_request.
Approvals were forgeable by naming an authorized approver, and the approval
threshold rewritable by naming the admin.

Also closed the initialize front-running window in assetsup, contrib,
multisig-transfer and asset-maintenance, and gated four asset-maintenance
entrypoints that had no authorization at all — anyone could write warranty
terms, file claims, and raise alerts, forging the audit evidence that contract
exists to provide.

execute_transfer stays permissionless, matching multisig-wallet's execute_*:
the approvers already made the decision. Its unused `caller` argument now says
so in a comment, since an authentic-looking unused parameter is what made this
class of bug easy to miss.

23 negative tests run without mock_all_auths, including one proving an
attacker's own valid signature does not authorize a transfer they named someone
else for. With auths mocked, an entrypoint that never authenticates is
indistinguishable from one that does, which is why none of this was caught
before. The full table is in contracts/AUTHORIZATION.md, including nine
assetsup tokenization entrypoints still unguarded — those need a decision about
which principal applies, so they are listed rather than guessed at.

SC-43: arithmetic
- Add assetsup/src/math.rs with checked add/sub/mul and a mul_div that survives
  an intermediate product overflowing i128 by cancelling common factors first.
- Apply it to the dividend split, token mint/burn/transfer, and every ownership
  percentage. (balance * total_amount) / total_supply was the worst case: it
  trapped whenever the product exceeded i128::MAX even though the quotient was
  small.
- Overflow now returns MathOverflow/MathUnderflow instead of trapping.
- Rounding is documented: mul_div rounds down and the remainder stays with the
  contract, so a distribution can never overpay and no holder is advantaged by
  iteration order.
- Boundary tests at 0, 1 and type max.

SC-44: storage TTL
- Add assetsup/src/ttl.rs holding the policy and constants in one place:
  extend persistent entries when under 30 days, out to 90.
- Extend on read as well as write. An asset that is only ever queried would
  otherwise be archived despite being in active use.
- Extend on write in initialize: a freshly written entry gets the network
  minimum, and a read cannot rescue an entry that is already gone.
- Bump the contract instance TTL. Nothing did, so the instance itself could be
  archived and take the whole contract with it regardless of what else
  survived. The ledger-advancing tests caught this.
- Tests advance 60 days and prove assets, transfers and configuration are still
  readable.

SC-41: asset-maintenance coverage
- 4 tests to 70, covering every entrypoint with happy and failure paths.
- Validation boundaries: service date exactly now versus one second future,
  zero cost, condition and quality ratings at 1 and 10 and just outside.
- Scheduling boundaries including a schedule due in the past reading as overdue
  and one due exactly now not yet overdue.
- Three tests for the core audit property: history is append-only, an earlier
  record is never rewritten by a later one, and no operation removes a record.
- Negative auth tests for each newly gated entrypoint.

multisig_transfer had no tests at all; it now has 8.

Closes DistinctCodes#1205
Closes DistinctCodes#1206
Closes DistinctCodes#1207
Closes DistinctCodes#1208
@vercel

vercel Bot commented Jul 27, 2026

Copy link
Copy Markdown

@tolulopedd26 is attempting to deploy a commit to the naijabuz's projects Team on Vercel.

A member of the Team first needs to authorize it.

@drips-wave

drips-wave Bot commented Jul 27, 2026

Copy link
Copy Markdown

@tolulopedd26 Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

@yusuftomilola yusuftomilola left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Important security hardening! Missing auth checks fixed, checked arithmetic prevents overflows, and TTL policy ensures data lifecycle management. Strong security focus.

@yusuftomilola
yusuftomilola merged commit 4035569 into DistinctCodes:main Jul 27, 2026
3 of 8 checks passed
portableDD added a commit to portableDD/AssetsUp that referenced this pull request Jul 27, 2026
…ents

Resolves the conflicts with main. The crate rename made this the largest of
them: git detected the directory rename and relocated main's new
multisig_transfer files (README.md, auth_tests.rs) into multisig-transfer/,
which is the right answer and is accepted as-is.

assetsup/src/lib.rs:
- initialize gained the typed contract_initialized event here and the TTL
  extend-on-write from SC-44 on main; kept both.
- The two-step admin transfer from SC-48 landed on main emitting three events
  through the deprecated untyped API this branch removes. SDK 23 deprecates
  env.events().publish(), and CI runs clippy with -D warnings, so leaving them
  would fail the build. propose_admin, accept_admin and cancel_admin_proposal
  now emit typed events, with two new #[contractevent] types added for the
  steps that had no equivalent: AdminProposed and AdminProposalCancelled.
  Each step is separately observable, which the old single-step admin_changed
  could not express.

asset-maintenance/src/lib.rs: kept main's crate docs with this branch's import
line, which no longer needs symbol_short. add_warranty_information gained the
require_admin gate from SC-42 on main and the factored store_warranty helper
plus typed event here; kept both.

assetsup/src/tests/mod.rs: kept both module lists.

Two things the merge surfaced that needed real fixes:

main does not currently compile its tests. The auth audit (DistinctCodes#1228) added a test
calling update_admin, and the two-step admin transfer (DistinctCodes#1229) removed it. The
two merged cleanly line by line and broke each other semantically, which no
conflict marker catches. Rewritten against the two-step API and split so both
halves are covered.

This branch's own event test used the same removed entrypoint; it now asserts
a distinct event at each step of the transfer, plus cancellation.

Documentation the rename made stale is corrected: the crate and workspace
READMEs no longer describe the directory/package mismatch as an open problem,
and the SECURITY.md line claiming multisig_transfer has no tests is corrected —
it has 10 since DistinctCodes#1228, though the request lifecycle itself is still uncovered.

All 393 tests pass, clippy and fmt are clean, all five crates resolve by
directory name, and all five build to WASM. The 16 remaining untyped publishes
are all in contrib's uncompiled files, which is why the build has no
deprecation warnings.
amberly-d added a commit to amberly-d/AssetsUp that referenced this pull request Jul 27, 2026
Resolves the conflicts with main.

contracts/.gitignore: both sides wanted. Kept the deployments/ ignore from
SC-55 and the lcov.info ignore from SC-39.

CI: the security and coverage jobs this branch adds were still installing a
floating `stable` toolchain. Stable has since moved to 1.97.1, which fails to
compile ethnum 1.5.2 in the soroban dependency tree, so both jobs would have
failed exactly as every unpinned contracts job on main currently does. They now
read the channel from rust-toolchain.toml like the jobs SC-53 already pinned.

assetsup/src/tests/auth.rs: main does not currently compile its tests. The auth
audit (DistinctCodes#1228) added a test calling update_admin, and the two-step admin
transfer (DistinctCodes#1229) replaced update_admin with propose_admin/accept_admin. The two
merged cleanly line by line and broke each other semantically, which no
conflict marker catches. The test is rewritten against the two-step API and
split in two, so both halves of the flow are covered: nominating requires the
current admin, and accepting requires the incoming address. It also asserts the
rejected proposal is not recorded and the admin is unchanged, which the
original did not.

All 435 tests pass, clippy is clean, fmt is clean.
TomikeDS added a commit to TomikeDS/AssetsUp that referenced this pull request Jul 27, 2026
…test

Resolves the conflicts with main.

assetsup/src/lib.rs, three hunks, all additive on both sides:
- DataKey gained StorageVersion here and PendingAdmin on main; kept both.
- initialize gained the version stamp here and the TTL extend-on-write from
  SC-44 on main; kept both, and added StorageVersion to the extended keys so
  the new entry gets the same lifetime as the rest of the configuration.
- The upgrade/migrate/storage_version entrypoints landed in the same place as
  main's accept_admin/cancel_admin_proposal/get_pending_admin; kept both, with
  the admin flow first so it reads continuously from propose_admin.

assetsup/src/tests/mod.rs: kept both module lists.

multisig_transfer/src/lib.rs: main added caller.require_auth() to
create_transfer_request while this branch renamed _admin to admin to use it in
the ownership check. Both are needed and both are kept.

Two things the merge surfaced that needed real fixes rather than resolution:

The pause guard-rail test from SC-47 failed, correctly, flagging upgrade,
migrate and storage_version as mutating entrypoints with no pause check. That
test is doing exactly its job. upgrade and migrate are deliberately exempt —
an upgrade is often how you fix the incident that caused the pause, as
UPGRADE.md already documented — so they are recorded in PAUSE_EXEMPT with that
reason. storage_version is a read and is classified as one.

main does not currently compile its tests: the auth audit (DistinctCodes#1228) added a test
calling update_admin, and the two-step admin transfer (DistinctCodes#1229) removed it. The
two merged cleanly line by line and broke each other semantically. The test is
rewritten against the two-step API and split so both halves are covered.

CI: the wasm-size-delta job this branch adds was still installing a floating
`stable` toolchain, which is now 1.97.1 and fails to compile ethnum in the
soroban tree. It now reads the pinned channel like every other contracts job.

All 404 tests pass, clippy and fmt are clean, and every contract is within its
WASM budget.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants