diff --git a/package-lock.json b/package-lock.json index f89c79ba..3a4988fd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2047,15 +2047,15 @@ } }, "node_modules/@ethereumjs/block": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/@ethereumjs/block/-/block-3.5.1.tgz", - "integrity": "sha512-MoY9bHKABOBK6BW0v1N1Oc0Cve4x/giX67M3TtrVBUsKQTj2eznLGKpydoitxWSZ+WgKKSVhfRMzbCGRwk7T5w==", + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/@ethereumjs/block/-/block-3.6.0.tgz", + "integrity": "sha512-dqLo1LtsLG+Oelu5S5tWUDG0pah3QUwV5TJZy2cm19BXDr4ka/S9XBSgao0i09gTcuPlovlHgcs6d7EZ37urjQ==", "peer": true, "dependencies": { - "@ethereumjs/common": "^2.5.0", - "@ethereumjs/tx": "^3.3.1", - "ethereumjs-util": "^7.1.1", - "merkle-patricia-tree": "^4.2.1" + "@ethereumjs/common": "^2.6.0", + "@ethereumjs/tx": "^3.4.0", + "ethereumjs-util": "^7.1.3", + "merkle-patricia-tree": "^4.2.2" } }, "node_modules/@ethereumjs/block/node_modules/level-ws": { @@ -2102,19 +2102,18 @@ } }, "node_modules/@ethereumjs/blockchain": { - "version": "5.4.2", - "resolved": "https://registry.npmjs.org/@ethereumjs/blockchain/-/blockchain-5.4.2.tgz", - "integrity": "sha512-AOAAwz/lw2lciG9gf5wHi7M/qknraXXnLR66lYgbQ04qfyFC3ZE5x/5rLVm1Vu+kfJLlKrYZTmA0IbOkc7kvgw==", + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/@ethereumjs/blockchain/-/blockchain-5.5.0.tgz", + "integrity": "sha512-879YVmWbM8OUKLVj+OuEZ+sZFkQOnXYGeak5oi7O1hOjaRv//je+fK2axGP04cbttu7sPCp41zy7O6xw4cut8A==", "peer": true, "dependencies": { - "@ethereumjs/block": "^3.5.1", - "@ethereumjs/common": "^2.5.0", + "@ethereumjs/block": "^3.6.0", + "@ethereumjs/common": "^2.6.0", "@ethereumjs/ethash": "^1.1.0", "debug": "^2.2.0", - "ethereumjs-util": "^7.1.1", + "ethereumjs-util": "^7.1.3", "level-mem": "^5.0.1", "lru-cache": "^5.1.1", - "rlp": "^2.2.4", "semaphore-async-await": "^1.5.1" } }, @@ -2134,12 +2133,12 @@ "peer": true }, "node_modules/@ethereumjs/common": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@ethereumjs/common/-/common-2.5.0.tgz", - "integrity": "sha512-DEHjW6e38o+JmB/NO3GZBpW4lpaiBpkFgXF6jLcJ6gETBYpEyaA5nTimsWBUJR3Vmtm/didUEbNjajskugZORg==", + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@ethereumjs/common/-/common-2.6.0.tgz", + "integrity": "sha512-Cq2qS0FTu6O2VU1sgg+WyU9Ps0M6j/BEMHN+hRaECXCV/r0aI78u4N6p52QW/BDVhwWZpCdrvG8X7NJdzlpNUA==", "dependencies": { "crc-32": "^1.2.0", - "ethereumjs-util": "^7.1.1" + "ethereumjs-util": "^7.1.3" } }, "node_modules/@ethereumjs/ethash": { @@ -2165,33 +2164,32 @@ } }, "node_modules/@ethereumjs/tx": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/@ethereumjs/tx/-/tx-3.3.2.tgz", - "integrity": "sha512-6AaJhwg4ucmwTvw/1qLaZUX5miWrwZ4nLOUsKyb/HtzS3BMw/CasKhdi1ims9mBKeK9sOJCH4qGKOBGyJCeeog==", + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/@ethereumjs/tx/-/tx-3.4.0.tgz", + "integrity": "sha512-WWUwg1PdjHKZZxPPo274ZuPsJCWV3SqATrEKQP1n2DrVYVP1aZIYpo/mFaA0BDoE0tIQmBeimRCEA0Lgil+yYw==", "dependencies": { - "@ethereumjs/common": "^2.5.0", - "ethereumjs-util": "^7.1.2" + "@ethereumjs/common": "^2.6.0", + "ethereumjs-util": "^7.1.3" } }, "node_modules/@ethereumjs/vm": { - "version": "5.5.3", - "resolved": "https://registry.npmjs.org/@ethereumjs/vm/-/vm-5.5.3.tgz", - "integrity": "sha512-0k5OreWnlgXYs54wohgO11jtGI05GDasj2EYxzuaStxTi15CS3vow5wGYELC1pG9xngE1F/mFmKi/f14XRuDow==", + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/@ethereumjs/vm/-/vm-5.6.0.tgz", + "integrity": "sha512-J2m/OgjjiGdWF2P9bj/4LnZQ1zRoZhY8mRNVw/N3tXliGI8ai1sI1mlDPkLpeUUM4vq54gH6n0ZlSpz8U/qlYQ==", "peer": true, "dependencies": { - "@ethereumjs/block": "^3.5.0", - "@ethereumjs/blockchain": "^5.4.1", - "@ethereumjs/common": "^2.5.0", - "@ethereumjs/tx": "^3.3.1", + "@ethereumjs/block": "^3.6.0", + "@ethereumjs/blockchain": "^5.5.0", + "@ethereumjs/common": "^2.6.0", + "@ethereumjs/tx": "^3.4.0", "async-eventemitter": "^0.2.4", "core-js-pure": "^3.0.1", "debug": "^2.2.0", - "ethereumjs-util": "^7.1.1", + "ethereumjs-util": "^7.1.3", "functional-red-black-tree": "^1.0.1", "mcl-wasm": "^0.7.1", - "merkle-patricia-tree": "^4.2.1", - "rustbn.js": "~0.2.0", - "util.promisify": "^1.0.1" + "merkle-patricia-tree": "^4.2.2", + "rustbn.js": "~0.2.0" } }, "node_modules/@ethereumjs/vm/node_modules/debug": { @@ -3053,7 +3051,7 @@ "node_modules/@oceanprotocol/contracts": { "name": "hardhat-project", "version": "v1.0.0-alpha.1", - "resolved": "git+ssh://git@github.com/oceanprotocol/contracts.git#a8292971264794219e86c46c4f5d8eb9d5ce9d37", + "resolved": "git+ssh://git@github.com/oceanprotocol/contracts.git#c1bea5033dfc9071105a11b63ce86d8e8f612b7b", "dependencies": { "@balancer-labs/v2-pool-utils": "^1.0.0", "@openzeppelin/contracts": "^4.2.0", @@ -3552,9 +3550,9 @@ "integrity": "sha512-BFo/nyxwhoHqPrqBQA1EAmSxeNnspGLiOCMa9pAL7WYSjyNBlrHaqCMO/F2O87G+NUK/u06E70DiSP2BFP0ZZw==" }, "node_modules/@truffle/codec": { - "version": "0.11.17", - "resolved": "https://registry.npmjs.org/@truffle/codec/-/codec-0.11.17.tgz", - "integrity": "sha512-WO9D5TVyTf9czqdsfK/qqYeSS//zWcHBgQgSNKPlCDb6koCNLxG5yGbb4P+0bZvTUNS2e2iIdN92QHg00wMbSQ==", + "version": "0.11.18", + "resolved": "https://registry.npmjs.org/@truffle/codec/-/codec-0.11.18.tgz", + "integrity": "sha512-OCsKQHOBAmjr+9DypTkTxWYqHmCd388astUvC+ycwlmAqGKIYy2GzpOIX4pX7YX0q/g2iuixEMCTlF440YiNMA==", "dependencies": { "@truffle/abi-utils": "^0.2.4", "@truffle/compile-common": "^0.7.22", @@ -3653,17 +3651,18 @@ } }, "node_modules/@truffle/contract": { - "version": "4.3.38", - "resolved": "https://registry.npmjs.org/@truffle/contract/-/contract-4.3.38.tgz", - "integrity": "sha512-11HL9IJTmd45pVXJvEaRYeyuhf8GmAgRD7bTYBZj2CiMBnt0337Fg7Zz/GuTpUUW2h3fbyTYO4hgOntxdQjZ5A==", + "version": "4.3.39", + "resolved": "https://registry.npmjs.org/@truffle/contract/-/contract-4.3.39.tgz", + "integrity": "sha512-ZBCrFUWaTtOvKlJw2J2bDwaBghGDvb5AeVU1WxHvwLnzGqCHzIE6dhZixItdFQgbuZ2gJAxzFQVT38miiK7ufA==", "dependencies": { "@ensdomains/ensjs": "^2.0.1", "@truffle/blockchain-utils": "^0.0.31", "@truffle/contract-schema": "^3.4.3", - "@truffle/debug-utils": "^5.1.18", + "@truffle/debug-utils": "^5.1.19", "@truffle/error": "^0.0.14", "@truffle/interface-adapter": "^0.5.8", "bignumber.js": "^7.2.1", + "debug": "^4.3.1", "ethers": "^4.0.32", "web3": "1.5.3", "web3-core-helpers": "1.5.3", @@ -3709,9 +3708,9 @@ } }, "node_modules/@truffle/contract/node_modules/@types/node": { - "version": "12.20.36", - "resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.36.tgz", - "integrity": "sha512-+5haRZ9uzI7rYqzDznXgkuacqb6LJhAti8mzZKWxIXn/WEtvB+GHVJ7AuMwcN1HMvXOSJcrvA6PPoYHYOYYebA==" + "version": "12.20.37", + "resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.37.tgz", + "integrity": "sha512-i1KGxqcvJaLQali+WuypQnXwcplhtNtjs66eNsZpp2P2FL/trJJxx/VWsM0YCL2iMoIJrbXje48lvIQAQ4p2ZA==" }, "node_modules/@truffle/contract/node_modules/cacheable-request": { "version": "6.1.0", @@ -4147,11 +4146,11 @@ } }, "node_modules/@truffle/debug-utils": { - "version": "5.1.18", - "resolved": "https://registry.npmjs.org/@truffle/debug-utils/-/debug-utils-5.1.18.tgz", - "integrity": "sha512-QBq1vA/YozksQZGjyA7o482AuT8KW5gvO8VmYM/PIDllCIqDruEZuz4DZ+zpVUPXyVoJycFo+RKnM/TLE1AZRQ==", + "version": "5.1.19", + "resolved": "https://registry.npmjs.org/@truffle/debug-utils/-/debug-utils-5.1.19.tgz", + "integrity": "sha512-MWk2DszA6Alme1r4SrWu7BZgz7RNf1TsxdJuSToSltGiJE7DDwV+qsfQ+AGY/PoLKPTQ2xW/sd23BQheA+W9qA==", "dependencies": { - "@truffle/codec": "^0.11.17", + "@truffle/codec": "^0.11.18", "@trufflesuite/chromafi": "^2.2.2", "bn.js": "^5.1.3", "chalk": "^2.4.2", @@ -4244,9 +4243,9 @@ } }, "node_modules/@truffle/interface-adapter/node_modules/@types/node": { - "version": "12.20.36", - "resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.36.tgz", - "integrity": "sha512-+5haRZ9uzI7rYqzDznXgkuacqb6LJhAti8mzZKWxIXn/WEtvB+GHVJ7AuMwcN1HMvXOSJcrvA6PPoYHYOYYebA==" + "version": "12.20.37", + "resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.37.tgz", + "integrity": "sha512-i1KGxqcvJaLQali+WuypQnXwcplhtNtjs66eNsZpp2P2FL/trJJxx/VWsM0YCL2iMoIJrbXje48lvIQAQ4p2ZA==" }, "node_modules/@truffle/interface-adapter/node_modules/bignumber.js": { "version": "9.0.1", @@ -7469,9 +7468,9 @@ } }, "node_modules/core-js-pure": { - "version": "3.19.0", - "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.19.0.tgz", - "integrity": "sha512-UEQk8AxyCYvNAs6baNoPqDADv7BX0AmBLGxVsrAifPPx/C8EAzV4Q+2ZUJqVzfI2TQQEZITnwUkWcHpgc/IubQ==", + "version": "3.19.1", + "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.19.1.tgz", + "integrity": "sha512-Q0Knr8Es84vtv62ei6/6jXH/7izKmOrtrxH9WJTHLCMAVeU+8TF8z8Nr08CsH4Ot0oJKzBzJJL9SJBYIv7WlfQ==", "hasInstallScript": true, "peer": true, "funding": { @@ -11108,15 +11107,6 @@ } } }, - "node_modules/for-each": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", - "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", - "peer": true, - "dependencies": { - "is-callable": "^1.1.3" - } - }, "node_modules/for-in": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", @@ -12662,9 +12652,9 @@ } }, "node_modules/hardhat": { - "version": "2.6.7", - "resolved": "https://registry.npmjs.org/hardhat/-/hardhat-2.6.7.tgz", - "integrity": "sha512-Mua01f6ZN1feQLktHSH2p5A5LCdA+Wf7+O2lJDH6wClvWPtI2eqKNNY2gxBwYXoQ28GZrT3K6mqQOZeRWAca6Q==", + "version": "2.6.8", + "resolved": "https://registry.npmjs.org/hardhat/-/hardhat-2.6.8.tgz", + "integrity": "sha512-iRVd5DgcIVV3rNXMlogOfwlXAhHp7Wy/OjjFiUhTey8Unvo6oq5+Is5ANiKVN+Iw07Pcb/HpkGt7jCB6a4ITgg==", "peer": true, "dependencies": { "@ethereumjs/block": "^3.4.0", @@ -13779,9 +13769,9 @@ } }, "node_modules/highlightjs-solidity": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/highlightjs-solidity/-/highlightjs-solidity-2.0.1.tgz", - "integrity": "sha512-9YY+HQpXMTrF8HgRByjeQhd21GXAz2ktMPTcs6oWSj5HJR52fgsNoelMOmgigwcpt9j4tu4IVSaWaJB2n2TbvQ==" + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/highlightjs-solidity/-/highlightjs-solidity-2.0.2.tgz", + "integrity": "sha512-q0aYUKiZ9MPQg41qx/KpXKaCpqql50qTvmwGYyLFfcjt9AE/+C9CwjVIdJZc7EYj6NGgJuFJ4im1gfgrzUU1fQ==" }, "node_modules/hmac-drbg": { "version": "1.0.1", @@ -23186,22 +23176,6 @@ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" }, - "node_modules/util.promisify": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/util.promisify/-/util.promisify-1.1.1.tgz", - "integrity": "sha512-/s3UsZUrIfa6xDhr7zZhnE9SLQ5RIXyYfiVnMMyMDzOc8WhWN4Nbh36H842OyurKbCDAesZOJaVyvmSl6fhGQw==", - "peer": true, - "dependencies": { - "call-bind": "^1.0.0", - "define-properties": "^1.1.3", - "for-each": "^0.3.3", - "has-symbols": "^1.0.1", - "object.getownpropertydescriptors": "^2.1.1" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/utils-merge": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", @@ -27215,15 +27189,15 @@ } }, "@ethereumjs/block": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/@ethereumjs/block/-/block-3.5.1.tgz", - "integrity": "sha512-MoY9bHKABOBK6BW0v1N1Oc0Cve4x/giX67M3TtrVBUsKQTj2eznLGKpydoitxWSZ+WgKKSVhfRMzbCGRwk7T5w==", + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/@ethereumjs/block/-/block-3.6.0.tgz", + "integrity": "sha512-dqLo1LtsLG+Oelu5S5tWUDG0pah3QUwV5TJZy2cm19BXDr4ka/S9XBSgao0i09gTcuPlovlHgcs6d7EZ37urjQ==", "peer": true, "requires": { - "@ethereumjs/common": "^2.5.0", - "@ethereumjs/tx": "^3.3.1", - "ethereumjs-util": "^7.1.1", - "merkle-patricia-tree": "^4.2.1" + "@ethereumjs/common": "^2.6.0", + "@ethereumjs/tx": "^3.4.0", + "ethereumjs-util": "^7.1.3", + "merkle-patricia-tree": "^4.2.2" }, "dependencies": { "level-ws": { @@ -27266,19 +27240,18 @@ } }, "@ethereumjs/blockchain": { - "version": "5.4.2", - "resolved": "https://registry.npmjs.org/@ethereumjs/blockchain/-/blockchain-5.4.2.tgz", - "integrity": "sha512-AOAAwz/lw2lciG9gf5wHi7M/qknraXXnLR66lYgbQ04qfyFC3ZE5x/5rLVm1Vu+kfJLlKrYZTmA0IbOkc7kvgw==", + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/@ethereumjs/blockchain/-/blockchain-5.5.0.tgz", + "integrity": "sha512-879YVmWbM8OUKLVj+OuEZ+sZFkQOnXYGeak5oi7O1hOjaRv//je+fK2axGP04cbttu7sPCp41zy7O6xw4cut8A==", "peer": true, "requires": { - "@ethereumjs/block": "^3.5.1", - "@ethereumjs/common": "^2.5.0", + "@ethereumjs/block": "^3.6.0", + "@ethereumjs/common": "^2.6.0", "@ethereumjs/ethash": "^1.1.0", "debug": "^2.2.0", - "ethereumjs-util": "^7.1.1", + "ethereumjs-util": "^7.1.3", "level-mem": "^5.0.1", "lru-cache": "^5.1.1", - "rlp": "^2.2.4", "semaphore-async-await": "^1.5.1" }, "dependencies": { @@ -27300,12 +27273,12 @@ } }, "@ethereumjs/common": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@ethereumjs/common/-/common-2.5.0.tgz", - "integrity": "sha512-DEHjW6e38o+JmB/NO3GZBpW4lpaiBpkFgXF6jLcJ6gETBYpEyaA5nTimsWBUJR3Vmtm/didUEbNjajskugZORg==", + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@ethereumjs/common/-/common-2.6.0.tgz", + "integrity": "sha512-Cq2qS0FTu6O2VU1sgg+WyU9Ps0M6j/BEMHN+hRaECXCV/r0aI78u4N6p52QW/BDVhwWZpCdrvG8X7NJdzlpNUA==", "requires": { "crc-32": "^1.2.0", - "ethereumjs-util": "^7.1.1" + "ethereumjs-util": "^7.1.3" } }, "@ethereumjs/ethash": { @@ -27333,33 +27306,32 @@ } }, "@ethereumjs/tx": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/@ethereumjs/tx/-/tx-3.3.2.tgz", - "integrity": "sha512-6AaJhwg4ucmwTvw/1qLaZUX5miWrwZ4nLOUsKyb/HtzS3BMw/CasKhdi1ims9mBKeK9sOJCH4qGKOBGyJCeeog==", + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/@ethereumjs/tx/-/tx-3.4.0.tgz", + "integrity": "sha512-WWUwg1PdjHKZZxPPo274ZuPsJCWV3SqATrEKQP1n2DrVYVP1aZIYpo/mFaA0BDoE0tIQmBeimRCEA0Lgil+yYw==", "requires": { - "@ethereumjs/common": "^2.5.0", - "ethereumjs-util": "^7.1.2" + "@ethereumjs/common": "^2.6.0", + "ethereumjs-util": "^7.1.3" } }, "@ethereumjs/vm": { - "version": "5.5.3", - "resolved": "https://registry.npmjs.org/@ethereumjs/vm/-/vm-5.5.3.tgz", - "integrity": "sha512-0k5OreWnlgXYs54wohgO11jtGI05GDasj2EYxzuaStxTi15CS3vow5wGYELC1pG9xngE1F/mFmKi/f14XRuDow==", + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/@ethereumjs/vm/-/vm-5.6.0.tgz", + "integrity": "sha512-J2m/OgjjiGdWF2P9bj/4LnZQ1zRoZhY8mRNVw/N3tXliGI8ai1sI1mlDPkLpeUUM4vq54gH6n0ZlSpz8U/qlYQ==", "peer": true, "requires": { - "@ethereumjs/block": "^3.5.0", - "@ethereumjs/blockchain": "^5.4.1", - "@ethereumjs/common": "^2.5.0", - "@ethereumjs/tx": "^3.3.1", + "@ethereumjs/block": "^3.6.0", + "@ethereumjs/blockchain": "^5.5.0", + "@ethereumjs/common": "^2.6.0", + "@ethereumjs/tx": "^3.4.0", "async-eventemitter": "^0.2.4", "core-js-pure": "^3.0.1", "debug": "^2.2.0", - "ethereumjs-util": "^7.1.1", + "ethereumjs-util": "^7.1.3", "functional-red-black-tree": "^1.0.1", "mcl-wasm": "^0.7.1", - "merkle-patricia-tree": "^4.2.1", - "rustbn.js": "~0.2.0", - "util.promisify": "^1.0.1" + "merkle-patricia-tree": "^4.2.2", + "rustbn.js": "~0.2.0" }, "dependencies": { "debug": { @@ -27883,7 +27855,7 @@ } }, "@oceanprotocol/contracts": { - "version": "git+ssh://git@github.com/oceanprotocol/contracts.git#a8292971264794219e86c46c4f5d8eb9d5ce9d37", + "version": "git+ssh://git@github.com/oceanprotocol/contracts.git#c1bea5033dfc9071105a11b63ce86d8e8f612b7b", "from": "@oceanprotocol/contracts@github:oceanprotocol/contracts#v4main", "requires": { "@balancer-labs/v2-pool-utils": "^1.0.0", @@ -28318,9 +28290,9 @@ "integrity": "sha512-BFo/nyxwhoHqPrqBQA1EAmSxeNnspGLiOCMa9pAL7WYSjyNBlrHaqCMO/F2O87G+NUK/u06E70DiSP2BFP0ZZw==" }, "@truffle/codec": { - "version": "0.11.17", - "resolved": "https://registry.npmjs.org/@truffle/codec/-/codec-0.11.17.tgz", - "integrity": "sha512-WO9D5TVyTf9czqdsfK/qqYeSS//zWcHBgQgSNKPlCDb6koCNLxG5yGbb4P+0bZvTUNS2e2iIdN92QHg00wMbSQ==", + "version": "0.11.18", + "resolved": "https://registry.npmjs.org/@truffle/codec/-/codec-0.11.18.tgz", + "integrity": "sha512-OCsKQHOBAmjr+9DypTkTxWYqHmCd388astUvC+ycwlmAqGKIYy2GzpOIX4pX7YX0q/g2iuixEMCTlF440YiNMA==", "requires": { "@truffle/abi-utils": "^0.2.4", "@truffle/compile-common": "^0.7.22", @@ -28413,17 +28385,18 @@ } }, "@truffle/contract": { - "version": "4.3.38", - "resolved": "https://registry.npmjs.org/@truffle/contract/-/contract-4.3.38.tgz", - "integrity": "sha512-11HL9IJTmd45pVXJvEaRYeyuhf8GmAgRD7bTYBZj2CiMBnt0337Fg7Zz/GuTpUUW2h3fbyTYO4hgOntxdQjZ5A==", + "version": "4.3.39", + "resolved": "https://registry.npmjs.org/@truffle/contract/-/contract-4.3.39.tgz", + "integrity": "sha512-ZBCrFUWaTtOvKlJw2J2bDwaBghGDvb5AeVU1WxHvwLnzGqCHzIE6dhZixItdFQgbuZ2gJAxzFQVT38miiK7ufA==", "requires": { "@ensdomains/ensjs": "^2.0.1", "@truffle/blockchain-utils": "^0.0.31", "@truffle/contract-schema": "^3.4.3", - "@truffle/debug-utils": "^5.1.18", + "@truffle/debug-utils": "^5.1.19", "@truffle/error": "^0.0.14", "@truffle/interface-adapter": "^0.5.8", "bignumber.js": "^7.2.1", + "debug": "^4.3.1", "ethers": "^4.0.32", "web3": "1.5.3", "web3-core-helpers": "1.5.3", @@ -28454,9 +28427,9 @@ } }, "@types/node": { - "version": "12.20.36", - "resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.36.tgz", - "integrity": "sha512-+5haRZ9uzI7rYqzDznXgkuacqb6LJhAti8mzZKWxIXn/WEtvB+GHVJ7AuMwcN1HMvXOSJcrvA6PPoYHYOYYebA==" + "version": "12.20.37", + "resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.37.tgz", + "integrity": "sha512-i1KGxqcvJaLQali+WuypQnXwcplhtNtjs66eNsZpp2P2FL/trJJxx/VWsM0YCL2iMoIJrbXje48lvIQAQ4p2ZA==" }, "cacheable-request": { "version": "6.1.0", @@ -28820,11 +28793,11 @@ } }, "@truffle/debug-utils": { - "version": "5.1.18", - "resolved": "https://registry.npmjs.org/@truffle/debug-utils/-/debug-utils-5.1.18.tgz", - "integrity": "sha512-QBq1vA/YozksQZGjyA7o482AuT8KW5gvO8VmYM/PIDllCIqDruEZuz4DZ+zpVUPXyVoJycFo+RKnM/TLE1AZRQ==", + "version": "5.1.19", + "resolved": "https://registry.npmjs.org/@truffle/debug-utils/-/debug-utils-5.1.19.tgz", + "integrity": "sha512-MWk2DszA6Alme1r4SrWu7BZgz7RNf1TsxdJuSToSltGiJE7DDwV+qsfQ+AGY/PoLKPTQ2xW/sd23BQheA+W9qA==", "requires": { - "@truffle/codec": "^0.11.17", + "@truffle/codec": "^0.11.18", "@trufflesuite/chromafi": "^2.2.2", "bn.js": "^5.1.3", "chalk": "^2.4.2", @@ -28915,9 +28888,9 @@ } }, "@types/node": { - "version": "12.20.36", - "resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.36.tgz", - "integrity": "sha512-+5haRZ9uzI7rYqzDznXgkuacqb6LJhAti8mzZKWxIXn/WEtvB+GHVJ7AuMwcN1HMvXOSJcrvA6PPoYHYOYYebA==" + "version": "12.20.37", + "resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.37.tgz", + "integrity": "sha512-i1KGxqcvJaLQali+WuypQnXwcplhtNtjs66eNsZpp2P2FL/trJJxx/VWsM0YCL2iMoIJrbXje48lvIQAQ4p2ZA==" }, "bignumber.js": { "version": "9.0.1", @@ -31528,9 +31501,9 @@ } }, "core-js-pure": { - "version": "3.19.0", - "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.19.0.tgz", - "integrity": "sha512-UEQk8AxyCYvNAs6baNoPqDADv7BX0AmBLGxVsrAifPPx/C8EAzV4Q+2ZUJqVzfI2TQQEZITnwUkWcHpgc/IubQ==", + "version": "3.19.1", + "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.19.1.tgz", + "integrity": "sha512-Q0Knr8Es84vtv62ei6/6jXH/7izKmOrtrxH9WJTHLCMAVeU+8TF8z8Nr08CsH4Ot0oJKzBzJJL9SJBYIv7WlfQ==", "peer": true }, "core-util-is": { @@ -34462,15 +34435,6 @@ "integrity": "sha512-wtphSXy7d4/OR+MvIFbCVBDzZ5520qV8XfPklSN5QtxuMUJZ+b0Wnst1e1lCDocfzuCkHqj8k0FpZqO+UIaKNA==", "peer": true }, - "for-each": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", - "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", - "peer": true, - "requires": { - "is-callable": "^1.1.3" - } - }, "for-in": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", @@ -35507,9 +35471,9 @@ } }, "hardhat": { - "version": "2.6.7", - "resolved": "https://registry.npmjs.org/hardhat/-/hardhat-2.6.7.tgz", - "integrity": "sha512-Mua01f6ZN1feQLktHSH2p5A5LCdA+Wf7+O2lJDH6wClvWPtI2eqKNNY2gxBwYXoQ28GZrT3K6mqQOZeRWAca6Q==", + "version": "2.6.8", + "resolved": "https://registry.npmjs.org/hardhat/-/hardhat-2.6.8.tgz", + "integrity": "sha512-iRVd5DgcIVV3rNXMlogOfwlXAhHp7Wy/OjjFiUhTey8Unvo6oq5+Is5ANiKVN+Iw07Pcb/HpkGt7jCB6a4ITgg==", "peer": true, "requires": { "@ethereumjs/block": "^3.4.0", @@ -36376,9 +36340,9 @@ "integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==" }, "highlightjs-solidity": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/highlightjs-solidity/-/highlightjs-solidity-2.0.1.tgz", - "integrity": "sha512-9YY+HQpXMTrF8HgRByjeQhd21GXAz2ktMPTcs6oWSj5HJR52fgsNoelMOmgigwcpt9j4tu4IVSaWaJB2n2TbvQ==" + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/highlightjs-solidity/-/highlightjs-solidity-2.0.2.tgz", + "integrity": "sha512-q0aYUKiZ9MPQg41qx/KpXKaCpqql50qTvmwGYyLFfcjt9AE/+C9CwjVIdJZc7EYj6NGgJuFJ4im1gfgrzUU1fQ==" }, "hmac-drbg": { "version": "1.0.1", @@ -43650,19 +43614,6 @@ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" }, - "util.promisify": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/util.promisify/-/util.promisify-1.1.1.tgz", - "integrity": "sha512-/s3UsZUrIfa6xDhr7zZhnE9SLQ5RIXyYfiVnMMyMDzOc8WhWN4Nbh36H842OyurKbCDAesZOJaVyvmSl6fhGQw==", - "peer": true, - "requires": { - "call-bind": "^1.0.0", - "define-properties": "^1.1.3", - "for-each": "^0.3.3", - "has-symbols": "^1.0.1", - "object.getownpropertydescriptors": "^2.1.1" - } - }, "utils-merge": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", diff --git a/package.json b/package.json index 4514ecf7..a5c64089 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,8 @@ "release": "release-it --non-interactive", "changelog": "auto-changelog -p", "prepublishOnly": "npm run build", + "test:ss": "mocha --config=test/unit/.mocharc.json --node-env=test --exit 'test/unit/pools/ssContracts/SideStaking.test.ts'", + "test:fixed": "mocha --config=test/unit/.mocharc.json --node-env=test --exit 'test/unit/pools/fixedRate/FixedRateExchange.test.ts'", "test:pool": "mocha --config=test/unit/.mocharc.json --node-env=test --exit 'test/unit/pools/balancer/Pool.test.ts'", "test:dt": "mocha --config=test/unit/.mocharc.json --node-env=test --exit 'test/unit/Datatoken.test.ts'", "test:nftDt": "mocha --config=test/unit/.mocharc.json --node-env=test --exit 'test/unit/NFTDatatoken.test.ts'", diff --git a/src/pools/balancer/Pool.ts b/src/pools/balancer/Pool.ts index d6a5585f..25baa7a4 100644 --- a/src/pools/balancer/Pool.ts +++ b/src/pools/balancer/Pool.ts @@ -2,12 +2,13 @@ import Web3 from 'web3' import { AbiItem } from 'web3-utils/types' import { TransactionReceipt } from 'web3-core' import { Contract } from 'web3-eth-contract' -import { Logger, getFairGasPrice } from '../../utils' +import { Logger, getFairGasPrice, LoggerInstance } from '../../utils' import BigNumber from 'bignumber.js' import PoolTemplate from '@oceanprotocol/contracts/artifacts/contracts/pools/balancer/BPool.sol/BPool.json' import defaultPool from '@oceanprotocol/contracts/artifacts/contracts/pools/FactoryRouter.sol/FactoryRouter.json' import defaultERC20ABI from '@oceanprotocol/contracts/artifacts/contracts/templates/ERC20Template.sol/ERC20Template.json' import Decimal from 'decimal.js' +const BN = require('bn.js') const MaxUint256 = '115792089237316195423570985008687907853269984665640564039457584007913129639934' @@ -29,7 +30,7 @@ export class Pool { } /** - * Estimate gas cost for collectMarketFee + * Estimate gas cost for approval function * @param {String} account * @param {String} tokenAddress * @param {String} spender @@ -53,10 +54,11 @@ export class Pool { let estGas try { estGas = await tokenContract.methods - .approve(spender, amount) + .approve(spender, new BigNumber(amount)) .estimateGas({ from: account }, (err, estGas) => (err ? gasLimitDefault : estGas)) } catch (e) { estGas = gasLimitDefault + LoggerInstance.error('estimage gas failed for approve!', e) } return estGas } @@ -162,6 +164,38 @@ export class Pool { return result } + /** + * Estimate gas cost for setSwapFee + * @param {String} account + * @param {String} tokenAddress + * @param {String} spender + * @param {String} amount + * @param {String} force + * @param {Contract} contractInstance optional contract instance + * @return {Promise} + */ + public async estSetSwapFee( + account: string, + poolAddress: string, + fee: string, + contractInstance?: Contract + ): Promise { + const poolContract = + contractInstance || + new this.web3.eth.Contract(defaultERC20ABI.abi as AbiItem[], poolAddress) + + const gasLimitDefault = this.GASLIMIT_DEFAULT + let estGas + try { + estGas = await poolContract.methods + .setSwapFee(fee) + .estimateGas({ from: account }, (err, estGas) => (err ? gasLimitDefault : estGas)) + } catch (e) { + estGas = gasLimitDefault + } + return estGas + } + /** * Set pool fee * @param {String} account @@ -177,10 +211,12 @@ export class Pool { from: account }) let result = null + const estGas = await this.estSetSwapFee(account, poolAddress, fee) + try { result = await pool.methods.setSwapFee(this.web3.utils.toWei(fee)).send({ from: account, - gas: this.GASLIMIT_DEFAULT, + gas: estGas, gasPrice: await getFairGasPrice(this.web3) }) } catch (e) { @@ -705,7 +741,7 @@ export class Pool { tokenAmountIn, tokenOut, minAmountOut, - maxPrice ? this.web3.utils.toWei(maxPrice) : MaxUint256 + maxPrice || MaxUint256 ) .estimateGas({ from: address }, (err, estGas) => (err ? gasLimitDefault : estGas)) } catch (e) { @@ -715,37 +751,38 @@ export class Pool { } async amountToUnits(token: string, amount: string): Promise { - let decimals = 18 - const tokenContract = new this.web3.eth.Contract( - defaultERC20ABI.abi as AbiItem[], - token - ) try { - decimals = await tokenContract.methods.decimals().call() + const tokenContract = new this.web3.eth.Contract( + defaultERC20ABI.abi as AbiItem[], + token + ) + let decimals = await tokenContract.methods.decimals().call() + if (decimals === '0') { + decimals = 18 + } + const amountFormatted = new BigNumber(parseInt(amount) * 10 ** decimals) + return amountFormatted.toString() } catch (e) { this.logger.error('ERROR: FAILED TO CALL DECIMALS(), USING 18') } - - const amountFormatted = new BigNumber(parseInt(amount) * 10 ** decimals) - - return amountFormatted.toString() } async unitsToAmount(token: string, amount: string): Promise { - let decimals = 18 - const tokenContract = new this.web3.eth.Contract( - defaultERC20ABI.abi as AbiItem[], - token - ) try { - decimals = await tokenContract.methods.decimals().call() + const tokenContract = new this.web3.eth.Contract( + defaultERC20ABI.abi as AbiItem[], + token + ) + let decimals = await tokenContract.methods.decimals().call() + if (decimals === '0') { + decimals = 18 + } + const amountFormatted = new BigNumber(parseInt(amount) / 10 ** decimals) + + return amountFormatted.toString() } catch (e) { this.logger.error('ERROR: FAILED TO CALL DECIMALS(), USING 18') } - - const amountFormatted = new BigNumber(parseInt(amount) / 10 ** decimals) - - return amountFormatted.toString() } /** @@ -785,7 +822,8 @@ export class Pool { minAmountOutFormatted.toString(), maxPrice ? this.web3.utils.toWei(maxPrice) : MaxUint256 ) - console.log(minAmountOutFormatted, 'minamoutnoutformatted') + + // console.log(minAmountOutFormatted, 'minamoutnoutformatted') try { result = await pool.methods .swapExactAmountIn( @@ -842,7 +880,7 @@ export class Pool { maxAmountIn, tokenOut, amountOut, - maxPrice ? this.web3.utils.toWei(maxPrice) : MaxUint256 + maxPrice || MaxUint256 ) .estimateGas({ from: address }, (err, estGas) => (err ? gasLimitDefault : estGas)) } catch (e) { @@ -930,7 +968,7 @@ export class Pool { let estGas try { estGas = await poolContract.methods - .joinPool(this.web3.utils.toWei(poolAmountOut), maxAmountsIn) + .joinPool(poolAmountOut, maxAmountsIn) .estimateGas({ from: address }, (err, estGas) => (err ? gasLimitDefault : estGas)) } catch (e) { estGas = gasLimitDefault @@ -1009,7 +1047,7 @@ export class Pool { let estGas try { estGas = await poolContract.methods - .exitPool(this.web3.utils.toWei(poolAmountIn), minAmountsOut) + .exitPool(poolAmountIn, minAmountsOut) .estimateGas({ from: address }, (err, estGas) => (err ? gasLimitDefault : estGas)) } catch (e) { estGas = gasLimitDefault @@ -1046,6 +1084,7 @@ export class Pool { this.web3.utils.toWei(poolAmountIn), weiMinAmountsOut ) + try { result = await pool.methods .exitPool(this.web3.utils.toWei(poolAmountIn), weiMinAmountsOut) @@ -1198,6 +1237,7 @@ export class Pool { this.web3.utils.toWei(poolAmountOut), maxAmountInFormatted ) + try { result = await pool.methods .joinswapPoolAmountOut( @@ -1277,6 +1317,7 @@ export class Pool { this.web3.utils.toWei(poolAmountIn), minTokenOutFormatted ) + try { result = await pool.methods .exitswapPoolAmountIn( @@ -1355,6 +1396,7 @@ export class Pool { this.web3.utils.toWei(tokenAmountOut), this.web3.utils.toWei(maxPoolAmountIn) ) + try { result = await pool.methods .exitswapExternAmountOut( @@ -1472,6 +1514,7 @@ export class Pool { const result = await pool.methods .getAmountOutExactIn(tokenIn, tokenOut, amountInFormatted) .call() + amount = await this.unitsToAmount(tokenOut, result) } catch (e) { this.logger.error('ERROR: Failed to calcOutGivenIn') @@ -1481,27 +1524,18 @@ export class Pool { public async calcPoolOutGivenSingleIn( poolAddress: string, - tokenBalanceIn: string, - tokenWeightIn: string, - poolSupply: string, - totalWeight: string, - tokenAmountIn: string, - swapFee: string + tokenIn: string, + tokenAmountIn: string ): Promise { const pool = new this.web3.eth.Contract(this.poolABI, poolAddress) let amount = null + try { const result = await pool.methods - .calcPoolOutGivenSingleIn( - this.web3.utils.toWei(tokenBalanceIn), - this.web3.utils.toWei(tokenWeightIn), - this.web3.utils.toWei(poolSupply), - this.web3.utils.toWei(totalWeight), - this.web3.utils.toWei(tokenAmountIn), - this.web3.utils.toWei(swapFee) - ) + .calcPoolOutSingleIn(tokenIn, await this.amountToUnits(tokenIn, tokenAmountIn)) .call() - amount = this.web3.utils.fromWei(result) + + amount = await this.unitsToAmount(poolAddress, result) } catch (e) { this.logger.error(`ERROR: Failed to calculate PoolOutGivenSingleIn : ${e.message}`) } @@ -1510,27 +1544,19 @@ export class Pool { public async calcSingleInGivenPoolOut( poolAddress: string, - tokenBalanceIn: string, - tokenWeightIn: string, - poolSupply: string, - totalWeight: string, - poolAmountOut: string, - swapFee: string + tokenIn: string, + poolAmountOut: string ): Promise { const pool = new this.web3.eth.Contract(this.poolABI, poolAddress) let amount = null + const amountFormatted = await this.amountToUnits(poolAddress, poolAmountOut) try { const result = await pool.methods - .calcSingleInGivenPoolOut( - this.web3.utils.toWei(tokenBalanceIn), - this.web3.utils.toWei(tokenWeightIn), - this.web3.utils.toWei(poolSupply), - this.web3.utils.toWei(totalWeight), - this.web3.utils.toWei(poolAmountOut), - this.web3.utils.toWei(swapFee) - ) + .calcSingleInPoolOut(tokenIn, amountFormatted) + .call() - amount = this.web3.utils.fromWei(result) + + amount = await this.unitsToAmount(tokenIn, result) } catch (e) { this.logger.error(`ERROR: Failed to calculate SingleInGivenPoolOut : ${e.message}`) } @@ -1539,56 +1565,40 @@ export class Pool { public async calcSingleOutGivenPoolIn( poolAddress: string, - tokenBalanceOut: string, - tokenWeightOut: string, - poolSupply: string, - totalWeight: string, - poolAmountIn: string, - swapFee: string + tokenOut: string, + poolAmountIn: string ): Promise { const pool = new this.web3.eth.Contract(this.poolABI, poolAddress) let amount = null + try { const result = await pool.methods - .calcSingleOutGivenPoolIn( - this.web3.utils.toWei(tokenBalanceOut), - this.web3.utils.toWei(tokenWeightOut), - this.web3.utils.toWei(poolSupply), - this.web3.utils.toWei(totalWeight), - this.web3.utils.toWei(poolAmountIn), - this.web3.utils.toWei(swapFee) + .calcSingleOutPoolIn( + tokenOut, + await this.amountToUnits(poolAddress, poolAmountIn) ) .call() - amount = this.web3.utils.fromWei(result) + amount = await this.unitsToAmount(tokenOut, result) } catch (e) { - this.logger.error(`ERROR: Failed to calculate SingleOutGivenPoolIn : ${e.message}`) + this.logger.error(`ERROR: Failed to calculate SingleOutGivenPoolIn : ${e}`) } return amount } public async calcPoolInGivenSingleOut( poolAddress: string, - tokenBalanceOut: string, - tokenWeightOut: string, - poolSupply: string, - totalWeight: string, - tokenAmountOut: string, - swapFee: string + tokenOut: string, + tokenAmountOut: string ): Promise { const pool = new this.web3.eth.Contract(this.poolABI, poolAddress) let amount = null + try { const result = await pool.methods - .calcPoolInGivenSingleOut( - this.web3.utils.toWei(tokenBalanceOut), - this.web3.utils.toWei(tokenWeightOut), - this.web3.utils.toWei(poolSupply), - this.web3.utils.toWei(totalWeight), - this.web3.utils.toWei(tokenAmountOut), - this.web3.utils.toWei(swapFee) - ) + .calcPoolInSingleOut(tokenOut, await this.amountToUnits(tokenOut, tokenAmountOut)) .call() - amount = this.web3.utils.fromWei(result) + + amount = await this.unitsToAmount(poolAddress, result) } catch (e) { this.logger.error(`ERROR: Failed to calculate PoolInGivenSingleOut : ${e.message}`) } diff --git a/src/pools/fixedRate/FixedRateExchange.ts b/src/pools/fixedRate/FixedRateExchange.ts index 66928124..b451f182 100644 --- a/src/pools/fixedRate/FixedRateExchange.ts +++ b/src/pools/fixedRate/FixedRateExchange.ts @@ -1,6 +1,1121 @@ +import defaultFixedRateExchangeABI from '@oceanprotocol/contracts/artifacts/contracts/pools/fixedRate/FixedRateExchange.sol/FixedRateExchange.json' +import defaultERC20ABI from '@oceanprotocol/contracts/artifacts/contracts/templates/ERC20Template.sol/ERC20Template.json' +import BigNumber from 'bignumber.js' +import { TransactionReceipt } from 'web3-core' +import { Contract, EventData } from 'web3-eth-contract' +import { AbiItem } from 'web3-utils/types' import Web3 from 'web3' +import { LoggerInstance, getFairGasPrice } from '../../utils' + +const MAX_AWAIT_PROMISES = 10 + +export interface FixedPriceExchange { + active: boolean + exchangeOwner: string + dataToken: string + baseToken: string + fixedRate: string + dtDecimals: string + btDecimals: string + dtBalance: string + btBalance: string + dtSupply: string + btSupply: string + withMint: boolean + allowedSwapper: string + exchangeID?: string +} + +export interface FeesInfo { + opfFee: string + marketFee: string + marketFeeCollector: string + marketFeeAvailable: string + oceanFeeAvailable: string + exchangeID: string +} +export interface FixedPriceSwap { + exchangeID: string + caller: string + baseTokenAmount: string + dataTokenAmount: string +} + +export enum FixedRateCreateProgressStep { + CreatingExchange, + ApprovingDatatoken +} export class FixedRateExchange { public GASLIMIT_DEFAULT = 1000000 - public web3: Web3 = null + /** Ocean related functions */ + public oceanAddress: string = null + public fixedRateAddress: string + public fixedRateExchangeABI: AbiItem | AbiItem[] + public fixedRateContract: Contract + public web3: Web3 + public contract: Contract = null + + public startBlock: number + public ssABI: AbiItem | AbiItem[] + + /** + * Instantiate FixedRateExchange + * @param {any} web3 + * @param {any} fixedRateExchangeABI + */ + constructor( + web3: Web3, + fixedRateAddress: string, + fixedRateExchangeABI: AbiItem | AbiItem[] = null, + oceanAddress: string = null, + startBlock?: number + ) { + this.web3 = web3 + + if (startBlock) this.startBlock = startBlock + else this.startBlock = 0 + this.fixedRateExchangeABI = + fixedRateExchangeABI || (defaultFixedRateExchangeABI.abi as AbiItem[]) + this.oceanAddress = oceanAddress + this.fixedRateAddress = fixedRateAddress + this.contract = new this.web3.eth.Contract( + this.fixedRateExchangeABI, + this.fixedRateAddress + ) + } + + async amountToUnits(token: string, amount: string): Promise { + let decimals = 18 + const tokenContract = new this.web3.eth.Contract( + defaultERC20ABI.abi as AbiItem[], + token + ) + + try { + decimals = await tokenContract.methods.decimals().call() + } catch (e) { + LoggerInstance.error('ERROR: FAILED TO CALL DECIMALS(), USING 18') + } + + const amountFormatted = new BigNumber(parseInt(amount) * 10 ** decimals) + + return amountFormatted.toString() + } + + async unitsToAmount(token: string, amount: string): Promise { + let decimals = 18 + const tokenContract = new this.web3.eth.Contract( + defaultERC20ABI.abi as AbiItem[], + token + ) + try { + decimals = await tokenContract.methods.decimals().call() + } catch (e) { + LoggerInstance.error('ERROR: FAILED TO CALL DECIMALS(), USING 18') + } + + const amountFormatted = new BigNumber(parseInt(amount) / 10 ** decimals) + + return amountFormatted.toString() + } + + /** + * Creates unique exchange identifier. + * @param {String} dataToken Data Token Contract Address + * @param {String} owner Owner of the exchange + * @return {Promise} exchangeId + */ + public async generateExchangeId( + basetoken: string, + dataToken: string, + owner: string + ): Promise { + const exchangeId = await this.contract.methods + .generateExchangeId(basetoken, dataToken, owner) + .call() + return exchangeId + } + + /** + * Estimate gas cost for buyDT + * @param {String} account + * @param {String} dtAmount datatoken amount we want to buy + * @param {String} datatokenAddress datatokenAddress + * @param {Contract} contractInstance optional contract instance + * @return {Promise} + */ + public async estBuyDT( + account: string, + datatokenAddress: string, + dtAmount: string, + maxBasetokenAmount: string, + contractInstance?: Contract + ): Promise { + const fixedRate = contractInstance || this.fixedRateContract + const gasLimitDefault = this.GASLIMIT_DEFAULT + let estGas + try { + estGas = await fixedRate.methods + .buyDT(datatokenAddress, dtAmount.toString(), maxBasetokenAmount.toString()) + .estimateGas({ from: account }, (err, estGas) => (err ? gasLimitDefault : estGas)) + } catch (e) { + estGas = gasLimitDefault + } + return estGas + } + + /** + * Atomic swap + * @param {String} exchangeId ExchangeId + * @param {String} datatokenAmount Amount of Data Tokens + * @param {String} maxBasetokenAmount max amount of basetoken we want to pay for dataTokenAmount + * @param {String} address User address + * @return {Promise} transaction receipt + */ + public async buyDT( + address: string, + exchangeId: string, + datatokenAmount: string, + maxBasetokenAmount: string + ): Promise { + const dtAmountFormatted = await this.amountToUnits( + ( + await this.getExchange(exchangeId) + ).dataToken, + datatokenAmount + ) + const maxBtFormatted = await this.amountToUnits( + ( + await this.getExchange(exchangeId) + ).baseToken, + maxBasetokenAmount + ) + + const estGas = await this.estBuyDT( + address, + exchangeId, + dtAmountFormatted, + maxBtFormatted + ) + try { + const trxReceipt = await this.contract.methods + .buyDT(exchangeId, dtAmountFormatted, maxBtFormatted) + .send({ + from: address, + gas: estGas + 1, + gasPrice: await getFairGasPrice(this.web3) + }) + return trxReceipt + } catch (e) { + LoggerInstance.error(`ERROR: Failed to buy datatokens: ${e.message}`) + return null + } + } + + /** + * Estimate gas cost for sellDT + * @param {String} account + * @param {String} dtAmount datatoken amount we want to sell + * @param {String} datatokenAddress datatokenAddress + * @param {Contract} contractInstance optional contract instance + * @return {Promise} + */ + public async estSellDT( + account: string, + datatokenAddress: string, + dtAmount: string, + maxBasetokenAmount: string, + contractInstance?: Contract + ): Promise { + const fixedRate = contractInstance || this.fixedRateContract + const gasLimitDefault = this.GASLIMIT_DEFAULT + let estGas + try { + estGas = await fixedRate.methods + .sellDT(datatokenAddress, dtAmount, maxBasetokenAmount) + .estimateGas({ from: account }, (err, estGas) => (err ? gasLimitDefault : estGas)) + } catch (e) { + estGas = gasLimitDefault + } + return estGas + } + + /** + * Atomic swap + * @param {String} exchangeId ExchangeId + * @param {String} datatokenAmount Amount of Data Tokens + * @param {String} minBasetokenAmount min amount of basetoken we want to receive back + * @param {String} address User address + * @return {Promise} transaction receipt + */ + public async sellDT( + address: string, + exchangeId: string, + datatokenAmount: string, + minBasetokenAmount: string + ): Promise { + const dtAmountFormatted = await this.amountToUnits( + ( + await this.getExchange(exchangeId) + ).dataToken, + datatokenAmount + ) + const minBtFormatted = await this.amountToUnits( + ( + await this.getExchange(exchangeId) + ).baseToken, + minBasetokenAmount + ) + const estGas = await this.estBuyDT( + address, + exchangeId, + dtAmountFormatted, + minBtFormatted + ) + try { + const trxReceipt = await this.contract.methods + .sellDT(exchangeId, dtAmountFormatted, minBtFormatted) + .send({ + from: address, + gas: estGas + 1, + gasPrice: await getFairGasPrice(this.web3) + }) + return trxReceipt + } catch (e) { + LoggerInstance.error(`ERROR: Failed to sell datatokens: ${e.message}`) + return null + } + } + + /** + * Gets total number of exchanges + * @param {String} exchangeId ExchangeId + * @param {Number} dataTokenAmount Amount of Data Tokens + * @return {Promise} no of available exchanges + */ + public async getNumberOfExchanges(): Promise { + const numExchanges = await this.contract.methods.getNumberOfExchanges().call() + return numExchanges + } + + /** + * Estimate gas cost for setRate + * @param {String} account + * @param {String} exchangeId ExchangeId + * @param {Number} newRate New rate + * @param {Contract} contractInstance optional contract instance + * @return {Promise} + */ + public async estSetRate( + account: string, + exchangeId: string, + newRate: string, + contractInstance?: Contract + ): Promise { + const fixedRate = contractInstance || this.fixedRateContract + const gasLimitDefault = this.GASLIMIT_DEFAULT + let estGas + try { + estGas = await fixedRate.methods + .setRate(exchangeId, newRate) + .estimateGas({ from: account }, (err, estGas) => (err ? gasLimitDefault : estGas)) + } catch (e) { + estGas = gasLimitDefault + } + return estGas + } + + /** + * Set new rate + * @param {String} exchangeId ExchangeId + * @param {Number} newRate New rate + * @param {String} address User account + * @return {Promise} transaction receipt + */ + public async setRate( + address: string, + exchangeId: string, + newRate: string + ): Promise { + const estGas = await this.estSetRate( + address, + exchangeId, + this.web3.utils.toWei(String(newRate)) + ) + const trxReceipt = await this.contract.methods + .setRate(exchangeId, this.web3.utils.toWei(newRate)) + .send({ + from: address, + gas: estGas + 1, + gasPrice: await getFairGasPrice(this.web3) + }) + return trxReceipt + } + + /** + * Estimate gas cost for setRate + * @param {String} account + * @param {String} exchangeId ExchangeId + * @param {String} newAllowedSwapper new allowed swapper address + * @param {Contract} contractInstance optional contract instance + * @return {Promise} + */ + public async estSetAllowedSwapper( + account: string, + exchangeId: string, + newAllowedSwapper: string, + contractInstance?: Contract + ): Promise { + const fixedRate = contractInstance || this.fixedRateContract + const gasLimitDefault = this.GASLIMIT_DEFAULT + let estGas + try { + estGas = await fixedRate.methods + .setRate(exchangeId, newAllowedSwapper) + .estimateGas({ from: account }, (err, estGas) => (err ? gasLimitDefault : estGas)) + } catch (e) { + estGas = gasLimitDefault + } + return estGas + } + + /** + * Set new rate + * @param {String} exchangeId ExchangeId + * @param {String} newAllowedSwapper newAllowedSwapper (set address zero if we want to remove allowed swapper) + * @param {String} address User account + * @return {Promise} transaction receipt + */ + public async setAllowedSwapper( + address: string, + exchangeId: string, + newAllowedSwapper: string + ): Promise { + const estGas = await this.estSetAllowedSwapper(address, exchangeId, newAllowedSwapper) + const trxReceipt = await this.contract.methods + .setAllowedSwapper(exchangeId, newAllowedSwapper) + .send({ + from: address, + gas: estGas + 1, + gasPrice: await getFairGasPrice(this.web3) + }) + return trxReceipt + } + + /** + * Estimate gas cost for activate + * @param {String} account + * @param {String} exchangeId ExchangeId + * @param {Contract} contractInstance optional contract instance + * @return {Promise} + */ + public async estActivate( + account: string, + exchangeId: string, + contractInstance?: Contract + ): Promise { + const fixedRate = contractInstance || this.fixedRateContract + const gasLimitDefault = this.GASLIMIT_DEFAULT + let estGas + try { + estGas = await fixedRate.methods + .toggleExchangeState(exchangeId) + .estimateGas({ from: account }, (err, estGas) => (err ? gasLimitDefault : estGas)) + } catch (e) { + estGas = gasLimitDefault + } + return estGas + } + + /** + * Activate an exchange + * @param {String} exchangeId ExchangeId + * @param {String} address User address + * @return {Promise} transaction receipt + */ + public async activate( + address: string, + exchangeId: string + ): Promise { + const exchange = await this.getExchange(exchangeId) + if (!exchange) return null + if (exchange.active === true) return null + const gasLimitDefault = this.GASLIMIT_DEFAULT + + const estGas = await this.estActivate(address, exchangeId) + const trxReceipt = await this.contract.methods.toggleExchangeState(exchangeId).send({ + from: address, + gas: estGas + 1, + gasPrice: await getFairGasPrice(this.web3) + }) + return trxReceipt + } + + /** + * Estimate gas cost for deactivate + * @param {String} account + * @param {String} exchangeId ExchangeId + * @param {Contract} contractInstance optional contract instance + * @return {Promise} + */ + public async estDeactivate( + account: string, + exchangeId: string, + contractInstance?: Contract + ): Promise { + const fixedRate = contractInstance || this.fixedRateContract + const gasLimitDefault = this.GASLIMIT_DEFAULT + let estGas + try { + estGas = await fixedRate.methods + .toggleExchangeState(exchangeId) + .estimateGas({ from: account }, (err, estGas) => (err ? gasLimitDefault : estGas)) + } catch (e) { + estGas = gasLimitDefault + } + return estGas + } + + /** + * Deactivate an exchange + * @param {String} exchangeId ExchangeId + * @param {String} address User address + * @return {Promise} transaction receipt + */ + public async deactivate( + address: string, + exchangeId: string + ): Promise { + const exchange = await this.getExchange(exchangeId) + if (!exchange) return null + if (exchange.active === false) return null + + const estGas = await this.estDeactivate(address, exchangeId) + + const trxReceipt = await this.contract.methods.toggleExchangeState(exchangeId).send({ + from: address, + gas: estGas + 1, + gasPrice: await getFairGasPrice(this.web3) + }) + + return trxReceipt + } + + /** + * Get Rate + * @param {String} exchangeId ExchangeId + * @return {Promise} Rate (converted from wei) + */ + public async getRate(exchangeId: string): Promise { + const weiRate = await this.contract.methods.getRate(exchangeId).call() + return this.web3.utils.fromWei(weiRate) + } + + /** + * Get Datatoken Supply in the exchange + * @param {String} exchangeId ExchangeId + * @return {Promise} dt supply formatted + */ + public async getDTSupply(exchangeId: string): Promise { + const dtSupply = await this.contract.methods.getDTSupply(exchangeId).call() + return await this.unitsToAmount( + ( + await this.getExchange(exchangeId) + ).dataToken, + dtSupply + ) + } + + /** + * Get Basetoken Supply in the exchange + * @param {String} exchangeId ExchangeId + * @return {Promise} dt supply formatted + */ + public async getBTSupply(exchangeId: string): Promise { + const btSupply = await this.contract.methods.getBTSupply(exchangeId).call() + return await this.unitsToAmount( + ( + await this.getExchange(exchangeId) + ).baseToken, + btSupply + ) + } + + /** + * Get Allower Swapper (if set this is the only account which can use this exchange, else is set at address(0)) + * @param {String} exchangeId ExchangeId + * @return {Promise} address of allowedSwapper + */ + public async getAllowedSwapper(exchangeId: string): Promise { + return await this.contract.methods.getAllowedSwapper(exchangeId).call() + } + + /** + * getBTNeeded - returns amount in basetoken that user will pay for dataTokenAmount + * @param {String} exchangeId ExchangeId + * @param {Number} dataTokenAmount Amount of Data Tokens user wants to buy + * @return {Promise} Amount of basetoken needed for buying + */ + public async getAmountBTIn( + exchangeId: string, + dataTokenAmount: string + ): Promise { + const result = await this.contract.methods + .calcBaseInGivenOutDT( + exchangeId, + await this.amountToUnits( + ( + await this.getExchange(exchangeId) + ).dataToken, + dataTokenAmount + ) + ) + .call() + + return await this.unitsToAmount( + ( + await this.getExchange(exchangeId) + ).baseToken, + result.baseTokenAmount + ) + } + + /** + * getBTOut - returns amount in basetoken that user will receive for dataTokenAmount sold + * @param {String} exchangeId ExchangeId + * @param {Number} dataTokenAmount Amount of Data Tokens + * @return {Promise} Amount of basetokens user will receive + */ + public async getAmountBTOut( + exchangeId: string, + dataTokenAmount: string + ): Promise { + const result = await this.contract.methods + .calcBaseOutGivenInDT( + exchangeId, + await this.amountToUnits( + ( + await this.getExchange(exchangeId) + ).dataToken, + dataTokenAmount + ) + ) + .call() + + return await this.unitsToAmount( + ( + await this.getExchange(exchangeId) + ).baseToken, + result.baseTokenAmount + ) + } + + /** + * Get exchange details + * @param {String} exchangeId ExchangeId + * @return {Promise} Exchange details + */ + public async getExchange(exchangeId: string): Promise { + const result: FixedPriceExchange = await this.contract.methods + .getExchange(exchangeId) + .call() + result.dtDecimals = result.dtDecimals.toString() + result.btDecimals = result.btDecimals.toString() + result.dtBalance = await this.unitsToAmount(result.dataToken, result.dtBalance) + result.btBalance = await this.unitsToAmount(result.baseToken, result.btBalance) + result.dtSupply = await this.unitsToAmount(result.dataToken, result.dtSupply) + result.btSupply = await this.unitsToAmount(result.baseToken, result.btSupply) + result.fixedRate = this.web3.utils.fromWei(result.fixedRate) + result.exchangeID = exchangeId + return result + } + + /** + * Get fee details for an exchange + * @param {String} exchangeId ExchangeId + * @return {Promise} Exchange details + */ + public async getFeesInfo(exchangeId: string): Promise { + const result: FeesInfo = await this.contract.methods.getFeesInfo(exchangeId).call() + result.opfFee = this.web3.utils.fromWei(result.opfFee.toString()) + result.marketFee = this.web3.utils.fromWei(result.marketFee.toString()) + + result.marketFeeAvailable = await this.unitsToAmount( + ( + await this.getExchange(exchangeId) + ).baseToken, + result.marketFeeAvailable + ) + result.oceanFeeAvailable = await this.unitsToAmount( + ( + await this.getExchange(exchangeId) + ).baseToken, + result.oceanFeeAvailable + ) + + result.exchangeID = exchangeId + return result + } + + /** + * Get all exchanges + * @param {String} exchangeId ExchangeId + * @return {Promise} Exchanges list + */ + public async getExchanges(): Promise { + return await this.contract.methods.getExchanges().call() + } + + /** + * Check if an exchange is active + * @param {String} exchangeId ExchangeId + * @return {Promise} Result + */ + public async isActive(exchangeId: string): Promise { + const result = await this.contract.methods.isActive(exchangeId).call() + return result + } + + /** + * Estimate gas cost for activate + * @param {String} account + * @param {String} exchangeId ExchangeId + * @param {Contract} contractInstance optional contract instance + * @return {Promise} + */ + public async estActivateMint( + account: string, + exchangeId: string, + contractInstance?: Contract + ): Promise { + const fixedRate = contractInstance || this.fixedRateContract + const gasLimitDefault = this.GASLIMIT_DEFAULT + let estGas + try { + estGas = await fixedRate.methods + .toggleMintState(exchangeId, true) + .estimateGas({ from: account }, (err, estGas) => (err ? gasLimitDefault : estGas)) + } catch (e) { + estGas = gasLimitDefault + } + return estGas + } + + /** + * Activate minting option for fixed rate contract + * @param {String} exchangeId ExchangeId + * @param {String} address User address + * @return {Promise} transaction receipt + */ + public async activateMint( + address: string, + exchangeId: string + ): Promise { + const exchange = await this.getExchange(exchangeId) + if (!exchange) return null + if (exchange.withMint === true) return null + const gasLimitDefault = this.GASLIMIT_DEFAULT + + const estGas = await this.estActivateMint(address, exchangeId) + const trxReceipt = await this.contract.methods + .toggleMintState(exchangeId, true) + .send({ + from: address, + gas: estGas + 1, + gasPrice: await getFairGasPrice(this.web3) + }) + return trxReceipt + } + + /** + * Estimate gas cost for deactivate + * @param {String} account + * @param {String} exchangeId ExchangeId + * @param {Contract} contractInstance optional contract instance + * @return {Promise} + */ + public async estDeactivateMint( + account: string, + exchangeId: string, + contractInstance?: Contract + ): Promise { + const fixedRate = contractInstance || this.fixedRateContract + const gasLimitDefault = this.GASLIMIT_DEFAULT + let estGas + try { + estGas = await fixedRate.methods + .toggleMintState(exchangeId) + .estimateGas({ from: account }, (err, estGas) => (err ? gasLimitDefault : estGas)) + } catch (e) { + estGas = gasLimitDefault + } + return estGas + } + + /** + * Deactivate minting for fixed rate + * @param {String} exchangeId ExchangeId + * @param {String} address User address + * @return {Promise} transaction receipt + */ + public async deactivateMint( + address: string, + exchangeId: string + ): Promise { + const exchange = await this.getExchange(exchangeId) + if (!exchange) return null + if (exchange.withMint === false) return null + + const estGas = await this.estDeactivate(address, exchangeId) + + const trxReceipt = await this.contract.methods + .toggleMintState(exchangeId, false) + .send({ + from: address, + gas: estGas + 1, + gasPrice: await getFairGasPrice(this.web3) + }) + + return trxReceipt + } + + /** + * Estimate gas cost for collectBT + * @param {String} account + * @param {String} exchangeId ExchangeId + * @param {Contract} contractInstance optional contract instance + * @return {Promise} + */ + public async estCollectBT( + account: string, + exchangeId: string, + contractInstance?: Contract + ): Promise { + const fixedRate = contractInstance || this.fixedRateContract + const gasLimitDefault = this.GASLIMIT_DEFAULT + let estGas + try { + estGas = await fixedRate.methods + .collectBT(exchangeId) + .estimateGas({ from: account }, (err, estGas) => (err ? gasLimitDefault : estGas)) + } catch (e) { + estGas = gasLimitDefault + } + return estGas + } + + /** + * Collect Basetokens in the contract (only exchange owner) + * @param {String} exchangeId ExchangeId + * @param {String} address User address + * @return {Promise} transaction receipt + */ + public async collectBT( + address: string, + exchangeId: string + ): Promise { + const exchange = await this.getExchange(exchangeId) + if (!exchange) return null + + const estGas = await this.estCollectBT(address, exchangeId) + const trxReceipt = await this.contract.methods.collectBT(exchangeId).send({ + from: address, + gas: estGas + 1, + gasPrice: await getFairGasPrice(this.web3) + }) + return trxReceipt + } + + /** + * Estimate gas cost for collecDT + * @param {String} account + * @param {String} exchangeId ExchangeId + * @param {Contract} contractInstance optional contract instance + * @return {Promise} + */ + public async estCollectDT( + account: string, + exchangeId: string, + contractInstance?: Contract + ): Promise { + const fixedRate = contractInstance || this.fixedRateContract + const gasLimitDefault = this.GASLIMIT_DEFAULT + let estGas + try { + estGas = await fixedRate.methods + .collectDT(exchangeId) + .estimateGas({ from: account }, (err, estGas) => (err ? gasLimitDefault : estGas)) + } catch (e) { + estGas = gasLimitDefault + } + return estGas + } + + /** + * Collect datatokens in the contract (only exchange owner) + * @param {String} exchangeId ExchangeId + * @param {String} address User address + * @return {Promise} transaction receipt + */ + public async collectDT( + address: string, + exchangeId: string + ): Promise { + const exchange = await this.getExchange(exchangeId) + if (!exchange) return null + + const estGas = await this.estCollectDT(address, exchangeId) + const trxReceipt = await this.contract.methods.collectDT(exchangeId).send({ + from: address, + gas: estGas + 1, + gasPrice: await getFairGasPrice(this.web3) + }) + return trxReceipt + } + + /** + * Estimate gas cost for collecMarketFee + * @param {String} account + * @param {String} exchangeId ExchangeId + * @param {Contract} contractInstance optional contract instance + * @return {Promise} + */ + public async estCollectMarketFee( + account: string, + exchangeId: string, + contractInstance?: Contract + ): Promise { + const fixedRate = contractInstance || this.fixedRateContract + const gasLimitDefault = this.GASLIMIT_DEFAULT + let estGas + try { + estGas = await fixedRate.methods + .collectMarketFee(exchangeId) + .estimateGas({ from: account }, (err, estGas) => (err ? gasLimitDefault : estGas)) + } catch (e) { + estGas = gasLimitDefault + } + return estGas + } + + /** + * Collect market fee and send it to marketFeeCollector (anyone can call it) + * @param {String} exchangeId ExchangeId + * @param {String} address User address + * @return {Promise} transaction receipt + */ + public async collectMarketFee( + address: string, + exchangeId: string + ): Promise { + const exchange = await this.getExchange(exchangeId) + if (!exchange) return null + + const estGas = await this.estCollectMarketFee(address, exchangeId) + const trxReceipt = await this.contract.methods.collectMarketFee(exchangeId).send({ + from: address, + gas: estGas + 1, + gasPrice: await getFairGasPrice(this.web3) + }) + return trxReceipt + } + + /** + * Estimate gas cost for collectOceanFee + * @param {String} account + * @param {String} exchangeId ExchangeId + * @param {Contract} contractInstance optional contract instance + * @return {Promise} + */ + public async estCollectOceanFee( + account: string, + exchangeId: string, + contractInstance?: Contract + ): Promise { + const fixedRate = contractInstance || this.fixedRateContract + const gasLimitDefault = this.GASLIMIT_DEFAULT + let estGas + try { + estGas = await fixedRate.methods + .collectMarketFee(exchangeId) + .estimateGas({ from: account }, (err, estGas) => (err ? gasLimitDefault : estGas)) + } catch (e) { + estGas = gasLimitDefault + } + return estGas + } + + /** + * Collect ocean fee and send it to OPF collector (anyone can call it) + * @param {String} exchangeId ExchangeId + * @param {String} address User address + * @return {Promise} transaction receipt + */ + public async collectOceanFee( + address: string, + exchangeId: string + ): Promise { + const exchange = await this.getExchange(exchangeId) + if (!exchange) return null + + const estGas = await this.estCollectOceanFee(address, exchangeId) + const trxReceipt = await this.contract.methods.collectOceanFee(exchangeId).send({ + from: address, + gas: estGas + 1, + gasPrice: await getFairGasPrice(this.web3) + }) + return trxReceipt + } + + /** + * Get OPF Collector of fixed rate contract + * @return {String} + */ + async getOPFCollector(): Promise { + let result = null + try { + result = await this.contract.methods.opfCollector().call() + } catch (e) { + LoggerInstance.error(`ERROR: Failed to get OPF Collector address: ${e.message}`) + } + return result + } + + /** + * Get Router address set in fixed rate contract + * @return {String} + */ + async getRouter(): Promise { + let result = null + try { + result = await this.contract.methods.router().call() + } catch (e) { + LoggerInstance.error(`ERROR: Failed to get Router address: ${e.message}`) + } + return result + } + + /** + * Get Exchange Owner given an exchangeId + * @param {String} exchangeId ExchangeId + * @return {String} return exchange owner + */ + async getExchangeOwner(exchangeId: string): Promise { + let result = null + try { + result = await (await this.getExchange(exchangeId)).exchangeOwner + } catch (e) { + LoggerInstance.error(`ERROR: Failed to get OPF Collector address: ${e.message}`) + } + return result + } + + /** + * Estimate gas cost for updateMarketFee + * @param {String} account + * @param {String} exchangeId ExchangeId + * @param {String} newMarketFee New market fee + * @param {Contract} contractInstance optional contract instance + * @return {Promise} + */ + public async estUpdateMarketFee( + account: string, + exchangeId: string, + newMarketFee: string, + contractInstance?: Contract + ): Promise { + const fixedRate = contractInstance || this.fixedRateContract + const gasLimitDefault = this.GASLIMIT_DEFAULT + let estGas + try { + estGas = await fixedRate.methods + .updateMarketFee(exchangeId, newMarketFee) + .estimateGas({ from: account }, (err, estGas) => (err ? gasLimitDefault : estGas)) + } catch (e) { + estGas = gasLimitDefault + } + return estGas + } + + /** + * Set new market fee, only market fee collector can update it + * @param {String} address user address + * @param {String} exchangeId ExchangeId + * @param {String} newMarketFee New market fee + * @return {Promise} transaction receipt + */ + public async updateMarketFee( + address: string, + exchangeId: string, + newMarketFee: string + ): Promise { + const estGas = await this.estSetRate( + address, + exchangeId, + this.web3.utils.toWei(newMarketFee) + ) + const trxReceipt = await this.contract.methods + .updateMarketFee(exchangeId, this.web3.utils.toWei(newMarketFee)) + .send({ + from: address, + gas: estGas + 1, + gasPrice: await getFairGasPrice(this.web3) + }) + return trxReceipt + } + + /** + * Estimate gas cost for updateMarketFeeCollector + * @param {String} account + * @param {String} exchangeId ExchangeId + * @param {String} newMarketFee New market fee collector + * @param {Contract} contractInstance optional contract instance + * @return {Promise} + */ + public async estUpdateMarketFeeCollector( + account: string, + exchangeId: string, + newMarketFeeCollector: string, + contractInstance?: Contract + ): Promise { + const fixedRate = contractInstance || this.fixedRateContract + const gasLimitDefault = this.GASLIMIT_DEFAULT + let estGas + try { + estGas = await fixedRate.methods + .updateMarketFeeCollector(exchangeId, newMarketFeeCollector) + .estimateGas({ from: account }, (err, estGas) => (err ? gasLimitDefault : estGas)) + } catch (e) { + estGas = gasLimitDefault + } + return estGas + } + + /** + * Set new market fee collector, only market fee collector can update it + * @param {String} address user address + * @param {String} exchangeId ExchangeId + * @param {String} newMarketFeeCollector New market fee collector + * @return {Promise} transaction receipt + */ + public async updateMarketFeeCollector( + address: string, + exchangeId: string, + newMarketFeeCollector: string + ): Promise { + const estGas = await this.estUpdateMarketFeeCollector( + address, + exchangeId, + newMarketFeeCollector + ) + const trxReceipt = await this.contract.methods + .updateMarketFeeCollector(exchangeId, newMarketFeeCollector) + .send({ + from: address, + gas: estGas + 1, + gasPrice: await getFairGasPrice(this.web3) + }) + return trxReceipt + } } diff --git a/src/pools/ssContracts/SideStaking.ts b/src/pools/ssContracts/SideStaking.ts new file mode 100644 index 00000000..c53b5740 --- /dev/null +++ b/src/pools/ssContracts/SideStaking.ts @@ -0,0 +1,355 @@ +import Web3 from 'web3' +import { AbiItem } from 'web3-utils/types' +import { TransactionReceipt } from 'web3-core' +import { Contract } from 'web3-eth-contract' +import { LoggerInstance, getFairGasPrice } from '../../utils' +import BigNumber from 'bignumber.js' +import SideStakingTemplate from '@oceanprotocol/contracts/artifacts/contracts/pools/ssContracts/SideStaking.sol/SideStaking.json' +import defaultPool from '@oceanprotocol/contracts/artifacts/contracts/pools/FactoryRouter.sol/FactoryRouter.json' +import defaultERC20ABI from '@oceanprotocol/contracts/artifacts/contracts/templates/ERC20Template.sol/ERC20Template.json' + +const MaxUint256 = + '115792089237316195423570985008687907853269984665640564039457584007913129639934' +/** + * Provides an interface to Ocean friendly fork from Balancer BPool + */ + +export class SideStaking { + public ssABI: AbiItem | AbiItem[] + public web3: Web3 + public GASLIMIT_DEFAULT = 1000000 + + constructor(web3: Web3, ssABI: AbiItem | AbiItem[] = null) { + if (ssABI) this.ssABI = ssABI + else this.ssABI = SideStakingTemplate.abi as AbiItem[] + this.web3 = web3 + } + + async amountToUnits(token: string, amount: string): Promise { + let decimals = 18 + const tokenContract = new this.web3.eth.Contract( + defaultERC20ABI.abi as AbiItem[], + token + ) + try { + decimals = await tokenContract.methods.decimals().call() + } catch (e) { + LoggerInstance.error('ERROR: FAILED TO CALL DECIMALS(), USING 18') + } + + const amountFormatted = new BigNumber(parseInt(amount) * 10 ** decimals) + + return amountFormatted.toString() + } + + async unitsToAmount(token: string, amount: string): Promise { + let decimals = 18 + const tokenContract = new this.web3.eth.Contract( + defaultERC20ABI.abi as AbiItem[], + token + ) + try { + decimals = await tokenContract.methods.decimals().call() + } catch (e) { + LoggerInstance.error('ERROR: FAILED TO CALL DECIMALS(), USING 18') + } + + const amountFormatted = new BigNumber(parseInt(amount) / 10 ** decimals) + + return amountFormatted.toString() + } + + /** + * Get (total vesting amount + token released from the contract when adding liquidity) + * @param {String} ssAddress side staking contract address + * @param {String} datatokenAddress datatoken address + * @return {String} + */ + async getDataTokenCirculatingSupply( + ssAddress: string, + datatokenAddress: string + ): Promise { + const sideStaking = new this.web3.eth.Contract(this.ssABI, ssAddress) + let result = null + try { + result = await sideStaking.methods + .getDataTokenCirculatingSupply(datatokenAddress) + .call() + } catch (e) { + LoggerInstance.error(`ERROR: Failed to get: ${e.message}`) + } + return result.toString() + } + + /** + * Get actual dts in circulation (vested token withdrawn from the contract + + token released from the contract when adding liquidity) + * @param {String} ssAddress side staking contract address + * @param {String} datatokenAddress datatoken address + * @return {String} + */ + async getDataTokenCurrentCirculatingSupply( + ssAddress: string, + datatokenAddress: string + ): Promise { + try { + const sideStaking = new this.web3.eth.Contract(this.ssABI, ssAddress) + let result = null + result = await sideStaking.methods + .getDataTokenCurrentCirculatingSupply(datatokenAddress) + .call() + return result.toString() + } catch (e) { + LoggerInstance.error(`ERROR: Failed to get: ${e.message}`) + } + } + + /** + * Get Publisher address + * @param {String} ssAddress side staking contract address + * @param {String} datatokenAddress datatoken address + * @return {String} + */ + async getPublisherAddress( + ssAddress: string, + datatokenAddress: string + ): Promise { + const sideStaking = new this.web3.eth.Contract(this.ssABI, ssAddress) + let result = null + try { + result = await sideStaking.methods.getPublisherAddress(datatokenAddress).call() + } catch (e) { + LoggerInstance.error(`ERROR: Failed to get: ${e.message}`) + } + return result + } + + /** + * Get + * @param {String} ssAddress side staking contract address + * @param {String} datatokenAddress datatokenAddress + * @return {String} + */ + async getBasetoken(ssAddress: string, datatokenAddress: string): Promise { + const sideStaking = new this.web3.eth.Contract(this.ssABI, ssAddress) + let result = null + try { + result = await sideStaking.methods.getBaseTokenAddress(datatokenAddress).call() + } catch (e) { + LoggerInstance.error(`ERROR: Failed to get: ${e.message}`) + } + return result + } + + /** + * Get Pool Address + * @param {String} ssAddress side staking contract address + * @param {String} datatokenAddress datatokenAddress + * @return {String} + */ + async getPoolAddress(ssAddress: string, datatokenAddress: string): Promise { + const sideStaking = new this.web3.eth.Contract(this.ssABI, ssAddress) + let result = null + try { + result = await sideStaking.methods.getPoolAddress(datatokenAddress).call() + } catch (e) { + LoggerInstance.error(`ERROR: Failed to get: ${e.message}`) + } + return result + } + + /** + * Get basetoken balance in the contract + * @param {String} ssAddress side staking contract address + * @param {String} datatokenAddress datatokenAddress + * @return {String} + */ + async getBasetokenBalance( + ssAddress: string, + datatokenAddress: string + ): Promise { + const sideStaking = new this.web3.eth.Contract(this.ssABI, ssAddress) + let result = null + try { + result = await sideStaking.methods.getBaseTokenBalance(datatokenAddress).call() + } catch (e) { + LoggerInstance.error(`ERROR: Failed to get: ${e.message}`) + } + return result + } + + /** + * Get dt balance in the staking contract available for being added as liquidity + * @param {String} ssAddress side staking contract address + * @param {String} datatokenAddress datatokenAddress + * @return {String} + */ + async getDatatokenBalance( + ssAddress: string, + datatokenAddress: string + ): Promise { + const sideStaking = new this.web3.eth.Contract(this.ssABI, ssAddress) + let result = null + try { + result = await sideStaking.methods.getDataTokenBalance(datatokenAddress).call() + } catch (e) { + LoggerInstance.error(`ERROR: Failed to get: ${e.message}`) + } + result = await this.unitsToAmount(datatokenAddress, result) + return result + } + + /** + * Get block when vesting ends + * @param {String} ssAddress side staking contract address + * @param {String} datatokenAddress datatokenAddress + * @return {String} end block for vesting amount + */ + async getvestingEndBlock(ssAddress: string, datatokenAddress: string): Promise { + const sideStaking = new this.web3.eth.Contract(this.ssABI, ssAddress) + let result = null + try { + result = await sideStaking.methods.getvestingEndBlock(datatokenAddress).call() + } catch (e) { + LoggerInstance.error(`ERROR: Failed to get: ${e.message}`) + } + return result + } + + /** + * Get total amount vesting + * @param {String} ssAddress side staking contract address + * @param {String} datatokenAddress datatokenAddress + * @return {String} + */ + async getvestingAmount(ssAddress: string, datatokenAddress: string): Promise { + const sideStaking = new this.web3.eth.Contract(this.ssABI, ssAddress) + let result = null + try { + result = await sideStaking.methods.getvestingAmount(datatokenAddress).call() + } catch (e) { + LoggerInstance.error(`ERROR: Failed to get: ${e.message}`) + } + result = await this.unitsToAmount(datatokenAddress, result) + return result + } + + /** + * Get last block publisher got some vested tokens + * @param {String} ssAddress side staking contract address + * @param {String} datatokenAddress datatokenAddress + * @return {String} + */ + async getvestingLastBlock( + ssAddress: string, + datatokenAddress: string + ): Promise { + const sideStaking = new this.web3.eth.Contract(this.ssABI, ssAddress) + let result = null + try { + result = await sideStaking.methods.getvestingLastBlock(datatokenAddress).call() + } catch (e) { + LoggerInstance.error(`ERROR: Failed to get: ${e.message}`) + } + return result + } + + /** + * Get how much has been taken from the vesting amount + * @param {String} ssAddress side staking contract address + * @param {String} datatokenAddress datatokenAddress + * @return {String} + */ + async getvestingAmountSoFar( + ssAddress: string, + datatokenAddress: string + ): Promise { + const sideStaking = new this.web3.eth.Contract(this.ssABI, ssAddress) + let result = null + try { + result = await sideStaking.methods.getvestingAmountSoFar(datatokenAddress).call() + } catch (e) { + LoggerInstance.error(`ERROR: Failed to get: ${e.message}`) + } + result = await this.unitsToAmount(datatokenAddress, result) + return result + } + + /** + * Estimate gas cost for getVesting + * @param {String} account + * @param {String} ssAddress side staking contract address + * @param {String} datatokenAddress datatokenAddress + * @param {Contract} contractInstance optional contract instance + * @return {Promise} + */ + public async estGetVesting( + account: string, + ssAddress: string, + datatokenAddress: string, + contractInstance?: Contract + ): Promise { + const sideStaking = + contractInstance || new this.web3.eth.Contract(this.ssABI as AbiItem[], ssAddress) + + const gasLimitDefault = this.GASLIMIT_DEFAULT + let estGas + try { + estGas = await sideStaking.methods + .getVesting(datatokenAddress) + .estimateGas({ from: account }, (err, estGas) => (err ? gasLimitDefault : estGas)) + } catch (e) { + estGas = gasLimitDefault + } + return estGas + } + + /** Send vested tokens available to the publisher address, can be called by anyone + * + * @param {String} account + * @param {String} ssAddress side staking contract address + * @param {String} datatokenAddress datatokenAddress + * @return {TransactionReceipt} + */ + async getVesting( + account: string, + ssAddress: string, + datatokenAddress: string + ): Promise { + const sideStaking = new this.web3.eth.Contract(this.ssABI, ssAddress) + let result = null + + const estGas = await this.estGetVesting( + account, + ssAddress, + datatokenAddress, + sideStaking + ) + try { + result = await sideStaking.methods.getVesting(datatokenAddress).send({ + from: account, + gas: estGas + 1, + gasPrice: await getFairGasPrice(this.web3) + }) + } catch (e) { + LoggerInstance.error('ERROR: Failed to join swap pool amount out') + } + return result + } + + /** + * Get Router address set in side staking contract + * @param {String} ssAddress side staking contract address + * @return {String} + */ + async getRouter(ssAddress: string): Promise { + const sideStaking = new this.web3.eth.Contract(this.ssABI, ssAddress) + let result = null + try { + result = await sideStaking.methods.router().call() + } catch (e) { + LoggerInstance.error(`ERROR: Failed to get Router address: ${e.message}`) + } + return result + } +} diff --git a/src/pools/ssContracts/index.ts b/src/pools/ssContracts/index.ts new file mode 100644 index 00000000..8ade0ddb --- /dev/null +++ b/src/pools/ssContracts/index.ts @@ -0,0 +1 @@ +export * from './SideStaking' diff --git a/test/unit/pools/balancer/Pool.test.ts b/test/unit/pools/balancer/Pool.test.ts index fb54f704..26446660 100644 --- a/test/unit/pools/balancer/Pool.test.ts +++ b/test/unit/pools/balancer/Pool.test.ts @@ -124,6 +124,8 @@ describe('Pool unit test', () => { await usdcContract.methods.decimals().call(), 'USDC DECIMALS IN THIS TEST' ) + + await pool.amountToUnits(contracts.usdcAddress, '20') }) describe('Test a pool with DAI (18 Decimals)', () => { @@ -241,7 +243,6 @@ describe('Pool unit test', () => { it('#isFinalized - should return true if pool is finalized', async () => { expect(await pool.isFinalized(poolAddress)).to.equal(true) - expect(await pool.isFinalized(contracts.oceanAddress)).to.equal(null) }) it('#getSwapFee - should return the swap fee', async () => { @@ -632,6 +633,77 @@ describe('Pool unit test', () => { expect(await erc20Contract.methods.balanceOf(user2).call()).to.equal('0') }) + it('#calcPoolOutGivenSingleIn - should get the amount of pool OUT for exact token IN', async () => { + // since rate is 1 and the pool is just created + // amount of pool out received for same amount of different token In is equal + const tokenInAmount = '10' // 10 USDC or 10 DTs + expect( + await pool.calcPoolOutGivenSingleIn(poolAddress, erc20Token, tokenInAmount) + ).to.equal( + await pool.calcPoolOutGivenSingleIn( + poolAddress, + contracts.usdcAddress, + tokenInAmount + ) + ) + // console.log(await pool.calcPoolOutGivenSingleIn(poolAddress, erc20Token, tokenInAmount)) + }) + + it('#calcSingleInGivenPoolOut - should get the amount of token IN for exact pool token OUT', async () => { + // since rate is 1 and the pool is just created + // amount of different token In for getting same pool amount out is equal + const poolAmountOut = '1' + expect( + parseInt( + await pool.calcSingleInGivenPoolOut(poolAddress, erc20Token, poolAmountOut) + ) + ).to.be.closeTo( + parseInt( + await pool.calcSingleInGivenPoolOut( + poolAddress, + contracts.usdcAddress, + poolAmountOut + ) + ), + 1e9 + ) + }) + + it('#calcSingleOutGivenPoolIn - should get the amount of token OUT for exact pool token IN', async () => { + // since rate is 1 and the pool is just created + // amount amount of different token Out for rediming the same pool In is equal + const poolAmountIn = '10' + expect( + await pool.calcSingleOutGivenPoolIn(poolAddress, erc20Token, poolAmountIn) + ).to.equal( + await pool.calcSingleOutGivenPoolIn( + poolAddress, + contracts.usdcAddress, + poolAmountIn + ) + ) + }) + + it('#calcPoolInGivenSingleOut - should get the amount of pool IN for exact token OUT', async () => { + // since rate is 1 and the pool is just created + // amount of pool In for getting the same amount of different token Out is equal + const tokenAmountOut = '10' + expect( + parseInt( + await pool.calcPoolInGivenSingleOut(poolAddress, erc20Token, tokenAmountOut) + ) + ).to.be.closeTo( + parseInt( + await pool.calcPoolInGivenSingleOut( + poolAddress, + contracts.usdcAddress, + tokenAmountOut + ) + ), + 1e11 + ) + }) + it('#sharesBalance - should return user shares balance (datatoken balance, LPT balance, etc) ', async () => { expect(await usdcContract.methods.balanceOf(user2).call()).to.equal( await pool.sharesBalance(user2, contracts.usdcAddress) @@ -685,7 +757,6 @@ describe('Pool unit test', () => { it('#isFinalized - should return true if pool is finalized', async () => { expect(await pool.isFinalized(poolAddress)).to.equal(true) - expect(await pool.isFinalized(contracts.oceanAddress)).to.equal(null) }) it('#getSwapFee - should return the swap fee', async () => { diff --git a/test/unit/pools/fixedRate/FixedRateExchange.test.ts b/test/unit/pools/fixedRate/FixedRateExchange.test.ts new file mode 100644 index 00000000..dd8ab5e3 --- /dev/null +++ b/test/unit/pools/fixedRate/FixedRateExchange.test.ts @@ -0,0 +1,761 @@ +import { assert, expect } from 'chai' +import { AbiItem } from 'web3-utils/types' +import { TestContractHandler } from '../../../TestContractHandler' +import { Contract } from 'web3-eth-contract' +import Web3 from 'web3' +import BigNumber from 'bignumber.js' +import BN from 'bn.js' +import ERC721Factory from '@oceanprotocol/contracts/artifacts/contracts/ERC721Factory.sol/ERC721Factory.json' +import ERC721Template from '@oceanprotocol/contracts/artifacts/contracts/templates/ERC721Template.sol/ERC721Template.json' +import SSContract from '@oceanprotocol/contracts/artifacts/contracts/pools/ssContracts/SideStaking.sol/SideStaking.json' +import FactoryRouter from '@oceanprotocol/contracts/artifacts/contracts/pools/FactoryRouter.sol/FactoryRouter.json' +import ERC20Template from '@oceanprotocol/contracts/artifacts/contracts/templates/ERC20Template.sol/ERC20Template.json' +import Dispenser from '@oceanprotocol/contracts/artifacts/contracts/pools/dispenser/Dispenser.sol/Dispenser.json' +import FixedRate from '@oceanprotocol/contracts/artifacts/contracts/pools/fixedRate/FixedRateExchange.sol/FixedRateExchange.json' +import MockERC20 from '@oceanprotocol/contracts/artifacts/contracts/utils/mock/MockERC20Decimals.sol/MockERC20Decimals.json' +import PoolTemplate from '@oceanprotocol/contracts/artifacts/contracts/pools/balancer/BPool.sol/BPool.json' +import OPFCollector from '@oceanprotocol/contracts/artifacts/contracts/communityFee/OPFCommunityFeeCollector.sol/OPFCommunityFeeCollector.json' +import { LoggerInstance } from '../../../../src/utils' +import { NFTFactory, NFTCreateData } from '../../../../src/factories/NFTFactory' +import { Pool } from '../../../../src/pools/balancer/Pool' +import { FixedRateExchange } from '../../../../src/pools/fixedRate/FixedRateExchange' +import { BADFAMILY } from 'dns' +import { FreCreationParams, Erc20CreateParams } from '../../../../src/interfaces' +const { keccak256 } = require('@ethersproject/keccak256') +const web3 = new Web3('http://127.0.0.1:8545') +const communityCollector = '0xeE9300b7961e0a01d9f0adb863C7A227A07AaD75' + +describe('Fixed Rate unit test', () => { + let factoryOwner: string + let nftOwner: string + let exchangeOwner: string + let user1: string + let user2: string + let user3: string + let user4: string + let initialBlock: number + let fixedRateAddress: string + let daiAddress: string + let usdcAddress: string + let exchangeId: string + let contracts: TestContractHandler + let fixedRate: FixedRateExchange + let dtAddress: string + let dtAddress2: string + let dtContract: Contract + let daiContract: Contract + let usdcContract: Contract + const vestedBlocks = 2500000 + const ADDRESS_ZERO = '0x0000000000000000000000000000000000000000' + it('should deploy contracts', async () => { + contracts = new TestContractHandler( + web3, + ERC721Template.abi as AbiItem[], + ERC20Template.abi as AbiItem[], + PoolTemplate.abi as AbiItem[], + ERC721Factory.abi as AbiItem[], + FactoryRouter.abi as AbiItem[], + SSContract.abi as AbiItem[], + FixedRate.abi as AbiItem[], + Dispenser.abi as AbiItem[], + OPFCollector.abi as AbiItem[], + + ERC721Template.bytecode, + ERC20Template.bytecode, + PoolTemplate.bytecode, + ERC721Factory.bytecode, + FactoryRouter.bytecode, + SSContract.bytecode, + FixedRate.bytecode, + Dispenser.bytecode, + OPFCollector.bytecode + ) + await contracts.getAccounts() + factoryOwner = contracts.accounts[0] + nftOwner = contracts.accounts[1] + user1 = contracts.accounts[2] + user2 = contracts.accounts[3] + user3 = contracts.accounts[4] + user4 = contracts.accounts[5] + exchangeOwner = contracts.accounts[0] + + await contracts.deployContracts(factoryOwner, FactoryRouter.abi as AbiItem[]) + + // initialize fixed rate + // + + daiContract = new web3.eth.Contract( + contracts.MockERC20.options.jsonInterface, + contracts.daiAddress + ) + + usdcContract = new web3.eth.Contract( + contracts.MockERC20.options.jsonInterface, + contracts.usdcAddress + ) + + console.log( + await usdcContract.methods.decimals().call(), + 'USDC DECIMALS IN THIS TEST' + ) + }) + + describe('Test a Fixed Rate Exchange with DAI (18 Decimals)', () => { + it('#create an exchange', async () => { + // CREATE AN Exchange + // we prepare transaction parameters objects + + const nftFactory = new NFTFactory(contracts.factory721Address, web3) + + const nftData: NFTCreateData = { + name: '72120Bundle', + symbol: '72Bundle', + templateIndex: 1, + baseURI: 'https://oceanprotocol.com/nft/' + } + + const ercParams: Erc20CreateParams = { + templateIndex: 1, + minter: contracts.accounts[0], + feeManager: user3, + mpFeeAddress: contracts.accounts[0], + feeToken: ADDRESS_ZERO, + cap: '1000000', + feeAmount: '0', + name: 'ERC20B1', + symbol: 'ERC20DT1Symbol' + } + + const freParams: FreCreationParams = { + fixedRateAddress: contracts.fixedRateAddress, + baseTokenAddress: contracts.daiAddress, + owner: exchangeOwner, + marketFeeCollector: user3, + baseTokenDecimals: 18, + dataTokenDecimals: 18, + fixedRate: web3.utils.toWei('1'), + marketFee: 1e15, + allowedConsumer: ADDRESS_ZERO, + withMint: false + } + + const txReceipt = await nftFactory.createNftErcWithFixedRate( + exchangeOwner, + nftData, + ercParams, + freParams + ) + + initialBlock = await web3.eth.getBlockNumber() + dtAddress = txReceipt.events.TokenCreated.returnValues.newTokenAddress + exchangeId = txReceipt.events.NewFixedRate.returnValues.exchangeId + + dtContract = new web3.eth.Contract(ERC20Template.abi as AbiItem[], dtAddress) + // user2 has no dt1 + expect(await dtContract.methods.balanceOf(user2).call()).to.equal('0') + + fixedRateAddress = contracts.fixedRateAddress + fixedRate = new FixedRateExchange( + web3, + fixedRateAddress, + FixedRate.abi as AbiItem[], + contracts.oceanAddress + ) + assert(fixedRate != null) + }) + + it('#isActive - should return true if exchange is active', async () => { + expect(await fixedRate.isActive(exchangeId)).to.equal(true) + expect(await fixedRate.isActive('0x00')).to.equal(false) + }) + it('#getOwner - should get exchange owner given an id', async () => { + expect(await fixedRate.getExchangeOwner(exchangeId)).to.equal(exchangeOwner) + }) + it('#getOPFCollector - should get OPF collector', async () => { + expect(await fixedRate.getOPFCollector()).to.equal(contracts.opfCollectorAddress) + }) + it('#getRouter - should get Router address', async () => { + expect(await fixedRate.getRouter()).to.equal(contracts.routerAddress) + }) + + it('#deactivate - should deactivate an exchange if exchangeOwner', async () => { + expect(await fixedRate.isActive(exchangeId)).to.equal(true) + await fixedRate.deactivate(exchangeOwner, exchangeId) + + expect(await fixedRate.isActive(exchangeId)).to.equal(false) + }) + + it('#activate - should activate an exchange if exchangeOwner', async () => { + expect(await fixedRate.isActive(exchangeId)).to.equal(false) + await fixedRate.activate(exchangeOwner, exchangeId) + expect(await fixedRate.isActive(exchangeId)).to.equal(true) + }) + + it('#activateMint - should activate Mint(allows fixed rate contract to mint dts if required), if exchangeOwner', async () => { + expect((await fixedRate.getExchange(exchangeId)).withMint).to.equal(false) + await fixedRate.activateMint(exchangeOwner, exchangeId) + expect((await fixedRate.getExchange(exchangeId)).withMint).to.equal(true) + }) + + it('#dectivateMint - should deactivate Mint if exchangeOwner', async () => { + expect((await fixedRate.getExchange(exchangeId)).withMint).to.equal(true) + await fixedRate.deactivateMint(exchangeOwner, exchangeId) + expect((await fixedRate.getExchange(exchangeId)).withMint).to.equal(false) + }) + + it('#generate exchangeId - should generate a specific exchangeId', async () => { + expect( + await fixedRate.generateExchangeId(contracts.daiAddress, dtAddress, exchangeOwner) + ).to.equal(exchangeId) + }) + + it('#getNumberOfExchanges - should return total number of exchanges', async () => { + expect(await fixedRate.getNumberOfExchanges()).to.equal('1') + }) + + it('#getExchanges - should return all exchanges ids', async () => { + const exchangeIds = await fixedRate.getExchanges() + expect(exchangeIds[0]).to.equal(exchangeId) + }) + + it('#getRate - should return rate', async () => { + expect(await fixedRate.getRate(exchangeId)).to.equal('1') + }) + + it('#setRate - set new rate if exchangeOwner', async () => { + await fixedRate.setRate(exchangeOwner, exchangeId, '2') + expect(await fixedRate.getRate(exchangeId)).to.equal('2') + await fixedRate.setRate(exchangeOwner, exchangeId, '1') + expect(await fixedRate.getRate(exchangeId)).to.equal('1') + }) + + it('#getDTSupply - should get the dt supply in the exchange', async () => { + // exchange owner hasn't approved any DT for sell + expect(await fixedRate.getDTSupply(exchangeId)).to.equal('0') + }) + it('#getBTSupply - should get the bt supply in the exchange', async () => { + // no basetoken at the beginning + expect(await fixedRate.getBTSupply(exchangeId)).to.equal('0') + }) + it('#getAmountBTIn - should get bt amount in for a specific dt amount', async () => { + // 100.2 DAI for 100 DT (0.1% market fee and 0.1% ocean fee) + expect(await fixedRate.getAmountBTIn(exchangeId, '100')).to.equal('100.2') + }) + it('#getAmountBTOut - should get bt amount out for a specific dt amount', async () => { + // 99.8 DAI for 100 DT (0.1% market fee and 0.1% ocean fee) + expect(await fixedRate.getAmountBTOut(exchangeId, '100')).to.equal('99.8') + }) + + it('#buyDT - user2 should buy some dt', async () => { + // total supply is ZERO right now so dt owner mints 1000 DT and approves the fixed rate contract + await dtContract.methods + .mint(exchangeOwner, web3.utils.toWei('1000')) + .send({ from: exchangeOwner }) + await dtContract.methods + .approve(fixedRateAddress, web3.utils.toWei('1000')) + .send({ from: exchangeOwner }) + // user2 gets 100 DAI so he can buy DTs + await daiContract.methods + .transfer(user2, web3.utils.toWei('100')) + .send({ from: exchangeOwner }) + await daiContract.methods + .approve(fixedRateAddress, web3.utils.toWei('100')) + .send({ from: user2 }) + + // user2 has no dts but has 100 DAI + expect(await dtContract.methods.balanceOf(user2).call()).to.equal('0') + const daiBalanceBefore = new BN(await daiContract.methods.balanceOf(user2).call()) + expect(daiBalanceBefore.toString()).to.equal(web3.utils.toWei('100')) + + // user2 buys 10 DT + const tx = await fixedRate.buyDT(user2, exchangeId, '10', '11') + // console.log(tx.events.Swapped.returnValues) + assert(tx.events.Swapped != null) + const args = tx.events.Swapped.returnValues + expect(args.exchangeId).to.equal(exchangeId) + expect(args.by).to.equal(user2) + expect(args.dataTokenSwappedAmount).to.equal(web3.utils.toWei('10')) + expect(args.tokenOutAddress).to.equal(dtAddress) + expect(await dtContract.methods.balanceOf(user2).call()).to.equal( + args.dataTokenSwappedAmount + ) + expect( + daiBalanceBefore.sub(new BN(args.baseTokenSwappedAmount)).toString() + ).to.equal(await daiContract.methods.balanceOf(user2).call()) + // basetoken stays in the contract + expect((await fixedRate.getExchange(exchangeId)).btBalance).to.equal('10') + // no dt in the contract + expect((await fixedRate.getExchange(exchangeId)).dtBalance).to.equal('0') + }) + + it('#sellDT - user2 should sell some dt', async () => { + await dtContract.methods + .approve(fixedRateAddress, web3.utils.toWei('10')) + .send({ from: user2 }) + const daiBalanceBefore = new BN(await daiContract.methods.balanceOf(user2).call()) + const tx = await fixedRate.sellDT(user2, exchangeId, '10', '9') + // console.log(tx.events.Swapped.returnValues) + assert(tx.events.Swapped != null) + const args = tx.events.Swapped.returnValues + expect(args.exchangeId).to.equal(exchangeId) + expect(args.by).to.equal(user2) + expect(args.dataTokenSwappedAmount).to.equal(web3.utils.toWei('10')) + expect(args.tokenOutAddress).to.equal(contracts.daiAddress) + expect(await dtContract.methods.balanceOf(user2).call()).to.equal('0') + expect( + daiBalanceBefore.add(new BN(args.baseTokenSwappedAmount)).toString() + ).to.equal(await daiContract.methods.balanceOf(user2).call()) + // DTs stay in the contract + expect((await fixedRate.getExchange(exchangeId)).dtBalance).to.equal('10') + // no BTs in the contract (except for the fees, but not accounted here) + expect((await fixedRate.getExchange(exchangeId)).btBalance).to.equal('0') + // DT supply is back at 1000 (exchange Owner allowance + dt balance in the fixed rate) + expect(await fixedRate.getDTSupply(exchangeId)).to.equal('1000') + }) + + it('#getExchange - should return exchange details', async () => { + const result = await fixedRate.getExchange(exchangeId) + expect(result.active).to.equal(true) + expect(result.btDecimals).to.equal('18') + expect(result.dtDecimals).to.equal('18') + expect(result.baseToken).to.equal(contracts.daiAddress) + expect(result.dataToken).to.equal(dtAddress) + expect(result.exchangeOwner).to.equal(exchangeOwner) + expect(result.withMint).to.equal(false) + expect(result.dtBalance).to.equal('10') // balance in the fixedRate + expect(result.btBalance).to.equal('0') // balance in the fixedRate + expect(result.dtSupply).to.equal('1000') // total supply available (owner allowance + dtBalance) + expect(result.btSupply).to.equal('0') // total supply available of basetoken in the contract + expect(result.fixedRate).to.equal('1') + }) + + it('#getFeesInfo - should return exchange fee details', async () => { + const result = await fixedRate.getFeesInfo(exchangeId) + expect(result.marketFee).to.equal('0.001') + // we made 2 swaps for 10 DT at rate 1, the fee is 0.1% for market and always in basetoken so it's 0.01 DAI + // we made 2 swaps for 10 DT at rate 1, the fee is 0.1% for ocean community and always in basetoken so it's 0.01 DAI + expect(result.marketFeeAvailable).to.equal('0.02') // formatted for basetoken decimals + expect(result.oceanFeeAvailable).to.equal('0.02') // formatted for basetoken decimals + expect(result.marketFeeCollector).to.equal(user3) + expect(result.opfFee).to.equal('0.001') + }) + + it('#getAllowedSwapper- should return address(0) if not set, if exchangeOwner', async () => { + expect(await fixedRate.getAllowedSwapper(exchangeId)).to.equal(ADDRESS_ZERO) + }) + it('#setAllowedSwapper- should set an allowed swapper, if exchangeOwner', async () => { + await fixedRate.setAllowedSwapper(exchangeOwner, exchangeId, user2) + expect(await fixedRate.getAllowedSwapper(exchangeId)).to.equal(user2) + }) + it('#setAllowedSwapper- should disable allowed swapper(return address(0)), if exchangeOwner', async () => { + await fixedRate.setAllowedSwapper(exchangeOwner, exchangeId, ADDRESS_ZERO) + expect(await fixedRate.getAllowedSwapper(exchangeId)).to.equal(ADDRESS_ZERO) + }) + it('#collectBT- should collect BT in the contract, if exchangeOwner', async () => { + // there are no bt in the contract + expect((await fixedRate.getExchange(exchangeId)).btBalance).to.equal('0') + // user2 buys 1 DT + await fixedRate.buyDT(user2, exchangeId, '1', '2') + // 1 DAI in the contract + expect((await fixedRate.getExchange(exchangeId)).btBalance).to.equal('1') + // owner collects BTs + await fixedRate.collectBT(exchangeOwner, exchangeId) + // btBalance is zero + expect((await fixedRate.getExchange(exchangeId)).btBalance).to.equal('0') + }) + it('#collectDT- should collect DT in the contract, if exchangeOwner', async () => { + const result = await fixedRate.getExchange(exchangeId) + // 9 dts left + expect(result.dtBalance).to.equal('9') + // owner collects DTs + await fixedRate.collectDT(exchangeOwner, exchangeId) + // no more dts in the contract + const result2 = await fixedRate.getExchange(exchangeId) + expect(result2.dtBalance).to.equal('0') + // Only allowance left since dt is ZERO + expect(result2.dtSupply).to.equal('990') + }) + it('#collectMarketFee- should collect marketFee and send it to marketFeeCollector, anyone can call it', async () => { + let result = await fixedRate.getFeesInfo(exchangeId) + // we made 2 swaps for 10 DT at rate 1, the fee is 0.1% for market and always in basetoken so it's 0.01 DAI + // plus another swap for 1 DT + expect(result.marketFeeAvailable).to.equal('0.021') // formatted for basetoken decimals + // same for ocean fee + expect(result.oceanFeeAvailable).to.equal('0.021') // formatted for basetoken decimals + expect(result.marketFeeCollector).to.equal(user3) + + // user4 calls collectMarketFee + await fixedRate.collectMarketFee(user4, exchangeId) + result = await fixedRate.getFeesInfo(exchangeId) + expect(result.marketFeeAvailable).to.equal('0') + // ocean fee still available + expect(result.oceanFeeAvailable).to.equal('0.021') + // user3 is the marketFeeCollector + expect(await daiContract.methods.balanceOf(user3).call()).to.equal( + web3.utils.toWei('0.021') + ) + }) + it('#collectOceanFee- should collect oceanFee and send it to OPF Collector, anyone can call it', async () => { + let result = await fixedRate.getFeesInfo(exchangeId) + // we made 2 swaps for 10 DT at rate 1, the fee is 0.1% for market and always in basetoken so it's 0.01 DAI + // plus another swap for 1 DT + expect(result.oceanFeeAvailable).to.equal('0.021') // formatted for basetoken decimals + + // user4 calls collectOceanFee + await fixedRate.collectOceanFee(user4, exchangeId) + result = await fixedRate.getFeesInfo(exchangeId) + // fee has been reset + expect(result.oceanFeeAvailable).to.equal('0') + // OPF collector got the fee + expect( + await daiContract.methods.balanceOf(await fixedRate.getOPFCollector()).call() + ).to.equal(web3.utils.toWei('0.021')) + }) + + it('#updateMarketFee- should update Market fee if market fee collector', async () => { + expect((await fixedRate.getFeesInfo(exchangeId)).marketFee).to.equal('0.001') + // user3 is marketFeeCollector + await fixedRate.updateMarketFee(user3, exchangeId, '0.01') + + expect((await fixedRate.getFeesInfo(exchangeId)).marketFee).to.equal('0.01') + }) + + it('#updateMarketFeeCollector - should update Market fee collector if market fee collector', async () => { + expect((await fixedRate.getFeesInfo(exchangeId)).marketFeeCollector).to.equal(user3) + + await fixedRate.updateMarketFeeCollector(user3, exchangeId, user2) + + expect((await fixedRate.getFeesInfo(exchangeId)).marketFeeCollector).to.equal(user2) + }) + }) + describe('Test a Fixed Rate Exchange with USDC (6 Decimals)', () => { + it('#create an exchange', async () => { + // CREATE AN Exchange + // we prepare transaction parameters objects + + const nftFactory = new NFTFactory(contracts.factory721Address, web3) + + const nftData: NFTCreateData = { + name: '72120Bundle', + symbol: '72Bundle', + templateIndex: 1, + baseURI: 'https://oceanprotocol.com/nft/' + } + + const ercParams: Erc20CreateParams = { + templateIndex: 1, + minter: contracts.accounts[0], + feeManager: user3, + mpFeeAddress: contracts.accounts[0], + feeToken: ADDRESS_ZERO, + cap: '1000000', + feeAmount: '0', + name: 'ERC20B1', + symbol: 'ERC20DT1Symbol' + } + + const freParams: FreCreationParams = { + fixedRateAddress: contracts.fixedRateAddress, + baseTokenAddress: contracts.usdcAddress, + owner: exchangeOwner, + marketFeeCollector: user3, + baseTokenDecimals: 6, + dataTokenDecimals: 18, + fixedRate: web3.utils.toWei('1'), + marketFee: 1e15, + allowedConsumer: ADDRESS_ZERO, + withMint: false + } + + const txReceipt = await nftFactory.createNftErcWithFixedRate( + exchangeOwner, + nftData, + ercParams, + freParams + ) + + initialBlock = await web3.eth.getBlockNumber() + dtAddress = txReceipt.events.TokenCreated.returnValues.newTokenAddress + exchangeId = txReceipt.events.NewFixedRate.returnValues.exchangeId + + dtContract = new web3.eth.Contract(ERC20Template.abi as AbiItem[], dtAddress) + // user2 has no dt1 + expect(await dtContract.methods.balanceOf(user2).call()).to.equal('0') + + fixedRateAddress = contracts.fixedRateAddress + fixedRate = new FixedRateExchange( + web3, + fixedRateAddress, + FixedRate.abi as AbiItem[], + contracts.oceanAddress + ) + assert(fixedRate != null) + }) + + it('#isActive - should return true if exchange is active', async () => { + expect(await fixedRate.isActive(exchangeId)).to.equal(true) + expect(await fixedRate.isActive('0x00')).to.equal(false) + }) + it('#getOwner - should get exchange owner given an id', async () => { + expect(await fixedRate.getExchangeOwner(exchangeId)).to.equal(exchangeOwner) + }) + it('#getOPFCollector - should get OPF collector', async () => { + expect(await fixedRate.getOPFCollector()).to.equal(contracts.opfCollectorAddress) + }) + it('#getRouter - should get Router address', async () => { + expect(await fixedRate.getRouter()).to.equal(contracts.routerAddress) + }) + + it('#deactivate - should deactivate an exchange if exchangeOwner', async () => { + expect(await fixedRate.isActive(exchangeId)).to.equal(true) + await fixedRate.deactivate(exchangeOwner, exchangeId) + + expect(await fixedRate.isActive(exchangeId)).to.equal(false) + }) + + it('#activate - should activate an exchange if exchangeOwner', async () => { + expect(await fixedRate.isActive(exchangeId)).to.equal(false) + await fixedRate.activate(exchangeOwner, exchangeId) + expect(await fixedRate.isActive(exchangeId)).to.equal(true) + }) + + it('#activateMint - should activate Mint(allows fixed rate contract to mint dts if required), if exchangeOwner', async () => { + expect((await fixedRate.getExchange(exchangeId)).withMint).to.equal(false) + await fixedRate.activateMint(exchangeOwner, exchangeId) + expect((await fixedRate.getExchange(exchangeId)).withMint).to.equal(true) + }) + + it('#dectivateMint - should deactivate Mint if exchangeOwner', async () => { + expect((await fixedRate.getExchange(exchangeId)).withMint).to.equal(true) + await fixedRate.deactivateMint(exchangeOwner, exchangeId) + expect((await fixedRate.getExchange(exchangeId)).withMint).to.equal(false) + }) + + it('#generate exchangeId - should generate a specific exchangeId', async () => { + expect( + await fixedRate.generateExchangeId( + contracts.usdcAddress, + dtAddress, + exchangeOwner + ) + ).to.equal(exchangeId) + }) + + it('#getNumberOfExchanges - should return total number of exchanges', async () => { + expect(await fixedRate.getNumberOfExchanges()).to.equal('2') + }) + + it('#getExchanges - should return all exchanges ids', async () => { + const exchangeIds = await fixedRate.getExchanges() + expect(exchangeIds[1]).to.equal(exchangeId) + }) + + it('#getRate - should return rate', async () => { + expect(await fixedRate.getRate(exchangeId)).to.equal('1') + }) + + it('#setRate - set new rate if exchangeOwner', async () => { + await fixedRate.setRate(exchangeOwner, exchangeId, '2') + expect(await fixedRate.getRate(exchangeId)).to.equal('2') + await fixedRate.setRate(exchangeOwner, exchangeId, '1') + expect(await fixedRate.getRate(exchangeId)).to.equal('1') + }) + + it('#getDTSupply - should get the dt supply in the exchange', async () => { + // exchange owner hasn't approved any DT for sell + expect(await fixedRate.getDTSupply(exchangeId)).to.equal('0') + }) + it('#getBTSupply - should get the bt supply in the exchange', async () => { + // no basetoken at the beginning + expect(await fixedRate.getBTSupply(exchangeId)).to.equal('0') + }) + it('#getAmountBTIn - should get bt amount in for a specific dt amount', async () => { + // 100.2 USDC for 100 DT (0.1% market fee and 0.1% ocean fee) + expect(await fixedRate.getAmountBTIn(exchangeId, '100')).to.equal('100.2') + }) + it('#getAmountBTOut - should get bt amount out for a specific dt amount', async () => { + // 99.8 USDC for 100 DT (0.1% market fee and 0.1% ocean fee) + expect(await fixedRate.getAmountBTOut(exchangeId, '100')).to.equal('99.8') + }) + + it('#buyDT - user2 should buy some dt', async () => { + // total supply is ZERO right now so dt owner mints 1000 DT and approves the fixed rate contract + await dtContract.methods + .mint(exchangeOwner, web3.utils.toWei('1000')) + .send({ from: exchangeOwner }) + await dtContract.methods + .approve(fixedRateAddress, web3.utils.toWei('1000')) + .send({ from: exchangeOwner }) + // user2 gets 100 USDC so he can buy DTs + await usdcContract.methods.transfer(user2, 100 * 1e6).send({ from: exchangeOwner }) + await usdcContract.methods + .approve(fixedRateAddress, 100 * 1e6) + .send({ from: user2 }) + + // user2 has no dts but has 100 USDC + expect(await dtContract.methods.balanceOf(user2).call()).to.equal('0') + const usdcBalanceBefore = new BN(await usdcContract.methods.balanceOf(user2).call()) + expect(usdcBalanceBefore.toString()).to.equal(new BN(100 * 1e6).toString()) + + // user2 buys 10 DT + const tx = await fixedRate.buyDT(user2, exchangeId, '10', '11') + // console.log(tx.events.Swapped.returnValues) + assert(tx.events.Swapped != null) + const args = tx.events.Swapped.returnValues + expect(args.exchangeId).to.equal(exchangeId) + expect(args.by).to.equal(user2) + expect(args.dataTokenSwappedAmount).to.equal(web3.utils.toWei('10')) + expect(args.tokenOutAddress).to.equal(dtAddress) + expect(await dtContract.methods.balanceOf(user2).call()).to.equal( + args.dataTokenSwappedAmount + ) + expect( + usdcBalanceBefore.sub(new BN(args.baseTokenSwappedAmount)).toString() + ).to.equal(await usdcContract.methods.balanceOf(user2).call()) + // basetoken stays in the contract + expect((await fixedRate.getExchange(exchangeId)).btBalance).to.equal('10') + // no dt in the contract + expect((await fixedRate.getExchange(exchangeId)).dtBalance).to.equal('0') + }) + + it('#sellDT - user2 should sell some dt', async () => { + await dtContract.methods + .approve(fixedRateAddress, web3.utils.toWei('10')) + .send({ from: user2 }) + const usdcBalanceBefore = new BN(await usdcContract.methods.balanceOf(user2).call()) + const tx = await fixedRate.sellDT(user2, exchangeId, '10', '9') + // console.log(tx.events.Swapped.returnValues) + assert(tx.events.Swapped != null) + const args = tx.events.Swapped.returnValues + expect(args.exchangeId).to.equal(exchangeId) + expect(args.by).to.equal(user2) + expect(args.dataTokenSwappedAmount).to.equal(web3.utils.toWei('10')) + expect(args.tokenOutAddress).to.equal(contracts.usdcAddress) + expect(await dtContract.methods.balanceOf(user2).call()).to.equal('0') + expect( + usdcBalanceBefore.add(new BN(args.baseTokenSwappedAmount)).toString() + ).to.equal(await usdcContract.methods.balanceOf(user2).call()) + // DTs stay in the contract + expect((await fixedRate.getExchange(exchangeId)).dtBalance).to.equal('10') + // no BTs in the contract (except for the fees, but not accounted here) + expect((await fixedRate.getExchange(exchangeId)).btBalance).to.equal('0') + // DT supply is back at 1000 (exchange Owner allowance + dt balance in the fixed rate) + expect(await fixedRate.getDTSupply(exchangeId)).to.equal('1000') + }) + + it('#getExchange - should return exchange details', async () => { + const result = await fixedRate.getExchange(exchangeId) + expect(result.active).to.equal(true) + expect(result.btDecimals).to.equal('6') + expect(result.dtDecimals).to.equal('18') + expect(result.baseToken).to.equal(contracts.usdcAddress) + expect(result.dataToken).to.equal(dtAddress) + expect(result.exchangeOwner).to.equal(exchangeOwner) + expect(result.withMint).to.equal(false) + expect(result.dtBalance).to.equal('10') // balance in the fixedRate + expect(result.btBalance).to.equal('0') // balance in the fixedRate + expect(result.dtSupply).to.equal('1000') // total supply available (owner allowance + dtBalance) + expect(result.btSupply).to.equal('0') // total supply available of basetoken in the contract + expect(result.fixedRate).to.equal('1') + }) + + it('#getFeesInfo - should return exchange fee details', async () => { + const result = await fixedRate.getFeesInfo(exchangeId) + expect(result.marketFee).to.equal('0.001') + // we made 2 swaps for 10 DT at rate 1, the fee is 0.1% for market and always in basetoken so it's 0.01 USDC + // we made 2 swaps for 10 DT at rate 1, the fee is 0.1% for ocean community and always in basetoken so it's 0.01 USDC + expect(result.marketFeeAvailable).to.equal('0.02') // formatted for basetoken decimals + expect(result.oceanFeeAvailable).to.equal('0.02') // formatted for basetoken decimals + expect(result.marketFeeCollector).to.equal(user3) + expect(result.opfFee).to.equal('0.001') + }) + + it('#getAllowedSwapper- should return address(0) if not set, if exchangeOwner', async () => { + expect(await fixedRate.getAllowedSwapper(exchangeId)).to.equal(ADDRESS_ZERO) + }) + it('#setAllowedSwapper- should set an allowed swapper, if exchangeOwner', async () => { + await fixedRate.setAllowedSwapper(exchangeOwner, exchangeId, user2) + expect(await fixedRate.getAllowedSwapper(exchangeId)).to.equal(user2) + }) + it('#setAllowedSwapper- should disable allowed swapper(return address(0)), if exchangeOwner', async () => { + await fixedRate.setAllowedSwapper(exchangeOwner, exchangeId, ADDRESS_ZERO) + expect(await fixedRate.getAllowedSwapper(exchangeId)).to.equal(ADDRESS_ZERO) + }) + it('#collectBT- should collect BT in the contract, if exchangeOwner', async () => { + // there are no bt in the contract + expect((await fixedRate.getExchange(exchangeId)).btBalance).to.equal('0') + // user2 buys 1 DT + await fixedRate.buyDT(user2, exchangeId, '1', '2') + // 1 DAI in the contract + expect((await fixedRate.getExchange(exchangeId)).btBalance).to.equal('1') + // owner collects BTs + await fixedRate.collectBT(exchangeOwner, exchangeId) + // btBalance is zero + expect((await fixedRate.getExchange(exchangeId)).btBalance).to.equal('0') + }) + it('#collectDT- should collect DT in the contract, if exchangeOwner', async () => { + const result = await fixedRate.getExchange(exchangeId) + // 9 dts left + expect(result.dtBalance).to.equal('9') + // owner collects DTs + await fixedRate.collectDT(exchangeOwner, exchangeId) + // no more dts in the contract + const result2 = await fixedRate.getExchange(exchangeId) + expect(result2.dtBalance).to.equal('0') + // Only allowance left since dt is ZERO + expect(result2.dtSupply).to.equal('990') + }) + it('#collectMarketFee- should collect marketFee and send it to marketFeeCollector, anyone can call it', async () => { + let result = await fixedRate.getFeesInfo(exchangeId) + // we made 2 swaps for 10 DT at rate 1, the fee is 0.1% for market and always in basetoken so it's 0.01 USDC + // plus another swap for 1 DT + expect(result.marketFeeAvailable).to.equal('0.021') // formatted for basetoken decimals + // same for ocean fee + expect(result.oceanFeeAvailable).to.equal('0.021') // formatted for basetoken decimals + expect(result.marketFeeCollector).to.equal(user3) + + // user4 calls collectMarketFee + await fixedRate.collectMarketFee(user4, exchangeId) + result = await fixedRate.getFeesInfo(exchangeId) + expect(result.marketFeeAvailable).to.equal('0') + // ocean fee still available + expect(result.oceanFeeAvailable).to.equal('0.021') + // user3 is the marketFeeCollector + expect(await usdcContract.methods.balanceOf(user3).call()).to.equal( + (0.021 * 1e6).toString() + ) + }) + it('#collectOceanFee- should collect oceanFee and send it to OPF Collector, anyone can call it', async () => { + let result = await fixedRate.getFeesInfo(exchangeId) + // we made 2 swaps for 10 DT at rate 1, the fee is 0.1% for market and always in basetoken so it's 0.01 DAI + // plus another swap for 1 DT + expect(result.oceanFeeAvailable).to.equal('0.021') // formatted for basetoken decimals + + // user4 calls collectOceanFee + await fixedRate.collectOceanFee(user4, exchangeId) + result = await fixedRate.getFeesInfo(exchangeId) + // fee has been reset + expect(result.oceanFeeAvailable).to.equal('0') + // OPF collector got the fee + expect( + await usdcContract.methods.balanceOf(await fixedRate.getOPFCollector()).call() + ).to.equal((0.021 * 1e6).toString()) + }) + + it('#updateMarketFee- should update Market fee if market fee collector', async () => { + expect((await fixedRate.getFeesInfo(exchangeId)).marketFee).to.equal('0.001') + // user3 is marketFeeCollector + await fixedRate.updateMarketFee(user3, exchangeId, '0.01') + + expect((await fixedRate.getFeesInfo(exchangeId)).marketFee).to.equal('0.01') + }) + + it('#updateMarketFeeCollector - should update Market fee collector if market fee collector', async () => { + expect((await fixedRate.getFeesInfo(exchangeId)).marketFeeCollector).to.equal(user3) + + await fixedRate.updateMarketFeeCollector(user3, exchangeId, user2) + + expect((await fixedRate.getFeesInfo(exchangeId)).marketFeeCollector).to.equal(user2) + }) + }) +}) diff --git a/test/unit/pools/ssContracts/SideStaking.test.ts b/test/unit/pools/ssContracts/SideStaking.test.ts new file mode 100644 index 00000000..46c6803f --- /dev/null +++ b/test/unit/pools/ssContracts/SideStaking.test.ts @@ -0,0 +1,656 @@ +import { assert, expect } from 'chai' +import { AbiItem } from 'web3-utils/types' +import { TestContractHandler } from '../../../TestContractHandler' +import { Contract } from 'web3-eth-contract' +import Web3 from 'web3' +import BigNumber from 'bignumber.js' +import BN from 'bn.js' +import ERC721Factory from '@oceanprotocol/contracts/artifacts/contracts/ERC721Factory.sol/ERC721Factory.json' +import ERC721Template from '@oceanprotocol/contracts/artifacts/contracts/templates/ERC721Template.sol/ERC721Template.json' +import SSContract from '@oceanprotocol/contracts/artifacts/contracts/pools/ssContracts/SideStaking.sol/SideStaking.json' +import FactoryRouter from '@oceanprotocol/contracts/artifacts/contracts/pools/FactoryRouter.sol/FactoryRouter.json' +import ERC20Template from '@oceanprotocol/contracts/artifacts/contracts/templates/ERC20Template.sol/ERC20Template.json' +import Dispenser from '@oceanprotocol/contracts/artifacts/contracts/pools/dispenser/Dispenser.sol/Dispenser.json' +import FixedRate from '@oceanprotocol/contracts/artifacts/contracts/pools/fixedRate/FixedRateExchange.sol/FixedRateExchange.json' +import MockERC20 from '@oceanprotocol/contracts/artifacts/contracts/utils/mock/MockERC20Decimals.sol/MockERC20Decimals.json' +import PoolTemplate from '@oceanprotocol/contracts/artifacts/contracts/pools/balancer/BPool.sol/BPool.json' +import OPFCollector from '@oceanprotocol/contracts/artifacts/contracts/communityFee/OPFCommunityFeeCollector.sol/OPFCommunityFeeCollector.json' +import { LoggerInstance } from '../../../../src/utils' +import { NFTFactory, NFTCreateData } from '../../../../src/factories/NFTFactory' +import { Pool } from '../../../../src/pools/balancer/Pool' +import { SideStaking } from '../../../../src/pools/ssContracts/SideStaking' +import { Erc20CreateParams, PoolCreationParams } from '../../../../src/interfaces' +const { keccak256 } = require('@ethersproject/keccak256') +const web3 = new Web3('http://127.0.0.1:8545') +const communityCollector = '0xeE9300b7961e0a01d9f0adb863C7A227A07AaD75' + +describe('SideStaking unit test', () => { + let factoryOwner: string + let nftOwner: string + let user1: string + let user2: string + let user3: string + let initialBlock: number + let sideStakingAddress: string + let contracts: TestContractHandler + let pool: Pool + let sideStaking: SideStaking + let dtAddress: string + let dtAddress2: string + let poolAddress: string + let erc20Token: string + let erc20Contract: Contract + let daiContract: Contract + let usdcContract: Contract + const vestedBlocks = 2500000 + + it('should deploy contracts', async () => { + contracts = new TestContractHandler( + web3, + ERC721Template.abi as AbiItem[], + ERC20Template.abi as AbiItem[], + PoolTemplate.abi as AbiItem[], + ERC721Factory.abi as AbiItem[], + FactoryRouter.abi as AbiItem[], + SSContract.abi as AbiItem[], + FixedRate.abi as AbiItem[], + Dispenser.abi as AbiItem[], + OPFCollector.abi as AbiItem[], + + ERC721Template.bytecode, + ERC20Template.bytecode, + PoolTemplate.bytecode, + ERC721Factory.bytecode, + FactoryRouter.bytecode, + SSContract.bytecode, + FixedRate.bytecode, + Dispenser.bytecode, + OPFCollector.bytecode + ) + await contracts.getAccounts() + factoryOwner = contracts.accounts[0] + nftOwner = contracts.accounts[1] + user1 = contracts.accounts[2] + user2 = contracts.accounts[3] + user3 = contracts.accounts[4] + sideStakingAddress = contracts.sideStakingAddress + await contracts.deployContracts(factoryOwner, FactoryRouter.abi as AbiItem[]) + + // initialize Pool instance + pool = new Pool(web3, LoggerInstance, PoolTemplate.abi as AbiItem[]) + assert(pool != null) + // + sideStaking = new SideStaking(web3, SSContract.abi as AbiItem[]) + assert(sideStaking != null) + + daiContract = new web3.eth.Contract( + contracts.MockERC20.options.jsonInterface, + contracts.daiAddress + ) + + usdcContract = new web3.eth.Contract( + contracts.MockERC20.options.jsonInterface, + contracts.usdcAddress + ) + await pool.approve( + contracts.accounts[0], + contracts.daiAddress, + contracts.factory721Address, + '2000' + ) + await pool.approve( + contracts.accounts[0], + contracts.usdcAddress, + contracts.factory721Address, + '10000' + ) + + expect( + await pool.allowance( + contracts.daiAddress, + contracts.accounts[0], + contracts.factory721Address + ) + ).to.equal('2000') + expect( + await pool.allowance( + contracts.usdcAddress, + contracts.accounts[0], + contracts.factory721Address + ) + ).to.equal('10000') + expect(await daiContract.methods.balanceOf(contracts.accounts[0]).call()).to.equal( + web3.utils.toWei('100000') + ) + + console.log( + await usdcContract.methods.decimals().call(), + 'USDC DECIMALS IN THIS TEST' + ) + + await pool.amountToUnits(contracts.usdcAddress, '20') + }) + + describe('Test a pool with DAI (18 Decimals)', () => { + it('#create a pool', async () => { + // CREATE A POOL + // we prepare transaction parameters objects + const nftFactory = new NFTFactory(contracts.factory721Address, web3) + + const nftData: NFTCreateData = { + name: '72120Bundle', + symbol: '72Bundle', + templateIndex: 1, + baseURI: 'https://oceanprotocol.com/nft/' + } + + const ercParams: Erc20CreateParams = { + templateIndex: 1, + minter: contracts.accounts[0], + feeManager: user3, + mpFeeAddress: contracts.accounts[0], + feeToken: '0x0000000000000000000000000000000000000000', + cap: '1000000', + feeAmount: '0', + name: 'ERC20B1', + symbol: 'ERC20DT1Symbol' + } + + const poolParams: PoolCreationParams = { + ssContract: contracts.sideStakingAddress, + basetokenAddress: contracts.daiAddress, + basetokenSender: contracts.factory721Address, + publisherAddress: contracts.accounts[0], + marketFeeCollector: contracts.accounts[0], + poolTemplateAddress: contracts.poolTemplateAddress, + rate: '1', + basetokenDecimals: 18, + vestingAmount: '10000', + vestedBlocks: vestedBlocks, + initialBasetokenLiquidity: '2000', + swapFeeLiquidityProvider: 1e15, + swapFeeMarketPlaceRunner: 1e15 + } + + const txReceipt = await nftFactory.createNftErcWithPool( + contracts.accounts[0], + nftData, + ercParams, + poolParams + ) + + initialBlock = await web3.eth.getBlockNumber() + erc20Token = txReceipt.events.TokenCreated.returnValues.newTokenAddress + poolAddress = txReceipt.events.NewPool.returnValues.poolAddress + + erc20Contract = new web3.eth.Contract(ERC20Template.abi as AbiItem[], erc20Token) + // user2 has no dt1 + expect(await erc20Contract.methods.balanceOf(user2).call()).to.equal('0') + + sideStakingAddress = contracts.sideStakingAddress + }) + it('#getRouter - should get Router address', async () => { + expect(await sideStaking.getRouter(sideStakingAddress)).to.equal( + contracts.routerAddress + ) + }) + + it('#getDataTokenCirculatingSupply - should get datatoken supply in circulation (vesting amount excluded)', async () => { + expect( + await sideStaking.getDataTokenCirculatingSupply( + contracts.sideStakingAddress, + erc20Token + ) + ).to.equal(web3.utils.toWei('12000')) + }) + it('#getDataTokenCurrentCirculatingSupply - should get datatoken supply in circulation ', async () => { + expect( + await sideStaking.getDataTokenCurrentCirculatingSupply( + contracts.sideStakingAddress, + erc20Token + ) + ).to.equal(web3.utils.toWei('2000')) + }) + it('#getBasetoken - should get basetoken address', async () => { + expect(await sideStaking.getBasetoken(sideStakingAddress, erc20Token)).to.equal( + contracts.daiAddress + ) + }) + it('#getPoolAddress - should get pool address', async () => { + expect(await sideStaking.getPoolAddress(sideStakingAddress, erc20Token)).to.equal( + poolAddress + ) + }) + it('#getPublisherAddress - should get publisher address', async () => { + expect( + await sideStaking.getPublisherAddress(sideStakingAddress, erc20Token) + ).to.equal(contracts.accounts[0]) + }) + it('#getBasetokenBalance ', async () => { + expect( + await sideStaking.getBasetokenBalance(sideStakingAddress, erc20Token) + ).to.equal('0') + }) + it('#getDatatokenBalance ', async () => { + expect( + await sideStaking.getDatatokenBalance(sideStakingAddress, erc20Token) + ).to.equal('988000') + }) + + it('#getvestingAmount ', async () => { + expect(await sideStaking.getvestingAmount(sideStakingAddress, erc20Token)).to.equal( + '10000' + ) + }) + it('#getvestingLastBlock ', async () => { + expect( + await sideStaking.getvestingLastBlock(sideStakingAddress, erc20Token) + ).to.equal(initialBlock.toString()) + }) + + it('#getvestingEndBlock ', async () => { + expect( + await sideStaking.getvestingEndBlock(sideStakingAddress, erc20Token) + ).to.equal((initialBlock + vestedBlocks).toString()) + }) + it('#getvestingAmountSoFar ', async () => { + expect( + await sideStaking.getvestingAmountSoFar(sideStakingAddress, erc20Token) + ).to.equal('0') + }) + + it('#getVesting ', async () => { + expect( + await erc20Contract.methods.balanceOf(contracts.accounts[0]).call() + ).to.equal('0') + + const tx = await sideStaking.getVesting( + contracts.accounts[0], + sideStakingAddress, + erc20Token + ) + + expect( + await sideStaking.unitsToAmount( + erc20Token, + await erc20Contract.methods.balanceOf(contracts.accounts[0]).call() + ) + ).to.equal(await sideStaking.getvestingAmountSoFar(sideStakingAddress, erc20Token)) + + expect( + await sideStaking.getvestingLastBlock(sideStakingAddress, erc20Token) + ).to.equal((await web3.eth.getBlockNumber()).toString()) + }) + + it('#swapExactAmountIn - should swap', async () => { + await daiContract.methods + .transfer(user2, web3.utils.toWei('1000')) + .send({ from: contracts.accounts[0] }) + expect(await daiContract.methods.balanceOf(user2).call()).to.equal( + web3.utils.toWei('1000') + ) + expect(await erc20Contract.methods.balanceOf(user2).call()).to.equal('0') + await pool.approve(user2, contracts.daiAddress, poolAddress, '10') + const tx = await pool.swapExactAmountIn( + user2, + poolAddress, + contracts.daiAddress, + '10', + erc20Token, + '1' + ) + expect(await erc20Contract.methods.balanceOf(user2).call()).to.equal( + tx.events.LOG_SWAP.returnValues.tokenAmountOut + ) + }) + + it('#swapExactAmountOut - should swap', async () => { + await pool.approve(user2, contracts.daiAddress, poolAddress, '100') + expect(await daiContract.methods.balanceOf(user2).call()).to.equal( + web3.utils.toWei('990') + ) + const tx = await pool.swapExactAmountOut( + user2, + poolAddress, + contracts.daiAddress, + '100', + erc20Token, + '50' + ) + assert(tx != null) + }) + + it('#joinswapExternAmountIn- user2 should add liquidity, receiving LP tokens', async () => { + const daiAmountIn = '100' + const minBPTOut = '0.1' + await pool.approve(user2, contracts.daiAddress, poolAddress, '100', true) + expect(await pool.allowance(contracts.daiAddress, user2, poolAddress)).to.equal( + '100' + ) + const tx = await pool.joinswapExternAmountIn( + user2, + poolAddress, + contracts.daiAddress, + daiAmountIn, + minBPTOut + ) + + assert(tx != null) + + expect(tx.events.LOG_JOIN[0].event === 'LOG_JOIN') + expect(tx.events.LOG_BPT.event === 'LOG_BPT') + // 2 JOIN EVENTS BECAUSE SIDE STAKING ALSO STAKED DTs, TODO: we should add to whom has been sent in the LOG_BPT event + expect(tx.events.LOG_JOIN[0].returnValues.bptAmount).to.equal( + tx.events.LOG_JOIN[1].returnValues.bptAmount + ) + }) + + it('#joinswapPoolAmountOut- user2 should add liquidity, receiving LP tokens', async () => { + const BPTAmountOut = '0.1' + const maxDAIIn = '100' + + await pool.approve(user2, contracts.daiAddress, poolAddress, '100') + + const tx = await pool.joinswapPoolAmountOut( + user2, + poolAddress, + contracts.daiAddress, + BPTAmountOut, + maxDAIIn + ) + + assert(tx != null) + + expect(tx.events.LOG_JOIN[0].event === 'LOG_JOIN') + expect(tx.events.LOG_BPT.event === 'LOG_BPT') + // 2 JOIN EVENTS BECAUSE SIDE STAKING ALSO STAKED DTs, TODO: we should add to whom has been sent in the LOG_BPT event + expect(tx.events.LOG_JOIN[0].returnValues.bptAmount).to.equal( + tx.events.LOG_JOIN[1].returnValues.bptAmount + ) + }) + + it('#exitswapPoolAmountIn- user2 exit the pool receiving only DAI', async () => { + const BPTAmountIn = '0.5' + const minDAIOut = '0.5' + + const tx = await pool.exitswapPoolAmountIn( + user2, + poolAddress, + contracts.daiAddress, + BPTAmountIn, + minDAIOut + ) + + assert(tx != null) + + expect(tx.events.LOG_EXIT[0].returnValues.tokenOut).to.equal(contracts.daiAddress) + + // DTs were also unstaked in the same transaction (went to the staking contract) + expect(tx.events.LOG_EXIT[1].returnValues.tokenOut).to.equal(erc20Token) + }) + + it('#exitswapExternAmountOut- user2 exit the pool receiving only DAI', async () => { + const maxBTPIn = '0.5' + const exactDAIOut = '1' + + const tx = await pool.exitswapPoolAmountIn( + user2, + poolAddress, + contracts.daiAddress, + maxBTPIn, + exactDAIOut + ) + + assert(tx != null) + + expect(tx.events.LOG_EXIT[0].returnValues.tokenOut).to.equal(contracts.daiAddress) + + // DTs were also unstaked in the same transaction (went to the staking contract) + expect(tx.events.LOG_EXIT[1].returnValues.tokenOut).to.equal(erc20Token) + }) + }) + + describe('Test a pool with USDC (6 Decimals)', () => { + it('#create a pool', async () => { + // CREATE A POOL + // we prepare transaction parameters objects + const nftFactory = new NFTFactory(contracts.factory721Address, web3) + + const nftData: NFTCreateData = { + name: '72120Bundle', + symbol: '72Bundle', + templateIndex: 1, + baseURI: 'https://oceanprotocol.com/nft/' + } + + const ercParams: Erc20CreateParams = { + templateIndex: 1, + minter: contracts.accounts[0], + feeManager: user3, + mpFeeAddress: contracts.accounts[0], + feeToken: '0x0000000000000000000000000000000000000000', + cap: '1000000', + feeAmount: '0', + name: 'ERC20B1', + symbol: 'ERC20DT1Symbol' + } + + const poolParams: PoolCreationParams = { + ssContract: contracts.sideStakingAddress, + basetokenAddress: contracts.usdcAddress, + basetokenSender: contracts.factory721Address, + publisherAddress: contracts.accounts[0], + marketFeeCollector: contracts.accounts[0], + poolTemplateAddress: contracts.poolTemplateAddress, + rate: '1', + basetokenDecimals: await usdcContract.methods.decimals().call(), + vestingAmount: '10000', + vestedBlocks: 2500000, + initialBasetokenLiquidity: web3.utils.fromWei( + await pool.amountToUnits(contracts.usdcAddress, '2000') + ), + swapFeeLiquidityProvider: 1e15, + swapFeeMarketPlaceRunner: 1e15 + } + + const txReceipt = await nftFactory.createNftErcWithPool( + contracts.accounts[0], + nftData, + ercParams, + poolParams + ) + + initialBlock = await web3.eth.getBlockNumber() + erc20Token = txReceipt.events.TokenCreated.returnValues.newTokenAddress + poolAddress = txReceipt.events.NewPool.returnValues.poolAddress + + erc20Contract = new web3.eth.Contract(ERC20Template.abi as AbiItem[], erc20Token) + // user2 has no dt1 + expect(await erc20Contract.methods.balanceOf(user2).call()).to.equal('0') + }) + + it('#getBasetokenBalance ', async () => { + expect( + await sideStaking.getBasetokenBalance(sideStakingAddress, erc20Token) + ).to.equal('0') + }) + it('#getDatatokenBalance ', async () => { + expect( + await sideStaking.getDatatokenBalance(sideStakingAddress, erc20Token) + ).to.equal('988000') + }) + + it('#getvestingAmount ', async () => { + expect(await sideStaking.getvestingAmount(sideStakingAddress, erc20Token)).to.equal( + '10000' + ) + }) + it('#getvestingLastBlock ', async () => { + expect( + await sideStaking.getvestingLastBlock(sideStakingAddress, erc20Token) + ).to.equal(initialBlock.toString()) + }) + + it('#getvestingEndBlock ', async () => { + expect( + await sideStaking.getvestingEndBlock(sideStakingAddress, erc20Token) + ).to.equal((initialBlock + vestedBlocks).toString()) + }) + it('#getvestingAmountSoFar ', async () => { + expect( + await sideStaking.getvestingAmountSoFar(sideStakingAddress, erc20Token) + ).to.equal('0') + }) + + it('#getVesting ', async () => { + expect( + await erc20Contract.methods.balanceOf(contracts.accounts[0]).call() + ).to.equal('0') + + const tx = await sideStaking.getVesting( + contracts.accounts[0], + sideStakingAddress, + erc20Token + ) + + expect( + await sideStaking.unitsToAmount( + erc20Token, + await erc20Contract.methods.balanceOf(contracts.accounts[0]).call() + ) + ).to.equal(await sideStaking.getvestingAmountSoFar(sideStakingAddress, erc20Token)) + + expect( + await sideStaking.getvestingLastBlock(sideStakingAddress, erc20Token) + ).to.equal((await web3.eth.getBlockNumber()).toString()) + }) + + it('#swapExactAmountIn - should swap', async () => { + const transferAmount = await pool.amountToUnits(contracts.usdcAddress, '1000') // 1000 USDC + await usdcContract.methods + .transfer(user2, transferAmount) + .send({ from: contracts.accounts[0] }) + expect(await usdcContract.methods.balanceOf(user2).call()).to.equal( + transferAmount.toString() + ) + + expect(await erc20Contract.methods.balanceOf(user2).call()).to.equal('0') + await pool.approve(user2, contracts.usdcAddress, poolAddress, '10') + const tx = await pool.swapExactAmountIn( + user2, + poolAddress, + contracts.usdcAddress, + '10', + erc20Token, + '1' + ) + expect(await erc20Contract.methods.balanceOf(user2).call()).to.equal( + tx.events.LOG_SWAP.returnValues.tokenAmountOut + ) + }) + + it('#swapExactAmountOut - should swap', async () => { + expect(await usdcContract.methods.balanceOf(user2).call()).to.equal( + (await pool.amountToUnits(contracts.usdcAddress, '990')).toString() + ) + await pool.approve(user2, contracts.usdcAddress, poolAddress, '100') + const tx = await pool.swapExactAmountOut( + user2, + poolAddress, + contracts.usdcAddress, + '100', + erc20Token, + '50' + ) + assert(tx != null) + // console.log(tx.events) + }) + + it('#joinswapExternAmountIn- user2 should add liquidity, receiving LP tokens', async () => { + const usdcAmountIn = '100' + const minBPTOut = '0.1' + await pool.approve(user2, contracts.usdcAddress, poolAddress, '100', true) + + const tx = await pool.joinswapExternAmountIn( + user2, + poolAddress, + contracts.usdcAddress, + usdcAmountIn, + minBPTOut + ) + + assert(tx != null) + + expect(tx.events.LOG_JOIN[0].event === 'LOG_JOIN') + expect(tx.events.LOG_BPT.event === 'LOG_BPT') + // 2 JOIN EVENTS BECAUSE SIDE STAKING ALSO STAKED DTs, TODO: we should add to whom has been sent in the LOG_BPT event + expect(tx.events.LOG_JOIN[0].returnValues.bptAmount).to.equal( + tx.events.LOG_JOIN[1].returnValues.bptAmount + ) + }) + + it('#joinswapPoolAmountOut- user2 should add liquidity, receiving LP tokens', async () => { + const BPTAmountOut = '0.1' + const maxUSDCIn = '100' + + await pool.approve(user2, contracts.usdcAddress, poolAddress, '100') + + const tx = await pool.joinswapPoolAmountOut( + user2, + poolAddress, + contracts.usdcAddress, + BPTAmountOut, + maxUSDCIn + ) + + assert(tx != null) + + expect(tx.events.LOG_JOIN[0].event === 'LOG_JOIN') + expect(tx.events.LOG_BPT.event === 'LOG_BPT') + // 2 JOIN EVENTS BECAUSE SIDE STAKING ALSO STAKED DTs, TODO: we should add to whom has been sent in the LOG_BPT event + expect(tx.events.LOG_JOIN[0].returnValues.bptAmount).to.equal( + tx.events.LOG_JOIN[1].returnValues.bptAmount + ) + }) + + it('#exitswapPoolAmountIn- user2 exit the pool receiving only USDC', async () => { + const BPTAmountIn = '0.5' + const minUSDCOut = '0.5' + + const tx = await pool.exitswapPoolAmountIn( + user2, + poolAddress, + contracts.usdcAddress, + BPTAmountIn, + minUSDCOut + ) + + assert(tx != null) + + expect(tx.events.LOG_EXIT[0].returnValues.tokenOut).to.equal(contracts.usdcAddress) + + // DTs were also unstaked in the same transaction (went to the staking contract) + expect(tx.events.LOG_EXIT[1].returnValues.tokenOut).to.equal(erc20Token) + }) + + it('#exitswapExternAmountOut- user2 exit the pool receiving only USDC', async () => { + const maxBTPIn = '0.5' + const exactUSDCOut = '1' + + const tx = await pool.exitswapPoolAmountIn( + user2, + poolAddress, + contracts.usdcAddress, + maxBTPIn, + exactUSDCOut + ) + + assert(tx != null) + + expect(tx.events.LOG_EXIT[0].returnValues.tokenOut).to.equal(contracts.usdcAddress) + + // DTs were also unstaked in the same transaction (went to the staking contract) + expect(tx.events.LOG_EXIT[1].returnValues.tokenOut).to.equal(erc20Token) + }) + }) +})