fix: sign a dApp's contract deployment with the connected wallet - #29
Open
chakipu2 wants to merge 1 commit into
Open
fix: sign a dApp's contract deployment with the connected wallet#29chakipu2 wants to merge 1 commit into
chakipu2 wants to merge 1 commit into
Conversation
A dApp can deploy by sending transfer with an "sc" field, the way Engram and the
TELA CLI accept it. InternalWalletCall resolves the connected wallet, then handed
that field to InstallSmartContract, which decided the signer again on its own. In
simulator mode it took the primary wallet, so a contract that records its deployer
- STORE("owner", SIGNER()), the ordinary pattern - recorded a wallet the dApp had
never connected to, while GetAddress, GetBalance and scinvoke on the same
connection all answered for the open wallet. The contract then refused its own
creator on the next call, and nothing on screen had said which wallet signed.
Off simulator the same handoff called GetWallet(), which takes walletManager's
read lock, from inside InternalWalletCall, which holds its write lock for the
whole body. sync.RWMutex is not reentrant: that read lock can never be taken by
the goroutine already holding the write lock, so the call never returned, the
deferred unlock never ran, and every later wallet operation blocked behind it.
Split the install into a core that signs with a caller-supplied wallet and takes
no wallet decision - it never reaches walletManager, so it can neither substitute
a signer nor re-enter the lock - and a selection step for callers that have no
wallet in hand. The XSWD branch passes the wallet it already resolved. The Studio
deploy screen and the simulator developer deploy now resolve their signer through
getWalletForDeployment, the policy every TELA deployment already used: the open
wallet, with the primary as a fallback when none is open.
The rule that was there before was justified by funding - the open wallet may
hold nothing. On the simulator that does not hold: derohe credits every wallet
the same amount at registration (transaction_execute.go, REGISTRATION case, on
any non-mainnet chain), none of the pre-seeded simulator wallets appears in the
premine list, and the simulator mines to its own genesis wallet, which is not one
of them. Wallet #0 has no funding advantage over the others. Off simulator the
rule never applied, since that path already used the open wallet.
Also names the signer in the approval prompt. A transfer carrying "sc" has no
destination, no SCID and no entrypoint, so it reached the modal as an empty
request while a contract was about to be deployed; the event now carries the
signing address and marks the deployment with the size of its code.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this fixes
Two defects on the path a dApp uses to deploy a contract —
transfercarrying anscfield, the form Engram and the TELA CLI accept.On the simulator, the deployment is signed by the wrong wallet. Whatever wallet is open in the interface, the contract is signed by wallet #0. A contract that records its own deployer —
STORE("owner", SIGNER()), the ordinary pattern — therefore records a wallet the dApp was never connected to, and refuses its own creator on the next call.Off the simulator, the same path deadlocks the wallet. It never returns, and every later wallet operation blocks behind it until HOLOGRAM is restarted.
How I ran into it
A TELA dApp connected over XSWD, deploying a contract whose
Initializestores its deployer. Every other call on that same connection —GetAddress,GetBalance,scinvoke— answered for the wallet I had open. Only the deployment did not: the owner the contract recorded was wallet #0's key, and the dApp could no longer act on the contract it had just created. The same gesture through Engram, which forwardstransferto the connected wallet, records the right key.Root cause
1. The
scbranch throws away the wallet the call already resolvedThe XSWD server has no wallet selection of its own. It delegates: after approval,
xswd_server.go:1174callss.app.InternalWalletCall(...), which takeswalletManager.Lock()and setswallet := walletManager.wallet— the wallet open in the interface (wallet.go:1982-2014).GetAddress(:2019),scinvoke(:2312) and the value-transfer path (:2088+) all use that wallet. Hence the consistency everywhere else.The
scbranch (wallet.go:2091-2105) is the sole exception. It discards that resolved wallet and callsInstallSmartContract, which decides again on its own (sc_function_parser.go:463-479):GetPrimaryWallet()isGetInternalWallet(0)(simulator_wallets.go:537-540). One line contradicts the wallet the rest of the session uses.2. Off the simulator, that same handoff cannot return
InternalWalletCallholdswalletManager.Lock()for its whole body (wallet.go:1983-1984; the comment atxswd_permissions.go:128says so explicitly). Off simulator,InstallSmartContractcallsGetWallet(), which takeswalletManager.RLock()(wallet.go:1613-1620).sync.RWMutexis not reentrant: a goroutine already holding the write lock can never acquire the read lock. I reproduced exactly that sequence in a minimal program — it never returns. The deferredUnlock()therefore never runs, the wallet manager stays write-locked, and the whole wallet interface freezes until restart.So "the two paths converge off simulator and nothing shows on mainnet" is not true: off simulator this path does not sign with the wrong wallet, it does not sign at all. That nobody has reported it suggests no mainnet dApp has yet deployed by
transferfrom HOLOGRAM.3. The funding rationale for wallet #0 does not hold
The comment at
sc_function_parser.go:468-471justifies the primary wallet by saying the open wallet "may not have a properly synced balance in simulator mode". The chain says otherwise:derohe/blockchain/transaction_execute.go,case transaction.REGISTRATION:if !globals.IsMainnet() { zerobalance.Plus(800000) }. HOLOGRAM registers all of its pre-seeded wallets at startup (simulator_manager.go,RegisterAllWallets).simulator_wallets.goand searchedderohe/premine/list.txt(1326 accounts): none appears. Positive control on three real entries from that file, decoded with thetransactionpackage — those are found.cmd/simulator/simulator.go:206, 225: the initial block andmine_block_autopaygenesis_wallet, a separate seed HOLOGRAM does not expose.So on the simulator, wallet #0 holds no funding advantage over any other. Off simulator, where a "the open wallet may be empty" argument could carry weight, the primary-wallet branch never applies.
4. Nothing on screen said which wallet would sign
xswd_server.go:1043emittedxswd:requestwithmethod,params,appNameandorigin— no signer address.App.sveltebuilt the modal payload fromtransfers,scid,entrypoint, deposits and fees — thescfield was not among them. Atransfercarryingschas no destination, no SCID and no entrypoint, so the approval prompt showed an empty request while a contract was about to be deployed under a signature the user could not see.A wallet nobody chose, signing in silence, is worse than the wrong choice itself.
The change
One commit, eight files.
sc_function_parser.go):installSmartContractWith(wallet, code, anonymous)signs with the wallet it is given and never toucheswalletManager.InstallSmartContractkeeps the selection for callers that have no wallet in hand.scbranch (wallet.go) passes the wallet it already resolved. That single change removes both the wrong signer and the deadlock.InstallSmartContract,DeployToSimulatoranddeployRawSCall resolve throughgetWalletForDeployment— open wallet first, primary as a fallback — which is what every TELA deployment already did.deployRawSCtakes its signer as a parameter.App.svelteforwards them;WalletModal.svelteshows "CONTRACT DEPLOYMENT" and "SIGNING WALLET".xswd_sc_deploy_signer_test.go: three source sentinels (thescbranch passes its wallet and no longer calls the selecting entry point; the core touches neitherwalletManager, norGetWallet(), norGetPrimaryWallet(; no deployment path takes the primary wallet directly), one behaviour test on the policy with a realWallet_Disk, and two tests on the approval event.Scope and risk
modal-tx-field/modal-tx-label/modal-tx-scidpattern — no new colors, spacing, class names or animations, per the Design System Rulebook.BatchDeployToSimulatorand the Studio deploy screen now resolve their signer the same way. That is a deliberate behaviour change on the simulator: a deploy from Studio is signed by the wallet you have open rather than by #0. It is the point of the fix, but flagging it since it is visible beyond the XSWD path.Testing
go build ./...,go vet ./...,go test -short ./...,gofmt -l— clean.npm run buildfor the frontend — clean.The three source sentinels were replayed against the unfixed tree: all three fail there, so they are testing what they claim to test.
Manually, on the simulator: a wallet other than #0 open, a dApp connected to it over XSWD deploying a contract whose
InitializedoesSTORE("owner", HEX(SIGNER())), then reading the stored key back withGetSC. Before the change the stored key is wallet #0's; after it, the connected wallet's.AI disclosure
Per CONTRIBUTING: this patch was written with an AI coding agent (Claude). The call paths, the lock behaviour and the premine/registration evidence above were read out of the actual sources —
wallet.go,sc_function_parser.go,xswd_permissions.go,derohe/blockchain/transaction_execute.go,derohe/premine/list.txt,cmd/simulator/simulator.go— rather than inferred, and the deadlock was confirmed with a standalone program reproducing the lock sequence.