diff --git a/ape-config.yaml b/ape-config.yaml index bfc2a8c..8680133 100644 --- a/ape-config.yaml +++ b/ape-config.yaml @@ -4,4 +4,15 @@ hardhat: fork: ethereum: mainnet: - upstream_provider: infura \ No newline at end of file + upstream_provider: infura + +hardhat: + fork: + ethereum: + mainnet: + upstream_provider: infura + +ethereum: + default_network: mainnet-fork + mainnet_fork: + default_provider: foundry \ No newline at end of file diff --git a/contracts/wbtc.json b/contracts/wbtc.json new file mode 100644 index 0000000..514a41f --- /dev/null +++ b/contracts/wbtc.json @@ -0,0 +1 @@ +[{"constant":true,"inputs":[],"name":"mintingFinished","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_value","type":"uint256"}],"name":"approve","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_token","type":"address"}],"name":"reclaimToken","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_from","type":"address"},{"name":"_to","type":"address"},{"name":"_value","type":"uint256"}],"name":"transferFrom","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"decimals","outputs":[{"name":"","type":"uint8"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"unpause","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_to","type":"address"},{"name":"_amount","type":"uint256"}],"name":"mint","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"value","type":"uint256"}],"name":"burn","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[],"name":"claimOwnership","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"paused","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_subtractedValue","type":"uint256"}],"name":"decreaseApproval","outputs":[{"name":"success","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_owner","type":"address"}],"name":"balanceOf","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"renounceOwnership","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[],"name":"finishMinting","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[],"name":"pause","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"owner","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"symbol","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_to","type":"address"},{"name":"_value","type":"uint256"}],"name":"transfer","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_addedValue","type":"uint256"}],"name":"increaseApproval","outputs":[{"name":"success","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_owner","type":"address"},{"name":"_spender","type":"address"}],"name":"allowance","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"pendingOwner","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"anonymous":false,"inputs":[],"name":"Pause","type":"event"},{"anonymous":false,"inputs":[],"name":"Unpause","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"burner","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Burn","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"to","type":"address"},{"indexed":false,"name":"amount","type":"uint256"}],"name":"Mint","type":"event"},{"anonymous":false,"inputs":[],"name":"MintFinished","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"previousOwner","type":"address"}],"name":"OwnershipRenounced","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"previousOwner","type":"address"},{"indexed":true,"name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"owner","type":"address"},{"indexed":true,"name":"spender","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"from","type":"address"},{"indexed":true,"name":"to","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Transfer","type":"event"}] \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index fe40610..54087c6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,15 +1,11 @@ { "name": "curve-leverage-bot-vyper", - "version": "1.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "curve-leverage-bot-vyper", - "version": "1.0.0", - "license": "ISC", "dependencies": { - "hardhat": "^2.19.1" + "hardhat": "^2.19.4" } }, "node_modules/@chainsafe/as-sha256": { @@ -1331,9 +1327,9 @@ } }, "node_modules/@scure/base": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.3.tgz", - "integrity": "sha512-/+SgoRjLq7Xlf0CWuLHq2LUZeL/w65kfzAPG5NH9pcmBhs+nunQTn4gvdwgMTIXnt9b2C/1SeL2XiysZEyIC9Q==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.5.tgz", + "integrity": "sha512-Brj9FiG2W1MRQSTB212YVPRrcbjkv48FoZi/u4l/zds/ieRrqsh7aUf6CLwkAq61oKXr/ZlTzlY66gLIj3TFTQ==", "funding": { "url": "https://paulmillr.com/funding/" } @@ -1478,9 +1474,9 @@ "integrity": "sha512-ssE3Vlrys7sdIzs5LOxCzTVMsU7i9oa/IaW92wF32JFb3CVczqOkru2xspuKczHEbG3nvmPY7IFqVmGGHdNbYw==" }, "node_modules/@types/node": { - "version": "20.10.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.10.0.tgz", - "integrity": "sha512-D0WfRmU9TQ8I9PFx9Yc+EBHw+vSpIub4IDvQivcp26PtPrdMGAq5SDcpXEo/epqa/DXotVpekHiLNTg3iaKXBQ==", + "version": "20.10.8", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.10.8.tgz", + "integrity": "sha512-f8nQs3cLxbAFc00vEU59yf9UyGUftkPaLGfvbVOIDdx2i1b8epBqj2aNGyP19fiyXWvlmZ7qC1XLjAzw/OKIeA==", "dependencies": { "undici-types": "~5.26.4" } @@ -2260,9 +2256,9 @@ } }, "node_modules/follow-redirects": { - "version": "1.15.3", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.3.tgz", - "integrity": "sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q==", + "version": "1.15.4", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.4.tgz", + "integrity": "sha512-Cr4D/5wlrb0z9dgERpUL3LrmPKVDsETIJhaCMeDfuFYcqa5bldGV6wBsAN6X/vxlXQtFBMrXdXxdL8CbDTGniw==", "funding": [ { "type": "individual", @@ -2363,9 +2359,9 @@ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" }, "node_modules/hardhat": { - "version": "2.19.1", - "resolved": "https://registry.npmjs.org/hardhat/-/hardhat-2.19.1.tgz", - "integrity": "sha512-bsWa63g1GB78ZyMN08WLhFElLPA+J+pShuKD1BFO2+88g3l+BL3R07vj9deIi9dMbssxgE714Gof1dBEDGqnCw==", + "version": "2.19.4", + "resolved": "https://registry.npmjs.org/hardhat/-/hardhat-2.19.4.tgz", + "integrity": "sha512-fTQJpqSt3Xo9Mn/WrdblNGAfcANM6XC3tAEi6YogB4s02DmTf93A8QsGb8uR0KR8TFcpcS8lgiW4ugAIYpnbrQ==", "dependencies": { "@ethersproject/abi": "^5.1.2", "@metamask/eth-sig-util": "^4.0.0", @@ -3159,9 +3155,9 @@ "integrity": "sha512-Ntyt4AIXyaLIuMHF6IOoTakB3K+RWxwtsHNRxllEoA6vPwP9o4866g6YWDLUdnucilZhmkxiHwHr11gAENw+QA==" }, "node_modules/node-gyp-build": { - "version": "4.7.1", - "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.7.1.tgz", - "integrity": "sha512-wTSrZ+8lsRRa3I3H8Xr65dLWSgCvY2l4AOnaeKdPA9TB/WYMPaTcrzf3rXvFoVvjKNVnu0CcWSx54qq9GKRUYg==", + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.0.tgz", + "integrity": "sha512-u6fs2AEUljNho3EYTJNBfImO5QTo/J/1Etd+NVdCj7qWKUSN/bSLkZwhDv7I+w/MSC6qJ4cknepkAYykDdK8og==", "bin": { "node-gyp-build": "bin.js", "node-gyp-build-optional": "optional.js", @@ -3746,9 +3742,9 @@ } }, "node_modules/undici": { - "version": "5.28.1", - "resolved": "https://registry.npmjs.org/undici/-/undici-5.28.1.tgz", - "integrity": "sha512-xcIIvj1LOQH9zAL54iWFkuDEaIVEjLrru7qRpa3GrEEHk6OBhb/LycuUY2m7VCcTuDeLziXCxobQVyKExyGeIA==", + "version": "5.28.2", + "resolved": "https://registry.npmjs.org/undici/-/undici-5.28.2.tgz", + "integrity": "sha512-wh1pHJHnUeQV5Xa8/kyQhO7WFa8M34l026L5P/+2TYiakvGy5Rdc8jWZVyG7ieht/0WgJLEd3kcU5gKx+6GC8w==", "dependencies": { "@fastify/busboy": "^2.0.0" }, diff --git a/package.json b/package.json index ac4ba79..c5d27dc 100644 --- a/package.json +++ b/package.json @@ -1,17 +1,5 @@ { - "name": "curve-leverage-bot-vyper", - "version": "1.0.0", - "description": "## factory.vy", - "main": "index.js", - "directories": { - "test": "tests" - }, - "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" - }, - "author": "", - "license": "ISC", "dependencies": { - "hardhat": "^2.19.1" + "hardhat": "^2.19.4" } } diff --git a/tests/conftest.py b/tests/conftest.py index ba3644c..c89c0c1 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,6 +1,8 @@ #!/usr/bin/python3 import pytest +from web3 import Web3 +from eth_abi import encode from typing import Union @@ -50,6 +52,11 @@ def USDC(project): return project.usdc.at("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48") +@pytest.fixture(scope="session") +def WBTC(project): + return project.wbtc.at("0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599") + + @pytest.fixture(scope="session") def UniswapV3Router(project): return project.uniswap_v3_router.at( @@ -73,12 +80,13 @@ def LeverageBotFactory( BotBlueprint, Compass, CurveRouter, RefundWallet, ServiceFeeCollector, Deployer, project): controller_factory = "0xC9332fdCB1C491Dcc683bAe86Fe3cb70360738BC" - gas_fee = 15000000000000000 # 0.015ETH + gas_fee = 25000000000000000 # 0.015ETH service_fee = 2000000000000000 # 0.2% - return Deployer.deploy( + factory_contract = Deployer.deploy( project.factory, BotBlueprint.contract_address, Compass, controller_factory, CurveRouter, RefundWallet, gas_fee, ServiceFeeCollector, service_fee) + return factory_contract def get_blueprint_initcode(initcode: Union[str, bytes]): @@ -90,3 +98,17 @@ def get_blueprint_initcode(initcode: Union[str, bytes]): b"\x3d\x81\x60\x0a\x3d\x39\xf3" + initcode ) return initcode + + +def set_paloma(project, factory_contract, Compass): + data = function_signature("set_paloma()") + bstring2bytes32(b"paloma") + tx = project.provider.network.ecosystem.create_transaction(chain_id=project.provider.chain_id, to=factory_contract.address, data=data) + Compass.call(tx) + + +def bstring2bytes32(str): + return encode(["bytes32"], [str]) + + +def function_signature(str): + return Web3.keccak(text=str)[:4] diff --git a/tests/test_leverage.py b/tests/test_leverage.py new file mode 100644 index 0000000..07d73bc --- /dev/null +++ b/tests/test_leverage.py @@ -0,0 +1,163 @@ +#!/usr/bin/python3 + +from conftest import function_signature, encode, bstring2bytes32, set_paloma +import ape + + +def test_set_paloma(project, LeverageBotFactory, Alice, Compass): + with ape.reverts(): + LeverageBotFactory.set_paloma(sender=Alice) + with ape.reverts(): + LeverageBotFactory.set_paloma(sender=Compass) + set_paloma(project, LeverageBotFactory, Compass) + + +def test_deposit(project, LeverageBotFactory, UniswapV3Router, WBTC, WETH, Alice, Compass): + set_paloma(project, LeverageBotFactory, Compass) + UniswapV3Router.exactInputSingle( + [WETH, WBTC, 3000, Alice, 2 ** 32, 10 ** 18, 2 * 10 ** 4, 0], + sender=Alice, + value=10 ** 18) + WBTC.approve(LeverageBotFactory, 2 * 10 ** 4, sender=Alice) + swap_infos = [[ + [ + '0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599', + '0x0000000000000000000000000000000000000000', + '0x0000000000000000000000000000000000000000', + '0x0000000000000000000000000000000000000000', + '0x0000000000000000000000000000000000000000', + '0x0000000000000000000000000000000000000000', + '0x0000000000000000000000000000000000000000', + '0x0000000000000000000000000000000000000000', + '0x0000000000000000000000000000000000000000', + '0x0000000000000000000000000000000000000000', + '0x0000000000000000000000000000000000000000', + ], + [ + [0, 0, 0, 0, 0], + [0, 0, 0, 0, 0], + [0, 0, 0, 0, 0], + [0, 0, 0, 0, 0], + [0, 0, 0, 0, 0], + ], + 20000, + 20000, + [ + '0x0000000000000000000000000000000000000000', + '0x0000000000000000000000000000000000000000', + '0x0000000000000000000000000000000000000000', + '0x0000000000000000000000000000000000000000', + '0x0000000000000000000000000000000000000000', + ]]] + collateral = '0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599' + debt = 30000000000000000000 + N = 10 + callbacker = '0xa2518b71ee64e910741f5cf480b19e8e402de4d7' + callback_args = [1, 10000] + leverage = 624 + deleverage_percentage = 100 + health_threshold = 206 + expire = 33241352537 + number_trades = 2 + interval = 30 + LeverageBotFactory.create_bot(swap_infos, collateral, debt, N, callbacker, callback_args, leverage, deleverage_percentage, health_threshold, expire, number_trades, interval, sender=Alice, value=5 * 10 ** 16) + + +def test_create_next_bot(project, LeverageBotFactory, UniswapV3Router, WBTC, WETH, Alice, Compass): + set_paloma(project, LeverageBotFactory, Compass) + UniswapV3Router.exactInputSingle( + [WETH, WBTC, 3000, Alice, 2 ** 32, 10 ** 18, 2 * 10 ** 4, 0], + sender=Alice, + value=10 ** 18) + WBTC.approve(LeverageBotFactory, 2 * 10 ** 4, sender=Alice) + swap_infos = [[ + [ + '0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599', + '0x0000000000000000000000000000000000000000', + '0x0000000000000000000000000000000000000000', + '0x0000000000000000000000000000000000000000', + '0x0000000000000000000000000000000000000000', + '0x0000000000000000000000000000000000000000', + '0x0000000000000000000000000000000000000000', + '0x0000000000000000000000000000000000000000', + '0x0000000000000000000000000000000000000000', + '0x0000000000000000000000000000000000000000', + '0x0000000000000000000000000000000000000000', + ], + [ + [0, 0, 0, 0, 0], + [0, 0, 0, 0, 0], + [0, 0, 0, 0, 0], + [0, 0, 0, 0, 0], + [0, 0, 0, 0, 0], + ], + 20000, + 20000, + [ + '0x0000000000000000000000000000000000000000', + '0x0000000000000000000000000000000000000000', + '0x0000000000000000000000000000000000000000', + '0x0000000000000000000000000000000000000000', + '0x0000000000000000000000000000000000000000', + ]]] + collateral = '0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599' + debt = 30000000000000000000 + N = 10 + callbacker = '0xa2518b71ee64e910741f5cf480b19e8e402de4d7' + callback_args = [1, 10000] + leverage = 624 + deleverage_percentage = 100 + health_threshold = 206 + expire = 33241352537 + number_trades = 2 + interval = 30 + LeverageBotFactory.create_bot(swap_infos, collateral, debt, N, callbacker, callback_args, leverage, deleverage_percentage, health_threshold, expire, number_trades, interval, sender=Alice, value=5 * 10 ** 16) + deposit_id = 0 + callbacker = '0xa2518b71ee64e910741f5cf480b19e8e402de4d7' + callback_args = [1, 10000] + remaining_count = 1 + data = function_signature("create_next_bot(uint256,address,uint256[],uint256)") + encode(["uint256", "address", "uint256[]", "uint256"], [deposit_id, callbacker, callback_args, remaining_count]) + bstring2bytes32(b"paloma") + tx = project.provider.network.ecosystem.create_transaction(chain_id=project.provider.chain_id, to=LeverageBotFactory.address, data=data) + Compass.call(tx) + + +def test_update_compass(project, LeverageBotFactory, Bob, Compass): + set_paloma(project, LeverageBotFactory, Compass) + data = LeverageBotFactory.update_compass.encode_input(Bob) + bstring2bytes32(b"paloma") + tx = project.provider.network.ecosystem.create_transaction(chain_id=project.provider.chain_id, to=LeverageBotFactory.address, data=data) + Compass.call(tx) + + +def test_update_blueprint(project, LeverageBotFactory, Bob, Compass): + set_paloma(project, LeverageBotFactory, Compass) + data = LeverageBotFactory.update_blueprint.encode_input(Bob) + bstring2bytes32(b"paloma") + tx = project.provider.network.ecosystem.create_transaction(chain_id=project.provider.chain_id, to=LeverageBotFactory.address, data=data) + Compass.call(tx) + + +def test_update_refund_wallet(project, LeverageBotFactory, Bob, Compass): + set_paloma(project, LeverageBotFactory, Compass) + data = LeverageBotFactory.update_refund_wallet.encode_input(Bob) + bstring2bytes32(b"paloma") + tx = project.provider.network.ecosystem.create_transaction(chain_id=project.provider.chain_id, to=LeverageBotFactory.address, data=data) + Compass.call(tx) + + +def test_update_gas_fee(project, LeverageBotFactory, Compass): + set_paloma(project, LeverageBotFactory, Compass) + data = LeverageBotFactory.update_gas_fee.encode_input(10 ** 16) + bstring2bytes32(b"paloma") + tx = project.provider.network.ecosystem.create_transaction(chain_id=project.provider.chain_id, to=LeverageBotFactory.address, data=data) + Compass.call(tx) + + +def test_update_service_fee_collector(project, LeverageBotFactory, Bob, Compass): + set_paloma(project, LeverageBotFactory, Compass) + data = LeverageBotFactory.update_service_fee_collector.encode_input(Bob) + bstring2bytes32(b"paloma") + tx = project.provider.network.ecosystem.create_transaction(chain_id=project.provider.chain_id, to=LeverageBotFactory.address, data=data) + Compass.call(tx) + + +def update_service_fee(project, LeverageBotFactory, Compass): + set_paloma(project, LeverageBotFactory, Compass) + data = LeverageBotFactory.update_service_fee.encode_input(10 ** 16) + bstring2bytes32(b"paloma") + tx = project.provider.network.ecosystem.create_transaction(chain_id=project.provider.chain_id, to=LeverageBotFactory.address, data=data) + Compass.call(tx)