├── tests ├── __init__.py ├── accounts │ ├── pass │ ├── 0_0x9596c16d7bf9323265c2f2e22f43e6c80eb3d943.json │ ├── 1_0xe415482ca06eeb684ad3f758c2129fca4b1eb1f4.json │ ├── 2_0x270b0e8d873e858abd698a000b0da0b94e21d84c.json │ ├── 3_0x812e87be5d4198fca55cb52fa60cb46620617474.json │ ├── 4_0x13314e21cd6d343ceb857073f3f6d9368919d1ef.json │ └── 5_0x176087fea5c41fc370fabbd850521bc4451690ca.json ├── abi │ ├── OasisMockPriceOracle.sol │ ├── OasisMockPriceOracle.abi │ ├── GemMock.sol │ ├── OasisMockPriceOracle.bin │ ├── DaiMock.abi │ └── DaiMock.sol ├── config │ └── keys │ │ └── UnlimitedChain │ │ ├── key1.json │ │ ├── key2.json │ │ ├── key3.json │ │ ├── key4.json │ │ └── key.json ├── test_cdpmanager.py ├── test_vault.py ├── test_model.py ├── helpers.py ├── test_sign.py ├── manual_test_dsr.py ├── manual_test_zrxv2.py ├── manual_test_cdpmanager.py ├── dss_token.py ├── manual_test_goerli.py ├── manual_test_tx_recovery.py ├── manual_test_node.py ├── test_savings.py ├── test_feed.py ├── test_auth.py ├── manual_test_mcd.py ├── manual_test_async_tx.py └── conftest.py ├── .python-version ├── pymaker ├── abi │ ├── ClipperCallee.bin │ ├── diff-abi.sh │ ├── ClipperCallee.abi │ ├── DSProxyCache.abi │ ├── ProxyRegistry.abi │ ├── DSProxyFactory.abi │ ├── DSAuth.abi │ ├── DssProxyActionsDsr.abi │ ├── DSProxyCache.bin │ ├── TxManager.abi │ ├── MakerOtcSupportMethods.abi │ ├── ERC20Token.abi │ ├── DSValue.abi │ ├── TokenTransferProxy.abi │ ├── ZRXToken.abi │ ├── ESM.abi │ ├── EtherToken.abi │ ├── DSProxy.abi │ ├── DSGuard.abi │ ├── DaiJoin.abi │ ├── TokenFaucet.abi │ ├── DsrManager.abi │ ├── Pit.abi │ ├── DSPause.abi │ ├── GemJoin.abi │ ├── GemJoin5.abi │ ├── DSEthToken.abi │ ├── SaiVox.abi │ ├── ProxyRegistry.bin │ ├── DSVault.abi │ ├── DSRoles.abi │ ├── Jug.abi │ ├── Spotter.abi │ ├── SaiTop.abi │ ├── Pot.abi │ ├── DSAuth.bin │ ├── ExchangeV2-ERC20Proxy.abi │ ├── OSM.abi │ ├── Cat.abi │ ├── SaiTap.abi │ └── Flapper.abi ├── tightly_packed.py ├── logging.py ├── vault.py ├── oracles.py ├── collateral.py ├── sign.py ├── model.py ├── keys.py ├── ilk.py ├── cdpmanager.py ├── dsrmanager.py └── auth.py ├── utils └── etherdelta-client │ ├── .gitignore │ ├── package.json │ └── main.js ├── requirements.txt ├── .gitignore ├── requirements-dev.txt ├── test.sh ├── Makefile ├── test-dss.sh ├── .github └── workflows │ └── tests.yaml ├── docker-compose.yml ├── setup.py ├── docs └── index.rst └── config └── testnet-addresses.json /tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.python-version: -------------------------------------------------------------------------------- 1 | 3.6.6 2 | -------------------------------------------------------------------------------- /tests/accounts/pass: -------------------------------------------------------------------------------- 1 | 12345678 -------------------------------------------------------------------------------- /pymaker/abi/ClipperCallee.bin: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /utils/etherdelta-client/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | pytz == 2017.3 2 | web3 == 5.12.0 3 | requests == 2.22.0 4 | eth-keys<0.3.0,>=0.2.1 5 | jsonnet == 0.9.5 6 | -------------------------------------------------------------------------------- /pymaker/abi/diff-abi.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # This script is useful when updating ABIs as newer contracts are released. 4 | vimdiff <(git show HEAD:pymaker/abi/$@ | jq '.') <(jq '.' < $@) 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | *.iml 3 | 4 | __pycache__ 5 | .cache 6 | .coverage 7 | .pytest_cache 8 | 9 | .DS_Store 10 | 11 | logs/*.json.log 12 | tests/config/keys/UnlimitedChain/address_book.json 13 | 14 | docs/_doc 15 | _virtualenv 16 | -------------------------------------------------------------------------------- /requirements-dev.txt: -------------------------------------------------------------------------------- 1 | attrs == 19.1.0 2 | codecov == 2.0.9 3 | mock == 2.0.0 4 | pytest == 3.3.0 5 | pytest-asyncio == 0.9.0 6 | pytest-cov == 2.5.1 7 | pytest-mock == 1.6.3 8 | pytest-timeout == 1.2.1 9 | asynctest == 0.13.0 10 | Sphinx == 1.6.2 11 | zipp == 3.4.1 12 | -------------------------------------------------------------------------------- /pymaker/abi/ClipperCallee.abi: -------------------------------------------------------------------------------- 1 | [{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bytes","name":"","type":"bytes"}],"name":"clipperCall","outputs":[],"stateMutability":"nonpayable","type":"function"}] 2 | -------------------------------------------------------------------------------- /tests/abi/OasisMockPriceOracle.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.12; 2 | 3 | contract OasisMockPriceOracle { 4 | uint256 price; 5 | function setPrice(address, uint256 _price) public { 6 | price = _price; 7 | } 8 | 9 | function getPriceFor(address, address, uint256) public view returns (uint256) { 10 | return price; 11 | } 12 | } -------------------------------------------------------------------------------- /pymaker/abi/DSProxyCache.abi: -------------------------------------------------------------------------------- 1 | [{"constant":false,"inputs":[{"name":"_code","type":"bytes"}],"name":"write","outputs":[{"name":"target","type":"address"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_code","type":"bytes"}],"name":"read","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"}] -------------------------------------------------------------------------------- /utils/etherdelta-client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "etherdelta-client", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "main.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "AGPL", 11 | "dependencies": { 12 | "minimist": "^1.2.0", 13 | "socket.io-client": "^2.0.4" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /tests/accounts/0_0x9596c16d7bf9323265c2f2e22f43e6c80eb3d943.json: -------------------------------------------------------------------------------- 1 | {"version":3,"id":"375c1ad3-b203-4e32-b32a-dd5cad819a4c","address":"9596c16d7bf9323265c2f2e22f43e6c80eb3d943","crypto":{"ciphertext":"4a24fa91bd5c9652ca0a3e9b03c8376d1c4cc1beda2641ea5e2642519b1614c6","cipherparams":{"iv":"39a62791ce1c0e80a3b8a159b1d1c2dd"},"cipher":"aes-128-ctr","kdf":"scrypt","kdfparams":{"dklen":32,"salt":"e1e7167139d333de7897971f90f6af69c3befe0a02604775c55d109e82659156","n":262144,"r":8,"p":1},"mac":"945369f4bf11492703ff7793038ab45f7f20a07f559be0b3721f159e8152d00e"}} -------------------------------------------------------------------------------- /tests/accounts/1_0xe415482ca06eeb684ad3f758c2129fca4b1eb1f4.json: -------------------------------------------------------------------------------- 1 | {"version":3,"id":"d2275fb9-533f-4c63-b970-9767bb45d38d","address":"e415482ca06eeb684ad3f758c2129fca4b1eb1f4","crypto":{"ciphertext":"9807f801eae14580b0641c6474e26e2ac2876f3903d0705965d2c5c5f1cfc4ab","cipherparams":{"iv":"ede22e7e1e3f74560328258df631f7d7"},"cipher":"aes-128-ctr","kdf":"scrypt","kdfparams":{"dklen":32,"salt":"9cdab7c0c88539b75946b03a0a3e3d637faf42cf0e99d92540d269b0bc358fa0","n":262144,"r":8,"p":1},"mac":"38c3c786a8b8d61d5169ae854b18c9e4968b6fb596028a71a78fab05477bf889"}} -------------------------------------------------------------------------------- /tests/accounts/2_0x270b0e8d873e858abd698a000b0da0b94e21d84c.json: -------------------------------------------------------------------------------- 1 | {"version":3,"id":"3088cf66-27a4-4da4-a5de-bb03fdfc1751","address":"270b0e8d873e858abd698a000b0da0b94e21d84c","crypto":{"ciphertext":"92da68991a680c24e806ce49bb3034c63c0597ef0e13b669c5d2bf3c4f8d4d03","cipherparams":{"iv":"758f2d23967c5d2a2b7e816c259647d5"},"cipher":"aes-128-ctr","kdf":"scrypt","kdfparams":{"dklen":32,"salt":"e5a6c8b70e5df30da1e53c075b243872c2cc404038a4ac76208aa481a629e377","n":262144,"r":8,"p":1},"mac":"c8316865a833729218c55fac5d709d3d2bfe37c893724c8fa879e9ef3abe0171"}} -------------------------------------------------------------------------------- /tests/accounts/3_0x812e87be5d4198fca55cb52fa60cb46620617474.json: -------------------------------------------------------------------------------- 1 | {"version":3,"id":"c0870e2f-9f3b-48b6-a670-cd056d3c6931","address":"812e87be5d4198fca55cb52fa60cb46620617474","crypto":{"ciphertext":"03a7f102806b520d17847f6dd75cd0b357fce4cd3097be0f3e40e93e55021b6d","cipherparams":{"iv":"fc81b85e0efded67ae9d6a90283f1886"},"cipher":"aes-128-ctr","kdf":"scrypt","kdfparams":{"dklen":32,"salt":"d7e85edd551f7d3fd836dfbd94430c78c2484e51eb5d1d314b3bba21a59a8694","n":262144,"r":8,"p":1},"mac":"6bc6b12ac5e9d2e61069aa76bda242fb2b07f08d07ccfc1437b318f5aaf61dd6"}} -------------------------------------------------------------------------------- /tests/accounts/4_0x13314e21cd6d343ceb857073f3f6d9368919d1ef.json: -------------------------------------------------------------------------------- 1 | {"version":3,"id":"f9455bbb-482b-46f7-9e29-503838c386be","address":"13314e21cd6d343ceb857073f3f6d9368919d1ef","crypto":{"ciphertext":"e16d398307f5cfc1331db001f1a9b0392eae1bb80299c3b7cf24fc56b3a24755","cipherparams":{"iv":"488f5f4c06909ccec258ff3aa9a40caf"},"cipher":"aes-128-ctr","kdf":"scrypt","kdfparams":{"dklen":32,"salt":"f054d8267209ef8e28ef30c3ed3bbb5291ae8738efd0448c7f8993f4c0505388","n":262144,"r":8,"p":1},"mac":"cf3a13bc8e6b6a4794f3890e350b972afe782fe092df7b99e443a34d18ad8ce8"}} -------------------------------------------------------------------------------- /tests/accounts/5_0x176087fea5c41fc370fabbd850521bc4451690ca.json: -------------------------------------------------------------------------------- 1 | {"version":3,"id":"aaf50a37-d4d1-41ef-ad91-ac5e20a0f5b4","address":"176087fea5c41fc370fabbd850521bc4451690ca","crypto":{"ciphertext":"e9429a2801c7a16ad339bd748fc352639e7380c72fa9bea984b2955664c4350d","cipherparams":{"iv":"5457ac288d8340c984f753e8f21f3bb5"},"cipher":"aes-128-ctr","kdf":"scrypt","kdfparams":{"dklen":32,"salt":"5eb1234e8d3ad07a3989c33f68378d7cffd314cee79f988d76623fe083f251f0","n":262144,"r":8,"p":1},"mac":"9470221ed35c56d8798fe238e276ab731b2f14f7671f69fcca16dfd9ccc8bccd"}} -------------------------------------------------------------------------------- /test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Pull the docker image 4 | docker pull makerdao/testchain-pymaker:unit-testing 5 | 6 | # Remove existing container if tests not gracefully stopped 7 | docker-compose down 8 | 9 | # Start ganache 10 | docker-compose up -d ganache 11 | 12 | # Start parity and wait to initialize 13 | docker-compose up -d parity 14 | sleep 2 15 | 16 | # Run the tests 17 | py.test --cov=pymaker --cov-report=term --cov-append tests/ $@ 18 | TEST_RESULT=$? 19 | 20 | # Cleanup 21 | docker-compose down 22 | 23 | exit $TEST_RESULT 24 | -------------------------------------------------------------------------------- /tests/config/keys/UnlimitedChain/key1.json: -------------------------------------------------------------------------------- 1 | {"id":"09068027-f04d-f8fb-79d9-9df6f03ac604","version":3,"crypto":{"cipher":"aes-128-ctr","cipherparams":{"iv":"4ac591b30b76366cf71de779fd32b5fe"},"ciphertext":"6eabb1137e388761f879d85f1f48ff48252ef657ba723e274b04c8b86c676f29","kdf":"pbkdf2","kdfparams":{"c":10240,"dklen":32,"prf":"hmac-sha256","salt":"e16ce22f791ffa5b8e9da1236e6c943e7988f82ba63a540892e12b5d874241bb"},"mac":"b81515fe8c2ccc669f35b859128bf431c100f6eeeb853f942c73bb7fba9db44c"},"address":"6c626f45e3b7ae5a3998478753634790fd0e82ee","name":"","meta":"{}"} -------------------------------------------------------------------------------- /tests/config/keys/UnlimitedChain/key2.json: -------------------------------------------------------------------------------- 1 | {"id":"6e7c69aa-e8d3-e381-ed85-36a08df7cfbe","version":3,"crypto":{"cipher":"aes-128-ctr","cipherparams":{"iv":"e19eee7d928a8d7df75bc58fd31ff05a"},"ciphertext":"b6f9bd7de52fbf59677de26a81b8a4b03bbf4eb0f849e126eae4a36b0ac5a676","kdf":"pbkdf2","kdfparams":{"c":10240,"dklen":32,"prf":"hmac-sha256","salt":"e9580e3282bcf4bf517d1eb78bf86b41e41bd9777f43afa517b2283c5a52ed9c"},"mac":"906d301903aae013fd98b184f153118a42af29e043865b2845e846eb5c5b4678"},"address":"50ff810797f75f6bfbf2227442e0c961a8562f4c","name":"","meta":"{}"} -------------------------------------------------------------------------------- /tests/config/keys/UnlimitedChain/key3.json: -------------------------------------------------------------------------------- 1 | {"id":"ecf98a52-7a41-0b18-a39a-e57ddc7a41d9","version":3,"crypto":{"cipher":"aes-128-ctr","cipherparams":{"iv":"c40c8be2726d367cdc885f59a1158488"},"ciphertext":"0ce8de6e23d2ebdb4ac559af6270e92a01668f11a20868caee672200d7f80c0c","kdf":"pbkdf2","kdfparams":{"c":10240,"dklen":32,"prf":"hmac-sha256","salt":"b69e7ff92bdb5e10963221ae4379f5a4bfc1e3856001e5a78cf26c1aaeea0d54"},"mac":"c067ac59f17e52586de59a9f4542c11149d84825e9b027f7145455124784337b"},"address":"5beb2d3aa2333a524703af18310acff462c04723","name":"","meta":"{}"} -------------------------------------------------------------------------------- /tests/config/keys/UnlimitedChain/key4.json: -------------------------------------------------------------------------------- 1 | {"id":"d2bed4ab-c313-7430-be81-8a0d04bfc9aa","version":3,"crypto":{"cipher":"aes-128-ctr","cipherparams":{"iv":"b7b2722e5e15d41ab83d0350fd72d6f6"},"ciphertext":"7dc899d78c010324b8600a729078d894517c28b5a4fef85e60d0d95c91d51a75","kdf":"pbkdf2","kdfparams":{"c":10240,"dklen":32,"prf":"hmac-sha256","salt":"fa15aef30d031c59d28794c1aec05797d47da1a5a0661d1a8e68841a613881b5"},"mac":"3f8c47c68a3c2428c44674e770dd1c16b47a968a2ec6f5f59901cdbbe994acb0"},"address":"57da1b8f38a5ecf91e9fee8a047df0f0a88716a1","name":"","meta":"{}"} -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | help: ## Show this help. 2 | @fgrep -h "##" $(MAKEFILE_LIST) | fgrep -v fgrep | sed -e 's/\\$$//' | sed -e 's/##//' 3 | 4 | doc-clean: ## Clean the documentation output directory 5 | cd docs; rm -rf _doc 6 | 7 | doc-build: doc-clean ## Build the documentation 8 | cd docs; sphinx-build . _doc 9 | 10 | doc-open: doc-build ## Open the documentation 11 | cd docs; open _doc/index.html 12 | 13 | doc-deploy: doc-build ## Deploy the documentation at http://maker-keeper-docs.surge.sh 14 | cd docs; surge --project _doc --domain maker-keeper-docs.surge.sh 15 | -------------------------------------------------------------------------------- /tests/abi/OasisMockPriceOracle.abi: -------------------------------------------------------------------------------- 1 | [{"constant":true,"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"getPriceFor","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"_price","type":"uint256"}],"name":"setPrice","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"}] 2 | -------------------------------------------------------------------------------- /tests/abi/GemMock.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.24; 2 | 3 | import "ds-token/token.sol"; 4 | 5 | contract GemMock is DSToken('') { 6 | 7 | constructor(bytes32 symbol_) public { 8 | symbol = symbol_; 9 | } 10 | 11 | function can(address src, address guy) public view returns (bool) { 12 | if (allowance(src, guy) > 0) { 13 | return true; 14 | } 15 | 16 | return false; 17 | } 18 | 19 | function push(bytes32 guy, uint wad) public { push(address(guy), wad); } 20 | function hope(address guy) public { approve(guy); } 21 | function nope(address guy) public { approve(guy, 0); } 22 | } 23 | -------------------------------------------------------------------------------- /tests/config/keys/UnlimitedChain/key.json: -------------------------------------------------------------------------------- 1 | {"id":"032b64c7-38c6-dfb4-714a-25eacd2daecc","version":3,"crypto":{"cipher":"aes-128-ctr","cipherparams":{"iv":"38f902a7336bf5160c427f499a6ded5c"},"ciphertext":"afcc67f82dd5327181a75cbd33319301575577f42aa42083f4f6620dcbb6fa60","kdf":"pbkdf2","kdfparams":{"c":10240,"dklen":32,"prf":"hmac-sha256","salt":"c7b2c641170cfb25a06c60e51d08bdf4e11d51acdfd65aaf832e2d7b13470812"},"mac":"dca205a615725a79073baec0e015a7a5c1d51c72d861081f2c26296abe7cec09"},"address":"00a329c0648769a73afac7f9381e08fb43dbea72","name":"Development Account","meta":"{\"description\":\"Never use this account outside of development chain!\",\"passwordHint\":\"Password is empty string\"}"} -------------------------------------------------------------------------------- /test-dss.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Pull the docker image 4 | docker pull makerdao/testchain-pymaker:unit-testing 5 | 6 | # Remove existing container if tests not gracefully stopped 7 | docker-compose down 8 | 9 | # Start ganache 10 | docker-compose up -d ganache 11 | 12 | # Start parity and wait to initialize 13 | docker-compose up -d parity 14 | sleep 2 15 | 16 | # Run the tests 17 | py.test --cov=pymaker --cov-report=term --cov-append tests/test_auctions.py tests/test_cdpmanager.py \ 18 | tests/test_dsrmanager.py tests/test_dss.py tests/test_savings.py tests/test_shutdown.py $@ 19 | TEST_RESULT=$? 20 | 21 | # Cleanup 22 | docker-compose down 23 | 24 | exit $TEST_RESULT 25 | -------------------------------------------------------------------------------- /tests/abi/OasisMockPriceOracle.bin: -------------------------------------------------------------------------------- 1 | 608060405234801561001057600080fd5b50610157806100206000396000f3fe608060405234801561001057600080fd5b50600436106100355760003560e01c8062e4768b1461003a578063413713c114610088575b600080fd5b6100866004803603604081101561005057600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff1690602001909291908035906020019092919050505061010a565b005b6100f46004803603606081101561009e57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190505050610115565b6040518082815260200191505060405180910390f35b806000819055505050565b600080549050939250505056fea265627a7a723158202ed147b02eb302020ecb419b6d41d429ebf14caf75ed2c3dfd1f1b7763abe66064736f6c634300050c0032 -------------------------------------------------------------------------------- /pymaker/abi/ProxyRegistry.abi: -------------------------------------------------------------------------------- 1 | [{"inputs":[{"internalType":"address","name":"factory_","type":"address"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"constant":false,"inputs":[],"name":"build","outputs":[{"internalType":"address payable","name":"proxy","type":"address"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"build","outputs":[{"internalType":"address payable","name":"proxy","type":"address"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"proxies","outputs":[{"internalType":"contract DSProxy","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"}] 2 | -------------------------------------------------------------------------------- /.github/workflows/tests.yaml: -------------------------------------------------------------------------------- 1 | on: 2 | # Trigger the workflow on push or pull request, 3 | # but only for the main branch 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | branches: 9 | - master 10 | 11 | jobs: 12 | build: 13 | runs-on: ubuntu-latest 14 | steps: 15 | 16 | - name: Checkout repository and submodules 17 | uses: actions/checkout@v3 18 | with: 19 | submodules: recursive 20 | 21 | - name: setup python 22 | uses: actions/setup-python@v4 23 | with: 24 | python-version: '3.8' 25 | 26 | - name: install python packages 27 | run: | 28 | python -m pip install --upgrade pip 29 | pip install virtualenv --upgrade 30 | pip install -r requirements.txt 31 | pip install -r requirements-dev.txt 32 | - name: execute tests 33 | run: ./test.sh 34 | -------------------------------------------------------------------------------- /pymaker/abi/DSProxyFactory.abi: -------------------------------------------------------------------------------- 1 | [{"constant":true,"inputs":[{"name":"","type":"address"}],"name":"isProxy","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"cache","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"build","outputs":[{"name":"proxy","type":"address"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"owner","type":"address"}],"name":"build","outputs":[{"name":"proxy","type":"address"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"anonymous":false,"inputs":[{"indexed":true,"name":"sender","type":"address"},{"indexed":true,"name":"owner","type":"address"},{"indexed":false,"name":"proxy","type":"address"},{"indexed":false,"name":"cache","type":"address"}],"name":"Created","type":"event"}] -------------------------------------------------------------------------------- /pymaker/abi/DSAuth.abi: -------------------------------------------------------------------------------- 1 | [{"constant":false,"inputs":[{"name":"owner_","type":"address"}],"name":"setOwner","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"authority_","type":"address"}],"name":"setAuthority","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":"authority","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"inputs":[],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"name":"authority","type":"address"}],"name":"LogSetAuthority","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"owner","type":"address"}],"name":"LogSetOwner","type":"event"}] -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3.2" 2 | services: 3 | parity: 4 | image: makerdao/testchain-pymaker:unit-testing-2.0.0 5 | container_name: parity-pymaker-test 6 | ports: 7 | - "8545:8545" 8 | - "8546:8546" 9 | expose: 10 | - "8545" 11 | - "8546" 12 | user: root 13 | working_dir: /home/parity 14 | 15 | ganache: 16 | image: trufflesuite/ganache-cli:v6.9.1 17 | container_name: ganache 18 | ports: 19 | - "8555:8555" 20 | expose: 21 | - "8555" 22 | command: "--gasLimit 10000000 23 | -p 8555 24 | --account=\"0x91cf2cc3671a365fcbf38010ff97ee31a5b7e674842663c56769e41600696ead,1000000000000000000000000\" 25 | --account=\"0xc0a550404067ce46a51283e0cc99ec3ba832940064587147a8db9a7ba355ef27,1000000000000000000000000\", 26 | --account=\"0x6ca1cfaba9715aa485504cb8a3d3fe54191e0991b5f47eb982e8fb40d1b8e8d8,1000000000000000000000000\", 27 | --account=\"0x1a9e422172e3d84487f7c833e3895f2f65c35eff7e68783adaa0c5bbe741ca8a,1000000000000000000000000\"" 28 | 29 | -------------------------------------------------------------------------------- /pymaker/abi/DssProxyActionsDsr.abi: -------------------------------------------------------------------------------- 1 | [{"constant":false,"inputs":[{"internalType":"address","name":"apt","type":"address"},{"internalType":"address","name":"urn","type":"address"},{"internalType":"uint256","name":"wad","type":"uint256"}],"name":"daiJoin_join","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"daiJoin","type":"address"},{"internalType":"address","name":"pot","type":"address"},{"internalType":"uint256","name":"wad","type":"uint256"}],"name":"exit","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"daiJoin","type":"address"},{"internalType":"address","name":"pot","type":"address"}],"name":"exitAll","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"daiJoin","type":"address"},{"internalType":"address","name":"pot","type":"address"},{"internalType":"uint256","name":"wad","type":"uint256"}],"name":"join","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"}] 2 | -------------------------------------------------------------------------------- /pymaker/tightly_packed.py: -------------------------------------------------------------------------------- 1 | # This file is part of Maker Keeper Framework. 2 | # 3 | # Copyright (C) 2017-2018 reverendus 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU Affero General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU Affero General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU Affero General Public License 16 | # along with this program. If not, see . 17 | 18 | from eth_abi.encoding import encode_uint_256, BytesEncoder, AddressEncoder 19 | 20 | from pymaker import Address 21 | 22 | 23 | def encode_address(address: Address) -> bytes: 24 | return AddressEncoder().encode(address.address)[12:] 25 | 26 | 27 | def encode_uint256(value: int) -> bytes: 28 | return encode_uint_256.encode(value) 29 | 30 | 31 | def encode_bytes(value: bytes) -> bytes: 32 | return BytesEncoder().encode(value) 33 | -------------------------------------------------------------------------------- /pymaker/abi/DSProxyCache.bin: -------------------------------------------------------------------------------- 1 | 608060405234801561001057600080fd5b5061029c806100206000396000f30060806040526004361061004b5763ffffffff7c01000000000000000000000000000000000000000000000000000000006000350416637ed0c3b281146100505780638bf4515c146100d2575b600080fd5b34801561005c57600080fd5b506040805160206004803580820135601f81018490048402850184019095528484526100a994369492936024939284019190819084018382808284375094975061012b9650505050505050565b6040805173ffffffffffffffffffffffffffffffffffffffff9092168252519081900360200190f35b3480156100de57600080fd5b506040805160206004803580820135601f81018490048402850184019095528484526100a99436949293602493928401919081908401838280828437509497506101e79650505050505050565b6000808251602084016000f09150813b156001811461004b5750826040518082805190602001908083835b602083106101755780518252601f199092019160209182019101610156565b51815160209384036101000a6000190180199092169116179052604080519290940182900390912060009081529081905291909120805473ffffffffffffffffffffffffffffffffffffffff191673ffffffffffffffffffffffffffffffffffffffff87161790555092949350505050565b600080826040518082805190602001908083835b6020831061021a5780518252601f1990920191602091820191016101fb565b51815160209384036101000a60001901801990921691161790526040805192909401829003909120600090815290819052919091205473ffffffffffffffffffffffffffffffffffffffff1696955050505050505600a165627a7a72305820105083b62f0f9cd6be84fcbd3c7208d0d48d7235d9538890eac2369f4864414d0029 -------------------------------------------------------------------------------- /pymaker/abi/TxManager.abi: -------------------------------------------------------------------------------- 1 | [{"constant":false,"inputs":[{"name":"owner_","type":"address"}],"name":"setOwner","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"tokens","type":"address[]"},{"name":"script","type":"bytes"}],"name":"execute","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"authority_","type":"address"}],"name":"setAuthority","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":"authority","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"anonymous":true,"inputs":[{"indexed":true,"name":"sig","type":"bytes4"},{"indexed":true,"name":"guy","type":"address"},{"indexed":true,"name":"foo","type":"bytes32"},{"indexed":true,"name":"bar","type":"bytes32"},{"indexed":false,"name":"wad","type":"uint256"},{"indexed":false,"name":"fax","type":"bytes"}],"name":"LogNote","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"authority","type":"address"}],"name":"LogSetAuthority","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"owner","type":"address"}],"name":"LogSetOwner","type":"event"}] -------------------------------------------------------------------------------- /tests/abi/DaiMock.abi: -------------------------------------------------------------------------------- 1 | [{"constant":true,"inputs":[],"name":"vat","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"},{"name":"","type":"address"}],"name":"can","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"guy","type":"address"}],"name":"hope","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"src","type":"address"},{"name":"dst","type":"address"},{"name":"wad","type":"uint256"}],"name":"move","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"guy","type":"address"}],"name":"nope","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"urn","type":"bytes32"},{"name":"wad","type":"uint256"}],"name":"join","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"guy","type":"address"},{"name":"wad","type":"uint256"}],"name":"exit","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"dai","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"inputs":[{"name":"vat_","type":"address"},{"name":"dai_","type":"address"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"}] -------------------------------------------------------------------------------- /pymaker/abi/MakerOtcSupportMethods.abi: -------------------------------------------------------------------------------- 1 | [{"constant":true,"inputs":[{"name":"otc","type":"address"},{"name":"payToken","type":"address"},{"name":"buyToken","type":"address"}],"name":"getOffers","outputs":[{"name":"ids","type":"uint256[100]"},{"name":"payAmts","type":"uint256[100]"},{"name":"buyAmts","type":"uint256[100]"},{"name":"owners","type":"address[100]"},{"name":"timestamps","type":"uint256[100]"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"otc","type":"address"},{"name":"buyToken","type":"address"},{"name":"buyAmt","type":"uint256"},{"name":"payToken","type":"address"}],"name":"getOffersAmountToBuyAll","outputs":[{"name":"ordersToTake","type":"uint256"},{"name":"takesPartialOrder","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"otc","type":"address"},{"name":"offerId","type":"uint256"}],"name":"getOffers","outputs":[{"name":"ids","type":"uint256[100]"},{"name":"payAmts","type":"uint256[100]"},{"name":"buyAmts","type":"uint256[100]"},{"name":"owners","type":"address[100]"},{"name":"timestamps","type":"uint256[100]"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"otc","type":"address"},{"name":"payToken","type":"address"},{"name":"payAmt","type":"uint256"},{"name":"buyToken","type":"address"}],"name":"getOffersAmountToSellAll","outputs":[{"name":"ordersToTake","type":"uint256"},{"name":"takesPartialOrder","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"}] -------------------------------------------------------------------------------- /pymaker/abi/ERC20Token.abi: -------------------------------------------------------------------------------- 1 | [{"constant":false,"inputs":[{"name":"guy","type":"address"},{"name":"wad","type":"uint256"}],"name":"approve","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"src","type":"address"},{"name":"dst","type":"address"},{"name":"wad","type":"uint256"}],"name":"transferFrom","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"src","type":"address"}],"name":"balanceOf","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"dst","type":"address"},{"name":"wad","type":"uint256"}],"name":"transfer","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"src","type":"address"},{"name":"guy","type":"address"}],"name":"allowance","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"inputs":[{"name":"supply","type":"uint256"}],"payable":false,"type":"constructor"},{"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"},{"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"}] -------------------------------------------------------------------------------- /pymaker/abi/DSValue.abi: -------------------------------------------------------------------------------- 1 | [{"constant":false,"inputs":[{"name":"owner_","type":"address"}],"name":"setOwner","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"wut","type":"bytes32"}],"name":"poke","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"read","outputs":[{"name":"","type":"bytes32"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"peek","outputs":[{"name":"","type":"bytes32"},{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"authority_","type":"address"}],"name":"setAuthority","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"owner","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":false,"inputs":[],"name":"void","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"authority","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"anonymous":true,"inputs":[{"indexed":true,"name":"sig","type":"bytes4"},{"indexed":true,"name":"guy","type":"address"},{"indexed":true,"name":"foo","type":"bytes32"},{"indexed":true,"name":"bar","type":"bytes32"},{"indexed":false,"name":"wad","type":"uint256"},{"indexed":false,"name":"fax","type":"bytes"}],"name":"LogNote","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"authority","type":"address"}],"name":"LogSetAuthority","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"owner","type":"address"}],"name":"LogSetOwner","type":"event"}] -------------------------------------------------------------------------------- /pymaker/abi/TokenTransferProxy.abi: -------------------------------------------------------------------------------- 1 | [{"constant":false,"inputs":[{"name":"token","type":"address"},{"name":"from","type":"address"},{"name":"to","type":"address"},{"name":"value","type":"uint256"}],"name":"transferFrom","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"target","type":"address"}],"name":"addAuthorizedAddress","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"","type":"uint256"}],"name":"authorities","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"target","type":"address"}],"name":"removeAuthorizedAddress","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"owner","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"}],"name":"authorized","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"getAuthorizedAddresses","outputs":[{"name":"","type":"address[]"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"payable":false,"type":"function"},{"anonymous":false,"inputs":[{"indexed":true,"name":"target","type":"address"},{"indexed":true,"name":"caller","type":"address"}],"name":"LogAuthorizedAddressAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"target","type":"address"},{"indexed":true,"name":"caller","type":"address"}],"name":"LogAuthorizedAddressRemoved","type":"event"}] -------------------------------------------------------------------------------- /tests/abi/DaiMock.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.24; 2 | 3 | // Fusion between a DaiJoin and a DaiMove 4 | 5 | contract GemLike { 6 | function transferFrom(address,address,uint) public returns (bool); 7 | function mint(address,uint) public; 8 | function burn(address,uint) public; 9 | } 10 | 11 | contract VatLike { 12 | function slip(bytes32,bytes32,int) public; 13 | function move(bytes32,bytes32,int) public; 14 | function flux(bytes32,bytes32,bytes32,int) public; 15 | } 16 | 17 | contract DaiMock { 18 | VatLike public vat; 19 | GemLike public dai; 20 | constructor(address vat_, address dai_) public { 21 | vat = VatLike(vat_); 22 | dai = GemLike(dai_); 23 | } 24 | uint constant ONE = 10 ** 27; 25 | function mul(uint x, uint y) internal pure returns (int z) { 26 | z = int(x * y); 27 | require(int(z) >= 0); 28 | require(y == 0 || uint(z) / y == x); 29 | } 30 | mapping(address => mapping (address => bool)) public can; 31 | function hope(address guy) public { can[msg.sender][guy] = true; } 32 | function nope(address guy) public { can[msg.sender][guy] = false; } 33 | function move(address src, address dst, uint wad) public { 34 | require(src == msg.sender || can[src][msg.sender]); 35 | vat.move(bytes32(src), bytes32(dst), mul(ONE, wad)); 36 | } 37 | function join(bytes32 urn, uint wad) public { 38 | vat.move(bytes32(address(this)), urn, mul(ONE, wad)); 39 | dai.burn(msg.sender, wad); 40 | } 41 | function exit(address guy, uint wad) public { 42 | vat.move(bytes32(msg.sender), bytes32(address(this)), mul(ONE, wad)); 43 | dai.mint(guy, wad); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /pymaker/abi/ZRXToken.abi: -------------------------------------------------------------------------------- 1 | [{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"","type":"string"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_value","type":"uint256"}],"name":"approve","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"name":"","type":"uint256"}],"payable":false,"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,"type":"function"},{"constant":true,"inputs":[],"name":"decimals","outputs":[{"name":"","type":"uint8"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_owner","type":"address"}],"name":"balanceOf","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"symbol","outputs":[{"name":"","type":"string"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_to","type":"address"},{"name":"_value","type":"uint256"}],"name":"transfer","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_owner","type":"address"},{"name":"_spender","type":"address"}],"name":"allowance","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"inputs":[],"payable":false,"type":"constructor"},{"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"},{"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"}] -------------------------------------------------------------------------------- /tests/test_cdpmanager.py: -------------------------------------------------------------------------------- 1 | # This file is part of Maker Keeper Framework. 2 | # 3 | # Copyright (C) 2020 EdNoepel 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU Affero General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU Affero General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU Affero General Public License 16 | # along with this program. If not, see . 17 | 18 | from pymaker import Address 19 | from pymaker.deployment import DssDeployment 20 | from pymaker.cdpmanager import Urn 21 | 22 | 23 | class TestCdpManager: 24 | 25 | def test_none(self, our_address: Address, mcd: DssDeployment): 26 | assert mcd.cdp_manager.first(our_address) == 0 27 | assert mcd.cdp_manager.last(our_address) == 0 28 | assert mcd.cdp_manager.count(our_address) == 0 29 | 30 | def test_open(self, our_address: Address, mcd: DssDeployment): 31 | ilk = mcd.collaterals['ETH-A'].ilk 32 | assert mcd.cdp_manager.open(ilk, our_address).transact() 33 | assert mcd.cdp_manager.last(our_address) == 1 34 | assert mcd.cdp_manager.ilk(1).name == ilk.name 35 | assert mcd.cdp_manager.owns(1) == our_address 36 | assert isinstance(mcd.cdp_manager.urn(1), Urn) 37 | 38 | def test_one(self, our_address: Address, mcd: DssDeployment): 39 | assert mcd.cdp_manager.first(our_address) == 1 40 | assert mcd.cdp_manager.last(our_address) == 1 41 | assert mcd.cdp_manager.count(our_address) == 1 42 | -------------------------------------------------------------------------------- /tests/test_vault.py: -------------------------------------------------------------------------------- 1 | # This file is part of Maker Keeper Framework. 2 | # 3 | # Copyright (C) 2017-2018 reverendus 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU Affero General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU Affero General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU Affero General Public License 16 | # along with this program. If not, see . 17 | 18 | import pytest 19 | from web3 import Web3, HTTPProvider 20 | 21 | from pymaker import Address 22 | from pymaker.vault import DSVault 23 | 24 | 25 | class TestDSVault: 26 | def setup_method(self): 27 | self.web3 = Web3(HTTPProvider("http://localhost:8555")) 28 | self.web3.eth.defaultAccount = self.web3.eth.accounts[0] 29 | self.our_address = Address(self.web3.eth.defaultAccount) 30 | self.dsvault = DSVault.deploy(self.web3) 31 | 32 | def test_fail_when_no_contract_under_that_address(self): 33 | # expect 34 | with pytest.raises(Exception): 35 | DSVault(web3=self.web3, address=Address('0xdeadadd1e5500000000000000000000000000000')) 36 | 37 | def test_authority(self): 38 | # given 39 | some_address = Address('0x0000000000111111111122222222223333333333') 40 | 41 | # when 42 | self.dsvault.set_authority(some_address).transact() 43 | 44 | # then 45 | assert self.dsvault.authority() == some_address 46 | 47 | def test_should_have_printable_representation(self): 48 | assert repr(self.dsvault) == f"DSVault('{self.dsvault.address}')" 49 | -------------------------------------------------------------------------------- /pymaker/abi/ESM.abi: -------------------------------------------------------------------------------- 1 | [{"inputs":[{"internalType":"address","name":"gem_","type":"address"},{"internalType":"address","name":"end_","type":"address"},{"internalType":"address","name":"proxy_","type":"address"},{"internalType":"uint256","name":"min_","type":"uint256"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[],"name":"Fire","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"usr","type":"address"},{"indexed":false,"internalType":"uint256","name":"wad","type":"uint256"}],"name":"Join","type":"event"},{"inputs":[],"name":"Sum","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"burn","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"target","type":"address"}],"name":"deny","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"end","outputs":[{"internalType":"contract EndLike","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"fire","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"gem","outputs":[{"internalType":"contract GemLike","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"wad","type":"uint256"}],"name":"join","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"min","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"proxy","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"revokesGovernanceAccess","outputs":[{"internalType":"bool","name":"ret","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"sum","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"}] 2 | -------------------------------------------------------------------------------- /pymaker/abi/EtherToken.abi: -------------------------------------------------------------------------------- 1 | [{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"","type":"string"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_value","type":"uint256"}],"name":"approve","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"name":"","type":"uint256"}],"payable":false,"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,"type":"function"},{"constant":false,"inputs":[{"name":"amount","type":"uint256"}],"name":"withdraw","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"decimals","outputs":[{"name":"","type":"uint8"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_owner","type":"address"}],"name":"balanceOf","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"symbol","outputs":[{"name":"","type":"string"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_to","type":"address"},{"name":"_value","type":"uint256"}],"name":"transfer","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":false,"inputs":[],"name":"deposit","outputs":[],"payable":true,"type":"function"},{"constant":true,"inputs":[{"name":"_owner","type":"address"},{"name":"_spender","type":"address"}],"name":"allowance","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"payable":true,"type":"fallback"},{"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"},{"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"}] -------------------------------------------------------------------------------- /tests/test_model.py: -------------------------------------------------------------------------------- 1 | # This file is part of Maker Keeper Framework. 2 | # 3 | # Copyright (C) 2020 EdNoepel 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU Affero General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU Affero General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU Affero General Public License 16 | # along with this program. If not, see . 17 | 18 | from pymaker import Address, Wad 19 | from pymaker.model import Token 20 | 21 | 22 | class TestToken: 23 | def setup_class(self): 24 | self.token = Token("COW", Address('0xbeef00000000000000000000000000000000BEEF'), 4) 25 | 26 | def test_convert(self): 27 | # two 28 | chain_amount = Wad(20000) 29 | assert self.token.normalize_amount(chain_amount) == Wad.from_number(2) 30 | 31 | # three 32 | normalized_amount = Wad.from_number(3) 33 | assert self.token.unnormalize_amount(normalized_amount) == Wad(30000) 34 | 35 | def test_min_amount(self): 36 | assert self.token.min_amount == Wad.from_number(0.0001) 37 | assert float(self.token.min_amount) == 0.0001 38 | assert self.token.unnormalize_amount(self.token.min_amount) == Wad(1) 39 | 40 | assert Wad.from_number(0.0004) > self.token.min_amount 41 | assert Wad.from_number(0.00005) < self.token.min_amount 42 | 43 | assert self.token.unnormalize_amount(Wad.from_number(0.0006)) > self.token.unnormalize_amount(self.token.min_amount) 44 | assert self.token.unnormalize_amount(Wad.from_number(0.00007)) < self.token.unnormalize_amount(self.token.min_amount) 45 | assert self.token.unnormalize_amount(Wad.from_number(0.00008)) == Wad(0) 46 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | """A setuptools based setup module. 2 | 3 | See: 4 | https://packaging.python.org/guides/distributing-packages-using-setuptools/ 5 | https://github.com/pypa/sampleproject 6 | https://github.com/pypa/sampleproject/blob/master/setup.py 7 | """ 8 | 9 | # Always prefer setuptools over distutils 10 | from setuptools import setup, find_packages 11 | from os import path 12 | 13 | here = path.abspath(path.dirname(__file__)) 14 | 15 | # Get the long description from the README file 16 | with open(path.join(here, 'README.md'), encoding='utf-8') as f: 17 | long_description = f.read() 18 | 19 | # Read requirements.txt 20 | with open(path.join(here, 'requirements.txt'), encoding='utf-8') as f: 21 | requirements = f.read().split('\n') 22 | 23 | # Arguments marked as "Required" below must be included for upload to PyPI. 24 | # Fields marked as "Optional" may be commented out. 25 | 26 | setup( 27 | name='pymaker', 28 | 29 | # Versions should comply with PEP 440: 30 | # https://www.python.org/dev/peps/pep-0440/ 31 | # 32 | # For a discussion on single-sourcing the version across setup.py and the 33 | # project code, see 34 | # https://packaging.python.org/en/latest/single_source_version.html 35 | version='1.1.3', # Required 36 | description='Python API for Maker contracts', 37 | license='COPYING', 38 | long_description=long_description, 39 | long_description_content_type='text/markdown', 40 | url='https://github.com/makerdao/pymaker', 41 | author='MakerDAO', 42 | packages=find_packages(include=['pymaker', 'pymaker.*']), # Required 43 | package_data={'pymaker': ['abi/*', '../config/*']}, 44 | include_package_data=True, 45 | python_requires='~=3.6', 46 | 47 | # This field lists other packages that your project depends on to run. 48 | # Any package you put here will be installed by pip when your project is 49 | # installed, so they must be valid existing projects. 50 | # 51 | # For an analysis of "install_requires" vs pip's requirements files see: 52 | # https://packaging.python.org/en/latest/requirements.html 53 | install_requires=requirements 54 | ) 55 | -------------------------------------------------------------------------------- /pymaker/abi/DSProxy.abi: -------------------------------------------------------------------------------- 1 | [{"constant":false,"inputs":[{"name":"owner_","type":"address"}],"name":"setOwner","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_target","type":"address"},{"name":"_data","type":"bytes"}],"name":"execute","outputs":[{"name":"response","type":"bytes32"}],"payable":true,"stateMutability":"payable","type":"function"},{"constant":false,"inputs":[{"name":"_code","type":"bytes"},{"name":"_data","type":"bytes"}],"name":"execute","outputs":[{"name":"target","type":"address"},{"name":"response","type":"bytes32"}],"payable":true,"stateMutability":"payable","type":"function"},{"constant":true,"inputs":[],"name":"cache","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"authority_","type":"address"}],"name":"setAuthority","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"owner","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_cacheAddr","type":"address"}],"name":"setCache","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"authority","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"inputs":[{"name":"_cacheAddr","type":"address"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"payable":true,"stateMutability":"payable","type":"fallback"},{"anonymous":true,"inputs":[{"indexed":true,"name":"sig","type":"bytes4"},{"indexed":true,"name":"guy","type":"address"},{"indexed":true,"name":"foo","type":"bytes32"},{"indexed":true,"name":"bar","type":"bytes32"},{"indexed":false,"name":"wad","type":"uint256"},{"indexed":false,"name":"fax","type":"bytes"}],"name":"LogNote","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"authority","type":"address"}],"name":"LogSetAuthority","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"owner","type":"address"}],"name":"LogSetOwner","type":"event"}] -------------------------------------------------------------------------------- /pymaker/abi/DSGuard.abi: -------------------------------------------------------------------------------- 1 | [{"constant":false,"inputs":[{"name":"owner_","type":"address"}],"name":"setOwner","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"src","type":"address"},{"name":"dst","type":"address"},{"name":"sig","type":"bytes32"}],"name":"forbid","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"src","type":"bytes32"},{"name":"dst","type":"bytes32"},{"name":"sig","type":"bytes32"}],"name":"forbid","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"authority_","type":"address"}],"name":"setAuthority","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"owner","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"ANY","outputs":[{"name":"","type":"bytes32"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"src_","type":"address"},{"name":"dst_","type":"address"},{"name":"sig","type":"bytes4"}],"name":"canCall","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"authority","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"src","type":"address"},{"name":"dst","type":"address"},{"name":"sig","type":"bytes32"}],"name":"permit","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"src","type":"bytes32"},{"name":"dst","type":"bytes32"},{"name":"sig","type":"bytes32"}],"name":"permit","outputs":[],"payable":false,"type":"function"},{"anonymous":false,"inputs":[{"indexed":true,"name":"src","type":"bytes32"},{"indexed":true,"name":"dst","type":"bytes32"},{"indexed":true,"name":"sig","type":"bytes32"}],"name":"LogPermit","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"src","type":"bytes32"},{"indexed":true,"name":"dst","type":"bytes32"},{"indexed":true,"name":"sig","type":"bytes32"}],"name":"LogForbid","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"authority","type":"address"}],"name":"LogSetAuthority","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"owner","type":"address"}],"name":"LogSetOwner","type":"event"}] -------------------------------------------------------------------------------- /utils/etherdelta-client/main.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * This file is part of Maker Keeper Framework. 3 | * 4 | * Copyright (C) 2017-2018 reverendus 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Affero General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Affero General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | var args = require('minimist')(process.argv.slice(2)); 21 | const order = args['_'].join(" "); 22 | const url = args['url']; 23 | const retryInterval = args['retry-interval']; 24 | const timeout = args['timeout']; 25 | 26 | function publishOrder() { 27 | socket.emit('message', JSON.parse(order)); 28 | console.log('Order sent'); 29 | } 30 | 31 | console.log("Sending order '" + order + "' to " + url); 32 | 33 | const io = require('socket.io-client'); 34 | const socket = io.connect(url, { transports: ['websocket'] }); 35 | 36 | socket.on('connect', () => { 37 | console.log("Connected to socket"); 38 | publishOrder(); 39 | }); 40 | 41 | socket.on('messageResult', (messageResult) => { 42 | console.log("Response received: ", messageResult); 43 | 44 | if (messageResult[0] === 'Added/updated order.') { 45 | console.log("Order placed successfully"); 46 | socket.disconnect(); 47 | setTimeout(() => process.exit(0), 2500); 48 | } 49 | else { 50 | console.log("Order placement failed"); 51 | setTimeout(publishOrder, retryInterval*1000); 52 | } 53 | }); 54 | 55 | 56 | socket.on('disconnect', () => { 57 | console.log('Disconnected from socket'); 58 | }); 59 | 60 | socket.on('reconnect', () => { 61 | console.log('Reconnected to socket'); 62 | }); 63 | 64 | setTimeout(() => { 65 | console.log('Timed out'); 66 | process.exit(-1); 67 | }, timeout*1000); 68 | -------------------------------------------------------------------------------- /tests/helpers.py: -------------------------------------------------------------------------------- 1 | # This file is part of Maker Keeper Framework. 2 | # 3 | # Copyright (C) 2017-2018 reverendus 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU Affero General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU Affero General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU Affero General Public License 16 | # along with this program. If not, see . 17 | 18 | import time 19 | from unittest.mock import Mock 20 | 21 | from web3 import Web3 22 | 23 | 24 | def is_hashable(v): 25 | """Determine whether `v` can be hashed.""" 26 | try: 27 | hash(v) 28 | except TypeError: 29 | return False 30 | return True 31 | 32 | 33 | def wait_until_mock_called(mock: Mock): 34 | while not mock.called: 35 | pass 36 | return mock.call_args[0] 37 | 38 | 39 | def time_travel_by(web3: Web3, seconds: int): 40 | assert(isinstance(web3, Web3)) 41 | assert(isinstance(seconds, int)) 42 | 43 | if "Parity" in web3.clientVersion: 44 | print(f"time travel unsupported by parity; waiting {seconds} seconds") 45 | time.sleep(seconds) 46 | # force a block mining to have a correct timestamp in latest block 47 | web3.eth.sendTransaction({'from': web3.eth.accounts[0], 'to': web3.eth.accounts[1], 'value': 1}) 48 | else: 49 | web3.manager.request_blocking("evm_increaseTime", [seconds]) 50 | # force a block mining to have a correct timestamp in latest block 51 | web3.manager.request_blocking("evm_mine", []) 52 | 53 | 54 | def snapshot(web3: Web3): 55 | assert(isinstance(web3, Web3)) 56 | 57 | return web3.manager.request_blocking("evm_snapshot", []) 58 | 59 | 60 | def reset(web3: Web3, snap_id): 61 | assert(isinstance(web3, Web3)) 62 | 63 | return web3.manager.request_blocking("evm_revert", [snap_id]) 64 | -------------------------------------------------------------------------------- /pymaker/abi/DaiJoin.abi: -------------------------------------------------------------------------------- 1 | [{"inputs":[{"internalType":"address","name":"vat_","type":"address"},{"internalType":"address","name":"dai_","type":"address"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"anonymous":true,"inputs":[{"indexed":true,"internalType":"bytes4","name":"sig","type":"bytes4"},{"indexed":true,"internalType":"address","name":"usr","type":"address"},{"indexed":true,"internalType":"bytes32","name":"arg1","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"arg2","type":"bytes32"},{"indexed":false,"internalType":"bytes","name":"data","type":"bytes"}],"name":"LogNote","type":"event"},{"constant":false,"inputs":[],"name":"cage","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"dai","outputs":[{"internalType":"contract DSTokenLike","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"usr","type":"address"}],"name":"deny","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"usr","type":"address"},{"internalType":"uint256","name":"wad","type":"uint256"}],"name":"exit","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"usr","type":"address"},{"internalType":"uint256","name":"wad","type":"uint256"}],"name":"join","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"live","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"usr","type":"address"}],"name":"rely","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"vat","outputs":[{"internalType":"contract VatLike","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"wards","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"}] 2 | -------------------------------------------------------------------------------- /pymaker/abi/TokenFaucet.abi: -------------------------------------------------------------------------------- 1 | [{"inputs":[],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"anonymous":true,"inputs":[{"indexed":true,"internalType":"bytes4","name":"sig","type":"bytes4"},{"indexed":true,"internalType":"address","name":"usr","type":"address"},{"indexed":true,"internalType":"bytes32","name":"arg1","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"arg2","type":"bytes32"},{"indexed":false,"internalType":"bytes","name":"data","type":"bytes"}],"name":"LogNote","type":"event"},{"constant":true,"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"amt","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"guy","type":"address"}],"name":"deny","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"done","outputs":[{"internalType":"bool","name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"gem","type":"address"},{"internalType":"address[]","name":"addrs","type":"address[]"}],"name":"gulp","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"gem","type":"address"}],"name":"gulp","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"guy","type":"address"}],"name":"rely","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"gem","type":"address"},{"internalType":"uint256","name":"amt_","type":"uint256"}],"name":"setAmt","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"contract ERC20Like","name":"gem","type":"address"}],"name":"shut","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"wards","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"}] 2 | -------------------------------------------------------------------------------- /pymaker/abi/DsrManager.abi: -------------------------------------------------------------------------------- 1 | [{"inputs":[{"internalType":"address","name":"pot_","type":"address"},{"internalType":"address","name":"daiJoin_","type":"address"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"dst","type":"address"},{"indexed":false,"internalType":"uint256","name":"wad","type":"uint256"}],"name":"Exit","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"dst","type":"address"},{"indexed":false,"internalType":"uint256","name":"wad","type":"uint256"}],"name":"Join","type":"event"},{"constant":true,"inputs":[],"name":"dai","outputs":[{"internalType":"contract GemLike","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"usr","type":"address"}],"name":"daiBalance","outputs":[{"internalType":"uint256","name":"wad","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"daiJoin","outputs":[{"internalType":"contract JoinLike","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"dst","type":"address"},{"internalType":"uint256","name":"wad","type":"uint256"}],"name":"exit","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"dst","type":"address"}],"name":"exitAll","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"dst","type":"address"},{"internalType":"uint256","name":"wad","type":"uint256"}],"name":"join","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"pieOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"pot","outputs":[{"internalType":"contract PotLike","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"supply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"}] 2 | -------------------------------------------------------------------------------- /tests/test_sign.py: -------------------------------------------------------------------------------- 1 | # This file is part of Maker Keeper Framework. 2 | # 3 | # Copyright (C) 2017-2018 reverendus 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU Affero General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU Affero General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU Affero General Public License 16 | # along with this program. If not, see . 17 | 18 | import pkg_resources 19 | from web3 import Web3, HTTPProvider 20 | 21 | from pymaker import Address 22 | from pymaker.keys import register_key_file 23 | from pymaker.sign import eth_sign 24 | 25 | 26 | def test_signing(): 27 | # given 28 | web3 = Web3(HTTPProvider("http://localhost:8555")) 29 | web3.eth.defaultAccount = web3.eth.accounts[0] 30 | 31 | # and 32 | text = "abc" 33 | msg = bytes(text, 'utf-8') 34 | 35 | # expect 36 | assert eth_sign(msg, web3).startswith("0x") 37 | 38 | 39 | def test_signing_with_key_and_rpc_should_return_same_result(): 40 | # given 41 | web3 = Web3(HTTPProvider("http://localhost:8555")) 42 | web3.eth.defaultAccount = web3.eth.accounts[0] 43 | 44 | assert Address(web3.eth.defaultAccount) == Address('0x9596c16d7bf9323265c2f2e22f43e6c80eb3d943') 45 | 46 | # and 47 | text = "abc" 48 | msg = bytes(text, 'utf-8') 49 | 50 | rpc_signature = eth_sign(msg, web3) 51 | 52 | # when 53 | keyfile_path = pkg_resources.resource_filename(__name__, "accounts/0_0x9596c16d7bf9323265c2f2e22f43e6c80eb3d943.json") 54 | passfile_path = pkg_resources.resource_filename(__name__, "accounts/pass") 55 | 56 | register_key_file(web3, keyfile_path, passfile_path) 57 | 58 | # and 59 | # [we do this in order to make sure that the message was signed using the local key] 60 | # [with `request_blocking` set to `None` any http requests will basically fail] 61 | web3.manager.request_blocking = None 62 | 63 | # and 64 | local_signature = eth_sign(msg, web3) 65 | 66 | # then 67 | assert rpc_signature == local_signature 68 | -------------------------------------------------------------------------------- /tests/manual_test_dsr.py: -------------------------------------------------------------------------------- 1 | # This file is part of Maker Keeper Framework. 2 | # 3 | # Copyright (C) 2019 grandizzy 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU Affero General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU Affero General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU Affero General Public License 16 | # along with this program. If not, see . 17 | 18 | 19 | import sys 20 | from web3 import Web3, HTTPProvider 21 | 22 | from pymaker import Address 23 | from pymaker.deployment import DssDeployment 24 | from pymaker.keys import register_keys 25 | from pymaker.numeric import Wad 26 | from pymaker.dsr import Dsr 27 | 28 | 29 | web3 = Web3(HTTPProvider(endpoint_uri="http://0.0.0.0:8545", 30 | request_kwargs={"timeout": 10})) 31 | web3.eth.defaultAccount = sys.argv[1] # ex: 0x0000000000000000000000000000000aBcdef123 32 | register_keys(web3, [sys.argv[2]]) # ex: key_file=~keys/default-account.json,pass_file=~keys/default-account.pass 33 | 34 | mcd = DssDeployment.from_network(web3, "kovan") 35 | our_address = Address(web3.eth.defaultAccount) 36 | print(our_address) 37 | 38 | dsr_client = Dsr(mcd, our_address) 39 | 40 | print(f"Chi: {dsr_client.chi()}") 41 | print(f"Total DAI: {dsr_client.get_total_dai()}") 42 | print(f"DSR: {dsr_client.dsr()}") 43 | 44 | proxy = dsr_client.get_proxy() 45 | print(f"Has Proxy: {dsr_client.has_proxy()}") 46 | 47 | if not dsr_client.has_proxy(): 48 | dsr_client.build_proxy().transact() 49 | 50 | proxy = dsr_client.get_proxy() 51 | print(f"Proxy address: {proxy.address.address}") 52 | 53 | print(f"Balance: {dsr_client.get_balance(proxy.address)}") 54 | 55 | # approve proxy to use 10 DAI from account 56 | dsr_client.mcd.dai.approve(proxy.address, Wad.from_number(10)).transact() 57 | 58 | dsr_client.join(Wad.from_number(2.2), proxy).transact() 59 | print(f"Balance: {dsr_client.get_balance(proxy.address)}") 60 | 61 | dsr_client.exit(Wad.from_number(1.01), proxy).transact() 62 | print(f"Balance: {dsr_client.get_balance(proxy.address)}") 63 | 64 | dsr_client.exit_all(proxy).transact() 65 | print(f"Balance: {dsr_client.get_balance(proxy.address)}") 66 | 67 | -------------------------------------------------------------------------------- /pymaker/abi/Pit.abi: -------------------------------------------------------------------------------- 1 | [{"constant":false,"inputs":[{"name":"ilk","type":"bytes32"},{"name":"what","type":"bytes32"},{"name":"data","type":"uint256"}],"name":"file","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"what","type":"bytes32"},{"name":"data","type":"uint256"}],"name":"file","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"vat","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"ilk","type":"bytes32"},{"name":"dink","type":"int256"},{"name":"dart","type":"int256"}],"name":"frob","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"guy","type":"address"}],"name":"rely","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"live","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"guy","type":"address"}],"name":"deny","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"Line","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"}],"name":"wards","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"bytes32"}],"name":"ilks","outputs":[{"name":"spot","type":"uint256"},{"name":"line","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"inputs":[{"name":"vat_","type":"address"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"name":"ilk","type":"bytes32"},{"indexed":true,"name":"urn","type":"bytes32"},{"indexed":false,"name":"ink","type":"uint256"},{"indexed":false,"name":"art","type":"uint256"},{"indexed":false,"name":"dink","type":"int256"},{"indexed":false,"name":"dart","type":"int256"},{"indexed":false,"name":"iInk","type":"uint256"},{"indexed":false,"name":"iArt","type":"uint256"}],"name":"Frob","type":"event"},{"anonymous":true,"inputs":[{"indexed":true,"name":"sig","type":"bytes4"},{"indexed":true,"name":"guy","type":"address"},{"indexed":true,"name":"foo","type":"bytes32"},{"indexed":true,"name":"bar","type":"bytes32"},{"indexed":false,"name":"wad","type":"uint256"},{"indexed":false,"name":"fax","type":"bytes"}],"name":"LogNote","type":"event"}] -------------------------------------------------------------------------------- /pymaker/abi/DSPause.abi: -------------------------------------------------------------------------------- 1 | [{"constant":false,"inputs":[{"name":"owner_","type":"address"}],"name":"setOwner","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"usr","type":"address"},{"name":"fax","type":"bytes"},{"name":"era","type":"uint256"}],"name":"drop","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"delay","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"authority_","type":"address"}],"name":"setAuthority","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"owner","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"usr","type":"address"},{"name":"fax","type":"bytes"},{"name":"era","type":"uint256"}],"name":"plan","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"","type":"bytes32"}],"name":"plans","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"usr","type":"address"},{"name":"fax","type":"bytes"},{"name":"era","type":"uint256"}],"name":"exec","outputs":[{"name":"response","type":"bytes"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"authority","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"inputs":[{"name":"delay_","type":"uint256"},{"name":"owner_","type":"address"},{"name":"authority_","type":"address"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"name":"usr","type":"address"},{"indexed":false,"name":"fax","type":"bytes"},{"indexed":false,"name":"era","type":"uint256"}],"name":"Plan","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"usr","type":"address"},{"indexed":false,"name":"fax","type":"bytes"},{"indexed":false,"name":"era","type":"uint256"}],"name":"Drop","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"usr","type":"address"},{"indexed":false,"name":"fax","type":"bytes"},{"indexed":false,"name":"era","type":"uint256"}],"name":"Exec","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"authority","type":"address"}],"name":"LogSetAuthority","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"owner","type":"address"}],"name":"LogSetOwner","type":"event"}] -------------------------------------------------------------------------------- /tests/manual_test_zrxv2.py: -------------------------------------------------------------------------------- 1 | # This file is part of Maker Keeper Framework. 2 | # 3 | # Copyright (C) 2017-2018 reverendus 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU Affero General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU Affero General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU Affero General Public License 16 | # along with this program. If not, see . 17 | 18 | import sys 19 | import time 20 | 21 | from web3 import EthereumTesterProvider, Web3, HTTPProvider 22 | 23 | from pymaker import Address 24 | from pymaker.approval import directly 25 | from pymaker.numeric import Wad 26 | from pymaker.token import DSToken, ERC20Token 27 | from pymaker.util import bytes_to_hexstring 28 | from pymaker.zrxv2 import ZrxExchangeV2, Order, ZrxRelayerApiV2, ERC20Asset 29 | from tests.helpers import is_hashable, wait_until_mock_called 30 | 31 | #EXCHANGE_ADDR = '0x4f833a24e1f95d70f028921e27040ca56e09ab0b' # Mainnet 32 | EXCHANGE_ADDR = sys.argv[1] 33 | SRAV2_URL = 'https://kovan-staging.ercdex.com/api' 34 | 35 | KOVAN_DAI = Address('0xc4375b7de8af5a38a93548eb8453a498222c4ff2') 36 | KOVAN_WETH = Address('0xd0a1e359811322d97991e03f863a0c30c2cf029c') 37 | 38 | 39 | web3 = Web3(HTTPProvider('http://localhost:8545')) 40 | web3.eth.defaultAccount = web3.eth.accounts[0] 41 | 42 | exchange = ZrxExchangeV2(web3=web3, address=Address(EXCHANGE_ADDR)) 43 | #exchange.approve([ERC20Token(web3=web3, address=KOVAN_DAI), 44 | # ERC20Token(web3=web3, address=KOVAN_WETH)], directly()) 45 | 46 | order = exchange.create_order(pay_asset=ERC20Asset(KOVAN_WETH), 47 | pay_amount=Wad.from_number(0.1), 48 | buy_asset=ERC20Asset(KOVAN_DAI), 49 | buy_amount=Wad.from_number(25), 50 | expiration=int(time.time())+60*35) 51 | 52 | api = ZrxRelayerApiV2(exchange=exchange, api_server=SRAV2_URL) 53 | order = api.configure_order(order) 54 | order = exchange.sign_order(order) 55 | #print(order) 56 | 57 | #print(api.submit_order(order)) 58 | #print(api.get_orders(KOVAN_WETH, KOVAN_DAI)) 59 | print(api.get_orders_by_maker(Address(web3.eth.defaultAccount))) 60 | 61 | #print(exchange.past_fill(500)) 62 | -------------------------------------------------------------------------------- /pymaker/abi/GemJoin.abi: -------------------------------------------------------------------------------- 1 | [{"inputs":[{"internalType":"address","name":"vat_","type":"address"},{"internalType":"bytes32","name":"ilk_","type":"bytes32"},{"internalType":"address","name":"gem_","type":"address"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"anonymous":true,"inputs":[{"indexed":true,"internalType":"bytes4","name":"sig","type":"bytes4"},{"indexed":true,"internalType":"address","name":"usr","type":"address"},{"indexed":true,"internalType":"bytes32","name":"arg1","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"arg2","type":"bytes32"},{"indexed":false,"internalType":"bytes","name":"data","type":"bytes"}],"name":"LogNote","type":"event"},{"constant":false,"inputs":[],"name":"cage","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"dec","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"usr","type":"address"}],"name":"deny","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"usr","type":"address"},{"internalType":"uint256","name":"wad","type":"uint256"}],"name":"exit","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"gem","outputs":[{"internalType":"contract GemLike","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"ilk","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"usr","type":"address"},{"internalType":"uint256","name":"wad","type":"uint256"}],"name":"join","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"live","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"usr","type":"address"}],"name":"rely","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"vat","outputs":[{"internalType":"contract VatLike","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"wards","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"}] 2 | -------------------------------------------------------------------------------- /pymaker/abi/GemJoin5.abi: -------------------------------------------------------------------------------- 1 | [{"inputs":[{"internalType":"address","name":"vat_","type":"address"},{"internalType":"bytes32","name":"ilk_","type":"bytes32"},{"internalType":"address","name":"gem_","type":"address"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"anonymous":true,"inputs":[{"indexed":true,"internalType":"bytes4","name":"sig","type":"bytes4"},{"indexed":true,"internalType":"address","name":"usr","type":"address"},{"indexed":true,"internalType":"bytes32","name":"arg1","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"arg2","type":"bytes32"},{"indexed":false,"internalType":"bytes","name":"data","type":"bytes"}],"name":"LogNote","type":"event"},{"constant":false,"inputs":[],"name":"cage","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"dec","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"usr","type":"address"}],"name":"deny","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"guy","type":"address"},{"internalType":"uint256","name":"amt","type":"uint256"}],"name":"exit","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"gem","outputs":[{"internalType":"contract GemLike","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"ilk","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"urn","type":"address"},{"internalType":"uint256","name":"amt","type":"uint256"}],"name":"join","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"live","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"usr","type":"address"}],"name":"rely","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"vat","outputs":[{"internalType":"contract VatLike","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"wards","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"}] 2 | -------------------------------------------------------------------------------- /pymaker/abi/DSEthToken.abi: -------------------------------------------------------------------------------- 1 | [{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"","type":"string"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"guy","type":"address"},{"name":"wad","type":"uint256"}],"name":"approve","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"src","type":"address"},{"name":"dst","type":"address"},{"name":"wad","type":"uint256"}],"name":"transferFrom","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"amount","type":"uint256"}],"name":"withdraw","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"decimals","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"src","type":"address"}],"name":"balanceOf","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"symbol","outputs":[{"name":"","type":"string"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"dst","type":"address"},{"name":"wad","type":"uint256"}],"name":"transfer","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":false,"inputs":[],"name":"deposit","outputs":[],"payable":true,"type":"function"},{"constant":false,"inputs":[],"name":"wrap","outputs":[],"payable":true,"type":"function"},{"constant":true,"inputs":[{"name":"src","type":"address"},{"name":"guy","type":"address"}],"name":"allowance","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"amount","type":"uint256"}],"name":"unwrap","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"amount","type":"uint256"}],"name":"tryWithdraw","outputs":[{"name":"ok","type":"bool"}],"payable":false,"type":"function"},{"payable":true,"type":"fallback"},{"anonymous":false,"inputs":[{"indexed":true,"name":"who","type":"address"},{"indexed":false,"name":"amount","type":"uint256"}],"name":"Deposit","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"who","type":"address"},{"indexed":false,"name":"amount","type":"uint256"}],"name":"Withdrawal","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"},{"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"}] -------------------------------------------------------------------------------- /pymaker/abi/SaiVox.abi: -------------------------------------------------------------------------------- 1 | [{"constant":false,"inputs":[],"name":"prod","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"owner_","type":"address"}],"name":"setOwner","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"era","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"how","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"par","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"ray","type":"uint256"}],"name":"tell","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[],"name":"way","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"authority_","type":"address"}],"name":"setAuthority","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"owner","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"param","type":"bytes32"},{"name":"val","type":"uint256"}],"name":"mold","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"fix","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"ray","type":"uint256"}],"name":"tune","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"authority","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"tau","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"inputs":[{"name":"par_","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"anonymous":true,"inputs":[{"indexed":true,"name":"sig","type":"bytes4"},{"indexed":true,"name":"guy","type":"address"},{"indexed":true,"name":"foo","type":"bytes32"},{"indexed":true,"name":"bar","type":"bytes32"},{"indexed":false,"name":"wad","type":"uint256"},{"indexed":false,"name":"fax","type":"bytes"}],"name":"LogNote","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"authority","type":"address"}],"name":"LogSetAuthority","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"owner","type":"address"}],"name":"LogSetOwner","type":"event"}] -------------------------------------------------------------------------------- /tests/manual_test_cdpmanager.py: -------------------------------------------------------------------------------- 1 | # This file is part of Maker Keeper Framework. 2 | # 3 | # Copyright (C) 2020 ith-harvey 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU Affero General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU Affero General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU Affero General Public License 16 | # along with this program. If not, see . 17 | 18 | 19 | import sys 20 | import os 21 | from web3 import Web3, HTTPProvider 22 | 23 | from pymaker import Address 24 | from pymaker.deployment import DssDeployment 25 | from pymaker.keys import register_keys 26 | from pymaker.numeric import Wad 27 | from pymaker.dsr import Dsr 28 | 29 | endpoint_uri = f"{os.environ['SERVER_ETH_RPC_HOST']}:{os.environ['SERVER_ETH_RPC_PORT']}" 30 | web3 = Web3(HTTPProvider(endpoint_uri=endpoint_uri, 31 | request_kwargs={"timeout": 10})) 32 | web3.eth.defaultAccount = sys.argv[1] # ex: 0x0000000000000000000000000000000aBcdef123 33 | register_keys(web3, [sys.argv[2]]) # ex: key_file=~keys/default-account.json,pass_file=~keys/default-account.pass 34 | cdpid = int(sys.argv[3]) 35 | 36 | mcd = DssDeployment.from_network(web3, "kovan") 37 | our_address = Address(web3.eth.defaultAccount) 38 | dsr_client = Dsr(mcd, our_address) 39 | print(our_address) 40 | 41 | print(f"Default account: {our_address.address}") 42 | if dsr_client.has_proxy(): 43 | proxy = dsr_client.get_proxy() 44 | print(f"{our_address} has a DS-Proxy - {proxy.address.address}, test will continue") 45 | 46 | print(f"Urn of Cdp ID {cdpid} - {mcd.cdp_manager.urn(cdpid)}") 47 | print(f"Owner of CDP ID {cdpid} - {mcd.cdp_manager.owns(cdpid)}") 48 | print(f"List of CDP IDs next to and previous to {cdpid} - {mcd.cdp_manager.list(cdpid)}") 49 | print(f"Ilk of CDP ID {cdpid} - {mcd.cdp_manager.ilk(cdpid)}") 50 | 51 | print(f"First of Cdp ID for account {proxy.address.address} - {mcd.cdp_manager.first(proxy.address)}") 52 | print(f"Last of Cdp ID for account {proxy.address.address} - {mcd.cdp_manager.last(proxy.address)}") 53 | print(f"Number of all CDPs created via DS-Cdp-Manager contract {proxy.address.address} - {mcd.cdp_manager.count(proxy.address)}") 54 | 55 | else: 56 | print(f"{our_address} does not have a DS-Proxy. Please create a cdp on kovan via Oasis.app (to create a proxy) to perform this test") 57 | 58 | 59 | -------------------------------------------------------------------------------- /pymaker/logging.py: -------------------------------------------------------------------------------- 1 | # This file is part of Maker Keeper Framework. 2 | # 3 | # Copyright (C) 2019 EdNoepel 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU Affero General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU Affero General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU Affero General Public License 16 | # along with this program. If not, see . 17 | 18 | import logging 19 | from pprint import pformat 20 | from web3 import Web3 21 | from web3._utils.events import get_event_data 22 | 23 | from eth_abi.codec import ABICodec 24 | from eth_abi.registry import registry as default_registry 25 | 26 | # Shared between DSNote and many MCD contracts 27 | class LogNote: 28 | def __init__(self, log): 29 | args = log['args'] 30 | self.sig = Web3.toHex(args['sig']) 31 | self.usr = args['usr'] if 'usr' in args else None # vat.frob doesn't offer `usr` 32 | self.arg1 = args['arg1'] if 'arg1' in args else None 33 | self.arg2 = args['arg2'] if 'arg2' in args else None 34 | self.arg3 = args['arg3'] if 'arg3' in args else None # Special variant used for vat.frob 35 | self.block = log['blockNumber'] 36 | self.tx_hash = log['transactionHash'].hex() 37 | self._data = args['data'] 38 | 39 | @classmethod 40 | def from_event(cls, event: dict, contract_abi: list): 41 | assert isinstance(event, dict) 42 | assert isinstance(contract_abi, list) 43 | 44 | log_note_abi = [abi for abi in contract_abi if abi.get('name') == 'LogNote'][0] 45 | try: 46 | codec = ABICodec(default_registry) 47 | event_data = get_event_data(codec, log_note_abi, event) 48 | return LogNote(event_data) 49 | except ValueError: 50 | # event is not a LogNote 51 | return None 52 | 53 | def get_bytes_at_index(self, index: int) -> bytes: 54 | assert isinstance(index, int) 55 | if index > 5: 56 | raise ValueError("Only six words of calldata are provided") 57 | 58 | start_index = len(self._data) - ((6-index) * 32) - 28 59 | return self._data[start_index:start_index+32] 60 | 61 | def __eq__(self, other): 62 | assert isinstance(other, LogNote) 63 | return self.__dict__ == other.__dict__ 64 | 65 | def __repr__(self): 66 | return f"LogNote({pformat(vars(self))})" 67 | -------------------------------------------------------------------------------- /pymaker/abi/ProxyRegistry.bin: -------------------------------------------------------------------------------- 1 | 608060405234801561001057600080fd5b506040516105b23803806105b28339818101604052602081101561003357600080fd5b810190808051906020019092919050505080600160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055505061051d806100956000396000f3fe608060405234801561001057600080fd5b50600436106100415760003560e01c80638e1a55fc14610046578063c455279114610090578063f3701da214610114575b600080fd5b61004e610198565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b6100d2600480360360208110156100a657600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff1690602001909291905050506101a8565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b6101566004803603602081101561012a57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff1690602001909291905050506101db565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b60006101a3336101db565b905090565b60006020528060005260406000206000915054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b60008073ffffffffffffffffffffffffffffffffffffffff166000808473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16148061037f57508173ffffffffffffffffffffffffffffffffffffffff166000808473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16638da5cb5b6040518163ffffffff1660e01b815260040160206040518083038186803b15801561032b57600080fd5b505afa15801561033f573d6000803e3d6000fd5b505050506040513d602081101561035557600080fd5b810190808051906020019092919050505073ffffffffffffffffffffffffffffffffffffffff1614155b61038857600080fd5b600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663f3701da2836040518263ffffffff1660e01b8152600401808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001915050602060405180830381600087803b15801561042957600080fd5b505af115801561043d573d6000803e3d6000fd5b505050506040513d602081101561045357600080fd5b81019080805190602001909291905050509050806000808473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555091905056fea265627a7a72315820dde22e20d59bcfe01bcafda726adc265175924ee37c24a292c4361b64a6ffa3364736f6c634300050c0032 2 | -------------------------------------------------------------------------------- /tests/dss_token.py: -------------------------------------------------------------------------------- 1 | # This file is part of Maker Keeper Framework. 2 | # 3 | # Copyright (C) 2017-2018 reverendus 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU Affero General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU Affero General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU Affero General Public License 16 | # along with this program. If not, see . 17 | from web3 import Web3 18 | 19 | from pymaker import Contract, Address, Transact 20 | from pymaker.dss import Urn, Ilk 21 | from pymaker.numeric import Wad 22 | 23 | 24 | class GemMock(Contract): 25 | """A client for `GemMock` contract. 26 | """ 27 | 28 | abi = Contract._load_abi(__name__, 'abi/GemMock.abi') 29 | bin = Contract._load_bin(__name__, 'abi/GemMock.bin') 30 | 31 | def __init__(self, web3: Web3, address: Address): 32 | assert(isinstance(web3, Web3)) 33 | assert(isinstance(address, Address)) 34 | 35 | self.web3 = web3 36 | self.address = address 37 | self._contract = self._get_contract(web3, self.abi, address) 38 | 39 | @staticmethod 40 | def deploy(web3: Web3, vat: Address, ilk: Ilk, gem: Address): 41 | assert isinstance(web3, Web3) 42 | assert isinstance(vat, Address) 43 | assert isinstance(ilk, Ilk) 44 | assert isinstance(gem, Address) 45 | 46 | return GemMock(web3=web3, address=Contract._deploy(web3, GemMock.abi, GemMock.bin, [vat.address, 47 | ilk.toBytes(), 48 | gem.address])) 49 | def ilk(self): 50 | return Ilk.fromBytes(self._contract.functions.ilk().call()) 51 | 52 | def join(self, urn: Urn, value: Wad) -> Transact: 53 | assert(isinstance(urn, Urn)) 54 | assert(isinstance(value, Wad)) 55 | 56 | return Transact(self, self.web3, self.abi, self.address, self._contract, 57 | 'join', [urn.toBytes() , value.value]) 58 | 59 | def hope(self, guy: Address) -> Transact: 60 | assert(isinstance(guy, Address)) 61 | 62 | return Transact(self, self.web3, self.abi, self.address, self._contract, 63 | 'hope', [guy.address]) 64 | 65 | def __eq__(self, other): 66 | return self.address == other.address 67 | 68 | def __repr__(self): 69 | return f"GemMock('{self.address}')" 70 | -------------------------------------------------------------------------------- /pymaker/abi/DSVault.abi: -------------------------------------------------------------------------------- 1 | [{"constant":false,"inputs":[{"name":"token_","type":"address"}],"name":"swap","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"owner_","type":"address"}],"name":"setOwner","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"dst","type":"address"},{"name":"wad","type":"uint128"}],"name":"push","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"token","type":"address"},{"name":"dst","type":"address"},{"name":"wad","type":"uint128"}],"name":"push","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[],"name":"burn","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"src","type":"address"}],"name":"pull","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"token","type":"address"},{"name":"src","type":"address"}],"name":"pull","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"wad","type":"uint128"}],"name":"mint","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"token","type":"address"},{"name":"src","type":"address"},{"name":"wad","type":"uint128"}],"name":"pull","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"token","type":"address"},{"name":"wad","type":"uint128"}],"name":"burn","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"authority_","type":"address"}],"name":"setAuthority","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"src","type":"address"},{"name":"wad","type":"uint128"}],"name":"pull","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"token","type":"address"}],"name":"burn","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"dst","type":"address"}],"name":"push","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"owner","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"wad","type":"uint128"}],"name":"burn","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"token","type":"address"},{"name":"dst","type":"address"}],"name":"push","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"token","type":"address"},{"name":"wad","type":"uint128"}],"name":"mint","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"authority","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"token","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"anonymous":false,"inputs":[{"indexed":true,"name":"authority","type":"address"}],"name":"LogSetAuthority","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"owner","type":"address"}],"name":"LogSetOwner","type":"event"}] -------------------------------------------------------------------------------- /pymaker/abi/DSRoles.abi: -------------------------------------------------------------------------------- 1 | [{"constant":true,"inputs":[{"name":"who","type":"address"}],"name":"getUserRoles","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"owner_","type":"address"}],"name":"setOwner","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"code","type":"address"},{"name":"sig","type":"bytes4"}],"name":"getCapabilityRoles","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"code","type":"address"},{"name":"sig","type":"bytes4"}],"name":"isCapabilityPublic","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"who","type":"address"},{"name":"role","type":"uint8"},{"name":"enabled","type":"bool"}],"name":"setUserRole","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"authority_","type":"address"}],"name":"setAuthority","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"role","type":"uint8"},{"name":"code","type":"address"},{"name":"sig","type":"bytes4"},{"name":"enabled","type":"bool"}],"name":"setRoleCapability","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":"who","type":"address"},{"name":"role","type":"uint8"}],"name":"hasUserRole","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"caller","type":"address"},{"name":"code","type":"address"},{"name":"sig","type":"bytes4"}],"name":"canCall","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"authority","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"code","type":"address"},{"name":"sig","type":"bytes4"},{"name":"enabled","type":"bool"}],"name":"setPublicCapability","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"who","type":"address"},{"name":"enabled","type":"bool"}],"name":"setRootUser","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"who","type":"address"}],"name":"isUserRoot","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"anonymous":false,"inputs":[{"indexed":true,"name":"authority","type":"address"}],"name":"LogSetAuthority","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"owner","type":"address"}],"name":"LogSetOwner","type":"event"}] -------------------------------------------------------------------------------- /pymaker/vault.py: -------------------------------------------------------------------------------- 1 | # This file is part of Maker Keeper Framework. 2 | # 3 | # Copyright (C) 2017-2018 reverendus 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU Affero General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU Affero General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU Affero General Public License 16 | # along with this program. If not, see . 17 | 18 | from web3 import Web3 19 | 20 | from pymaker import Contract, Address, Transact 21 | 22 | 23 | class DSVault(Contract): 24 | """A client for the `DSVault` contract. 25 | 26 | You can find the source code of the `DSVault` contract here: 27 | . 28 | 29 | Attributes: 30 | web3: An instance of `Web` from `web3.py`. 31 | address: Ethereum address of the `DSVault` contract. 32 | """ 33 | 34 | abi = Contract._load_abi(__name__, 'abi/DSVault.abi') 35 | bin = Contract._load_bin(__name__, 'abi/DSVault.bin') 36 | 37 | def __init__(self, web3: Web3, address: Address): 38 | assert(isinstance(web3, Web3)) 39 | assert(isinstance(address, Address)) 40 | 41 | self.web3 = web3 42 | self.address = address 43 | self._contract = self._get_contract(web3, self.abi, address) 44 | 45 | @staticmethod 46 | def deploy(web3: Web3): 47 | """Deploy a new instance of the `DSVault` contract. 48 | 49 | Args: 50 | web3: An instance of `Web` from `web3.py`. 51 | 52 | Returns: 53 | A `DSVault` class instance. 54 | """ 55 | return DSVault(web3=web3, address=Contract._deploy(web3, DSVault.abi, DSVault.bin, [])) 56 | 57 | def authority(self) -> Address: 58 | """Return the current `authority` of a `DSAuth`-ed contract. 59 | 60 | Returns: 61 | The address of the current `authority`. 62 | """ 63 | return Address(self._contract.functions.authority().call()) 64 | 65 | def set_authority(self, address: Address) -> Transact: 66 | """Set the `authority` of a `DSAuth`-ed contract. 67 | 68 | Args: 69 | address: The address of the new `authority`. 70 | 71 | Returns: 72 | A :py:class:`pymaker.Transact` instance, which can be used to trigger the transaction. 73 | """ 74 | assert(isinstance(address, Address)) 75 | return Transact(self, self.web3, self.abi, self.address, self._contract, 'setAuthority', [address.address]) 76 | 77 | def __repr__(self): 78 | return f"DSVault('{self.address}')" 79 | -------------------------------------------------------------------------------- /pymaker/oracles.py: -------------------------------------------------------------------------------- 1 | # This file is part of Maker Keeper Framework. 2 | # 3 | # Copyright (C) 2019 grandizzy 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU Affero General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU Affero General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU Affero General Public License 16 | # along with this program. If not, see . 17 | 18 | from web3 import Web3 19 | 20 | from pymaker import Contract, Address, Transact 21 | from pymaker.numeric import Wad 22 | 23 | 24 | # TODO: Complete implementation and unit test 25 | class OSM(Contract): 26 | """A client for the `OSM` contract. 27 | 28 | You can find the source code of the `OSM` contract here: 29 | . 30 | 31 | Attributes: 32 | web3: An instance of `Web` from `web3.py`. 33 | address: Ethereum address of the `OSM` contract. 34 | """ 35 | 36 | abi = Contract._load_abi(__name__, 'abi/OSM.abi') 37 | bin = Contract._load_bin(__name__, 'abi/OSM.bin') 38 | 39 | def __init__(self, web3: Web3, address: Address): 40 | assert (isinstance(web3, Web3)) 41 | assert (isinstance(address, Address)) 42 | 43 | self.web3 = web3 44 | self.address = address 45 | self._contract = self._get_contract(web3, self.abi, address) 46 | 47 | def poke(self) -> Transact: 48 | return Transact(self, self.web3, self.abi, self.address, self._contract, 'poke', []) 49 | 50 | def peek(self) -> Wad: 51 | return Wad(self._extract_price(3)) 52 | 53 | def peep(self) -> Wad: 54 | return Wad(self._extract_price(4)) 55 | 56 | def zzz(self) -> int: 57 | return self._contract.functions.zzz().call() 58 | 59 | def _extract_price(self, storage_slot: int) -> int: 60 | assert isinstance(storage_slot, int) 61 | return Web3.toInt(self.web3.eth.getStorageAt(self.address.address, storage_slot)[16:]) 62 | 63 | def __repr__(self): 64 | return f"OSM('{self.address}')" 65 | 66 | 67 | class OldUniv2LpOSM(OSM): 68 | """A custom `OSM` contract for Uniswap LP tokens which used different storage slots, obsolete as of dss-1.7.0 69 | 70 | You can find the source code of the `OSM` contract here: 71 | . 72 | """ 73 | 74 | def __init__(self, web3: Web3, address: Address): 75 | super().__init__(web3, address) 76 | 77 | def peek(self) -> Wad: 78 | return Wad(self._extract_price(6)) 79 | 80 | def peep(self) -> Wad: 81 | return Wad(self._extract_price(7)) 82 | -------------------------------------------------------------------------------- /pymaker/collateral.py: -------------------------------------------------------------------------------- 1 | # This file is part of Maker Keeper Framework. 2 | # 3 | # Copyright (C) 2019-2021 EdNoepel 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU Affero General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU Affero General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU Affero General Public License 16 | # along with this program. If not, see . 17 | 18 | import logging 19 | 20 | from pymaker import Address, Contract 21 | from pymaker.approval import directly, hope_directly 22 | from pymaker.auctions import AuctionContract, Clipper, Flipper 23 | from pymaker.ilk import Ilk 24 | from pymaker.gas import DefaultGasPrice 25 | from pymaker.join import GemJoin 26 | from pymaker.token import DSToken, ERC20Token 27 | 28 | 29 | logger = logging.getLogger() 30 | 31 | 32 | class Collateral: 33 | """The `Collateral` object wraps accounting information in the Ilk with token-wide artifacts shared across 34 | multiple collateral types for the same token. For example, ETH-A and ETH-B are represented by different Ilks, 35 | but will share the same gem (WETH token), GemJoin instance, and Flipper contract. 36 | """ 37 | 38 | def __init__(self, ilk: Ilk, gem: ERC20Token, adapter: GemJoin, auction: AuctionContract, pip, vat: Contract): 39 | assert isinstance(ilk, Ilk) 40 | assert isinstance(gem, ERC20Token) 41 | assert isinstance(adapter, GemJoin) 42 | assert isinstance(auction, AuctionContract) 43 | assert isinstance(vat, Contract) 44 | 45 | self.ilk = ilk 46 | self.gem = gem 47 | self.adapter = adapter 48 | if isinstance(auction, Flipper): 49 | self.flipper = auction 50 | self.clipper = None 51 | elif isinstance(auction, Clipper): 52 | self.flipper = None 53 | self.clipper = auction 54 | # Points to `median` for official deployments, `DSValue` for testing purposes. 55 | # Users generally have no need to interact with the pip. 56 | self.pip = pip 57 | self.vat = vat 58 | 59 | def approve(self, usr: Address, **kwargs): 60 | """ 61 | Allows the user to move this collateral into and out of their CDP. 62 | 63 | Args 64 | usr: User making transactions with this collateral 65 | """ 66 | gas_price = kwargs['gas_price'] if 'gas_price' in kwargs else DefaultGasPrice() 67 | self.adapter.approve(hope_directly(from_address=usr, gas_price=gas_price), self.vat.address) 68 | self.adapter.approve_token(directly(from_address=usr, gas_price=gas_price)) 69 | -------------------------------------------------------------------------------- /pymaker/abi/Jug.abi: -------------------------------------------------------------------------------- 1 | [{"inputs":[{"internalType":"address","name":"vat_","type":"address"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"anonymous":true,"inputs":[{"indexed":true,"internalType":"bytes4","name":"sig","type":"bytes4"},{"indexed":true,"internalType":"address","name":"usr","type":"address"},{"indexed":true,"internalType":"bytes32","name":"arg1","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"arg2","type":"bytes32"},{"indexed":false,"internalType":"bytes","name":"data","type":"bytes"}],"name":"LogNote","type":"event"},{"constant":true,"inputs":[],"name":"base","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"usr","type":"address"}],"name":"deny","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"bytes32","name":"ilk","type":"bytes32"}],"name":"drip","outputs":[{"internalType":"uint256","name":"rate","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"bytes32","name":"ilk","type":"bytes32"},{"internalType":"bytes32","name":"what","type":"bytes32"},{"internalType":"uint256","name":"data","type":"uint256"}],"name":"file","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"bytes32","name":"what","type":"bytes32"},{"internalType":"uint256","name":"data","type":"uint256"}],"name":"file","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"bytes32","name":"what","type":"bytes32"},{"internalType":"address","name":"data","type":"address"}],"name":"file","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"name":"ilks","outputs":[{"internalType":"uint256","name":"duty","type":"uint256"},{"internalType":"uint256","name":"rho","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"bytes32","name":"ilk","type":"bytes32"}],"name":"init","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"usr","type":"address"}],"name":"rely","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"vat","outputs":[{"internalType":"contract VatLike","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"vow","outputs":[{"internalType":"address","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"wards","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"}] 2 | -------------------------------------------------------------------------------- /pymaker/sign.py: -------------------------------------------------------------------------------- 1 | # This file is part of Maker Keeper Framework. 2 | # 3 | # Copyright (C) 2017-2018 reverendus 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU Affero General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU Affero General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU Affero General Public License 16 | # along with this program. If not, see . 17 | 18 | import logging 19 | import time 20 | from typing import Tuple 21 | 22 | from eth_account.messages import defunct_hash_message 23 | from eth_utils import encode_hex 24 | from web3 import Web3 25 | 26 | from pymaker import Address 27 | from pymaker.keys import _registered_accounts 28 | from pymaker.util import bytes_to_hexstring 29 | 30 | 31 | def eth_sign(message: bytes, web3: Web3, key=None, in_hexbytes=False, account=None): 32 | assert(isinstance(message, bytes)) 33 | assert(isinstance(web3, Web3)) 34 | 35 | local_account = _registered_accounts.get((web3, Address(web3.eth.defaultAccount))) 36 | 37 | if local_account or (account is not None): 38 | 39 | if key is None: 40 | pkey = local_account.privateKey 41 | else: 42 | pkey = key 43 | 44 | start_time = time.time() 45 | start_clock = time.process_time() 46 | try: 47 | if in_hexbytes: 48 | message_hash = message 49 | else: 50 | message_hash = defunct_hash_message(primitive=message) 51 | signature = web3.eth.account.signHash(message_hash, private_key=pkey).signature.hex() 52 | finally: 53 | end_time = time.time() 54 | end_clock = time.process_time() 55 | 56 | logging.debug(f"Local signing took {end_time - start_time:.3f}s time, {end_clock - start_clock:.3f}s clock") 57 | 58 | return signature 59 | 60 | else: 61 | signature = bytes_to_hexstring(web3.manager.request_blocking( 62 | "eth_sign", [web3.eth.defaultAccount, encode_hex(message)], 63 | )) 64 | 65 | # for `EthereumJS TestRPC/v2.2.1/ethereum-js` 66 | if signature.endswith("00"): 67 | signature = signature[:-2] + "1b" 68 | 69 | if signature.endswith("01"): 70 | signature = signature[:-2] + "1c" 71 | 72 | return signature 73 | 74 | 75 | def to_vrs(signature: str) -> Tuple[int, bytes, bytes]: 76 | assert(isinstance(signature, str)) 77 | assert(signature.startswith("0x")) 78 | 79 | signature_hex = signature[2:] 80 | r = bytes.fromhex(signature_hex[0:64]) 81 | s = bytes.fromhex(signature_hex[64:128]) 82 | v = ord(bytes.fromhex(signature_hex[128:130])) 83 | 84 | return v, r, s 85 | -------------------------------------------------------------------------------- /pymaker/model.py: -------------------------------------------------------------------------------- 1 | # This file is part of Maker Keeper Framework. 2 | # 3 | # Copyright (C) 2017-2020 mitakash, MikeHathaway 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU Affero General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU Affero General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU Affero General Public License 16 | # along with this program. If not, see . 17 | 18 | from pprint import pformat 19 | from typing import Optional, List 20 | 21 | from pymaker import Address 22 | from pymaker.numeric import Wad 23 | 24 | 25 | class Token: 26 | def __init__(self, name: str, address: Optional[Address], decimals: int): 27 | assert(isinstance(name, str)) 28 | assert(isinstance(address, Address) or (address is None)) 29 | assert(isinstance(decimals, int)) 30 | 31 | self.name = name 32 | self.address = address 33 | self.decimals = decimals 34 | 35 | self.min_amount = Wad.from_number(10 ** -self.decimals) 36 | 37 | def normalize_amount(self, amount: Wad) -> Wad: 38 | assert(isinstance(amount, Wad)) 39 | 40 | return amount * Wad.from_number(10 ** (18 - self.decimals)) 41 | 42 | def unnormalize_amount(self, amount: Wad) -> Wad: 43 | assert(isinstance(amount, Wad)) 44 | 45 | return amount * Wad.from_number(10 ** (self.decimals - 18)) 46 | 47 | def is_eth(self) -> bool: 48 | return self.address == Address('0x0000000000000000000000000000000000000000') 49 | 50 | def __eq__(self, other): 51 | assert(isinstance(other, Token)) 52 | return self.name == other.name and \ 53 | self.address == other.address and \ 54 | self.decimals == other.decimals 55 | 56 | def __hash__(self): 57 | return hash((self.name, self.address, self.decimals)) 58 | 59 | def __str__(self): 60 | return self.name 61 | 62 | def __repr__(self): 63 | return pformat(vars(self)) 64 | 65 | 66 | class TokenConfig: 67 | def __init__(self, data: dict): 68 | assert (isinstance(data, dict)) 69 | 70 | self.token_list = [] 71 | self.token_config = data['tokens'] 72 | 73 | def set_token_list(self, data): 74 | assert (isinstance(data, dict)) 75 | 76 | self.token_list = [Token(name=key, 77 | address=Address(value['tokenAddress']) if 'tokenAddress' in value else None, 78 | decimals=value['tokenDecimals'] if 'tokenDecimals' in value else 18) for key, value in 79 | data['tokens'].items()] 80 | 81 | def get_token_list(self) -> List[Token]: 82 | return self.token_list 83 | 84 | def __repr__(self): 85 | return pformat(vars(self)) 86 | -------------------------------------------------------------------------------- /pymaker/abi/Spotter.abi: -------------------------------------------------------------------------------- 1 | [{"inputs":[{"internalType":"address","name":"vat_","type":"address"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"anonymous":true,"inputs":[{"indexed":true,"internalType":"bytes4","name":"sig","type":"bytes4"},{"indexed":true,"internalType":"address","name":"usr","type":"address"},{"indexed":true,"internalType":"bytes32","name":"arg1","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"arg2","type":"bytes32"},{"indexed":false,"internalType":"bytes","name":"data","type":"bytes"}],"name":"LogNote","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"bytes32","name":"ilk","type":"bytes32"},{"indexed":false,"internalType":"bytes32","name":"val","type":"bytes32"},{"indexed":false,"internalType":"uint256","name":"spot","type":"uint256"}],"name":"Poke","type":"event"},{"constant":false,"inputs":[],"name":"cage","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"guy","type":"address"}],"name":"deny","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"bytes32","name":"ilk","type":"bytes32"},{"internalType":"bytes32","name":"what","type":"bytes32"},{"internalType":"uint256","name":"data","type":"uint256"}],"name":"file","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"bytes32","name":"what","type":"bytes32"},{"internalType":"uint256","name":"data","type":"uint256"}],"name":"file","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"bytes32","name":"ilk","type":"bytes32"},{"internalType":"bytes32","name":"what","type":"bytes32"},{"internalType":"address","name":"pip_","type":"address"}],"name":"file","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"name":"ilks","outputs":[{"internalType":"contract PipLike","name":"pip","type":"address"},{"internalType":"uint256","name":"mat","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"live","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"par","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"bytes32","name":"ilk","type":"bytes32"}],"name":"poke","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"guy","type":"address"}],"name":"rely","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"vat","outputs":[{"internalType":"contract VatLike","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"wards","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"}] 2 | -------------------------------------------------------------------------------- /pymaker/keys.py: -------------------------------------------------------------------------------- 1 | # This file is part of Maker Keeper Framework. 2 | # 3 | # Copyright (C) 2018 reverendus 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU Affero General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU Affero General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU Affero General Public License 16 | # along with this program. If not, see . 17 | 18 | import getpass 19 | from typing import Optional 20 | 21 | from eth_account import Account 22 | from web3 import Web3 23 | from web3.middleware import construct_sign_and_send_raw_middleware 24 | 25 | from pymaker import Address 26 | 27 | _registered_accounts = {} 28 | 29 | 30 | def register_keys(web3: Web3, keys: Optional[list]): 31 | for key in keys or []: 32 | register_key(web3, key) 33 | 34 | 35 | def register_key(web3: Web3, key: str): 36 | assert(isinstance(web3, Web3)) 37 | 38 | parsed = {} 39 | for p in key.split(","): 40 | var, val = p.split("=") 41 | parsed[var] = val 42 | 43 | register_key_file(web3, parsed.get('key_file'), parsed.get('pass_file', None)) 44 | 45 | 46 | def register_key_file(web3: Web3, key_file: str, pass_file: Optional[str] = None): 47 | assert(isinstance(web3, Web3)) 48 | assert(isinstance(key_file, str)) 49 | assert(isinstance(pass_file, str) or (pass_file is None)) 50 | 51 | with open(key_file) as key_file_open: 52 | read_key = key_file_open.read() 53 | if pass_file: 54 | with open(pass_file) as pass_file_open: 55 | read_pass = pass_file_open.read().replace("\n", "") 56 | else: 57 | read_pass = getpass.getpass(prompt=f"Password for {key_file}: ") 58 | 59 | private_key = Account.decrypt(read_key, read_pass) 60 | register_private_key(web3, private_key) 61 | 62 | def get_private_key(web3: Web3, key: str): 63 | assert(isinstance(web3, Web3)) 64 | assert(isinstance(key, str)) 65 | 66 | parsed = {} 67 | for p in key.split(","): 68 | var, val = p.split("=") 69 | parsed[var] = val 70 | 71 | with open(parsed.get('key_file')) as key_file_open: 72 | read_key = key_file_open.read() 73 | private_key = web3 74 | if parsed.get('pass_file'): 75 | with open(parsed.get('pass_file')) as pass_file_open: 76 | read_pass = pass_file_open.read().replace("\n", "") 77 | else: 78 | read_pass = getpass.getpass(prompt=f"Password for {key_file}: ") 79 | 80 | private_key = Account.decrypt(read_key, read_pass).hex() 81 | return private_key 82 | 83 | def register_private_key(web3: Web3, private_key): 84 | assert(isinstance(web3, Web3)) 85 | 86 | account = Account.privateKeyToAccount(private_key) 87 | 88 | _registered_accounts[(web3, Address(account.address))] = account 89 | web3.middleware_onion.add(construct_sign_and_send_raw_middleware(account)) 90 | -------------------------------------------------------------------------------- /pymaker/abi/SaiTop.abi: -------------------------------------------------------------------------------- 1 | [{"constant":true,"inputs":[],"name":"sin","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"skr","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"owner_","type":"address"}],"name":"setOwner","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"era","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"flow","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"tub","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"cooldown_","type":"uint256"}],"name":"setCooldown","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"vox","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"cage","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"cooldown","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"authority_","type":"address"}],"name":"setAuthority","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"gem","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"owner","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"sai","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"fix","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"authority","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"fit","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"caged","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"tap","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"inputs":[{"name":"tub_","type":"address"},{"name":"tap_","type":"address"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"anonymous":true,"inputs":[{"indexed":true,"name":"sig","type":"bytes4"},{"indexed":true,"name":"guy","type":"address"},{"indexed":true,"name":"foo","type":"bytes32"},{"indexed":true,"name":"bar","type":"bytes32"},{"indexed":false,"name":"wad","type":"uint256"},{"indexed":false,"name":"fax","type":"bytes"}],"name":"LogNote","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"authority","type":"address"}],"name":"LogSetAuthority","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"owner","type":"address"}],"name":"LogSetOwner","type":"event"}] -------------------------------------------------------------------------------- /tests/manual_test_goerli.py: -------------------------------------------------------------------------------- 1 | # This file is part of Maker Keeper Framework. 2 | # 3 | # Copyright (C) 2020-2021 EdNoepel 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU Affero General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU Affero General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU Affero General Public License 16 | # along with this program. If not, see . 17 | 18 | import logging 19 | import os 20 | import sys 21 | from web3 import Web3, HTTPProvider 22 | 23 | from pymaker import Address, eth_transfer, web3_via_http 24 | from pymaker.gas import GeometricGasPrice 25 | from pymaker.lifecycle import Lifecycle 26 | from pymaker.keys import register_keys 27 | from pymaker.numeric import Wad 28 | from pymaker.token import EthToken 29 | 30 | logging.basicConfig(format='%(asctime)-15s %(levelname)-8s %(message)s', level=logging.DEBUG) 31 | # reduce logspew 32 | logging.getLogger('urllib3').setLevel(logging.INFO) 33 | logging.getLogger("web3").setLevel(logging.INFO) 34 | logging.getLogger("asyncio").setLevel(logging.INFO) 35 | logging.getLogger("requests").setLevel(logging.INFO) 36 | 37 | endpoint_uri = sys.argv[1] 38 | web3 = web3_via_http(endpoint_uri, timeout=10) 39 | print(web3.clientVersion) 40 | 41 | """ 42 | Argument: Reqd? Example: 43 | Ethereum node URI yes https://localhost:8545 44 | Ethereum address no 0x0000000000000000000000000000000aBcdef123 45 | Private key no key_file=~keys/default-account.json,pass_file=~keys/default-account.pass 46 | Gas price (GWEI) no 9 47 | """ 48 | 49 | 50 | if len(sys.argv) > 3: 51 | web3.eth.defaultAccount = sys.argv[2] 52 | register_keys(web3, [sys.argv[3]]) 53 | our_address = Address(web3.eth.defaultAccount) 54 | run_transactions = True 55 | elif len(sys.argv) > 2: 56 | our_address = Address(sys.argv[2]) 57 | run_transactions = False 58 | else: 59 | our_address = None 60 | run_transactions = False 61 | 62 | gas_price = None if len(sys.argv) <= 4 else \ 63 | GeometricGasPrice(initial_price=int(float(sys.argv[4]) * GeometricGasPrice.GWEI), 64 | every_secs=15, 65 | max_price=100 * GeometricGasPrice.GWEI) 66 | 67 | eth = EthToken(web3, Address.zero()) 68 | 69 | 70 | class TestApp: 71 | def main(self): 72 | with Lifecycle(web3) as lifecycle: 73 | lifecycle.on_block(self.on_block) 74 | 75 | def on_block(self): 76 | block = web3.eth.blockNumber 77 | logging.info(f"Found block; web3.eth.blockNumber={block}") 78 | 79 | if run_transactions and block % 3 == 0: 80 | # dummy transaction: send 0 ETH to ourself 81 | eth_transfer(web3=web3, to=our_address, amount=Wad(0)).transact( 82 | from_address=our_address, gas=21000, gas_price=gas_price) 83 | 84 | if our_address: 85 | logging.info(f"Eth balance is {eth.balance_of(our_address)}") 86 | 87 | 88 | if __name__ == '__main__': 89 | TestApp().main() 90 | -------------------------------------------------------------------------------- /pymaker/ilk.py: -------------------------------------------------------------------------------- 1 | # This file is part of Maker Keeper Framework. 2 | # 3 | # Copyright (C) 2019-2021 EdNoepel 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU Affero General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU Affero General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU Affero General Public License 16 | # along with this program. If not, see . 17 | 18 | from typing import Optional 19 | from web3 import Web3 20 | 21 | from pymaker.numeric import Wad, Ray, Rad 22 | 23 | 24 | class Ilk: 25 | """Models one collateral type, the combination of a token and a set of risk parameters. 26 | For example, ETH-A and ETH-B are different collateral types with the same underlying token (WETH) but with 27 | different risk parameters. 28 | """ 29 | 30 | def __init__(self, name: str, rate: Optional[Ray] = None, 31 | ink: Optional[Wad] = None, 32 | art: Optional[Wad] = None, 33 | spot: Optional[Ray] = None, 34 | line: Optional[Rad] = None, 35 | dust: Optional[Rad] = None): 36 | assert (isinstance(name, str)) 37 | assert (isinstance(rate, Ray) or (rate is None)) 38 | assert (isinstance(ink, Wad) or (ink is None)) 39 | assert (isinstance(art, Wad) or (art is None)) 40 | assert (isinstance(spot, Ray) or (spot is None)) 41 | assert (isinstance(line, Rad) or (line is None)) 42 | assert (isinstance(dust, Rad) or (dust is None)) 43 | 44 | self.name = name 45 | self.rate = rate 46 | self.ink = ink 47 | self.art = art 48 | self.spot = spot 49 | self.line = line 50 | self.dust = dust 51 | 52 | def toBytes(self): 53 | return Web3.toBytes(text=self.name).ljust(32, bytes(1)) 54 | 55 | @staticmethod 56 | def fromBytes(ilk: bytes): 57 | assert (isinstance(ilk, bytes)) 58 | 59 | name = Web3.toText(ilk.strip(bytes(1))) 60 | return Ilk(name) 61 | 62 | def __eq__(self, other): 63 | assert isinstance(other, Ilk) 64 | 65 | return (self.name == other.name) \ 66 | and (self.rate == other.rate) \ 67 | and (self.ink == other.ink) \ 68 | and (self.art == other.art) \ 69 | and (self.spot == other.spot) \ 70 | and (self.line == other.line) \ 71 | and (self.dust == other.dust) 72 | 73 | def __repr__(self): 74 | repr = '' 75 | if self.rate: 76 | repr += f' rate={self.rate}' 77 | if self.ink: 78 | repr += f' Ink={self.ink}' 79 | if self.art: 80 | repr += f' Art={self.art}' 81 | if self.spot: 82 | repr += f' spot={self.spot}' 83 | if self.line: 84 | repr += f' line={self.line}' 85 | if self.dust: 86 | repr += f' dust={self.dust}' 87 | if repr: 88 | repr = f'[{repr.strip()}]' 89 | 90 | return f"Ilk('{self.name}'){repr}" 91 | -------------------------------------------------------------------------------- /pymaker/abi/Pot.abi: -------------------------------------------------------------------------------- 1 | [{"inputs":[{"internalType":"address","name":"vat_","type":"address"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"anonymous":true,"inputs":[{"indexed":true,"internalType":"bytes4","name":"sig","type":"bytes4"},{"indexed":true,"internalType":"address","name":"usr","type":"address"},{"indexed":true,"internalType":"bytes32","name":"arg1","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"arg2","type":"bytes32"},{"indexed":false,"internalType":"bytes","name":"data","type":"bytes"}],"name":"LogNote","type":"event"},{"constant":true,"inputs":[],"name":"Pie","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"cage","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"chi","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"guy","type":"address"}],"name":"deny","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[],"name":"drip","outputs":[{"internalType":"uint256","name":"tmp","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"dsr","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"uint256","name":"wad","type":"uint256"}],"name":"exit","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"bytes32","name":"what","type":"bytes32"},{"internalType":"uint256","name":"data","type":"uint256"}],"name":"file","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"bytes32","name":"what","type":"bytes32"},{"internalType":"address","name":"addr","type":"address"}],"name":"file","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"uint256","name":"wad","type":"uint256"}],"name":"join","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"live","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"pie","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"guy","type":"address"}],"name":"rely","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"rho","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"vat","outputs":[{"internalType":"contract VatLike","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"vow","outputs":[{"internalType":"address","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"wards","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"}] 2 | -------------------------------------------------------------------------------- /tests/manual_test_tx_recovery.py: -------------------------------------------------------------------------------- 1 | # This file is part of Maker Keeper Framework. 2 | # 3 | # Copyright (C) 2020 EdNoepel 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU Affero General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU Affero General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU Affero General Public License 16 | # along with this program. If not, see . 17 | 18 | import asyncio 19 | import logging 20 | import os 21 | import sys 22 | import time 23 | import threading 24 | from pprint import pprint 25 | 26 | from pymaker import Address, get_pending_transactions, Wad, web3_via_http 27 | from pymaker.deployment import DssDeployment 28 | from pymaker.gas import FixedGasPrice, GeometricGasPrice 29 | from pymaker.keys import register_keys 30 | 31 | logging.basicConfig(format='%(asctime)-15s %(levelname)-8s %(message)s', level=logging.DEBUG) 32 | # reduce logspew 33 | logging.getLogger('urllib3').setLevel(logging.INFO) 34 | logging.getLogger("web3").setLevel(logging.INFO) 35 | logging.getLogger("asyncio").setLevel(logging.INFO) 36 | logging.getLogger("requests").setLevel(logging.INFO) 37 | 38 | web3 = web3_via_http(endpoint_uri=os.environ['ETH_RPC_URL']) 39 | web3.eth.defaultAccount = sys.argv[1] # ex: 0x0000000000000000000000000000000aBcdef123 40 | register_keys(web3, [sys.argv[2]]) # ex: key_file=~keys/default-account.json,pass_file=~keys/default-account.pass 41 | our_address = Address(web3.eth.defaultAccount) 42 | weth = DssDeployment.from_node(web3).collaterals['ETH-A'].gem 43 | stuck_txes_to_submit = int(sys.argv[3]) if len(sys.argv) > 3 else 1 44 | 45 | GWEI = 1000000000 46 | increasing_gas = GeometricGasPrice(initial_price=int(1 * GWEI), every_secs=30, coefficient=1.5, max_price=100 * GWEI) 47 | 48 | 49 | class TestApp: 50 | def main(self): 51 | self.startup() 52 | 53 | pending_txes = get_pending_transactions(web3) 54 | pprint(list(map(lambda t: f"{t.name()} with gas {t.current_gas}", pending_txes))) 55 | 56 | if len(pending_txes) > 0: 57 | while len(pending_txes) > 0: 58 | pending_txes[0].cancel(gas_price=increasing_gas) 59 | # After the synchronous cancel, wait to see if subsequent transactions get mined 60 | time.sleep(15) 61 | pending_txes = get_pending_transactions(web3) 62 | else: 63 | logging.info(f"No pending transactions were found; submitting {stuck_txes_to_submit}") 64 | for i in range(1, stuck_txes_to_submit+1): 65 | self._run_future(weth.deposit(Wad(i)).transact_async(gas_price=FixedGasPrice(int(0.4 * i * GWEI)))) 66 | time.sleep(2) 67 | 68 | self.shutdown() 69 | 70 | def startup(self): 71 | pass 72 | 73 | def shutdown(self): 74 | pass 75 | 76 | @staticmethod 77 | def _run_future(future): 78 | def worker(): 79 | loop = asyncio.new_event_loop() 80 | asyncio.set_event_loop(loop) 81 | try: 82 | asyncio.get_event_loop().run_until_complete(future) 83 | finally: 84 | loop.close() 85 | 86 | thread = threading.Thread(target=worker, daemon=True) 87 | thread.start() 88 | 89 | 90 | if __name__ == '__main__': 91 | TestApp().main() 92 | -------------------------------------------------------------------------------- /pymaker/abi/DSAuth.bin: -------------------------------------------------------------------------------- 1 | 608060405234801561001057600080fd5b5033600160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055503373ffffffffffffffffffffffffffffffffffffffff167fce241d7ca1f669fee44b6fc00b8eba2df3bb514eed0f6f668f8f89096e81ed9460405160405180910390a26106cd806100a46000396000f3fe608060405234801561001057600080fd5b506004361061004c5760003560e01c806313af4035146100515780637a9e5e4b146100955780638da5cb5b146100d9578063bf7e214f14610123575b600080fd5b6100936004803603602081101561006757600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919050505061016d565b005b6100d7600480360360208110156100ab57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff1690602001909291905050506102b6565b005b6100e16103fd565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b61012b610423565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b61019b336000357fffffffff0000000000000000000000000000000000000000000000000000000016610448565b61020d576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260148152602001807f64732d617574682d756e617574686f72697a656400000000000000000000000081525060200191505060405180910390fd5b80600160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff167fce241d7ca1f669fee44b6fc00b8eba2df3bb514eed0f6f668f8f89096e81ed9460405160405180910390a250565b6102e4336000357fffffffff0000000000000000000000000000000000000000000000000000000016610448565b610356576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260148152602001807f64732d617574682d756e617574686f72697a656400000000000000000000000081525060200191505060405180910390fd5b806000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff167f1abebea81bfa2637f28358c371278fb15ede7ea8dd28d2e03b112ff6d936ada460405160405180910390a250565b600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b60003073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff161415610487576001905061069b565b600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff1614156104e6576001905061069b565b600073ffffffffffffffffffffffffffffffffffffffff166000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff161415610545576000905061069b565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663b70096138430856040518463ffffffff1660e01b8152600401808473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020018373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19167bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19168152602001935050505060206040518083038186803b15801561065d57600080fd5b505afa158015610671573d6000803e3d6000fd5b505050506040513d602081101561068757600080fd5b810190808051906020019092919050505090505b9291505056fea165627a7a72305820185e07675ea15995cd10a2394a35492441f7f0c23183422fa7b0f937153825ea0029 -------------------------------------------------------------------------------- /tests/manual_test_node.py: -------------------------------------------------------------------------------- 1 | # This file is part of Maker Keeper Framework. 2 | # 3 | # Copyright (C) 2020 EdNoepel 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU Affero General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU Affero General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU Affero General Public License 16 | # along with this program. If not, see . 17 | 18 | import logging 19 | import os 20 | import sys 21 | from web3 import Web3, HTTPProvider 22 | 23 | from pymaker import Address 24 | from pymaker.lifecycle import Lifecycle 25 | from pymaker.deployment import DssDeployment 26 | from pymaker.keys import register_keys 27 | from pymaker.numeric import Wad 28 | 29 | logging.basicConfig(format='%(asctime)-15s %(levelname)-8s %(message)s', level=logging.DEBUG) 30 | # reduce logspew 31 | logging.getLogger('urllib3').setLevel(logging.INFO) 32 | logging.getLogger("web3").setLevel(logging.INFO) 33 | logging.getLogger("asyncio").setLevel(logging.INFO) 34 | logging.getLogger("requests").setLevel(logging.INFO) 35 | 36 | endpoint_uri = sys.argv[1] # ex: https://localhost:8545 37 | web3 = Web3(HTTPProvider(endpoint_uri=endpoint_uri, request_kwargs={"timeout": 30})) 38 | if len(sys.argv) > 3: 39 | web3.eth.defaultAccount = sys.argv[2] # ex: 0x0000000000000000000000000000000aBcdef123 40 | register_keys(web3, [sys.argv[3]]) # ex: key_file=~keys/default-account.json,pass_file=~keys/default-account.pass 41 | our_address = Address(web3.eth.defaultAccount) 42 | run_transactions = True 43 | elif len(sys.argv) > 2: 44 | our_address = Address(sys.argv[2]) 45 | run_transactions = False 46 | else: 47 | our_address = None 48 | run_transactions = False 49 | 50 | mcd = DssDeployment.from_node(web3) 51 | collateral = mcd.collaterals['ETH-A'] 52 | ilk = collateral.ilk 53 | if run_transactions: 54 | collateral.approve(our_address) 55 | past_blocks = 100 56 | 57 | 58 | class TestApp: 59 | def __init__(self): 60 | self.amount = Wad(3) 61 | self.joined = Wad(0) 62 | 63 | def main(self): 64 | with Lifecycle(web3) as lifecycle: 65 | lifecycle.on_shutdown(self.on_shutdown) 66 | lifecycle.on_block(self.on_block) 67 | 68 | def on_block(self): 69 | if run_transactions: 70 | logging.info(f"Found block {web3.eth.blockNumber}, joining {self.amount} {ilk.name} to our urn") 71 | collateral.gem.deposit(self.amount).transact() 72 | assert collateral.adapter.join(our_address, self.amount).transact() 73 | self.joined += self.amount 74 | else: 75 | logging.info(f"Found block; web3.eth.blockNumber={web3.eth.blockNumber}") 76 | if our_address: 77 | logging.info(f"Urn balance is {mcd.vat.gem(ilk, our_address)} {ilk.name}") 78 | # self.request_history() 79 | 80 | def request_history(self): 81 | logs = mcd.vat.past_frobs(web3.eth.blockNumber - past_blocks) 82 | logging.info(f"Found {len(logs)} frobs in the past {past_blocks} blocks") 83 | 84 | def on_shutdown(self): 85 | if run_transactions and self.joined > Wad(0): 86 | logging.info(f"Exiting {self.joined} {ilk.name} from our urn") 87 | assert collateral.adapter.exit(our_address, self.joined).transact() 88 | assert collateral.gem.withdraw(self.joined).transact() 89 | 90 | 91 | if __name__ == '__main__': 92 | TestApp().main() 93 | -------------------------------------------------------------------------------- /pymaker/cdpmanager.py: -------------------------------------------------------------------------------- 1 | 2 | # This file is part of Maker Keeper Framework. 3 | # 4 | # Copyright (C) 2017-2020 ith-harvey 5 | # 6 | # This program is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU Affero General Public License as published by 8 | # the Free Software Foundation, either version 3 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # This program is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU Affero General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU Affero General Public License 17 | # along with this program. If not, see . 18 | 19 | 20 | from web3 import Web3 21 | from pymaker import Address, Contract, Transact 22 | from pymaker.dss import Ilk, Urn, Vat 23 | from pymaker.numeric import Wad 24 | 25 | 26 | class CdpManager(Contract): 27 | """A client for the `DSCdpManger` contract, which is a wrapper around the cdp system, for easier use. 28 | 29 | Ref. 30 | """ 31 | 32 | abi = Contract._load_abi(__name__, 'abi/DssCdpManager.abi') 33 | bin = Contract._load_bin(__name__, 'abi/DssCdpManager.bin') 34 | 35 | def __init__(self, web3: Web3, address: Address): 36 | assert isinstance(web3, Web3) 37 | assert isinstance(address, Address) 38 | 39 | self.web3 = web3 40 | self.address = address 41 | self._contract = self._get_contract(web3, self.abi, address) 42 | self.vat = Vat(self.web3, Address(self._contract.functions.vat().call())) 43 | 44 | def open(self, ilk: Ilk, address: Address) -> Transact: 45 | assert isinstance(ilk, Ilk) 46 | assert isinstance(address, Address) 47 | 48 | return Transact(self, self.web3, self.abi, self.address, self._contract, 'open', 49 | [ilk.toBytes(), address.address]) 50 | 51 | def urn(self, cdpid: int) -> Urn: 52 | '''Returns Urn for respective CDP ID''' 53 | assert isinstance(cdpid, int) 54 | 55 | urn_address = Address(self._contract.functions.urns(cdpid).call()) 56 | ilk = self.ilk(cdpid) 57 | urn = self.vat.urn(ilk, Address(urn_address)) 58 | 59 | return urn 60 | 61 | def owns(self, cdpid: int) -> Address: 62 | '''Returns owner Address of respective CDP ID''' 63 | assert isinstance(cdpid, int) 64 | 65 | owner = Address(self._contract.functions.owns(cdpid).call()) 66 | return owner 67 | 68 | def ilk(self, cdpid: int) -> Ilk: 69 | '''Returns Ilk for respective CDP ID''' 70 | assert isinstance(cdpid, int) 71 | 72 | ilk = Ilk.fromBytes(self._contract.functions.ilks(cdpid).call()) 73 | return ilk 74 | 75 | def first(self, address: Address) -> int: 76 | '''Returns first CDP Id created by owner address''' 77 | assert isinstance(address, Address) 78 | 79 | cdpid = int(self._contract.functions.first(address.address).call()) 80 | return cdpid 81 | 82 | def last(self, address: Address) -> int: 83 | '''Returns last CDP Id created by owner address''' 84 | assert isinstance(address, Address) 85 | 86 | cdpid = self._contract.functions.last(address.address).call() 87 | return int(cdpid) 88 | 89 | def count(self, address: Address) -> int: 90 | '''Returns number of CDP's created using the DS-Cdp-Manager contract specifically''' 91 | assert isinstance(address, Address) 92 | 93 | count = int(self._contract.functions.count(address.address).call()) 94 | return count 95 | 96 | def __repr__(self): 97 | return f"CdpManager('{self.address}')" 98 | -------------------------------------------------------------------------------- /tests/test_savings.py: -------------------------------------------------------------------------------- 1 | # This file is part of Maker Keeper Framework. 2 | # 3 | # Copyright (C) 2019 grandizzy 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU Affero General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU Affero General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU Affero General Public License 16 | # along with this program. If not, see . 17 | 18 | import pytest 19 | 20 | from pymaker import Address 21 | from pymaker.deployment import DssDeployment 22 | from pymaker.dsr import Dsr 23 | from pymaker.numeric import Wad, Ray, Rad 24 | 25 | from tests.test_dss import wrap_eth, frob 26 | 27 | 28 | @pytest.fixture 29 | def dsr(our_address: Address, mcd: DssDeployment) -> Dsr: 30 | return Dsr(mcd, our_address) 31 | 32 | 33 | @pytest.mark.dependency() 34 | def test_proxy(dsr): 35 | assert dsr.has_proxy() is False 36 | 37 | dsr.build_proxy().transact() 38 | assert dsr.has_proxy() is True 39 | 40 | 41 | @pytest.mark.dependency(depends=['test_proxy']) 42 | def test_join_and_exit(dsr): 43 | proxy = dsr.get_proxy() 44 | assert dsr.get_balance(proxy.address) == Wad.from_number(0) 45 | 46 | mcd = dsr.mcd 47 | 48 | # create a vault 49 | collateral = mcd.collaterals['ETH-C'] 50 | wrap_eth(mcd, dsr.owner, Wad.from_number(2)) 51 | collateral.approve(dsr.owner) 52 | assert collateral.adapter.join(dsr.owner, Wad.from_number(2)).transact(from_address=dsr.owner) 53 | frob(mcd, collateral, dsr.owner, dink=Wad.from_number(2), dart=Wad(0)) 54 | dart = Wad.from_number(100) 55 | frob(mcd, collateral, dsr.owner, dink=Wad(0), dart=dart) 56 | 57 | # mint and withdraw all the Dai 58 | mcd.approve_dai(dsr.owner) 59 | assert mcd.dai_adapter.exit(dsr.owner, dart).transact(from_address=dsr.owner) 60 | assert mcd.dai.balance_of(dsr.owner) == dart 61 | 62 | initial_dai_balance = mcd.dai.balance_of(dsr.owner) 63 | assert initial_dai_balance >= Wad.from_number(100) 64 | assert dsr.get_balance(proxy.address) == Wad.from_number(0) 65 | 66 | # approve Proxy to use 100 DAI from account 67 | mcd.dai.approve(proxy.address, Wad.from_number(100)).transact(from_address=dsr.owner) 68 | 69 | # join 100 DAI in DSR 70 | assert dsr.join(Wad.from_number(100), proxy).transact(from_address=dsr.owner) 71 | assert mcd.dai.balance_of(dsr.owner) == initial_dai_balance - Wad.from_number(100) 72 | assert round(dsr.get_balance(proxy.address)) == Wad.from_number(100) 73 | assert mcd.pot.drip().transact() 74 | 75 | # exit 33 DAI from DSR 76 | assert dsr.exit(Wad.from_number(33), proxy).transact(from_address=dsr.owner) 77 | assert round(mcd.dai.balance_of(dsr.owner)) == round(initial_dai_balance) - Wad.from_number(100) + Wad.from_number(33) 78 | assert round(dsr.get_balance(proxy.address)) == Wad.from_number(67) 79 | assert mcd.pot.drip().transact() 80 | 81 | # exit remaining DAI from DSR and join to vat 82 | assert dsr.exit_all(proxy).transact(from_address=dsr.owner) 83 | assert round(mcd.dai.balance_of(dsr.owner)) == round(initial_dai_balance) 84 | assert dsr.get_balance(proxy.address) == Wad.from_number(0) 85 | assert mcd.dai_adapter.join(dsr.owner, mcd.dai.balance_of(dsr.owner)).transact(from_address=dsr.owner) 86 | 87 | # repay the vault 88 | assert collateral.ilk.dust == Rad(0) 89 | wipe: Wad = mcd.vat.get_wipe_all_dart(collateral.ilk, dsr.owner) 90 | frob(mcd, collateral, dsr.owner, dink=Wad(0), dart=wipe*-1) 91 | -------------------------------------------------------------------------------- /tests/test_feed.py: -------------------------------------------------------------------------------- 1 | # This file is part of Maker Keeper Framework. 2 | # 3 | # Copyright (C) 2017-2018 reverendus 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU Affero General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU Affero General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU Affero General Public License 16 | # along with this program. If not, see . 17 | 18 | import pytest 19 | from web3 import Web3, HTTPProvider 20 | 21 | from pymaker import Address 22 | from pymaker.feed import DSValue 23 | 24 | 25 | class TestDSValue: 26 | def setup_method(self): 27 | self.web3 = Web3(HTTPProvider("http://localhost:8555")) 28 | self.web3.eth.defaultAccount = self.web3.eth.accounts[0] 29 | self.dsvalue = DSValue.deploy(self.web3) 30 | 31 | def test_fail_when_no_contract_under_that_address(self): 32 | # expect 33 | with pytest.raises(Exception): 34 | DSValue(web3=self.web3, address=Address('0xdeadadd1e5500000000000000000000000000000')) 35 | 36 | def test_address(self): 37 | assert isinstance(self.dsvalue.address, Address) 38 | 39 | def test_no_value_after_deploy(self): 40 | # expect 41 | assert self.dsvalue.has_value() is False 42 | with pytest.raises(Exception): 43 | self.dsvalue.read() 44 | with pytest.raises(Exception): 45 | self.dsvalue.read_as_int() 46 | with pytest.raises(Exception): 47 | self.dsvalue.read_as_hex() 48 | 49 | def test_poke(self): 50 | # when 51 | self.dsvalue.poke(bytes([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 52 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 53 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 54 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xf4])).transact() 55 | 56 | # then 57 | assert self.dsvalue.has_value() is True 58 | assert self.dsvalue.read_as_int() == 500 59 | assert self.dsvalue.read() == bytes([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 60 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 61 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 62 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xf4]) 63 | 64 | def test_poke_with_int(self): 65 | # when 66 | self.dsvalue.poke_with_int(500).transact() 67 | 68 | # then 69 | assert self.dsvalue.has_value() is True 70 | assert self.dsvalue.read_as_int() == 500 71 | assert self.dsvalue.read() == bytes([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 72 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 73 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 74 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xf4]) 75 | 76 | def test_void(self): 77 | # given 78 | self.dsvalue.poke_with_int(250).transact() 79 | assert self.dsvalue.has_value() is True 80 | 81 | # when 82 | self.dsvalue.void().transact() 83 | 84 | # then 85 | assert self.dsvalue.has_value() is False 86 | 87 | def test_should_have_printable_representation(self): 88 | assert repr(self.dsvalue) == f"DSValue('{self.dsvalue.address}')" 89 | -------------------------------------------------------------------------------- /pymaker/abi/ExchangeV2-ERC20Proxy.abi: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "constant": false, 4 | "inputs": [ 5 | { 6 | "name": "target", 7 | "type": "address" 8 | } 9 | ], 10 | "name": "addAuthorizedAddress", 11 | "outputs": [], 12 | "payable": false, 13 | "stateMutability": "nonpayable", 14 | "type": "function" 15 | }, 16 | { 17 | "constant": true, 18 | "inputs": [ 19 | { 20 | "name": "", 21 | "type": "uint256" 22 | } 23 | ], 24 | "name": "authorities", 25 | "outputs": [ 26 | { 27 | "name": "", 28 | "type": "address" 29 | } 30 | ], 31 | "payable": false, 32 | "stateMutability": "view", 33 | "type": "function" 34 | }, 35 | { 36 | "constant": false, 37 | "inputs": [ 38 | { 39 | "name": "target", 40 | "type": "address" 41 | } 42 | ], 43 | "name": "removeAuthorizedAddress", 44 | "outputs": [], 45 | "payable": false, 46 | "stateMutability": "nonpayable", 47 | "type": "function" 48 | }, 49 | { 50 | "constant": true, 51 | "inputs": [], 52 | "name": "owner", 53 | "outputs": [ 54 | { 55 | "name": "", 56 | "type": "address" 57 | } 58 | ], 59 | "payable": false, 60 | "stateMutability": "view", 61 | "type": "function" 62 | }, 63 | { 64 | "constant": false, 65 | "inputs": [ 66 | { 67 | "name": "target", 68 | "type": "address" 69 | }, 70 | { 71 | "name": "index", 72 | "type": "uint256" 73 | } 74 | ], 75 | "name": "removeAuthorizedAddressAtIndex", 76 | "outputs": [], 77 | "payable": false, 78 | "stateMutability": "nonpayable", 79 | "type": "function" 80 | }, 81 | { 82 | "constant": true, 83 | "inputs": [], 84 | "name": "getProxyId", 85 | "outputs": [ 86 | { 87 | "name": "", 88 | "type": "bytes4" 89 | } 90 | ], 91 | "payable": false, 92 | "stateMutability": "pure", 93 | "type": "function" 94 | }, 95 | { 96 | "constant": true, 97 | "inputs": [ 98 | { 99 | "name": "", 100 | "type": "address" 101 | } 102 | ], 103 | "name": "authorized", 104 | "outputs": [ 105 | { 106 | "name": "", 107 | "type": "bool" 108 | } 109 | ], 110 | "payable": false, 111 | "stateMutability": "view", 112 | "type": "function" 113 | }, 114 | { 115 | "constant": true, 116 | "inputs": [], 117 | "name": "getAuthorizedAddresses", 118 | "outputs": [ 119 | { 120 | "name": "", 121 | "type": "address[]" 122 | } 123 | ], 124 | "payable": false, 125 | "stateMutability": "view", 126 | "type": "function" 127 | }, 128 | { 129 | "constant": false, 130 | "inputs": [ 131 | { 132 | "name": "newOwner", 133 | "type": "address" 134 | } 135 | ], 136 | "name": "transferOwnership", 137 | "outputs": [], 138 | "payable": false, 139 | "stateMutability": "nonpayable", 140 | "type": "function" 141 | }, 142 | { 143 | "payable": false, 144 | "stateMutability": "nonpayable", 145 | "type": "fallback" 146 | }, 147 | { 148 | "anonymous": false, 149 | "inputs": [ 150 | { 151 | "indexed": true, 152 | "name": "target", 153 | "type": "address" 154 | }, 155 | { 156 | "indexed": true, 157 | "name": "caller", 158 | "type": "address" 159 | } 160 | ], 161 | "name": "AuthorizedAddressAdded", 162 | "type": "event" 163 | }, 164 | { 165 | "anonymous": false, 166 | "inputs": [ 167 | { 168 | "indexed": true, 169 | "name": "target", 170 | "type": "address" 171 | }, 172 | { 173 | "indexed": true, 174 | "name": "caller", 175 | "type": "address" 176 | } 177 | ], 178 | "name": "AuthorizedAddressRemoved", 179 | "type": "event" 180 | } 181 | ] -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | pymaker API 2 | =========== 3 | 4 | The `pymaker` API exists to provide a simple way of interacting with Maker smart contracts. 5 | 6 | It was designed to simplify and facilitate creation of external profit-seeking agents, usually called keepers, 7 | that operate around the stablecoin set of smart contracts. The API can also be used to automate certain tasks for 8 | other entities involved in the platform, like DAI issuers or traders. 9 | 10 | 11 | General 12 | ------- 13 | 14 | Address 15 | ~~~~~~~ 16 | 17 | .. autoclass:: pymaker.Address 18 | :members: 19 | 20 | Transact 21 | ~~~~~~~~ 22 | 23 | .. autoclass:: pymaker.Transact 24 | :members: 25 | 26 | Calldata 27 | ~~~~~~~~ 28 | 29 | .. autoclass:: pymaker.Calldata 30 | :members: 31 | 32 | Invocation 33 | ~~~~~~~~~~ 34 | 35 | .. autoclass:: pymaker.Invocation 36 | :members: 37 | 38 | Receipt 39 | ~~~~~~~ 40 | 41 | .. autoclass:: pymaker.Receipt 42 | :members: 43 | 44 | Transfer 45 | ~~~~~~~~ 46 | 47 | .. autoclass:: pymaker.Transfer 48 | :members: 49 | 50 | 51 | Numeric types 52 | ------------- 53 | 54 | Most of the numeric data throughout the entire platform is kept as either `Wad` (18-digit precision type) 55 | or `Ray` (27-digit precision type). 56 | 57 | Wad 58 | ~~~ 59 | 60 | .. autoclass:: pymaker.numeric.Wad 61 | :members: 62 | 63 | Ray 64 | ~~~ 65 | 66 | .. autoclass:: pymaker.numeric.Ray 67 | :members: 68 | 69 | 70 | Gas price 71 | --------- 72 | 73 | .. autoclass:: pymaker.gas.GasPrice 74 | :members: 75 | 76 | The following implementations of `GasPrice` are available: 77 | 78 | DefaultGasPrice 79 | ~~~~~~~~~~~~~~~ 80 | 81 | .. autoclass:: pymaker.gas.DefaultGasPrice 82 | :members: 83 | 84 | FixedGasPrice 85 | ~~~~~~~~~~~~~ 86 | 87 | .. autoclass:: pymaker.gas.FixedGasPrice 88 | :members: 89 | 90 | IncreasingGasPrice 91 | ~~~~~~~~~~~~~~~~~~ 92 | 93 | .. autoclass:: pymaker.gas.IncreasingGasPrice 94 | :members: 95 | 96 | 97 | Approvals 98 | --------- 99 | 100 | .. automodule:: pymaker.approval 101 | :members: 102 | 103 | 104 | Contracts 105 | --------- 106 | 107 | DAI Stablecoin 108 | ~~~~~~~~~~~~~~ 109 | 110 | Tub 111 | """ 112 | 113 | .. autoclass:: pymaker.sai.Tub 114 | :members: 115 | 116 | Tap 117 | """ 118 | 119 | .. autoclass:: pymaker.sai.Tap 120 | :members: 121 | 122 | Top 123 | """ 124 | 125 | .. autoclass:: pymaker.sai.Top 126 | :members: 127 | 128 | Vox 129 | """ 130 | 131 | .. autoclass:: pymaker.sai.Vox 132 | :members: 133 | 134 | ERC20 135 | ~~~~~ 136 | 137 | ERC20Token 138 | """""""""" 139 | 140 | .. autoclass:: pymaker.token.ERC20Token 141 | :members: 142 | 143 | DSToken 144 | """"""" 145 | 146 | .. autoclass:: pymaker.token.DSToken 147 | :members: 148 | 149 | DSEthToken 150 | """""""""" 151 | 152 | .. autoclass:: pymaker.token.DSEthToken 153 | :members: 154 | 155 | 156 | Exchanges 157 | ~~~~~~~~~ 158 | 159 | `OaaisDEX`, `EtherDelta` and `0x` are decentralized exchanges which also provide some arbitrage opportunities 160 | for profit-seeking agents. Because of that an API has been created around them as well. Also an API for 161 | the `Bibox` centralized exchange is present. 162 | 163 | OasisDEX 164 | """""""" 165 | 166 | .. automodule:: pymaker.oasis 167 | :members: 168 | 169 | EtherDelta 170 | """""""""" 171 | 172 | .. automodule:: pymaker.etherdelta 173 | :members: 174 | 175 | 0x 176 | "" 177 | 178 | .. automodule:: pymaker.zrx 179 | :members: 180 | 181 | Bibox 182 | """"" 183 | 184 | .. automodule:: pymaker.bibox 185 | :members: 186 | 187 | 188 | Authentication 189 | ~~~~~~~~~~~~~~ 190 | 191 | DSGuard 192 | """"""" 193 | 194 | .. autoclass:: pymaker.auth.DSGuard 195 | :members: 196 | 197 | 198 | DSValue 199 | ~~~~~~~ 200 | 201 | .. autoclass:: pymaker.feed.DSValue 202 | :members: 203 | 204 | DSVault 205 | ~~~~~~~ 206 | 207 | .. autoclass:: pymaker.vault.DSVault 208 | :members: 209 | 210 | 211 | 212 | Atomic transactions 213 | ------------------- 214 | 215 | TxManager 216 | ~~~~~~~~~ 217 | 218 | .. autoclass:: pymaker.transactional.TxManager 219 | :members: 220 | -------------------------------------------------------------------------------- /tests/test_auth.py: -------------------------------------------------------------------------------- 1 | # This file is part of Maker Keeper Framework. 2 | # 3 | # Copyright (C) 2017-2018 reverendus 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU Affero General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU Affero General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU Affero General Public License 16 | # along with this program. If not, see . 17 | 18 | import pytest 19 | from web3 import Web3, HTTPProvider 20 | 21 | from pymaker import Address 22 | from pymaker.auth import DSGuard, DSAuth 23 | from pymaker.util import hexstring_to_bytes 24 | 25 | 26 | class TestDSGuard: 27 | def setup_method(self): 28 | self.web3 = Web3(HTTPProvider("http://localhost:8555")) 29 | self.web3.eth.defaultAccount = self.web3.eth.accounts[0] 30 | self.our_address = Address(self.web3.eth.defaultAccount) 31 | self.ds_guard = DSGuard.deploy(self.web3) 32 | 33 | def can_call(self, src: str, dst: str, sig: str) -> bool: 34 | return self.ds_guard._contract.functions.canCall(src, dst, hexstring_to_bytes(sig)).call() 35 | 36 | def test_fail_when_no_contract_under_that_address(self): 37 | # expect 38 | with pytest.raises(Exception): 39 | DSGuard(web3=self.web3, address=Address('0xdeadadd1e5500000000000000000000000000000')) 40 | 41 | def test_no_permit_by_default(self): 42 | # expect 43 | assert not self.can_call(src='0x1111111111222222222211111111112222222222', 44 | dst='0x3333333333444444444433333333334444444444', 45 | sig='0xab121fd7') 46 | 47 | def test_permit_any_to_any_with_any_sig(self): 48 | # when 49 | self.ds_guard.permit(DSGuard.ANY, DSGuard.ANY, DSGuard.ANY).transact() 50 | 51 | # then 52 | assert self.can_call(src='0x1111111111222222222211111111112222222222', 53 | dst='0x3333333333444444444433333333334444444444', 54 | sig='0xab121fd7') 55 | 56 | def test_permit_specific_addresses_and_sig(self): 57 | # when 58 | self.ds_guard.permit(src=Address('0x1111111111222222222211111111112222222222'), 59 | dst=Address('0x3333333333444444444433333333334444444444'), 60 | sig=hexstring_to_bytes('0xab121fd7')).transact() 61 | 62 | # then 63 | assert self.can_call(src='0x1111111111222222222211111111112222222222', 64 | dst='0x3333333333444444444433333333334444444444', 65 | sig='0xab121fd7') 66 | 67 | # and 68 | assert not self.can_call(src='0x3333333333444444444433333333334444444444', 69 | dst='0x1111111111222222222211111111112222222222', 70 | sig='0xab121fd7') # different addresses 71 | assert not self.can_call(src='0x1111111111222222222211111111112222222222', 72 | dst='0x3333333333444444444433333333334444444444', 73 | sig='0xab121fd8') # different sig 74 | 75 | 76 | class TestDSAuth: 77 | def setup_method(self): 78 | self.web3 = Web3(HTTPProvider("http://localhost:8555")) 79 | self.web3.eth.defaultAccount = self.web3.eth.accounts[0] 80 | self.our_address = Address(self.web3.eth.defaultAccount) 81 | 82 | self.ds_auth = DSAuth.deploy(self.web3) 83 | 84 | @pytest.mark.skip(reason="calls to ABI/BIN are not working on ganache") 85 | def test_owner(self): 86 | owner = self.ds_auth.get_owner() 87 | assert isinstance(owner, Address) 88 | assert owner == self.web3.eth.accounts[0] 89 | 90 | assert self.ds_auth.set_owner(self.web3.eth.accounts[1]) 91 | assert self.ds_auth.get_owner() == self.web3.eth.accounts[1] 92 | -------------------------------------------------------------------------------- /pymaker/dsrmanager.py: -------------------------------------------------------------------------------- 1 | 2 | # This file is part of Maker Keeper Framework. 3 | # 4 | # Copyright (C) 2020 Maker Ecosystem Growth Holdings, INC 5 | # 6 | # This program is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU Affero General Public License as published by 8 | # the Free Software Foundation, either version 3 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # This program is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU Affero General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU Affero General Public License 17 | # along with this program. If not, see . 18 | 19 | 20 | from web3 import Web3 21 | from pymaker import Address, Contract, Transact 22 | from pymaker.dss import Pot 23 | from pymaker.join import DaiJoin 24 | from pymaker.numeric import Wad, Rad 25 | from pymaker.token import DSToken 26 | 27 | 28 | class DsrManager(Contract): 29 | """ 30 | A client for the `DsrManger` contract, which reduces the need for proxies 31 | when interacting with the Pot contract. 32 | 33 | Ref. 34 | """ 35 | 36 | abi = Contract._load_abi(__name__, 'abi/DsrManager.abi') 37 | bin = Contract._load_bin(__name__, 'abi/DsrManager.bin') 38 | 39 | def __init__(self, web3: Web3, address: Address): 40 | assert isinstance(web3, Web3) 41 | assert isinstance(address, Address) 42 | 43 | self.web3 = web3 44 | self.address = address 45 | self._contract = self._get_contract(web3, self.abi, address) 46 | 47 | def pot(self) -> Pot: 48 | address = Address(self._contract.functions.pot().call()) 49 | return Pot(self.web3, address) 50 | 51 | def dai(self) -> DSToken: 52 | address = Address(self._contract.functions.dai().call()) 53 | return DSToken(self.web3, address) 54 | 55 | def dai_adapter(self) -> DaiJoin: 56 | address = Address(self._contract.functions.daiJoin().call()) 57 | return DaiJoin(self.web3, address) 58 | 59 | def supply(self) -> Wad: 60 | """Total supply of pie locked in Pot through DsrManager""" 61 | return Wad(self._contract.functions.supply().call()) 62 | 63 | def pie_of(self, usr: Address) -> Wad: 64 | """Pie balance of a given usr address""" 65 | assert isinstance(usr, Address) 66 | 67 | return Wad(self._contract.functions.pieOf(usr.address).call()) 68 | 69 | def dai_of(self, usr: Address) -> Rad: 70 | """ 71 | Internal Dai balance of a given usr address - current Chi is used 72 | i.e. Dai balance potentially stale 73 | """ 74 | assert isinstance(usr, Address) 75 | 76 | pie = self.pie_of(usr) 77 | chi = self.pot().chi() 78 | 79 | dai = Rad(pie) * Rad(chi) 80 | 81 | return dai 82 | 83 | def join(self, dst: Address, dai: Wad) -> Transact: 84 | """Lock a given amount of ERC20 Dai into the DSR Contract and give to dst address """ 85 | assert isinstance(dst, Address) 86 | assert isinstance(dai, Wad) 87 | 88 | return Transact(self, self.web3, self.abi, self.address, self._contract, 'join', 89 | [dst.address, dai.value]) 90 | 91 | def exit(self, dst: Address, dai: Wad) -> Transact: 92 | """ Free a given amount of ERC20 Dai from the DSR Contract and give to dst address """ 93 | assert isinstance(dst, Address) 94 | assert isinstance(dai, Wad) 95 | 96 | return Transact(self, self.web3, self.abi, self.address, self._contract, 'exit', 97 | [dst.address, dai.value]) 98 | 99 | def exitAll(self, dst: Address) -> Transact: 100 | """ Free all ERC20 Dai from the DSR Contract and give to dst address """ 101 | assert isinstance(dst, Address) 102 | 103 | return Transact(self, self.web3, self.abi, self.address, self._contract, 'exitAll', [dst.address]) 104 | 105 | def __repr__(self): 106 | return f"DsrManager('{self.address}')" 107 | -------------------------------------------------------------------------------- /pymaker/abi/OSM.abi: -------------------------------------------------------------------------------- 1 | [{"inputs":[{"internalType":"address","name":"src_","type":"address"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"anonymous":true,"inputs":[{"indexed":true,"internalType":"bytes4","name":"sig","type":"bytes4"},{"indexed":true,"internalType":"address","name":"usr","type":"address"},{"indexed":true,"internalType":"bytes32","name":"arg1","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"arg2","type":"bytes32"},{"indexed":false,"internalType":"bytes","name":"data","type":"bytes"}],"name":"LogNote","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"bytes32","name":"val","type":"bytes32"}],"name":"LogValue","type":"event"},{"constant":true,"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"bud","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"src_","type":"address"}],"name":"change","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"usr","type":"address"}],"name":"deny","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"address[]","name":"a","type":"address[]"}],"name":"diss","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"a","type":"address"}],"name":"diss","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"hop","outputs":[{"internalType":"uint16","name":"","type":"uint16"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"address[]","name":"a","type":"address[]"}],"name":"kiss","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"a","type":"address"}],"name":"kiss","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"pass","outputs":[{"internalType":"bool","name":"ok","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"peek","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"},{"internalType":"bool","name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"peep","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"},{"internalType":"bool","name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"poke","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"read","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"usr","type":"address"}],"name":"rely","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"src","outputs":[{"internalType":"address","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"start","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"uint16","name":"ts","type":"uint16"}],"name":"step","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[],"name":"stop","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"stopped","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"void","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"wards","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"zzz","outputs":[{"internalType":"uint64","name":"","type":"uint64"}],"payable":false,"stateMutability":"view","type":"function"}] 2 | -------------------------------------------------------------------------------- /tests/manual_test_mcd.py: -------------------------------------------------------------------------------- 1 | # This file is part of Maker Keeper Framework. 2 | # 3 | # Copyright (C) 2019-2020 EdNoepel 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU Affero General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU Affero General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU Affero General Public License 16 | # along with this program. If not, see . 17 | 18 | import os 19 | import sys 20 | from web3 import Web3, HTTPProvider 21 | 22 | from pymaker import Address 23 | from pymaker.deployment import DssDeployment 24 | from pymaker.keys import register_keys 25 | from pymaker.numeric import Wad 26 | from pymaker.oracles import OSM 27 | 28 | assert os.environ['ETH_RPC_URL'] 29 | web3 = Web3(HTTPProvider(endpoint_uri=os.environ['ETH_RPC_URL'], request_kwargs={"timeout": 10})) 30 | our_address = None 31 | if len(sys.argv) > 1: 32 | web3.eth.defaultAccount = sys.argv[1] # ex: 0x0000000000000000000000000000000aBcdef123 33 | our_address = Address(web3.eth.defaultAccount) 34 | if len(sys.argv) > 2: 35 | register_keys(web3, [sys.argv[2]]) # ex: key_file=~keys/default-account.json,pass_file=~keys/default-account.pass 36 | run_transactions = True 37 | else: 38 | run_transactions = False 39 | mcd = DssDeployment.from_node(web3) 40 | 41 | # Print a list of collaterals available for this deployment of MCD 42 | for collateral in mcd.collaterals.values(): 43 | osm: OSM = collateral.pip 44 | liquidation = "clip" if collateral.clipper else "flip" 45 | print(f"Found {collateral.ilk.name:>15} - {collateral.gem.name():<21} with {collateral.adapter.dec():>2} decimals, " 46 | f"OSM price {round(float(osm.peek()), 3):>14}, " 47 | f"rate {float(collateral.ilk.rate):<18}, using {liquidation} liquidations") 48 | 49 | # Choose the desired collateral; in this case we'll wrap some Eth 50 | collateral = mcd.collaterals['ETH-A'] 51 | ilk = collateral.ilk 52 | 53 | if run_transactions: 54 | # Determine minimum amount of Dai which can be drawn 55 | dai_amount = Wad(ilk.dust) 56 | # Set an amount of collateral to join and an amount of Dai to draw 57 | collateral_amount = Wad.from_number(105) 58 | if collateral.gem.balance_of(our_address) > collateral_amount: 59 | if collateral.ilk.name.startswith("ETH"): 60 | # Wrap ETH to produce WETH 61 | assert collateral.gem.deposit(collateral_amount).transact() 62 | 63 | # Add collateral and allocate the desired amount of Dai 64 | collateral.approve(our_address) 65 | assert collateral.adapter.join(our_address, collateral_amount).transact() 66 | assert mcd.vat.frob(ilk, our_address, dink=collateral_amount, dart=Wad(0)).transact() 67 | assert mcd.vat.frob(ilk, our_address, dink=Wad(0), dart=dai_amount).transact() 68 | print(f"Urn balance: {mcd.vat.urn(ilk, our_address)}") 69 | print(f"Dai balance: {mcd.vat.dai(our_address)}") 70 | 71 | # Mint and withdraw our Dai 72 | mcd.approve_dai(our_address) 73 | assert mcd.dai_adapter.exit(our_address, dai_amount).transact() 74 | print(f"Dai balance after withdrawal: {mcd.vat.dai(our_address)}") 75 | 76 | # Repay (and burn) our Dai 77 | assert mcd.dai_adapter.join(our_address, dai_amount).transact() 78 | print(f"Dai balance after repayment: {mcd.vat.dai(our_address)}") 79 | 80 | # Withdraw our collateral; stability fee accumulation may make these revert 81 | assert mcd.vat.frob(ilk, our_address, dink=Wad(0), dart=dai_amount*-1).transact() 82 | assert mcd.vat.frob(ilk, our_address, dink=collateral_amount*-1, dart=Wad(0)).transact() 83 | assert collateral.adapter.exit(our_address, collateral_amount).transact() 84 | print(f"Dai balance w/o collateral: {mcd.vat.dai(our_address)}") 85 | else: 86 | print(f"Not enough {ilk.name} to join to the vat") 87 | 88 | if our_address: 89 | print(f"Collateral balance: {mcd.vat.gem(ilk, our_address)}") 90 | print(f"Urn balance: {mcd.vat.urn(ilk, our_address)}") 91 | -------------------------------------------------------------------------------- /pymaker/abi/Cat.abi: -------------------------------------------------------------------------------- 1 | [{"inputs":[{"internalType":"address","name":"vat_","type":"address"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"ilk","type":"bytes32"},{"indexed":true,"internalType":"address","name":"urn","type":"address"},{"indexed":false,"internalType":"uint256","name":"ink","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"art","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"tab","type":"uint256"},{"indexed":false,"internalType":"address","name":"flip","type":"address"},{"indexed":false,"internalType":"uint256","name":"id","type":"uint256"}],"name":"Bite","type":"event"},{"anonymous":true,"inputs":[{"indexed":true,"internalType":"bytes4","name":"sig","type":"bytes4"},{"indexed":true,"internalType":"address","name":"usr","type":"address"},{"indexed":true,"internalType":"bytes32","name":"arg1","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"arg2","type":"bytes32"},{"indexed":false,"internalType":"bytes","name":"data","type":"bytes"}],"name":"LogNote","type":"event"},{"constant":false,"inputs":[{"internalType":"bytes32","name":"ilk","type":"bytes32"},{"internalType":"address","name":"urn","type":"address"}],"name":"bite","outputs":[{"internalType":"uint256","name":"id","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"box","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"cage","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"uint256","name":"rad","type":"uint256"}],"name":"claw","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"usr","type":"address"}],"name":"deny","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"bytes32","name":"ilk","type":"bytes32"},{"internalType":"bytes32","name":"what","type":"bytes32"},{"internalType":"uint256","name":"data","type":"uint256"}],"name":"file","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"bytes32","name":"what","type":"bytes32"},{"internalType":"uint256","name":"data","type":"uint256"}],"name":"file","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"bytes32","name":"what","type":"bytes32"},{"internalType":"address","name":"data","type":"address"}],"name":"file","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"bytes32","name":"ilk","type":"bytes32"},{"internalType":"bytes32","name":"what","type":"bytes32"},{"internalType":"address","name":"flip","type":"address"}],"name":"file","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"name":"ilks","outputs":[{"internalType":"address","name":"flip","type":"address"},{"internalType":"uint256","name":"chop","type":"uint256"},{"internalType":"uint256","name":"dunk","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"litter","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"live","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"usr","type":"address"}],"name":"rely","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"vat","outputs":[{"internalType":"contract VatLike","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"vow","outputs":[{"internalType":"contract VowLike","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"wards","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"}] 2 | -------------------------------------------------------------------------------- /tests/manual_test_async_tx.py: -------------------------------------------------------------------------------- 1 | # This file is part of Maker Keeper Framework. 2 | # 3 | # Copyright (C) 2020 EdNoepel 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU Affero General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU Affero General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU Affero General Public License 16 | # along with this program. If not, see . 17 | 18 | import asyncio 19 | import logging 20 | import os 21 | import sys 22 | import threading 23 | import time 24 | 25 | from pymaker import Address, web3_via_http 26 | from pymaker.deployment import DssDeployment 27 | from pymaker.gas import FixedGasPrice, GeometricGasPrice 28 | from pymaker.keys import register_keys 29 | from pymaker.numeric import Wad 30 | 31 | logging.basicConfig(format='%(asctime)-15s %(levelname)-8s %(message)s', level=logging.DEBUG) 32 | # reduce logspew 33 | logging.getLogger('urllib3').setLevel(logging.INFO) 34 | logging.getLogger("web3").setLevel(logging.INFO) 35 | logging.getLogger("asyncio").setLevel(logging.INFO) 36 | logging.getLogger("requests").setLevel(logging.INFO) 37 | 38 | pool_size = int(sys.argv[3]) if len(sys.argv) > 3 else 10 39 | web3 = web3_via_http(endpoint_uri=os.environ['ETH_RPC_URL'], http_pool_size=pool_size) 40 | web3.eth.defaultAccount = sys.argv[1] # ex: 0x0000000000000000000000000000000aBcdef123 41 | register_keys(web3, [sys.argv[2]]) # ex: key_file=~keys/default-account.json,pass_file=~keys/default-account.pass 42 | 43 | mcd = DssDeployment.from_node(web3) 44 | our_address = Address(web3.eth.defaultAccount) 45 | weth = DssDeployment.from_node(web3).collaterals['ETH-A'].gem 46 | 47 | GWEI = 1000000000 48 | slow_gas = GeometricGasPrice(initial_price=int(15 * GWEI), every_secs=42, max_price=200 * GWEI) 49 | fast_gas = GeometricGasPrice(initial_price=int(30 * GWEI), every_secs=42, max_price=200 * GWEI) 50 | 51 | 52 | class TestApp: 53 | def main(self): 54 | self.test_replacement() 55 | self.test_simultaneous() 56 | self.shutdown() 57 | 58 | def test_replacement(self): 59 | first_tx = weth.deposit(Wad(4)) 60 | logging.info(f"Submitting first TX with gas price deliberately too low") 61 | self._run_future(first_tx.transact_async(gas_price=slow_gas)) 62 | time.sleep(0.5) 63 | 64 | second_tx = weth.deposit(Wad(6)) 65 | logging.info(f"Replacing first TX with legitimate gas price") 66 | second_tx.transact(replace=first_tx, gas_price=fast_gas) 67 | 68 | assert first_tx.replaced 69 | 70 | def test_simultaneous(self): 71 | self._run_future(weth.deposit(Wad(1)).transact_async(gas_price=fast_gas)) 72 | self._run_future(weth.deposit(Wad(3)).transact_async(gas_price=fast_gas)) 73 | self._run_future(weth.deposit(Wad(5)).transact_async(gas_price=fast_gas)) 74 | self._run_future(weth.deposit(Wad(7)).transact_async(gas_price=fast_gas)) 75 | time.sleep(33) 76 | 77 | def shutdown(self): 78 | balance = weth.balance_of(our_address) 79 | if Wad(0) < balance < Wad(100): # this account's tiny WETH balance came from this test 80 | logging.info(f"Unwrapping {balance} WETH") 81 | assert weth.withdraw(balance).transact(gas_price=fast_gas) 82 | elif balance >= Wad(22): # user already had a balance, so unwrap what a successful test would have consumed 83 | logging.info(f"Unwrapping 12 WETH") 84 | assert weth.withdraw(Wad(22)).transact(gas_price=fast_gas) 85 | 86 | @staticmethod 87 | def _run_future(future): 88 | def worker(): 89 | loop = asyncio.new_event_loop() 90 | asyncio.set_event_loop(loop) 91 | try: 92 | asyncio.get_event_loop().run_until_complete(future) 93 | finally: 94 | loop.close() 95 | 96 | thread = threading.Thread(target=worker, daemon=True) 97 | thread.start() 98 | 99 | 100 | if __name__ == '__main__': 101 | TestApp().main() 102 | -------------------------------------------------------------------------------- /pymaker/abi/SaiTap.abi: -------------------------------------------------------------------------------- 1 | [{"constant":false,"inputs":[],"name":"heal","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"sin","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"skr","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"owner_","type":"address"}],"name":"setOwner","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[],"name":"vent","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"wad","type":"uint256"}],"name":"cash","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"woe","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"tub","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"wad","type":"uint256"}],"name":"mock","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"wad","type":"uint256"}],"name":"bid","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"joy","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"s2s","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"off","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"vox","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"gap","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"fog","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"authority_","type":"address"}],"name":"setAuthority","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":"sai","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"param","type":"bytes32"},{"name":"val","type":"uint256"}],"name":"mold","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"fix_","type":"uint256"}],"name":"cage","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"fix","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"wad","type":"uint256"}],"name":"bust","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"wad","type":"uint256"}],"name":"boom","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"authority","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"wad","type":"uint256"}],"name":"ask","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"inputs":[{"name":"tub_","type":"address"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"anonymous":true,"inputs":[{"indexed":true,"name":"sig","type":"bytes4"},{"indexed":true,"name":"guy","type":"address"},{"indexed":true,"name":"foo","type":"bytes32"},{"indexed":true,"name":"bar","type":"bytes32"},{"indexed":false,"name":"wad","type":"uint256"},{"indexed":false,"name":"fax","type":"bytes"}],"name":"LogNote","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"authority","type":"address"}],"name":"LogSetAuthority","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"owner","type":"address"}],"name":"LogSetOwner","type":"event"}] -------------------------------------------------------------------------------- /pymaker/auth.py: -------------------------------------------------------------------------------- 1 | # This file is part of Maker Keeper Framework. 2 | # 3 | # Copyright (C) 2017-2018 reverendus 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU Affero General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU Affero General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU Affero General Public License 16 | # along with this program. If not, see . 17 | 18 | from web3 import Web3 19 | 20 | from pymaker import Contract, Address, Transact 21 | from pymaker.util import int_to_bytes32 22 | 23 | 24 | class DSGuard(Contract): 25 | """A client for the `DSGuard` contract. 26 | 27 | You can find the source code of the `DSGuard` contract here: 28 | . 29 | 30 | Attributes: 31 | web3: An instance of `Web` from `web3.py`. 32 | address: Ethereum address of the `DSGuard` contract. 33 | """ 34 | 35 | abi = Contract._load_abi(__name__, 'abi/DSGuard.abi') 36 | bin = Contract._load_bin(__name__, 'abi/DSGuard.bin') 37 | 38 | ANY = int_to_bytes32(2 ** 256 - 1) 39 | 40 | def __init__(self, web3: Web3, address: Address): 41 | assert(isinstance(web3, Web3)) 42 | assert(isinstance(address, Address)) 43 | 44 | self.web3 = web3 45 | self.address = address 46 | self._contract = self._get_contract(web3, self.abi, address) 47 | 48 | @staticmethod 49 | def deploy(web3: Web3): 50 | return DSGuard(web3=web3, address=Contract._deploy(web3, DSGuard.abi, DSGuard.bin, [])) 51 | 52 | def permit(self, src, dst, sig: bytes) -> Transact: 53 | """Grant access to a function call. 54 | 55 | Args: 56 | src: Address of the caller, or `ANY`. 57 | dst: Address of the called contract, or `ANY`. 58 | sig: Signature of the called function, or `ANY`. 59 | 60 | Returns: 61 | A :py:class:`pymaker.Transact` instance, which can be used to trigger the transaction. 62 | """ 63 | assert(isinstance(src, Address) or isinstance(src, bytes)) 64 | assert(isinstance(dst, Address) or isinstance(dst, bytes)) 65 | assert(isinstance(sig, bytes) and len(sig) in (4, 32)) 66 | 67 | if isinstance(src, Address) and isinstance(dst, Address): 68 | method = 'permit(address,address,bytes32)' 69 | src = src.address 70 | dst = dst.address 71 | 72 | else: 73 | method = 'permit(bytes32,bytes32,bytes32)' 74 | 75 | return Transact(self, self.web3, self.abi, self.address, self._contract, method, [src, dst, sig]) 76 | 77 | def __repr__(self): 78 | return f"DSGuard('{self.address}')" 79 | 80 | 81 | # TODO: Complete implementation and unit test 82 | class DSAuth(Contract): 83 | 84 | abi = Contract._load_abi(__name__, 'abi/DSAuth.abi') 85 | bin = Contract._load_bin(__name__, 'abi/DSAuth.bin') 86 | 87 | def __init__(self, web3: Web3, address: Address): 88 | assert (isinstance(web3, Web3)) 89 | assert (isinstance(address, Address)) 90 | 91 | self.web3 = web3 92 | self.address = address 93 | self._contract = self._get_contract(web3, self.abi, address) 94 | 95 | @staticmethod 96 | def deploy(web3: Web3): 97 | return DSAuth(web3=web3, address=Contract._deploy(web3, DSAuth.abi, DSAuth.bin, [])) 98 | 99 | def get_owner(self) -> Address: 100 | return Address(self._contract.functions.owner().call()) 101 | 102 | def set_owner(self, owner: Address) -> Transact: 103 | assert isinstance(owner, Address) 104 | 105 | return Transact(self, self.web3, self.abi, self.address, self._contract, 106 | "setOwner", [owner.address]) 107 | 108 | def set_authority(self, ds_authority: Address): 109 | assert isinstance(ds_authority, Address) 110 | 111 | return Transact(self, self.web3, self.abi, self.address, self._contract, 112 | "setAuthority", [ds_authority.address]) 113 | -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- 1 | # This file is part of Maker Keeper Framework. 2 | # 3 | # Copyright (C) 2017-2019 reverendus, EdNoepel 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU Affero General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU Affero General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU Affero General Public License 16 | # along with this program. If not, see . 17 | 18 | import logging 19 | import pytest 20 | 21 | from web3 import Web3, HTTPProvider 22 | 23 | from pymaker import Address, web3_via_http 24 | from pymaker.auctions import Flipper, Flapper, Flopper 25 | from pymaker.deployment import Deployment, DssDeployment 26 | from pymaker.dss import Vat, Vow, Cat, Dog, Jug, Pot 27 | from pymaker.keys import register_keys 28 | 29 | 30 | @pytest.fixture(scope='session') 31 | def new_deployment() -> Deployment: 32 | return Deployment() 33 | 34 | 35 | @pytest.fixture() 36 | def deployment(new_deployment: Deployment) -> Deployment: 37 | new_deployment.reset() 38 | return new_deployment 39 | 40 | 41 | @pytest.fixture(scope="session") 42 | def web3() -> Web3: 43 | # for local dockerized parity testchain 44 | web3 = web3_via_http("http://0.0.0.0:8545") 45 | web3.eth.defaultAccount = "0x50FF810797f75f6bfbf2227442e0c961a8562F4C" 46 | register_keys(web3, 47 | ["key_file=tests/config/keys/UnlimitedChain/key1.json,pass_file=/dev/null", 48 | "key_file=tests/config/keys/UnlimitedChain/key2.json,pass_file=/dev/null", 49 | "key_file=tests/config/keys/UnlimitedChain/key3.json,pass_file=/dev/null", 50 | "key_file=tests/config/keys/UnlimitedChain/key4.json,pass_file=/dev/null", 51 | "key_file=tests/config/keys/UnlimitedChain/key.json,pass_file=/dev/null"]) 52 | 53 | # reduce logspew 54 | logging.getLogger("web3").setLevel(logging.INFO) 55 | logging.getLogger("urllib3").setLevel(logging.INFO) 56 | logging.getLogger("asyncio").setLevel(logging.INFO) 57 | 58 | assert len(web3.eth.accounts) > 3 59 | return web3 60 | 61 | 62 | @pytest.fixture(scope="session") 63 | def our_address(web3) -> Address: 64 | return Address(web3.eth.accounts[0]) 65 | 66 | 67 | @pytest.fixture(scope="session") 68 | def other_address(web3) -> Address: 69 | return Address(web3.eth.accounts[1]) 70 | 71 | 72 | @pytest.fixture(scope="session") 73 | def deployment_address(web3) -> Address: 74 | # FIXME: Unsure why it isn't added to web3.eth.accounts list 75 | return Address("0x00a329c0648769A73afAc7F9381E08FB43dBEA72") 76 | 77 | 78 | @pytest.fixture(scope="session") 79 | def mcd(web3) -> DssDeployment: 80 | # for local dockerized parity testchain 81 | deployment = DssDeployment.from_node(web3=web3) 82 | validate_contracts_loaded(deployment) 83 | initialize_collaterals(deployment) 84 | return deployment 85 | 86 | 87 | def validate_contracts_loaded(deployment: DssDeployment): 88 | assert isinstance(deployment.vat, Vat) 89 | assert deployment.vat.address is not None 90 | assert isinstance(deployment.vow, Vow) 91 | assert deployment.vow.address is not None 92 | assert isinstance(deployment.cat, Cat) 93 | assert deployment.cat.address is not None 94 | assert isinstance(deployment.dog, Dog) 95 | assert deployment.dog.address is not None 96 | assert isinstance(deployment.jug, Jug) 97 | assert deployment.jug.address is not None 98 | assert isinstance(deployment.flapper, Flapper) 99 | assert deployment.flapper.address is not None 100 | assert isinstance(deployment.flopper, Flopper) 101 | assert deployment.flopper.address is not None 102 | assert isinstance(deployment.pot, Pot) 103 | assert deployment.pot.address is not None 104 | 105 | 106 | def initialize_collaterals(deployment: DssDeployment): 107 | for collateral in deployment.collaterals.values(): 108 | if collateral.clipper: 109 | collateral.clipper.upchost().transact(from_address=deployment_address(deployment.web3)) 110 | -------------------------------------------------------------------------------- /pymaker/abi/Flapper.abi: -------------------------------------------------------------------------------- 1 | [{"inputs":[{"internalType":"address","name":"vat_","type":"address"},{"internalType":"address","name":"gem_","type":"address"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"id","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"lot","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"bid","type":"uint256"}],"name":"Kick","type":"event"},{"anonymous":true,"inputs":[{"indexed":true,"internalType":"bytes4","name":"sig","type":"bytes4"},{"indexed":true,"internalType":"address","name":"usr","type":"address"},{"indexed":true,"internalType":"bytes32","name":"arg1","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"arg2","type":"bytes32"},{"indexed":false,"internalType":"bytes","name":"data","type":"bytes"}],"name":"LogNote","type":"event"},{"constant":true,"inputs":[],"name":"beg","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"bids","outputs":[{"internalType":"uint256","name":"bid","type":"uint256"},{"internalType":"uint256","name":"lot","type":"uint256"},{"internalType":"address","name":"guy","type":"address"},{"internalType":"uint48","name":"tic","type":"uint48"},{"internalType":"uint48","name":"end","type":"uint48"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"uint256","name":"rad","type":"uint256"}],"name":"cage","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"uint256","name":"id","type":"uint256"}],"name":"deal","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"usr","type":"address"}],"name":"deny","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"bytes32","name":"what","type":"bytes32"},{"internalType":"uint256","name":"data","type":"uint256"}],"name":"file","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"gem","outputs":[{"internalType":"contract GemLike","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"uint256","name":"lot","type":"uint256"},{"internalType":"uint256","name":"bid","type":"uint256"}],"name":"kick","outputs":[{"internalType":"uint256","name":"id","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"kicks","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"live","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"usr","type":"address"}],"name":"rely","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"tau","outputs":[{"internalType":"uint48","name":"","type":"uint48"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"uint256","name":"id","type":"uint256"},{"internalType":"uint256","name":"lot","type":"uint256"},{"internalType":"uint256","name":"bid","type":"uint256"}],"name":"tend","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"uint256","name":"id","type":"uint256"}],"name":"tick","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"ttl","outputs":[{"internalType":"uint48","name":"","type":"uint48"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"vat","outputs":[{"internalType":"contract VatLike","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"wards","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"uint256","name":"id","type":"uint256"}],"name":"yank","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"}] 2 | -------------------------------------------------------------------------------- /config/testnet-addresses.json: -------------------------------------------------------------------------------- 1 | { 2 | "DEPLOYER": "0x00a329c0648769A73afAc7F9381E08FB43dBEA72", 3 | "MULTICALL": "0x492934308E98b590A626666B703A6dDf2120e85e", 4 | "FAUCET": "0x0A64DF94bc0E039474DB42bb52FEca0c1d540402", 5 | "MCD_DEPLOY": "0xd29915F1A3fF9846fE5D8d9d2C954de21932AF7F", 6 | "MCD_GOV": "0x1FD8397e8108ada12eC07976D92F773364ba46e7", 7 | "GOV_GUARD": "0x39a812a6aA4C475b6562B73Bf0584eb3655e8D6C", 8 | "MCD_IOU": "0xDfBc5fbEaa41bD1cd15F9d6b77265DBc3CB2A677", 9 | "MCD_ADM": "0x8c39d4833812A7516BaCD455dA7F97f0a8C11B05", 10 | "VOTE_PROXY_FACTORY": "0x2dC383E93ec3DB735777a3E9ae69E2aD81edaF03", 11 | "MCD_VAT": "0x3D72e5B28FbA05Bd4090A2A587Bb3eCC899f33b2", 12 | "MCD_JUG": "0x77a371Ed06fbA2D93D05C5bDE6d8eC58b3a35fbd", 13 | "MCD_CAT": "0x31865076D1E28ad4eA06D5Db7aAa4AAF225f1Fb5", 14 | "MCD_DOG": "0xd9b3B2429F1b301156Bf3103419ef6B78E888386", 15 | "MCD_VOW": "0xA2F9C8C13118c88f14501cDCB2b52Af3751622ae", 16 | "MCD_JOIN_DAI": "0x7f8241b7250c5C5368788543E4dA2F9A919E9F02", 17 | "MCD_FLAP": "0xB5054202380d093A02916e0137d75b54D6182A23", 18 | "MCD_FLOP": "0xd57D9931b305f1bc1622B97c8Cc6747E4A9254a0", 19 | "MCD_PAUSE": "0x967aE1FB90aA36C7d3B16B5328504F542495D952", 20 | "MCD_PAUSE_PROXY": "0x3689de8F568e4A59254eE7eaB1A37d87044f52Da", 21 | "MCD_GOV_ACTIONS": "0x2287909BB95FA078C73CC2d5a5AF6fE1244b0911", 22 | "MCD_DAI": "0x8A1567046e610Fec30F120BB70Df94B50561C1d3", 23 | "MCD_SPOT": "0x5422Ee4e22603E336905CDC9D59aE3F0012fe4c8", 24 | "MCD_POT": "0xe51e9A4D22b7451c0232508455195E7c0a6e0f19", 25 | "MCD_END": "0x27547dE5f11122283825Adf97FB98eF301f5F73c", 26 | "MCD_ESM": "0x8a606fD18B9f0CA3Cf480e70639c58EAa98d7389", 27 | "PROXY_ACTIONS": "0x84617303947304444Ceb641582c024f277BBF4Ff", 28 | "PROXY_ACTIONS_END": "0x78c362A5690447EA2BBC3E8008502efD13936F79", 29 | "PROXY_ACTIONS_DSR": "0x277aD07109FE52a742B808a3E6765Ee1Ad0e7Ad2", 30 | "CDP_MANAGER": "0x79a8FC3D98Fc84c9BC2B3a737EA992321a1b86A3", 31 | "DSR_MANAGER": "0x0Faf2F31Ab165B55F42E55c8065c0EC7170A0d45", 32 | "GET_CDPS": "0x4f05AfbC371854D027263e756487BDefD099178f", 33 | "ILK_REGISTRY": "0x8e23974b151827f0E8151aC526C4c4c974c06A90", 34 | "OSM_MOM": "0x96724aa934979936aE5c3Afda1599b5ed61252ce", 35 | "FLIPPER_MOM": "0xcbfD09D76140D01E573b452bB984d82589571fC2", 36 | "CLIPPER_MOM": "0x9BB69befBAA567a7EaEE33b671756596517338F4", 37 | "MCD_IAM_AUTO_LINE": "0x01E354A7eF79962DbB690705e46bd54c1C855E80", 38 | "PROXY_FACTORY": "0x3DD0864668C36D27B53a98137764c99F9FD5B7B2", 39 | "PROXY_REGISTRY": "0x26C8d09E5C0B423E2827844c770F61c9af2870E7", 40 | "ETH": "0xEddA486ddB7eaa8f9FEce8c682EFD40f535b3Ad5", 41 | "VAL_ETH": "0xb4c79daB8f259C7Aee6E5b2Aa729821864227e84", 42 | "MCD_JOIN_ETH_A": "0x9119B5d8b735E4cEbaE7386AF6cD2B863c7d35A8", 43 | "MCD_FLIP_ETH_A": "0x0BD7632aF5F7020575e59E80ABbca739035Ac0EC", 44 | "MCD_JOIN_ETH_B": "0xe2dD18a6000030F30ecB1237B15605533f814c59", 45 | "MCD_CLIP_ETH_B": "0x3f2603979a4A185acE9B9c941193704FfBD24F4A", 46 | "MCD_CLIP_CALC_ETH_B": "0x8cabea65F0140962A7D7Fe9f31a265a2B19Dc305", 47 | "MCD_JOIN_ETH_C": "0x9FdC3bBD89ae1fB19054241644EF3dfbdcA85544", 48 | "MCD_FLIP_ETH_C": "0xB0b7Db244994E9B922998f49b7ae61956314CA35", 49 | "BAT": "0x39b4C0A63c4c16DD1816D104F2C18a296Dbd4e70", 50 | "VAL_BAT": "0x62d69f6867A0A084C6d313943dC22023Bc263691", 51 | "MCD_JOIN_BAT_A": "0xA616aD7D4562dCD9208425Af4038defD0a9057B0", 52 | "MCD_FLIP_BAT_A": "0xB36901dB56E2fb44862a7D0eAE9F5Cf9a7E449bD", 53 | "USDC": "0x32Ee2bF1267253f76298D4199095B9C6b5A389c0", 54 | "VAL_USDC": "0xee35211C4D9126D520bBfeaf3cFee5FE7B86F221", 55 | "MCD_JOIN_USDC_A": "0x59ea98A4b40B72140b7dc93c29c098AE607Ce20D", 56 | "MCD_FLIP_USDC_A": "0xE8a124764cCcb7Ee3E8e320aAaA841Ea249197D4", 57 | "MCD_JOIN_USDC_B": "0xaD1Bf7D34Fa48f7Cf7CA1CE3c7408f9151DF2745", 58 | "MCD_FLIP_USDC_B": "0xC8055bC4415Ac354fA6EFbC3bcf57d5cBcc072ed", 59 | "TUSD": "0xB014e899ddb9a55af72fE09E8570E700A5167b6d", 60 | "VAL_TUSD": "0x7C276DcAab99BD16163c1bcce671CaD6A1ec0945", 61 | "MCD_JOIN_TUSD_A": "0xAFB95880bc835B6Eeb041ce57D570D013360beC6", 62 | "MCD_FLIP_TUSD_A": "0x6b0f809E52218192AbAf32C9C74F31229E07B626", 63 | "WBTC": "0x123010c0Fe7D4d7420f309431bb95060393fe3B7", 64 | "VAL_WBTC": "0x3f85D0b6119B38b7E6B119F7550290fec4BE0e3c", 65 | "MCD_JOIN_WBTC_A": "0xf4C33a989bD0C9e9268c5bfCbCE2C8501B9dBa25", 66 | "MCD_FLIP_WBTC_A": "0x53781B6DC81F5b90421371172c1b06e384858e44", 67 | "GUSD": "0x0363Ef677c78bd8C8302DB33be2F6629E33E72Fe", 68 | "VAL_GUSD": "0xd5F051401ca478B34C80D0B5A119e437Dc6D9df5", 69 | "MCD_JOIN_GUSD_A": "0x01C957395029E9aCCbCb25a6Ab72C618252CACf9", 70 | "MCD_FLIP_GUSD_A": "0x334490acE32D96808B104A1f8723cFAB5881DE46", 71 | "PROXY_PAUSE_ACTIONS": "0x23263d4ebB1190A483A84e90B9a6Dd8720979284", 72 | "PROXY_DEPLOYER": "0x30c8860f6a38819B59E1255A499A10bCBF4Ee747" 73 | } --------------------------------------------------------------------------------