diff --git a/evm/contracts/bridge/BlobstreamO.sol b/evm/contracts/bridge/BlobstreamO.sol index db551b225..b89b7176f 100644 --- a/evm/contracts/bridge/BlobstreamO.sol +++ b/evm/contracts/bridge/BlobstreamO.sol @@ -3,7 +3,6 @@ pragma solidity 0.8.22; import "./ECDSA.sol"; import "./Constants.sol"; -// import "hardhat/console.sol"; struct OracleAttestationData { bytes32 queryId; diff --git a/evm/contracts/interfaces/IERC20.sol b/evm/contracts/interfaces/ITellorMaster.sol similarity index 75% rename from evm/contracts/interfaces/IERC20.sol rename to evm/contracts/interfaces/ITellorMaster.sol index 44fe6724a..d082eb114 100644 --- a/evm/contracts/interfaces/IERC20.sol +++ b/evm/contracts/interfaces/ITellorMaster.sol @@ -1,13 +1,12 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.22; -interface IERC20 { - function totalSupply() external view returns (uint256); - function balanceOf(address account) external view returns (uint256); - function transfer(address recipient, uint256 amount) external returns (bool); +interface ITellorMaster { function allowance(address owner, address spender) external view returns (uint256); function approve(address spender, uint256 amount) external returns (bool); + function balanceOf(address account) external view returns (uint256); + function mintToOracle() external; + function totalSupply() external view returns (uint256); + function transfer(address recipient, uint256 amount) external returns (bool); function transferFrom(address sender, address recipient, uint256 amount) external returns (bool); - event Transfer(address indexed from, address indexed to, uint256 value); - event Approval(address indexed owner, address indexed spender, uint256 value); } \ No newline at end of file diff --git a/evm/contracts/testing/TellorPlayground.sol b/evm/contracts/testing/TellorPlayground.sol index 52ba14759..dcd24828e 100644 --- a/evm/contracts/testing/TellorPlayground.sol +++ b/evm/contracts/testing/TellorPlayground.sol @@ -41,6 +41,8 @@ contract TellorPlayground { string private _name; string private _symbol; uint8 private _decimals; + address public oracleMintRecipient; + uint256 public lastReleaseTimeDao; // Structs struct StakeInfo { @@ -60,6 +62,7 @@ contract TellorPlayground { _symbol = "TRBP"; _decimals = 18; token = address(this); + lastReleaseTimeDao = block.timestamp; } /** @@ -131,6 +134,20 @@ contract TellorPlayground { _mint(_user, 1000 ether); } + /** + * @dev Simulates time based rewards minting + */ + function mintToOracle() external { + if (oracleMintRecipient == address(0)) { + return; + } + uint256 _releasedAmount = (146.94 ether * + (block.timestamp - lastReleaseTimeDao)) / + 86400; + _mint(oracleMintRecipient, _releasedAmount); + lastReleaseTimeDao = block.timestamp; + } + /** * @dev Allows a reporter to request to withdraw their stake * @param _amount amount of staked tokens requesting to withdraw @@ -147,6 +164,14 @@ contract TellorPlayground { emit StakeWithdrawRequested(msg.sender, _amount); } + /** + * @dev Allows the oracle mint recipient to be set for bridge testing + * @param _oracle The new oracle mint recipient + */ + function setOracleMintRecipient(address _oracle) external { + oracleMintRecipient = _oracle; + } + /** * @dev A mock function to submit a value to be read without reporter staking needed * @param _queryId the ID to associate the value to diff --git a/evm/contracts/testing/TellorUser.sol b/evm/contracts/testing/TellorUser.sol deleted file mode 100644 index bce18f2ef..000000000 --- a/evm/contracts/testing/TellorUser.sol +++ /dev/null @@ -1,25 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -// import "./UsingTellor.sol"; - -// contract TellorUser is UsingTellor { -// uint256 public ethPrice; -// uint256 public ethPriceTimestamp; -// BlobstreamO public bridge; - -// constructor(address _bridge) UsingTellor(_bridge) { -// bridge = BlobstreamO(_bridge); -// } - -// function updateEthPrice( -// OracleAttestationData calldata _attest, -// Validator[] calldata _currentValidatorSet, -// Signature[] calldata _sigs -// ) public { -// require(isCurrentConsensusValue(_attest, _currentValidatorSet, _sigs), "Invalid attestation"); -// ethPrice = abi.decode(_attest.report.value, (uint256)); -// ethPriceTimestamp = _attest.report.timestamp; -// } -// } - diff --git a/evm/contracts/token-bridge/LayerTransition.sol b/evm/contracts/token-bridge/LayerTransition.sol index 42e51a66a..247597d21 100644 --- a/evm/contracts/token-bridge/LayerTransition.sol +++ b/evm/contracts/token-bridge/LayerTransition.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.22; import { ITellorFlex } from "../interfaces/ITellorFlex.sol"; -import { IERC20 } from "../interfaces/IERC20.sol"; +import { ITellorMaster } from "../interfaces/ITellorMaster.sol"; /// @title LayerTransition. /// @dev The contract that enables users of really old tellor to keep using it (e.g. Liquity) @@ -11,7 +11,7 @@ import { IERC20 } from "../interfaces/IERC20.sol"; contract LayerTransition { /*Storage*/ bytes32 updateOracleQueryId = keccak256(abi.encode("TellorOracleAddress", abi.encode(bytes("")))); - IERC20 public token; + ITellorMaster public token; ITellorFlex public tellorFlex; /*Functions*/ @@ -20,7 +20,7 @@ contract LayerTransition { /// @param _token address of the tellor token (tellorMaster) constructor(address _tellorFlex, address _token) { tellorFlex = ITellorFlex(_tellorFlex); - token = IERC20(_token); + token = ITellorMaster(_token); } /// @notice this is needed because it's called when mintingToTeam. We hijack it to keep it in the bridge diff --git a/evm/contracts/token-bridge/TokenBridge.sol b/evm/contracts/token-bridge/TokenBridge.sol index 507d26049..2d6a723e8 100644 --- a/evm/contracts/token-bridge/TokenBridge.sol +++ b/evm/contracts/token-bridge/TokenBridge.sol @@ -9,7 +9,7 @@ import { LayerTransition } from "./LayerTransition.sol"; /// Ethereum to layer. No one needs to do this. The only reason you /// move your tokens over is to become a reporter/validator/tipper. It works by /// using layer itself as the bridge and then reads the lightclient contract for -/// bridging back. There is a long delay in bridging back (enforced by layer) of 21 days +/// bridging back. There is a long delay in bridging back (enforced by layer) of 12 hours contract TokenBridge is LayerTransition{ /*Storage*/ BlobstreamO public bridge; @@ -17,12 +17,11 @@ contract TokenBridge is LayerTransition{ uint256 public depositLimitUpdateTime;//last time the limit was updated uint256 public depositLimitRecord;//amount you can bridge per limit period uint256 public constant DEPOSIT_LIMIT_UPDATE_INTERVAL = 12 hours; - uint256 public constant INITIAL_LAYER_TOKEN_SUPPLY = 100 ether; // update this as needed uint256 public immutable DEPOSIT_LIMIT_DENOMINATOR = 100e18 / 20e18; // 100/depositLimitPercentage - mapping(uint256 => bool) public withdrawalClaimed; - mapping(address => uint256) public tokensToClaim; - mapping(uint256 => DepositDetails) public deposits; + mapping(uint256 => bool) public withdrawalClaimed; // withdrawal id => claimed status + mapping(address => uint256) public tokensToClaim; // recipient => extra amount to claim + mapping(uint256 => DepositDetails) public deposits; // deposit id => deposit details struct DepositDetails { address sender; @@ -38,11 +37,30 @@ contract TokenBridge is LayerTransition{ /*Functions*/ /// @notice constructor /// @param _token address of tellor token for bridging - /// @param _blobstream address of BlobstreamO for data bridge + /// @param _blobstream address of BlobstreamO data bridge /// @param _tellorFlex address of oracle(tellorFlex) on chain constructor(address _token, address _blobstream, address _tellorFlex) LayerTransition(_tellorFlex, _token){ bridge = BlobstreamO(_blobstream); - _refreshDepositLimit(); + // _refreshDepositLimit(); + } + + /// @notice claim extra withdrawals that were not fully withdrawn + /// @param _recipient address of the recipient + function claimExtraWithdraw(address _recipient) external { + uint256 _amountConverted = tokensToClaim[_recipient]; + require(_amountConverted > 0, "amount must be > 0"); + uint256 _depositLimit = _refreshDepositLimit(); + require(_depositLimit > 0, "TokenBridge: depositLimit must be > 0"); + if(_depositLimit < _amountConverted){ + tokensToClaim[_recipient] = tokensToClaim[_recipient] - _depositLimit; + _amountConverted = _depositLimit; + require(token.transfer(_recipient, _amountConverted), "TokenBridge: transfer failed"); + } + else{ + tokensToClaim[_recipient] = 0; + require(token.transfer(_recipient, _amountConverted), "TokenBridge: transfer failed"); + } + depositLimitRecord -= _amountConverted; } /// @notice deposits tokens from Ethereum to layer @@ -50,8 +68,8 @@ contract TokenBridge is LayerTransition{ /// @param _layerRecipient your cosmos address on layer (don't get it wrong!!) function depositToLayer(uint256 _amount, string memory _layerRecipient) external { require(_amount > 0, "TokenBridge: amount must be greater than 0"); - require(token.transferFrom(msg.sender, address(this), _amount), "TokenBridge: transferFrom failed"); require(_amount <= _refreshDepositLimit(), "TokenBridge: amount exceeds deposit limit for time period"); + require(token.transferFrom(msg.sender, address(this), _amount), "TokenBridge: transferFrom failed"); depositId++; depositLimitRecord -= _amount; deposits[depositId] = DepositDetails(msg.sender, _layerRecipient, _amount, block.number); @@ -59,23 +77,23 @@ contract TokenBridge is LayerTransition{ } /// @notice This withdraws tokens from layer to mainnet Ethereum - /// @param _attest The data being verified + /// @param _attestData The data being verified /// @param _valset array of current validator set /// @param _sigs Signatures /// @param _depositId depositId from the layer side function withdrawFromLayer( - OracleAttestationData calldata _attest, + OracleAttestationData calldata _attestData, Validator[] calldata _valset, Signature[] calldata _sigs, uint256 _depositId ) external { - require(_attest.queryId == keccak256(abi.encode("TRBBridge", abi.encode(false, _depositId))), "TokenBridge: invalid queryId"); + require(_attestData.queryId == keccak256(abi.encode("TRBBridge", abi.encode(false, _depositId))), "TokenBridge: invalid queryId"); require(!withdrawalClaimed[_depositId], "TokenBridge: withdrawal already claimed"); - require(block.timestamp - _attest.report.timestamp > 12 hours, "TokenBridge: premature attestation"); - bridge.verifyOracleData(_attest, _valset, _sigs); - require(_attest.report.aggregatePower >= bridge.powerThreshold(), "Report aggregate power must be greater than or equal to _minimumPower"); + require(block.timestamp - _attestData.report.timestamp > 12 hours, "TokenBridge: premature attestation"); + bridge.verifyOracleData(_attestData, _valset, _sigs); + require(_attestData.report.aggregatePower >= bridge.powerThreshold(), "Report aggregate power must be greater than or equal to _minimumPower"); withdrawalClaimed[_depositId] = true; - (address _recipient, string memory _layerSender,uint256 _amountLoya) = abi.decode(_attest.report.value, (address, string, uint256)); + (address _recipient, string memory _layerSender,uint256 _amountLoya) = abi.decode(_attestData.report.value, (address, string, uint256)); uint256 _amountConverted = _amountLoya * 1e12; uint256 _depositLimit = _refreshDepositLimit(); if(_depositLimit < _amountConverted){ @@ -90,39 +108,27 @@ contract TokenBridge is LayerTransition{ emit Withdrawal(_depositId, _layerSender, _recipient, _amountConverted); } - function claimExtraWithdraw(address _recipient) external { - uint256 _amountConverted = tokensToClaim[_recipient]; - require(_amountConverted > 0, "amount must be > 0"); - uint256 _depositLimit = _refreshDepositLimit(); - require(_depositLimit > 0, "TokenBridge: depositLimit must be > 0"); - if(_depositLimit < _amountConverted){ - tokensToClaim[_recipient] = tokensToClaim[_recipient] - _depositLimit; - _amountConverted = _depositLimit; - require(token.transfer(_recipient, _amountConverted), "TokenBridge: transfer failed"); - } - else{ - tokensToClaim[_recipient] = 0; - require(token.transfer(_recipient, _amountConverted), "TokenBridge: transfer failed"); - } - depositLimitRecord -= _amountConverted; - } - + /* View Functions */ /// @notice refreshes the deposit limit every 12 hours so no one can spam layer with new tokens function depositLimit() external view returns (uint256) { if (block.timestamp - depositLimitUpdateTime > DEPOSIT_LIMIT_UPDATE_INTERVAL) { - uint256 _layerTokenSupply = token.balanceOf(address(this)) + INITIAL_LAYER_TOKEN_SUPPLY; - return _layerTokenSupply / DEPOSIT_LIMIT_DENOMINATOR; + return token.balanceOf(address(this)) / DEPOSIT_LIMIT_DENOMINATOR; } else{ return depositLimitRecord; } } + /* Internal Functions */ /// @notice refreshes the deposit limit every 12 hours so no one can spam layer with new tokens function _refreshDepositLimit() internal returns (uint256) { if (block.timestamp - depositLimitUpdateTime > DEPOSIT_LIMIT_UPDATE_INTERVAL) { - uint256 _layerTokenSupply = token.balanceOf(address(this)) + INITIAL_LAYER_TOKEN_SUPPLY; - depositLimitRecord = _layerTokenSupply / DEPOSIT_LIMIT_DENOMINATOR; + uint256 _tokenBalance = token.balanceOf(address(this)); + if (_tokenBalance < 100 ether) { + token.mintToOracle(); + _tokenBalance = token.balanceOf(address(this)); + } + depositLimitRecord = _tokenBalance / DEPOSIT_LIMIT_DENOMINATOR; depositLimitUpdateTime = block.timestamp; } return depositLimitRecord; diff --git a/evm/hardhat.config.js b/evm/hardhat.config.js index 8ce8fe912..0e857528a 100644 --- a/evm/hardhat.config.js +++ b/evm/hardhat.config.js @@ -2,6 +2,8 @@ //require("hardhat-gas-reporter"); require("dotenv").config(); require("@nomiclabs/hardhat-ethers"); +require("hardhat-gas-reporter"); + // require("@nomiclabs/hardhat-web3"); module.exports = { @@ -56,16 +58,16 @@ module.exports = { }, networks: { hardhat: { - // accounts: { - // mnemonic: - // "nick lucian brenda kevin sam fiscal patch fly damp ocean produce wish", - // count: 40, - // }, + accounts: { + mnemonic: + "nick lucian brenda kevin sam fiscal patch fly damp ocean produce wish", + count: 40, + }, forking: { url: process.env.NODE_URL, blockNumber: 19891853 }, - // allowUnlimitedContractSize: true + allowUnlimitedContractSize: true } //, }, }; diff --git a/evm/package.json b/evm/package.json index 54c1bd993..e4447b742 100644 --- a/evm/package.json +++ b/evm/package.json @@ -15,12 +15,14 @@ "devDependencies": { "@nomiclabs/hardhat-ethers": "^2.0.4", "@nomicfoundation/hardhat-network-helpers": "^1.0.10", + "@nomiclabs/hardhat-ethers": "^2.0.4", "@nomiclabs/hardhat-web3": "^2.0.0", "axios": "^1.6.8", "chai": "^4.3.10", "elliptic": "^6.5.5", "ethers": "^5.7.0", "hardhat": "^2.22.3", + "hardhat-gas-reporter": "^2.2.0", "web3": "^1.10.4" }, "mocha": {}, diff --git a/evm/test/BlobstreamOFunctionTestsHH.js b/evm/test/BlobstreamOFunctionTestsHH.js index 7cb0af29d..66048e0b6 100644 --- a/evm/test/BlobstreamOFunctionTestsHH.js +++ b/evm/test/BlobstreamOFunctionTestsHH.js @@ -384,5 +384,20 @@ describe("Blobstream - Function Tests", async function () { currentValSetArray1, sigStructArray1 ) + + // update again + newValAddrs2 = [val4.address, val5.address, val6.address, val7.address] + newPowers2 = [4, 5, 6, 7] + newThreshold2 = 15 + newValHash2 = await h.calculateValHash(newValAddrs2, newPowers2) + blocky = await h.getBlock() + newValTimestamp2 = blocky.timestamp - 1 + newValCheckpoint2 = h.calculateValCheckpoint(newValHash2, newThreshold2, newValTimestamp2) + sigs2 = [] + for (i = 0; i < nVals; i++) { + sigs2.push(h.layerSign(newValCheckpoint2, wallets[i].privateKey)) + } + sigStructArray2 = await h.getSigStructArray(sigs2) + await blobstream.updateValidatorSet(newValHash2, newThreshold2, newValTimestamp2, currentValSetArray1, sigStructArray2); }) }) diff --git a/evm/test/TokenBridgeFunctionTestsHH.js b/evm/test/TokenBridgeFunctionTestsHH.js index accde2043..e4355a19a 100644 --- a/evm/test/TokenBridgeFunctionTestsHH.js +++ b/evm/test/TokenBridgeFunctionTestsHH.js @@ -8,13 +8,15 @@ const abiCoder = new ethers.utils.AbiCoder(); describe("TokenBridge - Function Tests", async function () { let blobstream, accounts, guardian, tbridge, token, blocky0, - valTs, valParams, valSet; + valTs, valParams, valSet, initialValAddrs, initialPowers, threshold; const UNBONDING_PERIOD = 86400 * 7 * 3; // 3 weeks const WITHDRAW1_QUERY_DATA_ARGS = abiCoder.encode(["bool", "uint256"], [false, 1]) const WITHDRAW1_QUERY_DATA = abiCoder.encode(["string", "bytes"], ["TRBBridge", WITHDRAW1_QUERY_DATA_ARGS]) const WITHDRAW1_QUERY_ID = h.hash(WITHDRAW1_QUERY_DATA) const EVM_RECIPIENT = "0x88dF592F8eb5D7Bd38bFeF7dEb0fBc02cf3778a0" const LAYER_RECIPIENT = "tellor1zy50vdk8fdae0var2ryjhj2ysxtcm8dp2qtckd" + const INITIAL_LAYER_TOKEN_SUPPLY = h.toWei("100") + beforeEach(async function () { // init accounts @@ -45,14 +47,19 @@ describe("TokenBridge - Function Tests", async function () { blocky0 = await h.getBlock() // fund accounts await token.faucet(accounts[0].address) + await token.faucet(accounts[10].address) + await token.connect(accounts[10]).transfer(tbridge.address, INITIAL_LAYER_TOKEN_SUPPLY) + + // sleep 1 second for api rate limit + await new Promise(r => setTimeout(r, 1000)); }); it("constructor", async function () { assert.equal(await tbridge.token(), await token.address) assert.equal(await tbridge.bridge(), await blobstream.address) - expect(Number(await tbridge.depositLimitUpdateTime())).to.be.closeTo(Number(blocky0.timestamp), 1) - expectedDepositLimit = BigInt(100e18) * BigInt(2) / BigInt(10) - assert.equal(BigInt(await tbridge.depositLimitRecord()), expectedDepositLimit); + // expect(Number(await tbridge.depositLimitUpdateTime())).to.be.closeTo(Number(blocky0.timestamp), 1) + // expectedDepositLimit = BigInt(100e18) * BigInt(2) / BigInt(10) + // assert.equal(BigInt(await tbridge.depositLimitRecord()), expectedDepositLimit); }) it("withdrawFromLayer", async function () { depositAmount = h.toWei("20") @@ -105,6 +112,7 @@ describe("TokenBridge - Function Tests", async function () { it("depositToLayer", async function () { depositAmount = h.toWei("1") assert.equal(await token.balanceOf(await accounts[0].address), h.toWei("1000")) + assert.equal(await token.balanceOf(await tbridge.address), INITIAL_LAYER_TOKEN_SUPPLY) await h.expectThrow(tbridge.depositToLayer(depositAmount, LAYER_RECIPIENT)) // not approved await token.approve(await tbridge.address, h.toWei("900")) await h.expectThrow(tbridge.depositToLayer(0, LAYER_RECIPIENT)) // zero amount @@ -112,7 +120,8 @@ describe("TokenBridge - Function Tests", async function () { await tbridge.depositToLayer(depositAmount, LAYER_RECIPIENT) blocky1 = await h.getBlock() tbridgeBal = await token.balanceOf(await tbridge.address) - assert.equal(tbridgeBal.toString(), h.toWei("1")) + expBalBridge = BigInt(depositAmount) + BigInt(INITIAL_LAYER_TOKEN_SUPPLY) + assert.equal(tbridgeBal.toString(), expBalBridge.toString()) userBal = await token.balanceOf(await accounts[0].address) assert.equal(userBal.toString(), h.toWei("999")) expectedDepositLimit = BigInt(100e18) * BigInt(2) / BigInt(10) - BigInt(depositAmount) @@ -131,6 +140,7 @@ describe("TokenBridge - Function Tests", async function () { await tbridge.refreshDepositLimit() assert.equal(BigInt(await tbridge.depositLimitRecord()), expectedDepositLimit2); }) + it("depositLimit", async function () { expectedDepositLimit = BigInt(100e18) * BigInt(2) / BigInt(10) await tbridge.refreshDepositLimit() @@ -148,7 +158,7 @@ describe("TokenBridge - Function Tests", async function () { }) it("claim extraWithdraw", async function () { await tbridge.refreshDepositLimit() - expectedDepositLimit = BigInt(100e18) * BigInt(2) / BigInt(10) + expectedDepositLimit = BigInt(INITIAL_LAYER_TOKEN_SUPPLY) * BigInt(2) / BigInt(10) depositAmount = expectedDepositLimit await h.expectThrow(tbridge.depositToLayer(depositAmount, LAYER_RECIPIENT)) // not approved await token.approve(await tbridge.address, h.toWei("100")) @@ -217,4 +227,233 @@ describe("TokenBridge - Function Tests", async function () { tokensToClaim = await tbridge.tokensToClaim(accounts[2].address) assert(tokensToClaim == BigInt(0), "tokensToClaim should be correct") }) + // more complex tests + it.only("100 deposits and withdrawals", async function () { + this.timeout(300000) + // fund accts + await token.faucet(accounts[0].address) + await token.faucet(accounts[1].address) + await token.faucet(accounts[1].address) + await token.connect(accounts[0]).approve(tbridge.address, h.toWei("10000")) + await token.connect(accounts[1]).approve(tbridge.address, h.toWei("10000")) + + initUserBal0 = await token.balanceOf(accounts[0].address) + initUserBal1 = await token.balanceOf(accounts[1].address) + niters = 100 + depositAmount0 = h.toWei("5") + depositAmount1 = h.toWei("10") + + // deposits + for (let i = 0; i < niters; i++) { + await tbridge.connect(accounts[0]).depositToLayer(depositAmount0, LAYER_RECIPIENT) + await tbridge.connect(accounts[1]).depositToLayer(depositAmount1, LAYER_RECIPIENT) + await h.advanceTime(43200) + } + // checks + userBal0 = await token.balanceOf(accounts[0].address) + userBal1 = await token.balanceOf(accounts[1].address) + bridgeBal = await token.balanceOf(await tbridge.address) + expectedBal0 = BigInt(initUserBal0) - BigInt(depositAmount0) * BigInt(niters) + expectedBal1 = BigInt(initUserBal1) - BigInt(depositAmount1) * BigInt(niters) + expectedBalBridge = BigInt(depositAmount0) * BigInt(niters) + BigInt(depositAmount1) * BigInt(niters) + BigInt(INITIAL_LAYER_TOKEN_SUPPLY) + assert(BigInt(userBal0) == expectedBal0, "user 0 balance should be correct") + assert(BigInt(userBal1) == expectedBal1, "user 1 balance should be correct") + assert(BigInt(bridgeBal) == expectedBalBridge, "bridge balance should be correct") + assert(await tbridge.depositId() == BigInt(niters * 2), "deposit id should be correct") + + // withdrawals + newValHash = await h.calculateValHash(initialValAddrs, initialPowers) + valCheckpoint = await h.calculateValCheckpoint(newValHash, threshold, valTimestamp) + withdrawValue0 = h.getWithdrawValue(accounts[0].address, LAYER_RECIPIENT, BigInt(depositAmount0) / BigInt(1e12)) + withdrawValue1 = h.getWithdrawValue(accounts[1].address, LAYER_RECIPIENT, BigInt(depositAmount1) / BigInt(1e12)) + aggregatePower = 3 + expTokensToClaim0 = BigInt(0) + expTokensToClaim1 = BigInt(0) + + for (let i = 0; i UNBONDING_PERIOD) { + valTimestamp = blocky.timestamp - 2 + newValHash = await h.calculateValHash(initialValAddrs, initialPowers) + valCheckpoint = h.calculateValCheckpoint(newValHash, threshold, valTimestamp) + await blobstream.connect(guardian).guardianResetValidatorSet(threshold, valTimestamp, valCheckpoint) + } + withdrawId0 = i * 2 + 1 + withdrawId1 = i * 2 + 2 + withdrawQueryDataArgs0 = abiCoder.encode(['bool', 'uint256'], [false, withdrawId0]) + withdrawQueryDataArgs1 = abiCoder.encode(['bool', 'uint256'], [false, withdrawId1]) + withdrawQueryData0 = abiCoder.encode(['string', 'bytes'], ['TRBBridge', withdrawQueryDataArgs0]) + withdrawQueryData1 = abiCoder.encode(['string', 'bytes'], ['TRBBridge', withdrawQueryDataArgs1]) + withdrawQueryId0 = h.hash(withdrawQueryData0) + withdrawQueryId1 = h.hash(withdrawQueryData1) + blocky = await h.getBlock() + reportTimestamp = blocky.timestamp - 84600 + attestationTimestamp = blocky.timestamp + dataDigest0 = await h.getDataDigest( + withdrawQueryId0, + withdrawValue0, + reportTimestamp, + aggregatePower, + reportTimestamp - 1, + 0, + valCheckpoint, + attestationTimestamp + ) + dataDigest1 = await h.getDataDigest( + withdrawQueryId1, + withdrawValue1, + reportTimestamp, + aggregatePower, + reportTimestamp - 1, + 0, + valCheckpoint, + attestationTimestamp + ) + currentValSetArray = await h.getValSetStructArray(initialValAddrs, initialPowers) + sig0_1 = await h.layerSign(dataDigest0, val1.privateKey) + sig0_2 = await h.layerSign(dataDigest0, val2.privateKey) + sig1_1 = await h.layerSign(dataDigest1, val1.privateKey) + sig1_2 = await h.layerSign(dataDigest1, val2.privateKey) + sigStructArray0 = await h.getSigStructArray([sig0_1, sig0_2]) + sigStructArray1 = await h.getSigStructArray([sig1_1, sig1_2]) + oracleDataStruct0 = await h.getOracleDataStruct( + withdrawQueryId0, + withdrawValue0, + reportTimestamp, + aggregatePower, + reportTimestamp - 1, + 0, + attestationTimestamp + ) + oracleDataStruct1 = await h.getOracleDataStruct( + withdrawQueryId1, + withdrawValue1, + reportTimestamp, + aggregatePower, + reportTimestamp - 1, + 0, + attestationTimestamp + ) + + depositLimitBefore0 = await tbridge.depositLimit() + await tbridge.withdrawFromLayer( + oracleDataStruct0, + currentValSetArray, + sigStructArray0, + withdrawId0, + ) + depositLimitBefore1 = await tbridge.depositLimit() + await tbridge.withdrawFromLayer( + oracleDataStruct1, + currentValSetArray, + sigStructArray1, + withdrawId1, + ) + + if (BigInt(depositAmount0) > BigInt(depositLimitBefore0)) { + expectedBal0 += BigInt(depositLimitBefore0) + expTokensToClaim0 += BigInt(depositAmount0) - BigInt(depositLimitBefore0) + expectedBalBridge -= BigInt(depositLimitBefore0) + } else { + expectedBal0 += BigInt(depositAmount0) + expectedBalBridge -= BigInt(depositAmount0) + } + if (depositAmount1 > depositLimitBefore1) { + expectedBal1 += BigInt(depositLimitBefore1) + expTokensToClaim1 += BigInt(depositAmount1) - BigInt(depositLimitBefore1) + expectedBalBridge -= BigInt(depositLimitBefore1) + } else { + expectedBal1 += BigInt(depositAmount1) + expectedBalBridge -= BigInt(depositAmount1) + } + userBal0 = await token.balanceOf(accounts[0].address) + userBal1 = await token.balanceOf(accounts[1].address) + bridgeBal = await token.balanceOf(await tbridge.address) + tokensToClaim0 = await tbridge.tokensToClaim(accounts[0].address) + tokensToClaim1 = await tbridge.tokensToClaim(accounts[1].address) + + assert(BigInt(userBal0) == expectedBal0, "user0 bal should be correct") + assert(BigInt(userBal1) == expectedBal1, "user1 bal should be correct") + assert(BigInt(bridgeBal) == expectedBalBridge, "bridge bal should be correct") + assert(BigInt(tokensToClaim0) == expTokensToClaim0, "tokensToClaim0 should be correct") + assert(BigInt(tokensToClaim1) == expTokensToClaim1, "tokensToClaim1 should be correct") + } + + await h.expectThrow(tbridge.claimExtraWithdraw(accounts[0].address)) + await h.expectThrow(tbridge.claimExtraWithdraw(accounts[1].address)) + await h.advanceTime(43200) + + while (tokensToClaim0 > 0) { + await tbridge.claimExtraWithdraw(accounts[0].address) + tokensToClaim0 = await tbridge.tokensToClaim(accounts[0].address) + await h.advanceTime(43200) + } + while (tokensToClaim1 > 0) { + await tbridge.claimExtraWithdraw(accounts[1].address) + tokensToClaim1 = await tbridge.tokensToClaim(accounts[1].address) + await h.advanceTime(43200) + } + + userBal0 = await token.balanceOf(accounts[0].address) + userBal1 = await token.balanceOf(accounts[1].address) + bridgeBal = await token.balanceOf(await tbridge.address) + assert(BigInt(userBal0) == initUserBal0, "user0 bal should be correct") + assert(BigInt(userBal1) == initUserBal1, "user1 bal should be correct") + assert(BigInt(bridgeBal) == BigInt(INITIAL_LAYER_TOKEN_SUPPLY), "bridge bal should be correct") + }, 300000) + + it("mint rate rounding error", async function() { + // test worst case rounding error of mintToOracle every block + // and no rounding errors from EVM->Layer decimal conversion. + // layer mintToOracle calculation: + // uint256 _releasedAmount = (146.94 ether * + // (block.timestamp - uints[keccak256("_LAST_RELEASE_TIME_DAO")])) / + // 86400; + + // minting rate per day on layer + const amtPerDayLayer = BigInt(h.toWei("146.94")); + + // minting rate per block on evm (12 sec blocks) + const blocksPerDayEVM = BigInt(86400) / BigInt(12); + const amtPerBlockEVM = amtPerDayLayer / blocksPerDayEVM; + + // total minted amount in a year on evm + const amtPerYearEVM = amtPerBlockEVM * blocksPerDayEVM * BigInt(365); + + // total minted amount in a year on layer + const amtPerYearLayer = amtPerDayLayer * BigInt(365); + + // difference in minted amounts between evm and layer in a year + const diffPerYear = amtPerYearLayer - amtPerYearEVM; + + // Assert that difference is less than a small threshold (e.g., 1e12 wei) + const threshold = BigInt(1e12); + assert(diffPerYear < threshold, "Difference in minting rates between EVM and Layer should be less than threshold"); + }) + + it("mintToOracle on deposit", async function() { + const bridge2 = await ethers.deployContract("TestTokenBridge", [token.address,blobstream.address, oldOracle.address]) + await token.setOracleMintRecipient(bridge2.address) + deployTime = await token.lastReleaseTimeDao() + bridgeBal = await token.balanceOf(await bridge2.address) + assert(BigInt(bridgeBal) == BigInt(0), "bridge bal should be correct") + await token.approve(bridge2.address, h.toWei("10000")) + await h.advanceTime(86400) + await bridge2.depositToLayer(h.toWei("20"), LAYER_RECIPIENT) + blocky1 = await h.getBlock() + // formula from Tellor360: + // uint256 _releasedAmount = (146.94 ether * + // (block.timestamp - uints[keccak256("_LAST_RELEASE_TIME_DAO")])) / + // 86400; + expectedBal = (BigInt(h.toWei("146.94")) * (BigInt(blocky1.timestamp) - BigInt(deployTime))) / BigInt(86400) + BigInt(h.toWei("20")) + bridgeBal = await token.balanceOf(await bridge2.address) + assert(BigInt(bridgeBal) == BigInt(expectedBal), "bridge bal should be correct") + await h.advanceTime(86400) + await bridge2.depositToLayer(h.toWei("20"), LAYER_RECIPIENT) + expectedBal = expectedBal + BigInt(h.toWei("20")) + bridgeBal = await token.balanceOf(await bridge2.address) + assert(BigInt(bridgeBal) == BigInt(expectedBal), "bridge bal should be correct") + }) }) diff --git a/evm/test/helpers/evmHelpers.js b/evm/test/helpers/evmHelpers.js index 777ebb70f..970658494 100644 --- a/evm/test/helpers/evmHelpers.js +++ b/evm/test/helpers/evmHelpers.js @@ -131,10 +131,10 @@ getEthSignedMessageHash = (messageHash) => { return digest; } -getDataDigest = (queryId, value, timestamp, aggregatePower, previousTimestamp, nextTimestamp, valCheckpoint, blockTimestamp) => { +getDataDigest = (queryId, value, timestamp, aggregatePower, previousTimestamp, nextTimestamp, valCheckpoint, attestationTimestamp) => { const DOMAIN_SEPARATOR = "0x74656c6c6f7243757272656e744174746573746174696f6e0000000000000000" enc = abiCoder.encode(["bytes32", "bytes32", "bytes", "uint256", "uint256", "uint256", "uint256", "bytes32", "uint256"], - [DOMAIN_SEPARATOR, queryId, value, timestamp, aggregatePower, previousTimestamp, nextTimestamp, valCheckpoint, blockTimestamp]) + [DOMAIN_SEPARATOR, queryId, value, timestamp, aggregatePower, previousTimestamp, nextTimestamp, valCheckpoint, attestationTimestamp]) return hash(enc) } diff --git a/start_scripts/start_chain_four_vals.sh b/start_scripts/start_chain_four_vals.sh index 9546237fc..3aa4055d4 100644 --- a/start_scripts/start_chain_four_vals.sh +++ b/start_scripts/start_chain_four_vals.sh @@ -74,13 +74,13 @@ sed -ie 's/"chain_id": .*"/"chain_id": '\"layer\"'/g' ~/.layer/config/genesis.js # Update vote_extensions_enable_height in genesis.json for alice echo "Updating vote_extensions_enable_height in genesis.json for alice..." -jq '.consensus.params.abci.vote_extensions_enable_height = "1" | .slashing.params.signed_blocks_window = "500"' ~/.layer/alice/config/genesis.json > temp.json && mv temp.json ~/.layer/alice/config/genesis.json -jq '.consensus.params.abci.vote_extensions_enable_height = "1" | .slashing.params.signed_blocks_window = "500"' ~/.layer/config/genesis.json > temp.json && mv temp.json ~/.layer/config/genesis.json +jq '.consensus.params.abci.vote_extensions_enable_height = "1" ~/.layer/alice/config/genesis.json > temp.json && mv temp.json ~/.layer/alice/config/genesis.json +jq '.consensus.params.abci.vote_extensions_enable_height = "1" ~/.layer/config/genesis.json > temp.json && mv temp.json ~/.layer/config/genesis.json -# # update the block grace period you have to join as a validator -# echo "Updating block grace period from 100 to 500" -# jq '.slashing.params.signed_blocks_window = "500"' ~/.layer/alice/config/genesis.json > temp.json && mv temp.json ~/.layer/alice/config/genesis.json -# jq '.slashing.params.signed_blocks_window = "500"' ~/.layer/config/genesis.json > temp.json && mv temp.json ~/.layer/config/genesis.json +# Update signed_blocks_window in genesis.json for alice +echo "Updating signed_blocks_window in genesis.json for alice..." +jq '.app_state.slashing.params.signed_blocks_window = "1000"' ~/.layer/alice/config/genesis.json > temp.json && mv temp.json ~/.layer/alice/config/genesis.json +jq '.app_state.slashing.params.signed_blocks_window = "1000"' ~/.layer/config/genesis.json > temp.json && mv temp.json ~/.layer/config/genesis.json # Get address/account for alice to use in gentx tx echo "Get address/account for alice to use in gentx tx" diff --git a/start_scripts/start_two_chains.sh b/start_scripts/start_two_chains.sh index 4a441dfac..b5a107e44 100755 --- a/start_scripts/start_two_chains.sh +++ b/start_scripts/start_two_chains.sh @@ -53,6 +53,14 @@ jq '.consensus.params.abci.vote_extensions_enable_height = "1"' ~/.layer/alice/c echo "bill..." jq '.consensus.params.abci.vote_extensions_enable_height = "1"' ~/.layer/bill/config/genesis.json > temp.json && mv temp.json ~/.layer/bill/config/genesis.json + +# Update signed_blocks_window in genesis.json for alice +echo "Updating signed_blocks_window in genesis.json for alice..." +jq '.app_state.slashing.params.signed_blocks_window = "1000"' ~/.layer/alice/config/genesis.json > temp.json && mv temp.json ~/.layer/alice/config/genesis.json +jq '.app_state.slashing.params.signed_blocks_window = "1000"' ~/.layer/config/genesis.json > temp.json && mv temp.json ~/.layer/config/genesis.json +echo "Updating signed_blocks_window in genesis.json for bill..." +jq '.app_state.slashing.params.signed_blocks_window = "1000"' ~/.layer/bill/config/genesis.json > temp.json && mv temp.json ~/.layer/bill/config/genesis.json + # Create a tx to give alice loyas to stake echo "Adding genesis accounts..." echo "alice..." diff --git a/tests/e2e/basic_dispute_test.go b/tests/e2e/basic_dispute_test.go index 106bdfd54..d54b692d2 100644 --- a/tests/e2e/basic_dispute_test.go +++ b/tests/e2e/basic_dispute_test.go @@ -217,7 +217,7 @@ func (s *E2ETestSuite) TestDisputes() { //--------------------------------------------------------------------------- s.Setup.Ctx = s.Setup.Ctx.WithBlockHeight(s.Setup.Ctx.BlockHeight() + 1) s.Setup.Ctx = s.Setup.Ctx.WithBlockTime(s.Setup.Ctx.BlockTime().Add(time.Second)) - err = s.Setup.Disputekeeper.Tallyvote(s.Setup.Ctx, dispute.DisputeId) + err = s.Setup.Disputekeeper.TallyVote(s.Setup.Ctx, dispute.DisputeId) require.Error(err, "vote period not ended and quorum not reached") _, err = s.Setup.App.BeginBlocker(s.Setup.Ctx) require.NoError(err) @@ -253,7 +253,7 @@ func (s *E2ETestSuite) TestDisputes() { //--------------------------------------------------------------------------- s.Setup.Ctx = s.Setup.Ctx.WithBlockHeight(s.Setup.Ctx.BlockHeight() + 1) s.Setup.Ctx = s.Setup.Ctx.WithBlockTime(s.Setup.Ctx.BlockTime().Add(time.Second)) - err = s.Setup.Disputekeeper.Tallyvote(s.Setup.Ctx, 1) + err = s.Setup.Disputekeeper.TallyVote(s.Setup.Ctx, 1) require.Error(err, "vote period not ended and quorum not reached") _, err = s.Setup.App.BeginBlocker(s.Setup.Ctx) require.NoError(err) @@ -341,9 +341,9 @@ func (s *E2ETestSuite) TestDisputes() { // --------------------------------------------------------------------------- s.Setup.Ctx = s.Setup.Ctx.WithBlockHeight(s.Setup.Ctx.BlockHeight() + 1) s.Setup.Ctx = s.Setup.Ctx.WithBlockTime(s.Setup.Ctx.BlockTime().Add(time.Second)) - err = s.Setup.Disputekeeper.Tallyvote(s.Setup.Ctx, 1) + err = s.Setup.Disputekeeper.TallyVote(s.Setup.Ctx, 1) require.Error(err, "vote period not ended and quorum not reached") - err = s.Setup.Disputekeeper.Tallyvote(s.Setup.Ctx, 2) + err = s.Setup.Disputekeeper.TallyVote(s.Setup.Ctx, 2) require.Error(err, "vote period not ended and quorum not reached") _, err = s.Setup.App.BeginBlocker(s.Setup.Ctx) require.NoError(err) @@ -407,8 +407,8 @@ func (s *E2ETestSuite) TestDisputes() { s.Setup.Ctx = s.Setup.Ctx.WithBlockHeight(s.Setup.Ctx.BlockHeight() + 1) s.Setup.Ctx = s.Setup.Ctx.WithBlockTime(s.Setup.Ctx.BlockTime().Add(time.Second)) - require.NoError(s.Setup.Disputekeeper.Tallyvote(s.Setup.Ctx, 1)) - require.NoError(s.Setup.Disputekeeper.Tallyvote(s.Setup.Ctx, 2)) + require.NoError(s.Setup.Disputekeeper.TallyVote(s.Setup.Ctx, 1)) + require.NoError(s.Setup.Disputekeeper.TallyVote(s.Setup.Ctx, 2)) require.NoError(s.Setup.Disputekeeper.ExecuteVote(s.Setup.Ctx, 1)) require.NoError(s.Setup.Disputekeeper.ExecuteVote(s.Setup.Ctx, 2)) @@ -516,7 +516,7 @@ func (s *E2ETestSuite) TestDisputes() { s.Setup.Ctx = s.Setup.Ctx.WithBlockHeight(s.Setup.Ctx.BlockHeight() + 1) s.Setup.Ctx = s.Setup.Ctx.WithBlockTime(s.Setup.Ctx.BlockTime().Add(time.Second)) - err = s.Setup.Disputekeeper.Tallyvote(s.Setup.Ctx, 3) + err = s.Setup.Disputekeeper.TallyVote(s.Setup.Ctx, 3) require.Error(err, "vote period not ended and quorum not reached") _, err = s.Setup.App.BeginBlocker(s.Setup.Ctx) require.NoError(err) @@ -573,7 +573,7 @@ func (s *E2ETestSuite) TestDisputes() { // _, err = s.Setup.App.BeginBlocker(s.Setup.Ctx) // require.NoError(err) - err = s.Setup.Disputekeeper.Tallyvote(s.Setup.Ctx, 3) + err = s.Setup.Disputekeeper.TallyVote(s.Setup.Ctx, 3) require.NoError(err) _, err = s.Setup.App.BeginBlocker(s.Setup.Ctx) require.NoError(err) diff --git a/tests/integration/dispute_keeper_test.go b/tests/integration/dispute_keeper_test.go index 0b3f8bbe6..6480e2fc9 100644 --- a/tests/integration/dispute_keeper_test.go +++ b/tests/integration/dispute_keeper_test.go @@ -268,7 +268,7 @@ func (s *IntegrationTestSuite) TestExecuteVoteInvalid() { // only 25 percent of the total power voted so vote should not be tallied unless it's expired s.Setup.Ctx = s.Setup.Ctx.WithBlockTime(s.Setup.Ctx.BlockTime().Add(keeper.THREE_DAYS + 1)) // // tally vote - err = s.Setup.Disputekeeper.Tallyvote(s.Setup.Ctx, 1) + err = s.Setup.Disputekeeper.TallyVote(s.Setup.Ctx, 1) s.NoError(err) repTknBeforeExecuteVote := repTokens @@ -363,7 +363,7 @@ func (s *IntegrationTestSuite) TestExecuteVoteNoQuorumInvalid() { s.NoError(err) ctx := s.Setup.Ctx.WithBlockTime(s.Setup.Ctx.BlockTime().Add(keeper.THREE_DAYS + 1)) - err = s.Setup.Disputekeeper.Tallyvote(ctx, 1) + err = s.Setup.Disputekeeper.TallyVote(ctx, 1) s.NoError(err) bond := sdk.DefaultPowerReduction.MulRaw(report.Power) @@ -466,7 +466,7 @@ func (s *IntegrationTestSuite) TestExecuteVoteSupport() { } } fmt.Println("rep", repAddr.String()) - err = s.Setup.Disputekeeper.Tallyvote(s.Setup.Ctx, 1) + err = s.Setup.Disputekeeper.TallyVote(s.Setup.Ctx, 1) s.NoError(err) // execute vote s.NoError(s.Setup.Disputekeeper.ExecuteVote(s.Setup.Ctx, 1)) @@ -602,7 +602,7 @@ func (s *IntegrationTestSuite) TestExecuteVoteAgainst() { s.NoError(err) fmt.Println(val.Tokens) // tally vote - err = s.Setup.Disputekeeper.Tallyvote(s.Setup.Ctx, 1) + err = s.Setup.Disputekeeper.TallyVote(s.Setup.Ctx, 1) s.NoError(err) // execute vote err = s.Setup.Disputekeeper.ExecuteVote(s.Setup.Ctx, 1) @@ -699,8 +699,8 @@ func (s *IntegrationTestSuite) TestDisputeMultipleRounds() { s.Error(err, "can't start a new round for this dispute 1; dispute status DISPUTE_STATUS_VOTING") // forward time to end voting period pre execute vote s.Setup.Ctx = s.Setup.Ctx.WithBlockTime(s.Setup.Ctx.BlockTime().Add(keeper.TWO_DAYS + 1)) - s.NoError(s.Setup.Disputekeeper.Tallyvote(s.Setup.Ctx, 1)) - s.ErrorContains(s.Setup.Disputekeeper.Tallyvote(s.Setup.Ctx, 1), "vote already tallied") + s.NoError(s.Setup.Disputekeeper.TallyVote(s.Setup.Ctx, 1)) + s.ErrorContains(s.Setup.Disputekeeper.TallyVote(s.Setup.Ctx, 1), "vote already tallied") s.Error(s.Setup.Disputekeeper.ExecuteVote(s.Setup.Ctx, 1), "dispute is not resolved yet") // start another dispute round _, err = msgServer.ProposeDispute(s.Setup.Ctx, &disputeMsg) @@ -710,7 +710,7 @@ func (s *IntegrationTestSuite) TestDisputeMultipleRounds() { reporter1, err = s.Setup.Reporterkeeper.Reporter(s.Setup.Ctx, reporter1Acc) s.NoError(err) - s.Error(s.Setup.Disputekeeper.Tallyvote(s.Setup.Ctx, 2), "vote period not ended and quorum not reached") + s.Error(s.Setup.Disputekeeper.TallyVote(s.Setup.Ctx, 2), "vote period not ended and quorum not reached") // voting that doesn't reach quorum voteMsg = types.MsgVote{ @@ -724,7 +724,7 @@ func (s *IntegrationTestSuite) TestDisputeMultipleRounds() { // expire vote period s.Setup.Ctx = s.Setup.Ctx.WithBlockTime(s.Setup.Ctx.BlockTime().Add(keeper.THREE_DAYS + 1)) - s.NoError(s.Setup.Disputekeeper.Tallyvote(s.Setup.Ctx, 2)) + s.NoError(s.Setup.Disputekeeper.TallyVote(s.Setup.Ctx, 2)) s.NoError(s.Setup.Disputekeeper.ExecuteVote(s.Setup.Ctx, 2)) // attempt to start another round _, err = msgServer.ProposeDispute(s.Setup.Ctx, &disputeMsg) @@ -783,7 +783,7 @@ func (s *IntegrationTestSuite) TestNoQorumSingleRound() { s.NoError(err) // forward time to expire dispute s.Setup.Ctx = s.Setup.Ctx.WithBlockTime(s.Setup.Ctx.BlockTime().Add(keeper.THREE_DAYS + 1)) - s.NoError(s.Setup.Disputekeeper.Tallyvote(s.Setup.Ctx, 1)) + s.NoError(s.Setup.Disputekeeper.TallyVote(s.Setup.Ctx, 1)) s.NoError(s.Setup.Disputekeeper.ExecuteVote(s.Setup.Ctx, 1)) } @@ -826,7 +826,7 @@ func (s *IntegrationTestSuite) TestDisputeButNoVotes() { // forward time to expire dispute s.Setup.Ctx = s.Setup.Ctx.WithBlockTime(s.Setup.Ctx.BlockTime().Add(keeper.THREE_DAYS + 1)) - s.NoError(s.Setup.Disputekeeper.Tallyvote(s.Setup.Ctx, 1)) + s.NoError(s.Setup.Disputekeeper.TallyVote(s.Setup.Ctx, 1)) s.NoError(s.Setup.Disputekeeper.ExecuteVote(s.Setup.Ctx, 1)) } diff --git a/x/bridge/keeper/keeper.go b/x/bridge/keeper/keeper.go index b897b8cab..6970083d9 100644 --- a/x/bridge/keeper/keeper.go +++ b/x/bridge/keeper/keeper.go @@ -119,7 +119,6 @@ func (k Keeper) GetCurrentValidatorsEVMCompatible(ctx context.Context) ([]*types for _, validator := range validators { evmAddress, err := k.OperatorToEVMAddressMap.Get(ctx, validator.GetOperator()) if err != nil { - k.Logger(ctx).Info("Error getting EVM address from operator address", "error", err) continue // Skip this validator if the EVM address is not found } adjustedPower := validator.GetConsensusPower(layertypes.PowerReduction) diff --git a/x/bridge/keeper/query_get_attestation_data_by_snapshot.go b/x/bridge/keeper/query_get_attestation_data_by_snapshot.go index 939061233..02103c7d9 100644 --- a/x/bridge/keeper/query_get_attestation_data_by_snapshot.go +++ b/x/bridge/keeper/query_get_attestation_data_by_snapshot.go @@ -34,11 +34,11 @@ func (q Querier) GetAttestationDataBySnapshot(goCtx context.Context, req *types. return nil, status.Error(codes.InvalidArgument, fmt.Sprintf("snapshot not found for snapshot %s", snapshot)) } queryId := snapshotData.QueryId - timestampTime := time.Unix(snapshotData.Timestamp, 0) + timestampTime := time.UnixMilli(snapshotData.Timestamp) aggReport, err := q.k.oracleKeeper.GetAggregateByTimestamp(ctx, queryId, timestampTime) if err != nil { - return nil, status.Error(codes.InvalidArgument, fmt.Sprintf("aggregate not found for queryId %s and timestamp %s", queryId, timestampTime)) + return nil, status.Error(codes.InvalidArgument, fmt.Sprintf("aggregate not found for queryId %s and timestamp %d", hex.EncodeToString(queryId), snapshotData.Timestamp)) } queryIdStr := hex.EncodeToString(queryId) diff --git a/x/bridge/keeper/query_get_snapshots_by_report.go b/x/bridge/keeper/query_get_snapshots_by_report.go index b88404895..c1f9d1cc6 100644 --- a/x/bridge/keeper/query_get_snapshots_by_report.go +++ b/x/bridge/keeper/query_get_snapshots_by_report.go @@ -29,7 +29,7 @@ func (q Querier) GetSnapshotsByReport(ctx context.Context, req *types.QueryGetSn } timestampTime := time.Unix(timestampInt, 0) - key := crypto.Keccak256([]byte(hex.EncodeToString(queryIdBytes) + fmt.Sprint(timestampTime.UnixMilli()))) + key := crypto.Keccak256([]byte(hex.EncodeToString(queryIdBytes) + fmt.Sprint(timestampTime.UnixMilli()/1000))) snapshots, err := q.k.AttestSnapshotsByReportMap.Get(ctx, key) if err != nil { return nil, status.Error(codes.InvalidArgument, fmt.Sprintf("snapshots not found for queryId %s and timestamp %s", queryIdStr, timestampStr)) diff --git a/x/dispute/abci_test.go b/x/dispute/abci_test.go new file mode 100644 index 000000000..b4c62aaf6 --- /dev/null +++ b/x/dispute/abci_test.go @@ -0,0 +1,85 @@ +package dispute_test + +import ( + "testing" + "time" + + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" + "github.com/tellor-io/layer/app/config" + keepertest "github.com/tellor-io/layer/testutil/keeper" + "github.com/tellor-io/layer/x/dispute" + "github.com/tellor-io/layer/x/dispute/keeper" + "github.com/tellor-io/layer/x/dispute/mocks" + "github.com/tellor-io/layer/x/dispute/types" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +type TestSuite struct { + suite.Suite + + ctx sdk.Context + disputeKeeper keeper.Keeper + + accountKeeper *mocks.AccountKeeper + bankKeeper *mocks.BankKeeper + oracleKeeper *mocks.OracleKeeper + reporterKeeper *mocks.ReporterKeeper +} + +func (s *TestSuite) SetupTest() { + config.SetupConfig() + + s.disputeKeeper, + s.oracleKeeper, + s.reporterKeeper, + s.accountKeeper, + s.bankKeeper, + s.ctx = keepertest.DisputeKeeper(s.T()) +} + +func TestTestSuite(t *testing.T) { + suite.Run(t, new(TestSuite)) +} + +func (s *TestSuite) TestBeginBlocker() { + require := require.New(s.T()) + k := s.disputeKeeper + ctx := s.ctx + + err := dispute.BeginBlocker(ctx, k) + require.NoError(err) +} + +func (s *TestSuite) TestCheckPrevoteDisputesForExpiration() { + require := require.New(s.T()) + k := s.disputeKeeper + ctx := s.ctx + ctx = ctx.WithBlockTime(ctx.BlockTime().Add(24 * time.Hour)) + + // check with no open disputes + err := dispute.CheckPrevoteDisputesForExpiration(ctx, k) + require.NoError(err) + + // check with open dispute + require.NoError(k.Disputes.Set(ctx, 1, types.Dispute{ + DisputeId: 1, + DisputeStatus: types.Prevote, + DisputeStartTime: ctx.BlockTime().Add(-time.Hour), + DisputeEndTime: ctx.BlockTime().Add(time.Hour), + Open: true, + })) + + err = dispute.CheckPrevoteDisputesForExpiration(ctx, k) + require.NoError(err) + + // check again after endtime passes + ctx = ctx.WithBlockTime(ctx.BlockTime().Add(2 * time.Hour)) + err = dispute.CheckPrevoteDisputesForExpiration(ctx, k) + require.NoError(err) + dispute, err := k.Disputes.Get(ctx, 1) + require.NoError(err) + require.Equal(dispute.DisputeStatus, types.Failed) + require.Equal(dispute.Open, false) +} diff --git a/x/dispute/autocli_test.go b/x/dispute/autocli_test.go new file mode 100644 index 000000000..f60043f13 --- /dev/null +++ b/x/dispute/autocli_test.go @@ -0,0 +1,16 @@ +package dispute + +import ( + "testing" + + "github.com/stretchr/testify/require" + "github.com/tellor-io/layer/x/dispute/keeper" +) + +func TestAutoCLIOptions(t *testing.T) { + require := require.New(t) + am := NewAppModule(nil, keeper.Keeper{}, nil, nil) + + moduleOptions := am.AutoCLIOptions() + require.NotNil(moduleOptions) +} diff --git a/x/dispute/keeper/indexes_test.go b/x/dispute/keeper/indexes_test.go index bc45e2f1e..abce97a2d 100644 --- a/x/dispute/keeper/indexes_test.go +++ b/x/dispute/keeper/indexes_test.go @@ -8,13 +8,31 @@ import ( "cosmossdk.io/collections/colltest" ) -func (k *KeeperTestSuite) TestIndexesList() { - sk, _ := colltest.MockStore() - schema := collections.NewSchemaBuilder(sk) +func (s *KeeperTestSuite) TestIndexesList_Disputes() { + storeService, _ := colltest.MockStore() + schema := collections.NewSchemaBuilder(storeService) im := keeper.NewDisputesIndex(schema) index := im.IndexesList() - require.NotNil(k.T(), index) + require.NotNil(s.T(), index) +} + +func (s *KeeperTestSuite) TestNewDisputesIndex() { + storeService, _ := colltest.MockStore() + schema := collections.NewSchemaBuilder(storeService) + disputesIndex := keeper.NewDisputesIndex(schema) + require.NotNil(s.T(), disputesIndex) + require.NotNil(s.T(), disputesIndex.OpenDisputes) + require.NotNil(s.T(), disputesIndex.DisputeByReporter) +} + +func (s *KeeperTestSuite) TestIndexesList_Voters() { + storeService, _ := colltest.MockStore() + schema := collections.NewSchemaBuilder(storeService) + + votersIndex := keeper.NewVotersIndex(schema) + index := votersIndex.IndexesList() + require.NotNil(s.T(), index) } // func (k *KeeperTestSuite) TestNewDisputesIndex() { @@ -68,3 +86,11 @@ func (k *KeeperTestSuite) TestIndexesList() { // ) // k.Equal(expectedOpenDisputesIndex, im.OpenDisputes) // } + +func (s *KeeperTestSuite) TestNewVotersIndex() { + storeService, _ := colltest.MockStore() + schema := collections.NewSchemaBuilder(storeService) + + votersIndex := keeper.NewVotersIndex(schema) + require.NotNil(s.T(), votersIndex) +} diff --git a/x/dispute/keeper/keeper_test.go b/x/dispute/keeper/keeper_test.go index 30a6e7463..47de3dc93 100644 --- a/x/dispute/keeper/keeper_test.go +++ b/x/dispute/keeper/keeper_test.go @@ -43,3 +43,9 @@ func (s *KeeperTestSuite) SetupTest() { func TestKeeperTestSuite(t *testing.T) { suite.Run(t, new(KeeperTestSuite)) } + +func (s *KeeperTestSuite) TestLogger() { + require := s.Require() + logger := s.disputeKeeper.Logger(s.ctx) + require.NotNil(logger) +} diff --git a/x/dispute/keeper/msg_server_vote.go b/x/dispute/keeper/msg_server_vote.go index f10d482e4..39d3dd7f1 100644 --- a/x/dispute/keeper/msg_server_vote.go +++ b/x/dispute/keeper/msg_server_vote.go @@ -58,18 +58,18 @@ func (k msgServer) Vote(goCtx context.Context, msg *types.MsgVote) (*types.MsgVo if err != nil { return nil, err } - upower = calculateVotingPower(upower, bI.TotalUserTips) + upower = CalculateVotingPower(upower, bI.TotalUserTips) repP, err := k.SetVoterReporterStake(ctx, msg.Id, voterAcc, dispute.BlockNumber) if err != nil { return nil, err } - repP = calculateVotingPower(repP, bI.TotalReporterPower) + repP = CalculateVotingPower(repP, bI.TotalReporterPower) acctBal, err := k.GetAccountBalance(ctx, voterAcc) if err != nil { return nil, err } totalSupply := k.GetTotalSupply(ctx) - voterPower := teampower.Add(upower).Add(repP).Add(calculateVotingPower(acctBal, totalSupply)) + voterPower := teampower.Add(upower).Add(repP).Add(CalculateVotingPower(acctBal, totalSupply)) if voterPower.IsZero() { return nil, errors.New("voter power is zero") } diff --git a/x/dispute/keeper/msg_tally_vote.go b/x/dispute/keeper/msg_tally_vote.go index 89de955ab..b8fd43ff0 100644 --- a/x/dispute/keeper/msg_tally_vote.go +++ b/x/dispute/keeper/msg_tally_vote.go @@ -7,7 +7,7 @@ import ( ) func (k msgServer) TallyVote(ctx context.Context, msg *types.MsgTallyVote) (*types.MsgTallyVoteResponse, error) { - err := k.Keeper.Tallyvote(ctx, msg.DisputeId) + err := k.Keeper.TallyVote(ctx, msg.DisputeId) if err != nil { return nil, err } diff --git a/x/dispute/keeper/msg_tally_vote_test.go b/x/dispute/keeper/msg_tally_vote_test.go index 729e4d6d9..13aa14c4c 100644 --- a/x/dispute/keeper/msg_tally_vote_test.go +++ b/x/dispute/keeper/msg_tally_vote_test.go @@ -1,15 +1,78 @@ package keeper_test -import "github.com/tellor-io/layer/x/dispute/types" +import ( + "github.com/tellor-io/layer/testutil/sample" + layertypes "github.com/tellor-io/layer/types" + "github.com/tellor-io/layer/x/dispute/types" + + "cosmossdk.io/collections" + "cosmossdk.io/math" + + sdk "github.com/cosmos/cosmos-sdk/types" +) func (s *KeeperTestSuite) TestMsgTallyVote() { require := s.Require() - require.NotNil(s.msgServer) - require.NotNil(s.ctx) + msgServer := s.msgServer + ctx := s.ctx + k := s.disputeKeeper + bk := s.bankKeeper - _, err := s.msgServer.TallyVote(s.ctx, &types.MsgTallyVote{ + res, err := msgServer.TallyVote(s.ctx, &types.MsgTallyVote{ CallerAddress: "caller_address", DisputeId: uint64(1), }) require.Error(err) + require.Nil(res) + + id1 := uint64(1) + userAddr := sample.AccAddressBytes() + tokenHolderAddr := sample.AccAddressBytes() + reporterAddr := sample.AccAddressBytes() + teamAddr, err := k.GetTeamAddress(ctx) + require.NoError(err) + + require.NoError(k.Votes.Set(ctx, id1, types.Vote{ + Id: id1, + VoteResult: types.VoteResult_NO_TALLY, + Executed: false, + })) + + require.NoError(k.Disputes.Set(ctx, id1, types.Dispute{ + DisputeId: id1, + HashId: []byte("hashId"), + })) + + require.NoError(k.BlockInfo.Set(ctx, []byte("hashId"), types.BlockInfo{ + TotalReporterPower: math.NewInt(250 * 1e6), + TotalUserTips: math.NewInt(250 * 1e6), + })) + bk.On("GetSupply", ctx, layertypes.BondDenom).Return(sdk.Coin{Denom: layertypes.BondDenom, Amount: math.NewInt(250 * 1e6)}, nil) + bk.On("GetSupply", ctx, layertypes.BondDenom).Return(sdk.Coin{Denom: layertypes.BondDenom, Amount: math.NewInt(250 * 1e6)}, nil) + require.ErrorContains(k.TallyVote(ctx, id1), "vote period not ended and quorum not reached") + + // set team vote (25% support) + require.NoError(k.Voter.Set(ctx, collections.Join(id1, teamAddr.Bytes()), types.Voter{Vote: types.VoteEnum_VOTE_SUPPORT, VoterPower: math.NewInt(250 * 1e6)})) + _, err = k.SetTeamVote(ctx, id1, teamAddr) + require.NoError(err) + bk.On("GetBalance", ctx, teamAddr, layertypes.BondDenom).Return(sdk.NewCoin(layertypes.BondDenom, math.NewInt(250*1e6)), nil).Once() + bk.On("GetBalance", ctx, userAddr, layertypes.BondDenom).Return(sdk.NewCoin(layertypes.BondDenom, math.NewInt(250*1e6)), nil).Once() + bk.On("GetBalance", ctx, reporterAddr, layertypes.BondDenom).Return(sdk.NewCoin(layertypes.BondDenom, math.NewInt(250*1e6)), nil).Once() + bk.On("GetBalance", ctx, tokenHolderAddr, layertypes.BondDenom).Return(sdk.NewCoin(layertypes.BondDenom, math.NewInt(250*1e6)), nil).Once() + require.Error(k.TallyVote(ctx, id1)) + + // set user vote (25% support) + require.NoError(k.UsersGroup.Set(ctx, collections.Join(id1, userAddr.Bytes()), math.NewInt(250*1e6))) + require.NoError(k.Voter.Set(ctx, collections.Join(id1, userAddr.Bytes()), types.Voter{Vote: types.VoteEnum_VOTE_SUPPORT, VoterPower: math.NewInt(250 * 1e6)})) + + // set reporter vote (25% support) + require.NoError(k.ReportersGroup.Set(ctx, collections.Join(id1, reporterAddr.Bytes()), math.NewInt(250*1e6))) + require.NoError(k.Voter.Set(ctx, collections.Join(id1, reporterAddr.Bytes()), types.Voter{Vote: types.VoteEnum_VOTE_SUPPORT, VoterPower: math.NewInt(250 * 1e6)})) + + res, err = msgServer.TallyVote(s.ctx, &types.MsgTallyVote{ + CallerAddress: "caller_address", + DisputeId: uint64(1), + }) + require.NoError(err) + require.NotNil(res) } diff --git a/x/dispute/keeper/msg_update_team_test.go b/x/dispute/keeper/msg_update_team_test.go new file mode 100644 index 000000000..80df64951 --- /dev/null +++ b/x/dispute/keeper/msg_update_team_test.go @@ -0,0 +1,55 @@ +package keeper_test + +import ( + "encoding/hex" + + "github.com/tellor-io/layer/testutil/sample" + "github.com/tellor-io/layer/x/dispute/types" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +func (s *KeeperTestSuite) TestMsgUpdateTeam() { + require := s.Require() + k := s.disputeKeeper + ctx := s.ctx + msgServer := s.msgServer + + params, err := k.Params.Get(ctx) + require.NoError(err) + oldAddr := params.TeamAddress + oldAccAddr := sdk.AccAddress(oldAddr) + newAddr := sample.AccAddress() + + res, err := msgServer.UpdateTeam(ctx, &types.MsgUpdateTeam{ + CurrentTeamAddress: oldAccAddr.String(), + NewTeamAddress: newAddr, + }) + require.NoError(err) + require.NotNil(res) + + // try to call from invalid current address + res, err = msgServer.UpdateTeam(ctx, &types.MsgUpdateTeam{ + CurrentTeamAddress: hex.EncodeToString(oldAddr), + NewTeamAddress: newAddr, + }) + require.Error(err) + require.Nil(res) + + // try to call from wrong current address + wrongAddr := sample.AccAddressBytes() + res, err = msgServer.UpdateTeam(ctx, &types.MsgUpdateTeam{ + CurrentTeamAddress: wrongAddr.String(), + NewTeamAddress: newAddr, + }) + require.Error(err) + require.Nil(res) + + // try to update to invalid Addr + res, err = msgServer.UpdateTeam(ctx, &types.MsgUpdateTeam{ + CurrentTeamAddress: newAddr, + NewTeamAddress: hex.EncodeToString(oldAddr), + }) + require.Error(err) + require.Nil(res) +} diff --git a/x/dispute/keeper/tally.go b/x/dispute/keeper/tally.go index c2dda4544..0908f9485 100644 --- a/x/dispute/keeper/tally.go +++ b/x/dispute/keeper/tally.go @@ -39,7 +39,8 @@ func (k Keeper) GetTotalSupply(ctx context.Context) math.Int { return k.bankKeeper.GetSupply(ctx, layertypes.BondDenom).Amount } -func ratio(total, part math.Int) math.LegacyDec { +// The `Ratio` function calculates the percentage ratio of `part` to `total`, scaled by a factor of 4 for the total before calculation. The result is expressed as a percentage. +func Ratio(total, part math.Int) math.LegacyDec { if total.IsZero() { return math.LegacyZeroDec() } @@ -48,7 +49,8 @@ func ratio(total, part math.Int) math.LegacyDec { return ratio.MulInt64(100) } -func calculateVotingPower(n, d math.Int) math.Int { +// CalculateVotingPower calculates the voting power of a given number (n) divided by another number (d). +func CalculateVotingPower(n, d math.Int) math.Int { if n.IsZero() || d.IsZero() { return math.ZeroInt() } @@ -56,7 +58,8 @@ func calculateVotingPower(n, d math.Int) math.Int { return n.Mul(scalingFactor).Quo(d).MulRaw(25_000_000).Quo(scalingFactor) } -func (k Keeper) Tallyvote(ctx context.Context, id uint64) error { +// CalculateVotingPower calculates the voting power of a given number (n) divided by another number (d). +func (k Keeper) TallyVote(ctx context.Context, id uint64) error { numGroups := math.LegacyNewDec(4) scaledSupport := math.LegacyZeroDec() scaledAgainst := math.LegacyZeroDec() @@ -81,9 +84,9 @@ func (k Keeper) Tallyvote(ctx context.Context, id uint64) error { totalRatio := math.LegacyZeroDec() // init tallies tallies := types.Tally{ - ForVotes: k.initVoterClasses(), - AgainstVotes: k.initVoterClasses(), - Invalid: k.initVoterClasses(), + ForVotes: k.InitVoterClasses(), + AgainstVotes: k.InitVoterClasses(), + Invalid: k.InitVoterClasses(), } teamVote, err := k.TeamVote(ctx, id) @@ -138,7 +141,7 @@ func (k Keeper) Tallyvote(ctx context.Context, id uint64) error { } if userVoteSum.GT(math.ZeroInt()) { - totalRatio = totalRatio.Add(ratio(info.TotalUserTips, userVoteSum)) + totalRatio = totalRatio.Add(Ratio(info.TotalUserTips, userVoteSum)) userVoteSumDec := math.LegacyNewDecFromInt(userVoteSum) @@ -164,7 +167,7 @@ func (k Keeper) Tallyvote(ctx context.Context, id uint64) error { tallies.Invalid.Reporters = tallies.Invalid.Reporters.Add(value) } reporterVoteSum = reporterVoteSum.Add(value) - reporterRatio = reporterRatio.Add(ratio(info.TotalReporterPower, reporterVoteSum)) + reporterRatio = reporterRatio.Add(Ratio(info.TotalReporterPower, reporterVoteSum)) totalRatio = totalRatio.Add(reporterRatio) if totalRatio.GTE(math.LegacyNewDec(51)) { return true, nil @@ -217,7 +220,7 @@ func (k Keeper) Tallyvote(ctx context.Context, id uint64) error { } tokenHolderVoteSum = tokenHolderVoteSum.Add(tkHol) - totalRatio = totalRatio.Add(ratio(tokenSupply, tokenHolderVoteSum)) + totalRatio = totalRatio.Add(Ratio(tokenSupply, tokenHolderVoteSum)) if totalRatio.GTE(math.LegacyNewDec(51)) { break diff --git a/x/dispute/keeper/tally_test.go b/x/dispute/keeper/tally_test.go new file mode 100644 index 000000000..1b41a9c8a --- /dev/null +++ b/x/dispute/keeper/tally_test.go @@ -0,0 +1,289 @@ +package keeper_test + +import ( + "time" + + "github.com/tellor-io/layer/testutil/sample" + layertypes "github.com/tellor-io/layer/types" + disputekeeper "github.com/tellor-io/layer/x/dispute/keeper" + "github.com/tellor-io/layer/x/dispute/types" + + "cosmossdk.io/collections" + "cosmossdk.io/math" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +func (s *KeeperTestSuite) TestGetVoters() { + require := s.Require() + ctx := s.ctx + k := s.disputeKeeper + require.NotNil(k) + require.NotNil(ctx) + + res, err := s.disputeKeeper.GetVoters(ctx, 1) + require.Empty(res) + require.NoError(err) + + voter := sample.AccAddressBytes() + require.NoError(k.Voter.Set(ctx, collections.Join(uint64(1), voter.Bytes()), types.Voter{Vote: types.VoteEnum_VOTE_SUPPORT, VoterPower: math.OneInt()})) + + res, err = s.disputeKeeper.GetVoters(ctx, 1) + require.NoError(err) + require.Equal(res[0].Value.Vote, types.VoteEnum_VOTE_SUPPORT) + require.Equal(res[0].Value.VoterPower, math.OneInt()) + + voter2 := sample.AccAddressBytes() + require.NoError(k.Voter.Set(ctx, collections.Join(uint64(1), voter2.Bytes()), types.Voter{Vote: types.VoteEnum_VOTE_SUPPORT, VoterPower: math.OneInt()})) + + res, err = s.disputeKeeper.GetVoters(ctx, 1) + require.NoError(err) + require.Equal(res[0].Value.Vote, types.VoteEnum_VOTE_SUPPORT) + require.Equal(res[0].Value.VoterPower, math.OneInt()) + require.Equal(res[1].Value.Vote, types.VoteEnum_VOTE_SUPPORT) + require.Equal(res[1].Value.VoterPower, math.OneInt()) +} + +func (s *KeeperTestSuite) TestGetAccountBalance() { + require := s.Require() + ctx := s.ctx + k := s.disputeKeeper + require.NotNil(k) + require.NotNil(ctx) + + addr := sample.AccAddressBytes() + s.bankKeeper.On("GetBalance", ctx, addr, layertypes.BondDenom).Return(sdk.NewCoin(layertypes.BondDenom, math.NewInt(100)), nil) + + balance, err := k.GetAccountBalance(ctx, addr) + require.NoError(err) + require.Equal(balance, math.NewInt(100)) +} + +func (s *KeeperTestSuite) TestGetTotalSupply() { + require := s.Require() + ctx := s.ctx + k := s.disputeKeeper + require.NotNil(k) + require.NotNil(ctx) + + s.bankKeeper.On("GetSupply", ctx, layertypes.BondDenom).Return(sdk.Coin{Denom: layertypes.BondDenom, Amount: math.NewInt(1_000 * 1e6)}, nil) + + totalSupply := k.GetTotalSupply(ctx) + require.Equal(totalSupply, math.NewInt(1_000*1e6)) +} + +func (s *KeeperTestSuite) TestRatio() { + require := s.Require() + + // 10/25 --> 10/100 + ratio := disputekeeper.Ratio(math.NewInt(25), math.NewInt(10)) + require.Equal(ratio, math.LegacyNewDecWithPrec(10, 0)) + // 25/25 --> 25/100 + ratio = disputekeeper.Ratio(math.NewInt(25), math.NewInt(25)) + require.Equal(ratio, math.LegacyNewDecWithPrec(25, 0)) + // 0/25 --> 0/100 + ratio = disputekeeper.Ratio(math.NewInt(25), math.NewInt(0)) + require.Equal(ratio, math.LegacyNewDecWithPrec(0, 0)) + // 25/0 --> 100/0 + ratio = disputekeeper.Ratio(math.NewInt(0), math.NewInt(25)) + require.Equal(ratio, math.LegacyNewDecWithPrec(0, 0)) +} + +func (s *KeeperTestSuite) TestCalculateVotingPower() { + require := s.Require() + + // 100/100 --> 100/400 + votingPower := disputekeeper.CalculateVotingPower(math.NewInt(100), math.NewInt(100)) + require.Equal(votingPower, math.NewInt(25*1e6)) + // 50/100 --> 50/400 + votingPower = disputekeeper.CalculateVotingPower(math.NewInt(50), math.NewInt(100)) + require.Equal(votingPower, math.NewInt(12.5*1e6)) + // 100/0 --> 100/0 + votingPower = disputekeeper.CalculateVotingPower(math.NewInt(100), math.NewInt(0)) + require.Equal(votingPower, math.NewInt(0)) + // 0/100 --> 0/400 + votingPower = disputekeeper.CalculateVotingPower(math.NewInt(0), math.NewInt(100)) + require.Equal(votingPower, math.NewInt(0)) +} + +func (s *KeeperTestSuite) TestTallyVoteQuorumReachedWithoutTokenHolders() { + require := s.Require() + ctx := s.ctx + k := s.disputeKeeper + bk := s.bankKeeper + + id1 := uint64(1) + userAddr := sample.AccAddressBytes() + tokenHolderAddr := sample.AccAddressBytes() + reporterAddr := sample.AccAddressBytes() + teamAddr, err := k.GetTeamAddress(ctx) + require.NoError(err) + + require.Error(k.TallyVote(ctx, id1)) + + require.NoError(k.Votes.Set(ctx, id1, types.Vote{ + Id: id1, + VoteResult: types.VoteResult_NO_TALLY, + Executed: false, + })) + require.Error(k.TallyVote(ctx, id1)) + + require.NoError(k.Disputes.Set(ctx, id1, types.Dispute{ + DisputeId: id1, + HashId: []byte("hashId"), + })) + require.Error(k.TallyVote(ctx, id1)) + + require.NoError(k.BlockInfo.Set(ctx, []byte("hashId"), types.BlockInfo{ + TotalReporterPower: math.NewInt(250 * 1e6), + TotalUserTips: math.NewInt(250 * 1e6), + })) + bk.On("GetSupply", ctx, layertypes.BondDenom).Return(sdk.Coin{Denom: layertypes.BondDenom, Amount: math.NewInt(250 * 1e6)}, nil) + require.ErrorContains(k.TallyVote(ctx, id1), "vote period not ended and quorum not reached") + + // set team vote (25% support) + require.NoError(k.Voter.Set(ctx, collections.Join(id1, teamAddr.Bytes()), types.Voter{Vote: types.VoteEnum_VOTE_SUPPORT, VoterPower: math.NewInt(250 * 1e6)})) + _, err = k.SetTeamVote(ctx, id1, teamAddr) + require.NoError(err) + bk.On("GetBalance", ctx, teamAddr, layertypes.BondDenom).Return(sdk.NewCoin(layertypes.BondDenom, math.NewInt(250*1e6)), nil).Once() + bk.On("GetBalance", ctx, userAddr, layertypes.BondDenom).Return(sdk.NewCoin(layertypes.BondDenom, math.NewInt(250*1e6)), nil).Once() + bk.On("GetBalance", ctx, reporterAddr, layertypes.BondDenom).Return(sdk.NewCoin(layertypes.BondDenom, math.NewInt(250*1e6)), nil).Once() + bk.On("GetBalance", ctx, tokenHolderAddr, layertypes.BondDenom).Return(sdk.NewCoin(layertypes.BondDenom, math.NewInt(250*1e6)), nil).Once() + require.Error(k.TallyVote(ctx, id1)) + + // set user vote (25% support) + require.NoError(k.UsersGroup.Set(ctx, collections.Join(id1, userAddr.Bytes()), math.NewInt(250*1e6))) + require.NoError(k.Voter.Set(ctx, collections.Join(id1, userAddr.Bytes()), types.Voter{Vote: types.VoteEnum_VOTE_SUPPORT, VoterPower: math.NewInt(250 * 1e6)})) + + // set reporter vote (25% support) + require.NoError(k.ReportersGroup.Set(ctx, collections.Join(id1, reporterAddr.Bytes()), math.NewInt(250*1e6))) + require.NoError(k.Voter.Set(ctx, collections.Join(id1, reporterAddr.Bytes()), types.Voter{Vote: types.VoteEnum_VOTE_SUPPORT, VoterPower: math.NewInt(250 * 1e6)})) + + // Vote ends with 75% Support + require.NoError(k.TallyVote(ctx, id1)) + dispute, err := k.Disputes.Get(ctx, id1) + require.NoError(err) + require.Equal(dispute.DisputeStatus, types.Resolved) + require.Equal(dispute.DisputeId, id1) + require.Equal(dispute.HashId, []byte("hashId")) + require.Equal(dispute.Open, false) +} + +func (s *KeeperTestSuite) TestTallyVoteQuorumReachedWithTokenHolders() { + require := s.Require() + ctx := s.ctx + k := s.disputeKeeper + bk := s.bankKeeper + + id1 := uint64(1) + userAddr := sample.AccAddressBytes() + tokenHolderAddr := sample.AccAddressBytes() + reporterAddr := sample.AccAddressBytes() + teamAddr, err := k.GetTeamAddress(ctx) + require.NoError(err) + + require.NoError(k.Votes.Set(ctx, id1, types.Vote{ + Id: id1, + VoteResult: types.VoteResult_NO_TALLY, + Executed: false, + })) + + require.NoError(k.Disputes.Set(ctx, id1, types.Dispute{ + DisputeId: id1, + HashId: []byte("hashId"), + })) + + require.NoError(k.BlockInfo.Set(ctx, []byte("hashId"), types.BlockInfo{ + TotalReporterPower: math.NewInt(250 * 1e6), + TotalUserTips: math.NewInt(250 * 1e6), + })) + bk.On("GetSupply", ctx, layertypes.BondDenom).Return(sdk.Coin{Denom: layertypes.BondDenom, Amount: math.NewInt(250 * 1e6)}, nil) + + // set team vote (25% support) + require.NoError(k.Voter.Set(ctx, collections.Join(id1, teamAddr.Bytes()), types.Voter{Vote: types.VoteEnum_VOTE_AGAINST, VoterPower: math.NewInt(250 * 1e6)})) + _, err = k.SetTeamVote(ctx, id1, teamAddr) + require.NoError(err) + bk.On("GetBalance", ctx, teamAddr, layertypes.BondDenom).Return(sdk.NewCoin(layertypes.BondDenom, math.NewInt(250*1e6)), nil).Once() + bk.On("GetBalance", ctx, userAddr, layertypes.BondDenom).Return(sdk.NewCoin(layertypes.BondDenom, math.NewInt(250*1e6)), nil).Once() + bk.On("GetBalance", ctx, reporterAddr, layertypes.BondDenom).Return(sdk.NewCoin(layertypes.BondDenom, math.NewInt(250*1e6)), nil).Once() + bk.On("GetBalance", ctx, tokenHolderAddr, layertypes.BondDenom).Return(sdk.NewCoin(layertypes.BondDenom, math.NewInt(250*1e6)), nil).Once() + + // set user vote (25% support) + require.NoError(k.UsersGroup.Set(ctx, collections.Join(id1, userAddr.Bytes()), math.NewInt(250*1e6))) + require.NoError(k.Voter.Set(ctx, collections.Join(id1, userAddr.Bytes()), types.Voter{Vote: types.VoteEnum_VOTE_AGAINST, VoterPower: math.NewInt(250 * 1e6)})) + + // set reporter vote (25% support) + require.NoError(k.ReportersGroup.Set(ctx, collections.Join(id1, reporterAddr.Bytes()), math.NewInt(1*1e6))) + require.NoError(k.Voter.Set(ctx, collections.Join(id1, reporterAddr.Bytes()), types.Voter{Vote: types.VoteEnum_VOTE_AGAINST, VoterPower: math.NewInt(1 * 1e6)})) + + // set token holder vote + require.NoError(k.Voter.Set(ctx, collections.Join(id1, tokenHolderAddr.Bytes()), types.Voter{Vote: types.VoteEnum_VOTE_AGAINST, VoterPower: math.NewInt(250 * 1e6)})) + + require.NoError(k.TallyVote(ctx, id1)) + dispute, err := k.Disputes.Get(ctx, id1) + require.NoError(err) + require.Equal(dispute.DisputeStatus, types.Resolved) + require.Equal(dispute.DisputeId, id1) + require.Equal(dispute.HashId, []byte("hashId")) + require.Equal(dispute.Open, false) +} + +func (s *KeeperTestSuite) TestTallyVoteQuorumNotReachedVotePeriodEnds() { + require := s.Require() + ctx := s.ctx + k := s.disputeKeeper + bk := s.bankKeeper + + id1 := uint64(1) + userAddr := sample.AccAddressBytes() + tokenHolderAddr := sample.AccAddressBytes() + reporterAddr := sample.AccAddressBytes() + teamAddr, err := k.GetTeamAddress(ctx) + require.NoError(err) + + require.NoError(k.Votes.Set(ctx, id1, types.Vote{ + Id: id1, + VoteResult: types.VoteResult_NO_TALLY, + Executed: false, + VoteEnd: ctx.BlockTime(), + })) + + require.NoError(k.Disputes.Set(ctx, id1, types.Dispute{ + DisputeId: id1, + HashId: []byte("hashId"), + })) + + ctx = ctx.WithBlockTime(ctx.BlockTime().Add(120 * time.Hour)) + require.NoError(k.BlockInfo.Set(ctx, []byte("hashId"), types.BlockInfo{ + TotalReporterPower: math.NewInt(250 * 1e6), + TotalUserTips: math.NewInt(250 * 1e6), + })) + bk.On("GetSupply", ctx, layertypes.BondDenom).Return(sdk.Coin{Denom: layertypes.BondDenom, Amount: math.NewInt(250 * 1e6)}, nil).Once() + + // set team vote (25% invalid) + require.NoError(k.Voter.Set(ctx, collections.Join(id1, teamAddr.Bytes()), types.Voter{Vote: types.VoteEnum_VOTE_INVALID, VoterPower: math.NewInt(250 * 1e6)})) + _, err = k.SetTeamVote(ctx, id1, teamAddr) + require.NoError(err) + bk.On("GetBalance", ctx, teamAddr, layertypes.BondDenom).Return(sdk.NewCoin(layertypes.BondDenom, math.NewInt(1*1e6)), nil).Once() + bk.On("GetBalance", ctx, userAddr, layertypes.BondDenom).Return(sdk.NewCoin(layertypes.BondDenom, math.NewInt(1*1e6)), nil).Once() + bk.On("GetBalance", ctx, reporterAddr, layertypes.BondDenom).Return(sdk.NewCoin(layertypes.BondDenom, math.NewInt(1*1e6)), nil).Once() + bk.On("GetBalance", ctx, tokenHolderAddr, layertypes.BondDenom).Return(sdk.NewCoin(layertypes.BondDenom, math.NewInt(1*1e6)), nil).Once() + + // set user vote (<1 % invalid) + require.NoError(k.UsersGroup.Set(ctx, collections.Join(id1, userAddr.Bytes()), math.NewInt(1*1e6))) + require.NoError(k.Voter.Set(ctx, collections.Join(id1, userAddr.Bytes()), types.Voter{Vote: types.VoteEnum_VOTE_INVALID, VoterPower: math.NewInt(1 * 1e6)})) + + // set reporter vote (<1% invalid) + require.NoError(k.ReportersGroup.Set(ctx, collections.Join(id1, reporterAddr.Bytes()), math.NewInt(1*1e6))) + require.NoError(k.Voter.Set(ctx, collections.Join(id1, reporterAddr.Bytes()), types.Voter{Vote: types.VoteEnum_VOTE_INVALID, VoterPower: math.NewInt(1 * 1e6)})) + + // set token holder vote (<1% invalid) + require.NoError(k.Voter.Set(ctx, collections.Join(id1, tokenHolderAddr.Bytes()), types.Voter{Vote: types.VoteEnum_VOTE_INVALID, VoterPower: math.NewInt(1 * 1e6)})) + + require.NoError(k.TallyVote(ctx, id1)) + + dispute, err := k.Disputes.Get(ctx, id1) + require.NoError(err) + require.Equal(dispute.DisputeStatus, types.Resolved) + require.Equal(dispute.Open, false) +} diff --git a/x/dispute/keeper/vote.go b/x/dispute/keeper/vote.go index 62ddc5291..768ce9e48 100644 --- a/x/dispute/keeper/vote.go +++ b/x/dispute/keeper/vote.go @@ -4,6 +4,7 @@ import ( "bytes" "context" "errors" + "fmt" "github.com/tellor-io/layer/x/dispute/types" @@ -13,7 +14,7 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" ) -func (k Keeper) initVoterClasses() *types.VoterClasses { +func (k Keeper) InitVoterClasses() *types.VoterClasses { return &types.VoterClasses{ Reporters: math.ZeroInt(), TokenHolders: math.ZeroInt(), @@ -96,9 +97,11 @@ func (k Keeper) SetVoterReporterStake(ctx context.Context, id uint64, voter sdk. return math.Int{}, err } reporter := sdk.AccAddress(delegation.Reporter) - // check if reporter has voted if not store voter tokens either full if reporter or delegation amount + // Check if reporter has voted. If not, store voter tokens either full if reporter or delegation amount // this amount the amount to reduce from reporter so total amount of delegators that voted reporterTokensVoted, err := k.ReportersWithDelegatorsVotedBefore.Get(ctx, collections.Join(reporter.Bytes(), id)) + fmt.Println("reporterTokensVoted: ", reporterTokensVoted) + fmt.Println("err: ", err) if err != nil { if !errors.Is(err, collections.ErrNotFound) { return math.Int{}, err @@ -118,6 +121,7 @@ func (k Keeper) SetVoterReporterStake(ctx context.Context, id uint64, voter sdk. if err != nil { return math.Int{}, err } + fmt.Println("exists: ", exists) if exists { // get reporter tokens and reduce the amount reporterTokens, err := k.ReportersGroup.Get(ctx, collections.Join(id, reporter.Bytes())) diff --git a/x/dispute/keeper/vote_test.go b/x/dispute/keeper/vote_test.go new file mode 100644 index 000000000..94400710b --- /dev/null +++ b/x/dispute/keeper/vote_test.go @@ -0,0 +1,390 @@ +package keeper_test + +import ( + "errors" + "time" + + "github.com/tellor-io/layer/testutil/sample" + "github.com/tellor-io/layer/x/dispute/types" + reportertypes "github.com/tellor-io/layer/x/reporter/types" + + "cosmossdk.io/collections" + "cosmossdk.io/math" +) + +func (s *KeeperTestSuite) TestInitVoterClasses() { + require := s.Require() + k := s.disputeKeeper + + classes := k.InitVoterClasses() + require.True(classes.Users.IsZero()) + require.True(classes.Reporters.IsZero()) + require.True(classes.Team.IsZero()) + require.True(classes.TokenHolders.IsZero()) +} + +func (s *KeeperTestSuite) TestSetStartVote() { + require := s.Require() + k := s.disputeKeeper + ctx := s.ctx + + exptectedStartTime := ctx.BlockTime() + expectedEndTime := exptectedStartTime.Add(time.Hour * 48) + require.NoError(k.SetStartVote(s.ctx, 1)) + + vote, err := k.Votes.Get(s.ctx, 1) + require.NoError(err) + require.Equal(vote.VoteStart, exptectedStartTime) + require.Equal(vote.VoteEnd, expectedEndTime) + require.Equal(vote.Id, uint64(1)) +} + +func (s *KeeperTestSuite) TestTeamVote() { + require := s.Require() + k := s.disputeKeeper + ctx := s.ctx + + teamTally, err := k.TeamVote(ctx, 1) + require.NoError(err) + require.Equal(teamTally, math.NewInt(0)) + + teamAddr, err := k.GetTeamAddress(ctx) + require.NoError(err) + tally, err := k.SetTeamVote(ctx, 1, teamAddr) + require.NoError(err) + require.Equal(tally, math.NewInt(25000000)) + tally, err = k.SetTeamVote(ctx, 1, sample.AccAddressBytes()) + require.NoError(err) + require.Equal(tally, math.NewInt(0)) + + teamTally, err = k.TeamVote(ctx, 1) + require.NoError(err) + require.Equal(teamTally, math.NewInt(1)) +} + +func (s *KeeperTestSuite) TestGetUserTotalTips() { + require := s.Require() + k := s.disputeKeeper + ctx := s.ctx + + voter := sample.AccAddressBytes() + s.oracleKeeper.On("GetTipsAtBlockForTipper", ctx, int64(1), voter).Return(math.NewInt(100), nil).Once() + + userTips, err := k.GetUserTotalTips(ctx, voter, 1) + require.NoError(err) + require.Equal(userTips, math.NewInt(100)) + + s.oracleKeeper.On("GetTipsAtBlockForTipper", ctx, int64(1), voter).Return(math.NewInt(100), collections.ErrNotFound).Once() + + userTips, err = k.GetUserTotalTips(ctx, voter, 1) + require.NoError(err) + require.Equal(userTips, math.NewInt(0)) + + s.oracleKeeper.On("GetTipsAtBlockForTipper", ctx, int64(1), voter).Return(math.NewInt(100), errors.New("error")).Once() + + userTips, err = k.GetUserTotalTips(ctx, voter, 1) + require.Error(err) + require.Equal(userTips, math.Int{}) +} + +func (s *KeeperTestSuite) TestSetVoterTips() { + require := s.Require() + k := s.disputeKeeper + ctx := s.ctx + + voter := sample.AccAddressBytes() + s.oracleKeeper.On("GetTipsAtBlockForTipper", ctx, int64(1), voter).Return(math.NewInt(100), nil).Once() + tips, err := k.SetVoterTips(ctx, uint64(1), voter, 1) + require.NoError(err) + require.Equal(tips, math.NewInt(100)) + + s.oracleKeeper.On("GetTipsAtBlockForTipper", ctx, int64(1), voter).Return(math.NewInt(0), nil).Once() + tips, err = k.SetVoterTips(ctx, uint64(1), voter, 1) + require.NoError(err) + require.Equal(tips, math.NewInt(0)) + + s.oracleKeeper.On("GetTipsAtBlockForTipper", ctx, int64(1), voter).Return(math.NewInt(100), errors.New("error")).Once() + tips, err = k.SetVoterTips(ctx, uint64(1), voter, 1) + require.Error(err) + require.Equal(tips, math.Int{}) +} + +func (s *KeeperTestSuite) TestSetVoterReportStake() { + require := s.Require() + k := s.disputeKeeper + rk := s.reporterKeeper + ctx := s.ctx + reporter := sample.AccAddressBytes() + selector := sample.AccAddressBytes() + blockNum := int64(0) + id := uint64(1) + + // delegation not found, collections not found + rk.On("Delegation", ctx, reporter).Return(reportertypes.Selection{}, collections.ErrNotFound).Once() + reporterTokens, err := k.SetVoterReporterStake(ctx, id, reporter, blockNum) + require.NoError(err) + require.Equal(reporterTokens, math.ZeroInt()) + + // delegation not found, not collections not found err + rk.On("Delegation", ctx, reporter).Return(reportertypes.Selection{}, errors.New("error")).Once() + reporterTokens, err = k.SetVoterReporterStake(ctx, id, reporter, blockNum) + require.Error(err) + require.Equal(reporterTokens, math.Int{}) + + // delegation found, ReportersWithDelegatorsVotedBefore empty, voter is reporter, error with GetReporterTokensAtBlock + rk.On("Delegation", ctx, reporter).Return(reportertypes.Selection{ + Reporter: reporter, + }, nil).Once() + rk.On("GetReporterTokensAtBlock", ctx, reporter.Bytes(), blockNum).Return(math.NewInt(100*1e6), errors.New("error")).Once() + reporterTokens, err = k.SetVoterReporterStake(ctx, id, reporter, blockNum) + require.Error(err) + require.Equal(reporterTokens, math.Int{}) + + // delegation found, ReportersWithDelegatorsVotedBefore empty, voter is reporter, no error with GetReporterTokensAtBlock, reportersgroup does not exist + rk.On("Delegation", ctx, reporter).Return(reportertypes.Selection{ + Reporter: reporter, + }, nil).Once() + rk.On("GetReporterTokensAtBlock", ctx, reporter.Bytes(), blockNum).Return(math.NewInt(100*1e6), nil).Once() + reporterTokens, err = k.SetVoterReporterStake(ctx, id, reporter, blockNum) + require.NoError(err) + require.Equal(reporterTokens, math.NewInt(100*1e6)) + reporterTokens, err = k.ReportersGroup.Get(ctx, collections.Join(id, reporter.Bytes())) + require.NoError(err) + require.Equal(reporterTokens, math.NewInt(100*1e6)) + + // delegation found, ReportersWithDelegatorsVotedBefore empty, voter is reporter, no error with GetReporterTokensAtBlock, reportersgroup exists + rk.On("Delegation", ctx, reporter).Return(reportertypes.Selection{ + Reporter: reporter, + }, nil).Once() + rk.On("GetReporterTokensAtBlock", ctx, reporter.Bytes(), blockNum).Return(math.NewInt(100*1e6), nil).Once() + reporterTokens, err = k.SetVoterReporterStake(ctx, id, reporter, blockNum) + require.NoError(err) + require.Equal(reporterTokens, math.NewInt(100*1e6)) + reporterTokens, err = k.ReportersGroup.Get(ctx, collections.Join(id, reporter.Bytes())) + require.NoError(err) + require.Equal(reporterTokens, math.NewInt(100*1e6)) + + // delegation found, ReportersWithDelegatorsVotedBefore empty, voter is delegator, error with GetDelegatorTokensAtBlock + rk.On("Delegation", ctx, selector).Return(reportertypes.Selection{ + Reporter: reporter, + }, nil).Once() + rk.On("GetDelegatorTokensAtBlock", ctx, reporter.Bytes(), blockNum).Return(math.NewInt(50*1e6), errors.New("error")).Once() + reporterTokens, err = k.SetVoterReporterStake(ctx, id, selector, blockNum) + require.Error(err) + require.Equal(reporterTokens, math.Int{}) + + // delegation found, ReportersWithDelegatorsVotedBefore empty, voter is delegator, no error with GetDelegatorTokensAtBlock, reportersgroup set, voter not set + rk.On("Delegation", ctx, selector).Return(reportertypes.Selection{ + Reporter: reporter, + }, nil).Once() + rk.On("GetDelegatorTokensAtBlock", ctx, reporter.Bytes(), blockNum).Return(math.NewInt(50*1e6), nil).Once() + reporterTokens, err = k.SetVoterReporterStake(ctx, id, selector, blockNum) + require.Error(err) + require.Equal(reporterTokens, math.Int{}) + + // delegation found, ReportersWithDelegatorsVotedBefore empty, voter is selector, no error with GetDelegatorTokensAtBlock, reportersgroup set, voter set + rk.On("Delegation", ctx, selector).Return(reportertypes.Selection{ + Reporter: reporter, + }, nil).Once() + rk.On("GetDelegatorTokensAtBlock", ctx, reporter.Bytes(), blockNum).Return(math.NewInt(50*1e6), nil).Once() + require.NoError(k.Voter.Set(ctx, collections.Join(id, reporter.Bytes()), types.Voter{ + Vote: types.VoteEnum_VOTE_SUPPORT, + VoterPower: math.NewInt(50 * 1e6), + })) + reporterTokens, err = k.SetVoterReporterStake(ctx, id, selector, blockNum) + require.NoError(err) + require.Equal(reporterTokens, math.NewInt(50*1e6)) + + // clear ReportersGroup to get outside exists block + require.NoError(k.ReportersGroup.Remove(ctx, collections.Join(id, reporter.Bytes()))) + // delegation found, ReportersWithDelegatorsVotedBefore empty, voter is selector, no error with GetDelegatorTokensAtBlock, reportersgroup empty + rk.On("Delegation", ctx, selector).Return(reportertypes.Selection{ + Reporter: reporter, + }, nil).Once() + rk.On("GetDelegatorTokensAtBlock", ctx, reporter.Bytes(), blockNum).Return(math.NewInt(50*1e6), nil).Once() + reporterTokens, err = k.SetVoterReporterStake(ctx, id, selector, blockNum) + require.NoError(err) + require.Equal(reporterTokens, math.NewInt(50*1e6)) + // clear ReportersWithDelegatorsVotedBefore + require.NoError(k.ReportersWithDelegatorsVotedBefore.Remove(ctx, collections.Join(selector.Bytes(), id))) + + // delegation found, ReportersWithDelegatorsVotedBefore set (selector has voted), voter is reporter, error with GetReporterTokensAtBlock + rk.On("Delegation", ctx, reporter).Return(reportertypes.Selection{ + Reporter: reporter, + }, nil).Once() + require.NoError(k.ReportersWithDelegatorsVotedBefore.Set(ctx, collections.Join(selector.Bytes(), id), math.NewInt(50*1e6))) + rk.On("GetReporterTokensAtBlock", ctx, reporter.Bytes(), blockNum).Return(math.NewInt(100*1e6), errors.New("error")).Once() + reporterTokens, err = k.SetVoterReporterStake(ctx, id, reporter, blockNum) + require.Error(err) + require.Equal(reporterTokens, math.Int{}) + + // delegation found, ReportersWithDelegatorsVotedBefore set (selector has voted), voter is reporter, error with GetReporterTokensAtBlock + rk.On("Delegation", ctx, reporter).Return(reportertypes.Selection{ + Reporter: reporter, + }, nil).Once() + require.NoError(k.ReportersWithDelegatorsVotedBefore.Set(ctx, collections.Join(selector.Bytes(), id), math.NewInt(50*1e6))) + rk.On("GetReporterTokensAtBlock", ctx, reporter.Bytes(), blockNum).Return(math.NewInt(100*1e6), nil).Once() + reporterTokens, err = k.SetVoterReporterStake(ctx, id, reporter, blockNum) + require.NoError(err) + require.Equal(reporterTokens, math.NewInt(100*1e6).Sub(math.NewInt(50*1e6))) + + // delegation found, ReportersWithDelegatorsBefore set (selector has voted), voter is selector, no error with GetDelegatorTokensAtBlock, selctor has voted with all of their tokens already + rk.On("Delegation", ctx, selector).Return(reportertypes.Selection{ + Reporter: reporter, + }, nil).Once() + require.NoError(k.ReportersWithDelegatorsVotedBefore.Set(ctx, collections.Join(selector.Bytes(), id), math.NewInt(50*1e6))) + rk.On("GetDelegatorTokensAtBlock", ctx, reporter.Bytes(), blockNum).Return(math.NewInt(50*1e6), nil).Once() + reporterTokens, err = k.SetVoterReporterStake(ctx, id, selector, blockNum) + require.NoError(err) + require.Equal(reporterTokens, math.ZeroInt()) + selectorTokens, err := k.ReportersWithDelegatorsVotedBefore.Get(ctx, collections.Join(selector.Bytes(), id)) + require.NoError(err) + require.Equal(selectorTokens, math.NewInt(50*1e6)) + + // delegation found, ReportersWithDelegatorsBefore set (selector has voted), voter is selector, no error with GetDelegatorTokensAtBlock, selctor has voted already + rk.On("Delegation", ctx, selector).Return(reportertypes.Selection{ + Reporter: reporter, + }, nil).Once() + require.NoError(k.ReportersWithDelegatorsVotedBefore.Set(ctx, collections.Join(selector.Bytes(), id), math.NewInt(50*1e6))) + rk.On("GetDelegatorTokensAtBlock", ctx, reporter.Bytes(), blockNum).Return(math.NewInt(60*1e6), nil).Once() + reporterTokens, err = k.SetVoterReporterStake(ctx, id, selector, blockNum) + require.NoError(err) + require.Equal(reporterTokens, math.ZeroInt()) + selectorTokens, err = k.ReportersWithDelegatorsVotedBefore.Get(ctx, collections.Join(reporter.Bytes(), id)) + require.NoError(err) + require.Equal(selectorTokens, math.NewInt(160*1e6)) +} + +func (s *KeeperTestSuite) TestUpdateDispute() { + k := s.disputeKeeper + ctx := s.ctx + require := s.Require() + require.NotNil(k) + require.NotNil(ctx) + id := uint64(1) + dispute := types.Dispute{ + HashId: []byte("hashId"), + DisputeId: id, + DisputeCategory: types.Minor, + } + + // quorum support + vote := types.Vote{ + Id: id, + VoteResult: types.VoteResult_SUPPORT, + } + scaledSupport := math.LegacyNewDec(100) + scaledAgainst := math.LegacyNewDec(0) + scaledInvalid := math.LegacyNewDec(0) + + require.NoError(k.UpdateDispute(ctx, id, dispute, vote, scaledSupport, scaledAgainst, scaledInvalid, true)) + disputeRes, err := k.Disputes.Get(ctx, id) + require.NoError(err) + require.Equal(disputeRes.DisputeId, dispute.DisputeId) + require.Equal(disputeRes.HashId, dispute.HashId) + require.Equal(disputeRes.DisputeCategory, dispute.DisputeCategory) + voteRes, err := k.Votes.Get(ctx, id) + require.NoError(err) + require.Equal(voteRes.Id, vote.Id) + require.Equal(voteRes.VoteResult, vote.VoteResult) + + // no quorum majority support + vote = types.Vote{ + Id: id, + VoteResult: types.VoteResult_NO_QUORUM_MAJORITY_SUPPORT, + } + scaledSupport = math.LegacyNewDec(50) + scaledAgainst = math.LegacyNewDec(0) + scaledInvalid = math.LegacyNewDec(0) + + require.NoError(k.UpdateDispute(ctx, id, dispute, vote, scaledSupport, scaledAgainst, scaledInvalid, false)) + disputeRes, err = k.Disputes.Get(ctx, id) + require.NoError(err) + require.Equal(disputeRes.DisputeId, dispute.DisputeId) + require.Equal(disputeRes.HashId, dispute.HashId) + require.Equal(disputeRes.DisputeCategory, dispute.DisputeCategory) + voteRes, err = k.Votes.Get(ctx, id) + require.NoError(err) + require.Equal(voteRes.Id, vote.Id) + require.Equal(voteRes.VoteResult, vote.VoteResult) + + // quorum against + vote = types.Vote{ + Id: id, + VoteResult: types.VoteResult_AGAINST, + } + scaledSupport = math.LegacyNewDec(0) + scaledAgainst = math.LegacyNewDec(100) + scaledInvalid = math.LegacyNewDec(0) + + require.NoError(k.UpdateDispute(ctx, id, dispute, vote, scaledSupport, scaledAgainst, scaledInvalid, true)) + disputeRes, err = k.Disputes.Get(ctx, id) + require.NoError(err) + require.Equal(disputeRes.DisputeId, dispute.DisputeId) + require.Equal(disputeRes.HashId, dispute.HashId) + require.Equal(disputeRes.DisputeCategory, dispute.DisputeCategory) + voteRes, err = k.Votes.Get(ctx, id) + require.NoError(err) + require.Equal(voteRes.Id, vote.Id) + require.Equal(voteRes.VoteResult, vote.VoteResult) + + // no quorum majority against + vote = types.Vote{ + Id: id, + VoteResult: types.VoteResult_NO_QUORUM_MAJORITY_AGAINST, + } + scaledSupport = math.LegacyNewDec(0) + scaledAgainst = math.LegacyNewDec(40) + scaledInvalid = math.LegacyNewDec(0) + + require.NoError(k.UpdateDispute(ctx, id, dispute, vote, scaledSupport, scaledAgainst, scaledInvalid, false)) + disputeRes, err = k.Disputes.Get(ctx, id) + require.NoError(err) + require.Equal(disputeRes.DisputeId, dispute.DisputeId) + require.Equal(disputeRes.HashId, dispute.HashId) + require.Equal(disputeRes.DisputeCategory, dispute.DisputeCategory) + voteRes, err = k.Votes.Get(ctx, id) + require.NoError(err) + require.Equal(voteRes.Id, vote.Id) + require.Equal(voteRes.VoteResult, vote.VoteResult) + + // quorum invalid + vote = types.Vote{ + Id: id, + VoteResult: types.VoteResult_INVALID, + } + scaledSupport = math.LegacyNewDec(0) + scaledAgainst = math.LegacyNewDec(0) + scaledInvalid = math.LegacyNewDec(51) + + require.NoError(k.UpdateDispute(ctx, id, dispute, vote, scaledSupport, scaledAgainst, scaledInvalid, true)) + disputeRes, err = k.Disputes.Get(ctx, id) + require.NoError(err) + require.Equal(disputeRes.DisputeId, dispute.DisputeId) + require.Equal(disputeRes.HashId, dispute.HashId) + require.Equal(disputeRes.DisputeCategory, dispute.DisputeCategory) + voteRes, err = k.Votes.Get(ctx, id) + require.NoError(err) + require.Equal(voteRes.Id, vote.Id) + require.Equal(voteRes.VoteResult, vote.VoteResult) + + // no quorum majority invalid + vote = types.Vote{ + Id: id, + VoteResult: types.VoteResult_NO_QUORUM_MAJORITY_INVALID, + } + scaledSupport = math.LegacyNewDec(0) + scaledAgainst = math.LegacyNewDec(0) + scaledInvalid = math.LegacyNewDec(49) + + require.NoError(k.UpdateDispute(ctx, id, dispute, vote, scaledSupport, scaledAgainst, scaledInvalid, false)) + disputeRes, err = k.Disputes.Get(ctx, id) + require.NoError(err) + require.Equal(disputeRes.DisputeId, dispute.DisputeId) + require.Equal(disputeRes.HashId, dispute.HashId) + require.Equal(disputeRes.DisputeCategory, dispute.DisputeCategory) + voteRes, err = k.Votes.Get(ctx, id) + require.NoError(err) + require.Equal(voteRes.Id, vote.Id) + require.Equal(voteRes.VoteResult, vote.VoteResult) +} diff --git a/x/dispute/types/genesis_test.go b/x/dispute/types/genesis_test.go index b59e2ec41..d67f79c4b 100644 --- a/x/dispute/types/genesis_test.go +++ b/x/dispute/types/genesis_test.go @@ -1,10 +1,14 @@ package types_test import ( + "encoding/json" "testing" "github.com/stretchr/testify/require" "github.com/tellor-io/layer/x/dispute/types" + + "github.com/cosmos/cosmos-sdk/codec" + codectypes "github.com/cosmos/cosmos-sdk/codec/types" ) func TestGenesisState_Validate(t *testing.T) { @@ -39,3 +43,14 @@ func TestGenesisState_Validate(t *testing.T) { }) } } + +func TestGenesisState_GetGenesisStateFromAppState(t *testing.T) { + require := require.New(t) + + cdc := codec.NewProtoCodec(codectypes.NewInterfaceRegistry()) + genState := types.GetGenesisStateFromAppState(cdc, map[string]json.RawMessage{types.ModuleName: []byte("{}")}) + require.NotNil(genState) + + genState = types.GetGenesisStateFromAppState(cdc, nil) + require.NotNil(genState) +} diff --git a/x/dispute/types/params_test.go b/x/dispute/types/params_test.go new file mode 100644 index 000000000..27e750594 --- /dev/null +++ b/x/dispute/types/params_test.go @@ -0,0 +1,44 @@ +package types + +import ( + "testing" + + "github.com/stretchr/testify/require" + "github.com/tellor-io/layer/testutil/sample" +) + +func TestParams_NewParams(t *testing.T) { + teamAddr := sample.AccAddressBytes() + params := NewParams(teamAddr) + require.NotNil(t, params) + require.Equal(t, params.TeamAddress, teamAddr.Bytes()) +} + +func TestParams_DefaultParams(t *testing.T) { + teamAddr := DefaultTeamAddress + params := DefaultParams() + require.NotNil(t, params) + require.Equal(t, params.TeamAddress, teamAddr.Bytes()) +} + +func TestParams_ParamSetPairs(t *testing.T) { + params := DefaultParams() + require.NotNil(t, params) + + pairs := params.ParamSetPairs() + require.NotNil(t, pairs) + require.Equal(t, pairs[0].Key, KeyTeamAddress) + require.Equal(t, pairs[0].Value, ¶ms.TeamAddress) +} + +func TestParams_Validate(t *testing.T) { + params := DefaultParams() + require.NotNil(t, params) + require.NoError(t, params.Validate()) +} + +func TestParams_validateTeamAddress(t *testing.T) { + params := DefaultParams() + require.NotNil(t, params) + require.NoError(t, validateTeamAddress(params.TeamAddress)) +} diff --git a/x/mint/abci.go b/x/mint/abci.go index ad6a31133..07468cb53 100644 --- a/x/mint/abci.go +++ b/x/mint/abci.go @@ -29,15 +29,15 @@ func BeginBlocker(ctx context.Context, k keeper.Keeper) error { return nil } - if err := mintBlockProvision(ctx, k, currentTime, minter); err != nil { + if err := MintBlockProvision(ctx, k, currentTime, minter); err != nil { return err } - return setPreviousBlockTime(ctx, k, currentTime) + return SetPreviousBlockTime(ctx, k, currentTime) } // mintBlockProvision mints the block provision for the current block. -func mintBlockProvision(ctx context.Context, k keeper.Keeper, currentTime time.Time, minter types.Minter) error { +func MintBlockProvision(ctx context.Context, k keeper.Keeper, currentTime time.Time, minter types.Minter) error { if minter.PreviousBlockTime == nil { return nil } @@ -55,7 +55,7 @@ func mintBlockProvision(ctx context.Context, k keeper.Keeper, currentTime time.T return k.SendInflationaryRewards(ctx, toMintCoins) } -func setPreviousBlockTime(ctx context.Context, k keeper.Keeper, blockTime time.Time) error { +func SetPreviousBlockTime(ctx context.Context, k keeper.Keeper, blockTime time.Time) error { minter, err := k.Minter.Get(ctx) if err != nil { return err diff --git a/x/mint/abci_test.go b/x/mint/abci_test.go new file mode 100644 index 000000000..ff570a1f0 --- /dev/null +++ b/x/mint/abci_test.go @@ -0,0 +1,88 @@ +package mint_test + +import ( + "testing" + "time" + + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" + "github.com/tellor-io/layer/testutil/keeper" + "github.com/tellor-io/layer/x/mint" + "github.com/tellor-io/layer/x/mint/types" + + "cosmossdk.io/math" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +func TestBeginBlocker(t *testing.T) { + require := require.New(t) + + k, _, _, ctx := keeper.MintKeeper(t) + err := mint.BeginBlocker(ctx, k) + require.Error(err) + + // set minter + minter := types.DefaultMinter() + require.NoError(k.Minter.Set(ctx, minter)) + err = mint.BeginBlocker(ctx, k) + require.Nil(err) + + // Initilize minter + minter.Initialized = true + require.NoError(k.Minter.Set(ctx, minter)) + err = mint.BeginBlocker(ctx, k) + require.Nil(err) + + // advance time past 0 + ctx = ctx.WithBlockTime(time.Unix(1000, 0)) + err = mint.BeginBlocker(ctx, k) + require.Nil(err) + + minter, err = k.Minter.Get(ctx) + require.Nil(err) + require.Equal(minter.PreviousBlockTime.Unix(), time.Unix(1000, 0).Unix()) +} + +func TestMintBlockProvision(t *testing.T) { + require := require.New(t) + + k, _, bk, ctx := keeper.MintKeeper(t) + minter := types.DefaultMinter() + minter.Initialized = true + + // prev block time is 0 + require.NoError(k.Minter.Set(ctx, minter)) + err := mint.MintBlockProvision(ctx, k, time.Unix(1000, 0), minter) + require.Nil(err) + + // prev block time 5 sec ago + time5SecAgo := time.Now().Add(-5 * time.Second) + minter.PreviousBlockTime = &time5SecAgo + require.NoError(k.Minter.Set(ctx, minter)) + expectedAmt := math.NewInt(types.DailyMintRate * 5 * 1000 / types.MillisecondsInDay) + bk.On("MintCoins", ctx, types.ModuleName, sdk.NewCoins(sdk.NewCoin("loya", expectedAmt))).Return(nil) + bk.On("InputOutputCoins", ctx, mock.Anything, mock.Anything).Return(nil) + + err = mint.MintBlockProvision(ctx, k, time.Now(), minter) + require.Nil(err) +} + +func TestSetPreviousBlockTime(t *testing.T) { + require := require.New(t) + + k, _, _, ctx := keeper.MintKeeper(t) + minter := types.DefaultMinter() + minter.Initialized = true + require.NoError(k.Minter.Set(ctx, minter)) + + time1 := time.Unix(1000, 0) + time2 := time.Unix(2000, 0) + ctx = ctx.WithBlockTime(time1) + err := mint.SetPreviousBlockTime(ctx, k, time2) + require.Nil(err) + + minter, err = k.Minter.Get(ctx) + require.Nil(err) + require.Equal(minter.PreviousBlockTime.Unix(), time2.Unix()) +} diff --git a/x/mint/keeper/genesis_test.go b/x/mint/keeper/genesis_test.go index 21a45024d..3f9d65020 100644 --- a/x/mint/keeper/genesis_test.go +++ b/x/mint/keeper/genesis_test.go @@ -9,10 +9,13 @@ import ( ) func TestGenesis(t *testing.T) { - genesisState := types.NewGenesisState("loya") + require := require.New(t) k, ak, _, ctx := keepertest.MintKeeper(t) + + genesisState := types.NewGenesisState("loya") + require.NotNil(genesisState) k.InitGenesis(ctx, ak, genesisState) got := k.ExportGenesis(ctx) - require.NotNil(t, got) - require.Equal(t, got.BondDenom, "loya") + require.NotNil(got) + require.Equal(got.BondDenom, "loya") } diff --git a/x/mint/keeper/keeper_test.go b/x/mint/keeper/keeper_test.go index 5fa66bad7..fe14db0fa 100644 --- a/x/mint/keeper/keeper_test.go +++ b/x/mint/keeper/keeper_test.go @@ -3,6 +3,7 @@ package keeper_test import ( "testing" + "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" "github.com/tellor-io/layer/app/config" keepertest "github.com/tellor-io/layer/testutil/keeper" @@ -19,6 +20,7 @@ import ( auth "github.com/cosmos/cosmos-sdk/x/auth" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" bank "github.com/cosmos/cosmos-sdk/x/bank" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" staking "github.com/cosmos/cosmos-sdk/x/staking" ) @@ -41,58 +43,78 @@ func (s *KeeperTestSuite) SetupTest() { s.ctx = keepertest.MintKeeper(s.T()) } -func (s *KeeperTestSuite) TestNewKeeper(t *testing.T) { - s.SetupTest() +func TestTestSuite(t *testing.T) { + suite.Run(t, new(KeeperTestSuite)) +} - s.accountKeeper.On("GetModuleAddress", types.ModuleName).Return(authtypes.NewModuleAddress(types.ModuleName)) - s.accountKeeper.On("GetModuleAddress", types.TimeBasedRewards).Return(authtypes.NewModuleAddress(types.TimeBasedRewards)) +func (s *KeeperTestSuite) TestNewKeeper() { + ak := s.accountKeeper + ak.On("GetModuleAddress", types.ModuleName).Return(authtypes.NewModuleAddress(types.ModuleName)).Once() + ak.On("GetModuleAddress", types.TimeBasedRewards).Return(authtypes.NewModuleAddress(types.TimeBasedRewards)).Once() appCodec := moduletestutil.MakeTestEncodingConfig(auth.AppModuleBasic{}, bank.AppModuleBasic{}, staking.AppModuleBasic{}).Codec keys := storetypes.NewKVStoreKey(types.StoreKey) - - keeper := keeper.NewKeeper(appCodec, runtime.NewKVStoreService(keys), s.accountKeeper, s.bankKeeper, authtypes.NewModuleAddress(govtypes.ModuleName).String()) - s.NotNil(keeper) + authority := authtypes.NewModuleAddress(govtypes.ModuleName).String() + k := keeper.NewKeeper(appCodec, runtime.NewKVStoreService(keys), s.accountKeeper, s.bankKeeper, authority) + s.NotNil(k) + + // invalid authority + require.Panics(s.T(), func() { + keeper.NewKeeper(appCodec, runtime.NewKVStoreService(keys), s.accountKeeper, s.bankKeeper, "badAuthority") + }) } -func (s *KeeperTestSuite) TestLogger(t *testing.T) { - s.SetupTest() - +func (s *KeeperTestSuite) TestLogger() { logger := s.mintKeeper.Logger(s.ctx) s.NotNil(logger) } -func (s *KeeperTestSuite) TestGetMinter(t *testing.T) { - s.SetupTest() - - minter, _ := s.mintKeeper.Minter.Get(s.ctx) - s.ctx.Logger().Info("Minter: %v", minter) - - s.NotNil(minter) - s.Equal("loya", minter.BondDenom) -} - -func (s *KeeperTestSuite) TestSetMinter(t *testing.T) { - s.SetupTest() +func (s *KeeperTestSuite) TestMintCoins() { + require := s.Require() + bk := s.bankKeeper + coins := sdk.NewCoins(sdk.NewCoin("loya", math.NewInt(100*1e6))) - minter := types.NewMinter("loya") - s.NoError(s.mintKeeper.Minter.Set(s.ctx, minter)) + bk.On("MintCoins", s.ctx, types.ModuleName, coins).Return(nil) + err := s.mintKeeper.MintCoins(s.ctx, coins) + require.NoError(err) - returnedMinter, _ := s.mintKeeper.Minter.Get(s.ctx) - s.Equal(minter, returnedMinter) + emptyCoins := sdk.NewCoins() + bk.On("MintCoins", s.ctx, types.ModuleName, emptyCoins).Return(nil) + err = s.mintKeeper.MintCoins(s.ctx, emptyCoins) + require.Nil(err) } -func (s *KeeperTestSuite) TestMintCoins(t *testing.T) { - s.SetupTest() - coins := sdk.NewCoins(sdk.NewCoin("loya", math.NewInt(100*1e6))) - - err := s.mintKeeper.MintCoins(s.ctx, coins) - s.NoError(err) +func (s *KeeperTestSuite) TestSendInflationaryRewards() { + require := s.Require() + bk := s.bankKeeper + threeQuartersCoins := sdk.NewCoins(sdk.NewCoin("loya", math.NewInt(75*1e6))) + oneQuarterCoins := sdk.NewCoins(sdk.NewCoin("loya", math.NewInt(25*1e6))) + totalCoins := sdk.NewCoins(sdk.NewCoin("loya", math.NewInt(100*1e6))) + + input := banktypes.NewInput(authtypes.NewModuleAddress(types.ModuleName), totalCoins) + outputs := []banktypes.Output{ + { + Address: authtypes.NewModuleAddressOrBech32Address(types.TimeBasedRewards).String(), + Coins: threeQuartersCoins, + }, + { + Address: authtypes.NewModuleAddressOrBech32Address(authtypes.FeeCollectorName).String(), + Coins: oneQuarterCoins, + }, + } + bk.On("InputOutputCoins", s.ctx, input, outputs).Return(nil) + err := s.mintKeeper.SendInflationaryRewards(s.ctx, totalCoins) + require.NoError(err) + + emptyCoins := sdk.NewCoins() + err = s.mintKeeper.SendInflationaryRewards(s.ctx, emptyCoins) + require.Nil(err) } -func (s *KeeperTestSuite) TestInflationaryRewards(t *testing.T) { - s.SetupTest() - coins := sdk.NewCoins(sdk.NewCoin("loya", math.NewInt(100*1e6))) +func (s *KeeperTestSuite) TestGetAuthority() { + require := s.Require() + k := s.mintKeeper - err := s.mintKeeper.SendInflationaryRewards(s.ctx, coins) - s.NoError(err) + authority := k.GetAuthority() + require.Equal(authority, authtypes.NewModuleAddress(govtypes.ModuleName).String()) } diff --git a/x/mint/keeper/msg_server_test.go b/x/mint/keeper/msg_server_test.go new file mode 100644 index 000000000..907c3296e --- /dev/null +++ b/x/mint/keeper/msg_server_test.go @@ -0,0 +1,35 @@ +package keeper_test + +import ( + "github.com/tellor-io/layer/x/mint/keeper" + "github.com/tellor-io/layer/x/mint/types" +) + +func (s *KeeperTestSuite) TestNewMsgServerImpl() { + require := s.Require() + k := s.mintKeeper + + msgServer := keeper.NewMsgServerImpl(k) + require.NotNil(msgServer) +} + +func (s *KeeperTestSuite) TestInit() { + require := s.Require() + k := s.mintKeeper + + msgServer := keeper.NewMsgServerImpl(k) + require.NotNil(msgServer) + + // call with empty authority + res, err := msgServer.Init(s.ctx, &types.MsgInit{}) + require.Error(err) + require.Nil(res) + + // call with good authority + require.NoError(k.Minter.Set(s.ctx, types.DefaultMinter())) + res, err = msgServer.Init(s.ctx, &types.MsgInit{ + Authority: k.GetAuthority(), + }) + require.NoError(err) + require.NotNil(res) +} diff --git a/x/mint/types/genesis_test.go b/x/mint/types/genesis_test.go new file mode 100644 index 000000000..fd5f8e7ed --- /dev/null +++ b/x/mint/types/genesis_test.go @@ -0,0 +1,38 @@ +package types_test + +import ( + "testing" + + "github.com/stretchr/testify/require" + "github.com/tellor-io/layer/x/mint/types" +) + +func TestNewGenesisState(t *testing.T) { + require := require.New(t) + + genesisState := types.NewGenesisState("loya") + require.NotNil(genesisState) + require.Equal(genesisState.BondDenom, "loya") +} + +func TestDefaultGenesis(t *testing.T) { + require := require.New(t) + + genesisState := types.DefaultGenesis() + require.NotNil(genesisState) + require.Equal(genesisState.BondDenom, "loya") +} + +func TestValidateGenesis(t *testing.T) { + require := require.New(t) + + genesisState := types.DefaultGenesis() + require.NotNil(genesisState) + require.Equal(genesisState.BondDenom, "loya") + err := types.ValidateGenesis(*genesisState) + require.NoError(err) + + emptyDenomGenesis := types.NewGenesisState("") + err = types.ValidateGenesis(*emptyDenomGenesis) + require.ErrorContains(err, "bond denom cannot be empty") +} diff --git a/x/mint/types/minter_test.go b/x/mint/types/minter_test.go new file mode 100644 index 000000000..f92ebe329 --- /dev/null +++ b/x/mint/types/minter_test.go @@ -0,0 +1,70 @@ +package types_test + +import ( + "testing" + time "time" + + "github.com/stretchr/testify/require" + "github.com/tellor-io/layer/x/mint/types" + + "cosmossdk.io/math" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +func TestNewMinter(t *testing.T) { + require := require.New(t) + + minter := types.NewMinter("loya") + require.NotNil(minter) + require.Equal(minter.BondDenom, "loya") +} + +func TestDefaultMinter(t *testing.T) { + require := require.New(t) + + minter := types.DefaultMinter() + require.NotNil(minter) + require.Equal(minter.BondDenom, "loya") +} + +func TestValidate(t *testing.T) { + require := require.New(t) + + // prevBlockTime is nil + minter := types.DefaultMinter() + err := minter.Validate() + require.ErrorContains(err, "previous block time cannot be nil") + + // prevBlockTime is not nil + prevBlockTime := time.Unix(1000, 0) + minter.PreviousBlockTime = &prevBlockTime + err = minter.Validate() + require.NoError(err) + + // bondDenom is empty string + minter.BondDenom = "" + err = minter.Validate() + require.ErrorContains(err, "should not be empty string") +} + +func TestCalculateBlockProvision(t *testing.T) { + require := require.New(t) + + // zero time passed + minter := types.DefaultMinter() + blockProvision, err := minter.CalculateBlockProvision(time.Unix(1000, 0), time.Unix(1000, 0)) + require.NoError(err) + require.Equal(blockProvision.Amount, math.ZeroInt()) + + // 10 sec passed + blockProvision, err = minter.CalculateBlockProvision(time.Unix(1010, 0), time.Unix(1000, 0)) + require.NoError(err) + expectedAmt := math.NewInt(types.DailyMintRate * 10 * 1000 / types.MillisecondsInDay) + require.Equal(blockProvision.Amount, expectedAmt) + + // curren time before prev time + blockProvision, err = minter.CalculateBlockProvision(time.Unix(1000, 0), time.Unix(1010, 0)) + require.ErrorContains(err, "cannot be before previous time") + require.Equal(blockProvision.Amount, sdk.Coin{}.Amount) +} diff --git a/x/oracle/keeper/msg_server_tip.go b/x/oracle/keeper/msg_server_tip.go index 795f185e2..60278b658 100644 --- a/x/oracle/keeper/msg_server_tip.go +++ b/x/oracle/keeper/msg_server_tip.go @@ -10,6 +10,7 @@ import ( "github.com/tellor-io/layer/x/oracle/types" "cosmossdk.io/collections" + "cosmossdk.io/math" sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" @@ -27,13 +28,7 @@ func (k msgServer) Tip(goCtx context.Context, msg *types.MsgTip) (*types.MsgTipR if err != nil { return nil, err } - // update totals - if err := k.keeper.AddToTipperTotal(ctx, tipper, tip.Amount); err != nil { - return nil, err - } - if err := k.keeper.AddtoTotalTips(ctx, tip.Amount); err != nil { - return nil, err - } + // get query id bytes hash from query data queryId := utils.QueryIDFromData(msg.QueryData) @@ -44,18 +39,13 @@ func (k msgServer) Tip(goCtx context.Context, msg *types.MsgTip) (*types.MsgTipR return nil, err } // initialize query tip first time - query, err := k.keeper.initializeQuery(ctx, msg.QueryData) + query, err = k.keeper.initializeQuery(ctx, msg.QueryData) if err != nil { return nil, err } - query.Amount = tip.Amount + query.Amount = math.ZeroInt() query.Expiration = ctx.BlockTime().Add(query.RegistrySpecTimeframe) - err = k.keeper.Query.Set(ctx, queryId, query) - if err != nil { - return nil, err - } - return &types.MsgTipResponse{}, nil } prevAmt := query.Amount query.Amount = query.Amount.Add(tip.Amount) @@ -81,6 +71,13 @@ func (k msgServer) Tip(goCtx context.Context, msg *types.MsgTip) (*types.MsgTipR return nil, err } + // update totals + if err := k.keeper.AddToTipperTotal(ctx, tipper, tip.Amount); err != nil { + return nil, err + } + if err := k.keeper.AddtoTotalTips(ctx, tip.Amount); err != nil { + return nil, err + } prevTip, err := k.keeper.Tips.Get(ctx, collections.Join(queryId, tipper.Bytes())) if err != nil && !errors.Is(err, collections.ErrNotFound) { return nil, fmt.Errorf("failed to get previous tip: %w", err)