foundry-rs / foundry

Foundry is a blazing fast, portable and modular toolkit for Ethereum application development written in Rust.
https://getfoundry.sh
Apache License 2.0
8.33k stars 1.76k forks source link

Discrepancy in Hash Values Due to block.timestamp Handling in Foundry Scripts? #9323

Closed geolffreym closed 1 week ago

geolffreym commented 1 week ago

Component

Forge

Have you ensured that all of these are up to date?

What version of Foundry are you on?

0.2.0

What command(s) is the bug in?

forge script

Operating System

Linux

Describe the bug

When executing a Foundry script that interacts with a smart contract on the Amoy network, an inconsistency in hash values has been observed. The script logs a specific hash value; however, querying the contract directly returns a different hash.

Observed Behavior:

The hash value logged by the Foundry script differs from the hash stored in the contract on Amoy.

Expected Behavior:

The hash values from both the script's log and the contract's storage should be identical, assuming consistent block.timestamp usage.

// script run
forge script --chain polygon-amoy script/01_Create_SubscriptionAgreement.s.sol --rpc-url polygon-amoy --broadcast --verify  --skip-simulation

// inside script
vm.startBroadcast(admin);
address[] memory parties = new address[](1);
parties[0] = 0x037f2b49721E34296fBD8F9E7e9cc6D5F9ecE7b4;
IERC20(mmc).approve(rightsAgreement, 10 * 1e18);

IRightsAccessAgreement(rightsAgreement).createAgreement(
    10 * 1e18,
    mmc,
    rightsPolicyManager,
    parties,
    ""
);

vm.stopBroadcast();

The returned in console: image The registered in explorer: image

I created a method to get the stored proof and is correctly retrieved and equal to explorer:

cast call 0xe40fd877e9510c5342062f936bca8463337822a9 "agrr(uint256) (uint256)" 3 --rpc-url=polygon-amoy
result: 101306063479486255180560820321998590842742970837040172950596646736320943069278

The proof is generated based on the struct and the following code:

T.Agreement({
     active: true, // the agreement status, true for active, false for closed.
     broker: broker, // the authorized account to manage the agreement
     currency: currency, // the currency used in transaction
     initiator: msg.sender, // the tx initiator
     amount: amount, // the transaction amount
     fees: deductions, // the protocol fees of the agreement
     available: available, // the remaining amount after fees
     createdAt: block.timestamp, // the agreement creation time <- removing this works fine
     parties: parties, // the accounts related to agreement
     payload: payload // any additional data needed during agreement execution
});

 /// @dev Generates a unique proof for an agreement using keccak256 hashing.
 function _createProof(T.Agreement memory agreement) private pure returns (uint256) {
        // yes, we can encode full struct as abi.encode with extra overhead..
        bytes memory rawProof = abi.encode(agreement);
        bytes32 proof = keccak256(rawProof);
        return uint256(proof);
}

The broadcast run show the same registered data as explorer too:

{
  "address": "0xe40fd877e9510c5342062f936bca8463337822a9",
  "topics": [
    "0xb649569e5c9bb3f3eb7cc2fd396f5abc452ec3f6f6e2d62638b48d3e7fa599ca",
    "0x000000000000000000000000efbbd14082cf2fbcf5badc7ee619f0f4e36d0a5b"
  ],
  "data": "0x33bc00873025602bc8d236178b5ff91d8411e3dfc839f26183f0a7177c5d3691",
  "blockHash": "0x425e6d4eeef4e7475ae7c912e4b77834a6c067674b55b5f72bb38a5f6b1abd91",
  "blockNumber": "0xdc0c86",
  "transactionHash": "0x252129d93251cce674afe7aa557d41271dd2eef8f8e7f8bbfdb605826512ccbc",
  "transactionIndex": "0x2",
  "logIndex": "0x7",
  "removed": false
}
grandizzy commented 1 week ago

hey @geolffreym is the screenshot where you say The returned in console: from Traces, that is by running script with -vvvv?

geolffreym commented 1 week ago

Hey @grandizzy , thats right, i missed the param in the issue description.. forge script --chain polygon-amoy script/01_Create_SubscriptionAgreement.s.sol --rpc-url polygon-amoy --broadcast --verify --skip-simulation -vvvv

grandizzy commented 1 week ago

so there's the presimulation phase, that is before tx to be included hence different block.timestamp, given a contract like

contract Counter {
    event CreatedAt(uint256 ts);

    constructor(uint256 ts) {
        emit CreatedAt(block.timestamp);
    } 
}

