From e9666ec33cd3b133dadfc5a9497e5996775fc2da Mon Sep 17 00:00:00 2001 From: excaliborr Date: Fri, 27 Oct 2023 06:35:06 -0400 Subject: [PATCH 01/29] chore: remove template contracts --- .gitignore | 1 - out/Greeter.sol/Greeter.json | 1672 ------------------------------ solidity/contracts/Greeter.sol | 59 -- solidity/interfaces/IGreeter.sol | 75 -- solidity/scripts/Deploy.sol | 30 - solidity/test/e2e/Common.sol | 8 - solidity/test/e2e/Greeter.t.sol | 16 - solidity/test/unit/Greeter.t.sol | 99 -- 8 files changed, 1960 deletions(-) delete mode 100644 out/Greeter.sol/Greeter.json delete mode 100644 solidity/contracts/Greeter.sol delete mode 100644 solidity/interfaces/IGreeter.sol delete mode 100644 solidity/scripts/Deploy.sol delete mode 100644 solidity/test/e2e/Greeter.t.sol delete mode 100644 solidity/test/unit/Greeter.t.sol diff --git a/.gitignore b/.gitignore index f39f513..6d577c5 100644 --- a/.gitignore +++ b/.gitignore @@ -15,7 +15,6 @@ out-via-ir # Keep related abi out/*/* -!out/Greeter.sol/* # Keep the latest deployment only broadcast/*/*/* diff --git a/out/Greeter.sol/Greeter.json b/out/Greeter.sol/Greeter.json deleted file mode 100644 index 68e5f73..0000000 --- a/out/Greeter.sol/Greeter.json +++ /dev/null @@ -1,1672 +0,0 @@ -{ - "abi": [ - { - "inputs": [ - { - "internalType": "string", - "name": "_greeting", - "type": "string" - }, - { - "internalType": "contract IERC20", - "name": "_token", - "type": "address" - } - ], - "stateMutability": "nonpayable", - "type": "constructor" - }, - { - "inputs": [], - "name": "Greeter_InvalidGreeting", - "type": "error" - }, - { - "inputs": [], - "name": "Greeter_OnlyOwner", - "type": "error" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "string", - "name": "_greeting", - "type": "string" - } - ], - "name": "GreetingSet", - "type": "event" - }, - { - "inputs": [], - "name": "OWNER", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "greet", - "outputs": [ - { - "internalType": "string", - "name": "_greeting", - "type": "string" - }, - { - "internalType": "uint256", - "name": "_balance", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "greeting", - "outputs": [ - { - "internalType": "string", - "name": "", - "type": "string" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "string", - "name": "_greeting", - "type": "string" - } - ], - "name": "setGreeting", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "token", - "outputs": [ - { - "internalType": "contract IERC20", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - } - ], - "bytecode": { - "object": "0x60a06040523480156200001157600080fd5b5060405162000b9c38038062000b9c833981016040819052620000349162000186565b33608052600180546001600160a01b0319166001600160a01b0383161790556200005e8262000066565b5050620003e3565b6080516001600160a01b0316336001600160a01b0316146200009b57604051630bec364760e31b815260040160405180910390fd5b805160208201207f3a2db9fe7908dcc36d81824d2338fc3f1aff49ac357dd8c4840527fba27a5b9001620000e2576040516342883a0560e11b815260040160405180910390fd5b6000620000f08282620002e2565b507fad181ee258ff92d26bf7ed2e6b571ef1cba3afc45f028b863b0f02adaffc2f0681604051620001229190620003ae565b60405180910390a150565b634e487b7160e01b600052604160045260246000fd5b60005b838110156200016057818101518382015260200162000146565b50506000910152565b80516001600160a01b03811681146200018157600080fd5b919050565b600080604083850312156200019a57600080fd5b82516001600160401b0380821115620001b257600080fd5b818501915085601f830112620001c757600080fd5b815181811115620001dc57620001dc6200012d565b604051601f8201601f19908116603f011681019083821181831017156200020757620002076200012d565b816040528281528860208487010111156200022157600080fd5b6200023483602083016020880162000143565b80965050505050506200024a6020840162000169565b90509250929050565b600181811c908216806200026857607f821691505b6020821081036200028957634e487b7160e01b600052602260045260246000fd5b50919050565b601f821115620002dd57600081815260208120601f850160051c81016020861015620002b85750805b601f850160051c820191505b81811015620002d957828155600101620002c4565b5050505b505050565b81516001600160401b03811115620002fe57620002fe6200012d565b62000316816200030f845462000253565b846200028f565b602080601f8311600181146200034e5760008415620003355750858301515b600019600386901b1c1916600185901b178555620002d9565b600085815260208120601f198616915b828110156200037f578886015182559484019460019091019084016200035e565b50858210156200039e5787850151600019600388901b60f8161c191681555b5050505050600190811b01905550565b6020815260008251806020840152620003cf81604085016020870162000143565b601f01601f19169190910160400192915050565b60805161079762000405600039600081816071015261013501526107976000f3fe608060405234801561001057600080fd5b50600436106100675760003560e01c8063cfae321711610050578063cfae3217146100d2578063ef690cc0146100e8578063fc0c546a146100fd57600080fd5b8063117803e31461006c578063a4136862146100bd575b600080fd5b6100937f000000000000000000000000000000000000000000000000000000000000000081565b60405173ffffffffffffffffffffffffffffffffffffffff90911681526020015b60405180910390f35b6100d06100cb36600461041d565b61011d565b005b6100da610232565b6040516100b4929190610550565b6100f0610360565b6040516100b49190610572565b6001546100939073ffffffffffffffffffffffffffffffffffffffff1681565b3373ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000161461018c576040517f5f61b23800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b805160208201207f3a2db9fe7908dcc36d81824d2338fc3f1aff49ac357dd8c4840527fba27a5b90016101eb576040517f8510740a00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60006101f7828261062e565b507fad181ee258ff92d26bf7ed2e6b571ef1cba3afc45f028b863b0f02adaffc2f06816040516102279190610572565b60405180910390a150565b606060008080546102429061058c565b80601f016020809104026020016040519081016040528092919081815260200182805461026e9061058c565b80156102bb5780601f10610290576101008083540402835291602001916102bb565b820191906000526020600020905b81548152906001019060200180831161029e57829003601f168201915b50506001546040517f70a0823100000000000000000000000000000000000000000000000000000000815233600482015294965073ffffffffffffffffffffffffffffffffffffffff16936370a08231935060240191506103199050565b602060405180830381865afa158015610336573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061035a9190610748565b90509091565b6000805461036d9061058c565b80601f01602080910402602001604051908101604052809291908181526020018280546103999061058c565b80156103e65780601f106103bb576101008083540402835291602001916103e6565b820191906000526020600020905b8154815290600101906020018083116103c957829003601f168201915b505050505081565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b60006020828403121561042f57600080fd5b813567ffffffffffffffff8082111561044757600080fd5b818401915084601f83011261045b57600080fd5b81358181111561046d5761046d6103ee565b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0908116603f011681019083821181831017156104b3576104b36103ee565b816040528281528760208487010111156104cc57600080fd5b826020860160208301376000928101602001929092525095945050505050565b6000815180845260005b81811015610512576020818501810151868301820152016104f6565b5060006020828601015260207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f83011685010191505092915050565b60408152600061056360408301856104ec565b90508260208301529392505050565b60208152600061058560208301846104ec565b9392505050565b600181811c908216806105a057607f821691505b6020821081036105d9577f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b50919050565b601f82111561062957600081815260208120601f850160051c810160208610156106065750805b601f850160051c820191505b8181101561062557828155600101610612565b5050505b505050565b815167ffffffffffffffff811115610648576106486103ee565b61065c81610656845461058c565b846105df565b602080601f8311600181146106af57600084156106795750858301515b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600386901b1c1916600185901b178555610625565b6000858152602081207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08616915b828110156106fc578886015182559484019460019091019084016106dd565b508582101561073857878501517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600388901b60f8161c191681555b5050505050600190811b01905550565b60006020828403121561075a57600080fd5b505191905056fea26469706673582212209640d61949a3120d03517f9655b0a2baddccbdfefaf0dd8e008fe343f7f033c964736f6c63430008130033", - "sourceMap": "178:1366:16:-:0;;;757:129;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;823:10;815:18;;839:5;:14;;-1:-1:-1;;;;;;839:14:16;-1:-1:-1;;;;;839:14:16;;;;;859:22;871:9;859:11;:22::i;:::-;757:129;;178:1366;;917:230;1483:5;;-1:-1:-1;;;;;1469:19:16;:10;-1:-1:-1;;;;;1469:19:16;;1465:66;;1505:19;;-1:-1:-1;;;1505:19:16;;;;;;;;;;;1465:66;990:27;;::::1;::::0;::::1;::::0;:44;;986:97:::1;;1051:25;;-1:-1:-1::0;;;1051:25:16::1;;;;;;;;;;;986:97;1089:8;:20;1100:9:::0;1089:8;:20:::1;:::i;:::-;;1120:22;1132:9;1120:22;;;;;;:::i;:::-;;;;;;;;917:230:::0;:::o;14:127:23:-;75:10;70:3;66:20;63:1;56:31;106:4;103:1;96:15;130:4;127:1;120:15;146:250;231:1;241:113;255:6;252:1;249:13;241:113;;;331:11;;;325:18;312:11;;;305:39;277:2;270:10;241:113;;;-1:-1:-1;;388:1:23;370:16;;363:27;146:250::o;401:185::-;488:13;;-1:-1:-1;;;;;530:31:23;;520:42;;510:70;;576:1;573;566:12;510:70;401:185;;;:::o;591:1014::-;696:6;704;757:2;745:9;736:7;732:23;728:32;725:52;;;773:1;770;763:12;725:52;800:16;;-1:-1:-1;;;;;865:14:23;;;862:34;;;892:1;889;882:12;862:34;930:6;919:9;915:22;905:32;;975:7;968:4;964:2;960:13;956:27;946:55;;997:1;994;987:12;946:55;1026:2;1020:9;1048:2;1044;1041:10;1038:36;;;1054:18;;:::i;:::-;1129:2;1123:9;1097:2;1183:13;;-1:-1:-1;;1179:22:23;;;1203:2;1175:31;1171:40;1159:53;;;1227:18;;;1247:22;;;1224:46;1221:72;;;1273:18;;:::i;:::-;1313:10;1309:2;1302:22;1348:2;1340:6;1333:18;1390:7;1383:4;1378:2;1374;1370:11;1366:22;1363:35;1360:55;;;1411:1;1408;1401:12;1360:55;1424:72;1493:2;1486:4;1478:6;1474:17;1467:4;1463:2;1459:13;1424:72;:::i;:::-;1515:6;1505:16;;;;;;;1540:59;1593:4;1582:9;1578:20;1540:59;:::i;:::-;1530:69;;591:1014;;;;;:::o;1610:380::-;1689:1;1685:12;;;;1732;;;1753:61;;1807:4;1799:6;1795:17;1785:27;;1753:61;1860:2;1852:6;1849:14;1829:18;1826:38;1823:161;;1906:10;1901:3;1897:20;1894:1;1887:31;1941:4;1938:1;1931:15;1969:4;1966:1;1959:15;1823:161;;1610:380;;;:::o;2121:545::-;2223:2;2218:3;2215:11;2212:448;;;2259:1;2284:5;2280:2;2273:17;2329:4;2325:2;2315:19;2399:2;2387:10;2383:19;2380:1;2376:27;2370:4;2366:38;2435:4;2423:10;2420:20;2417:47;;;-1:-1:-1;2458:4:23;2417:47;2513:2;2508:3;2504:12;2501:1;2497:20;2491:4;2487:31;2477:41;;2568:82;2586:2;2579:5;2576:13;2568:82;;;2631:17;;;2612:1;2601:13;2568:82;;;2572:3;;;2212:448;2121:545;;;:::o;2842:1352::-;2962:10;;-1:-1:-1;;;;;2984:30:23;;2981:56;;;3017:18;;:::i;:::-;3046:97;3136:6;3096:38;3128:4;3122:11;3096:38;:::i;:::-;3090:4;3046:97;:::i;:::-;3198:4;;3262:2;3251:14;;3279:1;3274:663;;;;3981:1;3998:6;3995:89;;;-1:-1:-1;4050:19:23;;;4044:26;3995:89;-1:-1:-1;;2799:1:23;2795:11;;;2791:24;2787:29;2777:40;2823:1;2819:11;;;2774:57;4097:81;;3244:944;;3274:663;2068:1;2061:14;;;2105:4;2092:18;;-1:-1:-1;;3310:20:23;;;3428:236;3442:7;3439:1;3436:14;3428:236;;;3531:19;;;3525:26;3510:42;;3623:27;;;;3591:1;3579:14;;;;3458:19;;3428:236;;;3432:3;3692:6;3683:7;3680:19;3677:201;;;3753:19;;;3747:26;-1:-1:-1;;3836:1:23;3832:14;;;3848:3;3828:24;3824:37;3820:42;3805:58;3790:74;;3677:201;-1:-1:-1;;;;;3924:1:23;3908:14;;;3904:22;3891:36;;-1:-1:-1;2842:1352:23:o;4199:396::-;4348:2;4337:9;4330:21;4311:4;4380:6;4374:13;4423:6;4418:2;4407:9;4403:18;4396:34;4439:79;4511:6;4506:2;4495:9;4491:18;4486:2;4478:6;4474:15;4439:79;:::i;:::-;4579:2;4558:15;-1:-1:-1;;4554:29:23;4539:45;;;;4586:2;4535:54;;4199:396;-1:-1:-1;;4199:396:23:o;:::-;178:1366:16;;;;;;;;;;;;;;;;;", - "linkReferences": {} - }, - "deployedBytecode": { - "object": "0x608060405234801561001057600080fd5b50600436106100675760003560e01c8063cfae321711610050578063cfae3217146100d2578063ef690cc0146100e8578063fc0c546a146100fd57600080fd5b8063117803e31461006c578063a4136862146100bd575b600080fd5b6100937f000000000000000000000000000000000000000000000000000000000000000081565b60405173ffffffffffffffffffffffffffffffffffffffff90911681526020015b60405180910390f35b6100d06100cb36600461041d565b61011d565b005b6100da610232565b6040516100b4929190610550565b6100f0610360565b6040516100b49190610572565b6001546100939073ffffffffffffffffffffffffffffffffffffffff1681565b3373ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000161461018c576040517f5f61b23800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b805160208201207f3a2db9fe7908dcc36d81824d2338fc3f1aff49ac357dd8c4840527fba27a5b90016101eb576040517f8510740a00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60006101f7828261062e565b507fad181ee258ff92d26bf7ed2e6b571ef1cba3afc45f028b863b0f02adaffc2f06816040516102279190610572565b60405180910390a150565b606060008080546102429061058c565b80601f016020809104026020016040519081016040528092919081815260200182805461026e9061058c565b80156102bb5780601f10610290576101008083540402835291602001916102bb565b820191906000526020600020905b81548152906001019060200180831161029e57829003601f168201915b50506001546040517f70a0823100000000000000000000000000000000000000000000000000000000815233600482015294965073ffffffffffffffffffffffffffffffffffffffff16936370a08231935060240191506103199050565b602060405180830381865afa158015610336573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061035a9190610748565b90509091565b6000805461036d9061058c565b80601f01602080910402602001604051908101604052809291908181526020018280546103999061058c565b80156103e65780601f106103bb576101008083540402835291602001916103e6565b820191906000526020600020905b8154815290600101906020018083116103c957829003601f168201915b505050505081565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b60006020828403121561042f57600080fd5b813567ffffffffffffffff8082111561044757600080fd5b818401915084601f83011261045b57600080fd5b81358181111561046d5761046d6103ee565b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0908116603f011681019083821181831017156104b3576104b36103ee565b816040528281528760208487010111156104cc57600080fd5b826020860160208301376000928101602001929092525095945050505050565b6000815180845260005b81811015610512576020818501810151868301820152016104f6565b5060006020828601015260207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f83011685010191505092915050565b60408152600061056360408301856104ec565b90508260208301529392505050565b60208152600061058560208301846104ec565b9392505050565b600181811c908216806105a057607f821691505b6020821081036105d9577f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b50919050565b601f82111561062957600081815260208120601f850160051c810160208610156106065750805b601f850160051c820191505b8181101561062557828155600101610612565b5050505b505050565b815167ffffffffffffffff811115610648576106486103ee565b61065c81610656845461058c565b846105df565b602080601f8311600181146106af57600084156106795750858301515b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600386901b1c1916600185901b178555610625565b6000858152602081207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08616915b828110156106fc578886015182559484019460019091019084016106dd565b508582101561073857878501517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600388901b60f8161c191681555b5050505050600190811b01905550565b60006020828403121561075a57600080fd5b505191905056fea26469706673582212209640d61949a3120d03517f9655b0a2baddccbdfefaf0dd8e008fe343f7f033c964736f6c63430008130033", - "sourceMap": "178:1366:16:-:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;454:30;;;;;;;;190:42:23;178:55;;;160:74;;148:2;133:18;454:30:16;;;;;;;;917:230;;;;;;:::i;:::-;;:::i;:::-;;1178:158;;;:::i;:::-;;;;;;;;:::i;516:22::-;;;:::i;:::-;;;;;;;:::i;570:19::-;;;;;;;;;917:230;1469:10;:19;1483:5;1469:19;;1465:66;;1505:19;;;;;;;;;;;;;;1465:66;990:27;;::::1;::::0;::::1;::::0;:44;;986:97:::1;;1051:25;;;;;;;;;;;;;;986:97;1089:8;:20;1100:9:::0;1089:8;:20:::1;:::i;:::-;;1120:22;1132:9;1120:22;;;;;;:::i;:::-;;;;;;;;917:230:::0;:::o;1178:158::-;1218:23;1243:16;1279:8;1267:20;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;1304:5:16;;:27;;;;;1320:10;1304:27;;;160:74:23;1267:20:16;;-1:-1:-1;1304:5:16;;;:15;;-1:-1:-1;133:18:23;;;-1:-1:-1;1304:27:16;;-1:-1:-1;14:226:23;1304:27:16;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;1293:38;;1178:158;;:::o;516:22::-;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::o;245:184:23:-;297:77;294:1;287:88;394:4;391:1;384:15;418:4;415:1;408:15;434:981;503:6;556:2;544:9;535:7;531:23;527:32;524:52;;;572:1;569;562:12;524:52;612:9;599:23;641:18;682:2;674:6;671:14;668:34;;;698:1;695;688:12;668:34;736:6;725:9;721:22;711:32;;781:7;774:4;770:2;766:13;762:27;752:55;;803:1;800;793:12;752:55;839:2;826:16;861:2;857;854:10;851:36;;;867:18;;:::i;:::-;1001:2;995:9;1063:4;1055:13;;906:66;1051:22;;;1075:2;1047:31;1043:40;1031:53;;;1099:18;;;1119:22;;;1096:46;1093:72;;;1145:18;;:::i;:::-;1185:10;1181:2;1174:22;1220:2;1212:6;1205:18;1260:7;1255:2;1250;1246;1242:11;1238:20;1235:33;1232:53;;;1281:1;1278;1271:12;1232:53;1337:2;1332;1328;1324:11;1319:2;1311:6;1307:15;1294:46;1382:1;1360:15;;;1377:2;1356:24;1349:35;;;;-1:-1:-1;1364:6:23;434:981;-1:-1:-1;;;;;434:981:23:o;1420:482::-;1462:3;1500:5;1494:12;1527:6;1522:3;1515:19;1552:1;1562:162;1576:6;1573:1;1570:13;1562:162;;;1638:4;1694:13;;;1690:22;;1684:29;1666:11;;;1662:20;;1655:59;1591:12;1562:162;;;1566:3;1769:1;1762:4;1753:6;1748:3;1744:16;1740:27;1733:38;1891:4;1821:66;1816:2;1808:6;1804:15;1800:88;1795:3;1791:98;1787:109;1780:116;;;1420:482;;;;:::o;1907:291::-;2084:2;2073:9;2066:21;2047:4;2104:45;2145:2;2134:9;2130:18;2122:6;2104:45;:::i;:::-;2096:53;;2185:6;2180:2;2169:9;2165:18;2158:34;1907:291;;;;;:::o;2203:220::-;2352:2;2341:9;2334:21;2315:4;2372:45;2413:2;2402:9;2398:18;2390:6;2372:45;:::i;:::-;2364:53;2203:220;-1:-1:-1;;;2203:220:23:o;2675:437::-;2754:1;2750:12;;;;2797;;;2818:61;;2872:4;2864:6;2860:17;2850:27;;2818:61;2925:2;2917:6;2914:14;2894:18;2891:38;2888:218;;2962:77;2959:1;2952:88;3063:4;3060:1;3053:15;3091:4;3088:1;3081:15;2888:218;;2675:437;;;:::o;3243:545::-;3345:2;3340:3;3337:11;3334:448;;;3381:1;3406:5;3402:2;3395:17;3451:4;3447:2;3437:19;3521:2;3509:10;3505:19;3502:1;3498:27;3492:4;3488:38;3557:4;3545:10;3542:20;3539:47;;;-1:-1:-1;3580:4:23;3539:47;3635:2;3630:3;3626:12;3623:1;3619:20;3613:4;3609:31;3599:41;;3690:82;3708:2;3701:5;3698:13;3690:82;;;3753:17;;;3734:1;3723:13;3690:82;;;3694:3;;;3334:448;3243:545;;;:::o;4024:1471::-;4150:3;4144:10;4177:18;4169:6;4166:30;4163:56;;;4199:18;;:::i;:::-;4228:97;4318:6;4278:38;4310:4;4304:11;4278:38;:::i;:::-;4272:4;4228:97;:::i;:::-;4380:4;;4444:2;4433:14;;4461:1;4456:782;;;;5282:1;5299:6;5296:89;;;-1:-1:-1;5351:19:23;;;5345:26;5296:89;3930:66;3921:1;3917:11;;;3913:84;3909:89;3899:100;4005:1;4001:11;;;3896:117;5398:81;;4426:1063;;4456:782;3190:1;3183:14;;;3227:4;3214:18;;4504:66;4492:79;;;4669:236;4683:7;4680:1;4677:14;4669:236;;;4772:19;;;4766:26;4751:42;;4864:27;;;;4832:1;4820:14;;;;4699:19;;4669:236;;;4673:3;4933:6;4924:7;4921:19;4918:261;;;4994:19;;;4988:26;5095:66;5077:1;5073:14;;;5089:3;5069:24;5065:97;5061:102;5046:118;5031:134;;4918:261;-1:-1:-1;;;;;5225:1:23;5209:14;;;5205:22;5192:36;;-1:-1:-1;4024:1471:23:o;5500:184::-;5570:6;5623:2;5611:9;5602:7;5598:23;5594:32;5591:52;;;5639:1;5636;5629:12;5591:52;-1:-1:-1;5662:16:23;;5500:184;-1:-1:-1;5500:184:23:o", - "linkReferences": {}, - "immutableReferences": { - "29704": [ - { - "start": 113, - "length": 32 - }, - { - "start": 309, - "length": 32 - } - ] - } - }, - "methodIdentifiers": { - "OWNER()": "117803e3", - "greet()": "cfae3217", - "greeting()": "ef690cc0", - "setGreeting(string)": "a4136862", - "token()": "fc0c546a" - }, - "rawMetadata": "{\"compiler\":{\"version\":\"0.8.19+commit.7dd6d404\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"inputs\":[{\"internalType\":\"string\",\"name\":\"_greeting\",\"type\":\"string\"},{\"internalType\":\"contract IERC20\",\"name\":\"_token\",\"type\":\"address\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"inputs\":[],\"name\":\"Greeter_InvalidGreeting\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"Greeter_OnlyOwner\",\"type\":\"error\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"string\",\"name\":\"_greeting\",\"type\":\"string\"}],\"name\":\"GreetingSet\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"OWNER\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"greet\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"_greeting\",\"type\":\"string\"},{\"internalType\":\"uint256\",\"name\":\"_balance\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"greeting\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"string\",\"name\":\"_greeting\",\"type\":\"string\"}],\"name\":\"setGreeting\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"token\",\"outputs\":[{\"internalType\":\"contract IERC20\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"}],\"devdoc\":{\"errors\":{\"Greeter_InvalidGreeting()\":[{\"details\":\"Empty string is an invalid greeting\"}]},\"events\":{\"GreetingSet(string)\":{\"params\":{\"_greeting\":\"The new greeting\"}}},\"kind\":\"dev\",\"methods\":{\"constructor\":{\"params\":{\"_greeting\":\"Initial greeting\",\"_token\":\"Initial token\"}},\"greet()\":{\"returns\":{\"_balance\":\" Current token balance of the caller\",\"_greeting\":\"The greeting\"}},\"setGreeting(string)\":{\"details\":\"Only callable by the owner\",\"params\":{\"_newGreeting\":\"The new greeting to be set\"}}},\"stateVariables\":{\"OWNER\":{\"details\":\"The owner will always be the deployer of the contract\",\"return\":\"The owner of the contract\",\"returns\":{\"_0\":\"The owner of the contract\"}},\"_EMPTY_STRING\":{\"details\":\"result of doing keccak256(bytes(''))\"},\"greeting\":{\"return\":\"The greeting\",\"returns\":{\"_0\":\"The greeting\"}},\"token\":{\"return\":\"The address of the token\",\"returns\":{\"_0\":\"The address of the token\"}}},\"version\":1},\"userdoc\":{\"errors\":{\"Greeter_InvalidGreeting()\":[{\"notice\":\"Throws if the greeting set is invalid\"}],\"Greeter_OnlyOwner()\":[{\"notice\":\"Throws if the function was called by someone else than the owner\"}]},\"events\":{\"GreetingSet(string)\":{\"notice\":\"Greeting has changed\"}},\"kind\":\"user\",\"methods\":{\"OWNER()\":{\"notice\":\"Returns the owner of the contract\"},\"constructor\":{\"notice\":\"Defines the owner to the msg.sender and sets the initial greeting\"},\"greet()\":{\"notice\":\"Greets the caller\"},\"greeting()\":{\"notice\":\"Returns the previously set greeting\"},\"setGreeting(string)\":{\"notice\":\"Sets a new greeting\"},\"token()\":{\"notice\":\"Returns the token used to greet callers\"}},\"version\":1}},\"settings\":{\"compilationTarget\":{\"solidity/contracts/Greeter.sol\":\"Greeter\"},\"evmVersion\":\"paris\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"ipfs\"},\"optimizer\":{\"enabled\":true,\"runs\":10000},\"remappings\":[\":contracts/=solidity/contracts/\",\":ds-test/=node_modules/ds-test/src/\",\":forge-std/=node_modules/forge-std/src/\",\":interfaces/=solidity/interfaces/\",\":isolmate/=node_modules/isolmate/src/\",\":prb/test/=node_modules/prb/test/src/\",\":test/=solidity/test/\"]},\"sources\":{\"node_modules/isolmate/src/interfaces/tokens/IERC20.sol\":{\"keccak256\":\"0xea23a986a9efba47639b24eae7dbfd6936be01e5fc3530202fe1f4b8bb4131b8\",\"license\":\"AGPL-3.0-only\",\"urls\":[\"bzz-raw://37e08545e18de038023d32de846032533af9513c0b7b84a6c5c371941b6bd5f7\",\"dweb:/ipfs/QmRYNSXwiJ9wE36ogE5J77g6pbGK7T7qqqAF99DZq3eW5B\"]},\"solidity/contracts/Greeter.sol\":{\"keccak256\":\"0x380ab9fd40c73e52202e49bc8ca254799aa135a5f7214c809f8a0cb65e8cc464\",\"license\":\"UNLICENSED\",\"urls\":[\"bzz-raw://1269c4308eb17da88bf144f70c38f81948a63176573a9274002d6b09a2da4605\",\"dweb:/ipfs/QmRjVeDWNNvqhXWnePLbMcskw66KogmAP2iGYLmq3SxcAQ\"]},\"solidity/interfaces/IGreeter.sol\":{\"keccak256\":\"0x747827aecbe821fe3ed4b108eefa46393ee1277489d5273ef0496d51200af6c4\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://d8380c91f28de084d723f29f5e01bb0ccf835e536fc7dd9a84763fc6a4351cfa\",\"dweb:/ipfs/QmePrqnZzC6L8dYqGPgNwwdG4AnwgFGqWQU4aBmMRMv2ez\"]}},\"version\":1}", - "metadata": { - "compiler": { - "version": "0.8.19+commit.7dd6d404" - }, - "language": "Solidity", - "output": { - "abi": [ - { - "inputs": [ - { - "internalType": "string", - "name": "_greeting", - "type": "string" - }, - { - "internalType": "contract IERC20", - "name": "_token", - "type": "address" - } - ], - "stateMutability": "nonpayable", - "type": "constructor" - }, - { - "inputs": [], - "type": "error", - "name": "Greeter_InvalidGreeting" - }, - { - "inputs": [], - "type": "error", - "name": "Greeter_OnlyOwner" - }, - { - "inputs": [ - { - "internalType": "string", - "name": "_greeting", - "type": "string", - "indexed": false - } - ], - "type": "event", - "name": "GreetingSet", - "anonymous": false - }, - { - "inputs": [], - "stateMutability": "view", - "type": "function", - "name": "OWNER", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ] - }, - { - "inputs": [], - "stateMutability": "view", - "type": "function", - "name": "greet", - "outputs": [ - { - "internalType": "string", - "name": "_greeting", - "type": "string" - }, - { - "internalType": "uint256", - "name": "_balance", - "type": "uint256" - } - ] - }, - { - "inputs": [], - "stateMutability": "view", - "type": "function", - "name": "greeting", - "outputs": [ - { - "internalType": "string", - "name": "", - "type": "string" - } - ] - }, - { - "inputs": [ - { - "internalType": "string", - "name": "_greeting", - "type": "string" - } - ], - "stateMutability": "nonpayable", - "type": "function", - "name": "setGreeting" - }, - { - "inputs": [], - "stateMutability": "view", - "type": "function", - "name": "token", - "outputs": [ - { - "internalType": "contract IERC20", - "name": "", - "type": "address" - } - ] - } - ], - "devdoc": { - "kind": "dev", - "methods": { - "constructor": { - "params": { - "_greeting": "Initial greeting", - "_token": "Initial token" - } - }, - "greet()": { - "returns": { - "_balance": " Current token balance of the caller", - "_greeting": "The greeting" - } - }, - "setGreeting(string)": { - "details": "Only callable by the owner", - "params": { - "_newGreeting": "The new greeting to be set" - } - } - }, - "version": 1 - }, - "userdoc": { - "kind": "user", - "methods": { - "OWNER()": { - "notice": "Returns the owner of the contract" - }, - "constructor": { - "notice": "Defines the owner to the msg.sender and sets the initial greeting" - }, - "greet()": { - "notice": "Greets the caller" - }, - "greeting()": { - "notice": "Returns the previously set greeting" - }, - "setGreeting(string)": { - "notice": "Sets a new greeting" - }, - "token()": { - "notice": "Returns the token used to greet callers" - } - }, - "version": 1 - } - }, - "settings": { - "remappings": [ - ":contracts/=solidity/contracts/", - ":ds-test/=node_modules/ds-test/src/", - ":forge-std/=node_modules/forge-std/src/", - ":interfaces/=solidity/interfaces/", - ":isolmate/=node_modules/isolmate/src/", - ":prb/test/=node_modules/prb/test/src/", - ":test/=solidity/test/" - ], - "optimizer": { - "enabled": true, - "runs": 10000 - }, - "metadata": { - "bytecodeHash": "ipfs" - }, - "compilationTarget": { - "solidity/contracts/Greeter.sol": "Greeter" - }, - "libraries": {} - }, - "sources": { - "node_modules/isolmate/src/interfaces/tokens/IERC20.sol": { - "keccak256": "0xea23a986a9efba47639b24eae7dbfd6936be01e5fc3530202fe1f4b8bb4131b8", - "urls": [ - "bzz-raw://37e08545e18de038023d32de846032533af9513c0b7b84a6c5c371941b6bd5f7", - "dweb:/ipfs/QmRYNSXwiJ9wE36ogE5J77g6pbGK7T7qqqAF99DZq3eW5B" - ], - "license": "AGPL-3.0-only" - }, - "solidity/contracts/Greeter.sol": { - "keccak256": "0x380ab9fd40c73e52202e49bc8ca254799aa135a5f7214c809f8a0cb65e8cc464", - "urls": [ - "bzz-raw://1269c4308eb17da88bf144f70c38f81948a63176573a9274002d6b09a2da4605", - "dweb:/ipfs/QmRjVeDWNNvqhXWnePLbMcskw66KogmAP2iGYLmq3SxcAQ" - ], - "license": "UNLICENSED" - }, - "solidity/interfaces/IGreeter.sol": { - "keccak256": "0x747827aecbe821fe3ed4b108eefa46393ee1277489d5273ef0496d51200af6c4", - "urls": [ - "bzz-raw://d8380c91f28de084d723f29f5e01bb0ccf835e536fc7dd9a84763fc6a4351cfa", - "dweb:/ipfs/QmePrqnZzC6L8dYqGPgNwwdG4AnwgFGqWQU4aBmMRMv2ez" - ], - "license": "MIT" - } - }, - "version": 1 - }, - "ast": { - "absolutePath": "solidity/contracts/Greeter.sol", - "id": 29801, - "exportedSymbols": { - "Greeter": [ - 29800 - ], - "IERC20": [ - 24217 - ], - "IGreeter": [ - 29850 - ] - }, - "nodeType": "SourceUnit", - "src": "39:1506:16", - "nodes": [ - { - "id": 29691, - "nodeType": "PragmaDirective", - "src": "39:24:16", - "nodes": [], - "literals": [ - "solidity", - "=", - "0.8", - ".19" - ] - }, - { - "id": 29693, - "nodeType": "ImportDirective", - "src": "65:61:16", - "nodes": [], - "absolutePath": "node_modules/isolmate/src/interfaces/tokens/IERC20.sol", - "file": "isolmate/interfaces/tokens/IERC20.sol", - "nameLocation": "-1:-1:-1", - "scope": 29801, - "sourceUnit": 24218, - "symbolAliases": [ - { - "foreign": { - "id": 29692, - "name": "IERC20", - "nodeType": "Identifier", - "overloadedDeclarations": [], - "referencedDeclaration": 24217, - "src": "73:6:16", - "typeDescriptions": {} - }, - "nameLocation": "-1:-1:-1" - } - ], - "unitAlias": "" - }, - { - "id": 29695, - "nodeType": "ImportDirective", - "src": "127:49:16", - "nodes": [], - "absolutePath": "solidity/interfaces/IGreeter.sol", - "file": "interfaces/IGreeter.sol", - "nameLocation": "-1:-1:-1", - "scope": 29801, - "sourceUnit": 29851, - "symbolAliases": [ - { - "foreign": { - "id": 29694, - "name": "IGreeter", - "nodeType": "Identifier", - "overloadedDeclarations": [], - "referencedDeclaration": 29850, - "src": "135:8:16", - "typeDescriptions": {} - }, - "nameLocation": "-1:-1:-1" - } - ], - "unitAlias": "" - }, - { - "id": 29800, - "nodeType": "ContractDefinition", - "src": "178:1366:16", - "nodes": [ - { - "id": 29701, - "nodeType": "VariableDeclaration", - "src": "314:108:16", - "nodes": [], - "constant": true, - "documentation": { - "id": 29698, - "nodeType": "StructuredDocumentation", - "src": "211:100:16", - "text": " @notice Empty string for revert checks\n @dev result of doing keccak256(bytes(''))" - }, - "mutability": "constant", - "name": "_EMPTY_STRING", - "nameLocation": "340:13:16", - "scope": 29800, - "stateVariable": true, - "storageLocation": "default", - "typeDescriptions": { - "typeIdentifier": "t_bytes32", - "typeString": "bytes32" - }, - "typeName": { - "id": 29699, - "name": "bytes32", - "nodeType": "ElementaryTypeName", - "src": "314:7:16", - "typeDescriptions": { - "typeIdentifier": "t_bytes32", - "typeString": "bytes32" - } - }, - "value": { - "hexValue": "307863356432343630313836663732333363393237653764623264636337303363306535303062363533636138323237336237626661643830343564383561343730", - "id": 29700, - "isConstant": false, - "isLValue": false, - "isPure": true, - "kind": "number", - "lValueRequested": false, - "nodeType": "Literal", - "src": "356:66:16", - "typeDescriptions": { - "typeIdentifier": "t_rational_89477152217924674838424037953991966239322087453347756267410168184682657981552_by_1", - "typeString": "int_const 8947...(69 digits omitted)...1552" - }, - "value": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470" - }, - "visibility": "internal" - }, - { - "id": 29704, - "nodeType": "VariableDeclaration", - "src": "454:30:16", - "nodes": [], - "baseFunctions": [ - 29822 - ], - "constant": false, - "documentation": { - "id": 29702, - "nodeType": "StructuredDocumentation", - "src": "427:24:16", - "text": "@inheritdoc IGreeter" - }, - "functionSelector": "117803e3", - "mutability": "immutable", - "name": "OWNER", - "nameLocation": "479:5:16", - "scope": 29800, - "stateVariable": true, - "storageLocation": "default", - "typeDescriptions": { - "typeIdentifier": "t_address", - "typeString": "address" - }, - "typeName": { - "id": 29703, - "name": "address", - "nodeType": "ElementaryTypeName", - "src": "454:7:16", - "stateMutability": "nonpayable", - "typeDescriptions": { - "typeIdentifier": "t_address", - "typeString": "address" - } - }, - "visibility": "public" - }, - { - "id": 29707, - "nodeType": "VariableDeclaration", - "src": "516:22:16", - "nodes": [], - "baseFunctions": [ - 29828 - ], - "constant": false, - "documentation": { - "id": 29705, - "nodeType": "StructuredDocumentation", - "src": "489:24:16", - "text": "@inheritdoc IGreeter" - }, - "functionSelector": "ef690cc0", - "mutability": "mutable", - "name": "greeting", - "nameLocation": "530:8:16", - "scope": 29800, - "stateVariable": true, - "storageLocation": "default", - "typeDescriptions": { - "typeIdentifier": "t_string_storage", - "typeString": "string" - }, - "typeName": { - "id": 29706, - "name": "string", - "nodeType": "ElementaryTypeName", - "src": "516:6:16", - "typeDescriptions": { - "typeIdentifier": "t_string_storage_ptr", - "typeString": "string" - } - }, - "visibility": "public" - }, - { - "id": 29711, - "nodeType": "VariableDeclaration", - "src": "570:19:16", - "nodes": [], - "baseFunctions": [ - 29835 - ], - "constant": false, - "documentation": { - "id": 29708, - "nodeType": "StructuredDocumentation", - "src": "543:24:16", - "text": "@inheritdoc IGreeter" - }, - "functionSelector": "fc0c546a", - "mutability": "mutable", - "name": "token", - "nameLocation": "584:5:16", - "scope": 29800, - "stateVariable": true, - "storageLocation": "default", - "typeDescriptions": { - "typeIdentifier": "t_contract$_IERC20_$24217", - "typeString": "contract IERC20" - }, - "typeName": { - "id": 29710, - "nodeType": "UserDefinedTypeName", - "pathNode": { - "id": 29709, - "name": "IERC20", - "nameLocations": [ - "570:6:16" - ], - "nodeType": "IdentifierPath", - "referencedDeclaration": 24217, - "src": "570:6:16" - }, - "referencedDeclaration": 24217, - "src": "570:6:16", - "typeDescriptions": { - "typeIdentifier": "t_contract$_IERC20_$24217", - "typeString": "contract IERC20" - } - }, - "visibility": "public" - }, - { - "id": 29734, - "nodeType": "FunctionDefinition", - "src": "757:129:16", - "nodes": [], - "body": { - "id": 29733, - "nodeType": "Block", - "src": "809:77:16", - "nodes": [], - "statements": [ - { - "expression": { - "id": 29723, - "isConstant": false, - "isLValue": false, - "isPure": false, - "lValueRequested": false, - "leftHandSide": { - "id": 29720, - "name": "OWNER", - "nodeType": "Identifier", - "overloadedDeclarations": [], - "referencedDeclaration": 29704, - "src": "815:5:16", - "typeDescriptions": { - "typeIdentifier": "t_address", - "typeString": "address" - } - }, - "nodeType": "Assignment", - "operator": "=", - "rightHandSide": { - "expression": { - "id": 29721, - "name": "msg", - "nodeType": "Identifier", - "overloadedDeclarations": [], - "referencedDeclaration": -15, - "src": "823:3:16", - "typeDescriptions": { - "typeIdentifier": "t_magic_message", - "typeString": "msg" - } - }, - "id": 29722, - "isConstant": false, - "isLValue": false, - "isPure": false, - "lValueRequested": false, - "memberLocation": "827:6:16", - "memberName": "sender", - "nodeType": "MemberAccess", - "src": "823:10:16", - "typeDescriptions": { - "typeIdentifier": "t_address", - "typeString": "address" - } - }, - "src": "815:18:16", - "typeDescriptions": { - "typeIdentifier": "t_address", - "typeString": "address" - } - }, - "id": 29724, - "nodeType": "ExpressionStatement", - "src": "815:18:16" - }, - { - "expression": { - "id": 29727, - "isConstant": false, - "isLValue": false, - "isPure": false, - "lValueRequested": false, - "leftHandSide": { - "id": 29725, - "name": "token", - "nodeType": "Identifier", - "overloadedDeclarations": [], - "referencedDeclaration": 29711, - "src": "839:5:16", - "typeDescriptions": { - "typeIdentifier": "t_contract$_IERC20_$24217", - "typeString": "contract IERC20" - } - }, - "nodeType": "Assignment", - "operator": "=", - "rightHandSide": { - "id": 29726, - "name": "_token", - "nodeType": "Identifier", - "overloadedDeclarations": [], - "referencedDeclaration": 29717, - "src": "847:6:16", - "typeDescriptions": { - "typeIdentifier": "t_contract$_IERC20_$24217", - "typeString": "contract IERC20" - } - }, - "src": "839:14:16", - "typeDescriptions": { - "typeIdentifier": "t_contract$_IERC20_$24217", - "typeString": "contract IERC20" - } - }, - "id": 29728, - "nodeType": "ExpressionStatement", - "src": "839:14:16" - }, - { - "expression": { - "arguments": [ - { - "id": 29730, - "name": "_greeting", - "nodeType": "Identifier", - "overloadedDeclarations": [], - "referencedDeclaration": 29714, - "src": "871:9:16", - "typeDescriptions": { - "typeIdentifier": "t_string_memory_ptr", - "typeString": "string memory" - } - } - ], - "expression": { - "argumentTypes": [ - { - "typeIdentifier": "t_string_memory_ptr", - "typeString": "string memory" - } - ], - "id": 29729, - "name": "setGreeting", - "nodeType": "Identifier", - "overloadedDeclarations": [], - "referencedDeclaration": 29764, - "src": "859:11:16", - "typeDescriptions": { - "typeIdentifier": "t_function_internal_nonpayable$_t_string_memory_ptr_$returns$__$", - "typeString": "function (string memory)" - } - }, - "id": 29731, - "isConstant": false, - "isLValue": false, - "isPure": false, - "kind": "functionCall", - "lValueRequested": false, - "nameLocations": [], - "names": [], - "nodeType": "FunctionCall", - "src": "859:22:16", - "tryCall": false, - "typeDescriptions": { - "typeIdentifier": "t_tuple$__$", - "typeString": "tuple()" - } - }, - "id": 29732, - "nodeType": "ExpressionStatement", - "src": "859:22:16" - } - ] - }, - "documentation": { - "id": 29712, - "nodeType": "StructuredDocumentation", - "src": "594:160:16", - "text": " @notice Defines the owner to the msg.sender and sets the initial greeting\n @param _greeting Initial greeting\n @param _token Initial token" - }, - "implemented": true, - "kind": "constructor", - "modifiers": [], - "name": "", - "nameLocation": "-1:-1:-1", - "parameters": { - "id": 29718, - "nodeType": "ParameterList", - "parameters": [ - { - "constant": false, - "id": 29714, - "mutability": "mutable", - "name": "_greeting", - "nameLocation": "783:9:16", - "nodeType": "VariableDeclaration", - "scope": 29734, - "src": "769:23:16", - "stateVariable": false, - "storageLocation": "memory", - "typeDescriptions": { - "typeIdentifier": "t_string_memory_ptr", - "typeString": "string" - }, - "typeName": { - "id": 29713, - "name": "string", - "nodeType": "ElementaryTypeName", - "src": "769:6:16", - "typeDescriptions": { - "typeIdentifier": "t_string_storage_ptr", - "typeString": "string" - } - }, - "visibility": "internal" - }, - { - "constant": false, - "id": 29717, - "mutability": "mutable", - "name": "_token", - "nameLocation": "801:6:16", - "nodeType": "VariableDeclaration", - "scope": 29734, - "src": "794:13:16", - "stateVariable": false, - "storageLocation": "default", - "typeDescriptions": { - "typeIdentifier": "t_contract$_IERC20_$24217", - "typeString": "contract IERC20" - }, - "typeName": { - "id": 29716, - "nodeType": "UserDefinedTypeName", - "pathNode": { - "id": 29715, - "name": "IERC20", - "nameLocations": [ - "794:6:16" - ], - "nodeType": "IdentifierPath", - "referencedDeclaration": 24217, - "src": "794:6:16" - }, - "referencedDeclaration": 24217, - "src": "794:6:16", - "typeDescriptions": { - "typeIdentifier": "t_contract$_IERC20_$24217", - "typeString": "contract IERC20" - } - }, - "visibility": "internal" - } - ], - "src": "768:40:16" - }, - "returnParameters": { - "id": 29719, - "nodeType": "ParameterList", - "parameters": [], - "src": "809:0:16" - }, - "scope": 29800, - "stateMutability": "nonpayable", - "virtual": false, - "visibility": "public" - }, - { - "id": 29764, - "nodeType": "FunctionDefinition", - "src": "917:230:16", - "nodes": [], - "body": { - "id": 29763, - "nodeType": "Block", - "src": "980:167:16", - "nodes": [], - "statements": [ - { - "condition": { - "commonType": { - "typeIdentifier": "t_bytes32", - "typeString": "bytes32" - }, - "id": 29749, - "isConstant": false, - "isLValue": false, - "isPure": false, - "lValueRequested": false, - "leftExpression": { - "arguments": [ - { - "arguments": [ - { - "id": 29745, - "name": "_greeting", - "nodeType": "Identifier", - "overloadedDeclarations": [], - "referencedDeclaration": 29737, - "src": "1006:9:16", - "typeDescriptions": { - "typeIdentifier": "t_string_memory_ptr", - "typeString": "string memory" - } - } - ], - "expression": { - "argumentTypes": [ - { - "typeIdentifier": "t_string_memory_ptr", - "typeString": "string memory" - } - ], - "id": 29744, - "isConstant": false, - "isLValue": false, - "isPure": true, - "lValueRequested": false, - "nodeType": "ElementaryTypeNameExpression", - "src": "1000:5:16", - "typeDescriptions": { - "typeIdentifier": "t_type$_t_bytes_storage_ptr_$", - "typeString": "type(bytes storage pointer)" - }, - "typeName": { - "id": 29743, - "name": "bytes", - "nodeType": "ElementaryTypeName", - "src": "1000:5:16", - "typeDescriptions": {} - } - }, - "id": 29746, - "isConstant": false, - "isLValue": false, - "isPure": false, - "kind": "typeConversion", - "lValueRequested": false, - "nameLocations": [], - "names": [], - "nodeType": "FunctionCall", - "src": "1000:16:16", - "tryCall": false, - "typeDescriptions": { - "typeIdentifier": "t_bytes_memory_ptr", - "typeString": "bytes memory" - } - } - ], - "expression": { - "argumentTypes": [ - { - "typeIdentifier": "t_bytes_memory_ptr", - "typeString": "bytes memory" - } - ], - "id": 29742, - "name": "keccak256", - "nodeType": "Identifier", - "overloadedDeclarations": [], - "referencedDeclaration": -8, - "src": "990:9:16", - "typeDescriptions": { - "typeIdentifier": "t_function_keccak256_pure$_t_bytes_memory_ptr_$returns$_t_bytes32_$", - "typeString": "function (bytes memory) pure returns (bytes32)" - } - }, - "id": 29747, - "isConstant": false, - "isLValue": false, - "isPure": false, - "kind": "functionCall", - "lValueRequested": false, - "nameLocations": [], - "names": [], - "nodeType": "FunctionCall", - "src": "990:27:16", - "tryCall": false, - "typeDescriptions": { - "typeIdentifier": "t_bytes32", - "typeString": "bytes32" - } - }, - "nodeType": "BinaryOperation", - "operator": "==", - "rightExpression": { - "id": 29748, - "name": "_EMPTY_STRING", - "nodeType": "Identifier", - "overloadedDeclarations": [], - "referencedDeclaration": 29701, - "src": "1021:13:16", - "typeDescriptions": { - "typeIdentifier": "t_bytes32", - "typeString": "bytes32" - } - }, - "src": "990:44:16", - "typeDescriptions": { - "typeIdentifier": "t_bool", - "typeString": "bool" - } - }, - "id": 29754, - "nodeType": "IfStatement", - "src": "986:97:16", - "trueBody": { - "id": 29753, - "nodeType": "Block", - "src": "1036:47:16", - "statements": [ - { - "errorCall": { - "arguments": [], - "expression": { - "argumentTypes": [], - "id": 29750, - "name": "Greeter_InvalidGreeting", - "nodeType": "Identifier", - "overloadedDeclarations": [], - "referencedDeclaration": 29816, - "src": "1051:23:16", - "typeDescriptions": { - "typeIdentifier": "t_function_error_pure$__$returns$__$", - "typeString": "function () pure" - } - }, - "id": 29751, - "isConstant": false, - "isLValue": false, - "isPure": false, - "kind": "functionCall", - "lValueRequested": false, - "nameLocations": [], - "names": [], - "nodeType": "FunctionCall", - "src": "1051:25:16", - "tryCall": false, - "typeDescriptions": { - "typeIdentifier": "t_tuple$__$", - "typeString": "tuple()" - } - }, - "id": 29752, - "nodeType": "RevertStatement", - "src": "1044:32:16" - } - ] - } - }, - { - "expression": { - "id": 29757, - "isConstant": false, - "isLValue": false, - "isPure": false, - "lValueRequested": false, - "leftHandSide": { - "id": 29755, - "name": "greeting", - "nodeType": "Identifier", - "overloadedDeclarations": [], - "referencedDeclaration": 29707, - "src": "1089:8:16", - "typeDescriptions": { - "typeIdentifier": "t_string_storage", - "typeString": "string storage ref" - } - }, - "nodeType": "Assignment", - "operator": "=", - "rightHandSide": { - "id": 29756, - "name": "_greeting", - "nodeType": "Identifier", - "overloadedDeclarations": [], - "referencedDeclaration": 29737, - "src": "1100:9:16", - "typeDescriptions": { - "typeIdentifier": "t_string_memory_ptr", - "typeString": "string memory" - } - }, - "src": "1089:20:16", - "typeDescriptions": { - "typeIdentifier": "t_string_storage", - "typeString": "string storage ref" - } - }, - "id": 29758, - "nodeType": "ExpressionStatement", - "src": "1089:20:16" - }, - { - "eventCall": { - "arguments": [ - { - "id": 29760, - "name": "_greeting", - "nodeType": "Identifier", - "overloadedDeclarations": [], - "referencedDeclaration": 29737, - "src": "1132:9:16", - "typeDescriptions": { - "typeIdentifier": "t_string_memory_ptr", - "typeString": "string memory" - } - } - ], - "expression": { - "argumentTypes": [ - { - "typeIdentifier": "t_string_memory_ptr", - "typeString": "string memory" - } - ], - "id": 29759, - "name": "GreetingSet", - "nodeType": "Identifier", - "overloadedDeclarations": [], - "referencedDeclaration": 29810, - "src": "1120:11:16", - "typeDescriptions": { - "typeIdentifier": "t_function_event_nonpayable$_t_string_memory_ptr_$returns$__$", - "typeString": "function (string memory)" - } - }, - "id": 29761, - "isConstant": false, - "isLValue": false, - "isPure": false, - "kind": "functionCall", - "lValueRequested": false, - "nameLocations": [], - "names": [], - "nodeType": "FunctionCall", - "src": "1120:22:16", - "tryCall": false, - "typeDescriptions": { - "typeIdentifier": "t_tuple$__$", - "typeString": "tuple()" - } - }, - "id": 29762, - "nodeType": "EmitStatement", - "src": "1115:27:16" - } - ] - }, - "baseFunctions": [ - 29849 - ], - "documentation": { - "id": 29735, - "nodeType": "StructuredDocumentation", - "src": "890:24:16", - "text": "@inheritdoc IGreeter" - }, - "functionSelector": "a4136862", - "implemented": true, - "kind": "function", - "modifiers": [ - { - "id": 29740, - "kind": "modifierInvocation", - "modifierName": { - "id": 29739, - "name": "onlyOwner", - "nameLocations": [ - "970:9:16" - ], - "nodeType": "IdentifierPath", - "referencedDeclaration": 29799, - "src": "970:9:16" - }, - "nodeType": "ModifierInvocation", - "src": "970:9:16" - } - ], - "name": "setGreeting", - "nameLocation": "926:11:16", - "parameters": { - "id": 29738, - "nodeType": "ParameterList", - "parameters": [ - { - "constant": false, - "id": 29737, - "mutability": "mutable", - "name": "_greeting", - "nameLocation": "952:9:16", - "nodeType": "VariableDeclaration", - "scope": 29764, - "src": "938:23:16", - "stateVariable": false, - "storageLocation": "memory", - "typeDescriptions": { - "typeIdentifier": "t_string_memory_ptr", - "typeString": "string" - }, - "typeName": { - "id": 29736, - "name": "string", - "nodeType": "ElementaryTypeName", - "src": "938:6:16", - "typeDescriptions": { - "typeIdentifier": "t_string_storage_ptr", - "typeString": "string" - } - }, - "visibility": "internal" - } - ], - "src": "937:25:16" - }, - "returnParameters": { - "id": 29741, - "nodeType": "ParameterList", - "parameters": [], - "src": "980:0:16" - }, - "scope": 29800, - "stateMutability": "nonpayable", - "virtual": false, - "visibility": "public" - }, - { - "id": 29785, - "nodeType": "FunctionDefinition", - "src": "1178:158:16", - "nodes": [], - "body": { - "id": 29784, - "nodeType": "Block", - "src": "1261:75:16", - "nodes": [], - "statements": [ - { - "expression": { - "id": 29774, - "isConstant": false, - "isLValue": false, - "isPure": false, - "lValueRequested": false, - "leftHandSide": { - "id": 29772, - "name": "_greeting", - "nodeType": "Identifier", - "overloadedDeclarations": [], - "referencedDeclaration": 29768, - "src": "1267:9:16", - "typeDescriptions": { - "typeIdentifier": "t_string_memory_ptr", - "typeString": "string memory" - } - }, - "nodeType": "Assignment", - "operator": "=", - "rightHandSide": { - "id": 29773, - "name": "greeting", - "nodeType": "Identifier", - "overloadedDeclarations": [], - "referencedDeclaration": 29707, - "src": "1279:8:16", - "typeDescriptions": { - "typeIdentifier": "t_string_storage", - "typeString": "string storage ref" - } - }, - "src": "1267:20:16", - "typeDescriptions": { - "typeIdentifier": "t_string_memory_ptr", - "typeString": "string memory" - } - }, - "id": 29775, - "nodeType": "ExpressionStatement", - "src": "1267:20:16" - }, - { - "expression": { - "id": 29782, - "isConstant": false, - "isLValue": false, - "isPure": false, - "lValueRequested": false, - "leftHandSide": { - "id": 29776, - "name": "_balance", - "nodeType": "Identifier", - "overloadedDeclarations": [], - "referencedDeclaration": 29770, - "src": "1293:8:16", - "typeDescriptions": { - "typeIdentifier": "t_uint256", - "typeString": "uint256" - } - }, - "nodeType": "Assignment", - "operator": "=", - "rightHandSide": { - "arguments": [ - { - "expression": { - "id": 29779, - "name": "msg", - "nodeType": "Identifier", - "overloadedDeclarations": [], - "referencedDeclaration": -15, - "src": "1320:3:16", - "typeDescriptions": { - "typeIdentifier": "t_magic_message", - "typeString": "msg" - } - }, - "id": 29780, - "isConstant": false, - "isLValue": false, - "isPure": false, - "lValueRequested": false, - "memberLocation": "1324:6:16", - "memberName": "sender", - "nodeType": "MemberAccess", - "src": "1320:10:16", - "typeDescriptions": { - "typeIdentifier": "t_address", - "typeString": "address" - } - } - ], - "expression": { - "argumentTypes": [ - { - "typeIdentifier": "t_address", - "typeString": "address" - } - ], - "expression": { - "id": 29777, - "name": "token", - "nodeType": "Identifier", - "overloadedDeclarations": [], - "referencedDeclaration": 29711, - "src": "1304:5:16", - "typeDescriptions": { - "typeIdentifier": "t_contract$_IERC20_$24217", - "typeString": "contract IERC20" - } - }, - "id": 29778, - "isConstant": false, - "isLValue": false, - "isPure": false, - "lValueRequested": false, - "memberLocation": "1310:9:16", - "memberName": "balanceOf", - "nodeType": "MemberAccess", - "referencedDeclaration": 24149, - "src": "1304:15:16", - "typeDescriptions": { - "typeIdentifier": "t_function_external_view$_t_address_$returns$_t_uint256_$", - "typeString": "function (address) view external returns (uint256)" - } - }, - "id": 29781, - "isConstant": false, - "isLValue": false, - "isPure": false, - "kind": "functionCall", - "lValueRequested": false, - "nameLocations": [], - "names": [], - "nodeType": "FunctionCall", - "src": "1304:27:16", - "tryCall": false, - "typeDescriptions": { - "typeIdentifier": "t_uint256", - "typeString": "uint256" - } - }, - "src": "1293:38:16", - "typeDescriptions": { - "typeIdentifier": "t_uint256", - "typeString": "uint256" - } - }, - "id": 29783, - "nodeType": "ExpressionStatement", - "src": "1293:38:16" - } - ] - }, - "baseFunctions": [ - 29843 - ], - "documentation": { - "id": 29765, - "nodeType": "StructuredDocumentation", - "src": "1151:24:16", - "text": "@inheritdoc IGreeter" - }, - "functionSelector": "cfae3217", - "implemented": true, - "kind": "function", - "modifiers": [], - "name": "greet", - "nameLocation": "1187:5:16", - "parameters": { - "id": 29766, - "nodeType": "ParameterList", - "parameters": [], - "src": "1192:2:16" - }, - "returnParameters": { - "id": 29771, - "nodeType": "ParameterList", - "parameters": [ - { - "constant": false, - "id": 29768, - "mutability": "mutable", - "name": "_greeting", - "nameLocation": "1232:9:16", - "nodeType": "VariableDeclaration", - "scope": 29785, - "src": "1218:23:16", - "stateVariable": false, - "storageLocation": "memory", - "typeDescriptions": { - "typeIdentifier": "t_string_memory_ptr", - "typeString": "string" - }, - "typeName": { - "id": 29767, - "name": "string", - "nodeType": "ElementaryTypeName", - "src": "1218:6:16", - "typeDescriptions": { - "typeIdentifier": "t_string_storage_ptr", - "typeString": "string" - } - }, - "visibility": "internal" - }, - { - "constant": false, - "id": 29770, - "mutability": "mutable", - "name": "_balance", - "nameLocation": "1251:8:16", - "nodeType": "VariableDeclaration", - "scope": 29785, - "src": "1243:16:16", - "stateVariable": false, - "storageLocation": "default", - "typeDescriptions": { - "typeIdentifier": "t_uint256", - "typeString": "uint256" - }, - "typeName": { - "id": 29769, - "name": "uint256", - "nodeType": "ElementaryTypeName", - "src": "1243:7:16", - "typeDescriptions": { - "typeIdentifier": "t_uint256", - "typeString": "uint256" - } - }, - "visibility": "internal" - } - ], - "src": "1217:43:16" - }, - "scope": 29800, - "stateMutability": "view", - "virtual": false, - "visibility": "external" - }, - { - "id": 29799, - "nodeType": "ModifierDefinition", - "src": "1438:104:16", - "nodes": [], - "body": { - "id": 29798, - "nodeType": "Block", - "src": "1459:83:16", - "nodes": [], - "statements": [ - { - "condition": { - "commonType": { - "typeIdentifier": "t_address", - "typeString": "address" - }, - "id": 29791, - "isConstant": false, - "isLValue": false, - "isPure": false, - "lValueRequested": false, - "leftExpression": { - "expression": { - "id": 29788, - "name": "msg", - "nodeType": "Identifier", - "overloadedDeclarations": [], - "referencedDeclaration": -15, - "src": "1469:3:16", - "typeDescriptions": { - "typeIdentifier": "t_magic_message", - "typeString": "msg" - } - }, - "id": 29789, - "isConstant": false, - "isLValue": false, - "isPure": false, - "lValueRequested": false, - "memberLocation": "1473:6:16", - "memberName": "sender", - "nodeType": "MemberAccess", - "src": "1469:10:16", - "typeDescriptions": { - "typeIdentifier": "t_address", - "typeString": "address" - } - }, - "nodeType": "BinaryOperation", - "operator": "!=", - "rightExpression": { - "id": 29790, - "name": "OWNER", - "nodeType": "Identifier", - "overloadedDeclarations": [], - "referencedDeclaration": 29704, - "src": "1483:5:16", - "typeDescriptions": { - "typeIdentifier": "t_address", - "typeString": "address" - } - }, - "src": "1469:19:16", - "typeDescriptions": { - "typeIdentifier": "t_bool", - "typeString": "bool" - } - }, - "id": 29796, - "nodeType": "IfStatement", - "src": "1465:66:16", - "trueBody": { - "id": 29795, - "nodeType": "Block", - "src": "1490:41:16", - "statements": [ - { - "errorCall": { - "arguments": [], - "expression": { - "argumentTypes": [], - "id": 29792, - "name": "Greeter_OnlyOwner", - "nodeType": "Identifier", - "overloadedDeclarations": [], - "referencedDeclaration": 29813, - "src": "1505:17:16", - "typeDescriptions": { - "typeIdentifier": "t_function_error_pure$__$returns$__$", - "typeString": "function () pure" - } - }, - "id": 29793, - "isConstant": false, - "isLValue": false, - "isPure": false, - "kind": "functionCall", - "lValueRequested": false, - "nameLocations": [], - "names": [], - "nodeType": "FunctionCall", - "src": "1505:19:16", - "tryCall": false, - "typeDescriptions": { - "typeIdentifier": "t_tuple$__$", - "typeString": "tuple()" - } - }, - "id": 29794, - "nodeType": "RevertStatement", - "src": "1498:26:16" - } - ] - } - }, - { - "id": 29797, - "nodeType": "PlaceholderStatement", - "src": "1536:1:16" - } - ] - }, - "documentation": { - "id": 29786, - "nodeType": "StructuredDocumentation", - "src": "1340:95:16", - "text": " @notice Reverts in case the function was not called by the owner of the contract" - }, - "name": "onlyOwner", - "nameLocation": "1447:9:16", - "parameters": { - "id": 29787, - "nodeType": "ParameterList", - "parameters": [], - "src": "1456:2:16" - }, - "virtual": false, - "visibility": "internal" - } - ], - "abstract": false, - "baseContracts": [ - { - "baseName": { - "id": 29696, - "name": "IGreeter", - "nameLocations": [ - "198:8:16" - ], - "nodeType": "IdentifierPath", - "referencedDeclaration": 29850, - "src": "198:8:16" - }, - "id": 29697, - "nodeType": "InheritanceSpecifier", - "src": "198:8:16" - } - ], - "canonicalName": "Greeter", - "contractDependencies": [], - "contractKind": "contract", - "fullyImplemented": true, - "linearizedBaseContracts": [ - 29800, - 29850 - ], - "name": "Greeter", - "nameLocation": "187:7:16", - "scope": 29801, - "usedErrors": [ - 29813, - 29816 - ] - } - ], - "license": "UNLICENSED" - }, - "id": 16 -} \ No newline at end of file diff --git a/solidity/contracts/Greeter.sol b/solidity/contracts/Greeter.sol deleted file mode 100644 index 053e933..0000000 --- a/solidity/contracts/Greeter.sol +++ /dev/null @@ -1,59 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity =0.8.19; - -import {IERC20} from 'isolmate/interfaces/tokens/IERC20.sol'; -import {IGreeter} from 'interfaces/IGreeter.sol'; - -contract Greeter is IGreeter { - /** - * @notice Empty string for revert checks - * @dev result of doing keccak256(bytes('')) - */ - bytes32 internal constant _EMPTY_STRING = 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470; - - /// @inheritdoc IGreeter - address public immutable OWNER; - - /// @inheritdoc IGreeter - string public greeting; - - /// @inheritdoc IGreeter - IERC20 public token; - - /** - * @notice Defines the owner to the msg.sender and sets the initial greeting - * @param _greeting Initial greeting - * @param _token Initial token - */ - constructor(string memory _greeting, IERC20 _token) { - OWNER = msg.sender; - token = _token; - setGreeting(_greeting); - } - - /// @inheritdoc IGreeter - function setGreeting(string memory _greeting) public onlyOwner { - if (keccak256(bytes(_greeting)) == _EMPTY_STRING) { - revert Greeter_InvalidGreeting(); - } - - greeting = _greeting; - emit GreetingSet(_greeting); - } - - /// @inheritdoc IGreeter - function greet() external view returns (string memory _greeting, uint256 _balance) { - _greeting = greeting; - _balance = token.balanceOf(msg.sender); - } - - /** - * @notice Reverts in case the function was not called by the owner of the contract - */ - modifier onlyOwner() { - if (msg.sender != OWNER) { - revert Greeter_OnlyOwner(); - } - _; - } -} diff --git a/solidity/interfaces/IGreeter.sol b/solidity/interfaces/IGreeter.sol deleted file mode 100644 index 1087702..0000000 --- a/solidity/interfaces/IGreeter.sol +++ /dev/null @@ -1,75 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity =0.8.19; - -import {IERC20} from 'isolmate/interfaces/tokens/IERC20.sol'; - -/** - * @title Greeter Contract - * @author Wonderland - * @notice This is a basic contract created in order to portray some - * best practices and foundry functionality. - */ -interface IGreeter { - /*/////////////////////////////////////////////////////////////// - EVENTS - //////////////////////////////////////////////////////////////*/ - /** - * @notice Greeting has changed - * @param _greeting The new greeting - */ - event GreetingSet(string _greeting); - - /*/////////////////////////////////////////////////////////////// - ERRORS - //////////////////////////////////////////////////////////////*/ - /** - * @notice Throws if the function was called by someone else than the owner - */ - error Greeter_OnlyOwner(); - - /** - * @notice Throws if the greeting set is invalid - * @dev Empty string is an invalid greeting - */ - error Greeter_InvalidGreeting(); - - /*/////////////////////////////////////////////////////////////// - VARIABLES - //////////////////////////////////////////////////////////////*/ - /** - * @notice Returns the owner of the contract - * @dev The owner will always be the deployer of the contract - * @return _owner The owner of the contract - */ - function OWNER() external view returns (address _owner); - - /** - * @notice Returns the previously set greeting - * @return _greet The greeting - */ - function greeting() external view returns (string memory _greet); - - /** - * @notice Returns the token used to greet callers - * @return _token The address of the token - */ - function token() external view returns (IERC20 _token); - - /** - * @notice Greets the caller - * - * @return _greeting The greeting - * @return _balance Current token balance of the caller - */ - function greet() external view returns (string memory _greeting, uint256 _balance); - - /*/////////////////////////////////////////////////////////////// - LOGIC - //////////////////////////////////////////////////////////////*/ - /** - * @notice Sets a new greeting - * @dev Only callable by the owner - * @param _newGreeting The new greeting to be set - */ - function setGreeting(string memory _newGreeting) external; -} diff --git a/solidity/scripts/Deploy.sol b/solidity/scripts/Deploy.sol deleted file mode 100644 index 6714510..0000000 --- a/solidity/scripts/Deploy.sol +++ /dev/null @@ -1,30 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity =0.8.19; - -import {Script} from 'forge-std/Script.sol'; -import {Greeter} from 'contracts/Greeter.sol'; -import {IERC20} from 'isolmate/interfaces/tokens/IERC20.sol'; - -abstract contract Deploy is Script { - function _deploy(string memory greeting, IERC20 token) internal { - vm.startBroadcast(); - new Greeter(greeting, token); - vm.stopBroadcast(); - } -} - -contract DeployMainnet is Deploy { - function run() external { - IERC20 weth = IERC20(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2); - - _deploy('some real greeting', weth); - } -} - -contract DeployGoerli is Deploy { - function run() external { - IERC20 weth = IERC20(0xB4FBF271143F4FBf7B91A5ded31805e42b2208d6); - - _deploy('some test greeting', weth); - } -} diff --git a/solidity/test/e2e/Common.sol b/solidity/test/e2e/Common.sol index 447e1a8..893124c 100644 --- a/solidity/test/e2e/Common.sol +++ b/solidity/test/e2e/Common.sol @@ -5,21 +5,13 @@ import {DSTestFull} from 'test/utils/DSTestFull.sol'; import {console} from 'forge-std/console.sol'; import {IERC20} from 'isolmate/interfaces/tokens/IERC20.sol'; -import {Greeter, IGreeter} from 'contracts/Greeter.sol'; - contract CommonE2EBase is DSTestFull { uint256 internal constant _FORK_BLOCK = 15_452_788; - string internal _initialGreeting = 'hola'; address internal _user = _label('user'); address internal _owner = _label('owner'); - address internal _daiWhale = 0x42f8CA49E88A8fd8F0bfA2C739e648468b8f9dec; - IERC20 internal _dai = IERC20(0x6B175474E89094C44Da98b954EedeAC495271d0F); - IGreeter internal _greeter; function setUp() public { vm.createSelectFork(vm.rpcUrl('mainnet'), _FORK_BLOCK); - vm.prank(_owner); - _greeter = new Greeter(_initialGreeting, _dai); } } diff --git a/solidity/test/e2e/Greeter.t.sol b/solidity/test/e2e/Greeter.t.sol deleted file mode 100644 index 6d20fdf..0000000 --- a/solidity/test/e2e/Greeter.t.sol +++ /dev/null @@ -1,16 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity =0.8.19; - -import {CommonE2EBase} from 'test/e2e/Common.sol'; - -contract E2EGreeter is CommonE2EBase { - function test_Greet() public { - uint256 _whaleBalance = _dai.balanceOf(_daiWhale); - - vm.prank(_daiWhale); - (string memory _greeting, uint256 _balance) = _greeter.greet(); - - assertEq(_whaleBalance, _balance); - assertEq(_initialGreeting, _greeting); - } -} diff --git a/solidity/test/unit/Greeter.t.sol b/solidity/test/unit/Greeter.t.sol deleted file mode 100644 index 06df96a..0000000 --- a/solidity/test/unit/Greeter.t.sol +++ /dev/null @@ -1,99 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity =0.8.19; - -import {IERC20} from 'isolmate/interfaces/tokens/IERC20.sol'; -import {DSTestFull} from 'test/utils/DSTestFull.sol'; -import {Greeter, IGreeter} from 'contracts/Greeter.sol'; - -abstract contract Base is DSTestFull { - address internal _owner = _label('owner'); - IERC20 internal _token = IERC20(_mockContract('token')); - string internal _initialGreeting = 'hola'; - bytes32 internal _emptyString = keccak256(bytes('')); - Greeter internal _greeter; - - function setUp() public virtual { - vm.prank(_owner); - _greeter = new Greeter(_initialGreeting, _token); - } -} - -contract UnitGreeterConstructor is Base { - function test_OwnerSet(address _owner) public { - vm.prank(_owner); - _greeter = new Greeter(_initialGreeting, _token); - - assertEq(_greeter.OWNER(), _owner); - } - - function test_TokenSet(IERC20 _token) public { - _greeter = new Greeter(_initialGreeting, _token); - - assertEq(address(_greeter.token()), address(_token)); - } - - function test_GreetingSet(string memory _greeting) public { - vm.assume(keccak256(bytes(_greeting)) != _emptyString); - - _greeter = new Greeter(_greeting, _token); - assertEq(_greeting, _greeter.greeting()); - } -} - -contract UnitGreeterSetGreeting is Base { - event GreetingSet(string _greeting); - - function setUp() public override { - super.setUp(); - vm.startPrank(_owner); - } - - function test_RevertIfNotOwner(address _caller, string memory _greeting) public { - vm.assume(keccak256(bytes(_greeting)) != _emptyString); - vm.assume(_caller != _owner); - - vm.stopPrank(); - vm.prank(_caller); - - vm.expectRevert(IGreeter.Greeter_OnlyOwner.selector); - _greeter.setGreeting(_greeting); - } - - function test_RevertIfEmptyGreeting() public { - vm.expectRevert(IGreeter.Greeter_InvalidGreeting.selector); - _greeter.setGreeting(''); - } - - function test_SetGreeting(string memory _greeting) public { - vm.assume(keccak256(bytes(_greeting)) != _emptyString); - _greeter.setGreeting(_greeting); - - assertEq(_greeting, _greeter.greeting()); - } - - function test_EmitEvent(string memory _greeting) public { - vm.assume(keccak256(bytes(_greeting)) != _emptyString); - - _expectEmitNoIndex(); - emit GreetingSet(_greeting); - - _greeter.setGreeting(_greeting); - } -} - -contract UnitGreeterGreet is Base { - function test_GetGreeting() public { - vm.mockCall(address(_token), abi.encodeWithSelector(IERC20.balanceOf.selector), abi.encode(0)); - - (string memory _greeting,) = _greeter.greet(); - assertEq(_initialGreeting, _greeting); - } - - function test_GetTokenBalance(address _caller, uint256 _balance) public { - vm.mockCall(address(_token), abi.encodeWithSelector(IERC20.balanceOf.selector, _caller), abi.encode(_balance)); - - vm.prank(_caller); - (, uint256 _greetBalance) = _greeter.greet(); - assertEq(_balance, _greetBalance); - } -} From b19299584b04f864e4d6d08832f98c1c74a9f221 Mon Sep 17 00:00:00 2001 From: excaliborr Date: Fri, 27 Oct 2023 07:41:34 -0400 Subject: [PATCH 02/29] chore: update package.json --- package.json | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/package.json b/package.json index e53cdf7..af31c00 100644 --- a/package.json +++ b/package.json @@ -1,20 +1,14 @@ { - "name": "solidity-foundry-boilerplate", + "name": "safe-liveness-poc", "version": "1.0.0", "private": true, - "description": "Production ready Solidity boilerplate with Foundry", - "homepage": "https://github.com/defi-wonderland/solidity-foundry-boilerplate#readme", + "description": "Safe Liveness PoC", + "homepage": "https://github.com/defi-wonderland/safe-liveness", "repository": { "type": "git", - "url": "git+https://github.com/defi-wonderland/solidity-foundry-boilerplate.git" + "url": "git+https://github.com/defi-wonderland/safe-liveness.git" }, - "license": "MIT", "author": "Wonderland", - "contributors": [ - "0xGorilla (https://github.com/0xGorilla)", - "0xng (https://github.com/0xng)", - "gas1cent (https://github.com/gas1cent)" - ], "scripts": { "build": "forge build", "build:optimized": "FOUNDRY_PROFILE=optimized forge build", From 18ec9142779d8cb78b8b32a1cd0b4e7575bd9958 Mon Sep 17 00:00:00 2001 From: excaliborr Date: Mon, 30 Oct 2023 08:24:38 -0400 Subject: [PATCH 03/29] chore: more config files --- .github/pull_request_template.md | 3 +++ .husky/pre-commit | 2 +- .solhint.json | 3 ++- .solhint.tests.json | 32 ++++++++++++++++++++++++++++++++ .solhintignore | 3 +++ 5 files changed, 41 insertions(+), 2 deletions(-) create mode 100644 .github/pull_request_template.md create mode 100644 .solhint.tests.json create mode 100644 .solhintignore diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..0bd7eb9 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,3 @@ +# 🤖 Linear + +Closes SAF-XXX \ No newline at end of file diff --git a/.husky/pre-commit b/.husky/pre-commit index 481885d..509d461 100755 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -4,4 +4,4 @@ # 1. Build the contracts # 2. Stage build output # 2. Lint and stage style improvements -yarn build && git add out && npx lint-staged \ No newline at end of file +yarn build && npx lint-staged \ No newline at end of file diff --git a/.solhint.json b/.solhint.json index 4592084..d0b9cc4 100644 --- a/.solhint.json +++ b/.solhint.json @@ -12,6 +12,7 @@ "const-name-snakecase": "off", "no-inline-assembly": "off", "no-empty-blocks": "off", + "func-named-parameters": ["error", 4], "private-vars-leading-underscore": ["warn", { "strict": false }], "defi-wonderland/non-state-vars-leading-underscore": ["warn"], "defi-wonderland/contract-data-order": ["warn"], @@ -25,4 +26,4 @@ "defi-wonderland/wonder-var-name-mixedcase": ["warn"], "avoid-low-level-calls": "off" } -} +} \ No newline at end of file diff --git a/.solhint.tests.json b/.solhint.tests.json new file mode 100644 index 0000000..372317c --- /dev/null +++ b/.solhint.tests.json @@ -0,0 +1,32 @@ +{ + "extends": "solhint:recommended", + "plugins": ["defi-wonderland"], + "rules": { + "compiler-version": ["off"], + "constructor-syntax": "warn", + "quotes": ["error", "single"], + "func-visibility": ["warn", { "ignoreConstructors": true }], + "not-rely-on-time": "off", + "func-name-mixedcase": "off", + "var-name-mixedcase": "off", + "const-name-snakecase": "off", + "no-inline-assembly": "off", + "no-empty-blocks": "off", + "contract-name-camelcase": "off", + "event-name-camelcase": "off", + "func-named-parameters": "off", + "no-global-import": "off", + "max-states-count": "off", + "private-vars-leading-underscore": ["warn", { "strict": false }], + "defi-wonderland/non-state-vars-leading-underscore": ["warn"], + "defi-wonderland/contract-data-order": ["warn"], + "defi-wonderland/enum-name-camelcase": ["warn"], + "defi-wonderland/immutable-name-snakecase": ["warn"], + "defi-wonderland/interface-member-order": ["warn"], + "defi-wonderland/interface-starts-with-i": ["warn"], + "defi-wonderland/named-return-values": ["warn"], + "defi-wonderland/struct-name-camelcase": ["warn"], + "defi-wonderland/wonder-var-name-mixedcase": ["warn"], + "avoid-low-level-calls": "off" + } +} \ No newline at end of file diff --git a/.solhintignore b/.solhintignore new file mode 100644 index 0000000..6da69ce --- /dev/null +++ b/.solhintignore @@ -0,0 +1,3 @@ +node_modules +cache +out \ No newline at end of file From fe35506f2c821a8c619fc4209ef59f5addb8f871 Mon Sep 17 00:00:00 2001 From: excaliborr Date: Fri, 3 Nov 2023 08:11:33 -0400 Subject: [PATCH 04/29] feat: adding storage mirror --- solidity/contracts/StorageMirror.sol | 25 +++++++++++++ solidity/interfaces/IStorageMirror.sol | 31 ++++++++++++++++ solidity/test/unit/StorageMirror.t.sol | 49 ++++++++++++++++++++++++++ solidity/test/utils/DSTestFull.sol | 4 +-- 4 files changed, 107 insertions(+), 2 deletions(-) create mode 100644 solidity/contracts/StorageMirror.sol create mode 100644 solidity/interfaces/IStorageMirror.sol create mode 100644 solidity/test/unit/StorageMirror.t.sol diff --git a/solidity/contracts/StorageMirror.sol b/solidity/contracts/StorageMirror.sol new file mode 100644 index 0000000..9ce9798 --- /dev/null +++ b/solidity/contracts/StorageMirror.sol @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity =0.8.19; + +import {IStorageMirror} from 'interfaces/IStorageMirror.sol'; + +contract StorageMirror is IStorageMirror { + /** + * @notice The mapping of the safe to the keccak256(abi.encode(SafeSettings)) + */ + + mapping(address => bytes32) public latestSettingsHash; + + /** + * @notice Updates a safe's settings hash + * @dev The safe should always be msg.sender + * @param _safeSettings The settings we are going to update to + */ + + function update(SafeSettings memory _safeSettings) external { + bytes32 _settingsHash = keccak256(abi.encode(_safeSettings)); + latestSettingsHash[msg.sender] = _settingsHash; + + emit SettingsUpdated(msg.sender, _settingsHash, _safeSettings); + } +} diff --git a/solidity/interfaces/IStorageMirror.sol b/solidity/interfaces/IStorageMirror.sol new file mode 100644 index 0000000..dd9aca2 --- /dev/null +++ b/solidity/interfaces/IStorageMirror.sol @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity =0.8.19; + +interface IStorageMirror { + /** + * @notice Emits when the settings have been updated + * + * @param _safe The address of the safe + * @param _settingsHash The hash of the settings + * @param _safeSettings The plaintext of the settings + */ + + event SettingsUpdated(address indexed _safe, bytes32 indexed _settingsHash, SafeSettings _safeSettings); + + struct SafeSettings { + address[] owners; + uint256 threshold; + } + + /** + * @notice Updates a safe's settings hash + * @dev The safe should always be msg.sender + * @param _safeSettings The settings we are going to update to + */ + function update(SafeSettings memory _safeSettings) external; + + /** + * @notice The mapping of the safe to the keccak256(abi.encode(SafeSettings)) + */ + function latestSettingsHash(address _safe) external view returns (bytes32 _latestSettingsHash); +} diff --git a/solidity/test/unit/StorageMirror.t.sol b/solidity/test/unit/StorageMirror.t.sol new file mode 100644 index 0000000..89c83ee --- /dev/null +++ b/solidity/test/unit/StorageMirror.t.sol @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity >=0.8.4 <0.9.0; + +import {DSTestFull} from '../utils/DSTestFull.sol'; +import {StorageMirror} from 'contracts/StorageMirror.sol'; +import {IStorageMirror} from 'interfaces/IStorageMirror.sol'; + +abstract contract Base is DSTestFull { + address internal _safe = _label('safe'); + + StorageMirror internal _storageMirror = new StorageMirror(); + + event SettingsUpdated( + address indexed _safe, bytes32 indexed _settingsHash, IStorageMirror.SafeSettings _safeSettings + ); +} + +contract UnitStorageMirror is Base { + function testUpdate(uint256 _threshold, address _owner) public { + address[] memory _owners = new address[](1); + _owners[0] = _owner; + + IStorageMirror.SafeSettings memory _safeSettings = + IStorageMirror.SafeSettings({owners: _owners, threshold: _threshold}); + + vm.prank(_safe); + _storageMirror.update(_safeSettings); + + bytes32 _settingsHash = keccak256(abi.encode(_safeSettings)); + bytes32 _savedHash = _storageMirror.latestSettingsHash(_safe); + + assertEq(_settingsHash, _savedHash, 'Settings hash should be saved'); + } + + function testUpdateEmitsEvent(uint256 _threshold, address _owner) public { + address[] memory _owners = new address[](1); + _owners[0] = _owner; + + IStorageMirror.SafeSettings memory _safeSettings = + IStorageMirror.SafeSettings({owners: _owners, threshold: _threshold}); + + bytes32 _expectedSettingsHash = keccak256(abi.encode(_safeSettings)); + + vm.prank(_safe); + vm.expectEmit(true, true, true, true); + emit SettingsUpdated(_safe, _expectedSettingsHash, _safeSettings); + _storageMirror.update(_safeSettings); + } +} diff --git a/solidity/test/utils/DSTestFull.sol b/solidity/test/utils/DSTestFull.sol index c2742d8..300212f 100644 --- a/solidity/test/utils/DSTestFull.sol +++ b/solidity/test/utils/DSTestFull.sol @@ -2,9 +2,9 @@ pragma solidity =0.8.19; import {console} from 'forge-std/console.sol'; -import {PRBTest} from 'prb/test/PRBTest.sol'; +import {Test} from 'forge-std/Test.sol'; -contract DSTestFull is PRBTest { +contract DSTestFull is Test { // Seed for the generation of pseudorandom addresses bytes32 private _nextAddressSeed = keccak256(abi.encodePacked('address')); From 63d061abacd28fa2680da188660c0c57673b2c34 Mon Sep 17 00:00:00 2001 From: excaliborr Date: Fri, 3 Nov 2023 08:46:47 -0400 Subject: [PATCH 05/29] chore: comments --- solidity/contracts/StorageMirror.sol | 5 +++++ solidity/interfaces/IStorageMirror.sol | 28 +++++++++++++++++++++----- 2 files changed, 28 insertions(+), 5 deletions(-) diff --git a/solidity/contracts/StorageMirror.sol b/solidity/contracts/StorageMirror.sol index 9ce9798..41ce3e3 100644 --- a/solidity/contracts/StorageMirror.sol +++ b/solidity/contracts/StorageMirror.sol @@ -3,6 +3,11 @@ pragma solidity =0.8.19; import {IStorageMirror} from 'interfaces/IStorageMirror.sol'; +/** + * @title StorageMirror + * @notice This contract is a storage of information about the safe’s settings. All safe’s settings changes should be mirrored in this contract and be saved. + * @notice In the end, this contract’s storage root is gonna be used to see if a proposed update on the non-home chain is valid. + */ contract StorageMirror is IStorageMirror { /** * @notice The mapping of the safe to the keccak256(abi.encode(SafeSettings)) diff --git a/solidity/interfaces/IStorageMirror.sol b/solidity/interfaces/IStorageMirror.sol index dd9aca2..16df475 100644 --- a/solidity/interfaces/IStorageMirror.sol +++ b/solidity/interfaces/IStorageMirror.sol @@ -2,6 +2,10 @@ pragma solidity =0.8.19; interface IStorageMirror { + /*/////////////////////////////////////////////////////////////// + EVENTS + //////////////////////////////////////////////////////////////*/ + /** * @notice Emits when the settings have been updated * @@ -12,20 +16,34 @@ interface IStorageMirror { event SettingsUpdated(address indexed _safe, bytes32 indexed _settingsHash, SafeSettings _safeSettings); + /*/////////////////////////////////////////////////////////////// + STRUCTS + //////////////////////////////////////////////////////////////*/ + struct SafeSettings { + // Aray of the safes owners address[] owners; + // The threshold of the safe uint256 threshold; } + /*/////////////////////////////////////////////////////////////// + VARIABLES + //////////////////////////////////////////////////////////////*/ + + /** + * @notice The mapping of the safe to the keccak256(abi.encode(SafeSettings)) + */ + function latestSettingsHash(address _safe) external view returns (bytes32 _latestSettingsHash); + + /*/////////////////////////////////////////////////////////////// + LOGIC + //////////////////////////////////////////////////////////////*/ + /** * @notice Updates a safe's settings hash * @dev The safe should always be msg.sender * @param _safeSettings The settings we are going to update to */ function update(SafeSettings memory _safeSettings) external; - - /** - * @notice The mapping of the safe to the keccak256(abi.encode(SafeSettings)) - */ - function latestSettingsHash(address _safe) external view returns (bytes32 _latestSettingsHash); } From ba9ff1299262fe6ca02322b972660b26124a31b4 Mon Sep 17 00:00:00 2001 From: excaliborr Date: Fri, 3 Nov 2023 08:48:13 -0400 Subject: [PATCH 06/29] fix: typo --- solidity/interfaces/IStorageMirror.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/solidity/interfaces/IStorageMirror.sol b/solidity/interfaces/IStorageMirror.sol index 16df475..501953c 100644 --- a/solidity/interfaces/IStorageMirror.sol +++ b/solidity/interfaces/IStorageMirror.sol @@ -21,7 +21,7 @@ interface IStorageMirror { //////////////////////////////////////////////////////////////*/ struct SafeSettings { - // Aray of the safes owners + // Array of the safes owners address[] owners; // The threshold of the safe uint256 threshold; From 8256551750a90b059b4fe65834093006b2f14b0b Mon Sep 17 00:00:00 2001 From: 0xOneTony <112496816+0xOneTony@users.noreply.github.com> Date: Wed, 15 Nov 2023 11:37:54 +0200 Subject: [PATCH 07/29] feat: update storage mirror guard (#3) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # 🤖 Linear Closes SAF-12 Closes SAF-13 Closes SAF-14 --- package.json | 4 +- remappings.txt | 2 + solidity/contracts/StorageMirror.sol | 2 - .../contracts/UpdateStorageMirrorGuard.sol | 77 ++++++++++++++++ solidity/interfaces/IGuardCallbackModule.sol | 6 ++ solidity/interfaces/IStorageMirror.sol | 1 - solidity/test/e2e/Common.sol | 9 +- solidity/test/unit/StorageMirror.t.sol | 28 +++--- .../test/unit/UpdateStorageMirrorGuard.t.sol | 88 +++++++++++++++++++ solidity/test/utils/DSTestFull.sol | 68 -------------- yarn.lock | 26 ++++++ 11 files changed, 222 insertions(+), 89 deletions(-) create mode 100644 solidity/contracts/UpdateStorageMirrorGuard.sol create mode 100644 solidity/interfaces/IGuardCallbackModule.sol create mode 100644 solidity/test/unit/UpdateStorageMirrorGuard.t.sol delete mode 100644 solidity/test/utils/DSTestFull.sol diff --git a/package.json b/package.json index af31c00..73b4170 100644 --- a/package.json +++ b/package.json @@ -31,10 +31,12 @@ "package.json": "sort-package-json" }, "dependencies": { + "@defi-wonderland/solidity-utils": "0.0.0-3e9c8e8b", "ds-test": "github:dapphub/ds-test#e282159", "forge-std": "github:foundry-rs/forge-std#v1.5.6", "isolmate": "github:defi-wonderland/isolmate#59e1804", - "prb/test": "github:paulrberg/prb-test#a245c71" + "prb/test": "github:paulrberg/prb-test#a245c71", + "safe-contracts": "github:safe-global/safe-contracts#v1.4.1" }, "devDependencies": { "@commitlint/cli": "17.0.3", diff --git a/remappings.txt b/remappings.txt index 89bb6bd..bcb55ae 100644 --- a/remappings.txt +++ b/remappings.txt @@ -2,6 +2,8 @@ ds-test/=node_modules/ds-test/src prb/test/=node_modules/prb/test/src/ forge-std/=node_modules/forge-std/src isolmate/=node_modules/isolmate/src +safe-contracts/=node_modules/safe-contracts/contracts +@defi-wonderland/=node_modules/@defi-wonderland/ contracts/=solidity/contracts interfaces/=solidity/interfaces diff --git a/solidity/contracts/StorageMirror.sol b/solidity/contracts/StorageMirror.sol index 41ce3e3..f34d3ca 100644 --- a/solidity/contracts/StorageMirror.sol +++ b/solidity/contracts/StorageMirror.sol @@ -12,7 +12,6 @@ contract StorageMirror is IStorageMirror { /** * @notice The mapping of the safe to the keccak256(abi.encode(SafeSettings)) */ - mapping(address => bytes32) public latestSettingsHash; /** @@ -20,7 +19,6 @@ contract StorageMirror is IStorageMirror { * @dev The safe should always be msg.sender * @param _safeSettings The settings we are going to update to */ - function update(SafeSettings memory _safeSettings) external { bytes32 _settingsHash = keccak256(abi.encode(_safeSettings)); latestSettingsHash[msg.sender] = _settingsHash; diff --git a/solidity/contracts/UpdateStorageMirrorGuard.sol b/solidity/contracts/UpdateStorageMirrorGuard.sol new file mode 100644 index 0000000..087af43 --- /dev/null +++ b/solidity/contracts/UpdateStorageMirrorGuard.sol @@ -0,0 +1,77 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity =0.8.19; + +import {BaseGuard} from 'safe-contracts/base/GuardManager.sol'; +import {Enum} from 'safe-contracts/common/Enum.sol'; +import {IGuardCallbackModule} from 'interfaces/IGuardCallbackModule.sol'; +import {IStorageMirror} from 'interfaces/IStorageMirror.sol'; + +/** + * @title UpdateStorageMirrorGuard + * @notice This guard is responsible for calling the GuardCallbackModule when a change in settings of a safe is executed. + */ +contract UpdateStorageMirrorGuard is BaseGuard { + /** + * @notice Emits when a change in a safe's settings is observed + */ + event SettingsChanged(address indexed _safe, bytes32 indexed _settingsHash, IStorageMirror.SafeSettings _settings); + /** + * @notice The address of the guard callback module + */ + + IGuardCallbackModule public immutable GUARD_CALLBACK_MODULE; + + /** + * @notice A boolean that returns true if a tx is changing the safe's settings + */ + bool public didSettingsChange; + + /** + * @notice The hash of the new settings + */ + bytes32 public settingsHash; + + constructor(IGuardCallbackModule _guardCallbackModule) { + GUARD_CALLBACK_MODULE = _guardCallbackModule; + } + + /** + * @notice Guard hook that is called before a Safe transaction is executed + */ + // solhint-disable no-unused-vars + function checkTransaction( + address _to, + uint256 _value, + bytes memory _data, + Enum.Operation _operation, + uint256 _safeTxGas, + uint256 _baseGas, + uint256 _gasPrice, + address _gasToken, + address payable _refundReceiver, + bytes memory _signatures, + address _msgSender + ) external { + didSettingsChange = true; + // TODO: change these data with the decoded ones + address[] memory _owners = new address[](1); + IStorageMirror.SafeSettings memory _safeSettings = IStorageMirror.SafeSettings({owners: _owners, threshold: 1}); + + settingsHash = keccak256(abi.encode(_safeSettings)); + + emit SettingsChanged(msg.sender, settingsHash, _safeSettings); + } + + /** + * @notice Guard hook that is called after a Safe transaction is executed + * @dev It should call the GuardCallbackModule + * @dev The msg.sender should be the safe + */ + function checkAfterExecution(bytes32 _txHash, bool _success) external { + if (didSettingsChange && _success) { + GUARD_CALLBACK_MODULE.saveUpdatedSettings(msg.sender, settingsHash); + didSettingsChange = false; + settingsHash = keccak256(abi.encodePacked('')); + } + } +} diff --git a/solidity/interfaces/IGuardCallbackModule.sol b/solidity/interfaces/IGuardCallbackModule.sol new file mode 100644 index 0000000..b52b2e6 --- /dev/null +++ b/solidity/interfaces/IGuardCallbackModule.sol @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity =0.8.19; + +interface IGuardCallbackModule { + function saveUpdatedSettings(address _safe, bytes32 _settingsHash) external; +} diff --git a/solidity/interfaces/IStorageMirror.sol b/solidity/interfaces/IStorageMirror.sol index 501953c..e75c4c8 100644 --- a/solidity/interfaces/IStorageMirror.sol +++ b/solidity/interfaces/IStorageMirror.sol @@ -13,7 +13,6 @@ interface IStorageMirror { * @param _settingsHash The hash of the settings * @param _safeSettings The plaintext of the settings */ - event SettingsUpdated(address indexed _safe, bytes32 indexed _settingsHash, SafeSettings _safeSettings); /*/////////////////////////////////////////////////////////////// diff --git a/solidity/test/e2e/Common.sol b/solidity/test/e2e/Common.sol index 893124c..c826103 100644 --- a/solidity/test/e2e/Common.sol +++ b/solidity/test/e2e/Common.sol @@ -1,15 +1,14 @@ // SPDX-License-Identifier: MIT pragma solidity =0.8.19; -import {DSTestFull} from 'test/utils/DSTestFull.sol'; -import {console} from 'forge-std/console.sol'; +import {DSTestPlus} from '@defi-wonderland/solidity-utils/solidity/test/DSTestPlus.sol'; import {IERC20} from 'isolmate/interfaces/tokens/IERC20.sol'; -contract CommonE2EBase is DSTestFull { +contract CommonE2EBase is DSTestPlus { uint256 internal constant _FORK_BLOCK = 15_452_788; - address internal _user = _label('user'); - address internal _owner = _label('owner'); + address internal _user = makeAddr('user'); + address internal _owner = makeAddr('owner'); function setUp() public { vm.createSelectFork(vm.rpcUrl('mainnet'), _FORK_BLOCK); diff --git a/solidity/test/unit/StorageMirror.t.sol b/solidity/test/unit/StorageMirror.t.sol index 89c83ee..ee898be 100644 --- a/solidity/test/unit/StorageMirror.t.sol +++ b/solidity/test/unit/StorageMirror.t.sol @@ -1,18 +1,22 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity >=0.8.4 <0.9.0; -import {DSTestFull} from '../utils/DSTestFull.sol'; +import {Test} from 'forge-std/Test.sol'; import {StorageMirror} from 'contracts/StorageMirror.sol'; import {IStorageMirror} from 'interfaces/IStorageMirror.sol'; -abstract contract Base is DSTestFull { - address internal _safe = _label('safe'); - - StorageMirror internal _storageMirror = new StorageMirror(); - +abstract contract Base is Test { event SettingsUpdated( address indexed _safe, bytes32 indexed _settingsHash, IStorageMirror.SafeSettings _safeSettings ); + + address public safe; + StorageMirror public storageMirror; + + function setUp() public { + safe = makeAddr('safe'); + storageMirror = new StorageMirror(); + } } contract UnitStorageMirror is Base { @@ -23,11 +27,11 @@ contract UnitStorageMirror is Base { IStorageMirror.SafeSettings memory _safeSettings = IStorageMirror.SafeSettings({owners: _owners, threshold: _threshold}); - vm.prank(_safe); - _storageMirror.update(_safeSettings); + vm.prank(safe); + storageMirror.update(_safeSettings); bytes32 _settingsHash = keccak256(abi.encode(_safeSettings)); - bytes32 _savedHash = _storageMirror.latestSettingsHash(_safe); + bytes32 _savedHash = storageMirror.latestSettingsHash(safe); assertEq(_settingsHash, _savedHash, 'Settings hash should be saved'); } @@ -41,9 +45,9 @@ contract UnitStorageMirror is Base { bytes32 _expectedSettingsHash = keccak256(abi.encode(_safeSettings)); - vm.prank(_safe); + vm.prank(safe); vm.expectEmit(true, true, true, true); - emit SettingsUpdated(_safe, _expectedSettingsHash, _safeSettings); - _storageMirror.update(_safeSettings); + emit SettingsUpdated(safe, _expectedSettingsHash, _safeSettings); + storageMirror.update(_safeSettings); } } diff --git a/solidity/test/unit/UpdateStorageMirrorGuard.t.sol b/solidity/test/unit/UpdateStorageMirrorGuard.t.sol new file mode 100644 index 0000000..70cd31e --- /dev/null +++ b/solidity/test/unit/UpdateStorageMirrorGuard.t.sol @@ -0,0 +1,88 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity >=0.8.4 <0.9.0; + +import {Test} from 'forge-std/Test.sol'; +import {Enum} from 'safe-contracts/common/Enum.sol'; +import {UpdateStorageMirrorGuard} from 'contracts/UpdateStorageMirrorGuard.sol'; +import {IGuardCallbackModule} from 'interfaces/IGuardCallbackModule.sol'; +import {IStorageMirror} from 'interfaces/IStorageMirror.sol'; + +abstract contract Base is Test { + event SettingsChanged(address indexed _safe, bytes32 indexed _settingsHash, IStorageMirror.SafeSettings _settings); + + address public safe; + IGuardCallbackModule public guardCallbackModule; + UpdateStorageMirrorGuard public updateStorageMirrorGuard; + + address[] public owners = new address[](1); + IStorageMirror.SafeSettings public safeSettings = IStorageMirror.SafeSettings({owners: owners, threshold: 1}); + bytes32 public settingsHash = keccak256(abi.encode(safeSettings)); + + function setUp() public { + safe = makeAddr('safe'); + guardCallbackModule = IGuardCallbackModule(makeAddr('guardCallbackModule')); + updateStorageMirrorGuard = new UpdateStorageMirrorGuard(guardCallbackModule); + } +} + +contract UnitUpdateStorageMirrorGuard is Base { + function testCheckTransaction() public { + assertFalse(updateStorageMirrorGuard.didSettingsChange()); + assertEq(updateStorageMirrorGuard.settingsHash(), bytes32('')); + + vm.expectEmit(true, true, true, true); + emit SettingsChanged(safe, settingsHash, safeSettings); + vm.prank(safe); + updateStorageMirrorGuard.checkTransaction( + address(0), 0, '', Enum.Operation.Call, 0, 0, 0, address(0), payable(0), '', address(0) + ); + + assertTrue(updateStorageMirrorGuard.didSettingsChange()); + assertEq(updateStorageMirrorGuard.settingsHash(), settingsHash, 'Settings hash should be stored'); + } + + function testCheckAfterExecution(bytes32 _txHash) public { + // Call checkTransaction to change didSettingsChange to true + vm.prank(safe); + updateStorageMirrorGuard.checkTransaction( + address(0), 0, '', Enum.Operation.Call, 0, 0, 0, address(0), payable(0), '', address(0) + ); + + vm.mockCall( + address(guardCallbackModule), + abi.encodeCall(IGuardCallbackModule.saveUpdatedSettings, (safe, settingsHash)), + abi.encode() + ); + vm.expectCall( + address(guardCallbackModule), abi.encodeCall(IGuardCallbackModule.saveUpdatedSettings, (safe, settingsHash)) + ); + vm.prank(safe); + updateStorageMirrorGuard.checkAfterExecution(_txHash, true); + + assertFalse(updateStorageMirrorGuard.didSettingsChange()); + assertEq(updateStorageMirrorGuard.settingsHash(), keccak256(abi.encodePacked('')), 'Settings hash should reset'); + } + + function testCheckAfterExecutionNoSettingsChange(bytes32 _txHash) public { + vm.prank(safe); + updateStorageMirrorGuard.checkAfterExecution(_txHash, true); + + assertFalse(updateStorageMirrorGuard.didSettingsChange()); + assertEq(updateStorageMirrorGuard.settingsHash(), bytes32(''), 'Settings hash should stay empty'); + } + + function testCheckAfterExecutionTxFailed(bytes32 _txHash) public { + // Call checkTransaction to change didSettingsChange to true + vm.prank(safe); + updateStorageMirrorGuard.checkTransaction( + address(0), 0, '', Enum.Operation.Call, 0, 0, 0, address(0), payable(0), '', address(0) + ); + + vm.prank(safe); + updateStorageMirrorGuard.checkAfterExecution(_txHash, false); + + // Should be true since the tx failed to execute and thus didnt make it to reset + assertTrue(updateStorageMirrorGuard.didSettingsChange()); + assertEq(updateStorageMirrorGuard.settingsHash(), settingsHash, 'Settings hash should stay the same'); + } +} diff --git a/solidity/test/utils/DSTestFull.sol b/solidity/test/utils/DSTestFull.sol deleted file mode 100644 index 300212f..0000000 --- a/solidity/test/utils/DSTestFull.sol +++ /dev/null @@ -1,68 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity =0.8.19; - -import {console} from 'forge-std/console.sol'; -import {Test} from 'forge-std/Test.sol'; - -contract DSTestFull is Test { - // Seed for the generation of pseudorandom addresses - bytes32 private _nextAddressSeed = keccak256(abi.encodePacked('address')); - - /** - * @dev Creates a new pseudorandom address and labels it with the given label - * @param _name Name of the label. - * @return _address The address generated and labeled - */ - function _label(string memory _name) internal returns (address _address) { - return _label(_newAddress(), _name); - } - - /** - * @dev Labels the given address and returns it - * - * @param _addy Address to label. - * @param _name Name of the label. - * - * @return _address The address Labeled address - */ - function _label(address _addy, string memory _name) internal returns (address _address) { - vm.label(_addy, _name); - return _addy; - } - - /** - * @dev Creates a mock contract in a pseudorandom address and labels it. - * @param _name Label for the mock contract. - * @return _address The address of the mock contract. - */ - function _mockContract(string memory _name) internal returns (address _address) { - return _mockContract(_newAddress(), _name); - } - - /** - * @dev Creates a mock contract in a specified address and labels it. - * - * @param _addy Address for the mock contract. - * @param _name Label for the mock contract. - * - * @return _address The address of the mock contract. - */ - function _mockContract(address _addy, string memory _name) internal returns (address _address) { - vm.etch(_addy, new bytes(0x1)); - return _label(_addy, _name); - } - - /** - * @dev Creates a pseudorandom address. - * @return _address The address of the mock contract. - */ - function _newAddress() internal returns (address _address) { - address payable _nextAddress = payable(address(uint160(uint256(_nextAddressSeed)))); - _nextAddressSeed = keccak256(abi.encodePacked(_nextAddressSeed)); - _address = _nextAddress; - } - - function _expectEmitNoIndex() internal { - vm.expectEmit(false, false, false, true); - } -} diff --git a/yarn.lock b/yarn.lock index 61cdac2..6974aff 100644 --- a/yarn.lock +++ b/yarn.lock @@ -191,6 +191,15 @@ dependencies: "@jridgewell/trace-mapping" "0.3.9" +"@defi-wonderland/solidity-utils@0.0.0-3e9c8e8b": + version "0.0.0-3e9c8e8b" + resolved "https://registry.yarnpkg.com/@defi-wonderland/solidity-utils/-/solidity-utils-0.0.0-3e9c8e8b.tgz#1f9c47506e1679ea36d0854e9aa69bd210af4da0" + integrity sha512-HCN5TTO58jTrLrAcxXwTPm++P0u4dMDnwVkssszoEu0SmVKwegzZOdoA+gNbfOjjYydph+3+9bAZbNXj+UK2rg== + dependencies: + "@openzeppelin/contracts" "4.9.2" + ds-test "https://github.com/dapphub/ds-test" + forge-std "https://github.com/foundry-rs/forge-std" + "@jridgewell/resolve-uri@^3.0.3": version "3.1.1" resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz#c08679063f279615a3326583ba3a90d1d82cc721" @@ -230,6 +239,11 @@ "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" +"@openzeppelin/contracts@4.9.2": + version "4.9.2" + resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-4.9.2.tgz#1cb2d5e4d3360141a17dbc45094a8cad6aac16c1" + integrity sha512-mO+y6JaqXjWeMh9glYVzVu8HYPGknAAnWyxTRhGeckOruyXQMNnlcW6w/Dx9ftLeIQk6N+ZJFuVmTwF7lEIFrg== + "@solidity-parser/parser@^0.13.2": version "0.13.2" resolved "https://registry.yarnpkg.com/@solidity-parser/parser/-/parser-0.13.2.tgz#b6c71d8ca0b382d90a7bbed241f9bc110af65cbe" @@ -791,6 +805,10 @@ dot-prop@^5.1.0: version "1.0.0" resolved "https://codeload.github.com/dapphub/ds-test/tar.gz/e282159d5170298eb2455a6c05280ab5a73a4ef0" +"ds-test@https://github.com/dapphub/ds-test": + version "1.0.0" + resolved "https://github.com/dapphub/ds-test#e282159d5170298eb2455a6c05280ab5a73a4ef0" + eastasianwidth@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb" @@ -1065,6 +1083,10 @@ flatted@^2.0.0: version "1.5.6" resolved "https://codeload.github.com/foundry-rs/forge-std/tar.gz/e8a047e3f40f13fa37af6fe14e6e06283d9a060e" +"forge-std@https://github.com/foundry-rs/forge-std": + version "1.7.2" + resolved "https://github.com/foundry-rs/forge-std#bdea49f9bb3c58c8c35850c3bdc17eaeea756e9a" + fs-extra@^11.0.0: version "11.1.1" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-11.1.1.tgz#da69f7c39f3b002378b0954bb6ae7efdc0876e2d" @@ -2087,6 +2109,10 @@ safe-buffer@~5.2.0: resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== +"safe-contracts@github:safe-global/safe-contracts#v1.4.1": + version "1.4.1" + resolved "https://codeload.github.com/safe-global/safe-contracts/tar.gz/bf943f80fec5ac647159d26161446ac5d716a294" + "safer-buffer@>= 2.1.2 < 3": version "2.1.2" resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" From 38fbdfc4cc24f49910c7c7e137d8c0d6a2747aab Mon Sep 17 00:00:00 2001 From: excaliborr <124819095+excaliborr@users.noreply.github.com> Date: Wed, 15 Nov 2023 12:42:21 -0500 Subject: [PATCH 08/29] feat: add GuardCallbackModule (#4) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # 🤖 Linear Closes SAF-17 Closes SAF-16 Closes SAF-15 --- solidity/contracts/GuardCallbackModule.sol | 45 ++++++++++++ solidity/interfaces/IGuardCallbackModule.sol | 42 ++++++++++++ solidity/interfaces/ISafe.sol | 40 +++++++++++ solidity/test/unit/GuardCallbackModule.t.sol | 72 ++++++++++++++++++++ 4 files changed, 199 insertions(+) create mode 100644 solidity/contracts/GuardCallbackModule.sol create mode 100644 solidity/interfaces/ISafe.sol create mode 100644 solidity/test/unit/GuardCallbackModule.t.sol diff --git a/solidity/contracts/GuardCallbackModule.sol b/solidity/contracts/GuardCallbackModule.sol new file mode 100644 index 0000000..7c43eeb --- /dev/null +++ b/solidity/contracts/GuardCallbackModule.sol @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity =0.8.19; + +import {IStorageMirror} from 'interfaces/IStorageMirror.sol'; +import {ISafe} from 'interfaces/ISafe.sol'; +import {Enum} from 'safe-contracts/common/Enum.sol'; +import {IGuardCallbackModule} from 'interfaces/IGuardCallbackModule.sol'; + +/** + * @title GuardCallbackModule + * @notice This contract is a module that is used to save the updated settings to the StorageMirror. + * @dev It can also be used to set the guard and module in one transaction. + */ +contract GuardCallbackModule is IGuardCallbackModule { + address public immutable STORAGE_MIRROR; + address public immutable GUARD; + + constructor(address _storageMirror, address _guard) { + STORAGE_MIRROR = _storageMirror; + GUARD = _guard; + } + + /** + * @notice Initates the module by setting the guard. + */ + + function setGuard() external { + ISafe(msg.sender).execTransactionFromModule( + msg.sender, 0, abi.encodeWithSelector(ISafe.setGuard.selector, GUARD), Enum.Operation.Call + ); + } + + /** + * @notice Saves the updated settings for the safe to the StorageMirror. + * @dev Executes a transaction from the module to update the safe settings in the StorageMirror. + * @param _safe The address of the safe. + * @param _settingsHash The hash of the new settings for the safe. + */ + + function saveUpdatedSettings(address _safe, bytes32 _settingsHash) external { + if (msg.sender != GUARD) revert OnlyGuard(); + bytes memory _txData = abi.encodeWithSelector(IStorageMirror.update.selector, _settingsHash); + ISafe(_safe).execTransactionFromModule(STORAGE_MIRROR, 0, _txData, Enum.Operation.Call); + } +} diff --git a/solidity/interfaces/IGuardCallbackModule.sol b/solidity/interfaces/IGuardCallbackModule.sol index b52b2e6..3297c30 100644 --- a/solidity/interfaces/IGuardCallbackModule.sol +++ b/solidity/interfaces/IGuardCallbackModule.sol @@ -2,5 +2,47 @@ pragma solidity =0.8.19; interface IGuardCallbackModule { + /*/////////////////////////////////////////////////////////////// + ERRORS + //////////////////////////////////////////////////////////////*/ + + /** + * @notice Reverts when a function is called from an address that isnt the guard + */ + error OnlyGuard(); + + /*/////////////////////////////////////////////////////////////// + VARIABLES + //////////////////////////////////////////////////////////////*/ + + /** + * @notice The address of the StorageMirror contract. + */ + + function STORAGE_MIRROR() external view returns (address _storageMirror); + + /** + * @notice The address of the UpdateStorageMirrorGuard contract. + */ + + function GUARD() external view returns (address _guard); + + /*/////////////////////////////////////////////////////////////// + LOGIC + //////////////////////////////////////////////////////////////*/ + + /** + * @notice Saves the updated settings for the safe to the StorageMirror. + * @dev Executes a transaction from the module to update the safe settings in the StorageMirror. + * @param _safe The address of the safe. + * @param _settingsHash The hash of the new settings for the safe. + */ + function saveUpdatedSettings(address _safe, bytes32 _settingsHash) external; + + /** + * @notice Initates the module by setting the guard. + */ + + function setGuard() external; } diff --git a/solidity/interfaces/ISafe.sol b/solidity/interfaces/ISafe.sol new file mode 100644 index 0000000..882255c --- /dev/null +++ b/solidity/interfaces/ISafe.sol @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity =0.8.19; + +import {Enum} from 'safe-contracts/common/Enum.sol'; + +interface ISafe { + /** + * @notice Execute `operation` (0: Call, 1: DelegateCall) to `to` with `value` (Native Token) + * @dev Function is virtual to allow overriding for L2 singleton to emit an event for indexing. + * @param _to Destination address of module transaction. + * @param _value Ether value of module transaction. + * @param _data Data payload of module transaction. + * @param _operation Operation type of module transaction. + * @return _success Boolean flag indicating if the call succeeded. + */ + function execTransactionFromModule( + address _to, + uint256 _value, + bytes memory _data, + Enum.Operation _operation + ) external virtual returns (bool _success); + + /** + * @dev Set a guard that checks transactions before execution + * This can only be done via a Safe transaction. + * ⚠️ IMPORTANT: Since a guard has full power to block Safe transaction execution, + * a broken guard can cause a denial of service for the Safe. Make sure to carefully + * audit the guard code and design recovery mechanisms. + * @notice Set Transaction Guard `guard` for the Safe. Make sure you trust the guard. + * @param guard The address of the guard to be used or the 0 address to disable the guard + */ + function setGuard(address guard) external; + + /** + * @notice Enables the module `module` for the Safe. + * @dev This can only be done via a Safe transaction. + * @param module Module to be whitelisted. + */ + function enableModule(address module) external; +} diff --git a/solidity/test/unit/GuardCallbackModule.t.sol b/solidity/test/unit/GuardCallbackModule.t.sol new file mode 100644 index 0000000..220e1d7 --- /dev/null +++ b/solidity/test/unit/GuardCallbackModule.t.sol @@ -0,0 +1,72 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity >=0.8.4 <0.9.0; + +import {Test} from 'forge-std/Test.sol'; +import {GuardCallbackModule, IGuardCallbackModule} from 'contracts/GuardCallbackModule.sol'; +import {IStorageMirror} from 'interfaces/IStorageMirror.sol'; +import {ISafe} from 'interfaces/ISafe.sol'; +import {Enum} from 'safe-contracts/common/Enum.sol'; + +contract FakeSafe { + // solhint-disable + function execTransactionFromModule( + address _to, + uint256 _value, + bytes memory _data, + Enum.Operation _operation + ) external virtual returns (bool _success) { + _success = true; + // solhint-enable + } +} + +abstract contract Base is Test { + address internal _guard = makeAddr('guard'); + address internal _storageMirror = makeAddr('storageMirror'); + + GuardCallbackModule internal _guardCallbackModule = new GuardCallbackModule(_storageMirror, _guard); + FakeSafe internal _fakeSafe = new FakeSafe(); + + event EnabledModule(address _module); + event ChangedGuard(address _guard); +} + +contract UnitGuardCallbackModuel is Base { + function testSetGuard() public { + bytes memory _txData = abi.encodeWithSelector( + ISafe.execTransactionFromModule.selector, + address(_fakeSafe), + 0, + abi.encodeWithSelector(ISafe.setGuard.selector, _guard), + Enum.Operation.Call + ); + vm.prank(address(_fakeSafe)); + vm.expectCall(address(_fakeSafe), _txData); + _guardCallbackModule.setGuard(); + } + + function testSaveUpdatedSettingsMakesCall() public { + address[] memory _owners = new address[](1); + _owners[0] = address(_fakeSafe); + bytes memory _txData = abi.encodeWithSelector( + ISafe.execTransactionFromModule.selector, + _storageMirror, + 0, + abi.encodeWithSelector( + IStorageMirror.update.selector, + keccak256(abi.encode(IStorageMirror.SafeSettings({owners: _owners, threshold: 1}))) + ), + Enum.Operation.Call + ); + vm.prank(_guard); + vm.expectCall(address(_fakeSafe), _txData); + _guardCallbackModule.saveUpdatedSettings( + address(_fakeSafe), keccak256(abi.encode(IStorageMirror.SafeSettings({owners: _owners, threshold: 1}))) + ); + } + + function testSaveUpdatedSettingsRevertsIfNotCalledFromGuard(bytes32 _fakeData) public { + vm.expectRevert(IGuardCallbackModule.OnlyGuard.selector); + _guardCallbackModule.saveUpdatedSettings(address(_fakeSafe), _fakeData); + } +} From 7e653b446659054e81e4151bdbf3fc76ca5f871c Mon Sep 17 00:00:00 2001 From: 0xOneTony <112496816+0xOneTony@users.noreply.github.com> Date: Thu, 16 Nov 2023 17:44:28 +0200 Subject: [PATCH 09/29] feat: e2e base (#5) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # 🤖 Linear Closes SAF-24 --- solidity/contracts/GuardCallbackModule.sol | 2 - solidity/interfaces/IGuardCallbackModule.sol | 4 - solidity/interfaces/ISafe.sol | 128 +++++++++++++++++- solidity/test/e2e/Common.sol | 110 ++++++++++++++- solidity/test/e2e/IGnosisSafeProxyFactory.sol | 30 ++++ solidity/test/e2e/Test.t.sol | 14 ++ .../test/utils/ContractDeploymentAddress.sol | 33 +++++ solidity/test/utils/TestConstants.sol | 8 ++ 8 files changed, 314 insertions(+), 15 deletions(-) create mode 100644 solidity/test/e2e/IGnosisSafeProxyFactory.sol create mode 100644 solidity/test/e2e/Test.t.sol create mode 100644 solidity/test/utils/ContractDeploymentAddress.sol create mode 100644 solidity/test/utils/TestConstants.sol diff --git a/solidity/contracts/GuardCallbackModule.sol b/solidity/contracts/GuardCallbackModule.sol index 7c43eeb..d84d532 100644 --- a/solidity/contracts/GuardCallbackModule.sol +++ b/solidity/contracts/GuardCallbackModule.sol @@ -23,7 +23,6 @@ contract GuardCallbackModule is IGuardCallbackModule { /** * @notice Initates the module by setting the guard. */ - function setGuard() external { ISafe(msg.sender).execTransactionFromModule( msg.sender, 0, abi.encodeWithSelector(ISafe.setGuard.selector, GUARD), Enum.Operation.Call @@ -36,7 +35,6 @@ contract GuardCallbackModule is IGuardCallbackModule { * @param _safe The address of the safe. * @param _settingsHash The hash of the new settings for the safe. */ - function saveUpdatedSettings(address _safe, bytes32 _settingsHash) external { if (msg.sender != GUARD) revert OnlyGuard(); bytes memory _txData = abi.encodeWithSelector(IStorageMirror.update.selector, _settingsHash); diff --git a/solidity/interfaces/IGuardCallbackModule.sol b/solidity/interfaces/IGuardCallbackModule.sol index 3297c30..235bb11 100644 --- a/solidity/interfaces/IGuardCallbackModule.sol +++ b/solidity/interfaces/IGuardCallbackModule.sol @@ -18,13 +18,11 @@ interface IGuardCallbackModule { /** * @notice The address of the StorageMirror contract. */ - function STORAGE_MIRROR() external view returns (address _storageMirror); /** * @notice The address of the UpdateStorageMirrorGuard contract. */ - function GUARD() external view returns (address _guard); /*/////////////////////////////////////////////////////////////// @@ -37,12 +35,10 @@ interface IGuardCallbackModule { * @param _safe The address of the safe. * @param _settingsHash The hash of the new settings for the safe. */ - function saveUpdatedSettings(address _safe, bytes32 _settingsHash) external; /** * @notice Initates the module by setting the guard. */ - function setGuard() external; } diff --git a/solidity/interfaces/ISafe.sol b/solidity/interfaces/ISafe.sol index 882255c..9908996 100644 --- a/solidity/interfaces/ISafe.sol +++ b/solidity/interfaces/ISafe.sol @@ -18,7 +18,41 @@ interface ISafe { uint256 _value, bytes memory _data, Enum.Operation _operation - ) external virtual returns (bool _success); + ) external returns (bool _success); + + /** + * @notice Executes a `operation` {0: Call, 1: DelegateCall}} transaction to `to` with `value` (Native Currency) + * and pays `gasPrice` * `gasLimit` in `gasToken` token to `refundReceiver`. + * @dev The fees are always transferred, even if the user transaction fails. + * This method doesn't perform any sanity check of the transaction, such as: + * - if the contract at `to` address has code or not + * - if the `gasToken` is a contract or not + * It is the responsibility of the caller to perform such checks. + * @param _to Destination address of Safe transaction. + * @param _value Ether value of Safe tsransaction. + * @param _data Data payload of Safe transaction. + * @param _operation Operation type of Safe transaction. + * @param _safeTxGas Gas that should be used for the Safe transaction. + * @param _baseGas Gas costs that are independent of the transaction execution(e.g. base transaction fee, signature check, payment of the refund) + * @param _gasPrice Gas price that should be used for the payment calculation. + * @param _gasToken Token address (or 0 if ETH) that is used for the payment. + * @param _refundReceiver Address of receiver of gas payment (or 0 if tx.origin). + * @param _signatures Signature data that should be verified. + * Can be packed ECDSA signature ({bytes32 r}{bytes32 s}{uint8 v}), contract signature (EIP-1271) or approved hash. + * @return _success Boolean indicating transaction's success. + */ + function execTransaction( + address _to, + uint256 _value, + bytes calldata _data, + Enum.Operation _operation, + uint256 _safeTxGas, + uint256 _baseGas, + uint256 _gasPrice, + address _gasToken, + address payable _refundReceiver, + bytes memory _signatures + ) external payable returns (bool _success); /** * @dev Set a guard that checks transactions before execution @@ -27,14 +61,98 @@ interface ISafe { * a broken guard can cause a denial of service for the Safe. Make sure to carefully * audit the guard code and design recovery mechanisms. * @notice Set Transaction Guard `guard` for the Safe. Make sure you trust the guard. - * @param guard The address of the guard to be used or the 0 address to disable the guard + * @param _guard The address of the guard to be used or the 0 address to disable the guard */ - function setGuard(address guard) external; + function setGuard(address _guard) external; /** * @notice Enables the module `module` for the Safe. * @dev This can only be done via a Safe transaction. - * @param module Module to be whitelisted. + * @param _module Module to be whitelisted. + */ + function enableModule(address _module) external; + + /** + * @notice Returns the number of required confirmations for a Safe transaction aka the threshold. + * @return _threshold Threshold number. + */ + function getThreshold() external view returns (uint256 _threshold); + + /** + * @notice Changes the threshold of the Safe to `_threshold`. + * @dev This can only be done via a Safe transaction. + * @param _threshold New threshold. */ - function enableModule(address module) external; + function changeThreshold(uint256 _threshold) external; + + /** + * @notice Returns a list of Safe owners. + * @return _owners Array of Safe owners. + */ + function getOwners() external view returns (address[] memory _owners); + + /** + * @notice Adds the owner `owner` to the Safe and updates the threshold to `_threshold`. + * @dev This can only be done via a Safe transaction. + * @param _owner New owner address. + * @param _threshold New threshold. + */ + function addOwnerWithThreshold(address _owner, uint256 _threshold) external; + + /** + * @notice Returns the nonce of the safe + * @return _nonce Current nonce. + */ + function nonce() external view returns (uint256 _nonce); + + /** + * @notice Sets an initial storage of the Safe contract. + * @dev This method can only be called once. + * If a proxy was created without setting up, anyone can call setup and claim the proxy. + * @param _owners List of Safe owners. + * @param _threshold Number of required confirmations for a Safe transaction. + * @param _to Contract address for optional delegate call. + * @param _data Data payload for optional delegate call. + * @param _fallbackHandler Handler for fallback calls to this contract + * @param _paymentToken Token that should be used for the payment (0 is ETH) + * @param _payment Value that should be paid + * @param _paymentReceiver Address that should receive the payment (or 0 if tx.origin) + */ + function setup( + address[] calldata _owners, + uint256 _threshold, + address _to, + bytes calldata _data, + address _fallbackHandler, + address _paymentToken, + uint256 _payment, + address payable _paymentReceiver + ) external; + + /** + * @notice Returns the pre-image of the transaction hash (see getTransactionHash). + * @param _to Destination address. + * @param _value Ether value. + * @param _data Data payload. + * @param _operation Operation type. + * @param _safeTxGas Gas that should be used for the safe transaction. + * @param _baseGas Gas costs for that are independent of the transaction execution(e.g. base transaction fee, signature check, payment of the refund) + * @param _gasPrice Maximum gas price that should be used for this transaction. + * @param _gasToken Token address (or 0 if ETH) that is used for the payment. + * @param _refundReceiver Address of receiver of gas payment (or 0 if tx.origin). + * @param _nonce Transaction nonce. + * @return _tx Transaction hash bytes. + */ + function encodeTransactionData( + address _to, + uint256 _value, + bytes calldata _data, + Enum.Operation _operation, + uint256 _safeTxGas, + uint256 _baseGas, + uint256 _gasPrice, + address _gasToken, + address _refundReceiver, + uint256 _nonce + ) external view returns (bytes memory _tx); } diff --git a/solidity/test/e2e/Common.sol b/solidity/test/e2e/Common.sol index c826103..a0a42ae 100644 --- a/solidity/test/e2e/Common.sol +++ b/solidity/test/e2e/Common.sol @@ -3,14 +3,116 @@ pragma solidity =0.8.19; import {DSTestPlus} from '@defi-wonderland/solidity-utils/solidity/test/DSTestPlus.sol'; import {IERC20} from 'isolmate/interfaces/tokens/IERC20.sol'; +import {SafeProxy} from 'safe-contracts/proxies/SafeProxy.sol'; +import {Enum} from 'safe-contracts/common/Enum.sol'; +import {StorageMirror} from 'contracts/StorageMirror.sol'; +import {UpdateStorageMirrorGuard} from 'contracts/UpdateStorageMirrorGuard.sol'; +import {GuardCallbackModule} from 'contracts/GuardCallbackModule.sol'; +import {IGuardCallbackModule} from 'interfaces/IGuardCallbackModule.sol'; +import {ISafe} from 'interfaces/ISafe.sol'; +import {IGnosisSafeProxyFactory} from 'test/e2e/IGnosisSafeProxyFactory.sol'; +import {TestConstants} from 'test/utils/TestConstants.sol'; +import {ContractDeploymentAddress} from 'test/utils/ContractDeploymentAddress.sol'; -contract CommonE2EBase is DSTestPlus { +contract CommonE2EBase is DSTestPlus, TestConstants { uint256 internal constant _FORK_BLOCK = 15_452_788; - address internal _user = makeAddr('user'); - address internal _owner = makeAddr('owner'); + address public deployer = makeAddr('deployer'); + address public proposer = makeAddr('proposer'); + address public safeOwner; + uint256 public safeOwnerKey; - function setUp() public { + StorageMirror public storageMirror; + UpdateStorageMirrorGuard public updateStorageMirrorGuard; + GuardCallbackModule public guardCallbackModule; + ISafe public safe; + IGnosisSafeProxyFactory public gnosisSafeProxyFactory = IGnosisSafeProxyFactory(GNOSIS_SAFE_PROXY_FACTORY); + + function setUp() public virtual { vm.createSelectFork(vm.rpcUrl('mainnet'), _FORK_BLOCK); + + // Make address and key of safe owner + (safeOwner, safeOwnerKey) = makeAddrAndKey('safeOwner'); + + vm.prank(safeOwner); + safe = ISafe(address(gnosisSafeProxyFactory.createProxy(GNOSIS_SAFE_SINGLETON, ''))); // safeOwner nonce 0 + label(address(safe), 'SafeProxy'); + + address _updateStorageMirrorGuardTheoriticalAddress = ContractDeploymentAddress.addressFrom(deployer, 2); + + vm.prank(deployer); + storageMirror = new StorageMirror(); // deployer nonce 0 + label(address(storageMirror), 'StorageMirror'); + + vm.prank(deployer); + guardCallbackModule = new GuardCallbackModule(address(storageMirror), _updateStorageMirrorGuardTheoriticalAddress); // deployer nonce 1 + label(address(guardCallbackModule), 'GuardCallbackModule'); + + vm.prank(deployer); + updateStorageMirrorGuard = new UpdateStorageMirrorGuard(guardCallbackModule); // deployer nonce 2 + label(address(updateStorageMirrorGuard), 'UpdateStorageMirrorGuard'); + + // Make sure the theoritical address was calculated correctly + assert(address(updateStorageMirrorGuard) == _updateStorageMirrorGuardTheoriticalAddress); + + // Set up safe + address[] memory _owners = new address[](1); + _owners[0] = safeOwner; + vm.prank(safeOwner); // safeOwner nonce 1 + safe.setup(_owners, 1, address(safe), bytes(''), address(0), address(0), 0, payable(0)); + + // Enable guard callback module + enableModule(safe, safeOwner, safeOwnerKey, address(guardCallbackModule)); + + // data to sign and send to set the guard + bytes memory _setGuardData = abi.encodeWithSelector(IGuardCallbackModule.setGuard.selector); + bytes memory _setGuardEncodedTxData = safe.encodeTransactionData( + address(guardCallbackModule), 0, _setGuardData, Enum.Operation.Call, 0, 0, 0, address(0), payable(0), safe.nonce() + ); + + // signature + (uint8 _v, bytes32 _r, bytes32 _s) = vm.sign(safeOwnerKey, keccak256(_setGuardEncodedTxData)); + bytes memory _setGuardSignature = abi.encodePacked(_r, _s, _v); + + // execute setup of guard + vm.prank(safeOwner); + safe.execTransaction( + address(guardCallbackModule), + 0, + _setGuardData, + Enum.Operation.Call, + 0, + 0, + 0, + address(0), + payable(0), + _setGuardSignature + ); + } + + /** + * @notice Enables a module for the given safe + * @param _safe The safe that will enable the module + * @param _safeOwner The address of the owner of the safe + * @param _safeOwnerKey The private key to sign the tx + * @param _module The module address to enable + */ + function enableModule(ISafe _safe, address _safeOwner, uint256 _safeOwnerKey, address _module) public { + uint256 _safeNonce = _safe.nonce(); + // data to sign to enable module + bytes memory _enableModuleData = abi.encodeWithSelector(ISafe.enableModule.selector, address(_module)); + bytes memory _enableModuleEncodedTxData = _safe.encodeTransactionData( + address(_safe), 0, _enableModuleData, Enum.Operation.Call, 0, 0, 0, address(0), payable(0), _safeNonce + ); + + // signature + (uint8 _v, bytes32 _r, bytes32 _s) = vm.sign(_safeOwnerKey, keccak256(_enableModuleEncodedTxData)); + bytes memory _enableModuleSignature = abi.encodePacked(_r, _s, _v); + + // execute enable module + vm.prank(_safeOwner); + _safe.execTransaction( + address(_safe), 0, _enableModuleData, Enum.Operation.Call, 0, 0, 0, address(0), payable(0), _enableModuleSignature + ); } } diff --git a/solidity/test/e2e/IGnosisSafeProxyFactory.sol b/solidity/test/e2e/IGnosisSafeProxyFactory.sol new file mode 100644 index 0000000..6f29fea --- /dev/null +++ b/solidity/test/e2e/IGnosisSafeProxyFactory.sol @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity >=0.7.0 <0.9.0; + +import {SafeProxy} from 'safe-contracts/proxies/SafeProxy.sol'; +import {IProxyCreationCallback} from 'safe-contracts/proxies/IProxyCreationCallback.sol'; + +interface IGnosisSafeProxyFactory { + event ProxyCreation(SafeProxy _proxy, address _singleton); + + function createProxy(address _singleton, bytes memory _data) external returns (SafeProxy _proxy); + + function createProxyWithNonce( + address _singleton, + bytes memory _initializer, + uint256 _saltNonce + ) external returns (SafeProxy _proxy); + + function createProxyWithCallback( + address _singleton, + bytes memory _initializer, + uint256 _saltNonce, + IProxyCreationCallback _callback + ) external returns (SafeProxy _proxy); + + function calculateCreateProxyWithNonceAddress( + address _singleton, + bytes calldata _initializer, + uint256 _saltNonce + ) external returns (SafeProxy _proxy); +} diff --git a/solidity/test/e2e/Test.t.sol b/solidity/test/e2e/Test.t.sol new file mode 100644 index 0000000..8462d82 --- /dev/null +++ b/solidity/test/e2e/Test.t.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity >=0.8.4 <0.9.0; + +import {CommonE2EBase} from 'test/e2e/Common.sol'; + +contract TestE2E is CommonE2EBase { + function setUp() public override { + super.setUp(); + } + + function test_test() public { + assertTrue(true); + } +} diff --git a/solidity/test/utils/ContractDeploymentAddress.sol b/solidity/test/utils/ContractDeploymentAddress.sol new file mode 100644 index 0000000..3f06fe7 --- /dev/null +++ b/solidity/test/utils/ContractDeploymentAddress.sol @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity =0.8.19; + +library ContractDeploymentAddress { + /// @notice compute the future address where a contract will be deployed, based on the deployer nonce and address + /// see https://ethereum.stackexchange.com/questions/24248/how-to-calculate-an-ethereum-contracts-address-during-its-creation-using-the-so + /// @dev this works for standard CREATE deployment + /// @param _origin the deployer address + /// @param _nonce the deployer nonce for which the corresponding address is computed + /// @return _address the deployment address + function addressFrom(address _origin, uint256 _nonce) internal pure returns (address _address) { + bytes memory _data; + if (_nonce == 0x00) { + _data = abi.encodePacked(bytes1(0xd6), bytes1(0x94), _origin, bytes1(0x80)); + } else if (_nonce <= 0x7f) { + _data = abi.encodePacked(bytes1(0xd6), bytes1(0x94), _origin, uint8(_nonce)); + } else if (_nonce <= 0xff) { + _data = abi.encodePacked(bytes1(0xd7), bytes1(0x94), _origin, bytes1(0x81), uint8(_nonce)); + } else if (_nonce <= 0xffff) { + _data = abi.encodePacked(bytes1(0xd8), bytes1(0x94), _origin, bytes1(0x82), uint16(_nonce)); + } else if (_nonce <= 0xffffff) { + _data = abi.encodePacked(bytes1(0xd9), bytes1(0x94), _origin, bytes1(0x83), uint24(_nonce)); + } else { + _data = abi.encodePacked(bytes1(0xda), bytes1(0x94), _origin, bytes1(0x84), uint32(_nonce)); + } + + bytes32 _hash = keccak256(_data); + assembly { + mstore(0, _hash) + _address := mload(0) + } + } +} diff --git a/solidity/test/utils/TestConstants.sol b/solidity/test/utils/TestConstants.sol new file mode 100644 index 0000000..84504e4 --- /dev/null +++ b/solidity/test/utils/TestConstants.sol @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity =0.8.19; + +contract TestConstants { + address public constant GNOSIS_SAFE_PROXY_FACTORY = 0xa6B71E26C5e0845f74c812102Ca7114b6a896AB2; + address public constant GNOSIS_SAFE_SINGLETON = 0xd9Db270c1B5E3Bd161E8c8503c55cEABeE709552; + address public constant GNOSIS_SAFE_SINGLETON_L2 = 0x3E5c63644E683549055b9Be8653de26E0B4CD36E; +} From 5abe1c2a8665f0a64b5f4d505f015b0e5cfd7a43 Mon Sep 17 00:00:00 2001 From: 0xOneTony <112496816+0xOneTony@users.noreply.github.com> Date: Mon, 20 Nov 2023 13:50:41 +0200 Subject: [PATCH 10/29] chore: update readme (#7) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # 🤖 Linear Closes SAF-33 --- README.md | 114 ++++++++++++------------------------------------------ 1 file changed, 25 insertions(+), 89 deletions(-) diff --git a/README.md b/README.md index 8518283..17f6105 100644 --- a/README.md +++ b/README.md @@ -1,107 +1,43 @@ -wonderland banner -
+# Safe Liveness -
Start your next Solidity project with Foundry in seconds
-
A highly scalable foundation focused on DX and best practices
+⚠️ The code has not been audited yet, tread with caution. -
+## Overview -## Features +Safe-Liveness is a module that will tackle the liveness problem, one of the main challenges faced by smart wallets to improve cross-chain user experience. -
-
Sample contracts
-
Basic Greeter contract with an external interface.
+Unlike EOAs, smart wallets have configuration settings, which can cause synchronization problems across chains. Consequently, SAFEs on different chains function as separate contracts, even though they may share the same address and configuration parameters during deployment. This problem becomes critical when there’s a change in the owners’ list. -
Foundry setup
-
Foundry configuration with multiple custom profiles and remappings.
- -
Deployment scripts
-
Sample scripts to deploy contracts on both mainnet and testnet.
- -
Sample e2e & unit tests
-
Example tests showcasing mocking, assertions and configuration for mainnet forking. As well it includes everything needed in order to check code coverage.
- -
Linter
-
Simple and fast solidity linting thanks to forge fmt.
- -
Github workflows CI
-
Run all tests and see the coverage as you push your changes.
-
+We will create a module that can verify Safe ownership based on a storage proof, allowing you to easily broadcast any changes in your Safe to other chains. ## Setup -1. Install Foundry by following the instructions from [their repository](https://github.com/foundry-rs/foundry#installation). -2. Copy the `.env.example` file to `.env` and fill in the variables. -3. Install the dependencies by running: `yarn install`. In case there is an error with the commands, run `foundryup` and try them again. +This project uses [Foundry](https://book.getfoundry.sh/). To build it locally, run: -## Build - -The default way to build the code is suboptimal but fast, you can run it via: - -```bash +```sh +git clone git@github.com:defi-wonderland/safe-liveness.git +cd safe-liveness +yarn install yarn build ``` -In order to build a more optimized code ([via IR](https://docs.soliditylang.org/en/v0.8.15/ir-breaking-changes.html#solidity-ir-based-codegen-changes)), run: - -```bash -yarn build:optimized -``` - -## Running tests - -Unit tests should be isolated from any externalities, while E2E usually run in a fork of the blockchain. In this boilerplate you will find example of both. - -In order to run both unit and E2E tests, run: - -```bash -yarn test -``` - -In order to just run unit tests, run: +### Available Commands -```bash -yarn test:unit -``` +Make sure to set `MAINNET_RPC` environment variable before running end-to-end tests. -In order to run unit tests and run way more fuzzing than usual (5x), run: +| Yarn Command | Description | +| ----------------------- | ---------------------------------------------------------- | +| `yarn build` | Compile all contracts. | +| `yarn coverage` | See `forge coverage` report. | +| `yarn deploy` | Deploy the contracts to Mainnet. | +| `yarn test` | Run all unit and e2e tests. | +| `yarn test:unit` | Run unit tests. | +| `yarn test:e2e` | Run e2e tests. | -```bash -yarn test:unit:deep -``` +## Contributors -In order to just run e2e tests, run: - -```bash -yarn test:e2e -``` - -In order to check your current code coverage, run: - -```bash -yarn coverage -``` - -
- -## Deploy & verify - -### Setup - -Configure the `.env` variables. - -### Goerli - -```bash -yarn deploy:goerli -``` - -### Mainnet - -```bash -yarn deploy:mainnet -``` +Safe-Liveness was built with ❤️ by [Wonderland](https://defi.sucks). -The deployments are stored in ./broadcast +Wonderland is a team of top Web3 researchers, developers, and operators who believe that the future needs to be open-source, permissionless, and decentralized. -See the [Foundry Book for available options](https://book.getfoundry.sh/reference/forge/forge-create.html). +[DeFi sucks](https://defi.sucks), but Wonderland is here to make it better. From 3e911f7bf5ccf79870f60b89d760fb5a0db6685a Mon Sep 17 00:00:00 2001 From: 0xOneTony <112496816+0xOneTony@users.noreply.github.com> Date: Mon, 20 Nov 2023 14:26:08 +0200 Subject: [PATCH 11/29] feat: oracle contract (#6) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # 🤖 Linear Closes SAF-29 --- solidity/contracts/BlockHeaderOracle.sol | 46 ++++++++++++++++++++++++ solidity/test/e2e/Common.sol | 9 +++++ solidity/test/unit/MockOracle.t.sol | 39 ++++++++++++++++++++ 3 files changed, 94 insertions(+) create mode 100644 solidity/contracts/BlockHeaderOracle.sol create mode 100644 solidity/test/unit/MockOracle.t.sol diff --git a/solidity/contracts/BlockHeaderOracle.sol b/solidity/contracts/BlockHeaderOracle.sol new file mode 100644 index 0000000..cc082ff --- /dev/null +++ b/solidity/contracts/BlockHeaderOracle.sol @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity =0.8.19; + +/** + * @title BlockHeaderOracle + * @notice This contract's purpose is to return the latest stored L1 block header and timestamp + * @notice Every X minutes a "magical" off-chain agent provides the latest block header and timestamp + */ +contract BlockHeaderOracle { + /** + * @notice Emits when the block header and timestamp are updated + */ + event BlockHeaderUpdated(bytes _blockHeader, uint256 _blockTimestamp, uint256 _blockNumber); + + /** + * @notice The block header + */ + bytes public blockHeader; + + /** + * @notice The block timestamp of the latest block header + */ + uint256 public blockTimestamp; + + /** + * @notice Updates the block header and timestamp + * @param _blockHeader The block header + * @param _blockTimestamp The block timestamp + * @param _blockNumber The block number + */ + function updateBlockHeader(bytes memory _blockHeader, uint256 _blockTimestamp, uint256 _blockNumber) external { + blockHeader = _blockHeader; + blockTimestamp = _blockTimestamp; + + emit BlockHeaderUpdated(_blockHeader, _blockTimestamp, _blockNumber); + } + + /** + * @notice Returns the latest block header and timestamp + * @return _blockHeader The block header + * @return _blockTimestamp The block timestamp + */ + function getLatestBlockHeader() external view returns (bytes memory _blockHeader, uint256 _blockTimestamp) { + return (blockHeader, blockTimestamp); + } +} diff --git a/solidity/test/e2e/Common.sol b/solidity/test/e2e/Common.sol index a0a42ae..e5c2fe7 100644 --- a/solidity/test/e2e/Common.sol +++ b/solidity/test/e2e/Common.sol @@ -5,11 +5,15 @@ import {DSTestPlus} from '@defi-wonderland/solidity-utils/solidity/test/DSTestPl import {IERC20} from 'isolmate/interfaces/tokens/IERC20.sol'; import {SafeProxy} from 'safe-contracts/proxies/SafeProxy.sol'; import {Enum} from 'safe-contracts/common/Enum.sol'; + import {StorageMirror} from 'contracts/StorageMirror.sol'; import {UpdateStorageMirrorGuard} from 'contracts/UpdateStorageMirrorGuard.sol'; import {GuardCallbackModule} from 'contracts/GuardCallbackModule.sol'; +import {BlockHeaderOracle} from 'contracts/BlockHeaderOracle.sol'; + import {IGuardCallbackModule} from 'interfaces/IGuardCallbackModule.sol'; import {ISafe} from 'interfaces/ISafe.sol'; + import {IGnosisSafeProxyFactory} from 'test/e2e/IGnosisSafeProxyFactory.sol'; import {TestConstants} from 'test/utils/TestConstants.sol'; import {ContractDeploymentAddress} from 'test/utils/ContractDeploymentAddress.sol'; @@ -25,6 +29,7 @@ contract CommonE2EBase is DSTestPlus, TestConstants { StorageMirror public storageMirror; UpdateStorageMirrorGuard public updateStorageMirrorGuard; GuardCallbackModule public guardCallbackModule; + BlockHeaderOracle public oracle; ISafe public safe; IGnosisSafeProxyFactory public gnosisSafeProxyFactory = IGnosisSafeProxyFactory(GNOSIS_SAFE_PROXY_FACTORY); @@ -52,6 +57,10 @@ contract CommonE2EBase is DSTestPlus, TestConstants { updateStorageMirrorGuard = new UpdateStorageMirrorGuard(guardCallbackModule); // deployer nonce 2 label(address(updateStorageMirrorGuard), 'UpdateStorageMirrorGuard'); + vm.prank(deployer); + oracle = new BlockHeaderOracle(); // deployer nonce 3 + label(address(oracle), 'MockOracle'); + // Make sure the theoritical address was calculated correctly assert(address(updateStorageMirrorGuard) == _updateStorageMirrorGuardTheoriticalAddress); diff --git a/solidity/test/unit/MockOracle.t.sol b/solidity/test/unit/MockOracle.t.sol new file mode 100644 index 0000000..7f586b0 --- /dev/null +++ b/solidity/test/unit/MockOracle.t.sol @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity >=0.8.4 <0.9.0; + +import {Test} from 'forge-std/Test.sol'; +import {BlockHeaderOracle} from 'contracts/BlockHeaderOracle.sol'; + +abstract contract Base is Test { + event BlockHeaderUpdated(bytes _blockHeader, uint256 _blockTimestamp, uint256 _blockNumber); + + BlockHeaderOracle public oracle; + + function setUp() public { + oracle = new BlockHeaderOracle(); + } +} + +contract UnitBlockHeaderOracle is Base { + function testUpdateBlockHeader(bytes memory _blockHeader, uint256 _blockTimestamp, uint256 _blockNumber) public { + vm.expectEmit(true, true, true, true); + emit BlockHeaderUpdated(_blockHeader, _blockTimestamp, _blockNumber); + oracle.updateBlockHeader(_blockHeader, _blockTimestamp, _blockNumber); + + assertEq(_blockHeader, oracle.blockHeader(), 'Block header should be saved'); + assertEq(_blockTimestamp, oracle.blockTimestamp(), 'Block timestamp should be saved'); + } + + function testGetLatestBlockHeader() public { + bytes memory _blockHeader = '0x1234'; + uint256 _blockTimestamp = 1234; + uint256 _blockNumber = 1234; + + oracle.updateBlockHeader(_blockHeader, _blockTimestamp, _blockNumber); + + (bytes memory _savedBlockHeader, uint256 _savedBlockTimestamp) = oracle.getLatestBlockHeader(); + + assertEq(_blockHeader, _savedBlockHeader, 'Block header should be saved'); + assertEq(_blockTimestamp, _savedBlockTimestamp, 'Block timestamp should be saved'); + } +} From 2343d594cf5af48f8e4f3b7bc9c9ddc934e56f7f Mon Sep 17 00:00:00 2001 From: 0xOneTony <112496816+0xOneTony@users.noreply.github.com> Date: Tue, 21 Nov 2023 15:23:36 +0200 Subject: [PATCH 12/29] chore: improve readme (#12) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # 🤖 Linear Closes SAF-XXX --- README.md | 23 +++++++++++++++++++ .../contracts/UpdateStorageMirrorGuard.sol | 2 +- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 17f6105..c0214a7 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,29 @@ Make sure to set `MAINNET_RPC` environment variable before running end-to-end te | `yarn test:unit` | Run unit tests. | | `yarn test:e2e` | Run e2e tests. | + +## Smart Contracts + +### Home Chain +- `UpdateStorageMirrorGuard`: This guard is responsible for calling the GuardCallbackModule when a change in the settings of a safe is executed. +- `GuardCallbackModule`: This contract is a module that is used to save the updated settings to the StorageMirror. +- `StorageMirror`: This contract is a storage of information about the safe’s settings. All safe’s settings changes should be mirrored in this contract and be saved. In the end, this contract’s storage root is gonna be used to see if a proposed update on the non-home chain is valid. + +### Non-Home Chain +- `BlockHeaderOracle`: This contract's purpose is to return the latest stored L1 block header and timestamp. Every X minutes a "magical" off-chain agent provides the latest block header and timestamp. +- `NeedsUpdateGuard`: This guard should prevent the safe from executing any transaction if an update is needed. An update is needed based on the owner's security settings that was inputed. +- `VerifierModule`: This contract is the verifier module that verifies the settings of a safe against the StorageMirror on the home chain. +- `StorageMirrorRootRegistry`: This contract should accept and store storageRoots of the StorageMirror contract in L1. + + +## ⚠️ Warnings + +The project is a PoC implementation and should be treated with caution. Bellow we describe some cases that should be taken into account before using the modules/guard. + +- `UpdateStorageMirrorGuard` for the PoC this guard is calling the `GuardCallbackModule` in every call. A possible improvement would be to decode the txData, on the guard `checkTransaction` pre-execute hook, and filter against certain function signatures that change the settings of a Safe to accurately catch the change. +- `NeedsUpdateGuard` this guard on the non-home chain can brick the user's safe, since it will block every tx, if their security settings expire. Also it's worth mentioning that before using the guard the safe owner must verify at least 1 set of settings using the VerifierModule in order for the guard to have a point of reference for the latest verified update. +- `VerifierModule` is executing a safeTx after the verification and update of their settings. This safeTx can become invalid since the signatures passed were created before the change of the settings, in this case the user(s) will need to re-sign the tx manually outside of the UI. A possible improvement would be to have a custom safe app that let's you sign even if you are not a "current owner" but are a "potential future owner" of the "soon-to-be-updated" settings + ## Contributors Safe-Liveness was built with ❤️ by [Wonderland](https://defi.sucks). diff --git a/solidity/contracts/UpdateStorageMirrorGuard.sol b/solidity/contracts/UpdateStorageMirrorGuard.sol index 087af43..c232d0a 100644 --- a/solidity/contracts/UpdateStorageMirrorGuard.sol +++ b/solidity/contracts/UpdateStorageMirrorGuard.sol @@ -8,7 +8,7 @@ import {IStorageMirror} from 'interfaces/IStorageMirror.sol'; /** * @title UpdateStorageMirrorGuard - * @notice This guard is responsible for calling the GuardCallbackModule when a change in settings of a safe is executed. + * @notice This guard is responsible for calling the GuardCallbackModule when a change in the settings of a safe is executed. */ contract UpdateStorageMirrorGuard is BaseGuard { /** From 0210608fb5bdee392d5aa526e7c667ae7f94467d Mon Sep 17 00:00:00 2001 From: 0xOneTony <112496816+0xOneTony@users.noreply.github.com> Date: Tue, 21 Nov 2023 15:47:07 +0200 Subject: [PATCH 13/29] feat: needs-update-guard (#8) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # 🤖 Linear Closes SAF-26, SAF-27, SAF-28 --- solidity/contracts/NeedsUpdateGuard.sol | 81 +++++++++++++++++++ solidity/interfaces/IVerifierModule.sol | 15 ++++ solidity/test/e2e/Common.sol | 48 ++++++++++-- solidity/test/unit/NeedsUpdateGuard.t.sol | 94 +++++++++++++++++++++++ 4 files changed, 233 insertions(+), 5 deletions(-) create mode 100644 solidity/contracts/NeedsUpdateGuard.sol create mode 100644 solidity/interfaces/IVerifierModule.sol create mode 100644 solidity/test/unit/NeedsUpdateGuard.t.sol diff --git a/solidity/contracts/NeedsUpdateGuard.sol b/solidity/contracts/NeedsUpdateGuard.sol new file mode 100644 index 0000000..db55f28 --- /dev/null +++ b/solidity/contracts/NeedsUpdateGuard.sol @@ -0,0 +1,81 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity =0.8.19; + +import {BaseGuard} from 'safe-contracts/base/GuardManager.sol'; +import {Enum} from 'safe-contracts/common/Enum.sol'; +import {IVerifierModule} from 'interfaces/IVerifierModule.sol'; + +/** + * @title NeedsUpdateGuard + * @notice This guard should prevent the safe from executing any transaction if an update is needed. + * @notice An update is needed based on the safe owner's time inputed on how long an update is trusted. + */ +contract NeedsUpdateGuard is BaseGuard { + /** + * @notice Emits when the owner changes the trustLatestUpdateForSeconds + * @param _safe The address of the safe + * @param _trustLatestUpdateForSeconds The new trustLatestUpdateForSeconds, how many seconds the safe trusts the last update + */ + event TrustLatestUpdateForSecondsChanged(address indexed _safe, uint256 _trustLatestUpdateForSeconds); + + /** + * @notice Throws if the safe needs an update + */ + error NeedsUpdateGuard_NeedsUpdate(); + + /** + * @notice The verifier module + */ + IVerifierModule public immutable VERIFIER_MODULE; + + /** + * @notice How many seconds the safe trusts the last update + */ + mapping(address => uint256) public trustLatestUpdateForSeconds; + + constructor(IVerifierModule _verifierModule) { + VERIFIER_MODULE = _verifierModule; + } + + /** + * @notice Guard hook that is called before a Safe transaction is executed + * @dev This function should revert if the safe needs an update + * @dev WARNING: This can brick the safe if the owner doesn't update the settings or change the trustLatestUpdateForSeconds + */ + // solhint-disable no-unused-vars + function checkTransaction( + address _to, + uint256 _value, + bytes memory _data, + Enum.Operation _operation, + uint256 _safeTxGas, + uint256 _baseGas, + uint256 _gasPrice, + address _gasToken, + address payable _refundReceiver, + bytes memory _signatures, + address _msgSender + ) external { + uint256 _lastVerifiedUpdateTimestamp = VERIFIER_MODULE.latestVerifiedSettingsTimestamp(_msgSender); + uint256 _trustLatestUpdateForSeconds = trustLatestUpdateForSeconds[_msgSender]; + + if (_lastVerifiedUpdateTimestamp + _trustLatestUpdateForSeconds < block.timestamp) { + revert NeedsUpdateGuard_NeedsUpdate(); + } + } + + /** + * @notice Guard hook that is called after a Safe transaction is executed + */ + function checkAfterExecution(bytes32 _txHash, bool _success) external {} + + /** + * @notice Should update the safe's trustLatestUpdateForSeconds + * @dev This function should be called by the safe + * @param _trustLatestUpdateForSeconds The new trustLatestUpdateForSeconds, how many seconds the safe trusts the last verified update + */ + function updateTrustLatestUpdateForSeconds(uint256 _trustLatestUpdateForSeconds) external { + trustLatestUpdateForSeconds[msg.sender] = _trustLatestUpdateForSeconds; + emit TrustLatestUpdateForSecondsChanged(msg.sender, _trustLatestUpdateForSeconds); + } +} diff --git a/solidity/interfaces/IVerifierModule.sol b/solidity/interfaces/IVerifierModule.sol new file mode 100644 index 0000000..08d4509 --- /dev/null +++ b/solidity/interfaces/IVerifierModule.sol @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity =0.8.19; + +interface IVerifierModule { + /*/////////////////////////////////////////////////////////////// + VARIABLES + //////////////////////////////////////////////////////////////*/ + + /** + * @notice The timestamp when the latest settings were verified + * @param _safe The address of the safe + * @return _timestamp The timestamp + */ + function latestVerifiedSettingsTimestamp(address _safe) external view returns (uint256 _timestamp); +} diff --git a/solidity/test/e2e/Common.sol b/solidity/test/e2e/Common.sol index e5c2fe7..508f5b3 100644 --- a/solidity/test/e2e/Common.sol +++ b/solidity/test/e2e/Common.sol @@ -10,9 +10,11 @@ import {StorageMirror} from 'contracts/StorageMirror.sol'; import {UpdateStorageMirrorGuard} from 'contracts/UpdateStorageMirrorGuard.sol'; import {GuardCallbackModule} from 'contracts/GuardCallbackModule.sol'; import {BlockHeaderOracle} from 'contracts/BlockHeaderOracle.sol'; +import {NeedsUpdateGuard} from 'contracts/NeedsUpdateGuard.sol'; import {IGuardCallbackModule} from 'interfaces/IGuardCallbackModule.sol'; import {ISafe} from 'interfaces/ISafe.sol'; +import {IVerifierModule} from 'interfaces/IVerifierModule.sol'; import {IGnosisSafeProxyFactory} from 'test/e2e/IGnosisSafeProxyFactory.sol'; import {TestConstants} from 'test/utils/TestConstants.sol'; @@ -26,11 +28,17 @@ contract CommonE2EBase is DSTestPlus, TestConstants { address public safeOwner; uint256 public safeOwnerKey; + address public nonHomeChainSafeOwner; + uint256 public nonHomeChainSafeOwnerKey; + StorageMirror public storageMirror; UpdateStorageMirrorGuard public updateStorageMirrorGuard; GuardCallbackModule public guardCallbackModule; BlockHeaderOracle public oracle; + NeedsUpdateGuard public needsUpdateGuard; ISafe public safe; + ISafe public nonHomeChainSafe; + IVerifierModule public verifierModule = IVerifierModule(makeAddr('verifierModule')); IGnosisSafeProxyFactory public gnosisSafeProxyFactory = IGnosisSafeProxyFactory(GNOSIS_SAFE_PROXY_FACTORY); function setUp() public virtual { @@ -38,7 +46,10 @@ contract CommonE2EBase is DSTestPlus, TestConstants { // Make address and key of safe owner (safeOwner, safeOwnerKey) = makeAddrAndKey('safeOwner'); + // Make address and key of non home chain safe owner + (nonHomeChainSafeOwner, nonHomeChainSafeOwnerKey) = makeAddrAndKey('nonHomeChainSafeOwner'); + /// =============== HOME CHAIN =============== vm.prank(safeOwner); safe = ISafe(address(gnosisSafeProxyFactory.createProxy(GNOSIS_SAFE_SINGLETON, ''))); // safeOwner nonce 0 label(address(safe), 'SafeProxy'); @@ -57,14 +68,10 @@ contract CommonE2EBase is DSTestPlus, TestConstants { updateStorageMirrorGuard = new UpdateStorageMirrorGuard(guardCallbackModule); // deployer nonce 2 label(address(updateStorageMirrorGuard), 'UpdateStorageMirrorGuard'); - vm.prank(deployer); - oracle = new BlockHeaderOracle(); // deployer nonce 3 - label(address(oracle), 'MockOracle'); - // Make sure the theoritical address was calculated correctly assert(address(updateStorageMirrorGuard) == _updateStorageMirrorGuardTheoriticalAddress); - // Set up safe + // Set up owner home chain safe address[] memory _owners = new address[](1); _owners[0] = safeOwner; vm.prank(safeOwner); // safeOwner nonce 1 @@ -97,6 +104,37 @@ contract CommonE2EBase is DSTestPlus, TestConstants { payable(0), _setGuardSignature ); + + /// =============== NON HOME CHAIN =============== + // Set up non home chain safe + vm.prank(nonHomeChainSafeOwner); + nonHomeChainSafe = ISafe(address(gnosisSafeProxyFactory.createProxy(GNOSIS_SAFE_SINGLETON, ''))); // nonHomeChainSafeOwner nonce 0 + label(address(nonHomeChainSafe), 'NonHomeChainSafeProxy'); + + // Deploy non home chain contracts + vm.prank(deployer); + oracle = new BlockHeaderOracle(); // deployer nonce 3 + label(address(oracle), 'MockOracle'); + + // vm.prank(deployer); + // verifierModule = new VerifierModule(..); // deployer nonce 4 + // label(address(verifierModule), 'VerifierModule'); + + vm.prank(deployer); + needsUpdateGuard = new NeedsUpdateGuard(verifierModule); // deployer nonce 5 + label(address(needsUpdateGuard), 'NeedsUpdateGuard'); + + // set up non home chain safe + address[] memory _nonHomeChainSafeOwners = new address[](1); + _nonHomeChainSafeOwners[0] = nonHomeChainSafeOwner; + vm.prank(nonHomeChainSafeOwner); // nonHomeChainSafeOwner nonce 1 + nonHomeChainSafe.setup( + _nonHomeChainSafeOwners, 1, address(nonHomeChainSafe), bytes(''), address(0), address(0), 0, payable(0) + ); + + // enable verifier module + + // set needs update guard } /** diff --git a/solidity/test/unit/NeedsUpdateGuard.t.sol b/solidity/test/unit/NeedsUpdateGuard.t.sol new file mode 100644 index 0000000..5523829 --- /dev/null +++ b/solidity/test/unit/NeedsUpdateGuard.t.sol @@ -0,0 +1,94 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity >=0.8.4 <0.9.0; + +import {Test} from 'forge-std/Test.sol'; +import {Enum} from 'safe-contracts/common/Enum.sol'; +import {NeedsUpdateGuard} from 'contracts/NeedsUpdateGuard.sol'; +import {IVerifierModule} from 'interfaces/IVerifierModule.sol'; + +abstract contract Base is Test { + event TrustLatestUpdateForSecondsChanged(address indexed _safe, uint256 _trustLatestUpdateForSeconds); + + error NeedsUpdateGuard_NeedsUpdate(); + + address public safe; + IVerifierModule public verifierModule; + NeedsUpdateGuard public needsUpdateGuard; + + function setUp() public { + safe = makeAddr('safe'); + verifierModule = IVerifierModule(makeAddr('verifierModule')); + needsUpdateGuard = new NeedsUpdateGuard(verifierModule); + // Warp to 2022-01-01 00:00:00 UTC + vm.warp(1_641_070_800); + } +} + +contract UnitNeedsUpdateGuard is Base { + function testCheckTransaction(address _to, uint256 _value, bytes memory _data) public { + // Set trustLatestUpdateForSeconds + vm.prank(safe); + needsUpdateGuard.updateTrustLatestUpdateForSeconds(200); + + // Mock latest verified settings timestamp to current timestamp - 100 + uint256 _currentTimeStamp = block.timestamp; + vm.mockCall( + address(verifierModule), + abi.encodeWithSelector(IVerifierModule.latestVerifiedSettingsTimestamp.selector, safe), + abi.encode(_currentTimeStamp - 100) + ); + + vm.expectCall( + address(verifierModule), abi.encodeWithSelector(IVerifierModule.latestVerifiedSettingsTimestamp.selector, safe) + ); + + vm.prank(safe); + needsUpdateGuard.checkTransaction( + _to, _value, _data, Enum.Operation.Call, 0, 0, 0, address(0), payable(0), '', safe + ); + } + + function testCheckTransactionReverts(address _to, uint256 _value, bytes memory _data) public { + // Set trustLatestUpdateForSeconds + vm.prank(safe); + needsUpdateGuard.updateTrustLatestUpdateForSeconds(200); + + // Mock latest verified settings timestamp to current timestamp - 1_000 to make the check fail + uint256 _currentTimeStamp = block.timestamp; + vm.mockCall( + address(verifierModule), + abi.encodeWithSelector(IVerifierModule.latestVerifiedSettingsTimestamp.selector, safe), + abi.encode(_currentTimeStamp - 1000) + ); + + vm.expectCall( + address(verifierModule), abi.encodeWithSelector(IVerifierModule.latestVerifiedSettingsTimestamp.selector, safe) + ); + + vm.expectRevert(abi.encodeWithSelector(NeedsUpdateGuard.NeedsUpdateGuard_NeedsUpdate.selector)); + + vm.prank(safe); + needsUpdateGuard.checkTransaction( + _to, _value, _data, Enum.Operation.Call, 0, 0, 0, address(0), payable(0), '', safe + ); + } + + function testCheckAfterExecution(bytes32 _txHash) public { + vm.prank(safe); + needsUpdateGuard.checkAfterExecution(_txHash, true); + } + + function testUpdateTrustLatestUpdateForSeconds(uint256 _newTrustLatestUpdateForSeconds) public { + vm.expectEmit(true, true, true, true); + emit TrustLatestUpdateForSecondsChanged(safe, _newTrustLatestUpdateForSeconds); + + vm.prank(safe); + needsUpdateGuard.updateTrustLatestUpdateForSeconds(_newTrustLatestUpdateForSeconds); + + assertEq( + needsUpdateGuard.trustLatestUpdateForSeconds(safe), + _newTrustLatestUpdateForSeconds, + 'Should update trustLatestUpdateForSeconds' + ); + } +} From 955bf40eb4e4c3382d9a5427d6fcbdeef19eaebb Mon Sep 17 00:00:00 2001 From: excaliborr <124819095+excaliborr@users.noreply.github.com> Date: Tue, 21 Nov 2023 09:57:07 -0500 Subject: [PATCH 14/29] feat: verifier module (#9) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # 🤖 Linear Closes SAF-34 Closes SAF-35 Closes SAF-36 --- package.json | 1 + remappings.txt | 4 +- solidity/contracts/BlockHeaderOracle.sol | 9 +- solidity/contracts/VerifierModule.sol | 288 +++++++ solidity/interfaces/IBlockHeaderOracle.sol | 47 ++ solidity/interfaces/ISafe.sol | 24 + .../interfaces/IStorageMirrorRootRegistry.sol | 25 + solidity/interfaces/IVerifierModule.sol | 101 ++- .../libraries/MerklePatriciaProofVerifier.sol | 250 ++++++ solidity/libraries/StateVerifier.sol | 50 ++ solidity/test/unit/VerifierModule.t.sol | 763 ++++++++++++++++++ yarn.lock | 4 + 12 files changed, 1557 insertions(+), 9 deletions(-) create mode 100644 solidity/contracts/VerifierModule.sol create mode 100644 solidity/interfaces/IBlockHeaderOracle.sol create mode 100644 solidity/interfaces/IStorageMirrorRootRegistry.sol create mode 100644 solidity/libraries/MerklePatriciaProofVerifier.sol create mode 100644 solidity/libraries/StateVerifier.sol create mode 100644 solidity/test/unit/VerifierModule.t.sol diff --git a/package.json b/package.json index 73b4170..c75b302 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,7 @@ }, "dependencies": { "@defi-wonderland/solidity-utils": "0.0.0-3e9c8e8b", + "Solidity-RLP": "github:hamdiallam/Solidity-RLP", "ds-test": "github:dapphub/ds-test#e282159", "forge-std": "github:foundry-rs/forge-std#v1.5.6", "isolmate": "github:defi-wonderland/isolmate#59e1804", diff --git a/remappings.txt b/remappings.txt index bcb55ae..549815a 100644 --- a/remappings.txt +++ b/remappings.txt @@ -4,7 +4,9 @@ forge-std/=node_modules/forge-std/src isolmate/=node_modules/isolmate/src safe-contracts/=node_modules/safe-contracts/contracts @defi-wonderland/=node_modules/@defi-wonderland/ +solidity-rlp/=node_modules/Solidity-RLP contracts/=solidity/contracts interfaces/=solidity/interfaces -test/=solidity/test \ No newline at end of file +test/=solidity/test +libraries/=solidity/libraries \ No newline at end of file diff --git a/solidity/contracts/BlockHeaderOracle.sol b/solidity/contracts/BlockHeaderOracle.sol index cc082ff..4e92dff 100644 --- a/solidity/contracts/BlockHeaderOracle.sol +++ b/solidity/contracts/BlockHeaderOracle.sol @@ -1,17 +1,14 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity =0.8.19; +import {IBlockHeaderOracle} from 'interfaces/IBlockHeaderOracle.sol'; + /** * @title BlockHeaderOracle * @notice This contract's purpose is to return the latest stored L1 block header and timestamp * @notice Every X minutes a "magical" off-chain agent provides the latest block header and timestamp */ -contract BlockHeaderOracle { - /** - * @notice Emits when the block header and timestamp are updated - */ - event BlockHeaderUpdated(bytes _blockHeader, uint256 _blockTimestamp, uint256 _blockNumber); - +contract BlockHeaderOracle is IBlockHeaderOracle { /** * @notice The block header */ diff --git a/solidity/contracts/VerifierModule.sol b/solidity/contracts/VerifierModule.sol new file mode 100644 index 0000000..80d01e7 --- /dev/null +++ b/solidity/contracts/VerifierModule.sol @@ -0,0 +1,288 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity =0.8.19; + +import {Enum} from 'safe-contracts/common/Enum.sol'; +import {RLPReader} from 'solidity-rlp/contracts/RLPReader.sol'; +import {IStorageMirror} from 'interfaces/IStorageMirror.sol'; +import {MerklePatriciaProofVerifier} from 'libraries/MerklePatriciaProofVerifier.sol'; +import {StateVerifier} from 'libraries/StateVerifier.sol'; +import {IStorageMirrorRootRegistry} from 'interfaces/IStorageMirrorRootRegistry.sol'; +import {IBlockHeaderOracle} from 'interfaces/IBlockHeaderOracle.sol'; +import {IVerifierModule} from 'interfaces/IVerifierModule.sol'; +import {ISafe} from 'interfaces/ISafe.sol'; + +/** + * @title VerifierModule + * @notice This contract is the verifier module that verifies the settings of a safe against the StorageMirror on the home chain + */ +contract VerifierModule is IVerifierModule { + using RLPReader for RLPReader.RLPItem; + using RLPReader for bytes; + + /** + * @notice The start of the linked list for the owners of a safe + * @dev Used for updating the owners of a safe + */ + address internal constant _SENTINEL_OWNERS = address(0x1); + + /** + * @notice The slot of the mapping of the safe to the keccak256 hash of the latest verified settings in the StorageMirror + * @dev This constant is used to access the mapping location from the storage mirror '0' is the slot of the mapping + */ + uint256 internal constant _LATEST_VERIFIED_SETTINGS_SLOT = 0; + + /** + * @notice The interface of the StorageMirrorRootRegistry contract + */ + IStorageMirrorRootRegistry public immutable STORAGE_MIRROR_ROOT_REGISTRY; + + /** + * @notice The address of the StorageMirror contract on the home chain + */ + address public immutable STORAGE_MIRROR; + + /** + * @notice The mapping of the safe to the keccak256 hash of the latest verified settings + */ + mapping(address => bytes32) public latestVerifiedSettings; + + /** + * @notice The mapping of the safe to the timestamp of when the settings where verified + */ + mapping(address => uint256) public latestVerifiedSettingsTimestamp; + + constructor(IStorageMirrorRootRegistry _storageMirrorRootRegistry, address _storageMirror) payable { + STORAGE_MIRROR_ROOT_REGISTRY = _storageMirrorRootRegistry; + STORAGE_MIRROR = _storageMirror; + } + + /** + * @notice Verifies the new settings that are incoming against a storage proof from the StorageMirror on the home chain + * + * @param _safe The address of the safe that has new settings + * @param _proposedSettings The new settings that are being proposed + * @param _storageMirrorStorageProof The storage proof of the StorageMirror contract on the home chain + * @param _arbitraryTxnParams The transaction parameters for the arbitrary safe transaction that will execute + */ + function proposeAndVerifyUpdate( + address _safe, + IStorageMirror.SafeSettings calldata _proposedSettings, + bytes memory _storageMirrorStorageProof, + SafeTxnParams calldata _arbitraryTxnParams + ) external { + _proposeAndVerifyUpdate(_safe, _proposedSettings, _storageMirrorStorageProof, _arbitraryTxnParams); + } + + /** + * @notice The function extracts the storage root of the StorageMirror contract from a given account proof + * + * @param _storageMirrorAccountProof The account proof of the StorageMirror contract from the latest block + * @param _blockHeader The block header of the latest block + * @return _storageRoot The verified storage root + * @return _blockNumber The block number from the _blockHeader + */ + function extractStorageMirrorStorageRoot( + bytes memory _storageMirrorAccountProof, + bytes memory _blockHeader + ) external view returns (bytes32 _storageRoot, uint256 _blockNumber) { + // Verify and parse the blockheader for the state root + StateVerifier.BlockHeader memory _parsedBlockHeader = StateVerifier.verifyBlockHeader(_blockHeader); + + // Verify the account proof against the state root + bytes memory _rlpAccount = MerklePatriciaProofVerifier.extractProofValue( + _parsedBlockHeader.stateRootHash, + abi.encodePacked(keccak256(abi.encode(STORAGE_MIRROR))), + _storageMirrorAccountProof.toRlpItem().toList() + ); + + // Extract the storage root from the output of the MPT + _storageRoot = StateVerifier.extractStorageRootFromAccount(_rlpAccount); + _blockNumber = _parsedBlockHeader.number; + } + + /** + * @notice Verifies the new settings that are incoming against a storage proof from the StorageMirror on the home chain + * + * @param _safe The address of the safe that has new settings + * @param _proposedSettings The new settings that are being proposed + * @param _storageMirrorStorageProof The storage proof of the StorageMirror contract on the home chain + * @param _arbitraryTxnParams The transaction parameters for the arbitrary safe transaction that will execute + */ + function _proposeAndVerifyUpdate( + address _safe, + IStorageMirror.SafeSettings calldata _proposedSettings, + bytes memory _storageMirrorStorageProof, + SafeTxnParams calldata _arbitraryTxnParams + ) internal { + bytes32 _hashedProposedSettings = _verifyNewSettings(_safe, _proposedSettings, _storageMirrorStorageProof); + + // If we dont revert from the _verifyNewSettings() call, then we can update the safe + + _updateLatestVerifiedSettings(_safe, _proposedSettings); + + // Call the arbitrary transaction + ISafe(_safe).execTransaction( + _arbitraryTxnParams.to, + _arbitraryTxnParams.value, + _arbitraryTxnParams.data, + _arbitraryTxnParams.operation, + _arbitraryTxnParams.safeTxGas, + _arbitraryTxnParams.baseGas, + _arbitraryTxnParams.gasPrice, + _arbitraryTxnParams.gasToken, + _arbitraryTxnParams.refundReceiver, + _arbitraryTxnParams.signatures + ); + + // Pay incentives + // TODO: Calculations for incentives so its not hardcoded to 1e18 + ISafe(_safe).execTransactionFromModule(msg.sender, 1e18, '', Enum.Operation.Call); + + // Make the storage updates at the end of the call to save gas in a revert scenario + latestVerifiedSettings[_safe] = _hashedProposedSettings; + latestVerifiedSettingsTimestamp[_safe] = block.timestamp; + + emit VerifiedUpdate(_safe, _hashedProposedSettings); + } + + /** + * @notice The function that verifies a given storage proof for the proposed settings + * + * @param _safe The address of the safe that has new settings + * @param _proposedSettings The new settings that are being proposed + * @param _storageMirrorStorageProof The storage proof of the StorageMirror contract on the home chain + * @return _hashedProposedSettings The keccak256 hash of the proposed settings + */ + function _verifyNewSettings( + address _safe, + IStorageMirror.SafeSettings memory _proposedSettings, + bytes memory _storageMirrorStorageProof + ) internal view virtual returns (bytes32 _hashedProposedSettings) { + bytes32 _latestStorageRoot = STORAGE_MIRROR_ROOT_REGISTRY.latestVerifiedStorageRoot(); + + // The slot of where the latest settings hash is stored in the storage mirror + bytes32 _safeSettingsSlot = keccak256(abi.encode(_safe, _LATEST_VERIFIED_SETTINGS_SLOT)); + + // Hash the storage slot + bytes32 _safeSettingsSlotHash = keccak256(abi.encode(_safeSettingsSlot)); + + // Turn the proof into a list to prepare it for input into the MPT + RLPReader.RLPItem[] memory _stack = _storageMirrorStorageProof.toRlpItem().toList(); + + // Use the MPT to get the value of the storage slot + bytes memory _slotValue = + MerklePatriciaProofVerifier.extractProofValue(_latestStorageRoot, abi.encodePacked(_safeSettingsSlotHash), _stack); + + // Convert the value into a bytes32 value, this should always fit because the slot should contain a keccak256 hash + bytes32 _hashedSavedSettings = _bytesToBytes32(_slotValue); + + // Hash the proposed settings + _hashedProposedSettings = keccak256(abi.encode(_proposedSettings)); + + // Verify the proposed settings match what is saved in the storage mirror + if (_hashedProposedSettings != _hashedSavedSettings) revert VerifierModule_SettingsDontMatch(); + } + + /** + * @notice The function that updates the safe with the latest verified settings + * + * @param _safe The address of the safe that has new settings + * @param _proposedSettings The new settings that are being updated to + */ + function _updateLatestVerifiedSettings( + address _safe, + IStorageMirror.SafeSettings calldata _proposedSettings + ) internal { + address[] memory _oldOwners = ISafe(_safe).getOwners(); + uint256 _newThreshold = _proposedSettings.threshold; + address[] memory _newOwners = _proposedSettings.owners; + bool _hasUpdatedThreshold; + + // NOTE: Threshold is automatically updated inside these calls if it needs to be updated + for (uint256 _i; _i < _newOwners.length;) { + if (!ISafe(_safe).isOwner(_newOwners[_i])) { + _hasUpdatedThreshold = true; + ISafe(_safe).execTransactionFromModule( + _safe, + 0, + abi.encodeWithSelector(ISafe.addOwnerWithThreshold.selector, _newOwners[_i], _newThreshold), + Enum.Operation.Call + ); + } + + unchecked { + ++_i; + } + } + + for (uint256 _i; _i < _oldOwners.length;) { + if (!_linearSearchOwners(_oldOwners[_i], _newOwners)) { + _hasUpdatedThreshold = true; + ISafe(_safe).execTransactionFromModule( + _safe, + 0, + abi.encodeWithSelector( + ISafe.removeOwner.selector, + _oldOwners[_i], + int256(_i) - 1 < 0 ? _SENTINEL_OWNERS : _oldOwners[_i - 1], + _newThreshold + ), + Enum.Operation.Call + ); + } + + unchecked { + ++_i; + } + } + + // If the threshold has not been updated, then we need to check if it needs to be updated + if (!_hasUpdatedThreshold) { + uint256 _oldThreshold = ISafe(_safe).getThreshold(); + + if (_oldThreshold != _newThreshold) { + ISafe(_safe).execTransactionFromModule( + _safe, 0, abi.encodeWithSelector(ISafe.changeThreshold.selector, _newThreshold), Enum.Operation.Call + ); + } + } + } + + /** + * @notice The function that linearly searches an array of addresses for a given address + * + * @param _owner The address to search for + * @param _owners The array of addresses to search through + * @return _result If the address was found or not + */ + function _linearSearchOwners(address _owner, address[] memory _owners) internal pure returns (bool _result) { + for (uint256 _i; _i < _owners.length;) { + if (_owners[_i] == _owner) { + _result = true; + break; + } + + unchecked { + ++_i; + } + } + } + + /** + * @notice Helpers function to convert bytes to bytes32 + * + * @param _source The bytes to convert + * @return _result The bytes32 variable + */ + function _bytesToBytes32(bytes memory _source) internal pure returns (bytes32 _result) { + // Ensure the source data is 32 bytes or less + + // Sanity check the keccak256() of the security settings should always fit in 32 bytes + if (_source.length > 32) revert VerifierModule_BytesToBytes32Failed(); + + // Copy the data into the bytes32 variable + assembly { + _result := mload(add(_source, 32)) + } + } +} diff --git a/solidity/interfaces/IBlockHeaderOracle.sol b/solidity/interfaces/IBlockHeaderOracle.sol new file mode 100644 index 0000000..0e175c1 --- /dev/null +++ b/solidity/interfaces/IBlockHeaderOracle.sol @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity =0.8.19; + +interface IBlockHeaderOracle { + /*/////////////////////////////////////////////////////////////// + EVENTS + //////////////////////////////////////////////////////////////*/ + + /** + * @notice Emits when the block header and timestamp are updated + */ + event BlockHeaderUpdated(bytes _blockHeader, uint256 _blockTimestamp, uint256 _blockNumber); + + /*/////////////////////////////////////////////////////////////// + VARIABLES + //////////////////////////////////////////////////////////////*/ + + /** + * @notice The block header + * @return _blockheader The block header + */ + function blockHeader() external view returns (bytes memory _blockheader); + + /** + * @notice The block timestamp of the latest block header + * @return _blockTimestamp The block timestamp + */ + function blockTimestamp() external view returns (uint256 _blockTimestamp); + + /*/////////////////////////////////////////////////////////////// + LOGIC + //////////////////////////////////////////////////////////////*/ + /** + * @notice Updates the block header and timestamp + * @param _blockHeader The block header + * @param _blockTimestamp The block timestamp + * @param _blockNumber The block number + */ + function updateBlockHeader(bytes memory _blockHeader, uint256 _blockTimestamp, uint256 _blockNumber) external; + + /** + * @notice Returns the latest block header and timestamp + * @return _blockHeader The block header + * @return _blockTimestamp The block timestamp + */ + function getLatestBlockHeader() external view returns (bytes memory _blockHeader, uint256 _blockTimestamp); +} diff --git a/solidity/interfaces/ISafe.sol b/solidity/interfaces/ISafe.sol index 9908996..9741604 100644 --- a/solidity/interfaces/ISafe.sol +++ b/solidity/interfaces/ISafe.sol @@ -99,6 +99,30 @@ interface ISafe { */ function addOwnerWithThreshold(address _owner, uint256 _threshold) external; + /** + * @notice Removes the owner `owner` from the Safe and updates the threshold to `_threshold`. + * @dev This can only be done via a Safe transaction. + * @param _prevOwner Owner that pointed to the owner to be removed in the linked list + * @param _owner Owner address to be removed. + * @param _threshold New threshold. + */ + function removeOwner(address _prevOwner, address _owner, uint256 _threshold) external; + + /** + * @notice Replaces the owner `oldOwner` in the Safe with `newOwner`. + * @dev This can only be done via a Safe transaction. + * @param _prevOwner Owner that pointed to the owner to be replaced in the linked list + * @param _oldOwner Owner address to be replaced. + * @param _newOwner New owner address. + */ + function swapOwner(address _prevOwner, address _oldOwner, address _newOwner) external; + + /** + * @notice Returns if `owner` is an owner of the Safe. + * @return _result if owner is an owner of the Safe. + */ + function isOwner(address _owner) external view returns (bool _result); + /** * @notice Returns the nonce of the safe * @return _nonce Current nonce. diff --git a/solidity/interfaces/IStorageMirrorRootRegistry.sol b/solidity/interfaces/IStorageMirrorRootRegistry.sol new file mode 100644 index 0000000..0f4bc39 --- /dev/null +++ b/solidity/interfaces/IStorageMirrorRootRegistry.sol @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity =0.8.19; + +interface IStorageMirrorRootRegistry { + /*/////////////////////////////////////////////////////////////// + VARIABLES + //////////////////////////////////////////////////////////////*/ + + /** + * @notice The latest verified storage root + */ + function latestVerifiedStorageRoot() external view returns (bytes32 _latestVerifiedStorageRoot); + + /*/////////////////////////////////////////////////////////////// + LOGIC + //////////////////////////////////////////////////////////////*/ + + /** + * @notice Users can use to propose and verify a storage root of the StorageMirror contract in Home chain + * @dev Calls queryL1BlockHeader to get the block header of the Home chain + * @dev Call verifier module for the actual verificationn + * @param _accountProof The account proof of the StorageMirror contract in Home chain + */ + function proposeAndVerifyStorageMirrorStorageRoot(bytes memory _accountProof) external; +} diff --git a/solidity/interfaces/IVerifierModule.sol b/solidity/interfaces/IVerifierModule.sol index 08d4509..de9bc9f 100644 --- a/solidity/interfaces/IVerifierModule.sol +++ b/solidity/interfaces/IVerifierModule.sol @@ -1,15 +1,112 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity =0.8.19; +import {Enum} from 'safe-contracts/common/Enum.sol'; +import {IStorageMirror} from 'interfaces/IStorageMirror.sol'; +import {IStorageMirrorRootRegistry} from 'interfaces/IStorageMirrorRootRegistry.sol'; + interface IVerifierModule { + /*/////////////////////////////////////////////////////////////// + EVENTS + //////////////////////////////////////////////////////////////*/ + + event VerifiedUpdate(address _safe, bytes32 _verifiedHash); + + /*/////////////////////////////////////////////////////////////// + ERRORS + //////////////////////////////////////////////////////////////*/ + + /** + * @notice Reverts when the proposed settings dont match the saved settings on the StorageMirror + */ + error VerifierModule_SettingsDontMatch(); + + /** + * @notice Reverts when the bytes cannot be converted to bytes32 + */ + + error VerifierModule_BytesToBytes32Failed(); + + /*/////////////////////////////////////////////////////////////// + STRUCTS + //////////////////////////////////////////////////////////////*/ + + struct SafeTxnParams { + address to; + uint256 value; + bytes data; + Enum.Operation operation; + uint256 safeTxGas; + uint256 baseGas; + uint256 gasPrice; + address gasToken; + address payable refundReceiver; + bytes signatures; + } + /*/////////////////////////////////////////////////////////////// VARIABLES //////////////////////////////////////////////////////////////*/ /** - * @notice The timestamp when the latest settings were verified + * @notice The address of the StorageMirror contract on L1. + * + * @return _storageMirror The address of the StorageMirror contract. + */ + function STORAGE_MIRROR() external view returns (address _storageMirror); + + /** + * @notice The address of the StorageMirrorRootRegistry contract. + * + * @return _storageMirrorRootRegistry The interface of the StorageMirrorRootRegistry contract. + */ + function STORAGE_MIRROR_ROOT_REGISTRY() external view returns (IStorageMirrorRootRegistry _storageMirrorRootRegistry); + + /** + * @notice The hash of the latest verified settings for a given safe + * @param _safe The address of the safe + * + * @return _latestVerifiedSettings The hash of the latest verified settings + */ + function latestVerifiedSettings(address _safe) external view returns (bytes32 _latestVerifiedSettings); + + /** + * @notice The timestamp for when the settings were last updated for a given safe * @param _safe The address of the safe - * @return _timestamp The timestamp + * + * @return _timestamp The timestamp of when it was saved */ function latestVerifiedSettingsTimestamp(address _safe) external view returns (uint256 _timestamp); + + /*/////////////////////////////////////////////////////////////// + LOGIC + //////////////////////////////////////////////////////////////*/ + + /** + * @notice The function extracts the storage root of the StorageMirror contract from a given account proof + * + * @param _storageMirrorAccountProof The account proof of the StorageMirror contract from the latest block + * @param _blockHeader The block header of the latest block + * @return _storageRoot The verified storage root + * @return _blockNumber The block number from the _blockHeader + */ + function extractStorageMirrorStorageRoot( + bytes memory _storageMirrorAccountProof, + bytes memory _blockHeader + ) external view returns (bytes32 _storageRoot, uint256 _blockNumber); + + /** + * @notice Verifies the new settings that are incoming against a storage proof from the StorageMirror on the home chain + * + * @param _safe The address of the safe that has new settings + * @param _proposedSettings The new settings that are being proposed + * @param _storageMirrorStorageProof The storage proof of the StorageMirror contract on the home chain + * @param _arbitraryTxnParams The transaction parameters for the arbitrary safe transaction that will execute + */ + function proposeAndVerifyUpdate( + address _safe, + IStorageMirror.SafeSettings memory _proposedSettings, + bytes memory _storageMirrorStorageProof, + SafeTxnParams calldata _arbitraryTxnParams + ) external; } diff --git a/solidity/libraries/MerklePatriciaProofVerifier.sol b/solidity/libraries/MerklePatriciaProofVerifier.sol new file mode 100644 index 0000000..c1d090c --- /dev/null +++ b/solidity/libraries/MerklePatriciaProofVerifier.sol @@ -0,0 +1,250 @@ +// SPDX-License-Identifier: MIT + +/** + * Copied from https://github.com/lorenzb/proveth/blob/c74b20e/onchain/ProvethVerifier.sol + * with minor performance and code style-related modifications. + */ +pragma solidity 0.8.19; + +import {RLPReader} from 'solidity-rlp/contracts/RLPReader.sol'; + +library MerklePatriciaProofVerifier { + using RLPReader for RLPReader.RLPItem; + using RLPReader for bytes; + + /// @dev Validates a Merkle-Patricia-Trie proof. + /// If the proof proves the inclusion of some key-value pair in the + /// trie, the value is returned. Otherwise, i.e. if the proof proves + /// the exclusion of a key from the trie, an empty byte array is + /// returned. + /// @param rootHash is the Keccak-256 hash of the root node of the MPT. + /// @param path is the key of the node whose inclusion/exclusion we are + /// proving. + /// @param stack is the stack of MPT nodes (starting with the root) that + /// need to be traversed during verification. + /// @return value whose inclusion is proved or an empty byte array for + /// a proof of exclusion + function extractProofValue( + bytes32 rootHash, + bytes memory path, + RLPReader.RLPItem[] memory stack + ) internal pure returns (bytes memory value) { + bytes memory mptKey = _decodeNibbles(path, 0); + uint256 mptKeyOffset = 0; + + bytes32 nodeHashHash; + RLPReader.RLPItem[] memory node; + + RLPReader.RLPItem memory rlpValue; + + if (stack.length == 0) { + // Root hash of empty Merkle-Patricia-Trie + require(rootHash == 0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421); + return new bytes(0); + } + + // Traverse stack of nodes starting at root. + for (uint256 i = 0; i < stack.length; i++) { + // We use the fact that an rlp encoded list consists of some + // encoding of its length plus the concatenation of its + // *rlp-encoded* items. + + // The root node is hashed with Keccak-256 ... + if (i == 0 && rootHash != stack[i].rlpBytesKeccak256()) { + revert(); + } + // ... whereas all other nodes are hashed with the MPT + // hash function. + if (i != 0 && nodeHashHash != _mptHashHash(stack[i])) { + revert(); + } + // We verified that stack[i] has the correct hash, so we + // may safely decode it. + node = stack[i].toList(); + + if (node.length == 2) { + // Extension or Leaf node + + bool isLeaf; + bytes memory nodeKey; + (isLeaf, nodeKey) = _merklePatriciaCompactDecode(node[0].toBytes()); + + uint256 prefixLength = _sharedPrefixLength(mptKeyOffset, mptKey, nodeKey); + mptKeyOffset += prefixLength; + + if (prefixLength < nodeKey.length) { + // Proof claims divergent extension or leaf. (Only + // relevant for proofs of exclusion.) + // An Extension/Leaf node is divergent iff it 'skips' over + // the point at which a Branch node should have been had the + // excluded key been included in the trie. + // Example: Imagine a proof of exclusion for path [1, 4], + // where the current node is a Leaf node with + // path [1, 3, 3, 7]. For [1, 4] to be included, there + // should have been a Branch node at [1] with a child + // at 3 and a child at 4. + + // Sanity check + if (i < stack.length - 1) { + // divergent node must come last in proof + revert(); + } + + return new bytes(0); + } + + if (isLeaf) { + // Sanity check + if (i < stack.length - 1) { + // leaf node must come last in proof + revert(); + } + + if (mptKeyOffset < mptKey.length) { + return new bytes(0); + } + + rlpValue = node[1]; + return rlpValue.toBytes(); + } else { + // extension + // Sanity check + if (i == stack.length - 1) { + // shouldn't be at last level + revert(); + } + + if (!node[1].isList()) { + // rlp(child) was at least 32 bytes. node[1] contains + // Keccak256(rlp(child)). + nodeHashHash = node[1].payloadKeccak256(); + } else { + // rlp(child) was less than 32 bytes. node[1] contains + // rlp(child). + nodeHashHash = node[1].rlpBytesKeccak256(); + } + } + } else if (node.length == 17) { + // Branch node + + if (mptKeyOffset != mptKey.length) { + // we haven't consumed the entire path, so we need to look at a child + uint8 nibble = uint8(mptKey[mptKeyOffset]); + mptKeyOffset += 1; + if (nibble >= 16) { + // each element of the path has to be a nibble + revert(); + } + + if (_isEmptyBytesequence(node[nibble])) { + // Sanity + if (i != stack.length - 1) { + // leaf node should be at last level + revert(); + } + + return new bytes(0); + } else if (!node[nibble].isList()) { + nodeHashHash = node[nibble].payloadKeccak256(); + } else { + nodeHashHash = node[nibble].rlpBytesKeccak256(); + } + } else { + // we have consumed the entire mptKey, so we need to look at what's contained in this node. + + // Sanity + if (i != stack.length - 1) { + // should be at last level + revert(); + } + + return node[16].toBytes(); + } + } + } + } + + /// @dev Computes the hash of the Merkle-Patricia-Trie hash of the RLP item. + /// Merkle-Patricia-Tries use a weird 'hash function' that outputs + /// *variable-length* hashes: If the item is shorter than 32 bytes, + /// the MPT hash is the item. Otherwise, the MPT hash is the + /// Keccak-256 hash of the item. + /// The easiest way to compare variable-length byte sequences is + /// to compare their Keccak-256 hashes. + /// @param item The RLP item to be hashed. + /// @return Keccak-256(MPT-hash(item)) + function _mptHashHash(RLPReader.RLPItem memory item) private pure returns (bytes32) { + if (item.len < 32) { + return item.rlpBytesKeccak256(); + } else { + return keccak256(abi.encodePacked(item.rlpBytesKeccak256())); + } + } + + function _isEmptyBytesequence(RLPReader.RLPItem memory item) private pure returns (bool) { + if (item.len != 1) { + return false; + } + uint8 b; + uint256 memPtr = item.memPtr; + assembly { + b := byte(0, mload(memPtr)) + } + return b == 0x80; /* empty byte string */ + } + + function _merklePatriciaCompactDecode(bytes memory compact) private pure returns (bool isLeaf, bytes memory nibbles) { + require(compact.length > 0); + uint256 first_nibble = uint8(compact[0]) >> 4 & 0xF; + uint256 skipNibbles; + if (first_nibble == 0) { + skipNibbles = 2; + isLeaf = false; + } else if (first_nibble == 1) { + skipNibbles = 1; + isLeaf = false; + } else if (first_nibble == 2) { + skipNibbles = 2; + isLeaf = true; + } else if (first_nibble == 3) { + skipNibbles = 1; + isLeaf = true; + } else { + // Not supposed to happen! + revert(); + } + return (isLeaf, _decodeNibbles(compact, skipNibbles)); + } + + function _decodeNibbles(bytes memory compact, uint256 skipNibbles) private pure returns (bytes memory nibbles) { + require(compact.length > 0); + + uint256 length = compact.length * 2; + require(skipNibbles <= length); + length -= skipNibbles; + + nibbles = new bytes(length); + uint256 nibblesLength = 0; + + for (uint256 i = skipNibbles; i < skipNibbles + length; i += 1) { + if (i % 2 == 0) { + nibbles[nibblesLength] = bytes1((uint8(compact[i / 2]) >> 4) & 0xF); + } else { + nibbles[nibblesLength] = bytes1((uint8(compact[i / 2]) >> 0) & 0xF); + } + nibblesLength += 1; + } + + assert(nibblesLength == nibbles.length); + } + + function _sharedPrefixLength(uint256 xsOffset, bytes memory xs, bytes memory ys) private pure returns (uint256) { + uint256 i; + for (i = 0; i + xsOffset < xs.length && i < ys.length; i++) { + if (xs[i + xsOffset] != ys[i]) { + return i; + } + } + return i; + } +} diff --git a/solidity/libraries/StateVerifier.sol b/solidity/libraries/StateVerifier.sol new file mode 100644 index 0000000..3fe2c71 --- /dev/null +++ b/solidity/libraries/StateVerifier.sol @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity =0.8.19; + +import {RLPReader} from 'solidity-rlp/contracts/RLPReader.sol'; + +library StateVerifier { + using RLPReader for RLPReader.RLPItem; + using RLPReader for bytes; + + error InvalidBlockHeader(); + error InvalidAccount(); + + uint256 internal constant _HEADER_STATE_ROOT_INDEX = 3; + uint256 internal constant _HEADER_NUMBER_INDEX = 8; + + struct BlockHeader { + bytes32 hash; + bytes32 stateRootHash; + uint256 number; + } + + function verifyBlockHeader(bytes memory _rlpBlockHeader) + internal + pure + returns (BlockHeader memory _parsedBlockHeader) + { + RLPReader.RLPItem[] memory headerFields = _rlpBlockHeader.toRlpItem().toList(); + + // Sanity check to ensure that the block header is long enough to be valid + if (headerFields.length <= _HEADER_NUMBER_INDEX) revert InvalidBlockHeader(); + + _parsedBlockHeader.stateRootHash = bytes32(headerFields[_HEADER_STATE_ROOT_INDEX].toUint()); + _parsedBlockHeader.number = headerFields[_HEADER_NUMBER_INDEX].toUint(); + _parsedBlockHeader.hash = keccak256(_rlpBlockHeader); + } + + function extractStorageRootFromAccount(bytes memory _rlpAccount) internal pure returns (bytes32 _storageRoot) { + // Non-inclusive proof + if (_rlpAccount.length == 0) { + return bytes32(0); + } + + RLPReader.RLPItem[] memory _accountFields = _rlpAccount.toRlpItem().toList(); + + // Sanity check to ensure that the account verification happend as expected + if (_accountFields.length != 4) revert InvalidBlockHeader(); + + _storageRoot = bytes32(_accountFields[2].toUint()); + } +} diff --git a/solidity/test/unit/VerifierModule.t.sol b/solidity/test/unit/VerifierModule.t.sol new file mode 100644 index 0000000..43664a2 --- /dev/null +++ b/solidity/test/unit/VerifierModule.t.sol @@ -0,0 +1,763 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity >=0.8.4 <0.9.0; + +import {Test} from 'forge-std/Test.sol'; +import {RLPReader} from 'solidity-rlp/contracts/RLPReader.sol'; +import {Enum} from 'safe-contracts/common/Enum.sol'; +import {VerifierModule, IVerifierModule} from 'contracts/VerifierModule.sol'; +import {IBlockHeaderOracle} from 'interfaces/IBlockHeaderOracle.sol'; +import {IStorageMirror} from 'interfaces/IStorageMirror.sol'; +import {ISafe} from 'interfaces/ISafe.sol'; +import {IStorageMirrorRootRegistry} from 'interfaces/IStorageMirrorRootRegistry.sol'; +import {MerklePatriciaProofVerifier} from 'libraries/MerklePatriciaProofVerifier.sol'; +import {StateVerifier} from 'libraries/StateVerifier.sol'; + +contract TestMPT { + using RLPReader for RLPReader.RLPItem; + using RLPReader for bytes; + + function extractProofValue( + bytes32 _root, + bytes memory _key, + bytes memory _storageProof + ) external pure returns (bytes memory _value) { + // Need to create the RLP item internally due to memory collision + RLPReader.RLPItem[] memory _stack = _storageProof.toRlpItem().toList(); + _value = MerklePatriciaProofVerifier.extractProofValue(_root, _key, _stack); + } + + function verifyBlockHeader(bytes memory _rlpBlockHeader) + external + pure + returns (StateVerifier.BlockHeader memory _parsedBlockHeader) + { + _parsedBlockHeader = StateVerifier.verifyBlockHeader(_rlpBlockHeader); + } + + function extractStorageRootFromAccount(bytes memory _rlpAccount) external pure returns (bytes32 _storageRoot) { + _storageRoot = StateVerifier.extractStorageRootFromAccount(_rlpAccount); + } +} + +contract TestVerifierModule is VerifierModule { + TestMPT public mpt; + + constructor( + IStorageMirrorRootRegistry _storageMirrorRootRegistry, + address _storageMirror, + TestMPT _testMPT + ) VerifierModule(_storageMirrorRootRegistry, _storageMirror) { + mpt = _testMPT; + } + + // Matches the function but needed an external MPT caller to mock it + function verifyNewSettings( + address _safe, + IStorageMirror.SafeSettings memory _proposedSettings, + bytes memory _storageMirrorStorageProof + ) public view returns (bytes32 _hashedProposedSettings) { + bytes32 _latestStorageRoot = IStorageMirrorRootRegistry(STORAGE_MIRROR_ROOT_REGISTRY).latestVerifiedStorageRoot(); + + // The slot of where the latest settings hash is stored in the storage mirror + bytes32 _safeSettingsSlot = keccak256(abi.encode(_safe, 0)); + + bytes32 _safeSettingsSlotHash = keccak256(abi.encode(_safeSettingsSlot)); + + bytes memory _slotValue = + mpt.extractProofValue(_latestStorageRoot, abi.encodePacked(_safeSettingsSlotHash), _storageMirrorStorageProof); + + bytes32 _hashedSavedSettings = _bytesToBytes32(_slotValue); + + _hashedProposedSettings = keccak256(abi.encode(_proposedSettings)); + + if (_hashedProposedSettings != _hashedSavedSettings) revert VerifierModule_SettingsDontMatch(); + } + + function updateLatestVerifiedSettings(address _safe, IStorageMirror.SafeSettings calldata _proposedSettings) public { + _updateLatestVerifiedSettings(_safe, _proposedSettings); + } + + function bytesToBytes32(bytes memory _bytes) public pure returns (bytes32 _bytes32) { + _bytes32 = _bytesToBytes32(_bytes); + } + + // NOTE: Needs to match the proposeAndVerify function logic with the fake MPT because we cant mock librariers + function proposeAndVerifyUpdateTest( + address _safe, + IStorageMirror.SafeSettings calldata _proposedSettings, + bytes memory _storageMirrorStorageProof, + IVerifierModule.SafeTxnParams calldata _safeTxnParams + ) external { + bytes32 _hashedProposedSettings = verifyNewSettings(_safe, _proposedSettings, _storageMirrorStorageProof); + + // If we dont revert from the _verifyNewSettings() call, then we can update the safe + + updateLatestVerifiedSettings(_safe, _proposedSettings); + + // Call the arbitrary transaction + ISafe(_safe).execTransaction( + _safeTxnParams.to, + _safeTxnParams.value, + _safeTxnParams.data, + _safeTxnParams.operation, + _safeTxnParams.safeTxGas, + _safeTxnParams.baseGas, + _safeTxnParams.gasPrice, + _safeTxnParams.gasToken, + _safeTxnParams.refundReceiver, + _safeTxnParams.signatures + ); + + // Pay incentives + // TODO: Calculations for incentives so its not hardcoded to 1e18 + ISafe(_safe).execTransactionFromModule(msg.sender, 1e18, '', Enum.Operation.Call); + + // Make the storage updates at the end of the call to save gas in a revert scenario + latestVerifiedSettings[_safe] = _hashedProposedSettings; + latestVerifiedSettingsTimestamp[_safe] = block.timestamp; + + emit VerifiedUpdate(_safe, _hashedProposedSettings); + } + + // NOTE: Should match the function from the verifier but externalizes the library calls + function extractStorageMirrorStorageRootTest( + bytes memory _storageMirrorAccountProof, + bytes memory _blockHeader + ) external view returns (bytes32 _storageRoot, uint256 _blockNumber) { + // Verify and parse the blockheader for the state root + StateVerifier.BlockHeader memory _parsedBlockHeader = mpt.verifyBlockHeader(_blockHeader); + + // Verify the account proof against the state root + bytes memory _rlpAccount = mpt.extractProofValue( + _parsedBlockHeader.stateRootHash, + abi.encodePacked(keccak256(abi.encode(STORAGE_MIRROR))), + _storageMirrorAccountProof + ); + + // Extract the storage root from the output of the MPT + _storageRoot = mpt.extractStorageRootFromAccount(_rlpAccount); + _blockNumber = 500; + } +} + +abstract contract Base is Test { + IStorageMirrorRootRegistry internal _storageMirrorRegistry = + IStorageMirrorRootRegistry(makeAddr('storageMirrorRegistry')); + address internal _storageMirror = makeAddr('storageMirror'); + address internal _fakeSafe = makeAddr('fakeSafe'); + + TestMPT public mpt = new TestMPT(); + TestVerifierModule public verifierModule = new TestVerifierModule(_storageMirrorRegistry, _storageMirror, mpt); + + event VerifiedUpdate(address _safe, bytes32 _verifiedHash); +} + +contract UnitUpdateSettings is Base { + function testUpdateSettingsAddsOwners() public { + address[] memory _oldOwners = new address[](2); + _oldOwners[0] = address(0x2); + _oldOwners[1] = address(0x3); + + address[] memory _newOwners = new address[](3); + _newOwners[0] = address(0x2); + _newOwners[1] = address(0x3); + _newOwners[2] = address(0x4); + + IStorageMirror.SafeSettings memory _newSettings = IStorageMirror.SafeSettings({owners: _newOwners, threshold: 2}); + + vm.mockCall(_fakeSafe, abi.encodeWithSelector(ISafe.getOwners.selector), abi.encode(_oldOwners)); + + vm.mockCall(_fakeSafe, abi.encodeWithSelector(ISafe.getThreshold.selector), abi.encode(2)); + + vm.mockCall(_fakeSafe, abi.encodeWithSelector(ISafe.isOwner.selector), abi.encode(true)); + + vm.mockCall(_fakeSafe, abi.encodeWithSelector(ISafe.isOwner.selector, address(0x4)), abi.encode(false)); + + vm.mockCall( + _fakeSafe, + abi.encodeWithSelector( + ISafe.execTransactionFromModule.selector, + _fakeSafe, + 0, + abi.encodeWithSelector(ISafe.addOwnerWithThreshold.selector, address(0x4), 2), + Enum.Operation.Call + ), + abi.encode(true) + ); + vm.expectCall( + _fakeSafe, + abi.encodeWithSelector( + ISafe.execTransactionFromModule.selector, + _fakeSafe, + 0, + abi.encodeWithSelector(ISafe.addOwnerWithThreshold.selector, address(0x4), 2), + Enum.Operation.Call + ) + ); + + verifierModule.updateLatestVerifiedSettings(_fakeSafe, _newSettings); + } + + function testUpdateSettingsRemovesOwners() public { + address[] memory _newOwners = new address[](2); + _newOwners[0] = address(0x2); + _newOwners[1] = address(0x3); + + address[] memory _oldOwners = new address[](3); + _oldOwners[0] = address(0x2); + _oldOwners[1] = address(0x3); + _oldOwners[2] = address(0x4); + + IStorageMirror.SafeSettings memory _newSettings = IStorageMirror.SafeSettings({owners: _newOwners, threshold: 2}); + + vm.mockCall(_fakeSafe, abi.encodeWithSelector(ISafe.getOwners.selector), abi.encode(_oldOwners)); + + vm.mockCall(_fakeSafe, abi.encodeWithSelector(ISafe.getThreshold.selector), abi.encode(2)); + + vm.mockCall(_fakeSafe, abi.encodeWithSelector(ISafe.isOwner.selector), abi.encode(true)); + + vm.mockCall( + _fakeSafe, + abi.encodeWithSelector( + ISafe.execTransactionFromModule.selector, + _fakeSafe, + 0, + abi.encodeWithSelector(ISafe.removeOwner.selector, address(0x4), address(0x3), 2), + Enum.Operation.Call + ), + abi.encode(true) + ); + vm.expectCall( + _fakeSafe, + abi.encodeWithSelector( + ISafe.execTransactionFromModule.selector, + _fakeSafe, + 0, + abi.encodeWithSelector(ISafe.removeOwner.selector, address(0x4), address(0x3), 2), + Enum.Operation.Call + ) + ); + + verifierModule.updateLatestVerifiedSettings(_fakeSafe, _newSettings); + } + + function testRemovesOwnerIfPreviousOwnerIsSentinel() public { + address[] memory _newOwners = new address[](2); + _newOwners[0] = address(0x3); + _newOwners[1] = address(0x4); + + address[] memory _oldOwners = new address[](3); + _oldOwners[0] = address(0x2); + _oldOwners[1] = address(0x3); + _oldOwners[2] = address(0x4); + + IStorageMirror.SafeSettings memory _newSettings = IStorageMirror.SafeSettings({owners: _newOwners, threshold: 2}); + + vm.mockCall(_fakeSafe, abi.encodeWithSelector(ISafe.getOwners.selector), abi.encode(_oldOwners)); + + vm.mockCall(_fakeSafe, abi.encodeWithSelector(ISafe.getThreshold.selector), abi.encode(2)); + + vm.mockCall(_fakeSafe, abi.encodeWithSelector(ISafe.isOwner.selector), abi.encode(true)); + + vm.mockCall( + _fakeSafe, + abi.encodeWithSelector( + ISafe.execTransactionFromModule.selector, + _fakeSafe, + 0, + abi.encodeWithSelector(ISafe.removeOwner.selector, address(0x2), address(0x1), 2), + Enum.Operation.Call + ), + abi.encode(true) + ); + vm.expectCall( + _fakeSafe, + abi.encodeWithSelector( + ISafe.execTransactionFromModule.selector, + _fakeSafe, + 0, + abi.encodeWithSelector(ISafe.removeOwner.selector, address(0x2), address(0x1), 2), + Enum.Operation.Call + ) + ); + + verifierModule.updateLatestVerifiedSettings(_fakeSafe, _newSettings); + } + + function testWillChangeThresholdIfNoUpdateIsMade() public { + address[] memory _newOwners = new address[](3); + _newOwners[0] = address(0x3); + _newOwners[1] = address(0x4); + _newOwners[2] = address(0x2); + + address[] memory _oldOwners = new address[](3); + _oldOwners[0] = address(0x2); + _oldOwners[1] = address(0x3); + _oldOwners[2] = address(0x4); + + IStorageMirror.SafeSettings memory _newSettings = IStorageMirror.SafeSettings({owners: _newOwners, threshold: 3}); + + vm.mockCall(_fakeSafe, abi.encodeWithSelector(ISafe.getOwners.selector), abi.encode(_oldOwners)); + + vm.mockCall(_fakeSafe, abi.encodeWithSelector(ISafe.getThreshold.selector), abi.encode(2)); + + vm.mockCall(_fakeSafe, abi.encodeWithSelector(ISafe.isOwner.selector), abi.encode(true)); + + vm.mockCall( + _fakeSafe, + abi.encodeWithSelector( + ISafe.execTransactionFromModule.selector, + _fakeSafe, + 0, + abi.encodeWithSelector(ISafe.changeThreshold.selector, 3), + Enum.Operation.Call + ), + abi.encode(true) + ); + vm.expectCall( + _fakeSafe, + abi.encodeWithSelector( + ISafe.execTransactionFromModule.selector, + _fakeSafe, + 0, + abi.encodeWithSelector(ISafe.changeThreshold.selector, 3), + Enum.Operation.Call + ) + ); + + verifierModule.updateLatestVerifiedSettings(_fakeSafe, _newSettings); + } +} + +contract UnitMerklePatriciaTree is Base { + function testMPTIsCalledWithCorrectParams(IStorageMirror.SafeSettings memory _fakeSettings) public { + bytes32 _fakeStorageRoot = keccak256(abi.encode(bytes32(uint256(1)))); + + bytes memory _storageProof = hex'e10e2d527612073b26eecdfd717e6a320f'; + + vm.mockCall( + address(_storageMirrorRegistry), + abi.encodeWithSelector(IStorageMirrorRootRegistry.latestVerifiedStorageRoot.selector), + abi.encode(_fakeStorageRoot) + ); + + bytes32 _safeSettingsSlot = keccak256(abi.encode(_fakeSafe, 0)); + + bytes32 _safeSettingsSlotHash = keccak256(abi.encode(_safeSettingsSlot)); + + bytes memory _expectedOutput = abi.encodePacked(keccak256(abi.encode(_fakeSettings))); + + vm.mockCall( + address(mpt), + abi.encodeWithSelector( + TestMPT.extractProofValue.selector, _fakeStorageRoot, abi.encodePacked(_safeSettingsSlotHash), _storageProof + ), + abi.encode(_expectedOutput) + ); + + vm.expectCall( + address(mpt), + abi.encodeWithSelector( + TestMPT.extractProofValue.selector, _fakeStorageRoot, abi.encodePacked(_safeSettingsSlotHash), _storageProof + ) + ); + + bytes32 _hashedProposedSettings = verifierModule.verifyNewSettings(_fakeSafe, _fakeSettings, _storageProof); + + assertEq( + abi.encodePacked(_hashedProposedSettings), + abi.encodePacked(_expectedOutput), + 'Hashed proposed settings should match expected output' + ); + } + + function testMPTRevertsIfSettingsDontMatch(IStorageMirror.SafeSettings memory _fakeSettings) public { + vm.assume(_fakeSettings.owners.length > 1 && _fakeSettings.threshold >= 1 && _fakeSettings.owners[0] != address(0)); + + bytes32 _fakeStorageRoot = keccak256(abi.encode(bytes32(uint256(1)))); + + bytes memory _storageProof = hex'e10e2d527612073b26eecdfd717e6a320f'; + + vm.mockCall( + address(_storageMirrorRegistry), + abi.encodeWithSelector(IStorageMirrorRootRegistry.latestVerifiedStorageRoot.selector), + abi.encode(_fakeStorageRoot) + ); + + bytes32 _safeSettingsSlot = keccak256(abi.encode(_fakeSafe, 0)); + + bytes32 _safeSettingsSlotHash = keccak256(abi.encode(_safeSettingsSlot)); + + bytes memory _expectedOutput = + abi.encodePacked(keccak256(abi.encode(IStorageMirror.SafeSettings({owners: new address[](1), threshold: 1})))); + + vm.mockCall( + address(mpt), + abi.encodeWithSelector( + TestMPT.extractProofValue.selector, _fakeStorageRoot, abi.encodePacked(_safeSettingsSlotHash), _storageProof + ), + abi.encode(_expectedOutput) + ); + + vm.expectRevert(IVerifierModule.VerifierModule_SettingsDontMatch.selector); + verifierModule.verifyNewSettings(_fakeSafe, _fakeSettings, _storageProof); + } + + function testMPTRevertsIfOutputIsLongerThen32Bytes(IStorageMirror.SafeSettings memory _fakeSettings) public { + vm.assume(_fakeSettings.owners.length > 1 && _fakeSettings.threshold >= 1 && _fakeSettings.owners[0] != address(0)); + + bytes32 _fakeStorageRoot = keccak256(abi.encode(bytes32(uint256(1)))); + + bytes memory _storageProof = hex'e10e2d527612073b26eecdfd717e6a320f'; + + vm.mockCall( + address(_storageMirrorRegistry), + abi.encodeWithSelector(IStorageMirrorRootRegistry.latestVerifiedStorageRoot.selector), + abi.encode(_fakeStorageRoot) + ); + + bytes32 _safeSettingsSlot = keccak256(abi.encode(_fakeSafe, 0)); + + bytes32 _safeSettingsSlotHash = keccak256(abi.encode(_safeSettingsSlot)); + + bytes memory _expectedOutput = abi.encodePacked(keccak256(abi.encode(_fakeSettings)), uint256(18)); + + vm.mockCall( + address(mpt), + abi.encodeWithSelector( + TestMPT.extractProofValue.selector, _fakeStorageRoot, abi.encodePacked(_safeSettingsSlotHash), _storageProof + ), + abi.encode(_expectedOutput) + ); + + vm.expectRevert(IVerifierModule.VerifierModule_BytesToBytes32Failed.selector); + verifierModule.verifyNewSettings(_fakeSafe, _fakeSettings, _storageProof); + } + + function testExecTransactionIsCalledAfterVerification(IStorageMirror.SafeSettings memory _fakeSettings) public { + address[] memory _oldOwners = _fakeSettings.owners; + + vm.mockCall(_fakeSafe, abi.encodeWithSelector(ISafe.getOwners.selector), abi.encode(_oldOwners)); + + vm.mockCall(_fakeSafe, abi.encodeWithSelector(ISafe.getThreshold.selector), abi.encode(_fakeSettings.threshold)); + + vm.mockCall(_fakeSafe, abi.encodeWithSelector(ISafe.isOwner.selector), abi.encode(true)); + + vm.mockCall( + _fakeSafe, + abi.encodeWithSelector( + ISafe.execTransactionFromModule.selector, + _fakeSafe, + 0, + abi.encodeWithSelector(ISafe.addOwnerWithThreshold.selector, address(0x4), 2), + Enum.Operation.Call + ), + abi.encode(true) + ); + + bytes32 _fakeStorageRoot = keccak256(abi.encode(bytes32(uint256(1)))); + + bytes memory _storageProof = hex'e10e2d527612073b26eecdfd717e6a320f'; + + vm.mockCall( + address(_storageMirrorRegistry), + abi.encodeWithSelector(IStorageMirrorRootRegistry.latestVerifiedStorageRoot.selector), + abi.encode(_fakeStorageRoot) + ); + + bytes32 _safeSettingsSlot = keccak256(abi.encode(_fakeSafe, 0)); + + bytes32 _safeSettingsSlotHash = keccak256(abi.encode(_safeSettingsSlot)); + + bytes memory _expectedOutput = abi.encodePacked(keccak256(abi.encode(_fakeSettings))); + + vm.mockCall( + address(mpt), + abi.encodeWithSelector( + TestMPT.extractProofValue.selector, _fakeStorageRoot, abi.encodePacked(_safeSettingsSlotHash), _storageProof + ), + abi.encode(_expectedOutput) + ); + + IVerifierModule.SafeTxnParams memory _txDetails = IVerifierModule.SafeTxnParams({ + to: _fakeSafe, + value: 0, + data: abi.encodeWithSelector(ISafe.addOwnerWithThreshold.selector, address(0x4), 2), + operation: Enum.Operation.Call, + safeTxGas: 0, + baseGas: 0, + gasPrice: 0, + gasToken: address(0), + refundReceiver: payable(address(0)), + signatures: '' + }); + + vm.mockCall( + _fakeSafe, + abi.encodeWithSelector(ISafe.execTransactionFromModule.selector, address(this), 1e18, '', Enum.Operation.Call), + abi.encode(true) + ); + + vm.mockCall( + _fakeSafe, + abi.encodeWithSelector( + ISafe.execTransaction.selector, + _txDetails.to, + _txDetails.value, + _txDetails.data, + _txDetails.operation, + _txDetails.safeTxGas, + _txDetails.baseGas, + _txDetails.gasPrice, + _txDetails.gasToken, + _txDetails.refundReceiver, + _txDetails.signatures + ), + abi.encode(true) + ); + + vm.expectCall( + _fakeSafe, + abi.encodeWithSelector( + ISafe.execTransaction.selector, + _txDetails.to, + _txDetails.value, + _txDetails.data, + _txDetails.operation, + _txDetails.safeTxGas, + _txDetails.baseGas, + _txDetails.gasPrice, + _txDetails.gasToken, + _txDetails.refundReceiver, + _txDetails.signatures + ) + ); + + verifierModule.proposeAndVerifyUpdateTest(_fakeSafe, _fakeSettings, _storageProof, _txDetails); + } + + function testEmitsEvent(IStorageMirror.SafeSettings memory _fakeSettings) public { + address[] memory _oldOwners = _fakeSettings.owners; + + vm.mockCall(_fakeSafe, abi.encodeWithSelector(ISafe.getOwners.selector), abi.encode(_oldOwners)); + + vm.mockCall(_fakeSafe, abi.encodeWithSelector(ISafe.getThreshold.selector), abi.encode(_fakeSettings.threshold)); + + vm.mockCall(_fakeSafe, abi.encodeWithSelector(ISafe.isOwner.selector), abi.encode(true)); + + vm.mockCall( + _fakeSafe, + abi.encodeWithSelector( + ISafe.execTransactionFromModule.selector, + _fakeSafe, + 0, + abi.encodeWithSelector(ISafe.addOwnerWithThreshold.selector, address(0x4), 2), + Enum.Operation.Call + ), + abi.encode(true) + ); + + bytes32 _fakeStorageRoot = keccak256(abi.encode(bytes32(uint256(1)))); + + bytes memory _storageProof = hex'e10e2d527612073b26eecdfd717e6a320f'; + + vm.mockCall( + address(_storageMirrorRegistry), + abi.encodeWithSelector(IStorageMirrorRootRegistry.latestVerifiedStorageRoot.selector), + abi.encode(_fakeStorageRoot) + ); + + bytes32 _safeSettingsSlot = keccak256(abi.encode(_fakeSafe, 0)); + + bytes32 _safeSettingsSlotHash = keccak256(abi.encode(_safeSettingsSlot)); + + bytes memory _expectedOutput = abi.encodePacked(keccak256(abi.encode(_fakeSettings))); + + vm.mockCall( + address(mpt), + abi.encodeWithSelector( + TestMPT.extractProofValue.selector, _fakeStorageRoot, abi.encodePacked(_safeSettingsSlotHash), _storageProof + ), + abi.encode(_expectedOutput) + ); + + IVerifierModule.SafeTxnParams memory _txDetails = IVerifierModule.SafeTxnParams({ + to: _fakeSafe, + value: 0, + data: abi.encodeWithSelector(ISafe.addOwnerWithThreshold.selector, address(0x4), 2), + operation: Enum.Operation.Call, + safeTxGas: 0, + baseGas: 0, + gasPrice: 0, + gasToken: address(0), + refundReceiver: payable(address(0)), + signatures: '' + }); + + vm.mockCall( + _fakeSafe, + abi.encodeWithSelector(ISafe.execTransactionFromModule.selector, address(this), 1e18, '', Enum.Operation.Call), + abi.encode(true) + ); + + vm.mockCall( + _fakeSafe, + abi.encodeWithSelector( + ISafe.execTransaction.selector, + _txDetails.to, + _txDetails.value, + _txDetails.data, + _txDetails.operation, + _txDetails.safeTxGas, + _txDetails.baseGas, + _txDetails.gasPrice, + _txDetails.gasToken, + _txDetails.refundReceiver, + _txDetails.signatures + ), + abi.encode(true) + ); + + vm.expectEmit(true, true, true, true); + emit VerifiedUpdate(_fakeSafe, verifierModule.bytesToBytes32(_expectedOutput)); + + verifierModule.proposeAndVerifyUpdateTest(_fakeSafe, _fakeSettings, _storageProof, _txDetails); + } + + function testStorageIsUpdated(IStorageMirror.SafeSettings memory _fakeSettings) public { + uint256 _fakeTimestamp = 1_234_567_890; + + vm.warp(_fakeTimestamp); + + address[] memory _oldOwners = _fakeSettings.owners; + + vm.mockCall(_fakeSafe, abi.encodeWithSelector(ISafe.getOwners.selector), abi.encode(_oldOwners)); + + vm.mockCall(_fakeSafe, abi.encodeWithSelector(ISafe.getThreshold.selector), abi.encode(_fakeSettings.threshold)); + + vm.mockCall(_fakeSafe, abi.encodeWithSelector(ISafe.isOwner.selector), abi.encode(true)); + + vm.mockCall( + _fakeSafe, + abi.encodeWithSelector( + ISafe.execTransactionFromModule.selector, + _fakeSafe, + 0, + abi.encodeWithSelector(ISafe.addOwnerWithThreshold.selector, address(0x4), 2), + Enum.Operation.Call + ), + abi.encode(true) + ); + + bytes32 _fakeStorageRoot = keccak256(abi.encode(bytes32(uint256(1)))); + + bytes memory _storageProof = hex'e10e2d527612073b26eecdfd717e6a320f'; + + vm.mockCall( + address(_storageMirrorRegistry), + abi.encodeWithSelector(IStorageMirrorRootRegistry.latestVerifiedStorageRoot.selector), + abi.encode(_fakeStorageRoot) + ); + + bytes32 _safeSettingsSlot = keccak256(abi.encode(_fakeSafe, 0)); + + bytes32 _safeSettingsSlotHash = keccak256(abi.encode(_safeSettingsSlot)); + + bytes memory _expectedOutput = abi.encodePacked(keccak256(abi.encode(_fakeSettings))); + + vm.mockCall( + address(mpt), + abi.encodeWithSelector( + TestMPT.extractProofValue.selector, _fakeStorageRoot, abi.encodePacked(_safeSettingsSlotHash), _storageProof + ), + abi.encode(_expectedOutput) + ); + + IVerifierModule.SafeTxnParams memory _txDetails = IVerifierModule.SafeTxnParams({ + to: _fakeSafe, + value: 0, + data: abi.encodeWithSelector(ISafe.addOwnerWithThreshold.selector, address(0x4), 2), + operation: Enum.Operation.Call, + safeTxGas: 0, + baseGas: 0, + gasPrice: 0, + gasToken: address(0), + refundReceiver: payable(address(0)), + signatures: '' + }); + + vm.mockCall( + _fakeSafe, + abi.encodeWithSelector(ISafe.execTransactionFromModule.selector, address(this), 1e18, '', Enum.Operation.Call), + abi.encode(true) + ); + + vm.mockCall( + _fakeSafe, + abi.encodeWithSelector( + ISafe.execTransaction.selector, + _txDetails.to, + _txDetails.value, + _txDetails.data, + _txDetails.operation, + _txDetails.safeTxGas, + _txDetails.baseGas, + _txDetails.gasPrice, + _txDetails.gasToken, + _txDetails.refundReceiver, + _txDetails.signatures + ), + abi.encode(true) + ); + + verifierModule.proposeAndVerifyUpdateTest(_fakeSafe, _fakeSettings, _storageProof, _txDetails); + + assertEq( + verifierModule.latestVerifiedSettings(_fakeSafe), + verifierModule.bytesToBytes32(_expectedOutput), + 'Storage should be updated' + ); + assertEq(verifierModule.latestVerifiedSettingsTimestamp(_fakeSafe), _fakeTimestamp, 'Timestamp should be updated'); + } +} + +contract UnitStorageRoot is Base { + function testStorageMirrorStorageRootIsCalledWithCorrectParams(bytes memory _accountProof) public { + vm.assume(_accountProof.length > 0); + + StateVerifier.BlockHeader memory _fakeHeader = + StateVerifier.BlockHeader({hash: bytes32(uint256(1)), stateRootHash: bytes32(uint256(2)), number: 500}); + + bytes memory _rlpHeader = abi.encodePacked(_fakeHeader.hash); + + vm.mockCall( + address(mpt), abi.encodeWithSelector(TestMPT.verifyBlockHeader.selector, _rlpHeader), abi.encode(_fakeHeader) + ); + + bytes memory _fakeAccount = abi.encodePacked(keccak256(abi.encode(bytes32(uint256(3))))); + bytes32 _fakeStorageRoot = keccak256(abi.encode(bytes32(uint256(4)))); + + vm.mockCall( + address(mpt), + abi.encodeWithSelector( + TestMPT.extractProofValue.selector, + _fakeHeader.stateRootHash, + abi.encodePacked(keccak256(abi.encode(_storageMirror))), + _accountProof + ), + abi.encode(_fakeAccount) + ); + + vm.mockCall( + address(mpt), + abi.encodeWithSelector(TestMPT.extractStorageRootFromAccount.selector, _fakeAccount), + abi.encode(_fakeStorageRoot) + ); + + (bytes32 _storageRoot, uint256 _blockNumber) = + verifierModule.extractStorageMirrorStorageRootTest(_accountProof, _rlpHeader); + + assertEq(_storageRoot, _fakeStorageRoot, 'Storage root should match'); + assertEq(_blockNumber, _fakeHeader.number, 'Should match the block header'); + } +} diff --git a/yarn.lock b/yarn.lock index 6974aff..21376eb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -314,6 +314,10 @@ JSONStream@^1.0.4: jsonparse "^1.2.0" through ">=2.2.7 <3" +"Solidity-RLP@github:hamdiallam/Solidity-RLP": + version "2.0.8" + resolved "https://codeload.github.com/hamdiallam/Solidity-RLP/tar.gz/0212f8e754471da67fc5387df7855f47f944f925" + acorn-jsx@^5.0.0: version "5.3.2" resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" From 8e55d38418f218f795d2a0fec4e88164f9d63a5a Mon Sep 17 00:00:00 2001 From: 0xOneTony <112496816+0xOneTony@users.noreply.github.com> Date: Tue, 21 Nov 2023 17:28:55 +0200 Subject: [PATCH 15/29] feat: storage mirror root registry (#10) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # 🤖 Linear Closes SAF-30, SAF-31, SAF-32 --- .../contracts/StorageMirrorRootRegistry.sol | 69 +++++++++++++++ solidity/contracts/VerifierModule.sol | 2 +- .../interfaces/IStorageMirrorRootRegistry.sol | 46 +++++++++- ...ckOracle.t.sol => BlockHeaderOracle.t.sol} | 0 .../test/unit/StorageMirrorRootRegistry.t.sol | 88 +++++++++++++++++++ solidity/test/unit/VerifierModule.t.sol | 15 ++-- 6 files changed, 210 insertions(+), 10 deletions(-) create mode 100644 solidity/contracts/StorageMirrorRootRegistry.sol rename solidity/test/unit/{MockOracle.t.sol => BlockHeaderOracle.t.sol} (100%) create mode 100644 solidity/test/unit/StorageMirrorRootRegistry.t.sol diff --git a/solidity/contracts/StorageMirrorRootRegistry.sol b/solidity/contracts/StorageMirrorRootRegistry.sol new file mode 100644 index 0000000..0bbed98 --- /dev/null +++ b/solidity/contracts/StorageMirrorRootRegistry.sol @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity =0.8.19; + +import {IBlockHeaderOracle} from 'interfaces/IBlockHeaderOracle.sol'; +import {IVerifierModule} from 'interfaces/IVerifierModule.sol'; +import {IStorageMirrorRootRegistry} from 'interfaces/IStorageMirrorRootRegistry.sol'; + +/** + * @title StorageMirrorRootRegistry + * @notice This contract should accept and store storageRoots of the StorageMirror contract in L1. + */ +contract StorageMirrorRootRegistry is IStorageMirrorRootRegistry { + /** + * @notice The address of the StorageMirror contract in Home chain + */ + address public immutable STORAGE_MIRROR; + + /** + * @notice The address of the Verifier Module + */ + IVerifierModule public immutable VERIFIER_MODULE; + + /** + * @notice The block header oracle + */ + IBlockHeaderOracle public immutable BLOCK_HEADER_ORACLE; + + /** + * @notice The latest verified storage root of the StorageMirror contract in Home chain + */ + bytes32 public latestVerifiedStorageMirrorStorageRoot; + + /** + * @notice The latest verified block number of the Home chain + */ + uint256 public latestVerifiedBlockNumber; + + constructor(address _storageMirror, IVerifierModule _verifierModule, IBlockHeaderOracle _blockHeaderOracle) { + STORAGE_MIRROR = _storageMirror; + VERIFIER_MODULE = _verifierModule; + BLOCK_HEADER_ORACLE = _blockHeaderOracle; + } + + /** + * @notice Users can use to propose and verify a storage root of the StorageMirror contract in Home chain + * @dev Calls queryL1BlockHeader to get the block header of the Home chain + * @dev Call verifier module for the actual verificationn + * @param _accountProof The account proof of the StorageMirror contract in Home chain + */ + function proposeAndVerifyStorageMirrorStorageRoot(bytes memory _accountProof) external { + bytes memory _blockHeader = _queryL1BlockHeader(); + + (bytes32 _latestVerifiedStorageMirrorStorageRoot, uint256 _blockNumber) = + VERIFIER_MODULE.extractStorageMirrorStorageRoot(_blockHeader, _accountProof); + + latestVerifiedStorageMirrorStorageRoot = _latestVerifiedStorageMirrorStorageRoot; + latestVerifiedBlockNumber = _blockNumber; + + emit VerifiedStorageMirrorStorageRoot(_blockNumber, latestVerifiedStorageMirrorStorageRoot); + } + + /** + * @notice Function that queries an oracle to get the latest bridged block header of the Home chain + * @return _blockHeader The block header of the Home chain + */ + function _queryL1BlockHeader() internal view returns (bytes memory _blockHeader) { + (_blockHeader,) = BLOCK_HEADER_ORACLE.getLatestBlockHeader(); + } +} diff --git a/solidity/contracts/VerifierModule.sol b/solidity/contracts/VerifierModule.sol index 80d01e7..f6c7e86 100644 --- a/solidity/contracts/VerifierModule.sol +++ b/solidity/contracts/VerifierModule.sol @@ -158,7 +158,7 @@ contract VerifierModule is IVerifierModule { IStorageMirror.SafeSettings memory _proposedSettings, bytes memory _storageMirrorStorageProof ) internal view virtual returns (bytes32 _hashedProposedSettings) { - bytes32 _latestStorageRoot = STORAGE_MIRROR_ROOT_REGISTRY.latestVerifiedStorageRoot(); + bytes32 _latestStorageRoot = STORAGE_MIRROR_ROOT_REGISTRY.latestVerifiedStorageMirrorStorageRoot(); // The slot of where the latest settings hash is stored in the storage mirror bytes32 _safeSettingsSlot = keccak256(abi.encode(_safe, _LATEST_VERIFIED_SETTINGS_SLOT)); diff --git a/solidity/interfaces/IStorageMirrorRootRegistry.sol b/solidity/interfaces/IStorageMirrorRootRegistry.sol index 0f4bc39..165686a 100644 --- a/solidity/interfaces/IStorageMirrorRootRegistry.sol +++ b/solidity/interfaces/IStorageMirrorRootRegistry.sol @@ -1,15 +1,57 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity =0.8.19; +import {IVerifierModule} from 'interfaces/IVerifierModule.sol'; +import {IBlockHeaderOracle} from 'interfaces/IBlockHeaderOracle.sol'; + interface IStorageMirrorRootRegistry { + /*/////////////////////////////////////////////////////////////// + EVENTS + //////////////////////////////////////////////////////////////*/ + + /** + * @notice Emits after the storage root gets verified + * @param _homeChainBlockNumber The block number of the Home chain + * @param _storageRoot The storage root of the StorageMirror contract in Home chain that was verified + */ + event VerifiedStorageMirrorStorageRoot(uint256 indexed _homeChainBlockNumber, bytes32 _storageRoot); + /*/////////////////////////////////////////////////////////////// VARIABLES //////////////////////////////////////////////////////////////*/ /** - * @notice The latest verified storage root + * @notice The address of the StorageMirror contract in Home chain + * @return _storageMirror The address of the StorageMirror contract in Home chain + */ + function STORAGE_MIRROR() external view returns (address _storageMirror); + + /** + * @notice The address of the Verifier Module + * @return _verifierModule The address of the Verifier Module + */ + function VERIFIER_MODULE() external view returns (IVerifierModule _verifierModule); + + /** + * @notice The address of the Block Header Oracle + * @return _blockHeaderOracle The address of the Block Header Oracle + */ + function BLOCK_HEADER_ORACLE() external view returns (IBlockHeaderOracle _blockHeaderOracle); + + /** + * @notice The latest verified block number of the Home chain + * @return _latestVerifiedBlockNumber The latest verified block number of the Home chain + */ + function latestVerifiedBlockNumber() external view returns (uint256 _latestVerifiedBlockNumber); + + /** + * @notice The latest verified storage root of the StorageMirror contract in Home chain + * @return _latestVerifiedStorageMirrorStorageRoot The latest verified storage root of the StorageMirror contract in Home chain */ - function latestVerifiedStorageRoot() external view returns (bytes32 _latestVerifiedStorageRoot); + function latestVerifiedStorageMirrorStorageRoot() + external + view + returns (bytes32 _latestVerifiedStorageMirrorStorageRoot); /*/////////////////////////////////////////////////////////////// LOGIC diff --git a/solidity/test/unit/MockOracle.t.sol b/solidity/test/unit/BlockHeaderOracle.t.sol similarity index 100% rename from solidity/test/unit/MockOracle.t.sol rename to solidity/test/unit/BlockHeaderOracle.t.sol diff --git a/solidity/test/unit/StorageMirrorRootRegistry.t.sol b/solidity/test/unit/StorageMirrorRootRegistry.t.sol new file mode 100644 index 0000000..dced08b --- /dev/null +++ b/solidity/test/unit/StorageMirrorRootRegistry.t.sol @@ -0,0 +1,88 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity >=0.8.4 <0.9.0; + +import {Test} from 'forge-std/Test.sol'; +import {BlockHeaderOracle} from 'contracts/BlockHeaderOracle.sol'; +import {StorageMirrorRootRegistry} from 'contracts/StorageMirrorRootRegistry.sol'; +import {IBlockHeaderOracle} from 'interfaces/IBlockHeaderOracle.sol'; +import {IStorageMirrorRootRegistry} from 'interfaces/IStorageMirrorRootRegistry.sol'; +import {IVerifierModule} from 'interfaces/IVerifierModule.sol'; + +contract StorageMirrorRootRegistryForTest is StorageMirrorRootRegistry { + constructor( + address _storageMirror, + IVerifierModule _verifierModule, + IBlockHeaderOracle _blockHeaderOracle + ) StorageMirrorRootRegistry(_storageMirror, _verifierModule, _blockHeaderOracle) {} + + function queryL1BlockHeader() external view returns (bytes memory _blockHeader) { + _blockHeader = _queryL1BlockHeader(); + } +} + +abstract contract Base is Test { + event VerifiedStorageMirrorStorageRoot(uint256 indexed _homeChainBlockNumber, bytes32 _storageRoot); + + address public user; + address public storageMirror; + StorageMirrorRootRegistry public storageMirrorRootRegistry; + StorageMirrorRootRegistryForTest public storageMirrorRootRegistryForTest; + BlockHeaderOracle public blockHeaderOracle; + IVerifierModule public verifierModule; + + function setUp() public { + user = makeAddr('user'); + storageMirror = makeAddr('StorageMirror'); + blockHeaderOracle = new BlockHeaderOracle(); + verifierModule = IVerifierModule(makeAddr('VerifierModule')); + storageMirrorRootRegistry = + new StorageMirrorRootRegistry(storageMirror, verifierModule, IBlockHeaderOracle(blockHeaderOracle)); + storageMirrorRootRegistryForTest = + new StorageMirrorRootRegistryForTest(storageMirror, verifierModule, IBlockHeaderOracle(blockHeaderOracle)); + } +} + +contract UnitStorageMirrorRootRegistryQueryL1BlockHeader is Base { + function testQueryL1BlockHeader(bytes memory _blockHeader, uint256 _blockTimestamp, uint256 _blockNumber) public { + vm.prank(user); + blockHeaderOracle.updateBlockHeader(_blockHeader, _blockTimestamp, _blockNumber); + + vm.expectCall(address(blockHeaderOracle), abi.encodeWithSelector(blockHeaderOracle.getLatestBlockHeader.selector)); + vm.prank(user); + bytes memory _savedBlockHeader = storageMirrorRootRegistryForTest.queryL1BlockHeader(); + + assertEq(_blockHeader, _savedBlockHeader, 'Block header should be saved'); + } +} + +contract UnitStorageMirrorRootRegistryProposeAndVerifyStorageMirrorStorageRoot is Base { + function testProposeAndVerifyStorageMirrorStorageRoot(bytes memory _accountProof) public { + bytes memory _blockHeader = '0x1234'; + uint256 _blockTimestamp = 1234; + uint256 _blockNumber = 1234; + bytes32 _storageRoot = '0x1234'; + + vm.prank(user); + blockHeaderOracle.updateBlockHeader(_blockHeader, _blockTimestamp, _blockNumber); + + vm.mockCall( + address(verifierModule), + abi.encodeWithSelector(verifierModule.extractStorageMirrorStorageRoot.selector, _blockHeader, _accountProof), + abi.encode(_storageRoot, _blockNumber) + ); + vm.expectCall( + address(verifierModule), + abi.encodeWithSelector(verifierModule.extractStorageMirrorStorageRoot.selector, _blockHeader, _accountProof) + ); + + vm.expectEmit(true, true, true, true); + emit VerifiedStorageMirrorStorageRoot(_blockNumber, _storageRoot); + + vm.prank(user); + storageMirrorRootRegistry.proposeAndVerifyStorageMirrorStorageRoot(_accountProof); + + assertEq( + _storageRoot, storageMirrorRootRegistry.latestVerifiedStorageMirrorStorageRoot(), 'Storage root should be saved' + ); + } +} diff --git a/solidity/test/unit/VerifierModule.t.sol b/solidity/test/unit/VerifierModule.t.sol index 43664a2..b1a79ca 100644 --- a/solidity/test/unit/VerifierModule.t.sol +++ b/solidity/test/unit/VerifierModule.t.sol @@ -56,7 +56,8 @@ contract TestVerifierModule is VerifierModule { IStorageMirror.SafeSettings memory _proposedSettings, bytes memory _storageMirrorStorageProof ) public view returns (bytes32 _hashedProposedSettings) { - bytes32 _latestStorageRoot = IStorageMirrorRootRegistry(STORAGE_MIRROR_ROOT_REGISTRY).latestVerifiedStorageRoot(); + bytes32 _latestStorageRoot = + IStorageMirrorRootRegistry(STORAGE_MIRROR_ROOT_REGISTRY).latestVerifiedStorageMirrorStorageRoot(); // The slot of where the latest settings hash is stored in the storage mirror bytes32 _safeSettingsSlot = keccak256(abi.encode(_safe, 0)); @@ -337,7 +338,7 @@ contract UnitMerklePatriciaTree is Base { vm.mockCall( address(_storageMirrorRegistry), - abi.encodeWithSelector(IStorageMirrorRootRegistry.latestVerifiedStorageRoot.selector), + abi.encodeWithSelector(IStorageMirrorRootRegistry.latestVerifiedStorageMirrorStorageRoot.selector), abi.encode(_fakeStorageRoot) ); @@ -380,7 +381,7 @@ contract UnitMerklePatriciaTree is Base { vm.mockCall( address(_storageMirrorRegistry), - abi.encodeWithSelector(IStorageMirrorRootRegistry.latestVerifiedStorageRoot.selector), + abi.encodeWithSelector(IStorageMirrorRootRegistry.latestVerifiedStorageMirrorStorageRoot.selector), abi.encode(_fakeStorageRoot) ); @@ -412,7 +413,7 @@ contract UnitMerklePatriciaTree is Base { vm.mockCall( address(_storageMirrorRegistry), - abi.encodeWithSelector(IStorageMirrorRootRegistry.latestVerifiedStorageRoot.selector), + abi.encodeWithSelector(IStorageMirrorRootRegistry.latestVerifiedStorageMirrorStorageRoot.selector), abi.encode(_fakeStorageRoot) ); @@ -461,7 +462,7 @@ contract UnitMerklePatriciaTree is Base { vm.mockCall( address(_storageMirrorRegistry), - abi.encodeWithSelector(IStorageMirrorRootRegistry.latestVerifiedStorageRoot.selector), + abi.encodeWithSelector(IStorageMirrorRootRegistry.latestVerifiedStorageMirrorStorageRoot.selector), abi.encode(_fakeStorageRoot) ); @@ -563,7 +564,7 @@ contract UnitMerklePatriciaTree is Base { vm.mockCall( address(_storageMirrorRegistry), - abi.encodeWithSelector(IStorageMirrorRootRegistry.latestVerifiedStorageRoot.selector), + abi.encodeWithSelector(IStorageMirrorRootRegistry.latestVerifiedStorageMirrorStorageRoot.selector), abi.encode(_fakeStorageRoot) ); @@ -655,7 +656,7 @@ contract UnitMerklePatriciaTree is Base { vm.mockCall( address(_storageMirrorRegistry), - abi.encodeWithSelector(IStorageMirrorRootRegistry.latestVerifiedStorageRoot.selector), + abi.encodeWithSelector(IStorageMirrorRootRegistry.latestVerifiedStorageMirrorStorageRoot.selector), abi.encode(_fakeStorageRoot) ); From a644c077654f46b64e326875dcdf41261701b76d Mon Sep 17 00:00:00 2001 From: excaliborr <124819095+excaliborr@users.noreply.github.com> Date: Wed, 22 Nov 2023 03:47:33 -0500 Subject: [PATCH 16/29] fix: update datatype (#15) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # 🤖 Linear Closes SAF-XXX --- solidity/contracts/NeedsUpdateGuard.sol | 4 +-- solidity/contracts/StorageMirror.sol | 9 +++---- .../contracts/UpdateStorageMirrorGuard.sol | 20 ++++++++------- solidity/interfaces/IStorageMirror.sol | 7 +++--- solidity/test/unit/StorageMirror.t.sol | 13 +++++----- .../test/unit/UpdateStorageMirrorGuard.t.sol | 25 +++++++++---------- 6 files changed, 38 insertions(+), 40 deletions(-) diff --git a/solidity/contracts/NeedsUpdateGuard.sol b/solidity/contracts/NeedsUpdateGuard.sol index db55f28..2d40ed1 100644 --- a/solidity/contracts/NeedsUpdateGuard.sol +++ b/solidity/contracts/NeedsUpdateGuard.sol @@ -56,8 +56,8 @@ contract NeedsUpdateGuard is BaseGuard { bytes memory _signatures, address _msgSender ) external { - uint256 _lastVerifiedUpdateTimestamp = VERIFIER_MODULE.latestVerifiedSettingsTimestamp(_msgSender); - uint256 _trustLatestUpdateForSeconds = trustLatestUpdateForSeconds[_msgSender]; + uint256 _lastVerifiedUpdateTimestamp = VERIFIER_MODULE.latestVerifiedSettingsTimestamp(msg.sender); + uint256 _trustLatestUpdateForSeconds = trustLatestUpdateForSeconds[msg.sender]; if (_lastVerifiedUpdateTimestamp + _trustLatestUpdateForSeconds < block.timestamp) { revert NeedsUpdateGuard_NeedsUpdate(); diff --git a/solidity/contracts/StorageMirror.sol b/solidity/contracts/StorageMirror.sol index f34d3ca..ee014ea 100644 --- a/solidity/contracts/StorageMirror.sol +++ b/solidity/contracts/StorageMirror.sol @@ -17,12 +17,11 @@ contract StorageMirror is IStorageMirror { /** * @notice Updates a safe's settings hash * @dev The safe should always be msg.sender - * @param _safeSettings The settings we are going to update to + * @param _hashedSafeSettings The hashed settings we are going to update to */ - function update(SafeSettings memory _safeSettings) external { - bytes32 _settingsHash = keccak256(abi.encode(_safeSettings)); - latestSettingsHash[msg.sender] = _settingsHash; + function update(bytes32 _hashedSafeSettings) external { + latestSettingsHash[msg.sender] = _hashedSafeSettings; - emit SettingsUpdated(msg.sender, _settingsHash, _safeSettings); + emit SettingsUpdated(msg.sender, _hashedSafeSettings); } } diff --git a/solidity/contracts/UpdateStorageMirrorGuard.sol b/solidity/contracts/UpdateStorageMirrorGuard.sol index c232d0a..4b6547a 100644 --- a/solidity/contracts/UpdateStorageMirrorGuard.sol +++ b/solidity/contracts/UpdateStorageMirrorGuard.sol @@ -24,12 +24,12 @@ contract UpdateStorageMirrorGuard is BaseGuard { /** * @notice A boolean that returns true if a tx is changing the safe's settings */ - bool public didSettingsChange; + mapping(address => bool) public didSettingsChange; /** * @notice The hash of the new settings */ - bytes32 public settingsHash; + mapping(address => bytes32) public settingsHash; constructor(IGuardCallbackModule _guardCallbackModule) { GUARD_CALLBACK_MODULE = _guardCallbackModule; @@ -52,14 +52,16 @@ contract UpdateStorageMirrorGuard is BaseGuard { bytes memory _signatures, address _msgSender ) external { - didSettingsChange = true; + didSettingsChange[msg.sender] = true; // TODO: change these data with the decoded ones address[] memory _owners = new address[](1); IStorageMirror.SafeSettings memory _safeSettings = IStorageMirror.SafeSettings({owners: _owners, threshold: 1}); - settingsHash = keccak256(abi.encode(_safeSettings)); + bytes32 _settingsHash = keccak256(abi.encode(_safeSettings)); + settingsHash[msg.sender] = _settingsHash; + didSettingsChange[msg.sender] = true; - emit SettingsChanged(msg.sender, settingsHash, _safeSettings); + emit SettingsChanged(msg.sender, _settingsHash, _safeSettings); } /** @@ -68,10 +70,10 @@ contract UpdateStorageMirrorGuard is BaseGuard { * @dev The msg.sender should be the safe */ function checkAfterExecution(bytes32 _txHash, bool _success) external { - if (didSettingsChange && _success) { - GUARD_CALLBACK_MODULE.saveUpdatedSettings(msg.sender, settingsHash); - didSettingsChange = false; - settingsHash = keccak256(abi.encodePacked('')); + if (didSettingsChange[msg.sender] && _success) { + // NOTE: No need to reset settings as this function will only be called when the settings change + GUARD_CALLBACK_MODULE.saveUpdatedSettings(msg.sender, settingsHash[msg.sender]); + didSettingsChange[msg.sender] = false; } } } diff --git a/solidity/interfaces/IStorageMirror.sol b/solidity/interfaces/IStorageMirror.sol index e75c4c8..c7244b3 100644 --- a/solidity/interfaces/IStorageMirror.sol +++ b/solidity/interfaces/IStorageMirror.sol @@ -11,9 +11,8 @@ interface IStorageMirror { * * @param _safe The address of the safe * @param _settingsHash The hash of the settings - * @param _safeSettings The plaintext of the settings */ - event SettingsUpdated(address indexed _safe, bytes32 indexed _settingsHash, SafeSettings _safeSettings); + event SettingsUpdated(address indexed _safe, bytes32 indexed _settingsHash); /*/////////////////////////////////////////////////////////////// STRUCTS @@ -42,7 +41,7 @@ interface IStorageMirror { /** * @notice Updates a safe's settings hash * @dev The safe should always be msg.sender - * @param _safeSettings The settings we are going to update to + * @param _hashedSafeSettings The hashed settings we are going to update to */ - function update(SafeSettings memory _safeSettings) external; + function update(bytes32 _hashedSafeSettings) external; } diff --git a/solidity/test/unit/StorageMirror.t.sol b/solidity/test/unit/StorageMirror.t.sol index ee898be..9464bbc 100644 --- a/solidity/test/unit/StorageMirror.t.sol +++ b/solidity/test/unit/StorageMirror.t.sol @@ -6,9 +6,7 @@ import {StorageMirror} from 'contracts/StorageMirror.sol'; import {IStorageMirror} from 'interfaces/IStorageMirror.sol'; abstract contract Base is Test { - event SettingsUpdated( - address indexed _safe, bytes32 indexed _settingsHash, IStorageMirror.SafeSettings _safeSettings - ); + event SettingsUpdated(address indexed _safe, bytes32 indexed _settingsHash); address public safe; StorageMirror public storageMirror; @@ -27,10 +25,11 @@ contract UnitStorageMirror is Base { IStorageMirror.SafeSettings memory _safeSettings = IStorageMirror.SafeSettings({owners: _owners, threshold: _threshold}); + bytes32 _settingsHash = keccak256(abi.encode(_safeSettings)); + vm.prank(safe); - storageMirror.update(_safeSettings); + storageMirror.update(_settingsHash); - bytes32 _settingsHash = keccak256(abi.encode(_safeSettings)); bytes32 _savedHash = storageMirror.latestSettingsHash(safe); assertEq(_settingsHash, _savedHash, 'Settings hash should be saved'); @@ -47,7 +46,7 @@ contract UnitStorageMirror is Base { vm.prank(safe); vm.expectEmit(true, true, true, true); - emit SettingsUpdated(safe, _expectedSettingsHash, _safeSettings); - storageMirror.update(_safeSettings); + emit SettingsUpdated(safe, _expectedSettingsHash); + storageMirror.update(keccak256(abi.encode(_safeSettings))); } } diff --git a/solidity/test/unit/UpdateStorageMirrorGuard.t.sol b/solidity/test/unit/UpdateStorageMirrorGuard.t.sol index 70cd31e..a45db99 100644 --- a/solidity/test/unit/UpdateStorageMirrorGuard.t.sol +++ b/solidity/test/unit/UpdateStorageMirrorGuard.t.sol @@ -27,25 +27,25 @@ abstract contract Base is Test { contract UnitUpdateStorageMirrorGuard is Base { function testCheckTransaction() public { - assertFalse(updateStorageMirrorGuard.didSettingsChange()); - assertEq(updateStorageMirrorGuard.settingsHash(), bytes32('')); + assertFalse(updateStorageMirrorGuard.didSettingsChange(safe)); + assertEq(updateStorageMirrorGuard.settingsHash(safe), bytes32('')); vm.expectEmit(true, true, true, true); emit SettingsChanged(safe, settingsHash, safeSettings); vm.prank(safe); updateStorageMirrorGuard.checkTransaction( - address(0), 0, '', Enum.Operation.Call, 0, 0, 0, address(0), payable(0), '', address(0) + address(0), 0, '', Enum.Operation.Call, 0, 0, 0, address(0), payable(0), '', safe ); - assertTrue(updateStorageMirrorGuard.didSettingsChange()); - assertEq(updateStorageMirrorGuard.settingsHash(), settingsHash, 'Settings hash should be stored'); + assertTrue(updateStorageMirrorGuard.didSettingsChange(safe)); + assertEq(updateStorageMirrorGuard.settingsHash(safe), settingsHash, 'Settings hash should be stored'); } function testCheckAfterExecution(bytes32 _txHash) public { // Call checkTransaction to change didSettingsChange to true vm.prank(safe); updateStorageMirrorGuard.checkTransaction( - address(0), 0, '', Enum.Operation.Call, 0, 0, 0, address(0), payable(0), '', address(0) + address(0), 0, '', Enum.Operation.Call, 0, 0, 0, address(0), payable(0), '', safe ); vm.mockCall( @@ -59,30 +59,29 @@ contract UnitUpdateStorageMirrorGuard is Base { vm.prank(safe); updateStorageMirrorGuard.checkAfterExecution(_txHash, true); - assertFalse(updateStorageMirrorGuard.didSettingsChange()); - assertEq(updateStorageMirrorGuard.settingsHash(), keccak256(abi.encodePacked('')), 'Settings hash should reset'); + assertFalse(updateStorageMirrorGuard.didSettingsChange(safe)); } function testCheckAfterExecutionNoSettingsChange(bytes32 _txHash) public { vm.prank(safe); updateStorageMirrorGuard.checkAfterExecution(_txHash, true); - assertFalse(updateStorageMirrorGuard.didSettingsChange()); - assertEq(updateStorageMirrorGuard.settingsHash(), bytes32(''), 'Settings hash should stay empty'); + assertFalse(updateStorageMirrorGuard.didSettingsChange(safe)); + assertEq(updateStorageMirrorGuard.settingsHash(safe), bytes32(''), 'Settings hash should stay empty'); } function testCheckAfterExecutionTxFailed(bytes32 _txHash) public { // Call checkTransaction to change didSettingsChange to true vm.prank(safe); updateStorageMirrorGuard.checkTransaction( - address(0), 0, '', Enum.Operation.Call, 0, 0, 0, address(0), payable(0), '', address(0) + address(0), 0, '', Enum.Operation.Call, 0, 0, 0, address(0), payable(0), '', safe ); vm.prank(safe); updateStorageMirrorGuard.checkAfterExecution(_txHash, false); // Should be true since the tx failed to execute and thus didnt make it to reset - assertTrue(updateStorageMirrorGuard.didSettingsChange()); - assertEq(updateStorageMirrorGuard.settingsHash(), settingsHash, 'Settings hash should stay the same'); + assertTrue(updateStorageMirrorGuard.didSettingsChange(safe)); + assertEq(updateStorageMirrorGuard.settingsHash(safe), settingsHash, 'Settings hash should stay the same'); } } From 3a0f90eca037517cfb4c93af932b61e4bf112227 Mon Sep 17 00:00:00 2001 From: 0xOneTony <112496816+0xOneTony@users.noreply.github.com> Date: Wed, 22 Nov 2023 13:43:31 +0200 Subject: [PATCH 17/29] fix: update mirror guard (#16) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # 🤖 Linear Closes SAF-XXX --- .../contracts/UpdateStorageMirrorGuard.sol | 33 ++++------- .../test/unit/UpdateStorageMirrorGuard.t.sol | 56 +++---------------- 2 files changed, 18 insertions(+), 71 deletions(-) diff --git a/solidity/contracts/UpdateStorageMirrorGuard.sol b/solidity/contracts/UpdateStorageMirrorGuard.sol index 4b6547a..3b36475 100644 --- a/solidity/contracts/UpdateStorageMirrorGuard.sol +++ b/solidity/contracts/UpdateStorageMirrorGuard.sol @@ -21,16 +21,6 @@ contract UpdateStorageMirrorGuard is BaseGuard { IGuardCallbackModule public immutable GUARD_CALLBACK_MODULE; - /** - * @notice A boolean that returns true if a tx is changing the safe's settings - */ - mapping(address => bool) public didSettingsChange; - - /** - * @notice The hash of the new settings - */ - mapping(address => bytes32) public settingsHash; - constructor(IGuardCallbackModule _guardCallbackModule) { GUARD_CALLBACK_MODULE = _guardCallbackModule; } @@ -52,16 +42,7 @@ contract UpdateStorageMirrorGuard is BaseGuard { bytes memory _signatures, address _msgSender ) external { - didSettingsChange[msg.sender] = true; - // TODO: change these data with the decoded ones - address[] memory _owners = new address[](1); - IStorageMirror.SafeSettings memory _safeSettings = IStorageMirror.SafeSettings({owners: _owners, threshold: 1}); - - bytes32 _settingsHash = keccak256(abi.encode(_safeSettings)); - settingsHash[msg.sender] = _settingsHash; - didSettingsChange[msg.sender] = true; - - emit SettingsChanged(msg.sender, _settingsHash, _safeSettings); + // TODO: This can be improved with the decoding of the data to accurate catch a change of the safe's settings } /** @@ -70,10 +51,16 @@ contract UpdateStorageMirrorGuard is BaseGuard { * @dev The msg.sender should be the safe */ function checkAfterExecution(bytes32 _txHash, bool _success) external { - if (didSettingsChange[msg.sender] && _success) { + if (_success) { + address[] memory _owners = new address[](1); + _owners[0] = msg.sender; + IStorageMirror.SafeSettings memory _safeSettings = IStorageMirror.SafeSettings({owners: _owners, threshold: 1}); + bytes32 _settingsHash = keccak256(abi.encode(_safeSettings)); + // NOTE: No need to reset settings as this function will only be called when the settings change - GUARD_CALLBACK_MODULE.saveUpdatedSettings(msg.sender, settingsHash[msg.sender]); - didSettingsChange[msg.sender] = false; + GUARD_CALLBACK_MODULE.saveUpdatedSettings(msg.sender, _settingsHash); + + emit SettingsChanged(msg.sender, _settingsHash, _safeSettings); } } } diff --git a/solidity/test/unit/UpdateStorageMirrorGuard.t.sol b/solidity/test/unit/UpdateStorageMirrorGuard.t.sol index a45db99..f4b5fc2 100644 --- a/solidity/test/unit/UpdateStorageMirrorGuard.t.sol +++ b/solidity/test/unit/UpdateStorageMirrorGuard.t.sol @@ -2,7 +2,6 @@ pragma solidity >=0.8.4 <0.9.0; import {Test} from 'forge-std/Test.sol'; -import {Enum} from 'safe-contracts/common/Enum.sol'; import {UpdateStorageMirrorGuard} from 'contracts/UpdateStorageMirrorGuard.sol'; import {IGuardCallbackModule} from 'interfaces/IGuardCallbackModule.sol'; import {IStorageMirror} from 'interfaces/IStorageMirror.sol'; @@ -15,39 +14,22 @@ abstract contract Base is Test { UpdateStorageMirrorGuard public updateStorageMirrorGuard; address[] public owners = new address[](1); - IStorageMirror.SafeSettings public safeSettings = IStorageMirror.SafeSettings({owners: owners, threshold: 1}); - bytes32 public settingsHash = keccak256(abi.encode(safeSettings)); + IStorageMirror.SafeSettings public safeSettings; + bytes32 public settingsHash; function setUp() public { safe = makeAddr('safe'); guardCallbackModule = IGuardCallbackModule(makeAddr('guardCallbackModule')); updateStorageMirrorGuard = new UpdateStorageMirrorGuard(guardCallbackModule); + + owners[0] = safe; + safeSettings = IStorageMirror.SafeSettings({owners: owners, threshold: 1}); + settingsHash = keccak256(abi.encode(safeSettings)); } } contract UnitUpdateStorageMirrorGuard is Base { - function testCheckTransaction() public { - assertFalse(updateStorageMirrorGuard.didSettingsChange(safe)); - assertEq(updateStorageMirrorGuard.settingsHash(safe), bytes32('')); - - vm.expectEmit(true, true, true, true); - emit SettingsChanged(safe, settingsHash, safeSettings); - vm.prank(safe); - updateStorageMirrorGuard.checkTransaction( - address(0), 0, '', Enum.Operation.Call, 0, 0, 0, address(0), payable(0), '', safe - ); - - assertTrue(updateStorageMirrorGuard.didSettingsChange(safe)); - assertEq(updateStorageMirrorGuard.settingsHash(safe), settingsHash, 'Settings hash should be stored'); - } - function testCheckAfterExecution(bytes32 _txHash) public { - // Call checkTransaction to change didSettingsChange to true - vm.prank(safe); - updateStorageMirrorGuard.checkTransaction( - address(0), 0, '', Enum.Operation.Call, 0, 0, 0, address(0), payable(0), '', safe - ); - vm.mockCall( address(guardCallbackModule), abi.encodeCall(IGuardCallbackModule.saveUpdatedSettings, (safe, settingsHash)), @@ -56,32 +38,10 @@ contract UnitUpdateStorageMirrorGuard is Base { vm.expectCall( address(guardCallbackModule), abi.encodeCall(IGuardCallbackModule.saveUpdatedSettings, (safe, settingsHash)) ); - vm.prank(safe); - updateStorageMirrorGuard.checkAfterExecution(_txHash, true); - - assertFalse(updateStorageMirrorGuard.didSettingsChange(safe)); - } - function testCheckAfterExecutionNoSettingsChange(bytes32 _txHash) public { + vm.expectEmit(true, true, true, true); + emit SettingsChanged(safe, settingsHash, safeSettings); vm.prank(safe); updateStorageMirrorGuard.checkAfterExecution(_txHash, true); - - assertFalse(updateStorageMirrorGuard.didSettingsChange(safe)); - assertEq(updateStorageMirrorGuard.settingsHash(safe), bytes32(''), 'Settings hash should stay empty'); - } - - function testCheckAfterExecutionTxFailed(bytes32 _txHash) public { - // Call checkTransaction to change didSettingsChange to true - vm.prank(safe); - updateStorageMirrorGuard.checkTransaction( - address(0), 0, '', Enum.Operation.Call, 0, 0, 0, address(0), payable(0), '', safe - ); - - vm.prank(safe); - updateStorageMirrorGuard.checkAfterExecution(_txHash, false); - - // Should be true since the tx failed to execute and thus didnt make it to reset - assertTrue(updateStorageMirrorGuard.didSettingsChange(safe)); - assertEq(updateStorageMirrorGuard.settingsHash(safe), settingsHash, 'Settings hash should stay the same'); } } From 39d378f6bad432b6b357a1b32e212ad49776a873 Mon Sep 17 00:00:00 2001 From: excaliborr <124819095+excaliborr@users.noreply.github.com> Date: Wed, 22 Nov 2023 07:09:00 -0500 Subject: [PATCH 18/29] fix: getting real safe settings data (#18) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # 🤖 Linear Closes SAF-XXX --- solidity/contracts/UpdateStorageMirrorGuard.sol | 9 ++++++--- solidity/test/unit/UpdateStorageMirrorGuard.t.sol | 10 ++++++++++ 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/solidity/contracts/UpdateStorageMirrorGuard.sol b/solidity/contracts/UpdateStorageMirrorGuard.sol index 3b36475..3584863 100644 --- a/solidity/contracts/UpdateStorageMirrorGuard.sol +++ b/solidity/contracts/UpdateStorageMirrorGuard.sol @@ -5,6 +5,7 @@ import {BaseGuard} from 'safe-contracts/base/GuardManager.sol'; import {Enum} from 'safe-contracts/common/Enum.sol'; import {IGuardCallbackModule} from 'interfaces/IGuardCallbackModule.sol'; import {IStorageMirror} from 'interfaces/IStorageMirror.sol'; +import {ISafe} from 'interfaces/ISafe.sol'; /** * @title UpdateStorageMirrorGuard @@ -52,9 +53,11 @@ contract UpdateStorageMirrorGuard is BaseGuard { */ function checkAfterExecution(bytes32 _txHash, bool _success) external { if (_success) { - address[] memory _owners = new address[](1); - _owners[0] = msg.sender; - IStorageMirror.SafeSettings memory _safeSettings = IStorageMirror.SafeSettings({owners: _owners, threshold: 1}); + address[] memory _owners = ISafe(msg.sender).getOwners(); + uint256 _threshold = ISafe(msg.sender).getThreshold(); + + IStorageMirror.SafeSettings memory _safeSettings = + IStorageMirror.SafeSettings({owners: _owners, threshold: _threshold}); bytes32 _settingsHash = keccak256(abi.encode(_safeSettings)); // NOTE: No need to reset settings as this function will only be called when the settings change diff --git a/solidity/test/unit/UpdateStorageMirrorGuard.t.sol b/solidity/test/unit/UpdateStorageMirrorGuard.t.sol index f4b5fc2..2c58c85 100644 --- a/solidity/test/unit/UpdateStorageMirrorGuard.t.sol +++ b/solidity/test/unit/UpdateStorageMirrorGuard.t.sol @@ -5,6 +5,7 @@ import {Test} from 'forge-std/Test.sol'; import {UpdateStorageMirrorGuard} from 'contracts/UpdateStorageMirrorGuard.sol'; import {IGuardCallbackModule} from 'interfaces/IGuardCallbackModule.sol'; import {IStorageMirror} from 'interfaces/IStorageMirror.sol'; +import {ISafe} from 'interfaces/ISafe.sol'; abstract contract Base is Test { event SettingsChanged(address indexed _safe, bytes32 indexed _settingsHash, IStorageMirror.SafeSettings _settings); @@ -30,6 +31,15 @@ abstract contract Base is Test { contract UnitUpdateStorageMirrorGuard is Base { function testCheckAfterExecution(bytes32 _txHash) public { + address[] memory _owners = new address[](1); + _owners[0] = safe; + + uint256 _threshold = 1; + + vm.mockCall(address(safe), abi.encodeCall(ISafe.getOwners, ()), abi.encode(_owners)); + + vm.mockCall(address(safe), abi.encodeCall(ISafe.getThreshold, ()), abi.encode(_threshold)); + vm.mockCall( address(guardCallbackModule), abi.encodeCall(IGuardCallbackModule.saveUpdatedSettings, (safe, settingsHash)), From 28d0193e5ce1e217c2488e04bb28f536cb6acf01 Mon Sep 17 00:00:00 2001 From: excaliborr <124819095+excaliborr@users.noreply.github.com> Date: Wed, 22 Nov 2023 07:11:46 -0500 Subject: [PATCH 19/29] feat: calculating incentive (#17) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # 🤖 Linear Closes SAF-XXX --- solidity/contracts/VerifierModule.sol | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/solidity/contracts/VerifierModule.sol b/solidity/contracts/VerifierModule.sol index f6c7e86..64775f4 100644 --- a/solidity/contracts/VerifierModule.sol +++ b/solidity/contracts/VerifierModule.sol @@ -114,6 +114,7 @@ contract VerifierModule is IVerifierModule { bytes memory _storageMirrorStorageProof, SafeTxnParams calldata _arbitraryTxnParams ) internal { + uint256 _startingGas = gasleft(); bytes32 _hashedProposedSettings = _verifyNewSettings(_safe, _proposedSettings, _storageMirrorStorageProof); // If we dont revert from the _verifyNewSettings() call, then we can update the safe @@ -134,15 +135,24 @@ contract VerifierModule is IVerifierModule { _arbitraryTxnParams.signatures ); - // Pay incentives - // TODO: Calculations for incentives so its not hardcoded to 1e18 - ISafe(_safe).execTransactionFromModule(msg.sender, 1e18, '', Enum.Operation.Call); - // Make the storage updates at the end of the call to save gas in a revert scenario latestVerifiedSettings[_safe] = _hashedProposedSettings; latestVerifiedSettingsTimestamp[_safe] = block.timestamp; emit VerifiedUpdate(_safe, _hashedProposedSettings); + + // NOTE: This is not the exact gas spent as we still have to make the transaction after the calculation + // NOTE: We do all this at the end of the call to get the most accurate gas calculations + + uint256 _gasLeft = gasleft(); + + uint256 _gasSpent = _startingGas - _gasLeft; + + // Gas spent plus 10% incentive + uint256 _incentive = _gasSpent + _gasSpent * 10 / 100; + + // Pay incentives + ISafe(_safe).execTransactionFromModule(msg.sender, _incentive, '', Enum.Operation.Call); } /** From 219414ec44e004964075fb822a3953b343cdf390 Mon Sep 17 00:00:00 2001 From: excaliborr <124819095+excaliborr@users.noreply.github.com> Date: Thu, 23 Nov 2023 08:49:58 -0500 Subject: [PATCH 20/29] feat: python scripts for generating proofs (#20) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # 🤖 Linear Closes SAF-XXX --- .gitignore | 3 ++ package.json | 1 + proofs/generate_proof.py | 88 ++++++++++++++++++++++++++++++++++++++++ proofs/proof_utils.py | 61 ++++++++++++++++++++++++++++ proofs/utils.py | 23 +++++++++++ requirements.txt | 3 ++ 6 files changed, 179 insertions(+) create mode 100644 proofs/generate_proof.py create mode 100644 proofs/proof_utils.py create mode 100644 proofs/utils.py create mode 100644 requirements.txt diff --git a/.gitignore b/.gitignore index 6d577c5..00c39fb 100644 --- a/.gitignore +++ b/.gitignore @@ -18,3 +18,6 @@ out/*/* # Keep the latest deployment only broadcast/*/*/* + +# Cache for the python scripts +proofs/__pycache__/* diff --git a/package.json b/package.json index c75b302..bcc1561 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "lint:sol-logic": "solhint -c .solhint.json 'solidity/contracts/**/*.sol' 'solidity/interfaces/**/*.sol'", "lint:sol-tests": "solhint 'solidity/test/**/*.sol'", "prepare": "husky install", + "proof": "python3 proofs/generate_proof.py", "test": "forge test -vvv", "test:e2e": "forge test --match-contract E2E -vvv", "test:unit": "forge test --match-contract Unit -vvv", diff --git a/proofs/generate_proof.py b/proofs/generate_proof.py new file mode 100644 index 0000000..3a9aba6 --- /dev/null +++ b/proofs/generate_proof.py @@ -0,0 +1,88 @@ +import argparse +import json +import rlp + +from proof_utils import request_block_header, request_account_proof + +def main(): + # Parse command line arguments + parser = argparse.ArgumentParser( + description="Patricia Merkle Trie Proof Generating Tool", + formatter_class=argparse.RawTextHelpFormatter) + + parser.add_argument("-b", "--block-number", + type=int, + help="Block number, defaults to `latest - 15`") + + parser.add_argument("-r", "--rpc", + default="http://localhost:8545", + type=str, + help="URL of a full node RPC endpoint, e.g. http://localhost:8545") + + parser.add_argument("--contract", + type=str, + help="Storage mirror contract address") + + parser.add_argument("--slot", + type=str, + help="Storage slot to generate proof for") + + # Save command line arguments into variables + args = parser.parse_args() + + rpc_endpoint = args.rpc + block_number = args.block_number + storage_mirror_contract_address = args.contract + storage_slot = args.slot + + # Generate proof data + (block_number, block_header, acct_proof, storage_proofs) = generate_proof_data(rpc_endpoint, block_number, storage_mirror_contract_address, [storage_slot]) + + # Encode the proof data + account_proof = rlp.encode(acct_proof) + storage_proof = rlp.encode(storage_proofs[0]) + encoded_block_header = rlp.encode(block_header) + + # Output the proof data in JSON format + output = { + "blockNumber": block_number, + "blockHeader": encoded_block_header.hex(), + "accountProof": account_proof.hex(), + "storageProof": storage_proof.hex() + } + + print(json.dumps(output)) + +def generate_proof_data( + rpc_endpoint, + block_number, + address, + slots +): + block_number = \ + block_number if block_number == "latest" or block_number == "earliest" \ + else hex(int(block_number)) + + # Block header is currently not being calculated correctly + (block_number, block_header) = request_block_header( + rpc_endpoint=rpc_endpoint, + block_number=block_number, + ) + + (acct_proof, storage_proofs) = request_account_proof( + rpc_endpoint=rpc_endpoint, + block_number=block_number, + address=address, + slots=slots, + ) + + return ( + block_number, + block_header, + acct_proof, + storage_proofs + ) + +if __name__ == "__main__": + main() + exit(0) diff --git a/proofs/proof_utils.py b/proofs/proof_utils.py new file mode 100644 index 0000000..ed2caed --- /dev/null +++ b/proofs/proof_utils.py @@ -0,0 +1,61 @@ +import requests +import rlp + +from utils import normalize_bytes, normalize_int, decode_hex, to_0x_string + + +BLOCK_HEADER_FIELDS = [ + "parentHash", "sha3Uncles", "miner", "stateRoot", "transactionsRoot", + "receiptsRoot", "logsBloom", "difficulty", "number", "gasLimit", + "gasUsed", "timestamp", "extraData", "mixHash", "nonce" +] + + +def request_block_header(rpc_endpoint, block_number): + r = requests.post(rpc_endpoint, json={ + "jsonrpc": "2.0", + "method": "eth_getBlockByNumber", + "params": [block_number, True], + "id": 1, + }) + + block_dict = get_json_rpc_result(r) + block_number = normalize_int(block_dict["number"]) + block_header_fields = [normalize_bytes(block_dict[f]) for f in BLOCK_HEADER_FIELDS] + + return (block_number, block_header_fields) + + +def request_account_proof(rpc_endpoint, block_number, address, slots): + hex_slots = [to_0x_string(s) for s in slots] + + r = requests.post(rpc_endpoint, json={ + "jsonrpc": "2.0", + "method": "eth_getProof", + "params": [address.lower(), hex_slots, to_0x_string(block_number)], + "id": 1, + }) + + result = get_json_rpc_result(r) + + account_proof = decode_rpc_proof(result["accountProof"]) + storage_proofs = [ + decode_rpc_proof(slot_data["proof"]) for slot_data in result["storageProof"] + ] + + return (account_proof, storage_proofs) + + +def decode_rpc_proof(proof_data): + return [rlp.decode(decode_hex(node)) for node in proof_data] + + +def get_json_rpc_result(response): + response.raise_for_status() + json_dict = response.json() + if "error" in json_dict: + raise requests.RequestException( + f"RPC error { json_dict['error']['code'] }: { json_dict['error']['message'] }", + response=response + ) + return json_dict["result"] \ No newline at end of file diff --git a/proofs/utils.py b/proofs/utils.py new file mode 100644 index 0000000..1d0ca28 --- /dev/null +++ b/proofs/utils.py @@ -0,0 +1,23 @@ +# Partially taken from: https://github.com/ethereum/pyethereum/blob/b704a5c/ethereum/utils.py + +from eth_utils import decode_hex, to_canonical_address, to_bytes, to_int, to_hex + + +def normalize_bytes(x): + return to_bytes(hexstr=x) if isinstance(x, str) else to_bytes(x) + + +def normalize_address(x): + return to_canonical_address(x) + + +def normalize_int(x): + if isinstance(x, str) and not x.startswith("0x"): + x = int(x) + return to_int(hexstr=x) if isinstance(x, str) else to_int(x) + + +def to_0x_string(x): + if isinstance(x, str) and not x.startswith("0x"): + x = int(x) + return to_hex(x) \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..77d4b6f --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +eth-utils==1.10.0 +requests==2.21.0 +rlp==2.0.1 \ No newline at end of file From bf92614f89191911d3bd3025841b4518d5ae209a Mon Sep 17 00:00:00 2001 From: excaliborr <124819095+excaliborr@users.noreply.github.com> Date: Thu, 23 Nov 2023 09:15:54 -0500 Subject: [PATCH 21/29] feat: periphery function to do the verification flow in one transaction (#11) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # 🤖 Linear Closes SAF-37 Needs to be merged after PR #9 --- solidity/contracts/VerifierModule.sol | 22 +++++ solidity/interfaces/IVerifierModule.sol | 18 +++- solidity/test/unit/VerifierModule.t.sol | 125 +++++++++++++++++++++++- yarn.lock | 69 ++++++++++++- 4 files changed, 229 insertions(+), 5 deletions(-) diff --git a/solidity/contracts/VerifierModule.sol b/solidity/contracts/VerifierModule.sol index 64775f4..1e24c51 100644 --- a/solidity/contracts/VerifierModule.sol +++ b/solidity/contracts/VerifierModule.sol @@ -56,6 +56,27 @@ contract VerifierModule is IVerifierModule { STORAGE_MIRROR = _storageMirror; } + /** + * @notice Sets the storage mirror storage root in the registry, verifies it, and then updates the safe in one call + * + * @param _safe The address of the safe that has new settings + * @param _proposedSettings The new settings that are being proposed + * @param _storageMirrorAccountProof The account proof of the StorageMirror contract on the home chain + * @param _storageMirrorStorageProof The storage proof of the StorageMirror contract on the home chain + * @param _arbitraryTxnParams The transaction parameters for the arbitrary safe transaction that will execute + */ + + function extractStorageRootAndVerifyUpdate( + address _safe, + IStorageMirror.SafeSettings calldata _proposedSettings, + bytes memory _storageMirrorAccountProof, + bytes memory _storageMirrorStorageProof, + SafeTxnParams calldata _arbitraryTxnParams + ) external { + STORAGE_MIRROR_ROOT_REGISTRY.proposeAndVerifyStorageMirrorStorageRoot(_storageMirrorAccountProof); + _proposeAndVerifyUpdate(_safe, _proposedSettings, _storageMirrorStorageProof, _arbitraryTxnParams); + } + /** * @notice Verifies the new settings that are incoming against a storage proof from the StorageMirror on the home chain * @@ -80,6 +101,7 @@ contract VerifierModule is IVerifierModule { * @param _blockHeader The block header of the latest block * @return _storageRoot The verified storage root * @return _blockNumber The block number from the _blockHeader + */ function extractStorageMirrorStorageRoot( bytes memory _storageMirrorAccountProof, diff --git a/solidity/interfaces/IVerifierModule.sol b/solidity/interfaces/IVerifierModule.sol index de9bc9f..d97bd1e 100644 --- a/solidity/interfaces/IVerifierModule.sol +++ b/solidity/interfaces/IVerifierModule.sol @@ -24,7 +24,6 @@ interface IVerifierModule { /** * @notice Reverts when the bytes cannot be converted to bytes32 */ - error VerifierModule_BytesToBytes32Failed(); /*/////////////////////////////////////////////////////////////// @@ -109,4 +108,21 @@ interface IVerifierModule { bytes memory _storageMirrorStorageProof, SafeTxnParams calldata _arbitraryTxnParams ) external; + + /** + * @notice Sets the storage mirror storage root in the registry, verifies it, and then updates the safe in one call + * + * @param _safe The address of the safe that has new settings + * @param _proposedSettings The new settings that are being proposed + * @param _storageMirrorAccountProof The account proof of the StorageMirror contract on the home chain + * @param _storageMirrorStorageProof The storage proof of the StorageMirror contract on the home chain + * @param _arbitraryTxnParams The transaction parameters for the arbitrary safe transaction that will execute + */ + function extractStorageRootAndVerifyUpdate( + address _safe, + IStorageMirror.SafeSettings calldata _proposedSettings, + bytes memory _storageMirrorAccountProof, + bytes memory _storageMirrorStorageProof, + SafeTxnParams calldata _arbitraryTxnParams + ) external; } diff --git a/solidity/test/unit/VerifierModule.t.sol b/solidity/test/unit/VerifierModule.t.sol index b1a79ca..dc837b8 100644 --- a/solidity/test/unit/VerifierModule.t.sol +++ b/solidity/test/unit/VerifierModule.t.sol @@ -82,13 +82,24 @@ contract TestVerifierModule is VerifierModule { _bytes32 = _bytesToBytes32(_bytes); } + function extractStorageRootAndVerifyUpdateTest( + address _safe, + IStorageMirror.SafeSettings calldata _proposedSettings, + bytes memory _storageMirrorAccountProof, + bytes memory _storageMirrorStorageProof, + SafeTxnParams calldata _arbitraryTxnParams + ) external { + STORAGE_MIRROR_ROOT_REGISTRY.proposeAndVerifyStorageMirrorStorageRoot(_storageMirrorAccountProof); + proposeAndVerifyUpdateTest(_safe, _proposedSettings, _storageMirrorStorageProof, _arbitraryTxnParams); + } + // NOTE: Needs to match the proposeAndVerify function logic with the fake MPT because we cant mock librariers function proposeAndVerifyUpdateTest( address _safe, IStorageMirror.SafeSettings calldata _proposedSettings, bytes memory _storageMirrorStorageProof, IVerifierModule.SafeTxnParams calldata _safeTxnParams - ) external { + ) public { bytes32 _hashedProposedSettings = verifyNewSettings(_safe, _proposedSettings, _storageMirrorStorageProof); // If we dont revert from the _verifyNewSettings() call, then we can update the safe @@ -137,7 +148,7 @@ contract TestVerifierModule is VerifierModule { // Extract the storage root from the output of the MPT _storageRoot = mpt.extractStorageRootFromAccount(_rlpAccount); - _blockNumber = 500; + _blockNumber = _parsedBlockHeader.number; } } @@ -720,6 +731,116 @@ contract UnitMerklePatriciaTree is Base { ); assertEq(verifierModule.latestVerifiedSettingsTimestamp(_fakeSafe), _fakeTimestamp, 'Timestamp should be updated'); } + + function testExtractRootAndVerifyUpdate( + IStorageMirror.SafeSettings memory _fakeSettings, + bytes memory _accountProof + ) public { + uint256 _fakeTimestamp = 1_234_567_890; + + vm.warp(_fakeTimestamp); + vm.assume(_accountProof.length > 0); + + address[] memory _oldOwners = _fakeSettings.owners; + + vm.mockCall(_fakeSafe, abi.encodeWithSelector(ISafe.getOwners.selector), abi.encode(_oldOwners)); + + vm.mockCall(_fakeSafe, abi.encodeWithSelector(ISafe.getThreshold.selector), abi.encode(_fakeSettings.threshold)); + + vm.mockCall(_fakeSafe, abi.encodeWithSelector(ISafe.isOwner.selector), abi.encode(true)); + + vm.mockCall( + _fakeSafe, + abi.encodeWithSelector( + ISafe.execTransactionFromModule.selector, + _fakeSafe, + 0, + abi.encodeWithSelector(ISafe.addOwnerWithThreshold.selector, address(0x4), 2), + Enum.Operation.Call + ), + abi.encode(true) + ); + + bytes32 _fakeStorageRoot = keccak256(abi.encode(bytes32(uint256(1)))); + + bytes memory _storageProof = hex'e10e2d527612073b26eecdfd717e6a320f'; + + vm.mockCall( + address(_storageMirrorRegistry), + abi.encodeWithSelector(IStorageMirrorRootRegistry.latestVerifiedStorageMirrorStorageRoot.selector), + abi.encode(_fakeStorageRoot) + ); + + bytes32 _safeSettingsSlot = keccak256(abi.encode(_fakeSafe, 0)); + + bytes32 _safeSettingsSlotHash = keccak256(abi.encode(_safeSettingsSlot)); + + bytes memory _expectedOutput = abi.encodePacked(keccak256(abi.encode(_fakeSettings))); + + vm.mockCall( + address(mpt), + abi.encodeWithSelector( + TestMPT.extractProofValue.selector, _fakeStorageRoot, abi.encodePacked(_safeSettingsSlotHash), _storageProof + ), + abi.encode(_expectedOutput) + ); + + IVerifierModule.SafeTxnParams memory _txDetails = IVerifierModule.SafeTxnParams({ + to: _fakeSafe, + value: 0, + data: abi.encodeWithSelector(ISafe.addOwnerWithThreshold.selector, address(0x4), 2), + operation: Enum.Operation.Call, + safeTxGas: 0, + baseGas: 0, + gasPrice: 0, + gasToken: address(0), + refundReceiver: payable(address(0)), + signatures: '' + }); + + vm.mockCall( + _fakeSafe, + abi.encodeWithSelector(ISafe.execTransactionFromModule.selector, address(this), 1e18, '', Enum.Operation.Call), + abi.encode(true) + ); + + vm.mockCall( + _fakeSafe, + abi.encodeWithSelector( + ISafe.execTransaction.selector, + _txDetails.to, + _txDetails.value, + _txDetails.data, + _txDetails.operation, + _txDetails.safeTxGas, + _txDetails.baseGas, + _txDetails.gasPrice, + _txDetails.gasToken, + _txDetails.refundReceiver, + _txDetails.signatures + ), + abi.encode(true) + ); + + vm.mockCall( + address(_storageMirrorRegistry), + abi.encodeWithSelector( + IStorageMirrorRootRegistry.proposeAndVerifyStorageMirrorStorageRoot.selector, _accountProof + ), + abi.encode() + ); + + vm.expectCall( + address(_storageMirrorRegistry), + abi.encodeWithSelector( + IStorageMirrorRootRegistry.proposeAndVerifyStorageMirrorStorageRoot.selector, _accountProof + ) + ); + + verifierModule.extractStorageRootAndVerifyUpdateTest( + _fakeSafe, _fakeSettings, _accountProof, _storageProof, _txDetails + ); + } } contract UnitStorageRoot is Base { diff --git a/yarn.lock b/yarn.lock index 21376eb..228b8f5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -200,6 +200,15 @@ ds-test "https://github.com/dapphub/ds-test" forge-std "https://github.com/foundry-rs/forge-std" +"@defi-wonderland/solidity-utils@0.0.0-4298c6c6": + version "0.0.0-4298c6c6" + resolved "https://registry.yarnpkg.com/@defi-wonderland/solidity-utils/-/solidity-utils-0.0.0-4298c6c6.tgz#4ac9e4bcc586f7434715357310a7a3c30e81f17f" + integrity sha512-jpecgVx9hpnXXGnYnN8ayd1ONj4ljk0hI36ltNfkNwlDkLLzPt4/FY5YSyY/628Exfuy5X9Rl9gGv5UtYgAmtw== + dependencies: + "@openzeppelin/contracts" "4.8.1" + ds-test "https://github.com/dapphub/ds-test" + forge-std "https://github.com/foundry-rs/forge-std" + "@jridgewell/resolve-uri@^3.0.3": version "3.1.1" resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz#c08679063f279615a3326583ba3a90d1d82cc721" @@ -239,6 +248,11 @@ "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" +"@openzeppelin/contracts@4.8.1": + version "4.8.1" + resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-4.8.1.tgz#709cfc4bbb3ca9f4460d60101f15dac6b7a2d5e4" + integrity sha512-xQ6eUZl+RDyb/FiZe1h+U7qr/f4p/SrTSQcTPH2bjur3C5DbuW/zFgCU/b1P/xcIaEqJep+9ju4xDRi3rmChdQ== + "@openzeppelin/contracts@4.9.2": version "4.9.2" resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-4.9.2.tgz#1cb2d5e4d3360141a17dbc45094a8cad6aac16c1" @@ -805,6 +819,11 @@ dot-prop@^5.1.0: dependencies: is-obj "^2.0.0" +"ds-test@git+https://github.com/dapphub/ds-test.git": + version "1.0.0" + uid e282159d5170298eb2455a6c05280ab5a73a4ef0 + resolved "git+https://github.com/dapphub/ds-test.git#e282159d5170298eb2455a6c05280ab5a73a4ef0" + "ds-test@github:dapphub/ds-test#e282159": version "1.0.0" resolved "https://codeload.github.com/dapphub/ds-test/tar.gz/e282159d5170298eb2455a6c05280ab5a73a4ef0" @@ -1083,6 +1102,11 @@ flatted@^2.0.0: resolved "https://registry.yarnpkg.com/flatted/-/flatted-2.0.2.tgz#4575b21e2bcee7434aa9be662f4b7b5f9c2b5138" integrity sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA== +"forge-std@git+https://github.com/foundry-rs/forge-std.git": + version "1.7.2" + uid bdea49f9bb3c58c8c35850c3bdc17eaeea756e9a + resolved "git+https://github.com/foundry-rs/forge-std.git#bdea49f9bb3c58c8c35850c3bdc17eaeea756e9a" + "forge-std@github:foundry-rs/forge-std#v1.5.6": version "1.5.6" resolved "https://codeload.github.com/foundry-rs/forge-std/tar.gz/e8a047e3f40f13fa37af6fe14e6e06283d9a060e" @@ -1091,6 +1115,15 @@ flatted@^2.0.0: version "1.7.2" resolved "https://github.com/foundry-rs/forge-std#bdea49f9bb3c58c8c35850c3bdc17eaeea756e9a" +foundry-mock-generator@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/foundry-mock-generator/-/foundry-mock-generator-1.0.1.tgz#7473cc24920a65cdd70ed14cc5f6896fc3be76de" + integrity sha512-tl9gkhsB8V+FqLjz/WNusuAKDpUXTqbs/KC9roVKaqc7d9oC+2EDLtHRt9bHWEKTE9ocsCs2nH9+r8letnZsLQ== + dependencies: + "@defi-wonderland/solidity-utils" "0.0.0-4298c6c6" + handlebars "4.7.7" + yargs "17.7.2" + fs-extra@^11.0.0: version "11.1.1" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-11.1.1.tgz#da69f7c39f3b002378b0954bb6ae7efdc0876e2d" @@ -1191,6 +1224,18 @@ graceful-fs@^4.1.6, graceful-fs@^4.2.0: resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== +handlebars@4.7.7: + version "4.7.7" + resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.7.7.tgz#9ce33416aad02dbd6c8fafa8240d5d98004945a1" + integrity sha512-aAcXm5OAfE/8IXkcZvCepKU3VzW1/39Fb5ZuqMtgI/hT8X2YgoMvBY5dLhq/cpOvw7Lk1nK/UF71aLG/ZnVYRA== + dependencies: + minimist "^1.2.5" + neo-async "^2.6.0" + source-map "^0.6.1" + wordwrap "^1.0.0" + optionalDependencies: + uglify-js "^3.1.4" + hard-rejection@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/hard-rejection/-/hard-rejection-2.1.0.tgz#1c6eda5c1685c63942766d79bb40ae773cecd883" @@ -1692,7 +1737,7 @@ minimist-options@4.1.0: is-plain-obj "^1.1.0" kind-of "^6.0.3" -minimist@^1.2.6: +minimist@^1.2.5, minimist@^1.2.6: version "1.2.8" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== @@ -1719,6 +1764,11 @@ natural-compare@^1.4.0: resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== +neo-async@^2.6.0: + version "2.6.2" + resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f" + integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== + nice-try@^1.0.4: version "1.0.5" resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" @@ -2283,6 +2333,11 @@ sort-package-json@1.53.1: is-plain-obj "2.1.0" sort-object-keys "^1.1.3" +source-map@^0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== + spdx-correct@^3.0.0: version "3.2.0" resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.2.0.tgz#4f5ab0668f0059e34f9c00dce331784a12de4e9c" @@ -2549,6 +2604,11 @@ type-fest@^0.8.1: resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.1.3.tgz#8d84219244a6b40b6fb2b33cc1c062f715b9e826" integrity sha512-XH627E9vkeqhlZFQuL+UsyAXEnibT0kWR2FWONlr4sTjvxyJYnyefgrkyECLzM5NenmKzRAy2rR/OlYLA1HkZw== +uglify-js@^3.1.4: + version "3.17.4" + resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.17.4.tgz#61678cf5fa3f5b7eb789bb345df29afb8257c22c" + integrity sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g== + universalify@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.0.tgz#75a4984efedc4b08975c5aeb73f530d02df25717" @@ -2598,6 +2658,11 @@ word-wrap@~1.2.3: resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== +wordwrap@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" + integrity sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q== + wrap-ansi@^6.2.0: version "6.2.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz#e9393ba07102e6c91a3b221478f0257cd2856e53" @@ -2653,7 +2718,7 @@ yargs-parser@^21.1.1: resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35" integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== -yargs@^17.0.0: +yargs@17.7.2, yargs@^17.0.0: version "17.7.2" resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269" integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w== From 59ac7804534359ff128c7c65887befbc645c3288 Mon Sep 17 00:00:00 2001 From: 0xOneTony <112496816+0xOneTony@users.noreply.github.com> Date: Thu, 23 Nov 2023 16:17:12 +0200 Subject: [PATCH 22/29] feat: deploy scripts (#19) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # 🤖 Linear Closes SAF-XXX --- .env.example | 13 +++-- package.json | 6 ++- remappings.txt | 3 +- solidity/scripts/DeployGoerli.s.sol | 20 ++++++++ solidity/scripts/DeployHomeChain.s.sol | 46 ++++++++++++++++++ solidity/scripts/DeployMainnet.s.sol | 20 ++++++++ solidity/scripts/DeployNonHomeChain.s.sol | 53 +++++++++++++++++++++ solidity/scripts/DeployOptimism.s.sol | 21 ++++++++ solidity/scripts/DeployOptimismGoerli.s.sol | 20 ++++++++ 9 files changed, 196 insertions(+), 6 deletions(-) create mode 100644 solidity/scripts/DeployGoerli.s.sol create mode 100644 solidity/scripts/DeployHomeChain.s.sol create mode 100644 solidity/scripts/DeployMainnet.s.sol create mode 100644 solidity/scripts/DeployNonHomeChain.s.sol create mode 100644 solidity/scripts/DeployOptimism.s.sol create mode 100644 solidity/scripts/DeployOptimismGoerli.s.sol diff --git a/.env.example b/.env.example index aa5efdc..e44c7bd 100644 --- a/.env.example +++ b/.env.example @@ -1,7 +1,14 @@ +## For e2e tests and deployment scripts MAINNET_RPC= -MAINNET_DEPLOYER_PK= - GOERLI_RPC= -GOERLI_DEPLOYER_PK= +OPTIMISM_RPC= +OPTIMISM_GOERLI_RPC= + +## For deployment scripts +DEPLOYER_MAINNNET_PRIVATE_KEY= +DEPLOYER_GOERLI_PRIVATE_KEY= +DEPLOYER_OPTIMISM_PRIVATE_KEY= +DEPLOYER_OPTIMISM_GOERLI_PRIVATE_KEY= +STORAGE_MIRROR_ADDRESS= ETHERSCAN_API_KEY= diff --git a/package.json b/package.json index bcc1561..238087d 100644 --- a/package.json +++ b/package.json @@ -13,8 +13,10 @@ "build": "forge build", "build:optimized": "FOUNDRY_PROFILE=optimized forge build", "coverage": "forge coverage --match-contract Unit", - "deploy:goerli": "bash -c 'source .env && forge script DeployGoerli --rpc-url $GOERLI_RPC --broadcast --private-key $GOERLI_DEPLOYER_PK --verify --etherscan-api-key $ETHERSCAN_API_KEY'", - "deploy:mainnet": "bash -c 'source .env && forge script DeployMainnet --rpc-url $MAINNET_RPC --broadcast --private-key $MAINNET_DEPLOYER_PK --verify --etherscan-api-key $ETHERSCAN_API_KEY'", + "deploy:goerli": "bash -c 'source .env && forge script -vv --rpc-url $GOERLI_RPC --slow --broadcast --private-key $DEPLOYER_GOERLI_PRIVATE_KEY solidity/scripts/DeployGoerli.s.sol:DeployGoerli'", + "deploy:mainnet": "bash -c 'source .env && forge script -vv --rpc-url $MAINNET_RPC --slow --broadcast --private-key $DEPLOYER_MAINNNET_PRIVATE_KEY solidity/scripts/DeployMainnet.s.sol:DeployMainnet'", + "deploy:optimism": "bash -c 'source .env && forge script -vv --rpc-url $OPTIMISM_RPC --slow --broadcast --private-key $DEPLOYER_OPTIMISM_PRIVATE_KEY solidity/scripts/DeployOptimism.s.sol:DeployOptimism'", + "deploy:optimismGoerli": "bash -c 'source .env && forge script -vv --rpc-url $OPTIMISM_GOERLI_RPC --slow --broadcast --private-key $DEPLOYER_OPTIMISM_GOERLI_PRIVATE_KEY solidity/scripts/DeployOptimismGoerli.s.sol:DeployOptimismGoerli'", "lint:check": "yarn lint:sol-tests && yarn lint:sol-logic && forge fmt check", "lint:fix": "sort-package-json && forge fmt && yarn lint:sol-tests --fix && yarn lint:sol-logic --fix", "lint:sol-logic": "solhint -c .solhint.json 'solidity/contracts/**/*.sol' 'solidity/interfaces/**/*.sol'", diff --git a/remappings.txt b/remappings.txt index 549815a..f68a97b 100644 --- a/remappings.txt +++ b/remappings.txt @@ -9,4 +9,5 @@ solidity-rlp/=node_modules/Solidity-RLP contracts/=solidity/contracts interfaces/=solidity/interfaces test/=solidity/test -libraries/=solidity/libraries \ No newline at end of file +libraries/=solidity/libraries +scripts/=solidity/scripts/ \ No newline at end of file diff --git a/solidity/scripts/DeployGoerli.s.sol b/solidity/scripts/DeployGoerli.s.sol new file mode 100644 index 0000000..46e98d6 --- /dev/null +++ b/solidity/scripts/DeployGoerli.s.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity =0.8.19; + +import {DeployHomeChain, DeployVars} from 'scripts/DeployHomeChain.s.sol'; + +// We threat Goerli as the Home Chain in this case +contract DeployGoerli is DeployHomeChain { + address public deployer = vm.rememberKey(vm.envUint('DEPLOYER_GOERLI_PRIVATE_KEY')); + + function run() external { + vm.startBroadcast(deployer); + + DeployVars memory _deployVars = DeployVars(deployer); + + // Deploy protocol + _deploy(_deployVars); + + vm.stopBroadcast(); + } +} diff --git a/solidity/scripts/DeployHomeChain.s.sol b/solidity/scripts/DeployHomeChain.s.sol new file mode 100644 index 0000000..70fbc14 --- /dev/null +++ b/solidity/scripts/DeployHomeChain.s.sol @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity =0.8.19; + +import {Script} from 'forge-std/Script.sol'; +import {console} from 'forge-std/console.sol'; + +import {StorageMirror} from 'contracts/StorageMirror.sol'; +import {UpdateStorageMirrorGuard} from 'contracts/UpdateStorageMirrorGuard.sol'; +import {GuardCallbackModule} from 'contracts/GuardCallbackModule.sol'; + +import {TestConstants} from 'test/utils/TestConstants.sol'; +import {ContractDeploymentAddress} from 'test/utils/ContractDeploymentAddress.sol'; + +struct DeployVars { + address deployer; +} + +abstract contract DeployHomeChain is Script, TestConstants { + function _deploy(DeployVars memory _deployVars) + internal + returns ( + StorageMirror _storageMirror, + UpdateStorageMirrorGuard _updateStorageMirrorGuard, + GuardCallbackModule _guardCallbackModule + ) + { + // Current nonce saved + uint256 _currentNonce = vm.getNonce(_deployVars.deployer); + + // Guard theoritical address since we need it for the deployment of the module + address _updateStorageMirrorGuardTheoriticalAddress = + ContractDeploymentAddress.addressFrom(_deployVars.deployer, _currentNonce + 2); + + // Deploy storage mirror + _storageMirror = new StorageMirror(); // deployer nonce 0 + console.log('STORAGE_MIRROR: ', address(_storageMirror)); + + _guardCallbackModule = new GuardCallbackModule(address(_storageMirror), _updateStorageMirrorGuardTheoriticalAddress); // deployer nonce 1 + console.log('GUARD_CALLBACK_MODULE: ', address(_guardCallbackModule)); + + _updateStorageMirrorGuard = new UpdateStorageMirrorGuard(_guardCallbackModule); // deployer nonce 2 + console.log('UPDATE_STORAGE_MIRROR_GUARD: ', address(_updateStorageMirrorGuard)); + + console.log('DEPLOYMENT DONE'); + } +} diff --git a/solidity/scripts/DeployMainnet.s.sol b/solidity/scripts/DeployMainnet.s.sol new file mode 100644 index 0000000..89ebe93 --- /dev/null +++ b/solidity/scripts/DeployMainnet.s.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity =0.8.19; + +import {DeployHomeChain, DeployVars} from 'scripts/DeployHomeChain.s.sol'; + +// We threat Mainnet as the Home Chain in this case +contract DeployMainnet is DeployHomeChain { + address public deployer = vm.rememberKey(vm.envUint('DEPLOYER_MAINNNET_PRIVATE_KEY')); + + function run() external { + vm.startBroadcast(deployer); + + DeployVars memory _deployVars = DeployVars(deployer); + + // Deploy protocol + _deploy(_deployVars); + + vm.stopBroadcast(); + } +} diff --git a/solidity/scripts/DeployNonHomeChain.s.sol b/solidity/scripts/DeployNonHomeChain.s.sol new file mode 100644 index 0000000..f06fcfc --- /dev/null +++ b/solidity/scripts/DeployNonHomeChain.s.sol @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity =0.8.19; + +import {Script} from 'forge-std/Script.sol'; +import {console} from 'forge-std/console.sol'; + +import {BlockHeaderOracle} from 'contracts/BlockHeaderOracle.sol'; +import {NeedsUpdateGuard} from 'contracts/NeedsUpdateGuard.sol'; +import {StorageMirrorRootRegistry} from 'contracts/StorageMirrorRootRegistry.sol'; +import {VerifierModule} from 'contracts/VerifierModule.sol'; +import {IVerifierModule} from 'interfaces/IVerifierModule.sol'; +import {IBlockHeaderOracle} from 'interfaces/IBlockHeaderOracle.sol'; +import {IStorageMirrorRootRegistry} from 'interfaces/IStorageMirrorRootRegistry.sol'; + +import {TestConstants} from 'test/utils/TestConstants.sol'; +import {ContractDeploymentAddress} from 'test/utils/ContractDeploymentAddress.sol'; + +struct DeployVars { + address deployer; + address storageMirror; +} + +abstract contract DeployNonHomeChain is Script, TestConstants { + function _deploy(DeployVars memory _deployVars) + internal + returns ( + BlockHeaderOracle _blockHeaderOracle, + NeedsUpdateGuard _needsUpdateGuard, + StorageMirrorRootRegistry _storageMirrorRootRegistry, + VerifierModule _verifierModule + ) + { + // Current nonce saved + uint256 _currentNonce = vm.getNonce(_deployVars.deployer); + + address _storageMirrorRootRegistryTheoriticalAddress = + ContractDeploymentAddress.addressFrom(_deployVars.deployer, _currentNonce + 2); + + _blockHeaderOracle = new BlockHeaderOracle(); // deployer nonce 0 + console.log('ORACLE: ', address(_blockHeaderOracle)); + + _verifierModule = + new VerifierModule(IStorageMirrorRootRegistry(_storageMirrorRootRegistryTheoriticalAddress), address(_deployVars.storageMirror)); // deployer nonce 1 + console.log('VERIFIER_MODULE: ', address(_verifierModule)); + + _storageMirrorRootRegistry = + new StorageMirrorRootRegistry(address(_deployVars.storageMirror), IVerifierModule(_verifierModule), IBlockHeaderOracle(_blockHeaderOracle)); // deployer nonce 2 + console.log('STORAGE_MIRROR_ROOT_REGISTRY: ', address(_storageMirrorRootRegistry)); + + _needsUpdateGuard = new NeedsUpdateGuard(_verifierModule); // deployer nonce 3 + console.log('NEEDS_UPDATE_GUARD: ', address(_needsUpdateGuard)); + } +} diff --git a/solidity/scripts/DeployOptimism.s.sol b/solidity/scripts/DeployOptimism.s.sol new file mode 100644 index 0000000..ad8f9f4 --- /dev/null +++ b/solidity/scripts/DeployOptimism.s.sol @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity =0.8.19; + +import {DeployNonHomeChain, DeployVars} from 'scripts/DeployNonHomeChain.s.sol'; + +// We threat Goerli as the Home Chain in this case +contract DeployOptimism is DeployNonHomeChain { + address public deployer = vm.rememberKey(vm.envUint('DEPLOYER_OPTIMISM_PRIVATE_KEY')); + address public storageMirrorAddress = vm.envAddress('STORAGE_MIRROR_ADDRESS'); + + function run() external { + vm.startBroadcast(deployer); + + DeployVars memory _deployVars = DeployVars(deployer, storageMirrorAddress); + + // Deploy protocol + _deploy(_deployVars); + + vm.stopBroadcast(); + } +} diff --git a/solidity/scripts/DeployOptimismGoerli.s.sol b/solidity/scripts/DeployOptimismGoerli.s.sol new file mode 100644 index 0000000..c39026c --- /dev/null +++ b/solidity/scripts/DeployOptimismGoerli.s.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity =0.8.19; + +import {DeployNonHomeChain, DeployVars} from 'scripts/DeployNonHomeChain.s.sol'; + +// We threat Goerli as the Home Chain in this case +contract DeployOptimismGoerli is DeployNonHomeChain { + address public deployer = vm.rememberKey(vm.envUint('DEPLOYER_OPTIMISM_PRIVATE_KEY')); + address public storageMirrorAddress = vm.envAddress('STORAGE_MIRROR_ADDRESS'); + + function run() external { + vm.startBroadcast(deployer); + + DeployVars memory _deployVars = DeployVars(deployer, storageMirrorAddress); + // Deploy protocol + _deploy(_deployVars); + + vm.stopBroadcast(); + } +} From 7ea5dc25b500c81c9cf7eaa88d8939b4d24238eb Mon Sep 17 00:00:00 2001 From: 0xOneTony <112496816+0xOneTony@users.noreply.github.com> Date: Thu, 23 Nov 2023 17:24:16 +0200 Subject: [PATCH 23/29] fix: deploy script (#21) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # 🤖 Linear Closes SAF-XXX --- solidity/contracts/VerifierModule.sol | 1 - solidity/scripts/DeployOptimismGoerli.s.sol | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/solidity/contracts/VerifierModule.sol b/solidity/contracts/VerifierModule.sol index 1e24c51..b871dec 100644 --- a/solidity/contracts/VerifierModule.sol +++ b/solidity/contracts/VerifierModule.sol @@ -101,7 +101,6 @@ contract VerifierModule is IVerifierModule { * @param _blockHeader The block header of the latest block * @return _storageRoot The verified storage root * @return _blockNumber The block number from the _blockHeader - */ function extractStorageMirrorStorageRoot( bytes memory _storageMirrorAccountProof, diff --git a/solidity/scripts/DeployOptimismGoerli.s.sol b/solidity/scripts/DeployOptimismGoerli.s.sol index c39026c..e9d8bc9 100644 --- a/solidity/scripts/DeployOptimismGoerli.s.sol +++ b/solidity/scripts/DeployOptimismGoerli.s.sol @@ -5,7 +5,7 @@ import {DeployNonHomeChain, DeployVars} from 'scripts/DeployNonHomeChain.s.sol'; // We threat Goerli as the Home Chain in this case contract DeployOptimismGoerli is DeployNonHomeChain { - address public deployer = vm.rememberKey(vm.envUint('DEPLOYER_OPTIMISM_PRIVATE_KEY')); + address public deployer = vm.rememberKey(vm.envUint('DEPLOYER_OPTIMISM_GOERLI_PRIVATE_KEY')); address public storageMirrorAddress = vm.envAddress('STORAGE_MIRROR_ADDRESS'); function run() external { From ab26a81b984aad2f623e020017e9dad91a6e7324 Mon Sep 17 00:00:00 2001 From: 0xOneTony <112496816+0xOneTony@users.noreply.github.com> Date: Thu, 23 Nov 2023 17:55:43 +0200 Subject: [PATCH 24/29] feat: mdbook docs (#22) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # 🤖 Linear Closes SAF-XXX --- .gitignore | 5 +++ build-docs.sh | 38 +++++++++++++++++++ docs/src/SUMMARY.md | 14 +++++++ docs/src/content/smart-contracts/addresses.md | 1 + foundry.toml | 3 ++ package.json | 2 + 6 files changed, 63 insertions(+) create mode 100755 build-docs.sh create mode 100644 docs/src/SUMMARY.md create mode 100644 docs/src/content/smart-contracts/addresses.md diff --git a/.gitignore b/.gitignore index 00c39fb..2854bbe 100644 --- a/.gitignore +++ b/.gitignore @@ -19,5 +19,10 @@ out/*/* # Keep the latest deployment only broadcast/*/*/* +# Docs build files +docs/book +docs/src/solidity/interfaces +docs/src/static + # Cache for the python scripts proofs/__pycache__/* diff --git a/build-docs.sh b/build-docs.sh new file mode 100755 index 0000000..78ba1d5 --- /dev/null +++ b/build-docs.sh @@ -0,0 +1,38 @@ +#!/bin/bash + +# generate docs in a temporary directory +FOUNDRY_PROFILE=docs forge doc --out tmp/safe-liveness-technical-docs + +# edit generated summary not to have container pages +# - [jobs](solidity/interfaces/jobs/README.md) +# should become +# - [jobs]() +# TODO + +# edit generated summary titles to start with an uppercase letter +# - [jobs]() +# should become +# - [Jobs]() +# TODO + +# edit the SUMMARY after the Interfaces section +# https://stackoverflow.com/questions/67086574/no-such-file-or-directory-when-using-sed-in-combination-with-find +if [[ "$OSTYPE" == "darwin"* ]]; then + sed -i '' -e '/\[Interfaces\]/q' docs/src/SUMMARY.md +else + sed -i -e '/\[Interfaces\]/q' docs/src/SUMMARY.md +fi +# copy the generated SUMMARY, from the tmp directory, without the first 5 lines +# and paste them after the Interfaces section on the original SUMMARY +tail -n +5 tmp/safe-liveness-technical-docs/src/SUMMARY.md >> docs/src/SUMMARY.md + +# delete old generated interfaces docs +rm -rf docs/src/solidity/interfaces +# there are differences in cp and mv behavior between UNIX and macOS when it comes to non-existing directories +# creating the directory to circumvent them +mkdir -p docs/src/solidity/interfaces +# move new generated interfaces docs from tmp to original directory +cp -R tmp/safe-liveness-technical-docs/src/solidity/interfaces docs/src/solidity/ + +# delete tmp directory +rm -rf tmp \ No newline at end of file diff --git a/docs/src/SUMMARY.md b/docs/src/SUMMARY.md new file mode 100644 index 0000000..3e879d9 --- /dev/null +++ b/docs/src/SUMMARY.md @@ -0,0 +1,14 @@ + + +# Overview + +- [Smart Contracts]() + + - [Addresses](content/smart-contracts/addresses.md) + - [Interfaces]() + - [IBlockHeaderOracle](solidity/interfaces/IBlockHeaderOracle.sol/interface.IBlockHeaderOracle.md) + - [IGuardCallbackModule](solidity/interfaces/IGuardCallbackModule.sol/interface.IGuardCallbackModule.md) + - [ISafe](solidity/interfaces/ISafe.sol/interface.ISafe.md) + - [IStorageMirror](solidity/interfaces/IStorageMirror.sol/interface.IStorageMirror.md) + - [IStorageMirrorRootRegistry](solidity/interfaces/IStorageMirrorRootRegistry.sol/interface.IStorageMirrorRootRegistry.md) + - [IVerifierModule](solidity/interfaces/IVerifierModule.sol/interface.IVerifierModule.md) diff --git a/docs/src/content/smart-contracts/addresses.md b/docs/src/content/smart-contracts/addresses.md new file mode 100644 index 0000000..d0bdffa --- /dev/null +++ b/docs/src/content/smart-contracts/addresses.md @@ -0,0 +1 @@ +# Addresses diff --git a/foundry.toml b/foundry.toml index 00f9bd4..a2f7a89 100644 --- a/foundry.toml +++ b/foundry.toml @@ -28,6 +28,9 @@ out = 'out-via-ir' fuzz_runs = 5000 src = 'solidity/test' +[profile.docs] +src = './solidity/interfaces/' + [rpc_endpoints] mainnet = "${MAINNET_RPC}" goerli = "${GOERLI_RPC}" diff --git a/package.json b/package.json index 238087d..0dbbaed 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,8 @@ "deploy:mainnet": "bash -c 'source .env && forge script -vv --rpc-url $MAINNET_RPC --slow --broadcast --private-key $DEPLOYER_MAINNNET_PRIVATE_KEY solidity/scripts/DeployMainnet.s.sol:DeployMainnet'", "deploy:optimism": "bash -c 'source .env && forge script -vv --rpc-url $OPTIMISM_RPC --slow --broadcast --private-key $DEPLOYER_OPTIMISM_PRIVATE_KEY solidity/scripts/DeployOptimism.s.sol:DeployOptimism'", "deploy:optimismGoerli": "bash -c 'source .env && forge script -vv --rpc-url $OPTIMISM_GOERLI_RPC --slow --broadcast --private-key $DEPLOYER_OPTIMISM_GOERLI_PRIVATE_KEY solidity/scripts/DeployOptimismGoerli.s.sol:DeployOptimismGoerli'", + "docs:build": "./build-docs.sh", + "docs:run": "mdbook serve docs", "lint:check": "yarn lint:sol-tests && yarn lint:sol-logic && forge fmt check", "lint:fix": "sort-package-json && forge fmt && yarn lint:sol-tests --fix && yarn lint:sol-logic --fix", "lint:sol-logic": "solhint -c .solhint.json 'solidity/contracts/**/*.sol' 'solidity/interfaces/**/*.sol'", From 82bb7443def0b2c46022720bb6cfc4b699277416 Mon Sep 17 00:00:00 2001 From: 0xOneTony <112496816+0xOneTony@users.noreply.github.com> Date: Mon, 27 Nov 2023 17:22:07 +0200 Subject: [PATCH 25/29] chore: improve e2e base (#14) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # 🤖 Linear Closes SAF-XXX --- .env.example | 15 +++- .github/workflows/test.yml | 12 +++- README.md | 2 +- e2e-tests-with-nodes.js | 75 ++++++++++++++++++++ foundry.toml | 3 + package.json | 4 ++ solidity/test/e2e/Common.sol | 128 ++++++++++++++++++++++++++--------- yarn.lock | 72 ++------------------ 8 files changed, 209 insertions(+), 102 deletions(-) create mode 100644 e2e-tests-with-nodes.js diff --git a/.env.example b/.env.example index e44c7bd..bfabc93 100644 --- a/.env.example +++ b/.env.example @@ -1,9 +1,22 @@ -## For e2e tests and deployment scripts +# For anvil to fork from MAINNET_RPC= GOERLI_RPC= OPTIMISM_RPC= OPTIMISM_GOERLI_RPC= +# Deployer address for the E2E Tests +MAINNET_DEPLOYER_ADDR= +# Safe owner address for the E2E Tests +MAINNET_SAFE_OWNER_ADDR= +# Deployer private key for the E2E Tests +MAINNET_DEPLOYER_PK= +# Safe owner private key for the E2E Tests +MAINNET_SAFE_OWNER_PK= +# Mainnet rpc for the E2E Tests, should be the anvil url +MAINNET_E2E_RPC= +# Optimism rpc for the E2E Tests, should be the anvil url +OPTIMISM_E2E_RPC= + ## For deployment scripts DEPLOYER_MAINNNET_PRIVATE_KEY= DEPLOYER_GOERLI_PRIVATE_KEY= diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 010bb44..d83799a 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -27,7 +27,7 @@ jobs: - name: Install dependencies run: yarn --frozen-lockfile --network-concurrency 1 - - name: Precompile using 0.8.14 and via-ir=false + - name: Precompile using 0.8.19 and via-ir=false run: yarn build - name: "Create env file" @@ -35,11 +35,17 @@ jobs: touch .env echo MAINNET_RPC="${{ secrets.MAINNET_RPC }}" >> .env echo GOERLI_RPC="${{ secrets.GOERLI_RPC }}" >> .env + echo OPTIMISM_RPC="${{ secrets.OPTIMISM_RPC }}" >> .env + echo MAINNET_E2E_RPC="${{ secrets.MAINNET_E2E_RPC }}" >> .env + echo OPTIMISM_E2E_RPC="${{ secrets.OPTIMISM_E2E_RPC }}" >> .env + echo MAINNET_SAFE_OWNER_ADDR="${{ secrets.MAINNET_SAFE_OWNER_ADDR }}" >> .env + echo MAINNET_SAFE_OWNER_PK="${{ secrets.MAINNET_SAFE_OWNER_PK }}" >> .env + echo MAINNET_DEPLOYER_ADDR="${{ secrets.MAINNET_DEPLOYER_ADDR }}" >> .env + echo MAINNET_DEPLOYER_PK="${{ secrets.MAINNET_DEPLOYER_PK }}" >> .env cat .env - name: Run tests - shell: bash - run: yarn test + run: yarn test:e2e-workflow forge-optimized: name: Run Optimized Unit Tests diff --git a/README.md b/README.md index c0214a7..f68c6ae 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ yarn build ### Available Commands -Make sure to set `MAINNET_RPC` environment variable before running end-to-end tests. +Make sure to set `MAINNET_RPC` and `OPTIMISM_RPC` environment variable before running end-to-end tests. | Yarn Command | Description | | ----------------------- | ---------------------------------------------------------- | diff --git a/e2e-tests-with-nodes.js b/e2e-tests-with-nodes.js new file mode 100644 index 0000000..08e0e3d --- /dev/null +++ b/e2e-tests-with-nodes.js @@ -0,0 +1,75 @@ +const { spawn } = require('child_process'); +require('dotenv').config(); // Initialize dotenv to load environment variables + +(async() => { + // Starting Anvil nodes for 'mainnet' and 'optimism' + console.debug(`Starting Anvil nodes`); + const anvilMainnetPromise = runAnvilNode('mainnet'); + const anvilOptimismPromise = runAnvilNode('optimism'); + + // Waiting for both nodes to be fully operational + console.debug(`Waiting for nodes to be up and running...`); + const [anvilMainnet, anvilOptimism] = await Promise.all([anvilMainnetPromise, anvilOptimismPromise]); + + // Running end-to-end tests + console.debug(`Running tests`); + const testProcess = spawn('yarn', [`test:e2e`]); + + // Handle test errors + testProcess.stderr.on('data', (data) => console.error(`Test error: ${data}`)); + + // Track the test result + let testPassed = true; + testProcess.stdout.on('data', (data) => { + console.info(String(data)); + if (String(data).includes('Failing tests:')) testPassed = false; + }); + + // When tests are complete, kill the Anvil nodes + testProcess.on('close', (code) => { + console.debug(`Tests finished running, killing anvil nodes...`); + try { + anvilMainnet.kill(); + anvilOptimism.kill(); + console.debug('Anvil nodes terminated successfully.'); + } catch (error) { + console.error(`Error terminating Anvil nodes: ${error}`); + } + + // Exit with an error code if tests failed + if (!testPassed) { + console.error('Tests failed. Setting exit code to 1.'); + process.exit(1); + } + + // Exit with a success code if tests passed + process.exit(0); + }); +})(); + + +/** + * Start an Anvil node for a given network + * + * @param network: name of the network to run (string) + * @returns promise which resolves when the node is running + */ +function runAnvilNode(network) { + return new Promise((resolve, reject) => { + const node = spawn('yarn', [`anvil:${network}`]); + + // Handle errors without exposing sensitive information + node.stderr.on('data', () => { + console.error(`Anvil ${network} node errored! Not showing the error log since it could reveal the RPC url.`); + reject(); + }); + + // Resolve the promise when the node is up + node.stdout.on('data', (data) => { + if (String(data).includes('Listening on')) { + console.debug(`Anvil ${network} node up and running`); + resolve(node); + } + }); + }); +} diff --git a/foundry.toml b/foundry.toml index a2f7a89..6d7ab48 100644 --- a/foundry.toml +++ b/foundry.toml @@ -34,3 +34,6 @@ src = './solidity/interfaces/' [rpc_endpoints] mainnet = "${MAINNET_RPC}" goerli = "${GOERLI_RPC}" +optimism = "${OPTIMISM_RPC}" +mainnet_e2e = "${MAINNET_E2E_RPC}" +optimism_e2e = "${OPTIMISM_E2E_RPC}" diff --git a/package.json b/package.json index 0dbbaed..9ec5fcc 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,8 @@ }, "author": "Wonderland", "scripts": { + "anvil:mainnet": "anvil --port 8545 -f $MAINNET_RPC --fork-block-number 18621047 --chain-id 1", + "anvil:optimism": "anvil --port 9545 -f $OPTIMISM_RPC --fork-block-number 112491451 --chain-id 10", "build": "forge build", "build:optimized": "FOUNDRY_PROFILE=optimized forge build", "coverage": "forge coverage --match-contract Unit", @@ -27,6 +29,7 @@ "proof": "python3 proofs/generate_proof.py", "test": "forge test -vvv", "test:e2e": "forge test --match-contract E2E -vvv", + "test:e2e-workflow": "node e2e-tests-with-nodes.js", "test:unit": "forge test --match-contract Unit -vvv", "test:unit:deep": "FOUNDRY_FUZZ_RUNS=5000 yarn test:unit" }, @@ -38,6 +41,7 @@ "dependencies": { "@defi-wonderland/solidity-utils": "0.0.0-3e9c8e8b", "Solidity-RLP": "github:hamdiallam/Solidity-RLP", + "dotenv": "16.3.1", "ds-test": "github:dapphub/ds-test#e282159", "forge-std": "github:foundry-rs/forge-std#v1.5.6", "isolmate": "github:defi-wonderland/isolmate#59e1804", diff --git a/solidity/test/e2e/Common.sol b/solidity/test/e2e/Common.sol index 508f5b3..6e28156 100644 --- a/solidity/test/e2e/Common.sol +++ b/solidity/test/e2e/Common.sol @@ -11,19 +11,30 @@ import {UpdateStorageMirrorGuard} from 'contracts/UpdateStorageMirrorGuard.sol'; import {GuardCallbackModule} from 'contracts/GuardCallbackModule.sol'; import {BlockHeaderOracle} from 'contracts/BlockHeaderOracle.sol'; import {NeedsUpdateGuard} from 'contracts/NeedsUpdateGuard.sol'; +import {VerifierModule} from 'contracts/VerifierModule.sol'; +import {StorageMirrorRootRegistry} from 'contracts/StorageMirrorRootRegistry.sol'; import {IGuardCallbackModule} from 'interfaces/IGuardCallbackModule.sol'; import {ISafe} from 'interfaces/ISafe.sol'; import {IVerifierModule} from 'interfaces/IVerifierModule.sol'; +import {IStorageMirrorRootRegistry} from 'interfaces/IStorageMirrorRootRegistry.sol'; +import {IBlockHeaderOracle} from 'interfaces/IBlockHeaderOracle.sol'; import {IGnosisSafeProxyFactory} from 'test/e2e/IGnosisSafeProxyFactory.sol'; import {TestConstants} from 'test/utils/TestConstants.sol'; import {ContractDeploymentAddress} from 'test/utils/ContractDeploymentAddress.sol'; +// solhint-disable-next-line max-states-count contract CommonE2EBase is DSTestPlus, TestConstants { - uint256 internal constant _FORK_BLOCK = 15_452_788; + uint256 internal constant _MAINNET_FORK_BLOCK = 18_621_047; + uint256 internal constant _OPTIMISM_FORK_BLOCK = 112_491_451; - address public deployer = makeAddr('deployer'); + uint256 internal _mainnetForkId; + uint256 internal _optimismForkId; + + address public deployer; + uint256 public deployerKey; + address public deployerOptimism = makeAddr('deployerOptimism'); address public proposer = makeAddr('proposer'); address public safeOwner; uint256 public safeOwnerKey; @@ -36,35 +47,45 @@ contract CommonE2EBase is DSTestPlus, TestConstants { GuardCallbackModule public guardCallbackModule; BlockHeaderOracle public oracle; NeedsUpdateGuard public needsUpdateGuard; + VerifierModule public verifierModule; + StorageMirrorRootRegistry public storageMirrorRootRegistry; ISafe public safe; ISafe public nonHomeChainSafe; - IVerifierModule public verifierModule = IVerifierModule(makeAddr('verifierModule')); IGnosisSafeProxyFactory public gnosisSafeProxyFactory = IGnosisSafeProxyFactory(GNOSIS_SAFE_PROXY_FACTORY); function setUp() public virtual { - vm.createSelectFork(vm.rpcUrl('mainnet'), _FORK_BLOCK); + // Set up both forks + _mainnetForkId = vm.createFork(vm.rpcUrl('mainnet_e2e'), _MAINNET_FORK_BLOCK); + _optimismForkId = vm.createFork(vm.rpcUrl('optimism_e2e'), _OPTIMISM_FORK_BLOCK); + // Select mainnet fork + vm.selectFork(_mainnetForkId); // Make address and key of safe owner - (safeOwner, safeOwnerKey) = makeAddrAndKey('safeOwner'); - // Make address and key of non home chain safe owner - (nonHomeChainSafeOwner, nonHomeChainSafeOwnerKey) = makeAddrAndKey('nonHomeChainSafeOwner'); + safeOwner = vm.envAddress('MAINNET_SAFE_OWNER_ADDR'); + safeOwnerKey = vm.envUint('MAINNET_SAFE_OWNER_PK'); + + // Make address and key of deployer + deployer = vm.envAddress('MAINNET_DEPLOYER_ADDR'); + deployerKey = vm.envUint('MAINNET_DEPLOYER_PK'); /// =============== HOME CHAIN =============== - vm.prank(safeOwner); + vm.broadcast(safeOwnerKey); safe = ISafe(address(gnosisSafeProxyFactory.createProxy(GNOSIS_SAFE_SINGLETON, ''))); // safeOwner nonce 0 label(address(safe), 'SafeProxy'); - address _updateStorageMirrorGuardTheoriticalAddress = ContractDeploymentAddress.addressFrom(deployer, 2); + uint256 _nonce = vm.getNonce(deployer); + + address _updateStorageMirrorGuardTheoriticalAddress = ContractDeploymentAddress.addressFrom(deployer, _nonce + 2); - vm.prank(deployer); + vm.broadcast(deployer); storageMirror = new StorageMirror(); // deployer nonce 0 label(address(storageMirror), 'StorageMirror'); - vm.prank(deployer); + vm.broadcast(deployer); guardCallbackModule = new GuardCallbackModule(address(storageMirror), _updateStorageMirrorGuardTheoriticalAddress); // deployer nonce 1 label(address(guardCallbackModule), 'GuardCallbackModule'); - vm.prank(deployer); + vm.broadcast(deployer); updateStorageMirrorGuard = new UpdateStorageMirrorGuard(guardCallbackModule); // deployer nonce 2 label(address(updateStorageMirrorGuard), 'UpdateStorageMirrorGuard'); @@ -74,11 +95,11 @@ contract CommonE2EBase is DSTestPlus, TestConstants { // Set up owner home chain safe address[] memory _owners = new address[](1); _owners[0] = safeOwner; - vm.prank(safeOwner); // safeOwner nonce 1 + vm.broadcast(safeOwnerKey); // safeOwner nonce 1 safe.setup(_owners, 1, address(safe), bytes(''), address(0), address(0), 0, payable(0)); // Enable guard callback module - enableModule(safe, safeOwner, safeOwnerKey, address(guardCallbackModule)); + enableModule(safe, safeOwnerKey, address(guardCallbackModule)); // data to sign and send to set the guard bytes memory _setGuardData = abi.encodeWithSelector(IGuardCallbackModule.setGuard.selector); @@ -91,7 +112,7 @@ contract CommonE2EBase is DSTestPlus, TestConstants { bytes memory _setGuardSignature = abi.encodePacked(_r, _s, _v); // execute setup of guard - vm.prank(safeOwner); + vm.broadcast(safeOwnerKey); safe.execTransaction( address(guardCallbackModule), 0, @@ -106,45 +127,90 @@ contract CommonE2EBase is DSTestPlus, TestConstants { ); /// =============== NON HOME CHAIN =============== + vm.selectFork(_optimismForkId); + // Make address and key of non home chain safe owner + (nonHomeChainSafeOwner, nonHomeChainSafeOwnerKey) = makeAddrAndKey('nonHomeChainSafeOwner'); + + address _storageMirrorRootRegistryTheoriticalAddress = ContractDeploymentAddress.addressFrom(deployerOptimism, 2); + // Set up non home chain safe - vm.prank(nonHomeChainSafeOwner); - nonHomeChainSafe = ISafe(address(gnosisSafeProxyFactory.createProxy(GNOSIS_SAFE_SINGLETON, ''))); // nonHomeChainSafeOwner nonce 0 + vm.broadcast(nonHomeChainSafeOwnerKey); + nonHomeChainSafe = ISafe(address(gnosisSafeProxyFactory.createProxy(GNOSIS_SAFE_SINGLETON_L2, ''))); // nonHomeChainSafeOwner nonce 0 label(address(nonHomeChainSafe), 'NonHomeChainSafeProxy'); // Deploy non home chain contracts - vm.prank(deployer); - oracle = new BlockHeaderOracle(); // deployer nonce 3 - label(address(oracle), 'MockOracle'); - - // vm.prank(deployer); - // verifierModule = new VerifierModule(..); // deployer nonce 4 - // label(address(verifierModule), 'VerifierModule'); - - vm.prank(deployer); - needsUpdateGuard = new NeedsUpdateGuard(verifierModule); // deployer nonce 5 + oracle = new BlockHeaderOracle(); // deployerOptimism nonce 0 + label(address(oracle), 'BlockHeaderOracle'); + + vm.broadcast(deployerOptimism); + verifierModule = new VerifierModule( + IStorageMirrorRootRegistry(_storageMirrorRootRegistryTheoriticalAddress), address(storageMirror) + ); // deployerOptimism nonce 1 + label(address(verifierModule), 'VerifierModule'); + + vm.broadcast(deployerOptimism); + storageMirrorRootRegistry = + new StorageMirrorRootRegistry(address(storageMirror), IVerifierModule(verifierModule), IBlockHeaderOracle(oracle)); // deployerOptimism nonce 2 + label(address(storageMirrorRootRegistry), 'StorageMirrorRootRegistry'); + + vm.broadcast(deployerOptimism); + needsUpdateGuard = new NeedsUpdateGuard(verifierModule); // deployer nonce 3 label(address(needsUpdateGuard), 'NeedsUpdateGuard'); // set up non home chain safe address[] memory _nonHomeChainSafeOwners = new address[](1); _nonHomeChainSafeOwners[0] = nonHomeChainSafeOwner; - vm.prank(nonHomeChainSafeOwner); // nonHomeChainSafeOwner nonce 1 + + vm.broadcast(nonHomeChainSafeOwnerKey); // nonHomeChainSafeOwner nonce 1 nonHomeChainSafe.setup( _nonHomeChainSafeOwners, 1, address(nonHomeChainSafe), bytes(''), address(0), address(0), 0, payable(0) ); // enable verifier module + enableModule(nonHomeChainSafe, nonHomeChainSafeOwnerKey, address(verifierModule)); + + // data to sign and send to set the guard + _setGuardData = abi.encodeWithSelector(ISafe.setGuard.selector, address(needsUpdateGuard)); + _setGuardEncodedTxData = nonHomeChainSafe.encodeTransactionData( + address(nonHomeChainSafe), + 0, + _setGuardData, + Enum.Operation.Call, + 0, + 0, + 0, + address(0), + payable(0), + nonHomeChainSafe.nonce() + ); + + // signature + (_v, _r, _s) = vm.sign(nonHomeChainSafeOwnerKey, keccak256(_setGuardEncodedTxData)); + _setGuardSignature = abi.encodePacked(_r, _s, _v); // set needs update guard + vm.broadcast(nonHomeChainSafeOwnerKey); + nonHomeChainSafe.execTransaction( + address(nonHomeChainSafe), + 0, + _setGuardData, + Enum.Operation.Call, + 0, + 0, + 0, + address(0), + payable(0), + _setGuardSignature + ); } /** * @notice Enables a module for the given safe * @param _safe The safe that will enable the module - * @param _safeOwner The address of the owner of the safe * @param _safeOwnerKey The private key to sign the tx * @param _module The module address to enable */ - function enableModule(ISafe _safe, address _safeOwner, uint256 _safeOwnerKey, address _module) public { + function enableModule(ISafe _safe, uint256 _safeOwnerKey, address _module) public { uint256 _safeNonce = _safe.nonce(); // data to sign to enable module bytes memory _enableModuleData = abi.encodeWithSelector(ISafe.enableModule.selector, address(_module)); @@ -157,7 +223,7 @@ contract CommonE2EBase is DSTestPlus, TestConstants { bytes memory _enableModuleSignature = abi.encodePacked(_r, _s, _v); // execute enable module - vm.prank(_safeOwner); + vm.broadcast(safeOwnerKey); _safe.execTransaction( address(_safe), 0, _enableModuleData, Enum.Operation.Call, 0, 0, 0, address(0), payable(0), _enableModuleSignature ); diff --git a/yarn.lock b/yarn.lock index 228b8f5..dc0d41e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -200,15 +200,6 @@ ds-test "https://github.com/dapphub/ds-test" forge-std "https://github.com/foundry-rs/forge-std" -"@defi-wonderland/solidity-utils@0.0.0-4298c6c6": - version "0.0.0-4298c6c6" - resolved "https://registry.yarnpkg.com/@defi-wonderland/solidity-utils/-/solidity-utils-0.0.0-4298c6c6.tgz#4ac9e4bcc586f7434715357310a7a3c30e81f17f" - integrity sha512-jpecgVx9hpnXXGnYnN8ayd1ONj4ljk0hI36ltNfkNwlDkLLzPt4/FY5YSyY/628Exfuy5X9Rl9gGv5UtYgAmtw== - dependencies: - "@openzeppelin/contracts" "4.8.1" - ds-test "https://github.com/dapphub/ds-test" - forge-std "https://github.com/foundry-rs/forge-std" - "@jridgewell/resolve-uri@^3.0.3": version "3.1.1" resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz#c08679063f279615a3326583ba3a90d1d82cc721" @@ -248,11 +239,6 @@ "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" -"@openzeppelin/contracts@4.8.1": - version "4.8.1" - resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-4.8.1.tgz#709cfc4bbb3ca9f4460d60101f15dac6b7a2d5e4" - integrity sha512-xQ6eUZl+RDyb/FiZe1h+U7qr/f4p/SrTSQcTPH2bjur3C5DbuW/zFgCU/b1P/xcIaEqJep+9ju4xDRi3rmChdQ== - "@openzeppelin/contracts@4.9.2": version "4.9.2" resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-4.9.2.tgz#1cb2d5e4d3360141a17dbc45094a8cad6aac16c1" @@ -819,10 +805,10 @@ dot-prop@^5.1.0: dependencies: is-obj "^2.0.0" -"ds-test@git+https://github.com/dapphub/ds-test.git": - version "1.0.0" - uid e282159d5170298eb2455a6c05280ab5a73a4ef0 - resolved "git+https://github.com/dapphub/ds-test.git#e282159d5170298eb2455a6c05280ab5a73a4ef0" +dotenv@16.3.1: + version "16.3.1" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.3.1.tgz#369034de7d7e5b120972693352a3bf112172cc3e" + integrity sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ== "ds-test@github:dapphub/ds-test#e282159": version "1.0.0" @@ -1102,11 +1088,6 @@ flatted@^2.0.0: resolved "https://registry.yarnpkg.com/flatted/-/flatted-2.0.2.tgz#4575b21e2bcee7434aa9be662f4b7b5f9c2b5138" integrity sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA== -"forge-std@git+https://github.com/foundry-rs/forge-std.git": - version "1.7.2" - uid bdea49f9bb3c58c8c35850c3bdc17eaeea756e9a - resolved "git+https://github.com/foundry-rs/forge-std.git#bdea49f9bb3c58c8c35850c3bdc17eaeea756e9a" - "forge-std@github:foundry-rs/forge-std#v1.5.6": version "1.5.6" resolved "https://codeload.github.com/foundry-rs/forge-std/tar.gz/e8a047e3f40f13fa37af6fe14e6e06283d9a060e" @@ -1115,15 +1096,6 @@ flatted@^2.0.0: version "1.7.2" resolved "https://github.com/foundry-rs/forge-std#bdea49f9bb3c58c8c35850c3bdc17eaeea756e9a" -foundry-mock-generator@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/foundry-mock-generator/-/foundry-mock-generator-1.0.1.tgz#7473cc24920a65cdd70ed14cc5f6896fc3be76de" - integrity sha512-tl9gkhsB8V+FqLjz/WNusuAKDpUXTqbs/KC9roVKaqc7d9oC+2EDLtHRt9bHWEKTE9ocsCs2nH9+r8letnZsLQ== - dependencies: - "@defi-wonderland/solidity-utils" "0.0.0-4298c6c6" - handlebars "4.7.7" - yargs "17.7.2" - fs-extra@^11.0.0: version "11.1.1" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-11.1.1.tgz#da69f7c39f3b002378b0954bb6ae7efdc0876e2d" @@ -1224,18 +1196,6 @@ graceful-fs@^4.1.6, graceful-fs@^4.2.0: resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== -handlebars@4.7.7: - version "4.7.7" - resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.7.7.tgz#9ce33416aad02dbd6c8fafa8240d5d98004945a1" - integrity sha512-aAcXm5OAfE/8IXkcZvCepKU3VzW1/39Fb5ZuqMtgI/hT8X2YgoMvBY5dLhq/cpOvw7Lk1nK/UF71aLG/ZnVYRA== - dependencies: - minimist "^1.2.5" - neo-async "^2.6.0" - source-map "^0.6.1" - wordwrap "^1.0.0" - optionalDependencies: - uglify-js "^3.1.4" - hard-rejection@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/hard-rejection/-/hard-rejection-2.1.0.tgz#1c6eda5c1685c63942766d79bb40ae773cecd883" @@ -1737,7 +1697,7 @@ minimist-options@4.1.0: is-plain-obj "^1.1.0" kind-of "^6.0.3" -minimist@^1.2.5, minimist@^1.2.6: +minimist@^1.2.6: version "1.2.8" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== @@ -1764,11 +1724,6 @@ natural-compare@^1.4.0: resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== -neo-async@^2.6.0: - version "2.6.2" - resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f" - integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== - nice-try@^1.0.4: version "1.0.5" resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" @@ -2333,11 +2288,6 @@ sort-package-json@1.53.1: is-plain-obj "2.1.0" sort-object-keys "^1.1.3" -source-map@^0.6.1: - version "0.6.1" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" - integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== - spdx-correct@^3.0.0: version "3.2.0" resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.2.0.tgz#4f5ab0668f0059e34f9c00dce331784a12de4e9c" @@ -2604,11 +2554,6 @@ type-fest@^0.8.1: resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.1.3.tgz#8d84219244a6b40b6fb2b33cc1c062f715b9e826" integrity sha512-XH627E9vkeqhlZFQuL+UsyAXEnibT0kWR2FWONlr4sTjvxyJYnyefgrkyECLzM5NenmKzRAy2rR/OlYLA1HkZw== -uglify-js@^3.1.4: - version "3.17.4" - resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.17.4.tgz#61678cf5fa3f5b7eb789bb345df29afb8257c22c" - integrity sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g== - universalify@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.0.tgz#75a4984efedc4b08975c5aeb73f530d02df25717" @@ -2658,11 +2603,6 @@ word-wrap@~1.2.3: resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== -wordwrap@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" - integrity sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q== - wrap-ansi@^6.2.0: version "6.2.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz#e9393ba07102e6c91a3b221478f0257cd2856e53" @@ -2718,7 +2658,7 @@ yargs-parser@^21.1.1: resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35" integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== -yargs@17.7.2, yargs@^17.0.0: +yargs@^17.0.0: version "17.7.2" resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269" integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w== From 9a4e1fcfc76bc84562997530b9ca3a935a973f48 Mon Sep 17 00:00:00 2001 From: excaliborr <124819095+excaliborr@users.noreply.github.com> Date: Tue, 28 Nov 2023 06:16:19 -0500 Subject: [PATCH 26/29] fix: e2e tests deploying to ganache node (#24) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # 🤖 Linear Closes SAF-XXX --------- Co-authored-by: OneTony --- .env.example | 14 +- .github/workflows/test.yml | 17 +- .gitignore | 4 + README.md | 8 + e2e-tests-with-nodes.js | 55 +++- foundry.toml | 4 + package.json | 7 +- proofs/generate_proof.py | 24 +- proofs/proof_utils.py | 14 +- .../contracts/StorageMirrorRootRegistry.sol | 2 +- solidity/contracts/VerifierModule.sol | 15 +- solidity/libraries/StateVerifier.sol | 7 +- solidity/scripts/DeployE2E.s.sol | 238 ++++++++++++++ solidity/scripts/DeployGoerli.s.sol | 2 +- solidity/scripts/DeployHomeChain.s.sol | 12 +- solidity/scripts/DeployMainnet.s.sol | 2 +- solidity/scripts/DeployNonHomeChain.s.sol | 14 +- solidity/scripts/DeployOptimism.s.sol | 6 +- solidity/scripts/DeployOptimismGoerli.s.sol | 6 +- solidity/scripts/deployments/deployments.md | 3 + solidity/test/e2e/Common.sol | 248 +++++---------- solidity/test/e2e/VerifierModule.t.sol | 108 +++++++ .../test/unit/StorageMirrorRootRegistry.t.sol | 4 +- solidity/test/unit/VerifierModule.t.sol | 26 +- yarn.lock | 293 +++++++++++++++++- 25 files changed, 883 insertions(+), 250 deletions(-) create mode 100644 solidity/scripts/DeployE2E.s.sol create mode 100644 solidity/scripts/deployments/deployments.md create mode 100644 solidity/test/e2e/VerifierModule.t.sol diff --git a/.env.example b/.env.example index bfabc93..ca80b02 100644 --- a/.env.example +++ b/.env.example @@ -1,21 +1,15 @@ -# For anvil to fork from MAINNET_RPC= GOERLI_RPC= OPTIMISM_RPC= OPTIMISM_GOERLI_RPC= -# Deployer address for the E2E Tests -MAINNET_DEPLOYER_ADDR= -# Safe owner address for the E2E Tests -MAINNET_SAFE_OWNER_ADDR= # Deployer private key for the E2E Tests MAINNET_DEPLOYER_PK= -# Safe owner private key for the E2E Tests -MAINNET_SAFE_OWNER_PK= +# Searcher PK for E2E Tests +SEARCHER_PK= + # Mainnet rpc for the E2E Tests, should be the anvil url MAINNET_E2E_RPC= -# Optimism rpc for the E2E Tests, should be the anvil url -OPTIMISM_E2E_RPC= ## For deployment scripts DEPLOYER_MAINNNET_PRIVATE_KEY= @@ -24,4 +18,4 @@ DEPLOYER_OPTIMISM_PRIVATE_KEY= DEPLOYER_OPTIMISM_GOERLI_PRIVATE_KEY= STORAGE_MIRROR_ADDRESS= -ETHERSCAN_API_KEY= +ETHERSCAN_API_KEY= \ No newline at end of file diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index d83799a..7bb6866 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -27,6 +27,16 @@ jobs: - name: Install dependencies run: yarn --frozen-lockfile --network-concurrency 1 + - name: setup python + uses: actions/setup-python@v4 + with: + python-version: '3.10' # install the python version needed + + - name: install python packages + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + - name: Precompile using 0.8.19 and via-ir=false run: yarn build @@ -34,13 +44,8 @@ jobs: run: | touch .env echo MAINNET_RPC="${{ secrets.MAINNET_RPC }}" >> .env - echo GOERLI_RPC="${{ secrets.GOERLI_RPC }}" >> .env - echo OPTIMISM_RPC="${{ secrets.OPTIMISM_RPC }}" >> .env echo MAINNET_E2E_RPC="${{ secrets.MAINNET_E2E_RPC }}" >> .env - echo OPTIMISM_E2E_RPC="${{ secrets.OPTIMISM_E2E_RPC }}" >> .env - echo MAINNET_SAFE_OWNER_ADDR="${{ secrets.MAINNET_SAFE_OWNER_ADDR }}" >> .env - echo MAINNET_SAFE_OWNER_PK="${{ secrets.MAINNET_SAFE_OWNER_PK }}" >> .env - echo MAINNET_DEPLOYER_ADDR="${{ secrets.MAINNET_DEPLOYER_ADDR }}" >> .env + echo SEARCHER_PK="${{ secrets.SEARCHER_PK }}" >> .env echo MAINNET_DEPLOYER_PK="${{ secrets.MAINNET_DEPLOYER_PK }}" >> .env cat .env diff --git a/.gitignore b/.gitignore index 2854bbe..0d9d254 100644 --- a/.gitignore +++ b/.gitignore @@ -26,3 +26,7 @@ docs/src/static # Cache for the python scripts proofs/__pycache__/* + +# Deployments for E2E +solidity/scripts/deployments/*.json +proofs/proof.json diff --git a/README.md b/README.md index f68c6ae..7e5aa16 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,13 @@ yarn install yarn build ``` +In order to run the E2E tests you will also need python setup to generate the proofs, to do this run: + +```sh +python -m pip install --upgrade pip +pip install -r requirements.txt +``` + ### Available Commands Make sure to set `MAINNET_RPC` and `OPTIMISM_RPC` environment variable before running end-to-end tests. @@ -56,6 +63,7 @@ The project is a PoC implementation and should be treated with caution. Bellow w - `UpdateStorageMirrorGuard` for the PoC this guard is calling the `GuardCallbackModule` in every call. A possible improvement would be to decode the txData, on the guard `checkTransaction` pre-execute hook, and filter against certain function signatures that change the settings of a Safe to accurately catch the change. - `NeedsUpdateGuard` this guard on the non-home chain can brick the user's safe, since it will block every tx, if their security settings expire. Also it's worth mentioning that before using the guard the safe owner must verify at least 1 set of settings using the VerifierModule in order for the guard to have a point of reference for the latest verified update. - `VerifierModule` is executing a safeTx after the verification and update of their settings. This safeTx can become invalid since the signatures passed were created before the change of the settings, in this case the user(s) will need to re-sign the tx manually outside of the UI. A possible improvement would be to have a custom safe app that let's you sign even if you are not a "current owner" but are a "potential future owner" of the "soon-to-be-updated" settings +- `VerifierModule` makes the assumption that the address of the safe is the same on both the home chain, and non-home chain. The current implementation will not work if these addresses are different ## Contributors diff --git a/e2e-tests-with-nodes.js b/e2e-tests-with-nodes.js index 08e0e3d..e6afc49 100644 --- a/e2e-tests-with-nodes.js +++ b/e2e-tests-with-nodes.js @@ -3,13 +3,12 @@ require('dotenv').config(); // Initialize dotenv to load environment variables (async() => { // Starting Anvil nodes for 'mainnet' and 'optimism' - console.debug(`Starting Anvil nodes`); - const anvilMainnetPromise = runAnvilNode('mainnet'); - const anvilOptimismPromise = runAnvilNode('optimism'); + console.debug(`Starting Ganache node`); + const ganachePromise = runGanacheNode(); // Waiting for both nodes to be fully operational - console.debug(`Waiting for nodes to be up and running...`); - const [anvilMainnet, anvilOptimism] = await Promise.all([anvilMainnetPromise, anvilOptimismPromise]); + console.debug(`Waiting for node to be up and running...`); + const ganache = await Promise.resolve(ganachePromise); // Running end-to-end tests console.debug(`Running tests`); @@ -25,15 +24,14 @@ require('dotenv').config(); // Initialize dotenv to load environment variables if (String(data).includes('Failing tests:')) testPassed = false; }); - // When tests are complete, kill the Anvil nodes + // When tests are complete, kill the Ganache nodes testProcess.on('close', (code) => { - console.debug(`Tests finished running, killing anvil nodes...`); + console.debug(`Tests finished running, killing ganache node...`); try { - anvilMainnet.kill(); - anvilOptimism.kill(); - console.debug('Anvil nodes terminated successfully.'); + ganache.kill(); + console.debug('Ganache nodes terminated successfully.'); } catch (error) { - console.error(`Error terminating Anvil nodes: ${error}`); + console.error(`Error terminating Ganache nodes: ${error}`); } // Exit with an error code if tests failed @@ -54,22 +52,47 @@ require('dotenv').config(); // Initialize dotenv to load environment variables * @param network: name of the network to run (string) * @returns promise which resolves when the node is running */ -function runAnvilNode(network) { +// function runAnvilNode(network) { +// return new Promise((resolve, reject) => { +// const node = spawn('yarn', [`anvil:${network}`]); + +// // Handle errors without exposing sensitive information +// node.stderr.on('data', () => { +// console.error(`Anvil ${network} node errored! Not showing the error log since it could reveal the RPC url.`); +// reject(); +// }); + +// // Resolve the promise when the node is up +// node.stdout.on('data', (data) => { +// if (String(data).includes('Listening on')) { +// console.debug(`Anvil ${network} node up and running`); +// resolve(node); +// } +// }); +// }); +// } + +/** + * Start a Ganache node + * + * @returns promise which resolves when the node is running + */ +function runGanacheNode() { return new Promise((resolve, reject) => { - const node = spawn('yarn', [`anvil:${network}`]); + const node = spawn('yarn', ['ganache']); // Handle errors without exposing sensitive information node.stderr.on('data', () => { - console.error(`Anvil ${network} node errored! Not showing the error log since it could reveal the RPC url.`); + console.error(`Ganache node errored! Not showing the error log since it could reveal the RPC url.`); reject(); }); // Resolve the promise when the node is up node.stdout.on('data', (data) => { if (String(data).includes('Listening on')) { - console.debug(`Anvil ${network} node up and running`); + console.debug(`Ganache node up and running`); resolve(node); } }); }); -} +} \ No newline at end of file diff --git a/foundry.toml b/foundry.toml index 6d7ab48..0550769 100644 --- a/foundry.toml +++ b/foundry.toml @@ -16,17 +16,21 @@ out = 'out' libs = ['lib'] fuzz_runs = 1000 optimizer_runs = 10_000 +fs_permissions = [{ access = "read-write", path = "./"}] [profile.optimized] via_ir = true out = 'out-via-ir' fuzz_runs = 5000 +src = 'solidity/contracts' +fs_permissions = [{ access = "read-write", path = "./"}] [profile.test] via_ir = true out = 'out-via-ir' fuzz_runs = 5000 src = 'solidity/test' +fs_permissions = [{ access = "read-write", path = "./"}] [profile.docs] src = './solidity/interfaces/' diff --git a/package.json b/package.json index 9ec5fcc..19cd46f 100644 --- a/package.json +++ b/package.json @@ -15,20 +15,22 @@ "build": "forge build", "build:optimized": "FOUNDRY_PROFILE=optimized forge build", "coverage": "forge coverage --match-contract Unit", + "deploy:e2e": "forge script solidity/scripts/DeployE2E.s.sol:DeployE2E --broadcast", "deploy:goerli": "bash -c 'source .env && forge script -vv --rpc-url $GOERLI_RPC --slow --broadcast --private-key $DEPLOYER_GOERLI_PRIVATE_KEY solidity/scripts/DeployGoerli.s.sol:DeployGoerli'", "deploy:mainnet": "bash -c 'source .env && forge script -vv --rpc-url $MAINNET_RPC --slow --broadcast --private-key $DEPLOYER_MAINNNET_PRIVATE_KEY solidity/scripts/DeployMainnet.s.sol:DeployMainnet'", "deploy:optimism": "bash -c 'source .env && forge script -vv --rpc-url $OPTIMISM_RPC --slow --broadcast --private-key $DEPLOYER_OPTIMISM_PRIVATE_KEY solidity/scripts/DeployOptimism.s.sol:DeployOptimism'", "deploy:optimismGoerli": "bash -c 'source .env && forge script -vv --rpc-url $OPTIMISM_GOERLI_RPC --slow --broadcast --private-key $DEPLOYER_OPTIMISM_GOERLI_PRIVATE_KEY solidity/scripts/DeployOptimismGoerli.s.sol:DeployOptimismGoerli'", "docs:build": "./build-docs.sh", "docs:run": "mdbook serve docs", + "ganache": "ganache --port 8545 --mnemonic 'chapter polar wool ethics pudding undo slide social second put segment chair'", "lint:check": "yarn lint:sol-tests && yarn lint:sol-logic && forge fmt check", "lint:fix": "sort-package-json && forge fmt && yarn lint:sol-tests --fix && yarn lint:sol-logic --fix", "lint:sol-logic": "solhint -c .solhint.json 'solidity/contracts/**/*.sol' 'solidity/interfaces/**/*.sol'", "lint:sol-tests": "solhint 'solidity/test/**/*.sol'", "prepare": "husky install", "proof": "python3 proofs/generate_proof.py", - "test": "forge test -vvv", - "test:e2e": "forge test --match-contract E2E -vvv", + "test": "yarn deploy:e2e && forge test --ffi -vvv", + "test:e2e": "yarn deploy:e2e && forge test --ffi --match-contract E2E -vvv", "test:e2e-workflow": "node e2e-tests-with-nodes.js", "test:unit": "forge test --match-contract Unit -vvv", "test:unit:deep": "FOUNDRY_FUZZ_RUNS=5000 yarn test:unit" @@ -44,6 +46,7 @@ "dotenv": "16.3.1", "ds-test": "github:dapphub/ds-test#e282159", "forge-std": "github:foundry-rs/forge-std#v1.5.6", + "ganache": "7.9.1", "isolmate": "github:defi-wonderland/isolmate#59e1804", "prb/test": "github:paulrberg/prb-test#a245c71", "safe-contracts": "github:safe-global/safe-contracts#v1.4.1" diff --git a/proofs/generate_proof.py b/proofs/generate_proof.py index 3a9aba6..cdcdefb 100644 --- a/proofs/generate_proof.py +++ b/proofs/generate_proof.py @@ -2,7 +2,7 @@ import json import rlp -from proof_utils import request_block_header, request_account_proof +from proof_utils import request_block_header, request_account_proof, mine_anvil_block def main(): # Parse command line arguments @@ -10,10 +10,6 @@ def main(): description="Patricia Merkle Trie Proof Generating Tool", formatter_class=argparse.RawTextHelpFormatter) - parser.add_argument("-b", "--block-number", - type=int, - help="Block number, defaults to `latest - 15`") - parser.add_argument("-r", "--rpc", default="http://localhost:8545", type=str, @@ -29,14 +25,18 @@ def main(): # Save command line arguments into variables args = parser.parse_args() - + + mine_anvil_block(args.rpc) + rpc_endpoint = args.rpc - block_number = args.block_number + block_number = "latest" storage_mirror_contract_address = args.contract storage_slot = args.slot - + clean_storage_slot = bytes.fromhex(storage_slot[2:]) + + mine_anvil_block(rpc_endpoint) # Generate proof data - (block_number, block_header, acct_proof, storage_proofs) = generate_proof_data(rpc_endpoint, block_number, storage_mirror_contract_address, [storage_slot]) + (block_number, block_header, acct_proof, storage_proofs) = generate_proof_data(rpc_endpoint, block_number, storage_mirror_contract_address, [clean_storage_slot]) # Encode the proof data account_proof = rlp.encode(acct_proof) @@ -51,7 +51,11 @@ def main(): "storageProof": storage_proof.hex() } - print(json.dumps(output)) + # Serializing json + json_object = json.dumps(output, indent=4) + + with open("./proofs/proof.json", "w") as outfile: + outfile.write(json_object) def generate_proof_data( rpc_endpoint, diff --git a/proofs/proof_utils.py b/proofs/proof_utils.py index ed2caed..2428e19 100644 --- a/proofs/proof_utils.py +++ b/proofs/proof_utils.py @@ -22,9 +22,21 @@ def request_block_header(rpc_endpoint, block_number): block_dict = get_json_rpc_result(r) block_number = normalize_int(block_dict["number"]) block_header_fields = [normalize_bytes(block_dict[f]) for f in BLOCK_HEADER_FIELDS] + return (block_number, block_header_fields) +def mine_anvil_block(rpc_endpoint): + r = requests.post(rpc_endpoint, json={ + "jsonrpc": "2.0", + "method": "evm_mine", + "params": [], + "id": 1, + }) + + result = get_json_rpc_result(r) + return result + def request_account_proof(rpc_endpoint, block_number, address, slots): hex_slots = [to_0x_string(s) for s in slots] @@ -42,7 +54,7 @@ def request_account_proof(rpc_endpoint, block_number, address, slots): storage_proofs = [ decode_rpc_proof(slot_data["proof"]) for slot_data in result["storageProof"] ] - + return (account_proof, storage_proofs) diff --git a/solidity/contracts/StorageMirrorRootRegistry.sol b/solidity/contracts/StorageMirrorRootRegistry.sol index 0bbed98..10dab5d 100644 --- a/solidity/contracts/StorageMirrorRootRegistry.sol +++ b/solidity/contracts/StorageMirrorRootRegistry.sol @@ -51,7 +51,7 @@ contract StorageMirrorRootRegistry is IStorageMirrorRootRegistry { bytes memory _blockHeader = _queryL1BlockHeader(); (bytes32 _latestVerifiedStorageMirrorStorageRoot, uint256 _blockNumber) = - VERIFIER_MODULE.extractStorageMirrorStorageRoot(_blockHeader, _accountProof); + VERIFIER_MODULE.extractStorageMirrorStorageRoot(_accountProof, _blockHeader); latestVerifiedStorageMirrorStorageRoot = _latestVerifiedStorageMirrorStorageRoot; latestVerifiedBlockNumber = _blockNumber; diff --git a/solidity/contracts/VerifierModule.sol b/solidity/contracts/VerifierModule.sol index b871dec..89481cd 100644 --- a/solidity/contracts/VerifierModule.sol +++ b/solidity/contracts/VerifierModule.sol @@ -112,7 +112,7 @@ contract VerifierModule is IVerifierModule { // Verify the account proof against the state root bytes memory _rlpAccount = MerklePatriciaProofVerifier.extractProofValue( _parsedBlockHeader.stateRootHash, - abi.encodePacked(keccak256(abi.encode(STORAGE_MIRROR))), + abi.encodePacked(keccak256(abi.encodePacked(STORAGE_MIRROR))), _storageMirrorAccountProof.toRlpItem().toList() ); @@ -123,7 +123,7 @@ contract VerifierModule is IVerifierModule { /** * @notice Verifies the new settings that are incoming against a storage proof from the StorageMirror on the home chain - * + * @dev This function makes the assumption that the safe address is the same on both the home and non-home chain as it will break if they are different * @param _safe The address of the safe that has new settings * @param _proposedSettings The new settings that are being proposed * @param _storageMirrorStorageProof The storage proof of the StorageMirror contract on the home chain @@ -142,6 +142,9 @@ contract VerifierModule is IVerifierModule { _updateLatestVerifiedSettings(_safe, _proposedSettings); + latestVerifiedSettings[_safe] = _hashedProposedSettings; + latestVerifiedSettingsTimestamp[_safe] = block.timestamp; + // Call the arbitrary transaction ISafe(_safe).execTransaction( _arbitraryTxnParams.to, @@ -156,10 +159,6 @@ contract VerifierModule is IVerifierModule { _arbitraryTxnParams.signatures ); - // Make the storage updates at the end of the call to save gas in a revert scenario - latestVerifiedSettings[_safe] = _hashedProposedSettings; - latestVerifiedSettingsTimestamp[_safe] = block.timestamp; - emit VerifiedUpdate(_safe, _hashedProposedSettings); // NOTE: This is not the exact gas spent as we still have to make the transaction after the calculation @@ -309,11 +308,11 @@ contract VerifierModule is IVerifierModule { // Ensure the source data is 32 bytes or less // Sanity check the keccak256() of the security settings should always fit in 32 bytes - if (_source.length > 32) revert VerifierModule_BytesToBytes32Failed(); + if (_source.length > 33) revert VerifierModule_BytesToBytes32Failed(); // Copy the data into the bytes32 variable assembly { - _result := mload(add(_source, 32)) + _result := mload(add(add(_source, 1), 32)) } } } diff --git a/solidity/libraries/StateVerifier.sol b/solidity/libraries/StateVerifier.sol index 3fe2c71..cc8a3ae 100644 --- a/solidity/libraries/StateVerifier.sol +++ b/solidity/libraries/StateVerifier.sol @@ -12,26 +12,29 @@ library StateVerifier { uint256 internal constant _HEADER_STATE_ROOT_INDEX = 3; uint256 internal constant _HEADER_NUMBER_INDEX = 8; + uint256 internal constant _HEADER_TIMESTAMP_INDEX = 11; struct BlockHeader { bytes32 hash; bytes32 stateRootHash; uint256 number; + uint256 timestamp; } function verifyBlockHeader(bytes memory _rlpBlockHeader) internal - pure + view returns (BlockHeader memory _parsedBlockHeader) { RLPReader.RLPItem[] memory headerFields = _rlpBlockHeader.toRlpItem().toList(); // Sanity check to ensure that the block header is long enough to be valid - if (headerFields.length <= _HEADER_NUMBER_INDEX) revert InvalidBlockHeader(); + if (headerFields.length <= _HEADER_TIMESTAMP_INDEX) revert InvalidBlockHeader(); _parsedBlockHeader.stateRootHash = bytes32(headerFields[_HEADER_STATE_ROOT_INDEX].toUint()); _parsedBlockHeader.number = headerFields[_HEADER_NUMBER_INDEX].toUint(); _parsedBlockHeader.hash = keccak256(_rlpBlockHeader); + _parsedBlockHeader.timestamp = headerFields[_HEADER_TIMESTAMP_INDEX].toUint(); } function extractStorageRootFromAccount(bytes memory _rlpAccount) internal pure returns (bytes32 _storageRoot) { diff --git a/solidity/scripts/DeployE2E.s.sol b/solidity/scripts/DeployE2E.s.sol new file mode 100644 index 0000000..dc5e404 --- /dev/null +++ b/solidity/scripts/DeployE2E.s.sol @@ -0,0 +1,238 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity =0.8.19; + +import {Script} from 'forge-std/Script.sol'; +import {console} from 'forge-std/console.sol'; +import {stdJson} from 'forge-std/StdJson.sol'; +import {Enum} from 'safe-contracts/common/Enum.sol'; +import {SafeProxy} from 'safe-contracts/proxies/SafeProxy.sol'; +import {Safe} from 'safe-contracts/Safe.sol'; + +import {DeployHomeChain, DeployVars} from 'scripts/DeployHomeChain.s.sol'; +import {DeployNonHomeChain, DeployVarsNonHomeChain} from 'scripts/DeployNonHomeChain.s.sol'; +import {TestConstants} from 'test/utils/TestConstants.sol'; +import {ISafe} from 'interfaces/ISafe.sol'; +import {IGuardCallbackModule} from 'interfaces/IGuardCallbackModule.sol'; +import {IVerifierModule} from 'interfaces/IVerifierModule.sol'; +import {IStorageMirrorRootRegistry} from 'interfaces/IStorageMirrorRootRegistry.sol'; +import {IStorageMirror} from 'interfaces/IStorageMirror.sol'; +import {IBlockHeaderOracle} from 'interfaces/IBlockHeaderOracle.sol'; +import {IGnosisSafeProxyFactory} from 'test/e2e/IGnosisSafeProxyFactory.sol'; + +struct Signature { + uint8 v; + bytes32 r; + bytes32 s; +} + +contract DeployE2E is Script, DeployHomeChain, DeployNonHomeChain { + address internal _deployer = vm.rememberKey(vm.envUint('MAINNET_DEPLOYER_PK')); + uint256 internal _pk = vm.envUint('MAINNET_DEPLOYER_PK'); + address[] internal _owners = [_deployer]; + Safe internal _singletonSafe; + Safe internal _singletonSafeOp; + IVerifierModule.SafeTxnParams internal _vars; + + function run() external { + vm.createSelectFork(vm.rpcUrl('mainnet_e2e')); + vm.startBroadcast(_deployer); + + _singletonSafe = new Safe(); + DeployVars memory _deployVarsHomeChain = DeployVars(_deployer); + + // Deploy protocol + _deployHomeChain(_deployVarsHomeChain); + ISafe _safe = ISafe(address(new SafeProxy(address(_singletonSafe)))); + address _storageMirrorAddr = + vm.parseJsonAddress(vm.readFile('./solidity/scripts/deployments/HomeChainDeployments.json'), '$.StorageMirror'); + + _setupHomeChain(_safe, _storageMirrorAddr); + + DeployVarsNonHomeChain memory _deployVarsNonHomeChain = DeployVarsNonHomeChain(_deployer, _storageMirrorAddr); + + // Deploy protocol + _deployNonHomeChain(_deployVarsNonHomeChain); + + address _verifierModule = vm.parseJsonAddress( + vm.readFile('./solidity/scripts/deployments/NonHomeChainDeployments.json'), '$.VerifierModule' + ); + + _setupNonHomeChain(_safe, _verifierModule); + + vm.stopBroadcast(); + + string memory _objectKey = 'deployments'; + + vm.serializeAddress(_objectKey, 'Safe', address(_safe)); + + string memory _output = vm.serializeAddress(_objectKey, 'SafeOp', address(_safe)); + + vm.writeJson(_output, './solidity/scripts/deployments/E2ESafeDeployments.json'); + } + + /** + * @notice Enables a module for the given safe + * @param _safe The safe that will enable the module + * @param _safeOwnerKey The private key to sign the tx + * @param _module The module address to enable + */ + function enableModule(ISafe _safe, uint256 _safeOwnerKey, address _module) public { + uint256 _safeNonce = _safe.nonce(); + // data to sign to enable module + bytes memory _enableModuleData = abi.encodeWithSelector(ISafe.enableModule.selector, address(_module)); + bytes memory _enableModuleEncodedTxData = _safe.encodeTransactionData( + address(_safe), 0, _enableModuleData, Enum.Operation.Call, 0, 0, 0, address(0), payable(0), _safeNonce + ); + + // signature + (uint8 _v, bytes32 _r, bytes32 _s) = vm.sign(_safeOwnerKey, keccak256(_enableModuleEncodedTxData)); + bytes memory _enableModuleSignature = abi.encodePacked(_r, _s, _v); + + // execute enable module + _safe.execTransaction( + address(_safe), 0, _enableModuleData, Enum.Operation.Call, 0, 0, 0, address(0), payable(0), _enableModuleSignature + ); + } + + function _setupHomeChain(ISafe _safe, address _storageMirrorAddr) internal { + _safe.setup(_owners, 1, address(_safe), bytes(''), address(0), address(0), 0, payable(address(0))); + + address _guardCallbackModule = vm.parseJsonAddress( + vm.readFile('./solidity/scripts/deployments/HomeChainDeployments.json'), '$.GuardCallbackModule' + ); + + enableModule(_safe, _pk, _guardCallbackModule); + + // data to sign and send to set the guard + bytes memory _txData = _safe.encodeTransactionData( + _guardCallbackModule, + 0, + abi.encodeWithSelector(IGuardCallbackModule.setGuard.selector), + Enum.Operation.Call, + 0, + 0, + 0, + address(0), + payable(0), + _safe.nonce() + ); + + Signature memory _signature; + + // signature + (_signature.v, _signature.r, _signature.s) = vm.sign(_pk, keccak256(_txData)); + + _vars = IVerifierModule.SafeTxnParams({ + to: address(_guardCallbackModule), + value: 0, + data: abi.encodeWithSelector(IGuardCallbackModule.setGuard.selector), + operation: Enum.Operation.Call, + safeTxGas: 0, + baseGas: 0, + gasPrice: 0, + gasToken: address(0), + refundReceiver: payable(address(0)), + signatures: abi.encodePacked(_signature.r, _signature.s, _signature.v) + }); + + _safe.execTransaction( + _vars.to, + _vars.value, + _vars.data, + _vars.operation, + _vars.safeTxGas, + _vars.baseGas, + _vars.gasPrice, + _vars.gasToken, + _vars.refundReceiver, + _vars.signatures + ); + + _txData = _safe.encodeTransactionData( + _storageMirrorAddr, + 0, + abi.encodeWithSelector(IStorageMirror.update.selector, keccak256(abi.encode(_owners, 1))), + Enum.Operation.Call, + 0, + 0, + 0, + address(0), + payable(0), + _safe.nonce() + ); + + // signature + (_signature.v, _signature.r, _signature.s) = vm.sign(_pk, keccak256(_txData)); + + _vars.to = _storageMirrorAddr; + _vars.data = abi.encodeWithSelector(IStorageMirror.update.selector, keccak256(abi.encode(_owners, 1))); + _vars.signatures = abi.encodePacked(_signature.r, _signature.s, _signature.v); + + // execute update storage mirror + _safe.execTransaction( + _vars.to, + _vars.value, + _vars.data, + _vars.operation, + _vars.safeTxGas, + _vars.baseGas, + _vars.gasPrice, + _vars.gasToken, + _vars.refundReceiver, + _vars.signatures + ); + } + + function _setupNonHomeChain(ISafe _safe, address _verifierModule) internal { + enableModule(_safe, _pk, _verifierModule); + + address _needsUpdateGuard = vm.parseJsonAddress( + vm.readFile('./solidity/scripts/deployments/NonHomeChainDeployments.json'), '$.NeedsUpdateGuard' + ); + + // data to sign and send to set the guard + bytes memory _txData = _safe.encodeTransactionData( + address(_safe), + 0, + abi.encodeWithSelector(ISafe.setGuard.selector, _needsUpdateGuard), + Enum.Operation.Call, + 0, + 0, + 0, + address(0), + payable(0), + _safe.nonce() + ); + + Signature memory _signature; + + // signature + (_signature.v, _signature.r, _signature.s) = vm.sign(_pk, keccak256(_txData)); + + _vars = IVerifierModule.SafeTxnParams({ + to: address(_safe), + value: 0, + data: abi.encodeWithSelector(ISafe.setGuard.selector, _needsUpdateGuard), + operation: Enum.Operation.Call, + safeTxGas: 0, + baseGas: 0, + gasPrice: 0, + gasToken: address(0), + refundReceiver: payable(address(0)), + signatures: abi.encodePacked(_signature.r, _signature.s, _signature.v) + }); + + _safe.execTransaction( + _vars.to, + _vars.value, + _vars.data, + _vars.operation, + _vars.safeTxGas, + _vars.baseGas, + _vars.gasPrice, + _vars.gasToken, + _vars.refundReceiver, + _vars.signatures + ); + } +} diff --git a/solidity/scripts/DeployGoerli.s.sol b/solidity/scripts/DeployGoerli.s.sol index 46e98d6..097aa04 100644 --- a/solidity/scripts/DeployGoerli.s.sol +++ b/solidity/scripts/DeployGoerli.s.sol @@ -13,7 +13,7 @@ contract DeployGoerli is DeployHomeChain { DeployVars memory _deployVars = DeployVars(deployer); // Deploy protocol - _deploy(_deployVars); + _deployHomeChain(_deployVars); vm.stopBroadcast(); } diff --git a/solidity/scripts/DeployHomeChain.s.sol b/solidity/scripts/DeployHomeChain.s.sol index 70fbc14..e81f974 100644 --- a/solidity/scripts/DeployHomeChain.s.sol +++ b/solidity/scripts/DeployHomeChain.s.sol @@ -3,7 +3,7 @@ pragma solidity =0.8.19; import {Script} from 'forge-std/Script.sol'; import {console} from 'forge-std/console.sol'; - +import {Test} from 'forge-std/Test.sol'; import {StorageMirror} from 'contracts/StorageMirror.sol'; import {UpdateStorageMirrorGuard} from 'contracts/UpdateStorageMirrorGuard.sol'; import {GuardCallbackModule} from 'contracts/GuardCallbackModule.sol'; @@ -16,7 +16,7 @@ struct DeployVars { } abstract contract DeployHomeChain is Script, TestConstants { - function _deploy(DeployVars memory _deployVars) + function _deployHomeChain(DeployVars memory _deployVars) internal returns ( StorageMirror _storageMirror, @@ -42,5 +42,13 @@ abstract contract DeployHomeChain is Script, TestConstants { console.log('UPDATE_STORAGE_MIRROR_GUARD: ', address(_updateStorageMirrorGuard)); console.log('DEPLOYMENT DONE'); + + string memory _objectKey = 'deployments'; + + vm.serializeAddress(_objectKey, 'StorageMirror', address(_storageMirror)); + vm.serializeAddress(_objectKey, 'UpdateStorageMirrorGuard', address(_updateStorageMirrorGuard)); + string memory _output = vm.serializeAddress(_objectKey, 'GuardCallbackModule', address(_guardCallbackModule)); + + vm.writeJson(_output, './solidity/scripts/deployments/HomeChainDeployments.json'); } } diff --git a/solidity/scripts/DeployMainnet.s.sol b/solidity/scripts/DeployMainnet.s.sol index 89ebe93..1422fc2 100644 --- a/solidity/scripts/DeployMainnet.s.sol +++ b/solidity/scripts/DeployMainnet.s.sol @@ -13,7 +13,7 @@ contract DeployMainnet is DeployHomeChain { DeployVars memory _deployVars = DeployVars(deployer); // Deploy protocol - _deploy(_deployVars); + _deployHomeChain(_deployVars); vm.stopBroadcast(); } diff --git a/solidity/scripts/DeployNonHomeChain.s.sol b/solidity/scripts/DeployNonHomeChain.s.sol index f06fcfc..a9b9761 100644 --- a/solidity/scripts/DeployNonHomeChain.s.sol +++ b/solidity/scripts/DeployNonHomeChain.s.sol @@ -15,13 +15,13 @@ import {IStorageMirrorRootRegistry} from 'interfaces/IStorageMirrorRootRegistry. import {TestConstants} from 'test/utils/TestConstants.sol'; import {ContractDeploymentAddress} from 'test/utils/ContractDeploymentAddress.sol'; -struct DeployVars { +struct DeployVarsNonHomeChain { address deployer; address storageMirror; } abstract contract DeployNonHomeChain is Script, TestConstants { - function _deploy(DeployVars memory _deployVars) + function _deployNonHomeChain(DeployVarsNonHomeChain memory _deployVars) internal returns ( BlockHeaderOracle _blockHeaderOracle, @@ -47,7 +47,17 @@ abstract contract DeployNonHomeChain is Script, TestConstants { new StorageMirrorRootRegistry(address(_deployVars.storageMirror), IVerifierModule(_verifierModule), IBlockHeaderOracle(_blockHeaderOracle)); // deployer nonce 2 console.log('STORAGE_MIRROR_ROOT_REGISTRY: ', address(_storageMirrorRootRegistry)); + assert(address(_storageMirrorRootRegistry) == _storageMirrorRootRegistryTheoriticalAddress); + _needsUpdateGuard = new NeedsUpdateGuard(_verifierModule); // deployer nonce 3 console.log('NEEDS_UPDATE_GUARD: ', address(_needsUpdateGuard)); + + string memory _objectKey = 'deployments'; + vm.serializeAddress(_objectKey, 'BlockHeaderOracle', address(_blockHeaderOracle)); + vm.serializeAddress(_objectKey, 'NeedsUpdateGuard', address(_needsUpdateGuard)); + vm.serializeAddress(_objectKey, 'StorageMirrorRootRegistry', address(_storageMirrorRootRegistry)); + string memory _output = vm.serializeAddress(_objectKey, 'VerifierModule', address(_verifierModule)); + + vm.writeJson(_output, './solidity/scripts/deployments/NonHomeChainDeployments.json'); } } diff --git a/solidity/scripts/DeployOptimism.s.sol b/solidity/scripts/DeployOptimism.s.sol index ad8f9f4..8bf6aef 100644 --- a/solidity/scripts/DeployOptimism.s.sol +++ b/solidity/scripts/DeployOptimism.s.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity =0.8.19; -import {DeployNonHomeChain, DeployVars} from 'scripts/DeployNonHomeChain.s.sol'; +import {DeployNonHomeChain, DeployVarsNonHomeChain} from 'scripts/DeployNonHomeChain.s.sol'; // We threat Goerli as the Home Chain in this case contract DeployOptimism is DeployNonHomeChain { @@ -11,10 +11,10 @@ contract DeployOptimism is DeployNonHomeChain { function run() external { vm.startBroadcast(deployer); - DeployVars memory _deployVars = DeployVars(deployer, storageMirrorAddress); + DeployVarsNonHomeChain memory _deployVars = DeployVarsNonHomeChain(deployer, storageMirrorAddress); // Deploy protocol - _deploy(_deployVars); + _deployNonHomeChain(_deployVars); vm.stopBroadcast(); } diff --git a/solidity/scripts/DeployOptimismGoerli.s.sol b/solidity/scripts/DeployOptimismGoerli.s.sol index e9d8bc9..92d9ce5 100644 --- a/solidity/scripts/DeployOptimismGoerli.s.sol +++ b/solidity/scripts/DeployOptimismGoerli.s.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity =0.8.19; -import {DeployNonHomeChain, DeployVars} from 'scripts/DeployNonHomeChain.s.sol'; +import {DeployNonHomeChain, DeployVarsNonHomeChain} from 'scripts/DeployNonHomeChain.s.sol'; // We threat Goerli as the Home Chain in this case contract DeployOptimismGoerli is DeployNonHomeChain { @@ -11,9 +11,9 @@ contract DeployOptimismGoerli is DeployNonHomeChain { function run() external { vm.startBroadcast(deployer); - DeployVars memory _deployVars = DeployVars(deployer, storageMirrorAddress); + DeployVarsNonHomeChain memory _deployVars = DeployVarsNonHomeChain(deployer, storageMirrorAddress); // Deploy protocol - _deploy(_deployVars); + _deployNonHomeChain(_deployVars); vm.stopBroadcast(); } diff --git a/solidity/scripts/deployments/deployments.md b/solidity/scripts/deployments/deployments.md new file mode 100644 index 0000000..189ac34 --- /dev/null +++ b/solidity/scripts/deployments/deployments.md @@ -0,0 +1,3 @@ +### This is the folder where the deployments JSON will go to for the E2E tests + +### This file is needed so the folder will show in github \ No newline at end of file diff --git a/solidity/test/e2e/Common.sol b/solidity/test/e2e/Common.sol index 6e28156..a18cd4c 100644 --- a/solidity/test/e2e/Common.sol +++ b/solidity/test/e2e/Common.sol @@ -31,16 +31,10 @@ contract CommonE2EBase is DSTestPlus, TestConstants { uint256 internal _mainnetForkId; uint256 internal _optimismForkId; + address internal _deployer = vm.rememberKey(vm.envUint('MAINNET_DEPLOYER_PK')); + address internal _searcher = vm.rememberKey(vm.envUint('SEARCHER_PK')); - address public deployer; - uint256 public deployerKey; - address public deployerOptimism = makeAddr('deployerOptimism'); - address public proposer = makeAddr('proposer'); - address public safeOwner; - uint256 public safeOwnerKey; - - address public nonHomeChainSafeOwner; - uint256 public nonHomeChainSafeOwnerKey; + address[] internal _owners = [_deployer]; StorageMirror public storageMirror; UpdateStorageMirrorGuard public updateStorageMirrorGuard; @@ -51,181 +45,95 @@ contract CommonE2EBase is DSTestPlus, TestConstants { StorageMirrorRootRegistry public storageMirrorRootRegistry; ISafe public safe; ISafe public nonHomeChainSafe; - IGnosisSafeProxyFactory public gnosisSafeProxyFactory = IGnosisSafeProxyFactory(GNOSIS_SAFE_PROXY_FACTORY); function setUp() public virtual { // Set up both forks - _mainnetForkId = vm.createFork(vm.rpcUrl('mainnet_e2e'), _MAINNET_FORK_BLOCK); - _optimismForkId = vm.createFork(vm.rpcUrl('optimism_e2e'), _OPTIMISM_FORK_BLOCK); - // Select mainnet fork - vm.selectFork(_mainnetForkId); - - // Make address and key of safe owner - safeOwner = vm.envAddress('MAINNET_SAFE_OWNER_ADDR'); - safeOwnerKey = vm.envUint('MAINNET_SAFE_OWNER_PK'); - - // Make address and key of deployer - deployer = vm.envAddress('MAINNET_DEPLOYER_ADDR'); - deployerKey = vm.envUint('MAINNET_DEPLOYER_PK'); - - /// =============== HOME CHAIN =============== - vm.broadcast(safeOwnerKey); - safe = ISafe(address(gnosisSafeProxyFactory.createProxy(GNOSIS_SAFE_SINGLETON, ''))); // safeOwner nonce 0 - label(address(safe), 'SafeProxy'); - - uint256 _nonce = vm.getNonce(deployer); - - address _updateStorageMirrorGuardTheoriticalAddress = ContractDeploymentAddress.addressFrom(deployer, _nonce + 2); - - vm.broadcast(deployer); - storageMirror = new StorageMirror(); // deployer nonce 0 - label(address(storageMirror), 'StorageMirror'); - - vm.broadcast(deployer); - guardCallbackModule = new GuardCallbackModule(address(storageMirror), _updateStorageMirrorGuardTheoriticalAddress); // deployer nonce 1 - label(address(guardCallbackModule), 'GuardCallbackModule'); - - vm.broadcast(deployer); - updateStorageMirrorGuard = new UpdateStorageMirrorGuard(guardCallbackModule); // deployer nonce 2 - label(address(updateStorageMirrorGuard), 'UpdateStorageMirrorGuard'); + _mainnetForkId = vm.createSelectFork(vm.rpcUrl('mainnet_e2e')); - // Make sure the theoritical address was calculated correctly - assert(address(updateStorageMirrorGuard) == _updateStorageMirrorGuardTheoriticalAddress); - - // Set up owner home chain safe - address[] memory _owners = new address[](1); - _owners[0] = safeOwner; - vm.broadcast(safeOwnerKey); // safeOwner nonce 1 - safe.setup(_owners, 1, address(safe), bytes(''), address(0), address(0), 0, payable(0)); - - // Enable guard callback module - enableModule(safe, safeOwnerKey, address(guardCallbackModule)); - - // data to sign and send to set the guard - bytes memory _setGuardData = abi.encodeWithSelector(IGuardCallbackModule.setGuard.selector); - bytes memory _setGuardEncodedTxData = safe.encodeTransactionData( - address(guardCallbackModule), 0, _setGuardData, Enum.Operation.Call, 0, 0, 0, address(0), payable(0), safe.nonce() + // Fetches all addresses from the deploy script + storageMirror = StorageMirror( + vm.parseJsonAddress(vm.readFile('./solidity/scripts/deployments/HomeChainDeployments.json'), '$.StorageMirror') ); - - // signature - (uint8 _v, bytes32 _r, bytes32 _s) = vm.sign(safeOwnerKey, keccak256(_setGuardEncodedTxData)); - bytes memory _setGuardSignature = abi.encodePacked(_r, _s, _v); - - // execute setup of guard - vm.broadcast(safeOwnerKey); - safe.execTransaction( - address(guardCallbackModule), - 0, - _setGuardData, - Enum.Operation.Call, - 0, - 0, - 0, - address(0), - payable(0), - _setGuardSignature + updateStorageMirrorGuard = UpdateStorageMirrorGuard( + vm.parseJsonAddress( + vm.readFile('./solidity/scripts/deployments/HomeChainDeployments.json'), '$.UpdateStorageMirrorGuard' + ) ); - - /// =============== NON HOME CHAIN =============== - vm.selectFork(_optimismForkId); - // Make address and key of non home chain safe owner - (nonHomeChainSafeOwner, nonHomeChainSafeOwnerKey) = makeAddrAndKey('nonHomeChainSafeOwner'); - - address _storageMirrorRootRegistryTheoriticalAddress = ContractDeploymentAddress.addressFrom(deployerOptimism, 2); - - // Set up non home chain safe - vm.broadcast(nonHomeChainSafeOwnerKey); - nonHomeChainSafe = ISafe(address(gnosisSafeProxyFactory.createProxy(GNOSIS_SAFE_SINGLETON_L2, ''))); // nonHomeChainSafeOwner nonce 0 - label(address(nonHomeChainSafe), 'NonHomeChainSafeProxy'); - - // Deploy non home chain contracts - oracle = new BlockHeaderOracle(); // deployerOptimism nonce 0 - label(address(oracle), 'BlockHeaderOracle'); - - vm.broadcast(deployerOptimism); - verifierModule = new VerifierModule( - IStorageMirrorRootRegistry(_storageMirrorRootRegistryTheoriticalAddress), address(storageMirror) - ); // deployerOptimism nonce 1 - label(address(verifierModule), 'VerifierModule'); - - vm.broadcast(deployerOptimism); - storageMirrorRootRegistry = - new StorageMirrorRootRegistry(address(storageMirror), IVerifierModule(verifierModule), IBlockHeaderOracle(oracle)); // deployerOptimism nonce 2 - label(address(storageMirrorRootRegistry), 'StorageMirrorRootRegistry'); - - vm.broadcast(deployerOptimism); - needsUpdateGuard = new NeedsUpdateGuard(verifierModule); // deployer nonce 3 - label(address(needsUpdateGuard), 'NeedsUpdateGuard'); - - // set up non home chain safe - address[] memory _nonHomeChainSafeOwners = new address[](1); - _nonHomeChainSafeOwners[0] = nonHomeChainSafeOwner; - - vm.broadcast(nonHomeChainSafeOwnerKey); // nonHomeChainSafeOwner nonce 1 - nonHomeChainSafe.setup( - _nonHomeChainSafeOwners, 1, address(nonHomeChainSafe), bytes(''), address(0), address(0), 0, payable(0) + guardCallbackModule = GuardCallbackModule( + vm.parseJsonAddress( + vm.readFile('./solidity/scripts/deployments/HomeChainDeployments.json'), '$.GuardCallbackModule' + ) ); - - // enable verifier module - enableModule(nonHomeChainSafe, nonHomeChainSafeOwnerKey, address(verifierModule)); - - // data to sign and send to set the guard - _setGuardData = abi.encodeWithSelector(ISafe.setGuard.selector, address(needsUpdateGuard)); - _setGuardEncodedTxData = nonHomeChainSafe.encodeTransactionData( - address(nonHomeChainSafe), - 0, - _setGuardData, - Enum.Operation.Call, - 0, - 0, - 0, - address(0), - payable(0), - nonHomeChainSafe.nonce() + oracle = BlockHeaderOracle( + vm.parseJsonAddress( + vm.readFile('./solidity/scripts/deployments/NonHomeChainDeployments.json'), '$.BlockHeaderOracle' + ) ); - - // signature - (_v, _r, _s) = vm.sign(nonHomeChainSafeOwnerKey, keccak256(_setGuardEncodedTxData)); - _setGuardSignature = abi.encodePacked(_r, _s, _v); - - // set needs update guard - vm.broadcast(nonHomeChainSafeOwnerKey); - nonHomeChainSafe.execTransaction( - address(nonHomeChainSafe), - 0, - _setGuardData, - Enum.Operation.Call, - 0, - 0, - 0, - address(0), - payable(0), - _setGuardSignature + needsUpdateGuard = NeedsUpdateGuard( + vm.parseJsonAddress( + vm.readFile('./solidity/scripts/deployments/NonHomeChainDeployments.json'), '$.NeedsUpdateGuard' + ) + ); + verifierModule = VerifierModule( + vm.parseJsonAddress( + vm.readFile('./solidity/scripts/deployments/NonHomeChainDeployments.json'), '$.VerifierModule' + ) ); + storageMirrorRootRegistry = StorageMirrorRootRegistry( + vm.parseJsonAddress( + vm.readFile('./solidity/scripts/deployments/NonHomeChainDeployments.json'), '$.StorageMirrorRootRegistry' + ) + ); + safe = ISafe(vm.parseJsonAddress(vm.readFile('./solidity/scripts/deployments/E2ESafeDeployments.json'), '$.Safe')); + nonHomeChainSafe = + ISafe(vm.parseJsonAddress(vm.readFile('./solidity/scripts/deployments/E2ESafeDeployments.json'), '$.SafeOp')); + + // Save the storage mirror proofs + saveProof( + vm.rpcUrl('mainnet_e2e'), + vm.toString(address(storageMirror)), + vm.toString((keccak256(abi.encode(address(safe), 0)))) + ); + } + + function saveProof(string memory _rpc, string memory _contractAddress, string memory _storageSlot) public { + string[] memory _commands = new string[](8); + _commands[0] = 'yarn'; + _commands[1] = 'proof'; + _commands[2] = '--rpc'; + _commands[3] = _rpc; + _commands[4] = '--contract'; + _commands[5] = _contractAddress; + _commands[6] = '--slot'; + _commands[7] = _storageSlot; + + vm.ffi(_commands); + } + + function getProof() + public + returns (bytes memory _storageProof, bytes memory _accountProof, bytes memory _blockHeader) + { + _storageProof = vm.parseJsonBytes(vm.readFile('./proofs/proof.json'), '$.storageProof'); + _blockHeader = vm.parseJsonBytes(vm.readFile('./proofs/proof.json'), '$.blockHeader'); + _accountProof = vm.parseJsonBytes(vm.readFile('./proofs/proof.json'), '$.accountProof'); } /** - * @notice Enables a module for the given safe - * @param _safe The safe that will enable the module - * @param _safeOwnerKey The private key to sign the tx - * @param _module The module address to enable + * @notice Helpers function to convert bytes to bytes32 + * + * @param _source The bytes to convert + * @return _result The bytes32 variable */ - function enableModule(ISafe _safe, uint256 _safeOwnerKey, address _module) public { - uint256 _safeNonce = _safe.nonce(); - // data to sign to enable module - bytes memory _enableModuleData = abi.encodeWithSelector(ISafe.enableModule.selector, address(_module)); - bytes memory _enableModuleEncodedTxData = _safe.encodeTransactionData( - address(_safe), 0, _enableModuleData, Enum.Operation.Call, 0, 0, 0, address(0), payable(0), _safeNonce - ); + function _bytesToBytes32(bytes memory _source) internal pure returns (bytes32 _result) { + // Ensure the source data is 32 bytes or less - // signature - (uint8 _v, bytes32 _r, bytes32 _s) = vm.sign(_safeOwnerKey, keccak256(_enableModuleEncodedTxData)); - bytes memory _enableModuleSignature = abi.encodePacked(_r, _s, _v); + // Sanity check the keccak256() of the security settings should always fit in 32 bytes + if (_source.length > 33) revert('cant fit'); - // execute enable module - vm.broadcast(safeOwnerKey); - _safe.execTransaction( - address(_safe), 0, _enableModuleData, Enum.Operation.Call, 0, 0, 0, address(0), payable(0), _enableModuleSignature - ); + // Copy the data into the bytes32 variable + assembly { + _result := mload(add(add(_source, 1), 32)) + } } } diff --git a/solidity/test/e2e/VerifierModule.t.sol b/solidity/test/e2e/VerifierModule.t.sol new file mode 100644 index 0000000..cbdacb2 --- /dev/null +++ b/solidity/test/e2e/VerifierModule.t.sol @@ -0,0 +1,108 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity >=0.8.4 <0.9.0; + +import {Enum} from 'safe-contracts/common/Enum.sol'; +import {CommonE2EBase} from 'test/e2e/Common.sol'; +import {StateVerifier} from 'libraries/StateVerifier.sol'; +import {MerklePatriciaProofVerifier} from 'libraries/MerklePatriciaProofVerifier.sol'; +import {RLPReader} from 'solidity-rlp/contracts/RLPReader.sol'; +import {IStorageMirror} from 'interfaces/IStorageMirror.sol'; +import {IVerifierModule} from 'interfaces/IVerifierModule.sol'; +import {StateVerifier} from 'libraries/StateVerifier.sol'; + +contract VerifierModuleE2E is CommonE2EBase { + using RLPReader for RLPReader.RLPItem; + using RLPReader for bytes; + + function setUp() public override { + super.setUp(); + + vm.selectFork(_mainnetForkId); + + vm.prank(address(_deployer)); + address(nonHomeChainSafe).call{value: 1e18}(''); + } + + function testExtractStateRoot() public { + (, bytes memory _accountProof, bytes memory _blockHeader) = getProof(); + + (bytes32 _stateRoot, uint256 _blockNumber) = + verifierModule.extractStorageMirrorStorageRoot(_accountProof, _blockHeader); + + uint256 _expectedBlockNumber = vm.parseJsonUint(vm.readFile('./proofs/proof.json'), '$.blockNumber'); + + assertEq(address(storageMirror), verifierModule.STORAGE_MIRROR()); + assertTrue(_stateRoot != bytes32(0)); + assertEq(_blockNumber, _expectedBlockNumber); + } + + function testStorageProofIsValid() public { + (bytes memory _storageProof, bytes memory _accountProof, bytes memory _blockHeader) = getProof(); + + (bytes32 _stateRoot,) = verifierModule.extractStorageMirrorStorageRoot(_accountProof, _blockHeader); + + bytes32 _slot = keccak256(abi.encode(address(safe), 0)); + bytes32 _slotHash = keccak256(abi.encodePacked(_slot)); + + address[] memory _owners = new address[](1); + _owners[0] = _deployer; + + uint256 _threshold = 1; + + bytes32 _expectedSettingsHash = + keccak256(abi.encode(IStorageMirror.SafeSettings({owners: _owners, threshold: _threshold}))); + + bytes memory _calculatedSettingsHash = MerklePatriciaProofVerifier.extractProofValue( + _stateRoot, abi.encodePacked(_slotHash), _storageProof.toRlpItem().toList() + ); + + bytes32 _calculatedSettingsHashBytes32 = _bytesToBytes32(_calculatedSettingsHash); + + assertEq(_calculatedSettingsHashBytes32, _expectedSettingsHash); + } + + function testFullFlowOfUpdatingSafe() public { + // Add an owner so the settings dont match the home chain + vm.prank(address(nonHomeChainSafe)); + nonHomeChainSafe.addOwnerWithThreshold(address(_searcher), 1); + + address[] memory _fakeOwners = nonHomeChainSafe.getOwners(); + + assertEq(_fakeOwners.length, 2, 'Owners should be 2'); + + IVerifierModule.SafeTxnParams memory _txn = IVerifierModule.SafeTxnParams({ + to: address(0), + value: 1, + data: '', + operation: Enum.Operation.Call, + safeTxGas: 0, + baseGas: 0, + gasPrice: 0, + gasToken: address(0), + refundReceiver: payable(address(0)), + signatures: '' + }); + + bytes memory _encodedTxn = nonHomeChainSafe.encodeTransactionData( + address(0), 1, '', Enum.Operation.Call, 0, 0, 0, address(0), payable(address(0)), nonHomeChainSafe.nonce() + ); + + (uint8 _v, bytes32 _r, bytes32 _s) = vm.sign(vm.envUint('MAINNET_DEPLOYER_PK'), keccak256(_encodedTxn)); + _txn.signatures = abi.encodePacked(_r, _s, _v); + + IStorageMirror.SafeSettings memory _safeSettings = IStorageMirror.SafeSettings({owners: _owners, threshold: 1}); + + (bytes memory _storageProof, bytes memory _accountProof, bytes memory _blockHeader) = getProof(); + + StateVerifier.BlockHeader memory _blockHeaderStruct = StateVerifier.verifyBlockHeader(_blockHeader); + + oracle.updateBlockHeader(_blockHeader, _blockHeaderStruct.timestamp, _blockHeaderStruct.number); + + assertEq(oracle.blockHeader(), _blockHeader, 'Block header should be saved'); + assertEq(oracle.blockTimestamp(), _blockHeaderStruct.timestamp, 'Timestamp should be saved'); + + verifierModule.extractStorageRootAndVerifyUpdate( + address(nonHomeChainSafe), _safeSettings, _accountProof, _storageProof, _txn + ); + } +} diff --git a/solidity/test/unit/StorageMirrorRootRegistry.t.sol b/solidity/test/unit/StorageMirrorRootRegistry.t.sol index dced08b..1a28c34 100644 --- a/solidity/test/unit/StorageMirrorRootRegistry.t.sol +++ b/solidity/test/unit/StorageMirrorRootRegistry.t.sol @@ -67,12 +67,12 @@ contract UnitStorageMirrorRootRegistryProposeAndVerifyStorageMirrorStorageRoot i vm.mockCall( address(verifierModule), - abi.encodeWithSelector(verifierModule.extractStorageMirrorStorageRoot.selector, _blockHeader, _accountProof), + abi.encodeWithSelector(verifierModule.extractStorageMirrorStorageRoot.selector, _accountProof, _blockHeader), abi.encode(_storageRoot, _blockNumber) ); vm.expectCall( address(verifierModule), - abi.encodeWithSelector(verifierModule.extractStorageMirrorStorageRoot.selector, _blockHeader, _accountProof) + abi.encodeWithSelector(verifierModule.extractStorageMirrorStorageRoot.selector, _accountProof, _blockHeader) ); vm.expectEmit(true, true, true, true); diff --git a/solidity/test/unit/VerifierModule.t.sol b/solidity/test/unit/VerifierModule.t.sol index dc837b8..d530fad 100644 --- a/solidity/test/unit/VerifierModule.t.sol +++ b/solidity/test/unit/VerifierModule.t.sol @@ -20,7 +20,7 @@ contract TestMPT { bytes32 _root, bytes memory _key, bytes memory _storageProof - ) external pure returns (bytes memory _value) { + ) external view returns (bytes memory _value) { // Need to create the RLP item internally due to memory collision RLPReader.RLPItem[] memory _stack = _storageProof.toRlpItem().toList(); _value = MerklePatriciaProofVerifier.extractProofValue(_root, _key, _stack); @@ -28,13 +28,13 @@ contract TestMPT { function verifyBlockHeader(bytes memory _rlpBlockHeader) external - pure + view returns (StateVerifier.BlockHeader memory _parsedBlockHeader) { _parsedBlockHeader = StateVerifier.verifyBlockHeader(_rlpBlockHeader); } - function extractStorageRootFromAccount(bytes memory _rlpAccount) external pure returns (bytes32 _storageRoot) { + function extractStorageRootFromAccount(bytes memory _rlpAccount) external view returns (bytes32 _storageRoot) { _storageRoot = StateVerifier.extractStorageRootFromAccount(_rlpAccount); } } @@ -67,7 +67,7 @@ contract TestVerifierModule is VerifierModule { bytes memory _slotValue = mpt.extractProofValue(_latestStorageRoot, abi.encodePacked(_safeSettingsSlotHash), _storageMirrorStorageProof); - bytes32 _hashedSavedSettings = _bytesToBytes32(_slotValue); + bytes32 _hashedSavedSettings = bytesToBytes32(_slotValue); _hashedProposedSettings = keccak256(abi.encode(_proposedSettings)); @@ -79,7 +79,15 @@ contract TestVerifierModule is VerifierModule { } function bytesToBytes32(bytes memory _bytes) public pure returns (bytes32 _bytes32) { - _bytes32 = _bytesToBytes32(_bytes); + // Ensure the source data is 32 bytes or less + + // Sanity check the keccak256() of the security settings should always fit in 32 bytes + if (_bytes.length > 33) revert VerifierModule_BytesToBytes32Failed(); + + // Copy the data into the bytes32 variable + assembly { + _bytes32 := mload(add(_bytes, 32)) + } } function extractStorageRootAndVerifyUpdateTest( @@ -847,8 +855,12 @@ contract UnitStorageRoot is Base { function testStorageMirrorStorageRootIsCalledWithCorrectParams(bytes memory _accountProof) public { vm.assume(_accountProof.length > 0); - StateVerifier.BlockHeader memory _fakeHeader = - StateVerifier.BlockHeader({hash: bytes32(uint256(1)), stateRootHash: bytes32(uint256(2)), number: 500}); + StateVerifier.BlockHeader memory _fakeHeader = StateVerifier.BlockHeader({ + hash: bytes32(uint256(1)), + stateRootHash: bytes32(uint256(2)), + number: 500, + timestamp: 5000 + }); bytes memory _rlpHeader = abi.encodePacked(_fakeHeader.hash); diff --git a/yarn.lock b/yarn.lock index dc0d41e..c5b537a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -258,6 +258,23 @@ dependencies: antlr4ts "^0.5.0-alpha.4" +"@trufflesuite/bigint-buffer@1.1.10": + version "1.1.10" + resolved "https://registry.yarnpkg.com/@trufflesuite/bigint-buffer/-/bigint-buffer-1.1.10.tgz#a1d9ca22d3cad1a138b78baaf15543637a3e1692" + integrity sha512-pYIQC5EcMmID74t26GCC67946mgTJFiLXOT/BYozgrd4UEY2JHEGLhWi9cMiQCt5BSqFEvKkCHNnoj82SRjiEw== + dependencies: + node-gyp-build "4.4.0" + +"@trufflesuite/uws-js-unofficial@20.30.0-unofficial.0": + version "20.30.0-unofficial.0" + resolved "https://registry.yarnpkg.com/@trufflesuite/uws-js-unofficial/-/uws-js-unofficial-20.30.0-unofficial.0.tgz#2fbc2f8ef7e82fbeea6abaf7e8a9d42a02b479d3" + integrity sha512-r5X0aOQcuT6pLwTRLD+mPnAM/nlKtvIK4Z+My++A8tTOR0qTjNRx8UB8jzRj3D+p9PMAp5LnpCUUGmz7/TppwA== + dependencies: + ws "8.13.0" + optionalDependencies: + bufferutil "4.0.7" + utf-8-validate "6.0.3" + "@tsconfig/node10@^1.0.7": version "1.0.9" resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.9.tgz#df4907fc07a886922637b15e02d4cebc4c0021b2" @@ -278,6 +295,13 @@ resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.4.tgz#0b92dcc0cc1c81f6f306a381f28e31b1a56536e9" integrity sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA== +"@types/bn.js@^5.1.0": + version "5.1.5" + resolved "https://registry.yarnpkg.com/@types/bn.js/-/bn.js-5.1.5.tgz#2e0dacdcce2c0f16b905d20ff87aedbc6f7b4bf0" + integrity sha512-V46N0zwKRF5Q00AZ6hWtN0T8gGmDUaUzLWQvHFo5yThtVwK/VCenFY3wXVbOvNfajEpsTfQM4IN9k/d6gUVX3A== + dependencies: + "@types/node" "*" + "@types/glob@^7.1.1": version "7.2.0" resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.2.0.tgz#bc1b5bf3aa92f25bd5dd39f35c57361bdce5b2eb" @@ -286,6 +310,11 @@ "@types/minimatch" "*" "@types/node" "*" +"@types/lru-cache@5.1.1": + version "5.1.1" + resolved "https://registry.yarnpkg.com/@types/lru-cache/-/lru-cache-5.1.1.tgz#c48c2e27b65d2a153b19bfc1a317e30872e01eef" + integrity sha512-ssE3Vlrys7sdIzs5LOxCzTVMsU7i9oa/IaW92wF32JFb3CVczqOkru2xspuKczHEbG3nvmPY7IFqVmGGHdNbYw== + "@types/minimatch@*": version "5.1.2" resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-5.1.2.tgz#07508b45797cb81ec3f273011b054cd0755eddca" @@ -306,6 +335,11 @@ resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz#d3357479a0fdfdd5907fe67e17e0a85c906e1301" integrity sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw== +"@types/seedrandom@3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@types/seedrandom/-/seedrandom-3.0.1.tgz#1254750a4fec4aff2ebec088ccd0bb02e91fedb4" + integrity sha512-giB9gzDeiCeloIXDgzFBCgjj1k4WxcDrZtGl6h1IqmUPlxF+Nx8Ve+96QCyDZ/HseB/uvDsKbpib9hU5cU53pw== + JSONStream@^1.0.4: version "1.3.5" resolved "https://registry.yarnpkg.com/JSONStream/-/JSONStream-1.3.5.tgz#3208c1f08d3a4d99261ab64f92302bc15e111ca0" @@ -318,6 +352,31 @@ JSONStream@^1.0.4: version "2.0.8" resolved "https://codeload.github.com/hamdiallam/Solidity-RLP/tar.gz/0212f8e754471da67fc5387df7855f47f944f925" +abstract-level@1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/abstract-level/-/abstract-level-1.0.3.tgz#78a67d3d84da55ee15201486ab44c09560070741" + integrity sha512-t6jv+xHy+VYwc4xqZMn2Pa9DjcdzvzZmQGRjTFc8spIbRGHgBrEKbPq+rYXc7CCo0lxgYvSgKVg9qZAhpVQSjA== + dependencies: + buffer "^6.0.3" + catering "^2.1.0" + is-buffer "^2.0.5" + level-supports "^4.0.0" + level-transcoder "^1.0.1" + module-error "^1.0.1" + queue-microtask "^1.2.3" + +abstract-leveldown@7.2.0, abstract-leveldown@^7.2.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/abstract-leveldown/-/abstract-leveldown-7.2.0.tgz#08d19d4e26fb5be426f7a57004851b39e1795a2e" + integrity sha512-DnhQwcFEaYsvYDnACLZhMmCWd3rkOeEvglpa4q5i/5Jlm3UIsWaxVzuXvDLFCSCWRO3yy2/+V/G7FusFgejnfQ== + dependencies: + buffer "^6.0.3" + catering "^2.0.0" + is-buffer "^2.0.5" + level-concat-iterator "^3.0.0" + level-supports "^2.0.1" + queue-microtask "^1.2.3" + acorn-jsx@^5.0.0: version "5.3.2" resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" @@ -474,11 +533,35 @@ astral-regex@^2.0.0: resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-2.0.0.tgz#483143c567aeed4785759c0865786dc77d7d2e31" integrity sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ== +async-eventemitter@0.2.4: + version "0.2.4" + resolved "https://registry.yarnpkg.com/async-eventemitter/-/async-eventemitter-0.2.4.tgz#f5e7c8ca7d3e46aab9ec40a292baf686a0bafaca" + integrity sha512-pd20BwL7Yt1zwDFy+8MX8F1+WCT8aQeKj0kQnTrH9WaeRETlRamVhD0JtRPmrV4GfOJ2F9CvdQkZeZhnh2TuHw== + dependencies: + async "^2.4.0" + +async@^2.4.0: + version "2.6.4" + resolved "https://registry.yarnpkg.com/async/-/async-2.6.4.tgz#706b7ff6084664cd7eae713f6f965433b5504221" + integrity sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA== + dependencies: + lodash "^4.17.14" + balanced-match@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== +base64-js@^1.3.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" + integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== + +bn.js@^4.11.9: + version "4.12.0" + resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.12.0.tgz#775b3f278efbb9718eec7361f483fb36fbbfea88" + integrity sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA== + brace-expansion@^1.1.7: version "1.1.11" resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" @@ -494,6 +577,33 @@ braces@^3.0.2: dependencies: fill-range "^7.0.1" +brorand@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f" + integrity sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w== + +buffer@^6.0.3: + version "6.0.3" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-6.0.3.tgz#2ace578459cc8fbe2a70aaa8f52ee63b6a74c6c6" + integrity sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA== + dependencies: + base64-js "^1.3.1" + ieee754 "^1.2.1" + +bufferutil@4.0.5: + version "4.0.5" + resolved "https://registry.yarnpkg.com/bufferutil/-/bufferutil-4.0.5.tgz#da9ea8166911cc276bf677b8aed2d02d31f59028" + integrity sha512-HTm14iMQKK2FjFLRTM5lAVcyaUzOnqbPtesFIvREgXpJHdQm8bWS+GkQgIkfaBYRHuCnea7w8UVNfwiAQhlr9A== + dependencies: + node-gyp-build "^4.3.0" + +bufferutil@4.0.7: + version "4.0.7" + resolved "https://registry.yarnpkg.com/bufferutil/-/bufferutil-4.0.7.tgz#60c0d19ba2c992dd8273d3f73772ffc894c153ad" + integrity sha512-kukuqc39WOHtdxtw4UScxF/WVnMFVSQVKhtx3AjZJzhd0RGZZldcrfSEbVsWWe6KNH253574cq5F+wpv0G9pJw== + dependencies: + node-gyp-build "^4.3.0" + caller-callsite@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/caller-callsite/-/caller-callsite-2.0.0.tgz#847e0fce0a223750a9a027c54b33731ad3154134" @@ -532,6 +642,11 @@ camelcase@^5.3.1: resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== +catering@^2.0.0, catering@^2.1.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/catering/-/catering-2.1.1.tgz#66acba06ed5ee28d5286133982a927de9a04b510" + integrity sha512-K7Qy8O9p76sL3/3m7/zLKbRkyOlSZAgzEaLhyj2mXS8PsCud2Eo4hAb8aLtZqHh0QGqLcb9dlJSu6lHRVENm1w== + chalk@5.2.0: version "5.2.0" resolved "https://registry.yarnpkg.com/chalk/-/chalk-5.2.0.tgz#249623b7d66869c673699fb66d65723e54dfcfb3" @@ -823,6 +938,24 @@ eastasianwidth@^0.2.0: resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb" integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA== +elliptic@^6.5.4: + version "6.5.4" + resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.4.tgz#da37cebd31e79a1367e941b592ed1fbebd58abbb" + integrity sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ== + dependencies: + bn.js "^4.11.9" + brorand "^1.1.0" + hash.js "^1.0.0" + hmac-drbg "^1.0.1" + inherits "^2.0.4" + minimalistic-assert "^1.0.1" + minimalistic-crypto-utils "^1.0.1" + +emittery@0.10.0: + version "0.10.0" + resolved "https://registry.yarnpkg.com/emittery/-/emittery-0.10.0.tgz#bb373c660a9d421bb44706ec4967ed50c02a8026" + integrity sha512-AGvFfs+d0JKCJQ4o01ASQLGPmSCxgfU9RFXvzPvZdjKK8oscynksuJhWrSTSw7j7Ep/sZct5b5ZhYCi8S/t0HQ== + emoji-regex@^7.0.1: version "7.0.3" resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156" @@ -1120,6 +1253,27 @@ functional-red-black-tree@^1.0.1: resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" integrity sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g== +ganache@7.9.1: + version "7.9.1" + resolved "https://registry.yarnpkg.com/ganache/-/ganache-7.9.1.tgz#94f8518215c7989ff5fd542db80bd47d7c7da786" + integrity sha512-Tqhd4J3cpiLeYTD6ek/zlchSB107IVPMIm4ypyg+xz1sdkeALUnYYZnmY4Bdjqj3i6QwtlZPCu7U4qKy7HlWTA== + dependencies: + "@trufflesuite/bigint-buffer" "1.1.10" + "@trufflesuite/uws-js-unofficial" "20.30.0-unofficial.0" + "@types/bn.js" "^5.1.0" + "@types/lru-cache" "5.1.1" + "@types/seedrandom" "3.0.1" + abstract-level "1.0.3" + abstract-leveldown "7.2.0" + async-eventemitter "0.2.4" + emittery "0.10.0" + keccak "3.0.2" + leveldown "6.1.0" + secp256k1 "4.0.3" + optionalDependencies: + bufferutil "4.0.5" + utf-8-validate "5.0.7" + get-caller-file@^2.0.5: version "2.0.5" resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" @@ -1218,6 +1372,23 @@ has@^1.0.3: dependencies: function-bind "^1.1.1" +hash.js@^1.0.0, hash.js@^1.0.3: + version "1.1.7" + resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.1.7.tgz#0babca538e8d4ee4a0f8988d68866537a003cf42" + integrity sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA== + dependencies: + inherits "^2.0.3" + minimalistic-assert "^1.0.1" + +hmac-drbg@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1" + integrity sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg== + dependencies: + hash.js "^1.0.3" + minimalistic-assert "^1.0.0" + minimalistic-crypto-utils "^1.0.1" + hosted-git-info@^2.1.4: version "2.8.9" resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.9.tgz#dffc0bf9a21c02209090f2aa69429e1414daf3f9" @@ -1252,6 +1423,11 @@ iconv-lite@^0.4.24: dependencies: safer-buffer ">= 2.1.2 < 3" +ieee754@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" + integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== + ignore@^4.0.6: version "4.0.6" resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc" @@ -1296,7 +1472,7 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2, inherits@^2.0.3: +inherits@2, inherits@^2.0.3, inherits@^2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== @@ -1330,6 +1506,11 @@ is-arrayish@^0.2.1: resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== +is-buffer@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-2.0.5.tgz#ebc252e400d22ff8d77fa09888821a24a658c191" + integrity sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ== + is-core-module@^2.11.0, is-core-module@^2.5.0: version "2.12.1" resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.12.1.tgz#0c0b6885b6f80011c71541ce15c8d66cf5a4f9fd" @@ -1474,11 +1655,54 @@ jsonparse@^1.2.0: resolved "https://registry.yarnpkg.com/jsonparse/-/jsonparse-1.3.1.tgz#3f4dae4a91fac315f71062f8521cc239f1366280" integrity sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg== +keccak@3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/keccak/-/keccak-3.0.2.tgz#4c2c6e8c54e04f2670ee49fa734eb9da152206e0" + integrity sha512-PyKKjkH53wDMLGrvmRGSNWgmSxZOUqbnXwKL9tmgbFYA1iAYqW21kfR7mZXV0MlESiefxQQE9X9fTa3X+2MPDQ== + dependencies: + node-addon-api "^2.0.0" + node-gyp-build "^4.2.0" + readable-stream "^3.6.0" + kind-of@^6.0.3: version "6.0.3" resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== +level-concat-iterator@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/level-concat-iterator/-/level-concat-iterator-3.1.0.tgz#5235b1f744bc34847ed65a50548aa88d22e881cf" + integrity sha512-BWRCMHBxbIqPxJ8vHOvKUsaO0v1sLYZtjN3K2iZJsRBYtp+ONsY6Jfi6hy9K3+zolgQRryhIn2NRZjZnWJ9NmQ== + dependencies: + catering "^2.1.0" + +level-supports@^2.0.1: + version "2.1.0" + resolved "https://registry.yarnpkg.com/level-supports/-/level-supports-2.1.0.tgz#9af908d853597ecd592293b2fad124375be79c5f" + integrity sha512-E486g1NCjW5cF78KGPrMDRBYzPuueMZ6VBXHT6gC7A8UYWGiM14fGgp+s/L1oFfDWSPV/+SFkYCmZ0SiESkRKA== + +level-supports@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/level-supports/-/level-supports-4.0.1.tgz#431546f9d81f10ff0fea0e74533a0e875c08c66a" + integrity sha512-PbXpve8rKeNcZ9C1mUicC9auIYFyGpkV9/i6g76tLgANwWhtG2v7I4xNBUlkn3lE2/dZF3Pi0ygYGtLc4RXXdA== + +level-transcoder@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/level-transcoder/-/level-transcoder-1.0.1.tgz#f8cef5990c4f1283d4c86d949e73631b0bc8ba9c" + integrity sha512-t7bFwFtsQeD8cl8NIoQ2iwxA0CL/9IFw7/9gAjOonH0PWTTiRfY7Hq+Ejbsxh86tXobDQ6IOiddjNYIfOBs06w== + dependencies: + buffer "^6.0.3" + module-error "^1.0.1" + +leveldown@6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/leveldown/-/leveldown-6.1.0.tgz#7ab1297706f70c657d1a72b31b40323aa612b9ee" + integrity sha512-8C7oJDT44JXxh04aSSsfcMI8YiaGRhOFI9/pMEL7nWJLVsWajDPTRxsSHTM2WcTVY5nXM+SuRHzPPi0GbnDX+w== + dependencies: + abstract-leveldown "^7.2.0" + napi-macros "~2.0.0" + node-gyp-build "^4.3.0" + levn@^0.3.0, levn@~0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee" @@ -1681,6 +1905,16 @@ min-indent@^1.0.0: resolved "https://registry.yarnpkg.com/min-indent/-/min-indent-1.0.1.tgz#a63f681673b30571fbe8bc25686ae746eefa9869" integrity sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg== +minimalistic-assert@^1.0.0, minimalistic-assert@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" + integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A== + +minimalistic-crypto-utils@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a" + integrity sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg== + minimatch@^3.0.4, minimatch@^3.1.1: version "3.1.2" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" @@ -1709,6 +1943,11 @@ mkdirp@^0.5.1: dependencies: minimist "^1.2.6" +module-error@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/module-error/-/module-error-1.0.2.tgz#8d1a48897ca883f47a45816d4fb3e3c6ba404d86" + integrity sha512-0yuvsqSCv8LbaOKhnsQ/T5JhyFlCYLPXK3U2sgV10zoKQwzs/MyfuQUOZQ1V/6OCOJsK/TRgNVrPuPDqtdMFtA== + ms@2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" @@ -1719,6 +1958,11 @@ mute-stream@0.0.7: resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab" integrity sha512-r65nCZhrbXXb6dXOACihYApHw2Q6pV0M3V0PSxd74N0+D8nzAdEAITq2oAjA1jVnKI+tGvEBUpqiMh0+rW6zDQ== +napi-macros@~2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/napi-macros/-/napi-macros-2.0.0.tgz#2b6bae421e7b96eb687aa6c77a7858640670001b" + integrity sha512-A0xLykHtARfueITVDernsAWdtIMbOJgKgcluwENp3AlsKN/PloyO10HtmoqnFAQAcxPkgZN7wdfPfEd0zNGxbg== + natural-compare@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" @@ -1729,6 +1973,21 @@ nice-try@^1.0.4: resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== +node-addon-api@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-2.0.2.tgz#432cfa82962ce494b132e9d72a15b29f71ff5d32" + integrity sha512-Ntyt4AIXyaLIuMHF6IOoTakB3K+RWxwtsHNRxllEoA6vPwP9o4866g6YWDLUdnucilZhmkxiHwHr11gAENw+QA== + +node-gyp-build@4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.4.0.tgz#42e99687ce87ddeaf3a10b99dc06abc11021f3f4" + integrity sha512-amJnQCcgtRVw9SvoebO3BKGESClrfXGCUTX9hSn1OuGQTQBOZmVd0Z0OlecpuRksKvbsUqALE8jls/ErClAPuQ== + +node-gyp-build@^4.2.0, node-gyp-build@^4.3.0: + version "4.7.1" + resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.7.1.tgz#cd7d2eb48e594874053150a9418ac85af83ca8f7" + integrity sha512-wTSrZ+8lsRRa3I3H8Xr65dLWSgCvY2l4AOnaeKdPA9TB/WYMPaTcrzf3rXvFoVvjKNVnu0CcWSx54qq9GKRUYg== + normalize-package-data@^2.5.0: version "2.5.0" resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8" @@ -1962,7 +2221,7 @@ q@^1.5.1: resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7" integrity sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw== -queue-microtask@^1.2.2: +queue-microtask@^1.2.2, queue-microtask@^1.2.3: version "1.2.3" resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== @@ -1991,7 +2250,7 @@ read-pkg@^5.2.0: parse-json "^5.0.0" type-fest "^0.6.0" -readable-stream@3, readable-stream@^3.0.0: +readable-stream@3, readable-stream@^3.0.0, readable-stream@^3.6.0: version "3.6.2" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967" integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA== @@ -2127,6 +2386,15 @@ safe-buffer@~5.2.0: resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== +secp256k1@4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/secp256k1/-/secp256k1-4.0.3.tgz#c4559ecd1b8d3c1827ed2d1b94190d69ce267303" + integrity sha512-NLZVf+ROMxwtEj3Xa562qgv2BK5e2WNmXPiOdVIPLgs6lyTzMvBq0aWTYMI5XCP9jZMVKOcqZLw/Wc4vDkuxhA== + dependencies: + elliptic "^6.5.4" + node-addon-api "^2.0.0" + node-gyp-build "^4.2.0" + "semver@2 || 3 || 4 || 5", semver@^5.5.0, semver@^5.5.1: version "5.7.1" resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" @@ -2566,6 +2834,20 @@ uri-js@^4.2.2: dependencies: punycode "^2.1.0" +utf-8-validate@5.0.7: + version "5.0.7" + resolved "https://registry.yarnpkg.com/utf-8-validate/-/utf-8-validate-5.0.7.tgz#c15a19a6af1f7ad9ec7ddc425747ca28c3644922" + integrity sha512-vLt1O5Pp+flcArHGIyKEQq883nBt8nN8tVBcoL0qUXj2XT1n7p70yGIq2VK98I5FdZ1YHc0wk/koOnHjnXWk1Q== + dependencies: + node-gyp-build "^4.3.0" + +utf-8-validate@6.0.3: + version "6.0.3" + resolved "https://registry.yarnpkg.com/utf-8-validate/-/utf-8-validate-6.0.3.tgz#7d8c936d854e86b24d1d655f138ee27d2636d777" + integrity sha512-uIuGf9TWQ/y+0Lp+KGZCMuJWc3N9BHA+l/UmHd/oUHwJJDeysyTRxNQVkbzsIWfGFbRe3OcgML/i0mvVRPOyDA== + dependencies: + node-gyp-build "^4.3.0" + util-deprecate@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" @@ -2633,6 +2915,11 @@ write@1.0.3: dependencies: mkdirp "^0.5.1" +ws@8.13.0: + version "8.13.0" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.13.0.tgz#9a9fb92f93cf41512a0735c8f4dd09b8a1211cd0" + integrity sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA== + y18n@^5.0.5: version "5.0.8" resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" From 5d5458b810464f15b045308a0ece479fa691b98f Mon Sep 17 00:00:00 2001 From: excaliborr <124819095+excaliborr@users.noreply.github.com> Date: Tue, 28 Nov 2023 08:08:49 -0500 Subject: [PATCH 27/29] test: e2e verification (#25) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # 🤖 Linear Closes SAF-XXX --------- Co-authored-by: OneTony --- solidity/contracts/VerifierModule.sol | 24 +++- solidity/test/e2e/VerifierModule.t.sol | 178 ++++++++++++++++++++++-- solidity/test/unit/VerifierModule.t.sol | 50 ++++++- 3 files changed, 231 insertions(+), 21 deletions(-) diff --git a/solidity/contracts/VerifierModule.sol b/solidity/contracts/VerifierModule.sol index 89481cd..76b6ce4 100644 --- a/solidity/contracts/VerifierModule.sol +++ b/solidity/contracts/VerifierModule.sol @@ -228,6 +228,18 @@ contract VerifierModule is IVerifierModule { address[] memory _newOwners = _proposedSettings.owners; bool _hasUpdatedThreshold; + // If all we need to do is a swap the rest of the logic is not needed and we can just call the swapOwner function + if (_oldOwners.length == 1 && _newOwners.length == 1 && _oldOwners[0] != _newOwners[0]) { + ISafe(_safe).execTransactionFromModule( + _safe, + 0, + abi.encodeWithSelector(ISafe.swapOwner.selector, _SENTINEL_OWNERS, _oldOwners[0], _newOwners[0]), + Enum.Operation.Call + ); + + return; + } + // NOTE: Threshold is automatically updated inside these calls if it needs to be updated for (uint256 _i; _i < _newOwners.length;) { if (!ISafe(_safe).isOwner(_newOwners[_i])) { @@ -245,16 +257,20 @@ contract VerifierModule is IVerifierModule { } } - for (uint256 _i; _i < _oldOwners.length;) { - if (!_linearSearchOwners(_oldOwners[_i], _newOwners)) { + // Get the owners again in case any updates were made from the previous loop + // We need to do this because the linked list for owners in safe inserts to the beginning of the list + address[] memory _changedOwners = ISafe(_safe).getOwners(); + + for (uint256 _i; _i < _changedOwners.length;) { + if (!_linearSearchOwners(_changedOwners[_i], _newOwners)) { _hasUpdatedThreshold = true; ISafe(_safe).execTransactionFromModule( _safe, 0, abi.encodeWithSelector( ISafe.removeOwner.selector, - _oldOwners[_i], - int256(_i) - 1 < 0 ? _SENTINEL_OWNERS : _oldOwners[_i - 1], + int256(_i) - 1 < 0 ? _SENTINEL_OWNERS : _changedOwners[_i - 1], + _changedOwners[_i], _newThreshold ), Enum.Operation.Call diff --git a/solidity/test/e2e/VerifierModule.t.sol b/solidity/test/e2e/VerifierModule.t.sol index cbdacb2..4d5b0b0 100644 --- a/solidity/test/e2e/VerifierModule.t.sol +++ b/solidity/test/e2e/VerifierModule.t.sol @@ -14,6 +14,19 @@ contract VerifierModuleE2E is CommonE2EBase { using RLPReader for RLPReader.RLPItem; using RLPReader for bytes; + IVerifierModule.SafeTxnParams _txn = IVerifierModule.SafeTxnParams({ + to: address(0), + value: 1, + data: '', + operation: Enum.Operation.Call, + safeTxGas: 0, + baseGas: 0, + gasPrice: 0, + gasToken: address(0), + refundReceiver: payable(address(0)), + signatures: '' + }); + function setUp() public override { super.setUp(); @@ -36,6 +49,143 @@ contract VerifierModuleE2E is CommonE2EBase { assertEq(_blockNumber, _expectedBlockNumber); } + function testProposeAndVerify() public { + // Add an owner so the settings dont match the home chain + vm.prank(address(nonHomeChainSafe)); + nonHomeChainSafe.addOwnerWithThreshold(address(_searcher), 1); + + address[] memory _fakeOwners = nonHomeChainSafe.getOwners(); + + assertEq(_fakeOwners.length, 2, 'Owners should be 2'); + + bytes memory _encodedTxn = nonHomeChainSafe.encodeTransactionData( + address(0), 1, '', Enum.Operation.Call, 0, 0, 0, address(0), payable(address(0)), nonHomeChainSafe.nonce() + ); + + (uint8 _v, bytes32 _r, bytes32 _s) = vm.sign(vm.envUint('MAINNET_DEPLOYER_PK'), keccak256(_encodedTxn)); + _txn.signatures = abi.encodePacked(_r, _s, _v); + + IStorageMirror.SafeSettings memory _safeSettings = IStorageMirror.SafeSettings({owners: _owners, threshold: 1}); + + (bytes memory _storageProof, bytes memory _accountProof, bytes memory _blockHeader) = getProof(); + + StateVerifier.BlockHeader memory _blockHeaderStruct = StateVerifier.verifyBlockHeader(_blockHeader); + + oracle.updateBlockHeader(_blockHeader, _blockHeaderStruct.timestamp, _blockHeaderStruct.number); + + storageMirrorRootRegistry.proposeAndVerifyStorageMirrorStorageRoot(_accountProof); + + uint256 _searcherBalance = address(_searcher).balance; + uint256 _addressZeroBalance = address(0).balance; + + vm.prank(_searcher); + verifierModule.proposeAndVerifyUpdate(address(nonHomeChainSafe), _safeSettings, _storageProof, _txn); + + address[] memory _newOwners = nonHomeChainSafe.getOwners(); + + assertEq(_newOwners.length, 1, 'Owners should be 1'); + assertEq(_newOwners[0], address(_deployer), 'Owner should be the deployer'); + assertGt(address(_searcher).balance, _searcherBalance, 'Searcher should be rewarded'); + assertGt( + address(0).balance, + _addressZeroBalance, + 'Address zero should have more funds because we sent 1 wei the arbitrary txn' + ); + } + + function testChangingOwnersAndThreshold() public { + // Add an owner so the settings dont match the home chain + vm.startPrank(address(nonHomeChainSafe)); + nonHomeChainSafe.addOwnerWithThreshold(address(_searcher), 1); + nonHomeChainSafe.changeThreshold(2); + vm.stopPrank(); + + address[] memory _fakeOwners = nonHomeChainSafe.getOwners(); + + assertEq(_fakeOwners.length, 2, 'Owners should be 2'); + assertEq(nonHomeChainSafe.getThreshold(), 2, 'Threshold should be 2'); + + bytes memory _encodedTxn = nonHomeChainSafe.encodeTransactionData( + address(0), 1, '', Enum.Operation.Call, 0, 0, 0, address(0), payable(address(0)), nonHomeChainSafe.nonce() + ); + + (uint8 _v, bytes32 _r, bytes32 _s) = vm.sign(vm.envUint('MAINNET_DEPLOYER_PK'), keccak256(_encodedTxn)); + _txn.signatures = abi.encodePacked(_r, _s, _v); + + IStorageMirror.SafeSettings memory _safeSettings = IStorageMirror.SafeSettings({owners: _owners, threshold: 1}); + + (bytes memory _storageProof, bytes memory _accountProof, bytes memory _blockHeader) = getProof(); + + StateVerifier.BlockHeader memory _blockHeaderStruct = StateVerifier.verifyBlockHeader(_blockHeader); + + oracle.updateBlockHeader(_blockHeader, _blockHeaderStruct.timestamp, _blockHeaderStruct.number); + + storageMirrorRootRegistry.proposeAndVerifyStorageMirrorStorageRoot(_accountProof); + + uint256 _searcherBalance = address(_searcher).balance; + uint256 _addressZeroBalance = address(0).balance; + + vm.prank(_searcher); + verifierModule.proposeAndVerifyUpdate(address(nonHomeChainSafe), _safeSettings, _storageProof, _txn); + + address[] memory _newOwners = nonHomeChainSafe.getOwners(); + + assertEq(_newOwners.length, 1, 'Owners should be 1'); + assertEq(_newOwners[0], address(_deployer), 'Owner should be the deployer'); + assertGt(address(_searcher).balance, _searcherBalance, 'Searcher should be rewarded'); + assertGt( + address(0).balance, + _addressZeroBalance, + 'Address zero should have more funds because we sent 1 wei the arbitrary txn' + ); + } + + function testVerifySwappingOwners() public { + // Add an owner so the settings dont match the home chain + vm.startPrank(address(nonHomeChainSafe)); + nonHomeChainSafe.addOwnerWithThreshold(address(_searcher), 1); + nonHomeChainSafe.removeOwner(address(_searcher), address(_deployer), 1); + vm.stopPrank(); + + address[] memory _fakeOwners = nonHomeChainSafe.getOwners(); + + assertEq(_fakeOwners.length, 1, 'Owners should be 1'); + + bytes memory _encodedTxn = nonHomeChainSafe.encodeTransactionData( + address(0), 1, '', Enum.Operation.Call, 0, 0, 0, address(0), payable(address(0)), nonHomeChainSafe.nonce() + ); + + (uint8 _v, bytes32 _r, bytes32 _s) = vm.sign(vm.envUint('MAINNET_DEPLOYER_PK'), keccak256(_encodedTxn)); + _txn.signatures = abi.encodePacked(_r, _s, _v); + + IStorageMirror.SafeSettings memory _safeSettings = IStorageMirror.SafeSettings({owners: _owners, threshold: 1}); + + (bytes memory _storageProof, bytes memory _accountProof, bytes memory _blockHeader) = getProof(); + + StateVerifier.BlockHeader memory _blockHeaderStruct = StateVerifier.verifyBlockHeader(_blockHeader); + + oracle.updateBlockHeader(_blockHeader, _blockHeaderStruct.timestamp, _blockHeaderStruct.number); + + storageMirrorRootRegistry.proposeAndVerifyStorageMirrorStorageRoot(_accountProof); + + uint256 _searcherBalance = address(_searcher).balance; + uint256 _addressZeroBalance = address(0).balance; + + vm.prank(_searcher); + verifierModule.proposeAndVerifyUpdate(address(nonHomeChainSafe), _safeSettings, _storageProof, _txn); + + address[] memory _newOwners = nonHomeChainSafe.getOwners(); + + assertEq(_newOwners.length, 1, 'Owners should be 1'); + assertEq(_newOwners[0], address(_deployer), 'Owner should be the deployer'); + assertGt(address(_searcher).balance, _searcherBalance, 'Searcher should be rewarded'); + assertGt( + address(0).balance, + _addressZeroBalance, + 'Address zero should have more funds because we sent 1 wei the arbitrary txn' + ); + } + function testStorageProofIsValid() public { (bytes memory _storageProof, bytes memory _accountProof, bytes memory _blockHeader) = getProof(); @@ -70,19 +220,6 @@ contract VerifierModuleE2E is CommonE2EBase { assertEq(_fakeOwners.length, 2, 'Owners should be 2'); - IVerifierModule.SafeTxnParams memory _txn = IVerifierModule.SafeTxnParams({ - to: address(0), - value: 1, - data: '', - operation: Enum.Operation.Call, - safeTxGas: 0, - baseGas: 0, - gasPrice: 0, - gasToken: address(0), - refundReceiver: payable(address(0)), - signatures: '' - }); - bytes memory _encodedTxn = nonHomeChainSafe.encodeTransactionData( address(0), 1, '', Enum.Operation.Call, 0, 0, 0, address(0), payable(address(0)), nonHomeChainSafe.nonce() ); @@ -101,8 +238,23 @@ contract VerifierModuleE2E is CommonE2EBase { assertEq(oracle.blockHeader(), _blockHeader, 'Block header should be saved'); assertEq(oracle.blockTimestamp(), _blockHeaderStruct.timestamp, 'Timestamp should be saved'); + uint256 _searcherBalance = address(_searcher).balance; + uint256 _addressZeroBalance = address(0).balance; + + vm.prank(_searcher); verifierModule.extractStorageRootAndVerifyUpdate( address(nonHomeChainSafe), _safeSettings, _accountProof, _storageProof, _txn ); + + address[] memory _newOwners = nonHomeChainSafe.getOwners(); + + assertEq(_newOwners.length, 1, 'Owners should be 1'); + assertEq(_newOwners[0], address(_deployer), 'Owner should be the deployer'); + assertGt(address(_searcher).balance, _searcherBalance, 'Searcher should be rewarded'); + assertGt( + address(0).balance, + _addressZeroBalance, + 'Address zero should have more funds because we sent 1 wei the arbitrary txn' + ); } } diff --git a/solidity/test/unit/VerifierModule.t.sol b/solidity/test/unit/VerifierModule.t.sol index d530fad..3f84441 100644 --- a/solidity/test/unit/VerifierModule.t.sol +++ b/solidity/test/unit/VerifierModule.t.sol @@ -173,6 +173,48 @@ abstract contract Base is Test { } contract UnitUpdateSettings is Base { + function testUpdateSwapsOwners() public { + address[] memory _oldOwners = new address[](1); + _oldOwners[0] = address(0x2); + + address[] memory _newOwners = new address[](1); + _newOwners[0] = address(0x3); + + IStorageMirror.SafeSettings memory _newSettings = IStorageMirror.SafeSettings({owners: _newOwners, threshold: 1}); + + vm.mockCall(_fakeSafe, abi.encodeWithSelector(ISafe.getOwners.selector), abi.encode(_oldOwners)); + + vm.mockCall(_fakeSafe, abi.encodeWithSelector(ISafe.getThreshold.selector), abi.encode(2)); + + vm.mockCall(_fakeSafe, abi.encodeWithSelector(ISafe.isOwner.selector), abi.encode(true)); + + vm.mockCall(_fakeSafe, abi.encodeWithSelector(ISafe.isOwner.selector, address(0x3)), abi.encode(false)); + + vm.mockCall( + _fakeSafe, + abi.encodeWithSelector( + ISafe.execTransactionFromModule.selector, + _fakeSafe, + 0, + abi.encodeWithSelector(ISafe.swapOwner.selector, address(0x1), address(0x2), address(0x3)), + Enum.Operation.Call + ), + abi.encode(true) + ); + vm.expectCall( + _fakeSafe, + abi.encodeWithSelector( + ISafe.execTransactionFromModule.selector, + _fakeSafe, + 0, + abi.encodeWithSelector(ISafe.swapOwner.selector, address(0x1), address(0x2), address(0x3)), + Enum.Operation.Call + ) + ); + + verifierModule.updateLatestVerifiedSettings(_fakeSafe, _newSettings); + } + function testUpdateSettingsAddsOwners() public { address[] memory _oldOwners = new address[](2); _oldOwners[0] = address(0x2); @@ -242,7 +284,7 @@ contract UnitUpdateSettings is Base { ISafe.execTransactionFromModule.selector, _fakeSafe, 0, - abi.encodeWithSelector(ISafe.removeOwner.selector, address(0x4), address(0x3), 2), + abi.encodeWithSelector(ISafe.removeOwner.selector, address(0x3), address(0x4), 2), Enum.Operation.Call ), abi.encode(true) @@ -253,7 +295,7 @@ contract UnitUpdateSettings is Base { ISafe.execTransactionFromModule.selector, _fakeSafe, 0, - abi.encodeWithSelector(ISafe.removeOwner.selector, address(0x4), address(0x3), 2), + abi.encodeWithSelector(ISafe.removeOwner.selector, address(0x3), address(0x4), 2), Enum.Operation.Call ) ); @@ -285,7 +327,7 @@ contract UnitUpdateSettings is Base { ISafe.execTransactionFromModule.selector, _fakeSafe, 0, - abi.encodeWithSelector(ISafe.removeOwner.selector, address(0x2), address(0x1), 2), + abi.encodeWithSelector(ISafe.removeOwner.selector, address(0x1), address(0x2), 2), Enum.Operation.Call ), abi.encode(true) @@ -296,7 +338,7 @@ contract UnitUpdateSettings is Base { ISafe.execTransactionFromModule.selector, _fakeSafe, 0, - abi.encodeWithSelector(ISafe.removeOwner.selector, address(0x2), address(0x1), 2), + abi.encodeWithSelector(ISafe.removeOwner.selector, address(0x1), address(0x2), 2), Enum.Operation.Call ) ); From 2386f79577b94e2a5a80dc4b736082cf2e3c480d Mon Sep 17 00:00:00 2001 From: 0xOneTony <112496816+0xOneTony@users.noreply.github.com> Date: Tue, 28 Nov 2023 15:56:48 +0200 Subject: [PATCH 28/29] fix: repo details (#26) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # 🤖 Linear Closes SAF-XXX --- .env.example | 8 ++-- .github/workflows/test.yml | 6 +-- .gitignore | 2 +- README.md | 39 ++++++++++++++++--- foundry.toml | 3 +- ...odes.js => integration-tests-with-nodes.js | 2 +- package.json | 10 ++--- ...eployE2E.s.sol => DeployIntegration.s.sol} | 8 ++-- solidity/scripts/DeployNonHomeChain.s.sol | 10 +++-- solidity/scripts/deployments/deployments.md | 2 +- solidity/test/e2e/Test.t.sol | 14 ------- .../IGnosisSafeProxyFactory.sol | 0 .../IntegrationBase.sol} | 17 ++++---- .../{e2e => integration}/VerifierModule.t.sol | 22 +++++------ 14 files changed, 79 insertions(+), 64 deletions(-) rename e2e-tests-with-nodes.js => integration-tests-with-nodes.js (98%) rename solidity/scripts/{DeployE2E.s.sol => DeployIntegration.s.sol} (95%) delete mode 100644 solidity/test/e2e/Test.t.sol rename solidity/test/{e2e => integration}/IGnosisSafeProxyFactory.sol (100%) rename solidity/test/{e2e/Common.sol => integration/IntegrationBase.sol} (90%) rename solidity/test/{e2e => integration}/VerifierModule.t.sol (94%) diff --git a/.env.example b/.env.example index ca80b02..541dd7e 100644 --- a/.env.example +++ b/.env.example @@ -3,13 +3,13 @@ GOERLI_RPC= OPTIMISM_RPC= OPTIMISM_GOERLI_RPC= -# Deployer private key for the E2E Tests +# Deployer private key for the Integrations Tests MAINNET_DEPLOYER_PK= -# Searcher PK for E2E Tests +# Searcher PK for Integration Tests SEARCHER_PK= -# Mainnet rpc for the E2E Tests, should be the anvil url -MAINNET_E2E_RPC= +# Mainnet rpc for the Integration Tests, should be the ganache url +MAINNET_INTEGRATION_TESTS_RPC= ## For deployment scripts DEPLOYER_MAINNNET_PRIVATE_KEY= diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 7bb6866..bb664f7 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -8,7 +8,7 @@ concurrency: jobs: forge: - name: Run Unit and E2E Tests + name: Run Unit and Integration Tests runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 @@ -44,13 +44,13 @@ jobs: run: | touch .env echo MAINNET_RPC="${{ secrets.MAINNET_RPC }}" >> .env - echo MAINNET_E2E_RPC="${{ secrets.MAINNET_E2E_RPC }}" >> .env + echo MAINNET_INTEGRATION_TESTS_RPC="${{ secrets.MAINNET_INTEGRATION_TESTS_RPC }}" >> .env echo SEARCHER_PK="${{ secrets.SEARCHER_PK }}" >> .env echo MAINNET_DEPLOYER_PK="${{ secrets.MAINNET_DEPLOYER_PK }}" >> .env cat .env - name: Run tests - run: yarn test:e2e-workflow + run: yarn test:integration-workflow forge-optimized: name: Run Optimized Unit Tests diff --git a/.gitignore b/.gitignore index 0d9d254..fc18317 100644 --- a/.gitignore +++ b/.gitignore @@ -27,6 +27,6 @@ docs/src/static # Cache for the python scripts proofs/__pycache__/* -# Deployments for E2E +# Deployments for Integration tests solidity/scripts/deployments/*.json proofs/proof.json diff --git a/README.md b/README.md index 7e5aa16..6983524 100644 --- a/README.md +++ b/README.md @@ -21,25 +21,52 @@ yarn install yarn build ``` -In order to run the E2E tests you will also need python setup to generate the proofs, to do this run: +## Integration Tests + +In order to run the integration tests you will need python setup to generate the proofs, ganache running and some enviroment variables. + +1. Set up python and install requirements ```sh python -m pip install --upgrade pip pip install -r requirements.txt ``` -### Available Commands +2. Run ganache + +```sh +yarn ganache +``` + +3. Set enviroment variables -Make sure to set `MAINNET_RPC` and `OPTIMISM_RPC` environment variable before running end-to-end tests. +`MAINNET_INTEGRATION_TESTS_RPC` should be the ganache endpoint +`MAINNET_DEPLOYER_PK` should be the deployer of the protocol and a safe owner +`SEARCHER_PK` should be the incentivized actor to verify + +4. Run the tests + +```sh +yarn test:integration +``` + +### Available Commands | Yarn Command | Description | | ----------------------- | ---------------------------------------------------------- | | `yarn build` | Compile all contracts. | | `yarn coverage` | See `forge coverage` report. | -| `yarn deploy` | Deploy the contracts to Mainnet. | -| `yarn test` | Run all unit and e2e tests. | +| `yarn deploy` | Deploy the contracts to Mainnet. | +| `yarn test` | Run all unit and integration tests. | | `yarn test:unit` | Run unit tests. | -| `yarn test:e2e` | Run e2e tests. | +| `yarn test:integration` | Run integration tests. | +| `yarn deploy:mainnet` | Deploys Home Chain contracts to Mainnet | +| `yarn deploy:optimism` | Deploys Non-Home Chain contracts to Optimism | +| `yarn deploy:goerli` | Deploys Home Chain contracts to Goerli | +| `yarn deploy:optimismGoerli`| Deploys Non-Home Chain contracts to Optimism Goerli | +| `yarn docs:build` | Build the docs | +| `yarn docs:run` | Runs the docs, needs mdbook | +| `yarn ganache` | Spawn a ganache instance | ## Smart Contracts diff --git a/foundry.toml b/foundry.toml index 0550769..7d12c3f 100644 --- a/foundry.toml +++ b/foundry.toml @@ -39,5 +39,4 @@ src = './solidity/interfaces/' mainnet = "${MAINNET_RPC}" goerli = "${GOERLI_RPC}" optimism = "${OPTIMISM_RPC}" -mainnet_e2e = "${MAINNET_E2E_RPC}" -optimism_e2e = "${OPTIMISM_E2E_RPC}" +mainnet_integration = "${MAINNET_INTEGRATION_TESTS_RPC}" diff --git a/e2e-tests-with-nodes.js b/integration-tests-with-nodes.js similarity index 98% rename from e2e-tests-with-nodes.js rename to integration-tests-with-nodes.js index e6afc49..4ac2802 100644 --- a/e2e-tests-with-nodes.js +++ b/integration-tests-with-nodes.js @@ -12,7 +12,7 @@ require('dotenv').config(); // Initialize dotenv to load environment variables // Running end-to-end tests console.debug(`Running tests`); - const testProcess = spawn('yarn', [`test:e2e`]); + const testProcess = spawn('yarn', [`test:integration`]); // Handle test errors testProcess.stderr.on('data', (data) => console.error(`Test error: ${data}`)); diff --git a/package.json b/package.json index 19cd46f..00f2b6b 100644 --- a/package.json +++ b/package.json @@ -10,13 +10,11 @@ }, "author": "Wonderland", "scripts": { - "anvil:mainnet": "anvil --port 8545 -f $MAINNET_RPC --fork-block-number 18621047 --chain-id 1", - "anvil:optimism": "anvil --port 9545 -f $OPTIMISM_RPC --fork-block-number 112491451 --chain-id 10", "build": "forge build", "build:optimized": "FOUNDRY_PROFILE=optimized forge build", "coverage": "forge coverage --match-contract Unit", - "deploy:e2e": "forge script solidity/scripts/DeployE2E.s.sol:DeployE2E --broadcast", "deploy:goerli": "bash -c 'source .env && forge script -vv --rpc-url $GOERLI_RPC --slow --broadcast --private-key $DEPLOYER_GOERLI_PRIVATE_KEY solidity/scripts/DeployGoerli.s.sol:DeployGoerli'", + "deploy:integration": "forge script solidity/scripts/DeployIntegration.s.sol:DeployIntegration --broadcast", "deploy:mainnet": "bash -c 'source .env && forge script -vv --rpc-url $MAINNET_RPC --slow --broadcast --private-key $DEPLOYER_MAINNNET_PRIVATE_KEY solidity/scripts/DeployMainnet.s.sol:DeployMainnet'", "deploy:optimism": "bash -c 'source .env && forge script -vv --rpc-url $OPTIMISM_RPC --slow --broadcast --private-key $DEPLOYER_OPTIMISM_PRIVATE_KEY solidity/scripts/DeployOptimism.s.sol:DeployOptimism'", "deploy:optimismGoerli": "bash -c 'source .env && forge script -vv --rpc-url $OPTIMISM_GOERLI_RPC --slow --broadcast --private-key $DEPLOYER_OPTIMISM_GOERLI_PRIVATE_KEY solidity/scripts/DeployOptimismGoerli.s.sol:DeployOptimismGoerli'", @@ -29,9 +27,9 @@ "lint:sol-tests": "solhint 'solidity/test/**/*.sol'", "prepare": "husky install", "proof": "python3 proofs/generate_proof.py", - "test": "yarn deploy:e2e && forge test --ffi -vvv", - "test:e2e": "yarn deploy:e2e && forge test --ffi --match-contract E2E -vvv", - "test:e2e-workflow": "node e2e-tests-with-nodes.js", + "test": "yarn deploy:integration && forge test --ffi -vvv", + "test:integration": "yarn deploy:integration && forge test --ffi --match-contract Integration -vvv", + "test:integration-workflow": "node integration-tests-with-nodes.js", "test:unit": "forge test --match-contract Unit -vvv", "test:unit:deep": "FOUNDRY_FUZZ_RUNS=5000 yarn test:unit" }, diff --git a/solidity/scripts/DeployE2E.s.sol b/solidity/scripts/DeployIntegration.s.sol similarity index 95% rename from solidity/scripts/DeployE2E.s.sol rename to solidity/scripts/DeployIntegration.s.sol index dc5e404..0186a19 100644 --- a/solidity/scripts/DeployE2E.s.sol +++ b/solidity/scripts/DeployIntegration.s.sol @@ -17,7 +17,7 @@ import {IVerifierModule} from 'interfaces/IVerifierModule.sol'; import {IStorageMirrorRootRegistry} from 'interfaces/IStorageMirrorRootRegistry.sol'; import {IStorageMirror} from 'interfaces/IStorageMirror.sol'; import {IBlockHeaderOracle} from 'interfaces/IBlockHeaderOracle.sol'; -import {IGnosisSafeProxyFactory} from 'test/e2e/IGnosisSafeProxyFactory.sol'; +import {IGnosisSafeProxyFactory} from 'test/integration/IGnosisSafeProxyFactory.sol'; struct Signature { uint8 v; @@ -25,7 +25,7 @@ struct Signature { bytes32 s; } -contract DeployE2E is Script, DeployHomeChain, DeployNonHomeChain { +contract DeployIntegration is Script, DeployHomeChain, DeployNonHomeChain { address internal _deployer = vm.rememberKey(vm.envUint('MAINNET_DEPLOYER_PK')); uint256 internal _pk = vm.envUint('MAINNET_DEPLOYER_PK'); address[] internal _owners = [_deployer]; @@ -34,7 +34,7 @@ contract DeployE2E is Script, DeployHomeChain, DeployNonHomeChain { IVerifierModule.SafeTxnParams internal _vars; function run() external { - vm.createSelectFork(vm.rpcUrl('mainnet_e2e')); + vm.createSelectFork(vm.rpcUrl('mainnet_integration')); vm.startBroadcast(_deployer); _singletonSafe = new Safe(); @@ -67,7 +67,7 @@ contract DeployE2E is Script, DeployHomeChain, DeployNonHomeChain { string memory _output = vm.serializeAddress(_objectKey, 'SafeOp', address(_safe)); - vm.writeJson(_output, './solidity/scripts/deployments/E2ESafeDeployments.json'); + vm.writeJson(_output, './solidity/scripts/deployments/IntegrationSafeDeployments.json'); } /** diff --git a/solidity/scripts/DeployNonHomeChain.s.sol b/solidity/scripts/DeployNonHomeChain.s.sol index a9b9761..6c76855 100644 --- a/solidity/scripts/DeployNonHomeChain.s.sol +++ b/solidity/scripts/DeployNonHomeChain.s.sol @@ -39,12 +39,14 @@ abstract contract DeployNonHomeChain is Script, TestConstants { _blockHeaderOracle = new BlockHeaderOracle(); // deployer nonce 0 console.log('ORACLE: ', address(_blockHeaderOracle)); - _verifierModule = - new VerifierModule(IStorageMirrorRootRegistry(_storageMirrorRootRegistryTheoriticalAddress), address(_deployVars.storageMirror)); // deployer nonce 1 + _verifierModule = new VerifierModule( + IStorageMirrorRootRegistry(_storageMirrorRootRegistryTheoriticalAddress), address(_deployVars.storageMirror) + ); // deployer nonce 1 console.log('VERIFIER_MODULE: ', address(_verifierModule)); - _storageMirrorRootRegistry = - new StorageMirrorRootRegistry(address(_deployVars.storageMirror), IVerifierModule(_verifierModule), IBlockHeaderOracle(_blockHeaderOracle)); // deployer nonce 2 + _storageMirrorRootRegistry = new StorageMirrorRootRegistry( + address(_deployVars.storageMirror), IVerifierModule(_verifierModule), IBlockHeaderOracle(_blockHeaderOracle) + ); // deployer nonce 2 console.log('STORAGE_MIRROR_ROOT_REGISTRY: ', address(_storageMirrorRootRegistry)); assert(address(_storageMirrorRootRegistry) == _storageMirrorRootRegistryTheoriticalAddress); diff --git a/solidity/scripts/deployments/deployments.md b/solidity/scripts/deployments/deployments.md index 189ac34..86c8767 100644 --- a/solidity/scripts/deployments/deployments.md +++ b/solidity/scripts/deployments/deployments.md @@ -1,3 +1,3 @@ -### This is the folder where the deployments JSON will go to for the E2E tests +### This is the folder where the deployments JSON will go to for the Integration tests ### This file is needed so the folder will show in github \ No newline at end of file diff --git a/solidity/test/e2e/Test.t.sol b/solidity/test/e2e/Test.t.sol deleted file mode 100644 index 8462d82..0000000 --- a/solidity/test/e2e/Test.t.sol +++ /dev/null @@ -1,14 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity >=0.8.4 <0.9.0; - -import {CommonE2EBase} from 'test/e2e/Common.sol'; - -contract TestE2E is CommonE2EBase { - function setUp() public override { - super.setUp(); - } - - function test_test() public { - assertTrue(true); - } -} diff --git a/solidity/test/e2e/IGnosisSafeProxyFactory.sol b/solidity/test/integration/IGnosisSafeProxyFactory.sol similarity index 100% rename from solidity/test/e2e/IGnosisSafeProxyFactory.sol rename to solidity/test/integration/IGnosisSafeProxyFactory.sol diff --git a/solidity/test/e2e/Common.sol b/solidity/test/integration/IntegrationBase.sol similarity index 90% rename from solidity/test/e2e/Common.sol rename to solidity/test/integration/IntegrationBase.sol index a18cd4c..52f7aa7 100644 --- a/solidity/test/e2e/Common.sol +++ b/solidity/test/integration/IntegrationBase.sol @@ -20,12 +20,12 @@ import {IVerifierModule} from 'interfaces/IVerifierModule.sol'; import {IStorageMirrorRootRegistry} from 'interfaces/IStorageMirrorRootRegistry.sol'; import {IBlockHeaderOracle} from 'interfaces/IBlockHeaderOracle.sol'; -import {IGnosisSafeProxyFactory} from 'test/e2e/IGnosisSafeProxyFactory.sol'; +import {IGnosisSafeProxyFactory} from 'test/integration/IGnosisSafeProxyFactory.sol'; import {TestConstants} from 'test/utils/TestConstants.sol'; import {ContractDeploymentAddress} from 'test/utils/ContractDeploymentAddress.sol'; // solhint-disable-next-line max-states-count -contract CommonE2EBase is DSTestPlus, TestConstants { +contract IntegrationBase is DSTestPlus, TestConstants { uint256 internal constant _MAINNET_FORK_BLOCK = 18_621_047; uint256 internal constant _OPTIMISM_FORK_BLOCK = 112_491_451; @@ -48,7 +48,7 @@ contract CommonE2EBase is DSTestPlus, TestConstants { function setUp() public virtual { // Set up both forks - _mainnetForkId = vm.createSelectFork(vm.rpcUrl('mainnet_e2e')); + _mainnetForkId = vm.createSelectFork(vm.rpcUrl('mainnet_integration')); // Fetches all addresses from the deploy script storageMirror = StorageMirror( @@ -84,13 +84,16 @@ contract CommonE2EBase is DSTestPlus, TestConstants { vm.readFile('./solidity/scripts/deployments/NonHomeChainDeployments.json'), '$.StorageMirrorRootRegistry' ) ); - safe = ISafe(vm.parseJsonAddress(vm.readFile('./solidity/scripts/deployments/E2ESafeDeployments.json'), '$.Safe')); - nonHomeChainSafe = - ISafe(vm.parseJsonAddress(vm.readFile('./solidity/scripts/deployments/E2ESafeDeployments.json'), '$.SafeOp')); + safe = ISafe( + vm.parseJsonAddress(vm.readFile('./solidity/scripts/deployments/IntegrationSafeDeployments.json'), '$.Safe') + ); + nonHomeChainSafe = ISafe( + vm.parseJsonAddress(vm.readFile('./solidity/scripts/deployments/IntegrationSafeDeployments.json'), '$.SafeOp') + ); // Save the storage mirror proofs saveProof( - vm.rpcUrl('mainnet_e2e'), + vm.rpcUrl('mainnet_integration'), vm.toString(address(storageMirror)), vm.toString((keccak256(abi.encode(address(safe), 0)))) ); diff --git a/solidity/test/e2e/VerifierModule.t.sol b/solidity/test/integration/VerifierModule.t.sol similarity index 94% rename from solidity/test/e2e/VerifierModule.t.sol rename to solidity/test/integration/VerifierModule.t.sol index 4d5b0b0..5f8674a 100644 --- a/solidity/test/e2e/VerifierModule.t.sol +++ b/solidity/test/integration/VerifierModule.t.sol @@ -2,7 +2,7 @@ pragma solidity >=0.8.4 <0.9.0; import {Enum} from 'safe-contracts/common/Enum.sol'; -import {CommonE2EBase} from 'test/e2e/Common.sol'; +import {IntegrationBase} from 'test/integration/IntegrationBase.sol'; import {StateVerifier} from 'libraries/StateVerifier.sol'; import {MerklePatriciaProofVerifier} from 'libraries/MerklePatriciaProofVerifier.sol'; import {RLPReader} from 'solidity-rlp/contracts/RLPReader.sol'; @@ -10,11 +10,11 @@ import {IStorageMirror} from 'interfaces/IStorageMirror.sol'; import {IVerifierModule} from 'interfaces/IVerifierModule.sol'; import {StateVerifier} from 'libraries/StateVerifier.sol'; -contract VerifierModuleE2E is CommonE2EBase { +contract IntegrationVerifierModule is IntegrationBase { using RLPReader for RLPReader.RLPItem; using RLPReader for bytes; - IVerifierModule.SafeTxnParams _txn = IVerifierModule.SafeTxnParams({ + IVerifierModule.SafeTxnParams public txn = IVerifierModule.SafeTxnParams({ to: address(0), value: 1, data: '', @@ -63,7 +63,7 @@ contract VerifierModuleE2E is CommonE2EBase { ); (uint8 _v, bytes32 _r, bytes32 _s) = vm.sign(vm.envUint('MAINNET_DEPLOYER_PK'), keccak256(_encodedTxn)); - _txn.signatures = abi.encodePacked(_r, _s, _v); + txn.signatures = abi.encodePacked(_r, _s, _v); IStorageMirror.SafeSettings memory _safeSettings = IStorageMirror.SafeSettings({owners: _owners, threshold: 1}); @@ -79,7 +79,7 @@ contract VerifierModuleE2E is CommonE2EBase { uint256 _addressZeroBalance = address(0).balance; vm.prank(_searcher); - verifierModule.proposeAndVerifyUpdate(address(nonHomeChainSafe), _safeSettings, _storageProof, _txn); + verifierModule.proposeAndVerifyUpdate(address(nonHomeChainSafe), _safeSettings, _storageProof, txn); address[] memory _newOwners = nonHomeChainSafe.getOwners(); @@ -110,7 +110,7 @@ contract VerifierModuleE2E is CommonE2EBase { ); (uint8 _v, bytes32 _r, bytes32 _s) = vm.sign(vm.envUint('MAINNET_DEPLOYER_PK'), keccak256(_encodedTxn)); - _txn.signatures = abi.encodePacked(_r, _s, _v); + txn.signatures = abi.encodePacked(_r, _s, _v); IStorageMirror.SafeSettings memory _safeSettings = IStorageMirror.SafeSettings({owners: _owners, threshold: 1}); @@ -126,7 +126,7 @@ contract VerifierModuleE2E is CommonE2EBase { uint256 _addressZeroBalance = address(0).balance; vm.prank(_searcher); - verifierModule.proposeAndVerifyUpdate(address(nonHomeChainSafe), _safeSettings, _storageProof, _txn); + verifierModule.proposeAndVerifyUpdate(address(nonHomeChainSafe), _safeSettings, _storageProof, txn); address[] memory _newOwners = nonHomeChainSafe.getOwners(); @@ -156,7 +156,7 @@ contract VerifierModuleE2E is CommonE2EBase { ); (uint8 _v, bytes32 _r, bytes32 _s) = vm.sign(vm.envUint('MAINNET_DEPLOYER_PK'), keccak256(_encodedTxn)); - _txn.signatures = abi.encodePacked(_r, _s, _v); + txn.signatures = abi.encodePacked(_r, _s, _v); IStorageMirror.SafeSettings memory _safeSettings = IStorageMirror.SafeSettings({owners: _owners, threshold: 1}); @@ -172,7 +172,7 @@ contract VerifierModuleE2E is CommonE2EBase { uint256 _addressZeroBalance = address(0).balance; vm.prank(_searcher); - verifierModule.proposeAndVerifyUpdate(address(nonHomeChainSafe), _safeSettings, _storageProof, _txn); + verifierModule.proposeAndVerifyUpdate(address(nonHomeChainSafe), _safeSettings, _storageProof, txn); address[] memory _newOwners = nonHomeChainSafe.getOwners(); @@ -225,7 +225,7 @@ contract VerifierModuleE2E is CommonE2EBase { ); (uint8 _v, bytes32 _r, bytes32 _s) = vm.sign(vm.envUint('MAINNET_DEPLOYER_PK'), keccak256(_encodedTxn)); - _txn.signatures = abi.encodePacked(_r, _s, _v); + txn.signatures = abi.encodePacked(_r, _s, _v); IStorageMirror.SafeSettings memory _safeSettings = IStorageMirror.SafeSettings({owners: _owners, threshold: 1}); @@ -243,7 +243,7 @@ contract VerifierModuleE2E is CommonE2EBase { vm.prank(_searcher); verifierModule.extractStorageRootAndVerifyUpdate( - address(nonHomeChainSafe), _safeSettings, _accountProof, _storageProof, _txn + address(nonHomeChainSafe), _safeSettings, _accountProof, _storageProof, txn ); address[] memory _newOwners = nonHomeChainSafe.getOwners(); From dc0d88a1e6945ed1ed395bfe38f0a8b07f9da7f3 Mon Sep 17 00:00:00 2001 From: 0xOneTony <112496816+0xOneTony@users.noreply.github.com> Date: Tue, 28 Nov 2023 21:53:53 +0200 Subject: [PATCH 29/29] chore: remove codeowners (#28) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # 🤖 Linear Closes SAF-XXX --- .github/CODEOWNERS | 1 - 1 file changed, 1 deletion(-) delete mode 100644 .github/CODEOWNERS diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS deleted file mode 100644 index 5e3e3ed..0000000 --- a/.github/CODEOWNERS +++ /dev/null @@ -1 +0,0 @@ -* defi-wonderland/default-codeowner \ No newline at end of file