diff --git a/broadcast/155-deploy-sale.s.sol/7887/run-1739030918.json b/broadcast/155-deploy-sale.s.sol/7887/run-1739030918.json new file mode 100644 index 00000000..1f85eff9 --- /dev/null +++ b/broadcast/155-deploy-sale.s.sol/7887/run-1739030918.json @@ -0,0 +1,302 @@ +{ + "transactions": [ + { + "hash": "0x55f9c9c447311fbe329d2f01414445cdfff118ffe049dd1127a20dedbfaa1f3e", + "transactionType": "CREATE", + "contractName": "SealedBidTokenSale", + "contractAddress": "0xe9ab275d7e9859bbef11a79b7c1854a030d0c171", + "function": null, + "arguments": [ + "0x010700808D59d2bb92257fCafACfe8e5bFF7aB87", + "0x793500709506652Fcc61F0d2D0fDa605638D4293", + "0x05DC0010C9902EcF6CBc921c6A4bd971c69E5A2E", + "1739296800", + "1739901600", + "250000000000" + ], + "transaction": { + "from": "0x660ad4b5a74130a4796b4d54bc6750ae93c86e6c", + "value": "0x0", + "input": "0x6101606040523060805234801562000015575f80fd5b50604051620021bd380380620021bd8339810160408190526200003891620001d2565b6200004262000102565b6001600160a01b0386166200007a576040516307094c3360e11b81526001600160a01b03871660048201526024015b60405180910390fd5b6001600160a01b038516620000ae576040516334d5d27d60e21b81526001600160a01b038616600482015260240162000071565b818310620000cf57604051631800812760e11b815260040160405180910390fd5b6001600160a01b0395861660a05293851660e0529190931660c05261010092909252610120919091526101405262000234565b7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00805468010000000000000000900460ff1615620001535760405163f92ee8a960e01b815260040160405180910390fd5b80546001600160401b0390811614620001b35780546001600160401b0319166001600160401b0390811782556040519081527fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d29060200160405180910390a15b50565b80516001600160a01b0381168114620001cd575f80fd5b919050565b5f805f805f8060c08789031215620001e8575f80fd5b620001f387620001b6565b95506200020360208801620001b6565b94506200021360408801620001b6565b9350606087015192506080870151915060a087015190509295509295509295565b60805160a05160c05160e051610100516101205161014051611eb9620003045f395f8181610689015281816109a90152610d6601525f818161048401528181610d3c01526111a301525f81816102ec01528181610b6801528181610ba301528181610d1901528181611109015261114401525f8181610388015261101601525f81816104ff015281816108d501528181610ae901528181611040015281816110b3015261129601525f8181610622015261088401525f8181611498015281816114c101526116000152611eb95ff3fe608060405260043610610207575f3560e01c806378e9792511610113578063ad3cb1cc1161009d578063ecfd89281161006d578063ecfd892814610644578063f2fde38b14610659578063f381f2a514610678578063fc7e286d146106ab578063ff50abdc146106d6575f80fd5b8063ad3cb1cc1461059e578063e1e158a5146105db578063e2bbb158146105f2578063e985e36714610611575f80fd5b806389a30271116100e357806389a30271146104ee5780638bd29968146105215780638da5cb5b146105365780639038e693146105725780639b8906ae14610586575f80fd5b806378e97925146104735780637cb64759146104a65780638129fc1c146104c557806388de2330146104d9575f80fd5b80634f93594511610194578063665828021161016457806366582802146103c25780636bb8a6e0146103ee578063715018a61461041c578063734d6db31461043057806373b2e80e14610445575f80fd5b80634f9359451461032157806352d1902d1461034e57806358334e291461036257806361d027b314610377575f80fd5b80634602d509116101da5780634602d5091461027c578063469619f2146102a75780634b319713146102c65780634dc41210146102db5780634f1ef2861461030e575f80fd5b80632496903c1461020b5780632eb4a7ab1461022c578063380d831b146102545780633ccfd60b14610268575b5f80fd5b348015610216575f80fd5b5061022a610225366004611ab4565b6106eb565b005b348015610237575f80fd5b5061024160055481565b6040519081526020015b60405180910390f35b34801561025f575f80fd5b5061022a610974565b348015610273575f80fd5b5061022a610a2d565b348015610287575f80fd5b50610241610296366004611b46565b60086020525f908152604090205481565b3480156102b2575f80fd5b5061022a6102c1366004611b5f565b610b5e565b3480156102d1575f80fd5b5061024160015481565b3480156102e6575f80fd5b506102417f000000000000000000000000000000000000000000000000000000000000000081565b61022a61031c366004611b8a565b610c67565b34801561032c575f80fd5b505f5461033e90610100900460ff1681565b604051901515815260200161024b565b348015610359575f80fd5b50610241610c86565b34801561036d575f80fd5b5061024160035481565b348015610382575f80fd5b506103aa7f000000000000000000000000000000000000000000000000000000000000000081565b6040516001600160a01b03909116815260200161024b565b3480156103cd575f80fd5b506103e16103dc366004611b46565b610ca1565b60405161024b9190611c46565b3480156103f9575f80fd5b5061033e610408366004611b46565b600b6020525f908152604090205460ff1681565b348015610427575f80fd5b5061022a610e21565b34801561043b575f80fd5b5061024160045481565b348015610450575f80fd5b5061033e61045f366004611b46565b60076020525f908152604090205460ff1681565b34801561047e575f80fd5b506102417f000000000000000000000000000000000000000000000000000000000000000081565b3480156104b1575f80fd5b5061022a6104c0366004611b5f565b610e32565b3480156104d0575f80fd5b5061022a610eab565b3480156104e4575f80fd5b50610241600a5481565b3480156104f9575f80fd5b506103aa7f000000000000000000000000000000000000000000000000000000000000000081565b34801561052c575f80fd5b506102416102bc81565b348015610541575f80fd5b507f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c199300546001600160a01b03166103aa565b34801561057d575f80fd5b5061022a610fbf565b348015610591575f80fd5b505f5461033e9060ff1681565b3480156105a9575f80fd5b506105ce604051806040016040528060058152602001640352e302e360dc1b81525081565b60405161024b9190611d11565b3480156105e6575f80fd5b50610241630ee6b28081565b3480156105fd575f80fd5b5061022a61060c366004611d43565b6110da565b34801561061c575f80fd5b506103aa7f000000000000000000000000000000000000000000000000000000000000000081565b34801561064f575f80fd5b5061024160095481565b348015610664575f80fd5b5061022a610673366004611b46565b611311565b348015610683575f80fd5b506102417f000000000000000000000000000000000000000000000000000000000000000081565b3480156106b6575f80fd5b506102416106c5366004611b46565b60066020525f908152604090205481565b3480156106e1575f80fd5b5061024160025481565b6106f361134b565b5f5460ff16158061070b57505f54610100900460ff16155b156107295760405163b4202dd560e01b815260040160405180910390fd5b60055461074957604051634fc5147960e11b815260040160405180910390fd5b6001600160a01b0381165f9081526007602052604090205460ff161561079257604051632058b6db60e01b81526001600160a01b03821660048201526024015b60405180910390fd5b604080516001600160a01b0383166020820152908101869052606081018590525f9060800160408051601f198184030181528282528051602091820120908301520160405160208183030381529060405280519060200120905061082c8484808060200260200160405190810160405280939291908181526020018383602002808284375f92019190915250506005549150849050611382565b61084f5783838260405163571e214960e11b815260040161078993929190611d63565b6001600160a01b0382165f908152600760205260409020805460ff1916600117905585156108c2576108ab6001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000168388611399565b8560045f8282546108bc9190611db3565b90915550505b8415610913576108fc6001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000168387611399565b8460035f82825461090d9190611db3565b90915550505b816001600160a01b03167fd8138f8a3f377c5259ca548e70e4c2de94f129f5a11036a15b69513cba2b426a8760405161094e91815260200190565b60405180910390a25061096d60015f80516020611e6483398151915255565b5050505050565b61097c6113fd565b5f5460ff16156109a157604051632252c26960e21b8152426004820152602401610789565b5f80546002547f000000000000000000000000000000000000000000000000000000000000000081101561010090810261ffff1990931692909217600117928390556040517f8233a98787b42c8a85877e43d0969d9758e213000c07b8e1db21707dd06d05d193610a2393900460ff1691909115158252602082015260400190565b60405180910390a1565b610a3561134b565b5f5460ff16610a59576040516336f9bbd760e11b8152426004820152602401610789565b5f54610100900460ff1615610a8157604051636bf4c8e960e11b815260040160405180910390fd5b335f9081526006602052604081205490819003610ab357604051636e34ee0b60e11b8152336004820152602401610789565b335f90815260066020526040812081905560018054839290610ad6908490611db3565b90915550610b1090506001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000163383611399565b60405181815233907f7084f5476618d8e60b11ef0d7d3f06914655adb8793e28ff7f018d4c76d505d59060200160405180910390a250610b5c60015f80516020611e6483398151915255565b565b610b6661134b565b7f0000000000000000000000000000000000000000000000000000000000000000421015610bcf5760405163457f873160e01b81524260048201527f00000000000000000000000000000000000000000000000000000000000000006024820152604401610789565b5f5460ff1615610bf457604051632252c26960e21b8152426004820152602401610789565b610bfd81611458565b335f81815260086020908152604091829020805490859055825181815291820185905292917f18332e48c9676c7696287f3a4e7bcd1cb6cad945dd233d5711ab89cf106b673c910160405180910390a250610c6460015f80516020611e6483398151915255565b50565b610c6f61148d565b610c7882611531565b610c828282611539565b5050565b5f610c8f6115f5565b505f80516020611e4483398151915290565b610d0b604051806101c001604052805f81526020015f81526020015f81526020015f81526020015f81526020015f81526020015f81526020015f151581526020015f151581526020015f151581526020015f81526020015f81526020015f81526020015f81525090565b50604080516101c0810182527f000000000000000000000000000000000000000000000000000000000000000081527f00000000000000000000000000000000000000000000000000000000000000006020808301919091527f0000000000000000000000000000000000000000000000000000000000000000828401526002546060830152600154608083015260035460a083015260045460c08301525f805460ff808216151560e08601526101009182900481161515918501919091526001600160a01b0390951680825260078352848220549095161515610120840152600954610140840152600a546101608401528481526006825283812054610180840152938452600890529120546101a082015290565b610e296113fd565b610b5c5f61163e565b610e3a6113fd565b5f5460ff161580610e5257505f54610100900460ff16155b15610e705760405163b4202dd560e01b815260040160405180910390fd5b60058190556040518181527f42cbc405e4dbf1b691e85b9a34b08ecfcf7a9ad9078bf4d645ccfa1fac11c10b9060200160405180910390a150565b7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a008054600160401b810460ff16159067ffffffffffffffff165f81158015610ef05750825b90505f8267ffffffffffffffff166001148015610f0c5750303b155b905081158015610f1a575080155b15610f385760405163f92ee8a960e01b815260040160405180910390fd5b845467ffffffffffffffff191660011785558315610f6257845460ff60401b1916600160401b1785555b610f6b336116ae565b610f736116bf565b831561096d57845460ff60401b19168555604051600181527fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d29060200160405180910390a15050505050565b610fc76113fd565b5f5460ff161580610fdf57505f54610100900460ff16155b15610ffd5760405163b4202dd560e01b815260040160405180910390fd5b6040516370a0823160e01b8152306004820152610b5c907f0000000000000000000000000000000000000000000000000000000000000000906001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016906370a0823190602401602060405180830381865afa158015611085573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906110a99190611dc6565b6001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000169190611399565b6110e261134b565b5f5460ff161561110757604051632252c26960e21b8152426004820152602401610789565b7f00000000000000000000000000000000000000000000000000000000000000004210156111705760405163457f873160e01b81524260048201527f00000000000000000000000000000000000000000000000000000000000000006024820152604401610789565b630ee6b28082101561119857604051630266752760e51b815260048101839052602401610789565b6111a181611458565b7f0000000000000000000000000000000000000000000000000000000000000000421015611230576102bc600a54106111ed5760405163b64bf0e160e01b815260040160405180910390fd5b335f908152600b602052604090205460ff1661123057335f908152600b60205260408120805460ff19166001179055600a80549161122a83611ddd565b91905055505b335f908152600660205260408120805484929061124e908490611db3565b925050819055508160025f8282546112669190611db3565b909155505060098054905f61127a83611ddd565b9091555050335f8181526008602052604090208290556112c6907f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03169030856116c7565b60405182815233907f2da466a7b24304f47e87fa2e1e5a81b9831ce54fec19055ce277ca2f39ba42c49060200160405180910390a2610c8260015f80516020611e6483398151915255565b6113196113fd565b6001600160a01b03811661134257604051631e4fbdf760e01b81525f6004820152602401610789565b610c648161163e565b5f80516020611e6483398151915280546001190161137c57604051633ee5aeb560e01b815260040160405180910390fd5b60029055565b5f8261138e8584611706565b1490505b9392505050565b6040516001600160a01b038381166024830152604482018390526113f891859182169063a9059cbb906064015b604051602081830303815290604052915060e01b6020820180516001600160e01b03838183161783525050505061174a565b505050565b3361142f7f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c199300546001600160a01b031690565b6001600160a01b031614610b5c5760405163118cdaa760e01b8152336004820152602401610789565b6298968081108061146c57506301c9c38081115b15610c64576040516319bfbdbd60e11b815260048101829052602401610789565b306001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016148061151357507f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03166115075f80516020611e44833981519152546001600160a01b031690565b6001600160a01b031614155b15610b5c5760405163703e46dd60e11b815260040160405180910390fd5b610c646113fd565b816001600160a01b03166352d1902d6040518163ffffffff1660e01b8152600401602060405180830381865afa925050508015611593575060408051601f3d908101601f1916820190925261159091810190611dc6565b60015b6115bb57604051634c9c8ce360e01b81526001600160a01b0383166004820152602401610789565b5f80516020611e4483398151915281146115eb57604051632a87526960e21b815260048101829052602401610789565b6113f883836117ab565b306001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001614610b5c5760405163703e46dd60e11b815260040160405180910390fd5b7f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c19930080546001600160a01b031981166001600160a01b03848116918217845560405192169182907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0905f90a3505050565b6116b6611800565b610c6481611849565b610b5c611800565b6040516001600160a01b0384811660248301528381166044830152606482018390526117009186918216906323b872dd906084016113c6565b50505050565b5f81815b8451811015611740576117368286838151811061172957611729611df5565b6020026020010151611851565b915060010161170a565b5090505b92915050565b5f61175e6001600160a01b0384168361187a565b905080515f141580156117825750808060200190518101906117809190611e09565b155b156113f857604051635274afe760e01b81526001600160a01b0384166004820152602401610789565b6117b482611887565b6040516001600160a01b038316907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b905f90a28051156117f8576113f882826118ea565b610c8261195c565b7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a0054600160401b900460ff16610b5c57604051631afcd79f60e31b815260040160405180910390fd5b611319611800565b5f81831061186b575f828152602084905260409020611392565b505f9182526020526040902090565b606061139283835f61197b565b806001600160a01b03163b5f036118bc57604051634c9c8ce360e01b81526001600160a01b0382166004820152602401610789565b5f80516020611e4483398151915280546001600160a01b0319166001600160a01b0392909216919091179055565b60605f80846001600160a01b0316846040516119069190611e28565b5f60405180830381855af49150503d805f811461193e576040519150601f19603f3d011682016040523d82523d5f602084013e611943565b606091505b5091509150611953858383611a14565b95945050505050565b3415610b5c5760405163b398979f60e01b815260040160405180910390fd5b6060814710156119a05760405163cd78605960e01b8152306004820152602401610789565b5f80856001600160a01b031684866040516119bb9190611e28565b5f6040518083038185875af1925050503d805f81146119f5576040519150601f19603f3d011682016040523d82523d5f602084013e6119fa565b606091505b5091509150611a0a868383611a14565b9695505050505050565b606082611a2957611a2482611a70565b611392565b8151158015611a4057506001600160a01b0384163b155b15611a6957604051639996b31560e01b81526001600160a01b0385166004820152602401610789565b5080611392565b805115611a805780518082602001fd5b604051630a12f52160e11b815260040160405180910390fd5b80356001600160a01b0381168114611aaf575f80fd5b919050565b5f805f805f60808688031215611ac8575f80fd5b8535945060208601359350604086013567ffffffffffffffff80821115611aed575f80fd5b818801915088601f830112611b00575f80fd5b813581811115611b0e575f80fd5b8960208260051b8501011115611b22575f80fd5b602083019550809450505050611b3a60608701611a99565b90509295509295909350565b5f60208284031215611b56575f80fd5b61139282611a99565b5f60208284031215611b6f575f80fd5b5035919050565b634e487b7160e01b5f52604160045260245ffd5b5f8060408385031215611b9b575f80fd5b611ba483611a99565b9150602083013567ffffffffffffffff80821115611bc0575f80fd5b818501915085601f830112611bd3575f80fd5b813581811115611be557611be5611b76565b604051601f8201601f19908116603f01168101908382118183101715611c0d57611c0d611b76565b81604052828152886020848701011115611c25575f80fd5b826020860160208301375f6020848301015280955050505050509250929050565b5f6101c082019050825182526020830151602083015260408301516040830152606083015160608301526080830151608083015260a083015160a083015260c083015160c083015260e0830151611ca160e084018215159052565b5061010083810151151590830152610120808401511515908301526101408084015190830152610160808401519083015261018080840151908301526101a092830151929091019190915290565b5f5b83811015611d09578181015183820152602001611cf1565b50505f910152565b602081525f8251806020840152611d2f816040850160208701611cef565b601f01601f19169190910160400192915050565b5f8060408385031215611d54575f80fd5b50508035926020909101359150565b604080825281018390525f6001600160fb1b03841115611d81575f80fd5b8360051b808660608501376020830193909352500160600192915050565b634e487b7160e01b5f52601160045260245ffd5b8082018082111561174457611744611d9f565b5f60208284031215611dd6575f80fd5b5051919050565b5f60018201611dee57611dee611d9f565b5060010190565b634e487b7160e01b5f52603260045260245ffd5b5f60208284031215611e19575f80fd5b81518015158114611392575f80fd5b5f8251611e39818460208701611cef565b919091019291505056fe360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f00a26469706673582212208dfdba2e9477dfb8915251f3d74ef98c8ef7ce5548326403dda523156367f64764736f6c63430008180033000000000000000000000000010700808d59d2bb92257fcafacfe8e5bff7ab87000000000000000000000000793500709506652fcc61f0d2d0fda605638d429300000000000000000000000005dc0010c9902ecf6cbc921c6a4bd971c69e5a2e0000000000000000000000000000000000000000000000000000000067ab90200000000000000000000000000000000000000000000000000000000067b4caa00000000000000000000000000000000000000000000000000000003a35294400", + "nonce": "0x4bc8d", + "chainId": "0x1ecf" + }, + "additionalContracts": [], + "isFixedGasLimit": false + }, + { + "hash": "0xd5ea37bc7d2fa77fa1857835cbb8a8546793308c384103ea10800be1accd454c", + "transactionType": "CREATE2", + "contractName": "UUPSProxy", + "contractAddress": "0x5a1e00884e35bf2dc39af51712d08bef24b1817f", + "function": null, + "arguments": [ + "0xe9aB275D7E9859bbeF11A79b7c1854a030D0c171", + "0x" + ], + "transaction": { + "from": "0x660ad4b5a74130a4796b4d54bc6750ae93c86e6c", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "value": "0x0", + "input": "0x6b5d0bf482f03bfc69060956ae59c41746097a4c9eeee42aef2212b60bf28a9c608060405234801561000f575f80fd5b506040516104d43803806104d483398101604081905261002e916102e2565b818161003b82825f610044565b505050506103f7565b61004d8361006f565b5f825111806100595750805b1561006a5761006883836100ae565b505b505050565b610078816100da565b6040516001600160a01b038216907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b905f90a250565b60606100d383836040518060600160405280602781526020016104ad6027913961018d565b9392505050565b6001600160a01b0381163b61014c5760405162461bcd60e51b815260206004820152602d60248201527f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60448201526c1bdd08184818dbdb9d1c9858dd609a1b60648201526084015b60405180910390fd5b7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc80546001600160a01b0319166001600160a01b0392909216919091179055565b60605f80856001600160a01b0316856040516101a991906103aa565b5f60405180830381855af49150503d805f81146101e1576040519150601f19603f3d011682016040523d82523d5f602084013e6101e6565b606091505b5090925090506101f886838387610202565b9695505050505050565b606083156102705782515f03610269576001600160a01b0385163b6102695760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e74726163740000006044820152606401610143565b508161027a565b61027a8383610282565b949350505050565b8151156102925781518083602001fd5b8060405162461bcd60e51b815260040161014391906103c5565b634e487b7160e01b5f52604160045260245ffd5b5f5b838110156102da5781810151838201526020016102c2565b50505f910152565b5f80604083850312156102f3575f80fd5b82516001600160a01b0381168114610309575f80fd5b60208401519092506001600160401b0380821115610325575f80fd5b818501915085601f830112610338575f80fd5b81518181111561034a5761034a6102ac565b604051601f8201601f19908116603f01168101908382118183101715610372576103726102ac565b8160405282815288602084870101111561038a575f80fd5b61039b8360208301602088016102c0565b80955050505050509250929050565b5f82516103bb8184602087016102c0565b9190910192915050565b602081525f82518060208401526103e38160408501602087016102c0565b601f01601f19169190910160400192915050565b60aa806104035f395ff3fe608060405236601057600e6013565b005b600e5b601f601b6021565b6057565b565b5f60527f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc546001600160a01b031690565b905090565b365f80375f80365f845af43d5f803e8080156070573d5ff35b3d5ffdfea264697066735822122039fd63d084b1b3efb382c0e80fee4b3d1b88fe0f3a68a497d4090acbcdbdc35864736f6c63430008180033416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564000000000000000000000000e9ab275d7e9859bbef11a79b7c1854a030d0c17100000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "nonce": "0x4bc8e", + "chainId": "0x1ecf" + }, + "additionalContracts": [], + "isFixedGasLimit": false + }, + { + "hash": "0xe6125be646cef90350f3c376b3aa689105017ec160c7b6ff5bbc1f41ad11ef54", + "transactionType": "CALL", + "contractName": null, + "contractAddress": "0x2843c269d2a64ecfa63548e8b3fc0fd23b7f70cb", + "function": "handleOps((address,uint256,bytes,bytes,uint256,uint256,uint256,uint256,uint256,bytes,bytes)[],address)", + "arguments": [ + "[(0x2e2B1c42E38f5af81771e65D87729E57ABD1337a, 4433, 0x, 0xb61d27f60000000000000000000000005a2b641b84b0230c8e75f55d5afd27f4dbd59d5b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000084db9e99080000000000000000000000003e9727470c66b1e77034590926cde0242b5a3dcc000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000010000000000000000000000005a1e00884e35bf2dc39af51712d08bef24b1817f00000000000000000000000000000000000000000000000000000000, 4000000, 210000, 21000, 1, 1000000000, 0x0000000000000000000000000000000000000000, 0x390d43b95a1e78bb154cee777f78cdcd4236c4b3b07d6ce13f45037b819f5f8e4ef23a9ac2e4b0d727345561a48d294c61ea1a055437df47d4e14c80a3f108751b4dafe04e525669c20f12056b9e11a36666de86b464c4a95722ae6b09b459caaf2db9363477c007fbf6bef1106b2f74871b3150d7bc8405f84f91050335eb230d1b)]", + "0x660ad4B5A74130a4796B4d54BC6750Ae93C86e6c" + ], + "transaction": { + "from": "0x660ad4b5a74130a4796b4d54bc6750ae93c86e6c", + "to": "0x2843c269d2a64ecfa63548e8b3fc0fd23b7f70cb", + "value": "0x0", + "input": "0x1fad948c0000000000000000000000000000000000000000000000000000000000000040000000000000000000000000660ad4b5a74130a4796b4d54bc6750ae93c86e6c000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000002e2b1c42e38f5af81771e65d87729e57abd1337a00000000000000000000000000000000000000000000000000000000000011510000000000000000000000000000000000000000000000000000000000000160000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000003d0900000000000000000000000000000000000000000000000000000000000003345000000000000000000000000000000000000000000000000000000000000052080000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000003b9aca0000000000000000000000000000000000000000000000000000000000000002e0000000000000000000000000000000000000000000000000000000000000032000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000124b61d27f60000000000000000000000005a2b641b84b0230c8e75f55d5afd27f4dbd59d5b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000084db9e99080000000000000000000000003e9727470c66b1e77034590926cde0242b5a3dcc000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000010000000000000000000000005a1e00884e35bf2dc39af51712d08bef24b1817f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000082390d43b95a1e78bb154cee777f78cdcd4236c4b3b07d6ce13f45037b819f5f8e4ef23a9ac2e4b0d727345561a48d294c61ea1a055437df47d4e14c80a3f108751b4dafe04e525669c20f12056b9e11a36666de86b464c4a95722ae6b09b459caaf2db9363477c007fbf6bef1106b2f74871b3150d7bc8405f84f91050335eb230d1b000000000000000000000000000000000000000000000000000000000000", + "nonce": "0x4bc8f", + "chainId": "0x1ecf" + }, + "additionalContracts": [], + "isFixedGasLimit": false + }, + { + "hash": "0xc9040ceaa02cb5c0cc139742d836d534d954628fe98eb9a930738c94d7646486", + "transactionType": "CALL", + "contractName": null, + "contractAddress": "0x2843c269d2a64ecfa63548e8b3fc0fd23b7f70cb", + "function": "handleOps((address,uint256,bytes,bytes,uint256,uint256,uint256,uint256,uint256,bytes,bytes)[],address)", + "arguments": [ + "[(0x2e2B1c42E38f5af81771e65D87729E57ABD1337a, 4434, 0x, 0xb61d27f60000000000000000000000005a1e00884e35bf2dc39af51712d08bef24b1817f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000048129fc1c00000000000000000000000000000000000000000000000000000000, 4000000, 210000, 21000, 1, 1000000000, 0x0000000000000000000000000000000000000000, 0xe22c27fb22621ad86cbdc98b07b2977fd03de9855c2051ffbca24a9771efd13342bdb6864c9c47bb5a478875ce28793a5cc7401ce27c6272e2f68e19a2ba6eb21c)]", + "0x660ad4B5A74130a4796B4d54BC6750Ae93C86e6c" + ], + "transaction": { + "from": "0x660ad4b5a74130a4796b4d54bc6750ae93c86e6c", + "to": "0x2843c269d2a64ecfa63548e8b3fc0fd23b7f70cb", + "value": "0x0", + "input": "0x1fad948c0000000000000000000000000000000000000000000000000000000000000040000000000000000000000000660ad4b5a74130a4796b4d54bc6750ae93c86e6c000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000002e2b1c42e38f5af81771e65d87729e57abd1337a00000000000000000000000000000000000000000000000000000000000011520000000000000000000000000000000000000000000000000000000000000160000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000003d0900000000000000000000000000000000000000000000000000000000000003345000000000000000000000000000000000000000000000000000000000000052080000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000003b9aca00000000000000000000000000000000000000000000000000000000000000026000000000000000000000000000000000000000000000000000000000000002a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a4b61d27f60000000000000000000000005a1e00884e35bf2dc39af51712d08bef24b1817f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000048129fc1c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000041e22c27fb22621ad86cbdc98b07b2977fd03de9855c2051ffbca24a9771efd13342bdb6864c9c47bb5a478875ce28793a5cc7401ce27c6272e2f68e19a2ba6eb21c00000000000000000000000000000000000000000000000000000000000000", + "nonce": "0x4bc90", + "chainId": "0x1ecf" + }, + "additionalContracts": [], + "isFixedGasLimit": false + } + ], + "receipts": [ + { + "status": "0x1", + "cumulativeGasUsed": "0x2155b7", + "logs": [ + { + "address": "0xe9ab275d7e9859bbef11a79b7c1854a030d0c171", + "topics": [ + "0xc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d2" + ], + "data": "0x000000000000000000000000000000000000000000000000ffffffffffffffff", + "blockHash": "0xba15178f1757f24c0360486a2b373b2240bfb3ccb12704a5420d8b124c027f5b", + "blockNumber": "0xb1e66", + "transactionHash": "0x55f9c9c447311fbe329d2f01414445cdfff118ffe049dd1127a20dedbfaa1f3e", + "transactionIndex": "0x1", + "logIndex": "0x0", + "removed": false + } + ], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000100000000000001000000000000000000000000100000000000000000000000000000", + "type": "0x2", + "transactionHash": "0x55f9c9c447311fbe329d2f01414445cdfff118ffe049dd1127a20dedbfaa1f3e", + "transactionIndex": "0x1", + "blockHash": "0xba15178f1757f24c0360486a2b373b2240bfb3ccb12704a5420d8b124c027f5b", + "blockNumber": "0xb1e66", + "gasUsed": "0x2155b7", + "effectiveGasPrice": "0x5f5e100", + "from": "0x660ad4b5a74130a4796b4d54bc6750ae93c86e6c", + "to": null, + "contractAddress": "0xe9ab275d7e9859bbef11a79b7c1854a030d0c171", + "gasUsedForL1": "0x6243d", + "l1BlockNumber": "0x14caf89" + }, + { + "status": "0x1", + "cumulativeGasUsed": "0x39ca8", + "logs": [ + { + "address": "0x5a1e00884e35bf2dc39af51712d08bef24b1817f", + "topics": [ + "0xbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b", + "0x000000000000000000000000e9ab275d7e9859bbef11a79b7c1854a030d0c171" + ], + "data": "0x", + "blockHash": "0x538925cecf0a66eeb5b1dad36a3d52c973ff8d288c140c98d19e382b27694674", + "blockNumber": "0xb1e67", + "transactionHash": "0xd5ea37bc7d2fa77fa1857835cbb8a8546793308c384103ea10800be1accd454c", + "transactionIndex": "0x1", + "logIndex": "0x0", + "removed": false + } + ], + "logsBloom": "0x00000000000000000000000000000000400000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000020000000000000000000000010000000000000000000000000000000000000000000000020000000000000000000200000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000", + "type": "0x2", + "transactionHash": "0xd5ea37bc7d2fa77fa1857835cbb8a8546793308c384103ea10800be1accd454c", + "transactionIndex": "0x1", + "blockHash": "0x538925cecf0a66eeb5b1dad36a3d52c973ff8d288c140c98d19e382b27694674", + "blockNumber": "0xb1e67", + "gasUsed": "0x39ca8", + "effectiveGasPrice": "0x5f5e100", + "from": "0x660ad4b5a74130a4796b4d54bc6750ae93c86e6c", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "contractAddress": null, + "gasUsedForL1": "0x18cac", + "l1BlockNumber": "0x14caf89" + }, + { + "status": "0x1", + "cumulativeGasUsed": "0x42501", + "logs": [ + { + "address": "0x2843c269d2a64ecfa63548e8b3fc0fd23b7f70cb", + "topics": [ + "0xbb47ee3e183a558b1a2ff0874b079f3fc5478b7454eacf2bfc5af2ff5878f972" + ], + "data": "0x", + "blockHash": "0xd402b1e97a4a2193007a52324784a69ab63952a7b352fe0490e7339e8890192c", + "blockNumber": "0xb1e68", + "transactionHash": "0xe6125be646cef90350f3c376b3aa689105017ec160c7b6ff5bbc1f41ad11ef54", + "transactionIndex": "0x1", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x5a2b641b84b0230c8e75f55d5afd27f4dbd59d5b", + "topics": [ + "0x5ed922fe11a834df4ad0556e8f265179bbe61f057f4808eab4c3b6542b621260", + "0x0000000000000000000000003e9727470c66b1e77034590926cde0242b5a3dcc" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000010000000000000000000000005a1e00884e35bf2dc39af51712d08bef24b1817f", + "blockHash": "0xd402b1e97a4a2193007a52324784a69ab63952a7b352fe0490e7339e8890192c", + "blockNumber": "0xb1e68", + "transactionHash": "0xe6125be646cef90350f3c376b3aa689105017ec160c7b6ff5bbc1f41ad11ef54", + "transactionIndex": "0x1", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x2843c269d2a64ecfa63548e8b3fc0fd23b7f70cb", + "topics": [ + "0x49628fd1471006c1482da88028e9ce4dbb080b815c9b0344d39e5a8e6ec1419f", + "0x04aeca23631dcf3b671dba54684da1d455df7229aa9a8f4d99598564da030af8", + "0x0000000000000000000000002e2b1c42e38f5af81771e65d87729e57abd1337a", + "0x0000000000000000000000000000000000000000000000000000000000000000" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000011510000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000008f82c000000000000000000000000000000000000000000000000000000000008f82c", + "blockHash": "0xd402b1e97a4a2193007a52324784a69ab63952a7b352fe0490e7339e8890192c", + "blockNumber": "0xb1e68", + "transactionHash": "0xe6125be646cef90350f3c376b3aa689105017ec160c7b6ff5bbc1f41ad11ef54", + "transactionIndex": "0x1", + "logIndex": "0x2", + "removed": false + } + ], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000008000000000000000000010000000000000002000000000000020000000000000000000000000000000000000000000000000000000000000000000008000800000000020000000000000000000800002000000000000000000000000000000000000000000000000002000000800000000000000000800000080000000000000400000000000000400000000000000000000000000000000002000000000000000000000000100001000000000000000000010000000800000000000020000000000000010000000200000000000000000000000000001000000084000000", + "type": "0x2", + "transactionHash": "0xe6125be646cef90350f3c376b3aa689105017ec160c7b6ff5bbc1f41ad11ef54", + "transactionIndex": "0x1", + "blockHash": "0xd402b1e97a4a2193007a52324784a69ab63952a7b352fe0490e7339e8890192c", + "blockNumber": "0xb1e68", + "gasUsed": "0x42501", + "effectiveGasPrice": "0x5f5e100", + "from": "0x660ad4b5a74130a4796b4d54bc6750ae93c86e6c", + "to": "0x2843c269d2a64ecfa63548e8b3fc0fd23b7f70cb", + "contractAddress": null, + "gasUsedForL1": "0xbc38", + "l1BlockNumber": "0x14caf89" + }, + { + "status": "0x1", + "cumulativeGasUsed": "0x391f6", + "logs": [ + { + "address": "0x2843c269d2a64ecfa63548e8b3fc0fd23b7f70cb", + "topics": [ + "0xbb47ee3e183a558b1a2ff0874b079f3fc5478b7454eacf2bfc5af2ff5878f972" + ], + "data": "0x", + "blockHash": "0x899762a7bdc297c47cbd27985b5b8eca4c6f34cb506e6bd4463b08832fdb54e2", + "blockNumber": "0xb1e69", + "transactionHash": "0xc9040ceaa02cb5c0cc139742d836d534d954628fe98eb9a930738c94d7646486", + "transactionIndex": "0x1", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x5a1e00884e35bf2dc39af51712d08bef24b1817f", + "topics": [ + "0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000002e2b1c42e38f5af81771e65d87729e57abd1337a" + ], + "data": "0x", + "blockHash": "0x899762a7bdc297c47cbd27985b5b8eca4c6f34cb506e6bd4463b08832fdb54e2", + "blockNumber": "0xb1e69", + "transactionHash": "0xc9040ceaa02cb5c0cc139742d836d534d954628fe98eb9a930738c94d7646486", + "transactionIndex": "0x1", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x5a1e00884e35bf2dc39af51712d08bef24b1817f", + "topics": [ + "0xc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d2" + ], + "data": "0x0000000000000000000000000000000000000000000000000000000000000001", + "blockHash": "0x899762a7bdc297c47cbd27985b5b8eca4c6f34cb506e6bd4463b08832fdb54e2", + "blockNumber": "0xb1e69", + "transactionHash": "0xc9040ceaa02cb5c0cc139742d836d534d954628fe98eb9a930738c94d7646486", + "transactionIndex": "0x1", + "logIndex": "0x2", + "removed": false + }, + { + "address": "0x2843c269d2a64ecfa63548e8b3fc0fd23b7f70cb", + "topics": [ + "0x49628fd1471006c1482da88028e9ce4dbb080b815c9b0344d39e5a8e6ec1419f", + "0xc50415f27c552212c0a8a073aad2a632c28483f2a43c9a6345f581aeb70fbf43", + "0x0000000000000000000000002e2b1c42e38f5af81771e65d87729e57abd1337a", + "0x0000000000000000000000000000000000000000000000000000000000000000" + ], + "data": "0x0000000000000000000000000000000000000000000000000000000000001152000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000898ad00000000000000000000000000000000000000000000000000000000000898ad", + "blockHash": "0x899762a7bdc297c47cbd27985b5b8eca4c6f34cb506e6bd4463b08832fdb54e2", + "blockNumber": "0xb1e69", + "transactionHash": "0xc9040ceaa02cb5c0cc139742d836d534d954628fe98eb9a930738c94d7646486", + "transactionIndex": "0x1", + "logIndex": "0x3", + "removed": false + } + ], + "logsBloom": "0x00000000000000000000000000000000000000000000000000800000000000000408000000000000000000010000000000000000000000000000020000000000000000000000000000000000000000000001000000000000000000000000000800000000020000000000000000000800002000000000000000000000000000400000000000000000000800000000800000000000000080000000000000000020000400000000000010400010000000000000000000000000000002000000000000000000000000100001000000000000000000000004010000000000000020000000000000010000000000000000000000000000000000000000000000000000", + "type": "0x2", + "transactionHash": "0xc9040ceaa02cb5c0cc139742d836d534d954628fe98eb9a930738c94d7646486", + "transactionIndex": "0x1", + "blockHash": "0x899762a7bdc297c47cbd27985b5b8eca4c6f34cb506e6bd4463b08832fdb54e2", + "blockNumber": "0xb1e69", + "gasUsed": "0x391f6", + "effectiveGasPrice": "0x5f5e100", + "from": "0x660ad4b5a74130a4796b4d54bc6750ae93c86e6c", + "to": "0x2843c269d2a64ecfa63548e8b3fc0fd23b7f70cb", + "contractAddress": null, + "gasUsedForL1": "0x9508", + "l1BlockNumber": "0x14caf8a" + } + ], + "libraries": [], + "pending": [], + "returns": {}, + "timestamp": 1739030918, + "chain": 7887, + "commit": "d16c447" +} \ No newline at end of file diff --git a/broadcast/155-deploy-sale.s.sol/7887/run-latest.json b/broadcast/155-deploy-sale.s.sol/7887/run-latest.json new file mode 100644 index 00000000..1f85eff9 --- /dev/null +++ b/broadcast/155-deploy-sale.s.sol/7887/run-latest.json @@ -0,0 +1,302 @@ +{ + "transactions": [ + { + "hash": "0x55f9c9c447311fbe329d2f01414445cdfff118ffe049dd1127a20dedbfaa1f3e", + "transactionType": "CREATE", + "contractName": "SealedBidTokenSale", + "contractAddress": "0xe9ab275d7e9859bbef11a79b7c1854a030d0c171", + "function": null, + "arguments": [ + "0x010700808D59d2bb92257fCafACfe8e5bFF7aB87", + "0x793500709506652Fcc61F0d2D0fDa605638D4293", + "0x05DC0010C9902EcF6CBc921c6A4bd971c69E5A2E", + "1739296800", + "1739901600", + "250000000000" + ], + "transaction": { + "from": "0x660ad4b5a74130a4796b4d54bc6750ae93c86e6c", + "value": "0x0", + "input": "0x6101606040523060805234801562000015575f80fd5b50604051620021bd380380620021bd8339810160408190526200003891620001d2565b6200004262000102565b6001600160a01b0386166200007a576040516307094c3360e11b81526001600160a01b03871660048201526024015b60405180910390fd5b6001600160a01b038516620000ae576040516334d5d27d60e21b81526001600160a01b038616600482015260240162000071565b818310620000cf57604051631800812760e11b815260040160405180910390fd5b6001600160a01b0395861660a05293851660e0529190931660c05261010092909252610120919091526101405262000234565b7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00805468010000000000000000900460ff1615620001535760405163f92ee8a960e01b815260040160405180910390fd5b80546001600160401b0390811614620001b35780546001600160401b0319166001600160401b0390811782556040519081527fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d29060200160405180910390a15b50565b80516001600160a01b0381168114620001cd575f80fd5b919050565b5f805f805f8060c08789031215620001e8575f80fd5b620001f387620001b6565b95506200020360208801620001b6565b94506200021360408801620001b6565b9350606087015192506080870151915060a087015190509295509295509295565b60805160a05160c05160e051610100516101205161014051611eb9620003045f395f8181610689015281816109a90152610d6601525f818161048401528181610d3c01526111a301525f81816102ec01528181610b6801528181610ba301528181610d1901528181611109015261114401525f8181610388015261101601525f81816104ff015281816108d501528181610ae901528181611040015281816110b3015261129601525f8181610622015261088401525f8181611498015281816114c101526116000152611eb95ff3fe608060405260043610610207575f3560e01c806378e9792511610113578063ad3cb1cc1161009d578063ecfd89281161006d578063ecfd892814610644578063f2fde38b14610659578063f381f2a514610678578063fc7e286d146106ab578063ff50abdc146106d6575f80fd5b8063ad3cb1cc1461059e578063e1e158a5146105db578063e2bbb158146105f2578063e985e36714610611575f80fd5b806389a30271116100e357806389a30271146104ee5780638bd29968146105215780638da5cb5b146105365780639038e693146105725780639b8906ae14610586575f80fd5b806378e97925146104735780637cb64759146104a65780638129fc1c146104c557806388de2330146104d9575f80fd5b80634f93594511610194578063665828021161016457806366582802146103c25780636bb8a6e0146103ee578063715018a61461041c578063734d6db31461043057806373b2e80e14610445575f80fd5b80634f9359451461032157806352d1902d1461034e57806358334e291461036257806361d027b314610377575f80fd5b80634602d509116101da5780634602d5091461027c578063469619f2146102a75780634b319713146102c65780634dc41210146102db5780634f1ef2861461030e575f80fd5b80632496903c1461020b5780632eb4a7ab1461022c578063380d831b146102545780633ccfd60b14610268575b5f80fd5b348015610216575f80fd5b5061022a610225366004611ab4565b6106eb565b005b348015610237575f80fd5b5061024160055481565b6040519081526020015b60405180910390f35b34801561025f575f80fd5b5061022a610974565b348015610273575f80fd5b5061022a610a2d565b348015610287575f80fd5b50610241610296366004611b46565b60086020525f908152604090205481565b3480156102b2575f80fd5b5061022a6102c1366004611b5f565b610b5e565b3480156102d1575f80fd5b5061024160015481565b3480156102e6575f80fd5b506102417f000000000000000000000000000000000000000000000000000000000000000081565b61022a61031c366004611b8a565b610c67565b34801561032c575f80fd5b505f5461033e90610100900460ff1681565b604051901515815260200161024b565b348015610359575f80fd5b50610241610c86565b34801561036d575f80fd5b5061024160035481565b348015610382575f80fd5b506103aa7f000000000000000000000000000000000000000000000000000000000000000081565b6040516001600160a01b03909116815260200161024b565b3480156103cd575f80fd5b506103e16103dc366004611b46565b610ca1565b60405161024b9190611c46565b3480156103f9575f80fd5b5061033e610408366004611b46565b600b6020525f908152604090205460ff1681565b348015610427575f80fd5b5061022a610e21565b34801561043b575f80fd5b5061024160045481565b348015610450575f80fd5b5061033e61045f366004611b46565b60076020525f908152604090205460ff1681565b34801561047e575f80fd5b506102417f000000000000000000000000000000000000000000000000000000000000000081565b3480156104b1575f80fd5b5061022a6104c0366004611b5f565b610e32565b3480156104d0575f80fd5b5061022a610eab565b3480156104e4575f80fd5b50610241600a5481565b3480156104f9575f80fd5b506103aa7f000000000000000000000000000000000000000000000000000000000000000081565b34801561052c575f80fd5b506102416102bc81565b348015610541575f80fd5b507f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c199300546001600160a01b03166103aa565b34801561057d575f80fd5b5061022a610fbf565b348015610591575f80fd5b505f5461033e9060ff1681565b3480156105a9575f80fd5b506105ce604051806040016040528060058152602001640352e302e360dc1b81525081565b60405161024b9190611d11565b3480156105e6575f80fd5b50610241630ee6b28081565b3480156105fd575f80fd5b5061022a61060c366004611d43565b6110da565b34801561061c575f80fd5b506103aa7f000000000000000000000000000000000000000000000000000000000000000081565b34801561064f575f80fd5b5061024160095481565b348015610664575f80fd5b5061022a610673366004611b46565b611311565b348015610683575f80fd5b506102417f000000000000000000000000000000000000000000000000000000000000000081565b3480156106b6575f80fd5b506102416106c5366004611b46565b60066020525f908152604090205481565b3480156106e1575f80fd5b5061024160025481565b6106f361134b565b5f5460ff16158061070b57505f54610100900460ff16155b156107295760405163b4202dd560e01b815260040160405180910390fd5b60055461074957604051634fc5147960e11b815260040160405180910390fd5b6001600160a01b0381165f9081526007602052604090205460ff161561079257604051632058b6db60e01b81526001600160a01b03821660048201526024015b60405180910390fd5b604080516001600160a01b0383166020820152908101869052606081018590525f9060800160408051601f198184030181528282528051602091820120908301520160405160208183030381529060405280519060200120905061082c8484808060200260200160405190810160405280939291908181526020018383602002808284375f92019190915250506005549150849050611382565b61084f5783838260405163571e214960e11b815260040161078993929190611d63565b6001600160a01b0382165f908152600760205260409020805460ff1916600117905585156108c2576108ab6001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000168388611399565b8560045f8282546108bc9190611db3565b90915550505b8415610913576108fc6001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000168387611399565b8460035f82825461090d9190611db3565b90915550505b816001600160a01b03167fd8138f8a3f377c5259ca548e70e4c2de94f129f5a11036a15b69513cba2b426a8760405161094e91815260200190565b60405180910390a25061096d60015f80516020611e6483398151915255565b5050505050565b61097c6113fd565b5f5460ff16156109a157604051632252c26960e21b8152426004820152602401610789565b5f80546002547f000000000000000000000000000000000000000000000000000000000000000081101561010090810261ffff1990931692909217600117928390556040517f8233a98787b42c8a85877e43d0969d9758e213000c07b8e1db21707dd06d05d193610a2393900460ff1691909115158252602082015260400190565b60405180910390a1565b610a3561134b565b5f5460ff16610a59576040516336f9bbd760e11b8152426004820152602401610789565b5f54610100900460ff1615610a8157604051636bf4c8e960e11b815260040160405180910390fd5b335f9081526006602052604081205490819003610ab357604051636e34ee0b60e11b8152336004820152602401610789565b335f90815260066020526040812081905560018054839290610ad6908490611db3565b90915550610b1090506001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000163383611399565b60405181815233907f7084f5476618d8e60b11ef0d7d3f06914655adb8793e28ff7f018d4c76d505d59060200160405180910390a250610b5c60015f80516020611e6483398151915255565b565b610b6661134b565b7f0000000000000000000000000000000000000000000000000000000000000000421015610bcf5760405163457f873160e01b81524260048201527f00000000000000000000000000000000000000000000000000000000000000006024820152604401610789565b5f5460ff1615610bf457604051632252c26960e21b8152426004820152602401610789565b610bfd81611458565b335f81815260086020908152604091829020805490859055825181815291820185905292917f18332e48c9676c7696287f3a4e7bcd1cb6cad945dd233d5711ab89cf106b673c910160405180910390a250610c6460015f80516020611e6483398151915255565b50565b610c6f61148d565b610c7882611531565b610c828282611539565b5050565b5f610c8f6115f5565b505f80516020611e4483398151915290565b610d0b604051806101c001604052805f81526020015f81526020015f81526020015f81526020015f81526020015f81526020015f81526020015f151581526020015f151581526020015f151581526020015f81526020015f81526020015f81526020015f81525090565b50604080516101c0810182527f000000000000000000000000000000000000000000000000000000000000000081527f00000000000000000000000000000000000000000000000000000000000000006020808301919091527f0000000000000000000000000000000000000000000000000000000000000000828401526002546060830152600154608083015260035460a083015260045460c08301525f805460ff808216151560e08601526101009182900481161515918501919091526001600160a01b0390951680825260078352848220549095161515610120840152600954610140840152600a546101608401528481526006825283812054610180840152938452600890529120546101a082015290565b610e296113fd565b610b5c5f61163e565b610e3a6113fd565b5f5460ff161580610e5257505f54610100900460ff16155b15610e705760405163b4202dd560e01b815260040160405180910390fd5b60058190556040518181527f42cbc405e4dbf1b691e85b9a34b08ecfcf7a9ad9078bf4d645ccfa1fac11c10b9060200160405180910390a150565b7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a008054600160401b810460ff16159067ffffffffffffffff165f81158015610ef05750825b90505f8267ffffffffffffffff166001148015610f0c5750303b155b905081158015610f1a575080155b15610f385760405163f92ee8a960e01b815260040160405180910390fd5b845467ffffffffffffffff191660011785558315610f6257845460ff60401b1916600160401b1785555b610f6b336116ae565b610f736116bf565b831561096d57845460ff60401b19168555604051600181527fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d29060200160405180910390a15050505050565b610fc76113fd565b5f5460ff161580610fdf57505f54610100900460ff16155b15610ffd5760405163b4202dd560e01b815260040160405180910390fd5b6040516370a0823160e01b8152306004820152610b5c907f0000000000000000000000000000000000000000000000000000000000000000906001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016906370a0823190602401602060405180830381865afa158015611085573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906110a99190611dc6565b6001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000169190611399565b6110e261134b565b5f5460ff161561110757604051632252c26960e21b8152426004820152602401610789565b7f00000000000000000000000000000000000000000000000000000000000000004210156111705760405163457f873160e01b81524260048201527f00000000000000000000000000000000000000000000000000000000000000006024820152604401610789565b630ee6b28082101561119857604051630266752760e51b815260048101839052602401610789565b6111a181611458565b7f0000000000000000000000000000000000000000000000000000000000000000421015611230576102bc600a54106111ed5760405163b64bf0e160e01b815260040160405180910390fd5b335f908152600b602052604090205460ff1661123057335f908152600b60205260408120805460ff19166001179055600a80549161122a83611ddd565b91905055505b335f908152600660205260408120805484929061124e908490611db3565b925050819055508160025f8282546112669190611db3565b909155505060098054905f61127a83611ddd565b9091555050335f8181526008602052604090208290556112c6907f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03169030856116c7565b60405182815233907f2da466a7b24304f47e87fa2e1e5a81b9831ce54fec19055ce277ca2f39ba42c49060200160405180910390a2610c8260015f80516020611e6483398151915255565b6113196113fd565b6001600160a01b03811661134257604051631e4fbdf760e01b81525f6004820152602401610789565b610c648161163e565b5f80516020611e6483398151915280546001190161137c57604051633ee5aeb560e01b815260040160405180910390fd5b60029055565b5f8261138e8584611706565b1490505b9392505050565b6040516001600160a01b038381166024830152604482018390526113f891859182169063a9059cbb906064015b604051602081830303815290604052915060e01b6020820180516001600160e01b03838183161783525050505061174a565b505050565b3361142f7f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c199300546001600160a01b031690565b6001600160a01b031614610b5c5760405163118cdaa760e01b8152336004820152602401610789565b6298968081108061146c57506301c9c38081115b15610c64576040516319bfbdbd60e11b815260048101829052602401610789565b306001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016148061151357507f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03166115075f80516020611e44833981519152546001600160a01b031690565b6001600160a01b031614155b15610b5c5760405163703e46dd60e11b815260040160405180910390fd5b610c646113fd565b816001600160a01b03166352d1902d6040518163ffffffff1660e01b8152600401602060405180830381865afa925050508015611593575060408051601f3d908101601f1916820190925261159091810190611dc6565b60015b6115bb57604051634c9c8ce360e01b81526001600160a01b0383166004820152602401610789565b5f80516020611e4483398151915281146115eb57604051632a87526960e21b815260048101829052602401610789565b6113f883836117ab565b306001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001614610b5c5760405163703e46dd60e11b815260040160405180910390fd5b7f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c19930080546001600160a01b031981166001600160a01b03848116918217845560405192169182907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0905f90a3505050565b6116b6611800565b610c6481611849565b610b5c611800565b6040516001600160a01b0384811660248301528381166044830152606482018390526117009186918216906323b872dd906084016113c6565b50505050565b5f81815b8451811015611740576117368286838151811061172957611729611df5565b6020026020010151611851565b915060010161170a565b5090505b92915050565b5f61175e6001600160a01b0384168361187a565b905080515f141580156117825750808060200190518101906117809190611e09565b155b156113f857604051635274afe760e01b81526001600160a01b0384166004820152602401610789565b6117b482611887565b6040516001600160a01b038316907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b905f90a28051156117f8576113f882826118ea565b610c8261195c565b7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a0054600160401b900460ff16610b5c57604051631afcd79f60e31b815260040160405180910390fd5b611319611800565b5f81831061186b575f828152602084905260409020611392565b505f9182526020526040902090565b606061139283835f61197b565b806001600160a01b03163b5f036118bc57604051634c9c8ce360e01b81526001600160a01b0382166004820152602401610789565b5f80516020611e4483398151915280546001600160a01b0319166001600160a01b0392909216919091179055565b60605f80846001600160a01b0316846040516119069190611e28565b5f60405180830381855af49150503d805f811461193e576040519150601f19603f3d011682016040523d82523d5f602084013e611943565b606091505b5091509150611953858383611a14565b95945050505050565b3415610b5c5760405163b398979f60e01b815260040160405180910390fd5b6060814710156119a05760405163cd78605960e01b8152306004820152602401610789565b5f80856001600160a01b031684866040516119bb9190611e28565b5f6040518083038185875af1925050503d805f81146119f5576040519150601f19603f3d011682016040523d82523d5f602084013e6119fa565b606091505b5091509150611a0a868383611a14565b9695505050505050565b606082611a2957611a2482611a70565b611392565b8151158015611a4057506001600160a01b0384163b155b15611a6957604051639996b31560e01b81526001600160a01b0385166004820152602401610789565b5080611392565b805115611a805780518082602001fd5b604051630a12f52160e11b815260040160405180910390fd5b80356001600160a01b0381168114611aaf575f80fd5b919050565b5f805f805f60808688031215611ac8575f80fd5b8535945060208601359350604086013567ffffffffffffffff80821115611aed575f80fd5b818801915088601f830112611b00575f80fd5b813581811115611b0e575f80fd5b8960208260051b8501011115611b22575f80fd5b602083019550809450505050611b3a60608701611a99565b90509295509295909350565b5f60208284031215611b56575f80fd5b61139282611a99565b5f60208284031215611b6f575f80fd5b5035919050565b634e487b7160e01b5f52604160045260245ffd5b5f8060408385031215611b9b575f80fd5b611ba483611a99565b9150602083013567ffffffffffffffff80821115611bc0575f80fd5b818501915085601f830112611bd3575f80fd5b813581811115611be557611be5611b76565b604051601f8201601f19908116603f01168101908382118183101715611c0d57611c0d611b76565b81604052828152886020848701011115611c25575f80fd5b826020860160208301375f6020848301015280955050505050509250929050565b5f6101c082019050825182526020830151602083015260408301516040830152606083015160608301526080830151608083015260a083015160a083015260c083015160c083015260e0830151611ca160e084018215159052565b5061010083810151151590830152610120808401511515908301526101408084015190830152610160808401519083015261018080840151908301526101a092830151929091019190915290565b5f5b83811015611d09578181015183820152602001611cf1565b50505f910152565b602081525f8251806020840152611d2f816040850160208701611cef565b601f01601f19169190910160400192915050565b5f8060408385031215611d54575f80fd5b50508035926020909101359150565b604080825281018390525f6001600160fb1b03841115611d81575f80fd5b8360051b808660608501376020830193909352500160600192915050565b634e487b7160e01b5f52601160045260245ffd5b8082018082111561174457611744611d9f565b5f60208284031215611dd6575f80fd5b5051919050565b5f60018201611dee57611dee611d9f565b5060010190565b634e487b7160e01b5f52603260045260245ffd5b5f60208284031215611e19575f80fd5b81518015158114611392575f80fd5b5f8251611e39818460208701611cef565b919091019291505056fe360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f00a26469706673582212208dfdba2e9477dfb8915251f3d74ef98c8ef7ce5548326403dda523156367f64764736f6c63430008180033000000000000000000000000010700808d59d2bb92257fcafacfe8e5bff7ab87000000000000000000000000793500709506652fcc61f0d2d0fda605638d429300000000000000000000000005dc0010c9902ecf6cbc921c6a4bd971c69e5a2e0000000000000000000000000000000000000000000000000000000067ab90200000000000000000000000000000000000000000000000000000000067b4caa00000000000000000000000000000000000000000000000000000003a35294400", + "nonce": "0x4bc8d", + "chainId": "0x1ecf" + }, + "additionalContracts": [], + "isFixedGasLimit": false + }, + { + "hash": "0xd5ea37bc7d2fa77fa1857835cbb8a8546793308c384103ea10800be1accd454c", + "transactionType": "CREATE2", + "contractName": "UUPSProxy", + "contractAddress": "0x5a1e00884e35bf2dc39af51712d08bef24b1817f", + "function": null, + "arguments": [ + "0xe9aB275D7E9859bbeF11A79b7c1854a030D0c171", + "0x" + ], + "transaction": { + "from": "0x660ad4b5a74130a4796b4d54bc6750ae93c86e6c", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "value": "0x0", + "input": "0x6b5d0bf482f03bfc69060956ae59c41746097a4c9eeee42aef2212b60bf28a9c608060405234801561000f575f80fd5b506040516104d43803806104d483398101604081905261002e916102e2565b818161003b82825f610044565b505050506103f7565b61004d8361006f565b5f825111806100595750805b1561006a5761006883836100ae565b505b505050565b610078816100da565b6040516001600160a01b038216907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b905f90a250565b60606100d383836040518060600160405280602781526020016104ad6027913961018d565b9392505050565b6001600160a01b0381163b61014c5760405162461bcd60e51b815260206004820152602d60248201527f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60448201526c1bdd08184818dbdb9d1c9858dd609a1b60648201526084015b60405180910390fd5b7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc80546001600160a01b0319166001600160a01b0392909216919091179055565b60605f80856001600160a01b0316856040516101a991906103aa565b5f60405180830381855af49150503d805f81146101e1576040519150601f19603f3d011682016040523d82523d5f602084013e6101e6565b606091505b5090925090506101f886838387610202565b9695505050505050565b606083156102705782515f03610269576001600160a01b0385163b6102695760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e74726163740000006044820152606401610143565b508161027a565b61027a8383610282565b949350505050565b8151156102925781518083602001fd5b8060405162461bcd60e51b815260040161014391906103c5565b634e487b7160e01b5f52604160045260245ffd5b5f5b838110156102da5781810151838201526020016102c2565b50505f910152565b5f80604083850312156102f3575f80fd5b82516001600160a01b0381168114610309575f80fd5b60208401519092506001600160401b0380821115610325575f80fd5b818501915085601f830112610338575f80fd5b81518181111561034a5761034a6102ac565b604051601f8201601f19908116603f01168101908382118183101715610372576103726102ac565b8160405282815288602084870101111561038a575f80fd5b61039b8360208301602088016102c0565b80955050505050509250929050565b5f82516103bb8184602087016102c0565b9190910192915050565b602081525f82518060208401526103e38160408501602087016102c0565b601f01601f19169190910160400192915050565b60aa806104035f395ff3fe608060405236601057600e6013565b005b600e5b601f601b6021565b6057565b565b5f60527f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc546001600160a01b031690565b905090565b365f80375f80365f845af43d5f803e8080156070573d5ff35b3d5ffdfea264697066735822122039fd63d084b1b3efb382c0e80fee4b3d1b88fe0f3a68a497d4090acbcdbdc35864736f6c63430008180033416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564000000000000000000000000e9ab275d7e9859bbef11a79b7c1854a030d0c17100000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "nonce": "0x4bc8e", + "chainId": "0x1ecf" + }, + "additionalContracts": [], + "isFixedGasLimit": false + }, + { + "hash": "0xe6125be646cef90350f3c376b3aa689105017ec160c7b6ff5bbc1f41ad11ef54", + "transactionType": "CALL", + "contractName": null, + "contractAddress": "0x2843c269d2a64ecfa63548e8b3fc0fd23b7f70cb", + "function": "handleOps((address,uint256,bytes,bytes,uint256,uint256,uint256,uint256,uint256,bytes,bytes)[],address)", + "arguments": [ + "[(0x2e2B1c42E38f5af81771e65D87729E57ABD1337a, 4433, 0x, 0xb61d27f60000000000000000000000005a2b641b84b0230c8e75f55d5afd27f4dbd59d5b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000084db9e99080000000000000000000000003e9727470c66b1e77034590926cde0242b5a3dcc000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000010000000000000000000000005a1e00884e35bf2dc39af51712d08bef24b1817f00000000000000000000000000000000000000000000000000000000, 4000000, 210000, 21000, 1, 1000000000, 0x0000000000000000000000000000000000000000, 0x390d43b95a1e78bb154cee777f78cdcd4236c4b3b07d6ce13f45037b819f5f8e4ef23a9ac2e4b0d727345561a48d294c61ea1a055437df47d4e14c80a3f108751b4dafe04e525669c20f12056b9e11a36666de86b464c4a95722ae6b09b459caaf2db9363477c007fbf6bef1106b2f74871b3150d7bc8405f84f91050335eb230d1b)]", + "0x660ad4B5A74130a4796B4d54BC6750Ae93C86e6c" + ], + "transaction": { + "from": "0x660ad4b5a74130a4796b4d54bc6750ae93c86e6c", + "to": "0x2843c269d2a64ecfa63548e8b3fc0fd23b7f70cb", + "value": "0x0", + "input": "0x1fad948c0000000000000000000000000000000000000000000000000000000000000040000000000000000000000000660ad4b5a74130a4796b4d54bc6750ae93c86e6c000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000002e2b1c42e38f5af81771e65d87729e57abd1337a00000000000000000000000000000000000000000000000000000000000011510000000000000000000000000000000000000000000000000000000000000160000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000003d0900000000000000000000000000000000000000000000000000000000000003345000000000000000000000000000000000000000000000000000000000000052080000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000003b9aca0000000000000000000000000000000000000000000000000000000000000002e0000000000000000000000000000000000000000000000000000000000000032000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000124b61d27f60000000000000000000000005a2b641b84b0230c8e75f55d5afd27f4dbd59d5b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000084db9e99080000000000000000000000003e9727470c66b1e77034590926cde0242b5a3dcc000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000010000000000000000000000005a1e00884e35bf2dc39af51712d08bef24b1817f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000082390d43b95a1e78bb154cee777f78cdcd4236c4b3b07d6ce13f45037b819f5f8e4ef23a9ac2e4b0d727345561a48d294c61ea1a055437df47d4e14c80a3f108751b4dafe04e525669c20f12056b9e11a36666de86b464c4a95722ae6b09b459caaf2db9363477c007fbf6bef1106b2f74871b3150d7bc8405f84f91050335eb230d1b000000000000000000000000000000000000000000000000000000000000", + "nonce": "0x4bc8f", + "chainId": "0x1ecf" + }, + "additionalContracts": [], + "isFixedGasLimit": false + }, + { + "hash": "0xc9040ceaa02cb5c0cc139742d836d534d954628fe98eb9a930738c94d7646486", + "transactionType": "CALL", + "contractName": null, + "contractAddress": "0x2843c269d2a64ecfa63548e8b3fc0fd23b7f70cb", + "function": "handleOps((address,uint256,bytes,bytes,uint256,uint256,uint256,uint256,uint256,bytes,bytes)[],address)", + "arguments": [ + "[(0x2e2B1c42E38f5af81771e65D87729E57ABD1337a, 4434, 0x, 0xb61d27f60000000000000000000000005a1e00884e35bf2dc39af51712d08bef24b1817f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000048129fc1c00000000000000000000000000000000000000000000000000000000, 4000000, 210000, 21000, 1, 1000000000, 0x0000000000000000000000000000000000000000, 0xe22c27fb22621ad86cbdc98b07b2977fd03de9855c2051ffbca24a9771efd13342bdb6864c9c47bb5a478875ce28793a5cc7401ce27c6272e2f68e19a2ba6eb21c)]", + "0x660ad4B5A74130a4796B4d54BC6750Ae93C86e6c" + ], + "transaction": { + "from": "0x660ad4b5a74130a4796b4d54bc6750ae93c86e6c", + "to": "0x2843c269d2a64ecfa63548e8b3fc0fd23b7f70cb", + "value": "0x0", + "input": "0x1fad948c0000000000000000000000000000000000000000000000000000000000000040000000000000000000000000660ad4b5a74130a4796b4d54bc6750ae93c86e6c000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000002e2b1c42e38f5af81771e65d87729e57abd1337a00000000000000000000000000000000000000000000000000000000000011520000000000000000000000000000000000000000000000000000000000000160000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000003d0900000000000000000000000000000000000000000000000000000000000003345000000000000000000000000000000000000000000000000000000000000052080000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000003b9aca00000000000000000000000000000000000000000000000000000000000000026000000000000000000000000000000000000000000000000000000000000002a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a4b61d27f60000000000000000000000005a1e00884e35bf2dc39af51712d08bef24b1817f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000048129fc1c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000041e22c27fb22621ad86cbdc98b07b2977fd03de9855c2051ffbca24a9771efd13342bdb6864c9c47bb5a478875ce28793a5cc7401ce27c6272e2f68e19a2ba6eb21c00000000000000000000000000000000000000000000000000000000000000", + "nonce": "0x4bc90", + "chainId": "0x1ecf" + }, + "additionalContracts": [], + "isFixedGasLimit": false + } + ], + "receipts": [ + { + "status": "0x1", + "cumulativeGasUsed": "0x2155b7", + "logs": [ + { + "address": "0xe9ab275d7e9859bbef11a79b7c1854a030d0c171", + "topics": [ + "0xc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d2" + ], + "data": "0x000000000000000000000000000000000000000000000000ffffffffffffffff", + "blockHash": "0xba15178f1757f24c0360486a2b373b2240bfb3ccb12704a5420d8b124c027f5b", + "blockNumber": "0xb1e66", + "transactionHash": "0x55f9c9c447311fbe329d2f01414445cdfff118ffe049dd1127a20dedbfaa1f3e", + "transactionIndex": "0x1", + "logIndex": "0x0", + "removed": false + } + ], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000100000000000001000000000000000000000000100000000000000000000000000000", + "type": "0x2", + "transactionHash": "0x55f9c9c447311fbe329d2f01414445cdfff118ffe049dd1127a20dedbfaa1f3e", + "transactionIndex": "0x1", + "blockHash": "0xba15178f1757f24c0360486a2b373b2240bfb3ccb12704a5420d8b124c027f5b", + "blockNumber": "0xb1e66", + "gasUsed": "0x2155b7", + "effectiveGasPrice": "0x5f5e100", + "from": "0x660ad4b5a74130a4796b4d54bc6750ae93c86e6c", + "to": null, + "contractAddress": "0xe9ab275d7e9859bbef11a79b7c1854a030d0c171", + "gasUsedForL1": "0x6243d", + "l1BlockNumber": "0x14caf89" + }, + { + "status": "0x1", + "cumulativeGasUsed": "0x39ca8", + "logs": [ + { + "address": "0x5a1e00884e35bf2dc39af51712d08bef24b1817f", + "topics": [ + "0xbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b", + "0x000000000000000000000000e9ab275d7e9859bbef11a79b7c1854a030d0c171" + ], + "data": "0x", + "blockHash": "0x538925cecf0a66eeb5b1dad36a3d52c973ff8d288c140c98d19e382b27694674", + "blockNumber": "0xb1e67", + "transactionHash": "0xd5ea37bc7d2fa77fa1857835cbb8a8546793308c384103ea10800be1accd454c", + "transactionIndex": "0x1", + "logIndex": "0x0", + "removed": false + } + ], + "logsBloom": "0x00000000000000000000000000000000400000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000020000000000000000000000010000000000000000000000000000000000000000000000020000000000000000000200000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000", + "type": "0x2", + "transactionHash": "0xd5ea37bc7d2fa77fa1857835cbb8a8546793308c384103ea10800be1accd454c", + "transactionIndex": "0x1", + "blockHash": "0x538925cecf0a66eeb5b1dad36a3d52c973ff8d288c140c98d19e382b27694674", + "blockNumber": "0xb1e67", + "gasUsed": "0x39ca8", + "effectiveGasPrice": "0x5f5e100", + "from": "0x660ad4b5a74130a4796b4d54bc6750ae93c86e6c", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "contractAddress": null, + "gasUsedForL1": "0x18cac", + "l1BlockNumber": "0x14caf89" + }, + { + "status": "0x1", + "cumulativeGasUsed": "0x42501", + "logs": [ + { + "address": "0x2843c269d2a64ecfa63548e8b3fc0fd23b7f70cb", + "topics": [ + "0xbb47ee3e183a558b1a2ff0874b079f3fc5478b7454eacf2bfc5af2ff5878f972" + ], + "data": "0x", + "blockHash": "0xd402b1e97a4a2193007a52324784a69ab63952a7b352fe0490e7339e8890192c", + "blockNumber": "0xb1e68", + "transactionHash": "0xe6125be646cef90350f3c376b3aa689105017ec160c7b6ff5bbc1f41ad11ef54", + "transactionIndex": "0x1", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x5a2b641b84b0230c8e75f55d5afd27f4dbd59d5b", + "topics": [ + "0x5ed922fe11a834df4ad0556e8f265179bbe61f057f4808eab4c3b6542b621260", + "0x0000000000000000000000003e9727470c66b1e77034590926cde0242b5a3dcc" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000010000000000000000000000005a1e00884e35bf2dc39af51712d08bef24b1817f", + "blockHash": "0xd402b1e97a4a2193007a52324784a69ab63952a7b352fe0490e7339e8890192c", + "blockNumber": "0xb1e68", + "transactionHash": "0xe6125be646cef90350f3c376b3aa689105017ec160c7b6ff5bbc1f41ad11ef54", + "transactionIndex": "0x1", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x2843c269d2a64ecfa63548e8b3fc0fd23b7f70cb", + "topics": [ + "0x49628fd1471006c1482da88028e9ce4dbb080b815c9b0344d39e5a8e6ec1419f", + "0x04aeca23631dcf3b671dba54684da1d455df7229aa9a8f4d99598564da030af8", + "0x0000000000000000000000002e2b1c42e38f5af81771e65d87729e57abd1337a", + "0x0000000000000000000000000000000000000000000000000000000000000000" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000011510000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000008f82c000000000000000000000000000000000000000000000000000000000008f82c", + "blockHash": "0xd402b1e97a4a2193007a52324784a69ab63952a7b352fe0490e7339e8890192c", + "blockNumber": "0xb1e68", + "transactionHash": "0xe6125be646cef90350f3c376b3aa689105017ec160c7b6ff5bbc1f41ad11ef54", + "transactionIndex": "0x1", + "logIndex": "0x2", + "removed": false + } + ], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000008000000000000000000010000000000000002000000000000020000000000000000000000000000000000000000000000000000000000000000000008000800000000020000000000000000000800002000000000000000000000000000000000000000000000000002000000800000000000000000800000080000000000000400000000000000400000000000000000000000000000000002000000000000000000000000100001000000000000000000010000000800000000000020000000000000010000000200000000000000000000000000001000000084000000", + "type": "0x2", + "transactionHash": "0xe6125be646cef90350f3c376b3aa689105017ec160c7b6ff5bbc1f41ad11ef54", + "transactionIndex": "0x1", + "blockHash": "0xd402b1e97a4a2193007a52324784a69ab63952a7b352fe0490e7339e8890192c", + "blockNumber": "0xb1e68", + "gasUsed": "0x42501", + "effectiveGasPrice": "0x5f5e100", + "from": "0x660ad4b5a74130a4796b4d54bc6750ae93c86e6c", + "to": "0x2843c269d2a64ecfa63548e8b3fc0fd23b7f70cb", + "contractAddress": null, + "gasUsedForL1": "0xbc38", + "l1BlockNumber": "0x14caf89" + }, + { + "status": "0x1", + "cumulativeGasUsed": "0x391f6", + "logs": [ + { + "address": "0x2843c269d2a64ecfa63548e8b3fc0fd23b7f70cb", + "topics": [ + "0xbb47ee3e183a558b1a2ff0874b079f3fc5478b7454eacf2bfc5af2ff5878f972" + ], + "data": "0x", + "blockHash": "0x899762a7bdc297c47cbd27985b5b8eca4c6f34cb506e6bd4463b08832fdb54e2", + "blockNumber": "0xb1e69", + "transactionHash": "0xc9040ceaa02cb5c0cc139742d836d534d954628fe98eb9a930738c94d7646486", + "transactionIndex": "0x1", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x5a1e00884e35bf2dc39af51712d08bef24b1817f", + "topics": [ + "0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000002e2b1c42e38f5af81771e65d87729e57abd1337a" + ], + "data": "0x", + "blockHash": "0x899762a7bdc297c47cbd27985b5b8eca4c6f34cb506e6bd4463b08832fdb54e2", + "blockNumber": "0xb1e69", + "transactionHash": "0xc9040ceaa02cb5c0cc139742d836d534d954628fe98eb9a930738c94d7646486", + "transactionIndex": "0x1", + "logIndex": "0x1", + "removed": false + }, + { + "address": "0x5a1e00884e35bf2dc39af51712d08bef24b1817f", + "topics": [ + "0xc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d2" + ], + "data": "0x0000000000000000000000000000000000000000000000000000000000000001", + "blockHash": "0x899762a7bdc297c47cbd27985b5b8eca4c6f34cb506e6bd4463b08832fdb54e2", + "blockNumber": "0xb1e69", + "transactionHash": "0xc9040ceaa02cb5c0cc139742d836d534d954628fe98eb9a930738c94d7646486", + "transactionIndex": "0x1", + "logIndex": "0x2", + "removed": false + }, + { + "address": "0x2843c269d2a64ecfa63548e8b3fc0fd23b7f70cb", + "topics": [ + "0x49628fd1471006c1482da88028e9ce4dbb080b815c9b0344d39e5a8e6ec1419f", + "0xc50415f27c552212c0a8a073aad2a632c28483f2a43c9a6345f581aeb70fbf43", + "0x0000000000000000000000002e2b1c42e38f5af81771e65d87729e57abd1337a", + "0x0000000000000000000000000000000000000000000000000000000000000000" + ], + "data": "0x0000000000000000000000000000000000000000000000000000000000001152000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000898ad00000000000000000000000000000000000000000000000000000000000898ad", + "blockHash": "0x899762a7bdc297c47cbd27985b5b8eca4c6f34cb506e6bd4463b08832fdb54e2", + "blockNumber": "0xb1e69", + "transactionHash": "0xc9040ceaa02cb5c0cc139742d836d534d954628fe98eb9a930738c94d7646486", + "transactionIndex": "0x1", + "logIndex": "0x3", + "removed": false + } + ], + "logsBloom": "0x00000000000000000000000000000000000000000000000000800000000000000408000000000000000000010000000000000000000000000000020000000000000000000000000000000000000000000001000000000000000000000000000800000000020000000000000000000800002000000000000000000000000000400000000000000000000800000000800000000000000080000000000000000020000400000000000010400010000000000000000000000000000002000000000000000000000000100001000000000000000000000004010000000000000020000000000000010000000000000000000000000000000000000000000000000000", + "type": "0x2", + "transactionHash": "0xc9040ceaa02cb5c0cc139742d836d534d954628fe98eb9a930738c94d7646486", + "transactionIndex": "0x1", + "blockHash": "0x899762a7bdc297c47cbd27985b5b8eca4c6f34cb506e6bd4463b08832fdb54e2", + "blockNumber": "0xb1e69", + "gasUsed": "0x391f6", + "effectiveGasPrice": "0x5f5e100", + "from": "0x660ad4b5a74130a4796b4d54bc6750ae93c86e6c", + "to": "0x2843c269d2a64ecfa63548e8b3fc0fd23b7f70cb", + "contractAddress": null, + "gasUsedForL1": "0x9508", + "l1BlockNumber": "0x14caf8a" + } + ], + "libraries": [], + "pending": [], + "returns": {}, + "timestamp": 1739030918, + "chain": 7887, + "commit": "d16c447" +} \ No newline at end of file diff --git a/script/migrations/155-deploy-sale.s.sol b/script/migrations/155-deploy-sale.s.sol new file mode 100644 index 00000000..4c6108df --- /dev/null +++ b/script/migrations/155-deploy-sale.s.sol @@ -0,0 +1,71 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.18; + +import {SealedBidTokenSale} from "@kinto-core/apps/SealedBidTokenSale.sol"; + +import {SafeBeaconProxy} from "@kinto-core/proxy/SafeBeaconProxy.sol"; +import {KintoAppRegistry} from "@kinto-core/apps/KintoAppRegistry.sol"; + +import {UUPSProxy} from "@kinto-core-test/helpers/UUPSProxy.sol"; +import {MigrationHelper} from "@kinto-core-script/utils/MigrationHelper.sol"; + +import "@kinto-core-test/helpers/ArrayHelpers.sol"; +import {Script} from "forge-std/Script.sol"; +import {console2} from "forge-std/console2.sol"; + +contract DeployScript is Script, MigrationHelper { + using ArrayHelpers for *; + + address public constant SOCKET_APP = 0x3e9727470C66B1e77034590926CDe0242B5A3dCc; + address public constant KINTO = 0x010700808D59d2bb92257fCafACfe8e5bFF7aB87; + address public constant TREASURY = 0x793500709506652Fcc61F0d2D0fDa605638D4293; + address public constant USDC = 0x05DC0010C9902EcF6CBc921c6A4bd971c69E5A2E; + //Tuesday, Feb 11, 2025, 10:00:00 AM PT. + uint256 public constant PRE_START_TIME = 1739296800; + //Tuesday, Feb 18, 2025, 10:00:00 AM PT. + uint256 public constant START_TIME = 1739901600; + uint256 public constant MINIMUM_CAP = 250_000 * 1e6; + + function run() public override { + super.run(); + + if (_getChainDeployment("SealedBidTokenSale") != address(0)) { + console2.log("SealedBidTokenSale is deployed"); + return; + } + + vm.broadcast(deployerPrivateKey); + SealedBidTokenSale impl = new SealedBidTokenSale(KINTO, TREASURY, USDC, PRE_START_TIME, START_TIME, MINIMUM_CAP); + + (bytes32 salt, address expectedAddress) = + mineSalt(keccak256(abi.encodePacked(type(UUPSProxy).creationCode, abi.encode(address(impl), ""))), "5A1E00"); + + vm.broadcast(deployerPrivateKey); + UUPSProxy proxy = new UUPSProxy{salt: salt}(address(impl), ""); + SealedBidTokenSale sale = SealedBidTokenSale(address(proxy)); + + _handleOps( + abi.encodeWithSelector( + KintoAppRegistry.addAppContracts.selector, SOCKET_APP, [address(sale)].toMemoryArray() + ), + address(_getChainDeployment("KintoAppRegistry")) + ); + + uint256[] memory privateKeys = new uint256[](1); + privateKeys[0] = deployerPrivateKey; + _handleOps( + abi.encodeWithSelector(SealedBidTokenSale.initialize.selector), + payable(kintoAdminWallet), + address(proxy), + 0, + address(0), + privateKeys + ); + + assertEq(address(sale), address(expectedAddress)); + assertEq(address(sale.USDC()), USDC); + + saveContractAddress("SealedBidTokenSale", address(sale)); + saveContractAddress("SealedBidTokenSale-impl", address(impl)); + } +} diff --git a/src/apps/SealedBidTokenSale.md b/src/apps/SealedBidTokenSale.md new file mode 100644 index 00000000..f88e0caa --- /dev/null +++ b/src/apps/SealedBidTokenSale.md @@ -0,0 +1,164 @@ +# SealedBidTokenSale Technical Specification + +Below is the **technical specification** for a Solidity **sealed‐bid** token sale contract. + +--- + +## 1. **Contract Overview** + +- **Name**: `SealedBidTokenSale` +- **Purpose**: Accept USDC deposits for a token sale, enforce timing and minimum cap requirements, enable refunds if the sale is unsuccessful, and distribute tokens and USDC allocations via a Merkle proof if successful. +- **Inheritance**: `Ownable`, `ReentrancyGuard` + +--- + +## 2. **Key Roles** + +- **Owner**: + - Inherits from OpenZeppelin `Ownable`. + - Sets crucial parameters during contract deployment. + - Controls sale finalization, Merkle root setting, and proceeds withdrawal. + +- **Participants**: + - Deposit USDC during the sale window. + - Withdraw their deposit if the sale fails. + - Claim tokens and USDC allocations after sale success using a Merkle proof. + +- **Treasury**: + - Immutable address specified at deployment. + - Receives USDC proceeds upon successful sale completion. + +--- + +## 3. **Immutable Parameters** + +1. **`saleToken`** (`IERC20`) + - Token being sold through the contract. + - Set at construction. + +2. **`USDC`** (`IERC20`) + - USDC token contract reference for deposits. + - Set at construction. + +3. **`treasury`** (`address`) + - Fixed address that receives proceeds. + - Set at construction. + +4. **`startTime`** (`uint256`) + - Sale start timestamp. + - Set at construction. + +5. **`minimumCap`** (`uint256`) + - Minimum USDC required for success. + - Set at construction. + +--- + +## 4. **State Variables** + +1. **`saleEnded`** (`bool`) + - Indicates if owner has ended the sale. + +2. **`capReached`** (`bool`) + - Set to `true` if `totalDeposited >= minimumCap` when sale ends. + +3. **`totalDeposited`** (`uint256`) + - Sum of all USDC deposits. + +4. **`merkleRoot`** (`bytes32`) + - Root hash for token and USDC allocation proofs. + +5. **`deposits`** (`mapping(address => uint256)`) + - Tracks each user's USDC deposit amount. + +6. **`hasClaimed`** (`mapping(address => bool)`) + - Records whether an address has claimed their allocation. + +--- + +## 5. **Core Functions** + +### 5.1 **`deposit(uint256 amount)`** +- **Purpose**: Accepts USDC deposits from participants. +- **Constraints**: + 1. Must be after `startTime`. + 2. Sale must not be ended. + 3. Amount must be non-zero. +- **Effects**: + - Updates `deposits[msg.sender]` and `totalDeposited`. + - Transfers USDC from sender to contract. + - Emits `Deposited` event. + +### 5.2 **`withdraw()`** +- **Purpose**: Returns USDC to depositors if sale fails. +- **Constraints**: + 1. Sale must be ended. + 2. Cap must not be reached. + 3. Caller must have non-zero deposit. +- **Effects**: + - Returns user's entire USDC deposit. + - Zeroes their deposit balance. + - Emits `Withdrawn` event. + +### 5.3 **`endSale()`** (Owner-only) +- **Purpose**: Finalizes sale and determines success. +- **Constraints**: + 1. Only callable by owner. + 2. Sale must not already be ended. +- **Effects**: + - Sets `saleEnded = true`. + - Sets `capReached` based on minimum cap check. + - Emits `SaleEnded` event. + +### 5.4 **`claimTokens(uint256 saleTokenAllocation, uint256 usdcAllocation, bytes32[] calldata proof, address user)`** +- **Purpose**: Processes token and USDC claims using Merkle proofs. +- **Constraints**: + 1. Sale must be ended and successful. + 2. Merkle root must be set. + 3. User must not have claimed. + 4. Valid Merkle proof required. +- **Effects**: + - Marks user as claimed. + - Transfers allocated sale tokens. + - Returns allocated USDC. + - Emits `Claimed` event. + +### 5.5 **`setMerkleRoot(bytes32 newRoot)`** (Owner-only) +- **Purpose**: Sets allocation Merkle root. +- **Constraints**: + 1. Sale must be ended and successful. + 2. Only callable by owner. +- **Effects**: + - Sets `merkleRoot`. + - Emits `MerkleRootSet` event. + +### 5.6 **`withdrawProceeds()`** (Owner-only) +- **Purpose**: Sends USDC to treasury. +- **Constraints**: + 1. Sale must be ended and successful. + 2. Only callable by owner. +- **Effects**: + - Transfers all USDC to treasury address. + +--- + +## 6. **Custom Errors** + +1. **Parameter Validation**: + - `InvalidSaleTokenAddress` + - `InvalidTreasuryAddress` + - `ZeroDeposit` + +2. **State Checks**: + - `SaleNotStarted` + - `SaleAlreadyEnded` + - `SaleNotEnded` + - `CapNotReached` + - `SaleWasSuccessful` + +3. **Claim Validation**: + - `NothingToWithdraw` + - `AlreadyClaimed` + - `InvalidProof` + - `MerkleRootNotSet` + diff --git a/src/apps/SealedBidTokenSale.sol b/src/apps/SealedBidTokenSale.sol new file mode 100644 index 00000000..19b4ae11 --- /dev/null +++ b/src/apps/SealedBidTokenSale.sol @@ -0,0 +1,421 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.17; + +import {OwnableUpgradeable} from "@openzeppelin-5.0.1/contracts-upgradeable/access/OwnableUpgradeable.sol"; +import {Initializable} from "@openzeppelin-5.0.1/contracts-upgradeable/proxy/utils/Initializable.sol"; +import {ReentrancyGuardUpgradeable} from + "@openzeppelin-5.0.1/contracts-upgradeable/utils/ReentrancyGuardUpgradeable.sol"; +import {UUPSUpgradeable} from "@openzeppelin-5.0.1/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; +import {IERC20} from "@openzeppelin-5.0.1/contracts/token/ERC20/IERC20.sol"; +import {MerkleProof} from "@openzeppelin-5.0.1/contracts/utils/cryptography/MerkleProof.sol"; +import {SafeERC20} from "@openzeppelin-5.0.1/contracts/token/ERC20/utils/SafeERC20.sol"; + +/** + * @title SealedBidTokenSale + * @notice Manages a sealed-bid token sale where users deposit USDC and receive tokens based on their allocations + * @dev Implements a non-custodial token sale mechanism with the following features: + * - Time-bound participation window + * - Minimum cap for sale success + * - USDC deposits from users + * - Merkle-based token allocation claims + * - Full refunds if minimum cap not reached + * - Early participation window for first 700 emissaries + */ +contract SealedBidTokenSale is Initializable, UUPSUpgradeable, OwnableUpgradeable, ReentrancyGuardUpgradeable { + using SafeERC20 for IERC20; + + /* ============ Struct ============ */ + + struct SaleInfo { + /// @notice Timestamp when emissary early access begins + uint256 preStartTime; + /// @notice Timestamp when public sale begins + uint256 startTime; + /// @notice Minimum USDC required for sale success + uint256 minimumCap; + /// @notice Total USDC deposited by all users + uint256 totalDeposited; + /// @notice Total USDC withdrawn after failed sale + uint256 totalWithdrawn; + /// @notice Total USDC claimed by users + uint256 totalUsdcClaimed; + /// @notice Total sale tokens claimed + uint256 totalSaleTokenClaimed; + /// @notice Whether sale has been officially ended + bool saleEnded; + /// @notice Whether minimum cap was reached + bool capReached; + /// @notice Whether specified user has claimed tokens + bool hasClaimed; + /// @notice Total number of unique depositors + uint256 contributorCount; + /// @notice Current number of emissary participants + uint256 currentEmissaryCount; + /// @notice Deposit amount for specified user + uint256 depositAmount; + /// @notice Max price set by specified user + uint256 maxPrice; + } + + /* ============ Custom Errors ============ */ + + /// @notice Thrown when attempting to initialize with zero address for sale token + error InvalidSaleTokenAddress(address token); + /// @notice Thrown when attempting to initialize with zero address for treasury + error InvalidTreasuryAddress(address treasury); + /// @notice Thrown when attempting operations before sale start time + error SaleNotStarted(uint256 currentTime, uint256 startTime); + /// @notice Thrown when attempting operations after sale has ended + error SaleAlreadyEnded(uint256 currentTime); + /// @notice Thrown when attempting operations that require sale to be ended + error SaleNotEnded(uint256 currentTime); + /// @notice Thrown when attempting operations that require minimum cap to be reached + error CapNotReached(); + /// @notice Thrown when attempting withdrawals if cap is reached + error CapReached(); + /// @notice Thrown when user has no funds to withdraw + error NothingToWithdraw(address user); + /// @notice Thrown when attempting to claim tokens more than once + error AlreadyClaimed(address user); + /// @notice Thrown when provided Merkle proof is invalid + error InvalidProof(bytes32[] proof, bytes32 leaf); + /// @notice Thrown when attempting claims before Merkle root is set + error MerkleRootNotSet(); + /// @notice Thrown when attempting to deposit less than MIN_DEPOSIT + error MinDeposit(uint256 amount); + /// @notice Thrown when new max price is out of range + error MaxPriceOutOfRange(uint256 amount); + /// @notice Thrown when emissary slots are fully occupied + error EmissaryFull(); + /// @notice Thrown when time configuration is invalid + error InvalidTimeConfiguration(); + + /* ============ Events ============ */ + + /// @notice Emitted when a user deposits USDC into the sale + /// @param user Address of the depositing user + /// @param amount Amount of USDC deposited + event Deposited(address indexed user, uint256 amount); + + /// @notice Emitted when a user withdraws USDC from a failed sale + /// @param user Address of the withdrawing user + /// @param amount Amount of USDC withdrawn + event Withdrawn(address indexed user, uint256 amount); + + /// @notice Emitted when the sale is officially ended + /// @param capReached Whether the minimum cap was reached + /// @param totalDeposited Total amount of USDC deposited in sale + event SaleEnded(bool capReached, uint256 totalDeposited); + + /// @notice Emitted when the Merkle root for token allocations is set + /// @param root New Merkle root value + event MerkleRootSet(bytes32 root); + + /// @notice Emitted when a user claims their allocated tokens + /// @param user Address of the claiming user + /// @param tokenAmount Amount of tokens claimed + event Claimed(address indexed user, uint256 tokenAmount); + + // Add this event to the SealedBidTokenSale contract's events section + /// @notice Emitted when a user updates their max price + /// @param user Address of the user updating their max price + /// @param oldPrice Previous max price value + /// @param newPrice New max price value + event MaxPriceUpdated(address indexed user, uint256 oldPrice, uint256 newPrice); + + /* ============ Constant ============ */ + + /// @notice Token being sold in the sale + uint256 public constant MIN_DEPOSIT = 250 * 1e6; + /// @notice Maximum number of emissaries + uint256 public constant MAX_EMISSARIES = 700; + + /* ============ Immutable ============ */ + + /// @notice Token being sold in the sale + IERC20 public immutable saleToken; + /// @notice USDC token contract for deposits + IERC20 public immutable USDC; + /// @notice Address where sale proceeds will be sent + address public immutable treasury; + /// @notice Timestamp when emissary early access begins + uint256 public immutable preStartTime; + /// @notice Timestamp when the sale begins + uint256 public immutable startTime; + /// @notice Minimum amount of USDC required for sale success + uint256 public immutable minimumCap; + + /* ============ State Variables ============ */ + + /// @notice Whether the sale period has officially ended + bool public saleEnded; + /// @notice Whether the minimum cap was reached by end of sale + bool public capReached; + /// @notice Running total of USDC withdrawn from sale + uint256 public totalWithdrawn; + /// @notice Running total of USDC deposited into sale + uint256 public totalDeposited; + /// @notice Running total of USDC claimed from sale + uint256 public totalUsdcClaimed; + /// @notice Running total of sale token withdrawn from sale + uint256 public totalSaleTokenClaimed; + /// @notice Merkle root for verifying token allocations + bytes32 public merkleRoot; + /// @notice Maps user addresses to their USDC deposit amounts + mapping(address => uint256) public deposits; + /// @notice Maps user addresses to whether they've claimed tokens + mapping(address => bool) public hasClaimed; + /// @notice Maps user addresses to their selected maxPrice + mapping(address => uint256) public maxPrices; + /// @notice Count of all contributors + uint256 public contributorCount; + /// @notice Current number of emissary participants + uint256 public currentEmissaryCount; + /// @notice Maps user addresses to emissary status + mapping(address => bool) public isEmissary; + + /* ============ Constructor ============ */ + /** + * @notice Initializes the token sale with required parameters + * @param _saleToken Address of the token being sold + * @param _treasury Address where sale proceeds will be sent + * @param _usdcToken Address of the USDC token contract + * @param _startTime Timestamp when sale will begin + * @param _minimumCap Minimum USDC amount required for sale success + */ + constructor( + address _saleToken, + address _treasury, + address _usdcToken, + uint256 _preStartTime, + uint256 _startTime, + uint256 _minimumCap + ) { + _disableInitializers(); + + if (_saleToken == address(0)) revert InvalidSaleTokenAddress(_saleToken); + if (_treasury == address(0)) revert InvalidTreasuryAddress(_treasury); + if (_preStartTime >= _startTime) revert InvalidTimeConfiguration(); + + saleToken = IERC20(_saleToken); + treasury = _treasury; + USDC = IERC20(_usdcToken); + preStartTime = _preStartTime; + startTime = _startTime; + minimumCap = _minimumCap; + } + + /// @dev initialize the proxy + function initialize() external virtual initializer { + __Ownable_init(msg.sender); + __UUPSUpgradeable_init(); + } + + /** + * @dev Authorize the upgrade. Only by an owner. + * @param newImplementation address of the new implementation + */ + // This function is called by the proxy contract when the factory is upgraded + function _authorizeUpgrade(address newImplementation) internal view override onlyOwner { + (newImplementation); + } + + /* ============ User Functions ============ */ + + /** + * @notice Allows users to deposit USDC into the token sale + * @dev - Sale must be active (after start time, before end) + * - Amount must be greater than zero + * - Updates user's deposit balance, total deposits, and maxPrice + * - Transfers USDC from user to contract + * @param amount Amount of USDC to deposit + * @param maxPrice The maximum price set by the user for the token sale + */ + function deposit(uint256 amount, uint256 maxPrice) external nonReentrant { + if (saleEnded) revert SaleAlreadyEnded(block.timestamp); + if (block.timestamp < preStartTime) revert SaleNotStarted(block.timestamp, preStartTime); + if (amount < MIN_DEPOSIT) revert MinDeposit(amount); + _checkMaxPrice(maxPrice); + + // Handle emissary period + if (block.timestamp < startTime) { + if (currentEmissaryCount >= MAX_EMISSARIES) revert EmissaryFull(); + if (!isEmissary[msg.sender]) { + isEmissary[msg.sender] = true; + currentEmissaryCount++; + } + } + + deposits[msg.sender] += amount; + totalDeposited += amount; + contributorCount++; + + // Save the user's maxPrice + maxPrices[msg.sender] = maxPrice; + + // Transfer USDC from user to contract + USDC.safeTransferFrom(msg.sender, address(this), amount); + + // Emit deposit event + emit Deposited(msg.sender, amount); + } + + /** + * @notice Allows users to withdraw their USDC if sale failed to reach minimum cap + * @dev - Sale must be ended and minimum cap not reached + * - User must have deposited USDC + * - Sends full deposit amount back to user + * - Sets user's deposit balance to zero + */ + function withdraw() external nonReentrant { + // Verify sale has ended unsuccessfully + if (!saleEnded) revert SaleNotEnded(block.timestamp); + if (capReached) revert CapReached(); + + // Get user's deposit amount + uint256 amount = deposits[msg.sender]; + if (amount == 0) revert NothingToWithdraw(msg.sender); + + // Clear user's deposit before transfer + deposits[msg.sender] = 0; + totalWithdrawn += amount; + + // Return USDC to user + USDC.safeTransfer(msg.sender, amount); + + // Emit withdrawal event + emit Withdrawn(msg.sender, amount); + } + + /** + * @notice Allows users to claim their allocated tokens using a Merkle proof + * @dev - Sale must be ended successfully and Merkle root set + * - User must not have claimed already + * - Proof must be valid for user's allocation + * - Transfers allocated tokens and USDC to user + * @param saleTokenAllocation Amount of sale tokens allocated to user + * @param usdcAllocation Amount of USDC allocated to user + * @param proof Merkle proof verifying the allocation + * @param user Address of user claiming tokens + */ + function claimTokens(uint256 saleTokenAllocation, uint256 usdcAllocation, bytes32[] calldata proof, address user) + external + nonReentrant + { + // Verify sale ended successfully and claims are enabled + if (!saleEnded || !capReached) revert CapNotReached(); + if (merkleRoot == bytes32(0)) revert MerkleRootNotSet(); + if (hasClaimed[user]) revert AlreadyClaimed(user); + + // Create and verify Merkle leaf + bytes32 leaf = keccak256(bytes.concat(keccak256(abi.encode(user, saleTokenAllocation, usdcAllocation)))); + if (!MerkleProof.verify(proof, merkleRoot, leaf)) revert InvalidProof(proof, leaf); + + // Mark as claimed before transfers + hasClaimed[user] = true; + + // Transfer allocated sale tokens if any + if (saleTokenAllocation > 0) { + saleToken.safeTransfer(user, saleTokenAllocation); + totalSaleTokenClaimed += saleTokenAllocation; + } + + // Transfer allocated USDC if any + if (usdcAllocation > 0) { + USDC.safeTransfer(user, usdcAllocation); + totalUsdcClaimed += usdcAllocation; + } + + // Emit claim event + emit Claimed(user, saleTokenAllocation); + } + + /** + * @notice Allows users to update their selected maxPrice for the token sale. + * @param newMaxPrice The new maximum price value to be set for the user. + */ + function updateMaxPrice(uint256 newMaxPrice) external nonReentrant { + if (block.timestamp < preStartTime) revert SaleNotStarted(block.timestamp, preStartTime); + if (saleEnded) revert SaleAlreadyEnded(block.timestamp); + _checkMaxPrice(newMaxPrice); + + uint256 oldPrice = maxPrices[msg.sender]; + maxPrices[msg.sender] = newMaxPrice; + emit MaxPriceUpdated(msg.sender, oldPrice, newMaxPrice); + } + + function _checkMaxPrice(uint256 newMaxPrice) internal pure { + if (newMaxPrice < 10 * 1e6 || newMaxPrice > 30 * 1e6) revert MaxPriceOutOfRange(newMaxPrice); + } + + /* ============ Admin Functions ============ */ + + /** + * @notice Allows owner to officially end the sale + * @dev - Can only be called once + * - Sets final sale status based on minimum cap + * - Emits event with final sale results + */ + function endSale() external onlyOwner { + // Verify sale hasn't already been ended + if (saleEnded) revert SaleAlreadyEnded(block.timestamp); + + // Mark sale as ended and determine if cap was reached + saleEnded = true; + capReached = totalDeposited >= minimumCap; + + // Emit sale end event with final status + emit SaleEnded(capReached, totalDeposited); + } + + /** + * @notice Sets the Merkle root for verifying token allocations + * @dev - Sale must be ended successfully + * - Enables token claiming process + * @param newRoot The Merkle root hash of all valid allocations + */ + function setMerkleRoot(bytes32 newRoot) external onlyOwner { + // Verify sale ended successfully before setting root + if (!saleEnded || !capReached) revert CapNotReached(); + + // Update Merkle root + merkleRoot = newRoot; + + // Emit root update event + emit MerkleRootSet(newRoot); + } + + /** + * @notice Allows owner to withdraw sale proceeds to treasury + * @dev - Sale must be ended successfully + * - Transfers all USDC to treasury address + */ + function withdrawProceeds() external onlyOwner { + // Verify sale ended successfully + if (!saleEnded || !capReached) revert CapNotReached(); + + // Transfer all USDC balance to treasury + USDC.safeTransfer(treasury, USDC.balanceOf(address(this))); + } + + /* ============ View Functions ============ */ + + function saleStatus(address user) external view returns (SaleInfo memory) { + return SaleInfo({ + preStartTime: preStartTime, + startTime: startTime, + minimumCap: minimumCap, + totalDeposited: totalDeposited, + totalWithdrawn: totalWithdrawn, + totalUsdcClaimed: totalUsdcClaimed, + totalSaleTokenClaimed: totalSaleTokenClaimed, + saleEnded: saleEnded, + capReached: capReached, + hasClaimed: hasClaimed[user], + contributorCount: contributorCount, + currentEmissaryCount: currentEmissaryCount, + depositAmount: deposits[user], + maxPrice: maxPrices[user] + }); + } +} diff --git a/test/artifacts/7887/addresses.json b/test/artifacts/7887/addresses.json index 0cbace32..a4b85722 100644 --- a/test/artifacts/7887/addresses.json +++ b/test/artifacts/7887/addresses.json @@ -264,5 +264,7 @@ "CRV": "0xC90000A619e56D12B9da6858509BA497B64e77eC", "BridgerL2V12-impl": "0xB0AC6E846079FA2A984298C056F304070EA24e31", "BridgerL2V13-impl": "0xfcdF95304e95aFb40d14300d39c258dB45194734", - "KintoIDV11-impl": "0x4aC06254558e144C41461a319822993900cE2eE4" + "KintoIDV11-impl": "0x4aC06254558e144C41461a319822993900cE2eE4", + "SealedBidTokenSale": "0x5a1E00884e35bF2dC39Af51712D08bEF24b1817f", + "SealedBidTokenSale-impl": "0xe9aB275D7E9859bbeF11A79b7c1854a030D0c171" } \ No newline at end of file diff --git a/test/fork/bridger/BridgerL2.t.sol b/test/fork/bridger/BridgerL2.t.sol index 69aa8551..0b4e1831 100644 --- a/test/fork/bridger/BridgerL2.t.sol +++ b/test/fork/bridger/BridgerL2.t.sol @@ -49,7 +49,6 @@ contract BridgerL2Test is SignatureHelper, SharedSetup, BridgeDataHelper { uint256 fee = 1; IBridger.BridgeData memory bridgeData = bridgeData[block.chainid][DAI_KINTO]; - deal(address(_bridgerL2), bridgeData.gasFee); deal(inputAsset, address(_kintoWallet), amountIn + fee); @@ -60,6 +59,7 @@ contract BridgerL2Test is SignatureHelper, SharedSetup, BridgeDataHelper { IERC20(inputAsset).approve(address(_bridgerL2), amountIn + fee); bridgeData.gasFee = IBridge(bridgeData.vault).getMinFees(bridgeData.connector, bridgeData.msgGasLimit, 322); + deal(address(_bridgerL2), bridgeData.gasFee); vm.startPrank(address(_kintoWallet)); _bridgerL2.withdrawERC20(inputAsset, amountIn, _kintoWallet.owners(0), fee, bridgeData); diff --git a/test/unit/apps/SealedBidTokenSale.t.sol b/test/unit/apps/SealedBidTokenSale.t.sol new file mode 100644 index 00000000..30294359 --- /dev/null +++ b/test/unit/apps/SealedBidTokenSale.t.sol @@ -0,0 +1,1468 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.18; + +import {Ownable} from "@openzeppelin-5.0.1/contracts/access/Ownable.sol"; +import {UUPSProxy} from "@kinto-core-test/helpers/UUPSProxy.sol"; +import {Test} from "forge-std/Test.sol"; +import {SealedBidTokenSale} from "@kinto-core/apps/SealedBidTokenSale.sol"; +import {ERC20Mock} from "@kinto-core-test/helpers/ERC20Mock.sol"; +import {MerkleProof} from "@openzeppelin-5.0.1/contracts/utils/cryptography/MerkleProof.sol"; +import {SharedSetup} from "@kinto-core-test/SharedSetup.t.sol"; + +contract SealedBidTokenSaleTest is SharedSetup { + using MerkleProof for bytes32[]; + + SealedBidTokenSale public sale; + ERC20Mock public usdc; + ERC20Mock public saleToken; + + uint256 public preStartTime; + uint256 public startTime; + uint256 public endTime; + uint256 public constant MIN_CAP = 10e6 * 1e6; + uint256 public constant MAX_CAP = 20e6 * 1e6; + + bytes32 public merkleRoot; + bytes32[] public proof; + uint256 public saleTokenAllocation = 1000 * 1e18; + uint256 public usdcAllocation = 1000 * 1e6; + uint256 public maxPrice = 10 * 1e6; + + function setUp() public override { + super.setUp(); + + preStartTime = block.timestamp + 1 days; + startTime = block.timestamp + 2 days; + endTime = startTime + 4 days; + + // Deploy mock tokens + usdc = new ERC20Mock("USDC", "USDC", 6); + saleToken = new ERC20Mock("K", "KINTO", 18); + + sale = new SealedBidTokenSale(address(saleToken), TREASURY, address(usdc), preStartTime, startTime, MIN_CAP); + vm.startPrank(admin); + sale = SealedBidTokenSale(address(new UUPSProxy{salt: 0}(address(sale), ""))); + sale.initialize(); + vm.stopPrank(); + + // Setup Merkle tree with alice and bob + bytes32[] memory leaves = new bytes32[](2); + leaves[0] = keccak256(bytes.concat(keccak256(abi.encode(alice, saleTokenAllocation, usdcAllocation)))); + leaves[1] = keccak256(bytes.concat(keccak256(abi.encode(bob, saleTokenAllocation * 2, usdcAllocation)))); + + merkleRoot = buildRoot(leaves); + proof = buildProof(leaves, 0); + } + + // Following code is adapted from https://github.com/dmfxyz/murky/blob/main/src/common/MurkyBase.sol. + function buildRoot(bytes32[] memory leaves) private pure returns (bytes32) { + require(leaves.length > 1, "leaves.length > 1"); + while (leaves.length > 1) { + leaves = hashLevel(leaves); + } + return leaves[0]; + } + + function buildProof(bytes32[] memory leaves, uint256 nodeIndex) private pure returns (bytes32[] memory) { + require(leaves.length > 1, "leaves.length > 1"); + + bytes32[] memory result = new bytes32[](64); + uint256 pos; + + while (leaves.length > 1) { + unchecked { + if (nodeIndex & 0x1 == 1) { + result[pos] = leaves[nodeIndex - 1]; + } else if (nodeIndex + 1 == leaves.length) { + result[pos] = bytes32(0); + } else { + result[pos] = leaves[nodeIndex + 1]; + } + ++pos; + nodeIndex /= 2; + } + leaves = hashLevel(leaves); + } + // Resize the length of the array to fit. + /// @solidity memory-safe-assembly + assembly { + mstore(result, pos) + } + + return result; + } + + function hashLevel(bytes32[] memory leaves) private pure returns (bytes32[] memory) { + bytes32[] memory result; + unchecked { + uint256 length = leaves.length; + if (length & 0x1 == 1) { + result = new bytes32[](length / 2 + 1); + result[result.length - 1] = hashPair(leaves[length - 1], bytes32(0)); + } else { + result = new bytes32[](length / 2); + } + uint256 pos = 0; + for (uint256 i = 0; i < length - 1; i += 2) { + result[pos] = hashPair(leaves[i], leaves[i + 1]); + ++pos; + } + } + return result; + } + + function hashPair(bytes32 left, bytes32 right) private pure returns (bytes32 result) { + /// @solidity memory-safe-assembly + assembly { + switch lt(left, right) + case 0 { + mstore(0x0, right) + mstore(0x20, left) + } + default { + mstore(0x0, left) + mstore(0x20, right) + } + result := keccak256(0x0, 0x40) + } + } + + /* ============ Constructor ============ */ + + function testConstructor() public view { + assertEq(address(sale.saleToken()), address(saleToken)); + assertEq(address(sale.USDC()), address(usdc)); + assertEq(sale.treasury(), TREASURY); + assertEq(sale.startTime(), startTime); + assertEq(sale.minimumCap(), MIN_CAP); + assertEq(sale.owner(), admin); + } + + /* ============ Deposit ============ */ + + function testDeposit() public { + vm.warp(startTime + 1); + uint256 amount = 1000 * 1e6; + + // Mint and approve USDC + usdc.mint(alice, amount); + vm.prank(alice); + usdc.approve(address(sale), amount); + + // Deposit with maxPrice + vm.prank(alice); + sale.deposit(amount, maxPrice); + + assertEq(sale.deposits(alice), amount); + assertEq(sale.totalDeposited(), amount); + assertEq(usdc.balanceOf(address(sale)), amount); + assertEq(sale.maxPrices(alice), maxPrice); + } + + function testDeposit_RevertWhen_BeforeStart() public { + vm.expectRevert( + abi.encodeWithSelector(SealedBidTokenSale.SaleNotStarted.selector, block.timestamp, preStartTime) + ); + vm.prank(alice); + sale.deposit(100 ether, maxPrice); + } + + function testDeposit_RevertWhen_SaleEnded() public { + // Advance time to start of sale + vm.warp(startTime + 1); + + // End the sale + vm.prank(admin); + sale.endSale(); + + // Try to deposit after sale has ended + uint256 amount = 1000 * 1e6; + usdc.mint(alice, amount); + vm.prank(alice); + usdc.approve(address(sale), amount); + + vm.prank(alice); + vm.expectRevert(abi.encodeWithSelector(SealedBidTokenSale.SaleAlreadyEnded.selector, block.timestamp)); + sale.deposit(amount, maxPrice); + } + + function testDeposit_RevertWhen_MinAmount() public { + // Advance time to start of sale + vm.warp(startTime + 1); + + // Try to deposit zero amount + vm.prank(alice); + vm.expectRevert(abi.encodeWithSelector(SealedBidTokenSale.MinDeposit.selector, 250 * 1e6 - 1)); + sale.deposit(250 * 1e6 - 1, maxPrice); + } + + function testDeposit_MultipleDeposits() public { + // Advance time to start of sale + vm.warp(startTime + 1); + + uint256 firstAmount = 250 * 1e6; + uint256 secondAmount = 2000 * 1e6; + uint256 totalAmount = firstAmount + secondAmount; + + // Mint and approve USDC for both deposits + usdc.mint(alice, totalAmount); + vm.prank(alice); + usdc.approve(address(sale), totalAmount); + + // Make first deposit + vm.prank(alice); + sale.deposit(firstAmount, maxPrice); + + // Make second deposit + vm.prank(alice); + sale.deposit(secondAmount, maxPrice * 2); + + // Verify final state + assertEq(sale.deposits(alice), totalAmount); + assertEq(sale.totalDeposited(), totalAmount); + assertEq(usdc.balanceOf(address(sale)), totalAmount); + assertEq(sale.maxPrices(alice), maxPrice * 2); + } + + function testDeposit_MultipleUsers() public { + // Advance time to start of sale + vm.warp(startTime + 1); + + uint256 aliceAmount = 1000 * 1e6; + uint256 bobAmount = 2000 * 1e6; + uint256 totalAmount = aliceAmount + bobAmount; + + // Setup Alice's deposit + usdc.mint(alice, aliceAmount); + vm.prank(alice); + usdc.approve(address(sale), aliceAmount); + + // Setup Bob's deposit + usdc.mint(bob, bobAmount); + vm.prank(bob); + usdc.approve(address(sale), bobAmount); + + // Make deposits + vm.prank(alice); + sale.deposit(aliceAmount, maxPrice * 2); + + vm.prank(bob); + sale.deposit(bobAmount, maxPrice); + + // Verify final state + assertEq(sale.deposits(alice), aliceAmount); + assertEq(sale.deposits(bob), bobAmount); + assertEq(sale.maxPrices(alice), maxPrice * 2); + assertEq(sale.totalDeposited(), totalAmount); + assertEq(usdc.balanceOf(address(sale)), totalAmount); + assertEq(sale.maxPrices(bob), maxPrice); + assertEq(sale.maxPrices(alice), maxPrice * 2); + } + + /* ============ endSale ============ */ + + function testEndSale() public { + vm.warp(startTime + 1); + + usdc.mint(alice, MAX_CAP); + + vm.prank(alice); + usdc.approve(address(sale), MAX_CAP); + + vm.prank(alice); + sale.deposit(MAX_CAP, maxPrice); + + vm.prank(admin); + sale.endSale(); + + assertTrue(sale.saleEnded()); + assertTrue(sale.capReached()); + } + + function testEndSale_WhenCapNotReached() public { + // Setup sale with amount below minimum cap + vm.warp(startTime + 1); + uint256 amount = MIN_CAP - 1e6; // Just under minimum cap + + usdc.mint(alice, amount); + vm.prank(alice); + usdc.approve(address(sale), amount); + + vm.prank(alice); + sale.deposit(amount, maxPrice); + + // End sale + vm.prank(admin); + sale.endSale(); + + assertTrue(sale.saleEnded()); + assertFalse(sale.capReached()); + assertEq(sale.totalDeposited(), amount); + } + + function testEndSale_ExactlyAtMinCap() public { + // Setup sale with amount exactly at minimum cap + vm.warp(startTime + 1); + uint256 amount = MIN_CAP; + + usdc.mint(alice, amount); + vm.prank(alice); + usdc.approve(address(sale), amount); + + vm.prank(alice); + sale.deposit(amount, maxPrice); + + // End sale + vm.prank(admin); + sale.endSale(); + + assertTrue(sale.saleEnded()); + assertTrue(sale.capReached()); + assertEq(sale.totalDeposited(), amount); + } + + function testEndSale_RevertWhen_NotOwner() public { + vm.warp(startTime + 1); + + vm.prank(alice); // Non-owner tries to end sale + vm.expectRevert(abi.encodeWithSelector(Ownable.OwnableUnauthorizedAccount.selector, alice)); + sale.endSale(); + } + + function testEndSale_RevertWhen_AlreadyEnded() public { + vm.warp(startTime + 1); + + // First end sale + vm.prank(admin); + sale.endSale(); + + // Try to end sale again + vm.prank(admin); + vm.expectRevert(abi.encodeWithSelector(SealedBidTokenSale.SaleAlreadyEnded.selector, block.timestamp)); + sale.endSale(); + } + + function testEndSale_WithNoDeposits() public { + vm.warp(startTime + 1); + + // End sale with no deposits + vm.prank(admin); + sale.endSale(); + + assertTrue(sale.saleEnded()); + assertFalse(sale.capReached()); + assertEq(sale.totalDeposited(), 0); + } + + function testEndSale_WithMultipleDeposits() public { + vm.warp(startTime + 1); + + // Setup multiple deposits that sum to above minimum cap + uint256 aliceAmount = MIN_CAP / 2; + uint256 bobAmount = MIN_CAP / 2 + 1e6; // Slightly more to go over MIN_CAP + + // Alice's deposit + usdc.mint(alice, aliceAmount); + vm.prank(alice); + usdc.approve(address(sale), aliceAmount); + + vm.prank(alice); + sale.deposit(aliceAmount, maxPrice); + + // Bob's deposit + usdc.mint(bob, bobAmount); + vm.prank(bob); + usdc.approve(address(sale), bobAmount); + + vm.prank(bob); + sale.deposit(bobAmount, maxPrice); + + // End sale + vm.prank(admin); + sale.endSale(); + + assertTrue(sale.saleEnded()); + assertTrue(sale.capReached()); + assertEq(sale.totalDeposited(), aliceAmount + bobAmount); + } + + /* ============ Withdraw ============ */ + + function testWithdraw() public { + uint256 amount = 1000 * 1e6; + + // Setup failed sale + vm.warp(startTime + 1); + + usdc.mint(alice, amount); + vm.prank(alice); + usdc.approve(address(sale), amount); + + vm.prank(alice); + sale.deposit(amount, maxPrice); + + vm.warp(endTime); + vm.prank(admin); + sale.endSale(); + + vm.prank(alice); + sale.withdraw(); + + assertEq(sale.deposits(alice), 0); + assertEq(usdc.balanceOf(alice), amount); + } + + function testWithdraw_RevertWhen_SaleNotEnded() public { + // Setup deposit + vm.warp(startTime + 1); + + uint256 amount = 1000 * 1e6; + usdc.mint(alice, amount); + vm.prank(alice); + usdc.approve(address(sale), amount); + + vm.prank(alice); + sale.deposit(amount, maxPrice); + + // Attempt withdrawal before sale ends + vm.prank(alice); + vm.expectRevert(abi.encodeWithSelector(SealedBidTokenSale.SaleNotEnded.selector, block.timestamp)); + sale.withdraw(); + } + + function testWithdraw_RevertWhen_CapReached() public { + // Setup successful sale + vm.warp(startTime + 1); + + uint256 amount = MIN_CAP; // Use minimum cap to ensure success + usdc.mint(alice, amount); + vm.prank(alice); + usdc.approve(address(sale), amount); + + vm.prank(alice); + sale.deposit(amount, maxPrice); + + // End sale successfully + vm.prank(admin); + sale.endSale(); + + // Attempt withdrawal on successful sale + vm.prank(alice); + vm.expectRevert(SealedBidTokenSale.CapReached.selector); + sale.withdraw(); + } + + function testWithdraw_RevertWhen_NoDeposit() public { + vm.warp(startTime + 1); + + vm.prank(admin); + sale.endSale(); + + // Attempt withdrawal with no deposit + vm.prank(alice); + vm.expectRevert(abi.encodeWithSelector(SealedBidTokenSale.NothingToWithdraw.selector, alice)); + sale.withdraw(); + } + + function testWithdraw_MultipleUsers() public { + // Setup deposits for multiple users + vm.warp(startTime + 1); + uint256 aliceAmount = 1000 * 1e6; + uint256 bobAmount = 2000 * 1e6; + + // Setup and execute Alice's deposit + usdc.mint(alice, aliceAmount); + vm.prank(alice); + usdc.approve(address(sale), aliceAmount); + + vm.prank(alice); + sale.deposit(aliceAmount, maxPrice); + + // Setup and execute Bob's deposit + usdc.mint(bob, bobAmount); + vm.prank(bob); + usdc.approve(address(sale), bobAmount); + + vm.prank(bob); + sale.deposit(bobAmount, maxPrice); + + // End sale as failed + vm.warp(endTime); + vm.prank(admin); + sale.endSale(); + + // Execute withdrawals + vm.prank(alice); + sale.withdraw(); + + vm.prank(bob); + sale.withdraw(); + + // Verify final states + assertEq(sale.deposits(alice), 0); + assertEq(sale.deposits(bob), 0); + assertEq(usdc.balanceOf(alice), aliceAmount); + assertEq(usdc.balanceOf(bob), bobAmount); + assertEq(usdc.balanceOf(address(sale)), 0); + } + + function testWithdraw_RevertWhen_DoubleWithdraw() public { + // Setup deposit + vm.warp(startTime + 1); + + uint256 amount = 1000 * 1e6; + usdc.mint(alice, amount); + vm.prank(alice); + usdc.approve(address(sale), amount); + + vm.prank(alice); + sale.deposit(amount, maxPrice); + + // End sale as failed + vm.warp(endTime); + vm.prank(admin); + sale.endSale(); + + // First withdrawal (should succeed) + vm.prank(alice); + sale.withdraw(); + + // Second withdrawal attempt (should fail) + vm.prank(alice); + vm.expectRevert(abi.encodeWithSelector(SealedBidTokenSale.NothingToWithdraw.selector, alice)); + sale.withdraw(); + + // Verify final state + assertEq(sale.deposits(alice), 0); + assertEq(usdc.balanceOf(alice), amount); + } + + /* ============ claimTokens ============ */ + + function testClaimTokens() public { + vm.warp(startTime + 1); + + usdc.mint(alice, MAX_CAP); + saleToken.mint(address(sale), saleTokenAllocation); + + vm.prank(alice); + usdc.approve(address(sale), MAX_CAP); + + vm.prank(alice); + sale.deposit(MAX_CAP, maxPrice); + + vm.warp(endTime); + vm.prank(admin); + sale.endSale(); + + vm.prank(admin); + sale.setMerkleRoot(merkleRoot); + + sale.claimTokens(saleTokenAllocation, usdcAllocation, proof, alice); + + assertTrue(sale.hasClaimed(alice)); + assertEq(saleToken.balanceOf(alice), saleTokenAllocation); + assertEq(usdc.balanceOf(alice), usdcAllocation); + assertEq(saleToken.balanceOf(address(sale)), 0); + } + + function testClaimTokens_RevertWhen_SaleNotEnded() public { + // Sale not ended yet + vm.warp(startTime + 1); + + vm.expectRevert(abi.encodeWithSelector(SealedBidTokenSale.CapNotReached.selector)); + sale.claimTokens(saleTokenAllocation, usdcAllocation, proof, alice); + } + + function testClaimTokens_RevertWhen_SaleNotSuccessful() public { + // Setup failed sale (deposit less than min cap) + vm.warp(startTime + 1); + uint256 amount = MIN_CAP - 1e6; // Just under minimum cap + + usdc.mint(alice, amount); + vm.prank(alice); + usdc.approve(address(sale), amount); + + vm.prank(alice); + sale.deposit(amount, maxPrice); + + // End sale (will fail due to not meeting min cap) + vm.warp(endTime); + vm.prank(admin); + sale.endSale(); + + vm.expectRevert(abi.encodeWithSelector(SealedBidTokenSale.CapNotReached.selector)); + sale.claimTokens(saleTokenAllocation, usdcAllocation, proof, alice); + } + + function testClaimTokens_RevertWhen_MerkleRootNotSet() public { + // Setup successful sale + vm.warp(startTime + 1); + + usdc.mint(alice, MAX_CAP); + vm.prank(alice); + usdc.approve(address(sale), MAX_CAP); + + vm.prank(alice); + sale.deposit(MAX_CAP, maxPrice); + + vm.prank(admin); + sale.endSale(); + + // Attempt claim before merkle root is set + vm.expectRevert(abi.encodeWithSelector(SealedBidTokenSale.MerkleRootNotSet.selector)); + sale.claimTokens(saleTokenAllocation, usdcAllocation, proof, alice); + } + + function testClaimTokens_RevertWhen_InvalidProof() public { + // Setup successful sale + vm.warp(startTime + 1); + + usdc.mint(alice, MAX_CAP); + saleToken.mint(address(sale), saleTokenAllocation); + + vm.prank(alice); + usdc.approve(address(sale), MAX_CAP); + + vm.prank(alice); + sale.deposit(MAX_CAP, maxPrice); + + vm.warp(endTime); + vm.prank(admin); + sale.endSale(); + + vm.prank(admin); + sale.setMerkleRoot(merkleRoot); + + // Create invalid proof by using bob's proof for alice + bytes32[] memory invalidProof = buildProof( + new bytes32[](2), // Empty leaves will create invalid proof + 0 + ); + + bytes32 leaf = keccak256(bytes.concat(keccak256(abi.encode(alice, saleTokenAllocation, usdcAllocation)))); + + vm.expectRevert(abi.encodeWithSelector(SealedBidTokenSale.InvalidProof.selector, invalidProof, leaf)); + sale.claimTokens(saleTokenAllocation, usdcAllocation, invalidProof, alice); + } + + function testClaimTokens_RevertWhen_AlreadyClaimed() public { + // Setup successful sale + vm.warp(startTime + 1); + + usdc.mint(alice, MAX_CAP); + saleToken.mint(address(sale), saleTokenAllocation); + + vm.prank(alice); + usdc.approve(address(sale), MAX_CAP); + + vm.prank(alice); + sale.deposit(MAX_CAP, maxPrice); + + vm.warp(endTime); + vm.prank(admin); + sale.endSale(); + + vm.prank(admin); + sale.setMerkleRoot(merkleRoot); + + // First claim (should succeed) + sale.claimTokens(saleTokenAllocation, usdcAllocation, proof, alice); + + // Second claim attempt (should fail) + vm.expectRevert(abi.encodeWithSelector(SealedBidTokenSale.AlreadyClaimed.selector, alice)); + sale.claimTokens(saleTokenAllocation, usdcAllocation, proof, alice); + } + + function testClaimTokens_WhenZeroTokenAllocation() public { + // Setup successful sale + vm.warp(startTime + 1); + + usdc.mint(alice, MAX_CAP); + usdc.mint(address(sale), usdcAllocation); + + vm.prank(alice); + usdc.approve(address(sale), MAX_CAP); + + vm.prank(alice); + sale.deposit(MAX_CAP, maxPrice); + + vm.warp(endTime); + vm.prank(admin); + sale.endSale(); + + // Create merkle tree with zero token allocation + bytes32[] memory leaves = new bytes32[](2); + leaves[0] = keccak256(bytes.concat(keccak256(abi.encode(alice, 0, usdcAllocation)))); + leaves[1] = keccak256(bytes.concat(keccak256(abi.encode(bob, 0, 0)))); + + bytes32 newRoot = buildRoot(leaves); + bytes32[] memory zeroTokenProof = buildProof(leaves, 0); + + vm.prank(admin); + sale.setMerkleRoot(newRoot); + + // Claim with zero token allocation + uint256 initialBalance = saleToken.balanceOf(alice); + sale.claimTokens(0, usdcAllocation, zeroTokenProof, alice); + + // Verify only USDC was transferred + assertEq(saleToken.balanceOf(alice), initialBalance); + assertEq(usdc.balanceOf(alice), usdcAllocation); + assertTrue(sale.hasClaimed(alice)); + } + + function testClaimTokens_WhenZeroUSDCAllocation() public { + // Setup successful sale + vm.warp(startTime + 1); + + usdc.mint(alice, MAX_CAP); + saleToken.mint(address(sale), saleTokenAllocation); + + vm.prank(alice); + usdc.approve(address(sale), MAX_CAP); + vm.prank(alice); + sale.deposit(MAX_CAP, maxPrice); + + vm.warp(endTime); + vm.prank(admin); + sale.endSale(); + + // Create merkle tree with zero USDC allocation + bytes32[] memory leaves = new bytes32[](2); + leaves[0] = keccak256(bytes.concat(keccak256(abi.encode(alice, saleTokenAllocation, 0)))); + leaves[1] = keccak256(bytes.concat(keccak256(abi.encode(bob, 0, 0)))); + + bytes32 newRoot = buildRoot(leaves); + bytes32[] memory zeroUsdcProof = buildProof(leaves, 0); + + vm.prank(admin); + sale.setMerkleRoot(newRoot); + + // Claim with zero USDC allocation + uint256 initialUsdcBalance = usdc.balanceOf(alice); + sale.claimTokens(saleTokenAllocation, 0, zeroUsdcProof, alice); + + // Verify only tokens were transferred + assertEq(saleToken.balanceOf(alice), saleTokenAllocation); + assertEq(usdc.balanceOf(alice), initialUsdcBalance); + assertTrue(sale.hasClaimed(alice)); + } + + /* ============ setMerkleRoot ============ */ + + function testSetMerkleRoot() public { + // Setup successful sale first + vm.warp(startTime + 1); + + usdc.mint(alice, MIN_CAP); + vm.prank(alice); + usdc.approve(address(sale), MIN_CAP); + + vm.prank(alice); + sale.deposit(MIN_CAP, maxPrice); + + vm.prank(admin); + sale.endSale(); + + // Set merkle root + bytes32 newRoot = keccak256("newRoot"); + vm.prank(admin); + vm.expectEmit(false, false, false, true); + emit SealedBidTokenSale.MerkleRootSet(newRoot); + sale.setMerkleRoot(newRoot); + + assertEq(sale.merkleRoot(), newRoot); + } + + function testSetMerkleRoot_RevertWhen_SaleNotEnded() public { + // Try to set merkle root before sale ends + vm.warp(startTime + 1); + + bytes32 newRoot = keccak256("newRoot"); + vm.prank(admin); + vm.expectRevert(abi.encodeWithSelector(SealedBidTokenSale.CapNotReached.selector)); + sale.setMerkleRoot(newRoot); + } + + function testSetMerkleRoot_RevertWhen_CapNotReached() public { + // Setup failed sale (below MIN_CAP) + vm.warp(startTime + 1); + + uint256 amount = MIN_CAP - 1e6; // Just below minimum cap + usdc.mint(alice, amount); + vm.prank(alice); + usdc.approve(address(sale), amount); + + vm.prank(alice); + sale.deposit(amount, maxPrice); + + vm.prank(admin); + sale.endSale(); + + // Try to set merkle root + bytes32 newRoot = keccak256("newRoot"); + vm.prank(admin); + vm.expectRevert(abi.encodeWithSelector(SealedBidTokenSale.CapNotReached.selector)); + sale.setMerkleRoot(newRoot); + } + + function testSetMerkleRoot_RevertWhen_NotOwner() public { + // Setup successful sale + vm.warp(startTime + 1); + + usdc.mint(alice, MIN_CAP); + vm.prank(alice); + usdc.approve(address(sale), MIN_CAP); + vm.prank(alice); + sale.deposit(MIN_CAP, maxPrice); + + vm.prank(admin); + sale.endSale(); + + // Try to set merkle root from non-owner + bytes32 newRoot = keccak256("newRoot"); + vm.prank(alice); + vm.expectRevert(abi.encodeWithSelector(Ownable.OwnableUnauthorizedAccount.selector, alice)); + sale.setMerkleRoot(newRoot); + } + + function testSetMerkleRoot_UpdateExisting() public { + // Setup successful sale + vm.warp(startTime + 1); + + usdc.mint(alice, MIN_CAP); + vm.prank(alice); + usdc.approve(address(sale), MIN_CAP); + + vm.prank(alice); + sale.deposit(MIN_CAP, maxPrice); + + vm.prank(admin); + sale.endSale(); + + // Set initial root + bytes32 initialRoot = keccak256("initialRoot"); + vm.prank(admin); + sale.setMerkleRoot(initialRoot); + assertEq(sale.merkleRoot(), initialRoot); + + // Update to new root + bytes32 newRoot = keccak256("newRoot"); + vm.prank(admin); + sale.setMerkleRoot(newRoot); + assertEq(sale.merkleRoot(), newRoot); + } + + function testSetMerkleRoot_ZeroRoot() public { + // Setup successful sale + vm.warp(startTime + 1); + + usdc.mint(alice, MIN_CAP); + vm.prank(alice); + usdc.approve(address(sale), MIN_CAP); + vm.prank(alice); + sale.deposit(MIN_CAP, maxPrice); + + vm.prank(admin); + sale.endSale(); + + // Set zero merkle root + bytes32 zeroRoot = bytes32(0); + vm.prank(admin); + sale.setMerkleRoot(zeroRoot); + assertEq(sale.merkleRoot(), zeroRoot); + } + + /* ============ withdrawProceeds ============ */ + + function testWithdrawProceeds() public { + // Setup successful sale + vm.warp(startTime + 1); + + uint256 depositAmount = MIN_CAP; + usdc.mint(alice, depositAmount); + vm.prank(alice); + usdc.approve(address(sale), depositAmount); + + vm.prank(alice); + sale.deposit(depositAmount, maxPrice); + + vm.prank(admin); + sale.endSale(); + + // Check initial balances + uint256 initialTreasuryBalance = usdc.balanceOf(TREASURY); + usdc.balanceOf(address(sale)); + + // Withdraw proceeds + vm.prank(admin); + sale.withdrawProceeds(); + + // Verify balances after withdrawal + assertEq(usdc.balanceOf(TREASURY), initialTreasuryBalance + depositAmount); + assertEq(usdc.balanceOf(address(sale)), 0); + } + + function testWithdrawProceeds_RevertWhen_SaleNotEnded() public { + // Setup sale but don't end it + vm.warp(startTime + 1); + + uint256 depositAmount = MIN_CAP; + usdc.mint(alice, depositAmount); + vm.prank(alice); + usdc.approve(address(sale), depositAmount); + + vm.prank(alice); + sale.deposit(depositAmount, maxPrice); + + // Try to withdraw before ending sale + vm.prank(admin); + vm.expectRevert(abi.encodeWithSelector(SealedBidTokenSale.CapNotReached.selector)); + sale.withdrawProceeds(); + } + + function testWithdrawProceeds_RevertWhen_CapNotReached() public { + // Setup failed sale + vm.warp(startTime + 1); + + uint256 depositAmount = MIN_CAP - 1e6; // Just under minimum cap + usdc.mint(alice, depositAmount); + vm.prank(alice); + usdc.approve(address(sale), depositAmount); + vm.prank(alice); + sale.deposit(depositAmount, maxPrice); + + vm.prank(admin); + sale.endSale(); + + // Try to withdraw when cap not reached + vm.prank(admin); + vm.expectRevert(abi.encodeWithSelector(SealedBidTokenSale.CapNotReached.selector)); + sale.withdrawProceeds(); + } + + function testWithdrawProceeds_RevertWhen_NotOwner() public { + // Setup successful sale + vm.warp(startTime + 1); + + uint256 depositAmount = MIN_CAP; + usdc.mint(alice, depositAmount); + vm.prank(alice); + usdc.approve(address(sale), depositAmount); + vm.prank(alice); + sale.deposit(depositAmount, maxPrice); + + vm.prank(admin); + sale.endSale(); + + // Try to withdraw from non-owner account + vm.prank(alice); + vm.expectRevert(abi.encodeWithSelector(Ownable.OwnableUnauthorizedAccount.selector, alice)); + sale.withdrawProceeds(); + } + + function testWithdrawProceeds_MultipleDeposits() public { + // Setup successful sale with multiple deposits + vm.warp(startTime + 1); + + uint256 aliceAmount = MIN_CAP / 2; + uint256 bobAmount = MIN_CAP / 2 + 1e6; // Slightly more to go over MIN_CAP + uint256 totalAmount = aliceAmount + bobAmount; + + // Alice's deposit + usdc.mint(alice, aliceAmount); + vm.prank(alice); + usdc.approve(address(sale), aliceAmount); + vm.prank(alice); + sale.deposit(aliceAmount, maxPrice); + + // Bob's deposit + usdc.mint(bob, bobAmount); + vm.prank(bob); + usdc.approve(address(sale), bobAmount); + vm.prank(bob); + sale.deposit(bobAmount, maxPrice); + + vm.prank(admin); + sale.endSale(); + + // Check initial balances + uint256 initialTreasuryBalance = usdc.balanceOf(TREASURY); + + // Withdraw proceeds + vm.prank(admin); + sale.withdrawProceeds(); + + // Verify full amount was transferred + assertEq(usdc.balanceOf(TREASURY), initialTreasuryBalance + totalAmount); + assertEq(usdc.balanceOf(address(sale)), 0); + } + + function testWithdrawProceeds_MultipleTimes() public { + // Setup successful sale + vm.warp(startTime + 1); + + uint256 depositAmount = MIN_CAP; + usdc.mint(alice, depositAmount); + vm.prank(alice); + usdc.approve(address(sale), depositAmount); + vm.prank(alice); + sale.deposit(depositAmount, maxPrice); + + vm.prank(admin); + sale.endSale(); + + // First withdrawal + vm.prank(admin); + sale.withdrawProceeds(); + assertEq(usdc.balanceOf(TREASURY), depositAmount); + assertEq(usdc.balanceOf(address(sale)), 0); + + // Second withdrawal (should succeed but transfer 0) + vm.prank(admin); + sale.withdrawProceeds(); + assertEq(usdc.balanceOf(TREASURY), depositAmount); // Balance unchanged + assertEq(usdc.balanceOf(address(sale)), 0); + } + + function testWithdrawProceeds_ExactlyAtMinCap() public { + // Setup successful sale exactly at minimum cap + vm.warp(startTime + 1); + + uint256 depositAmount = MIN_CAP; + usdc.mint(alice, depositAmount); + vm.prank(alice); + usdc.approve(address(sale), depositAmount); + vm.prank(alice); + sale.deposit(depositAmount, maxPrice); + + vm.prank(admin); + sale.endSale(); + + uint256 initialTreasuryBalance = usdc.balanceOf(TREASURY); + + // Withdraw proceeds + vm.prank(admin); + sale.withdrawProceeds(); + + assertEq(usdc.balanceOf(TREASURY), initialTreasuryBalance + depositAmount); + assertEq(usdc.balanceOf(address(sale)), 0); + } + + /* ============ updateMaxPrice ============ */ + + function testUpdateMaxPrice_Timing() public { + // Should fail before sale starts + vm.expectRevert( + abi.encodeWithSelector(SealedBidTokenSale.SaleNotStarted.selector, block.timestamp, preStartTime) + ); + vm.prank(alice); + sale.updateMaxPrice(1e6); + + // Should work during sale + vm.warp(startTime + 1); + vm.prank(alice); + sale.updateMaxPrice(10e6); + assertEq(sale.maxPrices(alice), 10e6); + + // Should fail after sale ends + vm.prank(admin); + sale.endSale(); + + vm.expectRevert(abi.encodeWithSelector(SealedBidTokenSale.SaleAlreadyEnded.selector, block.timestamp)); + vm.prank(alice); + sale.updateMaxPrice(2e6); + } + + function testUpdateMaxPrice_StateChanges() public { + vm.warp(startTime + 1); + + // Initial state + assertEq(sale.maxPrices(alice), 0); + + // First update + uint256 firstPrice = 10e6; + vm.prank(alice); + sale.updateMaxPrice(firstPrice); + assertEq(sale.maxPrices(alice), firstPrice); + + // Update to higher price + uint256 higherPrice = 20e6; + vm.prank(alice); + sale.updateMaxPrice(higherPrice); + assertEq(sale.maxPrices(alice), higherPrice); + + // Update to lower price + uint256 lowerPrice = 15e6; + vm.prank(alice); + sale.updateMaxPrice(lowerPrice); + assertEq(sale.maxPrices(alice), lowerPrice); + + // Update to same price + vm.prank(alice); + sale.updateMaxPrice(lowerPrice); + assertEq(sale.maxPrices(alice), lowerPrice); + } + + function testUpdateMaxPrice_MultipleUsersIndependently() public { + vm.warp(startTime + 1); + + // Update prices for different users + vm.prank(alice); + sale.updateMaxPrice(10e6); + assertEq(sale.maxPrices(alice), 10e6); + + vm.prank(bob); + sale.updateMaxPrice(20e6); + assertEq(sale.maxPrices(bob), 20e6); + + // Verify changes don't affect other users + assertEq(sale.maxPrices(alice), 10e6); + assertEq(sale.maxPrices(bob), 20e6); + + // Update alice's price again + vm.prank(alice); + sale.updateMaxPrice(30e6); + assertEq(sale.maxPrices(alice), 30e6); + assertEq(sale.maxPrices(bob), 20e6); + } + + function testUpdateMaxPrice_WithDeposit() public { + vm.warp(startTime + 1); + + // Setup initial deposit with maxPrice + uint256 depositAmount = 1000 * 1e6; + uint256 initialMaxPrice = 10 * 1e6; + + usdc.mint(alice, depositAmount); + vm.prank(alice); + usdc.approve(address(sale), depositAmount); + + vm.prank(alice); + sale.deposit(depositAmount, initialMaxPrice); + assertEq(sale.maxPrices(alice), initialMaxPrice); + + // Update maxPrice after deposit + uint256 newMaxPrice = 30 * 1e6; + vm.prank(alice); + sale.updateMaxPrice(newMaxPrice); + assertEq(sale.maxPrices(alice), newMaxPrice); + + // Verify deposit amount remains unchanged + assertEq(sale.deposits(alice), depositAmount); + } + + function testUpdateMaxPrice_EventEmission() public { + vm.warp(startTime + 1); + + uint256 oldPrice = 0; // Initial price + uint256 newPrice = 10e6; + + vm.expectEmit(true, false, false, true); + emit SealedBidTokenSale.MaxPriceUpdated(alice, oldPrice, newPrice); + + vm.prank(alice); + sale.updateMaxPrice(newPrice); + } + + /* ============ saleStatus ============ */ + + function testSaleStatus_InitialState() public view { + // Check initial state + SealedBidTokenSale.SaleInfo memory info = sale.saleStatus(alice); + + assertEq(info.startTime, startTime, "Start time should match constructor"); + assertEq(info.minimumCap, MIN_CAP, "Minimum cap should match constructor"); + assertEq(info.totalDeposited, 0, "Total deposited should be 0"); + assertEq(info.totalWithdrawn, 0, "Total withdrawn should be 0"); + assertEq(info.totalUsdcClaimed, 0, "Total USDC claimed should be 0"); + assertEq(info.totalSaleTokenClaimed, 0, "Total sale token claimed should be 0"); + assertEq(info.saleEnded, false, "Sale should not be ended"); + assertEq(info.capReached, false, "Cap should not be reached"); + assertEq(info.hasClaimed, false, "User should not have claimed"); + assertEq(info.depositAmount, 0, "User deposit amount should be 0"); + assertEq(info.contributorCount, 0, "Contributor count should be 0"); + assertEq(info.maxPrice, 0, "maxPrice should be 0"); + } + + function testSaleStatus_AfterDeposit() public { + // Setup deposit + vm.warp(startTime + 1); + uint256 amount = 1000 * 1e6; + + usdc.mint(alice, amount); + vm.prank(alice); + usdc.approve(address(sale), amount); + + // Make deposit + vm.prank(alice); + sale.deposit(amount, maxPrice); + + // Check state after deposit + SealedBidTokenSale.SaleInfo memory info = sale.saleStatus(alice); + + assertEq(info.totalDeposited, amount, "Total deposited should match deposit"); + assertEq(info.depositAmount, amount, "User deposit should match deposit"); + assertEq(info.contributorCount, 1, "Contributor count should be 1"); + } + + function testSaleStatus_AfterWithdraw() public { + // Setup deposit + vm.warp(startTime + 1); + uint256 amount = 1000 * 1e6; + + usdc.mint(alice, amount); + vm.prank(alice); + usdc.approve(address(sale), amount); + + vm.prank(alice); + sale.deposit(amount, maxPrice); + + // End sale without reaching cap + vm.warp(endTime); + vm.prank(admin); + sale.endSale(); + + // Withdraw + vm.prank(alice); + sale.withdraw(); + + // Check state after withdrawal + SealedBidTokenSale.SaleInfo memory info = sale.saleStatus(alice); + + assertEq(info.totalWithdrawn, amount, "Total withdrawn should match deposit"); + assertEq(info.depositAmount, 0, "User deposit should be 0 after withdrawal"); + assertEq(info.saleEnded, true, "Sale should be ended"); + assertEq(info.maxPrice, maxPrice, "maxPrice should be maxPrice"); + } + + function testSaleStatus_AfterSuccessfulSaleAndClaim() public { + // Setup successful sale + vm.warp(startTime + 1); + + usdc.mint(alice, MAX_CAP); + saleToken.mint(address(sale), saleTokenAllocation); + + vm.prank(alice); + usdc.approve(address(sale), MAX_CAP); + + vm.prank(alice); + sale.deposit(MAX_CAP, maxPrice); + + // End sale + vm.warp(endTime); + vm.prank(admin); + sale.endSale(); + + // Set merkle root and claim + vm.prank(admin); + sale.setMerkleRoot(merkleRoot); + + sale.claimTokens(saleTokenAllocation, usdcAllocation, proof, alice); + + // Check state after claim + SealedBidTokenSale.SaleInfo memory info = sale.saleStatus(alice); + + assertEq(info.totalUsdcClaimed, usdcAllocation, "Total USDC claimed should match allocation"); + assertEq(info.totalSaleTokenClaimed, saleTokenAllocation, "Total sale token claimed should match allocation"); + assertEq(info.saleEnded, true, "Sale should be ended"); + assertEq(info.capReached, true, "Cap should be reached"); + assertEq(info.hasClaimed, true, "User should have claimed"); + assertEq(info.maxPrice, maxPrice, "maxPrice should be maxPrice"); + } + + function testSaleStatus_MultipleUsers() public { + // Setup deposits for multiple users + vm.warp(startTime + 1); + uint256 amount = 1000 * 1e6; + + // Setup Alice + usdc.mint(alice, amount); + vm.prank(alice); + usdc.approve(address(sale), amount); + vm.prank(alice); + sale.deposit(amount, maxPrice); + + // Setup Bob + usdc.mint(bob, amount); + vm.prank(bob); + usdc.approve(address(sale), amount); + vm.prank(bob); + sale.deposit(amount, maxPrice); + + // Check states for both users + SealedBidTokenSale.SaleInfo memory aliceInfo = sale.saleStatus(alice); + SealedBidTokenSale.SaleInfo memory bobInfo = sale.saleStatus(bob); + + assertEq(aliceInfo.totalDeposited, amount * 2, "Total deposited should include both deposits"); + assertEq(bobInfo.totalDeposited, amount * 2, "Total deposited should be same for all users"); + assertEq(aliceInfo.depositAmount, amount, "Alice deposit should match her deposit"); + assertEq(bobInfo.depositAmount, amount, "Bob deposit should match his deposit"); + assertEq(aliceInfo.contributorCount, 2, "Contributor count should be 2"); + assertEq(bobInfo.contributorCount, 2, "Contributor count should be same for all users"); + } + + /* ============ saleStatus ============ */ + + function testEmissaryDeposit_During_EarlyAccess() public { + // Set time to early access period + vm.warp(preStartTime + 1); + + uint256 amount = 1000 * 1e6; + uint256 initialEmissaryCount = sale.currentEmissaryCount(); + + // Setup deposit + usdc.mint(alice, amount); + vm.prank(alice); + usdc.approve(address(sale), amount); + + // Make deposit during early access + vm.prank(alice); + sale.deposit(amount, maxPrice); + + // Verify emissary status + assertTrue(sale.isEmissary(alice)); + assertEq(sale.currentEmissaryCount(), initialEmissaryCount + 1); + assertEq(sale.deposits(alice), amount); + } + + function testEmissaryDeposit_RevertWhen_MaxEmissariesReached() public { + // Set time to early access period + vm.warp(preStartTime + 1); + + uint256 amount = 1000 * 1e6; + + // Fill up emissary slots + for (uint256 i = 0; i < sale.MAX_EMISSARIES(); i++) { + address emissary = address(uint160(i + 1000)); // Generate unique addresses + + usdc.mint(emissary, amount); + vm.prank(emissary); + usdc.approve(address(sale), amount); + + vm.prank(emissary); + sale.deposit(amount, maxPrice); + } + + // Try to add one more emissary + usdc.mint(alice, amount); + vm.prank(alice); + usdc.approve(address(sale), amount); + + vm.expectRevert(SealedBidTokenSale.EmissaryFull.selector); + vm.prank(alice); + sale.deposit(amount, maxPrice); + } + + function testEmissaryDeposit_MultipleDeposits_SameEmissary() public { + // Set time to early access period + vm.warp(preStartTime + 1); + + uint256 amount = 1000 * 1e6; + uint256 initialEmissaryCount = sale.currentEmissaryCount(); + + // First deposit + usdc.mint(alice, amount * 2); + vm.prank(alice); + usdc.approve(address(sale), amount * 2); + + vm.prank(alice); + sale.deposit(amount, maxPrice); + + // Second deposit from same emissary + vm.prank(alice); + sale.deposit(amount, maxPrice); + + // Verify emissary count only increased once + assertTrue(sale.isEmissary(alice)); + assertEq(sale.currentEmissaryCount(), initialEmissaryCount + 1); + assertEq(sale.deposits(alice), amount * 2); + } + + function testDeposit_After_EmissaryPeriod() public { + // Set time after early access period + vm.warp(startTime + 1); + + uint256 amount = 1000 * 1e6; + + // Setup deposit + usdc.mint(alice, amount); + vm.prank(alice); + usdc.approve(address(sale), amount); + + // Make regular deposit after early access + vm.prank(alice); + sale.deposit(amount, maxPrice); + + // Verify not counted as emissary + assertFalse(sale.isEmissary(alice)); + assertEq(sale.currentEmissaryCount(), 0); + assertEq(sale.deposits(alice), amount); + } + + function testSaleStatus_EmissaryCount() public { + // Set time to early access period + vm.warp(preStartTime + 1); + + uint256 amount = 1000 * 1e6; + + // Add a few emissaries + for (uint256 i = 0; i < 3; i++) { + address emissary = address(uint160(i + 1000)); + + usdc.mint(emissary, amount); + vm.prank(emissary); + usdc.approve(address(sale), amount); + + vm.prank(emissary); + sale.deposit(amount, maxPrice); + } + + // Check emissary count in status + SealedBidTokenSale.SaleInfo memory info = sale.saleStatus(alice); + assertEq(info.currentEmissaryCount, 3); + } + + function testEmissaryDeposit_Boundaries() public { + uint256 amount = 1000 * 1e6; + + // Try just before preStartTime + vm.warp(preStartTime - 1); + usdc.mint(alice, amount); + vm.prank(alice); + usdc.approve(address(sale), amount); + + vm.expectRevert( + abi.encodeWithSelector(SealedBidTokenSale.SaleNotStarted.selector, preStartTime - 1, preStartTime) + ); + vm.prank(alice); + sale.deposit(amount, maxPrice); + + // Try at exactly preStartTime + vm.warp(preStartTime); + vm.prank(alice); + sale.deposit(amount, maxPrice); + assertTrue(sale.isEmissary(alice)); + + // Try just before startTime + vm.warp(startTime - 1); + usdc.mint(bob, amount); + vm.prank(bob); + usdc.approve(address(sale), amount); + + vm.prank(bob); + sale.deposit(amount, maxPrice); + assertTrue(sale.isEmissary(bob)); + + // Try at exactly startTime + vm.warp(startTime); + usdc.mint(address(0x123), amount); + vm.prank(address(0x123)); + usdc.approve(address(sale), amount); + + vm.prank(address(0x123)); + sale.deposit(amount, maxPrice); + assertFalse(sale.isEmissary(address(0x123))); + } +}