diff --git a/contracts/.env.example b/contracts/.env.example new file mode 100644 index 0000000..332d935 --- /dev/null +++ b/contracts/.env.example @@ -0,0 +1,3 @@ +DEPLOYER_ADDRESS= +DEPLOYER_PRIVATE_KEY= +RPC_ENDPOINT= \ No newline at end of file diff --git a/contracts/.gitignore b/contracts/.gitignore index eb5a316..a33c2fa 100644 --- a/contracts/.gitignore +++ b/contracts/.gitignore @@ -1 +1,3 @@ target +node_modules +.env \ No newline at end of file diff --git a/contracts/Scarb.lock b/contracts/Scarb.lock index e6cb110..f5e345f 100644 --- a/contracts/Scarb.lock +++ b/contracts/Scarb.lock @@ -1,6 +1,137 @@ # Code generated by scarb DO NOT EDIT. version = 1 +[[package]] +name = "openzeppelin" +version = "0.17.0" +source = "registry+https://scarbs.xyz/" +checksum = "sha256:7e77855aaba0825a2a12cad72d52d85380a9fab732007754b3c5d98908918ce7" +dependencies = [ + "openzeppelin_access", + "openzeppelin_account", + "openzeppelin_finance", + "openzeppelin_governance", + "openzeppelin_introspection", + "openzeppelin_merkle_tree", + "openzeppelin_presets", + "openzeppelin_security", + "openzeppelin_token", + "openzeppelin_upgrades", + "openzeppelin_utils", +] + +[[package]] +name = "openzeppelin_access" +version = "0.17.0" +source = "registry+https://scarbs.xyz/" +checksum = "sha256:541bb8fdf1ad17fe0d275b00acb9f0d7f56ea5534741e21535ac3fda2c600281" +dependencies = [ + "openzeppelin_introspection", + "openzeppelin_utils", +] + +[[package]] +name = "openzeppelin_account" +version = "0.17.0" +source = "registry+https://scarbs.xyz/" +checksum = "sha256:c4e11609fdd1f4c3d3004cd1468711bd2ea664739c9e59a4b270567fe4c23ee3" +dependencies = [ + "openzeppelin_introspection", + "openzeppelin_utils", +] + +[[package]] +name = "openzeppelin_finance" +version = "0.17.0" +source = "registry+https://scarbs.xyz/" +checksum = "sha256:9adcbec76ee8ed08be8d87c6af6014aa7080d67578816f5ba77f4376b25bc165" +dependencies = [ + "openzeppelin_access", + "openzeppelin_token", +] + +[[package]] +name = "openzeppelin_governance" +version = "0.17.0" +source = "registry+https://scarbs.xyz/" +checksum = "sha256:b7e0142d88d69a8c367aea8c9dc7f659f27372551efc23f39a0cf71a189c1302" +dependencies = [ + "openzeppelin_access", + "openzeppelin_introspection", +] + +[[package]] +name = "openzeppelin_introspection" +version = "0.17.0" +source = "registry+https://scarbs.xyz/" +checksum = "sha256:892433a4a1ea0fc9cf7cdb01e06ddc2782182abcc188e4ea5dd480906d006cf8" + +[[package]] +name = "openzeppelin_merkle_tree" +version = "0.17.0" +source = "registry+https://scarbs.xyz/" +checksum = "sha256:3c338fa07cbaba8034051a42967816800abe535ef7d709a929175616603dccf9" + +[[package]] +name = "openzeppelin_presets" +version = "0.17.0" +source = "registry+https://scarbs.xyz/" +checksum = "sha256:0a39e0effff133ab7fb003961ee2986438ee09b53608ce0d71aca24459879597" +dependencies = [ + "openzeppelin_access", + "openzeppelin_account", + "openzeppelin_finance", + "openzeppelin_introspection", + "openzeppelin_token", + "openzeppelin_upgrades", +] + +[[package]] +name = "openzeppelin_security" +version = "0.17.0" +source = "registry+https://scarbs.xyz/" +checksum = "sha256:6e2dee39d87f9ddec2ad37e33e80cf0d8b6c6927fd7950f220dbc2baea658d43" + +[[package]] +name = "openzeppelin_token" +version = "0.17.0" +source = "registry+https://scarbs.xyz/" +checksum = "sha256:77997a7e217b69674c34b402dc0c7b2210540db66a56087572679c31896eaabb" +dependencies = [ + "openzeppelin_account", + "openzeppelin_governance", + "openzeppelin_introspection", +] + +[[package]] +name = "openzeppelin_upgrades" +version = "0.17.0" +source = "registry+https://scarbs.xyz/" +checksum = "sha256:a0fa5934f2924e1e85ec8f8c5b7dcd95c25295c029d3a745ba87b3191146004d" + +[[package]] +name = "openzeppelin_utils" +version = "0.17.0" +source = "registry+https://scarbs.xyz/" +checksum = "sha256:36d93e353f42fd6b824abcd8b4b51c3f5d02c893c5f886ae81403b0368aa5fde" + +[[package]] +name = "snforge_scarb_plugin" +version = "0.35.1" +source = "git+https://github.com/foundry-rs/starknet-foundry.git?tag=v0.35.1#089de2c7a391372a7aaa54ec2706b117f955d06a" + +[[package]] +name = "snforge_std" +version = "0.35.1" +source = "git+https://github.com/foundry-rs/starknet-foundry.git?tag=v0.35.1#089de2c7a391372a7aaa54ec2706b117f955d06a" +dependencies = [ + "snforge_scarb_plugin", +] + [[package]] name = "win_saved" version = "0.1.0" +dependencies = [ + "openzeppelin", + "snforge_std", +] diff --git a/contracts/Scarb.toml b/contracts/Scarb.toml index 27d49c1..606aa8c 100644 --- a/contracts/Scarb.toml +++ b/contracts/Scarb.toml @@ -6,7 +6,8 @@ edition = "2024_07" # See more keys and their definitions at https://docs.swmansion.com/scarb/docs/reference/manifest.html [dependencies] -starknet = "2.8.2" +starknet = "2.8.5" +openzeppelin = "0.17.0" snforge_std = { git = "https://github.com/foundry-rs/starknet-foundry.git", tag = "v0.35.1" } [[target.starknet-contract]] diff --git a/contracts/package-lock.json b/contracts/package-lock.json new file mode 100644 index 0000000..30327a4 --- /dev/null +++ b/contracts/package-lock.json @@ -0,0 +1,504 @@ +{ + "name": "winsaved", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "winsaved", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "dotenv": "^16.4.7", + "starknet": "^6.11.0" + } + }, + "node_modules/@noble/curves": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.4.2.tgz", + "integrity": "sha512-TavHr8qycMChk8UwMld0ZDRvatedkzWfH8IiaeGCfymOP5i0hSCozz9vHOL0nkwk7HRMlFnAiKpS2jrUmSybcw==", + "dependencies": { + "@noble/hashes": "1.4.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/curves/node_modules/@noble/hashes": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.4.0.tgz", + "integrity": "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/hashes": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.7.0.tgz", + "integrity": "sha512-HXydb0DgzTpDPwbVeDGCG1gIu7X6+AuU6Zl6av/E/KG8LMsvPntvq+w17CHRpKBmN6Ybdrt1eP3k4cj8DJa78w==", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/base": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.9.tgz", + "integrity": "sha512-8YKhl8GHiNI/pU2VMaofa2Tor7PJRAjwQLBBuilkJ9L5+13yVbC7JO/wS7piioAvPSwR3JKM1IJ/u4xQzbcXKg==", + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/starknet": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@scure/starknet/-/starknet-1.0.0.tgz", + "integrity": "sha512-o5J57zY0f+2IL/mq8+AYJJ4Xpc1fOtDhr+mFQKbHnYFmm3WQrC+8zj2HEgxak1a+x86mhmBC1Kq305KUpVf0wg==", + "dependencies": { + "@noble/curves": "~1.3.0", + "@noble/hashes": "~1.3.3" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/starknet/node_modules/@noble/curves": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.3.0.tgz", + "integrity": "sha512-t01iSXPuN+Eqzb4eBX0S5oubSqXbK/xXa1Ne18Hj8f9pStxztHCE2gfboSp/dZRLSqfuLpRK2nDXDK+W9puocA==", + "dependencies": { + "@noble/hashes": "1.3.3" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/starknet/node_modules/@noble/hashes": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.3.tgz", + "integrity": "sha512-V7/fPHgl+jsVPXqqeOzT8egNj2iBIVt+ECeMMG8TdcnTikP3oaBtUVqpT/gYCR68aEBJSF+XbYUxStjbFMqIIA==", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@starknet-io/types-js": { + "version": "0.7.10", + "resolved": "https://registry.npmjs.org/@starknet-io/types-js/-/types-js-0.7.10.tgz", + "integrity": "sha512-1VtCqX4AHWJlRRSYGSn+4X1mqolI1Tdq62IwzoU2vUuEE72S1OlEeGhpvd6XsdqXcfHmVzYfj8k1XtKBQqwo9w==" + }, + "node_modules/abi-wan-kanabi": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/abi-wan-kanabi/-/abi-wan-kanabi-2.2.4.tgz", + "integrity": "sha512-0aA81FScmJCPX+8UvkXLki3X1+yPQuWxEkqXBVKltgPAK79J+NB+Lp5DouMXa7L6f+zcRlIA/6XO7BN/q9fnvg==", + "dependencies": { + "ansicolors": "^0.3.2", + "cardinal": "^2.1.1", + "fs-extra": "^10.0.0", + "yargs": "^17.7.2" + }, + "bin": { + "generate": "dist/generate.js" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/ansicolors": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/ansicolors/-/ansicolors-0.3.2.tgz", + "integrity": "sha512-QXu7BPrP29VllRxH8GwB7x5iX5qWKAAMLqKQGWTeLWVlNHNOpVMJ91dsxQAIWXpjuW5wqvxu3Jd/nRjrJ+0pqg==" + }, + "node_modules/cardinal": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/cardinal/-/cardinal-2.1.1.tgz", + "integrity": "sha512-JSr5eOgoEymtYHBjNWyjrMqet9Am2miJhlfKNdqLp6zoeAh0KN5dRAcxlecj5mAJrmQomgiOBj35xHLrFjqBpw==", + "dependencies": { + "ansicolors": "~0.3.2", + "redeyed": "~2.1.0" + }, + "bin": { + "cdl": "bin/cdl.js" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/dotenv": { + "version": "16.4.7", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz", + "integrity": "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "engines": { + "node": ">=6" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/fetch-cookie": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fetch-cookie/-/fetch-cookie-3.1.0.tgz", + "integrity": "sha512-s/XhhreJpqH0ftkGVcQt8JE9bqk+zRn4jF5mPJXWZeQMCI5odV9K+wEWYbnzFPHgQZlvPSMjS4n4yawWE8RINw==", + "dependencies": { + "set-cookie-parser": "^2.4.8", + "tough-cookie": "^5.0.0" + } + }, + "node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-starknet-core": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/get-starknet-core/-/get-starknet-core-4.0.0.tgz", + "integrity": "sha512-6pLmidQZkC3wZsrHY99grQHoGpuuXqkbSP65F8ov1/JsEI8DDLkhsAuLCKFzNOK56cJp+f1bWWfTJ57e9r5eqQ==", + "dependencies": { + "@starknet-io/types-js": "^0.7.7" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "engines": { + "node": ">=8" + } + }, + "node_modules/isomorphic-fetch": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-3.0.0.tgz", + "integrity": "sha512-qvUtwJ3j6qwsF3jLxkZ72qCgjMysPzDfeV240JHiGZsANBYd+EEuu35v7dfrJ9Up0Ak07D7GGSkGhCHTqg/5wA==", + "dependencies": { + "node-fetch": "^2.6.1", + "whatwg-fetch": "^3.4.1" + } + }, + "node_modules/jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/lossless-json": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/lossless-json/-/lossless-json-4.0.2.tgz", + "integrity": "sha512-+z0EaLi2UcWi8MZRxA5iTb6m4Ys4E80uftGY+yG5KNFJb5EceQXOhdW/pWJZ8m97s26u7yZZAYMcKWNztSZssA==" + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/pako": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz", + "integrity": "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==" + }, + "node_modules/redeyed": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/redeyed/-/redeyed-2.1.1.tgz", + "integrity": "sha512-FNpGGo1DycYAdnrKFxCMmKYgo/mILAqtRYbkdQD8Ep/Hk2PQ5+aEAEx+IU713RTDmuBaH0c8P5ZozurNu5ObRQ==", + "dependencies": { + "esprima": "~4.0.0" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/set-cookie-parser": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz", + "integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==" + }, + "node_modules/starknet": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/starknet/-/starknet-6.11.0.tgz", + "integrity": "sha512-u50KrGDi9fbu1Ogu7ynwF/tSeFlp3mzOg1/Y5x50tYFICImo3OfY4lOz9OtYDk404HK4eUujKkhov9tG7GAKlg==", + "dependencies": { + "@noble/curves": "~1.4.0", + "@noble/hashes": "^1.4.0", + "@scure/base": "~1.1.3", + "@scure/starknet": "~1.0.0", + "abi-wan-kanabi": "^2.2.2", + "fetch-cookie": "^3.0.0", + "get-starknet-core": "^4.0.0-next.3", + "isomorphic-fetch": "^3.0.0", + "lossless-json": "^4.0.1", + "pako": "^2.0.4", + "starknet-types-07": "npm:@starknet-io/types-js@^0.7.7", + "ts-mixer": "^6.0.3", + "url-join": "^4.0.1" + } + }, + "node_modules/starknet-types-07": { + "name": "@starknet-io/types-js", + "version": "0.7.10", + "resolved": "https://registry.npmjs.org/@starknet-io/types-js/-/types-js-0.7.10.tgz", + "integrity": "sha512-1VtCqX4AHWJlRRSYGSn+4X1mqolI1Tdq62IwzoU2vUuEE72S1OlEeGhpvd6XsdqXcfHmVzYfj8k1XtKBQqwo9w==" + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tldts": { + "version": "6.1.71", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.71.tgz", + "integrity": "sha512-LQIHmHnuzfZgZWAf2HzL83TIIrD8NhhI0DVxqo9/FdOd4ilec+NTNZOlDZf7EwrTNoutccbsHjvWHYXLAtvxjw==", + "dependencies": { + "tldts-core": "^6.1.71" + }, + "bin": { + "tldts": "bin/cli.js" + } + }, + "node_modules/tldts-core": { + "version": "6.1.71", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.71.tgz", + "integrity": "sha512-LRbChn2YRpic1KxY+ldL1pGXN/oVvKfCVufwfVzEQdFYNo39uF7AJa/WXdo+gYO7PTvdfkCPCed6Hkvz/kR7jg==" + }, + "node_modules/tough-cookie": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.1.0.tgz", + "integrity": "sha512-rvZUv+7MoBYTiDmFPBrhL7Ujx9Sk+q9wwm22x8c8T5IJaR+Wsyc7TNxbVxo84kZoRJZZMazowFLqpankBEQrGg==", + "dependencies": { + "tldts": "^6.1.32" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, + "node_modules/ts-mixer": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/ts-mixer/-/ts-mixer-6.0.4.tgz", + "integrity": "sha512-ufKpbmrugz5Aou4wcr5Wc1UUFWOLhq+Fm6qa6P0w0K5Qw2yhaUoiWszhCVuNQyNwrlGiscHOmqYoAox1PtvgjA==" + }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/url-join": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/url-join/-/url-join-4.0.1.tgz", + "integrity": "sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==" + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "node_modules/whatwg-fetch": { + "version": "3.6.20", + "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.20.tgz", + "integrity": "sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg==" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "engines": { + "node": ">=12" + } + } + } +} diff --git a/contracts/package.json b/contracts/package.json new file mode 100644 index 0000000..335f89d --- /dev/null +++ b/contracts/package.json @@ -0,0 +1,17 @@ +{ + "name": "winsaved", + "version": "1.0.0", + "description": "", + "main": "index.js", + "type": "module", + "scripts": { + "deploy": "node scripts/deploy.js" + }, + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": { + "dotenv": "^16.4.7", + "starknet": "^6.11.0" + } +} diff --git a/contracts/scripts/deploy.js b/contracts/scripts/deploy.js new file mode 100644 index 0000000..37d2b17 --- /dev/null +++ b/contracts/scripts/deploy.js @@ -0,0 +1,57 @@ +import { Account, CallData, Contract, RpcProvider, stark } from "starknet"; +import * as dotenv from "dotenv"; +import { getCompiledCode } from "./reader.js"; +dotenv.config(); + +async function main() { + const provider = new RpcProvider({ + nodeUrl: process.env.RPC_ENDPOINT, + }); + + // initialize existing predeployed account 0 + console.log("ACCOUNT_ADDRESS=", process.env.DEPLOYER_ADDRESS); + const privateKey0 = process.env.DEPLOYER_PRIVATE_KEY ?? ""; + const accountAddress0 = process.env.DEPLOYER_ADDRESS ?? ""; + const account0 = new Account(provider, accountAddress0, privateKey0); + console.log("Account connected.\n"); + + // Declare & deploy contract + let sierraCode, casmCode; + + try { + ({ sierraCode, casmCode } = await getCompiledCode( + "win_saved.WinSaved" + )); + } catch (error) { + console.log("Failed to read contract files"); + console.log(error); + process.exit(1); + } + + const myCallData = new CallData(sierraCode.abi); + + const constructor = myCallData.compile("constructor", {}); + + const deployResponse = await account0.declareAndDeploy({ + contract: sierraCode, + casm: casmCode, + constructorCalldata: constructor, + salt: stark.randomAddress(), + }); + + // Connect the new contract instance : + const votingContract = new Contract( + sierraCode.abi, + deployResponse.deploy.contract_address, + provider + ); + console.log( + `✅ Contract has been deploy with the address: ${votingContract.address}` + ); +} +main() + .then(() => process.exit(0)) + .catch((error) => { + console.error(error); + process.exit(1); + }); \ No newline at end of file diff --git a/contracts/scripts/reader.js b/contracts/scripts/reader.js new file mode 100644 index 0000000..19210f1 --- /dev/null +++ b/contracts/scripts/reader.js @@ -0,0 +1,33 @@ +import { promises as fs } from "fs"; +import path from "path"; +import { fileURLToPath } from 'url'; // To convert URL to file path +import { dirname } from 'path'; // To extract the directory name + +// Get the current module's URL and convert it to a file path +const __filename = fileURLToPath(import.meta.url); + +// Get the directory name from the file path +const __dirname = dirname(__filename); + +export async function getCompiledCode(filename) { + const sierraFilePath = path.join( + path.dirname(__filename), + `../target/dev/${filename}.contract_class.json` + ); + const casmFilePath = path.join( + path.dirname(__filename), + `../target/dev/${filename}.compiled_contract_class.json` + ); + + const code = [sierraFilePath, casmFilePath].map(async (filePath) => { + const file = await fs.readFile(filePath); + return JSON.parse(file.toString("ascii")); + }); + + const [sierraCode, casmCode] = await Promise.all(code); + + return { + sierraCode, + casmCode, + }; +} \ No newline at end of file diff --git a/contracts/src/interfaces/ierc20.cairo b/contracts/src/interfaces/ierc20.cairo new file mode 100644 index 0000000..64c9d9d --- /dev/null +++ b/contracts/src/interfaces/ierc20.cairo @@ -0,0 +1,19 @@ +use starknet::ContractAddress; + +#[starknet::interface] +pub trait IERC20 { + fn name(self: @TContractState) -> ByteArray; + fn symbol(self: @TContractState) -> ByteArray; + fn decimals(self: @TContractState) -> u8; + + fn total_supply(self: @TContractState) -> u256; + fn balance_of(self: @TContractState, account: ContractAddress) -> u256; + fn allowance(self: @TContractState, owner: ContractAddress, spender: ContractAddress) -> u256; + fn approve(ref self: TContractState, spender: ContractAddress, amount: u256) -> bool; + fn transfer(ref self: TContractState, recipient: ContractAddress, amount: u256) -> bool; + fn transfer_from( + ref self: TContractState, sender: ContractAddress, recipient: ContractAddress, amount: u256 + ) -> bool; + + fn mint(ref self: TContractState, recipient: ContractAddress, amount: u256) -> bool; +} diff --git a/contracts/src/interfaces/ivault.cairo b/contracts/src/interfaces/ivault.cairo new file mode 100644 index 0000000..4e10156 --- /dev/null +++ b/contracts/src/interfaces/ivault.cairo @@ -0,0 +1,23 @@ +use core::starknet::ContractAddress; +use core::starknet::ClassHash; +use win_saved::types::YieldSourceData; + +#[derive(Drop, Serde)] +pub struct WinnerStruct { + pub address: ContractAddress, + pub date: u64, + pub amount: u256 +} + +#[starknet::interface] +pub trait IVault { + fn deposit(ref self: TContractState, amount: u256); + fn withdraw(ref self: TContractState, amount: u256); + fn draw(ref self: TContractState, random_value: u32); + fn get_total_assets(self: @TContractState) -> u256; + fn get_yield_source_data(self: @TContractState) -> YieldSourceData; + fn get_recent_winners(self: @TContractState) -> Array; + fn pause(ref self: TContractState); + fn unpause(ref self: TContractState); + fn upgrade(ref self: TContractState, new_class_hash: ClassHash); +} diff --git a/contracts/src/lib.cairo b/contracts/src/lib.cairo index c55f19b..462a29e 100644 --- a/contracts/src/lib.cairo +++ b/contracts/src/lib.cairo @@ -1,25 +1,10 @@ -fn main() -> u32 { - fib(16) -} +pub mod types; -fn fib(mut n: u32) -> u32 { - let mut a: u32 = 0; - let mut b: u32 = 1; - while n != 0 { - n = n - 1; - let temp = b; - b = a + b; - a = temp; - }; - a +pub mod interfaces { + pub mod ivault; + pub mod ierc20; } -#[cfg(test)] -mod tests { - use super::fib; - #[test] - fn it_works() { - assert(fib(16) == 987, 'it works!'); - } -} +pub mod vault; + diff --git a/contracts/src/types.cairo b/contracts/src/types.cairo new file mode 100644 index 0000000..66cd115 --- /dev/null +++ b/contracts/src/types.cairo @@ -0,0 +1,13 @@ +use starknet::ContractAddress; + +#[derive(Copy, Drop, Serde)] +pub struct YieldSourceData { + pub lending_accumulator: felt252, + pub deposit_limit: felt252, +} + +#[derive(Copy, Drop, Serde)] +pub struct UserBalanceStruct { + pub address: ContractAddress, + pub amount: u256, +} diff --git a/contracts/src/vault.cairo b/contracts/src/vault.cairo new file mode 100644 index 0000000..22529ec --- /dev/null +++ b/contracts/src/vault.cairo @@ -0,0 +1,275 @@ +#[starknet::contract] +pub mod Vault { + use core::num::traits::Zero; + use core::starknet::{ + ContractAddress, get_caller_address, get_contract_address, get_block_timestamp, ClassHash + }; + use core::starknet::storage::{ + StoragePointerReadAccess, StoragePointerWriteAccess, StoragePathEntry ,Vec, VecTrait, MutableVecTrait + }; + use openzeppelin_access::ownable::OwnableComponent; + use openzeppelin_token::erc20::{ERC20Component, ERC20HooksEmptyImpl}; + use openzeppelin_security::{PausableComponent, ReentrancyGuardComponent}; + use openzeppelin_upgrades::upgradeable::UpgradeableComponent; + use win_saved::interfaces::{ + ivault::{IVault, WinnerStruct}, ierc20::{IERC20Dispatcher, IERC20DispatcherTrait} + }; + use win_saved::types::{YieldSourceData, UserBalanceStruct}; + + component!(path: ERC20Component, storage: erc20, event: ERC20Event); + component!(path: OwnableComponent, storage: ownable, event: OwnableEvent); + component!(path: PausableComponent, storage: pausable, event: PausableEvent); + component!( + path: ReentrancyGuardComponent, storage: reentrancyguard, event: ReentrancyGuardEvent + ); + component!(path: UpgradeableComponent, storage: upgradable, event: UpgradableEvent); + + #[abi(embed_v0)] + impl ERC20MixinImpl = ERC20Component::ERC20MixinImpl; + impl ERC20InternalImpl = ERC20Component::InternalImpl; + + impl OwnableInternalImpl = OwnableComponent::InternalImpl; + + #[abi(embed_v0)] + impl PausableImpl = PausableComponent::PausableImpl; + impl PausableInternalImpl = PausableComponent::InternalImpl; + + impl ReentrancyGuardInternalImpl = ReentrancyGuardComponent::InternalImpl; + + impl UpgradeableInternalImpl = UpgradeableComponent::InternalImpl; + + #[storage] + struct Storage { + yield_token: ContractAddress, + yield_source: ContractAddress, + winners: Vec, + #[substorage(v0)] + erc20: ERC20Component::Storage, + #[substorage(v0)] + ownable: OwnableComponent::Storage, + #[substorage(v0)] + pausable: PausableComponent::Storage, + #[substorage(v0)] + reentrancyguard: ReentrancyGuardComponent::Storage, + #[substorage(v0)] + upgradable: UpgradeableComponent::Storage, + } + + #[starknet::storage_node] + struct Winner { + address: ContractAddress, + amount: u256, + date: u64, + claimed: bool, + } + + #[event] + #[derive(Drop, starknet::Event)] + pub enum Event { + UserDepositedEvent: UserDepositedEvent, + UserWithdrawalEvent: UserWithdrawalEvent, + PrizeDrawEvent: PrizeDrawEvent, + UserWinEvent: UserWinEvent, + #[flat] + ERC20Event: ERC20Component::Event, + #[flat] + OwnableEvent: OwnableComponent::Event, + #[flat] + PausableEvent: PausableComponent::Event, + #[flat] + ReentrancyGuardEvent: ReentrancyGuardComponent::Event, + #[flat] + UpgradableEvent: UpgradeableComponent::Event, + } + + #[derive(Drop, starknet::Event)] + struct UserDepositedEvent { + #[key] + user: ContractAddress, + amount: u256, + date: u64, + } + #[derive(Drop, starknet::Event)] + struct UserWithdrawalEvent { + #[key] + user: ContractAddress, + amount: u256, + date: u64, + } + #[derive(Drop, starknet::Event)] + struct PrizeDrawEvent { + date: u64, + } + #[derive(Drop, starknet::Event)] + struct UserWinEvent { + #[key] + user: ContractAddress, + amount: u256, + date: u64, + } + + #[constructor] + fn constructor( + ref self: ContractState, yield_token: ContractAddress, yield_source: ContractAddress + ) { + let owner = get_caller_address(); + self.ownable.initializer(owner); + // get yield token name for creater vault token (share) name + let erc20_dispatcher = IERC20Dispatcher { contract_address: yield_token }; + let token_name = format!("WinSaved_{}", erc20_dispatcher.name()); + let token_symbol = format!("ws{}", erc20_dispatcher.symbol()); + self.yield_token.write(yield_token); + self.yield_source.write(yield_source); + self.erc20.initializer(token_name, token_symbol); + } + + #[abi(embed_v0)] + impl VaultImpl of IVault { + fn deposit(ref self: ContractState, amount: u256) { + // check paused + self.pausable.assert_not_paused(); + self.reentrancyguard.start(); + let caller = get_caller_address(); + let contract_address = get_contract_address(); + //transfer token from user + let erc20_address = self.yield_token.read(); + let erc20_dispatcher = IERC20Dispatcher { contract_address: erc20_address }; + erc20_dispatcher.transfer_from(caller, contract_address, amount); + // make call to yield source to deposit + // mint token + self.erc20.mint(caller, amount); + let current_date_time = get_block_timestamp(); + self.emit(UserDepositedEvent { user: caller, amount: amount, date: current_date_time }); + self.reentrancyguard.end(); + } + fn pause(ref self: ContractState) { + self.ownable.assert_only_owner(); + self.pausable.pause(); + } + fn unpause(ref self: ContractState) { + self.ownable.assert_only_owner(); + self.pausable.unpause(); + } + fn withdraw(ref self: ContractState, amount: u256) { + self.pausable.assert_not_paused(); + self.reentrancyguard.start(); + let caller = get_caller_address(); + let contract_address = get_contract_address(); + // get user vault token balance and assert if balance is equal to amount + let user_balance = self.erc20.balance_of(caller); + assert!(user_balance >= amount, "Insufficient balance"); + // make call to yield source to withdraw amount + //transfer token to user + let erc20_address = self.yield_token.read(); + let erc20_dispatcher = IERC20Dispatcher { contract_address: erc20_address }; + // burn token + self.erc20.burn(caller, amount); + erc20_dispatcher.transfer(caller, amount); + + let current_date_time = get_block_timestamp(); + self + .emit( + UserWithdrawalEvent { user: caller, amount: amount, date: current_date_time } + ); + self.reentrancyguard.end(); + } + fn draw(ref self: ContractState, random_value: u32) { + self.ownable.assert_only_owner(); + // check whether last draw time is greater than draw duration + + // check whether yield is available + let users = self.get_all_users(); + let sorted_holders = self.sort_users_by_balance(users); + // get the top half for randomization + let top_average: u32 = sorted_holders.len() / 2; + let mut top_average_holders: Array = array![]; + for idx in 0..top_average { + top_average_holders.append(*sorted_holders.at(idx)); + }; + + let rand_num = random_value % top_average_holders.len(); + let winner = *top_average_holders.at(rand_num.into()); + let win_amount = self.disperse_prize_to_winner(winner); + self.emit(PrizeDrawEvent { date: get_block_timestamp() }); + self + .emit( + UserWinEvent { user: winner, amount: win_amount, date: get_block_timestamp() } + ); + } + fn get_total_assets(self: @ContractState) -> u256 { + // access total assets form yield source + 10 + } + fn get_yield_source_data(self: @ContractState) -> YieldSourceData { + // access yield source connector interface + // change later when connector is created + YieldSourceData { lending_accumulator: 10, deposit_limit: 10, } + } + fn get_recent_winners(self: @ContractState) -> Array { + let mut winners_array: Array = array![]; + for index in 0 + ..self + .winners + .len() { + let current = self.winners.at(index); + winners_array + .append( + WinnerStruct { + address: current.address.read(), + amount: current.amount.read(), + date: current.date.read() + } + ); + }; + winners_array + } + fn upgrade(ref self: ContractState, new_class_hash: ClassHash) { + self.ownable.assert_only_owner(); + assert!(new_class_hash.is_non_zero(), "Zero ClassHash detected.. aborting"); + self.upgradable.upgrade(new_class_hash); + } + } + + #[generate_trait] + impl InternalImpl of InternalImplTrait { + fn get_all_users(self: @ContractState) -> Array { + // query event for all users that has deposited and check balance of vault token + // return users with balance greater than zero + let all_holders: Array = array![]; + all_holders + } + /// Sort users by vault token balance + fn sort_users_by_balance( + self: @ContractState, users: Array + ) -> Array { + let sorted_holders: Array = array![]; + sorted_holders + } + fn disperse_prize_to_winner(ref self: ContractState, user: ContractAddress) -> u256 { + // functionality to disperse yield to winner + // liquidate yield from yield source to contract and transfer to winner + let yield_amount: u256 = + 1000; // access yield source after liquidation for the exact amount + // transfer yield amount to winner via dispatcher + + // add winner to winner struct + self + .winners + .append() + .write( + Winner { + address: user, + amount: yield_amount, + date: get_block_timestamp(), + claimed: true + } + ); + let erc20_address = self.yield_token.read(); + let erc20_dispatcher = IERC20Dispatcher { contract_address: erc20_address }; + erc20_dispatcher.transfer(user, yield_amount); + + yield_amount + } + } +} +