Skip to content

Commit c182e0c

Browse files
feat(evm): target-agnostic TrainRouter, payoutCurve in lock events, native + invariant coverage
TrainRouter: rewrite as a target-agnostic signed-call forwarder. Drops the ITrain coupling and Train-schema EIP-712 literals; the intent now binds (user, train, token, amount, keccak256(callData)) and forwardWithPermit / forwardWithPermit2 / forwardWithAuthorization pull funds and forward opaque callData with an exact-amount approval and a post-call conservation check. Train: emit payoutCurve in the UserLocked and SolverLocked events so indexers/solvers can see the curve without an extra RPC call (no core-logic change). Tests: rewrite the router unit/fork/invariant tests for the new API; add native-ETH redeemSolver failure-edge tests; assert payoutCurve in the lock-event tests. Full suite green including the mainnet-fork Permit2/USDC checks; Echidna/Medusa run clean. Scripts: add the native-ETH Sepolia flow (TestNative + native refund pair) and migrate the gasless scripts to the forwarder API. Also: add the Echidna/Medusa invariant suite and configs plus a Slither stub; refresh READMEs; remove the Tron deploy script and the deprecated Linear/Sqrt/Volatility payout curves.
1 parent deef428 commit c182e0c

66 files changed

Lines changed: 6723 additions & 1924 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

chains/evm/solidity/.gitignore

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
out/
33
cache/
44
broadcast/
5+
.gas-snapshot
56

67
# Dependencies
78
lib/
@@ -13,4 +14,15 @@ lib/
1314
# Environment
1415
.env
1516
.env.local
16-
deployments/
17+
deployments/
18+
19+
# Fuzzing runtime artifacts (regenerable)
20+
crytic-export/
21+
**/crytic-export/
22+
corpus/
23+
24+
# Node (deploy-tron.js)
25+
node_modules/
26+
27+
# Generated docs (forge doc)
28+
docs/

chains/evm/solidity/README.md

Lines changed: 205 additions & 606 deletions
Large diffs are not rendered by default.

chains/evm/solidity/echidna.yaml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
testMode: "assertion"
2+
prefix: "property_"
3+
seqLen: 100
4+
testLimit: 50000
5+
balanceContract: 0xffffffffffffffffffffffffffffffffffffffffffffffff
6+
coverage: true
7+
corpusDir: "corpus/echidna"
8+
cryticArgs: ["--foundry-compile-all"]
9+
contractAddr: "0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496"
10+
deployer: "0x1804c8AB1F12E6bbf3894d4083f33e07309d1f38"
11+
# output format (comment out for TUI)
12+
format: "text"
13+
# Hide solc stderr output and additional information during the testing.
14+
quiet: false
15+
stopOnFail: false
16+
disableSlither: true
17+
18+
# https://secure-contracts.com/program-analysis/echidna/configuration.html

chains/evm/solidity/foundry.toml

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,12 @@
22
src = "src"
33
out = "out"
44
libs = ["lib"]
5-
solc = "0.8.30"
5+
solc = "0.8.34"
66
evm_version = "cancun"
77
via_ir = true
88
optimizer = true
9-
optimizer_runs = 200
10-
# Temporarily exclude scripts until they are updated for new solverLock signature
11-
script = "script_disabled"
9+
optimizer_runs = 1000000
10+
script = "script"
1211

