From 6994adcec652aa77f41d00be6841589c19fd3ad0 Mon Sep 17 00:00:00 2001 From: Nicholas Fett Date: Tue, 21 May 2024 11:31:12 -0400 Subject: [PATCH 1/8] npm fix" " --- evm/test/Bridge-TestsAuto.js | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/evm/test/Bridge-TestsAuto.js b/evm/test/Bridge-TestsAuto.js index 88c83a9a3..52016515e 100644 --- a/evm/test/Bridge-TestsAuto.js +++ b/evm/test/Bridge-TestsAuto.js @@ -1,17 +1,12 @@ -const { expect } = require("chai"); -const { ethers, network } = require("hardhat"); +const { ethers} = require("hardhat"); const h = require("./helpers/helpers"); var assert = require('assert'); -const web3 = require('web3'); -const { prependOnceListener } = require("process"); -const BN = ethers.BigNumber.from const abiCoder = new ethers.utils.AbiCoder(); -const axios = require('axios'); describe("BlobstreamO - Auto Function and e2e Tests", function () { - let bridge, valPower, accounts, validators, powers, initialValAddrs, + let bridge, accounts,initialValAddrs, initialPowers, threshold, valCheckpoint, valTimestamp, guardian, bridgeCaller; const UNBONDING_PERIOD = 86400 * 7 * 3; // 3 weeks @@ -39,6 +34,7 @@ describe("BlobstreamO - Auto Function and e2e Tests", function () { it("constructor", async function () { assert.equal(await bridge.powerThreshold(), threshold) + assert.equal(await bridge.guardian(), accounts[10]) assert.equal(await bridge.validatorTimestamp(), valTimestamp) assert.equal(await bridge.unbondingPeriod(), UNBONDING_PERIOD) assert.equal(await bridge.lastValidatorSetCheckpoint(), valCheckpoint) From 0403bad6e7fce47fad7de357d6ad44dceb74cd6c Mon Sep 17 00:00:00 2001 From: Nicholas Fett Date: Tue, 21 May 2024 13:55:06 -0400 Subject: [PATCH 2/8] alphabetize --- evm/README.md | 22 +++++++++ evm/contracts/bridge/BlobstreamO.sol | 52 +++++++++++----------- evm/contracts/bridge/Constants.sol | 9 ++-- evm/contracts/bridge/ECDSA.sol | 1 - evm/contracts/interfaces/IERC20.sol | 9 ++++ evm/contracts/token-bridge/TokenBridge.sol | 18 +++----- evm/contracts/usingtellor/UsingTellor.sol | 52 +++++++++++----------- 7 files changed, 94 insertions(+), 69 deletions(-) create mode 100644 evm/README.md create mode 100644 evm/contracts/interfaces/IERC20.sol diff --git a/evm/README.md b/evm/README.md new file mode 100644 index 000000000..d001d583b --- /dev/null +++ b/evm/README.md @@ -0,0 +1,22 @@ +# Tellor EVM Contracts + +## Pieces + + +### Data Bridge + +### Forking Contract Examples + +### Token Bridge + +### UsingTellor + + + +## Compiling Testing + + +``` +npx hardhat compile +npx hardhat test +``` \ No newline at end of file diff --git a/evm/contracts/bridge/BlobstreamO.sol b/evm/contracts/bridge/BlobstreamO.sol index 602b63192..565bfeb6c 100644 --- a/evm/contracts/bridge/BlobstreamO.sol +++ b/evm/contracts/bridge/BlobstreamO.sol @@ -4,15 +4,10 @@ pragma solidity 0.8.22; import "./ECDSA.sol"; import "./Constants.sol"; -struct Validator { - address addr; - uint256 power; -} - -struct Signature { - uint8 v; - bytes32 r; - bytes32 s; +struct OracleAttestationData { + bytes32 queryId; + ReportData report; + uint256 attestationTimestamp; } struct ReportData { @@ -23,40 +18,44 @@ struct ReportData { uint256 nextTimestamp; } -struct OracleAttestationData { - bytes32 queryId; - ReportData report; - uint256 attestationTimestamp; +struct Signature { + uint8 v; + bytes32 r; + bytes32 s; } +struct Validator { + address addr; + uint256 power; +} + + /// @title BlobstreamO: Tellor Layer -> EVM, Oracle relay. /// @dev The relay relies on a set of signers to attest to some event on /// Tellor Layer. These signers are the validator set, who sign over every /// block. At least 2/3 of the voting power of the current /// view of the validator set must sign off on new relayed events. contract BlobstreamO is ECDSA { + /*Storage*/ + address public guardian; /// Able to reset the validator set only if the validator set becomes stale. bytes32 public lastValidatorSetCheckpoint; ///Domain-separated commitment to the latest validator set. uint256 public powerThreshold; /// Voting power required to submit a new update. - uint256 public validatorTimestamp; /// Timestamp of the block where validator set is updated. uint256 public unbondingPeriod; /// Time period after which a validator can withdraw their stake. - address public guardian; /// Able to reset the validator set only if the validator set becomes stale. + uint256 public validatorTimestamp; /// Timestamp of the block where validator set is updated. + /*Events*/ - event ValidatorSetUpdated( - uint256 _powerThreshold, - uint256 _validatorTimestamp, - bytes32 _validatorSetHash - ); + event ValidatorSetUpdated(uint256 _powerThreshold, uint256 _validatorTimestamp, bytes32 _validatorSetHash); /*Errors*/ error InsufficientVotingPower(); error InvalidSignature(); error MalformedCurrentValidatorSet(); + error NotConsensusValue(); error NotGuardian(); error StaleValidatorSet(); error SuppliedValidatorSetInvalid(); error ValidatorSetNotStale(); - error NotConsensusValue(); /*Functions*/ /// @param _powerThreshold Initial voting power that is needed to approve operations @@ -152,25 +151,26 @@ contract BlobstreamO is ECDSA { } /*Getter functions*/ - function verifyOracleData( + function verifyConsensusOracleData( OracleAttestationData calldata _attest, Validator[] calldata _currentValidatorSet, Signature[] calldata _sigs ) external view returns (bool) { + if (_attest.report.aggregatePower < powerThreshold) { + revert NotConsensusValue(); + } return _verifyOracleData(_attest, _currentValidatorSet, _sigs); } - function verifyConsensusOracleData( + function verifyOracleData( OracleAttestationData calldata _attest, Validator[] calldata _currentValidatorSet, Signature[] calldata _sigs ) external view returns (bool) { - if (_attest.report.aggregatePower < powerThreshold) { - revert NotConsensusValue(); - } return _verifyOracleData(_attest, _currentValidatorSet, _sigs); } + /*Internal functions*/ /// @dev Checks that enough voting power signed over a digest. /// It expects the signatures to be in the same order as the _currentValidators. diff --git a/evm/contracts/bridge/Constants.sol b/evm/contracts/bridge/Constants.sol index 19b00246f..4b7fdbbd1 100644 --- a/evm/contracts/bridge/Constants.sol +++ b/evm/contracts/bridge/Constants.sol @@ -1,10 +1,6 @@ // SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.22; -/// @dev bytes32 encoding of the string "checkpoint" -bytes32 constant VALIDATOR_SET_HASH_DOMAIN_SEPARATOR = - 0x636865636b706f696e7400000000000000000000000000000000000000000000; - /// @dev bytes32 encoding of the string "transactionBatch" bytes32 constant DATA_ROOT_TUPLE_ROOT_DOMAIN_SEPARATOR = 0x7472616e73616374696f6e426174636800000000000000000000000000000000; @@ -12,4 +8,9 @@ bytes32 constant DATA_ROOT_TUPLE_ROOT_DOMAIN_SEPARATOR = /// @dev bytes32 encoding of the string "tellorNewReport" bytes32 constant NEW_REPORT_ATTESTATION_DOMAIN_SEPARATOR = 0x74656c6c6f7243757272656e744174746573746174696f6e0000000000000000; + +/// @dev bytes32 encoding of the string "checkpoint" +bytes32 constant VALIDATOR_SET_HASH_DOMAIN_SEPARATOR = + 0x636865636b706f696e7400000000000000000000000000000000000000000000; + \ No newline at end of file diff --git a/evm/contracts/bridge/ECDSA.sol b/evm/contracts/bridge/ECDSA.sol index 7b2c71bde..9c3a087b1 100644 --- a/evm/contracts/bridge/ECDSA.sol +++ b/evm/contracts/bridge/ECDSA.sol @@ -1,6 +1,5 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v5.0.0) (utils/cryptography/ECDSA.sol) - pragma solidity ^0.8.20; /** diff --git a/evm/contracts/interfaces/IERC20.sol b/evm/contracts/interfaces/IERC20.sol new file mode 100644 index 000000000..6409e377d --- /dev/null +++ b/evm/contracts/interfaces/IERC20.sol @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.22; + +interface IERC20 { + function approve(address spender, uint256 amount) external returns (bool); + function balanceOf(address account) external view returns (uint256); + function transfer(address recipient, uint256 amount) external returns (bool); + function transferFrom(address sender, address recipient, uint256 amount) external returns (bool); +} \ No newline at end of file diff --git a/evm/contracts/token-bridge/TokenBridge.sol b/evm/contracts/token-bridge/TokenBridge.sol index 288a7e84d..673aa366f 100644 --- a/evm/contracts/token-bridge/TokenBridge.sol +++ b/evm/contracts/token-bridge/TokenBridge.sol @@ -1,25 +1,19 @@ // SPDX-License-Identifier: MIT - pragma solidity 0.8.22; import "../usingtellor/UsingTellor.sol"; - -interface IERC20 { - function transfer(address recipient, uint256 amount) external returns (bool); - function balanceOf(address account) external view returns (uint256); - function transferFrom(address sender, address recipient, uint256 amount) external returns (bool); - function approve(address spender, uint256 amount) external returns (bool); -} +import "../interfaces/IERC20.sol"; contract TokenBridge is UsingTellor { IERC20 public token; + uint256 public currentDepositLimit; uint256 public depositId; - uint256 public constant MAX_ATTESTATION_AGE = 12 hours; - uint256 public immutable DEPOSIT_LIMIT_DENOMINATOR = 100e18 / 20e18; // 100/depositLimitPercentage + uint256 public depositLimitUpdateTime; uint256 public constant DEPOSIT_LIMIT_UPDATE_INTERVAL = 12 hours; uint256 public constant INITIAL_LAYER_TOKEN_SUPPLY = 100 ether; // update this as needed - uint256 public depositLimitUpdateTime; - uint256 public currentDepositLimit; + uint256 public constant MAX_ATTESTATION_AGE = 12 hours; + uint256 public immutable DEPOSIT_LIMIT_DENOMINATOR = 100e18 / 20e18; // 100/depositLimitPercentage + mapping(uint256 => bool) public withdrawalClaimed; mapping(uint256 => DepositDetails) public deposits; diff --git a/evm/contracts/usingtellor/UsingTellor.sol b/evm/contracts/usingtellor/UsingTellor.sol index 002ba4f71..497f07e57 100644 --- a/evm/contracts/usingtellor/UsingTellor.sol +++ b/evm/contracts/usingtellor/UsingTellor.sol @@ -10,18 +10,26 @@ contract UsingTellor { bridge = BlobstreamO(_blobstreamO); } - function isCurrentConsensusValue( + function getDataWithFallback( OracleAttestationData calldata _attest, Validator[] calldata _currentValidatorSet, Signature[] calldata _sigs, + uint256 _fallbackTimestamp, + uint256 _fallbackMinimumPower, // or use percentage? uint256 _maxAttestationAge - ) public view returns(bool) { - require(bridge.verifyConsensusOracleData(_attest, _currentValidatorSet, _sigs), "Invalid attestation"); + ) public view returns(bool){ + require(bridge.verifyOracleData(_attest, _currentValidatorSet, _sigs), "Invalid signature"); require(block.timestamp - _attest.attestationTimestamp <= _maxAttestationAge, "Attestation is too old"); - require(_attest.report.nextTimestamp == 0, "Report is not latest"); + if(_attest.report.aggregatePower >= bridge.powerThreshold()) { + require(_attest.report.timestamp < _fallbackTimestamp, "Report timestamp must be before _fallbackTimestamp"); + } + require(_attest.report.nextTimestamp == 0 || _attest.report.nextTimestamp > _fallbackTimestamp, "Report is latest before timestamp"); + require(_attest.report.aggregatePower >= _fallbackMinimumPower, "Report aggregate power must be greater than or equal to _minimumPower"); + require(_attest.report.nextTimestamp == 0 || _attest.report.nextTimestamp < _fallbackTimestamp, "Report is latest after fallback timestamp"); + require(_attest.report.nextTimestamp == 0 || _attest.report.nextTimestamp > _fallbackTimestamp, "Report is latest before fallback timestamp"); return true; } - + function isAnyConsensusValue( OracleAttestationData calldata _attest, Validator[] calldata _currentValidatorSet, @@ -33,21 +41,15 @@ contract UsingTellor { return true; } - function isValidDataBefore( + function isCurrentConsensusValue( OracleAttestationData calldata _attest, Validator[] calldata _currentValidatorSet, Signature[] calldata _sigs, - uint256 _timestampBefore, - uint256 _maxReportAge, - uint256 _minimumPower, uint256 _maxAttestationAge - ) public view returns(bool){ + ) public view returns(bool) { + require(bridge.verifyConsensusOracleData(_attest, _currentValidatorSet, _sigs), "Invalid attestation"); require(block.timestamp - _attest.attestationTimestamp <= _maxAttestationAge, "Attestation is too old"); - require(_attest.report.timestamp < _timestampBefore, "Report timestamp must be before _timestampBefore"); - require(_attest.report.nextTimestamp == 0 || _attest.report.nextTimestamp > _timestampBefore, "Report is latest before timestamp"); - require(_attest.report.aggregatePower >= _minimumPower, "Report aggregate power must be greater than or equal to _minimumPower"); - require(block.timestamp - _attest.report.timestamp < _maxReportAge, "Report is too old"); - require(bridge.verifyOracleData(_attest, _currentValidatorSet, _sigs), "Invalid signature"); + require(_attest.report.nextTimestamp == 0, "Report is not latest"); return true; } @@ -69,23 +71,21 @@ contract UsingTellor { return true; } - function getDataWithFallback( + function isValidDataBefore( OracleAttestationData calldata _attest, Validator[] calldata _currentValidatorSet, Signature[] calldata _sigs, - uint256 _fallbackTimestamp, - uint256 _fallbackMinimumPower, // or use percentage? + uint256 _timestampBefore, + uint256 _maxReportAge, + uint256 _minimumPower, uint256 _maxAttestationAge ) public view returns(bool){ - require(bridge.verifyOracleData(_attest, _currentValidatorSet, _sigs), "Invalid signature"); require(block.timestamp - _attest.attestationTimestamp <= _maxAttestationAge, "Attestation is too old"); - if(_attest.report.aggregatePower >= bridge.powerThreshold()) { - require(_attest.report.timestamp < _fallbackTimestamp, "Report timestamp must be before _fallbackTimestamp"); - } - require(_attest.report.nextTimestamp == 0 || _attest.report.nextTimestamp > _fallbackTimestamp, "Report is latest before timestamp"); - require(_attest.report.aggregatePower >= _fallbackMinimumPower, "Report aggregate power must be greater than or equal to _minimumPower"); - require(_attest.report.nextTimestamp == 0 || _attest.report.nextTimestamp < _fallbackTimestamp, "Report is latest after fallback timestamp"); - require(_attest.report.nextTimestamp == 0 || _attest.report.nextTimestamp > _fallbackTimestamp, "Report is latest before fallback timestamp"); + require(_attest.report.timestamp < _timestampBefore, "Report timestamp must be before _timestampBefore"); + require(_attest.report.nextTimestamp == 0 || _attest.report.nextTimestamp > _timestampBefore, "Report is latest before timestamp"); + require(_attest.report.aggregatePower >= _minimumPower, "Report aggregate power must be greater than or equal to _minimumPower"); + require(block.timestamp - _attest.report.timestamp < _maxReportAge, "Report is too old"); + require(bridge.verifyOracleData(_attest, _currentValidatorSet, _sigs), "Invalid signature"); return true; } } \ No newline at end of file From cfa513520caa33c890d17816b3026d9e4c480775 Mon Sep 17 00:00:00 2001 From: Nicholas Fett Date: Tue, 21 May 2024 14:30:18 -0400 Subject: [PATCH 3/8] new structure --- evm/contracts/bridge/BlobstreamO.sol | 8 ++++---- evm/contracts/interfaces/IBridgeProxy.sol | 4 ++-- evm/contracts/testing/TellorUser.sol | 2 +- .../{usingtellor => testing}/UsingTellor.sol | 0 evm/contracts/token-bridge/TokenBridge.sol | 17 +++++++++++------ 5 files changed, 18 insertions(+), 13 deletions(-) rename evm/contracts/{usingtellor => testing}/UsingTellor.sol (100%) diff --git a/evm/contracts/bridge/BlobstreamO.sol b/evm/contracts/bridge/BlobstreamO.sol index 565bfeb6c..48b259c0b 100644 --- a/evm/contracts/bridge/BlobstreamO.sol +++ b/evm/contracts/bridge/BlobstreamO.sol @@ -189,16 +189,16 @@ contract BlobstreamO is ECDSA { revert StaleValidatorSet(); } uint256 _cumulativePower = 0; - for (uint256 i = 0; i < _currentValidators.length; i++) { + for (uint256 _i = 0; _i < _currentValidators.length; _i++) { // If the signature is nil, then it's not present so continue. - if (_sigs[i].r == 0 && _sigs[i].s == 0 && _sigs[i].v == 0) { + if (_sigs[_i].r == 0 && _sigs[_i].s == 0 && _sigs[_i].v == 0) { continue; } // Check that the current validator has signed off on the hash. - if (!_verifySig(_currentValidators[i].addr, _digest, _sigs[i])) { + if (!_verifySig(_currentValidators[_i].addr, _digest, _sigs[_i])) { revert InvalidSignature(); } - _cumulativePower += _currentValidators[i].power; + _cumulativePower += _currentValidators[_i].power; // Break early to avoid wasting gas. if (_cumulativePower >= _powerThreshold) { break; diff --git a/evm/contracts/interfaces/IBridgeProxy.sol b/evm/contracts/interfaces/IBridgeProxy.sol index a4676b9c5..7841c11b3 100644 --- a/evm/contracts/interfaces/IBridgeProxy.sol +++ b/evm/contracts/interfaces/IBridgeProxy.sol @@ -2,8 +2,8 @@ pragma solidity 0.8.22; interface IBridgeProxy { - function paused() external returns (bool); - function updateImplementation(address _newImplementation) external; function pauseBridge() external; + function paused() external returns (bool); function unpauseBridge() external; + function updateImplementation(address _newImplementation) external; } \ No newline at end of file diff --git a/evm/contracts/testing/TellorUser.sol b/evm/contracts/testing/TellorUser.sol index 2bc02e7ac..bce18f2ef 100644 --- a/evm/contracts/testing/TellorUser.sol +++ b/evm/contracts/testing/TellorUser.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; -// import "../usingtellor/UsingTellor.sol"; +// import "./UsingTellor.sol"; // contract TellorUser is UsingTellor { // uint256 public ethPrice; diff --git a/evm/contracts/usingtellor/UsingTellor.sol b/evm/contracts/testing/UsingTellor.sol similarity index 100% rename from evm/contracts/usingtellor/UsingTellor.sol rename to evm/contracts/testing/UsingTellor.sol diff --git a/evm/contracts/token-bridge/TokenBridge.sol b/evm/contracts/token-bridge/TokenBridge.sol index 673aa366f..c994b9259 100644 --- a/evm/contracts/token-bridge/TokenBridge.sol +++ b/evm/contracts/token-bridge/TokenBridge.sol @@ -1,10 +1,11 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.22; -import "../usingtellor/UsingTellor.sol"; +import "../bridge/BlobstreamO.sol"; import "../interfaces/IERC20.sol"; -contract TokenBridge is UsingTellor { +contract TokenBridge{ + BlobstreamO public bridge; IERC20 public token; uint256 public currentDepositLimit; uint256 public depositId; @@ -24,11 +25,12 @@ contract TokenBridge is UsingTellor { uint256 blockHeight; } - event Deposit(uint256 depositId, address sender, string recipient, uint256 amount); - event Withdrawal(uint256 depositId, string sender, address recipient, uint256 amount); + event Deposit(uint256 _depositId, address _sender, string _recipient, uint256 _amount); + event Withdrawal(uint256 _depositId, string _sender, address _recipient, uint256 _amount); - constructor(address _token, address _blobstream) UsingTellor(_blobstream) { + constructor(address _token, address _blobstream){ token = IERC20(_token); + bridge = BlobstreamO(_blobstream); _depositLimit(); } @@ -51,7 +53,10 @@ contract TokenBridge is UsingTellor { require(_attest.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"); - require(isAnyConsensusValue(_attest, _valset, _sigs, MAX_ATTESTATION_AGE), "TokenBridge: invalid attestation"); + //isAnyConsesnusValue here + require(bridge.verifyConsensusOracleData(_attest, _valset, _sigs), "Invalid attestation"); + require(block.timestamp - _attest.attestationTimestamp <= MAX_ATTESTATION_AGE , "Attestation is too old"); + //to here withdrawalClaimed[_depositId] = true; (address _recipient, string memory _layerSender,uint256 _amountLoya) = abi.decode(_attest.report.value, (address, string, uint256)); uint256 _amountConverted = _amountLoya * 1e12; From b73bd5e47b17c8385288963e3f5a0a4e4aeef45d Mon Sep 17 00:00:00 2001 From: Nicholas Fett Date: Thu, 23 May 2024 14:47:52 -0400 Subject: [PATCH 4/8] comments --- evm/contracts/bridge/BlobstreamO.sol | 8 +++-- evm/contracts/{ => testing}/forking/README.md | 0 .../{ => testing}/forking/SimpleSchelling.sol | 0 .../token-bridge/LayerTransition.sol | 34 +++++++++++++++++-- evm/contracts/token-bridge/TokenBridge.sol | 31 ++++++++++++++--- evm/hardhat.config.js | 24 ++++++------- temp.json | 0 7 files changed, 75 insertions(+), 22 deletions(-) rename evm/contracts/{ => testing}/forking/README.md (100%) rename evm/contracts/{ => testing}/forking/SimpleSchelling.sol (100%) create mode 100644 temp.json diff --git a/evm/contracts/bridge/BlobstreamO.sol b/evm/contracts/bridge/BlobstreamO.sol index 55ba1c277..94c28fdbe 100644 --- a/evm/contracts/bridge/BlobstreamO.sol +++ b/evm/contracts/bridge/BlobstreamO.sol @@ -7,12 +7,12 @@ import "./Constants.sol"; struct OracleAttestationData { bytes32 queryId; ReportData report; - uint256 attestationTimestamp; + uint256 attestationTimestamp;//timestamp of validatorSignatures on report } struct ReportData { bytes value; - uint256 timestamp; + uint256 timestamp;//timestamp of reporter signature aggregation uint256 aggregatePower; uint256 previousTimestamp; uint256 nextTimestamp; @@ -151,6 +151,10 @@ contract BlobstreamO is ECDSA { } /*Getter functions*/ + /// @notice This getter verifies a given piece of data vs Validator signatures + /// @param _attestData The data being verified + /// @param _currentValdatorSet array of current validator set + /// @param _sigs Signatures. function verifyOracleData( OracleAttestationData calldata _attestData, Validator[] calldata _currentValidatorSet, diff --git a/evm/contracts/forking/README.md b/evm/contracts/testing/forking/README.md similarity index 100% rename from evm/contracts/forking/README.md rename to evm/contracts/testing/forking/README.md diff --git a/evm/contracts/forking/SimpleSchelling.sol b/evm/contracts/testing/forking/SimpleSchelling.sol similarity index 100% rename from evm/contracts/forking/SimpleSchelling.sol rename to evm/contracts/testing/forking/SimpleSchelling.sol diff --git a/evm/contracts/token-bridge/LayerTransition.sol b/evm/contracts/token-bridge/LayerTransition.sol index 91368c01d..42e51a66a 100644 --- a/evm/contracts/token-bridge/LayerTransition.sol +++ b/evm/contracts/token-bridge/LayerTransition.sol @@ -4,23 +4,35 @@ pragma solidity 0.8.22; import { ITellorFlex } from "../interfaces/ITellorFlex.sol"; import { IERC20 } from "../interfaces/IERC20.sol"; +/// @title LayerTransition. +/// @dev The contract that enables users of really old tellor to keep using it (e.g. Liquity) +/// by forwarding calls to the Ethereum oracle contract +/// also disables all further changes of the oracle address for time based rewards contract LayerTransition { - + /*Storage*/ bytes32 updateOracleQueryId = keccak256(abi.encode("TellorOracleAddress", abi.encode(bytes("")))); IERC20 public token; ITellorFlex public tellorFlex; + /*Functions*/ + /// @notice constructor + /// @param _tellorFlex address of current tellor360 oracle contract + /// @param _token address of the tellor token (tellorMaster) constructor(address _tellorFlex, address _token) { tellorFlex = ITellorFlex(_tellorFlex); token = IERC20(_token); } - // needed for "mintToOracle" function + /// @notice this is needed because it's called when mintingToTeam. We hijack it to keep it in the bridge + /// @param _amount the amount of staking rewards to add to the token contract function addStakingRewards(uint256 _amount) external { token.transferFrom(msg.sender, address(this), _amount); } - // forward to tellor360: + /// @notice This forwards getDataBefore calls to the old tellorFlex + /// we're hijacking it a bit to disable further oracle updates + /// @param _queryId queryId of interest + /// @param _timestamp timestamp you want data to be older than function getDataBefore(bytes32 _queryId, uint256 _timestamp) external view returns( bool _ifRetrieve, bytes memory _value, @@ -32,30 +44,46 @@ contract LayerTransition { return tellorFlex.getDataBefore(_queryId, _timestamp); } + /// @notice This forwards getIndexForDataBefore calls to the old tellorFlex + /// @param _queryId queryId of interest + /// @param _timestamp timestamp you want data for function getIndexForDataBefore(bytes32 _queryId, uint256 _timestamp) external view returns(bool _found, uint256 _index) { return tellorFlex.getIndexForDataBefore(_queryId, _timestamp); } + /// @notice This forwards getNewValueCountbyQueryId calls to the old tellorFlex + /// @param _queryId queryId of interest function getNewValueCountbyQueryId(bytes32 _queryId) external view returns(uint256) { return tellorFlex.getNewValueCountbyQueryId(_queryId); } + /// @notice This forwards getReporterbyTimestamp calls to the old tellorFlex + /// @param _queryId queryId of interest + /// @param _timestamp timestamp you want data for function getReporterByTimestamp(bytes32 _queryId, uint256 _timestamp) external view returns(address) { return tellorFlex.getReporterByTimestamp(_queryId, _timestamp); } + /// @notice This forwards getTimestampbyQueryIdandIndex calls to the old tellorFlex + /// @param _queryId queryId of interest + /// @param _index index you want data for function getTimestampbyQueryIdandIndex(bytes32 _queryId, uint256 _index) external view returns(uint256) { return tellorFlex.getTimestampbyQueryIdandIndex(_queryId, _index); } + /// @notice This forwards getTimeOfLastNewValue calls to the old tellorFlex function getTimeOfLastNewValue() external view returns(uint256) { return tellorFlex.getTimeOfLastNewValue(); } + /// @notice This forwards isInDispute calls to the old tellorFlex + /// @param _queryId queryId of interest + /// @param _timestamp timestamp you want data for function isInDispute(bytes32 _queryId, uint256 _timestamp) external view returns(bool) { return tellorFlex.isInDispute(_queryId, _timestamp); } + /// @notice This returns a big number. Necessary for upgrading the contract function verify() external pure returns (uint256) { return 9999; } diff --git a/evm/contracts/token-bridge/TokenBridge.sol b/evm/contracts/token-bridge/TokenBridge.sol index d693174dc..400a5ca25 100644 --- a/evm/contracts/token-bridge/TokenBridge.sol +++ b/evm/contracts/token-bridge/TokenBridge.sol @@ -4,15 +4,21 @@ pragma solidity ^0.8.22; import "../bridge/BlobstreamO.sol"; import { LayerTransition } from "./LayerTransition.sol"; +/// @title TokenBridge +/// @dev This is the tellor token bridge to move tokens from +/// 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 contract TokenBridge is LayerTransition{ + /*Storage*/ BlobstreamO public bridge; - uint256 public currentDepositLimit; - uint256 public depositId; - uint256 public depositLimitUpdateTime; - uint256 public depositLimitRecord; + uint256 public depositId;//counterOfHowManydeposits have been made + 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 constant MAX_ATTESTATION_AGE = 12 hours; + uint256 public constant MAX_ATTESTATION_AGE = 12 hours;//max of how old an attestation can be for withdrawal from layer uint256 public immutable DEPOSIT_LIMIT_DENOMINATOR = 100e18 / 20e18; // 100/depositLimitPercentage mapping(uint256 => bool) public withdrawalClaimed; @@ -25,14 +31,23 @@ contract TokenBridge is LayerTransition{ uint256 blockHeight; } + /*Events*/ event Deposit(uint256 _depositId, address _sender, string _recipient, uint256 _amount); event Withdrawal(uint256 _depositId, string _sender, address _recipient, uint256 _amount); + /*Functions*/ + /// @notice constructor + /// @param _token address of tellor token for bridging + /// @param _blobstream address of BlobstreamO for data bridge + /// @param _tellorFlex address of oracle(tellorFlex) on chain constructor(address _token, address _blobstream, address _tellorFlex) LayerTransition(_tellorFlex, _token){ bridge = BlobstreamO(_blobstream); _refreshDepositLimit(); } + /// @notice deposits tokens from Ethereum to layer + /// @param _uint256 amount of tokens to bridge over + /// @param _string 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"); @@ -43,6 +58,11 @@ contract TokenBridge is LayerTransition{ emit Deposit(depositId, msg.sender, _layerRecipient, _amount); } + /// @notice This withdraws tokens from layer to mainnet Ethereum + /// @param _attest 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, Validator[] calldata _valset, @@ -64,6 +84,7 @@ contract TokenBridge is LayerTransition{ emit Withdrawal(_depositId, _layerSender, _recipient, _amountConverted); } + /// @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; diff --git a/evm/hardhat.config.js b/evm/hardhat.config.js index 1cbbd5645..448ccaa39 100644 --- a/evm/hardhat.config.js +++ b/evm/hardhat.config.js @@ -53,17 +53,17 @@ module.exports = { ], }, networks: { - // hardhat: { - // 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 - // } //, + hardhat: { + 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 + } //, }, }; diff --git a/temp.json b/temp.json new file mode 100644 index 000000000..e69de29bb From 2a49e863279ac347da3027c2e9f742fe6be6a847 Mon Sep 17 00:00:00 2001 From: Nicholas Fett Date: Fri, 24 May 2024 09:40:23 -0400 Subject: [PATCH 5/8] transisiton testing --- evm/contracts/bridge/BlobstreamO.sol | 2 +- .../testing/forking/SimpleSchelling.sol | 2 +- evm/contracts/token-bridge/TokenBridge.sol | 4 +- .../TokenBridgeTransition-FunctionTests.js | 33 +++-- evm/test/helpers/evmHelpers.js | 118 ++++++++++++++++++ temp.json | 0 6 files changed, 144 insertions(+), 15 deletions(-) create mode 100644 evm/test/helpers/evmHelpers.js delete mode 100644 temp.json diff --git a/evm/contracts/bridge/BlobstreamO.sol b/evm/contracts/bridge/BlobstreamO.sol index 94c28fdbe..c1dbd39bc 100644 --- a/evm/contracts/bridge/BlobstreamO.sol +++ b/evm/contracts/bridge/BlobstreamO.sol @@ -153,7 +153,7 @@ contract BlobstreamO is ECDSA { /*Getter functions*/ /// @notice This getter verifies a given piece of data vs Validator signatures /// @param _attestData The data being verified - /// @param _currentValdatorSet array of current validator set + /// @param _currentValidatorSet array of current validator set /// @param _sigs Signatures. function verifyOracleData( OracleAttestationData calldata _attestData, diff --git a/evm/contracts/testing/forking/SimpleSchelling.sol b/evm/contracts/testing/forking/SimpleSchelling.sol index 760ef1e94..84bdcb711 100644 --- a/evm/contracts/testing/forking/SimpleSchelling.sol +++ b/evm/contracts/testing/forking/SimpleSchelling.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.22; -import {IBridgeProxy} from "../interfaces/IBridgeProxy.sol"; +import {IBridgeProxy} from "../../interfaces/IBridgeProxy.sol"; /** @author Tellor Inc. diff --git a/evm/contracts/token-bridge/TokenBridge.sol b/evm/contracts/token-bridge/TokenBridge.sol index 400a5ca25..e814bfea4 100644 --- a/evm/contracts/token-bridge/TokenBridge.sol +++ b/evm/contracts/token-bridge/TokenBridge.sol @@ -46,8 +46,8 @@ contract TokenBridge is LayerTransition{ } /// @notice deposits tokens from Ethereum to layer - /// @param _uint256 amount of tokens to bridge over - /// @param _string your cosmos address on layer (don't get it wrong!!) + /// @param _amount amount of tokens to bridge over + /// @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"); diff --git a/evm/test/TokenBridgeTransition-FunctionTests.js b/evm/test/TokenBridgeTransition-FunctionTests.js index 2f993d4a7..2fd7a64db 100644 --- a/evm/test/TokenBridgeTransition-FunctionTests.js +++ b/evm/test/TokenBridgeTransition-FunctionTests.js @@ -1,9 +1,6 @@ // const { AbiCoder } = require("@ethersproject/abi"); const { expect } = require("chai"); -const h = require("./helpers/helpers"); -var assert = require('assert'); -const web3 = require('web3'); -const { hre, ethers } = require("hardhat"); +const h = require("./helpers/evmHelpers"); describe("Function Tests - NewTransition", function() { @@ -54,16 +51,16 @@ describe("Function Tests - NewTransition", function() { parachute = await ethers.getContractAt("contracts/tellor360/oldContracts/contracts/interfaces/ITellor.sol:ITellor",PARACHUTE, devWallet); // get blobstream initial params - valTs = await h.getValsetTimestampByIndex(0) - valParams = await h.getValsetCheckpointParams(valTs) - valSet = await h.getValset(valParams.timestamp) - // deploy blobstream + // valTs = await h.getValsetTimestampByIndex(0) + // valParams = await h.getValsetCheckpointParams(valTs) + // valSet = await h.getValset(valParams.timestamp) + // deploy blobstream (no need for actual queries) blobstream = await ethers.deployContract( "BlobstreamO", [ - valParams.powerThreshold, - valParams.timestamp, + 1, + 2, UNBONDING_PERIOD, - valParams.checkpoint, + h.hash("testy"), DEV_WALLET ] ) @@ -97,6 +94,11 @@ describe("Function Tests - NewTransition", function() { this.timeout(100000) }); + it("constructor", async function() { + // check if new oracle address is set + expect(await tbridge.token() == tellor.getAddress(), "tellor should be set") + expect(await tbridge.tellorFlex() == flex.getAddress(), "tellor should be set") + }) it("transition worked", async function() { // check if new oracle address is set expect(await tellor.getAddressVars(h.hash("_ORACLE_CONTRACT"))).to.equal(await tbridge.getAddress()) @@ -197,6 +199,15 @@ describe("Function Tests - NewTransition", function() { expect(await tellor.balanceOf(await tbridge.getAddress())).to.equal(0) await tellor.mintToOracle() expect(await tellor.balanceOf(await tbridge.getAddress())).to.be.greaterThan(0) + + }) + it("mintToTeam()", async function () { + expect(await tellor.balanceOf(await tbridge.getAddress())).to.equal(0) + await tellor.mintToOracle() + expect(await tellor.balanceOf(await tbridge.getAddress())).to.be.greaterThan(0) + let teamBal = await tellor.balanceOf(DEV_WALLET) + await tellor.mintToTeam() + expect(await tellor.balanceOf(DEV_WALLET) > teamBal, "mint to team should work") }) }) \ No newline at end of file diff --git a/evm/test/helpers/evmHelpers.js b/evm/test/helpers/evmHelpers.js new file mode 100644 index 000000000..57f66ce19 --- /dev/null +++ b/evm/test/helpers/evmHelpers.js @@ -0,0 +1,118 @@ +const web3 = require('web3'); +const { ethers, network } = require("hardhat"); +const hash = web3.utils.keccak256; +var assert = require('assert'); +const { impersonateAccount, takeSnapshot } = require("@nomicfoundation/hardhat-network-helpers"); + + +advanceTimeAndBlock = async (time) => { + await advanceTime(time); + await advanceBlock(); + console.log("Time Travelling..."); + return Promise.resolve(web3.eth.getBlock("latest")); +}; + +const takeFifteen = async () => { + await advanceTime(60 * 18); +}; + +advanceTime = async (time) => { + await network.provider.send("evm_increaseTime", [time]) + await network.provider.send("evm_mine") +} + +advanceBlock = () => { + return new Promise((resolve, reject) => { + web3.currentProvider.send( + { + jsonrpc: "2.0", + method: "evm_mine", + id: new Date().getTime(), + }, + (err, result) => { + if (err) { + return reject(err); + } + const newBlockHash = web3.eth.getBlock("latest").hash; + + return resolve(newBlockHash); + } + ); + }); +}; + +async function expectThrow(promise) { + try { + await promise; + } catch (error) { + const invalidOpcode = error.message.search("invalid opcode") >= 0; + const outOfGas = error.message.search("out of gas") >= 0; + const revert = error.message.search("revert") >= 0; + assert( + invalidOpcode || outOfGas || revert, + "Expected throw, got '" + error + "' instead" + ); + return; + } + assert.fail("Expected throw not received"); +} + +function to18(n) { + return ethers.BigNumber.from(n).mul(ethers.BigNumber.from(10).pow(18)) +} + +function tob32(n) { + return ethers.utils.formatBytes32String(n) +} + +function uintTob32(n) { + let vars = web3.utils.toHex(n) + vars = vars.slice(2) + while (vars.length < 64) { + vars = "0" + vars + } + vars = "0x" + vars + return vars +} + +function bytes(n) { + return web3.utils.toHex(n) +} + +function getBlock() { + return ethers.provider.getBlock() +} + +function toWei(n) { + return web3.utils.toWei(n, "ether") +} + +function fromWei(n) { + return web3.utils.fromWei(n) +} + +function sleep(s) { + return new Promise(resolve => setTimeout(resolve, s * 1000)); +} + +module.exports = { + timeTarget: 240, + hash, + zeroAddress: "0x0000000000000000000000000000000000000000", + to18, + uintTob32, + tob32, + bytes, + getBlock, + advanceTime, + advanceBlock, + advanceTimeAndBlock, + takeFifteen, + toWei, + fromWei, + expectThrow, + sleep, + takeSnapshot, + impersonateAccount, +}; + diff --git a/temp.json b/temp.json deleted file mode 100644 index e69de29bb..000000000 From a24f7f68116a3794ada5e3257954ae55a71845d7 Mon Sep 17 00:00:00 2001 From: Nicholas Fett Date: Tue, 28 May 2024 14:19:34 -0400 Subject: [PATCH 6/8] removing trues --- evm/contracts/bridge/BlobstreamO.sol | 3 +-- evm/contracts/bridge/mock/BridgeCaller.sol | 2 +- evm/contracts/testing/UsingTellor.sol | 25 +++++++++------------- evm/contracts/token-bridge/TokenBridge.sol | 3 +-- 4 files changed, 13 insertions(+), 20 deletions(-) diff --git a/evm/contracts/bridge/BlobstreamO.sol b/evm/contracts/bridge/BlobstreamO.sol index c1dbd39bc..b6eeb3164 100644 --- a/evm/contracts/bridge/BlobstreamO.sol +++ b/evm/contracts/bridge/BlobstreamO.sol @@ -159,7 +159,7 @@ contract BlobstreamO is ECDSA { OracleAttestationData calldata _attestData, Validator[] calldata _currentValidatorSet, Signature[] calldata _sigs - ) external view returns (bool) { + ) external view{ if (_currentValidatorSet.length != _sigs.length) { revert MalformedCurrentValidatorSet(); } @@ -183,7 +183,6 @@ contract BlobstreamO is ECDSA { _dataDigest, powerThreshold ); - return true; } diff --git a/evm/contracts/bridge/mock/BridgeCaller.sol b/evm/contracts/bridge/mock/BridgeCaller.sol index 25e418149..83384eed7 100644 --- a/evm/contracts/bridge/mock/BridgeCaller.sol +++ b/evm/contracts/bridge/mock/BridgeCaller.sol @@ -17,7 +17,7 @@ contract BridgeCaller { Validator[] calldata _currentValidatorSet, Signature[] calldata _sigs ) public { - require(bridge.verifyOracleData(_attest, _currentValidatorSet, _sigs)); + bridge.verifyOracleData(_attest, _currentValidatorSet, _sigs); oracleData = _attest.report.value; oracleDataTimestamp = _attest.report.timestamp; } diff --git a/evm/contracts/testing/UsingTellor.sol b/evm/contracts/testing/UsingTellor.sol index c6fac0d89..205b54123 100644 --- a/evm/contracts/testing/UsingTellor.sol +++ b/evm/contracts/testing/UsingTellor.sol @@ -17,8 +17,8 @@ contract UsingTellor { uint256 _fallbackTimestamp, uint256 _fallbackMinimumPower, // or use percentage? uint256 _maxAttestationAge - ) public view returns(bool){ - require(bridge.verifyOracleData(_attest, _currentValidatorSet, _sigs), "Invalid signature"); + ) public view{ + bridge.verifyOracleData(_attest, _currentValidatorSet, _sigs); require(block.timestamp - _attest.attestationTimestamp <= _maxAttestationAge, "Attestation is too old"); if(_attest.report.aggregatePower >= bridge.powerThreshold()) { require(_attest.report.timestamp < _fallbackTimestamp, "Report timestamp must be before _fallbackTimestamp"); @@ -27,7 +27,6 @@ contract UsingTellor { require(_attest.report.aggregatePower >= _fallbackMinimumPower, "Report aggregate power must be greater than or equal to _minimumPower"); require(_attest.report.nextTimestamp == 0 || _attest.report.nextTimestamp < _fallbackTimestamp, "Report is latest after fallback timestamp"); require(_attest.report.nextTimestamp == 0 || _attest.report.nextTimestamp > _fallbackTimestamp, "Report is latest before fallback timestamp"); - return true; } function isAnyConsensusValue( @@ -35,11 +34,10 @@ contract UsingTellor { Validator[] calldata _currentValidatorSet, Signature[] calldata _sigs, uint256 _maxAttestationAge - ) public view returns(bool) { - require(bridge.verifyOracleData(_attest, _currentValidatorSet, _sigs), "Invalid attestation"); + ) public view{ + bridge.verifyOracleData(_attest, _currentValidatorSet, _sigs); require(_attest.report.aggregatePower >= bridge.powerThreshold(), "Report aggregate power must be greater than or equal to _minimumPower"); require(block.timestamp - _attest.attestationTimestamp <= _maxAttestationAge, "Attestation is too old"); - return true; } function isCurrentConsensusValue( @@ -47,12 +45,11 @@ contract UsingTellor { Validator[] calldata _currentValidatorSet, Signature[] calldata _sigs, uint256 _maxAttestationAge - ) public view returns(bool) { - require(bridge.verifyOracleData(_attest, _currentValidatorSet, _sigs), "Invalid attestation"); + ) public view { + bridge.verifyOracleData(_attest, _currentValidatorSet, _sigs); require(_attest.report.aggregatePower >= bridge.powerThreshold(), "Report aggregate power must be greater than or equal to _minimumPower"); require(block.timestamp - _attest.attestationTimestamp <= _maxAttestationAge, "Attestation is too old"); require(_attest.report.nextTimestamp == 0, "Report is not latest"); - return true; } function isValidDataAfter( @@ -63,14 +60,13 @@ contract UsingTellor { uint256 _maxAge, uint256 _minimumPower, uint256 _maxAttestationAge - ) public view returns(bool){ + ) public view{ require(block.timestamp - _attest.attestationTimestamp <= _maxAttestationAge, "Attestation is too old"); require(_attest.report.timestamp > _timestampAfter, "Report timestamp must be after _timestamp"); require(_attest.report.nextTimestamp == 0 || _attest.report.nextTimestamp > _timestampAfter, "Report is latest before timestamp"); require(_attest.report.aggregatePower >= _minimumPower, "Report aggregate power must be greater than or equal to _minimumPower"); require(block.timestamp - _attest.report.timestamp < _maxAge, "Report is too old"); - require(bridge.verifyOracleData(_attest, _currentValidatorSet, _sigs), "Invalid signature"); - return true; + bridge.verifyOracleData(_attest, _currentValidatorSet, _sigs); } function isValidDataBefore( @@ -81,13 +77,12 @@ contract UsingTellor { uint256 _maxReportAge, uint256 _minimumPower, uint256 _maxAttestationAge - ) public view returns(bool){ + ) public view{ require(block.timestamp - _attest.attestationTimestamp <= _maxAttestationAge, "Attestation is too old"); require(_attest.report.timestamp < _timestampBefore, "Report timestamp must be before _timestampBefore"); require(_attest.report.nextTimestamp == 0 || _attest.report.nextTimestamp > _timestampBefore, "Report is latest before timestamp"); require(_attest.report.aggregatePower >= _minimumPower, "Report aggregate power must be greater than or equal to _minimumPower"); require(block.timestamp - _attest.report.timestamp < _maxReportAge, "Report is too old"); - require(bridge.verifyOracleData(_attest, _currentValidatorSet, _sigs), "Invalid signature"); - return true; + bridge.verifyOracleData(_attest, _currentValidatorSet, _sigs); } } \ No newline at end of file diff --git a/evm/contracts/token-bridge/TokenBridge.sol b/evm/contracts/token-bridge/TokenBridge.sol index e814bfea4..ce2ed58cd 100644 --- a/evm/contracts/token-bridge/TokenBridge.sol +++ b/evm/contracts/token-bridge/TokenBridge.sol @@ -73,10 +73,9 @@ contract TokenBridge is LayerTransition{ require(!withdrawalClaimed[_depositId], "TokenBridge: withdrawal already claimed"); require(block.timestamp - _attest.report.timestamp > 12 hours, "TokenBridge: premature attestation"); //isAnyConsesnusValue here - require(bridge.verifyOracleData(_attest, _valset, _sigs), "Invalid attestation"); + bridge.verifyOracleData(_attest, _valset, _sigs); require(block.timestamp - _attest.attestationTimestamp <= MAX_ATTESTATION_AGE , "Attestation is too old"); require(_attest.report.aggregatePower >= bridge.powerThreshold(), "Report aggregate power must be greater than or equal to _minimumPower"); - //to here withdrawalClaimed[_depositId] = true; (address _recipient, string memory _layerSender,uint256 _amountLoya) = abi.decode(_attest.report.value, (address, string, uint256)); uint256 _amountConverted = _amountLoya * 1e12; From 12ec8ec0cb9092537cd85f41382a3758a62ea1d4 Mon Sep 17 00:00:00 2001 From: Nicholas Fett Date: Tue, 28 May 2024 14:20:50 -0400 Subject: [PATCH 7/8] remove max attestation age --- evm/contracts/token-bridge/TokenBridge.sol | 2 -- 1 file changed, 2 deletions(-) diff --git a/evm/contracts/token-bridge/TokenBridge.sol b/evm/contracts/token-bridge/TokenBridge.sol index ce2ed58cd..d933c063f 100644 --- a/evm/contracts/token-bridge/TokenBridge.sol +++ b/evm/contracts/token-bridge/TokenBridge.sol @@ -18,7 +18,6 @@ contract TokenBridge is LayerTransition{ 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 constant MAX_ATTESTATION_AGE = 12 hours;//max of how old an attestation can be for withdrawal from layer uint256 public immutable DEPOSIT_LIMIT_DENOMINATOR = 100e18 / 20e18; // 100/depositLimitPercentage mapping(uint256 => bool) public withdrawalClaimed; @@ -74,7 +73,6 @@ contract TokenBridge is LayerTransition{ require(block.timestamp - _attest.report.timestamp > 12 hours, "TokenBridge: premature attestation"); //isAnyConsesnusValue here bridge.verifyOracleData(_attest, _valset, _sigs); - require(block.timestamp - _attest.attestationTimestamp <= MAX_ATTESTATION_AGE , "Attestation is too old"); require(_attest.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)); From dca609640c018363c21ce946573f15131238e5b6 Mon Sep 17 00:00:00 2001 From: Nicholas Fett Date: Thu, 30 May 2024 14:46:13 -0400 Subject: [PATCH 8/8] hh tests --- evm/contracts/bridge/BlobstreamO.sol | 61 ++-- evm/contracts/testing/TestTokenBridge.sol | 17 + evm/test/BlobstreamOFunctionTestsHH.js | 369 ++++++++++++++++++++++ evm/test/TokenBridgeFunctionTestsHH.js | 149 +++++++++ evm/test/helpers/evmHelpers.js | 120 +++++++ 5 files changed, 674 insertions(+), 42 deletions(-) create mode 100644 evm/contracts/testing/TestTokenBridge.sol create mode 100644 evm/test/BlobstreamOFunctionTestsHH.js create mode 100644 evm/test/TokenBridgeFunctionTestsHH.js diff --git a/evm/contracts/bridge/BlobstreamO.sol b/evm/contracts/bridge/BlobstreamO.sol index b6eeb3164..e8b8fd486 100644 --- a/evm/contracts/bridge/BlobstreamO.sol +++ b/evm/contracts/bridge/BlobstreamO.sol @@ -3,6 +3,7 @@ pragma solidity 0.8.22; import "./ECDSA.sol"; import "./Constants.sol"; +//import "hardhat/console.sol"; struct OracleAttestationData { bytes32 queryId; @@ -116,9 +117,7 @@ contract BlobstreamO is ECDSA { revert MalformedCurrentValidatorSet(); } // Check that the supplied current validator set matches the saved checkpoint. - bytes32 _currentValidatorSetHash = _computeValidatorSetHash( - _currentValidatorSet - ); + bytes32 _currentValidatorSetHash = keccak256(abi.encode(_currentValidatorSet)); if ( _domainSeparateValidatorSetHash( powerThreshold, @@ -149,7 +148,7 @@ contract BlobstreamO is ECDSA { _newValidatorSetHash ); } - + /*Getter functions*/ /// @notice This getter verifies a given piece of data vs Validator signatures /// @param _attestData The data being verified @@ -164,9 +163,7 @@ contract BlobstreamO is ECDSA { revert MalformedCurrentValidatorSet(); } // Check that the supplied current validator set matches the saved checkpoint. - bytes32 _currentValidatorSetHash = _computeValidatorSetHash( - _currentValidatorSet - ); + bytes32 _currentValidatorSetHash = keccak256(abi.encode(_currentValidatorSet)); if ( _domainSeparateValidatorSetHash( powerThreshold, @@ -176,7 +173,19 @@ contract BlobstreamO is ECDSA { ) { revert SuppliedValidatorSetInvalid(); } - bytes32 _dataDigest = _domainSeparateOracleAttestationData(_attestData); + bytes32 _dataDigest = keccak256( + abi.encode( + NEW_REPORT_ATTESTATION_DOMAIN_SEPARATOR, + _attestData.queryId, + _attestData.report.value, + _attestData.report.timestamp, + _attestData.report.aggregatePower, + _attestData.report.previousTimestamp, + _attestData.report.nextTimestamp, + lastValidatorSetCheckpoint, + _attestData.attestationTimestamp + ) + ); _checkValidatorSignatures( _currentValidatorSet, _sigs, @@ -185,7 +194,6 @@ contract BlobstreamO is ECDSA { ); } - /*Internal functions*/ /// @dev Checks that enough voting power signed over a digest. /// It expects the signatures to be in the same order as the _currentValidators. @@ -224,37 +232,6 @@ contract BlobstreamO is ECDSA { } } - /// @dev Computes the hash of a validator set. - /// @param _validators The validator set to hash. - /// @return The hash of the validator set. - function _computeValidatorSetHash( - Validator[] calldata _validators - ) internal pure returns (bytes32) { - return keccak256(abi.encode(_validators)); - } - - /// @dev A hash of all relevant information about the oracle attestation. - /// @param _attest The oracle attestation. - /// @return The domain separated hash of the oracle attestation. - function _domainSeparateOracleAttestationData( - OracleAttestationData calldata _attest - ) internal view returns (bytes32) { - return - keccak256( - abi.encode( - NEW_REPORT_ATTESTATION_DOMAIN_SEPARATOR, - _attest.queryId, - _attest.report.value, - _attest.report.timestamp, - _attest.report.aggregatePower, - _attest.report.previousTimestamp, - _attest.report.nextTimestamp, - lastValidatorSetCheckpoint, - _attest.attestationTimestamp - ) - ); - } - /// @dev A hash of all relevant information about the validator set. /// @param _powerThreshold Amount of voting power needed to approve operations. (2/3 of total) /// @param _validatorTimestamp The timestamp of the block where validator set is updated. @@ -285,7 +262,7 @@ contract BlobstreamO is ECDSA { bytes32 _digest, Signature calldata _sig ) internal pure returns (bool) { - bytes32 _digestSha256 = sha256(abi.encodePacked(_digest)); - return _signer == ECDSA.recover(_digestSha256, _sig.v, _sig.r, _sig.s); + _digest = keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", _digest)); + return _signer == ecrecover(_digest, _sig.v, _sig.r, _sig.s); } } diff --git a/evm/contracts/testing/TestTokenBridge.sol b/evm/contracts/testing/TestTokenBridge.sol new file mode 100644 index 000000000..e8cc5cc50 --- /dev/null +++ b/evm/contracts/testing/TestTokenBridge.sol @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.22; + +import { TokenBridge } from "../token-bridge/TokenBridge.sol"; + +/// @title TokenBridge +/// @dev allows us to test deposit limit externally +contract TestTokenBridge is TokenBridge{ + + constructor(address _token, address _blobstream, address _tellorFlex) TokenBridge(_token, _blobstream, _tellorFlex){ + } + + /// @notice refreshes the deposit limit every 12 hours so no one can spam layer with new tokens + function depositLimit() external returns (uint256) { + return _refreshDepositLimit(); + } +} diff --git a/evm/test/BlobstreamOFunctionTestsHH.js b/evm/test/BlobstreamOFunctionTestsHH.js new file mode 100644 index 000000000..31ff4e89c --- /dev/null +++ b/evm/test/BlobstreamOFunctionTestsHH.js @@ -0,0 +1,369 @@ +const { expect } = require("chai"); +const { ethers } = require("hardhat"); +const h = require("./helpers/evmHelpers"); +var assert = require('assert'); +const abiCoder = new ethers.AbiCoder(); + + +describe("Blobstream - Function Tests", async function () { + + let blobstream, accounts, guardian, initialPowers, initialValAddrs; + const UNBONDING_PERIOD = 86400 * 7 * 3; // 3 weeks + + beforeEach(async function () { + // init accounts + accounts = await ethers.getSigners(); + guardian = accounts[10] + val1 = await accounts[1].getAddress(); + val2 = await accounts[2].getAddress() + initialValAddrs = [val1,val2] + initialPowers = [1, 2] + threshold = 2 + blocky = await h.getBlock() + valTimestamp = blocky.timestamp - 2 + newValHash = await h.calculateValHash(initialValAddrs, initialPowers) + valCheckpoint = h.calculateValCheckpoint(newValHash, threshold, valTimestamp) + // deploy contracts + blobstream = await ethers.deployContract( + "BlobstreamO", [ + threshold, + valTimestamp, + UNBONDING_PERIOD, + valCheckpoint, + guardian.getAddress() + ] + ) + }); + it("constructor", async function () { + assert.equal(await blobstream.powerThreshold(), threshold) + assert.equal(await blobstream.validatorTimestamp(), valTimestamp) + assert.equal(await blobstream.unbondingPeriod(), UNBONDING_PERIOD) + assert.equal(await blobstream.lastValidatorSetCheckpoint(), valCheckpoint) + assert.equal(await blobstream.guardian(), await guardian.getAddress()) + }) + it("guardianResetValidatorSet", async function () { + newValAddrs = [accounts[1].address, accounts[2].address, accounts[3].address] + newPowers = [1, 2, 3] + newThreshold = 4 + newValHash = await h.calculateValHash(newValAddrs, newPowers) + blocky = await h.getBlock() + newValTimestamp = blocky.timestamp - 1 + newValCheckpoint = h.calculateValCheckpoint(newValHash,newThreshold, newValTimestamp) + await h.expectThrow(blobstream.connect(guardian).guardianResetValidatorSet(newThreshold, newValTimestamp, newValCheckpoint)); + await h.advanceTime(UNBONDING_PERIOD + 1) + await h.expectThrow(blobstream.guardianResetValidatorSet(newThreshold, newValTimestamp, newValCheckpoint));//not guardian + await blobstream.connect(guardian).guardianResetValidatorSet(newThreshold, newValTimestamp, newValCheckpoint); + }) + it("updateValidatorSet", async function () { + newValAddrs = [await accounts[1].getAddress(), await accounts[2].getAddress(), await accounts[3].getAddress()] + newPowers = [1, 2, 3] + newThreshold = 4 + newValHash = await h.calculateValHash(newValAddrs, newPowers) + blocky = await h.getBlock() + newValTimestamp = blocky.timestamp - 1 + newValCheckpoint = await h.calculateValCheckpoint(newValHash,newThreshold, newValTimestamp) + currentValSetArray = await h.getValSetStructArray(initialValAddrs, initialPowers) + sig1 = await accounts[1].signMessage(ethers.toBeArray(newValCheckpoint)) + sig2 = await accounts[2].signMessage(ethers.toBeArray(newValCheckpoint)) + sig3 = await accounts[3].signMessage(ethers.toBeArray(newValCheckpoint)) + sigStructArray = await h.getSigStructArray([sig1, sig2]) + badSigStructArray = await h.getSigStructArray([sig3, sig2]) + insufStructArray = await h.getSigStructArray([sig1,0]) + await h.expectThrow(blobstream.updateValidatorSet(newValHash, newThreshold, newValTimestamp, currentValSetArray, insufStructArray)); + //invalid signatures + await h.expectThrow(blobstream.updateValidatorSet(newValHash, newThreshold, newValTimestamp, currentValSetArray, badSigStructArray)); + await blobstream.updateValidatorSet(newValHash, newThreshold, newValTimestamp, currentValSetArray, sigStructArray); + + newValAddrs = [accounts[1].address] + newPowers = [6] + newThreshold = 5 + newValHash = await h.calculateValHash(newValAddrs, newPowers) + blocky = await h.getBlock() + newValTimestamp = blocky.timestamp - 1 + newValCheckpoint = h.calculateValCheckpoint(newValHash, newThreshold, newValTimestamp) + sig1 = await accounts[1].signMessage(ethers.toBeArray(newValCheckpoint)) + sig2 = await accounts[2].signMessage(ethers.toBeArray(newValCheckpoint)) + sigStructArray = await h.getSigStructArray([sig1, sig2]) + //stale validator set + await h.expectThrow(blobstream.updateValidatorSet(newValHash, newThreshold, newValTimestamp, currentValSetArray, sigStructArray)); + + }) + it("verifyOracleData", async function () { + queryId = h.hash("myquery") + value = abiCoder.encode(["uint256"], [2000]) + blocky = await h.getBlock() + timestamp = blocky.timestamp - 2 + aggregatePower = 3 + attestTimestamp = timestamp + 1 + previousTimestamp = 0 + nextTimestamp = 0 + newValHash = await h.calculateValHash(initialValAddrs, initialPowers) + valCheckpoint = await h.calculateValCheckpoint(newValHash, threshold, valTimestamp) + dataDigest = await h.getDataDigest( + queryId, + value, + timestamp, + aggregatePower, + previousTimestamp, + nextTimestamp, + valCheckpoint, + attestTimestamp + ) + currentValSetArray = await h.getValSetStructArray(initialValAddrs, initialPowers) + sig1 = await accounts[1].signMessage(ethers.getBytes(dataDigest)) + sig2 = await accounts[2].signMessage(ethers.getBytes(dataDigest)) + sigStructArray = await h.getSigStructArray([sig1, sig2]) + oracleDataStruct = await h.getOracleDataStruct( + queryId, + value, + timestamp, + aggregatePower, + previousTimestamp, + nextTimestamp, + attestTimestamp + ) + await blobstream.verifyOracleData( + oracleDataStruct, + currentValSetArray, + sigStructArray + ) + }) + //more complex + it("updateValidatorSet twice", async function () { + newValAddrs = [accounts[1].address, accounts[2].address, accounts[3].address] + newPowers = [1, 2, 3] + newThreshold = 4 + newValHash = await h.calculateValHash(newValAddrs, newPowers) + blocky = await h.getBlock() + newValTimestamp = blocky.timestamp - 1 + newValCheckpoint = h.calculateValCheckpoint(newValHash, newThreshold, newValTimestamp) + currentValSetArray = await h.getValSetStructArray(initialValAddrs, initialPowers) + sig1 = await accounts[1].signMessage(ethers.getBytes(newValCheckpoint)) + sig2 = await accounts[2].signMessage(ethers.getBytes(newValCheckpoint)) + sigStructArray = await h.getSigStructArray([sig1, sig2]) + await blobstream.updateValidatorSet(newValHash, newThreshold, newValTimestamp, currentValSetArray, sigStructArray); + newValAddrs2 = [accounts[4].address, accounts[5].address, accounts[6].address, accounts[7].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) + currentValSetArray2 = await h.getValSetStructArray(newValAddrs, newPowers) + sig1 = await accounts[1].signMessage(ethers.getBytes(newValCheckpoint2)) + sig2 = await accounts[2].signMessage(ethers.getBytes(newValCheckpoint2)) + sig3 = await accounts[3].signMessage(ethers.getBytes(newValCheckpoint2)) + sigStructArray2 = await h.getSigStructArray([sig1, sig2, sig3]) + await blobstream.updateValidatorSet(newValHash2, newThreshold2, newValTimestamp2, currentValSetArray2, sigStructArray2); + + }) + it("alternating validator set updates and verify oracle data", async function () { + queryId1 = h.hash("eth-usd") + value1 = abiCoder.encode(["uint256"], [2000]) + blocky = await h.getBlock() + timestamp1 = blocky.timestamp - 2 // report timestamp + aggregatePower1 = 3 + attestTimestamp1 = timestamp1 + 1 + previousTimestamp1 = 0 + nextTimestamp1 = 0 + newValHash = await h.calculateValHash(initialValAddrs, initialPowers) + valCheckpoint1 = h.calculateValCheckpoint(newValHash, threshold, valTimestamp) + dataDigest1 = await h.getDataDigest( + queryId1, + value1, + timestamp1, + aggregatePower1, + previousTimestamp1, + nextTimestamp1, + valCheckpoint1, + attestTimestamp1 + ) + currentValSetArray1 = await h.getValSetStructArray(initialValAddrs, initialPowers) + sig1 = await accounts[1].signMessage(ethers.getBytes(dataDigest1)) + sig2 = await accounts[2].signMessage(ethers.getBytes(dataDigest1)) + sigStructArray1 = await h.getSigStructArray([sig1, sig2]) + oracleDataStruct1 = await h.getOracleDataStruct( + queryId1, + value1, + timestamp1, + aggregatePower1, + previousTimestamp1, + nextTimestamp1, + attestTimestamp1 + ) + await blobstream.verifyOracleData( + oracleDataStruct1, + currentValSetArray1, + sigStructArray1 + ) + // update validator set + newValAddrs = [accounts[1].address, accounts[2].address, accounts[3].address] + newPowers = [1, 2, 3] + newThreshold = 4 + newValHash = await h.calculateValHash(newValAddrs, newPowers) + blocky = await h.getBlock() + newValTimestamp = blocky.timestamp - 1 + newValCheckpoint = h.calculateValCheckpoint(newValHash,newThreshold, newValTimestamp) + sig1 = await accounts[1].signMessage(ethers.getBytes(newValCheckpoint)) + sig2 = await accounts[2].signMessage(ethers.getBytes(newValCheckpoint)) + sigStructArray = await h.getSigStructArray([sig1, sig2]) + await blobstream.updateValidatorSet(newValHash, newThreshold, newValTimestamp, currentValSetArray1, sigStructArray); + + // verify oracle data + value2 = abiCoder.encode(["uint256"], [3000]) + blocky = await h.getBlock() + timestamp2 = blocky.timestamp - 2 + aggregatePower2 = 6 + attestTimestamp2 = timestamp2 + 1 + previousTimestamp2 = timestamp1 + nextTimestamp2 = 0 + valCheckpoint2 = newValCheckpoint + dataDigest2 = await h.getDataDigest( + queryId1, + value2, + timestamp2, + aggregatePower2, + previousTimestamp2, + nextTimestamp2, + valCheckpoint2, + attestTimestamp2 + ) + currentValSetArray2 = await h.getValSetStructArray(newValAddrs, newPowers) + sig1 = await accounts[1].signMessage(ethers.getBytes(dataDigest2)) + sig2 = await accounts[2].signMessage(ethers.getBytes(dataDigest2)) + sig3 = await accounts[3].signMessage(ethers.getBytes(dataDigest2)) + sigStructArray2 = await h.getSigStructArray([sig1, sig2, sig3]) + oracleDataStruct2 = await h.getOracleDataStruct( + queryId1, + value2, + timestamp2, + aggregatePower2, + previousTimestamp2, + nextTimestamp2, + attestTimestamp2 + ) + await blobstream.verifyOracleData( + oracleDataStruct2, + currentValSetArray2, + sigStructArray2 + ) + // update validator set + newValAddrs2 = [accounts[4].address, accounts[5].address, accounts[6].address, accounts[7].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) + sig1 = await accounts[1].signMessage(ethers.getBytes(newValCheckpoint2)) + sig2 = await accounts[2].signMessage(ethers.getBytes(newValCheckpoint2)) + sig3 = await accounts[3].signMessage(ethers.getBytes(newValCheckpoint2)) + sigStructArray2 = await h.getSigStructArray([sig1, sig2, sig3]) + await blobstream.updateValidatorSet(newValHash2, newThreshold2, newValTimestamp2, currentValSetArray2, sigStructArray2); + + // verify oracle data + value3 = abiCoder.encode(["uint256"], [4000]) + blocky = await h.getBlock() + timestamp3 = blocky.timestamp - 2 + aggregatePower3 = 22 + attestTimestamp3 = timestamp3 + 1 + previousTimestamp3 = timestamp2 + nextTimestamp3 = 0 + valCheckpoint3 = newValCheckpoint2 + + dataDigest3 = await h.getDataDigest( + queryId1, + value3, + timestamp3, + aggregatePower3, + previousTimestamp3, + nextTimestamp3, + valCheckpoint3, + attestTimestamp3 + ) + + currentValSetArray3 = await h.getValSetStructArray(newValAddrs2, newPowers2) + sig1 = await accounts[4].signMessage(ethers.getBytes(dataDigest3)) + sig2 = await accounts[5].signMessage(ethers.getBytes(dataDigest3)) + sig3 = await accounts[6].signMessage(ethers.getBytes(dataDigest3)) + sig4 = await accounts[7].signMessage(ethers.getBytes(dataDigest3)) + sigStructArray3 = await h.getSigStructArray([sig1, sig2, sig3, sig4]) + oracleDataStruct3 = await h.getOracleDataStruct( + queryId1, + value3, + timestamp3, + aggregatePower3, + previousTimestamp3, + nextTimestamp3, + attestTimestamp3 + ) + await blobstream.verifyOracleData( + oracleDataStruct3, + currentValSetArray3, + sigStructArray3 + ) + }) + it("update validator set to 100+ validators", async function () { + nVals = 158 + let wallets = [] + for (i = 0; i < nVals; i++) { + wallets.push(await ethers.Wallet.createRandom()) + } + newValAddrs = [] + newValPowers = [] + for (i = 0; i < nVals; i++) { + newValAddrs.push(wallets[i].address) + newValPowers.push(1) + } + newValHash = await h.calculateValHash(newValAddrs, newValPowers) + + newThreshold = Math.ceil(nVals * 2 / 3) + blocky = await h.getBlock() + newValTimestamp = blocky.timestamp - 1 + newValCheckpoint = h.calculateValCheckpoint(newValHash, newThreshold, newValTimestamp) + currentValSetArray = await h.getValSetStructArray(initialValAddrs, initialPowers) + sig1 = await accounts[1].signMessage(ethers.getBytes(newValCheckpoint)) + sig2 = await accounts[2].signMessage(ethers.getBytes(newValCheckpoint)) + sigStructArray = await h.getSigStructArray([sig1, sig2]) + await blobstream.updateValidatorSet(newValHash, newThreshold, newValTimestamp, currentValSetArray, sigStructArray); + // verify oracle data + queryId1 = h.hash("eth-usd") + value1 = abiCoder.encode(["uint256"], [2000]) + blocky = await h.getBlock() + timestamp1 = blocky.timestamp - 2 // report timestamp + aggregatePower1 = 3 + attestTimestamp1 = timestamp1 + 1 + previousTimestamp1 = 0 + nextTimestamp1 = 0 + dataDigest1 = await h.getDataDigest( + queryId1, + value1, + timestamp1, + aggregatePower1, + previousTimestamp1, + nextTimestamp1, + newValCheckpoint, + attestTimestamp1 + ) + currentValSetArray1 = await h.getValSetStructArray(newValAddrs, newValPowers) + sigs = [] + for (i = 0; i < nVals; i++) { + sigs.push(await wallets[i].signMessage(ethers.getBytes(dataDigest1))) + } + sigStructArray1 = await h.getSigStructArray(sigs) + oracleDataStruct1 = await h.getOracleDataStruct( + queryId1, + value1, + timestamp1, + aggregatePower1, + previousTimestamp1, + nextTimestamp1, + attestTimestamp1 + ) + await blobstream.verifyOracleData( + oracleDataStruct1, + currentValSetArray1, + sigStructArray1 + ) + }) + }) diff --git a/evm/test/TokenBridgeFunctionTestsHH.js b/evm/test/TokenBridgeFunctionTestsHH.js new file mode 100644 index 000000000..657d1de23 --- /dev/null +++ b/evm/test/TokenBridgeFunctionTestsHH.js @@ -0,0 +1,149 @@ +const { expect } = require("chai"); +const { ethers } = require("hardhat"); +const h = require("./helpers/evmHelpers"); +var assert = require('assert'); +const abiCoder = new ethers.AbiCoder(); + + +describe("TokenBridge - Function Tests", async function () { + + let blobstream, accounts, guardian, tbridge, token, blocky0, + valTs, valParams, valSet; + 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" + + beforeEach(async function () { + // init accounts + accounts = await ethers.getSigners(); + guardian = accounts[10] + val1 = await accounts[1].getAddress(); + val2 = await accounts[2].getAddress() + initialValAddrs = [val1,val2] + initialPowers = [1, 2] + threshold = 2 + blocky = await h.getBlock() + valTimestamp = blocky.timestamp - 2 + newValHash = await h.calculateValHash(initialValAddrs, initialPowers) + valCheckpoint = h.calculateValCheckpoint(newValHash, threshold, valTimestamp) + // deploy contracts + blobstream = await ethers.deployContract( + "BlobstreamO", [ + threshold, + valTimestamp, + UNBONDING_PERIOD, + valCheckpoint, + guardian.getAddress() + ] + ) + token = await ethers.deployContract("TellorPlayground") + oldOracle = await ethers.deployContract("TellorPlayground") + tbridge = await ethers.deployContract("TestTokenBridge", [token.getAddress(),blobstream.getAddress(), oldOracle.getAddress()]) + blocky0 = await h.getBlock() + // fund accounts + await token.faucet(accounts[0].getAddress()) + }); + + it("constructor", async function () { + assert.equal(await tbridge.token(), await token.getAddress()) + assert.equal(await tbridge.bridge(), await blobstream.getAddress()) + 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") + await h.expectThrow(tbridge.depositToLayer(depositAmount, LAYER_RECIPIENT)) // not approved + await token.approve(await tbridge.getAddress(), h.toWei("100")) + await tbridge.depositToLayer(depositAmount, LAYER_RECIPIENT) + value = h.getWithdrawValue(EVM_RECIPIENT,LAYER_RECIPIENT,20) + blocky = await h.getBlock() + timestamp = blocky.timestamp - 2 + aggregatePower = 3 + attestTimestamp = timestamp + 1 + previousTimestamp = 0 + nextTimestamp = 0 + newValHash = await h.calculateValHash(initialValAddrs, initialPowers) + valCheckpoint = await h.calculateValCheckpoint(newValHash, threshold, valTimestamp) + dataDigest = await h.getDataDigest( + WITHDRAW1_QUERY_ID, + value, + timestamp, + aggregatePower, + previousTimestamp, + nextTimestamp, + valCheckpoint, + attestTimestamp + ) + currentValSetArray = await h.getValSetStructArray(initialValAddrs, initialPowers) + sig1 = await accounts[1].signMessage(ethers.getBytes(dataDigest)) + sig2 = await accounts[2].signMessage(ethers.getBytes(dataDigest)) + sigStructArray = await h.getSigStructArray([sig1, sig2]) + oracleDataStruct = await h.getOracleDataStruct( + WITHDRAW1_QUERY_ID, + value, + timestamp, + aggregatePower, + previousTimestamp, + nextTimestamp, + attestTimestamp + ) + await h.advanceTime(43200) + await tbridge.withdrawFromLayer( + oracleDataStruct, + currentValSetArray, + sigStructArray, + 1, + ) + recipientBal = await token.balanceOf(EVM_RECIPIENT) + expectedBal = 20e12 // 20 loya + assert.equal(recipientBal.toString(), expectedBal) + }) + it("depositToLayer", async function () { + depositAmount = h.toWei("1") + assert.equal(await token.balanceOf(await accounts[0].getAddress()), h.toWei("1000")) + await h.expectThrow(tbridge.depositToLayer(depositAmount, LAYER_RECIPIENT)) // not approved + await token.approve(await tbridge.getAddress(), h.toWei("900")) + await h.expectThrow(tbridge.depositToLayer(0, LAYER_RECIPIENT)) // zero amount + await h.expectThrow(tbridge.depositToLayer(h.toWei("21"), LAYER_RECIPIENT)) // over limit + await tbridge.depositToLayer(depositAmount, LAYER_RECIPIENT) + blocky1 = await h.getBlock() + tbridgeBal = await token.balanceOf(await tbridge.getAddress()) + assert.equal(tbridgeBal.toString(), h.toWei("1")) + userBal = await token.balanceOf(await accounts[0].getAddress()) + assert.equal(userBal.toString(), h.toWei("999")) + expectedDepositLimit = BigInt(100e18) * BigInt(2) / BigInt(10) - BigInt(depositAmount) + assert.equal(BigInt(await tbridge.depositLimitRecord()), expectedDepositLimit); + await tbridge.depositLimit() + assert.equal(BigInt(await tbridge.depositLimitRecord()), expectedDepositLimit); + assert.equal(await tbridge.depositId(), 1) + depositDetails = await tbridge.deposits(1) + assert.equal(depositDetails.amount.toString(), depositAmount) + assert.equal(depositDetails.recipient, LAYER_RECIPIENT) + assert.equal(depositDetails.sender, await accounts[0].getAddress()) + assert.equal(depositDetails.blockHeight, blocky1.number) + assert.equal(await tbridge.depositId(), 1) + await h.advanceTime(43200) + expectedDepositLimit2 = (BigInt(100e18) + BigInt(depositAmount)) * BigInt(2) / BigInt(10) + await tbridge.depositLimit() + assert.equal(BigInt(await tbridge.depositLimitRecord()), expectedDepositLimit2); + }) + it("depositLimit", async function () { + expectedDepositLimit = BigInt(100e18) * BigInt(2) / BigInt(10) + await tbridge.depositLimit() + assert.equal(BigInt(await tbridge.depositLimitRecord()), expectedDepositLimit); + await token.approve(await tbridge.getAddress(), h.toWei("900")) + depositAmount = h.toWei("2") + await tbridge.depositToLayer(depositAmount, LAYER_RECIPIENT) + expectedDepositLimit = BigInt(100e18) * BigInt(2) / BigInt(10) - BigInt(depositAmount) + await tbridge.depositLimit() + assert.equal(BigInt(await tbridge.depositLimitRecord()), expectedDepositLimit); + await h.advanceTime(43200) + expectedDepositLimit2 = (BigInt(100e18) + BigInt(depositAmount)) / BigInt(5) + await tbridge.depositLimit() + assert.equal(BigInt(await tbridge.depositLimitRecord()), expectedDepositLimit2); + }) +}) diff --git a/evm/test/helpers/evmHelpers.js b/evm/test/helpers/evmHelpers.js index 57f66ce19..804072e7b 100644 --- a/evm/test/helpers/evmHelpers.js +++ b/evm/test/helpers/evmHelpers.js @@ -4,6 +4,8 @@ const hash = web3.utils.keccak256; var assert = require('assert'); const { impersonateAccount, takeSnapshot } = require("@nomicfoundation/hardhat-network-helpers"); +var assert = require('assert'); +const abiCoder = new ethers.AbiCoder(); advanceTimeAndBlock = async (time) => { await advanceTime(time); @@ -95,7 +97,125 @@ function sleep(s) { return new Promise(resolve => setTimeout(resolve, s * 1000)); } + +calculateValCheckpoint = (valHash, threshold, valTimestamp) => { + domainSeparator = "0x636865636b706f696e7400000000000000000000000000000000000000000000" + valCheckpoint = ethers.solidityPackedKeccak256(["bytes32", "uint256", "uint256", "bytes32"], [domainSeparator, threshold, valTimestamp, valHash]) + //valCheckpoint = ethers.solidityPackedKeccak256(enc) + return valCheckpoint +} + +calculateValHash = (valSet, powers) => { + structArray = [] + for (i = 0; i < valSet.length; i++) { + structArray[i] = { + addr: valSet[i], + power: powers[i] + } + } + // encode the array of Validator struct objects into bytes so they can be hashed + enc = abiCoder.encode(["tuple(address addr, uint256 power)[]"], [structArray]) + // hash the encoded bytes + valHash = hash(enc) + return valHash +} + +getEthSignedMessageHash = (messageHash) => { + const prefix = "\x19Ethereum Signed Message:\n32"; + const messageHashBytes = ethers.getBytes(messageHash); + const prefixBytes = ethers.getBytes(prefix); + const combined = ethers.utils.concat([prefixBytes, messageHashBytes]); + const digest = ethers.utils.keccak256(combined); + return digest; +} + +getDataDigest = (queryId, value, timestamp, aggregatePower, previousTimestamp, nextTimestamp, valCheckpoint, blockTimestamp) => { + 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]) + return hash(enc) +} + + +getValSetStructArray = (valAddrs, powers) => { + structArray = [] + for (i = 0; i < valAddrs.length; i++) { + structArray[i] = { + addr: valAddrs[i], + power: powers[i] + } + } + return structArray +} + +getSigStructArray = (sigs) => { + structArray = [] + for (i = 0; i < sigs.length; i++) { + if(sigs[i] == 0){ + structArray[i] = { + v: abiCoder.encode(["uint8"], [0]), + r: abiCoder.encode(["bytes32"], ['0x0000000000000000000000000000000000000000000000000000000000000000']), + s: abiCoder.encode(["bytes32"], ['0x0000000000000000000000000000000000000000000000000000000000000000']) + } + } + else{ + let { v, r, s } = ethers.Signature.from(sigs[i]) + structArray[i] = { + v: abiCoder.encode(["uint8"], [v]), + r: abiCoder.encode(["bytes32"], [r]), + s: abiCoder.encode(["bytes32"], [s]) + } + } + } + return structArray +} + +getOracleDataStruct = (queryId, value, timestamp, aggregatePower, previousTimestamp, nextTimestamp, attestTimestamp) => { + return { + queryId: queryId, + report: { + value: value, + timestamp: timestamp, + aggregatePower: aggregatePower, + previousTimestamp: previousTimestamp, + nextTimestamp: nextTimestamp + }, + attestationTimestamp: attestTimestamp + } +} + +getWithdrawValue = (_recipient, _sender, _amount) =>{ + myVal = abiCoder.encode(["address", "string", "uint256"], + [_recipient, _sender, _amount]) + return myVal +} + +getCurrentAggregateReport = (_queryId, _value, _timestamp,_reporterPower) => { + reportData = { + value: _value, + timestamp: _timestamp, + aggregatePower: _reporterPower, + previousTimestamp: 0, + nextTimestamp: 0 + } + oracleAttestationData = { + queryId: _queryId, + report: reportData, + attestTimestamp: _timestamp + } + return oracleAttestationData +} + module.exports = { + getWithdrawValue, + getCurrentAggregateReport, + getOracleDataStruct, + getSigStructArray, + getValSetStructArray, + getDataDigest, + getEthSignedMessageHash, + calculateValCheckpoint, + calculateValHash, timeTarget: 240, hash, zeroAddress: "0x0000000000000000000000000000000000000000",