running forge script script/Counter.s.sol --rpc-url RPC_URL -vvvv --broadcast you will see that's even before a simulation / timestamp different between these stages too

[⠒] Compiling...
No files changed, compilation skipped
Traces:
  [97106] CounterScript::run()
    ├─ [0] VM::startBroadcast()
    │   └─ ← [Return] 
    ├─ [56396] → new Counter@0x5b73C5498c1E3b4dbA84de0F1833c4a029d90519
    │   ├─ emit CreatedAt(ts: 1731601737 [1.731e9])
    │   └─ ← [Return] 265 bytes of code
    ├─ [0] VM::stopBroadcast()
    │   └─ ← [Return] 
    └─ ← [Stop] 

Script ran successfully.

## Setting up 1 EVM.
==========================
Simulated On-chain Traces:

  [56396] → new Counter@0x5b73C5498c1E3b4dbA84de0F1833c4a029d90519
    ├─ emit CreatedAt(ts: 1731601741 [1.731e9])
    └─ ← [Return] 265 bytes of code

==========================

Chain 10

Estimated gas price: 0.000101426 gwei

Estimated total gas used for script: 150618

Estimated amount required: 0.000000015276581268 ETH

==========================

therefore not possible to see in trace the exact value resulted within mined tx. Hope this makes sense

geolffreym commented 1 week ago

Ok! in my case i am skipping the simulation. Anyway i realized that the returned proof is inconsistent too?

uint256 proof = IRightsAccessAgreement(rightsAgreement).createAgreement(
    10 * 1e18,
    mmc,
    rightsPolicyManager,
    parties,
    ""
);
console.logUint(proof);
require(proof == expected); <- fail
grandizzy commented 1 week ago

presimulation always happens (even if skipping the simulation) take for example this run that won't broadcast tx because of using the default sender

forge script script/Counter.s.sol --rpc-url https://opt-mainnet.g.alchemy.com/v2/p5S0MgJrB48Y_RuVrOcVp3wLnV6Vzd8a -vvvv --broadcast --skip-simulation
[⠒] Compiling...
No files changed, compilation skipped
Traces:
  [97106] CounterScript::run()
    ├─ [0] VM::startBroadcast()
    │   └─ ← [Return] 
    ├─ [56396] → new Counter@0x5b73C5498c1E3b4dbA84de0F1833c4a029d90519
    │   ├─ emit CreatedAt(ts: 1731602227 [1.731e9])
    │   └─ ← [Return] 265 bytes of code
    ├─ [0] VM::stopBroadcast()
    │   └─ ← [Return] 
    └─ ← [Stop] 

Script ran successfully.

SKIPPING ON CHAIN SIMULATION.

Transactions saved to: broadcast/Counter.s.sol/10/run-latest.json

Sensitive values saved to: cache/Counter.s.sol/10/run-latest.json

Error: You seem to be using Foundry's default sender. Be sure to set your own --sender.

The trace is not from the mined tx but from the local presimulation phase.

geolffreym commented 1 week ago

Ok, just to be sure i am following you @grandizzy :


contract OrchestrateRightsAuthorizer is Script {
    function run() external {
        uint256 admin = vm.envUint("PRIVATE_KEY");
        address mmc = vm.envAddress("MMC");
        address rightsPolicyManager = vm.envAddress("RIGHT_POLICY_MANAGER");
        address rightsAgreement = vm.envAddress("RIGHT_ACCESS_AGREEMENT");

        vm.startBroadcast(admin);
        address[] memory parties = new address[](1);
        parties[0] = 0x037f2b49721E34296fBD8F9E7e9cc6D5F9ecE7b4;
        IERC20(mmc).approve(rightsAgreement, 10 * 1e18);
        uint256 proof = IRightsAccessAgreement(rightsAgreement).createAgreement(
            10 * 1e18,
            mmc,
            rightsPolicyManager,
            parties,
            ""
        );

        console.logUint(proof); <- this proof is the result of the simulation?
        vm.stopBroadcast();
    }
}
grandizzy commented 1 week ago

that's my understanding, the proof is computed using the block.timestamp from presimulation phase, hence different in traces than what will be eventually included, but I could be wrong, @klkvr mind to chime in? thank you!

klkvr commented 1 week ago

yep, this is probably same as https://github.com/foundry-rs/foundry/issues/8054

grandizzy commented 1 week ago

@geolffreym hope this clarifies why the difference, going to close this for now. Thank you

geolffreym commented 1 week ago

@geolffreym hope this clarifies why the difference, going to close this for now. Thank you

Yep! Thank you @grandizzy @klkvr