1312
remappings = [
1413
"@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/"
@@ -17,23 +16,33 @@ remappings = [
1716
[profile.default.fuzz]
1817
runs = 256
1918

19+
[profile.default.invariant]
20+
runs = 64
21+
depth = 128
22+
fail_on_revert = false
23+
2024
# Tron build profile.
2125
# Use: $env:FOUNDRY_PROFILE="tron"; forge build
2226
[profile.tron]
2327
src = "src"
2428
out = "out"
2529
libs = ["lib"]
26-
solc = "0.8.30"
30+
solc = "0.8.34"
2731
evm_version = "cancun"
2832
via_ir = true
2933
optimizer = true
30-
optimizer_runs = 200
31-
script = "script_disabled"
34+
optimizer_runs = 1000000
35+
script = "script"
3236
remappings = [
3337
"@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/"
3438
]
3539

40+
[etherscan]
41+
sepolia = { key = "${ETHERSCAN_API_KEY}", chain = 11155111 }
42+
3643
[fmt]
3744
line_length = 120
3845
tab_width = 2
3946
bracket_spacing = true
47+
48+

chains/evm/solidity/medusa.json

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
{
2+
"fuzzing": {
3+
"workers": 10,
4+
"workerResetLimit": 50,
5+
"timeout": 0,
6+
"testLimit": 500000,
7+
"callSequenceLength": 100,
8+
"corpusDirectory": "corpus/medusa",
9+
"coverageEnabled": true,
10+
"deploymentOrder": [
11+
"FuzzTester"
12+
],
13+
"targetContracts": [
14+
"FuzzTester"
15+
],
16+
"targetContractsBalances": [
17+
"0xffffffffffffffffffffffff"
18+
],
19+
"constructorArgs": {},
20+
"deployerAddress": "0x1804c8AB1F12E6bbf3894d4083f33e07309d1f38",
21+
"senderAddresses": [
22+
"0x10000",
23+
"0x20000",
24+
"0x30000"
25+
],
26+
"blockNumberDelayMax": 60480,
27+
"blockTimestampDelayMax": 604800,
28+
"blockGasLimit": 125000000,
29+
"transactionGasLimit": 12500000,
30+
"testing": {
31+
"stopOnFailedTest": false,
32+
"stopOnFailedContractMatching": false,
33+
"stopOnNoTests": true,
34+
"testAllContracts": false,
35+
"traceAll": false,
36+
"assertionTesting": {
37+
"enabled": true,
38+
"testViewMethods": true,
39+
"panicCodeConfig": {
40+
"failOnCompilerInsertedPanic": false,
41+
"failOnAssertion": true,
42+
"failOnArithmeticUnderflow": false,
43+
"failOnDivideByZero": false,
44+
"failOnEnumTypeConversionOutOfBounds": false,
45+
"failOnIncorrectStorageAccess": false,
46+
"failOnPopEmptyArray": false,
47+
"failOnOutOfBoundsArrayAccess": false,
48+
"failOnAllocateTooMuchMemory": false,
49+
"failOnCallUninitializedVariable": false
50+
}
51+
},
52+
"propertyTesting": {
53+
"enabled": true,
54+
"testPrefixes": [
55+
"property_"
56+
]
57+
},
58+
"optimizationTesting": {
59+
"enabled": false,
60+
"testPrefixes": [
61+
"optimize_"
62+
]
63+
}
64+
},
65+
"chainConfig": {
66+
"codeSizeCheckDisabled": true,
67+
"cheatCodes": {
68+
"cheatCodesEnabled": true,
69+
"enableFFI": false
70+
}
71+
}
72+
},
73+
"compilation": {
74+
"platform": "crytic-compile",
75+
"platformConfig": {
76+
"target": ".",
77+
"solcVersion": "",
78+
"exportDirectory": "corpus/crytic-export",
79+
"args": [
80+
"--foundry-compile-all"
81+
]
82+
}
83+
},
84+
"logging": {
85+
"level": "info",
86+
"logDirectory": "corpus/logs",
87+
"noColor": false
88+
}
89+
}

chains/evm/solidity/remappings.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/
2+
erc4626-tests/=lib/openzeppelin-contracts/lib/erc4626-tests/
3+
forge-std/=lib/forge-std/src/
4+
halmos-cheatcodes/=lib/openzeppelin-contracts/lib/halmos-cheatcodes/src/
5+
openzeppelin-contracts/=lib/openzeppelin-contracts/
Lines changed: 28 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,36 @@
11
// SPDX-License-Identifier: MIT
2-
pragma solidity 0.8.30;
2+
pragma solidity 0.8.34;
33

4-
import "forge-std/Script.sol";
5-
import "../src/Train.sol";
4+
import 'forge-std/Script.sol';
5+
import { Train } from '../src/Train.sol';
6+
import { TrainRouter } from '../src/TrainRouter.sol';
7+
import { ConstantPayoutCurve } from '../src/ConstantPayoutCurve.sol';
68

7-
/// @title Deploy Script for Train Protocol
9+
/// @title Deploy Train + TrainRouter + ConstantPayoutCurve (Sepolia)
810
/// @dev Usage:
9-
/// forge script script/Deploy.s.sol --rpc-url $RPC_URL --broadcast --verify
11+
/// export USER_PK=0x... # deployer = user account
12+
/// export ETHERSCAN_API_KEY=... # for --verify
13+
/// forge script script/Deploy.s.sol \
14+
/// --rpc-url $SEPOLIA_RPC_URL --broadcast \
15+
/// --verify --etherscan-api-key $ETHERSCAN_API_KEY
16+
/// @dev Train, TrainRouter, and ConstantPayoutCurve are all deployed via `new`, so they auto-verify.
1017
contract DeployScript is Script {
11-
function run() external {
12-
uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY");
18+
function run() external {
19+
uint256 pk = vm.envUint('USER_PK');
1320

14-
vm.startBroadcast(deployerPrivateKey);
21+
vm.startBroadcast(pk);
22+
address curve = address(new ConstantPayoutCurve());
23+
Train train = new Train();
24+
TrainRouter router = new TrainRouter();
25+
vm.stopBroadcast();
1526

16-
Train train = new Train();
17-
console.log("Train deployed at:", address(train));
18-
19-
vm.stopBroadcast();
20-
}
27+
console.log('ConstantPayoutCurve:', curve);
28+
console.log('Train :', address(train));
29+
console.log('TrainRouter :', address(router));
30+
console.log('');
31+
console.log('Export these before running the test scripts:');
32+
console.log(string.concat(' export TRAIN=', vm.toString(address(train))));
33+
console.log(string.concat(' export ROUTER=', vm.toString(address(router))));
34+
console.log(string.concat(' export CONSTANT_CURVE=', vm.toString(curve)));
35+
}
2136
}
Lines changed: 8 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
// SPDX-License-Identifier: MIT
2-
pragma solidity 0.8.30;
2+
pragma solidity 0.8.34;
33

44
import "forge-std/Script.sol";
5-
import "../src/SqrtPayoutCurve.sol";
6-
import "../src/LinearPayoutCurve.sol";
7-
import "../src/VolatilityDecayCurve.sol";
5+
import "../src/ConstantPayoutCurve.sol";
86

9-
/// @title Deploy Script for Payout Curve Libraries
7+
/// @title Deploy Script for the Payout Curve
8+
/// @notice The protocol retains a single curve: ConstantPayoutCurve (no decay, returns the
9+
/// full locked amount). Configure a lock with this curve address and any (ignored)
10+
/// payoutCurveData to opt into the curve mechanism as an explicit no-op.
1011
/// @dev Usage:
1112
/// forge script script/DeployPayoutCurves.s.sol --rpc-url $RPC_URL --broadcast --verify
1213
contract DeployPayoutCurvesScript is Script {
@@ -15,39 +16,9 @@ contract DeployPayoutCurvesScript is Script {
1516

1617
vm.startBroadcast(deployerPrivateKey);
1718

18-
address sqrtCurve = _deployLib(type(SqrtPayoutCurve).creationCode);
19-
console.log("SqrtPayoutCurve deployed at: ", sqrtCurve);
20-
21-
address linearCurve = _deployLib(type(LinearPayoutCurve).creationCode);
22-
console.log("LinearPayoutCurve deployed at: ", linearCurve);
23-
24-
address volCurve = _deployLib(type(VolatilityDecayCurve).creationCode);
25-
console.log("VolatilityDecayCurve deployed at: ", volCurve);
19+
address constantCurve = address(new ConstantPayoutCurve());
20+
console.log("ConstantPayoutCurve deployed at: ", constantCurve);
2621

2722
vm.stopBroadcast();
28-
29-
// Example LinearPayoutCurve config
30-
// Scenario: 1 ETH lock that decays to a 0.8 ETH floor over 1 hour after a 60-second grace period.
31-
//
32-
// gracePeriod = 60 seconds
33-
// Pmin = 0.8 ether
34-
// rate = (1 ether - 0.8 ether) / 3600 seconds = 0.2 ether / 3600 s
35-
// = 0.2e18 / 3600 ≈ 55_555_555_555_555 wei/s
36-
// rate_scaled = rate × 1e18 = 55_555_555_555_555 * 1e18
37-
// ≈ 5.556e31
38-
//
39-
// Any lock using this config and amount=1 ether will reach Pmin after exactly 3600 s post-grace.
40-
bytes memory linearConfig = abi.encode(
41-
uint256(60), // gracePeriod: 60 s relay buffer
42-
uint256(55_555_555_555_555 * 1e18), // rate: ~0.2 ETH decays over 3600 s (scaled ×1e18)
43-
uint256(0.8 ether) // Pmin: 80% floor (absolute, in token-wei)
44-
);
45-
console.log("Example LinearPayoutCurve config (hex):");
46-
console.logBytes(linearConfig);
47-
}
48-
49-
function _deployLib(bytes memory bytecode) internal returns (address addr) {
50-
assembly { addr := create(0, add(bytecode, 0x20), mload(bytecode)) }
51-
require(addr != address(0), 'lib deploy failed');
5223
}
5324
}

0 commit comments

Comments
 (0)