CryptoPunks
Walkthrough of a CryptoPunks bug and how GhostLogs fixes it
The CryptoPunks contract has a bug in the acceptBidForPunk
function. When a bid is accepted, right
before the PunkBought
event is emitted, the values are inadvertently reset as shown below.
If the PunkBought
event had been 2 lines up it would have been emitted correctly but instead the bidder
is erroneously logged as the zero address (0x0000000000000000000000000000000000000000
) and the value is
shown as 0
. (This only happens when the punk is purchased through acceptBidForPunk
)
We can confirm by looking at etherscan that these values are being emitted incorrectly.
Let's fix this with GhostLogs.
Step 1: Create a fork
Step 2: Add the CryptoPunks market contract
If you want to copy-paste the address, 0xb47e3cd837dDF8e4c57F05d70Ab865de6e193BBB
is the contract address
for the CryptoPunks marketplace
In the next screen you'll be able to set the version since GhostLogs supports multiple versions of a single contract and allows you to switch between them.
For now, insert "1" as the version and click 'Create'
Step 3: Edit the contract
Here is a link to the modified code for your reference: CryptoPunks
Declare a new event GhostPunkBidAccepted
In the acceptBidForPunk
function, emit your newly declared event above the line where the punkBids
storage value changes
Click on 'Compile and Save'
Step 4: Simulate to test new code
Let's simulate the same transaction
that we showed above and which had a wrong value
and toAddress
. Click on 'Add Tx Hash' and input a transaction
that you want to simulate against your newly added gasless events.
As we can see, the value
and toAddress
are now correct! Now that we are satisfied with our modifications
let's deploy the Fork to activate RPC and WS endpoints.
Step 5: Spin up a GhostFork RPC endpoint
Back on the Fork details page, click on 'Deploy fork' to activate your RPC and WS endpoints
In the deployments page you can find the endpoints for each deployed fork. Then you can connect using libraries like ethers, viem, alloy, etc using the Supported Methods
Let's look at how to synchronously fetch a few logs with getLogs
.
(Or check out a websocket subscription example)
import { EventFragment, Interface, JsonRpcProvider } from 'ethers'
const RPC_URL = 'https://rpc.ghostlogs.xyz/<FORK_KEY_FROM_DASHBOARD>'
const PUNKS_ADDRESS = '0xb47e3cd837dDF8e4c57F05d70Ab865de6e193BBB'
async function run() {
// You can also use the ABI, but let's write it out new event here to be explicit
const bidAccepted = EventFragment.from(
`GhostPunkBidAccepted(uint indexed punkIndex, uint value,
address indexed fromAddress, address indexed toAddress)`
)
const iface = new Interface([bidAccepted])
const provider = new JsonRpcProvider(RPC_URL)
const logs = await provider.getLogs({
address: PUNKS_ADDRESS,
fromBlock: 18903200,
toBlock: 18903300,
topics: [[bidAccepted.topicHash]],
})
for (let log of logs) {
const decodedLog = iface.decodeEventLog(bidAccepted, log.data, log.topics)
console.log({
block: log.blockNumber,
txHash: log.transactionHash,
txIdx: log.transactionIndex,
punkTokenId: decodedLog.punkIndex,
value: decodedLog.value,
from: decodedLog.fromAddress,
to: decodedLog.toAddress,
})
}
}
if (require.main === module) {
;(async () => {
await run()
})()
}
Output:
{
block: 18903238,
txHash: '0x028399088834d783207824dbe291285981be42e94c44d86c3859676ce5247613',
txIdx: 8,
punkTokenId: 5256n,
value: 53690000000000000000n,
from: '0x65967071bA921D1d3F650420a3e814d13994FA8e',
to: '0xd4f7335eaDfE176117B7719b323cf31A7f273310'
}