Created
September 2, 2026 23:20
-
-
Save Strykar/1ddef28a20db637677dc05bdbb4670a7 to your computer and use it in GitHub Desktop.
Verify LIP-118 delegated reward calling against deployed Arbitrum One contracts on a local anvil fork (no mainnet txs)
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
| === orchestrator under test (first in pool): 0x525419FF5707190389bfb5C87c375D710F5fCb0E === | |
| hints: prev=0x0000000000000000000000000000000000000000 next=0xac2e50c8f7Ac0f82923a7Df9d9903f6ec4741919 | |
| === 0. baseline === | |
| transcoderToRewardCaller = 0x0000000000000000000000000000000000000000 | |
| RoundsManager.currentRound() = 4323 | |
| getTranscoder(orch).lastRewardRound = 4323 | |
| === 1. negative control: selector that does not exist === | |
| execution reverted | |
| === 2. unauthorized caller === | |
| execution reverted: caller must be a reward caller set by the transcoder | |
| === 3. setRewardCaller from the orchestrator === | |
| status: 0x1 | |
| RewardCallerSet topic: 0x15932b87747cac36d3c505e86a946c6d3a79d9d7bdb79d81584d32bbe54516c2 | |
| transcoder : 0x525419ff5707190389bfb5c87c375d710f5fcb0e | |
| rewardCaller: 0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266 | |
| expected topic: 0x15932b87747cac36d3c505e86a946c6d3a79d9d7bdb79d81584d32bbe54516c2 | |
| mapping now: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 | |
| === 4. authorized caller, round not yet initialized === | |
| execution reverted: current round is not initialized | |
| === 5. initializeRound, then reward as the delegated caller === | |
| initializeRound status: 0x1 | |
| reward status: 0x1 | |
| Reward event -> transcoder: 0x525419ff5707190389bfb5c87c375d710f5fcb0e | |
| Reward event -> amount : 4362.805959257629 LPT | |
| lastRewardRound: 4323 -> 78847 [7.884e4] | |
| currentRound() now: 78847 [7.884e4] | |
| msg.sender: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 | |
| === 6. revoke === | |
| status: 0x1 | |
| mapping now: 0x0000000000000000000000000000000000000000 | |
| === 7. previously-authorized caller, after revoke === | |
| execution reverted: caller must be a reward caller set by the transcoder | |
| === END OF TRANSCRIPT (verify-lip118-fork.sh) === |
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
| #!/usr/bin/env bash | |
| # LIP-118 delegation against the deployed Arbitrum One contracts, on a local | |
| # anvil fork. Sends no mainnet transactions. | |
| # | |
| # Uses rewardForTranscoderWithHint (0xb556966a) throughout, not the sibling | |
| # rewardForTranscoder (0x1a8c41f8), because only the former is on the node's | |
| # path. Testing the sibling would pass while proving nothing. | |
| # | |
| # Requires foundry. | |
| set -euo pipefail | |
| RPC=${RPC:-http://127.0.0.1:8545} | |
| BM=0x35Bcf3c30594191d53231E4FF333E8A770453e40 # BondingManager (Arbitrum One) | |
| RM=0xdd6f56DcC28D3F5f27084381fE8Df634985cc39f # RoundsManager (Arbitrum One) | |
| CALLER=0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 # anvil account 0 | |
| STRANGER=0x70997970C51812dc3A010C7d01b50e0d17dc79C8 # anvil account 1 | |
| say() { printf '\n=== %s ===\n' "$*"; } | |
| reason() { grep -o 'execution reverted[^,]*' | head -1 || echo '(no revert)'; } | |
| ORCH=$(cast call --rpc-url "$RPC" $BM "getFirstTranscoderInPool()(address)") | |
| say "orchestrator under test (first in pool): $ORCH" | |
| # Real hints, not a fallback: this orchestrator is first in the pool and reward | |
| # only raises its stake, so it stays first. The node derives these from | |
| # simulateTranscoderPoolUpdate, which is not replicated here. | |
| ZERO=0x0000000000000000000000000000000000000000 | |
| PREV=$ZERO | |
| NEXT=$(cast call --rpc-url "$RPC" $BM "getNextTranscoderInPool(address)(address)" "$ORCH") | |
| echo "hints: prev=$PREV next=$NEXT" | |
| say "0. baseline" | |
| echo "transcoderToRewardCaller = $(cast call --rpc-url "$RPC" $BM 'transcoderToRewardCaller(address)(address)' "$ORCH")" | |
| echo "RoundsManager.currentRound() = $(cast call --rpc-url "$RPC" $RM 'currentRound()(uint256)')" | |
| echo "getTranscoder(orch).lastRewardRound = $(cast call --rpc-url "$RPC" $BM 'getTranscoder(address)(uint256,uint256,uint256,uint256,uint256,uint256,uint256)' "$ORCH" | head -1)" | |
| say "1. negative control: selector that does not exist" | |
| { cast call --rpc-url "$RPC" --from $STRANGER $BM "rewardForNobody(address)" "$ORCH" 2>&1 || true; } | reason | |
| say "2. unauthorized caller" | |
| { cast call --rpc-url "$RPC" --from $STRANGER $BM "rewardForTranscoderWithHint(address,address,address)" "$ORCH" $ZERO $ZERO 2>&1 || true; } | reason | |
| cast rpc --rpc-url "$RPC" anvil_impersonateAccount "$ORCH" >/dev/null | |
| cast rpc --rpc-url "$RPC" anvil_setBalance "$ORCH" 0xde0b6b3a7640000 >/dev/null | |
| cast rpc --rpc-url "$RPC" anvil_setBalance $CALLER 0xde0b6b3a7640000 >/dev/null | |
| say "3. setRewardCaller from the orchestrator" | |
| cast send --rpc-url "$RPC" --unlocked --from "$ORCH" $BM "setRewardCaller(address)" $CALLER --json \ | |
| | python3 -c 'import json,sys; r=json.load(sys.stdin); print("status:",r["status"]); print("RewardCallerSet topic:",r["logs"][0]["topics"][0]); print(" transcoder :","0x"+r["logs"][0]["topics"][1][-40:]); print(" rewardCaller:","0x"+r["logs"][0]["topics"][2][-40:])' | |
| echo "expected topic: $(cast keccak 'RewardCallerSet(address,address)')" | |
| echo "mapping now: $(cast call --rpc-url "$RPC" $BM 'transcoderToRewardCaller(address)(address)' "$ORCH")" | |
| say "4. authorized caller, round not yet initialized" | |
| { cast call --rpc-url "$RPC" --from $CALLER $BM "rewardForTranscoderWithHint(address,address,address)" "$ORCH" $PREV $NEXT 2>&1 || true; } | reason | |
| say "5. initializeRound, then reward as the delegated caller" | |
| cast send --rpc-url "$RPC" --unlocked --from $CALLER $RM "initializeRound()" --json \ | |
| | python3 -c 'import json,sys; print("initializeRound status:",json.load(sys.stdin)["status"])' | |
| BEFORE=$(cast call --rpc-url "$RPC" $BM "getTranscoder(address)(uint256,uint256,uint256,uint256,uint256,uint256,uint256)" "$ORCH" | head -1) | |
| cast send --rpc-url "$RPC" --unlocked --from $CALLER $BM "rewardForTranscoderWithHint(address,address,address)" "$ORCH" $PREV $NEXT --json \ | |
| | python3 -c ' | |
| import json,sys | |
| r=json.load(sys.stdin); print("reward status:",r["status"]) | |
| for l in r["logs"]: | |
| if l["topics"][0]=="0x619caafabdd75649b302ba8419e48cccf64f37f1983ac4727cfb38b57703ffc9": | |
| print("Reward event -> transcoder:","0x"+l["topics"][1][-40:]) | |
| print("Reward event -> amount :",int(l["data"],16)/1e18,"LPT")' | |
| AFTER=$(cast call --rpc-url "$RPC" $BM "getTranscoder(address)(uint256,uint256,uint256,uint256,uint256,uint256,uint256)" "$ORCH" | head -1) | |
| echo "lastRewardRound: $BEFORE -> $AFTER" | |
| echo "currentRound() now: $(cast call --rpc-url "$RPC" $RM 'currentRound()(uint256)')" | |
| echo "msg.sender: $CALLER" | |
| say "6. revoke" | |
| cast send --rpc-url "$RPC" --unlocked --from "$ORCH" $BM "setRewardCaller(address)" \ | |
| 0x0000000000000000000000000000000000000000 --json \ | |
| | python3 -c 'import json,sys; print("status:",json.load(sys.stdin)["status"])' | |
| echo "mapping now: $(cast call --rpc-url "$RPC" $BM 'transcoderToRewardCaller(address)(address)' "$ORCH")" | |
| say "7. previously-authorized caller, after revoke" | |
| { cast call --rpc-url "$RPC" --from $CALLER $BM "rewardForTranscoderWithHint(address,address,address)" "$ORCH" $PREV $NEXT 2>&1 || true; } | reason | |
| echo | |
| echo "=== END OF TRANSCRIPT (verify-lip118-fork.sh) ===" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment